@team-agent/installer 0.5.66 → 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 (164) 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 +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/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 +59 -0
  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 +30 -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 +2 -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 +920 -63
  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 +332 -12
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -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 +332 -0
  51. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
  52. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  53. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  54. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  55. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  56. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  57. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  58. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  59. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  60. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  61. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  62. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  63. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  64. package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
  65. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
  66. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  67. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  68. package/crates/team-agent/src/lifecycle/launch.rs +99 -29
  69. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  70. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  71. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  72. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  73. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  74. package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
  75. package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
  76. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  77. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  78. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  79. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  80. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  81. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  82. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  83. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  84. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  85. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  86. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  88. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  89. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  90. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  91. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  92. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  93. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  94. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  95. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  96. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  97. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  98. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  99. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  100. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  103. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
  104. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  105. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  106. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  107. package/crates/team-agent/src/lifecycle/tests.rs +23 -13
  108. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  109. package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
  110. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  111. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  112. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  113. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  114. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  115. package/crates/team-agent/src/messaging/results.rs +4 -0
  116. package/crates/team-agent/src/messaging/send.rs +34 -0
  117. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  118. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  119. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  121. package/crates/team-agent/src/messaging/types.rs +4 -1
  122. package/crates/team-agent/src/model/enums.rs +14 -5
  123. package/crates/team-agent/src/model/permissions.rs +3 -0
  124. package/crates/team-agent/src/os_probe.rs +73 -2
  125. package/crates/team-agent/src/provider/adapter.rs +231 -4
  126. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  127. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  128. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  129. package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
  130. package/crates/team-agent/src/provider/classify.rs +4 -2
  131. package/crates/team-agent/src/provider/faults.rs +2 -1
  132. package/crates/team-agent/src/provider/mod.rs +11 -0
  133. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  134. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  135. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  136. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  137. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  138. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  139. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  140. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  141. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  142. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  143. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  144. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  145. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  146. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  147. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  148. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  149. package/crates/team-agent/src/provider/types.rs +4 -2
  150. package/crates/team-agent/src/provider/wire.rs +23 -1
  151. package/crates/team-agent/src/state/persist.rs +301 -8
  152. package/crates/team-agent/src/state/repository.rs +5 -0
  153. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  154. package/crates/team-agent/src/tmux_backend.rs +1064 -75
  155. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  156. package/crates/team-agent/src/transport.rs +248 -1
  157. package/npm/install.mjs +129 -73
  158. package/package.json +4 -4
  159. package/skills/team-agent/SKILL.md +33 -238
  160. package/skills/team-agent/command-coverage.json +31 -0
  161. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  162. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  163. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  164. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
@@ -1,3 +1,41 @@
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::*;
2
40
 
