@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,7 +1,29 @@
1
+ //! ---
2
+ //! purpose: plan 的停止与状态读取
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: halt_plan
6
+ //! what: 读出 plan 状态并返回 Halted 结论
7
+ //! - name: plan_status
8
+ //! what: 读出 plan 的持久化状态
9
+ //! depends:
10
+ //! - crate::lifecycle::helpers
11
+ //! boundary:
12
+ //! - 只读状态文件,本文件不改写 plan 状态
13
+ //! - 不向席位投递任何消息
14
+ //! maturity: wired
15
+ //! ---
1
16
  use super::*;
2
17
 
3
18
  // ── lifecycle::orchestrator —— plan 多 stage 状态机(halt / status)──────────
4
19
 
20
+ /// ---
21
+ /// purpose: 对指定 plan 返回 Halted 结论
22
+ /// params:
23
+ /// reason: 当前实现未使用
24
+ /// returns: Halted,理由恒为 already_terminal;注意它不写盘,磁盘上的 status 不变
25
+ /// errors: 状态文件不存在或解析失败时返回 InvalidPlan
26
+ /// ---
5
27
  /// `halt_plan(workspace, plan_id, reason)`(`orchestrator/__init__.py:152`)。停 plan;
6
28
  /// 非 running → 幂等返回。
7
29
  pub(crate) fn halt_plan(
@@ -25,6 +47,11 @@ pub(crate) fn halt_plan(
25
47
  })
26
48
  }
27
49
 
50
+ /// ---
51
+ /// purpose: 读出 plan 的持久化状态
52
+ /// returns: 反序列化出的 PlanState
53
+ /// errors: 状态文件不存在或解析失败时返回 InvalidPlan
54
+ /// ---
28
55
  /// `plan_status(workspace, plan_id)`(`orchestrator/__init__.py:177`)。读 plan 持久态。
