@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,22 @@
1
+ //! ---
2
+ //! purpose: 冷启路径的 worker spawn 执行器,按 spec 逐个起席并汇总启动结果
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: spawn_agents
6
+ //! what: 按 spec 顺序起各非 paused 席位,返回已起席位清单
7
+ //! - name: agent_id_spec_source_path
8
+ //! what: 尽力还原某席位角色文档的路径,用于审计事件
9
+ //! depends:
10
+ //! - crate::transport::Transport
11
+ //! - crate::provider
12
+ //! - crate::state::persist
13
+ //! - crate::lifecycle::profile_launch
14
+ //! - crate::lifecycle::worker_command_context
15
+ //! boundary:
16
+ //! - 只负责起席与收集结果,state 落盘由 state_projection 负责
17
+ //! - 启动提示按掉与 fast 模式启用都是尽力而为,失败不阻断
18
+ //! maturity: wired
19
+ //! ---
1
20
  //!
2
21
  //! unit-8 (Stage 3) — `lifecycle::launch::spawn` phase boundary.
3
22
  //!
@@ -22,12 +41,19 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
22
41
 
23
42
  use super::*;
24
43
 
44
+ /// ---
45
+ /// purpose: 按 spec 顺序起各席位
46
+ /// params:
47
+ /// spec_path: spec 路径,team 目录优先取 state 里的 team_dir
48
+ /// session_name: 目标 tmux session
49
+ /// returns: 已起席位清单,含目标 pane、启动模式、布局与显示信息;spawn 后 pane 已死的席位被跳过
50
+ /// errors: 命令拼装或 transport spawn 失败时返回 LifecycleError
51
+ /// ---
25
52
  pub(super) fn spawn_agents(
26
53
  workspace: &Path,
27
54
  spec_path: &Path,
28
55
  spec: &Value,
29
56
  session_name: &SessionName,
30
- safety: &DangerousApproval,
31
57
  transport: &dyn Transport,
32
58
  ) -> Result<Vec<StartedAgent>, LifecycleError> {
33
59
  // E5 解耦:team_dir(角色定义 + profiles 所在)≠ spec_path.parent()(spec 已迁出到 .team/runtime)。
@@ -100,9 +126,10 @@ pub(super) fn spawn_agents(
100
126
  let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
101
127
  &command_agent,
102
128
  provider,
103
- safety,
104
129
  )?;
105
130
  let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
131
+ // 0.5.66 bypass 单源:per-agent safety 只服务 env 注入与审计,不参与 argv 决策。
132
+ let safety = effective_runtime_config_for_worker_spawn(agent, provider)?;
106
133
  let mcp_team_id = runtime_team_key_for_spec(spec_path, spec, session_name);
107
134
  let mcp_config = adapter
108
135
  .mcp_config(auth_mode)
@@ -207,8 +234,12 @@ pub(super) fn spawn_agents(
207
234
  )?;
208
235
  }
209
236
  fill_spawn_placeholders_full(&mut plan.argv, workspace, agent_id_raw, Some(&mcp_team_id));
210
- let mut env =
211
- inherited_env_with_team_overrides(workspace, agent_id_raw, Some(&mcp_team_id));
237
+ let mut env = inherited_env_with_team_overrides(
238
+ workspace,
239
+ agent_id_raw,
240
+ Some(&mcp_team_id),
241
+ Some(auth_mode_env_value(auth_mode)),
242
+ );
212
243
  apply_profile_launch_env(&mut env, &profile_launch);
213
244
  apply_mcp_auto_approval_env(&mut env, &safety);
214
245
  // Python providers.py:145 + launch/core.py:253 — fresh launch runs the worker
@@ -256,6 +287,31 @@ pub(super) fn spawn_agents(
256
287
  );
257
288
  }
258
289
  }
