@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.
@@ -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
- async listAgents() {
14649
- const result2 = await this.request("/v1/agents");
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 online peer agents in this project's hub pool, including their names and purposes.",
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
- async listAgents() {
15869
- const result = await this.request("/v1/agents");
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 online peer agents in this project's hub pool, including their names and purposes.",
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.70";
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: 3 });
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 = 3;
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
  }