@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
590 lines (550 loc) • 21.5 kB
text/typescript
import { spawnSync } from "node:child_process";
import { randomUUID } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import {
loadLocalMeshSnapshot,
resolveKxmSnapshotPaths,
type LocalMeshSnapshot,
type MeshTuiPlan,
type MeshTuiRun,
} from "./local-snapshot.ts";
import { readUpdateCache } from "./kxm-update.ts";
import { probeHubHealth, readHubBinding } from "./hub-binding.ts";
import { readRoutingRecords } from "./telemetry.ts";
export const SESSION_BRIEF_SKIP_LABEL = "Skip — start a fresh session";
export const MAX_SESSION_BRIEF_TASKS = 5;
export const MAX_SESSION_BRIEF_PLANS = 5;
export const SESSION_BRIEF_SCHEMA = "kxm.session-brief.v1" as const;
export const DEFAULT_SESSION_BRIEF_STALE_SECONDS = 5;
export interface SessionWorkItem {
kind: "task" | "plan";
id: string;
runId: string;
label: string;
detail: string;
prompt: string;
}
export interface SessionWorkStats {
activeTasks: number;
waitingTasks: number;
planCount: number;
inbox: number;
runTotal: number;
latestPlan?: string;
}
export interface SessionHubStatus {
state: "on" | "off" | "unknown";
evidence: "probed" | "bound" | "cached" | "unconfigured" | "process" | "timeout";
online?: boolean;
url?: string;
/** Loopback or remote, so a brief never reads the same on both. */
scope?: "loopback" | "remote";
}
export interface SessionShipStatus {
dirty: boolean;
ahead: number;
}
export interface SessionBrief {
schema: "kxm.session-brief.v1";
generatedAt: string;
staleSeconds: number;
source: "legacy" | "runtime" | "both";
hub: SessionHubStatus;
stats: SessionWorkStats;
tasks: SessionWorkItem[];
plans: SessionWorkItem[];
statusLine: string;
widgetLines: string[];
cost?: string;
sessionToken?: string;
}
function hubPrefix(hub?: Partial<SessionHubStatus>): string {
// A remote hub carries a bearer over a network; a loopback one does not. The JSON
// said so while every text surface printed the same bytes for both, which is the
// distinction an operator actually reads.
const suffix = hub?.scope === "remote" ? "/remote" : "";
if (hub?.state === "on" || (hub?.state === undefined && hub?.online === true)) return `kxm hub:on${suffix}`;
if (hub?.state === "off" || (hub?.state === undefined && hub?.online === false)) return `kxm hub:off${suffix}`;
if (hub?.state === "unknown") return `kxm hub:unknown${suffix}`;
return "kxm";
}
function truncate(value: string, width: number): string {
const text = value.replace(/\s+/g, " ").trim();
if (text.length <= width) return text;
return `${text.slice(0, Math.max(0, width - 1))}…`;
}
function uniqueLabels(items: SessionWorkItem[]): SessionWorkItem[] {
const seen = new Set<string>();
return items.map((item) => {
let label = item.label;
if (seen.has(label)) label = truncate(`${label} · ${item.id.slice(-6)}`, 72);
seen.add(label);
return label === item.label ? item : { ...item, label };
});
}
function taskItem(run: MeshTuiRun): SessionWorkItem {
const stage = run.currentStage ?? "-";
const progress = run.progress ? ` ${run.progress.done}/${run.progress.total}` : "";
const detail = `${run.definitionId}/${stage}${progress}`.trim();
const label = truncate(`Task ${run.definitionId} ${run.status} ${stage}${progress}`, 72);
const prompt = `Continue KXM task \`${run.definitionId}\` (run ${run.id}) at stage ${stage}${run.progress ? ` (${run.progress.done}/${run.progress.total})` : ""}. Load the run, then proceed without repeating completed work.`;
return { kind: "task", id: run.id, runId: run.id, label, detail, prompt };
}
function planItem(plan: MeshTuiPlan): SessionWorkItem {
const label = truncate(`Plan ${plan.summary}`, 72);
const prompt = `Continue from KXM plan: ${plan.summary} (run ${plan.runId}). Load that run and proceed.`;
return { kind: "plan", id: plan.id, runId: plan.runId, label, detail: plan.summary, prompt };
}
function recentTasks(runs: MeshTuiRun[]): MeshTuiRun[] {
const active = runs.filter((run) =>
run.status === "running" || run.status === "waiting" || run.status === "created" || run.status === "preparing"
);
const rest = runs.filter((run) =>
run.status !== "running" && run.status !== "waiting" && run.status !== "created" && run.status !== "preparing"
);
return [...active, ...rest].slice(0, MAX_SESSION_BRIEF_TASKS);
}
export function formatShipLine(ship?: SessionShipStatus): string {
if (!ship) return "ship verify · PR=CI";
if (ship.dirty) return "ship dirty · commit after verify";
if (ship.ahead > 0) return `ship ${ship.ahead} local · PR after CI`;
return "ship clean · PR after CI";
}
export function readGitShip(cwd: string): SessionShipStatus | undefined {
try {
// --no-optional-locks keeps a ship-status read from rewriting .git/index.
// git status refreshes the index under an optional lock; a dry run must not.
const dirty = spawnSync("git", ["--no-optional-locks", "-C", cwd, "status", "--porcelain"], { encoding: "utf8", windowsHide: true });
if (dirty.status !== 0) return undefined;
const isDirty = dirty.stdout.trim().length > 0;
// First try configured upstream
const upstream = spawnSync("git", ["-C", cwd, "rev-list", "--count", "@{u}..HEAD"], { encoding: "utf8", windowsHide: true });
if (upstream.status === 0) {
return {
dirty: isDirty,
ahead: Number.parseInt(upstream.stdout.trim(), 10) || 0,
};
}
// When no upstream, count against merge base of default branch
for (const baseRef of ["origin/HEAD", "main", "origin/main", "master", "origin/master"]) {
const mb = spawnSync("git", ["-C", cwd, "merge-base", baseRef, "HEAD"], { encoding: "utf8", windowsHide: true });
if (mb.status === 0 && mb.stdout.trim()) {
const count = spawnSync("git", ["-C", cwd, "rev-list", "--count", `${mb.stdout.trim()}..HEAD`], { encoding: "utf8", windowsHide: true });
if (count.status === 0) {
return {
dirty: isDirty,
ahead: Number.parseInt(count.stdout.trim(), 10) || 0,
};
}
}
}
return {
dirty: isDirty,
ahead: 0,
};
} catch {
return undefined;
}
}
export function formatCostLine(input?: {
sessionCostUsd?: number;
runCostUsd?: number;
harness?: string;
model?: string;
}): string | undefined {
if (!input) return undefined;
if (input.sessionCostUsd === undefined && input.runCostUsd === undefined) return "unknown";
const sess = input.sessionCostUsd !== undefined ? `$${input.sessionCostUsd.toFixed(2)} sess` : undefined;
const run = input.runCostUsd !== undefined ? `$${input.runCostUsd.toFixed(2)} run` : undefined;
const route = input.harness && input.model ? `${input.harness}/${input.model}` : (input.model ?? undefined);
const parts = [sess, run, route].filter(Boolean);
return parts.length > 0 ? parts.join(" · ") : "unknown";
}
export function renderStatusLine(
statsOrBrief: SessionWorkStats | SessionBrief,
current?: SessionWorkItem,
hub?: Partial<SessionHubStatus>,
ship?: SessionShipStatus,
updateLatest?: string,
cost?: string,
): string {
if ("schema" in statsOrBrief && statsOrBrief.schema === "kxm.session-brief.v1") {
const brief = statsOrBrief;
if (!current && !hub && !ship && !updateLatest && !cost) {
return brief.statusLine;
}
return formatSessionStatusLine(
brief.stats,
current,
hub ?? brief.hub,
ship,
updateLatest,
cost ?? brief.cost,
);
}
return formatSessionStatusLine(statsOrBrief as SessionWorkStats, current, hub, ship, updateLatest, cost);
}
export function formatSessionStatusLine(
stats: SessionWorkStats,
current?: SessionWorkItem,
hub?: Partial<SessionHubStatus>,
ship?: SessionShipStatus,
updateLatest?: string,
cost?: string,
): string {
const head = hubPrefix(hub);
if (!current && stats.activeTasks === 0 && stats.planCount === 0 && stats.inbox === 0) {
const idle = hub?.online === undefined && hub?.state === undefined ? "kxm idle" : `${head} · idle`;
const withShip = ship?.dirty
? `${idle} · dirty`
: (ship && ship.ahead > 0 ? `${idle} · ${ship.ahead} local` : idle);
const withCost = cost ? `${withShip} · ${cost}` : withShip;
const finalLine = updateLatest ? `${withCost} · upd ${updateLatest}` : withCost;
return finalLine.length <= 80 ? finalLine : `${finalLine.slice(0, 79)}…`;
}
const parts: string[] = [];
if (current?.kind === "task") parts.push(`${head} ${current.detail}`);
else if (current?.kind === "plan") parts.push(`${head} plan ${truncate(current.detail, 36)}`);
else parts.push(head);
parts.push(`${stats.activeTasks} task${stats.activeTasks === 1 ? "" : "s"}`);
if (stats.waitingTasks > 0) parts.push(`${stats.waitingTasks} waiting`);
parts.push(`${stats.planCount} plan${stats.planCount === 1 ? "" : "s"}`);
if (stats.inbox > 0) parts.push(`inbox ${stats.inbox}`);
if (ship?.dirty) parts.push("dirty");
else if (ship && ship.ahead > 0) parts.push(`${ship.ahead} local`);
if (cost) parts.push(cost);
if (updateLatest) parts.push(`upd ${updateLatest}`);
const line = parts.join(" · ");
return line.length <= 80 ? line : `${line.slice(0, 79)}…`;
}
export function formatSessionWidget(
stats: SessionWorkStats,
current?: SessionWorkItem,
hub?: Partial<SessionHubStatus>,
ship?: SessionShipStatus,
updateLatest?: string,
cost?: string,
): string[] {
const hubMark = hub?.state === "on" || hub?.online === true
? "hub:on "
: (hub?.state === "off" || hub?.online === false
? "hub:off "
: (hub?.state === "unknown" ? "hub:unknown " : ""));
const lines = [
`KXM ${hubMark}${stats.activeTasks} tasks ${stats.waitingTasks} waiting ${stats.planCount} plans inbox ${stats.inbox}`,
];
if (current) lines.push(`now ${current.kind} ${truncate(current.detail, 56)}`);
else if (stats.latestPlan) lines.push(`plan ${truncate(stats.latestPlan, 60)}`);
else lines.push("now no selected work");
lines.push(formatShipLine(ship));
if (cost) lines.push(`cost ${cost}`);
if (updateLatest) lines.push(`update ${updateLatest} available · kxm update --kxm`);
return lines;
}
export function buildSessionBrief(
snapshot: Pick<LocalMeshSnapshot, "runs" | "plans" | "openMessageTotal" | "runTotal"> & { source?: "legacy" | "runtime" | "both" | undefined },
current?: SessionWorkItem,
hub?: SessionHubStatus,
ship?: SessionShipStatus,
updateLatest?: string,
cost?: string,
sessionToken?: string,
source?: "legacy" | "runtime" | "both" | undefined,
staleSeconds = DEFAULT_SESSION_BRIEF_STALE_SECONDS,
): SessionBrief {
const resolvedSource = source ?? snapshot.source ?? "legacy";
const active = snapshot.runs.filter((run) =>
run.status === "running" || run.status === "waiting" || run.status === "created" || run.status === "preparing"
);
const stats: SessionWorkStats = {
activeTasks: active.length,
waitingTasks: snapshot.runs.filter((run) => run.status === "waiting").length,
planCount: snapshot.plans.length,
inbox: snapshot.openMessageTotal,
runTotal: snapshot.runTotal,
...(snapshot.plans[0]?.summary ? { latestPlan: snapshot.plans[0].summary } : {}),
};
const tasks = uniqueLabels(recentTasks(snapshot.runs).map(taskItem));
const plans = uniqueLabels(snapshot.plans.slice(0, MAX_SESSION_BRIEF_PLANS).map(planItem));
const resolvedHub: SessionHubStatus = hub ?? { state: "off", evidence: "unconfigured", online: false };
const statusLine = formatSessionStatusLine(stats, current, resolvedHub, ship, updateLatest, cost);
const widgetLines = formatSessionWidget(stats, current, resolvedHub, ship, updateLatest, cost);
return {
schema: SESSION_BRIEF_SCHEMA,
generatedAt: new Date().toISOString(),
staleSeconds,
source: resolvedSource,
hub: resolvedHub,
stats,
tasks,
plans,
statusLine,
widgetLines,
...(cost ? { cost } : {}),
...(sessionToken ? { sessionToken } : {}),
};
}
export function readCachedSessionBrief(stateDir: string): SessionBrief | undefined {
const file = join(stateDir, "session-brief.json");
try {
if (!existsSync(file)) return undefined;
const raw = readFileSync(file, "utf8");
const parsed = JSON.parse(raw) as SessionBrief;
if (parsed && parsed.schema === "kxm.session-brief.v1" && typeof parsed.generatedAt === "string") {
const ageMs = Date.now() - Date.parse(parsed.generatedAt);
const ttlMs = (parsed.staleSeconds ?? DEFAULT_SESSION_BRIEF_STALE_SECONDS) * 1000;
if (ageMs >= 0 && ageMs < ttlMs) {
return parsed;
}
}
} catch {
/* cache miss or unreadable */
}
return undefined;
}
export function writeCachedSessionBrief(stateDir: string, brief: SessionBrief): void {
try {
mkdirSync(stateDir, { recursive: true, mode: 0o700 });
const file = join(stateDir, "session-brief.json");
const tmp = `${file}.tmp.${randomUUID().slice(0, 8)}`;
writeFileSync(tmp, JSON.stringify(brief, null, 2), { encoding: "utf8", mode: 0o600 });
renameSync(tmp, file);
} catch {
/* best effort */
}
}
export function estimateSessionCost(stateDir: string): string | undefined {
try {
const telemetryFile = join(stateDir, "telemetry.jsonl");
if (!existsSync(telemetryFile)) return undefined;
const records = readRoutingRecords(telemetryFile);
if (records.length === 0) return undefined;
let totalCost = 0;
let hasMetered = false;
let latestModel: string | undefined;
let latestHarness: string | undefined;
for (const { routing } of records) {
const r = routing as unknown as Record<string, unknown>;
const costBasis = (r.costBasis as string | undefined) ?? (typeof r.costUsd === "number" ? "metered" : "unmetered");
if (costBasis === "metered" && typeof r.costUsd === "number") {
totalCost += r.costUsd;
hasMetered = true;
}
latestModel = (r.effectiveModel as string | undefined) ?? (r.requestedModel as string | undefined) ?? latestModel;
latestHarness = (r.harness as string | undefined) ?? latestHarness;
}
if (!hasMetered) return "unknown";
const route = latestHarness && latestModel ? `${latestHarness}/${latestModel}` : latestModel;
return `$${totalCost.toFixed(2)} sess${route ? ` · ${route}` : ""}`;
} catch {
return undefined;
}
}
export async function resolveSessionHubStatus(
url: string | undefined,
fetchImpl: typeof fetch = fetch,
timeoutMs = 300,
): Promise<SessionHubStatus> {
if (!url || !url.trim()) {
return { state: "off", evidence: "unconfigured", online: false };
}
try {
const { health } = await probeHubHealth(url.trim(), fetchImpl, timeoutMs);
if (health === "on") {
return { state: "on", evidence: "probed", online: true, url: url.trim() };
}
if (health === "off") {
return { state: "off", evidence: "probed", online: false, url: url.trim() };
}
return { state: "unknown", evidence: "timeout", online: false, url: url.trim() };
} catch {
return { state: "unknown", evidence: "timeout", online: false, url: url.trim() };
}
}
export function loadSessionBrief(
cwd: string,
env: NodeJS.ProcessEnv = process.env,
current?: SessionWorkItem,
hub?: SessionHubStatus,
options: {
force?: boolean;
updateLatest?: string;
cost?: string;
sessionToken?: string;
ship?: SessionShipStatus;
/** false answers from disk and the hub without refreshing session-brief.json. */
writeCache?: boolean;
} = {},
): SessionBrief {
const paths = resolveKxmSnapshotPaths(cwd, env);
if (!options.force) {
const cached = readCachedSessionBrief(paths.stateDir);
if (cached) {
if (current || hub || options.cost) {
const effectiveHub = hub ?? cached.hub;
const effectiveCost = options.cost ?? cached.cost;
const statusLine = formatSessionStatusLine(
cached.stats,
current,
effectiveHub,
options.ship,
options.updateLatest,
effectiveCost,
);
const widgetLines = formatSessionWidget(
cached.stats,
current,
effectiveHub,
options.ship,
options.updateLatest,
effectiveCost,
);
return {
...cached,
hub: effectiveHub,
...(effectiveCost ? { cost: effectiveCost } : {}),
statusLine,
widgetLines,
};
}
return cached;
}
}
const ship = options.ship ?? readGitShip(cwd);
let brief: SessionBrief;
try {
const cachedUpdate = readUpdateCache(paths.stateDir);
const updateLatest = options.updateLatest ?? (cachedUpdate?.available ? cachedUpdate.latest : undefined);
const cost = options.cost ?? estimateSessionCost(paths.stateDir);
const snapshot = loadLocalMeshSnapshot(paths.dataPath, paths.stateDir, { env });
brief = buildSessionBrief(
snapshot,
current,
hub,
ship,
updateLatest,
cost,
options.sessionToken,
snapshot.source,
);
} catch {
brief = buildSessionBrief(
{ runs: [], plans: [], openMessageTotal: 0, runTotal: 0 },
current,
hub,
ship,
options.updateLatest,
options.cost,
options.sessionToken,
);
}
if (options.writeCache !== false) writeCachedSessionBrief(paths.stateDir, brief);
return brief;
}
export async function loadSessionBriefAsync(
cwd: string,
env: NodeJS.ProcessEnv = process.env,
current?: SessionWorkItem,
hub?: SessionHubStatus,
options: {
force?: boolean;
fetchImpl?: typeof fetch;
cost?: string;
sessionToken?: string;
ship?: SessionShipStatus;
writeCache?: boolean;
} = {},
): Promise<SessionBrief> {
const paths = resolveKxmSnapshotPaths(cwd, env);
if (!options.force) {
const cached = readCachedSessionBrief(paths.stateDir);
if (cached) {
if (current || hub || options.cost) {
const effectiveHub = hub ?? cached.hub;
const effectiveCost = options.cost ?? cached.cost;
const statusLine = formatSessionStatusLine(
cached.stats,
current,
effectiveHub,
options.ship,
undefined,
effectiveCost,
);
const widgetLines = formatSessionWidget(
cached.stats,
current,
effectiveHub,
options.ship,
undefined,
effectiveCost,
);
return {
...cached,
hub: effectiveHub,
...(effectiveCost ? { cost: effectiveCost } : {}),
statusLine,
widgetLines,
};
}
return cached;
}
}
let resolvedHub = hub;
if (!resolvedHub) {
const serverUrl = env.KXM_SERVER_URL?.trim() || readHubBinding(env)?.url;
resolvedHub = await resolveSessionHubStatus(serverUrl, options.fetchImpl, 300);
}
return loadSessionBrief(cwd, env, current, resolvedHub, options);
}
export function sessionBriefChoices(brief: SessionBrief): string[] {
return [SESSION_BRIEF_SKIP_LABEL, ...brief.tasks.map((item) => item.label), ...brief.plans.map((item) => item.label)];
}
export function itemFromChoice(brief: SessionBrief, choice: string | undefined): SessionWorkItem | undefined {
if (!choice || choice === SESSION_BRIEF_SKIP_LABEL) return undefined;
return [...brief.tasks, ...brief.plans].find((item) => item.label === choice);
}
export function formatSessionBriefText(brief: SessionBrief): string {
const lines = [brief.statusLine, ""];
if (brief.tasks.length === 0 && brief.plans.length === 0) {
lines.push("No recent tasks or plans.");
return lines.join("\n");
}
if (brief.tasks.length > 0) {
lines.push("Tasks");
for (const task of brief.tasks) lines.push(` ${task.label.replace(/^Task\s+/, "")}`);
}
if (brief.plans.length > 0) {
lines.push("Plans");
for (const plan of brief.plans) lines.push(` ${plan.label.replace(/^Plan\s+/, "")}`);
}
lines.push("", "Reply with an item to continue, or start a fresh prompt. Pi TUI: /kxm");
return lines.join("\n");
}
export function sessionBriefPickerEnabled(input: { env?: NodeJS.ProcessEnv; mode?: string; reason?: string }): boolean {
if (input.env?.KXM_SESSION_BRIEF?.trim() === "off") return false;
if (input.mode !== "tui") return false;
const reason = input.reason ?? "startup";
return reason === "startup" || reason === "new" || reason === "fork";
}
export const KXM_SLASH_SUBCOMMANDS = ["status", "hub", "memory", "progress", "workflow", "help"] as const;
export type KxmSlashCommand = "brief" | typeof KXM_SLASH_SUBCOMMANDS[number];
export function parseKxmSlashArgs(args: string | undefined): KxmSlashCommand {
const raw = String(args ?? "").trim().toLowerCase();
if (raw === "" || raw === "brief") return "brief";
if (
raw === "status"
|| raw === "hub"
|| raw === "memory"
|| raw === "progress"
|| raw === "workflow"
|| raw === "help"
) return raw;
return "help";
}
export function kxmSlashCompletions(prefix: string): Array<{ value: string; label: string }> {
const p = prefix.trim().toLowerCase();
return KXM_SLASH_SUBCOMMANDS
.filter((name) => name.startsWith(p))
.map((name) => ({ value: name, label: name }));
}