@team-agent/installer 0.5.66 → 0.5.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -0
  4. package/crates/team-agent/src/cli/adapters.rs +5 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +156 -0
  6. package/crates/team-agent/src/cli/emit.rs +6 -5
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +22 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +3 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  17. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  18. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  19. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  20. package/crates/team-agent/src/compiler.rs +121 -5
  21. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  22. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  23. package/crates/team-agent/src/coordinator/health.rs +173 -0
  24. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  25. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  26. package/crates/team-agent/src/coordinator/runtime_detectors.rs +31 -0
  27. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  29. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  30. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  31. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  32. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  33. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  34. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  35. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  36. package/crates/team-agent/src/coordinator/types.rs +49 -0
  37. package/crates/team-agent/src/db/message_store.rs +39 -5
  38. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  39. package/crates/team-agent/src/layout/worker_window_helpers.rs +3 -0
  40. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  41. package/crates/team-agent/src/leader/registry.rs +65 -0
  42. package/crates/team-agent/src/leader/start.rs +973 -75
  43. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  44. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  45. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +398 -26
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -0
  48. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  49. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  50. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +548 -0
  51. package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
  52. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -456
  53. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  54. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  55. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  56. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  57. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  58. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +482 -1
  59. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  60. package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -0
  61. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  62. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  63. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +87 -3
  64. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  65. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  66. package/crates/team-agent/src/lifecycle/launch/spawn.rs +127 -25
  67. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -0
  68. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  69. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  70. package/crates/team-agent/src/lifecycle/launch.rs +107 -29
  71. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  72. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  73. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  74. package/crates/team-agent/src/lifecycle/profile_launch.rs +99 -0
  75. package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
  76. package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
  77. package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
  78. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  79. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  80. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  81. package/crates/team-agent/src/lifecycle/restart/remove.rs +233 -6
  82. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  83. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  84. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  85. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  86. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
  87. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  88. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  89. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  90. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  91. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +464 -0
  92. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
  93. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  94. package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
  95. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -0
  96. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  97. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  98. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +485 -0
  99. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  100. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  101. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
  102. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  103. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  104. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  105. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  106. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  107. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  108. package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
  109. package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
  110. package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
  111. package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
  112. package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
  113. package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
  114. package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
  115. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -0
  116. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  117. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  118. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  119. package/crates/team-agent/src/lifecycle/tests.rs +30 -13
  120. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  121. package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
  122. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  123. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  124. package/crates/team-agent/src/messaging/delivery.rs +430 -41
  125. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  126. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  127. package/crates/team-agent/src/messaging/results.rs +4 -0
  128. package/crates/team-agent/src/messaging/send.rs +34 -0
  129. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  130. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  131. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  132. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  133. package/crates/team-agent/src/messaging/types.rs +4 -1
  134. package/crates/team-agent/src/model/enums.rs +17 -6
  135. package/crates/team-agent/src/model/permissions.rs +3 -0
  136. package/crates/team-agent/src/os_probe.rs +73 -2
  137. package/crates/team-agent/src/provider/adapter.rs +259 -6
  138. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
  139. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  140. package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
  141. package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
  142. package/crates/team-agent/src/provider/bypass_flags.rs +47 -2
  143. package/crates/team-agent/src/provider/classify.rs +4 -2
  144. package/crates/team-agent/src/provider/faults.rs +2 -1
  145. package/crates/team-agent/src/provider/mod.rs +11 -0
  146. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  147. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  148. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  149. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  150. package/crates/team-agent/src/provider/session/context_fork.rs +78 -3
  151. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  152. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  153. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  154. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  155. package/crates/team-agent/src/provider/session_scan/common.rs +205 -2
  156. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  157. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  158. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  159. package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
  160. package/crates/team-agent/src/provider/session_scan.rs +12 -0
  161. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  162. package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
  163. package/crates/team-agent/src/provider/types.rs +4 -2
  164. package/crates/team-agent/src/provider/wire.rs +29 -1
  165. package/crates/team-agent/src/state/persist.rs +301 -8
  166. package/crates/team-agent/src/state/repository.rs +5 -0
  167. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  168. package/crates/team-agent/src/tmux_backend.rs +1116 -75
  169. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  170. package/crates/team-agent/src/transport.rs +248 -1
  171. package/npm/install.mjs +129 -73
  172. package/package.json +4 -4
  173. package/skills/team-agent/SKILL.md +32 -239
  174. package/skills/team-agent/command-coverage.json +31 -0
  175. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  176. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  177. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  178. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
