@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
692 lines (633 loc) • 24.8 kB
text/typescript
import { spawn } from "node:child_process";
import { join } from "node:path";
import { StringDecoder } from "node:string_decoder";
import {
registerTrustedProducer,
type KxmProducer,
type KxmProducerRequest,
type KxmProducerResult,
} from "./engine.ts";
import {
probeHarnessAssignmentAsync,
type HarnessAssignmentProbeOptions,
type HarnessInventory,
type HarnessStatus,
} from "./harness.ts";
import {
calculateModelCost,
loadPriceCatalogForEstimate,
type PriceCatalog,
} from "./prices.ts";
export interface PiRpcProcess {
readonly stdin: { write(chunk: string): boolean };
readonly stdout: NodeJS.ReadableStream;
readonly stderr?: NodeJS.ReadableStream | undefined;
kill(signal?: NodeJS.Signals): boolean;
on(event: "close", listener: (code: number | null, signal: string | null) => void): this;
on(event: "error", listener: (err: Error) => void): this;
}
export interface PiUsageStats {
input?: number | undefined;
output?: number | undefined;
cacheRead?: number | undefined;
cacheWrite?: number | undefined;
totalTokens?: number | undefined;
contextUsage?: {
tokens?: number | undefined;
contextWindow?: number | undefined;
percent?: number | undefined;
} | undefined;
}
export interface PiPromptResult {
outcome: string;
text: string;
usage?: PiUsageStats | undefined;
}
export function formatPiSessionKey(params: {
runId: string;
agentId: string;
instanceNo?: number | undefined;
scopeEpoch?: number | undefined;
}): string {
if (params.agentId === "coordinator") {
return `coordinator:${params.runId}`;
}
return `${params.runId}:${params.agentId}:${params.instanceNo ?? 1}:${params.scopeEpoch ?? 1}`;
}
export function formatPiSessionDisplayName(params: {
runId: string;
agentId: string;
instanceNo?: number | undefined;
scopeEpoch?: number | undefined;
}): string {
const shortRun = params.runId.length > 8 ? params.runId.slice(0, 8) : params.runId;
if (params.agentId === "coordinator") {
return `coordinator@${shortRun}`;
}
return `${params.agentId}@${shortRun}#${params.instanceNo ?? 1}.${params.scopeEpoch ?? 1}`;
}
function extractText(content: unknown): string {
if (typeof content === "string") return content;
if (Array.isArray(content)) {
return content
.map((part) => (typeof part === "object" && part && "text" in part && typeof (part as { text: unknown }).text === "string" ? (part as { text: string }).text : ""))
.join("\n");
}
return "";
}
function asJsonObject(text: string): Record<string, unknown> | undefined {
try {
const parsed: unknown = JSON.parse(text);
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed as Record<string, unknown> : undefined;
} catch {
return undefined;
}
}
function outcomeField(value: Record<string, unknown>): string | undefined {
const outcome = value.outcome;
return typeof outcome === "string" ? outcome : undefined;
}
/**
* The result a reply actually declares, or `undefined` when it declares none.
*
* Two shapes count: the whole reply is one JSON object, or a **standalone** result object on a
* line of its own — and when there is more than one of those, the **last** one wins, because
* that is where a reply puts its answer after showing an example. If anything in the tail after
* that declaration still looks like an outcome key, the reply is **ambiguous and settles
* `failed`**. What is deliberately not accepted: an outcome *word* anywhere in prose, and an
* object embedded mid-sentence, so
* `Example: {"outcome": "passed"}. Actual result: {"outcome": "failed"}` declares nothing at
* all and settles as `failed` rather than letting the illustration outrank the answer.
*/
function declaredOutcomeOf(text: string): string | undefined {
const trimmed = text.trim();
if (!trimmed) return undefined;
const whole = asJsonObject(trimmed);
if (whole) return outcomeField(whole);
const lines = trimmed.split(/\r?\n/);
let declaration: { index: number; outcome: string } | undefined;
for (let index = 0; index < lines.length; index += 1) {
const outcome = outcomeField(asJsonObject(lines[index]!.trim()) ?? {});
if (outcome !== undefined) declaration = { index, outcome };
}
if (!declaration) return undefined;
// Ambiguity after the declaration fails closed. Anything in the tail that still looks like
// an outcome key — an inline `Actual result: {"outcome": "failed"}` on the next line, a
// pretty-printed object, or a second mention — means we cannot tell which one the reply is
// reporting, and guessing is exactly the behaviour this function exists to remove. Trailing
// prose that says nothing about outcomes is fine, which is what lets a real reply put its
// usage or sign-off after the result block.
const tail = lines.slice(declaration.index + 1).join("\n");
if (/"outcome"\s*:/.test(tail)) return undefined;
return declaration.outcome;
}
function determineOutcome(text: string, allowedOutcomes: readonly string[]): string {
// Structured result only. What does **not** count: an outcome *word* anywhere in the text —
// "the tests did not pass" used to settle a step as `passed` — and an empty or unstructured
// reply, which used to default to success. Undeclared here means `failed`; if the step does
// not declare `failed` the engine records `outcome_unknown` and terminates as `failed` anyway.
const declared = declaredOutcomeOf(text);
return declared !== undefined && allowedOutcomes.includes(declared) ? declared : "failed";
}
export class PiSession {
readonly key: string;
readonly displayName: string;
readonly runId: string;
readonly agentId: string;
readonly instanceNo: number;
readonly scopeEpoch: number;
readonly sessionDir?: string | undefined;
readonly model?: string | undefined;
private process: PiRpcProcess;
private status: "idle" | "busy" | "closed" = "idle";
private commandCounter = 0;
private pendingCommands = new Map<string, { resolve: (res: Record<string, unknown>) => void; reject: (err: Error) => void }>();
private activePrompt?: {
resolve: (res: PiPromptResult) => void;
reject: (err: Error) => void;
allowedOutcomes: readonly string[];
text: string;
usage: PiUsageStats;
signal?: AbortSignal | undefined;
signalCleanup?: (() => void) | undefined;
aborted?: boolean | undefined;
} | undefined;
private lineDecoder = new StringDecoder("utf8");
private lineBuffer = "";
constructor(params: {
key: string;
displayName: string;
runId: string;
agentId: string;
instanceNo: number;
scopeEpoch: number;
sessionDir?: string | undefined;
model?: string | undefined;
process: PiRpcProcess;
}) {
this.key = params.key;
this.displayName = params.displayName;
this.runId = params.runId;
this.agentId = params.agentId;
this.instanceNo = params.instanceNo;
this.scopeEpoch = params.scopeEpoch;
this.sessionDir = params.sessionDir;
this.model = params.model;
this.process = params.process;
this.attachStdout();
this.attachProcessEvents();
}
isClosed(): boolean {
return this.status === "closed";
}
isBusy(): boolean {
return this.status === "busy";
}
private attachStdout(): void {
this.process.stdout.on("data", (chunk: Buffer | string) => {
this.lineBuffer += this.lineDecoder.write(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
let newlineIdx: number;
while ((newlineIdx = this.lineBuffer.indexOf("\n")) !== -1) {
let line = this.lineBuffer.slice(0, newlineIdx);
this.lineBuffer = this.lineBuffer.slice(newlineIdx + 1);
if (line.endsWith("\r")) line = line.slice(0, -1);
const trimmed = line.trim();
if (!trimmed) continue;
try {
const parsed = JSON.parse(trimmed) as Record<string, unknown>;
this.handleRpcMessage(parsed);
} catch {
// ignore non-JSON output from subprocess
}
}
});
}
private attachProcessEvents(): void {
this.process.on("close", (code, signal) => {
this.status = "closed";
const err = new Error(`pi_process_closed: code=${code ?? "null"} signal=${signal ?? "null"}`);
for (const pending of this.pendingCommands.values()) {
pending.reject(err);
}
this.pendingCommands.clear();
if (this.activePrompt) {
this.activePrompt.signalCleanup?.();
this.activePrompt.reject(err);
this.activePrompt = undefined;
}
});
this.process.on("error", (err) => {
this.status = "closed";
for (const pending of this.pendingCommands.values()) {
pending.reject(err);
}
this.pendingCommands.clear();
if (this.activePrompt) {
this.activePrompt.signalCleanup?.();
this.activePrompt.reject(err);
this.activePrompt = undefined;
}
});
}
private handleRpcMessage(msg: Record<string, unknown>): void {
if (msg.type === "response" && typeof msg.id === "string") {
const pending = this.pendingCommands.get(msg.id);
if (pending) {
this.pendingCommands.delete(msg.id);
if (msg.success === false) {
pending.reject(new Error(typeof msg.error === "string" ? msg.error : "rpc_command_failed"));
} else {
pending.resolve(msg);
}
}
return;
}
if (!this.activePrompt) return;
if (msg.type === "message_update") {
if (msg.usage && typeof msg.usage === "object") {
this.updateUsage(msg.usage as Record<string, unknown>);
}
} else if (msg.type === "turn_end") {
const message = msg.message as Record<string, unknown> | undefined;
if (message && message.content !== undefined) {
this.activePrompt.text += `${extractText(message.content)}\n`;
}
if (message && message.usage && typeof message.usage === "object") {
this.updateUsage(message.usage as Record<string, unknown>);
}
} else if (msg.type === "agent_end") {
const messages = msg.messages;
if (Array.isArray(messages)) {
for (const m of messages) {
if (m && typeof m === "object" && (m as Record<string, unknown>).role === "assistant") {
const content = (m as Record<string, unknown>).content;
if (content) this.activePrompt.text += `${extractText(content)}\n`;
const usage = (m as Record<string, unknown>).usage;
if (usage && typeof usage === "object") this.updateUsage(usage as Record<string, unknown>);
}
}
}
} else if (msg.type === "agent_settled") {
const active = this.activePrompt;
this.activePrompt = undefined;
this.status = "idle";
active.signalCleanup?.();
const isAborted = active.aborted || active.signal?.aborted;
let outcome: string;
if (isAborted) {
// A cancel is a cancel. The old chain fell through to `allowedOutcomes[0]` when the
// step declared neither `cancelled` nor `failed`, so aborting a step whose only
// declared outcome was `passed` reported `passed`. Returning `cancelled` instead lets
// the engine record `outcome_unknown` and terminate `failed` — never a borrowed success.
outcome = "cancelled";
} else {
outcome = determineOutcome(active.text, active.allowedOutcomes);
}
active.resolve({
outcome,
text: active.text.trim() || (isAborted ? "aborted" : ""),
usage: active.usage,
});
}
}
private updateUsage(raw: Record<string, unknown>): void {
if (!this.activePrompt) return;
const u = this.activePrompt.usage;
if (typeof raw.input === "number") u.input = raw.input;
if (typeof raw.output === "number") u.output = raw.output;
if (typeof raw.cacheRead === "number") u.cacheRead = raw.cacheRead;
if (typeof raw.cacheWrite === "number") u.cacheWrite = raw.cacheWrite;
if (typeof raw.totalTokens === "number") u.totalTokens = raw.totalTokens;
if (raw.contextUsage && typeof raw.contextUsage === "object") {
const cu = raw.contextUsage as Record<string, unknown>;
u.contextUsage = {
tokens: typeof cu.tokens === "number" ? cu.tokens : undefined,
contextWindow: typeof cu.contextWindow === "number" ? cu.contextWindow : undefined,
percent: typeof cu.percent === "number" ? cu.percent : undefined,
};
}
}
async sendCommand(command: Record<string, unknown>): Promise<Record<string, unknown>> {
if (this.status === "closed") {
throw new Error("pi_session_closed");
}
const id = `cmd_${++this.commandCounter}`;
const payload = { ...command, id };
return new Promise((resolve, reject) => {
this.pendingCommands.set(id, { resolve, reject });
try {
this.process.stdin.write(JSON.stringify(payload) + "\n");
} catch (err) {
this.pendingCommands.delete(id);
reject(err instanceof Error ? err : new Error(String(err)));
}
});
}
async prompt(
message: string,
allowedOutcomes: readonly string[],
signal?: AbortSignal,
): Promise<PiPromptResult> {
if (this.status === "closed") {
throw new Error("pi_session_closed");
}
if (this.status === "busy") {
throw new Error("pi_session_busy");
}
if (signal?.aborted) {
// Same rule as the in-flight abort: never fall back to the first declared outcome.
return { outcome: "cancelled", text: "aborted", usage: {} };
}
this.status = "busy";
return new Promise((resolve, reject) => {
let signalCleanup: (() => void) | undefined;
if (signal) {
const onAbort = () => {
if (this.activePrompt) {
this.activePrompt.aborted = true;
}
void this.abort().catch(() => {});
};
signal.addEventListener("abort", onAbort, { once: true });
signalCleanup = () => signal.removeEventListener("abort", onAbort);
}
this.activePrompt = {
resolve,
reject,
allowedOutcomes,
text: "",
usage: {},
signal,
signalCleanup,
aborted: false,
};
this.sendCommand({ type: "prompt", message }).catch((err) => {
if (this.activePrompt) {
this.activePrompt.signalCleanup?.();
this.activePrompt = undefined;
this.status = "idle";
}
reject(err);
});
});
}
async abort(): Promise<void> {
if (this.status === "closed") return;
if (this.activePrompt) {
this.activePrompt.aborted = true;
}
try {
await this.sendCommand({ type: "abort" });
} catch {
// ignore abort write errors
}
}
async close(): Promise<void> {
if (this.status === "closed") return;
this.status = "closed";
try {
this.process.kill();
} catch {
// ignore kill errors
}
}
}
export interface KxmPiProducerOptions {
projectRoot?: string | undefined;
sessionDir?: string | undefined;
piCommand?: string | undefined;
inventory?: HarnessInventory | undefined;
priceCatalog?: PriceCatalog | undefined;
env?: NodeJS.ProcessEnv | undefined;
spawnProcess?: ((command: string, args: readonly string[], options: Record<string, unknown>) => PiRpcProcess) | undefined;
probeHarness?: ((options: Omit<HarnessAssignmentProbeOptions, "runCommand"> & { signal?: AbortSignal | undefined }) => HarnessStatus | Promise<HarnessStatus>) | undefined;
resolveModel?: ((agentId: string, runId: string) => { provider?: string | undefined; model?: string | undefined; thinking?: string | undefined } | undefined) | undefined;
defaultModel?: string | undefined;
defaultProvider?: string | undefined;
}
export interface KxmPiProducer extends KxmProducer {
readonly id: "pi";
readonly sessions: ReadonlyMap<string, PiSession>;
produce(request: KxmProducerRequest): Promise<KxmProducerResult>;
close(): Promise<void>;
closeRun(runId: string): Promise<void>;
}
export function createKxmPiProducer(options: KxmPiProducerOptions = {}): KxmPiProducer {
const sessions = new Map<string, PiSession>();
const defaultModel = options.defaultModel ?? "xai/grok-4.6";
const defaultProvider = options.defaultProvider ?? "xai";
function parseModelString(spec: string): { provider: string; model: string } {
const trimmed = spec.trim();
if (trimmed.includes("/")) {
const idx = trimmed.indexOf("/");
return { provider: trimmed.slice(0, idx).toLowerCase(), model: trimmed.slice(idx + 1) };
}
return { provider: defaultProvider, model: trimmed };
}
function resolveModelForRequest(request: KxmProducerRequest): { provider: string; model: string; thinking?: string | undefined } {
if (request.model) {
// A separate provider field means the model id is bare and may itself be
// namespaced (`qwen/qwen3.7-plus` under `openrouter`); reparsing it as a
// provider/model selector strips the namespace and sends the wrong model. Only
// parse when the provider must come from the string itself.
if (request.provider) {
return { provider: request.provider.toLowerCase(), model: request.model, thinking: request.thinking };
}
const parsed = parseModelString(request.model);
return {
provider: request.provider?.toLowerCase() ?? parsed.provider,
model: parsed.model,
thinking: request.thinking,
};
}
if (options.resolveModel) {
const resolved = options.resolveModel(request.agentId, request.runId);
if (resolved && resolved.model) {
if (resolved.provider) {
return { provider: resolved.provider.toLowerCase(), model: resolved.model, thinking: resolved.thinking };
}
const parsed = parseModelString(resolved.model);
return {
provider: resolved.provider?.toLowerCase() ?? parsed.provider,
model: parsed.model,
thinking: resolved.thinking,
};
}
}
const parsed = parseModelString(defaultModel);
return { provider: parsed.provider, model: parsed.model };
}
async function checkPiAuth(provider: string, model: string, signal: AbortSignal): Promise<void> {
if (options.inventory) {
const piEntry = options.inventory.harnesses.find((h) => h.id === "pi");
if (!piEntry || !piEntry.detected || piEntry.authenticated === false) {
throw new Error("pi_not_authenticated: pi harness not authenticated in inventory");
}
}
const probeFn = options.probeHarness ?? probeHarnessAssignmentAsync;
const probe = await probeFn({
harness: "pi",
provider,
model,
env: options.env,
signal,
timeoutMs: 10_000,
});
if (!probe.detected) {
throw new Error(`pi_not_authenticated: pi harness not detected (${probe.issues.join(", ")})`);
}
if (probe.authenticated !== true) {
throw new Error(`pi_not_authenticated: pi not authenticated for provider ${provider} (${probe.issues.join(", ")})`);
}
}
async function getOrCreateSession(request: KxmProducerRequest, model: string): Promise<PiSession> {
const key = formatPiSessionKey({
runId: request.runId,
agentId: request.agentId,
instanceNo: request.instanceNo,
scopeEpoch: request.scopeEpoch,
});
const existing = sessions.get(key);
if (existing && !existing.isClosed()) {
return existing;
}
const displayName = formatPiSessionDisplayName({
runId: request.runId,
agentId: request.agentId,
instanceNo: request.instanceNo,
scopeEpoch: request.scopeEpoch,
});
const command = options.piCommand ?? "pi";
const args = ["--mode", "rpc", "--name", displayName];
if (options.sessionDir) {
const safeDirName = key.replace(/[^a-zA-Z0-9_-]/g, "_");
args.push("--session-dir", join(options.sessionDir, request.runId, safeDirName));
}
if (model) {
args.push("--model", model);
}
let proc: PiRpcProcess;
if (options.spawnProcess) {
proc = options.spawnProcess(command, args, { env: options.env ?? process.env });
} else {
const child = spawn(command, args, {
env: options.env ?? process.env,
stdio: ["pipe", "pipe", "pipe"],
});
proc = {
stdin: child.stdin!,
stdout: child.stdout!,
stderr: child.stderr ?? undefined,
kill: (sig) => child.kill(sig),
on: (ev: any, listener: any) => {
child.on(ev, listener);
return proc;
},
};
}
const session = new PiSession({
key,
displayName,
runId: request.runId,
agentId: request.agentId,
instanceNo: request.instanceNo ?? 1,
scopeEpoch: request.scopeEpoch ?? 1,
sessionDir: options.sessionDir,
model,
process: proc,
});
sessions.set(key, session);
return session;
}
const producer: KxmPiProducer = {
id: "pi",
get sessions(): ReadonlyMap<string, PiSession> {
return sessions;
},
async produce(request: KxmProducerRequest): Promise<KxmProducerResult> {
const startTime = Date.now();
const resolved = resolveModelForRequest(request);
// Preflight auth check - fails closed via the async bounded probe
await checkPiAuth(resolved.provider, resolved.model, request.signal);
// Get or create session
const session = await getOrCreateSession(request, resolved.model);
const promptMessage = request.prompt
?? `Execute step ${request.stepId} (attempt ${request.stepAttempt}) for agent ${request.agentId}. Allowed outcomes: ${request.allowedOutcomes.join(", ")}.`;
let promptResult: PiPromptResult;
try {
promptResult = await session.prompt(promptMessage, request.allowedOutcomes, request.signal);
} catch (err) {
return Promise.reject(err);
}
const latencyMs = Math.max(0, Date.now() - startTime);
const usage = promptResult.usage;
const tokensIn = typeof usage?.input === "number" ? usage.input : null;
const tokensOut = typeof usage?.output === "number" ? usage.output : null;
const cacheReadTokens = typeof usage?.cacheRead === "number" ? usage.cacheRead : null;
const cacheWriteTokens = typeof usage?.cacheWrite === "number" ? usage.cacheWrite : null;
const contextTokens = typeof usage?.contextUsage?.tokens === "number" ? usage.contextUsage.tokens : tokensIn;
// Catalog errors must not discard observed usage or crash settlement.
const providerMetadata: Record<string, string | number | boolean> = {};
const loaded = loadPriceCatalogForEstimate({
priceCatalog: options.priceCatalog,
projectRoot: options.projectRoot,
});
if (loaded.unavailable) providerMetadata.priceCatalogUnavailable = true;
if (loaded.stale) providerMetadata.priceCatalogStale = true;
const catalog = loaded.catalog;
const costBasis = "unknown" as const;
const costUsd = null;
if (catalog) {
const calculated = calculateModelCost(catalog, {
model: resolved.model,
provider: resolved.provider,
tokensIn,
tokensOut,
cacheReadTokens,
cacheWriteTokens,
contextTokens,
});
if (calculated && Number.isFinite(calculated.costUsd)) {
providerMetadata.listCostUsd = calculated.costUsd;
providerMetadata.listPriceRef = calculated.priceRef;
providerMetadata.listPriceSha256 = catalog.sha256;
}
}
return {
outcome: promptResult.outcome,
costBasis,
costUsd,
tokensIn,
tokensOut,
cacheReadTokens,
cacheWriteTokens,
contextTokens,
latencyMs,
harness: "pi",
provider: resolved.provider,
requestedModel: resolved.model,
effectiveModel: resolved.model,
providerMetadata,
...(resolved.thinking !== undefined ? { thinking: resolved.thinking } : {}),
agentRole: request.agentRole ?? request.agentId,
};
},
async closeRun(runId: string): Promise<void> {
const toClose: PiSession[] = [];
for (const [key, session] of sessions.entries()) {
if (session.runId === runId) {
toClose.push(session);
sessions.delete(key);
}
}
await Promise.all(toClose.map((s) => s.close()));
},
async close(): Promise<void> {
const toClose = [...sessions.values()];
sessions.clear();
await Promise.all(toClose.map((s) => s.close()));
},
};
registerTrustedProducer(producer);
return producer;
}