@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,13 @@
1
+ //! ---
2
+ //! purpose: 冷启、add/clone/fork 入口拼装
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: fork_agent
6
+ //! what: 成员分身入口,grok 走窗口内命令
7
+ //! boundary:
8
+ //! - 不读 provider 会话落盘
9
+ //! maturity: wired
10
+ //! ---
1
11
  //!
2
12
  //! lifecycle::launch —— 冷启 / quick-start / 危险审批探测 + add/fork / plan 起步与推进。
3
13
 
@@ -18,6 +28,16 @@ use super::*;
18
28
 
19
29
  // ── lifecycle::launch —— 冷启 / quick-start / 危险审批探测 ──────────────────
20
30
 
31
+ /// ---
32
+ /// purpose: 冷启全队的默认入口,按 spec 所在目录绑定 per-team tmux socket 后转 launch_with_transport
33
+ /// params:
34
+ /// spec_path: 已编译 spec 的路径,其父目录即 team 目录
35
+ /// dry_run: 只出路由与权限,不 spawn 也不落盘
36
+ /// auto_approve: 当前为 no-op
37
+ /// skip_profile_smoke: 当前为 no-op
38
+ /// returns: LaunchReport
39
+ /// errors: 透传 launch_with_transport 的错误
40
+ /// ---
21
41
  ///
22
42
  /// `launch(spec_path, dry_run, auto_approve, skip_profile_smoke)`(`launch/core.py:29`)。
23
43
  /// 冷启全队:路由 tasks、resolve 权限/危险审批门、session 冲突检查(冲突 →
@@ -42,6 +62,13 @@ pub fn launch(
42
62
  )
43
63
  }
44
64
 
65
+ /// ---
66
+ /// purpose: 由 spec 路径推出 workspace,再转 launch_with_transport_in_workspace
67
+ /// params:
68
+ /// transport: 已绑定 team socket 的 transport
69
+ /// returns: LaunchReport
70
+ /// errors: 透传 launch_with_transport_in_workspace 的错误
71
+ /// ---
45
72
  pub(crate) fn launch_with_transport(
46
73
  spec_path: &Path,
47
74
  dry_run: bool,
@@ -61,6 +88,17 @@ pub(crate) fn launch_with_transport(
61
88
  )
62
89
  }
63
90
 
