@cotal-ai/connector-hermes 0.8.2 → 0.9.0

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.2",
4
+ "version": "0.9.0",
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.2"
26
+ "@cotal-ai/connector-core": "0.9.0"
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.2"
33
+ "@cotal-ai/core": "0.9.0"
34
34
  },
35
35
  "files": [
36
36
  "bin",
@@ -17067,6 +17067,7 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17067
17067
  ttlMs;
17068
17068
  doRegister;
17069
17069
  doWatch;
17070
+ doWatchChannels;
17070
17071
  doConsume;
17071
17072
  ackWaitMs;
17072
17073
  inactiveThresholdMs;
@@ -17182,6 +17183,7 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17182
17183
  this.ttlMs = opts.ttlMs ?? 6e3;
17183
17184
  this.doRegister = opts.registerPresence ?? true;
17184
17185
  this.doWatch = opts.watchPresence ?? true;
17186
+ this.doWatchChannels = opts.watchChannels ?? true;
17185
17187
  this.doConsume = opts.consume ?? true;
17186
17188
  this.channelModes = opts.channelModes && Object.keys(opts.channelModes).length ? opts.channelModes : void 0;
17187
17189
  this.ackWaitMs = opts.ackWaitMs ?? 6e4;
@@ -17221,9 +17223,10 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17221
17223
  await this.startPresenceWatch();
17222
17224
  this.sweepTimer = setInterval(() => this.sweep(), Math.max(500, Math.floor(this.ttlMs / 3)));
17223
17225
  }
17224
- if (this.doWatch || this.doConsume) {
17226
+ const watchChannels = this.doWatch && this.doWatchChannels;
17227
+ if (watchChannels || this.doConsume) {
17225
17228
  this.channelKv = await openChannelRegistry(this.nc, this.space, { create: !this.creds });
17226
- if (this.doWatch)
17229
+ if (watchChannels)
17227
17230
  await this.startChannelWatch();
17228
17231
  }
17229
17232
  if (this.doRegister) {
@@ -17514,8 +17517,10 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17514
17517
  * PEER's reply lane (`ctl.delivery.<victim>.reply.<n>`) and turn the responder into a confused
17515
17518
  * deputy — the broker does NOT permission-check the requester's embedded reply subject. With it, a
17516
17519
  * 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.) */
17520
+ * (`${m.subject}.reply.…`), binding the reply to the broker-policed sender token. The manager's three
17521
+ * lifecycle tiers ALSO require it as of closure (i): they reply on bounded `ctl.<tier>.<caller>.reply.…`
17522
+ * (the manager cred holds the wildcard `ctl.<tier>.*.reply.>` pub — exactly the confused-deputy
17523
+ * condition above), so do NOT drop `boundReply` on them. */
17519
17524
  serveControl(service, handler, opts = {}) {
17520
17525
  if (!this.nc)
17521
17526
  throw new Error("endpoint not started");
@@ -17550,12 +17555,25 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
17550
17555
  })().catch((e) => this.emit("error", e));
17551
17556
  return sub;
17552
17557
  }
17553
- /** Send a control request to a service and await its reply (client side). */
17558
+ /** Send a control request to a service and await its reply (client side). Like {@link requestDelivery},
17559
+ * the reply rides a BOUNDED subject UNDER the request subject (`ctl.<service>.<id>.reply.<uuid>`), not
17560
+ * the per-id `_INBOX` — closure (i): this frees the manager's permission set from needing a position-1
17561
+ * inbox wildcard, so its publish surface can be an exact self-scoped allow-list (no message forging).
17562
+ * `noMux` lets us name the reply subject while keeping NoResponders detection. The random suffix is
17563
+ * defense-in-depth (a predictable suffix would let a peer target an in-flight named reply sub). The
17564
+ * reply sits under the sender's OWN request subject, so the responder's `boundReply` guard accepts it.
17565
+ *
17566
+ * CUTOVER (not backward-compatible): an agent cred minted BEFORE closure (i) lacks the
17567
+ * `ctl.<tier>.<id>.reply.>` sub grant — it can still publish the request but cannot subscribe the
17568
+ * reply, so its control calls (spawn/despawn/purge/definePersona, self-stop) hang. The per-user-auth
17569
+ * atomic cutover re-mints every agent; if this change ships ahead of that, agents must be RESPAWNED. */
17554
17570
  async requestControl(service, req, timeoutMs = 5e3) {
17555
17571
  if (!this.nc)
17556
17572
  throw new Error(this.notLiveMsg());
17573
+ const reqSubject = controlServiceSubject(this.space, service, this.card.id);
17574
+ const reply = `${reqSubject}.reply.${(0, import_node_crypto.randomUUID)()}`;
17557
17575
  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 });
