/** * `extend` — raise a live run's turn ceiling, or set a bounded one-shot ceiling after a failed, killed, orphaned or blocked run. * * Moved out of cli.ts's switch by the per-verb module split (#462); * the daemon discovery resolves through {@link requireDaemonControl}, the * same run-control target `worker` uses, so a live systemd-run daemon * stays reachable when its pidfile is missing (#811) and the refusal * answers cannot drift between the two verbs. * * The turn-limit endpoint is mutating, so it now carries the daemon's bearer * token (Phase 4). The token is read, never minted: the daemon mints it at * start, so an absent file means no daemon has run here — the honest refusal * is that message, not an unauthenticated request that comes back 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 extendCommand(ctx: CommandContext): Promise { const issue = ctx.issueArg("extend", ctx.argv[1]); const maxTurns = ctx.turnsFlag(); const project = findProject(loadConfig(), ctx.projectFlag); 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}/turn-limit`, { method: "PUT", headers: { "content-type": "application/json", ...auth }, body: JSON.stringify({ project: project.name, maxTurns }), }, ); const payload = (await response.json()) as { error?: unknown; kind?: unknown; runId?: unknown; maxTurns?: unknown; issue?: unknown; nextAttemptMaxTurns?: unknown; baseMaxTurns?: unknown; }; if (!response.ok) { throw new Error( typeof payload.error === "string" ? payload.error : `daemon returned HTTP ${response.status}`, ); } if ( payload.kind === "next-attempt" && payload.issue === issue && typeof payload.nextAttemptMaxTurns === "number" && typeof payload.baseMaxTurns === "number" ) { process.stdout.write( `#${issue} next attempt turn ceiling set to ${payload.nextAttemptMaxTurns} ` + `(project base ${payload.baseMaxTurns})\n`, ); return; } if (typeof payload.runId !== "string" || typeof payload.maxTurns !== "number") { throw new Error("daemon returned an invalid turn-extension response"); } process.stdout.write( `#${issue} turn ceiling extended to ${payload.maxTurns} (run ${payload.runId})\n`, ); }