@team-agent/installer 0.5.65 → 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 (208) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +6 -0
  4. package/crates/team-agent/src/cli/adapters.rs +6 -1
  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/compile.rs +1 -1
  17. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  18. package/crates/team-agent/src/cli/tests/mod.rs +2 -2
  19. package/crates/team-agent/src/cli/tests/run_delegation.rs +1 -1
  20. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  21. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  22. package/crates/team-agent/src/cli/tests/verb_validate.rs +1 -1
  23. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  24. package/crates/team-agent/src/compiler/tests.rs +23 -20
  25. package/crates/team-agent/src/compiler.rs +84 -5
  26. package/crates/team-agent/src/conpty/backend.rs +16 -0
  27. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  28. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  29. package/crates/team-agent/src/coordinator/health.rs +173 -0
  30. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  31. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  32. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  33. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  34. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  35. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  36. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  37. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  38. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  39. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  40. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  41. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  42. package/crates/team-agent/src/coordinator/types.rs +49 -0
  43. package/crates/team-agent/src/db/message_store.rs +39 -5
  44. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  45. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  46. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  47. package/crates/team-agent/src/leader/registry.rs +65 -0
  48. package/crates/team-agent/src/leader/start.rs +1080 -58
  49. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1 -1
  50. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  51. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  52. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -14
  53. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +69 -2
  54. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +196 -2
  55. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  56. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  57. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  58. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -457
  59. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  60. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  61. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  62. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  63. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  64. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  65. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  66. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  67. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  68. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  69. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  70. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  71. package/crates/team-agent/src/lifecycle/launch/spawn.rs +101 -4
  72. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +244 -48
  73. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -2
  74. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  75. package/crates/team-agent/src/lifecycle/launch.rs +209 -43
  76. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  77. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  78. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  79. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  80. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  81. package/crates/team-agent/src/lifecycle/restart/agent.rs +213 -3
  82. package/crates/team-agent/src/lifecycle/restart/common.rs +373 -18
  83. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  84. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  85. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +165 -15
  86. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  87. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  88. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  89. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  90. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -1
  91. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +3 -4
  92. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +1 -1
  93. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +4 -4
  95. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  96. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +159 -174
  97. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +15 -13
  99. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -18
  100. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  102. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  103. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  104. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +1 -1
  105. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +1 -1
  106. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  107. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  108. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  109. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  110. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  111. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  112. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -0
  113. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +3 -3
  114. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +65 -40
  115. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +20 -32
  116. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +17 -9
  117. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  118. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +24 -23
  119. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +98 -62
  120. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +2 -2
  121. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +1 -1
  122. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +1 -1
  123. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -1
  124. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +2 -2
  125. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +1 -1
  126. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +1 -1
  127. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +41 -8
  128. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +3 -3
  129. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1 -1
  130. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +1 -1
  131. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -1
  132. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1 -1
  133. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1 -1
  134. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +1 -1
  135. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +60 -2
  136. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +1 -1
  137. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -2
  138. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  139. package/crates/team-agent/src/lifecycle/tests.rs +23 -15
  140. package/crates/team-agent/src/lifecycle/types.rs +61 -2
  141. package/crates/team-agent/src/lifecycle/worker_command_context.rs +200 -33
  142. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  143. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  144. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  145. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  146. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  147. package/crates/team-agent/src/messaging/results.rs +4 -0
  148. package/crates/team-agent/src/messaging/send.rs +34 -0
  149. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  150. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  151. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  152. package/crates/team-agent/src/messaging/tests/main_preserved.rs +1 -1
  153. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  154. package/crates/team-agent/src/messaging/types.rs +4 -1
  155. package/crates/team-agent/src/model/enums.rs +14 -5
  156. package/crates/team-agent/src/model/permissions.rs +3 -0
  157. package/crates/team-agent/src/model/spec.rs +129 -6
  158. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +3 -1
  159. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +3 -1
  160. package/crates/team-agent/src/model/testdata/team.spec.yaml +3 -1
  161. package/crates/team-agent/src/os_probe.rs +73 -2
  162. package/crates/team-agent/src/provider/adapter.rs +231 -4
  163. package/crates/team-agent/src/provider/adapters/claude.rs +5 -2
  164. package/crates/team-agent/src/provider/adapters/codex.rs +5 -2
  165. package/crates/team-agent/src/provider/adapters/copilot.rs +5 -2
  166. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  167. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  168. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  169. package/crates/team-agent/src/provider/bypass_flags.rs +124 -0
  170. package/crates/team-agent/src/provider/classify.rs +4 -2
  171. package/crates/team-agent/src/provider/faults.rs +2 -1
  172. package/crates/team-agent/src/provider/mod.rs +13 -0
  173. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  174. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  175. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  176. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  177. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  178. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  179. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  180. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  181. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  182. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  183. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  184. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  185. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  186. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  187. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  188. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  189. package/crates/team-agent/src/provider/types.rs +4 -2
  190. package/crates/team-agent/src/provider/wire.rs +23 -1
  191. package/crates/team-agent/src/state/persist.rs +301 -8
  192. package/crates/team-agent/src/state/repository.rs +5 -0
  193. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  194. package/crates/team-agent/src/tmux_backend.rs +1083 -75
  195. package/crates/team-agent/src/transport/test_support.rs +20 -0
  196. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  197. package/crates/team-agent/src/transport.rs +265 -1
  198. package/npm/install.mjs +129 -73
  199. package/package.json +4 -4
  200. package/skills/team-agent/SKILL.md +33 -238
  201. package/skills/team-agent/command-coverage.json +31 -0
  202. package/crates/team-agent/src/lifecycle/launch/approval.rs +0 -134
  203. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  204. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -483
  205. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  206. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
  207. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +0 -1187
  208. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +0 -327
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.65"
584
+ version = "0.5.68"
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.65"
12
+ version = "0.5.68"
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
@@ -57,5 +58,10 @@ serial_test = { version = "3", features = ["file_locks"] }
57
58
  name = "e2e"
