@kontextmind/kxm 0.7.39 → 0.7.43
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 +41 -7
- package/docs/README.md +5 -3
- package/docs/agent-skills.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/configuration.md +28 -5
- package/docs/{vnext → contracts}/README.md +7 -7
- package/docs/{vnext → contracts}/architecture.md +2 -2
- package/docs/{vnext → contracts}/migration.md +25 -25
- package/docs/{vnext → contracts}/routing.md +2 -2
- package/docs/{vnext → contracts}/synchronization.md +1 -1
- package/docs/{vnext → contracts}/validation.md +7 -7
- package/docs/getting-started.md +1 -1
- package/docs/kb/qa-authentik-authentication.md +47 -0
- package/docs/kb/qa-extension-install-and-hub-bootstrap.md +68 -0
- package/docs/kb/qa-hub-on-a-public-host.md +31 -0
- package/docs/kb/qa-sqlite-vs-duckdb.md +18 -0
- package/docs/kb/qa-what-the-hub-stores.md +47 -0
- package/docs/packages.md +91 -0
- package/docs/templates/architecture.md +1 -1
- package/docs/test-matrix.md +6 -6
- package/docs/tui-components.md +131 -0
- package/examples/README.md +2 -2
- package/examples/{vnext → project}/.kxm/agents/critic-3.yaml +1 -1
- package/examples/{vnext → project}/README.md +4 -4
- package/package.json +18 -8
- package/packages/core/tui/CHANGELOG.md +19 -0
- package/packages/core/tui/LICENSE +21 -0
- package/packages/core/tui/README.md +45 -0
- package/packages/core/tui/dist/index.js +1439 -0
- package/packages/core/tui/src/adapters/pi.ts +73 -0
- package/packages/core/tui/src/adapters/terminal.ts +106 -0
- package/packages/core/tui/src/exports/index.ts +30 -0
- package/packages/core/tui/src/services/registry.ts +274 -0
- package/packages/core/tui/src/tui/keys.ts +130 -0
- package/packages/core/tui/src/tui/layout.ts +117 -0
- package/packages/core/tui/src/tui/panel.ts +435 -0
- package/packages/core/tui/src/tui/panelComponent.ts +208 -0
- package/packages/core/tui/src/tui/render.ts +309 -0
- package/packages/core/tui/src/tui/theme.ts +145 -0
- package/packages/core/tui/src/types/surface.ts +407 -0
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +681 -553
- package/plugins/kxm/dist/extension.js +18 -18
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/{vnext-runtime-supervisor.js → runtime-supervisor.js} +553 -553
- package/plugins/kxm/dist/runtime.js +608 -608
- package/plugins/kxm/dist/server.js +13 -13
- package/plugins/kxm/package.json +2 -2
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +2 -2
- package/plugins/kxm/skills/kxm-runs/SKILL.md +3 -3
- package/plugins/kxm/skills/kxm-session/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +1 -1
- package/plugins/kxm/src/autocomplete.ts +4 -4
- package/plugins/kxm/src/{vnext-bindings.ts → bindings.ts} +48 -48
- package/plugins/kxm/src/cli/hub.ts +3 -3
- package/plugins/kxm/src/cli/{vnext.ts → project.ts} +106 -106
- package/plugins/kxm/src/cli/roles.ts +9 -9
- package/plugins/kxm/src/cli/system.ts +2 -2
- package/plugins/kxm/src/cli/tasks.ts +8 -8
- package/plugins/kxm/src/cli/workflows.ts +8 -8
- package/plugins/kxm/src/cli.ts +47 -47
- package/plugins/kxm/src/config.ts +69 -14
- package/plugins/kxm/src/database.ts +4 -4
- package/plugins/kxm/src/{vnext-engine-artifacts.ts → engine-artifacts.ts} +7 -7
- package/plugins/kxm/src/{vnext-engine-command.ts → engine-command.ts} +40 -40
- package/plugins/kxm/src/{vnext-engine-compile.ts → engine-compile.ts} +64 -64
- package/plugins/kxm/src/{vnext-engine-evidence.ts → engine-evidence.ts} +26 -26
- package/plugins/kxm/src/{vnext-engine-fold.ts → engine-fold.ts} +99 -99
- package/plugins/kxm/src/{vnext-engine-gate-records.ts → engine-gate-records.ts} +105 -105
- package/plugins/kxm/src/{vnext-engine-plan.ts → engine-plan.ts} +69 -69
- package/plugins/kxm/src/{vnext-engine.ts → engine.ts} +502 -502
- package/plugins/kxm/src/gate-hash.ts +10 -0
- package/plugins/kxm/src/{vnext-harness.ts → harness.ts} +1 -1
- package/plugins/kxm/src/hub.ts +1 -1
- package/plugins/kxm/src/init-guide-setup.ts +3 -3
- package/plugins/kxm/src/{vnext-init.ts → init.ts} +93 -93
- package/plugins/kxm/src/kxm-install-kind.ts +1 -1
- package/plugins/kxm/src/kxm-update-config.ts +2 -2
- package/plugins/kxm/src/local-snapshot.ts +23 -23
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/{vnext-migrate.ts → migrate.ts} +123 -123
- package/plugins/kxm/src/model-inventory.ts +1 -1
- package/plugins/kxm/src/{vnext-oneshot-evidence.ts → oneshot-evidence.ts} +2 -2
- package/plugins/kxm/src/{vnext-oneshot-process.ts → oneshot-process.ts} +4 -4
- package/plugins/kxm/src/{vnext-oneshot-producer.ts → oneshot-producer.ts} +24 -24
- package/plugins/kxm/src/{vnext-permission.ts → permission.ts} +86 -86
- package/plugins/kxm/src/{vnext-pi-producer.ts → pi-producer.ts} +13 -13
- package/plugins/kxm/src/{vnext-config.ts → project-config.ts} +177 -177
- package/plugins/kxm/src/{vnext-repair.ts → repair.ts} +159 -159
- package/plugins/kxm/src/restricted-yaml.d.mts +3 -3
- package/plugins/kxm/src/restricted-yaml.mjs +4 -4
- package/plugins/kxm/src/{vnext-runtime-owner.ts → runtime-owner.ts} +35 -35
- package/plugins/kxm/src/{vnext-runtime.ts → runtime-service.ts} +136 -136
- package/plugins/kxm/src/{vnext-runtime-store.ts → runtime-store.ts} +145 -145
- package/plugins/kxm/src/{vnext-runtime-supervisor.ts → runtime-supervisor.ts} +80 -80
- package/plugins/kxm/src/runtime.ts +6 -6
- package/plugins/kxm/src/session-work.ts +3 -3
- package/plugins/kxm/src/studio-layout.ts +6 -6
- package/plugins/kxm/src/{vnext-template.ts → template.ts} +33 -33
- package/plugins/kxm/src/tui.ts +10 -24
- package/schemas/{vnext/README.md → README.md} +3 -3
- package/schemas/{vnext/agent.schema.json → agent.schema.json} +1 -1
- package/schemas/{vnext/assignment-result.schema.json → assignment-result.schema.json} +1 -1
- package/schemas/{vnext/backup-manifest.schema.json → backup-manifest.schema.json} +1 -1
- package/schemas/{vnext/candidate.schema.json → candidate.schema.json} +1 -1
- package/schemas/{vnext/common.schema.json → common.schema.json} +2 -2
- package/schemas/{vnext/context-candidate.schema.json → context-candidate.schema.json} +1 -1
- package/schemas/{vnext/context-packet.schema.json → context-packet.schema.json} +1 -1
- package/schemas/{vnext/delivery-manifest.schema.json → delivery-manifest.schema.json} +1 -1
- package/schemas/{vnext/drive-receipt.schema.json → drive-receipt.schema.json} +2 -2
- package/schemas/{vnext/environment.schema.json → environment.schema.json} +1 -1
- package/schemas/{vnext/gate-registry.schema.json → gate-registry.schema.json} +1 -1
- package/schemas/{vnext/handoff-manifest.schema.json → handoff-manifest.schema.json} +1 -1
- package/schemas/{vnext/init-operation.schema.json → init-operation.schema.json} +1 -1
- package/schemas/{vnext/local-repository-bindings.schema.json → local-repository-bindings.schema.json} +1 -1
- package/schemas/{vnext/memory-record.schema.json → memory-record.schema.json} +1 -1
- package/schemas/{vnext/migration-decision.schema.json → migration-decision.schema.json} +1 -1
- package/schemas/{vnext/migration-plan.schema.json → migration-plan.schema.json} +1 -1
- package/schemas/{vnext/migration-receipt.schema.json → migration-receipt.schema.json} +1 -1
- package/schemas/{vnext/model.schema.json → model.schema.json} +1 -1
- package/schemas/{vnext/modes.schema.json → modes.schema.json} +1 -1
- package/schemas/{vnext/permission-diff.schema.json → permission-diff.schema.json} +1 -1
- package/schemas/policy-draft/README.md +2 -2
- package/schemas/{vnext/prices.schema.json → prices.schema.json} +1 -1
- package/schemas/{vnext/project.schema.json → project.schema.json} +1 -1
- package/schemas/{vnext/repository.schema.json → repository.schema.json} +1 -1
- package/schemas/{vnext/role.schema.json → role.schema.json} +1 -1
- package/schemas/{vnext/run-event.schema.json → run-event.schema.json} +1 -1
- package/schemas/{vnext/session-brief.schema.json → session-brief.schema.json} +2 -2
- package/schemas/{vnext/sync-event.schema.json → sync-event.schema.json} +1 -1
- package/schemas/{vnext/template-provenance.schema.json → template-provenance.schema.json} +1 -1
- package/schemas/{vnext/workflow.schema.json → workflow.schema.json} +1 -1
- package/scripts/build-package.mjs +59 -0
- package/scripts/build-runtime.mjs +3 -2
- package/scripts/check-generated.mjs +2 -1
- package/scripts/check-versions.mjs +12 -1
- package/scripts/docker-install-smoke.mjs +268 -0
- package/scripts/emit-codex-artifacts.mjs +1 -1
- package/scripts/kxm-bump-version.mjs +53 -3
- package/scripts/kxm-runtime-supervisor.mjs +3 -3
- package/scripts/package-surfaces.mjs +39 -0
- package/scripts/roster-policy.mjs +1 -1
- package/plugins/kxm/src/vnext-gate-hash.ts +0 -10
- /package/docs/{vnext → contracts}/effects-and-recovery.md +0 -0
- /package/docs/{vnext → contracts}/lifecycles.md +0 -0
- /package/docs/{vnext → contracts}/terminology.md +0 -0
- /package/examples/{vnext → project}/.kxm/agents/coordinator.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/critic-1.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/critic-2.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/implementer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/planner.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/reproducer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/agents/reviewer.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/gates.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-claude.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-gemini.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/critic-grok.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/implementation.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/models/primary.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/prices.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/project/env.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/project.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/repo/repo.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/default.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/fix.yaml +0 -0
- /package/examples/{vnext → project}/.kxm/workflows/improve.yaml +0 -0
- /package/examples/{vnext → project}/records/assignment-result-recorded.json +0 -0
- /package/examples/{vnext → project}/records/assignment-result.json +0 -0
- /package/examples/{vnext → project}/records/context-candidate.json +0 -0
- /package/examples/{vnext → project}/records/delivery-manifest.json +0 -0
- /package/examples/{vnext → project}/records/effect-uncertainty-resolved-sync.json +0 -0
- /package/examples/{vnext → project}/records/effect-uncertainty-resolved.json +0 -0
- /package/examples/{vnext → project}/records/run-created.json +0 -0
- /package/examples/{vnext → project}/records/sync-event.json +0 -0
- /package/examples/{vnext → project}/repositories/api/.kxm/repo/env.yaml +0 -0
- /package/examples/{vnext → project}/repositories/api/.kxm/repo/repo.yaml +0 -0
- /package/examples/{vnext → project}/repositories/web/.kxm/repo/repo.yaml +0 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fresh-container install smoke: proves the packed kxm tarball installs into a
|
|
3
|
+
// clean Docker container with a brand-new Pi installation, that every runtime
|
|
4
|
+
// dependency lands, and that two configured plan providers complete a live
|
|
5
|
+
// turn. Secrets come from pass-cli and are injected through a mode-600 env
|
|
6
|
+
// file inside a temp directory that is removed afterwards; they never touch
|
|
7
|
+
// argv, the image, or the repository.
|
|
8
|
+
//
|
|
9
|
+
// Usage: just docker-install-smoke
|
|
10
|
+
// Requires: docker daemon, pass-cli session, ~/.pi/agent/models-store.json.
|
|
11
|
+
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { homedir, tmpdir } from "node:os";
|
|
15
|
+
import { join, resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
const repoRoot = resolve(import.meta.dirname, "..");
|
|
18
|
+
|
|
19
|
+
const vault = process.env.KXM_SMOKE_VAULT ?? "AI Provider Keys";
|
|
20
|
+
const image = process.env.KXM_SMOKE_IMAGE ?? "node:24-slim";
|
|
21
|
+
const modelsStorePath = process.env.KXM_SMOKE_MODELS_STORE ?? join(homedir(), ".pi/agent/models-store.json");
|
|
22
|
+
|
|
23
|
+
// provider id -> { env, item, field, model }. The zai key is stored in the
|
|
24
|
+
// SshKey public_key slot of its pass item (upstream quirk); the qwen key is an
|
|
25
|
+
// extra Hidden field named "API Key".
|
|
26
|
+
const providers = [
|
|
27
|
+
{
|
|
28
|
+
id: "zai-coding-cn",
|
|
29
|
+
env: "ZAI_CODING_CN_API_KEY",
|
|
30
|
+
item: process.env.KXM_SMOKE_ZAI_ITEM ?? "Z.ai",
|
|
31
|
+
field: process.env.KXM_SMOKE_ZAI_FIELD ?? "public_key",
|
|
32
|
+
model: process.env.KXM_SMOKE_ZAI_MODEL ?? "glm-5.3-flash",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "qwen-token-plan",
|
|
36
|
+
env: "QWEN_TOKEN_PLAN_API_KEY",
|
|
37
|
+
item: process.env.KXM_SMOKE_QWEN_ITEM ?? "Alibaba Cloud Personal Plan",
|
|
38
|
+
field: process.env.KXM_SMOKE_QWEN_FIELD ?? "API Key",
|
|
39
|
+
model: process.env.KXM_SMOKE_QWEN_MODEL ?? "qwen3.8-flash",
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function die(message) {
|
|
44
|
+
process.stderr.write(`docker-install-smoke: ${message}\n`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function run(command, args, options = {}) {
|
|
49
|
+
const result = spawnSync(command, args, { encoding: "utf8", ...options });
|
|
50
|
+
if (result.status !== 0) {
|
|
51
|
+
die(`\`${command} ${args.join(" ")}\` failed (${result.status})\n${result.stderr || result.stdout}`);
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function passItem(title) {
|
|
57
|
+
const result = spawnSync(
|
|
58
|
+
"pass-cli",
|
|
59
|
+
["item", "view", "--vault-name", vault, "--item-title", title, "--output", "json"],
|
|
60
|
+
{ encoding: "utf8" },
|
|
61
|
+
);
|
|
62
|
+
if (result.status !== 0) {
|
|
63
|
+
die(`pass-cli could not read item ${JSON.stringify(title)} in vault ${JSON.stringify(vault)}: ${result.stderr}`);
|
|
64
|
+
}
|
|
65
|
+
return JSON.parse(result.stdout)?.item?.content;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Field values hide in either extra_fields (by name) or the SshKey section.
|
|
69
|
+
function extractField(content, field) {
|
|
70
|
+
const extra = content.extra_fields?.find((candidate) => candidate.name === field);
|
|
71
|
+
if (extra) {
|
|
72
|
+
const values = extra.content ?? {};
|
|
73
|
+
return values.Hidden ?? values.Text;
|
|
74
|
+
}
|
|
75
|
+
return content.content?.SshKey?.[field];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function main() {
|
|
79
|
+
for (const tool of ["docker", "pass-cli", "npm"]) {
|
|
80
|
+
if (spawnSync(tool, ["--version"], { encoding: "utf8" }).status !== 0) {
|
|
81
|
+
die(`${tool} is required but not on PATH`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const daemon = spawnSync("docker", ["info", "--format", "ok"], { encoding: "utf8" });
|
|
85
|
+
if (daemon.status !== 0) {
|
|
86
|
+
die("docker daemon is not reachable; start Docker and retry");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const scratch = mkdtempSync(join(tmpdir(), "kxm-install-smoke-"));
|
|
90
|
+
try {
|
|
91
|
+
// 1. Pack the working tree. The committed dist must be current; the
|
|
92
|
+
// commit gate (`npm run verify` -> check:generated) enforces that.
|
|
93
|
+
process.stdout.write("==> npm pack\n");
|
|
94
|
+
const packed = run("npm", ["pack", "--pack-destination", scratch, "--json"], { cwd: repoRoot });
|
|
95
|
+
const filename = JSON.parse(packed.stdout)[0]?.filename;
|
|
96
|
+
if (!filename) die("npm pack produced no tarball");
|
|
97
|
+
const tarball = join(scratch, filename);
|
|
98
|
+
|
|
99
|
+
// 2. Provider models from the local pi catalog (no secrets in this file).
|
|
100
|
+
const store = JSON.parse(readFileSync(modelsStorePath, "utf8"));
|
|
101
|
+
const configured = {};
|
|
102
|
+
const envLines = [];
|
|
103
|
+
for (const provider of providers) {
|
|
104
|
+
const entry = store[provider.id];
|
|
105
|
+
if (!entry) {
|
|
106
|
+
die(`provider ${provider.id} is missing from ${modelsStorePath}`);
|
|
107
|
+
}
|
|
108
|
+
const models = entry.models.map((model) => {
|
|
109
|
+
const copy = { ...model };
|
|
110
|
+
delete copy.checkedAt;
|
|
111
|
+
delete copy.lastModified;
|
|
112
|
+
delete copy.etag;
|
|
113
|
+
return copy;
|
|
114
|
+
});
|
|
115
|
+
if (models.length === 0) die(`provider ${provider.id} has no models in ${modelsStorePath}`);
|
|
116
|
+
configured[provider.id] = {
|
|
117
|
+
baseUrl: models[0].baseUrl,
|
|
118
|
+
api: models[0].api ?? "openai-completions",
|
|
119
|
+
apiKey: `$${provider.env}`,
|
|
120
|
+
models,
|
|
121
|
+
};
|
|
122
|
+
const key = extractField(passItem(provider.item), provider.field);
|
|
123
|
+
if (!key) {
|
|
124
|
+
die(`no value found for ${provider.item} / ${provider.field} in vault ${vault}`);
|
|
125
|
+
}
|
|
126
|
+
process.stdout.write(`==> key for ${provider.id}: item=${JSON.stringify(provider.item)} field=${provider.field} len=${key.length}\n`);
|
|
127
|
+
envLines.push(`${provider.env}=${key}`);
|
|
128
|
+
}
|
|
129
|
+
const modelsJson = join(scratch, "models.json");
|
|
130
|
+
writeFileSync(modelsJson, `${JSON.stringify({ providers: configured }, null, 2)}\n`);
|
|
131
|
+
|
|
132
|
+
// 3. Keys go into a mode-600 env file only; docker --env-file injects them
|
|
133
|
+
// into the container environment without ever appearing in argv.
|
|
134
|
+
const envFile = join(scratch, "providers.env");
|
|
135
|
+
writeFileSync(envFile, `${envLines.join("\n")}\n`, { mode: 0o600 });
|
|
136
|
+
chmodSync(envFile, 0o600);
|
|
137
|
+
|
|
138
|
+
// 4. The in-container flow: fresh pi, both providers live, kxm operator
|
|
139
|
+
// install with the documented --omit=peer path, pi package install,
|
|
140
|
+
// then the README first-run flow.
|
|
141
|
+
const smokeModels = providers.map((provider) => ({
|
|
142
|
+
id: provider.id,
|
|
143
|
+
env: provider.env,
|
|
144
|
+
model: provider.model,
|
|
145
|
+
token: `${provider.id.toUpperCase().replaceAll(/[^A-Z]/g, "")}_SMOKE_OK`,
|
|
146
|
+
}));
|
|
147
|
+
const container = join(scratch, "container.sh");
|
|
148
|
+
writeFileSync(container, containerScript(smokeModels, filename));
|
|
149
|
+
|
|
150
|
+
process.stdout.write(`==> docker run ${image}\n`);
|
|
151
|
+
const result = spawnSync("docker", [
|
|
152
|
+
"run", "--rm",
|
|
153
|
+
"-v", `${scratch}:/mnt:ro`,
|
|
154
|
+
"--env-file", envFile,
|
|
155
|
+
image, "bash", "/mnt/container.sh",
|
|
156
|
+
], { stdio: "inherit" });
|
|
157
|
+
if (result.status !== 0) {
|
|
158
|
+
die(`container smoke failed with ${result.status}`);
|
|
159
|
+
}
|
|
160
|
+
process.stdout.write("==> install smoke passed\n");
|
|
161
|
+
} finally {
|
|
162
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function containerScript(smokeModels, filename) {
|
|
167
|
+
const [first, second] = smokeModels;
|
|
168
|
+
return `#!/bin/bash
|
|
169
|
+
# Generated by scripts/docker-install-smoke.mjs. Runs inside a clean container.
|
|
170
|
+
set -uo pipefail
|
|
171
|
+
fail() { echo "FAIL: $*" >&2; exit 1; }
|
|
172
|
+
|
|
173
|
+
echo "== toolchain versions =="
|
|
174
|
+
node --version
|
|
175
|
+
npm --version
|
|
176
|
+
apt-get update -qq >/dev/null 2>&1 && apt-get install -y -qq git >/dev/null 2>&1
|
|
177
|
+
git --version
|
|
178
|
+
|
|
179
|
+
echo
|
|
180
|
+
echo "== [1/7] fresh pi installation (npm global, --ignore-scripts) =="
|
|
181
|
+
npm install -g --ignore-scripts @earendil-works/pi-coding-agent --no-audit --no-fund >/tmp/pi-install.log 2>&1 \\
|
|
182
|
+
|| { tail -20 /tmp/pi-install.log; fail "pi global install failed"; }
|
|
183
|
+
pi --version || fail "pi not on PATH after global install"
|
|
184
|
+
|
|
185
|
+
echo
|
|
186
|
+
echo "== [2/7] custom providers load with env-var auth =="
|
|
187
|
+
mkdir -p ~/.pi/agent
|
|
188
|
+
cp /mnt/models.json ~/.pi/agent/models.json
|
|
189
|
+
${smokeModels.map((provider) => `env | grep -q '^${provider.env}=.' || fail "${provider.env} env missing"`).join("\n")}
|
|
190
|
+
LM=$(pi --list-models 2>&1) || fail "pi --list-models failed: $LM"
|
|
191
|
+
${smokeModels.map((provider) => `echo "$LM" | grep -qi "${provider.id}" || fail "${provider.id} models not available: $LM"`).join("\n")}
|
|
192
|
+
${smokeModels.map((provider) => `echo " ${provider.id}: $(echo "$LM" | grep -ci '${provider.id}') models listed"`).join("\n")}
|
|
193
|
+
|
|
194
|
+
echo
|
|
195
|
+
echo "== [3/7] live completion: ${first.id} / ${first.model} =="
|
|
196
|
+
Z=$(pi --provider ${first.id} --model ${first.model} -p "Reply with exactly this token and nothing else: ${first.token}") \\
|
|
197
|
+
|| fail "${first.id} completion failed"
|
|
198
|
+
echo " reply: $Z"
|
|
199
|
+
echo "$Z" | grep -q "${first.token}" || fail "${first.id} reply missing expected token"
|
|
200
|
+
|
|
201
|
+
echo
|
|
202
|
+
echo "== [4/7] live completion: ${second.id} / ${second.model} =="
|
|
203
|
+
Q=$(pi --provider ${second.id} --model ${second.model} -p "Reply with exactly this token and nothing else: ${second.token}") \\
|
|
204
|
+
|| fail "${second.id} completion failed"
|
|
205
|
+
echo " reply: $Q"
|
|
206
|
+
echo "$Q" | grep -q "${second.token}" || fail "${second.id} reply missing expected token"
|
|
207
|
+
|
|
208
|
+
echo
|
|
209
|
+
echo "== [5/7] kxm operator CLI: documented global --omit=peer tarball install =="
|
|
210
|
+
GROOT_BEFORE=$(ls "$(npm root -g)")
|
|
211
|
+
npm install --global --omit=peer --no-audit --no-fund /mnt/${filename} >/tmp/kxm-install.log 2>&1 \\
|
|
212
|
+
|| { tail -20 /tmp/kxm-install.log; fail "kxm global install failed"; }
|
|
213
|
+
kxm hub help 2>&1 | grep -q "Usage: kxm hub" || fail "kxm hub help did not run (runtime deps missing?)"
|
|
214
|
+
echo " kxm bin runs: OK"
|
|
215
|
+
GROOT=$(npm root -g)
|
|
216
|
+
for d in @anthropic-ai/claude-agent-sdk @earendil-works/pi-tui @modelcontextprotocol/sdk yaml; do
|
|
217
|
+
if [ -d "$GROOT/@kontextmind/kxm/node_modules/$d" ] || [ -d "$GROOT/$d" ]; then
|
|
218
|
+
echo " dependency installed: $d"
|
|
219
|
+
else
|
|
220
|
+
fail "dependency NOT installed: $d"
|
|
221
|
+
fi
|
|
222
|
+
done
|
|
223
|
+
if [ -d "$GROOT/typebox" ] && ! echo "$GROOT_BEFORE" | grep -q "^typebox$"; then
|
|
224
|
+
fail "peer typebox was installed despite --omit=peer"
|
|
225
|
+
fi
|
|
226
|
+
echo " peers correctly omitted (--omit=peer); CLI still runs"
|
|
227
|
+
|
|
228
|
+
echo
|
|
229
|
+
echo "== [6/7] pi package surface (extension + skills) =="
|
|
230
|
+
mkdir -p /opt/kxm-pkg
|
|
231
|
+
tar -xzf /mnt/${filename} -C /opt/kxm-pkg --strip-components=1
|
|
232
|
+
cd /opt/kxm-pkg
|
|
233
|
+
npm install --omit=dev --no-audit --no-fund >/tmp/pkg-install.log 2>&1 \\
|
|
234
|
+
|| { tail -20 /tmp/pkg-install.log; fail "package npm install failed (pi git-install step)"; }
|
|
235
|
+
pi install /opt/kxm-pkg >/tmp/pi-install-pkg.log 2>&1 || { tail -20 /tmp/pi-install-pkg.log; fail "pi install of kxm package failed"; }
|
|
236
|
+
pi list 2>&1 | grep -qi "kxm" || fail "pi list does not show kxm"
|
|
237
|
+
echo " pi list shows kxm package: OK"
|
|
238
|
+
|
|
239
|
+
echo
|
|
240
|
+
echo "== [7/7] first-run flow: kxm init + hub + bind + session brief =="
|
|
241
|
+
mkdir -p /testproj && cd /testproj && git init -q .
|
|
242
|
+
kxm init --json --name docker-smoke --project-id prj_01JDOCKERTESTSMOKE000000000 || fail "kxm init failed"
|
|
243
|
+
export KXM_AUTH_TOKEN="docker-admin-token"
|
|
244
|
+
export KXM_PROJECT_TOKENS='{"docker-smoke":"proj-token"}'
|
|
245
|
+
nohup kxm hub start >/tmp/hub.log 2>&1 &
|
|
246
|
+
for i in $(seq 1 30); do grep -q "listening at" /tmp/hub.log 2>/dev/null && break; sleep 1; done
|
|
247
|
+
grep "listening at" /tmp/hub.log || { cat /tmp/hub.log; fail "hub did not start"; }
|
|
248
|
+
kxm hub bind http://127.0.0.1:7331 || fail "hub bind failed"
|
|
249
|
+
kxm session brief >/dev/null || fail "session brief failed"
|
|
250
|
+
echo " first-run flow: OK"
|
|
251
|
+
|
|
252
|
+
echo
|
|
253
|
+
echo "== final: pi print-mode run with kxm extension loaded =="
|
|
254
|
+
OUT=$(pi --provider ${first.id} --model ${first.model} -p "Reply with exactly: FINAL_OK" 2>/tmp/pi-ext.err) \\
|
|
255
|
+
|| { cat /tmp/pi-ext.err; fail "final pi run failed"; }
|
|
256
|
+
if grep -iE "failed to load|extension error|cannot find" /tmp/pi-ext.err >/dev/null; then
|
|
257
|
+
cat /tmp/pi-ext.err; fail "kxm extension load errors in pi"
|
|
258
|
+
fi
|
|
259
|
+
echo " no extension load errors"
|
|
260
|
+
echo " final reply: $OUT"
|
|
261
|
+
echo "$OUT" | grep -q "FINAL_OK" || fail "final reply missing expected token"
|
|
262
|
+
|
|
263
|
+
echo
|
|
264
|
+
echo "ALL CHECKS PASSED"
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
main();
|
|
@@ -45,7 +45,7 @@ The \`kxm\` CLI is the unified agent surface for peer collaboration and workflow
|
|
|
45
45
|
| \`kxm workflow checkpoint [runId] [stageId] [status] [summary]\` | Record stage result with verified evidence | \`--run-id\`, \`--stage-id\`, \`--status <passed\\|warning\\|failed>\`, \`--summary\`, \`--evidence <json>\`, \`--evidence-refs <json>\` |
|
|
46
46
|
| \`kxm workflow record [runId] [category] [area] [summary]\` | Record plans, decisions, contradictions, errors, lessons | \`--run-id\`, \`--category <plan\\|decision\\|contradiction\\|error\\|lesson>\`, \`--area\`, \`--severity <info\\|warning\\|error>\`, \`--details\`, \`--evidence <items...>\` |
|
|
47
47
|
| \`kxm workflow wait [runId] [stageId] [signalKey] [summary]\` | Pause stage until an external signed signal arrives | \`--run-id\`, \`--stage-id\`, \`--signal-key\`, \`--summary\`, \`--evidence <json>\`, \`--evidence-refs <json>\`, \`--timeout-ms\` |
|
|
48
|
-
| \`kxm workflow signal <runId> <signalKey> <status> <summary>\` | Resume or unblock a waiting stage or
|
|
48
|
+
| \`kxm workflow signal <runId> <signalKey> <status> <summary>\` | Resume or unblock a waiting stage or KXM run | \`[evidence...]\`, \`--delivery-id\` |
|
|
49
49
|
| \`kxm workflow list\` | List local workflow runs | \`--json\` |
|
|
50
50
|
| \`kxm workflow get <runId>\` | Get stages and journal for a run | \`--json\` |
|
|
51
51
|
|
|
@@ -9,14 +9,22 @@
|
|
|
9
9
|
* node scripts/kxm-bump-version.mjs minor # bump minor
|
|
10
10
|
* node scripts/kxm-bump-version.mjs major # bump major
|
|
11
11
|
*
|
|
12
|
-
* Surfaces updated (must stay in sync with scripts/check-versions.mjs
|
|
12
|
+
* Surfaces updated (must stay in sync with scripts/check-versions.mjs — both
|
|
13
|
+
* take their package list from scripts/package-surfaces.mjs):
|
|
13
14
|
* 1. package.json .version
|
|
14
15
|
* 2. package-lock.json .version + .packages[""].version
|
|
16
|
+
* + .packages[<workspace path>].version
|
|
15
17
|
* 3. plugins/kxm/package.json .version
|
|
16
18
|
* 4. plugins/kxm/.claude-plugin/plugin.json .version
|
|
17
19
|
* 5. .claude-plugin/marketplace.json .plugins[name=kxm].version
|
|
18
20
|
* 6. plugins/kxm/src/mcp-server.ts const VERSION = "..."
|
|
19
21
|
* 7. plugins/kxm/dist/mcp-server.js const VERSION = "..." (rebuilt)
|
|
22
|
+
* 8. each workspace manifest scanned under packages/ —
|
|
23
|
+
* packages/<name>/package.json and packages/<tier>/<name>/package.json
|
|
24
|
+
*
|
|
25
|
+
* The lockfile is patched by key, never by searching for the old version string:
|
|
26
|
+
* a third-party dependency that happens to carry the same version as the
|
|
27
|
+
* product must not be rewritten into a phantom artifact.
|
|
20
28
|
*
|
|
21
29
|
* After running this script, run `npm run build` to rebuild dist bundles.
|
|
22
30
|
*/
|
|
@@ -24,6 +32,7 @@
|
|
|
24
32
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
25
33
|
import { resolve } from "node:path";
|
|
26
34
|
import { fileURLToPath } from "node:url";
|
|
35
|
+
import { workspaceManifestPaths } from "./package-surfaces.mjs";
|
|
27
36
|
|
|
28
37
|
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
|
|
29
38
|
|
|
@@ -79,6 +88,43 @@ function patchSourceVersion(filePath, newVersion) {
|
|
|
79
88
|
writeFileSync(filePath, patched, "utf8");
|
|
80
89
|
}
|
|
81
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Patch the lockfile structurally: the root entry and each workspace package
|
|
93
|
+
* entry, by key. Everything else — including an unrelated dependency whose
|
|
94
|
+
* version string equals ours — is left byte-for-byte alone.
|
|
95
|
+
*/
|
|
96
|
+
function patchLockVersions(root, newVersion) {
|
|
97
|
+
const file = resolve(root, "package-lock.json");
|
|
98
|
+
const raw = readFileSync(file, "utf8");
|
|
99
|
+
let data;
|
|
100
|
+
try {
|
|
101
|
+
data = JSON.parse(raw);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new Error(`package-lock.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
104
|
+
}
|
|
105
|
+
const touched = [];
|
|
106
|
+
if (typeof data.version === "string") {
|
|
107
|
+
data.version = newVersion;
|
|
108
|
+
touched.push("version");
|
|
109
|
+
}
|
|
110
|
+
if (data.packages?.[""]) {
|
|
111
|
+
data.packages[""].version = newVersion;
|
|
112
|
+
touched.push('packages[""]');
|
|
113
|
+
}
|
|
114
|
+
for (const rel of workspaceManifestPaths(root)) {
|
|
115
|
+
const entry = data.packages?.[rel];
|
|
116
|
+
if (!entry) {
|
|
117
|
+
throw new Error(`package-lock.json has no entry for workspace package ${rel}; run npm install to resync the lock`);
|
|
118
|
+
}
|
|
119
|
+
entry.version = newVersion;
|
|
120
|
+
touched.push(`packages[${rel}]`);
|
|
121
|
+
}
|
|
122
|
+
if (touched.length === 0) throw new Error("no version fields found to patch in package-lock.json");
|
|
123
|
+
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
124
|
+
writeFileSync(file, `${JSON.stringify(data, null, 2)}${trailingNewline}`, "utf8");
|
|
125
|
+
return touched;
|
|
126
|
+
}
|
|
127
|
+
|
|
82
128
|
export function applyVersionBump(root, newVersion) {
|
|
83
129
|
if (!parseSemver(newVersion)) {
|
|
84
130
|
throw new Error(`invalid semver: ${newVersion}`);
|
|
@@ -88,18 +134,22 @@ export function applyVersionBump(root, newVersion) {
|
|
|
88
134
|
const pkgPath = resolve(root, "package.json");
|
|
89
135
|
const oldVersion = JSON.parse(readFileSync(pkgPath, "utf8")).version;
|
|
90
136
|
|
|
91
|
-
// JSON
|
|
137
|
+
// JSON manifests (targeted patch preserves formatting). Workspace packages are
|
|
138
|
+
// scanned, not hard-coded: a new package must not be releasable-then-forgotten.
|
|
92
139
|
const jsonFiles = [
|
|
93
140
|
"package.json",
|
|
94
|
-
"package-lock.json",
|
|
95
141
|
"plugins/kxm/package.json",
|
|
96
142
|
"plugins/kxm/.claude-plugin/plugin.json",
|
|
97
143
|
".claude-plugin/marketplace.json",
|
|
144
|
+
...workspaceManifestPaths(root).map((rel) => `${rel}/package.json`),
|
|
98
145
|
];
|
|
99
146
|
for (const rel of jsonFiles) {
|
|
100
147
|
patchJsonVersion(resolve(root, rel), oldVersion, newVersion);
|
|
101
148
|
}
|
|
102
149
|
|
|
150
|
+
// Lockfile by key, after the manifests so a missing workspace entry fails loudly.
|
|
151
|
+
patchLockVersions(root, newVersion);
|
|
152
|
+
|
|
103
153
|
// TypeScript source
|
|
104
154
|
patchSourceVersion(resolve(root, "plugins/kxm/src/mcp-server.ts"), newVersion);
|
|
105
155
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// KXM
|
|
3
|
-
import {
|
|
2
|
+
// KXM Runtime supervisor entry point. Detached child of `kxm runtime start`.
|
|
3
|
+
import { startKxmRuntimeSupervisor } from "../plugins/kxm/dist/runtime-supervisor.js";
|
|
4
4
|
|
|
5
|
-
const supervisor = await
|
|
5
|
+
const supervisor = await startKxmRuntimeSupervisor({});
|
|
6
6
|
process.stdout.write(`kxm-runtime-supervisor ready runtimeId=${supervisor.runtimeId} port=${supervisor.port}\n`);
|
|
7
7
|
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single source of truth for "which directories are KXM packages".
|
|
3
|
+
*
|
|
4
|
+
* `scripts/check-versions.mjs` (the gate) and `scripts/kxm-bump-version.mjs`
|
|
5
|
+
* (the release writer) must agree exactly, or a release bumps some surfaces and
|
|
6
|
+
* not others: the 0.7.41 release job failed because a workspace manifest existed
|
|
7
|
+
* for the checker but not for the bumper. Both now import this scan.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Every package directory under `packages/`, one or two levels deep
|
|
15
|
+
* (`packages/<name>`, `packages/<tier>/<name>`), as POSIX-style paths relative
|
|
16
|
+
* to `root` — the same shape `package-lock.json` uses for its `packages` keys.
|
|
17
|
+
*
|
|
18
|
+
* A directory that itself carries a `package.json` is a package and is not
|
|
19
|
+
* descended into further.
|
|
20
|
+
*/
|
|
21
|
+
export function workspaceManifestPaths(root) {
|
|
22
|
+
const found = [];
|
|
23
|
+
const packagesDir = resolve(root, "packages");
|
|
24
|
+
const scan = (absoluteDir, relativeDir, depth) => {
|
|
25
|
+
if (depth > 2 || !existsSync(absoluteDir)) return;
|
|
26
|
+
for (const entry of readdirSync(absoluteDir, { withFileTypes: true })) {
|
|
27
|
+
if (!entry.isDirectory() || entry.name === "node_modules") continue;
|
|
28
|
+
const abs = join(absoluteDir, entry.name);
|
|
29
|
+
const rel = relativeDir ? `${relativeDir}/${entry.name}` : entry.name;
|
|
30
|
+
if (existsSync(join(abs, "package.json"))) {
|
|
31
|
+
found.push(rel);
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
scan(abs, rel, depth + 1);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
scan(packagesDir, "packages", 1);
|
|
38
|
+
return found.sort();
|
|
39
|
+
}
|
|
@@ -179,7 +179,7 @@ export function loadTrustedRosterPolicy() {
|
|
|
179
179
|
const snapshot = control();
|
|
180
180
|
const { blob, bytes } = blobAt(snapshot.head, POLICY);
|
|
181
181
|
if (!workingBytes(POLICY).equals(bytes)) refuse('working policy differs from committed bytes');
|
|
182
|
-
const policy = validate(bytes, snapshot.head, trusted);
|
|
182
|
+
const policy = validate(bytes, snapshot.head, snapshot.trusted);
|
|
183
183
|
unchanged(snapshot);
|
|
184
184
|
return frozen({ identity: { commit: snapshot.head, blob, sha256: sha256(bytes) }, policy });
|
|
185
185
|
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { vnextCanonicalJson, type JsonValue } from "./vnext-config.ts";
|
|
2
|
-
import { vnextSha256 } from "./vnext-engine-plan.ts";
|
|
3
|
-
|
|
4
|
-
export function gateDefinitionHash(id: string, definition: JsonValue): string {
|
|
5
|
-
return vnextSha256(vnextCanonicalJson({ id, ...(definition as object) } as JsonValue));
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function gateRegistryHash(registryValue: JsonValue): string {
|
|
9
|
-
return vnextSha256(vnextCanonicalJson(registryValue));
|
|
10
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|