fix: patch 3 production bugs found during deep audit

1. Post-phase verify timer leak — setTimeout for verifyCurrentPhase was
   never stored, so pause() couldn't cancel it. Timer now tracked in
   postPhaseTimer field and cleared in clearPhasePoll().

2. Event forwarding flag survives loop replacement — boolean
   eventForwardingAttached stayed true when a new loop was created,
   so the new loop never got SSE forwarding. Now tracks the loop
   instance reference instead of a boolean.

3. Replan stuck when no sessions — replanPhase() returned without
   setting up task handlers or polling when no idle sessions were
   available. Now starts polling so the queued task gets picked up
   when a session becomes idle.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-03-21 19:23:37 +01:00
co-authored by Claude Opus 4.6
parent 9449a8f157
commit afea91b92b
3 changed files with 45 additions and 6 deletions
+3 -3
View File
@@ -38,10 +38,10 @@ export function registerOrchestratorRoutes(app: FastifyInstance, ctx: Orchestrat
return loop;
}
let eventForwardingAttached = false;
let forwardingLoop: import('../../orchestrator-loop.js').OrchestratorLoop | null = null;
function setupEventForwarding(loop: import('../../orchestrator-loop.js').OrchestratorLoop) {
if (eventForwardingAttached) return;
eventForwardingAttached = true;
if (forwardingLoop === loop) return; // Already attached to this loop instance
forwardingLoop = loop;
loop.on('stateChanged', (state, prevState) => {
ctx.broadcast(SseEvent.OrchestratorStateChanged, { state, prevState });
});