@team-agent/installer 0.5.92 → 0.5.97
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 +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/adapters.rs +381 -157
- package/crates/team-agent/src/cli/diagnose.rs +40 -10
- package/crates/team-agent/src/cli/emit.rs +6 -4
- package/crates/team-agent/src/cli/mod.rs +483 -470
- package/crates/team-agent/src/cli/models.rs +1 -0
- package/crates/team-agent/src/cli/spec.rs +2 -2
- package/crates/team-agent/src/cli/tests/base.rs +20 -10
- package/crates/team-agent/src/cli/tests/divergence.rs +12 -21
- package/crates/team-agent/src/cli/tests/main_preserved.rs +6 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +20 -11
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +197 -30
- package/crates/team-agent/src/cli/triage.rs +31 -31
- package/crates/team-agent/src/cli/types.rs +3 -1
- package/crates/team-agent/src/coordinator/health.rs +92 -30
- package/crates/team-agent/src/coordinator/tick.rs +62 -8
- package/crates/team-agent/src/coordinator/types.rs +2 -0
- package/crates/team-agent/src/kill_audit.rs +41 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +48 -14
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +4 -8
- package/crates/team-agent/src/lifecycle/launch.rs +17 -0
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +10 -4
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +10 -7
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +91 -4
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +128 -11
- package/crates/team-agent/src/platform/process.rs +4 -45
- package/crates/team-agent/src/tmux_backend.rs +113 -3
- package/crates/team-agent/src/transport/test_support.rs +30 -0
- package/crates/team-agent/src/transport.rs +41 -0
- package/package.json +4 -4
|
@@ -902,6 +902,7 @@ mod tests {
|
|
|
902
902
|
|
|
903
903
|
#[cfg(unix)]
|
|
904
904
|
#[test]
|
|
905
|
+
#[serial_test::serial(env)]
|
|
905
906
|
fn command_boundary_connects_runner_parser_search_and_contract() {
|
|
906
907
|
let path = fixture(
|
|
907
908
|
"printf 'provider model\\nopenai-codex gpt-5.6-sol\\nopenai-codex gpt-5.6-luna\\n'",
|
|
@@ -176,7 +176,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
176
176
|
CommandSpec { name: "start-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StartAgent), summary: "start an existing worker", usage: "usage: team-agent start-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--force] [--allow-fresh] [--no-display] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
|
|
177
177
|
CommandSpec { name: "stop-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::StopAgent), summary: "stop a worker", usage: "usage: team-agent stop-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
178
178
|
CommandSpec { name: "reset-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ResetAgent), summary: "reset a worker session", usage: "usage: team-agent reset-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--discard-session] [--no-display] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
|
|
179
|
-
CommandSpec { name: "diagnose", tier: CommandTier::
|
|
179
|
+
CommandSpec { name: "diagnose", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::Diagnose), summary: "compatibility alias for doctor", usage: "usage: team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--gate orphans|comms] [--comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: Some("doctor"), sunset: None, action: None, governance: None },
|
|
180
180
|
CommandSpec { name: "claim-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::ClaimLeader), summary: "bind this pane when diagnose reports rebind_required", usage: "usage: team-agent claim-leader [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
181
181
|
CommandSpec { name: "takeover", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::Takeover), summary: "replace a live leader binding with explicit consent", usage: "usage: team-agent takeover [--workspace WORKSPACE] [--team TEAM] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
182
182
|
CommandSpec { name: "attach-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::AttachLeader), summary: "bind an external leader pane", usage: "usage: team-agent attach-leader [--workspace WORKSPACE] [--team TEAM] [--pane PANE] [--provider PROVIDER] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
@@ -196,7 +196,7 @@ pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
|
|
|
196
196
|
CommandSpec { name: "stuck-cancel", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::StuckCancel), summary: "legacy stuck alert suppression", usage: "usage: team-agent stuck-cancel AGENT [--workspace WORKSPACE] [--alert-type stuck|idle_fallback|cross_worker_deadlock|all] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: Some("C2"), action: Some("use the concrete notification action or `team-agent diagnose`"), governance: None },
|
|
197
197
|
CommandSpec { name: "acknowledge-idle", tier: CommandTier::CompatHidden, category: CommandCategory::Compat, kind: CommandKind::Dispatch(DispatchKind::AcknowledgeIdle), summary: "legacy idle acknowledgement", usage: "usage: team-agent acknowledge-idle [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: Some("C2"), action: Some("use the concrete idle notification action or `team-agent diagnose`"), governance: None },
|
|
198
198
|
CommandSpec { name: "leaders", tier: CommandTier::Secondary, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Leaders), summary: "list live, stale, or ambiguous host leaders", usage: "usage: team-agent leaders [QUERY|--search TEXT] [--all|--stale] [--json] | --prune [--dry-run] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
199
|
-
CommandSpec { name: "doctor", tier: CommandTier::
|
|
199
|
+
CommandSpec { name: "doctor", tier: CommandTier::Core, category: CommandCategory::Observe, kind: CommandKind::Dispatch(DispatchKind::Doctor), summary: "inspect environment and Team runtime health", usage: "usage: team-agent doctor [SPEC] [--workspace WORKSPACE] [--team TEAM] [--gate orphans|comms] [--comms] [--fix] [--fix-schema] [--cleanup-orphans] [--confirm] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
200
200
|
CommandSpec { name: "attach-app-server-leader", tier: CommandTier::Guided, category: CommandCategory::GuidedRecovery, kind: CommandKind::Dispatch(DispatchKind::AttachAppServerLeader), summary: "bind an app-server leader receiver", usage: "usage: team-agent attach-app-server-leader [--workspace WORKSPACE] [--team TEAM] --socket unix:///path.sock --thread-id THREAD_ID [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
201
201
|
CommandSpec { name: "remove-agent", tier: CommandTier::Secondary, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::RemoveAgent), summary: "remove a worker", usage: "usage: team-agent remove-agent AGENT [--workspace WORKSPACE] [--team TEAM] [--from-spec] [--confirm] [--force] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
|
|
202
202
|
CommandSpec { name: "fork-agent", tier: CommandTier::Secondary, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::ForkAgent), summary: "fork a worker", usage: "usage: team-agent fork-agent SOURCE_AGENT --as AGENT [--label LABEL] [--workspace WORKSPACE] [--team TEAM] [--no-display] [--json]", default_help: false, command_help: true, suggestion_index: false, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
|
|
@@ -671,13 +671,12 @@ fn exit_code_numeric() {
|
|
|
671
671
|
}
|
|
672
672
|
|
|
673
673
|
// =========================================================================
|
|
674
|
-
// cmd_doctor
|
|
674
|
+
// cmd_doctor: --fix without --gate is a structured failure in both renderers.
|
|
675
675
|
// =========================================================================
|
|
676
676
|
|
|
677
677
|
#[test]
|
|
678
|
-
fn
|
|
679
|
-
|
|
680
|
-
let args = DoctorArgs {
|
|
678
|
+
fn cmd_doctor_fix_without_gate_reports_usage_failure() {
|
|
679
|
+
let mut args = DoctorArgs {
|
|
681
680
|
spec: None,
|
|
682
681
|
workspace: PathBuf::from("."),
|
|
683
682
|
gate: None,
|
|
@@ -689,12 +688,23 @@ fn cmd_doctor_fix_without_gate_is_usage_error() {
|
|
|
689
688
|
confirm: false,
|
|
690
689
|
json: false,
|
|
691
690
|
};
|
|
692
|
-
let
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
);
|
|
691
|
+
let human = cmd_doctor(&args).expect("structured usage failure");
|
|
692
|
+
assert_eq!(human.exit, ExitCode::Error);
|
|
693
|
+
let CmdOutput::Human(text) = human.output else {
|
|
694
|
+
panic!("expected human triage");
|
|
695
|
+
};
|
|
696
|
+
assert!(text.contains("fix_requires_gate"), "{text}");
|
|
697
|
+
assert!(text.contains("add --gate orphans|comms"), "{text}");
|
|
698
|
+
|
|
699
|
+
args.json = true;
|
|
700
|
+
let result = cmd_doctor(&args).expect("structured JSON usage failure");
|
|
701
|
+
assert_eq!(result.exit, ExitCode::Error);
|
|
702
|
+
let CmdOutput::Json(value) = result.output else {
|
|
703
|
+
panic!("expected JSON usage failure");
|
|
704
|
+
};
|
|
705
|
+
assert_eq!(value["ok"], false);
|
|
706
|
+
assert_eq!(value["error"], "--fix requires --gate");
|
|
707
|
+
assert_eq!(value["issues"], json!(["fix_requires_gate"]));
|
|
698
708
|
}
|
|
699
709
|
|
|
700
710
|
// =========================================================================
|
|
@@ -457,14 +457,10 @@ fn red_send_target_none_is_not_broadcast() {
|
|
|
457
457
|
assert_eq!(send_target(None, Some("*")), MessageTarget::Broadcast);
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
//
|
|
461
|
-
// golden: for --comms WITHOUT --json, cmd_doctor returns the STRING
|
|
462
|
-
// f"{COMMS_BOUNDARY_TEXT}\n{json.dumps(result, indent=2, ensure_ascii=False, sort_keys=True)}".
|
|
463
|
-
// Rust always does CmdResult::from_json -> CmdOutput::Json (wrong shape).
|
|
460
|
+
// Unified doctor human output is triage only, including the comms gate.
|
|
464
461
|
#[test]
|
|
465
|
-
fn
|
|
466
|
-
|
|
467
|
-
let args = DoctorArgs {
|
|
462
|
+
fn cmd_doctor_comms_human_is_compact_triage_without_boundary_prefix() {
|
|
463
|
+
let mut args = DoctorArgs {
|
|
468
464
|
spec: None,
|
|
469
465
|
workspace: PathBuf::from("."),
|
|
470
466
|
gate: None,
|
|
@@ -479,21 +475,16 @@ fn red_cmd_doctor_comms_human_is_boundary_text_plus_sorted_json() {
|
|
|
479
475
|
let result = cmd_doctor(&args).expect("comms doctor returns CmdResult");
|
|
480
476
|
let text = match result.output {
|
|
481
477
|
CmdOutput::Human(s) => s,
|
|
482
|
-
other => panic!(
|
|
483
|
-
"comms WITHOUT --json MUST be a Human boundary-text + JSON string, got {other:?}"
|
|
484
|
-
),
|
|
478
|
+
other => panic!("comms WITHOUT --json must be human triage, got {other:?}"),
|
|
485
479
|
};
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
.unwrap();
|
|
494
|
-
let parsed: Value = serde_json::from_str(json_tail)
|
|
495
|
-
.expect("comms human tail MUST be indent=2 sort_keys JSON of the selftest result");
|
|
496
|
-
assert!(parsed.is_object(), "comms selftest JSON tail is an object");
|
|
480
|
+
args.json = true;
|
|
481
|
+
let CmdOutput::Json(report) = cmd_doctor(&args).unwrap().output else {
|
|
482
|
+
panic!("--json must preserve the structured report");
|
|
483
|
+
};
|
|
484
|
+
assert_eq!(text, crate::cli::triage::render("doctor", &report));
|
|
485
|
+
assert!(text.starts_with("doctor:"));
|
|
486
|
+
assert_eq!(text.lines().count(), 1 + report["issues"].as_array().unwrap().len()
|
|
487
|
+
+ report["suggested_repairs"].as_array().unwrap().len());
|
|
497
488
|
}
|
|
498
489
|
|
|
499
490
|
// ---- #13 / #27 (P2): run() must NOT treat 'claude_code' as a passthrough trigger ----
|
|
@@ -368,12 +368,17 @@ fn ux_doctor_secret_scan_findings_name_the_exact_trigger() {
|
|
|
368
368
|
.pointer("/secret_scan/findings/0")
|
|
369
369
|
.and_then(serde_json::Value::as_object)
|
|
370
370
|
.expect("secret-scan must report the concrete trigger");
|
|
371
|
-
for key in ["path", "line", "rule"
|
|
371
|
+
for key in ["path", "line", "rule"] {
|
|
372
372
|
assert!(
|
|
373
373
|
finding.contains_key(key),
|
|
374
374
|
"secret-scan finding missing `{key}`: {finding:?}"
|
|
375
375
|
);
|
|
376
376
|
}
|
|
377
|
+
assert_eq!(finding["path"], "leaky-role.md");
|
|
378
|
+
assert_eq!(finding["line"], 1);
|
|
379
|
+
assert_eq!(finding["rule"], "api_key_assignment");
|
|
380
|
+
assert!(!finding.contains_key("match_excerpt"));
|
|
381
|
+
assert!(!value.to_string().contains("sk-test-red-contract"));
|
|
377
382
|
let _ = std::fs::remove_dir_all(&ws);
|
|
378
383
|
}
|
|
379
384
|
#[test]
|
|
@@ -299,7 +299,7 @@ fn dispatch_routes_sessions_subcommand() {
|
|
|
299
299
|
// + NO session_name + NO agents -> issues=[] -> EXIT 0.
|
|
300
300
|
#[test]
|
|
301
301
|
#[serial(env)]
|
|
302
|
-
fn
|
|
302
|
+
fn dispatch_routes_diagnose_checks_runtime_beyond_healthy_leader() {
|
|
303
303
|
let _env = EnvUnsetGuard::unset(&[
|
|
304
304
|
"TMUX",
|
|
305
305
|
"TMUX_PANE",
|
|
@@ -332,17 +332,26 @@ fn dispatch_routes_diagnose_healthy_leader() {
|
|
|
332
332
|
pane_pid: None,
|
|
333
333
|
leader_env: Default::default(),
|
|
334
334
|
}]);
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
335
|
+
crate::transport_factory::with_leader_endpoint_transport(endpoint, transport, || {
|
|
336
|
+
for command in ["doctor", "diagnose"] {
|
|
337
|
+
let code = run(
|
|
338
|
+
&cli_argv(&[command, "--workspace", &ws.to_string_lossy(), "--json"]),
|
|
339
|
+
&ws,
|
|
340
|
+
);
|
|
341
|
+
assert_eq!(code, ExitCode::Error, "healthy binding alone cannot hide missing coordinator");
|
|
342
|
+
}
|
|
343
|
+
let result = cmd_diagnose(&DiagnoseArgs {
|
|
344
|
+
workspace: ws.clone(), json: true, team: None,
|
|
345
|
+
}).expect("diagnostic report");
|
|
346
|
+
let CmdOutput::Json(report) = result.output else {
|
|
347
|
+
panic!("expected JSON diagnostic report");
|
|
348
|
+
};
|
|
349
|
+
assert_eq!(report["coordinator"]["ok"], false, "{report}");
|
|
350
|
+
let issues = report["issues"].to_string();
|
|
351
|
+
for binding_failure in ["leader_not_attached", "leader_receiver_unbound", "leader_workspace_mismatch"] {
|
|
352
|
+
assert!(!issues.contains(binding_failure), "healthy binding regressed: {report}");
|
|
353
|
+
}
|
|
340
354
|
});
|
|
341
|
-
assert_eq!(
|
|
342
|
-
code,
|
|
343
|
-
ExitCode::Ok,
|
|
344
|
-
"`diagnose` must be Ok when canonical owner/receiver, production registry, and live align; got {code:?}"
|
|
345
|
-
);
|
|
346
355
|
let _ = std::fs::remove_dir_all(&ws);
|
|
347
356
|
}
|
|
348
357
|
|