补充项目交接聊天入口

新增聊天侧handoff命令基于当前状态生成项目交接摘要

补充主窗口交接入口和帮助命令测试

同步AI游戏创作App技术方案和共享决策记录
This commit is contained in:
AIGameCreator App
2026-07-03 04:37:08 +08:00
parent 4a6a9d4a2f
commit e47f346561
4 changed files with 144 additions and 2 deletions
+99
View File
@@ -1827,6 +1827,7 @@ const chatCommandHelp = [
'/status:查看项目状态',
'/brief:生成当前项目简报',
'/risks:查看当前项目风险',
'/handoff:生成当前项目交接摘要',
'/next:查看下一步建议',
'/open-project:在系统文件管理器中显示项目目录',
'/switch-project:回到项目启动器切换工作区',
@@ -2127,6 +2128,81 @@ function summarizeProjectRisks(
};
}
function summarizeProjectHandoff(
nextManifest: GameCreationAppManifest,
nextProjectPath: string,
trace: GameCreationAgentRunTrace | null,
history: AgentRunHistoryItem[],
agents: AgentStatusCard[],
) {
const tasks = taskRowsFromManifest(nextManifest);
const completedCount = tasks.filter(
(task) => task.status === 'completed',
).length;
const manifestTasksById = new Map(tasks.map((task) => [task.id, task]));
const traceTasksById = new Map(
trace?.taskGraph.tasks.map((task) => [task.id, task]) ?? [],
);
const traceReadyTasks =
trace?.taskGraph.readyTaskIds
.map((taskId) => traceTasksById.get(taskId) ?? manifestTasksById.get(taskId))
.filter((task): task is GameCreationAppTaskState => Boolean(task)) ?? [];
const readyTasks =
traceReadyTasks.length > 0
? traceReadyTasks
: selectGameCreationAppReadyTasks({ tasks });
const failedTasks = tasks.filter((task) => task.status === 'failed');
const sourceCounts = nextManifest.assets.reduce(
(counts, asset) => {
counts[asset.source.kind] += 1;
return counts;
},
{ uploaded: 0, generated: 0, canvas: 0 },
);
const preview = nextManifest.preview;
const previewSummary =
preview?.status === 'running' && preview.url
? `运行中 ${preview.url}`
: preview
? previewStatusLabels[preview.status]
: '未启动';
const commandRuns = nextManifest.commandRuns ?? [];
const latestCommandRun = commandRuns[commandRuns.length - 1];
const evidenceAgentCount = agents.filter(
(agent) => agent.hasRecentEvidence,
).length;
const activeAgentCount = agents.filter(
(agent) => agent.taskGraphState === 'active',
).length;
const runSummary = trace
? `${trace.runId} · ${formatAgentRunStatus(trace)} · next ${trace.nextStep}`
: history[0]
? `无 latest,最近历史 ${history[0].trace.runId} · ${formatAgentRunStatus(history[0].trace)}`
: '暂无 run';
const readySummary =
readyTasks.length > 0
? readyTasks
.slice(0, 3)
.map((task) => `${task.group}/${task.role} ${task.title}`)
.join('')
: '暂无';
const failedSummary =
failedTasks.length > 0
? failedTasks
.slice(0, 3)
.map((task) => `${task.group}/${task.role} ${task.title}`)
.join('')
: '暂无';
return `项目交接:\n- 项目:${nextManifest.name}\n- 目录:${nextProjectPath}\n- Run${runSummary}\n- 任务:完成 ${completedCount}/${tasks.length} · ready ${readyTasks.length} · 失败 ${failedTasks.length}\n- Ready${readySummary}\n- 失败项:${failedSummary}\n- 资产:${nextManifest.assets.length} 个 · 上传 ${sourceCounts.uploaded} / 生成 ${sourceCounts.generated} / 画板 ${sourceCounts.canvas}\n- 预览:${previewSummary}\n- Agent${evidenceAgentCount}/${agents.length} 有运行证据 · active ${activeAgentCount}\n- 历史:已加载 ${history.length} 个 run\n- 最近命令:${
latestCommandRun
? `${latestCommandRun.commandId} · ${
latestCommandRun.status === 'completed' ? '完成' : '失败'
}`
: '暂无'
}`;
}
function summarizeNextProjectActions(
nextManifest: GameCreationAppManifest,
trace: GameCreationAgentRunTrace | null,
@@ -5499,6 +5575,29 @@ export function App() {
return;
}
if (prompt === '/handoff') {
const nextProjectPath = requireChatProjectForUserAction();
if (!nextProjectPath) {
return;
}
setMessages((current) => [
...current,
{
role: 'assistant',
text: summarizeProjectHandoff(
manifest,
nextProjectPath,
agentRunTrace,
agentRunHistory,
agentStatusCards,
),
draftCommand: '/next',
draftCommandLabel: '查看下一步',
},
]);
return;
}
if (prompt === '/next') {
if (!requireChatProjectForUserAction()) {
return;
@@ -2168,6 +2168,41 @@ describe('AI 游戏创作 App 界面边界', () => {
),
).toHaveLength(runLocalCountBeforeRisks);
const manifestReadCountBeforeHandoff = invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
).length;
const fileReadCountBeforeHandoff = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
const runLocalCountBeforeHandoff = invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
).length;
submitChat('/handoff');
expect(await screen.findByText(/项目交接:/)).not.toBeNull();
expect(screen.getByText(/- Runrun-main-shortcut-trace/)).not.toBeNull();
expect(screen.getByText(/Readyart\/Asset 规划美术资产/)).not.toBeNull();
expect(screen.getByText(/历史:已加载 0 个 run/)).not.toBeNull();
const nextActionButtons = screen.getAllByRole('button', {
name: '查看下一步',
});
fireEvent.click(nextActionButtons[nextActionButtons.length - 1]);
expect(screen.getByLabelText('创作想法')).toHaveProperty('value', '/next');
expect(
invoke.mock.calls.filter(
([command]) => command === 'get_local_game_manifest',
),
).toHaveLength(manifestReadCountBeforeHandoff);
expect(
invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
),
).toHaveLength(fileReadCountBeforeHandoff);
expect(
invoke.mock.calls.filter(
([command]) => command === 'run_limited_local_command',
),
).toHaveLength(runLocalCountBeforeHandoff);
const fileReadCountBeforeRuns = invoke.mock.calls.filter(
([command]) => command === 'read_local_project_file',
).length;
@@ -2392,7 +2427,11 @@ describe('AI 游戏创作 App 界面边界', () => {
fireEvent.click(screen.getByRole('button', { name: 'Trace' }));
expect(
await screen.findByText(/Runrun-main-shortcut-trace/),
await screen.findByText(
(content) =>
content.trimStart().startsWith('Runrun-main-shortcut-trace') &&
content.includes('产物快照:'),
),
).not.toBeNull();
expect(screen.getByText(/产物快照:/)).not.toBeNull();
expect(
@@ -7791,6 +7830,7 @@ describe('AI 游戏创作 App 界面边界', () => {
).not.toBeNull();
expect(screen.getByText(/\/brief:生成当前项目简报/)).not.toBeNull();
expect(screen.getByText(/\/risks:查看当前项目风险/)).not.toBeNull();
expect(screen.getByText(/\/handoff:生成当前项目交接摘要/)).not.toBeNull();
expect(screen.getByText(/\/next:查看下一步建议/)).not.toBeNull();
expect(
screen.getByText(
@@ -3858,6 +3858,7 @@
- 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 调整:普通用户通过聊天输入 `/handoff` 生成当前项目交接摘要,只基于主窗口当前已加载的 manifest、授权项目路径、最近 run trace、Agent 状态和已加载 run 历史生成交接信息,并提供 `/next` 后续草稿;该入口不得触发 Tauri 读写、不得读取文件、不得启动或打开预览,也不得新增普通用户面板。
- 2026-07-03 调整:普通用户通过聊天输入 `/runs` 查看已加载 Run 历史读取命令,只基于主窗口当前已加载的 latest trace 和最多 100 个历史 run 中已经载入的批次生成 `/trace``/read .agent/runs/...` 草稿;该入口不得额外触发 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`
File diff suppressed because one or more lines are too long