@kontextmind/kxm 0.7.85 → 0.7.86

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.85",
14
+ "version": "0.7.86",
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.85",
3
+ "version": "0.7.86",
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.85",
5
+ "version": "0.7.86",
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",
@@ -32857,6 +32857,9 @@ function formatKxmSyncStatus(sync, running) {
32857
32857
  if (sync.length === 0) return ["sync: no project registered with this Runtime yet"];
32858
32858
  return sync.map((project) => {
32859
32859
  const codes = project.outbox.refusals.map((refusal) => `${refusal.code} x${refusal.count}`).join(", ");
32860
+ if (project.storeReadable === false) {
32861
+ return `sync ${project.projectId}: blocked (its store is not readable by this build) \u2014 ${project.lastError ?? "unknown reason"}`;
32862
+ }
32860
32863
  const counts = `pending ${project.outbox.pending}, acked ${project.outbox.acked}, refused ${project.outbox.refused}`;
32861
32864
  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}` : ""}` : "";
32862
32865
  return `sync ${project.projectId}: ${project.state} (${counts})${tail}`;
@@ -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.85";
17185
+ var VERSION = "0.7.86";
17186
17186
  var inbox = /* @__PURE__ */ new Map();
17187
17187
  var notifiedInbox = /* @__PURE__ */ new Set();
17188
17188
  var meshClient;
@@ -28483,17 +28483,38 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
28483
28483
  }
28484
28484
  }, 1e3);
28485
28485
  heartbeat.unref();
28486
- for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
28487
- try {
28488
- contextFor(reg.projectRoot);
28489
- } catch {
28490
- logger.warn({
28491
- event: "runtime_sync_context_unavailable",
28492
- projectId: reg.projectId,
28493
- message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`
28494
- });
28486
+ const reopenRegisteredProjects = () => {
28487
+ for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
28488
+ const key = projectRuntimeKey(reg.projectRoot);
28489
+ if (contexts.has(key)) continue;
28490
+ try {
28491
+ contextFor(reg.projectRoot);
28492
+ syncStatuses.delete(key);
28493
+ } catch (error) {
28494
+ const reason = syncFailureText(error);
28495
+ const prior = syncStatuses.get(key);
28496
+ if (prior === void 0 || prior.lastError !== reason) {
28497
+ logger.warn({
28498
+ event: "runtime_sync_context_unavailable",
28499
+ projectId: reg.projectId,
28500
+ message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`,
28501
+ reason
28502
+ });
28503
+ }
28504
+ syncStatuses.set(key, {
28505
+ projectId: reg.projectId,
28506
+ projectRoot: reg.projectRoot,
28507
+ homeRuntimeId: activeRuntimeId,
28508
+ state: "blocked",
28509
+ outbox: { pending: 0, acked: 0, refused: 0, refusals: [] },
28510
+ storeReadable: false,
28511
+ consecutiveFailures: 1,
28512
+ lastError: reason
28513
+ });
28514
+ }
28495
28515
  }
