import { mkdtemp, rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { createLogger } from "@/log"; import { startRunnerServer, type RunnerServer } from "@/server"; import { connectProtocolClient, handshake, type ProtocolClient, } from "./protocol-client"; /** * An in-process runner server plus one already-handshaken client, with * log capture (for the credential-leak assertions) and one-call * teardown. In-process so v8 coverage sees the server; the spawn suite * covers the real-child-process shape. */ export interface RunnerSession { client: ProtocolClient; workspaceDir: string; /** Every log line the runner emitted — greppable for leaks. */ logLines: string[]; close: () => Promise; } export async function startRunnerSession(options: { token: string; tmpPrefix: string; heartbeatMs?: number; }): Promise { const workspaceDir = await mkdtemp(path.join(os.tmpdir(), options.tmpPrefix)); const logLines: string[] = []; const server: RunnerServer = await startRunnerServer({ host: "127.0.0.1", port: 0, token: options.token, workspaceDir, heartbeatMs: options.heartbeatMs ?? 100, log: createLogger((line) => logLines.push(line)), }); const client = await connectProtocolClient(server.port, options.token); await handshake(client); return { client, workspaceDir, logLines, close: async () => { client.close(); await server.close(); await rm(workspaceDir, { recursive: true, force: true }); }, }; }