@@ -3383,6 +3383,90 @@ describe("aiOrchestratorService", () => {
33833383 }
33843384 } ) ;
33853385
3386+ it ( "keeps coordinator availability interventions open when no live coordinator exists" , async ( ) => {
3387+ const fixture = await createFixture ( ) ;
3388+ try {
3389+ const mission = fixture . missionService . create ( {
3390+ prompt : "Do not clear coordinator blockers without a live coordinator." ,
3391+ laneId : fixture . laneId ,
3392+ } ) ;
3393+ const started = fixture . orchestratorService . startRun ( {
3394+ missionId : mission . id ,
3395+ steps : [ ] ,
3396+ } ) ;
3397+ const runId = started . run . id ;
3398+ fixture . missionService . update ( {
3399+ missionId : mission . id ,
3400+ status : "in_progress" ,
3401+ } ) ;
3402+ fixture . orchestratorService . addSteps ( {
3403+ runId,
3404+ steps : [
3405+ {
3406+ stepKey : "manual-check" ,
3407+ title : "Manual check" ,
3408+ stepIndex : 0 ,
3409+ dependencyStepKeys : [ ] ,
3410+ executorKind : "manual" ,
3411+ } ,
3412+ ] ,
3413+ } ) ;
3414+ fixture . orchestratorService . tick ( { runId } ) ;
3415+ const graph = fixture . orchestratorService . getRunGraph ( { runId } ) ;
3416+ const readyStep = graph . steps . find ( ( step ) => step . stepKey === "manual-check" && step . status === "ready" ) ;
3417+ if ( ! readyStep ) throw new Error ( "Expected manual-check step to be ready" ) ;
3418+ const attempt = await fixture . orchestratorService . startAttempt ( {
3419+ runId,
3420+ stepId : readyStep . id ,
3421+ ownerId : "test-owner" ,
3422+ executorKind : "manual" ,
3423+ } ) ;
3424+ await fixture . orchestratorService . completeAttempt ( {
3425+ attemptId : attempt . id ,
3426+ status : "succeeded" ,
3427+ } ) ;
3428+ const intervention = fixture . missionService . addIntervention ( {
3429+ missionId : mission . id ,
3430+ interventionType : "failed_step" ,
3431+ title : "Coordinator unavailable" ,
3432+ body : "Coordinator agent is not available for this run." ,
3433+ requestedAction : "Resume after coordinator runtime is healthy." ,
3434+ metadata : {
3435+ runId,
3436+ stepId : readyStep . id ,
3437+ attemptId : attempt . id ,
3438+ reasonCode : "coordinator_unavailable" ,
3439+ } ,
3440+ } ) ;
3441+
3442+ fixture . aiOrchestratorService . onOrchestratorRuntimeEvent ( {
3443+ type : "orchestrator-step-updated" ,
3444+ runId,
3445+ stepId : readyStep . id ,
3446+ attemptId : attempt . id ,
3447+ at : new Date ( ) . toISOString ( ) ,
3448+ reason : "attempt_completed" ,
3449+ } as any ) ;
3450+
3451+ const refreshedMission = fixture . missionService . get ( mission . id ) ;
3452+ const refreshedIntervention = refreshedMission ?. interventions . find ( ( entry ) => entry . id === intervention . id ) ;
3453+ expect ( refreshedIntervention ?. status ) . toBe ( "open" ) ;
3454+
3455+ const resolvedEvents = fixture . orchestratorService . listRuntimeEvents ( {
3456+ runId,
3457+ eventTypes : [ "intervention_resolved" ] ,
3458+ limit : 10 ,
3459+ } ) ;
3460+ expect (
3461+ resolvedEvents . some ( ( entry ) =>
3462+ String ( ( entry . payload as Record < string , unknown > | null ) ?. interventionId ?? "" ) === intervention . id
3463+ )
3464+ ) . toBe ( false ) ;
3465+ } finally {
3466+ fixture . dispose ( ) ;
3467+ }
3468+ } ) ;
3469+
33863470 it ( "prefers mission launch failures over later coordinator-unavailable interventions in run view" , async ( ) => {
33873471 const fixture = await createFixture ( ) ;
33883472 try {
0 commit comments