290
+ // Cursor: role 经 .cursor/rules;MCP 身份必须写进 mcp.json env
291
+ // (不继承父进程 TEAM_AGENT_*)。--workspace 钉物理路径。
292
+ // mcp.json last-writer:同 workspace 第二 CursorAgent 拒绝。
293
+ if matches!(provider, Provider::CursorAgent) {
294
+ refuse_second_cursor_occupant(workspace, agent_id_raw, Some(spec))?;
295
+ apply_cursor_agent_rules_overlay(workspace, agent_id_raw, system_prompt.as_str())?;
296
+ apply_cursor_mcp_overlay(workspace, &mcp_config)?;
297
+ enable_cursor_workspace_mcp(workspace)?;
298
+ apply_cursor_workspace_physical_path(&mut plan.argv, workspace);
299
+ let proxy = apply_cursor_subscription_proxy_env(&mut env);
300
+ let event_log = crate::event_log::EventLog::new(workspace);
301
+ let _ = event_log.write(
302
+ "provider.cursor.proxy_presence",
303
+ serde_json::json!({
304
+ "agent_id": agent_id_raw,
305
+ "https_proxy": proxy.https_proxy,
306
+ "no_proxy": proxy.no_proxy,
307
+ }),
308
+ );
309
+ }
310
+ // Grok 无 --mcp-config;写 <cwd>/.grok/config.toml 让 CLI 读到 team-agent。
311
+ if matches!(provider, Provider::Grok) {
312
+ ensure_grok_login_and_folder_trust(workspace)?;
313
+ apply_grok_mcp_overlay(workspace, &mcp_config)?;
314
+ }
259
315
  let spawn_epoch = u64::try_from(started.len()).unwrap_or(u64::MAX);
260
316
  let spawned_at = spawn_timestamp_for_agent(u32::try_from(spawn_epoch).unwrap_or(u32::MAX));
261
317
  // E6 层1 实证3 + 诊断留痕:落最终 worker argv(spawn 前的真实形态)。
@@ -283,6 +339,25 @@ pub(super) fn spawn_agents(
283
339
  "spawn_epoch": spawn_epoch,
284
340
  })),
285
341
  );
342
+ // 0.5.66 bypass 单源 §2.7:最终 argv 含 bypass flag → 审计留痕。
343
+ if matches!(
344
+ agent.get("dangerously_skip_permissions"),
345
+ Some(Value::Bool(true))
346
+ ) {
347
+ let _ = event_log.write(
348
+ "worker.spawn_dangerously_skip_permissions",
349
+ serde_json::json!({
350
+ "agent_id": agent_id_raw,
351
+ "role": agent
352
+ .get("role")
353
+ .and_then(Value::as_str)
354
+ .unwrap_or_default(),
355
+ "provider": provider,
356
+ "trigger": "launch",
357
+ "spec_source_path": agent_id_spec_source_path(workspace, agent_id_raw),
358
+ }),
359
+ );
360
+ }
286
361
  }
287
362
  // 0.3.28 Step 4b: replaced the `adaptive_layout_plan` 3-pane tiling
288
363
  // with Python-parity 1-window-per-agent placement. Window name =
@@ -381,3 +456,25 @@ pub(super) fn spawn_agents(
381
456
  }
382
457
  Ok(started)
383
458
  }
