@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,28 @@
1
+ //! ---
2
+ //! purpose: worker 进程的环境变量与 argv 占位符处理,以及各 provider 的角色注入落盘
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: inherited_env_with_team_overrides
6
+ //! what: 由当前进程环境按白名单派生 worker spawn 环境,并叠加 team 身份
7
+ //! - name: apply_mcp_auto_approval_env
8
+ //! what: 按 bypass 结论写或清 MCP 自动放行相关变量
9
+ //! - name: apply_copilot_instructions_overlay
10
+ //! what: 把角色提示词写成 per-agent 的 AGENTS.md 并指向它
11
+ //! - name: apply_cursor_agent_rules_overlay
12
+ //! what: 把角色提示词写成 workspace 内 per-agent 的 cursor rules 文件
13
+ //! - name: fill_spawn_placeholders_full
14
+ //! what: 替换 argv 里的 workspace、agent_id 与 team_id 占位符
15
+ //! depends:
16
+ //! - crate::layout::worker_env
17
+ //! - crate::event_log::EventLog
18
+ //! - crate::provider
19
+ //! - std::fs
20
+ //! boundary:
21
+ //! - 不写用户全局配置,角色注入一律落在 workspace 或 runtime 目录下的 per-agent 路径
22
+ //! - 环境里的代理与凭据值只允许记存在与长度,不落值
23
+ //! - 不 spawn 进程,只准备环境与 argv
24
+ //! maturity: wired
25
+ //! ---
1
26
  use std::collections::{BTreeMap, BTreeSet};
2
27
  use std::path::{Path, PathBuf};
3
28
  use std::process::Command;
@@ -14,10 +39,18 @@ use crate::lifecycle::lock::{acquire_agent_lifecycle_lock, LifecycleLockRequest}
14
39
 
15
40
  use super::*;
16
41
 
42
+ /// ---
43
+ /// purpose: 判断 spec 里的该 agent 是否被标为 paused
44
+ /// returns: paused 字段确为真时 true
45
+ /// ---
17
46
  pub(super) fn agent_is_paused(agent: &Value) -> bool {
18
47
  matches!(agent.get("paused"), Some(Value::Bool(true)))
19
48
  }
20
49
 
