@team-agent/installer 0.5.42 → 0.5.44
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 +129 -54
- package/crates/team-agent/src/cli/diagnose.rs +1 -2
- package/crates/team-agent/src/cli/emit.rs +1 -7
- package/crates/team-agent/src/cli/helpers.rs +3 -1
- package/crates/team-agent/src/cli/leader.rs +2 -1
- package/crates/team-agent/src/cli/mod.rs +126 -16
- package/crates/team-agent/src/cli/named_address.rs +14 -5
- package/crates/team-agent/src/cli/profile.rs +19 -7
- package/crates/team-agent/src/cli/send.rs +9 -3
- package/crates/team-agent/src/cli/status.rs +8 -30
- package/crates/team-agent/src/cli/status_port.rs +1341 -1272
- package/crates/team-agent/src/cli/tests/base.rs +738 -660
- package/crates/team-agent/src/cli/tests/compile.rs +45 -18
- package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
- package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
- package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
- package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
- package/crates/team-agent/src/cli/tests/mod.rs +17 -7
- package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
- package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
- package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
- package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
- package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
- package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
- package/crates/team-agent/src/codex_app_server.rs +2 -5
- package/crates/team-agent/src/compiler/tests.rs +139 -33
- package/crates/team-agent/src/compiler.rs +55 -22
- package/crates/team-agent/src/conpty/backend.rs +23 -33
- package/crates/team-agent/src/coordinator/backoff.rs +2 -7
- package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
- package/crates/team-agent/src/coordinator/health.rs +46 -31
- package/crates/team-agent/src/coordinator/mod.rs +3 -3
- package/crates/team-agent/src/coordinator/orphan.rs +22 -10
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
- package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
- package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
- package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
- package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
- package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
- package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
- package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
- package/crates/team-agent/src/db/message_store.rs +138 -30
- package/crates/team-agent/src/db/migration.rs +249 -61
- package/crates/team-agent/src/db/schema.rs +303 -82
- package/crates/team-agent/src/diagnose/comms.rs +9 -2
- package/crates/team-agent/src/diagnose/mod.rs +1 -3
- package/crates/team-agent/src/diagnose/orphans.rs +79 -61
- package/crates/team-agent/src/event_log.rs +70 -16
- package/crates/team-agent/src/layout/manager.rs +15 -4
- package/crates/team-agent/src/layout/mod.rs +4 -4
- package/crates/team-agent/src/layout/overlay.rs +10 -3
- package/crates/team-agent/src/layout/placement.rs +5 -1
- package/crates/team-agent/src/layout/recovery.rs +4 -2
- package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
- package/crates/team-agent/src/layout/sessions.rs +17 -9
- package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
- package/crates/team-agent/src/layout/worker_env.rs +87 -19
- package/crates/team-agent/src/leader/helpers.rs +7 -1
- package/crates/team-agent/src/leader/lease.rs +199 -89
- package/crates/team-agent/src/leader/owner_bind.rs +55 -22
- package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
- package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
- package/crates/team-agent/src/leader/rediscover.rs +74 -25
- package/crates/team-agent/src/leader/registry.rs +1 -1
- package/crates/team-agent/src/leader/start.rs +75 -54
- package/crates/team-agent/src/leader/takeover.rs +46 -11
- package/crates/team-agent/src/leader/tests/basics.rs +320 -167
- package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
- package/crates/team-agent/src/leader/tests/identity.rs +428 -356
- package/crates/team-agent/src/leader/tests/idle.rs +285 -254
- package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
- package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
- package/crates/team-agent/src/leader/tests/mod.rs +115 -99
- package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
- package/crates/team-agent/src/lib.rs +4 -4
- package/crates/team-agent/src/lifecycle/display.rs +7 -3
- package/crates/team-agent/src/lifecycle/launch.rs +55 -15
- package/crates/team-agent/src/lifecycle/mod.rs +9 -1
- package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
- package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
- package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
- package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
- package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
- package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
- package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests.rs +2 -2
- package/crates/team-agent/src/main.rs +4 -4
- package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
- package/crates/team-agent/src/mcp_server/mod.rs +11 -2
- package/crates/team-agent/src/mcp_server/types.rs +14 -3
- package/crates/team-agent/src/mcp_server/wire.rs +230 -68
- package/crates/team-agent/src/messaging/delivery.rs +20 -18
- package/crates/team-agent/src/messaging/helpers.rs +25 -4
- package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
- package/crates/team-agent/src/messaging/mod.rs +2 -3
- package/crates/team-agent/src/messaging/selftest.rs +46 -26
- package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
- package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
- package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
- package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
- package/crates/team-agent/src/messaging/trust.rs +25 -2
- package/crates/team-agent/src/messaging/watchers.rs +37 -9
- package/crates/team-agent/src/model/enums.rs +65 -16
- package/crates/team-agent/src/model/ids.rs +12 -3
- package/crates/team-agent/src/model/paths.rs +28 -7
- package/crates/team-agent/src/model/permissions.rs +176 -33
- package/crates/team-agent/src/model/routing.rs +66 -20
- package/crates/team-agent/src/model/spec.rs +365 -69
- package/crates/team-agent/src/model/task_graph.rs +36 -9
- package/crates/team-agent/src/model/yaml/tests.rs +24 -6
- package/crates/team-agent/src/model/yaml.rs +7 -6
- package/crates/team-agent/src/packaging/install.rs +23 -9
- package/crates/team-agent/src/packaging/migrate.rs +5 -7
- package/crates/team-agent/src/packaging/mod.rs +9 -1
- package/crates/team-agent/src/packaging/repair.rs +13 -6
- package/crates/team-agent/src/packaging/tests.rs +63 -16
- package/crates/team-agent/src/packaging/types.rs +22 -7
- package/crates/team-agent/src/platform/argv.rs +4 -1
- package/crates/team-agent/src/platform/file_lock.rs +22 -8
- package/crates/team-agent/src/platform/process.rs +54 -24
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
- package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
- package/crates/team-agent/src/provider/classify.rs +127 -42
- package/crates/team-agent/src/provider/faults.rs +17 -5
- package/crates/team-agent/src/provider/helpers.rs +6 -5
- package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
- package/crates/team-agent/src/state/persist.rs +28 -0
- package/crates/team-agent/src/state/repository.rs +8 -3
- package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
- package/crates/team-agent/src/tmux_backend.rs +80 -44
- package/crates/team-agent/src/topology.rs +40 -20
- package/crates/team-agent/src/transport/test_support.rs +20 -23
- package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
- package/crates/team-agent/src/transport/tests/mod.rs +178 -187
- package/crates/team-agent/src/transport/tests/wire.rs +561 -525
- package/crates/team-agent/src/transport.rs +12 -17
- package/crates/team-agent/src/transport_factory.rs +29 -14
- package/package.json +4 -4
|
@@ -81,13 +81,13 @@ pub mod types;
|
|
|
81
81
|
// ── Re-export the full module-root surface (RE-EXPORT INVARIANT) ──────────────
|
|
82
82
|
// 这些 `pub use`/`pub(crate) use` 把每个曾在 module 根可见的 item 重新 surface 到
|
|
83
83
|
// `crate::coordinator::X`,使外部 crate 路径 + 测试 `use super::*` 解析保持不变。
|
|
84
|
-
pub use types::*;
|
|
85
|
-
pub use tick::*;
|
|
86
84
|
pub use backoff::*;
|
|
87
|
-
pub use orphan::*;
|
|
88
85
|
pub use health::*;
|
|
86
|
+
pub use orphan::*;
|
|
89
87
|
pub use runtime_detectors::*;
|
|
90
88
|
pub use runtime_observation::*;
|
|
89
|
+
pub use tick::*;
|
|
90
|
+
pub use types::*;
|
|
91
91
|
|
|
92
92
|
#[cfg(test)]
|
|
93
93
|
mod tests;
|
|
@@ -82,7 +82,10 @@ pub fn process_abnormal_records(
|
|
|
82
82
|
.unwrap_or("unknown")
|
|
83
83
|
.to_string();
|
|
84
84
|
let signature_lower = signature.to_lowercase();
|
|
85
|
-
let turn_id = record
|
|
85
|
+
let turn_id = record
|
|
86
|
+
.get("turn_id")
|
|
87
|
+
.and_then(Value::as_str)
|
|
88
|
+
.map(TurnId::new);
|
|
86
89
|
let bucket = match &turn_id {
|
|
87
90
|
Some(id) => id.as_str().to_string(),
|
|
88
91
|
None => stable_fingerprint(record),
|
|
@@ -95,13 +98,14 @@ pub fn process_abnormal_records(
|
|
|
95
98
|
if lists.whitelist.iter().any(|needle| raw.contains(needle)) {
|
|
96
99
|
continue;
|
|
97
100
|
}
|
|
98
|
-
let decision =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
let decision =
|
|
102
|
+
if lists.blacklist.iter().any(|needle| {
|
|
103
|
+
raw.contains(needle) || signature_lower.contains(&needle.to_lowercase())
|
|
104
|
+
}) {
|
|
105
|
+
AbnormalDecision::NotifyBlacklist
|
|
106
|
+
} else {
|
|
107
|
+
AbnormalDecision::NotifyDefault
|
|
108
|
+
};
|
|
105
109
|
let kind = record.get("kind").and_then(Value::as_str);
|
|
106
110
|
notifications.push(AbnormalNotification {
|
|
107
111
|
signature,
|
|
@@ -141,7 +145,13 @@ pub fn detect_whole_team_gone(
|
|
|
141
145
|
return whole_team_report(true, WholeTeamGoneClass::CleanShutdown, false, false, false);
|
|
142
146
|
}
|
|
143
147
|
if snapshot.restart_in_progress {
|
|
144
|
-
return whole_team_report(
|
|
148
|
+
return whole_team_report(
|
|
149
|
+
true,
|
|
150
|
+
WholeTeamGoneClass::RestartInProgress,
|
|
151
|
+
false,
|
|
152
|
+
false,
|
|
153
|
+
false,
|
|
154
|
+
);
|
|
145
155
|
}
|
|
146
156
|
let marker_written = marker_store.set_marker(
|
|
147
157
|
"whole_team_gone",
|
|
@@ -158,7 +168,9 @@ pub fn detect_whole_team_gone(
|
|
|
158
168
|
|
|
159
169
|
fn stable_fingerprint(value: &Value) -> String {
|
|
160
170
|
let mut hasher = std::collections::hash_map::DefaultHasher::new();
|
|
161
|
-
serde_json::to_string(value)
|
|
171
|
+
serde_json::to_string(value)
|
|
172
|
+
.unwrap_or_else(|_| value.to_string())
|
|
173
|
+
.hash(&mut hasher);
|
|
162
174
|
format!("{:016x}", hasher.finish())
|
|
163
175
|
}
|
|
164
176
|
|
|
@@ -178,11 +178,8 @@ pub(crate) fn detect_abnormal_exits(
|
|
|
178
178
|
// intent (retryable only), then compose the policy-aware tail. This
|
|
179
179
|
// is INTENT WRITE only — actual lifecycle work happens post-save
|
|
180
180
|
// via `attempt_due_recoveries` (§7.3, R6 guard).
|
|
181
|
-
let manual_command =
|
|
182
|
-
agent.agent_id.as_str(),
|
|
183
|
-
workspace,
|
|
184
|
-
team.as_str(),
|
|
185
|
-
);
|
|
181
|
+
let manual_command =
|
|
182
|
+
recovery_manual_command(agent.agent_id.as_str(), workspace, team.as_str());
|
|
186
183
|
let error_observation_key_string = error_observation_key.clone().unwrap_or_default();
|
|
187
184
|
let (recovery_class, recovery_schedule, recovery_cohort_key, recovery_backpressure_until) =
|
|
188
185
|
process_api_error_recovery_intent(
|
|
@@ -202,14 +199,8 @@ pub(crate) fn detect_abnormal_exits(
|
|
|
202
199
|
&fact,
|
|
203
200
|
&manual_command,
|
|
204
201
|
);
|
|
205
|
-
let content =
|
|
206
|
-
&team,
|
|
207
|
-
&agent,
|
|
208
|
-
&fact,
|
|
209
|
-
&liveness,
|
|
210
|
-
size,
|
|
211
|
-
&recovery_tail,
|
|
212
|
-
);
|
|
202
|
+
let content =
|
|
203
|
+
format_abnormal_exit_message(&team, &agent, &fact, &liveness, size, &recovery_tail);
|
|
213
204
|
let outcome = crate::messaging::send_to_leader_receiver(
|
|
214
205
|
workspace,
|
|
215
206
|
state,
|
|
@@ -722,8 +713,8 @@ fn command_process_check_with_marker(
|
|
|
722
713
|
return check;
|
|
723
714
|
}
|
|
724
715
|
process_check(
|
|
725
|
-
ProcessLiveness::
|
|
726
|
-
format!("
|
|
716
|
+
ProcessLiveness::Unverifiable,
|
|
717
|
+
format!("provider_evidence_unverifiable:{command}"),
|
|
727
718
|
)
|
|
728
719
|
}
|
|
729
720
|
|
|
@@ -742,8 +733,8 @@ fn pane_command_process_check_with_marker(
|
|
|
742
733
|
return check;
|
|
743
734
|
}
|
|
744
735
|
process_check(
|
|
745
|
-
ProcessLiveness::
|
|
746
|
-
format!("
|
|
736
|
+
ProcessLiveness::Unverifiable,
|
|
737
|
+
format!("provider_evidence_unverifiable:{command}"),
|
|
747
738
|
)
|
|
748
739
|
}
|
|
749
740
|
|
|
@@ -1471,7 +1462,10 @@ fn record_backpressure_event(
|
|
|
1471
1462
|
if reset_window {
|
|
1472
1463
|
obj.insert("window_started_at".to_string(), serde_json::json!(now_str));
|
|
1473
1464
|
obj.insert("count".to_string(), serde_json::json!(0u64));
|
|
1474
|
-
obj.insert(
|
|
1465
|
+
obj.insert(
|
|
1466
|
+
"agents".to_string(),
|
|
1467
|
+
serde_json::json!(Vec::<String>::new()),
|
|
1468
|
+
);
|
|
1475
1469
|
}
|
|
1476
1470
|
obj.insert("last_seen_at".to_string(), serde_json::json!(now_str));
|
|
1477
1471
|
let count = obj
|
|
@@ -1504,7 +1498,10 @@ fn record_backpressure_event(
|
|
|
1504
1498
|
.map(|dt| dt.with_timezone(&chrono::Utc));
|
|
1505
1499
|
} else if (count as usize) >= BACKPRESSURE_THRESHOLD {
|
|
1506
1500
|
let until = now + cooldown;
|
|
1507
|
-
obj.insert(
|
|
1501
|
+
obj.insert(
|
|
1502
|
+
"cooldown_until".to_string(),
|
|
1503
|
+
serde_json::json!(until.to_rfc3339()),
|
|
1504
|
+
);
|
|
1508
1505
|
obj.insert("status".to_string(), serde_json::json!("active"));
|
|
1509
1506
|
cooldown_until = Some(until);
|
|
1510
1507
|
just_activated = true;
|
|
@@ -1721,9 +1718,9 @@ fn process_api_error_recovery_intent(
|
|
|
1721
1718
|
// cohort already holds a scheduled intent, defer the new arrival to
|
|
1722
1719
|
// the cohort cooldown (or a synthetic short cooldown if none yet).
|
|
1723
1720
|
if canary_active {
|
|
1724
|
-
let synthetic = bp_decision
|
|
1725
|
-
|
|
1726
|
-
|
|
1721
|
+
let synthetic = bp_decision
|
|
1722
|
+
.cooldown_until
|
|
1723
|
+
.unwrap_or_else(|| now + chrono::Duration::seconds(BACKPRESSURE_COOLDOWN_SECS));
|
|
1727
1724
|
bp_decision.cooldown_until = Some(synthetic);
|
|
1728
1725
|
}
|
|
1729
1726
|
if bp_decision.just_activated {
|
|
@@ -1816,7 +1813,12 @@ fn process_api_error_recovery_intent(
|
|
|
1816
1813
|
}),
|
|
1817
1814
|
)?;
|
|
1818
1815
|
}
|
|
1819
|
-
Ok((
|
|
1816
|
+
Ok((
|
|
1817
|
+
class,
|
|
1818
|
+
schedule,
|
|
1819
|
+
bp_decision.cohort_key,
|
|
1820
|
+
bp_decision.cooldown_until,
|
|
1821
|
+
))
|
|
1820
1822
|
}
|
|
1821
1823
|
|
|
1822
1824
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1837,16 +1839,22 @@ pub(crate) fn attempt_due_recoveries(
|
|
|
1837
1839
|
event_log: &EventLog,
|
|
1838
1840
|
transport: &dyn crate::transport::Transport,
|
|
1839
1841
|
) {
|
|
1840
|
-
|
|
1842
|
+
// 0.5.43 debt-sweep (§5 D-j): single fresh post-save load. Do NOT
|
|
1843
|
+
// re-use the tick's pre-save Value — that would reopen the 0.5.36
|
|
1844
|
+
// R6 lifecycle-save-clobbered-by-tick window (highest risk item in
|
|
1845
|
+
// this slice per locate). We own our load here, mutate in memory
|
|
1846
|
+
// (`clear_stale_terminal_next_retry_at` is now a pure
|
|
1847
|
+
// `(&mut Value) -> bool` scrub), persist through the existing
|
|
1848
|
+
// allowlisted root save when the scrub actually changed a row, and
|
|
1849
|
+
// then collect due agents from the SAME post-scrub Value so
|
|
1850
|
+
// terminal intents newly stripped of `next_retry_at` cannot double-
|
|
1851
|
+
// fire below.
|
|
1852
|
+
let Ok(mut state) = crate::state::persist::load_runtime_state(workspace) else {
|
|
1841
1853
|
return;
|
|
1842
1854
|
};
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
// a coordinator restart could get re-dispatched on the next tick,
|
|
1847
|
-
// producing a spurious `recovery_started` event for an intent whose
|
|
1848
|
-
// lifecycle already completed.
|
|
1849
|
-
clear_stale_terminal_next_retry_at(workspace);
|
|
1855
|
+
if clear_stale_terminal_next_retry_at(&mut state) {
|
|
1856
|
+
let _ = crate::state::persist::save_runtime_state(workspace, &state);
|
|
1857
|
+
}
|
|
1850
1858
|
let due_agents = collect_due_recovery_agents(&state);
|
|
1851
1859
|
for agent_id in due_agents {
|
|
1852
1860
|
run_single_recovery(workspace, event_log, transport, &agent_id);
|
|
@@ -1863,10 +1871,7 @@ fn collect_due_recovery_agents(state: &Value) -> Vec<String> {
|
|
|
1863
1871
|
};
|
|
1864
1872
|
let mut due = Vec::new();
|
|
1865
1873
|
for (agent_id, intent) in agents {
|
|
1866
|
-
let status = intent
|
|
1867
|
-
.get("status")
|
|
1868
|
-
.and_then(Value::as_str)
|
|
1869
|
-
.unwrap_or("");
|
|
1874
|
+
let status = intent.get("status").and_then(Value::as_str).unwrap_or("");
|
|
1870
1875
|
// 0.5.37 R8: dispatch only for non-terminal states. `succeeded`,
|
|
1871
1876
|
// `blocked`, `exhausted`, `backpressured` are terminal / awaiting
|
|
1872
1877
|
// manual action — a lifecycle dispatch here would be double-fire
|
|
@@ -1890,17 +1895,15 @@ fn collect_due_recovery_agents(state: &Value) -> Vec<String> {
|
|
|
1890
1895
|
due
|
|
1891
1896
|
}
|
|
1892
1897
|
|
|
1893
|
-
/// 0.5.37
|
|
1894
|
-
/// (`succeeded
|
|
1895
|
-
/// `next_retry_at`.
|
|
1896
|
-
///
|
|
1897
|
-
///
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
return;
|
|
1901
|
-
};
|
|
1898
|
+
/// 0.5.37 R8 + 0.5.43 debt-sweep §5 D-j: pure scrub helper. Terminal
|
|
1899
|
+
/// recovery intents (`succeeded`/`blocked`/`exhausted`) must not carry
|
|
1900
|
+
/// a stale `next_retry_at`. Returns true when at least one row was
|
|
1901
|
+
/// stripped, so the caller can save through its own load/write cycle
|
|
1902
|
+
/// (no double-load, no double-save). No I/O in this function — the
|
|
1903
|
+
/// only responsibility is mutation on a caller-owned `Value`.
|
|
1904
|
+
fn clear_stale_terminal_next_retry_at(state: &mut Value) -> bool {
|
|
1902
1905
|
let mut mutated = false;
|
|
1903
|
-
if let Some(agents) = recovery_intent_agents(
|
|
1906
|
+
if let Some(agents) = recovery_intent_agents(state) {
|
|
1904
1907
|
for (_agent_id, entry) in agents.iter_mut() {
|
|
1905
1908
|
let Some(obj) = entry.as_object_mut() else {
|
|
1906
1909
|
continue;
|
|
@@ -1913,19 +1916,14 @@ fn clear_stale_terminal_next_retry_at(workspace: &Path) {
|
|
|
1913
1916
|
if !matches!(status.as_str(), "succeeded" | "blocked" | "exhausted") {
|
|
1914
1917
|
continue;
|
|
1915
1918
|
}
|
|
1916
|
-
let had_stale = obj
|
|
1917
|
-
.get("next_retry_at")
|
|
1918
|
-
.filter(|v| !v.is_null())
|
|
1919
|
-
.is_some();
|
|
1919
|
+
let had_stale = obj.get("next_retry_at").filter(|v| !v.is_null()).is_some();
|
|
1920
1920
|
if had_stale {
|
|
1921
1921
|
obj.remove("next_retry_at");
|
|
1922
1922
|
mutated = true;
|
|
1923
1923
|
}
|
|
1924
1924
|
}
|
|
1925
1925
|
}
|
|
1926
|
-
|
|
1927
|
-
let _ = crate::state::persist::save_runtime_state(workspace, &state);
|
|
1928
|
-
}
|
|
1926
|
+
mutated
|
|
1929
1927
|
}
|
|
1930
1928
|
|
|
1931
1929
|
fn run_single_recovery(
|
|
@@ -2094,10 +2092,7 @@ fn write_recovery_intent_result(workspace: &Path, agent_id: &str, update: Recove
|
|
|
2094
2092
|
);
|
|
2095
2093
|
match update.blocked_reason {
|
|
2096
2094
|
Some(reason) => {
|
|
2097
|
-
obj.insert(
|
|
2098
|
-
"blocked_reason".to_string(),
|
|
2099
|
-
serde_json::json!(reason),
|
|
2100
|
-
);
|
|
2095
|
+
obj.insert("blocked_reason".to_string(), serde_json::json!(reason));
|
|
2101
2096
|
}
|
|
2102
2097
|
None => {
|
|
2103
2098
|
obj.remove("blocked_reason");
|
|
@@ -22,9 +22,16 @@ fn abnormal_default_notify_for_structured_fault() {
|
|
|
22
22
|
)
|
|
23
23
|
.unwrap();
|
|
24
24
|
assert_eq!(out.notifications.len(), 1);
|
|
25
|
-
assert_eq!(
|
|
25
|
+
assert_eq!(
|
|
26
|
+
out.notifications[0].decision,
|
|
27
|
+
AbnormalDecision::NotifyDefault
|
|
28
|
+
);
|
|
26
29
|
// §84:绝不触碰 provider adapter client。
|
|
27
|
-
assert_eq!(
|
|
30
|
+
assert_eq!(
|
|
31
|
+
reg.adapter_calls.get(),
|
|
32
|
+
0,
|
|
33
|
+
"MUST NOT call adapter_for (provider client)"
|
|
34
|
+
);
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
#[test]
|
|
@@ -40,7 +47,10 @@ fn abnormal_blacklist_hit_yields_notify_blacklist() {
|
|
|
40
47
|
)
|
|
41
48
|
.unwrap();
|
|
42
49
|
assert_eq!(out.notifications.len(), 1);
|
|
43
|
-
assert_eq!(
|
|
50
|
+
assert_eq!(
|
|
51
|
+
out.notifications[0].decision,
|
|
52
|
+
AbnormalDecision::NotifyBlacklist
|
|
53
|
+
);
|
|
44
54
|
assert_eq!(reg.adapter_calls.get(), 0);
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -56,7 +66,11 @@ fn abnormal_whitelist_beats_blacklist_and_skips() {
|
|
|
56
66
|
&AbnormalNotificationState::default(),
|
|
57
67
|
)
|
|
58
68
|
.unwrap();
|
|
59
|
-
assert_eq!(
|
|
69
|
+
assert_eq!(
|
|
70
|
+
out.notifications.len(),
|
|
71
|
+
0,
|
|
72
|
+
"whitelist wins → skip (probe confirmed)"
|
|
73
|
+
);
|
|
60
74
|
assert_eq!(reg.adapter_calls.get(), 0);
|
|
61
75
|
}
|
|
62
76
|
|
|
@@ -91,7 +105,11 @@ fn abnormal_same_turn_id_folds_to_one_notification() {
|
|
|
91
105
|
&AbnormalNotificationState::default(),
|
|
92
106
|
)
|
|
93
107
|
.unwrap();
|
|
94
|
-
assert_eq!(
|
|
108
|
+
assert_eq!(
|
|
109
|
+
out.notifications.len(),
|
|
110
|
+
1,
|
|
111
|
+
"retry-loop in SAME turn folds (probe: 1)"
|
|
112
|
+
);
|
|
95
113
|
// seen 状态回存,key = "sig_a\0t1"。
|
|
96
114
|
assert!(out.notification_state.seen.contains("sig_a\u{0}t1"));
|
|
97
115
|
}
|
|
@@ -112,7 +130,11 @@ fn abnormal_missing_turn_id_distinct_faults_each_notify() {
|
|
|
112
130
|
&AbnormalNotificationState::default(),
|
|
113
131
|
)
|
|
114
132
|
.unwrap();
|
|
115
|
-
assert_eq!(
|
|
133
|
+
assert_eq!(
|
|
134
|
+
out.notifications.len(),
|
|
135
|
+
2,
|
|
136
|
+
"distinct raw → 2 notifies (probe)"
|
|
137
|
+
);
|
|
116
138
|
}
|
|
117
139
|
|
|
118
140
|
#[test]
|
|
@@ -130,7 +152,11 @@ fn abnormal_missing_turn_id_identical_faults_fold() {
|
|
|
130
152
|
&AbnormalNotificationState::default(),
|
|
131
153
|
)
|
|
132
154
|
.unwrap();
|
|
133
|
-
assert_eq!(
|
|
155
|
+
assert_eq!(
|
|
156
|
+
out.notifications.len(),
|
|
157
|
+
1,
|
|
158
|
+
"identical raw folds even w/o turn_id (probe)"
|
|
159
|
+
);
|
|
134
160
|
}
|
|
135
161
|
|
|
136
162
|
#[test]
|
|
@@ -140,14 +166,12 @@ fn abnormal_preseeded_seen_suppresses_duplicate_across_calls() {
|
|
|
140
166
|
let mut state = AbnormalNotificationState::default();
|
|
141
167
|
state.seen.insert("sig_a\u{0}t1".to_string());
|
|
142
168
|
let records = vec![serde_json::json!({"signature": "sig_a", "turn_id": "t1", "raw": "boom"})];
|
|
143
|
-
let out = process_abnormal_records(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
)
|
|
149
|
-
.unwrap();
|
|
150
|
-
assert_eq!(out.notifications.len(), 0, "pre-seeded seen suppresses (probe: 0)");
|
|
169
|
+
let out = process_abnormal_records(&records, ®, Provider::Codex, &state).unwrap();
|
|
170
|
+
assert_eq!(
|
|
171
|
+
out.notifications.len(),
|
|
172
|
+
0,
|
|
173
|
+
"pre-seeded seen suppresses (probe: 0)"
|
|
174
|
+
);
|
|
151
175
|
}
|
|
152
176
|
|
|
153
177
|
#[test]
|
|
@@ -387,7 +411,10 @@ fn whole_team_gone_clean_shutdown_is_silent() {
|
|
|
387
411
|
assert!(!r.notify);
|
|
388
412
|
assert!(!r.escalate_user_on_next_leader_command);
|
|
389
413
|
assert!(!r.marker_written);
|
|
390
|
-
assert!(
|
|
414
|
+
assert!(
|
|
415
|
+
ms.markers.is_empty(),
|
|
416
|
+
"clean shutdown writes NO durable marker"
|
|
417
|
+
);
|
|
391
418
|
}
|
|
392
419
|
|
|
393
420
|
#[test]
|
|
@@ -425,9 +452,15 @@ fn whole_team_gone_unexpected_writes_marker_and_defers_escalation() {
|
|
|
425
452
|
assert!(r.whole_team_gone);
|
|
426
453
|
assert_eq!(r.classification, WholeTeamGoneClass::UnexpectedExit);
|
|
427
454
|
assert!(r.notify);
|
|
428
|
-
assert!(
|
|
455
|
+
assert!(
|
|
456
|
+
r.escalate_user_on_next_leader_command,
|
|
457
|
+
"defer to next leader command"
|
|
458
|
+
);
|
|
429
459
|
assert!(r.marker_written);
|
|
430
|
-
assert!(
|
|
460
|
+
assert!(
|
|
461
|
+
ms.markers.contains_key("whole_team_gone"),
|
|
462
|
+
"durable marker named whole_team_gone"
|
|
463
|
+
);
|
|
431
464
|
}
|
|
432
465
|
|
|
433
466
|
#[test]
|
|
@@ -446,5 +479,8 @@ fn whole_team_gone_unexpected_marker_failure_reflected() {
|
|
|
446
479
|
let r = detect_whole_team_gone(&snap, &mut ms);
|
|
447
480
|
assert_eq!(r.classification, WholeTeamGoneClass::UnexpectedExit);
|
|
448
481
|
assert!(r.notify);
|
|
449
|
-
assert!(
|
|
482
|
+
assert!(
|
|
483
|
+
!r.marker_written,
|
|
484
|
+
"marker set failed → marker_written false"
|
|
485
|
+
);
|
|
450
486
|
}
|