@kontextmind/kxm 0.7.144 → 0.7.146
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.kxm/README.md +20 -2
- package/.kxm/agents/{coordinator.yaml → planner.yaml} +2 -0
- package/.kxm/agents/{critic-arch.yaml → reviewer-arch.yaml} +2 -0
- package/.kxm/agents/{critic-cli.yaml → reviewer-cli.yaml} +2 -0
- package/.kxm/agents/{implementer.yaml → writer.yaml} +2 -0
- package/.kxm/roles/planner.yaml +4 -2
- package/.kxm/roles/reviewer-arch.yaml +4 -2
- package/.kxm/roles/reviewer-cli.yaml +4 -2
- package/.kxm/roles/writer.yaml +6 -3
- package/.kxm/workflows/default.yaml +20 -12
- package/.kxm/workflows/land.yaml +1 -1
- package/.kxm/workflows/{review-arch-only.yaml → reviewer-arch-only.yaml} +7 -3
- package/.kxm/workflows/{review-cli-only.yaml → reviewer-cli-only.yaml} +7 -3
- package/.kxm/workflows/{implement-only.yaml → writer-only.yaml} +8 -4
- package/CHANGELOG.md +22 -5
- package/docs/contributing/harness-routing-internals.md +17 -11
- package/docs/reference/cli-reference.md +23 -24
- package/docs/reference/config-reference.md +69 -62
- package/docs/reference/harness-routing.md +6 -5
- package/docs/reference/workflow-catalog.md +3 -3
- package/package.json +3 -3
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +1144 -804
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +636 -308
- package/plugins/kxm/dist/runtime.js +707 -379
- package/plugins/kxm/dist/server.js +49 -5
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +3 -3
- package/plugins/kxm/src/cli/project.ts +2 -1
- package/plugins/kxm/src/cli/roles.ts +3 -4
- package/plugins/kxm/src/engine.ts +9 -6
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/policy-draft.mjs +11 -5
- package/plugins/kxm/src/project-config.ts +49 -11
- package/plugins/kxm/src/runtime-service.ts +2 -1
- package/plugins/kxm/src/template.ts +39 -10
- package/plugins/kxm/src/workflow-manager.ts +34 -28
- package/plugins/kxm/src/workforce-names.d.mts +38 -0
- package/plugins/kxm/src/workforce-names.mjs +317 -0
- package/schemas/agent.schema.json +7 -0
- package/schemas/model.schema.json +12 -0
- package/schemas/workflow.schema.json +14 -0
- package/scripts/harness-run.mjs +1 -1
- package/scripts/native-critic.mjs +5 -5
- package/scripts/roster-policy.mjs +9 -3
- package/scripts/workforce-lint.mjs +18 -0
|
@@ -14781,9 +14781,9 @@ var require_dist = __commonJS({
|
|
|
14781
14781
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
14782
14782
|
import { spawn as spawn3 } from "node:child_process";
|
|
14783
14783
|
import { createHash as createHash15, createHmac, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
14784
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
14784
|
+
import { chmodSync as chmodSync2, existsSync as existsSync17, lstatSync as lstatSync6, mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync5, rmSync as rmSync4, writeFileSync as writeFileSync9 } from "node:fs";
|
|
14785
14785
|
import { createServer } from "node:http";
|
|
14786
|
-
import { dirname as dirname11, isAbsolute as isAbsolute8, join as
|
|
14786
|
+
import { dirname as dirname11, isAbsolute as isAbsolute8, join as join19 } from "node:path";
|
|
14787
14787
|
|
|
14788
14788
|
// plugins/kxm/src/repo-root.ts
|
|
14789
14789
|
import { existsSync } from "node:fs";
|
|
@@ -14813,8 +14813,8 @@ function findKxmRepoRoot(fromUrl = import.meta.url) {
|
|
|
14813
14813
|
var import__ = __toESM(require__(), 1);
|
|
14814
14814
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
14815
14815
|
import { createHash as createHash3 } from "node:crypto";
|
|
14816
|
-
import { existsSync as
|
|
14817
|
-
import { basename, dirname as dirname2, extname, isAbsolute, join as
|
|
14816
|
+
import { existsSync as existsSync4, lstatSync, readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync } from "node:fs";
|
|
14817
|
+
import { basename, dirname as dirname2, extname, isAbsolute, join as join3, relative, resolve, sep } from "node:path";
|
|
14818
14818
|
|
|
14819
14819
|
// plugins/kxm/src/restricted-yaml.mjs
|
|
14820
14820
|
var import_yaml = __toESM(require_dist(), 1);
|
|
@@ -14966,6 +14966,7 @@ var MODES = Object.freeze(["headless", "interactive", "either"]);
|
|
|
14966
14966
|
var MODEL_KEYS = Object.freeze([
|
|
14967
14967
|
"schema",
|
|
14968
14968
|
"id",
|
|
14969
|
+
"aliases",
|
|
14969
14970
|
"harness",
|
|
14970
14971
|
"model",
|
|
14971
14972
|
"vendor",
|
|
@@ -15166,6 +15167,9 @@ function validateModelShape(document, id, label, issues) {
|
|
|
15166
15167
|
if (document.id !== void 0 && document.id !== id) {
|
|
15167
15168
|
issues.push(issue2("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
|
|
15168
15169
|
}
|
|
15170
|
+
if (document.aliases !== void 0 && !identifierList(document.aliases)) {
|
|
15171
|
+
issues.push(issue2("schema", "schema_pattern", label, "aliases must be unique identifiers"));
|
|
15172
|
+
}
|
|
15169
15173
|
if (document.harness !== void 0 && !identifier(document.harness)) {
|
|
15170
15174
|
issues.push(issue2("schema", "schema_pattern", label, "harness must be an identifier"));
|
|
15171
15175
|
}
|
|
@@ -15428,9 +15432,9 @@ function validateRoleSemantics(role, id, label, models, options, issues) {
|
|
|
15428
15432
|
}
|
|
15429
15433
|
if (id === role.purpose && CRITIC_PURPOSES.includes(role.purpose)) {
|
|
15430
15434
|
const admitted = role.roster.map((entry) => models.get(entry.route)).filter((model) => model && admittedRoute(model));
|
|
15431
|
-
if (admitted.length
|
|
15432
|
-
issues.push(issue2("semantic", "critic_route_count", label, "required critic purpose files must list
|
|
15433
|
-
} else if (admitted
|
|
15435
|
+
if (admitted.length < 1) {
|
|
15436
|
+
issues.push(issue2("semantic", "critic_route_count", label, "required critic purpose files must list at least one admitted route"));
|
|
15437
|
+
} else if (role.permission !== "read-only" || admitted.some((model) => model.permissions.length !== 1 || model.permissions[0] !== "read-only")) {
|
|
15434
15438
|
issues.push(issue2("semantic", "permission_escalation", label, "critic purpose files must be read-only"));
|
|
15435
15439
|
}
|
|
15436
15440
|
}
|
|
@@ -15462,7 +15466,7 @@ function validateCriticVendors(roles, models, options, issues) {
|
|
|
15462
15466
|
const role = roles.get(purpose);
|
|
15463
15467
|
if (!role || role.purpose !== purpose) continue;
|
|
15464
15468
|
const admitted = (role.roster ?? []).map((entry) => models.get(entry.route)).filter((model) => model && admittedRoute(model));
|
|
15465
|
-
if (admitted.length
|
|
15469
|
+
if (admitted.length >= 1) critics.push({ purpose, vendor: canonicalVendor(admitted[0].vendor, aliases) });
|
|
15466
15470
|
}
|
|
15467
15471
|
if (critics.length === 2 && critics[0].vendor === critics[1].vendor) {
|
|
15468
15472
|
issues.push(issue2("semantic", "critic_vendor_collision", "roles/reviewer-arch", "required critics must have independent vendors"));
|
|
@@ -15525,8 +15529,272 @@ function validatePolicyDraft(input, options) {
|
|
|
15525
15529
|
return { ok: true, data };
|
|
15526
15530
|
}
|
|
15527
15531
|
|
|
15528
|
-
// plugins/kxm/src/
|
|
15532
|
+
// plugins/kxm/src/workforce-names.mjs
|
|
15529
15533
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
15534
|
+
import { existsSync as existsSync2, readdirSync, readFileSync } from "node:fs";
|
|
15535
|
+
import { join as join2 } from "node:path";
|
|
15536
|
+
var ROSTER_EFFORTS = /* @__PURE__ */ new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
15537
|
+
var ROLE_IDS = Object.freeze([
|
|
15538
|
+
"writer",
|
|
15539
|
+
"planner",
|
|
15540
|
+
"reviewer-arch",
|
|
15541
|
+
"reviewer-cli",
|
|
15542
|
+
"experiment"
|
|
15543
|
+
]);
|
|
15544
|
+
var ROUTE_RENAMES = Object.freeze([
|
|
15545
|
+
["grok-native", "grok-grok-4-7"],
|
|
15546
|
+
["qwen-openrouter-pi", "pi-qwen3-coder-plus-openrouter"],
|
|
15547
|
+
["gemini-agy", "agy-gemini-3-8-flash-high"],
|
|
15548
|
+
["fable-claude", "claude-fable"],
|
|
15549
|
+
["sol-codex", "codex-gpt-5-6-sol"],
|
|
15550
|
+
["grok-default", "grok-grok-4-6"],
|
|
15551
|
+
["fable-default", "claude-fable"]
|
|
15552
|
+
]);
|
|
15553
|
+
var AGENT_RENAMES = Object.freeze([
|
|
15554
|
+
["coordinator", "planner"],
|
|
15555
|
+
["implementer", "writer"],
|
|
15556
|
+
["critic-arch", "reviewer-arch"],
|
|
15557
|
+
["critic-cli", "reviewer-cli"]
|
|
15558
|
+
]);
|
|
15559
|
+
var WORKFLOW_RENAMES = Object.freeze([
|
|
15560
|
+
["implement-only", "writer-only"],
|
|
15561
|
+
["review-arch-only", "reviewer-arch-only"],
|
|
15562
|
+
["review-cli-only", "reviewer-cli-only"]
|
|
15563
|
+
]);
|
|
15564
|
+
var STEP_RENAMES = Object.freeze([
|
|
15565
|
+
["implement", "writer"],
|
|
15566
|
+
["critic-arch", "reviewer-arch"],
|
|
15567
|
+
["review-arch", "reviewer-arch"],
|
|
15568
|
+
["critic-cli", "reviewer-cli"],
|
|
15569
|
+
["review-cli", "reviewer-cli"]
|
|
15570
|
+
]);
|
|
15571
|
+
var RENAMES = Object.freeze({
|
|
15572
|
+
route: ROUTE_RENAMES,
|
|
15573
|
+
agent: AGENT_RENAMES,
|
|
15574
|
+
workflow: WORKFLOW_RENAMES,
|
|
15575
|
+
step: STEP_RENAMES
|
|
15576
|
+
});
|
|
15577
|
+
var RESERVED_WORKFLOW_IDS = /* @__PURE__ */ new Set(["default", "land"]);
|
|
15578
|
+
var warned = /* @__PURE__ */ new Set();
|
|
15579
|
+
function noteDeprecatedId(kind, from, to) {
|
|
15580
|
+
const key = `${kind}\0${from}\0${to}`;
|
|
15581
|
+
if (warned.has(key)) return;
|
|
15582
|
+
warned.add(key);
|
|
15583
|
+
if (process.env.KXM_QUIET_ALIASES === "1") return;
|
|
15584
|
+
console.warn(`kxm: deprecated ${kind} id '${from}' resolves to '${to}'`);
|
|
15585
|
+
}
|
|
15586
|
+
function resolveRenamedId(requested, knownIds, pairs) {
|
|
15587
|
+
if (typeof requested !== "string" || knownIds.has(requested)) return requested;
|
|
15588
|
+
for (const [oldId, newId2] of pairs) {
|
|
15589
|
+
if (requested === oldId && knownIds.has(newId2)) return newId2;
|
|
15590
|
+
if (requested === newId2 && knownIds.has(oldId)) return oldId;
|
|
15591
|
+
}
|
|
15592
|
+
return requested;
|
|
15593
|
+
}
|
|
15594
|
+
function canonicalRouteId(harness, model) {
|
|
15595
|
+
if (typeof harness !== "string" || typeof model !== "string" || !harness || !model) return void 0;
|
|
15596
|
+
const parts = model.split("/").filter((part) => part.length > 0);
|
|
15597
|
+
if (parts.length === 0) return void 0;
|
|
15598
|
+
const slug = parts[parts.length - 1].replaceAll(".", "-").toLowerCase();
|
|
15599
|
+
const provider = parts.length > 1 ? parts[0].replaceAll(".", "-").toLowerCase() : void 0;
|
|
15600
|
+
const id = provider ? `${harness}-${slug}-${provider}` : `${harness}-${slug}`;
|
|
15601
|
+
return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(id) && id.length <= 64 ? id : void 0;
|
|
15602
|
+
}
|
|
15603
|
+
function aliasList(value) {
|
|
15604
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
15605
|
+
}
|
|
15606
|
+
function lookupById(records, requested, kind) {
|
|
15607
|
+
const list = [...records].filter((record2) => typeof record2?.id === "string" && record2.id.length > 0);
|
|
15608
|
+
const known = new Set(list.map((record2) => record2.id));
|
|
15609
|
+
const direct = list.find((record2) => record2.id === requested);
|
|
15610
|
+
if (direct) return { record: direct, viaAlias: false };
|
|
15611
|
+
for (const record2 of list) {
|
|
15612
|
+
if (aliasList(record2.aliases).includes(requested)) {
|
|
15613
|
+
noteDeprecatedId(kind, requested, record2.id);
|
|
15614
|
+
return { record: record2, viaAlias: true };
|
|
15615
|
+
}
|
|
15616
|
+
}
|
|
15617
|
+
const renamed = resolveRenamedId(requested, known, RENAMES[kind] ?? []);
|
|
15618
|
+
if (renamed !== requested) {
|
|
15619
|
+
const record2 = list.find((item) => item.id === renamed);
|
|
15620
|
+
if (record2) {
|
|
15621
|
+
noteDeprecatedId(kind, requested, renamed);
|
|
15622
|
+
return { record: record2, viaAlias: true };
|
|
15623
|
+
}
|
|
15624
|
+
}
|
|
15625
|
+
return void 0;
|
|
15626
|
+
}
|
|
15627
|
+
function findYamlBasename(directory, requested, kind) {
|
|
15628
|
+
if (typeof requested !== "string" || !requested) return void 0;
|
|
15629
|
+
if (existsSync2(join2(directory, `${requested}.yaml`))) return requested;
|
|
15630
|
+
if (!existsSync2(directory)) return void 0;
|
|
15631
|
+
const found = [];
|
|
15632
|
+
for (const name of readdirSync(directory)) {
|
|
15633
|
+
if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
|
|
15634
|
+
const id = name.slice(0, -5);
|
|
15635
|
+
let doc;
|
|
15636
|
+
try {
|
|
15637
|
+
doc = import_yaml2.default.parse(readFileSync(join2(directory, name), "utf8"));
|
|
15638
|
+
} catch {
|
|
15639
|
+
continue;
|
|
15640
|
+
}
|
|
15641
|
+
const aliases = doc && typeof doc === "object" ? aliasList(doc.aliases) : [];
|
|
15642
|
+
found.push({ id, aliases });
|
|
15643
|
+
}
|
|
15644
|
+
const hit = lookupById(found, requested, kind);
|
|
15645
|
+
return hit?.record.id;
|
|
15646
|
+
}
|
|
15647
|
+
function readYamlDir(directory, skipInventory) {
|
|
15648
|
+
if (!existsSync2(directory)) return [];
|
|
15649
|
+
const docs = [];
|
|
15650
|
+
for (const name of readdirSync(directory)) {
|
|
15651
|
+
if (!name.endsWith(".yaml")) continue;
|
|
15652
|
+
const id = name.slice(0, -5);
|
|
15653
|
+
if (skipInventory && id === "inventory") continue;
|
|
15654
|
+
let doc;
|
|
15655
|
+
try {
|
|
15656
|
+
doc = import_yaml2.default.parse(readFileSync(join2(directory, name), "utf8"));
|
|
15657
|
+
} catch (error) {
|
|
15658
|
+
docs.push({ id, file: `${directory}/${name}`, error: error instanceof Error ? error.message : "invalid YAML" });
|
|
15659
|
+
continue;
|
|
15660
|
+
}
|
|
15661
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
15662
|
+
docs.push({ id, file: `${directory}/${name}`, error: "root must be a mapping" });
|
|
15663
|
+
continue;
|
|
15664
|
+
}
|
|
15665
|
+
docs.push({ id, file: name, doc });
|
|
15666
|
+
}
|
|
15667
|
+
return docs;
|
|
15668
|
+
}
|
|
15669
|
+
function push(bucket, severity, code, file, message) {
|
|
15670
|
+
bucket.push({ severity, code, file, message });
|
|
15671
|
+
}
|
|
15672
|
+
function selectorsFor(doc) {
|
|
15673
|
+
const model = typeof doc.model === "string" ? doc.model : "";
|
|
15674
|
+
const vendor = typeof doc.vendor === "string" ? doc.vendor : "";
|
|
15675
|
+
const harness = typeof doc.harness === "string" ? doc.harness : "";
|
|
15676
|
+
return [model, vendor && model ? `${vendor}/${model}` : "", harness && model ? `${harness}/${model}` : ""].filter(Boolean);
|
|
15677
|
+
}
|
|
15678
|
+
function lintWorkforce(root) {
|
|
15679
|
+
const errors = [];
|
|
15680
|
+
const warnings = [];
|
|
15681
|
+
const models = readYamlDir(join2(root, ".kxm", "models"), true).filter((entry) => !entry.error);
|
|
15682
|
+
for (const entry of readYamlDir(join2(root, ".kxm", "models"), true)) {
|
|
15683
|
+
if (entry.error) push(errors, "error", "invalid_yaml", `.kxm/models/${entry.id}.yaml`, entry.error);
|
|
15684
|
+
}
|
|
15685
|
+
const roles = readYamlDir(join2(root, ".kxm", "roles"), false).filter((entry) => !entry.error);
|
|
15686
|
+
const agents = readYamlDir(join2(root, ".kxm", "agents"), false).filter((entry) => !entry.error);
|
|
15687
|
+
const workflows = readYamlDir(join2(root, ".kxm", "workflows"), false).filter((entry) => !entry.error);
|
|
15688
|
+
let admitted = [];
|
|
15689
|
+
const routesFile = join2(root, ".kxm", "routes.yaml");
|
|
15690
|
+
if (existsSync2(routesFile)) {
|
|
15691
|
+
try {
|
|
15692
|
+
const policy = import_yaml2.default.parse(readFileSync(routesFile, "utf8"));
|
|
15693
|
+
admitted = Array.isArray(policy?.admitted) ? policy.admitted.filter((item) => typeof item === "string") : [];
|
|
15694
|
+
} catch (error) {
|
|
15695
|
+
push(errors, "error", "invalid_yaml", ".kxm/routes.yaml", error instanceof Error ? error.message : "invalid YAML");
|
|
15696
|
+
}
|
|
15697
|
+
}
|
|
15698
|
+
const modelRecords = models.map((entry) => ({ id: entry.id, aliases: aliasList(entry.doc.aliases), entry }));
|
|
15699
|
+
const used = /* @__PURE__ */ new Set();
|
|
15700
|
+
for (const role of roles) {
|
|
15701
|
+
const purpose = role.doc.purpose;
|
|
15702
|
+
if (typeof purpose === "string" && role.id !== purpose) {
|
|
15703
|
+
push(errors, "error", "role_id_convention", `.kxm/roles/${role.id}.yaml`, `role id '${role.id}' must equal purpose '${purpose}'`);
|
|
15704
|
+
}
|
|
15705
|
+
const roster = Array.isArray(role.doc.roster) ? role.doc.roster : [];
|
|
15706
|
+
for (const item of roster) {
|
|
15707
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
15708
|
+
const route = item.route;
|
|
15709
|
+
const effort = item.effort;
|
|
15710
|
+
const routeLabel = typeof route === "string" && route ? route : "(missing route)";
|
|
15711
|
+
if (typeof effort !== "string" || !ROSTER_EFFORTS.has(effort)) {
|
|
15712
|
+
const detail = typeof effort === "string" ? `effort '${effort}' is not one of off, minimal, low, medium, high, xhigh, max` : "has no effort";
|
|
15713
|
+
push(errors, "error", "roster_effort_required", `.kxm/roles/${role.id}.yaml`, `roster route '${routeLabel}' ${detail}`);
|
|
15714
|
+
}
|
|
15715
|
+
if (typeof route !== "string" || !route) continue;
|
|
15716
|
+
const found = lookupById(modelRecords, route, "route");
|
|
15717
|
+
if (!found) {
|
|
15718
|
+
push(errors, "error", "route_unknown", `.kxm/roles/${role.id}.yaml`, `roster route '${route}' does not match a model file`);
|
|
15719
|
+
continue;
|
|
15720
|
+
}
|
|
15721
|
+
used.add(found.record.id);
|
|
15722
|
+
if (found.viaAlias) {
|
|
15723
|
+
push(warnings, "warning", "deprecated_id", `.kxm/roles/${role.id}.yaml`, `roster route '${route}' is a deprecated alias of '${found.record.id}'`);
|
|
15724
|
+
}
|
|
15725
|
+
}
|
|
15726
|
+
}
|
|
15727
|
+
const covered = /* @__PURE__ */ new Set();
|
|
15728
|
+
for (const model of models) {
|
|
15729
|
+
const doc = model.doc;
|
|
15730
|
+
if (typeof doc.id === "string" && doc.id !== model.id) {
|
|
15731
|
+
push(errors, "error", "route_id_convention", `.kxm/models/${model.id}.yaml`, `declared id '${doc.id}' does not match the filename`);
|
|
15732
|
+
}
|
|
15733
|
+
const expected = canonicalRouteId(doc.harness, doc.model);
|
|
15734
|
+
if (!expected || model.id !== expected) {
|
|
15735
|
+
push(errors, "error", "route_id_convention", `.kxm/models/${model.id}.yaml`, `route id '${model.id}' must be '${expected ?? "<harness>-<model-slug>[-<provider>]"}'`);
|
|
15736
|
+
}
|
|
15737
|
+
if (!used.has(model.id)) {
|
|
15738
|
+
push(errors, "error", "route_unused", `.kxm/models/${model.id}.yaml`, `route '${model.id}' is not on any role roster`);
|
|
15739
|
+
}
|
|
15740
|
+
const selectorHit = selectorsFor(doc).find((selector) => admitted.includes(selector));
|
|
15741
|
+
if (!selectorHit) {
|
|
15742
|
+
push(errors, "error", "route_not_admitted", `.kxm/models/${model.id}.yaml`, `model '${doc.model ?? ""}' is not an admitted selector`);
|
|
15743
|
+
} else {
|
|
15744
|
+
covered.add(selectorHit);
|
|
15745
|
+
}
|
|
15746
|
+
}
|
|
15747
|
+
for (const selector of admitted) {
|
|
15748
|
+
if (!covered.has(selector)) {
|
|
15749
|
+
push(warnings, "warning", "admitted_unrouted", ".kxm/routes.yaml", `admitted model '${selector}' is not used by any route`);
|
|
15750
|
+
}
|
|
15751
|
+
}
|
|
15752
|
+
const agentRecords = agents.map((entry) => ({ id: entry.id, aliases: aliasList(entry.doc.aliases), entry }));
|
|
15753
|
+
for (const agent of agents) {
|
|
15754
|
+
const role = agent.doc.role;
|
|
15755
|
+
if (typeof role !== "string" || agent.id !== role) {
|
|
15756
|
+
push(errors, "error", "agent_id_convention", `.kxm/agents/${agent.id}.yaml`, `agent id '${agent.id}' must equal its role '${role ?? ""}'`);
|
|
15757
|
+
}
|
|
15758
|
+
}
|
|
15759
|
+
for (const workflow of workflows) {
|
|
15760
|
+
if (!RESERVED_WORKFLOW_IDS.has(workflow.id) && !ROLE_IDS.some((role) => workflow.id === `${role}-only`)) {
|
|
15761
|
+
push(errors, "error", "workflow_id_convention", `.kxm/workflows/${workflow.id}.yaml`, `workflow id '${workflow.id}' must be default, land, or <role>-only`);
|
|
15762
|
+
}
|
|
15763
|
+
const steps = Array.isArray(workflow.doc.steps) ? workflow.doc.steps : [];
|
|
15764
|
+
const realIds = new Set(steps.map((step) => step && typeof step.id === "string" ? step.id : "").filter(Boolean));
|
|
15765
|
+
for (const step of steps) {
|
|
15766
|
+
if (!step || typeof step !== "object" || typeof step.id !== "string") continue;
|
|
15767
|
+
if (step.kind === "agent" || step.kind === "moa") {
|
|
15768
|
+
const agentName = typeof step.agent === "string" ? step.agent : "";
|
|
15769
|
+
const found = agentName ? lookupById(agentRecords, agentName, "agent") : void 0;
|
|
15770
|
+
if (!found) {
|
|
15771
|
+
push(errors, "error", "agent_unknown", `.kxm/workflows/${workflow.id}.yaml`, `step '${step.id}' references unknown agent '${agentName}'`);
|
|
15772
|
+
} else if (found.viaAlias) {
|
|
15773
|
+
push(warnings, "warning", "deprecated_id", `.kxm/workflows/${workflow.id}.yaml`, `step '${step.id}' agent '${agentName}' is a deprecated alias of '${found.record.id}'`);
|
|
15774
|
+
}
|
|
15775
|
+
const role = found?.record.entry.doc.role;
|
|
15776
|
+
if (typeof role === "string" && step.id !== role) {
|
|
15777
|
+
push(errors, "error", "step_id_convention", `.kxm/workflows/${workflow.id}.yaml`, `agent step '${step.id}' must use the role id '${role}'`);
|
|
15778
|
+
}
|
|
15779
|
+
} else if (step.kind === "gate") {
|
|
15780
|
+
const gate = typeof step.gate === "string" ? step.gate : "";
|
|
15781
|
+
const allowed = step.id === "verify" || step.id === gate || step.id.startsWith(`${workflow.id}-`);
|
|
15782
|
+
if (!allowed) {
|
|
15783
|
+
push(errors, "error", "step_id_convention", `.kxm/workflows/${workflow.id}.yaml`, `gate step '${step.id}' must be verify, the gate id, or ${workflow.id}-<name>`);
|
|
15784
|
+
}
|
|
15785
|
+
}
|
|
15786
|
+
for (const alias of aliasList(step.aliases)) {
|
|
15787
|
+
if (realIds.has(alias) && alias !== step.id) {
|
|
15788
|
+
push(errors, "error", "step_id_convention", `.kxm/workflows/${workflow.id}.yaml`, `step alias '${alias}' collides with another step id`);
|
|
15789
|
+
}
|
|
15790
|
+
}
|
|
15791
|
+
}
|
|
15792
|
+
}
|
|
15793
|
+
return { errors, warnings };
|
|
15794
|
+
}
|
|
15795
|
+
|
|
15796
|
+
// plugins/kxm/src/template.ts
|
|
15797
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
15530
15798
|
import { createHash as createHash2 } from "node:crypto";
|
|
15531
15799
|
var KXM_TEMPLATE_ID = "builtin-minimal";
|
|
15532
15800
|
var KXM_TEMPLATE_PROVENANCE_PATH = ".kxm/template-provenance.yaml";
|
|
@@ -15663,33 +15931,60 @@ function coreTemplate(projectId, projectName, variant) {
|
|
|
15663
15931
|
]);
|
|
15664
15932
|
if (variant === "v4-registry") {
|
|
15665
15933
|
const coordinator = files.get(".kxm/agents/coordinator.yaml");
|
|
15666
|
-
if (coordinator)
|
|
15934
|
+
if (coordinator) {
|
|
15935
|
+
files.delete(".kxm/agents/coordinator.yaml");
|
|
15936
|
+
files.set(".kxm/agents/planner.yaml", { ...coordinator, role: "planner", aliases: ["coordinator"] });
|
|
15937
|
+
}
|
|
15667
15938
|
const implementer = files.get(".kxm/agents/implementer.yaml");
|
|
15668
|
-
if (implementer)
|
|
15939
|
+
if (implementer) {
|
|
15940
|
+
files.delete(".kxm/agents/implementer.yaml");
|
|
15941
|
+
files.set(".kxm/agents/writer.yaml", { ...implementer, role: "writer", aliases: ["implementer"] });
|
|
15942
|
+
}
|
|
15669
15943
|
const workflow = files.get(".kxm/workflows/default.yaml");
|
|
15670
15944
|
if (workflow) {
|
|
15671
15945
|
const limits = { ...workflow.limits };
|
|
15672
15946
|
delete limits.maxAgentTimeMs;
|
|
15673
|
-
|
|
15947
|
+
const steps = workflow.steps.map((step) => {
|
|
15948
|
+
if (step.id === "plan") {
|
|
15949
|
+
const on = { ...step.on, passed: "writer" };
|
|
15950
|
+
return { ...step, agent: "planner", on };
|
|
15951
|
+
}
|
|
15952
|
+
if (step.id === "implement") {
|
|
15953
|
+
const assignments = { ...step.assignments, allowedAgents: ["writer"] };
|
|
15954
|
+
return { ...step, id: "writer", aliases: ["implement"], agent: "writer", assignments };
|
|
15955
|
+
}
|
|
15956
|
+
if (step.id === "verify") {
|
|
15957
|
+
const on = { ...step.on };
|
|
15958
|
+
const failure = on["implementation-failure"];
|
|
15959
|
+
if (failure && typeof failure === "object" && !Array.isArray(failure)) {
|
|
15960
|
+
on["implementation-failure"] = { ...failure, target: "writer" };
|
|
15961
|
+
}
|
|
15962
|
+
return { ...step, on };
|
|
15963
|
+
}
|
|
15964
|
+
return step;
|
|
15965
|
+
});
|
|
15966
|
+
files.set(".kxm/workflows/default.yaml", { ...workflow, coordinator: "planner", limits, steps });
|
|
15674
15967
|
}
|
|
15675
15968
|
files.set(".kxm/routes.yaml", {
|
|
15676
15969
|
schema: "kxm.routes.v2",
|
|
15677
|
-
updatedAt: "2026-09-
|
|
15970
|
+
updatedAt: "2026-09-27T00:00:00.000Z",
|
|
15678
15971
|
admitted: ["anthropic/fable", "xai/grok-4.6"],
|
|
15679
15972
|
disabled: []
|
|
15680
15973
|
});
|
|
15681
|
-
files.set(".kxm/models/grok-
|
|
15974
|
+
files.set(".kxm/models/grok-grok-4-6.yaml", {
|
|
15682
15975
|
schema: "kxm.model.v2",
|
|
15683
|
-
id: "grok-
|
|
15976
|
+
id: "grok-grok-4-6",
|
|
15977
|
+
aliases: ["grok-default"],
|
|
15684
15978
|
harness: "grok",
|
|
15685
15979
|
model: "grok-4.6",
|
|
15686
15980
|
vendor: "xai",
|
|
15687
15981
|
status: "admitted",
|
|
15688
15982
|
permissions: ["edit"]
|
|
15689
15983
|
});
|
|
15690
|
-
files.set(".kxm/models/fable
|
|
15984
|
+
files.set(".kxm/models/claude-fable.yaml", {
|
|
15691
15985
|
schema: "kxm.model.v2",
|
|
15692
|
-
id: "fable
|
|
15986
|
+
id: "claude-fable",
|
|
15987
|
+
aliases: ["fable-default"],
|
|
15693
15988
|
harness: "claude",
|
|
15694
15989
|
model: "fable",
|
|
15695
15990
|
vendor: "anthropic",
|
|
@@ -15702,7 +15997,7 @@ function coreTemplate(projectId, projectName, variant) {
|
|
|
15702
15997
|
purpose: "writer",
|
|
15703
15998
|
permission: "edit",
|
|
15704
15999
|
description: "Primary implementation agent.",
|
|
15705
|
-
roster: [{ route: "grok-
|
|
16000
|
+
roster: [{ route: "grok-grok-4-6", effort: "medium" }]
|
|
15706
16001
|
});
|
|
15707
16002
|
files.set(".kxm/roles/planner.yaml", {
|
|
15708
16003
|
schema: "kxm.role.v2",
|
|
@@ -15710,7 +16005,7 @@ function coreTemplate(projectId, projectName, variant) {
|
|
|
15710
16005
|
purpose: "planner",
|
|
15711
16006
|
permission: "read-only",
|
|
15712
16007
|
description: "Plans the change before implementation.",
|
|
15713
|
-
roster: [{ route: "fable
|
|
16008
|
+
roster: [{ route: "claude-fable", effort: "medium" }]
|
|
15714
16009
|
});
|
|
15715
16010
|
files.set(".kxm/gates.yaml", {
|
|
15716
16011
|
schema: "kxm.gate-registry.v1",
|
|
@@ -15731,7 +16026,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
|
|
|
15731
16026
|
const values = coreTemplate(projectId, projectName, variant);
|
|
15732
16027
|
const coreFiles = /* @__PURE__ */ new Map();
|
|
15733
16028
|
const records = [...values.entries()].map(([path, value]) => {
|
|
15734
|
-
const bytes = Buffer.from((0,
|
|
16029
|
+
const bytes = Buffer.from((0, import_yaml3.stringify)(value, { lineWidth: 0 }), "utf8");
|
|
15735
16030
|
coreFiles.set(path, bytes);
|
|
15736
16031
|
return {
|
|
15737
16032
|
path,
|
|
@@ -15751,7 +16046,7 @@ function renderKxmTemplate(projectId, projectName, variant = CURRENT_KXM_TEMPLAT
|
|
|
15751
16046
|
const fileEntries = [...coreFiles.entries()];
|
|
15752
16047
|
fileEntries.push([
|
|
15753
16048
|
KXM_TEMPLATE_PROVENANCE_PATH,
|
|
15754
|
-
Buffer.from((0,
|
|
16049
|
+
Buffer.from((0, import_yaml3.stringify)(provenance, { lineWidth: 0 }), "utf8")
|
|
15755
16050
|
]);
|
|
15756
16051
|
fileEntries.sort(([left], [right]) => compareCodeUnits3(left, right));
|
|
15757
16052
|
const files = new Map(fileEntries);
|
|
@@ -15776,7 +16071,7 @@ function resolveKxmTemplateBaseline(value) {
|
|
|
15776
16071
|
|
|
15777
16072
|
// plugins/kxm/src/harness.ts
|
|
15778
16073
|
import { spawnSync } from "node:child_process";
|
|
15779
|
-
import { existsSync as
|
|
16074
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
15780
16075
|
import { win32 as win32Path } from "node:path";
|
|
15781
16076
|
|
|
15782
16077
|
// plugins/kxm/src/oneshot-process.ts
|
|
@@ -16415,7 +16710,7 @@ function findWinNpmInnerExe(cliId, options = {}) {
|
|
|
16415
16710
|
if ((options.platform ?? process.platform) !== "win32") return void 0;
|
|
16416
16711
|
const segments = WIN_NPM_INNER_EXE[cliId];
|
|
16417
16712
|
if (!segments) return void 0;
|
|
16418
|
-
const exists = options.existsSync ??
|
|
16713
|
+
const exists = options.existsSync ?? existsSync3;
|
|
16419
16714
|
const pathEnv = options.pathEnv ?? process.env.PATH ?? "";
|
|
16420
16715
|
for (const dir of pathEnv.split(";").filter(Boolean)) {
|
|
16421
16716
|
if (!exists(win32Path.join(dir, `${cliId}.cmd`))) continue;
|
|
@@ -16943,7 +17238,7 @@ var KxmConfigError = class extends Error {
|
|
|
16943
17238
|
}
|
|
16944
17239
|
};
|
|
16945
17240
|
function defaultKxmSchemaDir() {
|
|
16946
|
-
return
|
|
17241
|
+
return join3(findKxmRepoRoot(import.meta.url), "schemas");
|
|
16947
17242
|
}
|
|
16948
17243
|
var DEFAULT_SCHEMA_DIR = defaultKxmSchemaDir();
|
|
16949
17244
|
var RESOURCE_SCHEMA = Object.freeze({
|
|
@@ -16992,7 +17287,7 @@ function parseRestrictedYaml2(input, label = "<yaml>", limits = KXM_YAML_LIMITS)
|
|
|
16992
17287
|
}
|
|
16993
17288
|
}
|
|
16994
17289
|
function readJsonObject(file) {
|
|
16995
|
-
const parsed = JSON.parse(
|
|
17290
|
+
const parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
16996
17291
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${file} is not a JSON object`);
|
|
16997
17292
|
return parsed;
|
|
16998
17293
|
}
|
|
@@ -17012,10 +17307,10 @@ var KxmSchemaRegistry = class {
|
|
|
17012
17307
|
constructor(schemasDir = DEFAULT_SCHEMA_DIR) {
|
|
17013
17308
|
this.schemasDir = resolve(schemasDir);
|
|
17014
17309
|
this.ajv = new import__.Ajv2020({ allErrors: true, strict: true, strictRequired: false });
|
|
17015
|
-
const common = readJsonObject(
|
|
17310
|
+
const common = readJsonObject(join3(this.schemasDir, "common.schema.json"));
|
|
17016
17311
|
this.ajv.addSchema(common);
|
|
17017
17312
|
for (const definition of Object.values(RESOURCE_SCHEMA)) {
|
|
17018
|
-
this.ajv.addSchema(readJsonObject(
|
|
17313
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, definition.file)));
|
|
17019
17314
|
}
|
|
17020
17315
|
const localBindingsFile = "local-repository-bindings.schema.json";
|
|
17021
17316
|
const templateProvenanceFile = "template-provenance.schema.json";
|
|
@@ -17026,15 +17321,15 @@ var KxmSchemaRegistry = class {
|
|
|
17026
17321
|
const coordinatorFile = "coordinator.schema.json";
|
|
17027
17322
|
const intakeMessageFile = "intake-message.schema.json";
|
|
17028
17323
|
const syncEventFile = "sync-event.schema.json";
|
|
17029
|
-
this.ajv.addSchema(readJsonObject(
|
|
17030
|
-
this.ajv.addSchema(readJsonObject(
|
|
17031
|
-
this.ajv.addSchema(readJsonObject(
|
|
17032
|
-
this.ajv.addSchema(readJsonObject(
|
|
17033
|
-
this.ajv.addSchema(readJsonObject(
|
|
17034
|
-
this.ajv.addSchema(readJsonObject(
|
|
17035
|
-
this.ajv.addSchema(readJsonObject(
|
|
17036
|
-
this.ajv.addSchema(readJsonObject(
|
|
17037
|
-
this.ajv.addSchema(readJsonObject(
|
|
17324
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, localBindingsFile)));
|
|
17325
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, templateProvenanceFile)));
|
|
17326
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, initOperationFile)));
|
|
17327
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, permissionDiffFile)));
|
|
17328
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, runEventFile)));
|
|
17329
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, driveReceiptFile)));
|
|
17330
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, coordinatorFile)));
|
|
17331
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, intakeMessageFile)));
|
|
17332
|
+
this.ajv.addSchema(readJsonObject(join3(this.schemasDir, syncEventFile)));
|
|
17038
17333
|
for (const [kind, definition] of Object.entries(RESOURCE_SCHEMA)) {
|
|
17039
17334
|
const validator = this.ajv.getSchema(`https://schemas.kxm.dev/${definition.file}`);
|
|
17040
17335
|
if (!validator) throw new Error(`schema did not compile: ${definition.file}`);
|
|
@@ -17190,15 +17485,15 @@ function containedHostPath(root, candidate) {
|
|
|
17190
17485
|
function portableBindingIssue(root, pathHint, repositoryId) {
|
|
17191
17486
|
let current = root;
|
|
17192
17487
|
for (const segment of pathHint === "." ? [] : pathHint.split("/")) {
|
|
17193
|
-
current =
|
|
17194
|
-
if (!
|
|
17488
|
+
current = join3(current, segment);
|
|
17489
|
+
if (!existsSync4(current)) break;
|
|
17195
17490
|
const stat = lstatSync(current);
|
|
17196
17491
|
if (stat.isSymbolicLink()) {
|
|
17197
17492
|
return issue3("path", "repository_binding_path_link", ".kxm/project.yaml", `portable pathHint for ${repositoryId} traverses a symbolic link or junction`);
|
|
17198
17493
|
}
|
|
17199
17494
|
}
|
|
17200
17495
|
const binding = resolve(root, ...pathHint.split("/"));
|
|
17201
|
-
if (
|
|
17496
|
+
if (existsSync4(binding)) {
|
|
17202
17497
|
const realRoot = canonicalHostPath(root);
|
|
17203
17498
|
const realBinding = canonicalHostPath(binding);
|
|
17204
17499
|
if (!containedHostPath(realRoot, realBinding)) {
|
|
@@ -17249,7 +17544,7 @@ function readResource(registry, root, file, logicalPath, kind, id, containmentRo
|
|
|
17249
17544
|
parent = dirname2(parent);
|
|
17250
17545
|
}
|
|
17251
17546
|
if (!stat.isFile()) fail2("path", "resource_not_file", label, "configuration resource must be a regular file");
|
|
17252
|
-
return { kind, ...id === void 0 ? {} : { id }, file, logicalPath, value: resourceValue(registry,
|
|
17547
|
+
return { kind, ...id === void 0 ? {} : { id }, file, logicalPath, value: resourceValue(registry, readFileSync2(file), label, kind) };
|
|
17253
17548
|
}
|
|
17254
17549
|
function resourceValue(registry, input, label, kind) {
|
|
17255
17550
|
const value = parseRestrictedYaml2(input, label);
|
|
@@ -17258,14 +17553,14 @@ function resourceValue(registry, input, label, kind) {
|
|
|
17258
17553
|
return value;
|
|
17259
17554
|
}
|
|
17260
17555
|
function readTemplateProvenance(registry, root) {
|
|
17261
|
-
const file =
|
|
17262
|
-
if (!
|
|
17556
|
+
const file = join3(root, ".kxm", "template-provenance.yaml");
|
|
17557
|
+
if (!existsSync4(file)) return void 0;
|
|
17263
17558
|
const label = ".kxm/template-provenance.yaml";
|
|
17264
17559
|
const stat = lstatSync(file);
|
|
17265
17560
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
17266
17561
|
fail2("path", "resource_not_file", label, "template provenance must be a regular file, not a link or directory");
|
|
17267
17562
|
}
|
|
17268
|
-
const configRoot =
|
|
17563
|
+
const configRoot = join3(root, ".kxm");
|
|
17269
17564
|
let parent = dirname2(file);
|
|
17270
17565
|
while (parent !== root) {
|
|
17271
17566
|
const parentStat = lstatSync(parent);
|
|
@@ -17275,7 +17570,7 @@ function readTemplateProvenance(registry, root) {
|
|
|
17275
17570
|
if (parent === configRoot) break;
|
|
17276
17571
|
parent = dirname2(parent);
|
|
17277
17572
|
}
|
|
17278
|
-
const value = parseRestrictedYaml2(
|
|
17573
|
+
const value = parseRestrictedYaml2(readFileSync2(file), label);
|
|
17279
17574
|
const issues = registry.validateTemplateProvenance(value, label);
|
|
17280
17575
|
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
17281
17576
|
if (!resolveKxmTemplateBaseline(value)) {
|
|
@@ -17306,37 +17601,37 @@ function readTemplateProvenance(registry, root) {
|
|
|
17306
17601
|
}
|
|
17307
17602
|
function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
|
|
17308
17603
|
const resources = /* @__PURE__ */ new Map();
|
|
17309
|
-
if (!
|
|
17604
|
+
if (!existsSync4(directory)) return resources;
|
|
17310
17605
|
const stat = lstatSync(directory);
|
|
17311
17606
|
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
17312
17607
|
fail2("path", "resource_directory_invalid", displayPath(root, directory), "resource directory must be a regular directory");
|
|
17313
17608
|
}
|
|
17314
17609
|
const issues = [];
|
|
17315
|
-
const entries =
|
|
17610
|
+
const entries = readdirSync2(directory, { withFileTypes: true }).sort((left, right) => compareCodeUnits4(left.name, right.name));
|
|
17316
17611
|
for (const entry of entries) {
|
|
17317
17612
|
if (entry.isDirectory()) {
|
|
17318
|
-
issues.push(issue3("path", "nested_resource_directory", displayPath(root,
|
|
17613
|
+
issues.push(issue3("path", "nested_resource_directory", displayPath(root, join3(directory, entry.name)), "nested resource directories are not allowed"));
|
|
17319
17614
|
continue;
|
|
17320
17615
|
}
|
|
17321
17616
|
if (!entry.name.toLowerCase().endsWith(".yaml") && !entry.name.toLowerCase().endsWith(".yml")) continue;
|
|
17322
17617
|
if (!entry.isFile() || extname(entry.name) !== ".yaml") {
|
|
17323
|
-
issues.push(issue3("path", "resource_filename_invalid", displayPath(root,
|
|
17618
|
+
issues.push(issue3("path", "resource_filename_invalid", displayPath(root, join3(directory, entry.name)), "resource must be a regular file with the exact .yaml extension"));
|
|
17324
17619
|
continue;
|
|
17325
17620
|
}
|
|
17326
17621
|
const id = basename(entry.name, ".yaml");
|
|
17327
17622
|
if (kind === "model" && id === "inventory") continue;
|
|
17328
17623
|
if (id === replacedId) continue;
|
|
17329
17624
|
if (!resourceIdentifier(id)) {
|
|
17330
|
-
issues.push(issue3("path", "resource_id_invalid", displayPath(root,
|
|
17625
|
+
issues.push(issue3("path", "resource_id_invalid", displayPath(root, join3(directory, entry.name)), `filename-derived identity ${id} is invalid or platform-reserved`));
|
|
17331
17626
|
continue;
|
|
17332
17627
|
}
|
|
17333
17628
|
const collision = [...resources.keys()].find((candidate) => candidate.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
17334
17629
|
if (collision) {
|
|
17335
|
-
issues.push(issue3("path", "resource_id_collision", displayPath(root,
|
|
17630
|
+
issues.push(issue3("path", "resource_id_collision", displayPath(root, join3(directory, entry.name)), `${id} case-folds to existing ${collision}`));
|
|
17336
17631
|
continue;
|
|
17337
17632
|
}
|
|
17338
17633
|
try {
|
|
17339
|
-
const resource = readResource(registry, root,
|
|
17634
|
+
const resource = readResource(registry, root, join3(directory, entry.name), `${logicalDirectory}/${id}.yaml`, kind, id);
|
|
17340
17635
|
resources.set(id, resource);
|
|
17341
17636
|
} catch (error) {
|
|
17342
17637
|
if (error instanceof KxmConfigError) issues.push(...error.issues);
|
|
@@ -17365,6 +17660,14 @@ function numberValue(value, fallback) {
|
|
|
17365
17660
|
function names(value) {
|
|
17366
17661
|
return Array.isArray(value) ? value.filter((candidate) => typeof candidate === "string") : [];
|
|
17367
17662
|
}
|
|
17663
|
+
function lookupKxmResource(resources, requested, kind) {
|
|
17664
|
+
const records = [...resources].map((resource) => ({
|
|
17665
|
+
id: resource.id,
|
|
17666
|
+
aliases: names(resource.value.aliases),
|
|
17667
|
+
resource
|
|
17668
|
+
}));
|
|
17669
|
+
return lookupById(records, requested, kind)?.record.resource;
|
|
17670
|
+
}
|
|
17368
17671
|
function mapResource(resource, map, issues) {
|
|
17369
17672
|
const id = resource.id;
|
|
17370
17673
|
if (!id) return;
|
|
@@ -17555,14 +17858,31 @@ function transition(value) {
|
|
|
17555
17858
|
function validateWorkflow(workflow, agents, models, repositories, gates, issues) {
|
|
17556
17859
|
const file = workflow.logicalPath;
|
|
17557
17860
|
const coordinator = stringValue(workflow.value.coordinator) ?? "coordinator";
|
|
17558
|
-
if (!agents.
|
|
17861
|
+
if (!lookupKxmResource(agents.values(), coordinator, "agent")) issues.push(issue3("reference", "coordinator_unknown", file, `coordinator ${coordinator} does not exist`));
|
|
17559
17862
|
const steps = valuesOf(workflow.value, "steps").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
17560
17863
|
const stepIndex = /* @__PURE__ */ new Map();
|
|
17864
|
+
const primarySteps = /* @__PURE__ */ new Set();
|
|
17561
17865
|
for (const [index, step] of steps.entries()) {
|
|
17562
17866
|
const id = stringValue(step.id);
|
|
17563
17867
|
if (!id) continue;
|
|
17564
17868
|
if (stepIndex.has(id)) issues.push(issue3("semantic", "step_id_duplicate", file, `step ${id} is duplicated`));
|
|
17565
|
-
else
|
|
17869
|
+
else {
|
|
17870
|
+
stepIndex.set(id, index);
|
|
17871
|
+
primarySteps.add(id);
|
|
17872
|
+
}
|
|
17873
|
+
}
|
|
17874
|
+
for (const [index, step] of steps.entries()) {
|
|
17875
|
+
const id = stringValue(step.id);
|
|
17876
|
+
if (!id) continue;
|
|
17877
|
+
const aliases = new Set(names(step.aliases));
|
|
17878
|
+
for (const [oldId, newId2] of STEP_RENAMES) {
|
|
17879
|
+
if (id === newId2) aliases.add(oldId);
|
|
17880
|
+
if (id === oldId) aliases.add(newId2);
|
|
17881
|
+
}
|
|
17882
|
+
for (const alias of aliases) {
|
|
17883
|
+
if (alias === id || primarySteps.has(alias) || stepIndex.has(alias)) continue;
|
|
17884
|
+
stepIndex.set(alias, index);
|
|
17885
|
+
}
|
|
17566
17886
|
}
|
|
17567
17887
|
for (const field of ["reproOracle", "planHash"]) {
|
|
17568
17888
|
const oracle = objectValue(workflow.value[field]);
|
|
@@ -17591,7 +17911,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17591
17911
|
const kind = stringValue(step.kind);
|
|
17592
17912
|
validateToolPolicy(workflow, objectValue(step.tools), `${stepId}.tools`, issues);
|
|
17593
17913
|
const primaryAgentId = stringValue(step.agent);
|
|
17594
|
-
if ((kind === "agent" || kind === "moa") && primaryAgentId && !agents.
|
|
17914
|
+
if ((kind === "agent" || kind === "moa") && primaryAgentId && !lookupKxmResource(agents.values(), primaryAgentId, "agent")) {
|
|
17595
17915
|
issues.push(issue3("reference", "agent_unknown", file, `${stepId} references unknown agent ${primaryAgentId}`));
|
|
17596
17916
|
}
|
|
17597
17917
|
const gate = stringValue(step.gate);
|
|
@@ -17618,7 +17938,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17618
17938
|
const declaredAgents = assignment ? names(assignment.allowedAgents) : [];
|
|
17619
17939
|
const allowedAgents = declaredAgents.length > 0 ? declaredAgents : primaryAgentId ? [primaryAgentId] : [];
|
|
17620
17940
|
for (const agentId of allowedAgents) {
|
|
17621
|
-
const agent = agents.
|
|
17941
|
+
const agent = lookupKxmResource(agents.values(), agentId, "agent");
|
|
17622
17942
|
if (!agent) issues.push(issue3("reference", "assignment_agent_unknown", file, `${stepId} allows unknown agent ${agentId}`));
|
|
17623
17943
|
else {
|
|
17624
17944
|
validateAgentScope(agent, step, repositories, file, stepId, issues);
|
|
@@ -17649,8 +17969,8 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17649
17969
|
const writable = Object.values(objectValue(step.repositories) ?? {}).filter((access) => access === "write").length;
|
|
17650
17970
|
if (maxWriteRepositories > writable) issues.push(issue3("semantic", "write_repository_bound_invalid", file, `${stepId} maxWriteRepositories exceeds writable repository scope`));
|
|
17651
17971
|
}
|
|
17652
|
-
const
|
|
17653
|
-
const minimumPassed =
|
|
17972
|
+
const join20 = objectValue(step.join);
|
|
17973
|
+
const minimumPassed = join20 && typeof join20.minimumPassed === "number" ? join20.minimumPassed : void 0;
|
|
17654
17974
|
if (minimumPassed !== void 0 && minimumPassed > maximum) issues.push(issue3("semantic", "join_impossible", file, `${stepId} minimumPassed exceeds assignment maximum`));
|
|
17655
17975
|
const distinctBy = names(assignment?.distinctBy);
|
|
17656
17976
|
if (distinctBy.length > 0) {
|
|
@@ -17675,7 +17995,7 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17675
17995
|
const producerMinimum = numberValue(policy.minimumProducers, 1);
|
|
17676
17996
|
if (producerMinimum > target || producerMinimum > eligible.length) issues.push(issue3("semantic", "producer_minimum_impossible", file, `${stepId}.${String(key)} producer minimum exceeds its eligible or target pool`));
|
|
17677
17997
|
for (const agentId of eligible) {
|
|
17678
|
-
if (!agents.
|
|
17998
|
+
if (!lookupKxmResource(agents.values(), agentId, "agent")) issues.push(issue3("reference", "producer_agent_unknown", file, `${stepId}.${String(key)} references unknown producer ${agentId}`));
|
|
17679
17999
|
if (allowedAgents.length > 0 && !allowedAgents.includes(agentId)) issues.push(issue3("semantic", "producer_agent_ineligible", file, `${stepId}.${String(key)} producer ${agentId} is outside allowedAgents`));
|
|
17680
18000
|
}
|
|
17681
18001
|
const degradation = objectValue(policy.degradation);
|
|
@@ -17696,6 +18016,10 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
17696
18016
|
if (parsed.terminalStatus === "completed") completedTerminalSources.add(index);
|
|
17697
18017
|
continue;
|
|
17698
18018
|
}
|
|
18019
|
+
if (parsed.target && parsed.target !== "$terminal" && !primarySteps.has(parsed.target) && stepIndex.has(parsed.target)) {
|
|
18020
|
+
const canonical = stringValue(steps[stepIndex.get(parsed.target) ?? -1]?.id);
|
|
18021
|
+
if (canonical) noteDeprecatedId("step", parsed.target, canonical);
|
|
18022
|
+
}
|
|
17699
18023
|
const targetIndex = parsed.target === void 0 ? void 0 : stepIndex.get(parsed.target);
|
|
17700
18024
|
if (targetIndex === void 0) {
|
|
17701
18025
|
issues.push(issue3("reference", "transition_target_unknown", file, `${stepId}.${outcome} references unknown target ${String(parsed.target)}`));
|
|
@@ -17850,12 +18174,12 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
17850
18174
|
}
|
|
17851
18175
|
validateModelReferences(models, issues);
|
|
17852
18176
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
17853
|
-
if (!workflows.
|
|
18177
|
+
if (!lookupKxmResource(workflows.values(), defaultWorkflow, "workflow")) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
17854
18178
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
17855
|
-
if (projectRoot &&
|
|
18179
|
+
if (projectRoot && existsSync4(join3(projectRoot, ".kxm", "roster.yaml"))) {
|
|
17856
18180
|
issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
|
|
17857
18181
|
}
|
|
17858
|
-
if (projectRoot &&
|
|
18182
|
+
if (projectRoot && existsSync4(join3(projectRoot, ".kxm", "roles"))) {
|
|
17859
18183
|
issues.push(...developerRolePolicyIssues(projectRoot));
|
|
17860
18184
|
}
|
|
17861
18185
|
if (projectRoot) {
|
|
@@ -17870,11 +18194,12 @@ function rosterModelIssues(projectRoot, roles) {
|
|
|
17870
18194
|
for (const entry of valuesOf(role.value, "roster")) {
|
|
17871
18195
|
const route = stringValue(objectValue(entry)?.route);
|
|
17872
18196
|
if (!route) continue;
|
|
17873
|
-
const
|
|
17874
|
-
const
|
|
18197
|
+
const resolved = findYamlBasename(join3(projectRoot, ".kxm", "models"), route, "route") ?? route;
|
|
18198
|
+
const logical = `.kxm/models/${resolved}.yaml`;
|
|
18199
|
+
const file = join3(projectRoot, ".kxm", "models", `${resolved}.yaml`);
|
|
17875
18200
|
let harness;
|
|
17876
18201
|
try {
|
|
17877
|
-
if (
|
|
18202
|
+
if (existsSync4(file)) harness = stringValue(parseRestrictedYaml2(readFileSync2(file, "utf8"), logical).harness);
|
|
17878
18203
|
} catch {
|
|
17879
18204
|
harness = void 0;
|
|
17880
18205
|
}
|
|
@@ -17900,7 +18225,7 @@ function originHashIssues(projectRoot, models) {
|
|
|
17900
18225
|
}
|
|
17901
18226
|
let actual;
|
|
17902
18227
|
try {
|
|
17903
|
-
actual = createHash3("sha256").update(
|
|
18228
|
+
actual = createHash3("sha256").update(readFileSync2(file, "utf8"), "utf8").digest("hex");
|
|
17904
18229
|
} catch (error) {
|
|
17905
18230
|
const code = error.code;
|
|
17906
18231
|
if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
|
|
@@ -17916,7 +18241,7 @@ function originHashIssues(projectRoot, models) {
|
|
|
17916
18241
|
return issues;
|
|
17917
18242
|
}
|
|
17918
18243
|
function developerCeilings() {
|
|
17919
|
-
const script =
|
|
18244
|
+
const script = join3(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
|
|
17920
18245
|
let loaded;
|
|
17921
18246
|
try {
|
|
17922
18247
|
loaded = spawnSync2(process.execPath, ["--input-type=module", "-e", `const m = await import(${JSON.stringify(pathToFileURL(script).href)}); process.stdout.write(JSON.stringify({ROUTES:m.ROUTES,NATIVE_PI_BRAKE_PROVIDERS:m.NATIVE_PI_BRAKE_PROVIDERS,PI_ALLOWED_PROVIDERS:m.PI_ALLOWED_PROVIDERS,PI_NATIVE_VENDOR_PROVIDERS:m.PI_NATIVE_VENDOR_PROVIDERS}))`], {
|
|
@@ -17943,20 +18268,21 @@ function developerCeilings() {
|
|
|
17943
18268
|
}
|
|
17944
18269
|
function developerRolePolicyIssues(projectRoot) {
|
|
17945
18270
|
if (resolve(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
|
|
17946
|
-
const
|
|
17947
|
-
const
|
|
18271
|
+
const lintIssues = lintWorkforce(projectRoot).errors.map((entry) => issue3("semantic", entry.code, entry.file, entry.message));
|
|
18272
|
+
const rolesDir = join3(projectRoot, ".kxm", "roles");
|
|
18273
|
+
const modelsDir = join3(projectRoot, ".kxm", "models");
|
|
17948
18274
|
const roles = {};
|
|
17949
18275
|
const models = {};
|
|
17950
18276
|
const evidence = {};
|
|
17951
18277
|
const parseIssues = [];
|
|
17952
18278
|
const readMap = (dir, into, skipInventory) => {
|
|
17953
|
-
if (!
|
|
17954
|
-
for (const name of
|
|
18279
|
+
if (!existsSync4(dir)) return;
|
|
18280
|
+
for (const name of readdirSync2(dir)) {
|
|
17955
18281
|
if (!name.endsWith(".yaml")) continue;
|
|
17956
18282
|
const id = basename(name, ".yaml");
|
|
17957
18283
|
if (skipInventory && id === "inventory") continue;
|
|
17958
18284
|
try {
|
|
17959
|
-
const value = parseRestrictedYaml2(
|
|
18285
|
+
const value = parseRestrictedYaml2(readFileSync2(join3(dir, name), "utf8"), `${dir}/${name}`);
|
|
17960
18286
|
into[id] = value;
|
|
17961
18287
|
} catch (error) {
|
|
17962
18288
|
if (error instanceof KxmConfigError) {
|
|
@@ -17973,12 +18299,12 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
17973
18299
|
const origin = objectValue(model.origin);
|
|
17974
18300
|
const source = origin ? stringValue(origin.source) : void 0;
|
|
17975
18301
|
if (!source || evidence[source] !== void 0) continue;
|
|
17976
|
-
const evidenceFile = isAbsolute(source) ? source :
|
|
17977
|
-
if (
|
|
18302
|
+
const evidenceFile = isAbsolute(source) ? source : join3(projectRoot, source);
|
|
18303
|
+
if (existsSync4(evidenceFile)) evidence[source] = readFileSync2(evidenceFile, "utf8");
|
|
17978
18304
|
}
|
|
17979
18305
|
const ceilings = developerCeilings();
|
|
17980
18306
|
if (!ceilings.ok) {
|
|
17981
|
-
return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
|
|
18307
|
+
return [...parseIssues, ...lintIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
|
|
17982
18308
|
}
|
|
17983
18309
|
const result = validatePolicyDraft({ models, roles, evidence }, {
|
|
17984
18310
|
ceilings: ceilings.ROUTES,
|
|
@@ -17987,8 +18313,8 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
17987
18313
|
piNativeVendorProviders: ceilings.PI_NATIVE_VENDOR_PROVIDERS,
|
|
17988
18314
|
vendorAliases: { "x-ai": "xai", moonshotai: "moonshot", "google-ai": "google", qwen: "alibaba" }
|
|
17989
18315
|
});
|
|
17990
|
-
if (result.ok) return parseIssues;
|
|
17991
|
-
return [...parseIssues, ...result.issues.map((entry) => issue3(entry.phase, entry.code, entry.file, entry.message))];
|
|
18316
|
+
if (result.ok) return [...parseIssues, ...lintIssues];
|
|
18317
|
+
return [...parseIssues, ...lintIssues, ...result.issues.map((entry) => issue3(entry.phase, entry.code, entry.file, entry.message))];
|
|
17992
18318
|
}
|
|
17993
18319
|
function kxmCanonicalJson(value) {
|
|
17994
18320
|
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
@@ -18013,7 +18339,7 @@ function gitEnvironment() {
|
|
|
18013
18339
|
}
|
|
18014
18340
|
function discoverGitRoot(start = process.cwd()) {
|
|
18015
18341
|
let current = resolve(start);
|
|
18016
|
-
if (
|
|
18342
|
+
if (existsSync4(current) && !lstatSync(current).isDirectory()) current = dirname2(current);
|
|
18017
18343
|
const result = spawnSync2("git", ["-C", current, "rev-parse", "--show-toplevel"], {
|
|
18018
18344
|
encoding: "utf8",
|
|
18019
18345
|
env: gitEnvironment(),
|
|
@@ -18022,7 +18348,7 @@ function discoverGitRoot(start = process.cwd()) {
|
|
|
18022
18348
|
});
|
|
18023
18349
|
if (result.status !== 0 || result.error || !result.stdout.trim()) return void 0;
|
|
18024
18350
|
const root = resolve(result.stdout.trim());
|
|
18025
|
-
return
|
|
18351
|
+
return existsSync4(root) && lstatSync(root).isDirectory() ? root : void 0;
|
|
18026
18352
|
}
|
|
18027
18353
|
function assertNoRegisteredGates(options) {
|
|
18028
18354
|
if ("registeredGates" in options) {
|
|
@@ -18047,7 +18373,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18047
18373
|
)));
|
|
18048
18374
|
}
|
|
18049
18375
|
const registry = new KxmSchemaRegistry(options.schemasDir);
|
|
18050
|
-
const project = readResource(registry, root,
|
|
18376
|
+
const project = readResource(registry, root, join3(root, ".kxm", "project.yaml"), ".kxm/project.yaml", "project");
|
|
18051
18377
|
const earlyIssues = [];
|
|
18052
18378
|
validatePortablePaths(project, earlyIssues);
|
|
18053
18379
|
const templateProvenance = readTemplateProvenance(registry, root);
|
|
@@ -18066,30 +18392,30 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18066
18392
|
}
|
|
18067
18393
|
}
|
|
18068
18394
|
if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
|
|
18069
|
-
const agents = listNamedResources(registry, root,
|
|
18395
|
+
const agents = listNamedResources(registry, root, join3(root, ".kxm", "agents"), ".kxm/agents", "agent");
|
|
18070
18396
|
const modelIssues = [];
|
|
18071
|
-
const models = listNamedResources(registry, root,
|
|
18072
|
-
const roles = listNamedResources(registry, root,
|
|
18397
|
+
const models = listNamedResources(registry, root, join3(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
|
|
18398
|
+
const roles = listNamedResources(registry, root, join3(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
|
|
18073
18399
|
if (writerRole !== void 0) {
|
|
18074
18400
|
const logicalPath = ".kxm/roles/writer.yaml";
|
|
18075
|
-
roles.set("writer", { kind: "role", id: "writer", file:
|
|
18401
|
+
roles.set("writer", { kind: "role", id: "writer", file: join3(root, logicalPath), logicalPath, value: resourceValue(registry, writerRole, logicalPath, "role") });
|
|
18076
18402
|
}
|
|
18077
|
-
const workflows = listNamedResources(registry, root,
|
|
18403
|
+
const workflows = listNamedResources(registry, root, join3(root, ".kxm", "workflows"), ".kxm/workflows", "workflow", workflowCandidate?.id);
|
|
18078
18404
|
if (workflowCandidate) {
|
|
18079
18405
|
const { id, document } = workflowCandidate;
|
|
18080
18406
|
const logicalPath = `.kxm/workflows/${id}.yaml`;
|
|
18081
18407
|
if (!resourceIdentifier(id)) fail2("path", "resource_id_invalid", logicalPath, `filename-derived identity ${id} is invalid or platform-reserved`);
|
|
18082
18408
|
const collision = [...workflows.keys()].find((candidate2) => candidate2.toLocaleLowerCase("en-US") === id.toLocaleLowerCase("en-US"));
|
|
18083
18409
|
if (collision) fail2("path", "resource_id_collision", logicalPath, `${id} case-folds to existing ${collision}`);
|
|
18084
|
-
workflows.set(id, { kind: "workflow", id, file:
|
|
18410
|
+
workflows.set(id, { kind: "workflow", id, file: join3(root, logicalPath), logicalPath, value: resourceValue(registry, document, logicalPath, "workflow") });
|
|
18085
18411
|
}
|
|
18086
|
-
const gatePath =
|
|
18087
|
-
const gateRegistry =
|
|
18412
|
+
const gatePath = join3(root, ".kxm", "gates.yaml");
|
|
18413
|
+
const gateRegistry = existsSync4(gatePath) ? readResource(registry, root, gatePath, ".kxm/gates.yaml", "gate-registry") : void 0;
|
|
18088
18414
|
const environments = [];
|
|
18089
18415
|
const repositories = /* @__PURE__ */ new Map();
|
|
18090
18416
|
const loadIssues = [];
|
|
18091
|
-
const projectEnvironment =
|
|
18092
|
-
if (
|
|
18417
|
+
const projectEnvironment = join3(root, ".kxm", "project", "env.yaml");
|
|
18418
|
+
if (existsSync4(projectEnvironment)) {
|
|
18093
18419
|
try {
|
|
18094
18420
|
environments.push(readResource(registry, root, projectEnvironment, ".kxm/project/env.yaml", "environment"));
|
|
18095
18421
|
} catch (error) {
|
|
@@ -18131,7 +18457,7 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18131
18457
|
if (required2) loadIssues.push(issue3("discovery", "repository_binding_missing", ".kxm/project.yaml", `required repository ${repositoryId} has no portable pathHint or host-local binding`));
|
|
18132
18458
|
continue;
|
|
18133
18459
|
}
|
|
18134
|
-
if (!
|
|
18460
|
+
if (!existsSync4(binding)) {
|
|
18135
18461
|
if (required2 || hasLocalBinding) loadIssues.push(issue3("discovery", "repository_binding_unavailable", ".kxm/project.yaml", `${hasLocalBinding ? "explicit" : "required"} repository binding ${repositoryId} is unavailable`));
|
|
18136
18462
|
continue;
|
|
18137
18463
|
}
|
|
@@ -18163,9 +18489,9 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18163
18489
|
continue;
|
|
18164
18490
|
}
|
|
18165
18491
|
seenBindings.set(foldedBinding, repositoryId);
|
|
18166
|
-
const repositoryFile =
|
|
18492
|
+
const repositoryFile = join3(binding, ".kxm", "repo", "repo.yaml");
|
|
18167
18493
|
let definitionValidated = false;
|
|
18168
|
-
if (
|
|
18494
|
+
if (existsSync4(repositoryFile)) {
|
|
18169
18495
|
try {
|
|
18170
18496
|
const resource = readResource(registry, root, repositoryFile, `.kxm/repositories/${repositoryId}/repo.yaml`, "repository", repositoryId, binding);
|
|
18171
18497
|
if (resource.value.repositoryId !== repositoryId) {
|
|
@@ -18181,8 +18507,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
18181
18507
|
} else if (required2 || hasLocalBinding) {
|
|
18182
18508
|
loadIssues.push(issue3("discovery", "repository_definition_missing", `.kxm/repositories/${repositoryId}/repo.yaml`, `${hasLocalBinding ? "explicit" : "required"} repository ${repositoryId} has no repo.yaml`));
|
|
18183
18509
|
}
|
|
18184
|
-
const environmentFile =
|
|
18185
|
-
if (
|
|
18510
|
+
const environmentFile = join3(binding, ".kxm", "repo", "env.yaml");
|
|
18511
|
+
if (existsSync4(environmentFile)) {
|
|
18186
18512
|
if (!definitionValidated) {
|
|
18187
18513
|
loadIssues.push(issue3("reference", "repository_environment_without_definition", `.kxm/repositories/${repositoryId}/env.yaml`, `repository ${repositoryId} environment requires a validated matching repo.yaml`));
|
|
18188
18514
|
} else {
|
|
@@ -18218,21 +18544,21 @@ var LEGACY_CONFIG_FILES = [".kxm/config/agents.json", ".kxm/config/gates.json"];
|
|
|
18218
18544
|
function legacyConfigFilesAt(root) {
|
|
18219
18545
|
const files = [];
|
|
18220
18546
|
for (const component of [".kxm", ".kxm/config"]) {
|
|
18221
|
-
const absolute =
|
|
18222
|
-
if (
|
|
18547
|
+
const absolute = join3(root, ...component.split("/"));
|
|
18548
|
+
if (existsSync4(absolute) && lstatSync(absolute).isSymbolicLink()) return [component];
|
|
18223
18549
|
}
|
|
18224
18550
|
for (const candidate of LEGACY_CONFIG_FILES) {
|
|
18225
|
-
if (
|
|
18551
|
+
if (existsSync4(join3(root, ...candidate.split("/")))) files.push(candidate);
|
|
18226
18552
|
}
|
|
18227
|
-
const workflowsDir =
|
|
18228
|
-
if (
|
|
18553
|
+
const workflowsDir = join3(root, ".kxm", "config", "workflows");
|
|
18554
|
+
if (existsSync4(workflowsDir)) {
|
|
18229
18555
|
const stat = lstatSync(workflowsDir);
|
|
18230
18556
|
if (stat.isSymbolicLink()) {
|
|
18231
18557
|
files.push(".kxm/config/workflows");
|
|
18232
18558
|
return files;
|
|
18233
18559
|
}
|
|
18234
18560
|
if (stat.isDirectory()) {
|
|
18235
|
-
for (const entry of
|
|
18561
|
+
for (const entry of readdirSync2(workflowsDir, { withFileTypes: true }).filter((candidate) => (candidate.isFile() || candidate.isSymbolicLink()) && candidate.name.endsWith(".json")).sort((left, right) => compareCodeUnits4(left.name, right.name))) {
|
|
18236
18562
|
files.push(`.kxm/config/workflows/${entry.name}`);
|
|
18237
18563
|
}
|
|
18238
18564
|
}
|
|
@@ -18243,11 +18569,11 @@ function legacyConfigFilesAt(root) {
|
|
|
18243
18569
|
// plugins/kxm/src/runtime-store.ts
|
|
18244
18570
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
18245
18571
|
import { createHash as createHash6, randomUUID } from "node:crypto";
|
|
18246
|
-
import { existsSync as
|
|
18572
|
+
import { existsSync as existsSync7, lstatSync as lstatSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, realpathSync as realpathSync3, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18247
18573
|
import { dirname as dirname5, isAbsolute as isAbsolute4, resolve as resolve6 } from "node:path";
|
|
18248
18574
|
|
|
18249
18575
|
// plugins/kxm/src/sqlite.ts
|
|
18250
|
-
import { existsSync as
|
|
18576
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
18251
18577
|
import { createRequire } from "node:module";
|
|
18252
18578
|
import { resolve as resolve2 } from "node:path";
|
|
18253
18579
|
import { pathToFileURL as pathToFileURL2 } from "node:url";
|
|
@@ -18288,7 +18614,7 @@ var DatabaseSync = class {
|
|
|
18288
18614
|
}
|
|
18289
18615
|
};
|
|
18290
18616
|
function openReadOnlyDatabase(path) {
|
|
18291
|
-
if (
|
|
18617
|
+
if (existsSync5(`${path}-wal`)) return new DatabaseSync(path, { readOnly: true });
|
|
18292
18618
|
const uri = pathToFileURL2(resolve2(path));
|
|
18293
18619
|
uri.searchParams.set("immutable", "1");
|
|
18294
18620
|
return new DatabaseSync(uri.href, { readOnly: true });
|
|
@@ -18297,11 +18623,11 @@ function openReadOnlyDatabase(path) {
|
|
|
18297
18623
|
// plugins/kxm/src/runtime-paths.ts
|
|
18298
18624
|
import { createHash as createHash4 } from "node:crypto";
|
|
18299
18625
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
18300
|
-
import { join as
|
|
18626
|
+
import { join as join5, resolve as resolve4 } from "node:path";
|
|
18301
18627
|
|
|
18302
18628
|
// plugins/kxm/src/bindings.ts
|
|
18303
18629
|
import { homedir } from "node:os";
|
|
18304
|
-
import { dirname as dirname3, isAbsolute as isAbsolute2, join as
|
|
18630
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, parse, relative as relative2, resolve as resolve3, sep as sep2 } from "node:path";
|
|
18305
18631
|
var MAX_BINDING_RECORD_BYTES = 256 * 1024;
|
|
18306
18632
|
var BINDING_LABEL = "Runtime-local repository bindings";
|
|
18307
18633
|
function bindingIssue(phase, code, message) {
|
|
@@ -18321,24 +18647,24 @@ function kxmUserStateRoot(options = {}) {
|
|
|
18321
18647
|
}
|
|
18322
18648
|
if (platform === "win32") {
|
|
18323
18649
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
18324
|
-
const base2 = localAppData && isAbsolute2(localAppData) ? localAppData :
|
|
18650
|
+
const base2 = localAppData && isAbsolute2(localAppData) ? localAppData : join4(home, "AppData", "Local");
|
|
18325
18651
|
return resolve3(base2, "KXM");
|
|
18326
18652
|
}
|
|
18327
18653
|
if (platform === "darwin") return resolve3(home, "Library", "Application Support", "KXM");
|
|
18328
18654
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
18329
|
-
const base = xdgState && isAbsolute2(xdgState) ? xdgState :
|
|
18655
|
+
const base = xdgState && isAbsolute2(xdgState) ? xdgState : join4(home, ".local", "state");
|
|
18330
18656
|
return resolve3(base, "kxm");
|
|
18331
18657
|
}
|
|
18332
18658
|
|
|
18333
18659
|
// plugins/kxm/src/runtime-paths.ts
|
|
18334
18660
|
function kxmRuntimePaths(options = {}) {
|
|
18335
18661
|
const stateRoot = options.stateRoot ? resolve4(options.stateRoot) : kxmUserStateRoot({ ...options.env ? { env: options.env } : {}, ...options.homeDir ? { homeDir: options.homeDir } : {} });
|
|
18336
|
-
const runtimeDir =
|
|
18662
|
+
const runtimeDir = join5(stateRoot, "runtime");
|
|
18337
18663
|
return {
|
|
18338
18664
|
stateRoot,
|
|
18339
18665
|
runtimeDir,
|
|
18340
|
-
registryDb:
|
|
18341
|
-
projectsDir:
|
|
18666
|
+
registryDb: join5(runtimeDir, "registry.db"),
|
|
18667
|
+
projectsDir: join5(runtimeDir, "projects")
|
|
18342
18668
|
};
|
|
18343
18669
|
}
|
|
18344
18670
|
function projectRuntimeKey(projectRoot) {
|
|
@@ -18687,22 +19013,22 @@ function kxmSyncEventBytes(event) {
|
|
|
18687
19013
|
import {
|
|
18688
19014
|
chmodSync,
|
|
18689
19015
|
copyFileSync,
|
|
18690
|
-
existsSync as
|
|
19016
|
+
existsSync as existsSync6,
|
|
18691
19017
|
lstatSync as lstatSync2,
|
|
18692
19018
|
mkdirSync,
|
|
18693
|
-
readdirSync as
|
|
18694
|
-
readFileSync as
|
|
19019
|
+
readdirSync as readdirSync3,
|
|
19020
|
+
readFileSync as readFileSync3,
|
|
18695
19021
|
unlinkSync,
|
|
18696
19022
|
writeFileSync
|
|
18697
19023
|
} from "node:fs";
|
|
18698
|
-
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, join as
|
|
19024
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, join as join6, relative as relative3, resolve as resolve5, sep as sep3 } from "node:path";
|
|
18699
19025
|
function databaseError(code, file, message) {
|
|
18700
19026
|
const issue4 = { phase: "semantic", code, file, message };
|
|
18701
19027
|
return new KxmConfigError([issue4]);
|
|
18702
19028
|
}
|
|
18703
19029
|
function checkedParent(path, description) {
|
|
18704
19030
|
const parent = dirname4(path);
|
|
18705
|
-
if (!
|
|
19031
|
+
if (!existsSync6(parent)) mkdirSync(parent, { recursive: true, mode: 448 });
|
|
18706
19032
|
const stat = lstatSync2(parent, { throwIfNoEntry: false });
|
|
18707
19033
|
if (!stat || stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
18708
19034
|
throw databaseError("runtime_path_invalid", description, `${description} parent must be a regular directory, not a link`);
|
|
@@ -19772,9 +20098,9 @@ var KxmRunEventStore = class {
|
|
|
19772
20098
|
}
|
|
19773
20099
|
readRunPrompts() {
|
|
19774
20100
|
const sidecar = this.runPromptSidecarPath();
|
|
19775
|
-
if (!
|
|
20101
|
+
if (!existsSync7(sidecar)) return {};
|
|
19776
20102
|
try {
|
|
19777
|
-
const parsed = JSON.parse(
|
|
20103
|
+
const parsed = JSON.parse(readFileSync4(sidecar, "utf8"));
|
|
19778
20104
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
|
|
19779
20105
|
return Object.fromEntries(
|
|
19780
20106
|
Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
|
|
@@ -20861,8 +21187,8 @@ function capabilityFromRow(row) {
|
|
|
20861
21187
|
|
|
20862
21188
|
// plugins/kxm/src/runtime-service.ts
|
|
20863
21189
|
import { createHash as createHash8 } from "node:crypto";
|
|
20864
|
-
import { existsSync as
|
|
20865
|
-
import { join as
|
|
21190
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
21191
|
+
import { join as join8 } from "node:path";
|
|
20866
21192
|
|
|
20867
21193
|
// plugins/kxm/src/engine-fold.ts
|
|
20868
21194
|
var KXM_RUN_STATE_SCHEMA = "kxm.run-state.v2";
|
|
@@ -22223,7 +22549,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
22223
22549
|
const maxAttempts = compileCountField(step.maxAttempts, 1, `${id}.maxAttempts`, id, sink);
|
|
22224
22550
|
const timeoutMs = compileOptionalDuration(step.timeoutMs, `${id}.timeoutMs`, id, sink);
|
|
22225
22551
|
const assignments = compileAssignments(step, id, agent, sink);
|
|
22226
|
-
const
|
|
22552
|
+
const join20 = compileJoin(step, id, sink);
|
|
22227
22553
|
const requiredEvidence = compileEvidence(step, id, sink);
|
|
22228
22554
|
const transitions = compileTransitions(step, id, index, stepIndex, sink);
|
|
22229
22555
|
const outcomes = Object.keys(transitions).sort(compareCodeUnits5);
|
|
@@ -22251,7 +22577,7 @@ function compileStep(step, index, stepIndex, requirePlanHash, sink) {
|
|
|
22251
22577
|
transitions: orderedTransitions,
|
|
22252
22578
|
requiresPlanHash: requirePlanHash.includes(id),
|
|
22253
22579
|
assignments,
|
|
22254
|
-
join:
|
|
22580
|
+
join: join20
|
|
22255
22581
|
};
|
|
22256
22582
|
if (kind === "agent" || kind === "moa") {
|
|
22257
22583
|
if (!agent) return void 0;
|
|
@@ -22301,15 +22627,15 @@ function compileAssignments(step, stepId, primaryAgentId, sink) {
|
|
|
22301
22627
|
};
|
|
22302
22628
|
}
|
|
22303
22629
|
function compileJoin(step, stepId, sink) {
|
|
22304
|
-
const
|
|
22305
|
-
if (!
|
|
22306
|
-
const declared = stringValue2(
|
|
22630
|
+
const join20 = objectValue2(step.join);
|
|
22631
|
+
if (!join20) return { strategy: "all" };
|
|
22632
|
+
const declared = stringValue2(join20.strategy);
|
|
22307
22633
|
const strategy = declared && JOIN_STRATEGIES.has(declared) ? declared : "all";
|
|
22308
|
-
const minimumPassed = compileOptionalCount(
|
|
22634
|
+
const minimumPassed = compileOptionalCount(join20.minimumPassed, `${stepId}.join.minimumPassed`, stepId, sink);
|
|
22309
22635
|
const compiled = {
|
|
22310
22636
|
strategy,
|
|
22311
22637
|
...minimumPassed !== void 0 ? { minimumPassed } : {},
|
|
22312
|
-
...typeof
|
|
22638
|
+
...typeof join20.cancelRemaining === "boolean" ? { cancelRemaining: join20.cancelRemaining } : {}
|
|
22313
22639
|
};
|
|
22314
22640
|
return compiled;
|
|
22315
22641
|
}
|
|
@@ -23728,11 +24054,11 @@ function kxmToolPolicyRevision(bundle) {
|
|
|
23728
24054
|
return sha256Of(kxmCanonicalJson({ policies, gateRegistry: bundle.gateRegistry?.value ?? null }));
|
|
23729
24055
|
}
|
|
23730
24056
|
function collectMemoryFiles(dir, baseDir, ignoreSubdirs = /* @__PURE__ */ new Set()) {
|
|
23731
|
-
if (!
|
|
23732
|
-
const entries =
|
|
24057
|
+
if (!existsSync8(dir)) return [];
|
|
24058
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
23733
24059
|
const results = [];
|
|
23734
24060
|
for (const entry of entries) {
|
|
23735
|
-
const fullPath =
|
|
24061
|
+
const fullPath = join8(dir, entry.name);
|
|
23736
24062
|
if (entry.isDirectory()) {
|
|
23737
24063
|
if (ignoreSubdirs.has(entry.name)) continue;
|
|
23738
24064
|
results.push(...collectMemoryFiles(fullPath, baseDir, ignoreSubdirs));
|
|
@@ -23745,19 +24071,19 @@ function collectMemoryFiles(dir, baseDir, ignoreSubdirs = /* @__PURE__ */ new Se
|
|
|
23745
24071
|
}
|
|
23746
24072
|
function computeKxmMemoryRevision(bundle, options = {}) {
|
|
23747
24073
|
const hash = createHash8("sha256");
|
|
23748
|
-
const memoryDir = options.memoryDir ??
|
|
24074
|
+
const memoryDir = options.memoryDir ?? join8(bundle.projectRoot, ".kxm", "memory");
|
|
23749
24075
|
const memoryFiles = collectMemoryFiles(memoryDir, memoryDir, /* @__PURE__ */ new Set(["candidates"])).sort((left, right) => compareCodeUnits6(left.relPath, right.relPath));
|
|
23750
24076
|
for (const file of memoryFiles) {
|
|
23751
24077
|
hash.update(`memory:${file.relPath}\0`, "utf8");
|
|
23752
|
-
hash.update(
|
|
24078
|
+
hash.update(readFileSync5(file.fullPath));
|
|
23753
24079
|
hash.update("\0", "utf8");
|
|
23754
24080
|
}
|
|
23755
|
-
const skillsDir = options.skillsDir ??
|
|
23756
|
-
const promotedSkillsDir =
|
|
24081
|
+
const skillsDir = options.skillsDir ?? join8(bundle.projectRoot, ".kxm", "skills");
|
|
24082
|
+
const promotedSkillsDir = join8(skillsDir, "promoted");
|
|
23757
24083
|
const skillFiles = collectMemoryFiles(promotedSkillsDir, promotedSkillsDir).sort((left, right) => compareCodeUnits6(left.relPath, right.relPath));
|
|
23758
24084
|
for (const file of skillFiles) {
|
|
23759
24085
|
hash.update(`skill:${file.relPath}\0`, "utf8");
|
|
23760
|
-
hash.update(
|
|
24086
|
+
hash.update(readFileSync5(file.fullPath));
|
|
23761
24087
|
hash.update("\0", "utf8");
|
|
23762
24088
|
}
|
|
23763
24089
|
if (options.promotedState && options.promotedState.length > 0) {
|
|
@@ -23835,7 +24161,7 @@ function openKxmRuntimeContext(projectRoot, options) {
|
|
|
23835
24161
|
now: options.now ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
23836
24162
|
...options.logger ? { logger: options.logger } : {}
|
|
23837
24163
|
});
|
|
23838
|
-
const eventStore = new KxmRunEventStore(
|
|
24164
|
+
const eventStore = new KxmRunEventStore(join8(paths.projectsDir, registration.projectKey, "run-events.db"));
|
|
23839
24165
|
registerKxmRuntimeHandle(eventStore.path);
|
|
23840
24166
|
return {
|
|
23841
24167
|
registry,
|
|
@@ -23896,7 +24222,7 @@ function acceptKxmRun(context, bundle, request, options = {}) {
|
|
|
23896
24222
|
const commandId = request.commandId ?? newKxmCommandId();
|
|
23897
24223
|
const now = options.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
23898
24224
|
const monotonicNs = options.monotonicNs ?? kxmMonotonicNs();
|
|
23899
|
-
const workflow = bundle.workflows.
|
|
24225
|
+
const workflow = lookupKxmResource(bundle.workflows.values(), request.workflowId, "workflow");
|
|
23900
24226
|
if (!workflow) {
|
|
23901
24227
|
throw runtimeError("run_workflow_unknown", ".kxm/workflows", `workflow ${request.workflowId} does not exist in this project`);
|
|
23902
24228
|
}
|
|
@@ -24065,7 +24391,7 @@ function cancelKxmRun(context, runId, options = {}) {
|
|
|
24065
24391
|
const events = [];
|
|
24066
24392
|
let sequence = context.eventStore.nextSequence(runId);
|
|
24067
24393
|
let nextMono = monotonicNs;
|
|
24068
|
-
const
|
|
24394
|
+
const push2 = (eventType, payload) => {
|
|
24069
24395
|
const event = {
|
|
24070
24396
|
...kxmEventBase(context, run, now, nextMono, commandId),
|
|
24071
24397
|
eventId: newKxmEventId(),
|
|
@@ -24089,11 +24415,11 @@ function cancelKxmRun(context, runId, options = {}) {
|
|
|
24089
24415
|
}
|
|
24090
24416
|
cancelPayload.budget = options.budget;
|
|
24091
24417
|
}
|
|
24092
|
-
|
|
24418
|
+
push2("run.cancel_requested", cancelPayload);
|
|
24093
24419
|
let status = "cancelled";
|
|
24094
24420
|
if (folded.status === "running" && activeAttempt) {
|
|
24095
24421
|
status = "cancelling";
|
|
24096
|
-
|
|
24422
|
+
push2("run.status_changed", { status: "cancelling", reason: cancelReason });
|
|
24097
24423
|
const attemptIds = new Set(kxmFoldPanelAttemptIds(folded.currentStep));
|
|
24098
24424
|
if (folded.currentStep?.attemptId) attemptIds.add(folded.currentStep.attemptId);
|
|
24099
24425
|
for (const attemptId of attemptIds) {
|
|
@@ -24106,7 +24432,7 @@ function cancelKxmRun(context, runId, options = {}) {
|
|
|
24106
24432
|
abortControllers.push(owned.controller);
|
|
24107
24433
|
}
|
|
24108
24434
|
} else if (folded.status === "blocked_uncertain") {
|
|
24109
|
-
|
|
24435
|
+
push2("run.status_changed", { status: "cancelling", reason: cancelReason });
|
|
24110
24436
|
const attemptId = folded.currentStep?.attemptId;
|
|
24111
24437
|
if (attemptId) {
|
|
24112
24438
|
const capability = context.eventStore.capabilityByAttempt(attemptId);
|
|
@@ -24118,13 +24444,13 @@ function cancelKxmRun(context, runId, options = {}) {
|
|
|
24118
24444
|
for (const owned of kxmAttemptControllers(context.eventStore.path, runId)) {
|
|
24119
24445
|
abortControllers.push(owned.controller);
|
|
24120
24446
|
}
|
|
24121
|
-
|
|
24447
|
+
push2("run.status_changed", { status: "cancelled", reason: cancelReason });
|
|
24122
24448
|
status = "cancelled";
|
|
24123
24449
|
} else if (folded.status === "running" && !activeAttempt) {
|
|
24124
|
-
|
|
24125
|
-
|
|
24450
|
+
push2("run.status_changed", { status: "cancelling", reason: cancelReason });
|
|
24451
|
+
push2("run.status_changed", { status: "cancelled", reason: cancelReason });
|
|
24126
24452
|
} else {
|
|
24127
|
-
|
|
24453
|
+
push2("run.status_changed", { status: "cancelled", reason: cancelReason });
|
|
24128
24454
|
}
|
|
24129
24455
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
24130
24456
|
const nextState = foldStoredKxmRun(context, run);
|
|
@@ -24145,9 +24471,9 @@ function cancelKxmRun(context, runId, options = {}) {
|
|
|
24145
24471
|
|
|
24146
24472
|
// plugins/kxm/src/oneshot-evidence.ts
|
|
24147
24473
|
import { createHash as createHash9, randomUUID as randomUUID3 } from "node:crypto";
|
|
24148
|
-
import { lstatSync as lstatSync4, readdirSync as
|
|
24474
|
+
import { lstatSync as lstatSync4, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
24149
24475
|
import { lstat, mkdir, open, rename } from "node:fs/promises";
|
|
24150
|
-
import { join as
|
|
24476
|
+
import { join as join9 } from "node:path";
|
|
24151
24477
|
var ONESHOT_EVIDENCE_SCHEMA = "kxm.oneshot-evidence.v2";
|
|
24152
24478
|
var RETIRED_ONESHOT_EVIDENCE_SCHEMAS = /* @__PURE__ */ new Set(["kxm.oneshot-evidence.v1"]);
|
|
24153
24479
|
var TEXT_LIMIT = 4 * 1024 * 1024;
|
|
@@ -24171,12 +24497,12 @@ function parseOneShotEvidenceRecord(bytes) {
|
|
|
24171
24497
|
return parsed;
|
|
24172
24498
|
}
|
|
24173
24499
|
function oneShotEvidenceRoot() {
|
|
24174
|
-
return
|
|
24500
|
+
return join9(kxmUserStateRoot(), "runtime", "oneshot-evidence");
|
|
24175
24501
|
}
|
|
24176
24502
|
function readOneShotObservedChildExit(runId, attemptId, root = oneShotEvidenceRoot()) {
|
|
24177
24503
|
let names3;
|
|
24178
24504
|
try {
|
|
24179
|
-
names3 =
|
|
24505
|
+
names3 = readdirSync5(root);
|
|
24180
24506
|
} catch {
|
|
24181
24507
|
return void 0;
|
|
24182
24508
|
}
|
|
@@ -24185,13 +24511,13 @@ function readOneShotObservedChildExit(runId, attemptId, root = oneShotEvidenceRo
|
|
|
24185
24511
|
const capped = names3.slice(0, 4096);
|
|
24186
24512
|
for (const name of capped) {
|
|
24187
24513
|
if (!name.startsWith("evd_")) continue;
|
|
24188
|
-
const dir =
|
|
24514
|
+
const dir = join9(root, name);
|
|
24189
24515
|
try {
|
|
24190
24516
|
const info = lstatSync4(dir);
|
|
24191
24517
|
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
24192
|
-
const intent = parseOneShotEvidenceRecord(
|
|
24518
|
+
const intent = parseOneShotEvidenceRecord(readFileSync6(join9(dir, "intent.json"), "utf8"));
|
|
24193
24519
|
if (intent.runId !== runId || intent.attemptId !== attemptId) continue;
|
|
24194
|
-
const result = parseOneShotEvidenceRecord(
|
|
24520
|
+
const result = parseOneShotEvidenceRecord(readFileSync6(join9(dir, "result.json"), "utf8"));
|
|
24195
24521
|
if (result.observedChildExit === true) seenTrue = true;
|
|
24196
24522
|
else if (result.observedChildExit === false) seenFalse = true;
|
|
24197
24523
|
} catch {
|
|
@@ -24208,7 +24534,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
24208
24534
|
throw new Error("oneshot_evidence_root_not_private");
|
|
24209
24535
|
}
|
|
24210
24536
|
const id = `evd_${randomUUID3().replaceAll("-", "")}`;
|
|
24211
|
-
const dir =
|
|
24537
|
+
const dir = join9(root, id);
|
|
24212
24538
|
await mkdir(dir, { mode: 448 });
|
|
24213
24539
|
const secrets = [...new Set(sensitive.filter((value) => value.length >= 8))].sort((a, b) => b.length - a.length);
|
|
24214
24540
|
const redact = (value) => {
|
|
@@ -24228,7 +24554,7 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
24228
24554
|
const bytes = JSON.stringify(value);
|
|
24229
24555
|
if (Buffer.byteLength(bytes) > RECORD_LIMIT) throw new Error("oneshot_evidence_record_limit");
|
|
24230
24556
|
parseOneShotEvidenceRecord(bytes);
|
|
24231
|
-
const target =
|
|
24557
|
+
const target = join9(dir, name);
|
|
24232
24558
|
const partial = `${target}.partial`;
|
|
24233
24559
|
const file = await open(partial, "wx", 384);
|
|
24234
24560
|
try {
|
|
@@ -24294,10 +24620,10 @@ async function beginOneShotEvidence(root, intent, sensitive) {
|
|
|
24294
24620
|
}
|
|
24295
24621
|
|
|
24296
24622
|
// plugins/kxm/src/prices.ts
|
|
24297
|
-
var
|
|
24623
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
24298
24624
|
import { createHash as createHash10 } from "node:crypto";
|
|
24299
|
-
import { existsSync as
|
|
24300
|
-
import { join as
|
|
24625
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
24626
|
+
import { join as join10 } from "node:path";
|
|
24301
24627
|
|
|
24302
24628
|
// plugins/kxm/src/price-calc.ts
|
|
24303
24629
|
var PRICES_SCHEMA = "kxm.prices.v1";
|
|
@@ -24361,7 +24687,7 @@ function hashPriceCatalog(catalog) {
|
|
|
24361
24687
|
return createHash10("sha256").update(JSON.stringify(canonical), "utf8").digest("hex");
|
|
24362
24688
|
}
|
|
24363
24689
|
function parsePriceCatalog(text) {
|
|
24364
|
-
const parsed = (0,
|
|
24690
|
+
const parsed = (0, import_yaml4.parse)(text);
|
|
24365
24691
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
24366
24692
|
throw new Error("price catalog must be an object");
|
|
24367
24693
|
}
|
|
@@ -24449,49 +24775,49 @@ function parsePriceCatalog(text) {
|
|
|
24449
24775
|
return catalog;
|
|
24450
24776
|
}
|
|
24451
24777
|
function loadPriceCatalog(rootOrPath) {
|
|
24452
|
-
const candidatePath =
|
|
24453
|
-
if (!candidatePath || !
|
|
24778
|
+
const candidatePath = existsSync9(join10(rootOrPath, ".kxm", "prices.yaml")) ? join10(rootOrPath, ".kxm", "prices.yaml") : existsSync9(join10(rootOrPath, "prices.yaml")) ? join10(rootOrPath, "prices.yaml") : existsSync9(rootOrPath) && statSync2(rootOrPath).isFile() ? rootOrPath : void 0;
|
|
24779
|
+
if (!candidatePath || !existsSync9(candidatePath)) {
|
|
24454
24780
|
return void 0;
|
|
24455
24781
|
}
|
|
24456
|
-
const content =
|
|
24782
|
+
const content = readFileSync7(candidatePath, "utf8");
|
|
24457
24783
|
return parsePriceCatalog(content);
|
|
24458
24784
|
}
|
|
24459
24785
|
|
|
24460
24786
|
// plugins/kxm/src/engine.ts
|
|
24461
|
-
var
|
|
24787
|
+
var import_yaml10 = __toESM(require_dist(), 1);
|
|
24462
24788
|
import { createHash as createHash14, randomBytes, timingSafeEqual } from "node:crypto";
|
|
24463
|
-
import { existsSync as
|
|
24464
|
-
import { join as
|
|
24789
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
|
|
24790
|
+
import { join as join16 } from "node:path";
|
|
24465
24791
|
|
|
24466
24792
|
// plugins/kxm/src/routes.ts
|
|
24467
|
-
var
|
|
24468
|
-
import { existsSync as
|
|
24469
|
-
import { join as
|
|
24793
|
+
var import_yaml7 = __toESM(require_dist(), 1);
|
|
24794
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readdirSync as readdirSync6 } from "node:fs";
|
|
24795
|
+
import { join as join11 } from "node:path";
|
|
24470
24796
|
|
|
24471
24797
|
// plugins/kxm/src/role.ts
|
|
24472
|
-
var
|
|
24798
|
+
var import_yaml6 = __toESM(require_dist(), 1);
|
|
24473
24799
|
|
|
24474
24800
|
// plugins/kxm/src/config.ts
|
|
24475
|
-
var
|
|
24801
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
24476
24802
|
|
|
24477
24803
|
// plugins/kxm/src/routes.ts
|
|
24478
24804
|
var RETIRED_POLICY = ".kxm/producers.yaml";
|
|
24479
24805
|
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [] });
|
|
24480
24806
|
function loadRoutePolicy(root) {
|
|
24481
|
-
if (
|
|
24482
|
-
const path =
|
|
24483
|
-
if (!
|
|
24484
|
-
const value = (0,
|
|
24807
|
+
if (existsSync10(join11(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
24808
|
+
const path = join11(root, ".kxm", "routes.yaml");
|
|
24809
|
+
if (!existsSync10(path)) return empty();
|
|
24810
|
+
const value = (0, import_yaml7.parse)(readFileSync8(path, "utf8"));
|
|
24485
24811
|
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
24486
24812
|
return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x) => typeof x === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x) => typeof x === "string") : [] };
|
|
24487
24813
|
}
|
|
24488
24814
|
function listRoleBindings(root) {
|
|
24489
|
-
const dir =
|
|
24815
|
+
const dir = join11(root, ".kxm", "roles");
|
|
24490
24816
|
const result = {};
|
|
24491
|
-
if (!
|
|
24492
|
-
for (const file of
|
|
24817
|
+
if (!existsSync10(dir)) return result;
|
|
24818
|
+
for (const file of readdirSync6(dir).filter((name) => name.endsWith(".yaml"))) {
|
|
24493
24819
|
const role = file.slice(0, -5);
|
|
24494
|
-
const value = (0,
|
|
24820
|
+
const value = (0, import_yaml7.parse)(readFileSync8(join11(dir, file), "utf8"));
|
|
24495
24821
|
result[role] = (value.roster ?? []).map((entry) => entry.route).filter((route) => typeof route === "string");
|
|
24496
24822
|
}
|
|
24497
24823
|
return result;
|
|
@@ -24823,8 +25149,8 @@ function pruneContextPacket(packet, tokenBudget) {
|
|
|
24823
25149
|
|
|
24824
25150
|
// plugins/kxm/src/dispatch-context.ts
|
|
24825
25151
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24826
|
-
import { readdirSync as
|
|
24827
|
-
import { extname as extname3, join as
|
|
25152
|
+
import { readdirSync as readdirSync9 } from "node:fs";
|
|
25153
|
+
import { extname as extname3, join as join14 } from "node:path";
|
|
24828
25154
|
|
|
24829
25155
|
// plugins/kxm/src/protocol.ts
|
|
24830
25156
|
var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
|
|
@@ -25452,9 +25778,9 @@ function memoryRecordToContextItem(record2, project) {
|
|
|
25452
25778
|
}
|
|
25453
25779
|
|
|
25454
25780
|
// plugins/kxm/src/memory.ts
|
|
25455
|
-
var
|
|
25456
|
-
import { existsSync as
|
|
25457
|
-
import { extname as extname2, join as
|
|
25781
|
+
var import_yaml8 = __toESM(require_dist(), 1);
|
|
25782
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readdirSync as readdirSync7, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
25783
|
+
import { extname as extname2, join as join12, resolve as resolve7 } from "node:path";
|
|
25458
25784
|
var MEMORY_SCHEMA = "kxm.memory.v1";
|
|
25459
25785
|
var VALID_SCOPES = /* @__PURE__ */ new Set(["agent", "project", "run", "operator"]);
|
|
25460
25786
|
var VALID_AUTHORITIES = /* @__PURE__ */ new Set(["instruction", "evidence", "promoted"]);
|
|
@@ -25484,7 +25810,7 @@ function parseFrontmatter(content) {
|
|
|
25484
25810
|
}
|
|
25485
25811
|
function parseMemoryRecord(raw, filename = "memory.md") {
|
|
25486
25812
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
25487
|
-
const data = (0,
|
|
25813
|
+
const data = (0, import_yaml8.parse)(frontmatter);
|
|
25488
25814
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
25489
25815
|
throw new Error(`invalid YAML frontmatter in ${filename}`);
|
|
25490
25816
|
}
|
|
@@ -25553,20 +25879,20 @@ function parseMemoryRecord(raw, filename = "memory.md") {
|
|
|
25553
25879
|
}
|
|
25554
25880
|
function memoryDirectories(repoRoot) {
|
|
25555
25881
|
const root = resolve7(repoRoot);
|
|
25556
|
-
const memoryDir =
|
|
25557
|
-
const candidatesDir =
|
|
25882
|
+
const memoryDir = join12(root, ".kxm", "memory");
|
|
25883
|
+
const candidatesDir = join12(memoryDir, "candidates");
|
|
25558
25884
|
return { memoryDir, candidatesDir };
|
|
25559
25885
|
}
|
|
25560
25886
|
function loadAuthoredMemory(repoRoot) {
|
|
25561
25887
|
const { memoryDir } = memoryDirectories(repoRoot);
|
|
25562
|
-
if (!
|
|
25888
|
+
if (!existsSync11(memoryDir)) return [];
|
|
25563
25889
|
const records = [];
|
|
25564
|
-
const entries =
|
|
25890
|
+
const entries = readdirSync7(memoryDir, { withFileTypes: true });
|
|
25565
25891
|
for (const entry of entries) {
|
|
25566
25892
|
if (entry.isFile() && extname2(entry.name) === ".md") {
|
|
25567
|
-
const fullPath =
|
|
25893
|
+
const fullPath = join12(memoryDir, entry.name);
|
|
25568
25894
|
try {
|
|
25569
|
-
const text =
|
|
25895
|
+
const text = readFileSync9(fullPath, "utf8");
|
|
25570
25896
|
const record2 = parseMemoryRecord(text, entry.name);
|
|
25571
25897
|
if (record2.lifecycle === "active") {
|
|
25572
25898
|
records.push(record2);
|
|
@@ -25580,10 +25906,10 @@ function loadAuthoredMemory(repoRoot) {
|
|
|
25580
25906
|
}
|
|
25581
25907
|
|
|
25582
25908
|
// plugins/kxm/src/skills.ts
|
|
25583
|
-
var
|
|
25909
|
+
var import_yaml9 = __toESM(require_dist(), 1);
|
|
25584
25910
|
import { createHash as createHash11 } from "node:crypto";
|
|
25585
|
-
import { existsSync as
|
|
25586
|
-
import { dirname as dirname7, join as
|
|
25911
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, readdirSync as readdirSync8, readFileSync as readFileSync10, renameSync, rmSync, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
25912
|
+
import { dirname as dirname7, join as join13 } from "node:path";
|
|
25587
25913
|
var SKILL_CANDIDATE_SCHEMA = "kxm.skill-candidate.v1";
|
|
25588
25914
|
var SKILL_EVALUATION_SCHEMA = "kxm.skill-evaluation.v1";
|
|
25589
25915
|
var SKILL_DECISION_SCHEMA = "kxm.skill-decision.v1";
|
|
@@ -25624,7 +25950,7 @@ function parseSkillFrontmatter(content) {
|
|
|
25624
25950
|
return { frontmatter: null, body: content };
|
|
25625
25951
|
}
|
|
25626
25952
|
try {
|
|
25627
|
-
const parsed = (0,
|
|
25953
|
+
const parsed = (0, import_yaml9.parse)(rawFm);
|
|
25628
25954
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
25629
25955
|
return { frontmatter: parsed, body: rawBody };
|
|
25630
25956
|
}
|
|
@@ -25689,20 +26015,20 @@ var SkillLifecycle = class {
|
|
|
25689
26015
|
writeFileSync6(file, content);
|
|
25690
26016
|
}
|
|
25691
26017
|
dir(state) {
|
|
25692
|
-
return
|
|
26018
|
+
return join13(this.root, state === "candidate" ? "candidates" : `${state}s`.replace("rejecteds", "rejected").replace("promoteds", "promoted"));
|
|
25693
26019
|
}
|
|
25694
26020
|
historyFile(id) {
|
|
25695
|
-
return
|
|
26021
|
+
return join13(this.root, "history", `${id}.jsonl`);
|
|
25696
26022
|
}
|
|
25697
26023
|
paths(state, id) {
|
|
25698
|
-
const dir =
|
|
25699
|
-
return { dir, metadata:
|
|
26024
|
+
const dir = join13(this.dir(state), id);
|
|
26025
|
+
return { dir, metadata: join13(dir, "metadata.json"), skill: join13(dir, "SKILL.md") };
|
|
25700
26026
|
}
|
|
25701
26027
|
appendHistory(id, record2) {
|
|
25702
26028
|
const line = `${JSON.stringify(record2)}
|
|
25703
26029
|
`;
|
|
25704
|
-
if (
|
|
25705
|
-
const existing =
|
|
26030
|
+
if (existsSync12(this.historyFile(id))) {
|
|
26031
|
+
const existing = readFileSync10(this.historyFile(id), "utf8");
|
|
25706
26032
|
const lines = existing.split("\n").filter((entry) => entry.trim());
|
|
25707
26033
|
this.write(this.historyFile(id), [...lines.slice(-499), line.trim()].join("\n") + "\n");
|
|
25708
26034
|
} else {
|
|
@@ -25711,20 +26037,20 @@ var SkillLifecycle = class {
|
|
|
25711
26037
|
}
|
|
25712
26038
|
history(id) {
|
|
25713
26039
|
const file = this.historyFile(id);
|
|
25714
|
-
if (!
|
|
25715
|
-
return
|
|
26040
|
+
if (!existsSync12(file)) return [];
|
|
26041
|
+
return readFileSync10(file, "utf8").split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
25716
26042
|
}
|
|
25717
26043
|
readMetadata(state, id) {
|
|
25718
26044
|
const { metadata } = this.paths(state, id);
|
|
25719
|
-
if (!
|
|
26045
|
+
if (!existsSync12(metadata)) {
|
|
25720
26046
|
throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${state}`);
|
|
25721
26047
|
}
|
|
25722
|
-
return JSON.parse(
|
|
26048
|
+
return JSON.parse(readFileSync10(metadata, "utf8"));
|
|
25723
26049
|
}
|
|
25724
26050
|
move(from, to, id) {
|
|
25725
|
-
const fromDir =
|
|
25726
|
-
const toDir =
|
|
25727
|
-
if (!
|
|
26051
|
+
const fromDir = join13(this.dir(from), id);
|
|
26052
|
+
const toDir = join13(this.dir(to), id);
|
|
26053
|
+
if (!existsSync12(fromDir)) {
|
|
25728
26054
|
throw new SkillLifecycleError("skill_not_found", `skill ${id} not found in ${from}`);
|
|
25729
26055
|
}
|
|
25730
26056
|
if (this.dryRun) {
|
|
@@ -25732,7 +26058,7 @@ var SkillLifecycle = class {
|
|
|
25732
26058
|
return;
|
|
25733
26059
|
}
|
|
25734
26060
|
mkdirSync5(this.dir(to), { recursive: true });
|
|
25735
|
-
if (
|
|
26061
|
+
if (existsSync12(toDir)) rmSync(toDir, { recursive: true, force: true });
|
|
25736
26062
|
renameSync(fromDir, toDir);
|
|
25737
26063
|
}
|
|
25738
26064
|
/** Submit a new skill candidate. Content is redacted of secret material at
|
|
@@ -25775,7 +26101,7 @@ var SkillLifecycle = class {
|
|
|
25775
26101
|
const contentSha256 = skillContentSha256(content);
|
|
25776
26102
|
const id = skillIdFor(name, contentSha256);
|
|
25777
26103
|
const { metadata, skill } = this.paths("candidate", id);
|
|
25778
|
-
if (
|
|
26104
|
+
if (existsSync12(metadata)) {
|
|
25779
26105
|
throw new SkillLifecycleError(
|
|
25780
26106
|
"skill_candidate_exists",
|
|
25781
26107
|
`identical candidate ${id} already exists; changed behavior requires changed content`
|
|
@@ -25884,11 +26210,11 @@ var SkillLifecycle = class {
|
|
|
25884
26210
|
};
|
|
25885
26211
|
const candidatePaths = this.paths("candidate", candidateId);
|
|
25886
26212
|
const promotedPaths = this.paths("promoted", candidateId);
|
|
25887
|
-
const skillContent =
|
|
25888
|
-
const metadataContent =
|
|
26213
|
+
const skillContent = readFileSync10(candidatePaths.skill, "utf8");
|
|
26214
|
+
const metadataContent = readFileSync10(candidatePaths.metadata, "utf8");
|
|
25889
26215
|
this.write(promotedPaths.skill, skillContent);
|
|
25890
26216
|
this.write(promotedPaths.metadata, metadataContent);
|
|
25891
|
-
const patchPath =
|
|
26217
|
+
const patchPath = join13(this.root, "patches", `${candidateId}.patch`);
|
|
25892
26218
|
const relSkillPath = `.kxm/skills/promoted/${candidateId}/SKILL.md`;
|
|
25893
26219
|
const relMetaPath = `.kxm/skills/promoted/${candidateId}/metadata.json`;
|
|
25894
26220
|
const patch = `${createUnifiedPatch(relSkillPath, skillContent)}${createUnifiedPatch(relMetaPath, metadataContent)}`;
|
|
@@ -25916,7 +26242,7 @@ var SkillLifecycle = class {
|
|
|
25916
26242
|
verify(state, id) {
|
|
25917
26243
|
const metadata = this.readMetadata(state, id);
|
|
25918
26244
|
const { skill } = this.paths(state, id);
|
|
25919
|
-
const content =
|
|
26245
|
+
const content = readFileSync10(skill, "utf8");
|
|
25920
26246
|
if (skillContentSha256(content) !== metadata.contentSha256) {
|
|
25921
26247
|
throw new SkillLifecycleError(
|
|
25922
26248
|
"skill_integrity_violation",
|
|
@@ -25934,7 +26260,7 @@ var SkillLifecycle = class {
|
|
|
25934
26260
|
}
|
|
25935
26261
|
list(state) {
|
|
25936
26262
|
const dir = this.dir(state);
|
|
25937
|
-
if (!
|
|
26263
|
+
if (!existsSync12(dir)) return [];
|
|
25938
26264
|
const ids = readdirSorted(dir);
|
|
25939
26265
|
return ids.map((id) => {
|
|
25940
26266
|
try {
|
|
@@ -25947,7 +26273,7 @@ var SkillLifecycle = class {
|
|
|
25947
26273
|
read(state, id) {
|
|
25948
26274
|
const metadata = this.readMetadata(state, id);
|
|
25949
26275
|
const { skill } = this.paths(state, id);
|
|
25950
|
-
return { metadata, content:
|
|
26276
|
+
return { metadata, content: readFileSync10(skill, "utf8") };
|
|
25951
26277
|
}
|
|
25952
26278
|
};
|
|
25953
26279
|
function boundedList(value, field) {
|
|
@@ -25962,7 +26288,7 @@ function boundedList(value, field) {
|
|
|
25962
26288
|
return [...new Set(refs)];
|
|
25963
26289
|
}
|
|
25964
26290
|
function readdirSorted(dir) {
|
|
25965
|
-
return
|
|
26291
|
+
return readdirSync8(dir).filter((entry) => statSync3(join13(dir, entry)).isDirectory()).sort();
|
|
25966
26292
|
}
|
|
25967
26293
|
|
|
25968
26294
|
// plugins/kxm/src/dispatch-context.ts
|
|
@@ -26003,18 +26329,18 @@ function contextRoleForAgent(agentId) {
|
|
|
26003
26329
|
return agentId;
|
|
26004
26330
|
}
|
|
26005
26331
|
function dispatchContextPresent(projectRoot) {
|
|
26006
|
-
return hasTopLevelMarkdown(
|
|
26332
|
+
return hasTopLevelMarkdown(join14(projectRoot, ".kxm", "memory")) || hasAnyEntry(join14(projectRoot, ".kxm", "skills", "promoted"));
|
|
26007
26333
|
}
|
|
26008
26334
|
function hasTopLevelMarkdown(dir) {
|
|
26009
26335
|
try {
|
|
26010
|
-
return
|
|
26336
|
+
return readdirSync9(dir, { withFileTypes: true }).some((entry) => entry.isFile() && extname3(entry.name) === ".md");
|
|
26011
26337
|
} catch {
|
|
26012
26338
|
return false;
|
|
26013
26339
|
}
|
|
26014
26340
|
}
|
|
26015
26341
|
function hasAnyEntry(dir) {
|
|
26016
26342
|
try {
|
|
26017
|
-
return
|
|
26343
|
+
return readdirSync9(dir).length > 0;
|
|
26018
26344
|
} catch {
|
|
26019
26345
|
return false;
|
|
26020
26346
|
}
|
|
@@ -26079,7 +26405,7 @@ function loadDispatchContextSources(input) {
|
|
|
26079
26405
|
}
|
|
26080
26406
|
const promoted = [];
|
|
26081
26407
|
try {
|
|
26082
|
-
const lifecycle = new SkillLifecycle(
|
|
26408
|
+
const lifecycle = new SkillLifecycle(join14(projectRoot, ".kxm", "skills"));
|
|
26083
26409
|
for (const metadata of lifecycle.list("promoted")) {
|
|
26084
26410
|
try {
|
|
26085
26411
|
promoted.push(lifecycle.verify("promoted", metadata.id));
|
|
@@ -26162,7 +26488,7 @@ function assembleDispatchContext(sources, input) {
|
|
|
26162
26488
|
}
|
|
26163
26489
|
|
|
26164
26490
|
// plugins/kxm/src/engine-artifacts.ts
|
|
26165
|
-
import { join as
|
|
26491
|
+
import { join as join15 } from "node:path";
|
|
26166
26492
|
|
|
26167
26493
|
// plugins/kxm/src/artifacts-exist.ts
|
|
26168
26494
|
import { lstatSync as lstatSync5, realpathSync as realpathSync4, statSync as statSync4 } from "node:fs";
|
|
@@ -26209,14 +26535,14 @@ function verifyArtifactExists(rootInput, pathInput) {
|
|
|
26209
26535
|
// plugins/kxm/src/engine-artifacts.ts
|
|
26210
26536
|
var kxmArtifactsGateSeams = {};
|
|
26211
26537
|
function evaluateArtifactsGate(context, definition) {
|
|
26212
|
-
const root =
|
|
26538
|
+
const root = join15(context.projectRoot, ".kxm", "assets");
|
|
26213
26539
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
26214
26540
|
const startTime = process.hrtime.bigint();
|
|
26215
26541
|
let checkedCount = 0;
|
|
26216
26542
|
let failedCount = 0;
|
|
26217
26543
|
for (const [index, path] of definition.paths.entries()) {
|
|
26218
26544
|
checkedCount += 1;
|
|
26219
|
-
const result = verifyArtifactExists(root,
|
|
26545
|
+
const result = verifyArtifactExists(root, join15(root, path));
|
|
26220
26546
|
if (!result.ok) failedCount += 1;
|
|
26221
26547
|
kxmArtifactsGateSeams.afterPathChecked?.({ path, index, checkedCount, failedCount });
|
|
26222
26548
|
}
|
|
@@ -27343,7 +27669,7 @@ function pinKxmCompiledPlan(context, bundle, runId, options) {
|
|
|
27343
27669
|
if (String(bundle.project.value.id) !== run.projectId) {
|
|
27344
27670
|
throw runtimeError("run_owner_mismatch", runId, "bundle project does not match the run");
|
|
27345
27671
|
}
|
|
27346
|
-
const workflow = bundle.workflows.
|
|
27672
|
+
const workflow = lookupKxmResource(bundle.workflows.values(), run.workflowId, "workflow");
|
|
27347
27673
|
if (!workflow) throw runtimeError("run_workflow_unknown", run.workflowId, `workflow ${run.workflowId} does not exist in this project`);
|
|
27348
27674
|
const revisions = kxmPolicyRevisions(bundle, options);
|
|
27349
27675
|
if (revisions.configRevision !== run.configRevision || revisions.executorPolicyRevision !== run.executorPolicyRevision || revisions.toolPolicyRevision !== run.toolPolicyRevision || revisions.memoryRevision !== run.memoryRevision) {
|
|
@@ -28076,9 +28402,9 @@ function readYamlFile(file) {
|
|
|
28076
28402
|
return readModelDocument(file);
|
|
28077
28403
|
}
|
|
28078
28404
|
function readModelDocument(file) {
|
|
28079
|
-
if (!
|
|
28405
|
+
if (!existsSync13(file)) return void 0;
|
|
28080
28406
|
try {
|
|
28081
|
-
const parsed = (0,
|
|
28407
|
+
const parsed = (0, import_yaml10.parse)(readFileSync11(file, "utf8"));
|
|
28082
28408
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
28083
28409
|
return parsed;
|
|
28084
28410
|
} catch {
|
|
@@ -28109,7 +28435,8 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
28109
28435
|
}
|
|
28110
28436
|
};
|
|
28111
28437
|
}
|
|
28112
|
-
const
|
|
28438
|
+
const agentFile = findYamlBasename(join16(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
|
|
28439
|
+
const agent = readYamlFile(join16(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
|
|
28113
28440
|
const role = typeof agent?.role === "string" ? agent.role : "";
|
|
28114
28441
|
if (!role) {
|
|
28115
28442
|
return {
|
|
@@ -28120,7 +28447,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
28120
28447
|
}
|
|
28121
28448
|
};
|
|
28122
28449
|
}
|
|
28123
|
-
const roleDoc = readYamlFile(
|
|
28450
|
+
const roleDoc = readYamlFile(join16(projectRoot, ".kxm", "roles", `${role}.yaml`));
|
|
28124
28451
|
const roster = Array.isArray(roleDoc?.roster) ? roleDoc.roster : void 0;
|
|
28125
28452
|
if (!roster) {
|
|
28126
28453
|
return {
|
|
@@ -28136,7 +28463,8 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
28136
28463
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
28137
28464
|
const record2 = entry;
|
|
28138
28465
|
if (typeof record2.route !== "string" || record2.route.length === 0) continue;
|
|
28139
|
-
const
|
|
28466
|
+
const routeFile = findYamlBasename(join16(projectRoot, ".kxm", "models"), record2.route, "route") ?? record2.route;
|
|
28467
|
+
const modelPath = join16(projectRoot, ".kxm", "models", `${routeFile}.yaml`);
|
|
28140
28468
|
const doc = readModelDocument(modelPath);
|
|
28141
28469
|
if (!doc) {
|
|
28142
28470
|
return {
|
|
@@ -28199,7 +28527,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
28199
28527
|
model: selector.slice(slash + 1),
|
|
28200
28528
|
selector,
|
|
28201
28529
|
harness,
|
|
28202
|
-
routeId:
|
|
28530
|
+
routeId: routeFile,
|
|
28203
28531
|
role,
|
|
28204
28532
|
permissions,
|
|
28205
28533
|
...typeof record2.effort === "string" ? { effort: record2.effort } : {}
|
|
@@ -28241,7 +28569,7 @@ function settleExitedExecutingAttempt(context, run, executing) {
|
|
|
28241
28569
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
28242
28570
|
let mono = kxmMonotonicNs();
|
|
28243
28571
|
const events = [];
|
|
28244
|
-
const
|
|
28572
|
+
const push2 = (eventType, payload) => {
|
|
28245
28573
|
events.push({
|
|
28246
28574
|
...kxmEventBase(context, run, now, mono),
|
|
28247
28575
|
eventId: newKxmEventId(),
|
|
@@ -28251,18 +28579,18 @@ function settleExitedExecutingAttempt(context, run, executing) {
|
|
|
28251
28579
|
});
|
|
28252
28580
|
mono = kxmIncrementMonotonicNs(mono);
|
|
28253
28581
|
};
|
|
28254
|
-
|
|
28255
|
-
|
|
28582
|
+
push2("attempt.status_changed", { attemptId: executing.attemptId, status: "settling" });
|
|
28583
|
+
push2("assignment.result_recorded", {
|
|
28256
28584
|
assignmentId: executing.assignmentId,
|
|
28257
28585
|
resultClass: "producer_rejected",
|
|
28258
28586
|
status: "result_recorded",
|
|
28259
28587
|
producerError: "executing_unrecorded",
|
|
28260
28588
|
reason: "executing_unrecorded"
|
|
28261
28589
|
});
|
|
28262
|
-
|
|
28263
|
-
|
|
28264
|
-
|
|
28265
|
-
|
|
28590
|
+
push2("attempt.status_changed", { attemptId: executing.attemptId, status: "terminal" });
|
|
28591
|
+
push2("assignment.terminal", { assignmentId: executing.assignmentId, outcome: "failed", status: "terminal" });
|
|
28592
|
+
push2("step.status_changed", { stepId: executing.stepId, status: "cancelled", previousStatus: "running" });
|
|
28593
|
+
push2("run.status_changed", { status: "cancelled", reason: cancelReasonFromLog2(context, run.runId) });
|
|
28266
28594
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
28267
28595
|
context.eventStore.settleCapability(executing.attemptId, "settled");
|
|
28268
28596
|
const next = foldStoredKxmRun(context, run);
|
|
@@ -28443,7 +28771,7 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
28443
28771
|
let sequence = context.eventStore.nextSequence(runId);
|
|
28444
28772
|
let mono = kxmMonotonicNs();
|
|
28445
28773
|
const events = [];
|
|
28446
|
-
const
|
|
28774
|
+
const push2 = (eventType, payload) => {
|
|
28447
28775
|
events.push({
|
|
28448
28776
|
...kxmEventBase(context, run, now, mono),
|
|
28449
28777
|
eventId: newKxmEventId(),
|
|
@@ -28453,8 +28781,8 @@ function prepareDispatch(context, runId, producerId, dispatchSources) {
|
|
|
28453
28781
|
});
|
|
28454
28782
|
mono = kxmIncrementMonotonicNs(mono);
|
|
28455
28783
|
};
|
|
28456
|
-
|
|
28457
|
-
|
|
28784
|
+
push2("step.entered", { stepId, stepAttempt, status: "pending" });
|
|
28785
|
+
push2("step.status_changed", { stepId, status: "preparing", previousStatus: "pending" });
|
|
28458
28786
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
28459
28787
|
const entered = foldStoredKxmRun(context, run);
|
|
28460
28788
|
persistKxmRunState(context, runId, entered, events[events.length - 1].sequence);
|
|
@@ -28555,7 +28883,7 @@ function birthMember(context, input) {
|
|
|
28555
28883
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
28556
28884
|
let mono = kxmMonotonicNs();
|
|
28557
28885
|
const events = [];
|
|
28558
|
-
const
|
|
28886
|
+
const push2 = (eventType, payload) => {
|
|
28559
28887
|
events.push({
|
|
28560
28888
|
...kxmEventBase(context, run, now, mono),
|
|
28561
28889
|
eventId: newKxmEventId(),
|
|
@@ -28565,18 +28893,18 @@ function birthMember(context, input) {
|
|
|
28565
28893
|
});
|
|
28566
28894
|
mono = kxmIncrementMonotonicNs(mono);
|
|
28567
28895
|
};
|
|
28568
|
-
|
|
28569
|
-
|
|
28570
|
-
|
|
28571
|
-
|
|
28896
|
+
push2("assignment.created", { assignmentId, stepId: input.stepId, stepAttempt: input.stepAttempt, agentId, status: "created" });
|
|
28897
|
+
push2("assignment.accepted", { assignmentId, status: "accepted" });
|
|
28898
|
+
push2("attempt.created", { attemptId, assignmentId, status: "created" });
|
|
28899
|
+
push2("assignment.dispatched", {
|
|
28572
28900
|
assignmentId,
|
|
28573
28901
|
capabilityHash: minted.hash,
|
|
28574
28902
|
status: "dispatched",
|
|
28575
28903
|
...resolvedRoute ? { model: { provider: resolvedRoute.provider, model: resolvedRoute.model } } : {}
|
|
28576
28904
|
});
|
|
28577
|
-
|
|
28905
|
+
push2("attempt.status_changed", { attemptId, status: "starting" });
|
|
28578
28906
|
if (input.enterRunning) {
|
|
28579
|
-
|
|
28907
|
+
push2("step.status_changed", { stepId: input.stepId, status: "running", previousStatus: "preparing" });
|
|
28580
28908
|
}
|
|
28581
28909
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
28582
28910
|
context.eventStore.insertCapability({
|
|
@@ -29032,7 +29360,7 @@ function settleMember(context, dispatch, result, produceError, produceErrorCode)
|
|
|
29032
29360
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
29033
29361
|
let mono = kxmMonotonicNs();
|
|
29034
29362
|
const events = [];
|
|
29035
|
-
const
|
|
29363
|
+
const push2 = (eventType, payload) => {
|
|
29036
29364
|
events.push({
|
|
29037
29365
|
...kxmEventBase(context, run, now, mono),
|
|
29038
29366
|
eventId: newKxmEventId(),
|
|
@@ -29042,17 +29370,17 @@ function settleMember(context, dispatch, result, produceError, produceErrorCode)
|
|
|
29042
29370
|
});
|
|
29043
29371
|
mono = kxmIncrementMonotonicNs(mono);
|
|
29044
29372
|
};
|
|
29045
|
-
|
|
29373
|
+
push2("attempt.status_changed", { attemptId: dispatch.attemptId, status: "settling" });
|
|
29046
29374
|
if (cancelling) {
|
|
29047
|
-
|
|
29375
|
+
push2("routing.attempt.recorded", { routing: producerRoutingRecord(
|
|
29048
29376
|
context,
|
|
29049
29377
|
dispatch,
|
|
29050
29378
|
result ?? { outcome: "cancelled", costBasis: "unknown", costUsd: null },
|
|
29051
29379
|
now
|
|
29052
29380
|
) });
|
|
29053
|
-
|
|
29054
|
-
|
|
29055
|
-
|
|
29381
|
+
push2("assignment.result_recorded", { assignmentId: dispatch.assignmentId, resultClass: "cancelled", status: "result_recorded" });
|
|
29382
|
+
push2("attempt.status_changed", { attemptId: dispatch.attemptId, status: "terminal" });
|
|
29383
|
+
push2("assignment.terminal", { assignmentId: dispatch.assignmentId, outcome: "cancelled", status: "terminal" });
|
|
29056
29384
|
} else if (produceError) {
|
|
29057
29385
|
const envelope2 = loadKxmRunPlanEnvelope(context.eventStore, run);
|
|
29058
29386
|
const rawHash = result?.behavioralSha256 ?? behavioralConfigHash({
|
|
@@ -29086,22 +29414,22 @@ function settleMember(context, dispatch, result, produceError, produceErrorCode)
|
|
|
29086
29414
|
finalOutcome: "failed",
|
|
29087
29415
|
providerMetadata: engineRoutingMetadata(dispatch)
|
|
29088
29416
|
};
|
|
29089
|
-
|
|
29090
|
-
|
|
29417
|
+
push2("routing.attempt.recorded", { routing: parseRoutingRecordV2(routingRecord) });
|
|
29418
|
+
push2("assignment.result_recorded", {
|
|
29091
29419
|
assignmentId: dispatch.assignmentId,
|
|
29092
29420
|
resultClass: "producer_rejected",
|
|
29093
29421
|
status: "result_recorded",
|
|
29094
29422
|
...produceErrorCode !== void 0 ? { producerError: produceErrorCode } : {}
|
|
29095
29423
|
});
|
|
29096
|
-
|
|
29097
|
-
|
|
29424
|
+
push2("attempt.status_changed", { attemptId: dispatch.attemptId, status: "terminal" });
|
|
29425
|
+
push2("assignment.terminal", { assignmentId: dispatch.assignmentId, outcome: "failed", status: "terminal" });
|
|
29098
29426
|
} else {
|
|
29099
29427
|
if (!result) {
|
|
29100
29428
|
throw runtimeError("settle_missing_cost_basis", run.runId, `attempt settlement rejected: missing required costBasis for attempt ${dispatch.attemptId}`);
|
|
29101
29429
|
}
|
|
29102
29430
|
const outcome = typeof result.outcome === "string" ? result.outcome : void 0;
|
|
29103
29431
|
const known = outcome !== void 0 && dispatch.step.outcomes.includes(outcome);
|
|
29104
|
-
|
|
29432
|
+
push2("routing.attempt.recorded", {
|
|
29105
29433
|
routing: producerRoutingRecord(
|
|
29106
29434
|
context,
|
|
29107
29435
|
dispatch,
|
|
@@ -29111,22 +29439,22 @@ function settleMember(context, dispatch, result, produceError, produceErrorCode)
|
|
|
29111
29439
|
)
|
|
29112
29440
|
});
|
|
29113
29441
|
if (!known) {
|
|
29114
|
-
|
|
29442
|
+
push2("assignment.result_recorded", {
|
|
29115
29443
|
assignmentId: dispatch.assignmentId,
|
|
29116
29444
|
resultClass: "outcome_unknown",
|
|
29117
29445
|
status: "result_recorded"
|
|
29118
29446
|
});
|
|
29119
|
-
|
|
29120
|
-
|
|
29447
|
+
push2("attempt.status_changed", { attemptId: dispatch.attemptId, status: "terminal" });
|
|
29448
|
+
push2("assignment.terminal", { assignmentId: dispatch.assignmentId, outcome: "failed", status: "terminal" });
|
|
29121
29449
|
} else {
|
|
29122
|
-
|
|
29450
|
+
push2("assignment.result_recorded", {
|
|
29123
29451
|
assignmentId: dispatch.assignmentId,
|
|
29124
29452
|
outcome,
|
|
29125
29453
|
resultClass: "outcome",
|
|
29126
29454
|
status: "result_recorded"
|
|
29127
29455
|
});
|
|
29128
|
-
|
|
29129
|
-
|
|
29456
|
+
push2("attempt.status_changed", { attemptId: dispatch.attemptId, status: "terminal" });
|
|
29457
|
+
push2("assignment.terminal", { assignmentId: dispatch.assignmentId, outcome, status: "terminal" });
|
|
29130
29458
|
}
|
|
29131
29459
|
}
|
|
29132
29460
|
context.eventStore.settleCapability(dispatch.attemptId, "settled");
|
|
@@ -29146,7 +29474,7 @@ function joinPanel(context, panel) {
|
|
|
29146
29474
|
let sequence = context.eventStore.nextSequence(run.runId);
|
|
29147
29475
|
let mono = kxmMonotonicNs();
|
|
29148
29476
|
const events = [];
|
|
29149
|
-
const
|
|
29477
|
+
const push2 = (eventType, payload) => {
|
|
29150
29478
|
events.push({
|
|
29151
29479
|
...kxmEventBase(context, run, now, mono),
|
|
29152
29480
|
eventId: newKxmEventId(),
|
|
@@ -29157,8 +29485,8 @@ function joinPanel(context, panel) {
|
|
|
29157
29485
|
mono = kxmIncrementMonotonicNs(mono);
|
|
29158
29486
|
};
|
|
29159
29487
|
const failRun = (reason) => {
|
|
29160
|
-
|
|
29161
|
-
|
|
29488
|
+
push2("step.status_changed", { stepId: panel.stepId, status: "failed", previousStatus: "running" });
|
|
29489
|
+
push2("run.status_changed", { status: "failed", reason, stepId: panel.stepId });
|
|
29162
29490
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
29163
29491
|
const next2 = foldStoredKxmRun(context, run);
|
|
29164
29492
|
persistKxmRunState(context, run.runId, next2, events[events.length - 1].sequence);
|
|
@@ -29166,8 +29494,8 @@ function joinPanel(context, panel) {
|
|
|
29166
29494
|
return { state: next2 };
|
|
29167
29495
|
};
|
|
29168
29496
|
const cancelRun = () => {
|
|
29169
|
-
|
|
29170
|
-
|
|
29497
|
+
push2("step.status_changed", { stepId: panel.stepId, status: "cancelled", previousStatus: "running" });
|
|
29498
|
+
push2("run.status_changed", { status: "cancelled", reason: cancelReasonFromLog2(context, run.runId) });
|
|
29171
29499
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
29172
29500
|
const next2 = foldStoredKxmRun(context, run);
|
|
29173
29501
|
persistKxmRunState(context, run.runId, next2, events[events.length - 1].sequence);
|
|
@@ -29199,12 +29527,12 @@ function joinPanel(context, panel) {
|
|
|
29199
29527
|
const outcome = joined.outcome;
|
|
29200
29528
|
const selected = panel.step.transitions[outcome];
|
|
29201
29529
|
if (!selected) return failRun("outcome_unknown");
|
|
29202
|
-
|
|
29530
|
+
push2("step.outcome_recorded", { stepId: panel.stepId, stepAttempt: panel.stepAttempt, outcome });
|
|
29203
29531
|
const stepStatus = outcome === "passed" ? "passed" : outcome === "cancelled" ? "cancelled" : "failed";
|
|
29204
|
-
|
|
29532
|
+
push2("step.status_changed", { stepId: panel.stepId, status: stepStatus, previousStatus: "running" });
|
|
29205
29533
|
const budget = transitionBudgetFailure(panel.plan, state, panel.stepId, outcome);
|
|
29206
29534
|
if (budget) {
|
|
29207
|
-
|
|
29535
|
+
push2("run.status_changed", { status: "failed", reason: budget, stepId: panel.stepId, outcome });
|
|
29208
29536
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
29209
29537
|
const next2 = foldStoredKxmRun(context, run);
|
|
29210
29538
|
persistKxmRunState(context, run.runId, next2, events[events.length - 1].sequence);
|
|
@@ -29212,10 +29540,10 @@ function joinPanel(context, panel) {
|
|
|
29212
29540
|
return { state: next2 };
|
|
29213
29541
|
}
|
|
29214
29542
|
if (selected.to === "step") {
|
|
29215
|
-
|
|
29543
|
+
push2("step.transitioned", { fromStepId: panel.stepId, toStepId: selected.target, outcome });
|
|
29216
29544
|
} else {
|
|
29217
|
-
|
|
29218
|
-
|
|
29545
|
+
push2("step.transitioned", { fromStepId: panel.stepId, status: selected.terminalStatus, outcome });
|
|
29546
|
+
push2("run.status_changed", { status: selected.terminalStatus });
|
|
29219
29547
|
}
|
|
29220
29548
|
for (const event of events) context.eventStore.appendEvent(event);
|
|
29221
29549
|
const next = foldStoredKxmRun(context, run);
|
|
@@ -29819,7 +30147,7 @@ function recoverKxmRun(context, runId, request) {
|
|
|
29819
30147
|
let sequence = context.eventStore.nextSequence(runId);
|
|
29820
30148
|
let mono = kxmMonotonicNs();
|
|
29821
30149
|
const events = [];
|
|
29822
|
-
const
|
|
30150
|
+
const push2 = (eventType, payload) => {
|
|
29823
30151
|
events.push({
|
|
29824
30152
|
...kxmEventBase(context, run, now, mono, request.commandId),
|
|
29825
30153
|
eventId: newKxmEventId(),
|
|
@@ -29830,7 +30158,7 @@ function recoverKxmRun(context, runId, request) {
|
|
|
29830
30158
|
mono = kxmIncrementMonotonicNs(mono);
|
|
29831
30159
|
};
|
|
29832
30160
|
if (state.status === "running" && state.currentStep?.effectState === "blocked_uncertain") {
|
|
29833
|
-
|
|
30161
|
+
push2("run.status_changed", {
|
|
29834
30162
|
status: "blocked_uncertain",
|
|
29835
30163
|
previousStatus: "running",
|
|
29836
30164
|
reason: request.reason ?? "uncertain gate effect"
|
|
@@ -29850,7 +30178,7 @@ function recoverKxmRun(context, runId, request) {
|
|
|
29850
30178
|
}
|
|
29851
30179
|
}
|
|
29852
30180
|
if (request.action === "retry" || request.action === "unblock") {
|
|
29853
|
-
|
|
30181
|
+
push2("run.status_changed", {
|
|
29854
30182
|
status: "running",
|
|
29855
30183
|
previousStatus: "blocked_uncertain",
|
|
29856
30184
|
reason: request.reason ?? "operator_retry"
|
|
@@ -29862,7 +30190,7 @@ function recoverKxmRun(context, runId, request) {
|
|
|
29862
30190
|
return { state: nextState, unblocked: true };
|
|
29863
30191
|
}
|
|
29864
30192
|
if (request.action === "fail") {
|
|
29865
|
-
|
|
30193
|
+
push2("run.status_changed", {
|
|
29866
30194
|
status: "failed",
|
|
29867
30195
|
previousStatus: "blocked_uncertain",
|
|
29868
30196
|
reason: request.reason ?? "operator_fail"
|
|
@@ -29874,16 +30202,16 @@ function recoverKxmRun(context, runId, request) {
|
|
|
29874
30202
|
return { state: nextState, unblocked: true };
|
|
29875
30203
|
}
|
|
29876
30204
|
if (request.action === "cancel") {
|
|
29877
|
-
|
|
30205
|
+
push2("run.cancel_requested", {
|
|
29878
30206
|
actor: { kind: "runtime", id: context.homeRuntimeId },
|
|
29879
30207
|
reason: request.reason ?? "operator_cancel"
|
|
29880
30208
|
});
|
|
29881
|
-
|
|
30209
|
+
push2("run.status_changed", {
|
|
29882
30210
|
status: "cancelling",
|
|
29883
30211
|
previousStatus: "blocked_uncertain",
|
|
29884
30212
|
reason: request.reason ?? "operator_cancel"
|
|
29885
30213
|
});
|
|
29886
|
-
|
|
30214
|
+
push2("run.status_changed", {
|
|
29887
30215
|
status: "cancelled",
|
|
29888
30216
|
previousStatus: "cancelling",
|
|
29889
30217
|
reason: request.reason ?? "operator_cancel"
|
|
@@ -30390,9 +30718,9 @@ var RuntimeHubClient = class {
|
|
|
30390
30718
|
};
|
|
30391
30719
|
|
|
30392
30720
|
// plugins/kxm/src/hub-binding.ts
|
|
30393
|
-
import { existsSync as
|
|
30721
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, readFileSync as readFileSync12, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
30394
30722
|
import { homedir as homedir2 } from "node:os";
|
|
30395
|
-
import { dirname as dirname8, isAbsolute as isAbsolute6, join as
|
|
30723
|
+
import { dirname as dirname8, isAbsolute as isAbsolute6, join as join17, resolve as resolve9 } from "node:path";
|
|
30396
30724
|
var HUB_BINDING_SCHEMA = "kxm.hub-binding.v1";
|
|
30397
30725
|
var HubBindingError = class extends Error {
|
|
30398
30726
|
constructor(message) {
|
|
@@ -30408,16 +30736,16 @@ function resolveUserStateRoot(env) {
|
|
|
30408
30736
|
}
|
|
30409
30737
|
if (process.platform === "win32") {
|
|
30410
30738
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
30411
|
-
const base2 = localAppData && isAbsolute6(localAppData) ? localAppData :
|
|
30739
|
+
const base2 = localAppData && isAbsolute6(localAppData) ? localAppData : join17(homedir2(), "AppData", "Local");
|
|
30412
30740
|
return resolve9(base2, "KXM");
|
|
30413
30741
|
}
|
|
30414
30742
|
if (process.platform === "darwin") return resolve9(homedir2(), "Library", "Application Support", "KXM");
|
|
30415
30743
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
30416
|
-
const base = xdgState && isAbsolute6(xdgState) ? xdgState :
|
|
30744
|
+
const base = xdgState && isAbsolute6(xdgState) ? xdgState : join17(homedir2(), ".local", "state");
|
|
30417
30745
|
return resolve9(base, "kxm");
|
|
30418
30746
|
}
|
|
30419
30747
|
function hubBindingFile(env = process.env) {
|
|
30420
|
-
return
|
|
30748
|
+
return join17(resolveUserStateRoot(env), "hub-binding.json");
|
|
30421
30749
|
}
|
|
30422
30750
|
function validateHubUrl(raw) {
|
|
30423
30751
|
let parsed;
|
|
@@ -30437,10 +30765,10 @@ function isIsoTimestamp(value) {
|
|
|
30437
30765
|
}
|
|
30438
30766
|
function readHubBinding(env = process.env) {
|
|
30439
30767
|
const file = hubBindingFile(env);
|
|
30440
|
-
if (!
|
|
30768
|
+
if (!existsSync14(file)) return void 0;
|
|
30441
30769
|
let parsed;
|
|
30442
30770
|
try {
|
|
30443
|
-
parsed = JSON.parse(
|
|
30771
|
+
parsed = JSON.parse(readFileSync12(file, "utf8"));
|
|
30444
30772
|
} catch {
|
|
30445
30773
|
throw new HubBindingError(`malformed hub binding at ${file}`);
|
|
30446
30774
|
}
|
|
@@ -30462,9 +30790,9 @@ function readHubBinding(env = process.env) {
|
|
|
30462
30790
|
}
|
|
30463
30791
|
|
|
30464
30792
|
// plugins/kxm/src/hub-env.ts
|
|
30465
|
-
import { existsSync as
|
|
30793
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync13, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "node:fs";
|
|
30466
30794
|
import { homedir as homedir3 } from "node:os";
|
|
30467
|
-
import { dirname as dirname9, isAbsolute as isAbsolute7, join as
|
|
30795
|
+
import { dirname as dirname9, isAbsolute as isAbsolute7, join as join18, resolve as resolve10 } from "node:path";
|
|
30468
30796
|
var HUB_ENV_SCHEMA = "kxm.hub-env.v1";
|
|
30469
30797
|
var HubEnvError = class extends Error {
|
|
30470
30798
|
constructor(message) {
|
|
@@ -30480,23 +30808,23 @@ function resolveUserStateRoot2(env) {
|
|
|
30480
30808
|
}
|
|
30481
30809
|
if (process.platform === "win32") {
|
|
30482
30810
|
const localAppData = env.LOCALAPPDATA?.trim();
|
|
30483
|
-
const base2 = localAppData && isAbsolute7(localAppData) ? localAppData :
|
|
30811
|
+
const base2 = localAppData && isAbsolute7(localAppData) ? localAppData : join18(homedir3(), "AppData", "Local");
|
|
30484
30812
|
return resolve10(base2, "KXM");
|
|
30485
30813
|
}
|
|
30486
30814
|
if (process.platform === "darwin") return resolve10(homedir3(), "Library", "Application Support", "KXM");
|
|
30487
30815
|
const xdgState = env.XDG_STATE_HOME?.trim();
|
|
30488
|
-
const base = xdgState && isAbsolute7(xdgState) ? xdgState :
|
|
30816
|
+
const base = xdgState && isAbsolute7(xdgState) ? xdgState : join18(homedir3(), ".local", "state");
|
|
30489
30817
|
return resolve10(base, "kxm");
|
|
30490
30818
|
}
|
|
30491
30819
|
function hubEnvFile(env = process.env) {
|
|
30492
|
-
return
|
|
30820
|
+
return join18(resolveUserStateRoot2(env), "hub-env.json");
|
|
30493
30821
|
}
|
|
30494
30822
|
function readHubEnvRecord(env = process.env) {
|
|
30495
30823
|
const file = hubEnvFile(env);
|
|
30496
|
-
if (!
|
|
30824
|
+
if (!existsSync15(file)) return void 0;
|
|
30497
30825
|
let parsed;
|
|
30498
30826
|
try {
|
|
30499
|
-
parsed = JSON.parse(
|
|
30827
|
+
parsed = JSON.parse(readFileSync13(file, "utf8"));
|
|
30500
30828
|
} catch (error) {
|
|
30501
30829
|
throw new HubEnvError(`hub env file is malformed at ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
30502
30830
|
}
|
|
@@ -30536,7 +30864,7 @@ function resolveClientHubAuthToken(env, project) {
|
|
|
30536
30864
|
}
|
|
30537
30865
|
|
|
30538
30866
|
// plugins/kxm/src/logger.ts
|
|
30539
|
-
import { appendFileSync, existsSync as
|
|
30867
|
+
import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync8, renameSync as renameSync4, statSync as statSync5, unlinkSync as unlinkSync2 } from "node:fs";
|
|
30540
30868
|
import { dirname as dirname10 } from "node:path";
|
|
30541
30869
|
var LOG_LEVEL_PRIORITY = {
|
|
30542
30870
|
debug: 10,
|
|
@@ -30574,7 +30902,7 @@ function redactLogValue(val, key) {
|
|
|
30574
30902
|
function rotateLogFiles(filePath, maxFiles) {
|
|
30575
30903
|
for (let i = maxFiles; i >= 1; i--) {
|
|
30576
30904
|
const current = `${filePath}.${i}`;
|
|
30577
|
-
if (
|
|
30905
|
+
if (existsSync16(current)) {
|
|
30578
30906
|
if (i >= maxFiles) {
|
|
30579
30907
|
try {
|
|
30580
30908
|
unlinkSync2(current);
|
|
@@ -30588,7 +30916,7 @@ function rotateLogFiles(filePath, maxFiles) {
|
|
|
30588
30916
|
}
|
|
30589
30917
|
}
|
|
30590
30918
|
}
|
|
30591
|
-
if (
|
|
30919
|
+
if (existsSync16(filePath)) {
|
|
30592
30920
|
try {
|
|
30593
30921
|
renameSync4(filePath, `${filePath}.1`);
|
|
30594
30922
|
} catch {
|
|
@@ -30605,7 +30933,7 @@ function createLogger(options) {
|
|
|
30605
30933
|
const shouldStdout = options.stdout ?? !isDaemon;
|
|
30606
30934
|
const correlationDefaults = options.correlation ?? {};
|
|
30607
30935
|
let currentSize = 0;
|
|
30608
|
-
if (filePath &&
|
|
30936
|
+
if (filePath && existsSync16(filePath)) {
|
|
30609
30937
|
try {
|
|
30610
30938
|
currentSize = statSync5(filePath).size;
|
|
30611
30939
|
} catch {
|
|
@@ -30686,7 +31014,7 @@ function createLogger(options) {
|
|
|
30686
31014
|
|
|
30687
31015
|
// plugins/kxm/src/runtime-supervisor.ts
|
|
30688
31016
|
function kxmSupervisorTokenFile(paths) {
|
|
30689
|
-
return
|
|
31017
|
+
return join19(paths.runtimeDir, "supervisor.token");
|
|
30690
31018
|
}
|
|
30691
31019
|
function publishKxmSupervisorToken(paths, token) {
|
|
30692
31020
|
const file = kxmSupervisorTokenFile(paths);
|
|
@@ -30714,7 +31042,7 @@ function readKxmSupervisorToken(paths) {
|
|
|
30714
31042
|
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
30715
31043
|
throw runtimeError("runtime_path_invalid", file, "supervisor token file must be a regular file, not a link");
|
|
30716
31044
|
}
|
|
30717
|
-
const token =
|
|
31045
|
+
const token = readFileSync14(file, "utf8").trim();
|
|
30718
31046
|
return token.length >= 32 ? token : void 0;
|
|
30719
31047
|
}
|
|
30720
31048
|
function processAlive(pid) {
|
|
@@ -30728,11 +31056,11 @@ function processAlive(pid) {
|
|
|
30728
31056
|
var HEARTBEAT_STALE_MS = 15e3;
|
|
30729
31057
|
var SUPERVISOR_ERROR_MAX_AGE_MS = 3e4;
|
|
30730
31058
|
function supervisorErrorFile(paths) {
|
|
30731
|
-
return
|
|
31059
|
+
return join19(paths.runtimeDir, "supervisor.error");
|
|
30732
31060
|
}
|
|
30733
31061
|
function clearSupervisorError(paths) {
|
|
30734
31062
|
const file = supervisorErrorFile(paths);
|
|
30735
|
-
if (
|
|
31063
|
+
if (existsSync17(file)) rmSync4(file, { force: true });
|
|
30736
31064
|
}
|
|
30737
31065
|
function recordSupervisorError(paths, message) {
|
|
30738
31066
|
try {
|
|
@@ -30749,7 +31077,7 @@ function readRecentSupervisorError(paths) {
|
|
|
30749
31077
|
const ageMs = Date.now() - stat.mtimeMs;
|
|
30750
31078
|
if (ageMs > SUPERVISOR_ERROR_MAX_AGE_MS) return void 0;
|
|
30751
31079
|
try {
|
|
30752
|
-
return
|
|
31080
|
+
return readFileSync14(file, "utf8").trim();
|
|
30753
31081
|
} catch {
|
|
30754
31082
|
return void 0;
|
|
30755
31083
|
}
|
|
@@ -30788,7 +31116,7 @@ function supervisorStatusOf(record2) {
|
|
|
30788
31116
|
};
|
|
30789
31117
|
}
|
|
30790
31118
|
function kxmSupervisorStatus(paths, options = {}) {
|
|
30791
|
-
if (!
|
|
31119
|
+
if (!existsSync17(paths.registryDb)) return { running: false };
|
|
30792
31120
|
if (options.readOnly) {
|
|
30793
31121
|
const database = openReadOnlyDatabase(paths.registryDb);
|
|
30794
31122
|
try {
|
|
@@ -30854,7 +31182,7 @@ async function ensureKxmSupervisor(options = {}) {
|
|
|
30854
31182
|
throw runtimeError("runtime_supervisor_unreachable", paths.registryDb, `runtime supervisor pid ${status.pid} is registered as running but cannot be probed`);
|
|
30855
31183
|
}
|
|
30856
31184
|
clearSupervisorError(paths);
|
|
30857
|
-
const scriptPath =
|
|
31185
|
+
const scriptPath = join19(findKxmRepoRoot(import.meta.url), "scripts", "kxm-runtime-supervisor.mjs");
|
|
30858
31186
|
const spawnImpl = options.spawnImpl ?? ((script, env) => {
|
|
30859
31187
|
const child = spawn3(process.execPath, [script], {
|
|
30860
31188
|
detached: true,
|
|
@@ -31077,7 +31405,7 @@ function syncFailureText(error) {
|
|
|
31077
31405
|
return `${code ? `${code}: ` : ""}${message}`.replace(/(kxm_[A-Za-z0-9_]+|[A-Za-z0-9._-]{40,})/g, "[redacted]").slice(0, 300);
|
|
31078
31406
|
}
|
|
31079
31407
|
function runtimeSupervisorLogFile(paths) {
|
|
31080
|
-
return
|
|
31408
|
+
return join19(paths.runtimeDir, "logs", "kxm-runtime.jsonl");
|
|
31081
31409
|
}
|
|
31082
31410
|
async function startKxmRuntimeSupervisor(options = {}) {
|
|
31083
31411
|
const now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
@@ -31202,8 +31530,8 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
|
|
|
31202
31530
|
openRunIds = unsettledRunIds(existing2);
|
|
31203
31531
|
} else {
|
|
31204
31532
|
const registration = registry.projectByRoot(projectRoot);
|
|
31205
|
-
const eventsPath =
|
|
31206
|
-
if (registration &&
|
|
31533
|
+
const eventsPath = join19(paths.projectsDir, registration?.projectKey ?? projectRuntimeKey(projectRoot), "run-events.db");
|
|
31534
|
+
if (registration && existsSync17(eventsPath)) {
|
|
31207
31535
|
const store = new KxmRunEventStore(eventsPath);
|
|
31208
31536
|
try {
|
|
31209
31537
|
openRunIds = store.runsForProject(registration.projectId, 1e4).filter((run) => !SETTLED_RUN_STATUS.has(run.status)).map((run) => run.runId);
|