@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,22 @@
1
+ //! ---
2
+ //! purpose: 冷启路径的 worker spawn 执行器,按 spec 逐个起席并汇总启动结果
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: spawn_agents
6
+ //! what: 按 spec 顺序起各非 paused 席位,返回已起席位清单
7
+ //! - name: agent_id_spec_source_path
8
+ //! what: 尽力还原某席位角色文档的路径,用于审计事件
9
+ //! depends:
10
+ //! - crate::transport::Transport
11
+ //! - crate::provider
12
+ //! - crate::state::persist
13
+ //! - crate::lifecycle::profile_launch
14
+ //! - crate::lifecycle::worker_command_context
15
+ //! boundary:
16
+ //! - 只负责起席与收集结果,state 落盘由 state_projection 负责
17
+ //! - 启动提示按掉与 fast 模式启用都是尽力而为,失败不阻断
18
+ //! maturity: wired
19
+ //! ---
1
20
  //!
2
21
  //! unit-8 (Stage 3) — `lifecycle::launch::spawn` phase boundary.
3
22
  //!
@@ -22,6 +41,14 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
22
41
 
23
42
  use super::*;
24
43
 
44
+ /// ---
45
+ /// purpose: 按 spec 顺序起各席位
46
+ /// params:
47
+ /// spec_path: spec 路径,team 目录优先取 state 里的 team_dir
48
+ /// session_name: 目标 tmux session
49
+ /// returns: 已起席位清单,含目标 pane、启动模式、布局与显示信息;spawn 后 pane 已死的席位被跳过
50
+ /// errors: 命令拼装或 transport spawn 失败时返回 LifecycleError
51
+ /// ---
25
52
  pub(super) fn spawn_agents(
26
53
  workspace: &Path,
27
54
  spec_path: &Path,
@@ -108,12 +135,16 @@ pub(super) fn spawn_agents(
108
135
  .mcp_config(auth_mode)
109
136
  .map_err(|e| LifecycleError::Provider(e.to_string()))?;
110
137
  let mcp_config = resolve_mcp_config(mcp_config, workspace, agent_id_raw, &mcp_team_id);
111
- let mcp_config_path = write_worker_mcp_config_for_provider(
112
- workspace,
113
- agent_id_raw,
114
- &mcp_config,
115
- Some(provider),
116
- )?;
138
+ let mcp_config_path = if provider == Provider::Pi {
139
+ None
140
+ } else {
141
+ Some(write_worker_mcp_config_for_provider(
142
+ workspace,
143
+ agent_id_raw,
144
+ &mcp_config,
145
+ Some(provider),
146
+ )?)
147
+ };
117
148
  let profile_dir = team_dir.join("profiles");
118
149
  let profile_launch =
119
150
  crate::lifecycle::profile_launch::prepare_provider_profile_launch_with_profile_dir(
@@ -132,24 +163,54 @@ pub(super) fn spawn_agents(
132
163
  let _ = crate::event_log::EventLog::new(workspace)
133
164
  .write("provider.effort_unsupported", event_value);
134
165
  }
135
- let mut plan = adapter
136
- .build_command_plan(crate::provider::ProviderCommandContext {
137
- auth_mode,
138
- mcp_config: Some(&mcp_config),
139
- system_prompt: Some(system_prompt.as_str()),
140
- model: command_model,
141
- tools: &resolved_tool_refs,
142
- profile_launch: Some(&profile_launch),
143
- // Layer 1 self-healing (architect probe 2026-06-22): expose
144
- // agent_id as a display-name hint so Claude / Copilot
145
- // adapters can pass `--name <agent_id>`. Codex has no
146
- // equivalent flag and ignores the hint.
147
- agent_id_hint: Some(agent_id_raw),
148
- effort: agent_effort,
149
- })
150
- .map_err(|e| LifecycleError::Provider(e.to_string()))?;
166
+ let command_context = crate::provider::ProviderCommandContext {
167
+ auth_mode,
168
+ mcp_config: Some(&mcp_config),
169
+ system_prompt: Some(system_prompt.as_str()),
170
+ model: command_model,
171
+ tools: &resolved_tool_refs,
172
+ profile_launch: Some(&profile_launch),
173
+ // Layer 1 self-healing (architect probe 2026-06-22): expose
174
+ // agent_id as a display-name hint so Claude / Copilot
175
+ // adapters can pass `--name <agent_id>`. Codex has no
176
+ // equivalent flag and ignores the hint.
177
+ agent_id_hint: Some(agent_id_raw),
178
+ effort: agent_effort,
179
+ };
180
+ let mut plan = if provider == Provider::Pi {
181
+ let model = command_model.ok_or_else(|| {
182
+ LifecycleError::RequirementUnmet(
183
+ "Pi launch requires an explicit qualified model".to_string(),
184
+ )
185
+ })?;
186
+ let effort = agent_effort.ok_or_else(|| {
187
+ LifecycleError::RequirementUnmet(
188
+ "Pi launch requires an explicit thinking effort".to_string(),
189
+ )
190
+ })?;
191
+ crate::lifecycle::launch::pi_mcp::materialize_pi_plan(
192
+ crate::lifecycle::launch::pi_mcp::PiMaterializeRequest {
193
+ workspace,
194
+ team_id: &mcp_team_id,
195
+ agent_id: agent_id_raw,
196
+ model,
197
+ effort,
198
+ system_prompt: &system_prompt,
199
+ tool_categories: &resolved_tool_refs,
200
+ team_mcp_tools: &["send_message", "report_result"],
201
+ mcp_config: &mcp_config,
202
+ },
203
+ )
204
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?
205
+ } else {
206
+ adapter
207
+ .build_command_plan(command_context)
208
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?
209
+ };
151
210
  if !plan.managed_mcp_config && !profile_launch.managed_mcp_config {
152
- point_native_mcp_config_at_file(&mut plan.argv, provider, &mcp_config_path);
211
+ if let Some(mcp_config_path) = mcp_config_path.as_ref() {
212
+ point_native_mcp_config_at_file(&mut plan.argv, provider, mcp_config_path);
213
+ }
153
214
  }
154
215
  // C-A-4 cr verdict v2 — Copilot BYOK(compatible_api)硬性校验:
155
216
  // "A model is required for BYOK"(help-providers 原文)。检查 agent
@@ -207,8 +268,12 @@ pub(super) fn spawn_agents(
207
268
  )?;
208
269
  }
209
270
  fill_spawn_placeholders_full(&mut plan.argv, workspace, agent_id_raw, Some(&mcp_team_id));
210
- let mut env =
211
- inherited_env_with_team_overrides(workspace, agent_id_raw, Some(&mcp_team_id));
271
+ let mut env = inherited_env_with_team_overrides(
272
+ workspace,
273
+ agent_id_raw,
274
+ Some(&mcp_team_id),
275
+ Some(auth_mode_env_value(auth_mode)),
276
+ );
212
277
  apply_profile_launch_env(&mut env, &profile_launch);
213
278
  apply_mcp_auto_approval_env(&mut env, &safety);
214
279
  // Python providers.py:145 + launch/core.py:253 — fresh launch runs the worker
@@ -256,6 +321,39 @@ pub(super) fn spawn_agents(
256
321
  );
257
322
  }
258
323
  }
