新增立项策划链路的无头入口

`--swarm-chat` 加 `--plan`,把新根 Run 绑定到 project-supervisor-plan
standard 档,和 GUI「做方案」按钮走同一条门禁;与 --autonomous-game-build /
--game-chat-smoke 互斥,非总控父 Agent 拒绝。plan 与做游戏同为 standard 档,
只按 profile 匹配会串链,因此 plan 入口按 source 精确认领自己的 Run。plan 根
Run 不接受 steer,无头入口把 source 交给后端才能触发既有否决,其余入口维持
不带 source 的旧行为。

harness 侧 --plan 跳过游戏产物验收和试玩,改为报告策划产物落盘清单。立项策划
跑 standard 档,agent.delegate 这类动作按项目权限策略必须逐个确认,而确认只从
CLI stdin 读;自主构建档没有这一步,所以只给 --plan 加一个 stdin 应答器,确认
本身仍然走后端确认命令。做游戏链路不受影响。

真实无头跑已验证:plan 根 Run 正常起来,source=project-supervisor-plan、
runProfile=standard,两个 agent.delegate 确认卡自动批准并进入委派。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 04:39:54 +00:00
parent 400595756b
commit 5100c18fba
8 changed files with 277 additions and 11 deletions
+2
View File
@@ -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",
@@ -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,
@@ -706,7 +706,7 @@ pub(crate) fn parse_cli_command(args: &[String]) -> Result<Option<CliCommand>, 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<Option<CliCommand>, 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<Option<CliCommand>, 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<Option<CliCommand>, 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());
@@ -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());
@@ -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!(
@@ -377,6 +377,12 @@ fn steer_and_wait_for_swarm_turn<W: Write>(
) -> Result<SwarmChatFlow, String> {
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<W: Write>(
steer_id.clone(),
message.to_string(),
Some(run_profile.to_string()),
None,
steer_source,
))?;
writeln!(
output,
@@ -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,
);
+2
View File
@@ -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 --",