diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 028330053..b194919ba 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -1826,6 +1826,7 @@ const chatCommandHelp = [ '/audit:审计当前项目的 Agent 能力证据', '/status:查看项目状态', '/brief:生成当前项目简报', + '/risks:查看当前项目风险', '/next:查看下一步建议', '/open-project:在系统文件管理器中显示项目目录', '/switch-project:回到项目启动器切换工作区', @@ -2020,6 +2021,111 @@ function summarizeProjectBrief( }`; } +function summarizeProjectRisks( + nextManifest: GameCreationAppManifest, + trace: GameCreationAgentRunTrace | null, +) { + const risks: Array<{ text: string; command?: string }> = []; + const addRisk = (text: string, command?: string) => { + if (command && risks.some((risk) => risk.command === command)) { + return; + } + risks.push({ text, command }); + }; + + const tasks = taskRowsFromManifest(nextManifest); + const failedTasks = tasks.filter((task) => task.status === 'failed'); + const readyTasks = selectGameCreationAppReadyTasks({ tasks }); + const commandRuns = nextManifest.commandRuns ?? []; + const latestCommandRun = commandRuns[commandRuns.length - 1]; + const preview = nextManifest.preview; + const tracePassed = + trace?.status === 'passed' || + trace?.status === 'artifacts-written' || + trace?.stopReason === 'evaluator-passed'; + const hasCanvasImageAsset = nextManifest.assets.some( + (asset) => + asset.source.kind === 'canvas' && + (asset.mediaType.startsWith('image/') || + asset.mediaType === 'application/vnd.genarrative.image-sequence'), + ); + + if (!trace) { + addRisk( + '暂无最近 Agent run,当前项目还缺少生成闭环证据。', + '/next', + ); + } else if ( + trace.lifecycleStatus === 'killed' || + trace.status === 'failed' || + trace.stopReason === 'max-passes-exhausted' + ) { + addRisk( + `最近 run 未完成:${trace.status} / ${trace.stopReason}。`, + '/agent-resume ', + ); + } else if (!tracePassed) { + addRisk( + `最近 run 尚未通过 Evaluator:${trace.status} / ${trace.stopReason}。`, + '/trace', + ); + } + + if (failedTasks.length > 0) { + addRisk( + `有 ${failedTasks.length} 个任务处于失败状态。`, + '/tasks', + ); + } + + if (latestCommandRun?.status === 'failed') { + addRisk( + `最近命令 ${latestCommandRun.commandId} 失败。`, + '/logs', + ); + } + + if (tracePassed && !(preview?.status === 'running' && preview.url)) { + addRisk( + '最近 run 已通过,但当前本地预览未运行。', + '/run', + ); + } + + if (readyTasks.length > 0) { + addRisk( + `还有 ${readyTasks.length} 个 ready 任务等待处理。`, + '/tasks', + ); + } + + if (nextManifest.assets.length === 0) { + addRisk( + '暂无本地资产,首版原型可能缺少可复用素材。', + '/asset-register assets/hero.png image image/png', + ); + } else if (!hasCanvasImageAsset) { + addRisk( + '暂无画板来源图片资产,美术组可能只能先使用占位素材。', + '/sync-canvas-project ', + ); + } + + const firstAction = risks.find((risk) => risk.command); + return { + text: + risks.length > 0 + ? `项目风险:\n${risks + .map( + (risk) => `- ${risk.text}${risk.command ? ` 建议:${risk.command}` : ''}`, + ) + .join('\n')}` + : '项目风险:\n- 暂未发现需要立即处理的风险。', + draftCommand: firstAction?.command, + draftCommandLabel: firstAction ? '处理首个风险' : undefined, + }; +} + function summarizeNextProjectActions( nextManifest: GameCreationAppManifest, trace: GameCreationAgentRunTrace | null, @@ -5352,6 +5458,23 @@ export function App() { return; } + if (prompt === '/risks') { + if (!requireChatProjectForUserAction()) { + return; + } + const summary = summarizeProjectRisks(manifest, agentRunTrace); + setMessages((current) => [ + ...current, + { + role: 'assistant', + text: summary.text, + draftCommand: summary.draftCommand, + draftCommandLabel: summary.draftCommandLabel, + }, + ]); + return; + } + if (prompt === '/next') { if (!requireChatProjectForUserAction()) { return; diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index ea9b5c761..e77d97f5e 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -2134,6 +2134,40 @@ describe('AI 游戏创作 App 界面边界', () => { ), ).toHaveLength(manifestReadCountBeforeBrief); + const manifestReadCountBeforeRisks = invoke.mock.calls.filter( + ([command]) => command === 'get_local_game_manifest', + ).length; + const fileReadCountBeforeRisks = invoke.mock.calls.filter( + ([command]) => command === 'read_local_project_file', + ).length; + const runLocalCountBeforeRisks = invoke.mock.calls.filter( + ([command]) => command === 'run_limited_local_command', + ).length; + submitChat('/risks'); + expect(await screen.findByText(/项目风险:/)).not.toBeNull(); + expect( + screen.getByText(/最近 run 已通过,但当前本地预览未运行。 建议:\/run/), + ).not.toBeNull(); + expect(screen.getByText(/还有 1 个 ready 任务等待处理。 建议:\/tasks/)) + .not.toBeNull(); + fireEvent.click(screen.getByRole('button', { name: '处理首个风险' })); + expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/run'); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'get_local_game_manifest', + ), + ).toHaveLength(manifestReadCountBeforeRisks); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'read_local_project_file', + ), + ).toHaveLength(fileReadCountBeforeRisks); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'run_limited_local_command', + ), + ).toHaveLength(runLocalCountBeforeRisks); + const runLocalCountBeforeNext = invoke.mock.calls.filter( ([command]) => command === 'run_limited_local_command', ).length; @@ -7712,6 +7746,7 @@ describe('AI 游戏创作 App 界面边界', () => { screen.getByText(/\/switch-project:回到项目启动器切换工作区/), ).not.toBeNull(); expect(screen.getByText(/\/brief:生成当前项目简报/)).not.toBeNull(); + expect(screen.getByText(/\/risks:查看当前项目风险/)).not.toBeNull(); expect(screen.getByText(/\/next:查看下一步建议/)).not.toBeNull(); expect( screen.getByText( diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 9e4da8fed..ecd4970e1 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3857,6 +3857,7 @@ - 2026-07-03 调整:主窗口 Agent 状态栏新增“继续说明”,只把 `/agent-resume ` 填入聊天输入框,让用户补充说明后再走原确认流;策略快捷入口新增 project.index、asset.register、memory.write、preview.open、preview.stop、conversation.read 和 conversation.write 确认草稿,同样只填输入框,不直接写 `.agent/policy.json`。 - 2026-07-03 调整:主窗口 header 常驻项目摘要只从当前已加载的 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令结果;未选择工作区时不显示,不为了摘要额外触发 Tauri 读取或写入,也不把任务、文件、run history 或预览开发面板搬进普通用户窗口。 - 2026-07-03 调整:普通用户通过聊天输入 `/brief` 触发项目简报入口,只基于主窗口当前已加载的 manifest、最近 run trace、预览状态、资产数量和最近命令生成聊天内简报,并提供 `/next` 作为后续草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览,也不得新增普通用户面板。 +- 2026-07-03 调整:普通用户通过聊天输入 `/risks` 查看当前项目风险,只基于主窗口当前已加载的 manifest、最近 run trace、预览状态、任务状态、资产来源和最近命令派生风险摘要,并提供首个风险处理草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览,也不得新增普通用户面板。 - 2026-07-03 调整:`/llm-status` 读取到的 agent 级 LLM 配置状态可回填到主窗口 Agent 状态列表、聊天侧 `/agents` 汇总和单 Agent 对话头部,显示 provider 类型、模型、流式开关和 API Key 是否已读取;密钥本体仍不能进入聊天、状态列表、manifest、trace 或本地项目文件。 - 2026-07-03 调整:开发窗口日志面板提供 `.agent/logs/command.log`、`.agent/logs/preview.log` 和 `.agent/logs/agent.log` 的只读查看入口,复用 `file.read` 授权策略;普通用户聊天输入 `/logs` 只列出这三个日志文件对应的 `/read ...` 草稿 / 命令并提供首个草稿,不直接读取日志,不新增普通用户日志面板,实际读取仍走聊天侧 `file.read`。 - 2026-07-03 调整:单 Agent 对话面板允许用户把当前输入手动追加到该 agent 的 `memory/agents//.md` 私有记忆;写入复用 `memory.write` 项目策略、项目锁和 Tauri 本地目录能力,不把普通对话流水自动混入私有记忆;聊天侧 `/agent-conversations` 和 `/agent-memories` 只列出同一批 Agent 对话与私有记忆读取命令并提供首个 `/read` 草稿,不直接读取文件。 diff --git a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md index ee9c05af2..76e461243 100644 --- a/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md +++ b/docs/technical/【技术方案】AI游戏创作智能体App实施计划-2026-06-24.md @@ -100,7 +100,7 @@ game-project/ ## v1 验收 - 用户能创建本地 Web 游戏项目。 -- 用户侧看到当前工作区项目名 / 路径、最近 run、预览状态、任务完成数 / ready 数、资产数量 / 来源分布、最近命令摘要、聊天框、上传入口、Agent 状态列表和单 Agent 对话;聊天输入 `/brief` 可在普通聊天消息里生成当前项目简报并提供 `/next` 草稿,不新增普通用户面板;Agent 状态列表和单 Agent 对话在 `/llm-status` 后显示该 agent 当前 LLM provider / 模型 / 流式 / API Key 读取状态,但不显示密钥本体;最近项目资产入口显示本地路径、kind、mediaType 和来源类型,并可一键填入 `/read` 草稿,开发环境通过独立窗口查看任务拆分、专业组细节、产物、文件面板、嵌入预览以及 `.agent/logs/command.log` / `preview.log` / `agent.log`。 +- 用户侧看到当前工作区项目名 / 路径、最近 run、预览状态、任务完成数 / ready 数、资产数量 / 来源分布、最近命令摘要、聊天框、上传入口、Agent 状态列表和单 Agent 对话;聊天输入 `/brief` 可在普通聊天消息里生成当前项目简报并提供 `/next` 草稿,不新增普通用户面板;聊天输入 `/risks` 可在普通聊天消息里查看当前项目风险并提供首个风险处理草稿,不新增普通用户面板;Agent 状态列表和单 Agent 对话在 `/llm-status` 后显示该 agent 当前 LLM provider / 模型 / 流式 / API Key 读取状态,但不显示密钥本体;最近项目资产入口显示本地路径、kind、mediaType 和来源类型,并可一键填入 `/read` 草稿,开发环境通过独立窗口查看任务拆分、专业组细节、产物、文件面板、嵌入预览以及 `.agent/logs/command.log` / `preview.log` / `agent.log`。 - 生成代码和资产进入用户本地项目目录。 - 本地 HTTP 预览能启动,并在外部浏览器展示可玩原型。 - 美术/音乐资产能从画板链路回流到本地项目。 @@ -112,6 +112,7 @@ game-project/ ## v1 验收证据矩阵 - `npm run ai-game-creator-shell:check`:覆盖壳 typecheck、聊天命令单测、用户 / 开发窗口 UI 边界 smoke、主窗口命令按钮复用 `/help` 命令列表、主窗口项目摘要从 manifest / trace 派生任务完成数、ready 数、资产来源分布和最近命令且未选项目时不显示、灵感草稿只填充输入框不提交、能力按钮复用 `/capabilities`、LLM状态按钮复用 `/llm-status` 且结果回填 Agent 状态列表、聊天侧 `/agents` 汇总和单 Agent 对话的 provider / 模型 / 流式 / API Key 读取状态、开发日志面板只读读取 `.agent/logs/command.log` / `preview.log` / `agent.log`、项目状态按钮复用 `/status`、权限按钮复用 `/policy` 且策略草稿按钮只填入 `/policy-confirm project.index` / `/policy-confirm asset.register` / `/policy-confirm memory.write` / `/policy-confirm preview.start` / `/policy-confirm preview.open` / `/policy-confirm preview.stop` / `/policy-confirm agent.run_status` / `/policy-confirm conversation.read` / `/policy-confirm conversation.write`、审计按钮复用 `/audit`、资产按钮复用 `/assets` 且资产结果可一键复用 `/read`、任务按钮复用 `/tasks`、聊天侧 `/agents` 汇总每个 Agent 的当前状态、聊天侧 `/agent-conversations` 列出 Agent 对话读取命令、聊天侧 `/agent-memories` 列出 Agent 私有记忆读取命令、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` 策略、主窗口提供音效登记、画板音频导入和常用生成产物读取草稿入口,聊天侧 `/artifacts` 可列出常用生成产物读取命令,聊天侧 `/run-artifacts` 可列出最近 run 产物读取命令,聊天侧 `/logs` 可列出固定日志读取命令且不直接读取日志,聊天侧 `/brief` 只基于当前已加载的 manifest / 最近 run trace / 预览状态 / 资产数量 / 最近命令生成项目简报,提供 `/next` 后续草稿且不直接触发 Tauri 读写、文件读取、预览启动或新增面板,聊天侧 `/next` 基于当前已加载的 manifest / 最近 run trace 输出下一步建议和 `/tasks` / `/trace` / `/run` / `/open-preview` / `/assets` / `/artifacts` / `/run-artifacts` / `/logs` / `/agent-resume ` 等安全命令草稿方向,提供一个首选草稿且不直接触发 Tauri 读写、预览启动或文件读取,聊天侧 `/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 轮失败时不会写入最终游戏产物。 +- `/risks` 聊天入口由 `appSurface.test.ts` 的主窗口 smoke 覆盖:只基于当前已加载的 manifest / trace / 预览 / 任务 / 资产 / 最近命令生成风险摘要,提供首个风险处理草稿,不触发 Tauri 读写、文件读取、预览启动或新增普通用户面板。 - 主窗口策略快捷入口只填入 `/policy-confirm project.index`、`/policy-confirm asset.register`、`/policy-confirm memory.write`、`/policy-confirm preview.start`、`/policy-confirm preview.open`、`/policy-confirm preview.stop`、`/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 编译。 @@ -176,6 +177,7 @@ game-project/ - 聊天输入 `/tasks` 会读取 `.agent/manifest.json` 并在聊天里列出专业组、角色、任务状态、产物交接和下一步可执行任务;普通用户仍不暴露任务面板。 - 聊天输入 `/trace` 会通过 `agent.trace_read` 只读读取 `.agent/run.latest.json`,在聊天里汇总最近一次 loop 的轮次、stopReason、nextStep、active/carry-over 任务、repairRoutes、agent 建议命令、产物快照和最近 step;没有待同步建议命令时,首个安全产物可一键填入 `/read ` 草稿;active、carry-over、repairRoutes 和 dependencyWaves 必须把 taskId 映射成专业组 / 角色 / 任务名,避免普通用户只能看到内部 id;普通用户仍不暴露开发 trace 面板。 - 聊天输入 `/brief` 只使用主窗口当前已加载的 manifest、最近 run trace、预览状态、资产数量和最近命令摘要,在聊天里生成项目简报,并提供 `/next` 作为后续草稿;该命令不调用 Tauri 读写、不读取文件、不启动或打开预览、不新增普通用户面板,用户可再发送 `/next` 草稿继续。 +- 聊天输入 `/risks` 只使用主窗口当前已加载的 manifest、最近 run trace、预览状态、任务状态、资产来源和最近命令摘要,在聊天里列出当前项目风险,并提供首个风险处理草稿;该命令不调用 Tauri 读写、不读取文件、不启动或打开预览、不新增普通用户面板,用户必须再发送草稿并按原命令权限流继续。 - 聊天输入 `/next` 只使用主窗口当前已加载的 manifest、最近 run trace 和最近命令摘要,在聊天里给出下一步建议,列出 `/tasks`、`/trace`、`/run`、`/open-preview`、`/assets`、`/artifacts`、`/run-artifacts`、`/logs`、`/agent-resume ` 等安全命令草稿方向,并提供一个首选草稿;该命令不调用 Tauri 读写、不启动或打开预览、不读取文件,用户必须再发送草稿并按原命令权限流继续。 - 主窗口可从 agent 状态列表进入单个 agent 对话;该入口只加载目标 agent 的 conversation JSONL,发送消息后追加到同一 agent conversation,不开启平行任务图、不 fork run,也不归档历史会话。 - v1 通过独立启动器窗口选择本地项目后再进入主窗口;主窗口只承载当前工作区的聊天、配置和 agent 状态,切换项目时关闭当前主窗口并回到启动器,主窗口按钮和 `/switch-project` 聊天命令都复用同一 Tauri 启动器入口,避免在主窗口内用遮罩面板混合多个工作区上下文。