From 7a01f944dda327d17eaa4f9a9ddb2bc970f123ee Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 21 Aug 2026 04:50:37 +0000 Subject: [PATCH] =?UTF-8?q?=E7=AB=8B=E9=A1=B9=E7=AD=96=E5=88=92=E6=A0=B9?= =?UTF-8?q?=20run=20=E6=94=B9=E7=94=A8=E4=B8=93=E7=94=A8=20Supervisor=20pr?= =?UTF-8?q?ompt=EF=BC=8C=E4=B8=8D=E5=86=8D=E6=98=AF=E9=80=9A=E7=94=A8?= =?UTF-8?q?=E5=90=88=E5=90=8C=E7=9A=84=E5=B7=AE=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原型系统几分钟就能跑完同一条链路且全程有澄清问答,生产链路 run 11/12/13 三次 澄清轮数都是 0。拓扑不是差异来源——两边都是 D11。差的是 Supervisor 读到的 东西:原型给它一份 1.4k 的专用提示词,明写「你不生产策划内容」和固定动作序列; 生产给它的是 13.5k 的通用总控合同,预设手里有 43 个工具、六个专业组、isolated child、正式任务图和视觉产物合同,而 plan 根一个都没有。 此前的做法是在通用合同上逐段做减法($visualContract、supervisorIntro、 isolatedTemplateCatalogIntro、$isolatedAgentTemplates 四个 `if plan_root` 分支)。这个形状本身在漏:模板目录那一段就是漏到第三轮才发现的,而每漏一段 都是一次已实测的偏航。现在整份换成 manifest 里的 supervisorPlan composition, plan 根的段落清单一眼可读,不再散落在两个函数的四个否定分支里。 保留的三段各有硬理由: - isolatedAgentContract —— expectedArtifacts 合同,agent.delegate 同样要用。 - supervisorRepair —— 返工必须用原 delegationId 重读 claimedDelegateContract 并逐字继承 acceptanceCriteria/expectedArtifacts。run 12 连撞两次的就是这条。 - planCommon —— common.md 的子集副本,见下。 替换的两段: - planSupervisorIdentity 取代 supervisorIdentityContract。后者的「用验收标准和 预期产物把边界清晰的任务委派给合适的专业 Agent」正是替用户预先裁定的压力源, 且 plan 根只有一个可委派目标;末句的黑板与 Agent 记忆在收窄后的工具面上是 死文本。 - planSupervisorPlaybook 取代 supervisorPlaybook。后者首段讲 spawn_isolated 批次与 manifest DAG、末段要求「manifest 正式任务图已完成」,在本链路都不可 执行。新段落照原型形状写死六步动作顺序,并补上生产此前完全没有的转述保真 规则:`[已确认] {header} → 用户答:{原文}` 逐条列出,任务接近长度上限时压缩 自己的说明而不是压缩用户答案。 删掉的三段: - common.md 有 43% 是 plan 根执行不了的内容(改码流程、git 提交、联网检索、 整段「作为被委派的专业 Agent 时」的身份错位),其中「最后一次修改后必须成功 执行 project.verify 才能 respond_to_user」还是一道 plan 根永远满足不了的假 门禁。但同一段压着这条链路唯一的原创性红线、user.input_request 协议和静态 委派协议——D11 的协议正文在这里,不在 playbook。所以 planCommon 逐字复制这 三段,并用 tripwire 断言反向钉住:planCommon 的每段都必须能在 common.md 里 逐字找到。 - supervisorClaimGate 讲 minIsolatedGroupsBeforeClaim,无 isolated group 时恒 不触发。 - $platform 整段是 command.start/exec/poll/stdin/terminate 的用法合同,plan 根 一个 command 工具都没有。GDD 里的平台事实由 Runtime 另行注入,与这段无关。 提示词头部改用上一笔提交的 allowlist 拼工具清单,与 Provider 请求实际广告的 函数目录共用同一份事实。两边各自维护一份就会退回「合同说有 43 个、请求里只有 9 个」的自相矛盾,而那正是收窄工具面本身要消灭的东西;这一致性单独钉了一条 断言。 supervisor/playbook.md 里的反预先裁定段落保留。执行层只单向拦住 plan 根, agent.delegate 的 agentId 是自由字符串,gui/cli Supervisor 委派 project-planning 并未被禁,那段在通用 lane 不是死文本。两份逐字相同,另有一条 tripwire 钉住。 plan 根实际拼到的提示词从 13531 字节降到 9049 字节,但重点不是体积——是被删的 全部执行不了、补上的固定动作序列此前完全缺失。 已变异验证四条新断言。全量 2238 passed / 10 failed,逐条单线程复跑后 7 条转绿 (并行噪声),剩余 3 条是本机缺 rg 与 CRLF,与本次改动无关。 尚未验证的是这些文字是否真的改变模型行为——单测只能证明它进了提示词。判据是 下一次 E2E 能否触发 AGC_NEEDS_USER_INPUT_V1 信封。 Co-Authored-By: Claude Opus 5 --- .../build_support/runtime_prompt_bundle.rs | 34 ++ .../src-tauri/prompts/runtime/manifest.json | 13 +- .../src-tauri/prompts/runtime/plan/common.md | 10 + .../runtime/plan/supervisor-identity.md | 7 + .../runtime/plan/supervisor-playbook.md | 20 ++ .../prompts/runtime/supervisor/playbook.md | 2 + .../src-tauri/src/agent/prompt.rs | 296 ++++++++++++++---- .../tests/runtime_prompt_bundle_build.rs | 11 + 8 files changed, 327 insertions(+), 66 deletions(-) create mode 100644 apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/common.md create mode 100644 apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/supervisor-identity.md create mode 100644 apps/ai-game-creator-shell/src-tauri/prompts/runtime/plan/supervisor-playbook.md diff --git a/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs b/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs index 4225d51f4..d04cd43b9 100644 --- a/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs +++ b/apps/ai-game-creator-shell/src-tauri/build_support/runtime_prompt_bundle.rs @@ -39,6 +39,11 @@ struct PromptBundleManifest { struct PromptCompositions { runtime: Vec, supervisor: Vec, + /// 立项策划根 run 的 Supervisor system prompt。它不是 `supervisor` 的差集, + /// 而是一份独立的完整清单:plan 根的工具面只有 7 个原生工具,专业组、 + /// isolated child、任务图与视觉产物合同在这条链路上全部不可执行,逐段 + /// 减法会把「plan 根到底看到什么」摊在两个函数的四个否定分支里。 + supervisor_plan: Vec, supervisor_chat: SupervisorChatComposition, } @@ -241,6 +246,12 @@ pub fn compile_manifest(manifest_path: &Path) -> Result Result>(); @@ -431,6 +443,14 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str { register("composition supervisor", section); } + for section in manifest + .compositions + .supervisor_plan + .iter() + .filter(|section| !section.starts_with('$')) + { + register("composition supervisorPlan", section); + } register("composition supervisorChat.identity", identity); register( "composition supervisorChat.finalReply", @@ -459,9 +479,19 @@ fn validate_section_ownership(manifest: &PromptBundleManifest) -> Result<(), Str "composition supervisor", "composition supervisorChat.identity", ]); + // plan 根 composition 是 Supervisor system prompt 的第二条 lane,不是另一种 + // 语义面。它按设计复用 runtime lane 的 `isolatedAgentContract`(`agent.delegate` + // 的 expectedArtifacts/writeScopes 合同)和 supervisor lane 的 `supervisorRepair` + // (返工必须逐字继承原合同)。除这两个方向外,跨所有者复用仍然是错误。 + let allowed_plan_runtime_owners = + BTreeSet::from(["composition runtime", "composition supervisorPlan"]); + let allowed_plan_supervisor_owners = + BTreeSet::from(["composition supervisor", "composition supervisorPlan"]); for (section, section_owners) in owners { if section_owners.len() > 1 && !(section == identity && section_owners == allowed_identity_owners) + && section_owners != allowed_plan_runtime_owners + && section_owners != allowed_plan_supervisor_owners { return Err(format!( "Prompt section 跨语义所有者复用:{section} -> {section_owners:?}" @@ -912,6 +942,10 @@ fn render_rust(manifest: &PromptBundleManifest, sections: &BTreeMap String { - let plan_root = agent_runtime_supervisor_source_is_plan(source); + // 只看 `source` 就够:本函数唯一的调用方在上面用 + // `agent_id != GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID` 提前返回过,而 + // `source` 只有 Supervisor 自己的 run binding 会填成 plan(见 + // provider_request_builders.rs),其余角色恒为空串。 + if agent_runtime_supervisor_source_is_plan(source) { + return game_creator_project_supervisor_plan_tool_plan_system_prompt(); + } let visual_section = if editor_api_key_is_configured { RUNTIME_PROMPT_VISUAL_EDITOR_SECTION } else { @@ -597,17 +598,41 @@ fn game_creator_project_supervisor_tool_plan_prompt( RUNTIME_PROMPT_SUPERVISOR_COMPOSITION, |marker| match marker { "$base" => Some(prompt), - "$visualContract" => Some(if plan_root { - "" - } else { - required_runtime_prompt_section(visual_section) - }), - "supervisorIntro" if plan_root => Some(""), + "$visualContract" => Some(required_runtime_prompt_section(visual_section)), _ => None, }, ) } +/// 立项策划根 run(`source == project-supervisor-plan`)的 Supervisor system +/// prompt 是一份独立清单,不是通用 Supervisor 合同的差集。 +/// +/// 通用合同预设 Supervisor 手里有 43 个原生工具、六个专业组、isolated child、 +/// 正式任务图和视觉产物合同;plan 根一个都没有——它只有 7 个工具,只能委派 +/// `project-planning` 一个子 Agent。继续用逐段否定去改写通用合同,等于让 +/// 「plan 根到底看到什么」散落在几个 `if plan_root` 分支里,而每漏一段就是一次 +/// 已经实测到的偏航(专业角色目录残留曾让 Supervisor 照着委派 +/// `design-director`)。因此这里整份换成 `supervisorPlan` composition,plan 根 +/// 的全部段落在 manifest 里一眼可读。 +fn game_creator_project_supervisor_plan_tool_plan_system_prompt() -> String { + // 与 Provider 请求的函数目录共用 `agent_runtime_plan_root_supervisor_tools`: + // 提示词里列的工具和实际广告的工具必须是同一份事实,否则又会出现 + // 「合同说有、请求里没有」的自相矛盾。 + let tool_catalog = agent_runtime_plan_root_supervisor_tools().join("、"); + let prompt_header = format!( + "你正在使用 Genarrative AI 游戏创作多智能体 Runtime。你必须直接调用当前请求广告的原生函数:复杂任务首次拆解、实际进度变化、steer 调整顺序或最终收束时调用 update_agent_plan,并提交 explanation 与完整 steps;无需更新时不要调用 update_agent_plan。steps 只允许 pending、in_progress、completed 且同时最多一个 in_progress;已完成步骤必须保留且不得回退,所有必要步骤 completed 前不得调用 respond_to_user,Runtime 不会按工具动作下标代替你更新进度。本 run 的原生可执行工具目录只有:{tool_catalog}。写入、补丁、删除、命令、预览、素材生成、任务图、记忆、黑板、isolated child 与 MCP 工具在本 run 都不存在,调用它们只会失败。" + ); + // `$platform` 整段是 command.start/exec/poll/stdin/terminate 的用法合同, + // plan 根一个 command 工具都没有;GDD 里的平台事实由 Runtime 另行注入,与 + // 这段无关。 + render_runtime_prompt_composition(RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION, |marker| { + match marker { + "$header" => Some(&prompt_header), + _ => None, + } + }) +} + fn render_runtime_prompt_sections(sections: &[&str]) -> String { sections .iter() @@ -678,31 +703,14 @@ pub(crate) fn game_creator_agent_runtime_role_overlay_prompt( } pub(crate) fn game_creator_agent_runtime_tool_plan_system_prompt() -> String { - game_creator_agent_runtime_tool_plan_system_prompt_for_plan_root(false) -} - -/// plan 根 run 的 `agent.spawn_isolated` 已被 M1A-4 无条件拒绝,因此共享 -/// runtime 合同里那份「合法 templateAgentId」静态模板目录对它没有任何可执行 -/// 语义,只剩下把 design-director 等全部专业角色名摆在 Provider 眼前的副作用。 -/// 该目录第一个名字就是 `design-director`,实测中 Supervisor 会照着它发起 -/// `agent.delegate`,被执行层拒绝后再也没能自行改回 `project-planning`。 -/// 因此这里在上下文层直接删掉目录本身——这正是第 19 节第 2 条要求的第二层, -/// 不替代执行层硬拒。 -fn game_creator_agent_runtime_tool_plan_system_prompt_for_plan_root(plan_root: bool) -> String { #[cfg(target_os = "linux")] const CURRENT_PLATFORM_IS_LINUX: bool = true; #[cfg(not(target_os = "linux"))] const CURRENT_PLATFORM_IS_LINUX: bool = false; - game_creator_agent_runtime_tool_plan_system_prompt_for_platform( - CURRENT_PLATFORM_IS_LINUX, - plan_root, - ) + game_creator_agent_runtime_tool_plan_system_prompt_for_platform(CURRENT_PLATFORM_IS_LINUX) } -fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform( - linux: bool, - plan_root: bool, -) -> String { +fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform(linux: bool) -> String { let tool_catalog = agent_runtime_native_executable_tools().join("、"); let prompt_header = format!( "你正在使用 Genarrative AI 游戏创作多智能体 Runtime。你必须直接调用当前请求广告的原生函数:复杂任务首次拆解、实际进度变化、steer 调整顺序或最终收束时调用 update_agent_plan,并提交 explanation 与完整 steps;无需更新时不要调用 update_agent_plan。steps 只允许 pending、in_progress、completed 且同时最多一个 in_progress;已完成步骤必须保留且不得回退,所有必要步骤 completed 前不得调用 respond_to_user,Runtime 不会按工具动作下标代替你更新进度。只能请求以下 Runtime 当前注册的原生可执行工具:{tool_catalog}。MCP 工具仅以当前请求提供的动态目录为准。" @@ -716,15 +724,7 @@ fn game_creator_agent_runtime_tool_plan_system_prompt_for_platform( let platform_section = runtime_prompt_platform_section_id(linux); render_runtime_prompt_composition(RUNTIME_PROMPT_RUNTIME_COMPOSITION, |marker| match marker { "$header" => Some(&prompt_header), - // 只删 spawn_isolated 专属的两段。`isolatedAgentContract` 讲的是 - // expectedArtifacts 与 writeScopes,`agent.delegate` 同样要用,plan 根 - // 委派 project-planning 时就带 expectedArtifacts,必须逐字保留。 - "isolatedTemplateCatalogIntro" if plan_root => Some(""), - "$isolatedAgentTemplates" => Some(if plan_root { - "" - } else { - &isolated_agent_templates - }), + "$isolatedAgentTemplates" => Some(&isolated_agent_templates), "$platform" => Some(required_runtime_prompt_section(platform_section)), _ => None, }) @@ -928,6 +928,38 @@ mod tests { } #[test] + /// Supervisor 的核心行为准则是「用验收标准和预期产物把边界清晰的任务委派 + /// 出去」(`identity-contract.md`),这在自主构建链路上正确,搬到策划链路 + /// 上却恰好碾过决策卡协议:把边界定清楚等于把用户没说的都替他决定掉。 + /// 实测中 Supervisor 写出的 acceptanceCriteria 含「未由用户指定的具体规则 + /// 标注为立项假设」和「目标平台、输入设备…须标为待定或建议」,前者作废了 + /// 3 轮问询预算(三次真实 run 里信封一次都没触发),后者还与 Runtime 强制 + /// 注入的平台事实直接矛盾。playbook 因此必须显式反向约束,否则整条策划链路 + /// 上只有「问完之后怎么转述」有指导、没有「你不该替用户回答」。 + #[test] + fn supervisor_playbook_forbids_pre_deciding_the_planning_tradeoffs() { + let playbook = required_runtime_prompt_section("supervisorPlaybook"); + for required in [ + "不得替用户预先裁定产品取舍", + "3 轮问询预算", + "不得要求标为待定", + ] { + assert!( + playbook.contains(required), + "supervisorPlaybook 缺少策划委派约束:{required}" + ); + } + // 该约束必须真的到达 plan 根 Supervisor,而不只是躺在 section 里。 + let plan_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_agent( + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + ); + assert!( + plan_prompt.contains("不得替用户预先裁定产品取舍"), + "plan 根 Supervisor 的 system prompt 必须带上该约束" + ); + } + /// `common.md` 的原创性红线随 `$base` 分发给做游戏链路的每个专业 Agent,但 /// 策划子 Agent 走 exact allowlist 的专用小 prompt、不拼 `$base`(理由见 /// `game_creator_project_planning_tool_plan_system_prompt` 的注释),该条款 @@ -1014,10 +1046,8 @@ mod tests { #[test] fn runtime_prompt_selects_exactly_one_manifest_platform_variant() { - let default_prompt = - game_creator_agent_runtime_tool_plan_system_prompt_for_platform(false, false); - let linux_prompt = - game_creator_agent_runtime_tool_plan_system_prompt_for_platform(true, false); + let default_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_platform(false); + let linux_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_platform(true); let default_section = required_runtime_prompt_section(RUNTIME_PROMPT_PLATFORM_DEFAULT_SECTION).trim(); let linux_section = @@ -1334,21 +1364,30 @@ mod tests { } /// M1A-4:plan 根 run(source == project-supervisor-plan)的 Supervisor - /// system prompt 不拼 supervisorIntro 与 $visualContract 这两个 section - /// ——两段都在暗示存在可并行委派的专业组,而策划链路的合同是只能委派 - /// project-planning 一个子 Agent。这里用两段各自最短的、只出现在该 section - /// 里的独有句子做断言,不用裸的 agent-id 字面量。 + /// system prompt 整份换成 `supervisorPlan` composition,不再是通用 Supervisor + /// 合同的差集。 /// - /// **订正(2026-08-20)**:本注释原先记着 `$base` 里的 isolated agent 模板 - /// 目录「只是死文本,不是可利用的委派入口」,理由是执行层已无条件拒绝 - /// `agent.spawn_isolated`。该推理只覆盖了 spawn 这一条路径,漏掉了 Provider - /// 会把目录里的名字挪去当 `agent.delegate` 的目标:实测 Supervisor 首轮就 - /// 委派了目录里排第一的 `design-director`,被执行层拒绝后未能自行改回 - /// `project-planning`,整个 run 空转到 loop 预算耗尽、零产物。目录现已在 - /// plan 根的上下文层删除,覆盖断言见 - /// `plan_root_supervisor_prompt_drops_the_specialist_role_catalog`。 + /// **沿革**:这条最早只断言「不拼 supervisorIntro 与 $visualContract」——两段 + /// 都在暗示存在可并行委派的专业组,而策划链路只能委派 project-planning。 + /// 2026-08-20 又发现 `$base` 里的 isolated agent 模板目录同样会被挪去当 + /// `agent.delegate` 的目标:实测 Supervisor 首轮委派了目录里排第一的 + /// `design-director`,被执行层拒绝后未能自行改回 `project-planning`,整个 run + /// 空转到 loop 预算耗尽、零产物。逐段做减法每漏一段就是一次这样的偏航, + /// 因此现在改为整份替换:plan 根的段落清单由 manifest 的 `supervisorPlan` + /// 单点定义,本测试钉住它。 #[test] - fn plan_root_supervisor_prompt_drops_intro_and_visual_contract_sections() { + fn plan_root_supervisor_prompt_is_the_dedicated_plan_composition() { + assert_eq!( + RUNTIME_PROMPT_SUPERVISOR_PLAN_COMPOSITION, + &[ + "$header", + "planCommon", + "isolatedAgentContract", + "planSupervisorIdentity", + "planSupervisorPlaybook", + "supervisorRepair" + ] + ); for editor_api_key_is_configured in [false, true] { let plan_prompt = game_creator_project_supervisor_tool_plan_prompt( "shared runtime contract", @@ -1373,19 +1412,146 @@ mod tests { ), "plan 根 prompt 不应再包含 without-editor 视觉合同" ); + // 通用 $base 不再进入 plan 根:它带着改码流程、revision 验证门禁、 + // git 提交与「作为被委派的专业 Agent 时」等整段不可执行的合同。 assert!( - plan_prompt.contains("shared runtime contract"), - "plan 根 prompt 必须保留 $base" + !plan_prompt.contains("shared runtime contract"), + "plan 根 prompt 不应再拼通用 $base" ); + for dropped in [ + "supervisorIdentityContract", + "supervisorIntro", + "supervisorPlaybook", + "supervisorClaimGate", + "common", + "isolatedTemplateCatalogIntro", + "platformDefault", + "platformLinux", + ] { + assert!( + !plan_prompt.contains(required_runtime_prompt_section(dropped).trim()), + "plan 根 prompt 不应包含 section {dropped}" + ); + } + for kept in [ + "planCommon", + "isolatedAgentContract", + "planSupervisorIdentity", + "planSupervisorPlaybook", + // 返工必须逐字继承原 acceptanceCriteria / expectedArtifacts, + // 这条规则只在 supervisorRepair 里,plan 根实测撞过两次。 + "supervisorRepair", + ] { + assert!( + plan_prompt.contains(required_runtime_prompt_section(kept).trim()), + "plan 根 prompt 必须包含 section {kept}" + ); + } + } + } + + /// 提示词头部列出的工具清单和 Provider 请求实际广告的函数目录必须是同一份 + /// 事实。二者各自维护一份,就会退回「合同说有 43 个、请求里只有 9 个」的 + /// 自相矛盾——这正是收窄工具面本身要消灭的东西。 + #[test] + fn plan_root_prompt_tool_catalog_matches_the_advertised_allowlist() { + let plan_prompt = game_creator_agent_runtime_tool_plan_system_prompt_for_agent( + GAME_CREATOR_PROJECT_SUPERVISOR_AGENT_ID, + AGENT_RUNTIME_SUPERVISOR_PLAN_SOURCE, + ); + let allowlist = agent_runtime_plan_root_supervisor_tools(); + assert!( + plan_prompt.contains(&format!( + "本 run 的原生可执行工具目录只有:{}。", + allowlist.join("、") + )), + "plan 根 prompt 头部必须逐字列出 allowlist:{plan_prompt}" + ); + for tool in agent_runtime_native_executable_tools() { + if allowlist.contains(&tool) { + continue; + } assert!( - plan_prompt.contains(required_runtime_prompt_section("supervisorPlaybook").trim()) + !plan_prompt.contains(&format!("、{tool}")) + && !plan_prompt.contains(&format!("{tool}、")), + "plan 根 prompt 不应在工具目录里出现被裁掉的 {tool}" ); + } + } + + /// `planCommon` 是 `common.md` 的子集副本,不是重写。 + /// + /// 通用 `common` 段有 43% 是 plan 根执行不了的内容(改码流程、revision 验证 + /// 门禁、git 提交、联网检索、以及整段「作为被委派的专业 Agent 时」的身份错位), + /// 其中「最后一次修改后必须成功执行 project.verify 才能 respond_to_user」还是 + /// 一道 plan 根永远满足不了的假门禁。但同一段里也压着这条链路唯一的原创性 + /// 红线、`user.input_request` 协议和静态委派协议——D11 拓扑的协议正文就在 + /// 这里,不在 playbook。所以 plan lane 只挑走这三段,逐字复制。 + /// + /// 复制就会漂移,因此这条断言反过来钉:`planCommon` 里的每一段都必须能在 + /// `common.md` 里逐字找到。谁单改一边,这条就红。 + #[test] + fn plan_common_paragraphs_are_verbatim_slices_of_the_shared_common_section() { + let common = required_runtime_prompt_section("common"); + let plan_common = required_runtime_prompt_section("planCommon"); + let inherited = [ + "用户只描述玩法类型、机制或相似体验时,不代表授权复刻现有游戏。", + "用户输入请求协议:", + "静态委派协议:", + ]; + let mut matched = 0; + for paragraph in plan_common + .split( + " + +", + ) + .map(str::trim) + { + if !inherited.iter().any(|head| paragraph.starts_with(head)) { + continue; + } assert!( - plan_prompt.contains(required_runtime_prompt_section("supervisorClaimGate").trim()) - ); - assert!( - plan_prompt.contains(required_runtime_prompt_section("supervisorRepair").trim()) + common.contains(paragraph), + "planCommon 段落已与 common.md 漂移:{paragraph}" ); + matched += 1; + } + assert_eq!(matched, inherited.len(), "planCommon 缺少继承段落"); + // 反向:plan 根不得继承那道它永远满足不了的验证门禁。 + assert!(common.contains("才能调用 respond_to_user 收束")); + assert!(!plan_common.contains("才能调用 respond_to_user 收束")); + } + + /// 「不得替用户预先裁定产品取舍」这段同时写进两条 lane 的 playbook。 + /// + /// 执行层只单向拦住 plan 根(`立项策划根 Run 只能委派 project-planning`), + /// 反方向没有对称限制:`agent.delegate` 的 `agentId` 是自由字符串,gui/cli + /// Supervisor 委派 `project-planning` 并未被禁。所以通用 playbook 里这段不是 + /// 死文本,两边都要有;这条断言钉住它们逐字相同。 + #[test] + fn both_playbooks_carry_the_same_anti_pre_deciding_contract() { + const SHARED: &str = "不得替用户预先裁定产品取舍"; + let generic = required_runtime_prompt_section("supervisorPlaybook"); + let plan = required_runtime_prompt_section("planSupervisorPlaybook"); + let paragraph = generic + .split( + " + +", + ) + .map(str::trim) + .find(|paragraph| paragraph.contains(SHARED)) + .expect("supervisorPlaybook 必须包含反预先裁定段落"); + assert!( + plan.contains(paragraph), + "两条 lane 的反预先裁定段落已漂移: +{paragraph} +--- +{plan}" + ); + for required in ["3 轮问询预算", "不得要求标为待定"] { + assert!(paragraph.contains(required), "缺少 {required}"); } } diff --git a/apps/ai-game-creator-shell/src-tauri/tests/runtime_prompt_bundle_build.rs b/apps/ai-game-creator-shell/src-tauri/tests/runtime_prompt_bundle_build.rs index 62941d560..540193af2 100644 --- a/apps/ai-game-creator-shell/src-tauri/tests/runtime_prompt_bundle_build.rs +++ b/apps/ai-game-creator-shell/src-tauri/tests/runtime_prompt_bundle_build.rs @@ -28,6 +28,9 @@ const SECTION_FILES: &[(&str, &str)] = &[ ("supervisorPlaybook", "supervisor/playbook.md"), ("supervisorClaimGate", "supervisor/claim-gate.md"), ("supervisorRepair", "supervisor/repair.md"), + ("planCommon", "plan/common.md"), + ("planSupervisorIdentity", "plan/supervisor-identity.md"), + ("planSupervisorPlaybook", "plan/supervisor-playbook.md"), ("codePrototypeGameChat", "roles/code-prototype-game-chat.md"), ( "providerIsolatedToolContract", @@ -141,6 +144,14 @@ fn valid_manifest() -> Value { "supervisorClaimGate", "supervisorRepair" ], + "supervisorPlan": [ + "$header", + "planCommon", + "isolatedAgentContract", + "planSupervisorIdentity", + "planSupervisorPlaybook", + "supervisorRepair" + ], "supervisorChat": { "identity": "supervisorIdentityContract", "finalReply": "supervisorFinalReplyContract"