UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

1,189 lines (1,124 loc) • 48.6 kB
import { Box, Container, HStack, Key, matchesKey, ProcessTerminal, ScrollView, stripTerminalSequences, Text, TruncatedText, TuiAltScreen, VStack, type Component, type Terminal, type TUI, } from "@earendil-works/pi-tui"; import { mkdirSync } from "node:fs"; import { join, resolve, dirname } from "node:path"; import { spawnSync } from "node:child_process"; import type { AgentRecord } from "./protocol.ts"; import { createKxmTuiAnsiTheme, type KxmTuiTheme } from "@kontextmind/tui"; import { slugifyBranchPart } from "./external-effects.ts"; import { loadLocalMeshSnapshot, summarizeMeshRun, type LocalMeshSnapshot, type MeshTuiPidClaim, type MeshTuiPlan, type MeshTuiRun, type MeshTuiRunStage, } from "./local-snapshot.ts"; export { loadLocalMeshSnapshot, summarizeMeshRun, type LocalMeshSnapshot, type MeshTuiPidClaim, type MeshTuiPlan, type MeshTuiRun, type MeshTuiRunStage, }; export const MESH_TUI_PANELS = ["agents", "tasks", "workflows", "plans", "inbox", "procs", "spend"] as const; export type MeshTuiPanel = typeof MESH_TUI_PANELS[number]; export const MESH_TUI_TAB_LABELS: Record<MeshTuiPanel, string> = { agents: "Agents", tasks: "Tasks", workflows: "Workflows", plans: "Plans", inbox: "Inbox", procs: "Procs", spend: "Spend", }; export interface DashboardAction { action: "approve" | "reject" | "degrade" | "signal" | "cancel"; at: string; targetId?: string | undefined; } export interface MeshTuiView { tab: MeshTuiPanel; selected: number; pane: "list" | "detail"; help: boolean; statusMessage?: string | undefined; lastAction?: DashboardAction | undefined; } export function defaultMeshTuiView(screen?: MeshTuiPanel): MeshTuiView { return { tab: screen ?? "agents", selected: 0, pane: "list", help: false }; } export function applyMeshTuiKey(view: MeshTuiView, key: string, itemCount = 0): MeshTuiView | "quit" { if (matchesKey(key, "q") || matchesKey(key, Key.ctrl("c"))) return "quit"; if (matchesKey(key, Key.escape)) return view.help ? { ...view, help: false } : "quit"; if (matchesKey(key, "h") || matchesKey(key, "?")) return { ...view, help: !view.help }; const byNumber: Record<string, MeshTuiPanel> = { "1": "agents", "2": "tasks", "3": "workflows", "4": "plans", "5": "inbox", "6": "procs", "7": "spend", }; const tab = byNumber[key]; if (tab) return { tab, selected: 0, pane: "list", help: false }; const index = MESH_TUI_PANELS.indexOf(view.tab); if (matchesKey(key, Key.tab) || matchesKey(key, "]")) { return { tab: MESH_TUI_PANELS[(index + 1) % MESH_TUI_PANELS.length]!, selected: 0, pane: "list", help: false }; } if (matchesKey(key, Key.shift("tab")) || matchesKey(key, "[")) { return { tab: MESH_TUI_PANELS[(index - 1 + MESH_TUI_PANELS.length) % MESH_TUI_PANELS.length]!, selected: 0, pane: "list", help: false }; } if (matchesKey(key, Key.left)) return { ...view, pane: "list", help: false }; if (matchesKey(key, Key.right) || matchesKey(key, Key.enter) || matchesKey(key, Key.space)) { return { ...view, pane: "detail", help: false }; } if (matchesKey(key, Key.up)) { return { ...view, selected: Math.max(0, view.selected - 1), pane: "list", help: false }; } if (matchesKey(key, Key.down)) { return { ...view, selected: Math.min(Math.max(0, itemCount - 1), view.selected + 1), pane: "list", help: false }; } if (matchesKey(key, "a")) { return { ...view, statusMessage: `[APPROVE] Queued approval for ${view.tab} item #${view.selected + 1}`, lastAction: { action: "approve", at: new Date().toISOString() }, }; } if (matchesKey(key, "r")) { return { ...view, statusMessage: `[REJECT] Marked ${view.tab} item #${view.selected + 1} for rework`, lastAction: { action: "reject", at: new Date().toISOString() }, }; } if (matchesKey(key, "d")) { return { ...view, statusMessage: `[DEGRADE] Degraded ${view.tab} item #${view.selected + 1} to operator`, lastAction: { action: "degrade", at: new Date().toISOString() }, }; } if (matchesKey(key, "s")) { return { ...view, statusMessage: `[SIGNAL] Signal dispatched for ${view.tab} item #${view.selected + 1}`, lastAction: { action: "signal", at: new Date().toISOString() }, }; } if (matchesKey(key, "c")) { return { ...view, statusMessage: `[CANCEL] Cancellation requested for ${view.tab} item #${view.selected + 1}`, lastAction: { action: "cancel", at: new Date().toISOString() }, }; } return view; } export interface DegradeWorktreeResult { ok: boolean; branchName: string; worktreePath: string; jumpCommand: string; copiedToClipboard: boolean; error?: string | undefined; } export function copyToClipboard(text: string): boolean { try { if (process.platform === "darwin") { const proc = spawnSync("pbcopy", { input: text, encoding: "utf8", windowsHide: true }); return proc.status === 0; } if (process.platform === "win32") { const proc = spawnSync("clip", { input: text, encoding: "utf8", windowsHide: true }); return proc.status === 0; } const wl = spawnSync("wl-copy", [text], { encoding: "utf8", windowsHide: true }); if (wl.status === 0) return true; const xclip = spawnSync("xclip", ["-selection", "clipboard"], { input: text, encoding: "utf8", windowsHide: true }); return xclip.status === 0; } catch { return false; } } /** * Automated Degrade Worktree Spawn (Decision Q1). * Spawns an isolated git worktree for human operator intervention on key 'd'. * Detaches the AI worker, copies directory path to clipboard, and prints jump command. */ export function spawnDegradeWorktree( repoRoot: string, runId: string, options?: { description?: string | undefined; execFn?: ((cmd: string, args: string[]) => { status: number; stdout: string; stderr: string }) | undefined; clipboardFn?: ((text: string) => boolean) | undefined; }, ): DegradeWorktreeResult { const runner = options?.execFn ?? ((cmd: string, args: string[]) => { const res = spawnSync(cmd, args, { cwd: repoRoot, encoding: "utf8", windowsHide: true, env: Object.fromEntries( Object.entries(process.env).filter(([name]) => !name.toUpperCase().startsWith("GIT_")), ), }); return { status: res.status ?? 1, stdout: res.stdout || "", stderr: res.stderr || "", }; }); const cleanRunId = runId.replace(/^run_/, ""); const desc = options?.description ? slugifyBranchPart(options.description, 30) : "degrade"; const branchName = `kxm/run-${cleanRunId}-${desc}`; const relativeWtPath = join(".kxm", "worktrees", `run-${cleanRunId}-${desc}`); const absoluteWtPath = resolve(repoRoot, relativeWtPath); const jumpCommand = `cd "${absoluteWtPath}"`; try { mkdirSync(dirname(absoluteWtPath), { recursive: true }); const branchCheck = runner("git", ["rev-parse", "--verify", `refs/heads/${branchName}`]); let wtRes: { status: number; stdout: string; stderr: string }; if (branchCheck.status === 0) { wtRes = runner("git", ["worktree", "add", absoluteWtPath, branchName]); } else { wtRes = runner("git", ["worktree", "add", "-b", branchName, absoluteWtPath]); } if (wtRes.status !== 0) { return { ok: false, branchName, worktreePath: absoluteWtPath, jumpCommand, copiedToClipboard: false, error: `failed_to_add_worktree: ${wtRes.stderr.trim() || wtRes.stdout.trim()}`, }; } const clipFn = options?.clipboardFn ?? copyToClipboard; const copied = clipFn(absoluteWtPath); return { ok: true, branchName, worktreePath: absoluteWtPath, jumpCommand, copiedToClipboard: copied, }; } catch (err: unknown) { return { ok: false, branchName, worktreePath: absoluteWtPath, jumpCommand, copiedToClipboard: false, error: `degrade_worktree_spawn_error: ${(err as Error).message}`, }; } } export interface MeshTuiSnapshot extends LocalMeshSnapshot { serverUrl: string; healthOk: boolean; readyOk: boolean; storage?: string; onlineCount: number; transport: "sse" | "snapshot"; metadataMode?: "ops" | "legacy"; error?: string; fetchedAt: string; } function age(iso: string, now: number): string { const ms = now - Date.parse(iso); if (!Number.isFinite(ms) || ms < 0) return "?"; const s = Math.floor(ms / 1000); if (s < 60) return `${s}s`; const m = Math.floor(s / 60); if (m < 60) return `${m}m`; const h = Math.floor(m / 60); if (h < 24) return `${h}h`; return `${Math.floor(h / 24)}d`; } function pad(value: string, width: number): string { const text = value.replace(/\s+/g, " ").trim(); if (text.length <= width) return text.padEnd(width); return `${text.slice(0, Math.max(0, width - 1))}…`; } function progressBar(done: number, total: number, width = 10): string { if (total <= 0) return "-".repeat(width); const filled = Math.max(0, Math.min(width, Math.round((done / total) * width))); return "#".repeat(filled) + "-".repeat(width - filled); } function stageMark(status: string, theme: MeshTuiTheme): string { if (status === "passed") return theme.success("[x]"); if (status === "failed") return theme.error("[F]"); if (status === "warning") return theme.warning("[!]"); if (status === "in_progress") return theme.warning("[~]"); if (status === "waiting") return theme.dim("[.]"); return theme.dim("[ ]"); } /** * The dashboard palette lives in `tui/theme.ts` so a config surface and the * live screens cannot drift to different meanings for the same colour. */ type MeshTuiTheme = Pick<KxmTuiTheme, "accent" | "dim" | "error" | "success" | "warning" | "headerBg" | "panelBg">; function meshTuiTheme(color: boolean): MeshTuiTheme { return createKxmTuiAnsiTheme(color); } function visibleAgents(snapshot: MeshTuiSnapshot): AgentRecord[] { return snapshot.agents.filter((agent) => agent.model !== "tui"); } /** Presence comes from the hub clock, so the dash colours it rather than * recomputing it: green holds a lease, amber has lost it but has not been * swept, dim is a registered peer that is gone. */ function presenceCell(agent: AgentRecord, theme: MeshTuiTheme, width = 7): string { // Local-store projections carry no hub-clocked presence; the dash shows "n/a" // rather than inventing a lease from the reader's clock. const presence = agent.presence ?? "n/a"; const cell = pad(presence, width); if (presence === "online") return theme.success(cell); if (presence === "stale") return theme.warning(cell); return theme.dim(cell); } function panelMetric(snapshot: MeshTuiSnapshot, panel: MeshTuiPanel): string { if (panel === "agents") { const agents = visibleAgents(snapshot); return `${agents.filter((agent) => agent.online).length}/${agents.length}`; } if (panel === "tasks") { const active = snapshot.runs.filter((run) => run.status === "running" || run.status === "waiting"); const total = active.reduce((sum, run) => sum + (run.progress?.total ?? 0), 0); const done = active.reduce((sum, run) => sum + (run.progress?.done ?? 0), 0); return total > 0 ? `${done}/${total}` : String(active.length); } if (panel === "workflows") return snapshot.runs.length === snapshot.runTotal ? String(snapshot.runTotal) : `${snapshot.runs.length}/${snapshot.runTotal}`; if (panel === "plans") return String(snapshot.plans.length); if (panel === "inbox") return snapshot.openMessages.length === snapshot.openMessageTotal ? String(snapshot.openMessageTotal) : `${snapshot.openMessages.length}/${snapshot.openMessageTotal}`; if (panel === "spend") { const spend = snapshot.spend ?? []; let totalCost = 0; let hasMetered = false; for (const item of spend) { const r = item.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; } } return hasMetered ? `$${totalCost.toFixed(2)}` : String(spend.length); } return `${snapshot.pids.filter((claim) => claim.live).length}/${snapshot.pids.length}`; } function panelRows(snapshot: MeshTuiSnapshot, panel: MeshTuiPanel, theme: MeshTuiTheme): string[] { const now = Date.parse(snapshot.fetchedAt); if (panel === "agents") { const agents = visibleAgents(snapshot); return agents.length === 0 ? [] : [ theme.dim(`${pad("name", 14)} ${pad("on", 3)} ${pad("model", 24)} ${pad("seen", 4)} purpose`), ...agents.map((agent) => ( `${pad(agent.name, 14)} ${agent.online ? theme.success(pad("yes", 3)) : theme.dim(pad("no", 3))} ${pad(agent.model ?? "-", 24)} ${pad(age(agent.lastSeenAt, now), 4)} ${agent.purpose}` )), ]; } if (panel === "tasks") { const active = snapshot.runs.filter((run) => run.status === "running" || run.status === "waiting"); if (active.length === 0) return []; const rows: string[] = []; for (const run of active) { const progress = run.progress; const bar = progress ? progressBar(progress.done, progress.total) : "----------"; const counts = progress ? `${progress.done}/${progress.total}` : "-"; rows.push(`${pad(run.definitionId, 16)} ${pad(run.status, 9)} ${bar} ${pad(counts, 5)} ${run.currentStage ?? "-"} ${run.id}`); for (const stage of run.stages ?? []) { const attempts = stage.attempts && stage.attempts > 1 ? ` attempt ${stage.attempts}` : ""; rows.push(` ${stageMark(stage.status, theme)} ${pad(stage.label ?? stage.id, 22)} ${stage.status}${attempts}`); } } return rows; } if (panel === "workflows") { return [ ...(snapshot.runs.length === 0 ? [] : [theme.dim(`${pad("state", 10)} ${pad("workflow", 16)} ${pad("stage", 14)} ${pad("done", 5)} id`)]), ...snapshot.runs.map((run) => { const counts = run.progress ? `${run.progress.done}/${run.progress.total}` : "-"; return `${pad(run.status, 10)} ${pad(run.definitionId, 16)} ${pad(run.currentStage ?? "-", 14)} ${pad(counts, 5)} ${run.id}`; }), ...(snapshot.runTotal > snapshot.runs.length ? [theme.dim(`… +${snapshot.runTotal - snapshot.runs.length} more`)] : []), ]; } if (panel === "plans") { return snapshot.plans.length === 0 ? [] : [ theme.dim(`${pad("age", 4)} ${pad("run", 12)} ${pad("stage", 14)} plan`), ...snapshot.plans.map((plan) => ( `${pad(age(plan.createdAt, now), 4)} ${pad(plan.runId, 12)} ${pad(plan.stageId ?? "-", 14)} ${plan.summary}` )), ]; } if (panel === "inbox") { return snapshot.openMessages.length === 0 ? [] : [ theme.dim(`${pad("state", 9)} ${pad("from", 12)} ${pad("to", 12)} ${pad("mode", 8)} ${pad("age", 4)} id`), ...snapshot.openMessages.map((message) => ( `${theme.warning(pad(message.status, 9))} ${pad(message.fromName, 12)} ${pad(message.toName, 12)} ${pad(message.delivery, 8)} ${pad(age(message.createdAt, now), 4)} ${message.id}` )), ...(snapshot.openMessageTotal > snapshot.openMessages.length ? [theme.dim(`… +${snapshot.openMessageTotal - snapshot.openMessages.length} more`)] : []), ]; } if (panel === "spend") { const spend = snapshot.spend ?? []; if (spend.length === 0) return []; return [ theme.dim(`${pad("age", 4)} ${pad("harness/model", 22)} ${pad("cost", 8)} ${pad("tokens", 14)} outcome`), ...spend.slice().reverse().map((entry) => { const r = entry.routing as unknown as Record<string, unknown>; const model = (r.effectiveModel as string | undefined) ?? (r.requestedModel as string | undefined) ?? "-"; const harness = (r.harness as string | undefined) ?? "-"; const route = `${harness}/${model}`; const cost = typeof r.costUsd === "number" ? `$${r.costUsd.toFixed(3)}` : ((r.costBasis as string) ?? "-"); const tokensIn = typeof r.tokensIn === "number" ? r.tokensIn : 0; const tokensOut = typeof r.tokensOut === "number" ? r.tokensOut : 0; const tokens = `${tokensIn}+${tokensOut}`; const outcome = (r.verifierOutcome as string | undefined) ?? (r.finalOutcome as string | undefined) ?? "-"; return `${pad(age(entry.recordedAt, now), 4)} ${pad(route, 22)} ${pad(cost, 8)} ${pad(tokens, 14)} ${outcome}`; }), ]; } return snapshot.pids.map((claim) => `${claim.live ? theme.success("live") : theme.error("dead")} ${pad(claim.role ?? "-", 8)} pid=${claim.pid ?? "-"} ${claim.file}`); } function tabItemCount(snapshot: MeshTuiSnapshot, tab: MeshTuiPanel): number { if (tab === "agents") return visibleAgents(snapshot).length; if (tab === "tasks") return snapshot.runs.filter((run) => run.status === "running" || run.status === "waiting").length; if (tab === "workflows") return snapshot.runs.length; if (tab === "plans") return snapshot.plans.length; if (tab === "inbox") return snapshot.openMessages.length; if (tab === "spend") return snapshot.spend?.length ?? 0; return snapshot.pids.length; } function tabSplits(tab: MeshTuiPanel): boolean { return tab !== "procs"; } function cursor(theme: MeshTuiTheme, selected: boolean, pane: "list" | "detail", activePane: "list" | "detail"): string { if (!selected) return " "; return activePane === pane ? theme.accent("› ") : theme.dim("· "); } function listLines(snapshot: MeshTuiSnapshot, view: MeshTuiView, theme: MeshTuiTheme): string[] { const now = Date.parse(snapshot.fetchedAt); const mark = (index: number) => cursor(theme, index === view.selected, "list", view.pane); if (view.tab === "agents") { return visibleAgents(snapshot).map((agent, index) => ( `${mark(index)}${pad(agent.name, 14)} ${presenceCell(agent, theme)} ${pad(agent.host ?? "-", 12)} ${pad(agent.model ?? "-", 14)} ${pad(age(agent.lastSeenAt, now), 4)}` )); } if (view.tab === "tasks" || view.tab === "workflows") { const runs = view.tab === "tasks" ? snapshot.runs.filter((run) => run.status === "running" || run.status === "waiting") : snapshot.runs; return runs.map((run, index) => { const counts = run.progress ? `${run.progress.done}/${run.progress.total}` : "-"; const bar = run.progress ? progressBar(run.progress.done, run.progress.total, 8) : "--------"; return `${mark(index)}${pad(run.definitionId, 14)} ${pad(run.status, 8)} ${bar} ${pad(counts, 5)} ${run.id}`; }); } if (view.tab === "plans") { return snapshot.plans.map((plan, index) => ( `${mark(index)}${pad(age(plan.createdAt, now), 4)} ${pad(plan.stageId ?? "-", 12)} ${plan.summary}` )); } if (view.tab === "inbox") { return snapshot.openMessages.map((message, index) => ( `${mark(index)}${pad(message.status, 9)} ${pad(message.fromName, 10)} → ${pad(message.toName, 10)} ${pad(age(message.createdAt, now), 4)}` )); } if (view.tab === "spend") { const spend = (snapshot.spend ?? []).slice().reverse(); return spend.map((entry, index) => { const r = entry.routing as unknown as Record<string, unknown>; const model = (r.effectiveModel as string | undefined) ?? (r.requestedModel as string | undefined) ?? "-"; const harness = (r.harness as string | undefined) ?? "-"; const route = `${harness}/${model}`; const cost = typeof r.costUsd === "number" ? `$${r.costUsd.toFixed(3)}` : ((r.costBasis as string) ?? "-"); const outcome = (r.verifierOutcome as string | undefined) ?? (r.finalOutcome as string | undefined) ?? "-"; return `${mark(index)}${pad(age(entry.recordedAt, now), 4)} ${pad(route, 16)} ${pad(cost, 8)} ${outcome}`; }); } return snapshot.pids.map((claim, index) => ( `${mark(index)}${claim.live ? theme.success("live") : theme.error("dead")} ${pad(claim.role ?? "-", 8)} pid=${claim.pid ?? "-"}` )); } function detailLines(snapshot: MeshTuiSnapshot, view: MeshTuiView, theme: MeshTuiTheme): string[] { const now = Date.parse(snapshot.fetchedAt); if (view.tab === "agents") { const agent = visibleAgents(snapshot)[view.selected]; if (!agent) return [theme.dim("No agent selected")]; const related = snapshot.openMessages.filter((message) => message.fromName === agent.name || message.toName === agent.name); return [ theme.accent(agent.name), `${presenceCell(agent, theme, (agent.presence ?? "n/a").length)} ${agent.model ?? "-"}`, `host ${agent.host ?? "-"}`, agent.purpose, `seen ${age(agent.lastSeenAt, now)} ago · lease ${agent.leaseExpiresAt?.slice(11, 19) ?? "n/a"} UTC`, "", theme.dim("Open work"), ...(related.length === 0 ? [theme.dim("none")] : related.map((message) => `${message.status} ${message.fromName} → ${message.toName} ${age(message.createdAt, now)}`)), ]; } if (view.tab === "tasks" || view.tab === "workflows") { const runs = view.tab === "tasks" ? snapshot.runs.filter((run) => run.status === "running" || run.status === "waiting") : snapshot.runs; const run = runs[view.selected]; if (!run) return [theme.dim("No run selected")]; const counts = run.progress ? `${run.progress.done}/${run.progress.total}` : "-"; return [ theme.accent(run.definitionId), `${run.status} ${counts} ${run.currentStage ?? "-"}`, run.id, run.targetAgentName ? `owner ${run.targetAgentName}` : "", "", ...(run.stages ?? []).map((stage) => { const attempts = stage.attempts && stage.attempts > 1 ? ` attempt ${stage.attempts}` : ""; return `${stageMark(stage.status, theme)} ${pad(stage.label ?? stage.id, 22)} ${stage.status}${attempts}`; }), ].filter((line) => line !== ""); } if (view.tab === "plans") { const plan = snapshot.plans[view.selected]; if (!plan) return [theme.dim("No plan selected")]; const run = snapshot.runs.find((candidate) => candidate.id === plan.runId); return [ theme.accent("Plan"), plan.summary, `run ${plan.runId}`, plan.stageId ? `stage ${plan.stageId}` : "", `${plan.severity ?? "info"} ${age(plan.createdAt, now)} ago`, "", ...(run?.stages ?? []).map((stage) => `${stageMark(stage.status, theme)} ${stage.label ?? stage.id}`), ].filter((line) => line !== ""); } if (view.tab === "inbox") { const message = snapshot.openMessages[view.selected]; if (!message) return [theme.dim("No message selected")]; return [ theme.accent(message.id), `${message.status} ${message.delivery}`, `${message.fromName} → ${message.toName}`, age(message.createdAt, now), message.correlationId ? `corr ${message.correlationId}` : theme.dim("bodies never shown"), ]; } if (view.tab === "spend") { const spend = (snapshot.spend ?? []).slice().reverse(); const entry = spend[view.selected]; if (!entry) return [theme.dim("No spend record selected")]; const r = entry.routing as unknown as Record<string, unknown>; const model = (r.effectiveModel as string | undefined) ?? (r.requestedModel as string | undefined) ?? "-"; const harness = (r.harness as string | undefined) ?? "-"; const cost = typeof r.costUsd === "number" ? `$${r.costUsd.toFixed(4)}` : ((r.costBasis as string) ?? "-"); const tokensIn = typeof r.tokensIn === "number" ? r.tokensIn : 0; const tokensOut = typeof r.tokensOut === "number" ? r.tokensOut : 0; const cacheRead = typeof r.cacheReadTokens === "number" ? r.cacheReadTokens : 0; const outcome = (r.verifierOutcome as string | undefined) ?? (r.finalOutcome as string | undefined) ?? "-"; return [ theme.accent(`${harness}/${model}`), `cost ${cost} (${(r.costBasis as string) ?? "unknown"})`, `tokens: in=${tokensIn} out=${tokensOut} cacheRead=${cacheRead}`, `outcome: ${outcome}`, `recorded ${age(entry.recordedAt, now)} ago (${entry.recordedAt})`, ...((r.runId || r.workflowRunId) ? [`run ${((r.runId ?? r.workflowRunId) as string)}`] : []), ...((r.stepId || r.stageId) ? [`stage ${((r.stepId ?? r.stageId) as string)}`] : []), ]; } const claim = snapshot.pids[view.selected]; if (!claim) return [theme.dim("No process selected")]; return [ claim.live ? theme.success("live") : theme.error("dead"), claim.role ?? "-", `pid ${claim.pid ?? "-"}`, claim.file, ]; } function renderTabBar(snapshot: MeshTuiSnapshot, view: MeshTuiView, theme: MeshTuiTheme): string { return MESH_TUI_PANELS.map((panel, index) => { const label = `${index + 1} ${MESH_TUI_TAB_LABELS[panel]} ${panelMetric(snapshot, panel)}`; return view.tab === panel ? theme.accent(`[${label}]`) : theme.dim(` ${label} `); }).join(" "); } function dataPanel(title: string, metric: string, rows: string[], emptyText: string, theme: MeshTuiTheme): Component { const panel = new Box(1, 0, theme.panelBg); panel.addChild(new Text(`${theme.accent(`▌ ${title}`)} ${theme.dim(metric)}`, 0, 0)); const table = new Container(); if (rows.length === 0) { table.addChild(new Text(theme.dim(emptyText), 0, 0)); } else { for (const row of rows) table.addChild(new TruncatedText(row, 0, 0)); } panel.addChild(table); return panel; } export class KxmDashboard implements Component { readonly root = new VStack([], { gap: 0 }); private readonly listContent = new VStack([], { gap: 0 }); private readonly listScroll = new ScrollView(this.listContent, { primary: true, overscroll: "contain", scrollbar: "auto" }); private readonly detailContent = new VStack([], { gap: 0 }); private readonly detailScroll = new ScrollView(this.detailContent, { primary: true, overscroll: "contain", scrollbar: "auto" }); private snapshot: MeshTuiSnapshot; private view: MeshTuiView; private readonly color: boolean; private readonly requestRender: () => void; private readonly onQuit: () => void; private readonly getWidth: () => number; private readonly onAction?: ((action: DashboardAction, snapshot: MeshTuiSnapshot, view: MeshTuiView) => Promise<void> | void) | undefined; constructor( snapshot: MeshTuiSnapshot, view: MeshTuiView, color: boolean, requestRender: () => void, onQuit: () => void, getWidth: () => number = () => 120, onAction?: ((action: DashboardAction, snapshot: MeshTuiSnapshot, view: MeshTuiView) => Promise<void> | void) | undefined, ) { this.snapshot = snapshot; this.view = view; this.color = color; this.requestRender = requestRender; this.onQuit = onQuit; this.getWidth = getWidth; this.onAction = onAction; this.rebuild(); } update(snapshot: MeshTuiSnapshot): void { this.snapshot = snapshot; this.rebuild(); this.requestRender(); } setStatusMessage(message: string): void { this.view = { ...this.view, statusMessage: message }; this.rebuild(); this.requestRender(); } private activeScroll(): ScrollView { return this.view.pane === "detail" ? this.detailScroll : this.listScroll; } private fillPane(target: VStack, rows: string[], empty: string, theme: MeshTuiTheme): void { target.clear(); if (rows.length === 0) target.addChild(new Text(theme.dim(empty), 0, 0)); else for (const row of rows) target.addChild(new TruncatedText(row, 0, 0)); } private rebuild(): void { const theme = meshTuiTheme(this.color); const title = new TruncatedText(theme.accent("kxm dash"), 0, 0); const hub = this.snapshot.healthOk ? theme.success("● hub ok") : theme.error("✗ hub down"); const ready = this.snapshot.readyOk ? theme.success("● ready") : theme.error("✗ not ready"); const agents = visibleAgents(this.snapshot); const online = agents.filter((agent) => agent.online).length; const transport = this.snapshot.transport === "snapshot" ? "snapshot" : this.snapshot.metadataMode === "ops" ? "live ops" : this.snapshot.metadataMode === "legacy" ? "presence/local" : "live"; const updated = this.snapshot.fetchedAt.slice(11, 19); const status = new TruncatedText( `${hub} ${ready} ${transport} ${online}/${agents.length} online ${theme.dim(`updated ${updated} UTC · ${this.snapshot.serverUrl}`)}`, 0, 0, ); const header = new Box(1, 0, theme.headerBg); header.addChild(new HStack([ { component: title, basis: 16, shrink: 1, minSize: 10 }, { component: status, grow: 1, shrink: 1, minSize: 20 }, ], { gap: 2 })); const tabs = new Box(1, 0, theme.panelBg); tabs.addChild(new TruncatedText(renderTabBar(this.snapshot, this.view, theme), 0, 0)); this.fillPane(this.listContent, listLines(this.snapshot, this.view, theme), "Nothing here yet", theme); this.fillPane(this.detailContent, detailLines(this.snapshot, this.view, theme), "Select an item", theme); const listTitle = this.view.pane === "list" ? theme.accent("list") : theme.dim("list"); const detailTitle = this.view.pane === "detail" ? theme.accent("detail") : theme.dim("detail"); const listPane = new Box(1, 0, theme.panelBg); listPane.addChild(new Text(listTitle, 0, 0)); listPane.addChild(this.listScroll); const detailPane = new Box(1, 0, theme.panelBg); detailPane.addChild(new Text(detailTitle, 0, 0)); detailPane.addChild(this.detailScroll); const split = tabSplits(this.view.tab); const body = this.view.help ? (() => { const help = new Box(1, 0, theme.panelBg); help.addChild(new Text(`${theme.accent("kxm dash")}\n1–7 or Tab/[ ] switch tabs · ←→ list/detail · ↑↓ select · PgUp/PgDn scroll · h help · q quit\nAccess Control Plane: [a] approve · [r] reject · [d] degrade · [s] signal · [c] cancel\nAgents, Tasks, Workflows, Plans, Inbox, Procs, Spend. Split pane on wide terminals. No message bodies.`, 0, 0)); return help; })() : split ? new HStack([ { component: listPane, basis: 44, shrink: 1, minSize: 28, visible: ({ width }) => width >= 76 || this.view.pane === "list" }, { component: detailPane, grow: 1, shrink: 1, minSize: 28, visible: ({ width }) => width >= 76 || this.view.pane === "detail" }, ], { gap: 1 }) : listPane; const error = this.snapshot.error ? ` ${theme.error(`ERROR ${this.snapshot.error}`)}` : ""; const feed = this.snapshot.metadataMode === "legacy" ? "presence + local" : "live"; const actionNotice = this.view.statusMessage ? ` ${theme.accent(this.view.statusMessage)}` : ""; const footer = new TruncatedText( `${theme.dim(`tab ${MESH_TUI_TAB_LABELS[this.view.tab]} · ${this.view.pane} · 1–7 tabs · h help · a/r/d/s/c · q quit · ${feed}`)}${actionNotice}${error}`, 1, 0, ); this.root.clear(); this.root.addChild(header, { basis: "auto", shrink: 0 }); this.root.addChild(tabs, { basis: "auto", shrink: 0 }); this.root.addChild(body, { basis: "auto", grow: 1, shrink: 1, minSize: 1 }); this.root.addChild(footer, { basis: "auto", shrink: 0 }); } handleInput(data: string): void { const viewport = this.activeScroll().viewportHeight; const page = viewport > 3 ? viewport - 2 : 10; if (matchesKey(data, Key.pageUp) || matchesKey(data, Key.ctrl("u"))) { this.activeScroll().scrollBy(-page); this.requestRender(); return; } if (matchesKey(data, Key.pageDown) || matchesKey(data, Key.ctrl("d"))) { this.activeScroll().scrollBy(page); this.requestRender(); return; } const next = applyMeshTuiKey(this.view, data, tabItemCount(this.snapshot, this.view.tab)); if (next === "quit") { this.onQuit(); return; } if (next !== this.view) { this.view = next; if (next.lastAction && this.onAction) { void this.onAction(next.lastAction, this.snapshot, next); } this.rebuild(); this.requestRender(); } } invalidate(): void { this.root.invalidate(); } render(width: number): string[] { return this.root.render(width); } } export function renderMeshTui(snapshot: MeshTuiSnapshot, view: MeshTuiView = defaultMeshTuiView(), width = 120): string { const dashboard = new KxmDashboard(snapshot, view, false, () => undefined, () => undefined); return `${dashboard.render(width).map(stripTerminalSequences).join("\n")}\n`; } async function readJson<T>(response: Response): Promise<T> { return await response.json() as T; } async function waitForReconnect(signal: AbortSignal, milliseconds = 1_000): Promise<void> { if (signal.aborted) return; await new Promise<void>((resolve) => { const done = () => { clearTimeout(timer); signal.removeEventListener("abort", done); resolve(); }; const timer = setTimeout(done, milliseconds); timer.unref(); signal.addEventListener("abort", done, { once: true }); }); } export async function runMeshTui(input: { serverUrl: string; dataPath: string; stateDir: string; project: string; authToken?: string; fetchImpl: typeof fetch; stdout: (text: string) => void; stdin?: NodeJS.ReadStream; isTty?: boolean; now?: () => Date; abort?: AbortSignal; terminal?: Terminal; reconnectMs?: number; screen?: MeshTuiPanel; env?: Record<string, string | undefined>; }): Promise<number> { const base = input.serverUrl.replace(/\/$/, ""); const headers = (identity?: { id: string; key: string }): Record<string, string> => ({ "content-type": "application/json", ...(input.authToken ? { authorization: `Bearer ${input.authToken}` } : {}), ...(identity ? { "x-kxm-agent-id": identity.id, "x-kxm-agent-key": identity.key } : {}), }); const tty = input.isTty ?? Boolean(input.stdin?.isTTY && process.stdout.isTTY); let identity: { id: string; key: string } | undefined; let useOpsStream = true; let interactive: { tui: TUI; dashboard: KxmDashboard } | undefined; const name = `tui-${process.pid}`; const view = defaultMeshTuiView(input.screen); const paint = (snapshot: MeshTuiSnapshot) => { if (interactive) { interactive.dashboard.update(snapshot); return; } input.stdout(renderMeshTui(snapshot, view)); }; const snapshotFromHub = async (transport: "sse" | "snapshot", extra?: Partial<MeshTuiSnapshot>): Promise<MeshTuiSnapshot> => { const fetchedAt = (input.now?.() ?? new Date()).toISOString(); let healthOk = false; let readyOk = false; let storage: string | undefined; let onlineCount = 0; let error: string | undefined; try { const health = await input.fetchImpl(`${base}/health`); const body = await readJson<{ ok?: boolean; agents?: number }>(health); healthOk = health.ok && body.ok === true; onlineCount = Number.isInteger(body.agents) ? body.agents as number : 0; } catch { error = "hub_unreachable"; } try { const ready = await input.fetchImpl(`${base}/ready`); const body = await readJson<{ ok?: boolean; storage?: string }>(ready); readyOk = ready.ok && body.ok === true; storage = body.storage; } catch { error = error ?? "hub_unreachable"; } let local = loadLocalMeshSnapshot(input.dataPath, input.stateDir, { env: input.env ?? process.env }); if (useOpsStream) { try { const ops = await input.fetchImpl(`${base}/v1/ops/snapshot?project=${encodeURIComponent(input.project)}`, { headers: headers(), }); if (ops.ok) { const body = await readJson<{ agents: AgentRecord[]; openMessages: MeshTuiSnapshot["openMessages"]; openMessageTotal: number; runs: MeshTuiSnapshot["runs"]; runTotal: number; plans?: MeshTuiPlan[]; }>(ops); local = { ...local, agents: body.agents, openMessages: body.openMessages, openMessageTotal: body.openMessageTotal, runs: body.runs, runTotal: body.runTotal, plans: body.plans ?? local.plans, }; } else if (ops.status === 401 || ops.status === 403 || ops.status === 404 || ops.status === 503) { useOpsStream = false; } else { error = error ?? `ops_snapshot_http_${ops.status}`; } } catch { error = error ?? "ops_snapshot_unreachable"; } } if (!useOpsStream && identity) { try { // Offline members too: the ops snapshot lists the whole project, and // the legacy path is a fallback for the same screen, not a narrower one. const listed = await input.fetchImpl(`${base}/v1/agents?includeOffline=true`, { headers: headers(identity) }); if (listed.ok) { const body = await readJson<{ agents: AgentRecord[] }>(listed); const byId = new Map(local.agents.map((agent) => [agent.id, agent])); for (const agent of body.agents) byId.set(agent.id, agent); local = { ...local, agents: [...byId.values()].sort((a, b) => Number(b.online) - Number(a.online) || a.name.localeCompare(b.name)), }; } } catch { error = error ?? "agents_unreachable"; } } return { serverUrl: input.serverUrl, healthOk, readyOk, ...(storage ? { storage } : {}), onlineCount, transport, metadataMode: useOpsStream ? "ops" : "legacy", fetchedAt, ...(error ? { error } : {}), ...local, ...extra, }; }; const registerObserver = async () => { if (identity) return; const registration = await input.fetchImpl(`${base}/v1/agents/register`, { method: "POST", headers: headers(), body: JSON.stringify({ name, purpose: "Read-only mesh observer TUI", project: input.project, model: "tui", }), }); if (!registration.ok) throw new Error(`observer registration failed with HTTP ${registration.status}`); const registered = await readJson<{ agent: AgentRecord; agentKey: string }>(registration); identity = { id: registered.agent.id, key: registered.agentKey }; }; const unregister = async () => { if (!identity) return; const registeredIdentity = identity; identity = undefined; await input.fetchImpl(`${base}/v1/agents/${encodeURIComponent(registeredIdentity.id)}`, { method: "DELETE", headers: headers(registeredIdentity), }).catch(() => undefined); }; try { let snapshot = await snapshotFromHub("sse"); if (!useOpsStream) { try { await registerObserver(); } catch (error) { const failed = await snapshotFromHub("snapshot", { error: error instanceof Error ? error.message : "observer registration failed", }); paint(failed); return 1; } snapshot = await snapshotFromHub("sse"); } if (!tty) { paint(snapshot); return snapshot.healthOk ? 0 : 1; } const abort = new AbortController(); const onAbort = () => abort.abort(); input.abort?.addEventListener("abort", onAbort); if (input.abort?.aborted) abort.abort(); const terminal = input.terminal ?? new ProcessTerminal(); const tui = new TuiAltScreen(terminal, false, undefined, { mouse: true }); const onAction = async (action: DashboardAction, currentSnapshot: MeshTuiSnapshot, currentView: MeshTuiView) => { let targetId: string | undefined; if (currentView.tab === "workflows" || currentView.tab === "tasks") { const runs = currentView.tab === "tasks" ? currentSnapshot.runs.filter((r) => r.status === "running" || r.status === "waiting") : currentSnapshot.runs; targetId = runs[currentView.selected]?.id; } else if (currentView.tab === "inbox") { targetId = currentSnapshot.openMessages[currentView.selected]?.id; } if (!targetId) return; try { if (action.action === "cancel") { await input.fetchImpl(`${base}/v1/runs/${encodeURIComponent(targetId)}/cancel?project=${encodeURIComponent(input.project)}`, { method: "POST", headers: headers(), signal: abort.signal, }); } else if (action.action === "approve" || action.action === "signal") { await input.fetchImpl(`${base}/v1/runs/${encodeURIComponent(targetId)}/signal?project=${encodeURIComponent(input.project)}`, { method: "POST", headers: headers(), body: JSON.stringify({ signalKey: "operator-approval", status: "passed", summary: "Interactive operator approval from kxm dash" }), signal: abort.signal, }); } else if (action.action === "reject") { await input.fetchImpl(`${base}/v1/runs/${encodeURIComponent(targetId)}/signal?project=${encodeURIComponent(input.project)}`, { method: "POST", headers: headers(), body: JSON.stringify({ signalKey: "operator-approval", status: "failed", summary: "Rejected by operator in kxm dash" }), signal: abort.signal, }); } else if (action.action === "degrade") { await input.fetchImpl(`${base}/v1/runs/${encodeURIComponent(targetId)}/signal?project=${encodeURIComponent(input.project)}`, { method: "POST", headers: headers(), body: JSON.stringify({ signalKey: "operator-degrade", status: "degraded", summary: "Degraded to operator worktree from kxm dash" }), signal: abort.signal, }); const repoRoot = process.cwd(); const degradeRes = spawnDegradeWorktree(repoRoot, targetId, { description: "operator-degrade", }); if (dashboard) { if (degradeRes.ok) { dashboard.setStatusMessage( `[DEGRADE] Worktree: ${degradeRes.worktreePath} (cd command copied to clipboard)`, ); } else { dashboard.setStatusMessage( `[DEGRADE] Signal sent, worktree failed: ${degradeRes.error}`, ); } } } } catch { // fail-soft on dashboard network errors during key press } }; const dashboard = new KxmDashboard( snapshot, view, process.env.NO_COLOR === undefined, () => tui.requestRender(), () => abort.abort(), () => terminal.columns, onAction, ); interactive = { tui, dashboard }; tui.setLayoutRoot(dashboard.root); tui.setFocus(dashboard); tui.start(); const applyPresence = (agent: AgentRecord) => { const byId = new Map(snapshot.agents.map((row) => [row.id, row])); byId.set(agent.id, agent); const { error: _staleError, ...healthySnapshot } = snapshot; snapshot = { ...healthySnapshot, transport: "sse", fetchedAt: (input.now?.() ?? new Date()).toISOString(), agents: [...byId.values()].sort((a, b) => Number(b.online) - Number(a.online) || a.name.localeCompare(b.name)), onlineCount: [...byId.values()].filter((row) => row.online).length, }; }; try { while (!abort.signal.aborted) { let events: Response; try { if (!useOpsStream && !identity) throw new Error("legacy SSE requires an observer identity"); const eventUrl = useOpsStream ? `${base}/v1/ops/events?project=${encodeURIComponent(input.project)}` : `${base}/v1/events?agentId=${encodeURIComponent(identity!.id)}&presenceOnly=true`; events = await input.fetchImpl(eventUrl, { headers: { ...headers(useOpsStream ? undefined : identity!), accept: "text/event-stream" }, signal: abort.signal, }); } catch (error) { if (abort.signal.aborted) break; snapshot = await snapshotFromHub("snapshot", { error: error instanceof Error ? `sse_${error.message}` : "sse_unreachable" }); paint(snapshot); await waitForReconnect(abort.signal, input.reconnectMs); continue; } if (useOpsStream && (events.status === 401 || events.status === 403 || events.status === 404 || events.status === 503)) { useOpsStream = false; try { await registerObserver(); } catch { snapshot = await snapshotFromHub("snapshot", { error: "live metadata access was lost and legacy observer registration failed", }); paint(snapshot); break; } snapshot = await snapshotFromHub("snapshot", { error: "admin metadata stream unavailable; using legacy presence and local snapshots", }); paint(snapshot); continue; } if (!useOpsStream && events.ok && events.headers.get("x-kxm-events-mode") !== "presence") { await events.body?.cancel(); snapshot = await snapshotFromHub("snapshot", { error: "hub does not support metadata-only presence SSE; live fallback disabled", }); paint(snapshot); break; } if (!events.ok || !events.body) { snapshot = await snapshotFromHub("snapshot", { error: `sse_http_${events.status}` }); paint(snapshot); await waitForReconnect(abort.signal, input.reconnectMs); continue; } if (snapshot.error || snapshot.transport !== "sse") { const { error: _staleError, ...healthySnapshot } = snapshot; snapshot = { ...healthySnapshot, transport: "sse", fetchedAt: (input.now?.() ?? new Date()).toISOString(), }; paint(snapshot); } const reader = events.body.getReader(); const decoder = new TextDecoder(); let pending = ""; while (!abort.signal.aborted) { const { done, value } = await reader.read(); if (done) break; pending += decoder.decode(value, { stream: true }); const parts = pending.split("\n\n"); pending = parts.pop() ?? ""; for (const part of parts) { if (part.startsWith(":")) continue; const lines = part.split("\n"); const eventLine = lines.find((line) => line.startsWith("event:")); if (!useOpsStream && eventLine?.slice(6).trim() !== "presence") continue; const dataLine = lines.find((line) => line.startsWith("data:")); if (!dataLine) continue; let parsed: { type?: "ops" | "presence"; agent?: AgentRecord; project?: string; topic?: string; at?: string }; try { parsed = JSON.parse(dataLine.slice(5).trim()) as typeof parsed; } catch { continue; } if ("type" in parsed && parsed.type === "ops") { snapshot = await snapshotFromHub("sse"); paint(snapshot); } else if ("type" in parsed && parsed.type === "presence" && parsed.agent) { applyPresence(parsed.agent); const local = loadLocalMeshSnapshot(input.dataPath, input.stateDir, { env: input.env ?? process.env }); snapshot = { ...snapshot, openMessages: local.openMessages, openMessageTotal: local.openMessageTotal, runs: local.runs, runTotal: local.runTotal, pids: local.pids, spend: local.spend, source: local.source, }; paint(snapshot); } } } if (!abort.signal.aborted) { snapshot = await snapshotFromHub("snapshot", { error: "sse_reconnecting" }); paint(snapshot); await waitForReconnect(abort.signal, input.reconnectMs); } } } catch (error) { if (!abort.signal.aborted) throw error; } finally { input.abort?.removeEventListener("abort", onAbort); tui.stop(); interactive = undefined; } return 0; } catch (error) { const failed = await snapshotFromHub("snapshot", { error: error instanceof Error ? error.message : "tui_failed", }); paint(failed); return 1; } finally { await unregister(); } }