@kontextmind/kxm 0.7.108 → 0.7.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@
11
11
  "name": "kxm",
12
12
  "source": "./plugins/kxm",
13
13
  "description": "Durable workflows, peer agents, and kxm tui",
14
- "version": "0.7.108",
14
+ "version": "0.7.110",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.108",
3
+ "version": "0.7.110",
4
4
  "description": "KXM local-first multi-agent orchestration and operator dashboard",
5
5
  "type": "module",
6
6
  "author": "KontextMind",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
3
  "name": "kxm",
4
4
  "displayName": "KXM",
5
- "version": "0.7.108",
5
+ "version": "0.7.110",
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",
@@ -35052,7 +35052,7 @@ function formatKxmSyncStatus(sync, running) {
35052
35052
  return sync.map((project) => {
35053
35053
  const codes = project.outbox.refusals.map((refusal) => `${refusal.code} x${refusal.count}`).join(", ");
35054
35054
  if (project.storeReadable === false) {
35055
- return `sync ${project.projectId}: blocked (its store is not readable by this build) \u2014 ${project.lastError ?? "unknown reason"}`;
35055
+ return `sync ${project.projectId}: blocked (this build cannot open the project) \u2014 ${project.lastError ?? "unknown reason"}`;
35056
35056
  }
35057
35057
  const counts = `pending ${project.outbox.pending}, acked ${project.outbox.acked}, refused ${project.outbox.refused}`;
35058
35058
  const tail = project.state === "refusing" ? ` (${codes || "see log"}) \u2014 fix the hub, then: kxm runtime sync-retry` : project.state === "blocked" ? ` \u2014 last error: ${project.lastError ?? "unreachable"}${project.nextAttemptAt ? `; next attempt ${project.nextAttemptAt}` : ""}` : "";
@@ -17313,7 +17313,7 @@ function sessionTokenFixHint(policy) {
17313
17313
  }
17314
17314
 
17315
17315
  // plugins/kxm/src/mcp-server.ts
17316
- var VERSION = "0.7.108";
17316
+ var VERSION = "0.7.110";
17317
17317
  var CONFIGURE_PLUGIN = "/plugin configure kxm@kxm";
17318
17318
  var inbox = /* @__PURE__ */ new Map();
17319
17319
  var notifiedInbox = /* @__PURE__ */ new Set();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.108",
3
+ "version": "0.7.110",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -1003,7 +1003,11 @@ function formatKxmSyncStatus(sync: KxmProjectSyncStatus[] | undefined, running:
1003
1003
  // empty outbox. Saying "pending 0" there would send the operator looking
1004
1004
  // somewhere else while every row in the project sits unreachable.
1005
1005
  if (project.storeReadable === false) {
1006
- return `sync ${project.projectId}: blocked (its store is not readable by this build) — ${project.lastError ?? "unknown reason"}`;
1006
+ // "cannot open this project", not "its store is unreadable": what the
1007
+ // supervisor actually sees is whatever `openKxmRuntimeContext` refused —
1008
+ // an out-of-date event store *or* a project config a newer brake rejects.
1009
+ // Witnessed on kxm-dev-svr on 0.7.94, where it was the workflow config.
1010
+ return `sync ${project.projectId}: blocked (this build cannot open the project) — ${project.lastError ?? "unknown reason"}`;
1007
1011
  }
1008
1012
  const counts = `pending ${project.outbox.pending}, acked ${project.outbox.acked}, refused ${project.outbox.refused}`;
1009
1013
  const tail = project.state === "refusing"
@@ -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.108";
14
+ const VERSION = "0.7.110";
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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
@@ -22,6 +22,55 @@ export const STATIC_GENERATED_ARTIFACTS = Object.freeze([
22
22
  "GEMINI.md",
23
23
  ]);
24
24
 
25
+ /**
26
+ * Packages whose installed version differs from `package-lock.json`.
27
+ *
28
+ * The generated bundles embed dependency **bytes and paths**, so a
29
+ * `node_modules` that has drifted from the lock produces artifacts that pass
30
+ * this check locally and fail in CI — which installs from the lock on every
31
+ * leg. That asymmetry is worse than a broken build: it lets a machine ship a
32
+ * bundle nobody else can reproduce. Refuse before the build, naming the
33
+ * packages, instead of reporting a diff the developer cannot act on.
34
+ *
35
+ * @param {string} [repository]
36
+ * @returns {string[]}
37
+ */
38
+ export function findLockfileDrift(repository = process.cwd()) {
39
+ const root = resolve(repository);
40
+ if (!existsSync(resolve(root, "node_modules"))) return [];
41
+ const lockPath = resolve(root, "package-lock.json");
42
+ if (!existsSync(lockPath)) return [];
43
+ let lock;
44
+ try {
45
+ lock = JSON.parse(readFileSync(lockPath, "utf8"));
46
+ } catch {
47
+ return ["package-lock.json is unreadable"];
48
+ }
49
+ const packages = lock?.packages;
50
+ if (!packages || typeof packages !== "object") return [];
51
+ const drifted = [];
52
+ for (const [key, entry] of Object.entries(packages)) {
53
+ if (!key.startsWith("node_modules/")) continue; // the root importer and workspaces
54
+ if (entry?.link) continue; // a symlinked workspace, not an installed version
55
+ const name = key.split("node_modules/").pop();
56
+ const manifest = resolve(root, key, "package.json");
57
+ if (!existsSync(manifest)) {
58
+ if (!entry.optional) drifted.push(`${name}@${entry.version} is not installed`);
59
+ continue;
60
+ }
61
+ let installed;
62
+ try {
63
+ installed = JSON.parse(readFileSync(manifest, "utf8")).version;
64
+ } catch {
65
+ installed = undefined;
66
+ }
67
+ if (installed !== entry.version) {
68
+ drifted.push(`${name}: lock ${entry.version}, installed ${installed ?? "unreadable"}`);
69
+ }
70
+ }
71
+ return drifted;
72
+ }
73
+
25
74
  /**
26
75
  * Compute the generated artifact list from the committed suite manifest.
27
76
  * The manifest is required; missing or invalid manifests fail closed.
@@ -65,6 +114,16 @@ export function checkGeneratedArtifacts(repository = process.cwd()) {
65
114
  const root = resolve(topLevel);
66
115
  const artifacts = computeGeneratedArtifacts(root);
67
116
 
117
+ const drifted = findLockfileDrift(root);
118
+ if (drifted.length > 0) {
119
+ const shown = drifted.slice(0, 8).map((line) => ` ${line}`).join("\n");
120
+ throw new Error(
121
+ `node_modules does not match package-lock.json (${drifted.length} package(s)); `
122
+ + "the generated bundles embed dependency bytes, so CI — which runs npm ci — will rebuild them differently.\n"
123
+ + `${shown}${drifted.length > 8 ? "\n …" : ""}\nRun: npm ci`,
124
+ );
125
+ }
126
+
68
127
  const missing = artifacts.filter((path) => !existsSync(resolve(root, path)));
69
128
  if (missing.length > 0) {
70
129
  throw new Error(`generated artifacts are missing: ${missing.join(", ")}`);