@kontextmind/kxm 0.7.111 → 0.7.115

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.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.111",
14
+ "version": "0.7.115",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
@@ -3,7 +3,7 @@ purpose: Implement the approved change within the declared repository scope.
3
3
  harness: grok
4
4
  model:
5
5
  provider: xai
6
- model: grok-4.6
6
+ model: grok-4.7
7
7
  tools:
8
8
  preset: workspace-writer
9
9
  defaultRepositoryAccess: none
@@ -5,7 +5,7 @@ id: writer
5
5
  # Effort default: medium for implementation (AGENTS.md). agy ids bake in the
6
6
  # effort tier, so those entries carry no effort field.
7
7
  roster:
8
- - model: xai/grok-4.6
8
+ - model: xai/grok-4.7
9
9
  effort: medium
10
10
  enabled: true
11
11
  - model: openrouter/qwen/qwen3-coder-plus
package/CHANGELOG.md CHANGED
@@ -81,6 +81,12 @@ All notable user-facing changes are documented here. The project follows [Semant
81
81
 
82
82
  ### Changed
83
83
 
84
+ - **Windows Validate legs run again on GitHub-hosted `windows-latest`.** The PR gate
85
+ still requires only `Validate (linux, Node 22.19.0)` and `Validate (linux, Node 24)`
86
+ on ARC `kontextmind-doks`. Windows Node 22.19.0 and 24 report `validate:pr` but are
87
+ not protect-main required checks. Nightly complete coverage and release stay on
88
+ Linux. See [CI and release](docs/contributing/ci-and-release.md).
89
+
84
90
  - **The rotation surface drive uses is the one setup writes.** Guide setup appends only
85
91
  reviewed selectors to `.kxm/routes.yaml`. It skips Google guide candidates: Google's
86
92
  route is the Pi `antigravity` provider, which a Runtime drive cannot reach yet, so a
@@ -33,7 +33,7 @@ permissions:
33
33
 
34
34
  | Role | Admitted route (harness / model) | Vendor | Permission |
35
35
  |---|---|---|---|
36
- | `writer` | `grok` / `grok-4.6`; relief: `pi` / `openrouter/qwen/qwen3-coder-plus` | `xai`; `alibaba` | `edit` |
36
+ | `writer` | `grok` / `grok-4.7`; relief: `pi` / `openrouter/qwen/qwen3-coder-plus` | `xai`; `alibaba` | `edit` |
37
37
  | `planner` | `claude` / `fable` | `anthropic` | `read-only` |
38
38
  | `reviewer-arch` | `claude` / `fable` | `anthropic` | `read-only` |
39
39
  | `reviewer-cli` | `codex` / `gpt-5.6-sol` | `openai` | `read-only` |
@@ -115,7 +115,7 @@ Write a closed `kxm.assignment.v1` manifest. Unknown keys are refused.
115
115
  "assignment_id": "asg-writer-1",
116
116
  "kind": "implement",
117
117
  "harness": "grok",
118
- "model": "grok-4.6",
118
+ "model": "grok-4.7",
119
119
  "effort": "medium",
120
120
  "permission": "edit",
121
121
  "cwd": "/abs/kxm-fix-improve-sources",
@@ -247,7 +247,7 @@ then is that `BLOCK` resolved.
247
247
  ├── output-schema.json # the structured-output schema given to the harness
248
248
  ├── pre-dispatch.json # written before spawn (kxm.assignment-dispatch.v1)
249
249
  ├── completion.json # kxm.assignment-completion.v1, or refusal.json
250
- ├── routing-record.json # read by kxm routing report
250
+ ├── routing-record.json # export with `node scripts/assignment-run.mjs routing-export --task-dir <abs>` and pass the JSONL to `kxm improve report --file` / `kxm routing report --file`
251
251
  ├── telemetry.jsonl # usage, latency and cost
252
252
  ├── runner-errors.jsonl # bounded failure codes
253
253
  ├── witness/ # latest.json and history/<receipt-id>.json
@@ -24,10 +24,11 @@ flowchart LR
24
24
  MAIN -->|"daily 04:00 UTC"| NIGHT[Nightly<br/>complete suite]
25
25
  ```
26
26
 
27
- All workflows live in `.github/workflows/`. Every job runs on the organization's
28
- ARC runner scale set, and `test/core/ci-contract.test.ts` pins the
29
- runner selector, job names, coverage floors and release triggers. Change a
30
- workflow and that test together.
27
+ All workflows live in `.github/workflows/`. Linux jobs run on the organization's
28
+ ARC runner scale set. Validate also runs two Windows legs on GitHub-hosted
29
+ `windows-latest`. `test/core/ci-contract.test.ts` pins the runner selectors, job
30
+ names, coverage floors and release triggers. Change a workflow and that test
31
+ together.
31
32
 
32
33
  ## What runs where
33
34
 
@@ -64,11 +65,14 @@ Three differences matter when a check fails on one side only:
64
65
 
65
66
  ### Validate matrix and required checks
66
67
 
67
- The Validate job runs on Node 22.19.0 and Node 24, on Linux, with a
68
- three-minute job timeout. The branch ruleset
69
- requires the job names `Validate (linux, Node 22.19.0)` and
70
- `Validate (linux, Node 24)`, so renaming the job or the matrix means updating
71
- the ruleset in the same change. A newer push cancels an older pull request run;
68
+ The Validate job runs on Node 22.19.0 and Node 24, on Linux and Windows. Linux
69
+ uses the ARC scale set `kontextmind-doks` with a three-minute job timeout.
70
+ Windows uses GitHub-hosted `windows-latest` with a fifteen-minute timeout so
71
+ `npm ci` can finish. The branch ruleset requires the job names
72
+ `Validate (linux, Node 22.19.0)` and `Validate (linux, Node 24)` only; the
73
+ Windows names are reported but not required, so a Windows-only failure does not
74
+ block merge. Renaming a required Linux job or the matrix means updating the
75
+ ruleset in the same change. A newer push cancels an older pull request run;
72
76
  runs on `main` are never cancelled.
73
77
 
74
78
  ### CI jobs stay queued while a runner is online
@@ -106,9 +110,11 @@ autoscaler. Do not relabel `km-gh-rn01` or push an empty commit as a routing
106
110
  workaround.
107
111
 
108
112
  > [!NOTE]
109
- > Windows legs are paused, not removed. Windows stays a supported target, and
110
- > Windows-specific fixtures (for example the `pi.cmd` worker launch in
111
- > `test/core/worker.test.ts`) run when you test on Windows locally.
113
+ > Windows Validate uses GitHub-hosted `windows-latest`, not a self-hosted
114
+ > homelab runner and not the ARC scale set. Do not add a `kontextmind-doks`
115
+ > label to a Windows runner. Nightly complete coverage and release stay on
116
+ > Linux. Windows-specific fixtures (for example the `pi.cmd` worker launch in
117
+ > `test/core/worker.test.ts`) also run in the hosted Windows Validate legs.
112
118
 
113
119
  ### The docs-only classifier
114
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.111",
3
+ "version": "0.7.115",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -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.111",
5
+ "version": "0.7.115",
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",
@@ -17313,7 +17313,7 @@ function sessionTokenFixHint(policy) {
17313
17313
  }
17314
17314
 
17315
17315
  // plugins/kxm/src/mcp-server.ts
17316
- var VERSION = "0.7.111";
17316
+ var VERSION = "0.7.115";
17317
17317
  var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
17318
17318
  var inbox = /* @__PURE__ */ new Map();
17319
17319
  var notifiedInbox = /* @__PURE__ */ new Set();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.111",
3
+ "version": "0.7.115",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -11,7 +11,7 @@ import { deliverInboxNotification } from "./inbox.ts";
11
11
  import type { HubEvent, MessageRecord } from "./protocol.ts";
12
12
  import { sessionTokenFixHint } from "./session-token-hint.ts";
13
13
 
14
- const VERSION = "0.7.111";
14
+ const VERSION = "0.7.115";
15
15
  const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
16
16
  const inbox = new Map<string, MessageRecord>();
17
17
  const notifiedInbox = new Set<string>();
@@ -339,6 +339,13 @@ export function changeReport(request: { taskDir: string }, deps?: Record<string,
339
339
  ranking: "not-ranked";
340
340
  orchestration_usage: "unavailable";
341
341
  };
342
+ export const ROUTING_EXPORT_SCHEMA: "kxm.assignment-routing-export.v1";
343
+ export function exportAssignmentRoutingRecords(request: { taskDir: string }, deps?: Record<string, unknown>): {
344
+ schema: "kxm.assignment-routing-export.v1";
345
+ task_id: string;
346
+ records: Array<Record<string, any>>;
347
+ skipped: Array<{ assignment_id: string | null; reason: "acceptance_record_invalid" | "no_routing_record" | "routing_record_invalid" | "manifest_invalid" }>;
348
+ };
342
349
  export function writeCurrentPlan(request: {
343
350
  taskDir: string; plan: string; sha256: string; baseCommit: string; expectedGeneration: number; settledDecisions?: string[];
344
351
  }, deps?: Record<string, unknown>): {
@@ -2330,7 +2330,7 @@ export async function observeAssignment(outputDir, deps = {}) {
2330
2330
  return appended;
2331
2331
  }
2332
2332
 
2333
- const CLI_USAGE = "usage: assignment-run.mjs run --manifest <absolute-path> | observe --record-dir <absolute-path> | witness --record-dir <absolute-path> | accept --task-dir <absolute-path> --commit <commit> --record-dir <absolute-path> --critic <absolute-path> --critic <absolute-path> [--observed-pr <id>] [--observed-ci <id>] | attribute --task-dir <absolute-path> --record-dir <absolute-path> --class <orchestration|model|environment|unclassified> --explanation-file <absolute-path> | observe-cost --task-dir <absolute-path> --input <absolute-path> | plan-current --task-dir <absolute-path> --plan <absolute-path> --sha256 <hex64> --base-commit <commit> --expected-generation <n> | change-report --task-dir <absolute-path>";
2333
+ const CLI_USAGE = "usage: assignment-run.mjs run --manifest <absolute-path> | observe --record-dir <absolute-path> | witness --record-dir <absolute-path> | accept --task-dir <absolute-path> --commit <commit> --record-dir <absolute-path> --critic <absolute-path> --critic <absolute-path> [--observed-pr <id>] [--observed-ci <id>] | attribute --task-dir <absolute-path> --record-dir <absolute-path> --class <orchestration|model|environment|unclassified> --explanation-file <absolute-path> | observe-cost --task-dir <absolute-path> --input <absolute-path> | plan-current --task-dir <absolute-path> --plan <absolute-path> --sha256 <hex64> --base-commit <commit> --expected-generation <n> | change-report --task-dir <absolute-path> | routing-export --task-dir <absolute-path>";
2334
2334
 
2335
2335
  const WITNESS_RECEIPT_KEYS = Object.freeze([
2336
2336
  "schema",
@@ -4293,8 +4293,135 @@ export function writeCurrentPlan(request, deps = {}) {
4293
4293
  } catch (error) { throw observationFailure(error, "plan_ref_invalid"); }
4294
4294
  }
4295
4295
 
4296
+ export const ROUTING_EXPORT_SCHEMA = "kxm.assignment-routing-export.v1";
4297
+
4298
+ function contractExportable(contract) {
4299
+ return Boolean(contract)
4300
+ && typeof contract === "object"
4301
+ && !Array.isArray(contract)
4302
+ && typeof contract.boundary === "string"
4303
+ && Array.isArray(contract.deliverables)
4304
+ && contract.witness
4305
+ && typeof contract.witness === "object"
4306
+ && !Array.isArray(contract.witness)
4307
+ && typeof contract.witness.id === "string"
4308
+ && Array.isArray(contract.deferred);
4309
+ }
4310
+
4311
+ export function exportAssignmentRoutingRecords(request, deps = {}) {
4312
+ const io = ioDeps(deps);
4313
+ const taskDir = observationTask(request.taskDir, io);
4314
+ const taskId = basename(taskDir);
4315
+ const skipped = [];
4316
+ const acceptedIds = new Set();
4317
+ const acceptedPath = join(taskDir, ACCEPTED_FILENAME);
4318
+ if (lstatOrNull(acceptedPath, io.lstatSync)) {
4319
+ try {
4320
+ const value = privateRecord(acceptedPath, io).value;
4321
+ const valid = value.schema === ACCEPTED_SCHEMA
4322
+ && value.task_id === taskId
4323
+ && typeof value.writer?.assignment_id === "string"
4324
+ && Array.isArray(value.critics);
4325
+ if (!valid) throw new Error("acceptance invalid");
4326
+ acceptedIds.add(value.writer.assignment_id);
4327
+ for (const critic of value.critics) {
4328
+ if (typeof critic?.assignment_id === "string") acceptedIds.add(critic.assignment_id);
4329
+ }
4330
+ } catch {
4331
+ skipped.push({ assignment_id: null, reason: "acceptance_record_invalid" });
4332
+ }
4333
+ }
4334
+ const collected = [];
4335
+ const dirs = listDirectAssignmentDirs(taskDir, io).sort((left, right) => left.assignment_id < right.assignment_id ? -1 : left.assignment_id > right.assignment_id ? 1 : 0);
4336
+ for (const { assignment_id, path } of dirs) {
4337
+ const routingPath = join(path, "routing-record.json");
4338
+ if (!lstatOrNull(routingPath, io.lstatSync)) {
4339
+ skipped.push({ assignment_id, reason: "no_routing_record" });
4340
+ continue;
4341
+ }
4342
+ let record;
4343
+ try {
4344
+ record = parseRoutingRecord(privateRecord(routingPath, io).value);
4345
+ if (record.workflowRunId !== assignment_id) throw new Error("workflowRunId");
4346
+ } catch {
4347
+ skipped.push({ assignment_id, reason: "routing_record_invalid" });
4348
+ continue;
4349
+ }
4350
+ let contract;
4351
+ try {
4352
+ const manifest = privateRecord(join(path, "manifest.json"), io).value;
4353
+ contract = manifest.contract;
4354
+ if (!contractExportable(contract)) throw new Error("contract");
4355
+ } catch {
4356
+ skipped.push({ assignment_id, reason: "manifest_invalid" });
4357
+ continue;
4358
+ }
4359
+ collected.push({ id: assignment_id, record, contract });
4360
+ }
4361
+ const supersededBy = new Map();
4362
+ for (const entry of collected) {
4363
+ const reworkOf = entry.record.providerMetadata?.rework_of;
4364
+ if (typeof reworkOf === "string" && !supersededBy.has(reworkOf)) supersededBy.set(reworkOf, entry.id);
4365
+ }
4366
+ const records = [];
4367
+ for (const entry of collected) {
4368
+ const kind = entry.record.stageId;
4369
+ const role = entry.record.agentRole;
4370
+ const askSha256 = sha256Bytes(Buffer.from(JSON.stringify(["kxm.assignment-ask.v1", kind, role])));
4371
+ const objectiveSha256 = sha256Bytes(Buffer.from(JSON.stringify([
4372
+ "kxm.assignment-brief.v1",
4373
+ kind,
4374
+ entry.contract.boundary,
4375
+ entry.contract.deliverables,
4376
+ entry.contract.witness.id,
4377
+ entry.contract.deferred,
4378
+ ])));
4379
+ const finalOutcome = acceptedIds.has(entry.id) ? "accepted" : supersededBy.has(entry.id) ? "failed" : "pending";
4380
+ try {
4381
+ records.push(parseRoutingRecord({
4382
+ ...entry.record,
4383
+ workflowRunId: taskId,
4384
+ finalOutcome,
4385
+ providerMetadata: {
4386
+ ...entry.record.providerMetadata,
4387
+ workflowId: "assignment-runner",
4388
+ askSha256,
4389
+ objectiveSha256,
4390
+ stepWrites: WRITER_KINDS.includes(kind),
4391
+ assignmentId: entry.id,
4392
+ ...(finalOutcome === "failed" ? { supersededBy: supersededBy.get(entry.id) } : {}),
4393
+ },
4394
+ }));
4395
+ } catch {
4396
+ skipped.push({ assignment_id: entry.id, reason: "routing_record_invalid" });
4397
+ }
4398
+ }
4399
+ return { schema: ROUTING_EXPORT_SCHEMA, task_id: taskId, records, skipped };
4400
+ }
4401
+
4296
4402
  export async function main(argv = process.argv, io = { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr }) {
4297
4403
  const args = argv.slice(2).filter((arg) => arg !== "--");
4404
+ if (args[0] === "routing-export") {
4405
+ try {
4406
+ const values = {};
4407
+ for (let i = 1; i < args.length; i += 2) {
4408
+ if (args[i] !== "--task-dir" || values[args[i]] !== undefined || args[i + 1] === undefined) throw failClosed("invalid report/plan arguments", "manifest_invalid");
4409
+ values[args[i]] = args[i + 1];
4410
+ }
4411
+ if (values["--task-dir"] === undefined) throw failClosed("missing report/plan arguments", "manifest_invalid");
4412
+ const result = exportAssignmentRoutingRecords({ taskDir: values["--task-dir"] });
4413
+ for (const record of result.records) io.stdout.write(`${JSON.stringify(record)}\n`);
4414
+ io.stderr.write(`${JSON.stringify({ schema: result.schema, task_id: result.task_id, records: result.records.length, skipped: result.skipped })}\n`);
4415
+ process.exitCode = 0;
4416
+ return result;
4417
+ } catch (error) {
4418
+ const failure = observationFailure(error);
4419
+ io.stderr.write(`${failure.runnerCode}: assignment routing export refused\n`);
4420
+ process.exitCode = 1;
4421
+ throw failure;
4422
+ }
4423
+ }
4424
+
4298
4425
  if (["change-report", "plan-current"].includes(args[0])) {
4299
4426
  try {
4300
4427
  const flags = args[0] === "change-report" ? ["--task-dir"] : ["--task-dir", "--plan", "--sha256", "--base-commit", "--expected-generation"];
@@ -137,7 +137,7 @@ export const ROUTES = Object.freeze({
137
137
  provider: "xai",
138
138
  roles: Object.freeze(["writer"]),
139
139
  permissions: Object.freeze(["edit"]),
140
- models: Object.freeze(["grok-4.6"]),
140
+ models: Object.freeze(["grok-4.7"]),
141
141
  efforts: Object.freeze(["low", "medium", "high"]),
142
142
  auth: Object.freeze({ args: ["models"], loginHint: "grok (OAuth to auth.x.ai)" }),
143
143
  },
@@ -153,7 +153,7 @@ export const ROUTES = Object.freeze({
153
153
  provider: "anthropic",
154
154
  roles: Object.freeze(["planner", "reviewer-arch"]),
155
155
  permissions: Object.freeze(["read-only"]),
156
- models: Object.freeze(["fable"]),
156
+ models: Object.freeze(["fable", "opus"]),
157
157
  efforts: Object.freeze(["low", "medium", "high", "xhigh", "max"]),
158
158
  auth: Object.freeze({ args: ["auth", "status"], loginHint: "claude auth login" }),
159
159
  },
@@ -8,13 +8,13 @@ import { buildArgv } from "./harness-run.mjs";
8
8
 
9
9
  // Launch intent only: these labels are not observed model identity or admission.
10
10
  export function nativeCriticLaunch(harness) {
11
- if (!["fable", "astra"].includes(harness)) {
12
- throw new Error("usage: native-critic.mjs <fable|astra> <prompt>");
11
+ if (!["fable", "astra", "opus"].includes(harness)) {
12
+ throw new Error("usage: native-critic.mjs <fable|astra|opus> <prompt>");
13
13
  }
14
- const model = harness === "fable" ? "fable" : "gpt-6-astra";
15
- const command = harness === "fable" ? "claude" : "codex";
16
- const args = harness === "fable"
17
- ? ["-p", "--model", model, "--output-format", "json", "--no-session-persistence", "--tools", "Read,Glob,Grep", "--setting-sources", "user"]
14
+ const model = harness === "fable" ? "fable" : harness === "opus" ? "opus" : "gpt-6-astra";
15
+ const command = harness === "fable" || harness === "opus" ? "claude" : "codex";
16
+ const args = harness === "fable" || harness === "opus"
17
+ ? ["-p", "--model", model, "--effort", "medium", "--output-format", "json", "--no-session-persistence", "--tools", "Read,Glob,Grep", "--setting-sources", "user"]
18
18
  : buildArgv({ harness: "codex", model, effort: "low", permission: "read-only", prompt_file: "-" });
19
19
  return { model, command, args };
20
20
  }
@@ -36,7 +36,7 @@ async function main() {
36
36
  mkdirSync(outDir, { recursive: true });
37
37
  const client = new HubClient({ serverUrl, authToken: env.KXM_AUTH_TOKEN, name: `${harness}-critic`, purpose: "independent CLI critic", project: env.KXM_PROJECT || ".kxm", model });
38
38
  const prompt = promptParts.join(" ");
39
- const agent = await client.start(() => {});
39
+ const agent = await client.start(() => { });
40
40
  const result = await new Promise((resolve) => {
41
41
  const child = spawn(command, args, { env, stdio: ["pipe", "pipe", "pipe"], shell: false });
42
42
  let stdout = "", stderr = "";