@team-agent/installer 0.5.61 → 0.5.63
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 +5 -2
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +1 -0
- package/crates/team-agent/src/cli/emit.rs +41 -7
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +6 -1
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +33 -3
- package/crates/team-agent/src/cli/named_address.rs +8 -1
- package/crates/team-agent/src/cli/profile.rs +2 -1
- package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +1 -0
- package/crates/team-agent/src/cli/send/resolve.rs +206 -1
- package/crates/team-agent/src/cli/send.rs +2 -1
- package/crates/team-agent/src/cli/spec.rs +1 -0
- package/crates/team-agent/src/cli/status.rs +2 -1
- package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
- package/crates/team-agent/src/cli/status_port/format.rs +1 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +3 -2
- package/crates/team-agent/src/communication_mode/mod.rs +1 -0
- package/crates/team-agent/src/compiler.rs +1 -0
- package/crates/team-agent/src/conpty/backend.rs +1 -0
- package/crates/team-agent/src/conpty/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/backoff.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
- package/crates/team-agent/src/coordinator/health.rs +1 -0
- package/crates/team-agent/src/coordinator/mod.rs +2 -2
- package/crates/team-agent/src/coordinator/orphan.rs +4 -116
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +15 -866
- package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
- package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +139 -375
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
- package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -49
- package/crates/team-agent/src/coordinator/tick.rs +23 -40
- package/crates/team-agent/src/coordinator/types.rs +3 -176
- package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
- package/crates/team-agent/src/db/message_store.rs +2 -1
- package/crates/team-agent/src/db/migration.rs +5 -4
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/db/schema.rs +1 -0
- package/crates/team-agent/src/diagnose/comms.rs +2 -1
- package/crates/team-agent/src/diagnose/mod.rs +1 -0
- package/crates/team-agent/src/diagnose/orphans.rs +1 -0
- package/crates/team-agent/src/layout/manager.rs +3 -7
- package/crates/team-agent/src/layout/mod.rs +2 -2
- package/crates/team-agent/src/layout/overlay.rs +6 -1
- package/crates/team-agent/src/layout/placement.rs +1 -0
- package/crates/team-agent/src/layout/recovery.rs +3 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
- package/crates/team-agent/src/layout/sessions.rs +8 -1
- package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
- package/crates/team-agent/src/layout/worker_env.rs +3 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/helpers.rs +1 -0
- package/crates/team-agent/src/leader/incident.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +27 -22
- package/crates/team-agent/src/leader/mod.rs +2 -3
- package/crates/team-agent/src/leader/owner_bind.rs +13 -3
- package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +4 -17
- package/crates/team-agent/src/leader/registry.rs +3 -15
- package/crates/team-agent/src/leader/start.rs +373 -125
- package/crates/team-agent/src/leader/takeover.rs +10 -232
- package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
- package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
- package/crates/team-agent/src/leader/tests/identity.rs +7 -2
- package/crates/team-agent/src/leader/tests/idle.rs +0 -193
- package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
- package/crates/team-agent/src/leader/tests/mod.rs +4 -41
- package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
- package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
- package/crates/team-agent/src/leader/types.rs +1 -28
- package/crates/team-agent/src/lib.rs +3 -0
- package/crates/team-agent/src/lifecycle/display.rs +7 -1
- package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -27
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +11 -12
- package/crates/team-agent/src/lifecycle/mod.rs +2 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -66
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
- package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
- package/crates/team-agent/src/lifecycle/restart.rs +13 -15
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
- package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
- package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
- package/crates/team-agent/src/lifecycle/tests.rs +59 -0
- package/crates/team-agent/src/lifecycle/types.rs +0 -9
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
- package/crates/team-agent/src/mcp_server/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +4 -3
- package/crates/team-agent/src/mcp_server/tools.rs +1 -0
- package/crates/team-agent/src/mcp_server/types.rs +1 -18
- package/crates/team-agent/src/mcp_server/wire.rs +5 -5
- package/crates/team-agent/src/messaging/activity.rs +1 -0
- package/crates/team-agent/src/messaging/address.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +4 -3
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
- package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
- package/crates/team-agent/src/messaging/mod.rs +10 -14
- package/crates/team-agent/src/messaging/peers.rs +2 -0
- package/crates/team-agent/src/messaging/persist.rs +3 -1
- package/crates/team-agent/src/messaging/presentation.rs +2 -1
- package/crates/team-agent/src/messaging/results.rs +1 -0
- package/crates/team-agent/src/messaging/scheduler.rs +1 -0
- package/crates/team-agent/src/messaging/selftest.rs +1 -1
- package/crates/team-agent/src/messaging/send.rs +41 -8
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/types.rs +2 -2
- package/crates/team-agent/src/messaging/watchers.rs +19 -13
- package/crates/team-agent/src/model/enums.rs +1 -0
- package/crates/team-agent/src/model/errors.rs +1 -0
- package/crates/team-agent/src/model/ids.rs +1 -0
- package/crates/team-agent/src/model/mod.rs +1 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -0
- package/crates/team-agent/src/model/paths.rs +1 -0
- package/crates/team-agent/src/model/permissions.rs +3 -2
- package/crates/team-agent/src/model/routing.rs +1 -0
- package/crates/team-agent/src/model/spec.rs +1 -0
- package/crates/team-agent/src/model/task_graph.rs +1 -0
- package/crates/team-agent/src/model/yaml/tests.rs +1 -0
- package/crates/team-agent/src/model/yaml.rs +1 -0
- package/crates/team-agent/src/packaging/install.rs +9 -147
- package/crates/team-agent/src/packaging/migrate.rs +2 -0
- package/crates/team-agent/src/packaging/mod.rs +1 -0
- package/crates/team-agent/src/packaging/repair.rs +2 -0
- package/crates/team-agent/src/packaging/tests.rs +29 -218
- package/crates/team-agent/src/packaging/types.rs +8 -15
- package/crates/team-agent/src/platform/argv.rs +4 -0
- package/crates/team-agent/src/platform/errors.rs +10 -1
- package/crates/team-agent/src/platform/file_lock.rs +4 -157
- package/crates/team-agent/src/platform/mod.rs +0 -61
- package/crates/team-agent/src/platform/process.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
- package/crates/team-agent/src/provider/classify.rs +1 -0
- package/crates/team-agent/src/provider/faults.rs +1 -26
- package/crates/team-agent/src/provider/helpers.rs +1 -0
- package/crates/team-agent/src/provider/mod.rs +1 -1
- package/crates/team-agent/src/provider/session/capture.rs +1 -4
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
- package/crates/team-agent/src/provider/session/mod.rs +2 -4
- package/crates/team-agent/src/provider/session/resume.rs +2 -209
- package/crates/team-agent/src/provider/session_scan/claude.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +1 -42
- package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +1 -0
- package/crates/team-agent/src/provider/wire.rs +1 -0
- package/crates/team-agent/src/state/identity.rs +16 -5
- package/crates/team-agent/src/state/identity_keys.rs +1 -0
- package/crates/team-agent/src/state/mod.rs +2 -0
- package/crates/team-agent/src/state/owner_gate.rs +5 -0
- package/crates/team-agent/src/state/ownership.rs +10 -4
- package/crates/team-agent/src/state/paths.rs +12 -0
- package/crates/team-agent/src/state/persist.rs +9 -3
- package/crates/team-agent/src/state/projection.rs +15 -2
- package/crates/team-agent/src/state/repository/intent.rs +1 -0
- package/crates/team-agent/src/state/repository/tests.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +7 -13
- package/crates/team-agent/src/state/selector.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
- package/crates/team-agent/src/tmux_backend.rs +63 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -1,306 +1,134 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
-
use
|
|
4
|
-
atomic::{AtomicU32, Ordering},
|
|
5
|
-
Arc,
|
|
6
|
-
};
|
|
3
|
+
use crate::transport::test_support::OfflineTransport;
|
|
7
4
|
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// bounded, visible worker recovery or loudly explains why not. Recovery must not
|
|
12
|
-
// run inside the abnormal detector's pre-save window, must not treat a live-pid
|
|
13
|
-
// Noop as success, and must never synthesize a hidden provider turn.
|
|
5
|
+
// Removal contract for the retired abnormal-exit automatic recovery path.
|
|
6
|
+
// Abnormal exits remain visible and keep a copyable manual start-agent hint,
|
|
7
|
+
// but coordinator ticks must never schedule or execute lifecycle work.
|
|
14
8
|
|
|
15
9
|
#[test]
|
|
16
|
-
fn
|
|
17
|
-
let case = RecoveryCase::new("
|
|
18
|
-
case.
|
|
19
|
-
let
|
|
10
|
+
fn abnormal_exit_is_reported_without_auto_recovery_intent_or_action() {
|
|
11
|
+
let case = RecoveryCase::new("reported-no-auto");
|
|
12
|
+
case.seed_agent("baseline-429");
|
|
13
|
+
let transport = OfflineTransport::new().with_session_present(true);
|
|
14
|
+
let coord = case.coord(transport.clone());
|
|
20
15
|
coord.tick().expect("baseline tick");
|
|
21
16
|
|
|
22
|
-
case.append_api_error("
|
|
23
|
-
coord.tick().expect("fresh
|
|
17
|
+
case.append_api_error("fresh-429");
|
|
18
|
+
coord.tick().expect("fresh abnormal tick");
|
|
24
19
|
|
|
25
20
|
let events = case.events();
|
|
26
21
|
let abnormal = find_event(&events, "worker.abnormal_exit").expect("abnormal event");
|
|
27
22
|
assert_eq!(abnormal["agent_id"], serde_json::json!("fe-admin"));
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
assert_eq!(scheduled["agent_id"], serde_json::json!("fe-admin"));
|
|
31
|
-
assert_eq!(scheduled["apiErrorStatus"], serde_json::json!(429));
|
|
32
|
-
assert_eq!(scheduled["error"], serde_json::json!("rate_limit"));
|
|
33
|
-
assert_eq!(scheduled["attempt"], serde_json::json!(0));
|
|
23
|
+
assert_eq!(abnormal["signature"], serde_json::json!("api_error"));
|
|
24
|
+
assert_eq!(abnormal["apiErrorStatus"], serde_json::json!(429));
|
|
34
25
|
assert!(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
26
|
+
events.iter().all(|event| {
|
|
27
|
+
let name = event
|
|
28
|
+
.get("event")
|
|
29
|
+
.and_then(serde_json::Value::as_str)
|
|
30
|
+
.unwrap_or_default();
|
|
31
|
+
!name.starts_with("worker.abnormal_exit.recovery_")
|
|
32
|
+
&& !name.starts_with("worker.abnormal_exit.backpressure_")
|
|
33
|
+
}),
|
|
34
|
+
"abnormal detection must not emit automatic recovery/backpressure events; events={events:?}"
|
|
40
35
|
);
|
|
41
36
|
|
|
42
37
|
let state = case.state();
|
|
43
|
-
let intent = state
|
|
44
|
-
.pointer("/coordinator/abnormal_api_error_recovery/agents/fe-admin")
|
|
45
|
-
.expect("R1: state must persist recovery intent");
|
|
46
|
-
assert_eq!(intent["status"], serde_json::json!("scheduled"));
|
|
47
|
-
assert_eq!(intent["attempts"], serde_json::json!(0));
|
|
48
38
|
assert!(
|
|
49
|
-
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
state
|
|
40
|
+
.pointer("/coordinator/abnormal_api_error_recovery")
|
|
41
|
+
.is_none(),
|
|
42
|
+
"abnormal detection must not persist automatic recovery intent; state={state}"
|
|
43
|
+
);
|
|
44
|
+
assert!(
|
|
45
|
+
transport.spawn_records().is_empty(),
|
|
46
|
+
"abnormal detection must not respawn a worker; spawns={:?}",
|
|
47
|
+
transport.spawn_records()
|
|
54
48
|
);
|
|
55
49
|
|
|
56
50
|
let content = case.latest_leader_notification();
|
|
57
51
|
assert!(
|
|
58
|
-
content.contains("
|
|
52
|
+
content.contains("No automatic restart was performed.")
|
|
53
|
+
&& content.contains("To recover manually, run: team-agent start-agent fe-admin")
|
|
59
54
|
&& content.contains("--workspace")
|
|
60
55
|
&& content.contains("--team research")
|
|
61
56
|
&& content.contains("--force")
|
|
62
57
|
&& content.contains("--json"),
|
|
63
|
-
"
|
|
64
|
-
);
|
|
65
|
-
assert!(
|
|
66
|
-
!content.contains("No automatic restart was performed."),
|
|
67
|
-
"R1: old unconditional no-auto-restart text must be replaced; content={content}"
|
|
58
|
+
"abnormal notification must remain visible and keep the copyable manual recovery command; content={content}"
|
|
68
59
|
);
|
|
69
60
|
}
|
|
70
61
|
|
|
71
62
|
#[test]
|
|
72
|
-
fn
|
|
73
|
-
let case = RecoveryCase::new("
|
|
74
|
-
case.
|
|
75
|
-
let
|
|
63
|
+
fn first_turn_death_cannot_enter_auto_respawn_loop_and_manual_start_still_recovers() {
|
|
64
|
+
let case = RecoveryCase::new("first-turn-death-no-loop");
|
|
65
|
+
case.seed_agent("baseline-429");
|
|
66
|
+
let transport = OfflineTransport::new().with_session_present(true);
|
|
67
|
+
let coord = case.coord(transport.clone());
|
|
76
68
|
coord.tick().expect("baseline tick");
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"R2: non-retryable model errors must not schedule auto recovery; state={state}"
|
|
87
|
-
);
|
|
88
|
-
let content = case.latest_leader_notification();
|
|
89
|
-
assert!(
|
|
90
|
-
content.contains("non_retryable_api_error")
|
|
91
|
-
&& content.contains("model_not_found")
|
|
92
|
-
&& content.contains("team-agent start-agent fe-admin")
|
|
93
|
-
&& content.contains("--force"),
|
|
94
|
-
"R2: notification must be guide-only with model/config context and manual command; content={content}"
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
#[test]
|
|
99
|
-
fn r3_live_pid_noop_is_never_counted_as_recovery_success() {
|
|
100
|
-
let abnormal = source("src/coordinator/steps/abnormal.rs");
|
|
101
|
-
let lifecycle = source("src/lifecycle/restart/agent.rs");
|
|
102
|
-
let recovery_mentions_noop_block = abnormal.contains("noop_not_recovery")
|
|
103
|
-
&& abnormal.contains("worker.abnormal_exit.recovery_blocked");
|
|
104
|
-
let lifecycle_has_stop_then_start_mode =
|
|
105
|
-
abnormal.contains("stop_then_start") || lifecycle.contains("stop_before_start");
|
|
106
|
-
assert!(
|
|
107
|
-
recovery_mentions_noop_block || lifecycle_has_stop_then_start_mode,
|
|
108
|
-
"R3: api_error recovery must either stop-before-start live panes or record recovery_blocked(reason=noop_not_recovery); abnormal.rs/lifecycle.rs missing both guards"
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#[test]
|
|
113
|
-
fn r4_short_window_429_cohort_enters_backpressure_with_one_canary() {
|
|
114
|
-
let case = RecoveryCase::new("r4-backpressure");
|
|
115
|
-
let agents = [
|
|
116
|
-
("architect", 429, "rate_limit", "baseline-a"),
|
|
117
|
-
("fe-admin", 429, "rate_limit", "baseline-b"),
|
|
118
|
-
("fe-staff", 429, "rate_limit", "baseline-c"),
|
|
119
|
-
("fe-shop", 429, "rate_limit", "baseline-d"),
|
|
120
|
-
];
|
|
121
|
-
case.seed_agents(&agents);
|
|
122
|
-
let coord = case.coord();
|
|
123
|
-
coord.tick().expect("baseline tick");
|
|
124
|
-
for (agent, _, _, _) in agents {
|
|
125
|
-
case.append_api_error(agent, 429, "rate_limit", &format!("fresh-{agent}"));
|
|
126
|
-
}
|
|
127
|
-
coord.tick().expect("fresh cohort tick");
|
|
70
|
+
// A-29 shape: the replacement worker dies before completing its first
|
|
71
|
+
// turn, with a fresh explicit provider error. The following tick reports
|
|
72
|
+
// the death; a persisted due row models the next 6.5-second loop turn.
|
|
73
|
+
case.mark_first_turn_dead();
|
|
74
|
+
case.append_api_error("fresh-first-turn-death");
|
|
75
|
+
coord.tick().expect("fresh first-turn death tick");
|
|
76
|
+
case.seed_legacy_due_recovery();
|
|
77
|
+
coord.tick().expect("tick with legacy due intent");
|
|
128
78
|
|
|
129
79
|
let events = case.events();
|
|
130
|
-
let
|
|
131
|
-
|
|
132
|
-
assert_eq!(
|
|
133
|
-
assert_eq!(backpressure["provider"], serde_json::json!("claude_code"));
|
|
134
|
-
assert_eq!(backpressure["apiErrorStatus"], serde_json::json!(429));
|
|
135
|
-
assert!(
|
|
136
|
-
backpressure
|
|
137
|
-
.get("cooldown_until")
|
|
138
|
-
.and_then(serde_json::Value::as_str)
|
|
139
|
-
.is_some(),
|
|
140
|
-
"R4: backpressure event must include shared cooldown_until; event={backpressure}"
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
let scheduled_count = events
|
|
144
|
-
.iter()
|
|
145
|
-
.filter(|event| {
|
|
146
|
-
event.get("event").and_then(serde_json::Value::as_str)
|
|
147
|
-
== Some("worker.abnormal_exit.recovery_scheduled")
|
|
148
|
-
})
|
|
149
|
-
.count();
|
|
80
|
+
let abnormal = find_event(&events, "worker.abnormal_exit").expect("abnormal event");
|
|
81
|
+
assert_eq!(abnormal["dead_process"], serde_json::json!(true));
|
|
82
|
+
assert_eq!(abnormal["fresh_error"], serde_json::json!(true));
|
|
150
83
|
assert!(
|
|
151
|
-
|
|
152
|
-
|
|
84
|
+
events.iter().all(|event| {
|
|
85
|
+
let name = event
|
|
86
|
+
.get("event")
|
|
87
|
+
.and_then(serde_json::Value::as_str)
|
|
88
|
+
.unwrap_or_default();
|
|
89
|
+
!name.starts_with("worker.abnormal_exit.recovery_")
|
|
90
|
+
&& !name.starts_with("worker.abnormal_exit.backpressure_")
|
|
91
|
+
}),
|
|
92
|
+
"first-turn death must report only; automatic recovery events make the A-29 loop possible; events={events:?}"
|
|
153
93
|
);
|
|
154
|
-
let state = case.state();
|
|
155
|
-
let bp = state
|
|
156
|
-
.pointer("/coordinator/abnormal_api_error_recovery/backpressure")
|
|
157
|
-
.and_then(serde_json::Value::as_object)
|
|
158
|
-
.expect("R4: backpressure state must be persisted");
|
|
159
|
-
assert_eq!(
|
|
160
|
-
bp.len(),
|
|
161
|
-
1,
|
|
162
|
-
"R4: cohort must share one backpressure record; state={state}"
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
#[test]
|
|
167
|
-
fn r5_retry_budget_exhausts_and_stays_loud() {
|
|
168
|
-
let abnormal = source("src/coordinator/steps/abnormal.rs");
|
|
169
|
-
for needle in [
|
|
170
|
-
"max_attempts",
|
|
171
|
-
"worker.abnormal_exit.recovery_exhausted",
|
|
172
|
-
"manual_command",
|
|
173
|
-
"attempts",
|
|
174
|
-
] {
|
|
175
|
-
assert!(
|
|
176
|
-
abnormal.contains(needle),
|
|
177
|
-
"R5: retry budget contract requires `{needle}` in abnormal api_error recovery state/events"
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
#[test]
|
|
183
|
-
fn r6_abnormal_detector_does_not_mutate_lifecycle_inside_pre_save_window() {
|
|
184
|
-
let body = detect_abnormal_body();
|
|
185
|
-
for forbidden in [
|
|
186
|
-
"start_agent_at_paths",
|
|
187
|
-
"start_agent(",
|
|
188
|
-
"stop_agent_at_paths",
|
|
189
|
-
"reset_agent",
|
|
190
|
-
] {
|
|
191
|
-
assert!(
|
|
192
|
-
!body.contains(forbidden),
|
|
193
|
-
"R6: detect_abnormal_exits is pre-save detection only and must not call `{forbidden}`"
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
#[test]
|
|
199
|
-
fn r6_recovery_fields_survive_the_next_tick_after_atomic_save() {
|
|
200
|
-
let case = RecoveryCase::new("r6-save-boundary");
|
|
201
|
-
case.seed_agents(&[("fe-admin", 429, "rate_limit", "baseline-429")]);
|
|
202
|
-
case.seed_due_recovery("fe-admin");
|
|
203
|
-
let coord = case.coord();
|
|
204
|
-
coord.tick().expect("recovery tick");
|
|
205
|
-
|
|
206
|
-
let events = case.events();
|
|
207
94
|
assert!(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
95
|
+
transport.spawn_records().is_empty(),
|
|
96
|
+
"first-turn death and legacy due intents must never respawn; spawns={:?}",
|
|
97
|
+
transport.spawn_records()
|
|
211
98
|
);
|
|
212
|
-
let state = case.state();
|
|
213
|
-
let recovery = state
|
|
214
|
-
.pointer("/coordinator/abnormal_api_error_recovery/agents/fe-admin")
|
|
215
|
-
.expect("R6: recovery entry must survive tick");
|
|
216
99
|
assert!(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
100
|
+
transport.calls().iter().all(|call| !matches!(
|
|
101
|
+
*call,
|
|
102
|
+
"spawn_first" | "spawn_into" | "kill_pane" | "kill_window"
|
|
103
|
+
)),
|
|
104
|
+
"automatic loop prevention must be structural: no lifecycle transport action; calls={:?}",
|
|
105
|
+
transport.calls()
|
|
221
106
|
);
|
|
222
|
-
}
|
|
223
107
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
let
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
let calls = std::sync::Arc::clone(&transport.calls);
|
|
233
|
-
let coord = Coordinator::for_test(
|
|
234
|
-
WorkspacePath::new(case.root.clone()),
|
|
235
|
-
Box::new(registry),
|
|
236
|
-
Box::new(transport),
|
|
237
|
-
None,
|
|
108
|
+
case.seed_healthy_coordinator();
|
|
109
|
+
let manual_transport = OfflineTransport::new().with_session_present(true);
|
|
110
|
+
let outcome = crate::lifecycle::start_agent_with_transport(
|
|
111
|
+
&case.root,
|
|
112
|
+
&crate::model::ids::AgentId::new("fe-admin"),
|
|
113
|
+
false,
|
|
114
|
+
false,
|
|
115
|
+
false,
|
|
238
116
|
None,
|
|
117
|
+
&manual_transport,
|
|
239
118
|
);
|
|
240
119
|
|
|
241
|
-
coord.tick().expect("recovery tick");
|
|
242
|
-
|
|
243
|
-
assert_eq!(
|
|
244
|
-
counters.adapter_calls.load(Ordering::SeqCst),
|
|
245
|
-
0,
|
|
246
|
-
"R7: recovery must not call provider adapter/client APIs"
|
|
247
|
-
);
|
|
248
120
|
assert!(
|
|
249
|
-
!
|
|
250
|
-
"
|
|
251
|
-
calls.lock().unwrap()
|
|
121
|
+
matches!(outcome, Ok(crate::lifecycle::StartAgentOutcome::Running { .. })),
|
|
122
|
+
"manual start-agent path must remain functional after an abnormal exit; outcome={outcome:?}"
|
|
252
123
|
);
|
|
253
124
|
assert_eq!(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
"
|
|
125
|
+
manual_transport.spawn_records().len(),
|
|
126
|
+
1,
|
|
127
|
+
"manual start-agent must spawn exactly one replacement worker; spawns={:?}",
|
|
128
|
+
manual_transport.spawn_records()
|
|
257
129
|
);
|
|
258
130
|
}
|
|
259
131
|
|
|
260
|
-
#[test]
|
|
261
|
-
fn r8_terminal_recovery_intents_are_idempotent_and_clear_due_time() {
|
|
262
|
-
for status in ["succeeded", "blocked", "exhausted"] {
|
|
263
|
-
let case = RecoveryCase::new(&format!("r8-terminal-{status}"));
|
|
264
|
-
case.seed_agents(&[("fe-admin", 429, "rate_limit", "baseline-429")]);
|
|
265
|
-
case.seed_due_recovery_with_status("fe-admin", status);
|
|
266
|
-
|
|
267
|
-
run_due_recoveries(&case);
|
|
268
|
-
|
|
269
|
-
let events = case.events();
|
|
270
|
-
assert!(
|
|
271
|
-
find_event(&events, "worker.abnormal_exit.recovery_started").is_none(),
|
|
272
|
-
"R8: terminal status `{status}` with stale next_retry_at must be skipped before lifecycle dispatch; events={events:?}"
|
|
273
|
-
);
|
|
274
|
-
let state = case.state();
|
|
275
|
-
let recovery = state
|
|
276
|
-
.pointer("/coordinator/abnormal_api_error_recovery/agents/fe-admin")
|
|
277
|
-
.expect("R8: terminal recovery entry remains auditable");
|
|
278
|
-
assert_eq!(recovery["status"], serde_json::json!(status));
|
|
279
|
-
assert!(
|
|
280
|
-
recovery.get("next_retry_at").is_none()
|
|
281
|
-
|| recovery.get("next_retry_at") == Some(&serde_json::Value::Null),
|
|
282
|
-
"R8: terminal status `{status}` must clear stale next_retry_at so future ticks cannot reuse it; recovery={recovery}"
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
#[test]
|
|
288
|
-
fn r8_non_terminal_due_intents_still_dispatch() {
|
|
289
|
-
for status in ["scheduled", "running"] {
|
|
290
|
-
let case = RecoveryCase::new(&format!("r8-nonterminal-{status}"));
|
|
291
|
-
case.seed_agents(&[("fe-admin", 429, "rate_limit", "baseline-429")]);
|
|
292
|
-
case.seed_due_recovery_with_status("fe-admin", status);
|
|
293
|
-
|
|
294
|
-
run_due_recoveries(&case);
|
|
295
|
-
|
|
296
|
-
let events = case.events();
|
|
297
|
-
assert!(
|
|
298
|
-
find_event(&events, "worker.abnormal_exit.recovery_started").is_some(),
|
|
299
|
-
"R8: non-terminal status `{status}` with due next_retry_at must still dispatch recovery; events={events:?}"
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
132
|
struct RecoveryCase {
|
|
305
133
|
root: std::path::PathBuf,
|
|
306
134
|
}
|
|
@@ -312,7 +140,7 @@ impl RecoveryCase {
|
|
|
312
140
|
.unwrap_or_else(std::env::temp_dir);
|
|
313
141
|
std::fs::create_dir_all(&base).unwrap();
|
|
314
142
|
let root = base.join(format!(
|
|
315
|
-
"team-agent-
|
|
143
|
+
"team-agent-remove-auto-recovery-{tag}-{}-{}",
|
|
316
144
|
std::process::id(),
|
|
317
145
|
std::time::SystemTime::now()
|
|
318
146
|
.duration_since(std::time::UNIX_EPOCH)
|
|
@@ -323,37 +151,19 @@ impl RecoveryCase {
|
|
|
323
151
|
Self { root }
|
|
324
152
|
}
|
|
325
153
|
|
|
326
|
-
fn coord(&self) -> Coordinator {
|
|
154
|
+
fn coord(&self, transport: OfflineTransport) -> Coordinator {
|
|
327
155
|
Coordinator::for_test(
|
|
328
156
|
WorkspacePath::new(self.root.clone()),
|
|
329
157
|
Box::new(MockRegistry::new(&[], &[])),
|
|
330
|
-
Box::new(
|
|
158
|
+
Box::new(transport),
|
|
331
159
|
None,
|
|
332
160
|
None,
|
|
333
161
|
)
|
|
334
162
|
}
|
|
335
163
|
|
|
336
|
-
fn
|
|
337
|
-
let
|
|
338
|
-
|
|
339
|
-
let rollout = self.rollout(agent);
|
|
340
|
-
std::fs::write(&rollout, claude_api_error_line(uuid, *status, error)).unwrap();
|
|
341
|
-
agent_map.insert(
|
|
342
|
-
(*agent).to_string(),
|
|
343
|
-
serde_json::json!({
|
|
344
|
-
"agent_id": agent,
|
|
345
|
-
"id": agent,
|
|
346
|
-
"provider": "claude_code",
|
|
347
|
-
"model": "sonnet",
|
|
348
|
-
"status": "running",
|
|
349
|
-
"window": agent,
|
|
350
|
-
"rollout_path": rollout,
|
|
351
|
-
"provider_process_alive": true,
|
|
352
|
-
"spawn_epoch": 1,
|
|
353
|
-
"spawned_at": "2026-07-12T10:28:00Z"
|
|
354
|
-
}),
|
|
355
|
-
);
|
|
356
|
-
}
|
|
164
|
+
fn seed_agent(&self, uuid: &str) {
|
|
165
|
+
let rollout = self.rollout();
|
|
166
|
+
std::fs::write(&rollout, claude_api_error_line(uuid)).unwrap();
|
|
357
167
|
let state = serde_json::json!({
|
|
358
168
|
"active_team_key": "research",
|
|
359
169
|
"team_key": "research",
|
|
@@ -365,44 +175,73 @@ impl RecoveryCase {
|
|
|
365
175
|
"pane_id": "%leader",
|
|
366
176
|
"provider": "codex"
|
|
367
177
|
},
|
|
368
|
-
"agents":
|
|
178
|
+
"agents": {
|
|
179
|
+
"fe-admin": {
|
|
180
|
+
"agent_id": "fe-admin",
|
|
181
|
+
"id": "fe-admin",
|
|
182
|
+
"provider": "claude_code",
|
|
183
|
+
"model": "sonnet",
|
|
184
|
+
"status": "running",
|
|
185
|
+
"window": "fe-admin",
|
|
186
|
+
"rollout_path": rollout,
|
|
187
|
+
"session_id": "session-supermarket",
|
|
188
|
+
"provider_process_alive": true,
|
|
189
|
+
"spawn_epoch": 1,
|
|
190
|
+
"spawned_at": "2026-07-12T10:28:00Z"
|
|
191
|
+
}
|
|
192
|
+
},
|
|
369
193
|
"tasks": []
|
|
370
194
|
});
|
|
371
195
|
crate::state::persist::save_runtime_state(&self.root, &state).unwrap();
|
|
372
196
|
}
|
|
373
197
|
|
|
374
|
-
fn
|
|
375
|
-
self.seed_due_recovery_with_status(agent, "scheduled");
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
fn seed_due_recovery_with_status(&self, agent: &str, status: &str) {
|
|
198
|
+
fn seed_legacy_due_recovery(&self) {
|
|
379
199
|
let mut state = self.state();
|
|
380
|
-
state["coordinator"]["abnormal_api_error_recovery"]["agents"][
|
|
381
|
-
"error_key":
|
|
200
|
+
state["coordinator"]["abnormal_api_error_recovery"]["agents"]["fe-admin"] = serde_json::json!({
|
|
201
|
+
"error_key": "worker.abnormal_exit.error:fe-admin:rollout:api_error:seed",
|
|
382
202
|
"cohort_key": "research:claude_code:api_error:429:rate_limit",
|
|
383
|
-
"status":
|
|
203
|
+
"status": "scheduled",
|
|
384
204
|
"attempts": 0,
|
|
385
205
|
"max_attempts": 2,
|
|
386
206
|
"next_retry_at": "2000-01-01T00:00:00Z",
|
|
387
|
-
"
|
|
388
|
-
|
|
389
|
-
|
|
207
|
+
"manual_command": format!(
|
|
208
|
+
"team-agent start-agent fe-admin --workspace '{}' --team research --force --json",
|
|
209
|
+
self.root.display()
|
|
210
|
+
)
|
|
390
211
|
});
|
|
391
212
|
crate::state::persist::save_runtime_state(&self.root, &state).unwrap();
|
|
392
213
|
}
|
|
393
214
|
|
|
394
|
-
fn
|
|
215
|
+
fn mark_first_turn_dead(&self) {
|
|
216
|
+
let mut state = self.state();
|
|
217
|
+
let agent = &mut state["agents"]["fe-admin"];
|
|
218
|
+
agent["provider_process_alive"] = serde_json::json!(false);
|
|
219
|
+
agent["process_liveness"] = serde_json::json!("dead");
|
|
220
|
+
agent["last_output_at"] = serde_json::Value::Null;
|
|
221
|
+
agent["first_send_at"] = serde_json::Value::Null;
|
|
222
|
+
crate::state::persist::save_runtime_state(&self.root, &state).unwrap();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn seed_healthy_coordinator(&self) {
|
|
226
|
+
let workspace = WorkspacePath::new(self.root.clone());
|
|
227
|
+
let _ = crate::message_store::MessageStore::open(&self.root).unwrap();
|
|
228
|
+
let pid = Pid::new(std::process::id());
|
|
229
|
+
write_coordinator_metadata(&workspace, pid, MetadataSource::Boot).unwrap();
|
|
230
|
+
std::fs::write(coordinator_pid_path(&workspace), pid.to_string()).unwrap();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn append_api_error(&self, uuid: &str) {
|
|
395
234
|
use std::io::Write as _;
|
|
396
235
|
let mut file = std::fs::OpenOptions::new()
|
|
397
236
|
.append(true)
|
|
398
|
-
.open(self.rollout(
|
|
237
|
+
.open(self.rollout())
|
|
399
238
|
.unwrap();
|
|
400
|
-
file.write_all(claude_api_error_line(uuid
|
|
239
|
+
file.write_all(claude_api_error_line(uuid).as_bytes())
|
|
401
240
|
.unwrap();
|
|
402
241
|
}
|
|
403
242
|
|
|
404
|
-
fn rollout(&self
|
|
405
|
-
self.root.join(
|
|
243
|
+
fn rollout(&self) -> std::path::PathBuf {
|
|
244
|
+
self.root.join("rollout-fe-admin.jsonl")
|
|
406
245
|
}
|
|
407
246
|
|
|
408
247
|
fn state(&self) -> serde_json::Value {
|
|
@@ -423,17 +262,6 @@ impl RecoveryCase {
|
|
|
423
262
|
)
|
|
424
263
|
.unwrap_or_default()
|
|
425
264
|
}
|
|
426
|
-
|
|
427
|
-
fn non_leader_message_count(&self) -> i64 {
|
|
428
|
-
let db = crate::model::paths::runtime_dir(&self.root).join("team.db");
|
|
429
|
-
let conn = crate::db::schema::open_db(&db).unwrap();
|
|
430
|
-
conn.query_row(
|
|
431
|
-
"select count(*) from messages where recipient != 'leader'",
|
|
432
|
-
[],
|
|
433
|
-
|row| row.get::<_, i64>(0),
|
|
434
|
-
)
|
|
435
|
-
.unwrap()
|
|
436
|
-
}
|
|
437
265
|
}
|
|
438
266
|
|
|
439
267
|
impl Drop for RecoveryCase {
|
|
@@ -444,46 +272,7 @@ impl Drop for RecoveryCase {
|
|
|
444
272
|
}
|
|
445
273
|
}
|
|
446
274
|
|
|
447
|
-
|
|
448
|
-
struct CounterState {
|
|
449
|
-
adapter_calls: Arc<AtomicU32>,
|
|
450
|
-
error_list_calls: Arc<AtomicU32>,
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
struct CountingRegistry {
|
|
454
|
-
counters: CounterState,
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
impl CountingRegistry {
|
|
458
|
-
fn new() -> Self {
|
|
459
|
-
Self {
|
|
460
|
-
counters: CounterState {
|
|
461
|
-
adapter_calls: Arc::new(AtomicU32::new(0)),
|
|
462
|
-
error_list_calls: Arc::new(AtomicU32::new(0)),
|
|
463
|
-
},
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
fn counters(&self) -> CounterState {
|
|
468
|
-
self.counters.clone()
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
impl ProviderRegistry for CountingRegistry {
|
|
473
|
-
fn adapter_for(&self, provider: Provider) -> Box<dyn ProviderAdapter> {
|
|
474
|
-
self.counters.adapter_calls.fetch_add(1, Ordering::SeqCst);
|
|
475
|
-
crate::provider::get_adapter(provider)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
fn error_lists(&self, _provider: Provider) -> ErrorLists {
|
|
479
|
-
self.counters
|
|
480
|
-
.error_list_calls
|
|
481
|
-
.fetch_add(1, Ordering::SeqCst);
|
|
482
|
-
ErrorLists::default()
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
fn claude_api_error_line(uuid: &str, status: i64, error: &str) -> String {
|
|
275
|
+
fn claude_api_error_line(uuid: &str) -> String {
|
|
487
276
|
format!(
|
|
488
277
|
"{}\n",
|
|
489
278
|
serde_json::json!({
|
|
@@ -492,11 +281,11 @@ fn claude_api_error_line(uuid: &str, status: i64, error: &str) -> String {
|
|
|
492
281
|
"uuid": uuid,
|
|
493
282
|
"requestId": format!("req-{uuid}"),
|
|
494
283
|
"message": {"role": "assistant", "content": [
|
|
495
|
-
{"type": "text", "text":
|
|
284
|
+
{"type": "text", "text": "API Error: 429 rate_limit"}
|
|
496
285
|
]},
|
|
497
|
-
"error":
|
|
286
|
+
"error": "rate_limit",
|
|
498
287
|
"isApiErrorMessage": true,
|
|
499
|
-
"apiErrorStatus":
|
|
288
|
+
"apiErrorStatus": 429,
|
|
500
289
|
"sessionId": "session-supermarket",
|
|
501
290
|
"version": "2.1.181"
|
|
502
291
|
})
|
|
@@ -509,28 +298,3 @@ fn find_event<'a>(events: &'a [serde_json::Value], name: &str) -> Option<&'a ser
|
|
|
509
298
|
.rev()
|
|
510
299
|
.find(|event| event.get("event").and_then(serde_json::Value::as_str) == Some(name))
|
|
511
300
|
}
|
|
512
|
-
|
|
513
|
-
fn source(rel: &str) -> String {
|
|
514
|
-
std::fs::read_to_string(std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(rel))
|
|
515
|
-
.expect("read source")
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
fn detect_abnormal_body() -> String {
|
|
519
|
-
let source = source("src/coordinator/steps/abnormal.rs");
|
|
520
|
-
let start = source
|
|
521
|
-
.find("pub(crate) fn detect_abnormal_exits")
|
|
522
|
-
.expect("detect_abnormal_exits exists");
|
|
523
|
-
let end = source[start..]
|
|
524
|
-
.find("#[derive")
|
|
525
|
-
.map(|offset| start + offset)
|
|
526
|
-
.expect("derive after detect_abnormal_exits");
|
|
527
|
-
source[start..end].to_string()
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
fn run_due_recoveries(case: &RecoveryCase) {
|
|
531
|
-
crate::coordinator::steps::abnormal::attempt_due_recoveries(
|
|
532
|
-
&case.root,
|
|
533
|
-
&crate::event_log::EventLog::new(&case.root),
|
|
534
|
-
&MockTransport::new(true),
|
|
535
|
-
);
|
|
536
|
-
}
|