From ae245e11df83b82359eb7c995ee220220051520c Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Fri, 3 Jul 2026 02:58:53 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85Agent=E7=A7=81=E6=9C=89?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E5=86=99=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增单Agent对话面板私有记忆追加入口 补充Tauri本地Agent记忆写入命令和策略测试 同步AI游戏创作App技术方案和共享决策记录 --- .../src-tauri/src/main.rs | 84 ++++++++++++ apps/ai-game-creator-shell/src/App.tsx | 122 +++++++++++++++++- .../tests/appSurface.test.ts | 32 ++++- .../shared-memory/decision-log.md | 1 + ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 6 +- 5 files changed, 239 insertions(+), 6 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/main.rs b/apps/ai-game-creator-shell/src-tauri/src/main.rs index c1c368916..c16c2dc70 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/main.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/main.rs @@ -1590,6 +1590,18 @@ fn read_local_agent_memory( read_local_agent_memory_at(root, task_id.trim()) } +#[tauri::command] +fn write_local_agent_memory( + project_path: String, + task_id: String, + content: String, +) -> Result { + let root = Path::new(project_path.trim()); + enforce_project_permission_policy(root, "memory.write")?; + let _lock = acquire_project_write_lock(root, "memory.write")?; + write_local_agent_memory_at(root, task_id.trim(), &content) +} + #[tauri::command] fn write_local_game_memory( project_path: String, @@ -7044,6 +7056,27 @@ fn read_local_agent_memory_at( } } +fn write_local_agent_memory_at( + root: &Path, + task_id: &str, + content: &str, +) -> Result { + let relative_path = agent_role_memory_relative_path_for_task(task_id)?; + let path = resolve_local_project_path(root, &relative_path)?; + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|error| format!("创建 Agent 记忆目录失败:{}: {error}", parent.display()))?; + } + fs::write(&path, content) + .map_err(|error| format!("写入 Agent 记忆失败:{}: {error}", path.display()))?; + Ok(LocalAgentMemoryResult { + task_id: task_id.to_string(), + path: path.to_string_lossy().into_owned(), + content: content.to_string(), + exists: true, + }) +} + fn write_local_game_memory_at( root: &Path, scope: &str, @@ -8689,6 +8722,7 @@ fn main() { delete_local_project_file, read_local_game_memory, read_local_agent_memory, + write_local_agent_memory, write_local_game_memory, delete_local_game_memory, read_local_conversation, @@ -11648,6 +11682,30 @@ mod tests { fs::remove_dir_all(root).ok(); } + #[test] + fn local_agent_memory_writes_private_memory_by_task_id() { + let root = unique_project_path(); + + let written = write_local_agent_memory_at( + &root, + "design-director", + "# 策划 Director 私有记忆\n- 保留轻量像素风\n", + ) + .expect("write agent memory"); + assert_eq!(written.task_id, "design-director"); + assert!(written.path.ends_with("memory/agents/design/director.md")); + assert!(written.exists); + assert_eq!( + written.content, + "# 策划 Director 私有记忆\n- 保留轻量像素风\n" + ); + + let read = read_local_agent_memory_at(&root, "design-director").expect("read agent memory"); + assert_eq!(read.content, "# 策划 Director 私有记忆\n- 保留轻量像素风\n"); + + fs::remove_dir_all(root).ok(); + } + #[test] fn local_memory_reads_respect_project_policy() { let root = unique_project_path(); @@ -11677,6 +11735,32 @@ mod tests { fs::remove_dir_all(root).ok(); } + #[test] + fn local_agent_memory_writes_respect_project_policy() { + let root = unique_project_path(); + init_local_game_project_at(&root, "project-1", "像素动作原型").expect("project init"); + write_project_permission_policy_at( + &root, + ProjectPermissionPolicy { + denied_commands: vec!["memory.write".to_string()], + confirm_commands: Vec::new(), + }, + ) + .expect("write policy"); + + let error = write_local_agent_memory( + root.to_string_lossy().into_owned(), + "design-director".to_string(), + "denied memory".to_string(), + ) + .expect_err("agent memory write denied"); + + assert!(error.contains("项目权限策略拒绝执行:memory.write")); + assert!(!root.join("memory/agents/design/director.md").exists()); + + fs::remove_dir_all(root).ok(); + } + #[test] fn local_game_memory_rejects_unknown_scope() { let root = unique_project_path(); diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 81fd57140..7fdb259bd 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -2867,6 +2867,8 @@ function isProjectPolicyConfirmableCommandId(value: string) { 'file.list', 'file.read', 'memory.read', + 'memory.write', + 'memory.delete', 'asset.register', 'asset.list', 'task.list', @@ -4111,6 +4113,14 @@ export function App() { ]); } } + if (pending.commandId === 'memory.write') { + setMemoryStatus((current) => + current === '等待确认' ? '已取消保存项目记忆' : current, + ); + setAgentMemoryStatus((current) => + current === '等待确认' ? '已取消保存 Agent 私有记忆' : current, + ); + } if ( pending.commandId === 'file.list' || pending.commandId === 'file.read' || @@ -4852,6 +4862,100 @@ export function App() { } } + async function saveSelectedAgentPrivateMemory( + agent: AgentStatusCard, + content: string, + skipPolicyConfirm = false, + ) { + if (!agent || !content || agentConversationSavingRef.current) { + return; + } + const invoke = resolveTauriInvoke(); + const nextProjectPath = resolveChatProjectPath(localProject); + if (!invoke) { + setAgentMemoryStatus('需要在 Tauri App 内运行'); + return; + } + if (!nextProjectPath) { + setAgentMemoryStatus('请先初始化本地项目'); + return; + } + const saveVersion = agentConversationLoadVersionRef.current; + try { + if (!skipPolicyConfirm) { + const policyView = await invoke( + 'read_project_permission_policy', + { projectPath: nextProjectPath }, + ); + if (policyView.policy.deniedCommands.includes('memory.write')) { + const message = '项目权限策略拒绝执行:memory.write'; + markProjectPolicyDenied('memory.write', message); + setAgentMemoryStatus(message); + setCommandLog((current) => [ + ...current, + 'permission.deny memory.write', + ]); + setMessages((current) => [ + ...current, + { role: 'assistant', text: message }, + ]); + return; + } + if (policyView.policy.confirmCommands.includes('memory.write')) { + requestProjectPolicyConfirmation( + 'memory.write', + nextProjectPath, + `写入 ${agent.title} Agent 私有记忆`, + () => void saveSelectedAgentPrivateMemory(agent, content, true), + ); + setAgentMemoryStatus('等待确认'); + setMessages((current) => [ + ...current, + { role: 'assistant', text: '准备保存 Agent 私有记忆。' }, + ]); + return; + } + } + } catch (error) { + setAgentMemoryStatus( + error instanceof Error ? error.message : String(error), + ); + return; + } + agentConversationSavingRef.current = true; + setAgentConversationSaving(true); + setAgentConversationInput(''); + setAgentMemoryStatus('正在保存'); + try { + const result = await invoke( + 'write_local_agent_memory', + { + projectPath: nextProjectPath, + taskId: agent.id, + content: appendMemoryContent(agentMemoryContent, content), + }, + ); + if (agentConversationLoadVersionRef.current !== saveVersion) { + return; + } + setAgentMemoryContent(result.content); + setAgentMemoryStatus(`已追加私有记忆:${result.path}`); + setAgentConversationStatus(`已写入 ${agent.title} 私有记忆。`); + setCommandLog((current) => [...current, 'memory.agent.write']); + } catch (error) { + if (agentConversationLoadVersionRef.current !== saveVersion) { + return; + } + setAgentConversationInput(content); + setAgentMemoryStatus( + error instanceof Error ? error.message : String(error), + ); + } finally { + agentConversationSavingRef.current = false; + setAgentConversationSaving(false); + } + } + function handleAgentConversationSubmit(event: FormEvent) { event.preventDefault(); const agent = selectedAgent; @@ -4862,6 +4966,15 @@ export function App() { void saveAgentConversationMessage(agent, content); } + function handleAgentPrivateMemorySubmit() { + const agent = selectedAgent; + const content = agentConversationInput.trim(); + if (!agent || !content || agentConversationSavingRef.current) { + return; + } + void saveSelectedAgentPrivateMemory(agent, content); + } + function showChatHelp() { setCommandLog((current) => [...current, 'help.show']); setMessages((current) => [ @@ -5070,7 +5183,7 @@ export function App() { ...current, { role: 'assistant', - text: '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、file.list、file.read、memory.read、asset.register、asset.list、task.list、agent.run_status、agent.kill、agent.retry、agent.resume、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', + text: '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、agent.run_status、agent.kill、agent.retry、agent.resume、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', }, ]); return; @@ -10362,6 +10475,13 @@ export function App() { +

