@kontextmind/kxm 0.7.145 → 0.7.147
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 +20 -2
- package/.kxm/agents/{coordinator.yaml → planner.yaml} +2 -0
- package/.kxm/agents/{critic-arch.yaml → reviewer-arch.yaml} +2 -0
- package/.kxm/agents/{critic-cli.yaml → reviewer-cli.yaml} +2 -0
- package/.kxm/agents/{implementer.yaml → writer.yaml} +2 -0
- package/.kxm/roles/planner.yaml +4 -2
- package/.kxm/roles/reviewer-arch.yaml +4 -2
- package/.kxm/roles/reviewer-cli.yaml +4 -2
- package/.kxm/roles/writer.yaml +6 -3
- package/.kxm/workflows/default.yaml +20 -12
- package/.kxm/workflows/land.yaml +1 -1
- package/.kxm/workflows/{review-arch-only.yaml → reviewer-arch-only.yaml} +7 -3
- package/.kxm/workflows/{review-cli-only.yaml → reviewer-cli-only.yaml} +7 -3
- package/.kxm/workflows/{implement-only.yaml → writer-only.yaml} +8 -4
- package/CHANGELOG.md +48 -8
- package/docs/README.md +1 -1
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +8 -5
- package/docs/adr/ADR-0005-obscura-default-playwright.md +4 -3
- package/docs/adr/ADR-0006-machine-account-names.md +90 -0
- package/docs/adr/ADR-0007-steel-caddy-authentik.md +97 -0
- package/docs/adr/README.md +3 -1
- package/docs/contributing/harness-routing-internals.md +17 -11
- package/docs/contributing/operating-rules.md +13 -1
- package/docs/guides/agent-skills.md +1 -1
- package/docs/guides/browser-automation.md +44 -28
- package/docs/kb/how-credentials-retrieved-safely.md +22 -23
- package/docs/kb/how-to-connect-playwright-to-steel.md +8 -5
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +11 -10
- package/docs/kb/why-automation-opened-different-browser.md +4 -3
- package/docs/operations/deploy.md +31 -0
- package/docs/operations/troubleshooting.md +23 -1
- package/docs/prompts/browser-diagnose-recover.md +2 -2
- package/docs/prompts/browser-start.md +1 -1
- package/docs/reference/cli-reference.md +23 -24
- package/docs/reference/config-reference.md +69 -62
- package/docs/reference/configuration.md +9 -7
- package/docs/reference/harness-routing.md +6 -5
- package/docs/reference/workflow-catalog.md +3 -3
- package/package.json +3 -3
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +1145 -805
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +636 -308
- package/plugins/kxm/dist/runtime.js +710 -382
- package/plugins/kxm/dist/server.js +49 -5
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +11 -12
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +5 -4
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +4 -3
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +5 -5
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +3 -3
- package/plugins/kxm/src/browser.ts +12 -9
- package/plugins/kxm/src/cli/project.ts +2 -1
- package/plugins/kxm/src/cli/roles.ts +3 -4
- package/plugins/kxm/src/engine.ts +9 -6
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/modes.ts +1 -1
- package/plugins/kxm/src/policy-draft.mjs +11 -5
- package/plugins/kxm/src/project-config.ts +49 -11
- package/plugins/kxm/src/runtime-service.ts +2 -1
- package/plugins/kxm/src/template.ts +39 -10
- package/plugins/kxm/src/workflow-manager.ts +34 -28
- package/plugins/kxm/src/workforce-names.d.mts +38 -0
- package/plugins/kxm/src/workforce-names.mjs +317 -0
- package/schemas/agent.schema.json +7 -0
- package/schemas/model.schema.json +12 -0
- package/schemas/workflow.schema.json +14 -0
- package/scripts/harness-run.mjs +1 -1
- package/scripts/native-critic.mjs +5 -5
- package/scripts/roster-policy.mjs +9 -3
- package/scripts/workforce-lint.mjs +18 -0
|
@@ -14958,6 +14958,7 @@ var MODES = Object.freeze(["headless", "interactive", "either"]);
|
|
|
14958
14958
|
var MODEL_KEYS = Object.freeze([
|
|
14959
14959
|
"schema",
|
|
14960
14960
|
"id",
|
|
14961
|
+
"aliases",
|
|
14961
14962
|
"harness",
|
|
14962
14963
|
"model",
|
|
14963
14964
|
"vendor",
|
|
@@ -14996,8 +14997,51 @@ var FALLBACK_REVERT = Object.freeze(["next_run", "never"]);
|
|
|
14996
14997
|
var ROSTER_ENTRY_KEYS = Object.freeze(["route", "effort", "mode"]);
|
|
14997
14998
|
var CRITIC_PURPOSES = Object.freeze(["reviewer-arch", "reviewer-cli"]);
|
|
14998
14999
|
|
|
14999
|
-
// plugins/kxm/src/
|
|
15000
|
+
// plugins/kxm/src/workforce-names.mjs
|
|
15000
15001
|
var import_yaml2 = __toESM(require_dist(), 1);
|
|
15002
|
+
var ROLE_IDS = Object.freeze([
|
|
15003
|
+
"writer",
|
|
15004
|
+
"planner",
|
|
15005
|
+
"reviewer-arch",
|
|
15006
|
+
"reviewer-cli",
|
|
15007
|
+
"experiment"
|
|
15008
|
+
]);
|
|
15009
|
+
var ROUTE_RENAMES = Object.freeze([
|
|
15010
|
+
["grok-native", "grok-grok-4-7"],
|
|
15011
|
+
["qwen-openrouter-pi", "pi-qwen3-coder-plus-openrouter"],
|
|
15012
|
+
["gemini-agy", "agy-gemini-3-8-flash-high"],
|
|
15013
|
+
["fable-claude", "claude-fable"],
|
|
15014
|
+
["sol-codex", "codex-gpt-5-6-sol"],
|
|
15015
|
+
["grok-default", "grok-grok-4-6"],
|
|
15016
|
+
["fable-default", "claude-fable"]
|
|
15017
|
+
]);
|
|
15018
|
+
var AGENT_RENAMES = Object.freeze([
|
|
15019
|
+
["coordinator", "planner"],
|
|
15020
|
+
["implementer", "writer"],
|
|
15021
|
+
["critic-arch", "reviewer-arch"],
|
|
15022
|
+
["critic-cli", "reviewer-cli"]
|
|
15023
|
+
]);
|
|
15024
|
+
var WORKFLOW_RENAMES = Object.freeze([
|
|
15025
|
+
["implement-only", "writer-only"],
|
|
15026
|
+
["review-arch-only", "reviewer-arch-only"],
|
|
15027
|
+
["review-cli-only", "reviewer-cli-only"]
|
|
15028
|
+
]);
|
|
15029
|
+
var STEP_RENAMES = Object.freeze([
|
|
15030
|
+
["implement", "writer"],
|
|
15031
|
+
["critic-arch", "reviewer-arch"],
|
|
15032
|
+
["review-arch", "reviewer-arch"],
|
|
15033
|
+
["critic-cli", "reviewer-cli"],
|
|
15034
|
+
["review-cli", "reviewer-cli"]
|
|
15035
|
+
]);
|
|
15036
|
+
var RENAMES = Object.freeze({
|
|
15037
|
+
route: ROUTE_RENAMES,
|
|
15038
|
+
agent: AGENT_RENAMES,
|
|
15039
|
+
workflow: WORKFLOW_RENAMES,
|
|
15040
|
+
step: STEP_RENAMES
|
|
15041
|
+
});
|
|
15042
|
+
|
|
15043
|
+
// plugins/kxm/src/template.ts
|
|
15044
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
15001
15045
|
|
|
15002
15046
|
// plugins/kxm/src/repo-root.ts
|
|
15003
15047
|
import { existsSync } from "node:fs";
|
|
@@ -18253,7 +18297,7 @@ function explainContextItem(id, pool) {
|
|
|
18253
18297
|
}
|
|
18254
18298
|
|
|
18255
18299
|
// plugins/kxm/src/memory.ts
|
|
18256
|
-
var
|
|
18300
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
18257
18301
|
import { existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
18258
18302
|
import { extname as extname2, join as join3, resolve as resolve2 } from "node:path";
|
|
18259
18303
|
var MEMORY_SCHEMA = "kxm.memory.v1";
|
|
@@ -18285,7 +18329,7 @@ function parseFrontmatter(content) {
|
|
|
18285
18329
|
}
|
|
18286
18330
|
function parseMemoryRecord(raw, filename = "memory.md") {
|
|
18287
18331
|
const { frontmatter, body } = parseFrontmatter(raw);
|
|
18288
|
-
const data = (0,
|
|
18332
|
+
const data = (0, import_yaml4.parse)(frontmatter);
|
|
18289
18333
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
18290
18334
|
throw new Error(`invalid YAML frontmatter in ${filename}`);
|
|
18291
18335
|
}
|
|
@@ -18622,7 +18666,7 @@ function boundedRefs(value, field) {
|
|
|
18622
18666
|
}
|
|
18623
18667
|
|
|
18624
18668
|
// plugins/kxm/src/skills.ts
|
|
18625
|
-
var
|
|
18669
|
+
var import_yaml5 = __toESM(require_dist(), 1);
|
|
18626
18670
|
import { createHash as createHash4 } from "node:crypto";
|
|
18627
18671
|
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readdirSync as readdirSync3, readFileSync as readFileSync3, renameSync, rmSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
18628
18672
|
import { dirname as dirname4, join as join4 } from "node:path";
|
|
@@ -18666,7 +18710,7 @@ function parseSkillFrontmatter(content) {
|
|
|
18666
18710
|
return { frontmatter: null, body: content };
|
|
18667
18711
|
}
|
|
18668
18712
|
try {
|
|
18669
|
-
const parsed = (0,
|
|
18713
|
+
const parsed = (0, import_yaml5.parse)(rawFm);
|
|
18670
18714
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
18671
18715
|
return { frontmatter: parsed, body: rawBody };
|
|
18672
18716
|
}
|
package/plugins/kxm/package.json
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: kxm-browser-auth
|
|
3
|
-
description: Retrieve application credentials and manage authenticated browser profiles
|
|
3
|
+
description: Retrieve application credentials and manage authenticated browser profiles without secret exposure. Steel uses the svc-steel credential from 1Password via op read.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# KXM Browser Credentials & Authenticated Profiles
|
|
7
7
|
|
|
8
|
-
Use this skill to retrieve target application credentials and manage browser session state
|
|
8
|
+
Use this skill to retrieve target application credentials and manage browser session state. The Steel `svc-steel` credential comes from 1Password and is read with `op read`. It is never written to disk.
|
|
9
9
|
|
|
10
10
|
## Purpose & Scope
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Read the Steel credential at runtime from 1Password.
|
|
13
13
|
- Prevent secrets from leaking into git repositories, logs, prompts, or model-visible tool outputs.
|
|
14
14
|
- Support safe storage and retrieval of session storage state and authenticated profiles.
|
|
15
15
|
|
|
16
16
|
## Credential Retrieval Guidelines
|
|
17
17
|
|
|
18
|
-
### 1.
|
|
18
|
+
### 1. Steel credential: 1Password
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
`STEEL_API_KEY` is deprecated. Steel and Caddy do not enforce it. Authenticate as `svc-steel`. Precedence is `STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`. Those override `STEEL_API_KEY`. `STEEL_API_URL` defaults to `https://steel.kontextmind.com`. `kxm` 0.7.135 or newer is required.
|
|
21
21
|
|
|
22
22
|
```bash
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
# Retrieve the Authentik app password for Steel. The proxy accepts Authorization: Basic.
|
|
27
|
-
# STEEL_AUTH_USER is the Authentik username (for example svc-steel).
|
|
28
|
-
pass-cli item view --vault-name "<vault>" --item-title "<steel-item>" --field password
|
|
23
|
+
# Vault kontextmind, item "Steel (svc-steel)", field basic_auth.
|
|
24
|
+
# Do not redirect this into a file.
|
|
25
|
+
export STEEL_AUTH_BASIC="$(op read 'op://kontextmind/Steel (svc-steel)/basic_auth')"
|
|
29
26
|
```
|
|
30
27
|
|
|
28
|
+
`STEEL_AUTH_USER` is `svc-steel` when you use the user and token pair. Send the value as `Authorization` on `/v1/devtools`. Never put it in the URL.
|
|
29
|
+
|
|
31
30
|
### 2. Secret Redaction Invariants
|
|
32
31
|
|
|
33
32
|
- **Never** write plain passwords, session tokens, or API keys into markdown docs, commit messages, or prompts.
|
|
34
33
|
- **Never** pass plain credentials as unredacted command line arguments in shared logs.
|
|
35
|
-
-
|
|
34
|
+
- Keep the `op read` result in the environment of the process that calls Steel.
|
|
36
35
|
|
|
37
36
|
### 3. Profile & Storage State Management
|
|
38
37
|
|
|
@@ -15,16 +15,17 @@ Use this skill to investigate and resolve connectivity failures, CDP attachment
|
|
|
15
15
|
- **Diagnosis**:
|
|
16
16
|
- KontextMind Steel is behind Authentik forward auth. Unauthenticated requests redirect to `id.kxmd.dev`. Authentik accepts an app password only as `Authorization: Basic`. A Bearer token is refused.
|
|
17
17
|
- Check that `STEEL_AUTH_BASIC` is set, or that both `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN` are set (`test -n "$STEEL_AUTH_TOKEN" && echo set`); never print the value.
|
|
18
|
-
-
|
|
19
|
-
-
|
|
18
|
+
- `STEEL_API_KEY` is deprecated. Steel and Caddy do not enforce it. Do not put a credential in the URL. Direct LAN, tailnet, and host-forward connections are blocked.
|
|
19
|
+
- `websocketUrl` `ws://steel-browser/` means the client is older than `kxm` 0.7.135. The server returns `wss://steel.kontextmind.com/`.
|
|
20
|
+
- **Remedy**: Re-read the `svc-steel` field `basic_auth` with `op read 'op://kontextmind/Steel (svc-steel)/basic_auth'` into `STEEL_AUTH_BASIC`. Do not log it or write it to disk. Precedence is `STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`.
|
|
20
21
|
|
|
21
22
|
### 2. CDP WebSocket Attachment Failure
|
|
22
23
|
|
|
23
24
|
- **Symptom**: `WebSocket connection to wss://... failed: 404/500`.
|
|
24
25
|
- **Diagnosis**:
|
|
25
26
|
- Check if the target session ID has already been released or timed out.
|
|
26
|
-
-
|
|
27
|
-
- **Remedy**: Query `GET /v1/sessions/<id
|
|
27
|
+
- The CDP path is `/v1/devtools` on `wss://steel.kontextmind.com/` with an `Authorization` header. Caddy must pass the WebSocket upgrade. A credential in the URL is not accepted.
|
|
28
|
+
- **Remedy**: Query `GET /v1/sessions/<id>` with the same `Authorization` header. If status is `released`, launch a fresh session.
|
|
28
29
|
|
|
29
30
|
### 3. Session Timeout & Expiration
|
|
30
31
|
|
|
@@ -21,11 +21,12 @@ Use this skill for exploratory navigation, DOM inspection, scraping, and interac
|
|
|
21
21
|
Ensure an active Steel session exists and obtain its CDP endpoint:
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
# CDP
|
|
25
|
-
|
|
24
|
+
# CDP path only. The session websocketUrl is wss://steel.kontextmind.com/.
|
|
25
|
+
# The Authentik credential is an Authorization header, not a query parameter.
|
|
26
|
+
CDP_URL="wss://steel.kontextmind.com/v1/devtools?sessionId=<sessionId>"
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
Build that URL with `formatCDPConnect()` so the `Authorization
|
|
29
|
+
Build that URL with `formatCDPConnect()` so the `Authorization` header is available for the handshake. Precedence is `STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`. Those override `STEEL_API_KEY`, which Steel and Caddy do not enforce. A Bearer token is not accepted. `kxm` 0.7.135 or newer is required. Read `basic_auth` with `op read` and do not write it to disk.
|
|
29
30
|
|
|
30
31
|
### 2. Connect a client that can send the header
|
|
31
32
|
|
|
@@ -5,7 +5,7 @@ description: Start, attach to, inspect, and release self-hosted Steel browser se
|
|
|
5
5
|
|
|
6
6
|
# KXM Browser Session Management
|
|
7
7
|
|
|
8
|
-
Use this skill to create, inspect, attach automation tools to, and release isolated browser sessions
|
|
8
|
+
Use this skill to create, inspect, attach automation tools to, and release isolated browser sessions on the Steel server. `STEEL_API_URL` defaults to `https://steel.kontextmind.com`. `steel.theneuro.me` is an alias of that server. `kxm` 0.7.135 or newer is required.
|
|
9
9
|
|
|
10
10
|
Playwright testing and verification use Obscura by default (`resolveBrowserCdpEndpoint()`, or `npm run e2e`). Use this skill's Steel session for human takeover, MFA, and the live session viewer. Attach Playwright to that session only when `KXM_BROWSER=steel`.
|
|
11
11
|
|
|
@@ -18,9 +18,9 @@ Playwright testing and verification use Obscura by default (`resolveBrowserCdpEn
|
|
|
18
18
|
|
|
19
19
|
## Prerequisites
|
|
20
20
|
|
|
21
|
-
1.
|
|
22
|
-
2.
|
|
23
|
-
3.
|
|
21
|
+
1. `kxm` 0.7.135 or newer. The server is `https://steel.kontextmind.com`. Caddy and Authentik forward auth are the only path. Direct LAN, tailnet, and host-forward access is blocked. Sessions return `websocketUrl` `wss://steel.kontextmind.com/` (previously `ws://steel-browser/`).
|
|
22
|
+
2. The `svc-steel` Authentik credential in the environment. Allowed groups are `steel-users`, `kxmd-users`, `kxmd-admins`, and `kxmd-owners`. Read it at runtime and do not write it to disk: `export STEEL_AUTH_BASIC="$(op read 'op://kontextmind/Steel (svc-steel)/basic_auth')"`. Precedence is `STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`. Those override `STEEL_API_KEY`. `STEEL_API_KEY` is deprecated. Steel and Caddy do not enforce it. A Bearer token is refused. Never put the credential in a URL.
|
|
23
|
+
3. HTTPS to `steel.kontextmind.com` for the CDP path `/v1/devtools`. Send `Authorization` on the handshake.
|
|
24
24
|
|
|
25
25
|
## Session Lifecycle States
|
|
26
26
|
|
|
@@ -48,7 +48,7 @@ Playwright testing and verification use Obscura by default (`resolveBrowserCdpEn
|
|
|
48
48
|
- **Inputs**: Task ID, target URL, session timeout (default 300s, max 1800s), optional proxy or viewport dimensions.
|
|
49
49
|
- **Outputs**:
|
|
50
50
|
- `sessionId`: Unique session UUID.
|
|
51
|
-
- `cdpUrl`:
|
|
51
|
+
- `cdpUrl`: `wss://steel.kontextmind.com/v1/devtools?sessionId=<id>`. Send `Authorization` on the handshake. The URL has no credential. The session `websocketUrl` is `wss://steel.kontextmind.com/`.
|
|
52
52
|
- `sessionViewerUrl`: Interactive web session viewer URL (`$STEEL_UI_URL?sessionId=<id>`).
|
|
53
53
|
- `status`: `live` | `idle` | `released`.
|
|
54
54
|
|
|
@@ -77,7 +77,7 @@ Local pages require the launcher flag `--allow-private-network` (the launcher al
|
|
|
77
77
|
|
|
78
78
|
## Steel, only for takeover
|
|
79
79
|
|
|
80
|
-
When the case is human takeover, MFA, or the live session viewer, set `KXM_BROWSER=steel` and a session id. `connectBrowserOverCdp()` passes `formatCDPConnect()` headers into `chromium.connectOverCDP
|
|
80
|
+
When the case is human takeover, MFA, or the live session viewer, set `KXM_BROWSER=steel` and a session id. That path needs `kxm` 0.7.135 or newer. `connectBrowserOverCdp()` passes `formatCDPConnect()` headers into `chromium.connectOverCDP` for `/v1/devtools` on `wss://steel.kontextmind.com/`. The `Authorization` header is the `svc-steel` credential (`STEEL_AUTH_HEADER`, then `STEEL_AUTH_BASIC`, then `STEEL_AUTH_USER` and `STEEL_AUTH_TOKEN`). `STEEL_API_KEY` is deprecated and is not enforced. Closing the Playwright browser disconnects the client and does not release the Steel session.
|
|
81
81
|
|
|
82
82
|
```typescript
|
|
83
83
|
import { chromium } from "playwright";
|
|
@@ -22,7 +22,7 @@ to run in their own terminal or in Claude Code.
|
|
|
22
22
|
`ready`, `repair`, or `legacy`, and `issues` lists anything to fix first.
|
|
23
23
|
2. `kxm init --name "<display name>"` prints
|
|
24
24
|
`initialized KXM project at <root>`. It writes `.kxm/project.yaml`,
|
|
25
|
-
`.kxm/agents/
|
|
25
|
+
`.kxm/agents/planner.yaml`, `.kxm/agents/writer.yaml`,
|
|
26
26
|
`.kxm/gates.yaml`, `.kxm/repo/repo.yaml`, `.kxm/template-provenance.yaml`,
|
|
27
27
|
and `.kxm/workflows/default.yaml`. Outside Git it fails with
|
|
28
28
|
`git_root_required`. In an existing project, plain `kxm init` validates,
|
|
@@ -44,7 +44,7 @@ After the user's commit:
|
|
|
44
44
|
1. `kxm workflow add first --template spec-and-plan` prints
|
|
45
45
|
`Added workflow 'first' to local (<root>/.kxm/workflows/first.yaml)`. Add
|
|
46
46
|
`--dry-run` first to see the path without writing. The template has two
|
|
47
|
-
steps, `plan` then `
|
|
47
|
+
steps, `plan` then `reviewer-arch`, both run by the `planner` agent with
|
|
48
48
|
`repositories: control: read`. It writes nothing and runs no test command,
|
|
49
49
|
and a failed review goes back to `plan` at most twice.
|
|
50
50
|
2. `kxm init` validates it and prints `validated KXM project at <root>`.
|
|
@@ -71,7 +71,7 @@ After the user's commit:
|
|
|
71
71
|
4. `kxm runs status <runId>` prints `created`.
|
|
72
72
|
5. `kxm runs drive <runId> --simulated --wait --timeout-ms 60000` prints a
|
|
73
73
|
`kxm.drive-receipt.v1` whose settlement is terminal `completed`, and exits 0.
|
|
74
|
-
The run moves from `plan` to `
|
|
74
|
+
The run moves from `plan` to `reviewer-arch` to `completed`. Always pass
|
|
75
75
|
`--simulated`; without it, drive calls live harnesses.
|
|
76
76
|
6. `kxm runs status <runId>` prints `completed … (receipt verified)`.
|
|
77
77
|
7. `kxm runs receipt <runId>` and `kxm runs list`. Cancel a stuck run with
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* (`resolveBrowserCdpEndpoint()`). Steel remains the client for human
|
|
6
6
|
* takeover, MFA, and the live session viewer (`KXM_BROWSER=steel`).
|
|
7
7
|
* Manages remote Steel sessions, CDP endpoints, human takeover handoffs,
|
|
8
|
-
*
|
|
8
|
+
* and automated cleanup without leaking secrets.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* steel.kontextmind.com is reached only through Caddy and Authentik forward
|
|
11
|
+
* auth. Clients send `Authorization: Basic` for the svc-steel credential.
|
|
12
|
+
* A Bearer token is refused. `STEEL_API_KEY` is deprecated and is not
|
|
13
|
+
* enforced by Steel or Caddy. Do not put a credential in the URL.
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import { execSync } from "node:child_process";
|
|
@@ -188,9 +189,9 @@ export class SteelAuthRedirectError extends Error {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
const LEGACY_STEEL_AUTH_WARNING =
|
|
191
|
-
"kxm: STEEL_API_KEY is deprecated for Steel.
|
|
192
|
-
"Set STEEL_AUTH_BASIC, or STEEL_AUTH_USER and STEEL_AUTH_TOKEN. " +
|
|
193
|
-
"
|
|
192
|
+
"kxm: STEEL_API_KEY is deprecated for Steel. Steel and Caddy do not enforce it. " +
|
|
193
|
+
"Set STEEL_AUTH_HEADER, or STEEL_AUTH_BASIC, or STEEL_AUTH_USER and STEEL_AUTH_TOKEN. " +
|
|
194
|
+
"Those override STEEL_API_KEY. Send Authorization on the request, not in the URL.\n";
|
|
194
195
|
|
|
195
196
|
let legacySteelAuthWarned = false;
|
|
196
197
|
|
|
@@ -310,6 +311,7 @@ export function resolvePassCliApiKey(
|
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
try {
|
|
314
|
+
// Deprecated lookup. The hosted server does not enforce STEEL_API_KEY.
|
|
313
315
|
const output = execFn(
|
|
314
316
|
'pass-cli item view --vault-name "AI Provider Keys" --item-title "Steel Browser (KontextMind DOKS)" --output json',
|
|
315
317
|
);
|
|
@@ -325,8 +327,9 @@ export function resolvePassCliApiKey(
|
|
|
325
327
|
}
|
|
326
328
|
|
|
327
329
|
/**
|
|
328
|
-
* Resolve Steel configuration from environment
|
|
330
|
+
* Resolve Steel configuration from the environment.
|
|
329
331
|
* Does not write secrets to disk or logs.
|
|
332
|
+
* Authentik Basic auth overrides `STEEL_API_KEY`. Steel and Caddy do not enforce that key.
|
|
330
333
|
*
|
|
331
334
|
* Authentik Basic auth (`STEEL_AUTH_HEADER`, `STEEL_AUTH_BASIC`, or
|
|
332
335
|
* `STEEL_AUTH_USER` + `STEEL_AUTH_TOKEN`) wins over `STEEL_API_KEY`.
|
|
@@ -610,7 +613,7 @@ export class SteelClient {
|
|
|
610
613
|
}
|
|
611
614
|
|
|
612
615
|
/**
|
|
613
|
-
* Launch a new Steel browser session
|
|
616
|
+
* Launch a new Steel browser session.
|
|
614
617
|
*/
|
|
615
618
|
async createSession(options?: CreateSessionOptions): Promise<SteelSession> {
|
|
616
619
|
const timeoutMs = options?.timeoutMs ?? this.config.timeoutMs ?? 300000;
|
|
@@ -11,6 +11,7 @@ import { runVisionGate } from "../vision-gate.ts";
|
|
|
11
11
|
import {
|
|
12
12
|
KxmConfigError,
|
|
13
13
|
discoverKxmProjectRoot,
|
|
14
|
+
lookupKxmResource,
|
|
14
15
|
type KxmInitializationPlan,
|
|
15
16
|
} from "../project-config.ts";
|
|
16
17
|
import { initializeKxmProject } from "../init.ts";
|
|
@@ -439,7 +440,7 @@ export function resolveKxmRunTarget(
|
|
|
439
440
|
}
|
|
440
441
|
const bundle = loadKxmProject(projectRoot, {});
|
|
441
442
|
const workflowId = workflow ?? String(bundle.project.value.defaultWorkflow ?? "default");
|
|
442
|
-
if (!bundle.workflows.
|
|
443
|
+
if (!lookupKxmResource(bundle.workflows.values(), workflowId, "workflow")) {
|
|
443
444
|
print(runtime.io, runtime.json, { ok: false, command: "run", error: "run_workflow_unknown", workflow: workflowId, ...envelope }, `workflow ${workflowId} does not exist in this project`);
|
|
444
445
|
return 1;
|
|
445
446
|
}
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type KxmRoleDefinition,
|
|
16
16
|
} from "../role.ts";
|
|
17
17
|
import { discoverKxmProjectRoot, kxmRoleWriteIssues } from "../project-config.ts";
|
|
18
|
+
import { findYamlBasename } from "../workforce-names.mjs";
|
|
18
19
|
import { ensureKxmSupervisor, kxmRuntimeRequest } from "../runtime-supervisor.ts";
|
|
19
20
|
import { projectRuntimeOwnsRun } from "../runtime-store.ts";
|
|
20
21
|
import { resumeWorkflowFromRuling, type WorkflowRun } from "../workflow.ts";
|
|
@@ -185,8 +186,7 @@ export async function cmdRoleAdd(
|
|
|
185
186
|
// after its roster is parsed, for both local and global scope.
|
|
186
187
|
const projectRoot = discoverKxmProjectRoot(runtime.cwd) ?? runtime.cwd;
|
|
187
188
|
const refuseMissingRoute = (routeId: string): boolean => {
|
|
188
|
-
|
|
189
|
-
if (existsSync(modelFile)) return false;
|
|
189
|
+
if (findYamlBasename(join(projectRoot, ".kxm", "models"), routeId, "route")) return false;
|
|
190
190
|
runtime.io.stderr(`kxm: route '${routeId}' is not a file under .kxm/models/\n`);
|
|
191
191
|
return true;
|
|
192
192
|
};
|
|
@@ -398,8 +398,7 @@ export async function cmdRoleModify(
|
|
|
398
398
|
if (options.addRoute) {
|
|
399
399
|
const routeId = options.addRoute;
|
|
400
400
|
const projectRoot = discoverKxmProjectRoot(runtime.cwd) ?? runtime.cwd;
|
|
401
|
-
|
|
402
|
-
if (!existsSync(modelFile)) {
|
|
401
|
+
if (!findYamlBasename(join(projectRoot, ".kxm", "models"), routeId, "route")) {
|
|
403
402
|
runtime.io.stderr(`kxm: route '${routeId}' is not a file under .kxm/models/\n`);
|
|
404
403
|
return 1;
|
|
405
404
|
}
|
|
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { parse } from "yaml";
|
|
5
5
|
import { listRoleBindings, loadRoutePolicy } from "./routes.ts";
|
|
6
|
+
import { findYamlBasename } from "./workforce-names.mjs";
|
|
6
7
|
import { applyAuthoringWitness, captureWorktreeWitness } from "./worktree-witness.ts";
|
|
7
8
|
import {
|
|
8
9
|
buildFormalContextPacket,
|
|
@@ -101,7 +102,7 @@ import {
|
|
|
101
102
|
type KxmRunRecord,
|
|
102
103
|
type KxmRunStatus,
|
|
103
104
|
} from "./runtime-store.ts";
|
|
104
|
-
import { KxmConfigError, kxmCanonicalJson, type JsonValue, type KxmProjectBundle } from "./project-config.ts";
|
|
105
|
+
import { KxmConfigError, kxmCanonicalJson, lookupKxmResource, type JsonValue, type KxmProjectBundle } from "./project-config.ts";
|
|
105
106
|
import { evaluateArtifactsGate } from "./engine-artifacts.ts";
|
|
106
107
|
import { createCommandObserver } from "./engine-command.ts";
|
|
107
108
|
import {
|
|
@@ -277,7 +278,7 @@ export function pinKxmCompiledPlan(
|
|
|
277
278
|
if (String(bundle.project.value.id) !== run.projectId) {
|
|
278
279
|
throw runtimeError("run_owner_mismatch", runId, "bundle project does not match the run");
|
|
279
280
|
}
|
|
280
|
-
const workflow = bundle.workflows.
|
|
281
|
+
const workflow = lookupKxmResource(bundle.workflows.values(), run.workflowId, "workflow");
|
|
281
282
|
if (!workflow) throw runtimeError("run_workflow_unknown", run.workflowId, `workflow ${run.workflowId} does not exist in this project`);
|
|
282
283
|
const revisions = kxmPolicyRevisions(bundle, options);
|
|
283
284
|
if (
|
|
@@ -1397,7 +1398,8 @@ function resolveProducerRoute(
|
|
|
1397
1398
|
},
|
|
1398
1399
|
};
|
|
1399
1400
|
}
|
|
1400
|
-
const
|
|
1401
|
+
const agentFile = findYamlBasename(join(projectRoot, ".kxm", "agents"), agentId, "agent") ?? agentId;
|
|
1402
|
+
const agent = readYamlFile(join(projectRoot, ".kxm", "agents", `${agentFile}.yaml`));
|
|
1401
1403
|
const role = typeof agent?.role === "string" ? agent.role : "";
|
|
1402
1404
|
if (!role) {
|
|
1403
1405
|
return {
|
|
@@ -1424,7 +1426,8 @@ function resolveProducerRoute(
|
|
|
1424
1426
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
1425
1427
|
const record = entry as { route?: unknown; effort?: unknown };
|
|
1426
1428
|
if (typeof record.route !== "string" || record.route.length === 0) continue;
|
|
1427
|
-
const
|
|
1429
|
+
const routeFile = findYamlBasename(join(projectRoot, ".kxm", "models"), record.route, "route") ?? record.route;
|
|
1430
|
+
const modelPath = join(projectRoot, ".kxm", "models", `${routeFile}.yaml`);
|
|
1428
1431
|
const doc = readModelDocument(modelPath);
|
|
1429
1432
|
if (!doc) {
|
|
1430
1433
|
return {
|
|
@@ -1487,7 +1490,7 @@ function resolveProducerRoute(
|
|
|
1487
1490
|
model: selector.slice(slash + 1),
|
|
1488
1491
|
selector,
|
|
1489
1492
|
harness,
|
|
1490
|
-
routeId:
|
|
1493
|
+
routeId: routeFile,
|
|
1491
1494
|
role,
|
|
1492
1495
|
permissions,
|
|
1493
1496
|
...(typeof record.effort === "string" ? { effort: record.effort } : {}),
|
|
@@ -2871,7 +2874,7 @@ export function kxmLiveRunPrerequisites(
|
|
|
2871
2874
|
workflowId: string,
|
|
2872
2875
|
projectRoot: string,
|
|
2873
2876
|
): KxmRunHandoff[] {
|
|
2874
|
-
const workflow = bundle.workflows.
|
|
2877
|
+
const workflow = lookupKxmResource(bundle.workflows.values(), workflowId, "workflow");
|
|
2875
2878
|
if (!workflow) throw runtimeError("run_workflow_unknown", workflowId, `workflow ${workflowId} does not exist in this project`);
|
|
2876
2879
|
const plan = compileKxmWorkflow({ id: workflowId, value: workflow.value, logicalPath: workflow.logicalPath });
|
|
2877
2880
|
const envelope = { plan, projectLimits: kxmProjectAdmissionLimits(bundle), gates: pinnedGatesForCompiledPlan(plan, bundle, projectRoot) };
|
|
@@ -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.147";
|
|
15
15
|
const CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
|
|
16
16
|
const inbox = new Map<string, MessageRecord>();
|
|
17
17
|
const notifiedInbox = new Set<string>();
|
package/plugins/kxm/src/modes.ts
CHANGED
|
@@ -123,7 +123,7 @@ export const DEFAULT_MODES_CONFIG: ModesConfig = Object.freeze({
|
|
|
123
123
|
browser: {
|
|
124
124
|
description: "Remote Steel browser sessions and visual testing",
|
|
125
125
|
tools: ["steel_session", "steel_scrape", "steel_screenshot"],
|
|
126
|
-
promptSnippet: "
|
|
126
|
+
promptSnippet: "Playwright tests use Obscura. Steel is remote browsing and takeover through Caddy and Authentik; send Authorization, never a credential in the URL.",
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
});
|
|
@@ -20,7 +20,7 @@ const TOKEN = /[\s\x00-\x1f]/u;
|
|
|
20
20
|
const EFFORTS = Object.freeze(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
21
21
|
const MODES = Object.freeze(["headless", "interactive", "either"]);
|
|
22
22
|
const MODEL_KEYS = Object.freeze([
|
|
23
|
-
"schema", "id", "harness", "model", "vendor", "status", "permissions", "origin",
|
|
23
|
+
"schema", "id", "aliases", "harness", "model", "vendor", "status", "permissions", "origin",
|
|
24
24
|
"thinking", "tags", "capabilities", "priority", "fallbacks", "limits",
|
|
25
25
|
]);
|
|
26
26
|
const ROLE_KEYS = Object.freeze([
|
|
@@ -226,6 +226,9 @@ function validateModelShape(document, id, label, issues) {
|
|
|
226
226
|
if (document.id !== undefined && document.id !== id) {
|
|
227
227
|
issues.push(issue("schema", "identity_mismatch", label, `declared id ${String(document.id)} does not match ${id}`));
|
|
228
228
|
}
|
|
229
|
+
if (document.aliases !== undefined && !identifierList(document.aliases)) {
|
|
230
|
+
issues.push(issue("schema", "schema_pattern", label, "aliases must be unique identifiers"));
|
|
231
|
+
}
|
|
229
232
|
if (document.harness !== undefined && !identifier(document.harness)) {
|
|
230
233
|
issues.push(issue("schema", "schema_pattern", label, "harness must be an identifier"));
|
|
231
234
|
}
|
|
@@ -511,9 +514,12 @@ function validateRoleSemantics(role, id, label, models, options, issues) {
|
|
|
511
514
|
const admitted = role.roster
|
|
512
515
|
.map((entry) => models.get(entry.route))
|
|
513
516
|
.filter((model) => model && admittedRoute(model));
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
+
// The first roster entry is the required critic. Later entries are
|
|
518
|
+
// fallbacks so one provider outage does not stop the role. Every entry
|
|
519
|
+
// stays read-only; vendor independence uses the first entry only.
|
|
520
|
+
if (admitted.length < 1) {
|
|
521
|
+
issues.push(issue("semantic", "critic_route_count", label, "required critic purpose files must list at least one admitted route"));
|
|
522
|
+
} else if (role.permission !== "read-only" || admitted.some((model) => model.permissions.length !== 1 || model.permissions[0] !== "read-only")) {
|
|
517
523
|
issues.push(issue("semantic", "permission_escalation", label, "critic purpose files must be read-only"));
|
|
518
524
|
}
|
|
519
525
|
}
|
|
@@ -549,7 +555,7 @@ function validateCriticVendors(roles, models, options, issues) {
|
|
|
549
555
|
const admitted = (role.roster ?? [])
|
|
550
556
|
.map((entry) => models.get(entry.route))
|
|
551
557
|
.filter((model) => model && admittedRoute(model));
|
|
552
|
-
if (admitted.length
|
|
558
|
+
if (admitted.length >= 1) critics.push({ purpose, vendor: canonicalVendor(admitted[0].vendor, aliases) });
|
|
553
559
|
}
|
|
554
560
|
if (critics.length === 2 && critics[0].vendor === critics[1].vendor) {
|
|
555
561
|
issues.push(issue("semantic", "critic_vendor_collision", "roles/reviewer-arch", "required critics must have independent vendors"));
|