@agentvault/claude-bridge 0.7.16 → 0.8.0
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 +9 -0
- package/dist/config.d.ts +44 -0
- package/dist/index.js +629 -70
- package/dist/log.d.ts +12 -0
- package/dist/pending-invite.d.ts +24 -0
- package/dist/service/backend.d.ts +15 -0
- package/dist/service/backend.d.ts.map +1 -1
- package/dist/service/doctor-cli.d.ts +36 -0
- package/dist/service/doctor-cli.d.ts.map +1 -0
- package/dist/service/doctor.d.ts +93 -0
- package/dist/service/doctor.d.ts.map +1 -0
- package/dist/service/launchd.d.ts +27 -1
- package/dist/service/launchd.d.ts.map +1 -1
- package/dist/service/self-uninstall.d.ts +19 -0
- package/dist/service/self-uninstall.d.ts.map +1 -0
- package/dist/service/subcommand.d.ts +12 -0
- package/dist/service/subcommand.d.ts.map +1 -1
- package/dist/service/systemd.d.ts +19 -1
- package/dist/service/systemd.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,36 +9,138 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
// src/pending-invite.ts
|
|
13
|
+
import { existsSync, mkdirSync, chmodSync, readFileSync as readFileSync2, writeFileSync, rmSync } from "node:fs";
|
|
14
|
+
import { join as join5 } from "node:path";
|
|
15
|
+
function pendingInvitePath(dataDir) {
|
|
16
|
+
return join5(dataDir, PENDING_INVITE_FILE);
|
|
17
|
+
}
|
|
18
|
+
function writePendingInvite(dataDir, token) {
|
|
19
|
+
mkdirSync(dataDir, { recursive: true, mode: DIR_MODE2 });
|
|
20
|
+
chmodSync(dataDir, DIR_MODE2);
|
|
21
|
+
const p2 = pendingInvitePath(dataDir);
|
|
22
|
+
writeFileSync(p2, token, { mode: FILE_MODE2 });
|
|
23
|
+
chmodSync(p2, FILE_MODE2);
|
|
24
|
+
}
|
|
25
|
+
function readPendingInvite(dataDir) {
|
|
26
|
+
const p2 = pendingInvitePath(dataDir);
|
|
27
|
+
if (!existsSync(p2)) return void 0;
|
|
28
|
+
try {
|
|
29
|
+
const raw = readFileSync2(p2, "utf-8").trim();
|
|
30
|
+
return raw.length > 0 ? raw : void 0;
|
|
31
|
+
} catch {
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function clearPendingInvite(dataDir) {
|
|
36
|
+
try {
|
|
37
|
+
rmSync(pendingInvitePath(dataDir), { force: true });
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function clearPendingInviteIfConsumed(dataDir, credsExist) {
|
|
42
|
+
if (!existsSync(pendingInvitePath(dataDir))) return false;
|
|
43
|
+
if (!credsExist(dataDir)) return false;
|
|
44
|
+
clearPendingInvite(dataDir);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
var DIR_MODE2, FILE_MODE2, PENDING_INVITE_FILE;
|
|
48
|
+
var init_pending_invite = __esm({
|
|
49
|
+
"src/pending-invite.ts"() {
|
|
50
|
+
"use strict";
|
|
51
|
+
DIR_MODE2 = 448;
|
|
52
|
+
FILE_MODE2 = 384;
|
|
53
|
+
PENDING_INVITE_FILE = "pending-invite";
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// src/log.ts
|
|
58
|
+
import { format } from "node:util";
|
|
59
|
+
function stamp(message) {
|
|
60
|
+
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
61
|
+
return message.split("\n").map((line) => `${ts2} ${line}`).join("\n");
|
|
62
|
+
}
|
|
63
|
+
function logLine(...args) {
|
|
64
|
+
console.error(stamp(format(...args)));
|
|
65
|
+
}
|
|
66
|
+
function printLine(text) {
|
|
67
|
+
console.log(text);
|
|
68
|
+
}
|
|
69
|
+
var init_log = __esm({
|
|
70
|
+
"src/log.ts"() {
|
|
71
|
+
"use strict";
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
12
75
|
// src/config.ts
|
|
13
|
-
import { existsSync, readFileSync as
|
|
14
|
-
import { join as
|
|
76
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
77
|
+
import { join as join6, resolve as resolve2, sep, dirname } from "node:path";
|
|
78
|
+
import { tmpdir } from "node:os";
|
|
79
|
+
function isEphemeralEntrypoint(entrypoint) {
|
|
80
|
+
return entrypoint.includes("/_npx/") || entrypoint.includes("/dlx-") || entrypoint.includes("/.bun/install/cache/") || entrypoint.startsWith(tmpdir() + sep);
|
|
81
|
+
}
|
|
82
|
+
function assertInstallableEntrypoint(entrypoint) {
|
|
83
|
+
if (isEphemeralEntrypoint(entrypoint)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`cannot install a background service from an ephemeral package cache (${entrypoint}) \u2014 it is deleted on cleanup and the service would silently die later. Install globally first:
|
|
86
|
+
npm i -g @agentvault/claude-bridge && agentvault-claude-bridge install`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
15
90
|
function hasRecoverableBackup(dataDir) {
|
|
16
91
|
try {
|
|
17
|
-
const parsed = JSON.parse(
|
|
92
|
+
const parsed = JSON.parse(readFileSync3(join6(dataDir, BACKUP_FILE), "utf-8"));
|
|
18
93
|
return !!(parsed && parsed.deviceId && parsed.deviceJwt && parsed.sessions && Object.keys(parsed.sessions).length > 0);
|
|
19
94
|
} catch {
|
|
20
95
|
return false;
|
|
21
96
|
}
|
|
22
97
|
}
|
|
98
|
+
function readPersistedDeviceId(dataDir) {
|
|
99
|
+
for (const f7 of [...CRED_FILES, BACKUP_FILE]) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = JSON.parse(readFileSync3(join6(dataDir, f7), "utf-8"));
|
|
102
|
+
const id = parsed?.deviceId;
|
|
103
|
+
if (typeof id === "string" && id) return id;
|
|
104
|
+
} catch {
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
23
109
|
function hasPersistedCreds(dataDir) {
|
|
24
|
-
if (CRED_FILES.some((f7) =>
|
|
110
|
+
if (CRED_FILES.some((f7) => existsSync2(join6(dataDir, f7)))) return true;
|
|
25
111
|
return hasRecoverableBackup(dataDir);
|
|
26
112
|
}
|
|
27
113
|
function slugify2(name) {
|
|
28
114
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
29
115
|
}
|
|
30
116
|
function resolveDataDir(env) {
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
117
|
+
const supplied = env.AV_AGENT_NAME?.trim();
|
|
118
|
+
const agentName = supplied || LEGACY_DEFAULT_AGENT_NAME;
|
|
119
|
+
const agentNameSource = supplied ? "supplied" : "defaulted";
|
|
120
|
+
if (env.AV_DATA_DIR)
|
|
121
|
+
return { dataDir: env.AV_DATA_DIR, source: "explicit", agentName, agentNameSource };
|
|
122
|
+
const base = join6(env.HOME ?? "", ".agentvault", "claude-room-bridge");
|
|
123
|
+
const perAgent = join6(base, slugify2(agentName));
|
|
124
|
+
if (hasPersistedCreds(perAgent))
|
|
125
|
+
return { dataDir: perAgent, source: "per-agent", agentName, agentNameSource };
|
|
126
|
+
if (hasPersistedCreds(base))
|
|
127
|
+
return { dataDir: base, source: "legacy", agentName, agentNameSource };
|
|
128
|
+
if (!supplied) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`AV_AGENT_NAME is required \u2014 it names WHICH agent this bridge runs as, and therefore where its credentials live. No credentials were found at ${perAgent} or ${base}, so there is no existing install to infer a name from. Re-run with AV_AGENT_NAME=<agent-name> set.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return { dataDir: perAgent, source: "per-agent", agentName, agentNameSource };
|
|
37
134
|
}
|
|
38
135
|
function loadConfig(env, argv = []) {
|
|
39
|
-
const { dataDir, source: dataDirSource } = resolveDataDir(env);
|
|
40
|
-
|
|
41
|
-
|
|
136
|
+
const { dataDir, source: dataDirSource, agentName, agentNameSource } = resolveDataDir(env);
|
|
137
|
+
if (agentNameSource === "defaulted") {
|
|
138
|
+
logLine(
|
|
139
|
+
`[config] AV_AGENT_NAME was not set \u2014 running as "${agentName}" from the existing credentials at ${dataDir}. Set AV_AGENT_NAME to name this agent explicitly; a future install will require it.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || readPendingInvite(dataDir) || "";
|
|
143
|
+
const workspaceDir = env.AV_WORKSPACE_DIR || join6(dirname(dataDir), "workspaces", slugify2(agentName));
|
|
42
144
|
const wsReal = resolve2(workspaceDir);
|
|
43
145
|
const ddReal = resolve2(dataDir);
|
|
44
146
|
if (wsReal === ddReal || wsReal.startsWith(ddReal + sep) || ddReal.startsWith(wsReal + sep)) {
|
|
@@ -47,8 +149,8 @@ function loadConfig(env, argv = []) {
|
|
|
47
149
|
);
|
|
48
150
|
}
|
|
49
151
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
152
|
+
mkdirSync2(workspaceDir, { recursive: true, mode: 448 });
|
|
153
|
+
chmodSync2(workspaceDir, 448);
|
|
52
154
|
} catch (e7) {
|
|
53
155
|
throw new Error(
|
|
54
156
|
`Failed to create/secure the agent workspace at ${workspaceDir} (override with AV_WORKSPACE_DIR): ${e7.message}`
|
|
@@ -83,8 +185,10 @@ function loadConfig(env, argv = []) {
|
|
|
83
185
|
apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
|
|
84
186
|
// Identity shown to the agent's own session. Set this to the agent's
|
|
85
187
|
// AgentVault name via AV_AGENT_NAME (the native connect command passes it).
|
|
86
|
-
//
|
|
87
|
-
|
|
188
|
+
// Resolved in resolveDataDir so the name, the data dir and the workspace dir
|
|
189
|
+
// can never disagree about who this agent is (#921/#927).
|
|
190
|
+
agentName,
|
|
191
|
+
agentNameSource,
|
|
88
192
|
roomFilter: env.AV_ROOM_ID || void 0,
|
|
89
193
|
model: env.AV_CLAUDE_MODEL || void 0,
|
|
90
194
|
systemPrompt: env.AV_SYSTEM_PROMPT || void 0,
|
|
@@ -94,12 +198,15 @@ function loadConfig(env, argv = []) {
|
|
|
94
198
|
osIsolated
|
|
95
199
|
};
|
|
96
200
|
}
|
|
97
|
-
var CRED_FILES, BACKUP_FILE;
|
|
201
|
+
var CRED_FILES, BACKUP_FILE, LEGACY_DEFAULT_AGENT_NAME;
|
|
98
202
|
var init_config = __esm({
|
|
99
203
|
"src/config.ts"() {
|
|
100
204
|
"use strict";
|
|
205
|
+
init_pending_invite();
|
|
206
|
+
init_log();
|
|
101
207
|
CRED_FILES = ["agentvault.json", "secure-channel.json"];
|
|
102
208
|
BACKUP_FILE = "agentvault.json.bak";
|
|
209
|
+
LEGACY_DEFAULT_AGENT_NAME = "Claude";
|
|
103
210
|
}
|
|
104
211
|
});
|
|
105
212
|
|
|
@@ -227,6 +334,37 @@ ${envEntries}
|
|
|
227
334
|
</plist>
|
|
228
335
|
`;
|
|
229
336
|
}
|
|
337
|
+
function parsePlistEnv(xml) {
|
|
338
|
+
const out = {};
|
|
339
|
+
const keyIdx = xml.indexOf("<key>EnvironmentVariables</key>");
|
|
340
|
+
if (keyIdx < 0) return out;
|
|
341
|
+
const open = xml.indexOf("<dict>", keyIdx);
|
|
342
|
+
if (open < 0) return out;
|
|
343
|
+
let depth = 0;
|
|
344
|
+
let i2 = open;
|
|
345
|
+
let close = -1;
|
|
346
|
+
const tag = /<(\/?)dict>/g;
|
|
347
|
+
tag.lastIndex = open;
|
|
348
|
+
let m6;
|
|
349
|
+
while (m6 = tag.exec(xml)) {
|
|
350
|
+
depth += m6[1] ? -1 : 1;
|
|
351
|
+
if (depth === 0) {
|
|
352
|
+
close = m6.index;
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
i2 = m6.index;
|
|
356
|
+
}
|
|
357
|
+
void i2;
|
|
358
|
+
if (close < 0) return out;
|
|
359
|
+
const body = xml.slice(open, close);
|
|
360
|
+
const pair = /<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/g;
|
|
361
|
+
let p2;
|
|
362
|
+
while (p2 = pair.exec(body)) out[unesc(p2[1])] = unesc(p2[2]);
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
function unesc(s10) {
|
|
366
|
+
return s10.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
367
|
+
}
|
|
230
368
|
var LaunchdBackend;
|
|
231
369
|
var init_launchd = __esm({
|
|
232
370
|
"src/service/launchd.ts"() {
|
|
@@ -281,6 +419,26 @@ var init_launchd = __esm({
|
|
|
281
419
|
const r7 = this.deps.exec("launchctl", ["print", `${this.domain()}/${label}`]);
|
|
282
420
|
return { loaded: r7.code === 0, detail: r7.stdout };
|
|
283
421
|
}
|
|
422
|
+
/** #913 item 3 — every installed service whose label starts with `prefix`.
|
|
423
|
+
*
|
|
424
|
+
* Reads the plist directory rather than `launchctl list`, because an ORPHAN
|
|
425
|
+
* is typically installed-but-not-loaded: the revoked device exits 0 and
|
|
426
|
+
* KeepAlive leaves it down. `launchctl list` would not show the very thing
|
|
427
|
+
* doctor exists to find. */
|
|
428
|
+
list(prefix) {
|
|
429
|
+
const dir = join9(this.deps.home, "Library", "LaunchAgents");
|
|
430
|
+
const out = [];
|
|
431
|
+
for (const name of this.deps.readdir(dir)) {
|
|
432
|
+
if (!name.startsWith(prefix) || !name.endsWith(".plist")) continue;
|
|
433
|
+
const path2 = join9(dir, name);
|
|
434
|
+
out.push({
|
|
435
|
+
label: name.slice(0, -".plist".length),
|
|
436
|
+
path: path2,
|
|
437
|
+
env: parsePlistEnv(this.deps.readFile(path2))
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
return out.sort((a2, b5) => a2.label.localeCompare(b5.label));
|
|
441
|
+
}
|
|
284
442
|
};
|
|
285
443
|
}
|
|
286
444
|
});
|
|
@@ -312,6 +470,24 @@ StandardError=append:${spec.logErr}
|
|
|
312
470
|
WantedBy=default.target
|
|
313
471
|
`;
|
|
314
472
|
}
|
|
473
|
+
function parseUnitEnv(text) {
|
|
474
|
+
const out = {};
|
|
475
|
+
for (const line of text.split("\n")) {
|
|
476
|
+
const m6 = /^Environment="([\s\S]*)"\s*$/.exec(line.trim());
|
|
477
|
+
if (!m6) continue;
|
|
478
|
+
const eq2 = unq(m6[1]);
|
|
479
|
+
const at = eq2.indexOf("=");
|
|
480
|
+
if (at <= 0) continue;
|
|
481
|
+
out[eq2.slice(0, at)] = eq2.slice(at + 1);
|
|
482
|
+
}
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
485
|
+
function unq(s10) {
|
|
486
|
+
return s10.replace(/%%/g, "%").replace(
|
|
487
|
+
/\\([\s\S])/g,
|
|
488
|
+
(_4, c4) => c4 === "n" ? "\n" : c4 === "r" ? "\r" : c4 === "t" ? " " : c4
|
|
489
|
+
);
|
|
490
|
+
}
|
|
315
491
|
var SystemdBackend;
|
|
316
492
|
var init_systemd = __esm({
|
|
317
493
|
"src/service/systemd.ts"() {
|
|
@@ -353,13 +529,33 @@ var init_systemd = __esm({
|
|
|
353
529
|
const active = this.deps.exec("systemctl", ["--user", "is-active", `${label}.service`]);
|
|
354
530
|
return { loaded: enabled.code === 0, detail: `${enabled.stdout.trim() || "?"} / ${active.stdout.trim() || "?"}` };
|
|
355
531
|
}
|
|
532
|
+
/** #913 item 3 — every installed unit whose label starts with `prefix`.
|
|
533
|
+
*
|
|
534
|
+
* Reads the unit directory rather than `systemctl list-units`, because an
|
|
535
|
+
* ORPHAN is installed-but-inactive: the revoked device exits 0, which
|
|
536
|
+
* SuccessExitStatus=0 treats as success, so it is never restarted. A
|
|
537
|
+
* running-units listing would not show the thing doctor exists to find. */
|
|
538
|
+
list(prefix) {
|
|
539
|
+
const dir = join10(this.deps.home, ".config", "systemd", "user");
|
|
540
|
+
const out = [];
|
|
541
|
+
for (const name of this.deps.readdir(dir)) {
|
|
542
|
+
if (!name.startsWith(prefix) || !name.endsWith(".service")) continue;
|
|
543
|
+
const path2 = join10(dir, name);
|
|
544
|
+
out.push({
|
|
545
|
+
label: name.slice(0, -".service".length),
|
|
546
|
+
path: path2,
|
|
547
|
+
env: parseUnitEnv(this.deps.readFile(path2))
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
return out.sort((a2, b5) => a2.label.localeCompare(b5.label));
|
|
551
|
+
}
|
|
356
552
|
};
|
|
357
553
|
}
|
|
358
554
|
});
|
|
359
555
|
|
|
360
556
|
// src/service/backend.ts
|
|
361
557
|
import { spawnSync } from "node:child_process";
|
|
362
|
-
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, rmSync as rmSync4, existsSync as existsSync4, chmodSync as
|
|
558
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, rmSync as rmSync4, existsSync as existsSync4, chmodSync as chmodSync4, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
363
559
|
import { userInfo } from "node:os";
|
|
364
560
|
function defaultDeps() {
|
|
365
561
|
return {
|
|
@@ -372,9 +568,23 @@ function defaultDeps() {
|
|
|
372
568
|
},
|
|
373
569
|
writeFile: (p2, d10) => writeFileSync3(p2, d10),
|
|
374
570
|
mkdir: (p2) => mkdirSync5(p2, { recursive: true }),
|
|
375
|
-
chmod: (p2, m6) =>
|
|
571
|
+
chmod: (p2, m6) => chmodSync4(p2, m6),
|
|
376
572
|
rm: (p2) => rmSync4(p2, { force: true }),
|
|
377
|
-
exists: (p2) => existsSync4(p2)
|
|
573
|
+
exists: (p2) => existsSync4(p2),
|
|
574
|
+
readdir: (p2) => {
|
|
575
|
+
try {
|
|
576
|
+
return readdirSync3(p2);
|
|
577
|
+
} catch {
|
|
578
|
+
return [];
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
readFile: (p2) => {
|
|
582
|
+
try {
|
|
583
|
+
return readFileSync6(p2, "utf8");
|
|
584
|
+
} catch {
|
|
585
|
+
return "";
|
|
586
|
+
}
|
|
587
|
+
}
|
|
378
588
|
};
|
|
379
589
|
}
|
|
380
590
|
function selectBackend(platform, deps) {
|
|
@@ -397,7 +607,8 @@ var init_backend = __esm({
|
|
|
397
607
|
// src/service/subcommand.ts
|
|
398
608
|
var subcommand_exports = {};
|
|
399
609
|
__export(subcommand_exports, {
|
|
400
|
-
maybeRunServiceSubcommand: () => maybeRunServiceSubcommand
|
|
610
|
+
maybeRunServiceSubcommand: () => maybeRunServiceSubcommand,
|
|
611
|
+
resolveLabel: () => resolveLabel
|
|
401
612
|
});
|
|
402
613
|
function requireAgentName(env) {
|
|
403
614
|
const name = env.AV_AGENT_NAME?.trim();
|
|
@@ -429,7 +640,9 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
429
640
|
const looksLikeFlag = cmd.startsWith("-");
|
|
430
641
|
if (!looksLikeToken && !looksLikeFlag) {
|
|
431
642
|
throw new Error(
|
|
432
|
-
|
|
643
|
+
// "doctor" is dispatched earlier, in index.ts, so it never reaches here.
|
|
644
|
+
// Named anyway: a customer who typed `doctr` should see it offered.
|
|
645
|
+
`unknown subcommand: ${cmd} \u2014 expected one of ${[...SERVICE_CMDS, "doctor"].join(", ")}, an invite token, or no argument at all`
|
|
433
646
|
);
|
|
434
647
|
}
|
|
435
648
|
return false;
|
|
@@ -440,11 +653,11 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
440
653
|
try {
|
|
441
654
|
if (cmd === "install") {
|
|
442
655
|
requireAgentName(env);
|
|
656
|
+
assertInstallableEntrypoint(entrypoint);
|
|
443
657
|
const cfg = loadConfig(env, []);
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
);
|
|
658
|
+
const firstBoot = !hasPersistedCreds(cfg.dataDir) && !!cfg.inviteToken;
|
|
659
|
+
if (firstBoot) {
|
|
660
|
+
writePendingInvite(cfg.dataDir, cfg.inviteToken);
|
|
448
661
|
}
|
|
449
662
|
const spec = buildServiceSpec(cfg, {
|
|
450
663
|
entrypoint,
|
|
@@ -455,7 +668,11 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
455
668
|
path: env.PATH ?? ""
|
|
456
669
|
});
|
|
457
670
|
backend.install(spec);
|
|
458
|
-
log(`installed service ${spec.label} \u2014 logs: ${spec.
|
|
671
|
+
log(`installed service ${spec.label} \u2014 logs: ${spec.logErr}`);
|
|
672
|
+
if (firstBoot) {
|
|
673
|
+
log("now APPROVE the device in AgentVault \u2014 the service is waiting to enrol");
|
|
674
|
+
log("if it does not connect within a minute, the log above says why");
|
|
675
|
+
}
|
|
459
676
|
log(
|
|
460
677
|
`check it with: AV_AGENT_NAME=${cfg.agentName} agentvault-claude-bridge status`
|
|
461
678
|
);
|
|
@@ -487,14 +704,324 @@ var init_subcommand = __esm({
|
|
|
487
704
|
"use strict";
|
|
488
705
|
init_config();
|
|
489
706
|
init_spec();
|
|
707
|
+
init_pending_invite();
|
|
490
708
|
init_backend();
|
|
491
709
|
SERVICE_CMDS = /* @__PURE__ */ new Set(["install", "uninstall", "restart", "status", "logs"]);
|
|
492
710
|
}
|
|
493
711
|
});
|
|
494
712
|
|
|
713
|
+
// src/service/doctor.ts
|
|
714
|
+
import { basename as basename2 } from "node:path";
|
|
715
|
+
async function buildDoctorReport(d10) {
|
|
716
|
+
const installed = d10.labelPrefixes.flatMap((p2) => d10.backend.list(p2));
|
|
717
|
+
const services = [];
|
|
718
|
+
for (const s10 of installed) services.push(await inspect(s10, d10));
|
|
719
|
+
const claimed = new Set(installed.map((s10) => s10.env.AV_DATA_DIR).filter(Boolean));
|
|
720
|
+
const unsupervised = [];
|
|
721
|
+
for (const dataDir of d10.credDirs()) {
|
|
722
|
+
if (claimed.has(dataDir)) continue;
|
|
723
|
+
unsupervised.push(await inspectUnsupervised(dataDir, d10));
|
|
724
|
+
}
|
|
725
|
+
return {
|
|
726
|
+
labelPrefixes: d10.labelPrefixes,
|
|
727
|
+
services,
|
|
728
|
+
unsupervised,
|
|
729
|
+
notChecked: d10.otherLabels()
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
async function inspect(s10, d10) {
|
|
733
|
+
const agentName = s10.env.AV_AGENT_NAME ?? "";
|
|
734
|
+
const dataDir = s10.env.AV_DATA_DIR ?? "";
|
|
735
|
+
let loaded = false;
|
|
736
|
+
try {
|
|
737
|
+
loaded = d10.backend.status(s10.label).loaded;
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
const base = { label: s10.label, agentName, dataDir, loaded, deviceId: "" };
|
|
741
|
+
if (!dataDir) {
|
|
742
|
+
return {
|
|
743
|
+
...base,
|
|
744
|
+
verdict: "WARN",
|
|
745
|
+
detail: "the service definition bakes no AV_DATA_DIR, so doctor cannot find its credentials and cannot check its device"
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const creds = d10.readCreds(dataDir);
|
|
749
|
+
if (!creds?.deviceId) {
|
|
750
|
+
return {
|
|
751
|
+
...base,
|
|
752
|
+
verdict: "WARN",
|
|
753
|
+
detail: `no credentials at ${dataDir} \u2014 installed, but this agent never enrolled`
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const deviceId = creds.deviceId;
|
|
757
|
+
let probe;
|
|
758
|
+
try {
|
|
759
|
+
probe = await d10.fetchStatus(d10.apiUrl, deviceId);
|
|
760
|
+
} catch (e7) {
|
|
761
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${e7.message}` };
|
|
762
|
+
}
|
|
763
|
+
if (probe.inconclusive) {
|
|
764
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${probe.inconclusive}` };
|
|
765
|
+
}
|
|
766
|
+
if (probe.gone) {
|
|
767
|
+
return {
|
|
768
|
+
...base,
|
|
769
|
+
deviceId,
|
|
770
|
+
verdict: "FAIL",
|
|
771
|
+
detail: "the device no longer exists \u2014 this service is an orphan",
|
|
772
|
+
fix: d10.uninstallCommand(agentName)
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
switch (probe.status) {
|
|
776
|
+
case "ACTIVE":
|
|
777
|
+
return { ...base, deviceId, verdict: "PASS", detail: "device ACTIVE" };
|
|
778
|
+
case "PENDING":
|
|
779
|
+
return {
|
|
780
|
+
...base,
|
|
781
|
+
deviceId,
|
|
782
|
+
verdict: "WARN",
|
|
783
|
+
detail: "device PENDING \u2014 enrolled, but never approved in AgentVault"
|
|
784
|
+
};
|
|
785
|
+
case "REVOKED":
|
|
786
|
+
return {
|
|
787
|
+
...base,
|
|
788
|
+
deviceId,
|
|
789
|
+
verdict: "FAIL",
|
|
790
|
+
detail: "the device was REVOKED \u2014 this service is an orphan",
|
|
791
|
+
fix: d10.uninstallCommand(agentName)
|
|
792
|
+
};
|
|
793
|
+
default:
|
|
794
|
+
return {
|
|
795
|
+
...base,
|
|
796
|
+
deviceId,
|
|
797
|
+
verdict: "SKIP",
|
|
798
|
+
detail: `the server reported a status this build does not recognise (${probe.status || "none"})`
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
async function inspectUnsupervised(dataDir, d10) {
|
|
803
|
+
const agentName = basename2(dataDir);
|
|
804
|
+
const base = { dataDir, agentName, deviceId: "" };
|
|
805
|
+
const deviceId = d10.readCreds(dataDir)?.deviceId;
|
|
806
|
+
if (!deviceId) {
|
|
807
|
+
return { ...base, verdict: "WARN", detail: "credentials present but they hold no device id" };
|
|
808
|
+
}
|
|
809
|
+
let probe;
|
|
810
|
+
try {
|
|
811
|
+
probe = await d10.fetchStatus(d10.apiUrl, deviceId);
|
|
812
|
+
} catch (e7) {
|
|
813
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${e7.message}` };
|
|
814
|
+
}
|
|
815
|
+
if (probe.inconclusive) {
|
|
816
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${probe.inconclusive}` };
|
|
817
|
+
}
|
|
818
|
+
if (probe.gone) {
|
|
819
|
+
return {
|
|
820
|
+
...base,
|
|
821
|
+
deviceId,
|
|
822
|
+
verdict: "WARN",
|
|
823
|
+
detail: "the device no longer exists \u2014 these are leftover credentials for a deleted agent"
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
if (probe.status === "ACTIVE") {
|
|
827
|
+
return {
|
|
828
|
+
...base,
|
|
829
|
+
deviceId,
|
|
830
|
+
verdict: "WARN",
|
|
831
|
+
detail: "the device is ACTIVE but nothing supervises it \u2014 this agent is not running",
|
|
832
|
+
fix: d10.installCommand(agentName)
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
if (probe.status === "PENDING") {
|
|
836
|
+
return {
|
|
837
|
+
...base,
|
|
838
|
+
deviceId,
|
|
839
|
+
verdict: "WARN",
|
|
840
|
+
detail: "device PENDING \u2014 enrolled, but never approved in AgentVault"
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (probe.status === "REVOKED") {
|
|
844
|
+
return {
|
|
845
|
+
...base,
|
|
846
|
+
deviceId,
|
|
847
|
+
verdict: "WARN",
|
|
848
|
+
detail: "the device was REVOKED \u2014 these are leftover credentials"
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
return {
|
|
852
|
+
...base,
|
|
853
|
+
deviceId,
|
|
854
|
+
verdict: "SKIP",
|
|
855
|
+
detail: `the server reported a status this build does not recognise (${probe.status || "none"})`
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function formatDoctorReport(r7) {
|
|
859
|
+
const out = [];
|
|
860
|
+
out.push(`Services installed for this bridge (${r7.labelPrefixes.map((p2) => p2 + "*").join(", ")})`);
|
|
861
|
+
if (r7.services.length === 0) {
|
|
862
|
+
out.push(` No services installed for this bridge on this machine.`);
|
|
863
|
+
}
|
|
864
|
+
for (const s10 of r7.services) {
|
|
865
|
+
out.push(
|
|
866
|
+
` ${s10.verdict.padEnd(PAD)}${s10.label} [${s10.agentName || "unnamed"}] ${s10.loaded ? "loaded" : "not loaded"} \u2014 ${s10.detail}`
|
|
867
|
+
);
|
|
868
|
+
if (s10.fix) out.push(` ${"".padEnd(PAD)}fix: ${s10.fix}`);
|
|
869
|
+
}
|
|
870
|
+
if (r7.unsupervised.length) {
|
|
871
|
+
out.push("");
|
|
872
|
+
out.push("Credentials on disk that no installed service points at");
|
|
873
|
+
for (const u2 of r7.unsupervised) {
|
|
874
|
+
out.push(` ${u2.verdict.padEnd(PAD)}${u2.agentName} \u2014 ${u2.detail}`);
|
|
875
|
+
out.push(` ${"".padEnd(PAD)}${u2.dataDir}`);
|
|
876
|
+
if (u2.fix) out.push(` ${"".padEnd(PAD)}fix: ${u2.fix}`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
if (r7.notChecked.length) {
|
|
880
|
+
out.push("");
|
|
881
|
+
out.push("Found on this machine, NOT checked by this command");
|
|
882
|
+
for (const l10 of r7.notChecked) out.push(` ${l10}`);
|
|
883
|
+
out.push(
|
|
884
|
+
" These belong to other AgentVault agent families. This command can only read the credentials of its own, so it says nothing about their health."
|
|
885
|
+
);
|
|
886
|
+
}
|
|
887
|
+
const counts = { PASS: 0, WARN: 0, FAIL: 0, SKIP: 0 };
|
|
888
|
+
for (const s10 of r7.services) counts[s10.verdict]++;
|
|
889
|
+
for (const u2 of r7.unsupervised) counts[u2.verdict]++;
|
|
890
|
+
out.push("");
|
|
891
|
+
out.push(
|
|
892
|
+
`Summary: ${counts.PASS} PASS, ${counts.WARN} WARN, ${counts.FAIL} FAIL, ${counts.SKIP} SKIP` + (counts.SKIP ? " (SKIP = could not check, NOT a problem found)" : "")
|
|
893
|
+
);
|
|
894
|
+
if (counts.FAIL) {
|
|
895
|
+
out.push(
|
|
896
|
+
"`uninstall` removes the SERVICE only \u2014 your credentials stay on disk, so a mistake here costs a re-install, not an identity."
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
return out;
|
|
900
|
+
}
|
|
901
|
+
var PAD;
|
|
902
|
+
var init_doctor = __esm({
|
|
903
|
+
"src/service/doctor.ts"() {
|
|
904
|
+
"use strict";
|
|
905
|
+
PAD = 6;
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
// src/service/doctor-cli.ts
|
|
910
|
+
var doctor_cli_exports = {};
|
|
911
|
+
__export(doctor_cli_exports, {
|
|
912
|
+
fetchDeviceStatus: () => fetchDeviceStatus,
|
|
913
|
+
maybeRunDoctorSubcommand: () => maybeRunDoctorSubcommand,
|
|
914
|
+
realDoctorDeps: () => realDoctorDeps
|
|
915
|
+
});
|
|
916
|
+
import { join as join12 } from "node:path";
|
|
917
|
+
import { readdirSync as readdirSync5 } from "node:fs";
|
|
918
|
+
async function fetchDeviceStatus(apiUrl, deviceId, doFetch = fetch) {
|
|
919
|
+
const res = await doFetch(`${apiUrl}/api/v1/devices/${deviceId}/status`, {
|
|
920
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
921
|
+
});
|
|
922
|
+
if (res.status === 404) {
|
|
923
|
+
try {
|
|
924
|
+
await res.json();
|
|
925
|
+
} catch {
|
|
926
|
+
return {
|
|
927
|
+
inconclusive: `${apiUrl} answered 404 with a non-JSON body \u2014 check AV_API_URL points at the API`
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
return { gone: true };
|
|
931
|
+
}
|
|
932
|
+
if (res.status === 429) return { inconclusive: "rate limited by the API (429)" };
|
|
933
|
+
if (!res.ok) return { inconclusive: `the API answered ${res.status}` };
|
|
934
|
+
let body;
|
|
935
|
+
try {
|
|
936
|
+
body = await res.json();
|
|
937
|
+
} catch {
|
|
938
|
+
return { inconclusive: `${apiUrl} answered 200 with a non-JSON body \u2014 check AV_API_URL` };
|
|
939
|
+
}
|
|
940
|
+
return { status: body?.status };
|
|
941
|
+
}
|
|
942
|
+
function listCredDirs(base) {
|
|
943
|
+
const out = [];
|
|
944
|
+
if (hasPersistedCreds(base)) out.push(base);
|
|
945
|
+
let names;
|
|
946
|
+
try {
|
|
947
|
+
names = readdirSync5(base);
|
|
948
|
+
} catch {
|
|
949
|
+
return out;
|
|
950
|
+
}
|
|
951
|
+
for (const name of names) {
|
|
952
|
+
const dir = join12(base, name);
|
|
953
|
+
if (hasPersistedCreds(dir)) out.push(dir);
|
|
954
|
+
}
|
|
955
|
+
return out;
|
|
956
|
+
}
|
|
957
|
+
function otherFamilyLabels(backend) {
|
|
958
|
+
const out = [];
|
|
959
|
+
for (const p2 of OTHER_FAMILY_PREFIXES) {
|
|
960
|
+
try {
|
|
961
|
+
for (const s10 of backend.list(p2)) out.push(s10.label);
|
|
962
|
+
} catch {
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
return out.sort();
|
|
966
|
+
}
|
|
967
|
+
function realDoctorDeps(env) {
|
|
968
|
+
const backend = selectBackend(process.platform);
|
|
969
|
+
const base = join12(env.HOME ?? "", ".agentvault", "claude-room-bridge");
|
|
970
|
+
return {
|
|
971
|
+
backend,
|
|
972
|
+
labelPrefixes: [LABEL_PREFIX, LEGACY_LABEL_PREFIX],
|
|
973
|
+
base,
|
|
974
|
+
// Same default as config.ts. Read from the env directly rather than through
|
|
975
|
+
// loadConfig, which now REFUSES without AV_AGENT_NAME (#921) — and doctor's
|
|
976
|
+
// whole job is to run when you do not know which agents exist.
|
|
977
|
+
apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
|
|
978
|
+
uninstallCommand: (n10) => `AV_AGENT_NAME=${n10 || "<agent-name>"} agentvault-claude-bridge uninstall`,
|
|
979
|
+
installCommand: (n10) => `AV_AGENT_NAME=${n10 || "<agent-name>"} agentvault-claude-bridge install`,
|
|
980
|
+
readCreds: (dir) => {
|
|
981
|
+
const deviceId = readPersistedDeviceId(dir);
|
|
982
|
+
return deviceId ? { deviceId } : null;
|
|
983
|
+
},
|
|
984
|
+
credDirs: () => listCredDirs(base),
|
|
985
|
+
fetchStatus: (apiUrl, deviceId) => fetchDeviceStatus(apiUrl, deviceId),
|
|
986
|
+
otherLabels: () => otherFamilyLabels(backend)
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
async function maybeRunDoctorSubcommand(argv, opts = {}) {
|
|
990
|
+
if (argv[2] !== "doctor") return false;
|
|
991
|
+
const log = opts.log ?? printLine;
|
|
992
|
+
try {
|
|
993
|
+
const report = await buildDoctorReport(opts.deps ?? realDoctorDeps(opts.env ?? process.env));
|
|
994
|
+
for (const line of formatDoctorReport(report)) log(line);
|
|
995
|
+
if (report.services.some((s10) => s10.verdict === "FAIL")) process.exitCode = 1;
|
|
996
|
+
} catch (e7) {
|
|
997
|
+
log(`doctor could not run: ${e7.message}`);
|
|
998
|
+
process.exitCode = 1;
|
|
999
|
+
}
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
1002
|
+
var LABEL_PREFIX, LEGACY_LABEL_PREFIX, OTHER_FAMILY_PREFIXES, PROBE_TIMEOUT_MS;
|
|
1003
|
+
var init_doctor_cli = __esm({
|
|
1004
|
+
"src/service/doctor-cli.ts"() {
|
|
1005
|
+
"use strict";
|
|
1006
|
+
init_backend();
|
|
1007
|
+
init_config();
|
|
1008
|
+
init_log();
|
|
1009
|
+
init_doctor();
|
|
1010
|
+
LABEL_PREFIX = "dev.agentvault.bridge-";
|
|
1011
|
+
LEGACY_LABEL_PREFIX = "com.agentvault.claude-bridge.";
|
|
1012
|
+
OTHER_FAMILY_PREFIXES = [
|
|
1013
|
+
"dev.agentvault.grok-",
|
|
1014
|
+
// grok bridge
|
|
1015
|
+
"ai.hermes.gateway"
|
|
1016
|
+
// hermes, one service per profile
|
|
1017
|
+
];
|
|
1018
|
+
PROBE_TIMEOUT_MS = 8e3;
|
|
1019
|
+
}
|
|
1020
|
+
});
|
|
1021
|
+
|
|
495
1022
|
// src/index.ts
|
|
496
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
497
|
-
import { join as
|
|
1023
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync7 } from "node:fs";
|
|
1024
|
+
import { join as join13 } from "node:path";
|
|
498
1025
|
|
|
499
1026
|
// ../plugin/dist/index.js
|
|
500
1027
|
import * as nc from "node:crypto";
|
|
@@ -98151,6 +98678,7 @@ await init_index();
|
|
|
98151
98678
|
|
|
98152
98679
|
// src/index.ts
|
|
98153
98680
|
init_config();
|
|
98681
|
+
init_pending_invite();
|
|
98154
98682
|
|
|
98155
98683
|
// ../../node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
98156
98684
|
import { createRequire as $S } from "node:module";
|
|
@@ -119451,18 +119979,18 @@ import { realpathSync as realpathSync2 } from "node:fs";
|
|
|
119451
119979
|
import { resolve as resolve3, dirname as dirname2, basename, sep as sep2, isAbsolute } from "node:path";
|
|
119452
119980
|
|
|
119453
119981
|
// src/host-trust.ts
|
|
119454
|
-
import { mkdirSync as
|
|
119455
|
-
import { join as
|
|
119982
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2, rmSync as rmSync3, readdirSync as readdirSync2, lstatSync as lstatSync2, readFileSync as readFileSync5, chmodSync as chmodSync3 } from "node:fs";
|
|
119983
|
+
import { join as join7 } from "node:path";
|
|
119456
119984
|
import { homedir, hostname as hostname3 } from "node:os";
|
|
119457
119985
|
var TRUST_SUBDIR = "host-trust";
|
|
119458
|
-
var
|
|
119459
|
-
var
|
|
119986
|
+
var DIR_MODE3 = 448;
|
|
119987
|
+
var FILE_MODE3 = 384;
|
|
119460
119988
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
119461
119989
|
var IDENTITY_FILES = ["agentvault.json", "secure-channel.json", "agentvault.json.bak"];
|
|
119462
119990
|
var HostTrustError = class extends Error {
|
|
119463
119991
|
};
|
|
119464
119992
|
function trustDir(root3) {
|
|
119465
|
-
return
|
|
119993
|
+
return join7(root3 ?? join7(homedir(), ".agentvault"), TRUST_SUBDIR);
|
|
119466
119994
|
}
|
|
119467
119995
|
function sanitize(deviceId) {
|
|
119468
119996
|
if (typeof deviceId !== "string" || !ID_RE.test(deviceId)) {
|
|
@@ -119473,7 +120001,7 @@ function sanitize(deviceId) {
|
|
|
119473
120001
|
function isTrusted(deviceId, root3) {
|
|
119474
120002
|
try {
|
|
119475
120003
|
if (typeof deviceId !== "string" || !ID_RE.test(deviceId)) return false;
|
|
119476
|
-
const p2 =
|
|
120004
|
+
const p2 = join7(trustDir(root3), deviceId);
|
|
119477
120005
|
return lstatSync2(p2).isFile();
|
|
119478
120006
|
} catch {
|
|
119479
120007
|
return false;
|
|
@@ -119482,26 +120010,26 @@ function isTrusted(deviceId, root3) {
|
|
|
119482
120010
|
function grant(deviceId, root3) {
|
|
119483
120011
|
const id = sanitize(deviceId);
|
|
119484
120012
|
const dir = trustDir(root3);
|
|
119485
|
-
const p2 =
|
|
119486
|
-
|
|
119487
|
-
|
|
120013
|
+
const p2 = join7(dir, id);
|
|
120014
|
+
mkdirSync4(dir, { recursive: true, mode: DIR_MODE3 });
|
|
120015
|
+
writeFileSync2(
|
|
119488
120016
|
p2,
|
|
119489
120017
|
`granted_at=${(/* @__PURE__ */ new Date()).toISOString()} host=${hostname3()}
|
|
119490
120018
|
`,
|
|
119491
|
-
{ mode:
|
|
120019
|
+
{ mode: FILE_MODE3 }
|
|
119492
120020
|
);
|
|
119493
|
-
|
|
119494
|
-
|
|
120021
|
+
chmodSync3(dir, DIR_MODE3);
|
|
120022
|
+
chmodSync3(p2, FILE_MODE3);
|
|
119495
120023
|
}
|
|
119496
120024
|
function revoke(deviceId, root3) {
|
|
119497
120025
|
const id = sanitize(deviceId);
|
|
119498
|
-
const p2 =
|
|
120026
|
+
const p2 = join7(trustDir(root3), id);
|
|
119499
120027
|
try {
|
|
119500
120028
|
if (!lstatSync2(p2).isFile()) return false;
|
|
119501
120029
|
} catch {
|
|
119502
120030
|
return false;
|
|
119503
120031
|
}
|
|
119504
|
-
|
|
120032
|
+
rmSync3(p2, { force: true });
|
|
119505
120033
|
return true;
|
|
119506
120034
|
}
|
|
119507
120035
|
function listTrusted(root3) {
|
|
@@ -119516,7 +120044,7 @@ function listTrusted(root3) {
|
|
|
119516
120044
|
function readDeviceId(dataDir) {
|
|
119517
120045
|
for (const f7 of IDENTITY_FILES) {
|
|
119518
120046
|
try {
|
|
119519
|
-
const parsed = JSON.parse(
|
|
120047
|
+
const parsed = JSON.parse(readFileSync5(join7(dataDir, f7), "utf-8"));
|
|
119520
120048
|
if (parsed?.deviceId && ID_RE.test(parsed.deviceId)) return parsed.deviceId;
|
|
119521
120049
|
} catch {
|
|
119522
120050
|
}
|
|
@@ -119526,17 +120054,8 @@ function readDeviceId(dataDir) {
|
|
|
119526
120054
|
);
|
|
119527
120055
|
}
|
|
119528
120056
|
|
|
119529
|
-
// src/log.ts
|
|
119530
|
-
import { format } from "node:util";
|
|
119531
|
-
function stamp(message) {
|
|
119532
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
119533
|
-
return message.split("\n").map((line) => `${ts2} ${line}`).join("\n");
|
|
119534
|
-
}
|
|
119535
|
-
function logLine(...args) {
|
|
119536
|
-
console.error(stamp(format(...args)));
|
|
119537
|
-
}
|
|
119538
|
-
|
|
119539
120057
|
// src/worker-permission.ts
|
|
120058
|
+
init_log();
|
|
119540
120059
|
var PATH_FIELDS = ["file_path", "path", "notebook_path"];
|
|
119541
120060
|
function canonical(p2) {
|
|
119542
120061
|
const abs = resolve3(p2);
|
|
@@ -133462,6 +133981,7 @@ function date7(params) {
|
|
|
133462
133981
|
config2(en_default3());
|
|
133463
133982
|
|
|
133464
133983
|
// src/session.ts
|
|
133984
|
+
init_log();
|
|
133465
133985
|
function makeRoomSayTool(onSay) {
|
|
133466
133986
|
return bs(
|
|
133467
133987
|
"say",
|
|
@@ -134072,9 +134592,36 @@ var ArmingState = class {
|
|
|
134072
134592
|
}
|
|
134073
134593
|
};
|
|
134074
134594
|
|
|
134595
|
+
// src/service/self-uninstall.ts
|
|
134596
|
+
init_subcommand();
|
|
134597
|
+
init_backend();
|
|
134598
|
+
init_log();
|
|
134599
|
+
var DEVICE_IS_GONE = "device_revoked";
|
|
134600
|
+
function selfUninstallOnTerminal(reason, env, deps = {}) {
|
|
134601
|
+
if (reason !== DEVICE_IS_GONE) return false;
|
|
134602
|
+
const log = deps.log ?? ((m6) => logLine("[bridge] " + m6));
|
|
134603
|
+
let label = "";
|
|
134604
|
+
try {
|
|
134605
|
+
if (!deps.backend && process.platform !== "darwin" && process.platform !== "linux") return false;
|
|
134606
|
+
const backend = deps.backend ?? selectBackend(process.platform);
|
|
134607
|
+
label = resolveLabel(backend, env);
|
|
134608
|
+
if (!backend.status(label).loaded) return false;
|
|
134609
|
+
backend.uninstall(label);
|
|
134610
|
+
log(
|
|
134611
|
+
`device was revoked \u2014 removed the supervised service ${label} so it does not sit installed on this machine pointing at an agent that no longer exists. Your credentials are still on disk and were NOT touched; re-install with a fresh invite token from AgentVault to reconnect.`
|
|
134612
|
+
);
|
|
134613
|
+
return true;
|
|
134614
|
+
} catch (e7) {
|
|
134615
|
+
log(
|
|
134616
|
+
`device was revoked, but the supervised service ${label || "for this agent"} could not be removed: ${e7.message}. Remove it by hand with: AV_AGENT_NAME=<agent-name> agentvault-claude-bridge uninstall`
|
|
134617
|
+
);
|
|
134618
|
+
return false;
|
|
134619
|
+
}
|
|
134620
|
+
}
|
|
134621
|
+
|
|
134075
134622
|
// src/approve-cli.ts
|
|
134076
|
-
import { mkdirSync as
|
|
134077
|
-
import { join as
|
|
134623
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync4, readFileSync as readFileSync7, readdirSync as readdirSync4, rmSync as rmSync5, existsSync as existsSync5 } from "node:fs";
|
|
134624
|
+
import { join as join11 } from "node:path";
|
|
134078
134625
|
var APPROVALS_SUBDIR = "arm-approvals";
|
|
134079
134626
|
var ID_RE2 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
134080
134627
|
var ApproveArmError = class extends Error {
|
|
@@ -134094,24 +134641,24 @@ function sanitizeRoomId(roomId) {
|
|
|
134094
134641
|
function writeApproval(dataDir, requestId, roomId) {
|
|
134095
134642
|
const id = sanitizeRequestId(requestId);
|
|
134096
134643
|
const room = sanitizeRoomId(roomId);
|
|
134097
|
-
const dir =
|
|
134098
|
-
|
|
134099
|
-
|
|
134644
|
+
const dir = join11(dataDir, APPROVALS_SUBDIR);
|
|
134645
|
+
mkdirSync6(dir, { recursive: true });
|
|
134646
|
+
writeFileSync4(join11(dir, id), room);
|
|
134100
134647
|
}
|
|
134101
134648
|
function drainApprovals(dataDir) {
|
|
134102
|
-
const dir =
|
|
134103
|
-
if (!
|
|
134649
|
+
const dir = join11(dataDir, APPROVALS_SUBDIR);
|
|
134650
|
+
if (!existsSync5(dir)) return [];
|
|
134104
134651
|
const out = [];
|
|
134105
|
-
for (const name of
|
|
134652
|
+
for (const name of readdirSync4(dir)) {
|
|
134106
134653
|
if (!ID_RE2.test(name)) continue;
|
|
134107
134654
|
let roomId = "";
|
|
134108
134655
|
try {
|
|
134109
|
-
roomId =
|
|
134656
|
+
roomId = readFileSync7(join11(dir, name), "utf8").trim();
|
|
134110
134657
|
} catch {
|
|
134111
134658
|
}
|
|
134112
134659
|
out.push({ requestId: name, roomId: ID_RE2.test(roomId) ? roomId : "" });
|
|
134113
134660
|
try {
|
|
134114
|
-
|
|
134661
|
+
rmSync5(join11(dir, name), { force: true, recursive: true });
|
|
134115
134662
|
} catch {
|
|
134116
134663
|
}
|
|
134117
134664
|
}
|
|
@@ -134252,11 +134799,13 @@ var ActiveTarget = class {
|
|
|
134252
134799
|
function attachLifecycle2(channel, opts = {}) {
|
|
134253
134800
|
const log = opts.log ?? (() => {
|
|
134254
134801
|
});
|
|
134802
|
+
const selfUninstall = opts.selfUninstall ?? selfUninstallOnTerminal;
|
|
134255
134803
|
const onTerminal = opts.onTerminal ?? ((_reason, o10) => process.exit(o10.restart ? 1 : 0));
|
|
134256
134804
|
const terminal = (reason, restart) => {
|
|
134257
134805
|
log(
|
|
134258
134806
|
`terminal condition (${reason}) \u2014 ${restart ? "gave up reconnecting; supervisor may respawn" : "this device must re-enroll or another session has taken over"}. Re-run/re-install with a fresh invite token from AgentVault if this persists.`
|
|
134259
134807
|
);
|
|
134808
|
+
selfUninstall(reason, process.env);
|
|
134260
134809
|
onTerminal(reason, { restart });
|
|
134261
134810
|
};
|
|
134262
134811
|
channel.on("auth_failed", (e7) => terminal(e7.reason, false));
|
|
@@ -134479,7 +135028,12 @@ function wireBridge(channel, session, target, opts = {}) {
|
|
|
134479
135028
|
}
|
|
134480
135029
|
|
|
134481
135030
|
// src/index.ts
|
|
135031
|
+
init_log();
|
|
134482
135032
|
async function main() {
|
|
135033
|
+
const { maybeRunDoctorSubcommand: maybeRunDoctorSubcommand2 } = await Promise.resolve().then(() => (init_doctor_cli(), doctor_cli_exports));
|
|
135034
|
+
if (await maybeRunDoctorSubcommand2(process.argv)) {
|
|
135035
|
+
process.exit(process.exitCode ?? 0);
|
|
135036
|
+
}
|
|
134483
135037
|
const { dataDir: dataDirForSubcommand } = resolveDataDir(process.env);
|
|
134484
135038
|
if (maybeRunApproveArmSubcommand(process.argv, dataDirForSubcommand)) {
|
|
134485
135039
|
process.exit(process.exitCode ?? 0);
|
|
@@ -134498,7 +135052,7 @@ async function main() {
|
|
|
134498
135052
|
"[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"
|
|
134499
135053
|
);
|
|
134500
135054
|
}
|
|
134501
|
-
logLine(`[bridge] version: ${true ? "0.
|
|
135055
|
+
logLine(`[bridge] version: ${true ? "0.8.0" : "dev"}`);
|
|
134502
135056
|
logLine(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
134503
135057
|
logLine(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
|
|
134504
135058
|
if (cfg.armRoom) {
|
|
@@ -134530,7 +135084,7 @@ async function main() {
|
|
|
134530
135084
|
// its default would render "@agentvault/agentvault@0.7.x" — the wrong
|
|
134531
135085
|
// package name attached to the bridge's version number, which is worse
|
|
134532
135086
|
// than either alone.
|
|
134533
|
-
clientVersion: `@agentvault/claude-bridge@${true ? "0.
|
|
135087
|
+
clientVersion: `@agentvault/claude-bridge@${true ? "0.8.0" : "dev"}`
|
|
134534
135088
|
});
|
|
134535
135089
|
const agentSystemPrompt = cfg.systemPrompt ?? `You are ${cfg.agentName}, an AI agent on AgentVault. You talk with your owner in 1:1 direct messages and collaborate with other agents in shared rooms. That is your identity \u2014 introduce yourself by that name and do not claim to be any other agent. To speak, call the say tool. In a 1:1 with your owner, reply to what they say. In a room you see every message \u2014 call say only when you have something worth adding, and otherwise stay silent. Keep messages concise.`;
|
|
134536
135090
|
const deviceJwt = () => {
|
|
@@ -134594,9 +135148,9 @@ async function main() {
|
|
|
134594
135148
|
`[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}` : "")
|
|
134595
135149
|
);
|
|
134596
135150
|
try {
|
|
134597
|
-
const dir =
|
|
134598
|
-
|
|
134599
|
-
appendFileSync2(
|
|
135151
|
+
const dir = join13(cfg.dataDir, "logs");
|
|
135152
|
+
mkdirSync7(dir, { recursive: true });
|
|
135153
|
+
appendFileSync2(join13(dir, "worker-incidents.jsonl"), JSON.stringify(rec) + "\n");
|
|
134600
135154
|
} catch (err) {
|
|
134601
135155
|
logLine(`[worker-trap] could not persist incident: ${err.message}`);
|
|
134602
135156
|
}
|
|
@@ -134628,6 +135182,11 @@ async function main() {
|
|
|
134628
135182
|
attachLifecycle2(channel, {
|
|
134629
135183
|
log: (m6) => logLine("[bridge] " + m6)
|
|
134630
135184
|
});
|
|
135185
|
+
channel.on("ready", () => {
|
|
135186
|
+
if (clearPendingInviteIfConsumed(cfg.dataDir, hasPersistedCreds)) {
|
|
135187
|
+
logLine("[bridge] enrolled \u2014 pending invite consumed and removed");
|
|
135188
|
+
}
|
|
135189
|
+
});
|
|
134631
135190
|
channel.on("state", (s10) => logLine(`[bridge] channel state: ${JSON.stringify(s10)}`));
|
|
134632
135191
|
channel.on(
|
|
134633
135192
|
"room_joined",
|