{agentConversationStatus}

diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index 6b5610f62..29f56ef8f 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -4519,6 +4519,7 @@ describe('AI 游戏创作 App 界面边界', () => { updatedAt: number; }> = []; let agentConversationReadCount = 0; + let agentMemoryContent = '# 策划 Director 私有记忆\n- 保留轻量像素风\n'; const invoke = vi.fn( async (command: string, args?: Record) => { if (command === 'append_local_permission_log') { @@ -4549,7 +4550,16 @@ describe('AI 游戏创作 App 界面边界', () => { return { taskId: args?.taskId, path: '/tmp/authorized-game/memory/agents/design/director.md', - content: '# 策划 Director 私有记忆\n- 保留轻量像素风\n', + content: agentMemoryContent, + exists: true, + }; + } + if (command === 'write_local_agent_memory') { + agentMemoryContent = String(args?.content ?? ''); + return { + taskId: args?.taskId, + path: '/tmp/authorized-game/memory/agents/design/director.md', + content: agentMemoryContent, exists: true, }; } @@ -4733,6 +4743,24 @@ describe('AI 游戏创作 App 界面边界', () => { agentId: null, }, }); + fireEvent.change(screen.getByLabelText('Agent 对话内容'), { + target: { value: '稳定结论:锅铲音效要跟随连击节奏' }, + }); + fireEvent.click( + within(agentDialog).getByRole('button', { name: '记入记忆' }), + ); + expect( + await screen.findByText('已写入 拆解创作方向 私有记忆。'), + ).not.toBeNull(); + expect(screen.getByLabelText('Agent 私有记忆').textContent).toContain( + '锅铲音效要跟随连击节奏', + ); + expect(invoke).toHaveBeenCalledWith('write_local_agent_memory', { + projectPath: '/tmp/authorized-game', + taskId: 'design-director', + content: + '# 策划 Director 私有记忆\n- 保留轻量像素风\n- 稳定结论:锅铲音效要跟随连击节奏\n', + }); agentMessages.push({ schemaVersion: 'game-creator-conversation.v1', role: 'assistant', @@ -10080,7 +10108,7 @@ describe('AI 游戏创作 App 界面边界', () => { expect( await screen.findByText( - '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、file.list、file.read、memory.read、asset.register、asset.list、task.list、agent.run_status、agent.kill、agent.retry、agent.resume、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', + '当前仅支持确认 project.index、project.status、project.checkpoint、project.diff、project.restore、file.list、file.read、memory.read、memory.write、memory.delete、asset.register、asset.list、task.list、agent.run_status、agent.kill、agent.retry、agent.resume、agent.audit、agent.trace_read、preview.status、preview.start、preview.open、preview.stop、canvas.project_sync、canvas.asset_import、canvas.asset_generate、canvas.export_import、conversation.read 和 conversation.write。', ), ).not.toBeNull(); expect(screen.queryByText('project.policy_write')).toBeNull(); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 23b1ad5dc..c4923eb5e 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3858,6 +3858,7 @@ - 2026-07-03 调整:主窗口 header 常驻项目摘要只从当前已加载的 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令结果;未选择工作区时不显示,不为了摘要额外触发 Tauri 读取或写入,也不把任务、文件、run history 或预览开发面板搬进普通用户窗口。 - 2026-07-03 调整:`/llm-status` 读取到的 agent 级 LLM 配置状态可回填到主窗口 Agent 状态列表和单 Agent 对话头部,显示 provider 类型、模型、流式开关和 API Key 是否已读取;密钥本体仍不能进入聊天、状态列表、manifest、trace 或本地项目文件。 - 2026-07-03 调整:开发窗口日志面板提供 `.agent/logs/command.log`、`.agent/logs/preview.log` 和 `.agent/logs/agent.log` 的只读查看入口,复用 `file.read` 授权策略;普通用户窗口仍只通过聊天 `/read` 和摘要消息查看需要的日志,不新增日志面板。 +- 2026-07-03 调整:单 Agent 对话面板允许用户把当前输入手动追加到该 agent 的 `memory/agents//.md` 私有记忆;写入复用 `memory.write` 项目策略、项目锁和 Tauri 本地目录能力,不把普通对话流水自动混入私有记忆。 - 2026-06-25 调整:本地 HTTP 预览静态 `HEAD` 必须返回与 `GET` 相同的真实 `Content-Length`,但不返回 body;浏览器、图片、音频和视频探测不能拿到 `Content-Length: 0` 的假响应。 - 2026-06-25 调整:普通用户通过聊天输入 `/run` 触发待确认 `game.run_local`,确认后只能复用白名单 `game.static_smoke` 自检当前 `game/index.html`,通过后启动 `127.0.0.1` 本地 HTTP 预览。独立执行 `game.static_smoke` 时如果已有 `.agent/run.latest.json`,必须追加 `Playtest / game.static_smoke` trace step,避免“运行了代码但编排 trace 不可见”。 - 2026-06-25 调整:普通用户通过聊天输入 `/trace` 触发只读 `agent.trace_read`,读取 `.agent/run.latest.json` 并在聊天里摘要 loop 轮次、stopReason、nextStep、active / carry-over 任务、repairRoutes、agent 建议命令和最近 step。trace 面板仍只在开发窗口展示,普通用户窗口不新增面板。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index 130e4c0c1..8688ea403 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -111,7 +111,7 @@ game-project/ ## v1 验收证据矩阵 -- `npm run ai-game-creator-shell:check`:覆盖壳 typecheck、聊天命令单测、用户 / 开发窗口 UI 边界 smoke、主窗口命令按钮复用 `/help` 命令列表、主窗口项目摘要从 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令且未选项目时不显示、灵感草稿只填充输入框不提交、能力按钮复用 `/capabilities`、LLM状态按钮复用 `/llm-status` 且结果回填 Agent 状态列表和单 Agent 对话的 provider / 模型 / 流式 / API Key 读取状态、开发日志面板只读读取 `.agent/logs/command.log` / `preview.log` / `agent.log`、项目状态按钮复用 `/status`、权限按钮复用 `/policy` 且策略草稿按钮只填入 `/policy-confirm preview.start` / `/policy-confirm agent.run_status`、审计按钮复用 `/audit`、资产按钮复用 `/assets` 且资产结果可一键复用 `/read`、任务按钮复用 `/tasks`、Trace 按钮复用 `/trace`、文件按钮复用 `/files` 且文件结果可一键复用 `/read`、索引按钮复用 `/index`、记忆 / 短期记忆 / 黑板按钮复用 `/memory long|short|blackboard`、快照按钮复用 `/checkpoint`、快照列表按钮复用 `/checkpoints` 且 checkpoint 结果可一键复用 `/diff` / `/restore`、历史按钮复用 `/history`、受限命令白名单按钮复用 `/commands` 且无需项目初始化、静态自检快捷按钮复用 `/smoke`、预览状态快捷按钮复用 `/preview-status`、主窗口运行时配置面板读写 Tauri 配置目录中的 `game-creator.config.json`、支持全局与每个 agent 单独选择 LLM Provider 且不把 API Key 写入聊天、聊天侧 `/capabilities` 展示标准 Agent 能力清单且不打开开发面板、聊天侧 `/audit` 从 manifest / 本地文件 / `.agent/run.latest.json` 分别汇总用户面、6 组任务配置、6 组协作证据、任务编排、loop、记忆、本地产物、HTTP 预览、画板回流和权限日志证据且不打开开发面板;未生成 `.agent/run.latest.json` 前,`/audit` 只能标记任务配置通过,不能把 6 组协作证据误判为通过;trace 已存在但状态为 `failed`、`needs-revision`、`running`、`max-passes-exhausted` 或缺少 `Evaluator passed` 步骤时,`/audit` 不能把 loop 误判为通过。聊天侧 `/llm-status` 只显示 base_url / model / API Key 已读取状态且不泄露密钥本体、聊天侧长期记忆查看 / 追加 / 覆盖 / 删除的授权本地项目路径、上传资产写入后的 manifest 刷新和 `/assets` 聊天可见性、`/smoke` 聊天侧确认后只通过授权本地项目路径执行白名单 `game.static_smoke`、`/run` 聊天侧确认后通过授权本地项目路径执行 `game.static_smoke`、启动 `127.0.0.1` 本地预览并交给外部浏览器、`/preview` 聊天侧确认后通过授权本地项目路径启动 `127.0.0.1` 本地预览并交给外部浏览器、`/status` 聊天侧项目 / 任务 / 资产 / 预览 / 最近命令汇总、`/files` 聊天侧本地项目文件列表、`/read` 聊天侧文件读取的授权本地项目路径、`/tasks` 聊天侧任务拆分与下一步专业组展示的授权本地项目路径、聊天确认生成后实时展示 Planner / Orchestrator / 角色 brief / Generator / Evaluator / 写盘 / 自检进度,并自动读取 `.agent/run.latest.json` 在普通聊天消息里展示 Run、LLM 对话、loop 轮次、工具调用、active / carry-over 任务、返工焦点、编排轮次、最近步骤、画板同步建议命令和本地产物快照、`/trace` 聊天侧读取 `.agent/run.latest.json` 并展示 loop 轮次 / active 任务 / 返工路线 / dependency waves 的授权本地项目路径、`platform-agent` 编排测试、共享契约测试、Tauri 本地能力测试和无密钥本地 provider 端到端 smoke;用于证明独立 App、真实 LLM-compatible loop、本地落盘、自检和 HTTP 预览闭环,并覆盖 loop 跑满 3 轮失败时不会写入最终游戏产物。 +- `npm run ai-game-creator-shell:check`:覆盖壳 typecheck、聊天命令单测、用户 / 开发窗口 UI 边界 smoke、主窗口命令按钮复用 `/help` 命令列表、主窗口项目摘要从 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令且未选项目时不显示、灵感草稿只填充输入框不提交、能力按钮复用 `/capabilities`、LLM状态按钮复用 `/llm-status` 且结果回填 Agent 状态列表和单 Agent 对话的 provider / 模型 / 流式 / API Key 读取状态、开发日志面板只读读取 `.agent/logs/command.log` / `preview.log` / `agent.log`、项目状态按钮复用 `/status`、权限按钮复用 `/policy` 且策略草稿按钮只填入 `/policy-confirm preview.start` / `/policy-confirm agent.run_status`、审计按钮复用 `/audit`、资产按钮复用 `/assets` 且资产结果可一键复用 `/read`、任务按钮复用 `/tasks`、Trace 按钮复用 `/trace`、文件按钮复用 `/files` 且文件结果可一键复用 `/read`、索引按钮复用 `/index`、记忆 / 短期记忆 / 黑板按钮复用 `/memory long|short|blackboard`、快照按钮复用 `/checkpoint`、快照列表按钮复用 `/checkpoints` 且 checkpoint 结果可一键复用 `/diff` / `/restore`、历史按钮复用 `/history`、受限命令白名单按钮复用 `/commands` 且无需项目初始化、静态自检快捷按钮复用 `/smoke`、预览状态快捷按钮复用 `/preview-status`、主窗口运行时配置面板读写 Tauri 配置目录中的 `game-creator.config.json`、支持全局与每个 agent 单独选择 LLM Provider 且不把 API Key 写入聊天、单 Agent 对话面板可手动追加私有记忆且走 `memory.write` 策略、聊天侧 `/capabilities` 展示标准 Agent 能力清单且不打开开发面板、聊天侧 `/audit` 从 manifest / 本地文件 / `.agent/run.latest.json` 分别汇总用户面、6 组任务配置、6 组协作证据、任务编排、loop、记忆、本地产物、HTTP 预览、画板回流和权限日志证据且不打开开发面板;未生成 `.agent/run.latest.json` 前,`/audit` 只能标记任务配置通过,不能把 6 组协作证据误判为通过;trace 已存在但状态为 `failed`、`needs-revision`、`running`、`max-passes-exhausted` 或缺少 `Evaluator passed` 步骤时,`/audit` 不能把 loop 误判为通过。聊天侧 `/llm-status` 只显示 base_url / model / API Key 已读取状态且不泄露密钥本体、聊天侧长期记忆查看 / 追加 / 覆盖 / 删除的授权本地项目路径、上传资产写入后的 manifest 刷新和 `/assets` 聊天可见性、`/smoke` 聊天侧确认后只通过授权本地项目路径执行白名单 `game.static_smoke`、`/run` 聊天侧确认后通过授权本地项目路径执行 `game.static_smoke`、启动 `127.0.0.1` 本地预览并交给外部浏览器、`/preview` 聊天侧确认后通过授权本地项目路径启动 `127.0.0.1` 本地预览并交给外部浏览器、`/status` 聊天侧项目 / 任务 / 资产 / 预览 / 最近命令汇总、`/files` 聊天侧本地项目文件列表、`/read` 聊天侧文件读取的授权本地项目路径、`/tasks` 聊天侧任务拆分与下一步专业组展示的授权本地项目路径、聊天确认生成后实时展示 Planner / Orchestrator / 角色 brief / Generator / Evaluator / 写盘 / 自检进度,并自动读取 `.agent/run.latest.json` 在普通聊天消息里展示 Run、LLM 对话、loop 轮次、工具调用、active / carry-over 任务、返工焦点、编排轮次、最近步骤、画板同步建议命令和本地产物快照、`/trace` 聊天侧读取 `.agent/run.latest.json` 并展示 loop 轮次 / active 任务 / 返工路线 / dependency waves 的授权本地项目路径、`platform-agent` 编排测试、共享契约测试、Tauri 本地能力测试和无密钥本地 provider 端到端 smoke;用于证明独立 App、真实 LLM-compatible loop、本地落盘、自检和 HTTP 预览闭环,并覆盖 loop 跑满 3 轮失败时不会写入最终游戏产物。 - 主窗口策略快捷入口只填入 `/policy-confirm preview.start`、`/policy-confirm agent.run_status`、`/policy-confirm conversation.read` 或 `/policy-confirm conversation.write` 草稿;Agent 状态栏的“继续说明”只填入 `/agent-resume ` 草稿,不直接触发 run 生命周期写入。 - 主窗口最近 checkpoint 列表展示 checkpoint id、文件数、大小和创建时间,同时提供直接对比、填入 `/diff`、确认回滚和填入 `/restore`;回滚继续走 `project.restore` 确认卡,不直接写项目文件。 - `npm run check:native-shells`:覆盖 AI 游戏创作壳的 release/dev 窗口边界、正式用户 App 不嵌入游戏预览 iframe、用户侧预览命令交给外部浏览器和 Tauri release `--no-bundle` 构建 smoke;用于证明正式发布只登记 `launcher` 启动器窗口,选择工作区后才关闭启动器并打开 `main` 主窗口,开发面板只在 debug/dev 路径打开,独立壳能完成 release 编译。 @@ -144,9 +144,9 @@ game-project/ - loop 每次运行会写 `.agent/run.latest.json` 和 `.agent/runs/.json`,记录 `Planner` / `Orchestrator` agenda / 16 个组内角色 brief 或 carry-over / 6 个 `GroupCoordinator` 汇总 / `Generator` / 6 个专业组交接 / `Evaluator` 质量评审 / `ArtifactWriter` / `Playtest` step、每步 `toolCalls`、输入文件、输出文件、状态、轮次、maxPasses、toolCallCount、maxToolCalls、stopReason、nextStep 和错误摘要;Planner、角色 agent 和 Generator 的 `inputPaths` 必须包含对应记忆文件、`.agent/conversations/project.jsonl`、`.agent/conversations/agents/`、`.agent/manifest.json` 和 agenda 等上下文来源,其中角色 agent 必须包含自己的 `memory/agents//.md` 和 `memory/blackboard.md`;conversation 输入只取最近少量 project / agent 对话摘要,不读取全量历史;每个 step 必须带 phase、taskId、group 和 role,`.agent/run.latest.json.taskGraph` 必须记录 goal、readyTaskIds、activeTaskIds、carriedTaskIds、repairFocus、repairRoutes 和当前任务状态;`.agent/run.latest.json.passPlans` 必须按轮记录 mode、summary、activeTaskIds、carriedTaskIds、dependencyWaves、repairFocus 和 repairRoutes,作为 `/trace` 与开发窗口判断编排 loop 是否真实发生的直接证据;`run.latest.json` 是当前指针,`.agent/runs/` 保留历史 run trace;开发窗口读取 latest 展示编排过程,并复用 `file.list/read` 按文件修改时间先载入最近 20 个历史 run,滚动时再按批次读取剩余历史,普通用户窗口不展示。 - `.agent/run.latest.json` 的 `artifacts` 使用结构化记录,包含相对路径、字节数和 `fnv1a64:` checksum;除最终本地产物外,也会收集 `.agent/passes/pass-N/` 快照,便于确认返工前后的产物差异。 - 通过 Evaluator 和 `game.static_smoke` 后,Agent loop 会把本次 runId、状态、轮次、下一步、active / carry-over 任务和最终本地产物摘要追加到 `memory/session.md` 与 `memory/project.md`,把重要跨 agent 决策 / 依赖 / 风险摘要追加到 `memory/blackboard.md`,并把各角色本轮成功产出的角色摘要追加到 `memory/agents//.md`;下一次 Planner、组内角色和 Generator 会通过记忆输入自然读取上一轮稳定原型状态,而不只依赖开发窗口 trace。 -- 单 agent 对话入口读取对应 agent conversation;用户提交后先追加用户消息,再追加一条本地 agent 回执,二者都写入对应 `.agent/conversations/agents/.jsonl`。最近对话可作为本次生成 prompt 上下文读取,但只有经过显式总结或生成 loop 成功沉淀的稳定结论,才追加到 `memory/blackboard.md` 或 `memory/agents//.md`;v1 不把本地回执伪装成实时 LLM 回复。 +- 单 agent 对话入口读取对应 agent conversation;用户提交后先追加用户消息,再追加一条本地 agent 回执,二者都写入对应 `.agent/conversations/agents/.jsonl`。单 agent 面板可把当前输入手动追加到对应 `memory/agents//.md`,写入前复用 `memory.write` 项目策略和本地项目锁;最近对话可作为本次生成 prompt 上下文读取,但只有经过显式总结、用户显式手动沉淀或生成 loop 成功沉淀的稳定结论,才追加到 `memory/blackboard.md` 或 `memory/agents//.md`;v1 不把本地回执伪装成实时 LLM 回复。 - `.agent/agent.db` 当前作为最小本地项目索引文件使用 JSONL:初始化写入 `project.init`,每次 `game.generate_draft` 追加目标、标题、本地产物路径、checkpoint 和 diff 摘要,上传 / 登记 / 画板导入资产时追加 `asset.register` 或 `asset.update`;v1 不引入 SQLite 依赖。 -- `game.generate_draft`、资产登记 / 导入、记忆写入、预览状态写入、checkpoint / restore、agent 生命周期控制、画板资源回流 / 生成和 policy 写入会先按 `.agent/policy.json` 判断本次命令是否被项目策略拒绝,再拿项目级 `.agent/project.lock` 串行化;锁只保护同一本地项目,v1 不做后台锁管理。`confirmCommands` 可把索引、状态读取、资产登记、checkpoint、预览、agent 生命周期、画板资源回流 / 生成、conversation 读写等命令转成项目策略确认,命中时用户确认后才执行;用户可用 `/policy-confirm 命令` 加入确认列表,用 `/policy-auto 命令` 移除确认项。 +- `game.generate_draft`、资产登记 / 导入、记忆写入、预览状态写入、checkpoint / restore、agent 生命周期控制、画板资源回流 / 生成和 policy 写入会先按 `.agent/policy.json` 判断本次命令是否被项目策略拒绝,再拿项目级 `.agent/project.lock` 串行化;锁只保护同一本地项目,v1 不做后台锁管理。`confirmCommands` 可把索引、状态读取、资产登记、checkpoint、预览、agent 生命周期、画板资源回流 / 生成、memory 读写删除和 conversation 读写等命令转成项目策略确认,命中时用户确认后才执行;用户可用 `/policy-confirm 命令` 加入确认列表,用 `/policy-auto 命令` 移除确认项。 - `ArtifactWriter` 写入最终产物前把当前项目文件保存到 `.agent/checkpoints//`,写入后把新增、修改、删除计数记录到 `.agent/agent.db`;聊天命令 `/checkpoint`、`/checkpoints`、`/diff checkpoint-id` 和 `/restore checkpoint-id` 允许用户手动保存、列出最近 checkpoint、对比和确认回滚到 checkpoint,回滚时会删除 checkpoint 后新增的受跟踪项目文件。 - Planner、组内角色和 Generator 读取上下文前会先做安全过滤:拒绝 `.env*`、`game-creator.config*`、Authorization / Cookie / API Key / Token / Bearer 等密钥样式内容,并清理 `sk-*` / `tnr_sk_*` token;memory、资产摘要和 conversation JSONL 中被过滤的内容不进入 LLM prompt。 - `.agent/run.latest.json` 的 schema 固定为共享契约 `GAME_CREATION_AGENT_RUN_SCHEMA_VERSION = game-creator-agent-run.v1`;TS 与 Rust 都从共享契约读取 run trace 类型,避免开发窗口和 Tauri 写入结构漂移。