@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,4 +1,43 @@
1
+ //! ---
2
+ //! purpose: restart 与 start 共用的底座,含 spawn 执行、transport 解析、state 标记与各类读取判定
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: spawn_agent_window
6
+ //! what: 为一个席位拼命令并在目标 session 里开窗或分格起进程
7
+ //! - name: lifecycle_worker_tmux_backend_for_selected_state
8
+ //! what: 按团队已持久化的 endpoint 解析 tmux 后端,非 tmux 状态直接拒绝
9
+ //! - name: save_restart_projected_state
10
+ //! what: 同步团队投影后经 repository 带写意图落盘
11
+ //! - name: resume_backing_probe_for_agent
12
+ //! what: 探测该席位的 resume 依据是否在盘上,并记下所有查过的路径
13
+ //! - name: converge_missing_provider_sessions
14
+ //! what: 有界等待会话捕获收敛,并逐轮写进度事件
15
+ //! - name: mark_agent_stopped
16
+ //! what: 把席位标记为已停并清掉 window 与 pane_id 字段
17
+ //! depends:
18
+ //! - crate::transport::Transport
19
+ //! - crate::tmux_backend
20
+ //! - crate::transport_factory
21
+ //! - crate::provider
22
+ //! - crate::session_capture
23
+ //! - crate::state::projection
24
+ //! - crate::state::repository
25
+ //! - crate::state::persist
26
+ //! - crate::coordinator::health
27
+ //! - crate::event_log::EventLog
28
+ //! - crate::lifecycle::launch
29
+ //! - crate::lifecycle::profile_launch
30
+ //! - crate::lifecycle::worker_command_context
31
+ //! - crate::lifecycle::restart::selection
32
+ //! boundary:
33
+ //! - 状态是 conpty 时 tmux 专用解析器直接报错,不静默降级到 tmux
34
+ //! - 探测类判定失败一律倒向保守值,不把不确定当成存活
35
+ //! - 清活动观测只删观测字段,不动生命周期与拓扑字段
36
+ //! - 收敛与排空都是有界等待,不无限阻塞
37
+ //! maturity: wired
38
+ //! ---
1
39
  use super::*;
40
+ use crate::transport::Transport;
2
41
 
