@agentvault/grok-bridge 0.3.1 → 0.4.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 +28 -14
- package/dist/index.js +572 -170
- 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 +1 -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
|
@@ -54,6 +54,24 @@ var init_pending_invite = __esm({
|
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
+
// src/log.ts
|
|
58
|
+
import { format } from "node:util";
|
|
59
|
+
function stamp(message) {
|
|
60
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
61
|
+
return message.split("\n").map((line) => `${ts} ${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
|
+
|
|
57
75
|
// src/config.ts
|
|
58
76
|
import { existsSync as existsSync2, readFileSync as readFileSync3, mkdirSync as mkdirSync2, chmodSync as chmodSync2 } from "node:fs";
|
|
59
77
|
import { join as join6, resolve as resolve2, sep, dirname } from "node:path";
|
|
@@ -71,7 +89,7 @@ function assertInstallableEntrypoint(entrypoint) {
|
|
|
71
89
|
if (isEphemeralEntrypoint(entrypoint)) {
|
|
72
90
|
throw new Error(
|
|
73
91
|
`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:
|
|
74
|
-
npm i -g @agentvault/grok-bridge && agentvault-grok-bridge `
|
|
92
|
+
npm i -g @agentvault/grok-bridge && agentvault-grok-bridge install`
|
|
75
93
|
);
|
|
76
94
|
}
|
|
77
95
|
}
|
|
@@ -83,6 +101,17 @@ function hasRecoverableBackup(dataDir) {
|
|
|
83
101
|
return false;
|
|
84
102
|
}
|
|
85
103
|
}
|
|
104
|
+
function readPersistedDeviceId(dataDir) {
|
|
105
|
+
for (const f2 of [...CRED_FILES, BACKUP_FILE]) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(readFileSync3(join6(dataDir, f2), "utf-8"));
|
|
108
|
+
const id = parsed?.deviceId;
|
|
109
|
+
if (typeof id === "string" && id) return id;
|
|
110
|
+
} catch {
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
86
115
|
function hasPersistedCreds(dataDir) {
|
|
87
116
|
if (CRED_FILES.some((f2) => existsSync2(join6(dataDir, f2)))) return true;
|
|
88
117
|
return hasRecoverableBackup(dataDir);
|
|
@@ -91,20 +120,41 @@ function slugify2(name) {
|
|
|
91
120
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
92
121
|
}
|
|
93
122
|
function resolveDataDir(env) {
|
|
94
|
-
|
|
123
|
+
const supplied = env.AV_AGENT_NAME?.trim();
|
|
124
|
+
const agentName = supplied || LEGACY_DEFAULT_AGENT_NAME;
|
|
125
|
+
const agentNameSource = supplied ? "supplied" : "defaulted";
|
|
126
|
+
if (env.AV_DATA_DIR)
|
|
127
|
+
return { dataDir: env.AV_DATA_DIR, source: "explicit", agentName, agentNameSource };
|
|
95
128
|
const base = join6(env.HOME ?? "", ".agentvault", "grok-room-bridge");
|
|
96
|
-
|
|
129
|
+
const perAgent = join6(base, slugify2(agentName));
|
|
130
|
+
if (!supplied && !hasPersistedCreds(perAgent)) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`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}, so there is no existing install to infer a name from. Re-run with AV_AGENT_NAME=<agent-name> set.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return { dataDir: perAgent, source: "per-agent", agentName, agentNameSource };
|
|
97
136
|
}
|
|
98
137
|
function loadConfig(env, argv = []) {
|
|
99
|
-
const { dataDir, source: dataDirSource } = resolveDataDir(env);
|
|
138
|
+
const { dataDir, source: dataDirSource, agentName, agentNameSource } = resolveDataDir(env);
|
|
139
|
+
if (agentNameSource === "defaulted") {
|
|
140
|
+
logLine(
|
|
141
|
+
`[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.`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
100
144
|
const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || readPendingInvite(dataDir) || "";
|
|
145
|
+
if (argv.includes(REMOVED_INSTALL_FLAG)) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`${REMOVED_INSTALL_FLAG} was removed \u2014 it handed the agent over from a foreground process that could be reaped before the encrypted group finished forming, leaving an agent that was connected and mute (#909). Use the subcommand instead, which lets the service enrol itself:
|
|
148
|
+
npm i -g @agentvault/grok-bridge && AV_AGENT_NAME=<agent-name> AV_INVITE_TOKEN=<token> agentvault-grok-bridge install`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
101
151
|
const unknownFlags = argv.filter((a2) => a2.startsWith("-") && !KNOWN_FLAGS.has(a2));
|
|
102
152
|
if (unknownFlags.length) {
|
|
103
153
|
throw new Error(
|
|
104
154
|
`unknown option(s): ${unknownFlags.join(", ")} \u2014 supported flags are ${[...KNOWN_FLAGS].join(", ")}`
|
|
105
155
|
);
|
|
106
156
|
}
|
|
107
|
-
const workspaceDir = env.AV_WORKSPACE_DIR || join6(dirname(dataDir), "workspaces", slugify2(
|
|
157
|
+
const workspaceDir = env.AV_WORKSPACE_DIR || join6(dirname(dataDir), "workspaces", slugify2(agentName));
|
|
108
158
|
const wsReal = resolve2(workspaceDir);
|
|
109
159
|
const ddReal = resolve2(dataDir);
|
|
110
160
|
if (wsReal === ddReal || wsReal.startsWith(ddReal + sep) || ddReal.startsWith(wsReal + sep)) {
|
|
@@ -129,17 +179,14 @@ function loadConfig(env, argv = []) {
|
|
|
129
179
|
inviteToken,
|
|
130
180
|
dataDir,
|
|
131
181
|
dataDirSource,
|
|
132
|
-
// One-paste install (--install): enrol, hand off to the supervisor, exit.
|
|
133
|
-
// Default false keeps a bare `npx @agentvault/grok-bridge` run identical.
|
|
134
|
-
installAfterEnroll: argv.includes(INSTALL_FLAG),
|
|
135
182
|
...parseInfoFlags(argv),
|
|
136
183
|
apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
|
|
137
184
|
// Identity shown to the agent's own session. Set this to the agent's
|
|
138
185
|
// AgentVault name via AV_AGENT_NAME (the native connect command passes it).
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
186
|
+
// Resolved in resolveDataDir so the name, the data dir and the workspace dir
|
|
187
|
+
// can never disagree about who this agent is (#921/#927).
|
|
188
|
+
agentName,
|
|
189
|
+
agentNameSource,
|
|
143
190
|
roomFilter: env.AV_ROOM_ID || void 0,
|
|
144
191
|
workspaceDir,
|
|
145
192
|
grokPath: env.AV_GROK_PATH || void 0,
|
|
@@ -149,35 +196,19 @@ function loadConfig(env, argv = []) {
|
|
|
149
196
|
systemPrompt: env.AV_SYSTEM_PROMPT || void 0
|
|
150
197
|
};
|
|
151
198
|
}
|
|
152
|
-
var
|
|
199
|
+
var REMOVED_INSTALL_FLAG, VERSION_FLAGS, HELP_FLAGS, KNOWN_FLAGS, CRED_FILES, BACKUP_FILE, LEGACY_DEFAULT_AGENT_NAME;
|
|
153
200
|
var init_config = __esm({
|
|
154
201
|
"src/config.ts"() {
|
|
155
202
|
"use strict";
|
|
156
203
|
init_pending_invite();
|
|
157
|
-
|
|
204
|
+
init_log();
|
|
205
|
+
REMOVED_INSTALL_FLAG = "--install";
|
|
158
206
|
VERSION_FLAGS = ["--version", "-v"];
|
|
159
207
|
HELP_FLAGS = ["--help", "-h"];
|
|
160
|
-
KNOWN_FLAGS = /* @__PURE__ */ new Set([
|
|
208
|
+
KNOWN_FLAGS = /* @__PURE__ */ new Set([...VERSION_FLAGS, ...HELP_FLAGS]);
|
|
161
209
|
CRED_FILES = ["agentvault.json", "secure-channel.json"];
|
|
162
210
|
BACKUP_FILE = "agentvault.json.bak";
|
|
163
|
-
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// src/log.ts
|
|
167
|
-
import { format } from "node:util";
|
|
168
|
-
function stamp(message) {
|
|
169
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
170
|
-
return message.split("\n").map((line) => `${ts} ${line}`).join("\n");
|
|
171
|
-
}
|
|
172
|
-
function logLine(...args) {
|
|
173
|
-
console.error(stamp(format(...args)));
|
|
174
|
-
}
|
|
175
|
-
function printLine(text) {
|
|
176
|
-
console.log(text);
|
|
177
|
-
}
|
|
178
|
-
var init_log = __esm({
|
|
179
|
-
"src/log.ts"() {
|
|
180
|
-
"use strict";
|
|
211
|
+
LEGACY_DEFAULT_AGENT_NAME = "Grok";
|
|
181
212
|
}
|
|
182
213
|
});
|
|
183
214
|
|
|
@@ -211,16 +242,6 @@ function writeLauncher(deps, spec) {
|
|
|
211
242
|
deps.writeFile(spec.launcherPath, spec.launcherScript);
|
|
212
243
|
deps.chmod(spec.launcherPath, 493);
|
|
213
244
|
}
|
|
214
|
-
function parseLauncherEntrypoint(script) {
|
|
215
|
-
if (!script) return void 0;
|
|
216
|
-
const m2 = /^if n=\$\(command -v .+\); then exec "\$n" (.+) "\$@"; fi$/m.exec(script);
|
|
217
|
-
if (!m2) return void 0;
|
|
218
|
-
return unSq(m2[1]);
|
|
219
|
-
}
|
|
220
|
-
function unSq(quoted) {
|
|
221
|
-
if (quoted.length < 2 || !quoted.startsWith("'") || !quoted.endsWith("'")) return void 0;
|
|
222
|
-
return quoted.slice(1, -1).replace(/'\\''/g, "'");
|
|
223
|
-
}
|
|
224
245
|
var FALLBACK_NODES;
|
|
225
246
|
var init_launcher = __esm({
|
|
226
247
|
"src/service/launcher.ts"() {
|
|
@@ -314,6 +335,37 @@ ${envEntries}
|
|
|
314
335
|
</plist>
|
|
315
336
|
`;
|
|
316
337
|
}
|
|
338
|
+
function parsePlistEnv(xml) {
|
|
339
|
+
const out = {};
|
|
340
|
+
const keyIdx = xml.indexOf("<key>EnvironmentVariables</key>");
|
|
341
|
+
if (keyIdx < 0) return out;
|
|
342
|
+
const open = xml.indexOf("<dict>", keyIdx);
|
|
343
|
+
if (open < 0) return out;
|
|
344
|
+
let depth = 0;
|
|
345
|
+
let i2 = open;
|
|
346
|
+
let close = -1;
|
|
347
|
+
const tag = /<(\/?)dict>/g;
|
|
348
|
+
tag.lastIndex = open;
|
|
349
|
+
let m2;
|
|
350
|
+
while (m2 = tag.exec(xml)) {
|
|
351
|
+
depth += m2[1] ? -1 : 1;
|
|
352
|
+
if (depth === 0) {
|
|
353
|
+
close = m2.index;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
i2 = m2.index;
|
|
357
|
+
}
|
|
358
|
+
void i2;
|
|
359
|
+
if (close < 0) return out;
|
|
360
|
+
const body = xml.slice(open, close);
|
|
361
|
+
const pair = /<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/g;
|
|
362
|
+
let p2;
|
|
363
|
+
while (p2 = pair.exec(body)) out[unesc(p2[1])] = unesc(p2[2]);
|
|
364
|
+
return out;
|
|
365
|
+
}
|
|
366
|
+
function unesc(s2) {
|
|
367
|
+
return s2.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
368
|
+
}
|
|
317
369
|
var LaunchdBackend;
|
|
318
370
|
var init_launchd = __esm({
|
|
319
371
|
"src/service/launchd.ts"() {
|
|
@@ -368,6 +420,26 @@ var init_launchd = __esm({
|
|
|
368
420
|
const r2 = this.deps.exec("launchctl", ["print", `${this.domain()}/${label}`]);
|
|
369
421
|
return { loaded: r2.code === 0, detail: r2.stdout };
|
|
370
422
|
}
|
|
423
|
+
/** #913 item 3 — every installed service whose label starts with `prefix`.
|
|
424
|
+
*
|
|
425
|
+
* Reads the plist directory rather than `launchctl list`, because an ORPHAN
|
|
426
|
+
* is typically installed-but-not-loaded: the revoked device exits 0 and
|
|
427
|
+
* KeepAlive leaves it down. `launchctl list` would not show the very thing
|
|
428
|
+
* doctor exists to find. */
|
|
429
|
+
list(prefix) {
|
|
430
|
+
const dir = join11(this.deps.home, "Library", "LaunchAgents");
|
|
431
|
+
const out = [];
|
|
432
|
+
for (const name of this.deps.readdir(dir)) {
|
|
433
|
+
if (!name.startsWith(prefix) || !name.endsWith(".plist")) continue;
|
|
434
|
+
const path2 = join11(dir, name);
|
|
435
|
+
out.push({
|
|
436
|
+
label: name.slice(0, -".plist".length),
|
|
437
|
+
path: path2,
|
|
438
|
+
env: parsePlistEnv(this.deps.readFile(path2))
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
return out.sort((a2, b2) => a2.label.localeCompare(b2.label));
|
|
442
|
+
}
|
|
371
443
|
};
|
|
372
444
|
}
|
|
373
445
|
});
|
|
@@ -399,6 +471,24 @@ StandardError=append:${spec.logErr}
|
|
|
399
471
|
WantedBy=default.target
|
|
400
472
|
`;
|
|
401
473
|
}
|
|
474
|
+
function parseUnitEnv(text) {
|
|
475
|
+
const out = {};
|
|
476
|
+
for (const line of text.split("\n")) {
|
|
477
|
+
const m2 = /^Environment="([\s\S]*)"\s*$/.exec(line.trim());
|
|
478
|
+
if (!m2) continue;
|
|
479
|
+
const eq = unq(m2[1]);
|
|
480
|
+
const at = eq.indexOf("=");
|
|
481
|
+
if (at <= 0) continue;
|
|
482
|
+
out[eq.slice(0, at)] = eq.slice(at + 1);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
function unq(s2) {
|
|
487
|
+
return s2.replace(/%%/g, "%").replace(
|
|
488
|
+
/\\([\s\S])/g,
|
|
489
|
+
(_2, c2) => c2 === "n" ? "\n" : c2 === "r" ? "\r" : c2 === "t" ? " " : c2
|
|
490
|
+
);
|
|
491
|
+
}
|
|
402
492
|
var SystemdBackend;
|
|
403
493
|
var init_systemd = __esm({
|
|
404
494
|
"src/service/systemd.ts"() {
|
|
@@ -439,18 +529,33 @@ var init_systemd = __esm({
|
|
|
439
529
|
const active = this.deps.exec("systemctl", ["--user", "is-active", `${label}.service`]);
|
|
440
530
|
return { loaded: enabled.code === 0, detail: `${enabled.stdout.trim() || "?"} / ${active.stdout.trim() || "?"}` };
|
|
441
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 = join12(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 = join12(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, b2) => a2.label.localeCompare(b2.label));
|
|
551
|
+
}
|
|
442
552
|
};
|
|
443
553
|
}
|
|
444
554
|
});
|
|
445
555
|
|
|
446
556
|
// src/service/backend.ts
|
|
447
|
-
var backend_exports = {};
|
|
448
|
-
__export(backend_exports, {
|
|
449
|
-
defaultDeps: () => defaultDeps,
|
|
450
|
-
selectBackend: () => selectBackend
|
|
451
|
-
});
|
|
452
557
|
import { spawnSync } from "node:child_process";
|
|
453
|
-
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, rmSync as rmSync2, existsSync as existsSync4, chmodSync as chmodSync5 } from "node:fs";
|
|
558
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync3, rmSync as rmSync2, existsSync as existsSync4, chmodSync as chmodSync5, readdirSync, readFileSync as readFileSync4 } from "node:fs";
|
|
454
559
|
import { userInfo } from "node:os";
|
|
455
560
|
function defaultDeps() {
|
|
456
561
|
return {
|
|
@@ -465,7 +570,21 @@ function defaultDeps() {
|
|
|
465
570
|
mkdir: (p2) => mkdirSync5(p2, { recursive: true }),
|
|
466
571
|
chmod: (p2, m2) => chmodSync5(p2, m2),
|
|
467
572
|
rm: (p2) => rmSync2(p2, { force: true }),
|
|
468
|
-
exists: (p2) => existsSync4(p2)
|
|
573
|
+
exists: (p2) => existsSync4(p2),
|
|
574
|
+
readdir: (p2) => {
|
|
575
|
+
try {
|
|
576
|
+
return readdirSync(p2);
|
|
577
|
+
} catch {
|
|
578
|
+
return [];
|
|
579
|
+
}
|
|
580
|
+
},
|
|
581
|
+
readFile: (p2) => {
|
|
582
|
+
try {
|
|
583
|
+
return readFileSync4(p2, "utf8");
|
|
584
|
+
} catch {
|
|
585
|
+
return "";
|
|
586
|
+
}
|
|
587
|
+
}
|
|
469
588
|
};
|
|
470
589
|
}
|
|
471
590
|
function selectBackend(platform, deps) {
|
|
@@ -488,16 +607,20 @@ var init_backend = __esm({
|
|
|
488
607
|
// src/service/subcommand.ts
|
|
489
608
|
var subcommand_exports = {};
|
|
490
609
|
__export(subcommand_exports, {
|
|
610
|
+
labelFor: () => labelFor,
|
|
491
611
|
maybeRunServiceSubcommand: () => maybeRunServiceSubcommand
|
|
492
612
|
});
|
|
493
|
-
function
|
|
613
|
+
function requireAgentName(env) {
|
|
494
614
|
const name = env.AV_AGENT_NAME?.trim();
|
|
495
615
|
if (!name) {
|
|
496
616
|
throw new Error(
|
|
497
617
|
"AV_AGENT_NAME is required \u2014 it names WHICH agent's service to act on. Re-run as: AV_AGENT_NAME=<agent-name> agentvault-grok-bridge <subcommand>"
|
|
498
618
|
);
|
|
499
619
|
}
|
|
500
|
-
return
|
|
620
|
+
return name;
|
|
621
|
+
}
|
|
622
|
+
function labelFor(env) {
|
|
623
|
+
return `dev.agentvault.grok-${slugify2(requireAgentName(env))}`;
|
|
501
624
|
}
|
|
502
625
|
function maybeRunServiceSubcommand(argv, env, deps) {
|
|
503
626
|
const cmd = argv[2];
|
|
@@ -507,7 +630,9 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
507
630
|
const looksLikeFlag = cmd.startsWith("-");
|
|
508
631
|
if (!looksLikeToken && !looksLikeFlag) {
|
|
509
632
|
throw new Error(
|
|
510
|
-
|
|
633
|
+
// "doctor" is dispatched earlier, in index.ts, so it never reaches here.
|
|
634
|
+
// Named anyway: a customer who typed `doctr` should see it offered.
|
|
635
|
+
`unknown subcommand: ${cmd} \u2014 expected one of ${[...SERVICE_CMDS, "doctor"].join(", ")}, an invite token, or no argument at all`
|
|
511
636
|
);
|
|
512
637
|
}
|
|
513
638
|
return false;
|
|
@@ -517,6 +642,8 @@ function maybeRunServiceSubcommand(argv, env, deps) {
|
|
|
517
642
|
const log = deps?.log ?? ((m2) => logLine("[grok-bridge] " + m2));
|
|
518
643
|
try {
|
|
519
644
|
if (cmd === "install") {
|
|
645
|
+
requireAgentName(env);
|
|
646
|
+
assertInstallableEntrypoint(entrypoint);
|
|
520
647
|
const cfg = loadConfig(env, []);
|
|
521
648
|
const firstBoot = !hasPersistedCreds(cfg.dataDir) && !!cfg.inviteToken;
|
|
522
649
|
if (firstBoot) {
|
|
@@ -574,96 +701,313 @@ var init_subcommand = __esm({
|
|
|
574
701
|
}
|
|
575
702
|
});
|
|
576
703
|
|
|
577
|
-
// src/service/
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
704
|
+
// src/service/doctor.ts
|
|
705
|
+
import { basename } from "node:path";
|
|
706
|
+
async function buildDoctorReport(d2) {
|
|
707
|
+
const installed = d2.labelPrefixes.flatMap((p2) => d2.backend.list(p2));
|
|
708
|
+
const services = [];
|
|
709
|
+
for (const s2 of installed) services.push(await inspect(s2, d2));
|
|
710
|
+
const claimed = new Set(installed.map((s2) => s2.env.AV_DATA_DIR).filter(Boolean));
|
|
711
|
+
const unsupervised = [];
|
|
712
|
+
for (const dataDir of d2.credDirs()) {
|
|
713
|
+
if (claimed.has(dataDir)) continue;
|
|
714
|
+
unsupervised.push(await inspectUnsupervised(dataDir, d2));
|
|
715
|
+
}
|
|
716
|
+
return {
|
|
717
|
+
labelPrefixes: d2.labelPrefixes,
|
|
718
|
+
services,
|
|
719
|
+
unsupervised,
|
|
720
|
+
notChecked: d2.otherLabels()
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
async function inspect(s2, d2) {
|
|
724
|
+
const agentName = s2.env.AV_AGENT_NAME ?? "";
|
|
725
|
+
const dataDir = s2.env.AV_DATA_DIR ?? "";
|
|
726
|
+
let loaded = false;
|
|
727
|
+
try {
|
|
728
|
+
loaded = d2.backend.status(s2.label).loaded;
|
|
729
|
+
} catch {
|
|
730
|
+
}
|
|
731
|
+
const base = { label: s2.label, agentName, dataDir, loaded, deviceId: "" };
|
|
732
|
+
if (!dataDir) {
|
|
733
|
+
return {
|
|
734
|
+
...base,
|
|
735
|
+
verdict: "WARN",
|
|
736
|
+
detail: "the service definition bakes no AV_DATA_DIR, so doctor cannot find its credentials and cannot check its device"
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
const creds = d2.readCreds(dataDir);
|
|
740
|
+
if (!creds?.deviceId) {
|
|
741
|
+
return {
|
|
742
|
+
...base,
|
|
743
|
+
verdict: "WARN",
|
|
744
|
+
detail: `no credentials at ${dataDir} \u2014 installed, but this agent never enrolled`
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
const deviceId = creds.deviceId;
|
|
748
|
+
let probe;
|
|
749
|
+
try {
|
|
750
|
+
probe = await d2.fetchStatus(d2.apiUrl, deviceId);
|
|
751
|
+
} catch (e) {
|
|
752
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${e.message}` };
|
|
753
|
+
}
|
|
754
|
+
if (probe.inconclusive) {
|
|
755
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${probe.inconclusive}` };
|
|
756
|
+
}
|
|
757
|
+
if (probe.gone) {
|
|
758
|
+
return {
|
|
759
|
+
...base,
|
|
760
|
+
deviceId,
|
|
761
|
+
verdict: "FAIL",
|
|
762
|
+
detail: "the device no longer exists \u2014 this service is an orphan",
|
|
763
|
+
fix: d2.uninstallCommand(agentName)
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
switch (probe.status) {
|
|
767
|
+
case "ACTIVE":
|
|
768
|
+
return { ...base, deviceId, verdict: "PASS", detail: "device ACTIVE" };
|
|
769
|
+
case "PENDING":
|
|
770
|
+
return {
|
|
771
|
+
...base,
|
|
772
|
+
deviceId,
|
|
773
|
+
verdict: "WARN",
|
|
774
|
+
detail: "device PENDING \u2014 enrolled, but never approved in AgentVault"
|
|
775
|
+
};
|
|
776
|
+
case "REVOKED":
|
|
777
|
+
return {
|
|
778
|
+
...base,
|
|
779
|
+
deviceId,
|
|
780
|
+
verdict: "FAIL",
|
|
781
|
+
detail: "the device was REVOKED \u2014 this service is an orphan",
|
|
782
|
+
fix: d2.uninstallCommand(agentName)
|
|
783
|
+
};
|
|
784
|
+
default:
|
|
785
|
+
return {
|
|
786
|
+
...base,
|
|
787
|
+
deviceId,
|
|
788
|
+
verdict: "SKIP",
|
|
789
|
+
detail: `the server reported a status this build does not recognise (${probe.status || "none"})`
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async function inspectUnsupervised(dataDir, d2) {
|
|
794
|
+
const agentName = basename(dataDir);
|
|
795
|
+
const base = { dataDir, agentName, deviceId: "" };
|
|
796
|
+
const deviceId = d2.readCreds(dataDir)?.deviceId;
|
|
797
|
+
if (!deviceId) {
|
|
798
|
+
return { ...base, verdict: "WARN", detail: "credentials present but they hold no device id" };
|
|
799
|
+
}
|
|
800
|
+
let probe;
|
|
801
|
+
try {
|
|
802
|
+
probe = await d2.fetchStatus(d2.apiUrl, deviceId);
|
|
803
|
+
} catch (e) {
|
|
804
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${e.message}` };
|
|
805
|
+
}
|
|
806
|
+
if (probe.inconclusive) {
|
|
807
|
+
return { ...base, deviceId, verdict: "SKIP", detail: `could not check the device: ${probe.inconclusive}` };
|
|
808
|
+
}
|
|
809
|
+
if (probe.gone) {
|
|
810
|
+
return {
|
|
811
|
+
...base,
|
|
812
|
+
deviceId,
|
|
813
|
+
verdict: "WARN",
|
|
814
|
+
detail: "the device no longer exists \u2014 these are leftover credentials for a deleted agent"
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
if (probe.status === "ACTIVE") {
|
|
818
|
+
return {
|
|
819
|
+
...base,
|
|
820
|
+
deviceId,
|
|
821
|
+
verdict: "WARN",
|
|
822
|
+
detail: "the device is ACTIVE but nothing supervises it \u2014 this agent is not running",
|
|
823
|
+
fix: d2.installCommand(agentName)
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
if (probe.status === "PENDING") {
|
|
827
|
+
return {
|
|
828
|
+
...base,
|
|
829
|
+
deviceId,
|
|
830
|
+
verdict: "WARN",
|
|
831
|
+
detail: "device PENDING \u2014 enrolled, but never approved in AgentVault"
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
if (probe.status === "REVOKED") {
|
|
835
|
+
return {
|
|
836
|
+
...base,
|
|
837
|
+
deviceId,
|
|
838
|
+
verdict: "WARN",
|
|
839
|
+
detail: "the device was REVOKED \u2014 these are leftover credentials"
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
return {
|
|
843
|
+
...base,
|
|
844
|
+
deviceId,
|
|
845
|
+
verdict: "SKIP",
|
|
846
|
+
detail: `the server reported a status this build does not recognise (${probe.status || "none"})`
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function formatDoctorReport(r2) {
|
|
850
|
+
const out = [];
|
|
851
|
+
out.push(`Services installed for this bridge (${r2.labelPrefixes.map((p2) => p2 + "*").join(", ")})`);
|
|
852
|
+
if (r2.services.length === 0) {
|
|
853
|
+
out.push(` No services installed for this bridge on this machine.`);
|
|
854
|
+
}
|
|
855
|
+
for (const s2 of r2.services) {
|
|
856
|
+
out.push(
|
|
857
|
+
` ${s2.verdict.padEnd(PAD)}${s2.label} [${s2.agentName || "unnamed"}] ${s2.loaded ? "loaded" : "not loaded"} \u2014 ${s2.detail}`
|
|
858
|
+
);
|
|
859
|
+
if (s2.fix) out.push(` ${"".padEnd(PAD)}fix: ${s2.fix}`);
|
|
860
|
+
}
|
|
861
|
+
if (r2.unsupervised.length) {
|
|
862
|
+
out.push("");
|
|
863
|
+
out.push("Credentials on disk that no installed service points at");
|
|
864
|
+
for (const u2 of r2.unsupervised) {
|
|
865
|
+
out.push(` ${u2.verdict.padEnd(PAD)}${u2.agentName} \u2014 ${u2.detail}`);
|
|
866
|
+
out.push(` ${"".padEnd(PAD)}${u2.dataDir}`);
|
|
867
|
+
if (u2.fix) out.push(` ${"".padEnd(PAD)}fix: ${u2.fix}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
if (r2.notChecked.length) {
|
|
871
|
+
out.push("");
|
|
872
|
+
out.push("Found on this machine, NOT checked by this command");
|
|
873
|
+
for (const l2 of r2.notChecked) out.push(` ${l2}`);
|
|
874
|
+
out.push(
|
|
875
|
+
" These belong to other AgentVault agent families. This command can only read the credentials of its own, so it says nothing about their health."
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
const counts = { PASS: 0, WARN: 0, FAIL: 0, SKIP: 0 };
|
|
879
|
+
for (const s2 of r2.services) counts[s2.verdict]++;
|
|
880
|
+
for (const u2 of r2.unsupervised) counts[u2.verdict]++;
|
|
881
|
+
out.push("");
|
|
882
|
+
out.push(
|
|
883
|
+
`Summary: ${counts.PASS} PASS, ${counts.WARN} WARN, ${counts.FAIL} FAIL, ${counts.SKIP} SKIP` + (counts.SKIP ? " (SKIP = could not check, NOT a problem found)" : "")
|
|
884
|
+
);
|
|
885
|
+
if (counts.FAIL) {
|
|
886
|
+
out.push(
|
|
887
|
+
"`uninstall` removes the SERVICE only \u2014 your credentials stay on disk, so a mistake here costs a re-install, not an identity."
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
return out;
|
|
891
|
+
}
|
|
892
|
+
var PAD;
|
|
893
|
+
var init_doctor = __esm({
|
|
894
|
+
"src/service/doctor.ts"() {
|
|
895
|
+
"use strict";
|
|
896
|
+
PAD = 6;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
// src/service/doctor-cli.ts
|
|
901
|
+
var doctor_cli_exports = {};
|
|
902
|
+
__export(doctor_cli_exports, {
|
|
903
|
+
fetchDeviceStatus: () => fetchDeviceStatus,
|
|
904
|
+
maybeRunDoctorSubcommand: () => maybeRunDoctorSubcommand,
|
|
905
|
+
realDoctorDeps: () => realDoctorDeps
|
|
581
906
|
});
|
|
582
|
-
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
583
907
|
import { join as join13 } from "node:path";
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
let fired = false;
|
|
589
|
-
channel.on("ready", () => {
|
|
590
|
-
if (fired) return;
|
|
591
|
-
fired = true;
|
|
592
|
-
void (async () => {
|
|
593
|
-
try {
|
|
594
|
-
if (!hasCreds(cfg.dataDir)) {
|
|
595
|
-
throw new Error(
|
|
596
|
-
`enrolment did not persist credentials to ${cfg.dataDir} \u2014 refusing to install a service that would crash-loop`
|
|
597
|
-
);
|
|
598
|
-
}
|
|
599
|
-
if (!fenceAuthExists(cfg.fenceHome)) {
|
|
600
|
-
throw new Error(
|
|
601
|
-
"no grok credential in the fence \u2014 run `grok login`, then paste the same command again (your enrolment is already saved, so the token is not needed). Note that XAI_API_KEY from your shell is NOT inherited by the background service."
|
|
602
|
-
);
|
|
603
|
-
}
|
|
604
|
-
if (deps.waitForIdle) {
|
|
605
|
-
const timeoutMs = deps.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
606
|
-
let timer;
|
|
607
|
-
const timedOut = await Promise.race([
|
|
608
|
-
deps.waitForIdle().then(() => false),
|
|
609
|
-
new Promise((r2) => {
|
|
610
|
-
timer = setTimeout(() => r2(true), timeoutMs);
|
|
611
|
-
})
|
|
612
|
-
]);
|
|
613
|
-
if (timer) clearTimeout(timer);
|
|
614
|
-
if (timedOut) {
|
|
615
|
-
deps.log(
|
|
616
|
-
`agent still busy after ${timeoutMs}ms \u2014 handing off anyway; that reply may be lost`
|
|
617
|
-
);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
const reaped = await deps.reap();
|
|
621
|
-
if (!reaped.ok) {
|
|
622
|
-
throw new Error(
|
|
623
|
-
`teardown did not complete (${reaped.failures.join("; ")}) \u2014 refusing to install over a session that may still be open`
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
const backend = deps.backend ?? selectBackend(process.platform);
|
|
627
|
-
const spec = buildServiceSpec(cfg, {
|
|
628
|
-
entrypoint: deps.entrypoint,
|
|
629
|
-
home: deps.env.HOME ?? "",
|
|
630
|
-
path: deps.env.PATH ?? ""
|
|
631
|
-
});
|
|
632
|
-
const installedEntrypoint = parseLauncherEntrypoint(readLauncher(spec.launcherPath));
|
|
633
|
-
const entrypointIsCurrent = installedEntrypoint !== void 0 && !isEphemeralEntrypoint(installedEntrypoint) && installedEntrypoint === deps.entrypoint;
|
|
634
|
-
if (backend.status(spec.label).loaded && entrypointIsCurrent) {
|
|
635
|
-
backend.restart(spec.label);
|
|
636
|
-
deps.log(`service ${spec.label} already installed and current \u2014 restarted`);
|
|
637
|
-
} else {
|
|
638
|
-
backend.install(spec);
|
|
639
|
-
deps.log(
|
|
640
|
-
installedEntrypoint && !entrypointIsCurrent ? `service ${spec.label} pointed at a stale entrypoint (${installedEntrypoint}) \u2014 re-installed` : `installed service ${spec.label}`
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
const after = backend.status(spec.label);
|
|
644
|
-
if (!after.loaded) {
|
|
645
|
-
throw new Error(`service did not load: ${after.detail}`);
|
|
646
|
-
}
|
|
647
|
-
deps.log(`it is running now. logs: ${spec.logErr}`);
|
|
648
|
-
deps.log(`check it: agentvault-grok-bridge status`);
|
|
649
|
-
deps.log(`restart it: agentvault-grok-bridge restart (never 'install')`);
|
|
650
|
-
deps.exit(0);
|
|
651
|
-
} catch (e) {
|
|
652
|
-
deps.log(`--install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
653
|
-
deps.exit(1);
|
|
654
|
-
}
|
|
655
|
-
})();
|
|
908
|
+
import { readdirSync as readdirSync2 } from "node:fs";
|
|
909
|
+
async function fetchDeviceStatus(apiUrl, deviceId, doFetch = fetch) {
|
|
910
|
+
const res = await doFetch(`${apiUrl}/api/v1/devices/${deviceId}/status`, {
|
|
911
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
|
|
656
912
|
});
|
|
913
|
+
if (res.status === 404) {
|
|
914
|
+
try {
|
|
915
|
+
await res.json();
|
|
916
|
+
} catch {
|
|
917
|
+
return {
|
|
918
|
+
inconclusive: `${apiUrl} answered 404 with a non-JSON body \u2014 check AV_API_URL points at the API`
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
return { gone: true };
|
|
922
|
+
}
|
|
923
|
+
if (res.status === 429) return { inconclusive: "rate limited by the API (429)" };
|
|
924
|
+
if (!res.ok) return { inconclusive: `the API answered ${res.status}` };
|
|
925
|
+
let body;
|
|
926
|
+
try {
|
|
927
|
+
body = await res.json();
|
|
928
|
+
} catch {
|
|
929
|
+
return { inconclusive: `${apiUrl} answered 200 with a non-JSON body \u2014 check AV_API_URL` };
|
|
930
|
+
}
|
|
931
|
+
return { status: body?.status };
|
|
932
|
+
}
|
|
933
|
+
function listCredDirs(base) {
|
|
934
|
+
const out = [];
|
|
935
|
+
if (hasPersistedCreds(base)) out.push(base);
|
|
936
|
+
let names;
|
|
937
|
+
try {
|
|
938
|
+
names = readdirSync2(base);
|
|
939
|
+
} catch {
|
|
940
|
+
return out;
|
|
941
|
+
}
|
|
942
|
+
for (const name of names) {
|
|
943
|
+
const dir = join13(base, name);
|
|
944
|
+
if (hasPersistedCreds(dir)) out.push(dir);
|
|
945
|
+
}
|
|
946
|
+
return out;
|
|
947
|
+
}
|
|
948
|
+
function otherFamilyLabels(backend) {
|
|
949
|
+
const out = [];
|
|
950
|
+
for (const p2 of OTHER_FAMILY_PREFIXES) {
|
|
951
|
+
try {
|
|
952
|
+
for (const s2 of backend.list(p2)) out.push(s2.label);
|
|
953
|
+
} catch {
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return out.sort();
|
|
957
|
+
}
|
|
958
|
+
function realDoctorDeps(env) {
|
|
959
|
+
const backend = selectBackend(process.platform);
|
|
960
|
+
const base = join13(env.HOME ?? "", ".agentvault", "grok-room-bridge");
|
|
961
|
+
return {
|
|
962
|
+
backend,
|
|
963
|
+
labelPrefixes: [LABEL_PREFIX],
|
|
964
|
+
base,
|
|
965
|
+
// Same default as config.ts. Read from the env directly rather than through
|
|
966
|
+
// loadConfig, which now REFUSES without AV_AGENT_NAME (#921) — and doctor's
|
|
967
|
+
// whole job is to run when you do not know which agents exist.
|
|
968
|
+
apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
|
|
969
|
+
uninstallCommand: (n2) => `AV_AGENT_NAME=${n2 || "<agent-name>"} agentvault-grok-bridge uninstall`,
|
|
970
|
+
installCommand: (n2) => `AV_AGENT_NAME=${n2 || "<agent-name>"} agentvault-grok-bridge install`,
|
|
971
|
+
readCreds: (dir) => {
|
|
972
|
+
const deviceId = readPersistedDeviceId(dir);
|
|
973
|
+
return deviceId ? { deviceId } : null;
|
|
974
|
+
},
|
|
975
|
+
credDirs: () => listCredDirs(base),
|
|
976
|
+
fetchStatus: (apiUrl, deviceId) => fetchDeviceStatus(apiUrl, deviceId),
|
|
977
|
+
otherLabels: () => otherFamilyLabels(backend)
|
|
978
|
+
};
|
|
657
979
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
980
|
+
async function maybeRunDoctorSubcommand(argv, opts = {}) {
|
|
981
|
+
if (argv[2] !== "doctor") return false;
|
|
982
|
+
const log = opts.log ?? printLine;
|
|
983
|
+
try {
|
|
984
|
+
const report = await buildDoctorReport(opts.deps ?? realDoctorDeps(opts.env ?? process.env));
|
|
985
|
+
for (const line of formatDoctorReport(report)) log(line);
|
|
986
|
+
if (report.services.some((s2) => s2.verdict === "FAIL")) process.exitCode = 1;
|
|
987
|
+
} catch (e) {
|
|
988
|
+
log(`doctor could not run: ${e.message}`);
|
|
989
|
+
process.exitCode = 1;
|
|
990
|
+
}
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
var LABEL_PREFIX, OTHER_FAMILY_PREFIXES, PROBE_TIMEOUT_MS;
|
|
994
|
+
var init_doctor_cli = __esm({
|
|
995
|
+
"src/service/doctor-cli.ts"() {
|
|
661
996
|
"use strict";
|
|
662
|
-
init_config();
|
|
663
|
-
init_spec();
|
|
664
|
-
init_launcher();
|
|
665
997
|
init_backend();
|
|
666
|
-
|
|
998
|
+
init_config();
|
|
999
|
+
init_log();
|
|
1000
|
+
init_doctor();
|
|
1001
|
+
LABEL_PREFIX = "dev.agentvault.grok-";
|
|
1002
|
+
OTHER_FAMILY_PREFIXES = [
|
|
1003
|
+
"dev.agentvault.bridge-",
|
|
1004
|
+
// claude bridge, current label
|
|
1005
|
+
"com.agentvault.claude-bridge.",
|
|
1006
|
+
// claude bridge, pre-rename label (#837)
|
|
1007
|
+
"ai.hermes.gateway"
|
|
1008
|
+
// hermes, one service per profile
|
|
1009
|
+
];
|
|
1010
|
+
PROBE_TIMEOUT_MS = 8e3;
|
|
667
1011
|
}
|
|
668
1012
|
});
|
|
669
1013
|
|
|
@@ -730,6 +1074,25 @@ var __toESM = (mod4, isNodeMode, target) => (target = mod4 != null ? __create(__
|
|
|
730
1074
|
isNodeMode || !mod4 || !mod4.__esModule ? __defProp2(target, "default", { value: mod4, enumerable: true }) : target,
|
|
731
1075
|
mod4
|
|
732
1076
|
));
|
|
1077
|
+
function toEpoch(v2) {
|
|
1078
|
+
if (typeof v2 === "bigint") return v2;
|
|
1079
|
+
if (typeof v2 === "number") {
|
|
1080
|
+
return Number.isFinite(v2) ? BigInt(Math.trunc(v2)) : null;
|
|
1081
|
+
}
|
|
1082
|
+
if (typeof v2 === "string" && /^\d+$/.test(v2)) return BigInt(v2);
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
function classifyCommitFailure(messageEpoch, groupEpoch) {
|
|
1086
|
+
const msg = toEpoch(messageEpoch);
|
|
1087
|
+
const grp = toEpoch(groupEpoch);
|
|
1088
|
+
if (msg === null || grp === null) return "count";
|
|
1089
|
+
return msg < grp ? "already-applied" : "count";
|
|
1090
|
+
}
|
|
1091
|
+
var init_mls_commit_classify = __esm2({
|
|
1092
|
+
"src/mls-commit-classify.ts"() {
|
|
1093
|
+
"use strict";
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
733
1096
|
var __filename;
|
|
734
1097
|
var __dirname;
|
|
735
1098
|
var url2;
|
|
@@ -64077,7 +64440,7 @@ var init_mls_kp_pool = __esm2({
|
|
|
64077
64440
|
}
|
|
64078
64441
|
});
|
|
64079
64442
|
function ownIdentity() {
|
|
64080
|
-
const v2 = true ? "0.23.
|
|
64443
|
+
const v2 = true ? "0.23.23" : FALLBACK;
|
|
64081
64444
|
return `${PACKAGE}@${v2}`;
|
|
64082
64445
|
}
|
|
64083
64446
|
function buildClientVersion(override) {
|
|
@@ -64989,6 +65352,7 @@ var SecureChannel;
|
|
|
64989
65352
|
var init_channel = __esm2({
|
|
64990
65353
|
async "src/channel.ts"() {
|
|
64991
65354
|
"use strict";
|
|
65355
|
+
init_mls_commit_classify();
|
|
64992
65356
|
await init_libsodium_wrappers();
|
|
64993
65357
|
await init_dist();
|
|
64994
65358
|
await init_dist();
|
|
@@ -66055,7 +66419,7 @@ var init_channel = __esm2({
|
|
|
66055
66419
|
*/
|
|
66056
66420
|
sendActivitySpan(spanData) {
|
|
66057
66421
|
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) return;
|
|
66058
|
-
const pluginVersion = true ? "0.23.
|
|
66422
|
+
const pluginVersion = true ? "0.23.23" : "0.0.0-dev";
|
|
66059
66423
|
const agentName = this.config.agentName ?? "Agent";
|
|
66060
66424
|
const resource = {
|
|
66061
66425
|
"service.name": "agentvault-agent",
|
|
@@ -67965,7 +68329,7 @@ var init_channel = __esm2({
|
|
|
67965
68329
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
67966
68330
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
67967
68331
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
67968
|
-
pluginVersion: true ? "0.23.
|
|
68332
|
+
pluginVersion: true ? "0.23.23" : "0.0.0-dev"
|
|
67969
68333
|
});
|
|
67970
68334
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
67971
68335
|
}
|
|
@@ -68289,7 +68653,7 @@ var init_channel = __esm2({
|
|
|
68289
68653
|
agentVersion: this.config.agentVersion ?? "0.0.0",
|
|
68290
68654
|
// __AV_VERSION__ is injected by esbuild from package.json at build time.
|
|
68291
68655
|
// Falls back to "0.0.0-dev" in non-bundled contexts (tests).
|
|
68292
|
-
pluginVersion: true ? "0.23.
|
|
68656
|
+
pluginVersion: true ? "0.23.23" : "0.0.0-dev"
|
|
68293
68657
|
});
|
|
68294
68658
|
this._telemetryReporter.startAutoFlush(3e4);
|
|
68295
68659
|
}
|
|
@@ -69695,7 +70059,14 @@ ${messageText}`;
|
|
|
69695
70059
|
console.log(`[SecureChannel] MLS commit processed for room ${roomId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
|
|
69696
70060
|
this._mlsCommitFailCounts.delete(roomId);
|
|
69697
70061
|
} catch (err) {
|
|
69698
|
-
await this._onCommitFailure(
|
|
70062
|
+
await this._onCommitFailure(
|
|
70063
|
+
roomId,
|
|
70064
|
+
groupId,
|
|
70065
|
+
err,
|
|
70066
|
+
`room ${roomId.slice(0, 8)}`,
|
|
70067
|
+
data.epoch,
|
|
70068
|
+
mlsGroup.epoch
|
|
70069
|
+
);
|
|
69699
70070
|
}
|
|
69700
70071
|
} else {
|
|
69701
70072
|
this._bufferMlsCommit(groupId, epoch, data);
|
|
@@ -69715,7 +70086,14 @@ ${messageText}`;
|
|
|
69715
70086
|
console.log(`[SecureChannel] MLS commit processed for A2A ${chId.slice(0, 8)} (epoch=${mlsGroup.epoch})`);
|
|
69716
70087
|
this._mlsCommitFailCounts.delete(`a2a:${chId}`);
|
|
69717
70088
|
} catch (err) {
|
|
69718
|
-
await this._onCommitFailure(
|
|
70089
|
+
await this._onCommitFailure(
|
|
70090
|
+
`a2a:${chId}`,
|
|
70091
|
+
groupId,
|
|
70092
|
+
err,
|
|
70093
|
+
`A2A ${chId.slice(0, 8)}`,
|
|
70094
|
+
data.epoch,
|
|
70095
|
+
mlsGroup.epoch
|
|
70096
|
+
);
|
|
69719
70097
|
}
|
|
69720
70098
|
} else {
|
|
69721
70099
|
this._bufferMlsCommit(groupId, epoch, data);
|
|
@@ -69801,7 +70179,13 @@ ${messageText}`;
|
|
|
69801
70179
|
* Never re-throws. The caller's only handler logs and drops, so throwing here
|
|
69802
70180
|
* is indistinguishable from swallowing — it just moves the swallow one frame up.
|
|
69803
70181
|
*/
|
|
69804
|
-
async _onCommitFailure(groupKey, mlsGroupId, err, label) {
|
|
70182
|
+
async _onCommitFailure(groupKey, mlsGroupId, err, label, messageEpoch, groupEpoch) {
|
|
70183
|
+
if (classifyCommitFailure(messageEpoch, groupEpoch) === "already-applied") {
|
|
70184
|
+
console.debug(
|
|
70185
|
+
`[SecureChannel] Skipping already-applied MLS commit for ${label} (commit epoch ${String(messageEpoch)} is behind group epoch ${String(groupEpoch)})`
|
|
70186
|
+
);
|
|
70187
|
+
return;
|
|
70188
|
+
}
|
|
69805
70189
|
const count = (this._mlsCommitFailCounts.get(groupKey) ?? 0) + 1;
|
|
69806
70190
|
this._mlsCommitFailCounts.set(groupKey, count);
|
|
69807
70191
|
console.error(
|
|
@@ -71699,7 +72083,7 @@ ${messageText}`;
|
|
|
71699
72083
|
return;
|
|
71700
72084
|
}
|
|
71701
72085
|
this._authFailedThisSession = true;
|
|
71702
|
-
const authReason = data?.reason ?? "
|
|
72086
|
+
const authReason = data?.reason ?? "unknown";
|
|
71703
72087
|
console.warn(
|
|
71704
72088
|
`[SecureChannel] connection_rejected (reason=${data?.reason ?? "unknown"}, retryable=false) \u2014 terminal; surfacing auth_failed`
|
|
71705
72089
|
);
|
|
@@ -98280,7 +98664,7 @@ var init_index = __esm2({
|
|
|
98280
98664
|
init_skill_invoker();
|
|
98281
98665
|
await init_skill_telemetry();
|
|
98282
98666
|
await init_policy_enforcer();
|
|
98283
|
-
VERSION = true ? "0.23.
|
|
98667
|
+
VERSION = true ? "0.23.23" : "0.0.0-dev";
|
|
98284
98668
|
}
|
|
98285
98669
|
});
|
|
98286
98670
|
await init_index();
|
|
@@ -98921,6 +99305,40 @@ async function startRoomMcpServer(opts) {
|
|
|
98921
99305
|
};
|
|
98922
99306
|
}
|
|
98923
99307
|
|
|
99308
|
+
// src/service/self-uninstall.ts
|
|
99309
|
+
init_subcommand();
|
|
99310
|
+
init_backend();
|
|
99311
|
+
init_log();
|
|
99312
|
+
var DEVICE_IS_GONE = "device_revoked";
|
|
99313
|
+
function selfUninstallOnTerminal(reason, env, deps = {}) {
|
|
99314
|
+
if (reason !== DEVICE_IS_GONE) return false;
|
|
99315
|
+
const log = deps.log ?? ((m2) => logLine("[grok-bridge] " + m2));
|
|
99316
|
+
let label;
|
|
99317
|
+
try {
|
|
99318
|
+
label = labelFor(env);
|
|
99319
|
+
} catch (e) {
|
|
99320
|
+
log(
|
|
99321
|
+
`device was revoked, but ${e.message} \u2014 cannot tell which service to remove. Remove it by hand with: AV_AGENT_NAME=<agent-name> agentvault-grok-bridge uninstall`
|
|
99322
|
+
);
|
|
99323
|
+
return false;
|
|
99324
|
+
}
|
|
99325
|
+
try {
|
|
99326
|
+
if (!deps.backend && process.platform !== "darwin" && process.platform !== "linux") return false;
|
|
99327
|
+
const backend = deps.backend ?? selectBackend(process.platform);
|
|
99328
|
+
if (!backend.status(label).loaded) return false;
|
|
99329
|
+
backend.uninstall(label);
|
|
99330
|
+
log(
|
|
99331
|
+
`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.`
|
|
99332
|
+
);
|
|
99333
|
+
return true;
|
|
99334
|
+
} catch (e) {
|
|
99335
|
+
log(
|
|
99336
|
+
`device was revoked, but removing the service ${label} failed: ${e.message}. Remove it by hand with: AV_AGENT_NAME=<agent-name> agentvault-grok-bridge uninstall`
|
|
99337
|
+
);
|
|
99338
|
+
return false;
|
|
99339
|
+
}
|
|
99340
|
+
}
|
|
99341
|
+
|
|
98924
99342
|
// src/bridge.ts
|
|
98925
99343
|
var RoomHushState = class {
|
|
98926
99344
|
until = /* @__PURE__ */ new Map();
|
|
@@ -98976,11 +99394,13 @@ var ActiveTarget = class {
|
|
|
98976
99394
|
function attachLifecycle2(channel, opts = {}) {
|
|
98977
99395
|
const log = opts.log ?? (() => {
|
|
98978
99396
|
});
|
|
99397
|
+
const selfUninstall = opts.selfUninstall ?? selfUninstallOnTerminal;
|
|
98979
99398
|
const onTerminal = opts.onTerminal ?? ((_reason, o2) => process.exit(o2.restart ? 1 : 0));
|
|
98980
99399
|
const terminal = (reason, restart) => {
|
|
98981
99400
|
log(
|
|
98982
99401
|
`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.`
|
|
98983
99402
|
);
|
|
99403
|
+
selfUninstall(reason, process.env);
|
|
98984
99404
|
onTerminal(reason, { restart });
|
|
98985
99405
|
};
|
|
98986
99406
|
channel.on("auth_failed", (e) => terminal(e.reason, false));
|
|
@@ -99134,6 +99554,10 @@ var shutdown = async (code = 0) => {
|
|
|
99134
99554
|
}
|
|
99135
99555
|
};
|
|
99136
99556
|
async function main() {
|
|
99557
|
+
const { maybeRunDoctorSubcommand: maybeRunDoctorSubcommand2 } = await Promise.resolve().then(() => (init_doctor_cli(), doctor_cli_exports));
|
|
99558
|
+
if (await maybeRunDoctorSubcommand2(process.argv)) {
|
|
99559
|
+
process.exit(process.exitCode ?? 0);
|
|
99560
|
+
}
|
|
99137
99561
|
const { maybeRunServiceSubcommand: maybeRunServiceSubcommand2 } = await Promise.resolve().then(() => (init_subcommand(), subcommand_exports));
|
|
99138
99562
|
if (maybeRunServiceSubcommand2(process.argv, process.env, {
|
|
99139
99563
|
log: (m2) => logLine("[grok-bridge] " + m2)
|
|
@@ -99143,7 +99567,7 @@ async function main() {
|
|
|
99143
99567
|
const info = parseInfoFlags(process.argv.slice(2));
|
|
99144
99568
|
if (info.showVersion || info.showHelp) {
|
|
99145
99569
|
const version2 = buildClientVersion2(
|
|
99146
|
-
true ? "0.
|
|
99570
|
+
true ? "0.4.0" : void 0
|
|
99147
99571
|
);
|
|
99148
99572
|
if (info.showVersion) printLine(version2);
|
|
99149
99573
|
if (info.showHelp) {
|
|
@@ -99182,11 +99606,6 @@ async function main() {
|
|
|
99182
99606
|
process.exit(0);
|
|
99183
99607
|
}
|
|
99184
99608
|
const cfg = loadConfig(process.env, process.argv.slice(2));
|
|
99185
|
-
if (cfg.installAfterEnroll) {
|
|
99186
|
-
assertInstallableEntrypoint(process.argv[1]);
|
|
99187
|
-
const { selectBackend: selectBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
|
99188
|
-
selectBackend2(process.platform);
|
|
99189
|
-
}
|
|
99190
99609
|
mkdirSync6(cfg.workspaceDir, { recursive: true });
|
|
99191
99610
|
mkdirSync6(cfg.fenceHome, { recursive: true });
|
|
99192
99611
|
seedFenceCredential({
|
|
@@ -99201,7 +99620,7 @@ async function main() {
|
|
|
99201
99620
|
agentName: cfg.agentName,
|
|
99202
99621
|
platform: "node",
|
|
99203
99622
|
clientVersion: buildClientVersion2(
|
|
99204
|
-
true ? "0.
|
|
99623
|
+
true ? "0.4.0" : void 0
|
|
99205
99624
|
)
|
|
99206
99625
|
});
|
|
99207
99626
|
channelRef = channel;
|
|
@@ -99292,23 +99711,6 @@ async function main() {
|
|
|
99292
99711
|
logLine("[grok-bridge] enrolled \u2014 pending invite consumed and removed");
|
|
99293
99712
|
}
|
|
99294
99713
|
});
|
|
99295
|
-
if (cfg.installAfterEnroll) {
|
|
99296
|
-
const { installOnReady: installOnReady2 } = await Promise.resolve().then(() => (init_install_on_ready(), install_on_ready_exports));
|
|
99297
|
-
installOnReady2(channel, cfg, {
|
|
99298
|
-
entrypoint: process.argv[1],
|
|
99299
|
-
env: process.env,
|
|
99300
|
-
// FOUND LIVE 2026-08-16: the owner's activation greeting is usually already
|
|
99301
|
-
// being answered when `ready` fires, and reaping mid-turn killed the reply
|
|
99302
|
-
// for good — the message was acked, so the service never saw it again.
|
|
99303
|
-
// GrokSession.idle() resolves when the queue drains.
|
|
99304
|
-
waitForIdle: () => session.idle(),
|
|
99305
|
-
// The one shared teardown. `exiting` is deliberately NOT set here: Ctrl-C
|
|
99306
|
-
// must keep working for the whole multi-second launchctl window.
|
|
99307
|
-
reap: reapAll,
|
|
99308
|
-
exit: (code) => process.exit(code),
|
|
99309
|
-
log: (m2) => logLine("[grok-bridge] " + m2)
|
|
99310
|
-
});
|
|
99311
|
-
}
|
|
99312
99714
|
await channel.start();
|
|
99313
99715
|
}
|
|
99314
99716
|
main().catch(async (err) => {
|