459
+
460
+ /// ---
461
+ /// purpose: 尽力还原某席位角色文档的路径
462
+ /// returns: state 里有 team_dir 时用它下的 agents 目录,否则退到 workspace 下的当前团队目录
463
+ /// ---
464
+ /// 0.5.66 bypass 单源 §2.7:审计事件里角色 md 的 spec 来源路径(尽力解析,失败空串)。
465
+ pub(crate) fn agent_id_spec_source_path(workspace: &Path, agent_id: &str) -> String {
466
+ let team_dir = crate::state::persist::load_runtime_state(workspace)
467
+ .ok()
468
+ .and_then(|state| {
469
+ state
470
+ .get("team_dir")
471
+ .and_then(serde_json::Value::as_str)
472
+ .filter(|s| !s.is_empty())
473
+ .map(PathBuf::from)
474
+ });
475
+ team_dir
476
+ .map(|dir| dir.join("agents").join(format!("{agent_id}.md")))
477
+ .unwrap_or_else(|| workspace.join(".team/current/agents").join(format!("{agent_id}.md")))
478
+ .display()
479
+ .to_string()
480
+ }
@@ -1,3 +1,35 @@
1
+ //! ---
2
+ //! purpose: spec 的读取改写与 runtime state 初值物化,含 quick-start 的 owner 种入
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: initial_runtime_state
6
+ //! what: 由 spec 生成首份 runtime state 树,并种入 launched owner
7
+ //! - name: write_spec_atomic
8
+ //! what: 先写同目录临时文件再 rename,避免留下半截 spec
9
+ //! - name: spec_session_name
10
+ //! what: 取 runtime.session_name,缺省由 team 名派生
11
+ //! - name: spec_agents
12
+ //! what: 取 spec 里全部 agent id
13
+ //! - name: spec_routes
14
+ //! what: 对 spec 里每个 task 算出路由决策
15
+ //! - name: effective_runtime_config_for_worker_spawn
16
+ //! what: 由单个 agent 的 dangerously_skip_permissions 定出 bypass 审批结论
17
+ //! depends:
18
+ //! - crate::state::projection
19
+ //! - crate::state::identity
20
+ //! - crate::state::ownership
21
+ //! - crate::model::yaml
22
+ //! - crate::model::routing
23
+ //! - crate::provider::bypass_flags
24
+ //! - crate::lifecycle::launch::identity
25
+ //! - crate::lifecycle::launch::leader_context
26
+ //! - crate::lifecycle::launch::worker_env
27
+ //! boundary:
28
+ //! - 只生成与改写数据,不 spawn 进程也不开显示
29
+ //! - bypass 只认单个 agent 自己的声明,不从 runtime 配置或 leader argv 全队继承
30
+ //! - spec 写盘一律走原子替换,不做就地截断写
31
+ //! maturity: wired
32
+ //! ---
1
33
  //!
2
34
  //! unit-8 (Stage 3) — `lifecycle::launch::spec_state` phase boundary.
3
35
  //!
@@ -33,7 +65,6 @@ use crate::transport::{PaneId, SessionName, Target, Transport, WindowName};
33
65
 
34
66
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
35
67
 
36
- use super::approval::detect_dangerous_approval;
37
68
  use super::identity::spec_display_backend;
