@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,25 @@
1
+ //! ---
2
+ //! purpose: 零配置一键起队入口,编译角色目录、起全队、判嵌套层级并给出 attach 指引
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: quick_start
6
+ //! what: 由角色目录一键起队
7
+ //! - name: quick_start_in_workspace_with_display_and_backend
8
+ //! what: 带显示开关与后端选择的起队入口
9
+ //! - name: quick_start_with_transport_in_workspace_with_display
10
+ //! what: 起队的实体实现,含 leader pane 校验、层级门与已有 runtime 的早退
11
+ //! depends:
12
+ //! - crate::compiler
13
+ //! - crate::state::persist
14
+ //! - crate::transport_factory
15
+ //! - crate::lifecycle::launch::identity
16
+ //! - crate::lifecycle::launch::quick_start_transport
17
+ //! boundary:
18
+ //! - 只管初次起队,已有 runtime 时给出 restart 指引而不接管
19
+ //! - 显式指定非 tmux 后端时不静默退回 tmux,不可用就如实报错
20
+ //! - 团队嵌套超过两层直接拒绝
21
+ //! maturity: wired
22
+ //! ---
1
23
  use std::collections::{BTreeMap, BTreeSet};
2
24
  use std::path::{Path, PathBuf};
3
25
  use std::process::Command;
@@ -14,6 +36,17 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
14
36
 
15
37
  use super::*;
16
38
 
39
+ /// ---
40
+ /// purpose: 由角色目录推出 workspace 后一键起队
41
+ /// params:
42
+ /// agents_dir: 角色定义目录
43
+ /// name: 请求的团队名
44
+ /// yes: 免确认标志
45
+ /// team_id: 显式团队键,优先于 name
46
+ /// returns: 起队报告
47
+ /// errors: 透传实体实现的错误
48
+ /// contract_id: lifecycle.quick_start.entry
49
+ /// ---
17
50
  /// `quick_start(agents_dir, name, yes, team_id)`(`diagnose/quick_start.py:18`)。
18
51
  /// 面向用户的零配置入口:编译 team_dir → `launch` → autobind leader receiver → 起
19
52
  /// coordinator → `wait_ready` 轮询就绪。归入 lifecycle module(不与 diagnose 混)。
@@ -27,6 +60,12 @@ pub fn quick_start(
27
60
  quick_start_in_workspace(&workspace, agents_dir, name, yes, team_id)
28
61
  }
29
62
 
63
+ /// ---
64
+ /// purpose: 在指定 workspace 起队,transport 优先复用调用方所在 tmux socket
65
+ /// returns: 起队报告
66
+ /// errors: 透传实体实现的错误
67
+ /// contract_id: lifecycle.quick_start.entry
68
+ /// ---
30
69
  pub(crate) fn quick_start_in_workspace(
31
70
  workspace: &Path,
32
71
  agents_dir: &Path,
@@ -39,6 +78,14 @@ pub(crate) fn quick_start_in_workspace(
39
78
  quick_start_with_transport_in_workspace(&workspace, agents_dir, name, yes, team_id, &transport)
40
79
  }
41
80
 
81
+ /// ---
82
+ /// purpose: 带显示开关与后端字面量的起队入口
83
+ /// params:
84
+ /// open_display: 为假时把 spec 的显示后端改成 none
85
+ /// backend: 未给或为 tmux 时走既有 tmux 路径,其余字面量经 transport 工厂解析
86
+ /// returns: 起队报告
87
+ /// errors: 后端字面量不认识时返回 TeamSelect;工厂拒绝或后端不可用时如实报错,不退回 tmux
88
+ /// ---
42
89
  /// 0.5.x Phase 1d Batch 2: quick-start with an optional
43
90
  /// `--backend <tmux|conpty>` override. When `backend` is `None` or
44
91
  /// `Some("tmux")` the transport is the same one the legacy entrypoint
@@ -182,6 +229,12 @@ pub fn quick_start_in_workspace_with_display_and_backend(
182
229
  )
183
230
  }
184
231
 
