@team-agent/installer 0.5.66 → 0.5.69

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 (178) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -0
  4. package/crates/team-agent/src/cli/adapters.rs +5 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +156 -0
  6. package/crates/team-agent/src/cli/emit.rs +6 -5
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +22 -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 +3 -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/missing_subcommands.rs +129 -19
  17. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  18. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  19. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  20. package/crates/team-agent/src/compiler.rs +121 -5
  21. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  22. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  23. package/crates/team-agent/src/coordinator/health.rs +173 -0
  24. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  25. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  26. package/crates/team-agent/src/coordinator/runtime_detectors.rs +31 -0
  27. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  29. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  30. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  31. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  32. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  33. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  34. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  35. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  36. package/crates/team-agent/src/coordinator/types.rs +49 -0
  37. package/crates/team-agent/src/db/message_store.rs +39 -5
  38. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  39. package/crates/team-agent/src/layout/worker_window_helpers.rs +3 -0
  40. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  41. package/crates/team-agent/src/leader/registry.rs +65 -0
  42. package/crates/team-agent/src/leader/start.rs +973 -75
  43. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  44. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  45. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +398 -26
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -0
  48. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  49. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  50. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +548 -0
  51. package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
  52. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -456
  53. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  54. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  55. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  56. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  57. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  58. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +482 -1
  59. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  60. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -0
  61. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  62. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  63. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +87 -3
  64. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  65. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  66. package/crates/team-agent/src/lifecycle/launch/spawn.rs +127 -25
  67. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -0
  68. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  69. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  70. package/crates/team-agent/src/lifecycle/launch.rs +107 -29
  71. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  72. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  73. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  74. package/crates/team-agent/src/lifecycle/profile_launch.rs +99 -0
  75. package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
  76. package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
  77. package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
  78. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  79. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  80. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  81. package/crates/team-agent/src/lifecycle/restart/remove.rs +233 -6
  82. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  83. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  84. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  85. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  86. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
  87. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  88. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  89. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  90. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  91. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +464 -0
  92. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
  93. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  94. package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
  95. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -0
  96. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  97. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  98. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +485 -0
  99. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  100. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  101. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
  102. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  103. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  104. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  105. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  106. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  107. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  108. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
  109. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
  110. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
  111. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
  112. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
  113. package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
  114. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
  115. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -0
  116. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  117. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  118. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  119. package/crates/team-agent/src/lifecycle/tests.rs +30 -13
  120. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  121. package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
  122. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  123. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  124. package/crates/team-agent/src/messaging/delivery.rs +430 -41
  125. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  126. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  127. package/crates/team-agent/src/messaging/results.rs +4 -0
  128. package/crates/team-agent/src/messaging/send.rs +34 -0
  129. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  130. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  131. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  132. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  133. package/crates/team-agent/src/messaging/types.rs +4 -1
  134. package/crates/team-agent/src/model/enums.rs +17 -6
  135. package/crates/team-agent/src/model/permissions.rs +3 -0
  136. package/crates/team-agent/src/os_probe.rs +73 -2
  137. package/crates/team-agent/src/provider/adapter.rs +259 -6
  138. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
  139. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  140. package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
  141. package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
  142. package/crates/team-agent/src/provider/bypass_flags.rs +47 -2
  143. package/crates/team-agent/src/provider/classify.rs +4 -2
  144. package/crates/team-agent/src/provider/faults.rs +2 -1
  145. package/crates/team-agent/src/provider/mod.rs +11 -0
  146. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  147. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  148. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  149. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  150. package/crates/team-agent/src/provider/session/context_fork.rs +78 -3
  151. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  152. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  153. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  154. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  155. package/crates/team-agent/src/provider/session_scan/common.rs +205 -2
  156. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  157. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  158. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  159. package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
  160. package/crates/team-agent/src/provider/session_scan.rs +12 -0
  161. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  162. package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
  163. package/crates/team-agent/src/provider/types.rs +4 -2
  164. package/crates/team-agent/src/provider/wire.rs +29 -1
  165. package/crates/team-agent/src/state/persist.rs +301 -8
  166. package/crates/team-agent/src/state/repository.rs +5 -0
  167. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  168. package/crates/team-agent/src/tmux_backend.rs +1116 -75
  169. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  170. package/crates/team-agent/src/transport.rs +248 -1
  171. package/npm/install.mjs +129 -73
  172. package/package.json +4 -4
  173. package/skills/team-agent/SKILL.md +32 -239
  174. package/skills/team-agent/command-coverage.json +31 -0
  175. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  176. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  177. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  178. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