@@ -1,3 +1,24 @@
1
+ //! ---
2
+ //! purpose: 对 compatible_api 席位做发车前的 profile 连通性冒烟
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: profile_smoke_checks_for_agents_with_profile_dir
6
+ //! what: 对一组 agent 逐个冒烟,可指定优先查找的 profile 目录
7
+ //! - name: profile_smoke_checks_for_agents
8
+ //! what: 对一组 agent 逐个冒烟,返回逐个结果
9
+ //! - name: smoke_check_agent_profile
10
+ //! what: 单 agent 冒烟,非 compatible_api 直接判 not_required
11
+ //! - name: format_profile_smoke_failures
12
+ //! what: 把失败结果排成一行可读文本
13
+ //! depends:
14
+ //! - crate::lifecycle::profile_launch
15
+ //! - crate::provider::wire
16
+ //! boundary:
17
+ //! - 只读 profile 并发探测请求,不改 profile、不写 runtime state
18
+ //! - 不做重试与退避,超时即判失败
19
+ //! - 结果里不带密钥值
20
+ //! maturity: wired
21
+ //! ---
1
22
  use std::collections::BTreeMap;
2
23
  use std::io::Write as _;
3
24
  use std::path::Path;
@@ -13,6 +34,13 @@ use crate::provider::wire::provider_wire;
13
34
 
14
35
  pub(crate) const DEFAULT_PROFILE_SMOKE_TIMEOUT: Duration = Duration::from_secs(8);
15
36
 
