From 98abcf4f0dc3540afd3c3a7edf94064772290737 Mon Sep 17 00:00:00 2001 From: Linghong Date: Fri, 21 Aug 2026 05:33:48 +0000 Subject: [PATCH] =?UTF-8?q?=E6=BE=84=E6=B8=85=E4=BB=BB=E5=8A=A1=E7=9A=84?= =?UTF-8?q?=E8=BA=AB=E4=BB=BD=E8=B1=81=E5=85=8D=E6=8C=89=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=88=A4=E8=80=8C=E4=B8=8D=E6=98=AF=E6=8C=89=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E5=88=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 上一笔把豁免收窄成「工具必须是 user.input_request」,只覆盖了转述动作本身。 实际上用户答完后 pending_execution 调的是 run_recovered_game_creator_context_on_fresh_task(root, agent_id, pending.task.clone(), ...):整条 run 从此改跑在转述任务上,同一 run 后续的 agent.run_status、agent.delegate 全都带着它,而 runtime.current_task 仍是用户 原始请求。 于是 run 15 过了转述那一关,却在续跑第一步就挂:Supervisor 按 supervisorRepair 的要求调 agent.run_status 重读权威合同,动作在等到项目锁之后被 validate_agent_runtime_pending_context 判成「pending action 身份已变化」,仍是 needs-reconciliation。 pending.tool = agent.run_status pending.task = 子 Agent 需要用户澄清后才能继续。delegationId=delegation-4216f572… runtime.currentTask = 做个横版像素解谜小游戏,主角是个能操控自己影子的小机器人。 判据改成只看 task 是不是 Runtime 生成的转述指令。同一个 || 链里的 validate_agent_runtime_pending_action_after_lock 不比较 task,所以豁免面仍然只 有这一条文本相等;agent/task_id/session/run/source 与轮次检查照旧全走。 回归测试补上 run 15 实测的那个形状:同一 run、同一转述任务、工具是 agent.run_status 的续跑动作必须通过,而 task 被改写的同形状动作必须照旧被拒。 三向变异验证:恢复严格比较、整条删掉、把判据换成恒真替身,都会红。 Co-Authored-By: Claude Opus 5 --- .../agent/runtime_protocol/context_bundle.rs | 36 ++++++++++++++----- .../src-tauri/src/user_input.rs | 15 ++++---- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs index 542620fbd..6ca737782 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/runtime_protocol/context_bundle.rs @@ -173,18 +173,22 @@ pub(in crate::agent) fn validate_agent_runtime_pending_context( { return Err("Agent Runtime 待确认动作身份与当前状态不匹配".to_string()); } - // 转述 pending 是唯一一处 `task` 字段存的不是本 run 任务的动作:Runtime 代 + // 澄清转述是唯一一处 `task` 字段存的不是本 run 任务的场景:Runtime 代 // Supervisor 汇总子 Agent 澄清问题时,把「回答后创建唯一 continuation 委派」 // 那段指令连同 delegationId 写在这里(provider_recovery.rs),而 run 的 // current_task 始终是用户原始请求。其余五个 pending 创建点传的都是 run 的真实 // task,所以这条相等断言对它们成立,对转述则永不可能成立——D11 澄清路径实测 // 每一次都会在 resume 时被判成 needs-reconciliation,用户答案已落盘却无法继续。 // + // 豁免按 task 判、不按工具判:用户答完后整条 run 都改跑在转述任务上 + // (run_recovered_game_creator_context_on_fresh_task 传的就是 pending.task), + // 所以续跑里的 agent.run_status、agent.delegate 同样带着它。 + // // 这里只豁免文本相等这一条。pending 与 runtime 的 agent/task_id/session/run/ // source 五项身份检查在上面已经全部通过,轮次检查在下面继续执行,转述 pending // 本身也只能由 Runtime 在本 run 内生成,所以豁免不放开任何跨 run 或跨身份的 // 重放面。 - if !agent_runtime_pending_is_delegate_clarification_relay(pending) { + if !agent_runtime_task_is_delegate_clarification_relay(&pending.task) { validate_agent_runtime_context_task_parameter(root, runtime, &pending.task)?; } if pending.loop_iteration != runtime.loop_iteration { @@ -894,20 +898,36 @@ mod tests { }; let relay = build(&relay_task, &action); - assert!(agent_runtime_pending_is_delegate_clarification_relay( - &relay + assert!(agent_runtime_task_is_delegate_clarification_relay( + &relay.task )); assert_ne!(relay.task, runtime.current_task); validate_agent_runtime_pending_context(&root, &runtime, &relay) .expect("转述 pending 必须能通过身份校验,否则用户答案落盘后无法续跑"); - // 豁免只覆盖转述本身。同一工具、同一 run,但 task 不是 Runtime 生成的 - // 转述指令时,原有的相等断言必须照旧拒绝——否则这就是个洞而不是修复。 + // 用户答完后整条 run 都改跑在转述任务上,后续动作同样带着它。按工具收窄 + // 豁免会让续跑第一步 agent.run_status 就被判身份变化——run 15 实测如此, + // 所以这里显式覆盖非 user.input_request 的后续动作。 + let follow_up = AgentRuntimeToolAction { + tool: "agent.run_status".to_string(), + reason: Some("读取原委派的权威合同,准备创建唯一一次续跑委派".to_string()), + input: serde_json::json!({}), + }; + let resumed = build(&relay_task, &follow_up); + validate_agent_runtime_pending_context(&root, &runtime, &resumed) + .expect("续跑动作同样跑在转述任务上,必须通过身份校验"); + + // 豁免只覆盖 Runtime 生成的转述任务。task 不是转述指令时,原有的相等断言 + // 必须照旧拒绝——否则这就是个洞而不是修复。 let forged = build("被改写的任务", &action); - assert!(!agent_runtime_pending_is_delegate_clarification_relay( - &forged + assert!(!agent_runtime_task_is_delegate_clarification_relay( + &forged.task )); assert!(validate_agent_runtime_pending_context(&root, &runtime, &forged).is_err()); + let forged_follow_up = build("被改写的任务", &follow_up); + assert!( + validate_agent_runtime_pending_context(&root, &runtime, &forged_follow_up).is_err() + ); // 身份五项仍然照查:换一个 run_id 的转述 pending 不得被放行。 let mut cross_run = relay.clone(); diff --git a/apps/ai-game-creator-shell/src-tauri/src/user_input.rs b/apps/ai-game-creator-shell/src-tauri/src/user_input.rs index 9efa09623..017142cfb 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/user_input.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/user_input.rs @@ -17,12 +17,15 @@ pub(crate) fn agent_runtime_delegate_clarification_delegation_id(task: &str) -> .filter(|value| !value.is_empty()) } -/// 该 pending 是否由 Runtime 代 Supervisor 生成的子 Agent 澄清转述。 -pub(crate) fn agent_runtime_pending_is_delegate_clarification_relay( - pending: &AgentRuntimePendingToolAction, -) -> bool { - pending.action.tool == GAME_CREATOR_USER_INPUT_REQUEST_TOOL - && agent_runtime_delegate_clarification_delegation_id(&pending.task).is_some() +/// 该任务是否是 Runtime 代 Supervisor 生成的子 Agent 澄清转述任务。 +/// +/// 判据只看 task,不看工具:用户答完后 +/// `run_recovered_game_creator_context_on_fresh_task` 会把 `pending.task` 当作同一 +/// run 后续每一轮的任务,所以转述任务会一路传播到 `agent.run_status`、 +/// `agent.delegate` 等动作上,而不是只停在那一次 `user.input_request`。按工具收窄 +/// 会让续跑第一步就被判身份变化——run 15 实测如此。 +pub(crate) fn agent_runtime_task_is_delegate_clarification_relay(task: &str) -> bool { + agent_runtime_delegate_clarification_delegation_id(task).is_some() } pub(crate) const AGENT_RUNTIME_USER_INPUT_SCHEMA_VERSION: &str =