@agentvault/claude-bridge 0.7.15 → 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 +679 -79
- 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;
|
|
@@ -439,11 +652,12 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
439
652
|
const log = deps?.log ?? ((m6) => console.error("[bridge] " + m6));
|
|
440
653
|
try {
|
|
441
654
|
if (cmd === "install") {
|
|
655
|
+
requireAgentName(env);
|
|
656
|
+
assertInstallableEntrypoint(entrypoint);
|
|
442
657
|
const cfg = loadConfig(env, []);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
);
|
|
658
|
+
const firstBoot = !hasPersistedCreds(cfg.dataDir) && !!cfg.inviteToken;
|
|
659
|
+
if (firstBoot) {
|
|
660
|
+
writePendingInvite(cfg.dataDir, cfg.inviteToken);
|
|
447
661
|
}
|
|
448
662
|
const spec = buildServiceSpec(cfg, {
|
|
449
663
|
entrypoint,
|
|
@@ -454,7 +668,11 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
454
668
|
path: env.PATH ?? ""
|
|
455
669
|
});
|
|
456
670
|
backend.install(spec);
|
|
457
|
-
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
|
+
}
|
|
458
676
|
log(
|
|
459
677
|
`check it with: AV_AGENT_NAME=${cfg.agentName} agentvault-claude-bridge status`
|
|
460
678
|
);
|
|
@@ -486,14 +704,324 @@ var init_subcommand = __esm({
|
|
|
486
704
|
"use strict";
|
|
487
705
|
init_config();
|
|
488
706
|
init_spec();
|
|
707
|
+
init_pending_invite();
|
|
489
708
|
init_backend();
|
|
490
709
|
SERVICE_CMDS = /* @__PURE__ */ new Set(["install", "uninstall", "restart", "status", "logs"]);
|
|
491
710
|
}
|
|
492
711
|
});
|
|
493
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
|
+
|
|
494
1022
|
// src/index.ts
|
|
495
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
496
|
-
import { join as
|
|
1023
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync7 } from "node:fs";
|
|
1024
|
+
import { join as join13 } from "node:path";
|
|
497
1025
|
|
|
498
1026
|
// ../plugin/dist/index.js
|
|
499
1027
|
import * as nc from "node:crypto";
|
|
@@ -553,6 +1081,25 @@ var __toESM = (mod4, isNodeMode, target) => (target = mod4 != null ? __create(__
|
|
|
553
1081
|
isNodeMode || !mod4 || !mod4.__esModule ? __defProp2(target, "default", { value: mod4, enumerable: true }) : target,
|
|
554
1082
|
mod4
|
|
555
1083
|
));
|
|
1084
|
+
function toEpoch(v22) {
|
|
1085
|
+
if (typeof v22 === "bigint") return v22;
|
|
1086
|
+
if (typeof v22 === "number") {
|
|
1087
|
+
return Number.isFinite(v22) ? BigInt(Math.trunc(v22)) : null;
|
|
1088
|
+
}
|
|
1089
|
+
if (typeof v22 === "string" && /^\d+$/.test(v22)) return BigInt(v22);
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
function classifyCommitFailure(messageEpoch, groupEpoch) {
|
|
1093
|
+
const msg = toEpoch(messageEpoch);
|
|
1094
|
+
const grp = toEpoch(groupEpoch);
|
|
1095
|
+
if (msg === null || grp === null) return "count";
|
|
1096
|
+
return msg < grp ? "already-applied" : "count";
|
|
1097
|
+
}
|
|
1098
|
+
var init_mls_commit_classify = __esm2({
|
|
1099
|
+
"src/mls-commit-classify.ts"() {
|
|
1100
|
+
"use strict";
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
556
1103
|
var __filename;
|
|
557
1104
|
var __dirname;
|
|
558
1105
|
var url2;
|
|
@@ -63900,7 +64447,7 @@ var init_mls_kp_pool = __esm2({
|
|
|
63900
64447
|
}
|
|
63901
64448
|
});
|
|
63902
64449
|
function ownIdentity() {
|
|
63903
|
-
const v22 = true ? "0.23.
|
|
64450
|
+
const v22 = true ? "0.23.23" : FALLBACK;
|
|
63904
64451
|
return `${PACKAGE}@${v22}`;
|
|
63905
64452
|
}
|
|
63906
64453
|
function buildClientVersion(override) {
|
|
@@ -64812,6 +65359,7 @@ var SecureChannel;
|
|
|
64812
65359
|
var init_channel = __esm2({
|
|
64813
65360
|
async "src/channel.ts"() {
|
|
64814
65361
|
"use strict";
|
|
65362
|
+
init_mls_commit_classify();
|
|
64815
65363
|
await init_libsodium_wrappers();
|
|
64816
65364
|
await init_dist();
|
|
64817
65365
|
await init_dist();
|
|
@@ -65878,7 +66426,7 @@ var init_channel = __esm2({
|
|
|
65878
66426
|
*/
|
|
65879
66427
|
sendActivitySpan(spanData) {
|
|
65880
66428
|
if (!this._ws || this._ws.readyState !== WebSocket2.OPEN) return;
|
|
65881
|
-
const pluginVersion = true ? "0.23.
|
|
66429
|
+
const pluginVersion = true ? "0.23.23" : "0.0.0-dev";
|
|
65882
66430
|
const agentName = this.config.agentName ?? "Agent";
|
|
65883
66431
|
const resource = {
|
|
65884
66432
|
"service.name": "agentvault-agent",
|
|
@@ -67788,7 +68336,7 @@ var init_channel = __esm2({
|
|
|
67788
68336
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67789
68337
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67790
68338
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67791
|
-
pluginVersion: true ? "0.23.
|
|
68339
|
+
pluginVersion: true ? "0.23.23" : "0.0.0-dev"
|
|
67792
68340
|
});
|
|
67793
68341
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67794
68342
|
}
|
|
@@ -68112,7 +68660,7 @@ var init_channel = __esm2({
|
|
|
68112
68660
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
68113
68661
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
68114
68662
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
68115
|
-
pluginVersion: true ? "0.23.
|
|
68663
|
+
pluginVersion: true ? "0.23.23" : "0.0.0-dev"
|
|
68116
68664
|
});
|
|
68117
68665
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
68118
68666
|
}
|
|
@@ -69518,7 +70066,14 @@ ${messageText}`;
|
|
|
69518
70066
|
console.log(`[SecureChannel] MLS commit processed for room ${roomId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
|
|
69519
70067
|
this._mlsCommitFailCounts.delete(roomId);
|
|
69520
70068
|
} catch (err) {
|
|
69521
|
-
await this._onCommitFailure(
|
|
70069
|
+
await this._onCommitFailure(
|
|
70070
|
+
roomId,
|
|
70071
|
+
groupId,
|
|
70072
|
+
err,
|
|
70073
|
+
`room ${roomId.slice(0, 8)}`,
|
|
70074
|
+
data.epoch,
|
|
70075
|
+
mlsGroup.epoch
|
|
70076
|
+
);
|
|
69522
70077
|
}
|
|
69523
70078
|
} else {
|
|
69524
70079
|
this._bufferMlsCommit(groupId, epoch, data);
|
|
@@ -69538,7 +70093,14 @@ ${messageText}`;
|
|
|
69538
70093
|
console.log(`[SecureChannel] MLS commit processed for A2A ${chId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
|
|
69539
70094
|
this._mlsCommitFailCounts.delete(`a2a:${chId}`);
|
|
69540
70095
|
} catch (err) {
|
|
69541
|
-
await this._onCommitFailure(
|
|
70096
|
+
await this._onCommitFailure(
|
|
70097
|
+
`a2a:${chId}`,
|
|
70098
|
+
groupId,
|
|
70099
|
+
err,
|
|
70100
|
+
`A2A ${chId.slice(0, 8)}`,
|
|
70101
|
+
data.epoch,
|
|
70102
|
+
mlsGroup.epoch
|
|
70103
|
+
);
|
|
69542
70104
|
}
|
|
69543
70105
|
} else {
|
|
69544
70106
|
this._bufferMlsCommit(groupId, epoch, data);
|
|
@@ -69624,7 +70186,13 @@ ${messageText}`;
|
|
|
69624
70186
|
* Never re-throws. The caller's only handler logs and drops, so throwing here
|
|
69625
70187
|
* is indistinguishable from swallowing — it just moves the swallow one frame up.
|
|
69626
70188
|
*/
|
|
69627
|
-
async _onCommitFailure(groupKey, mlsGroupId, err, label) {
|
|
70189
|
+
async _onCommitFailure(groupKey, mlsGroupId, err, label, messageEpoch, groupEpoch) {
|
|
70190
|
+
if (classifyCommitFailure(messageEpoch, groupEpoch) === "already-applied") {
|
|
70191
|
+
console.debug(
|
|
70192
|
+
`[SecureChannel] Skipping already-applied MLS commit for ${label} (commit epoch ${String(messageEpoch)} is behind group epoch ${String(groupEpoch)})`
|
|
70193
|
+
);
|
|
70194
|
+
return;
|
|
70195
|
+
}
|
|
69628
70196
|
const count = (this._mlsCommitFailCounts.get(groupKey) ?? 0) + 1;
|
|
69629
70197
|
this._mlsCommitFailCounts.set(groupKey, count);
|
|
69630
70198
|
console.error(
|
|
@@ -71522,7 +72090,7 @@ ${messageText}`;
|
|
|
71522
72090
|
return;
|
|
71523
72091
|
}
|
|
71524
72092
|
this._authFailedThisSession = true;
|
|
71525
|
-
const authReason = data?.reason ?? "
|
|
72093
|
+
const authReason = data?.reason ?? "unknown";
|
|
71526
72094
|
console.warn(
|
|
71527
72095
|
`[SecureChannel] connection_rejected (reason=${data?.reason ?? "unknown"}, retryable=false) \u2014 terminal; surfacing auth_failed`
|
|
71528
72096
|
);
|
|
@@ -98103,13 +98671,14 @@ var init_index = __esm2({
|
|
|
98103
98671
|
init_skill_invoker();
|
|
98104
98672
|
await init_skill_telemetry();
|
|
98105
98673
|
await init_policy_enforcer();
|
|
98106
|
-
VERSION = true ? "0.23.
|
|
98674
|
+
VERSION = true ? "0.23.23" : "0.0.0-dev";
|
|
98107
98675
|
}
|
|
98108
98676
|
});
|
|
98109
98677
|
await init_index();
|
|
98110
98678
|
|
|
98111
98679
|
// src/index.ts
|
|
98112
98680
|
init_config();
|
|
98681
|
+
init_pending_invite();
|
|
98113
98682
|
|
|
98114
98683
|
// ../../node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
98115
98684
|
import { createRequire as $S } from "node:module";
|
|
@@ -119410,18 +119979,18 @@ import { realpathSync as realpathSync2 } from "node:fs";
|
|
|
119410
119979
|
import { resolve as resolve3, dirname as dirname2, basename, sep as sep2, isAbsolute } from "node:path";
|
|
119411
119980
|
|
|
119412
119981
|
// src/host-trust.ts
|
|
119413
|
-
import { mkdirSync as
|
|
119414
|
-
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";
|
|
119415
119984
|
import { homedir, hostname as hostname3 } from "node:os";
|
|
119416
119985
|
var TRUST_SUBDIR = "host-trust";
|
|
119417
|
-
var
|
|
119418
|
-
var
|
|
119986
|
+
var DIR_MODE3 = 448;
|
|
119987
|
+
var FILE_MODE3 = 384;
|
|
119419
119988
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
119420
119989
|
var IDENTITY_FILES = ["agentvault.json", "secure-channel.json", "agentvault.json.bak"];
|
|
119421
119990
|
var HostTrustError = class extends Error {
|
|
119422
119991
|
};
|
|
119423
119992
|
function trustDir(root3) {
|
|
119424
|
-
return
|
|
119993
|
+
return join7(root3 ?? join7(homedir(), ".agentvault"), TRUST_SUBDIR);
|
|
119425
119994
|
}
|
|
119426
119995
|
function sanitize(deviceId) {
|
|
119427
119996
|
if (typeof deviceId !== "string" || !ID_RE.test(deviceId)) {
|
|
@@ -119432,7 +120001,7 @@ function sanitize(deviceId) {
|
|
|
119432
120001
|
function isTrusted(deviceId, root3) {
|
|
119433
120002
|
try {
|
|
119434
120003
|
if (typeof deviceId !== "string" || !ID_RE.test(deviceId)) return false;
|
|
119435
|
-
const p2 =
|
|
120004
|
+
const p2 = join7(trustDir(root3), deviceId);
|
|
119436
120005
|
return lstatSync2(p2).isFile();
|
|
119437
120006
|
} catch {
|
|
119438
120007
|
return false;
|
|
@@ -119441,26 +120010,26 @@ function isTrusted(deviceId, root3) {
|
|
|
119441
120010
|
function grant(deviceId, root3) {
|
|
119442
120011
|
const id = sanitize(deviceId);
|
|
119443
120012
|
const dir = trustDir(root3);
|
|
119444
|
-
const p2 =
|
|
119445
|
-
|
|
119446
|
-
|
|
120013
|
+
const p2 = join7(dir, id);
|
|
120014
|
+
mkdirSync4(dir, { recursive: true, mode: DIR_MODE3 });
|
|
120015
|
+
writeFileSync2(
|
|
119447
120016
|
p2,
|
|
119448
120017
|
`granted_at=${(/* @__PURE__ */ new Date()).toISOString()} host=${hostname3()}
|
|
119449
120018
|
`,
|
|
119450
|
-
{ mode:
|
|
120019
|
+
{ mode: FILE_MODE3 }
|
|
119451
120020
|
);
|
|
119452
|
-
|
|
119453
|
-
|
|
120021
|
+
chmodSync3(dir, DIR_MODE3);
|
|
120022
|
+
chmodSync3(p2, FILE_MODE3);
|
|
119454
120023
|
}
|
|
119455
120024
|
function revoke(deviceId, root3) {
|
|
119456
120025
|
const id = sanitize(deviceId);
|
|
119457
|
-
const p2 =
|
|
120026
|
+
const p2 = join7(trustDir(root3), id);
|
|
119458
120027
|
try {
|
|
119459
120028
|
if (!lstatSync2(p2).isFile()) return false;
|
|
119460
120029
|
} catch {
|
|
119461
120030
|
return false;
|
|
119462
120031
|
}
|
|
119463
|
-
|
|
120032
|
+
rmSync3(p2, { force: true });
|
|
119464
120033
|
return true;
|
|
119465
120034
|
}
|
|
119466
120035
|
function listTrusted(root3) {
|
|
@@ -119475,7 +120044,7 @@ function listTrusted(root3) {
|
|
|
119475
120044
|
function readDeviceId(dataDir) {
|
|
119476
120045
|
for (const f7 of IDENTITY_FILES) {
|
|
119477
120046
|
try {
|
|
119478
|
-
const parsed = JSON.parse(
|
|
120047
|
+
const parsed = JSON.parse(readFileSync5(join7(dataDir, f7), "utf-8"));
|
|
119479
120048
|
if (parsed?.deviceId && ID_RE.test(parsed.deviceId)) return parsed.deviceId;
|
|
119480
120049
|
} catch {
|
|
119481
120050
|
}
|
|
@@ -119485,17 +120054,8 @@ function readDeviceId(dataDir) {
|
|
|
119485
120054
|
);
|
|
119486
120055
|
}
|
|
119487
120056
|
|
|
119488
|
-
// src/log.ts
|
|
119489
|
-
import { format } from "node:util";
|
|
119490
|
-
function stamp(message) {
|
|
119491
|
-
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
119492
|
-
return message.split("\n").map((line) => `${ts2} ${line}`).join("\n");
|
|
119493
|
-
}
|
|
119494
|
-
function logLine(...args) {
|
|
119495
|
-
console.error(stamp(format(...args)));
|
|
119496
|
-
}
|
|
119497
|
-
|
|
119498
120057
|
// src/worker-permission.ts
|
|
120058
|
+
init_log();
|
|
119499
120059
|
var PATH_FIELDS = ["file_path", "path", "notebook_path"];
|
|
119500
120060
|
function canonical(p2) {
|
|
119501
120061
|
const abs = resolve3(p2);
|
|
@@ -133421,6 +133981,7 @@ function date7(params) {
|
|
|
133421
133981
|
config2(en_default3());
|
|
133422
133982
|
|
|
133423
133983
|
// src/session.ts
|
|
133984
|
+
init_log();
|
|
133424
133985
|
function makeRoomSayTool(onSay) {
|
|
133425
133986
|
return bs(
|
|
133426
133987
|
"say",
|
|
@@ -134031,9 +134592,36 @@ var ArmingState = class {
|
|
|
134031
134592
|
}
|
|
134032
134593
|
};
|
|
134033
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
|
+
|
|
134034
134622
|
// src/approve-cli.ts
|
|
134035
|
-
import { mkdirSync as
|
|
134036
|
-
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";
|
|
134037
134625
|
var APPROVALS_SUBDIR = "arm-approvals";
|
|
134038
134626
|
var ID_RE2 = /^[A-Za-z0-9_-]{1,128}$/;
|
|
134039
134627
|
var ApproveArmError = class extends Error {
|
|
@@ -134053,24 +134641,24 @@ function sanitizeRoomId(roomId) {
|
|
|
134053
134641
|
function writeApproval(dataDir, requestId, roomId) {
|
|
134054
134642
|
const id = sanitizeRequestId(requestId);
|
|
134055
134643
|
const room = sanitizeRoomId(roomId);
|
|
134056
|
-
const dir =
|
|
134057
|
-
|
|
134058
|
-
|
|
134644
|
+
const dir = join11(dataDir, APPROVALS_SUBDIR);
|
|
134645
|
+
mkdirSync6(dir, { recursive: true });
|
|
134646
|
+
writeFileSync4(join11(dir, id), room);
|
|
134059
134647
|
}
|
|
134060
134648
|
function drainApprovals(dataDir) {
|
|
134061
|
-
const dir =
|
|
134062
|
-
if (!
|
|
134649
|
+
const dir = join11(dataDir, APPROVALS_SUBDIR);
|
|
134650
|
+
if (!existsSync5(dir)) return [];
|
|
134063
134651
|
const out = [];
|
|
134064
|
-
for (const name of
|
|
134652
|
+
for (const name of readdirSync4(dir)) {
|
|
134065
134653
|
if (!ID_RE2.test(name)) continue;
|
|
134066
134654
|
let roomId = "";
|
|
134067
134655
|
try {
|
|
134068
|
-
roomId =
|
|
134656
|
+
roomId = readFileSync7(join11(dir, name), "utf8").trim();
|
|
134069
134657
|
} catch {
|
|
134070
134658
|
}
|
|
134071
134659
|
out.push({ requestId: name, roomId: ID_RE2.test(roomId) ? roomId : "" });
|
|
134072
134660
|
try {
|
|
134073
|
-
|
|
134661
|
+
rmSync5(join11(dir, name), { force: true, recursive: true });
|
|
134074
134662
|
} catch {
|
|
134075
134663
|
}
|
|
134076
134664
|
}
|
|
@@ -134211,11 +134799,13 @@ var ActiveTarget = class {
|
|
|
134211
134799
|
function attachLifecycle2(channel, opts = {}) {
|
|
134212
134800
|
const log = opts.log ?? (() => {
|
|
134213
134801
|
});
|
|
134802
|
+
const selfUninstall = opts.selfUninstall ?? selfUninstallOnTerminal;
|
|
134214
134803
|
const onTerminal = opts.onTerminal ?? ((_reason, o10) => process.exit(o10.restart ? 1 : 0));
|
|
134215
134804
|
const terminal = (reason, restart) => {
|
|
134216
134805
|
log(
|
|
134217
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.`
|
|
134218
134807
|
);
|
|
134808
|
+
selfUninstall(reason, process.env);
|
|
134219
134809
|
onTerminal(reason, { restart });
|
|
134220
134810
|
};
|
|
134221
134811
|
channel.on("auth_failed", (e7) => terminal(e7.reason, false));
|
|
@@ -134438,7 +135028,12 @@ function wireBridge(channel, session, target, opts = {}) {
|
|
|
134438
135028
|
}
|
|
134439
135029
|
|
|
134440
135030
|
// src/index.ts
|
|
135031
|
+
init_log();
|
|
134441
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
|
+
}
|
|
134442
135037
|
const { dataDir: dataDirForSubcommand } = resolveDataDir(process.env);
|
|
134443
135038
|
if (maybeRunApproveArmSubcommand(process.argv, dataDirForSubcommand)) {
|
|
134444
135039
|
process.exit(process.exitCode ?? 0);
|
|
@@ -134457,7 +135052,7 @@ async function main() {
|
|
|
134457
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"
|
|
134458
135053
|
);
|
|
134459
135054
|
}
|
|
134460
|
-
logLine(`[bridge] version: ${true ? "0.
|
|
135055
|
+
logLine(`[bridge] version: ${true ? "0.8.0" : "dev"}`);
|
|
134461
135056
|
logLine(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
|
|
134462
135057
|
logLine(`[bridge] workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
|
|
134463
135058
|
if (cfg.armRoom) {
|
|
@@ -134489,7 +135084,7 @@ async function main() {
|
|
|
134489
135084
|
// its default would render "@agentvault/agentvault@0.7.x" — the wrong
|
|
134490
135085
|
// package name attached to the bridge's version number, which is worse
|
|
134491
135086
|
// than either alone.
|
|
134492
|
-
clientVersion: `@agentvault/claude-bridge@${true ? "0.
|
|
135087
|
+
clientVersion: `@agentvault/claude-bridge@${true ? "0.8.0" : "dev"}`
|
|
134493
135088
|
});
|
|
134494
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.`;
|
|
134495
135090
|
const deviceJwt = () => {
|
|
@@ -134553,9 +135148,9 @@ async function main() {
|
|
|
134553
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}` : "")
|
|
134554
135149
|
);
|
|
134555
135150
|
try {
|
|
134556
|
-
const dir =
|
|
134557
|
-
|
|
134558
|
-
appendFileSync2(
|
|
135151
|
+
const dir = join13(cfg.dataDir, "logs");
|
|
135152
|
+
mkdirSync7(dir, { recursive: true });
|
|
135153
|
+
appendFileSync2(join13(dir, "worker-incidents.jsonl"), JSON.stringify(rec) + "\n");
|
|
134559
135154
|
} catch (err) {
|
|
134560
135155
|
logLine(`[worker-trap] could not persist incident: ${err.message}`);
|
|
134561
135156
|
}
|
|
@@ -134587,6 +135182,11 @@ async function main() {
|
|
|
134587
135182
|
attachLifecycle2(channel, {
|
|
134588
135183
|
log: (m6) => logLine("[bridge] " + m6)
|
|
134589
135184
|
});
|
|
135185
|
+
channel.on("ready", () => {
|
|
135186
|
+
if (clearPendingInviteIfConsumed(cfg.dataDir, hasPersistedCreds)) {
|
|
135187
|
+
logLine("[bridge] enrolled \u2014 pending invite consumed and removed");
|
|
135188
|
+
}
|
|
135189
|
+
});
|
|
134590
135190
|
channel.on("state", (s10) => logLine(`[bridge] channel state: ${JSON.stringify(s10)}`));
|
|
134591
135191
|
channel.on(
|
|
134592
135192
|
"room_joined",
|