28496
- }
28516
+ };
28517
+ reopenRegisteredProjects();
28497
28518
  const recordSyncStatus = (context, next) => {
28498
28519
  const key = projectRuntimeKey(context.projectRoot);
28499
28520
  const previous = syncStatuses.get(key);
@@ -28522,6 +28543,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
28522
28543
  if (syncing || stopping) return;
28523
28544
  syncing = true;
28524
28545
  void (async () => {
28546
+ reopenRegisteredProjects();
28525
28547
  for (const context of [...contexts.values()]) {
28526
28548
  const key = projectRuntimeKey(context.projectRoot);
28527
28549
  const prior = syncStatuses.get(key);
@@ -29003,17 +29003,38 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
29003
29003
  }
29004
29004
  }, 1e3);
29005
29005
  heartbeat.unref();
29006
- for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
29007
- try {
29008
- contextFor(reg.projectRoot);
29009
- } catch {
29010
- logger.warn({
29011
- event: "runtime_sync_context_unavailable",
29012
- projectId: reg.projectId,
29013
- message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`
29014
- });
29006
+ const reopenRegisteredProjects = () => {
29007
+ for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
29008
+ const key = projectRuntimeKey(reg.projectRoot);
29009
+ if (contexts.has(key)) continue;
29010
+ try {
29011
+ contextFor(reg.projectRoot);
29012
+ syncStatuses.delete(key);
29013
+ } catch (error) {
29014
+ const reason = syncFailureText(error);
29015
+ const prior = syncStatuses.get(key);
29016
+ if (prior === void 0 || prior.lastError !== reason) {
29017
+ logger.warn({
29018
+ event: "runtime_sync_context_unavailable",
29019
+ projectId: reg.projectId,
29020
+ message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`,
29021
+ reason
29022
+ });
29023
+ }
29024
+ syncStatuses.set(key, {
29025
+ projectId: reg.projectId,
29026
+ projectRoot: reg.projectRoot,
29027
+ homeRuntimeId: activeRuntimeId,
29028
+ state: "blocked",
29029
+ outbox: { pending: 0, acked: 0, refused: 0, refusals: [] },
29030
+ storeReadable: false,
29031
+ consecutiveFailures: 1,
29032
+ lastError: reason
29033
+ });
29034
+ }
29015
29035
  }
