@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,35 @@
1
+ //! ---
2
+ //! purpose: spec 的读取改写与 runtime state 初值物化,含 quick-start 的 owner 种入
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: initial_runtime_state
6
+ //! what: 由 spec 生成首份 runtime state 树,并种入 launched owner
7
+ //! - name: write_spec_atomic
8
+ //! what: 先写同目录临时文件再 rename,避免留下半截 spec
9
+ //! - name: spec_session_name
10
+ //! what: 取 runtime.session_name,缺省由 team 名派生
11
+ //! - name: spec_agents
12
+ //! what: 取 spec 里全部 agent id
13
+ //! - name: spec_routes
14
+ //! what: 对 spec 里每个 task 算出路由决策
15
+ //! - name: effective_runtime_config_for_worker_spawn
16
+ //! what: 由单个 agent 的 dangerously_skip_permissions 定出 bypass 审批结论
17
+ //! depends:
18
+ //! - crate::state::projection
19
+ //! - crate::state::identity
20
+ //! - crate::state::ownership
21
+ //! - crate::model::yaml
22
+ //! - crate::model::routing
23
+ //! - crate::provider::bypass_flags
24
+ //! - crate::lifecycle::launch::identity
25
+ //! - crate::lifecycle::launch::leader_context
26
+ //! - crate::lifecycle::launch::worker_env
27
+ //! boundary:
28
+ //! - 只生成与改写数据,不 spawn 进程也不开显示
29
+ //! - bypass 只认单个 agent 自己的声明,不从 runtime 配置或 leader argv 全队继承
30
+ //! - spec 写盘一律走原子替换,不做就地截断写
31
+ //! maturity: wired
32
+ //! ---
1
33
  //!
2
34
  //! unit-8 (Stage 3) — `lifecycle::launch::spec_state` phase boundary.
3
35
  //!
@@ -40,6 +72,12 @@ use super::leader_context::{
40
72
  };
41
73
  use super::worker_env::spawn_timestamp;
42
74
 
