@agentvault/claude-bridge 0.5.4 → 0.5.6
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 +178 -131
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -65024,145 +65024,140 @@ var init_channel = __esm2({
|
|
|
65024
65024
|
}
|
|
65025
65025
|
const raw = await loadState(this.config.dataDir);
|
|
65026
65026
|
if (raw) {
|
|
65027
|
-
await
|
|
65028
|
-
this.
|
|
65029
|
-
|
|
65030
|
-
|
|
65031
|
-
|
|
65032
|
-
|
|
65033
|
-
|
|
65034
|
-
|
|
65035
|
-
|
|
65036
|
-
|
|
65037
|
-
|
|
65038
|
-
|
|
65039
|
-
}
|
|
65040
|
-
|
|
65041
|
-
|
|
65042
|
-
|
|
65043
|
-
|
|
65044
|
-
|
|
65045
|
-
|
|
65046
|
-
|
|
65047
|
-
|
|
65048
|
-
|
|
65049
|
-
|
|
65050
|
-
|
|
65051
|
-
|
|
65052
|
-
|
|
65053
|
-
|
|
65054
|
-
|
|
65055
|
-
|
|
65056
|
-
|
|
65057
|
-
|
|
65058
|
-
|
|
65027
|
+
await this._hydrateFromPersisted(raw);
|
|
65028
|
+
this._connect();
|
|
65029
|
+
return;
|
|
65030
|
+
}
|
|
65031
|
+
const restored = await restoreState(this.config.dataDir);
|
|
65032
|
+
if (restored) {
|
|
65033
|
+
console.log("[SecureChannel] Restored state from backup");
|
|
65034
|
+
const restoredRaw = await loadState(this.config.dataDir);
|
|
65035
|
+
if (restoredRaw) {
|
|
65036
|
+
await this._hydrateFromPersisted(restoredRaw);
|
|
65037
|
+
this._connect();
|
|
65038
|
+
return;
|
|
65039
|
+
}
|
|
65040
|
+
}
|
|
65041
|
+
await this._enroll();
|
|
65042
|
+
}
|
|
65043
|
+
/**
|
|
65044
|
+
* Hydrate all in-memory state from persisted creds. The single source of
|
|
65045
|
+
* truth for both the primary boot and the .bak-restore boot, so the two can
|
|
65046
|
+
* never drift (the drift is what caused #416: the backup branch used to skip
|
|
65047
|
+
* MLS group loading and dropped owner MLS DMs). Callers invoke _connect()
|
|
65048
|
+
* after this resolves.
|
|
65049
|
+
*/
|
|
65050
|
+
async _hydrateFromPersisted(raw) {
|
|
65051
|
+
await backupState(this.config.dataDir);
|
|
65052
|
+
this._persisted = migratePersistedState(raw);
|
|
65053
|
+
if (!this._persisted.messageHistory) {
|
|
65054
|
+
this._persisted.messageHistory = [];
|
|
65055
|
+
}
|
|
65056
|
+
this._deviceId = this._persisted.deviceId;
|
|
65057
|
+
this._deviceJwt = this._persisted.deviceJwt;
|
|
65058
|
+
this._primaryConversationId = this._persisted.primaryConversationId;
|
|
65059
|
+
this._fingerprint = this._persisted.fingerprint;
|
|
65060
|
+
this._lastInboundRoomId = this._persisted.lastInboundRoomId;
|
|
65061
|
+
if (this._persisted.seenMessageIds) {
|
|
65062
|
+
this._seenMessageIds = new Set(this._persisted.seenMessageIds.slice(-_SecureChannel.SEEN_MSG_MAX));
|
|
65063
|
+
}
|
|
65064
|
+
if (this._persisted.seenA2AMessageIds) {
|
|
65065
|
+
this._a2aSeenMessageIds = new Set(this._persisted.seenA2AMessageIds.slice(-_SecureChannel.A2A_SEEN_MAX));
|
|
65066
|
+
}
|
|
65067
|
+
if (this._persisted?.rooms) {
|
|
65068
|
+
const roomConvIds = /* @__PURE__ */ new Set();
|
|
65069
|
+
for (const room of Object.values(this._persisted.rooms)) {
|
|
65070
|
+
for (const cid of room.conversationIds || []) {
|
|
65071
|
+
roomConvIds.add(cid);
|
|
65059
65072
|
}
|
|
65060
65073
|
}
|
|
65061
|
-
|
|
65062
|
-
|
|
65063
|
-
|
|
65064
|
-
|
|
65065
|
-
|
|
65066
|
-
this._sessions.set(convId, {
|
|
65067
|
-
ownerDeviceId: sessionData.ownerDeviceId,
|
|
65068
|
-
ratchet,
|
|
65069
|
-
activated: sessionData.activated ?? false,
|
|
65070
|
-
epoch: sessionData.epoch
|
|
65071
|
-
});
|
|
65074
|
+
let cleanedCount = 0;
|
|
65075
|
+
for (const cid of roomConvIds) {
|
|
65076
|
+
if (this._persisted.sessions[cid]) {
|
|
65077
|
+
delete this._persisted.sessions[cid];
|
|
65078
|
+
cleanedCount++;
|
|
65072
65079
|
}
|
|
65073
65080
|
}
|
|
65074
|
-
if (
|
|
65075
|
-
|
|
65076
|
-
console.log(`[SecureChannel] Restored ${this._sessionGroupIds.size} conversation\u2192group mappings`);
|
|
65081
|
+
if (cleanedCount > 0) {
|
|
65082
|
+
console.log(`[SecureChannel] Cleaned ${cleanedCount} stale DR room sessions`);
|
|
65077
65083
|
}
|
|
65078
|
-
|
|
65079
|
-
|
|
65080
|
-
|
|
65081
|
-
|
|
65082
|
-
|
|
65083
|
-
|
|
65084
|
-
|
|
65085
|
-
|
|
65086
|
-
|
|
65087
|
-
|
|
65088
|
-
|
|
65089
|
-
|
|
65090
|
-
console.warn(`[SecureChannel] Failed to restore MLS state for room ${roomId.slice(0, 8)}:`, mlsErr);
|
|
65091
|
-
}
|
|
65092
|
-
}
|
|
65093
|
-
}
|
|
65084
|
+
}
|
|
65085
|
+
for (const [convId, sessionData] of Object.entries(
|
|
65086
|
+
this._persisted.sessions
|
|
65087
|
+
)) {
|
|
65088
|
+
if (sessionData.ratchetState) {
|
|
65089
|
+
const ratchet = DoubleRatchet.deserialize(sessionData.ratchetState);
|
|
65090
|
+
this._sessions.set(convId, {
|
|
65091
|
+
ownerDeviceId: sessionData.ownerDeviceId,
|
|
65092
|
+
ratchet,
|
|
65093
|
+
activated: sessionData.activated ?? false,
|
|
65094
|
+
epoch: sessionData.epoch
|
|
65095
|
+
});
|
|
65094
65096
|
}
|
|
65095
|
-
|
|
65096
|
-
|
|
65097
|
+
}
|
|
65098
|
+
if (this._persisted.conversationGroupIds) {
|
|
65099
|
+
this._sessionGroupIds = new Map(Object.entries(this._persisted.conversationGroupIds));
|
|
65100
|
+
console.log(`[SecureChannel] Restored ${this._sessionGroupIds.size} conversation\u2192group mappings`);
|
|
65101
|
+
}
|
|
65102
|
+
if (this._persisted.rooms) {
|
|
65103
|
+
for (const [roomId, room] of Object.entries(this._persisted.rooms)) {
|
|
65104
|
+
const mlsGroupId = room.mlsGroupId;
|
|
65105
|
+
if (mlsGroupId) {
|
|
65097
65106
|
try {
|
|
65098
|
-
const mlsState = await loadMlsState(this.config.dataDir,
|
|
65107
|
+
const mlsState = await loadMlsState(this.config.dataDir, mlsGroupId);
|
|
65099
65108
|
if (mlsState) {
|
|
65100
65109
|
const mgr = new MLSGroupManager();
|
|
65101
65110
|
mgr.importState(JSON.parse(mlsState));
|
|
65102
|
-
this._mlsGroups.set(
|
|
65111
|
+
this._mlsGroups.set(roomId, mgr);
|
|
65103
65112
|
}
|
|
65104
65113
|
} catch (mlsErr) {
|
|
65105
|
-
console.warn(`[SecureChannel] Failed to restore MLS state for
|
|
65114
|
+
console.warn(`[SecureChannel] Failed to restore MLS state for room ${roomId.slice(0, 8)}:`, mlsErr);
|
|
65106
65115
|
}
|
|
65107
65116
|
}
|
|
65108
65117
|
}
|
|
65109
|
-
|
|
65110
|
-
|
|
65118
|
+
}
|
|
65119
|
+
for (const [convId, entry] of Object.entries(this._persisted.mlsConversations ?? {})) {
|
|
65120
|
+
if (entry.mlsGroupId) {
|
|
65111
65121
|
try {
|
|
65112
|
-
const
|
|
65113
|
-
if (
|
|
65122
|
+
const mlsState = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
|
|
65123
|
+
if (mlsState) {
|
|
65114
65124
|
const mgr = new MLSGroupManager();
|
|
65115
|
-
mgr.importState(JSON.parse(
|
|
65116
|
-
this._mlsGroups.set(`
|
|
65117
|
-
console.log(`[SecureChannel] Loaded shared MLS group for ${gid.slice(0, 8)} (epoch=${mgr.epoch})`);
|
|
65125
|
+
mgr.importState(JSON.parse(mlsState));
|
|
65126
|
+
this._mlsGroups.set(`conv:${convId}`, mgr);
|
|
65118
65127
|
}
|
|
65119
|
-
} catch (
|
|
65120
|
-
console.warn(`[SecureChannel] Failed to
|
|
65128
|
+
} catch (mlsErr) {
|
|
65129
|
+
console.warn(`[SecureChannel] Failed to restore MLS state for conv ${convId.slice(0, 8)}:`, mlsErr);
|
|
65121
65130
|
}
|
|
65122
65131
|
}
|
|
65123
|
-
|
|
65124
|
-
|
|
65125
|
-
|
|
65126
|
-
|
|
65127
|
-
|
|
65128
|
-
|
|
65129
|
-
|
|
65130
|
-
|
|
65131
|
-
|
|
65132
|
-
|
|
65133
|
-
console.warn(`[SecureChannel] Failed to restore MLS state for A2A ${channelId.slice(0, 8)}:`, mlsErr);
|
|
65134
|
-
}
|
|
65132
|
+
}
|
|
65133
|
+
for (const [gid, entry] of Object.entries(this._persisted.mlsGroups ?? {})) {
|
|
65134
|
+
if (!entry.mlsGroupId) continue;
|
|
65135
|
+
try {
|
|
65136
|
+
const stateJson = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
|
|
65137
|
+
if (stateJson) {
|
|
65138
|
+
const mgr = new MLSGroupManager();
|
|
65139
|
+
mgr.importState(JSON.parse(stateJson));
|
|
65140
|
+
this._mlsGroups.set(`1to1-group:${gid}`, mgr);
|
|
65141
|
+
console.log(`[SecureChannel] Loaded shared MLS group for ${gid.slice(0, 8)} (epoch=${mgr.epoch})`);
|
|
65135
65142
|
}
|
|
65143
|
+
} catch (loadErr) {
|
|
65144
|
+
console.warn(`[SecureChannel] Failed to load shared MLS group ${gid.slice(0, 8)}:`, loadErr);
|
|
65136
65145
|
}
|
|
65137
|
-
this._connect();
|
|
65138
|
-
return;
|
|
65139
65146
|
}
|
|
65140
|
-
const
|
|
65141
|
-
|
|
65142
|
-
|
|
65143
|
-
|
|
65144
|
-
|
|
65145
|
-
|
|
65146
|
-
|
|
65147
|
-
|
|
65148
|
-
this._deviceJwt = this._persisted.deviceJwt;
|
|
65149
|
-
this._primaryConversationId = this._persisted.primaryConversationId;
|
|
65150
|
-
this._fingerprint = this._persisted.fingerprint;
|
|
65151
|
-
for (const [convId, sd] of Object.entries(this._persisted.sessions)) {
|
|
65152
|
-
if (sd.ratchetState) {
|
|
65153
|
-
this._sessions.set(convId, {
|
|
65154
|
-
ownerDeviceId: sd.ownerDeviceId,
|
|
65155
|
-
ratchet: DoubleRatchet.deserialize(sd.ratchetState),
|
|
65156
|
-
activated: sd.activated ?? false,
|
|
65157
|
-
epoch: sd.epoch
|
|
65158
|
-
});
|
|
65147
|
+
for (const [channelId, entry] of Object.entries(this._persisted.a2aChannels ?? {})) {
|
|
65148
|
+
if (entry.mlsGroupId) {
|
|
65149
|
+
try {
|
|
65150
|
+
const mlsState = await loadMlsState(this.config.dataDir, entry.mlsGroupId);
|
|
65151
|
+
if (mlsState) {
|
|
65152
|
+
const mgr = new MLSGroupManager();
|
|
65153
|
+
mgr.importState(JSON.parse(mlsState));
|
|
65154
|
+
this._mlsGroups.set(`a2a:${channelId}`, mgr);
|
|
65159
65155
|
}
|
|
65156
|
+
} catch (mlsErr) {
|
|
65157
|
+
console.warn(`[SecureChannel] Failed to restore MLS state for A2A ${channelId.slice(0, 8)}:`, mlsErr);
|
|
65160
65158
|
}
|
|
65161
|
-
this._connect();
|
|
65162
|
-
return;
|
|
65163
65159
|
}
|
|
65164
65160
|
}
|
|
65165
|
-
await this._enroll();
|
|
65166
65161
|
}
|
|
65167
65162
|
/**
|
|
65168
65163
|
* Fetch scan rules from the server and load them into the ScanEngine.
|
|
@@ -65502,7 +65497,7 @@ var init_channel = __esm2({
|
|
|
65502
65497
|
*/
|
|
65503
65498
|
sendActivitySpan(spanData) {
|
|
65504
65499
|
if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
|
|
65505
|
-
const pluginVersion = true ? "0.23.
|
|
65500
|
+
const pluginVersion = true ? "0.23.3" : "0.0.0-dev";
|
|
65506
65501
|
const agentName = this.config.agentName ?? "Agent";
|
|
65507
65502
|
const resource = {
|
|
65508
65503
|
"service.name": "agentvault-agent",
|
|
@@ -67119,7 +67114,7 @@ var init_channel = __esm2({
|
|
|
67119
67114
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67120
67115
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67121
67116
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67122
|
-
pluginVersion: true ? "0.23.
|
|
67117
|
+
pluginVersion: true ? "0.23.3" : "0.0.0-dev"
|
|
67123
67118
|
});
|
|
67124
67119
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67125
67120
|
}
|
|
@@ -67195,7 +67190,7 @@ var init_channel = __esm2({
|
|
|
67195
67190
|
return;
|
|
67196
67191
|
}
|
|
67197
67192
|
if (data.event === "device_revoked") {
|
|
67198
|
-
this._handleDeviceRevoked();
|
|
67193
|
+
await this._handleDeviceRevoked();
|
|
67199
67194
|
return;
|
|
67200
67195
|
}
|
|
67201
67196
|
if (data.event === "device_linked") {
|
|
@@ -67429,7 +67424,7 @@ var init_channel = __esm2({
|
|
|
67429
67424
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67430
67425
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67431
67426
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67432
|
-
pluginVersion: true ? "0.23.
|
|
67427
|
+
pluginVersion: true ? "0.23.3" : "0.0.0-dev"
|
|
67433
67428
|
});
|
|
67434
67429
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67435
67430
|
}
|
|
@@ -70594,22 +70589,74 @@ ${messageText}`;
|
|
|
70594
70589
|
this._setState("error");
|
|
70595
70590
|
this.emit("error", err);
|
|
70596
70591
|
}
|
|
70592
|
+
// Phase 2 — rolling-window timestamps of device_revoked self-heal reconnects,
|
|
70593
|
+
// used as a war cap so a takeover war or a server status/WS inconsistency can't
|
|
70594
|
+
// thrash forever.
|
|
70595
|
+
_revokeRecoveries = [];
|
|
70596
|
+
static REVOKE_RECOVERY_MAX = 3;
|
|
70597
|
+
static REVOKE_RECOVERY_WINDOW_MS = 12e4;
|
|
70597
70598
|
/**
|
|
70598
|
-
* Handle a WS `device_revoked` event.
|
|
70599
|
-
* attachLifecycle stops the channel — but does NOT delete credentials.
|
|
70599
|
+
* Handle a WS `device_revoked` event.
|
|
70600
70600
|
*
|
|
70601
|
-
*
|
|
70602
|
-
* session-takeover of a device that is still ACTIVE
|
|
70603
|
-
* text even reads "or another session has taken over"
|
|
70604
|
-
*
|
|
70605
|
-
*
|
|
70606
|
-
*
|
|
70607
|
-
*
|
|
70608
|
-
*
|
|
70609
|
-
*
|
|
70610
|
-
*
|
|
70601
|
+
* The signal is ambiguous: the server sends it both for a genuine owner revoke
|
|
70602
|
+
* AND for a transient/racy session-takeover of a device that is still ACTIVE
|
|
70603
|
+
* (the error text even reads "or another session has taken over" — loopita's
|
|
70604
|
+
* case, where the device stayed ACTIVE on the server). So we do NOT terminate
|
|
70605
|
+
* blindly. We ask the PUBLIC /devices/{id}/status endpoint (no JWT needed, so
|
|
70606
|
+
* it answers even when our device JWT is being rejected):
|
|
70607
|
+
* - ACTIVE -> reconnect in place (transient); stay online.
|
|
70608
|
+
* - non-ACTIVE -> terminal (genuinely revoked; operator must re-enroll).
|
|
70609
|
+
* - unreachable/429 -> reconnect (inconclusive; a network blip shouldn't
|
|
70610
|
+
* permanently kill a healthy agent — the WS is down too,
|
|
70611
|
+
* so normal backoff handles it, and the war cap bounds
|
|
70612
|
+
* thrash if the device really is dead).
|
|
70613
|
+
*
|
|
70614
|
+
* A rolling-window cap (REVOKE_RECOVERY_MAX self-heals per WINDOW_MS) gives up
|
|
70615
|
+
* (terminal) on a repeating loop. Never clears credentials in any branch —
|
|
70616
|
+
* re-enrollment, which SHOULD wipe creds, goes through setup --force /
|
|
70617
|
+
* _forceReEnroll in start().
|
|
70611
70618
|
*/
|
|
70612
|
-
_handleDeviceRevoked() {
|
|
70619
|
+
async _handleDeviceRevoked() {
|
|
70620
|
+
const now = Date.now();
|
|
70621
|
+
this._revokeRecoveries = this._revokeRecoveries.filter(
|
|
70622
|
+
(t22) => now - t22 < _SecureChannel.REVOKE_RECOVERY_WINDOW_MS
|
|
70623
|
+
);
|
|
70624
|
+
if (this._revokeRecoveries.length >= _SecureChannel.REVOKE_RECOVERY_MAX) {
|
|
70625
|
+
console.warn(
|
|
70626
|
+
"[SecureChannel] device_revoked self-healed too many times in a short window \u2014 giving up (terminal)"
|
|
70627
|
+
);
|
|
70628
|
+
this._handleError(new Error("Device was revoked"));
|
|
70629
|
+
return;
|
|
70630
|
+
}
|
|
70631
|
+
const deviceId = this._deviceId ?? this._persisted?.deviceId;
|
|
70632
|
+
if (!deviceId) {
|
|
70633
|
+
this._handleError(new Error("Device was revoked"));
|
|
70634
|
+
return;
|
|
70635
|
+
}
|
|
70636
|
+
let reconnect;
|
|
70637
|
+
try {
|
|
70638
|
+
const status = await pollDeviceStatus(this.config.apiUrl, deviceId);
|
|
70639
|
+
reconnect = status.rateLimited === true || status.status === "ACTIVE";
|
|
70640
|
+
if (!reconnect) {
|
|
70641
|
+
console.warn(
|
|
70642
|
+
`[SecureChannel] device_revoked confirmed by server (status=${status.status}) \u2014 terminal`
|
|
70643
|
+
);
|
|
70644
|
+
}
|
|
70645
|
+
} catch (err) {
|
|
70646
|
+
console.warn(
|
|
70647
|
+
"[SecureChannel] device_revoked status check failed; reconnecting (inconclusive):",
|
|
70648
|
+
err
|
|
70649
|
+
);
|
|
70650
|
+
reconnect = true;
|
|
70651
|
+
}
|
|
70652
|
+
if (reconnect) {
|
|
70653
|
+
this._revokeRecoveries.push(now);
|
|
70654
|
+
console.log(
|
|
70655
|
+
"[SecureChannel] device_revoked but device not confirmed dead \u2014 reconnecting (self-heal)"
|
|
70656
|
+
);
|
|
70657
|
+
this._scheduleReconnect();
|
|
70658
|
+
return;
|
|
70659
|
+
}
|
|
70613
70660
|
this._handleError(new Error("Device was revoked"));
|
|
70614
70661
|
}
|
|
70615
70662
|
/**
|
|
@@ -97124,7 +97171,7 @@ var init_index = __esm2({
|
|
|
97124
97171
|
init_skill_invoker();
|
|
97125
97172
|
await init_skill_telemetry();
|
|
97126
97173
|
await init_policy_enforcer();
|
|
97127
|
-
VERSION = true ? "0.23.
|
|
97174
|
+
VERSION = true ? "0.23.3" : "0.0.0-dev";
|
|
97128
97175
|
}
|
|
97129
97176
|
});
|
|
97130
97177
|
await init_index();
|
|
@@ -132866,7 +132913,7 @@ async function main() {
|
|
|
132866
132913
|
"[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"
|
|
132867
132914
|
);
|
|
132868
132915
|
}
|
|
132869
|
-
console.error(`[bridge] version: ${true ? "0.5.
|
|
132916
|
+
console.error(`[bridge] version: ${true ? "0.5.6" : "dev"}`);
|
|
132870
132917
|
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
132871
132918
|
if (cfg.worker) {
|
|
132872
132919
|
console.error(`[bridge] WORKER MODE \u2014 workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentvault/claude-bridge",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"type": "module",
|
|
5
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",
|