@agentvault/claude-bridge 0.5.3 → 0.5.4
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 +105 -9
- 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
|
}
|
|
@@ -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();
|
|
@@ -67117,8 +67195,7 @@ var init_channel = __esm2({
|
|
|
67117
67195
|
return;
|
|
67118
67196
|
}
|
|
67119
67197
|
if (data.event === "device_revoked") {
|
|
67120
|
-
|
|
67121
|
-
this._handleError(new Error("Device was revoked"));
|
|
67198
|
+
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,
|
|
@@ -70516,6 +70594,24 @@ ${messageText}`;
|
|
|
70516
70594
|
this._setState("error");
|
|
70517
70595
|
this.emit("error", err);
|
|
70518
70596
|
}
|
|
70597
|
+
/**
|
|
70598
|
+
* Handle a WS `device_revoked` event. Surfaces a terminal error so
|
|
70599
|
+
* attachLifecycle stops the channel — but does NOT delete credentials.
|
|
70600
|
+
*
|
|
70601
|
+
* This signal fires both for a genuine owner revoke AND for a transient
|
|
70602
|
+
* session-takeover of a device that is still ACTIVE on the server (the error
|
|
70603
|
+
* text even reads "or another session has taken over"). Deleting
|
|
70604
|
+
* agentvault.json here — as this path used to via clearState() — strands a
|
|
70605
|
+
* still-valid identity: the primary creds vanish, only the .bak survives, and
|
|
70606
|
+
* the bridge's token-gate then demands a fresh invite token to restart.
|
|
70607
|
+
* Keeping the creds means a restart reconnects via restoreState() when the
|
|
70608
|
+
* device is still valid, and fails cleanly (device_revoked again, no data
|
|
70609
|
+
* lost) when it isn't. Re-enrollment, which SHOULD wipe creds, goes through
|
|
70610
|
+
* the explicit setup --force / _forceReEnroll path in start().
|
|
70611
|
+
*/
|
|
70612
|
+
_handleDeviceRevoked() {
|
|
70613
|
+
this._handleError(new Error("Device was revoked"));
|
|
70614
|
+
}
|
|
70519
70615
|
/**
|
|
70520
70616
|
* Persist all ratchet session states to disk.
|
|
70521
70617
|
* Syncs live ratchet states back into the persisted sessions map.
|
|
@@ -132488,7 +132584,7 @@ var ArmingState = class {
|
|
|
132488
132584
|
};
|
|
132489
132585
|
|
|
132490
132586
|
// src/approve-cli.ts
|
|
132491
|
-
import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as
|
|
132587
|
+
import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync4, readdirSync as readdirSync2, rmSync as rmSync2, existsSync as existsSync3 } from "node:fs";
|
|
132492
132588
|
import { join as join6 } from "node:path";
|
|
132493
132589
|
var APPROVALS_SUBDIR = "arm-approvals";
|
|
132494
132590
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
@@ -132521,7 +132617,7 @@ function drainApprovals(dataDir) {
|
|
|
132521
132617
|
if (!ID_RE.test(name)) continue;
|
|
132522
132618
|
let roomId = "";
|
|
132523
132619
|
try {
|
|
132524
|
-
roomId =
|
|
132620
|
+
roomId = readFileSync4(join6(dir, name), "utf8").trim();
|
|
132525
132621
|
} catch {
|
|
132526
132622
|
}
|
|
132527
132623
|
out.push({ requestId: name, roomId: ID_RE.test(roomId) ? roomId : "" });
|
|
@@ -132770,7 +132866,7 @@ async function main() {
|
|
|
132770
132866
|
"[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
132867
|
);
|
|
132772
132868
|
}
|
|
132773
|
-
console.error(`[bridge] version: ${true ? "0.5.
|
|
132869
|
+
console.error(`[bridge] version: ${true ? "0.5.4" : "dev"}`);
|
|
132774
132870
|
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
132775
132871
|
if (cfg.worker) {
|
|
132776
132872
|
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.4",
|
|
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",
|