import test from "node:test"; import assert from "node:assert/strict"; import { EventEmitter } from "node:events"; import { mkdtemp, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { createRunArtifacts } from "./artifacts.ts"; import { monitorRun } from "./monitor.ts"; import { createProcessSingleRunner } from "./process-runner.ts"; class FakeChild extends EventEmitter {} test("createProcessSingleRunner launches wrapper without tmux and returns monitored result", async () => { const cwd = await mkdtemp(join(tmpdir(), "pi-subagents-process-")); let metaPathSeen = ""; const runSingleTask = createProcessSingleRunner({ createArtifacts: createRunArtifacts, buildWrapperSpawn(metaPath: string) { metaPathSeen = metaPath; return { command: process.execPath, args: ["-e", "process.exit(0)"] }; }, spawnChild() { const child = new FakeChild() as any; process.nextTick(async () => { const meta = JSON.parse(await readFile(metaPathSeen, "utf8")); await writeFile( meta.resultPath, JSON.stringify( { runId: meta.runId, mode: meta.mode, task: meta.task, requestedModel: meta.requestedModel, resolvedModel: meta.resolvedModel, sessionPath: meta.sessionPath, exitCode: 0, finalText: "done", stdoutPath: meta.stdoutPath, stderrPath: meta.stderrPath, transcriptPath: meta.transcriptPath, resultPath: meta.resultPath, eventsPath: meta.eventsPath, }, null, 2, ), "utf8", ); child.emit("close", 0); }); return child; }, monitorRun: (input) => monitorRun({ ...input, pollMs: 1 }), }); const result = await runSingleTask({ cwd, meta: { mode: "single", task: "inspect auth", requestedModel: "openai/gpt-5", resolvedModel: "openai/gpt-5", }, }); assert.equal(result.finalText, "done"); assert.equal(result.exitCode, 0); assert.match(result.resultPath ?? "", /\.pi\/subagents\/runs\//); }); test("createProcessSingleRunner writes error result.json when wrapper launch fails", async () => { const cwd = await mkdtemp(join(tmpdir(), "pi-subagents-process-")); const runSingleTask = createProcessSingleRunner({ createArtifacts: createRunArtifacts, buildWrapperSpawn() { return { command: process.execPath, args: ["-e", "process.exit(0)"] }; }, spawnChild() { const child = new FakeChild() as any; process.nextTick(() => { child.emit("error", new Error("spawn boom")); }); return child; }, monitorRun: (input) => monitorRun({ ...input, pollMs: 1 }), }); const result = await runSingleTask({ cwd, meta: { mode: "single", task: "inspect auth", requestedModel: "openai/gpt-5", resolvedModel: "openai/gpt-5", }, }); assert.equal(result.exitCode, 1); assert.equal(result.stopReason, "error"); assert.match(result.errorMessage ?? "", /spawn boom/); const saved = JSON.parse(await readFile(result.resultPath!, "utf8")); assert.equal(saved.exitCode, 1); assert.equal("agent" in saved, false); assert.match(saved.errorMessage ?? "", /spawn boom/); });