/** * `worker` — pause/resume/stop one live worker session through the daemon's run-control HTTP endpoints. * * Moved out of cli.ts's switch by the per-verb module split (#462); * the daemon discovery resolves through {@link requireDaemonControl} so a * live systemd-run daemon stays reachable when its pidfile is missing (#811). * * Run control is the most consequential mutating route on the daemon port — it * can terminally settle a live run — so it carries the daemon's bearer token * (Phase 4). The token is read, never minted: an absent file means no daemon * has run under this home, and saying so is more useful than a bare 401. */ import type { CommandContext } from "./context.ts"; import { findProject, loadConfig } from "../config.ts"; import { requireDaemonControl } from "../lifecycle.ts"; import { httpAuthHeader, missingHttpTokenMessage } from "../http-token.ts"; export async function workerCommand(ctx: CommandContext): Promise { const sub = ctx.argv[1]; if (sub !== "pause" && sub !== "resume" && sub !== "stop") { process.stderr.write( "omp-conductor: worker needs pause, resume, or stop, then an issue number\n", ); process.exit(2); } const issue = ctx.issueArg("worker", ctx.argv[2]); const rawReason = sub === "stop" ? ctx.flag("reason") : undefined; const reason = rawReason?.trim().replace(/\s+/g, " "); if (sub === "stop" && (reason === undefined || reason === "" || reason.length > 500)) { process.stderr.write( "omp-conductor: worker stop needs --reason with 1-500 characters\n", ); process.exit(2); } // A bare presence flag, parsed like every other boolean flag in this CLI: // the daemon refuses a stop that would strand an open PR (#1101), and this is // the operator's explicit record of choosing to do so anyway. const allowOpenPr = sub === "stop" && ctx.argv.includes("--allow-open-pr"); const project = findProject(loadConfig(), ctx.projectFlag); // The pidfile is authoritative when it names a live process; a missing or // stale record falls back to the systemd unit, proved through its own // /healthz answer, so a live unit-owned daemon is never reported stopped // because the runtime directory was wiped (#811). The refusal chain is // shared with `extend` through requireDaemonControl. const daemon = await requireDaemonControl(project.name); const auth = httpAuthHeader(); if (auth === undefined) throw new Error(missingHttpTokenMessage()); const response = await fetch( `http://127.0.0.1:${daemon.port}/runs/${issue}/${sub}`, { method: "PUT", headers: { "content-type": "application/json", ...auth }, body: JSON.stringify({ project: project.name, source: "cli", ...(reason === undefined ? {} : { reason }), ...(allowOpenPr ? { allowOpenPr: true } : {}), }), }, ); const payload = (await response.json()) as { error?: unknown; runId?: unknown; phase?: unknown; outcome?: unknown; state?: unknown; reason?: unknown; salvageSha?: unknown; salvageError?: unknown; worktree?: unknown; }; if (!response.ok) { throw new Error( typeof payload.error === "string" ? payload.error : `daemon returned HTTP ${response.status}`, ); } if (sub === "stop") { if ( typeof payload.runId !== "string" || typeof payload.state !== "string" || (payload.outcome !== "stopped" && payload.outcome !== "already-terminal") ) { throw new Error("daemon returned an invalid worker-stop response"); } if (payload.outcome === "already-terminal") { process.stdout.write( `#${issue} worker already terminal: ${payload.state} (run ${payload.runId})\n`, ); return; } if (typeof payload.reason !== "string") { throw new Error("daemon returned an invalid worker-stop response"); } process.stdout.write( `#${issue} worker stopped (run ${payload.runId}): ${payload.reason}\n`, ); if (typeof payload.salvageSha === "string") { process.stdout.write(`work preserved: ${payload.salvageSha}\n`); } if (typeof payload.salvageError === "string") { process.stdout.write( `WIP SALVAGE FAILED: ${payload.salvageError}\n` + `worktree kept: ${typeof payload.worktree === "string" ? payload.worktree : "(path unavailable)"}\n`, ); } return; } if (typeof payload.runId !== "string" || typeof payload.phase !== "string") { throw new Error("daemon returned an invalid worker-control response"); } process.stdout.write(`#${issue} worker ${payload.phase} (run ${payload.runId})\n`); }