@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
@@ -4081,6 +4081,9 @@ pub mod lifecycle_port {
4081
4081
  /// `run_comms_selftest`、`diagnose/orphan_cleanup.py` `orphan_gate`/`cleanup_orphan_coordinators`、
4082
4082
  /// `message_store/schema_migration.py` `schema_diagnosis`/`fix_schema_layout`)。
4083
4083
  /// `cmd_doctor` 的所有分支委派点。返回 `Value`(稳定 JSON 形状由 diagnose lane 拥有)。
4084
+ pub mod grok_slot;
4085
+ pub(crate) use grok_slot::grok_slot_report;
4086
+
4084
4087
  pub mod diagnose_port {
4085
4088
  use super::*;
4086
4089
 
@@ -4134,6 +4137,13 @@ pub mod diagnose_port {
4134
4137
  let health = crate::coordinator::coordinator_health(
4135
4138
  &crate::coordinator::WorkspacePath::new(workspace.to_path_buf()),
4136
4139
  );
4140
+ let coordinator_ok = health.ok;
4141
+ let coordinator_error = health.metadata_mismatch_reason.clone();
4142
+ let state = crate::state::persist::load_runtime_state(workspace).unwrap_or(Value::Null);
4143
+ let grok_slot = crate::cli::grok_slot_report(workspace, &state).to_json();
4144
+ let grok_slot_ok = grok_slot.get("consistent").and_then(Value::as_bool) == Some(true)
4145
+ && grok_slot.get("readable").and_then(Value::as_bool) == Some(true);
4146
+ let ok = ok && grok_slot_ok && coordinator_ok;
4137
4147
  Ok(json!({
4138
4148
  "tmux": {
4139
4149
  "installed": tmux_installed,
@@ -4149,11 +4159,21 @@ pub mod diagnose_port {
4149
4159
  "secret_scan": secret_scan(workspace),
4150
4160
  "profile_smoke": profile_smoke_value,
4151
4161
  "coordinator": coordinator_health_value(health),
4162
+ "grok_slot": grok_slot,
4152
4163
  "ok": ok,
4153
4164
  "error": if ok {
4154
4165
  Value::Null
4166
+ } else if !grok_slot_ok {
4167
+ grok_slot
4168
+ .get("reason")
4169
+ .cloned()
4170
+ .unwrap_or_else(|| json!("grok_slot_mismatch"))
4155
4171
  } else if !profile_smoke_ok && !legacy_only_failure {
4156
4172
  json!("profile_smoke_failed")
4173
+ } else if !coordinator_ok {
4174
+ coordinator_error
4175
+ .map(Value::String)
4176
+ .unwrap_or_else(|| json!("coordinator_unavailable"))
4157
4177
  } else if workspace_valid {
4158
4178
  json!("workspace has no Team Agent spec or runtime context")
4159
4179
  } else {
@@ -3,6 +3,7 @@ use crate::cli::{CliError, CmdOutput, CmdResult, ExitCode};
3
3
  use crate::messaging::{
4
4
  DeliveryOutcome, DeliveryRefusal, DeliveryStage, DeliveryStatus, MessageTarget, SendOptions,
5
5
  };
6
+ use crate::transport::{turn_verification_wire, TurnVerification};
6
7
  use serde_json::{json, Value};
7
8
 
8
9
  pub(super) fn watch_notice_json(target: &MessageTarget, opts: &SendOptions) -> Value {
@@ -113,6 +114,11 @@ pub(super) fn delivery_outcome_json(
113
114
  "stage": outcome.stage.map(delivery_stage_wire),
114
115
  "reason": outcome.reason.map(delivery_refusal_wire),
115
116
  "channel": outcome.channel,
117
+ "turn_verification": turn_verification_wire(
118
+ outcome
119
+ .turn_verification
120
+ .unwrap_or(TurnVerification::NotYetObserved),
121
+ ),
116
122
  })
117
123
  }
118
124
 
@@ -183,7 +189,7 @@ pub(super) fn send_human_output(value: &Value) -> String {
183
189
  send_human_field(value, "message_id"),
184
190
  format!("target: {}", send_human_target(value)),
185
191
  ];
186
- for key in ["verification", "stage", "reason", "channel"] {
192
+ for key in ["verification", "stage", "reason", "channel", "turn_verification"] {
187
193
  if !value.get(key).is_none_or(Value::is_null) {
188
194
  parts.push(send_human_field(value, key));
189
195
  }
@@ -179,6 +179,8 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
179
179
  CommandSpec { name: "codex", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "codex" }, summary: "launch a Codex leader", usage: "usage: team-agent codex [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, alias_of: None, sunset: None, action: None, governance: None },
180
180
  CommandSpec { name: "claude", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "claude" }, summary: "launch a Claude leader", usage: "usage: team-agent claude [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, alias_of: None, sunset: None, action: None, governance: None },
181
181
  CommandSpec { name: "copilot", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "copilot" }, summary: "launch a Copilot leader", usage: "usage: team-agent copilot [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, alias_of: None, sunset: None, action: None, governance: None },
182
+ CommandSpec { name: "grok", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "grok" }, summary: "launch a Grok leader", usage: "usage: team-agent grok [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, alias_of: None, sunset: None, action: None, governance: None },
183
+ CommandSpec { name: "cursor", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::LeaderPassthrough { provider: "cursor" }, summary: "launch a Cursor leader", usage: "usage: team-agent cursor [provider args...]", default_help: false, command_help: true, suggestion_index: true, token_usage: TokenUsage::Yes, alias_of: None, sunset: None, action: None, governance: None },
182
184
 
183
185
  CommandSpec { name: "init", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::Init), summary: "legacy bootstrap command", usage: "usage: team-agent init [--workspace WORKSPACE] [--force] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: Some("quick-start"), sunset: Some("C2"), action: Some("use `team-agent quick-start`"), governance: None },
184
186
  CommandSpec { name: "start", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::SpecOnlyAlias { alias_of: "quick-start" }, summary: "legacy start alias", usage: "usage: team-agent start [TEAMDIR] [--yes] [--fresh] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::Conditional, alias_of: Some("quick-start"), sunset: Some("C2"), action: Some("use `team-agent quick-start` or `team-agent restart`"), governance: None },
@@ -4,6 +4,9 @@ use super::*;
4
4
  pub(super) fn compact_status(full: Value) -> Value {
5
5
  let not_ready = compact_not_ready(&full);
6
6
  let ready = compact_ready(&full, &not_ready);
7
+ let grok_slot = full.get("grok_slot").cloned().unwrap_or(Value::Null);
8
+ let grok_identity_slot_ok = grok_slot.get("consistent").and_then(Value::as_bool) == Some(true)
9
+ && grok_slot.get("readable").and_then(Value::as_bool) == Some(true);
7
10
  json!({
8
11
  "ok": true,
9
12
  "team": full.get("team").cloned().unwrap_or(Value::Null),
@@ -11,6 +14,8 @@ pub(super) fn compact_status(full: Value) -> Value {
11
14
  "leader_attach_command": full.get("leader_attach_command").cloned().unwrap_or(Value::Null),
12
15
  "ready": ready,
13
16
  "not_ready": not_ready,
17
+ "grok_slot": grok_slot,
18
+ "grok_identity_slot_ok": grok_identity_slot_ok,
14
19
  "agents": compact_agents(full.get("agents")),
15
20
  })
16
21
  }
@@ -55,11 +60,20 @@ pub(super) fn compact_not_ready(full: &Value) -> Value {
55
60
  })
56
61
  .unwrap_or_default();
57
62
  let mut obj = Map::new();
63
+ let channel_unbound = reasons
64
+ .iter()
65
+ .any(|reason| reason == "leader_receiver_unbound");
58
66
  obj.insert(
59
67
  "reasons".to_string(),
60
68
  Value::Array(reasons.into_iter().map(Value::String).collect()),
61
69
  );
62
70
  obj.insert("agents".to_string(), Value::Array(agents));
71
+ if channel_unbound {
72
+ obj.insert(
73
+ "next_action".to_string(),
74
+ Value::String("claim-leader".to_string()),
75
+ );
76
+ }
63
77
  Value::Object(obj)
64
78
  }
65
79
 
@@ -112,6 +126,20 @@ pub(super) fn not_ready_reasons(full: &Value) -> Vec<String> {
112
126
  {
113
127
  reasons.push("awaiting_trust_prompt".to_string());
114
128
  }
129
+ let grok_slot = full.get("grok_slot");
130
+ let grok_readable = grok_slot
131
+ .and_then(|slot| slot.get("readable"))
132
+ .and_then(Value::as_bool);
133
+ let grok_consistent = grok_slot
134
+ .and_then(|slot| slot.get("consistent"))
135
+ .and_then(Value::as_bool);
136
+ if grok_readable != Some(true) || grok_consistent != Some(true) {
137
+ if grok_readable == Some(false) || grok_slot.is_none() || grok_slot == Some(&Value::Null) {
138
+ reasons.push("grok_slot_unjudgeable".to_string());
139
+ } else {
140
+ reasons.push("grok_slot_mismatch".to_string());
141
+ }
142
+ }
115
143
  reasons
116
144
  }
117
145
 
@@ -90,7 +90,29 @@ impl RuntimeSnapshot {
90
90
  serde_json::json!(tmux_present),
91
91
  );
92
92
  }
93
- let readiness = crate::cli::diagnose::wait_readiness(&readiness_state);
93
+ let mut readiness = crate::cli::diagnose::wait_readiness(&readiness_state);
94
+ let team_key = owner_team_id
95
+ .filter(|team| !team.is_empty())
96
+ .map(str::to_string)
97
+ .unwrap_or_else(|| crate::state::projection::team_state_key(state));
98
+ if !team_key.is_empty()
99
+ && !crate::lifecycle::launch::launched_team_receiver_is_attached(workspace, &team_key)
100
+ {
101
+ if let Some(object) = readiness.as_object_mut() {
102
+ object.insert("all_attached_receiver".to_string(), json!(false));
103
+ object.insert("ready".to_string(), json!(false));
104
+ object.insert("state".to_string(), json!("leader_receiver_unbound"));
105
+ object.insert("next_action".to_string(), json!("claim-leader"));
106
+ object.insert(
107
+ "reason".to_string(),
108
+ json!("leader registry does not authorize this workspace"),
109
+ );
110
+ }
111
+ }
112
+ let grok_slot = crate::cli::grok_slot_report(workspace, state).to_json();
113
+ let grok_identity_slot_ok = grok_slot.get("consistent").and_then(Value::as_bool)
114
+ == Some(true)
115
+ && grok_slot.get("readable").and_then(Value::as_bool) == Some(true);
94
116
  let full = crate::redaction::redact_external_value(&json!({
95
117
  "ok": true,
96
118
  "team": state.pointer("/leader/id").cloned().unwrap_or_else(|| json!("leader")),
@@ -120,6 +142,8 @@ impl RuntimeSnapshot {
120
142
  "readiness": readiness,
121
143
  "coordinator": coordinator_health_value(health),
122
144
  "runtime": runtime_block,
145
+ "grok_slot": grok_slot,
146
+ "grok_identity_slot_ok": grok_identity_slot_ok,
123
147
  "reminder": crate::cli::STATUS_REMINDER,
124
148
  "last_events": Value::Array(
125
149
  crate::event_log::EventLog::new(workspace)
@@ -753,20 +753,43 @@ fn cmd_leader_passthrough_help_returns_none() {
753
753
  assert_eq!(r2.output, CmdOutput::None);
754
754
  let r3 = cmd_leader_passthrough("copilot", &["--help".into()], Path::new(".")).unwrap();
755
755
  assert_eq!(r3.output, CmdOutput::None);
756
+ let r4 = cmd_leader_passthrough("grok", &["-h".into()], Path::new(".")).unwrap();
757
+ assert_eq!(r4.output, CmdOutput::None);
758
+ let r5 = cmd_leader_passthrough("cursor", &["--help".into()], Path::new(".")).unwrap();
759
+ assert_eq!(r5.output, CmdOutput::None);
756
760
  }
757
761
 
758
762
  #[test]
759
763
  fn cmd_leader_passthrough_maps_copilot_provider() {
760
764
  assert_eq!(
761
765
  leader_passthrough_provider("codex"),
762
- crate::model::enums::Provider::Codex
766
+ Some(crate::model::enums::Provider::Codex)
763
767
  );
764
768
  assert_eq!(
765
769
  leader_passthrough_provider("claude"),
766
- crate::model::enums::Provider::ClaudeCode
770
+ Some(crate::model::enums::Provider::ClaudeCode)
767
771
  );
768
772
  assert_eq!(
769
773
  leader_passthrough_provider("copilot"),
770
- crate::model::enums::Provider::Copilot
774
+ Some(crate::model::enums::Provider::Copilot)
771
775
  );
772
776
  }
777
+
778
+ #[test]
779
+ fn cmd_leader_passthrough_maps_grok_and_cursor_not_claude() {
780
+ assert_eq!(
781
+ leader_passthrough_provider("grok"),
782
+ Some(crate::model::enums::Provider::Grok)
783
+ );
784
+ assert_eq!(
785
+ leader_passthrough_provider("cursor"),
786
+ Some(crate::model::enums::Provider::CursorAgent)
787
+ );
788
+ assert_ne!(
789
+ leader_passthrough_provider("cursor"),
790
+ Some(crate::model::enums::Provider::ClaudeCode)
791
+ );
792
+ assert_eq!(leader_passthrough_provider("agent"), None);
793
+ assert_eq!(leader_passthrough_provider("cursor_agent"), None);
794
+ assert_eq!(leader_passthrough_provider("not-a-passthrough"), None);
795
+ }
@@ -37,6 +37,75 @@ impl Drop for WorkspaceCleanup {
37
37
  }
38
38
  }
39
39
 
40
+ struct IsolatedHome {
41
+ prev: Option<String>,
42
+ dir: std::path::PathBuf,
43
+ }
44
+
45
+ impl IsolatedHome {
46
+ fn enter(tag: &str) -> Self {
47
+ let base = std::env::var_os("TEAM_AGENT_TEST_TMP")
48
+ .map(std::path::PathBuf::from)
49
+ .unwrap_or_else(std::env::temp_dir);
50
+ let dir = base.join(format!(
51
+ "ta-cli-home-{tag}-{}-{}",
52
+ std::process::id(),
53
+ std::time::SystemTime::now()
54
+ .duration_since(std::time::UNIX_EPOCH)
55
+ .unwrap()
56
+ .as_nanos()
57
+ ));
58
+ std::fs::create_dir_all(dir.join(".team-agent").join("leaders")).unwrap();
59
+ let prev = std::env::var("HOME").ok();
60
+ std::env::set_var("HOME", &dir);
61
+ Self { prev, dir }
62
+ }
63
+ }
64
+
65
+ impl Drop for IsolatedHome {
66
+ fn drop(&mut self) {
67
+ match &self.prev {
68
+ Some(value) => std::env::set_var("HOME", value),
69
+ None => std::env::remove_var("HOME"),
70
+ }
71
+ let _ = std::fs::remove_dir_all(&self.dir);
72
+ }
73
+ }
74
+
75
+ fn seed_self_signed_attached_leader(ws: &std::path::Path) {
76
+ std::fs::write(
77
+ ws.join(".team").join("runtime").join("state.json"),
78
+ serde_json::to_vec(&json!({
79
+ "team_key": "current",
80
+ "leader": {"id": "leader"},
81
+ "leader_receiver": {
82
+ "mode": "direct_tmux",
83
+ "status": "attached",
84
+ "pane_id": "%1",
85
+ "provider": "codex"
86
+ },
87
+ }))
88
+ .unwrap(),
89
+ )
90
+ .unwrap();
91
+ }
92
+
93
+ fn seed_leader_registry_entry(ws: &std::path::Path) {
94
+ let entry = crate::leader::registry::build_entry(
95
+ ws,
96
+ "current",
97
+ "direct_tmux",
98
+ json!({"status": "attached", "pane_id": "%1"}),
99
+ 1,
100
+ "diagnose-healthy-fixture",
101
+ "2026-08-18T00:00:00Z".to_string(),
102
+ );
103
+ assert!(
104
+ crate::leader::registry::write_entry_best_effort(&entry).is_some(),
105
+ "hermetic HOME must accept a leaders/ registry write"
106
+ );
107
+ }
108
+
40
109
  // =========================================================================
41
110
  // WAVE-2 NON-SUB CHECKPOINT — missing CLI subcommands (ABSENT from cli/emit.rs dispatch).
42
111
  // emit.rs `dispatch` has NO arm for sessions, peek, collect, e2e, diagnose,
@@ -255,33 +324,74 @@ fn dispatch_routes_collect_with_spec() {
255
324
  );
256
325
  }
257
326
 
258
- // ── diagnose ── golden parser.py:298 `cmd_diagnose` -> runtime.diagnose(ws) (diagnose/health.py:19).
259
- // ok:true only when there are zero issues. Seed leader_receiver=attached + NO session_name + NO
260
- // agents -> issues=[] -> EXIT 0, golden top keys (sort): event_log,issues,ok,runtime,suggested_repairs.
261
- // RED: unrouted -> Error.
327
+ // ── diagnose ── attached is host-registry authority, not state.json self-sign.
328
+ // Seed leader_receiver=attached + a ~/.team-agent/leaders/ row for this workspace
329
+ // + NO session_name + NO agents -> issues=[] -> EXIT 0.
262
330
  #[test]
331
+ #[serial(env)]
263
332
  fn dispatch_routes_diagnose_healthy_leader() {
333
+ let _env = EnvUnsetGuard::unset(&[
334
+ "TMUX",
335
+ "TMUX_PANE",
336
+ "TEAM_AGENT_WORKSPACE",
337
+ "TEAM_AGENT_TEAM_ID",
338
+ "TEAM_AGENT_OWNER_TEAM_ID",
339
+ "TEAM_AGENT_ACTIVE_TEAM",
340
+ "TEAM_AGENT_ID",
341
+ "TEAM_AGENT_LEADER_PANE_ID",
342
+ "TEAM_AGENT_LEADER_SESSION_UUID",
343
+ "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
344
+ "TEAM_AGENT_LEADER_PROVIDER",
345
+ ]);
346
+ let _home = IsolatedHome::enter("diagnose-healthy");
264
347
  let ws = tmp_workspace();
265
- std::fs::write(
266
- ws.join(".team").join("runtime").join("state.json"),
267
- serde_json::to_vec(&json!({
268
- "leader": {"id": "leader"},
269
- "leader_receiver": {"mode": "direct_tmux", "status": "attached", "pane_id": "%1", "provider": "codex"},
270
- }))
271
- .unwrap(),
272
- )
273
- .unwrap();
348
+ seed_self_signed_attached_leader(&ws);
349
+ seed_leader_registry_entry(&ws);
274
350
  let code = run(
275
351
  &cli_argv(&["diagnose", "--workspace", &ws.to_string_lossy(), "--json"]),
276
352
  &ws,
277
353
  );
278
354
  assert_eq!(
279
- code,
280
- ExitCode::Ok,
281
- "`diagnose` must ROUTE to cmd_diagnose (parser.py:298); a healthy (attached, no-session, \
282
- no-agent) state yields zero issues -> exit 0 {{event_log,issues,ok,runtime,suggested_repairs}}; \
283
- today -> unknown-subcommand Error"
284
- );
355
+ code,
356
+ ExitCode::Ok,
357
+ "`diagnose` must be Ok when state.json looks attached AND ~/.team-agent/leaders/ \
358
+ has a matching attached row; got {code:?}"
359
+ );
360
+ let _ = std::fs::remove_dir_all(&ws);
361
+ }
362
+
363
+ #[test]
364
+ #[serial(env)]
365
+ fn dispatch_routes_diagnose_self_signed_attached_without_registry_is_not_ok() {
366
+ // 已废除的行为:旧实现只信 `state.json` 的自签 attached(F2 谎话本体),此断言证明它确实没了。
367
+ // 夹具只种 leader_receiver.status=attached,注册表无该 workspace 条目。
368
+ // 旧 diagnose 会因此报 Ok;新 diagnose 必须以注册表为可投递权威,判非 Ok。
369
+ let _env = EnvUnsetGuard::unset(&[
370
+ "TMUX",
371
+ "TMUX_PANE",
372
+ "TEAM_AGENT_WORKSPACE",
373
+ "TEAM_AGENT_TEAM_ID",
374
+ "TEAM_AGENT_OWNER_TEAM_ID",
375
+ "TEAM_AGENT_ACTIVE_TEAM",
376
+ "TEAM_AGENT_ID",
377
+ "TEAM_AGENT_LEADER_PANE_ID",
378
+ "TEAM_AGENT_LEADER_SESSION_UUID",
379
+ "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
380
+ "TEAM_AGENT_LEADER_PROVIDER",
381
+ ]);
382
+ let _home = IsolatedHome::enter("diagnose-f2-tombstone");
383
+ let ws = tmp_workspace();
384
+ seed_self_signed_attached_leader(&ws);
385
+ let code = run(
386
+ &cli_argv(&["diagnose", "--workspace", &ws.to_string_lossy(), "--json"]),
387
+ &ws,
388
+ );
389
+ assert_ne!(
390
+ code,
391
+ ExitCode::Ok,
392
+ "self-signed state.json attached with no leaders/ row must not diagnose Ok (F2 lie is dead); \
393
+ got {code:?}"
394
+ );
285
395
  let _ = std::fs::remove_dir_all(&ws);
286
396
  }
287
397