@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
@@ -18,6 +18,15 @@ fn e23_fallback_pane_has_single_shared_noisy_surface() {
18
18
  .unwrap(),
19
19
  "primary-ok guard must run before any fallback audit/inject attempt"
20
20
  );
21
+ let already = leader_receiver
22
+ .split("fn message_already_delivered")
23
+ .nth(1)
24
+ .unwrap_or("");
25
+ assert!(
26
+ already.contains("submitted_pending_acceptance")
27
+ && already.contains("injected_awaiting_receipt"),
28
+ "fallback already-delivered set must include the live post-submit statuses, not only the dead string submitted"
29
+ );
21
30
  }
22
31
 
23
32
  #[test]
@@ -99,6 +99,72 @@ fn attached_leader_mailbox_is_rechecked_by_the_normal_delivery_tick() {
99
99
  assert!(case.events().contains("leader_receiver.mailbox_requeued"));
100
100
  }
101
101
 
102
+ #[test]
103
+ fn claimed_leader_delivery_revalidates_a_stale_unattached_snapshot() {
104
+ let workspace = tmp_ws("leader-inject-acceptance-stale-attach");
105
+ let rollout = workspace.join("leader.jsonl");
106
+ std::fs::write(&rollout, "").unwrap();
107
+ let current_state = serde_json::json!({
108
+ "active_team_key": "acceptance-team",
109
+ "session_name": "team-acceptance",
110
+ "leader_receiver": {
111
+ "pane_id": "%leader",
112
+ "status": "attached",
113
+ "provider": "claude",
114
+ "rollout_path": rollout,
115
+ },
116
+ "teams": {
117
+ "acceptance-team": {
118
+ "session_name": "team-acceptance",
119
+ }
120
+ }
121
+ });
122
+ crate::state::persist::save_runtime_state(&workspace, &current_state).unwrap();
123
+ let store = MessageStore::open(&workspace).unwrap();
124
+ let event_log = EventLog::new(&workspace);
125
+ let message_id = store
126
+ .create_message(
127
+ None,
128
+ "worker",
129
+ "leader",
130
+ "stale attach canary",
131
+ None,
132
+ false,
133
+ Some("acceptance-team"),
134
+ )
135
+ .unwrap();
136
+ let stale_state = serde_json::json!({
137
+ "active_team_key": "acceptance-team",
138
+ "teams": {
139
+ "acceptance-team": {
140
+ "session_name": "team-acceptance"
141
+ }
142
+ }
143
+ });
144
+ let transport = RecordingTransport::new("");
145
+
146
+ let outcome = deliver_pending_message(
147
+ &workspace,
148
+ &store,
149
+ &transport,
150
+ &message_id,
151
+ &event_log,
152
+ &stale_state,
153
+ )
154
+ .expect("delivery from stale coordinator snapshot");
155
+
156
+ assert_ne!(
157
+ outcome.reason,
158
+ Some(DeliveryRefusal::LeaderNotAttached),
159
+ "a claim made after attach must revalidate durable state before writing leader_not_attached"
160
+ );
161
+ assert_ne!(
162
+ outcome.message_status.0, "failed",
163
+ "a stale pre-attach snapshot must not strand the requeued row"
164
+ );
165
+ assert_eq!(transport.inject_count(), 1);
166
+ }
167
+
102
168
  #[test]