75
+ /// ---
76
+ /// purpose: 由 spec 生成首份 runtime state 树
77
+ /// params:
78
+ /// team_key: 本团队的 runtime 键
79
+ /// returns: 含 spec_path、workspace、team_dir、session_name、leader、agents、tasks 与 display_backend 的 state;随后按环境种入 launched owner,种不到则种一份 unbound owner
80
+ /// ---
43
81
  pub(super) fn initial_runtime_state(
44
82
  spec: &Value,
45
83
  spec_path: &Path,
@@ -113,6 +151,12 @@ pub(super) fn initial_runtime_state(
113
151
  state
114
152
  }
115
153
 
154
+ /// ---
155
+ /// purpose: 从环境里的 caller 身份种入 launched owner
156
+ /// params:
157
+ /// state: 待改写的 state,成功时写入 owner 与 leader receiver
158
+ /// returns: true 表示已种入,取不到 caller 身份或无 pane 时为 false
159
+ /// ---
116
160
  pub(super) fn seed_launched_owner_from_env(state: &mut serde_json::Value) -> bool {
117
161
  let team_id = crate::state::projection::team_state_key(state);
118
162
  let Ok(caller) = crate::state::identity::caller_identity_from_env(
@@ -130,6 +174,13 @@ pub(super) fn seed_launched_owner_from_env(state: &mut serde_json::Value) -> boo
130
174
  )
131
175
  }
132
176
 
177
+ /// ---
178
+ /// purpose: 用给定的 caller 身份与 pane provider 查询函数种入 owner 与 leader receiver
179
+ /// params:
180
+ /// caller: 调用方身份,pane_id 为空则不种
181
+ /// lookup_pane_provider: 由 pane 反查 provider 的函数,便于测试替换
182
+ /// returns: true 表示已写入 owner 记录
183
+ /// ---
133
184
  pub(super) fn seed_launched_owner_from_caller_with_provider_lookup(
134
185
  state: &mut serde_json::Value,
135
186
  caller: crate::state::owner_gate::CallerIdentity,
@@ -186,6 +237,10 @@ pub(super) fn seed_launched_owner_from_caller_with_provider_lookup(
186
237
  true
187
238
  }
188
239
 
240
+ /// ---
241
+ /// purpose: 判断环境里是否带有任一 leader 身份变量
242
+ /// returns: pane id、session uuid、其 override 或 provider 任一非空即 true
243
+ /// ---
189
244
  pub(super) fn has_positive_caller_leader_env() -> bool {
190
245
  env_nonempty("TEAM_AGENT_LEADER_PANE_ID")
191
246
  || env_nonempty("TEAM_AGENT_LEADER_SESSION_UUID")
@@ -193,12 +248,20 @@ pub(super) fn has_positive_caller_leader_env() -> bool {
193
248
  || env_nonempty("TEAM_AGENT_LEADER_PROVIDER")
194
249
  }
195
250
 
251
+ /// ---
252
+ /// purpose: 判断某环境变量存在且非空
253
+ /// returns: 存在且非空为 true
254
+ /// ---
196
255
  pub(super) fn env_nonempty(key: &str) -> bool {
197
256
  std::env::var(key)
198
257
  .ok()
199
258
  .is_some_and(|value| !value.is_empty())
200
259
  }
201
260
 
261
+ /// ---
262
+ /// purpose: 把 spec 的 tasks 列表转成 JSON 数组
263
+ /// returns: 转换后的数组,没有 tasks 时为空数组
264
+ /// ---
202
265
  pub(super) fn spec_tasks_json(spec: &Value) -> serde_json::Value {
203
266
  spec.get("tasks")
204
267
  .and_then(Value::as_list)
@@ -206,6 +269,10 @@ pub(super) fn spec_tasks_json(spec: &Value) -> serde_json::Value {
206
269
  .unwrap_or_else(|| serde_json::json!([]))
207
270
  }
208
271
 
272
+ /// ---
273
+ /// purpose: 把 YAML 值递归转成等价 JSON 值
274
+ /// returns: 结构与标量一一对应的 JSON
275
+ /// ---
209
276
  pub(super) fn yaml_value_to_json(value: &Value) -> serde_json::Value {
210
277
  match value {
211
278
  Value::Null => serde_json::Value::Null,
@@ -226,6 +293,11 @@ pub(super) fn yaml_value_to_json(value: &Value) -> serde_json::Value {
226
293
  }
227
294
  }
228
295
 
296
+ /// ---
297
+ /// purpose: 原子写 spec,先写带进程号后缀的临时文件再 rename 覆盖
298
+ /// returns: 成功返回空值
299
+ /// errors: 建父目录、写临时文件或 rename 失败时返回 StatePersist;rename 失败会删掉临时文件且原 spec 不动
300
+ /// ---
229
301
  ///
230
302
  /// Set `runtime.session_name` on the compiled spec to `session_name`, creating the
231
303
  /// `runtime` map and/or the `session_name` entry if absent. Used by quick-start to
@@ -252,10 +324,18 @@ pub(crate) fn write_spec_atomic(spec_path: &Path, spec: &Value) -> Result<(), Li
252
324
  Ok(())
253
325
  }
254
326
 
327
+ /// ---
328
+ /// purpose: 就地把 spec 的 runtime.session_name 改成给定值
329
+ /// ---
255
330
  pub(crate) fn override_spec_session_name(spec: &mut Value, session_name: &str) {
256
331
  override_spec_runtime_str(spec, "session_name", session_name);
257
332
  }
258
333
 
334
+ /// ---
335
+ /// purpose: 就地把 spec 里 team.workspace 与各 agent 的 working_directory 改成给定路径
336
+ /// params:
337
+ /// spec: 非 Map 时不动;只改已存在的字段,不新建
338
+ /// ---
259
339
  pub(crate) fn override_spec_workspace(spec: &mut Value, workspace: &Path) {
260
340
  let workspace_s = workspace.to_string_lossy().to_string();
261
341
  let Value::Map(root) = spec else { return };
@@ -276,10 +356,19 @@ pub(crate) fn override_spec_workspace(spec: &mut Value, workspace: &Path) {
276
356
  }
277
357
  }
278
358
 
359
+ /// ---
360
+ /// purpose: 就地把 spec 的 runtime.display_backend 改成给定值
361
+ /// ---
279
362
  pub(super) fn override_spec_display_backend(spec: &mut Value, display_backend: &str) {
280
363
  override_spec_runtime_str(spec, "display_backend", display_backend);
281
364
  }
282
365
 
366
+ /// ---
367
+ /// purpose: 就地写 spec 的 runtime 段下某个字符串字段
368
+ /// params:
369
+ /// spec: 非 Map 时不动
370
+ /// returns: runtime 段缺失则新建,已存在同名键则覆盖,runtime 是非 Map 值则整段替换
371
+ /// ---
283
372
  pub(super) fn override_spec_runtime_str(spec: &mut Value, key: &str, value: &str) {
284
373
  let Value::Map(root) = spec else { return };
285
374
  let runtime_slot = root
@@ -306,6 +395,10 @@ pub(super) fn override_spec_runtime_str(spec: &mut Value, key: &str, value: &str
306
395
  }
307
396
  }
308
397
 
398
+ /// ---
399
+ /// purpose: 定出本 spec 的 tmux session 名
400
+ /// returns: runtime.session_name 的非空值;缺失时由 team.name 派生,team 名也缺失时用 agent 兜底
401
+ /// ---
309
402
  pub(super) fn spec_session_name(spec: &Value) -> SessionName {
310
403
  if let Some(name) = spec
311
404
  .get("runtime")
@@ -325,6 +418,10 @@ pub(super) fn spec_session_name(spec: &Value) -> SessionName {
325
418
  SessionName::new(format!("team-{team_name}"))
326
419
  }
327
420
 
421
+ /// ---
422
+ /// purpose: 把 spec_session_name 以更宽可见性转出,供 layout 复用同一份实现
423
+ /// returns: 同 spec_session_name
424
+ /// ---
328
425
  ///
329
426
  /// 0.3.28 layout step 1: pub re-export of `spec_session_name` for the new
330
427
  /// `layout::sessions::worker_session_name` to delegate to. Single underlying
@@ -333,6 +430,10 @@ pub fn worker_session_name_pub(spec: &Value) -> SessionName {
333
430
  spec_session_name(spec)
334
431
  }
335
432
 
433
+ /// ---
434
+ /// purpose: 取出 spec 里全部 agent id
435
+ /// returns: 按 spec 顺序的 agent id,无 id 字段的条目被跳过
436
+ /// ---
336
437
  pub(super) fn spec_agents(spec: &Value) -> Vec<AgentId> {
337
438
  spec_agent_values(spec)
338
439
  .into_iter()
@@ -340,6 +441,10 @@ pub(super) fn spec_agents(spec: &Value) -> Vec<AgentId> {
340
441
  .collect()
341
442
  }
342
443
 
444
+ /// ---
445
+ /// purpose: 取出 spec 里全部 agent id 的集合,便于成员判定
446
+ /// returns: 有序集合
447
+ /// ---
343
448
  ///
344
449
  /// Bug 1 (0.4.2): expose spec agent id set so the restart path can filter
345
450
  /// state.agents to only the agents currently defined in the rebuilt spec.
@@ -351,6 +456,10 @@ pub(crate) fn spec_agent_id_set(spec: &Value) -> std::collections::BTreeSet<Stri
351
456
  .collect()
352
457
  }
353
458
 
459
+ /// ---
460
+ /// purpose: 取出 spec 的 agents 列表原始节点
461
+ /// returns: 节点引用列表,字段缺失或类型不对时为空
462
+ /// ---
354
463
  pub(super) fn spec_agent_values(spec: &Value) -> Vec<&Value> {
355
464
  spec.get("agents")
356
465
  .and_then(Value::as_list)
@@ -358,6 +467,10 @@ pub(super) fn spec_agent_values(spec: &Value) -> Vec<&Value> {
358
467
  .unwrap_or_default()
359
468
  }
360
469
 
470
+ /// ---
471
+ /// purpose: 对 spec 里每个 task 算出路由决策
472
+ /// returns: 每个 task 的选中 agent 与理由,manual_override 恒为 false
473
+ /// ---
361
474
  pub(super) fn spec_routes(spec: &Value) -> Vec<RoutingDecision> {
362
475
  spec.get("tasks")
363
476
  .and_then(Value::as_list)
@@ -378,6 +491,10 @@ pub(super) fn spec_routes(spec: &Value) -> Vec<RoutingDecision> {
378
491
  .unwrap_or_default()
379
492
  }
380
493
 
494
+ /// ---
495
+ /// purpose: 定出默认承接人
496
+ /// returns: routing.default_assignee,缺失时取第一个 agent
497
+ /// ---
381
498
  pub(super) fn spec_default_assignee(spec: &Value) -> Option<AgentId> {
382
499
  spec.get("routing")
383
500
  .and_then(|v| v.get("default_assignee"))
@@ -386,6 +503,12 @@ pub(super) fn spec_default_assignee(spec: &Value) -> Option<AgentId> {
386
503
  .or_else(|| spec_agents(spec).into_iter().next())
387
504
  }
388
505
 
506
+ /// ---
507
+ /// purpose: 由单个 YAML agent 的 dangerously_skip_permissions 定出 bypass 审批结论
508
+ /// returns: 字段为 true 时 enabled 且来源记 RuntimeConfig,并带上该 provider 的 bypass argv;否则 Disabled
509
+ /// errors: 当前实现不产生 Err
510
+ /// contract_id: lifecycle.spec_state.effective_runtime_config
511
+ /// ---
389
512
  /// 0.5.66 bypass 单源:从**单个 agent**(yaml spec)的 `dangerously_skip_permissions` 构造
390
513
  /// `DangerousApproval`(取代旧"runtime config + leader argv"全队一份)。
391
514
  /// true → source=RuntimeConfig(角色声明即用户明确同意,coordinator 的
@@ -423,6 +546,12 @@ pub(crate) fn effective_runtime_config_for_worker_spawn(
423
546
  })
424
547
  }
425
548
 
549
+ /// ---
550
+ /// purpose: 同上,但读的是 runtime state 里的 JSON agent 节点
551
+ /// returns: 同 YAML 版
552
+ /// errors: 当前实现不产生 Err
553
+ /// contract_id: lifecycle.spec_state.effective_runtime_config
554
+ /// ---
426
555
  /// 0.5.66 bypass 单源:restart/state 路径的 serde_json 版(agent 来自 state JSON)。
427
556
  pub(crate) fn effective_runtime_config_for_worker_spawn_json(
428
557
  agent: &serde_json::Value,
@@ -457,6 +586,10 @@ pub(crate) fn effective_runtime_config_for_worker_spawn_json(
457
586
  })
458
587
  }
459
588
 
589
+ /// ---
590
+ /// purpose: 给出 provider 的稳定 wire 名
591
+ /// returns: 与 spec 写法一致的小写下划线名
592
+ /// ---
460
593
  pub(crate) fn provider_display_str(provider: Provider) -> &'static str {
461
594
  match provider {
462
595
  Provider::Claude => "claude",
@@ -464,10 +597,16 @@ pub(crate) fn provider_display_str(provider: Provider) -> &'static str {
464
597
  Provider::Codex => "codex",
465
598
  Provider::Copilot => "copilot",
466
599
  Provider::GeminiCli => "gemini_cli",
600
+ Provider::Grok => "grok",
601
+ Provider::CursorAgent => "cursor_agent",
467
602
  Provider::Fake => "fake",
468
603
  }
469
604
  }
470
605
 
606
+ /// ---
607
+ /// purpose: 由 team 目录推出 workspace 根
608
+ /// returns: 解析成功用解析值,失败时退到 team 目录的父目录,没有父目录则用 team 目录本身
609
+ /// ---
471
610
  pub(super) fn team_workspace(team_dir: &Path) -> PathBuf {
472
611
  crate::model::paths::team_workspace(team_dir).unwrap_or_else(|_| {
473
612
  team_dir
@@ -1,3 +1,32 @@
1
+ //! ---
2
+ //! purpose: 冷启后把席位实况投影进 runtime state,并做团队级合并、投影与 owner 清洗
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: persist_spawn_agent_state
6
+ //! what: 按 spawn 结果与 tmux 实况写出各席位状态,分 running/paused/spawn_failed
7
+ //! - name: save_launched_team_state_for_key
8
+ //! what: 把本次团队状态并入既有 state,投影后经 repository 落盘
9
+ //! - name: merge_workspace_team_state_with_key
10
+ //! what: 以显式团队键把本次状态并进 teams 表,不丢既有团队
11
+ //! - name: drop_worker_pane_seeded_owner
12
+ //! what: 识别出被 worker pane 误种的 owner 并改种 unbound owner
13
+ //! depends:
14
+ //! - crate::state::repository
15
+ //! - crate::state::projection
16
+ //! - crate::state::persist
17
+ //! - crate::state::ownership
18
+ //! - crate::tmux_backend
19
+ //! - crate::lifecycle::launch::agent_state
20
+ //! - crate::lifecycle::launch::identity
21
+ //! - crate::lifecycle::launch::leader_context
22
+ //! - crate::lifecycle::launch::spec_state
23
+ //! - crate::lifecycle::launch::worker_env
24
+ //! boundary:
25
+ //! - 只写 state,不 spawn、不 kill、不开显示
26
+ //! - 落盘一律经 StateRepository 带写意图,不直接改文件
27
+ //! - owner 归属判不准时倒向 unbound,不认领他队的 pane
28
+ //! maturity: wired
29
+ //! ---
1
30
  //!
2
31
  use std::collections::{BTreeMap, BTreeSet};
3
32
  use std::path::{Path, PathBuf};
@@ -21,6 +50,14 @@ use super::leader_context::{
21
50
  use super::spec_state::{has_positive_caller_leader_env, spec_agent_values};
22
51
  use super::worker_env::{agent_is_paused, spawn_timestamp_for_agent};
23
52
 
53
+ /// ---
54
+ /// purpose: 把本次 spawn 的结果写成 runtime state 里的各席位状态
55
+ /// params:
56
+ /// started: 本次 spawn 成功返回的席位
57
+ /// transport: 用于查活窗口与 pane pid
58
+ /// returns: 成功返回空值。spec 里 paused 的席位记 paused,spawn 后窗口不在的记 spawn_failed,其余记 running
59
+ /// errors: 读或写 runtime state 失败时返回 StatePersist
60
+ /// ---
24
61
  pub(super) fn persist_spawn_agent_state(
25
62
  workspace: &Path,
26
63
  spec_path: &Path,
@@ -148,6 +185,10 @@ pub(super) fn persist_spawn_agent_state(
148
185
  save_launched_team_state_for_key(workspace, &state, Some(&team_id), None)
149
186
  }
150
187
 
188
+ /// ---
189
+ /// purpose: 给出各已起席位对应 pane 的进程号
190
+ /// returns: agent_id 到 pane pid 的映射,查不到的席位不出现
191
+ /// ---
151
192
  pub(super) fn pane_pids_by_started_agent(
152
193
  transport: &dyn Transport,
153
194
  started: &[StartedAgent],
@@ -165,6 +206,10 @@ pub(super) fn pane_pids_by_started_agent(
165
206
  .collect()
166
207
  }
167
208
 
209
+ /// ---
210
+ /// purpose: 由 agent id 找出它这次 spawn 的目标 pane
211
+ /// returns: 命中的 pane 串,找不到时为空串
212
+ /// ---
168
213
  pub(super) fn agent_id_to_pane_id<'a>(started: &'a [StartedAgent], agent_id: &str) -> &'a str {
169
214
  started
170
215
  .iter()
@@ -173,6 +218,12 @@ pub(super) fn agent_id_to_pane_id<'a>(started: &'a [StartedAgent], agent_id: &st
173
218
  .unwrap_or("")
174
219
  }
175
220
 
221
+ /// ---
222
+ /// purpose: 不带显式团队键地保存本次团队状态
223
+ /// returns: 成功返回空值
224
+ /// errors: 落盘失败时返回 StatePersist
225
+ /// contract_id: lifecycle.state_projection.save_launched_team_state
226
+ /// ---
176
227
  pub(super) fn save_launched_team_state(
177
228
  workspace: &Path,
178
229
  launched: &serde_json::Value,
@@ -180,6 +231,15 @@ pub(super) fn save_launched_team_state(
180
231
  save_launched_team_state_for_key(workspace, launched, None, None)
181
232
  }
182
233
 
234
+ /// ---
235
+ /// purpose: 保存本次团队状态的实体实现,含团队键钉死、owner 清洗、合并与投影
236
+ /// params:
237
+ /// team_key: 显式团队键,为空则由 state 推算
238
+ /// added_agent_id: 本次新增席位 id,写进写意图
239
+ /// returns: 成功返回空值
240
+ /// errors: 落盘失败时返回 StatePersist
241
+ /// contract_id: lifecycle.state_projection.save_launched_team_state
242
+ /// ---
183
243
  pub(super) fn save_launched_team_state_for_key(
184
244
  workspace: &Path,
185
245
  launched: &serde_json::Value,
@@ -240,6 +300,11 @@ pub(super) fn save_launched_team_state_for_key(
240
300
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
241
301
  }
242
302
 
303
+ /// ---
304
+ /// purpose: 本次状态没写 leader 拓扑字段时,从既有状态里补回来
305
+ /// params:
306
+ /// launched: 就地补 is_external_leader 与 leader_client,已有同名键不覆盖
307
+ /// ---
243
308
  pub(super) fn preserve_existing_leader_topology(
244
309
  existing: &serde_json::Value,
245
310
  launched_key: &str,
@@ -262,6 +327,11 @@ pub(super) fn preserve_existing_leader_topology(
262
327
  }
263
328
  }
264
329
 
330
+ /// ---
331
+ /// purpose: 无 leader 身份环境且 owner pane 形如首席 pane 时改种 unbound owner
332
+ /// params:
333
+ /// launched: 就地改写
334
+ /// ---
265
335
  pub(super) fn drop_bare_worker_seeded_owner(launched: &mut serde_json::Value, launched_key: &str) {
266
336
  if has_positive_caller_leader_env() {
267
337
  return;
@@ -276,6 +346,10 @@ pub(super) fn drop_bare_worker_seeded_owner(launched: &mut serde_json::Value, la
276
346
  }
277
347
  }
278
348
 
349
+ /// ---
350
+ /// purpose: 以显式团队键把本次状态并进 teams 表
351
+ /// returns: 合并后的 state;既有顶层 session 名为空或团队键相同时保留既有 teams 并覆盖本键,否则以既有状态为底再补进本次团队
352
+ /// ---
279
353
  pub(super) fn merge_workspace_team_state_with_key(
280
354
  existing: &serde_json::Value,
281
355
  launched: &serde_json::Value,
@@ -360,6 +434,11 @@ mod merge_workspace_team_state_with_key_tests {
360
434
  }
361
435
  }
362
436
 
437
+ /// ---
438
+ /// purpose: 把 teams 表里本团队的绑定字段提到顶层
439
+ /// params:
440
+ /// launched: 就地补 leader_receiver、team_owner 与 owner_epoch,已有同名键不覆盖
441
+ /// ---
363
442
  pub(super) fn promote_launched_binding_from_team_entry(
364
443
  launched: &mut serde_json::Value,
365
444
  launched_key: &str,
@@ -383,6 +462,11 @@ pub(super) fn promote_launched_binding_from_team_entry(
383
462
  }
384
463
  }
385
464
 
465
+ /// ---
466
+ /// purpose: 顶层 owner 的 pane 不像真实 pane 时把顶层绑定字段整组删掉
467
+ /// params:
468
+ /// state: 就地删 leader_receiver、team_owner 与 owner_epoch
469
+ /// ---
386
470
  pub(super) fn drop_unbound_top_level_owner(state: &mut serde_json::Value) {
387
471
  let pane = state
388
472
  .get("team_owner")
@@ -399,6 +483,12 @@ pub(super) fn drop_unbound_top_level_owner(state: &mut serde_json::Value) {
399
483
  }
400
484
  }
401
485
 
486
+ /// ---
487
+ /// purpose: 本次 owner 的 pane 已属于别的团队时,改种 unbound owner 或整组删除
488
+ /// params:
489
+ /// existing: 既有 state,用于判断 pane 归属
490
+ /// launched: 就地改写
491
+ /// ---
402
492
  pub(super) fn drop_foreign_seeded_owner(
403
493
  existing: &serde_json::Value,
404
494
  launched_key: &str,
@@ -434,6 +524,13 @@ pub(super) fn drop_foreign_seeded_owner(
434
524
  }
435
525
  }
436
526
 
527
+ /// ---
528
+ /// purpose: 识别 owner 是被 worker 自己的 pane 误种的情形并改种 unbound owner
529
+ /// params:
530
+ /// started: 本次起的席位,用于判断 pane 是否属于 worker
531
+ /// worker_tmux_socket: worker 侧 socket 名,与调用方 socket 比对
532
+ /// returns: 只有在无 leader 身份环境、pane 与调用方 TMUX_PANE 相同且看起来是 worker pane 时才改种
533
+ /// ---
437
534
  pub(super) fn drop_worker_pane_seeded_owner(
438
535
  launched: &mut serde_json::Value,
439
536
  launched_key: &str,
@@ -466,6 +563,10 @@ pub(super) fn drop_worker_pane_seeded_owner(
466
563
  }
467
564
  }
468
565
 
566
+ /// ---
567
+ /// purpose: 判断某 pane 是否像本次起的 worker 的 pane
568
+ /// returns: 形如首席 pane,或与任一席位目标互为前缀时为 true
569
+ /// ---
469
570
  pub(super) fn seeded_pane_looks_like_worker(pane: &str, started: &[StartedAgent]) -> bool {
470
571
  pane.ends_with("-first")
471
572
  || started.iter().any(|agent| {
@@ -475,6 +576,10 @@ pub(super) fn seeded_pane_looks_like_worker(pane: &str, started: &[StartedAgent]
475
576
  })
476
577
  }
477
578
 
579
+ /// ---
580
+ /// purpose: 给出 worker 侧使用的 tmux socket 名
581
+ /// returns: transport 是 tmux 后端时按 workspace 推出 socket 名,否则 None
582
+ /// ---
478
583
  pub(super) fn launched_worker_tmux_socket(
479
584
  transport: &dyn Transport,
480
585
  workspace: &Path,
@@ -486,6 +591,10 @@ pub(super) fn launched_worker_tmux_socket(
486
591
  }
487
592
  }
488
593
 
594
+ /// ---
595
+ /// purpose: 判断两侧 socket 是否一致或不可判
596
+ /// returns: 两侧都已知时比相等;调用方已知而 worker 未知时为 false;调用方未知时一律为 true
597
+ /// ---
489
598
  pub(super) fn tmux_sockets_match_or_unknown(
490
599
  caller_socket: Option<&str>,
491
600
  worker_socket: Option<&str>,