/**
 * Subprocess bootstrap script — entry point for forked Agency subprocesses.
 *
 * Receives a startup message from the parent via IPC, dynamically imports the
 * compiled Agency script, runs the requested node via runNode(), and sends the
 * result back over IPC.
 *
 * The AGENCY_IPC=1 env var is set by the parent before forking, which tells
 * interruptWithHandlers to route interrupts to the parent instead of returning
 * them as Interrupt[] objects.
 */
import { pathToFileURL } from "url";
import { ipcLog } from "./ipc.js";
let ipcPayloadLimit = Infinity;
/**
 * Send an IPC message and wait for the channel to flush before resolving.
 * Callers that follow with `process.exit(...)` MUST `await` this — otherwise
 * the immediate exit can race with the async send and the parent will see
 * only an abnormal close, missing the structured message entirely.
 */
function sendOrDie(msg) {
    if (typeof process.send !== "function") {
        console.error("[bootstrap] No IPC channel — was this script run directly? It should only be forked by _run().");
        process.exit(1);
    }
    ipcLog("send", msg);
    return new Promise((resolve) => {
        // Errors here mean the IPC channel is already gone; resolve anyway so
        // the caller can proceed to exit instead of hanging.
        process.send(msg, undefined, undefined, () => resolve());
    });
}
/**
 * Send a result message, but first verify it fits under the parent's
 * ipcPayload limit. If too big, send a structured error and exit instead.
 * The parent recognizes `reason: "limit_exceeded"` errors and converts
 * them back to the same Result.failure shape that wall_clock and memory
 * limits produce.
 */
async function sendResultOrLimitError(msg) {
    const serialized = JSON.stringify(msg);
    if (serialized.length > ipcPayloadLimit) {
        const samplePrefix = serialized.slice(0, 1024);
        await sendOrDie({
            type: "error",
            error: JSON.stringify({
                reason: "limit_exceeded",
                limit: "ipc_payload",
                threshold: ipcPayloadLimit,
                value: serialized.length,
                message: `Result payload (${serialized.length} bytes) exceeded ipcPayload limit of ${ipcPayloadLimit}`,
                samplePrefix,
            }),
        });
        process.exit(1);
    }
    await sendOrDie(msg);
}
// Listen for the initial run instruction, then remove the listener
// so it doesn't interfere with sendInterruptToParent's decision handler.
const bootstrapHandler = async (msg) => {
    if (msg.type === "decision") {
        // Decision messages are for sendInterruptToParent, not for us
        return;
    }
    process.removeListener("message", bootstrapHandler);
    ipcLog("recv", msg);
    if (msg.type !== "run") {
        await sendOrDie({
            type: "error",
            error: `Unknown message type: ${msg.type ?? "undefined"}`,
        });
        process.exit(1);
    }
    if (typeof msg.ipcPayload === "number") {
        ipcPayloadLimit = msg.ipcPayload;
    }
    try {
        const scriptUrl = pathToFileURL(msg.scriptPath).href;
        ipcLog("send", { type: "log", detail: `importing ${scriptUrl}` });
        // eslint-disable-next-line no-restricted-syntax -- dynamic import required: script path is determined at runtime by the parent process
        const mod = await import(scriptUrl);
        const nodeFn = mod[msg.node];
        if (typeof nodeFn !== "function") {
            await sendOrDie({
                type: "error",
                error: `Node "${msg.node}" not found in compiled module. Available exports: ${Object.keys(mod).join(", ")}`,
            });
            process.exit(1);
            return;
        }
        // Compiled nodes export a params list as __<nodeName>NodeParams.
        // Node args are positional: main(name, { messages, callbacks }).
        const paramsKey = `__${msg.node}NodeParams`;
        if (!(paramsKey in mod)) {
            await sendOrDie({
                type: "error",
                error: `Node params metadata "${paramsKey}" not found in compiled module. The module may have been compiled with an incompatible version.`,
            });
            process.exit(1);
            return;
        }
        const paramNames = mod[paramsKey];
        const positionalArgs = paramNames.map((p) => msg.args[p]);
        ipcLog("send", { type: "log", detail: `calling node ${msg.node}` });
        // No top-level `agencyStore.run(...)` wrap here: the node entry
        // point (`runNode` in `lib/runtime/node.ts`) installs its own
        // initial frame once the compiled module's `main(...)`-style
        // wrapper invokes it, and the per-step `agencyStore.run` inside
        // generated function/node bodies re-establishes the scope frame.
        // Wrapping again here would attach the subprocess's parent-process
        // context (which doesn't even exist as a peer ALS) instead of the
        // child's own `RuntimeContext`.
        const result = await nodeFn(...positionalArgs);
        ipcLog("send", { type: "log", detail: `node ${msg.node} returned` });
        await sendResultOrLimitError({
            type: "result",
            value: {
                data: result.data,
                tokens: result.tokens,
                messages: result.messages?.toJSON?.() ?? result.messages,
            },
        });
        process.exit(0);
    }
    catch (err) {
        await sendOrDie({
            type: "error",
            error: err instanceof Error ? err.message : String(err),
        });
        process.exit(1);
    }
};
process.on("message", bootstrapHandler);
