@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
168 lines (155 loc) • 7.17 kB
text/typescript
import { appendFileSync, chmodSync, mkdirSync } from "node:fs";
import { join } from "node:path";
import { discoverKxmProjectRoot } from "../project-config.ts";
import {
applyObservation,
formatTickLine,
loadSupervisorState,
resolveSupervisorNow,
resumeTick,
saveSupervisorState,
type SupervisorState,
} from "../supervise-state.ts";
import { print, printPlan, type Runtime } from "./types.ts";
export interface SuperviseRecordOptions {
lane: string;
pr?: string;
mergeState?: string;
ciStatus?: string;
ciConclusion?: string;
backoffMs?: string;
settled?: boolean;
}
function refuse(runtime: Runtime, error: string, text: string): number {
print(runtime.io, runtime.json, { ok: false, command: "supervise", error }, text);
return 1;
}
function requireProject(runtime: Runtime): number | undefined {
if (!discoverKxmProjectRoot(runtime.cwd)) {
return refuse(runtime, "project_required", "supervise requires a KXM project (run kxm init first)");
}
return undefined;
}
function nowOf(runtime: Runtime): string | number {
const now = resolveSupervisorNow(runtime.env.KXM_SUPERVISE_NOW, new Date());
if (now === "supervisor_clock_invalid") {
return refuse(runtime, "supervisor_clock_invalid", "KXM_SUPERVISE_NOW must be an ISO-8601 UTC timestamp");
}
return now;
}
function loaded(runtime: Runtime, now: string): SupervisorState | number {
const state = loadSupervisorState(runtime.dirs.state, now);
if (state === "supervisor_unreadable") {
return refuse(runtime, "supervisor_unreadable", "supervisor state is unreadable and was left unchanged");
}
if (state === "supervisor_schema_unsupported") {
return refuse(runtime, "supervisor_schema_unsupported", "supervisor state schema is not kxm.supervisor.v1 and was left unchanged");
}
return state;
}
function laneText(state: SupervisorState): string {
const units = Object.keys(state.lanes).sort();
if (units.length === 0) return "supervisor: no lanes";
const lines = [`supervisor: ${units.length} lane${units.length === 1 ? "" : "s"}`];
for (const unit of units) {
const lane = state.lanes[unit];
if (!lane) continue;
const flight = lane.inFlight ? "in-flight" : "settled";
const pr = lane.pr ? ` pr ${lane.pr.number} ${lane.pr.mergeState}` : "";
const ci = lane.ci ? ` ci ${lane.ci.status}/${lane.ci.conclusion}` : "";
const backoff = lane.backoffUntil ? ` backoff ${lane.backoffUntil}` : "";
lines.push(`${unit} ${flight}${pr}${ci}${backoff}`);
}
return lines.join("\n");
}
function show(runtime: Runtime, state: SupervisorState, extra: Record<string, unknown> = {}): number {
print(runtime.io, runtime.json, { ok: true, command: "supervise", ...extra, state }, laneText(state));
return 0;
}
export function cmdSuperviseStatus(runtime: Runtime): number {
const missing = requireProject(runtime);
if (missing !== undefined) return missing;
const now = nowOf(runtime);
if (typeof now === "number") return now;
const state = loaded(runtime, now);
if (typeof state === "number") return state;
return show(runtime, state);
}
function appendTick(runtime: Runtime, line: string): void {
mkdirSync(runtime.dirs.logs, { recursive: true });
const path = join(runtime.dirs.logs, "supervisor.log");
appendFileSync(path, `${line}\n`, { encoding: "utf8", mode: 0o600 });
try { chmodSync(path, 0o600); } catch { /* Windows may not expose POSIX modes. */ }
}
export function cmdSuperviseTick(runtime: Runtime): number {
const missing = requireProject(runtime);
if (missing !== undefined) return missing;
const now = nowOf(runtime);
if (typeof now === "number") return now;
const state = loaded(runtime, now);
if (typeof state === "number") return state;
const tick = resumeTick(state, now);
const line = formatTickLine(now, tick.resumed, tick.waiting);
if (runtime.dryRun) {
printPlan(runtime, { command: "supervise", resumed: tick.resumed, waiting: tick.waiting, log: line, state: tick.state }, [
{ action: "write", target: join(runtime.dirs.state, "supervisor.json") },
{ action: "write", target: join(runtime.dirs.logs, "supervisor.log") },
], "resume lanes whose backoff has elapsed");
return 0;
}
saveSupervisorState(runtime.dirs.state, tick.state);
appendTick(runtime, line);
return show(runtime, tick.state, { resumed: tick.resumed, waiting: tick.waiting, log: line });
}
function parseBackoff(value: string | undefined): number | undefined | "supervisor_backoff_invalid" {
if (value === undefined) return undefined;
if (!/^[0-9]+$/.test(value)) return "supervisor_backoff_invalid";
const parsed = Number(value);
if (!Number.isSafeInteger(parsed)) return "supervisor_backoff_invalid";
return parsed;
}
export function cmdSuperviseRecord(runtime: Runtime, options: SuperviseRecordOptions): number {
const missing = requireProject(runtime);
if (missing !== undefined) return missing;
const now = nowOf(runtime);
if (typeof now === "number") return now;
const state = loaded(runtime, now);
if (typeof state === "number") return state;
const hasPr = options.pr !== undefined || options.mergeState !== undefined;
if (hasPr && (options.pr === undefined || options.mergeState === undefined || !/^[0-9]+$/.test(options.pr))) {
return refuse(runtime, "supervisor_pr_invalid", "record a pull request with both --pr and --merge-state");
}
const hasCi = options.ciStatus !== undefined || options.ciConclusion !== undefined;
if (hasCi && (options.ciStatus === undefined || options.ciConclusion === undefined)) {
return refuse(runtime, "supervisor_ci_invalid", "record CI with both --ci-status and --ci-conclusion");
}
const backoffMs = parseBackoff(options.backoffMs);
if (backoffMs === "supervisor_backoff_invalid") {
return refuse(runtime, "supervisor_backoff_invalid", "--backoff-ms must be a whole number of milliseconds from 0 through 86400000");
}
const next = applyObservation(state, {
unit: options.lane,
inFlight: options.settled !== true,
...(hasPr ? { pr: { number: Number(options.pr), mergeState: String(options.mergeState).trim().toUpperCase() } } : {}),
...(hasCi ? { ci: { status: String(options.ciStatus).trim().toLowerCase(), conclusion: String(options.ciConclusion).trim().toLowerCase() } } : {}),
...(backoffMs !== undefined ? { backoffMs } : {}),
}, now);
if (typeof next === "string") {
const text = next === "lane_unit_invalid"
? `lane unit ${options.lane} must match the KXM identifier pattern`
: next === "supervisor_pr_invalid"
? "pull request number and merge state are not valid"
: next === "supervisor_ci_invalid"
? "CI status and conclusion are not valid"
: "--backoff-ms must be a whole number of milliseconds from 0 through 86400000";
return refuse(runtime, next, text);
}
if (runtime.dryRun) {
printPlan(runtime, { command: "supervise", state: next }, [
{ action: "write", target: join(runtime.dirs.state, "supervisor.json") },
], `record lane ${options.lane}`);
return 0;
}
saveSupervisorState(runtime.dirs.state, next);
return show(runtime, next);
}