UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

418 lines (402 loc) • 15.1 kB
import { existsSync, readFileSync, readdirSync } from "node:fs"; import { basename, join, resolve } from "node:path"; import { parse as parseYaml } from "yaml"; import { probeHarnessesAsync } from "../harness.ts"; import { suggestWorkflowAndRoles } from "../suggest.ts"; import { createGoal, createTask, listGoals, listTasks, getTask, syncTaskWithTracker, goalFilePath, taskFilePath, type TaskStatus, type TrackerType, } from "../task-manager.ts"; import { discoverKxmProjectRoot } from "../project-config.ts"; import { compileKxmWorkflow } from "../engine-compile.ts"; import { createStudioServer, DEFAULT_STUDIO_PORT, generateStudioLayout, } from "../studio-layout.ts"; import { readSessionTokenFromDisk } from "../commands.ts"; import { print, printPlan, type Runtime } from "./types.ts"; import { cmdKxmRun, resolveKxmRunTarget } from "./project.ts"; export async function cmdSuggest( runtime: Runtime, promptParts: string[], probeHarnesses = probeHarnessesAsync, ): Promise<number> { try { const prompt = promptParts.join(" ").trim(); if (!prompt) { print(runtime.io, runtime.json, { ok: false, command: "suggest", error: "prompt_required" }, "prompt must be non-empty"); return 2; } // Native authentication probes may initialize state. A dry run must not invoke them. const inventory = runtime.dryRun ? undefined : await probeHarnesses({ env: runtime.env }); const suggestion = suggestWorkflowAndRoles(prompt, { availableHarnesses: inventory?.harnesses }); if (suggestion.execution.supported && [".yaml", ".yml"].some((extension) => existsSync(join(runtime.cwd, ".kxm", "workflows", `${suggestion.workflowId}${extension}`)))) { suggestion.roles = []; suggestion.execution = { supported: false, error: "workflow_already_exists", reason: `Workflow ${suggestion.workflowId} already exists; its agents and permissions may differ from the recommended template.`, nextSteps: ["Review the existing definition and every agent's harness/model before running it, or install the recommended template under a fresh flat ID. No existing workflow is overwritten or recommended for execution."], }; } const execution = suggestion.execution; const text = [ `Suggested Workflow: ${suggestion.workflowId} (${suggestion.area})`, `Template: ${suggestion.template}`, `Confidence: ${(suggestion.confidence * 100).toFixed(0)}%`, `Reasons: ${suggestion.reasons.join("; ")}`, `Suggested Skills: ${suggestion.suggestedSkills.join(", ") || "none"}`, "", "Install definition only (requires kxm init; does not execute):", ` ${suggestion.suggestedCommand}`, "", ...(execution.supported ? [ "Suggested agent routing (not applied):", ...suggestion.roles.map((role) => ` ${role.agent}: ${role.harness} (${role.role}; use a configured compatible model)`), "Prerequisites:", ...execution.prerequisites.map((step) => ` ${step}`), `Create a run only (${execution.shell}; does not execute steps):`, ` ${execution.createCommand}`, "Then drive the returned run ID with live calls and inspect its result:", ` ${execution.driveCommand}`, ` ${execution.statusCommand}`, ` ${execution.receiptCommand}`, ] : [ `Execution unavailable: ${execution.reason}`, ...execution.nextSteps.map((step) => ` ${step}`), ...(runtime.dryRun ? ["Harness authentication was not probed during --dry-run."] : []), ]), ].join("\n"); print(runtime.io, runtime.json, { ok: execution.supported, command: "suggest", prompt, ...suggestion, ...(runtime.dryRun ? { dryRun: true } : {}), ...(!execution.supported ? { error: execution.error } : {}), }, text); return execution.supported ? 0 : 1; } catch (error) { const message = error instanceof Error ? error.message : String(error); print(runtime.io, runtime.json, { ok: false, command: "suggest", error: "suggest_failed", detail: message }, `suggest failed: ${message}`); return 1; } } export async function cmdGoalCreate( runtime: Runtime, title: string, options: { area?: string | undefined; metric?: string[] | undefined; targetDate?: string | undefined }, ): Promise<number> { try { const goal = createGoal(runtime.cwd, { title, area: options.area, successMetrics: options.metric, targetDate: options.targetDate, }, { dryRun: runtime.dryRun }); if (runtime.dryRun) { printPlan(runtime, { command: "goal create", goal }, [{ action: "write", target: goalFilePath(runtime.cwd, goal.id) }], `create goal ${goal.title} (the id is assigned when it is created)`); return 0; } print( runtime.io, runtime.json, { ok: true, command: "goal create", goal }, `Created goal ${goal.id}: ${goal.title}`, ); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`goal create failed: ${message}\n`); return 1; } } export async function cmdGoalList(runtime: Runtime): Promise<number> { try { const goals = listGoals(runtime.cwd); const text = goals.length === 0 ? "No goals recorded in .kxm/goals/" : goals.map((g) => `[${g.status}] ${g.id}: ${g.title} (${g.area})`).join("\n"); print(runtime.io, runtime.json, { ok: true, command: "goal list", count: goals.length, goals }, text); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`goal list failed: ${message}\n`); return 1; } } export async function cmdTaskCreate( runtime: Runtime, title: string, options: { goal?: string | undefined; objective?: string | undefined; workflow?: string | undefined; tracker?: string | undefined; issue?: string | undefined }, ): Promise<number> { try { const task = createTask(runtime.cwd, { title, goalId: options.goal, objective: options.objective ?? title, assignedWorkflow: options.workflow, trackerSync: options.tracker && options.issue ? { tracker: options.tracker as TrackerType, issueKey: options.issue } : undefined, }, { dryRun: runtime.dryRun }); if (runtime.dryRun) { printPlan(runtime, { command: "task create", task }, [{ action: "write", target: taskFilePath(runtime.cwd, task.id) }], `create task ${task.title} [${task.status}] (the id is assigned when it is created)`); return 0; } print( runtime.io, runtime.json, { ok: true, command: "task create", task }, `Created task ${task.id}: ${task.title} [${task.status}]`, ); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`task create failed: ${message}\n`); return 1; } } export async function cmdTaskList( runtime: Runtime, options: { goal?: string | undefined; status?: string | undefined }, ): Promise<number> { try { const tasks = listTasks(runtime.cwd, { goalId: options.goal, status: options.status as TaskStatus | undefined, }); const text = tasks.length === 0 ? "No tasks recorded in .kxm/tasks/" : tasks.map((t) => `[${t.status}] ${t.id}: ${t.title}${t.assignedWorkflow ? ` -> ${t.assignedWorkflow}` : ""}`).join("\n"); print(runtime.io, runtime.json, { ok: true, command: "task list", count: tasks.length, tasks }, text); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`task list failed: ${message}\n`); return 1; } } export async function cmdTaskGet(runtime: Runtime, taskId: string): Promise<number> { try { const task = getTask(runtime.cwd, taskId); if (!task) { runtime.io.stderr(`Task ${taskId} not found\n`); return 1; } const text = [ `Task: ${task.id}`, `Title: ${task.title}`, `Status: ${task.status}`, `Objective: ${task.objective}`, task.assignedWorkflow ? `Workflow: ${task.assignedWorkflow}` : "", task.workflowRunId ? `Active Run: ${task.workflowRunId}` : "", task.trackerSync ? `Tracker: ${task.trackerSync.tracker} (#${task.trackerSync.issueKey}) [${task.trackerSync.syncStatus}]` : "", ].filter(Boolean).join("\n"); print(runtime.io, runtime.json, { ok: true, command: "task get", task }, text); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`task get failed: ${message}\n`); return 1; } } export async function cmdTaskRun(runtime: Runtime, taskId: string): Promise<number> { try { const task = getTask(runtime.cwd, taskId); if (!task) { runtime.io.stderr(`Task ${taskId} not found\n`); return 1; } const workflow = task.assignedWorkflow; if (runtime.dryRun) { const target = resolveKxmRunTarget(runtime, workflow, true); if (typeof target === "number") return target; printPlan( runtime, { command: "task run", taskId, ...target, status: task.status, execution: { status: "not_started", mode: "live" } }, [ { action: "request", target: "POST kxm-runtime /v1/runs (starts the Runtime supervisor if it is not running)" }, ], `create workflow ${target.workflowId} for task ${taskId}; task status stays ${task.status} until work actually starts`, ); return 0; } return await cmdKxmRun(runtime, workflow, [task.objective], true); } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`task run failed: ${message}\n`); return 1; } } export async function cmdTaskSync(runtime: Runtime, taskId: string): Promise<number> { try { const synced = syncTaskWithTracker(runtime.cwd, taskId, { dryRun: runtime.dryRun }); if (runtime.dryRun) { printPlan(runtime, { command: "task sync", task: synced }, [{ action: "write", target: taskFilePath(runtime.cwd, taskId) }], `mark task ${taskId} synced with ${synced.trackerSync?.tracker} #${synced.trackerSync?.issueKey}`); return 0; } print( runtime.io, runtime.json, { ok: true, command: "task sync", task: synced }, `Synced task ${taskId} with ${synced.trackerSync?.tracker} #${synced.trackerSync?.issueKey}`, ); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`task sync failed: ${message}\n`); return 1; } } export async function cmdStudioLayout(runtime: Runtime, workflowPath?: string | undefined): Promise<number> { try { const projectRoot = discoverKxmProjectRoot(runtime.cwd) ?? runtime.cwd; let filePath = workflowPath; if (!filePath) { filePath = resolve(projectRoot, ".kxm", "workflows", "default.yaml"); } let yamlContent: string; let workflowId = "default"; if (existsSync(filePath)) { yamlContent = readFileSync(filePath, "utf8"); } else { yamlContent = `schema: kxm.workflow.v1 description: Feature implementation workflow coordinator: coordinator limits: maxTransitions: 12 steps: - id: plan kind: agent agent: planner maxAttempts: 2 on: passed: implement failed: target: $terminal terminalStatus: failed - id: implement kind: agent agent: writer maxAttempts: 3 on: passed: verify failed: target: $terminal terminalStatus: failed - id: verify kind: gate gate: verify-gate expect: pass maxAttempts: 2 on: passed: target: $terminal terminalStatus: completed failed: target: implement maxTransitions: 2 `; } const parsedYaml = parseYaml(yamlContent) as any; const plan = compileKxmWorkflow({ id: workflowId, value: parsedYaml }); const layout = generateStudioLayout(plan); print(runtime.io, runtime.json, { ok: true, command: "studio layout", layout }, JSON.stringify(layout, null, 2)); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`studio layout failed: ${message}\n`); return 1; } } export async function cmdStudioServe( runtime: Runtime, options: { port?: string | undefined; host?: string | undefined; token?: string | undefined }, ): Promise<number> { const port = options.port ? parseInt(options.port, 10) : DEFAULT_STUDIO_PORT; const host = options.host ?? "127.0.0.1"; const sessionToken = options.token ?? runtime.env.KXM_SESSION_TOKEN ?? readSessionTokenFromDisk({ userConfigDir: runtime.env.KXM_USER_CONFIG_DIR })?.token; if (runtime.dryRun) { print( runtime.io, runtime.json, { ok: true, command: "studio serve", dryRun: true, port, host }, `would start studio server on http://${host}:${port}`, ); return 0; } try { const serverHandle = createStudioServer({ port, host, projectRoot: runtime.cwd, sessionToken, planProvider: () => { try { const wfDir = join(runtime.cwd, ".kxm", "workflows"); const defaultPath = join(wfDir, "default.yaml"); let targetPath = existsSync(defaultPath) ? defaultPath : undefined; if (!targetPath && existsSync(wfDir)) { const yml = readdirSync(wfDir).find((f) => f.endsWith(".yaml") || f.endsWith(".yml")); if (yml) targetPath = join(wfDir, yml); } if (targetPath && existsSync(targetPath)) { const raw = readFileSync(targetPath, "utf8"); const parsed = parseYaml(raw) as any; const wfId = basename(targetPath).replace(/\.(yaml|yml)$/, ""); return compileKxmWorkflow({ id: wfId, value: parsed }); } } catch { // fallback } return undefined; }, }); const actualPort = await serverHandle.listen(); const info = { ok: true, command: "studio serve", port: actualPort, host, url: `http://${host}:${actualPort}`, }; print( runtime.io, runtime.json, info, `KXM Web Studio listening on http://${host}:${actualPort} (Decision Q8 & D14)\nPress Ctrl+C to stop.\n`, ); if (runtime.env.KXM_STUDIO_ONCE) { await serverHandle.close(); return 0; } await new Promise<void>((resolveClose) => { const shutdown = async () => { process.off("SIGINT", shutdown); process.off("SIGTERM", shutdown); await serverHandle.close(); resolveClose(); }; process.once("SIGINT", shutdown); process.once("SIGTERM", shutdown); }); return 0; } catch (error) { const message = error instanceof Error ? error.message : String(error); runtime.io.stderr(`studio serve failed: ${message}\n`); return 1; } }