/**
 * @fileoverview Orchestrator Loop — phased plan execution with team agents.
 *
 * State machine that generates plans from user goals, executes them
 * phase-by-phase with verification gates, and adapts on failure.
 *
 * States: idle → planning → approval → executing → verifying → (replanning) → completed/failed
 *
 * Key exports:
 * - `OrchestratorLoop` class — main engine, extends EventEmitter
 * - `OrchestratorLoopEvents` interface — typed event map
 *
 * Lifecycle: `start(goal)` → plan → approve → execute phases → verify → complete
 *
 * @dependencies orchestrator-planner (plan generation), orchestrator-verifier (phase verification),
 *   session-manager (sessions), task-queue (task execution), state-store (persistence),
 *   prompts/orchestrator (prompt templates)
 * @consumedby web/server (orchestrator routes, SSE)
 * @emits stateChanged, planReady, phaseStarted, phaseCompleted, phaseFailed,
 *   taskAssigned, taskCompleted, taskFailed, verificationResult, completed, error
 * @persistence Orchestrator state saved to `~/.codeman/state.json` (orchestrator key)
 *
 * @module orchestrator-loop
 */
import { EventEmitter } from 'node:events';
import { getSessionManager } from './session-manager.js';
import { getTaskQueue } from './task-queue.js';
import { getStore } from './state-store.js';
import { OrchestratorPlanner } from './orchestrator-planner.js';
import { OrchestratorVerifier } from './orchestrator-verifier.js';
import { PHASE_EXECUTION_PROMPT, REPLAN_PROMPT, SINGLE_TASK_PROMPT, TEAM_LEAD_PROMPT } from './prompts/index.js';
import { DEFAULT_ORCHESTRATOR_CONFIG, createInitialOrchestratorStats, getErrorMessage, } from './types.js';
// ═══════════════════════════════════════════════════════════════
// Constants
// ═══════════════════════════════════════════════════════════════
/** Poll interval for checking task completion within a phase (2 seconds) */
const PHASE_POLL_INTERVAL_MS = 2000;
/** Delay between phase completion and verification (1 second) */
const POST_PHASE_DELAY_MS = 1000;
// ═══════════════════════════════════════════════════════════════
// Events
// ═══════════════════════════════════════════════════════════════
// ═══════════════════════════════════════════════════════════════
// OrchestratorLoop
// ═══════════════════════════════════════════════════════════════
export class OrchestratorLoop extends EventEmitter {
    _state = 'idle';
    plan = null;
    currentPhaseIndex = 0;
    config;
    stats;
    startedAt = null;
    completedAt = null;
    workingDir;
    planner;
    verifier;
    sessionManager;
    taskQueue;
    store;
    /** State before pause (to resume to correct state) */
    pausedState = null;
    /** Phase poll timer for checking task completion */
    phasePollTimer = null;
    /** Phase-level timeout timer */
    phaseTimeoutTimer = null;
    /** Post-phase delay timer before verification */
    postPhaseTimer = null;
    /** Session completion listener (bound for cleanup) */
    sessionCompletionListener = null;
    /** Active sessions assigned to current phase */
    phaseSessionIds = new Set();
    constructor(mux, workingDir, config) {
        super();
        this.workingDir = workingDir;
        this.config = { ...DEFAULT_ORCHESTRATOR_CONFIG, ...config };
        this.stats = createInitialOrchestratorStats();
        this.sessionManager = getSessionManager();
        this.taskQueue = getTaskQueue();
        this.store = getStore();
        this.planner = new OrchestratorPlanner(mux, workingDir, this.config);
        this.verifier = new OrchestratorVerifier(this.config);
        // Restore state if crashed while running
        this.restore();
    }
    // ═══════════════════════════════════════════════════════════════
    // Public API — Lifecycle
    // ═══════════════════════════════════════════════════════════════
    /** Start orchestration with a goal. Transitions: idle → planning */
    async start(goal) {
        if (this._state !== 'idle' && this._state !== 'failed' && this._state !== 'completed') {
            throw new Error(`Cannot start from state "${this._state}"`);
        }
        this.reset();
        this.startedAt = Date.now();
        this.setState('planning');
        try {
            const plan = await this.planner.generatePlan(goal, (phase, detail) => {
                this.emit('planProgress', phase, detail);
            });
            if (this.currentState() !== 'planning') {
                // Cancelled during planning
                return;
            }
            this.plan = plan;
            this.persist();
            if (this.config.autoApprove) {
                this.setState('executing');
                await this.executeCurrentPhase();
            }
            else {
                this.setState('approval');
                this.emit('planReady', plan);
            }
        }
        catch (err) {
            this.handleError(err);
        }
    }
    /** Approve the generated plan. Transitions: approval → executing */
    async approve() {
        this.requireState('approval');
        if (!this.plan) {
            throw new Error('No plan to approve');
        }
        this.setState('executing');
        await this.executeCurrentPhase();
    }
    /** Reject plan with feedback. Transitions: approval → planning (regenerate) */
    async reject(feedback) {
        this.requireState('approval');
        if (!this.plan) {
            throw new Error('No plan to reject');
        }
        const goal = this.plan.goal + '\n\nFeedback on previous plan: ' + feedback;
        this.plan = null;
        this.setState('planning');
        try {
            const plan = await this.planner.generatePlan(goal);
            if (this._state !== 'planning')
                return;
            this.plan = plan;
            this.persist();
            this.setState('approval');
            this.emit('planReady', plan);
        }
        catch (err) {
            this.handleError(err);
        }
    }
    /** Pause execution. Saves current state. */
    pause() {
        if (this._state === 'idle' || this._state === 'paused' || this._state === 'completed' || this._state === 'failed') {
            return;
        }
        this.pausedState = this._state;
        this.clearPhasePoll();
        this.cleanupTaskHandlers();
        this.setState('paused');
    }
    /** Resume from pause. */
    async resume() {
        if (this._state !== 'paused' || !this.pausedState) {
            throw new Error('Not paused');
        }
        const resumeTo = this.pausedState;
        this.pausedState = null;
        this.setState(resumeTo);
        // Re-enter the appropriate phase of execution
        if (resumeTo === 'executing') {
            await this.executeCurrentPhase();
        }
        else if (resumeTo === 'verifying') {
            await this.verifyCurrentPhase();
        }
    }
    /** Stop everything and clean up. */
    async stop() {
        this.clearPhasePoll();
        this.cleanupTaskHandlers();
        await this.planner.cancel();
        this.setState('idle');
        this.store.clearOrchestratorState();
    }
    /** Skip a specific phase. */
    async skipPhase(phaseId) {
        if (!this.plan)
            return;
        const phase = this.plan.phases.find((p) => p.id === phaseId);
        if (!phase)
            throw new Error(`Phase "${phaseId}" not found`);
        phase.status = 'skipped';
        phase.completedAt = Date.now();
        this.persist();
        // If this is the current phase, advance
        if (this.plan.phases[this.currentPhaseIndex]?.id === phaseId) {
            await this.advanceToNextPhase();
        }
    }
    /** Retry a failed phase. */
    async retryPhase(phaseId) {
        if (!this.plan)
            return;
        if (this._state !== 'executing' && this._state !== 'failed') {
            throw new Error(`Cannot retry from state "${this._state}"`);
        }
        const phaseIndex = this.plan.phases.findIndex((p) => p.id === phaseId);
        if (phaseIndex === -1)
            throw new Error(`Phase "${phaseId}" not found`);
        const phase = this.plan.phases[phaseIndex];
        phase.status = 'pending';
        phase.attempts = 0;
        for (const task of phase.tasks) {
            task.status = 'pending';
            task.error = null;
            task.assignedSessionId = null;
            task.queueTaskId = null;
        }
        this.currentPhaseIndex = phaseIndex;
        this.setState('executing');
        await this.executeCurrentPhase();
    }
    // ═══════════════════════════════════════════════════════════════
    // Public API — Getters
    // ═══════════════════════════════════════════════════════════════
    get state() {
        return this._state;
    }
    getPlan() {
        return this.plan;
    }
    getCurrentPhase() {
        if (!this.plan)
            return null;
        return this.plan.phases[this.currentPhaseIndex] ?? null;
    }
    getStats() {
        return { ...this.stats };
    }
    getStatus() {
        return {
            state: this._state,
            plan: this.plan,
            currentPhaseIndex: this.currentPhaseIndex,
            startedAt: this.startedAt,
            completedAt: this.completedAt,
            config: this.config,
            stats: this.stats,
        };
    }
    isRunning() {
        return this._state !== 'idle' && this._state !== 'completed' && this._state !== 'failed';
    }
    // ═══════════════════════════════════════════════════════════════
    // Internal — Phase Execution
    // ═══════════════════════════════════════════════════════════════
    async executeCurrentPhase() {
        if (!this.plan || this._state !== 'executing')
            return;
        const phase = this.plan.phases[this.currentPhaseIndex];
        if (!phase) {
            // All phases done
            await this.handleCompletion();
            return;
        }
        // Skip already completed/skipped phases
        if (phase.status === 'passed' || phase.status === 'skipped') {
            await this.advanceToNextPhase();
            return;
        }
        phase.status = 'executing';
        phase.startedAt = Date.now();
        phase.attempts++;
        this.persist();
        this.emit('phaseStarted', phase);
        try {
            await this.assignPhaseTasks(phase);
            this.startPhasePoll(phase);
        }
        catch (err) {
            this.handlePhaseError(phase, getErrorMessage(err));
        }
    }
    async assignPhaseTasks(phase) {
        // For team strategy, send a single comprehensive prompt to a lead session
        if (phase.teamStrategy.type === 'team') {
            await this.assignTeamPhase(phase);
            return;
        }
        // For single/parallel strategy, add individual tasks to TaskQueue
        for (const task of phase.tasks) {
            if (task.status !== 'pending')
                continue;
            const prompt = this.buildTaskPrompt(task, phase);
            const taskOptions = {
                prompt,
                workingDir: this.workingDir,
                priority: 100 - phase.order, // Earlier phases get higher priority
                completionPhrase: task.completionPhrase,
                timeoutMs: Math.min(task.timeoutMs, this.config.phaseTimeoutMs),
            };
            const queueTask = this.taskQueue.addTask(taskOptions);
            task.queueTaskId = queueTask.id;
            task.status = 'running';
        }
        this.persist();
        this.setupTaskHandlers();
        // Manually assign tasks to idle sessions
        await this.assignQueuedTasksToSessions();
    }
    async assignTeamPhase(phase) {
        const teamConfig = phase.teamStrategy.type === 'team' ? phase.teamStrategy.config : null;
        if (!teamConfig)
            return;
        // Find or use an idle session
        const sessions = this.sessionManager.getIdleSessions();
        if (sessions.length === 0) {
            throw new Error('No idle sessions available for team phase execution');
        }
        const session = sessions[0];
        this.phaseSessionIds.add(session.id);
        // Mark all tasks as running under this session
        for (const task of phase.tasks) {
            task.status = 'running';
            task.assignedSessionId = session.id;
        }
        // Build and send the team lead prompt
        const prompt = TEAM_LEAD_PROMPT.replace('{PHASE_NAME}', phase.name)
            .replace('{TASK_LIST}', phase.tasks.map((t, i) => `${i + 1}. ${t.prompt}`).join('\n'))
            .replace('{TEAMMATE_HINTS}', teamConfig.suggestedTeammates.map((h, i) => `${i + 1}. ${h}`).join('\n'))
            .replace('{COMPLETION_PHRASE}', `${phase.id.toUpperCase()}_COMPLETE`);
        // Create a TaskQueue task for the entire phase
        const queueTask = this.taskQueue.addTask({
            prompt,
            workingDir: this.workingDir,
            priority: 100 - phase.order,
            completionPhrase: `${phase.id.toUpperCase()}_COMPLETE`,
            timeoutMs: this.config.phaseTimeoutMs,
        });
        // Link all phase tasks to this single queue task
        for (const task of phase.tasks) {
            task.queueTaskId = queueTask.id;
        }
        this.persist();
        this.setupTaskHandlers();
        // Assign the task to the session
        try {
            queueTask.assign(session.id);
            session.assignTask(queueTask.id);
            this.taskQueue.updateTask(queueTask);
            await session.sendInput(prompt);
        }
        catch (err) {
            queueTask.fail(getErrorMessage(err));
            this.taskQueue.updateTask(queueTask);
            throw err;
        }
    }
    async assignQueuedTasksToSessions() {
        const idleSessions = this.sessionManager.getIdleSessions();
        const maxSessions = this.getCurrentPhase()?.teamStrategy.type === 'parallel'
            ? (this.getCurrentPhase()?.teamStrategy).maxSessions
            : 1;
        const sessionsToUse = idleSessions.slice(0, maxSessions);
        for (const session of sessionsToUse) {
            const task = this.taskQueue.next();
            if (!task)
                break;
            try {
                task.assign(session.id);
                session.assignTask(task.id);
                this.taskQueue.updateTask(task);
                await session.sendInput(task.prompt);
                this.phaseSessionIds.add(session.id);
                // Find the orchestrator task linked to this queue task
                const orchTask = this.findOrchestratorTaskByQueueId(task.id);
                if (orchTask) {
                    orchTask.assignedSessionId = session.id;
                    orchTask.startedAt = Date.now();
                    this.emit('taskAssigned', orchTask, session.id);
                }
            }
            catch (err) {
                task.fail(getErrorMessage(err));
                session.clearTask();
                this.taskQueue.updateTask(task);
            }
        }
    }
    // ═══════════════════════════════════════════════════════════════
    // Internal — Task Completion Tracking
    // ═══════════════════════════════════════════════════════════════
    setupTaskHandlers() {
        this.cleanupTaskHandlers();
        this.sessionCompletionListener = (_sessionId, _phrase) => {
            // Session completion — check if it's related to our phase tasks
            this.checkPhaseCompletion();
        };
        this.sessionManager.on('sessionCompletion', this.sessionCompletionListener);
    }
    cleanupTaskHandlers() {
        if (this.sessionCompletionListener) {
            this.sessionManager.off('sessionCompletion', this.sessionCompletionListener);
            this.sessionCompletionListener = null;
        }
    }
    _finalizeTask(queueTaskId, status, error) {
        const orchTask = this.findOrchestratorTaskByQueueId(queueTaskId);
        if (!orchTask)
            return null;
        orchTask.status = status;
        if (status === 'completed') {
            orchTask.completedAt = Date.now();
            this.stats.totalTasksCompleted++;
        }
        else {
            orchTask.error = error ?? null;
            this.stats.totalTasksFailed++;
        }
        this.persist();
        return orchTask;
    }
    handleTaskCompleted(queueTaskId) {
        const orchTask = this._finalizeTask(queueTaskId, 'completed');
        if (!orchTask)
            return;
        this.emit('taskCompleted', orchTask);
        this.checkPhaseCompletion();
    }
    handleTaskFailed(queueTaskId, error) {
        const orchTask = this._finalizeTask(queueTaskId, 'failed', error);
        if (!orchTask)
            return;
        this.emit('taskFailed', orchTask, error);
        // Check if we should retry the task or fail the phase
        if (orchTask.retries < 2) {
            orchTask.retries++;
            orchTask.status = 'pending';
            orchTask.error = null;
            orchTask.queueTaskId = null;
            // Will be re-queued on next poll
        }
        else {
            this.checkPhaseCompletion();
        }
    }
    startPhasePoll(phase) {
        this.clearPhasePoll();
        this.phasePollTimer = setInterval(() => {
            if (this._state !== 'executing') {
                this.clearPhasePoll();
                return;
            }
            this.pollPhaseStatus(phase);
        }, PHASE_POLL_INTERVAL_MS);
        // Phase-level timeout — fail the phase if it exceeds the configured timeout
        this.phaseTimeoutTimer = setTimeout(() => {
            if (this._state === 'executing' && phase.status === 'executing') {
                console.warn(`[Orchestrator] Phase "${phase.name}" timed out after ${this.config.phaseTimeoutMs}ms`);
                this.handlePhaseError(phase, `Phase timed out after ${Math.round(this.config.phaseTimeoutMs / 60000)} minutes`);
            }
        }, this.config.phaseTimeoutMs);
    }
    _clearTimer(timerKey, clearFn) {
        if (this[timerKey]) {
            clearFn(this[timerKey]);
            this[timerKey] = null;
        }
    }
    clearPhasePoll() {
        this._clearTimer('phasePollTimer', clearInterval);
        this._clearTimer('phaseTimeoutTimer', clearTimeout);
        this._clearTimer('postPhaseTimer', clearTimeout);
    }
    pollPhaseStatus(phase) {
        // Check for queued tasks that need assignment
        const pendingTasks = phase.tasks.filter((t) => t.status === 'pending' && !t.queueTaskId);
        if (pendingTasks.length > 0) {
            // Re-queue pending tasks
            for (const task of pendingTasks) {
                const prompt = this.buildTaskPrompt(task, phase);
                const queueTask = this.taskQueue.addTask({
                    prompt,
                    workingDir: this.workingDir,
                    priority: 100 - phase.order,
                    completionPhrase: task.completionPhrase,
                    timeoutMs: Math.min(task.timeoutMs, this.config.phaseTimeoutMs),
                });
                task.queueTaskId = queueTask.id;
                task.status = 'running';
            }
            this.assignQueuedTasksToSessions().catch(() => { }); // Best effort
        }
        // Check completion status of queue tasks
        for (const task of phase.tasks) {
            if (task.status === 'running' && task.queueTaskId) {
                const queueTask = this.taskQueue.getTask(task.queueTaskId);
                if (queueTask) {
                    if (queueTask.isCompleted()) {
                        this.handleTaskCompleted(task.queueTaskId);
                    }
                    else if (queueTask.isFailed()) {
                        this.handleTaskFailed(task.queueTaskId, queueTask.error || 'Task failed');
                    }
                }
            }
        }
        this.checkPhaseCompletion();
    }
    checkPhaseCompletion() {
        if (this._state !== 'executing')
            return;
        const phase = this.getCurrentPhase();
        if (!phase)
            return;
        const allDone = phase.tasks.every((t) => t.status === 'completed' || t.status === 'failed');
        if (!allDone)
            return;
        const anyFailed = phase.tasks.some((t) => t.status === 'failed');
        this.clearPhasePoll();
        if (anyFailed) {
            // Phase has failed tasks
            this.handlePhaseError(phase, 'One or more tasks failed');
        }
        else {
            // All tasks completed — run verification after brief delay
            this.postPhaseTimer = setTimeout(() => {
                this.postPhaseTimer = null;
                this.verifyCurrentPhase().catch((err) => this.handleError(err));
            }, POST_PHASE_DELAY_MS);
        }
    }
    // ═══════════════════════════════════════════════════════════════
    // Internal — Verification
    // ═══════════════════════════════════════════════════════════════
    async verifyCurrentPhase() {
        if (!this.plan)
            return;
        const phase = this.plan.phases[this.currentPhaseIndex];
        if (!phase)
            return;
        // Skip verification if no criteria defined
        if (phase.verificationCriteria.length === 0 && phase.testCommands.length === 0) {
            phase.status = 'passed';
            phase.completedAt = Date.now();
            phase.durationMs = phase.startedAt ? Date.now() - phase.startedAt : null;
            this.stats.phasesCompleted++;
            this.persist();
            this.emit('phaseCompleted', phase);
            await this.advanceToNextPhase();
            return;
        }
        this.setState('verifying');
        // Get a session for verification — wait briefly for sessions to become idle
        let sessions = this.sessionManager.getIdleSessions();
        if (sessions.length === 0) {
            // Wait up to 10s for a session to become idle
            await new Promise((resolve) => setTimeout(resolve, 10_000));
            sessions = this.sessionManager.getIdleSessions();
        }
        if (sessions.length === 0) {
            // Still no sessions — log warning and skip verification (don't silently pass)
            console.warn('[Orchestrator] No idle sessions for verification — skipping (marking passed with warning)');
            phase.status = 'passed';
            phase.completedAt = Date.now();
            phase.durationMs = phase.startedAt ? Date.now() - phase.startedAt : null;
            this.stats.phasesCompleted++;
            this.persist();
            this.emit('phaseCompleted', phase);
            this.setState('executing');
            await this.advanceToNextPhase();
            return;
        }
        try {
            const result = await this.verifier.verifyPhase(phase, sessions[0]);
            this.emit('verificationResult', phase, result);
            if (result.passed) {
                phase.status = 'passed';
                phase.completedAt = Date.now();
                phase.durationMs = phase.startedAt ? Date.now() - phase.startedAt : null;
                this.stats.phasesCompleted++;
                this.persist();
                this.emit('phaseCompleted', phase);
                this.setState('executing');
                await this.advanceToNextPhase();
            }
            else {
                // Verification failed — attempt replan
                await this.handleVerificationFailure(phase, result);
            }
        }
        catch (err) {
            // Verification error — treat as pass (don't block on verification bugs)
            console.warn('[Orchestrator] Verification error, treating as pass:', err);
            phase.status = 'passed';
            phase.completedAt = Date.now();
            phase.durationMs = phase.startedAt ? Date.now() - phase.startedAt : null;
            this.stats.phasesCompleted++;
            this.persist();
            this.emit('phaseCompleted', phase);
            this.setState('executing');
            await this.advanceToNextPhase();
        }
    }
    async handleVerificationFailure(phase, result) {
        if (phase.attempts >= phase.maxAttempts) {
            // Max retries exceeded
            phase.status = 'failed';
            phase.completedAt = Date.now();
            phase.durationMs = phase.startedAt ? Date.now() - phase.startedAt : null;
            this.stats.phasesFailed++;
            this.persist();
            this.emit('phaseFailed', phase, `Verification failed after ${phase.attempts} attempts: ${result.summary}`);
            this.setState('failed');
            return;
        }
        // Replan and retry
        this.stats.replanCount++;
        this.setState('replanning');
        try {
            await this.replanPhase(phase, result);
            // Reset task states for retry
            for (const task of phase.tasks) {
                task.status = 'pending';
                task.error = null;
                task.assignedSessionId = null;
                task.queueTaskId = null;
                task.completedAt = null;
                task.startedAt = null;
            }
            phase.status = 'pending';
            phase.startedAt = null;
            this.persist();
            this.setState('executing');
            await this.executeCurrentPhase();
        }
        catch (err) {
            this.handleError(err);
        }
    }
    async replanPhase(phase, result) {
        const completionPhrase = phase.tasks[0]?.completionPhrase || `${phase.id.toUpperCase()}_FIXED`;
        const prompt = REPLAN_PROMPT.replace('{PHASE_NAME}', phase.name)
            .replace('{ATTEMPT_NUMBER}', String(phase.attempts))
            .replace('{MAX_ATTEMPTS}', String(phase.maxAttempts))
            .replace('{FAILURE_SUMMARY}', result.summary)
            .replace('{SUGGESTIONS}', result.suggestions.join('\n'))
            .replace('{ORIGINAL_TASKS}', phase.tasks.map((t, i) => `${i + 1}. ${t.prompt}`).join('\n'))
            .replace('{COMPLETION_PHRASE}', completionPhrase);
        // Create a tracked queue task for the replan (so completion is detected)
        const queueTask = this.taskQueue.addTask({
            prompt,
            workingDir: this.workingDir,
            priority: 100,
            completionPhrase,
            timeoutMs: this.config.phaseTimeoutMs,
        });
        // Link to first phase task for tracking
        if (phase.tasks[0]) {
            phase.tasks[0].queueTaskId = queueTask.id;
            phase.tasks[0].status = 'running';
        }
        this.persist();
        // Set up handlers so task completion is tracked
        this.setupTaskHandlers();
        // Assign to a session
        const sessions = this.sessionManager.getIdleSessions();
        if (sessions.length === 0) {
            console.warn('[Orchestrator] No idle sessions for replan — task queued, will pick up on next poll');
            // Start polling so the task gets assigned when a session becomes idle
            this.startPhasePoll(phase);
            return;
        }
        try {
            queueTask.assign(sessions[0].id);
            sessions[0].assignTask(queueTask.id);
            this.taskQueue.updateTask(queueTask);
            await sessions[0].sendInput(prompt);
        }
        catch (err) {
            queueTask.fail(getErrorMessage(err));
            this.taskQueue.updateTask(queueTask);
        }
    }
    // ═══════════════════════════════════════════════════════════════
    // Internal — State Machine
    // ═══════════════════════════════════════════════════════════════
    /** Read current state (bypasses TypeScript narrowing from guards) */
    currentState() {
        return this._state;
    }
    /** Assert state matches expected or throw */
    requireState(...expected) {
        if (!expected.includes(this._state)) {
            throw new Error(`Expected state "${expected.join('|')}", got "${this._state}"`);
        }
    }
    setState(newState) {
        const prev = this._state;
        if (prev === newState)
            return;
        this._state = newState;
        this.persist();
        this.emit('stateChanged', newState, prev);
    }
    async advanceToNextPhase() {
        this.currentPhaseIndex++;
        this.phaseSessionIds.clear();
        this.persist();
        if (!this.plan || this.currentPhaseIndex >= this.plan.phases.length) {
            await this.handleCompletion();
        }
        else {
            // Compact between phases if configured
            if (this.config.compactBetweenPhases) {
                const sessions = this.sessionManager.getIdleSessions();
                for (const session of sessions) {
                    try {
                        await session.writeViaMux('/compact');
                    }
                    catch {
                        // Best effort
                    }
                }
                // Brief delay for compact to take effect
                await new Promise((resolve) => setTimeout(resolve, 2000));
            }
            await this.executeCurrentPhase();
        }
    }
    async handleCompletion() {
        this.completedAt = Date.now();
        this.stats.totalDurationMs = this.startedAt ? this.completedAt - this.startedAt : 0;
        this.clearPhasePoll();
        this.cleanupTaskHandlers();
        this.setState('completed');
        this.emit('completed', this.stats);
    }
    handlePhaseError(phase, error) {
        if (phase.attempts >= phase.maxAttempts) {
            phase.status = 'failed';
            phase.completedAt = Date.now();
            phase.durationMs = phase.startedAt ? Date.now() - phase.startedAt : null;
            this.stats.phasesFailed++;
            this.persist();
            this.emit('phaseFailed', phase, error);
            this.setState('failed');
        }
        else {
            // Retry the phase
            for (const task of phase.tasks) {
                if (task.status === 'failed') {
                    task.status = 'pending';
                    task.error = null;
                    task.queueTaskId = null;
                    task.assignedSessionId = null;
                }
            }
            phase.status = 'pending';
            this.persist();
            this.executeCurrentPhase().catch((err) => this.handleError(err));
        }
    }
    handleError(err) {
        const error = err instanceof Error ? err : new Error(getErrorMessage(err));
        console.error('[Orchestrator] Error:', error.message);
        this.setState('failed');
        this.emit('error', error);
    }
    // ═══════════════════════════════════════════════════════════════
    // Internal — Persistence
    // ═══════════════════════════════════════════════════════════════
    persist() {
        this.store.setOrchestratorState(this.getStatus());
    }
    restore() {
        const saved = this.store.getOrchestratorState();
        if (!saved)
            return;
        // If we crashed while running, reset to failed
        if (saved.state === 'executing' || saved.state === 'verifying' || saved.state === 'replanning') {
            this._state = 'failed';
            this.plan = saved.plan;
            this.currentPhaseIndex = saved.currentPhaseIndex;
            this.startedAt = saved.startedAt;
            this.config = saved.config;
            this.stats = saved.stats;
            this.store.setOrchestratorState({ ...saved, state: 'failed' });
        }
        else if (saved.state === 'planning' || saved.state === 'approval') {
            // Planning/approval — reset to idle (plan is lost)
            this.store.clearOrchestratorState();
        }
        else if (saved.state === 'completed' || saved.state === 'failed') {
            // Preserve completed/failed state for UI display
            this._state = saved.state;
            this.plan = saved.plan;
            this.currentPhaseIndex = saved.currentPhaseIndex;
            this.startedAt = saved.startedAt;
            this.completedAt = saved.completedAt;
            this.config = saved.config;
            this.stats = saved.stats;
        }
    }
    reset() {
        this._state = 'idle';
        this.plan = null;
        this.currentPhaseIndex = 0;
        this.startedAt = null;
        this.completedAt = null;
        this.stats = createInitialOrchestratorStats();
        this.pausedState = null;
        this.phaseSessionIds.clear();
        this.clearPhasePoll();
        this.cleanupTaskHandlers();
    }
    // ═══════════════════════════════════════════════════════════════
    // Internal — Helpers
    // ═══════════════════════════════════════════════════════════════
    buildTaskPrompt(task, phase) {
        if (phase.tasks.length === 1) {
            // Single task — use simpler prompt
            const completedPhases = this.getCompletedPhasesSummary();
            return SINGLE_TASK_PROMPT.replace('{TASK}', task.prompt)
                .replace('{GOAL}', this.plan?.goal || '')
                .replace('{CONTEXT}', completedPhases ? `Previous phases completed: ${completedPhases}` : '')
                .replace('{COMPLETION_PHRASE}', task.completionPhrase);
        }
        // Multi-task phase — use full prompt
        return PHASE_EXECUTION_PROMPT.replace('{PHASE_NAME}', phase.name)
            .replace('{GOAL}', this.plan?.goal || '')
            .replace('{COMPLETED_PHASES}', this.getCompletedPhasesSummary() || 'None yet')
            .replace('{TASK_LIST}', phase.tasks.map((t, i) => `${i + 1}. ${t.prompt}`).join('\n'))
            .replace('{VERIFICATION_CRITERIA}', phase.verificationCriteria.join('\n') || 'No specific criteria')
            .replace('{COMPLETION_PHRASE}', task.completionPhrase);
    }
    getCompletedPhasesSummary() {
        if (!this.plan)
            return '';
        return this.plan.phases
            .filter((p) => p.status === 'passed' || p.status === 'skipped')
            .map((p) => `${p.name}: ${p.status}`)
            .join(', ');
    }
    findOrchestratorTaskByQueueId(queueTaskId) {
        if (!this.plan)
            return null;
        for (const phase of this.plan.phases) {
            for (const task of phase.tasks) {
                if (task.queueTaskId === queueTaskId)
                    return task;
            }
        }
        return null;
    }
    /** Clean up resources when the loop is being destroyed. */
    destroy() {
        this.clearPhasePoll();
        this.cleanupTaskHandlers();
    }
}
//# sourceMappingURL=orchestrator-loop.js.map