@@ -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,
@@ -71,8 +109,8 @@ pub fn launch_with_transport_in_workspace(
71
109
  ) -> Result<LaunchReport, LifecycleError> {
72
110
  let _ = skip_profile_smoke;
73
111
  let _ = auto_approve; // 0.5.66: bypass 单源后无 runtime.dangerous_auto_approve,`--yes` 为 no-op。
74
- // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
75
- // timer with monotonic `elapsed_ms` for latency triage.
112
+ // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
113
+ // timer with monotonic `elapsed_ms` for latency triage.
76
114
  let phase_timer = crate::lifecycle::restart::RestartPhaseTimer::start();
77
115
  if !spec_path.exists() {
78
116
  return Err(LifecycleError::Compile(format!(
@@ -107,13 +145,7 @@ pub fn launch_with_transport_in_workspace(
107
145
  Vec::new()
108
146
  } else {
109
147
  phase_timer.emit(workspace, "launch.phase", "spawn_all");
110
- let started = spawn_agents(
111
- workspace,
112
- spec_path,
113
- &spec,
114
- &session_name,
115
- transport,
116
- )?;
148
+ let started = spawn_agents(workspace, spec_path, &spec, &session_name, transport)?;
117
149
  persist_spawn_agent_state(
118
150
  workspace,
119
151
  spec_path,
@@ -224,8 +256,8 @@ mod plan;
224
256
  pub(crate) use plan::{handle_report_result, start_plan};
225
257
 
226
258
  pub mod spawn;
227
- pub(super) use spawn::*;
228
259
  pub(crate) use spawn::agent_id_spec_source_path;
260
+ pub(super) use spawn::*;
229
261
 
230
262
  mod layout;
231
263
  pub(super) use layout::*;
@@ -260,20 +292,46 @@ pub(crate) use agent_state::{
260
292
  persist_effective_approval_policy_from_agent,
261
293
  };
262
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
+ };
300
+
263
301
  mod mcp_config;
302
+ pub use mcp_config::apply_grok_mcp_overlay;
264
303
  pub(super) use mcp_config::*;
265
304
  pub(crate) use mcp_config::{
266
- 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,
267
307
  write_worker_mcp_config_for_provider,
268
308
  };
269
309
 
310
+ pub(crate) mod pi_mcp;
311
+
312
+ mod cursor_mcp_iso;
313
+ pub use cursor_mcp_iso::{
314
+ cursor_mcp_isolation_enabled, cursor_mcp_json_path, cursor_mcp_project_dir,
315
+ materialize_cursor_mcp_project,
316
+ };
317
+ mod cursor_mcp;
318
+ pub use cursor_mcp::{
319
+ apply_cursor_mcp_overlay, apply_cursor_spawn_workspace_pointers,
320
+ apply_cursor_workspace_physical_path, cursor_mcp_enable_argv, enable_cursor_workspace_mcp,
321
+ physical_workspace_path, refuse_second_cursor_occupant,
322
+ };
323
+
324
+ mod cursor_create_chat;
325
+
270
326
  mod worker_env;
271
327
  pub(super) use worker_env::*;
272
328
  pub(crate) use worker_env::{
273
- apply_copilot_instructions_overlay, apply_mcp_auto_approval_env, apply_profile_launch_env,
329
+ apply_copilot_instructions_overlay, apply_cursor_agent_rules_overlay,
330
+ apply_mcp_auto_approval_env, apply_profile_launch_env, auth_mode_env_value,
274
331
  fill_spawn_placeholders, fill_spawn_placeholders_full, inherited_env_with_team_overrides,
275
332
  persist_command_plan_state, spawn_timestamp,
276
333
  };
334
+ pub use worker_env::{apply_cursor_subscription_proxy_env, CursorProxyPresence};
277
335
 
278
336
  mod identity;
279
337
  pub(super) use identity::*;
@@ -300,7 +358,7 @@ pub(crate) use quick_start_transport::{
300
358
  };
301
359
 
302
360
  pub mod readiness;
303
- pub(crate) use readiness::launched_team_receiver_is_attached;
361
+ pub use readiness::launched_team_receiver_is_attached;
304
362
  pub(super) use readiness::*;
305
363
 
306
364
  mod add_agent;
@@ -313,19 +371,12 @@ pub(crate) use add_agent_state::inject_agent_into_spec;
313
371
  pub(super) use add_agent_state::*;
314
372
 
315
373
  mod fork_agent;
316
- pub use fork_agent::fork_agent_with_transport;
317
374
  pub(super) use fork_agent::*;
375
+ pub use fork_agent::{fork_agent_with_transport, in_window_fork, in_window_fork_command};
318
376
 
319
377
  mod fork_entry;
320
378
  pub use fork_entry::fork_agent;
321
379
 
322
- mod fork_state;
323
- pub(super) use fork_state::*;
324
-
325
- mod fork_finalize;
326
- pub(super) use fork_finalize::*;
327
- pub(crate) use fork_finalize::{finalize_pending_fork_capture, ContextForkFinalized};
328
-
329
380
  mod role_source;
330
381
  pub(super) use role_source::*;
331
382
 
@@ -337,9 +388,7 @@ pub(super) use ownership::*;
337
388
  pub(crate) use ownership::{ensure_owner_allowed, ensure_owner_allowed_for_state, state_path};
338
389
 
339
390
  pub mod spec_state;
340
- pub(crate) use spec_state::{
341
- override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
342
- };
391
+ pub use spec_state::worker_session_name_pub;
343
392
  pub(crate) use spec_state::{
344
393
  effective_runtime_config_for_worker_spawn, effective_runtime_config_for_worker_spawn_json,
345
394
  };
@@ -350,7 +399,9 @@ use spec_state::{
350
399
  spec_agent_values, spec_agents, spec_default_assignee, spec_routes, spec_session_name,
351
400
  spec_tasks_json, team_workspace, yaml_value_to_json,
352
401
  };
353
- pub use spec_state::worker_session_name_pub;
402
+ pub(crate) use spec_state::{
403
+ override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
404
+ };
354
405
 
355
406
  #[cfg(test)]
356
407
  mod tests {
@@ -361,10 +412,8 @@ mod tests {
361
412
  // leader argv 带 bypass flag + 无角色声明 true → 写 warning 事件。
362
413
  #[test]
363
414
  fn test_leader_flag_present_but_no_role_true_writes_warning_event() {
364
- let ws = std::env::temp_dir().join(format!(
365
- "ta-bypass-warning-test-{}",
366
- std::process::id()
367
- ));
415
+ let ws =
416
+ std::env::temp_dir().join(format!("ta-bypass-warning-test-{}", std::process::id()));
368
417
  let _ = std::fs::remove_dir_all(&ws);
369
418
  std::fs::create_dir_all(&ws).unwrap();
370
419
  let spec = yaml::loads(
@@ -415,4 +464,33 @@ mod tests {
415
464
  );
416
465
  let _ = std::fs::remove_dir_all(&ws);
417
466
  }
467
+
468
+ #[test]
469
+ fn team_agent_force_flag_does_not_look_like_bypass() {
470
+ let ws =
471
+ std::env::temp_dir().join(format!("ta-bypass-force-not-bypass-{}", std::process::id()));
472
+ let _ = std::fs::remove_dir_all(&ws);
473
+ std::fs::create_dir_all(&ws).unwrap();
474
+ let spec = yaml::loads(
475
+ "version: 1\nteam: {}\nleader: {}\nagents:\n - id: a\n role: r\n provider: fake\n dangerously_skip_permissions: false\n",
476
+ )
477
+ .unwrap();
478
+ let argv = vec![
479
+ "team-agent".to_string(),
480
+ "remove-agent".to_string(),
481
+ "x".to_string(),
482
+ "--confirm".to_string(),
483
+ "--force".to_string(),
484
+ ];
485
+ emit_dangerous_flag_no_role_declared_warning_with_argv(&ws, &spec, &argv);
486
+ let events = crate::event_log::EventLog::new(&ws).tail(0).unwrap();
487
+ assert!(
488
+ !events.iter().any(|event| {
489
+ event.get("event").and_then(serde_json::Value::as_str)
490
+ == Some("dangerously_flag_present_but_no_role_declared")
491
+ }),
492
+ "team-agent --force must not emit bypass warning; events={events:?}"
493
+ );
494
+ let _ = std::fs::remove_dir_all(&ws);
495
+ }
418
496
  }
@@ -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
+ }
@@ -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 | Provider::Pi => {}
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,12 @@ 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 => {}
570
+ Provider::Pi => {}
500
571
  Provider::Fake => {}
501
572
  }
502
573
  unsets
@@ -893,6 +964,12 @@ fn compatible_api_network_exports(
893
964
  .collect()
894
965
  }
895
966
 
967
+ /// ---
968
+ /// purpose: 读出 profile 声明的代理模式
969
+ /// params:
970
+ /// values: 已解析的 profile 键值,先看 PROXY_MODE 再看 NETWORK_MODE
971
+ /// returns: 小写去空白后的模式串,两个键都没有时为 inherit
972
+ /// ---
896
973
  pub(crate) fn profile_proxy_mode(values: &BTreeMap<String, String>) -> String {
897
974
  values
898
975
  .get("PROXY_MODE")
@@ -915,10 +992,18 @@ fn required_profile_keys(provider: Provider, auth_mode: AuthMode) -> &'static [&
915
992
  // C-7-1 cr verdict: copilot 一期 subscription-only,无 BYOK required key 集合;
916
993
  // 二期 BYOK 立项时填(向后兼容字段保留)。
917
994
  Provider::Copilot => &[],
995
+ // 0.5.67: grok/cursor_agent 一期无 BYOK required key 集合 (同 copilot 精神)。
996
+ Provider::Grok | Provider::CursorAgent | Provider::Pi => &[],
918
997
  Provider::Fake => &[],
919
998
  }
920
999
  }
921
1000
 
1001
+ /// ---
1002
+ /// purpose: 取 profile 里某个规范键,取不到时按该键的已知别名回退
1003
+ /// params:
1004
+ /// key: 规范键名,API_KEY BASE_URL MODEL 各有一组别名,其余键不回退
1005
+ /// returns: 命中的非空值
1006
+ /// ---
922
1007
  pub(crate) fn profile_value_or_alternate<'a>(
923
1008
  values: &'a BTreeMap<String, String>,
924
1009
  key: &str,
@@ -949,6 +1034,10 @@ pub(crate) fn profile_value_or_alternate<'a>(
949
1034
  })
950
1035
  }
951
1036
 
1037
+ /// ---
1038
+ /// purpose: 在两个显式给定的键之间取第一个非空值
1039
+ /// returns: 命中的非空值,两个都空则 None
1040
+ /// ---
952
1041
  pub(crate) fn value_or_alternate<'a>(
953
1042
  values: &'a BTreeMap<String, String>,
954
1043
  primary: &str,
@@ -961,6 +1050,10 @@ pub(crate) fn value_or_alternate<'a>(
961
1050
  .map(String::as_str)
962
1051
  }
963
1052
 
1053
+ /// ---
1054
+ /// purpose: 取 profile 里声明的模型名
1055
+ /// returns: MODEL 的非空值,没有时取 ANTHROPIC_MODEL
1056
+ /// ---
964
1057
  pub(crate) fn profile_model(values: &BTreeMap<String, String>) -> Option<&str> {
965
1058
  values
966
1059
  .get("MODEL")
@@ -982,6 +1075,12 @@ fn effective_profile_or_agent_model<'a>(
982
1075
 
983
1076
  pub(crate) use crate::provider::wire::parse_provider;
984
1077
 
1078
+ /// ---
1079
+ /// purpose: 把 auth_mode 字符串解析成枚举
1080
+ /// params:
1081
+ /// raw: 只接受 subscription、official_api、compatible_api
1082
+ /// returns: 对应枚举;未知取值返回 None,由调用方决定兜底
1083
+ /// ---
985
1084
  pub(crate) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
986
1085
  match raw {
987
1086
  "subscription" => Some(AuthMode::Subscription),