UNPKG

@kontextmind/kxm

Version:

KXM local-first multi-agent orchestration and operator dashboard

100 lines (92 loc) • 5.93 kB
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { parse, stringify } from "yaml"; import { rolePurposeForId } from "./role.ts"; const RETIRED_POLICY = ".kxm/producers.yaml"; export interface RoutePolicy { schema: "kxm.routes.v2"; updatedAt: string; admitted: string[]; disabled: string[]; } const empty = (): RoutePolicy => ({ schema: "kxm.routes.v2", updatedAt: new Date().toISOString(), admitted: [], disabled: [] }); export function loadRoutePolicy(root: string): RoutePolicy { if (existsSync(join(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`); const path = join(root, ".kxm", "routes.yaml"); if (!existsSync(path)) return empty(); const value = parse(readFileSync(path, "utf8")) as Partial<RoutePolicy>; if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml"); return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date().toISOString(), admitted: value.admitted.filter((x): x is string => typeof x === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x): x is string => typeof x === "string") : [] }; } export function updateRouteState(root: string, model: string, state: "admitted" | "disabled"): RoutePolicy { return setRouteState(root, model, state); } export function setRouteState(root: string, model: string, state: "admitted" | "disabled", role?: string, removeRole = false): RoutePolicy { const policy = loadRoutePolicy(root); policy.admitted = policy.admitted.filter((x) => x !== model); policy.disabled = policy.disabled.filter((x) => x !== model); if (state === "admitted") policy.admitted.push(model); else policy.disabled.push(model); if (role) { if (!/^[a-z][a-z0-9_-]{0,63}$/i.test(role)) throw new Error("invalid role id"); const rolePath = join(root, ".kxm", "roles", `${role}.yaml`); let roleFile: Record<string, unknown> = {}; if (existsSync(rolePath)) { const parsed = parse(readFileSync(rolePath, "utf8")); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) roleFile = parsed as Record<string, unknown>; } const roster = Array.isArray(roleFile.roster) ? roleFile.roster.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : []; const routeId = routeIdForInventoryModel(root, model); if (!routeId) throw new Error(`unknown route: no v2 model file matches '${model}'`); const existing = roster.findIndex((entry) => entry.route === routeId || entry.model === model); if (removeRole) { if (existing >= 0) roster.splice(existing, 1); } else if (existing < 0) roster.push({ route: routeId }); const purpose = typeof roleFile.purpose === "string" ? roleFile.purpose : rolePurposeForId(role); roleFile.schema = "kxm.role.v2"; roleFile.id ??= role; roleFile.purpose ??= purpose; roleFile.permission ??= purpose === "writer" ? "edit" : "read-only"; roleFile.description ??= role; roleFile.roster = roster; mkdirSync(join(root, ".kxm", "roles"), { recursive: true }); writeFileSync(rolePath, stringify(roleFile), "utf8"); } policy.updatedAt = new Date().toISOString(); mkdirSync(join(root, ".kxm"), { recursive: true }); writeFileSync(join(root, ".kxm", "routes.yaml"), stringify(policy), "utf8"); return policy; } /** Route-file id whose vendor/model matches an inventory selector, if one exists. */ export function routeIdForInventoryModel(root: string, model: string): string | undefined { const modelsDir = join(root, ".kxm", "models"); if (!existsSync(modelsDir)) return undefined; for (const name of readdirSync(modelsDir)) { if (!name.endsWith(".yaml") || name === "inventory.yaml") continue; const parsedModel = parse(readFileSync(join(modelsDir, name), "utf8")) as { vendor?: unknown; model?: unknown; schema?: unknown }; if (parsedModel?.schema !== "kxm.model.v2") continue; const vendor = typeof parsedModel.vendor === "string" ? parsedModel.vendor : ""; const named = typeof parsedModel.model === "string" ? parsedModel.model : ""; if (named === model || (vendor && `${vendor}/${named}` === model)) return name.slice(0, -5); } return undefined; } /** Role ids whose roster names the model file that matches this inventory selector. */ export function rolesForInventoryModel(root: string, inventoryId: string): string[] { const routeId = routeIdForInventoryModel(root, inventoryId); if (!routeId) return []; return Object.entries(listRoleBindings(root)).filter(([, ids]) => ids.includes(routeId)).map(([role]) => role); } export function listRoleBindings(root: string): Record<string, string[]> { const dir = join(root, ".kxm", "roles"); const result: Record<string, string[]> = {}; if (!existsSync(dir)) return result; for (const file of readdirSync(dir).filter((name) => name.endsWith(".yaml"))) { const role = file.slice(0, -5); const value = parse(readFileSync(join(dir, file), "utf8")) as { roster?: Array<{ route?: unknown }> }; result[role] = (value.roster ?? []).map((entry) => entry.route).filter((route): route is string => typeof route === "string"); } return result; } export function isRouteAdmitted(root: string, modelId: string): boolean { const policy = loadRoutePolicy(root); return policy.admitted.includes(modelId) && !policy.disabled.includes(modelId); } export function listInventoryModels(root: string): string[] { const path = join(root, ".kxm", "models", "inventory.yaml"); if (!existsSync(path)) return []; const value = parse(readFileSync(path, "utf8")) as { models?: Array<{ id?: unknown }> }; return (value.models ?? []).map((m) => m.id).filter((id): id is string => typeof id === "string"); }