@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.
package/dist/extension.d.ts.map
CHANGED
|
@@ -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,
|
|
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).
|
package/dist/extension.js.map
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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.
|
|
17518
|
-
* tiers
|
|
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(
|
|
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.
|
|
18014
|
-
*
|
|
18015
|
-
*
|
|
18016
|
-
*
|
|
18017
|
-
*
|
|
18018
|
-
*
|
|
18019
|
-
*
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
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 {
|