@kontextmind/kxm 0.7.83 → 0.7.85

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.83",
14
+ "version": "0.7.85",
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.83",
3
+ "version": "0.7.85",
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.83",
5
+ "version": "0.7.85",
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",
@@ -18176,6 +18176,13 @@ function readInstalledKxmVersion(root) {
18176
18176
  }
18177
18177
  return pkg.version;
18178
18178
  }
18179
+ function installedKxmVersion(root) {
18180
+ try {
18181
+ return readInstalledKxmVersion(root);
18182
+ } catch {
18183
+ return void 0;
18184
+ }
18185
+ }
18179
18186
  function parseSemver(value) {
18180
18187
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
18181
18188
  if (!match) return void 0;
@@ -44610,6 +44617,13 @@ function installProbeFrom(runtime) {
44610
44617
  env: partial.env ?? runtime.env
44611
44618
  };
44612
44619
  }
44620
+ function moduleInstallRoot() {
44621
+ try {
44622
+ return findKxmRepoRoot(import.meta.url);
44623
+ } catch {
44624
+ return dirname20(fileURLToPath3(import.meta.url));
44625
+ }
44626
+ }
44613
44627
  function warnIgnoredProjectUpdateYaml(runtime) {
44614
44628
  const projectFile = join41(runtime.dirs.workspace, "update.yaml");
44615
44629
  if (!existsSync31(projectFile)) return;
@@ -44617,11 +44631,20 @@ function warnIgnoredProjectUpdateYaml(runtime) {
44617
44631
  runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}
44618
44632
  `);
44619
44633
  }
44620
- async function refreshKxmUpdateNotice(runtime, config) {
44634
+ async function refreshKxmUpdateNotice(runtime, config, current) {
44621
44635
  const resolved = config ?? loadKxmUpdateConfig(runtime.env);
44622
- const current = readInstalledKxmVersion(resolve24("."));
44636
+ const installed = current ?? installedKxmVersion(findKxmRepoRoot(import.meta.url)) ?? installedKxmVersion(moduleInstallRoot());
44637
+ if (installed === void 0) {
44638
+ return {
44639
+ current: "unknown",
44640
+ available: false,
44641
+ auto: false,
44642
+ source: resolved.source,
44643
+ message: `kxm update check skipped: the installed version is unreadable at ${moduleInstallRoot()}`
44644
+ };
44645
+ }
44623
44646
  const fetched = await fetchLatestKxmVersion(resolved.source, runtime.env, runtime.fetchImpl);
44624
- const notice = noticeFromVersions(current, fetched.latest, resolved, fetched.error, fetched.asset);
44647
+ const notice = noticeFromVersions(installed, fetched.latest, resolved, fetched.error, fetched.asset);
44625
44648
  writeUpdateCache(runtime.dirs.state, notice);
44626
44649
  return notice;
44627
44650
  }
@@ -47142,16 +47165,16 @@ async function cmdUpdate(runtime, harness, options) {
47142
47165
  warnIgnoredProjectUpdateYaml(runtime);
47143
47166
  const probe = installProbeFrom(runtime);
47144
47167
  const classified = classifyInstallRoot(probe);
47145
- const current = readInstalledKxmVersion(findKxmRepoRoot(import.meta.url));
47168
+ const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
47146
47169
  let notice;
47147
47170
  let kindReport = classified;
47148
47171
  if (classified.kind === "source") {
47149
47172
  if (options.check) {
47150
- const message = `kxm ${current} (running from source at ${classified.root})`;
47173
+ const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
47151
47174
  print(runtime.io, runtime.json, {
47152
47175
  ok: true,
47153
47176
  command: "update check",
47154
- current,
47177
+ current: current ?? "unknown",
47155
47178
  available: false,
47156
47179
  auto: false,
47157
47180
  source: "github",
@@ -47173,15 +47196,15 @@ async function cmdUpdate(runtime, harness, options) {
47173
47196
  return 2;
47174
47197
  }
47175
47198
  notice = {
47176
- current,
47199
+ current: current ?? "unknown",
47177
47200
  available: false,
47178
47201
  auto: false,
47179
47202
  source: "github",
47180
- message: `kxm ${current} (running from source)`
47203
+ message: `kxm ${current ?? "unknown"} (running from source)`
47181
47204
  };
47182
47205
  } else {
47183
47206
  try {
47184
- notice = await refreshKxmUpdateNotice(runtime);
47207
+ notice = await refreshKxmUpdateNotice(runtime, void 0, current);
47185
47208
  } catch (error) {
47186
47209
  if (error instanceof KxmUpdateConfigError) {
47187
47210
  print(runtime.io, runtime.json, { ok: false, command: "update", error: error.code, ...installKindPayload(classified) }, error.message);
@@ -17182,7 +17182,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
17182
17182
  }
17183
17183
 
17184
17184
  // plugins/kxm/src/mcp-server.ts
17185
- var VERSION = "0.7.83";
17185
+ var VERSION = "0.7.85";
17186
17186
  var inbox = /* @__PURE__ */ new Map();
17187
17187
  var notifiedInbox = /* @__PURE__ */ new Set();
17188
17188
  var meshClient;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.83",
3
+ "version": "0.7.85",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -93,7 +93,17 @@ export function installProbeFrom(runtime: Runtime): InstallProbe {
93
93
 
94
94
  import { fileURLToPath } from "node:url";
95
95
  import { dirname } from "node:path";
96
- import { tryFindKxmRepoRoot } from "../repo-root.ts";
96
+ import { findKxmRepoRoot, tryFindKxmRepoRoot } from "../repo-root.ts";
97
+ import { installedKxmVersion } from "../kxm-update.ts";
98
+
99
+ /** The install root derived from the loaded module; `unknown` never crashes a caller. */
100
+ function moduleInstallRoot(): string {
101
+ try {
102
+ return findKxmRepoRoot(import.meta.url);
103
+ } catch {
104
+ return dirname(fileURLToPath(import.meta.url));
105
+ }
106
+ }
97
107
  import { homedir } from "node:os";
98
108
 
99
109
  export function warnIgnoredProjectUpdateYaml(runtime: Runtime): void {
@@ -103,11 +113,33 @@ export function warnIgnoredProjectUpdateYaml(runtime: Runtime): void {
103
113
  runtime.io.stderr(`kxm: ignoring .kxm/update.yaml in ${runtime.dirs.workdir}; update settings are read only from ${userFile}\n`);
104
114
  }
105
115
 
106
- export async function refreshKxmUpdateNotice(runtime: Runtime, config?: KxmUpdateConfig): Promise<KxmUpdateNotice> {
116
+ export async function refreshKxmUpdateNotice(
117
+ runtime: Runtime,
118
+ config?: KxmUpdateConfig,
119
+ current?: string,
120
+ ): Promise<KxmUpdateNotice> {
107
121
  const resolved = config ?? loadKxmUpdateConfig(runtime.env);
108
- const current = readInstalledKxmVersion(resolve("."));
122
+ // The version being compared is the one **running**, taken from the install
123
+ // root. Reading it from the caller's directory made `cd ~ && kxm update --kxm`
124
+ // die with a raw ENOENT on `$HOME/package.json` — and silently compared a
125
+ // stranger's version wherever the operator happened to stand and a
126
+ // package.json did exist. Callers that already resolved it pass it in.
127
+ const installed = current
128
+ ?? installedKxmVersion(findKxmRepoRoot(import.meta.url))
129
+ ?? installedKxmVersion(moduleInstallRoot());
130
+ if (installed === undefined) {
131
+ // No version to compare against: say nothing rather than crash a hub start
132
+ // over an update notice.
133
+ return {
134
+ current: "unknown",
135
+ available: false,
136
+ auto: false,
137
+ source: resolved.source,
138
+ message: `kxm update check skipped: the installed version is unreadable at ${moduleInstallRoot()}`,
139
+ };
140
+ }
109
141
  const fetched = await fetchLatestKxmVersion(resolved.source, runtime.env, runtime.fetchImpl);
110
- const notice = noticeFromVersions(current, fetched.latest, resolved, fetched.error, fetched.asset);
142
+ const notice = noticeFromVersions(installed, fetched.latest, resolved, fetched.error, fetched.asset);
111
143
  writeUpdateCache(runtime.dirs.state, notice);
112
144
  return notice;
113
145
  }
@@ -63,6 +63,7 @@ import {
63
63
  type InstallProbe,
64
64
  } from "../kxm-install-kind.ts";
65
65
  import {
66
+ installedKxmVersion,
66
67
  fetchLatestKxmVersion,
67
68
  kxmReleaseAssetName,
68
69
  noticeFromVersions,
@@ -296,16 +297,22 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
296
297
  warnIgnoredProjectUpdateYaml(runtime);
297
298
  const probe = installProbeFrom(runtime);
298
299
  const classified = classifyInstallRoot(probe);
299
- const current = readInstalledKxmVersion(findKxmRepoRoot(import.meta.url));
300
+ // The version of the install being updated, taken from the root that was just
301
+ // classified — not from whichever directory the operator stood in. A
302
+ // `readInstalledKxmVersion(resolve("."))` in the notice path turned
303
+ // `cd ~ && kxm update --kxm` into an uncaught ENOENT on `$HOME/package.json`,
304
+ // and would happily compare a stranger project's version wherever one did
305
+ // exist. Unreadable is now a stated refusal.
306
+ const current = installedKxmVersion(classified.root) ?? installedKxmVersion(findKxmRepoRoot(import.meta.url));
300
307
  let notice: KxmUpdateNotice;
301
308
  let kindReport = classified;
302
309
  if (classified.kind === "source") {
303
310
  if (options.check) {
304
- const message = `kxm ${current} (running from source at ${classified.root})`;
311
+ const message = `kxm ${current ?? "unknown"} (running from source at ${classified.root})`;
305
312
  print(runtime.io, runtime.json, {
306
313
  ok: true,
307
314
  command: "update check",
308
- current,
315
+ current: current ?? "unknown",
309
316
  available: false,
310
317
  auto: false,
311
318
  source: "github",
@@ -327,15 +334,15 @@ export async function cmdUpdate(runtime: Runtime, harness: string | undefined, o
327
334
  return 2;
328
335
  }
329
336
  notice = {
330
- current,
337
+ current: current ?? "unknown",
331
338
  available: false,
332
339
  auto: false,
333
340
  source: "github",
334
- message: `kxm ${current} (running from source)`,
341
+ message: `kxm ${current ?? "unknown"} (running from source)`,
335
342
  };
336
343
  } else {
337
344
  try {
338
- notice = await refreshKxmUpdateNotice(runtime);
345
+ notice = await refreshKxmUpdateNotice(runtime, undefined, current);
339
346
  } catch (error) {
340
347
  if (error instanceof KxmUpdateConfigError) {
341
348
  print(runtime.io, runtime.json, { ok: false, command: "update", error: error.code, ...installKindPayload(classified) }, error.message);
@@ -51,6 +51,21 @@ export function readInstalledKxmVersion(root: string): string {
51
51
  return pkg.version;
52
52
  }
53
53
 
54
+ /**
55
+ * `readInstalledKxmVersion` without the raw filesystem failure. The installed
56
+ * version is read to decide whether a candidate is an upgrade, a downgrade or
57
+ * the same thing — so a directory that has no readable `package.json` must come
58
+ * back as "unknown" and be refused in words, not surface as an uncaught ENOENT
59
+ * stack from `kxm update`.
60
+ */
61
+ export function installedKxmVersion(root: string): string | undefined {
62
+ try {
63
+ return readInstalledKxmVersion(root);
64
+ } catch {
65
+ return undefined;
66
+ }
67
+ }
68
+
54
69
  export function parseSemver(value: string): [number, number, number] | undefined {
55
70
  const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
56
71
  if (!match) return undefined;
@@ -8,7 +8,7 @@ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } fr
8
8
  import { deliverInboxNotification } from "./inbox.ts";
9
9
  import type { HubEvent, MessageRecord } from "./protocol.ts";
10
10
 
11
- const VERSION = "0.7.83";
11
+ const VERSION = "0.7.85";
12
12
  const inbox = new Map<string, MessageRecord>();
13
13
  const notifiedInbox = new Set<string>();
14
14
  let meshClient: HubClient | undefined;