29
56
  pub(crate) fn plan_status(workspace: &Path, plan_id: &PlanId) -> Result<PlanState, LifecycleError> {
30
57
  let path = plan_state_path(workspace, plan_id);
@@ -1,3 +1,18 @@
1
+ //! ---
2
+ //! purpose: restart 拆除 worker session 之前的身份守卫,防止误杀 leader 会话
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: SessionPreflight
6
+ //! what: 守卫结论,通过或指出 worker session 名其实是 leader 会话名
7
+ //! - name: check_session_preflight
8
+ //! what: 纯函数检查 state 里的 session 名字段
9
+ //! depends:
10
+ //! - crate::layout::RuntimeSessions
11
+ //! boundary:
12
+ //! - 纯计算,不做 IO 也不改 state
13
+ //! - 只判这一种异常,其他 session 异常不在本守卫范围
14
+ //! maturity: wired
15
+ //! ---
1
16
  //! unit-3 (Stage 1) — restart preflight session-identity guard.
2
17
  //!
3
18
  //! Single check that runs BEFORE the worker-session teardown in
@@ -27,12 +42,22 @@ pub enum SessionPreflight {
27
42
  }
28
43
 
29
44
  impl SessionPreflight {
45
+ /// ---
46
+ /// purpose: 判断守卫是否放行
47
+ /// returns: 结论为 Ok 时为 true
48
+ /// ---
30
49
  /// True when the preflight passed and the caller may proceed.
31
50
  pub fn is_ok(&self) -> bool {
32
51
  matches!(self, SessionPreflight::Ok)
33
52
  }
34
53
  }
35
54
 
55
+ /// ---
56
+ /// purpose: 检查 state 里的 session 名,决定接下来的 worker session 拆除是否安全
57
+ /// params:
58
+ /// state: runtime state
59
+ /// returns: 发现 worker session 名带 leader 前缀时返回拒绝结论并附名字与原因,否则 Ok
60
+ /// ---
36
61
  /// Inspect `state.json` and decide whether the upcoming worker-session
37
62
  /// kill is safe. Pure: no I/O, no mutation.
38
63
  pub fn check_session_preflight(state: &serde_json::Value) -> SessionPreflight {
@@ -1,3 +1,33 @@
1
+ //! ---
2
+ //! purpose: 整队重建,先算 resume 计划与拒绝结论,再做破坏性拆除与重起
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: restart
6
+ //! what: 整队重建入口,解析 team 与它实际使用的 transport 后执行
7
+ //! - name: restart_with_transport_with_session_convergence_deadline
8
+ //! what: 重建的实体实现,含入口门、spec 重建、计划分类、四种拒绝与重起
9
+ //! - name: RestartPhaseTimer
10
+ //! what: 阶段计时器,把 restart 与 launch 的分段耗时写成事件
11
+ //! - name: restart_candidates
12
+ //! what: 列出本 workspace 可重启的团队
13
+ //! - name: select_restart_state
14
+ //! what: 按 team 参数或唯一性选出重启目标
15
+ //! depends:
16
+ //! - crate::lifecycle::lock
17
+ //! - crate::lifecycle::display
18
+ //! - crate::lifecycle::restart::selection
19
+ //! - crate::lifecycle::restart::preflight
20
+ //! - crate::lifecycle::restart::remove
21
+ //! - crate::state::selector
22
+ //! - crate::state::projection
23
+ //! - crate::state::persist
24
+ //! - crate::event_log::EventLog
25
+ //! boundary:
26
+ //! - 四种拒绝一律先于任何 teardown 产出,拒绝时现场不变
27
+ //! - 每个非 paused 席位必发一条 resume 决策事件
28
+ //! - 只有计划里不含 Resumed 时才并行起席,串行臂的会话消失检测没有并发等价物
29
+ //! maturity: wired
30
+ //! ---
1
31
  use super::common::*;
2
32
  use super::selection::classify_restart_plan_with_resume_validation;
3
33
  use super::*;
@@ -17,16 +47,31 @@ pub(crate) struct RestartPhaseTimer {
17
47
  }
18
48
 
19
49
  impl RestartPhaseTimer {
50
+ /// ---
51
+ /// purpose: 起一个阶段计时器
52
+ /// returns: 以当前单调时钟为起点的计时器
53
+ /// ---
20
54
  pub(crate) fn start() -> Self {
21
55
  Self {
22
56
  started_at: std::time::Instant::now(),
23
57
  }
24
58
  }
25
59
 
60
+ /// ---
61
+ /// purpose: 取从起点到现在的毫秒数
62
+ /// returns: 毫秒数,溢出时取上限值
63
+ /// ---
26
64
  pub(crate) fn elapsed_ms(&self) -> u64 {
27
65
  u64::try_from(self.started_at.elapsed().as_millis()).unwrap_or(u64::MAX)
28
66
  }
29
67
 
68
+ /// ---
69
+ /// purpose: 写一条带阶段名与已用毫秒数的事件
70
+ /// params:
71
+ /// kind: 事件名,restart.phase 或 launch.phase
72
+ /// phase: 阶段名
73
+ /// returns: 写事件失败被吞掉,不影响主流程
74
+ /// ---
30
75
  pub(crate) fn emit(&self, workspace: &Path, kind: &'static str, phase: &'static str) {
31
76
  let event_log = crate::event_log::EventLog::new(workspace);
32
77
  let _ = event_log.write(
@@ -41,6 +86,15 @@ impl RestartPhaseTimer {
41
86
 
42
87
  // ── lifecycle::restart —— 整队 Route B resume-or-fresh 重建 ──────────────────
43
88
 
89
+ /// ---
90
+ /// purpose: 整队重建的对外入口
91
+ /// params:
92
+ /// allow_fresh: 允许把不可 resume 的席位改跑全新会话
93
+ /// team: 指定团队,None 时按唯一性选
94
+ /// returns: 重建报告,含成功与失败席位,或四种拒绝之一
95
+ /// errors: 选不到 team 返回 TeamSelect,路径归一失败返回 StatePersist
96
+ /// contract_id: lifecycle.rebuild.restart_entry
97
+ /// ---
44
98
  /// `restart(workspace, allow_fresh, team)`(`restart/orchestration.py:26`)。整队重建:
45
99
  /// **先**算 resume 决策(Route B)+ `first_send_at` 严格校验(corrupt → hard refuse),
46
100
  /// **再**做破坏性 teardown(关显示、建 session)、起后 leader rebind、adaptive 显示重建。
@@ -53,6 +107,14 @@ pub fn restart(
53
107
  restart_with_session_convergence_deadline(workspace, allow_fresh, team, None)
54
108
  }
55
109
 
110
+ /// ---
111
+ /// purpose: 带会话收敛等待上限的整队重建入口
112
+ /// params:
113
+ /// session_converge_deadline_ms: 等待会话收敛的上限,None 用默认
114
+ /// returns: 重建报告
115
+ /// errors: 同 restart
116
+ /// contract_id: lifecycle.rebuild.restart_entry
117
+ /// ---
56
118
  pub fn restart_with_session_convergence_deadline(
57
119
  workspace: &Path,
58
120
  allow_fresh: bool,
@@ -106,6 +168,12 @@ fn resolve_restart_context(
106
168
  })
107
169
  }
108
170
 
171
+ /// ---
172
+ /// purpose: 带注入 transport 的整队重建入口
173
+ /// returns: 重建报告
174
+ /// errors: 透传实体实现的错误
175
+ /// contract_id: lifecycle.rebuild.restart_entry
176
+ /// ---
109
177
  /// `restart` with an injected transport (tests: recording mock; prod: real TmuxBackend). The Route-B
110
178
  /// resume/fresh worker spawn + start_coordinator are wired here over `transport`. (rt-host-a sweep:
111
179
  /// was a stub returning RequirementUnmet at the spawn boundary — never spawned/resumed/started coordinator.)
@@ -118,6 +186,13 @@ pub(crate) fn restart_with_transport(
118
186
  restart_with_transport_with_readiness_deadline(workspace, allow_fresh, team, transport, None)
119
187
  }
120
188
 
189
+ /// ---
190
+ /// purpose: 带就绪等待上限的重建入口
191
+ /// params:
192
+ /// readiness_deadline_ms: 等待就绪的上限
193
+ /// returns: 重建报告;注意本入口把 RefusedResumeNotReady 重映射成 RefusedResumeAtomicity 并把原因记为会话捕获未收敛,两种拒绝在此入口不可分辨
194
+ /// errors: 透传实体实现的错误
195
+ /// ---
121
196
  pub(crate) fn restart_with_transport_with_readiness_deadline(
122
197
  workspace: &Path,
123
198
  allow_fresh: bool,
@@ -158,6 +233,14 @@ pub(crate) fn restart_with_transport_with_readiness_deadline(
158
233
  }
159
234
  }
160
235
 
236
+ /// ---
237
+ /// purpose: 整队重建的实体实现
238
+ /// params:
239
+ /// session_converge_deadline_ms: 会话收敛等待上限
240
+ /// readiness_deadline_ms: 就绪等待上限
241
+ /// returns: 重建报告,或计划分类给出的拒绝结论
242
+ /// errors: 归一路径失败返回 StatePersist;无本地 team 上下文或 spec 缺失返回 TeamSelect
243
+ /// ---
161
244
  pub(crate) fn restart_with_transport_with_session_convergence_deadline(
162
245
  workspace: &Path,
163
246
  allow_fresh: bool,
@@ -2829,6 +2912,13 @@ fn apply_marked_respawn(
2829
2912
  );
2830
2913
  }
2831
2914
 
2915
+ /// ---
2916
+ /// purpose: 写一条 per-worker 的启动分段耗时事件
2917
+ /// params:
2918
+ /// source: 事件来源,launch 或 restart
2919
+ /// command_plan_ms: 命令拼装耗时,调用方给不出真实值时可能是占位 0
2920
+ /// returns: 写事件失败被吞掉
2921
+ /// ---
2832
2922
  /// 0.5.38 Step 1 (`.team/artifacts/startup-latency-locate.md` §5): per-worker
2833
2923
  /// timing tag so operators can pinpoint whether wall time is spent in
2834
2924
  /// command plan compilation, transport spawn, pane verification, or the
@@ -2865,6 +2955,10 @@ pub(crate) fn write_worker_spawn_timing_event(
2865
2955
  );
2866
2956
  }
2867
2957
 
2958
+ /// ---
2959
+ /// purpose: 从 state 里取某席位的 provider wire 名
2960
+ /// returns: 取到的名字,缺失时为 fake
2961
+ /// ---
2868
2962
  pub(crate) fn provider_wire_from_state<'a>(
2869
2963
  state: &'a serde_json::Value,
2870
2964
  agent_id: &str,
@@ -3112,6 +3206,11 @@ fn write_restart_resume_decision_event(
3112
3206
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
3113
3207
  }
3114
3208
 
3209
+ /// ---
3210
+ /// purpose: 列出本 workspace 里可重启的团队
3211
+ /// returns: 每个团队一条候选;没有活团队但顶层 state 有可重启形状时返回它自己
3212
+ /// errors: 读 runtime state 失败时返回 StatePersist
3213
+ /// ---
3115
3214
  /// `restart_candidates(workspace)`(`restart/selection.py:12`)。从 snapshot + active
3116
3215
  /// state 收集可重启 team。
3117
3216
  pub(crate) fn restart_candidates(workspace: &Path) -> Result<Vec<RestartCandidate>, LifecycleError> {
@@ -3134,6 +3233,13 @@ pub(crate) fn restart_candidates(workspace: &Path) -> Result<Vec<RestartCandidat
3134
3233
  .collect())
3135
3234
  }
3136
3235
 
3236
+ /// ---
3237
+ /// purpose: 选出本次重启的目标团队
3238
+ /// params:
3239
+ /// team: 指定团队名,None 时按唯一性选
3240
+ /// returns: 选中的候选
3241
+ /// errors: 选不出或选中的 state 不具备可重启形状时返回 TeamSelect
3242
+ /// ---
3137
3243
  /// `select_restart_state(workspace, team)`(`restart/selection.py:49`)。按 `--team` 或
3138
3244
  /// 唯一性选一个;歧义/未找到 → `TeamSelect`。
3139
3245
  pub(crate) fn select_restart_state(
@@ -1,11 +1,49 @@
1
+ //! ---
2
+ //! purpose: 摘掉一席,含双确认门、六态一致性判定、原子摘除与字节级回滚快照
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: remove_agent
6
+ //! what: 从 spec、state、team_state 与 role 文件原子摘除一席
7
+ //! - name: remove_agent_flag_requirements
8
+ //! what: 只做前置判定,告诉调用方这次需要哪些确认标志
9
+ //! - name: ForceRecreateSnapshot
10
+ //! what: 强制重建用的快照,可在失败后恢复席位并清掉本事务新起的 pane
11
+ //! - name: resolve_seat
12
+ //! what: 由期望、持久与物理三源定出席位的唯一身份与一致性态
13
+ //! - name: spec_without_agent
14
+ //! what: 生成摘掉该席位后的 spec,并清掉指向它的路由与启动项
15
+ //! depends:
16
+ //! - crate::lifecycle::lock
17
+ //! - crate::lifecycle::restart::agent
18
+ //! - crate::lifecycle::restart::team_state
19
+ //! - crate::state::projection
20
+ //! - crate::transport::Transport
21
+ //! boundary:
22
+ //! - 未给 from_spec 确认或运行中未给 force 时拒绝,不擅自摘除
23
+ //! - 物理身份必须收敛到唯一的 session 加 window 加 pane,全局同名窗口不算身份
24
+ //! - 只删托管目录下的角色副本,用户自带的 role 文件不删
25
+ //! maturity: wired
26
+ //! ---
1
27
  use super::agent::{resolve_team_scoped_state_or_refuse, start_agent_at_paths};
2
28
  use super::common::*;
3
29
  use super::team_state::write_team_state;
4
30
  use super::*;
5
31
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
6
32
 
33
+ /// ---
34
+ /// purpose: 摘掉一席的对外入口,取生命周期锁后走实体实现
35
+ /// params:
36
+ /// from_spec: 确认同时从 spec 里摘掉
37
+ /// force: 席位仍在运行时必须给出
38
+ /// returns: 摘除结果
39
+ /// errors: 选不到 team 返回 TeamSelect;确认标志不足或一致性态不允许时返回 RequirementUnmet;写盘失败返回 StatePersist
40
+ /// contract_id: lifecycle.remove_agent.entry
41
+ /// ---
7
42
  /// `remove_agent(workspace, agent_id, from_spec, force, team)`(`lifecycle/agents.py:22`)。
8
- /// 从 spec/state/team_state/agent_health 原子摘除;role markdown 是用户资产,始终保留。
43
+ /// 从 spec/state/team_state/agent_health 原子摘除。
44
+ /// 托管目录 `.team/dynamic-role-files/` 下的物化副本随席位清掉;托管目录之外的
45
+ /// `--role-file` 仍是用户资产,不删。A-28 对托管文件「默认保留」的承诺已由
46
+ /// `ledger.seat-supply-prereq` 推翻。
9
47
  /// `_RemoveRollback` 字节级快照回滚全部运行时变更。未传 from_spec 确认 / 运行中未传 force → 拒绝。
10
48
  pub fn remove_agent(
11
49
  workspace: &Path,
@@ -35,6 +73,12 @@ pub fn remove_agent(
35
73
  )
36
74
  }
37
75
 
76
+ /// ---
77
+ /// purpose: 带注入 transport 的摘席入口,自行取锁
78
+ /// returns: 摘除结果
79
+ /// errors: 同 remove_agent
80
+ /// contract_id: lifecycle.remove_agent.entry
81
+ /// ---
38
82
  pub(crate) fn remove_agent_with_transport(
39
83
  workspace: &Path,
40
84
  agent_id: &AgentId,
@@ -63,6 +107,12 @@ pub(crate) fn remove_agent_with_transport(
63
107
  )
64
108
  }
65
109
 
110
+ /// ---
111
+ /// purpose: 调用方已持有生命周期锁时的摘席入口,本函数不再取锁
112
+ /// returns: 摘除结果
113
+ /// errors: 同 remove_agent
114
+ /// contract_id: lifecycle.remove_agent.entry
115
+ /// ---
66
116
  pub(crate) fn remove_agent_with_transport_locked(
67
117
  workspace: &Path,
68
118
  agent_id: &AgentId,
@@ -93,6 +143,11 @@ pub(crate) struct ForceRecreateSnapshot {
93
143
  }
94
144
 
95
145
  impl ForceRecreateSnapshot {
146
+ /// ---
147
+ /// purpose: 为强制重建拍一份可回滚的快照
148
+ /// returns: 含逻辑回滚数据与摘除前物理 pane 身份的快照
149
+ /// errors: 选不到 team 或席位解析失败时返回 LifecycleError
150
+ /// ---
96
151
  pub(crate) fn capture(
97
152
  workspace: &Path,
98
153
  agent_id: &AgentId,
@@ -126,6 +181,10 @@ impl ForceRecreateSnapshot {
126
181
  })
127
182
  }
128
183
 
184
+ /// ---
185
+ /// purpose: 按快照恢复席位的逻辑状态
186
+ /// returns: 恢复过程中的错误描述列表,空表示恢复干净
187
+ /// ---
129
188
  pub(crate) fn restore(
130
189
  &self,
131
190
  team: Option<&str>,
@@ -135,6 +194,10 @@ impl ForceRecreateSnapshot {
135
194
  .restore(&self.run_workspace, &self.spec_workspace, team, transport)
136
195
  }
137
196
 
197
+ /// ---
198
+ /// purpose: 旧 pane 已被消费后的恢复,先杀掉本次事务新起的 pane 再恢复逻辑快照
199
+ /// returns: 错误描述列表;恢复干净时还会校验物理身份是否回到摘除前的 session 与窗口
200
+ /// ---
138
201
  /// The old pane has already been consumed. Any exact pane now resolved for
139
202
  /// this seat belongs to this force-recreate transaction and must be removed
140
203
  /// before the logical snapshot is restored, otherwise rollback can leave a
@@ -193,6 +256,11 @@ impl ForceRecreateSnapshot {
193
256
  errors
194
257
  }
195
258
 
259
+ /// ---
260
+ /// purpose: 强制重建之后要求席位处于一致态
261
+ /// returns: 一致时返回空值
262
+ /// errors: 解析失败透传;解析出的一致性态不是 Coherent 时返回 StatePersist
263
+ /// ---
196
264
  pub(crate) fn require_coherent(
197
265
  &self,
198
266
  agent_id: &AgentId,
@@ -217,6 +285,11 @@ impl ForceRecreateSnapshot {
217
285
  }
218
286
  }
219
287
 
288
+ /// ---
289
+ /// purpose: 只做前置判定,给出这次摘席需要哪些确认标志
290
+ /// returns: 标志要求;不做任何摘除动作
291
+ /// errors: 选不到 team 或席位解析失败时返回 LifecycleError
292
+ /// ---
220
293
  pub fn remove_agent_flag_requirements(
221
294
  workspace: &Path,
222
295
  agent_id: &AgentId,
@@ -263,6 +336,13 @@ pub(super) struct ResolvedSeat {
263
336
  pub(super) consistency: SeatConsistency,
264
337
  }
265
338
 
339
+ /// ---
340
+ /// purpose: 由 spec、runtime state 与物理 pane 三源定出席位身份与一致性态
341
+ /// params:
342
+ /// transport: 已绑定该 team endpoint 的 transport
343
+ /// returns: 席位解析结果,含所在 session、窗口、物理 pane 与六态之一的一致性判定
344
+ /// errors: 团队作用域 state 取不到或 owner 门不过时返回 LifecycleError
345
+ /// ---
266
346
  /// Resolve one seat from the selected team's desired, persisted and physical
267
347
  /// sources. The transport is already bound to the selected team's endpoint;
268
348
  /// physical identity is then narrowed to exactly one `(session, window, pane)`
@@ -552,6 +632,7 @@ fn remove_agent_inner(
552
632
  // after the stop (stop_agent persisted it); otherwise the originally-resolved projection drives the
553
633
  // removal. Either way we operate on the PROJECTION, never a raw load_runtime_state.
554
634
  let working_state = state;
635
+ let recorded_role_file = recorded_dynamic_role_file(&working_state, agent_id);
555
636
  let mut stopped = false;
556
637
  let mut cleared_locations = Vec::new();
557
638
  if force {
@@ -620,10 +701,16 @@ fn remove_agent_inner(
620
701
  "team.spec.yaml",
621
702
  None,
622
703
  )?;
623
- // Role markdown is user-owned input, including files under the registered
624
- // dynamic-role path. Removing a seat only unregisters runtime state/spec;
625
- // cleanup is intentionally not part of the default operation.
626
- let role_file_removed = false;
704
+ // Managed copies under `.team/dynamic-role-files/` are framework residue
705
+ // and must not block the next same-id clone. External --role-file paths
706
+ // stay user-owned. Classify by the path's directory (do not follow a
707
+ // last-component symlink); unlink with remove_file so only the link dies.
708
+ let role_file_removed = clear_managed_role_residue(
709
+ paths.run_workspace,
710
+ agent_id,
711
+ recorded_role_file.as_deref(),
712
+ &mut cleared_locations,
713
+ )?;
627
714
  let agent_health_deleted = delete_agent_health(paths.run_workspace, team_key, agent_id)?;
628
715
  cleared_locations.push(serde_json::json!("agent_health"));
629
716
  write_remove_step_event(
@@ -663,6 +750,94 @@ struct RemoveSuccess {
663
750
  cleared_locations: Vec<serde_json::Value>,
664
751
  }
665
752
 
753
+ fn recorded_dynamic_role_file(
754
+ state: &serde_json::Value,
755
+ agent_id: &AgentId,
756
+ ) -> Option<std::path::PathBuf> {
757
+ state
758
+ .get("agents")
759
+ .and_then(|v| v.get(agent_id.as_str()))
760
+ .and_then(|v| v.get("dynamic_role_file"))
761
+ .and_then(|v| v.as_str())
762
+ .map(str::trim)
763
+ .filter(|s| !s.is_empty())
764
+ .map(std::path::PathBuf::from)
765
+ }
766
+
767
+ fn resolve_role_path(workspace: &Path, role_file: &Path) -> std::path::PathBuf {
768
+ if role_file.is_absolute() {
769
+ role_file.to_path_buf()
770
+ } else {
771
+ workspace.join(role_file)
772
+ }
773
+ }
774
+
775
+ fn default_managed_role_file(workspace: &Path, agent_id: &AgentId) -> std::path::PathBuf {
776
+ workspace
777
+ .join(".team")
778
+ .join("dynamic-role-files")
779
+ .join(format!("{}.md", agent_id.as_str()))
780
+ }
781
+
782
+ /// Same prefix rule as `role_source_ownership`, but canonicalize the parent
783
+ /// only. Following the last component would classify a managed symlink whose
784
+ /// target lives outside the managed dir as external, and leave residue.
785
+ fn role_path_is_managed(workspace: &Path, role_file: &Path) -> bool {
786
+ let managed_root = workspace.join(".team").join("dynamic-role-files");
787
+ let Ok(root) = std::fs::canonicalize(&managed_root) else {
788
+ return false;
789
+ };
790
+ let abs = resolve_role_path(workspace, role_file);
791
+ let Some(parent) = abs.parent() else {
792
+ return false;
793
+ };
794
+ match std::fs::canonicalize(parent) {
795
+ Ok(parent_canon) => parent_canon.starts_with(&root),
796
+ Err(_) => false,
797
+ }
798
+ }
799
+
800
+ fn unlink_role_path(path: &Path) -> Result<bool, LifecycleError> {
801
+ match std::fs::symlink_metadata(path) {
802
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(false),
803
+ Err(err) => Err(LifecycleError::StatePersist(format!(
804
+ "inspect managed role file {}: {err}",
805
+ path.display()
806
+ ))),
807
+ Ok(_) => std::fs::remove_file(path).map(|_| true).map_err(|err| {
808
+ LifecycleError::StatePersist(format!(
809
+ "remove managed role file {}: {err}",
810
+ path.display()
811
+ ))
812
+ }),
813
+ }
814
+ }
815
+
816
+ fn clear_managed_role_residue(
817
+ workspace: &Path,
818
+ agent_id: &AgentId,
819
+ recorded: Option<&Path>,
820
+ cleared_locations: &mut Vec<serde_json::Value>,
821
+ ) -> Result<bool, LifecycleError> {
822
+ let target = match recorded {
823
+ Some(recorded) => {
824
+ let abs = resolve_role_path(workspace, recorded);
825
+ if !role_path_is_managed(workspace, &abs) {
826
+ return Ok(false);
827
+ }
828
+ abs
829
+ }
830
+ None => default_managed_role_file(workspace, agent_id),
831
+ };
832
+ if !unlink_role_path(&target)? {
833
+ return Ok(false);
834
+ }
835
+ let resource = target.to_string_lossy().into_owned();
836
+ write_remove_step_event(workspace, agent_id, "role_file", &resource, None)?;
837
+ cleared_locations.push(serde_json::json!(resource));
838
+ Ok(true)
839
+ }
840
+
666
841
  fn write_remove_step_event(
667
842
  workspace: &Path,
668
843
  agent_id: &AgentId,
@@ -780,6 +955,13 @@ fn remove_agent_from_state(
780
955
  }
781
956
  }
782
957
 
958
+ /// ---
959
+ /// purpose: 在 state 里给该席位打上退役标记
960
+ /// params:
961
+ /// state: 就地写 agent_lifecycle 下该席位的状态、时间与原因
962
+ /// returns: 已是退役态时幂等返回
963
+ /// errors: state 根、agent_lifecycle 或该条目不是对象时返回 StatePersist
964
+ /// ---
783
965
  pub(crate) fn mark_agent_retired_in_state(
784
966
  state: &mut serde_json::Value,
785
967
  agent_id: &AgentId,
@@ -814,6 +996,11 @@ pub(crate) fn mark_agent_retired_in_state(
814
996
  Ok(())
815
997
  }
816
998
 
999
+ /// ---
1000
+ /// purpose: 清掉该席位的退役标记
1001
+ /// params:
1002
+ /// state: 就地删除;只有当前确为退役态才删
1003
+ /// ---
817
1004
  pub(crate) fn clear_agent_retirement_in_state(state: &mut serde_json::Value, agent_id: &AgentId) {
818
1005
  let Some(lifecycle) = state
819
1006
  .get_mut("agent_lifecycle")
@@ -831,6 +1018,10 @@ pub(crate) fn clear_agent_retirement_in_state(state: &mut serde_json::Value, age
831
1018
  }
832
1019
  }
833
1020
 
1021
+ /// ---
1022
+ /// purpose: 生成摘掉该席位后的 spec
1023
+ /// returns: 去掉该 agent、去掉它的启动项、并清掉指向它的路由引用后的 spec;spec 不是 map 时原样返回
1024
+ /// ---
834
1025
  /// Build the persisted spec after removing one worker. Besides deleting the worker and startup entry,
835
1026
  /// prune routing references that would otherwise point at the removed worker.
836
1027
  pub(crate) fn spec_without_agent(spec: &YamlValue, agent_id: &AgentId) -> YamlValue {