import { randomUUID } from "node:crypto";
import { cleanupSessionResources } from "@earendil-works/pi-ai";
import { formatToolUiEventForProgress } from "../utils/subagent_utils.js";
import { runSubagent } from "./subagent_engine.js";
const MAX_ACTIVE_SUBAGENTS = 8;
const MAX_PROGRESS_EVENTS = 200;
async function raceWithAbort(promise, signal) {
    if (!signal) {
        return await promise;
    }
    if (signal.aborted) {
        throw new Error("aborted");
    }
    return await new Promise((resolve, reject) => {
        const onAbort = () => {
            signal.removeEventListener("abort", onAbort);
            reject(new Error("aborted"));
        };
        signal.addEventListener("abort", onAbort);
        promise.then((value) => {
            signal.removeEventListener("abort", onAbort);
            resolve(value);
        }, (error) => {
            signal.removeEventListener("abort", onAbort);
            reject(error);
        });
    });
}
export class SubagentControlPlane {
    records = new Map();
    onEvent;
    constructor(options) {
        this.onEvent = options.onEvent;
    }
    reset() {
        for (const record of this.records.values()) {
            cleanupSessionResources(record.id);
            if (record.status === "running") {
                record.abortRequested = true;
                record.controller.abort();
            }
        }
        this.records.clear();
    }
    getActiveCount() {
        let count = 0;
        for (const record of this.records.values()) {
            if (record.status === "running") {
                count += 1;
            }
        }
        return count;
    }
    spawn(options) {
        if (this.getActiveCount() >= MAX_ACTIVE_SUBAGENTS) {
            return {
                ok: false,
                reason: `Subagent limit reached (max ${MAX_ACTIVE_SUBAGENTS} active). ` +
                    "Wait for existing agents to finish.",
            };
        }
        const { runtimeConfig, prompt, title, modelLabel, originHistoryEntryId, config, modelResolver, authPath, backend, personaId, } = options;
        const workingDirectory = runtimeConfig.workingDirectory.trim();
        if (!workingDirectory) {
            return {
                ok: false,
                reason: "Subagent workingDirectory must not be blank.",
            };
        }
        const normalizedRuntimeConfig = {
            ...runtimeConfig,
            workingDirectory,
        };
        const id = randomUUID();
        const record = {
            id,
            name: normalizedRuntimeConfig.name,
            title,
            modelLabel,
            originHistoryEntryId,
            runtimeConfig: normalizedRuntimeConfig,
            modelResolver,
            messages: [],
            status: "running",
            costTotal: 0,
            turns: 0,
            toolCalls: 0,
            usage: {
                input: 0,
                output: 0,
                cacheRead: 0,
                cacheWrite: 0,
                contextWindowUsageTokens: 0,
                contextWindow: normalizedRuntimeConfig.model?.contextWindow ?? 0,
            },
            costOffset: 0,
            turnsOffset: 0,
            toolCallsOffset: 0,
            inputOffset: 0,
            outputOffset: 0,
            cacheReadOffset: 0,
            cacheWriteOffset: 0,
            startedAt: Date.now(),
            abortRequested: false,
            progress: [],
            outputs: [],
            controller: new AbortController(),
            completion: Promise.resolve(undefined),
        };
        this.records.set(id, record);
        this.startRun({ record, prompt, config, authPath, backend, personaId });
        return { ok: true, id };
    }
    sendInput(options) {
        const { id, prompt, config, modelResolver, authPath, backend, personaId } = options;
        const record = this.records.get(id);
        if (!record) {
            return { ok: false, reason: `Unknown subagent ID: ${id}` };
        }
        if (record.status === "running") {
            return {
                ok: false,
                reason: `Subagent ${id} is already running. Wait for it to finish before sending input.`,
            };
        }
        if (this.getActiveCount() >= MAX_ACTIVE_SUBAGENTS) {
            return {
                ok: false,
                reason: `Subagent limit reached (max ${MAX_ACTIVE_SUBAGENTS} active). ` +
                    "Wait for existing agents to finish.",
            };
        }
        record.modelResolver = modelResolver;
        this.startRun({ record, prompt, config, authPath, backend, personaId });
        return { ok: true, id: record.id, name: record.name, title: record.title };
    }
    recordEmitOutput(id, text) {
        const record = this.records.get(id);
        if (!record)
            return;
        const trimmed = text.trim();
        if (!trimmed)
            return;
        const payload = text.trimEnd();
        record.outputs.push(payload);
        this.emit({ type: "subagent_emit_output", id, text: payload });
    }
    async waitFor(ids, signal) {
        const missing = ids.filter((id) => !this.records.has(id));
        if (missing.length > 0) {
            throw new Error(`Unknown subagent ID(s): ${missing.join(", ")}`);
        }
        const completions = ids.map((id) => this.waitForRecord(id));
        const all = Promise.all(completions);
        const resolved = await raceWithAbort(all, signal);
        return resolved.map((record) => this.toResult(record));
    }
    async terminate(id, signal) {
        const record = this.records.get(id);
        if (!record)
            return undefined;
        const aborting = record.status === "running";
        if (aborting) {
            if (!record.abortRequested) {
                record.abortRequested = true;
                this.emit({ type: "subagent_abort_requested", id });
            }
            record.controller.abort();
        }
        const completion = this.waitForRecord(id);
        const resolved = await raceWithAbort(completion, signal);
        if (aborting) {
            cleanupSessionResources(record.id);
        }
        return this.toResult(resolved);
    }
    getSnapshot(id) {
        const record = this.records.get(id);
        return record ? this.toSnapshot(record) : undefined;
    }
    listSnapshots() {
        return [...this.records.values()].map((record) => this.toSnapshot(record));
    }
    getOriginHistoryEntryId(id) {
        const record = this.records.get(id);
        if (!record) {
            throw new Error(`Unknown subagent ID: ${id}`);
        }
        return record.originHistoryEntryId;
    }
    startRun(options) {
        const { record, prompt, config, authPath, backend, personaId } = options;
        record.status = "running";
        record.startedAt = Date.now();
        record.finishedAt = undefined;
        record.abortRequested = false;
        record.progress = [];
        record.outputs = [];
        record.error = undefined;
        record.finalText = undefined;
        record.controller = new AbortController();
        record.costOffset = record.costTotal;
        record.turnsOffset = record.turns;
        record.toolCallsOffset = record.toolCalls;
        record.inputOffset = record.usage.input;
        record.outputOffset = record.usage.output;
        record.cacheReadOffset = record.usage.cacheRead;
        record.cacheWriteOffset = record.usage.cacheWrite;
        const subagentContext = {
            id: record.id,
            name: record.name,
            title: record.title,
            originHistoryEntryId: record.originHistoryEntryId,
            controlPlane: this,
        };
        this.emit({ type: "subagent_spawned", state: this.toSnapshot(record) });
        record.completion = runSubagent({
            runtimeConfig: record.runtimeConfig,
            prompt,
            config,
            authPath,
            backend,
            signal: record.controller.signal,
            sessionId: record.id,
            personaId,
            originHistoryEntryId: record.originHistoryEntryId,
            subagentContext,
            modelResolver: record.modelResolver,
            messages: record.messages,
            onProgress: (event) => this.recordProgress(record.id, event),
            onToolUiEvent: (event) => this.recordToolUiEvent(record.id, event),
        })
            .then((result) => {
            record.status = "success";
            record.costTotal = record.costOffset + result.costTotal;
            record.turns = record.turnsOffset + result.turns;
            record.toolCalls = record.toolCallsOffset + result.toolCalls;
            record.finalText = result.finalText;
            record.finishedAt = Date.now();
            if (this.records.get(record.id) === record) {
                this.emit({ type: "subagent_finished", state: this.toSnapshot(record) });
            }
            return record;
        })
            .catch((error) => {
            const message = error instanceof Error ? error.message : String(error);
            const wasAborted = record.controller.signal.aborted || record.abortRequested;
            record.status = wasAborted ? "aborted" : "error";
            record.error = wasAborted ? undefined : message;
            record.finishedAt = Date.now();
            if (this.records.get(record.id) === record) {
                this.emit({ type: "subagent_finished", state: this.toSnapshot(record) });
            }
            return record;
        });
    }
    async waitForRecord(id) {
        const record = this.records.get(id);
        if (!record) {
            throw new Error(`Unknown subagent ID: ${id}`);
        }
        if (record.status !== "running") {
            return record;
        }
        return await record.completion;
    }
    recordProgress(id, event) {
        const record = this.records.get(id);
        if (!record)
            return;
        record.costTotal = record.costOffset + event.costTotal;
        record.turns = record.turnsOffset + event.turns;
        record.toolCalls = record.toolCallsOffset + event.toolCalls;
        record.usage = {
            input: record.inputOffset + event.usage.input,
            output: record.outputOffset + event.usage.output,
            cacheRead: record.cacheReadOffset + event.usage.cacheRead,
            cacheWrite: record.cacheWriteOffset + event.usage.cacheWrite,
            contextWindowUsageTokens: event.usage.contextWindowUsageTokens,
            contextWindow: event.usage.contextWindow,
        };
        const text = event.text.trim();
        if (text) {
            record.progress.push({ kind: "progress", text });
            if (record.progress.length > MAX_PROGRESS_EVENTS) {
                record.progress.shift();
            }
        }
        this.emit({
            type: "subagent_progress",
            id,
            text,
            costTotal: record.costTotal,
            turns: record.turns,
            toolCalls: record.toolCalls,
            usage: record.usage,
        });
    }
    recordToolUiEvent(id, event) {
        const record = this.records.get(id);
        if (!record)
            return;
        record.costTotal = record.costOffset + event.costTotal;
        record.turns = record.turnsOffset + event.turns;
        record.toolCalls = record.toolCallsOffset + event.toolCalls;
        record.usage = {
            input: record.inputOffset + event.usage.input,
            output: record.outputOffset + event.usage.output,
            cacheRead: record.cacheReadOffset + event.usage.cacheRead,
            cacheWrite: record.cacheWriteOffset + event.usage.cacheWrite,
            contextWindowUsageTokens: event.usage.contextWindowUsageTokens,
            contextWindow: event.usage.contextWindow,
        };
        const text = formatToolUiEventForProgress(event.uiEvent) ?? "";
        if (text) {
            record.progress.push({ kind: "progress", text });
            if (record.progress.length > MAX_PROGRESS_EVENTS) {
                record.progress.shift();
            }
        }
        this.emit({
            type: "subagent_progress",
            id,
            text,
            costTotal: record.costTotal,
            turns: record.turns,
            toolCalls: record.toolCalls,
            usage: record.usage,
        });
    }
    toSnapshot(record) {
        return {
            id: record.id,
            name: record.name,
            title: record.title,
            modelLabel: record.modelLabel,
            status: record.status,
            costTotal: record.costTotal,
            turns: record.turns,
            toolCalls: record.toolCalls,
            usage: record.usage,
            startedAt: record.startedAt,
            finishedAt: record.finishedAt,
            abortRequested: record.abortRequested,
            error: record.error,
            finalText: record.finalText,
        };
    }
    toResult(record) {
        return {
            id: record.id,
            name: record.name,
            title: record.title,
            status: record.status,
            costTotal: record.costTotal,
            turns: record.turns,
            toolCalls: record.toolCalls,
            outputs: [...record.outputs],
            finalText: record.finalText,
            error: record.error,
            startedAt: record.startedAt,
            finishedAt: record.finishedAt,
        };
    }
    emit(event) {
        this.onEvent(event);
    }
}
//# sourceMappingURL=control_plane.js.map