@cotal-ai/connector-hermes 0.9.0 → 0.10.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.
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.9.0",
4
+ "version": "0.10.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.9.0"
26
+ "@cotal-ai/connector-core": "0.10.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.9.0"
33
+ "@cotal-ai/core": "0.10.0"
34
34
  },
35
35
  "files": [
36
36
  "bin",
@@ -16601,6 +16601,7 @@ var Algorithms;
16601
16601
 
16602
16602
  // ../../packages/core/dist/provision.js
16603
16603
  var import_nkeys2 = __toESM(require_mod(), 1);
16604
+ var FIVE_MINUTES = 5 * 60;
16604
16605
  var BASE_LIMITS = {
16605
16606
  subs: -1,
16606
16607
  conn: -1,
@@ -16617,7 +16618,56 @@ var SYS_LIMITS = { ...BASE_LIMITS, mem_storage: 0, disk_storage: 0 };
16617
16618
  // ../../packages/core/dist/streams.js
16618
16619
  var import_jetstream = __toESM(require_mod4(), 1);
16619
16620
  var import_transport_node = __toESM(require_transport_node(), 1);
16621
+ var import_kv2 = __toESM(require_mod6(), 1);
16622
+
16623
+ // ../../packages/core/dist/acls.js
16620
16624
  var import_kv = __toESM(require_mod6(), 1);
16625
+ async function openAclRegistry(nc, space, opts = {}) {
16626
+ const kvm = new import_kv.Kvm(nc);
16627
+ return opts.create ? kvm.create(aclBucket(space)) : kvm.open(aclBucket(space));
16628
+ }
16629
+ async function readAcl(kv, owner) {
16630
+ const e = await kv.get(aclKey(owner));
16631
+ if (!e || e.operation === "DEL" || e.operation === "PURGE")
16632
+ return void 0;
16633
+ try {
16634
+ const record2 = e.json();
16635
+ if (!Array.isArray(record2.allowSubscribe))
16636
+ return void 0;
16637
+ return { record: record2, revision: e.revision };
16638
+ } catch {
16639
+ return void 0;
16640
+ }
16641
+ }
16642
+ async function commitAcl(kv, owner, allowSubscribe) {
16643
+ const key = aclKey(owner);
16644
+ for (let attempt = 0; attempt < 5; attempt++) {
16645
+ const cur = await readAcl(kv, owner);
16646
+ const next = {
16647
+ allowSubscribe: [...allowSubscribe],
16648
+ revision: (cur?.record.revision ?? 0) + 1,
16649
+ updatedAt: Date.now()
16650
+ };
16651
+ const data = new TextEncoder().encode(JSON.stringify(next));
16652
+ if (!cur) {
16653
+ try {
16654
+ await kv.create(key, data);
16655
+ return next;
16656
+ } catch {
16657
+ continue;
16658
+ }
16659
+ }
16660
+ try {
16661
+ await kv.update(key, data, cur.revision);
16662
+ return next;
16663
+ } catch {
16664
+ continue;
16665
+ }
16666
+ }
16667
+ throw new Error(`acl CAS exhausted retries for ${owner}`);
16668
+ }
16669
+
16670
+ // ../../packages/core/dist/streams.js
16621
16671
  var MAX_MSGS_PER_SUBJECT = 1e3;
16622
16672
  var PLANE3_DEDUP_WINDOW_MS = 2 * 60 * 60 * 1e3;
16623
16673
  var DINBOX_MAX_ACK_PENDING = 1e3;
@@ -16723,7 +16773,7 @@ function fanoutDurableConfig(space, opts = {}) {
16723
16773
  }
16724
16774
 
16725
16775
  // ../../packages/core/dist/channels.js
16726
- var import_kv2 = __toESM(require_mod6(), 1);
16776
+ var import_kv3 = __toESM(require_mod6(), 1);
16727
16777
  var import_transport_node2 = __toESM(require_transport_node(), 1);
16728
16778
  function parseDuration(s) {
16729
16779
  const m = /^(\d+)(s|m|h|d)$/.exec(s.trim());
@@ -16744,7 +16794,7 @@ function effectiveDeliveryClass(cfg, defaults) {
16744
16794
  return cfg?.deliveryClass ?? defaults?.deliveryClass ?? "durable";
16745
16795
  }
16746
16796
  async function openChannelRegistry(nc, space, opts = {}) {
16747
- const kvm = new import_kv2.Kvm(nc);
16797
+ const kvm = new import_kv3.Kvm(nc);
16748
16798
  return opts.create ? kvm.create(channelBucket(space)) : kvm.open(channelBucket(space));
16749
16799
  }
16750
16800
  async function readChannelConfig(kv, channel) {
@@ -16765,7 +16815,7 @@ async function decode(kv, key) {
16765
16815
  }
16766
16816
 
16767
16817
  // ../../packages/core/dist/members.js
16768
- var import_kv3 = __toESM(require_mod6(), 1);
16818
+ var import_kv4 = __toESM(require_mod6(), 1);
16769
16819
  var StaleMembershipWrite = class extends Error {
16770
16820
  constructor(channel, owner, attempted, current) {
16771
16821
  super(`stale membership write for ${channel}/${owner}: generation ${attempted} < current ${current}`);
@@ -16773,7 +16823,7 @@ var StaleMembershipWrite = class extends Error {
16773
16823
  }
16774
16824
  };
16775
16825
  async function openMembersRegistry(nc, space, opts = {}) {
16776
- const kvm = new import_kv3.Kvm(nc);
16826
+ const kvm = new import_kv4.Kvm(nc);
16777
16827
  return opts.create ? kvm.create(membersBucket(space)) : kvm.open(membersBucket(space));
16778
16828
  }
16779
16829
  async function readMember(kv, channel, owner) {
@@ -16872,53 +16922,6 @@ function durableEligible(rec, seq) {
16872
16922
  return true;
16873
16923
  }
16874
16924
 
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
16925
  // ../../packages/core/dist/membership-feed.js
16923
16926
  var import_transport_node3 = __toESM(require_transport_node(), 1);
16924
16927
  var import_kv5 = __toESM(require_mod6(), 1);
@@ -19371,6 +19374,7 @@ function configFromEnv(env = process.env) {
19371
19374
 
19372
19375
  // ../connector-core/dist/agent.js
19373
19376
  var import_node_events2 = require("node:events");
19377
+ var SPAWN_TIMEOUT_MS = 4e4;
19374
19378
  function buildMeta(config2) {
19375
19379
  const meta3 = { ...config2.meta ?? {} };
19376
19380
  if (config2.model)
@@ -19732,11 +19736,14 @@ var MeshAgent = class extends import_node_events2.EventEmitter {
19732
19736
  }
19733
19737
  // ---- supervision ---------------------------------------------------------
19734
19738
  /** Ask the manager to spawn a new teammate into this space (its `start` op).
19739
+ * #159 B1: the manager replies to `start` only on a REAL outcome — presence join, process exit,
19740
+ * or its ~30s readiness backstop — so the request must outlive that window ({@link SPAWN_TIMEOUT_MS}),
19741
+ * not the 5s op default.
19735
19742
  * How it lands — a detached PTY, a tmux window, a cmux tab — is the manager's
19736
19743
  * runtime; from here it just joins the mesh as a lateral peer. `opts.agent` picks
19737
- * the harness (default the manager's `cotal`/Claude), `opts.model` overrides the
19744
+ * the harness (default the manager's `COTAL_DEFAULT_AGENT`, else `cotal`/Claude), `opts.model` overrides the
19738
19745
  * persona file's `model:`, and `opts.cwd` roots the new peer at a different folder/repo
19739
- * than the manager's workspace — the same knobs the operator's `cotal start` carries, so
19746
+ * than the manager's workspace — the same knobs the operator's `cotal spawn --detach` carries, so
19740
19747
  * the agent and operator spawn doors share one control-op contract. (Session `resume` is
19741
19748
  * intentionally NOT forwarded here: forking a host-local `~/.claude` transcript is an
19742
19749
  * operator-local intent, kept off the peer-facing spawn door — see #159.) */
@@ -19745,7 +19752,7 @@ var MeshAgent = class extends import_node_events2.EventEmitter {
19745
19752
  return this.ep.requestControl(CONTROL_PRIVILEGED, {
19746
19753
  op: "start",
19747
19754
  args: { name, role, agent: opts?.agent, model: opts?.model, cwd: opts?.cwd }
19748
- });
19755
+ }, SPAWN_TIMEOUT_MS);
19749
19756
  }
19750
19757
  /** Ask the manager to tear a teammate down (its `stop` op). Graceful by default —
19751
19758
  * the session is told to exit cleanly (so it leaves the mesh) before the
@@ -34817,14 +34824,14 @@ ${info}${caught}`);
34817
34824
  schema: {
34818
34825
  name: external_exports.string().describe("Which persona to spawn \u2014 the persona FILENAME in .cotal/agents (e.g. `review-critic`), without the .md. The new peer joins under the persona's own `name:` (auto-numbered, e.g. socrates-2, if that's taken). Fails if no such persona file exists \u2014 spawn an existing persona, don't invent a name."),
34819
34826
  role: external_exports.string().optional().describe("Optional role for the new peer (e.g. worker, reviewer); overrides the persona file's role."),
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)."),
34827
+ 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 COTAL_DEFAULT_AGENT, else Claude."),
34821
34828
  model: external_exports.string().optional().describe("Optional model override (e.g. opus, sonnet) \u2014 wins over the persona file's model:."),
34822
34829
  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
34830
  // NOTE: session `resume` is deliberately NOT exposed here. Forking a host-local `~/.claude`
34824
34831
  // transcript is an operator-local intent; letting a spawn-capable mesh PEER name a host
34825
34832
  // 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).
34833
+ // boundary. Resume lives only on the operator CLI (`cotal spawn --resume`, foreground or
34834
+ // --detach); a peer-facing, capability-gated resume is deferred (see #159).
34828
34835
  },
34829
34836
  async run(agent, _config, { name, role, agent: agentType, model, cwd }) {
34830
34837
  try {