/**
 * @module        runtime-coordinator
 * @description   运行时协调器，管理 Worker 生命周期调度与状态恢复
 *
 * @author        eddy
 * @organization  wykj
 * @ownership     wykj/eddy
 *
 * @created       2026-03-27
 * @updated       2026-04-03
 *
 * @role          manager
 * @layer         manager
 * @boundedContext worker-lifecycle
 */
import { RuntimeStore } from '../core/runtimeStore.js';
import { isPersistedSessionAlive, } from '../core/sessionLiveness.js';
import { createIdleWorkerSlot, } from '../core/state.js';
const TERMINAL_RUN_STATUSES = new Set(['completed', 'failed', 'cancelled', 'lost']);
/**
 * RuntimeCoordinator — simplified for single-worker model.
 *
 * No worktree scanning, no branch evidence inspection. The coordinator
 * normalizes ACP sessions and rebuilds worker slot state from persisted
 * leases and PM card states.
 */
export class RuntimeCoordinator {
    ctx;
    _taskBackend;
    constructor(ctx, _taskBackend) {
        this.ctx = ctx;
        this._taskBackend = _taskBackend;
    }
    async buildRuntimeProjection() {
        return this.computeRuntimeProjection(false, 'runtime-coordinator');
    }
    async rebuildRuntimeProjection(updatedBy = 'runtime-coordinator') {
        return this.computeRuntimeProjection(true, updatedBy);
    }
    /**
     * Single-worker model: no worktree/branch scanning.
     * Just normalize sessions and ensure worker slots are consistent.
     */
    async computeRuntimeProjection(persist, updatedBy) {
        const store = new RuntimeStore(this.ctx);
        const state = store.readState();
        const normalizedSessions = this.normalizeSessions(state);
        // Ensure correct number of worker slots
        const nextWorkers = {};
        for (let i = 1; i <= this.ctx.maxWorkers; i++) {
            const slotName = `worker-${i}`;
            nextWorkers[slotName] = state.workers[slotName] ?? createIdleWorkerSlot();
        }
        const changed = JSON.stringify(state.workers) !== JSON.stringify(nextWorkers) ||
            JSON.stringify(state.sessions) !== JSON.stringify(normalizedSessions);
        if (persist && changed) {
            store.updateState(updatedBy, (runtimeState) => {
                runtimeState.workers = nextWorkers;
                runtimeState.sessions = normalizedSessions;
            });
        }
        const nextState = structuredClone(state);
        nextState.workers = nextWorkers;
        nextState.sessions = normalizedSessions;
        return { state: nextState, updated: changed };
    }
    normalizeSessions(state) {
        const nextSessions = { ...state.sessions };
        for (const [slotName, session] of Object.entries(state.sessions)) {
            const slot = state.workers[slotName];
            if (!slot)
                continue;
            if (isPersistedSessionAlive(slot, session))
                continue;
            const nextRun = session.currentRun && !TERMINAL_RUN_STATUSES.has(session.currentRun.status)
                ? {
                    ...session.currentRun,
                    status: 'lost',
                    updatedAt: new Date().toISOString(),
                    completedAt: session.currentRun.completedAt || new Date().toISOString(),
                }
                : session.currentRun;
            nextSessions[slotName] = {
                ...session,
                status: 'offline',
                sessionState: 'offline',
                currentRun: nextRun,
                pendingInput: null,
                updatedAt: new Date().toISOString(),
            };
        }
        return nextSessions;
    }
}
//# sourceMappingURL=runtime-coordinator.js.map