@kontextmind/kxm
Version:
KXM local-first multi-agent orchestration and operator dashboard
336 lines (307 loc) • 14.6 kB
JavaScript
import {
cpSync,
existsSync,
lstatSync,
mkdirSync,
readFileSync,
readdirSync,
realpathSync,
rmSync,
writeFileSync,
} from "node:fs";
import { join, resolve, sep } from "node:path";
import { fileURLToPath } from "node:url";
const MARKER_START = "<!-- kxm:codex:commands:start -->";
const MARKER_END = "<!-- kxm:codex:commands:end -->";
const SKILL_NAME = /^[a-z][a-z0-9-]*[a-z0-9]$/u;
const COMMAND_NAME = /^[a-z][a-z0-9-]*$/u;
const MANIFEST_REL = "plugins/kxm/skill-suite.json";
export const CODEX_COMMANDS_BLOCK = `${MARKER_START}
## KXM agent commands
The \`kxm\` CLI is the unified agent surface for peer collaboration and workflow stages. Every command supports \`--json\`.
### Peer messaging (\`kxm peer <verb> --json\`)
| Command | Purpose | Key options |
|---|---|---|
| \`kxm peer list\` | List online peer agents and purposes | \`--json\` |
| \`kxm peer send [target] [content]\` | Send a focused request to a peer | \`--target\`, \`--content\`, \`--delivery <steer\\|followUp\\|nextTurn>\`, \`--correlation-id\`, \`--idempotency-key\`, \`--workflow-context <json>\`, \`--ttl-ms\`, \`--allow-offline\` |
| \`kxm peer get [messageId]\` | Check request status without blocking | \`--message-id\` |
| \`kxm peer await [messageId]\` | Wait for reply (capped at 60 seconds) | \`--message-id\`, \`--timeout-ms\` (max 60000) |
| \`kxm peer cancel [messageId]\` | Cancel a queued or delivered request | \`--message-id\` |
| \`kxm peer fanout\` | Send same request to 1–3 peers | \`--targets <t1,t2>\`, \`--content\`, \`--timeout-ms\`, \`--workflow-context <json>\` |
| \`kxm peer inbox\` | List inbound requests awaiting a reply | \`--json\` |
| \`kxm peer reply [messageId] [content]\` | Reply to an inbound request | \`--message-id\`, \`--content\` |
### Workflow lifecycle (\`kxm workflow <verb> --json\`)
| Command | Purpose | Key options |
|---|---|---|
| \`kxm workflow checkpoint [runId] [stageId] [status] [summary]\` | Record stage result with verified evidence | \`--run-id\`, \`--stage-id\`, \`--status <passed\\|warning\\|failed>\`, \`--summary\`, \`--evidence <json>\`, \`--evidence-refs <json>\` |
| \`kxm workflow record [runId] [category] [area] [summary]\` | Record journal knowledge in one of ten categories; area is optional with \`--stage-id\` | \`--run-id\`, \`--category <plan\\|decision\\|contradiction\\|error\\|lesson\\|observation\\|hypothesis\\|experiment\\|state-change\\|skill-candidate>\`, \`--stage-id\`, \`--area\`, \`--severity <info\\|warning\\|error>\`, \`--details\`, \`--evidence <items...>\` |
| \`kxm workflow wait [runId] [stageId] [signalKey] [summary]\` | Pause stage until an external signed signal arrives | \`--run-id\`, \`--stage-id\`, \`--signal-key\`, \`--summary\`, \`--evidence <json>\`, \`--evidence-refs <json>\`, \`--timeout-ms\` |
| \`kxm workflow signal <runId> <signalKey> <status> <summary>\` | Resume or unblock a waiting stage or KXM run | \`[evidence...]\`, \`--delivery-id\` |
| \`kxm workflow list\` | List local workflow runs | \`--json\` |
| \`kxm workflow get <runId>\` | Get stages and journal for a run | \`--json\` |
### Context operating system (\`kxm context <verb> --json\`)
| Command | Purpose | Key options |
|---|---|---|
| \`kxm context get <project>\` | Assemble role-aware context packet | \`--role\`, \`--task\`, \`--run\`, \`--stage\`, \`--budget\` |
| \`kxm context recall <project>\` | Search durable context metadata | \`--query\`, \`--kinds\`, \`--limit\` |
| \`kxm context state <project> <key>\` | Query authoritative temporal state | \`--as-of <timestamp>\` |
| \`kxm context episode <project>\` | Query workflow learning episodes | \`--run\` |
| \`kxm context promote <project> <proposalId>\` | Promote an approved state proposal (control plane) | required \`--evidence <refs>\` |
${MARKER_END}`;
function refuse(message) {
throw new Error(message);
}
function isPlainObject(value) {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function posixContained(value, label) {
if (typeof value !== "string" || !value || value.includes("\\") || value.includes("\0")) {
refuse(`${label} must be a contained relative path`);
}
const normalized = value.startsWith("./") ? value.slice(2) : value;
if (!normalized || normalized.startsWith("/") || normalized.split("/").some((part) => !part || part === "." || part === "..")) {
refuse(`${label} must be a contained relative path`);
}
return value;
}
function containedRoot(root) {
return existsSync(root) ? realpathSync(root) : resolve(root);
}
function assertPathStaysInRoot(resolvedRoot, current, relativePath) {
const real = realpathSync(current);
const prefix = resolvedRoot.endsWith(sep) ? resolvedRoot : `${resolvedRoot}${sep}`;
if (real !== resolvedRoot && !real.startsWith(prefix)) {
refuse(`path escapes repository: ${relativePath}`);
}
return real;
}
function assertExistingAncestorsAreRegularContained(root, relativePath, { required = false } = {}) {
const resolvedRoot = containedRoot(root);
const parts = posixContained(relativePath, "path").split("/");
let current = resolvedRoot;
for (let i = 0; i < parts.length; i++) {
current = join(current, parts[i]);
const rel = parts.slice(0, i + 1).join("/");
let info;
try {
info = lstatSync(current);
} catch {
if (required) refuse(`path is missing: ${rel}`);
return;
}
if (info.isSymbolicLink()) refuse(`symlinks are not allowed: ${rel}`);
if (!info.isDirectory()) {
refuse(`path must be a regular contained directory: ${rel}`);
}
assertPathStaysInRoot(resolvedRoot, current, rel);
}
}
function assertRegularContainedPath(root, relativePath, { directory = false } = {}) {
const resolvedRoot = containedRoot(root);
const parts = posixContained(relativePath, "path").split("/");
let current = resolvedRoot;
for (let i = 0; i < parts.length; i++) {
current = join(current, parts[i]);
let info;
try {
info = lstatSync(current);
} catch {
refuse(`path is missing: ${relativePath}`);
}
if (info.isSymbolicLink()) refuse(`symlinks are not allowed: ${relativePath}`);
const last = i === parts.length - 1;
if (last ? (directory ? !info.isDirectory() : !info.isFile()) : !info.isDirectory()) {
refuse(`path must be a regular contained ${directory ? "directory" : "file"}: ${relativePath}`);
}
}
assertPathStaysInRoot(resolvedRoot, current, relativePath);
return current;
}
function validateSkillSuiteManifest(manifest) {
if (!isPlainObject(manifest)) refuse("skill suite manifest must be an object");
for (const key of ["id", "version", "name", "description"]) {
if (typeof manifest[key] !== "string" || !manifest[key].trim()) {
refuse(`skill suite manifest missing ${key}`);
}
}
if (!Array.isArray(manifest.skills) || manifest.skills.length === 0) {
refuse("skill suite manifest skills must be a nonempty array");
}
const names = new Set();
const commands = new Set();
for (const skill of manifest.skills) {
if (!isPlainObject(skill)) refuse("skill suite entry must be an object");
const extra = Object.keys(skill).filter((key) => !["name", "path", "ownedCommands", "intent"].includes(key));
if (extra.length > 0) refuse(`skill suite entry has unknown field(s): ${extra.join(", ")}`);
if (typeof skill.name !== "string" || !SKILL_NAME.test(skill.name)) {
refuse(`invalid skill name: ${skill.name ?? ""}`);
}
if (names.has(skill.name)) refuse(`duplicate skill name: ${skill.name}`);
names.add(skill.name);
posixContained(skill.path, `skill path for ${skill.name}`);
if (!skill.path.startsWith("./skills/") || skill.path !== `./skills/${skill.name}`) {
refuse(`skill path must be ./skills/${skill.name}`);
}
if (typeof skill.intent !== "string" || skill.intent.length < 10 || skill.intent.length > 200) {
refuse(`skill ${skill.name} intent must be 10-200 characters`);
}
if (!Array.isArray(skill.ownedCommands) || skill.ownedCommands.some((cmd) => typeof cmd !== "string" || !COMMAND_NAME.test(cmd))) {
refuse(`skill ${skill.name} ownedCommands must be command tokens`);
}
for (const command of skill.ownedCommands) {
if (commands.has(command)) refuse(`command ${command} is owned by multiple skills`);
commands.add(command);
}
}
return manifest;
}
/**
* Read and validate the committed skill-suite manifest. Missing, symlink,
* malformed, or shape-invalid manifests fail closed.
*
* @param {string} repoRoot
* @returns {{ id: string, version: string, name: string, description: string, skills: Array<{ name: string, path: string, ownedCommands: string[], intent: string }> }}
*/
export function loadSkillSuiteManifest(repoRoot) {
const root = resolve(repoRoot);
const fullPath = assertRegularContainedPath(root, MANIFEST_REL);
let parsed;
try {
parsed = JSON.parse(readFileSync(fullPath, "utf8"));
} catch {
refuse("skill suite manifest is malformed");
}
return validateSkillSuiteManifest(parsed);
}
function listFilesRecursive(dir, relativeBase = "") {
const results = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const rel = relativeBase ? `${relativeBase}/${entry.name}` : entry.name;
const full = join(dir, entry.name);
if (entry.isSymbolicLink() || lstatSync(full).isSymbolicLink()) {
refuse(`symlinks are not allowed: ${rel}`);
}
if (entry.isDirectory()) results.push(...listFilesRecursive(full, rel));
else if (entry.isFile()) results.push(rel);
else refuse(`unsupported skill artifact: ${rel}`);
}
return results.sort();
}
function ownedSkillDir(root, skillName) {
return assertRegularContainedPath(root, `plugins/kxm/skills/${skillName}`, { directory: true });
}
/**
* Return generated skill-mirror artifact paths relative to the repo root.
*
* @param {string} [repoRoot]
* @returns {string[]}
*/
export function listSkillMirrorArtifacts(repoRoot = process.cwd()) {
const root = resolve(repoRoot);
const suiteManifest = loadSkillSuiteManifest(root);
const artifacts = [];
for (const skill of suiteManifest.skills) {
const srcSkillDir = ownedSkillDir(root, skill.name);
const files = listFilesRecursive(srcSkillDir);
if (!files.includes("SKILL.md")) refuse(`SKILL.md missing for ${skill.name}`);
for (const rel of files) artifacts.push(`.agents/skills/${skill.name}/${rel}`);
}
return artifacts;
}
/**
* Emit the portable .agents/skills mirror from the authored plugin skills tree
* and update the AGENTS.md command block. Owned skills are replaced; unrelated
* skills are preserved. Missing or invalid manifests fail closed.
*
* @param {string} [repoRoot]
*/
export function emitCodexArtifacts(repoRoot = process.cwd()) {
const root = resolve(repoRoot);
const suiteManifest = loadSkillSuiteManifest(root);
const destSkillsDir = join(root, ".agents", "skills");
assertExistingAncestorsAreRegularContained(root, ".agents/skills");
mkdirSync(destSkillsDir, { recursive: true });
assertExistingAncestorsAreRegularContained(root, ".agents/skills", { required: true });
const destRoot = realpathSync(destSkillsDir);
const repoReal = containedRoot(root);
const repoPrefix = repoReal.endsWith(sep) ? repoReal : `${repoReal}${sep}`;
if (destRoot !== repoReal && !destRoot.startsWith(repoPrefix)) {
refuse("path escapes repository: .agents/skills");
}
const ownedSkillNames = suiteManifest.skills.map((skill) => skill.name);
const emittedSkillPaths = [];
const preservedSkillNames = [];
for (const skill of suiteManifest.skills) {
const srcSkillDir = ownedSkillDir(root, skill.name);
listFilesRecursive(srcSkillDir);
const destSkillPath = join(destSkillsDir, skill.name);
if (existsSync(destSkillPath)) {
if (lstatSync(destSkillPath).isSymbolicLink()) {
refuse(`symlinks are not allowed: .agents/skills/${skill.name}`);
}
rmSync(destSkillPath, { recursive: true, force: true });
}
cpSync(srcSkillDir, destSkillPath, {
recursive: true,
dereference: false,
});
if (lstatSync(destSkillPath).isSymbolicLink()) {
rmSync(destSkillPath, { recursive: true, force: true });
refuse(`symlinks are not allowed: .agents/skills/${skill.name}`);
}
const realDest = realpathSync(destSkillPath);
const prefix = destRoot.endsWith(sep) ? destRoot : `${destRoot}${sep}`;
if (realDest !== destRoot && !realDest.startsWith(prefix)) {
rmSync(destSkillPath, { recursive: true, force: true });
refuse(`path escapes repository: .agents/skills/${skill.name}`);
}
listFilesRecursive(destSkillPath);
emittedSkillPaths.push(destSkillPath);
}
for (const entry of readdirSync(destSkillsDir, { withFileTypes: true })) {
if (entry.isDirectory() && !ownedSkillNames.includes(entry.name)) {
preservedSkillNames.push(entry.name);
}
}
const agentsPath = join(root, "AGENTS.md");
let agentsUpdated = false;
if (existsSync(agentsPath)) {
if (lstatSync(agentsPath).isSymbolicLink()) refuse("AGENTS.md must be a regular file");
const original = readFileSync(agentsPath, "utf8");
let updated;
if (original.includes(MARKER_START) && original.includes(MARKER_END)) {
const startIdx = original.indexOf(MARKER_START);
const endIdx = original.indexOf(MARKER_END) + MARKER_END.length;
updated = original.slice(0, startIdx) + CODEX_COMMANDS_BLOCK + original.slice(endIdx);
} else {
const doNotIdx = original.indexOf("## Do not");
if (doNotIdx !== -1) {
updated = `${original.slice(0, doNotIdx)}${CODEX_COMMANDS_BLOCK}\n\n${original.slice(doNotIdx)}`;
} else {
updated = `${original.trimEnd()}\n\n${CODEX_COMMANDS_BLOCK}\n`;
}
}
if (updated !== original) {
writeFileSync(agentsPath, updated);
agentsUpdated = true;
}
}
return {
destSkillsDir,
agentsPath,
agentsUpdated,
ownedSkills: ownedSkillNames,
preservedSkills: preservedSkillNames,
emittedSkillPaths,
artifacts: listSkillMirrorArtifacts(root),
};
}
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
const result = emitCodexArtifacts();
process.stdout.write(
`Codex artifacts emitted: skills -> ${result.destSkillsDir}, ` +
`owned=${result.ownedSkills.length}, preserved=${result.preservedSkills.length}, ` +
`AGENTS.md updated -> ${result.agentsUpdated}\n`,
);
}