@team-agent/installer 0.5.99 → 0.5.100
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 +1 -156
- package/crates/team-agent/src/cli/diagnose.rs +3 -3
- package/crates/team-agent/src/cli/emit.rs +6 -185
- package/crates/team-agent/src/cli/mod.rs +8 -174
- package/crates/team-agent/src/cli/send/coordinator.rs +2 -2
- package/crates/team-agent/src/cli/spec.rs +41 -67
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -185
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +9 -9
- package/crates/team-agent/src/cli/triage.rs +1 -1
- package/crates/team-agent/src/cli/types.rs +0 -49
- package/crates/team-agent/src/leader/lease.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +6 -6
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +9 -2
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +0 -60
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +2 -2
- package/crates/team-agent/src/messaging/send.rs +2 -2
- package/crates/team-agent/src/provider/types.rs +1 -1
- package/crates/team-agent/src/state/repository.rs +0 -5
- package/package.json +4 -4
- package/skills/team-agent/references/recovery-runbook.md +2 -2
- package/skills/team-agent/references/team-in-team.md +0 -6
|
@@ -106,92 +106,6 @@ fn seed_team_spec(ws: &std::path::Path) {
|
|
|
106
106
|
std::fs::write(ws.join("team.spec.yaml"), spec).unwrap();
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
// ── ACK-CRACK [P1 byte-shape] — acknowledge_idle must write golden's TTL suppression shape ───────
|
|
110
|
-
// golden runtime.py:680-688: manual-acknowledge persists
|
|
111
|
-
// coordinator.idle_acknowledged[team] = {acknowledged_at, expires_at, ttl_seconds}
|
|
112
|
-
// coordinator.suppressed_idle_alerts[team][worker].idle_fallback =
|
|
113
|
-
// {suppressed_at, suppressed_by:"manual_acknowledge", manual_acknowledge:true, expires_at, ttl_seconds}
|
|
114
|
-
// The clear logic does datetime.fromisoformat(entry["expires_at"]); a MISSING expires_at -> ValueError
|
|
115
|
-
// -> "invalid_suppression_timestamp" -> immediate self-clear (latent crack once detect_idle_fallbacks
|
|
116
|
-
// is ported). So BOTH idle_acknowledged and the entry MUST carry a non-empty expires_at.
|
|
117
|
-
#[test]
|
|
118
|
-
fn acknowledge_idle_writes_golden_ttl_suppression_shape() {
|
|
119
|
-
let ws = tmp_workspace();
|
|
120
|
-
crate::state::persist::save_runtime_state(
|
|
121
|
-
&ws,
|
|
122
|
-
&serde_json::json!({
|
|
123
|
-
"active_team_key": "teamX",
|
|
124
|
-
"agents": {"w1": {"status": "running", "provider": "codex"}}
|
|
125
|
-
}),
|
|
126
|
-
)
|
|
127
|
-
.unwrap();
|
|
128
|
-
let _ = lifecycle_port::acknowledge_idle(&ws, None).expect("acknowledge_idle ok");
|
|
129
|
-
let state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
130
|
-
let ack = &state["coordinator"]["idle_acknowledged"]["teamX"];
|
|
131
|
-
assert!(
|
|
132
|
-
ack.get("expires_at")
|
|
133
|
-
.and_then(serde_json::Value::as_str)
|
|
134
|
-
.is_some_and(|s| !s.is_empty()),
|
|
135
|
-
"ACK-CRACK: idle_acknowledged[team] must carry a non-empty expires_at (golden); got {ack}"
|
|
136
|
-
);
|
|
137
|
-
assert!(
|
|
138
|
-
ack.get("ttl_seconds").is_some(),
|
|
139
|
-
"idle_acknowledged[team] must carry ttl_seconds; got {ack}"
|
|
140
|
-
);
|
|
141
|
-
let entry = &state["coordinator"]["suppressed_idle_alerts"]["teamX"]["w1"]["idle_fallback"];
|
|
142
|
-
assert!(
|
|
143
|
-
entry.get("expires_at").and_then(serde_json::Value::as_str).is_some_and(|s| !s.is_empty()),
|
|
144
|
-
"ACK-CRACK: the manual-ack suppression entry must carry expires_at (else clear logic ValueErrors \
|
|
145
|
-
-> instant self-clear); got {entry}"
|
|
146
|
-
);
|
|
147
|
-
assert_eq!(
|
|
148
|
-
entry["suppressed_by"],
|
|
149
|
-
serde_json::json!("manual_acknowledge"),
|
|
150
|
-
"golden suppressed_by; got {entry}"
|
|
151
|
-
);
|
|
152
|
-
assert_eq!(
|
|
153
|
-
entry["manual_acknowledge"],
|
|
154
|
-
serde_json::json!(true),
|
|
155
|
-
"golden manual_acknowledge:true; got {entry}"
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
// ── ACK return-shape [P1 byte-parity] — acknowledge_idle must RETURN golden's keys ───────────────
|
|
159
|
-
// golden runtime.py:691: return {ok, team, agent_id, acknowledged_at, expires_at, ttl_seconds}.
|
|
160
|
-
// Rust (cli/mod.rs) returns only {ok, team, ttl_seconds} -> missing agent_id, acknowledged_at,
|
|
161
|
-
// expires_at. RED. (acknowledged_at/expires_at are the same values written into idle_acknowledged.)
|
|
162
|
-
#[test]
|
|
163
|
-
fn acknowledge_idle_return_carries_golden_keys() {
|
|
164
|
-
let ws = tmp_workspace();
|
|
165
|
-
crate::state::persist::save_runtime_state(
|
|
166
|
-
&ws,
|
|
167
|
-
&serde_json::json!({ "active_team_key": "teamX", "agents": {"w1": {"status": "running", "provider": "codex"}} }),
|
|
168
|
-
)
|
|
169
|
-
.unwrap();
|
|
170
|
-
let r = lifecycle_port::acknowledge_idle(&ws, None).expect("acknowledge_idle ok");
|
|
171
|
-
let obj = r.as_object().expect("ack returns a dict");
|
|
172
|
-
for key in [
|
|
173
|
-
"ok",
|
|
174
|
-
"team",
|
|
175
|
-
"agent_id",
|
|
176
|
-
"acknowledged_at",
|
|
177
|
-
"expires_at",
|
|
178
|
-
"ttl_seconds",
|
|
179
|
-
] {
|
|
180
|
-
assert!(
|
|
181
|
-
obj.contains_key(key),
|
|
182
|
-
"ACK return-shape: golden return carries `{key}` (runtime.py:691: ok/team/agent_id/\
|
|
183
|
-
acknowledged_at/expires_at/ttl_seconds); Rust omits it. got keys {:?}",
|
|
184
|
-
obj.keys().collect::<Vec<_>>()
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
assert!(
|
|
188
|
-
obj.get("expires_at")
|
|
189
|
-
.and_then(serde_json::Value::as_str)
|
|
190
|
-
.is_some_and(|s| !s.is_empty()),
|
|
191
|
-
"ACK return-shape: expires_at must be a non-empty timestamp; got {r}"
|
|
192
|
-
);
|
|
193
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
194
|
-
}
|
|
195
109
|
// ── BUG-2 [real bug] — inbox must RETURN the stored messages, not a hardcoded []. ────────────────
|
|
196
110
|
// Golden status/inbox.py:35-38 -> MessageStore.inbox(agent_id) (core.py:242, owner_team_id=None):
|
|
197
111
|
// select <MESSAGE_SELECT> from messages where sender = ? or recipient = ? order by created_at desc
|
|
@@ -680,16 +594,7 @@ fn remove_agent_from_spec_refusal_is_not_success_envelope() {
|
|
|
680
594
|
fn stuck_cancel_persists_suppression_and_stuck_list_reads_state() {
|
|
681
595
|
let ws = tmp_workspace();
|
|
682
596
|
seed_collect_state(&ws);
|
|
683
|
-
let out =
|
|
684
|
-
cmd_stuck_cancel(&StuckCancelArgs {
|
|
685
|
-
agent: "fake_impl".to_string(),
|
|
686
|
-
workspace: ws.clone(),
|
|
687
|
-
alert_type: None,
|
|
688
|
-
json: true,
|
|
689
|
-
team: None,
|
|
690
|
-
})
|
|
691
|
-
.unwrap(),
|
|
692
|
-
);
|
|
597
|
+
let out = messaging::stuck_cancel(&ws, "fake_impl", None, "leader").unwrap();
|
|
693
598
|
let team_key = seeded_team_key(&ws);
|
|
694
599
|
assert_eq!(out["ok"], json!(true));
|
|
695
600
|
assert_eq!(
|
|
@@ -715,98 +620,10 @@ fn stuck_cancel_persists_suppression_and_stuck_list_reads_state() {
|
|
|
715
620
|
&& e["agent_id"] == json!("fake_impl")),
|
|
716
621
|
"stuck_cancel must write coordinator.idle_alert_suppressed"
|
|
717
622
|
);
|
|
718
|
-
let listed =
|
|
719
|
-
cmd_stuck_list(&StuckListArgs {
|
|
720
|
-
workspace: ws.clone(),
|
|
721
|
-
json: true,
|
|
722
|
-
team: None,
|
|
723
|
-
})
|
|
724
|
-
.unwrap(),
|
|
725
|
-
);
|
|
623
|
+
let listed = messaging::stuck_list(&ws).unwrap();
|
|
726
624
|
assert_eq!(
|
|
727
625
|
listed["suppressed_idle_alerts"]["fake_impl"]["stuck"]["suppressed_by"],
|
|
728
626
|
json!("leader"),
|
|
729
627
|
"stuck-list must read the persisted state mirror, not return a hard-coded empty list"
|
|
730
628
|
);
|
|
731
629
|
}
|
|
732
|
-
|
|
733
|
-
#[test]
|
|
734
|
-
fn stuck_cancel_explicit_team_is_rejected_until_backend_is_scoped() {
|
|
735
|
-
let ws = tmp_workspace();
|
|
736
|
-
seed_collect_state(&ws);
|
|
737
|
-
let err = cmd_stuck_cancel(&StuckCancelArgs {
|
|
738
|
-
agent: "fake_impl".to_string(),
|
|
739
|
-
workspace: ws.clone(),
|
|
740
|
-
alert_type: None,
|
|
741
|
-
json: true,
|
|
742
|
-
team: Some("current".to_string()),
|
|
743
|
-
})
|
|
744
|
-
.expect_err("explicit --team must not silently write global stuck suppression");
|
|
745
|
-
assert!(
|
|
746
|
-
err.to_string().contains("not supported yet"),
|
|
747
|
-
"stuck-cancel --team must be an explicit refusal until backend supports scoped writes; got {err}"
|
|
748
|
-
);
|
|
749
|
-
let _ = std::fs::remove_dir_all(&ws);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
#[test]
|
|
753
|
-
fn stuck_cancel_invalid_alert_type_is_rejected() {
|
|
754
|
-
let ws = tmp_workspace();
|
|
755
|
-
seed_collect_state(&ws);
|
|
756
|
-
let code = run(
|
|
757
|
-
&cli_argv(&[
|
|
758
|
-
"stuck-cancel",
|
|
759
|
-
"fake_impl",
|
|
760
|
-
"--workspace",
|
|
761
|
-
&ws.to_string_lossy(),
|
|
762
|
-
"--alert-type",
|
|
763
|
-
"bogus",
|
|
764
|
-
"--json",
|
|
765
|
-
]),
|
|
766
|
-
&ws,
|
|
767
|
-
);
|
|
768
|
-
assert_eq!(
|
|
769
|
-
code,
|
|
770
|
-
ExitCode::Error,
|
|
771
|
-
"Python rejects alert_type outside stuck/idle_fallback/cross_worker_deadlock/all; Rust must not silently coerce bogus to stuck"
|
|
772
|
-
);
|
|
773
|
-
}
|
|
774
|
-
#[test]
|
|
775
|
-
fn acknowledge_idle_records_manual_idle_fallback_suppression_and_event() {
|
|
776
|
-
let ws = tmp_workspace();
|
|
777
|
-
seed_collect_state(&ws);
|
|
778
|
-
let out = json_output(
|
|
779
|
-
cmd_acknowledge_idle(&AcknowledgeIdleArgs {
|
|
780
|
-
team: None,
|
|
781
|
-
workspace: ws.clone(),
|
|
782
|
-
json: true,
|
|
783
|
-
})
|
|
784
|
-
.unwrap(),
|
|
785
|
-
);
|
|
786
|
-
let team_key = seeded_team_key(&ws);
|
|
787
|
-
assert_eq!(out["ok"], json!(true));
|
|
788
|
-
assert_eq!(out["team"], json!(team_key));
|
|
789
|
-
assert_eq!(out["ttl_seconds"], json!(1800));
|
|
790
|
-
let state = read_state(&ws);
|
|
791
|
-
let ack = &state["coordinator"]["idle_acknowledged"][&team_key];
|
|
792
|
-
assert_eq!(ack["ttl_seconds"], json!(1800));
|
|
793
|
-
assert!(ack["acknowledged_at"].as_str().is_some());
|
|
794
|
-
assert_eq!(
|
|
795
|
-
state["coordinator"]["suppressed_idle_alerts"][&team_key]["fake_impl"]["idle_fallback"]
|
|
796
|
-
["suppressed_by"],
|
|
797
|
-
json!("manual_acknowledge")
|
|
798
|
-
);
|
|
799
|
-
assert_eq!(
|
|
800
|
-
state["coordinator"]["suppressed_idle_alerts"][&team_key]["fake_impl"]["idle_fallback"]
|
|
801
|
-
["manual_acknowledge"],
|
|
802
|
-
json!(true)
|
|
803
|
-
);
|
|
804
|
-
assert!(
|
|
805
|
-
read_events(&ws)
|
|
806
|
-
.iter()
|
|
807
|
-
.any(|e| e["event"] == json!("coordinator.idle_acknowledged")
|
|
808
|
-
&& e["team"] == json!(team_key)
|
|
809
|
-
&& e["ttl_seconds"] == json!(1800)),
|
|
810
|
-
"acknowledge-idle must emit coordinator.idle_acknowledged"
|
|
811
|
-
);
|
|
812
|
-
}
|
|
@@ -332,15 +332,15 @@ fn dispatch_routes_diagnose_checks_runtime_beyond_healthy_leader() {
|
|
|
332
332
|
leader_env: Default::default(),
|
|
333
333
|
}]);
|
|
334
334
|
crate::transport_factory::with_leader_endpoint_transport(endpoint, transport, || {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
let result = cmd_diagnose(&DiagnoseArgs {
|
|
335
|
+
let code = run(
|
|
336
|
+
&cli_argv(&["doctor", "--workspace", &ws.to_string_lossy(), "--json"]),
|
|
337
|
+
&ws,
|
|
338
|
+
);
|
|
339
|
+
assert_eq!(code, ExitCode::Error, "healthy binding alone cannot hide missing coordinator");
|
|
340
|
+
let result = cmd_doctor(&DoctorArgs {
|
|
343
341
|
workspace: ws.clone(), json: true, team: None,
|
|
342
|
+
spec: None, gate: None, comms: false, fix: false,
|
|
343
|
+
fix_schema: false, cleanup_orphans: false, confirm: false,
|
|
344
344
|
}).expect("diagnostic report");
|
|
345
345
|
let CmdOutput::Json(report) = result.output else {
|
|
346
346
|
panic!("expected JSON diagnostic report");
|
|
@@ -377,7 +377,7 @@ fn dispatch_routes_diagnose_self_signed_attached_without_registry_is_not_ok() {
|
|
|
377
377
|
let ws = tmp_workspace();
|
|
378
378
|
seed_self_signed_attached_leader(&ws);
|
|
379
379
|
let code = run(
|
|
380
|
-
&cli_argv(&["
|
|
380
|
+
&cli_argv(&["doctor", "--workspace", &ws.to_string_lossy(), "--json"]),
|
|
381
381
|
&ws,
|
|
382
382
|
);
|
|
383
383
|
assert_ne!(
|
|
@@ -138,7 +138,7 @@ mod tests {
|
|
|
138
138
|
"providers": {"claude": {"version": "secret"}},
|
|
139
139
|
"runtime": {"workspace": "/tmp/secret"}
|
|
140
140
|
});
|
|
141
|
-
let output = render("
|
|
141
|
+
let output = render("doctor", &report);
|
|
142
142
|
assert!(output.contains("issue: short_issue"));
|
|
143
143
|
assert!(output.contains("issue: code=object_issue"));
|
|
144
144
|
assert!(output.contains("repair: action=run repair"));
|
|
@@ -312,14 +312,6 @@ pub struct QuickStartArgs {
|
|
|
312
312
|
// user-confirmed. quick-start is now an initial-creation-only verb.
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
/// `init`(`parser.py` bootstrap verb)。
|
|
316
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
317
|
-
pub struct InitArgs {
|
|
318
|
-
pub workspace: PathBuf,
|
|
319
|
-
pub force: bool,
|
|
320
|
-
pub json: bool,
|
|
321
|
-
}
|
|
322
|
-
|
|
323
315
|
/// `send`(`parser.py:262`)。`target` xor `--to`(fanout);`message` 多 token join 空格。
|
|
324
316
|
#[derive(Debug, Clone, PartialEq)]
|
|
325
317
|
pub struct SendArgs {
|
|
@@ -554,36 +546,6 @@ pub struct RemoveAgentArgs {
|
|
|
554
546
|
pub json: bool,
|
|
555
547
|
}
|
|
556
548
|
|
|
557
|
-
/// `stuck-list`(`parser.py:424`)。
|
|
558
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
559
|
-
pub struct StuckListArgs {
|
|
560
|
-
pub workspace: PathBuf,
|
|
561
|
-
pub json: bool,
|
|
562
|
-
/// Stage 4: explicit `--team` scope (read-only enumeration).
|
|
563
|
-
pub team: Option<String>,
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
/// `stuck-cancel`(`parser.py:429`)。`--alert-type` ∈ {stuck, idle_fallback, all},默认 stuck。
|
|
567
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
568
|
-
pub struct StuckCancelArgs {
|
|
569
|
-
pub agent: String,
|
|
570
|
-
pub workspace: PathBuf,
|
|
571
|
-
/// `None` 表 `all`(展开全集);`Some(AlertType)` 表单类型。
|
|
572
|
-
pub alert_type: Option<AlertType>,
|
|
573
|
-
pub json: bool,
|
|
574
|
-
/// Parsed but rejected until stuck suppression storage grows a team-scoped
|
|
575
|
-
/// backend selector.
|
|
576
|
-
pub team: Option<String>,
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/// `acknowledge-idle`(`parser.py:436`)。
|
|
580
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
581
|
-
pub struct AcknowledgeIdleArgs {
|
|
582
|
-
pub team: Option<String>,
|
|
583
|
-
pub workspace: PathBuf,
|
|
584
|
-
pub json: bool,
|
|
585
|
-
}
|
|
586
|
-
|
|
587
549
|
/// `doctor`(`parser.py:318`)。`--gate` ∈ {orphans, comms};`--fix` 须配 `--gate`(`cmd_doctor` 校验)。
|
|
588
550
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
589
551
|
pub struct DoctorArgs {
|
|
@@ -683,17 +645,6 @@ pub struct WaitArgs {
|
|
|
683
645
|
pub json: bool,
|
|
684
646
|
}
|
|
685
647
|
|
|
686
|
-
/// `diagnose` compatibility spelling for the canonical `doctor` health report.
|
|
687
|
-
/// The CLI normalizes it to `DoctorArgs`; this narrow type remains for direct
|
|
688
|
-
/// Rust callers that used the pre-unification API.
|
|
689
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
690
|
-
pub struct DiagnoseArgs {
|
|
691
|
-
pub workspace: PathBuf,
|
|
692
|
-
pub json: bool,
|
|
693
|
-
/// Stage 4: explicit `--team` scope (read-only health report).
|
|
694
|
-
pub team: Option<String>,
|
|
695
|
-
}
|
|
696
|
-
|
|
697
648
|
/// `preflight`(`parser.py:160`)。
|
|
698
649
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
699
650
|
pub struct PreflightArgs {
|
|
@@ -1313,7 +1313,7 @@ fn apply_endpoint_convergence(
|
|
|
1313
1313
|
"persisted": false,
|
|
1314
1314
|
"checked_paths": convergence_checked_paths(team_id.as_str()),
|
|
1315
1315
|
"action": format!(
|
|
1316
|
-
"old tmux endpoint {old_endpoint} still has this team's session or pane tuple; run team-agent
|
|
1316
|
+
"old tmux endpoint {old_endpoint} still has this team's session or pane tuple; run team-agent doctor --json before retrying restart"
|
|
1317
1317
|
),
|
|
1318
1318
|
})),
|
|
1319
1319
|
false,
|
|
@@ -336,7 +336,7 @@ fn restart_with_selected_team_and_transport(
|
|
|
336
336
|
.first()
|
|
337
337
|
.cloned()
|
|
338
338
|
.unwrap_or_else(|| "dirty_topology".to_string()),
|
|
339
|
-
error: "restart refused: tmux endpoint/socket topology is inconsistent; run
|
|
339
|
+
error: "restart refused: tmux endpoint/socket topology is inconsistent; run doctor from the intended leader socket before restarting".to_string(),
|
|
340
340
|
issue_ids: topology_issue_ids,
|
|
341
341
|
});
|
|
342
342
|
}
|
|
@@ -1804,7 +1804,7 @@ fn restart_readiness_timeout_message(
|
|
|
1804
1804
|
- tmux session created: {session}\n\
|
|
1805
1805
|
- worker pane addressable: {pane}\n\
|
|
1806
1806
|
- coordinator alive: {coordinator}\n\
|
|
1807
|
-
Action: check coordinator log {log}, then inspect this workspace with `team-agent
|
|
1807
|
+
Action: check coordinator log {log}, then inspect this workspace with `team-agent doctor --workspace '{workspace}'`\n\
|
|
1808
1808
|
Log: coordinator_log={log} state={state} pid_file={pid}",
|
|
1809
1809
|
missing = restart_readiness_missing_summary(readiness),
|
|
1810
1810
|
session = yes_no(readiness.session_created),
|
|
@@ -2703,7 +2703,7 @@ fn restart_failure_phase(
|
|
|
2703
2703
|
// 0.5.39 Slice 1 (tmux-server-death-locate §11.1 B): promote
|
|
2704
2704
|
// spawn/readiness errors whose stderr contains "server exited
|
|
2705
2705
|
// unexpectedly" to `tmux_server_crashed`, so downstream event
|
|
2706
|
-
// classification and
|
|
2706
|
+
// classification and doctor next_actions ("team-agent doctor")
|
|
2707
2707
|
// don't misattribute a whole-server death to a per-agent provider
|
|
2708
2708
|
// failure. Restart itself does not recover from server crashes in
|
|
2709
2709
|
// 0.5.39 (§11.1 C is Slice 3, later car); it just classifies clearly.
|
|
@@ -2776,7 +2776,7 @@ fn write_restart_agent_failed_event(
|
|
|
2776
2776
|
"phase": phase,
|
|
2777
2777
|
"error": error,
|
|
2778
2778
|
"action": format!(
|
|
2779
|
-
"inspect worker {} output, then
|
|
2779
|
+
"inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
|
|
2780
2780
|
decision.agent_id,
|
|
2781
2781
|
decision.agent_id
|
|
2782
2782
|
),
|
|
@@ -3262,7 +3262,7 @@ fn restart_failure_next_actions(failed_agents: &[RestartFailedAgent]) -> Vec<Str
|
|
|
3262
3262
|
.iter()
|
|
3263
3263
|
.map(|failure| {
|
|
3264
3264
|
format!(
|
|
3265
|
-
"inspect worker {} output, then
|
|
3265
|
+
"inspect worker {} output, then reset that worker with `team-agent reset-agent {} --discard-session` or rerun `team-agent restart --allow-fresh`",
|
|
3266
3266
|
failure.agent_id, failure.agent_id
|
|
3267
3267
|
)
|
|
3268
3268
|
})
|
|
@@ -4911,7 +4911,7 @@ tasks:
|
|
|
4911
4911
|
},
|
|
4912
4912
|
std::time::Duration::from_secs(30),
|
|
4913
4913
|
);
|
|
4914
|
-
assert!(message.contains("team-agent
|
|
4914
|
+
assert!(message.contains("team-agent doctor --workspace"));
|
|
4915
4915
|
assert!(message.contains(workspace.to_string_lossy().as_ref()));
|
|
4916
4916
|
assert!(!message.contains("--allow-fresh"));
|
|
4917
4917
|
assert!(!message.contains("restart <agent>"));
|
|
@@ -122,7 +122,7 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
|
|
|
122
122
|
"--json",
|
|
123
123
|
]);
|
|
124
124
|
let diagnose_before = case.run_json(&[
|
|
125
|
-
"
|
|
125
|
+
"doctor",
|
|
126
126
|
"--workspace",
|
|
127
127
|
case.workspace_str(),
|
|
128
128
|
"--team",
|
|
@@ -142,7 +142,7 @@ fn stale_snapshot_cannot_flip_status_or_diagnose_ok_readiness() {
|
|
|
142
142
|
"--json",
|
|
143
143
|
]);
|
|
144
144
|
let diagnose_after = case.run_json(&[
|
|
145
|
-
"
|
|
145
|
+
"doctor",
|
|
146
146
|
"--workspace",
|
|
147
147
|
case.workspace_str(),
|
|
148
148
|
"--team",
|
|
@@ -182,7 +182,7 @@ fn diagnose_missing_profile_is_rc1_ok_false_and_zero_token() {
|
|
|
182
182
|
.unwrap();
|
|
183
183
|
|
|
184
184
|
let output = Command::new(crate::lifecycle::tests::test_binary_path())
|
|
185
|
-
.args(["
|
|
185
|
+
.args(["doctor", team.to_str().unwrap(), "--workspace", ws.to_str().unwrap(), "--json"])
|
|
186
186
|
.current_dir(&team)
|
|
187
187
|
.output()
|
|
188
188
|
.expect("diagnose subprocess should run");
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
use super::launch_spawn::seed_healthy_coordinator;
|
|
2
2
|
use super::*;
|
|
3
3
|
use crate::cli::{
|
|
4
|
-
|
|
4
|
+
cmd_doctor, cmd_send, cmd_status, lifecycle_port, CmdOutput, CmdResult, DoctorArgs,
|
|
5
5
|
SendArgs, StatusArgs,
|
|
6
6
|
};
|
|
7
7
|
use crate::transport::test_support::OfflineTransport;
|
|
@@ -582,10 +582,17 @@ fn run_diagnose_after_quick_start() -> Value {
|
|
|
582
582
|
.is_some(),
|
|
583
583
|
"index-missing diagnose requires a persisted seeded owner"
|
|
584
584
|
);
|
|
585
|
-
let diagnose =
|
|
585
|
+
let diagnose = cmd_doctor(&DoctorArgs {
|
|
586
586
|
workspace,
|
|
587
587
|
json: true,
|
|
588
588
|
team: Some("teamdir".to_string()),
|
|
589
|
+
spec: None,
|
|
590
|
+
gate: None,
|
|
591
|
+
comms: false,
|
|
592
|
+
fix: false,
|
|
593
|
+
fix_schema: false,
|
|
594
|
+
cleanup_orphans: false,
|
|
595
|
+
confirm: false,
|
|
589
596
|
});
|
|
590
597
|
match cmd_value(diagnose) {
|
|
591
598
|
Value::Object(map) => map
|
|
@@ -272,66 +272,6 @@ fn c1_runtime_commands_resolve_active_team_for_status_send_and_collect() {
|
|
|
272
272
|
);
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
-
#[test]
|
|
276
|
-
#[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
|
|
277
|
-
fn c2_stop_team_level_command_exists_and_keeps_state_while_stopping_runtime() {
|
|
278
|
-
let ws = tmp_dir("c2-stop");
|
|
279
|
-
seed_runtime_state(&ws);
|
|
280
|
-
|
|
281
|
-
let out = run(
|
|
282
|
-
&["stop", "--workspace", ws.to_str().unwrap(), "--json"],
|
|
283
|
-
&ws,
|
|
284
|
-
);
|
|
285
|
-
let err = String::from_utf8_lossy(&out.stderr);
|
|
286
|
-
assert!(
|
|
287
|
-
!err.contains("invalid choice: 'stop'"),
|
|
288
|
-
"CR-005: canonical `team-agent stop --workspace <ws> --json` must be a registered team-level stop verb, not argparse invalid-choice; stderr={err:?}"
|
|
289
|
-
);
|
|
290
|
-
assert_eq!(
|
|
291
|
-
out.status.code(),
|
|
292
|
-
Some(0),
|
|
293
|
-
"CR-005: stop should stop the runtime while keeping state on a valid workspace; stdout={} stderr={}",
|
|
294
|
-
String::from_utf8_lossy(&out.stdout),
|
|
295
|
-
err
|
|
296
|
-
);
|
|
297
|
-
let value = stdout_json(&out);
|
|
298
|
-
assert_eq!(
|
|
299
|
-
value["ok"],
|
|
300
|
-
json!(true),
|
|
301
|
-
"stop must return a successful JSON envelope"
|
|
302
|
-
);
|
|
303
|
-
assert!(
|
|
304
|
-
team_agent::state::persist::runtime_state_path(&ws).exists(),
|
|
305
|
-
"stop keeps runtime state for later restart"
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
#[test]
|
|
310
|
-
#[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
|
|
311
|
-
fn c2_canonical_verbs_expose_help_instead_of_invalid_choice() {
|
|
312
|
-
let cwd = tmp_dir("c2-help-verbs");
|
|
313
|
-
let mut failures = Vec::new();
|
|
314
|
-
for verb in ["start", "restart-agent", "purge-agent"] {
|
|
315
|
-
let out = run(&[verb, "--help"], &cwd);
|
|
316
|
-
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
317
|
-
let stderr = String::from_utf8_lossy(&out.stderr);
|
|
318
|
-
if out.status.code() != Some(0)
|
|
319
|
-
|| stderr.contains("invalid choice:")
|
|
320
|
-
|| !(stdout.contains("usage") || stderr.contains("usage"))
|
|
321
|
-
{
|
|
322
|
-
failures.push(format!(
|
|
323
|
-
"{verb}: code={:?} stdout={stdout:?} stderr={stderr:?}",
|
|
324
|
-
out.status.code()
|
|
325
|
-
));
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
assert!(
|
|
329
|
-
failures.is_empty(),
|
|
330
|
-
"CR-030/032/035/063: canonical lifecycle verbs must be registered and expose help, not invalid-choice:\n{}",
|
|
331
|
-
failures.join("\n")
|
|
332
|
-
);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
275
|
#[test]
|
|
336
276
|
#[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
|
|
337
277
|
fn c2_quick_start_help_is_zero_token_help_only_and_does_not_compile_workspace() {
|
|
@@ -80,7 +80,7 @@ fn quick_start_workspace_override_is_not_dropped_before_runtime_state_write() {
|
|
|
80
80
|
let quick_start_args = source_section(
|
|
81
81
|
&cli_types,
|
|
82
82
|
"pub struct QuickStartArgs",
|
|
83
|
-
"pub struct
|
|
83
|
+
"pub struct SendArgs",
|
|
84
84
|
);
|
|
85
85
|
assert!(
|
|
86
86
|
quick_start_args.contains("pub workspace: PathBuf"),
|
|
@@ -97,7 +97,7 @@ fn quick_start_workspace_override_is_not_dropped_before_runtime_state_write() {
|
|
|
97
97
|
|
|
98
98
|
let emit =
|
|
99
99
|
std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/src/cli/emit.rs")).unwrap();
|
|
100
|
-
let quick_start_args_fn = source_section(&emit, "fn quick_start_args", "fn
|
|
100
|
+
let quick_start_args_fn = source_section(&emit, "fn quick_start_args", "fn resolve_cli_path");
|
|
101
101
|
assert!(
|
|
102
102
|
quick_start_args_fn.contains("workspace: workspace.clone()")
|
|
103
103
|
|| quick_start_args_fn.contains("workspace,"),
|
|
@@ -551,11 +551,11 @@ fn coordinator_unavailable_outcome(
|
|
|
551
551
|
);
|
|
552
552
|
let warning = if persist {
|
|
553
553
|
format!(
|
|
554
|
-
"coordinator is not running; message was queued for {recipient} and will retry. Run `team-agent
|
|
554
|
+
"coordinator is not running; message was queued for {recipient} and will retry. Run `team-agent doctor` or restart the team."
|
|
555
555
|
)
|
|
556
556
|
} else {
|
|
557
557
|
format!(
|
|
558
|
-
"coordinator protocol or schema is incompatible; message was not queued for {recipient}. Run `team-agent
|
|
558
|
+
"coordinator protocol or schema is incompatible; message was not queued for {recipient}. Run `team-agent doctor` or upgrade the runtime."
|
|
559
559
|
)
|
|
560
560
|
};
|
|
561
561
|
event_log.write(
|
|
@@ -172,7 +172,7 @@ pub enum AuthHintStatus {
|
|
|
172
172
|
pub enum NoPingReason {
|
|
173
173
|
/// worker 从未 open 过 turn(`idle_predicate.py:61`)。
|
|
174
174
|
NotArmedNoWorkerTurn,
|
|
175
|
-
///
|
|
175
|
+
/// Suppress flag from a legacy idle acknowledgement (`idle_predicate.py:63`).
|
|
176
176
|
Acknowledged,
|
|
177
177
|
/// armed 但 elapsed < debounce(`idle_predicate.py:65`)。
|
|
178
178
|
DebounceActive,
|
|
@@ -419,9 +419,6 @@ pub enum StateWriteIntent<'a> {
|
|
|
419
419
|
SchedulerSuppression {
|
|
420
420
|
owner_team_id: Option<&'a str>,
|
|
421
421
|
},
|
|
422
|
-
IdleAck {
|
|
423
|
-
team_key: Option<&'a str>,
|
|
424
|
-
},
|
|
425
422
|
TaskRepair {
|
|
426
423
|
team_key: Option<&'a str>,
|
|
427
424
|
},
|
|
@@ -650,8 +647,6 @@ fn route_direct(
|
|
|
650
647
|
},
|
|
651
648
|
// SchedulerSuppression -> root save (messaging/scheduler.rs:259).
|
|
652
649
|
StateWriteIntent::SchedulerSuppression { .. } => helper_write_root(workspace, state),
|
|
653
|
-
// IdleAck -> root save (cli/mod.rs:2544).
|
|
654
|
-
StateWriteIntent::IdleAck { .. } => helper_write_root(workspace, state),
|
|
655
650
|
// TaskRepair -> `save_team_scoped_state` (cli/adapters.rs:675).
|
|
656
651
|
StateWriteIntent::TaskRepair { .. } => helper_write_team_scoped(workspace, state),
|
|
657
652
|
// FakeE2eSeed / SelfMigration are diagnostic seams. The current
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@team-agent/installer",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.100",
|
|
4
4
|
"description": "npx installer for Team Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codex",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"team-agent-installer": "npm/install.mjs"
|
|
21
21
|
},
|
|
22
22
|
"optionalDependencies": {
|
|
23
|
-
"@team-agent/cli-darwin-arm64": "0.5.
|
|
24
|
-
"@team-agent/cli-darwin-x64": "0.5.
|
|
25
|
-
"@team-agent/cli-linux-x64": "0.5.
|
|
23
|
+
"@team-agent/cli-darwin-arm64": "0.5.100",
|
|
24
|
+
"@team-agent/cli-darwin-x64": "0.5.100",
|
|
25
|
+
"@team-agent/cli-linux-x64": "0.5.100"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"postinstall": "node npm/bincheck.mjs",
|
|
@@ -157,10 +157,10 @@ Resume/repair:
|
|
|
157
157
|
team-agent restart "$WORKSPACE" --team "$TEAM" --json
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
A single-worker clean-context reset is not a resume operation. Only after the user explicitly approves discarding that worker's session, use:
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
|
-
team-agent
|
|
163
|
+
team-agent reset-agent "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" --discard-session --json
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
Validate:
|
|
@@ -112,12 +112,6 @@ For an existing worker that should restart with a blank provider context, the le
|
|
|
112
112
|
team-agent reset-agent "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" --discard-session --json
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
or the equivalent restart form:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
team-agent restart-agent "$AGENT_ID" --workspace "$WORKSPACE" --team "$TEAM" --discard-session --json
|
|
119
|
-
```
|
|
120
|
-
|
|
121
115
|
Use this only when the user intentionally wants a clean context. For incident recovery, follow `recovery-runbook.md`, where context-preserving repair comes first.
|
|
122
116
|
|
|
123
117
|
## Framework Gaps To Track
|