@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
100 lines (92 loc) • 5.93 kB
text/typescript
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");
}