@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
@@ -0,0 +1,288 @@
1
+ //! ---
2
+ //! purpose: 按 pending uuid 捕获 ~/.grok/sessions/<urlencoded-cwd>/<uuid>/,不读会话正文
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: grok_session_dir
6
+ //! what: 由 spawn cwd + session id 派生磁盘存档目录
7
+ //! - name: grok_session_archive_present
8
+ //! what: 只凭 marker 文件存在性判定 backing,不打开正文
9
+ //! - name: scan_session_store
10
+ //! what: expected_session_id 命中存档则返回唯一候选,否则空
11
+ //! requires:
12
+ //! - name: expected_session_id
13
+ //! what: 无 pending id 不扫描、不拾取同 cwd 其它会话
14
+ //! boundary:
15
+ //! - 只服务 Provider::Grok
16
+ //! - 不读 chat_history/events 正文,不改 claude/codex/copilot 扫描
17
+ //! maturity: wired
18
+ //! ---
19
+
20
+ use std::path::{Path, PathBuf};
21
+
22
+ use crate::provider::types::{CaptureVia, CapturedSession, Confidence, RolloutPath};
23
+
24
+ use super::{CaptureSessionContext, CapturedSessionCandidate};
25
+
26
+ /// RFC 3986 unreserved set — same as Python `urllib.parse.quote(path, safe='')`.
27
+ fn percent_encode_unreserved(raw: &str) -> String {
28
+ let mut out = String::with_capacity(raw.len() * 3);
29
+ for &byte in raw.as_bytes() {
30
+ match byte {
31
+ b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
32
+ out.push(byte as char);
33
+ }
34
+ _ => {
35
+ out.push('%');
36
+ const HEX: &[u8; 16] = b"0123456789ABCDEF";
37
+ out.push(HEX[(byte >> 4) as usize] as char);
38
+ out.push(HEX[(byte & 0x0F) as usize] as char);
39
+ }
40
+ }
41
+ }
42
+ out
43
+ }
44
+
45
+ /// ---
46
+ /// purpose: 由 spawn cwd 与 session id 派生 ~/.grok/sessions 下的存档目录
47
+ /// params:
48
+ /// spawn_cwd: 席位工作目录;canonicalize 后再 percent-encode
49
+ /// session_id: 框架预定 uuid;空或含路径分隔则拒绝
50
+ /// returns: HOME 下对应目录路径;HOME 缺失或 id 非法则 None
51
+ /// contract:
52
+ /// provides:
53
+ /// - name: grok_session_dir
54
+ /// what: 只派生路径,不创建目录、不读存档
55
+ /// boundary:
56
+ /// - 不枚举其它 uuid,不读会话文件
57
+ /// ---
58
+ pub(crate) fn grok_session_dir(spawn_cwd: &Path, session_id: &str) -> Option<PathBuf> {
59
+ if session_id.is_empty()
60
+ || session_id.contains('/')
61
+ || session_id.contains('\\')
62
+ || session_id.contains('\0')
63
+ {
64
+ return None;
65
+ }
66
+ let home = std::env::var_os("HOME").map(PathBuf::from)?;
67
+ let canonical = std::fs::canonicalize(spawn_cwd).unwrap_or_else(|_| spawn_cwd.to_path_buf());
68
+ let encoded = percent_encode_unreserved(&canonical.to_string_lossy());
69
+ if encoded.is_empty() {
70
+ return None;
71
+ }
72
+ Some(
73
+ home.join(".grok")
74
+ .join("sessions")
75
+ .join(encoded)
76
+ .join(session_id),
77
+ )
78
+ }
79
+
80
+ /// ---
81
+ /// purpose: 只凭 marker 文件存在性判断 grok 会话存档是否可 resume
82
+ /// params:
83
+ /// dir: grok_session_dir 给出的目录
84
+ /// returns: 目录存在且含 events.jsonl 或 chat_history.jsonl 或 summary.json
85
+ /// contract:
86
+ /// provides:
87
+ /// - name: grok_session_archive_present
88
+ /// what: 用 is_file 判 backing,不打开正文
89
+ /// boundary:
90
+ /// - 不读 jsonl / summary 内容
91
+ /// ---
92
+ pub(crate) fn grok_session_archive_present(dir: &Path) -> bool {
93
+ if !dir.is_dir() {
94
+ return false;
95
+ }
96
+ ["events.jsonl", "chat_history.jsonl", "summary.json"]
97
+ .iter()
98
+ .any(|name| dir.join(name).is_file())
99
+ }
100
+
101
+ /// ---
102
+ /// purpose: 按 pending expected_session_id 捕获唯一 grok 磁盘存档
103
+ /// params:
104
+ /// context: 含 spawn_cwd 与 expected_session_id 的捕获上下文
105
+ /// returns: 命中则一条 FsWatch 候选,否则空向量
106
+ /// contract:
107
+ /// provides:
108
+ /// - name: scan_session_store
109
+ /// what: expected id 与目录对齐才捕获
110
+ /// boundary:
111
+ /// - 无 pending id 不扫描;不拾取同 cwd 其它会话
112
+ /// ---
113
+ pub(super) fn scan_session_store(context: &CaptureSessionContext) -> Vec<CapturedSessionCandidate> {
114
+ let Some(expected) = context.expected_session_id.as_ref() else {
115
+ return Vec::new();
116
+ };
117
+ let Some(dir) = grok_session_dir(&context.spawn_cwd, expected.as_str()) else {
118
+ return Vec::new();
119
+ };
120
+ if !grok_session_archive_present(&dir) {
121
+ return Vec::new();
122
+ }
123
+ vec![CapturedSessionCandidate {
124
+ captured: CapturedSession {
125
+ session_id: Some(expected.clone()),
126
+ rollout_path: Some(RolloutPath::new(dir)),
127
+ captured_via: CaptureVia::FsWatch,
128
+ attribution_confidence: Confidence::High,
129
+ spawn_cwd: context.spawn_cwd.clone(),
130
+ },
131
+ embedded_agent_id: None,
132
+ positive_agent_id_match: false,
133
+ agent_path_match: false,
134
+ }]
135
+ }
136
+
137
+ #[cfg(test)]
138
+ mod tests {
139
+ #![allow(clippy::unwrap_used)]
140
+
141
+ use super::*;
142
+ use crate::provider::types::SessionId;
143
+ use std::sync::atomic::{AtomicU64, Ordering};
144
+
145
+ struct HomeGuard {
146
+ prev: Option<std::ffi::OsString>,
147
+ }
148
+
149
+ impl HomeGuard {
150
+ fn set(home: &Path) -> Self {
151
+ let prev = std::env::var_os("HOME");
152
+ std::env::set_var("HOME", home);
153
+ Self { prev }
154
+ }
155
+ }
156
+
157
+ impl Drop for HomeGuard {
158
+ fn drop(&mut self) {
159
+ match &self.prev {
160
+ Some(v) => std::env::set_var("HOME", v),
161
+ None => std::env::remove_var("HOME"),
162
+ }
163
+ }
164
+ }
165
+
166
+ fn tmp_root(tag: &str) -> PathBuf {
167
+ static CTR: AtomicU64 = AtomicU64::new(0);
168
+ let dir = std::env::temp_dir().join(format!(
169
+ "ta-d111-grok-scan-{}-{}-{}",
170
+ tag,
171
+ std::process::id(),
172
+ CTR.fetch_add(1, Ordering::Relaxed)
173
+ ));
174
+ let _ = std::fs::remove_dir_all(&dir);
175
+ std::fs::create_dir_all(&dir).unwrap();
176
+ std::fs::canonicalize(&dir).unwrap()
177
+ }
178
+
179
+ fn seed_archive(home: &Path, cwd: &Path, session_id: &str) -> PathBuf {
180
+ let dir = grok_session_dir(cwd, session_id).expect("dir");
181
+ std::fs::create_dir_all(&dir).unwrap();
182
+ std::fs::write(dir.join("events.jsonl"), b"").unwrap();
183
+ dir
184
+ }
185
+
186
+ fn ctx(cwd: &Path, expected: Option<&str>) -> CaptureSessionContext {
187
+ CaptureSessionContext {
188
+ agent_id: "w1".to_string(),
189
+ spawn_cwd: cwd.to_path_buf(),
190
+ pane_id: None,
191
+ pane_pid: None,
192
+ spawned_at: None,
193
+ expected_session_id: expected.map(SessionId::new),
194
+ provider_projects_root: None,
195
+ }
196
+ }
197
+
198
+ #[test]
199
+ fn grok_cwd_encoding_matches_python_quote_empty_safe() {
200
+ assert_eq!(
201
+ percent_encode_unreserved("/private/tmp/ta-t105-repro-83327/ws"),
202
+ "%2Fprivate%2Ftmp%2Fta-t105-repro-83327%2Fws"
203
+ );
204
+ }
205
+
206
+ #[test]
207
+ #[serial_test::serial(env)]
208
+ fn grok_expected_id_captures_archive_without_reading_body() {
209
+ let base = tmp_root("hit");
210
+ let home = base.join("home");
211
+ let cwd = base.join("ws");
212
+ std::fs::create_dir_all(&home).unwrap();
213
+ std::fs::create_dir_all(&cwd).unwrap();
214
+ let sid = "75fce302-0ce9-4635-a788-f4aec11a3f6a";
215
+ let _guard = HomeGuard::set(&home);
216
+ let dir = seed_archive(&home, &cwd, sid);
217
+ std::fs::write(
218
+ dir.join("chat_history.jsonl"),
219
+ b"THIS-BODY-MUST-NOT-BE-PARSED\n",
220
+ )
221
+ .unwrap();
222
+ let out = scan_session_store(&ctx(&cwd, Some(sid)));
223
+ assert_eq!(out.len(), 1);
224
+ assert_eq!(out[0].captured.session_id.as_ref().unwrap().as_str(), sid);
225
+ assert_eq!(
226
+ out[0]
227
+ .captured
228
+ .rollout_path
229
+ .as_ref()
230
+ .map(|p| p.as_path().to_path_buf()),
231
+ Some(dir)
232
+ );
233
+ let _ = std::fs::remove_dir_all(&base);
234
+ }
235
+
236
+ #[test]
237
+ #[serial_test::serial(env)]
238
+ fn grok_expected_id_does_not_steal_foreign_cwd_archive() {
239
+ let base = tmp_root("foreign");
240
+ let home = base.join("home");
241
+ let cwd = base.join("ws");
242
+ let foreign = base.join("other");
243
+ std::fs::create_dir_all(&home).unwrap();
244
+ std::fs::create_dir_all(&cwd).unwrap();
245
+ std::fs::create_dir_all(&foreign).unwrap();
246
+ let sid = "4e7b5a6e-e09a-4291-9c75-563107105185";
247
+ let _guard = HomeGuard::set(&home);
248
+ seed_archive(&home, &foreign, sid);
249
+ let out = scan_session_store(&ctx(&cwd, Some(sid)));
250
+ assert!(
251
+ out.is_empty(),
252
+ "foreign cwd archive must not bind; got {out:?}"
253
+ );
254
+ let _ = std::fs::remove_dir_all(&base);
255
+ }
256
+
257
+ #[test]
258
+ #[serial_test::serial(env)]
259
+ fn grok_scan_without_expected_id_returns_empty() {
260
+ let base = tmp_root("no-pending");
261
+ let home = base.join("home");
262
+ let cwd = base.join("ws");
263
+ std::fs::create_dir_all(&home).unwrap();
264
+ std::fs::create_dir_all(&cwd).unwrap();
265
+ let _guard = HomeGuard::set(&home);
266
+ seed_archive(&home, &cwd, "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa");
267
+ let out = scan_session_store(&ctx(&cwd, None));
268
+ assert!(out.is_empty());
269
+ let _ = std::fs::remove_dir_all(&base);
270
+ }
271
+
272
+ #[test]
273
+ #[serial_test::serial(env)]
274
+ fn grok_scan_empty_dir_without_marker_is_not_backing() {
275
+ let base = tmp_root("empty");
276
+ let home = base.join("home");
277
+ let cwd = base.join("ws");
278
+ std::fs::create_dir_all(&home).unwrap();
279
+ std::fs::create_dir_all(&cwd).unwrap();
280
+ let sid = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb";
281
+ let _guard = HomeGuard::set(&home);
282
+ let dir = grok_session_dir(&cwd, sid).unwrap();
283
+ std::fs::create_dir_all(&dir).unwrap();
284
+ assert!(!grok_session_archive_present(&dir));
285
+ assert!(scan_session_store(&ctx(&cwd, Some(sid))).is_empty());
286
+ let _ = std::fs::remove_dir_all(&base);
287
+ }
288
+ }
@@ -14,6 +14,8 @@ pub(crate) mod claude;
14
14
  pub(crate) mod codex;