91
+ /// ---
92
+ /// purpose: 冷启全队的实体实现,编译 spec、检查 session 冲突、按序 spawn、落 state、出报告
93
+ /// params:
94
+ /// workspace: run workspace 根,事件与 state 都落在这里
95
+ /// spec_path: 已编译 spec 的路径
96
+ /// dry_run: 为真时不 spawn 不落盘,started 为空
97
+ /// auto_approve: 未使用
98
+ /// skip_profile_smoke: 未使用
99
+ /// returns: LaunchReport,其中 leader_receiver_attached 恒 false、session_capture_incomplete_agents 恒空
100
+ /// errors: spec 不存在或解析失败返回 Compile,tmux session 已存在返回 SessionConflict,spawn 与落 state 的错误透传
101
+ /// ---
64
102
  pub fn launch_with_transport_in_workspace(
65
103
  workspace: &Path,
66
104
  spec_path: &Path,
@@ -70,8 +108,9 @@ pub fn launch_with_transport_in_workspace(
70
108
  transport: &dyn Transport,
71
109
  ) -> Result<LaunchReport, LifecycleError> {
72
110
  let _ = skip_profile_smoke;
73
- // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
74
- // timer with monotonic `elapsed_ms` for latency triage.
111
+ let _ = auto_approve; // 0.5.66: bypass 单源后无 runtime.dangerous_auto_approve,`--yes` no-op。
112
+ // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
113
+ // timer with monotonic `elapsed_ms` for latency triage.
75
114
  let phase_timer = crate::lifecycle::restart::RestartPhaseTimer::start();
76
115
  if !spec_path.exists() {
77
116
  return Err(LifecycleError::Compile(format!(
@@ -82,14 +121,12 @@ pub fn launch_with_transport_in_workspace(
82
121
  let text = std::fs::read_to_string(spec_path)
83
122
  .map_err(|e| LifecycleError::Compile(format!("{}: {e}", spec_path.display())))?;
84
123
  let spec = yaml::loads(&text).map_err(|e| LifecycleError::Compile(e.to_string()))?;
124
+ // 0.5.66 §3.2 跨 workspace 兼容警示:leader argv 带 bypass flag 但当前团队
125
+ // 所有角色都未声明 dangerously_skip_permissions=true → 写 warning 事件不阻塞。
126
+ // TODO(0.6.0): remove(0.6.0 彻底删源头 A 后此警示无意义)。
127
+ emit_dangerous_flag_no_role_declared_warning(workspace, &spec);
85
128
  phase_timer.emit(workspace, "launch.phase", "compile_spec");
86
129
  let session_name = spec_session_name(&spec);
87
- let safety = effective_runtime_config(&spec)?;
88
- if safety.enabled && !safety.inherited && !auto_approve && !dry_run {
89
- return Err(LifecycleError::DangerousApprovalRequired(
90
- "runtime dangerous_auto_approve is enabled".to_string(),
91
- ));
92
- }
93
130
  if !dry_run && transport_has_session(transport, &session_name) {
94
131
  return Err(LifecycleError::SessionConflict(format!(
95
132
  "tmux session already exists: {}",
@@ -103,20 +140,12 @@ pub fn launch_with_transport_in_workspace(
103
140
  raw: serde_json::json!({"source": "compiled_spec"}),
104
141
  })
105
142
  .collect::<Vec<_>>();
106
- write_launch_permission_audit(workspace, &safety)?;
107
143
  let routes = spec_routes(&spec);
108
144
  let started = if dry_run {
109
145
  Vec::new()
110
146
  } else {
111
147
  phase_timer.emit(workspace, "launch.phase", "spawn_all");
112
- let started = spawn_agents(
113
- workspace,
114
- spec_path,
115
- &spec,
116
- &session_name,
117
- &safety,
118
- transport,
119
- )?;
148
+ let started = spawn_agents(workspace, spec_path, &spec, &session_name, transport)?;
120
149
  persist_spawn_agent_state(
121
150
  workspace,
122
151
  spec_path,
@@ -124,7 +153,6 @@ pub fn launch_with_transport_in_workspace(
124
153
  &session_name,
125
154
  transport,
126
155
  &started,
127
- &safety,
128
156
  )?;
129
157
  // 0.5.38: per-worker timing tags (source="launch") so operators can
130
158
  // trace which worker's spawn dominates wall time. Zeros for now on
@@ -181,16 +209,54 @@ pub fn launch_with_transport_in_workspace(
181
209
  tmux_endpoint: transport.tmux_endpoint(),
182
210
  routes,
183
211
  permissions,
184
- safety,
185
212
  leader_receiver_attached: false,
186
213
  session_capture_incomplete_agents: Vec::new(),
187
214
  })
188
215
  }
189
216
 
217
+ /// 0.5.66 §3.2 跨 workspace 升级警示(仅警示,不做行为决策)。
218
+ /// leader argv 含 bypass flag、但当前 spec 所有 agent 都未声明
219
+ /// `dangerously_skip_permissions=true` → 写 `dangerously_flag_present_but_no_role_declared`。
220
+ ///
221
+ /// TODO(0.6.0): remove(0.6.0 删源头 A 后此警示无意义)。
222
+ fn emit_dangerous_flag_no_role_declared_warning(workspace: &Path, spec: &Value) {
223
+ let argv: Vec<String> = std::env::args().collect();
224
+ emit_dangerous_flag_no_role_declared_warning_with_argv(workspace, spec, &argv);
225
+ }
226
+
227
+ /// 带显式 argv 的可测版。
228
+ fn emit_dangerous_flag_no_role_declared_warning_with_argv(
229
+ workspace: &Path,
230
+ spec: &Value,
231
+ argv: &[String],
232
+ ) {
233
+ if crate::provider::bypass_flags::detect_bypass_flag_in_argv(argv).is_none() {
234
+ return;
235
+ }
236
+ let any_role_declared = spec_agent_values(spec).iter().any(|agent| {
237
+ matches!(
238
+ agent.get("dangerously_skip_permissions"),
239
+ Some(Value::Bool(true))
240
+ )
241
+ });
242
+ if any_role_declared {
243
+ return;
244
+ }
245
+ let _ = crate::event_log::EventLog::new(workspace).write(
246
+ "dangerously_flag_present_but_no_role_declared",
247
+ serde_json::json!({
248
+ "message": "leader process carries a bypass flag but no role in this team declares \
249
+ dangerously_skip_permissions=true; bypass no longer inherits to workers \
250
+ (0.5.66 single-source). Declare the field per role to opt in.",
251
+ }),
252
+ );
253
+ }
254
+
190
255
  mod plan;
191
256
  pub(crate) use plan::{handle_report_result, start_plan};
192
257
 
193
258
  pub mod spawn;
259
+ pub(crate) use spawn::agent_id_spec_source_path;
194
260
  pub(super) use spawn::*;
195
261
 
196
262
  mod layout;
@@ -221,22 +287,43 @@ pub(crate) use leader_context::{
221
287
 
222
288
  mod agent_state;
223
289
  pub(super) use agent_state::*;
224
- pub(crate) use agent_state::{effective_approval_policy, persist_effective_approval_policy};
290
+ pub(crate) use agent_state::{
291
+ effective_approval_policy, persist_effective_approval_policy,
292
+ persist_effective_approval_policy_from_agent,
293
+ };
294
+
295
+ mod grok_per_seat;
296
+ pub(crate) use grok_per_seat::{
297
+ is_per_seat_env_key, non_per_seat_env_in_tables, per_seat_keys_in_toml,
298
+ strip_per_seat_keys_from_toml,
299
+ };
225
300
 
226
301
  mod mcp_config;
302
+ pub use mcp_config::apply_grok_mcp_overlay;
227
303
  pub(super) use mcp_config::*;
228
304
  pub(crate) use mcp_config::{
229
- point_native_mcp_config_at_file, resolve_mcp_config, write_worker_mcp_config,
305
+ ensure_grok_login_and_folder_trust, grok_shared_cwd_error, point_native_mcp_config_at_file,
306
+ reconcile_grok_toml_per_seat_keys, resolve_mcp_config, write_worker_mcp_config,
230
307
  write_worker_mcp_config_for_provider,
231
308
  };
232
309
 
310
+ mod cursor_mcp;
311
+ pub use cursor_mcp::{
312
+ apply_cursor_mcp_overlay, apply_cursor_workspace_physical_path, cursor_mcp_enable_argv,
313
+ enable_cursor_workspace_mcp, physical_workspace_path, refuse_second_cursor_occupant,
314
+ };
315
+
316
+ mod cursor_create_chat;
317
+
233
318
  mod worker_env;
234
319
  pub(super) use worker_env::*;
235
320
  pub(crate) use worker_env::{
236
- apply_copilot_instructions_overlay, apply_mcp_auto_approval_env, apply_profile_launch_env,
321
+ apply_copilot_instructions_overlay, apply_cursor_agent_rules_overlay,
322
+ apply_mcp_auto_approval_env, apply_profile_launch_env, auth_mode_env_value,
237
323
  fill_spawn_placeholders, fill_spawn_placeholders_full, inherited_env_with_team_overrides,
238
324
  persist_command_plan_state, spawn_timestamp,
239
325
  };
326
+ pub use worker_env::{apply_cursor_subscription_proxy_env, CursorProxyPresence};
240
327
 
241
328
  mod identity;
242
329
  pub(super) use identity::*;
@@ -263,17 +350,9 @@ pub(crate) use quick_start_transport::{
263
350
  };
264
351
 
265
352
  pub mod readiness;
266
- pub(crate) use readiness::launched_team_receiver_is_attached;
353
+ pub use readiness::launched_team_receiver_is_attached;
267
354
  pub(super) use readiness::*;
268
355
 
269
- mod approval;
270
- pub use approval::detect_dangerous_approval;
271
- use approval::{
272
- binary_matches_provider, binary_name, dangerous_leader_flags,
273
- detect_dangerous_approval_in_argv, disabled_dangerous_approval, process_ancestry_argv,
274
- process_argv_tokens, process_parent_pid,
275
- };
276
-
277
356
  mod add_agent;
278
357
  pub(super) use add_agent::*;
279
358
  pub use add_agent::{add_agent, add_agent_force};
@@ -284,19 +363,12 @@ pub(crate) use add_agent_state::inject_agent_into_spec;
284
363
  pub(super) use add_agent_state::*;
285
364
 
286
365
  mod fork_agent;
287
- pub use fork_agent::fork_agent_with_transport;
288
366
  pub(super) use fork_agent::*;
367
+ pub use fork_agent::{fork_agent_with_transport, in_window_fork, in_window_fork_command};
289
368
 
290
369
  mod fork_entry;
291
370
  pub use fork_entry::fork_agent;
292
371
 
293
- mod fork_state;
294
- pub(super) use fork_state::*;
295
-
296
- mod fork_finalize;
297
- pub(super) use fork_finalize::*;
298
- pub(crate) use fork_finalize::{finalize_pending_fork_capture, ContextForkFinalized};
299
-
300
372
  mod role_source;
301
373
  pub(super) use role_source::*;
302
374
 
@@ -308,15 +380,109 @@ pub(super) use ownership::*;
308
380
  pub(crate) use ownership::{ensure_owner_allowed, ensure_owner_allowed_for_state, state_path};
309
381
 
310
382
  pub mod spec_state;
383
+ pub use spec_state::worker_session_name_pub;
311
384
  pub(crate) use spec_state::{
312
- effective_runtime_config, effective_runtime_config_for_worker_spawn,
313
- override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
385
+ effective_runtime_config_for_worker_spawn, effective_runtime_config_for_worker_spawn_json,
314
386
  };
315
387
  use spec_state::{
316
388
  env_nonempty, has_positive_caller_leader_env, initial_runtime_state,
317
389
  override_spec_display_backend, override_spec_runtime_str,
318
390
  seed_launched_owner_from_caller_with_provider_lookup, seed_launched_owner_from_env,
319
391
  spec_agent_values, spec_agents, spec_default_assignee, spec_routes, spec_session_name,
320
- spec_tasks_json, team_workspace, write_launch_permission_audit, yaml_value_to_json,
392
+ spec_tasks_json, team_workspace, yaml_value_to_json,
321
393
  };
322
- pub use spec_state::worker_session_name_pub;
394
+ pub(crate) use spec_state::{
395
+ override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
396
+ };
397
+
398
+ #[cfg(test)]
399
+ mod tests {
400
+ #![allow(clippy::unwrap_used)]
401
+ use super::*;
402
+
403
+ // 0.5.66 §4.1 test_leader_flag_present_but_no_role_true_writes_warning_event:
404
+ // leader argv 带 bypass flag + 无角色声明 true → 写 warning 事件。
405
+ #[test]
406
+ fn test_leader_flag_present_but_no_role_true_writes_warning_event() {
407
+ let ws =
408
+ std::env::temp_dir().join(format!("ta-bypass-warning-test-{}", std::process::id()));
409
+ let _ = std::fs::remove_dir_all(&ws);
410
+ std::fs::create_dir_all(&ws).unwrap();
411
+ let spec = yaml::loads(
412
+ "version: 1\nteam: {}\nleader: {}\nagents:\n - id: a\n role: r\n provider: fake\n dangerously_skip_permissions: false\n",
413
+ )
414
+ .unwrap();
415
+ let argv = vec![
416
+ "team-agent".to_string(),
417
+ "--dangerously-skip-permissions".to_string(),
418
+ ];
419
+ emit_dangerous_flag_no_role_declared_warning_with_argv(&ws, &spec, &argv);
420
+ let events = crate::event_log::EventLog::new(&ws).tail(0).unwrap();
421
+ assert!(
422
+ events.iter().any(|event| {
423
+ event.get("event").and_then(serde_json::Value::as_str)
424
+ == Some("dangerously_flag_present_but_no_role_declared")
425
+ }),
426
+ "leader flag + no role declared must write the warning event; events={events:?}"
427
+ );
428
+ let _ = std::fs::remove_dir_all(&ws);
429
+ }
430
+
431
+ // 0.5.66 §4.1 同测试的反向:有角色声明 true → 不写 warning。
432
+ #[test]
433
+ fn test_leader_flag_present_but_role_declared_no_warning() {
434
+ let ws = std::env::temp_dir().join(format!(
435
+ "ta-bypass-warning-none-test-{}",
436
+ std::process::id()
437
+ ));
438
+ let _ = std::fs::remove_dir_all(&ws);
439
+ std::fs::create_dir_all(&ws).unwrap();
440
+ let spec = yaml::loads(
441
+ "version: 1\nteam: {}\nleader: {}\nagents:\n - id: a\n role: r\n provider: fake\n dangerously_skip_permissions: true\n",
442
+ )
443
+ .unwrap();
444
+ let argv = vec![
445
+ "team-agent".to_string(),
446
+ "--dangerously-skip-permissions".to_string(),
447
+ ];
448
+ emit_dangerous_flag_no_role_declared_warning_with_argv(&ws, &spec, &argv);
449
+ let events = crate::event_log::EventLog::new(&ws).tail(0).unwrap();
450
+ assert!(
451
+ !events.iter().any(|event| {
452
+ event.get("event").and_then(serde_json::Value::as_str)
453
+ == Some("dangerously_flag_present_but_no_role_declared")
454
+ }),
455
+ "role declared true must suppress the warning; events={events:?}"
456
+ );
457
+ let _ = std::fs::remove_dir_all(&ws);
458
+ }
459
+
460
+ #[test]
461
+ fn team_agent_force_flag_does_not_look_like_bypass() {
462
+ let ws =
463
+ std::env::temp_dir().join(format!("ta-bypass-force-not-bypass-{}", std::process::id()));
464
+ let _ = std::fs::remove_dir_all(&ws);
465
+ std::fs::create_dir_all(&ws).unwrap();
466
+ let spec = yaml::loads(
467
+ "version: 1\nteam: {}\nleader: {}\nagents:\n - id: a\n role: r\n provider: fake\n dangerously_skip_permissions: false\n",
468
+ )
469
+ .unwrap();
470
+ let argv = vec![
471
+ "team-agent".to_string(),
472
+ "remove-agent".to_string(),
473
+ "x".to_string(),
474
+ "--confirm".to_string(),
475
+ "--force".to_string(),
476
+ ];
477
+ emit_dangerous_flag_no_role_declared_warning_with_argv(&ws, &spec, &argv);
478
+ let events = crate::event_log::EventLog::new(&ws).tail(0).unwrap();
479
+ assert!(
480
+ !events.iter().any(|event| {
481
+ event.get("event").and_then(serde_json::Value::as_str)
482
+ == Some("dangerously_flag_present_but_no_role_declared")
483
+ }),
484
+ "team-agent --force must not emit bypass warning; events={events:?}"
485
+ );
486
+ let _ = std::fs::remove_dir_all(&ws);
487
+ }
488
+ }
@@ -1,3 +1,22 @@
1
+ //! ---
2
+ //! purpose: workspace 级 agent-lifecycle 互斥锁,让同一 workspace 的席位增删起停串行
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: acquire_agent_lifecycle_lock
6
+ //! what: 轮询抢锁,成功返回持有到 Drop 的 guard,超时返回 LifecycleLockTimeout
7
+ //! - name: agent_lifecycle_lock_path
8
+ //! what: 给出该 workspace 的锁文件路径
9
+ //! depends:
10
+ //! - crate::platform::file_lock
11
+ //! - crate::event_log::EventLog
12
+ //! - crate::model::paths
13
+ //! boundary:
14
+ //! - 不锁 pane 输入通道,那是 pane_input_lock
15
+ //! - 不做跨 workspace 互斥,锁作用域就是一个 workspace
16
+ //! - 拿不到锁只超时报错,不强抢也不清理他人锁文件
17
+ //! - 锁文件内容只作观测用,不作为持有权判据
18
+ //! maturity: wired
19
+ //! ---
1
20
  use std::io::{Seek, Write};
2
21
  use std::path::{Path, PathBuf};
3
22
  use std::time::{Duration, Instant};
@@ -17,10 +36,16 @@ pub(crate) struct LifecycleLockRequest<'a> {
17
36
  }
18
37
 
19
38
  pub(crate) struct LifecycleLockGuard {
20
- #[allow(dead_code)]
21
39
  file: std::fs::File,
22
40
  }
23
41
 
42
+ /// ---
43
+ /// purpose: 抢 workspace 级 agent-lifecycle 锁,默认 30 秒超时、5 秒起写 lock_held_long 事件
44
+ /// params:
45
+ /// request: 携带 workspace、操作名与可选 team/agent,仅用于锁元数据与事件
46
+ /// returns: 持有锁的 guard,Drop 时解锁
47
+ /// errors: 建目录或打开锁文件失败返回 StatePersist,等待超时返回 LifecycleLockTimeout
48
+ /// ---
24
49
  pub(crate) fn acquire_agent_lifecycle_lock(
25
50
  request: LifecycleLockRequest<'_>,
26
51
  ) -> Result<LifecycleLockGuard, LifecycleError> {
@@ -35,6 +60,14 @@ pub(crate) fn acquire_agent_lifecycle_lock(
35
60
  )
36
61
  }
37
62
 
63
+ /// ---
64
+ /// purpose: 以显式超时与告警阈值抢同一把锁,仅测试构建可见
65
+ /// params:
66
+ /// timeout: 放弃等待的上限
67
+ /// held_long: 超过它写一次 lock_held_long 事件
68
+ /// returns: 持有锁的 guard
69
+ /// errors: 同 acquire_agent_lifecycle_lock
70
+ /// ---
38
71
  #[cfg(test)]
39
72
  pub(crate) fn acquire_agent_lifecycle_lock_for_test(
40
73
  request: LifecycleLockRequest<'_>,
@@ -55,6 +88,10 @@ pub(crate) struct LifecycleLockDeadlineOverrideGuard {
55
88
  previous: Option<(Duration, Duration)>,
56
89
  }
57
90
 
91
+ /// ---
92
+ /// purpose: 线程内改写默认锁超时与告警阈值,仅测试构建可见
93
+ /// returns: guard,Drop 时恢复上一次的取值
94
+ /// ---
58
95
  #[cfg(test)]
59
96
  pub(crate) fn override_agent_lifecycle_lock_deadlines_for_test(
60
97
  timeout: Duration,
@@ -145,6 +182,10 @@ fn acquire_agent_lifecycle_lock_with_deadlines(
145
182
  }
146
183
  }
147
184
 
185
+ /// ---
186
+ /// purpose: 给出该 workspace 的 agent-lifecycle 锁文件路径
187
+ /// returns: runtime 目录下的 agent-lifecycle.lock
188
+ /// ---
148
189
  pub(crate) fn agent_lifecycle_lock_path(workspace: &Path) -> PathBuf {
149
190
  crate::model::paths::runtime_dir(workspace).join(format!("{AGENT_LIFECYCLE_LOCK_NAME}.lock"))
150
191
  }
@@ -1,3 +1,13 @@
1
+ //! ---
2
+ //! purpose: team 进程级生命周期编排(起停加减 clone/fork)
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: launch
6
+ //! what: 冷启与成员生命周期入口
7
+ //! boundary:
8
+ //! - 不拥有物理注入,只调用 transport
9
+ //! maturity: wired
10
+ //! ---
1
11
  //!
2
12
  //! step 13 · lifecycle — team 进程级生命周期编排器(ROUND-0.5 骨架)。
3
13
  //!
@@ -46,6 +56,7 @@ pub mod display;
46
56
  pub mod helpers;
47
57
  pub mod launch;
48
58
  pub(crate) mod lock;
59
+ pub(crate) mod pane_input_lock;
49
60
  pub(crate) mod profile_launch;
50
61
  pub(crate) mod profile_smoke;
51
62
  pub mod restart;
@@ -0,0 +1,161 @@
1
+ //! ---
2
+ //! purpose: pane 输入通道互斥锁,让同一 pane 上的写入串行化
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: acquire_or_proceed
6
+ //! what: 取到锁则持有到 Drop;超时或锁不可用则告警后放行,不无限等待
7
+ //! depends:
8
+ //! - crate::platform::file_lock
9
+ //! - crate::event_log::EventLog
10
+ //! boundary:
11
+ //! - 不锁席位生命周期(那是 lock.rs)
12
+ //! - 不重粘文本、不加自动兜底重投;重试只重按回车由既有 inject 路径负责
13
+ //! - 不在投递层发明临时持有态
14
+ //! maturity: wired
15
+ //! ---
16
+
17
+ use std::path::{Path, PathBuf};
18
+ use std::time::{Duration, Instant};
19
+
20
+ use crate::event_log::EventLog;
21
+ use crate::model::paths::runtime_dir;
22
+
23
+ /// Hard upper bound for waiting on the pane input lock. Constant, not a magic number in a loop.
24
+ pub(crate) const PANE_INPUT_LOCK_TIMEOUT: Duration = Duration::from_millis(200);
25
+
26
+ pub(crate) const PANE_INPUT_LOCK_NAME: &str = "pane-input";
27
+
28
+ /// Event / stderr token. A5 greps this exact string.
29
+ pub(crate) const PANE_INPUT_LOCK_TIMEOUT_EVENT: &str = "pane_input_lock.timeout";
30
+
31
+ pub(crate) struct PaneInputLockRequest<'a> {
32
+ pub workspace: Option<&'a Path>,
33
+ pub target_key: &'a str,
34
+ pub operation: &'static str,
35
+ }
36
+
37
+ pub(crate) struct PaneInputLockGuard {
38
+ file: Option<std::fs::File>,
39
+ }
40
+
41
+ /// ---
42
+ /// purpose: 取 pane 输入锁;拿不到或超时则告警后放行,绝不无限等待
43
+ /// params:
44
+ /// request: 携带可选 workspace、目标 pane 键与操作名
45
+ /// returns: Some 表示已持锁,None 表示无 workspace 可锁或已超时放行
46
+ /// ---
47
+ /// Acquire the pane lock or proceed after the hard timeout.
48
+ ///
49
+ /// On timeout / lock I/O failure: emit [`PANE_INPUT_LOCK_TIMEOUT_EVENT`] (stderr + events.jsonl)
50
+ /// and return `None` so the caller still writes. Relief messages must not wait forever.
51
+ pub(crate) fn acquire_or_proceed(request: PaneInputLockRequest<'_>) -> Option<PaneInputLockGuard> {
52
+ let timeout = PANE_INPUT_LOCK_TIMEOUT;
53
+ let Some(workspace) = request.workspace else {
54
+ // No workspace (some unit backends): nothing to lock against. Not an alarm.
55
+ return None;
56
+ };
57
+ acquire_file_lock(workspace, &request, timeout)
58
+ }
59
+
60
+ fn acquire_file_lock(
61
+ workspace: &Path,
62
+ request: &PaneInputLockRequest<'_>,
63
+ timeout: Duration,
64
+ ) -> Option<PaneInputLockGuard> {
65
+ let path = pane_input_lock_path(workspace, request.target_key);
66
+ if let Some(parent) = path.parent() {
67
+ if std::fs::create_dir_all(parent).is_err() {
68
+ emit_timeout_alarm(request, timeout, Duration::ZERO, "lock_dir");
69
+ return None;
70
+ }
71
+ }
72
+ let file = match std::fs::OpenOptions::new()
73
+ .create(true)
74
+ .read(true)
75
+ .write(true)
76
+ .truncate(false)
77
+ .open(&path)
78
+ {
79
+ Ok(file) => file,
80
+ Err(_) => {
81
+ emit_timeout_alarm(request, timeout, Duration::ZERO, "lock_open");
82
+ return None;
83
+ }
84
+ };
85
+ let started = Instant::now();
86
+ loop {
87
+ match crate::platform::file_lock::try_lock_once_nonblocking(&file) {
88
+ Ok(true) => {
89
+ return Some(PaneInputLockGuard { file: Some(file) });
90
+ }
91
+ Ok(false) => {}
92
+ Err(_) => {
93
+ emit_timeout_alarm(request, timeout, started.elapsed(), "lock_io");
94
+ return None;
95
+ }
96
+ }
97
+ let elapsed = started.elapsed();
98
+ if elapsed >= timeout {
99
+ emit_timeout_alarm(request, timeout, elapsed, "timeout");
100
+ return None;
101
+ }
102
+ std::thread::sleep(std::cmp::min(
103
+ Duration::from_millis(10),
104
+ timeout.saturating_sub(elapsed),
105
+ ));
106
+ }
107
+ }
108
+
109
+ fn pane_input_lock_path(workspace: &Path, target_key: &str) -> PathBuf {
110
+ let safe: String = target_key
111
+ .chars()
112
+ .map(|c| {
113
+ if c.is_ascii_alphanumeric() {
114
+ c
115
+ } else {
116
+ '_'
117
+ }
118
+ })
119
+ .collect();
120
+ runtime_dir(workspace)
121
+ .join(PANE_INPUT_LOCK_NAME)
122
+ .join(format!("{safe}.lock"))
123
+ }
124
+
125
+ fn emit_timeout_alarm(
126
+ request: &PaneInputLockRequest<'_>,
127
+ timeout: Duration,
128
+ waited: Duration,
129
+ reason: &str,
130
+ ) {
131
+ // A5 watches this exact token on stderr. Commenting this line must turn A5 red.
132
+ eprintln!(
133
+ "{PANE_INPUT_LOCK_TIMEOUT_EVENT} target={} operation={} waited_ms={} timeout_ms={} reason={reason} proceeding",
134
+ request.target_key,
135
+ request.operation,
136
+ waited.as_millis(),
137
+ timeout.as_millis()
138
+ );
139
+ if let Some(workspace) = request.workspace {
140
+ let _ = EventLog::new(workspace).write(
141
+ PANE_INPUT_LOCK_TIMEOUT_EVENT,
142
+ serde_json::json!({
143
+ "lock_name": PANE_INPUT_LOCK_NAME,
144
+ "target": request.target_key,
145
+ "operation": request.operation,
146
+ "waited_ms": waited.as_millis() as u64,
147
+ "timeout_ms": timeout.as_millis() as u64,
148
+ "reason": reason,
149
+ "disposition": "proceed",
150
+ }),
151
+ );
152
+ }
153
+ }
154
+
155
+ impl Drop for PaneInputLockGuard {
156
+ fn drop(&mut self) {
157
+ if let Some(file) = self.file.take() {
158
+ let _ = crate::platform::file_lock::unlock(&file);
159
+ }
160
+ }
161
+ }