/** * `status` — layered fleet report: dispatch, ticks, pane, herdr, Telegram target, daemon, caps and active runs. * * Moved out of cli.ts's switch by the per-verb module split (#462); * only the case wrapper, the injected `ctx` lookups and the imports * changed from the original bodies. */ import type { CommandContext } from "./context.ts"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { stateDir } from "../config.ts"; import { collectFleetStatus, renderFleetStatusReport } from "../fleet.ts"; import { STALL_MARKER_FILE } from "../orchestrator-tick.ts"; import { fail, heading, ok, warn } from "../ui/style.ts"; /** * The orchestrator half, and the one thing `status` has ever known about the * supervising session: the stall marker its heartbeat writes when its own * prompts stop being consumed (see {@link STALL_MARKER_FILE}). * * The marker is written in the *session's* cwd, which this process has no way * to discover — so this reads the state directory, on the reference deploy's * convention that the orchestrator session runs from exactly there. That makes * the reading one-directional: a line printed here is proof of a wedge, and no * line is proof of nothing at all. On a fleet whose session lives elsewhere the * check is simply inert, which is why it never prints a reassuring "healthy". */ function stallLine(): string | undefined { let raw: string; try { raw = readFileSync(join(stateDir(), STALL_MARKER_FILE), "utf8").trim(); } catch { return undefined; } // " ". A file truncated by something else // still gets reported: that the marker exists at all is the news. const cut = raw.indexOf(" "); const since = cut < 0 ? raw : raw.slice(0, cut); const diagnosis = cut < 0 ? "" : ` — ${raw.slice(cut + 1)}`; return `orchestrator STALLED since ${since === "" ? "an unrecorded time" : since}${diagnosis}`; } function styleStatus(text: string): string { return text .split("\n") .map((line) => { if ( line === "caps" || line === "active runs" || line === "active runs (none)" || line.startsWith("project ") ) return heading(line); if (line.includes("STALLED")) return fail(line); if (/^(dispatch|ticks|pane|herdr|telegram| healthz)\s+.*\b(running|healthy|ok)\b/.test(line)) return ok(line); // The daemon headline carries its own three states (#685): green while // serving, amber when the probe timed out or the pid is gone — a bare // `daemon` heading line no longer exists to style. if (/^daemon\s+.*\b(not running|unresponsive)\b/.test(line)) return warn(line); if (/^daemon\s+running\b/.test(line)) return ok(line); if (/^(dispatch|ticks|daemon)\s+.*\b(paused|stopped|not running|overdue)\b/.test(line)) return warn(line); return line; }) .join("\n"); } export async function statusCommand(ctx: CommandContext): Promise { const report = await collectFleetStatus(ctx.projectFlag); const stalled = stallLine(); if (ctx.argv.includes("--json")) { process.stdout.write(`${JSON.stringify({ ...report, orchestratorStall: stalled }, null, 2)}\n`); return; } const text = styleStatus(renderFleetStatusReport(report)); process.stdout.write(`${text}${stalled === undefined ? "\n" : `\n\n${fail(stalled)}\n`}`); }