103
169
  fn leader_submit_without_receipt_source_cannot_park_forever_without_a_completion_writer() {
104
170
  let case = Case::new_without_rollout("missing-receipt-source");
@@ -296,6 +296,7 @@ impl Drop for EnvGuardMsg {
296
296
  }
297
297
 
298
298
  mod basic;
299
+ mod dup_inject;
299
300
  mod e23;
300
301
  mod leader_channel;
301
302
  mod leader_inject_acceptance;
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
5
5
 
6
6
  use crate::model::enums::Provider;
7
7
  use crate::model::ids::{LeaderSessionUuid, OwnerEpoch, TaskId, TeamKey};
8
- use crate::transport::PaneId;
8
+ use crate::transport::{PaneId, TurnVerification};
9
9
 
10
10
  use super::helpers::MessageStatusShadow;
11
11
 
@@ -308,6 +308,9 @@ pub struct DeliveryOutcome {
308
308
  pub reason: Option<DeliveryRefusal>,
309
309
  /// fallback / broadcast / fanout 等通道标注 (`channel` 字段)。
310
310
  pub channel: Option<String>,
311
+ /// 入箱 vs 开跑。None = 本路径没观测(JSON 必须落成 not_yet_observed,不得报开跑)。
312
+ /// Gap42:只是 metadata,不是投递闸门。
313
+ pub turn_verification: Option<TurnVerification>,
311
314
  }
312
315
 
313
316
  /// trust retry payload (card §45;`delivery.py:273` scheduled_events.payload_json 的 typed 视图)。
@@ -21,6 +21,14 @@ pub enum Provider {
21
21
  /// AGENTS.md + `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` env(B2 灵魂件降级,§C2)。
22
22
  Copilot,
23
23
  GeminiCli,
24
+ /// Grok CLI (0.5.67 provider-adapter). Append system prompt via `--rules`,
25
+ /// bypass `--always-approve`, session `--session-id`.
26
+ Grok,
27
+ /// Cursor `agent` CLI (0.5.67 provider-adapter). Append system prompt via
28
+ /// workspace `.cursor/rules/*.mdc` (方案 1 变体), bypass `--force`.
29
+ CursorAgent,
30
+ /// Pi coding agent CLI. Models are selected from its live catalog.
31
+ Pi,
24
32
  Fake,
25
33
  }
26
34
 
@@ -33,7 +41,8 @@ pub enum Provider {
33
41
  /// Provider support:
34
42
  /// - claude / claude_code: low|medium|high|xhigh|max → `--effort <level>`
35
43
  /// - codex: low|medium|high|xhigh (NOT max) → `-c model_reasoning_effort=<level>`
36
- /// - copilot / gemini_cli / fake: unsupported — warning event, no flag
44
+ /// - grok: low|medium|high|xhigh (NOT max) → `--effort <level>`
45
+ /// - copilot / gemini_cli / cursor_agent / fake: unsupported — warning event, no flag
37
46
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38
47
  pub enum ProviderEffort {
39
48
  #[serde(rename = "low")]
@@ -78,13 +87,15 @@ impl ProviderEffort {
78
87
  }
79
88
 
80
89
  /// True when the given provider supports this effort level. `max` is
81
- /// Claude-only; other levels are supported by Claude and Codex, ignored
82
- /// by Copilot/Gemini/Fake (warning emitted at runtime).
90
+ /// Claude-only; other levels are supported by Claude, Codex, and Grok.
91
+ /// Copilot/Gemini/CursorAgent/Fake ignore the field (warning at runtime).
83
92
  pub fn is_supported_by(self, provider: Provider) -> bool {
84
93
  match provider {
85
- Provider::Claude | Provider::ClaudeCode => true,
86
- Provider::Codex => !self.is_claude_only(),
87
- Provider::Copilot | Provider::GeminiCli | Provider::Fake => false,
94
+ Provider::Claude | Provider::ClaudeCode | Provider::Pi => true,
95
+ Provider::Codex | Provider::Grok => !self.is_claude_only(),
96
+ Provider::Copilot | Provider::GeminiCli | Provider::CursorAgent | Provider::Fake => {
97
+ false
98
+ }
88
99
  }
89
100
  }
90
101
  }
@@ -189,6 +189,9 @@ fn provider_enforcement(provider: Provider, tool: Tool) -> Enforcement {
189
189
  FsRead | FsWrite | FsList | ExecuteBash | GitDiff | ProviderBuiltin => Hard,
190
190
  },
191
191
  Provider::Fake => Hard,
192
+ // 0.5.67: grok/cursor_agent 无 deny-kind 概念 (同 codex 精神), 诚实
193
+ // prompt_only, 不替 provider 决。
194
+ Provider::Grok | Provider::CursorAgent | Provider::Pi => PromptOnly,
192
195
  // Copilot(C-2-1 cr verdict):execute_bash/fs_write/network/mcp_team = hard,
193
196
  // fs_read/fs_list/git_diff/provider_builtin = prompt_only(诚实:copilot 无
194
197
  // 对应 deny kind,framework 不替决,留给 provider prompt 控制;MUST-NOT-13)。
@@ -1,10 +1,31 @@
1
+ //! ---
2
+ //! purpose: 为运行时提供有界的进程输出读取与前台进程组探测
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: bounded_command_output_with_probe
6
+ //! what: 在固定超时内执行命令并读取 stdout,超时后终止子进程
7
+ //! - name: pane_foreground_and_root_pgrp
8
+ //! what: 读取 pane PID 的前台与根进程组 ID,无法判定时返回 None
9
+ //! depends:
10
+ //! - std
11
+ //! boundary:
12
+ //! - 只负责有界探测、临时 stdout 文件的独占创建与清理
13
+ //! - 不决定 shutdown 状态,不重试探测,不修改调用方行为
14
+ //! arch:
15
+ //! allowed_dependencies:
16
+ //! - std
17
+ //! maturity: wired
18
+ //! ---
19
+
1
20
  use std::cell::RefCell;
2
21
  use std::fs::OpenOptions;
3
22
  use std::io::{self, Read};
4
23
  use std::process::{Command, ExitStatus, Stdio};
24
+ use std::sync::atomic::{AtomicU64, Ordering};
5
25
  use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
6
26
 
7
27
  const DEFAULT_TIMEOUT: Duration = Duration::from_millis(900);
28
+ static NEXT_TEMP_OUTPUT_ID: AtomicU64 = AtomicU64::new(0);
8
29
 
9
30
  thread_local! {
10
31
  static PROBE_TIMEOUT: RefCell<Option<ProbeTimeout>> = const { RefCell::new(None) };
@@ -23,19 +44,31 @@ pub(crate) struct BoundedCommandOutput {
23
44
  pub(crate) stdout: Vec<u8>,
24
45
  }
25
46
 
47
+ /// ---
48
+ /// purpose: 清除当前线程记录的探测超时
49
+ /// ---
26
50
  pub(crate) fn clear_probe_timeout() {
27
51
  PROBE_TIMEOUT.with(|timeout| *timeout.borrow_mut() = None);
28
52
  }
29
53
 
54
+ /// ---
55
+ /// purpose: 判断当前线程是否记录了探测超时
56
+ /// ---
30
57
  pub(crate) fn probe_timed_out() -> bool {
31
58
  PROBE_TIMEOUT.with(|timeout| timeout.borrow().is_some())
32
59
  }
33
60
 
61
+ /// ---
62
+ /// purpose: 获取当前线程记录的探测超时详情
63
+ /// ---
34
64
  pub(crate) fn probe_timeout() -> Option<ProbeTimeout> {
35
65
  PROBE_TIMEOUT.with(|timeout| timeout.borrow().clone())
36
66
  }
37
67
 
38
68
  #[cfg(test)]
69
+ /// ---
70
+ /// purpose: 为单元测试设置探测超时详情
71
+ /// ---
39
72
  pub(crate) fn set_probe_timeout_for_test(probe: &'static str, pid: Option<u32>, timeout_ms: u64) {
40
73
  PROBE_TIMEOUT.with(|current| {
41
74
  *current.borrow_mut() = Some(ProbeTimeout {
@@ -46,6 +79,9 @@ pub(crate) fn set_probe_timeout_for_test(probe: &'static str, pid: Option<u32>,
46
79
  });
47
80
  }
48
81
 
82
+ /// ---
83
+ /// purpose: 执行一个带默认超时与探测标识的命令并读取 stdout
84
+ /// ---
49
85
  pub(crate) fn bounded_command_output_with_probe(
50
86
  command: &mut Command,
51
87
  probe: &'static str,
@@ -114,9 +150,14 @@ fn temp_output_path(kind: &str) -> std::path::PathBuf {
114
150
  .duration_since(UNIX_EPOCH)
115
151
  .map(|duration| duration.as_nanos())
116
152
  .unwrap_or(0);
153
+ temp_output_path_with_nanos(kind, nanos)
154
+ }
155
+
156
+ fn temp_output_path_with_nanos(kind: &str, nanos: u128) -> std::path::PathBuf {
157
+ let unique_id = NEXT_TEMP_OUTPUT_ID.fetch_add(1, Ordering::Relaxed);
117
158
  std::env::temp_dir().join(format!(
118
- "team-agent-os-probe-{}-{nanos}.{kind}",
119
- std::process::id()
159
+ "team-agent-os-probe-{}-{nanos}-{unique_id}.{kind}",
160
+ std::process::id(),
120
161
  ))
121
162
  }
122
163
 
@@ -146,6 +187,9 @@ fn read_and_remove(path: &std::path::Path) -> Vec<u8> {
146
187
  ///
147
188
  /// Bounded via [`run_bounded_command`] so a wedged ps cannot stall the
148
189
  /// coordinator tick.
190
+ /// ---
191
+ /// purpose: 读取 pane PID 的前台与根进程组 ID,无法判定时返回 None
192
+ /// ---
149
193
  pub(crate) fn pane_foreground_and_root_pgrp(pane_pid: u32) -> io::Result<Option<(u32, u32)>> {
150
194
  let mut command = Command::new("ps");
151
195
  command.args(["-o", "tpgid=,pgid=", "-p", &pane_pid.to_string()]);
@@ -206,4 +250,31 @@ mod fg_pgrp_tests {
206
250
  pane_foreground_and_root_pgrp(0xFFFF_FFFE).expect("must not error on missing pid");
207
251
  assert!(result.is_none(), "missing pid → None");
208
252
  }
253
+
254
+ #[test]
255
+ fn temp_output_create_new_requires_atomic_discriminator() {
256
+ let fixed_nanos = 0x5eed_u128;
257
+ let first_path = temp_output_path_with_nanos("stdout", fixed_nanos);
258
+ let second_path = temp_output_path_with_nanos("stdout", fixed_nanos);
259
+ let _ = std::fs::remove_file(&first_path);
260
+ let _ = std::fs::remove_file(&second_path);
261
+
262
+ let first = OpenOptions::new()
263
+ .create_new(true)
264
+ .read(true)
265
+ .write(true)
266
+ .open(&first_path)
267
+ .expect("first atomic path create");
268
+ let second = OpenOptions::new()
269
+ .create_new(true)
270
+ .read(true)
271
+ .write(true)
272
+ .open(&second_path)
273
+ .expect("second atomic path create");
274
+ assert_ne!(first_path, second_path, "atomic discriminator must advance");
275
+ drop(first);
276
+ drop(second);
277
+ let _ = std::fs::remove_file(first_path);
278
+ let _ = std::fs::remove_file(second_path);
279
+ }
209
280
  }
@@ -342,6 +342,28 @@ impl ProviderAdapter for BasicProviderAdapter {
342
342
  native_mcp_config: false,
343
343
  writes_global_settings: true,
344
344
  },
345
+ // Grok: --resume/--fork-session 齐全 → resume/fork true; 无原生
346
+ // --mcp-config flag → native_mcp_config false (MCP 走 .grok/config.toml)。
347
+ Provider::Grok => ProviderCaps {
348
+ resume: true,
349
+ fork: true,
350
+ native_mcp_config: false,
351
+ writes_global_settings: false,
352
+ },
353
+ // Cursor agent: --resume [chatId] 有 → resume true; 无 fork flag →
354
+ // fork false; 无 --mcp-config → native_mcp_config false。
355
+ Provider::CursorAgent => ProviderCaps {
356
+ resume: true,
357
+ fork: false,
358
+ native_mcp_config: false,
359
+ writes_global_settings: false,
360
+ },
361
+ Provider::Pi => ProviderCaps {
362
+ resume: true,
363
+ fork: false,
364
+ native_mcp_config: false,
365
+ writes_global_settings: false,
366
+ },
345
367
  Provider::Fake => ProviderCaps {
346
368
  resume: false,
347
369
  fork: false,
@@ -389,6 +411,10 @@ impl ProviderAdapter for BasicProviderAdapter {
389
411
  // 标"weak / no auth-status command available";Compatible/Official 走 BYOK
390
412
  // 路径,有 COPILOT_PROVIDER_BASE_URL 时已脱离 GitHub 登录通道。
391
413
  Provider::Copilot => copilot_auth_hint(auth_mode),
414
+ // Cursor: 登录探测未接。U-17 前不得把未观测写成 Present(shim 对
415
+ // `cursor-agent status` 会假绿 Logged in)。缺键 ≠ 已知未登录,故不用 Missing。
416
+ Provider::CursorAgent => AuthHintStatus::Unknown,
417
+ Provider::Pi => AuthHintStatus::Unknown,
392
418
  _ => match auth_mode {
393
419
  AuthMode::Subscription => AuthHintStatus::Present,
394
420
  AuthMode::OfficialApi | AuthMode::CompatibleApi => AuthHintStatus::MissingOrUnknown,
@@ -454,6 +480,27 @@ impl ProviderAdapter for BasicProviderAdapter {
454
480
  }
455
481
  Ok(argv)
456
482
  }
483
+ Provider::Grok => Ok(grok_launch_command(
484
+ self,
485
+ auth_mode,
486
+ mcp_config,
487
+ system_prompt,
488
+ model,
489
+ tools,
490
+ )?),
491
+ Provider::CursorAgent => Ok(cursor_agent_base_command(
492
+ self,
493
+ auth_mode,
494
+ mcp_config,
495
+ system_prompt,
496
+ model,
497
+ tools,
498
+ false,
499
+ None,
500
+ )?),
501
+ Provider::Pi => Err(ProviderError::Command(
502
+ "Pi commands require the shared lifecycle materializer".to_string(),
503
+ )),
457
504
  Provider::Fake => Ok(fake_worker_command()),
458
505
  }
459
506
  }
@@ -543,6 +590,32 @@ impl ProviderAdapter for BasicProviderAdapter {
543
590
  // * `-C <workspace>`(双保险,即便 spawn cwd 漂移也能锚定)
544
591
  // mcp_config inline 形态由 build_command 写入,launch 路径会用
545
592
  // point_native_mcp_config_at_file 重写为 @<file> 形(§C1 note)。
593
+ Provider::Grok => {
594
+ // Align with Claude/Copilot: the UUID on `--session-id` is
595
+ // also the capture hint. `CommandPlan::argv_only` used to
596
+ // drop expected_session_id, so `_pending_session_id` was
597
+ // never written and restart could not resume.
598
+ let expected = next_session_token();
599
+ let mut argv = grok_base_command(
600
+ self,
601
+ ctx.auth_mode,
602
+ ctx.mcp_config,
603
+ ctx.system_prompt,
604
+ ctx.model,
605
+ ctx.tools,
606
+ ctx.profile_launch
607
+ .is_some_and(|profile| profile.managed_mcp_config),
608
+ ctx.effort,
609
+ )?;
610
+ argv.push("--session-id".to_string());
611
+ argv.push(expected.clone());
612
+ Ok(CommandPlan {
613
+ argv,
614
+ expected_session_id: Some(SessionId::new(expected)),
615
+ provider_projects_root: None,
616
+ managed_mcp_config: false,
617
+ })
618
+ }
546
619
  Provider::Copilot => {
547
620
  let expected = next_session_token();
548
621
  let mut argv = copilot_base_command(
@@ -561,6 +634,30 @@ impl ProviderAdapter for BasicProviderAdapter {
561
634
  managed_mcp_config: false,
562
635
  })
563
636
  }
637
+ Provider::CursorAgent => {
638
+ // U-01 create-chat 无登录超时;禁止发明 --session-id。
639
+ // pending 只能是 CLI chatId,本拍不造假 uuid。
640
+ let argv = cursor_agent_base_command(
641
+ self,
642
+ ctx.auth_mode,
643
+ ctx.mcp_config,
644
+ ctx.system_prompt,
645
+ ctx.model,
646
+ ctx.tools,
647
+ ctx.profile_launch
648
+ .is_some_and(|profile| profile.managed_mcp_config),
649
+ ctx.effort,
650
+ )?;
651
+ Ok(CommandPlan {
652
+ argv,
653
+ expected_session_id: None,
654
+ provider_projects_root: None,
655
+ managed_mcp_config: false,
656
+ })
657
+ }
658
+ Provider::Pi => Err(ProviderError::Command(
659
+ "Pi commands require the shared lifecycle materializer".to_string(),
660
+ )),
564
661
  _ => self
565
662
  .build_command_with_tools(
566
663
  ctx.auth_mode,
@@ -725,6 +822,43 @@ impl ProviderAdapter for BasicProviderAdapter {
725
822
  argv.push(session_id.as_str().to_string());
726
823
  Ok(argv)
727
824
  }
825
+ // Grok resume: base + `--resume <id>` (grok -r/--resume 接受 id|title)。
826
+ Provider::Grok => {
827
+ let mut argv = grok_base_command(
828
+ self,
829
+ auth_mode,
830
+ mcp_config,
831
+ system_prompt,
832
+ model,
833
+ tools,
834
+ false,
835
+ None,
836
+ )?;
837
+ argv.push("--resume".to_string());
838
+ argv.push(session_id.as_str().to_string());
839
+ Ok(argv)
840
+ }
841
+ // Cursor resume: base + `--resume <chatId>`。
842
+ Provider::CursorAgent => {
843
+ let mut argv = cursor_agent_base_command(
844
+ self,
845
+ auth_mode,
846
+ mcp_config,
847
+ system_prompt,
848
+ model,
849
+ tools,
850
+ false,
851
+ None,
852
+ )?;
853
+ // Put --resume immediately after `agent` so the CLI treats it as
854
+ // the session mode rather than a trailing flag after --workspace.
855
+ argv.insert(1, "--resume".to_string());
856
+ argv.insert(2, session_id.as_str().to_string());
857
+ Ok(argv)
858
+ }
859
+ Provider::Pi => Err(ProviderError::ResumeUnavailable(
860
+ "pi resume requires the captured exact session path".to_string(),
861
+ )),
728
862
  Provider::GeminiCli | Provider::Fake => Err(ProviderError::ResumeUnavailable(format!(
729
863
  "{} resume requires session_id",
730
864
  provider_wire(self.provider)
@@ -801,6 +935,54 @@ impl ProviderAdapter for BasicProviderAdapter {
801
935
  argv.push(session_id.as_str().to_string());
802
936
  Ok(CommandPlan::argv_only(argv))
803
937
  }
938
+ Provider::Grok => {
939
+ let Some(session_id) = session_id else {
940
+ return Err(ProviderError::ResumeUnavailable(
941
+ "resume requires session_id".to_string(),
942
+ ));
943
+ };
944
+ let mut argv = grok_base_command(
945
+ self,
946
+ ctx.auth_mode,
947
+ ctx.mcp_config,
948
+ ctx.system_prompt,
949
+ ctx.model,
950
+ ctx.tools,
951
+ ctx.profile_launch
952
+ .is_some_and(|profile| profile.managed_mcp_config),
953
+ ctx.effort,
954
+ )?;
955
+ argv.push("--resume".to_string());
956
+ argv.push(session_id.as_str().to_string());
957
+ Ok(CommandPlan::argv_only(argv))
958
+ }
959
+ Provider::CursorAgent => {
960
+ let Some(session_id) = session_id else {
961
+ return Err(ProviderError::ResumeUnavailable(
962
+ "cursor_agent resume requires chatId; empty --resume is forbidden (picker)".to_string(),
963
+ ));
964
+ };
965
+ let raw = session_id.as_str();
966
+ if raw.is_empty() || raw.contains('/') || raw.contains('\\') || raw.contains('\0') {
967
+ return Err(ProviderError::ResumeUnavailable(
968
+ "cursor_agent resume rejected forged or empty chatId".to_string(),
969
+ ));
970
+ }
971
+ let mut argv = cursor_agent_base_command(
972
+ self,
973
+ ctx.auth_mode,
974
+ ctx.mcp_config,
975
+ ctx.system_prompt,
976
+ ctx.model,
977
+ ctx.tools,
978
+ ctx.profile_launch
979
+ .is_some_and(|profile| profile.managed_mcp_config),
980
+ ctx.effort,
981
+ )?;
982
+ argv.push("--resume".to_string());
983
+ argv.push(raw.to_string());
984
+ Ok(CommandPlan::argv_only(argv))
985
+ }
804
986
  _ => self
805
987
  .build_resume_command_with_context(
806
988
  session_id,
@@ -882,7 +1064,27 @@ impl ProviderAdapter for BasicProviderAdapter {
882
1064
  Provider::Copilot => Err(ProviderError::CapabilityUnsupported(
883
1065
  "copilot CLI 无 fork 旗标,session-store 不支持 branched continuation".to_string(),
884
1066
  )),
885
- Provider::GeminiCli | Provider::Fake => {
1067
+ // Grok fork: base + 新 `--session-id` + `--resume <src>` + `--fork-session`
1068
+ // (grok help: --fork-session 需配合 --resume/--continue)。
1069
+ Provider::Grok => {
1070
+ let mut argv = grok_base_command(
1071
+ self,
1072
+ auth_mode,
1073
+ mcp_config,
1074
+ system_prompt,
1075
+ model,
1076
+ tools,
1077
+ false,
1078
+ None,
1079
+ )?;
1080
+ argv.push("--session-id".to_string());
1081
+ argv.push(next_session_token());
1082
+ argv.push("--resume".to_string());
1083
+ argv.push(session_id.as_str().to_string());
1084
+ argv.push("--fork-session".to_string());
1085
+ Ok(argv)
1086
+ }
1087
+ Provider::Pi | Provider::CursorAgent | Provider::GeminiCli | Provider::Fake => {
886
1088
  Err(ProviderError::CapabilityUnsupported(format!(
887
1089
  "{} does not support native session fork",
888
1090
  provider_wire(self.provider)
@@ -1006,6 +1208,30 @@ impl ProviderAdapter for BasicProviderAdapter {
1006
1208
  managed_mcp_config: profile.managed_mcp_config,
1007
1209
  })
1008
1210
  }
1211
+ Provider::Grok => {
1212
+ let Some(session_id) = session_id else {
1213
+ return Err(ProviderError::ResumeUnavailable(
1214
+ "fork requires session_id".to_string(),
1215
+ ));
1216
+ };
1217
+ let mut argv = grok_base_command(
1218
+ self,
1219
+ ctx.auth_mode,
1220
+ ctx.mcp_config,
1221
+ ctx.system_prompt,
1222
+ ctx.model,
1223
+ ctx.tools,
1224
+ ctx.profile_launch
1225
+ .is_some_and(|profile| profile.managed_mcp_config),
1226
+ ctx.effort,
1227
+ )?;
1228
+ argv.push("--session-id".to_string());
1229
+ argv.push(next_session_token());
1230
+ argv.push("--resume".to_string());
1231
+ argv.push(session_id.as_str().to_string());
1232
+ argv.push("--fork-session".to_string());
1233
+ Ok(CommandPlan::argv_only(argv))
1234
+ }
1009
1235
  _ => self
1010
1236
  .fork_with_context(
1011
1237
  session_id,
@@ -1061,14 +1287,32 @@ impl ProviderAdapter for BasicProviderAdapter {
1061
1287
  r"working|processing",
1062
1288
  r"Error|panic",
1063
1289
  ),
1064
- Provider::GeminiCli | Provider::Fake => {
1290
+ // Pi: 无已验证 turn-state 检测;占位串不会匹配真实 pane chrome。
1291
+ Provider::Pi => patterns(
1292
+ r"pi-idle-unset-until-turn-state",
1293
+ r"pi-busy-unset-until-turn-state",
1294
+ r"pi-error-unset-until-turn-state",
1295
+ ),
1296
+ // Grok: 暂无专有 status 正则, 沿用 generic。
1297
+ Provider::Grok | Provider::GeminiCli | Provider::Fake => {
1065
1298
  patterns(r">", r"working|processing", r"Error|Traceback")
1066
1299
  }
1300
+ // Cursor: U-15 未过前不得用 generic `>` 单独判已证实 idle。
1301
+ // 字面量 `cursor-idle-unset-until-u15` 不会出现在屏上。
1302
+ Provider::CursorAgent => patterns(
1303
+ r"cursor-idle-unset-until-u15",
1304
+ r"cursor-busy-unset-until-u15",
1305
+ r"Error|Traceback",
1306
+ ),
1067
1307
  }
1068
1308
  }
1069
1309
 
1070
- fn validate_model(&self, _model: &str) -> Result<bool, ProviderError> {
1071
- Ok(true)
1310
+ fn validate_model(&self, model: &str) -> Result<bool, ProviderError> {
1311
+ if self.provider != Provider::Pi {
1312
+ return Ok(true);
1313
+ }
1314
+ let (_, catalog) = crate::lifecycle::launch::pi_mcp::resolve_pi_executable_chain()?;
1315
+ Ok(crate::lifecycle::launch::pi_mcp::select_exact_pi_model(&catalog, model).is_ok())
1072
1316
  }
1073
1317
  }
1074
1318
 
@@ -1164,7 +1408,9 @@ pub(crate) fn json_inline(value: &serde_json::Value) -> String {
1164
1408
  use super::adapters::claude::{claude_base_command, claude_launch_command};
1165
1409
  use super::adapters::codex::codex_base_command;
1166
1410
  use super::adapters::copilot::{copilot_base_command, copilot_base_command_resume};
1411
+ use super::adapters::cursor_agent::cursor_agent_base_command;
1167
1412
  use super::adapters::fake::fake_worker_command;
1413
+ use super::adapters::grok::{grok_base_command, grok_launch_command};
1168
1414
 
1169
1415
  /// Contract C / MUST-8: the per-worker Team Agent MCP server config used by Claude
1170
1416
  /// (`--mcp-config`) and Codex (`-c mcp_servers.*` injection). Placeholders
@@ -1249,9 +1495,16 @@ pub(crate) fn next_session_token() -> String {
1249
1495
  #[cfg(test)]
1250
1496
  mod fork_materialization_source_guard {
1251
1497
  #[test]
1252
- fn lifecycle_uses_provider_neutral_fork_materialization_dispatch() {
1498
+ fn lifecycle_uses_in_window_fork_not_session_file_materialization() {
1253
1499
  let source = include_str!("../lifecycle/launch/fork_agent.rs");
1254
- assert!(source.contains(".materialize_fork_backing("));
1500
+ assert!(
1501
+ source.contains("in_window_fork(") && source.contains("inject_clean_command"),
1502
+ "fork must stay on the shared in-window inject path"
1503
+ );
1504
+ assert!(
1505
+ !source.contains(".materialize_fork_backing("),
1506
+ "session-file fork materialization must stay gone"
1507
+ );
1255
1508
  assert!(!source.contains("materialize_codex_fork"));
1256
1509
  assert!(!source.contains("codex_fork"));
1257
1510
  }