From 160598d8f95bd32bf58c7d7debf48d51766a205a Mon Sep 17 00:00:00 2001 From: AIGameCreator App Date: Sat, 4 Jul 2026 10:52:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=B8=8B=E4=B8=80=E8=BD=AE?= =?UTF-8?q?=E5=B0=8F=E6=AD=A5=E6=B8=85=E5=8D=95=E8=81=8A=E5=A4=A9=E5=85=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 /todo 下一轮小步清单摘要 补充 /next 与 /help 的小步清单入口覆盖 验证 /todo 不触发文件读取、运行、预览或导出命令 --- apps/ai-game-creator-shell/src/App.tsx | 117 ++++++++++++++++++ .../tests/appSurface.test.ts | 91 ++++++++++++++ 2 files changed, 208 insertions(+) diff --git a/apps/ai-game-creator-shell/src/App.tsx b/apps/ai-game-creator-shell/src/App.tsx index 65df8e81d..cf46dfe55 100644 --- a/apps/ai-game-creator-shell/src/App.tsx +++ b/apps/ai-game-creator-shell/src/App.tsx @@ -1889,6 +1889,7 @@ const chatCommandHelp = [ '/timeline:查看项目活动时间线', '/handoff:生成当前项目交接摘要', '/next:查看下一步建议', + '/todo:查看下一轮小步清单', '/publish:查看发布准备清单', '/listing:准备作品页文案清单', '/playtest:查看试玩状态与下一步', @@ -3028,6 +3029,104 @@ function summarizeProjectAcceptanceCriteria( }; } +function summarizeProjectTodoList( + nextManifest: GameCreationAppManifest, + trace: GameCreationAgentRunTrace | null, +) { + const manifestTasks = taskRowsFromManifest(nextManifest); + const traceTasks = trace?.taskGraph.tasks ?? []; + const taskSource = traceTasks.length > 0 ? traceTasks : manifestTasks; + const manifestTasksById = new Map( + manifestTasks.map((task) => [task.id, task]), + ); + const taskSourceById = new Map(taskSource.map((task) => [task.id, task])); + const selectedTasks: Array<{ + task: GameCreationAppTaskState; + marker: string; + }> = []; + const seenTaskIds = new Set(); + const addTask = (taskId: string, marker: string) => { + if (seenTaskIds.has(taskId)) { + return; + } + const task = taskSourceById.get(taskId) ?? manifestTasksById.get(taskId); + if (!task) { + return; + } + selectedTasks.push({ task, marker }); + seenTaskIds.add(task.id); + }; + + taskSource + .filter((task) => task.status === 'failed') + .forEach((task) => addTask(task.id, '失败')); + trace?.taskGraph.activeTaskIds.forEach((taskId) => + addTask(taskId, 'active'), + ); + trace?.taskGraph.carriedTaskIds.forEach((taskId) => + addTask(taskId, 'carry'), + ); + trace?.taskGraph.readyTaskIds.forEach((taskId) => addTask(taskId, 'ready')); + + if (selectedTasks.length === 0) { + selectGameCreationAppReadyTasks({ tasks: manifestTasks }).forEach((task) => + addTask(task.id, 'ready'), + ); + } + + if (selectedTasks.length === 0) { + taskSource + .filter((task) => task.status !== 'completed') + .slice(0, 5) + .forEach((task) => addTask(task.id, taskStatusLabels[task.status])); + } + + const visibleTasks = selectedTasks.slice(0, 5); + const taskLines = visibleTasks.map(({ task, marker }, index) => { + const acceptance = + task.acceptanceCriteria.length > 0 + ? task.acceptanceCriteria[0] + : '暂无'; + const artifact = task.artifacts[0] ?? '暂无'; + return `- ${index + 1}. ${marker}:${taskGroupLabels[task.group]} / ${task.role} ${task.title}(${task.id}) · ${taskStatusLabels[task.status]} · 验收:${acceptance} · 产物:${artifact}`; + }); + if (selectedTasks.length > visibleTasks.length) { + taskLines.push( + `- 还有 ${selectedTasks.length - visibleTasks.length} 个候选任务`, + ); + } + + const blockedTrace = + trace?.lifecycleStatus === 'killed' || + trace?.status === 'failed' || + trace?.status === 'needs-revision' || + trace?.stopReason === 'max-passes-exhausted'; + let draftCommand = selectedTasks.length > 0 ? '/tasks' : '/next'; + let draftCommandLabel = selectedTasks.length > 0 ? '查看任务' : '查看下一步'; + if (blockedTrace) { + draftCommand = '/review'; + draftCommandLabel = '查看评审'; + } + + return { + text: [ + '下一轮小步:', + `- 项目:${nextManifest.name}`, + trace ? `- Run:${trace.runId} · ${formatAgentRunStatus(trace)}` : null, + trace?.nextStep ? `- 编排下一步:${trace.nextStep}` : null, + taskLines.length > 0 + ? `- 小步清单:\n${taskLines.join('\n')}` + : '- 小步清单:暂无待处理任务', + '- 边界:只整理下一步;不读取任务文件;不启动 run;不修改项目', + `- 建议:${draftCommand}`, + ] + .filter(Boolean) + .join('\n'), + draftCommand, + draftCommandLabel, + }; +} + function summarizeProjectGroupProgress( nextManifest: GameCreationAppManifest, trace: GameCreationAgentRunTrace | null, @@ -4232,6 +4331,7 @@ function summarizeNextProjectActions( } addSuggestion('查看质量检查清单', '/qa'); addSuggestion('查看最近生成变更', '/changes'); + addSuggestion('查看下一轮小步清单', '/todo'); if (nextManifest.assets.length > 0) { addSuggestion(`查看 ${nextManifest.assets.length} 个本地资产`, '/assets'); @@ -8273,6 +8373,23 @@ export function App() { return; } + if (prompt === '/todo') { + if (!requireChatProjectForUserAction()) { + return; + } + const summary = summarizeProjectTodoList(manifest, agentRunTrace); + setMessages((current) => [ + ...current, + { + role: 'assistant', + text: summary.text, + draftCommand: summary.draftCommand, + draftCommandLabel: summary.draftCommandLabel, + }, + ]); + return; + } + if (prompt === '/publish') { const nextProjectPath = requireChatProjectForUserAction(); if (!nextProjectPath) { diff --git a/apps/ai-game-creator-shell/tests/appSurface.test.ts b/apps/ai-game-creator-shell/tests/appSurface.test.ts index 2aedba46e..dd0b4a890 100644 --- a/apps/ai-game-creator-shell/tests/appSurface.test.ts +++ b/apps/ai-game-creator-shell/tests/appSurface.test.ts @@ -3863,6 +3863,95 @@ describe('AI 游戏创作 App 界面边界', () => { ), ).toHaveLength(fileReadCountBeforeInternals); + const commandCountsBeforeTodo = { + manifestRead: invoke.mock.calls.filter( + ([command]) => command === 'get_local_game_manifest', + ).length, + fileRead: invoke.mock.calls.filter( + ([command]) => command === 'read_local_project_file', + ).length, + fileList: invoke.mock.calls.filter( + ([command]) => command === 'list_local_project_files', + ).length, + runLocal: invoke.mock.calls.filter( + ([command]) => command === 'run_limited_local_command', + ).length, + startPreview: invoke.mock.calls.filter( + ([command]) => command === 'start_local_game_preview', + ).length, + openPreview: invoke.mock.calls.filter( + ([command]) => command === 'open_local_game_preview', + ).length, + exportPackage: invoke.mock.calls.filter( + ([command]) => command === 'export_local_project_package', + ).length, + exportList: invoke.mock.calls.filter( + ([command]) => command === 'list_local_project_export_packages', + ).length, + }; + submitChat('/todo'); + const todoMessages = await screen.findAllByText(/下一轮小步:/); + const todoMessage = todoMessages[todoMessages.length - 1]; + expect(todoMessage.textContent).toContain('项目:未命名游戏原型'); + expect(todoMessage.textContent).toContain( + 'Run:run-main-shortcut-trace · passed / done · 1/3 轮 · evaluator-passed', + ); + expect(todoMessage.textContent).toContain('编排下一步:preview'); + expect(todoMessage.textContent).toContain( + '1. ready:美术组 / Asset 规划美术资产(art-asset-plan) · 待处理 · 验收:角色、场景、UI 和动画需求已映射到画板或本地资产 · 产物:assets/manifest.art.json', + ); + expect(todoMessage.textContent).toContain( + '边界:只整理下一步;不读取任务文件;不启动 run;不修改项目', + ); + expect(todoMessage.textContent).toContain('建议:/tasks'); + const todoMessageList = document.querySelector('.message-list'); + expect(todoMessageList).not.toBeNull(); + const todoDraftButtons = within( + todoMessageList as HTMLElement, + ).getAllByRole('button', { name: '查看任务' }); + fireEvent.click(todoDraftButtons[todoDraftButtons.length - 1]); + expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/tasks'); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'get_local_game_manifest', + ), + ).toHaveLength(commandCountsBeforeTodo.manifestRead); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'read_local_project_file', + ), + ).toHaveLength(commandCountsBeforeTodo.fileRead); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'list_local_project_files', + ), + ).toHaveLength(commandCountsBeforeTodo.fileList); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'run_limited_local_command', + ), + ).toHaveLength(commandCountsBeforeTodo.runLocal); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'start_local_game_preview', + ), + ).toHaveLength(commandCountsBeforeTodo.startPreview); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'open_local_game_preview', + ), + ).toHaveLength(commandCountsBeforeTodo.openPreview); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'export_local_project_package', + ), + ).toHaveLength(commandCountsBeforeTodo.exportPackage); + expect( + invoke.mock.calls.filter( + ([command]) => command === 'list_local_project_export_packages', + ), + ).toHaveLength(commandCountsBeforeTodo.exportList); + const runLocalCountBeforeNext = invoke.mock.calls.filter( ([command]) => command === 'run_limited_local_command', ).length; @@ -3888,6 +3977,7 @@ describe('AI 游戏创作 App 界面边界', () => { expect(screen.getByText(/查看专业组进度:\/groups/)).not.toBeNull(); expect(screen.getByText(/查看质量检查清单:\/qa/)).not.toBeNull(); expect(screen.getByText(/查看最近生成变更:\/changes/)).not.toBeNull(); + expect(screen.getByText(/查看下一轮小步清单:\/todo/)).not.toBeNull(); expect(screen.getByText(/查看素材署名与来源:\/credits/)).not.toBeNull(); expect(screen.getByText(/查看美术素材:\/art/)).not.toBeNull(); expect(screen.getByText(/查看发布准备清单:\/publish/)).not.toBeNull(); @@ -10197,6 +10287,7 @@ describe('AI 游戏创作 App 界面边界', () => { expect(screen.getByText(/\/timeline:查看项目活动时间线/)).not.toBeNull(); expect(screen.getByText(/\/handoff:生成当前项目交接摘要/)).not.toBeNull(); expect(screen.getByText(/\/next:查看下一步建议/)).not.toBeNull(); + expect(screen.getByText(/\/todo:查看下一轮小步清单/)).not.toBeNull(); expect(screen.getByText(/\/publish:查看发布准备清单/)).not.toBeNull(); expect(screen.getByText(/\/listing:准备作品页文案清单/)).not.toBeNull(); expect(screen.getByText(/\/playtest:查看试玩状态与下一步/)).not.toBeNull();