@agentvault/agentvault 0.19.34 → 0.19.36
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/cli.js +171 -48
- package/dist/cli.js.map +2 -2
- package/dist/index.js +171 -48
- package/dist/index.js.map +2 -2
- package/dist/openclaw-entry.js.map +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -44744,7 +44744,7 @@ var init_ratchet = __esm({
|
|
|
44744
44744
|
async "../crypto/dist/ratchet.js"() {
|
|
44745
44745
|
"use strict";
|
|
44746
44746
|
await init_libsodium_wrappers();
|
|
44747
|
-
MAX_SKIP =
|
|
44747
|
+
MAX_SKIP = 1e3;
|
|
44748
44748
|
CHAIN_KEY_SEED = new Uint8Array([1]);
|
|
44749
44749
|
MSG_KEY_SEED = new Uint8Array([2]);
|
|
44750
44750
|
HEADER_KEY_SEED = new Uint8Array([3]);
|
|
@@ -47175,6 +47175,12 @@ var init_channel = __esm({
|
|
|
47175
47175
|
this._primaryConversationId = this._persisted.primaryConversationId;
|
|
47176
47176
|
this._fingerprint = this._persisted.fingerprint;
|
|
47177
47177
|
this._lastInboundRoomId = this._persisted.lastInboundRoomId;
|
|
47178
|
+
if (this._persisted.seenMessageIds) {
|
|
47179
|
+
this._seenMessageIds = new Set(this._persisted.seenMessageIds.slice(-_SecureChannel.SEEN_MSG_MAX));
|
|
47180
|
+
}
|
|
47181
|
+
if (this._persisted.seenA2AMessageIds) {
|
|
47182
|
+
this._a2aSeenMessageIds = new Set(this._persisted.seenA2AMessageIds.slice(-_SecureChannel.A2A_SEEN_MAX));
|
|
47183
|
+
}
|
|
47178
47184
|
for (const [convId, sessionData] of Object.entries(
|
|
47179
47185
|
this._persisted.sessions
|
|
47180
47186
|
)) {
|
|
@@ -47183,7 +47189,8 @@ var init_channel = __esm({
|
|
|
47183
47189
|
this._sessions.set(convId, {
|
|
47184
47190
|
ownerDeviceId: sessionData.ownerDeviceId,
|
|
47185
47191
|
ratchet,
|
|
47186
|
-
activated: sessionData.activated ?? false
|
|
47192
|
+
activated: sessionData.activated ?? false,
|
|
47193
|
+
epoch: sessionData.epoch
|
|
47187
47194
|
});
|
|
47188
47195
|
}
|
|
47189
47196
|
}
|
|
@@ -47222,7 +47229,8 @@ var init_channel = __esm({
|
|
|
47222
47229
|
this._sessions.set(convId, {
|
|
47223
47230
|
ownerDeviceId: sd.ownerDeviceId,
|
|
47224
47231
|
ratchet: DoubleRatchet.deserialize(sd.ratchetState),
|
|
47225
|
-
activated: sd.activated ?? false
|
|
47232
|
+
activated: sd.activated ?? false,
|
|
47233
|
+
epoch: sd.epoch
|
|
47226
47234
|
});
|
|
47227
47235
|
}
|
|
47228
47236
|
}
|
|
@@ -47321,6 +47329,7 @@ var init_channel = __esm({
|
|
|
47321
47329
|
}
|
|
47322
47330
|
const messageGroupId = randomUUID2();
|
|
47323
47331
|
let sentCount = 0;
|
|
47332
|
+
const pendingWsSends = [];
|
|
47324
47333
|
for (const [convId, session] of this._sessions) {
|
|
47325
47334
|
if (!session.activated) continue;
|
|
47326
47335
|
if (roomConvIds.has(convId)) continue;
|
|
@@ -47353,7 +47362,7 @@ var init_channel = __esm({
|
|
|
47353
47362
|
if (this._persisted?.hubId) {
|
|
47354
47363
|
payload.sender_hub_id = this._persisted.hubId;
|
|
47355
47364
|
}
|
|
47356
|
-
|
|
47365
|
+
pendingWsSends.push(
|
|
47357
47366
|
JSON.stringify({
|
|
47358
47367
|
event: "message",
|
|
47359
47368
|
data: payload
|
|
@@ -47379,6 +47388,9 @@ var init_channel = __esm({
|
|
|
47379
47388
|
console.warn("[SecureChannel] send() delivered to 0 sessions (all skipped or failed)");
|
|
47380
47389
|
}
|
|
47381
47390
|
await this._persistState();
|
|
47391
|
+
for (const frame of pendingWsSends) {
|
|
47392
|
+
this._ws.send(frame);
|
|
47393
|
+
}
|
|
47382
47394
|
}
|
|
47383
47395
|
/**
|
|
47384
47396
|
* Send a typing indicator to all owner devices.
|
|
@@ -47558,13 +47570,15 @@ var init_channel = __esm({
|
|
|
47558
47570
|
this._sessions.set(conv.id, {
|
|
47559
47571
|
ownerDeviceId: otherDeviceId,
|
|
47560
47572
|
ratchet,
|
|
47561
|
-
activated: isInitiator
|
|
47573
|
+
activated: isInitiator,
|
|
47562
47574
|
// initiator can send immediately
|
|
47575
|
+
epoch: 1
|
|
47563
47576
|
});
|
|
47564
47577
|
this._persisted.sessions[conv.id] = {
|
|
47565
47578
|
ownerDeviceId: otherDeviceId,
|
|
47566
47579
|
ratchetState: ratchet.serialize(),
|
|
47567
|
-
activated: isInitiator
|
|
47580
|
+
activated: isInitiator,
|
|
47581
|
+
epoch: 1
|
|
47568
47582
|
};
|
|
47569
47583
|
conversationIds.push(conv.id);
|
|
47570
47584
|
console.log(
|
|
@@ -47657,6 +47671,7 @@ var init_channel = __esm({
|
|
|
47657
47671
|
if (recipients.length === 0) {
|
|
47658
47672
|
throw new Error("No active sessions in room");
|
|
47659
47673
|
}
|
|
47674
|
+
await this._persistState();
|
|
47660
47675
|
if (this._state === "ready" && this._ws) {
|
|
47661
47676
|
this._ws.send(
|
|
47662
47677
|
JSON.stringify({
|
|
@@ -47696,7 +47711,6 @@ var init_channel = __esm({
|
|
|
47696
47711
|
throw new Error(`Failed to send room message: ${err}`);
|
|
47697
47712
|
}
|
|
47698
47713
|
}
|
|
47699
|
-
await this._persistState();
|
|
47700
47714
|
}
|
|
47701
47715
|
/**
|
|
47702
47716
|
* Leave a room: remove sessions and persisted room state.
|
|
@@ -47795,11 +47809,12 @@ var init_channel = __esm({
|
|
|
47795
47809
|
attachment: attachMeta
|
|
47796
47810
|
});
|
|
47797
47811
|
const messageGroupId = randomUUID2();
|
|
47812
|
+
const pendingWsSends = [];
|
|
47798
47813
|
for (const [convId, session] of this._sessions) {
|
|
47799
47814
|
if (!session.activated) continue;
|
|
47800
47815
|
const encrypted = session.ratchet.encrypt(envelope);
|
|
47801
47816
|
const transport = encryptedMessageToTransport(encrypted);
|
|
47802
|
-
|
|
47817
|
+
pendingWsSends.push(
|
|
47803
47818
|
JSON.stringify({
|
|
47804
47819
|
event: "message",
|
|
47805
47820
|
data: {
|
|
@@ -47813,6 +47828,9 @@ var init_channel = __esm({
|
|
|
47813
47828
|
);
|
|
47814
47829
|
}
|
|
47815
47830
|
await this._persistState();
|
|
47831
|
+
for (const frame of pendingWsSends) {
|
|
47832
|
+
this._ws.send(frame);
|
|
47833
|
+
}
|
|
47816
47834
|
}
|
|
47817
47835
|
async sendActionConfirmation(confirmation) {
|
|
47818
47836
|
const envelope = {
|
|
@@ -48655,12 +48673,14 @@ var init_channel = __esm({
|
|
|
48655
48673
|
this._sessions.set(conv.conversation_id, {
|
|
48656
48674
|
ownerDeviceId: conv.owner_device_id,
|
|
48657
48675
|
ratchet,
|
|
48658
|
-
activated: false
|
|
48676
|
+
activated: false,
|
|
48659
48677
|
// Wait for owner's first message before sending to this session
|
|
48678
|
+
epoch: 1
|
|
48660
48679
|
});
|
|
48661
48680
|
sessions[conv.conversation_id] = {
|
|
48662
48681
|
ownerDeviceId: conv.owner_device_id,
|
|
48663
|
-
ratchetState: ratchet.serialize()
|
|
48682
|
+
ratchetState: ratchet.serialize(),
|
|
48683
|
+
epoch: 1
|
|
48664
48684
|
};
|
|
48665
48685
|
console.log(
|
|
48666
48686
|
`[SecureChannel] Session initialized for conv ${conv.conversation_id.slice(0, 8)}... (owner ${conv.owner_device_id.slice(0, 8)}..., primary=${conv.is_primary})`
|
|
@@ -49159,8 +49179,9 @@ var init_channel = __esm({
|
|
|
49159
49179
|
ownerDeviceId: "",
|
|
49160
49180
|
// A2A sessions don't have an owner device
|
|
49161
49181
|
ratchetState: ratchet.serialize(),
|
|
49162
|
-
activated: activatedRole === "initiator"
|
|
49182
|
+
activated: activatedRole === "initiator",
|
|
49163
49183
|
// initiator can send immediately
|
|
49184
|
+
epoch: 1
|
|
49164
49185
|
};
|
|
49165
49186
|
channelEntry.role = activatedRole;
|
|
49166
49187
|
if (convId) channelEntry.conversationId = convId;
|
|
@@ -49345,7 +49366,8 @@ var init_channel = __esm({
|
|
|
49345
49366
|
conversation_id: convId,
|
|
49346
49367
|
reason: "no_session",
|
|
49347
49368
|
identity_public_key: this._persisted.identityKeypair.publicKey,
|
|
49348
|
-
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey
|
|
49369
|
+
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey,
|
|
49370
|
+
epoch: this._persisted.sessions[convId]?.epoch
|
|
49349
49371
|
}
|
|
49350
49372
|
})
|
|
49351
49373
|
);
|
|
@@ -49384,7 +49406,8 @@ var init_channel = __esm({
|
|
|
49384
49406
|
conversation_id: convId,
|
|
49385
49407
|
reason: "decrypt_failure",
|
|
49386
49408
|
identity_public_key: idPubHex,
|
|
49387
|
-
ephemeral_public_key: ephPubHex
|
|
49409
|
+
ephemeral_public_key: ephPubHex,
|
|
49410
|
+
epoch: this._persisted?.sessions[convId]?.epoch
|
|
49388
49411
|
}
|
|
49389
49412
|
})
|
|
49390
49413
|
);
|
|
@@ -49629,11 +49652,12 @@ ${messageText}`;
|
|
|
49629
49652
|
});
|
|
49630
49653
|
this._appendHistory("agent", plaintext, topicId);
|
|
49631
49654
|
const messageGroupId = randomUUID2();
|
|
49655
|
+
const pendingWsSends = [];
|
|
49632
49656
|
for (const [convId, session] of this._sessions) {
|
|
49633
49657
|
if (!session.activated) continue;
|
|
49634
49658
|
const encrypted = session.ratchet.encrypt(envelope);
|
|
49635
49659
|
const transport = encryptedMessageToTransport(encrypted);
|
|
49636
|
-
|
|
49660
|
+
pendingWsSends.push(
|
|
49637
49661
|
JSON.stringify({
|
|
49638
49662
|
event: "message",
|
|
49639
49663
|
data: {
|
|
@@ -49647,6 +49671,9 @@ ${messageText}`;
|
|
|
49647
49671
|
);
|
|
49648
49672
|
}
|
|
49649
49673
|
await this._persistState();
|
|
49674
|
+
for (const frame of pendingWsSends) {
|
|
49675
|
+
this._ws.send(frame);
|
|
49676
|
+
}
|
|
49650
49677
|
}
|
|
49651
49678
|
/**
|
|
49652
49679
|
* Relay an owner's message to all sibling sessions as encrypted sync messages.
|
|
@@ -49669,13 +49696,14 @@ ${messageText}`;
|
|
|
49669
49696
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49670
49697
|
topicId
|
|
49671
49698
|
});
|
|
49699
|
+
const pendingWsSends = [];
|
|
49672
49700
|
for (const [siblingConvId, siblingSession] of this._sessions) {
|
|
49673
49701
|
if (siblingConvId === sourceConvId) continue;
|
|
49674
49702
|
if (!siblingSession.activated) continue;
|
|
49675
49703
|
if (roomConvIds.has(siblingConvId)) continue;
|
|
49676
49704
|
const syncEncrypted = siblingSession.ratchet.encrypt(syncPayload);
|
|
49677
49705
|
const syncTransport = encryptedMessageToTransport(syncEncrypted);
|
|
49678
|
-
|
|
49706
|
+
pendingWsSends.push(
|
|
49679
49707
|
JSON.stringify({
|
|
49680
49708
|
event: "message",
|
|
49681
49709
|
data: {
|
|
@@ -49686,6 +49714,10 @@ ${messageText}`;
|
|
|
49686
49714
|
})
|
|
49687
49715
|
);
|
|
49688
49716
|
}
|
|
49717
|
+
await this._persistState();
|
|
49718
|
+
for (const frame of pendingWsSends) {
|
|
49719
|
+
this._ws.send(frame);
|
|
49720
|
+
}
|
|
49689
49721
|
}
|
|
49690
49722
|
/**
|
|
49691
49723
|
* Resolve the agent's workspace directory.
|
|
@@ -49729,6 +49761,7 @@ ${messageText}`;
|
|
|
49729
49761
|
const plaintext = JSON.stringify(payload);
|
|
49730
49762
|
const encrypted = session.ratchet.encrypt(plaintext);
|
|
49731
49763
|
const transport = encryptedMessageToTransport(encrypted);
|
|
49764
|
+
await this._persistState();
|
|
49732
49765
|
this._ws.send(
|
|
49733
49766
|
JSON.stringify({
|
|
49734
49767
|
event: "message",
|
|
@@ -49739,7 +49772,6 @@ ${messageText}`;
|
|
|
49739
49772
|
}
|
|
49740
49773
|
})
|
|
49741
49774
|
);
|
|
49742
|
-
await this._persistState();
|
|
49743
49775
|
}
|
|
49744
49776
|
/**
|
|
49745
49777
|
* Send stored message history to a newly-activated session.
|
|
@@ -49762,6 +49794,7 @@ ${messageText}`;
|
|
|
49762
49794
|
});
|
|
49763
49795
|
const encrypted = session.ratchet.encrypt(replayPayload);
|
|
49764
49796
|
const transport = encryptedMessageToTransport(encrypted);
|
|
49797
|
+
await this._persistState();
|
|
49765
49798
|
this._ws.send(
|
|
49766
49799
|
JSON.stringify({
|
|
49767
49800
|
event: "message",
|
|
@@ -49808,13 +49841,15 @@ ${messageText}`;
|
|
|
49808
49841
|
this._sessions.set(event.conversation_id, {
|
|
49809
49842
|
ownerDeviceId: event.owner_device_id,
|
|
49810
49843
|
ratchet,
|
|
49811
|
-
activated: false
|
|
49844
|
+
activated: false,
|
|
49812
49845
|
// Wait for owner's first message
|
|
49846
|
+
epoch: 1
|
|
49813
49847
|
});
|
|
49814
49848
|
this._persisted.sessions[event.conversation_id] = {
|
|
49815
49849
|
ownerDeviceId: event.owner_device_id,
|
|
49816
49850
|
ratchetState: ratchet.serialize(),
|
|
49817
|
-
activated: false
|
|
49851
|
+
activated: false,
|
|
49852
|
+
epoch: 1
|
|
49818
49853
|
};
|
|
49819
49854
|
if (event.owner_hub_id) {
|
|
49820
49855
|
this._persisted.ownerHubId = event.owner_hub_id;
|
|
@@ -49861,17 +49896,21 @@ ${messageText}`;
|
|
|
49861
49896
|
keyType: "ed25519"
|
|
49862
49897
|
}, hexToBytes(data.identity_public_key));
|
|
49863
49898
|
const existingSession = this._sessions.get(convId);
|
|
49899
|
+
const existingPersisted = this._persisted.sessions[convId];
|
|
49864
49900
|
const ownerDeviceId = data.sender_device_id ?? existingSession?.ownerDeviceId ?? "";
|
|
49901
|
+
const newEpoch = (existingPersisted?.epoch ?? existingSession?.epoch ?? 0) + 1;
|
|
49865
49902
|
this._sessions.set(convId, {
|
|
49866
49903
|
ownerDeviceId,
|
|
49867
49904
|
ratchet,
|
|
49868
|
-
activated: false
|
|
49905
|
+
activated: false,
|
|
49869
49906
|
// Wait for owner's encrypted session_init
|
|
49907
|
+
epoch: newEpoch
|
|
49870
49908
|
});
|
|
49871
49909
|
this._persisted.sessions[convId] = {
|
|
49872
49910
|
ownerDeviceId,
|
|
49873
49911
|
ratchetState: ratchet.serialize(),
|
|
49874
|
-
activated: false
|
|
49912
|
+
activated: false,
|
|
49913
|
+
epoch: newEpoch
|
|
49875
49914
|
};
|
|
49876
49915
|
await this._persistState();
|
|
49877
49916
|
if (this._ws) {
|
|
@@ -49881,7 +49920,8 @@ ${messageText}`;
|
|
|
49881
49920
|
data: {
|
|
49882
49921
|
conversation_id: convId,
|
|
49883
49922
|
identity_public_key: identity.publicKey,
|
|
49884
|
-
ephemeral_public_key: ephemeral.publicKey
|
|
49923
|
+
ephemeral_public_key: ephemeral.publicKey,
|
|
49924
|
+
epoch: newEpoch
|
|
49885
49925
|
}
|
|
49886
49926
|
})
|
|
49887
49927
|
);
|
|
@@ -49895,6 +49935,32 @@ ${messageText}`;
|
|
|
49895
49935
|
this.emit("error", err);
|
|
49896
49936
|
}
|
|
49897
49937
|
}
|
|
49938
|
+
/**
|
|
49939
|
+
* Send a resync_request for a room conversation if the cooldown has elapsed.
|
|
49940
|
+
* Shared by all room ratchet failure paths to avoid code duplication.
|
|
49941
|
+
*/
|
|
49942
|
+
_sendRoomResyncIfCooled(convId, reason, session) {
|
|
49943
|
+
const RESYNC_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
49944
|
+
const lastResync = this._lastResyncRequest.get(convId) ?? 0;
|
|
49945
|
+
if (Date.now() - lastResync > RESYNC_COOLDOWN_MS && this._ws && this._persisted) {
|
|
49946
|
+
this._lastResyncRequest.set(convId, Date.now());
|
|
49947
|
+
this._ws.send(
|
|
49948
|
+
JSON.stringify({
|
|
49949
|
+
event: "resync_request",
|
|
49950
|
+
data: {
|
|
49951
|
+
conversation_id: convId,
|
|
49952
|
+
reason,
|
|
49953
|
+
identity_public_key: this._persisted.identityKeypair.publicKey,
|
|
49954
|
+
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey,
|
|
49955
|
+
epoch: session?.epoch
|
|
49956
|
+
}
|
|
49957
|
+
})
|
|
49958
|
+
);
|
|
49959
|
+
console.log(
|
|
49960
|
+
`[SecureChannel] Room resync requested for conv ${convId.slice(0, 8)} (${reason})`
|
|
49961
|
+
);
|
|
49962
|
+
}
|
|
49963
|
+
}
|
|
49898
49964
|
/**
|
|
49899
49965
|
* Handle an incoming room message. Finds the pairwise conversation
|
|
49900
49966
|
* for the sender, decrypts, and emits a room_message event.
|
|
@@ -50014,20 +50080,39 @@ ${messageText}`;
|
|
|
50014
50080
|
console.log(
|
|
50015
50081
|
`[SecureChannel] Room ratchet re-initialized for conv ${convId.slice(0, 8)}...`
|
|
50016
50082
|
);
|
|
50017
|
-
|
|
50018
|
-
|
|
50019
|
-
|
|
50020
|
-
|
|
50083
|
+
const incomingMsgNum = encrypted.header.messageNumber;
|
|
50084
|
+
if (incomingMsgNum <= 5) {
|
|
50085
|
+
try {
|
|
50086
|
+
plaintext = session.ratchet.decrypt(encrypted);
|
|
50087
|
+
session.activated = true;
|
|
50088
|
+
if (this._persisted.sessions[convId]) {
|
|
50089
|
+
this._persisted.sessions[convId].activated = true;
|
|
50090
|
+
}
|
|
50091
|
+
await this._persistState();
|
|
50092
|
+
console.log(
|
|
50093
|
+
`[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
|
|
50094
|
+
);
|
|
50095
|
+
} catch (retryErr) {
|
|
50096
|
+
console.warn(
|
|
50097
|
+
`[SecureChannel] Room re-init retry failed for conv ${convId.slice(0, 8)} (msgNum=${incomingMsgNum}):`,
|
|
50098
|
+
retryErr
|
|
50099
|
+
);
|
|
50100
|
+
this._sendRoomResyncIfCooled(convId, "room_reinit_retry_failed", session);
|
|
50101
|
+
return;
|
|
50102
|
+
}
|
|
50103
|
+
} else {
|
|
50104
|
+
console.log(
|
|
50105
|
+
`[SecureChannel] Room re-init: skipping message with msgNum=${incomingMsgNum} for conv ${convId.slice(0, 8)} (too far ahead for fresh ratchet)`
|
|
50106
|
+
);
|
|
50107
|
+
this._sendRoomResyncIfCooled(convId, "room_message_skip");
|
|
50108
|
+
return;
|
|
50021
50109
|
}
|
|
50022
|
-
await this._persistState();
|
|
50023
|
-
console.log(
|
|
50024
|
-
`[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
|
|
50025
|
-
);
|
|
50026
50110
|
} catch (reinitErr) {
|
|
50027
50111
|
console.error(
|
|
50028
50112
|
`[SecureChannel] Room ratchet re-init failed for conv ${convId.slice(0, 8)}...:`,
|
|
50029
50113
|
reinitErr
|
|
50030
50114
|
);
|
|
50115
|
+
this._sendRoomResyncIfCooled(convId, "room_reinit_failed");
|
|
50031
50116
|
return;
|
|
50032
50117
|
}
|
|
50033
50118
|
}
|
|
@@ -50201,13 +50286,14 @@ ${messageText}`;
|
|
|
50201
50286
|
const dist = chain.getDistribution(this._deviceId);
|
|
50202
50287
|
const distJson = JSON.stringify(dist);
|
|
50203
50288
|
const alreadyDistributed = new Set(room.distributedTo ?? []);
|
|
50289
|
+
const pendingWsSends = [];
|
|
50204
50290
|
for (const convId of room.conversationIds) {
|
|
50205
50291
|
const session = this._sessions.get(convId);
|
|
50206
50292
|
if (!session || alreadyDistributed.has(session.ownerDeviceId)) continue;
|
|
50207
50293
|
const encrypted = session.ratchet.encrypt(distJson);
|
|
50208
50294
|
const transport = encryptedMessageToTransport(encrypted);
|
|
50209
50295
|
if (this._state === "ready" && this._ws) {
|
|
50210
|
-
|
|
50296
|
+
pendingWsSends.push(
|
|
50211
50297
|
JSON.stringify({
|
|
50212
50298
|
event: "sender_key_distribution",
|
|
50213
50299
|
data: {
|
|
@@ -50223,6 +50309,9 @@ ${messageText}`;
|
|
|
50223
50309
|
}
|
|
50224
50310
|
room.distributedTo = [...alreadyDistributed];
|
|
50225
50311
|
await this._persistState();
|
|
50312
|
+
for (const frame of pendingWsSends) {
|
|
50313
|
+
this._ws.send(frame);
|
|
50314
|
+
}
|
|
50226
50315
|
}
|
|
50227
50316
|
/**
|
|
50228
50317
|
* Handle an incoming sender_key_distribution event.
|
|
@@ -50420,8 +50509,10 @@ ${messageText}`;
|
|
|
50420
50509
|
try {
|
|
50421
50510
|
a2aPlaintext = ratchet.decrypt(encryptedMessage);
|
|
50422
50511
|
} catch (decryptErr) {
|
|
50423
|
-
console.error(`[SecureChannel] A2A decrypt failed \u2014 restoring ratchet
|
|
50512
|
+
console.error(`[SecureChannel] A2A decrypt failed \u2014 restoring ratchet, initiating rekey:`, decryptErr);
|
|
50424
50513
|
channelEntry.session.ratchetState = ratchetSnapshot;
|
|
50514
|
+
await this._persistState();
|
|
50515
|
+
await this._initiateA2ARekey(channelId);
|
|
50425
50516
|
return;
|
|
50426
50517
|
}
|
|
50427
50518
|
channelEntry.session.ratchetState = ratchet.serialize();
|
|
@@ -50463,24 +50554,9 @@ ${messageText}`;
|
|
|
50463
50554
|
}
|
|
50464
50555
|
} else {
|
|
50465
50556
|
console.warn(
|
|
50466
|
-
`[SecureChannel] Received encrypted A2A but no session for ${channelId.slice(0, 8)} \u2014
|
|
50557
|
+
`[SecureChannel] Received encrypted A2A but no session for ${channelId.slice(0, 8)} \u2014 initiating rekey`
|
|
50467
50558
|
);
|
|
50468
|
-
|
|
50469
|
-
if (entry && !entry.pendingEphemeralPrivateKey && !entry.session) {
|
|
50470
|
-
try {
|
|
50471
|
-
const a2aEphemeral = await generateEphemeralKeypair();
|
|
50472
|
-
const ephPubHex = bytesToHex(a2aEphemeral.publicKey);
|
|
50473
|
-
entry.pendingEphemeralPrivateKey = bytesToHex(a2aEphemeral.privateKey);
|
|
50474
|
-
this._ws?.send(JSON.stringify({
|
|
50475
|
-
event: "a2a_key_exchange",
|
|
50476
|
-
data: { channel_id: channelId, ephemeral_key: ephPubHex }
|
|
50477
|
-
}));
|
|
50478
|
-
await this._persistState();
|
|
50479
|
-
console.log(`[SecureChannel] On-demand A2A key exchange for ${channelId.slice(0, 8)}`);
|
|
50480
|
-
} catch (kxErr) {
|
|
50481
|
-
console.warn(`[SecureChannel] On-demand key exchange failed:`, kxErr);
|
|
50482
|
-
}
|
|
50483
|
-
}
|
|
50559
|
+
await this._initiateA2ARekey(channelId);
|
|
50484
50560
|
}
|
|
50485
50561
|
} else {
|
|
50486
50562
|
const a2aMsg = {
|
|
@@ -50497,6 +50573,51 @@ ${messageText}`;
|
|
|
50497
50573
|
}
|
|
50498
50574
|
}
|
|
50499
50575
|
}
|
|
50576
|
+
/**
|
|
50577
|
+
* Initiate A2A channel rekey after decrypt failure.
|
|
50578
|
+
* Calls the backend to reset channel to approved, clears local session,
|
|
50579
|
+
* and submits a fresh ephemeral key for key exchange.
|
|
50580
|
+
*/
|
|
50581
|
+
async _initiateA2ARekey(channelId) {
|
|
50582
|
+
const entry = this._persisted?.a2aChannels?.[channelId];
|
|
50583
|
+
if (!entry || !this._deviceJwt || !this._ws) return;
|
|
50584
|
+
const now = Date.now();
|
|
50585
|
+
const lastRekey = entry._lastRekeyAttempt ?? 0;
|
|
50586
|
+
if (now - lastRekey < 3e4) return;
|
|
50587
|
+
entry._lastRekeyAttempt = now;
|
|
50588
|
+
console.log(`[SecureChannel] Initiating A2A rekey for ${channelId.slice(0, 8)}...`);
|
|
50589
|
+
try {
|
|
50590
|
+
const res = await fetch(
|
|
50591
|
+
`${this.config.apiUrl}/api/v1/a2a/channels/${channelId}/rekey`,
|
|
50592
|
+
{
|
|
50593
|
+
method: "POST",
|
|
50594
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
50595
|
+
}
|
|
50596
|
+
);
|
|
50597
|
+
if (!res.ok) {
|
|
50598
|
+
const detail = await res.text();
|
|
50599
|
+
console.warn(`[SecureChannel] A2A rekey API failed (${res.status}): ${detail}`);
|
|
50600
|
+
return;
|
|
50601
|
+
}
|
|
50602
|
+
delete entry.session;
|
|
50603
|
+
delete entry.observerSession;
|
|
50604
|
+
delete entry.pendingEphemeralPrivateKey;
|
|
50605
|
+
delete entry.pendingObserverEphemeralPrivateKey;
|
|
50606
|
+
const a2aEphemeral = await generateEphemeralKeypair();
|
|
50607
|
+
entry.pendingEphemeralPrivateKey = bytesToHex(a2aEphemeral.privateKey);
|
|
50608
|
+
this._ws.send(JSON.stringify({
|
|
50609
|
+
event: "a2a_key_exchange",
|
|
50610
|
+
data: {
|
|
50611
|
+
channel_id: channelId,
|
|
50612
|
+
ephemeral_key: bytesToHex(a2aEphemeral.publicKey)
|
|
50613
|
+
}
|
|
50614
|
+
}));
|
|
50615
|
+
await this._persistState();
|
|
50616
|
+
console.log(`[SecureChannel] A2A rekey submitted for ${channelId.slice(0, 8)}`);
|
|
50617
|
+
} catch (err) {
|
|
50618
|
+
console.warn(`[SecureChannel] A2A rekey failed:`, err);
|
|
50619
|
+
}
|
|
50620
|
+
}
|
|
50500
50621
|
/**
|
|
50501
50622
|
* Paginated sync: fetch missed messages in pages of 200, up to 5 pages (1000 messages).
|
|
50502
50623
|
* Tracks message IDs in _syncMessageIds to prevent duplicate processing from concurrent WS messages.
|
|
@@ -50917,6 +51038,8 @@ ${messageText}`;
|
|
|
50917
51038
|
const hasA2AChannels = !!this._persisted.a2aChannels && Object.keys(this._persisted.a2aChannels).length > 0;
|
|
50918
51039
|
if (!hasOwnerSessions && !hasA2AChannels) return;
|
|
50919
51040
|
this._persisted.lastInboundRoomId = this._lastInboundRoomId;
|
|
51041
|
+
this._persisted.seenMessageIds = [...this._seenMessageIds].slice(-_SecureChannel.SEEN_MSG_MAX);
|
|
51042
|
+
this._persisted.seenA2AMessageIds = [...this._a2aSeenMessageIds].slice(-_SecureChannel.A2A_SEEN_MAX);
|
|
50920
51043
|
for (const [convId, session] of this._sessions) {
|
|
50921
51044
|
this._persisted.sessions[convId] = {
|
|
50922
51045
|
ownerDeviceId: session.ownerDeviceId,
|