3
41
  pub(super) struct SpawnedAgentWindow {
@@ -23,6 +61,12 @@ pub(super) struct SameRoleCohortTarget {
23
61
  }
24
62
 
25
63
  impl SameRoleCohortTarget {
64
+ /// ---
65
+ /// purpose: 构造一个同角色同批目标
66
+ /// params:
67
+ /// window: 该席位的窗口名
68
+ /// returns: 未带期望 pane 的目标
69
+ /// ---
26
70
  pub(super) fn new(agent_id: &AgentId, window: &str) -> Self {
27
71
  Self {
28
72
  agent_id: agent_id.as_str().to_string(),
@@ -31,16 +75,28 @@ impl SameRoleCohortTarget {
31
75
  }
32
76
  }
33
77
 
78
+ /// ---
79
+ /// purpose: 给目标补上期望的旧 pane
80
+ /// returns: 带期望 pane 的目标
81
+ /// ---
34
82
  pub(super) fn with_expected_pane_id(mut self, pane_id: Option<&str>) -> Self {
35
83
  self.expected_pane_id = pane_id.map(ToString::to_string);
36
84
  self
37
85
  }
38
86
  }
39
87
 
88
+ /// ---
89
+ /// purpose: 判断该窗口是不是按席位命名的独立窗口
90
+ /// returns: 窗口名等于席位名且不是规范布局窗口时为 true
91
+ /// ---
40
92
  pub(super) fn is_per_agent_cohort_window(window: &str, agent_id: &AgentId) -> bool {
41
93
  window == agent_id.as_str() && !crate::lifecycle::launch::is_adaptive_layout_window_pub(window)
42
94
  }
43
95
 
96
+ /// ---
97
+ /// purpose: spawn 之前检查同角色同批是否已有残留
98
+ /// returns: 期望基数为 0;不满足时给出可读的拒绝说明,满足则 None
99
+ /// ---
44
100
  pub(super) fn same_role_cohort_pre_spawn_error(
45
101
  transport: &dyn crate::transport::Transport,
46
102
  session_name: &SessionName,
@@ -50,6 +106,10 @@ pub(super) fn same_role_cohort_pre_spawn_error(
50
106
  same_role_cohort_error(transport, session_name, operation, targets, 0, true)
51
107
  }
52
108
 
109
+ /// ---
110
+ /// purpose: spawn 之后检查同角色同批是否恰好只剩一个
111
+ /// returns: 期望基数为 1;不满足时给出可读的拒绝说明,满足则 None
112
+ /// ---
53
113
  pub(super) fn same_role_cohort_exactly_one_error(
54
114
  transport: &dyn crate::transport::Transport,
55
115
  session_name: &SessionName,
@@ -59,6 +119,11 @@ pub(super) fn same_role_cohort_exactly_one_error(
59
119
  same_role_cohort_error(transport, session_name, operation, targets, 1, false)
60
120
  }
61
121
 
122
+ /// ---
123
+ /// purpose: 杀掉这些目标记录的旧 pane
124
+ /// returns: 全部处理完返回空值;目标没有期望 pane 或该 pane 明确不存在时跳过
125
+ /// errors: 杀 pane 失败时返回带席位、窗口与 pane 的错误串
126
+ /// ---
62
127
  pub(super) fn retire_expected_same_role_cohorts(
63
128
  transport: &dyn crate::transport::Transport,
64
129
  operation: &str,
@@ -175,6 +240,17 @@ fn same_role_cohort_error(
175
240
  None
176
241
  }
177
242
 
243
+ /// ---
244
+ /// purpose: 为一个席位拼出命令并在目标 session 里起进程
245
+ /// params:
246
+ /// resume_session_id: 给出且该 provider 支持 resume 时按 resume 起,否则丢弃它全新起
247
+ /// into_existing_session: 目标 session 已存在时开新窗口,否则新建 session
248
+ /// layout_placement: 有布局位置时按布局开窗或分格
249
+ /// spawn_cwd_override: 覆盖工作目录
250
+ /// owner_team_id_override: 显式指定写进 worker 环境的 owner team,缺省时退回席位行与顶层活跃键
251
+ /// returns: spawn 结果、时间戳、命令计划、profile 启动参数、布局位置与实际使用的 owner team
252
+ /// errors: 命令拼装、profile 准备或 transport spawn 失败时返回 LifecycleError
253
+ /// ---
178
254
  #[allow(clippy::too_many_arguments)]
179
255
  pub(super) fn spawn_agent_window(
180
256
  workspace: &Path,
@@ -214,8 +290,7 @@ pub(super) fn spawn_agent_window(
214
290
  safety
215
291
  } else {
216
292
  detected_safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn_json(
217
- agent,
218
- provider,
293
+ agent, provider,
219
294
  )?;
220
295
  &detected_safety
221
296
  };
@@ -327,6 +402,7 @@ pub(super) fn spawn_agent_window(
327
402
  workspace,
328
403
  agent_id.as_str(),
329
404
  team_id.as_deref(),
405
+ Some(crate::lifecycle::launch::auth_mode_env_value(auth_mode)),
330
406
  );
331
407
  crate::lifecycle::launch::apply_profile_launch_env(&mut env, &profile_launch);
332
408
  crate::lifecycle::launch::apply_mcp_auto_approval_env(&mut env, safety);
@@ -338,6 +414,27 @@ pub(super) fn spawn_agent_window(
338
414
  &mut env,
339
415
  )?;
340
416
  }
417
+ // 0.5.67 Cursor 方案 1 变体: role 经 workspace rules 文件注入 (不 argv)。
418
+ if provider == crate::provider::Provider::CursorAgent {
419
+ crate::lifecycle::launch::refuse_second_cursor_occupant(
420
+ workspace,
421
+ agent_id.as_str(),
422
+ None,
423
+ )?;
424
+ crate::lifecycle::launch::apply_cursor_agent_rules_overlay(
425
+ workspace,
426
+ agent_id.as_str(),
427
+ &system_prompt,
428
+ )?;
429
+ crate::lifecycle::launch::apply_cursor_mcp_overlay(workspace, &mcp_config)?;
430
+ crate::lifecycle::launch::enable_cursor_workspace_mcp(workspace)?;
431
+ crate::lifecycle::launch::apply_cursor_workspace_physical_path(&mut plan.argv, workspace);
432
+ crate::lifecycle::launch::apply_cursor_subscription_proxy_env(&mut env);
433
+ }
434
+ if provider == crate::provider::Provider::Grok {
435
+ crate::lifecycle::launch::ensure_grok_login_and_folder_trust(workspace)?;
436
+ crate::lifecycle::launch::apply_grok_mcp_overlay(workspace, &mcp_config)?;
437
+ }
341
438
  // 0.3.28 Step 3: per Python parity, worker spawn cwd is ALWAYS `workspace`.
342
439
  // The persisted-state `agent.spawn_cwd` override is ignored (it was a
343
440
  // Rust-only extension that drifted to `.team/runtime/<team_key>/` after
@@ -560,6 +657,10 @@ fn is_structural_startup_prompt_error(error: &str) -> bool {
560
657
  .any(|needle| lower.contains(needle))
561
658
  }
562
659
 
660
+ /// ---
661
+ /// purpose: 用 spec 里的同名 agent 补回 state 席位行缺的命令上下文字段
662
+ /// returns: 合并后的席位行;spec 读不到或找不到该 agent 时原样返回
663
+ /// ---
563
664
  pub(super) fn rehydrate_agent_command_context_from_spec(
564
665
  spec_workspace: &Path,
565
666
  agent_id: &AgentId,
@@ -652,6 +753,13 @@ fn window_present_in_live(
652
753
  false
653
754
  }
654
755
 
756
+ /// ---
757
+ /// purpose: 起该 workspace 的 coordinator
758
+ /// params:
759
+ /// team_key: 传给 coordinator 的团队键
760
+ /// returns: 启动摘要
761
+ /// errors: 启动失败时返回 StatePersist
762
+ /// ---
655
763
  pub(super) fn start_coordinator_for_workspace(
656
764
  workspace: &Path,
657
765
  team_key: Option<&str>,
@@ -662,6 +770,13 @@ pub(super) fn start_coordinator_for_workspace(
662
770
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
663
771
  }
664
772
 
773
+ /// ---
774
+ /// purpose: 按团队持久化的 tmux endpoint 解析出后端,让 restart、add 与 fork 落在同一 socket
775
+ /// params:
776
+ /// team: 目标团队,None 时按唯一性选
777
+ /// returns: 绑定到该 endpoint 的 tmux 后端;冷 workspace 无持久 endpoint 时退到按 workspace 派生
778
+ /// errors: 团队目标歧义或未解析返回 TeamSelect;state 声明后端是 conpty 时也返回 TeamSelect 拒绝,不降级
779
+ /// ---
665
780
  /// State-aware tmux backend resolver. Reads the team's persisted
666
781
  /// `tmux_endpoint` (set at `team-agent launch` time and shared across
667
782
  /// restart/add-agent/fork-agent) and constructs a TmuxBackend on THAT socket,
@@ -736,6 +851,11 @@ pub(crate) fn lifecycle_worker_tmux_backend_for_selected_state(
736
851
  .unwrap_or_else(|| crate::tmux_backend::TmuxBackend::for_workspace(run_workspace)))
737
852
  }
738
853
 
854
+ /// ---
855
+ /// purpose: 与上面同样的团队选择语义,但返回工厂解析出的通用 transport
856
+ /// returns: 已解析的 transport,含后端种类、来源与提示
857
+ /// errors: 团队选择失败或工厂拒绝时返回 TeamSelect,读 state 失败返回 StatePersist
858
+ /// ---
739
859
  /// 0.5.x Phase 1d Batch 1: new generic-typed lifecycle resolver.
740
860
  ///
741
861
  /// Same team-selection semantics as the legacy tmux-typed variant, but
@@ -774,6 +894,11 @@ pub(crate) fn lifecycle_worker_transport_for_selected_state(
774
894
  .map_err(|e| LifecycleError::TeamSelect(e.to_string()))
775
895
  }
776
896
 
897
+ /// ---
898
+ /// purpose: 由已取到的 state 解析 tmux 后端与它的来源
899
+ /// returns: 后端与 endpoint 来源
900
+ /// errors: state 声明后端是 conpty 时返回 TeamSelect
901
+ /// ---
777
902
  pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
778
903
  run_workspace: &Path,
779
904
  state: &serde_json::Value,
@@ -795,6 +920,10 @@ pub(super) fn lifecycle_worker_tmux_backend_selection_for_state(
795
920
  )
796
921
  }
797
922
 
923
+ /// ---
924
+ /// purpose: 由已取到的 state 直接给出 tmux 后端
925
+ /// returns: 绑定到该 state endpoint 的后端,缺失时按 workspace 派生
926
+ /// ---
798
927
  pub(super) fn lifecycle_worker_tmux_backend_for_state(
799
928
  run_workspace: &Path,
800
929
  state: &serde_json::Value,
@@ -803,6 +932,14 @@ pub(super) fn lifecycle_worker_tmux_backend_for_state(
803
932
  .backend
804
933
  }
805
934
 
935
+ /// ---
936
+ /// purpose: 同步团队投影后落盘 restart 结果
937
+ /// params:
938
+ /// topology_authority_agent_ids: 本次以内存值为拓扑权威的席位
939
+ /// returns: 成功返回空值
940
+ /// errors: 落盘失败时返回 StatePersist
941
+ /// contract_id: lifecycle.common.save_restart_projected_state
942
+ /// ---
806
943
  pub(super) fn save_restart_projected_state(
807
944
  workspace: &Path,
808
945
  state: &mut serde_json::Value,
@@ -818,6 +955,14 @@ pub(super) fn save_restart_projected_state(
818
955
  )
819
956
  }
820
957
 
958
+ /// ---
959
+ /// purpose: 同上,并可指定哪些席位跳过会话捕获回填
960
+ /// params:
961
+ /// skip_capture_backfill_agent_ids: 跳过回填的席位
962
+ /// returns: 成功返回空值
963
+ /// errors: 落盘失败时返回 StatePersist
964
+ /// contract_id: lifecycle.common.save_restart_projected_state
965
+ /// ---
821
966
  pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
822
967
  workspace: &Path,
823
968
  state: &mut serde_json::Value,
@@ -826,17 +971,22 @@ pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
826
971
  topology_authority_agent_ids: &[&str],
827
972
  ) -> Result<(), LifecycleError> {
828
973
  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()))
974
+ crate::state::repository::StateRepository::new(workspace)
975
+ .save(
976
+ crate::state::repository::StateWriteIntent::RestartTeam {
977
+ team_key,
978
+ topology_authority_agent_ids,
979
+ skip_capture_backfill_agent_ids,
980
+ },
981
+ state,
982
+ )
983
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))
838
984
  }
839
985
 
986
+ /// ---
987
+ /// purpose: 定出本次投影使用的团队键
988
+ /// returns: 显式 team 优先,其次 state 里的活跃键,最后由 state 推算
989
+ /// ---
840
990
  pub(super) fn restart_projection_team_key(state: &serde_json::Value, team: Option<&str>) -> String {
841
991
  team.filter(|key| !key.is_empty())
842
992
  .map(str::to_string)
@@ -850,6 +1000,12 @@ pub(super) fn restart_projection_team_key(state: &serde_json::Value, team: Optio
850
1000
  .unwrap_or_else(|| crate::state::projection::team_state_key(state))
851
1001
  }
852
1002
 
1003
+ /// ---
1004
+ /// purpose: 把当前顶层状态压实后写回 teams 表
1005
+ /// params:
1006
+ /// state: 就地改写;显式团队键允许覆盖或新建,别名键只在盘上已有且身份不冲突时才写
1007
+ /// returns: teams 表缺失或为空时不动
1008
+ /// ---
853
1009
  pub(super) fn sync_restart_team_projections(state: &mut serde_json::Value, team_key: &str) {
854
1010
  let Some(teams) = state.get("teams").and_then(serde_json::Value::as_object) else {
855
1011
  return;
@@ -930,6 +1086,10 @@ fn json_team_identity_matches(existing: &serde_json::Value, compact: &serde_json
930
1086
  true
931
1087
  }
932
1088
 
1089
+ /// ---
1090
+ /// purpose: 取 state 里的 session 名
1091
+ /// returns: 非空的 session_name,缺失时退到默认名
1092
+ /// ---
933
1093
  pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
934
1094
  state
935
1095
  .get("session_name")
@@ -939,6 +1099,10 @@ pub(super) fn state_session_name(state: &serde_json::Value) -> SessionName {
939
1099
  .unwrap_or_else(|| SessionName::new("team-agent"))
940
1100
  }
941
1101
 
1102
+ /// ---
1103
+ /// purpose: 判断 state 里是否记了非空 session 名
1104
+ /// returns: 记了则 true
1105
+ /// ---
942
1106
  pub(super) fn session_name_present(state: &serde_json::Value) -> bool {
943
1107
  state
944
1108
  .get("session_name")
@@ -947,6 +1111,12 @@ pub(super) fn session_name_present(state: &serde_json::Value) -> bool {
947
1111
  .unwrap_or(false)
948
1112
  }
949
1113
 
1114
+ /// ---
1115
+ /// purpose: 探测 session 是否存活
1116
+ /// params:
1117
+ /// default: 探测本身 panic 时采用的兜底判定
1118
+ /// returns: transport 明确回答时用它;返回错误时判为不存活;探测 panic 时用兜底值
1119
+ /// ---
950
1120
  pub(super) fn session_live_or_default(
951
1121
  transport: &dyn crate::transport::Transport,
952
1122
  session_name: &SessionName,
@@ -961,6 +1131,10 @@ pub(super) fn session_live_or_default(
961
1131
  }
962
1132
  }
963
1133
 
1134
+ /// ---
1135
+ /// purpose: 取席位行的 provider
1136
+ /// returns: 解析成功用它,缺失或不认识时退到 codex
1137
+ /// ---
964
1138
  pub(super) fn agent_provider(agent: &serde_json::Value) -> Provider {
965
1139
  agent
966
1140
  .get("provider")
@@ -969,6 +1143,10 @@ pub(super) fn agent_provider(agent: &serde_json::Value) -> Provider {
969
1143
  .unwrap_or(Provider::Codex)
970
1144
  }
971
1145
 
1146
+ /// ---
1147
+ /// purpose: 取席位行的 auth_mode
1148
+ /// returns: 解析成功用它,缺失或不认识时退到 subscription
1149
+ /// ---
972
1150
  pub(super) fn agent_auth_mode(agent: &serde_json::Value) -> AuthMode {
973
1151
  agent
974
1152
  .get("auth_mode")
@@ -977,6 +1155,10 @@ pub(super) fn agent_auth_mode(agent: &serde_json::Value) -> AuthMode {
977
1155
  .unwrap_or(AuthMode::Subscription)
978
1156
  }
979
1157
 
1158
+ /// ---
1159
+ /// purpose: 取席位行记录的会话 id
1160
+ /// returns: 非空时返回,否则 None
1161
+ /// ---
980
1162
  pub(super) fn agent_session_id(agent: &serde_json::Value) -> Option<SessionId> {
981
1163
  agent
982
1164
  .get("session_id")
@@ -985,6 +1167,10 @@ pub(super) fn agent_session_id(agent: &serde_json::Value) -> Option<SessionId> {
985
1167
  .map(SessionId::new)
986
1168
  }
987
1169
 
1170
+ /// ---
1171
+ /// purpose: 取席位行记录的 rollout 路径
1172
+ /// returns: 非空时返回,否则 None
1173
+ /// ---
988
1174
  pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPath> {
989
1175
  agent
990
1176
  .get("rollout_path")
@@ -993,6 +1179,10 @@ pub(super) fn agent_rollout_path(agent: &serde_json::Value) -> Option<RolloutPat
993
1179
  .map(RolloutPath::new)
994
1180
  }
995
1181
 
1182
+ /// ---
1183
+ /// purpose: 判断该席位的 resume 依据是否存在
1184
+ /// returns: 探测结果里的存在位
1185
+ /// ---
996
1186
  pub(super) fn resume_backing_exists_for_agent(
997
1187
  workspace: &Path,
998
1188
  agent_id: &AgentId,
@@ -1032,6 +1222,13 @@ pub(crate) struct SessionIdentityProbeResult {
1032
1222
  pub rollout_path: Option<PathBuf>,
1033
1223
  }
1034
1224
 
1225
+ /// ---
1226
+ /// purpose: 探测 rollout 文件里嵌的席位身份是否与本席位一致
1227
+ /// params:
1228
+ /// _provider: 未参与判定
1229
+ /// rollout_path: 没有路径时三项都返回未知
1230
+ /// returns: 一致性判定、读到的嵌入席位名与实际探测路径;读不出嵌入身份时一致性为未知
1231
+ /// ---
1035
1232
  pub(crate) fn session_identity_probe_for_agent(
1036
1233
  agent_id: &AgentId,
1037
1234
  _provider: Provider,
@@ -1056,6 +1253,10 @@ pub(crate) fn session_identity_probe_for_agent(
1056
1253
  }
1057
1254
  }
1058
1255
 
1256
+ /// ---
1257
+ /// purpose: 按 provider 分支探测 resume 依据,并记录所有查过的路径
1258
+ /// returns: 存在位与查过的路径列表;持久化的 rollout 路径即使不存在也记进列表
1259
+ /// ---
1059
1260
  pub(super) fn resume_backing_probe_for_agent(
1060
1261
  workspace: &Path,
1061
1262
  agent_id: &AgentId,
@@ -1107,6 +1308,47 @@ pub(super) fn resume_backing_probe_for_agent(
1107
1308
  }
1108
1309
  copilot_session_store_has_session(session_id.as_str())
1109
1310
  }
1311
+ Provider::Grok => {
1312
+ let spawn_cwd = agent
1313
+ .get("spawn_cwd")
1314
+ .and_then(serde_json::Value::as_str)
1315
+ .map(PathBuf::from)
1316
+ .unwrap_or_else(|| workspace.to_path_buf());
1317
+ match crate::provider::session_scan::grok::grok_session_dir(
1318
+ &spawn_cwd,
1319
+ session_id.as_str(),
1320
+ ) {
1321
+ Some(dir) => {
1322
+ checked_paths.push(dir.clone());
1323
+ crate::provider::session_scan::grok::grok_session_archive_present(&dir)
1324
+ }
1325
+ None => false,
1326
+ }
1327
+ }
1328
+ Provider::CursorAgent => {
1329
+ let spawn_cwd = agent
1330
+ .get("spawn_cwd")
1331
+ .and_then(serde_json::Value::as_str)
1332
+ .map(PathBuf::from)
1333
+ .unwrap_or_else(|| workspace.to_path_buf());
1334
+ let rollout_ok = rollout_path.is_some_and(|path| {
1335
+ crate::provider::session_scan::cursor::cursor_session_archive_present(path.as_path())
1336
+ });
1337
+ let discovered = crate::provider::session_scan::cursor::cursor_session_dir_for_cwd(
1338
+ session_id.as_str(),
1339
+ &spawn_cwd,
1340
+ );
1341
+ if let Some(dir) = discovered.as_ref() {
1342
+ checked_paths.push(dir.clone());
1343
+ }
1344
+ if let Some(home) = std::env::var_os("HOME").map(PathBuf::from) {
1345
+ checked_paths.push(home.join(".cursor").join("chats"));
1346
+ }
1347
+ rollout_ok
1348
+ || discovered.as_ref().is_some_and(|dir| {
1349
+ crate::provider::session_scan::cursor::cursor_session_archive_present(dir)
1350
+ })
1351
+ }
1110
1352
  Provider::GeminiCli | Provider::Fake => false,
1111
1353
  };
1112
1354
 
@@ -1121,10 +1363,20 @@ pub(super) fn resume_backing_probe_for_agent(
1121
1363
  }
1122
1364
  }
1123
1365
 
1366
+ /// ---
1367
+ /// purpose: 判断该 provider 是否支持 resume
1368
+ /// returns: 由 provider 适配器的能力位给出
1369
+ /// contract_id: lifecycle.common.provider_supports_resume
1370
+ /// ---
1124
1371
  pub(super) fn provider_supports_resume(provider: Provider) -> bool {
1125
1372
  crate::provider::get_adapter(provider).caps().resume
1126
1373
  }
1127
1374
 
1375
+ /// ---
1376
+ /// purpose: 由 provider wire 名判断是否支持 resume
1377
+ /// returns: 名字不认识时为 false
1378
+ /// contract_id: lifecycle.common.provider_supports_resume
1379
+ /// ---
1128
1380
  pub(super) fn provider_wire_supports_resume(provider: &str) -> bool {
1129
1381
  parse_provider(provider)
1130
1382
  .map(provider_supports_resume)
@@ -1300,6 +1552,13 @@ fn copilot_session_store_has_session(session_id: &str) -> bool {
1300
1552
  .is_ok()
1301
1553
  }
1302
1554
 
1555
+ /// ---
1556
+ /// purpose: 对缺会话的席位做一次捕获尝试
1557
+ /// params:
1558
+ /// state: 就地改写
1559
+ /// returns: 本次是否改动了 state
1560
+ /// errors: 捕获过程出错时返回 Provider
1561
+ /// ---
1303
1562
  pub(crate) fn refresh_missing_provider_sessions(
1304
1563
  state: &mut serde_json::Value,
1305
1564
  ) -> Result<bool, LifecycleError> {
@@ -1313,6 +1572,15 @@ pub(crate) fn refresh_missing_provider_sessions(
1313
1572
  .map_err(|e| LifecycleError::Provider(e.to_string()))
1314
1573
  }
1315
1574
 
1575
+ /// ---
1576
+ /// purpose: 有界等待缺会话席位收敛,并逐轮写进度事件
1577
+ /// params:
1578
+ /// deadline: 等待上限
1579
+ /// poll_interval: 轮询间隔
1580
+ /// allow_fresh: 只写进事件载荷,不改变收敛判定
1581
+ /// returns: 收敛结论
1582
+ /// errors: 收敛过程出错时返回 StatePersist
1583
+ /// ---
1316
1584
  pub(crate) fn converge_missing_provider_sessions(
1317
1585
  state: &mut serde_json::Value,
1318
1586
  deadline: std::time::Duration,
@@ -1358,6 +1626,10 @@ fn write_session_convergence_progress_event(
1358
1626
  .map_err(|e| e.to_string())
1359
1627
  }
1360
1628
 
1629
+ /// ---
1630
+ /// purpose: 列出必须等到会话收敛才能 restart 的席位
1631
+ /// returns: 排序后的席位 id;只保留无会话、状态为 running 且确有待保留上下文的席位,从未捕获过的席位不进入
1632
+ /// ---
1361
1633
  pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Value) -> Vec<String> {
1362
1634
  let mut missing = crate::session_capture::incomplete_resumable_agent_ids(state)
1363
1635
  .into_iter()
@@ -1397,6 +1669,10 @@ pub(crate) fn restart_required_missing_session_agent_ids(state: &serde_json::Val
1397
1669
  missing.sort();
1398
1670
  missing
1399
1671
  }
1672
+ /// ---
1673
+ /// purpose: 取该席位的窗口名
1674
+ /// returns: 席位行里的非空 window,缺失时用席位 id
1675
+ /// ---
1400
1676
  pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> String {
1401
1677
  agent
1402
1678
  .get("window")
@@ -1408,6 +1684,10 @@ pub(super) fn agent_window(agent: &serde_json::Value, agent_id: &AgentId) -> Str
1408
1684
 
1409
1685
  pub(super) use crate::provider::wire::{parse_provider, provider_wire};
1410
1686
 
1687
+ /// ---
1688
+ /// purpose: 把 auth_mode 字符串解析成枚举
1689
+ /// returns: 只认三种取值,未知返回 None
1690
+ /// ---
1411
1691
  pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
1412
1692
  match raw {
1413
1693
  "subscription" => Some(AuthMode::Subscription),
@@ -1417,6 +1697,11 @@ pub(super) fn parse_auth_mode(raw: &str) -> Option<AuthMode> {
1417
1697
  }
1418
1698
  }
1419
1699
 
1700
+ /// ---
1701
+ /// purpose: 从给定目录读出 team spec
1702
+ /// returns: 解析后的 YAML
1703
+ /// errors: 文件不存在返回 TeamSelect,读文件或解析失败返回 Compile
1704
+ /// ---
1420
1705
  pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleError> {
1421
1706
  let spec_path = workspace.join("team.spec.yaml");
1422
1707
  if !spec_path.exists() {
@@ -1430,6 +1715,10 @@ pub(super) fn load_team_spec(workspace: &Path) -> Result<YamlValue, LifecycleErr
1430
1715
  yaml::loads(&text).map_err(|e| LifecycleError::Compile(e.to_string()))
1431
1716
  }
1432
1717
 
1718
+ /// ---
1719
+ /// purpose: 在 spec 的 agents 列表里找该席位
1720
+ /// returns: 命中的节点;该 id 其实是 leader 时返回 None
1721
+ /// ---
1433
1722
  pub(super) fn find_spec_agent<'a>(
1434
1723
  spec: &'a YamlValue,
1435
1724
  agent_id: &AgentId,
@@ -1452,10 +1741,18 @@ pub(super) fn find_spec_agent<'a>(
1452
1741
  })
1453
1742
  }
1454
1743
 
1744
+ /// ---
1745
+ /// purpose: 构造未知席位的错误
1746
+ /// returns: 带席位 id 的 RequirementUnmet
1747
+ /// ---
1455
1748
  pub(super) fn unknown_worker(agent_id: &AgentId) -> LifecycleError {
1456
1749
  LifecycleError::RequirementUnmet(format!("unknown worker agent id: {agent_id}"))
1457
1750
  }
1458
1751
 
1752
+ /// ---
1753
+ /// purpose: 定出 session 名,state 缺失时回落到 spec
1754
+ /// returns: 依次取 state 的 session_name、spec 的 runtime.session_name、由 team 名派生,最后用默认名
1755
+ /// ---
1459
1756
  pub(super) fn state_session_name_from_spec(
1460
1757
  state: &serde_json::Value,
1461
1758
  spec: &YamlValue,
@@ -1480,6 +1777,14 @@ pub(super) fn state_session_name_from_spec(
1480
1777
  .unwrap_or_else(|| SessionName::new("team-agent"))
1481
1778
  }
1482
1779
 
1780
+ /// ---
1781
+ /// purpose: 把席位标记为已停并清掉 window 与 pane_id 字段(pane_pid 等其余字段保留)
1782
+ /// params:
1783
+ /// state: 就地改写,非对象时先重置成空对象
1784
+ /// spec_agent: 提供 provider 等最小投影
1785
+ /// returns: 成功返回空值
1786
+ /// errors: state 结构不是对象时返回 StatePersist
1787
+ /// ---
1483
1788
  pub(super) fn mark_agent_stopped(
1484
1789
  state: &mut serde_json::Value,
1485
1790
  agent_id: &AgentId,
@@ -1529,6 +1834,13 @@ pub(super) fn mark_agent_stopped(
1529
1834
  Ok(())
1530
1835
  }
1531
1836
 
1837
+ /// ---
1838
+ /// purpose: 窗口本就存在而未新起进程时,把席位标记为运行中
1839
+ /// params:
1840
+ /// pane: 探到的现有 pane,用于写 pane id 与进程号
1841
+ /// returns: 成功返回空值
1842
+ /// errors: state 结构不是对象时返回 StatePersist
1843
+ /// ---
1532
1844
  pub(super) fn mark_agent_running_noop(
1533
1845
  state: &mut serde_json::Value,
1534
1846
  agent_id: &AgentId,
@@ -1587,6 +1899,11 @@ pub(super) fn mark_agent_running_noop(
1587
1899
  Ok(())
1588
1900
  }
1589
1901
 
1902
+ /// ---
1903
+ /// purpose: 写一条起席无操作事件
1904
+ /// returns: 成功返回空值
1905
+ /// errors: 事件写入失败时返回 StatePersist
1906
+ /// ---
1590
1907
  pub(super) fn write_start_agent_noop_event(
1591
1908
  workspace: &Path,
1592
1909
  agent_id: &AgentId,
@@ -1606,6 +1923,10 @@ pub(super) fn write_start_agent_noop_event(
1606
1923
  Ok(())
1607
1924
  }
1608
1925
 
1926
+ /// ---
1927
+ /// purpose: 判断某 session 里是否存在该窗口
1928
+ /// returns: 明确列出该窗口才为 true;列窗口出错或 panic 时为 false
1929
+ /// ---
1609
1930
  pub(super) fn window_exists(
1610
1931
  transport: &dyn crate::transport::Transport,
1611
1932
  session_name: &SessionName,
@@ -1619,6 +1940,11 @@ pub(super) fn window_exists(
1619
1940
  }
1620
1941
  }
1621
1942
 
1943
+ /// ---
1944
+ /// purpose: 在 state 里把该席位的显示标记为已关
1945
+ /// params:
1946
+ /// state: 就地改写;只有 ghostty 工作区后端会被改写状态与标题,其余后端不动
1947
+ /// ---
1622
1948
  pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &AgentId) {
1623
1949
  let Some(display) = state
1624
1950
  .get_mut("agents")
@@ -1646,6 +1972,13 @@ pub(super) fn close_agent_display(state: &mut serde_json::Value, agent_id: &Agen
1646
1972
  }
1647
1973
  }
1648
1974
 
1975
+ /// ---
1976
+ /// purpose: 丢弃该席位的会话捕获字段并标记为已停
1977
+ /// params:
1978
+ /// state: 就地改写;只删会话捕获相关字段,工作目录等状态字段保留
1979
+ /// returns: 成功返回空值
1980
+ /// errors: 席位不存在返回 RequirementUnmet,席位行不是对象返回 StatePersist
1981
+ /// ---
1649
1982
  pub(super) fn discard_agent_session_fields(
1650
1983
  state: &mut serde_json::Value,
1651
1984
  agent_id: &AgentId,
@@ -1693,6 +2026,10 @@ pub(super) fn discard_agent_session_fields(
1693
2026
  Ok(())
1694
2027
  }
1695
2028
 
2029
+ /// ---
2030
+ /// purpose: 判断该席位是否在运行
2031
+ /// returns: 状态为 running 或 busy 直接为真;其余状态都退到按 session 与窗口存在性判定
2032
+ /// ---
1696
2033
  pub(super) fn agent_is_running(
1697
2034
  state: &serde_json::Value,
1698
2035
  agent_id: &AgentId,
@@ -1724,6 +2061,10 @@ pub(super) fn agent_is_running(
1724
2061
  window_exists(transport, &session_name, window)
1725
2062
  }
1726
2063
 
2064
+ /// ---
2065
+ /// purpose: 判断该席位是不是动态生成的
2066
+ /// returns: state 里记了动态角色文件,或 spec 里标了来源席位时为 true
2067
+ /// ---
1727
2068
  pub(super) fn is_dynamic_agent(
1728
2069
  state: &serde_json::Value,
1729
2070
  spec_agent: &YamlValue,
@@ -1742,6 +2083,13 @@ pub(super) fn is_dynamic_agent(
1742
2083
  .is_some_and(|s| !s.is_empty())
1743
2084
  }
1744
2085
 
2086
+ /// ---
2087
+ /// purpose: 在 spawn 之前预判 tmux 的起法,供审计事件记录
2088
+ /// params:
2089
+ /// layout_placement: 有布局位置时按是否起新窗口区分
2090
+ /// into_existing_session: 目标 session 已存在
2091
+ /// returns: new-session、new-window 或 split-window
2092
+ /// ---
1745
2093
  /// 0.4.6 Stage 2: predict the tmux start mode BEFORE the spawn call, so
1746
2094
  /// the `provider.worker.spawn_argv` event can record what the spawn will
1747
2095
  /// actually do. Same logic as `tmux_start_mode_for_spawn` in agent.rs
@@ -1768,6 +2116,10 @@ pub(super) fn predict_tmux_start_mode(
1768
2116
  }
1769
2117
  }
1770
2118
 
2119
+ /// ---
2120
+ /// purpose: 从盘上读该席位当前的 spawn 世代号
2121
+ /// returns: 读到的值;读不出 state 或字段缺失时为 0
2122
+ /// ---
1771
2123
  /// 0.4.6 Stage 2: read state.agents[agent_id].spawn_epoch from disk to
1772
2124
  /// stamp the spawn_argv event with the current cohort identifier. Returns
1773
2125
  /// 0 if the agent row / field is missing.
@@ -1783,6 +2135,12 @@ pub(super) fn state_spawn_epoch_for_agent(workspace: &Path, agent_id: &AgentId)
1783
2135
  .unwrap_or(0)
1784
2136
  }
1785
2137
 
2138
+ /// ---
2139
+ /// purpose: 新起进程后清掉该席位的活动观测字段
2140
+ /// params:
2141
+ /// agent: 就地删除观测字段;生命周期与拓扑字段一概不动
2142
+ /// returns: 清完之后观测缺失表示未知,不等于空闲
2143
+ /// ---
1786
2144
  /// 0.5.32 (`.team/artifacts/restart-resumed-stale-activity-locate.md` §5):
1787
2145
  /// clear the per-agent turn/activity observation set on a successful new
1788
2146
  /// worker process cohort. Called from `mark_agent_started` /