@kontextmind/kxm 0.7.185 → 0.7.187
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 +1 -1
- package/.kxm/roles/writer.yaml +1 -1
- package/CHANGELOG.md +18 -0
- package/docs/contributing/assignment-runner.md +2 -2
- package/docs/contributing/ci-and-release.md +2 -1
- package/docs/contributing/development.md +22 -0
- package/docs/contributing/harness-routing-internals.md +12 -12
- package/docs/glossary.md +1 -1
- package/docs/guides/governed-skills.md +1 -1
- package/docs/guides/pi-workers.md +2 -2
- package/docs/guides/provenance-gates.md +1 -1
- package/docs/operations/troubleshooting.md +1 -1
- package/docs/reference/cli-reference.md +11 -11
- package/docs/reference/config-reference.md +44 -10
- package/docs/reference/configuration.md +2 -2
- package/docs/reference/harness-routing.md +27 -12
- package/package.json +1 -1
- package/packages/core/tui/dist/index.js +1 -1
- package/packages/core/tui/src/tui/optimizer.ts +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +47 -28
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +6 -2
- package/plugins/kxm/dist/runtime.js +7 -3
- package/plugins/kxm/dist/server.js +0 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/system.ts +1 -1
- package/plugins/kxm/src/init-guide-setup.ts +9 -9
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/model-catalog-refresh.ts +572 -0
- package/plugins/kxm/src/model-inventory.ts +42 -15
- package/plugins/kxm/src/oneshot-producer.ts +2 -2
- package/plugins/kxm/src/pi-producer.ts +1 -1
- package/plugins/kxm/src/subagent-control.ts +1 -1
- package/plugins/kxm/src/workforce-names.mjs +12 -4
- package/scripts/harness-run.d.mts +1 -1
- package/scripts/harness-run.mjs +2 -2
- package/scripts/refresh-model-catalog.mjs +67 -0
|
@@ -77,7 +77,7 @@ export const SUBAGENT_TYPES: readonly SubagentType[] = Object.freeze([
|
|
|
77
77
|
|
|
78
78
|
export const DEFAULT_SUBAGENT_MODELS: Readonly<Record<SubagentType, string>> = Object.freeze({
|
|
79
79
|
general: "grok/grok-4.6",
|
|
80
|
-
Explore: "openrouter/qwen/qwen3-
|
|
80
|
+
Explore: "openrouter/qwen/qwen3.7-plus",
|
|
81
81
|
Plan: "claude/fable",
|
|
82
82
|
Reviewer: "openai/gpt-5.6-sol",
|
|
83
83
|
Auditor: "claude/fable",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
* the assignment runner reads at one commit. A tree that has already renamed
|
|
13
13
|
* the files still accepts the old id. A mixed tree accepts either side.
|
|
14
14
|
* `opus-claude` is not renamed: that route pointed at an unadmitted model and
|
|
15
|
-
* was removed.
|
|
15
|
+
* was removed. `qwen-openrouter-pi` is not renamed either: it belonged to
|
|
16
|
+
* retired `pi-qwen3-coder-plus-openrouter` and does not resolve to
|
|
17
|
+
* `pi-qwen3-7-plus-openrouter`.
|
|
16
18
|
*/
|
|
17
19
|
|
|
18
20
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
@@ -32,7 +34,6 @@ export const ROLE_IDS = Object.freeze([
|
|
|
32
34
|
/** Old id, canonical id. Listed once; lookup tries both directions. */
|
|
33
35
|
export const ROUTE_RENAMES = Object.freeze([
|
|
34
36
|
["grok-native", "grok-grok-4-7"],
|
|
35
|
-
["qwen-openrouter-pi", "pi-qwen3-coder-plus-openrouter"],
|
|
36
37
|
["gemini-agy", "agy-gemini-4-argon-high"],
|
|
37
38
|
["fable-claude", "claude-fable"],
|
|
38
39
|
// sol-codex stays the GPT-5.6 route. codex-gpt-6-1-sol is a separate route.
|
|
@@ -191,7 +192,9 @@ function selectorsFor(doc) {
|
|
|
191
192
|
|
|
192
193
|
/**
|
|
193
194
|
* Lint one project's `.kxm` workforce. Errors fail CI. An admitted model with
|
|
194
|
-
* no route is a warning.
|
|
195
|
+
* no route is a warning. A model file with `status: retired` may stay off
|
|
196
|
+
* every roster and off `.kxm/routes.yaml`; a roster that still names it is
|
|
197
|
+
* `route_not_admitted`. Alias declarations are not uses; a roster or step
|
|
195
198
|
* that still cites an old id is a `deprecated_id` warning.
|
|
196
199
|
*/
|
|
197
200
|
export function lintWorkforce(root) {
|
|
@@ -257,9 +260,14 @@ export function lintWorkforce(root) {
|
|
|
257
260
|
if (!expected || model.id !== expected) {
|
|
258
261
|
push(errors, "error", "route_id_convention", `.kxm/models/${model.id}.yaml`, `route id '${model.id}' must be '${expected ?? "<harness>-<model-slug>[-<provider>]"}'`);
|
|
259
262
|
}
|
|
260
|
-
|
|
263
|
+
const retired = doc.status === "retired";
|
|
264
|
+
if (retired && used.has(model.id)) {
|
|
265
|
+
push(errors, "error", "route_not_admitted", `.kxm/models/${model.id}.yaml`, `retired route '${model.id}' is still on a role roster`);
|
|
266
|
+
}
|
|
267
|
+
if (!retired && !used.has(model.id)) {
|
|
261
268
|
push(errors, "error", "route_unused", `.kxm/models/${model.id}.yaml`, `route '${model.id}' is not on any role roster`);
|
|
262
269
|
}
|
|
270
|
+
if (retired) continue;
|
|
263
271
|
const selectorHit = selectorsFor(doc).find((selector) => admitted.includes(selector));
|
|
264
272
|
if (!selectorHit) {
|
|
265
273
|
push(errors, "error", "route_not_admitted", `.kxm/models/${model.id}.yaml`, `model '${doc.model ?? ""}' is not an admitted selector`);
|
|
@@ -19,7 +19,7 @@ export const PI_ALLOWED_PROVIDERS: readonly string[];
|
|
|
19
19
|
export const PI_NATIVE_VENDOR_PROVIDERS: Readonly<Record<string, string>>;
|
|
20
20
|
export const PI_ANTIGRAVITY_MODEL_ID: RegExp;
|
|
21
21
|
export const PI_NOUS_PORTAL_HY4: "nous-portal/tencent/hy4-preview";
|
|
22
|
-
export const PI_ADMITTED_WRITER: "openrouter/qwen/qwen3-
|
|
22
|
+
export const PI_ADMITTED_WRITER: "openrouter/qwen/qwen3.7-plus";
|
|
23
23
|
export const ROUTES: Record<string, {
|
|
24
24
|
provider: string;
|
|
25
25
|
roles: readonly string[];
|
package/scripts/harness-run.mjs
CHANGED
|
@@ -88,7 +88,7 @@ export const PI_ANTIGRAVITY_MODEL_ID = /^gemini-[a-z0-9.-]+$/;
|
|
|
88
88
|
export const PI_NOUS_PORTAL_HY4 = "nous-portal/tencent/hy4-preview";
|
|
89
89
|
|
|
90
90
|
/** Narrowly admitted Pi writer. Other Pi models need reviewed route admission. */
|
|
91
|
-
export const PI_ADMITTED_WRITER = "openrouter/qwen/qwen3-
|
|
91
|
+
export const PI_ADMITTED_WRITER = "openrouter/qwen/qwen3.7-plus";
|
|
92
92
|
|
|
93
93
|
const REQUEST_KEYS = Object.freeze([
|
|
94
94
|
"schema",
|
|
@@ -1555,7 +1555,7 @@ export function preflightRequest(request) {
|
|
|
1555
1555
|
}
|
|
1556
1556
|
}
|
|
1557
1557
|
if (request.role === "writer" && (request.model !== PI_ADMITTED_WRITER || request.permission !== "edit")) {
|
|
1558
|
-
throw failClosed("pi writer refuses unsupported route; allows only authenticated openrouter/qwen/qwen3-
|
|
1558
|
+
throw failClosed("pi writer refuses unsupported route; allows only authenticated openrouter/qwen/qwen3.7-plus with edit permission; other models need reviewed route admission");
|
|
1559
1559
|
}
|
|
1560
1560
|
const segment = piProviderOf(piModelId(request.model));
|
|
1561
1561
|
const vendor = segment && Object.hasOwn(PI_VENDOR_SEGMENT_ALIASES, segment) ? PI_VENDOR_SEGMENT_ALIASES[segment] : segment;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Refresh .kxm/prices.yaml and the OpenRouter slice of .kxm/models/inventory.yaml.
|
|
3
|
+
// Usage:
|
|
4
|
+
// node --disable-warning=ExperimentalWarning --experimental-strip-types scripts/refresh-model-catalog.mjs [--dry-run] [--json] [--date YYYY-MM-DD] [--root dir] [--openrouter-file path]
|
|
5
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { OPENROUTER_MODELS_URL, loadCatalogReferences, planModelCatalogRefresh, readCatalogFiles } from "../plugins/kxm/src/model-catalog-refresh.ts";
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const option = (name) => {
|
|
11
|
+
const index = args.indexOf(name);
|
|
12
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
13
|
+
};
|
|
14
|
+
const dryRun = args.includes("--dry-run");
|
|
15
|
+
const json = args.includes("--json");
|
|
16
|
+
const root = resolve(option("--root") ?? process.cwd());
|
|
17
|
+
const date = option("--date") ?? new Date().toISOString().slice(0, 10);
|
|
18
|
+
const fixture = option("--openrouter-file");
|
|
19
|
+
|
|
20
|
+
async function loadOpenRouter() {
|
|
21
|
+
if (fixture) return JSON.parse(readFileSync(resolve(fixture), "utf8"));
|
|
22
|
+
const url = process.env.KXM_OPENROUTER_MODELS_URL?.trim() || OPENROUTER_MODELS_URL;
|
|
23
|
+
const headers = { Accept: "application/json" };
|
|
24
|
+
if (process.env.OPENROUTER_API_KEY?.trim()) headers.Authorization = `Bearer ${process.env.OPENROUTER_API_KEY.trim()}`;
|
|
25
|
+
const response = await fetch(url, { headers, signal: AbortSignal.timeout(60_000) });
|
|
26
|
+
if (!response.ok) throw new Error(`OpenRouter models request failed: HTTP ${response.status}`);
|
|
27
|
+
const payload = await response.json();
|
|
28
|
+
if (!payload || typeof payload !== "object" || !Array.isArray(payload.data)) {
|
|
29
|
+
throw new Error("OpenRouter models response has no data array");
|
|
30
|
+
}
|
|
31
|
+
return payload.data;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const items = await loadOpenRouter();
|
|
35
|
+
const catalog = readCatalogFiles(root);
|
|
36
|
+
const plan = planModelCatalogRefresh({
|
|
37
|
+
...catalog,
|
|
38
|
+
openRouterItems: items,
|
|
39
|
+
references: loadCatalogReferences(root),
|
|
40
|
+
date,
|
|
41
|
+
openRouterUrl: process.env.KXM_OPENROUTER_MODELS_URL?.trim() || OPENROUTER_MODELS_URL,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (!dryRun && plan.changed) {
|
|
45
|
+
writeFileSync(resolve(root, ".kxm", "models", "inventory.yaml"), plan.inventoryText);
|
|
46
|
+
writeFileSync(resolve(root, ".kxm", "prices.yaml"), plan.pricesText);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const report = {
|
|
50
|
+
ok: !plan.guardFailed,
|
|
51
|
+
changed: plan.changed,
|
|
52
|
+
guardFailed: plan.guardFailed,
|
|
53
|
+
dryRun,
|
|
54
|
+
date,
|
|
55
|
+
added: plan.added,
|
|
56
|
+
removed: plan.removed,
|
|
57
|
+
repriced: plan.repriced.map((row) => row.id),
|
|
58
|
+
priceRepriced: plan.priceRepriced.map((row) => row.id),
|
|
59
|
+
retained: plan.retained.map((row) => row.id),
|
|
60
|
+
flaggedRepriced: plan.flaggedRepriced.map((row) => row.id),
|
|
61
|
+
summary: plan.summary,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (json) console.log(JSON.stringify(report, null, 2));
|
|
65
|
+
else console.log(plan.summary);
|
|
66
|
+
|
|
67
|
+
process.exitCode = plan.guardFailed ? 2 : 0;
|