From c63e87e5f243926e3fa4f9f28911f07120879345 Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Fri, 3 Jul 2026 15:51:55 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=9F=B3=E9=A2=91=E7=B4=A0?= =?UTF-8?q?=E6=9D=90=E8=81=8A=E5=A4=A9=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 /audio 音频素材盘点和安全草稿 补充音频入口界面测试和帮助覆盖 同步技术方案和项目决策记录 --- apps/ai-game-creator-shell/src/App.tsx | 93 +++++++++ .../tests/appSurface.test.ts | 186 +++++++++++++++++- .../shared-memory/decision-log.md | 4 +- ...案】AI游戏创作智能体App实施计划-2026-06-24.md | 7 +- 4 files changed, 283 insertions(+), 7 deletions(-) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 3da5b0f9e..e60b9abc5 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -1884,6 +1884,7 @@ const chatCommandHelp = [ '/history:重新读取当前项目对话历史', '/files:列出本地项目文件', '/assets:列出本地项目资产', + '/audio:查看音频素材与下一步草稿', '/artifacts:列出常用生成产物读取命令', '/run-artifacts:列出最近 Run 产物读取命令', '/runs:列出已加载 Run 历史读取命令', @@ -2274,12 +2275,19 @@ function summarizeNextProjectActions( if (nextManifest.assets.length > 0) { addSuggestion(`查看 ${nextManifest.assets.length} 个本地资产`, '/assets'); + addSuggestion( + nextManifest.assets.some(isProjectAudioAsset) + ? '查看音频素材' + : '登记或导入音频素材', + '/audio', + ); } else { addSuggestion( '登记本地素材或同步画板资源', '/asset-register assets/hero.png image image/png', ); addSuggestion('同步已有画板项目资源', '/sync-canvas-project '); + addSuggestion('登记或导入音频素材', '/audio'); } if (trace) { @@ -2685,6 +2693,74 @@ function summarizeProjectAssets(nextManifest: GameCreationAppManifest) { return `本地项目资产:\n${lines.join('\n')}`; } +function isProjectAudioAsset(asset: GameCreationAppManifest['assets'][number]) { + const mediaType = asset.mediaType.toLowerCase(); + const kind = asset.kind.toLowerCase(); + return ( + mediaType.startsWith('audio/') || + kind === 'audio' || + kind === 'sound-effect' || + kind === 'background-music' + ); +} + +function summarizeProjectAudioAssets(nextManifest: GameCreationAppManifest) { + const audioAssets = nextManifest.assets.filter(isProjectAudioAsset); + if (audioAssets.length === 0) { + return { + text: [ + '音频素材:暂无登记音频。', + '可先登记项目内音效,或把已有画板音频作为素材导入。', + ].join('\n'), + draftCommand: '/asset-register assets/audio/sfx.wav audio audio/wav', + draftCommandLabel: '登记音效', + }; + } + + const sourceCounts = audioAssets.reduce( + (counts, asset) => { + counts[asset.source.kind] += 1; + return counts; + }, + { uploaded: 0, generated: 0, canvas: 0 } satisfies Record< + GameCreationAppAssetSourceKind, + number + >, + ); + const visibleAssets = audioAssets.slice(0, 8); + const lines = visibleAssets.map( + (asset) => + `- ${asset.localPath} · ${asset.mediaType} · ${ + assetSourceKindLabels[asset.source.kind] + }${ + asset.source.canvasProjectId + ? ` · 画板 ${asset.source.canvasProjectId}` + : '' + }`, + ); + if (audioAssets.length > visibleAssets.length) { + lines.push( + `- 还有 ${audioAssets.length - visibleAssets.length} 个音频素材`, + ); + } + + return { + text: [ + `音频素材:${audioAssets.length} 个`, + `来源:${(Object.keys(sourceCounts) as GameCreationAppAssetSourceKind[]) + .filter((source) => sourceCounts[source] > 0) + .map( + (source) => + `${assetSourceKindLabels[source]} ${sourceCounts[source]}`, + ) + .join('、')}`, + lines.join('\n'), + ].join('\n'), + draftCommand: '/read assets/manifest.audio.json', + draftCommandLabel: '读音频清单', + }; +} + function firstReadableProjectAssetPath(nextManifest: GameCreationAppManifest) { return nextManifest.assets.find((asset) => isSafeProjectRelativePath(asset.localPath), @@ -5885,6 +5961,23 @@ export function App() { return; } + if (prompt === '/audio') { + if (!requireChatProjectForUserAction()) { + return; + } + const summary = summarizeProjectAudioAssets(manifest); + setMessages((current) => [ + ...current, + { + role: 'assistant', + text: summary.text, + draftCommand: summary.draftCommand, + draftCommandLabel: summary.draftCommandLabel, + }, + ]); + return; + } + if (prompt === '/artifacts') { 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 77fd23893..a8d76200a 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -2374,6 +2374,35 @@ describe('AI 游戏创作 App 界面边界', () => { 'uploaded · text/plain · uploaded', ), ).not.toBeNull(); + const audioManifestReadCountBefore = invoke.mock.calls.filter( + ([command, args]) => + command === 'read_local_project_file' && + args && + 'relativePath' in args && + args.relativePath === 'assets/manifest.audio.json', + ).length; + submitChat('/audio'); + expect(await screen.findByText(/音频素材:暂无登记音频/)).not.toBeNull(); + expect( + screen.getByText(/可先登记项目内音效,或把已有画板音频作为素材导入/), + ).not.toBeNull(); + const audioDraftButtons = screen.getAllByRole('button', { + name: '登记音效', + }); + fireEvent.click(audioDraftButtons[audioDraftButtons.length - 1]); + expect(composerInput).toHaveProperty( + 'value', + '/asset-register assets/audio/sfx.wav audio audio/wav', + ); + expect( + invoke.mock.calls.filter( + ([command, args]) => + command === 'read_local_project_file' && + args && + 'relativePath' in args && + args.relativePath === 'assets/manifest.audio.json', + ), + ).toHaveLength(audioManifestReadCountBefore); for (const [buttonName, draftValue] of [ ['读入口', '/read game/index.html'], ['读设计', '/read game/game_design.md'], @@ -2452,7 +2481,11 @@ describe('AI 游戏创作 App 界面边界', () => { fireEvent.click(screen.getByRole('button', { name: '生成美术' })); expect(composerInput).toHaveProperty('value', '/generate-art '); await waitFor(() => expect(document.activeElement).toBe(composerInput)); - fireEvent.click(screen.getByRole('button', { name: '登记音效' })); + fireEvent.click( + within(screen.getByLabelText('预览快捷操作')).getByRole('button', { + name: '登记音效', + }), + ); expect(composerInput).toHaveProperty( 'value', '/asset-register assets/audio/sfx.wav audio audio/wav', @@ -7396,7 +7429,26 @@ describe('AI 游戏创作 App 界面边界', () => { expect(screen.queryByText('file.write')).toBeNull(); expect(screen.queryByText('file.delete')).toBeNull(); expect(screen.queryByRole('button', { name: '确认' })).toBeNull(); - expect(invoke).not.toHaveBeenCalled(); + expect(invoke).not.toHaveBeenCalledWith( + 'read_local_project_file', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'get_local_game_manifest', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'register_local_asset', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'import_canvas_asset', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'run_limited_local_command', + expect.anything(), + ); }); it('rejects unsafe developer file panel project paths before confirmation or invoke', () => { @@ -7967,6 +8019,9 @@ describe('AI 游戏创作 App 界面边界', () => { expect( screen.getByText(/\/artifacts:列出常用生成产物读取命令/), ).not.toBeNull(); + expect( + screen.getByText(/\/audio:查看音频素材与下一步草稿/), + ).not.toBeNull(); expect( screen.getByText(/\/run-artifacts:列出最近 Run 产物读取命令/), ).not.toBeNull(); @@ -13685,6 +13740,133 @@ describe('AI 游戏创作 App 界面边界', () => { }); }); + it('summarizes audio assets from chat without reading project files', async () => { + const manifest = createGameCreationAppManifest( + 'local-project-draft', + '未命名游戏原型', + ); + manifest.assets.push( + { + id: 'audio-uploaded', + kind: 'audio', + mediaType: 'audio/wav', + localPath: 'assets/audio/sfx.wav', + source: { kind: 'uploaded' }, + }, + { + id: 'audio-canvas', + kind: 'canvas', + mediaType: 'audio/mpeg', + localPath: 'assets/canvas-sync/theme.mp3', + source: { kind: 'canvas', canvasProjectId: 'canvas-audio' }, + }, + { + id: 'audio-kind-fallback', + kind: 'sound-effect', + mediaType: 'application/octet-stream', + localPath: 'assets/audio/hit.bin', + source: { kind: 'generated' }, + }, + { + id: 'image-asset', + kind: 'image', + mediaType: 'image/png', + localPath: 'assets/hero.png', + source: { kind: 'generated' }, + }, + ); + const invoke = vi.fn( + async (command: string, args?: Record) => { + if (command === 'append_local_permission_log') { + return {}; + } + if (command === 'init_local_game_project') { + const projectPath = String(args?.projectPath ?? ''); + return { + projectPath, + manifestPath: `${projectPath}/.agent/manifest.json`, + manifest, + }; + } + if (command === 'read_local_conversation') { + return { + path: '/tmp/authorized-game/.agent/conversations/project.jsonl', + agentId: null, + messages: [], + }; + } + if (command === 'append_local_conversation_message') { + return { + path: '/tmp/authorized-game/.agent/conversations/project.jsonl', + agentId: null, + messages: [], + }; + } + if (command === 'read_local_project_file') { + throw new Error('audio summary should not read project files'); + } + throw new Error(`unexpected invoke ${command}`); + }, + ); + window.__TAURI__ = { core: { invoke } }; + renderAppAt('/'); + + submitChat('/project /tmp/authorized-game'); + fireEvent.click(screen.getByRole('button', { name: '确认' })); + expect( + await screen.findByText('已设置本地项目:/tmp/authorized-game'), + ).not.toBeNull(); + invoke.mockClear(); + + submitChat('/audio'); + + expect(await screen.findByText(/音频素材:3 个/)).not.toBeNull(); + expect(screen.getByText(/来源:上传 1、生成 1、画板 1/)).not.toBeNull(); + expect( + screen.getByText(/assets\/audio\/sfx\.wav · audio\/wav · 上传/), + ).not.toBeNull(); + expect( + screen.getByText( + /assets\/canvas-sync\/theme\.mp3 · audio\/mpeg · 画板 · 画板 canvas-audio/, + ), + ).not.toBeNull(); + expect( + screen.getByText( + /assets\/audio\/hit\.bin · application\/octet-stream · 生成/, + ), + ).not.toBeNull(); + const audioManifestDraftButtons = screen.getAllByRole('button', { + name: '读音频清单', + }); + fireEvent.click( + audioManifestDraftButtons[audioManifestDraftButtons.length - 1], + ); + expect(screen.getByLabelText('创作想法')).toHaveProperty( + 'value', + '/read assets/manifest.audio.json', + ); + expect(invoke).not.toHaveBeenCalledWith( + 'read_local_project_file', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'get_local_game_manifest', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'register_local_asset', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'import_canvas_asset', + expect.anything(), + ); + expect(invoke).not.toHaveBeenCalledWith( + 'run_limited_local_command', + expect.anything(), + ); + }); + it('registers an existing project asset from chat after confirmation', async () => { const manifest = createGameCreationAppManifest( 'local-project-draft', diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 5b7977164..ee1f585af 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3873,9 +3873,9 @@ - 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` 草稿,不直接读取文件。 -- 2026-07-03 调整:主窗口新增音效登记和画板音频导入快捷入口,只填入 `/asset-register assets/audio/sfx.wav audio audio/wav` 或 `/import-canvas-asset assets/audio/sfx.wav ` 草稿;音乐组仍复用现有资产登记 / 画板回流链路,不新增独立音频生成系统。 +- 2026-07-03 调整:主窗口新增音效登记和画板音频导入快捷入口,只填入 `/asset-register assets/audio/sfx.wav audio audio/wav` 或 `/import-canvas-asset assets/audio/sfx.wav ` 草稿;聊天输入 `/audio` 只基于当前 manifest 盘点音频素材、来源和路径,并给出登记音效或读取 `assets/manifest.audio.json` 的草稿。音乐组仍复用现有资产登记 / 画板回流链路,不新增独立音频生成系统。 - 2026-07-03 调整:主窗口新增常用生成产物读取入口,只把入口 HTML、设计、数值、美术清单、音频清单和发布说明对应的 `/read` 草稿填入聊天输入框;聊天命令 `/artifacts` 只列出同一组固定读取命令并提供首个读取草稿,`/run-artifacts` 只列出最近 trace 里的产物读取命令并提供首个 `/read` 草稿,`/logs` 只列出固定日志读取命令;实际读取仍走聊天侧 `file.read` 权限流,不直接读本地文件。 -- 2026-07-03 调整:普通用户通过聊天输入 `/next` 触发下一步建议入口,只基于主窗口当前已加载的 manifest、最近 run trace 和最近命令摘要生成聊天建议,列出 `/tasks`、`/trace`、`/run`、`/open-preview`、`/assets`、`/artifacts`、`/run-artifacts`、`/run-files`、`/logs`、`/agent-resume ` 等安全命令草稿方向,并提供一个首选草稿;该命令不得直接执行 Tauri 读写、启动或打开预览、读取本地文件,也不得绕过原有命令确认和 `file.read` 权限流。 +- 2026-07-03 调整:普通用户通过聊天输入 `/next` 触发下一步建议入口,只基于主窗口当前已加载的 manifest、最近 run trace 和最近命令摘要生成聊天建议,列出 `/tasks`、`/trace`、`/run`、`/open-preview`、`/assets`、`/audio`、`/artifacts`、`/run-artifacts`、`/run-files`、`/logs`、`/agent-resume ` 等安全命令草稿方向,并提供一个首选草稿;该命令不得直接执行 Tauri 读写、启动或打开预览、读取本地文件,也不得绕过原有命令确认和 `file.read` 权限流。 - 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 bf9cfb27a..9f31d7a3c 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` 草稿,不新增普通用户面板;聊天输入 `/risks` 可在普通聊天消息里查看当前项目风险并提供首个风险处理草稿,不新增普通用户面板;聊天输入 `/handoff` 可在普通聊天消息里生成当前项目交接摘要并提供 `/next` 草稿,不新增普通用户面板;聊天输入 `/runs` 可在普通聊天消息里列出已加载 Run 历史读取命令,不新增普通用户面板;聊天输入 `/run-files` 可在普通聊天消息里列出 Agent 运行辅助文件读取命令,不新增普通用户面板;聊天输入 `/export` 确认后把当前可试玩原型导出为本地试玩 ZIP,聊天输入 `/exports` 可只读列出已导出的本地试玩包;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` 可在普通聊天消息里查看当前项目风险并提供首个风险处理草稿,不新增普通用户面板;聊天输入 `/handoff` 可在普通聊天消息里生成当前项目交接摘要并提供 `/next` 草稿,不新增普通用户面板;聊天输入 `/runs` 可在普通聊天消息里列出已加载 Run 历史读取命令,不新增普通用户面板;聊天输入 `/run-files` 可在普通聊天消息里列出 Agent 运行辅助文件读取命令,不新增普通用户面板;聊天输入 `/audio` 可基于当前 manifest 盘点音频素材并提供登记或读取草稿,不新增音频生成系统;聊天输入 `/export` 确认后把当前可试玩原型导出为本地试玩 ZIP,聊天输入 `/exports` 可只读列出已导出的本地试玩包;Agent 状态列表和单 Agent 对话在 `/llm-status` 后显示该 agent 当前 LLM provider / 模型 / 流式 / API Key 读取状态,但不显示密钥本体;最近项目资产入口显示本地路径、kind、mediaType 和来源类型,并可一键填入 `/read` 草稿,开发环境通过独立窗口查看任务拆分、专业组细节、产物、文件面板、嵌入预览以及 `.agent/logs/command.log` / `preview.log` / `agent.log`。 - 生成代码和资产进入用户本地项目目录。 - 本地 HTTP 预览能启动,并在外部浏览器展示可玩原型。 - 美术/音乐资产能从画板链路回流到本地项目。 @@ -118,6 +118,7 @@ game-project/ - `/run-files` 聊天入口由 `appSurface.test.ts` 的主窗口 smoke 覆盖:只列出 `.agent/output.jsonl`、`.agent/activity.jsonl` 和 `.agent/context.bundle.json` 的 `/read` 草稿,不直接读取辅助文件、不触发 Tauri 读写或新增普通用户面板。 - `/export` 聊天入口由 `appSurface.test.ts` 的主窗口 smoke 和 Tauri Rust 测试覆盖:确认后执行 `project.export_package`,导出 `exports/playtest-package-*.zip`,只打包 `game/**`、`assets/**` 和 `exports/README.md`,不包含 `.agent/`、`memory/`、运行时配置、日志、trace 或密钥文件;导出前必须通过 `game/index.html` 可试玩静态验收,并写入 manifest `commandRuns`、`.agent/logs/command.log` 和 `.agent/agent.db`。 - `/exports` 聊天入口由 `appSurface.test.ts` 主窗口 smoke、项目权限确认测试和 Tauri Rust 测试覆盖:只读执行 `project.export_list`,列出 `exports/playtest-package-*.zip`,跳过符号链接和非试玩包文件,不删除旧包、不做系统分享、不新增普通用户面板。 +- `/audio` 聊天入口由 `appSurface.test.ts` 主窗口 smoke 和音频资产定向测试覆盖:只基于当前已加载 manifest 盘点音频素材数量、来源和路径,提供 `/asset-register assets/audio/sfx.wav audio audio/wav` 或 `/read assets/manifest.audio.json` 草稿,不触发 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 编译。 @@ -175,7 +176,7 @@ game-project/ - 聊天区待确认命令必须先记录 `permission.pending`,用户点击后再记录 `permission.confirm` 或 `permission.cancel`;已授权本地项目内的权限事件同步追加到 `.agent/logs/command.log`,待确认卡片显示本地写入目标路径。 - 聊天输入 `/status` 会读取 `.agent/manifest.json` 并在聊天里汇总项目目录、任务状态、资产数量、预览状态和最近命令,不向普通用户暴露任务或文件面板。 - 聊天输入 `/files` 会通过 `file.list` 只读列出本地项目内的文件摘要;主窗口最近项目文件可一键读取,也可一键填入 `/read` 或 `/asset-register` 草稿,但资产登记仍必须走聊天确认;`/checkpoints` 复用 `file.list` / `file.read` 只读列出最近 checkpoint id、文件数、大小和可复制的 `/diff` / `/restore` 命令,主窗口最近 checkpoint 列表也可填入对应草稿;普通用户仍不暴露文件读写面板。 -- 聊天输入 `/assets` 会读取 `.agent/manifest.json` 并在聊天里列出本地项目资产路径、类型和来源,资产列表消息和主窗口最近项目资产入口都可一键填入对应资产的 `/read` 草稿;`/asset-register 路径 [kind] [mediaType]` 可确认后登记项目内已有资产;主窗口音效快捷入口只填入 `/asset-register assets/audio/sfx.wav audio audio/wav` 草稿,不直接写 manifest;普通用户仍不暴露资产面板。 +- 聊天输入 `/assets` 会读取 `.agent/manifest.json` 并在聊天里列出本地项目资产路径、类型和来源,资产列表消息和主窗口最近项目资产入口都可一键填入对应资产的 `/read` 草稿;聊天输入 `/audio` 只使用当前已加载 manifest 盘点音频素材,并提供登记音效或读取音频清单草稿,不直接读取文件、不触发资产写入;`/asset-register 路径 [kind] [mediaType]` 可确认后登记项目内已有资产;主窗口音效快捷入口只填入 `/asset-register assets/audio/sfx.wav audio audio/wav` 草稿,不直接写 manifest;普通用户仍不暴露资产面板。 - 聊天输入 `/read 本地相对路径` 会通过 `file.read` 只读返回项目内文本文件内容并在聊天中截断长文本;普通用户仍不暴露文件写入或删除能力。 - 主窗口常用生成产物入口只把 `game/index.html`、`game/game_design.md`、`game/balance.json`、`assets/manifest.art.json`、`assets/manifest.audio.json` 和 `exports/README.md` 的 `/read` 草稿填入聊天输入框;聊天输入 `/artifacts` 只列出这组固定读取命令并提供首个 `/read` 草稿,聊天输入 `/run-artifacts` 只列出最近 run trace 里的产物读取命令并提供首个 `/read` 草稿,聊天输入 `/run-files` 只列出 `.agent/output.jsonl`、`.agent/activity.jsonl` 和 `.agent/context.bundle.json` 的读取命令并提供首个 `/read` 草稿;读取仍由聊天侧 `file.read` 权限流执行。 - 聊天输入 `/logs` 只列出 `.agent/logs/command.log`、`.agent/logs/preview.log` 和 `.agent/logs/agent.log` 对应的 `/read ...` 草稿 / 命令,并提供首个 `/read` 草稿;该命令不直接读取日志,不新增普通用户日志面板,实际读取仍由聊天侧 `file.read` 权限流执行。 @@ -185,7 +186,7 @@ game-project/ - 聊天输入 `/risks` 只使用主窗口当前已加载的 manifest、最近 run trace、预览状态、任务状态、资产来源和最近命令摘要,在聊天里列出当前项目风险,并提供首个风险处理草稿;该命令不调用 Tauri 读写、不读取文件、不启动或打开预览、不新增普通用户面板,用户必须再发送草稿并按原命令权限流继续。 - 聊天输入 `/handoff` 只使用主窗口当前已加载的 manifest、最近 run trace、Agent 状态和已载入历史 run 批次,在聊天里生成项目交接摘要,并提供 `/next` 作为后续草稿;该命令不调用 Tauri 读写、不读取文件、不启动或打开预览、不新增普通用户面板,用户必须再发送草稿并按原命令权限流继续。 - 聊天输入 `/runs` 只使用主窗口当前已加载的 latest trace 和已载入历史 run 批次,在聊天里列出 `/trace` 和 `/read .agent/runs/...` 读取草稿;该命令不调用 Tauri 读写、不滚动加载更多历史、不启动或打开预览、不新增普通用户面板,用户必须再发送草稿并按原命令权限流继续。 -- 聊天输入 `/next` 只使用主窗口当前已加载的 manifest、最近 run trace 和最近命令摘要,在聊天里给出下一步建议,列出 `/tasks`、`/trace`、`/run`、`/export`、`/exports`、`/open-preview`、`/assets`、`/artifacts`、`/run-artifacts`、`/run-files`、`/logs`、`/agent-resume ` 等安全命令草稿方向,并提供一个首选草稿;该命令不调用 Tauri 读写、不启动或打开预览、不读取文件,用户必须再发送草稿并按原命令权限流继续。 +- 聊天输入 `/next` 只使用主窗口当前已加载的 manifest、最近 run trace 和最近命令摘要,在聊天里给出下一步建议,列出 `/tasks`、`/trace`、`/run`、`/export`、`/exports`、`/open-preview`、`/assets`、`/audio`、`/artifacts`、`/run-artifacts`、`/run-files`、`/logs`、`/agent-resume ` 等安全命令草稿方向,并提供一个首选草稿;该命令不调用 Tauri 读写、不启动或打开预览、不读取文件,用户必须再发送草稿并按原命令权限流继续。 - 主窗口可从 agent 状态列表进入单个 agent 对话;该入口只加载目标 agent 的 conversation JSONL,发送消息后追加到同一 agent conversation,不开启平行任务图、不 fork run,也不归档历史会话。 - v1 通过独立启动器窗口选择本地项目后再进入主窗口;主窗口只承载当前工作区的聊天、配置和 agent 状态,切换项目时关闭当前主窗口并回到启动器,主窗口按钮和 `/switch-project` 聊天命令都复用同一 Tauri 启动器入口,避免在主窗口内用遮罩面板混合多个工作区上下文。 - 主窗口可通过系统文件管理器显示当前项目目录,也可在聊天输入 `/open-project` 走同一只读打开动作;该操作只打开本地目录,不初始化项目、不写项目文件、不切换工作区。主窗口头部显示最近 `.agent/run.latest.json` 的 run 状态摘要和当前项目预览状态,并通过“刷新状态”重新读取同一 trace,不新增状态数据库。