37
+ /// ---
38
+ /// purpose: 用默认 profile 目录对一组 agent 逐个冒烟
39
+ /// params:
40
+ /// agents: spec 里的 agent 节点列表
41
+ /// timeout: 单次探测的超时
42
+ /// returns: 与入参同序的逐 agent 结果 JSON
43
+ /// ---
16
44
  pub(crate) fn profile_smoke_checks_for_agents(
17
45
  workspace: &Path,
18
46
  agents: &[YamlValue],
@@ -21,6 +49,12 @@ pub(crate) fn profile_smoke_checks_for_agents(
21
49
  profile_smoke_checks_for_agents_with_profile_dir(workspace, agents, None, timeout)
22
50
  }
23
51
 
52
+ /// ---
53
+ /// purpose: 用指定 profile 目录对一组 agent 逐个冒烟
54
+ /// params:
55
+ /// profile_dir: 优先查找的 profile 目录,None 表示只用默认查找顺序
56
+ /// returns: 与入参同序的逐 agent 结果 JSON
57
+ /// ---
24
58
  pub(crate) fn profile_smoke_checks_for_agents_with_profile_dir(
25
59
  workspace: &Path,
26
60
  agents: &[YamlValue],
@@ -33,6 +67,13 @@ pub(crate) fn profile_smoke_checks_for_agents_with_profile_dir(
33
67
  .collect()
34
68
  }
35
69
 
70
+ /// ---
71
+ /// purpose: 单个 agent 的 profile 冒烟
72
+ /// params:
73
+ /// agent: 单个 agent 节点,缺 provider 按 codex、缺 auth_mode 按 subscription
74
+ /// timeout: 单次 HTTP 探测的超时
75
+ /// returns: 结果 JSON。非 compatible_api 记 not_required,profile 里关掉冒烟记 skipped_by_profile,其余走真实请求
76
+ /// ---
36
77
  pub(crate) fn smoke_check_agent_profile(
37
78
  workspace: &Path,
38
79
  agent: &YamlValue,
@@ -108,6 +149,10 @@ pub(crate) fn smoke_check_agent_profile(
108
149
  result
109
150
  }
110
151
 
152
+ /// ---
153
+ /// purpose: 把失败结果拼成一行人类可读的摘要
154
+ /// returns: 以分号分隔的 agent 与原因,缺字段时退化成 agent 与 failed
155
+ /// ---
111
156
  pub(crate) fn format_profile_smoke_failures(failures: &[Value]) -> String {
112
157
  failures
113
158
  .iter()
@@ -187,7 +232,13 @@ impl SmokeTarget {
187
232
  }
188
233
  // C-7-1 cr verdict: copilot 一期 subscription-only,无 BYOK HTTP smoke
189
234
  // 入口;同 GeminiCli/Fake 走 unsupported_provider_smoke_skipped。
190
- Provider::Copilot | Provider::GeminiCli | Provider::Fake => {
235
+ // 0.5.67: grok/cursor_agent 同走 skipped (一期 subscription 已登录态)。
236
+ Provider::Copilot
237
+ | Provider::Grok
238
+ | Provider::CursorAgent
239
+ | Provider::Pi
240
+ | Provider::GeminiCli
241
+ | Provider::Fake => {
191
242
  Err("unsupported_provider_smoke_skipped")
192
243
  }
193
244
  }
@@ -1,9 +1,52 @@
1
+ //! ---
2
+ //! purpose: 单席位的起、停与重置,全部在 workspace 级生命周期锁下串行
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: start_agent
6
+ //! what: 起或复活一席,按四象限定出 resume 或 fresh
7
+ //! - name: stop_agent
8
+ //! what: 停一席,按 pane、同角色残留 pane、窗口三级收敛并取排空证据
9
+ //! - name: reset_agent
10
+ //! what: 必须显式丢弃会话,停席后换血再起
11
+ //! - name: start_agent_at_paths
12
+ //! what: 起席的实体实现,owner 门、暂停判定、启动模式决策与落 state
13
+ //! - name: drain_old_pane_and_pid
14
+ //! what: 有界轮询等旧 pane 与旧进程消失,产出可核的证据载荷
15
+ //! depends:
16
+ //! - crate::lifecycle::lock
17
+ //! - crate::lifecycle::restart::selection
18
+ //! - crate::lifecycle::restart::remove
19
+ //! - crate::lifecycle::restart::team_state
20
+ //! - crate::lifecycle::launch
21
+ //! - crate::state::projection
22
+ //! - crate::state::persist
23
+ //! - crate::platform::process
24
+ //! - crate::lifecycle::profile_launch
25
+ //! - crate::lifecycle::worker_command_context
26
+ //! - crate::event_log::EventLog
27
+ //! boundary:
28
+ //! - 每个对外入口先取 workspace 级生命周期锁,本文件不做无锁写
29
+ //! - owner 门先于未知席位报错,权限判定不被存在性判定绕过
30
+ //! - reset 未显式丢弃会话时直接拒绝,不擅自换血
31
+ //! - 排空是有界等待,等不到只如实记录,不无限阻塞
32
+ //! maturity: wired
33
+ //! ---
1
34
  use super::common::*;
2
35
  use super::selection::decide_start_mode;
3
36
  use super::team_state::write_team_state;
4
37
  use super::*;
5
38
  use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest};
6
39
 
40
+ /// ---
41
+ /// purpose: 起或复活一席的对外入口
42
+ /// params:
43
+ /// force: 窗口已存在时也重起
44
+ /// open_display: 是否开显示
45
+ /// allow_fresh: resume 依据缺失时允许改跑全新会话
46
+ /// returns: Running、Noop 或 Paused
47
+ /// errors: 选不到 team 返回 TeamSelect,owner 门不过返回 OwnerRefused,席位不存在返回 RequirementUnmet
48
+ /// contract_id: lifecycle.start_agent.entry
49
+ /// ---
7
50
  /// `start_agent(workspace, agent_id, force, open_display, allow_fresh, team)`
8
51
  /// (`lifecycle/start.py:72`)。`_runtime_lock("start-agent")` 下串行:resume-or-fresh
9
52
  /// 决策、resume 窗口退出回退 fresh、起后投递 pending message、起 coordinator。
@@ -39,6 +82,12 @@ pub fn start_agent(
39
82
  )
40
83
  }
41
84
 
85
+ /// ---
86
+ /// purpose: 带注入 transport 的起席入口;未指定 team 且解析失败时退回把入参 workspace 当作两个路径
87
+ /// returns: Running、Noop 或 Paused
88
+ /// errors: 指定了 team 而解析失败时透传该错误,其余同 start_agent
89
+ /// contract_id: lifecycle.start_agent.entry
90
+ /// ---
42
91
  /// `start_agent` with an injected transport — wires the single-worker resume/fresh spawn +
43
92
  /// start_coordinator (rt-host-a sweep: was a stub returning RequirementUnmet at the spawn boundary).
44
93
  #[allow(clippy::too_many_arguments)]
@@ -80,6 +129,14 @@ pub(crate) fn start_agent_with_transport(
80
129
  )
81
130
  }
82
131
 
132
+ /// ---
133
+ /// purpose: 起席的实体实现,读团队作用域 state、过 owner 门、定启动模式、spawn 并标记已起
134
+ /// params:
135
+ /// spec_workspace: spec 所在目录,用于补回席位的命令上下文
136
+ /// open_display: 当前实现未直接使用,仅参与自适应布局判定
137
+ /// returns: 席位暂停时直接返回 Paused,其余按启动模式返回 Running 或 Noop
138
+ /// errors: 读 state 失败返回 StatePersist,owner 门不过返回 OwnerRefused,席位不存在返回 RequirementUnmet
139
+ /// ---
83
140
  #[allow(clippy::too_many_arguments)]
84
141
  pub(crate) fn start_agent_at_paths(
85
142
  workspace: &Path,
@@ -342,85 +399,97 @@ pub(crate) fn start_agent_at_paths(
342
399
  return Err(error);
343
400
  }
344
401
  let actual_spawn_window = spawn.spawn.window.as_str().to_string();
345
- mark_agent_started(
346
- &mut state,
347
- agent_id,
348
- &actual_spawn_window,
349
- &spawn,
350
- transport,
351
- &safety,
352
- start_mode,
353
- )?;
354
- // 0.5.66 bypass 单源 §2.7:启动即带 bypass → 审计留痕。
355
- if safety.enabled {
356
- let state_agent = state
357
- .get("agents")
358
- .and_then(|v| v.get(agent_id.as_str()));
359
- let _ = crate::event_log::EventLog::new(workspace).write(
360
- "worker.spawn_dangerously_skip_permissions",
361
- serde_json::json!({
362
- "agent_id": agent_id.as_str(),
363
- "role": state_agent
364
- .and_then(|a| a.get("role"))
365
- .and_then(serde_json::Value::as_str)
366
- .unwrap_or_default(),
367
- "provider": state_agent
368
- .and_then(|a| a.get("provider"))
369
- .and_then(serde_json::Value::as_str)
370
- .unwrap_or_default(),
371
- "trigger": "start-agent",
372
- "spec_source_path": crate::lifecycle::launch::agent_id_spec_source_path(
373
- workspace, agent_id.as_str(),
374
- ),
375
- }),
402
+ let post_spawn = (|| -> Result<bool, LifecycleError> {
403
+ mark_agent_started(
404
+ &mut state,
405
+ agent_id,
406
+ &actual_spawn_window,
407
+ &spawn,
408
+ transport,
409
+ &safety,
410
+ start_mode,
411
+ )?;
412
+ // 0.5.66 bypass 单源 §2.7:启动即带 bypass → 审计留痕。
413
+ if safety.enabled {
414
+ let state_agent = state.get("agents").and_then(|v| v.get(agent_id.as_str()));
415
+ let _ = crate::event_log::EventLog::new(workspace).write(
416
+ "worker.spawn_dangerously_skip_permissions",
417
+ serde_json::json!({
418
+ "agent_id": agent_id.as_str(),
419
+ "role": state_agent
420
+ .and_then(|a| a.get("role"))
421
+ .and_then(serde_json::Value::as_str)
422
+ .unwrap_or_default(),
423
+ "provider": state_agent
424
+ .and_then(|a| a.get("provider"))
425
+ .and_then(serde_json::Value::as_str)
426
+ .unwrap_or_default(),
427
+ "trigger": "start-agent",
428
+ "spec_source_path": crate::lifecycle::launch::agent_id_spec_source_path(
429
+ workspace, agent_id.as_str(),
430
+ ),
431
+ }),
432
+ );
433
+ }
434
+ // **0.3.24 add-agent socket drift fix**: keep `state.tmux_endpoint` /
435
+ // `state.tmux_socket` synchronized with the transport actually used for the
436
+ // spawn. Without this, add-agent / fork-agent could spawn to a socket that
437
+ // never gets persisted, and the next coordinator tick would re-resolve to
438
+ // the workspace-hash socket and lose the new pane. Annotation runs inside
439
+ // the same `save_restart_projected_state` window — no parallel "annotate
440
+ // after spawn" race with coordinator and no double source of truth.
441
+ crate::lifecycle::launch::annotate_runtime_tmux_endpoint(&mut state, transport, workspace);
442
+ let team_key = restart_projection_team_key(&state, team);
443
+ // 0.5.32 (`.team/artifacts/restart-resumed-stale-activity-locate.md` §5):
444
+ // clear the matching `agent_health` observation on the new spawn cohort
445
+ // so five-line status summary and status --json do not surface a stale
446
+ // WORKING row from the pre-shutdown process. Best-effort: DB failure
447
+ // must not fail the start-agent path.
448
+ let _ = crate::db::agent_health_capture::clear_agent_health_observation(
449
+ workspace, &team_key, agent_id,
376
450
  );
377
- }
378
- // **0.3.24 add-agent socket drift fix**: keep `state.tmux_endpoint` /
379
- // `state.tmux_socket` synchronized with the transport actually used for the
380
- // spawn. Without this, add-agent / fork-agent could spawn to a socket that
381
- // never gets persisted, and the next coordinator tick would re-resolve to
382
- // the workspace-hash socket and lose the new pane. Annotation runs inside
383
- // the same `save_restart_projected_state` window — no parallel "annotate
384
- // after spawn" race with coordinator and no double source of truth.
385
- crate::lifecycle::launch::annotate_runtime_tmux_endpoint(&mut state, transport, workspace);
386
- let team_key = restart_projection_team_key(&state, team);
387
- // 0.5.32 (`.team/artifacts/restart-resumed-stale-activity-locate.md` §5):
388
- // clear the matching `agent_health` observation on the new spawn cohort
389
- // so five-line status summary and status --json do not surface a stale
390
- // WORKING row from the pre-shutdown process. Best-effort: DB failure
391
- // must not fail the start-agent path.
392
- let _ = crate::db::agent_health_capture::clear_agent_health_observation(
393
- workspace, &team_key, agent_id,
394
- );
395
- let skip_capture_backfill = if matches!(
396
- start_mode,
397
- StartMode::Fresh | StartMode::FreshAfterMissingRollout
398
- ) {
399
- vec![agent_id.as_str()]
400
- } else {
401
- Vec::new()
451
+ let skip_capture_backfill = if matches!(
452
+ start_mode,
453
+ StartMode::Fresh | StartMode::FreshAfterMissingRollout
454
+ ) {
455
+ vec![agent_id.as_str()]
456
+ } else {
457
+ Vec::new()
458
+ };
459
+ save_restart_projected_state_with_capture_backfill_skip(
460
+ workspace,
461
+ &mut state,
462
+ &team_key,
463
+ &skip_capture_backfill,
464
+ &[agent_id.as_str()],
465
+ )?;
466
+ write_start_agent_start_event(
467
+ workspace,
468
+ agent_id,
469
+ &agent,
470
+ provider,
471
+ &spawn.plan,
472
+ start_mode,
473
+ &session_name,
474
+ &actual_spawn_window,
475
+ spawn_session_id,
476
+ tmux_start_mode_for_spawn(&spawn, into_existing_session),
477
+ )?;
478
+ replay_worker_target_missing_messages(workspace, agent_id, &team_key, &state, transport)?;
479
+ Ok(start_coordinator_for_workspace(workspace, Some(&team_key))?.ok)
480
+ })();
481
+ let coordinator_started = match post_spawn {
482
+ Ok(started) => started,
483
+ Err(error) => {
484
+ if let Err(rollback_error) = transport.kill_pane(&spawn.spawn.pane_id) {
485
+ return Err(LifecycleError::RequirementUnmet(format!(
486
+ "{error}; failed to roll back spawned pane {}: {rollback_error}",
487
+ spawn.spawn.pane_id.as_str()
488
+ )));
489
+ }
490
+ return Err(error);
491
+ }
402
492
  };
403
- save_restart_projected_state_with_capture_backfill_skip(
404
- workspace,
405
- &mut state,
406
- &team_key,
407
- &skip_capture_backfill,
408
- &[agent_id.as_str()],
409
- )?;
410
- write_start_agent_start_event(
411
- workspace,
412
- agent_id,
413
- &agent,
414
- provider,
415
- start_mode,
416
- &session_name,
417
- &actual_spawn_window,
418
- spawn_session_id,
419
- tmux_start_mode_for_spawn(&spawn, into_existing_session),
420
- )?;
421
- replay_worker_target_missing_messages(workspace, agent_id, &team_key, &state, transport)?;
422
- let coordinator = start_coordinator_for_workspace(workspace, Some(&team_key))?;
423
- let coordinator_started = coordinator.ok;
424
493
  Ok(StartAgentOutcome::Running {
425
494
  env: AgentActionEnvelope {
426
495
  agent_id: agent_id.clone(),
@@ -788,6 +857,13 @@ fn tmux_start_mode_for_spawn(
788
857
  // 0.4.6 Stage 1: reset/start clean-boundary helpers
789
858
  // ═══════════════════════════════════════════════════════════════════════
790
859
 
860
+ /// ---
861
+ /// purpose: 有界轮询等待旧 pane 不可达且旧进程退出
862
+ /// params:
863
+ /// old_pane: 旧 pane,None 视作已死
864
+ /// old_pid: 旧进程号,None 视作已退出
865
+ /// returns: 证据载荷,含两者是否确认消失与实际等待毫秒数;到上限仍未消失也照实返回
866
+ /// ---
791
867
  /// Bounded polling: wait for the old pane to become unreachable AND, when
792
868
  /// possible, for the old pane_pid to exit. Returns a JSON evidence blob
793
869
  /// recording the observed transition for events.
@@ -830,6 +906,10 @@ pub(super) fn drain_old_pane_and_pid(
830
906
  })
831
907
  }
832
908
 
909
+ /// ---
910
+ /// purpose: 尽力探测某进程号是否还活着
911
+ /// returns: 由平台层给出的判定
912
+ /// ---
833
913
  /// Best-effort liveness probe for a pid.
834
914
  ///
835
915
  /// 0.5.x Windows portability Batch 3: routes through
@@ -843,6 +923,10 @@ pub(super) fn pid_is_alive(pid: u32) -> bool {
843
923
  crate::platform::process::pid_is_alive(pid)
844
924
  }
845
925
 
926
+ /// ---
927
+ /// purpose: 从 state 里读该席位的 pane 进程号
928
+ /// returns: 存在且是无符号整数时返回它,否则 None
929
+ /// ---
846
930
  /// Read state.agents[agent_id].pane_pid (u32) from the runtime state.
847
931
  pub(super) fn state_pane_pid(state: &serde_json::Value, agent_id: &AgentId) -> Option<u32> {
848
932
  state
@@ -853,6 +937,10 @@ pub(super) fn state_pane_pid(state: &serde_json::Value, agent_id: &AgentId) -> O
853
937
  .map(|p| p as u32)
854
938
  }
855
939
 
940
+ /// ---
941
+ /// purpose: 算出该席位下一次的 spawn 世代号
942
+ /// returns: 当前值加一,缺失时按 0 起算;世代号用于把捕获与事件归因到本次进程
943
+ /// ---
856
944
  /// Read + increment state.agents[agent_id].spawn_epoch. The epoch is a
857
945
  /// monotonic counter persisted with the agent row that uniquely tags each
858
946
  /// fresh-start / reset / restart cycle. Subsequent capture / event /
@@ -869,6 +957,12 @@ pub(super) fn next_spawn_epoch(state: &serde_json::Value, agent_id: &AgentId) ->
869
957
  current.saturating_add(1)
870
958
  }
871
959
 
960
+ /// ---
961
+ /// purpose: 停一席的对外入口
962
+ /// returns: 停止报告
963
+ /// errors: 选不到 team 返回 TeamSelect,席位不存在返回 RequirementUnmet
964
+ /// contract_id: lifecycle.stop_agent.entry
965
+ /// ---
872
966
  /// `stop_agent(workspace, agent_id, team)`(`lifecycle/operations.py:62`)。
873
967
  /// owner-gate → kill window → **同时关显示** → 写 state。
874
968
  pub fn stop_agent(
@@ -895,6 +989,12 @@ pub fn stop_agent(
895
989
  )
896
990
  }
897
991
 
992
+ /// ---
993
+ /// purpose: 带注入 transport 的停席入口
994
+ /// returns: 停止报告
995
+ /// errors: 同 stop_agent
996
+ /// contract_id: lifecycle.stop_agent.entry
997
+ /// ---
898
998
  pub(crate) fn stop_agent_with_transport(
899
999
  workspace: &Path,
900
1000
  agent_id: &AgentId,
@@ -919,6 +1019,11 @@ pub(crate) fn stop_agent_with_transport(
919
1019
  )
920
1020
  }
921
1021
 
1022
+ /// ---
1023
+ /// purpose: 停席的实体实现,解析席位、收敛杀 pane 与窗口、取排空证据后落 state
1024
+ /// returns: 停止报告;注意报告里的显示已关闭字段恒为真,不反映实际关显示结果
1025
+ /// errors: 席位解析不到实体返回 RequirementUnmet,写 state 失败返回 StatePersist
1026
+ /// ---
922
1027
  pub(super) fn stop_agent_at_paths(
923
1028
  workspace: &Path,
924
1029
  spec_workspace: &Path,
@@ -1076,6 +1181,13 @@ pub(super) fn stop_agent_at_paths(
1076
1181
  })
1077
1182
  }
1078
1183
 
1184
+ /// ---
1185
+ /// purpose: 取团队作用域的投影 state,把选择拒绝转成带原因的错误
1186
+ /// params:
1187
+ /// team: 目标团队,None 时按唯一性选
1188
+ /// returns: 投影后的 state
1189
+ /// errors: 目标歧义或选不出时返回 TeamSelect 并在消息里带原因与细节,读取失败返回 StatePersist
1190
+ /// ---
1079
1191
  /// golden `resolve_team_scoped_state` (state.py:243): returns the team-scoped projected state, or
1080
1192
  /// surfaces the refusal dict (`team_target_ambiguous` / `team_target_unresolved`) as a typed error
1081
1193
  /// BEFORE the owner gate / unknown-worker raise (operations.py:64-66). The lifecycle return types are
@@ -1251,6 +1363,14 @@ fn mark_agent_started(
1251
1363
  Ok(())
1252
1364
  }
1253
1365
 
1366
+ /// ---
1367
+ /// purpose: 重置一席的对外入口
1368
+ /// params:
1369
+ /// discard_session: 必须为真,否则直接返回拒绝
1370
+ /// returns: 重置结果,未显式丢弃会话时返回 Refused
1371
+ /// errors: 选不到 team 返回 TeamSelect,其余透传实体实现
1372
+ /// contract_id: lifecycle.reset_agent.entry
1373
+ /// ---
1254
1374
  /// `reset_agent(workspace, agent_id, discard_session, open_display, team)`
1255
1375
  /// (`lifecycle/operations.py:102`)。discard + 重起;**未传 discard_session → 拒绝**。
1256
1376
  pub fn reset_agent(
@@ -1286,6 +1406,14 @@ pub fn reset_agent(
1286
1406
  )
1287
1407
  }
1288
1408
 
1409
+ /// ---
1410
+ /// purpose: 带注入 transport 的重置入口
1411
+ /// params:
1412
+ /// discard_session: 必须为真,否则直接返回拒绝
1413
+ /// returns: 重置结果
1414
+ /// errors: 同 reset_agent
1415
+ /// contract_id: lifecycle.reset_agent.entry
1416
+ /// ---
1289
1417
  pub(crate) fn reset_agent_with_transport(
1290
1418
  workspace: &Path,
1291
1419
  agent_id: &AgentId,
@@ -1656,95 +1784,13 @@ fn write_start_agent_start_event(
1656
1784
  agent_id: &AgentId,
1657
1785
  agent: &serde_json::Value,
1658
1786
  provider: crate::provider::Provider,
1787
+ plan: &crate::provider::CommandPlan,
1659
1788
  start_mode: StartMode,
1660
1789
  session_name: &SessionName,
1661
1790
  window: &str,
1662
1791
  session_id: Option<&SessionId>,
1663
1792
  tmux_start_mode: &'static str,
1664
1793
  ) -> Result<(), LifecycleError> {
1665
- let auth_mode = agent_auth_mode(agent);
1666
- let model = agent.get("model").and_then(|v| v.as_str());
1667
- let adapter = crate::provider::get_adapter(provider);
1668
- // Contract C / F6.4: event log must record the same context-aware argv that the
1669
- // actual spawn used — so the role/tools/MCP context appears in `start_agent.agent_start`.
1670
- let command_agent = crate::lifecycle::worker_command_context::WorkerCommandAgent::from_json(
1671
- agent,
1672
- Some(agent_id.as_str()),
1673
- provider,
1674
- )?;
1675
- let system_prompt =
1676
- crate::lifecycle::worker_command_context::compile_worker_system_prompt(&command_agent)?;
1677
- let tools = crate::lifecycle::worker_command_context::resolved_tool_strings_for_command(
1678
- &command_agent,
1679
- provider,
1680
- )?;
1681
- let resolved_tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
1682
- let mcp_config = adapter
1683
- .mcp_config(auth_mode)
1684
- .map_err(|e| LifecycleError::Provider(e.to_string()))?;
1685
- let team_id = agent.get("owner_team_id").and_then(|v| v.as_str());
1686
- let mcp_config = crate::lifecycle::launch::resolve_mcp_config(
1687
- mcp_config,
1688
- workspace,
1689
- agent_id.as_str(),
1690
- team_id.unwrap_or(""),
1691
- );
1692
- let mcp_config_path = crate::lifecycle::launch::write_worker_mcp_config(
1693
- workspace,
1694
- agent_id.as_str(),
1695
- &mcp_config,
1696
- )?;
1697
- let profile_launch =
1698
- crate::lifecycle::profile_launch::prepare_provider_profile_launch_from_json(
1699
- workspace,
1700
- agent_id.as_str(),
1701
- agent,
1702
- Some(&mcp_config),
1703
- )?;
1704
- let command_model = profile_launch.command_overrides.model.as_deref().or(model);
1705
- // 0.4.x provider effort MVP: start_agent path preserves effort from the
1706
- // persisted agent JSON.
1707
- let start_agent_effort =
1708
- crate::lifecycle::launch::provider_effort_for_spawn_json(&agent, provider);
1709
- if let Some(event_value) = crate::lifecycle::launch::provider_effort_event_if_dropped_json(
1710
- &agent,
1711
- provider,
1712
- agent_id.as_str(),
1713
- ) {
1714
- let _ = crate::event_log::EventLog::new(workspace)
1715
- .write("provider.effort_unsupported", event_value);
1716
- }
1717
- let context = crate::provider::ProviderCommandContext {
1718
- auth_mode,
1719
- mcp_config: Some(&mcp_config),
1720
- system_prompt: Some(system_prompt.as_str()),
1721
- model: command_model,
1722
- tools: &resolved_tool_refs,
1723
- profile_launch: Some(&profile_launch),
1724
- agent_id_hint: Some(agent_id.as_str()),
1725
- effort: start_agent_effort,
1726
- };
1727
- let mut plan = match session_id {
1728
- Some(session_id) => adapter
1729
- .build_resume_command_plan(Some(session_id), context)
1730
- .map_err(|e| LifecycleError::Provider(e.to_string()))?,
1731
- None => adapter
1732
- .build_command_plan(context)
1733
- .map_err(|e| LifecycleError::Provider(e.to_string()))?,
1734
- };
1735
- if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
1736
- crate::lifecycle::launch::point_native_mcp_config_at_file(
1737
- &mut plan.argv,
1738
- provider,
1739
- &mcp_config_path,
1740
- );
1741
- }
1742
- crate::lifecycle::launch::fill_spawn_placeholders_full(
1743
- &mut plan.argv,
1744
- workspace,
1745
- agent_id.as_str(),
1746
- team_id,
1747
- );
1748
1794
  crate::event_log::EventLog::new(workspace)
1749
1795
  .write(
1750
1796
  "start_agent.agent_start",
@@ -1783,6 +1829,14 @@ fn write_stop_complete_event(
1783
1829
  Ok(())
1784
1830
  }
1785
1831
 
1832
+ /// ---
1833
+ /// purpose: 写一条停席排空证据事件
1834
+ /// params:
1835
+ /// target: 被停的目标描述
1836
+ /// drain: 排空证据载荷,其字段被并进事件顶层
1837
+ /// returns: 成功返回空值
1838
+ /// errors: 事件写入失败时返回 StatePersist
1839
+ /// ---
1786
1840
  /// 0.4.6 Stage 1: drain evidence for the old pane+pid after stop. Fires
1787
1841
  /// during reset/stop so operators can see whether the prior worker really
1788
1842
  /// went away or stuck around inside the resp budget.