@kontextmind/kxm 0.7.130 → 0.7.131
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 +4 -1
- package/.kxm/agents/coordinator.yaml +1 -0
- package/.kxm/agents/critic-arch.yaml +1 -4
- package/.kxm/agents/critic-cli.yaml +1 -4
- package/.kxm/agents/implementer.yaml +1 -4
- package/.kxm/workflows/implement-only.yaml +1 -1
- package/.kxm/workflows/review-arch-only.yaml +1 -1
- package/.kxm/workflows/review-cli-only.yaml +1 -1
- package/CHANGELOG.md +22 -1
- package/docs/concepts/architecture.md +1 -1
- package/docs/contracts/routing.md +3 -3
- package/docs/contributing/assignment-runner.md +14 -15
- package/docs/contributing/development.md +19 -5
- package/docs/contributing/harness-routing-internals.md +8 -8
- package/docs/glossary.md +1 -1
- package/docs/guides/agent-skills.md +1 -1
- package/docs/operations/backup-and-restore.md +4 -4
- package/docs/reference/cli-reference.md +26 -8
- package/docs/reference/config-reference.md +76 -153
- package/docs/reference/harness-routing.md +29 -21
- package/docs/reference/workflow-catalog.md +4 -4
- package/docs/start/first-workflow.md +1 -1
- package/examples/project/.kxm/agents/coordinator.yaml +1 -2
- package/examples/project/.kxm/agents/critic-1.yaml +1 -3
- package/examples/project/.kxm/agents/critic-2.yaml +1 -2
- package/examples/project/.kxm/agents/critic-3.yaml +2 -3
- package/examples/project/.kxm/agents/implementer.yaml +1 -2
- package/examples/project/.kxm/agents/planner.yaml +1 -2
- package/examples/project/.kxm/agents/reproducer.yaml +1 -2
- package/examples/project/.kxm/agents/reviewer.yaml +1 -2
- package/examples/project/.kxm/workflows/fix.yaml +0 -12
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +287 -249
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +222 -242
- package/plugins/kxm/dist/runtime.js +224 -244
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +8 -0
- package/plugins/kxm/src/cli/project.ts +4 -4
- package/plugins/kxm/src/engine.ts +168 -163
- package/plugins/kxm/src/init-guide-setup.ts +75 -15
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/oneshot-producer.ts +3 -15
- package/plugins/kxm/src/project-config.ts +83 -70
- package/plugins/kxm/src/routes.ts +26 -18
- package/plugins/kxm/src/runtime-supervisor.ts +0 -18
- package/plugins/kxm/src/suggest.ts +2 -2
- package/plugins/kxm/src/template.ts +2 -14
- package/schemas/agent.schema.json +8 -0
- package/scripts/roster-policy.d.mts +2 -1
- package/scripts/roster-policy.mjs +119 -22
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -17362,21 +17362,9 @@ function coreTemplate(projectId, projectName, variant) {
|
|
|
17362
17362
|
]);
|
|
17363
17363
|
if (variant === "v4-registry") {
|
|
17364
17364
|
const coordinator = files.get(".kxm/agents/coordinator.yaml");
|
|
17365
|
-
if (coordinator) {
|
|
17366
|
-
files.set(".kxm/agents/coordinator.yaml", {
|
|
17367
|
-
...coordinator,
|
|
17368
|
-
harness: "claude",
|
|
17369
|
-
model: { provider: "anthropic", model: "fable" }
|
|
17370
|
-
});
|
|
17371
|
-
}
|
|
17365
|
+
if (coordinator) files.set(".kxm/agents/coordinator.yaml", { ...coordinator, role: "planner" });
|
|
17372
17366
|
const implementer = files.get(".kxm/agents/implementer.yaml");
|
|
17373
|
-
if (implementer) {
|
|
17374
|
-
files.set(".kxm/agents/implementer.yaml", {
|
|
17375
|
-
...implementer,
|
|
17376
|
-
harness: "grok",
|
|
17377
|
-
model: { provider: "xai", model: "grok-4.6" }
|
|
17378
|
-
});
|
|
17379
|
-
}
|
|
17367
|
+
if (implementer) files.set(".kxm/agents/implementer.yaml", { ...implementer, role: "writer" });
|
|
17380
17368
|
const workflow = files.get(".kxm/workflows/default.yaml");
|
|
17381
17369
|
if (workflow) {
|
|
17382
17370
|
const limits = { ...workflow.limits };
|
|
@@ -18910,7 +18898,7 @@ function readTemplateProvenance(registry, root) {
|
|
|
18910
18898
|
}
|
|
18911
18899
|
return value;
|
|
18912
18900
|
}
|
|
18913
|
-
function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId) {
|
|
18901
|
+
function listNamedResources(registry, root, directory, logicalDirectory, kind, replacedId, collected) {
|
|
18914
18902
|
const resources = /* @__PURE__ */ new Map();
|
|
18915
18903
|
if (!existsSync6(directory)) return resources;
|
|
18916
18904
|
const stat = lstatSync(directory);
|
|
@@ -18949,7 +18937,10 @@ function listNamedResources(registry, root, directory, logicalDirectory, kind, r
|
|
|
18949
18937
|
else throw error;
|
|
18950
18938
|
}
|
|
18951
18939
|
}
|
|
18952
|
-
if (issues.length > 0)
|
|
18940
|
+
if (issues.length > 0) {
|
|
18941
|
+
if (collected) collected.push(...issues);
|
|
18942
|
+
else throw new KxmConfigError(issues);
|
|
18943
|
+
}
|
|
18953
18944
|
return resources;
|
|
18954
18945
|
}
|
|
18955
18946
|
function valuesOf(object2, field) {
|
|
@@ -19209,6 +19200,9 @@ function validateWorkflow(workflow, agents, models, repositories, gates, issues)
|
|
|
19209
19200
|
for (const repositoryId of Object.keys(objectValue(step.repositories) ?? {})) {
|
|
19210
19201
|
if (!repositories.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", file, `${stepId} references unknown repository ${repositoryId}`));
|
|
19211
19202
|
}
|
|
19203
|
+
if ((kind === "agent" || kind === "moa") && step.model !== void 0) {
|
|
19204
|
+
issues.push(issue3("semantic", "producer_route_unsupported", file, `${stepId} model is not honored; remove model from the step`));
|
|
19205
|
+
}
|
|
19212
19206
|
selectorCandidates(step.model, models, file, `${stepId}.model`, issues);
|
|
19213
19207
|
const assignment = objectValue(step.assignments);
|
|
19214
19208
|
const declaredAgents = assignment ? names(assignment.allowedAgents) : [];
|
|
@@ -19391,7 +19385,7 @@ function validateModelReferences(models, issues) {
|
|
|
19391
19385
|
};
|
|
19392
19386
|
for (const id of models.keys()) walk(id, []);
|
|
19393
19387
|
}
|
|
19394
|
-
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot,
|
|
19388
|
+
function validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, projectRoot, roles = /* @__PURE__ */ new Map()) {
|
|
19395
19389
|
const issues = [];
|
|
19396
19390
|
const entries = valuesOf(project.value, "repositories").map((candidate) => objectValue(candidate)).filter((candidate) => Boolean(candidate));
|
|
19397
19391
|
const repositoryIds = /* @__PURE__ */ new Set();
|
|
@@ -19432,84 +19426,84 @@ function validateBundle(project, repositories, agents, models, workflows, enviro
|
|
|
19432
19426
|
const defaultHarness = stringValue(project.value.defaultHarness) ?? DEFAULT_HARNESS;
|
|
19433
19427
|
if (!harnesses.has(defaultHarness)) issues.push(issue3("reference", "harness_unknown", project.logicalPath, `defaultHarness ${defaultHarness} is not registered`));
|
|
19434
19428
|
for (const agent of agents.values()) {
|
|
19429
|
+
if (agent.value.model !== void 0 || agent.value.harness !== void 0) {
|
|
19430
|
+
issues.push(issue3("semantic", "retired_agent_routing_fields", agent.logicalPath, "routing resolves from role; remove model and harness"));
|
|
19431
|
+
}
|
|
19435
19432
|
validateToolPolicy(agent, objectValue(agent.value.tools), "tools", issues);
|
|
19436
19433
|
const executor = stringValue(agent.value.executor);
|
|
19437
19434
|
if (executor && !executors.has(executor)) issues.push(issue3("reference", "executor_unknown", agent.logicalPath, `executor ${executor} is not registered`));
|
|
19438
|
-
const harness = stringValue(agent.value.harness) ?? defaultHarness;
|
|
19439
|
-
if (!harnesses.has(harness)) issues.push(issue3("reference", "harness_unknown", agent.logicalPath, `harness ${harness} is not registered`));
|
|
19440
19435
|
const preset = stringValue(objectValue(agent.value.tools)?.preset);
|
|
19441
19436
|
if (preset && !presets.has(preset)) issues.push(issue3("reference", "tool_preset_unknown", agent.logicalPath, `tool preset ${preset} is not registered`));
|
|
19442
19437
|
for (const repositoryId of Object.keys(objectValue(agent.value.repositories) ?? {})) {
|
|
19443
19438
|
if (!repositoryIds.has(repositoryId)) issues.push(issue3("reference", "repository_unknown", agent.logicalPath, `references unknown repository ${repositoryId}`));
|
|
19444
19439
|
}
|
|
19445
|
-
const candidates = selectorCandidates(agent.value.model, models, agent.logicalPath, "model", issues);
|
|
19446
|
-
const declaredHarness = stringValue(agent.value.harness);
|
|
19447
|
-
if (declaredHarness && harnesses.has(declaredHarness)) {
|
|
19448
|
-
for (const candidate of candidates) {
|
|
19449
|
-
const candidateProvider = stringValue(candidate.value.provider);
|
|
19450
|
-
const candidateModel = stringValue(candidate.value.model);
|
|
19451
|
-
const validation = validateHarnessModelPair(declaredHarness, { provider: candidateProvider, model: candidateModel });
|
|
19452
|
-
if (!validation.valid) {
|
|
19453
|
-
issues.push(issue3("semantic", validation.issue ?? "harness_unhosted_model", agent.logicalPath, validation.message ?? `harness ${declaredHarness} cannot host model ${candidateModel ?? candidate.logicalPath}`));
|
|
19454
|
-
}
|
|
19455
|
-
}
|
|
19456
|
-
}
|
|
19457
19440
|
}
|
|
19458
19441
|
validateModelReferences(models, issues);
|
|
19459
19442
|
const defaultWorkflow = stringValue(project.value.defaultWorkflow) ?? "default";
|
|
19460
19443
|
if (!workflows.has(defaultWorkflow)) issues.push(issue3("reference", "default_workflow_unknown", project.logicalPath, `default workflow ${defaultWorkflow} does not exist`));
|
|
19461
19444
|
for (const workflow of workflows.values()) validateWorkflow(workflow, agents, models, repositoryIds, gates, issues);
|
|
19445
|
+
if (projectRoot && existsSync6(join6(projectRoot, ".kxm", "roster.yaml"))) {
|
|
19446
|
+
issues.push(issue3("semantic", "retired_roster_file", ".kxm/roster.yaml", "create the role and model files and delete roster.yaml"));
|
|
19447
|
+
}
|
|
19448
|
+
if (projectRoot && existsSync6(join6(projectRoot, ".kxm", "roles"))) {
|
|
19449
|
+
issues.push(...developerRolePolicyIssues(projectRoot));
|
|
19450
|
+
}
|
|
19462
19451
|
if (projectRoot) {
|
|
19463
|
-
|
|
19464
|
-
|
|
19465
|
-
|
|
19452
|
+
issues.push(...rosterModelIssues(projectRoot, roles));
|
|
19453
|
+
issues.push(...originHashIssues(projectRoot, models));
|
|
19454
|
+
}
|
|
19455
|
+
return sortIssues3(issues);
|
|
19456
|
+
}
|
|
19457
|
+
function rosterModelIssues(projectRoot, roles) {
|
|
19458
|
+
const issues = [];
|
|
19459
|
+
for (const role of roles.values()) {
|
|
19460
|
+
for (const entry of valuesOf(role.value, "roster")) {
|
|
19461
|
+
const route = stringValue(objectValue(entry)?.route);
|
|
19462
|
+
if (!route) continue;
|
|
19463
|
+
const logical = `.kxm/models/${route}.yaml`;
|
|
19464
|
+
const file = join6(projectRoot, ".kxm", "models", `${route}.yaml`);
|
|
19465
|
+
let harness;
|
|
19466
19466
|
try {
|
|
19467
|
-
|
|
19468
|
-
|
|
19469
|
-
|
|
19470
|
-
|
|
19471
|
-
|
|
19472
|
-
|
|
19473
|
-
const named = direct ? [direct] : [];
|
|
19474
|
-
if (route) {
|
|
19475
|
-
const modelFile = join6(projectRoot, ".kxm", "models", `${route}.yaml`);
|
|
19476
|
-
if (existsSync6(modelFile)) {
|
|
19477
|
-
try {
|
|
19478
|
-
const modelDoc = objectValue(parseRestrictedYaml2(readFileSync5(modelFile, "utf8"), modelFile));
|
|
19479
|
-
const model = modelDoc ? stringValue(modelDoc.model) : void 0;
|
|
19480
|
-
const vendor = modelDoc ? stringValue(modelDoc.vendor) : void 0;
|
|
19481
|
-
const harness = modelDoc ? stringValue(modelDoc.harness) : void 0;
|
|
19482
|
-
if (model) named.push(model);
|
|
19483
|
-
if (vendor && model) named.push(`${vendor}/${model}`);
|
|
19484
|
-
if (harness && model) named.push(`${harness}/${model}`);
|
|
19485
|
-
} catch {
|
|
19486
|
-
}
|
|
19487
|
-
}
|
|
19488
|
-
}
|
|
19489
|
-
return named;
|
|
19490
|
-
});
|
|
19491
|
-
const agentModelObj = objectValue(implementerAgent.value.model);
|
|
19492
|
-
const agentModelStr = stringValue(implementerAgent.value.model);
|
|
19493
|
-
const agentProvider = agentModelObj ? stringValue(agentModelObj.provider) : void 0;
|
|
19494
|
-
const agentModel = agentModelObj ? stringValue(agentModelObj.model) : agentModelStr;
|
|
19495
|
-
const canonicalAgentModel = agentProvider && agentModel ? `${agentProvider}/${agentModel}` : agentModel;
|
|
19496
|
-
if (enabledRosterModels.length > 0 && canonicalAgentModel) {
|
|
19497
|
-
const matches = enabledRosterModels.some((rm) => rm === canonicalAgentModel || rm === agentModel || rm.endsWith(`/${agentModel}`));
|
|
19498
|
-
if (!matches) {
|
|
19499
|
-
issues.push(issue3("semantic", "role_roster_conflicts_with_agent", ".kxm/roles/writer.yaml", `role roster in .kxm/roles/writer.yaml does not include agent model ${canonicalAgentModel} from ${implementerAgent.logicalPath}`));
|
|
19500
|
-
}
|
|
19501
|
-
}
|
|
19502
|
-
} catch (error) {
|
|
19503
|
-
if (error instanceof KxmConfigError) {
|
|
19504
|
-
issues.push(...error.issues);
|
|
19505
|
-
}
|
|
19467
|
+
if (existsSync6(file)) harness = stringValue(parseRestrictedYaml2(readFileSync5(file, "utf8"), logical).harness);
|
|
19468
|
+
} catch {
|
|
19469
|
+
harness = void 0;
|
|
19470
|
+
}
|
|
19471
|
+
if (!harness) {
|
|
19472
|
+
issues.push(issue3("semantic", "roster_model_unreadable", role.logicalPath, `roster route ${route} does not resolve to a readable ${logical} carrying harness`));
|
|
19506
19473
|
}
|
|
19507
19474
|
}
|
|
19508
19475
|
}
|
|
19509
|
-
|
|
19510
|
-
|
|
19476
|
+
return issues;
|
|
19477
|
+
}
|
|
19478
|
+
function originHashIssues(projectRoot, models) {
|
|
19479
|
+
const issues = [];
|
|
19480
|
+
for (const model of models.values()) {
|
|
19481
|
+
const origin = objectValue(model.value.origin);
|
|
19482
|
+
const source = origin ? stringValue(origin.source) : void 0;
|
|
19483
|
+
const digest = origin ? stringValue(origin.sha256) : void 0;
|
|
19484
|
+
if (!source || !digest) continue;
|
|
19485
|
+
const file = resolve3(projectRoot, source);
|
|
19486
|
+
const escaped = relative(projectRoot, file);
|
|
19487
|
+
if (isAbsolute2(source) || escaped.startsWith("..") || isAbsolute2(escaped)) {
|
|
19488
|
+
issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
|
|
19489
|
+
continue;
|
|
19490
|
+
}
|
|
19491
|
+
let actual;
|
|
19492
|
+
try {
|
|
19493
|
+
actual = createHash7("sha256").update(readFileSync5(file, "utf8"), "utf8").digest("hex");
|
|
19494
|
+
} catch (error) {
|
|
19495
|
+
const code = error.code;
|
|
19496
|
+
if (code === "ENOENT" || code === "EACCES" || code === "EPERM" || code === "EISDIR") {
|
|
19497
|
+
issues.push(issue3("semantic", "origin_evidence_missing", model.logicalPath, `missing evidence bytes for ${source}`));
|
|
19498
|
+
continue;
|
|
19499
|
+
}
|
|
19500
|
+
throw error;
|
|
19501
|
+
}
|
|
19502
|
+
if (actual !== digest) {
|
|
19503
|
+
issues.push(issue3("semantic", "origin_hash_mismatch", model.logicalPath, "origin evidence hash does not match supplied bytes"));
|
|
19504
|
+
}
|
|
19511
19505
|
}
|
|
19512
|
-
return
|
|
19506
|
+
return issues;
|
|
19513
19507
|
}
|
|
19514
19508
|
function developerCeilings() {
|
|
19515
19509
|
const script = join6(findKxmRepoRoot(import.meta.url), "scripts", "harness-run.mjs");
|
|
@@ -19538,6 +19532,7 @@ function developerCeilings() {
|
|
|
19538
19532
|
}
|
|
19539
19533
|
}
|
|
19540
19534
|
function developerRolePolicyIssues(projectRoot) {
|
|
19535
|
+
if (resolve3(projectRoot) !== findKxmRepoRoot(import.meta.url)) return [];
|
|
19541
19536
|
const rolesDir = join6(projectRoot, ".kxm", "roles");
|
|
19542
19537
|
const modelsDir = join6(projectRoot, ".kxm", "models");
|
|
19543
19538
|
const roles = {};
|
|
@@ -19558,6 +19553,7 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
19558
19553
|
parseIssues.push(...error.issues);
|
|
19559
19554
|
continue;
|
|
19560
19555
|
}
|
|
19556
|
+
throw error;
|
|
19561
19557
|
}
|
|
19562
19558
|
}
|
|
19563
19559
|
};
|
|
@@ -19572,7 +19568,7 @@ function developerRolePolicyIssues(projectRoot) {
|
|
|
19572
19568
|
}
|
|
19573
19569
|
const ceilings = developerCeilings();
|
|
19574
19570
|
if (!ceilings.ok) {
|
|
19575
|
-
return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/
|
|
19571
|
+
return [...parseIssues, issue3("semantic", "developer_ceilings_unavailable", ".kxm/models", `developer ceilings could not be loaded: ${ceilings.detail}`)];
|
|
19576
19572
|
}
|
|
19577
19573
|
const result = validatePolicyDraft({ models, roles, evidence }, {
|
|
19578
19574
|
ceilings: ceilings.ROUTES,
|
|
@@ -19680,7 +19676,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
19680
19676
|
}
|
|
19681
19677
|
if (earlyIssues.length > 0) throw new KxmConfigError(earlyIssues);
|
|
19682
19678
|
const agents = listNamedResources(registry, root, join6(root, ".kxm", "agents"), ".kxm/agents", "agent");
|
|
19683
|
-
const
|
|
19679
|
+
const modelIssues = [];
|
|
19680
|
+
const models = listNamedResources(registry, root, join6(root, ".kxm", "models"), ".kxm/models", "model", void 0, modelIssues);
|
|
19684
19681
|
const roles = listNamedResources(registry, root, join6(root, ".kxm", "roles"), ".kxm/roles", "role", writerRole === void 0 ? void 0 : "writer");
|
|
19685
19682
|
if (writerRole !== void 0) {
|
|
19686
19683
|
const logicalPath = ".kxm/roles/writer.yaml";
|
|
@@ -19807,8 +19804,8 @@ function loadProjectBundle(projectRoot, options, candidate) {
|
|
|
19807
19804
|
}
|
|
19808
19805
|
}
|
|
19809
19806
|
}
|
|
19810
|
-
if (loadIssues.length > 0) throw new KxmConfigError(loadIssues);
|
|
19811
|
-
const issues = validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root,
|
|
19807
|
+
if (loadIssues.length > 0) throw new KxmConfigError([...loadIssues, ...modelIssues]);
|
|
19808
|
+
const issues = [...modelIssues, ...validateBundle(project, repositories, agents, models, workflows, environments, options, gateRegistry, root, roles)];
|
|
19812
19809
|
if (issues.length > 0) throw new KxmConfigError(issues);
|
|
19813
19810
|
const resources = [project, ...repositories.values(), ...agents.values(), ...models.values(), ...roles.values(), ...workflows.values(), ...environments, ...gateRegistry ? [gateRegistry] : []].sort((left, right) => compareCodeUnits4(left.logicalPath, right.logicalPath));
|
|
19814
19811
|
return {
|
|
@@ -24724,7 +24721,7 @@ function loadRoutePolicy(root) {
|
|
|
24724
24721
|
if (!existsSync14(path4)) return empty();
|
|
24725
24722
|
const value = (0, import_yaml6.parse)(readFileSync12(path4, "utf8"));
|
|
24726
24723
|
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
24727
|
-
return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x2) => typeof x2 === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x2) => typeof x2 === "string") : []
|
|
24724
|
+
return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString(), admitted: value.admitted.filter((x2) => typeof x2 === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x2) => typeof x2 === "string") : [] };
|
|
24728
24725
|
}
|
|
24729
24726
|
function updateRouteState(root, model, state) {
|
|
24730
24727
|
return setRouteState(root, model, state);
|
|
@@ -24744,21 +24741,7 @@ function setRouteState(root, model, state, role, removeRole2 = false) {
|
|
|
24744
24741
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) roleFile = parsed;
|
|
24745
24742
|
}
|
|
24746
24743
|
const roster = Array.isArray(roleFile.roster) ? roleFile.roster.filter((entry) => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
24747
|
-
const
|
|
24748
|
-
let routeId2;
|
|
24749
|
-
if (existsSync14(modelsDir)) {
|
|
24750
|
-
for (const name of readdirSync4(modelsDir)) {
|
|
24751
|
-
if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
|
|
24752
|
-
const parsedModel = (0, import_yaml6.parse)(readFileSync12(join14(modelsDir, name), "utf8"));
|
|
24753
|
-
if (parsedModel?.schema !== "kxm.model.v2") continue;
|
|
24754
|
-
const vendor = typeof parsedModel.vendor === "string" ? parsedModel.vendor : "";
|
|
24755
|
-
const named = typeof parsedModel.model === "string" ? parsedModel.model : "";
|
|
24756
|
-
if (named === model || vendor && `${vendor}/${named}` === model) {
|
|
24757
|
-
routeId2 = name.slice(0, -5);
|
|
24758
|
-
break;
|
|
24759
|
-
}
|
|
24760
|
-
}
|
|
24761
|
-
}
|
|
24744
|
+
const routeId2 = routeIdForInventoryModel(root, model);
|
|
24762
24745
|
if (!routeId2) throw new Error(`unknown route: no v2 model file matches '${model}'`);
|
|
24763
24746
|
const existing = roster.findIndex((entry) => entry.route === routeId2 || entry.model === model);
|
|
24764
24747
|
if (removeRole2) {
|
|
@@ -24779,6 +24762,24 @@ function setRouteState(root, model, state, role, removeRole2 = false) {
|
|
|
24779
24762
|
writeFileSync10(join14(root, ".kxm", "routes.yaml"), (0, import_yaml6.stringify)(policy), "utf8");
|
|
24780
24763
|
return policy;
|
|
24781
24764
|
}
|
|
24765
|
+
function routeIdForInventoryModel(root, model) {
|
|
24766
|
+
const modelsDir = join14(root, ".kxm", "models");
|
|
24767
|
+
if (!existsSync14(modelsDir)) return void 0;
|
|
24768
|
+
for (const name of readdirSync4(modelsDir)) {
|
|
24769
|
+
if (!name.endsWith(".yaml") || name === "inventory.yaml") continue;
|
|
24770
|
+
const parsedModel = (0, import_yaml6.parse)(readFileSync12(join14(modelsDir, name), "utf8"));
|
|
24771
|
+
if (parsedModel?.schema !== "kxm.model.v2") continue;
|
|
24772
|
+
const vendor = typeof parsedModel.vendor === "string" ? parsedModel.vendor : "";
|
|
24773
|
+
const named = typeof parsedModel.model === "string" ? parsedModel.model : "";
|
|
24774
|
+
if (named === model || vendor && `${vendor}/${named}` === model) return name.slice(0, -5);
|
|
24775
|
+
}
|
|
24776
|
+
return void 0;
|
|
24777
|
+
}
|
|
24778
|
+
function rolesForInventoryModel(root, inventoryId) {
|
|
24779
|
+
const routeId2 = routeIdForInventoryModel(root, inventoryId);
|
|
24780
|
+
if (!routeId2) return [];
|
|
24781
|
+
return Object.entries(listRoleBindings(root)).filter(([, ids]) => ids.includes(routeId2)).map(([role]) => role);
|
|
24782
|
+
}
|
|
24782
24783
|
function listRoleBindings(root) {
|
|
24783
24784
|
const dir = join14(root, ".kxm", "roles");
|
|
24784
24785
|
const result = {};
|
|
@@ -24790,10 +24791,6 @@ function listRoleBindings(root) {
|
|
|
24790
24791
|
}
|
|
24791
24792
|
return result;
|
|
24792
24793
|
}
|
|
24793
|
-
function isRouteAdmitted(root, modelId2) {
|
|
24794
|
-
const policy = loadRoutePolicy(root);
|
|
24795
|
-
return policy.admitted.includes(modelId2) && !policy.disabled.includes(modelId2);
|
|
24796
|
-
}
|
|
24797
24794
|
function listInventoryModels(root) {
|
|
24798
24795
|
const path4 = join14(root, ".kxm", "models", "inventory.yaml");
|
|
24799
24796
|
if (!existsSync14(path4)) return [];
|
|
@@ -24807,7 +24804,7 @@ var init_routes = __esm({
|
|
|
24807
24804
|
import_yaml6 = __toESM(require_dist(), 1);
|
|
24808
24805
|
init_role();
|
|
24809
24806
|
RETIRED_POLICY = ".kxm/producers.yaml";
|
|
24810
|
-
empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: []
|
|
24807
|
+
empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [] });
|
|
24811
24808
|
}
|
|
24812
24809
|
});
|
|
24813
24810
|
|
|
@@ -26235,103 +26232,132 @@ var init_routing = __esm({
|
|
|
26235
26232
|
// plugins/kxm/src/engine.ts
|
|
26236
26233
|
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
|
|
26237
26234
|
import { join as join17 } from "node:path";
|
|
26238
|
-
function
|
|
26239
|
-
|
|
26240
|
-
|
|
26241
|
-
|
|
26242
|
-
|
|
26243
|
-
|
|
26244
|
-
|
|
26245
|
-
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
const model = typeof parsed.model === "string" ? parsed.model : "";
|
|
26250
|
-
const vendor = typeof parsed.vendor === "string" ? parsed.vendor : "";
|
|
26251
|
-
const harness = typeof parsed.harness === "string" ? parsed.harness : "";
|
|
26252
|
-
if (model) selectors.add(model);
|
|
26253
|
-
if (vendor && model) selectors.add(`${vendor}/${model}`);
|
|
26254
|
-
if (harness && model) selectors.add(`${harness}/${model}`);
|
|
26235
|
+
function readYamlFile(file) {
|
|
26236
|
+
return readModelDocument(file);
|
|
26237
|
+
}
|
|
26238
|
+
function readModelDocument(file) {
|
|
26239
|
+
if (!existsSync17(file)) return void 0;
|
|
26240
|
+
try {
|
|
26241
|
+
const parsed = (0, import_yaml9.parse)(readFileSync15(file, "utf8"));
|
|
26242
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
26243
|
+
return parsed;
|
|
26244
|
+
} catch {
|
|
26245
|
+
return void 0;
|
|
26255
26246
|
}
|
|
26256
|
-
return [...selectors];
|
|
26257
26247
|
}
|
|
26258
26248
|
function resolveProducerRoute(projectRoot, step, agentId) {
|
|
26259
|
-
|
|
26260
|
-
const agentFile = join17(projectRoot, ".kxm", "agents", `${agentId}.yaml`);
|
|
26261
|
-
if (existsSync17(agentFile)) {
|
|
26262
|
-
try {
|
|
26263
|
-
const parsed = (0, import_yaml9.parse)(readFileSync15(agentFile, "utf8"));
|
|
26264
|
-
if (typeof parsed?.model === "string") {
|
|
26265
|
-
agentModel = parsed.model;
|
|
26266
|
-
} else if (parsed?.model && typeof parsed.model === "object" && !Array.isArray(parsed.model)) {
|
|
26267
|
-
const declared = parsed.model;
|
|
26268
|
-
const providerOk = typeof declared.provider === "string" && declared.provider.length > 0 && !declared.provider.includes("/");
|
|
26269
|
-
const modelOk = typeof declared.model === "string" && declared.model.length > 0 && !declared.model.startsWith("/") && !declared.model.endsWith("/");
|
|
26270
|
-
if (providerOk && modelOk) {
|
|
26271
|
-
agentModel = `${declared.provider}/${declared.model}`;
|
|
26272
|
-
} else {
|
|
26273
|
-
return {
|
|
26274
|
-
error: {
|
|
26275
|
-
reason: "step_unsupported",
|
|
26276
|
-
field: "model",
|
|
26277
|
-
detail: "producer_route_unsupported: invalid model declaration"
|
|
26278
|
-
}
|
|
26279
|
-
};
|
|
26280
|
-
}
|
|
26281
|
-
}
|
|
26282
|
-
} catch {
|
|
26283
|
-
}
|
|
26284
|
-
}
|
|
26285
|
-
let selector = typeof step.model === "string" ? step.model : agentModel;
|
|
26286
|
-
if (!selector) {
|
|
26287
|
-
if (agentId === "implementer") {
|
|
26288
|
-
selector = "xai/grok-4.6";
|
|
26289
|
-
}
|
|
26290
|
-
}
|
|
26291
|
-
if (!selector) {
|
|
26249
|
+
if ((step.kind === "agent" || step.kind === "moa") && step.model !== void 0) {
|
|
26292
26250
|
return {
|
|
26293
26251
|
error: {
|
|
26294
26252
|
reason: "step_unsupported",
|
|
26295
26253
|
field: "model",
|
|
26296
|
-
detail: "producer_route_unsupported: agent
|
|
26254
|
+
detail: "producer_route_unsupported: agent step model is not honored"
|
|
26297
26255
|
}
|
|
26298
26256
|
};
|
|
26299
26257
|
}
|
|
26300
|
-
const
|
|
26301
|
-
|
|
26258
|
+
const agent = readYamlFile(join17(projectRoot, ".kxm", "agents", `${agentId}.yaml`));
|
|
26259
|
+
const role = typeof agent?.role === "string" ? agent.role : "";
|
|
26260
|
+
if (!role) {
|
|
26302
26261
|
return {
|
|
26303
26262
|
error: {
|
|
26304
26263
|
reason: "step_unsupported",
|
|
26305
26264
|
field: "model",
|
|
26306
|
-
detail:
|
|
26265
|
+
detail: "producer_route_unsupported: agent has no role"
|
|
26307
26266
|
}
|
|
26308
26267
|
};
|
|
26309
26268
|
}
|
|
26310
|
-
|
|
26269
|
+
const roleDoc = readYamlFile(join17(projectRoot, ".kxm", "roles", `${role}.yaml`));
|
|
26270
|
+
const roster = Array.isArray(roleDoc?.roster) ? roleDoc.roster : void 0;
|
|
26271
|
+
if (!roster) {
|
|
26311
26272
|
return {
|
|
26312
26273
|
error: {
|
|
26313
26274
|
reason: "step_unsupported",
|
|
26314
26275
|
field: "model",
|
|
26315
|
-
detail: `producer_route_unsupported:
|
|
26276
|
+
detail: `producer_route_unsupported: role '${role}' has no roster`
|
|
26316
26277
|
}
|
|
26317
26278
|
};
|
|
26318
26279
|
}
|
|
26319
|
-
const
|
|
26320
|
-
const
|
|
26321
|
-
|
|
26322
|
-
|
|
26323
|
-
|
|
26280
|
+
const policy = loadRoutePolicy(projectRoot);
|
|
26281
|
+
for (const entry of roster) {
|
|
26282
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
26283
|
+
const record = entry;
|
|
26284
|
+
if (typeof record.route !== "string" || record.route.length === 0) continue;
|
|
26285
|
+
const modelPath = join17(projectRoot, ".kxm", "models", `${record.route}.yaml`);
|
|
26286
|
+
const doc = readModelDocument(modelPath);
|
|
26287
|
+
if (!doc) {
|
|
26288
|
+
return {
|
|
26289
|
+
error: {
|
|
26290
|
+
reason: "step_unsupported",
|
|
26291
|
+
field: "model",
|
|
26292
|
+
detail: `producer_route_unsupported: model file '.kxm/models/${record.route}.yaml' is missing or unreadable`
|
|
26293
|
+
}
|
|
26294
|
+
};
|
|
26295
|
+
}
|
|
26296
|
+
if (doc.status !== "admitted") continue;
|
|
26297
|
+
const modelName = typeof doc.model === "string" ? doc.model : "";
|
|
26298
|
+
const vendor = typeof doc.vendor === "string" ? doc.vendor : "";
|
|
26299
|
+
const harness = typeof doc.harness === "string" ? doc.harness : "";
|
|
26300
|
+
if (!modelName || !vendor || !harness) continue;
|
|
26301
|
+
const candidates = [modelName, `${vendor}/${modelName}`, `${harness}/${modelName}`];
|
|
26302
|
+
const admittedSelector = candidates.find((candidate) => policy.admitted.includes(candidate));
|
|
26303
|
+
const disabledSelector = candidates.find((candidate) => policy.disabled.includes(candidate));
|
|
26304
|
+
if (!admittedSelector && disabledSelector) {
|
|
26305
|
+
return {
|
|
26306
|
+
error: {
|
|
26307
|
+
reason: "step_unsupported",
|
|
26308
|
+
field: "model",
|
|
26309
|
+
detail: `producer_route_unsupported: first admitted route '${record.route}' for role '${role}' is disabled`
|
|
26310
|
+
}
|
|
26311
|
+
};
|
|
26312
|
+
}
|
|
26313
|
+
const selector = admittedSelector ?? (modelName.includes("/") ? modelName : `${vendor}/${modelName}`);
|
|
26314
|
+
if (policy.disabled.includes(selector)) {
|
|
26315
|
+
return {
|
|
26316
|
+
error: {
|
|
26317
|
+
reason: "step_unsupported",
|
|
26318
|
+
field: "model",
|
|
26319
|
+
detail: `producer_route_unsupported: first admitted route '${record.route}' for role '${role}' is disabled`
|
|
26320
|
+
}
|
|
26321
|
+
};
|
|
26322
|
+
}
|
|
26323
|
+
if (!policy.admitted.includes(selector)) {
|
|
26324
|
+
return {
|
|
26325
|
+
error: {
|
|
26326
|
+
reason: "step_unsupported",
|
|
26327
|
+
field: "model",
|
|
26328
|
+
detail: `producer_route_unsupported: model '${selector}' is not admitted`
|
|
26329
|
+
}
|
|
26330
|
+
};
|
|
26331
|
+
}
|
|
26332
|
+
const slash = selector.indexOf("/");
|
|
26333
|
+
if (slash <= 0 || slash === selector.length - 1) {
|
|
26334
|
+
return {
|
|
26335
|
+
error: {
|
|
26336
|
+
reason: "step_unsupported",
|
|
26337
|
+
field: "model",
|
|
26338
|
+
detail: `producer_route_unsupported: invalid selector '${selector}'`
|
|
26339
|
+
}
|
|
26340
|
+
};
|
|
26341
|
+
}
|
|
26342
|
+
const permissions = Array.isArray(doc.permissions) ? doc.permissions.filter((item) => typeof item === "string") : [];
|
|
26324
26343
|
return {
|
|
26325
|
-
|
|
26326
|
-
|
|
26327
|
-
|
|
26328
|
-
|
|
26329
|
-
|
|
26344
|
+
provider: selector.slice(0, slash),
|
|
26345
|
+
model: selector.slice(slash + 1),
|
|
26346
|
+
selector,
|
|
26347
|
+
harness,
|
|
26348
|
+
routeId: record.route,
|
|
26349
|
+
role,
|
|
26350
|
+
permissions,
|
|
26351
|
+
...typeof record.effort === "string" ? { effort: record.effort } : {}
|
|
26330
26352
|
};
|
|
26331
26353
|
}
|
|
26332
|
-
|
|
26333
|
-
|
|
26334
|
-
|
|
26354
|
+
return {
|
|
26355
|
+
error: {
|
|
26356
|
+
reason: "step_unsupported",
|
|
26357
|
+
field: "model",
|
|
26358
|
+
detail: `producer_route_unsupported: role '${role}' has no admitted route`
|
|
26359
|
+
}
|
|
26360
|
+
};
|
|
26335
26361
|
}
|
|
26336
26362
|
function unsupportedAgentStepTimeout(step, limitMs) {
|
|
26337
26363
|
if (step.kind !== "agent" && step.kind !== "moa") return void 0;
|
|
@@ -26369,26 +26395,26 @@ function kxmLiveRunPrerequisites(bundle, workflowId, projectRoot) {
|
|
|
26369
26395
|
if (step.kind !== "agent" && step.kind !== "moa") continue;
|
|
26370
26396
|
const agentIds = step.assignments.allowedAgents.length > 0 ? step.assignments.allowedAgents : [step.agent];
|
|
26371
26397
|
for (const agentId of agentIds) {
|
|
26372
|
-
const
|
|
26373
|
-
|
|
26398
|
+
const route = resolveProducerRoute(projectRoot, step, agentId);
|
|
26399
|
+
if ("error" in route) {
|
|
26400
|
+
const detail = route.error.detail === "producer_route_unsupported: agent step model is not honored" ? `${agentId}: agent step model is not honored; remove model from the step` : `${agentId}: ${route.error.detail}; set role in .kxm/agents/${agentId}.yaml and admit the route model with kxm routes admit --model <provider/model>`;
|
|
26401
|
+
prerequisites.push({ ...route.error, stepId, detail });
|
|
26402
|
+
continue;
|
|
26403
|
+
}
|
|
26404
|
+
const harness = route.harness;
|
|
26374
26405
|
const permission = Object.values(step.repositories).includes("write") ? "edit" : "read-only";
|
|
26375
26406
|
if (!BUILTIN_HARNESSES.some((entry) => entry.id === harness && entry.oneShot) || !oneShotPermissionArgs(harness, permission)) {
|
|
26376
26407
|
prerequisites.push({ reason: "step_unsupported", stepId, field: "harness", detail: `${agentId}: ${harness} has no audited ${permission} one-shot profile; use a supported workflow or execute this work directly in ${harness}, without substituting another harness` });
|
|
26377
26408
|
continue;
|
|
26378
26409
|
}
|
|
26379
|
-
const
|
|
26380
|
-
if ("error" in route) {
|
|
26381
|
-
prerequisites.push({ ...route.error, stepId, detail: `${agentId}: ${route.error.detail}; configure its model in .kxm/agents/${agentId}.yaml and admit the installed model with kxm routes admit --model <provider/model>` });
|
|
26382
|
-
continue;
|
|
26383
|
-
}
|
|
26384
|
-
const writeRefusal = unsupportedLiveWrite(projectRoot, step, agentId, route.selector, envelope2.projectLimits.maxConcurrentRuns);
|
|
26410
|
+
const writeRefusal = unsupportedLiveWrite(projectRoot, step, route, envelope2.projectLimits.maxConcurrentRuns);
|
|
26385
26411
|
if (writeRefusal) {
|
|
26386
26412
|
prerequisites.push({ ...writeRefusal, stepId });
|
|
26387
26413
|
continue;
|
|
26388
26414
|
}
|
|
26389
26415
|
const compatible = validateHarnessModelPair(harness, route);
|
|
26390
26416
|
if (!compatible.valid) {
|
|
26391
|
-
prerequisites.push({ reason: "step_unsupported", stepId, field: "model", detail: `${agentId}: ${compatible.message ?? `${harness} cannot run ${route.selector}`}; configure a model supported by ${harness}
|
|
26417
|
+
prerequisites.push({ reason: "step_unsupported", stepId, field: "model", detail: `${agentId}: ${compatible.message ?? `${harness} cannot run ${route.selector}`}; configure a model supported by ${harness} on the role roster` });
|
|
26392
26418
|
}
|
|
26393
26419
|
}
|
|
26394
26420
|
}
|
|
@@ -26461,25 +26487,7 @@ function unsupportedStep(plan, step, producerId) {
|
|
|
26461
26487
|
}
|
|
26462
26488
|
return void 0;
|
|
26463
26489
|
}
|
|
26464
|
-
function
|
|
26465
|
-
if (!existsSync17(path4)) return void 0;
|
|
26466
|
-
try {
|
|
26467
|
-
const parsed = (0, import_yaml9.parse)(readFileSync15(path4, "utf8"));
|
|
26468
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
|
|
26469
|
-
} catch {
|
|
26470
|
-
return void 0;
|
|
26471
|
-
}
|
|
26472
|
-
return void 0;
|
|
26473
|
-
}
|
|
26474
|
-
function agentHarness(projectRoot, agentId) {
|
|
26475
|
-
const harness = readYamlRecord(join17(projectRoot, ".kxm", "agents", `${agentId}.yaml`))?.harness;
|
|
26476
|
-
return typeof harness === "string" && harness.length > 0 ? harness : void 0;
|
|
26477
|
-
}
|
|
26478
|
-
function projectDefaultHarness(projectRoot) {
|
|
26479
|
-
const harness = readYamlRecord(join17(projectRoot, ".kxm", "project.yaml"))?.defaultHarness;
|
|
26480
|
-
return typeof harness === "string" && harness.length > 0 ? harness : "pi";
|
|
26481
|
-
}
|
|
26482
|
-
function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurrentRuns) {
|
|
26490
|
+
function unsupportedLiveWrite(projectRoot, step, route, maxConcurrentRuns) {
|
|
26483
26491
|
if (!Object.values(step.repositories).some((access) => access === "write")) return void 0;
|
|
26484
26492
|
if (step.assignments.maximum !== 1) {
|
|
26485
26493
|
return {
|
|
@@ -26495,42 +26503,23 @@ function unsupportedLiveWrite(projectRoot, step, agentId, selector, maxConcurren
|
|
|
26495
26503
|
detail: "live write steps require limits.maxConcurrentRuns of 1; concurrent runs share one checkout"
|
|
26496
26504
|
};
|
|
26497
26505
|
}
|
|
26498
|
-
|
|
26499
|
-
if (!oneShotWriterArgs(harness)) {
|
|
26506
|
+
if (!oneShotWriterArgs(route.harness)) {
|
|
26500
26507
|
return {
|
|
26501
26508
|
reason: "step_unsupported",
|
|
26502
26509
|
field: "repositories",
|
|
26503
|
-
detail: `live write steps require an audited writer profile; ${harness} has none`
|
|
26504
|
-
};
|
|
26505
|
-
}
|
|
26506
|
-
const
|
|
26507
|
-
if (!
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
const routes = roster.routes;
|
|
26513
|
-
const lineup = roster.lineup;
|
|
26514
|
-
const writerIds = lineup && typeof lineup === "object" && !Array.isArray(lineup) ? lineup.writer : void 0;
|
|
26515
|
-
if (!routes || typeof routes !== "object" || Array.isArray(routes) || !Array.isArray(writerIds)) {
|
|
26516
|
-
return { reason: "step_unsupported", field: "model", detail: "developer roster has no writer lineup" };
|
|
26517
|
-
}
|
|
26518
|
-
const allowed = writerIds.some((id) => {
|
|
26519
|
-
if (typeof id !== "string") return false;
|
|
26520
|
-
const route = routes[id];
|
|
26521
|
-
if (!route || typeof route !== "object" || Array.isArray(route)) return false;
|
|
26522
|
-
const record = route;
|
|
26523
|
-
if (record.harness !== harness || record.status !== "admitted") return false;
|
|
26524
|
-
if (!Array.isArray(record.permissions) || !record.permissions.includes("edit")) return false;
|
|
26525
|
-
const model = typeof record.model === "string" ? record.model : "";
|
|
26526
|
-
const vendor = typeof record.vendor === "string" ? record.vendor : "";
|
|
26527
|
-
return model === selector || vendor.length > 0 && `${vendor}/${model}` === selector;
|
|
26528
|
-
});
|
|
26529
|
-
if (!allowed) {
|
|
26510
|
+
detail: `live write steps require an audited writer profile; ${route.harness} has none`
|
|
26511
|
+
};
|
|
26512
|
+
}
|
|
26513
|
+
const writerIds = listRoleBindings(projectRoot).writer;
|
|
26514
|
+
if (!Array.isArray(writerIds) || writerIds.length === 0) {
|
|
26515
|
+
return { reason: "step_unsupported", field: "model", detail: "writer roster has no admitted edit route" };
|
|
26516
|
+
}
|
|
26517
|
+
const named = writerIds.includes(route.routeId);
|
|
26518
|
+
if (!named || !route.permissions.includes("edit")) {
|
|
26530
26519
|
return {
|
|
26531
26520
|
reason: "step_unsupported",
|
|
26532
26521
|
field: "model",
|
|
26533
|
-
detail: `live write route ${selector} on ${harness} is not on the
|
|
26522
|
+
detail: `live write route ${route.selector} on ${route.harness} is not an admitted edit route on the writer roster`
|
|
26534
26523
|
};
|
|
26535
26524
|
}
|
|
26536
26525
|
return void 0;
|
|
@@ -27063,7 +27052,6 @@ var init_runtime_supervisor = __esm({
|
|
|
27063
27052
|
init_sqlite();
|
|
27064
27053
|
init_runtime_service();
|
|
27065
27054
|
init_oneshot_producer();
|
|
27066
|
-
init_routes();
|
|
27067
27055
|
init_engine();
|
|
27068
27056
|
init_runtime_owner();
|
|
27069
27057
|
init_client();
|
|
@@ -30445,8 +30433,7 @@ async function cmdModelsScreen(runtime) {
|
|
|
30445
30433
|
try {
|
|
30446
30434
|
while (true) {
|
|
30447
30435
|
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
30448
|
-
|
|
30449
|
-
runtime.io.stdout(models.map((m2, i) => `${i + 1}. ${m2} [${policy.admitted.includes(m2) ? "admitted" : policy.disabled.includes(m2) ? "disabled" : "unset"}] roles:${Object.entries(roleBindings).filter(([, xs]) => xs.includes(m2)).map(([r]) => r).join(",") || "-"}`).join("\n") + "\n");
|
|
30436
|
+
runtime.io.stdout(models.map((m2, i) => `${i + 1}. ${m2} [${policy.admitted.includes(m2) ? "admitted" : policy.disabled.includes(m2) ? "disabled" : "unset"}] roles:${rolesForInventoryModel(runtime.dirs.workdir, m2).join(",") || "-"}`).join("\n") + "\n");
|
|
30450
30437
|
const command = (await ask("[a]dmit [d]isable [r]ole-add [x]ole-remove [q]uit: ")).trim().toLowerCase();
|
|
30451
30438
|
if (command === "q" || command === "quit") return 0;
|
|
30452
30439
|
const index = Number.parseInt((await ask("model number: ")).trim(), 10) - 1;
|
|
@@ -30468,7 +30455,8 @@ async function cmdModelsScreen(runtime) {
|
|
|
30468
30455
|
}
|
|
30469
30456
|
async function cmdRouteList(runtime) {
|
|
30470
30457
|
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
30471
|
-
|
|
30458
|
+
const membership = Object.entries(listRoleBindings(runtime.dirs.workdir)).flatMap(([role, ids]) => ids.map((id) => `${role} ${id}`));
|
|
30459
|
+
print(runtime.io, runtime.json, { ok: true, command: "routes list", policy, membership }, [...policy.admitted.map((x2) => `admitted ${x2}`), ...policy.disabled.map((x2) => `disabled ${x2}`), ...membership].join("\n") || "no route decisions");
|
|
30472
30460
|
return 0;
|
|
30473
30461
|
}
|
|
30474
30462
|
async function cmdRouteCount(runtime) {
|
|
@@ -37179,10 +37167,10 @@ function suggestWorkflowAndRoles(prompt, options = {}) {
|
|
|
37179
37167
|
supported: true,
|
|
37180
37168
|
prerequisites: [
|
|
37181
37169
|
"Run kxm init in the target repository if needed, then install the exact template with the suggested command. If that workflow ID already exists, stop and review it; the commands below apply only to a newly installed template, not an existing definition with potentially different agents or permissions.",
|
|
37182
|
-
...roles.map((role) => `Set
|
|
37170
|
+
...roles.map((role) => `Set role on .kxm/agents/${role.agent}.yaml to a role whose roster names an admitted route for harness ${role.harness}. Admit that route's selector in .kxm/routes.yaml and ensure it is not disabled; this recommendation does not choose a model or change routing.`),
|
|
37183
37171
|
...writeStep ? [
|
|
37184
37172
|
`Keep each writer step at assignments.maximum: 1 and set limits.maxConcurrentRuns: 1 in .kxm/project.yaml; the live ${selected.id} writer profile requires a lone writer in the checkout.`,
|
|
37185
|
-
`
|
|
37173
|
+
`The writer role roster must name a route whose harness is ${selected.id}, whose status is admitted, and whose permissions contain edit.`,
|
|
37186
37174
|
"Configure the test gate in .kxm/gates.yaml with kind: command and argv for this repository's actual verification command. A scaffold/example command or simulation is not evidence that the implementation works."
|
|
37187
37175
|
] : [],
|
|
37188
37176
|
`Validate the installed workflow with kxm gate validate --file .kxm/workflows/${bestPattern.id}.yaml and the project configuration with kxm init --dry-run before creating a run.`
|
|
@@ -50874,8 +50862,8 @@ init_telemetry();
|
|
|
50874
50862
|
init_routing();
|
|
50875
50863
|
init_prices();
|
|
50876
50864
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
50877
|
-
import { createHash as
|
|
50878
|
-
import { existsSync as existsSync44, mkdtempSync as mkdtempSync3, readFileSync as
|
|
50865
|
+
import { createHash as createHash19 } from "node:crypto";
|
|
50866
|
+
import { existsSync as existsSync44, mkdtempSync as mkdtempSync3, readFileSync as readFileSync40, rmSync as rmSync13 } from "node:fs";
|
|
50879
50867
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
50880
50868
|
import { basename as basename11, extname as extname3, join as join54, resolve as resolve32 } from "node:path";
|
|
50881
50869
|
import { createInterface as createInterface3 } from "node:readline";
|
|
@@ -52506,8 +52494,7 @@ ${line}
|
|
|
52506
52494
|
// plugins/kxm/src/init-guide-setup.ts
|
|
52507
52495
|
var import_yaml18 = __toESM(require_dist(), 1);
|
|
52508
52496
|
init_routes();
|
|
52509
|
-
import {
|
|
52510
|
-
import { existsSync as existsSync43, mkdirSync as mkdirSync32, readFileSync as readFileSync40, writeFileSync as writeFileSync28 } from "node:fs";
|
|
52497
|
+
import { existsSync as existsSync43, mkdirSync as mkdirSync32, writeFileSync as writeFileSync28 } from "node:fs";
|
|
52511
52498
|
import { dirname as dirname24, join as join53 } from "node:path";
|
|
52512
52499
|
var ADMITTED_GUIDE_BINDINGS = Object.freeze({
|
|
52513
52500
|
"anthropic/claude-fable-5.1": { harness: "claude", provider: "anthropic", model: "fable" },
|
|
@@ -52852,13 +52839,12 @@ var WRITER_ROLE_PATTERN = /(writer|implementer|scaffold|backend|frontend|sdk-int
|
|
|
52852
52839
|
function isWriterRole(roleSlug) {
|
|
52853
52840
|
return WRITER_ROLE_PATTERN.test(roleSlug);
|
|
52854
52841
|
}
|
|
52855
|
-
function agentDocument(roleSlug, stage
|
|
52842
|
+
function agentDocument(roleSlug, stage) {
|
|
52856
52843
|
const writer = isWriterRole(roleSlug);
|
|
52857
52844
|
return {
|
|
52858
52845
|
schema: "kxm.agent.v1",
|
|
52859
52846
|
purpose: `${stage.domain} (workflow-guide ${stage.title}; candidate verification is the operator's responsibility)`,
|
|
52860
|
-
|
|
52861
|
-
model: { provider: binding.provider, model: binding.model },
|
|
52847
|
+
role: roleSlug,
|
|
52862
52848
|
tools: { preset: writer ? "workspace-writer" : "read-only" },
|
|
52863
52849
|
defaultRepositoryAccess: writer ? "none" : "read",
|
|
52864
52850
|
repositories: { control: writer ? "write" : "read" },
|
|
@@ -52889,19 +52875,62 @@ function workflowDocument(workflow) {
|
|
|
52889
52875
|
steps
|
|
52890
52876
|
};
|
|
52891
52877
|
}
|
|
52878
|
+
var GUIDE_PI_EVIDENCE_SOURCE = "plans/evidence/route-guide-qwen-pi.md";
|
|
52879
|
+
var GUIDE_PI_EVIDENCE_SHA256 = "109f39728b251dd0565e275ae689a6e1d9b889a488d996b157d500c538115759";
|
|
52880
|
+
var GUIDE_PI_NOTE = {
|
|
52881
|
+
harness: "pi",
|
|
52882
|
+
vendor: "openrouter",
|
|
52883
|
+
model: "qwen/qwen3-coder-plus",
|
|
52884
|
+
reason: "Qwen has no supported native harness on this runner, and the operator admitted that exact model for guided setup.",
|
|
52885
|
+
provingCommand: "pi auth check --provider openrouter"
|
|
52886
|
+
};
|
|
52887
|
+
var GUIDE_PI_EVIDENCE_NOTE = `---
|
|
52888
|
+
schema: "kxm.doc.v1"
|
|
52889
|
+
id: "RES-GUIDE-QWEN-PI"
|
|
52890
|
+
type: "research"
|
|
52891
|
+
title: "Research: guided setup Pi Qwen origin"
|
|
52892
|
+
project: "kxm"
|
|
52893
|
+
status: "approved"
|
|
52894
|
+
owner: "@operator"
|
|
52895
|
+
created: "2026-09-26"
|
|
52896
|
+
updated: "2026-09-26"
|
|
52897
|
+
authority: "evidence"
|
|
52898
|
+
confidence: "verified"
|
|
52899
|
+
summary: "Guided setup pins every Pi model origin to this note so a later project.yaml edit cannot change the hash."
|
|
52900
|
+
tags: ["routing", "qwen", "pi", "init-guide"]
|
|
52901
|
+
related: []
|
|
52902
|
+
details:
|
|
52903
|
+
research_status: "complete"
|
|
52904
|
+
target_decision_date: "2026-09-26"
|
|
52905
|
+
---
|
|
52906
|
+
|
|
52907
|
+
# route-guide-qwen-pi
|
|
52908
|
+
|
|
52909
|
+
Dated 2026-09-26. Route ids are the guided role slugs. Harness \`${GUIDE_PI_NOTE.harness}\`. Model id \`${GUIDE_PI_NOTE.model}\`. Vendor \`${GUIDE_PI_NOTE.vendor}\`. Admitted because ${GUIDE_PI_NOTE.reason} Auth was proved with \`${GUIDE_PI_NOTE.provingCommand}\`.
|
|
52910
|
+
`;
|
|
52911
|
+
function guidePiNoteMatches(binding) {
|
|
52912
|
+
return binding.harness === GUIDE_PI_NOTE.harness && binding.provider === GUIDE_PI_NOTE.vendor && binding.model === GUIDE_PI_NOTE.model;
|
|
52913
|
+
}
|
|
52892
52914
|
function renderGuideSetupFiles(projectRoot, plan) {
|
|
52893
|
-
const projectYaml = join53(projectRoot, ".kxm", "project.yaml");
|
|
52894
|
-
const projectOrigin = existsSync43(projectYaml) ? { source: ".kxm/project.yaml", sha256: createHash19("sha256").update(readFileSync40(projectYaml)).digest("hex") } : void 0;
|
|
52895
52915
|
const files = [];
|
|
52896
52916
|
const roleStages = /* @__PURE__ */ new Map();
|
|
52897
52917
|
for (const workflow of plan.workflows) {
|
|
52898
52918
|
for (const stage of workflow.stages) roleStages.set(stage.role, stage);
|
|
52899
52919
|
}
|
|
52920
|
+
let evidenceQueued = false;
|
|
52900
52921
|
for (const [role, binding] of [...plan.agents.entries()].sort(([a], [b2]) => a.localeCompare(b2))) {
|
|
52901
52922
|
const stage = roleStages.get(role);
|
|
52902
52923
|
if (!stage) continue;
|
|
52903
52924
|
const routeId2 = role.replaceAll("_", "-");
|
|
52904
52925
|
const writer = isWriterRole(role);
|
|
52926
|
+
const piMatch = binding.harness === "pi" && guidePiNoteMatches(binding);
|
|
52927
|
+
if (binding.harness === "pi" && !piMatch) {
|
|
52928
|
+
files.push({
|
|
52929
|
+
path: join53(projectRoot, ".kxm", "agents", `${role}.yaml`),
|
|
52930
|
+
content: (0, import_yaml18.stringify)(agentDocument(role, stage))
|
|
52931
|
+
});
|
|
52932
|
+
continue;
|
|
52933
|
+
}
|
|
52905
52934
|
const modelDocument = {
|
|
52906
52935
|
schema: "kxm.model.v2",
|
|
52907
52936
|
id: routeId2,
|
|
@@ -52911,10 +52940,19 @@ function renderGuideSetupFiles(projectRoot, plan) {
|
|
|
52911
52940
|
status: "admitted",
|
|
52912
52941
|
permissions: [writer ? "edit" : "read-only"]
|
|
52913
52942
|
};
|
|
52914
|
-
if (
|
|
52943
|
+
if (piMatch) {
|
|
52944
|
+
modelDocument.origin = { source: GUIDE_PI_EVIDENCE_SOURCE, sha256: GUIDE_PI_EVIDENCE_SHA256 };
|
|
52945
|
+
if (!evidenceQueued) {
|
|
52946
|
+
files.push({
|
|
52947
|
+
path: join53(projectRoot, GUIDE_PI_EVIDENCE_SOURCE),
|
|
52948
|
+
content: GUIDE_PI_EVIDENCE_NOTE
|
|
52949
|
+
});
|
|
52950
|
+
evidenceQueued = true;
|
|
52951
|
+
}
|
|
52952
|
+
}
|
|
52915
52953
|
files.push({
|
|
52916
52954
|
path: join53(projectRoot, ".kxm", "agents", `${role}.yaml`),
|
|
52917
|
-
content: (0, import_yaml18.stringify)(agentDocument(role, stage
|
|
52955
|
+
content: (0, import_yaml18.stringify)(agentDocument(role, stage))
|
|
52918
52956
|
});
|
|
52919
52957
|
files.push({
|
|
52920
52958
|
path: join53(projectRoot, ".kxm", "models", `${routeId2}.yaml`),
|
|
@@ -53040,7 +53078,7 @@ function applyKxmPackageUpdate(runtime, notice) {
|
|
|
53040
53078
|
for (const step of planned) {
|
|
53041
53079
|
if (step.kind === "verify") {
|
|
53042
53080
|
if (!verifyReleaseAssetDigest(step.path, step.sha256)) {
|
|
53043
|
-
const actual = existsSync44(step.path) ?
|
|
53081
|
+
const actual = existsSync44(step.path) ? createHash19("sha256").update(readFileSync40(step.path)).digest("hex") : "missing";
|
|
53044
53082
|
return {
|
|
53045
53083
|
ok: false,
|
|
53046
53084
|
error: "release_digest_mismatch",
|
|
@@ -53323,7 +53361,7 @@ async function cmdValidate(runtime, fileFlag) {
|
|
|
53323
53361
|
return 1;
|
|
53324
53362
|
}
|
|
53325
53363
|
try {
|
|
53326
|
-
const raw = file ?
|
|
53364
|
+
const raw = file ? readFileSync40(file, "utf8") : inline;
|
|
53327
53365
|
if (explicitFile && file && !raw.trimStart().startsWith("[")) {
|
|
53328
53366
|
const value = parseRestrictedYaml2(raw, file);
|
|
53329
53367
|
const issues = new KxmSchemaRegistry().validate("workflow", value, file);
|
|
@@ -53573,7 +53611,7 @@ async function maybeOfferCompletionInstall(runtime) {
|
|
|
53573
53611
|
const { rcFile } = completionRcTarget(shell, { env: runtime.env });
|
|
53574
53612
|
if (rcFile && existsSync44(rcFile)) {
|
|
53575
53613
|
try {
|
|
53576
|
-
if (
|
|
53614
|
+
if (readFileSync40(rcFile, "utf8").includes(scriptPath)) return;
|
|
53577
53615
|
} catch {
|
|
53578
53616
|
}
|
|
53579
53617
|
}
|