@cotal-ai/connector-hermes 0.8.3 → 0.9.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AACA,OAAO,EAA2B,KAAK,SAAS,EAAoC,MAAM,gBAAgB,CAAC;AAW3G;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,SAkC7B,CAAC"}
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AACA,OAAO,EAA2B,KAAK,SAAS,EAAoC,MAAM,gBAAgB,CAAC;AAW3G;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,SAuC7B,CAAC"}
package/dist/extension.js CHANGED
@@ -28,6 +28,11 @@ export const hermesConnector = {
28
28
  // the manager can't drive (no Windows named-pipe bridge, no cooperative shutdown). No fallback.
29
29
  if (process.platform === "win32")
30
30
  throw new Error("the Hermes connector is Unix-only (AF_UNIX bridge + Python sidecar) — not supported on Windows");
31
+ // Resuming an existing session isn't supported by Hermes (no fork-from-transcript primitive in
32
+ // the gateway launcher). Throw rather than spawn fresh silently — this connector otherwise
33
+ // ignores opts it doesn't render, so without this guard `resume` would be dropped without a word.
34
+ if (opts.resume)
35
+ throw new Error("the Hermes connector does not support resuming an existing session (resume)");
31
36
  // OS allow-list + the named model-provider key (Hermes is model-agnostic; any one unlocks a
32
37
  // provider), forwarded BY NAME — never `...process.env` — so the operator's unrelated secrets
33
38
  // don't reach the gateway child (P3).
@@ -1 +1 @@
1
- {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAoD,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAElF;;;2DAG2D;AAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChF,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,YAAY,SAAS,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAc;IACxC,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,CAAC,QAAQ,CAAC;IACpB,WAAW,CAAC,IAAgB;QAC1B,8FAA8F;QAC9F,gGAAgG;QAChG,gGAAgG;QAChG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC9B,MAAM,IAAI,KAAK,CAAC,gGAAgG,CAAC,CAAC;QACpH,4FAA4F;QAC5F,8FAA8F;QAC9F,sCAAsC;QACtC,MAAM,GAAG,GAA2B;YAClC,GAAG,SAAS,CAAC,EAAE,YAAY,EAAE,mBAAmB,EAAE,CAAC;YACnD,GAAG,MAAM,CAAC,IAAI,CAAC;YACf,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,UAAU,EAAE,IAAI,CAAC,IAAI;SACtB,CAAC;QACF,IAAI,IAAI,CAAC,IAAI;YAAE,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1C,IAAI,IAAI,CAAC,EAAE;YAAE,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,KAAK;YAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;QAC7C,IAAI,IAAI,CAAC,OAAO;YAAE,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC;QACnD,wFAAwF;QACxF,mFAAmF;QACnF,IAAI,IAAI,CAAC,UAAU;YAAE,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC,UAAU,CAAC;QAC5D,gGAAgG;QAChG,8FAA8F;QAC9F,2FAA2F;QAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACrF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAClE,IAAI,KAAK;YAAE,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;QACpC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrD,CAAC;CACF,CAAC;AAEF,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC"}
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAoD,MAAM,gBAAgB,CAAC;AAC3G,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAElF;;;2DAG2D;AAC3D,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AACnE,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,0BAA0B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChF,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,YAAY,SAAS,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAEtF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAc;IACxC,IAAI,EAAE,WAAW;IACjB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,CAAC,QAAQ,CAAC;IACpB,WAAW,CAAC,IAAgB;QAC1B,8FAA8F;QAC9F,gGAAgG;QAChG,gGAAgG;QAChG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC9B,MAAM,IAAI,KAAK,CAAC,gGAAgG,CAAC,CAAC;QACpH,+FAA+F;QAC/F,2FAA2F;QAC3F,kGAAkG;QAClG,IAAI,IAAI,CAAC,MAAM;YACb,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;QACjG,4FAA4F;QAC5F,8FAA8F;QAC9F,sCAAsC;QACtC,MAAM,GAAG,GAA2B;YAClC,GAAG,SAAS,CAAC,EAAE,YAAY,EAAE,mBAAmB,EAAE,CAAC;YACnD,GAAG,MAAM,CAAC,IAAI,CAAC;YACf,WAAW,EAAE,IAAI,CAAC,KAAK;YACvB,UAAU,EAAE,IAAI,CAAC,IAAI;SACtB,CAAC;QACF,IAAI,IAAI,CAAC,IAAI;YAAE,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC;QAC1C,IAAI,IAAI,CAAC,EAAE;YAAE,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,IAAI,CAAC,KAAK;YAAE,GAAG,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC;QAC7C,IAAI,IAAI,CAAC,OAAO;YAAE,GAAG,CAAC,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC;QACnD,wFAAwF;QACxF,mFAAmF;QACnF,IAAI,IAAI,CAAC,UAAU;YAAE,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC,UAAU,CAAC;QAC5D,gGAAgG;QAChG,8FAA8F;QAC9F,2FAA2F;QAC3F,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACrF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAClE,IAAI,KAAK;YAAE,GAAG,CAAC,YAAY,GAAG,KAAK,CAAC;QACpC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrD,CAAC;CACF,CAAC;AAEF,QAAQ,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cotal-ai/connector-hermes",
3
3
  "description": "Cotal connector for the Hermes (Nous Research) agent.",
4
- "version": "0.8.3",
4
+ "version": "0.9.1",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -23,14 +23,14 @@
23
23
  "dependencies": {
24
24
  "tsx": "^4.22.4",
25
25
  "zod": "^4.4.3",
26
- "@cotal-ai/connector-core": "0.8.3"
26
+ "@cotal-ai/connector-core": "0.9.1"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "@cotal-ai/core": ">=0.1.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "esbuild": "^0.28.0",
33
- "@cotal-ai/core": "0.8.3"
33
+ "@cotal-ai/core": "0.9.1"
34
34
  },
35
35
  "files": [
36
36
  "bin",
@@ -16617,7 +16617,56 @@ var SYS_LIMITS = { ...BASE_LIMITS, mem_storage: 0, disk_storage: 0 };
16617
16617
  // ../../packages/core/dist/streams.js
16618
16618
  var import_jetstream = __toESM(require_mod4(), 1);
16619
16619
  var import_transport_node = __toESM(require_transport_node(), 1);
16620
+ var import_kv2 = __toESM(require_mod6(), 1);
16621
+
16622
+ // ../../packages/core/dist/acls.js
16620
16623
  var import_kv = __toESM(require_mod6(), 1);
16624
+ async function openAclRegistry(nc, space, opts = {}) {
16625
+ const kvm = new import_kv.Kvm(nc);
16626
+ return opts.create ? kvm.create(aclBucket(space)) : kvm.open(aclBucket(space));
16627
+ }
16628
+ async function readAcl(kv, owner) {
16629
+ const e = await kv.get(aclKey(owner));
16630
+ if (!e || e.operation === "DEL" || e.operation === "PURGE")
16631
+ return void 0;
16632
+ try {
16633
+ const record2 = e.json();
16634
+ if (!Array.isArray(record2.allowSubscribe))
16635
+ return void 0;
16636
+ return { record: record2, revision: e.revision };
16637
+ } catch {
16638
+ return void 0;
16639
+ }
16640
+ }
16641
+ async function commitAcl(kv, owner, allowSubscribe) {
16642
+ const key = aclKey(owner);
16643
+ for (let attempt = 0; attempt < 5; attempt++) {
16644
+ const cur = await readAcl(kv, owner);
16645
+ const next = {
16646
+ allowSubscribe: [...allowSubscribe],
16647
+ revision: (cur?.record.revision ?? 0) + 1,
16648
+ updatedAt: Date.now()
16649
+ };
16650
+ const data = new TextEncoder().encode(JSON.stringify(next));
16651
+ if (!cur) {
16652
+ try {
16653
+ await kv.create(key, data);
16654
+ return next;
16655
+ } catch {
16656
+ continue;
16657
+ }
16658
+ }
16659
+ try {
16660
+ await kv.update(key, data, cur.revision);
16661
+ return next;
16662
+ } catch {
16663
+ continue;
16664
+ }
16665
+ }
16666
+ throw new Error(`acl CAS exhausted retries for ${owner}`);
16667
+ }
16668
+
16669
+ // ../../packages/core/dist/streams.js
16621
16670
  var MAX_MSGS_PER_SUBJECT = 1e3;
16622
16671
  var PLANE3_DEDUP_WINDOW_MS = 2 * 60 * 60 * 1e3;
16623
16672
  var DINBOX_MAX_ACK_PENDING = 1e3;
@@ -16723,7 +16772,7 @@ function fanoutDurableConfig(space, opts = {}) {
16723
16772
  }
16724
16773
 
16725
16774
  // ../../packages/core/dist/channels.js
16726
- var import_kv2 = __toESM(require_mod6(), 1);
16775
+ var import_kv3 = __toESM(require_mod6(), 1);
16727
16776
  var import_transport_node2 = __toESM(require_transport_node(), 1);
16728
16777
  function parseDuration(s) {
16729
16778
  const m = /^(\d+)(s|m|h|d)$/.exec(s.trim());
@@ -16744,7 +16793,7 @@ function effectiveDeliveryClass(cfg, defaults) {
16744
16793
  return cfg?.deliveryClass ?? defaults?.deliveryClass ?? "durable";
16745
16794
  }
16746
16795
  async function openChannelRegistry(nc, space, opts = {}) {
16747
- const kvm = new import_kv2.Kvm(nc);
16796
+ const kvm = new import_kv3.Kvm(nc);
16748
16797
  return opts.create ? kvm.create(channelBucket(space)) : kvm.open(channelBucket(space));
16749
16798
  }
16750
16799
  async function readChannelConfig(kv, channel) {
@@ -16765,7 +16814,7 @@ async function decode(kv, key) {
16765
16814
  }
16766
16815
 
16767
16816
  // ../../packages/core/dist/members.js
16768
- var import_kv3 = __toESM(require_mod6(), 1);
16817
+ var import_kv4 = __toESM(require_mod6(), 1);
16769
16818
  var StaleMembershipWrite = class extends Error {
16770
16819
  constructor(channel, owner, attempted, current) {
16771
16820
  super(`stale membership write for ${channel}/${owner}: generation ${attempted} < current ${current}`);
@@ -16773,7 +16822,7 @@ var StaleMembershipWrite = class extends Error {
16773
16822
  }
16774
16823
  };
16775
16824
  async function openMembersRegistry(nc, space, opts = {}) {
16776
- const kvm = new import_kv3.Kvm(nc);
16825
+ const kvm = new import_kv4.Kvm(nc);
16777
16826
  return opts.create ? kvm.create(membersBucket(space)) : kvm.open(membersBucket(space));
16778
16827
  }
16779
16828
  async function readMember(kv, channel, owner) {
@@ -16872,53 +16921,6 @@ function durableEligible(rec, seq) {
16872
16921
  return true;
16873
16922
  }
16874
16923
 
16875
- // ../../packages/core/dist/acls.js
16876
- var import_kv4 = __toESM(require_mod6(), 1);
16877
- async function openAclRegistry(nc, space, opts = {}) {
16878
- const kvm = new import_kv4.Kvm(nc);
16879
- return opts.create ? kvm.create(aclBucket(space)) : kvm.open(aclBucket(space));
16880
- }
16881
- async function readAcl(kv, owner) {
16882
- const e = await kv.get(aclKey(owner));
16883
- if (!e || e.operation === "DEL" || e.operation === "PURGE")
16884
- return void 0;
16885
- try {
16886
- const record2 = e.json();
16887
- if (!Array.isArray(record2.allowSubscribe))
16888
- return void 0;
16889
- return { record: record2, revision: e.revision };
16890
- } catch {
16891
- return void 0;
16892
- }
16893
- }
16894
- async function commitAcl(kv, owner, allowSubscribe) {
16895
- const key = aclKey(owner);
16896
- for (let attempt = 0; attempt < 5; attempt++) {
16897
- const cur = await readAcl(kv, owner);
16898
- const next = {
16899
- allowSubscribe: [...allowSubscribe],
16900
- revision: (cur?.record.revision ?? 0) + 1,
16901
- updatedAt: Date.now()
16902
- };
16903
- const data = new TextEncoder().encode(JSON.stringify(next));
16904
- if (!cur) {
16905
- try {
16906
- await kv.create(key, data);
16907
- return next;
16908
- } catch {
16909
- continue;
16910
- }
16911
- }
16912
- try {
16913
- await kv.update(key, data, cur.revision);
16914
- return next;
16915
- } catch {
16916
- continue;
16917
- }
16918
- }
16919
- throw new Error(`acl CAS exhausted retries for ${owner}`);
16920
- }
16921
-
16922
16924
  // ../../packages/core/dist/membership-feed.js
16923
16925
  var import_transport_node3 = __toESM(require_transport_node(), 1);
16924
16926
  var import_kv5 = __toESM(require_mod6(), 1);
@@ -17067,6 +17069,7 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17067
17069
  ttlMs;
17068
17070
  doRegister;
17069
17071
  doWatch;
17072
+ doWatchChannels;
17070
17073
  doConsume;
17071
17074
  ackWaitMs;
17072
17075
  inactiveThresholdMs;
@@ -17182,6 +17185,7 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17182
17185
  this.ttlMs = opts.ttlMs ?? 6e3;
17183
17186
  this.doRegister = opts.registerPresence ?? true;
17184
17187
  this.doWatch = opts.watchPresence ?? true;
17188
+ this.doWatchChannels = opts.watchChannels ?? true;
17185
17189
  this.doConsume = opts.consume ?? true;
17186
17190
  this.channelModes = opts.channelModes && Object.keys(opts.channelModes).length ? opts.channelModes : void 0;
17187
17191
  this.ackWaitMs = opts.ackWaitMs ?? 6e4;
@@ -17221,9 +17225,10 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17221
17225
  await this.startPresenceWatch();
17222
17226
  this.sweepTimer = setInterval(() => this.sweep(), Math.max(500, Math.floor(this.ttlMs / 3)));
17223
17227
  }
17224
- if (this.doWatch || this.doConsume) {
17228
+ const watchChannels = this.doWatch && this.doWatchChannels;
17229
+ if (watchChannels || this.doConsume) {
17225
17230
  this.channelKv = await openChannelRegistry(this.nc, this.space, { create: !this.creds });
17226
- if (this.doWatch)
17231
+ if (watchChannels)
17227
17232
  await this.startChannelWatch();
17228
17233
  }
17229
17234
  if (this.doRegister) {
@@ -17514,8 +17519,10 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17514
17519
  * PEER's reply lane (`ctl.delivery.<victim>.reply.<n>`) and turn the responder into a confused
17515
17520
  * deputy — the broker does NOT permission-check the requester's embedded reply subject. With it, a
17516
17521
  * reply is published only when `m.reply` is under the AUTHENTICATED request subject
17517
- * (`${m.subject}.reply.…`), binding the reply to the broker-policed sender token. (The manager's
17518
- * tiers reply into the per-id `_INBOX` and leave it off.) */
17522
+ * (`${m.subject}.reply.…`), binding the reply to the broker-policed sender token. The manager's three
17523
+ * lifecycle tiers ALSO require it as of closure (i): they reply on bounded `ctl.<tier>.<caller>.reply.…`
17524
+ * (the manager cred holds the wildcard `ctl.<tier>.*.reply.>` pub — exactly the confused-deputy
17525
+ * condition above), so do NOT drop `boundReply` on them. */
17519
17526
  serveControl(service, handler, opts = {}) {
17520
17527
  if (!this.nc)
17521
17528
  throw new Error("endpoint not started");
@@ -17550,12 +17557,25 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17550
17557
  })().catch((e) => this.emit("error", e));
17551
17558
  return sub;
17552
17559
  }
17553
- /** Send a control request to a service and await its reply (client side). */
17560
+ /** Send a control request to a service and await its reply (client side). Like {@link requestDelivery},
17561
+ * the reply rides a BOUNDED subject UNDER the request subject (`ctl.<service>.<id>.reply.<uuid>`), not
17562
+ * the per-id `_INBOX` — closure (i): this frees the manager's permission set from needing a position-1
17563
+ * inbox wildcard, so its publish surface can be an exact self-scoped allow-list (no message forging).
17564
+ * `noMux` lets us name the reply subject while keeping NoResponders detection. The random suffix is
17565
+ * defense-in-depth (a predictable suffix would let a peer target an in-flight named reply sub). The
17566
+ * reply sits under the sender's OWN request subject, so the responder's `boundReply` guard accepts it.
17567
+ *
17568
+ * CUTOVER (not backward-compatible): an agent cred minted BEFORE closure (i) lacks the
17569
+ * `ctl.<tier>.<id>.reply.>` sub grant — it can still publish the request but cannot subscribe the
17570
+ * reply, so its control calls (spawn/despawn/purge/definePersona, self-stop) hang. The per-user-auth
17571
+ * atomic cutover re-mints every agent; if this change ships ahead of that, agents must be RESPAWNED. */
17554
17572
  async requestControl(service, req, timeoutMs = 5e3) {
17555
17573
  if (!this.nc)
17556
17574
  throw new Error(this.notLiveMsg());
17575
+ const reqSubject = controlServiceSubject(this.space, service, this.card.id);
17576
+ const reply = `${reqSubject}.reply.${(0, import_node_crypto.randomUUID)()}`;
17557
17577
  const body = { ...req, from: req.from ?? this.ref() };
17558
- const m = await this.nc.request(controlServiceSubject(this.space, service, this.card.id), JSON.stringify(body), { timeout: timeoutMs });
17578
+ const m = await this.nc.request(reqSubject, JSON.stringify(body), { timeout: timeoutMs, noMux: true, reply });
17559
17579
  return m.json();
17560
17580
  }
17561
17581
  /** Send a durable-membership request to the SERVER-SIDE delivery daemon (`ctl.delivery`) and await its
@@ -18010,23 +18030,29 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
18010
18030
  return void 0;
18011
18031
  }
18012
18032
  }
18013
- /** Ensure + bind the manager singleton-lease bucket. NOTE: `Kvm.open` binds LAZILY — it does NOT
18014
- * verify the stream exists or throw when it's missing, so it can't "create if absent" (a fresh bucket
18015
- * then fails 'stream not found' on the first write — unlike the delivery bucket, which is pre-created
18016
- * at `cotal up`). `create` is the ensure-exists call: it makes the bucket (bucket-level TTL) or, when
18017
- * another manager already did, throws and we bind the now-existing one. Either way the per-KEY CAS
18018
- * create stays the only single-flight gate, so a lost bucket-create race never reads as "lease held".
18019
- * The manager is allow-all, so it may create. */
18033
+ /** Ensure + bind the manager singleton-lease bucket. Mirrors the presence-bucket pattern (connectAndBind):
18034
+ * AUTH mode OPENs the bucket pre-created at `cotal up` (the scoped `supervisor` cred holds no
18035
+ * STREAM.CREATE and `open` binds direct=false, so the CAS-conflict `kv.get` inside `acquire` rides
18036
+ * STREAM.MSG.GET, the verb the supervisor grants, never DIRECT.GET). OPEN mode (no creds) create-firsts:
18037
+ * `Kvm.open` binds LAZILY it does NOT verify the stream exists or throw when it's missing (a fresh
18038
+ * bucket then fails 'stream not found' on the first write), so `create` is the ensure-exists call (it
18039
+ * makes the bucket or, when another endpoint already did, throws and we bind the existing one). Either
18040
+ * way the per-KEY CAS create stays the only single-flight gate, so a lost bucket-create race never reads
18041
+ * as "lease held". */
18020
18042
  async managerLeaseRegistry() {
18021
18043
  if (!this.nc)
18022
18044
  throw new Error("endpoint not started");
18023
18045
  if (this.managerLeaseKv)
18024
18046
  return this.managerLeaseKv;
18025
18047
  const kvm = new import_kv7.Kvm(this.nc);
18026
- try {
18027
- this.managerLeaseKv = await kvm.create(managerBucket(this.space), { ttl: MANAGER_LEASE_TTL_MS });
18028
- } catch {
18048
+ if (this.creds) {
18029
18049
  this.managerLeaseKv = await kvm.open(managerBucket(this.space));
18050
+ } else {
18051
+ try {
18052
+ this.managerLeaseKv = await kvm.create(managerBucket(this.space), { ttl: MANAGER_LEASE_TTL_MS });
18053
+ } catch {
18054
+ this.managerLeaseKv = await kvm.open(managerBucket(this.space));
18055
+ }
18030
18056
  }
18031
18057
  return this.managerLeaseKv;
18032
18058
  }
@@ -19153,6 +19179,8 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
19153
19179
  this.applyPresence(e.key, p);
19154
19180
  }
19155
19181
  applyPresence(id, raw) {
19182
+ if (raw.card?.id !== id)
19183
+ return;
19156
19184
  const prev = this.roster.get(id);
19157
19185
  const stale = Date.now() - raw.ts > this.ttlMs;
19158
19186
  const p = stale || raw.status === "offline" ? this.toOffline(raw) : raw;
@@ -19711,7 +19739,9 @@ var MeshAgent = class extends import_node_events2.EventEmitter {
19711
19739
  * the harness (default the manager's `cotal`/Claude), `opts.model` overrides the
19712
19740
  * persona file's `model:`, and `opts.cwd` roots the new peer at a different folder/repo
19713
19741
  * than the manager's workspace — the same knobs the operator's `cotal start` carries, so
19714
- * the agent and operator spawn doors share one control-op contract. */
19742
+ * the agent and operator spawn doors share one control-op contract. (Session `resume` is
19743
+ * intentionally NOT forwarded here: forking a host-local `~/.claude` transcript is an
19744
+ * operator-local intent, kept off the peer-facing spawn door — see #159.) */
19715
19745
  async spawn(name, role, opts) {
19716
19746
  this.assertConnected();
19717
19747
  return this.ep.requestControl(CONTROL_PRIVILEGED, {
@@ -34481,8 +34511,6 @@ function controlFailure(action, e) {
34481
34511
  function statusGlyph(s) {
34482
34512
  return s === "working" ? "\u25CF" : s === "waiting" ? "\u25D0" : s === "idle" ? "\u25CB" : "\xB7";
34483
34513
  }
34484
- var FACE_TAG_RE = /\[\[\s*face\s*:\s*[\w-]+\s*\]\]\s?/gi;
34485
- var stripFaceTags = (text) => text.replace(FACE_TAG_RE, "");
34486
34514
  var ATTENTION_DESC = {
34487
34515
  open: "open \u2014 you receive everything; untagged channel chatter wakes you when idle",
34488
34516
  dnd: "dnd \u2014 channel chatter no longer wakes you (it still arrives in your next turn); DMs, anycast, and @mentions still wake you",
@@ -34613,7 +34641,7 @@ ${all.map(fmtItem).join("\n")}`);
34613
34641
  },
34614
34642
  async run(agent, _config, { text: msg, channel, mentions }) {
34615
34643
  try {
34616
- const m = await agent.send(stripFaceTags(msg), channel, mentions);
34644
+ const m = await agent.send(msg, channel, mentions);
34617
34645
  return ok(`Sent to #${m.channel}${m.mentions?.length ? ` (mentioned @${m.mentions.join(", @")})` : ""}.`);
34618
34646
  } catch (e) {
34619
34647
  return err(`Couldn't send: ${e.message}`);
@@ -34630,7 +34658,7 @@ ${all.map(fmtItem).join("\n")}`);
34630
34658
  },
34631
34659
  async run(agent, _config, { to, text: msg }) {
34632
34660
  try {
34633
- const { peer } = await agent.dm(to, stripFaceTags(msg));
34661
+ const { peer } = await agent.dm(to, msg);
34634
34662
  return ok(`DM sent to ${peer.card.name}.`);
34635
34663
  } catch (e) {
34636
34664
  if (e instanceof AmbiguousPeerError) {
@@ -34652,7 +34680,7 @@ ${who}`);
34652
34680
  },
34653
34681
  async run(agent, _config, { role, text: msg }) {
34654
34682
  try {
34655
- await agent.anycast(role, stripFaceTags(msg));
34683
+ await agent.anycast(role, msg);
34656
34684
  return ok(`Sent to one @${role}.`);
34657
34685
  } catch (e) {
34658
34686
  return err(`Couldn't send: ${e.message}`);
@@ -34794,6 +34822,11 @@ ${info}${caught}`);
34794
34822
  agent: external_exports.string().optional().describe("Optional harness the new peer runs on \u2014 the agent/connector type (claude, opencode, hermes), NOT the persona to spawn (that's `name`). Defaults to the manager's default (Claude)."),
34795
34823
  model: external_exports.string().optional().describe("Optional model override (e.g. opus, sonnet) \u2014 wins over the persona file's model:."),
34796
34824
  cwd: external_exports.string().optional().describe("Optional working directory to root the new peer at (e.g. a different repo). A relative path resolves against the manager's workspace; omitted \u2192 it shares the manager's workspace.")
34825
+ // NOTE: session `resume` is deliberately NOT exposed here. Forking a host-local `~/.claude`
34826
+ // transcript is an operator-local intent; letting a spawn-capable mesh PEER name a host
34827
+ // session id would expand `spawn` into host-transcript disclosure with no broker-enforced
34828
+ // boundary. Resume lives only on the operator CLI (`cotal spawn --resume` / `cotal start
34829
+ // --resume`); a peer-facing, capability-gated resume is deferred (see #159).
34797
34830
  },
34798
34831
  async run(agent, _config, { name, role, agent: agentType, model, cwd }) {
34799
34832
  try {