@team-agent/installer 0.5.65 → 0.5.68

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +6 -0
  4. package/crates/team-agent/src/cli/adapters.rs +6 -1
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/compile.rs +1 -1
  17. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  18. package/crates/team-agent/src/cli/tests/mod.rs +2 -2
  19. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  20. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  21. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  22. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  23. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  24. package/crates/team-agent/src/compiler/tests.rs +23 -20
  25. package/crates/team-agent/src/compiler.rs +84 -5
  26. package/crates/team-agent/src/conpty/backend.rs +16 -0
  27. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  28. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  29. package/crates/team-agent/src/coordinator/health.rs +173 -0
  30. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  31. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  32. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  33. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  34. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  35. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  36. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  37. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  38. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  39. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  40. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  41. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  42. package/crates/team-agent/src/coordinator/types.rs +49 -0
  43. package/crates/team-agent/src/db/message_store.rs +39 -5
  44. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  45. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  46. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  47. package/crates/team-agent/src/leader/registry.rs +65 -0
  48. package/crates/team-agent/src/leader/start.rs +1080 -58
  49. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  50. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  51. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  52. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -14
  53. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
  54. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
  55. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  56. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  57. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  58. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -457
  59. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  60. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  61. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  62. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  63. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  64. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  65. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  66. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  67. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  68. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  69. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  70. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  71. package/crates/team-agent/src/lifecycle/launch/spawn.rs +101 -4
  72. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
  73. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
  74. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  75. package/crates/team-agent/src/lifecycle/launch.rs +209 -43
  76. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  77. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  78. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  79. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  80. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  81. package/crates/team-agent/src/lifecycle/restart/agent.rs +213 -3
  82. package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
  83. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  84. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  85. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -15
  86. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  87. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  88. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  89. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  90. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  91. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
  92. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  93. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  95. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  96. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +159 -174
  97. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
  99. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
  100. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  102. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  103. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  104. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  105. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  106. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  107. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  108. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  109. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  110. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  111. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  112. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  113. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  114. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
  115. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
  116. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
  117. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  118. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +24 -23
  119. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
  120. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  121. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  122. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  123. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
  124. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  125. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  126. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  127. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
  128. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  129. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  130. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  131. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  132. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  133. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  134. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  135. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
  136. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  137. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  138. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  139. package/crates/team-agent/src/lifecycle/tests.rs +23 -15
  140. package/crates/team-agent/src/lifecycle/types.rs +61 -2
  141. package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
  142. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  143. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  144. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  145. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  146. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  147. package/crates/team-agent/src/messaging/results.rs +4 -0
  148. package/crates/team-agent/src/messaging/send.rs +34 -0
  149. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  150. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  151. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  152. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  153. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  154. package/crates/team-agent/src/messaging/types.rs +4 -1
  155. package/crates/team-agent/src/model/enums.rs +14 -5
  156. package/crates/team-agent/src/model/permissions.rs +3 -0
  157. package/crates/team-agent/src/model/spec.rs +129 -6
  158. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  159. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  160. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  161. package/crates/team-agent/src/os_probe.rs +73 -2
  162. package/crates/team-agent/src/provider/adapter.rs +231 -4
  163. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  164. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  165. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  166. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  167. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  168. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  169. package/crates/team-agent/src/provider/bypass_flags.rs +124 -0
  170. package/crates/team-agent/src/provider/classify.rs +4 -2
  171. package/crates/team-agent/src/provider/faults.rs +2 -1
  172. package/crates/team-agent/src/provider/mod.rs +13 -0
  173. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  174. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  175. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  176. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  177. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  178. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  179. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  180. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  181. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  182. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  183. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  184. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  185. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  186. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  187. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  188. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  189. package/crates/team-agent/src/provider/types.rs +4 -2
  190. package/crates/team-agent/src/provider/wire.rs +23 -1
  191. package/crates/team-agent/src/state/persist.rs +301 -8
  192. package/crates/team-agent/src/state/repository.rs +5 -0
  193. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  194. package/crates/team-agent/src/tmux_backend.rs +1083 -75
  195. package/crates/team-agent/src/transport/test_support.rs +20 -0
  196. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  197. package/crates/team-agent/src/transport.rs +265 -1
  198. package/npm/install.mjs +129 -73
  199. package/package.json +4 -4
  200. package/skills/team-agent/SKILL.md +33 -238
  201. package/skills/team-agent/command-coverage.json +31 -0
  202. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  203. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  204. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
  205. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  206. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
  207. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  208. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
