import { spawnSync } from "node:child_process"; import { existsSync } from "node:fs"; import { win32 as win32Path } from "node:path"; import { defaultSpawn } from "./oneshot-process.ts"; export const DEFAULT_HARNESS = "pi"; export type HarnessMode = "headless" | "either"; export type HarnessUpdateScope = "self" | "extensions" | "models" | "all"; export interface HarnessCommandResult { ok: boolean; code: number | null; stdout: string; stderr: string; error?: string; } export type OneShotPromptVia = "stdin" | "arg"; export interface OneShotUsage { tokensIn?: number | null | undefined; tokensOut?: number | null | undefined; cacheReadTokens?: number | null | undefined; cacheWriteTokens?: number | null | undefined; contextTokens?: number | null | undefined; costUsd?: number | null | undefined; } export interface OneShotParsedOutput { text: string; usage?: OneShotUsage | undefined; outcome?: string | undefined; effectiveModel?: string | undefined; isError?: boolean | undefined; errorMessage?: string | undefined; } export interface HarnessOneShotConfig { argv: readonly string[]; promptVia: OneShotPromptVia; outputFormat: "json" | "text" | "stream-json" | "jsonl"; usageParser: (stdout: string, stderr: string, requestedModel?: string) => OneShotParsedOutput; } export interface HarnessCatalogEntry { id: string; label: string; default: boolean; mode: HarnessMode; commands: readonly string[]; versionArgs: readonly string[]; authArgs?: readonly string[]; update: { self: readonly string[]; extensions?: readonly string[]; models?: readonly string[]; }; oneShot?: HarnessOneShotConfig | undefined; } export type HarnessRunCommand = (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult; export type HarnessRunCommandAsync = (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult | Promise; export interface HarnessProbeOptions { defaultHarness?: string | undefined; env?: NodeJS.ProcessEnv | undefined; runCommand?: HarnessRunCommand | undefined; timeoutMs?: number | undefined; platform?: NodeJS.Platform | undefined; existsSync?: ((path: string) => boolean) | undefined; } export interface AsyncHarnessProbeOptions extends Omit { signal?: AbortSignal | undefined; runCommand?: HarnessRunCommandAsync | undefined; } export interface HarnessDispatchStatus { status: "yes" | "no"; supported: boolean; reason?: string | undefined; } export interface HarnessStatus { id: string; label: string; default: boolean; mode: HarnessMode; detected: boolean; authenticated: boolean | null; authMethod?: "claude.ai" | "ChatGPT" | "api-key" | "antigravity-oauth" | undefined; dispatch?: HarnessDispatchStatus | undefined; command?: string; version?: string; canUpdate: { self: boolean; extensions: boolean; models: boolean }; issues: readonly string[]; } export interface HarnessInventory { defaultHarness: string; harnesses: readonly HarnessStatus[]; } const UNKNOWN_AUTH_HARNESSES = new Set(["deepseek"]); const GROK_LOGIN_LINE = "You are logged in with grok.com."; /** Tab-separated `idlabel` rows from the committed `agy models` probe. */ const AGY_MODEL_ROW = /^[a-z0-9]+(?:[._-][a-z0-9]+)+\s+\S/im; const CODEX_CHATGPT_LINE = "Logged in using ChatGPT"; const CODEX_API_KEY_PREFIX = "Logged in using an API key"; const CODEX_NEGATIVE_LINE = "Not logged in"; /** Providers that have a dedicated native harness rule. */ export const NATIVE_HARNESS_PROVIDERS: Readonly> = Object.freeze({ claude: "anthropic", codex: "openai", grok: "xai", agy: "google", kimi: "moonshot", deepseek: "deepseek", }); /** Allowlisted Pi aggregator provider prefixes. */ export const PI_ALLOWED_PROVIDERS: readonly string[] = Object.freeze([ "openrouter", "nous-portal", "nous", "nous-proxy", ]); /** Vendors with a native harness rule. Pi must not run their models under any provider id. */ export const PI_NATIVE_BRAKE_PROVIDERS: readonly string[] = Object.freeze([ "anthropic", "openai", "xai", "moonshot", "google", "deepseek", ]); /** Pi provider ids that are a braked vendor's own API or subscription under another name. */ const PI_VENDOR_OWNED_PROVIDERS: Readonly> = Object.freeze({ "openai-codex": "openai", moonshotai: "moonshot", "moonshotai-cn": "moonshot", "kimi-coding": "moonshot", "google-vertex": "google", // Claude subscription via the Agent SDK: experiment-only, never a product route. "claude-bridge": "anthropic", // The decided Google route, for Gemini ids only (see PI_ANTIGRAVITY_MODEL_ID). antigravity: "google", }); /** Aggregator spellings of braked vendors in `provider/vendor/model` ids (roster policy aliases). */ const PI_VENDOR_SEGMENT_ALIASES: Readonly> = Object.freeze({ "x-ai": "xai", moonshotai: "moonshot", "google-ai": "google", }); const PI_ANTIGRAVITY_MODEL_ID = /^gemini-[a-z0-9.-]+$/; export interface HarnessModelSpec { provider?: string | undefined; model?: string | undefined; } export interface HarnessModelValidation { valid: boolean; issue?: "harness_unknown" | "harness_unhosted_model" | "pi_native_impersonation_blocked"; message?: string; } export interface HarnessUpdateStep { harness: string; scope: Exclude; command: string; args: readonly string[]; outcome: "would" | "passed" | "failed" | "skipped"; code?: number | null; detail?: string; } function reportedModelId(value: unknown): string | undefined { return typeof value === "string" && /^[a-z0-9][a-z0-9._:/-]{0,199}$/i.test(value) ? value : undefined; } export function parseClaudeOneShotUsage(stdout: string, stderr: string, requestedModel?: string): OneShotParsedOutput { const trimmed = stdout.trim(); if (!trimmed) { return { text: "", isError: true, errorMessage: stderr.trim() || "empty stdout from claude", }; } try { const payload = JSON.parse(trimmed) as Record; const usage = (payload.usage as Record | undefined) ?? {}; const entries = payload.modelUsage && typeof payload.modelUsage === "object" && !Array.isArray(payload.modelUsage) ? Object.entries(payload.modelUsage as Record).filter(([, value]) => value && typeof value === "object") : []; const matches = entries.filter(([model]) => model === requestedModel || (requestedModel && (model.startsWith(`${requestedModel}-`) || model.startsWith(`claude-${requestedModel}-`)))); const selected = matches.length === 1 ? matches[0] : entries.length === 1 ? entries[0] : undefined; const modelUsageDetail = selected?.[1] as Record | undefined; const effectiveModel = reportedModelId(payload.model) ?? reportedModelId(selected?.[0]); // Top-level usage is the attempt total; an auxiliary model is not the task's model. const tokensIn = (typeof usage.input_tokens === "number" ? usage.input_tokens : undefined) ?? (typeof modelUsageDetail?.inputTokens === "number" ? modelUsageDetail.inputTokens : null); const tokensOut = (typeof usage.output_tokens === "number" ? usage.output_tokens : undefined) ?? (typeof modelUsageDetail?.outputTokens === "number" ? modelUsageDetail.outputTokens : null); const cacheReadTokens = (typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : undefined) ?? (typeof modelUsageDetail?.cacheReadInputTokens === "number" ? modelUsageDetail.cacheReadInputTokens : null); const cacheWriteTokens = (typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : undefined) ?? (typeof modelUsageDetail?.cacheCreationInputTokens === "number" ? modelUsageDetail.cacheCreationInputTokens : null); const costUsd = typeof payload.total_cost_usd === "number" ? payload.total_cost_usd : (typeof modelUsageDetail?.costUSD === "number" ? modelUsageDetail.costUSD : null); const isError = Boolean(payload.is_error || payload.error || ["error", "aborted", "failed", "max_tokens", "length"].includes(String(payload.stopReason ?? payload.stop_reason ?? ""))); const text = typeof payload.result === "string" ? payload.result : (typeof payload.text === "string" ? payload.text : ""); const errorMessage = isError ? (typeof payload.error === "string" ? payload.error : (typeof (payload.error as Record | undefined)?.message === "string" ? ((payload.error as Record).message as string) : text)) : undefined; return { text, effectiveModel, isError, errorMessage, usage: { tokensIn, tokensOut, cacheReadTokens, cacheWriteTokens, contextTokens: null, costUsd, }, }; } catch { return { text: trimmed, isError: true, errorMessage: "invalid Claude JSON response", usage: {}, }; } } /** Grok's observed one-shot JSON uses the same usage/modelUsage envelope. */ export function parseGrokOneShotUsage(stdout: string, stderr: string, requestedModel?: string): OneShotParsedOutput { return parseClaudeOneShotUsage(stdout, stderr, requestedModel); } export function parseCodexOneShotUsage(stdout: string, stderr: string): OneShotParsedOutput { const lines = stdout.split(/\r?\n/).map((l) => l.trim()).filter(Boolean); if (lines.length === 0) { return { text: "", isError: true, errorMessage: stderr.trim() || "empty stdout from codex", }; } const events: Record[] = []; for (const line of lines) { try { const parsed = JSON.parse(line); if (parsed && typeof parsed === "object") events.push(parsed as Record); } catch { // non-json line ignored } } const messageTexts: string[] = []; let usage: Record | undefined; let hasError = false; let errorMessage: string | undefined; for (const ev of events) { if (ev.type === "turn.failed" || ev.type === "error") { hasError = true; errorMessage = typeof ev.message === "string" ? ev.message : (typeof (ev.error as Record | undefined)?.message === "string" ? ((ev.error as Record).message as string) : "turn_failed"); } if (ev.type === "item.completed" && ev.item && typeof ev.item === "object" && (ev.item as Record).type === "agent_message") { const t = (ev.item as Record).text; if (typeof t === "string") messageTexts.push(t); } if (ev.type === "turn.completed" && ev.usage && typeof ev.usage === "object") { usage = ev.usage as Record; } } const text = (messageTexts.at(-1) ?? "").trim(); if (!events.some((event) => event.type === "turn.completed") || !text) hasError = true; const tokensIn = typeof usage?.input_tokens === "number" ? usage.input_tokens : null; const tokensOut = typeof usage?.output_tokens === "number" ? usage.output_tokens : null; const cacheReadTokens = typeof usage?.cached_input_tokens === "number" ? usage.cached_input_tokens : null; const cacheWriteTokens = typeof usage?.cache_write_input_tokens === "number" ? usage.cache_write_input_tokens : null; return { text, isError: hasError, errorMessage, usage: { tokensIn, tokensOut, cacheReadTokens, cacheWriteTokens, contextTokens: null, costUsd: null, }, }; } /** * Parse pi's `--mode json` output: one NDJSON event object per line. The reply text is * the text parts of the final assistant message (`message_end`/`turn_end` carry it * complete, after the deltas); usage and the effective model ride the same message. * Anything unparseable falls back to the raw stream so a shape change is visible in the * recorded text rather than silently empty. */ export function parsePiOneShotUsage(stdout: string, _stderr: string): OneShotParsedOutput { // Select the FINAL assistant message first, then extract every field from that one // message atomically. Carrying text, usage or model forward from an earlier message // let a truncated or empty final turn inherit a previous turn's PASS text and usage. let finalMessage: Record | undefined; for (const line of stdout.split("\n")) { const trimmed = line.trim(); if (!trimmed.startsWith("{")) continue; try { const event = JSON.parse(trimmed) as Record; if (event.type !== "message_end" && event.type !== "turn_end") continue; const message = event.message as Record | undefined; if (!message || message.role !== "assistant") continue; finalMessage = message; } catch { // skip malformed lines; the stream is append-only and a partial line means truncation } } if (!finalMessage) { // No assistant message at all: fail closed with empty text. Returning the raw stream // would hand back thinking deltas or diagnostics as if they were the answer. return { text: "", isError: true, usage: {} }; } const content = Array.isArray(finalMessage.content) ? finalMessage.content : []; const text = content .map((part) => (part && typeof part === "object" && (part as Record).type === "text" ? String((part as Record).text ?? "") : "")) .filter((part) => part.length > 0) .join("\n"); // pi can exit zero on a failed turn; the stop reason is the fact that matters. // Metadata comes from the selected final message even on a failed turn: usage and // model are facts about what ran, not claims about the outcome, and dropping them // turned a real 7/3-token failure into null/null with model "unknown". const effectiveModel = reportedModelId(finalMessage.model); const rawUsage = finalMessage.usage as Record | undefined; const usage: OneShotUsage = rawUsage && typeof rawUsage === "object" ? { tokensIn: typeof rawUsage.input === "number" ? rawUsage.input : null, tokensOut: typeof rawUsage.output === "number" ? rawUsage.output : null, cacheReadTokens: typeof rawUsage.cacheRead === "number" ? rawUsage.cacheRead : null, cacheWriteTokens: typeof rawUsage.cacheWrite === "number" ? rawUsage.cacheWrite : null, contextTokens: null, costUsd: null, } : {}; const stopReason = typeof finalMessage.stopReason === "string" ? finalMessage.stopReason : undefined; const terminalError = stopReason === "error" || stopReason === "aborted"; if (terminalError || text.length === 0) { return { text, isError: true, ...(effectiveModel !== undefined ? { effectiveModel } : {}), usage }; } return { text, ...(effectiveModel !== undefined ? { effectiveModel } : {}), usage }; } export function parseGenericOneShotUsage(stdout: string, _stderr: string): OneShotParsedOutput { const trimmed = stdout.trim(); try { const parsed = JSON.parse(trimmed); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { const rec = parsed as Record; const text = typeof rec.result === "string" ? rec.result : (typeof rec.text === "string" ? rec.text : (typeof rec.response === "string" ? rec.response : trimmed)); const u = rec.usage && typeof rec.usage === "object" ? rec.usage as Record : {}; return { text, effectiveModel: reportedModelId(rec.model), isError: Boolean(rec.is_error || rec.error || rec.success === false), usage: { tokensIn: typeof u.input_tokens === "number" ? u.input_tokens : null, tokensOut: typeof u.output_tokens === "number" ? u.output_tokens : null, cacheReadTokens: typeof u.cache_read_input_tokens === "number" ? u.cache_read_input_tokens : null, cacheWriteTokens: typeof u.cache_creation_input_tokens === "number" ? u.cache_creation_input_tokens : null, contextTokens: null, costUsd: typeof rec.total_cost_usd === "number" ? rec.total_cost_usd : null, } }; } } catch { // not json } return { text: trimmed, usage: {} }; } export function parseKimiOneShotUsage(stdout: string, _stderr: string): OneShotParsedOutput { const trimmed = stdout.trim(); const assistantTexts: string[] = []; let isError = false; let errorMessage: string | undefined; for (const line of trimmed.split("\n")) { const lineTrimmed = line.trim(); if (!lineTrimmed) continue; try { const parsed = JSON.parse(lineTrimmed); if (parsed && typeof parsed === "object") { const rec = parsed as Record; if (rec.role === "assistant" && typeof rec.content === "string") { assistantTexts.push(rec.content); } else if (rec.role === "error" || rec.type === "error") { isError = true; errorMessage = typeof rec.message === "string" ? rec.message : (typeof rec.content === "string" ? rec.content : "kimi_error"); } } } catch { // non-JSON line (e.g. startup banner or markdown) } } const text = assistantTexts.length > 0 ? assistantTexts.join("\n") : trimmed; return { text, isError, errorMessage, usage: { tokensIn: null, tokensOut: null, cacheReadTokens: null, cacheWriteTokens: null, contextTokens: null, costUsd: null, }, }; } export function parseAgyOneShotUsage(stdout: string, _stderr: string): OneShotParsedOutput { const trimmed = stdout.trim(); try { const parsed = JSON.parse(trimmed); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { const rec = parsed as Record; const text = typeof rec.response === "string" ? rec.response : (typeof rec.result === "string" ? rec.result : trimmed); const isError = rec.status === "ERROR"; const rawError = rec.error; const errorMessage = isError ? (typeof rawError === "string" ? rawError : (typeof (rawError as any)?.message === "string" ? (rawError as any).message : "agy_error")) : undefined; const usageRec = rec.usage && typeof rec.usage === "object" ? rec.usage as Record : {}; const tokensIn = typeof usageRec.input_tokens === "number" ? usageRec.input_tokens : null; const tokensOut = typeof usageRec.output_tokens === "number" ? usageRec.output_tokens : null; const cacheReadTokens = typeof usageRec.cache_read_tokens === "number" ? usageRec.cache_read_tokens : null; const contextTokens = typeof usageRec.total_tokens === "number" ? usageRec.total_tokens : tokensIn; return { text, isError, errorMessage, usage: { tokensIn, tokensOut, cacheReadTokens, cacheWriteTokens: null, contextTokens, costUsd: null, }, }; } } catch { // not json } return { text: trimmed, usage: { tokensIn: null, tokensOut: null, cacheReadTokens: null, cacheWriteTokens: null, contextTokens: null, }, }; } /** Built-in harnesses. Unknown ids fail closed. Model lists live in `.kxm/models/*.yaml`, not here. */ const READ_ONLY_ONESHOT_ARGS = Object.freeze({ // pi: total containment for a one-shot witness — no tools (built-in, extension and // custom), no ambient extension/hook discovery, no skills, no prompt templates, no // AGENTS.md/CLAUDE.md context discovery, and an ephemeral session. `--no-tools` alone // is not enough: extensions and hooks can still run with their own permissions. pi: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]), omp: Object.freeze(["--no-tools", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-context-files", "--no-session"]), claude: Object.freeze(["--tools", "Read,Glob,Grep", "--restricted", "--safe-mode", "--permission-mode", "plan", "--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--disable-slash-commands", "--no-session-persistence"]), codex: Object.freeze(["--sandbox", "read-only", "--ignore-user-config", "-c", 'approval_policy="never"']), grok: Object.freeze(["--sandbox", "read-only", "--permission-mode", "plan", "--tools", "Read,Glob,Grep", "--no-subagents", "--disable-web-search"]), agy: Object.freeze(["--mode", "plan", "--sandbox", "--disable-slash-commands"]), kimi: Object.freeze(["--plan"]), }); export function oneShotReadOnlyArgs(harness: string): readonly string[] | undefined { return Object.hasOwn(READ_ONLY_ONESHOT_ARGS, harness) ? READ_ONLY_ONESHOT_ARGS[harness as keyof typeof READ_ONLY_ONESHOT_ARGS] : undefined; } /** * Audited edit profiles. These are a writer containment, not read-only flags * with the sandbox removed. They match the assignment helper * (`scripts/harness-run.mjs` `buildArgv` for `permission: "edit"` with hooks * and skills refused): * * - Pi: `-a` auto-approves tools so the process can edit the checkout, while * extensions, skills, prompt templates, and session persistence stay off. * `--no-tools` is the read-only profile and is not used here. * - Grok: `--always-approve` so edits are not an interactive prompt, with * subagents and web search disabled. Grok has no audited read-only edit * mix; the read-only one-shot flags stay on the read-only profile. * * Claude, Codex, agy, and Kimi stay read-only. A live write step on a harness * without an entry here hands off instead of spawning unconstrained. */ const WRITER_ONESHOT_ARGS = Object.freeze({ pi: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]), omp: Object.freeze(["-a", "--no-extensions", "--no-skills", "--no-prompt-templates", "--no-session"]), grok: Object.freeze(["--always-approve", "--no-subagents", "--disable-web-search"]), }); export function oneShotWriterArgs(harness: string): readonly string[] | undefined { return Object.hasOwn(WRITER_ONESHOT_ARGS, harness) ? WRITER_ONESHOT_ARGS[harness as keyof typeof WRITER_ONESHOT_ARGS] : undefined; } export function oneShotPermissionArgs(harness: string, permission: "read-only" | "edit"): readonly string[] | undefined { return permission === "edit" ? oneShotWriterArgs(harness) : oneShotReadOnlyArgs(harness); } export const BUILTIN_HARNESSES: readonly HarnessCatalogEntry[] = Object.freeze([ { id: "pi", label: "Pi", default: true, mode: "headless", commands: ["pi"], versionArgs: ["--version"], update: { self: ["update", "--self"], extensions: ["update", "--extensions"], models: ["update", "--models"], }, oneShot: { argv: ["-p", "--mode", "json"], promptVia: "arg", outputFormat: "json", usageParser: parsePiOneShotUsage, }, }, { id: "omp", label: "Oh My Pi", default: false, mode: "headless", commands: ["omp"], versionArgs: ["--version"], authArgs: ["models", "--json"], update: { self: ["update"], extensions: ["plugin", "upgrade"], models: ["models", "refresh"], }, oneShot: { argv: ["-p", ...oneShotReadOnlyArgs("omp")!, "--mode", "json"], promptVia: "arg", outputFormat: "json", usageParser: parsePiOneShotUsage, }, }, { id: "claude", label: "Claude Code", default: false, mode: "either", commands: ["claude"], versionArgs: ["--version"], authArgs: ["auth", "status"], update: { self: ["update"], extensions: ["plugin", "update", "kxm", "-y"], }, oneShot: { argv: ["-p", ...oneShotReadOnlyArgs("claude")!, "--output-format", "json"], promptVia: "stdin", outputFormat: "json", usageParser: parseClaudeOneShotUsage, }, }, { id: "kimi", label: "Kimi Code", default: false, mode: "either", commands: ["kimi"], versionArgs: ["--version"], authArgs: ["provider", "list"], update: { self: ["upgrade"] }, oneShot: { argv: [...oneShotReadOnlyArgs("kimi")!, "--output-format", "stream-json", "-p"], promptVia: "arg", outputFormat: "stream-json", usageParser: parseKimiOneShotUsage, }, }, { id: "codex", label: "Codex", default: false, mode: "either", commands: ["codex"], versionArgs: ["--version"], authArgs: ["login", "status"], update: { self: ["update"] }, oneShot: { argv: ["exec", ...oneShotReadOnlyArgs("codex")!, "--json", "-"], promptVia: "stdin", outputFormat: "json", usageParser: parseCodexOneShotUsage, }, }, { id: "deepseek", label: "DeepSeek", default: false, mode: "either", commands: ["deepseek"], versionArgs: ["--version"], update: { self: ["update"] }, oneShot: { argv: ["--json"], promptVia: "stdin", outputFormat: "json", usageParser: parseGenericOneShotUsage, }, }, { id: "grok", label: "Grok CLI", default: false, mode: "either", commands: ["grok"], versionArgs: ["--version"], authArgs: ["models"], update: { self: ["update"] }, oneShot: { argv: [...oneShotReadOnlyArgs("grok")!, "--output-format", "json", "--single"], promptVia: "arg", outputFormat: "json", usageParser: parseGrokOneShotUsage, }, }, { id: "agy", label: "Antigravity CLI", default: false, mode: "either", commands: ["agy"], versionArgs: ["--version"], authArgs: ["models"], update: { self: ["update"] }, oneShot: { argv: [...oneShotReadOnlyArgs("agy")!, "--output-format", "json", "-p"], promptVia: "arg", outputFormat: "json", usageParser: parseAgyOneShotUsage, }, }, ]); export const BUILTIN_HARNESS_IDS: readonly string[] = BUILTIN_HARNESSES.map((entry) => entry.id); export function isKnownHarnessId(id: string, extra: Iterable = []): boolean { if (BUILTIN_HARNESS_IDS.includes(id)) return true; for (const candidate of extra) if (candidate === id) return true; return false; } /** Bare catalog command ids only; never a path or shell metacharacter. */ export const SAFE_HARNESS_COMMAND_ID = /^[A-Za-z][A-Za-z0-9_-]*$/; export function harnessCommandCandidates( command: string, platform: NodeJS.Platform = process.platform, ): readonly string[] { if (platform !== "win32" || !SAFE_HARNESS_COMMAND_ID.test(command)) return [command]; return [command, `${command}.exe`, `${command}.cmd`]; } export function isWindowsHarnessShim(command: string): boolean { return /\.(cmd|bat)$/i.test(command.replace(/\\/g, "/").split("/").pop() ?? command); } /** Shell only for allowlisted `name.cmd` on win32. Bare ids stay spawn-without-shell. */ export function harnessSpawnUsesShell(command: string, platform: NodeJS.Platform = process.platform): boolean { if (platform !== "win32") return false; const base = command.replace(/\\/g, "/").split("/").pop() ?? ""; return /^[A-Za-z][A-Za-z0-9_-]*\.cmd$/i.test(base); } /** Allowlisted npm-global inner `.exe`, relative to the directory that contains `name.cmd`. */ export const WIN_NPM_INNER_EXE: Readonly> = Object.freeze({ claude: Object.freeze(["node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe"]), }); export function findWinNpmInnerExe( cliId: string, options: { platform?: NodeJS.Platform | undefined; pathEnv?: string | undefined; existsSync?: ((path: string) => boolean) | undefined; } = {}, ): string | undefined { if ((options.platform ?? process.platform) !== "win32") return undefined; const segments = WIN_NPM_INNER_EXE[cliId]; if (!segments) return undefined; const exists = options.existsSync ?? existsSync; const pathEnv = options.pathEnv ?? process.env.PATH ?? ""; for (const dir of pathEnv.split(";").filter(Boolean)) { if (!exists(win32Path.join(dir, `${cliId}.cmd`))) continue; const inner = win32Path.join(dir, ...segments); if (exists(inner)) return inner; } return undefined; } function defaultRunner(env: NodeJS.ProcessEnv): HarnessRunCommand { return (command, args, timeoutMs) => { try { const result = spawnSync(command, [...args], { encoding: "utf8", timeout: timeoutMs, windowsHide: true, env, shell: harnessSpawnUsesShell(command), }); if (result.error) { const code = (result.error as NodeJS.ErrnoException).code; return { ok: false, code: result.status, stdout: "", stderr: result.stderr?.toString() ?? "", error: code ?? result.error.message }; } return { ok: result.status === 0, code: result.status, stdout: result.stdout?.toString() ?? "", stderr: result.stderr?.toString() ?? "", }; } catch (error) { return { ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : "spawn_failed" }; } }; } function commandResultFromSpawn(result: Awaited>): HarnessCommandResult { const errno = result.error && "code" in result.error ? (result.error as NodeJS.ErrnoException).code : undefined; const error = (typeof errno === "string" && errno ? errno : undefined) ?? result.error?.message ?? (result.observedChildExit !== true ? "auth_probe_exit_unobserved" : undefined); return { ok: result.code === 0 && !error, code: result.code, stdout: result.stdout, stderr: result.stderr, ...(error ? { error } : {}), }; } function defaultAsyncRunner(env: NodeJS.ProcessEnv, signal?: AbortSignal): HarnessRunCommandAsync { return async (command, args, timeoutMs) => { try { return commandResultFromSpawn(await defaultSpawn(command, args, { env, timeoutMs, signal, shell: harnessSpawnUsesShell(command), })); } catch (error) { return { ok: false, code: null, stdout: "", stderr: "", error: error instanceof Error ? error.message : "spawn_failed" }; } }; } function firstLine(text: string): string | undefined { const line = text.split(/\r?\n/).map((candidate) => candidate.trim()).find(Boolean); return line && line.length <= 200 ? line : undefined; } function boundText(text: string, max = 4000): string | undefined { const trimmed = text.trim(); if (!trimmed) return undefined; return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max)}…`; } function authLines(result: HarnessCommandResult): string[] { return `${result.stdout}\n${result.stderr}`.split(/\r?\n/).map((line) => line.trim()).filter(Boolean); } function parseJsonObject(text: string): Record | undefined { try { const value: unknown = JSON.parse(text.trim()); if (value && typeof value === "object" && !Array.isArray(value)) return value as Record; } catch { // Inventory auth is fail-closed unknown, not a dump of the raw body. } return undefined; } function authConflictIssue(result: HarnessCommandResult): string | undefined { const fields: string[] = []; const okTrue = result.ok === true; const codeZero = result.code === 0; if (okTrue !== codeZero) fields.push("ok", "code"); if (okTrue && result.error) { if (!fields.includes("ok")) fields.push("ok"); fields.push("error"); } return fields.length > 0 ? `auth_conflict:${fields.join(",")}` : undefined; } function commandSucceeded(result: HarnessCommandResult): boolean { return result.ok === true && result.code === 0 && !result.error; } function isCodexApiKeyLine(line: string): boolean { return line === CODEX_API_KEY_PREFIX || line.startsWith(`${CODEX_API_KEY_PREFIX} - `); } function interpretAuth(id: string, result: HarnessCommandResult): Pick & { issues: string[] } { const conflict = authConflictIssue(result); if (conflict) return { authenticated: null, issues: [conflict] }; if (result.error) return { authenticated: null, issues: ["auth_probe_error"] }; const lines = authLines(result); if (id === "claude") { const payload = parseJsonObject(result.stdout); if (payload && payload.loggedIn === false) return { authenticated: false, issues: ["not_authenticated"] }; if (commandSucceeded(result) && payload && payload.loggedIn === true) return { authenticated: true, issues: [], ...(payload.authMethod === "claude.ai" || payload.authMethod === "api-key" ? { authMethod: payload.authMethod } : {}), }; return { authenticated: null, issues: ["auth_unparsed"] }; } if (id === "codex") { if (lines.some((line) => line === CODEX_NEGATIVE_LINE || line.startsWith(`${CODEX_NEGATIVE_LINE} `))) { return { authenticated: false, issues: ["not_authenticated"] }; } if (commandSucceeded(result) && lines.some((line) => line === CODEX_CHATGPT_LINE)) { return { authenticated: true, authMethod: "ChatGPT", issues: [] }; } if (commandSucceeded(result) && lines.some((line) => isCodexApiKeyLine(line))) { return { authenticated: true, authMethod: "api-key", issues: ["auth_api_key"] }; } return { authenticated: null, issues: ["auth_unparsed"] }; } if (id === "grok") { if (commandSucceeded(result) && lines.some((line) => line === GROK_LOGIN_LINE)) return { authenticated: true, issues: [] }; return { authenticated: null, issues: ["auth_unparsed"] }; } if (id === "omp") { if (commandSucceeded(result)) { const payload = parseJsonObject(result.stdout); if (payload && Array.isArray(payload.models) && payload.models.length > 0) { return { authenticated: true, issues: [] }; } if (commandSucceeded(result)) return { authenticated: true, issues: [] }; } return { authenticated: null, issues: ["auth_unparsed"] }; } if (id === "agy") { const text = `${result.stdout}\n${result.stderr}`; if (!commandSucceeded(result) || !text.trim()) { return { authenticated: false, issues: ["not_authenticated"] }; } if (AGY_MODEL_ROW.test(text)) return { authenticated: true, authMethod: "antigravity-oauth", issues: [] }; return { authenticated: null, issues: ["auth_unparsed"] }; } if (id === "kimi") { if (!commandSucceeded(result)) { return { authenticated: false, issues: ["not_authenticated"] }; } if (lines.some((line) => /not logged in|no provider/i.test(line))) { return { authenticated: false, issues: ["not_authenticated"] }; } if (lines.some((line) => line.includes("managed:kimi") || line.includes("type=kimi") || line.includes("Default model:"))) { return { authenticated: true, issues: [] }; } return { authenticated: null, issues: ["auth_unparsed"] }; } return { authenticated: null, issues: ["auth_unparsed"] }; } export function resolveDispatchStatus( entry: HarnessCatalogEntry, detected: boolean, authenticated: boolean | null, issues: readonly string[], ): HarnessDispatchStatus { if (!detected) { return { status: "no", supported: false, reason: "not_detected" }; } if (entry.id !== "pi" && !entry.oneShot) { return { status: "no", supported: false, reason: "no_headless_mode", }; } if (entry.id !== "pi" && !oneShotReadOnlyArgs(entry.id)) { return { status: "no", supported: false, reason: "permission_profile_unaudited" }; } if (authenticated === false) { return { status: "no", supported: false, reason: "not_authenticated" }; } if (authenticated === null) { const authIssue = issues.find((issue) => issue === "auth_context_required" || issue === "auth_unknown" || issue === "auth_unparsed" || issue.startsWith("auth_")); return { status: "no", supported: false, reason: authIssue ?? issues[0] ?? "auth_unknown" }; } return { status: "yes", supported: true }; } function windowsCmdReportsMissing(result: HarnessCommandResult): boolean { if (result.ok) return false; const text = `${result.stdout}\n${result.stderr}`; return /not recognized as an internal or external command/i.test(text) || /the system cannot find the (?:file|path) specified/i.test(text); } function tryHarnessCommand( candidate: string, entry: HarnessCatalogEntry, runCommand: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult, timeoutMs: number, ): { command: string; version: string | undefined } | undefined { const result = runCommand(candidate, entry.versionArgs, timeoutMs); if (result.error === "ENOENT") return undefined; if (result.error && result.code === null && !result.stdout && !result.stderr) return undefined; // spawn with shell runs cmd.exe, which exists even when the shim does not. // Its "not recognized" answer is a miss, not a detected harness. if (isWindowsHarnessShim(candidate) && windowsCmdReportsMissing(result)) return undefined; return { command: candidate, version: firstLine(result.stdout) ?? firstLine(result.stderr), }; } function detectHarnessCommand( entry: HarnessCatalogEntry, runCommand: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult, timeoutMs: number, platform: NodeJS.Platform, probe: Pick = {}, ): { command: string; version: string | undefined } | undefined { const candidates = entry.commands.flatMap((base) => [...harnessCommandCandidates(base, platform)]); for (const candidate of candidates) { if (isWindowsHarnessShim(candidate)) continue; const found = tryHarnessCommand(candidate, entry, runCommand, timeoutMs); if (found) return found; } const inner = findWinNpmInnerExe(entry.id, { platform, pathEnv: probe.env?.PATH ?? process.env.PATH ?? "", existsSync: probe.existsSync, }); if (inner) { const found = tryHarnessCommand(inner, entry, runCommand, timeoutMs); if (found) return found; } for (const candidate of candidates) { if (!isWindowsHarnessShim(candidate)) continue; const found = tryHarnessCommand(candidate, entry, runCommand, timeoutMs); if (found) return found; } return undefined; } function probeEntry( entry: HarnessCatalogEntry, runCommand: (command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult, timeoutMs: number, platform: NodeJS.Platform, probe: Pick = {}, ): HarnessStatus { const issues: string[] = []; const found = detectHarnessCommand(entry, runCommand, timeoutMs, platform, probe); const detected = Boolean(found); const command = found?.command; const version = found?.version; let authenticated: boolean | null = null; let authMethod: HarnessStatus["authMethod"]; if (!detected) authenticated = false; else if (entry.id === "pi") { authenticated = null; issues.push("auth_context_required"); } else if (UNKNOWN_AUTH_HARNESSES.has(entry.id) || !entry.authArgs) { authenticated = null; if (UNKNOWN_AUTH_HARNESSES.has(entry.id)) issues.push("auth_unknown"); } else if (command) { // Auth CLIs may initialize hooks/OAuth state and exceed the short binary-detection timeout. // Keep detection bounded while allowing the documented auth probe to complete. const parsed = interpretAuth(entry.id, runCommand(command, entry.authArgs, Math.max(timeoutMs, 10_000))); authenticated = parsed.authenticated; authMethod = parsed.authMethod; issues.push(...parsed.issues); } if (command && isWindowsHarnessShim(command)) issues.push("windows_shim"); const dispatch = resolveDispatchStatus(entry, detected, authenticated, issues); return { id: entry.id, label: entry.label, default: entry.id === (probe.defaultHarness ?? DEFAULT_HARNESS), mode: entry.mode, detected, authenticated, ...(authMethod ? { authMethod } : {}), dispatch, ...(command ? { command } : {}), ...(version ? { version } : {}), canUpdate: { self: Boolean(entry.update.self.length), extensions: Boolean(entry.update.extensions?.length), models: Boolean(entry.update.models?.length), }, issues, }; } export function probeHarnesses(options: HarnessProbeOptions = {}): HarnessInventory { const timeoutMs = options.timeoutMs ?? 3000; const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env); const platform = options.platform ?? process.platform; return { defaultHarness: options.defaultHarness ?? DEFAULT_HARNESS, harnesses: BUILTIN_HARNESSES.map((entry) => probeEntry(entry, runCommand, timeoutMs, platform, options)), }; } /** * Inventory probe that never uses spawnSync. Product CLI/hub/extension paths * must await this so capability/auth checks cannot block the event loop. */ export async function probeHarnessesAsync(options: AsyncHarnessProbeOptions = {}): Promise { return replayHarnessProbe( options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal), (runCommand) => probeHarnesses({ ...options, runCommand }), 256, ); } export function eligibleHarnesses(inventory: HarnessInventory): readonly string[] { const eligible = inventory.harnesses .filter((entry) => entry !== undefined && entry.detected && entry.authenticated === true) .map((entry) => entry.id); if (eligible.length === 0) throw new Error("no_authenticated_harness"); return eligible; } export function validateHarnessModelPair( harnessId: string, modelSpec: string | HarnessModelSpec, ): HarnessModelValidation { if (!isKnownHarnessId(harnessId)) { return { valid: false, issue: "harness_unknown", message: `unknown harness: ${harnessId}` }; } let provider: string | undefined; let model: string | undefined; if (typeof modelSpec === "string") { const trimmed = modelSpec.trim(); if (trimmed.includes("/")) { const idx = trimmed.indexOf("/"); provider = trimmed.slice(0, idx).toLowerCase(); model = trimmed.slice(idx + 1); } else { model = trimmed; } } else { provider = modelSpec.provider?.trim().toLowerCase(); model = modelSpec.model?.trim(); } if (harnessId === "claude") { if (provider && provider !== "anthropic") { return { valid: false, issue: "harness_unhosted_model", message: `harness claude does not host provider ${provider}` }; } if (model && /^(gpt|o1|o3|grok|gemini|kimi|moonshot|deepseek|qwen)-/i.test(model)) { return { valid: false, issue: "harness_unhosted_model", message: `harness claude does not host model ${model}` }; } return { valid: true }; } if (harnessId === "codex") { if (provider && provider !== "openai") { return { valid: false, issue: "harness_unhosted_model", message: `harness codex does not host provider ${provider}` }; } if (model && /^(claude|fable|grok|gemini|kimi|moonshot|deepseek|qwen)-/i.test(model)) { return { valid: false, issue: "harness_unhosted_model", message: `harness codex does not host model ${model}` }; } return { valid: true }; } if (harnessId === "grok") { if (provider && provider !== "xai") { return { valid: false, issue: "harness_unhosted_model", message: `harness grok does not host provider ${provider}` }; } if (model && !/^grok-/i.test(model)) { return { valid: false, issue: "harness_unhosted_model", message: `harness grok only hosts grok models, received ${model}` }; } return { valid: true }; } if (harnessId === "agy") { if (provider && provider !== "google") { return { valid: false, issue: "harness_unhosted_model", message: `harness ${harnessId} does not host provider ${provider}` }; } if (model && !/^gemini-/i.test(model)) { return { valid: false, issue: "harness_unhosted_model", message: `harness ${harnessId} only hosts gemini models, received ${model}` }; } return { valid: true }; } if (harnessId === "kimi") { if (provider && provider !== "moonshot") { return { valid: false, issue: "harness_unhosted_model", message: `harness kimi does not host provider ${provider}` }; } if (model && !/^(kimi|moonshot)(?:-|$)/i.test(model) && !/^kimi-for-coding(?:-|$)/i.test(model)) { return { valid: false, issue: "harness_unhosted_model", message: `harness kimi only hosts kimi/moonshot models, received ${model}` }; } return { valid: true }; } if (harnessId === "deepseek") { if (provider && provider !== "deepseek") { return { valid: false, issue: "harness_unhosted_model", message: `harness deepseek does not host provider ${provider}` }; } if (model && !/^deepseek-/i.test(model)) { return { valid: false, issue: "harness_unhosted_model", message: `harness deepseek only hosts deepseek models, received ${model}` }; } return { valid: true }; } if (harnessId === "pi" || harnessId === "omp") { if (!provider && model?.includes("/")) { const idx = model.indexOf("/"); provider = model.slice(0, idx).toLowerCase(); model = model.slice(idx + 1); } if (!provider) return { valid: true }; const blocked = (message: string): HarnessModelValidation => ({ valid: false, issue: "pi_native_impersonation_blocked", message }); if (PI_NATIVE_BRAKE_PROVIDERS.includes(provider)) { return blocked(`${harnessId} must not impersonate native provider ${provider}; use the native harness`); } if (provider === "antigravity" && model && PI_ANTIGRAVITY_MODEL_ID.test(model)) return { valid: true }; const owned = Object.hasOwn(PI_VENDOR_OWNED_PROVIDERS, provider) ? PI_VENDOR_OWNED_PROVIDERS[provider] : undefined; if (owned) { return blocked(`${harnessId} provider ${provider} bills native vendor ${owned} for ${model ?? "this model"}; use the native harness`); } const segment = model?.includes("/") ? model.slice(0, model.indexOf("/")).toLowerCase() : undefined; const vendor = segment && Object.hasOwn(PI_VENDOR_SEGMENT_ALIASES, segment) ? PI_VENDOR_SEGMENT_ALIASES[segment] : segment; if (vendor && PI_NATIVE_BRAKE_PROVIDERS.includes(vendor)) { return blocked(`${harnessId} must not bill native vendor ${vendor} through ${provider}; use the native harness`); } return { valid: true }; } return { valid: true }; } export interface HarnessAssignmentProbeOptions { harness: string; provider?: string | undefined; model?: string | undefined; env?: NodeJS.ProcessEnv | undefined; runCommand?: ((command: string, args: readonly string[], timeoutMs: number) => HarnessCommandResult) | undefined; timeoutMs?: number | undefined; platform?: NodeJS.Platform | undefined; existsSync?: ((path: string) => boolean) | undefined; } export function probeHarnessAssignment(options: HarnessAssignmentProbeOptions): HarnessStatus { const timeoutMs = options.timeoutMs ?? 3000; const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env); const platform = options.platform ?? process.platform; const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === options.harness); if (!entry) { return { id: options.harness, label: options.harness, default: false, mode: "either", detected: false, authenticated: false, dispatch: { status: "no", supported: false, reason: "harness_unknown" }, canUpdate: { self: false, extensions: false, models: false }, issues: ["harness_unknown"], }; } if (options.provider || options.model) { const validation = validateHarnessModelPair(options.harness, { provider: options.provider, model: options.model }); if (!validation.valid) { return { id: entry.id, label: entry.label, default: entry.default, mode: entry.mode, detected: false, authenticated: false, dispatch: { status: "no", supported: false, reason: validation.issue ?? "harness_unhosted_model" }, canUpdate: { self: Boolean(entry.update.self.length), extensions: Boolean(entry.update.extensions?.length), models: Boolean(entry.update.models?.length), }, issues: [validation.issue ?? "harness_unhosted_model"], }; } } const found = detectHarnessCommand(entry, runCommand, timeoutMs, platform, options); const detected = Boolean(found); const command = found?.command; const version = found?.version; const shimIssue = command && isWindowsHarnessShim(command) ? ["windows_shim"] : []; if (!detected || !command) { return { id: entry.id, label: entry.label, default: entry.default, mode: entry.mode, detected: false, authenticated: false, dispatch: { status: "no", supported: false, reason: "not_detected" }, canUpdate: { self: Boolean(entry.update.self.length), extensions: Boolean(entry.update.extensions?.length), models: Boolean(entry.update.models?.length), }, issues: [], }; } if (entry.id === "pi") { if (!options.provider && !options.model) { return { id: entry.id, label: entry.label, default: entry.default, mode: entry.mode, detected: true, authenticated: null, dispatch: { status: "no", supported: false, reason: "auth_context_required" }, command, ...(version ? { version } : {}), canUpdate: { self: Boolean(entry.update.self.length), extensions: Boolean(entry.update.extensions?.length), models: Boolean(entry.update.models?.length), }, issues: ["auth_context_required", ...shimIssue], }; } const authArgs = ["auth", "check"]; // `pi auth check --model` requires a provider-qualified id: a bare model answers // status "invalid", which reads as not_authenticated and hides the real state. When // the caller holds provider and bare model separately (the engine's request shape), // qualify the id; a model that already carries its namespace goes through as-is. const qualifiedModel = options.model ? (options.provider && options.model.startsWith(`${options.provider}/`) ? options.model : options.provider ? `${options.provider}/${options.model}` : options.model) : undefined; if (qualifiedModel) { authArgs.push("--model", qualifiedModel); } else if (options.provider) { authArgs.push("--provider", options.provider); } authArgs.push("--json"); const result = runCommand(command, authArgs, timeoutMs); const parsed = parseJsonObject(result.stdout); let authenticated: boolean | null = false; const issues: string[] = []; if (result.error) { issues.push("auth_probe_error"); } else if (parsed && parsed.status === "ready") { authenticated = true; } else if (parsed && parsed.status === "not_ready") { authenticated = false; issues.push("not_authenticated"); } else if (commandSucceeded(result) && /\bready\b/i.test(`${result.stdout}\n${result.stderr}`)) { authenticated = true; } else { authenticated = false; issues.push("not_authenticated"); } issues.push(...shimIssue); const dispatch = resolveDispatchStatus(entry, true, authenticated, issues); return { id: entry.id, label: entry.label, default: entry.default, mode: entry.mode, detected: true, authenticated, dispatch, command, ...(version ? { version } : {}), canUpdate: { self: Boolean(entry.update.self.length), extensions: Boolean(entry.update.extensions?.length), models: Boolean(entry.update.models?.length), }, issues, }; } return probeEntry(entry, runCommand, timeoutMs, platform); } export interface AsyncHarnessAssignmentProbeOptions extends Omit { signal?: AbortSignal | undefined; runCommand?: HarnessRunCommandAsync | undefined; } class PendingHarnessCommand { command: string; args: readonly string[]; timeoutMs: number; key: string; constructor(command: string, args: readonly string[], timeoutMs: number, key: string) { this.command = command; this.args = [...args]; this.timeoutMs = timeoutMs; this.key = key; } } async function replayHarnessProbe( run: HarnessRunCommandAsync, execute: (runCommand: HarnessRunCommand) => T, limit: number, ): Promise { const observed = new Map(); for (let commands = 0; commands <= limit; commands++) { try { return execute((command, args, timeoutMs) => { const key = JSON.stringify([command, args, timeoutMs]); const result = observed.get(key); if (result) return result; throw new PendingHarnessCommand(command, args, timeoutMs, key); }); } catch (pending) { if (!(pending instanceof PendingHarnessCommand)) throw pending; if (commands === limit) throw new Error("auth_probe_command_limit"); observed.set(pending.key, await run(pending.command, pending.args, pending.timeoutMs)); } } throw new Error("auth_probe_command_limit"); } /** * Execute the existing probe policy without blocking the Runtime event loop. * Replay pure policy decisions from per-call command observations, yielding for * every missing observation. This reuses the exact auth/model/brake parsers, * deduplicates repeated detection, and never caches auth across assignments. */ export async function probeHarnessAssignmentAsync(options: AsyncHarnessAssignmentProbeOptions): Promise { return replayHarnessProbe( options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal), (runCommand) => probeHarnessAssignment({ ...options, runCommand }), 32, ); } export function probeHarnessesForModel( modelSpec: HarnessModelSpec, options: HarnessProbeOptions = {}, ): HarnessInventory { const timeoutMs = options.timeoutMs ?? 3000; const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env); const platform = options.platform ?? process.platform; return { defaultHarness: DEFAULT_HARNESS, harnesses: BUILTIN_HARNESSES.map((entry) => probeHarnessAssignment({ harness: entry.id, provider: modelSpec.provider, model: modelSpec.model, runCommand, timeoutMs, platform, env: options.env, existsSync: options.existsSync, }) ), }; } export async function probeHarnessesForModelAsync( modelSpec: HarnessModelSpec, options: AsyncHarnessProbeOptions = {}, ): Promise { return replayHarnessProbe( options.runCommand ?? defaultAsyncRunner(options.env ?? process.env, options.signal), (runCommand) => probeHarnessesForModel(modelSpec, { ...options, runCommand }), 256, ); } function scopesFor(scope: HarnessUpdateScope, entry: HarnessCatalogEntry): Exclude[] { if (scope === "self") return ["self"]; if (scope === "extensions") return entry.update.extensions ? ["extensions"] : []; if (scope === "models") return entry.update.models ? ["models"] : []; return [ ...(entry.update.self.length ? ["self" as const] : []), ...(entry.update.extensions ? ["extensions" as const] : []), ...(entry.update.models ? ["models" as const] : []), ]; } function argsFor(entry: HarnessCatalogEntry, scope: Exclude): readonly string[] | undefined { if (scope === "self") return entry.update.self; if (scope === "extensions") return entry.update.extensions; return entry.update.models; } function detectedHarnesses( inventory: HarnessInventory, requestedHarness?: string, ): HarnessStatus[] { if (requestedHarness) return inventory.harnesses.filter((entry) => entry.id === requestedHarness); const detected = inventory.harnesses.filter((entry) => entry.detected); const preferred = inventory.defaultHarness; return [ ...detected.filter((entry) => entry.id === preferred), ...detected.filter((entry) => entry.id !== preferred), ]; } export function planHarnessUpdate( inventory: HarnessInventory, requested: { harness?: string; scope: HarnessUpdateScope }, ): HarnessUpdateStep[] { const selected = detectedHarnesses(inventory, requested.harness); if (requested.harness && selected.length === 0) { return [{ harness: requested.harness, scope: "self", command: requested.harness, args: [], outcome: "skipped", detail: isKnownHarnessId(requested.harness) ? "not_detected" : "unknown_harness", }]; } const waves: Exclude[] = requested.scope === "all" ? ["self", "extensions", "models"] : [requested.scope]; const steps: HarnessUpdateStep[] = []; for (const scope of waves) { for (const status of selected) { const entry = BUILTIN_HARNESSES.find((candidate) => candidate.id === status.id); if (!entry) continue; const supported = scopesFor(requested.scope === "all" ? "all" : scope, entry); if (!supported.includes(scope)) { if (requested.scope !== "all") { steps.push({ harness: status.id, scope, command: status.command ?? entry.commands[0]!, args: [], outcome: "skipped", detail: "no_updater", }); } continue; } if (!status.detected || !status.command) { steps.push({ harness: status.id, scope, command: entry.commands[0]!, args: argsFor(entry, scope) ?? [], outcome: "skipped", detail: "not_detected", }); continue; } steps.push({ harness: status.id, scope, command: status.command, args: argsFor(entry, scope) ?? [], outcome: "would", }); } } return steps; } export function runHarnessUpdate( steps: readonly HarnessUpdateStep[], options: HarnessProbeOptions & { dryRun?: boolean; updateTimeoutMs?: number } = {}, ): HarnessUpdateStep[] { if (options.dryRun) return steps.map((step) => ({ ...step, outcome: step.outcome === "would" ? "would" : step.outcome })); const timeoutMs = options.updateTimeoutMs ?? 180_000; const runCommand = options.runCommand ?? defaultRunner(options.env ?? process.env); return steps.map((step) => { if (step.outcome !== "would") return step; const result = runCommand(step.command, step.args, timeoutMs); const detail = boundText(result.stderr) ?? boundText(result.stdout) ?? result.error; return { ...step, outcome: result.ok ? "passed" : "failed", ...(result.code !== undefined && result.code !== null ? { code: result.code } : {}), ...(detail ? { detail } : {}), }; }); } export function formatHarnessInventory(inventory: HarnessInventory): string { const header = [ "id".padEnd(9), "default".padEnd(8), "detected".padEnd(9), "auth".padEnd(8), "dispatch".padEnd(26), "updates", ].join(" "); const rows = inventory.harnesses.map((entry) => { const auth = entry.authenticated === true ? "yes" : entry.authenticated === false ? "no" : "unknown"; const dispatchText = entry.dispatch ? (entry.dispatch.status === "yes" ? "yes" : `no (${entry.dispatch.reason ?? "unsupported"})`) : (entry.authenticated === true ? "yes" : "no"); const updates = [ entry.canUpdate.self ? "self" : undefined, entry.canUpdate.extensions ? "extensions" : undefined, entry.canUpdate.models ? "models" : undefined, ].filter(Boolean).join(",") || "none"; const apiKeyNote = entry.issues.includes("auth_api_key") ? " API key" : ""; return [ entry.id.padEnd(9), (entry.default ? "yes" : "no").padEnd(8), (entry.detected ? "yes" : "no").padEnd(9), auth.padEnd(8), dispatchText.padEnd(26), `${updates}${apiKeyNote}`, ].join(" "); }); return [ `default harness: ${inventory.defaultHarness} (used when an agent omits harness:)`, "enable/disable = Git YAML (.kxm/agents, .kxm/models) or the harness's own plugin CLI", "governed kxm skills are not auto-updated", header, ...rows, ].join("\n"); } export function formatHarnessUpdate(steps: readonly HarnessUpdateStep[]): string { if (steps.length === 0) return "nothing to update"; return steps.map((step) => { const argv = [step.command, ...step.args].join(" "); const detail = step.detail ? ` (${step.detail})` : ""; return `${step.harness} ${step.scope}: ${step.outcome} ${argv}${detail}`; }).join("\n"); }