@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/index.js
CHANGED
|
@@ -44743,7 +44743,7 @@ var init_ratchet = __esm({
|
|
|
44743
44743
|
async "../crypto/dist/ratchet.js"() {
|
|
44744
44744
|
"use strict";
|
|
44745
44745
|
await init_libsodium_wrappers();
|
|
44746
|
-
MAX_SKIP =
|
|
44746
|
+
MAX_SKIP = 1e3;
|
|
44747
44747
|
CHAIN_KEY_SEED = new Uint8Array([1]);
|
|
44748
44748
|
MSG_KEY_SEED = new Uint8Array([2]);
|
|
44749
44749
|
HEADER_KEY_SEED = new Uint8Array([3]);
|
|
@@ -47236,6 +47236,12 @@ var init_channel = __esm({
|
|
|
47236
47236
|
this._primaryConversationId = this._persisted.primaryConversationId;
|
|
47237
47237
|
this._fingerprint = this._persisted.fingerprint;
|
|
47238
47238
|
this._lastInboundRoomId = this._persisted.lastInboundRoomId;
|
|
47239
|
+
if (this._persisted.seenMessageIds) {
|
|
47240
|
+
this._seenMessageIds = new Set(this._persisted.seenMessageIds.slice(-_SecureChannel.SEEN_MSG_MAX));
|
|
47241
|
+
}
|
|
47242
|
+
if (this._persisted.seenA2AMessageIds) {
|
|
47243
|
+
this._a2aSeenMessageIds = new Set(this._persisted.seenA2AMessageIds.slice(-_SecureChannel.A2A_SEEN_MAX));
|
|
47244
|
+
}
|
|
47239
47245
|
for (const [convId, sessionData] of Object.entries(
|
|
47240
47246
|
this._persisted.sessions
|
|
47241
47247
|
)) {
|
|
@@ -47244,7 +47250,8 @@ var init_channel = __esm({
|
|
|
47244
47250
|
this._sessions.set(convId, {
|
|
47245
47251
|
ownerDeviceId: sessionData.ownerDeviceId,
|
|
47246
47252
|
ratchet,
|
|
47247
|
-
activated: sessionData.activated ?? false
|
|
47253
|
+
activated: sessionData.activated ?? false,
|
|
47254
|
+
epoch: sessionData.epoch
|
|
47248
47255
|
});
|
|
47249
47256
|
}
|
|
47250
47257
|
}
|
|
@@ -47283,7 +47290,8 @@ var init_channel = __esm({
|
|
|
47283
47290
|
this._sessions.set(convId, {
|
|
47284
47291
|
ownerDeviceId: sd.ownerDeviceId,
|
|
47285
47292
|
ratchet: DoubleRatchet.deserialize(sd.ratchetState),
|
|
47286
|
-
activated: sd.activated ?? false
|
|
47293
|
+
activated: sd.activated ?? false,
|
|
47294
|
+
epoch: sd.epoch
|
|
47287
47295
|
});
|
|
47288
47296
|
}
|
|
47289
47297
|
}
|
|
@@ -47382,6 +47390,7 @@ var init_channel = __esm({
|
|
|
47382
47390
|
}
|
|
47383
47391
|
const messageGroupId = randomUUID2();
|
|
47384
47392
|
let sentCount = 0;
|
|
47393
|
+
const pendingWsSends = [];
|
|
47385
47394
|
for (const [convId, session] of this._sessions) {
|
|
47386
47395
|
if (!session.activated) continue;
|
|
47387
47396
|
if (roomConvIds.has(convId)) continue;
|
|
@@ -47414,7 +47423,7 @@ var init_channel = __esm({
|
|
|
47414
47423
|
if (this._persisted?.hubId) {
|
|
47415
47424
|
payload.sender_hub_id = this._persisted.hubId;
|
|
47416
47425
|
}
|
|
47417
|
-
|
|
47426
|
+
pendingWsSends.push(
|
|
47418
47427
|
JSON.stringify({
|
|
47419
47428
|
event: "message",
|
|
47420
47429
|
data: payload
|
|
@@ -47440,6 +47449,9 @@ var init_channel = __esm({
|
|
|
47440
47449
|
console.warn("[SecureChannel] send() delivered to 0 sessions (all skipped or failed)");
|
|
47441
47450
|
}
|
|
47442
47451
|
await this._persistState();
|
|
47452
|
+
for (const frame of pendingWsSends) {
|
|
47453
|
+
this._ws.send(frame);
|
|
47454
|
+
}
|
|
47443
47455
|
}
|
|
47444
47456
|
/**
|
|
47445
47457
|
* Send a typing indicator to all owner devices.
|
|
@@ -47619,13 +47631,15 @@ var init_channel = __esm({
|
|
|
47619
47631
|
this._sessions.set(conv.id, {
|
|
47620
47632
|
ownerDeviceId: otherDeviceId,
|
|
47621
47633
|
ratchet,
|
|
47622
|
-
activated: isInitiator
|
|
47634
|
+
activated: isInitiator,
|
|
47623
47635
|
// initiator can send immediately
|
|
47636
|
+
epoch: 1
|
|
47624
47637
|
});
|
|
47625
47638
|
this._persisted.sessions[conv.id] = {
|
|
47626
47639
|
ownerDeviceId: otherDeviceId,
|
|
47627
47640
|
ratchetState: ratchet.serialize(),
|
|
47628
|
-
activated: isInitiator
|
|
47641
|
+
activated: isInitiator,
|
|
47642
|
+
epoch: 1
|
|
47629
47643
|
};
|
|
47630
47644
|
conversationIds.push(conv.id);
|
|
47631
47645
|
console.log(
|
|
@@ -47718,6 +47732,7 @@ var init_channel = __esm({
|
|
|
47718
47732
|
if (recipients.length === 0) {
|
|
47719
47733
|
throw new Error("No active sessions in room");
|
|
47720
47734
|
}
|
|
47735
|
+
await this._persistState();
|
|
47721
47736
|
if (this._state === "ready" && this._ws) {
|
|
47722
47737
|
this._ws.send(
|
|
47723
47738
|
JSON.stringify({
|
|
@@ -47757,7 +47772,6 @@ var init_channel = __esm({
|
|
|
47757
47772
|
throw new Error(`Failed to send room message: ${err}`);
|
|
47758
47773
|
}
|
|
47759
47774
|
}
|
|
47760
|
-
await this._persistState();
|
|
47761
47775
|
}
|
|
47762
47776
|
/**
|
|
47763
47777
|
* Leave a room: remove sessions and persisted room state.
|
|
@@ -47856,11 +47870,12 @@ var init_channel = __esm({
|
|
|
47856
47870
|
attachment: attachMeta
|
|
47857
47871
|
});
|
|
47858
47872
|
const messageGroupId = randomUUID2();
|
|
47873
|
+
const pendingWsSends = [];
|
|
47859
47874
|
for (const [convId, session] of this._sessions) {
|
|
47860
47875
|
if (!session.activated) continue;
|
|
47861
47876
|
const encrypted = session.ratchet.encrypt(envelope);
|
|
47862
47877
|
const transport = encryptedMessageToTransport(encrypted);
|
|
47863
|
-
|
|
47878
|
+
pendingWsSends.push(
|
|
47864
47879
|
JSON.stringify({
|
|
47865
47880
|
event: "message",
|
|
47866
47881
|
data: {
|
|
@@ -47874,6 +47889,9 @@ var init_channel = __esm({
|
|
|
47874
47889
|
);
|
|
47875
47890
|
}
|
|
47876
47891
|
await this._persistState();
|
|
47892
|
+
for (const frame of pendingWsSends) {
|
|
47893
|
+
this._ws.send(frame);
|
|
47894
|
+
}
|
|
47877
47895
|
}
|
|
47878
47896
|
async sendActionConfirmation(confirmation) {
|
|
47879
47897
|
const envelope = {
|
|
@@ -48716,12 +48734,14 @@ var init_channel = __esm({
|
|
|
48716
48734
|
this._sessions.set(conv.conversation_id, {
|
|
48717
48735
|
ownerDeviceId: conv.owner_device_id,
|
|
48718
48736
|
ratchet,
|
|
48719
|
-
activated: false
|
|
48737
|
+
activated: false,
|
|
48720
48738
|
// Wait for owner's first message before sending to this session
|
|
48739
|
+
epoch: 1
|
|
48721
48740
|
});
|
|
48722
48741
|
sessions[conv.conversation_id] = {
|
|
48723
48742
|
ownerDeviceId: conv.owner_device_id,
|
|
48724
|
-
ratchetState: ratchet.serialize()
|
|
48743
|
+
ratchetState: ratchet.serialize(),
|
|
48744
|
+
epoch: 1
|
|
48725
48745
|
};
|
|
48726
48746
|
console.log(
|
|
48727
48747
|
`[SecureChannel] Session initialized for conv ${conv.conversation_id.slice(0, 8)}... (owner ${conv.owner_device_id.slice(0, 8)}..., primary=${conv.is_primary})`
|
|
@@ -49220,8 +49240,9 @@ var init_channel = __esm({
|
|
|
49220
49240
|
ownerDeviceId: "",
|
|
49221
49241
|
// A2A sessions don't have an owner device
|
|
49222
49242
|
ratchetState: ratchet.serialize(),
|
|
49223
|
-
activated: activatedRole === "initiator"
|
|
49243
|
+
activated: activatedRole === "initiator",
|
|
49224
49244
|
// initiator can send immediately
|
|
49245
|
+
epoch: 1
|
|
49225
49246
|
};
|
|
49226
49247
|
channelEntry.role = activatedRole;
|
|
49227
49248
|
if (convId) channelEntry.conversationId = convId;
|
|
@@ -49406,7 +49427,8 @@ var init_channel = __esm({
|
|
|
49406
49427
|
conversation_id: convId,
|
|
49407
49428
|
reason: "no_session",
|
|
49408
49429
|
identity_public_key: this._persisted.identityKeypair.publicKey,
|
|
49409
|
-
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey
|
|
49430
|
+
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey,
|
|
49431
|
+
epoch: this._persisted.sessions[convId]?.epoch
|
|
49410
49432
|
}
|
|
49411
49433
|
})
|
|
49412
49434
|
);
|
|
@@ -49445,7 +49467,8 @@ var init_channel = __esm({
|
|
|
49445
49467
|
conversation_id: convId,
|
|
49446
49468
|
reason: "decrypt_failure",
|
|
49447
49469
|
identity_public_key: idPubHex,
|
|
49448
|
-
ephemeral_public_key: ephPubHex
|
|
49470
|
+
ephemeral_public_key: ephPubHex,
|
|
49471
|
+
epoch: this._persisted?.sessions[convId]?.epoch
|
|
49449
49472
|
}
|
|
49450
49473
|
})
|
|
49451
49474
|
);
|
|
@@ -49690,11 +49713,12 @@ ${messageText}`;
|
|
|
49690
49713
|
});
|
|
49691
49714
|
this._appendHistory("agent", plaintext, topicId);
|
|
49692
49715
|
const messageGroupId = randomUUID2();
|
|
49716
|
+
const pendingWsSends = [];
|
|
49693
49717
|
for (const [convId, session] of this._sessions) {
|
|
49694
49718
|
if (!session.activated) continue;
|
|
49695
49719
|
const encrypted = session.ratchet.encrypt(envelope);
|
|
49696
49720
|
const transport = encryptedMessageToTransport(encrypted);
|
|
49697
|
-
|
|
49721
|
+
pendingWsSends.push(
|
|
49698
49722
|
JSON.stringify({
|
|
49699
49723
|
event: "message",
|
|
49700
49724
|
data: {
|
|
@@ -49708,6 +49732,9 @@ ${messageText}`;
|
|
|
49708
49732
|
);
|
|
49709
49733
|
}
|
|
49710
49734
|
await this._persistState();
|
|
49735
|
+
for (const frame of pendingWsSends) {
|
|
49736
|
+
this._ws.send(frame);
|
|
49737
|
+
}
|
|
49711
49738
|
}
|
|
49712
49739
|
/**
|
|
49713
49740
|
* Relay an owner's message to all sibling sessions as encrypted sync messages.
|
|
@@ -49730,13 +49757,14 @@ ${messageText}`;
|
|
|
49730
49757
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
49731
49758
|
topicId
|
|
49732
49759
|
});
|
|
49760
|
+
const pendingWsSends = [];
|
|
49733
49761
|
for (const [siblingConvId, siblingSession] of this._sessions) {
|
|
49734
49762
|
if (siblingConvId === sourceConvId) continue;
|
|
49735
49763
|
if (!siblingSession.activated) continue;
|
|
49736
49764
|
if (roomConvIds.has(siblingConvId)) continue;
|
|
49737
49765
|
const syncEncrypted = siblingSession.ratchet.encrypt(syncPayload);
|
|
49738
49766
|
const syncTransport = encryptedMessageToTransport(syncEncrypted);
|
|
49739
|
-
|
|
49767
|
+
pendingWsSends.push(
|
|
49740
49768
|
JSON.stringify({
|
|
49741
49769
|
event: "message",
|
|
49742
49770
|
data: {
|
|
@@ -49747,6 +49775,10 @@ ${messageText}`;
|
|
|
49747
49775
|
})
|
|
49748
49776
|
);
|
|
49749
49777
|
}
|
|
49778
|
+
await this._persistState();
|
|
49779
|
+
for (const frame of pendingWsSends) {
|
|
49780
|
+
this._ws.send(frame);
|
|
49781
|
+
}
|
|
49750
49782
|
}
|
|
49751
49783
|
/**
|
|
49752
49784
|
* Resolve the agent's workspace directory.
|
|
@@ -49790,6 +49822,7 @@ ${messageText}`;
|
|
|
49790
49822
|
const plaintext = JSON.stringify(payload);
|
|
49791
49823
|
const encrypted = session.ratchet.encrypt(plaintext);
|
|
49792
49824
|
const transport = encryptedMessageToTransport(encrypted);
|
|
49825
|
+
await this._persistState();
|
|
49793
49826
|
this._ws.send(
|
|
49794
49827
|
JSON.stringify({
|
|
49795
49828
|
event: "message",
|
|
@@ -49800,7 +49833,6 @@ ${messageText}`;
|
|
|
49800
49833
|
}
|
|
49801
49834
|
})
|
|
49802
49835
|
);
|
|
49803
|
-
await this._persistState();
|
|
49804
49836
|
}
|
|
49805
49837
|
/**
|
|
49806
49838
|
* Send stored message history to a newly-activated session.
|
|
@@ -49823,6 +49855,7 @@ ${messageText}`;
|
|
|
49823
49855
|
});
|
|
49824
49856
|
const encrypted = session.ratchet.encrypt(replayPayload);
|
|
49825
49857
|
const transport = encryptedMessageToTransport(encrypted);
|
|
49858
|
+
await this._persistState();
|
|
49826
49859
|
this._ws.send(
|
|
49827
49860
|
JSON.stringify({
|
|
49828
49861
|
event: "message",
|
|
@@ -49869,13 +49902,15 @@ ${messageText}`;
|
|
|
49869
49902
|
this._sessions.set(event.conversation_id, {
|
|
49870
49903
|
ownerDeviceId: event.owner_device_id,
|
|
49871
49904
|
ratchet,
|
|
49872
|
-
activated: false
|
|
49905
|
+
activated: false,
|
|
49873
49906
|
// Wait for owner's first message
|
|
49907
|
+
epoch: 1
|
|
49874
49908
|
});
|
|
49875
49909
|
this._persisted.sessions[event.conversation_id] = {
|
|
49876
49910
|
ownerDeviceId: event.owner_device_id,
|
|
49877
49911
|
ratchetState: ratchet.serialize(),
|
|
49878
|
-
activated: false
|
|
49912
|
+
activated: false,
|
|
49913
|
+
epoch: 1
|
|
49879
49914
|
};
|
|
49880
49915
|
if (event.owner_hub_id) {
|
|
49881
49916
|
this._persisted.ownerHubId = event.owner_hub_id;
|
|
@@ -49922,17 +49957,21 @@ ${messageText}`;
|
|
|
49922
49957
|
keyType: "ed25519"
|
|
49923
49958
|
}, hexToBytes(data.identity_public_key));
|
|
49924
49959
|
const existingSession = this._sessions.get(convId);
|
|
49960
|
+
const existingPersisted = this._persisted.sessions[convId];
|
|
49925
49961
|
const ownerDeviceId = data.sender_device_id ?? existingSession?.ownerDeviceId ?? "";
|
|
49962
|
+
const newEpoch = (existingPersisted?.epoch ?? existingSession?.epoch ?? 0) + 1;
|
|
49926
49963
|
this._sessions.set(convId, {
|
|
49927
49964
|
ownerDeviceId,
|
|
49928
49965
|
ratchet,
|
|
49929
|
-
activated: false
|
|
49966
|
+
activated: false,
|
|
49930
49967
|
// Wait for owner's encrypted session_init
|
|
49968
|
+
epoch: newEpoch
|
|
49931
49969
|
});
|
|
49932
49970
|
this._persisted.sessions[convId] = {
|
|
49933
49971
|
ownerDeviceId,
|
|
49934
49972
|
ratchetState: ratchet.serialize(),
|
|
49935
|
-
activated: false
|
|
49973
|
+
activated: false,
|
|
49974
|
+
epoch: newEpoch
|
|
49936
49975
|
};
|
|
49937
49976
|
await this._persistState();
|
|
49938
49977
|
if (this._ws) {
|
|
@@ -49942,7 +49981,8 @@ ${messageText}`;
|
|
|
49942
49981
|
data: {
|
|
49943
49982
|
conversation_id: convId,
|
|
49944
49983
|
identity_public_key: identity.publicKey,
|
|
49945
|
-
ephemeral_public_key: ephemeral.publicKey
|
|
49984
|
+
ephemeral_public_key: ephemeral.publicKey,
|
|
49985
|
+
epoch: newEpoch
|
|
49946
49986
|
}
|
|
49947
49987
|
})
|
|
49948
49988
|
);
|
|
@@ -49956,6 +49996,32 @@ ${messageText}`;
|
|
|
49956
49996
|
this.emit("error", err);
|
|
49957
49997
|
}
|
|
49958
49998
|
}
|
|
49999
|
+
/**
|
|
50000
|
+
* Send a resync_request for a room conversation if the cooldown has elapsed.
|
|
50001
|
+
* Shared by all room ratchet failure paths to avoid code duplication.
|
|
50002
|
+
*/
|
|
50003
|
+
_sendRoomResyncIfCooled(convId, reason, session) {
|
|
50004
|
+
const RESYNC_COOLDOWN_MS = 5 * 60 * 1e3;
|
|
50005
|
+
const lastResync = this._lastResyncRequest.get(convId) ?? 0;
|
|
50006
|
+
if (Date.now() - lastResync > RESYNC_COOLDOWN_MS && this._ws && this._persisted) {
|
|
50007
|
+
this._lastResyncRequest.set(convId, Date.now());
|
|
50008
|
+
this._ws.send(
|
|
50009
|
+
JSON.stringify({
|
|
50010
|
+
event: "resync_request",
|
|
50011
|
+
data: {
|
|
50012
|
+
conversation_id: convId,
|
|
50013
|
+
reason,
|
|
50014
|
+
identity_public_key: this._persisted.identityKeypair.publicKey,
|
|
50015
|
+
ephemeral_public_key: this._persisted.ephemeralKeypair.publicKey,
|
|
50016
|
+
epoch: session?.epoch
|
|
50017
|
+
}
|
|
50018
|
+
})
|
|
50019
|
+
);
|
|
50020
|
+
console.log(
|
|
50021
|
+
`[SecureChannel] Room resync requested for conv ${convId.slice(0, 8)} (${reason})`
|
|
50022
|
+
);
|
|
50023
|
+
}
|
|
50024
|
+
}
|
|
49959
50025
|
/**
|
|
49960
50026
|
* Handle an incoming room message. Finds the pairwise conversation
|
|
49961
50027
|
* for the sender, decrypts, and emits a room_message event.
|
|
@@ -50075,20 +50141,39 @@ ${messageText}`;
|
|
|
50075
50141
|
console.log(
|
|
50076
50142
|
`[SecureChannel] Room ratchet re-initialized for conv ${convId.slice(0, 8)}...`
|
|
50077
50143
|
);
|
|
50078
|
-
|
|
50079
|
-
|
|
50080
|
-
|
|
50081
|
-
|
|
50144
|
+
const incomingMsgNum = encrypted.header.messageNumber;
|
|
50145
|
+
if (incomingMsgNum <= 5) {
|
|
50146
|
+
try {
|
|
50147
|
+
plaintext = session.ratchet.decrypt(encrypted);
|
|
50148
|
+
session.activated = true;
|
|
50149
|
+
if (this._persisted.sessions[convId]) {
|
|
50150
|
+
this._persisted.sessions[convId].activated = true;
|
|
50151
|
+
}
|
|
50152
|
+
await this._persistState();
|
|
50153
|
+
console.log(
|
|
50154
|
+
`[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
|
|
50155
|
+
);
|
|
50156
|
+
} catch (retryErr) {
|
|
50157
|
+
console.warn(
|
|
50158
|
+
`[SecureChannel] Room re-init retry failed for conv ${convId.slice(0, 8)} (msgNum=${incomingMsgNum}):`,
|
|
50159
|
+
retryErr
|
|
50160
|
+
);
|
|
50161
|
+
this._sendRoomResyncIfCooled(convId, "room_reinit_retry_failed", session);
|
|
50162
|
+
return;
|
|
50163
|
+
}
|
|
50164
|
+
} else {
|
|
50165
|
+
console.log(
|
|
50166
|
+
`[SecureChannel] Room re-init: skipping message with msgNum=${incomingMsgNum} for conv ${convId.slice(0, 8)} (too far ahead for fresh ratchet)`
|
|
50167
|
+
);
|
|
50168
|
+
this._sendRoomResyncIfCooled(convId, "room_message_skip");
|
|
50169
|
+
return;
|
|
50082
50170
|
}
|
|
50083
|
-
await this._persistState();
|
|
50084
|
-
console.log(
|
|
50085
|
-
`[SecureChannel] Room session ${convId.slice(0, 8)}... re-activated after ratchet re-init`
|
|
50086
|
-
);
|
|
50087
50171
|
} catch (reinitErr) {
|
|
50088
50172
|
console.error(
|
|
50089
50173
|
`[SecureChannel] Room ratchet re-init failed for conv ${convId.slice(0, 8)}...:`,
|
|
50090
50174
|
reinitErr
|
|
50091
50175
|
);
|
|
50176
|
+
this._sendRoomResyncIfCooled(convId, "room_reinit_failed");
|
|
50092
50177
|
return;
|
|
50093
50178
|
}
|
|
50094
50179
|
}
|
|
@@ -50262,13 +50347,14 @@ ${messageText}`;
|
|
|
50262
50347
|
const dist = chain.getDistribution(this._deviceId);
|
|
50263
50348
|
const distJson = JSON.stringify(dist);
|
|
50264
50349
|
const alreadyDistributed = new Set(room.distributedTo ?? []);
|
|
50350
|
+
const pendingWsSends = [];
|
|
50265
50351
|
for (const convId of room.conversationIds) {
|
|
50266
50352
|
const session = this._sessions.get(convId);
|
|
50267
50353
|
if (!session || alreadyDistributed.has(session.ownerDeviceId)) continue;
|
|
50268
50354
|
const encrypted = session.ratchet.encrypt(distJson);
|
|
50269
50355
|
const transport = encryptedMessageToTransport(encrypted);
|
|
50270
50356
|
if (this._state === "ready" && this._ws) {
|
|
50271
|
-
|
|
50357
|
+
pendingWsSends.push(
|
|
50272
50358
|
JSON.stringify({
|
|
50273
50359
|
event: "sender_key_distribution",
|
|
50274
50360
|
data: {
|
|
@@ -50284,6 +50370,9 @@ ${messageText}`;
|
|
|
50284
50370
|
}
|
|
50285
50371
|
room.distributedTo = [...alreadyDistributed];
|
|
50286
50372
|
await this._persistState();
|
|
50373
|
+
for (const frame of pendingWsSends) {
|
|
50374
|
+
this._ws.send(frame);
|
|
50375
|
+
}
|
|
50287
50376
|
}
|
|
50288
50377
|
/**
|
|
50289
50378
|
* Handle an incoming sender_key_distribution event.
|
|
@@ -50481,8 +50570,10 @@ ${messageText}`;
|
|
|
50481
50570
|
try {
|
|
50482
50571
|
a2aPlaintext = ratchet.decrypt(encryptedMessage);
|
|
50483
50572
|
} catch (decryptErr) {
|
|
50484
|
-
console.error(`[SecureChannel] A2A decrypt failed \u2014 restoring ratchet
|
|
50573
|
+
console.error(`[SecureChannel] A2A decrypt failed \u2014 restoring ratchet, initiating rekey:`, decryptErr);
|
|
50485
50574
|
channelEntry.session.ratchetState = ratchetSnapshot;
|
|
50575
|
+
await this._persistState();
|
|
50576
|
+
await this._initiateA2ARekey(channelId);
|
|
50486
50577
|
return;
|
|
50487
50578
|
}
|
|
50488
50579
|
channelEntry.session.ratchetState = ratchet.serialize();
|
|
@@ -50524,24 +50615,9 @@ ${messageText}`;
|
|
|
50524
50615
|
}
|
|
50525
50616
|
} else {
|
|
50526
50617
|
console.warn(
|
|
50527
|
-
`[SecureChannel] Received encrypted A2A but no session for ${channelId.slice(0, 8)} \u2014
|
|
50618
|
+
`[SecureChannel] Received encrypted A2A but no session for ${channelId.slice(0, 8)} \u2014 initiating rekey`
|
|
50528
50619
|
);
|
|
50529
|
-
|
|
50530
|
-
if (entry && !entry.pendingEphemeralPrivateKey && !entry.session) {
|
|
50531
|
-
try {
|
|
50532
|
-
const a2aEphemeral = await generateEphemeralKeypair();
|
|
50533
|
-
const ephPubHex = bytesToHex(a2aEphemeral.publicKey);
|
|
50534
|
-
entry.pendingEphemeralPrivateKey = bytesToHex(a2aEphemeral.privateKey);
|
|
50535
|
-
this._ws?.send(JSON.stringify({
|
|
50536
|
-
event: "a2a_key_exchange",
|
|
50537
|
-
data: { channel_id: channelId, ephemeral_key: ephPubHex }
|
|
50538
|
-
}));
|
|
50539
|
-
await this._persistState();
|
|
50540
|
-
console.log(`[SecureChannel] On-demand A2A key exchange for ${channelId.slice(0, 8)}`);
|
|
50541
|
-
} catch (kxErr) {
|
|
50542
|
-
console.warn(`[SecureChannel] On-demand key exchange failed:`, kxErr);
|
|
50543
|
-
}
|
|
50544
|
-
}
|
|
50620
|
+
await this._initiateA2ARekey(channelId);
|
|
50545
50621
|
}
|
|
50546
50622
|
} else {
|
|
50547
50623
|
const a2aMsg = {
|
|
@@ -50558,6 +50634,51 @@ ${messageText}`;
|
|
|
50558
50634
|
}
|
|
50559
50635
|
}
|
|
50560
50636
|
}
|
|
50637
|
+
/**
|
|
50638
|
+
* Initiate A2A channel rekey after decrypt failure.
|
|
50639
|
+
* Calls the backend to reset channel to approved, clears local session,
|
|
50640
|
+
* and submits a fresh ephemeral key for key exchange.
|
|
50641
|
+
*/
|
|
50642
|
+
async _initiateA2ARekey(channelId) {
|
|
50643
|
+
const entry = this._persisted?.a2aChannels?.[channelId];
|
|
50644
|
+
if (!entry || !this._deviceJwt || !this._ws) return;
|
|
50645
|
+
const now = Date.now();
|
|
50646
|
+
const lastRekey = entry._lastRekeyAttempt ?? 0;
|
|
50647
|
+
if (now - lastRekey < 3e4) return;
|
|
50648
|
+
entry._lastRekeyAttempt = now;
|
|
50649
|
+
console.log(`[SecureChannel] Initiating A2A rekey for ${channelId.slice(0, 8)}...`);
|
|
50650
|
+
try {
|
|
50651
|
+
const res = await fetch(
|
|
50652
|
+
`${this.config.apiUrl}/api/v1/a2a/channels/${channelId}/rekey`,
|
|
50653
|
+
{
|
|
50654
|
+
method: "POST",
|
|
50655
|
+
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
50656
|
+
}
|
|
50657
|
+
);
|
|
50658
|
+
if (!res.ok) {
|
|
50659
|
+
const detail = await res.text();
|
|
50660
|
+
console.warn(`[SecureChannel] A2A rekey API failed (${res.status}): ${detail}`);
|
|
50661
|
+
return;
|
|
50662
|
+
}
|
|
50663
|
+
delete entry.session;
|
|
50664
|
+
delete entry.observerSession;
|
|
50665
|
+
delete entry.pendingEphemeralPrivateKey;
|
|
50666
|
+
delete entry.pendingObserverEphemeralPrivateKey;
|
|
50667
|
+
const a2aEphemeral = await generateEphemeralKeypair();
|
|
50668
|
+
entry.pendingEphemeralPrivateKey = bytesToHex(a2aEphemeral.privateKey);
|
|
50669
|
+
this._ws.send(JSON.stringify({
|
|
50670
|
+
event: "a2a_key_exchange",
|
|
50671
|
+
data: {
|
|
50672
|
+
channel_id: channelId,
|
|
50673
|
+
ephemeral_key: bytesToHex(a2aEphemeral.publicKey)
|
|
50674
|
+
}
|
|
50675
|
+
}));
|
|
50676
|
+
await this._persistState();
|
|
50677
|
+
console.log(`[SecureChannel] A2A rekey submitted for ${channelId.slice(0, 8)}`);
|
|
50678
|
+
} catch (err) {
|
|
50679
|
+
console.warn(`[SecureChannel] A2A rekey failed:`, err);
|
|
50680
|
+
}
|
|
50681
|
+
}
|
|
50561
50682
|
/**
|
|
50562
50683
|
* Paginated sync: fetch missed messages in pages of 200, up to 5 pages (1000 messages).
|
|
50563
50684
|
* Tracks message IDs in _syncMessageIds to prevent duplicate processing from concurrent WS messages.
|
|
@@ -50978,6 +51099,8 @@ ${messageText}`;
|
|
|
50978
51099
|
const hasA2AChannels = !!this._persisted.a2aChannels && Object.keys(this._persisted.a2aChannels).length > 0;
|
|
50979
51100
|
if (!hasOwnerSessions && !hasA2AChannels) return;
|
|
50980
51101
|
this._persisted.lastInboundRoomId = this._lastInboundRoomId;
|
|
51102
|
+
this._persisted.seenMessageIds = [...this._seenMessageIds].slice(-_SecureChannel.SEEN_MSG_MAX);
|
|
51103
|
+
this._persisted.seenA2AMessageIds = [...this._a2aSeenMessageIds].slice(-_SecureChannel.A2A_SEEN_MAX);
|
|
50981
51104
|
for (const [convId, session] of this._sessions) {
|
|
50982
51105
|
this._persisted.sessions[convId] = {
|
|
50983
51106
|
ownerDeviceId: session.ownerDeviceId,
|