@agentvault/claude-bridge 0.5.2 → 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 +108 -17
- 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
|
|
|
@@ -332,7 +342,7 @@ var init_systemd = __esm({
|
|
|
332
342
|
});
|
|
333
343
|
|
|
334
344
|
// src/service/backend.ts
|
|
335
|
-
import {
|
|
345
|
+
import { spawnSync } from "node:child_process";
|
|
336
346
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, rmSync as rmSync3, existsSync as existsSync4, chmodSync } from "node:fs";
|
|
337
347
|
import { userInfo } from "node:os";
|
|
338
348
|
function defaultDeps() {
|
|
@@ -341,13 +351,8 @@ function defaultDeps() {
|
|
|
341
351
|
uid: userInfo().uid,
|
|
342
352
|
user: userInfo().username,
|
|
343
353
|
exec: (cmd, args) => {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
return { code: 0, stdout };
|
|
347
|
-
} catch (e7) {
|
|
348
|
-
const err = e7;
|
|
349
|
-
return { code: err.status ?? 1, stdout: String(err.stdout ?? "") };
|
|
350
|
-
}
|
|
354
|
+
const r7 = spawnSync(cmd, args, { encoding: "utf8" });
|
|
355
|
+
return { code: r7.status ?? 1, stdout: r7.stdout ?? "" };
|
|
351
356
|
},
|
|
352
357
|
writeFile: (p2, d10) => writeFileSync2(p2, d10),
|
|
353
358
|
mkdir: (p2) => mkdirSync3(p2, { recursive: true }),
|
|
@@ -64555,6 +64560,11 @@ function migratePersistedState(raw) {
|
|
|
64555
64560
|
messageHistory: []
|
|
64556
64561
|
};
|
|
64557
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
|
+
}
|
|
64558
64568
|
var ROOM_AGENT_TYPES;
|
|
64559
64569
|
var CREDENTIAL_MESSAGE_TYPES;
|
|
64560
64570
|
var CATCHUP_MESSAGE_TYPES;
|
|
@@ -64626,6 +64636,15 @@ var init_channel = __esm2({
|
|
|
64626
64636
|
_ackTimer = null;
|
|
64627
64637
|
_stopped = false;
|
|
64628
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;
|
|
64629
64648
|
_httpServer = null;
|
|
64630
64649
|
_mcpServer = null;
|
|
64631
64650
|
_pollFallbackTimer = null;
|
|
@@ -64657,7 +64676,7 @@ var init_channel = __esm2({
|
|
|
64657
64676
|
_pendingMlsKpBundle;
|
|
64658
64677
|
/**
|
|
64659
64678
|
* Pool of pending KeyPackage bundles this device has published but not yet
|
|
64660
|
-
* consumed by a Welcome. The backend
|
|
64679
|
+
* consumed by a Welcome. The backend consumes published KeyPackages newest-first (#508),
|
|
64661
64680
|
* so a given room's creator may have been served ANY of these — `_handleMlsWelcome`
|
|
64662
64681
|
* trial-decrypts the incoming Welcome against every bundle in the pool (plus the
|
|
64663
64682
|
* connect KP and the persisted per-group bundle). Capped at `_KP_POOL_TARGET`;
|
|
@@ -64895,6 +64914,50 @@ var init_channel = __esm2({
|
|
|
64895
64914
|
get deviceId() {
|
|
64896
64915
|
return this._deviceId;
|
|
64897
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
|
+
}
|
|
64898
64961
|
get fingerprint() {
|
|
64899
64962
|
return this._fingerprint;
|
|
64900
64963
|
}
|
|
@@ -66418,6 +66481,11 @@ var init_channel = __esm2({
|
|
|
66418
66481
|
if (!this._persisted?.hubAddress) {
|
|
66419
66482
|
throw new Error("This agent does not have a hub address assigned");
|
|
66420
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
|
+
}
|
|
66421
66489
|
if (!this._deviceJwt) {
|
|
66422
66490
|
throw new Error("Channel not authenticated");
|
|
66423
66491
|
}
|
|
@@ -66498,6 +66566,11 @@ var init_channel = __esm2({
|
|
|
66498
66566
|
* Falls back to plaintext for channels without a session (legacy/pre-encryption).
|
|
66499
66567
|
*/
|
|
66500
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
|
+
}
|
|
66501
66574
|
if (!this._persisted?.a2aChannels) {
|
|
66502
66575
|
try {
|
|
66503
66576
|
await this.listA2AChannels();
|
|
@@ -67122,8 +67195,7 @@ var init_channel = __esm2({
|
|
|
67122
67195
|
return;
|
|
67123
67196
|
}
|
|
67124
67197
|
if (data.event === "device_revoked") {
|
|
67125
|
-
|
|
67126
|
-
this._handleError(new Error("Device was revoked"));
|
|
67198
|
+
this._handleDeviceRevoked();
|
|
67127
67199
|
return;
|
|
67128
67200
|
}
|
|
67129
67201
|
if (data.event === "device_linked") {
|
|
@@ -67347,6 +67419,7 @@ var init_channel = __esm2({
|
|
|
67347
67419
|
this._persisted.agentHubId = data.data.hub_id;
|
|
67348
67420
|
this._persisted.agentRole = data.data.agent_role ?? "peer";
|
|
67349
67421
|
if (changed) this._persistState();
|
|
67422
|
+
void this._resolveOwnDisplayName();
|
|
67350
67423
|
if (!this._telemetryReporter && this._persisted.deviceJwt && this._persisted.hubId) {
|
|
67351
67424
|
this._telemetryReporter = new TelemetryReporter({
|
|
67352
67425
|
apiBase: this.config.apiUrl,
|
|
@@ -70521,6 +70594,24 @@ ${messageText}`;
|
|
|
70521
70594
|
this._setState("error");
|
|
70522
70595
|
this.emit("error", err);
|
|
70523
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
|
+
}
|
|
70524
70615
|
/**
|
|
70525
70616
|
* Persist all ratchet session states to disk.
|
|
70526
70617
|
* Syncs live ratchet states back into the persisted sessions map.
|
|
@@ -132493,7 +132584,7 @@ var ArmingState = class {
|
|
|
132493
132584
|
};
|
|
132494
132585
|
|
|
132495
132586
|
// src/approve-cli.ts
|
|
132496
|
-
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";
|
|
132497
132588
|
import { join as join6 } from "node:path";
|
|
132498
132589
|
var APPROVALS_SUBDIR = "arm-approvals";
|
|
132499
132590
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
@@ -132526,7 +132617,7 @@ function drainApprovals(dataDir) {
|
|
|
132526
132617
|
if (!ID_RE.test(name)) continue;
|
|
132527
132618
|
let roomId = "";
|
|
132528
132619
|
try {
|
|
132529
|
-
roomId =
|
|
132620
|
+
roomId = readFileSync4(join6(dir, name), "utf8").trim();
|
|
132530
132621
|
} catch {
|
|
132531
132622
|
}
|
|
132532
132623
|
out.push({ requestId: name, roomId: ID_RE.test(roomId) ? roomId : "" });
|
|
@@ -132775,7 +132866,7 @@ async function main() {
|
|
|
132775
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"
|
|
132776
132867
|
);
|
|
132777
132868
|
}
|
|
132778
|
-
console.error(`[bridge] version: ${true ? "0.5.
|
|
132869
|
+
console.error(`[bridge] version: ${true ? "0.5.4" : "dev"}`);
|
|
132779
132870
|
console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
132780
132871
|
if (cfg.worker) {
|
|
132781
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",
|