@kontextmind/kxm 0.7.70 → 0.7.72
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 +22 -0
- package/docs/operations.md +37 -5
- package/docs/test-matrix.md +1 -0
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/dist/cli.js +83 -19
- package/plugins/kxm/dist/client.js +50 -3
- package/plugins/kxm/dist/core.js +31 -4
- package/plugins/kxm/dist/extension.js +59 -7
- package/plugins/kxm/dist/mcp-server.js +60 -8
- package/plugins/kxm/dist/runtime.js +4 -2
- package/plugins/kxm/dist/server.js +317 -20
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/kxm/references/protocol.md +18 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +3 -1
- package/plugins/kxm/src/cli.ts +2 -1
- package/plugins/kxm/src/client.ts +62 -3
- package/plugins/kxm/src/commands.ts +10 -4
- package/plugins/kxm/src/database.ts +6 -2
- package/plugins/kxm/src/external-effects.ts +390 -66
- package/plugins/kxm/src/hub.ts +171 -12
- package/plugins/kxm/src/local-snapshot.ts +10 -2
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/protocol.ts +67 -1
- package/plugins/kxm/src/store.ts +217 -6
- package/plugins/kxm/src/tui.ts +20 -4
|
@@ -14534,6 +14534,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
14534
14534
|
|
|
14535
14535
|
// plugins/kxm/src/client.ts
|
|
14536
14536
|
import { createHash } from "node:crypto";
|
|
14537
|
+
import { hostname } from "node:os";
|
|
14537
14538
|
|
|
14538
14539
|
// plugins/kxm/src/protocol.ts
|
|
14539
14540
|
var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
|
|
@@ -14541,6 +14542,9 @@ var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
|
14541
14542
|
var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
|
|
14542
14543
|
var MAX_BODY_BYTES = 256 * 1024;
|
|
14543
14544
|
var MAX_CONTENT_CHARS = 32e3;
|
|
14545
|
+
var MAX_AGENT_HOST_CHARS = 64;
|
|
14546
|
+
var MAX_LEASE_TTL_MS = 10 * 6e4;
|
|
14547
|
+
var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
|
|
14544
14548
|
|
|
14545
14549
|
// plugins/kxm/src/workflow.ts
|
|
14546
14550
|
function canonicalWorkflowEvidenceKey(value) {
|
|
@@ -14556,6 +14560,13 @@ var MeshWaitError = class extends Error {
|
|
|
14556
14560
|
this.waitStatus = waitStatus;
|
|
14557
14561
|
}
|
|
14558
14562
|
};
|
|
14563
|
+
function defaultHostLabel() {
|
|
14564
|
+
try {
|
|
14565
|
+
return hostname().trim().slice(0, MAX_AGENT_HOST_CHARS) || void 0;
|
|
14566
|
+
} catch {
|
|
14567
|
+
return void 0;
|
|
14568
|
+
}
|
|
14569
|
+
}
|
|
14559
14570
|
function completedFanoutResult(target, message) {
|
|
14560
14571
|
if (message.status === "queued" || message.status === "delivered") {
|
|
14561
14572
|
throw new Error(`message ${message.id} is not complete`);
|
|
@@ -14645,8 +14656,11 @@ var HubClient = class {
|
|
|
14645
14656
|
this.onEvent = void 0;
|
|
14646
14657
|
this.eventLoop = void 0;
|
|
14647
14658
|
}
|
|
14648
|
-
|
|
14649
|
-
|
|
14659
|
+
/** Online peers of this client's project. `includeOffline` also returns
|
|
14660
|
+
* registered members whose lease the hub has already retired. */
|
|
14661
|
+
async listAgents(options = {}) {
|
|
14662
|
+
const path = options.includeOffline ? "/v1/agents?includeOffline=true" : "/v1/agents";
|
|
14663
|
+
const result2 = await this.request(path);
|
|
14650
14664
|
return result2.agents;
|
|
14651
14665
|
}
|
|
14652
14666
|
async send(options) {
|
|
@@ -14742,6 +14756,37 @@ var HubClient = class {
|
|
|
14742
14756
|
);
|
|
14743
14757
|
return result2.message;
|
|
14744
14758
|
}
|
|
14759
|
+
// ----- Fenced leases over shared resources (P3) -----
|
|
14760
|
+
/**
|
|
14761
|
+
* Take or extend the lease over `resource` inside this client's project.
|
|
14762
|
+
*
|
|
14763
|
+
* The returned `fencingToken` is the whole point: hold it, present it on every
|
|
14764
|
+
* renewal, and present it again before committing anything shared. A hub that
|
|
14765
|
+
* has moved past it refuses, and the caller must stop rather than retry —
|
|
14766
|
+
* another holder owns the resource now. Rejects `HubHttpError` with code
|
|
14767
|
+
* `lease_held` when a live holder has it.
|
|
14768
|
+
*/
|
|
14769
|
+
async acquireLease(resource, ttlMs) {
|
|
14770
|
+
return await this.request(`/v1/leases/${encodeURIComponent(resource)}/acquire`, {
|
|
14771
|
+
method: "POST",
|
|
14772
|
+
body: JSON.stringify(ttlMs === void 0 ? {} : { ttlMs })
|
|
14773
|
+
});
|
|
14774
|
+
}
|
|
14775
|
+
/** Extend a lease this client holds. The token never changes on renewal; a
|
|
14776
|
+
* `lease_superseded` or `lease_expired` refusal means it is gone. */
|
|
14777
|
+
async renewLease(resource, fencingToken, ttlMs) {
|
|
14778
|
+
return await this.request(`/v1/leases/${encodeURIComponent(resource)}/renew`, {
|
|
14779
|
+
method: "POST",
|
|
14780
|
+
body: JSON.stringify(ttlMs === void 0 ? { fencingToken } : { fencingToken, ttlMs })
|
|
14781
|
+
});
|
|
14782
|
+
}
|
|
14783
|
+
/** Give the resource back. */
|
|
14784
|
+
async releaseLease(resource, fencingToken) {
|
|
14785
|
+
return await this.request(`/v1/leases/${encodeURIComponent(resource)}/release`, {
|
|
14786
|
+
method: "POST",
|
|
14787
|
+
body: JSON.stringify({ fencingToken })
|
|
14788
|
+
});
|
|
14789
|
+
}
|
|
14745
14790
|
async listWorkflows() {
|
|
14746
14791
|
const result2 = await this.request("/v1/workflows");
|
|
14747
14792
|
return result2.runs;
|
|
@@ -14886,7 +14931,8 @@ var HubClient = class {
|
|
|
14886
14931
|
name: this.options.name,
|
|
14887
14932
|
purpose: this.options.purpose,
|
|
14888
14933
|
project: this.options.project,
|
|
14889
|
-
model: this.options.model
|
|
14934
|
+
model: this.options.model,
|
|
14935
|
+
host: this.options.host ?? defaultHostLabel()
|
|
14890
14936
|
})
|
|
14891
14937
|
}, false);
|
|
14892
14938
|
this.agent = registration.agent;
|
|
@@ -14934,6 +14980,7 @@ var HubClient = class {
|
|
|
14934
14980
|
for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
|
|
14935
14981
|
if (typeof body[key] === "string") extras[key] = body[key];
|
|
14936
14982
|
}
|
|
14983
|
+
if (body.lease && typeof body.lease === "object") extras.lease = body.lease;
|
|
14937
14984
|
throw new HubHttpError(
|
|
14938
14985
|
response.status,
|
|
14939
14986
|
String(body.error ?? `HTTP ${response.status}`),
|
|
@@ -15014,14 +15061,19 @@ var AGENT_COMMANDS = [
|
|
|
15014
15061
|
group: "peer",
|
|
15015
15062
|
verb: "list",
|
|
15016
15063
|
label: "List hub peers",
|
|
15017
|
-
description: "List
|
|
15064
|
+
description: "List peer agents in this project's hub pool with their names, purposes, host label, and hub-clocked presence (online, stale, offline). Registered offline peers are listed only when includeOffline is set.",
|
|
15018
15065
|
parameters: {
|
|
15019
15066
|
type: "object",
|
|
15020
|
-
properties: {
|
|
15067
|
+
properties: {
|
|
15068
|
+
includeOffline: {
|
|
15069
|
+
type: "boolean",
|
|
15070
|
+
description: "Also list registered peers whose hub lease has expired"
|
|
15071
|
+
}
|
|
15072
|
+
},
|
|
15021
15073
|
additionalProperties: false
|
|
15022
15074
|
},
|
|
15023
|
-
async execute(client) {
|
|
15024
|
-
return { agents: await client.listAgents() };
|
|
15075
|
+
async execute(client, args) {
|
|
15076
|
+
return { agents: await client.listAgents({ includeOffline: args.includeOffline === true }) };
|
|
15025
15077
|
}
|
|
15026
15078
|
},
|
|
15027
15079
|
{
|
|
@@ -15755,12 +15755,16 @@ var StdioServerTransport = class {
|
|
|
15755
15755
|
|
|
15756
15756
|
// plugins/kxm/src/client.ts
|
|
15757
15757
|
import { createHash } from "node:crypto";
|
|
15758
|
+
import { hostname } from "node:os";
|
|
15758
15759
|
|
|
15759
15760
|
// plugins/kxm/src/protocol.ts
|
|
15760
15761
|
var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
|
|
15761
15762
|
var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
15762
15763
|
var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
|
|
15763
15764
|
var MAX_BODY_BYTES = 256 * 1024;
|
|
15765
|
+
var MAX_AGENT_HOST_CHARS = 64;
|
|
15766
|
+
var MAX_LEASE_TTL_MS = 10 * 6e4;
|
|
15767
|
+
var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
|
|
15764
15768
|
|
|
15765
15769
|
// plugins/kxm/src/workflow.ts
|
|
15766
15770
|
function canonicalWorkflowEvidenceKey(value) {
|
|
@@ -15776,6 +15780,13 @@ var MeshWaitError = class extends Error {
|
|
|
15776
15780
|
this.waitStatus = waitStatus;
|
|
15777
15781
|
}
|
|
15778
15782
|
};
|
|
15783
|
+
function defaultHostLabel() {
|
|
15784
|
+
try {
|
|
15785
|
+
return hostname().trim().slice(0, MAX_AGENT_HOST_CHARS) || void 0;
|
|
15786
|
+
} catch {
|
|
15787
|
+
return void 0;
|
|
15788
|
+
}
|
|
15789
|
+
}
|
|
15779
15790
|
function completedFanoutResult(target, message) {
|
|
15780
15791
|
if (message.status === "queued" || message.status === "delivered") {
|
|
15781
15792
|
throw new Error(`message ${message.id} is not complete`);
|
|
@@ -15865,8 +15876,11 @@ var HubClient = class {
|
|
|
15865
15876
|
this.onEvent = void 0;
|
|
15866
15877
|
this.eventLoop = void 0;
|
|
15867
15878
|
}
|
|
15868
|
-
|
|
15869
|
-
|
|
15879
|
+
/** Online peers of this client's project. `includeOffline` also returns
|
|
15880
|
+
* registered members whose lease the hub has already retired. */
|
|
15881
|
+
async listAgents(options = {}) {
|
|
15882
|
+
const path = options.includeOffline ? "/v1/agents?includeOffline=true" : "/v1/agents";
|
|
15883
|
+
const result = await this.request(path);
|
|
15870
15884
|
return result.agents;
|
|
15871
15885
|
}
|
|
15872
15886
|
async send(options) {
|
|
@@ -15962,6 +15976,37 @@ var HubClient = class {
|
|
|
15962
15976
|
);
|
|
15963
15977
|
return result.message;
|
|
15964
15978
|
}
|
|
15979
|
+
// ----- Fenced leases over shared resources (P3) -----
|
|
15980
|
+
/**
|
|
15981
|
+
* Take or extend the lease over `resource` inside this client's project.
|
|
15982
|
+
*
|
|
15983
|
+
* The returned `fencingToken` is the whole point: hold it, present it on every
|
|
15984
|
+
* renewal, and present it again before committing anything shared. A hub that
|
|
15985
|
+
* has moved past it refuses, and the caller must stop rather than retry —
|
|
15986
|
+
* another holder owns the resource now. Rejects `HubHttpError` with code
|
|
15987
|
+
* `lease_held` when a live holder has it.
|
|
15988
|
+
*/
|
|
15989
|
+
async acquireLease(resource, ttlMs) {
|
|
15990
|
+
return await this.request(`/v1/leases/${encodeURIComponent(resource)}/acquire`, {
|
|
15991
|
+
method: "POST",
|
|
15992
|
+
body: JSON.stringify(ttlMs === void 0 ? {} : { ttlMs })
|
|
15993
|
+
});
|
|
15994
|
+
}
|
|
15995
|
+
/** Extend a lease this client holds. The token never changes on renewal; a
|
|
15996
|
+
* `lease_superseded` or `lease_expired` refusal means it is gone. */
|
|
15997
|
+
async renewLease(resource, fencingToken, ttlMs) {
|
|
15998
|
+
return await this.request(`/v1/leases/${encodeURIComponent(resource)}/renew`, {
|
|
15999
|
+
method: "POST",
|
|
16000
|
+
body: JSON.stringify(ttlMs === void 0 ? { fencingToken } : { fencingToken, ttlMs })
|
|
16001
|
+
});
|
|
16002
|
+
}
|
|
16003
|
+
/** Give the resource back. */
|
|
16004
|
+
async releaseLease(resource, fencingToken) {
|
|
16005
|
+
return await this.request(`/v1/leases/${encodeURIComponent(resource)}/release`, {
|
|
16006
|
+
method: "POST",
|
|
16007
|
+
body: JSON.stringify({ fencingToken })
|
|
16008
|
+
});
|
|
16009
|
+
}
|
|
15965
16010
|
async listWorkflows() {
|
|
15966
16011
|
const result = await this.request("/v1/workflows");
|
|
15967
16012
|
return result.runs;
|
|
@@ -16106,7 +16151,8 @@ var HubClient = class {
|
|
|
16106
16151
|
name: this.options.name,
|
|
16107
16152
|
purpose: this.options.purpose,
|
|
16108
16153
|
project: this.options.project,
|
|
16109
|
-
model: this.options.model
|
|
16154
|
+
model: this.options.model,
|
|
16155
|
+
host: this.options.host ?? defaultHostLabel()
|
|
16110
16156
|
})
|
|
16111
16157
|
}, false);
|
|
16112
16158
|
this.agent = registration.agent;
|
|
@@ -16154,6 +16200,7 @@ var HubClient = class {
|
|
|
16154
16200
|
for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
|
|
16155
16201
|
if (typeof body[key] === "string") extras[key] = body[key];
|
|
16156
16202
|
}
|
|
16203
|
+
if (body.lease && typeof body.lease === "object") extras.lease = body.lease;
|
|
16157
16204
|
throw new HubHttpError(
|
|
16158
16205
|
response.status,
|
|
16159
16206
|
String(body.error ?? `HTTP ${response.status}`),
|
|
@@ -16325,14 +16372,19 @@ var AGENT_COMMANDS = [
|
|
|
16325
16372
|
group: "peer",
|
|
16326
16373
|
verb: "list",
|
|
16327
16374
|
label: "List hub peers",
|
|
16328
|
-
description: "List
|
|
16375
|
+
description: "List peer agents in this project's hub pool with their names, purposes, host label, and hub-clocked presence (online, stale, offline). Registered offline peers are listed only when includeOffline is set.",
|
|
16329
16376
|
parameters: {
|
|
16330
16377
|
type: "object",
|
|
16331
|
-
properties: {
|
|
16378
|
+
properties: {
|
|
16379
|
+
includeOffline: {
|
|
16380
|
+
type: "boolean",
|
|
16381
|
+
description: "Also list registered peers whose hub lease has expired"
|
|
16382
|
+
}
|
|
16383
|
+
},
|
|
16332
16384
|
additionalProperties: false
|
|
16333
16385
|
},
|
|
16334
|
-
async execute(client) {
|
|
16335
|
-
return { agents: await client.listAgents() };
|
|
16386
|
+
async execute(client, args) {
|
|
16387
|
+
return { agents: await client.listAgents({ includeOffline: args.includeOffline === true }) };
|
|
16336
16388
|
}
|
|
16337
16389
|
},
|
|
16338
16390
|
{
|
|
@@ -17127,7 +17179,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
|
|
|
17127
17179
|
}
|
|
17128
17180
|
|
|
17129
17181
|
// plugins/kxm/src/mcp-server.ts
|
|
17130
|
-
var VERSION = "0.7.
|
|
17182
|
+
var VERSION = "0.7.72";
|
|
17131
17183
|
var inbox = /* @__PURE__ */ new Map();
|
|
17132
17184
|
var notifiedInbox = /* @__PURE__ */ new Set();
|
|
17133
17185
|
var meshClient;
|
|
@@ -17989,7 +17989,7 @@ function discoverProjectStores(projectRoot, options = {}) {
|
|
|
17989
17989
|
const stores = [];
|
|
17990
17990
|
const hubPath = options.hubDataPath ? resolve3(options.hubDataPath) : join4(root, ".kxm", "state", "kxm.db");
|
|
17991
17991
|
if (existsSync4(hubPath)) {
|
|
17992
|
-
stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion:
|
|
17992
|
+
stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion: 4 });
|
|
17993
17993
|
}
|
|
17994
17994
|
const registryPath = join4(root, ".kxm", "runtime", "registry.db");
|
|
17995
17995
|
if (existsSync4(registryPath)) {
|
|
@@ -18095,7 +18095,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
|
|
|
18095
18095
|
`backup file ${store.backupFile} sha256 ${actualSha256} does not match manifest hash ${store.sha256}`
|
|
18096
18096
|
);
|
|
18097
18097
|
}
|
|
18098
|
-
let maxSupported =
|
|
18098
|
+
let maxSupported = 4;
|
|
18099
18099
|
if (store.storeId === "registry" || store.storeId === "binding-store") {
|
|
18100
18100
|
maxSupported = 1;
|
|
18101
18101
|
} else if (store.storeId.startsWith("events:")) {
|
|
@@ -28684,6 +28684,8 @@ var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 6e4;
|
|
|
28684
28684
|
var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
28685
28685
|
var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
|
|
28686
28686
|
var MAX_BODY_BYTES = 256 * 1024;
|
|
28687
|
+
var MAX_LEASE_TTL_MS = 10 * 6e4;
|
|
28688
|
+
var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
|
|
28687
28689
|
function nowIso() {
|
|
28688
28690
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
28689
28691
|
}
|