@agentvault/claude-bridge 0.5.3 → 0.5.5
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 +161 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,10 +10,19 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
13
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
14
14
|
import { join as join5 } from "node:path";
|
|
15
|
+
function hasRecoverableBackup(dataDir) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(readFileSync2(join5(dataDir, BACKUP_FILE), "utf-8"));
|
|
18
|
+
return !!(parsed && parsed.deviceId && parsed.deviceJwt && parsed.sessions && Object.keys(parsed.sessions).length > 0);
|
|
19
|
+
} catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
15
23
|
function hasPersistedCreds(dataDir) {
|
|
16
|
-
|
|
24
|
+
if (CRED_FILES.some((f7) => existsSync(join5(dataDir, f7)))) return true;
|
|
25
|
+
return hasRecoverableBackup(dataDir);
|
|
17
26
|
}
|
|
18
27
|
function slugify2(name) {
|
|
19
28
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
@@ -78,11 +87,12 @@ function loadConfig(env, argv = []) {
|
|
|
78
87
|
armRoom
|
|
79
88
|
};
|
|
80
89
|
}
|
|
81
|
-
var CRED_FILES;
|
|
90
|
+
var CRED_FILES, BACKUP_FILE;
|
|
82
91
|
var init_config = __esm({
|
|
83
92
|
"src/config.ts"() {
|
|
84
93
|
"use strict";
|
|
85
94
|
CRED_FILES = ["agentvault.json", "secure-channel.json"];
|
|
95
|
+
BACKUP_FILE = "agentvault.json.bak";
|
|
86
96
|
}
|
|
87
97
|
});
|
|
88
98
|
|
|
@@ -64550,6 +64560,11 @@ function migratePersistedState(raw) {
|
|
|
64550
64560
|
messageHistory: []
|
|
64551
64561
|
};
|
|
64552
64562
|
}
|
|
64563
|
+
function _isSelfHubAddress(ownHubAddress, target) {
|
|
64564
|
+
const own = (ownHubAddress ?? "").trim().toLowerCase();
|
|
64565
|
+
const tgt = (target ?? "").trim().toLowerCase();
|
|
64566
|
+
return own.length > 0 && own === tgt;
|
|
64567
|
+
}
|
|
64553
64568
|
var ROOM_AGENT_TYPES;
|
|
64554
64569
|
var CREDENTIAL_MESSAGE_TYPES;
|
|
64555
64570
|
var CATCHUP_MESSAGE_TYPES;
|
|
@@ -64621,6 +64636,15 @@ var init_channel = __esm2({
|
|
|
64621
64636
|
_ackTimer = null;
|
|
64622
64637
|
_stopped = false;
|
|
64623
64638
|
_persisted = null;
|
|
64639
|
+
// AgentVault display_name, resolved from GET /hub/identities (#499, A1).
|
|
64640
|
+
// hub_address alone isn't enough — its random suffix (wren-8789ee6d…) differs
|
|
64641
|
+
// from what the owner types (Wren). Empty until resolved; the DM preamble is
|
|
64642
|
+
// then omitted rather than asserting a wrong name. Re-resolved on every
|
|
64643
|
+
// connect (picks up renames; retries after a failed fetch).
|
|
64644
|
+
_ownDisplayName = "";
|
|
64645
|
+
// Dedupes concurrent resolves (connect-trigger + lazy DM-trigger) so a burst
|
|
64646
|
+
// of DMs can't stampede /hub/identities.
|
|
64647
|
+
_resolvingOwnDisplayName = null;
|
|
64624
64648
|
_httpServer = null;
|
|
64625
64649
|
_mcpServer = null;
|
|
64626
64650
|
_pollFallbackTimer = null;
|
|
@@ -64652,7 +64676,7 @@ var init_channel = __esm2({
|
|
|
64652
64676
|
_pendingMlsKpBundle;
|
|
64653
64677
|
/**
|
|
64654
64678
|
* Pool of pending KeyPackage bundles this device has published but not yet
|
|
64655
|
-
* consumed by a Welcome. The backend
|
|
64679
|
+
* consumed by a Welcome. The backend consumes published KeyPackages newest-first (#508),
|
|
64656
64680
|
* so a given room's creator may have been served ANY of these — `_handleMlsWelcome`
|
|
64657
64681
|
* trial-decrypts the incoming Welcome against every bundle in the pool (plus the
|
|
64658
64682
|
* connect KP and the persisted per-group bundle). Capped at `_KP_POOL_TARGET`;
|
|
@@ -64890,6 +64914,50 @@ var init_channel = __esm2({
|
|
|
64890
64914
|
get deviceId() {
|
|
64891
64915
|
return this._deviceId;
|
|
64892
64916
|
}
|
|
64917
|
+
/** This agent's AgentVault display_name (#499, A1). "" until resolved. */
|
|
64918
|
+
get ownDisplayName() {
|
|
64919
|
+
return this._ownDisplayName;
|
|
64920
|
+
}
|
|
64921
|
+
/** This agent's AgentVault hub_address (#499, A1). "" until known. */
|
|
64922
|
+
get ownHubAddress() {
|
|
64923
|
+
return this._persisted?.hubAddress ?? "";
|
|
64924
|
+
}
|
|
64925
|
+
/** Public best-effort trigger to (re)resolve this agent's display_name.
|
|
64926
|
+
* Called lazily from the 1:1 DM path when the name is still unknown, so a
|
|
64927
|
+
* resolve that failed at connect (or a fresh agent that hadn't registered
|
|
64928
|
+
* yet) is retried without waiting for the next WS reconnect (#499, A1). */
|
|
64929
|
+
ensureOwnIdentityResolved() {
|
|
64930
|
+
return this._resolveOwnDisplayName();
|
|
64931
|
+
}
|
|
64932
|
+
/** Resolve this agent's own display_name from GET /api/v1/hub/identities
|
|
64933
|
+
* (matches its own device_id). Deduped — concurrent callers share one
|
|
64934
|
+
* in-flight fetch. Refreshes the cached value on success (picks up a rename);
|
|
64935
|
+
* on failure leaves the prior value intact. Best-effort — never throws.
|
|
64936
|
+
* Mirrors Hermes RestClient.get_own_identity (#499, Component 0). */
|
|
64937
|
+
_resolveOwnDisplayName() {
|
|
64938
|
+
if (this._resolvingOwnDisplayName) return this._resolvingOwnDisplayName;
|
|
64939
|
+
const p2 = (async () => {
|
|
64940
|
+
const jwt22 = this._persisted?.deviceJwt ?? this._deviceJwt;
|
|
64941
|
+
const deviceId = this._persisted?.deviceId ?? this._deviceId;
|
|
64942
|
+
if (!jwt22 || !deviceId) return;
|
|
64943
|
+
try {
|
|
64944
|
+
const res = await fetch(`${this.config.apiUrl}/api/v1/hub/identities`, {
|
|
64945
|
+
headers: { Authorization: `Bearer ${jwt22}` }
|
|
64946
|
+
});
|
|
64947
|
+
if (!res.ok) return;
|
|
64948
|
+
const list = await res.json();
|
|
64949
|
+
if (!Array.isArray(list)) return;
|
|
64950
|
+
const mine = list.find((x22) => x22?.device_id === deviceId);
|
|
64951
|
+
if (mine?.display_name) this._ownDisplayName = String(mine.display_name);
|
|
64952
|
+
} catch {
|
|
64953
|
+
}
|
|
64954
|
+
})();
|
|
64955
|
+
this._resolvingOwnDisplayName = p2;
|
|
64956
|
+
void p2.finally(() => {
|
|
64957
|
+
if (this._resolvingOwnDisplayName === p2) this._resolvingOwnDisplayName = null;
|
|
64958
|
+
});
|
|
64959
|
+
return p2;
|
|
64960
|
+
}
|
|
64893
64961
|
get fingerprint() {
|
|
64894
64962
|
return this._fingerprint;
|
|
64895
64963
|
}
|
|
@@ -65434,7 +65502,7 @@ var init_channel = __esm2({
|
|
|
65434
65502
|
*/
|
|
65435
65503
|
sendActivitySpan(spanData) {
|
|
65436
65504
|
if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
|
|
65437
|
-
const pluginVersion = true ? "0.23.
|
|
65505
|
+
const pluginVersion = true ? "0.23.2" : "0.0.0-dev";
|
|
65438
65506
|
const agentName = this.config.agentName ?? "Agent";
|
|
65439
65507
|
const resource = {
|
|
65440
65508
|
"service.name": "agentvault-agent",
|
|
@@ -66413,6 +66481,11 @@ var init_channel = __esm2({
|
|
|
66413
66481
|
if (!this._persisted?.hubAddress) {
|
|
66414
66482
|
throw new Error("This agent does not have a hub address assigned");
|
|
66415
66483
|
}
|
|
66484
|
+
if (_isSelfHubAddress(this._persisted.hubAddress, responderHubAddress)) {
|
|
66485
|
+
throw new Error(
|
|
66486
|
+
"Refusing to open an A2A channel to this agent's own hub address (self-reference). The owner is addressing you by your own name \u2014 reply to them directly instead of opening a channel to yourself."
|
|
66487
|
+
);
|
|
66488
|
+
}
|
|
66416
66489
|
if (!this._deviceJwt) {
|
|
66417
66490
|
throw new Error("Channel not authenticated");
|
|
66418
66491
|
}
|
|
@@ -66493,6 +66566,11 @@ var init_channel = __esm2({
|
|
|
66493
66566
|
* Falls back to plaintext for channels without a session (legacy/pre-encryption).
|
|
66494
66567
|
*/
|
|
66495
66568
|
async sendToAgent(hubAddress, text, opts) {
|
|
66569
|
+
if (_isSelfHubAddress(this._persisted?.hubAddress ?? "", hubAddress)) {
|
|
66570
|
+
throw new Error(
|
|
66571
|
+
"Refusing to send an A2A message to this agent's own hub address (self-reference) \u2014 reply to the owner directly."
|
|
66572
|
+
);
|
|
66573
|
+
}
|
|
66496
66574
|
if (!this._persisted?.a2aChannels) {
|
|
66497
66575
|
try {
|
|
66498
66576
|
await this.listA2AChannels();
|
|
@@ -67041,7 +67119,7 @@ var init_channel = __esm2({
|
|
|
67041
67119
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67042
67120
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67043
67121
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67044
|
-
pluginVersion: true ? "0.23.
|
|
67122
|
+
pluginVersion: true ? "0.23.2" : "0.0.0-dev"
|
|
67045
67123
|
});
|
|
67046
67124
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67047
67125
|
}
|
|
@@ -67117,8 +67195,7 @@ var init_channel = __esm2({
|
|
|
67117
67195
|
return;
|
|
67118
67196
|
}
|
|
67119
67197
|
if (data.event === "device_revoked") {
|
|
67120
|
-
await
|
|
67121
|
-
this._handleError(new Error("Device was revoked"));
|
|
67198
|
+
await this._handleDeviceRevoked();
|
|
67122
67199
|
return;
|
|
67123
67200
|
}
|
|
67124
67201
|
if (data.event === "device_linked") {
|
|
@@ -67342,6 +67419,7 @@ var init_channel = __esm2({
|
|
|
67342
67419
|
this._persisted.agentHubId = data.data.hub_id;
|
|
67343
67420
|
this._persisted.agentRole = data.data.agent_role ?? "peer";
|
|
67344
67421
|
if (changed) this._persistState();
|
|
67422
|
+
void this._resolveOwnDisplayName();
|
|
67345
67423
|
if (!this._telemetryReporter && this._persisted.deviceJwt && this._persisted.hubId) {
|
|
67346
67424
|
this._telemetryReporter = new TelemetryReporter({
|
|
67347
67425
|
apiBase: this.config.apiUrl,
|
|
@@ -67351,7 +67429,7 @@ var init_channel = __esm2({
|
|
|
67351
67429
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67352
67430
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67353
67431
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67354
|
-
pluginVersion: true ? "0.23.
|
|
67432
|
+
pluginVersion: true ? "0.23.2" : "0.0.0-dev"
|
|
67355
67433
|
});
|
|
67356
67434
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67357
67435
|
}
|
|
@@ -70516,6 +70594,76 @@ ${messageText}`;
|
|
|
70516
70594
|
this._setState("error");
|
|
70517
70595
|
this.emit("error", err);
|
|
70518
70596
|
}
|
|
70597
|
+
// Phase 2 — rolling-window timestamps of device_revoked self-heal reconnects,
|
|
70598
|
+
// used as a war cap so a takeover war or a server status/WS inconsistency can't
|
|
70599
|
+
// thrash forever.
|
|
70600
|
+
_revokeRecoveries = [];
|
|
70601
|
+
static REVOKE_RECOVERY_MAX = 3;
|
|
70602
|
+
static REVOKE_RECOVERY_WINDOW_MS = 12e4;
|
|
70603
|
+
/**
|
|
70604
|
+
* Handle a WS `device_revoked` event.
|
|
70605
|
+
*
|
|
70606
|
+
* The signal is ambiguous: the server sends it both for a genuine owner revoke
|
|
70607
|
+
* AND for a transient/racy session-takeover of a device that is still ACTIVE
|
|
70608
|
+
* (the error text even reads "or another session has taken over" — loopita's
|
|
70609
|
+
* case, where the device stayed ACTIVE on the server). So we do NOT terminate
|
|
70610
|
+
* blindly. We ask the PUBLIC /devices/{id}/status endpoint (no JWT needed, so
|
|
70611
|
+
* it answers even when our device JWT is being rejected):
|
|
70612
|
+
* - ACTIVE -> reconnect in place (transient); stay online.
|
|
70613
|
+
* - non-ACTIVE -> terminal (genuinely revoked; operator must re-enroll).
|
|
70614
|
+
* - unreachable/429 -> reconnect (inconclusive; a network blip shouldn't
|
|
70615
|
+
* permanently kill a healthy agent — the WS is down too,
|
|
70616
|
+
* so normal backoff handles it, and the war cap bounds
|
|
70617
|
+
* thrash if the device really is dead).
|
|
70618
|
+
*
|
|
70619
|
+
* A rolling-window cap (REVOKE_RECOVERY_MAX self-heals per WINDOW_MS) gives up
|
|
70620
|
+
* (terminal) on a repeating loop. Never clears credentials in any branch —
|
|
70621
|
+
* re-enrollment, which SHOULD wipe creds, goes through setup --force /
|
|
70622
|
+
* _forceReEnroll in start().
|
|
70623
|
+
*/
|
|
70624
|
+
async _handleDeviceRevoked() {
|
|
70625
|
+
const now = Date.now();
|
|
70626
|
+
this._revokeRecoveries = this._revokeRecoveries.filter(
|
|
70627
|
+
(t22) => now - t22 < _SecureChannel.REVOKE_RECOVERY_WINDOW_MS
|
|
70628
|
+
);
|
|
70629
|
+
if (this._revokeRecoveries.length >= _SecureChannel.REVOKE_RECOVERY_MAX) {
|
|
70630
|
+
console.warn(
|
|
70631
|
+
"[SecureChannel] device_revoked self-healed too many times in a short window \u2014 giving up (terminal)"
|
|
70632
|
+
);
|
|
70633
|
+
this._handleError(new Error("Device was revoked"));
|
|
70634
|
+
return;
|
|
70635
|
+
}
|
|
70636
|
+
const deviceId = this._deviceId ?? this._persisted?.deviceId;
|
|
70637
|
+
if (!deviceId) {
|
|
70638
|
+
this._handleError(new Error("Device was revoked"));
|
|
70639
|
+
return;
|
|
70640
|
+
}
|
|
70641
|
+
let reconnect;
|
|
70642
|
+
try {
|
|
70643
|
+
const status = await pollDeviceStatus(this.config.apiUrl, deviceId);
|
|
70644
|
+
reconnect = status.rateLimited === true || status.status === "ACTIVE";
|
|
70645
|
+
if (!reconnect) {
|
|
70646
|
+
console.warn(
|
|
70647
|
+
`[SecureChannel] device_revoked confirmed by server (status=${status.status}) \u2014 terminal`
|
|
70648
|
+
);
|
|
70649
|
+
}
|
|
70650
|
+
} catch (err) {
|
|
70651
|
+
console.warn(
|
|
70652
|
+
"[SecureChannel] device_revoked status check failed; reconnecting (inconclusive):",
|
|
70653
|
+
err
|
|
70654
|
+
);
|
|
70655
|
+
reconnect = true;
|
|
70656
|
+
}
|
|
70657
|
+
if (reconnect) {
|
|
70658
|
+
this._revokeRecoveries.push(now);
|
|
70659
|
+
console.log(
|
|
70660
|
+
"[SecureChannel] device_revoked but device not confirmed dead \u2014 reconnecting (self-heal)"
|
|
70661
|
+
);
|
|
70662
|
+
this._scheduleReconnect();
|
|
70663
|
+
return;
|
|
70664
|
+
}
|
|
70665
|
+
this._handleError(new Error("Device was revoked"));
|
|
70666
|
+
}
|
|
70519
70667
|
/**
|
|
70520
70668
|
* Persist all ratchet session states to disk.
|
|
70521
70669
|
* Syncs live ratchet states back into the persisted sessions map.
|
|
@@ -97028,7 +97176,7 @@ var init_index = __esm2({
|
|
|
97028
97176
|
init_skill_invoker();
|
|
97029
97177
|
await init_skill_telemetry();
|
|
97030
97178
|
await init_policy_enforcer();
|
|
97031
|
-
VERSION = true ? "0.23.
|
|
97179
|
+
VERSION = true ? "0.23.2" : "0.0.0-dev";
|
|
97032
97180
|
}
|
|
97033
97181
|
});
|
|
97034
97182
|
await init_index();
|
|
@@ -132488,7 +132636,7 @@ var ArmingState = class {
|
|
|
132488
132636
|
};
|
|
132489
132637
|
|
|
132490
132638
|
// src/approve-cli.ts
|
|
132491
|
-
import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as
|
|
132639
|
+
import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync4, readdirSync as readdirSync2, rmSync as rmSync2, existsSync as existsSync3 } from "node:fs";
|
|
132492
132640
|
import { join as join6 } from "node:path";
|
|
132493
132641
|
var APPROVALS_SUBDIR = "arm-approvals";
|
|
132494
132642
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
@@ -132521,7 +132669,7 @@ function drainApprovals(dataDir) {
|
|
|
132521
132669
|
if (!ID_RE.test(name)) continue;
|
|
132522
132670
|
let roomId = "";
|
|
132523
132671
|
try {
|
|
132524
|
-
roomId =
|
|
132672
|
+
roomId = readFileSync4(join6(dir, name), "utf8").trim();
|
|
132525
132673
|
} catch {
|
|
132526
132674
|
}
|
|
132527
132675
|
out.push({ requestId: name, roomId: ID_RE.test(roomId) ? roomId : "" });
|
|
@@ -132770,7 +132918,7 @@ async function main() {
|
|
|
132770
132918
|
"[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"
|
|
132771
132919
|
);
|
|
132772
132920
|
}
|
|
132773
|
-
console.error(`[bridge] version: ${true ? "0.5.
|
|
132921
|
+
console.error(`[bridge] version: ${true ? "0.5.5" : "dev"}`);
|
|
132774
132922
|
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
132775
132923
|
if (cfg.worker) {
|
|
132776
132924
|
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.5",
|
|
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",
|