@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.
- package/Cargo.lock +8 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/Cargo.toml +1 -0
- package/crates/team-agent/src/cli/adapters.rs +5 -0
- package/crates/team-agent/src/cli/diagnose.rs +156 -0
- package/crates/team-agent/src/cli/emit.rs +6 -5
- package/crates/team-agent/src/cli/grok_slot.rs +299 -0
- package/crates/team-agent/src/cli/leader.rs +22 -7
- package/crates/team-agent/src/cli/leaders.rs +125 -1
- package/crates/team-agent/src/cli/mod.rs +20 -0
- package/crates/team-agent/src/cli/send/presentation.rs +7 -1
- package/crates/team-agent/src/cli/spec.rs +3 -0
- package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
- package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
- package/crates/team-agent/src/cli/tests/base.rs +26 -3
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
- package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
- package/crates/team-agent/src/communication_mode/mod.rs +6 -4
- package/crates/team-agent/src/compiler.rs +121 -5
- package/crates/team-agent/src/coordinator/backoff.rs +55 -0
- package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
- package/crates/team-agent/src/coordinator/health.rs +173 -0
- package/crates/team-agent/src/coordinator/mod.rs +31 -0
- package/crates/team-agent/src/coordinator/orphan.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +31 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
- package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
- package/crates/team-agent/src/coordinator/tick.rs +133 -22
- package/crates/team-agent/src/coordinator/types.rs +49 -0
- package/crates/team-agent/src/db/message_store.rs +39 -5
- package/crates/team-agent/src/layout/worker_env.rs +20 -3
- package/crates/team-agent/src/layout/worker_window_helpers.rs +3 -0
- package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
- package/crates/team-agent/src/leader/registry.rs +65 -0
- package/crates/team-agent/src/leader/start.rs +973 -75
- package/crates/team-agent/src/lifecycle/display.rs +56 -0
- package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +398 -26
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +80 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
- package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +548 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +120 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +208 -456
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
- package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
- package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
- package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +482 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +830 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +87 -3
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
- package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +127 -25
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +140 -0
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
- package/crates/team-agent/src/lifecycle/launch.rs +107 -29
- package/crates/team-agent/src/lifecycle/lock.rs +42 -1
- package/crates/team-agent/src/lifecycle/mod.rs +11 -0
- package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
- package/crates/team-agent/src/lifecycle/profile_launch.rs +99 -0
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +52 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +214 -160
- package/crates/team-agent/src/lifecycle/restart/common.rs +513 -37
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
- package/crates/team-agent/src/lifecycle/restart/remove.rs +233 -6
- package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
- package/crates/team-agent/src/lifecycle/restart.rs +29 -0
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +24 -3
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +464 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +247 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_g0.stdout.txt +222 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +787 -0
- package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +485 -0
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
- package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +97 -0
- package/crates/team-agent/src/lifecycle/tests/pi_command_red.rs +128 -0
- package/crates/team-agent/src/lifecycle/tests/pi_compiler_red.rs +207 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +838 -0
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +320 -0
- package/crates/team-agent/src/lifecycle/tests/pi_lifecycle_doctor_red.rs +98 -0
- package/crates/team-agent/src/lifecycle/tests/pi_session_delivery_red.rs +216 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
- package/crates/team-agent/src/lifecycle/tests.rs +30 -13
- package/crates/team-agent/src/lifecycle/types.rs +61 -0
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +143 -12
- package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
- package/crates/team-agent/src/mcp_server/tests.rs +196 -2
- package/crates/team-agent/src/messaging/delivery.rs +430 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
- package/crates/team-agent/src/messaging/results.rs +4 -0
- package/crates/team-agent/src/messaging/send.rs +34 -0
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
- package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
- package/crates/team-agent/src/messaging/types.rs +4 -1
- package/crates/team-agent/src/model/enums.rs +17 -6
- package/crates/team-agent/src/model/permissions.rs +3 -0
- package/crates/team-agent/src/os_probe.rs +73 -2
- package/crates/team-agent/src/provider/adapter.rs +259 -6
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +89 -0
- package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +3 -0
- package/crates/team-agent/src/provider/adapters/pi.rs +152 -0
- package/crates/team-agent/src/provider/bypass_flags.rs +47 -2
- package/crates/team-agent/src/provider/classify.rs +4 -2
- package/crates/team-agent/src/provider/faults.rs +2 -1
- package/crates/team-agent/src/provider/mod.rs +11 -0
- package/crates/team-agent/src/provider/session/capture.rs +158 -20
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
- package/crates/team-agent/src/provider/session/context_fork.rs +78 -3
- package/crates/team-agent/src/provider/session/mod.rs +18 -0
- package/crates/team-agent/src/provider/session/resume.rs +57 -0
- package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
- package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
- package/crates/team-agent/src/provider/session_scan/common.rs +205 -2
- package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
- package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
- package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
- package/crates/team-agent/src/provider/session_scan/pi.rs +172 -0
- package/crates/team-agent/src/provider/session_scan.rs +12 -0
- package/crates/team-agent/src/provider/submit_now.rs +94 -0
- package/crates/team-agent/src/provider/tests/adapter.rs +384 -0
- package/crates/team-agent/src/provider/types.rs +4 -2
- package/crates/team-agent/src/provider/wire.rs +29 -1
- package/crates/team-agent/src/state/persist.rs +301 -8
- package/crates/team-agent/src/state/repository.rs +5 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
- package/crates/team-agent/src/tmux_backend.rs +1116 -75
- package/crates/team-agent/src/transport/tests/wire.rs +28 -2
- package/crates/team-agent/src/transport.rs +248 -1
- package/npm/install.mjs +129 -73
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +32 -239
- package/skills/team-agent/command-coverage.json +31 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
- package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
- 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.
|
|
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
|
@@ -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] =
|
|
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`
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
// =============================================================================
|