@agentvault/claude-bridge 0.8.0 → 0.8.2
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/bridge.d.ts +6 -0
- package/dist/index.js +794 -234
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1081,6 +1081,28 @@ var __toESM = (mod4, isNodeMode, target) => (target = mod4 != null ? __create(__
|
|
|
1081
1081
|
isNodeMode || !mod4 || !mod4.__esModule ? __defProp2(target, "default", { value: mod4, enumerable: true }) : target,
|
|
1082
1082
|
mod4
|
|
1083
1083
|
));
|
|
1084
|
+
function stripWrappers(s22) {
|
|
1085
|
+
let start = 0;
|
|
1086
|
+
let end = s22.length;
|
|
1087
|
+
while (start < end && WRAPPERS.includes(s22[start])) start++;
|
|
1088
|
+
while (end > start && WRAPPERS.includes(s22[end - 1])) end--;
|
|
1089
|
+
return s22.slice(start, end);
|
|
1090
|
+
}
|
|
1091
|
+
function isAbstention(text) {
|
|
1092
|
+
if (typeof text !== "string") return false;
|
|
1093
|
+
return stripWrappers(text).toLowerCase().startsWith(SENTINEL);
|
|
1094
|
+
}
|
|
1095
|
+
var SENTINEL;
|
|
1096
|
+
var WRAPPERS;
|
|
1097
|
+
var INSTRUCTION;
|
|
1098
|
+
var init_abstention = __esm2({
|
|
1099
|
+
"src/abstention.ts"() {
|
|
1100
|
+
"use strict";
|
|
1101
|
+
SENTINEL = "[[no-reply]]";
|
|
1102
|
+
WRAPPERS = " \r\n`\"'*_";
|
|
1103
|
+
INSTRUCTION = "not every message needs an answer from you: if this one is not addressed to you and asks nothing of you, reply with exactly " + SENTINEL + " and nothing else \u2014 that is discarded, not posted, and is how you stay quiet; never post a message SAYING that you are staying quiet";
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1084
1106
|
function toEpoch(v22) {
|
|
1085
1107
|
if (typeof v22 === "bigint") return v22;
|
|
1086
1108
|
if (typeof v22 === "number") {
|
|
@@ -45906,6 +45928,11 @@ var init_ratchet = __esm2({
|
|
|
45906
45928
|
return { header, headerSignature, ciphertext, nonce };
|
|
45907
45929
|
}
|
|
45908
45930
|
decrypt(message) {
|
|
45931
|
+
if (this.state.peerIdentityPublicKey) {
|
|
45932
|
+
if (!verifyHeaderSignature(message.header, message.headerSignature, this.state.peerIdentityPublicKey)) {
|
|
45933
|
+
throw new Error("Header signature verification failed");
|
|
45934
|
+
}
|
|
45935
|
+
}
|
|
45909
45936
|
const isV2 = message.envelopeVersion === "2.0.0" && message.encryptedHeader != null && message.headerNonce != null;
|
|
45910
45937
|
const skippedResult = this.trySkippedKeys(message, isV2);
|
|
45911
45938
|
if (skippedResult !== null) {
|
|
@@ -45919,14 +45946,14 @@ var init_ratchet = __esm2({
|
|
|
45919
45946
|
if (message.header.messageNumber === 0) {
|
|
45920
45947
|
try {
|
|
45921
45948
|
const { messageKey: testKey, headerKey: testHeaderKey, nextChainKey: nextChainKey2 } = kdfChainKey(this.state.rootKey);
|
|
45922
|
-
let
|
|
45949
|
+
let ad;
|
|
45923
45950
|
if (isV2) {
|
|
45924
45951
|
libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.encryptedHeader, null, message.headerNonce, testHeaderKey);
|
|
45925
|
-
|
|
45952
|
+
ad = message.encryptedHeader;
|
|
45926
45953
|
} else {
|
|
45927
|
-
|
|
45954
|
+
ad = serializeHeader(message.header);
|
|
45928
45955
|
}
|
|
45929
|
-
const ptBytes = libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.ciphertext,
|
|
45956
|
+
const ptBytes = libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.ciphertext, ad, message.nonce, testKey);
|
|
45930
45957
|
this.state.dhReceivingPublicKey = message.header.dhPublicKey;
|
|
45931
45958
|
this.state.receivingChain = {
|
|
45932
45959
|
chainKey: nextChainKey2,
|
|
@@ -45946,13 +45973,7 @@ var init_ratchet = __esm2({
|
|
|
45946
45973
|
this.skipMessages(this.state.receivingChain, message.header.messageNumber, message.header.dhPublicKey, isV2);
|
|
45947
45974
|
const chain = this.state.receivingChain;
|
|
45948
45975
|
const { nextChainKey, messageKey, headerKey } = kdfChainKey(chain.chainKey);
|
|
45949
|
-
|
|
45950
|
-
chain.messageNumber++;
|
|
45951
|
-
if (this.state.peerIdentityPublicKey) {
|
|
45952
|
-
if (!verifyHeaderSignature(message.header, message.headerSignature, this.state.peerIdentityPublicKey)) {
|
|
45953
|
-
throw new Error("Header signature verification failed");
|
|
45954
|
-
}
|
|
45955
|
-
}
|
|
45976
|
+
let plaintextBytes;
|
|
45956
45977
|
if (isV2) {
|
|
45957
45978
|
try {
|
|
45958
45979
|
libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.encryptedHeader, null, message.headerNonce, headerKey);
|
|
@@ -45960,19 +45981,21 @@ var init_ratchet = __esm2({
|
|
|
45960
45981
|
throw new Error("V2 header decryption failed");
|
|
45961
45982
|
}
|
|
45962
45983
|
try {
|
|
45963
|
-
|
|
45964
|
-
return libsodium_wrappers_default.to_string(plaintextBytes);
|
|
45984
|
+
plaintextBytes = libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.ciphertext, message.encryptedHeader, message.nonce, messageKey);
|
|
45965
45985
|
} catch {
|
|
45966
45986
|
throw new Error("V2 decryption failed: ciphertext tampered or wrong key");
|
|
45967
45987
|
}
|
|
45988
|
+
} else {
|
|
45989
|
+
const ad = serializeHeader(message.header);
|
|
45990
|
+
try {
|
|
45991
|
+
plaintextBytes = libsodium_wrappers_default.crypto_aead_xchacha20poly1305_ietf_decrypt(null, message.ciphertext, ad, message.nonce, messageKey);
|
|
45992
|
+
} catch {
|
|
45993
|
+
throw new Error("Decryption failed: ciphertext tampered or wrong key");
|
|
45994
|
+
}
|
|
45968
45995
|
}
|
|
45969
|
-
|
|
45970
|
-
|
|
45971
|
-
|
|
45972
|
-
return libsodium_wrappers_default.to_string(plaintextBytes);
|
|
45973
|
-
} catch {
|
|
45974
|
-
throw new Error("Decryption failed: ciphertext tampered or wrong key");
|
|
45975
|
-
}
|
|
45996
|
+
chain.chainKey = nextChainKey;
|
|
45997
|
+
chain.messageNumber++;
|
|
45998
|
+
return libsodium_wrappers_default.to_string(plaintextBytes);
|
|
45976
45999
|
}
|
|
45977
46000
|
dhRatchetReceive(theirDhPublic) {
|
|
45978
46001
|
this.state.previousSendingChainLength = this.state.sendingChain?.messageNumber ?? 0;
|
|
@@ -63758,6 +63781,39 @@ var init_mls_group = __esm2({
|
|
|
63758
63781
|
const s22 = this._requireState();
|
|
63759
63782
|
return s22.groupContext.groupId;
|
|
63760
63783
|
}
|
|
63784
|
+
/**
|
|
63785
|
+
* Proof that this client actually holds the group's keys at its current
|
|
63786
|
+
* epoch. Throws if not initialized.
|
|
63787
|
+
*
|
|
63788
|
+
* RFC 9420 §8.2's `epoch_authenticator`, derived from the epoch secret. Two
|
|
63789
|
+
* properties make it the thing this codebase has been missing:
|
|
63790
|
+
*
|
|
63791
|
+
* * every member at the same epoch computes the SAME value, so it is
|
|
63792
|
+
* comparable across clients and can be checked by a server;
|
|
63793
|
+
* * it cannot be computed without the key schedule, so possessing it is
|
|
63794
|
+
* evidence of membership rather than a claim about it. RFC 9420 names
|
|
63795
|
+
* this as its purpose: "to authenticate the shared group state".
|
|
63796
|
+
*
|
|
63797
|
+
* It is safe to publish. It is a KDF output over the epoch secret, not the
|
|
63798
|
+
* secret, and the RFC intends it to be shown to out-of-band verifiers.
|
|
63799
|
+
*
|
|
63800
|
+
* WHY THIS EXISTS. `mls_groups.member_device_ids` records membership; the
|
|
63801
|
+
* ability to decrypt lives only in one browser's localStorage. Nothing
|
|
63802
|
+
* compared the two, and nothing could — so a device in the roster holding no
|
|
63803
|
+
* keys was indistinguishable from a healthy one, on every signal the server
|
|
63804
|
+
* has. Measured on production 2026-08-26: one owner device carried the keys
|
|
63805
|
+
* for 19 of the 21 owner leaves on the platform, and one 1:1 group had been
|
|
63806
|
+
* unreadable by its owner's browser for two months while every status column
|
|
63807
|
+
* read healthy.
|
|
63808
|
+
*
|
|
63809
|
+
* `epoch` alone cannot do this job: it is a number the client reports about
|
|
63810
|
+
* itself, it collides across groups, and a client that has lost its state
|
|
63811
|
+
* still remembers one.
|
|
63812
|
+
*/
|
|
63813
|
+
get epochAuthenticator() {
|
|
63814
|
+
const s22 = this._requireState();
|
|
63815
|
+
return s22.keySchedule.epochAuthenticator;
|
|
63816
|
+
}
|
|
63761
63817
|
/** The cipher suite name in use. */
|
|
63762
63818
|
get ciphersuiteName() {
|
|
63763
63819
|
return this._ciphersuiteName;
|
|
@@ -63908,11 +63964,84 @@ var init_mls_group = __esm2({
|
|
|
63908
63964
|
return { commit: commitBytes, welcome: welcomeBytes };
|
|
63909
63965
|
}
|
|
63910
63966
|
/**
|
|
63911
|
-
*
|
|
63967
|
+
* Admit N members with TWO commits total, replacing any stale leaf they hold.
|
|
63968
|
+
*
|
|
63969
|
+
* `addMembers` cannot do this alone: MLS rejects an Add for an identity
|
|
63970
|
+
* already in the tree, so a member that lost its local state must be removed
|
|
63971
|
+
* first. Doing that pair PER MEMBER is what the fulfiller did, and it is not
|
|
63972
|
+
* merely slow — it is self-defeating.
|
|
63973
|
+
*
|
|
63974
|
+
* Live 2026-08-28 14:02:21-28Z: the owner sweep re-admitted 7 room members
|
|
63975
|
+
* sequentially, each as its own removeMember + addMembers. Room 007 ran
|
|
63976
|
+
* **epoch 71 -> 85 in seven seconds**, and the Welcome minted for member n
|
|
63977
|
+
* named an epoch that member n+1's commit had already replaced — so six of
|
|
63978
|
+
* seven were stale before they finished joining, and one (`smartcode`) could
|
|
63979
|
+
* not replay forward afterwards at all.
|
|
63980
|
+
*
|
|
63981
|
+
* 🎯 A REPAIR THAT MOVES THE EPOCH ONCE PER MEMBER IS RACING ITSELF.
|
|
63982
|
+
*
|
|
63983
|
+
* ⚠️ **Why two commits and not one.** RFC 9420 §12.3 allows Remove and Add in
|
|
63984
|
+
* a single commit, and ts-mls builds it happily — but with MORE THAN ONE of
|
|
63985
|
+
* each, a REMAINING member fails to process it:
|
|
63912
63986
|
*
|
|
63913
|
-
*
|
|
63914
|
-
*
|
|
63987
|
+
* OpenError at applyUpdatePathSecret (ts-mls/src/createCommit.ts:471)
|
|
63988
|
+
* at updatePrivateKeyPath (processMessages.ts:397)
|
|
63989
|
+
*
|
|
63990
|
+
* One remove + one add in a single commit is fine; 2+2 is not. That is
|
|
63991
|
+
* pinned by a test in `mls-group.test.ts` so this cannot be "optimised" back
|
|
63992
|
+
* into one commit without the failure showing up first. Two commits still
|
|
63993
|
+
* turns 7 members from 14 epochs into 2, and every joiner lands current.
|
|
63994
|
+
*
|
|
63995
|
+
* @param keyPackages - Fresh KeyPackages, one per member to admit. Members
|
|
63996
|
+
* already holding a leaf are removed by the first commit; members with no
|
|
63997
|
+
* leaf are plain adds. Duplicate identities keep the first KeyPackage.
|
|
63998
|
+
* @returns Every commit to broadcast IN ORDER, and the Welcome to deliver to
|
|
63999
|
+
* EVERY member in the batch (each finds its own secrets inside it).
|
|
63915
64000
|
*/
|
|
64001
|
+
async reAdmitMembers(keyPackages) {
|
|
64002
|
+
this._requireState();
|
|
64003
|
+
await this._ensureCiphersuite();
|
|
64004
|
+
const seenIdentities = [];
|
|
64005
|
+
const toAdd = [];
|
|
64006
|
+
const leavesToRemove = [];
|
|
64007
|
+
for (const kp of keyPackages) {
|
|
64008
|
+
const identity = keyPackageIdentity(kp);
|
|
64009
|
+
if (identity !== null) {
|
|
64010
|
+
if (seenIdentities.some((id) => bytesEqual(id, identity)))
|
|
64011
|
+
continue;
|
|
64012
|
+
seenIdentities.push(identity);
|
|
64013
|
+
const leaf = this.memberLeafIndex(identity);
|
|
64014
|
+
if (leaf !== null)
|
|
64015
|
+
leavesToRemove.push(leaf);
|
|
64016
|
+
}
|
|
64017
|
+
toAdd.push(kp);
|
|
64018
|
+
}
|
|
64019
|
+
if (toAdd.length === 0) {
|
|
64020
|
+
throw new Error("reAdmitMembers: no members to admit");
|
|
64021
|
+
}
|
|
64022
|
+
const commits = [];
|
|
64023
|
+
if (leavesToRemove.length > 0) {
|
|
64024
|
+
const { commit } = await this.removeMembers(leavesToRemove);
|
|
64025
|
+
commits.push({ commit, epoch: this.epoch });
|
|
64026
|
+
}
|
|
64027
|
+
const added = await this.addMembers(toAdd);
|
|
64028
|
+
commits.push({ commit: added.commit, epoch: this.epoch });
|
|
64029
|
+
return { commits, welcome: added.welcome };
|
|
64030
|
+
}
|
|
64031
|
+
async removeMembers(memberIndices) {
|
|
64032
|
+
const s22 = this._requireState();
|
|
64033
|
+
const cs = await this._ensureCiphersuite();
|
|
64034
|
+
if (memberIndices.length === 0) {
|
|
64035
|
+
throw new Error("removeMembers: no members to remove");
|
|
64036
|
+
}
|
|
64037
|
+
const extraProposals = memberIndices.map((removed) => ({
|
|
64038
|
+
proposalType: "remove",
|
|
64039
|
+
remove: { removed }
|
|
64040
|
+
}));
|
|
64041
|
+
const result = await createCommit({ state: s22, cipherSuite: cs, pskIndex: this._pskIndex }, { extraProposals });
|
|
64042
|
+
this.state = result.newState;
|
|
64043
|
+
return { commit: encodeMlsMessage(result.commit) };
|
|
64044
|
+
}
|
|
63916
64045
|
async removeMember(memberIndex) {
|
|
63917
64046
|
const s22 = this._requireState();
|
|
63918
64047
|
const cs = await this._ensureCiphersuite();
|
|
@@ -64146,6 +64275,23 @@ var init_mls_delivery_order = __esm2({
|
|
|
64146
64275
|
"use strict";
|
|
64147
64276
|
}
|
|
64148
64277
|
});
|
|
64278
|
+
function noteObservedEpoch(current, seen) {
|
|
64279
|
+
const n22 = Number(seen);
|
|
64280
|
+
if (!Number.isInteger(n22) || n22 < 0)
|
|
64281
|
+
return current;
|
|
64282
|
+
return n22 > current ? n22 : current;
|
|
64283
|
+
}
|
|
64284
|
+
function needsPreSendResync(localEpoch, observedEpoch) {
|
|
64285
|
+
if (!Number.isInteger(localEpoch) || !Number.isInteger(observedEpoch)) {
|
|
64286
|
+
return false;
|
|
64287
|
+
}
|
|
64288
|
+
return observedEpoch > localEpoch;
|
|
64289
|
+
}
|
|
64290
|
+
var init_mls_presend_epoch = __esm2({
|
|
64291
|
+
"../crypto/dist/mls-presend-epoch.js"() {
|
|
64292
|
+
"use strict";
|
|
64293
|
+
}
|
|
64294
|
+
});
|
|
64149
64295
|
var dist_exports = {};
|
|
64150
64296
|
__export2(dist_exports, {
|
|
64151
64297
|
AV_CREDENTIAL_CONTEXT: () => AV_CREDENTIAL_CONTEXT,
|
|
@@ -64215,7 +64361,9 @@ __export2(dist_exports, {
|
|
|
64215
64361
|
hexTransportToEncryptedMessage: () => hexTransportToEncryptedMessage,
|
|
64216
64362
|
issueCredential: () => issueCredential,
|
|
64217
64363
|
multibaseToPublicKey: () => multibaseToPublicKey,
|
|
64364
|
+
needsPreSendResync: () => needsPreSendResync,
|
|
64218
64365
|
normalizeBackupCode: () => normalizeBackupCode,
|
|
64366
|
+
noteObservedEpoch: () => noteObservedEpoch,
|
|
64219
64367
|
orderDeliveryBatch: () => orderDeliveryBatch,
|
|
64220
64368
|
parseTraceparent: () => parseTraceparent,
|
|
64221
64369
|
performX3DH: () => performX3DH,
|
|
@@ -64255,6 +64403,7 @@ var init_dist = __esm2({
|
|
|
64255
64403
|
init_mls_group();
|
|
64256
64404
|
await init_owner_sync();
|
|
64257
64405
|
init_mls_delivery_order();
|
|
64406
|
+
init_mls_presend_epoch();
|
|
64258
64407
|
}
|
|
64259
64408
|
});
|
|
64260
64409
|
async function ensureSecureDir(dir) {
|
|
@@ -64447,7 +64596,7 @@ var init_mls_kp_pool = __esm2({
|
|
|
64447
64596
|
}
|
|
64448
64597
|
});
|
|
64449
64598
|
function ownIdentity() {
|
|
64450
|
-
const v22 = true ? "0.23.
|
|
64599
|
+
const v22 = true ? "0.23.29" : FALLBACK;
|
|
64451
64600
|
return `${PACKAGE}@${v22}`;
|
|
64452
64601
|
}
|
|
64453
64602
|
function buildClientVersion(override) {
|
|
@@ -65359,10 +65508,12 @@ var SecureChannel;
|
|
|
65359
65508
|
var init_channel = __esm2({
|
|
65360
65509
|
async "src/channel.ts"() {
|
|
65361
65510
|
"use strict";
|
|
65511
|
+
init_abstention();
|
|
65362
65512
|
init_mls_commit_classify();
|
|
65363
65513
|
await init_libsodium_wrappers();
|
|
65364
65514
|
await init_dist();
|
|
65365
65515
|
await init_dist();
|
|
65516
|
+
init_mls_presend_epoch();
|
|
65366
65517
|
init_mls_state();
|
|
65367
65518
|
await init_mls_kp_pool();
|
|
65368
65519
|
init_client_version();
|
|
@@ -65476,6 +65627,17 @@ var init_channel = __esm2({
|
|
|
65476
65627
|
* self-heal rather than be logged and dropped. */
|
|
65477
65628
|
_mlsCommitFailCounts = /* @__PURE__ */ new Map();
|
|
65478
65629
|
static MAX_MLS_DECRYPT_FAILS = 3;
|
|
65630
|
+
/**
|
|
65631
|
+
* #1084: consecutive resyncs that found NOTHING to apply, per group.
|
|
65632
|
+
*
|
|
65633
|
+
* "No new commits" means the server holds no commit past our epoch — we are
|
|
65634
|
+
* NOT BEHIND. Treating that as corruption and re-keying is what drove the
|
|
65635
|
+
* epoch churn (see `_resyncOnDivergence`). But a state that is genuinely
|
|
65636
|
+
* broken while its epoch counter happens to match must still recover, so the
|
|
65637
|
+
* suppression is bounded rather than absolute.
|
|
65638
|
+
*/
|
|
65639
|
+
_resyncNoOpStrikes = /* @__PURE__ */ new Map();
|
|
65640
|
+
static MAX_RESYNC_NOOPS = 3;
|
|
65479
65641
|
/** Cached MLS KeyPackage bundle for this device (regenerated on each connect). */
|
|
65480
65642
|
_mlsKeyPackage = null;
|
|
65481
65643
|
/** Pending KeyPackage bundle from request-Welcome flow (used by _handleMlsWelcome). */
|
|
@@ -65513,6 +65675,13 @@ var init_channel = __esm2({
|
|
|
65513
65675
|
_kpPoolFilling = false;
|
|
65514
65676
|
/** Buffer for MLS commits received before Welcome (keyed by groupId, sorted by epoch). */
|
|
65515
65677
|
_pendingMlsCommits = /* @__PURE__ */ new Map();
|
|
65678
|
+
/**
|
|
65679
|
+
* #1013: the highest group epoch any inbound frame has reported, per MLS
|
|
65680
|
+
* group id. Every inbound frame carries the epoch the group was at when it
|
|
65681
|
+
* was produced, so each is a LOWER BOUND on the group's current epoch — the
|
|
65682
|
+
* hint is monotonic and only ever rises.
|
|
65683
|
+
*/
|
|
65684
|
+
_observedGroupEpochs = /* @__PURE__ */ new Map();
|
|
65516
65685
|
/** In-memory credential store for renter-provided credentials (never persisted). */
|
|
65517
65686
|
_credentialStore = new CredentialStore();
|
|
65518
65687
|
/** Rooms whose roster has been refreshed from the backend at least once in
|
|
@@ -65769,6 +65938,18 @@ var init_channel = __esm2({
|
|
|
65769
65938
|
get ownHubAddress() {
|
|
65770
65939
|
return this._persisted?.hubAddress ?? "";
|
|
65771
65940
|
}
|
|
65941
|
+
/** This agent's AgentVault hub_id. "" until enrolment persists it.
|
|
65942
|
+
*
|
|
65943
|
+
* Lives in the credential store, NOT in the channel config — which is where
|
|
65944
|
+
* the memory-recall helper was reading it from. `channel.config.hubId` is
|
|
65945
|
+
* unset for every agent enrolled through the normal flow, so
|
|
65946
|
+
* `channel.config.hubId || ""` produced `/api/v1/agents//memory/search`, a
|
|
65947
|
+
* 404 on every inbound message, swallowed by `if (!res.ok) return ""`.
|
|
65948
|
+
* Memory recall was silently dead while the hub_id sat right here.
|
|
65949
|
+
*/
|
|
65950
|
+
get ownHubId() {
|
|
65951
|
+
return this._persisted?.hubId ?? "";
|
|
65952
|
+
}
|
|
65772
65953
|
/** Public best-effort trigger to (re)resolve this agent's display_name.
|
|
65773
65954
|
* Called lazily from the 1:1 DM path when the name is still unknown, so a
|
|
65774
65955
|
* resolve that failed at connect (or a fresh agent that hadn't registered
|
|
@@ -66157,6 +66338,7 @@ var init_channel = __esm2({
|
|
|
66157
66338
|
* Each session gets the same plaintext encrypted independently.
|
|
66158
66339
|
*/
|
|
66159
66340
|
async send(plaintext, options) {
|
|
66341
|
+
if (isAbstention(plaintext)) return;
|
|
66160
66342
|
if (this._state === "error" || this._state === "idle") {
|
|
66161
66343
|
throw new Error("Channel is not ready");
|
|
66162
66344
|
}
|
|
@@ -66197,16 +66379,44 @@ var init_channel = __esm2({
|
|
|
66197
66379
|
const pendingWsSends = [];
|
|
66198
66380
|
const sentSharedGroupIds = /* @__PURE__ */ new Set();
|
|
66199
66381
|
const addressedMlsGroupIds = [];
|
|
66382
|
+
if (!(this._persisted?.mlsGroups && this._state === "ready" && this._ws)) {
|
|
66383
|
+
console.warn(
|
|
66384
|
+
`[SecureChannel] send(): shared-MLS block SKIPPED \u2014 mlsGroups=${this._persisted?.mlsGroups ? Object.keys(this._persisted.mlsGroups).length : "none"} state=${this._state} ws=${this._ws ? "open" : "null"}`
|
|
66385
|
+
);
|
|
66386
|
+
}
|
|
66200
66387
|
if (this._persisted?.mlsGroups && this._state === "ready" && this._ws) {
|
|
66201
66388
|
const targetSharedGid = targetConvId ? this._sessionGroupIds?.get(targetConvId) : void 0;
|
|
66389
|
+
console.log(
|
|
66390
|
+
`[SecureChannel] send(): considering ${Object.keys(this._persisted.mlsGroups).length} shared group(s); targetConvId=${targetConvId ? targetConvId.slice(0, 8) : "(none \u2014 broadcast)"} targetSharedGid=${targetSharedGid ? targetSharedGid.slice(0, 8) : "(none)"}`
|
|
66391
|
+
);
|
|
66392
|
+
const skipped = [];
|
|
66202
66393
|
for (const [gid, entry] of Object.entries(this._persisted.mlsGroups)) {
|
|
66203
|
-
if (targetConvId && gid !== targetSharedGid)
|
|
66204
|
-
|
|
66394
|
+
if (targetConvId && gid !== targetSharedGid) {
|
|
66395
|
+
skipped.push(`${gid.slice(0, 8)}:not-target`);
|
|
66396
|
+
continue;
|
|
66397
|
+
}
|
|
66398
|
+
if (!entry.mlsGroupId) {
|
|
66399
|
+
skipped.push(`${gid.slice(0, 8)}:no-mlsGroupId`);
|
|
66400
|
+
continue;
|
|
66401
|
+
}
|
|
66205
66402
|
const mlsGroup = this._mlsGroups.get(`1to1-group:${gid}`);
|
|
66206
|
-
if (!mlsGroup?.isInitialized
|
|
66403
|
+
if (!mlsGroup?.isInitialized) {
|
|
66404
|
+
skipped.push(`${gid.slice(0, 8)}:not-in-memory`);
|
|
66405
|
+
continue;
|
|
66406
|
+
}
|
|
66407
|
+
if (Number(mlsGroup.epoch) <= 0) {
|
|
66408
|
+
skipped.push(`${gid.slice(0, 8)}:epoch<=0`);
|
|
66409
|
+
continue;
|
|
66410
|
+
}
|
|
66207
66411
|
try {
|
|
66208
66412
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
66209
|
-
const cipherBytes = await
|
|
66413
|
+
const cipherBytes = await this._encryptWithCatchUp(
|
|
66414
|
+
mlsGroup,
|
|
66415
|
+
`1to1-group:${gid}`,
|
|
66416
|
+
entry.mlsGroupId,
|
|
66417
|
+
`1:1 group ${gid.slice(0, 8)}`,
|
|
66418
|
+
plaintextBytes
|
|
66419
|
+
);
|
|
66210
66420
|
await saveMlsState(this.config.dataDir, entry.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
66211
66421
|
const payload = {
|
|
66212
66422
|
group_id: entry.mlsGroupId,
|
|
@@ -66228,8 +66438,12 @@ var init_channel = __esm2({
|
|
|
66228
66438
|
console.log(`[SecureChannel] Shared MLS group send for group ${gid.slice(0, 8)} (${entry.mlsGroupId.slice(0, 8)})`);
|
|
66229
66439
|
} catch (err) {
|
|
66230
66440
|
console.error(`[SecureChannel] Shared MLS group send failed for ${gid.slice(0, 8)}:`, err);
|
|
66441
|
+
skipped.push(`${gid.slice(0, 8)}:encrypt-threw`);
|
|
66231
66442
|
}
|
|
66232
66443
|
}
|
|
66444
|
+
if (skipped.length > 0) {
|
|
66445
|
+
console.log(`[SecureChannel] send(): skipped ${skipped.length} group(s) \u2014 ${skipped.join(", ")}`);
|
|
66446
|
+
}
|
|
66233
66447
|
}
|
|
66234
66448
|
for (const [convId, session] of this._sessions) {
|
|
66235
66449
|
if (!session.activated) continue;
|
|
@@ -66249,7 +66463,14 @@ var init_channel = __esm2({
|
|
|
66249
66463
|
}
|
|
66250
66464
|
if (mlsGroup?.isInitialized && mlsGroupId && Number(mlsGroup.epoch) > 0 && this._state === "ready" && this._ws) {
|
|
66251
66465
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
66252
|
-
const
|
|
66466
|
+
const sessionGroupKey = convGroupId && this._mlsGroups.has(`1to1-group:${convGroupId}`) ? `1to1-group:${convGroupId}` : `conv:${convId}`;
|
|
66467
|
+
const cipherBytes = await this._encryptWithCatchUp(
|
|
66468
|
+
mlsGroup,
|
|
66469
|
+
sessionGroupKey,
|
|
66470
|
+
mlsGroupId,
|
|
66471
|
+
`conversation ${convId.slice(0, 8)}`,
|
|
66472
|
+
plaintextBytes
|
|
66473
|
+
);
|
|
66253
66474
|
await saveMlsState(this.config.dataDir, mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
66254
66475
|
const payload = {
|
|
66255
66476
|
conversation_id: convId,
|
|
@@ -66344,7 +66565,14 @@ var init_channel = __esm2({
|
|
|
66344
66565
|
}
|
|
66345
66566
|
try {
|
|
66346
66567
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
66347
|
-
const
|
|
66568
|
+
const mlsOnlyGroupKey = mlsOnlyConvGroupId && this._mlsGroups.has(`1to1-group:${mlsOnlyConvGroupId}`) ? `1to1-group:${mlsOnlyConvGroupId}` : `conv:${mlsConvId}`;
|
|
66569
|
+
const cipherBytes = await this._encryptWithCatchUp(
|
|
66570
|
+
mlsGroup,
|
|
66571
|
+
mlsOnlyGroupKey,
|
|
66572
|
+
resolvedMlsGroupId,
|
|
66573
|
+
`conversation ${mlsConvId.slice(0, 8)}`,
|
|
66574
|
+
plaintextBytes
|
|
66575
|
+
);
|
|
66348
66576
|
await saveMlsState(this.config.dataDir, resolvedMlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
66349
66577
|
const payload = {
|
|
66350
66578
|
conversation_id: mlsConvId,
|
|
@@ -66370,13 +66598,26 @@ var init_channel = __esm2({
|
|
|
66370
66598
|
}
|
|
66371
66599
|
}
|
|
66372
66600
|
}
|
|
66373
|
-
if (sentCount === 0
|
|
66374
|
-
console.warn(
|
|
66601
|
+
if (sentCount === 0) {
|
|
66602
|
+
console.warn(
|
|
66603
|
+
`[SecureChannel] send() delivered to 0 destinations \u2014 nothing was encrypted or queued (sessions=${this._sessions.size}, sharedGroups=${this._persisted?.mlsGroups ? Object.keys(this._persisted.mlsGroups).length : 0})`
|
|
66604
|
+
);
|
|
66375
66605
|
}
|
|
66376
66606
|
await this._persistState();
|
|
66607
|
+
const wsReady = this._ws?.readyState === 1;
|
|
66608
|
+
if (pendingWsSends.length > 0 && !wsReady) {
|
|
66609
|
+
console.warn(
|
|
66610
|
+
`[SecureChannel] send(): ${pendingWsSends.length} frame(s) NOT written \u2014 socket readyState=${this._ws?.readyState ?? "null"} (1=OPEN)`
|
|
66611
|
+
);
|
|
66612
|
+
}
|
|
66377
66613
|
for (const frame of pendingWsSends) {
|
|
66378
66614
|
this._ws.send(frame);
|
|
66379
66615
|
}
|
|
66616
|
+
if (pendingWsSends.length > 0) {
|
|
66617
|
+
console.log(
|
|
66618
|
+
`[SecureChannel] send(): wrote ${pendingWsSends.length} frame(s) to the socket (sentCount=${sentCount}, readyState=${this._ws?.readyState ?? "null"})`
|
|
66619
|
+
);
|
|
66620
|
+
}
|
|
66380
66621
|
if (!options?.isResend) {
|
|
66381
66622
|
for (const mlsGroupId of addressedMlsGroupIds) {
|
|
66382
66623
|
this._rememberRetryCandidate(mlsGroupId, plaintext, options);
|
|
@@ -66426,7 +66667,7 @@ var init_channel = __esm2({
|
|
|
66426
66667
|
*/
|
|
66427
66668
|
sendActivitySpan(spanData) {
|
|
66428
66669
|
if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
|
|
66429
|
-
const pluginVersion = true ? "0.23.
|
|
66670
|
+
const pluginVersion = true ? "0.23.29" : "0.0.0-dev";
|
|
66430
66671
|
const agentName = this.config.agentName ?? "Agent";
|
|
66431
66672
|
const resource = {
|
|
66432
66673
|
"service.name": "agentvault-agent",
|
|
@@ -66620,6 +66861,7 @@ var init_channel = __esm2({
|
|
|
66620
66861
|
* Uses MLS (preferred) or pairwise fan-out as fallback.
|
|
66621
66862
|
*/
|
|
66622
66863
|
async sendToRoom(roomId, plaintext, opts) {
|
|
66864
|
+
if (isAbstention(plaintext)) return;
|
|
66623
66865
|
if (!this._persisted?.rooms?.[roomId]) {
|
|
66624
66866
|
throw new Error(`Room ${roomId} not found`);
|
|
66625
66867
|
}
|
|
@@ -66636,7 +66878,13 @@ var init_channel = __esm2({
|
|
|
66636
66878
|
if (mlsGroup?.isInitialized) {
|
|
66637
66879
|
try {
|
|
66638
66880
|
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
66639
|
-
const ciphertext = await
|
|
66881
|
+
const ciphertext = await this._encryptWithCatchUp(
|
|
66882
|
+
mlsGroup,
|
|
66883
|
+
roomId,
|
|
66884
|
+
room.mlsGroupId,
|
|
66885
|
+
`room ${roomId.slice(0, 8)}`,
|
|
66886
|
+
plaintextBytes
|
|
66887
|
+
);
|
|
66640
66888
|
await saveMlsState(this.config.dataDir, room.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
66641
66889
|
if (this._state === "ready" && this._ws) {
|
|
66642
66890
|
this._ws.send(JSON.stringify({
|
|
@@ -66887,11 +67135,17 @@ var init_channel = __esm2({
|
|
|
66887
67135
|
const before = this._persisted.groupId;
|
|
66888
67136
|
this._sessionGroupIds = new Map(mine.map((r22) => [r22.id, r22.group_id]));
|
|
66889
67137
|
this._persisted.conversationGroupIds = Object.fromEntries(this._sessionGroupIds);
|
|
66890
|
-
const
|
|
67138
|
+
const joined = mine.find((r22) => this._persisted.mlsGroups?.[r22.group_id]);
|
|
67139
|
+
const primary = mine.find((r22) => r22.id === this._persisted.primaryConversationId) ?? joined ?? mine[0];
|
|
67140
|
+
if (!mine.some((r22) => r22.id === this._persisted.primaryConversationId)) {
|
|
67141
|
+
console.log(
|
|
67142
|
+
`[SecureChannel] Adopting new primary conversation ${primary.id.slice(0, 8)} (group ${primary.group_id.slice(0, 8)}) \u2014 ${joined ? "we hold MLS state for it" : "no joined conversation found, first active row"}`
|
|
67143
|
+
);
|
|
67144
|
+
}
|
|
66891
67145
|
this._persisted.groupId = primary.group_id;
|
|
66892
67146
|
this._persisted.primaryConversationId = primary.id;
|
|
66893
67147
|
const liveGroupIds = new Set(mine.map((r22) => r22.group_id));
|
|
66894
|
-
if (!liveGroupIds.has(staleGid)) {
|
|
67148
|
+
if (staleGid && staleMlsGroupId && !liveGroupIds.has(staleGid)) {
|
|
66895
67149
|
try {
|
|
66896
67150
|
await deleteMlsState(this.config.dataDir, staleMlsGroupId);
|
|
66897
67151
|
} catch {
|
|
@@ -66905,9 +67159,9 @@ var init_channel = __esm2({
|
|
|
66905
67159
|
}
|
|
66906
67160
|
await this._persistState();
|
|
66907
67161
|
console.log(
|
|
66908
|
-
`[SecureChannel] Re-resolved conversation groups after 'unknown group' ${staleMlsGroupId.slice(0, 8)}: primary group ${String(before).slice(0, 8)} \u2192 ${this._persisted.groupId.slice(0, 8)} (${mine.length} active conversation(s))`
|
|
67162
|
+
`[SecureChannel] Re-resolved conversation groups ${staleMlsGroupId ? `after 'unknown group' ${staleMlsGroupId.slice(0, 8)}` : "on connect (#1034)"}: primary group ${String(before).slice(0, 8)} \u2192 ${this._persisted.groupId.slice(0, 8)} (${mine.length} active conversation(s))`
|
|
66909
67163
|
);
|
|
66910
|
-
return this._persisted.groupId !== before && before === staleGid;
|
|
67164
|
+
return staleGid !== void 0 && this._persisted.groupId !== before && before === staleGid;
|
|
66911
67165
|
}
|
|
66912
67166
|
/**
|
|
66913
67167
|
* Resend the message that a now-reconciled `unknown group` refusal killed (#732).
|
|
@@ -67815,7 +68069,13 @@ var init_channel = __esm2({
|
|
|
67815
68069
|
const mlsGroup = this._mlsGroups.get(`a2a:${channelEntry.channelId}`);
|
|
67816
68070
|
if (mlsGroup?.isInitialized) {
|
|
67817
68071
|
const plaintextBytes = new TextEncoder().encode(text);
|
|
67818
|
-
const ciphertext = await
|
|
68072
|
+
const ciphertext = await this._encryptWithCatchUp(
|
|
68073
|
+
mlsGroup,
|
|
68074
|
+
`a2a:${channelEntry.channelId}`,
|
|
68075
|
+
channelEntry.mlsGroupId,
|
|
68076
|
+
`A2A ${channelEntry.channelId.slice(0, 8)}`,
|
|
68077
|
+
plaintextBytes
|
|
68078
|
+
);
|
|
67819
68079
|
await saveMlsState(
|
|
67820
68080
|
this.config.dataDir,
|
|
67821
68081
|
channelEntry.mlsGroupId,
|
|
@@ -67863,7 +68123,13 @@ var init_channel = __esm2({
|
|
|
67863
68123
|
const mlsGroup = this._mlsGroups.get(`a2a:${channelId}`);
|
|
67864
68124
|
if (mlsGroup?.isInitialized) {
|
|
67865
68125
|
const plaintextBytes = new TextEncoder().encode(text);
|
|
67866
|
-
const ciphertext = await
|
|
68126
|
+
const ciphertext = await this._encryptWithCatchUp(
|
|
68127
|
+
mlsGroup,
|
|
68128
|
+
`a2a:${channelId}`,
|
|
68129
|
+
entry.mlsGroupId,
|
|
68130
|
+
`A2A ${channelId.slice(0, 8)}`,
|
|
68131
|
+
plaintextBytes
|
|
68132
|
+
);
|
|
67867
68133
|
await saveMlsState(this.config.dataDir, entry.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
67868
68134
|
this._ws.send(JSON.stringify({
|
|
67869
68135
|
event: "a2a_message_mls",
|
|
@@ -68309,6 +68575,9 @@ var init_channel = __esm2({
|
|
|
68309
68575
|
await this._pullDrDeliveryQueue();
|
|
68310
68576
|
await this._flushOutboundQueue();
|
|
68311
68577
|
this._setState("ready");
|
|
68578
|
+
void this._reconcileConversationGroups().catch((err) => {
|
|
68579
|
+
console.warn("[SecureChannel] Conversation-group reconcile failed:", err);
|
|
68580
|
+
});
|
|
68312
68581
|
void this._reconcileRoomsWithServer().catch(
|
|
68313
68582
|
(err) => console.warn(`[SecureChannel] room reconcile failed (ignored): ${err instanceof Error ? err.message : String(err)}`)
|
|
68314
68583
|
).then(() => this._quarantineOrphanedMlsGroups()).catch(
|
|
@@ -68336,7 +68605,7 @@ var init_channel = __esm2({
|
|
|
68336
68605
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
68337
68606
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
68338
68607
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
68339
|
-
pluginVersion: true ? "0.23.
|
|
68608
|
+
pluginVersion: true ? "0.23.29" : "0.0.0-dev"
|
|
68340
68609
|
});
|
|
68341
68610
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
68342
68611
|
}
|
|
@@ -68660,7 +68929,7 @@ var init_channel = __esm2({
|
|
|
68660
68929
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
68661
68930
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
68662
68931
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
68663
|
-
pluginVersion: true ? "0.23.
|
|
68932
|
+
pluginVersion: true ? "0.23.29" : "0.0.0-dev"
|
|
68664
68933
|
});
|
|
68665
68934
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
68666
68935
|
}
|
|
@@ -69026,6 +69295,7 @@ var init_channel = __esm2({
|
|
|
69026
69295
|
const convGroupId = data.conversation_group_id;
|
|
69027
69296
|
const groupId = data.group_id;
|
|
69028
69297
|
const senderDeviceId = data.sender_device_id;
|
|
69298
|
+
this._noteGroupEpoch(groupId, data.epoch);
|
|
69029
69299
|
if (senderDeviceId === this._deviceId) return;
|
|
69030
69300
|
let mgr;
|
|
69031
69301
|
let mgrKey;
|
|
@@ -69103,9 +69373,15 @@ var init_channel = __esm2({
|
|
|
69103
69373
|
})),
|
|
69104
69374
|
no_history: filtered.length === 0
|
|
69105
69375
|
});
|
|
69106
|
-
if (mgr && mlsGroupId) {
|
|
69376
|
+
if (mgr && mgrKey && mlsGroupId) {
|
|
69107
69377
|
const responseBytes = new TextEncoder().encode(responsePayload);
|
|
69108
|
-
const cipher = await
|
|
69378
|
+
const cipher = await this._encryptWithCatchUp(
|
|
69379
|
+
mgr,
|
|
69380
|
+
mgrKey,
|
|
69381
|
+
mlsGroupId,
|
|
69382
|
+
`history catch-up ${mlsGroupId.slice(0, 8)}`,
|
|
69383
|
+
responseBytes
|
|
69384
|
+
);
|
|
69109
69385
|
await saveMlsState(this.config.dataDir, mlsGroupId, JSON.stringify(mgr.exportState()));
|
|
69110
69386
|
if (this._ws) {
|
|
69111
69387
|
this._ws.send(JSON.stringify({
|
|
@@ -69848,9 +70124,36 @@ ${messageText}`;
|
|
|
69848
70124
|
// ---------------------------------------------------------------------------
|
|
69849
70125
|
// MLS room message handlers
|
|
69850
70126
|
// ---------------------------------------------------------------------------
|
|
70127
|
+
/**
|
|
70128
|
+
* #1014: hand a room-framed frame to the handler that owns its group family.
|
|
70129
|
+
*
|
|
70130
|
+
* Returns true when the frame was routed. Routing means "give it to the right
|
|
70131
|
+
* handler" — NOT "decrypt it as a room message". Decrypting under the wrong
|
|
70132
|
+
* group is how MLS state gets corrupted, so an unplaceable group is left for
|
|
70133
|
+
* the caller to self-heal rather than guessed at.
|
|
70134
|
+
*/
|
|
70135
|
+
async _routeNonRoomGroupMessage(data, groupId) {
|
|
70136
|
+
const target = this._resolveGroupFamily(groupId);
|
|
70137
|
+
if (!target || target.family === "room") return false;
|
|
70138
|
+
console.log(
|
|
70139
|
+
`[SecureChannel] Room-framed message belongs to ${target.label} \u2014 routing to the handler that owns it (#1014)`
|
|
70140
|
+
);
|
|
70141
|
+
switch (target.family) {
|
|
70142
|
+
case "shared1to1":
|
|
70143
|
+
await this._handleMessageMLS({ ...data, conversation_group_id: target.id });
|
|
70144
|
+
return true;
|
|
70145
|
+
case "conversation":
|
|
70146
|
+
await this._handleMessageMLS({ ...data, conversation_id: target.id });
|
|
70147
|
+
return true;
|
|
70148
|
+
case "a2a":
|
|
70149
|
+
await this._handleA2AMessageMLS({ ...data, a2a_channel_id: target.id });
|
|
70150
|
+
return true;
|
|
70151
|
+
}
|
|
70152
|
+
}
|
|
69851
70153
|
async _handleRoomMessageMLS(data) {
|
|
69852
70154
|
const groupId = data.group_id;
|
|
69853
70155
|
const senderDeviceId = data.sender_device_id;
|
|
70156
|
+
this._noteGroupEpoch(groupId, data.epoch);
|
|
69854
70157
|
if (senderDeviceId === this._deviceId) return;
|
|
69855
70158
|
let roomId;
|
|
69856
70159
|
for (const [rid, room] of Object.entries(this._persisted?.rooms ?? {})) {
|
|
@@ -69864,7 +70167,9 @@ ${messageText}`;
|
|
|
69864
70167
|
roomId = data.room_id;
|
|
69865
70168
|
console.log(`[SecureChannel] Room ${roomId.slice(0, 8)} matched by room_id (group_id ${groupId?.slice(0, 8)} mismatch)`);
|
|
69866
70169
|
} else {
|
|
70170
|
+
if (await this._routeNonRoomGroupMessage(data, groupId)) return;
|
|
69867
70171
|
console.warn(`[SecureChannel] No room found for MLS group ${groupId?.slice(0, 8)}`);
|
|
70172
|
+
if (groupId) void this._requestWelcomeSelfHeal(groupId);
|
|
69868
70173
|
return;
|
|
69869
70174
|
}
|
|
69870
70175
|
}
|
|
@@ -70052,63 +70357,63 @@ ${messageText}`;
|
|
|
70052
70357
|
console.warn(`[SecureChannel] Failed to send history catchup response:`, sendErr);
|
|
70053
70358
|
}
|
|
70054
70359
|
}
|
|
70360
|
+
/**
|
|
70361
|
+
* Apply an inbound MLS commit to whichever group it addresses.
|
|
70362
|
+
*
|
|
70363
|
+
* Returns TRUE when the commit was applied, buffered, or handed to the
|
|
70364
|
+
* failure ladder — i.e. when this agent has taken responsibility for it and
|
|
70365
|
+
* the delivery-queue row may safely be acked. Returns FALSE when the commit
|
|
70366
|
+
* names a group we hold no state for at all, so the caller must NACK and let
|
|
70367
|
+
* the row survive.
|
|
70368
|
+
*
|
|
70369
|
+
* #1012: this used to loop `rooms` then `a2aChannels` and then END. A shared
|
|
70370
|
+
* 1:1 commit (`mlsGroups`) and a legacy per-conversation commit
|
|
70371
|
+
* (`mlsConversations`) matched neither. The call fell off the bottom with no
|
|
70372
|
+
* log, no buffer and no error — and because it did not THROW,
|
|
70373
|
+
* `_pullDeliveryQueue` acked the row and the server deleted it. An MLS commit
|
|
70374
|
+
* cannot be regenerated, so the agent stayed at epoch N while the group moved
|
|
70375
|
+
* to N+1 and every later send() encrypted at an epoch nobody could decrypt.
|
|
70376
|
+
*
|
|
70377
|
+
* The families are enumerated in ONE list resolved by ONE loop, because two
|
|
70378
|
+
* hand-copied loops are exactly how the 1:1 path fell two families behind.
|
|
70379
|
+
*/
|
|
70055
70380
|
async _handleMlsCommit(data) {
|
|
70056
70381
|
const groupId = data.group_id;
|
|
70057
70382
|
const epoch = typeof data.epoch === "number" ? data.epoch : 0;
|
|
70058
|
-
|
|
70059
|
-
|
|
70060
|
-
|
|
70061
|
-
|
|
70062
|
-
|
|
70063
|
-
|
|
70064
|
-
|
|
70065
|
-
|
|
70066
|
-
|
|
70067
|
-
this._mlsCommitFailCounts.delete(roomId);
|
|
70068
|
-
} catch (err) {
|
|
70069
|
-
await this._onCommitFailure(
|
|
70070
|
-
roomId,
|
|
70071
|
-
groupId,
|
|
70072
|
-
err,
|
|
70073
|
-
`room ${roomId.slice(0, 8)}`,
|
|
70074
|
-
data.epoch,
|
|
70075
|
-
mlsGroup.epoch
|
|
70076
|
-
);
|
|
70077
|
-
}
|
|
70078
|
-
} else {
|
|
70079
|
-
this._bufferMlsCommit(groupId, epoch, data);
|
|
70080
|
-
console.log(`[SecureChannel] Buffered MLS commit for room ${roomId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
|
|
70081
|
-
}
|
|
70082
|
-
return;
|
|
70383
|
+
this._noteGroupEpoch(groupId, data.epoch);
|
|
70384
|
+
const target = this._resolveGroupFamily(groupId);
|
|
70385
|
+
if (target) {
|
|
70386
|
+
const { managerKey: groupKey, label } = target;
|
|
70387
|
+
const mlsGroup = this._mlsGroups.get(groupKey);
|
|
70388
|
+
if (!mlsGroup?.isInitialized) {
|
|
70389
|
+
this._bufferMlsCommit(groupId, epoch, data);
|
|
70390
|
+
console.log(`[SecureChannel] Buffered MLS commit for ${label} (epoch=${epoch}, group not initialized)`);
|
|
70391
|
+
return true;
|
|
70083
70392
|
}
|
|
70084
|
-
|
|
70085
|
-
|
|
70086
|
-
|
|
70087
|
-
|
|
70088
|
-
|
|
70089
|
-
|
|
70090
|
-
|
|
70091
|
-
|
|
70092
|
-
|
|
70093
|
-
|
|
70094
|
-
|
|
70095
|
-
|
|
70096
|
-
|
|
70097
|
-
|
|
70098
|
-
|
|
70099
|
-
err,
|
|
70100
|
-
`A2A ${chId.slice(0, 8)}`,
|
|
70101
|
-
data.epoch,
|
|
70102
|
-
mlsGroup.epoch
|
|
70103
|
-
);
|
|
70104
|
-
}
|
|
70105
|
-
} else {
|
|
70106
|
-
this._bufferMlsCommit(groupId, epoch, data);
|
|
70107
|
-
console.log(`[SecureChannel] Buffered MLS commit for A2A ${chId.slice(0, 8)} (epoch=${epoch}, group not initialized)`);
|
|
70108
|
-
}
|
|
70109
|
-
return;
|
|
70393
|
+
try {
|
|
70394
|
+
const commitBytes = new Uint8Array(Buffer.from(data.payload, "hex"));
|
|
70395
|
+
await mlsGroup.processCommit(commitBytes);
|
|
70396
|
+
await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mlsGroup.exportState()));
|
|
70397
|
+
console.log(`[SecureChannel] MLS commit processed for ${label} (epoch=${mlsGroup.epoch})`);
|
|
70398
|
+
this._mlsCommitFailCounts.delete(groupKey);
|
|
70399
|
+
} catch (err) {
|
|
70400
|
+
await this._onCommitFailure(
|
|
70401
|
+
groupKey,
|
|
70402
|
+
groupId,
|
|
70403
|
+
err,
|
|
70404
|
+
label,
|
|
70405
|
+
data.epoch,
|
|
70406
|
+
mlsGroup.epoch
|
|
70407
|
+
);
|
|
70110
70408
|
}
|
|
70409
|
+
return true;
|
|
70111
70410
|
}
|
|
70411
|
+
this._bufferMlsCommit(groupId, epoch, data);
|
|
70412
|
+
console.warn(
|
|
70413
|
+
`[SecureChannel] MLS commit for UNKNOWN group ${String(groupId).slice(0, 8)} (epoch=${epoch}) \u2014 buffered, requesting a Welcome, NOT acked`
|
|
70414
|
+
);
|
|
70415
|
+
if (groupId) void this._requestWelcomeSelfHeal(groupId);
|
|
70416
|
+
return false;
|
|
70112
70417
|
}
|
|
70113
70418
|
/** Buffer an MLS commit for replay after Welcome join. Max 50 per group. */
|
|
70114
70419
|
_bufferMlsCommit(groupId, epoch, data) {
|
|
@@ -70298,6 +70603,229 @@ ${messageText}`;
|
|
|
70298
70603
|
* `1to1-group:<gid>`, `a2a:<channelId>`, …) — also the decrypt-fail-count key.
|
|
70299
70604
|
* @param groupId the server-side MLS group id used in the /sync URL.
|
|
70300
70605
|
*/
|
|
70606
|
+
/**
|
|
70607
|
+
* #1015 — the ONLY place this package fulfils a Welcome request.
|
|
70608
|
+
*
|
|
70609
|
+
* Add the members, transmit, and record `fulfilled` **only for the targets
|
|
70610
|
+
* whose frames actually went out**. Every one of the five sites that used to
|
|
70611
|
+
* do this by hand followed the same sequence and had the same three holes:
|
|
70612
|
+
* `this._ws` null (both sends skipped by an `if`), a CLOSING socket (`send`
|
|
70613
|
+
* returns without throwing), and a fulfil POST wrapped in `.catch(() => {})`.
|
|
70614
|
+
*
|
|
70615
|
+
* A fulfilled request is never re-requested. So a Welcome that evaporated
|
|
70616
|
+
* left the requester locked out, the fulfiller at epoch N+1, everyone else at
|
|
70617
|
+
* N, and the bridging commit untransmitted and impossible to regenerate — a
|
|
70618
|
+
* permanently wedged group in which every signal read success.
|
|
70619
|
+
*
|
|
70620
|
+
* Returns the number of requests actually fulfilled.
|
|
70621
|
+
*/
|
|
70622
|
+
async _fulfilWelcomeRequests(opts) {
|
|
70623
|
+
const { mlsGroup, mlsGroupId, label, welcomeFields, targets } = opts;
|
|
70624
|
+
if (targets.length === 0) return 0;
|
|
70625
|
+
if (!this._wsIsOpen()) {
|
|
70626
|
+
console.warn(
|
|
70627
|
+
`[SecureChannel] ${label}: socket is not OPEN \u2014 NOT advancing the group; ${targets.length} request(s) stay pending for the next pull`
|
|
70628
|
+
);
|
|
70629
|
+
return 0;
|
|
70630
|
+
}
|
|
70631
|
+
const snapshot = mlsGroup.exportState();
|
|
70632
|
+
const { commit, welcome } = await mlsGroup.addMembers(targets.map((t22) => t22.keyPackage));
|
|
70633
|
+
const commitSent = await this._sendFrameConfirmed(
|
|
70634
|
+
{
|
|
70635
|
+
event: "mls_commit",
|
|
70636
|
+
data: {
|
|
70637
|
+
group_id: mlsGroupId,
|
|
70638
|
+
epoch: Number(mlsGroup.epoch),
|
|
70639
|
+
payload: Buffer.from(commit).toString("hex")
|
|
70640
|
+
}
|
|
70641
|
+
},
|
|
70642
|
+
`${label} commit`
|
|
70643
|
+
);
|
|
70644
|
+
if (!commitSent) {
|
|
70645
|
+
mlsGroup.importState(snapshot);
|
|
70646
|
+
console.warn(
|
|
70647
|
+
`[SecureChannel] ${label}: commit was NOT written \u2014 rolled back to epoch ${Number(mlsGroup.epoch)}, nothing fulfilled, requests stay pending`
|
|
70648
|
+
);
|
|
70649
|
+
return 0;
|
|
70650
|
+
}
|
|
70651
|
+
await saveMlsState(this.config.dataDir, mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
70652
|
+
if (!welcome) {
|
|
70653
|
+
console.warn(
|
|
70654
|
+
`[SecureChannel] ${label}: addMembers produced NO Welcome \u2014 ${targets.length} request(s) left pending rather than marked fulfilled`
|
|
70655
|
+
);
|
|
70656
|
+
return 0;
|
|
70657
|
+
}
|
|
70658
|
+
let fulfilled = 0;
|
|
70659
|
+
for (const t22 of targets) {
|
|
70660
|
+
const sent = await this._sendFrameConfirmed(
|
|
70661
|
+
{
|
|
70662
|
+
event: "mls_welcome",
|
|
70663
|
+
data: {
|
|
70664
|
+
target_device_id: t22.deviceId,
|
|
70665
|
+
group_id: mlsGroupId,
|
|
70666
|
+
...welcomeFields,
|
|
70667
|
+
payload: Buffer.from(welcome).toString("hex")
|
|
70668
|
+
}
|
|
70669
|
+
},
|
|
70670
|
+
`${label} welcome\u2192${t22.deviceId.slice(0, 8)}`
|
|
70671
|
+
);
|
|
70672
|
+
if (!sent) {
|
|
70673
|
+
console.warn(
|
|
70674
|
+
`[SecureChannel] ${label}: Welcome for ${t22.deviceId.slice(0, 8)} was NOT written \u2014 leaving the request PENDING so the next pull retries it`
|
|
70675
|
+
);
|
|
70676
|
+
continue;
|
|
70677
|
+
}
|
|
70678
|
+
if (await this._postFulfilWelcome(mlsGroupId, t22.requestId, `${label} ${t22.deviceId.slice(0, 8)}`)) {
|
|
70679
|
+
fulfilled++;
|
|
70680
|
+
}
|
|
70681
|
+
}
|
|
70682
|
+
return fulfilled;
|
|
70683
|
+
}
|
|
70684
|
+
/**
|
|
70685
|
+
* Resolve an MLS group id to the family that owns it.
|
|
70686
|
+
*
|
|
70687
|
+
* There are four: rooms, A2A channels, shared 1:1 groups, and the legacy
|
|
70688
|
+
* per-conversation groups. They all store the MLS group id under
|
|
70689
|
+
* `mlsGroupId`; they differ only in the key their live `MLSGroupManager` sits
|
|
70690
|
+
* under in `_mlsGroups`.
|
|
70691
|
+
*
|
|
70692
|
+
* ONE resolver, because hand-copying this enumeration is what produced
|
|
70693
|
+
* #1012 (`_handleMlsCommit` handled two families of four), #1014 (a
|
|
70694
|
+
* room-framed 1:1 replay matched nothing) and #1016 (a rejoin dropped only
|
|
70695
|
+
* the room manager). Three bugs, one missing abstraction.
|
|
70696
|
+
*/
|
|
70697
|
+
_resolveGroupFamily(mlsGroupId) {
|
|
70698
|
+
if (!mlsGroupId) return null;
|
|
70699
|
+
const families = [
|
|
70700
|
+
{ family: "room", entries: this._persisted?.rooms, managerKey: (id) => id, label: "room" },
|
|
70701
|
+
{ family: "a2a", entries: this._persisted?.a2aChannels, managerKey: (id) => `a2a:${id}`, label: "A2A" },
|
|
70702
|
+
{ family: "shared1to1", entries: this._persisted?.mlsGroups, managerKey: (id) => `1to1-group:${id}`, label: "1:1 group" },
|
|
70703
|
+
{ family: "conversation", entries: this._persisted?.mlsConversations, managerKey: (id) => `conv:${id}`, label: "conversation" }
|
|
70704
|
+
];
|
|
70705
|
+
for (const f22 of families) {
|
|
70706
|
+
for (const [id, entry] of Object.entries(f22.entries ?? {})) {
|
|
70707
|
+
if (entry?.mlsGroupId !== mlsGroupId) continue;
|
|
70708
|
+
return {
|
|
70709
|
+
family: f22.family,
|
|
70710
|
+
id,
|
|
70711
|
+
managerKey: f22.managerKey(id),
|
|
70712
|
+
label: `${f22.label} ${id.slice(0, 8)}`
|
|
70713
|
+
};
|
|
70714
|
+
}
|
|
70715
|
+
}
|
|
70716
|
+
return null;
|
|
70717
|
+
}
|
|
70718
|
+
/** #1015: is the socket in a state that can actually take a write? */
|
|
70719
|
+
_wsIsOpen() {
|
|
70720
|
+
return this._ws?.readyState === 1;
|
|
70721
|
+
}
|
|
70722
|
+
/**
|
|
70723
|
+
* #1015: write one frame and report whether the socket took it.
|
|
70724
|
+
*
|
|
70725
|
+
* `ws.send()` on a CLOSING or CLOSED socket returns WITHOUT throwing — the
|
|
70726
|
+
* error surfaces only through the callback, and every fulfilment site passed
|
|
70727
|
+
* none. So a Welcome could be "sent" into a dead socket and the requester
|
|
70728
|
+
* still marked `fulfilled`, which it never re-requests.
|
|
70729
|
+
*/
|
|
70730
|
+
_sendFrameConfirmed(frame, label) {
|
|
70731
|
+
const ws = this._ws;
|
|
70732
|
+
if (!ws || ws.readyState !== 1) {
|
|
70733
|
+
console.warn(
|
|
70734
|
+
`[SecureChannel] ${label}: socket is not OPEN (readyState=${ws?.readyState ?? "null"}) \u2014 frame NOT written`
|
|
70735
|
+
);
|
|
70736
|
+
return Promise.resolve(false);
|
|
70737
|
+
}
|
|
70738
|
+
return new Promise((resolve32) => {
|
|
70739
|
+
try {
|
|
70740
|
+
ws.send(JSON.stringify(frame), (err) => {
|
|
70741
|
+
if (err) {
|
|
70742
|
+
console.error(`[SecureChannel] ${label}: socket refused the frame:`, err);
|
|
70743
|
+
resolve32(false);
|
|
70744
|
+
} else {
|
|
70745
|
+
resolve32(true);
|
|
70746
|
+
}
|
|
70747
|
+
});
|
|
70748
|
+
} catch (err) {
|
|
70749
|
+
console.error(`[SecureChannel] ${label}: send threw:`, err);
|
|
70750
|
+
resolve32(false);
|
|
70751
|
+
}
|
|
70752
|
+
});
|
|
70753
|
+
}
|
|
70754
|
+
/**
|
|
70755
|
+
* #1015: record a Welcome as fulfilled — and say so when it fails.
|
|
70756
|
+
*
|
|
70757
|
+
* The batched shared-1:1 site posted this with `.catch(() => {})`, so an HTTP
|
|
70758
|
+
* failure here was invisible on top of the sends being invisible.
|
|
70759
|
+
*/
|
|
70760
|
+
async _postFulfilWelcome(mlsGroupId, requestId, label) {
|
|
70761
|
+
try {
|
|
70762
|
+
const res = await fetch(
|
|
70763
|
+
`${this.config.apiUrl}/api/v1/mls/groups/${mlsGroupId}/fulfill-welcome`,
|
|
70764
|
+
{
|
|
70765
|
+
method: "POST",
|
|
70766
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
|
|
70767
|
+
body: JSON.stringify({ request_id: requestId })
|
|
70768
|
+
}
|
|
70769
|
+
);
|
|
70770
|
+
if (!res.ok) {
|
|
70771
|
+
console.warn(`[SecureChannel] ${label}: fulfill-welcome returned ${res.status}`);
|
|
70772
|
+
return false;
|
|
70773
|
+
}
|
|
70774
|
+
return true;
|
|
70775
|
+
} catch (err) {
|
|
70776
|
+
console.warn(`[SecureChannel] ${label}: fulfill-welcome failed:`, err);
|
|
70777
|
+
return false;
|
|
70778
|
+
}
|
|
70779
|
+
}
|
|
70780
|
+
/**
|
|
70781
|
+
* #1013: fold an epoch seen on an inbound frame into the running hint.
|
|
70782
|
+
*
|
|
70783
|
+
* Anything unusable (missing, non-numeric, negative, fractional) is ignored
|
|
70784
|
+
* rather than defaulted. A defaulted epoch here would be a guess, and a guess
|
|
70785
|
+
* that reads LOW silently disables the check it exists to drive.
|
|
70786
|
+
*/
|
|
70787
|
+
_noteGroupEpoch(mlsGroupId, epoch) {
|
|
70788
|
+
if (typeof mlsGroupId !== "string" || !mlsGroupId) return;
|
|
70789
|
+
const current = this._observedGroupEpochs.get(mlsGroupId) ?? 0;
|
|
70790
|
+
const next = noteObservedEpoch(current, epoch);
|
|
70791
|
+
if (next > current) this._observedGroupEpochs.set(mlsGroupId, next);
|
|
70792
|
+
}
|
|
70793
|
+
/**
|
|
70794
|
+
* #1013 — the ONLY place this package encrypts an MLS application message.
|
|
70795
|
+
*
|
|
70796
|
+
* Catch up first when the group is known to have moved past us, then encrypt.
|
|
70797
|
+
* Hermes has done this since #440 and the browser since #996; the plugin —
|
|
70798
|
+
* the implementation that actually ships to customers — did not, so an agent
|
|
70799
|
+
* that missed one commit encrypted every later message at an epoch nobody
|
|
70800
|
+
* could read, while the server stored and relayed it and every signal read
|
|
70801
|
+
* 200.
|
|
70802
|
+
*
|
|
70803
|
+
* Funnelling every sender through one method is the point. A guard applied at
|
|
70804
|
+
* seven call sites decays to a guard applied at six: that is exactly how
|
|
70805
|
+
* `_handleMlsCommit` ended up handling two group families out of four (#1012).
|
|
70806
|
+
*
|
|
70807
|
+
* Best-effort by design. A catch-up that cannot replay — or that throws — must
|
|
70808
|
+
* NOT block the send; we fall through and encrypt at the local epoch, which is
|
|
70809
|
+
* precisely today's behaviour, so the worst case is no regression.
|
|
70810
|
+
*/
|
|
70811
|
+
async _encryptWithCatchUp(mlsGroup, groupKey, mlsGroupId, label, plaintextBytes) {
|
|
70812
|
+
const observed = this._observedGroupEpochs.get(mlsGroupId);
|
|
70813
|
+
if (observed !== void 0 && needsPreSendResync(Number(mlsGroup.epoch), observed)) {
|
|
70814
|
+
console.warn(
|
|
70815
|
+
`[SecureChannel] ${label}: local epoch ${Number(mlsGroup.epoch)} is behind the observed group epoch ${observed} \u2014 catching up before encrypt (#1013)`
|
|
70816
|
+
);
|
|
70817
|
+
try {
|
|
70818
|
+
if (!await this._resyncOnDivergence(groupKey, mlsGroupId)) {
|
|
70819
|
+
console.warn(
|
|
70820
|
+
`[SecureChannel] ${label}: catch-up could not replay \u2014 sending at epoch ${Number(mlsGroup.epoch)}. Peers past this epoch will not read it.`
|
|
70821
|
+
);
|
|
70822
|
+
}
|
|
70823
|
+
} catch (err) {
|
|
70824
|
+
console.warn(`[SecureChannel] ${label}: catch-up failed:`, err);
|
|
70825
|
+
}
|
|
70826
|
+
}
|
|
70827
|
+
return await mlsGroup.encrypt(plaintextBytes);
|
|
70828
|
+
}
|
|
70301
70829
|
async _resyncOnDivergence(groupKey, groupId) {
|
|
70302
70830
|
const mgr = this._mlsGroups.get(groupKey);
|
|
70303
70831
|
if (!mgr?.isInitialized) return false;
|
|
@@ -70324,11 +70852,24 @@ ${messageText}`;
|
|
|
70324
70852
|
applied++;
|
|
70325
70853
|
}
|
|
70326
70854
|
if (applied === 0) {
|
|
70327
|
-
|
|
70855
|
+
const strikes = (this._resyncNoOpStrikes.get(groupKey) ?? 0) + 1;
|
|
70856
|
+
this._resyncNoOpStrikes.set(groupKey, strikes);
|
|
70857
|
+
if (strikes < _SecureChannel.MAX_RESYNC_NOOPS) {
|
|
70858
|
+
this._mlsDecryptFailCounts.delete(groupKey);
|
|
70859
|
+
console.log(
|
|
70860
|
+
`[SecureChannel] Resync for ${groupId.slice(0, 8)}: already current at epoch=${mgr.epoch} (strike ${strikes}/${_SecureChannel.MAX_RESYNC_NOOPS}) \u2014 NOT re-keying (#1084)`
|
|
70861
|
+
);
|
|
70862
|
+
return true;
|
|
70863
|
+
}
|
|
70864
|
+
this._resyncNoOpStrikes.delete(groupKey);
|
|
70865
|
+
console.warn(
|
|
70866
|
+
`[SecureChannel] Resync for ${groupId.slice(0, 8)}: still failing at epoch=${mgr.epoch} after ${_SecureChannel.MAX_RESYNC_NOOPS} no-op resyncs \u2014 escalating to re-key`
|
|
70867
|
+
);
|
|
70328
70868
|
return false;
|
|
70329
70869
|
}
|
|
70330
70870
|
await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mgr.exportState()));
|
|
70331
70871
|
this._mlsDecryptFailCounts.delete(groupKey);
|
|
70872
|
+
this._resyncNoOpStrikes.delete(groupKey);
|
|
70332
70873
|
console.log(
|
|
70333
70874
|
`[SecureChannel] Resync-on-divergence: applied ${applied} missed commit(s) for ${groupId.slice(0, 8)}, now epoch=${mgr.epoch}`
|
|
70334
70875
|
);
|
|
@@ -70715,6 +71256,7 @@ ${messageText}`;
|
|
|
70715
71256
|
const ackedIds = [];
|
|
70716
71257
|
const nackedIds = [];
|
|
70717
71258
|
for (const msg of messages) {
|
|
71259
|
+
let handled = true;
|
|
70718
71260
|
try {
|
|
70719
71261
|
if (msg.sender_device_id === this._deviceId) {
|
|
70720
71262
|
ackedIds.push(msg.queue_id);
|
|
@@ -70731,7 +71273,7 @@ ${messageText}`;
|
|
|
70731
71273
|
a2a_channel_id: msg.a2a_channel_id
|
|
70732
71274
|
});
|
|
70733
71275
|
} else if (msg.message_type === "commit") {
|
|
70734
|
-
await this._handleMlsCommit({
|
|
71276
|
+
handled = await this._handleMlsCommit({
|
|
70735
71277
|
group_id: msg.group_id,
|
|
70736
71278
|
sender_device_id: msg.sender_device_id,
|
|
70737
71279
|
epoch: msg.epoch,
|
|
@@ -70769,9 +71311,18 @@ ${messageText}`;
|
|
|
70769
71311
|
conversation_group_id: msg.conversation_group_id,
|
|
70770
71312
|
created_at: msg.created_at
|
|
70771
71313
|
});
|
|
71314
|
+
} else {
|
|
71315
|
+
handled = false;
|
|
71316
|
+
console.warn(
|
|
71317
|
+
`[SecureChannel] Delivery application row ${String(msg.message_id).slice(0, 8)} names no room, A2A channel or conversation \u2014 NOT acked`
|
|
71318
|
+
);
|
|
70772
71319
|
}
|
|
70773
71320
|
}
|
|
70774
|
-
|
|
71321
|
+
if (handled) {
|
|
71322
|
+
ackedIds.push(msg.queue_id);
|
|
71323
|
+
} else {
|
|
71324
|
+
nackedIds.push(msg.queue_id);
|
|
71325
|
+
}
|
|
70775
71326
|
} catch (err) {
|
|
70776
71327
|
console.warn(`[SecureChannel] Delivery ${msg.message_type} processing failed:`, err);
|
|
70777
71328
|
nackedIds.push(msg.queue_id);
|
|
@@ -70840,38 +71391,20 @@ ${messageText}`;
|
|
|
70840
71391
|
if (!kpHex) continue;
|
|
70841
71392
|
const kpBytes = new Uint8Array(Buffer.from(kpHex, "hex"));
|
|
70842
71393
|
const memberKp = MLSGroupManager.deserializeKeyPackage(kpBytes);
|
|
70843
|
-
const
|
|
70844
|
-
|
|
70845
|
-
|
|
70846
|
-
|
|
70847
|
-
|
|
70848
|
-
|
|
70849
|
-
|
|
70850
|
-
|
|
70851
|
-
|
|
70852
|
-
}
|
|
70853
|
-
}
|
|
70854
|
-
if (
|
|
70855
|
-
|
|
70856
|
-
event: "mls_welcome",
|
|
70857
|
-
data: {
|
|
70858
|
-
target_device_id: req.requesting_device_id,
|
|
70859
|
-
group_id: roomState.mlsGroupId,
|
|
70860
|
-
room_id: roomId,
|
|
70861
|
-
payload: Buffer.from(welcome).toString("hex")
|
|
70862
|
-
}
|
|
70863
|
-
}));
|
|
71394
|
+
const done = await this._fulfilWelcomeRequests({
|
|
71395
|
+
mlsGroup,
|
|
71396
|
+
mlsGroupId: roomState.mlsGroupId,
|
|
71397
|
+
label: `room ${roomId.slice(0, 8)}`,
|
|
71398
|
+
welcomeFields: { room_id: roomId },
|
|
71399
|
+
targets: [{
|
|
71400
|
+
requestId: req.id,
|
|
71401
|
+
deviceId: req.requesting_device_id,
|
|
71402
|
+
keyPackage: memberKp
|
|
71403
|
+
}]
|
|
71404
|
+
});
|
|
71405
|
+
if (done > 0) {
|
|
71406
|
+
console.log(`[SecureChannel] Fulfilled Welcome for ${req.requesting_device_id.slice(0, 8)} in room ${roomId.slice(0, 8)}`);
|
|
70864
71407
|
}
|
|
70865
|
-
await fetch(
|
|
70866
|
-
`${this.config.apiUrl}/api/v1/mls/groups/${roomState.mlsGroupId}/fulfill-welcome`,
|
|
70867
|
-
{
|
|
70868
|
-
method: "POST",
|
|
70869
|
-
headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
|
|
70870
|
-
body: JSON.stringify({ request_id: req.id })
|
|
70871
|
-
}
|
|
70872
|
-
);
|
|
70873
|
-
await saveMlsState(this.config.dataDir, roomState.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
70874
|
-
console.log(`[SecureChannel] Fulfilled Welcome for ${req.requesting_device_id.slice(0, 8)} in room ${roomId.slice(0, 8)}`);
|
|
70875
71408
|
} catch (fulfillErr) {
|
|
70876
71409
|
console.warn(`[SecureChannel] Welcome fulfill failed for ${req.requesting_device_id.slice(0, 8)}:`, fulfillErr);
|
|
70877
71410
|
}
|
|
@@ -70910,65 +71443,38 @@ ${messageText}`;
|
|
|
70910
71443
|
validReqs.push({ req, kp: MLSGroupManager.deserializeKeyPackage(kpBytes) });
|
|
70911
71444
|
}
|
|
70912
71445
|
if (validReqs.length === 0) continue;
|
|
71446
|
+
const sharedTargets = validReqs.map((v22) => ({
|
|
71447
|
+
requestId: v22.req.id,
|
|
71448
|
+
deviceId: v22.req.requesting_device_id,
|
|
71449
|
+
keyPackage: v22.kp
|
|
71450
|
+
}));
|
|
70913
71451
|
try {
|
|
70914
|
-
const
|
|
70915
|
-
|
|
70916
|
-
|
|
70917
|
-
|
|
70918
|
-
|
|
70919
|
-
|
|
70920
|
-
|
|
70921
|
-
|
|
70922
|
-
|
|
70923
|
-
}));
|
|
70924
|
-
}
|
|
70925
|
-
if (welcome && this._ws) {
|
|
70926
|
-
for (const { req } of validReqs) {
|
|
70927
|
-
this._ws.send(JSON.stringify({
|
|
70928
|
-
event: "mls_welcome",
|
|
70929
|
-
data: {
|
|
70930
|
-
target_device_id: req.requesting_device_id,
|
|
70931
|
-
group_id: entry.mlsGroupId,
|
|
70932
|
-
conversation_group_id: gid,
|
|
70933
|
-
payload: Buffer.from(welcome).toString("hex")
|
|
70934
|
-
}
|
|
70935
|
-
}));
|
|
70936
|
-
}
|
|
70937
|
-
}
|
|
70938
|
-
for (const { req } of validReqs) {
|
|
70939
|
-
await fetch(
|
|
70940
|
-
`${this.config.apiUrl}/api/v1/mls/groups/${entry.mlsGroupId}/fulfill-welcome`,
|
|
70941
|
-
{
|
|
70942
|
-
method: "POST",
|
|
70943
|
-
headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
|
|
70944
|
-
body: JSON.stringify({ request_id: req.id })
|
|
70945
|
-
}
|
|
70946
|
-
).catch(() => {
|
|
70947
|
-
});
|
|
71452
|
+
const done = await this._fulfilWelcomeRequests({
|
|
71453
|
+
mlsGroup,
|
|
71454
|
+
mlsGroupId: entry.mlsGroupId,
|
|
71455
|
+
label: `shared group ${gid.slice(0, 8)}`,
|
|
71456
|
+
welcomeFields: { conversation_group_id: gid },
|
|
71457
|
+
targets: sharedTargets
|
|
71458
|
+
});
|
|
71459
|
+
if (done > 0) {
|
|
71460
|
+
console.log(`[SecureChannel] Batched Welcome for ${done}/${validReqs.length} devices in shared group ${gid.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
|
|
70948
71461
|
}
|
|
70949
|
-
await saveMlsState(this.config.dataDir, entry.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
70950
|
-
console.log(`[SecureChannel] Batched Welcome for ${validReqs.length} devices in shared group ${gid.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
|
|
70951
71462
|
} catch (batchErr) {
|
|
70952
71463
|
console.warn(`[SecureChannel] Batched Welcome failed for shared group ${gid.slice(0, 8)}, falling back to individual:`, batchErr);
|
|
70953
|
-
for (const
|
|
71464
|
+
for (const target of sharedTargets) {
|
|
70954
71465
|
try {
|
|
70955
|
-
const
|
|
70956
|
-
|
|
70957
|
-
|
|
70958
|
-
|
|
70959
|
-
|
|
70960
|
-
|
|
70961
|
-
}
|
|
70962
|
-
await fetch(`${this.config.apiUrl}/api/v1/mls/groups/${entry.mlsGroupId}/fulfill-welcome`, {
|
|
70963
|
-
method: "POST",
|
|
70964
|
-
headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
|
|
70965
|
-
body: JSON.stringify({ request_id: req.id })
|
|
70966
|
-
}).catch(() => {
|
|
71466
|
+
const done = await this._fulfilWelcomeRequests({
|
|
71467
|
+
mlsGroup,
|
|
71468
|
+
mlsGroupId: entry.mlsGroupId,
|
|
71469
|
+
label: `shared group ${gid.slice(0, 8)}`,
|
|
71470
|
+
welcomeFields: { conversation_group_id: gid },
|
|
71471
|
+
targets: [target]
|
|
70967
71472
|
});
|
|
70968
|
-
|
|
70969
|
-
|
|
71473
|
+
if (done > 0) {
|
|
71474
|
+
console.log(`[SecureChannel] Individual Welcome for ${target.deviceId.slice(0, 8)} in shared group ${gid.slice(0, 8)}`);
|
|
71475
|
+
}
|
|
70970
71476
|
} catch (indivErr) {
|
|
70971
|
-
console.warn(`[SecureChannel] Individual Welcome failed for ${
|
|
71477
|
+
console.warn(`[SecureChannel] Individual Welcome failed for ${target.deviceId.slice(0, 8)}:`, indivErr);
|
|
70972
71478
|
}
|
|
70973
71479
|
}
|
|
70974
71480
|
}
|
|
@@ -71000,38 +71506,20 @@ ${messageText}`;
|
|
|
71000
71506
|
if (!kpHex) continue;
|
|
71001
71507
|
const kpBytes = new Uint8Array(Buffer.from(kpHex, "hex"));
|
|
71002
71508
|
const memberKp = MLSGroupManager.deserializeKeyPackage(kpBytes);
|
|
71003
|
-
const
|
|
71004
|
-
|
|
71005
|
-
|
|
71006
|
-
|
|
71007
|
-
|
|
71008
|
-
|
|
71009
|
-
|
|
71010
|
-
|
|
71011
|
-
|
|
71012
|
-
}
|
|
71013
|
-
}
|
|
71014
|
-
if (
|
|
71015
|
-
|
|
71016
|
-
event: "mls_welcome",
|
|
71017
|
-
data: {
|
|
71018
|
-
target_device_id: req.requesting_device_id,
|
|
71019
|
-
group_id: convEntry.mlsGroupId,
|
|
71020
|
-
conversation_id: convId,
|
|
71021
|
-
payload: Buffer.from(welcome).toString("hex")
|
|
71022
|
-
}
|
|
71023
|
-
}));
|
|
71509
|
+
const done = await this._fulfilWelcomeRequests({
|
|
71510
|
+
mlsGroup,
|
|
71511
|
+
mlsGroupId: convEntry.mlsGroupId,
|
|
71512
|
+
label: `conv ${convId.slice(0, 8)}`,
|
|
71513
|
+
welcomeFields: { conversation_id: convId },
|
|
71514
|
+
targets: [{
|
|
71515
|
+
requestId: req.id,
|
|
71516
|
+
deviceId: req.requesting_device_id,
|
|
71517
|
+
keyPackage: memberKp
|
|
71518
|
+
}]
|
|
71519
|
+
});
|
|
71520
|
+
if (done > 0) {
|
|
71521
|
+
console.log(`[SecureChannel] Fulfilled Welcome for ${req.requesting_device_id.slice(0, 8)} in conv ${convId.slice(0, 8)}`);
|
|
71024
71522
|
}
|
|
71025
|
-
await fetch(
|
|
71026
|
-
`${this.config.apiUrl}/api/v1/mls/groups/${convEntry.mlsGroupId}/fulfill-welcome`,
|
|
71027
|
-
{
|
|
71028
|
-
method: "POST",
|
|
71029
|
-
headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
|
|
71030
|
-
body: JSON.stringify({ request_id: req.id })
|
|
71031
|
-
}
|
|
71032
|
-
);
|
|
71033
|
-
await saveMlsState(this.config.dataDir, convEntry.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
71034
|
-
console.log(`[SecureChannel] Fulfilled Welcome for ${req.requesting_device_id.slice(0, 8)} in conv ${convId.slice(0, 8)}`);
|
|
71035
71523
|
} catch (fulfillErr) {
|
|
71036
71524
|
console.warn(`[SecureChannel] Conv Welcome fulfill failed for ${req.requesting_device_id.slice(0, 8)}:`, fulfillErr);
|
|
71037
71525
|
}
|
|
@@ -71212,12 +71700,13 @@ ${messageText}`;
|
|
|
71212
71700
|
} catch (distErr) {
|
|
71213
71701
|
console.warn(`[SecureChannel] A2A distribute network error:`, distErr);
|
|
71214
71702
|
}
|
|
71215
|
-
if (!distributed && this.
|
|
71216
|
-
|
|
71703
|
+
if (!distributed && this._wsIsOpen()) {
|
|
71704
|
+
const a2aLabel = `A2A ${chId.slice(0, 8)}`;
|
|
71705
|
+
const commitSent = await this._sendFrameConfirmed({
|
|
71217
71706
|
event: "mls_commit",
|
|
71218
71707
|
data: { group_id: chState.mlsGroupId, epoch: Number(mlsGroup.epoch), payload: commitHex }
|
|
71219
|
-
})
|
|
71220
|
-
this.
|
|
71708
|
+
}, `${a2aLabel} commit`);
|
|
71709
|
+
const welcomeSent = commitSent && await this._sendFrameConfirmed({
|
|
71221
71710
|
event: "mls_welcome",
|
|
71222
71711
|
data: {
|
|
71223
71712
|
target_device_id: req.requesting_device_id,
|
|
@@ -71225,7 +71714,11 @@ ${messageText}`;
|
|
|
71225
71714
|
a2a_channel_id: chId,
|
|
71226
71715
|
payload: welcomeHex
|
|
71227
71716
|
}
|
|
71228
|
-
})
|
|
71717
|
+
}, `${a2aLabel} welcome`);
|
|
71718
|
+
if (!welcomeSent) {
|
|
71719
|
+
console.warn(`[SecureChannel] ${a2aLabel}: Welcome frames were NOT written \u2014 leaving the request pending`);
|
|
71720
|
+
continue;
|
|
71721
|
+
}
|
|
71229
71722
|
try {
|
|
71230
71723
|
await fetch(
|
|
71231
71724
|
`${this.config.apiUrl}/api/v1/mls/groups/${chState.mlsGroupId}/distribute`,
|
|
@@ -71248,16 +71741,15 @@ ${messageText}`;
|
|
|
71248
71741
|
console.warn(`[SecureChannel] Cannot deliver Welcome \u2014 HTTP distribute and WS both unavailable`);
|
|
71249
71742
|
continue;
|
|
71250
71743
|
}
|
|
71251
|
-
await
|
|
71252
|
-
|
|
71253
|
-
|
|
71254
|
-
|
|
71255
|
-
headers: { Authorization: `Bearer ${this._deviceJwt}`, "Content-Type": "application/json" },
|
|
71256
|
-
body: JSON.stringify({ request_id: req.id })
|
|
71257
|
-
}
|
|
71744
|
+
const a2aFulfilled = await this._postFulfilWelcome(
|
|
71745
|
+
chState.mlsGroupId,
|
|
71746
|
+
req.id,
|
|
71747
|
+
`A2A ${chId.slice(0, 8)}`
|
|
71258
71748
|
);
|
|
71259
71749
|
await saveMlsState(this.config.dataDir, chState.mlsGroupId, JSON.stringify(mlsGroup.exportState()));
|
|
71260
|
-
|
|
71750
|
+
if (a2aFulfilled) {
|
|
71751
|
+
console.log(`[SecureChannel] Fulfilled A2A Welcome for ${req.requesting_device_id.slice(0, 8)} in channel ${chId.slice(0, 8)}`);
|
|
71752
|
+
}
|
|
71261
71753
|
} catch (fulfillErr) {
|
|
71262
71754
|
console.log(`[SecureChannel] A2A Welcome fulfill failed: ${fulfillErr instanceof Error ? fulfillErr.message : String(fulfillErr)}`);
|
|
71263
71755
|
}
|
|
@@ -71431,12 +71923,12 @@ ${messageText}`;
|
|
|
71431
71923
|
console.log(`[SecureChannel] MLS sync: re-join needed for group ${groupId?.slice(0, 8)}`);
|
|
71432
71924
|
if (groupId) {
|
|
71433
71925
|
await deleteMlsState(this.config.dataDir, groupId);
|
|
71434
|
-
|
|
71435
|
-
|
|
71436
|
-
|
|
71437
|
-
|
|
71438
|
-
}
|
|
71926
|
+
const target = this._resolveGroupFamily(groupId);
|
|
71927
|
+
if (target) {
|
|
71928
|
+
this._mlsGroups.delete(target.managerKey);
|
|
71929
|
+
console.log(`[SecureChannel] MLS sync rejoin: dropped local state for ${target.label}`);
|
|
71439
71930
|
}
|
|
71931
|
+
void this._requestWelcomeSelfHeal(groupId);
|
|
71440
71932
|
}
|
|
71441
71933
|
} else if (action === "replay_complete") {
|
|
71442
71934
|
console.log(`[SecureChannel] MLS sync complete for group ${groupId?.slice(0, 8)} (${data.count} messages replayed)`);
|
|
@@ -71448,6 +71940,7 @@ ${messageText}`;
|
|
|
71448
71940
|
*/
|
|
71449
71941
|
async _handleA2AMessageMLS(data) {
|
|
71450
71942
|
const groupId = data.group_id;
|
|
71943
|
+
this._noteGroupEpoch(groupId, data.epoch);
|
|
71451
71944
|
const channelId = data.channel_id ?? data.a2a_channel_id;
|
|
71452
71945
|
let a2aChannelId = channelId;
|
|
71453
71946
|
if (!a2aChannelId) {
|
|
@@ -72580,6 +73073,45 @@ var init_gateway_send = __esm2({
|
|
|
72580
73073
|
init_openclaw_compat();
|
|
72581
73074
|
}
|
|
72582
73075
|
});
|
|
73076
|
+
function parseMentions(text) {
|
|
73077
|
+
const mentions = [];
|
|
73078
|
+
const re22 = /@(\w[\w]*)/gi;
|
|
73079
|
+
let match;
|
|
73080
|
+
while ((match = re22.exec(text)) !== null) {
|
|
73081
|
+
mentions.push(match[1].toLowerCase());
|
|
73082
|
+
}
|
|
73083
|
+
return mentions;
|
|
73084
|
+
}
|
|
73085
|
+
function owesRoomReply(plaintext, agentName, accountId, senderIsAgent, roomDisplayName, ownDisplayName, selfResolved = true) {
|
|
73086
|
+
const mentions = parseMentions(plaintext);
|
|
73087
|
+
if (mentions.length === 0) return false;
|
|
73088
|
+
if (mentions.includes("all") || mentions.includes("everyone")) {
|
|
73089
|
+
return senderIsAgent !== true;
|
|
73090
|
+
}
|
|
73091
|
+
const ownNames = ownRoomNames(agentName, accountId, roomDisplayName, ownDisplayName);
|
|
73092
|
+
if (mentions.some((m22) => ownNames.has(m22))) return true;
|
|
73093
|
+
if (!selfResolved) return true;
|
|
73094
|
+
return false;
|
|
73095
|
+
}
|
|
73096
|
+
function ownRoomNames(agentName, accountId, roomDisplayName, ownDisplayName) {
|
|
73097
|
+
const names = /* @__PURE__ */ new Set();
|
|
73098
|
+
const add4 = (n22) => {
|
|
73099
|
+
if (!n22) return;
|
|
73100
|
+
const lower = n22.toLowerCase();
|
|
73101
|
+
names.add(lower);
|
|
73102
|
+
names.add(lower.split(/[\s(]/)[0]);
|
|
73103
|
+
};
|
|
73104
|
+
add4(agentName);
|
|
73105
|
+
add4(accountId);
|
|
73106
|
+
add4(roomDisplayName);
|
|
73107
|
+
add4(ownDisplayName);
|
|
73108
|
+
return names;
|
|
73109
|
+
}
|
|
73110
|
+
var init_room_protocol = __esm2({
|
|
73111
|
+
"src/room-protocol.ts"() {
|
|
73112
|
+
"use strict";
|
|
73113
|
+
}
|
|
73114
|
+
});
|
|
72583
73115
|
var init_llm_response_parser = __esm2({
|
|
72584
73116
|
"src/llm-response-parser.ts"() {
|
|
72585
73117
|
"use strict";
|
|
@@ -72613,6 +73145,7 @@ var isUsingManagedRoutes;
|
|
|
72613
73145
|
var init_openclaw_entry = __esm2({
|
|
72614
73146
|
"src/openclaw-entry.ts"() {
|
|
72615
73147
|
"use strict";
|
|
73148
|
+
init_room_protocol();
|
|
72616
73149
|
init_account_config();
|
|
72617
73150
|
init_fetch_interceptor();
|
|
72618
73151
|
init_http_handlers();
|
|
@@ -98671,7 +99204,8 @@ var init_index = __esm2({
|
|
|
98671
99204
|
init_skill_invoker();
|
|
98672
99205
|
await init_skill_telemetry();
|
|
98673
99206
|
await init_policy_enforcer();
|
|
98674
|
-
|
|
99207
|
+
init_room_protocol();
|
|
99208
|
+
VERSION = true ? "0.23.29" : "0.0.0-dev";
|
|
98675
99209
|
}
|
|
98676
99210
|
});
|
|
98677
99211
|
await init_index();
|
|
@@ -134839,9 +135373,32 @@ function roomTurnPrefix(roomId, senderName, roomName) {
|
|
|
134839
135373
|
const room = roomName ? `"${roomName}"` : roomId.slice(0, 8);
|
|
134840
135374
|
return `[room ${room} | ${senderName}]`;
|
|
134841
135375
|
}
|
|
135376
|
+
function _makeOwesReply(agentName, channel, log) {
|
|
135377
|
+
let warnedNoIdentity = false;
|
|
135378
|
+
return (e7) => {
|
|
135379
|
+
const own = agentName ?? "";
|
|
135380
|
+
const hub = channel.ownDisplayName ?? "";
|
|
135381
|
+
if (!own && !hub && !warnedNoIdentity) {
|
|
135382
|
+
warnedNoIdentity = true;
|
|
135383
|
+
log(
|
|
135384
|
+
"WARNING: this bridge has no agentName and no hub display_name, so it cannot recognise its own @mention (#1107). It will answer @all and any @-addressed message (fail-open), but targeted replies will be unreliable. Set the agent name in the bridge config."
|
|
135385
|
+
);
|
|
135386
|
+
}
|
|
135387
|
+
return owesRoomReply(
|
|
135388
|
+
e7.plaintext,
|
|
135389
|
+
own,
|
|
135390
|
+
own,
|
|
135391
|
+
e7.senderIsAgent,
|
|
135392
|
+
void 0,
|
|
135393
|
+
hub,
|
|
135394
|
+
Boolean(own || hub)
|
|
135395
|
+
);
|
|
135396
|
+
};
|
|
135397
|
+
}
|
|
134842
135398
|
function wireBridge(channel, session, target, opts = {}) {
|
|
134843
135399
|
const log = opts.log ?? (() => {
|
|
134844
135400
|
});
|
|
135401
|
+
const _owesReply = _makeOwesReply(opts.agentName, channel, log);
|
|
134845
135402
|
let workAllowed = false;
|
|
134846
135403
|
const workAllowedGetter = () => workAllowed;
|
|
134847
135404
|
opts.onWorkAllowed?.(workAllowedGetter);
|
|
@@ -134893,7 +135450,7 @@ function wireBridge(channel, session, target, opts = {}) {
|
|
|
134893
135450
|
target.setRoom(e7.roomId);
|
|
134894
135451
|
session.push(`${roomTurnPrefix(e7.roomId, e7.senderName, roomNames.get(e7.roomId))}: ${e7.plaintext}`, target.snapshotReply(channel, log), {
|
|
134895
135452
|
autoReplyOnText: false,
|
|
134896
|
-
replyExpected: e7
|
|
135453
|
+
replyExpected: _owesReply(e7),
|
|
134897
135454
|
armed: () => arming.isArmed(e7.roomId)
|
|
134898
135455
|
});
|
|
134899
135456
|
});
|
|
@@ -135052,7 +135609,7 @@ async function main() {
|
|
|
135052
135609
|
"[bridge] warning: passing the invite token on the command line is visible to other local users via 'ps'. Prefer: AV_INVITE_TOKEN=\u2026 npx @agentvault/claude-bridge"
|
|
135053
135610
|
);
|
|
135054
135611
|
}
|
|
135055
|
-
logLine(`[bridge] version: ${true ? "0.8.
|
|
135612
|
+
logLine(`[bridge] version: ${true ? "0.8.2" : "dev"}`);
|
|
135056
135613
|
logLine(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
135057
135614
|
logLine(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
|
|
135058
135615
|
if (cfg.armRoom) {
|
|
@@ -135084,7 +135641,7 @@ async function main() {
|
|
|
135084
135641
|
// its default would render "@agentvault/agentvault@0.7.x" — the wrong
|
|
135085
135642
|
// package name attached to the bridge's version number, which is worse
|
|
135086
135643
|
// than either alone.
|
|
135087
|
-
clientVersion: `@agentvault/claude-bridge@${true ? "0.8.
|
|
135644
|
+
clientVersion: `@agentvault/claude-bridge@${true ? "0.8.2" : "dev"}`
|
|
135088
135645
|
});
|
|
135089
135646
|
const agentSystemPrompt = cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent on AgentVault. You talk with your owner in 1:1 direct messages and collaborate with other agents in shared rooms. That is your identity \u2014 introduce yourself by that name and do not claim to be any other agent. To speak, call the say tool. In a 1:1 with your owner, reply to what they say. In a room you see every message \u2014 call say only when you have something worth adding, and otherwise stay silent. Keep messages concise.`;
|
|
135090
135647
|
const deviceJwt = () => {
|
|
@@ -135164,6 +135721,9 @@ async function main() {
|
|
|
135164
135721
|
{
|
|
135165
135722
|
roomFilter: cfg.roomFilter,
|
|
135166
135723
|
armRoom: cfg.armRoom,
|
|
135724
|
+
// #1107: needed to recognise this agent's own @mention. Without it the
|
|
135725
|
+
// agent can only answer @all.
|
|
135726
|
+
agentName: cfg.agentName,
|
|
135167
135727
|
log: (m6) => logLine("[bridge] " + m6),
|
|
135168
135728
|
// Slice 2 Plan C (T11): live arm/disarm + local-approval poll + heartbeat.
|
|
135169
135729
|
workspaceDir: cfg.workspaceDir,
|