232
+ /// ---
233
+ /// purpose: 带注入 transport 的起队入口,由角色目录推 workspace
234
+ /// returns: 起队报告
235
+ /// errors: 透传实体实现的错误
236
+ /// contract_id: lifecycle.quick_start.entry
237
+ /// ---
185
238
  pub(crate) fn quick_start_with_transport(
186
239
  agents_dir: &Path,
187
240
  name: Option<&str>,
@@ -193,6 +246,12 @@ pub(crate) fn quick_start_with_transport(
193
246
  quick_start_with_transport_in_workspace(&workspace, agents_dir, name, yes, team_id, transport)
194
247
  }
195
248
 
249
+ /// ---
250
+ /// purpose: 带注入 transport 与显式 workspace 的起队入口,默认开显示
251
+ /// returns: 起队报告
252
+ /// errors: 透传实体实现的错误
253
+ /// contract_id: lifecycle.quick_start.entry
254
+ /// ---
196
255
  pub(crate) fn quick_start_with_transport_in_workspace(
197
256
  workspace: &Path,
198
257
  agents_dir: &Path,
@@ -206,6 +265,13 @@ pub(crate) fn quick_start_with_transport_in_workspace(
206
265
  )
207
266
  }
208
267
 
268
+ /// ---
269
+ /// purpose: 起队的实体实现,校验 leader pane、编译 spec、定团队键、判层级、必要时早退,然后起队并给 attach 指引
270
+ /// params:
271
+ /// open_display: 为假时把显示后端改成 none
272
+ /// returns: 起队报告,含 session 名与 attach 命令
273
+ /// errors: leader pane 环境无效返回 RequirementUnmet;角色目录不存在或编译失败返回 Compile;嵌套层级超限返回 RequirementUnmet;读 state 失败返回 StatePersist
274
+ /// ---
209
275
  pub(crate) fn quick_start_with_transport_in_workspace_with_display(
210
276
  workspace: &Path,
211
277
  agents_dir: &Path,
@@ -1,3 +1,26 @@
1
+ //! ---
2
+ //! purpose: 起队路径上的 transport 选择、runtime state 里的 transport 标注与 attach 命令拼装
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: quick_start_tmux_backend
6
+ //! what: 优先复用调用方所在 tmux socket,否则按 workspace 派生
7
+ //! - name: annotate_runtime_transport
8
+ //! what: 把 transport 种类与来源写进 state,tmux 时再写 endpoint 三字段
9
+ //! - name: attach_commands_for_runtime_windows
10
+ //! what: 拼出用户可直接粘贴的 attach 命令
11
+ //! - name: attach_window_names_for_state_agents
12
+ //! what: 由 state 推出这些席位对应的窗口名
13
+ //! depends:
14
+ //! - crate::tmux_backend
15
+ //! - crate::transport::Transport
16
+ //! - crate::state::projection
17
+ //! - crate::event_log::EventLog
18
+ //! boundary:
19
+ //! - 只做选择与标注,不 spawn 也不 kill
20
+ //! - tmux 专有字段不写进非 tmux 后端的 state
21
+ //! - pane 标题设置失败只告警不阻断
22
+ //! maturity: wired
23
+ //! ---
1
24
  use std::collections::{BTreeMap, BTreeSet};
2
25
  use std::path::{Path, PathBuf};
3
26
  use std::process::Command;
@@ -14,6 +37,10 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
14
37
 
15
38
  use super::*;
16
39
 
40
+ /// ---
41
+ /// purpose: 定出 quick-start 使用的 tmux 后端
42
+ /// returns: 调用方环境里有 socket 就复用它,否则按 workspace 派生一个
43
+ /// ---
17
44
  pub(crate) fn quick_start_tmux_backend(workspace: &Path) -> crate::tmux_backend::TmuxBackend {
18
45
  if let Some(endpoint) = crate::tmux_backend::socket_name_from_tmux_env() {
19
46
  crate::tmux_backend::TmuxBackend::for_tmux_endpoint(&endpoint)
@@ -22,6 +49,10 @@ pub(crate) fn quick_start_tmux_backend(workspace: &Path) -> crate::tmux_backend:
22
49
  }
23
50
  }
24
51
 
52
+ /// ---
53
+ /// purpose: 说明当前 transport 的 socket 是从哪来的
54
+ /// returns: leader_env 或 workspace;无 endpoint 或两者都不匹配时为 None
55
+ /// ---
25
56
  pub(crate) fn selected_tmux_socket_source(
26
57
  transport: &dyn Transport,
27
58
  workspace: &Path,
@@ -36,6 +67,10 @@ pub(crate) fn selected_tmux_socket_source(
36
67
  }
37
68
  }
38
69
 
70
+ /// ---
71
+ /// purpose: 给 adaptive 布局的 pane 设置标题
72
+ /// returns: 失败只打印告警并写一条事件,不向调用方报错
73
+ /// ---
39
74
  pub(crate) fn configure_adaptive_pane_title(
40
75
  workspace: &Path,
41
76
  transport: &dyn Transport,
@@ -66,6 +101,10 @@ pub(crate) fn configure_adaptive_pane_title(
66
101
  }
67
102
  }
68
103
 
104
+ /// ---
105
+ /// purpose: 把用户给的 workspace 归一成绝对路径
106
+ /// returns: 能 canonicalize 就用它;否则绝对路径原样返回,相对路径接到当前目录后
107
+ /// ---
69
108
  pub(super) fn explicit_quick_start_workspace(workspace: &Path) -> PathBuf {
70
109
  std::fs::canonicalize(workspace).unwrap_or_else(|_| {
71
110
  if workspace.is_absolute() {
@@ -78,6 +117,13 @@ pub(super) fn explicit_quick_start_workspace(workspace: &Path) -> PathBuf {
78
117
  })
79
118
  }
80
119
 
120
+ /// ---
121
+ /// purpose: 把 transport 的种类与来源写进 runtime state
122
+ /// params:
123
+ /// state: 就地写入 transport 段
124
+ /// source: 来源说明,None 时记 unknown
125
+ /// returns: tmux 后端会再调 annotate_runtime_tmux_endpoint,非 tmux 不写 tmux 专有字段
126
+ /// ---
81
127
  /// `quick_start` with an injected transport — tests inject a recording mock so the REAL spawn path
82
128
  /// (launch dry_run=false → spawn_agents) is asserted without a live tmux; prod uses the real TmuxBackend.
83
129
  /// Annotate `state.tmux_endpoint` / `state.tmux_socket` (and `tmux_socket_source`)
@@ -126,6 +172,12 @@ pub fn annotate_runtime_transport(
126
172
  }
127
173
  }
128
174
 
175
+ /// ---
176
+ /// purpose: 把 tmux endpoint、socket 与 socket 来源写进 state
177
+ /// params:
178
+ /// state: 就地写入
179
+ /// returns: transport 没有 endpoint 时不写;socket 名能解析成路径时写路径
180
+ /// ---
129
181
  pub(crate) fn annotate_runtime_tmux_endpoint(
130
182
  state: &mut serde_json::Value,
131
183
  transport: &dyn Transport,
@@ -162,6 +214,12 @@ pub(crate) fn annotate_runtime_tmux_endpoint(
162
214
  }
163
215
  }
164
216
 
217
+ /// ---
218
+ /// purpose: 拼出这些窗口的 tmux attach 命令
219
+ /// params:
220
+ /// endpoint: 已知 endpoint;是绝对路径时直接拼 -S 形式,否则交给 tmux_backend 按 workspace 拼
221
+ /// returns: 与窗口一一对应的命令行
222
+ /// ---
165
223
  pub(super) fn attach_commands_for_runtime_windows<'a>(
166
224
  endpoint: Option<&str>,
167
225
  workspace: &Path,
@@ -199,6 +257,10 @@ pub(super) fn attach_commands_for_runtime_windows<'a>(
199
257
  attach
200
258
  }
201
259
 
260
+ /// ---
261
+ /// purpose: 由本次起的席位推出窗口名
262
+ /// returns: 排序去重后的窗口名,席位没有布局窗口时用 agent id
263
+ /// ---
202
264
  pub(super) fn started_attach_window_names(started: &[StartedAgent]) -> Vec<String> {
203
265
  let mut windows = started
204
266
  .iter()
@@ -215,6 +277,10 @@ pub(super) fn started_attach_window_names(started: &[StartedAgent]) -> Vec<Strin
215
277
  windows
216
278
  }
217
279
 
280
+ /// ---
281
+ /// purpose: 由 state 推出指定席位的窗口名
282
+ /// returns: 每个席位取 layout_window 再取 window,都没有时用 agent id;托管 leader 时追加 leader 窗口
283
+ /// ---
218
284
  pub(crate) fn attach_window_names_for_state_agents<'a>(
219
285
  state: &serde_json::Value,
220
286
  agent_ids: impl IntoIterator<Item = &'a str>,
@@ -240,6 +306,10 @@ pub(crate) fn attach_window_names_for_state_agents<'a>(
240
306
  attach_window_names_with_managed_leader(state, windows)
241
307
  }
242
308
 
309
+ /// ---
310
+ /// purpose: 由 state 里的全部席位推出窗口名
311
+ /// returns: 排序去重后的窗口名,托管 leader 时含 leader 窗口
312
+ /// ---
243
313
  pub(super) fn quick_start_attach_window_names(state: &serde_json::Value) -> Vec<String> {
244
314
  let windows = state
245
315
  .get("agents")
@@ -261,6 +331,10 @@ pub(super) fn quick_start_attach_window_names(state: &serde_json::Value) -> Vec<
261
331
  attach_window_names_with_managed_leader(state, windows)
262
332
  }
263
333
 
334
+ /// ---
335
+ /// purpose: 托管 leader 时补上 leader 窗口并排序去重
336
+ /// returns: 处理后的窗口名列表
337
+ /// ---
264
338
  pub(super) fn attach_window_names_with_managed_leader(
265
339
  state: &serde_json::Value,
266
340
  mut windows: Vec<String>,
@@ -273,6 +347,10 @@ pub(super) fn attach_window_names_with_managed_leader(
273
347
  windows
274
348
  }
275
349
 
350
+ /// ---
351
+ /// purpose: 判断该 state 是否走托管 leader
352
+ /// returns: 由 state::projection 给出的判定
353
+ /// ---
276
354
  pub(super) fn state_uses_managed_leader(state: &serde_json::Value) -> bool {
277
355
  crate::state::projection::state_is_managed_leader(state)
278
356
  }
@@ -1,3 +1,22 @@
1
+ //! ---
2
+ //! purpose: 起队后的就绪判定,含 worker 状态汇总与 leader 收件可达性
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: quick_start_worker_readiness
6
+ //! what: 汇总各席位状态给出就绪结论
7
+ //! - name: quick_start_session_capture_incomplete_agents
8
+ //! what: 列出会话捕获尚未收敛的席位
9
+ //! - name: launched_team_receiver_is_attached
10
+ //! what: 以宿主注册表为准判断 leader 收件是否已挂上
11
+ //! depends:
12
+ //! - crate::state::persist
13
+ //! - crate::session_capture
14
+ //! - crate::leader::registry
15
+ //! boundary:
16
+ //! - 可达性以宿主注册表为权威,workspace state 只是副本
17
+ //! - 判不出来一律当作未挂上,不乐观放行
18
+ //! maturity: wired
19
+ //! ---
1
20
  //! unit-8 (Stage 3) — `lifecycle::launch::readiness` phase boundary.
2
21
  //!
3
22
  //! Dedicated home for coordinator-start + readiness-verdict computation.
@@ -20,6 +39,12 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
20
39
 
21
40
  use super::*;
22
41
 
42
+ /// ---
43
+ /// purpose: 由 runtime state 汇总该团队的就绪结论
44
+ /// params:
45
+ /// team_key: 团队键,teams 表里没有时退到顶层 state
46
+ /// returns: 有非 running 席位时返回 Degraded 并列出它们,否则返回 PendingToolLoad
47
+ /// ---
23
48
  pub(super) fn quick_start_worker_readiness(
24
49
  workspace: &Path,
25
50
  team_key: &str,
@@ -66,6 +91,10 @@ pub(super) fn quick_start_worker_readiness(
66
91
  }
67
92
  }
68
93
 
94
+ /// ---
95
+ /// purpose: 列出会话捕获尚未收敛的席位
96
+ /// returns: 席位 id 列表;读不出 state 时为空
97
+ /// ---
69
98
  pub(super) fn quick_start_session_capture_incomplete_agents(
70
99
  workspace: &Path,
71
100
  team_key: &str,
@@ -81,48 +110,66 @@ pub(super) fn quick_start_session_capture_incomplete_agents(
81
110
  crate::session_capture::incomplete_interacted_resumable_agent_ids(team_state)
82
111
  }
83
112
 
84
- pub(crate) fn launched_team_receiver_is_attached(workspace: &Path, team_key: &str) -> bool {
85
- let Ok(state) = load_runtime_state(workspace) else {
86
- return true;
87
- };
88
- let team_state = state
89
- .get("teams")
90
- .and_then(serde_json::Value::as_object)
91
- .and_then(|teams| teams.get(team_key))
92
- .unwrap_or(&state);
93
- if team_state.get("leader_receiver").is_none() {
94
- return crate::state::projection::state_is_external_leader(team_state);
113
+ /// ---
114
+ /// purpose: 判断该团队的 leader 收件端是否真的挂上了
115
+ /// returns: 注册表明确记为 attached 且 state 可读时为 true;未绑定或判不出一律 false
116
+ /// ---
117
+ /// Host registry is the deliverability authority. Workspace `state.json`
118
+ /// is only a copy. Detection failure is unbound, never attached.
119
+ pub fn launched_team_receiver_is_attached(workspace: &Path, team_key: &str) -> bool {
120
+ match registry_deliverability(workspace, team_key) {
121
+ RegistryDeliverability::Attached => {}
122
+ RegistryDeliverability::Unbound | RegistryDeliverability::Undecidable => return false,
95
123
  }
96
- if team_uses_fake_model_harness(team_state) {
97
- return true;
98
- }
99
- leader_receiver_is_attached(team_state)
124
+ load_runtime_state(workspace).is_ok()
100
125
  }
101
126
 
102
- pub(super) fn team_uses_fake_model_harness(team_state: &serde_json::Value) -> bool {
103
- team_state
104
- .get("agents")
105
- .and_then(serde_json::Value::as_object)
106
- .is_some_and(|agents| {
107
- !agents.is_empty()
108
- && agents.values().all(|agent| {
109
- agent.get("model").and_then(serde_json::Value::as_str) == Some("fake")
110
- })
111
- })
127
+ enum RegistryDeliverability {
128
+ Attached,
129
+ Unbound,
130
+ Undecidable,
112
131
  }
113
132
 
114
- pub(super) fn leader_receiver_is_attached(team_state: &serde_json::Value) -> bool {
115
- let Some(receiver) = team_state.get("leader_receiver") else {
116
- return false;
133
+ fn registry_deliverability(workspace: &Path, team_key: &str) -> RegistryDeliverability {
134
+ let Some(dir) = crate::leader::registry::registry_dir() else {
135
+ return RegistryDeliverability::Undecidable;
117
136
  };
118
- let status = receiver
119
- .get("status")
120
- .and_then(serde_json::Value::as_str)
121
- .unwrap_or("");
122
- let pane_id = receiver
123
- .get("pane_id")
124
- .and_then(serde_json::Value::as_str)
125
- .or_else(|| receiver.get("pane").and_then(serde_json::Value::as_str))
126
- .unwrap_or("");
127
- status == "attached" && !pane_id.is_empty() && pane_id != "__team_agent_unbound__"
137
+ if team_key.is_empty() {
138
+ return RegistryDeliverability::Unbound;
139
+ }
140
+ let hash = crate::leader::registry::workspace_hash(workspace);
141
+ let path = dir.join(format!("{hash}__{team_key}.json"));
142
+ match std::fs::read_to_string(&path) {
143
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
144
+ RegistryDeliverability::Unbound
145
+ }
146
+ Err(_) => RegistryDeliverability::Undecidable,
147
+ Ok(text) => {
148
+ match serde_json::from_str::<crate::leader::registry::LeaderRegistryEntry>(&text) {
149
+ Err(_) => RegistryDeliverability::Undecidable,
150
+ Ok(entry) => {
151
+ if entry.status != "attached" {
152
+ return RegistryDeliverability::Unbound;
153
+ }
154
+ if let Some(authorized) = entry
155
+ .channel
156
+ .get("authorized_team_workspace")
157
+ .and_then(serde_json::Value::as_str)
158
+ .filter(|value| !value.is_empty())
159
+ {
160
+ if !same_workspace_path(Path::new(authorized), workspace) {
161
+ return RegistryDeliverability::Unbound;
162
+ }
163
+ }
164
+ RegistryDeliverability::Attached
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ fn same_workspace_path(left: &Path, right: &Path) -> bool {
172
+ let left = std::fs::canonicalize(left).unwrap_or_else(|_| left.to_path_buf());
173
+ let right = std::fs::canonicalize(right).unwrap_or_else(|_| right.to_path_buf());
174
+ left == right
128
175
  }
@@ -1,10 +1,37 @@
1
+ //! ---
2
+ //! purpose: 把源席最新角色文件物化到 dynamic-role-files
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: materialize_latest_role
6
+ //! what: clone/add 用的角色落盘,以磁盘最新文件为准
7
+ //! boundary:
8
+ //! - 不负责 fork 窗口注入
9
+ //! - 不读 provider session 落盘
10
+ //! maturity: wired
11
+ //! ---
1
12
  use std::path::{Path, PathBuf};
2
13
 
3
14
  use crate::lifecycle::LifecycleError;
4
15
  use crate::model::ids::AgentId;
5
16
  use crate::model::yaml::{self, Value};
6
17
 
7
- use super::set_yaml_map_value;
18
+ fn set_yaml_map_value(
19
+ value: &mut Value,
20
+ key: &str,
21
+ next: Value,
22
+ ) -> Result<(), LifecycleError> {
23
+ let Value::Map(pairs) = value else {
24
+ return Err(LifecycleError::Compile(
25
+ "agent entry is not a map".to_string(),
26
+ ));
27
+ };
28
+ if let Some((_, existing)) = pairs.iter_mut().find(|(k, _)| k == key) {
29
+ *existing = next;
30
+ } else {
31
+ pairs.push((key.to_string(), next));
32
+ }
33
+ Ok(())
34
+ }
8
35
 
9
36
  pub(super) struct MaterializedRole {
10
37
  path: PathBuf,
@@ -12,10 +39,17 @@ pub(super) struct MaterializedRole {
12
39
  }
13
40
 
14
41
  impl MaterializedRole {
42
+ /// ---
43
+ /// purpose: 取物化出来的角色文件路径
44
+ /// returns: 落盘路径
45
+ /// ---
15
46
  pub(super) fn path(&self) -> &Path {
16
47
  &self.path
17
48
  }
18
49
 
50
+ /// ---
51
+ /// purpose: 标记该文件由调用方接管,Drop 时不再删除
52
+ /// ---
19
53
  pub(super) fn keep(&mut self) {
20
54
  self.keep = true;
21
55
  }
@@ -29,6 +63,15 @@ impl Drop for MaterializedRole {
29
63
  }
30
64
  }
31
65
 
66
+ /// ---
67
+ /// purpose: 读源席最新角色文件,改名后物化到托管目录
68
+ /// params:
69
+ /// state: 用于找源席的 dynamic_role_file,找不到时退到 team 目录下的同名 md
70
+ /// as_agent_id: 新席位名,写进 front matter 的 name
71
+ /// label: 非空时覆盖 front matter 的 role
72
+ /// returns: 物化结果,未调用 keep 时 Drop 会删掉该文件
73
+ /// errors: 源文件缺失、未声明 name 或声明与源席不符时返回 Compile;目标已存在返回 RequirementUnmet;建目录或写盘失败返回 StatePersist
74
+ /// ---
32
75
  pub(super) fn materialize_latest_role(
33
76
  run_workspace: &Path,
34
77
  team_dir: &Path,
@@ -86,49 +129,6 @@ pub(super) fn materialize_latest_role(
86
129
  Ok(MaterializedRole { path, keep: false })
87
130
  }
88
131
 
89
- pub(super) fn clamp_materialized_role_to_leader(
90
- materialized: &Path,
91
- spec: &Value,
92
- ) -> Result<(), LifecycleError> {
93
- let leader_tools = spec
94
- .get("leader")
95
- .and_then(|leader| leader.get("tools"))
96
- .and_then(Value::as_list)
97
- .map(|items| {
98
- items
99
- .iter()
100
- .filter_map(Value::as_str)
101
- .map(str::to_string)
102
- .collect::<Vec<_>>()
103
- })
104
- .unwrap_or_default();
105
- let (mut meta, body) = crate::compiler::read_front_matter(materialized)
106
- .map_err(|error| LifecycleError::Compile(error.to_string()))?;
107
- let requested = meta
108
- .get("tools")
109
- .and_then(Value::as_list)
110
- .map(|items| {
111
- items
112
- .iter()
113
- .filter_map(Value::as_str)
114
- .map(str::to_string)
115
- .collect::<Vec<_>>()
116
- })
117
- .unwrap_or_default();
118
- let ceiling = crate::model::permissions::expand_tool_strings(&leader_tools)
119
- .into_iter()
120
- .collect::<std::collections::BTreeSet<_>>();
121
- let effective = crate::model::permissions::expand_tool_strings(&requested)
122
- .into_iter()
123
- .filter(|tool| ceiling.contains(tool))
124
- .map(Value::Str)
125
- .collect::<Vec<_>>();
126
- set_yaml_map_value(&mut meta, "tools", Value::List(effective))?;
127
- let rendered = format!("---\n{}---\n\n{}", yaml::dumps(&meta), body);
128
- std::fs::write(materialized, rendered.as_bytes())
129
- .map_err(|error| LifecycleError::StatePersist(error.to_string()))
130
- }
131
-
132
132
  fn resolve_role_source(
133
133
  run_workspace: &Path,
134
134
  team_dir: &Path,
@@ -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,
@@ -207,8 +234,12 @@ pub(super) fn spawn_agents(
207
234
  )?;
208
235
  }
209
236
  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));
237
+ let mut env = inherited_env_with_team_overrides(
238
+ workspace,
239
+ agent_id_raw,
240
+ Some(&mcp_team_id),
241
+ Some(auth_mode_env_value(auth_mode)),
242
+ );
212
243
  apply_profile_launch_env(&mut env, &profile_launch);
213
244
  apply_mcp_auto_approval_env(&mut env, &safety);
214
245
  // Python providers.py:145 + launch/core.py:253 — fresh launch runs the worker
@@ -256,6 +287,31 @@ pub(super) fn spawn_agents(
256
287
  );
257
288
  }
258
289
  }
290
+ // Cursor: role 经 .cursor/rules;MCP 身份必须写进 mcp.json env
291
+ // (不继承父进程 TEAM_AGENT_*)。--workspace 钉物理路径。
292
+ // mcp.json last-writer:同 workspace 第二 CursorAgent 拒绝。
293
+ if matches!(provider, Provider::CursorAgent) {
294
+ refuse_second_cursor_occupant(workspace, agent_id_raw, Some(spec))?;
295
+ apply_cursor_agent_rules_overlay(workspace, agent_id_raw, system_prompt.as_str())?;
296
+ apply_cursor_mcp_overlay(workspace, &mcp_config)?;
297
+ enable_cursor_workspace_mcp(workspace)?;
298
+ apply_cursor_workspace_physical_path(&mut plan.argv, workspace);
299
+ let proxy = apply_cursor_subscription_proxy_env(&mut env);
300
+ let event_log = crate::event_log::EventLog::new(workspace);
301
+ let _ = event_log.write(
302
+ "provider.cursor.proxy_presence",
303
+ serde_json::json!({
304
+ "agent_id": agent_id_raw,
305
+ "https_proxy": proxy.https_proxy,
306
+ "no_proxy": proxy.no_proxy,
307
+ }),
308
+ );
309
+ }
310
+ // Grok 无 --mcp-config;写 <cwd>/.grok/config.toml 让 CLI 读到 team-agent。
311
+ if matches!(provider, Provider::Grok) {
312
+ ensure_grok_login_and_folder_trust(workspace)?;
313
+ apply_grok_mcp_overlay(workspace, &mcp_config)?;
314
+ }
259
315
  let spawn_epoch = u64::try_from(started.len()).unwrap_or(u64::MAX);
260
316
  let spawned_at = spawn_timestamp_for_agent(u32::try_from(spawn_epoch).unwrap_or(u32::MAX));
261
317
  // E6 层1 实证3 + 诊断留痕:落最终 worker argv(spawn 前的真实形态)。
@@ -401,6 +457,10 @@ pub(super) fn spawn_agents(
401
457
  Ok(started)
402
458
  }
403
459
 
460
+ /// ---
461
+ /// purpose: 尽力还原某席位角色文档的路径
462
+ /// returns: state 里有 team_dir 时用它下的 agents 目录,否则退到 workspace 下的当前团队目录
463
+ /// ---
404
464
  /// 0.5.66 bypass 单源 §2.7:审计事件里角色 md 的 spec 来源路径(尽力解析,失败空串)。
405
465
  pub(crate) fn agent_id_spec_source_path(workspace: &Path, agent_id: &str) -> String {
406
466
  let team_dir = crate::state::persist::load_runtime_state(workspace)