@kontextmind/kxm 0.7.193 → 0.7.195
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/CHANGELOG.md +8 -1
- package/docs/README.md +1 -0
- package/docs/audits/2026-10-gap-audit.md +87 -0
- package/docs/reference/cli-reference.md +34 -2
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +24 -8
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +2 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/vision-gate.ts +29 -8
package/CHANGELOG.md
CHANGED
|
@@ -714,6 +714,14 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
714
714
|
|
|
715
715
|
### Fixed
|
|
716
716
|
|
|
717
|
+
- **`kxm vision assert --route` selects the Pi model, and an unadmitted route names the fix.**
|
|
718
|
+
The flag used to change only the admission check. Pi still ran
|
|
719
|
+
`zai-coding-cn/glm-5.3-flash`. Another admitted id is now `pi --model <id>`
|
|
720
|
+
after the native-vendor brake. The verified default invocation is unchanged.
|
|
721
|
+
`route_not_admitted` includes the route id and `kxm routes admit --model`.
|
|
722
|
+
This checkout does not admit the default; pass
|
|
723
|
+
`--route openrouter/z-ai/glm-5.3-flash`.
|
|
724
|
+
|
|
717
725
|
- **The weekly model refresh opens its pull request with a GitHub App.**
|
|
718
726
|
`.github/workflows/refresh-models.yml` uses `actions/create-github-app-token`
|
|
719
727
|
with repository secrets `KXM_REFRESH_APP_ID` and
|
|
@@ -731,7 +739,6 @@ All notable user-facing changes are documented here. The project follows [Semant
|
|
|
731
739
|
the same). The refresh does not rewrite a shared anchor that a
|
|
732
740
|
non-OpenRouter row depends on. `.kxm/prices.yaml` is dated 2026-10-20.
|
|
733
741
|
The OpenRouter `standard` rates for those two inventory rows match.
|
|
734
|
-
|
|
735
742
|
- **`kxm improve report` and `kxm routing report` include attempts settled in a registered lane.**
|
|
736
743
|
Inside a KXM project, a present user-state `registry.db` is opened read-only at schema version 2. One query loads the home row and every lane row for that project id. Event-store paths come from the stored project keys, so a stale checkout path cannot hide a lane. A missing store stays a zero-record source. An absent registry, or a checkout that is not registered, still reads only that checkout's own store. `--file` still reads only the named file. See
|
|
737
744
|
[kxm improve report](docs/reference/cli-reference.md#kxm-improve-report).
|
package/docs/README.md
CHANGED
|
@@ -121,6 +121,7 @@ KXM connects coding agents through a durable, authenticated [hub](glossary.md#hu
|
|
|
121
121
|
| [Decisions](contributing/decisions.md) | Agents, maintainers | Medium-or-higher decisions from automated work, with weights and evidence |
|
|
122
122
|
| [Learnings](contributing/learnings.md) | Agents, maintainers | Durable lessons from running the writer, landing, and docs loops |
|
|
123
123
|
| [Planner bake-off, October 2026](evals/planner-bakeoff-2026-10.md) | Maintainers | Scored plans from two planner models on six KXM tasks, with cost and time |
|
|
124
|
+
| [October 2026 gap audit](audits/2026-10-gap-audit.md) | Maintainers | What the 2026-10-21 pass fixed, and the decisions still waiting on the operator |
|
|
124
125
|
| [Artifact templates](templates/README.md) | Workflow authors | Document templates and where workflows use them |
|
|
125
126
|
|
|
126
127
|
The templates: [feature](templates/feature.md) · [bug fix](templates/bug-fix.md) · [ADR](templates/adr.md) · [architecture](templates/architecture.md) · [research](templates/research.md) · [review](templates/review.md) · [test plan](templates/test-plan.md) · [test report](templates/test-report.md) · [runbook](templates/runbook.md) · [postmortem](templates/postmortem.md) · [handoff](templates/handoff.md).
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# October 2026 gap audit
|
|
2
|
+
|
|
3
|
+
This page records the gap audit of this repository on 2026-10-21, against `main` at `19ec8a5`. It is for the operator who still has to decide runner placement, route admission, and the witnesses already listed in Tracking. The four code fixes from this pass are separate pull requests.
|
|
4
|
+
|
|
5
|
+
The pass compared the CLI, routing, and CI workflows with the docs, the tests, and `.kxm/knowledge`. It left `.kxm/prices.yaml` and `.github/workflows/refresh-models.yml` untouched because [pull request #410](https://github.com/kontextmind/kxm/pull/410) owns those files. Playwright stays on Obscura. [Playwright e2e](https://github.com/kontextmind/kxm/blob/main/.github/workflows/e2e.yml) already runs `npm run e2e` on `ubuntu-latest`.
|
|
6
|
+
|
|
7
|
+
## What this pass fixed
|
|
8
|
+
|
|
9
|
+
Each fix is a small pull request with squash auto-merge. None of them needs a secret, a repository setting, DNS, or a runner change.
|
|
10
|
+
|
|
11
|
+
| Pull request | Fix |
|
|
12
|
+
|---|---|
|
|
13
|
+
| [#411](https://github.com/kontextmind/kxm/pull/411) | Whole-word workflow keywords in `kxm suggest` |
|
|
14
|
+
| [#412](https://github.com/kontextmind/kxm/pull/412) | `kxm vision assert --route` follows the admitted Pi model |
|
|
15
|
+
| [#413](https://github.com/kontextmind/kxm/pull/413) | Next step when Git or the hub is missing |
|
|
16
|
+
| [#414](https://github.com/kontextmind/kxm/pull/414) | A rate-limit abort still walks the fallback chain |
|
|
17
|
+
|
|
18
|
+
`kxm suggest` used to treat a keyword as a substring. "guide" and "build" took the feature workflow because they contain `ui`, "author" took vulnerability remediation because it contains `auth`, and "change" took bug-fix because it contains `hang`. Hyphenated keywords such as `circuit-breaker` still match as one token. A prompt with no keyword still selects `feature-implementation` at score 0.
|
|
19
|
+
|
|
20
|
+
`kxm vision assert` accepted `--route` and then ignored it. The flag now selects that Pi model after admission. An unknown route names `kxm routes admit`. A native-vendor id is refused before `pi` starts. The CLI reference documents the command. The built-in default route is unchanged. See decision 2.
|
|
21
|
+
|
|
22
|
+
`kxm init` outside any Git worktree now says to run `git init` and then `kxm init` again. A directory that is not the worktree root still says to run from that root. `kxm hub view` says to start the hub when nothing is listening on a loopback URL, and names the tunnel and `kxm hub bind` for any other URL. An auth proxy or redirect keeps its existing probe note.
|
|
23
|
+
|
|
24
|
+
A provider HTTP 429 whose body says the request was aborted used to classify as `cancelled`, which does not walk. It now stays `rate_limit` and walks. `AbortError`, a process abort, and a user cancel stay `cancelled`. `tool_policy` is its own class. Both non-walkable classes still stop the chain.
|
|
25
|
+
|
|
26
|
+
## What was checked and left
|
|
27
|
+
|
|
28
|
+
Product TypeScript and Python sources have no `TODO` or `FIXME` markers outside generated bundles and narrative plan text. The CLI reference already covers the registered commands. `kxm vision assert` was the missing heading, and pull request #412 adds it.
|
|
29
|
+
|
|
30
|
+
Phase pages under `docs/roadmap/phases/` are symlinks to `plans/kxm-roadmap/phases/`. Plan links such as `../../plan-lane-cli.md` resolve from the real file. A scan that does not follow the symlink reports nine missing targets under `docs/roadmap/phases/`. The generator was not changed.
|
|
31
|
+
|
|
32
|
+
`.kxm/knowledge` has ten pages, last indexed 2026-10-17. It has no first-run page, and [CI conventions](../../.kxm/knowledge/patterns/ci.md) does not describe the runner queue below. Those notes should wait until the runner decision lands, so the wiki does not record a queue that the operator is about to move.
|
|
33
|
+
|
|
34
|
+
## Decisions for the operator
|
|
35
|
+
|
|
36
|
+
Ranked by how often they block a pull request or a first run. Each one needs a setting, a host, a secret, or an admission the code must not guess.
|
|
37
|
+
|
|
38
|
+
### 1. Where pull-request Linux jobs run
|
|
39
|
+
|
|
40
|
+
Pull-request jobs that select the ARC scale set `kontextmind-doks` are Classify changes, Docs lint, both Linux engine shards, the Linux unit lanes (`serial`, `light-1`, `light-2`), Python MG1, Plugin validation, and `required`. Windows unit lanes already use `windows-latest`, and only when the classifier marks the change platform-sensitive. The Validate matrix does not run on a pull request. On `main` its Linux cells use `kontextmind-doks` with a three-minute timeout, and its Windows cells use `windows-latest` with a fifteen-minute timeout.
|
|
41
|
+
|
|
42
|
+
[CI and release](../contributing/ci-and-release.md) records the capacity policy: one warm runner, a burst to four, three CPUs each. `test/core/ci-contract.test.ts` refuses `ubuntu-latest` inside `ci.yml`, `release.yml`, `smoke.yml`, and `nightly.yml`. The same page says the Python lane stays on `kontextmind-doks` because that scale set has no container-engine socket. `scripts/ci-postgres-17.sh` installs a pinned PostgreSQL 17.11.0 linux/x86_64 archive and maps ICU 74 to the `libicu74` apt package.
|
|
43
|
+
|
|
44
|
+
Observed on 2026-10-21:
|
|
45
|
+
|
|
46
|
+
- Workflow [39513962307](https://github.com/kontextmind/kxm/actions/runs/39513962307) (pull request #410, platform-sensitive) was created at 2026-10-20T23:16:53Z. Docs lint started at 2026-10-21T00:02:27Z, about 46 minutes later. Windows lanes started at 00:03:51Z, within seconds of Classify. Linux lanes then ran mostly one at a time through about 00:20Z.
|
|
47
|
+
- Workflow [39510255392](https://github.com/kontextmind/kxm/actions/runs/39510255392) (planner fallback, code pull request) was created at 2026-10-20T22:44:14Z and completed at 2026-10-20T23:08:16Z, about 24 minutes, while the scale set was less busy.
|
|
48
|
+
|
|
49
|
+
Classify, Docs lint, the Node unit lanes, Plugin validation, and `required` do not need the cluster. Moving those jobs to `ubuntu-latest` would drop the queue and would also edit the contract test, spend GitHub-hosted minutes, and reverse the pinned-runner rule. Python can stay on the scale set, or move to a GitHub-hosted service container, which is a different script than the pinned archive. A newer image that no longer ships `libicu74` fails that archive until the pin moves. Linux Validate on `main` has a three-minute timeout that is tight for a cold `npm ci` on a GitHub-hosted runner. Do not relabel `km-gh-rn01`. That bypasses ARC and serializes the queue.
|
|
50
|
+
|
|
51
|
+
### 2. The built-in vision route
|
|
52
|
+
|
|
53
|
+
`kxm vision assert` still defaults to `zai-coding-cn/glm-5.3-flash` and, for that id only, still runs `pi --provider zai-coding-cn --model glm-5.3-flash`. `.kxm/routes.yaml` removed `zai-coding-cn/*` on 2026-09-27 because that provider is not allowlisted. The admitted replacement in this checkout is `openrouter/z-ai/glm-5.3-flash`. A vision call here with no `--route` still fail-closes, and pull request #412 names `kxm routes admit` in that message. `kxm init` does not admit a vision route.
|
|
54
|
+
|
|
55
|
+
Changing the default needs a vision accuracy witness on the admitted id. This pass kept the verified default argv.
|
|
56
|
+
|
|
57
|
+
### 3. The starter writer model
|
|
58
|
+
|
|
59
|
+
`kxm init` still admits `xai/grok-4.6`, writes `.kxm/models/grok-grok-4-6.yaml`, and puts that route on the writer roster. This repository admits `xai/grok-4.7`. `modes.ts`, `subagent-control.ts`, and the Pi producer default still name grok-4.6. Glossary and catalog examples that show grok-4.6 are selector shapes. Replacing the starter is an admission decision, with the tests and docs that pin 4.6 updated in the same change.
|
|
60
|
+
|
|
61
|
+
### 4. Confirm or reverse the defaults already in Tracking
|
|
62
|
+
|
|
63
|
+
These are already implemented. They wait on a yes, a change, or a decline. A decline is its own revert. The queue is [Still open](../../plans/implementation-plan.md#tracking-working-tree-not-a-release).
|
|
64
|
+
|
|
65
|
+
- Q-B, Q-C, and Q-E through Q-K on pull request #298: promotion policy, schema identity, evidence on repro and implementer roles, task ranking, pass semantics, coded-repeat candidacy, direct readers, log sizes, and dispatch context. Each shipped with the recommended default and is not a Decided entry.
|
|
66
|
+
- D-1: keep the nine KontextMind knowledge-plane skills, rescoped so they trigger when the user names KontextMind, or delete them. The current tree keeps them.
|
|
67
|
+
- D-2: leave `plugin.json` and `marketplace.json` at 0.7.1, so `claude plugin update` does not refresh an existing install, or bump the plugin version.
|
|
68
|
+
- A0: the live Claude Code witness for `kxm_hook_tool_failure` in an isolated `CLAUDE_CONFIG_DIR`. The tool stays unpublished until that witness passes.
|
|
69
|
+
|
|
70
|
+
### 5. Witnesses that need a host or a secret
|
|
71
|
+
|
|
72
|
+
Still open already names the owners. This pass did not schedule them.
|
|
73
|
+
|
|
74
|
+
- S5 interactive login, logout, and refresh on the deployed edge, plus the deployed restore witness that is still tied to that window.
|
|
75
|
+
- P0 cross-box `kxm peer`: box B binds hub A over an operator-owned forward and completes send, await, reply, checkpoint, and signal.
|
|
76
|
+
- Backup restore does not remap absolute paths stored under `$S`.
|
|
77
|
+
- The hub context pool reads `.kxm/memory` and skills from the hub checkout and relabels them as the caller's project.
|
|
78
|
+
- Runtime runs have no journal. `kxm workflow record` looks the id up among hub webhook runs, so a `kxm run` id returns `workflow_not_found`. A journal needs a new store, and the S0–S5 queue forbids that schema change while S5 is open.
|
|
79
|
+
- Routing and cost questions in the same Still open list: the Google route still wired as `agy` while Decided says `antigravity`, two Pi allowlists, `limits.maxModelCost` never tripping because no producer writes metered cost, and `qwen-token-plan/deepseek-v4.1-flash` admitted beside the native-vendor brake.
|
|
80
|
+
|
|
81
|
+
## Related
|
|
82
|
+
|
|
83
|
+
- [CI and release](../contributing/ci-and-release.md)
|
|
84
|
+
- [Decisions](../contributing/decisions.md)
|
|
85
|
+
- [KXM CLI reference](../reference/cli-reference.md)
|
|
86
|
+
- [Troubleshoot KXM](../operations/troubleshooting.md)
|
|
87
|
+
- [Project knowledge wiki](../../.kxm/knowledge/index.md)
|
|
@@ -13,7 +13,7 @@ Output shown under examples was captured from a source checkout, inside a throwa
|
|
|
13
13
|
- [Task to command](#task-to-command)
|
|
14
14
|
- Setup: [`init`](#kxm-init), [`config`](#kxm-config), [`completion`](#kxm-completion), [`trust`](#kxm-trust)
|
|
15
15
|
- Hub and sessions: [`hub`](#kxm-hub-view), [`session`](#kxm-session), [`dash`](#kxm-dash), [`studio`](#kxm-studio)
|
|
16
|
-
- Harnesses, models, and roles: [`harness`](#kxm-harness), [`plugin`](#kxm-plugin), [`auth`](#kxm-auth), [`update`](#kxm-update), [`models`](#kxm-models), [`routes`](#kxm-routes), [`role`](#kxm-role)
|
|
16
|
+
- Harnesses, models, and roles: [`harness`](#kxm-harness), [`plugin`](#kxm-plugin), [`auth`](#kxm-auth), [`update`](#kxm-update), [`models`](#kxm-models), [`routes`](#kxm-routes), [`vision`](#kxm-vision), [`role`](#kxm-role)
|
|
17
17
|
- Running work: [`lane`](#kxm-lane), [`land`](#kxm-land), [`assign`](#kxm-assign), [`run`](#kxm-run), [`runs`](#kxm-runs), [`docs`](#kxm-docs), [`runtime`](#kxm-runtime), [`agent`](#kxm-agent), [`workflow`](#kxm-workflow), [`gate`](#kxm-gate), [`peer`](#kxm-peer), [`task`](#kxm-task), [`goal`](#kxm-goal), [`suggest`](#kxm-suggest), [`explain`](#kxm-explain)
|
|
18
18
|
- Context and learning: [`context`](#kxm-context), [`memory`](#kxm-memory), [`skills`](#kxm-skills), [`improve`](#kxm-improve), [`routing`](#kxm-routing), [`prices`](#kxm-prices)
|
|
19
19
|
- Operations: [`backup`](#kxm-backup), [`restore`](#kxm-restore), [`tenant`](#kxm-tenant), [`ssh`](#kxm-ssh), [`help`](#kxm-help)
|
|
@@ -103,7 +103,7 @@ kxm -V
|
|
|
103
103
|
- Plan with `planned`: `backup`, `restore`, `config set`, `role add|remove|modify|resume`, `workflow add|remove|modify`, `goal create`, `task create|run|sync`, `memory note|sync`, `skills evaluate|promote|reject`, `context promote`, `context wiki-compile --out`, `auth token`, `session token`, `session brief`, and `ssh run|file|close`. Each prints its normal result plus `dryRun: true` and `planned`, a list of `{action, target}` entries whose `action` is `write`, `delete`, `move`, `request`, or `ssh`. Text mode prints `dry run: <summary>` and one indented `would <action> <target>` line per entry (`session brief` appends `dry run: would write <file>` lines to the brief instead).
|
|
104
104
|
- Plan in their own shape (described in each section): `init`, `run`, `runs drive|cancel`, `docs build|serve`, `runtime start|stop|sync-retry`, `hub start|stop|bind|unbind`, `session start|stop`, `agent worker`, `dash`, `studio serve`, `models inventory-refresh`, `routes admit|disable`, `update`, `completion install`, `workflow start|signal|export|checkpoint|record|wait`, every `peer` subcommand, `gate degrade|signal`, `skills create`, `improve report`, and `config edit`. `config edit --dry-run` prints one plain frame and writes nothing. `gate github watch --dry-run` still polls GitHub but does not post the signal.
|
|
105
105
|
- Read-only commands run as usual, without leaving a trace: a local SQLite store is opened without creating `-wal` or `-shm` files, and `runs status|list|receipt` only attach to a running Runtime supervisor. With no supervisor running they exit 2 with `dry_run_unsupported` instead of starting one. `context wiki-compile --dry-run` still asks the hub to compile, which is a read.
|
|
106
|
-
- Refused: `kxm models` (the interactive screen) and `kxm prices acknowledge` exit 2 with `dry_run_unsupported`. The CLI keeps one list of the commands that answer `--dry-run` and refuses every other command the same way before it runs, so a command added without dry-run support fails closed.
|
|
106
|
+
- Refused: `kxm models` (the interactive screen), `kxm vision assert`, and `kxm prices acknowledge` exit 2 with `dry_run_unsupported`. The CLI keeps one list of the commands that answer `--dry-run` and refuses every other command the same way before it runs, so a command added without dry-run support fails closed.
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
109
|
kxm config set user.theme light --dry-run
|
|
@@ -908,6 +908,38 @@ kxm studio serve --port 5000
|
|
|
908
908
|
|
|
909
909
|
Not run: starts a long-lived server.
|
|
910
910
|
|
|
911
|
+
## `kxm vision`
|
|
912
|
+
|
|
913
|
+
Judge one captured screenshot through an admitted Pi route. The gate sends a bounded yes/no prompt and accepts only `{"verdict": true}` or `{"verdict": false}`. Any other answer fails closed. It does not replace a Playwright assertion.
|
|
914
|
+
|
|
915
|
+
### `kxm vision assert`
|
|
916
|
+
|
|
917
|
+
```text
|
|
918
|
+
kxm vision assert --image <path> --question <text> [--route <id>] [--timeout-ms <n>] [--expect <true|false>]
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
| Option | Argument | Default | Description |
|
|
922
|
+
|---|---|---|---|
|
|
923
|
+
| `--image` | `<path>` | required | Screenshot file. A missing file fails closed with `image unreadable` |
|
|
924
|
+
| `--question` | `<text>` | required | Yes/no assertion, trimmed to 500 characters inside the prompt |
|
|
925
|
+
| `--route` | `<id>` | `zai-coding-cn/glm-5.3-flash` | Admitted route id. The default keeps the verified Pi command `--provider zai-coding-cn --model glm-5.3-flash`. Any other id is `pi --model <id>` after the native-vendor brake |
|
|
926
|
+
| `--timeout-ms` | `<n>` | `90000` | Integer from 5000 through 300000 |
|
|
927
|
+
| `--expect` | `true` or `false` | none | Exit 0 only when the verdict equals this value |
|
|
928
|
+
|
|
929
|
+
- Global options apply, except `--dry-run`, which exits 2 with `dry_run_unsupported` because the command calls Pi.
|
|
930
|
+
- The route must be listed under `admitted` in `.kxm/routes.yaml`. Otherwise the command exits 1 and the `divergence` names the id and `kxm routes admit --model <id>`.
|
|
931
|
+
- This repository does not admit the default `zai-coding-cn/glm-5.3-flash` (`zai-coding-cn` is not an allowlisted harness). Pass `--route openrouter/z-ai/glm-5.3-flash`, which is admitted here, or admit the route you pass.
|
|
932
|
+
- A native-vendor id such as `xai/grok-4.7` or `anthropic/fable` is refused before Pi starts, even when that id is admitted. The `divergence` is the Pi brake message.
|
|
933
|
+
- Exit 0 when the verdict resolves and, if `--expect` is set, matches it. Exit 1 for a fail-closed divergence. Exit 2 when `--image` or `--question` is missing, `--expect` is not `true` or `false`, or `--timeout-ms` is not an integer.
|
|
934
|
+
- JSON keys: `ok`, `command` (`vision assert`), `verdict` (`true`, `false`, or `null`), `route`, `raw`, `divergence` when the gate fails closed, and `expect` when `--expect` is set.
|
|
935
|
+
- Text mode prints `verdict true` or `verdict false`, or `vision gate fail-closed: <divergence>`.
|
|
936
|
+
|
|
937
|
+
Not run. The command calls Pi.
|
|
938
|
+
|
|
939
|
+
```bash
|
|
940
|
+
kxm vision assert --image .kxm/artifacts/browser/shot.png --question "Does the page show the error banner?" --route openrouter/z-ai/glm-5.3-flash --expect false
|
|
941
|
+
```
|
|
942
|
+
|
|
911
943
|
## `kxm harness`
|
|
912
944
|
|
|
913
945
|
### `kxm harness list`
|
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.195",
|
|
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
|
@@ -28903,12 +28903,22 @@ function parseVisionVerdict(raw) {
|
|
|
28903
28903
|
const match = /"verdict"\s*:\s*(true|false)/i.exec(raw);
|
|
28904
28904
|
return match ? match[1] === "true" : null;
|
|
28905
28905
|
}
|
|
28906
|
-
function
|
|
28907
|
-
|
|
28908
|
-
"
|
|
28909
|
-
|
|
28910
|
-
|
|
28911
|
-
)
|
|
28906
|
+
function visionPiArgs(route) {
|
|
28907
|
+
if (route === VISION_GATE_DEFAULT_ROUTE) {
|
|
28908
|
+
return { ok: true, args: ["--provider", "zai-coding-cn", "--model", "glm-5.3-flash", "-p", "--no-session"] };
|
|
28909
|
+
}
|
|
28910
|
+
const validation = validateHarnessModelPair("pi", route);
|
|
28911
|
+
if (!validation.valid) {
|
|
28912
|
+
return { ok: false, divergence: validation.message ?? "route_not_pi" };
|
|
28913
|
+
}
|
|
28914
|
+
const trimmed = route.trim();
|
|
28915
|
+
if (!trimmed.includes("/")) {
|
|
28916
|
+
return { ok: false, divergence: `route_not_pi: ${trimmed} is not a provider-qualified Pi model id` };
|
|
28917
|
+
}
|
|
28918
|
+
return { ok: true, args: ["--model", trimmed, "-p", "--no-session"] };
|
|
28919
|
+
}
|
|
28920
|
+
function defaultTransport(args, timeoutMs) {
|
|
28921
|
+
return execFileSync("pi", [...args], { encoding: "utf8", timeout: timeoutMs, windowsHide: true });
|
|
28912
28922
|
}
|
|
28913
28923
|
async function runVisionGate(options) {
|
|
28914
28924
|
const projectRoot = options.projectRoot ?? process.cwd();
|
|
@@ -28922,12 +28932,17 @@ async function runVisionGate(options) {
|
|
|
28922
28932
|
return { ...base, divergence: "image unreadable" };
|
|
28923
28933
|
}
|
|
28924
28934
|
if (!isRouteAdmitted(projectRoot, route)) {
|
|
28925
|
-
return {
|
|
28935
|
+
return {
|
|
28936
|
+
...base,
|
|
28937
|
+
divergence: `route_not_admitted: ${route} is not admitted; run kxm routes admit --model ${route} or pass --route <admitted-id>`
|
|
28938
|
+
};
|
|
28926
28939
|
}
|
|
28940
|
+
const piArgs = visionPiArgs(route);
|
|
28941
|
+
if (!piArgs.ok) return { ...base, divergence: piArgs.divergence };
|
|
28927
28942
|
const prompt = visionGatePrompt(options.question, options.imagePath);
|
|
28928
28943
|
let raw;
|
|
28929
28944
|
try {
|
|
28930
|
-
raw = options.transport ? await options.transport(prompt) : defaultTransport(prompt, timeoutMs);
|
|
28945
|
+
raw = options.transport ? await options.transport(prompt) : defaultTransport([...piArgs.args, prompt], timeoutMs);
|
|
28931
28946
|
} catch (error) {
|
|
28932
28947
|
return { ...base, divergence: `transport failed: ${String(error?.message ?? error).slice(0, 200)}` };
|
|
28933
28948
|
}
|
|
@@ -28942,6 +28957,7 @@ var VISION_GATE_DEFAULT_ROUTE, QUESTION_MAX_CHARS, RAW_MAX_CHARS;
|
|
|
28942
28957
|
var init_vision_gate = __esm({
|
|
28943
28958
|
"plugins/kxm/src/vision-gate.ts"() {
|
|
28944
28959
|
"use strict";
|
|
28960
|
+
init_harness();
|
|
28945
28961
|
init_routes();
|
|
28946
28962
|
VISION_GATE_DEFAULT_ROUTE = "zai-coding-cn/glm-5.3-flash";
|
|
28947
28963
|
QUESTION_MAX_CHARS = 500;
|
|
@@ -25462,7 +25462,7 @@ function sessionTokenFixHint(policy) {
|
|
|
25462
25462
|
}
|
|
25463
25463
|
|
|
25464
25464
|
// plugins/kxm/src/mcp-server.ts
|
|
25465
|
-
var VERSION = "0.7.
|
|
25465
|
+
var VERSION = "0.7.195";
|
|
25466
25466
|
var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
25467
25467
|
var inbox = /* @__PURE__ */ new Map();
|
|
25468
25468
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
package/plugins/kxm/package.json
CHANGED
|
@@ -105,6 +105,6 @@ kxm vision assert --image .kxm/artifacts/browser/shot.png \
|
|
|
105
105
|
--expect false
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
- Exit 0 only when the verdict resolves and matches `--expect`; fail-closed divergences (`route_not_admitted`, `image unreadable`, `verdict unreadable
|
|
109
|
-
- The route must be admitted in `.kxm/routes.yaml
|
|
108
|
+
- Exit 0 only when the verdict resolves and matches `--expect`; fail-closed divergences (`route_not_admitted`, `image unreadable`, `verdict unreadable`, or the Pi native-vendor brake) exit 1.
|
|
109
|
+
- The route must be admitted in `.kxm/routes.yaml`. The built-in default is `zai-coding-cn/glm-5.3-flash` (the verified Pi invocation `--provider zai-coding-cn --model glm-5.3-flash`). This checkout does not admit that id. Pass `--route openrouter/z-ai/glm-5.3-flash`, which is admitted here. `--route` selects the Pi model; a native-vendor id is refused before Pi starts.
|
|
110
110
|
- The gate is a supplement to deterministic Playwright assertions, never a replacement.
|
|
@@ -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.
|
|
14
|
+
const VERSION = "0.7.195";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { existsSync, statSync } from "node:fs";
|
|
3
|
+
import { validateHarnessModelPair } from "./harness.ts";
|
|
3
4
|
import { isRouteAdmitted } from "./routes.ts";
|
|
4
5
|
|
|
5
6
|
/** Deterministic vision gate: judge a UI screenshot through an admitted
|
|
@@ -52,12 +53,27 @@ export function parseVisionVerdict(raw: string): boolean | null {
|
|
|
52
53
|
return match ? match[1] === "true" : null;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
)
|
|
56
|
+
/** Pi argv for an admitted vision route, without the prompt.
|
|
57
|
+
* The verified default keeps `--provider zai-coding-cn --model glm-5.3-flash`.
|
|
58
|
+
* Every other id is `pi --model <id>` after the native-vendor brake, so
|
|
59
|
+
* `--route` selects the model instead of only the admission check. */
|
|
60
|
+
export function visionPiArgs(route: string): { ok: true; args: readonly string[] } | { ok: false; divergence: string } {
|
|
61
|
+
if (route === VISION_GATE_DEFAULT_ROUTE) {
|
|
62
|
+
return { ok: true, args: ["--provider", "zai-coding-cn", "--model", "glm-5.3-flash", "-p", "--no-session"] };
|
|
63
|
+
}
|
|
64
|
+
const validation = validateHarnessModelPair("pi", route);
|
|
65
|
+
if (!validation.valid) {
|
|
66
|
+
return { ok: false, divergence: validation.message ?? "route_not_pi" };
|
|
67
|
+
}
|
|
68
|
+
const trimmed = route.trim();
|
|
69
|
+
if (!trimmed.includes("/")) {
|
|
70
|
+
return { ok: false, divergence: `route_not_pi: ${trimmed} is not a provider-qualified Pi model id` };
|
|
71
|
+
}
|
|
72
|
+
return { ok: true, args: ["--model", trimmed, "-p", "--no-session"] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function defaultTransport(args: readonly string[], timeoutMs: number): string {
|
|
76
|
+
return execFileSync("pi", [...args], { encoding: "utf8", timeout: timeoutMs, windowsHide: true });
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
export async function runVisionGate(options: VisionGateOptions): Promise<VisionGateResult> {
|
|
@@ -73,15 +89,20 @@ export async function runVisionGate(options: VisionGateOptions): Promise<VisionG
|
|
|
73
89
|
return { ...base, divergence: "image unreadable" };
|
|
74
90
|
}
|
|
75
91
|
if (!isRouteAdmitted(projectRoot, route)) {
|
|
76
|
-
return {
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
divergence: `route_not_admitted: ${route} is not admitted; run kxm routes admit --model ${route} or pass --route <admitted-id>`,
|
|
95
|
+
};
|
|
77
96
|
}
|
|
97
|
+
const piArgs = visionPiArgs(route);
|
|
98
|
+
if (!piArgs.ok) return { ...base, divergence: piArgs.divergence };
|
|
78
99
|
const prompt = visionGatePrompt(options.question, options.imagePath);
|
|
79
100
|
|
|
80
101
|
let raw: string;
|
|
81
102
|
try {
|
|
82
103
|
raw = options.transport
|
|
83
104
|
? await options.transport(prompt)
|
|
84
|
-
: defaultTransport(prompt, timeoutMs);
|
|
105
|
+
: defaultTransport([...piArgs.args, prompt], timeoutMs);
|
|
85
106
|
} catch (error) {
|
|
86
107
|
return { ...base, divergence: `transport failed: ${String((error as Error)?.message ?? error).slice(0, 200)}` };
|
|
87
108
|
}
|