@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
package/Cargo.lock CHANGED
@@ -325,6 +325,12 @@ version = "0.4.29"
325
325
  source = "registry+https://github.com/rust-lang/crates.io-index"
326
326
  checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
327
327
 
328
+ [[package]]
329
+ name = "md5"
330
+ version = "0.7.0"
331
+ source = "registry+https://github.com/rust-lang/crates.io-index"
332
+ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
333
+
328
334
  [[package]]
329
335
  name = "memchr"
330
336
  version = "2.8.0"
@@ -575,12 +581,13 @@ dependencies = [
575
581
 
576
582
  [[package]]
577
583
  name = "team-agent"
578
- version = "0.5.66"
584
+ version = "0.5.69"
579
585
  dependencies = [
580
586
  "anyhow",
581
587
  "chrono",
582
588
  "conpty-transport",
583
589
  "libc",
590
+ "md5",
584
591
  "regex",
585
592
  "rusqlite",
586
593
  "serde",
package/Cargo.toml CHANGED
@@ -9,7 +9,7 @@ members = ["crates/team-agent", "crates/win-conpty-phase0", "crates/conpty-trans
9
9
 
10
10
  [workspace.package]
11
11
  edition = "2021"
12
- version = "0.5.66"
12
+ version = "0.5.69"
13
13
  license = "AGPL-3.0"
14
14
  rust-version = "1.95"
15
15
 
@@ -22,6 +22,7 @@ thiserror.workspace = true
22
22
  serde.workspace = true
23
23
  serde_json.workspace = true
24
24
  sha2.workspace = true
25
+ md5 = "0.7"
25
26
  regex.workspace = true
26
27
  chrono.workspace = true
27
28
  libc.workspace = true
@@ -1536,6 +1536,11 @@ pub fn cmd_doctor(args: &DoctorArgs) -> Result<CmdResult, CliError> {
1536
1536
  args.team.as_deref(),
1537
1537
  &mut value,
1538
1538
  );
1539
+ crate::cli::diagnose::append_registry_channel_unbound_to_report(
1540
+ &args.workspace,
1541
+ args.team.as_deref(),
1542
+ &mut value,
1543
+ );
1539
1544
  value
1540
1545
  };
1541
1546
  Ok(CmdResult::from_json(value, args.json))
@@ -9,6 +9,74 @@ use crate::model::pane_authority_refusal::{
9
9
  use crate::provider::wire::{command_name, parse_provider, provider_wire};
10
10
  use crate::transport::Transport;
11
11
 
12
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
13
+ #[serde(rename_all = "snake_case")]
14
+ pub(crate) enum PiBackingStatus {
15
+ Pending,
16
+ Captured,
17
+ Missing,
18
+ }
19
+
20
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
21
+ #[serde(rename_all = "snake_case")]
22
+ pub(crate) enum PiMcpStatus {
23
+ Unavailable,
24
+ ConfiguredLazy,
25
+ RoundtripVerified,
26
+ }
27
+
28
+ pub(crate) struct PiDoctorInput {
29
+ pub executable_chain_verified: bool,
30
+ pub pi_version: Option<String>,
31
+ pub adapter_version: Option<String>,
32
+ pub catalog_sha256: Option<String>,
33
+ pub selected_model: Option<String>,
34
+ pub candidate_executable: Option<std::path::PathBuf>,
35
+ pub wrapper: Option<std::path::PathBuf>,
36
+ pub mcp_roundtrip_at: Option<String>,
37
+ pub backing: PiBackingStatus,
38
+ pub activity: crate::provider::TurnState,
39
+ pub activity_source: crate::provider::ClassifySource,
40
+ pub env_key_names: Vec<String>,
41
+ }
42
+
43
+ #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
44
+ pub(crate) struct PiDoctorFacts {
45
+ pub mcp: PiMcpStatus,
46
+ pub mcp_connected: bool,
47
+ pub backing: PiBackingStatus,
48
+ pub activity: crate::provider::TurnState,
49
+ pub activity_source: crate::provider::ClassifySource,
50
+ pub auth_hint: crate::provider::AuthHintStatus,
51
+ }
52
+
53
+ pub(crate) fn pi_doctor_facts(input: PiDoctorInput) -> PiDoctorFacts {
54
+ let mcp = if input.mcp_roundtrip_at.is_some() {
55
+ PiMcpStatus::RoundtripVerified
56
+ } else if input.wrapper.is_some() {
57
+ PiMcpStatus::ConfiguredLazy
58
+ } else {
59
+ PiMcpStatus::Unavailable
60
+ };
61
+ let _ = (
62
+ input.executable_chain_verified,
63
+ input.pi_version,
64
+ input.adapter_version,
65
+ input.catalog_sha256,
66
+ input.selected_model,
67
+ input.candidate_executable,
68
+ input.env_key_names,
69
+ );
70
+ PiDoctorFacts {
71
+ mcp,
72
+ mcp_connected: mcp == PiMcpStatus::RoundtripVerified,
73
+ backing: input.backing,
74
+ activity: input.activity,
75
+ activity_source: input.activity_source,
76
+ auth_hint: crate::provider::AuthHintStatus::Unknown,
77
+ }
78
+ }
79
+
12
80
  /// 0.5.39 Slice 1 (tmux-server-death-locate §11.1 B): classify a tmux
13
81
  /// transport error string into a diagnose issue id. When the underlying
14
82
  /// tmux subprocess stderr contains `server exited unexpectedly`, the
@@ -208,12 +276,45 @@ pub(crate) fn diagnose_runtime_for_workspace(
208
276
  &mut issues,
209
277
  &mut repairs,
210
278
  );
279
+ append_registry_channel_unbound_issue(workspace, state, &mut issues, &mut repairs);
211
280
  append_legacy_snapshot_issue(workspace, state, &mut issues);
212
281
  append_coordinator_health_issue(workspace, state, &mut issues, &mut repairs);
213
282
  append_runtime_bindings_stale_after_boot_issue(workspace, state, &mut issues, &mut repairs);
214
283
  (issues, repairs)
215
284
  }
216
285
 
286
+ fn append_registry_channel_unbound_issue(
287
+ workspace: &std::path::Path,
288
+ state: &Value,
289
+ issues: &mut Value,
290
+ repairs: &mut Value,
291
+ ) {
292
+ let team_key = crate::state::projection::team_state_key(state);
293
+ if team_key.is_empty()
294
+ || crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
295
+ {
296
+ return;
297
+ }
298
+ if let Some(items) = issues.as_array_mut() {
299
+ if !items.iter().any(|item| {
300
+ item.as_str() == Some("leader_not_attached")
301
+ || item.get("id").and_then(Value::as_str) == Some("leader_channel_unbound")
302
+ }) {
303
+ items.push(json!("leader_not_attached"));
304
+ }
305
+ }
306
+ if let Some(items) = repairs.as_array_mut() {
307
+ items.push(recovery_hint(
308
+ state
309
+ .get("session_name")
310
+ .and_then(Value::as_str)
311
+ .unwrap_or(team_key.as_str()),
312
+ "leader_not_attached",
313
+ "team-agent claim-leader",
314
+ ));
315
+ }
316
+ }
317
+
217
318
  fn append_live_leader_workspace_mismatch_issue(
218
319
  workspace: &std::path::Path,
219
320
  state: &Value,
@@ -339,6 +440,53 @@ fn live_leader_workspace_mismatch(
339
440
  Some((issue, repair))
340
441
  }
341
442
 
443
+ pub(crate) fn append_registry_channel_unbound_to_report(
444
+ workspace: &std::path::Path,
445
+ team: Option<&str>,
446
+ report: &mut Value,
447
+ ) {
448
+ let team_key = team
449
+ .filter(|team| !team.is_empty())
450
+ .map(str::to_string)
451
+ .or_else(|| {
452
+ crate::state::persist::load_runtime_state(workspace)
453
+ .ok()
454
+ .map(|state| crate::state::projection::team_state_key(&state))
455
+ })
456
+ .unwrap_or_default();
457
+ if team_key.is_empty()
458
+ || crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
459
+ {
460
+ return;
461
+ }
462
+ let Some(object) = report.as_object_mut() else {
463
+ return;
464
+ };
465
+ let mut issues = object
466
+ .get("issues")
467
+ .and_then(Value::as_array)
468
+ .cloned()
469
+ .unwrap_or_default();
470
+ if !issues.iter().any(|item| {
471
+ item.as_str() == Some("leader_not_attached")
472
+ || item.get("id").and_then(Value::as_str) == Some("leader_channel_unbound")
473
+ }) {
474
+ issues.push(json!("leader_not_attached"));
475
+ }
476
+ object.insert("issues".to_string(), Value::Array(issues));
477
+ let mut repairs = object
478
+ .get("suggested_repairs")
479
+ .and_then(Value::as_array)
480
+ .cloned()
481
+ .unwrap_or_default();
482
+ repairs.push(recovery_hint(
483
+ &team_key,
484
+ "leader_not_attached",
485
+ "team-agent claim-leader",
486
+ ));
487
+ object.insert("suggested_repairs".to_string(), Value::Array(repairs));
488
+ }
489
+
342
490
  pub(crate) fn append_selected_live_leader_workspace_mismatch(
343
491
  workspace: &std::path::Path,
344
492
  team: Option<&str>,
@@ -1344,6 +1492,11 @@ fn fake_agent_started(agent: &Value) -> bool {
1344
1492
  }
1345
1493
 
1346
1494
  fn agent_mcp_ready(agent: &Value) -> bool {
1495
+ if agent.get("provider").and_then(Value::as_str) == Some("pi") {
1496
+ // The generated wrapper proves configured-lazy only. No persisted
1497
+ // objective Pi MCP roundtrip receipt exists on the readiness path yet.
1498
+ return false;
1499
+ }
1347
1500
  agent
1348
1501
  .get("mcp_config")
1349
1502
  .and_then(Value::as_str)
@@ -1485,6 +1638,9 @@ pub(crate) fn provider_doctor_checks() -> Value {
1485
1638
  crate::provider::Provider::ClaudeCode,
1486
1639
  crate::provider::Provider::Codex,
1487
1640
  crate::provider::Provider::GeminiCli,
1641
+ crate::provider::Provider::Grok,
1642
+ crate::provider::Provider::CursorAgent,
1643
+ crate::provider::Provider::Pi,
1488
1644
  crate::provider::Provider::Fake,
1489
1645
  ] {
1490
1646
  let adapter = crate::provider::get_adapter(provider);
@@ -45,7 +45,7 @@ fn emit_with_json_order(
45
45
  }
46
46
 
47
47
  ///
48
- /// `main(argv)`(`parser.py:84`):**CLI 唯一进程入口**。codex/claude/copilot passthrough 早返回 →
48
+ /// `main(argv)`(`parser.py:84`):**CLI 唯一进程入口**。codex/claude/copilot/grok/cursor passthrough 早返回 →
49
49
  /// 解析 argv 到 subcommand → 调对应 handler → 异常落盘 + 信封 + `ExitCode::Error` →
50
50
  /// `consume_leader_inbox_summary` → `emit` → `result.ok is False ? Error : Ok`。
51
51
  /// **行为入口**:契约可端到端跑 argv→(stdout, exit code)。
@@ -262,9 +262,10 @@ const DISPATCH_COMMANDS: &[&str] = &[
262
262
  const SPEC_ONLY_HELP_COMMANDS: &[&str] = &["start"];
263
263
  // Command grammar, not provider identity parsing: these are top-level CLI
264
264
  // passthrough verbs for starting a leader under a provider executable.
265
- const LEADER_PASSTHROUGH_COMMANDS: &[&str] = &["codex", "claude", "copilot"];
265
+ const LEADER_PASSTHROUGH_COMMANDS: &[&str] =
266
+ &["codex", "claude", "copilot", "grok", "cursor", "pi"];
266
267
 
267
- fn is_leader_passthrough_command(command: &str) -> bool {
268
+ pub(crate) fn is_leader_passthrough_command(command: &str) -> bool {
268
269
  LEADER_PASSTHROUGH_COMMANDS.contains(&command)
269
270
  && matches!(
270
271
  command_spec(command).map(|spec| spec.kind),
@@ -285,7 +286,7 @@ fn is_known_subcommand(command: &str) -> bool {
285
286
  command_spec(command).is_some_and(|spec| spec.command_help)
286
287
  }
287
288
 
288
- fn default_help() -> String {
289
+ pub(crate) fn default_help() -> String {
289
290
  let mut out = String::from("usage: team-agent <command> [options]\n");
290
291
  append_help_section(
291
292
  &mut out,
@@ -310,7 +311,7 @@ fn default_help() -> String {
310
311
  "Guided recovery",
311
312
  &["claim-leader", "takeover", "attach-leader"],
312
313
  );
313
- out.push_str("\nProvider launchers:\n team-agent codex|claude|copilot ...\n");
314
+ out.push_str("\nProvider launchers:\n team-agent codex|claude|copilot|grok|cursor|pi ...\n");
314
315
  out.push_str("\nRun `team-agent <command> --help` for command flags.");
315
316
  out
316
317
  }
@@ -0,0 +1,299 @@
1
+ //! ---
2
+ //! purpose: 对账 .grok/config.toml 残余共享槽与在役 grok 席,含未首 turn 危险窗
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: grok_slot_report
6
+ //! what: 盘上 ID/OWNER/AUTH、在役 grok、pre_first_turn;读不出为 unjudgeable
7
+ //! boundary:
8
+ //! - 读不出不报一致
9
+ //! - 不写盘、不改 overlay
10
+ //! maturity: wired
11
+ //! ---
12
+
13
+ use serde_json::{json, Value};
14
+ use std::path::Path;
15
+
16
+ #[derive(Debug, Clone, PartialEq, Eq)]
17
+ pub(crate) struct GrokSlotReport {
18
+ pub readable: bool,
19
+ pub consistent: bool,
20
+ pub disk_team_agent_id: Option<String>,
21
+ pub disk_owner_team_id: Option<String>,
22
+ pub disk_auth_mode: Option<String>,
23
+ pub expected_owner_team_id: Option<String>,
24
+ pub expected_auth_mode: Option<String>,
25
+ pub live_seats: Vec<String>,
26
+ pub pre_first_turn: Vec<String>,
27
+ pub reason: String,
28
+ }
29
+
30
+ impl GrokSlotReport {
31
+ pub(crate) fn to_json(&self) -> Value {
32
+ json!({
33
+ "readable": self.readable,
34
+ "consistent": self.consistent,
35
+ "disk_team_agent_id": self.disk_team_agent_id,
36
+ "disk_owner_team_id": self.disk_owner_team_id,
37
+ "disk_auth_mode": self.disk_auth_mode,
38
+ "expected_owner_team_id": self.expected_owner_team_id,
39
+ "expected_auth_mode": self.expected_auth_mode,
40
+ "live_seats": self.live_seats,
41
+ "pre_first_turn": self.pre_first_turn,
42
+ "reason": self.reason,
43
+ })
44
+ }
45
+ }
46
+
47
+ pub(crate) fn grok_slot_report(workspace: &Path, state: &Value) -> GrokSlotReport {
48
+ if let Some(reason) = unreadable_state_reason(workspace, state) {
49
+ return unjudgeable(reason, Vec::new(), Vec::new());
50
+ }
51
+ let live = live_grok_from_state(state);
52
+ let pre_first_turn = live
53
+ .iter()
54
+ .filter(|seat| seat.pre_first_turn)
55
+ .map(|seat| seat.id.clone())
56
+ .collect::<Vec<_>>();
57
+ let live_ids = live.iter().map(|seat| seat.id.clone()).collect::<Vec<_>>();
58
+ let expected_owner = expected_owner_team_id(state);
59
+ let expected_auth = expected_auth_mode(&live);
60
+
61
+ let toml_path = workspace.join(".grok").join("config.toml");
62
+ if toml_path.exists() {
63
+ match std::fs::read_to_string(&toml_path) {
64
+ Err(error) => {
65
+ return unjudgeable(
66
+ format!("unjudgeable: cannot read {} ({error})", toml_path.display()),
67
+ live_ids,
68
+ pre_first_turn,
69
+ );
70
+ }
71
+ Ok(text) => {
72
+ let disk = parse_disk_slot(&text);
73
+ return classify(disk, live_ids, pre_first_turn, expected_owner, expected_auth);
74
+ }
75
+ }
76
+ }
77
+ classify(
78
+ DiskSlot::default(),
79
+ live_ids,
80
+ pre_first_turn,
81
+ expected_owner,
82
+ expected_auth,
83
+ )
84
+ }
85
+
86
+ #[derive(Debug, Clone, Default, PartialEq, Eq)]
87
+ struct DiskSlot {
88
+ team_agent_id: Option<String>,
89
+ owner_team_id: Option<String>,
90
+ auth_mode: Option<String>,
91
+ extras: Vec<(String, String)>,
92
+ }
93
+
94
+ fn parse_disk_slot(text: &str) -> DiskSlot {
95
+ let keys = crate::lifecycle::launch::per_seat_keys_in_toml(text);
96
+ DiskSlot {
97
+ team_agent_id: find_key(&keys, "TEAM_AGENT_ID"),
98
+ owner_team_id: find_key(&keys, "TEAM_AGENT_OWNER_TEAM_ID"),
99
+ auth_mode: find_key(&keys, "TEAM_AGENT_AUTH_MODE"),
100
+ extras: keys
101
+ .into_iter()
102
+ .filter(|(key, _)| {
103
+ key != "TEAM_AGENT_ID"
104
+ && key != "TEAM_AGENT_OWNER_TEAM_ID"
105
+ && key != "TEAM_AGENT_AUTH_MODE"
106
+ })
107
+ .collect(),
108
+ }
109
+ }
110
+
111
+ fn find_key(keys: &[(String, String)], want: &str) -> Option<String> {
112
+ keys.iter()
113
+ .find(|(key, _)| key == want)
114
+ .map(|(_, value)| value.clone())
115
+ }
116
+
117
+ fn unjudgeable(
118
+ reason: String,
119
+ live_seats: Vec<String>,
120
+ pre_first_turn: Vec<String>,
121
+ ) -> GrokSlotReport {
122
+ GrokSlotReport {
123
+ readable: false,
124
+ consistent: false,
125
+ disk_team_agent_id: None,
126
+ disk_owner_team_id: None,
127
+ disk_auth_mode: None,
128
+ expected_owner_team_id: None,
129
+ expected_auth_mode: None,
130
+ live_seats,
131
+ pre_first_turn,
132
+ reason,
133
+ }
134
+ }
135
+
136
+ fn unreadable_state_reason(workspace: &Path, state: &Value) -> Option<String> {
137
+ let path = workspace.join(".team").join("runtime").join("state.json");
138
+ if !path.exists() {
139
+ return None;
140
+ }
141
+ match std::fs::read_to_string(&path) {
142
+ Err(error) => Some(format!(
143
+ "unjudgeable: cannot read {} ({error})",
144
+ path.display()
145
+ )),
146
+ Ok(text) => match serde_json::from_str::<Value>(&text) {
147
+ Err(error) => Some(format!(
148
+ "unjudgeable: cannot parse {} ({error})",
149
+ path.display()
150
+ )),
151
+ Ok(_) if state.is_null() => {
152
+ Some("unjudgeable: runtime state exists but could not be loaded".to_string())
153
+ }
154
+ Ok(_) => None,
155
+ },
156
+ }
157
+ }
158
+
159
+ fn classify(
160
+ disk: DiskSlot,
161
+ live_ids: Vec<String>,
162
+ pre_first_turn: Vec<String>,
163
+ expected_owner: Option<String>,
164
+ expected_auth: Option<String>,
165
+ ) -> GrokSlotReport {
166
+ let mut mismatches = Vec::new();
167
+ if let Some(disk_id) = disk.team_agent_id.as_deref() {
168
+ mismatches.push(format!("per-seat key TEAM_AGENT_ID={disk_id}"));
169
+ }
170
+ if let Some(disk_owner) = disk.owner_team_id.as_deref() {
171
+ mismatches.push(format!("per-seat key TEAM_AGENT_OWNER_TEAM_ID={disk_owner}"));
172
+ }
173
+ if let Some(disk_auth) = disk.auth_mode.as_deref() {
174
+ mismatches.push(format!("per-seat key TEAM_AGENT_AUTH_MODE={disk_auth}"));
175
+ }
176
+ for (key, value) in &disk.extras {
177
+ mismatches.push(format!("per-seat key {key}={value}"));
178
+ }
179
+
180
+ if mismatches.is_empty() {
181
+ let reason =
182
+ "toml carries no per-seat identity keys; identity inherits from pane env".to_string();
183
+ return GrokSlotReport {
184
+ readable: true,
185
+ consistent: true,
186
+ disk_team_agent_id: disk.team_agent_id,
187
+ disk_owner_team_id: disk.owner_team_id,
188
+ disk_auth_mode: disk.auth_mode,
189
+ expected_owner_team_id: expected_owner,
190
+ expected_auth_mode: expected_auth,
191
+ live_seats: live_ids,
192
+ pre_first_turn,
193
+ reason,
194
+ };
195
+ }
196
+
197
+ GrokSlotReport {
198
+ readable: true,
199
+ consistent: false,
200
+ disk_team_agent_id: disk.team_agent_id,
201
+ disk_owner_team_id: disk.owner_team_id,
202
+ disk_auth_mode: disk.auth_mode,
203
+ expected_owner_team_id: expected_owner,
204
+ expected_auth_mode: expected_auth,
205
+ live_seats: live_ids,
206
+ pre_first_turn,
207
+ reason: format!("grok slot mismatch: {}", mismatches.join("; ")),
208
+ }
209
+ }
210
+
211
+ struct LiveGrok {
212
+ id: String,
213
+ pre_first_turn: bool,
214
+ auth_mode: Option<String>,
215
+ }
216
+
217
+ fn live_grok_from_state(state: &Value) -> Vec<LiveGrok> {
218
+ let mut out = Vec::new();
219
+ push_live_from_map(state.get("agents").and_then(Value::as_object), &mut out);
220
+ if let Some(teams) = state.get("teams").and_then(Value::as_object) {
221
+ for team in teams.values() {
222
+ push_live_from_map(team.get("agents").and_then(Value::as_object), &mut out);
223
+ }
224
+ }
225
+ out
226
+ }
227
+
228
+ fn push_live_from_map(agents: Option<&serde_json::Map<String, Value>>, out: &mut Vec<LiveGrok>) {
229
+ let Some(agents) = agents else {
230
+ return;
231
+ };
232
+ for (id, agent) in agents {
233
+ if out.iter().any(|row| row.id == *id) {
234
+ continue;
235
+ }
236
+ let provider = agent.get("provider").and_then(Value::as_str).unwrap_or("");
237
+ if provider != "grok" {
238
+ continue;
239
+ }
240
+ let status = agent
241
+ .get("status")
242
+ .and_then(Value::as_str)
243
+ .unwrap_or("running");
244
+ if matches!(
245
+ status,
246
+ "stopped" | "stopping" | "removed" | "spawn_failed" | "failed"
247
+ ) {
248
+ continue;
249
+ }
250
+ let first = agent
251
+ .get("first_send_at")
252
+ .and_then(Value::as_str)
253
+ .filter(|s| !s.is_empty());
254
+ out.push(LiveGrok {
255
+ id: id.clone(),
256
+ pre_first_turn: first.is_none(),
257
+ auth_mode: agent_auth_mode(agent),
258
+ });
259
+ }
260
+ }
261
+
262
+ fn agent_auth_mode(agent: &Value) -> Option<String> {
263
+ match agent.get("auth_mode") {
264
+ Some(Value::String(s)) if !s.trim().is_empty() => Some(s.clone()),
265
+ Some(other) if !other.is_null() => Some(other.to_string().trim_matches('"').to_string()),
266
+ _ => None,
267
+ }
268
+ }
269
+
270
+ fn expected_owner_team_id(state: &Value) -> Option<String> {
271
+ for key in ["active_team_key", "team_key", "TEAM_AGENT_OWNER_TEAM_ID"] {
272
+ if let Some(value) = state
273
+ .get(key)
274
+ .and_then(Value::as_str)
275
+ .map(str::trim)
276
+ .filter(|s| !s.is_empty())
277
+ {
278
+ return Some(value.to_string());
279
+ }
280
+ }
281
+ None
282
+ }
283
+
284
+ fn expected_auth_mode(live: &[LiveGrok]) -> Option<String> {
285
+ let mut found = None;
286
+ for seat in live {
287
+ let Some(auth) = seat.auth_mode.as_deref() else {
288
+ continue;
289
+ };
290
+ match found {
291
+ None => found = Some(auth.to_string()),
292
+ Some(ref existing) if existing != auth => return None,
293
+ Some(_) => {}
294
+ }
295
+ }
296
+ found
297
+ }
298
+
299
+
@@ -1,5 +1,5 @@
1
1
  //!
2
- //! cli · leader — `codex`/`claude`/`copilot` passthrough(`cmd_leader_passthrough` + `_provider_args` /
2
+ //! cli · leader — `codex`/`claude`/`copilot`/`grok`/`cursor` passthrough(`cmd_leader_passthrough` + `_provider_args` /
3
3
  //! `_leader_launcher_args`)+ leader fallback inbox 摘要(`consume_leader_inbox_summary` 及
4
4
  //! `_leader_inbox_entries` / `_leader_inbox_summary` / `_leader_inbox_entry_title`)。
5
5
 
@@ -96,9 +96,9 @@ fn without_leader_json(values: &[String]) -> Vec<String> {
96
96
  }
97
97
 
98
98
  ///
99
- /// `codex`/`claude`/`copilot` passthrough(`parser.py:86`/`_run_leader_passthrough`):leader 早返回,
99
+ /// `codex`/`claude`/`copilot`/`grok`/`cursor` passthrough(`parser.py:86`/`_run_leader_passthrough`):leader 早返回,
100
100
  /// **不**进 subparser。`-h`/`--help` 打 usage 直接返回 [`CmdResult::none`]。否则解析 attach
101
- /// 旗标 + `lifecycle_port::start_leader`。`command` ∈ {codex, claude, copilot}。
101
+ /// 旗标 + `lifecycle_port::start_leader`。`command` 必须是闭集动词,禁止 command-text 回落到 ClaudeCode。
102
102
  pub fn cmd_leader_passthrough(
103
103
  command: &str,
104
104
  provider_args: &[String],
@@ -110,14 +110,29 @@ pub fn cmd_leader_passthrough(
110
110
  let as_json = leader_launcher_json(provider_args);
111
111
  let launcher_args = without_leader_json(provider_args);
112
112
  let attach = leader_launcher_args(&launcher_args)?;
113
- let provider = leader_passthrough_provider(command);
113
+ let provider = leader_passthrough_provider(command).ok_or_else(|| {
114
+ CliError::Usage(format!(
115
+ "unknown leader passthrough command: {command}"
116
+ ))
117
+ })?;
114
118
  let value = lifecycle_port::start_leader(provider, &attach.provider_args, cwd, &attach)?;
115
119
  Ok(CmdResult::from_json(value, as_json))
116
120
  }
117
121
 
118
- pub(crate) fn leader_passthrough_provider(command: &str) -> crate::model::enums::Provider {
119
- crate::leader::attribute_command_provider(command)
120
- .unwrap_or(crate::model::enums::Provider::ClaudeCode)
122
+ /// Closed-set verb → Provider. `None` = not a passthrough verb (must not become ClaudeCode).
123
+ pub(crate) fn leader_passthrough_provider(
124
+ command: &str,
125
+ ) -> Option<crate::model::enums::Provider> {
126
+ use crate::model::enums::Provider;
127
+ match command {
128
+ "codex" => Some(Provider::Codex),
129
+ "claude" => Some(Provider::ClaudeCode),
130
+ "copilot" => Some(Provider::Copilot),
131
+ "grok" => Some(Provider::Grok),
132
+ "cursor" => Some(Provider::CursorAgent),
133
+ "pi" => Some(Provider::Pi),
134
+ _ => None,
135
+ }
121
136
  }
122
137
 
123
138
  // =============================================================================