@agentvault/claude-bridge 0.7.5 → 0.7.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/bridge.d.ts +3 -0
- package/dist/index.js +180 -71
- package/dist/worker-queue.d.ts +28 -3
- package/package.json +2 -2
package/dist/bridge.d.ts
CHANGED
|
@@ -34,6 +34,9 @@ export interface ArmingSnapshot {
|
|
|
34
34
|
* its ABSENCE is how we identify a true owner↔agent 1:1 DM. */
|
|
35
35
|
export interface MessageMeta {
|
|
36
36
|
roomId?: string;
|
|
37
|
+
/** #621 — set by SecureChannel when the message decrypted under the owner's
|
|
38
|
+
* 1:1 material. Gates the full-access DM lane; see the `message` handler. */
|
|
39
|
+
ownerDm1to1?: boolean;
|
|
37
40
|
}
|
|
38
41
|
/** #392 room hush (advisory) — emitted by SecureChannel from the backend's
|
|
39
42
|
* `room_hushed` event. ``hushedUntil`` is an ISO string, or null when cleared. */
|
package/dist/index.js
CHANGED
|
@@ -452,12 +452,13 @@ import { join as join11 } from "node:path";
|
|
|
452
452
|
|
|
453
453
|
// ../plugin/dist/index.js
|
|
454
454
|
import * as nc from "node:crypto";
|
|
455
|
-
import {
|
|
455
|
+
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
456
|
+
import { readdir, readFile, rename, rm } from "node:fs/promises";
|
|
456
457
|
import { join } from "node:path";
|
|
457
|
-
import {
|
|
458
|
+
import { readFile as readFile2, rename as rename2, rm as rm2 } from "node:fs/promises";
|
|
458
459
|
import { join as join2 } from "node:path";
|
|
459
460
|
import { Readable } from "node:stream";
|
|
460
|
-
import { readdir, readFile as readFile3, writeFile as
|
|
461
|
+
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile2, rename as rename3, stat, unlink, mkdir as mkdir2 } from "node:fs/promises";
|
|
461
462
|
import { join as join3 } from "node:path";
|
|
462
463
|
import { randomUUID } from "node:crypto";
|
|
463
464
|
import { EventEmitter } from "node:events";
|
|
@@ -465,7 +466,7 @@ import { createServer } from "node:http";
|
|
|
465
466
|
import { randomUUID as randomUUID2, createHash } from "node:crypto";
|
|
466
467
|
import { readFileSync } from "node:fs";
|
|
467
468
|
import { homedir as osHomedir } from "node:os";
|
|
468
|
-
import { writeFile as
|
|
469
|
+
import { writeFile as writeFile3, mkdir as mkdir3 } from "node:fs/promises";
|
|
469
470
|
import { join as join4 } from "node:path";
|
|
470
471
|
import { readFile as readFile4 } from "node:fs/promises";
|
|
471
472
|
import WebSocket2 from "ws";
|
|
@@ -63664,16 +63665,39 @@ var init_dist = __esm2({
|
|
|
63664
63665
|
init_mls_delivery_order();
|
|
63665
63666
|
}
|
|
63666
63667
|
});
|
|
63668
|
+
async function ensureSecureDir(dir) {
|
|
63669
|
+
await mkdir(dir, { recursive: true, mode: DIR_MODE });
|
|
63670
|
+
try {
|
|
63671
|
+
await chmod(dir, DIR_MODE);
|
|
63672
|
+
} catch {
|
|
63673
|
+
}
|
|
63674
|
+
}
|
|
63675
|
+
async function writeSecureFile(filePath, data, flag) {
|
|
63676
|
+
await writeFile(filePath, data, { encoding: "utf-8", mode: FILE_MODE, ...flag ? { flag } : {} });
|
|
63677
|
+
try {
|
|
63678
|
+
await chmod(filePath, FILE_MODE);
|
|
63679
|
+
} catch {
|
|
63680
|
+
}
|
|
63681
|
+
}
|
|
63682
|
+
var DIR_MODE;
|
|
63683
|
+
var FILE_MODE;
|
|
63684
|
+
var init_secure_file = __esm2({
|
|
63685
|
+
"src/secure-file.ts"() {
|
|
63686
|
+
"use strict";
|
|
63687
|
+
DIR_MODE = 448;
|
|
63688
|
+
FILE_MODE = 384;
|
|
63689
|
+
}
|
|
63690
|
+
});
|
|
63667
63691
|
function syncLockPath(dataDir, channelId) {
|
|
63668
63692
|
const safe = channelId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
63669
63693
|
return join(dataDir, `mls-a2a-sync-${safe}.lock`);
|
|
63670
63694
|
}
|
|
63671
63695
|
async function acquireA2ASyncLock(dataDir, channelId) {
|
|
63672
|
-
await
|
|
63696
|
+
await ensureSecureDir(dataDir);
|
|
63673
63697
|
const lockFile = syncLockPath(dataDir, channelId);
|
|
63674
63698
|
const content = JSON.stringify({ pid: process.pid, timestamp: Date.now() });
|
|
63675
63699
|
try {
|
|
63676
|
-
await
|
|
63700
|
+
await writeSecureFile(lockFile, content, "wx");
|
|
63677
63701
|
return true;
|
|
63678
63702
|
} catch (err) {
|
|
63679
63703
|
if (err.code !== "EEXIST") throw err;
|
|
@@ -63684,7 +63708,7 @@ async function acquireA2ASyncLock(dataDir, channelId) {
|
|
|
63684
63708
|
await rm(lockFile).catch(() => {
|
|
63685
63709
|
});
|
|
63686
63710
|
try {
|
|
63687
|
-
await
|
|
63711
|
+
await writeSecureFile(lockFile, content, "wx");
|
|
63688
63712
|
return true;
|
|
63689
63713
|
} catch {
|
|
63690
63714
|
return false;
|
|
@@ -63714,7 +63738,7 @@ async function hasPendingWelcome(dataDir, groupId) {
|
|
|
63714
63738
|
}
|
|
63715
63739
|
}
|
|
63716
63740
|
async function savePendingKpBundle(dataDir, groupId, kp, serializePublicFn) {
|
|
63717
|
-
await
|
|
63741
|
+
await ensureSecureDir(dataDir);
|
|
63718
63742
|
const serialized = {
|
|
63719
63743
|
publicPackageBytes: Buffer.from(serializePublicFn(kp.publicPackage)).toString("base64"),
|
|
63720
63744
|
privatePackage: {
|
|
@@ -63723,7 +63747,7 @@ async function savePendingKpBundle(dataDir, groupId, kp, serializePublicFn) {
|
|
|
63723
63747
|
signaturePrivateKey: Buffer.from(kp.privatePackage.signaturePrivateKey).toString("base64")
|
|
63724
63748
|
}
|
|
63725
63749
|
};
|
|
63726
|
-
await
|
|
63750
|
+
await writeSecureFile(pendingKpPath(dataDir, groupId), JSON.stringify(serialized));
|
|
63727
63751
|
}
|
|
63728
63752
|
async function loadPendingKpBundle(dataDir, groupId) {
|
|
63729
63753
|
try {
|
|
@@ -63748,9 +63772,9 @@ async function clearPendingWelcome(dataDir, groupId) {
|
|
|
63748
63772
|
}
|
|
63749
63773
|
}
|
|
63750
63774
|
async function saveMlsState(dataDir, groupId, state) {
|
|
63751
|
-
await
|
|
63775
|
+
await ensureSecureDir(dataDir);
|
|
63752
63776
|
const filePath = join(dataDir, mlsFileName(groupId));
|
|
63753
|
-
await
|
|
63777
|
+
await writeSecureFile(filePath, state);
|
|
63754
63778
|
}
|
|
63755
63779
|
async function loadMlsState(dataDir, groupId) {
|
|
63756
63780
|
try {
|
|
@@ -63767,15 +63791,12 @@ async function deleteMlsState(dataDir, groupId) {
|
|
|
63767
63791
|
} catch {
|
|
63768
63792
|
}
|
|
63769
63793
|
}
|
|
63770
|
-
var FILE_MODE;
|
|
63771
|
-
var DIR_MODE;
|
|
63772
63794
|
var SYNC_LOCK_STALE_MS;
|
|
63773
63795
|
var pendingKpPath;
|
|
63774
63796
|
var init_mls_state = __esm2({
|
|
63775
63797
|
"src/mls-state.ts"() {
|
|
63776
63798
|
"use strict";
|
|
63777
|
-
|
|
63778
|
-
DIR_MODE = 448;
|
|
63799
|
+
init_secure_file();
|
|
63779
63800
|
SYNC_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
63780
63801
|
pendingKpPath = (dataDir, groupId) => join(dataDir, `mls-kp-pending-${groupId.replace(/[^a-zA-Z0-9_-]/g, "_")}.json`);
|
|
63781
63802
|
}
|
|
@@ -63926,9 +63947,9 @@ var init_crypto_helpers = __esm2({
|
|
|
63926
63947
|
}
|
|
63927
63948
|
});
|
|
63928
63949
|
async function saveState(dataDir, state) {
|
|
63929
|
-
await
|
|
63950
|
+
await ensureSecureDir(dataDir);
|
|
63930
63951
|
const filePath = join2(dataDir, STATE_FILE);
|
|
63931
|
-
await
|
|
63952
|
+
await writeSecureFile(filePath, JSON.stringify(state, null, 2));
|
|
63932
63953
|
try {
|
|
63933
63954
|
await rm2(join2(dataDir, LEGACY_STATE_FILE));
|
|
63934
63955
|
} catch {
|
|
@@ -63945,8 +63966,8 @@ async function loadState(dataDir) {
|
|
|
63945
63966
|
try {
|
|
63946
63967
|
const raw = await readFile2(legacyPath, "utf-8");
|
|
63947
63968
|
const parsed = JSON.parse(raw);
|
|
63948
|
-
await
|
|
63949
|
-
await
|
|
63969
|
+
await ensureSecureDir(dataDir);
|
|
63970
|
+
await writeSecureFile(filePath, JSON.stringify(parsed, null, 2));
|
|
63950
63971
|
await rm2(legacyPath);
|
|
63951
63972
|
return parsed;
|
|
63952
63973
|
} catch {
|
|
@@ -63969,8 +63990,8 @@ async function backupState(dataDir) {
|
|
|
63969
63990
|
const tmp = join2(dataDir, `${STATE_FILE}.bak.tmp`);
|
|
63970
63991
|
try {
|
|
63971
63992
|
const data = await readFile2(src, "utf-8");
|
|
63972
|
-
await
|
|
63973
|
-
await
|
|
63993
|
+
await writeSecureFile(tmp, data);
|
|
63994
|
+
await rename2(tmp, bak);
|
|
63974
63995
|
} catch {
|
|
63975
63996
|
}
|
|
63976
63997
|
}
|
|
@@ -63984,8 +64005,8 @@ async function restoreState(dataDir) {
|
|
|
63984
64005
|
if (!parsed || !parsed.deviceId || !parsed.deviceJwt || !parsed.sessions || Object.keys(parsed.sessions).length === 0) {
|
|
63985
64006
|
return false;
|
|
63986
64007
|
}
|
|
63987
|
-
await
|
|
63988
|
-
await
|
|
64008
|
+
await ensureSecureDir(dataDir);
|
|
64009
|
+
await writeSecureFile(join2(dataDir, STATE_FILE), data);
|
|
63989
64010
|
return true;
|
|
63990
64011
|
} catch {
|
|
63991
64012
|
return false;
|
|
@@ -64048,16 +64069,13 @@ async function clearState(dataDir) {
|
|
|
64048
64069
|
}
|
|
64049
64070
|
var STATE_FILE;
|
|
64050
64071
|
var LEGACY_STATE_FILE;
|
|
64051
|
-
var DIR_MODE2;
|
|
64052
|
-
var FILE_MODE2;
|
|
64053
64072
|
var init_state = __esm2({
|
|
64054
64073
|
async "src/state.ts"() {
|
|
64055
64074
|
"use strict";
|
|
64056
64075
|
await init_dist();
|
|
64076
|
+
init_secure_file();
|
|
64057
64077
|
STATE_FILE = "agentvault.json";
|
|
64058
64078
|
LEGACY_STATE_FILE = "secure-channel.json";
|
|
64059
|
-
DIR_MODE2 = 448;
|
|
64060
|
-
FILE_MODE2 = 384;
|
|
64061
64079
|
}
|
|
64062
64080
|
});
|
|
64063
64081
|
async function enrollDevice(apiUrl, inviteToken, identityPkHex, ephemeralPkHex, proofHex, platform) {
|
|
@@ -64108,19 +64126,30 @@ var init_transport2 = __esm2({
|
|
|
64108
64126
|
});
|
|
64109
64127
|
var openclaw_compat_exports = {};
|
|
64110
64128
|
__export2(openclaw_compat_exports, {
|
|
64129
|
+
AGENT_EVENT_CANDIDATES: () => AGENT_EVENT_CANDIDATES,
|
|
64130
|
+
HEARTBEAT_CANDIDATES: () => HEARTBEAT_CANDIDATES,
|
|
64131
|
+
TRANSCRIPT_CANDIDATES: () => TRANSCRIPT_CANDIDATES,
|
|
64132
|
+
_resetCompatCacheForTest: () => _resetCompatCacheForTest,
|
|
64111
64133
|
onAgentEvent: () => onAgentEvent,
|
|
64112
64134
|
onSessionTranscriptUpdate: () => onSessionTranscriptUpdate,
|
|
64113
|
-
requestHeartbeatNow: () => requestHeartbeatNow
|
|
64135
|
+
requestHeartbeatNow: () => requestHeartbeatNow,
|
|
64136
|
+
resolveSdkFn: () => resolveSdkFn
|
|
64114
64137
|
});
|
|
64115
|
-
async function
|
|
64116
|
-
|
|
64138
|
+
async function resolveSdkFn(candidates, importer = defaultImporter) {
|
|
64139
|
+
for (const { specifier, symbol: symbol22 } of candidates) {
|
|
64117
64140
|
try {
|
|
64118
|
-
const mod4 = await
|
|
64119
|
-
|
|
64141
|
+
const mod4 = await importer(specifier);
|
|
64142
|
+
const found = mod4?.[symbol22] ?? mod4?.default?.[symbol22];
|
|
64143
|
+
if (typeof found === "function") return found;
|
|
64120
64144
|
} catch {
|
|
64121
|
-
_heartbeatFn = false;
|
|
64122
64145
|
}
|
|
64123
64146
|
}
|
|
64147
|
+
return false;
|
|
64148
|
+
}
|
|
64149
|
+
async function requestHeartbeatNow(opts) {
|
|
64150
|
+
if (_heartbeatFn === null) {
|
|
64151
|
+
_heartbeatFn = await resolveSdkFn(HEARTBEAT_CANDIDATES);
|
|
64152
|
+
}
|
|
64124
64153
|
if (typeof _heartbeatFn === "function") {
|
|
64125
64154
|
try {
|
|
64126
64155
|
await _heartbeatFn(opts);
|
|
@@ -64133,12 +64162,7 @@ async function requestHeartbeatNow(opts) {
|
|
|
64133
64162
|
}
|
|
64134
64163
|
async function onAgentEvent(callback) {
|
|
64135
64164
|
if (_agentEventFn === null) {
|
|
64136
|
-
|
|
64137
|
-
const mod4 = await import("openclaw/dist/plugin-sdk/infra/agent-events.js");
|
|
64138
|
-
_agentEventFn = mod4.onAgentEvent ?? mod4.default?.onAgentEvent ?? false;
|
|
64139
|
-
} catch {
|
|
64140
|
-
_agentEventFn = false;
|
|
64141
|
-
}
|
|
64165
|
+
_agentEventFn = await resolveSdkFn(AGENT_EVENT_CANDIDATES);
|
|
64142
64166
|
}
|
|
64143
64167
|
if (typeof _agentEventFn === "function") {
|
|
64144
64168
|
try {
|
|
@@ -64153,12 +64177,7 @@ async function onAgentEvent(callback) {
|
|
|
64153
64177
|
}
|
|
64154
64178
|
async function onSessionTranscriptUpdate(callback) {
|
|
64155
64179
|
if (_transcriptFn === null) {
|
|
64156
|
-
|
|
64157
|
-
const mod4 = await import("openclaw/dist/plugin-sdk/sessions/transcript-events.js");
|
|
64158
|
-
_transcriptFn = mod4.onSessionTranscriptUpdate ?? mod4.default?.onSessionTranscriptUpdate ?? false;
|
|
64159
|
-
} catch {
|
|
64160
|
-
_transcriptFn = false;
|
|
64161
|
-
}
|
|
64180
|
+
_transcriptFn = await resolveSdkFn(TRANSCRIPT_CANDIDATES);
|
|
64162
64181
|
}
|
|
64163
64182
|
if (typeof _transcriptFn === "function") {
|
|
64164
64183
|
try {
|
|
@@ -64171,12 +64190,45 @@ async function onSessionTranscriptUpdate(callback) {
|
|
|
64171
64190
|
return () => {
|
|
64172
64191
|
};
|
|
64173
64192
|
}
|
|
64193
|
+
function _resetCompatCacheForTest() {
|
|
64194
|
+
_heartbeatFn = null;
|
|
64195
|
+
_agentEventFn = null;
|
|
64196
|
+
_transcriptFn = null;
|
|
64197
|
+
}
|
|
64198
|
+
var defaultImporter;
|
|
64199
|
+
var HEARTBEAT_CANDIDATES;
|
|
64200
|
+
var AGENT_EVENT_CANDIDATES;
|
|
64201
|
+
var TRANSCRIPT_CANDIDATES;
|
|
64174
64202
|
var _heartbeatFn;
|
|
64175
64203
|
var _agentEventFn;
|
|
64176
64204
|
var _transcriptFn;
|
|
64177
64205
|
var init_openclaw_compat = __esm2({
|
|
64178
64206
|
"src/openclaw-compat.ts"() {
|
|
64179
64207
|
"use strict";
|
|
64208
|
+
defaultImporter = (specifier) => import(
|
|
64209
|
+
/* @vite-ignore */
|
|
64210
|
+
specifier
|
|
64211
|
+
);
|
|
64212
|
+
HEARTBEAT_CANDIDATES = [
|
|
64213
|
+
// 2026.7.x — exported subpath, and the symbol lost its `Now` suffix.
|
|
64214
|
+
{ specifier: "openclaw/plugin-sdk/heartbeat-runtime", symbol: "requestHeartbeat" },
|
|
64215
|
+
// <=2026.6.x — deep dist path, original name.
|
|
64216
|
+
{ specifier: "openclaw/dist/plugin-sdk/infra/heartbeat-wake.js", symbol: "requestHeartbeatNow" }
|
|
64217
|
+
];
|
|
64218
|
+
AGENT_EVENT_CANDIDATES = [
|
|
64219
|
+
{ specifier: "openclaw/plugin-sdk/agent-harness", symbol: "onAgentEvent" },
|
|
64220
|
+
{ specifier: "openclaw/dist/plugin-sdk/infra/agent-events.js", symbol: "onAgentEvent" }
|
|
64221
|
+
];
|
|
64222
|
+
TRANSCRIPT_CANDIDATES = [
|
|
64223
|
+
{
|
|
64224
|
+
specifier: "openclaw/plugin-sdk/memory-core-host-engine-foundation",
|
|
64225
|
+
symbol: "onSessionTranscriptUpdate"
|
|
64226
|
+
},
|
|
64227
|
+
{
|
|
64228
|
+
specifier: "openclaw/dist/plugin-sdk/sessions/transcript-events.js",
|
|
64229
|
+
symbol: "onSessionTranscriptUpdate"
|
|
64230
|
+
}
|
|
64231
|
+
];
|
|
64180
64232
|
_heartbeatFn = null;
|
|
64181
64233
|
_agentEventFn = null;
|
|
64182
64234
|
_transcriptFn = null;
|
|
@@ -64486,12 +64538,12 @@ async function handleWorkspaceUpload(data, workspaceDir) {
|
|
|
64486
64538
|
if (!verified) {
|
|
64487
64539
|
return { status: "error", error: "Invalid signature \u2014 file may have been tampered with" };
|
|
64488
64540
|
}
|
|
64489
|
-
await
|
|
64541
|
+
await mkdir2(workspaceDir, { recursive: true });
|
|
64490
64542
|
const targetPath = join3(workspaceDir, data.filename);
|
|
64491
64543
|
const tempPath = join3(workspaceDir, `.tmp_${randomUUID()}_${data.filename}`);
|
|
64492
64544
|
try {
|
|
64493
|
-
await
|
|
64494
|
-
await
|
|
64545
|
+
await writeFile2(tempPath, data.content, "utf-8");
|
|
64546
|
+
await rename3(tempPath, targetPath);
|
|
64495
64547
|
} catch (err) {
|
|
64496
64548
|
try {
|
|
64497
64549
|
await unlink(tempPath);
|
|
@@ -64508,7 +64560,7 @@ async function handleWorkspaceUpload(data, workspaceDir) {
|
|
|
64508
64560
|
}
|
|
64509
64561
|
async function handleWorkspaceList(workspaceDir) {
|
|
64510
64562
|
try {
|
|
64511
|
-
const entries = await
|
|
64563
|
+
const entries = await readdir2(workspaceDir);
|
|
64512
64564
|
const files = [];
|
|
64513
64565
|
for (const entry of entries) {
|
|
64514
64566
|
if (!entry.endsWith(".md")) continue;
|
|
@@ -65669,7 +65721,7 @@ var init_channel = __esm2({
|
|
|
65669
65721
|
*/
|
|
65670
65722
|
sendActivitySpan(spanData) {
|
|
65671
65723
|
if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
|
|
65672
|
-
const pluginVersion = true ? "0.23.
|
|
65724
|
+
const pluginVersion = true ? "0.23.19" : "0.0.0-dev";
|
|
65673
65725
|
const agentName = this.config.agentName ?? "Agent";
|
|
65674
65726
|
const resource = {
|
|
65675
65727
|
"service.name": "agentvault-agent",
|
|
@@ -67534,7 +67586,7 @@ var init_channel = __esm2({
|
|
|
67534
67586
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67535
67587
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67536
67588
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67537
|
-
pluginVersion: true ? "0.23.
|
|
67589
|
+
pluginVersion: true ? "0.23.19" : "0.0.0-dev"
|
|
67538
67590
|
});
|
|
67539
67591
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67540
67592
|
}
|
|
@@ -67857,7 +67909,7 @@ var init_channel = __esm2({
|
|
|
67857
67909
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67858
67910
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67859
67911
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67860
|
-
pluginVersion: true ? "0.23.
|
|
67912
|
+
pluginVersion: true ? "0.23.19" : "0.0.0-dev"
|
|
67861
67913
|
});
|
|
67862
67914
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67863
67915
|
}
|
|
@@ -68341,6 +68393,10 @@ var init_channel = __esm2({
|
|
|
68341
68393
|
conversationId: convId ?? this._primaryConversationId,
|
|
68342
68394
|
timestamp: data.created_at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
68343
68395
|
topicId,
|
|
68396
|
+
// #621: positive attestation that this decrypted under the OWNER'S 1:1
|
|
68397
|
+
// material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
|
|
68398
|
+
// that a roomId was absent. The bridge's full-access DM lane requires it.
|
|
68399
|
+
ownerDm1to1: true,
|
|
68344
68400
|
messageType
|
|
68345
68401
|
};
|
|
68346
68402
|
this.emit("message", text, metadata);
|
|
@@ -68583,7 +68639,7 @@ ${messageText}`;
|
|
|
68583
68639
|
*/
|
|
68584
68640
|
async _downloadAndDecryptAttachment(info) {
|
|
68585
68641
|
const attachDir = join4(this.config.dataDir, "attachments");
|
|
68586
|
-
await
|
|
68642
|
+
await mkdir3(attachDir, { recursive: true });
|
|
68587
68643
|
const url22 = `${this.config.apiUrl}${info.blobUrl}`;
|
|
68588
68644
|
const res = await fetch(url22, {
|
|
68589
68645
|
headers: { Authorization: `Bearer ${this._deviceJwt}` }
|
|
@@ -68601,7 +68657,7 @@ ${messageText}`;
|
|
|
68601
68657
|
const fileNonce = base64ToBytes(info.fileNonce);
|
|
68602
68658
|
const decrypted = decryptFile(encryptedData, fileKey, fileNonce);
|
|
68603
68659
|
const filePath = join4(attachDir, info.filename);
|
|
68604
|
-
await
|
|
68660
|
+
await writeFile3(filePath, decrypted);
|
|
68605
68661
|
console.log(`[SecureChannel] Attachment saved: ${filePath} (${decrypted.length} bytes)`);
|
|
68606
68662
|
return { filePath, decrypted };
|
|
68607
68663
|
}
|
|
@@ -69621,7 +69677,7 @@ ${messageText}`;
|
|
|
69621
69677
|
console.warn("[SecureChannel] KeyPackage pool replenish after join failed:", err);
|
|
69622
69678
|
});
|
|
69623
69679
|
console.log(`[SecureChannel] Welcome joined: group=${groupId?.slice(0, 8)} kpSource=${kpSource} welcomeLen=${welcomeBytes.length} candidates=${candidateCount} poolRemaining=${this._pendingKpBundles.length}`);
|
|
69624
|
-
if (conversationGroupId) {
|
|
69680
|
+
if (conversationGroupId && !data.room_id) {
|
|
69625
69681
|
const key = `1to1-group:${conversationGroupId}`;
|
|
69626
69682
|
this._mlsGroups.set(key, mgr);
|
|
69627
69683
|
await saveMlsState(this.config.dataDir, groupId, JSON.stringify(mgr.exportState()));
|
|
@@ -70442,7 +70498,11 @@ ${messageText}`;
|
|
|
70442
70498
|
messageId: msg.message_id,
|
|
70443
70499
|
conversationId: msg.conversation_id,
|
|
70444
70500
|
timestamp: msg.created_at,
|
|
70445
|
-
topicId
|
|
70501
|
+
topicId,
|
|
70502
|
+
// #621: positive attestation that this decrypted under the OWNER'S 1:1
|
|
70503
|
+
// material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
|
|
70504
|
+
// that a roomId was absent. The bridge's full-access DM lane requires it.
|
|
70505
|
+
ownerDm1to1: true
|
|
70446
70506
|
};
|
|
70447
70507
|
this.emit("message", messageText, metadata);
|
|
70448
70508
|
Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
|
|
@@ -70774,7 +70834,11 @@ ${messageText}`;
|
|
|
70774
70834
|
messageId: msg.id,
|
|
70775
70835
|
conversationId: msg.conversation_id,
|
|
70776
70836
|
timestamp: msg.created_at,
|
|
70777
|
-
topicId
|
|
70837
|
+
topicId,
|
|
70838
|
+
// #621: positive attestation that this decrypted under the OWNER'S 1:1
|
|
70839
|
+
// material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
|
|
70840
|
+
// that a roomId was absent. The bridge's full-access DM lane requires it.
|
|
70841
|
+
ownerDm1to1: true
|
|
70778
70842
|
};
|
|
70779
70843
|
this.emit("message", messageText, metadata);
|
|
70780
70844
|
Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
|
|
@@ -71055,7 +71119,11 @@ ${messageText}`;
|
|
|
71055
71119
|
messageId: msg.id,
|
|
71056
71120
|
conversationId: msg.conversation_id,
|
|
71057
71121
|
timestamp: msg.created_at,
|
|
71058
|
-
topicId
|
|
71122
|
+
topicId,
|
|
71123
|
+
// #621: positive attestation that this decrypted under the OWNER'S 1:1
|
|
71124
|
+
// material (MLS 1to1-group map, or a Double Ratchet session), NOT merely
|
|
71125
|
+
// that a roomId was absent. The bridge's full-access DM lane requires it.
|
|
71126
|
+
ownerDm1to1: true
|
|
71059
71127
|
};
|
|
71060
71128
|
this.emit("message", messageText, metadata);
|
|
71061
71129
|
Promise.resolve(this.config.onMessage?.(messageText, metadata)).catch((err) => {
|
|
@@ -71658,6 +71726,16 @@ var init_fetch_interceptor = __esm2({
|
|
|
71658
71726
|
traceStore = new AsyncLocalStorage();
|
|
71659
71727
|
}
|
|
71660
71728
|
});
|
|
71729
|
+
var init_tool_audit = __esm2({
|
|
71730
|
+
"src/tool-audit.ts"() {
|
|
71731
|
+
"use strict";
|
|
71732
|
+
}
|
|
71733
|
+
});
|
|
71734
|
+
var init_harness_compat = __esm2({
|
|
71735
|
+
"src/harness-compat.ts"() {
|
|
71736
|
+
"use strict";
|
|
71737
|
+
}
|
|
71738
|
+
});
|
|
71661
71739
|
var isUsingManagedRoutes;
|
|
71662
71740
|
var init_openclaw_entry = __esm2({
|
|
71663
71741
|
"src/openclaw-entry.ts"() {
|
|
@@ -71667,6 +71745,8 @@ var init_openclaw_entry = __esm2({
|
|
|
71667
71745
|
init_http_handlers();
|
|
71668
71746
|
init_openclaw_compat();
|
|
71669
71747
|
init_lifecycle();
|
|
71748
|
+
init_tool_audit();
|
|
71749
|
+
init_harness_compat();
|
|
71670
71750
|
init_types();
|
|
71671
71751
|
isUsingManagedRoutes = false;
|
|
71672
71752
|
}
|
|
@@ -97718,7 +97798,7 @@ var init_index = __esm2({
|
|
|
97718
97798
|
init_skill_invoker();
|
|
97719
97799
|
await init_skill_telemetry();
|
|
97720
97800
|
await init_policy_enforcer();
|
|
97721
|
-
VERSION = true ? "0.23.
|
|
97801
|
+
VERSION = true ? "0.23.19" : "0.0.0-dev";
|
|
97722
97802
|
}
|
|
97723
97803
|
});
|
|
97724
97804
|
await init_index();
|
|
@@ -119029,8 +119109,8 @@ import { mkdirSync as mkdirSync3, writeFileSync, rmSync as rmSync2, readdirSync
|
|
|
119029
119109
|
import { join as join6 } from "node:path";
|
|
119030
119110
|
import { homedir, hostname as hostname3 } from "node:os";
|
|
119031
119111
|
var TRUST_SUBDIR = "host-trust";
|
|
119032
|
-
var
|
|
119033
|
-
var
|
|
119112
|
+
var DIR_MODE2 = 448;
|
|
119113
|
+
var FILE_MODE2 = 384;
|
|
119034
119114
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
119035
119115
|
var IDENTITY_FILES = ["agentvault.json", "secure-channel.json", "agentvault.json.bak"];
|
|
119036
119116
|
var HostTrustError = class extends Error {
|
|
@@ -119057,15 +119137,15 @@ function grant(deviceId, root3) {
|
|
|
119057
119137
|
const id = sanitize(deviceId);
|
|
119058
119138
|
const dir = trustDir(root3);
|
|
119059
119139
|
const p2 = join6(dir, id);
|
|
119060
|
-
mkdirSync3(dir, { recursive: true, mode:
|
|
119140
|
+
mkdirSync3(dir, { recursive: true, mode: DIR_MODE2 });
|
|
119061
119141
|
writeFileSync(
|
|
119062
119142
|
p2,
|
|
119063
119143
|
`granted_at=${(/* @__PURE__ */ new Date()).toISOString()} host=${hostname3()}
|
|
119064
119144
|
`,
|
|
119065
|
-
{ mode:
|
|
119145
|
+
{ mode: FILE_MODE2 }
|
|
119066
119146
|
);
|
|
119067
|
-
chmodSync2(dir,
|
|
119068
|
-
chmodSync2(p2,
|
|
119147
|
+
chmodSync2(dir, DIR_MODE2);
|
|
119148
|
+
chmodSync2(p2, FILE_MODE2);
|
|
119069
119149
|
}
|
|
119070
119150
|
function revoke(deviceId, root3) {
|
|
119071
119151
|
const id = sanitize(deviceId);
|
|
@@ -133358,6 +133438,16 @@ var WorkerQueue = class {
|
|
|
133358
133438
|
this.running = false;
|
|
133359
133439
|
}
|
|
133360
133440
|
}
|
|
133441
|
+
/**
|
|
133442
|
+
* What the worker had produced, when the session can report it. Read on BOTH the
|
|
133443
|
+
* clean and the failing path: on a clean turn `said: false` with a non-zero
|
|
133444
|
+
* `composedChars` is the "composed but could never ship it" signature (agent-
|
|
133445
|
+
* authored room traffic, where `replyExpected` is false), which is otherwise
|
|
133446
|
+
* indistinguishable from a turn that did nothing.
|
|
133447
|
+
*/
|
|
133448
|
+
snapshotOf(session) {
|
|
133449
|
+
return session?.snapshot?.();
|
|
133450
|
+
}
|
|
133361
133451
|
/** Best-effort trap emit. A broken sink must never break the queue. */
|
|
133362
133452
|
emitIncident(rec) {
|
|
133363
133453
|
try {
|
|
@@ -133389,10 +133479,23 @@ var WorkerQueue = class {
|
|
|
133389
133479
|
}, this.deps.timeoutMs);
|
|
133390
133480
|
});
|
|
133391
133481
|
await Promise.race([session.start(), timeout]);
|
|
133482
|
+
const okSnap = this.snapshotOf(session);
|
|
133483
|
+
this.emitIncident({
|
|
133484
|
+
at: new Date(startedAt).toISOString(),
|
|
133485
|
+
outcome: "ok",
|
|
133486
|
+
waitedMs,
|
|
133487
|
+
ranMs: Date.now() - startedAt,
|
|
133488
|
+
timeoutMs: this.deps.timeoutMs,
|
|
133489
|
+
queueDepthAtEnqueue: entry.queueDepthAtEnqueue,
|
|
133490
|
+
queueDepthAtStart,
|
|
133491
|
+
instructionChars: task.instruction.length,
|
|
133492
|
+
replyExpected: task.replyExpected,
|
|
133493
|
+
...okSnap ? { session: okSnap } : {}
|
|
133494
|
+
});
|
|
133392
133495
|
} catch (e7) {
|
|
133393
133496
|
const err = e7;
|
|
133394
133497
|
this.deps.log(`[worker-queue] task failed: ${err.message}`);
|
|
133395
|
-
const snap = session
|
|
133498
|
+
const snap = this.snapshotOf(session);
|
|
133396
133499
|
this.emitIncident({
|
|
133397
133500
|
at: new Date(startedAt).toISOString(),
|
|
133398
133501
|
outcome: timedOut ? "timeout" : "error",
|
|
@@ -133403,6 +133506,7 @@ var WorkerQueue = class {
|
|
|
133403
133506
|
queueDepthAtEnqueue: entry.queueDepthAtEnqueue,
|
|
133404
133507
|
queueDepthAtStart,
|
|
133405
133508
|
instructionChars: task.instruction.length,
|
|
133509
|
+
replyExpected: task.replyExpected,
|
|
133406
133510
|
...snap ? { session: snap } : {}
|
|
133407
133511
|
});
|
|
133408
133512
|
session?.abort();
|
|
@@ -133424,7 +133528,7 @@ function makeRouter(deps) {
|
|
|
133424
133528
|
const replySink = reply ?? (() => {
|
|
133425
133529
|
});
|
|
133426
133530
|
const isOwnerDm = opts?.autoReplyOnText === true && opts?.armed === void 0 && deps.workAllowed();
|
|
133427
|
-
const isArmedRoom = opts?.armed?.() === true && deps.workAllowed();
|
|
133531
|
+
const isArmedRoom = opts?.armed?.() === true && deps.workAllowed() && opts?.replyExpected !== false;
|
|
133428
133532
|
if (isOwnerDm) {
|
|
133429
133533
|
deps.queue.enqueue({
|
|
133430
133534
|
instruction: text,
|
|
@@ -133839,9 +133943,14 @@ function wireBridge(channel, session, target, opts = {}) {
|
|
|
133839
133943
|
});
|
|
133840
133944
|
channel.on("message", (text, metadata) => {
|
|
133841
133945
|
if (metadata?.roomId) return;
|
|
133842
|
-
|
|
133946
|
+
const ownerAttested = metadata?.ownerDm1to1 === true;
|
|
133947
|
+
if (!ownerAttested) {
|
|
133948
|
+
log("inbound 1:1 DM WITHOUT owner attestation \u2014 delivering to the locked listener (no tools)");
|
|
133949
|
+
} else {
|
|
133950
|
+
log("inbound 1:1 DM from owner");
|
|
133951
|
+
}
|
|
133843
133952
|
target.setDm();
|
|
133844
|
-
session.push(text, target.snapshotReply(channel, log), { autoReplyOnText:
|
|
133953
|
+
session.push(text, target.snapshotReply(channel, log), { autoReplyOnText: ownerAttested });
|
|
133845
133954
|
});
|
|
133846
133955
|
const workerCapable = !!opts.workspaceDir;
|
|
133847
133956
|
const osIsolated = opts.osIsolated !== void 0 ? opts.osIsolated : process.env.AV_WORKER_OS_ISOLATED === "1" || process.env.AV_WORKER_OS_ISOLATED === "true";
|
|
@@ -133982,7 +134091,7 @@ async function main() {
|
|
|
133982
134091
|
"[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"
|
|
133983
134092
|
);
|
|
133984
134093
|
}
|
|
133985
|
-
console.error(`[bridge] version: ${true ? "0.7.
|
|
134094
|
+
console.error(`[bridge] version: ${true ? "0.7.7" : "dev"}`);
|
|
133986
134095
|
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
133987
134096
|
console.error(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
|
|
133988
134097
|
if (cfg.armRoom) {
|
|
@@ -134065,7 +134174,7 @@ async function main() {
|
|
|
134065
134174
|
// incident is visible in bridge.error.log at the moment it happens.
|
|
134066
134175
|
onIncident: (rec) => {
|
|
134067
134176
|
console.error(
|
|
134068
|
-
`[worker-trap] ${rec.at} ${rec.outcome} after ${rec.ranMs}ms (waited ${rec.waitedMs}ms behind ${rec.queueDepthAtEnqueue}, ${rec.queueDepthAtStart} still queued)` + (rec.session ? ` composed=${rec.session.composedChars} result=${rec.session.sawResult} said=${rec.session.said}` : "")
|
|
134177
|
+
`[worker-trap] ${rec.at} ${rec.outcome} after ${rec.ranMs}ms (waited ${rec.waitedMs}ms behind ${rec.queueDepthAtEnqueue}, ${rec.queueDepthAtStart} still queued) replyExpected=${rec.replyExpected}` + (rec.session ? ` composed=${rec.session.composedChars} result=${rec.session.sawResult} said=${rec.session.said}` : "")
|
|
134069
134178
|
);
|
|
134070
134179
|
try {
|
|
134071
134180
|
const dir = join11(cfg.dataDir, "logs");
|
package/dist/worker-queue.d.ts
CHANGED
|
@@ -17,19 +17,27 @@ export type WorkerTask = {
|
|
|
17
17
|
armed?: () => boolean;
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
|
-
* Forensic record of
|
|
20
|
+
* Forensic record of EVERY task the queue runs — clean or not.
|
|
21
21
|
*
|
|
22
22
|
* Exists because the 2026-07-24 "loopita went quiet for 5 minutes" incident was
|
|
23
23
|
* diagnosable only by coincidence: the drop-trap happened to capture that the
|
|
24
24
|
* reply landed 300.016s after the ack, and 300s is `WORKER_TIMEOUT_MS` exactly.
|
|
25
25
|
* Nothing in the bridge recorded WHY the worker hung, and the logs carry no
|
|
26
26
|
* timestamps at all, so the evidence was gone by the time we looked.
|
|
27
|
+
*
|
|
28
|
+
* 2026-08-06: widened from failures-only to every turn. Recording only failures
|
|
29
|
+
* made the SLOW case unmeasurable, and slow is the common case: loopita answered
|
|
30
|
+
* Ballroom2 in 21s when the room was quiet and 81s when a second agent was
|
|
31
|
+
* talking, and nothing said whether the extra 60s was `waitedMs` or `ranMs`.
|
|
32
|
+
* Those are different bugs with identical symptoms. A trap that only reports
|
|
33
|
+
* when it breaks cannot tell you it is the reason things are slow.
|
|
27
34
|
*/
|
|
28
35
|
export type WorkerIncident = {
|
|
29
36
|
/** ISO timestamp — the bridge's own logs are unstamped, so this is the anchor. */
|
|
30
37
|
at: string;
|
|
31
|
-
outcome: "timeout" | "error";
|
|
32
|
-
|
|
38
|
+
outcome: "ok" | "timeout" | "error";
|
|
39
|
+
/** Absent on a clean turn — its presence is what marks a real incident. */
|
|
40
|
+
error?: string;
|
|
33
41
|
/**
|
|
34
42
|
* enqueue -> start. THE load-bearing field: it separates "this task hung" from
|
|
35
43
|
* "this task was stuck behind one that hung". Both look identical to the owner
|
|
@@ -44,6 +52,15 @@ export type WorkerIncident = {
|
|
|
44
52
|
/** Depth still waiting when this task began — the blast radius of this hang. */
|
|
45
53
|
queueDepthAtStart: number;
|
|
46
54
|
instructionChars: number;
|
|
55
|
+
/**
|
|
56
|
+
* Whether a reply was expected of this turn — mirrors `WorkerTask.replyExpected`,
|
|
57
|
+
* i.e. `senderIsAgent === false` upstream. Without it a silent turn
|
|
58
|
+
* (composed=0, said=false) is unattributable: it could be agent-authored traffic
|
|
59
|
+
* that was never going to answer, or a turn that waited so long the previous one
|
|
60
|
+
* already answered. Ballroom2 on 2026-08-06 produced both readings and the record
|
|
61
|
+
* could not separate them.
|
|
62
|
+
*/
|
|
63
|
+
replyExpected?: boolean;
|
|
47
64
|
/** What the worker had produced when it died, when the session can report it. */
|
|
48
65
|
session?: {
|
|
49
66
|
composedChars: number;
|
|
@@ -91,6 +108,14 @@ export declare class WorkerQueue {
|
|
|
91
108
|
/** Resolves when the queue has processed everything enqueued so far. */
|
|
92
109
|
whenDrained(): Promise<void>;
|
|
93
110
|
private drain;
|
|
111
|
+
/**
|
|
112
|
+
* What the worker had produced, when the session can report it. Read on BOTH the
|
|
113
|
+
* clean and the failing path: on a clean turn `said: false` with a non-zero
|
|
114
|
+
* `composedChars` is the "composed but could never ship it" signature (agent-
|
|
115
|
+
* authored room traffic, where `replyExpected` is false), which is otherwise
|
|
116
|
+
* indistinguishable from a turn that did nothing.
|
|
117
|
+
*/
|
|
118
|
+
private snapshotOf;
|
|
94
119
|
/** Best-effort trap emit. A broken sink must never break the queue. */
|
|
95
120
|
private emitIncident;
|
|
96
121
|
private runOne;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentvault/claude-bridge",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "AgentVault Claude Bridge
|
|
5
|
+
"description": "AgentVault Claude Bridge — daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|