@kontextmind/kxm 0.7.71 → 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.
@@ -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.71",
14
+ "version": "0.7.72",
15
15
  "category": "development",
16
16
  "tags": ["kxm", "multi-agent", "workflows", "mcp"]
17
17
  }
package/CHANGELOG.md CHANGED
@@ -6,6 +6,21 @@ All notable user-facing changes are documented here. The project follows [Semant
6
6
 
7
7
  ### Added
8
8
 
9
+ - **Fenced hub leases, and shared external effects that will not run without one.**
10
+ `POST /v1/leases/:resource/acquire|renew|release` are agent-authenticated and
11
+ project-scoped (the hub prefixes the caller's project onto the resource name). Each call
12
+ is a compare-and-set inside one store transaction on the **hub clock**, with TTLs bounded
13
+ to 5 s–10 min. The fencing token starts at 1, survives renewal unchanged, and increments
14
+ only when a new holder takes over an expired lease, so a holder that returns after its
15
+ deadline is told its token was superseded instead of writing behind its replacement.
16
+ Shared external effects (`git-push` to a ref the run does not own, `pr-create`,
17
+ `tracker-issue`, `webhook`) acquire a lease keyed by `targetRef` before executing, record
18
+ `{leaseResource, fencingToken}` in the receipt, renew on the existing Q6 heartbeat, and
19
+ re-present the token at commit; a superseded token leaves the effect `in-flight` and the
20
+ attempt `blocked_uncertain` with nothing retrying. An unreachable hub refuses the effect
21
+ (`effect_lease_unavailable`) rather than executing it unfenced. Unique-namespace kinds
22
+ (`git-branch`, `git-commit`, and a push to the run's own branch) stay lease-free.
23
+
9
24
  - **`kxm peer send --allow-offline` queues to a registered offline peer.**
10
25
  `POST /v1/messages` accepts `allowOffline: true` (also `kxm_send.allowOffline`): a
11
26
  registered agent in the same project is stored `queued` instead of `target_not_found`,
@@ -42,6 +57,13 @@ All notable user-facing changes are documented here. The project follows [Semant
42
57
 
43
58
  ### Changed
44
59
 
60
+ - **Hub store schema v3 → v4, external-effects ledger v1 → v2.** The hub store gains a
61
+ `leases` table and the ledger gains `lease_resource`/`fencing_token` columns. Neither has
62
+ a migration lane: an older file is refused at open with `runtime_schema_outdated`, and the
63
+ hub backup/restore ceiling in `database.ts` moves to 4 with the bump, so a v3 backup must
64
+ be restored with the release that produced it. Delete the state file to start fresh and
65
+ let `kxm hub start` recreate it.
66
+
45
67
  - **`kxm tenant status`: one composed read for the portal, with the authorities labelled.**
46
68
  The portal needs hub metadata (roster, message queue, the hub's run projection) *and* the
47
69
  Runtime's authoritative run state, and the failure mode S2 exists to prevent is rendering
@@ -315,7 +315,10 @@ restore lands somewhere the running service will not look.
315
315
  copies, not the file copy**: configuration, repository bindings, prompt sidecars,
316
316
  routing manifests and update configuration are not databases, so a snapshot-only backup
317
317
  reproduces exactly the failure this section exists to remove. The hub's own backup path already writes a hashed manifest and records
318
- a schema version ceiling; keep that manifest with the files.
318
+ a schema version ceiling; keep that manifest with the files. That ceiling is
319
+ **hub store v4** as of the fenced-lease release: a backup taken by an earlier
320
+ build records v3 and is refused by this one, because there is no migration lane.
321
+ Restore such a backup with the release that produced it, or start fresh.
319
322
  3. Record the package version, configuration revision and schema versions beside the copy.
320
323
  A restore that cannot state which release produced it is not a restore path.
321
324
  4. Keep at least one rotation, and bound retention explicitly — run events and prompt
@@ -392,10 +395,10 @@ Broader deployments need shared state and coordination, external identity and fi
392
395
 
393
396
  ## v0.5 context/state storage
394
397
 
395
- The hub database (schema version 3) carries `context_items` alongside
396
- agents, messages, workflow runs, and the journal. Temporal state, knowledge
397
- records, and their audit trails live in the same SQLite file and upgrade in
398
- place from v0.4 databases.
398
+ The hub database (schema version 4) carries `context_items` and `leases`
399
+ alongside agents, messages, workflow runs, and the journal. Temporal state,
400
+ knowledge records, and their audit trails live in the same SQLite file and
401
+ upgrade in place from v0.4 databases.
399
402
 
400
403
  - **Backup and restore**: include the hub database file and, if used, the
401
404
  `.kxm/skills/` and `.kxm/knowledge/` trees. The wiki is a compiled view and
@@ -412,6 +415,35 @@ place from v0.4 databases.
412
415
  to open on an older runtime). Restore a database backup taken before the
413
416
  upgrade instead.
414
417
 
418
+ ### Fenced leases over shared resources
419
+
420
+ `leases` holds one row per project-scoped resource: the holder, a monotonic
421
+ fencing token, and a deadline. `POST /v1/leases/:resource/acquire|renew|release`
422
+ are agent-authenticated and scoped to the caller's project, which the hub
423
+ prefixes onto the resource name — two projects naming the same branch never
424
+ contend. TTLs are bounded to 5 s–10 min and every decision is made on the **hub
425
+ clock** inside one store transaction, so a skewed client cannot extend its own
426
+ grip.
427
+
428
+ The token is the safety property. It starts at 1, stays put across renewals, and
429
+ increments only when a new holder takes over an expired lease. A holder that
430
+ comes back after its deadline is therefore told its token was superseded rather
431
+ than allowed to write behind whoever replaced it. Shared external effects
432
+ (`git-push` to a ref the run does not own, `pr-create`, `tracker-issue`,
433
+ `webhook`) take a lease before executing and re-present the token at commit; a
434
+ superseded token leaves the effect `in-flight` and the attempt
435
+ `blocked_uncertain` for an operator to resolve, and nothing retries it. An
436
+ unreachable hub refuses the effect (`effect_lease_unavailable`) rather than
437
+ running it unfenced.
438
+
439
+ Expired rows are **not** reaped immediately — their token is what the next
440
+ takeover has to increment past. The retention sweep drops rows whose deadline is
441
+ older than the run-retention window (7 days by default), far beyond any live
442
+ holder. `kxm_leases_granted_total`, `kxm_leases_refused_total` and
443
+ `kxm_leases_released_total` in `/metrics` report contention;
444
+ `lease_acquired`, `lease_renewed`, `lease_released`, `lease_denied` and
445
+ `lease_purged` are the structured log events.
446
+
415
447
  ## Hub Q&A / knowledge base
416
448
 
417
449
  - [What is all stored on the hub?](kb/qa-what-the-hub-stores.md)
@@ -23,6 +23,7 @@ npm run verify
23
23
  | Queue, acknowledgement, visibility, reply, and authorization | `test/core/hub-api.test.ts`, `test/core/hub.test.ts` |
24
24
  | Queued/delivered replay after recipient restart reuses one message record | `test/core/hub.test.ts`, `test/core/extension.test.ts`, `test/core/mcp.test.ts` |
25
25
  | Known-offline peer send with `allowOffline` queues, delivers once on resumption, and expires unread by TTL | `test/core/hub-api.test.ts` |
26
+ | Fenced hub leases: CAS acquire/renew/release, monotonic token on takeover only, hub-clocked expiry, and a shared external effect refused at commit under a superseded token | `test/core/hub-api.test.ts`, `test/core/store.test.ts`, `test/core/external-effects.test.ts` |
26
27
  | One-to-three-peer fanout, recoverable local timeouts/aborts, exact retries, and partial-error collection | `test/core/client.test.ts`, `test/core/hub-api.test.ts`, `test/core/extension.test.ts`, `test/core/mcp.test.ts` |
27
28
  | TTL expiry, sender cancellation, and terminal retention | `test/core/hub-api.test.ts` |
28
29
  | Terminal inbound cleanup and next-request activation | `test/core/extension.test.ts`, `test/core/mcp.test.ts` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kontextmind/kxm",
3
- "version": "0.7.71",
3
+ "version": "0.7.72",
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.71",
5
+ "version": "0.7.72",
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",
@@ -18360,6 +18360,8 @@ var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
18360
18360
  var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
18361
18361
  var MAX_BODY_BYTES = 256 * 1024;
18362
18362
  var MAX_AGENT_HOST_CHARS = 64;
18363
+ var MAX_LEASE_TTL_MS = 10 * 6e4;
18364
+ var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
18363
18365
  var ProtocolError = class extends Error {
18364
18366
  statusCode;
18365
18367
  code;
@@ -19396,6 +19398,37 @@ var HubClient = class {
19396
19398
  );
19397
19399
  return result.message;
19398
19400
  }
19401
+ // ----- Fenced leases over shared resources (P3) -----
19402
+ /**
19403
+ * Take or extend the lease over `resource` inside this client's project.
19404
+ *
19405
+ * The returned `fencingToken` is the whole point: hold it, present it on every
19406
+ * renewal, and present it again before committing anything shared. A hub that
19407
+ * has moved past it refuses, and the caller must stop rather than retry —
19408
+ * another holder owns the resource now. Rejects `HubHttpError` with code
19409
+ * `lease_held` when a live holder has it.
19410
+ */
19411
+ async acquireLease(resource, ttlMs) {
19412
+ return await this.request(`/v1/leases/${encodeURIComponent(resource)}/acquire`, {
19413
+ method: "POST",
19414
+ body: JSON.stringify(ttlMs === void 0 ? {} : { ttlMs })
19415
+ });
19416
+ }
19417
+ /** Extend a lease this client holds. The token never changes on renewal; a
19418
+ * `lease_superseded` or `lease_expired` refusal means it is gone. */
19419
+ async renewLease(resource, fencingToken, ttlMs) {
19420
+ return await this.request(`/v1/leases/${encodeURIComponent(resource)}/renew`, {
19421
+ method: "POST",
19422
+ body: JSON.stringify(ttlMs === void 0 ? { fencingToken } : { fencingToken, ttlMs })
19423
+ });
19424
+ }
19425
+ /** Give the resource back. */
19426
+ async releaseLease(resource, fencingToken) {
19427
+ return await this.request(`/v1/leases/${encodeURIComponent(resource)}/release`, {
19428
+ method: "POST",
19429
+ body: JSON.stringify({ fencingToken })
19430
+ });
19431
+ }
19399
19432
  async listWorkflows() {
19400
19433
  const result = await this.request("/v1/workflows");
19401
19434
  return result.runs;
@@ -19589,6 +19622,7 @@ var HubClient = class {
19589
19622
  for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
19590
19623
  if (typeof body[key] === "string") extras[key] = body[key];
19591
19624
  }
19625
+ if (body.lease && typeof body.lease === "object") extras.lease = body.lease;
19592
19626
  throw new HubHttpError(
19593
19627
  response.status,
19594
19628
  String(body.error ?? `HTTP ${response.status}`),
@@ -23862,7 +23896,7 @@ function discoverProjectStores(projectRoot, options = {}) {
23862
23896
  const stores = [];
23863
23897
  const hubPath = options.hubDataPath ? resolve5(options.hubDataPath) : join8(root, ".kxm", "state", "kxm.db");
23864
23898
  if (existsSync8(hubPath)) {
23865
- stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion: 3 });
23899
+ stores.push({ storeId: "hub-store", sourcePath: hubPath, maxSupportedVersion: 4 });
23866
23900
  }
23867
23901
  const registryPath = join8(root, ".kxm", "runtime", "registry.db");
23868
23902
  if (existsSync8(registryPath)) {
@@ -23968,7 +24002,7 @@ function restoreBackup(manifestPathOrDir, options = {}) {
23968
24002
  `backup file ${store.backupFile} sha256 ${actualSha256} does not match manifest hash ${store.sha256}`
23969
24003
  );
23970
24004
  }
23971
- let maxSupported = 3;
24005
+ let maxSupported = 4;
23972
24006
  if (store.storeId === "registry" || store.storeId === "binding-store") {
23973
24007
  maxSupported = 1;
23974
24008
  } else if (store.storeId.startsWith("events:")) {
@@ -42603,6 +42637,12 @@ function slugifyBranchPart(text, maxLength = 40) {
42603
42637
  if (cleaned.length <= maxLength) return cleaned;
42604
42638
  return cleaned.slice(0, maxLength).replace(/-+$/, "");
42605
42639
  }
42640
+ var SHARED_EFFECT_KINDS = Object.freeze([
42641
+ "git-push",
42642
+ "pr-create",
42643
+ "tracker-issue",
42644
+ "webhook"
42645
+ ]);
42606
42646
 
42607
42647
  // plugins/kxm/src/local-snapshot.ts
42608
42648
  import { existsSync as existsSync26, readdirSync as readdirSync11, readFileSync as readFileSync26 } from "node:fs";
@@ -8,6 +8,8 @@ var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
8
8
  var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
9
9
  var MAX_BODY_BYTES = 256 * 1024;
10
10
  var MAX_AGENT_HOST_CHARS = 64;
11
+ var MAX_LEASE_TTL_MS = 10 * 6e4;
12
+ var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
11
13
 
12
14
  // plugins/kxm/src/workflow.ts
13
15
  function canonicalWorkflowEvidenceKey(value) {
@@ -219,6 +221,37 @@ var HubClient = class {
219
221
  );
220
222
  return result.message;
221
223
  }
224
+ // ----- Fenced leases over shared resources (P3) -----
225
+ /**
226
+ * Take or extend the lease over `resource` inside this client's project.
227
+ *
228
+ * The returned `fencingToken` is the whole point: hold it, present it on every
229
+ * renewal, and present it again before committing anything shared. A hub that
230
+ * has moved past it refuses, and the caller must stop rather than retry —
231
+ * another holder owns the resource now. Rejects `HubHttpError` with code
232
+ * `lease_held` when a live holder has it.
233
+ */
234
+ async acquireLease(resource, ttlMs) {
235
+ return await this.request(`/v1/leases/${encodeURIComponent(resource)}/acquire`, {
236
+ method: "POST",
237
+ body: JSON.stringify(ttlMs === void 0 ? {} : { ttlMs })
238
+ });
239
+ }
240
+ /** Extend a lease this client holds. The token never changes on renewal; a
241
+ * `lease_superseded` or `lease_expired` refusal means it is gone. */
242
+ async renewLease(resource, fencingToken, ttlMs) {
243
+ return await this.request(`/v1/leases/${encodeURIComponent(resource)}/renew`, {
244
+ method: "POST",
245
+ body: JSON.stringify(ttlMs === void 0 ? { fencingToken } : { fencingToken, ttlMs })
246
+ });
247
+ }
248
+ /** Give the resource back. */
249
+ async releaseLease(resource, fencingToken) {
250
+ return await this.request(`/v1/leases/${encodeURIComponent(resource)}/release`, {
251
+ method: "POST",
252
+ body: JSON.stringify({ fencingToken })
253
+ });
254
+ }
222
255
  async listWorkflows() {
223
256
  const result = await this.request("/v1/workflows");
224
257
  return result.runs;
@@ -412,6 +445,7 @@ var HubClient = class {
412
445
  for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
413
446
  if (typeof body[key] === "string") extras[key] = body[key];
414
447
  }
448
+ if (body.lease && typeof body.lease === "object") extras.lease = body.lease;
415
449
  throw new HubHttpError(
416
450
  response.status,
417
451
  String(body.error ?? `HTTP ${response.status}`),
@@ -13,6 +13,10 @@ var DEFAULT_RATE_LIMIT_WINDOW_MS = 6e4;
13
13
  var MAX_BODY_BYTES = 256 * 1024;
14
14
  var MAX_CONTENT_CHARS = 32e3;
15
15
  var MAX_AGENT_HOST_CHARS = 64;
16
+ var MIN_LEASE_TTL_MS = 5e3;
17
+ var MAX_LEASE_TTL_MS = 10 * 6e4;
18
+ var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
19
+ var MAX_LEASE_RESOURCE_CHARS = 200;
16
20
  function agentPresenceView(agent, staleAfterMs = DEFAULT_STALE_AFTER_MS, now = Date.now()) {
17
21
  const lastSeenMs = Date.parse(agent.lastSeenAt);
18
22
  const leaseExpiresAtMs = (Number.isFinite(lastSeenMs) ? lastSeenMs : 0) + staleAfterMs;
@@ -2017,6 +2021,7 @@ export {
2017
2021
  AGENT_COMMANDS,
2018
2022
  AGENT_COMMANDS_MAP,
2019
2023
  BEHAVIORAL_HASH_VERSION,
2024
+ DEFAULT_LEASE_TTL_MS,
2020
2025
  DEFAULT_LOG_MAX_BYTES,
2021
2026
  DEFAULT_LOG_MAX_FILES,
2022
2027
  DEFAULT_MAX_HOPS,
@@ -2031,9 +2036,12 @@ export {
2031
2036
  MAX_BODY_BYTES,
2032
2037
  MAX_CONTENT_CHARS,
2033
2038
  MAX_CONTEXT_ITEM_IDS,
2039
+ MAX_LEASE_RESOURCE_CHARS,
2040
+ MAX_LEASE_TTL_MS,
2034
2041
  MAX_MESSAGE_TTL_MS,
2035
2042
  MAX_PROVIDER_METADATA_FIELDS,
2036
2043
  MAX_SKILL_REFS,
2044
+ MIN_LEASE_TTL_MS,
2037
2045
  MIN_MESSAGE_RETENTION_MS,
2038
2046
  MIN_MESSAGE_TTL_MS,
2039
2047
  ProtocolError,
@@ -14543,6 +14543,8 @@ var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
14543
14543
  var MAX_BODY_BYTES = 256 * 1024;
14544
14544
  var MAX_CONTENT_CHARS = 32e3;
14545
14545
  var MAX_AGENT_HOST_CHARS = 64;
14546
+ var MAX_LEASE_TTL_MS = 10 * 6e4;
14547
+ var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
14546
14548
 
14547
14549
  // plugins/kxm/src/workflow.ts
14548
14550
  function canonicalWorkflowEvidenceKey(value) {
@@ -14754,6 +14756,37 @@ var HubClient = class {
14754
14756
  );
14755
14757
  return result2.message;
14756
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
+ }
14757
14790
  async listWorkflows() {
14758
14791
  const result2 = await this.request("/v1/workflows");
14759
14792
  return result2.runs;
@@ -14947,6 +14980,7 @@ var HubClient = class {
14947
14980
  for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
14948
14981
  if (typeof body[key] === "string") extras[key] = body[key];
14949
14982
  }
14983
+ if (body.lease && typeof body.lease === "object") extras.lease = body.lease;
14950
14984
  throw new HubHttpError(
14951
14985
  response.status,
14952
14986
  String(body.error ?? `HTTP ${response.status}`),
@@ -15763,6 +15763,8 @@ var MAX_MESSAGE_TTL_MS = 7 * 24 * 60 * 6e4;
15763
15763
  var DEFAULT_MESSAGE_RETENTION_MS = 7 * 24 * 60 * 6e4;
15764
15764
  var MAX_BODY_BYTES = 256 * 1024;
15765
15765
  var MAX_AGENT_HOST_CHARS = 64;
15766
+ var MAX_LEASE_TTL_MS = 10 * 6e4;
15767
+ var DEFAULT_LEASE_TTL_MS = 5 * 6e4;
15766
15768
 
15767
15769
  // plugins/kxm/src/workflow.ts
15768
15770
  function canonicalWorkflowEvidenceKey(value) {
@@ -15974,6 +15976,37 @@ var HubClient = class {
15974
15976
  );
15975
15977
  return result.message;
15976
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
+ }
15977
16010
  async listWorkflows() {
15978
16011
  const result = await this.request("/v1/workflows");
15979
16012
  return result.runs;
@@ -16167,6 +16200,7 @@ var HubClient = class {
16167
16200
  for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
16168
16201
  if (typeof body[key] === "string") extras[key] = body[key];
16169
16202
  }
16203
+ if (body.lease && typeof body.lease === "object") extras.lease = body.lease;
16170
16204
  throw new HubHttpError(
16171
16205
  response.status,
16172
16206
  String(body.error ?? `HTTP ${response.status}`),
@@ -17145,7 +17179,7 @@ async function deliverInboxNotification(messageId, delivered, notify) {
17145
17179
  }
17146
17180
 
17147
17181
  // plugins/kxm/src/mcp-server.ts
17148
- var VERSION = "0.7.71";
17182
+ var VERSION = "0.7.72";
17149
17183
  var inbox = /* @__PURE__ */ new Map();
17150
17184
  var notifiedInbox = /* @__PURE__ */ new Set();
17151
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
  }