15
15
  pub(crate) mod common;
16
16
  pub(crate) mod copilot;
17
+ pub(crate) mod cursor;
18
+ pub(crate) mod grok;
17
19
 
18
20
  #[derive(Debug, Clone, PartialEq, Eq)]
19
21
  pub struct CaptureSessionContext {
@@ -41,6 +43,12 @@ pub(crate) fn scan_session_candidates_once(
41
43
  if matches!(provider, Provider::Copilot) {
42
44
  return Ok(copilot::scan_session_store(context));
43
45
  }
46
+ if matches!(provider, Provider::Grok) {
47
+ return Ok(grok::scan_session_store(context));
48
+ }
49
+ if matches!(provider, Provider::CursorAgent) {
50
+ return Ok(cursor::scan_session_store(context));
51
+ }
44
52
  if matches!(provider, Provider::Claude | Provider::ClaudeCode)
45
53
  && context.expected_session_id.is_some()
46
54
  {
@@ -0,0 +1,94 @@
1
+ //! ---
2
+ //! purpose: grok 忙时默认把显式队列顶出去(再按回车)
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: flush_explicit_queue
6
+ //! what: 屏幕出现 grok `Enter:send now` 时只重按回车直到消失;默认不认 cursor 页脚
7
+ //! depends:
8
+ //! - crate::transport::Transport
9
+ //! boundary:
10
+ //! - 不重粘文本、不用 Escape、不用 Ctrl-C、不加自动重投
11
+ //! - 不按忙闲决定发不发;只在已经注入之后确认队列态
12
+ //! - 无标记时不改 claude/codex 提交行为
13
+ //! - cursor 第二下 Enter 会打断进行中回合;生产 flush 不带 cursor 标记
14
+ //! maturity: wired
15
+ //! ---
16
+
17
+ use std::time::Duration;
18
+
19
+ use crate::transport::{CaptureRange, Key, Target, Transport, TransportError};
20
+
21
+ /// grok 1.0.4 实测:页脚出现这串就是显式队列。
22
+ pub const GROK_SEND_NOW_MARK: &str = "Enter:send now";
23
+
24
+ /// 2026-08-18 真机,cursor-agent 2026.08.11-e8db854。
25
+ /// 忙时 follow-ups 盒页脚原文(大小写按屏上):
26
+ /// `enter send now · ↑ select/edit · esc cancel`
27
+ /// 出处:`.team/scripts/cursor_send.sh` 的 `QUEUE_MARK='enter send now'`
28
+ /// (脚本头 2026-08-18 真机段)。与 grok 的 `Enter:send now`(大写 E、冒号)
29
+ /// 不是同一串。
30
+ pub const CURSOR_SEND_NOW_MARK: &str = "enter send now";
31
+
32
+ /// 与 grok_send.sh / cursor_send.sh 对齐:最多再按 8 次回车。
33
+ pub const GROK_SEND_NOW_MAX_ENTERS: u32 = 8;
34
+
35
+ pub struct FlushReport {
36
+ pub extra_enters: u32,
37
+ pub mark_cleared: bool,
38
+ }
39
+
40
+ /// After paste+first Enter, if the pane still shows a queue footer,
41
+ /// press Enter only until the mark is gone. No mark ⇒ no keys (claude/codex).
42
+ pub fn flush_explicit_queue(
43
+ transport: &dyn Transport,
44
+ target: &Target,
45
+ ) -> Result<FlushReport, TransportError> {
46
+ flush_explicit_queue_for(transport, target, &[GROK_SEND_NOW_MARK])
47
+ }
48
+
49
+ /// One detector path at a time. Production flush is grok-only.
50
+ /// Tests enable a single mark so a false-positive cannot hide behind the other path.
51
+ pub fn flush_explicit_queue_for(
52
+ transport: &dyn Transport,
53
+ target: &Target,
54
+ marks: &[&str],
55
+ ) -> Result<FlushReport, TransportError> {
56
+ let mut extra_enters = 0;
57
+ for _ in 0..GROK_SEND_NOW_MAX_ENTERS {
58
+ let captured = transport.capture(target, CaptureRange::Tail(40))?;
59
+ if !queue_mark_visible_for(&captured.text, marks) {
60
+ return Ok(FlushReport {
61
+ extra_enters,
62
+ mark_cleared: true,
63
+ });
64
+ }
65
+ transport.send_keys(target, &[Key::Enter])?;
66
+ extra_enters = extra_enters.saturating_add(1);
67
+ std::thread::sleep(retry_pause());
68
+ }
69
+ let captured = transport.capture(target, CaptureRange::Tail(40))?;
70
+ Ok(FlushReport {
71
+ extra_enters,
72
+ mark_cleared: !queue_mark_visible_for(&captured.text, marks),
73
+ })
74
+ }
75
+
76
+ pub fn keep_provider_queue_requested() -> bool {
77
+ matches!(
78
+ std::env::var("TEAM_AGENT_KEEP_PROVIDER_QUEUE").as_deref(),
79
+ Ok("1") | Ok("true") | Ok("yes")
80
+ )
81
+ }
82
+
83
+ pub fn queue_mark_visible(text: &str) -> bool {
84
+ queue_mark_visible_for(text, &[GROK_SEND_NOW_MARK])
85
+ }
86
+
87
+ pub fn queue_mark_visible_for(text: &str, marks: &[&str]) -> bool {
88
+ marks.iter().any(|mark| text.contains(mark))
89
+ }
90
+
91
+ #[allow(dead_code)]
92
+ fn retry_pause() -> Duration {
93
+ Duration::from_millis(50)
94
+ }