@@ -1,3 +1,29 @@
1
+ //! ---
2
+ //! purpose: 由 agent 的 profile 声明推出 provider 启动所需的环境覆盖、环境清除与命令覆写
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: prepare_provider_profile_launch
6
+ //! what: 从 YAML agent 节点算出 ProviderProfileLaunch
7
+ //! - name: prepare_provider_profile_launch_from_json
8
+ //! what: 从 runtime state 的 JSON agent 节点算出同一结果
9
+ //! - name: load_profile
10
+ //! what: 按查找顺序找到 profile env 文件并解析成键值
11
+ //! - name: provider_env_unsets
12
+ //! what: 该 provider 与 auth_mode 下必须清除的环境变量集,worker 与 leader 共用这一份
13
+ //! - name: profile_proxy_mode
14
+ //! what: 读出 profile 的代理模式,缺省 inherit
15
+ //! - name: parse_auth_mode
16
+ //! what: 把 auth_mode 字符串解析成枚举,未知值返回 None
17
+ //! depends:
18
+ //! - crate::provider
19
+ //! - crate::model::enums
20
+ //! - std::fs
21
+ //! boundary:
22
+ //! - 只读 profile 文件,不改写也不创建 profile
23
+ //! - 不把密钥值写进事件或错误消息
24
+ //! - provider 的环境清除集只此一份,leader 侧不得另建
25
+ //! maturity: wired
26
+ //! ---
1
27
  use std::collections::{BTreeMap, BTreeSet};
2
28
  use std::path::{Path, PathBuf};
3
29
 
@@ -37,6 +63,15 @@ struct AgentProfileInput {
37
63
  model_source: Option<String>,
38
64
  }
39
65
 
66
+ /// ---
67
+ /// purpose: 从 YAML agent 节点准备 provider 启动参数,profile 目录用默认查找顺序
68
+ /// params:
69
+ /// agent_id: agent 节点没写 id 时的兜底 id
70
+ /// mcp_config: 需要托管 MCP 配置时传入,否则 None
71
+ /// returns: 环境覆盖、环境清除、命令覆写与 claude 配置目录
72
+ /// errors: profile 找不到、必填键缺失或写 runtime env 文件失败时返回 LifecycleError
73
+ /// contract_id: lifecycle.profile_launch.prepare
74
+ /// ---
40
75
  pub(crate) fn prepare_provider_profile_launch(
41
76
  workspace: &Path,
42
77
  agent_id: &str,
@@ -46,6 +81,14 @@ pub(crate) fn prepare_provider_profile_launch(
46
81
  prepare_provider_profile_launch_with_profile_dir(workspace, agent_id, agent, None, mcp_config)
47
82
  }
48
83
 
84
+ /// ---
85
+ /// purpose: 同上,但允许指定优先查找的 profile 目录
86
+ /// params:
87
+ /// profile_dir: 优先查找目录;agent 节点里的 _profile_dir 优先于它
88
+ /// returns: 环境覆盖、环境清除、命令覆写与 claude 配置目录
89
+ /// errors: 同 prepare_provider_profile_launch
90
+ /// contract_id: lifecycle.profile_launch.prepare
91
+ /// ---
49
92
  pub(crate) fn prepare_provider_profile_launch_with_profile_dir(
50
93
  workspace: &Path,
51
94
  agent_id: &str,
@@ -94,6 +137,12 @@ pub(crate) fn prepare_provider_profile_launch_with_profile_dir(
94
137
  prepare_profile_launch(workspace, input, mcp_config)
95
138
  }
96
139
 
140
+ /// ---
141
+ /// purpose: 从 runtime state 的 JSON agent 节点准备 provider 启动参数
142
+ /// returns: 环境覆盖、环境清除、命令覆写与 claude 配置目录
143
+ /// errors: 同 prepare_provider_profile_launch
144
+ /// contract_id: lifecycle.profile_launch.prepare
145
+ /// ---
97
146
  pub(crate) fn prepare_provider_profile_launch_from_json(
98
147
  workspace: &Path,
99
148
  agent_id: &str,
@@ -199,6 +248,14 @@ fn prepare_profile_launch(
199
248
  })
200
249
  }
201
250
 
251
+ /// ---
252
+ /// purpose: 按查找目录顺序定位 profile env 文件并解析成键值表
253
+ /// params:
254
+ /// name: profile 名,先找 name.env 再找 name.example.env
255
+ /// profile_dir: 优先查找目录,None 表示只用默认顺序
256
+ /// returns: 命中的文件路径与解析出的键值
257
+ /// errors: 所有目录都没命中返回 RequirementUnmet,读文件失败返回 StatePersist
258
+ /// ---
202
259
  pub(crate) fn load_profile(
203
260
  workspace: &Path,
204
261
  name: &str,
@@ -424,11 +481,19 @@ fn provider_env_exports(
424
481
  }
425
482
  }
426
483
  }
484
+ // grok/cursor_agent 一期无 profile exports(subscription 已登录态)。
485
+ // Cursor 订阅席的代理透传不在这里:profile 只服务 compatible-api。
486
+ // 订阅路径见 apply_cursor_subscription_proxy_env(只记有无,不记值)。
487
+ Provider::Grok | Provider::CursorAgent => {}
427
488
  Provider::Fake => {}
428
489
  }
429
490
  exports
430
491
  }
431
492
 
493
+ /// ---
494
+ /// purpose: 给出该 provider 与 auth_mode 下必须从子进程环境里清掉的变量名集
495
+ /// returns: 变量名集合;claude 系清掉会让子进程并入父会话的那组标识
496
+ /// ---
432
497
  /// 0.4.x (CR C-1): exposed as `pub(crate)` so the leader launcher