3
42
  pub(super) struct SpawnedAgentWindow {
4
43
  pub spawn: crate::transport::SpawnResult,
@@ -23,6 +62,12 @@ pub(super) struct SameRoleCohortTarget {
23
62
  }
24
63
 
25
64
  impl SameRoleCohortTarget {
65
+ /// ---
66
+ /// purpose: 构造一个同角色同批目标
67
+ /// params:
68
+ /// window: 该席位的窗口名
69
+ /// returns: 未带期望 pane 的目标
70
+ /// ---
26
71
  pub(super) fn new(agent_id: &AgentId, window: &str) -> Self {
27
72
  Self {
28
73
  agent_id: agent_id.as_str().to_string(),
@@ -31,16 +76,28 @@ impl SameRoleCohortTarget {
31
76
  }
32
77
  }
33
78
 
79
+ /// ---
80
+ /// purpose: 给目标补上期望的旧 pane
81
+ /// returns: 带期望 pane 的目标
82
+ /// ---
34
83
  pub(super) fn with_expected_pane_id(mut self, pane_id: Option<&str>) -> Self {
35
84
  self.expected_pane_id = pane_id.map(ToString::to_string);
36
85
  self
37
86
  }
38
87
  }
39
88
 
89
+ /// ---
90
+ /// purpose: 判断该窗口是不是按席位命名的独立窗口
91
+ /// returns: 窗口名等于席位名且不是规范布局窗口时为 true
92
+ /// ---
40
93
  pub(super) fn is_per_agent_cohort_window(window: &str, agent_id: &AgentId) -> bool {
41
94
  window == agent_id.as_str() && !crate::lifecycle::launch::is_adaptive_layout_window_pub(window)
42
95
  }
43
96
 
97
+ /// ---
98
+ /// purpose: spawn 之前检查同角色同批是否已有残留
99
+ /// returns: 期望基数为 0;不满足时给出可读的拒绝说明,满足则 None
100
+ /// ---
44
101
  pub(super) fn same_role_cohort_pre_spawn_error(
45
102
  transport: &dyn crate::transport::Transport,
46
103
  session_name: &SessionName,
@@ -50,6 +107,10 @@ pub(super) fn same_role_cohort_pre_spawn_error(
50
107
  same_role_cohort_error(transport, session_name, operation, targets, 0, true)
51
108
  }
52
109
 
110
+ /// ---
111
+ /// purpose: spawn 之后检查同角色同批是否恰好只剩一个
112
+ /// returns: 期望基数为 1;不满足时给出可读的拒绝说明,满足则 None
113
+ /// ---
53
114
  pub(super) fn same_role_cohort_exactly_one_error(
54
115
  transport: &dyn crate::transport::Transport,
55
116
  session_name: &SessionName,
@@ -59,6 +120,11 @@ pub(super) fn same_role_cohort_exactly_one_error(
59
120
  same_role_cohort_error(transport, session_name, operation, targets, 1, false)
60
121
  }
61
122
 
123
+ /// ---
124
+ /// purpose: 杀掉这些目标记录的旧 pane
125
+ /// returns: 全部处理完返回空值;目标没有期望 pane 或该 pane 明确不存在时跳过
126
+ /// errors: 杀 pane 失败时返回带席位、窗口与 pane 的错误串
127
+ /// ---
62
128
  pub(super) fn retire_expected_same_role_cohorts(
63
129
  transport: &dyn crate::transport::Transport,
64
130
  operation: &str,
@@ -175,6 +241,17 @@ fn same_role_cohort_error(
175
241
  None
176
242
  }
177
243
 
244
+ /// ---
245
+ /// purpose: 为一个席位拼出命令并在目标 session 里起进程
246
+ /// params:
247
+ /// resume_session_id: 给出且该 provider 支持 resume 时按 resume 起,否则丢弃它全新起
248
+ /// into_existing_session: 目标 session 已存在时开新窗口,否则新建 session
249
+ /// layout_placement: 有布局位置时按布局开窗或分格
250
+ /// spawn_cwd_override: 覆盖工作目录
251
+ /// owner_team_id_override: 显式指定写进 worker 环境的 owner team,缺省时退回席位行与顶层活跃键
252
+ /// returns: spawn 结果、时间戳、命令计划、profile 启动参数、布局位置与实际使用的 owner team
253
+ /// errors: 命令拼装、profile 准备或 transport spawn 失败时返回 LifecycleError
254
+ /// ---
178
255
  #[allow(clippy::too_many_arguments)]
179
256
  pub(super) fn spawn_agent_window(
180
257
  workspace: &Path,
@@ -214,8 +291,7 @@ pub(super) fn spawn_agent_window(
214
291
  safety
215
292
  } else {
216
293
  detected_safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn_json(
217
- agent,
218
- provider,
294
+ agent, provider,
219
295
  )?;
220
296
  &detected_safety
221
297
  };
@@ -265,11 +341,15 @@ pub(super) fn spawn_agent_window(
265
341
  agent_id.as_str(),
266
342
  team_id.as_deref().unwrap_or(""),
267
343
  );
268
- let mcp_config_path = crate::lifecycle::launch::write_worker_mcp_config(
269
- workspace,
270
- agent_id.as_str(),
271
- &mcp_config,
272
- )?;
344
+ let mcp_config_path = if provider == Provider::Pi {
345
+ None
346
+ } else {
347
+ Some(crate::lifecycle::launch::write_worker_mcp_config(
348
+ workspace,
349
+ agent_id.as_str(),
350
+ &mcp_config,
351
+ )?)
352
+ };
273
353
  let profile_launch =
274
354
  crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
275
355
  workspace,
@@ -299,20 +379,75 @@ pub(super) fn spawn_agent_window(
299
379
  agent_id_hint: Some(agent_id.as_str()),
300
380
  effort: restart_effort,
301
381
  };
302
- let mut plan = match resume_session_id {
303
- Some(session_id) => adapter
304
- .build_resume_command_plan(Some(session_id), context)
305
- .map_err(|e| LifecycleError::Provider(e.to_string()))?,
306
- None => adapter
307
- .build_command_plan(context)
308
- .map_err(|e| LifecycleError::Provider(e.to_string()))?,
382
+ let pi_spawn_cwd = (provider == Provider::Pi)
383
+ .then(|| {
384
+ agent
385
+ .get("spawn_cwd")
386
+ .and_then(serde_json::Value::as_str)
387
+ .filter(|value| !value.is_empty())
388
+ .map(PathBuf::from)
389
+ })
390
+ .flatten();
391
+ let spawn_cwd = spawn_cwd_override
392
+ .or(pi_spawn_cwd.as_deref())
393
+ .unwrap_or(workspace);
394
+ let mut plan = if provider == Provider::Pi {
395
+ let model = command_model.ok_or_else(|| {
396
+ LifecycleError::RequirementUnmet(
397
+ "Pi restart requires an explicit qualified model".to_string(),
398
+ )
399
+ })?;
400
+ let effort = restart_effort.ok_or_else(|| {
401
+ LifecycleError::RequirementUnmet(
402
+ "Pi restart requires an explicit thinking effort".to_string(),
403
+ )
404
+ })?;
405
+ let request = crate::lifecycle::launch::pi_mcp::PiMaterializeRequest {
406
+ workspace,
407
+ team_id: team_id.as_deref().unwrap_or(""),
408
+ agent_id: agent_id.as_str(),
409
+ model,
410
+ effort,
411
+ system_prompt: &system_prompt,
412
+ tool_categories: &resolved_tool_refs,
413
+ team_mcp_tools: &["send_message", "report_result"],
414
+ mcp_config: &mcp_config,
415
+ };
416
+ match resume_session_id {
417
+ Some(session_id) => {
418
+ let rollout_path = agent_rollout_path(agent).ok_or_else(|| {
419
+ LifecycleError::RequirementUnmet(
420
+ "Pi resume requires the persisted exact session path".to_string(),
421
+ )
422
+ })?;
423
+ crate::lifecycle::launch::pi_mcp::materialize_pi_resume_plan(
424
+ request,
425
+ session_id,
426
+ rollout_path.as_path(),
427
+ spawn_cwd,
428
+ )
429
+ }
430
+ None => crate::lifecycle::launch::pi_mcp::materialize_pi_plan(request),
431
+ }
432
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?
433
+ } else {
434
+ match resume_session_id {
435
+ Some(session_id) => adapter
436
+ .build_resume_command_plan(Some(session_id), context)
437
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?,
438
+ None => adapter
439
+ .build_command_plan(context)
440
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?,
441
+ }
309
442
  };
310
443
  if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
311
- crate::lifecycle::launch::point_native_mcp_config_at_file(
312
- &mut plan.argv,
313
- provider,
314
- &mcp_config_path,
315
- );
444
+ if let Some(mcp_config_path) = mcp_config_path.as_ref() {
445
+ crate::lifecycle::launch::point_native_mcp_config_at_file(
446
+ &mut plan.argv,
447
+ provider,
448
+ mcp_config_path,
449
+ );
450
+ }
316
451
  }
317
452
  crate::lifecycle::launch::fill_spawn_placeholders_full(
318
453
  &mut plan.argv,
@@ -327,6 +462,7 @@ pub(super) fn spawn_agent_window(
327
462
  workspace,
328
463
  agent_id.as_str(),
329
464
  team_id.as_deref(),
465
+ Some(crate::lifecycle::launch::auth_mode_env_value(auth_mode)),
330
466
  );
331
467
  crate::lifecycle::launch::apply_profile_launch_env(&mut env, &profile_launch);
332
468
  crate::lifecycle::launch::apply_mcp_auto_approval_env(&mut env, safety);
@@ -338,6 +474,39 @@ pub(super) fn spawn_agent_window(
338
474
  &mut env,
339
475
  )?;
340
476
  }
477
+ // 0.5.67 Cursor 方案 1 变体: role 经 workspace rules 文件注入 (不 argv)。
478
+ if provider == crate::provider::Provider::CursorAgent {
479
+ crate::lifecycle::launch::refuse_second_cursor_occupant(
480
+ workspace,
481
+ agent_id.as_str(),
482
+ None,
483
+ )?;
484
+ crate::lifecycle::launch::apply_cursor_agent_rules_overlay(
485
+ workspace,
486
+ agent_id.as_str(),
487
+ &system_prompt,
488
+ )?;
489
+ crate::lifecycle::launch::apply_cursor_mcp_overlay(workspace, &mcp_config)?;
490
+ let project = if crate::lifecycle::launch::cursor_mcp_isolation_enabled() {
491
+ Some(crate::lifecycle::launch::cursor_mcp_project_dir(
492
+ workspace,
493
+ agent_id.as_str(),
494
+ )?)
495
+ } else {
496
+ None
497
+ };
498
+ crate::lifecycle::launch::enable_cursor_workspace_mcp(workspace, project.as_deref())?;
499
+ crate::lifecycle::launch::apply_cursor_spawn_workspace_pointers(
500
+ &mut plan.argv,
501
+ workspace,
502
+ agent_id.as_str(),
503
+ )?;
504
+ crate::lifecycle::launch::apply_cursor_subscription_proxy_env(&mut env);
505
+ }
506
+ if provider == crate::provider::Provider::Grok {
507
+ crate::lifecycle::launch::ensure_grok_login_and_folder_trust(workspace)?;
508
+ crate::lifecycle::launch::apply_grok_mcp_overlay(workspace, &mcp_config)?;
509
+ }
341
510
  // 0.3.28 Step 3: per Python parity, worker spawn cwd is ALWAYS `workspace`.
342
511
  // The persisted-state `agent.spawn_cwd` override is ignored (it was a
343
512
  // Rust-only extension that drifted to `.team/runtime/<team_key>/` after
@@ -349,8 +518,9 @@ pub(super) fn spawn_agent_window(
349
518
  //
350
519
  // NOTE: Step 4 will thread the YAML spec down to here so we can honour
351
520
  // a per-agent YAML `spawn_cwd` field if one is set. Until then, override
352
- // > workspace; state-based override is silently dropped.
353
- let spawn_cwd = spawn_cwd_override.unwrap_or(workspace);
521
+ // > workspace; state-based override is silently dropped for existing
522
+ // providers. Pi is the narrow exception because exact header/path resume
523
+ // must preserve the captured cwd or refuse.
354
524
  // 0.4.x provider effort MVP step 9: scrub CLAUDE_EFFORT for Claude
355
525
  // worker spawn so a parent shell env cannot silently override the
356
526
  // framework's effort decision.
@@ -560,6 +730,10 @@ fn is_structural_startup_prompt_error(error: &str) -> bool {
560
730
  .any(|needle| lower.contains(needle))
561
731
  }
562
732
 
733
+ /// ---
734
+ /// purpose: 用 spec 里的同名 agent 补回 state 席位行缺的命令上下文字段
735
+ /// returns: 合并后的席位行;spec 读不到或找不到该 agent 时原样返回
736
+ /// ---
563
737
  pub(super) fn rehydrate_agent_command_context_from_spec(
564
738
  spec_workspace: &Path,
565
739
  agent_id: &AgentId,
@@ -652,6 +826,13 @@ fn window_present_in_live(
652
826
  false
653
827
  }
654
828
 
829
+ /// ---
830
+ /// purpose: 起该 workspace 的 coordinator
831
+ /// params:
832
+ /// team_key: 传给 coordinator 的团队键
833
+ /// returns: 启动摘要
834
+ /// errors: 启动失败时返回 StatePersist
835
+ /// ---
655
836
  pub(super) fn start_coordinator_for_workspace(
656
837
  workspace: &Path,
657
838
  team_key: Option<&str>,
@@ -662,6 +843,13 @@ pub(super) fn start_coordinator_for_workspace(
662
843
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
663
844
  }
664
845
 
846
+ /// ---
847
+ /// purpose: 按团队持久化的 tmux endpoint 解析出后端,让 restart、add 与 fork 落在同一 socket
848
+ /// params:
849
+ /// team: 目标团队,None 时按唯一性选
850
+ /// returns: 绑定到该 endpoint 的 tmux 后端;冷 workspace 无持久 endpoint 时退到按 workspace 派生
851
+ /// errors: 团队目标歧义或未解析返回 TeamSelect;state 声明后端是 conpty 时也返回 TeamSelect 拒绝,不降级
852
+ /// ---
665
853
  /// State-aware tmux backend resolver. Reads the team's persisted
666
854
  /// `tmux_endpoint` (set at `team-agent launch` time and shared across
667
855
  /// restart/add-agent/fork-agent) and constructs a TmuxBackend on THAT socket,
@@ -736,6 +924,11 @@ pub(crate) fn lifecycle_worker_tmux_backend_for_selected_state(
736
924
  .unwrap_or_else(|| crate::tmux_backend::TmuxBackend::for_workspace(run_workspace)))
737
925
  }
738
926
 
927
+ /// ---
928
+ /// purpose: 与上面同样的团队选择语义,但返回工厂解析出的通用 transport
929
+ /// returns: 已解析的 transport,含后端种类、来源与提示
930
+ /// errors: 团队选择失败或工厂拒绝时返回 TeamSelect,读 state 失败返回 StatePersist
931
+ /// ---
739
932
  /// 0.5.x Phase 1d Batch 1: new generic-typed lifecycle resolver.
740
933
  ///
741
934
  /// Same team-selection semantics as the legacy tmux-typed variant, but
@@ -774,6 +967,11 @@ pub(crate) fn lifecycle_worker_transport_for_selected_state(
774
967
  .map_err(|e| LifecycleError::TeamSelect(e.to_string()))
775
968
  }
776
969
 
970
+ /// ---
971
+ /// purpose: 由已取到的 state 解析 tmux 后端与它的来源
972
+ /// returns: 后端与 endpoint 来源
973
+ /// errors: state 声明后端是 conpty 时返回 TeamSelect
974
+ /// ---
777
975
  pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
778
976
  run_workspace: &Path,
779
977
  state: &serde_json::Value,
@@ -787,6 +985,14 @@ pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
787
985
  )));
788
986
  }
789
987
  }
988
+ if let Some((endpoint, source)) = crate::tmux_backend::owning_tmux_endpoint_from_state(state) {
989
+ let backend = crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint);
990
+ return Ok(crate::tmux_backend::RuntimeTmuxBackendSelection {
991
+ tmux_endpoint_used: backend.tmux_endpoint(),
992
+ backend,
993
+ tmux_endpoint_source: source,
994
+ });
995
+ }
790
996
  Ok(
791
997
  crate::tmux_backend::tmux_backend_for_runtime_state_or_workspace(
792
998
  run_workspace,
@@ -795,14 +1001,29 @@ pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
795
1001
  )
796
1002
  }
797
1003
 
1004
+ /// ---
1005
+ /// purpose: 由已取到的 state 直接给出 tmux 后端
1006
+ /// returns: 绑定到该 state endpoint 的后端,缺失时按 workspace 派生
1007
+ /// ---
798
1008
  pub(super) fn lifecycle_worker_tmux_backend_for_state(
799
1009
  run_workspace: &Path,
800
1010
  state: &serde_json::Value,
801
1011
  ) -> crate::tmux_backend::TmuxBackend {
1012
+ if let Some((endpoint, _source)) = crate::tmux_backend::owning_tmux_endpoint_from_state(state) {
1013
+ return crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint);
1014
+ }
802
1015
  crate::tmux_backend::tmux_backend_for_runtime_state_or_workspace(run_workspace, Some(state))
803
1016
  .backend
804
1017
  }
805
1018
 
1019
+ /// ---
1020
+ /// purpose: 同步团队投影后落盘 restart 结果
1021
+ /// params:
1022
+ /// topology_authority_agent_ids: 本次以内存值为拓扑权威的席位
1023
+ /// returns: 成功返回空值
1024
+ /// errors: 落盘失败时返回 StatePersist
1025
+ /// contract_id: lifecycle.common.save_restart_projected_state
1026
+ /// ---
806
1027
  pub(super) fn save_restart_projected_state(
807
1028
  workspace: &Path,
808
1029
  state: &mut serde_json::Value,
@@ -818,6 +1039,14 @@ pub(super) fn save_restart_projected_state(
818
1039
  )
819
1040
  }
820
1041
 
1042
+ /// ---
1043
+ /// purpose: 同上,并可指定哪些席位跳过会话捕获回填
1044
+ /// params:
1045
+ /// skip_capture_backfill_agent_ids: 跳过回填的席位
1046
+ /// returns: 成功返回空值
1047
+ /// errors: 落盘失败时返回 StatePersist
1048
+ /// contract_id: lifecycle.common.save_restart_projected_state
1049
+ /// ---
821
1050
  pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
822
1051
  workspace: &Path,
823
1052
  state: &mut serde_json::Value,
@@ -826,17 +1055,22 @@ pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
826
1055
  topology_authority_agent_ids: &[&str],
827
1056
  ) -> Result<(), LifecycleError> {
828
1057
  sync_restart_team_projections(state, team_key);
829
- crate::state::repository::StateRepository::new(workspace).save(
830
- crate::state::repository::StateWriteIntent::RestartTeam {
831
- team_key,
832
- topology_authority_agent_ids,
833
- skip_capture_backfill_agent_ids,
834
- },
835
- state,
836
- )
837
- .map_err(|e| LifecycleError::StatePersist(e.to_string()))
1058
+ crate::state::repository::StateRepository::new(workspace)
1059
+ .save(
1060
+ crate::state::repository::StateWriteIntent::RestartTeam {
1061
+ team_key,
1062
+ topology_authority_agent_ids,
1063
+ skip_capture_backfill_agent_ids,
1064
+ },
1065
+ state,
1066
+ )
1067
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))
838
1068
  }
