@kontextmind/kxm 0.7.29 → 0.7.32
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/docs/agent-skills.md +1 -1
- package/docs/assignment-runner.md +4 -4
- package/docs/getting-started.md +1 -1
- package/docs/workflow-guide.md +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +40 -45
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime.js +13 -11
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +13 -11
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +1 -1
- package/plugins/kxm/src/cli/vnext.ts +16 -17
- package/plugins/kxm/src/cli.ts +8 -8
- package/plugins/kxm/src/init-guide-setup.ts +2 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/routes.ts +68 -0
- package/plugins/kxm/src/vnext-engine.ts +2 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +2 -2
- package/schemas/policy-draft/README.md +1 -1
- package/scripts/roster-policy.mjs +7 -3
- package/.kxm/producers.yaml +0 -53
- package/plugins/kxm/src/producers.ts +0 -71
package/docs/agent-skills.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
This document describes the bundled KXM Agent Skills suite: focused skills
|
|
4
4
|
that cover current KXM top-level command groups. The suite documents the
|
|
5
5
|
existing CLI. It does **not** land unified YAML role/project/workflow
|
|
6
|
-
authority, admit new writers, or replace trusted `.kxm/roster.
|
|
6
|
+
authority, admit new writers, or replace trusted `.kxm/roster.yaml` policy.
|
|
7
7
|
|
|
8
8
|
## Feature-to-Skill Matrix
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ policy.
|
|
|
16
16
|
## 1. Overview and role rotation
|
|
17
17
|
|
|
18
18
|
Developer orchestration on this runner uses a role-based rotation backed by
|
|
19
|
-
trusted policy in [`.kxm/roster.
|
|
19
|
+
trusted policy in [`.kxm/roster.yaml`](../.kxm/roster.yaml). Roles, harnesses,
|
|
20
20
|
and models are admitted with strict permission and vendor boundaries:
|
|
21
21
|
|
|
22
22
|
| Role | Admitted route | Vendor | Permission | Purpose |
|
|
@@ -36,7 +36,7 @@ and models are admitted with strict permission and vendor boundaries:
|
|
|
36
36
|
exceed the admitted ceiling (e.g. attempting to give edit permissions to a
|
|
37
37
|
read-only reviewer).
|
|
38
38
|
- **Trusted roster policy:** Dispatch (`assign` / `run`) and accept load
|
|
39
|
-
`.kxm/roster.
|
|
39
|
+
`.kxm/roster.yaml` through the trusted control Git loader
|
|
40
40
|
(`loadTrustedRosterPolicy`). Loader errors, missing/empty policy, and
|
|
41
41
|
malformed policy fail closed with `route_invalid`. There is no raw working-tree
|
|
42
42
|
JSON fallback and no null-policy acceptance. Tests may inject an explicit
|
|
@@ -96,7 +96,7 @@ node scripts/assignment-run.mjs run --manifest /absolute/path/to/manifest.json
|
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
- Manifest validation asserts worktree cleanliness and validates the route
|
|
99
|
-
against `.kxm/roster.
|
|
99
|
+
against `.kxm/roster.yaml`.
|
|
100
100
|
- Headless execution dispatches to the native harness (or OpenRouter via Pi for
|
|
101
101
|
admitted relief).
|
|
102
102
|
- Successful runs write `completion.json`, candidate snapshot metadata, and
|
|
@@ -219,7 +219,7 @@ The runner fails closed with bounded error codes defined in `RUNNER_CODES`:
|
|
|
219
219
|
|
|
220
220
|
| Code | Trigger condition | Remedy |
|
|
221
221
|
|---|---|---|
|
|
222
|
-
| `route_invalid` | Harness/model not admitted in lineup for the requested role, permission exceeds route ceiling, or trusted roster policy cannot be loaded/validated. | Use a trusted clean control checkout; do not dispatch from a dirty implementation branch. Check `.kxm/roster.
|
|
222
|
+
| `route_invalid` | Harness/model not admitted in lineup for the requested role, permission exceeds route ceiling, or trusted roster policy cannot be loaded/validated. | Use a trusted clean control checkout; do not dispatch from a dirty implementation branch. Check `.kxm/roster.yaml` lineup and permissions for the role. |
|
|
223
223
|
| `critic_invalid` | Missing required critic role, duplicate roles, wrong model, or vendor collision between writer and critics. | Ensure independent critics (Fable + Sol) from distinct providers. |
|
|
224
224
|
| `critic_block` | An unresolved `BLOCK` verdict exists for the target tree. | Rework the changes, address findings, and pass review with a `rework_of` link. |
|
|
225
225
|
| `commit_tree_mismatch` | Git commit tree does not equal the witnessed tree. | Commit the exact candidate tree verified by the witness before running accept. |
|
package/docs/getting-started.md
CHANGED
|
@@ -77,7 +77,7 @@ too). kxm resolves each role's first guide candidate whose harness is
|
|
|
77
77
|
authenticated and writes only current vNext project resources —
|
|
78
78
|
`.kxm/agents/<role>.yaml` (`kxm.agent.v1`) and `.kxm/workflows/<slug>.yaml`
|
|
79
79
|
(`kxm.workflow.v1`). It never writes retired legacy authority (`.kxm/config`,
|
|
80
|
-
`.kxm/roster.json`). Roles whose candidates have no authenticated harness are
|
|
80
|
+
retired `.kxm/roster.json`) or the trusted `.kxm/roster.yaml` policy. Roles whose candidates have no authenticated harness are
|
|
81
81
|
reported as skipped, not silently downgraded. Guide candidates are dated
|
|
82
82
|
research — verify them before dispatch. Declining is safe: set
|
|
83
83
|
`KXM_SKIP_GUIDE_SETUP_PROMPT=1` to suppress the offer.
|
package/docs/workflow-guide.md
CHANGED
|
@@ -74,7 +74,7 @@ pull request (five CI jobs: 2 Linux validate, classify, docs, plugin)
|
|
|
74
74
|
|
|
75
75
|
**Runner loop invariants:**
|
|
76
76
|
|
|
77
|
-
- **Roles and rotation:** The runner strictly maps assignments to authenticated roster roles (`AGENTS.md` and `.kxm/roster.
|
|
77
|
+
- **Roles and rotation:** The runner strictly maps assignments to authenticated roster roles (`AGENTS.md` and `.kxm/roster.yaml`):
|
|
78
78
|
- **Implement / write code (`writer`):** **Grok** (`grok --model grok-4.6`, headless). Currently admitted native writer on this runner. Failover follows attempts-and-relief: after an empty or failed attempt, immediately fail over to the next eligible authenticated writer in the roster (**Qwen** `openrouter/qwen/qwen3-coder-plus` via Pi).
|
|
79
79
|
- **Plan (`planner`):** **Claude Fable** (`claude --model fable`), read-only architecture and permissions planning.
|
|
80
80
|
- **Review architecture (`reviewer-arch`):** **Claude Fable** (`claude --model fable`), designated architecture and permissions critic.
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "kxm",
|
|
4
4
|
"displayName": "KXM",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.32",
|
|
6
6
|
"description": "Headless multi-agent orchestration, durable workflows, and a live operator dashboard for Pi and Claude Code",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "KontextMind",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -24951,33 +24951,29 @@ function loadPriceCatalogForEstimate(options) {
|
|
|
24951
24951
|
// plugins/kxm/src/vnext-engine.ts
|
|
24952
24952
|
var import_yaml5 = __toESM(require_dist(), 1);
|
|
24953
24953
|
|
|
24954
|
-
// plugins/kxm/src/
|
|
24954
|
+
// plugins/kxm/src/routes.ts
|
|
24955
24955
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
24956
24956
|
import { existsSync as existsSync11, readFileSync as readFileSync10, mkdirSync as mkdirSync7, writeFileSync as writeFileSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
24957
24957
|
import { join as join11 } from "node:path";
|
|
24958
|
-
var
|
|
24959
|
-
|
|
24960
|
-
|
|
24958
|
+
var RETIRED_POLICY = ".kxm/producers.yaml";
|
|
24959
|
+
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
|
|
24960
|
+
function loadRoutePolicy(root) {
|
|
24961
|
+
if (existsSync11(join11(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
24962
|
+
const path4 = join11(root, ".kxm", "routes.yaml");
|
|
24961
24963
|
if (!existsSync11(path4)) return empty();
|
|
24962
24964
|
const value = (0, import_yaml4.parse)(readFileSync10(path4, "utf8"));
|
|
24963
|
-
if (value?.schema !== "kxm.
|
|
24964
|
-
return { schema: "kxm.
|
|
24965
|
-
}
|
|
24966
|
-
function
|
|
24967
|
-
|
|
24968
|
-
policy.promoted = policy.promoted.filter((x2) => x2 !== model);
|
|
24969
|
-
policy.demoted = policy.demoted.filter((x2) => x2 !== model);
|
|
24970
|
-
policy[status].push(model);
|
|
24971
|
-
policy.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
24972
|
-
mkdirSync7(join11(root, ".kxm"), { recursive: true });
|
|
24973
|
-
writeFileSync7(join11(root, ".kxm", "producers.yaml"), (0, import_yaml4.stringify)(policy), "utf8");
|
|
24974
|
-
return policy;
|
|
24965
|
+
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
24966
|
+
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") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v2]) => Array.isArray(v2)).map(([k, v2]) => [k, v2.filter((x2) => typeof x2 === "string")])) : {} };
|
|
24967
|
+
}
|
|
24968
|
+
function updateRouteState(root, model, state) {
|
|
24969
|
+
return setRouteState(root, model, state);
|
|
24975
24970
|
}
|
|
24976
|
-
function
|
|
24977
|
-
const policy =
|
|
24978
|
-
policy.
|
|
24971
|
+
function setRouteState(root, model, state, role, removeRole2 = false) {
|
|
24972
|
+
const policy = loadRoutePolicy(root);
|
|
24973
|
+
policy.admitted = policy.admitted.filter((x2) => x2 !== model);
|
|
24979
24974
|
policy.disabled = policy.disabled.filter((x2) => x2 !== model);
|
|
24980
|
-
policy
|
|
24975
|
+
if (state === "admitted") policy.admitted.push(model);
|
|
24976
|
+
else policy.disabled.push(model);
|
|
24981
24977
|
if (role) {
|
|
24982
24978
|
if (!/^[a-z][a-z0-9_-]{0,63}$/i.test(role)) throw new Error("invalid role id");
|
|
24983
24979
|
const rolePath = join11(root, ".kxm", "roles", `${role}.yaml`);
|
|
@@ -24999,7 +24995,7 @@ function setModelState(root, model, state, role, removeRole2 = false) {
|
|
|
24999
24995
|
}
|
|
25000
24996
|
policy.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25001
24997
|
mkdirSync7(join11(root, ".kxm"), { recursive: true });
|
|
25002
|
-
writeFileSync7(join11(root, ".kxm", "
|
|
24998
|
+
writeFileSync7(join11(root, ".kxm", "routes.yaml"), (0, import_yaml4.stringify)(policy), "utf8");
|
|
25003
24999
|
return policy;
|
|
25004
25000
|
}
|
|
25005
25001
|
function listRoleBindings(root) {
|
|
@@ -33853,7 +33849,7 @@ async function cmdHarnessList(runtime) {
|
|
|
33853
33849
|
print(runtime.io, runtime.json, { ok: true, command: "harness list", ...inventory }, formatHarnessInventory(inventory));
|
|
33854
33850
|
return 0;
|
|
33855
33851
|
}
|
|
33856
|
-
async function
|
|
33852
|
+
async function selectInventoryModel(runtime, requested) {
|
|
33857
33853
|
const models = listInventoryModels(runtime.dirs.workdir);
|
|
33858
33854
|
if (requested) return models.find((model) => model.toLowerCase() === requested.toLowerCase());
|
|
33859
33855
|
if (runtime.json || !process.stdin.isTTY || models.length === 0) return void 0;
|
|
@@ -33864,18 +33860,18 @@ async function selectProducerModel(runtime, requested) {
|
|
|
33864
33860
|
const index = Number.parseInt(answer.trim(), 10) - 1;
|
|
33865
33861
|
return Number.isInteger(index) && index >= 0 && index < models.length ? models[index] : void 0;
|
|
33866
33862
|
}
|
|
33867
|
-
async function
|
|
33868
|
-
const model = await
|
|
33863
|
+
async function cmdRouteChange(runtime, status, requested) {
|
|
33864
|
+
const model = await selectInventoryModel(runtime, requested);
|
|
33869
33865
|
if (!model) {
|
|
33870
|
-
print(runtime.io, runtime.json, { ok: false, command: `
|
|
33866
|
+
print(runtime.io, runtime.json, { ok: false, command: `routes ${status}`, error: "model_selection_required" }, "select a model from the refreshed inventory");
|
|
33871
33867
|
return 2;
|
|
33872
33868
|
}
|
|
33873
33869
|
if (runtime.dryRun) {
|
|
33874
|
-
print(runtime.io, runtime.json, { ok: true, command: `
|
|
33870
|
+
print(runtime.io, runtime.json, { ok: true, command: `routes ${status}`, model, dryRun: true }, `would ${status === "admitted" ? "admit" : "disable"} ${model}`);
|
|
33875
33871
|
return 0;
|
|
33876
33872
|
}
|
|
33877
|
-
const policy =
|
|
33878
|
-
print(runtime.io, runtime.json, { ok: true, command: `
|
|
33873
|
+
const policy = updateRouteState(runtime.dirs.workdir, model, status);
|
|
33874
|
+
print(runtime.io, runtime.json, { ok: true, command: `routes ${status}`, model, policy }, `${status} ${model}`);
|
|
33879
33875
|
return 0;
|
|
33880
33876
|
}
|
|
33881
33877
|
async function cmdModelsScreen(runtime) {
|
|
@@ -33888,25 +33884,24 @@ async function cmdModelsScreen(runtime) {
|
|
|
33888
33884
|
const ask = (q2) => new Promise((resolve31) => rl.question(q2, resolve31));
|
|
33889
33885
|
try {
|
|
33890
33886
|
while (true) {
|
|
33891
|
-
const policy =
|
|
33887
|
+
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
33892
33888
|
const roleBindings = listRoleBindings(runtime.dirs.workdir);
|
|
33893
|
-
runtime.io.stdout(models.map((m2, i) => `${i + 1}. ${m2} [${policy.
|
|
33894
|
-
const command = (await ask("[
|
|
33889
|
+
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");
|
|
33890
|
+
const command = (await ask("[a]dmit [d]isable [r]ole-add [x]ole-remove [q]uit: ")).trim().toLowerCase();
|
|
33895
33891
|
if (command === "q" || command === "quit") return 0;
|
|
33896
33892
|
const index = Number.parseInt((await ask("model number: ")).trim(), 10) - 1;
|
|
33897
33893
|
if (!Number.isInteger(index) || !models[index]) continue;
|
|
33898
33894
|
const model = models[index];
|
|
33899
|
-
if (command === "
|
|
33900
|
-
else if (command === "
|
|
33901
|
-
else if (command === "a" || command === "r") setModelState(runtime.dirs.workdir, model, "enabled", (await ask("role: ")).trim(), command === "r");
|
|
33895
|
+
if (command === "a" || command === "d") setRouteState(runtime.dirs.workdir, model, command === "a" ? "admitted" : "disabled");
|
|
33896
|
+
else if (command === "r" || command === "x") setRouteState(runtime.dirs.workdir, model, "admitted", (await ask("role: ")).trim(), command === "x");
|
|
33902
33897
|
}
|
|
33903
33898
|
} finally {
|
|
33904
33899
|
rl.close();
|
|
33905
33900
|
}
|
|
33906
33901
|
}
|
|
33907
|
-
async function
|
|
33908
|
-
const policy =
|
|
33909
|
-
print(runtime.io, runtime.json, { ok: true, command: "
|
|
33902
|
+
async function cmdRouteList(runtime) {
|
|
33903
|
+
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
33904
|
+
print(runtime.io, runtime.json, { ok: true, command: "routes list", policy }, [...policy.admitted.map((x2) => `admitted ${x2}`), ...policy.disabled.map((x2) => `disabled ${x2}`)].join("\n") || "no route decisions");
|
|
33910
33905
|
return 0;
|
|
33911
33906
|
}
|
|
33912
33907
|
async function cmdModelInventoryRefresh(runtime) {
|
|
@@ -48797,7 +48792,7 @@ function createProgram(ctx, result) {
|
|
|
48797
48792
|
addGlobalOptions(runCmd.command("list").description("List recent runs for the current project")).action(async function runListAction() {
|
|
48798
48793
|
result.code = await cmdVnextRunList(runtimeFrom(ctx, this));
|
|
48799
48794
|
});
|
|
48800
|
-
const modelsCmd = addGlobalOptions(program2.command("models").description("Manage model catalogs, roles, and
|
|
48795
|
+
const modelsCmd = addGlobalOptions(program2.command("models").description("Manage model catalogs, roles, and route state"));
|
|
48801
48796
|
modelsCmd.action(async function modelsScreenAction() {
|
|
48802
48797
|
result.code = await cmdModelsScreen(runtimeFrom(ctx, this));
|
|
48803
48798
|
});
|
|
@@ -48805,14 +48800,14 @@ function createProgram(ctx, result) {
|
|
|
48805
48800
|
addGlobalOptions(modelsCmd.command("inventory-refresh").alias("refresh").description("Refresh the YAML model inventory with standard and Nous/OpenRouter prices")).action(async function modelInventoryRefreshAction() {
|
|
48806
48801
|
result.code = await cmdModelInventoryRefresh(runtimeFrom(ctx, this));
|
|
48807
48802
|
});
|
|
48808
|
-
const
|
|
48809
|
-
|
|
48810
|
-
addGlobalOptions(
|
|
48811
|
-
result.code = await
|
|
48803
|
+
const routesCmd = addGlobalOptions(program2.command("routes").description("Admit or disable verified model routes"));
|
|
48804
|
+
routesCmd.helpCommand("help", "Show routes help");
|
|
48805
|
+
addGlobalOptions(routesCmd.command("list").description("List route decisions")).action(async function routesListAction() {
|
|
48806
|
+
result.code = await cmdRouteList(runtimeFrom(ctx, this));
|
|
48812
48807
|
});
|
|
48813
|
-
for (const status of ["
|
|
48814
|
-
addGlobalOptions(
|
|
48815
|
-
result.code = await
|
|
48808
|
+
for (const status of ["admit", "disable"]) {
|
|
48809
|
+
addGlobalOptions(routesCmd.command(status).description(`${status === "admit" ? "Admit" : "Disable"} a model route from the inventory`)).option("--model <id>", "Exact model id; omit to choose interactively").action(async function routeChangeAction(options) {
|
|
48810
|
+
result.code = await cmdRouteChange(runtimeFrom(ctx, this), status === "admit" ? "admitted" : "disabled", options.model);
|
|
48816
48811
|
});
|
|
48817
48812
|
}
|
|
48818
48813
|
const harnessCmd = addGlobalOptions(program2.command("harness").description("Detect coding-agent harnesses and authentication"));
|
|
@@ -17121,7 +17121,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
|
|
|
17121
17121
|
}
|
|
17122
17122
|
|
|
17123
17123
|
// plugins/kxm/src/mcp-server.ts
|
|
17124
|
-
var VERSION = "0.7.
|
|
17124
|
+
var VERSION = "0.7.32";
|
|
17125
17125
|
var inbox = /* @__PURE__ */ new Map();
|
|
17126
17126
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
17127
17127
|
var meshClient;
|
|
@@ -22945,17 +22945,19 @@ import { createHash as createHash11, randomBytes as randomBytes2, timingSafeEqua
|
|
|
22945
22945
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
22946
22946
|
import { join as join11 } from "node:path";
|
|
22947
22947
|
|
|
22948
|
-
// plugins/kxm/src/
|
|
22948
|
+
// plugins/kxm/src/routes.ts
|
|
22949
22949
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
22950
22950
|
import { existsSync as existsSync8, readFileSync as readFileSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
|
|
22951
22951
|
import { join as join9 } from "node:path";
|
|
22952
|
-
var
|
|
22953
|
-
|
|
22954
|
-
|
|
22952
|
+
var RETIRED_POLICY = ".kxm/producers.yaml";
|
|
22953
|
+
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
|
|
22954
|
+
function loadRoutePolicy(root) {
|
|
22955
|
+
if (existsSync8(join9(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
22956
|
+
const path = join9(root, ".kxm", "routes.yaml");
|
|
22955
22957
|
if (!existsSync8(path)) return empty();
|
|
22956
22958
|
const value = (0, import_yaml4.parse)(readFileSync6(path, "utf8"));
|
|
22957
|
-
if (value?.schema !== "kxm.
|
|
22958
|
-
return { schema: "kxm.
|
|
22959
|
+
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
22960
|
+
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") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v]) => Array.isArray(v)).map(([k, v]) => [k, v.filter((x) => typeof x === "string")])) : {} };
|
|
22959
22961
|
}
|
|
22960
22962
|
function listRoleBindings(root) {
|
|
22961
22963
|
const dir = join9(root, ".kxm", "roles");
|
|
@@ -22968,9 +22970,9 @@ function listRoleBindings(root) {
|
|
|
22968
22970
|
}
|
|
22969
22971
|
return result;
|
|
22970
22972
|
}
|
|
22971
|
-
function
|
|
22972
|
-
const policy =
|
|
22973
|
-
return policy.
|
|
22973
|
+
function isRouteAdmitted(root, modelId) {
|
|
22974
|
+
const policy = loadRoutePolicy(root);
|
|
22975
|
+
return policy.admitted.includes(modelId) && !policy.disabled.includes(modelId);
|
|
22974
22976
|
}
|
|
22975
22977
|
|
|
22976
22978
|
// plugins/kxm/src/context-packet.ts
|
|
@@ -25164,7 +25166,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
25164
25166
|
}
|
|
25165
25167
|
};
|
|
25166
25168
|
}
|
|
25167
|
-
if (!
|
|
25169
|
+
if (!isRouteAdmitted(projectRoot, selector)) {
|
|
25168
25170
|
return {
|
|
25169
25171
|
error: {
|
|
25170
25172
|
reason: "step_unsupported",
|
|
@@ -27337,7 +27339,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
27337
27339
|
const value = model;
|
|
27338
27340
|
const provider = typeof value.provider === "string" ? value.provider : void 0;
|
|
27339
27341
|
const modelName = typeof value.model === "string" ? value.model : void 0;
|
|
27340
|
-
if (!provider || !modelName || !
|
|
27342
|
+
if (!provider || !modelName || !isRouteAdmitted(projectRoot, `${provider}/${modelName}`)) {
|
|
27341
27343
|
throw new Error("producer_route_not_admitted");
|
|
27342
27344
|
}
|
|
27343
27345
|
return { provider, model: modelName };
|
|
@@ -22470,17 +22470,19 @@ import { createHash as createHash10, randomBytes, timingSafeEqual } from "node:c
|
|
|
22470
22470
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
22471
22471
|
import { join as join11 } from "node:path";
|
|
22472
22472
|
|
|
22473
|
-
// plugins/kxm/src/
|
|
22473
|
+
// plugins/kxm/src/routes.ts
|
|
22474
22474
|
var import_yaml4 = __toESM(require_dist(), 1);
|
|
22475
22475
|
import { existsSync as existsSync8, readFileSync as readFileSync6, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readdirSync as readdirSync4 } from "node:fs";
|
|
22476
22476
|
import { join as join9 } from "node:path";
|
|
22477
|
-
var
|
|
22478
|
-
|
|
22479
|
-
|
|
22477
|
+
var RETIRED_POLICY = ".kxm/producers.yaml";
|
|
22478
|
+
var empty = () => ({ schema: "kxm.routes.v2", updatedAt: (/* @__PURE__ */ new Date()).toISOString(), admitted: [], disabled: [], roles: {} });
|
|
22479
|
+
function loadRoutePolicy(root) {
|
|
22480
|
+
if (existsSync8(join9(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
22481
|
+
const path = join9(root, ".kxm", "routes.yaml");
|
|
22480
22482
|
if (!existsSync8(path)) return empty();
|
|
22481
22483
|
const value = (0, import_yaml4.parse)(readFileSync6(path, "utf8"));
|
|
22482
|
-
if (value?.schema !== "kxm.
|
|
22483
|
-
return { schema: "kxm.
|
|
22484
|
+
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
22485
|
+
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") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v]) => Array.isArray(v)).map(([k, v]) => [k, v.filter((x) => typeof x === "string")])) : {} };
|
|
22484
22486
|
}
|
|
22485
22487
|
function listRoleBindings(root) {
|
|
22486
22488
|
const dir = join9(root, ".kxm", "roles");
|
|
@@ -22493,9 +22495,9 @@ function listRoleBindings(root) {
|
|
|
22493
22495
|
}
|
|
22494
22496
|
return result;
|
|
22495
22497
|
}
|
|
22496
|
-
function
|
|
22497
|
-
const policy =
|
|
22498
|
-
return policy.
|
|
22498
|
+
function isRouteAdmitted(root, modelId) {
|
|
22499
|
+
const policy = loadRoutePolicy(root);
|
|
22500
|
+
return policy.admitted.includes(modelId) && !policy.disabled.includes(modelId);
|
|
22499
22501
|
}
|
|
22500
22502
|
|
|
22501
22503
|
// plugins/kxm/src/context-packet.ts
|
|
@@ -24678,7 +24680,7 @@ function resolveProducerRoute(projectRoot, step, agentId) {
|
|
|
24678
24680
|
}
|
|
24679
24681
|
};
|
|
24680
24682
|
}
|
|
24681
|
-
if (!
|
|
24683
|
+
if (!isRouteAdmitted(projectRoot, selector)) {
|
|
24682
24684
|
return {
|
|
24683
24685
|
error: {
|
|
24684
24686
|
reason: "step_unsupported",
|
|
@@ -26851,7 +26853,7 @@ async function startVnextRuntimeSupervisorInner(paths, requestedPortOption, now)
|
|
|
26851
26853
|
const value = model;
|
|
26852
26854
|
const provider = typeof value.provider === "string" ? value.provider : void 0;
|
|
26853
26855
|
const modelName = typeof value.model === "string" ? value.model : void 0;
|
|
26854
|
-
if (!provider || !modelName || !
|
|
26856
|
+
if (!provider || !modelName || !isRouteAdmitted(projectRoot, `${provider}/${modelName}`)) {
|
|
26855
26857
|
throw new Error("producer_route_not_admitted");
|
|
26856
26858
|
}
|
|
26857
26859
|
return { provider, model: modelName };
|
package/plugins/kxm/package.json
CHANGED
|
@@ -8,7 +8,7 @@ description: Select the right suite skill; state universal safety rules and port
|
|
|
8
8
|
This skill routes to the appropriate specialized skill for each KXM command category. Use this skill to determine which specific skill handles the command you need.
|
|
9
9
|
|
|
10
10
|
These bundled skills document the current CLI. They do not switch runtime YAML
|
|
11
|
-
authority, admit writers, or replace `.kxm/roster.
|
|
11
|
+
authority, admit writers, or replace `.kxm/roster.yaml` trusted policy.
|
|
12
12
|
|
|
13
13
|
## Command Routing Guide
|
|
14
14
|
|
|
@@ -5,7 +5,7 @@ import { join, resolve } from "node:path";
|
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { createBackup, restoreBackup } from "../database.ts";
|
|
7
7
|
import { refreshModelInventory } from "../model-inventory.ts";
|
|
8
|
-
import { listInventoryModels, listRoleBindings,
|
|
8
|
+
import { listInventoryModels, listRoleBindings, loadRoutePolicy, setRouteState, updateRouteState } from "../routes.ts";
|
|
9
9
|
import {
|
|
10
10
|
VnextConfigError,
|
|
11
11
|
discoverVnextProjectRoot,
|
|
@@ -723,7 +723,7 @@ export async function cmdHarnessList(runtime: Runtime): Promise<number> {
|
|
|
723
723
|
return 0;
|
|
724
724
|
}
|
|
725
725
|
|
|
726
|
-
export async function
|
|
726
|
+
export async function selectInventoryModel(runtime: Runtime, requested?: string | undefined): Promise<string | undefined> {
|
|
727
727
|
const models = listInventoryModels(runtime.dirs.workdir);
|
|
728
728
|
if (requested) return models.find((model) => model.toLowerCase() === requested.toLowerCase());
|
|
729
729
|
if (runtime.json || !process.stdin.isTTY || models.length === 0) return undefined;
|
|
@@ -735,12 +735,12 @@ export async function selectProducerModel(runtime: Runtime, requested?: string |
|
|
|
735
735
|
return Number.isInteger(index) && index >= 0 && index < models.length ? models[index] : undefined;
|
|
736
736
|
}
|
|
737
737
|
|
|
738
|
-
export async function
|
|
739
|
-
const model = await
|
|
740
|
-
if (!model) { print(runtime.io, runtime.json, { ok: false, command: `
|
|
741
|
-
if (runtime.dryRun) { print(runtime.io, runtime.json, { ok: true, command: `
|
|
742
|
-
const policy =
|
|
743
|
-
print(runtime.io, runtime.json, { ok: true, command: `
|
|
738
|
+
export async function cmdRouteChange(runtime: Runtime, status: "admitted" | "disabled", requested?: string | undefined): Promise<number> {
|
|
739
|
+
const model = await selectInventoryModel(runtime, requested);
|
|
740
|
+
if (!model) { print(runtime.io, runtime.json, { ok: false, command: `routes ${status}`, error: "model_selection_required" }, "select a model from the refreshed inventory"); return 2; }
|
|
741
|
+
if (runtime.dryRun) { print(runtime.io, runtime.json, { ok: true, command: `routes ${status}`, model, dryRun: true }, `would ${status === "admitted" ? "admit" : "disable"} ${model}`); return 0; }
|
|
742
|
+
const policy = updateRouteState(runtime.dirs.workdir, model, status);
|
|
743
|
+
print(runtime.io, runtime.json, { ok: true, command: `routes ${status}`, model, policy }, `${status} ${model}`);
|
|
744
744
|
return 0;
|
|
745
745
|
}
|
|
746
746
|
|
|
@@ -751,24 +751,23 @@ export async function cmdModelsScreen(runtime: Runtime): Promise<number> {
|
|
|
751
751
|
const ask = (q: string) => new Promise<string>((resolve) => rl.question(q, resolve));
|
|
752
752
|
try {
|
|
753
753
|
while (true) {
|
|
754
|
-
const policy =
|
|
754
|
+
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
755
755
|
const roleBindings = listRoleBindings(runtime.dirs.workdir);
|
|
756
|
-
runtime.io.stdout(models.map((m, i) => `${i + 1}. ${m} [${policy.
|
|
757
|
-
const command = (await ask("[
|
|
756
|
+
runtime.io.stdout(models.map((m, i) => `${i + 1}. ${m} [${policy.admitted.includes(m) ? "admitted" : policy.disabled.includes(m) ? "disabled" : "unset"}] roles:${Object.entries(roleBindings).filter(([, xs]) => xs.includes(m)).map(([r]) => r).join(",") || "-"}`).join("\n") + "\n");
|
|
757
|
+
const command = (await ask("[a]dmit [d]isable [r]ole-add [x]ole-remove [q]uit: ")).trim().toLowerCase();
|
|
758
758
|
if (command === "q" || command === "quit") return 0;
|
|
759
759
|
const index = Number.parseInt((await ask("model number: ")).trim(), 10) - 1;
|
|
760
760
|
if (!Number.isInteger(index) || !models[index]) continue;
|
|
761
761
|
const model = models[index];
|
|
762
|
-
if (command === "
|
|
763
|
-
else if (command === "
|
|
764
|
-
else if (command === "a" || command === "r") setModelState(runtime.dirs.workdir, model, "enabled", (await ask("role: ")).trim(), command === "r");
|
|
762
|
+
if (command === "a" || command === "d") setRouteState(runtime.dirs.workdir, model, command === "a" ? "admitted" : "disabled");
|
|
763
|
+
else if (command === "r" || command === "x") setRouteState(runtime.dirs.workdir, model, "admitted", (await ask("role: ")).trim(), command === "x");
|
|
765
764
|
}
|
|
766
765
|
} finally { rl.close(); }
|
|
767
766
|
}
|
|
768
767
|
|
|
769
|
-
export async function
|
|
770
|
-
const policy =
|
|
771
|
-
print(runtime.io, runtime.json, { ok: true, command: "
|
|
768
|
+
export async function cmdRouteList(runtime: Runtime): Promise<number> {
|
|
769
|
+
const policy = loadRoutePolicy(runtime.dirs.workdir);
|
|
770
|
+
print(runtime.io, runtime.json, { ok: true, command: "routes list", policy }, [...policy.admitted.map((x) => `admitted ${x}`), ...policy.disabled.map((x) => `disabled ${x}`)].join("\n") || "no route decisions");
|
|
772
771
|
return 0;
|
|
773
772
|
}
|
|
774
773
|
|
package/plugins/kxm/src/cli.ts
CHANGED
|
@@ -111,9 +111,9 @@ import {
|
|
|
111
111
|
cmdVnextRunCancel,
|
|
112
112
|
cmdVnextRunList,
|
|
113
113
|
cmdHarnessList,
|
|
114
|
-
|
|
114
|
+
cmdRouteChange,
|
|
115
115
|
cmdModelsScreen,
|
|
116
|
-
|
|
116
|
+
cmdRouteList,
|
|
117
117
|
cmdModelInventoryRefresh,
|
|
118
118
|
cmdVnextRuntime,
|
|
119
119
|
} from "./cli/vnext.ts";
|
|
@@ -415,7 +415,7 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
415
415
|
result.code = await cmdVnextRunList(runtimeFrom(ctx, this));
|
|
416
416
|
});
|
|
417
417
|
|
|
418
|
-
const modelsCmd = addGlobalOptions(program.command("models").description("Manage model catalogs, roles, and
|
|
418
|
+
const modelsCmd = addGlobalOptions(program.command("models").description("Manage model catalogs, roles, and route state"));
|
|
419
419
|
modelsCmd.action(async function modelsScreenAction(this: Command) { result.code = await cmdModelsScreen(runtimeFrom(ctx, this)); });
|
|
420
420
|
modelsCmd.helpCommand("help", "Show models help");
|
|
421
421
|
addGlobalOptions(modelsCmd.command("inventory-refresh").alias("refresh").description("Refresh the YAML model inventory with standard and Nous/OpenRouter prices"))
|
|
@@ -423,11 +423,11 @@ function createProgram(ctx: CliContext, result: { code: number }): Command {
|
|
|
423
423
|
result.code = await cmdModelInventoryRefresh(runtimeFrom(ctx, this));
|
|
424
424
|
});
|
|
425
425
|
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
addGlobalOptions(
|
|
429
|
-
for (const status of ["
|
|
430
|
-
addGlobalOptions(
|
|
426
|
+
const routesCmd = addGlobalOptions(program.command("routes").description("Admit or disable verified model routes"));
|
|
427
|
+
routesCmd.helpCommand("help", "Show routes help");
|
|
428
|
+
addGlobalOptions(routesCmd.command("list").description("List route decisions")).action(async function routesListAction(this: Command) { result.code = await cmdRouteList(runtimeFrom(ctx, this)); });
|
|
429
|
+
for (const status of ["admit", "disable"] as const) {
|
|
430
|
+
addGlobalOptions(routesCmd.command(status).description(`${status === "admit" ? "Admit" : "Disable"} a model route from the inventory`)).option("--model <id>", "Exact model id; omit to choose interactively").action(async function routeChangeAction(this: Command, options: { model?: string }) { result.code = await cmdRouteChange(runtimeFrom(ctx, this), status === "admit" ? "admitted" : "disabled", options.model); });
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
const harnessCmd = addGlobalOptions(program.command("harness").description("Detect coding-agent harnesses and authentication"));
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* This module writes only current vNext project resources:
|
|
11
11
|
* - `.kxm/agents/<role-slug>.yaml` (kxm.agent.v1)
|
|
12
12
|
* - `.kxm/workflows/<slug>.yaml` (kxm.workflow.v1)
|
|
13
|
-
* It never writes retired legacy authority (`.kxm/config`,
|
|
13
|
+
* It never writes retired legacy authority (`.kxm/config`, retired
|
|
14
|
+
* `.kxm/roster.json`) or the trusted `.kxm/roster.yaml` policy
|
|
14
15
|
* and does not use the kxm.role.v1 subsystem.
|
|
15
16
|
*/
|
|
16
17
|
|
|
@@ -8,7 +8,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
|
|
|
8
8
|
import { deliverInboxNotification } from "./inbox.ts";
|
|
9
9
|
import type { HubEvent, MessageRecord } from "./protocol.ts";
|
|
10
10
|
|
|
11
|
-
const VERSION = "0.7.
|
|
11
|
+
const VERSION = "0.7.32";
|
|
12
12
|
const inbox = new Map<string, MessageRecord>();
|
|
13
13
|
const notifiedInbox = new Set<string>();
|
|
14
14
|
let meshClient: HubClient | undefined;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { parse, stringify } from "yaml";
|
|
4
|
+
|
|
5
|
+
const RETIRED_POLICY = ".kxm/producers.yaml";
|
|
6
|
+
|
|
7
|
+
export interface RoutePolicy { schema: "kxm.routes.v2"; updatedAt: string; admitted: string[]; disabled: string[]; roles: Record<string, string[]>; }
|
|
8
|
+
const empty = (): RoutePolicy => ({ schema: "kxm.routes.v2", updatedAt: new Date().toISOString(), admitted: [], disabled: [], roles: {} });
|
|
9
|
+
export function loadRoutePolicy(root: string): RoutePolicy {
|
|
10
|
+
if (existsSync(join(root, RETIRED_POLICY))) throw new Error(`retired ${RETIRED_POLICY} present; use .kxm/routes.yaml (kxm.routes.v2)`);
|
|
11
|
+
const path = join(root, ".kxm", "routes.yaml");
|
|
12
|
+
if (!existsSync(path)) return empty();
|
|
13
|
+
const value = parse(readFileSync(path, "utf8")) as Partial<RoutePolicy>;
|
|
14
|
+
if (value?.schema !== "kxm.routes.v2" || !Array.isArray(value.admitted)) throw new Error("invalid .kxm/routes.yaml");
|
|
15
|
+
return { schema: "kxm.routes.v2", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date().toISOString(), admitted: value.admitted.filter((x): x is string => typeof x === "string"), disabled: Array.isArray(value.disabled) ? value.disabled.filter((x): x is string => typeof x === "string") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v]) => Array.isArray(v)).map(([k, v]) => [k, (v as unknown[]).filter((x): x is string => typeof x === "string")])) : {} };
|
|
16
|
+
}
|
|
17
|
+
export function updateRouteState(root: string, model: string, state: "admitted" | "disabled"): RoutePolicy {
|
|
18
|
+
return setRouteState(root, model, state);
|
|
19
|
+
}
|
|
20
|
+
export function setRouteState(root: string, model: string, state: "admitted" | "disabled", role?: string, removeRole = false): RoutePolicy {
|
|
21
|
+
const policy = loadRoutePolicy(root);
|
|
22
|
+
policy.admitted = policy.admitted.filter((x) => x !== model);
|
|
23
|
+
policy.disabled = policy.disabled.filter((x) => x !== model);
|
|
24
|
+
if (state === "admitted") policy.admitted.push(model);
|
|
25
|
+
else policy.disabled.push(model);
|
|
26
|
+
if (role) {
|
|
27
|
+
if (!/^[a-z][a-z0-9_-]{0,63}$/i.test(role)) throw new Error("invalid role id");
|
|
28
|
+
const rolePath = join(root, ".kxm", "roles", `${role}.yaml`);
|
|
29
|
+
let roleFile: Record<string, unknown> = {};
|
|
30
|
+
if (existsSync(rolePath)) {
|
|
31
|
+
const parsed = parse(readFileSync(rolePath, "utf8"));
|
|
32
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) roleFile = parsed as Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
const roster = Array.isArray(roleFile.roster) ? roleFile.roster.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
35
|
+
const existing = roster.findIndex((entry) => entry.model === model);
|
|
36
|
+
if (removeRole) { if (existing >= 0) roster.splice(existing, 1); }
|
|
37
|
+
else if (existing < 0) roster.push({ model, enabled: true });
|
|
38
|
+
roleFile.schema ??= "kxm.role.v1"; roleFile.id ??= role; roleFile.roster = roster;
|
|
39
|
+
mkdirSync(join(root, ".kxm", "roles"), { recursive: true });
|
|
40
|
+
writeFileSync(rolePath, stringify(roleFile), "utf8");
|
|
41
|
+
}
|
|
42
|
+
policy.updatedAt = new Date().toISOString();
|
|
43
|
+
mkdirSync(join(root, ".kxm"), { recursive: true });
|
|
44
|
+
writeFileSync(join(root, ".kxm", "routes.yaml"), stringify(policy), "utf8");
|
|
45
|
+
return policy;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function listRoleBindings(root: string): Record<string, string[]> {
|
|
49
|
+
const dir = join(root, ".kxm", "roles"); const result: Record<string, string[]> = {};
|
|
50
|
+
if (!existsSync(dir)) return result;
|
|
51
|
+
for (const file of readdirSync(dir).filter((name) => name.endsWith(".yaml"))) {
|
|
52
|
+
const role = file.slice(0, -5); const value = parse(readFileSync(join(dir, file), "utf8")) as { roster?: Array<{ model?: unknown }> };
|
|
53
|
+
result[role] = (value.roster ?? []).map((entry) => entry.model).filter((model): model is string => typeof model === "string");
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isRouteAdmitted(root: string, modelId: string): boolean {
|
|
59
|
+
const policy = loadRoutePolicy(root);
|
|
60
|
+
return policy.admitted.includes(modelId) && !policy.disabled.includes(modelId);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function listInventoryModels(root: string): string[] {
|
|
64
|
+
const path = join(root, ".kxm", "models", "inventory.yaml");
|
|
65
|
+
if (!existsSync(path)) return [];
|
|
66
|
+
const value = parse(readFileSync(path, "utf8")) as { models?: Array<{ id?: unknown }> };
|
|
67
|
+
return (value.models ?? []).map((m) => m.id).filter((id): id is string => typeof id === "string");
|
|
68
|
+
}
|
|
@@ -2,7 +2,7 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { parse } from "yaml";
|
|
5
|
-
import {
|
|
5
|
+
import { isRouteAdmitted, listRoleBindings } from "./routes.ts";
|
|
6
6
|
import {
|
|
7
7
|
buildFormalContextPacket,
|
|
8
8
|
buildHandoffManifest,
|
|
@@ -1317,7 +1317,7 @@ function resolveProducerRoute(
|
|
|
1317
1317
|
};
|
|
1318
1318
|
}
|
|
1319
1319
|
|
|
1320
|
-
if (!
|
|
1320
|
+
if (!isRouteAdmitted(projectRoot, selector)) {
|
|
1321
1321
|
return {
|
|
1322
1322
|
error: {
|
|
1323
1323
|
reason: "step_unsupported",
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
type VnextRuntimeContext,
|
|
24
24
|
} from "./vnext-runtime.ts";
|
|
25
25
|
import { createVnextOneShotProducer } from "./vnext-oneshot-producer.ts";
|
|
26
|
-
import {
|
|
26
|
+
import { isRouteAdmitted } from "./routes.ts";
|
|
27
27
|
import { VnextRunScheduler, createVnextSimulatedProducer, recordDriveReceipt, recoverVnextRun, vnextDrivePollProjection } from "./vnext-engine.ts";
|
|
28
28
|
import { vnextDriveSession, vnextOpenDriveSessions } from "./vnext-runtime-owner.ts";
|
|
29
29
|
|
|
@@ -487,7 +487,7 @@ async function startVnextRuntimeSupervisorInner(
|
|
|
487
487
|
const value = model as Record<string, unknown>;
|
|
488
488
|
const provider = typeof value.provider === "string" ? value.provider : undefined;
|
|
489
489
|
const modelName = typeof value.model === "string" ? value.model : undefined;
|
|
490
|
-
if (!provider || !modelName || !
|
|
490
|
+
if (!provider || !modelName || !isRouteAdmitted(projectRoot, `${provider}/${modelName}`)) {
|
|
491
491
|
throw new Error("producer_route_not_admitted");
|
|
492
492
|
}
|
|
493
493
|
return { provider, model: modelName };
|
|
@@ -5,7 +5,7 @@ operator settings, live registry identities, or admission.
|
|
|
5
5
|
|
|
6
6
|
- Live project models remain `kxm.model.v1` under `schemas/vnext/`.
|
|
7
7
|
- Live CLI roles remain `kxm.role.v1`.
|
|
8
|
-
- Runner admission remains `.kxm/roster.
|
|
8
|
+
- Runner admission remains `.kxm/roster.yaml` via the trusted control Git loader.
|
|
9
9
|
- This directory is not discovered as a vNext project resource.
|
|
10
10
|
|
|
11
11
|
Pure validation lives in `plugins/kxm/src/policy-draft.mjs`
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/** Unwired developer policy foundation. No dispatch or acceptance authority. */
|
|
2
2
|
import { execFileSync } from 'node:child_process';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
|
-
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { existsSync, lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import YAML from 'yaml';
|
|
7
8
|
import { NATIVE_PI_BRAKE_PROVIDERS, PI_ALLOWED_PROVIDERS, PI_ANTIGRAVITY_MODEL_ID, PI_NATIVE_VENDOR_PROVIDERS, ROUTES } from './harness-run.mjs';
|
|
8
9
|
|
|
9
10
|
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
-
const POLICY = '.kxm/roster.
|
|
11
|
+
const POLICY = '.kxm/roster.yaml';
|
|
12
|
+
const RETIRED_POLICY = '.kxm/roster.json';
|
|
11
13
|
const TRUSTED = 'refs/remotes/origin/main';
|
|
12
14
|
const ROLES = ['writer', 'planner', 'reviewer-arch', 'reviewer-cli', 'experiment'];
|
|
13
15
|
const ALIASES = Object.freeze({ 'x-ai': 'xai', moonshotai: 'moonshot', 'google-ai': 'google', qwen: 'alibaba' });
|
|
@@ -54,6 +56,7 @@ function frozen(value) {
|
|
|
54
56
|
return value;
|
|
55
57
|
}
|
|
56
58
|
function control() {
|
|
59
|
+
if (existsSync(path.join(ROOT, RETIRED_POLICY))) refuse(`retired ${RETIRED_POLICY} present; use ${POLICY}`);
|
|
57
60
|
if (realpathSync(gitText('rev-parse', '--show-toplevel')) !== realpathSync(ROOT)) refuse('module is outside its control repository');
|
|
58
61
|
const head = gitText('rev-parse', '--verify', 'HEAD^{commit}');
|
|
59
62
|
const trusted = gitText('rev-parse', '--verify', `${TRUSTED}^{commit}`);
|
|
@@ -159,7 +162,8 @@ export function validateRosterDocument(policy, readBlob) {
|
|
|
159
162
|
}
|
|
160
163
|
function validate(bytes, commit) {
|
|
161
164
|
let policy;
|
|
162
|
-
try { policy =
|
|
165
|
+
try { policy = YAML.parse(bytes.toString('utf8')); } catch { refuse('invalid policy YAML'); }
|
|
166
|
+
if (policy === undefined || policy === null || typeof policy !== 'object' || Array.isArray(policy)) refuse('invalid policy YAML');
|
|
163
167
|
return validateRosterDocument(policy, source => blobAt(commit, source).bytes);
|
|
164
168
|
}
|
|
165
169
|
export function loadTrustedRosterPolicy() {
|
package/.kxm/producers.yaml
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
schema: kxm.producers.v1
|
|
2
|
-
updatedAt: 2026-09-16T14:55:00.000Z
|
|
3
|
-
promoted:
|
|
4
|
-
- anthropic/fable
|
|
5
|
-
- openai/gpt-5.6-sol
|
|
6
|
-
- xai/grok-4.6
|
|
7
|
-
- openrouter/qwen/qwen3-coder-plus
|
|
8
|
-
# Qualified 2026-09-16 from the refreshed catalog (.kxm/models/inventory.yaml,
|
|
9
|
-
# fetchedAt 2026-09-16T14:54:53Z). Quality evidence (side-by-side runs) is
|
|
10
|
-
# queued — pending OpenRouter auth; not in any role rotation yet.
|
|
11
|
-
- openrouter/qwen/qwen3.8-flash # 1M ctx, thinking; $0.15/M in, $0.47/M out
|
|
12
|
-
- openrouter/z-ai/glm-5.3-flash # 1.31M ctx, thinking; $0.09/M in, $0.30/M out
|
|
13
|
-
# Qualified 2026-09-16 via live probes on the native z.ai coding plan
|
|
14
|
-
# (provider zai-coding-cn, subscription/unmetered): smoke + repo-shaped edit
|
|
15
|
-
# probe + vision probe (screenshot color read) all passed. glm-5.3-highspeed
|
|
16
|
-
# and glm-5v-turbo NOT admitted: plan returns 429 1311 (not in subscription).
|
|
17
|
-
# glm-4.6v verified for vision but not admitted (superseded by 5.3-flash).
|
|
18
|
-
- zai-coding-cn/glm-5.3 # 1M ctx, thinking; coding-plan subscription
|
|
19
|
-
- zai-coding-cn/glm-5.3-flash # 1M ctx, thinking, vision; coding-plan subscription
|
|
20
|
-
# Qualified 2026-09-16 via live probes (smoke; subscription/unmetered):
|
|
21
|
-
# agy = Antigravity CLI (google sub, Gemini kebab ids bake in the effort tier;
|
|
22
|
-
# harness default is flash-high). qwen-token-plan edit probe passed on
|
|
23
|
-
# qwen3.8-flash. deepseek-v4.1-flash and glm-5.x on qwen-token-plan are
|
|
24
|
-
# third-party models hosted by that plan.
|
|
25
|
-
- google/gemini-3.8-flash-medium # agy harness, 1M ctx
|
|
26
|
-
- google/gemini-3.8-flash-high # agy harness, 1M ctx; oneshot default for agy
|
|
27
|
-
- qwen-token-plan/qwen3.8-flash # 1M ctx, thinking, vision; edit probe passed
|
|
28
|
-
- qwen-token-plan/qwen3.8-max # 1M ctx, thinking, vision
|
|
29
|
-
- qwen-token-plan/deepseek-v4.1-flash # 1M ctx, thinking, vision
|
|
30
|
-
demoted: []
|
|
31
|
-
enabled:
|
|
32
|
-
- anthropic/fable
|
|
33
|
-
- openai/gpt-5.6-sol
|
|
34
|
-
- xai/grok-4.6
|
|
35
|
-
- openrouter/qwen/qwen3-coder-plus
|
|
36
|
-
- openrouter/qwen/qwen3.8-flash
|
|
37
|
-
- openrouter/z-ai/glm-5.3-flash
|
|
38
|
-
- zai-coding-cn/glm-5.3
|
|
39
|
-
- zai-coding-cn/glm-5.3-flash
|
|
40
|
-
- google/gemini-3.8-flash-medium
|
|
41
|
-
- google/gemini-3.8-flash-high
|
|
42
|
-
- qwen-token-plan/qwen3.8-flash
|
|
43
|
-
- qwen-token-plan/qwen3.8-max
|
|
44
|
-
- qwen-token-plan/deepseek-v4.1-flash
|
|
45
|
-
disabled: []
|
|
46
|
-
roles:
|
|
47
|
-
implementer:
|
|
48
|
-
- xai/grok-4.6
|
|
49
|
-
- openrouter/qwen/qwen3-coder-plus
|
|
50
|
-
critic-arch:
|
|
51
|
-
- anthropic/fable
|
|
52
|
-
critic-cli:
|
|
53
|
-
- openai/gpt-5.6-sol
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { parse, stringify } from "yaml";
|
|
4
|
-
|
|
5
|
-
export interface ProducerPolicy { schema: "kxm.producers.v1"; updatedAt: string; promoted: string[]; demoted: string[]; enabled: string[]; disabled: string[]; roles: Record<string, string[]>; }
|
|
6
|
-
const empty = (): ProducerPolicy => ({ schema: "kxm.producers.v1", updatedAt: new Date().toISOString(), promoted: [], demoted: [], enabled: [], disabled: [], roles: {} });
|
|
7
|
-
export function loadProducerPolicy(root: string): ProducerPolicy {
|
|
8
|
-
const path = join(root, ".kxm", "producers.yaml");
|
|
9
|
-
if (!existsSync(path)) return empty();
|
|
10
|
-
const value = parse(readFileSync(path, "utf8")) as Partial<ProducerPolicy>;
|
|
11
|
-
if (value?.schema !== "kxm.producers.v1" || !Array.isArray(value.promoted) || !Array.isArray(value.demoted)) throw new Error("invalid .kxm/producers.yaml");
|
|
12
|
-
return { schema: "kxm.producers.v1", updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : new Date().toISOString(), promoted: value.promoted.filter((x): x is string => typeof x === "string"), demoted: value.demoted.filter((x): x is string => typeof x === "string"), enabled: Array.isArray(value.enabled) ? value.enabled.filter((x): x is string => typeof x === "string") : [], disabled: Array.isArray(value.disabled) ? value.disabled.filter((x): x is string => typeof x === "string") : [], roles: value.roles && typeof value.roles === "object" ? Object.fromEntries(Object.entries(value.roles).filter(([, v]) => Array.isArray(v)).map(([k, v]) => [k, (v as unknown[]).filter((x): x is string => typeof x === "string")])) : {} };
|
|
13
|
-
}
|
|
14
|
-
export function updateProducer(root: string, model: string, status: "promoted" | "demoted"): ProducerPolicy {
|
|
15
|
-
const policy = loadProducerPolicy(root);
|
|
16
|
-
policy.promoted = policy.promoted.filter((x) => x !== model);
|
|
17
|
-
policy.demoted = policy.demoted.filter((x) => x !== model);
|
|
18
|
-
policy[status].push(model);
|
|
19
|
-
policy.updatedAt = new Date().toISOString();
|
|
20
|
-
mkdirSync(join(root, ".kxm"), { recursive: true });
|
|
21
|
-
writeFileSync(join(root, ".kxm", "producers.yaml"), stringify(policy), "utf8");
|
|
22
|
-
return policy;
|
|
23
|
-
}
|
|
24
|
-
export function setModelState(root: string, model: string, state: "enabled" | "disabled", role?: string, removeRole = false): ProducerPolicy {
|
|
25
|
-
const policy = loadProducerPolicy(root);
|
|
26
|
-
policy.enabled = policy.enabled.filter((x) => x !== model);
|
|
27
|
-
policy.disabled = policy.disabled.filter((x) => x !== model);
|
|
28
|
-
policy[state].push(model);
|
|
29
|
-
if (role) {
|
|
30
|
-
if (!/^[a-z][a-z0-9_-]{0,63}$/i.test(role)) throw new Error("invalid role id");
|
|
31
|
-
const rolePath = join(root, ".kxm", "roles", `${role}.yaml`);
|
|
32
|
-
let roleFile: Record<string, unknown> = {};
|
|
33
|
-
if (existsSync(rolePath)) {
|
|
34
|
-
const parsed = parse(readFileSync(rolePath, "utf8"));
|
|
35
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) roleFile = parsed as Record<string, unknown>;
|
|
36
|
-
}
|
|
37
|
-
const roster = Array.isArray(roleFile.roster) ? roleFile.roster.filter((entry): entry is Record<string, unknown> => Boolean(entry) && typeof entry === "object" && !Array.isArray(entry)) : [];
|
|
38
|
-
const existing = roster.findIndex((entry) => entry.model === model);
|
|
39
|
-
if (removeRole) { if (existing >= 0) roster.splice(existing, 1); }
|
|
40
|
-
else if (existing < 0) roster.push({ model, enabled: true });
|
|
41
|
-
roleFile.schema ??= "kxm.role.v1"; roleFile.id ??= role; roleFile.roster = roster;
|
|
42
|
-
mkdirSync(join(root, ".kxm", "roles"), { recursive: true });
|
|
43
|
-
writeFileSync(rolePath, stringify(roleFile), "utf8");
|
|
44
|
-
}
|
|
45
|
-
policy.updatedAt = new Date().toISOString();
|
|
46
|
-
mkdirSync(join(root, ".kxm"), { recursive: true });
|
|
47
|
-
writeFileSync(join(root, ".kxm", "producers.yaml"), stringify(policy), "utf8");
|
|
48
|
-
return policy;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function listRoleBindings(root: string): Record<string, string[]> {
|
|
52
|
-
const dir = join(root, ".kxm", "roles"); const result: Record<string, string[]> = {};
|
|
53
|
-
if (!existsSync(dir)) return result;
|
|
54
|
-
for (const file of readdirSync(dir).filter((name) => name.endsWith(".yaml"))) {
|
|
55
|
-
const role = file.slice(0, -5); const value = parse(readFileSync(join(dir, file), "utf8")) as { roster?: Array<{ model?: unknown }> };
|
|
56
|
-
result[role] = (value.roster ?? []).map((entry) => entry.model).filter((model): model is string => typeof model === "string");
|
|
57
|
-
}
|
|
58
|
-
return result;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function isProducerAdmitted(root: string, modelId: string): boolean {
|
|
62
|
-
const policy = loadProducerPolicy(root);
|
|
63
|
-
return policy.enabled.includes(modelId) && policy.promoted.includes(modelId) && !policy.disabled.includes(modelId) && !policy.demoted.includes(modelId);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function listInventoryModels(root: string): string[] {
|
|
67
|
-
const path = join(root, ".kxm", "models", "inventory.yaml");
|
|
68
|
-
if (!existsSync(path)) return [];
|
|
69
|
-
const value = parse(readFileSync(path, "utf8")) as { models?: Array<{ id?: unknown }> };
|
|
70
|
-
return (value.models ?? []).map((m) => m.id).filter((id): id is string => typeof id === "string");
|
|
71
|
-
}
|