diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index 50f3050c7..a468efd86 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -19,8 +19,6 @@ "config": "node scripts/game-creator-config-wizard.mjs", "test:chat": "node scripts/agent-swarm-test-chat.mjs --task \"制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。\" --no-open", "test:chat:manual": "node scripts/agent-swarm-test-chat.mjs", - "test:plan": "node scripts/agent-swarm-test-chat.mjs --plan --task \"我想做一款原创横版像素解谜小游戏,主角是一个能操控自己影子的小机器人,影子可以变成平台和开关。请完成立项策划并给出 Fast GDD。主题、角色名与视觉语言必须原创,不使用任何现有游戏角色、名称、Logo 或受保护视觉语言。\"", - "test:plan:manual": "node scripts/agent-swarm-test-chat.mjs --plan", "agent-run": "node scripts/run-cli-with-config.mjs --agent-run", "agent-run:smoke": "node scripts/smoke-agent-run-local-provider.mjs", "agent-runtime:real-e2e": "node scripts/agent-runtime-real-e2e.mjs", diff --git a/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs b/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs index e170b07c6..14a529600 100644 --- a/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs +++ b/apps/ai-game-creator-shell/scripts/agent-swarm-test-chat.mjs @@ -36,8 +36,6 @@ export const ungeneratedGameEntryMarker = '还没有生成游戏。回到聊天输入创意并确认生成后'; export const defaultRealSwarmTestTask = '制作一个可直接试玩的原创植物塔防小游戏:玩家选择并放置原创守卫阻挡敌人,完成波次后可以进入下一关并重新开始。主题、单位名称与视觉语言必须原创,不使用任何现有游戏角色、单位名、Logo 或受保护视觉语言。请自主完成正式产物、静态检查和双视口试玩验证。'; -export const defaultRealSwarmPlanTask = - '我想做一款原创横版像素解谜小游戏,主角是一个能操控自己影子的小机器人,影子可以变成平台和开关。请完成立项策划并给出 Fast GDD。主题、角色名与视觉语言必须原创,不使用任何现有游戏角色、名称、Logo 或受保护视觉语言。'; export const swarmTurnReportPrefix = '[turn.report] '; export const swarmTurnReportSchema = 'game-creator-swarm-turn-report.v1'; @@ -140,15 +138,10 @@ export const usage = `用法: --keep-project 保留自动创建的一次性项目 --no-open 手工模式启动预览但不自动打开浏览器 --task <需求> 通过 manual 入口非交互提交自定义需求 - --plan 走「做方案」立项策划入口,不做游戏,不做产物验收和试玩 - --timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,--plan 默认 6 分钟,手工模式默认不限时 + --timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,手工模式默认不限时 --dry-run 只检查目录发现和项目准备,不启动 LLM -h, --help 显示帮助 - -环境变量: - AGC_PLAN_GDD_DECISION 审批卡自动应答动作,默认 approve;revise/reject 必须 - 同时用 AGC_PLAN_GDD_COMMENT 给出真实修改意见 - AGC_PLAN_GDD_COMMENT revise/reject 的意见原文`; +`; function readOptionValue(args, index, option) { const value = args[index + 1]?.trim(); @@ -177,7 +170,6 @@ export function parseSwarmTestArguments(args) { keepProject: false, openBrowser: true, task: null, - plan: false, timeoutMinutes: null, dryRun: false, help: false, @@ -202,8 +194,6 @@ export function parseSwarmTestArguments(args) { if (task.length > 4_000) throw new Error('--task 不能超过 4000 字符'); options.task = task; index += 1; - } else if (argument === '--plan') { - options.plan = true; } else if (argument === '--timeout-minutes') { if (options.timeoutMinutes !== null) { throw new Error('--timeout-minutes 只能指定一次'); @@ -222,16 +212,11 @@ export function parseSwarmTestArguments(args) { } export function shouldStartPersistentPreview(options) { - // 立项策划链路只出 GDD,没有可试玩产物,任何模式都不该起预览。 - return !options.task && !options.plan; + return !options.task; } export function resolveSwarmTestTimeoutMs(options) { - // 立项策划的设计目标是五分钟出方案,给一分钟余量;再久就是卡住了,早失败 - // 比让 harness 空等更有用。做游戏那条链路的 50 分钟不变。 - const planMinutes = options.plan ? 6 : null; - const minutes = - options.timeoutMinutes ?? planMinutes ?? (options.task ? 50 : null); + const minutes = options.timeoutMinutes ?? (options.task ? 50 : null); return minutes === null ? null : minutes * 60_000; } @@ -979,25 +964,12 @@ export function swarmAutoPilotShouldCloseInput(output, promptsAfterSubmit) { return swarmAutoPilotSitsAtPrompt(output) && promptsAfterSubmit >= 1; } -// GDD 审批位不能等 CLI 退出之后再处理:Run 停在这里时状态是 waiting-for-user-input, -// 而 swarm CLI 恰好把这个状态算作「本轮还在跑」,turn 永远不 settle,CLI 也就永远 -// 不退出。所以审批必须在 CLI 还活着的时候并发做完,让 Run 自己继续跑到收束。 -// 这一句是 PlanGddCompletionBlockerKind::AwaitingApprovalDecision 专有的投影文案, -// 另外三个 blocked 子状态都不会打出它;即便认错了,真正的判据也是随后那次 -// --plan-gdd-status,没有待决定审批时不会有任何写入。 -const planGddApprovalWaitPattern = /等待 Fast GDD 审批决定/u; - -export function swarmOutputAwaitsPlanGddApproval(line) { - return planGddApprovalWaitPattern.test(line); -} - async function runTaskCargo( cliArguments, task, setActiveChild, timeoutMs, autoPilot = false, - onPlanGddApprovalWait = null, ) { const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), { stdio: ['pipe', 'pipe', 'inherit'], @@ -1009,25 +981,6 @@ async function runTaskCargo( let taskSubmitted = false; let promptsSeen = 0; let sittingAtPrompt = false; - let planGddApproval = null; - let planGddApprovalError = null; - let planGddApprovalStarted = false; - let planGddApprovalPromise = null; - const startPlanGddApproval = () => { - planGddApprovalStarted = true; - console.log( - `[自动审批] 检测到 Fast GDD 审批位,正在提交 ${resolvePlanGddAutoDecision().action}`, - ); - planGddApprovalPromise = onPlanGddApprovalWait() - .then((value) => { - planGddApproval = value; - }) - .catch((error) => { - planGddApprovalError = error; - // 审批没成的话 Run 会一直停在等待位,干等到超时只会把真正的原因埋掉。 - void terminateChildTree(child).catch(() => {}); - }); - }; child.stdout.setEncoding('utf8'); child.stdout.on('data', (chunk) => { process.stdout.write(chunk); @@ -1040,13 +993,6 @@ async function runTaskCargo( reportLines.push(normalizedLine); settled = true; } - if ( - onPlanGddApprovalWait && - !planGddApprovalStarted && - swarmOutputAwaitsPlanGddApproval(normalizedLine) - ) { - startPlanGddApproval(); - } } if (!autoPilot || child.stdin.writableEnded) return; const atPrompt = swarmAutoPilotSitsAtPrompt(pendingLine); @@ -1085,12 +1031,9 @@ async function runTaskCargo( if (normalizedPendingLine.startsWith(swarmTurnReportPrefix)) { reportLines.push(normalizedPendingLine); } - await planGddApprovalPromise; - if (planGddApprovalError) throw planGddApprovalError; return { ...result, turnReportOutput: reportLines.join('\n'), - planGddApproval, }; } finally { setActiveChild(null); @@ -1780,196 +1723,6 @@ export async function validateSwarmProjectArtifacts(projectPath, options) { return inspection; } -// 这四条路径的权威定义都在 Rust 侧 `planning_storage.rs`(`PLAN_SESSION_PATH`、 -// `PLAN_GDD_INDEX_PATH`、`PLAN_STORAGE_ROOT`、`PLAN_FAST_GDD_PATH`)。跨语言没有共享 -// 常量的通道,改路径时要连同 `GddApprovalCard.tsx` 一起动。 -export const planningOutputPaths = [ - '.agent/planning/session.json', - '.agent/planning/index.json', - '.agent/planning/pending.json', - 'game/fast_gdd.md', -]; - -export async function inspectPlanningOutputs(projectPath) { - const outputs = []; - for (const relativePath of planningOutputPaths) { - const absolutePath = path.join(projectPath, ...relativePath.split('/')); - const metadata = await lstat(absolutePath).catch((error) => { - if (error?.code === 'ENOENT') return null; - throw error; - }); - outputs.push({ - path: relativePath, - exists: Boolean(metadata?.isFile()), - bytes: metadata?.isFile() ? metadata.size : 0, - }); - } - return outputs; -} - -async function reportPlanningOutputs(projectPath) { - const outputs = await inspectPlanningOutputs(projectPath); - console.log('\n立项策划产物:'); - for (const output of outputs) { - console.log( - output.exists - ? ` [有] ${output.path}(${output.bytes} 字节)` - : ` [无] ${output.path}`, - ); - } -} - -const planGddApprovalTimeoutMs = 60_000; -export const planGddStatusOutputPrefix = 'planGddStateJson='; -export const planGddDecisionOutputPrefix = 'planGddDecisionJson='; - -function parsePrefixedJsonLine(output, prefix, label) { - const line = output - .split('\n') - .map((value) => (value.endsWith('\r') ? value.slice(0, -1) : value)) - .find((value) => value.startsWith(prefix)); - if (!line) throw new Error(`${label}缺少 ${prefix} 输出`); - try { - return JSON.parse(line.slice(prefix.length)); - } catch (error) { - throw new Error(`解析${label}失败:${error.message}`); - } -} - -export function parsePlanGddStatusOutput(output) { - return parsePrefixedJsonLine( - output, - planGddStatusOutputPrefix, - 'Fast GDD 审批状态', - ); -} - -export function parsePlanGddDecisionOutput(output) { - return parsePrefixedJsonLine( - output, - planGddDecisionOutputPrefix, - 'Fast GDD 审批回执', - ); -} - -// 审批卡是这条链路唯一的人类判据,所以自动应答默认只投 approve,且只在投影确实有 -// 一张待决定审批时出手。revise/reject 需要一段真实的修改意见,让机器编一段等于把 -// 判据换成噪声——所以那两条分支只在跑的人自己用 AGC_PLAN_GDD_COMMENT 给出意见时 -// 才走。手工调 --plan-gdd-decide 也能达到同样效果,但那要求 plan 根 run 仍然活着, -// 而它恰好是本进程持有的 CLI 子进程。 -export function planGddAutoApprovalIsPending(state) { - return Boolean(state?.pendingApproval); -} - -export function resolvePlanGddAutoDecision(env = process.env) { - const action = (env.AGC_PLAN_GDD_DECISION ?? 'approve').trim(); - if (!['approve', 'revise', 'reject'].includes(action)) { - throw new Error('AGC_PLAN_GDD_DECISION 只能是 approve / revise / reject'); - } - const comment = (env.AGC_PLAN_GDD_COMMENT ?? '').trim(); - if (action === 'approve') return { action, comment: null }; - if (!comment) { - throw new Error( - `${action} 必须同时设 AGC_PLAN_GDD_COMMENT 提供真实修改意见`, - ); - } - return { action, comment }; -} - -async function settlePlanGddApproval( - projectPath, - runtimeConfigPath, - setActiveChild, -) { - const readStatus = async () => { - const result = await runCapturedCargo( - ['--config-dir', runtimeConfigPath, '--plan-gdd-status', projectPath], - setActiveChild, - { - timeoutMs: planGddApprovalTimeoutMs, - label: 'Fast GDD 审批状态查询', - }, - ); - if (result.code !== 0 || result.signal) { - throw new Error( - `读取 Fast GDD 审批状态失败:${result.stderr.trim() || result.stdout.trim()}`, - ); - } - return parsePlanGddStatusOutput(result.stdout); - }; - - const before = await readStatus(); - if (!planGddAutoApprovalIsPending(before)) { - return { decided: false, state: before }; - } - const { action, comment } = resolvePlanGddAutoDecision(); - const decision = await runCapturedCargo( - [ - '--config-dir', - runtimeConfigPath, - '--plan-gdd-decide', - projectPath, - action, - ...(comment === null ? [] : ['--stdin']), - ], - setActiveChild, - { - timeoutMs: planGddApprovalTimeoutMs, - label: 'Fast GDD 审批决定', - stdin: comment, - }, - ); - if (decision.code !== 0 || decision.signal) { - throw new Error( - `提交 Fast GDD 审批决定失败:${decision.stderr.trim() || decision.stdout.trim()}`, - ); - } - const receipt = parsePlanGddDecisionOutput(decision.stdout); - // 回执落盘和唤醒后台任务是两件事:decide 命令把唤醒失败降级成 recoveryPending, - // 于是审批已经生效、Run 却仍停在 waiting-for-user-input。实测就是这样——只有 - // 补一次 --agent-resume 才会重新起 turn。这是仓库自己给这个状态定义的恢复动作。 - let recovered = false; - if (receipt.recoveryPending) { - const resume = await runCapturedCargo( - ['--config-dir', runtimeConfigPath, '--agent-resume', projectPath], - setActiveChild, - { - timeoutMs: planGddApprovalTimeoutMs, - label: 'Fast GDD 审批后恢复后台任务', - }, - ); - if (resume.code !== 0 || resume.signal) { - throw new Error( - `审批已提交但恢复后台任务失败:${resume.stderr.trim() || resume.stdout.trim()}`, - ); - } - recovered = true; - } - return { decided: true, receipt, recovered, state: await readStatus() }; -} - -async function reportPlanGddApproval(approval) { - const { state } = approval; - console.log('\nFast GDD 审批:'); - if (!approval.decided) { - console.log(` [无待决定审批] 当前投影状态=${state.state}`); - return; - } - console.log( - ` [已决定 ${approval.receipt.decisionRef.action}] outcome=${approval.receipt.outcome} v${approval.receipt.decisionRef.version} 投影状态=${state.state}`, - ); - if (approval.recovered) { - console.log( - ' [已恢复] 审批回执的 recoveryPending 由一次 --agent-resume 收口', - ); - } - if (state.session) { - console.log( - ` 澄清轮次=${state.session.clarificationRound} 返工深度=${state.session.repairDepth} phase=${state.session.phase}`, - ); - } -} - export async function hasConfiguredEditorApiKey(configDir) { let configured = false; for (const fileName of [configFileName, localConfigFileName]) { @@ -2122,13 +1875,7 @@ export async function runSwarmTestChat(options) { const setActiveChild = (child) => { activeChild = child; }; - // GDD 审批要和 swarm CLI 并发跑,两者不能共用 activeChild 这一个槽位:审批子进程 - // 结束时的 setActiveChild(null) 会把 CLI 从槽里抹掉,Ctrl-C 就杀不到它了。 const concurrentChildren = new Set(); - const setConcurrentChild = (child) => { - if (child) concurrentChildren.add(child); - else concurrentChildren.clear(); - }; const stopRequested = () => receivedSignal !== null; const handleSignal = (signal) => { const repeatedSignal = receivedSignal !== null; @@ -2206,11 +1953,10 @@ export async function runSwarmTestChat(options) { ); } console.log('LLM 配置已就绪。'); - const requirementNoun = options.plan ? '立项策划需求' : '游戏需求'; console.log( options.task - ? `已提交一条非交互${requirementNoun},正在等待 Swarm 自主完成。\n` - : `输入一条${requirementNoun}并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n`, + ? '已提交一条非交互游戏需求,正在等待 Swarm 自主完成。\n' + : '输入一条游戏需求并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n', ); phase = 'chat'; @@ -2220,8 +1966,7 @@ export async function runSwarmTestChat(options) { runtimeConfig.path, '--swarm-chat', '--init', - // 做方案链路只能跑 standard 档,后端对 plan + autonomous 是硬否决。 - options.plan ? '--plan' : '--autonomous-game-build', + '--autonomous-game-build', project.path, ]; let chat; @@ -2234,15 +1979,6 @@ export async function runSwarmTestChat(options) { timeoutDeadline === null ? null : Math.max(1, timeoutDeadline - Date.now()), - options.plan, - options.plan - ? () => - settlePlanGddApproval( - project.path, - runtimeConfig.path, - setConcurrentChild, - ) - : null, ) : await runInteractiveCargo(chatArguments, setActiveChild); } catch (error) { @@ -2258,32 +1994,6 @@ export async function runSwarmTestChat(options) { if (options.task) { turnReport = parseSettledSwarmTurnReport(chat.turnReportOutput); } - if (options.plan) { - // 立项策划不出游戏产物,正式验收在 GDD 审批卡上;这里只报告落盘情况, - // 是否收束已经由 CLI 的退出码判过了。 - // 自动任务档的审批已经在 CLI 运行期间并发做完了;手工档(人自己敲 Ctrl+D - // 退出)没有那次触发,退出后补一次,没有待决定审批时它是只读的。 - phase = 'plan-approval'; - const approval = - chat.planGddApproval ?? - (await settlePlanGddApproval( - project.path, - runtimeConfig.path, - setConcurrentChild, - )); - if (receivedSignal) break session; - phase = 'plan-report'; - await reportPlanGddApproval(approval); - await reportPlanningOutputs(project.path); - phase = 'complete'; - console.log( - approval.decided - ? '\n立项策划链路已收束:Fast GDD 已批准,策划产物见上方清单。' - : '\n立项策划链路已收束:Run 正常结束但没有待决定审批,策划产物见上方清单。', - ); - break session; - } - phase = 'artifact-validation'; const requireEditorImages = await hasConfiguredEditorApiKey( runtimeConfig.path, ); diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/GddApprovalCard.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/GddApprovalCard.tsx index 5deaaae93..3018200fb 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/GddApprovalCard.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/GddApprovalCard.tsx @@ -7,12 +7,10 @@ import type { } from '../../app/types'; /** - * 立项策划唯一的产品产物,由 `plan.submit_gdd` 与审批回执渲染到项目内。 + * 立项策划唯一的产品产物,由策划会话提交与审批回执渲染到项目内。 * - * 权威定义在 Rust 侧 `planning_storage.rs` 的 `PLAN_FAST_GDD_PATH`,那里同时管着渲染 - * 落盘和写入守卫。跨语言没有共享常量的通道,这里只能重复一份;改路径时三处要一起动: - * `PLAN_FAST_GDD_PATH`、这里、以及 `scripts/agent-swarm-test-chat.mjs` 的 - * `planningOutputPaths`。 + * 权威定义在 Rust 侧 `planning_gdd_model.rs` 的 `PLAN_FAST_GDD_PATH`,那里同时管着渲染 + * 落盘和写入守卫。跨语言没有共享常量的通道,这里只能重复一份;改路径时两处要一起动。 */ const PLAN_FAST_GDD_RELATIVE_PATH = 'game/fast_gdd.md'; diff --git a/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts b/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts index 71213e1c5..f0152b2ec 100644 --- a/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts +++ b/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts @@ -26,7 +26,6 @@ import { cleanupSwarmTestProject, cleanupSwarmTestRuntimeConfig, configFileName, - defaultRealSwarmPlanTask, defaultRealSwarmTestTask, defaultRuntimeConfigDirCandidates, discoverRuntimeConfigDir, @@ -36,12 +35,9 @@ import { inspectSwarmProjectArtifacts, localConfigFileName, nextSwarmAutoPilotReply, - parsePlanGddDecisionOutput, - parsePlanGddStatusOutput, parseRunnerShutdownOutput, parseSettledSwarmTurnReport, parseSwarmTestArguments, - planGddAutoApprovalIsPending, prepareSwarmTestProject, prepareSwarmTestRuntimeConfig, removeDirectoryWithTimeout, @@ -51,7 +47,6 @@ import { shouldStartPersistentPreview, swarmAutoPilotShouldCloseInput, swarmAutoPilotSitsAtPrompt, - swarmOutputAwaitsPlanGddApproval, terminateChildTree, testProjectPrefix, testProjectSentinelName, @@ -658,7 +653,6 @@ describe('Swarm test argument parsing', () => { keepProject: false, openBrowser: true, task: null, - plan: false, timeoutMinutes: null, dryRun: false, help: false, @@ -679,7 +673,6 @@ describe('Swarm test argument parsing', () => { '--no-open', '--task', '生成一款可试玩的塔防游戏', - '--plan', '--timeout-minutes', '75', '--dry-run', @@ -691,7 +684,6 @@ describe('Swarm test argument parsing', () => { keepProject: true, openBrowser: false, task: '生成一款可试玩的塔防游戏', - plan: true, timeoutMinutes: 75, dryRun: true, help: true, @@ -700,7 +692,7 @@ describe('Swarm test argument parsing', () => { expect(() => parseSwarmTestArguments(['--task', ''])).toThrowError(); }); - it('auto-answers only the prompts the plan lane actually blocks on', () => { + it('auto-answers only the confirmation and choice prompts', () => { expect( nextSwarmAutoPilotReply( '[待确认] agent=project-supervisor run=r action=a tool=agent.delegate\n输入 approve 或 reject:', @@ -740,75 +732,6 @@ describe('Swarm test argument parsing', () => { ).toBe(false); }); - it('triggers the approval while the chat CLI is still running', () => { - // Run 停在审批位时状态是 waiting-for-user-input,而 swarm CLI 把这个状态算作 - // 「本轮还在跑」,turn 永远不 settle。等 CLI 退出再审批那一步根本到不了,所以 - // 触发必须认这条运行期状态行。 - expect( - swarmOutputAwaitsPlanGddApproval( - '[状态] project-supervisor running/waiting-for-user-input run=r queue=0/1/0/0 | 等待 Fast GDD 审批决定', - ), - ).toBe(true); - // 另外三个 plan_gdd blocked 子状态各有自己的文案,都不该触发审批。 - expect( - swarmOutputAwaitsPlanGddApproval( - '[状态] project-supervisor running/planning | 推进本根 Run 的 Fast GDD 提交', - ), - ).toBe(false); - expect( - swarmOutputAwaitsPlanGddApproval( - '[状态] project-supervisor running/planning | 等待 Fast GDD 审批投影收尾', - ), - ).toBe(false); - expect( - swarmOutputAwaitsPlanGddApproval( - '[状态] project-supervisor needs-reconciliation | Fast GDD 审批投影需要人工核对', - ), - ).toBe(false); - }); - - it('reads the headless Fast GDD approval projection off the CLI output', () => { - const state = parsePlanGddStatusOutput( - [ - 'plan.gdd.status', - 'planGddStateJson={"state":"ready_for_approval","pendingApproval":{"gddRef":{"gddId":"gdd-1","version":1,"fingerprint":"fp"},"pendingActionId":"action-1","approvalRequestId":"approval-1"},"session":{"clarificationRound":2,"phase":"awaiting_gdd_approval"}}', - '', - ].join('\n'), - ); - expect(state.state).toBe('ready_for_approval'); - expect(state.session.clarificationRound).toBe(2); - expect(planGddAutoApprovalIsPending(state)).toBe(true); - // CLI 在 Windows 上按行输出会带 CR,解析必须先剥掉再判前缀。 - expect( - parsePlanGddStatusOutput('planGddStateJson={"state":"approved"}\r\n') - .state, - ).toBe('approved'); - expect( - parsePlanGddDecisionOutput( - 'plan.gdd.decided\nplanGddDecisionJson={"outcome":"decided","decisionRef":{"version":1}}', - ).outcome, - ).toBe('decided'); - }); - - it('auto-approves only when the projection really carries a pending card', () => { - // 审批状态是「有没有待决定」的唯一权威。没有待决定时自动批准会把一次空跑 - // 报成通过,所以这里必须 false 而不是继续往下走。 - expect( - planGddAutoApprovalIsPending({ - state: 'approved', - pendingApproval: null, - }), - ).toBe(false); - expect(planGddAutoApprovalIsPending({ state: 'draft' })).toBe(false); - expect(planGddAutoApprovalIsPending(null)).toBe(false); - // 缺前缀或 JSON 坏掉都要报错,不能静默当成「没有待决定」。 - expect(() => parsePlanGddStatusOutput('plan.gdd.status')).toThrowError(); - expect(() => - parsePlanGddStatusOutput('planGddStateJson={oops'), - ).toThrowError(); - expect(() => parsePlanGddDecisionOutput('plan.gdd.decided')).toThrowError(); - }); - it('keeps persistent preview only for manual chat mode', () => { expect(shouldStartPersistentPreview(parseSwarmTestArguments([]))).toBe( true, @@ -818,32 +741,6 @@ describe('Swarm test argument parsing', () => { parseSwarmTestArguments(['--task', '生成一款可试玩的塔防游戏']), ), ).toBe(false); - // 做方案链路不出可试玩产物,交互模式同样不该起预览。 - expect( - shouldStartPersistentPreview(parseSwarmTestArguments(['--plan'])), - ).toBe(false); - }); - - it('bounds the plan lane to its own five-minute design target', () => { - // 交互模式也要有上限:立项策划没有可试玩产物可等,卡住就该早失败。 - expect(resolveSwarmTestTimeoutMs(parseSwarmTestArguments(['--plan']))).toBe( - 6 * 60_000, - ); - expect( - resolveSwarmTestTimeoutMs( - parseSwarmTestArguments([ - '--plan', - '--task', - '做一个解谜游戏的立项方案', - ]), - ), - ).toBe(6 * 60_000); - // 显式 --timeout-minutes 仍然压过默认值。 - expect( - resolveSwarmTestTimeoutMs( - parseSwarmTestArguments(['--plan', '--timeout-minutes', '20']), - ), - ).toBe(20 * 60_000); }); it('applies a bounded default only to non-interactive tasks', () => { @@ -1964,12 +1861,6 @@ describe('package script registration', () => { expect(rootPackage.scripts?.['agc:test:chat:manual']).toBe( 'npm --prefix apps/ai-game-creator-shell run test:chat:manual --', ); - expect(rootPackage.scripts?.['agc:test:plan']).toBe( - 'npm --prefix apps/ai-game-creator-shell run test:plan --', - ); - expect(rootPackage.scripts?.['agc:test:plan:manual']).toBe( - 'npm --prefix apps/ai-game-creator-shell run test:plan:manual --', - ); expect(appPackage.scripts?.['test:chat']).toBe( `node scripts/agent-swarm-test-chat.mjs --task "${defaultRealSwarmTestTask}" --no-open`, ); @@ -1979,12 +1870,6 @@ describe('package script registration', () => { expect(appPackage.scripts?.['test:chat:manual']).toBe( 'node scripts/agent-swarm-test-chat.mjs', ); - expect(appPackage.scripts?.['test:plan']).toBe( - `node scripts/agent-swarm-test-chat.mjs --plan --task "${defaultRealSwarmPlanTask}"`, - ); - expect(appPackage.scripts?.['test:plan:manual']).toBe( - 'node scripts/agent-swarm-test-chat.mjs --plan', - ); expect(checkConfigSource).toMatch( /packageConfig\.scripts\?\.config\s*!==\s*'node scripts\/game-creator-config-wizard\.mjs'/u, ); diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index bc7953782..dac81e41c 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -8156,3 +8156,11 @@ CI 上 `background_agent_runtime_recovers_stale_running_before_pending_task` 在 - 配置契约提升为 `game-creator-config.v2`:新模板默认 `stream=true`、受控搜索开启;无版本旧 DirectProject 配置仅在省略 `webSearchEnabled` 时把历史默认补为开启,旧配置显式 `false` 不覆盖,v2 显式 `false` 同样保留;Provider / Anthropic 未提供搜索覆盖时保持关闭。本地覆盖配置只补 schema 版本,不凭不完整 overlay 推断或写入 `agentMode` / 搜索布尔值;未知版本失败关闭。 - 搜索结果始终是不可信外部输入,只可作为资料;工具 schema、参数、客户端权限、Agent 身份、系统规则和工具协议不得由网页内容修改。搜索结果与状态投影不携带 API Key、请求头、宿主绝对路径或 Provider 原始错误正文。 - 验证锁定:`configuration`、`direct_tools_mcp`、`direct_tool_bridge`、`codex_app_server` 定向 Rust 测试,以及前端状态格式化 / AppSurface 测试;真实 Provider 登录态与真实公网搜索仍需单独现场 smoke。 + +## 2026-09-08 策划 V1 链路源码整体退役(四不写) + +- 策划会话 Runtime V2 全量接管“做方案”入口后,旧 V1 链路(`project-supervisor-plan` 根 Run、`project-planning` 子 Agent、`plan.submit_gdd` 工具、Fast GDD 审批门禁与恢复车道)按四不写原则整体删除源码,不保留兼容实现、墓碑注释或防御性测试。 +- 删除范围:`runtime_protocol` 六个 V1 模块与四条 V1 提示词、prompt manifest planning 目录与生成常量、`capabilities.planning` 配置开关、CLI `--swarm-chat --plan` 与 `--plan-gdd-status/--plan-gdd-decide`、provider_retry 的 planning session binding、provider_action_batch 的 V1 提交批次校验、main_loop/pending_recovery/recovery_scan/acceptance_graph 的 plan 根特判、agent_db 三条 V1 专用持久车道(`agent.runtime.plan.provider_usage`、`agent.runtime.plan.gdd_decided`、`agent.runtime.plan_submit_gdd.committed`)及预留配额、前端旧 V1 读模型、`scripts/agent-swarm-test-chat.mjs --plan` 与 `test:plan*` 脚本。 +- 保留项:V2 与 V1 共用的 GDD 数据模型收敛到 `runtime_protocol/planning_gdd_model.rs`;`.agent/planning/` 旧文件不迁移、不删除,旧 `fast_gdd.md` 仍可只读打开;前端 `PROJECT_SUPERVISOR_PLAN_SOURCE` 字符串与 `GddApprovalCard.tsx`(`PlanGddSurface`)是 V2 现役的适配/展示面,不属于退役对象。 +- 旧 sidecar 中带已删字段(`clarification`、`continuation`、`requestId`、`turnId`、`pendingApproval` 等)的记录会因 `deny_unknown_fields` 拒绝反序列化,这是退役语义的一部分,不做迁移。 +- 测试基线说明:收尾时测试套件存在 25 个既有失败(mock LLM 时序敏感类,HEAD worktree 对照验证与本次无关),后续清理时不要误记到本次退役头上。 diff --git a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md index 0ada7c977..48a74dd94 100644 --- a/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md +++ b/docs/technical/【技术方案】立项策划Agent(Fast GDD)-2026-08-10.md @@ -1,6 +1,7 @@ # 立项策划 Agent(Fast GDD)技术方案 - 日期:2026-08-10 +- 状态:**已退役**。本文描述的 V1 策划链路(`project-supervisor-plan` 根 Run、`project-planning` 子 Agent、`plan.submit_gdd` 工具、Fast GDD 审批门禁与恢复机制)已由策划会话 Runtime V2 取代,源码已于 2026-09 按四不写原则整体删除;现行方案见 `【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md`。本文仅作为历史推导记录保留。 - 适用范围:AI 游戏创作独立 App、Project Supervisor、Agent Runtime、本地项目策划 sidecar 与后续完整构建准入 > 当前口径(2026-08-30):以本文件中标注的 D11 / 最新修订和当前 `apps/ai-game-creator-shell` 实现为准。D6~D9 等被明确标注为作废或被取代的段落仅保留推导背景,不得作为现行拓扑、入口或 Runtime 真相;产品入口与 DirectProject 总体口径见 `docs/README.md` 和 App 实施计划。 diff --git a/docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md b/docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md index 24d9ce051..4700531ac 100644 --- a/docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md +++ b/docs/technical/【技术方案】策划会话RuntimeV2接入与旧链路退役-2026-09-03.md @@ -1,7 +1,7 @@ # 策划会话 Runtime V2 接入与旧链路退役方案 - 日期:2026-09-03 -- 状态:P0 合同冻结、P1 内核、P2 产物闭环、P3 入口/UI 接入和 P4 灰度回归验收已完成;P5 待实施;本文是新生产实现的目标方案与阶段验收合同 +- 状态:P0 合同冻结、P1 内核、P2 产物闭环、P3 入口/UI 接入、P4 灰度回归验收与 P5 旧链路退役均已完成;本文是新生产实现的目标方案与阶段验收合同 - 适用范围:AGC 桌面 App 的“做方案”入口、策划会话、GDD 产物与审批 > 本文规定新策划 Agent 的生产接入和旧链路退役方式。P3 开始修改正式 AGC 入口与工作台,但旧 `project-supervisor-plan` / `project-planning` 源码仍保留,直到 P5 完成退役;V2 切换时旧链路直接封存,所有未完成旧会话强制失败,旧 Fast GDD 文档之后只作为历史记录依据。 @@ -737,7 +737,7 @@ hydrate_planning_session_v2 - 前端通过 V2 适配层复用现有聊天区、澄清卡、GDD 审批卡和阶段进度条;V2 Session 的 hydrate 结果额外携带 `conversation`,用于刷新/重启恢复历史消息。 - V2 只将结构化工具调用结果投影到澄清选项卡和 GDD 审批卡;旧 Runtime 轮询、专业 Agent 轮询和旧 Runtime 事件在 V2 策划会话中关闭。Provider 中间文本不作为策划用户对话展示。 - 对已存在 V2 Session 的项目,打开项目时先 hydrate V2;没有 V2 authority 的旧项目继续走旧读取路径,避免误把旧项目数据当成 V2。 -- P3 已完成;P4 的真实 Provider、前端工作台、失败恢复和安全门禁验收已通过。旧会话 `legacy_retired` 封存与旧入口彻底关闭仍属于 P5。 +- P3 已完成;P4 的真实 Provider、前端工作台、失败恢复和安全门禁验收已通过。旧会话 `legacy_retired` 封存、旧入口关闭与旧 Runtime 源码删除均已在 P5 完成。 P3 之后的协议修正:V2 不再用正文 JSON 输出问询/GDD;Provider 请求挂 `plan_ask_question` / `plan_submit_gdd`,`tool_choice=required`,形状由工具 schema 承担。system prompt 只保留三项核心闭环等策略和当前问询进度;数量和字数由既有校验器在失败时回灌。`plan_submit_gdd` 的工具参数只包含决定和原型验证内容,不包含任何 Runtime 分配的 ID;Runtime 在落盘前为决定分配首项 `initial-request`、后续 `decision-{序号}`,并按 `prototype_pending` 顺序绑定原型验证项。入参不必回声 `schemaVersion`,落盘 GDD 仍写 `plan-gdd.v2`。失败结果不重复渲染,严格解析和失败不落盘成功产物的规则保持不变。历史 V2 数据不做兼容转换,按现有恢复/失败策略处理。 @@ -767,10 +767,17 @@ P3 之后的协议修正:V2 不再用正文 JSON 输出问询/GDD;Provider 依赖:P3。 -### P5:旧链路退役(最简方案) +### P5:旧链路退役(已完成) 目标:停止新业务进入旧 Supervisor,并将所有未完成旧会话一次性封存为失败。 +执行情况(2026-09):在 P5-1~P5-4 的“入口关闭、会话封存、产物只读”之上,进一步按四不写原则删除了旧 V1 Runtime 源码本体,避免退役代码继续占用编译与维护成本: + +- 前端数据通路与命令入口:`planGddState/planGddDecision` 读模型、`--plan-gdd-status/--plan-gdd-decide` CLI 等旧 V1 投影入口。 +- Runtime 源码:`runtime_protocol` 下 `planning_storage/planning_submit/planning_approval/planning_coordinator/planning_hydrate/planning_provider_usage` 六个模块、prompt manifest 中 planning Agent 目录与 plan sections、四个 V1 提示词文件、`game-creator.config.json` 的 `capabilities.planning` 开关、CLI `--swarm-chat --plan` 入口、provider_retry 的 planning session binding、provider_action_batch 的 V1 提交批次校验、tool_policy/main_loop/pending_recovery/recovery_scan/acceptance_graph 中的 plan 根特判与恢复车道、agent_db 的三条 V1 专用持久车道(`agent.runtime.plan.provider_usage`、`agent.runtime.plan.gdd_decided`、`agent.runtime.plan_submit_gdd.committed`)及对应测试。 +- 测试脚本:`scripts/agent-swarm-test-chat.mjs` 的 `--plan` 模式与自动 GDD 审批回路、根/应用 `package.json` 的 `test:plan*` / `agc:test:plan*` 脚本。 +- 保留项:V2 与 V1 共用的 GDD 数据模型抽到 `planning_gdd_model.rs` 继续复用;`.agent/planning/` 旧文件保持原样只读(不迁移、不删除);`fast_gdd.md` 仍可通过既有命令打开查看;前端 `GddApprovalCard.tsx`(`PlanGddSurface`)是 V2 现役的 GDD 展示面,不属于退役对象。 + 任务: | ID | 任务 | 产出 | diff --git a/docs/technical/【技术说明】AGC接第三方Provider的兼容性缺陷-2026-08-19.md b/docs/technical/【技术说明】AGC接第三方Provider的兼容性缺陷-2026-08-19.md index 521fdf34b..0bc7324d8 100644 --- a/docs/technical/【技术说明】AGC接第三方Provider的兼容性缺陷-2026-08-19.md +++ b/docs/technical/【技术说明】AGC接第三方Provider的兼容性缺陷-2026-08-19.md @@ -176,7 +176,9 @@ error=unable to locate image at `/memory/README.md`: (os error 2) --- -## 4. 缺陷 4:普通 action 批次带 plan update 时预检自相矛盾(已修) +## 4. 缺陷 4:普通 action 批次带 plan update 时预检自相矛盾(已修,2026-09 随策划 V1 退役删除相关代码) + +> 本节引用的 `plan.submit_gdd` 特判、`provider_batch_plan_update` 与 `planning_session_binding` 字段属于已退役的策划 V1 链路,2026-09 已按四不写原则整体删除;回归测试 `supervisor_collaboration_batch_keeps_plan_update_off_members` 保留并收敛为「非 planning 批次的 plan update 不落入成员」这一不变量。 换成 `gpt-5.6-terra` 后 LLM 链路可用,但首次复现曾在第二步「委派 project-planning 产出 Fast GDD」失败。这一条与 Provider 无关,是当时实现引入、现已修复的回归。 diff --git a/package.json b/package.json index 80aa34e94..e7ac811f0 100644 --- a/package.json +++ b/package.json @@ -166,8 +166,6 @@ "agc:test": "npm --prefix apps/ai-game-creator-shell run agent-runtime:supervisor-autonomous-playable-lane-defense-deterministic-e2e --", "agc:test:chat": "npm --prefix apps/ai-game-creator-shell run test:chat --", "agc:test:chat:manual": "npm --prefix apps/ai-game-creator-shell run test:chat:manual --", - "agc:test:plan": "npm --prefix apps/ai-game-creator-shell run test:plan --", - "agc:test:plan:manual": "npm --prefix apps/ai-game-creator-shell run test:plan:manual --", "ai-game-creator-shell:dev": "npm --prefix apps/ai-game-creator-shell run dev", "ai-game-creator-shell:dev-server": "npm --prefix apps/ai-game-creator-shell run dev-server", "ai-game-creator-shell:build": "npm --prefix apps/ai-game-creator-shell run build --",