/** * Source for the inline `subagent-artifact` CLI helper. * * The launch script generated by interactive-tmux.ts writes this script body * to `$ARTIFACT_DIR/cli.mjs` (via a heredoc) and invokes it for lifecycle * events. The wrapper shell writes `started` / `done` / `cancelled`; the * child pi process writes `done` / `error` itself. * * Keeping the source as a single string means there's no build step, no * extra file in the published package, and no path-resolution concerns when * the extension is loaded from arbitrary cwd's. * * Subcommands: * start — write a `started` event * done — write a `done` event with the child's exit code * error "" — write an `error` event * cancelled — write a `cancelled` event * * Exit codes: 0 on success, 2 on usage error (missing env var / unknown cmd). */ import { MAX_EVENT_TEXT_LENGTH, MAX_OUTPUT_SNAPSHOT_BYTES } from "./artifact"; /** * The body of the CLI as a string, written verbatim to * `$ARTIFACT_DIR/cli.mjs` by `writeLaunchScript`. The string is the literal * JavaScript the child process executes. */ export const CLI_SOURCE = String.raw`#!/usr/bin/env node // Generated by pi-subagentura. Writes lifecycle events to the artifact // directory (\$ARTIFACT_DIR). Subcommands: start, done, error, cancelled. // See subagent-artifact-cli.ts for the source. import { appendFileSync, closeSync, constants, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs"; import { createHash, randomUUID } from "node:crypto"; import { join } from "node:path"; const dir = process.env.ARTIFACT_DIR; if (!dir) { process.stderr.write("ARTIFACT_DIR not set\n"); process.exit(2); } mkdirSync(dir, { recursive: true, mode: 0o700 }); const statusFile = join(dir, "events.ndjson"); const activeTurnFile = join(dir, "active-turn.json"); const MAX_OUTPUT_SNAPSHOT_BYTES = ${MAX_OUTPUT_SNAPSHOT_BYTES}; const MAX_EVENT_TEXT_LENGTH = ${MAX_EVENT_TEXT_LENGTH}; const boundedOptionalEventText = (value) => typeof value === "string" ? value.slice(0, MAX_EVENT_TEXT_LENGTH) : undefined; const cmd = process.argv[2]; const arg = process.argv[3]; const write = (obj) => appendFileSync(statusFile, JSON.stringify(obj) + "\n", { mode: 0o600 }); const readEvents = () => { try { return readFileSync(statusFile, "utf8") .split("\n") .filter(Boolean) .flatMap((line) => { try { return [JSON.parse(line)]; } catch { return []; } }); } catch { return []; } }; const activeTurn = () => { try { const value = JSON.parse(readFileSync(activeTurnFile, "utf8")); return typeof value.turnId === "string" ? value.turnId : null; } catch { return null; } }; const turnId = activeTurn() ?? "process"; const lockKey = createHash("sha256").update(turnId).digest("hex").slice(0, 24); const completionLock = join(dir, ".completion-" + lockKey + ".lock"); const lockWaiter = new Int32Array(new SharedArrayBuffer(4)); const withCompletionLock = (operation) => { const deadline = Date.now() + 2000; while (true) { try { mkdirSync(completionLock, { mode: 0o700 }); break; } catch (error) { if (error?.code !== "EEXIST") throw error; try { if (Date.now() - statSync(completionLock).mtimeMs > 30000) { rmSync(completionLock, { recursive: true, force: true }); continue; } } catch { continue; } if (Date.now() >= deadline) { throw new Error("timed out acquiring completion lock for " + turnId); } Atomics.wait(lockWaiter, 0, 0, 10); } } try { return operation(); } finally { rmSync(completionLock, { recursive: true, force: true }); } }; const completed = () => readEvents().some((event) => event.version === 2 && event.type === "completion" && event.turnId === turnId); const snapshot = (eventId) => { const source = join(dir, "output.md"); let fd; let content; try { fd = openSync(source, constants.O_RDONLY | constants.O_NOFOLLOW); const stat = fstatSync(fd); if (!stat.isFile()) { return { outputError: { code: "output_unavailable", message: "output.md is not a regular file" } }; } if (stat.size > MAX_OUTPUT_SNAPSHOT_BYTES) { return { outputError: { code: "output_too_large", bytes: stat.size, maxBytes: MAX_OUTPUT_SNAPSHOT_BYTES, }, }; } content = Buffer.alloc(stat.size); let offset = 0; while (offset < content.length) { const bytesRead = readSync(fd, content, offset, content.length - offset, offset); if (bytesRead === 0) break; offset += bytesRead; } content = content.subarray(0, offset); } catch (error) { if (error?.code === "ENOENT") return {}; return { outputError: { code: "output_unavailable", message: "output.md could not be read safely" } }; } finally { if (fd !== undefined) { try { closeSync(fd); } catch { /* bounded content is already in memory */ } } } const outputs = join(dir, "outputs"); mkdirSync(outputs, { recursive: true, mode: 0o700 }); const target = join(outputs, eventId + ".md"); if (!existsSync(target)) { const tmp = target + ".tmp"; writeFileSync(tmp, content, { mode: 0o600 }); renameSync(tmp, target); } return { output: { path: target, bytes: content.byteLength, sha256: createHash("sha256").update(content).digest("hex"), }, }; }; const completion = (outcome, source, extra = {}) => { withCompletionLock(() => { if (completed()) return; const eventId = randomUUID(); const snapshotResult = snapshot(eventId); write({ version: 2, eventId, turnId, ts: Date.now(), type: "completion", status: outcome, outcome, source, ...snapshotResult, ...extra, }); }); }; switch (cmd) { case "start": write({ ts: Date.now(), type: "started", status: "running" }); break; case "done": { const exitCode = Number(arg ?? 0); completion(exitCode === 0 ? "done" : "error", "explicit", { exitCode }); break; } case "error": const errorMessage = boundedOptionalEventText(arg ?? "unknown error"); completion("error", "explicit", { ...(errorMessage ? { message: errorMessage, errorMessage } : {}), }); break; case "cancelled": completion("cancelled", "parent"); break; case "process-exit": { const exitCode = Number(arg ?? 0); const cancelled = existsSync(join(dir, ".cancelled")); if (!completed()) { completion(cancelled ? "cancelled" : "error", "process_exit", { exitCode, ...(!cancelled ? { message: "sub-agent process exited before turn completion" } : {}), }); } write({ version: 2, eventId: randomUUID(), turnId, ts: Date.now(), type: "process_exited", status: cancelled ? "cancelled" : exitCode === 0 ? "done" : "error", exitCode, }); break; } default: process.stderr.write("Unknown command: " + cmd + "\n"); process.exit(2); } `; /** Test-only helper: write the CLI to a file and chmod it. */ export function writeCliScript(targetPath: string): void { const { writeFileSync, chmodSync } = require("node:fs") as typeof import("node:fs"); writeFileSync(targetPath, CLI_SOURCE, { mode: 0o700 }); chmodSync(targetPath, 0o700); }