@agentvault/muse-bridge 0.1.5 → 0.1.7
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/index.js +212 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -11465,11 +11465,11 @@ async function joinWithFirstMatchingKp(welcomeBytes, candidates) {
|
|
|
11465
11465
|
return null;
|
|
11466
11466
|
}
|
|
11467
11467
|
function ownIdentity() {
|
|
11468
|
-
const v2 = true ? "0.23.
|
|
11468
|
+
const v2 = true ? "0.23.32" : FALLBACK;
|
|
11469
11469
|
return `${PACKAGE}@${v2}`;
|
|
11470
11470
|
}
|
|
11471
11471
|
function buildSha() {
|
|
11472
|
-
const raw = true ? "
|
|
11472
|
+
const raw = true ? "564f8b1-dirty" : "";
|
|
11473
11473
|
const cleaned = (raw ?? "").trim();
|
|
11474
11474
|
return !cleaned || cleaned === "unknown" ? null : cleaned;
|
|
11475
11475
|
}
|
|
@@ -68283,6 +68283,8 @@ var init_dist = __esm({
|
|
|
68283
68283
|
continue;
|
|
68284
68284
|
seenIdentities.push(identity);
|
|
68285
68285
|
const leaf = this.memberLeafIndex(identity);
|
|
68286
|
+
if (leaf !== null && leaf === this.ownLeafIndex())
|
|
68287
|
+
continue;
|
|
68286
68288
|
if (leaf !== null)
|
|
68287
68289
|
leavesToRemove.push(leaf);
|
|
68288
68290
|
}
|
|
@@ -68338,6 +68340,20 @@ var init_dist = __esm({
|
|
|
68338
68340
|
* then re-adds it with a fresh KeyPackage so it gets a Welcome at the
|
|
68339
68341
|
* current epoch it can actually apply.
|
|
68340
68342
|
*/
|
|
68343
|
+
/**
|
|
68344
|
+
* The leaf index THIS manager occupies — the leaf whose private key it holds.
|
|
68345
|
+
*
|
|
68346
|
+
* Read from `privatePath.leafIndex`, which is the same value ts-mls uses to
|
|
68347
|
+
* decide whether a commit removes its own committer, so a caller checking
|
|
68348
|
+
* against this cannot disagree with the library about who "we" are.
|
|
68349
|
+
*
|
|
68350
|
+
* Exists so a fulfiller can tell a request from ANOTHER device apart from one
|
|
68351
|
+
* from itself (#1147) without any "who am I" plumbing: the manager already
|
|
68352
|
+
* knows, and a guard that reads it here cannot be forgotten by a call site.
|
|
68353
|
+
*/
|
|
68354
|
+
ownLeafIndex() {
|
|
68355
|
+
return this._requireState().privatePath.leafIndex;
|
|
68356
|
+
}
|
|
68341
68357
|
memberLeafIndex(identity) {
|
|
68342
68358
|
const s2 = this._requireState();
|
|
68343
68359
|
const tree = s2.ratchetTree;
|
|
@@ -69702,6 +69718,118 @@ var init_dist = __esm({
|
|
|
69702
69718
|
* Encrypt and send a message to ALL owner devices (fanout).
|
|
69703
69719
|
* Each session gets the same plaintext encrypted independently.
|
|
69704
69720
|
*/
|
|
69721
|
+
/**
|
|
69722
|
+
* #952 — sends held because MLS is expected for this conversation but the
|
|
69723
|
+
* group is not joined yet.
|
|
69724
|
+
*
|
|
69725
|
+
* ⚠️ IN MEMORY ONLY. These entries hold PLAINTEXT. `_persisted` is written to
|
|
69726
|
+
* disk, and the product's entire premise is that plaintext does not rest
|
|
69727
|
+
* where it does not have to; a crash losing a held greeting is the correct
|
|
69728
|
+
* trade. This is why the existing `outboundQueue` could not be reused — that
|
|
69729
|
+
* one is persisted, and holds already-DR-encrypted bytes, which is exactly
|
|
69730
|
+
* the encryption we are trying not to perform yet.
|
|
69731
|
+
*/
|
|
69732
|
+
_mlsHold = /* @__PURE__ */ new Map();
|
|
69733
|
+
/** How long a send waits for the Welcome. The two measured bursts were each
|
|
69734
|
+
* confined to a single minute, with the group already existing server-side
|
|
69735
|
+
* in the same second, so this is generous rather than tight. */
|
|
69736
|
+
static MLS_HOLD_MS = 3e4;
|
|
69737
|
+
/** Bound on held sends per group — see `_holdForMls`. */
|
|
69738
|
+
static MLS_HOLD_MAX = 50;
|
|
69739
|
+
/**
|
|
69740
|
+
* Should this 1:1 send wait for MLS rather than go out over Double Ratchet?
|
|
69741
|
+
*
|
|
69742
|
+
* The discriminator exists at the moment of the bug: `_sessionGroupIds`
|
|
69743
|
+
* already carries the conversation's group id — the server told us the group
|
|
69744
|
+
* exists — while `_mlsGroups` has no usable manager for it. Group expected,
|
|
69745
|
+
* group not ready.
|
|
69746
|
+
*
|
|
69747
|
+
* The negative case is load-bearing. A conversation with NO group id is
|
|
69748
|
+
* legitimately Double Ratchet, and holding it would break owner-to-agent
|
|
69749
|
+
* messaging outright, which is far worse than the bug this fixes. "Present"
|
|
69750
|
+
* is also not "ready": a manager parked at epoch 0 has not joined (#1092),
|
|
69751
|
+
* and reading presence as readiness is how #991 shipped.
|
|
69752
|
+
*/
|
|
69753
|
+
_shouldHoldForMls(convGroupId, mlsGroup, mlsGroupId) {
|
|
69754
|
+
if (!convGroupId) return false;
|
|
69755
|
+
const ready = Boolean(mlsGroup?.isInitialized) && Boolean(mlsGroupId) && Number(mlsGroup?.epoch) > 0;
|
|
69756
|
+
return !ready;
|
|
69757
|
+
}
|
|
69758
|
+
/** Park a send until the Welcome lands, or until the hold expires. */
|
|
69759
|
+
_holdForMls(convGroupId, convId, plaintext, options) {
|
|
69760
|
+
let entry = this._mlsHold.get(convGroupId);
|
|
69761
|
+
if (!entry) {
|
|
69762
|
+
const timer = setTimeout(() => {
|
|
69763
|
+
void this._releaseHeldOverDr(convGroupId);
|
|
69764
|
+
}, _SecureChannel.MLS_HOLD_MS);
|
|
69765
|
+
timer.unref?.();
|
|
69766
|
+
entry = { items: [], timer };
|
|
69767
|
+
this._mlsHold.set(convGroupId, entry);
|
|
69768
|
+
}
|
|
69769
|
+
if (entry.items.length >= _SecureChannel.MLS_HOLD_MAX) {
|
|
69770
|
+
entry.items.shift();
|
|
69771
|
+
console.warn(
|
|
69772
|
+
`[SecureChannel] MLS hold full for group ${convGroupId.slice(0, 8)} \u2014 dropped the oldest held message`
|
|
69773
|
+
);
|
|
69774
|
+
}
|
|
69775
|
+
entry.items.push({ convId, plaintext, options });
|
|
69776
|
+
console.log(
|
|
69777
|
+
`[SecureChannel] Holding send for group ${convGroupId.slice(0, 8)} until its Welcome lands (held=${entry.items.length})`
|
|
69778
|
+
);
|
|
69779
|
+
}
|
|
69780
|
+
_takeHold(convGroupId) {
|
|
69781
|
+
const entry = this._mlsHold.get(convGroupId);
|
|
69782
|
+
if (!entry) return null;
|
|
69783
|
+
clearTimeout(entry.timer);
|
|
69784
|
+
this._mlsHold.delete(convGroupId);
|
|
69785
|
+
return entry;
|
|
69786
|
+
}
|
|
69787
|
+
/** Re-drive one held send through the normal path, now that MLS is ready. */
|
|
69788
|
+
async _resendHeld(item) {
|
|
69789
|
+
await this.send(item.plaintext, {
|
|
69790
|
+
...item.options ?? {},
|
|
69791
|
+
conversationId: item.convId,
|
|
69792
|
+
bypassMlsHold: true
|
|
69793
|
+
});
|
|
69794
|
+
}
|
|
69795
|
+
/** The Welcome landed — release everything held for this group, in order. */
|
|
69796
|
+
async _flushMlsHold(convGroupId) {
|
|
69797
|
+
const entry = this._takeHold(convGroupId);
|
|
69798
|
+
if (!entry) return;
|
|
69799
|
+
console.log(
|
|
69800
|
+
`[SecureChannel] Welcome landed for group ${convGroupId.slice(0, 8)} \u2014 releasing ${entry.items.length} held message(s) over MLS`
|
|
69801
|
+
);
|
|
69802
|
+
for (const item of entry.items) {
|
|
69803
|
+
try {
|
|
69804
|
+
await this._resendHeld(item);
|
|
69805
|
+
} catch (err) {
|
|
69806
|
+
console.warn("[SecureChannel] Held message failed to send after Welcome:", err);
|
|
69807
|
+
}
|
|
69808
|
+
}
|
|
69809
|
+
}
|
|
69810
|
+
/**
|
|
69811
|
+
* The hold expired. Send over Double Ratchet exactly as before.
|
|
69812
|
+
*
|
|
69813
|
+
* This is deliberately NOT a drop. #853 and #831 are Welcomes that never
|
|
69814
|
+
* arrive; under those an unbounded hold turns a lost minute into an agent
|
|
69815
|
+
* that never speaks again. The hold removes the race — it must not invent a
|
|
69816
|
+
* new failure mode. The warning is the part that is new: today the loss is
|
|
69817
|
+
* silent.
|
|
69818
|
+
*/
|
|
69819
|
+
async _releaseHeldOverDr(convGroupId) {
|
|
69820
|
+
const entry = this._takeHold(convGroupId);
|
|
69821
|
+
if (!entry) return;
|
|
69822
|
+
console.warn(
|
|
69823
|
+
`[SecureChannel] No Welcome for group ${convGroupId.slice(0, 8)} within ${_SecureChannel.MLS_HOLD_MS}ms \u2014 releasing ${entry.items.length} message(s) over Double Ratchet. The recipient may not hold a ratchet with this device, in which case they are undeliverable (#952).`
|
|
69824
|
+
);
|
|
69825
|
+
for (const item of entry.items) {
|
|
69826
|
+
try {
|
|
69827
|
+
await this._resendHeld(item);
|
|
69828
|
+
} catch (err) {
|
|
69829
|
+
console.warn("[SecureChannel] Held message failed on Double Ratchet release:", err);
|
|
69830
|
+
}
|
|
69831
|
+
}
|
|
69832
|
+
}
|
|
69705
69833
|
async send(plaintext, options) {
|
|
69706
69834
|
if (isAbstention(plaintext)) return;
|
|
69707
69835
|
if (this._state === "error" || this._state === "idle") {
|
|
@@ -69863,6 +69991,10 @@ var init_dist = __esm({
|
|
|
69863
69991
|
);
|
|
69864
69992
|
addressedMlsGroupIds.push(mlsGroupId);
|
|
69865
69993
|
if (convGroupId) sentSharedGroupIds.add(convGroupId);
|
|
69994
|
+
} else if (!options?.bypassMlsHold && this._shouldHoldForMls(convGroupId, mlsGroup, mlsGroupId)) {
|
|
69995
|
+
this._holdForMls(convGroupId, convId, plaintext, options);
|
|
69996
|
+
sentCount++;
|
|
69997
|
+
continue;
|
|
69866
69998
|
} else {
|
|
69867
69999
|
const encrypted = session.ratchet.encrypt(plaintext);
|
|
69868
70000
|
const transport = encryptedMessageToTransport(encrypted);
|
|
@@ -70032,7 +70164,7 @@ var init_dist = __esm({
|
|
|
70032
70164
|
*/
|
|
70033
70165
|
sendActivitySpan(spanData) {
|
|
70034
70166
|
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
|
|
70035
|
-
const pluginVersion = true ? "0.23.
|
|
70167
|
+
const pluginVersion = true ? "0.23.32" : "0.0.0-dev";
|
|
70036
70168
|
const agentName = this.config.agentName ?? "Agent";
|
|
70037
70169
|
const resource = {
|
|
70038
70170
|
"service.name": "agentvault-agent",
|
|
@@ -71970,7 +72102,7 @@ var init_dist = __esm({
|
|
|
71970
72102
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
71971
72103
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
71972
72104
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
71973
|
-
pluginVersion: true ? "0.23.
|
|
72105
|
+
pluginVersion: true ? "0.23.32" : "0.0.0-dev"
|
|
71974
72106
|
});
|
|
71975
72107
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
71976
72108
|
}
|
|
@@ -72294,7 +72426,7 @@ var init_dist = __esm({
|
|
|
72294
72426
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
72295
72427
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
72296
72428
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
72297
|
-
pluginVersion: true ? "0.23.
|
|
72429
|
+
pluginVersion: true ? "0.23.32" : "0.0.0-dev"
|
|
72298
72430
|
});
|
|
72299
72431
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
72300
72432
|
}
|
|
@@ -72710,11 +72842,13 @@ var init_dist = __esm({
|
|
|
72710
72842
|
let text = plaintext;
|
|
72711
72843
|
let messageType = "text";
|
|
72712
72844
|
let topicId = data.topic_id;
|
|
72845
|
+
let mlsAttachment = null;
|
|
72713
72846
|
try {
|
|
72714
72847
|
const parsed = JSON.parse(plaintext);
|
|
72715
72848
|
text = parsed.text ?? plaintext;
|
|
72716
72849
|
messageType = parsed.type ?? "text";
|
|
72717
72850
|
topicId = parsed.topicId ?? topicId;
|
|
72851
|
+
if (parsed.attachment) mlsAttachment = parsed.attachment;
|
|
72718
72852
|
} catch {
|
|
72719
72853
|
}
|
|
72720
72854
|
if (messageType === "history_catchup_request") {
|
|
@@ -72781,6 +72915,9 @@ var init_dist = __esm({
|
|
|
72781
72915
|
console.log(`[SecureChannel] Session ${convId.slice(0, 8)} activated via MLS message`);
|
|
72782
72916
|
}
|
|
72783
72917
|
}
|
|
72918
|
+
if (mlsAttachment) {
|
|
72919
|
+
text = await this._augmentWithAttachment(mlsAttachment, text);
|
|
72920
|
+
}
|
|
72784
72921
|
const metadata = {
|
|
72785
72922
|
messageId: data.message_id ?? `mls_${Date.now()}`,
|
|
72786
72923
|
conversationId: convId ?? this._primaryConversationId,
|
|
@@ -73030,10 +73167,70 @@ ${messageText}`;
|
|
|
73030
73167
|
* Download an encrypted attachment blob, decrypt it, verify integrity,
|
|
73031
73168
|
* and save the plaintext file to disk.
|
|
73032
73169
|
*/
|
|
73170
|
+
/** Where an attachment may be written — always directly inside `attachDir` (#1143).
|
|
73171
|
+
*
|
|
73172
|
+
* The filename arrives INSIDE the envelope and is chosen by whoever sent the
|
|
73173
|
+
* message. `join(attachDir, info.filename)` resolves `..` normally, so
|
|
73174
|
+
* `../../agentvault.json` wrote over the device credential store. Only the
|
|
73175
|
+
* final path component is honoured, and anything that is not a usable name
|
|
73176
|
+
* falls back to a fixed one.
|
|
73177
|
+
*/
|
|
73178
|
+
_safeAttachmentPath(attachDir, filename) {
|
|
73179
|
+
const raw = String(filename ?? "");
|
|
73180
|
+
const last = raw.split(/[\\/]/).pop() ?? "";
|
|
73181
|
+
const cleaned = last.replace(/\0/g, "").trim();
|
|
73182
|
+
const safe = cleaned === "" || cleaned === "." || cleaned === ".." ? "attachment.bin" : cleaned;
|
|
73183
|
+
return join42(attachDir, safe);
|
|
73184
|
+
}
|
|
73185
|
+
/** Download an attachment and fold it into the text the agent sees (#1139).
|
|
73186
|
+
*
|
|
73187
|
+
* Extracted so the MLS receive path can use it too. It used to live inline in
|
|
73188
|
+
* `_handleIncomingMessage`, which meant attachments worked on the Double
|
|
73189
|
+
* Ratchet path ONLY — and hermes drops DR by design (`secure_channel.py:594`,
|
|
73190
|
+
* "DR fallback is logged-and-dropped"), so no single transport could serve
|
|
73191
|
+
* both client families. That is why an attachment reached ohtani and never
|
|
73192
|
+
* reached smartcoder2 on 2026-08-31.
|
|
73193
|
+
*
|
|
73194
|
+
* Returns the text unchanged when there is no attachment or the fetch fails —
|
|
73195
|
+
* but a failure is LOGGED and announced in the text, never silent.
|
|
73196
|
+
*/
|
|
73197
|
+
async _augmentWithAttachment(attachmentInfo, baseText) {
|
|
73198
|
+
if (!attachmentInfo) return baseText;
|
|
73199
|
+
try {
|
|
73200
|
+
const { filePath, decrypted } = await this._downloadAndDecryptAttachment(attachmentInfo);
|
|
73201
|
+
const mime = String(attachmentInfo.mime ?? "application/octet-stream");
|
|
73202
|
+
const name = String(attachmentInfo.filename ?? "attachment");
|
|
73203
|
+
const textMimes = ["text/", "application/json", "application/xml", "application/csv"];
|
|
73204
|
+
if (textMimes.some((m2) => mime.startsWith(m2))) {
|
|
73205
|
+
const content = new TextDecoder().decode(decrypted);
|
|
73206
|
+
return `[Attachment: ${name} (${mime}) saved to ${filePath}]
|
|
73207
|
+
---
|
|
73208
|
+
${content}
|
|
73209
|
+
---
|
|
73210
|
+
|
|
73211
|
+
${baseText}`;
|
|
73212
|
+
}
|
|
73213
|
+
if (mime.startsWith("image/")) {
|
|
73214
|
+
return `[Image attachment: ${name} saved to ${filePath}]
|
|
73215
|
+
Use your Read tool to view this image file.
|
|
73216
|
+
|
|
73217
|
+
${baseText}`;
|
|
73218
|
+
}
|
|
73219
|
+
return `[Attachment: ${name} saved to ${filePath}]
|
|
73220
|
+
|
|
73221
|
+
${baseText}`;
|
|
73222
|
+
} catch (err) {
|
|
73223
|
+
console.error("[SecureChannel] Failed to download attachment:", err);
|
|
73224
|
+
return `[Attachment: ${String(attachmentInfo.filename ?? "file")} was sent but could not be retrieved: ${err}]
|
|
73225
|
+
|
|
73226
|
+
${baseText}`;
|
|
73227
|
+
}
|
|
73228
|
+
}
|
|
73033
73229
|
async _downloadAndDecryptAttachment(info) {
|
|
73034
73230
|
const attachDir = join42(this.config.dataDir, "attachments");
|
|
73035
73231
|
await mkdir3(attachDir, { recursive: true });
|
|
73036
|
-
const
|
|
73232
|
+
const sep2 = info.blobUrl.includes("?") ? "&" : "?";
|
|
73233
|
+
const url22 = `${this.config.apiUrl}${info.blobUrl}${sep2}device_id=${encodeURIComponent(this._deviceId ?? "")}`;
|
|
73037
73234
|
const res = await fetch(url22, {
|
|
73038
73235
|
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
73039
73236
|
});
|
|
@@ -73049,7 +73246,7 @@ ${messageText}`;
|
|
|
73049
73246
|
const fileKey = base64ToBytes(info.fileKey);
|
|
73050
73247
|
const fileNonce = base64ToBytes(info.fileNonce);
|
|
73051
73248
|
const decrypted = decryptFile(encryptedData, fileKey, fileNonce);
|
|
73052
|
-
const filePath =
|
|
73249
|
+
const filePath = this._safeAttachmentPath(attachDir, info.filename);
|
|
73053
73250
|
await writeFile3(filePath, decrypted);
|
|
73054
73251
|
console.log(`[SecureChannel] Attachment saved: ${filePath} (${decrypted.length} bytes)`);
|
|
73055
73252
|
return { filePath, decrypted };
|
|
@@ -73585,10 +73782,12 @@ ${messageText}`;
|
|
|
73585
73782
|
const rawPlaintext = new TextDecoder().decode(result.plaintext);
|
|
73586
73783
|
let messageText;
|
|
73587
73784
|
let messageType;
|
|
73785
|
+
let roomAttachment = null;
|
|
73588
73786
|
try {
|
|
73589
73787
|
const parsed = JSON.parse(rawPlaintext);
|
|
73590
73788
|
messageType = parsed.type || "message";
|
|
73591
73789
|
messageText = parsed.text || rawPlaintext;
|
|
73790
|
+
if (parsed.attachment) roomAttachment = parsed.attachment;
|
|
73592
73791
|
} catch {
|
|
73593
73792
|
messageType = "message";
|
|
73594
73793
|
messageText = rawPlaintext;
|
|
@@ -73628,6 +73827,9 @@ ${messageText}`;
|
|
|
73628
73827
|
await this._refreshRoomRoster(resolvedRoomId);
|
|
73629
73828
|
}
|
|
73630
73829
|
}
|
|
73830
|
+
if (roomAttachment) {
|
|
73831
|
+
messageText = await this._augmentWithAttachment(roomAttachment, messageText);
|
|
73832
|
+
}
|
|
73631
73833
|
const roomMembers = this._persisted?.rooms?.[resolvedRoomId]?.members ?? [];
|
|
73632
73834
|
const senderMember = roomMembers.find((m2) => m2.deviceId === senderDeviceId);
|
|
73633
73835
|
const senderIsAgent = senderMember?.entityType === "agent";
|
|
@@ -74470,6 +74672,7 @@ ${messageText}`;
|
|
|
74470
74672
|
await this._persistState();
|
|
74471
74673
|
}
|
|
74472
74674
|
console.log(`[SecureChannel] Joined shared 1:1 MLS group for group ${conversationGroupId.slice(0, 8)} via Welcome (epoch=${mgr.epoch})`);
|
|
74675
|
+
await this._flushMlsHold(conversationGroupId);
|
|
74473
74676
|
return;
|
|
74474
74677
|
}
|
|
74475
74678
|
if (conversationId) {
|
|
@@ -97536,7 +97739,7 @@ data:
|
|
|
97536
97739
|
await init_skill_telemetry();
|
|
97537
97740
|
await init_policy_enforcer();
|
|
97538
97741
|
init_room_protocol();
|
|
97539
|
-
VERSION = true ? "0.23.
|
|
97742
|
+
VERSION = true ? "0.23.32" : "0.0.0-dev";
|
|
97540
97743
|
}
|
|
97541
97744
|
});
|
|
97542
97745
|
await init_index();
|
|
@@ -98912,7 +99115,7 @@ function mkdir700(path2) {
|
|
|
98912
99115
|
chmodSync5(path2, 448);
|
|
98913
99116
|
}
|
|
98914
99117
|
function packageVersion() {
|
|
98915
|
-
return true ? "0.1.
|
|
99118
|
+
return true ? "0.1.7" : void 0;
|
|
98916
99119
|
}
|
|
98917
99120
|
function helpText(version2) {
|
|
98918
99121
|
return [
|
package/package.json
CHANGED