@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/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 ` + INSTALL_FLAG
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
- if (env.AV_DATA_DIR) return { dataDir: env.AV_DATA_DIR, source: "explicit" };
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
- return { dataDir: join6(base, slugify2(env.AV_AGENT_NAME ?? "grok")), source: "per-agent" };
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(env.AV_AGENT_NAME ?? "grok"));
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
- // Defaults to "Grok" — this package IS the Grok bridge, so an unset
140
- // AV_AGENT_NAME still names the actual model running the session, not a
141
- // placeholder impersonating some other agent.
142
- agentName: env.AV_AGENT_NAME ?? "Grok",
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 INSTALL_FLAG, VERSION_FLAGS, HELP_FLAGS, KNOWN_FLAGS, CRED_FILES, BACKUP_FILE;
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
- INSTALL_FLAG = "--install";
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([INSTALL_FLAG, ...VERSION_FLAGS, ...HELP_FLAGS]);
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(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/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 labelFor(env) {
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 `dev.agentvault.grok-${slugify2(name)}`;
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
- `unknown subcommand: ${cmd} \u2014 expected one of ${[...SERVICE_CMDS].join(", ")}, an invite token, or no argument at all`
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/install-on-ready.ts
578
- var install_on_ready_exports = {};
579
- __export(install_on_ready_exports, {
580
- installOnReady: () => installOnReady
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
- function installOnReady(channel, cfg, deps) {
585
- const hasCreds = deps.hasCreds ?? hasPersistedCreds;
586
- const readLauncher = deps.readLauncher ?? ((p2) => existsSync5(p2) ? readFileSync4(p2, "utf8") : void 0);
587
- const fenceAuthExists = deps.fenceAuthExists ?? ((home) => existsSync5(join13(home, ".grok", "auth.json")));
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
- var DEFAULT_IDLE_TIMEOUT_MS;
659
- var init_install_on_ready = __esm({
660
- "src/service/install-on-ready.ts"() {
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
- DEFAULT_IDLE_TIMEOUT_MS = 3e4;
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.22" : FALLBACK;
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.22" : "0.0.0-dev";
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.22" : "0.0.0-dev"
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.22" : "0.0.0-dev"
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(roomId, groupId, err, `room ${roomId.slice(0, 8)}`);
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(`a2a:${chId}`, groupId, err, `A2A ${chId.slice(0, 8)}`);
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 ?? "device_revoked";
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.22" : "0.0.0-dev";
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.3.1" : void 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.3.1" : void 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) => {