50
+ /// ---
51
+ /// purpose: 给出 spawn 时间戳
52
+ /// returns: 测试固定时间戳环境变量优先,否则取当前 UTC 时间的微秒精度串
53
+ /// ---
21
54
  pub(crate) fn spawn_timestamp() -> String {
22
55
  match std::env::var("TEAM_AGENT_TEST_FIXED_SPAWNED_AT") {
23
56
  Ok(value) => value,
@@ -27,6 +60,12 @@ pub(crate) fn spawn_timestamp() -> String {
27
60
  }
28
61
  }
29
62
 
63
+ /// ---
64
+ /// purpose: 给出带偏移的 spawn 时间戳,用于同批席位间保持先后次序
65
+ /// params:
66
+ /// offset_micros: 相对基准时间的微秒偏移,为 0 时等同 spawn_timestamp
67
+ /// returns: 固定时间戳可解析时加上偏移,否则原样返回固定值;无固定值时取当前时间
68
+ /// ---
30
69
  pub(super) fn spawn_timestamp_for_agent(offset_micros: u32) -> String {
31
70
  if offset_micros == 0 {
32
71
  return spawn_timestamp();
@@ -44,10 +83,17 @@ pub(super) fn spawn_timestamp_for_agent(offset_micros: u32) -> String {
44
83
  }
45
84
  }
46
85
 
86
+ /// ---
87
+ /// purpose: 替换 argv 里的 workspace 与 agent_id 占位符,不带 team_id
88
+ /// ---
47
89
  pub(crate) fn fill_spawn_placeholders(argv: &mut [String], workspace: &Path, agent_id: &str) {
48
90
  fill_spawn_placeholders_full(argv, workspace, agent_id, None);
49
91
  }
50
92
 
93
+ /// ---
94
+ /// purpose: 给出 auth_mode 写进环境变量时的稳定字符串
95
+ /// returns: subscription、official_api 或 compatible_api
96
+ /// ---
51
97
  /// #229 B-layer worker env contract (`worker_spawn_inherits_parent_process_env_for_proxy_and_ca`):
52
98
  /// every worker `transport.spawn_first/into` MUST receive an env map that is the **complete**
53
99
  /// `team-agent` process environ (so the child sees the user's PATH ordering, HTTP_PROXY /
@@ -59,10 +105,26 @@ pub(crate) fn fill_spawn_placeholders(argv: &mut [String], workspace: &Path, age
59
105
  ///
60
106
  /// `TMUX` / `TMUX_PANE` are stripped because they bind the inherited shell to the **launching**
61
107
  /// tmux pane; leaving them in would point worker-side tmux integrations at the wrong pane.
108
+ pub(crate) fn auth_mode_env_value(mode: AuthMode) -> &'static str {
109
+ match mode {
110
+ AuthMode::Subscription => "subscription",
111
+ AuthMode::OfficialApi => "official_api",
112
+ AuthMode::CompatibleApi => "compatible_api",
113
+ }
114
+ }
115
+
116
+ /// ---
117
+ /// purpose: 由当前进程环境派生 worker 的 spawn 环境
118
+ /// params:
119
+ /// team_id: 作为 owner team id 重新注入给 worker
120
+ /// auth_mode: 写进环境的 auth_mode 字符串
121
+ /// returns: 白名单过滤后的环境,leader 身份类变量与 TMUX 绑定变量已被剥离
122
+ /// ---
62
123
  pub(crate) fn inherited_env_with_team_overrides(
63
124
  workspace: &Path,
64
125
  agent_id: &str,
65
126
  team_id: Option<&str>,
127
+ auth_mode: Option<&str>,
66
128
  ) -> BTreeMap<String, String> {
67
129
  // 0.3.28 Step 3: delegate to `layout::worker_env::worker_spawn_env` which
68
130
  // implements Python's whitelist semantics (`providers.py:130-145`). The
@@ -77,9 +139,22 @@ pub(crate) fn inherited_env_with_team_overrides(
77
139
  // * Strips `COPILOT_DISABLE_TERMINAL_TITLE` (re-injected per-agent by
78
140
  // `apply_copilot_instructions_overlay` based on the WORKER's provider).
79
141
  // * Strips `TMUX` / `TMUX_PANE` (would attach worker to leader's pane).
80
- crate::layout::worker_env::worker_spawn_env(std::env::vars(), workspace, agent_id, team_id)
142
+ crate::layout::worker_env::worker_spawn_env(
143
+ std::env::vars(),
144
+ workspace,
145
+ agent_id,
146
+ team_id,
147
+ auth_mode,
148
+ )
81
149
  }
82
150
 
151
+ /// ---
152
+ /// purpose: 按 bypass 结论设置或清除 MCP 自动放行相关环境变量
153
+ /// params:
154
+ /// env: 就地改写的环境表,六个相关键先被无条件清掉
155
+ /// safety: 只有来源为 leader 进程且确为继承而来时才写入放行变量
156
+ /// returns: 不满足条件时只写一个关闭标记
157
+ /// ---
83
158
  pub(crate) fn apply_mcp_auto_approval_env(
84
159
  env: &mut BTreeMap<String, String>,
85
160
  safety: &DangerousApproval,
@@ -128,6 +203,14 @@ pub(crate) fn apply_mcp_auto_approval_env(
128
203
  }
129
204
  }
130
205
 
206
+ /// ---
207
+ /// purpose: 把角色提示词写进 per-agent 的 copilot 指令目录并指向它
208
+ /// params:
209
+ /// system_prompt: 写入 AGENTS.md 的正文
210
+ /// env: 就地写入指令目录与禁用终端标题的变量
211
+ /// returns: 成功返回空值
212
+ /// errors: 建目录或写文件失败时返回 StatePersist,不静默吞
213
+ /// ---
131
214
  /// BUG / B2 灵魂件 + C-1-2 + C-6-1 cr verdict — Copilot per-worker AGENTS.md
132
215
  /// 写入 + `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` 注入。
133
216
  ///
@@ -172,6 +255,50 @@ pub(crate) fn apply_copilot_instructions_overlay(
172
255
  Ok(())
173
256
  }
174
257
 
258
+ /// ---
259
+ /// purpose: 把角色提示词写成 workspace 内 per-agent 的 cursor rules 文件
260
+ /// params:
261
+ /// system_prompt: 写入 rules 正文,文件头声明 alwaysApply
262
+ /// returns: 成功返回空值
263
+ /// errors: 建目录或写文件失败时返回 StatePersist
264
+ /// ---
265
+ /// 0.5.67 Cursor append-system-prompt 方案 1 变体(冒烟 PASS, see
266
+ /// .team/artifacts/0.5.67-cursor-rules-smoke.md)— Cursor `agent` CLI 无
267
+ /// append/rule CLI flag, 只从 workspace 静态 rules 文件读:
268
+ /// `.cursor/rules/**/*.mdc` (alwaysApply: true) / `AGENTS.md` / `.cursorrules`。
269
+ ///
270
+ /// 这里在 launch 路径写 `<workspace>/.cursor/rules/team-agent-role-<agent_id>.mdc`
271
+ /// 并把 role(= compile_worker_system_prompt 输出)放进去, argv 只带
272
+ /// `--workspace <workspace>`(cursor_agent.rs)。cursor CLI 读取该目录即加载 role。
273
+ ///
274
+ /// 目录布局为 workspace 内 `.cursor/rules/`(Cursor 唯一 rules 通道; 无法像 copilot
275
+ /// 那样放 `.team/runtime/` —— 那是 Cursor 不读的路径)。文件名含 `<agent_id>`
276
+ /// 保证 per-agent 隔离, 不互相污染。
277
+ pub(crate) fn apply_cursor_agent_rules_overlay(
278
+ workspace: &Path,
279
+ agent_id: &str,
280
+ system_prompt: &str,
281
+ ) -> Result<(), LifecycleError> {
282
+ let dir = workspace.join(".cursor").join("rules");
283
+ std::fs::create_dir_all(&dir)
284
+ .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", dir.display())))?;
285
+ let rules = dir.join(format!("team-agent-role-{agent_id}.mdc"));
286
+ let content = format!(
287
+ "---\ndescription: Team Agent role injection (auto-generated)\nalwaysApply: true\n---\n\n{system_prompt}\n"
288
+ );
289
+ std::fs::write(&rules, content.as_bytes())
290
+ .map_err(|e| LifecycleError::StatePersist(format!("{}: {e}", rules.display())))?;
291
+ Ok(())
292
+ }
293
+
294
+ /// ---
295
+ /// purpose: 扫出 copilot 侧残留的 MCP server,对非本框架的逐个追加禁用参数
296
+ /// params:
297
+ /// argv: 就地追加禁用参数
298
+ /// log_dir: 残留清单落盘目录
299
+ /// returns: 成功返回空值;copilot 命令不可用或退出码非零时只记录不阻断
300
+ /// errors: 写残留清单文件失败时返回 StatePersist
301
+ /// ---
175
302
  /// C-3-2/C-3-3 cr verdict v2 — Copilot spawn 前调 `copilot mcp list` 扫用户全局
176
303
  /// `~/.copilot/mcp-config.json` 与 workspace `.mcp.json` 的 MCP 残留;对每个非
177
304
  /// `team_orchestrator` server 追加 `--disable-mcp-server <name>`(main-help:72-73)
@@ -246,6 +373,12 @@ pub(super) fn apply_copilot_mcp_residual_disables(
246
373
  Ok(())
247
374
  }
248
375
 
376
+ /// ---
377
+ /// purpose: 从 copilot mcp list 输出里解析 server 名集合
378
+ /// params:
379
+ /// text: 命令原始输出
380
+ /// returns: server 名列表;遇到无配置的提示行立即返回空;只在 servers 段内取缩进行首 token
381
+ /// ---
249
382
  /// 解析 `copilot mcp list` 输出取 server 名集合(te 真机实证 v2,1.0.59 形态):
250
383
  /// ```text
251
384
  /// User servers:
@@ -318,6 +451,65 @@ pub(super) fn parse_copilot_mcp_list_server_names(text: &str) -> Vec<String> {
318
451
  out
319
452
  }
320
453
 
454
+ /// Subscription seats inherit parent env, but that is implicit. Cursor cloud
455
+ /// on this host is blocked without a proxy; `profile_launch` copies these
456
+ /// keys only on the compatible-api path. Copy them explicitly here so a
457
+ /// subscription cursor seat sees the same keys. Values may contain
458
+ /// credentials — callers must only log presence/length, never the value.
459
+ const CURSOR_PROXY_KEYS: &[&str] = &[
460
+ "HTTPS_PROXY",
461
+ "HTTP_PROXY",
462
+ "ALL_PROXY",
463
+ "NO_PROXY",
464
+ "https_proxy",
465
+ "http_proxy",
466
+ "all_proxy",
467
+ "no_proxy",
468
+ ];
469
+
470
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
471
+ pub struct CursorProxyPresence {
472
+ pub https_proxy: bool,
473
+ pub no_proxy: bool,
474
+ }
475
+
476
+ /// ---
477
+ /// purpose: 给订阅制 cursor 席位显式补上父进程里的代理变量
478
+ /// params:
479
+ /// env: 就地补键,已有同名键不覆盖
480
+ /// returns: 记录是否存在 https 代理与 no_proxy,只记存在与否不记值
481
+ /// ---
482
+ pub fn apply_cursor_subscription_proxy_env(
483
+ env: &mut BTreeMap<String, String>,
484
+ ) -> CursorProxyPresence {
485
+ let mut https_proxy = false;
486
+ let mut no_proxy = false;
487
+ for key in CURSOR_PROXY_KEYS {
488
+ let Ok(value) = std::env::var(key) else {
489
+ continue;
490
+ };
491
+ if value.is_empty() {
492
+ continue;
493
+ }
494
+ if key.eq_ignore_ascii_case("HTTPS_PROXY") {
495
+ https_proxy = true;
496
+ }
497
+ if key.eq_ignore_ascii_case("NO_PROXY") {
498
+ no_proxy = true;
499
+ }
500
+ env.entry((*key).to_string()).or_insert(value);
501
+ }
502
+ CursorProxyPresence {
503
+ https_proxy,
504
+ no_proxy,
505
+ }
506
+ }
507
+
508
+ /// ---
509
+ /// purpose: 把 profile 推出的环境清除与覆盖套用到 spawn 环境上
510
+ /// params:
511
+ /// env: 先按 env_unset 删键,再并入 env_overlay
512
+ /// ---
321
513
  pub(crate) fn apply_profile_launch_env(
322
514
  env: &mut BTreeMap<String, String>,
323
515
  profile_launch: &crate::provider::ProviderProfileLaunch,
@@ -328,6 +520,11 @@ pub(crate) fn apply_profile_launch_env(
328
520
  env.extend(profile_launch.env_overlay.clone());
329
521
  }
330
522
 
523
+ /// ---
524
+ /// purpose: 把已 spawn 席位的 profile 相关信息写进该席位的 state 节点
525
+ /// params:
526
+ /// state: 就地写入待定 session id、projects 根与 profile_launch 摘要
527
+ /// ---
331
528
  pub(super) fn persist_started_agent_plan_state(
332
529
  state: &mut serde_json::Map<String, serde_json::Value>,
333
530
  started_agent: &StartedAgent,
@@ -362,6 +559,12 @@ pub(super) fn persist_started_agent_plan_state(
362
559
  }
363
560
  }
364
561
 
562
+ /// ---
563
+ /// purpose: 把命令计划与 profile 推出的信息写进该席位的 state 节点
564
+ /// params:
565
+ /// plan: 命令计划,其 projects 根优先于 profile 给出的
566
+ /// profile_launch: profile 推出的启动参数
567
+ /// ---
365
568
  pub(crate) fn persist_command_plan_state(
366
569
  state: &mut serde_json::Map<String, serde_json::Value>,
367
570
  plan: &crate::provider::CommandPlan,
@@ -399,6 +602,10 @@ pub(crate) fn persist_command_plan_state(
399
602
  }
400
603
  }
401
604
 
605
+ /// ---
606
+ /// purpose: 判断名字是否是合法的 POSIX shell 标识符
607
+ /// returns: 首字符为字母或下划线且其余为字母数字下划线时为 true
608
+ /// ---
402
609
  pub(super) fn is_posix_shell_identifier(name: &str) -> bool {
403
610
  let mut chars = name.chars();
404
611
  match chars.next() {
@@ -408,6 +615,12 @@ pub(super) fn is_posix_shell_identifier(name: &str) -> bool {
408
615
  chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
409
616
  }
410
617
 
618
+ /// ---
619
+ /// purpose: 替换 argv 里的 workspace、agent_id 与 team_id 占位符
620
+ /// params:
621
+ /// argv: 就地改写;整串等于占位符时整体替换,否则按子串替换
622
+ /// team_id: None 时按空串替换
623
+ /// ---
411
624
  /// Same as [`fill_spawn_placeholders`] plus `{team_id}` substitution everywhere it
412
625
  /// appears as a SUBSTRING (the MCP config encodes it as `mcp_servers.team_orchestrator
413
626
  /// .env.TEAM_AGENT_OWNER_TEAM_ID="{team_id}"`, embedded inside `-c key=value` strings,
@@ -1,3 +1,13 @@
1
+ //! ---
2
+ //! purpose: 冷启、add/clone/fork 入口拼装
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: fork_agent
6
+ //! what: 成员分身入口,grok 走窗口内命令
7
+ //! boundary:
8
+ //! - 不读 provider 会话落盘
9
+ //! maturity: wired
10
+ //! ---
1
11
  //!
2
12
  //! lifecycle::launch —— 冷启 / quick-start / 危险审批探测 + add/fork / plan 起步与推进。
3
13
 
@@ -18,6 +28,16 @@ use super::*;
18
28
 
19
29
  // ── lifecycle::launch —— 冷启 / quick-start / 危险审批探测 ──────────────────
20
30
 
31
+ /// ---
32
+ /// purpose: 冷启全队的默认入口,按 spec 所在目录绑定 per-team tmux socket 后转 launch_with_transport
33
+ /// params:
34
+ /// spec_path: 已编译 spec 的路径,其父目录即 team 目录
35
+ /// dry_run: 只出路由与权限,不 spawn 也不落盘
36
+ /// auto_approve: 当前为 no-op
37
+ /// skip_profile_smoke: 当前为 no-op
38
+ /// returns: LaunchReport
39
+ /// errors: 透传 launch_with_transport 的错误
40
+ /// ---
21
41
  ///
22
42
  /// `launch(spec_path, dry_run, auto_approve, skip_profile_smoke)`(`launch/core.py:29`)。
23
43
  /// 冷启全队:路由 tasks、resolve 权限/危险审批门、session 冲突检查(冲突 →
@@ -42,6 +62,13 @@ pub fn launch(
42
62
  )
43
63
  }
44
64
 
65
+ /// ---
66
+ /// purpose: 由 spec 路径推出 workspace,再转 launch_with_transport_in_workspace
67
+ /// params:
68
+ /// transport: 已绑定 team socket 的 transport
69
+ /// returns: LaunchReport
70
+ /// errors: 透传 launch_with_transport_in_workspace 的错误
71
+ /// ---
45
72
  pub(crate) fn launch_with_transport(
46
73
  spec_path: &Path,
47
74
  dry_run: bool,
@@ -61,6 +88,17 @@ pub(crate) fn launch_with_transport(
61
88
  )
62
89
  }
63
90
 
91
+ /// ---
92
+ /// purpose: 冷启全队的实体实现,编译 spec、检查 session 冲突、按序 spawn、落 state、出报告
93
+ /// params:
94
+ /// workspace: run workspace 根,事件与 state 都落在这里
95
+ /// spec_path: 已编译 spec 的路径
96
+ /// dry_run: 为真时不 spawn 不落盘,started 为空
97
+ /// auto_approve: 未使用
98
+ /// skip_profile_smoke: 未使用
99
+ /// returns: LaunchReport,其中 leader_receiver_attached 恒 false、session_capture_incomplete_agents 恒空
100
+ /// errors: spec 不存在或解析失败返回 Compile,tmux session 已存在返回 SessionConflict,spawn 与落 state 的错误透传
101
+ /// ---
64
102
  pub fn launch_with_transport_in_workspace(
65
103
  workspace: &Path,
66
104
  spec_path: &Path,
@@ -71,8 +109,8 @@ pub fn launch_with_transport_in_workspace(
71
109
  ) -> Result<LaunchReport, LifecycleError> {
72
110
  let _ = skip_profile_smoke;
73
111
  let _ = auto_approve; // 0.5.66: bypass 单源后无 runtime.dangerous_auto_approve,`--yes` 为 no-op。
74
- // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
75
- // timer with monotonic `elapsed_ms` for latency triage.
112
+ // 0.5.38 (`.team/artifacts/startup-latency-locate.md` §5): launch.phase
113
+ // timer with monotonic `elapsed_ms` for latency triage.
76
114
  let phase_timer = crate::lifecycle::restart::RestartPhaseTimer::start();
77
115
  if !spec_path.exists() {
78
116
  return Err(LifecycleError::Compile(format!(
@@ -107,13 +145,7 @@ pub fn launch_with_transport_in_workspace(
107
145
  Vec::new()
108
146
  } else {
109
147
  phase_timer.emit(workspace, "launch.phase", "spawn_all");
110
- let started = spawn_agents(
111
- workspace,
112
- spec_path,
113
- &spec,
114
- &session_name,
115
- transport,
116
- )?;
148
+ let started = spawn_agents(workspace, spec_path, &spec, &session_name, transport)?;
117
149
  persist_spawn_agent_state(
118
150
  workspace,
119
151
  spec_path,
@@ -224,8 +256,8 @@ mod plan;
224
256
  pub(crate) use plan::{handle_report_result, start_plan};
225
257
 
226
258
  pub mod spawn;
227
- pub(super) use spawn::*;
228
259
  pub(crate) use spawn::agent_id_spec_source_path;
260
+ pub(super) use spawn::*;
229
261
 
230
262
  mod layout;
231
263
  pub(super) use layout::*;
@@ -260,20 +292,38 @@ pub(crate) use agent_state::{
260
292
  persist_effective_approval_policy_from_agent,
261
293
  };
262
294
 
295
+ mod grok_per_seat;
296
+ pub(crate) use grok_per_seat::{
297
+ is_per_seat_env_key, non_per_seat_env_in_tables, per_seat_keys_in_toml,
298
+ strip_per_seat_keys_from_toml,
299
+ };
300
+
263
301
  mod mcp_config;
302
+ pub use mcp_config::apply_grok_mcp_overlay;
264
303
  pub(super) use mcp_config::*;
265
304
  pub(crate) use mcp_config::{
266
- point_native_mcp_config_at_file, resolve_mcp_config, write_worker_mcp_config,
305
+ ensure_grok_login_and_folder_trust, grok_shared_cwd_error, point_native_mcp_config_at_file,
306
+ reconcile_grok_toml_per_seat_keys, resolve_mcp_config, write_worker_mcp_config,
267
307
  write_worker_mcp_config_for_provider,
268
308
  };
269
309
 
310
+ mod cursor_mcp;
311
+ pub use cursor_mcp::{
312
+ apply_cursor_mcp_overlay, apply_cursor_workspace_physical_path, cursor_mcp_enable_argv,
313
+ enable_cursor_workspace_mcp, physical_workspace_path, refuse_second_cursor_occupant,
314
+ };
315
+
316
+ mod cursor_create_chat;
317
+
270
318
  mod worker_env;
271
319
  pub(super) use worker_env::*;
272
320
  pub(crate) use worker_env::{
273
- apply_copilot_instructions_overlay, apply_mcp_auto_approval_env, apply_profile_launch_env,
321
+ apply_copilot_instructions_overlay, apply_cursor_agent_rules_overlay,
322
+ apply_mcp_auto_approval_env, apply_profile_launch_env, auth_mode_env_value,
274
323
  fill_spawn_placeholders, fill_spawn_placeholders_full, inherited_env_with_team_overrides,
275
324
  persist_command_plan_state, spawn_timestamp,
276
325
  };
326
+ pub use worker_env::{apply_cursor_subscription_proxy_env, CursorProxyPresence};
277
327
 
278
328
  mod identity;
279
329
  pub(super) use identity::*;
@@ -300,7 +350,7 @@ pub(crate) use quick_start_transport::{
300
350
  };
301
351
 
302
352
  pub mod readiness;
303
- pub(crate) use readiness::launched_team_receiver_is_attached;
353
+ pub use readiness::launched_team_receiver_is_attached;
304
354
  pub(super) use readiness::*;
305
355
 
306
356
  mod add_agent;
@@ -313,19 +363,12 @@ pub(crate) use add_agent_state::inject_agent_into_spec;
313
363
  pub(super) use add_agent_state::*;
314
364
 
315
365
  mod fork_agent;
316
- pub use fork_agent::fork_agent_with_transport;
317
366
  pub(super) use fork_agent::*;
367
+ pub use fork_agent::{fork_agent_with_transport, in_window_fork, in_window_fork_command};
318
368
 
319
369
  mod fork_entry;
320
370
  pub use fork_entry::fork_agent;
321
371
 
322
- mod fork_state;
323
- pub(super) use fork_state::*;
324
-
325
- mod fork_finalize;
326
- pub(super) use fork_finalize::*;
327
- pub(crate) use fork_finalize::{finalize_pending_fork_capture, ContextForkFinalized};
328
-
329
372
  mod role_source;
330
373
  pub(super) use role_source::*;
331
374
 
@@ -337,9 +380,7 @@ pub(super) use ownership::*;
337
380
  pub(crate) use ownership::{ensure_owner_allowed, ensure_owner_allowed_for_state, state_path};
338
381
 
339
382
  pub mod spec_state;
340
- pub(crate) use spec_state::{
341
- override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
342
- };
383
+ pub use spec_state::worker_session_name_pub;
343
384
  pub(crate) use spec_state::{
344
385
  effective_runtime_config_for_worker_spawn, effective_runtime_config_for_worker_spawn_json,
345
386
  };
@@ -350,7 +391,9 @@ use spec_state::{
350
391
  spec_agent_values, spec_agents, spec_default_assignee, spec_routes, spec_session_name,
351
392
  spec_tasks_json, team_workspace, yaml_value_to_json,
352
393
  };
353
- pub use spec_state::worker_session_name_pub;
394
+ pub(crate) use spec_state::{
395
+ override_spec_session_name, override_spec_workspace, spec_agent_id_set, write_spec_atomic,
396
+ };
354
397
 
355
398
  #[cfg(test)]
356
399
  mod tests {
@@ -361,10 +404,8 @@ mod tests {
361
404
  // leader argv 带 bypass flag + 无角色声明 true → 写 warning 事件。
362
405
  #[test]
363
406
  fn test_leader_flag_present_but_no_role_true_writes_warning_event() {
364
- let ws = std::env::temp_dir().join(format!(
365
- "ta-bypass-warning-test-{}",
366
- std::process::id()
367
- ));
407
+ let ws =
408
+ std::env::temp_dir().join(format!("ta-bypass-warning-test-{}", std::process::id()));
368
409
  let _ = std::fs::remove_dir_all(&ws);
369
410
  std::fs::create_dir_all(&ws).unwrap();
370
411
  let spec = yaml::loads(
@@ -415,4 +456,33 @@ mod tests {
415
456
  );
416
457
  let _ = std::fs::remove_dir_all(&ws);
417
458
  }
459
+
460
+ #[test]
461
+ fn team_agent_force_flag_does_not_look_like_bypass() {
462
+ let ws =
463
+ std::env::temp_dir().join(format!("ta-bypass-force-not-bypass-{}", std::process::id()));
464
+ let _ = std::fs::remove_dir_all(&ws);
465
+ std::fs::create_dir_all(&ws).unwrap();
466
+ let spec = yaml::loads(
467
+ "version: 1\nteam: {}\nleader: {}\nagents:\n - id: a\n role: r\n provider: fake\n dangerously_skip_permissions: false\n",
468
+ )
469
+ .unwrap();
470
+ let argv = vec![
471
+ "team-agent".to_string(),
472
+ "remove-agent".to_string(),
473
+ "x".to_string(),
474
+ "--confirm".to_string(),
475
+ "--force".to_string(),
476
+ ];
477
+ emit_dangerous_flag_no_role_declared_warning_with_argv(&ws, &spec, &argv);
478
+ let events = crate::event_log::EventLog::new(&ws).tail(0).unwrap();
479
+ assert!(
480
+ !events.iter().any(|event| {
481
+ event.get("event").and_then(serde_json::Value::as_str)
482
+ == Some("dangerously_flag_present_but_no_role_declared")
483
+ }),
484
+ "team-agent --force must not emit bypass warning; events={events:?}"
485
+ );
486
+ let _ = std::fs::remove_dir_all(&ws);
487
+ }
418
488
  }
@@ -1,3 +1,22 @@
1
+ //! ---
2
+ //! purpose: workspace 级 agent-lifecycle 互斥锁,让同一 workspace 的席位增删起停串行
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: acquire_agent_lifecycle_lock
6
+ //! what: 轮询抢锁,成功返回持有到 Drop 的 guard,超时返回 LifecycleLockTimeout
7
+ //! - name: agent_lifecycle_lock_path
8
+ //! what: 给出该 workspace 的锁文件路径
9
+ //! depends:
10
+ //! - crate::platform::file_lock
11
+ //! - crate::event_log::EventLog
12
+ //! - crate::model::paths
13
+ //! boundary:
14
+ //! - 不锁 pane 输入通道,那是 pane_input_lock
15
+ //! - 不做跨 workspace 互斥,锁作用域就是一个 workspace
16
+ //! - 拿不到锁只超时报错,不强抢也不清理他人锁文件
17
+ //! - 锁文件内容只作观测用,不作为持有权判据
18
+ //! maturity: wired
19
+ //! ---
1
20
  use std::io::{Seek, Write};
2
21
  use std::path::{Path, PathBuf};
3
22
  use std::time::{Duration, Instant};
@@ -17,10 +36,16 @@ pub(crate) struct LifecycleLockRequest<'a> {
17
36
  }
18
37
 
19
38
  pub(crate) struct LifecycleLockGuard {
20
- #[allow(dead_code)]
21
39
  file: std::fs::File,
22
40
  }
23
41
 
42
+ /// ---
43
+ /// purpose: 抢 workspace 级 agent-lifecycle 锁,默认 30 秒超时、5 秒起写 lock_held_long 事件
44
+ /// params:
45
+ /// request: 携带 workspace、操作名与可选 team/agent,仅用于锁元数据与事件
46
+ /// returns: 持有锁的 guard,Drop 时解锁
47
+ /// errors: 建目录或打开锁文件失败返回 StatePersist,等待超时返回 LifecycleLockTimeout
48
+ /// ---
24
49
  pub(crate) fn acquire_agent_lifecycle_lock(
25
50
  request: LifecycleLockRequest<'_>,
26
51
  ) -> Result<LifecycleLockGuard, LifecycleError> {
@@ -35,6 +60,14 @@ pub(crate) fn acquire_agent_lifecycle_lock(
35
60
  )
36
61
  }
37
62
 
63
+ /// ---
64
+ /// purpose: 以显式超时与告警阈值抢同一把锁,仅测试构建可见
65
+ /// params:
66
+ /// timeout: 放弃等待的上限
67
+ /// held_long: 超过它写一次 lock_held_long 事件
68
+ /// returns: 持有锁的 guard
69
+ /// errors: 同 acquire_agent_lifecycle_lock
70
+ /// ---
38
71
  #[cfg(test)]
39
72
  pub(crate) fn acquire_agent_lifecycle_lock_for_test(
40
73
  request: LifecycleLockRequest<'_>,
@@ -55,6 +88,10 @@ pub(crate) struct LifecycleLockDeadlineOverrideGuard {
55
88
  previous: Option<(Duration, Duration)>,
56
89
  }
57
90
 
91
+ /// ---
92
+ /// purpose: 线程内改写默认锁超时与告警阈值,仅测试构建可见
93
+ /// returns: guard,Drop 时恢复上一次的取值
94
+ /// ---
58
95
  #[cfg(test)]
59
96
  pub(crate) fn override_agent_lifecycle_lock_deadlines_for_test(
60
97
  timeout: Duration,
@@ -145,6 +182,10 @@ fn acquire_agent_lifecycle_lock_with_deadlines(
145
182
  }
146
183
  }
147
184
 
185
+ /// ---
186
+ /// purpose: 给出该 workspace 的 agent-lifecycle 锁文件路径
187
+ /// returns: runtime 目录下的 agent-lifecycle.lock
188
+ /// ---
148
189
  pub(crate) fn agent_lifecycle_lock_path(workspace: &Path) -> PathBuf {
149
190
  crate::model::paths::runtime_dir(workspace).join(format!("{AGENT_LIFECYCLE_LOCK_NAME}.lock"))
150
191
  }
@@ -1,3 +1,13 @@
1
+ //! ---
2
+ //! purpose: team 进程级生命周期编排(起停加减 clone/fork)
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: launch
6
+ //! what: 冷启与成员生命周期入口
7
+ //! boundary:
8
+ //! - 不拥有物理注入,只调用 transport
9
+ //! maturity: wired
10
+ //! ---
1
11
  //!
2
12
  //! step 13 · lifecycle — team 进程级生命周期编排器(ROUND-0.5 骨架)。
3
13
  //!
@@ -46,6 +56,7 @@ pub mod display;
46
56
  pub mod helpers;
47
57
  pub mod launch;
48
58
  pub(crate) mod lock;
59
+ pub(crate) mod pane_input_lock;
49
60
  pub(crate) mod profile_launch;
50
61
  pub(crate) mod profile_smoke;
51
62
  pub mod restart;