29016
- }
29036
+ };
29037
+ reopenRegisteredProjects();
29017
29038
  const recordSyncStatus = (context, next) => {
29018
29039
  const key = projectRuntimeKey(context.projectRoot);
29019
29040
  const previous = syncStatuses.get(key);
@@ -29042,6 +29063,7 @@ async function startKxmRuntimeSupervisorInner(paths, requestedPortOption, now, e
29042
29063
  if (syncing || stopping) return;
29043
29064
  syncing = true;
29044
29065
  void (async () => {
29066
+ reopenRegisteredProjects();
29045
29067
  for (const context of [...contexts.values()]) {
29046
29068
  const key = projectRuntimeKey(context.projectRoot);
29047
29069
  const prior = syncStatuses.get(key);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-plugin",
3
- "version": "0.7.85",
3
+ "version": "0.7.86",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -828,6 +828,12 @@ function formatKxmSyncStatus(sync: KxmProjectSyncStatus[] | undefined, running:
828
828
  if (sync.length === 0) return ["sync: no project registered with this Runtime yet"];
829
829
  return sync.map((project) => {
830
830
  const codes = project.outbox.refusals.map((refusal) => `${refusal.code} x${refusal.count}`).join(", ");
831
+ // A store this build refuses to open reports zeros it cannot read, not an
832
+ // empty outbox. Saying "pending 0" there would send the operator looking
833
+ // somewhere else while every row in the project sits unreachable.
834
+ if (project.storeReadable === false) {
835
+ return `sync ${project.projectId}: blocked (its store is not readable by this build) — ${project.lastError ?? "unknown reason"}`;
836
+ }
831
837
  const counts = `pending ${project.outbox.pending}, acked ${project.outbox.acked}, refused ${project.outbox.refused}`;
832
838
  const tail = project.state === "refusing"
833
839
  ? ` (${codes || "see log"}) — fix the hub, then: kxm runtime sync-retry`
@@ -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.85";
11
+ const VERSION = "0.7.86";
12
12
  const inbox = new Map<string, MessageRecord>();
13
13
  const notifiedInbox = new Set<string>();
14
14
  let meshClient: HubClient | undefined;
@@ -568,6 +568,12 @@ export interface KxmProjectSyncStatus {
568
568
  state: "ok" | "no_hub" | "blocked" | "refusing";
569
569
  outbox: KxmOutboxStatus;
570
570
  consecutiveFailures: number;
571
+ /**
572
+ * False when the counts above are zeros because the store could not be opened
573
+ * at all — a refused schema is not an empty outbox, and reporting the two the
574
+ * same way is how a blocked project reads as "nothing to do".
575
+ */
576
+ storeReadable?: boolean;
571
577
  hubUrl?: string;
572
578
  lastCompletedAt?: string;
573
579
  lastPushed?: number;
@@ -1096,21 +1102,48 @@ async function startKxmRuntimeSupervisorInner(
1096
1102
  // Restart recovery: contexts are only populated on demand (a project request
1097
1103
  // opens one), so a restarted supervisor would see an empty map and silently
1098
1104
  // stop syncing every registered project's pending outbox rows. Reopen the
1099
- // projects this Runtime owns before the first tick.
1100
- for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
1101
- try {
1102
- contextFor(reg.projectRoot);
1103
- } catch {
1104
- // A project whose checkout has moved or been deleted stays skipped; its
1105
- // outbox rows remain pending and its presence expires, which is visible
1106
- // in the ops snapshot as orphaned.
1107
- logger.warn({
1108
- event: "runtime_sync_context_unavailable",
1109
- projectId: reg.projectId,
1110
- message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`,
1111
- });
1105
+ // projects this Runtime owns before the first tick, and remember the ones that
1106
+ // will not open — a refused schema and an empty outbox are different answers,
1107
+ // and the status surface has to tell them apart.
1108
+ const reopenRegisteredProjects = (): void => {
1109
+ for (const reg of registry.projectsForRuntime(activeRuntimeId)) {
1110
+ const key = projectRuntimeKey(reg.projectRoot);
1111
+ if (contexts.has(key)) continue;
1112
+ try {
1113
+ contextFor(reg.projectRoot);
1114
+ syncStatuses.delete(key);
1115
+ } catch (error) {
1116
+ // A project whose checkout has moved, was deleted, or carries a store
1117
+ // this build refuses to open. Its outbox rows stay pending and its
1118
+ // presence expires, which the ops snapshot shows as orphaned — but the
1119
+ // reason is reported here, on the Runtime, where the operator looks.
1120
+ const reason = syncFailureText(error);
1121
+ const prior = syncStatuses.get(key);
1122
+ // Retried on every tick so a repaired checkout or a retired store heals
1123
+ // without a restart — which means this line is logged on a change of
1124
+ // state only, never once per tick.
1125
+ if (prior === undefined || prior.lastError !== reason) {
1126
+ logger.warn({
1127
+ event: "runtime_sync_context_unavailable",
1128
+ projectId: reg.projectId,
1129
+ message: `cannot reopen ${reg.projectRoot}: its outbox rows stay pending`,
1130
+ reason,
1131
+ });
1132
+ }
1133
+ syncStatuses.set(key, {
1134
+ projectId: reg.projectId,
1135
+ projectRoot: reg.projectRoot,
1136
+ homeRuntimeId: activeRuntimeId,
1137
+ state: "blocked",
1138
+ outbox: { pending: 0, acked: 0, refused: 0, refusals: [] },
1139
+ storeReadable: false,
1140
+ consecutiveFailures: 1,
1141
+ lastError: reason,
1142
+ });
1143
+ }
1112
1144
  }
1113
- }
1145
+ };
1146
+ reopenRegisteredProjects();
1114
1147
 
1115
1148
  const recordSyncStatus = (context: KxmRuntimeContext, next: KxmProjectSyncStatus): void => {
1116
1149
  const key = projectRuntimeKey(context.projectRoot);
@@ -1147,6 +1180,11 @@ async function startKxmRuntimeSupervisorInner(
1147
1180
  if (syncing || stopping) return;
1148
1181
  syncing = true;
1149
1182
  void (async () => {
1183
+ // A project that could not open last time gets another attempt: the fix on
1184
+ // disk (retired store, restored backup, moved checkout) is not followed by
1185
+ // a restart, and a blocked state that only clears on restart is its own
1186
+ // kind of silence.
1187
+ reopenRegisteredProjects();
1150
1188
  for (const context of [...contexts.values()]) {
1151
1189
  const key = projectRuntimeKey(context.projectRoot);
1152
1190
  const prior = syncStatuses.get(key);