diff --git a/apps/ai-game-creator-shell/package.json b/apps/ai-game-creator-shell/package.json index 69bd74590..ba1fd4e0e 100644 --- a/apps/ai-game-creator-shell/package.json +++ b/apps/ai-game-creator-shell/package.json @@ -17,6 +17,8 @@ "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 756e17422..246cc297a 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,6 +36,8 @@ 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'; @@ -138,6 +140,7 @@ export const usage = `用法: --keep-project 保留自动创建的一次性项目 --no-open 手工模式启动预览但不自动打开浏览器 --task <需求> 通过 manual 入口非交互提交自定义需求 + --plan 走「做方案」立项策划入口,不做游戏,不做产物验收和试玩 --timeout-minutes <分钟> 设置本次执行期限;自动任务默认 50 分钟,手工模式默认不限时 --dry-run 只检查目录发现和项目准备,不启动 LLM -h, --help 显示帮助`; @@ -169,6 +172,7 @@ export function parseSwarmTestArguments(args) { keepProject: false, openBrowser: true, task: null, + plan: false, timeoutMinutes: null, dryRun: false, help: false, @@ -193,6 +197,8 @@ 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 只能指定一次'); @@ -211,7 +217,8 @@ export function parseSwarmTestArguments(args) { } export function shouldStartPersistentPreview(options) { - return !options.task; + // 立项策划链路只出 GDD,没有可试玩产物,任何模式都不该起预览。 + return !options.task && !options.plan; } export function resolveSwarmTestTimeoutMs(options) { @@ -923,13 +930,32 @@ async function runInteractiveCargo(cliArguments, setActiveChild) { return result; } -async function runTaskCargo(cliArguments, task, setActiveChild, timeoutMs) { +// 立项策划跑 standard 档,`agent.delegate` 这类动作按项目权限策略必须逐个确认, +// 而确认和问询都只从 CLI 的 stdin 读。自主构建档没有这一步,所以只有 --plan 需要 +// 一个把「人坐在终端前敲 approve」自动化掉的应答器;判据本身仍然走后端确认命令。 +const swarmConfirmationPromptPattern = /输入 approve 或 reject:$/u; +const swarmUserInputPromptPattern = /请选择 1-\d+,或直接输入其他答案:$/u; + +export function nextSwarmAutoPilotReply(output) { + if (swarmConfirmationPromptPattern.test(output)) return 'approve'; + if (swarmUserInputPromptPattern.test(output)) return '1'; + return null; +} + +async function runTaskCargo( + cliArguments, + task, + setActiveChild, + timeoutMs, + autoPilot = false, +) { const child = spawnChild(cargoCommand, buildCargoCliArguments(cliArguments), { stdio: ['pipe', 'pipe', 'inherit'], }); setActiveChild(child); const reportLines = []; let pendingLine = ''; + let settled = false; child.stdout.setEncoding('utf8'); child.stdout.on('data', (chunk) => { process.stdout.write(chunk); @@ -940,10 +966,26 @@ async function runTaskCargo(cliArguments, task, setActiveChild, timeoutMs) { const normalizedLine = line.endsWith('\r') ? line.slice(0, -1) : line; if (normalizedLine.startsWith(swarmTurnReportPrefix)) { reportLines.push(normalizedLine); + settled = true; } } + if (!autoPilot || child.stdin.writableEnded) return; + // turn 已给出回执后不再应答,直接收 stdin 让 CLI 正常退出。 + if (settled) { + child.stdin.end(); + return; + } + const reply = nextSwarmAutoPilotReply(pendingLine); + if (reply === null) return; + console.log(`[自动应答] ${reply}`); + pendingLine = ''; + child.stdin.write(`${reply}\n`); }); - child.stdin.end(`${task}\n`); + if (autoPilot) { + child.stdin.write(`${task}\n`); + } else { + child.stdin.end(`${task}\n`); + } try { const result = await childExitWithTimeout( child, @@ -1645,6 +1687,42 @@ export async function validateSwarmProjectArtifacts(projectPath, options) { return inspection; } +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}`, + ); + } +} + export async function hasConfiguredEditorApiKey(configDir) { let configured = false; for (const fileName of [configFileName, localConfigFileName]) { @@ -1869,10 +1947,11 @@ export async function runSwarmTestChat(options) { ); } console.log('LLM 配置已就绪。'); + const requirementNoun = options.plan ? '立项策划需求' : '游戏需求'; console.log( options.task - ? '已提交一条非交互游戏需求,正在等待 Swarm 自主完成。\n' - : '输入一条游戏需求并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n', + ? `已提交一条非交互${requirementNoun},正在等待 Swarm 自主完成。\n` + : `输入一条${requirementNoun}并回车;提交后按 Ctrl+D,让 Swarm 自主完成。\n`, ); phase = 'chat'; @@ -1882,7 +1961,8 @@ export async function runSwarmTestChat(options) { runtimeConfig.path, '--swarm-chat', '--init', - '--autonomous-game-build', + // 做方案链路只能跑 standard 档,后端对 plan + autonomous 是硬否决。 + options.plan ? '--plan' : '--autonomous-game-build', project.path, ]; let chat; @@ -1895,6 +1975,7 @@ export async function runSwarmTestChat(options) { timeoutDeadline === null ? null : Math.max(1, timeoutDeadline - Date.now()), + options.plan, ) : await runInteractiveCargo(chatArguments, setActiveChild); } catch (error) { @@ -1910,6 +1991,15 @@ export async function runSwarmTestChat(options) { if (options.task) { turnReport = parseSettledSwarmTurnReport(chat.turnReportOutput); } + if (options.plan) { + // 立项策划不出游戏产物,正式验收在 GDD 审批卡上;这里只报告落盘情况, + // 是否收束已经由 CLI 的退出码判过了。 + phase = 'plan-report'; + await reportPlanningOutputs(project.path); + phase = 'complete'; + console.log('\n立项策划链路已收束:Run 正常结束,策划产物见上方清单。'); + break session; + } phase = 'artifact-validation'; const requireEditorImages = await hasConfiguredEditorApiKey( runtimeConfig.path, diff --git a/apps/ai-game-creator-shell/src-tauri/src/cli.rs b/apps/ai-game-creator-shell/src-tauri/src/cli.rs index d957fc284..803454cb9 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/cli.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/cli.rs @@ -706,7 +706,7 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result, S })); } if args.first().map(String::as_str) == Some("--swarm-chat") { - const USAGE: &str = "用法:--swarm-chat [--init] [--autonomous-game-build] [--game-chat-smoke] <本地项目绝对路径> [parentAgentId]"; + const USAGE: &str = "用法:--swarm-chat [--init] [--autonomous-game-build] [--game-chat-smoke] [--plan] <本地项目绝对路径> [parentAgentId]"; let mut rest = args[1..].to_vec(); let initialize = if let Some(index) = rest.iter().position(|arg| arg == "--init") { rest.remove(index); @@ -746,6 +746,18 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result, S } _ => return Err(USAGE.to_string()), }; + let plan = match rest.iter().filter(|arg| arg.as_str() == "--plan").count() { + 0 => false, + 1 => { + let index = rest + .iter() + .position(|arg| arg == "--plan") + .expect("counted plan flag"); + rest.remove(index); + true + } + _ => return Err(USAGE.to_string()), + }; if !(1..=2).contains(&rest.len()) || rest.iter().any(|value| value.trim().is_empty()) { return Err(USAGE.to_string()); } @@ -760,6 +772,20 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result, S .to_string(), ); } + // 立项策划根 Run 只跑 standard 档(后端 reject_supervisor_plan_autonomous_profile + // 同样否决),且必须挂在总控上;这里先拦一道,免得建完项目才失败。 + if plan + && (autonomous_game_build + || game_chat_smoke + || rest.get(1).is_some_and(|parent| { + parent.trim() != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID + })) + { + return Err( + "--plan 仅允许 project-supervisor 的 standard 档,不能搭配 --autonomous-game-build / --game-chat-smoke" + .to_string(), + ); + } return Ok(Some(CliCommand::SwarmChat { project_path: PathBuf::from(&rest[0]), parent_agent_id: rest @@ -774,6 +800,8 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result, S }, supervisor_source: if game_chat_smoke { AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE + } else if plan { + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE } else { AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE }, @@ -2110,6 +2138,58 @@ mod tests { .is_err()); } + #[test] + fn swarm_chat_plan_flag_selects_plan_source_on_standard_profile() { + let project_path = std::env::current_dir().expect("current directory"); + let command = parse_cli_command(&[ + "--swarm-chat".to_string(), + "--init".to_string(), + "--plan".to_string(), + project_path.display().to_string(), + ]) + .expect("parse plan swarm chat") + .expect("plan swarm chat command"); + assert_eq!( + command, + CliCommand::SwarmChat { + project_path, + parent_agent_id: GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID.to_string(), + initialize: true, + run_profile: AGENT_RUNTIME_RUN_PROFILE_STANDARD.to_string(), + supervisor_source: AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + } + ); + assert!(parse_cli_command(&[ + "--swarm-chat".to_string(), + "--plan".to_string(), + "--autonomous-game-build".to_string(), + "/tmp/game-project".to_string(), + ]) + .is_err()); + assert!(parse_cli_command(&[ + "--swarm-chat".to_string(), + "--plan".to_string(), + "--autonomous-game-build".to_string(), + "--game-chat-smoke".to_string(), + "/tmp/game-project".to_string(), + ]) + .is_err()); + assert!(parse_cli_command(&[ + "--swarm-chat".to_string(), + "--plan".to_string(), + "/tmp/game-project".to_string(), + "code-prototype".to_string(), + ]) + .is_err()); + assert!(parse_cli_command(&[ + "--swarm-chat".to_string(), + "--plan".to_string(), + "--plan".to_string(), + "/tmp/game-project".to_string(), + ]) + .is_err()); + } + #[test] fn swarm_chat_rejects_missing_or_extra_arguments() { assert!(parse_cli_command(&["--swarm-chat".to_string()]).is_err()); diff --git a/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/input.rs b/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/input.rs index dd4c51ee4..8ecff389f 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/input.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/input.rs @@ -47,8 +47,11 @@ pub(super) enum SwarmNewRunLaunch<'a> { impl SwarmNewRunLaunch<'_> { pub(super) fn expected_parent_source(self) -> Option<&'static str> { match self { + // 受限入口按 source 精确认领自己的 Run:立项策划和做游戏同样是 standard 档, + // 只按 profile 匹配会让 --plan 把上一条 CLI 链路的残留 Run 当成自己的。 Self::ProjectSupervisor { source, .. } - if source == AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE => + if source == AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE + || source == AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE => { Some(source) } @@ -71,7 +74,9 @@ pub(super) fn resolve_swarm_new_run_launch<'a>( if parent_agent_id == GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID { if !matches!( supervisor_source, - AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE | AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE + AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE + | AGENT_RUNTIME_SUPERVISOR_GAME_CHAT_SOURCE + | AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE ) { return Err(format!( "不支持的 Project Supervisor source:{supervisor_source}" @@ -82,13 +87,18 @@ pub(super) fn resolve_swarm_new_run_launch<'a>( { return Err("game-chat smoke source 仅支持 autonomous-game-build".to_string()); } + // 与 GUI「做方案」入口同一条门禁;能力开关和 source 可信性由 task_start 统一判定, + // 这里只把档位冲突提前到起 Run 之前。 + reject_supervisor_plan_autonomous_profile(supervisor_source, run_profile)?; return Ok(SwarmNewRunLaunch::ProjectSupervisor { source: supervisor_source, run_profile, }); } if supervisor_source != AGENT_RUNTIME_SUPERVISOR_CLI_SOURCE { - return Err("game-chat smoke source 仅支持 project-supervisor 总控入口".to_string()); + return Err(format!( + "受限 Supervisor source 仅支持 project-supervisor 总控入口:{supervisor_source}" + )); } if run_profile != AGENT_RUNTIME_RUN_PROFILE_STANDARD { return Err("--autonomous-game-build 仅支持 project-supervisor 总控入口".to_string()); diff --git a/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/tests.rs b/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/tests.rs index f0eda7231..66f1c908c 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/tests.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/tests.rs @@ -138,6 +138,43 @@ fn restricted_game_chat_smoke_launch_selects_trusted_single_main_source() { .is_err()); } +#[test] +fn plan_launch_claims_its_own_source_and_rejects_autonomous_profile() { + assert_eq!( + resolve_swarm_new_run_launch( + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + ) + .expect("resolve plan launch"), + SwarmNewRunLaunch::ProjectSupervisor { + source: AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + run_profile: AGENT_RUNTIME_RUN_PROFILE_STANDARD, + } + ); + // 立项策划 Run 和做游戏 Run 都是 standard 档,只有按 source 认领才不会串链。 + assert_eq!( + SwarmNewRunLaunch::ProjectSupervisor { + source: AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + run_profile: AGENT_RUNTIME_RUN_PROFILE_STANDARD, + } + .expected_parent_source(), + Some(AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE) + ); + assert!(resolve_swarm_new_run_launch( + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + AGENT_RUNTIME_RUN_PROFILE_AUTONOMOUS_GAME_BUILD, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + ) + .is_err()); + assert!(resolve_swarm_new_run_launch( + "code-prototype", + AGENT_RUNTIME_RUN_PROFILE_STANDARD, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + ) + .is_err()); +} + #[test] fn same_run_steer_preserves_bound_autonomous_profile() { let root = std::env::temp_dir().join(format!( diff --git a/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/turn_dispatch.rs b/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/turn_dispatch.rs index 7d98a5cd6..9040e9097 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/turn_dispatch.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/swarm_cli/turn_dispatch.rs @@ -377,6 +377,12 @@ fn steer_and_wait_for_swarm_turn( ) -> Result { require_external_agent_runner_for_cli_runtime_write(root)?; let steer_id = format!("swarm-steer-{}", unix_millis()); + // 立项策划根 Run 不接受 steer(换根会作废旧委派链剩余的问询轮次),GUI 侧不发起、 + // 后端另有否决。无头入口只有把 plan source 交给后端,这道否决才会生效;其余入口 + // 继续沿用不带 source 的旧行为,免得给 game-chat / CLI 链路加新的一致性判据。 + let steer_source = expected_parent_source + .filter(|source| agent_runtime_supervisor_source_is_plan(source)) + .map(str::to_string); let result = tauri::async_runtime::block_on(steer_game_creator_agent_runtime_task( root.display().to_string(), parent_agent_id.to_string(), @@ -385,7 +391,7 @@ fn steer_and_wait_for_swarm_turn( steer_id.clone(), message.to_string(), Some(run_profile.to_string()), - None, + steer_source, ))?; writeln!( output, diff --git a/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts b/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts index 37290c083..f06404855 100644 --- a/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts +++ b/apps/ai-game-creator-shell/tests/agentSwarmTestEntry.test.ts @@ -26,6 +26,7 @@ import { cleanupSwarmTestProject, cleanupSwarmTestRuntimeConfig, configFileName, + defaultRealSwarmPlanTask, defaultRealSwarmTestTask, defaultRuntimeConfigDirCandidates, discoverRuntimeConfigDir, @@ -34,6 +35,7 @@ import { hasIncompleteArtifactMarker, inspectSwarmProjectArtifacts, localConfigFileName, + nextSwarmAutoPilotReply, parseRunnerShutdownOutput, parseSettledSwarmTurnReport, parseSwarmTestArguments, @@ -653,6 +655,7 @@ describe('Swarm test argument parsing', () => { keepProject: false, openBrowser: true, task: null, + plan: false, timeoutMinutes: null, dryRun: false, help: false, @@ -673,6 +676,7 @@ describe('Swarm test argument parsing', () => { '--no-open', '--task', '生成一款可试玩的塔防游戏', + '--plan', '--timeout-minutes', '75', '--dry-run', @@ -684,6 +688,7 @@ describe('Swarm test argument parsing', () => { keepProject: true, openBrowser: false, task: '生成一款可试玩的塔防游戏', + plan: true, timeoutMinutes: 75, dryRun: true, help: true, @@ -692,6 +697,24 @@ describe('Swarm test argument parsing', () => { expect(() => parseSwarmTestArguments(['--task', ''])).toThrowError(); }); + it('auto-answers only the prompts the plan lane actually blocks on', () => { + expect( + nextSwarmAutoPilotReply( + '[待确认] agent=project-supervisor run=r action=a tool=agent.delegate\n输入 approve 或 reject:', + ), + ).toBe('approve'); + expect(nextSwarmAutoPilotReply('请选择 1-3,或直接输入其他答案:')).toBe( + '1', + ); + // 提示词已经被上一轮消费掉、不在缓冲末尾时不得重复应答。 + expect( + nextSwarmAutoPilotReply('输入 approve 或 reject:\n[已批准] action-1\n'), + ).toBeNull(); + expect( + nextSwarmAutoPilotReply('[状态] project-supervisor running'), + ).toBeNull(); + }); + it('keeps persistent preview only for manual chat mode', () => { expect(shouldStartPersistentPreview(parseSwarmTestArguments([]))).toBe( true, @@ -701,6 +724,10 @@ describe('Swarm test argument parsing', () => { parseSwarmTestArguments(['--task', '生成一款可试玩的塔防游戏']), ), ).toBe(false); + // 做方案链路不出可试玩产物,交互模式同样不该起预览。 + expect( + shouldStartPersistentPreview(parseSwarmTestArguments(['--plan'])), + ).toBe(false); }); it('applies a bounded default only to non-interactive tasks', () => { @@ -1804,6 +1831,12 @@ 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`, ); @@ -1813,6 +1846,12 @@ 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/package.json b/package.json index 4b468881b..f89e69d31 100644 --- a/package.json +++ b/package.json @@ -155,6 +155,8 @@ "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 --",