433
498
  /// (leader/start.rs) can reuse the SAME Claude/Codex/Copilot env-unset set
434
499
  /// used by worker spawn. Single source of truth — adding a new provider env
@@ -497,6 +562,11 @@ pub(crate) fn provider_env_unsets(provider: Provider, auth_mode: AuthMode) -> BT
497
562
  // C-7-1 cr verdict: 一期 subscription-only(已登录态),exports/unsets 空;
498
563
  // BYOK(COPILOT_PROVIDER_*)二期立项时单独 cr verdict 再开。
499
564
  Provider::Copilot => {}
565
+ // grok 无 env 需 unset。cursor 订阅席必须保留代理键(TUI 连云要用);
566
+ // `--print` 路径会因 https:// 方案撞 GLOBAL_AGENT,但主路径是 TUI。
567
+ // 禁止在这里 unset 代理。
568
+ Provider::Grok => {}
569
+ Provider::CursorAgent => {}
500
570
  Provider::Fake => {}
501
571
  }
502
572
  unsets
@@ -893,6 +963,12 @@ fn compatible_api_network_exports(
893
963
  .collect()
894
964
  }
895
965
 
966
+ /// ---
967
+ /// purpose: 读出 profile 声明的代理模式
968
+ /// params:
969
+ /// values: 已解析的 profile 键值,先看 PROXY_MODE 再看 NETWORK_MODE
970
+ /// returns: 小写去空白后的模式串,两个键都没有时为 inherit
971
+ /// ---
896
972
  pub(crate) fn profile_proxy_mode(values: &BTreeMap<String, String>) -> String {
897
973
  values
898
974
  .get("PROXY_MODE")
@@ -915,10 +991,18 @@ fn required_profile_keys(provider: Provider, auth_mode: AuthMode) -> &'static [&
915
991
  // C-7-1 cr verdict: copilot 一期 subscription-only,无 BYOK required key 集合;
916
992
  // 二期 BYOK 立项时填(向后兼容字段保留)。
917
993
  Provider::Copilot => &[],
994
+ // 0.5.67: grok/cursor_agent 一期无 BYOK required key 集合 (同 copilot 精神)。
995
+ Provider::Grok | Provider::CursorAgent => &[],
918
996
  Provider::Fake => &[],
919
997
  }
920
998
  }
921
999
 
1000
+ /// ---
1001
+ /// purpose: 取 profile 里某个规范键,取不到时按该键的已知别名回退
1002
+ /// params:
1003
+ /// key: 规范键名,API_KEY BASE_URL MODEL 各有一组别名,其余键不回退
1004
+ /// returns: 命中的非空值
1005
+ /// ---
922
1006
  pub(crate) fn profile_value_or_alternate<'a>(
923
1007
  values: &'a BTreeMap<String, String>,
924
1008
  key: &str,
@@ -949,6 +1033,10 @@ pub(crate) fn profile_value_or_alternate<'a>(
949
1033
  })
950
1034
  }
951
1035
 
1036
+ /// ---
1037
+ /// purpose: 在两个显式给定的键之间取第一个非空值
1038
+ /// returns: 命中的非空值,两个都空则 None
1039
+ /// ---
952
1040
  pub(crate) fn value_or_alternate<'a>(
953
1041
  values: &'a BTreeMap<String, String>,
954
1042
  primary: &str,
@@ -961,6 +1049,10 @@ pub(crate) fn value_or_alternate<'a>(
961
1049
  .map(String::as_str)
962
1050
  }
963
1051
 