58
59
  path = "tests/e2e/main.rs"
59
60
 
61
+ # 0.5.66 bypass 单源:E2E 验证角色字段 → 运行面 → drift 挡板 → force fresh spawn。
62
+ [[test]]
63
+ name = "e2e_bypass_single_source"
64
+ path = "tests/e2e_bypass_single_source.rs"
65
+
60
66
  [lints]
61
67
  workspace = true
@@ -75,7 +75,7 @@ pub fn cmd_init(args: &InitArgs) -> Result<CmdResult, CliError> {
75
75
  if args.force || !default_agent_path.exists() {
76
76
  std::fs::write(
77
77
  &default_agent_path,
78
- "---\nname: worker\nrole: Worker\nprovider: fake\ntools:\n - mcp_team\n---\n\nWait for instructions.\n",
78
+ "---\nname: worker\nrole: Worker\nprovider: fake\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWait for instructions.\n",
79
79
  )?;
80
80
  }
81
81
  if args.force || !state_path.exists() {
@@ -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))
@@ -208,12 +208,45 @@ pub(crate) fn diagnose_runtime_for_workspace(
208
208
  &mut issues,
209
209
  &mut repairs,
210
210
  );
211
+ append_registry_channel_unbound_issue(workspace, state, &mut issues, &mut repairs);
211
212
  append_legacy_snapshot_issue(workspace, state, &mut issues);
212
213
  append_coordinator_health_issue(workspace, state, &mut issues, &mut repairs);
213
214
  append_runtime_bindings_stale_after_boot_issue(workspace, state, &mut issues, &mut repairs);
214
215
  (issues, repairs)
215
216
  }
216
217
 
