@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +3 -0
- package/plugins/kxm/dist/mcp-server.js +1 -1
- package/plugins/kxm/dist/runtime-supervisor.js +32 -10
- package/plugins/kxm/dist/runtime.js +32 -10
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/src/cli/project.ts +6 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/runtime-supervisor.ts +52 -14
package/package.json
CHANGED
|
@@ -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.
|
|
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",
|
package/plugins/kxm/dist/cli.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
28487
|
-
|
|
28488
|
-
|
|
28489
|
-
|
|
28490
|
-
|
|
28491
|
-
|
|
28492
|
-
|
|
28493
|
-
|
|
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
|
-
|
|
29007
|
-
|
|
29008
|
-
|
|
29009
|
-
|
|
29010
|
-
|
|
29011
|
-
|
|
29012
|
-
|
|
29013
|
-
|
|
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);
|
package/plugins/kxm/package.json
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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);
|