1052
+ /// ---
1053
+ /// purpose: 取 profile 里声明的模型名
1054
+ /// returns: MODEL 的非空值,没有时取 ANTHROPIC_MODEL
1055
+ /// ---
964
1056
  pub(crate) fn profile_model(values: &BTreeMap<String, String>) -> Option<&str> {
965
1057
  values
966
1058
  .get("MODEL")
@@ -982,6 +1074,12 @@ fn effective_profile_or_agent_model<'a>(
982
1074
 
983
1075
  pub(crate) use crate::provider::wire::parse_provider;
984
1076
 
1077
+ /// ---
1078
+ /// purpose: 把 auth_mode 字符串解析成枚举
1079
+ /// params:
1080
+ /// raw: 只接受 subscription、official_api、compatible_api
1081
+ /// returns: 对应枚举;未知取值返回 None,由调用方决定兜底
1082
+ /// ---
985
1083
  pub(crate) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
986
1084
  match raw {
987
1085
  "subscription" => Some(AuthMode::Subscription),
@@ -1,3 +1,24 @@
1
+ //! ---
2
+ //! purpose: 对 compatible_api 席位做发车前的 profile 连通性冒烟
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: profile_smoke_checks_for_agents_with_profile_dir
6
+ //! what: 对一组 agent 逐个冒烟,可指定优先查找的 profile 目录
7
+ //! - name: profile_smoke_checks_for_agents
8
+ //! what: 对一组 agent 逐个冒烟,返回逐个结果
9
+ //! - name: smoke_check_agent_profile
10
+ //! what: 单 agent 冒烟,非 compatible_api 直接判 not_required
11
+ //! - name: format_profile_smoke_failures
12
+ //! what: 把失败结果排成一行可读文本
13
+ //! depends:
14
+ //! - crate::lifecycle::profile_launch
15
+ //! - crate::provider::wire
16
+ //! boundary:
17
+ //! - 只读 profile 并发探测请求,不改 profile、不写 runtime state
18
+ //! - 不做重试与退避,超时即判失败
19
+ //! - 结果里不带密钥值
20
+ //! maturity: wired
21
+ //! ---
1
22
  use std::collections::BTreeMap;
2
23
  use std::io::Write as _;
3
24
  use std::path::Path;
@@ -13,6 +34,13 @@ use crate::provider::wire::provider_wire;
13
34
 
14
35
  pub(crate) const DEFAULT_PROFILE_SMOKE_TIMEOUT: Duration = Duration::from_secs(8);
15
36
 
37
+ /// ---
38
+ /// purpose: 用默认 profile 目录对一组 agent 逐个冒烟
39
+ /// params:
40
+ /// agents: spec 里的 agent 节点列表
41
+ /// timeout: 单次探测的超时
42
+ /// returns: 与入参同序的逐 agent 结果 JSON
43
+ /// ---
16
44
  pub(crate) fn profile_smoke_checks_for_agents(
17
45
  workspace: &Path,
18
46
  agents: &[YamlValue],
@@ -21,6 +49,12 @@ pub(crate) fn profile_smoke_checks_for_agents(
21
49
  profile_smoke_checks_for_agents_with_profile_dir(workspace, agents, None, timeout)
22
50
  }
23
51
 
52
+ /// ---
53
+ /// purpose: 用指定 profile 目录对一组 agent 逐个冒烟
54
+ /// params:
55
+ /// profile_dir: 优先查找的 profile 目录,None 表示只用默认查找顺序
56
+ /// returns: 与入参同序的逐 agent 结果 JSON
57
+ /// ---
24
58
  pub(crate) fn profile_smoke_checks_for_agents_with_profile_dir(
25
59
  workspace: &Path,
26
60
  agents: &[YamlValue],
@@ -33,6 +67,13 @@ pub(crate) fn profile_smoke_checks_for_agents_with_profile_dir(
33
67
  .collect()
34
68
  }
35
69
 
70
+ /// ---
71
+ /// purpose: 单个 agent 的 profile 冒烟
72
+ /// params:
73
+ /// agent: 单个 agent 节点,缺 provider 按 codex、缺 auth_mode 按 subscription
74
+ /// timeout: 单次 HTTP 探测的超时
75
+ /// returns: 结果 JSON。非 compatible_api 记 not_required,profile 里关掉冒烟记 skipped_by_profile,其余走真实请求
76
+ /// ---
36
77
  pub(crate) fn smoke_check_agent_profile(
37
78
  workspace: &Path,
38
79
  agent: &YamlValue,
@@ -108,6 +149,10 @@ pub(crate) fn smoke_check_agent_profile(
108
149
  result
109
150
  }
110
151
 
152
+ /// ---
153
+ /// purpose: 把失败结果拼成一行人类可读的摘要
154
+ /// returns: 以分号分隔的 agent 与原因,缺字段时退化成 agent 与 failed
155
+ /// ---
111
156
  pub(crate) fn format_profile_smoke_failures(failures: &[Value]) -> String {
112
157
  failures
113
158
  .iter()
@@ -187,7 +232,12 @@ impl SmokeTarget {
187
232
  }
188
233
  // C-7-1 cr verdict: copilot 一期 subscription-only,无 BYOK HTTP smoke
189
234
  // 入口;同 GeminiCli/Fake 走 unsupported_provider_smoke_skipped。
190
- Provider::Copilot | Provider::GeminiCli | Provider::Fake => {
235
+ // 0.5.67: grok/cursor_agent 同走 skipped (一期 subscription 已登录态)。
236
+ Provider::Copilot
237
+ | Provider::Grok
238
+ | Provider::CursorAgent
239
+ | Provider::GeminiCli
240
+ | Provider::Fake => {
191
241
  Err("unsupported_provider_smoke_skipped")
192
242
  }
193
243
  }
@@ -1,9 +1,52 @@
1
+ //! ---
2
+ //! purpose: 单席位的起、停与重置,全部在 workspace 级生命周期锁下串行
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: start_agent
6
+ //! what: 起或复活一席,按四象限定出 resume 或 fresh
7
+ //! - name: stop_agent
8
+ //! what: 停一席,按 pane、同角色残留 pane、窗口三级收敛并取排空证据
9
+ //! - name: reset_agent
10
+ //! what: 必须显式丢弃会话,停席后换血再起
11
+ //! - name: start_agent_at_paths
12
+ //! what: 起席的实体实现,owner 门、暂停判定、启动模式决策与落 state
13
+ //! - name: drain_old_pane_and_pid
14
+ //! what: 有界轮询等旧 pane 与旧进程消失,产出可核的证据载荷
15
+ //! depends:
16
+ //! - crate::lifecycle::lock
17
+ //! - crate::lifecycle::restart::selection
18
+ //! - crate::lifecycle::restart::remove
19
+ //! - crate::lifecycle::restart::team_state
20
+ //! - crate::lifecycle::launch
21
+ //! - crate::state::projection
22
+ //! - crate::state::persist
23
+ //! - crate::platform::process
24
+ //! - crate::lifecycle::profile_launch
25
+ //! - crate::lifecycle::worker_command_context
26
+ //! - crate::event_log::EventLog
27
+ //! boundary:
28
+ //! - 每个对外入口先取 workspace 级生命周期锁,本文件不做无锁写
29
+ //! - owner 门先于未知席位报错,权限判定不被存在性判定绕过
30
+ //! - reset 未显式丢弃会话时直接拒绝,不擅自换血
31
+ //! - 排空是有界等待,等不到只如实记录,不无限阻塞
32
+ //! maturity: wired
33
+ //! ---
1
34
  use super::common::*;
2
35
  use super::selection::decide_start_mode;
3
36
  use super::team_state::write_team_state;
4
37
  use super::*;
5
38
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
6
39
 
40
+ /// ---
41
+ /// purpose: 起或复活一席的对外入口
42
+ /// params:
43
+ /// force: 窗口已存在时也重起
44
+ /// open_display: 是否开显示
45
+ /// allow_fresh: resume 依据缺失时允许改跑全新会话
46
+ /// returns: Running、Noop 或 Paused
47
+ /// errors: 选不到 team 返回 TeamSelect,owner 门不过返回 OwnerRefused,席位不存在返回 RequirementUnmet
48
+ /// contract_id: lifecycle.start_agent.entry
49
+ /// ---
7
50
  /// `start_agent(workspace, agent_id, force, open_display, allow_fresh, team)`
8
51
  /// (`lifecycle/start.py:72`)。`_runtime_lock("start-agent")` 下串行:resume-or-fresh
9
52
  /// 决策、resume 窗口退出回退 fresh、起后投递 pending message、起 coordinator。
@@ -39,6 +82,12 @@ pub fn start_agent(
39
82
  )
40
83
  }
41
84
 
85
+ /// ---
86
+ /// purpose: 带注入 transport 的起席入口;未指定 team 且解析失败时退回把入参 workspace 当作两个路径
87
+ /// returns: Running、Noop 或 Paused
88
+ /// errors: 指定了 team 而解析失败时透传该错误,其余同 start_agent
89
+ /// contract_id: lifecycle.start_agent.entry
90
+ /// ---
42
91
  /// `start_agent` with an injected transport — wires the single-worker resume/fresh spawn +
43
92
  /// start_coordinator (rt-host-a sweep: was a stub returning RequirementUnmet at the spawn boundary).
44
93
  #[allow(clippy::too_many_arguments)]
@@ -80,6 +129,14 @@ pub(crate) fn start_agent_with_transport(
80
129
  )
81
130
  }
82
131
 
132
+ /// ---
133
+ /// purpose: 起席的实体实现,读团队作用域 state、过 owner 门、定启动模式、spawn 并标记已起
134
+ /// params:
135
+ /// spec_workspace: spec 所在目录,用于补回席位的命令上下文
136
+ /// open_display: 当前实现未直接使用,仅参与自适应布局判定
137
+ /// returns: 席位暂停时直接返回 Paused,其余按启动模式返回 Running 或 Noop
138
+ /// errors: 读 state 失败返回 StatePersist,owner 门不过返回 OwnerRefused,席位不存在返回 RequirementUnmet
139
+ /// ---
83
140
  #[allow(clippy::too_many_arguments)]
84
141
  pub(crate) fn start_agent_at_paths(
85
142
  workspace: &Path,
@@ -181,6 +238,31 @@ pub(crate) fn start_agent_at_paths(
181
238
  );
182
239
  }
183
240
  let agent_live = agent_live && (!has_collision || noop_pane.is_some());
241
+ // 0.5.66 bypass 单源 §2.6 — dangerously_skip_permissions drift 挡板:
242
+ // Noop 路径不 spawn,若声明面(角色 md / spec)与运行面(as-launched)不一致,
243
+ // 拒 Noop 报明,防"改了 md 却以为生效、进程仍带旧 bypass 跑着"的暗门。
244
+ let file_bypass = agent
245
+ .get("dangerously_skip_permissions")
246
+ .and_then(serde_json::Value::as_bool);
247
+ let launched_bypass = raw_agent
248
+ .get("as_launched_dangerously_skip_permissions")
249
+ .and_then(serde_json::Value::as_bool);
250
+ if !force && agent_live && check_bypass_drift(file_bypass, launched_bypass) {
251
+ // 0.5.66 bypass 单源 §2.7:Noop drift 拒也写审计事件。
252
+ let _ = crate::event_log::EventLog::new(workspace).write(
253
+ "worker.spawn_dangerously_skip_permissions_drift_denied",
254
+ serde_json::json!({
255
+ "agent_id": agent_id.as_str(),
256
+ "file_value": file_bypass,
257
+ "launched_value": launched_bypass,
258
+ "action": "noop_denied_require_remove_add_or_force",
259
+ }),
260
+ );
261
+ return Err(LifecycleError::RequirementUnmet(format!(
262
+ "dangerously_skip_permissions drift for agent {agent_id}: file={file_bypass:?} but running process launched with {launched_bypass:?}. \
263
+ Run `remove-agent {agent_id}` then `add-agent {agent_id}` to apply, or `restart --force {agent_id}` to accept fresh spawn."
264
+ )));
265
+ }
184
266
  if !force && agent_live {
185
267
  let old_binding = pane_binding_snapshot(&raw_agent);
186
268
  let refreshed_binding = noop_pane.as_ref().map(pane_binding_from_live);
@@ -257,7 +339,10 @@ pub(crate) fn start_agent_at_paths(
257
339
  };
258
340
  let into_existing_session =
259
341
  session_live_or_default(transport, &session_name, session_name_present(&state));
260
- let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
342
+ let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn_json(
343
+ &agent,
344
+ provider,
345
+ )?;
261
346
  let layout_placement = if adaptive_layout {
262
347
  crate::lifecycle::launch::adaptive_existing_placement_for_agent(
263
348
  &state,
@@ -323,6 +408,30 @@ pub(crate) fn start_agent_at_paths(
323
408
  &safety,
324
409
  start_mode,
325
410
  )?;
411
+ // 0.5.66 bypass 单源 §2.7:启动即带 bypass → 审计留痕。
412
+ if safety.enabled {
413
+ let state_agent = state
414
+ .get("agents")
415
+ .and_then(|v| v.get(agent_id.as_str()));
416
+ let _ = crate::event_log::EventLog::new(workspace).write(
417
+ "worker.spawn_dangerously_skip_permissions",
418
+ serde_json::json!({
419
+ "agent_id": agent_id.as_str(),
420
+ "role": state_agent
421
+ .and_then(|a| a.get("role"))
422
+ .and_then(serde_json::Value::as_str)
423
+ .unwrap_or_default(),
424
+ "provider": state_agent
425
+ .and_then(|a| a.get("provider"))
426
+ .and_then(serde_json::Value::as_str)
427
+ .unwrap_or_default(),
428
+ "trigger": "start-agent",
429
+ "spec_source_path": crate::lifecycle::launch::agent_id_spec_source_path(
430
+ workspace, agent_id.as_str(),
431
+ ),
432
+ }),
433
+ );
434
+ }
326
435
  // **0.3.24 add-agent socket drift fix**: keep `state.tmux_endpoint` /
327
436
  // `state.tmux_socket` synchronized with the transport actually used for the
328
437
  // spawn. Without this, add-agent / fork-agent could spawn to a socket that
@@ -736,6 +845,13 @@ fn tmux_start_mode_for_spawn(
736
845
  // 0.4.6 Stage 1: reset/start clean-boundary helpers
737
846
  // ═══════════════════════════════════════════════════════════════════════
738
847
 
848
+ /// ---
849
+ /// purpose: 有界轮询等待旧 pane 不可达且旧进程退出
850
+ /// params:
851
+ /// old_pane: 旧 pane,None 视作已死
852
+ /// old_pid: 旧进程号,None 视作已退出
853
+ /// returns: 证据载荷,含两者是否确认消失与实际等待毫秒数;到上限仍未消失也照实返回
854
+ /// ---
739
855
  /// Bounded polling: wait for the old pane to become unreachable AND, when
740
856
  /// possible, for the old pane_pid to exit. Returns a JSON evidence blob
741
857
  /// recording the observed transition for events.
@@ -778,6 +894,10 @@ pub(super) fn drain_old_pane_and_pid(
778
894
  })
779
895
  }
780
896
 
897
+ /// ---
898
+ /// purpose: 尽力探测某进程号是否还活着
899
+ /// returns: 由平台层给出的判定
900
+ /// ---
781
901
  /// Best-effort liveness probe for a pid.
782
902
  ///
783
903
  /// 0.5.x Windows portability Batch 3: routes through
@@ -791,6 +911,10 @@ pub(super) fn pid_is_alive(pid: u32) -> bool {
791
911
  crate::platform::process::pid_is_alive(pid)
792
912
  }
793
913
 
914
+ /// ---
915
+ /// purpose: 从 state 里读该席位的 pane 进程号
916
+ /// returns: 存在且是无符号整数时返回它,否则 None
917
+ /// ---
794
918
  /// Read state.agents[agent_id].pane_pid (u32) from the runtime state.
795
919
  pub(super) fn state_pane_pid(state: &serde_json::Value, agent_id: &AgentId) -> Option<u32> {
796
920
  state
@@ -801,6 +925,10 @@ pub(super) fn state_pane_pid(state: &serde_json::Value, agent_id: &AgentId) -> O
801
925
  .map(|p| p as u32)
802
926
  }
803
927
 
928
+ /// ---
929
+ /// purpose: 算出该席位下一次的 spawn 世代号
930
+ /// returns: 当前值加一,缺失时按 0 起算;世代号用于把捕获与事件归因到本次进程
931
+ /// ---
804
932
  /// Read + increment state.agents[agent_id].spawn_epoch. The epoch is a
805
933
  /// monotonic counter persisted with the agent row that uniquely tags each
806
934
  /// fresh-start / reset / restart cycle. Subsequent capture / event /
@@ -817,6 +945,12 @@ pub(super) fn next_spawn_epoch(state: &serde_json::Value, agent_id: &AgentId) ->
817
945
  current.saturating_add(1)
818
946
  }
819
947
 
948
+ /// ---
949
+ /// purpose: 停一席的对外入口
950
+ /// returns: 停止报告
951
+ /// errors: 选不到 team 返回 TeamSelect,席位不存在返回 RequirementUnmet
952
+ /// contract_id: lifecycle.stop_agent.entry
953
+ /// ---
820
954
  /// `stop_agent(workspace, agent_id, team)`(`lifecycle/operations.py:62`)。
821
955
  /// owner-gate → kill window → **同时关显示** → 写 state。
822
956
  pub fn stop_agent(
@@ -843,6 +977,12 @@ pub fn stop_agent(
843
977
  )
844
978
  }
845
979
 
980
+ /// ---
981
+ /// purpose: 带注入 transport 的停席入口
982
+ /// returns: 停止报告
983
+ /// errors: 同 stop_agent
984
+ /// contract_id: lifecycle.stop_agent.entry
985
+ /// ---
846
986
  pub(crate) fn stop_agent_with_transport(
847
987
  workspace: &Path,
848
988
  agent_id: &AgentId,
@@ -867,6 +1007,11 @@ pub(crate) fn stop_agent_with_transport(
867
1007
  )
868
1008
  }
869
1009
 
1010
+ /// ---
1011
+ /// purpose: 停席的实体实现,解析席位、收敛杀 pane 与窗口、取排空证据后落 state
1012
+ /// returns: 停止报告;注意报告里的显示已关闭字段恒为真,不反映实际关显示结果
1013
+ /// errors: 席位解析不到实体返回 RequirementUnmet,写 state 失败返回 StatePersist
1014
+ /// ---
870
1015
  pub(super) fn stop_agent_at_paths(
871
1016
  workspace: &Path,
872
1017
  spec_workspace: &Path,
@@ -1024,6 +1169,13 @@ pub(super) fn stop_agent_at_paths(
1024
1169
  })
1025
1170
  }
1026
1171
 
1172
+ /// ---
1173
+ /// purpose: 取团队作用域的投影 state,把选择拒绝转成带原因的错误
1174
+ /// params:
1175
+ /// team: 目标团队,None 时按唯一性选
1176
+ /// returns: 投影后的 state
1177
+ /// errors: 目标歧义或选不出时返回 TeamSelect 并在消息里带原因与细节,读取失败返回 StatePersist
1178
+ /// ---
1027
1179
  /// golden `resolve_team_scoped_state` (state.py:243): returns the team-scoped projected state, or
1028
1180
  /// surfaces the refusal dict (`team_target_ambiguous` / `team_target_unresolved`) as a typed error
1029
1181
  /// BEFORE the owner gate / unknown-worker raise (operations.py:64-66). The lifecycle return types are
@@ -1078,6 +1230,12 @@ fn mark_agent_started(
1078
1230
  // lifecycle/topology fields so absence == UNKNOWN until the next
1079
1231
  // coordinator tick or pane fallback produces a post-spawn observation.
1080
1232
  clear_agent_runtime_activity_observation(agent);
1233
+ // 0.5.66 bypass 单源 §2.6:fresh spawn 那一刻记录运行面 bypass 值,
1234
+ // 供下一次 restart 的 Noop drift 挡板比较。
1235
+ agent.insert(
1236
+ "as_launched_dangerously_skip_permissions".to_string(),
1237
+ serde_json::json!(safety.enabled),
1238
+ );
1081
1239
  // S1-CAPTURE-001 (0.4.8, CR M3 provider-agnostic): on a Fresh /
1082
1240
  // FreshAfterMissingRollout start, the prior session's authoritative
1083
1241
  // capture tuple MUST be cleared before persist_command_plan_state
@@ -1193,6 +1351,14 @@ fn mark_agent_started(
1193
1351
  Ok(())
1194
1352
  }
1195
1353
 
1354
+ /// ---
1355
+ /// purpose: 重置一席的对外入口
1356
+ /// params:
1357
+ /// discard_session: 必须为真,否则直接返回拒绝
1358
+ /// returns: 重置结果,未显式丢弃会话时返回 Refused
1359
+ /// errors: 选不到 team 返回 TeamSelect,其余透传实体实现
1360
+ /// contract_id: lifecycle.reset_agent.entry
1361
+ /// ---
1196
1362
  /// `reset_agent(workspace, agent_id, discard_session, open_display, team)`
1197
1363
  /// (`lifecycle/operations.py:102`)。discard + 重起;**未传 discard_session → 拒绝**。
1198
1364
  pub fn reset_agent(
@@ -1228,6 +1394,14 @@ pub fn reset_agent(
1228
1394
  )
1229
1395
  }
1230
1396
 
1397
+ /// ---
1398
+ /// purpose: 带注入 transport 的重置入口
1399
+ /// params:
1400
+ /// discard_session: 必须为真,否则直接返回拒绝
1401
+ /// returns: 重置结果
1402
+ /// errors: 同 reset_agent
1403
+ /// contract_id: lifecycle.reset_agent.entry
1404
+ /// ---
1231
1405
  pub(crate) fn reset_agent_with_transport(
1232
1406
  workspace: &Path,
1233
1407
  agent_id: &AgentId,
@@ -1609,7 +1783,6 @@ fn write_start_agent_start_event(
1609
1783
  let adapter = crate::provider::get_adapter(provider);
1610
1784
  // Contract C / F6.4: event log must record the same context-aware argv that the
1611
1785
  // actual spawn used — so the role/tools/MCP context appears in `start_agent.agent_start`.
1612
- let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
1613
1786
  let command_agent = crate::lifecycle::worker_command_context::WorkerCommandAgent::from_json(
1614
1787
  agent,
1615
1788
  Some(agent_id.as_str()),
@@ -1620,7 +1793,6 @@ fn write_start_agent_start_event(
1620
1793
  let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
1621
1794
  &command_agent,
1622
1795
  provider,
1623
- &safety,
1624
1796
  )?;
1625
1797
  let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
1626
1798
  let mcp_config = adapter
@@ -1727,6 +1899,14 @@ fn write_stop_complete_event(
1727
1899
  Ok(())
1728
1900
  }
1729
1901
 
1902
+ /// ---
1903
+ /// purpose: 写一条停席排空证据事件
1904
+ /// params:
1905
+ /// target: 被停的目标描述
1906
+ /// drain: 排空证据载荷,其字段被并进事件顶层
1907
+ /// returns: 成功返回空值
1908
+ /// errors: 事件写入失败时返回 StatePersist
1909
+ /// ---
1730
1910
  /// 0.4.6 Stage 1: drain evidence for the old pane+pid after stop. Fires
1731
1911
  /// during reset/stop so operators can see whether the prior worker really
1732
1912
  /// went away or stuck around inside the resp budget.
@@ -1831,6 +2011,17 @@ fn write_reset_complete_event(
1831
2011
  Ok(())
1832
2012
  }
1833
2013
 
2014
+ /// 0.5.66 bypass 单源 §2.6:声明面(file)与运行面(as-launched)是否 drift。
2015
+ /// 缺 as-launched(从未 spawn)= 视为未启动,不 drift(首次 Noop 无进程可比)。
2016
+ fn check_bypass_drift(file: Option<bool>, launched: Option<bool>) -> bool {
2017
+ match (file, launched) {
2018
+ (Some(file), Some(launched)) => file != launched,
2019
+ _ => false,
2020
+ }
2021
+ }
2022
+
2023
+ #[cfg(test)]
2024
+
1834
2025
  #[cfg(test)]
1835
2026
  mod tests {
1836
2027
  use super::*;
@@ -1907,4 +2098,23 @@ mod tests {
1907
2098
  "other-agent collision checks must also include tmux_socket"
1908
2099
  );
1909
2100
  }
2101
+
2102
+ // 0.5.66 bypass 单源 §4.1:Noop drift 检测——声明面与运行面不一致 → drift。
2103
+ #[test]
2104
+ fn test_restart_noop_denied_on_drift() {
2105
+ // file=true, launched=false → drift(改了 md 升 bypass 但进程没带)。
2106
+ assert!(check_bypass_drift(Some(true), Some(false)));
2107
+ // file=false, launched=true → drift(改了 md 撤 bypass 但进程还带着)。
2108
+ assert!(check_bypass_drift(Some(false), Some(true)));
2109
+ }
2110
+
2111
+ // 0.5.66 bypass 单源 §4.1:无 drift 或从未 spawn(as-launched 缺)→ 不拒。
2112
+ #[test]
2113
+ fn test_restart_fresh_spawn_after_drift_uses_file_value() {
2114
+ assert!(!check_bypass_drift(Some(true), Some(true)));
2115
+ assert!(!check_bypass_drift(Some(false), Some(false)));
2116
+ // 从未 spawn:as-launched 缺失 → 不 drift(首次 Noop 无进程可比)。
2117
+ assert!(!check_bypass_drift(Some(true), None));
2118
+ assert!(!check_bypass_drift(None, None));
2119
+ }
1910
2120
  }