17576
+ const m = await this.nc.request(reqSubject, JSON.stringify(body), { timeout: timeoutMs, noMux: true, reply });
17559
17577
  return m.json();
17560
17578
  }
17561
17579
  /** Send a durable-membership request to the SERVER-SIDE delivery daemon (`ctl.delivery`) and await its
@@ -18010,23 +18028,29 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
18010
18028
  return void 0;
18011
18029
  }
18012
18030
  }
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. */
18031
+ /** Ensure + bind the manager singleton-lease bucket. Mirrors the presence-bucket pattern (connectAndBind):
18032
+ * AUTH mode OPENs the bucket pre-created at `cotal up` (the scoped `supervisor` cred holds no
18033
+ * STREAM.CREATE and `open` binds direct=false, so the CAS-conflict `kv.get` inside `acquire` rides
18034
+ * STREAM.MSG.GET, the verb the supervisor grants, never DIRECT.GET). OPEN mode (no creds) create-firsts:
18035
+ * `Kvm.open` binds LAZILY it does NOT verify the stream exists or throw when it's missing (a fresh
18036
+ * bucket then fails 'stream not found' on the first write), so `create` is the ensure-exists call (it
18037
+ * makes the bucket or, when another endpoint already did, throws and we bind the existing one). Either
18038
+ * way the per-KEY CAS create stays the only single-flight gate, so a lost bucket-create race never reads
18039
+ * as "lease held". */
18020
18040
  async managerLeaseRegistry() {
18021
18041
  if (!this.nc)
18022
18042
  throw new Error("endpoint not started");
18023
18043
  if (this.managerLeaseKv)
18024
18044
  return this.managerLeaseKv;
18025
18045
  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 {
18046
+ if (this.creds) {
18029
18047
  this.managerLeaseKv = await kvm.open(managerBucket(this.space));
18048
+ } else {
18049
+ try {
18050
+ this.managerLeaseKv = await kvm.create(managerBucket(this.space), { ttl: MANAGER_LEASE_TTL_MS });
18051
+ } catch {
18052
+ this.managerLeaseKv = await kvm.open(managerBucket(this.space));
18053
+ }
18030
18054
  }
18031
18055
  return this.managerLeaseKv;
18032
18056
  }
@@ -19153,6 +19177,8 @@ var CotalEndpoint = class extends import_node_events.EventEmitter {
19153
19177
  this.applyPresence(e.key, p);
19154
19178
  }
19155
19179
  applyPresence(id, raw) {
19180
+ if (raw.card?.id !== id)
19181
+ return;
19156
19182
  const prev = this.roster.get(id);
19157
19183
  const stale = Date.now() - raw.ts > this.ttlMs;
19158
19184
  const p = stale || raw.status === "offline" ? this.toOffline(raw) : raw;
@@ -19323,7 +19349,7 @@ function configFromEnv(env = process.env) {
19323
19349
  description: def?.description,
19324
19350
  tags: def?.tags,
19325
19351
  meta: def?.meta,
19326
- capabilities: def?.capabilities,
19352
+ capabilities: splitList(env.COTAL_CAPABILITIES).length ? splitList(env.COTAL_CAPABILITIES) : def?.capabilities,
19327
19353
  model: env.COTAL_MODEL?.trim() || def?.model || void 0,
19328
19354
  servers: env.COTAL_SERVERS?.trim() || link?.servers || DEFAULT_SERVER,
19329
19355
  subscribe: resolvedSubscribe,
@@ -19711,7 +19737,9 @@ var MeshAgent = class extends import_node_events2.EventEmitter {
19711
19737
  * the harness (default the manager's `cotal`/Claude), `opts.model` overrides the
19712
19738
  * persona file's `model:`, and `opts.cwd` roots the new peer at a different folder/repo
19713
19739
  * 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. */
19740
+ * the agent and operator spawn doors share one control-op contract. (Session `resume` is
19741
+ * intentionally NOT forwarded here: forking a host-local `~/.claude` transcript is an
19742
+ * operator-local intent, kept off the peer-facing spawn door — see #159.) */
19715
19743
  async spawn(name, role, opts) {
19716
19744
  this.assertConnected();
19717
19745
  return this.ep.requestControl(CONTROL_PRIVILEGED, {
@@ -34481,8 +34509,6 @@ function controlFailure(action, e) {
34481
34509
  function statusGlyph(s) {
34482
34510
  return s === "working" ? "\u25CF" : s === "waiting" ? "\u25D0" : s === "idle" ? "\u25CB" : "\xB7";
34483
34511
  }
34484
- var FACE_TAG_RE = /\[\[\s*face\s*:\s*[\w-]+\s*\]\]\s?/gi;
34485
- var stripFaceTags = (text) => text.replace(FACE_TAG_RE, "");
34486
34512
  var ATTENTION_DESC = {
34487
34513
  open: "open \u2014 you receive everything; untagged channel chatter wakes you when idle",
34488
34514
  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 +34639,7 @@ ${all.map(fmtItem).join("\n")}`);
34613
34639
  },
34614
34640
  async run(agent, _config, { text: msg, channel, mentions }) {
34615
34641
  try {
34616
- const m = await agent.send(stripFaceTags(msg), channel, mentions);
34642
+ const m = await agent.send(msg, channel, mentions);
34617
34643
  return ok(`Sent to #${m.channel}${m.mentions?.length ? ` (mentioned @${m.mentions.join(", @")})` : ""}.`);
34618
34644
  } catch (e) {
34619
34645
  return err(`Couldn't send: ${e.message}`);
@@ -34630,7 +34656,7 @@ ${all.map(fmtItem).join("\n")}`);
34630
34656
  },
34631
34657
  async run(agent, _config, { to, text: msg }) {
34632
34658
  try {
34633
- const { peer } = await agent.dm(to, stripFaceTags(msg));
34659
+ const { peer } = await agent.dm(to, msg);
34634
34660
  return ok(`DM sent to ${peer.card.name}.`);
34635
34661
  } catch (e) {
34636
34662
  if (e instanceof AmbiguousPeerError) {
@@ -34652,7 +34678,7 @@ ${who}`);
34652
34678
  },
34653
34679
  async run(agent, _config, { role, text: msg }) {
34654
34680
  try {
34655
- await agent.anycast(role, stripFaceTags(msg));
34681
+ await agent.anycast(role, msg);
34656
34682
  return ok(`Sent to one @${role}.`);
34657
34683
  } catch (e) {
34658
34684
  return err(`Couldn't send: ${e.message}`);
@@ -34794,6 +34820,11 @@ ${info}${caught}`);
34794
34820
  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
34821
  model: external_exports.string().optional().describe("Optional model override (e.g. opus, sonnet) \u2014 wins over the persona file's model:."),
34796
34822
  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.")
34823
+ // NOTE: session `resume` is deliberately NOT exposed here. Forking a host-local `~/.claude`
34824
+ // transcript is an operator-local intent; letting a spawn-capable mesh PEER name a host
34825
+ // session id would expand `spawn` into host-transcript disclosure with no broker-enforced
34826
+ // boundary. Resume lives only on the operator CLI (`cotal spawn --resume` / `cotal start
34827
+ // --resume`); a peer-facing, capability-gated resume is deferred (see #159).
34797
34828
  },
34798
34829
  async run(agent, _config, { name, role, agent: agentType, model, cwd }) {
34799
34830
  try {