324
+ // Cursor: role 经 .cursor/rules。MCP 身份默认写 provider-config/<id>/cursor
325
+ // (不继承父进程 TEAM_AGENT_*)。--workspace 指该工程根,--add-dir 真 workspace。
326
+ // 隔离关闭时仍拒绝第二席(共用 mcp.json last-writer)。不改 HOME。
327
+ if matches!(provider, Provider::CursorAgent) {
328
+ refuse_second_cursor_occupant(workspace, agent_id_raw, Some(spec))?;
329
+ apply_cursor_agent_rules_overlay(workspace, agent_id_raw, system_prompt.as_str())?;
330
+ apply_cursor_mcp_overlay(workspace, &mcp_config)?;
331
+ let project = if crate::lifecycle::launch::cursor_mcp_isolation_enabled() {
332
+ Some(crate::lifecycle::launch::cursor_mcp_project_dir(
333
+ workspace,
334
+ agent_id_raw,
335
+ )?)
336
+ } else {
337
+ None
338
+ };
339
+ enable_cursor_workspace_mcp(workspace, project.as_deref())?;
340
+ apply_cursor_spawn_workspace_pointers(&mut plan.argv, workspace, agent_id_raw)?;
341
+ let proxy = apply_cursor_subscription_proxy_env(&mut env);
342
+ let event_log = crate::event_log::EventLog::new(workspace);
343
+ let _ = event_log.write(
344
+ "provider.cursor.proxy_presence",
345
+ serde_json::json!({
346
+ "agent_id": agent_id_raw,
347
+ "https_proxy": proxy.https_proxy,
348
+ "no_proxy": proxy.no_proxy,
349
+ }),
350
+ );
351
+ }
352
+ // Grok 无 --mcp-config;写 <cwd>/.grok/config.toml 让 CLI 读到 team-agent。
353
+ if matches!(provider, Provider::Grok) {
354
+ ensure_grok_login_and_folder_trust(workspace)?;
355
+ apply_grok_mcp_overlay(workspace, &mcp_config)?;
356
+ }
259
357
  let spawn_epoch = u64::try_from(started.len()).unwrap_or(u64::MAX);
260
358
  let spawned_at = spawn_timestamp_for_agent(u32::try_from(spawn_epoch).unwrap_or(u32::MAX));
261
359
  // E6 层1 实证3 + 诊断留痕:落最终 worker argv(spawn 前的真实形态)。
@@ -401,6 +499,10 @@ pub(super) fn spawn_agents(
401
499
  Ok(started)
402
500
  }
403
501
 
502
+ /// ---
503
+ /// purpose: 尽力还原某席位角色文档的路径
504
+ /// returns: state 里有 team_dir 时用它下的 agents 目录,否则退到 workspace 下的当前团队目录
505
+ /// ---
404
506
  /// 0.5.66 bypass 单源 §2.7:审计事件里角色 md 的 spec 来源路径(尽力解析,失败空串)。
405
507
  pub(crate) fn agent_id_spec_source_path(workspace: &Path, agent_id: &str) -> String {
406
508
  let team_dir = crate::state::persist::load_runtime_state(workspace)
@@ -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,17 @@ 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",
602
+ Provider::Pi => "pi",
467
603
  Provider::Fake => "fake",
468
604
  }
469
605
  }
470
606
 
607
+ /// ---
608
+ /// purpose: 由 team 目录推出 workspace 根
609
+ /// returns: 解析成功用解析值,失败时退到 team 目录的父目录,没有父目录则用 team 目录本身
610
+ /// ---
471
611
  pub(super) fn team_workspace(team_dir: &Path) -> PathBuf {
472
612
  crate::model::paths::team_workspace(team_dir).unwrap_or_else(|_| {
473
613
  team_dir