missions: retry failed phases + auto-purge on re-launch
Every re-attempted phase now starts with a clean slate:
- phase_runner::launch_phase DELETEs prior status IN ('failed',
'cancelled') topology_runs for the phase before enqueuing the
new ones. Completed runs are kept for audit; only the failure
noise from earlier attempts goes.
- POST /api/missions/{id}/phases/{phase_id}/retry — resets a
failed/cancelled phase to 'pending' (auth-scoped to the calling
workspace + guarded on mission.status='running'). phase_runner
picks it up on the next 10s tick.
- MissionCanvas phase card grows a coral 'Retry' button, visible
only when phase.status='failed' and mission.status='running'.
Click → resets + refreshes; the prior failed run rows disappear
from the card as soon as phase_runner enqueues the new attempt.
Design: auto-purge in phase_runner rather than a separate 'clear
failed runs' endpoint. Users don't have to manually clean up before
retrying; the runner does it as part of the natural work of firing
a fresh attempt.
Verified: cargo check + tsc + eslint --quiet all green.
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
getMission,
|
||||
listMissionRuns,
|
||||
refineMission,
|
||||
retryMissionPhase,
|
||||
setMissionDescription,
|
||||
setMissionStatus,
|
||||
triggerBenchmark,
|
||||
@@ -759,6 +760,37 @@ export function MissionCanvas({
|
||||
})()}
|
||||
{mission.status === "running" || mission.status === "completed" ? (
|
||||
<div style={{ display: "flex", gap: 6, marginTop: 6 }}>
|
||||
{p.status === "failed" && mission.status === "running" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
if (phaseBusy === `retry:${p.id}`) return;
|
||||
setPhaseBusy(`retry:${p.id}`);
|
||||
setError(null);
|
||||
try {
|
||||
await retryMissionPhase(mission.id, p.id);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError(
|
||||
e instanceof Error ? e.message : "retry failed",
|
||||
);
|
||||
} finally {
|
||||
setPhaseBusy(null);
|
||||
}
|
||||
}}
|
||||
disabled={phaseBusy === `retry:${p.id}`}
|
||||
title="Reset this phase to pending; prior failed runs are purged. phase_runner picks it up in ≤10s."
|
||||
style={{
|
||||
...secondaryBtn,
|
||||
borderColor: "rgba(255,138,122,.45)",
|
||||
color: "#ff8a7a",
|
||||
background: "rgba(255,138,122,.08)",
|
||||
opacity: phaseBusy === `retry:${p.id}` ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
{phaseBusy === `retry:${p.id}` ? "Retrying…" : "Retry"}
|
||||
</button>
|
||||
)}
|
||||
{p.kind === "security_scan" && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
Reference in New Issue
Block a user