839
1069
 
1070
+ /// ---
1071
+ /// purpose: 定出本次投影使用的团队键
1072
+ /// returns: 显式 team 优先,其次 state 里的活跃键,最后由 state 推算
1073
+ /// ---
840
1074
  pub(super) fn restart_projection_team_key(state: &serde_json::Value, team: Option<&str>) -> String {
841
1075
  team.filter(|key| !key.is_empty())
842
1076
  .map(str::to_string)
@@ -850,6 +1084,12 @@ pub(super) fn restart_projection_team_key(state: &serde_json::Value, team: Optio
850
1084
  .unwrap_or_else(|| crate::state::projection::team_state_key(state))
851
1085
  }
852
1086
 
1087
+ /// ---
1088
+ /// purpose: 把当前顶层状态压实后写回 teams 表
1089
+ /// params:
1090
+ /// state: 就地改写;显式团队键允许覆盖或新建,别名键只在盘上已有且身份不冲突时才写
1091
+ /// returns: teams 表缺失或为空时不动
1092
+ /// ---
853
1093
  pub(super) fn sync_restart_team_projections(state: &mut serde_json::Value, team_key: &str) {
854
1094
  let Some(teams) = state.get("teams").and_then(serde_json::Value::as_object) else {
855
1095
  return;
@@ -930,23 +1170,55 @@ fn json_team_identity_matches(existing: &serde_json::Value, compact: &serde_json
930
1170
  true
931
1171
  }
932
1172
 
1173
+ /// ---
1174
+ /// purpose: 取 state 里的 session 名
1175
+ /// returns: 非空的 session_name,缺失时退到默认名
1176
+ /// ---
933
1177
  pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
934
- state
935
- .get("session_name")
936
- .and_then(|v| v.as_str())
937
- .filter(|s| !s.is_empty())
1178
+ worker_session_name_from_state(state)
938
1179
  .map(SessionName::new)
939
1180
  .unwrap_or_else(|| SessionName::new("team-agent"))
940
1181
  }
941
1182
 
942
- pub(super) fn session_name_present(state: &serde_json::Value) -> bool {
1183
+ fn worker_session_name_from_state(state: &serde_json::Value) -> Option<&str> {
943
1184
  state
944
1185
  .get("session_name")
945
1186
  .and_then(|v| v.as_str())
946
- .map(|s| !s.is_empty())
947
- .unwrap_or(false)
1187
+ .filter(|s| !s.is_empty())
1188
+ .or_else(|| {
1189
+ let team_key = state
1190
+ .get("active_team_key")
1191
+ .and_then(serde_json::Value::as_str)
1192
+ .filter(|team_key| !team_key.is_empty())?;
1193
+ state
1194
+ .get("teams")?
1195
+ .get(team_key)?
1196
+ .pointer("/leader_receiver/session_name")?
1197
+ .as_str()
1198
+ .filter(|session| !session.is_empty())
1199
+ })
1200
+ .or_else(|| {
1201
+ state
1202
+ .pointer("/leader_receiver/session_name")
1203
+ .and_then(serde_json::Value::as_str)
1204
+ .filter(|session| !session.is_empty())
1205
+ })
1206
+ }
1207
+
1208
+ /// ---
1209
+ /// purpose: 判断 state 里是否记了非空 session 名
1210
+ /// returns: 记了则 true
1211
+ /// ---
1212
+ pub(super) fn session_name_present(state: &serde_json::Value) -> bool {
1213
+ worker_session_name_from_state(state).is_some()
948
1214
  }
949
1215
 
1216
+ /// ---
1217
+ /// purpose: 探测 session 是否存活
1218
+ /// params:
1219
+ /// default: 探测本身 panic 时采用的兜底判定
1220
+ /// returns: transport 明确回答时用它;返回错误时判为不存活;探测 panic 时用兜底值
1221
+ /// ---
950
1222
  pub(super) fn session_live_or_default(
951
1223
  transport: &dyn crate::transport::Transport,
952
1224
  session_name: &SessionName,
@@ -961,6 +1233,10 @@ pub(super) fn session_live_or_default(
961
1233
  }
962
1234
  }
963
1235
 
1236
+ /// ---
1237
+ /// purpose: 取席位行的 provider
1238
+ /// returns: 解析成功用它,缺失或不认识时退到 codex
1239
+ /// ---
964
1240
  pub(super) fn agent_provider(agent: &serde_json::Value) -> Provider {
965
1241
  agent
966
1242
  .get("provider")
@@ -969,6 +1245,10 @@ pub(super) fn agent_provider(agent: &serde_json::Value) -> Provider {
969
1245
  .unwrap_or(Provider::Codex)
970
1246
  }
971
1247
 
1248
+ /// ---
1249
+ /// purpose: 取席位行的 auth_mode
1250
+ /// returns: 解析成功用它,缺失或不认识时退到 subscription
1251
+ /// ---
972
1252
  pub(super) fn agent_auth_mode(agent: &serde_json::Value) -> AuthMode {
973
1253
  agent
974
1254
  .get("auth_mode")
@@ -977,6 +1257,10 @@ pub(super) fn agent_auth_mode(agent: &serde_json::Value) -> AuthMode {
977
1257
  .unwrap_or(AuthMode::Subscription)
978
1258
  }
979
1259
 
1260
+ /// ---
1261
+ /// purpose: 取席位行记录的会话 id
1262
+ /// returns: 非空时返回,否则 None
1263
+ /// ---
980
1264
  pub(super) fn agent_session_id(agent: &serde_json::Value) -> Option<SessionId> {
981
1265
  agent
982
1266
  .get("session_id")
@@ -985,6 +1269,10 @@ pub(super) fn agent_session_id(agent: &serde_json::Value) -> Option<SessionId> {
985
1269
  .map(SessionId::new)
986
1270
  }
987
1271
 
1272
+ /// ---
1273
+ /// purpose: 取席位行记录的 rollout 路径
1274
+ /// returns: 非空时返回,否则 None
1275
+ /// ---
988
1276
  pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPath> {
989
1277
  agent
990
1278
  .get("rollout_path")
@@ -993,6 +1281,10 @@ pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPat
993
1281
  .map(RolloutPath::new)
994
1282
  }
995
1283
 
1284
+ /// ---
1285
+ /// purpose: 判断该席位的 resume 依据是否存在
1286
+ /// returns: 探测结果里的存在位
1287
+ /// ---
996
1288
  pub(super) fn resume_backing_exists_for_agent(
997
1289
  workspace: &Path,
998
1290
  agent_id: &AgentId,
@@ -1032,6 +1324,13 @@ pub(crate) struct SessionIdentityProbeResult {
1032
1324
  pub rollout_path: Option<PathBuf>,
1033
1325
  }
1034
1326
 
1327
+ /// ---
1328
+ /// purpose: 探测 rollout 文件里嵌的席位身份是否与本席位一致
1329
+ /// params:
1330
+ /// _provider: 未参与判定
1331
+ /// rollout_path: 没有路径时三项都返回未知
1332
+ /// returns: 一致性判定、读到的嵌入席位名与实际探测路径;读不出嵌入身份时一致性为未知
1333
+ /// ---
1035
1334
  pub(crate) fn session_identity_probe_for_agent(
1036
1335
  agent_id: &AgentId,
1037
1336
  _provider: Provider,
@@ -1056,6 +1355,10 @@ pub(crate) fn session_identity_probe_for_agent(
1056
1355
  }
1057
1356
  }
1058
1357
 
1358
+ /// ---
1359
+ /// purpose: 按 provider 分支探测 resume 依据,并记录所有查过的路径
1360
+ /// returns: 存在位与查过的路径列表;持久化的 rollout 路径即使不存在也记进列表
1361
+ /// ---
1059
1362
  pub(super) fn resume_backing_probe_for_agent(
1060
1363
  workspace: &Path,
1061
1364
  agent_id: &AgentId,
@@ -1107,6 +1410,63 @@ pub(super) fn resume_backing_probe_for_agent(
1107
1410
  }
1108
1411
  copilot_session_store_has_session(session_id.as_str())
1109
1412
  }
1413
+ Provider::Grok => {
1414
+ let spawn_cwd = agent
1415
+ .get("spawn_cwd")
1416
+ .and_then(serde_json::Value::as_str)
1417
+ .map(PathBuf::from)
1418
+ .unwrap_or_else(|| workspace.to_path_buf());
1419
+ match crate::provider::session_scan::grok::grok_session_dir(
1420
+ &spawn_cwd,
1421
+ session_id.as_str(),
1422
+ ) {
1423
+ Some(dir) => {
1424
+ checked_paths.push(dir.clone());
1425
+ crate::provider::session_scan::grok::grok_session_archive_present(&dir)
1426
+ }
1427
+ None => false,
1428
+ }
1429
+ }
1430
+ Provider::CursorAgent => {
1431
+ let spawn_cwd = agent
1432
+ .get("spawn_cwd")
1433
+ .and_then(serde_json::Value::as_str)
1434
+ .map(PathBuf::from)
1435
+ .unwrap_or_else(|| workspace.to_path_buf());
1436
+ let rollout_ok = rollout_path.is_some_and(|path| {
1437
+ crate::provider::session_scan::cursor::cursor_session_archive_present(path.as_path())
1438
+ });
1439
+ let discovered = crate::provider::session_scan::cursor::cursor_session_dir_for_cwd(
1440
+ session_id.as_str(),
1441
+ &spawn_cwd,
1442
+ );
1443
+ if let Some(dir) = discovered.as_ref() {
1444
+ checked_paths.push(dir.clone());
1445
+ }
1446
+ if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
1447
+ checked_paths.push(home.join(".cursor").join("chats"));
1448
+ }
1449
+ rollout_ok
1450
+ || discovered.as_ref().is_some_and(|dir| {
1451
+ crate::provider::session_scan::cursor::cursor_session_archive_present(dir)
1452
+ })
1453
+ }
1454
+ Provider::Pi => {
1455
+ let spawn_cwd = agent
1456
+ .get("spawn_cwd")
1457
+ .and_then(serde_json::Value::as_str)
1458
+ .filter(|value| !value.is_empty())
1459
+ .map(PathBuf::from)
1460
+ .unwrap_or_else(|| workspace.to_path_buf());
1461
+ rollout_path.is_some_and(|path| {
1462
+ crate::provider::session_scan::pi::validate_exact_backing(
1463
+ path.as_path(),
1464
+ session_id,
1465
+ &spawn_cwd,
1466
+ )
1467
+ .is_ok()
1468
+ })
1469
+ }
1110
1470
  Provider::GeminiCli | Provider::Fake => false,
1111
1471
  };
1112
1472
 
@@ -1121,10 +1481,20 @@ pub(super) fn resume_backing_probe_for_agent(
1121
1481
  }
1122
1482
  }
1123
1483
 
1484
+ /// ---
1485
+ /// purpose: 判断该 provider 是否支持 resume
1486
+ /// returns: 由 provider 适配器的能力位给出
1487
+ /// contract_id: lifecycle.common.provider_supports_resume
1488
+ /// ---
1124
1489
  pub(super) fn provider_supports_resume(provider: Provider) -> bool {
1125
1490
  crate::provider::get_adapter(provider).caps().resume
1126
1491
  }
1127
1492
 
1493
+ /// ---
1494
+ /// purpose: 由 provider wire 名判断是否支持 resume
1495
+ /// returns: 名字不认识时为 false
1496
+ /// contract_id: lifecycle.common.provider_supports_resume
1497
+ /// ---
1128
1498
  pub(super) fn provider_wire_supports_resume(provider: &str) -> bool {
1129
1499
  parse_provider(provider)
1130
1500
  .map(provider_supports_resume)
@@ -1300,6 +1670,13 @@ fn copilot_session_store_has_session(session_id: &str) -> bool {
1300
1670
  .is_ok()
1301
1671
  }
1302
1672
 
1673
+ /// ---
1674
+ /// purpose: 对缺会话的席位做一次捕获尝试
1675
+ /// params:
1676
+ /// state: 就地改写
1677
+ /// returns: 本次是否改动了 state
1678
+ /// errors: 捕获过程出错时返回 Provider
1679
+ /// ---
1303
1680
  pub(crate) fn refresh_missing_provider_sessions(
1304
1681
  state: &mut serde_json::Value,
1305
1682
  ) -> Result<bool, LifecycleError> {
@@ -1313,6 +1690,15 @@ pub(crate) fn refresh_missing_provider_sessions(
1313
1690
  .map_err(|e| LifecycleError::Provider(e.to_string()))
1314
1691
  }
1315
1692
 
1693
+ /// ---
1694
+ /// purpose: 有界等待缺会话席位收敛,并逐轮写进度事件
1695
+ /// params:
1696
+ /// deadline: 等待上限
1697
+ /// poll_interval: 轮询间隔
1698
+ /// allow_fresh: 只写进事件载荷,不改变收敛判定
1699
+ /// returns: 收敛结论
1700
+ /// errors: 收敛过程出错时返回 StatePersist
1701
+ /// ---
1316
1702
  pub(crate) fn converge_missing_provider_sessions(
1317
1703
  state: &mut serde_json::Value,
1318
1704
  deadline: std::time::Duration,
@@ -1358,6 +1744,10 @@ fn write_session_convergence_progress_event(
1358
1744
  .map_err(|e| e.to_string())
1359
1745
  }
1360
1746
 
1747
+ /// ---
1748
+ /// purpose: 列出必须等到会话收敛才能 restart 的席位
1749
+ /// returns: 排序后的席位 id;只保留无会话、状态为 running 且确有待保留上下文的席位,从未捕获过的席位不进入
1750
+ /// ---
1361
1751
  pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Value) -> Vec<String> {
1362
1752
  let mut missing = crate::session_capture::incomplete_resumable_agent_ids(state)
1363
1753
  .into_iter()
@@ -1397,6 +1787,10 @@ pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Val
1397
1787
  missing.sort();
1398
1788
  missing
1399
1789
  }
1790
+ /// ---
1791
+ /// purpose: 取该席位的窗口名
1792
+ /// returns: 席位行里的非空 window,缺失时用席位 id
1793
+ /// ---
1400
1794
  pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> String {
1401
1795
  agent
1402
1796
  .get("window")
@@ -1408,6 +1802,10 @@ pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> Str
1408
1802
 
1409
1803
  pub(super) use crate::provider::wire::{parse_provider, provider_wire};
1410
1804
 
1805
+ /// ---
1806
+ /// purpose: 把 auth_mode 字符串解析成枚举
1807
+ /// returns: 只认三种取值,未知返回 None
1808
+ /// ---
1411
1809
  pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
1412
1810
  match raw {
1413
1811
  "subscription" => Some(AuthMode::Subscription),
@@ -1417,6 +1815,11 @@ pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
1417
1815
  }
1418
1816
  }
1419
1817
 
1818
+ /// ---
1819
+ /// purpose: 从给定目录读出 team spec
1820
+ /// returns: 解析后的 YAML
1821
+ /// errors: 文件不存在返回 TeamSelect,读文件或解析失败返回 Compile
1822
+ /// ---
1420
1823
  pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleError> {
1421
1824
  let spec_path = workspace.join("team.spec.yaml");
1422
1825
  if !spec_path.exists() {
@@ -1430,6 +1833,10 @@ pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleErr
1430
1833
  yaml::loads(&text).map_err(|e| LifecycleError::Compile(e.to_string()))
1431
1834
  }
1432
1835
 
1836
+ /// ---
1837
+ /// purpose: 在 spec 的 agents 列表里找该席位
1838
+ /// returns: 命中的节点;该 id 其实是 leader 时返回 None
1839
+ /// ---
1433
1840
  pub(super) fn find_spec_agent<'a>(
1434
1841
  spec: &'a YamlValue,
1435
1842
  agent_id: &AgentId,
@@ -1452,10 +1859,18 @@ pub(super) fn find_spec_agent<'a>(
1452
1859
  })
1453
1860
  }
1454
1861
 
1862
+ /// ---
1863
+ /// purpose: 构造未知席位的错误
1864
+ /// returns: 带席位 id 的 RequirementUnmet
1865
+ /// ---
1455
1866
  pub(super) fn unknown_worker(agent_id: &AgentId) -> LifecycleError {
1456
1867
  LifecycleError::RequirementUnmet(format!("unknown worker agent id: {agent_id}"))
1457
1868
  }
1458
1869
 
1870
+ /// ---
1871
+ /// purpose: 定出 session 名,state 缺失时回落到 spec
1872
+ /// returns: 依次取 state 的 session_name、spec 的 runtime.session_name、由 team 名派生,最后用默认名
1873
+ /// ---
1459
1874
  pub(super) fn state_session_name_from_spec(
1460
1875
  state: &serde_json::Value,
1461
1876
  spec: &YamlValue,
@@ -1480,6 +1895,14 @@ pub(super) fn state_session_name_from_spec(
1480
1895
  .unwrap_or_else(|| SessionName::new("team-agent"))
1481
1896
  }
1482
1897
 
1898
+ /// ---
1899
+ /// purpose: 把席位标记为已停并清掉 window 与 pane_id 字段(pane_pid 等其余字段保留)
1900
+ /// params:
1901
+ /// state: 就地改写,非对象时先重置成空对象
1902
+ /// spec_agent: 提供 provider 等最小投影
1903
+ /// returns: 成功返回空值
1904
+ /// errors: state 结构不是对象时返回 StatePersist
1905
+ /// ---
1483
1906
  pub(super) fn mark_agent_stopped(
1484
1907
  state: &mut serde_json::Value,
1485
1908
  agent_id: &AgentId,
@@ -1529,6 +1952,13 @@ pub(super) fn mark_agent_stopped(
1529
1952
  Ok(())
1530
1953
  }
1531
1954
 
1955
+ /// ---
1956
+ /// purpose: 窗口本就存在而未新起进程时,把席位标记为运行中
1957
+ /// params:
1958
+ /// pane: 探到的现有 pane,用于写 pane id 与进程号
1959
+ /// returns: 成功返回空值
1960
+ /// errors: state 结构不是对象时返回 StatePersist
1961
+ /// ---
1532
1962
  pub(super) fn mark_agent_running_noop(
1533
1963
  state: &mut serde_json::Value,
1534
1964
  agent_id: &AgentId,
@@ -1587,6 +2017,11 @@ pub(super) fn mark_agent_running_noop(
1587
2017
  Ok(())
1588
2018
  }
1589
2019
 
2020
+ /// ---
2021
+ /// purpose: 写一条起席无操作事件
2022
+ /// returns: 成功返回空值
2023
+ /// errors: 事件写入失败时返回 StatePersist
2024
+ /// ---
1590
2025
  pub(super) fn write_start_agent_noop_event(
1591
2026
  workspace: &Path,
1592
2027
  agent_id: &AgentId,
@@ -1606,6 +2041,10 @@ pub(super) fn write_start_agent_noop_event(
1606
2041
  Ok(())
1607
2042
  }
1608
2043
 
2044
+ /// ---
2045
+ /// purpose: 判断某 session 里是否存在该窗口
2046
+ /// returns: 明确列出该窗口才为 true;列窗口出错或 panic 时为 false
2047
+ /// ---
1609
2048
  pub(super) fn window_exists(
1610
2049
  transport: &dyn crate::transport::Transport,
1611
2050
  session_name: &SessionName,
@@ -1619,6 +2058,11 @@ pub(super) fn window_exists(
1619
2058
  }
1620
2059
  }
1621
2060
 
2061
+ /// ---
2062
+ /// purpose: 在 state 里把该席位的显示标记为已关
2063
+ /// params:
2064
+ /// state: 就地改写;只有 ghostty 工作区后端会被改写状态与标题,其余后端不动
2065
+ /// ---
1622
2066
  pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &AgentId) {
1623
2067
  let Some(display) = state
1624
2068
  .get_mut("agents")
@@ -1646,6 +2090,13 @@ pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &Agen
1646
2090
  }
1647
2091
  }
1648
2092
 
2093
+ /// ---
2094
+ /// purpose: 丢弃该席位的会话捕获字段并标记为已停
2095
+ /// params:
2096
+ /// state: 就地改写;只删会话捕获相关字段,工作目录等状态字段保留
2097
+ /// returns: 成功返回空值
2098
+ /// errors: 席位不存在返回 RequirementUnmet,席位行不是对象返回 StatePersist
2099
+ /// ---
1649
2100
  pub(super) fn discard_agent_session_fields(
1650
2101
  state: &mut serde_json::Value,
1651
2102
  agent_id: &AgentId,
@@ -1693,6 +2144,10 @@ pub(super) fn discard_agent_session_fields(
1693
2144
  Ok(())
1694
2145
  }
1695
2146
 
2147
+ /// ---
2148
+ /// purpose: 判断该席位是否在运行
2149
+ /// returns: 状态为 running 或 busy 直接为真;其余状态都退到按 session 与窗口存在性判定
2150
+ /// ---
1696
2151
  pub(super) fn agent_is_running(
1697
2152
  state: &serde_json::Value,
1698
2153
  agent_id: &AgentId,
@@ -1724,6 +2179,10 @@ pub(super) fn agent_is_running(
1724
2179
  window_exists(transport, &session_name, window)
1725
2180
  }
1726
2181
 
2182
+ /// ---
2183
+ /// purpose: 判断该席位是不是动态生成的
2184
+ /// returns: state 里记了动态角色文件,或 spec 里标了来源席位时为 true
2185
+ /// ---
1727
2186
  pub(super) fn is_dynamic_agent(
1728
2187
  state: &serde_json::Value,
1729
2188
  spec_agent: &YamlValue,
@@ -1742,6 +2201,13 @@ pub(super) fn is_dynamic_agent(
1742
2201
  .is_some_and(|s| !s.is_empty())
1743
2202
  }
1744
2203
 
2204
+ /// ---
2205
+ /// purpose: 在 spawn 之前预判 tmux 的起法,供审计事件记录
2206
+ /// params:
2207
+ /// layout_placement: 有布局位置时按是否起新窗口区分
2208
+ /// into_existing_session: 目标 session 已存在
2209
+ /// returns: new-session、new-window 或 split-window
2210
+ /// ---
1745
2211
  /// 0.4.6 Stage 2: predict the tmux start mode BEFORE the spawn call, so
1746
2212
  /// the `provider.worker.spawn_argv` event can record what the spawn will
1747
2213
  /// actually do. Same logic as `tmux_start_mode_for_spawn` in agent.rs
@@ -1768,6 +2234,10 @@ pub(super) fn predict_tmux_start_mode(
1768
2234
  }
1769
2235
  }
1770
2236
 
2237
+ /// ---
2238
+ /// purpose: 从盘上读该席位当前的 spawn 世代号
2239
+ /// returns: 读到的值;读不出 state 或字段缺失时为 0
2240
+ /// ---
1771
2241
  /// 0.4.6 Stage 2: read state.agents[agent_id].spawn_epoch from disk to
1772
2242
  /// stamp the spawn_argv event with the current cohort identifier. Returns
1773
2243
  /// 0 if the agent row / field is missing.
@@ -1783,6 +2253,12 @@ pub(super) fn state_spawn_epoch_for_agent(workspace: &Path, agent_id: &AgentId)
1783
2253
  .unwrap_or(0)
1784
2254
  }
1785
2255
 
2256
+ /// ---
2257
+ /// purpose: 新起进程后清掉该席位的活动观测字段
2258
+ /// params:
2259
+ /// agent: 就地删除观测字段;生命周期与拓扑字段一概不动
2260
+ /// returns: 清完之后观测缺失表示未知,不等于空闲
2261
+ /// ---
1786
2262
  /// 0.5.32 (`.team/artifacts/restart-resumed-stale-activity-locate.md` §5):
1787
2263
  /// clear the per-agent turn/activity observation set on a successful new
1788
2264
  /// worker process cohort. Called from `mark_agent_started` /