38
69
  use super::leader_context::{
39
70
  attributed_provider_for_pane_across_tmux_sockets, caller_provider_for_seed_with_lookup,
@@ -41,6 +72,12 @@ use super::leader_context::{
41
72
  };
42
73
  use super::worker_env::spawn_timestamp;
43
74
 
75
+ /// ---
76
+ /// purpose: 由 spec 生成首份 runtime state 树
77
+ /// params:
78
+ /// team_key: 本团队的 runtime 键
79
+ /// returns: 含 spec_path、workspace、team_dir、session_name、leader、agents、tasks 与 display_backend 的 state;随后按环境种入 launched owner,种不到则种一份 unbound owner
80
+ /// ---
44
81
  pub(super) fn initial_runtime_state(
45
82
  spec: &Value,
46
83
  spec_path: &Path,
@@ -114,6 +151,12 @@ pub(super) fn initial_runtime_state(
114
151
  state
115
152
  }
116
153
 
154
+ /// ---
155
+ /// purpose: 从环境里的 caller 身份种入 launched owner
156
+ /// params:
157
+ /// state: 待改写的 state,成功时写入 owner 与 leader receiver
158
+ /// returns: true 表示已种入,取不到 caller 身份或无 pane 时为 false
159
+ /// ---
117
160
  pub(super) fn seed_launched_owner_from_env(state: &mut serde_json::Value) -> bool {
118
161
  let team_id = crate::state::projection::team_state_key(state);
119
162
  let Ok(caller) = crate::state::identity::caller_identity_from_env(
@@ -131,6 +174,13 @@ pub(super) fn seed_launched_owner_from_env(state: &mut serde_json::Value) -> boo
131
174
  )
132
175
  }
133
176
 
177
+ /// ---
178
+ /// purpose: 用给定的 caller 身份与 pane provider 查询函数种入 owner 与 leader receiver
179
+ /// params:
180
+ /// caller: 调用方身份,pane_id 为空则不种
181
+ /// lookup_pane_provider: 由 pane 反查 provider 的函数,便于测试替换
182
+ /// returns: true 表示已写入 owner 记录
183
+ /// ---
134
184
  pub(super) fn seed_launched_owner_from_caller_with_provider_lookup(
135
185
  state: &mut serde_json::Value,
136
186
  caller: crate::state::owner_gate::CallerIdentity,
@@ -187,6 +237,10 @@ pub(super) fn seed_launched_owner_from_caller_with_provider_lookup(
187
237
  true
188
238
  }
189
239
 
240
+ /// ---
241
+ /// purpose: 判断环境里是否带有任一 leader 身份变量
242
+ /// returns: pane id、session uuid、其 override 或 provider 任一非空即 true
243
+ /// ---
190
244
  pub(super) fn has_positive_caller_leader_env() -> bool {
191
245
  env_nonempty("TEAM_AGENT_LEADER_PANE_ID")
192
246
  || env_nonempty("TEAM_AGENT_LEADER_SESSION_UUID")
@@ -194,12 +248,20 @@ pub(super) fn has_positive_caller_leader_env() -> bool {
194
248
  || env_nonempty("TEAM_AGENT_LEADER_PROVIDER")
195
249
  }
196
250
 
251
+ /// ---
252
+ /// purpose: 判断某环境变量存在且非空
253
+ /// returns: 存在且非空为 true
254
+ /// ---
197
255
  pub(super) fn env_nonempty(key: &str) -> bool {
198
256
  std::env::var(key)
199
257
  .ok()
200
258
  .is_some_and(|value| !value.is_empty())
201
259
  }
202
260
 
261
+ /// ---
262
+ /// purpose: 把 spec 的 tasks 列表转成 JSON 数组
263
+ /// returns: 转换后的数组,没有 tasks 时为空数组
264
+ /// ---
203
265
  pub(super) fn spec_tasks_json(spec: &Value) -> serde_json::Value {
204
266
  spec.get("tasks")
205
267
  .and_then(Value::as_list)
@@ -207,6 +269,10 @@ pub(super) fn spec_tasks_json(spec: &Value) -> serde_json::Value {
207
269
  .unwrap_or_else(|| serde_json::json!([]))
208
270
  }
209
271
 
272
+ /// ---
273
+ /// purpose: 把 YAML 值递归转成等价 JSON 值
274
+ /// returns: 结构与标量一一对应的 JSON
275
+ /// ---
210
276
  pub(super) fn yaml_value_to_json(value: &Value) -> serde_json::Value {
211
277
  match value {
212
278
  Value::Null => serde_json::Value::Null,
@@ -227,6 +293,11 @@ pub(super) fn yaml_value_to_json(value: &Value) -> serde_json::Value {
227
293
  }
228
294
  }
229
295
 
296
+ /// ---
297
+ /// purpose: 原子写 spec,先写带进程号后缀的临时文件再 rename 覆盖
298
+ /// returns: 成功返回空值
299
+ /// errors: 建父目录、写临时文件或 rename 失败时返回 StatePersist;rename 失败会删掉临时文件且原 spec 不动
300
+ /// ---
230
301
  ///
231
302
  /// Set `runtime.session_name` on the compiled spec to `session_name`, creating the
232
303
  /// `runtime` map and/or the `session_name` entry if absent. Used by quick-start to
@@ -253,10 +324,18 @@ pub(crate) fn write_spec_atomic(spec_path: &Path, spec: &Value) -> Result<(), Li
253
324
  Ok(())
254
325
  }
255
326
 
327
+ /// ---
328
+ /// purpose: 就地把 spec 的 runtime.session_name 改成给定值
329
+ /// ---
256
330
  pub(crate) fn override_spec_session_name(spec: &mut Value, session_name: &str) {
257
331
  override_spec_runtime_str(spec, "session_name", session_name);
258
332
  }
259
333
 
334
+ /// ---
335
+ /// purpose: 就地把 spec 里 team.workspace 与各 agent 的 working_directory 改成给定路径
336
+ /// params:
337
+ /// spec: 非 Map 时不动;只改已存在的字段,不新建
338
+ /// ---
260
339
  pub(crate) fn override_spec_workspace(spec: &mut Value, workspace: &Path) {
261
340
  let workspace_s = workspace.to_string_lossy().to_string();
262
341
  let Value::Map(root) = spec else { return };
@@ -277,10 +356,19 @@ pub(crate) fn override_spec_workspace(spec: &mut Value, workspace: &Path) {
277
356
  }
278
357
  }
279
358
 
359
+ /// ---
360
+ /// purpose: 就地把 spec 的 runtime.display_backend 改成给定值
361
+ /// ---
280
362
  pub(super) fn override_spec_display_backend(spec: &mut Value, display_backend: &str) {
281
363
  override_spec_runtime_str(spec, "display_backend", display_backend);
282
364
  }
283
365
 
366
+ /// ---
367
+ /// purpose: 就地写 spec 的 runtime 段下某个字符串字段
368
+ /// params:
369
+ /// spec: 非 Map 时不动
370
+ /// returns: runtime 段缺失则新建,已存在同名键则覆盖,runtime 是非 Map 值则整段替换
371
+ /// ---
284
372
  pub(super) fn override_spec_runtime_str(spec: &mut Value, key: &str, value: &str) {
285
373
  let Value::Map(root) = spec else { return };
286
374
  let runtime_slot = root
@@ -307,6 +395,10 @@ pub(super) fn override_spec_runtime_str(spec: &mut Value, key: &str, value: &str
307
395
  }
308
396
  }
309
397
 
398
+ /// ---
399
+ /// purpose: 定出本 spec 的 tmux session 名
400
+ /// returns: runtime.session_name 的非空值;缺失时由 team.name 派生,team 名也缺失时用 agent 兜底
401
+ /// ---
310
402
  pub(super) fn spec_session_name(spec: &Value) -> SessionName {
311
403
  if let Some(name) = spec
312
404
  .get("runtime")
@@ -326,6 +418,10 @@ pub(super) fn spec_session_name(spec: &Value) -> SessionName {
326
418
  SessionName::new(format!("team-{team_name}"))
327
419
  }
328
420
 
421
+ /// ---
422
+ /// purpose: 把 spec_session_name 以更宽可见性转出,供 layout 复用同一份实现
423
+ /// returns: 同 spec_session_name
424
+ /// ---
329
425
  ///
330
426
  /// 0.3.28 layout step 1: pub re-export of `spec_session_name` for the new
331
427
  /// `layout::sessions::worker_session_name` to delegate to. Single underlying
@@ -334,6 +430,10 @@ pub fn worker_session_name_pub(spec: &Value) -> SessionName {
334
430
  spec_session_name(spec)
335
431
  }
336
432
 
433
+ /// ---
434
+ /// purpose: 取出 spec 里全部 agent id
435
+ /// returns: 按 spec 顺序的 agent id,无 id 字段的条目被跳过
436
+ /// ---
337
437
  pub(super) fn spec_agents(spec: &Value) -> Vec<AgentId> {
338
438
  spec_agent_values(spec)
339
439
  .into_iter()
@@ -341,6 +441,10 @@ pub(super) fn spec_agents(spec: &Value) -> Vec<AgentId> {
341
441
  .collect()
342
442
  }
343
443
 
444
+ /// ---
445
+ /// purpose: 取出 spec 里全部 agent id 的集合,便于成员判定
446
+ /// returns: 有序集合
447
+ /// ---
344
448
  ///
345
449
  /// Bug 1 (0.4.2): expose spec agent id set so the restart path can filter
346
450
  /// state.agents to only the agents currently defined in the rebuilt spec.
@@ -352,6 +456,10 @@ pub(crate) fn spec_agent_id_set(spec: &Value) -> std::collections::BTreeSet<Stri
352
456
  .collect()
353
457
  }
354
458
 
459
+ /// ---
460
+ /// purpose: 取出 spec 的 agents 列表原始节点
461
+ /// returns: 节点引用列表,字段缺失或类型不对时为空
462
+ /// ---
355
463
  pub(super) fn spec_agent_values(spec: &Value) -> Vec<&Value> {
356
464
  spec.get("agents")
357
465
  .and_then(Value::as_list)
@@ -359,6 +467,10 @@ pub(super) fn spec_agent_values(spec: &Value) -> Vec<&Value> {
359
467
  .unwrap_or_default()
360
468
  }
361
469
 
470
+ /// ---
471
+ /// purpose: 对 spec 里每个 task 算出路由决策
472
+ /// returns: 每个 task 的选中 agent 与理由,manual_override 恒为 false
473
+ /// ---
362
474
  pub(super) fn spec_routes(spec: &Value) -> Vec<RoutingDecision> {
363
475
  spec.get("tasks")
364
476
  .and_then(Value::as_list)
@@ -379,6 +491,10 @@ pub(super) fn spec_routes(spec: &Value) -> Vec<RoutingDecision> {
379
491
  .unwrap_or_default()
380
492
  }
381
493
 
494
+ /// ---
495
+ /// purpose: 定出默认承接人
496
+ /// returns: routing.default_assignee,缺失时取第一个 agent
497
+ /// ---
382
498
  pub(super) fn spec_default_assignee(spec: &Value) -> Option<AgentId> {
383
499
  spec.get("routing")
384
500
  .and_then(|v| v.get("default_assignee"))
@@ -387,66 +503,110 @@ pub(super) fn spec_default_assignee(spec: &Value) -> Option<AgentId> {
387
503
  .or_else(|| spec_agents(spec).into_iter().next())
388
504
  }
389
505
 
390
- pub(crate) fn effective_runtime_config(spec: &Value) -> Result<DangerousApproval, LifecycleError> {
391
- let enabled = spec
392
- .get("runtime")
393
- .and_then(|v| v.get("dangerous_auto_approve"))
394
- .is_some_and(Value::is_truthy);
395
- if enabled {
396
- let leader = detect_dangerous_approval()?;
397
- Ok(DangerousApproval {
398
- enabled: true,
399
- source: DangerousApprovalSource::RuntimeConfig,
506
+ /// ---
507
+ /// purpose: 由单个 YAML agent 的 dangerously_skip_permissions 定出 bypass 审批结论
508
+ /// returns: 字段为 true 时 enabled 且来源记 RuntimeConfig,并带上该 provider 的 bypass argv;否则 Disabled
509
+ /// errors: 当前实现不产生 Err
510
+ /// contract_id: lifecycle.spec_state.effective_runtime_config
511
+ /// ---
512
+ /// 0.5.66 bypass 单源:从**单个 agent**(yaml spec)的 `dangerously_skip_permissions` 构造
513
+ /// `DangerousApproval`(取代旧"runtime config + leader argv"全队一份)。
514
+ /// true → source=RuntimeConfig(角色声明即用户明确同意,coordinator 的
515
+ /// `explicit_yes_confirmed` 自动放行);false → Disabled。
516
+ pub(crate) fn effective_runtime_config_for_worker_spawn(
517
+ agent: &Value,
518
+ provider: Provider,
519
+ ) -> Result<DangerousApproval, LifecycleError> {
520
+ let enabled = matches!(
521
+ agent.get("dangerously_skip_permissions"),
522
+ Some(Value::Bool(true))
523
+ );
524
+ if !enabled {
525
+ return Ok(DangerousApproval {
526
+ enabled: false,
527
+ source: DangerousApprovalSource::Disabled,
400
528
  inherited: false,
401
529
  provider: None,
402
530
  flag: None,
403
- worker_capability_above_leader: !leader.enabled,
404
- ancestry_binary_name: leader.ancestry_binary_name,
531
+ worker_capability_above_leader: false,
532
+ ancestry_binary_name: None,
405
533
  unexpected_binary: false,
406
- })
407
- } else {
408
- Ok(detect_dangerous_approval()?)
534
+ });
409
535
  }
536
+ let flag = crate::provider::bypass_flags::provider_bypass_flag(provider);
537
+ Ok(DangerousApproval {
538
+ enabled: true,
539
+ source: DangerousApprovalSource::RuntimeConfig,
540
+ inherited: false,
541
+ provider: Some(provider_display_str(provider).to_string()),
542
+ flag: flag.map(str::to_string),
543
+ worker_capability_above_leader: false,
544
+ ancestry_binary_name: None,
545
+ unexpected_binary: false,
546
+ })
410
547
  }
411
548
 
412
- pub(crate) fn effective_runtime_config_for_worker_spawn(
549
+ /// ---
550
+ /// purpose: 同上,但读的是 runtime state 里的 JSON agent 节点
551
+ /// returns: 同 YAML 版
552
+ /// errors: 当前实现不产生 Err
553
+ /// contract_id: lifecycle.spec_state.effective_runtime_config
554
+ /// ---
555
+ /// 0.5.66 bypass 单源:restart/state 路径的 serde_json 版(agent 来自 state JSON)。
556
+ pub(crate) fn effective_runtime_config_for_worker_spawn_json(
557
+ agent: &serde_json::Value,
558
+ provider: Provider,
413
559
  ) -> Result<DangerousApproval, LifecycleError> {
414
- detect_dangerous_approval()
560
+ let enabled = agent
561
+ .get("dangerously_skip_permissions")
562
+ .and_then(serde_json::Value::as_bool)
563
+ .unwrap_or(false);
564
+ if !enabled {
565
+ return Ok(DangerousApproval {
566
+ enabled: false,
567
+ source: DangerousApprovalSource::Disabled,
568
+ inherited: false,
569
+ provider: None,
570
+ flag: None,
571
+ worker_capability_above_leader: false,
572
+ ancestry_binary_name: None,
573
+ unexpected_binary: false,
574
+ });
575
+ }
576
+ let flag = crate::provider::bypass_flags::provider_bypass_flag(provider);
577
+ Ok(DangerousApproval {
578
+ enabled: true,
579
+ source: DangerousApprovalSource::RuntimeConfig,
580
+ inherited: false,
581
+ provider: Some(provider_display_str(provider).to_string()),
582
+ flag: flag.map(str::to_string),
583
+ worker_capability_above_leader: false,
584
+ ancestry_binary_name: None,
585
+ unexpected_binary: false,
586
+ })
415
587
  }
416
588
 
417
- pub(super) fn write_launch_permission_audit(
418
- workspace: &Path,
419
- safety: &DangerousApproval,
420
- ) -> Result<(), LifecycleError> {
421
- crate::event_log::EventLog::new(workspace)
422
- .write(
423
- "launch.permissions_resolved",
424
- serde_json::json!({
425
- "dangerous_auto_approve": safety.enabled,
426
- "dangerous_auto_approve_source": safety.source,
427
- "dangerous_auto_approve_inherited": safety.inherited,
428
- "dangerous_auto_approve_provider": safety.provider,
429
- "dangerous_auto_approve_flag": safety.flag,
430
- "worker_capability_above_leader": safety.worker_capability_above_leader,
431
- "ancestry_binary_name": safety.ancestry_binary_name,
432
- }),
433
- )
434
- .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
435
- if safety.unexpected_binary {
436
- crate::event_log::EventLog::new(workspace)
437
- .write(
438
- "dangerous_flag_in_unexpected_binary",
439
- serde_json::json!({
440
- "provider": safety.provider,
441
- "flag": safety.flag,
442
- "ancestry_binary_name": safety.ancestry_binary_name,
443
- }),
444
- )
445
- .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
589
+ /// ---
590
+ /// purpose: 给出 provider 的稳定 wire 名
591
+ /// returns: 与 spec 写法一致的小写下划线名
592
+ /// ---
593
+ pub(crate) fn provider_display_str(provider: Provider) -> &'static str {
594
+ match provider {
595
+ Provider::Claude => "claude",
596
+ Provider::ClaudeCode => "claude_code",
597
+ Provider::Codex => "codex",
598
+ Provider::Copilot => "copilot",
599
+ Provider::GeminiCli => "gemini_cli",
600
+ Provider::Grok => "grok",
601
+ Provider::CursorAgent => "cursor_agent",
602
+ Provider::Fake => "fake",
446
603
  }
447
- Ok(())
448
604
  }
449
605
 
606
+ /// ---
607
+ /// purpose: 由 team 目录推出 workspace 根
608
+ /// returns: 解析成功用解析值,失败时退到 team 目录的父目录,没有父目录则用 team 目录本身
609
+ /// ---
450
610
  pub(super) fn team_workspace(team_dir: &Path) -> PathBuf {
451
611
  crate::model::paths::team_workspace(team_dir).unwrap_or_else(|_| {
452
612
  team_dir
@@ -455,3 +615,39 @@ pub(super) fn team_workspace(team_dir: &Path) -> PathBuf {
455
615
  .unwrap_or_else(|| team_dir.to_path_buf())
456
616
  })
457
617
  }
618
+
619
+ #[cfg(test)]
620
+ mod tests {
621
+ #![allow(clippy::unwrap_used)]
622
+ use super::*;
623
+ use crate::model::enums::Provider;
624
+
625
+ fn yaml_agent(bypass: bool) -> Value {
626
+ crate::model::yaml::loads(&format!(
627
+ "id: a\nrole: r\nprovider: codex\ndangerously_skip_permissions: {bypass}\n"
628
+ ))
629
+ .unwrap()
630
+ }
631
+
632
+ // 0.5.66 bypass 单源 §4.1:true → enabled + source=runtime_config(角色声明即明确同意)。
633
+ #[test]
634
+ fn test_effective_safety_from_true_field() {
635
+ let safety =
636
+ effective_runtime_config_for_worker_spawn(&yaml_agent(true), Provider::Codex).unwrap();
637
+ assert!(safety.enabled);
638
+ assert_eq!(safety.source, DangerousApprovalSource::RuntimeConfig);
639
+ assert_eq!(
640
+ safety.flag.as_deref(),
641
+ Some("--dangerously-bypass-approvals-and-sandbox")
642
+ );
643
+ }
644
+
645
+ // 0.5.66 bypass 单源 §4.1:false → Disabled。
646
+ #[test]
647
+ fn test_effective_safety_from_false_field() {
648
+ let safety =
649
+ effective_runtime_config_for_worker_spawn(&yaml_agent(false), Provider::Codex).unwrap();
650
+ assert!(!safety.enabled);
651
+ assert_eq!(safety.source, DangerousApprovalSource::Disabled);
652
+ }
653
+ }