218
+ fn append_registry_channel_unbound_issue(
219
+ workspace: &std::path::Path,
220
+ state: &Value,
221
+ issues: &mut Value,
222
+ repairs: &mut Value,
223
+ ) {
224
+ let team_key = crate::state::projection::team_state_key(state);
225
+ if team_key.is_empty()
226
+ || crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
227
+ {
228
+ return;
229
+ }
230
+ if let Some(items) = issues.as_array_mut() {
231
+ if !items.iter().any(|item| {
232
+ item.as_str() == Some("leader_not_attached")
233
+ || item.get("id").and_then(Value::as_str) == Some("leader_channel_unbound")
234
+ }) {
235
+ items.push(json!("leader_not_attached"));
236
+ }
237
+ }
238
+ if let Some(items) = repairs.as_array_mut() {
239
+ items.push(recovery_hint(
240
+ state
241
+ .get("session_name")
242
+ .and_then(Value::as_str)
243
+ .unwrap_or(team_key.as_str()),
244
+ "leader_not_attached",
245
+ "team-agent claim-leader",
246
+ ));
247
+ }
248
+ }
249
+
217
250
  fn append_live_leader_workspace_mismatch_issue(
218
251
  workspace: &std::path::Path,
219
252
  state: &Value,
@@ -339,6 +372,53 @@ fn live_leader_workspace_mismatch(
339
372
  Some((issue, repair))
340
373
  }
341
374
 
375
+ pub(crate) fn append_registry_channel_unbound_to_report(
376
+ workspace: &std::path::Path,
377
+ team: Option<&str>,
378
+ report: &mut Value,
379
+ ) {
380
+ let team_key = team
381
+ .filter(|team| !team.is_empty())
382
+ .map(str::to_string)
383
+ .or_else(|| {
384
+ crate::state::persist::load_runtime_state(workspace)
385
+ .ok()
386
+ .map(|state| crate::state::projection::team_state_key(&state))
387
+ })
388
+ .unwrap_or_default();
389
+ if team_key.is_empty()
390
+ || crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
391
+ {
392
+ return;
393
+ }
394
+ let Some(object) = report.as_object_mut() else {
395
+ return;
396
+ };
397
+ let mut issues = object
398
+ .get("issues")
399
+ .and_then(Value::as_array)
400
+ .cloned()
401
+ .unwrap_or_default();
402
+ if !issues.iter().any(|item| {
403
+ item.as_str() == Some("leader_not_attached")
404
+ || item.get("id").and_then(Value::as_str) == Some("leader_channel_unbound")
405
+ }) {
406
+ issues.push(json!("leader_not_attached"));
407
+ }
408
+ object.insert("issues".to_string(), Value::Array(issues));
409
+ let mut repairs = object
410
+ .get("suggested_repairs")
411
+ .and_then(Value::as_array)
412
+ .cloned()
413
+ .unwrap_or_default();
414
+ repairs.push(recovery_hint(
415
+ &team_key,
416
+ "leader_not_attached",
417
+ "team-agent claim-leader",
418
+ ));
419
+ object.insert("suggested_repairs".to_string(), Value::Array(repairs));
420
+ }
421
+
342
422
  pub(crate) fn append_selected_live_leader_workspace_mismatch(
343
423
  workspace: &std::path::Path,
344
424
  team: Option<&str>,
@@ -1485,6 +1565,8 @@ pub(crate) fn provider_doctor_checks() -> Value {
1485
1565
  crate::provider::Provider::ClaudeCode,
1486
1566
  crate::provider::Provider::Codex,
1487
1567
  crate::provider::Provider::GeminiCli,
1568
+ crate::provider::Provider::Grok,
1569
+ crate::provider::Provider::CursorAgent,
1488
1570
  crate::provider::Provider::Fake,
1489
1571
  ] {
1490
1572
  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,7 +262,7 @@ 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] = &["codex", "claude", "copilot", "grok", "cursor"];
266
266
 
267
267
  fn is_leader_passthrough_command(command: &str) -> bool {
268
268
  LEADER_PASSTHROUGH_COMMANDS.contains(&command)
@@ -310,7 +310,7 @@ fn default_help() -> String {
310
310
  "Guided recovery",
311
311
  &["claim-leader", "takeover", "attach-leader"],
312
312
  );
313
- out.push_str("\nProvider launchers:\n team-agent codex|claude|copilot ...\n");
313
+ out.push_str("\nProvider launchers:\n team-agent codex|claude|copilot|grok|cursor ...\n");
314
314
  out.push_str("\nRun `team-agent <command> --help` for command flags.");
315
315
  out
316
316
  }
@@ -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,28 @@ 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
+ _ => None,
134
+ }
121
135
  }
122
136
 
123
137
  // =============================================================================