@agentvault/grok-bridge 0.1.0 → 0.2.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/README.md +54 -0
- package/dist/config.d.ts +30 -0
- package/dist/index.js +234 -33
- package/dist/reap.d.ts +66 -0
- package/dist/service/install-on-ready.d.ts +57 -0
- package/dist/service/install-on-ready.d.ts.map +1 -0
- package/dist/service/launcher.d.ts +19 -0
- package/dist/service/launcher.d.ts.map +1 -1
- package/package.json +19 -6
package/README.md
CHANGED
|
@@ -6,6 +6,60 @@ Bridges a Grok agent into E2E-encrypted AgentVault direct messages and rooms.
|
|
|
6
6
|
no runtime code. The only shared dependency is `@agentvault/agentvault`, the
|
|
7
7
|
protocol and crypto library.
|
|
8
8
|
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
**Prerequisite:** install Grok and run `grok login`. The bridge copies that
|
|
12
|
+
credential into its sandbox once, at first run.
|
|
13
|
+
|
|
14
|
+
### One paste (recommended) — `--install`
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npm i -g @agentvault/grok-bridge
|
|
18
|
+
AV_AGENT_NAME=<agent> AV_INVITE_TOKEN=<token> agentvault-grok-bridge --install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Enrols the agent, waits for you to click **Approve** in the dashboard, installs a
|
|
22
|
+
supervised OS service (launchd on macOS, systemd on Linux), and **exits** — so
|
|
23
|
+
your terminal comes back and the agent survives closing it and rebooting.
|
|
24
|
+
|
|
25
|
+
Three things worth knowing:
|
|
26
|
+
|
|
27
|
+
- **`npm i -g` is not optional.** The service bakes the running entrypoint into
|
|
28
|
+
its launcher. Under `npx` that is a package cache the package manager later
|
|
29
|
+
prunes, so the service would work today and die silently weeks later.
|
|
30
|
+
`--install` refuses an ephemeral entrypoint at startup, before your single-use
|
|
31
|
+
invite token is spent.
|
|
32
|
+
- **`XAI_API_KEY` is NOT inherited by the service.** A supervisor does not read
|
|
33
|
+
your shell environment. Use `grok login`; the bridge refuses to install a
|
|
34
|
+
service that would have no credential and run mute.
|
|
35
|
+
- **Re-running is safe.** With credentials already saved you can omit the token.
|
|
36
|
+
If the installed service is current it is restarted, not re-installed, so
|
|
37
|
+
settings baked at install time are preserved.
|
|
38
|
+
|
|
39
|
+
### Foreground (no service)
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
AV_AGENT_NAME=<agent> AV_INVITE_TOKEN=<token> npx @agentvault/grok-bridge
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Runs in the terminal and stops when you close it. Unchanged by `--install`.
|
|
46
|
+
|
|
47
|
+
### Managing the service
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
agentvault-grok-bridge status # is it loaded?
|
|
51
|
+
agentvault-grok-bridge restart # pick up new credentials / recover
|
|
52
|
+
agentvault-grok-bridge uninstall # remove it
|
|
53
|
+
agentvault-grok-bridge logs # where the logs are
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use `restart`, **never `install`**, to restart. `install` rebuilds the service
|
|
57
|
+
from your current shell and re-bakes `PATH`, silently discarding environment
|
|
58
|
+
baked at the original install.
|
|
59
|
+
|
|
60
|
+
Logs go to `<data-dir>/logs/grok-bridge.error.log` — the bridge writes to stderr,
|
|
61
|
+
so the `.error.log` file is the one with content in it.
|
|
62
|
+
|
|
9
63
|
## Copied-from ledger — REVIEW PERIODICALLY
|
|
10
64
|
|
|
11
65
|
These files were copied once from the Claude bridge and are now owned here. A fix
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one-paste install flag: enrol, install the supervised service, then EXIT so
|
|
3
|
+
* the customer's terminal comes back.
|
|
4
|
+
*
|
|
5
|
+
* WIRE LITERAL. `packages/web/lib/framework-install.ts` emits this exact string in
|
|
6
|
+
* the connect command. The two packages cannot import each other, so the only
|
|
7
|
+
* tripwire is a test on each side that names the other — see config.test.ts
|
|
8
|
+
* "freezes the wire literal".
|
|
9
|
+
*
|
|
10
|
+
* Deliberately "-" prefixed: loadConfig's token slot rejects "-" args, so adding a
|
|
11
|
+
* flag can never be mistaken for an invite token.
|
|
12
|
+
*/
|
|
13
|
+
export declare const INSTALL_FLAG = "--install";
|
|
14
|
+
/**
|
|
15
|
+
* Package-manager scratch caches. `service/subcommand.ts` bakes `process.argv[1]`
|
|
16
|
+
* into the launcher script, so a service installed from one of these points at a
|
|
17
|
+
* directory the package manager later prunes — it works today and dies weeks later
|
|
18
|
+
* with nothing naming the cause.
|
|
19
|
+
*
|
|
20
|
+
* A DENYLIST, not proof of durability: it covers npm's npx cache, pnpm/yarn `dlx`,
|
|
21
|
+
* `bunx`, and anything under the OS temp dir. Other ephemeral runners are knowingly
|
|
22
|
+
* unhandled.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isEphemeralEntrypoint(entrypoint: string): boolean;
|
|
25
|
+
/** Throw unless `entrypoint` is durable enough to bake into a supervised service. */
|
|
26
|
+
export declare function assertInstallableEntrypoint(entrypoint: string): void;
|
|
1
27
|
export declare function hasPersistedCreds(dataDir: string): boolean;
|
|
2
28
|
export declare function slugify(name: string): string;
|
|
3
29
|
export type DataDirSource = "explicit" | "per-agent";
|
|
@@ -38,6 +64,10 @@ export interface BridgeConfig {
|
|
|
38
64
|
* instructs the agent. It does not: given a bare message the model answered
|
|
39
65
|
* in prose and never called room__say, so the reply was discarded. */
|
|
40
66
|
systemPrompt?: string;
|
|
67
|
+
/** Set by --install: enrol in the foreground, install the supervised service,
|
|
68
|
+
* then EXIT so the customer's terminal returns. False leaves a bare run
|
|
69
|
+
* byte-for-byte as it behaves today. */
|
|
70
|
+
installAfterEnroll: boolean;
|
|
41
71
|
}
|
|
42
72
|
export declare function loadConfig(env: NodeJS.ProcessEnv, argv?: string[]): BridgeConfig;
|
|
43
73
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,18 @@ var __export = (target, all) => {
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { existsSync, readFileSync as readFileSync2, mkdirSync, chmodSync } from "node:fs";
|
|
14
14
|
import { join as join5, resolve as resolve2, sep, dirname } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
function isEphemeralEntrypoint(entrypoint) {
|
|
17
|
+
return entrypoint.includes("/_npx/") || entrypoint.includes("/dlx-") || entrypoint.includes("/.bun/install/cache/") || entrypoint.startsWith(tmpdir() + sep);
|
|
18
|
+
}
|
|
19
|
+
function assertInstallableEntrypoint(entrypoint) {
|
|
20
|
+
if (isEphemeralEntrypoint(entrypoint)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`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:
|
|
23
|
+
npm i -g @agentvault/grok-bridge && agentvault-grok-bridge ` + INSTALL_FLAG
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
function hasRecoverableBackup(dataDir) {
|
|
16
28
|
try {
|
|
17
29
|
const parsed = JSON.parse(readFileSync2(join5(dataDir, BACKUP_FILE), "utf-8"));
|
|
@@ -35,6 +47,12 @@ function resolveDataDir(env) {
|
|
|
35
47
|
function loadConfig(env, argv = []) {
|
|
36
48
|
const { dataDir, source: dataDirSource } = resolveDataDir(env);
|
|
37
49
|
const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || "";
|
|
50
|
+
const unknownFlags = argv.filter((a2) => a2.startsWith("-") && !KNOWN_FLAGS.has(a2));
|
|
51
|
+
if (unknownFlags.length) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`unknown option(s): ${unknownFlags.join(", ")} \u2014 the only supported flag is ${INSTALL_FLAG}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
38
56
|
const workspaceDir = env.AV_WORKSPACE_DIR || join5(dirname(dataDir), "workspaces", slugify2(env.AV_AGENT_NAME ?? "grok"));
|
|
39
57
|
const wsReal = resolve2(workspaceDir);
|
|
40
58
|
const ddReal = resolve2(dataDir);
|
|
@@ -60,6 +78,9 @@ function loadConfig(env, argv = []) {
|
|
|
60
78
|
inviteToken,
|
|
61
79
|
dataDir,
|
|
62
80
|
dataDirSource,
|
|
81
|
+
// One-paste install (--install): enrol, hand off to the supervisor, exit.
|
|
82
|
+
// Default false keeps a bare `npx @agentvault/grok-bridge` run identical.
|
|
83
|
+
installAfterEnroll: argv.includes(INSTALL_FLAG),
|
|
63
84
|
apiUrl: env.AV_API_URL ?? "https://api.agentvault.chat",
|
|
64
85
|
// Identity shown to the agent's own session. Set this to the agent's
|
|
65
86
|
// AgentVault name via AV_AGENT_NAME (the native connect command passes it).
|
|
@@ -76,10 +97,12 @@ function loadConfig(env, argv = []) {
|
|
|
76
97
|
systemPrompt: env.AV_SYSTEM_PROMPT || void 0
|
|
77
98
|
};
|
|
78
99
|
}
|
|
79
|
-
var CRED_FILES, BACKUP_FILE;
|
|
100
|
+
var INSTALL_FLAG, KNOWN_FLAGS, CRED_FILES, BACKUP_FILE;
|
|
80
101
|
var init_config = __esm({
|
|
81
102
|
"src/config.ts"() {
|
|
82
103
|
"use strict";
|
|
104
|
+
INSTALL_FLAG = "--install";
|
|
105
|
+
KNOWN_FLAGS = /* @__PURE__ */ new Set([INSTALL_FLAG]);
|
|
83
106
|
CRED_FILES = ["agentvault.json", "secure-channel.json"];
|
|
84
107
|
BACKUP_FILE = "agentvault.json.bak";
|
|
85
108
|
}
|
|
@@ -130,6 +153,16 @@ function writeLauncher(deps, spec) {
|
|
|
130
153
|
deps.writeFile(spec.launcherPath, spec.launcherScript);
|
|
131
154
|
deps.chmod(spec.launcherPath, 493);
|
|
132
155
|
}
|
|
156
|
+
function parseLauncherEntrypoint(script) {
|
|
157
|
+
if (!script) return void 0;
|
|
158
|
+
const m2 = /^if n=\$\(command -v .+\); then exec "\$n" (.+) "\$@"; fi$/m.exec(script);
|
|
159
|
+
if (!m2) return void 0;
|
|
160
|
+
return unSq(m2[1]);
|
|
161
|
+
}
|
|
162
|
+
function unSq(quoted) {
|
|
163
|
+
if (quoted.length < 2 || !quoted.startsWith("'") || !quoted.endsWith("'")) return void 0;
|
|
164
|
+
return quoted.slice(1, -1).replace(/'\\''/g, "'");
|
|
165
|
+
}
|
|
133
166
|
var FALLBACK_NODES;
|
|
134
167
|
var init_launcher = __esm({
|
|
135
168
|
"src/service/launcher.ts"() {
|
|
@@ -232,7 +265,6 @@ var init_launchd = __esm({
|
|
|
232
265
|
constructor(deps) {
|
|
233
266
|
this.deps = deps;
|
|
234
267
|
}
|
|
235
|
-
deps;
|
|
236
268
|
plistPath(label) {
|
|
237
269
|
return join10(this.deps.home, "Library", "LaunchAgents", `${label}.plist`);
|
|
238
270
|
}
|
|
@@ -317,7 +349,6 @@ var init_systemd = __esm({
|
|
|
317
349
|
constructor(deps) {
|
|
318
350
|
this.deps = deps;
|
|
319
351
|
}
|
|
320
|
-
deps;
|
|
321
352
|
unitPath(label) {
|
|
322
353
|
return join11(this.deps.home, ".config", "systemd", "user", `${label}.service`);
|
|
323
354
|
}
|
|
@@ -353,6 +384,11 @@ var init_systemd = __esm({
|
|
|
353
384
|
});
|
|
354
385
|
|
|
355
386
|
// src/service/backend.ts
|
|
387
|
+
var backend_exports = {};
|
|
388
|
+
__export(backend_exports, {
|
|
389
|
+
defaultDeps: () => defaultDeps,
|
|
390
|
+
selectBackend: () => selectBackend
|
|
391
|
+
});
|
|
356
392
|
import { spawnSync } from "node:child_process";
|
|
357
393
|
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync2, rmSync, existsSync as existsSync3, chmodSync as chmodSync4 } from "node:fs";
|
|
358
394
|
import { userInfo } from "node:os";
|
|
@@ -456,12 +492,105 @@ var init_subcommand = __esm({
|
|
|
456
492
|
}
|
|
457
493
|
});
|
|
458
494
|
|
|
495
|
+
// src/service/install-on-ready.ts
|
|
496
|
+
var install_on_ready_exports = {};
|
|
497
|
+
__export(install_on_ready_exports, {
|
|
498
|
+
installOnReady: () => installOnReady
|
|
499
|
+
});
|
|
500
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
|
|
501
|
+
import { join as join12 } from "node:path";
|
|
502
|
+
function installOnReady(channel, cfg, deps) {
|
|
503
|
+
const hasCreds = deps.hasCreds ?? hasPersistedCreds;
|
|
504
|
+
const readLauncher = deps.readLauncher ?? ((p2) => existsSync4(p2) ? readFileSync3(p2, "utf8") : void 0);
|
|
505
|
+
const fenceAuthExists = deps.fenceAuthExists ?? ((home) => existsSync4(join12(home, ".grok", "auth.json")));
|
|
506
|
+
let fired = false;
|
|
507
|
+
channel.on("ready", () => {
|
|
508
|
+
if (fired) return;
|
|
509
|
+
fired = true;
|
|
510
|
+
void (async () => {
|
|
511
|
+
try {
|
|
512
|
+
if (!hasCreds(cfg.dataDir)) {
|
|
513
|
+
throw new Error(
|
|
514
|
+
`enrolment did not persist credentials to ${cfg.dataDir} \u2014 refusing to install a service that would crash-loop`
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (!fenceAuthExists(cfg.fenceHome)) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
"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."
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
if (deps.waitForIdle) {
|
|
523
|
+
const timeoutMs = deps.idleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
|
|
524
|
+
let timer;
|
|
525
|
+
const timedOut = await Promise.race([
|
|
526
|
+
deps.waitForIdle().then(() => false),
|
|
527
|
+
new Promise((r2) => {
|
|
528
|
+
timer = setTimeout(() => r2(true), timeoutMs);
|
|
529
|
+
})
|
|
530
|
+
]);
|
|
531
|
+
if (timer) clearTimeout(timer);
|
|
532
|
+
if (timedOut) {
|
|
533
|
+
deps.log(
|
|
534
|
+
`agent still busy after ${timeoutMs}ms \u2014 handing off anyway; that reply may be lost`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const reaped = await deps.reap();
|
|
539
|
+
if (!reaped.ok) {
|
|
540
|
+
throw new Error(
|
|
541
|
+
`teardown did not complete (${reaped.failures.join("; ")}) \u2014 refusing to install over a session that may still be open`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
const backend = deps.backend ?? selectBackend(process.platform);
|
|
545
|
+
const spec = buildServiceSpec(cfg, {
|
|
546
|
+
entrypoint: deps.entrypoint,
|
|
547
|
+
home: deps.env.HOME ?? "",
|
|
548
|
+
path: deps.env.PATH ?? ""
|
|
549
|
+
});
|
|
550
|
+
const installedEntrypoint = parseLauncherEntrypoint(readLauncher(spec.launcherPath));
|
|
551
|
+
const entrypointIsCurrent = installedEntrypoint !== void 0 && !isEphemeralEntrypoint(installedEntrypoint) && installedEntrypoint === deps.entrypoint;
|
|
552
|
+
if (backend.status(spec.label).loaded && entrypointIsCurrent) {
|
|
553
|
+
backend.restart(spec.label);
|
|
554
|
+
deps.log(`service ${spec.label} already installed and current \u2014 restarted`);
|
|
555
|
+
} else {
|
|
556
|
+
backend.install(spec);
|
|
557
|
+
deps.log(
|
|
558
|
+
installedEntrypoint && !entrypointIsCurrent ? `service ${spec.label} pointed at a stale entrypoint (${installedEntrypoint}) \u2014 re-installed` : `installed service ${spec.label}`
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
const after = backend.status(spec.label);
|
|
562
|
+
if (!after.loaded) {
|
|
563
|
+
throw new Error(`service did not load: ${after.detail}`);
|
|
564
|
+
}
|
|
565
|
+
deps.log(`it is running now. logs: ${spec.logErr}`);
|
|
566
|
+
deps.log(`check it: agentvault-grok-bridge status`);
|
|
567
|
+
deps.log(`restart it: agentvault-grok-bridge restart (never 'install')`);
|
|
568
|
+
deps.exit(0);
|
|
569
|
+
} catch (e) {
|
|
570
|
+
deps.log(`--install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
571
|
+
deps.exit(1);
|
|
572
|
+
}
|
|
573
|
+
})();
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
var DEFAULT_IDLE_TIMEOUT_MS;
|
|
577
|
+
var init_install_on_ready = __esm({
|
|
578
|
+
"src/service/install-on-ready.ts"() {
|
|
579
|
+
"use strict";
|
|
580
|
+
init_config();
|
|
581
|
+
init_spec();
|
|
582
|
+
init_launcher();
|
|
583
|
+
init_backend();
|
|
584
|
+
DEFAULT_IDLE_TIMEOUT_MS = 3e4;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
459
588
|
// src/index.ts
|
|
460
589
|
import { spawn } from "node:child_process";
|
|
461
590
|
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
462
|
-
import { join as
|
|
591
|
+
import { join as join13 } from "node:path";
|
|
463
592
|
|
|
464
|
-
//
|
|
593
|
+
// ../../../../packages/plugin/dist/index.js
|
|
465
594
|
import * as nc from "node:crypto";
|
|
466
595
|
import { chmod, mkdir, writeFile } from "node:fs/promises";
|
|
467
596
|
import { readdir, readFile, rename, rm } from "node:fs/promises";
|
|
@@ -732,7 +861,7 @@ var init_libsodium_sumo = __esm2({
|
|
|
732
861
|
}, Module: function(binary) {
|
|
733
862
|
}, Instance: function(module2, info) {
|
|
734
863
|
this.exports = // EMSCRIPTEN_START_ASM
|
|
735
|
-
|
|
864
|
+
function instantiate(ea2) {
|
|
736
865
|
var a2;
|
|
737
866
|
var b2 = new Uint8Array(123);
|
|
738
867
|
for (var c2 = 25; c2 >= 0; --c2) {
|
|
@@ -40527,7 +40656,7 @@ var init_libsodium_sumo = __esm2({
|
|
|
40527
40656
|
} } }), f: yi, g: ma2, h: Ba2, i: ma2, j: ka2, k: Le2, l: Je2, m: zg, n: yg, o: xg, p: wg, q: ka2, r: Ba2, s: ka2, t: ka2, u: Le2, v: ra2, w: vg, x: ug, y: tg, z: sg, A: Ba2, B: rg, C: qg, D: pg, E: ng, F: mg, G: lg, H: kg, I: jg, J: ka2, K: Ce2, L: Ba2, M: ma2, N: Ta2, O: ra2, P: ka2, Q: Oa2, R: Ba2, S: ma2, T: Ta2, U: ra2, V: ig, W: hg, X: gg, Y: fg, Z: ka2, _: Ma2, $: Ba2, aa: ma2, ba: Ta2, ca: ra2, da: ka2, ea: ka2, fa: Kg, ga: wd, ha: vd, ia: ra2, ja: ka2, ka: ka2, la: Cb, ma: ra2, na: Xa2, oa: eg, pa: qb, qa: dg, ra: cg, sa: ua2, ta: ka2, ua: Dc, va: ra2, wa: Va2, xa: ud, ya: Ya2, za: bg, Aa: ag, Ba: ka2, Ca: ka2, Da: Dc, Ea: ra2, Fa: ze2, Ga: ud, Ha: Ng, Ia: wd, Ja: vd, Ka: ka2, La: ka2, Ma: ka2, Na: ka2, Oa: Ma2, Pa: ka2, Qa: ma2, Ra: ma2, Sa: Ta2, Ta: dh2, Ua: ch, Va: bh, Wa: ah, Xa: Lb, Ya: Kb, Za: td, _a: sd, $a: rd, ab: $f, bb: _f, cb: Zf, db: qd, eb: Yf, fb: pd, gb: Xf, hb: Wf, ib: Uf, jb: Md, kb: tc, lb: nb, mb, nb: Lb, ob: Kb, pb: td, qb: sd, rb: ka2, sb: ka2, tb: ka2, ub: ka2, vb: Ma2, wb: ka2, xb: ma2, yb: ma2, zb: Ta2, Ab: Aa2, Bb: ka2, Cb: ma2, Db: ka2, Eb: ma2, Fb: Sa2, Gb: ka2, Hb: ma2, Ib: ka2, Jb: ma2, Kb: vb, Lb: ua2, Mb: ma2, Nb: ka2, Ob: ma2, Pb: ub, Qb: ua2, Rb: ma2, Sb: ka2, Tb: ma2, Ub: tb, Vb: ua2, Wb: ma2, Xb: ka2, Yb: ma2, Zb: ma2, _b: ua2, $b: ka2, ac: ma2, bc: ua2, cc: ka2, dc: Nd, ec: pe2, fc: od, gc: fh, hc: nd, ic: eh, jc: ra2, kc: ma2, lc: ua2, mc: ka2, nc: ma2, oc: ua2, pc: ka2, qc: ma2, rc: ma2, sc: pe2, tc: ra2, uc: od, vc: Tf, wc: Ja2, xc: xh, yc: nd, zc: Ia2, Ac: ua2, Bc: md, Cc: Lg, Dc: ka2, Ec: Ag, Fc: Ub, Gc: Sf, Hc: Ua2, Ic: Rf, Jc: ua2, Kc: Cb, Lc: Za2, Mc: ic, Nc: Ca2, Oc: md, Pc: ma2, Qc: ua2, Rc: Oa2, Sc: ka2, Tc: ld, Uc: Nd, Vc: ma2, Wc: ua2, Xc: Oa2, Yc: ka2, Zc: ld, _c: ra2, $c: Gh, ad: Fh, bd: Eh, cd: Dh, dd: ra2, ed: Ch, fd: ka2, gd: Ba2, hd: Bh, id: Cb, jd: ze2, kd: hi, ld: gi, md: fi, nd: ei, od: di, pd: ua2, qd: Ba2, rd: ci, sd: Dc, td: Gg, ud: nb, vd: Fg, wd: Eg, xd: ka2, yd: ka2, zd: ka2, Ad: ka2, Bd: Dg, Cd: Rc, Dd: ma2, Ed: ka2, Fd: kd, Gd: jd, Hd: Sg, Id: id, Jd: Rg, Kd: Qg, Ld: ra2, Md: kd, Nd: jd, Od: Ga2, Pd: id, Qd: Fa2, Rd: ma2, Sd: ka2, Td: Rc, Ud: ra2, Vd: Db, Wd: ma2, Xd: wa2, Yd: Ba2, Zd: wa2, _d: ma2, $d: yc, ae: wh, be: Vb, ce: wa2, de: xc, ee: wc, fe: vc, ge: me2, he: vh, ie: uh, je: Oa2, ke: th, le: Pf, me: Of, ne: Nf, oe: Mf, pe: Lf, qe: fb, re: ma2, se: wa2, te: Ba2, ue: wa2, ve: ma2, we: yc, xe: fe2, ye: Db, ze: wa2, Ae: xc, Be: wc, Ce: fb, De: ee2, Ee: Vb, Fe: de2, Ge: vc, He: uc, Ie: Kf, Je: hd, Ke: Jf, Le: Db, Me: fb, Ne: fb, Oe: ma2, Pe: wa2, Qe: Ba2, Re: wa2, Se: ma2, Te: yc, Ue: fe2, Ve: Db, We: wa2, Xe: xc, Ye: wc, Ze: fb, _e: ee2, $e: Vb, af: de2, bf: vc, cf: uc, df: Hf, ef: hd, ff: Gf, gf: Ff, hf: Ef, jf: sh, kf: Jg, lf: Ig, mf: Hg, nf: ka2, of: ka2, pf: Ea2, qf: wb, rf: ka2, sf: ka2, tf: ka2, uf: Ma2, vf: ka2, wf: ma2, xf: ma2, yf: Ta2, zf: Mg, Af: Lb, Bf: Kb, Cf: ra2, Df: rd, Ef: Df, Ff: qd, Gf: pd, Hf: Lb, If: Kb, Jf: ka2, Kf: Ma2, Lf: ka2, Mf: ma2, Nf: ma2, Of: Ta2, Pf: ra2, Qf: ra2, Rf: Mh, Sf: Lh, Tf: Kh, Uf: Cf, Vf: Bf, Wf: Jh, Xf: Ih, Yf: Ma2, Zf: ka2, _f: Hh, $f: Ba2, ag: Db, bg: fb, cg: Vb, dg: Oa2, eg: ma2, fg: Tg, gg: gd, hg: Je2, ig: Oa2, jg: ma2, kg: gd, lg: Cb, mg: ua2, ng: ka2, og: ka2, pg: ua2, qg: Wd, rg: lh, sg: kh, tg: jh, ug: fd, vg: ed, wg: dd, xg: cd, yg: ih, zg: ic, Ag: hh, Bg: gh, Cg: Cb, Dg: ua2, Eg: ka2, Fg: ka2, Gg: ua2, Hg: Wd, Ig: nh, Jg: mh, Kg: Vd, Lg: ic, Mg: Ud, Ng: Td, Og: Yd, Pg: Xd, Qg: ph, Rg: oh, Sg: cd, Tg: ed, Ug: dd, Vg: fd, Wg: ka2, Xg: Oa2, Yg: wa2, Zg: ka2, _g: Ce2, $g: wa2, ah: zf, bh: yf, ch: xf, dh: wf, eh: vf, fh: uf, gh: ra2, hh: ra2, ih: ka2, jh: Ma2, kh: wa2, lh: qh, mh: bd, nh: ad, oh: ra2, ph: ka2, qh: Oa2, rh: wa2, sh: tf, th: sf, uh: rf, vh: ra2, wh: bd, xh: qf, yh: ad, zh: ka2, Ah: Ma2, Bh: wa2, Ch: ra2, Dh: ma2, Eh: ka2, Fh: ua2, Gh: db, Hh: kb, Ih: xd, Jh: oi, Kh: ni, Lh: Ae2, Mh: mi, Nh: za2, Oh: li, Ph: ka2, Qh: ki, Rh: pf, Sh: Ah, Th: zh, Uh: yh, Vh: Bc, Wh: Ac, Xh: ti, Yh: ji, Zh: ii, _h: Cg, $h: Bg, ai: fb, bi: Ba2, ci: tc, di: nb, ei: rh, fi: $c, gi: of, hi: nf, ii: mf, ji: _c, ki: lf, li: Yc, mi: kf, ni: ka2, oi: ka2, pi: ka2, qi: ka2, ri: Ma2, si: ma2, ti: Ta2, ui: jf, vi: hf, wi: Md, xi: bi, yi: ai, zi: $h, Ai: _h, Bi: Zh, Ci: ye2, Di: xe2, Ei: we2, Fi: ve2, Gi: ue2, Hi: te2, Ii: se2, Ji: re2, Ki: ka2, Li: ua2, Mi: ka2, Ni: ua2, Oi: ka2, Pi: Yh, Qi: Xh, Ri: Wh, Si: Vh, Ti: Uh, Ui: Th, Vi: Sh, Wi: Rh, Xi: Qh, Yi: Ph, Zi: Oh, _i: re2, $i: Nh, aj: ka2, bj: ua2, cj: ua2, dj: ka2, ej: gf, fj: ma2, gj: wa2, hj: Ba2, ij: wa2, jj: ka2, kj: $g, lj: _g, mj: Zg, nj: wa2, oj: Ed, pj: wa2, qj: Yg, rj: Ed, sj: me2, tj: uc, uj: ff, vj: ef, wj: df, xj: cf, yj: Xg, zj: Wg, Aj: Vg, Bj: Ug, Cj: ka2, Dj: ka2, Ej: Pg, Fj: Og, Gj: ka2, Hj: ka2, Ij: $c, Jj: bf, Kj: _c, Lj: Yc, Mj: ka2, Nj: Ma2, Oj: ma2, Pj: Ta2, Qj: ma2, Rj: ma2, Sj: af, Tj: $e2, Uj: _e2, Vj: ka2, Wj: Oa2, Xj: wa2, Yj: ra2, Zj: Ze2, _j: Ye2, $j: ka2, ak: Oa2, bk: wa2, ck: ra2, dk: ka2, ek: Ma2, fk: wa2, gk: Xe2, hk: We2, ik: Ve2, jk: ra2, kk: Da2, lk: ta2 };
|
|
40528
40657
|
}
|
|
40529
40658
|
return da2(ea2);
|
|
40530
|
-
}
|
|
40659
|
+
}(info);
|
|
40531
40660
|
}, instantiate: function(binary, info) {
|
|
40532
40661
|
return { then: function(ok) {
|
|
40533
40662
|
var module2 = new WebAssembly2.Module(binary);
|
|
@@ -81102,7 +81231,7 @@ function _stringbool(Classes, _params) {
|
|
|
81102
81231
|
type: "pipe",
|
|
81103
81232
|
in: stringSchema,
|
|
81104
81233
|
out: booleanSchema,
|
|
81105
|
-
transform: (
|
|
81234
|
+
transform: (input, payload) => {
|
|
81106
81235
|
let data = input;
|
|
81107
81236
|
if (params.case !== "sensitive")
|
|
81108
81237
|
data = data.toLowerCase();
|
|
@@ -81121,14 +81250,14 @@ function _stringbool(Classes, _params) {
|
|
|
81121
81250
|
});
|
|
81122
81251
|
return {};
|
|
81123
81252
|
}
|
|
81124
|
-
}
|
|
81125
|
-
reverseTransform: (
|
|
81253
|
+
},
|
|
81254
|
+
reverseTransform: (input, _payload) => {
|
|
81126
81255
|
if (input === true) {
|
|
81127
81256
|
return truthyArray[0] || "true";
|
|
81128
81257
|
} else {
|
|
81129
81258
|
return falsyArray[0] || "false";
|
|
81130
81259
|
}
|
|
81131
|
-
}
|
|
81260
|
+
},
|
|
81132
81261
|
error: params.error
|
|
81133
81262
|
});
|
|
81134
81263
|
return codec2;
|
|
@@ -82184,10 +82313,10 @@ var init_schemas2 = __esm2({
|
|
|
82184
82313
|
inst.with = inst.check;
|
|
82185
82314
|
inst.clone = (_def, params) => clone(inst, _def, params);
|
|
82186
82315
|
inst.brand = () => inst;
|
|
82187
|
-
inst.register = (
|
|
82316
|
+
inst.register = (reg, meta3) => {
|
|
82188
82317
|
reg.add(inst, meta3);
|
|
82189
82318
|
return inst;
|
|
82190
|
-
}
|
|
82319
|
+
};
|
|
82191
82320
|
inst.apply = (fn) => fn(inst);
|
|
82192
82321
|
});
|
|
82193
82322
|
ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
|
|
@@ -83273,10 +83402,10 @@ var init_schemas3 = __esm2({
|
|
|
83273
83402
|
inst.with = inst.check;
|
|
83274
83403
|
inst.clone = (def2, params) => clone(inst, def2, params);
|
|
83275
83404
|
inst.brand = () => inst;
|
|
83276
|
-
inst.register = (
|
|
83405
|
+
inst.register = (reg, meta3) => {
|
|
83277
83406
|
reg.add(inst, meta3);
|
|
83278
83407
|
return inst;
|
|
83279
|
-
}
|
|
83408
|
+
};
|
|
83280
83409
|
inst.parse = (data, params) => parse2(inst, data, params, { callee: inst.parse });
|
|
83281
83410
|
inst.safeParse = (data, params) => safeParse3(inst, data, params);
|
|
83282
83411
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
|
@@ -83938,13 +84067,11 @@ function assertCompleteRequestPrompt(request) {
|
|
|
83938
84067
|
if (request.params.ref.type !== "ref/prompt") {
|
|
83939
84068
|
throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
|
|
83940
84069
|
}
|
|
83941
|
-
void request;
|
|
83942
84070
|
}
|
|
83943
84071
|
function assertCompleteRequestResourceTemplate(request) {
|
|
83944
84072
|
if (request.params.ref.type !== "ref/resource") {
|
|
83945
84073
|
throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
|
83946
84074
|
}
|
|
83947
|
-
void request;
|
|
83948
84075
|
}
|
|
83949
84076
|
var LATEST_PROTOCOL_VERSION;
|
|
83950
84077
|
var DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
@@ -98107,8 +98234,6 @@ var GrokSession = class {
|
|
|
98107
98234
|
this.driver = driver2;
|
|
98108
98235
|
this.opts = opts;
|
|
98109
98236
|
}
|
|
98110
|
-
driver;
|
|
98111
|
-
opts;
|
|
98112
98237
|
queue = [];
|
|
98113
98238
|
running = false;
|
|
98114
98239
|
/** The message whose turn is in flight, or null between turns. Null is a
|
|
@@ -98221,9 +98346,6 @@ var AcpChildDied = class extends Error {
|
|
|
98221
98346
|
this.signal = signal;
|
|
98222
98347
|
this.name = "AcpChildDied";
|
|
98223
98348
|
}
|
|
98224
|
-
method;
|
|
98225
|
-
code;
|
|
98226
|
-
signal;
|
|
98227
98349
|
};
|
|
98228
98350
|
var AcpTimeout = class extends Error {
|
|
98229
98351
|
constructor(method, ms) {
|
|
@@ -98232,8 +98354,6 @@ var AcpTimeout = class extends Error {
|
|
|
98232
98354
|
this.ms = ms;
|
|
98233
98355
|
this.name = "AcpTimeout";
|
|
98234
98356
|
}
|
|
98235
|
-
method;
|
|
98236
|
-
ms;
|
|
98237
98357
|
};
|
|
98238
98358
|
var AcpConnection = class {
|
|
98239
98359
|
constructor(child, opts) {
|
|
@@ -98251,7 +98371,6 @@ var AcpConnection = class {
|
|
|
98251
98371
|
this.stdinErrors++;
|
|
98252
98372
|
});
|
|
98253
98373
|
}
|
|
98254
|
-
child;
|
|
98255
98374
|
nextId = 1;
|
|
98256
98375
|
pending = /* @__PURE__ */ new Map();
|
|
98257
98376
|
/** Undecoded leftover. Decode UTF-8 only on complete lines so a 4-byte
|
|
@@ -98434,8 +98553,6 @@ var GrokDriver = class {
|
|
|
98434
98553
|
this.acp.onNotification((method, params) => this.onNotification(method, params));
|
|
98435
98554
|
this.acp.onRequest((method, params) => this.onAgentRequest(method, params));
|
|
98436
98555
|
}
|
|
98437
|
-
child;
|
|
98438
|
-
opts;
|
|
98439
98556
|
acp;
|
|
98440
98557
|
session = null;
|
|
98441
98558
|
/** State for the turn currently in flight. Null between turns, so a late
|
|
@@ -98846,16 +98963,76 @@ function buildClientVersion2(version2) {
|
|
|
98846
98963
|
return `@agentvault/grok-bridge@${version2 ?? "dev"}`;
|
|
98847
98964
|
}
|
|
98848
98965
|
|
|
98966
|
+
// src/reap.ts
|
|
98967
|
+
var DEFAULT_KILL_TIMEOUT_MS = 5e3;
|
|
98968
|
+
var SIGKILL_GRACE_MS = 250;
|
|
98969
|
+
function childHasExited(child) {
|
|
98970
|
+
return child.exitCode !== null && child.exitCode !== void 0 || child.signalCode !== null && child.signalCode !== void 0;
|
|
98971
|
+
}
|
|
98972
|
+
function waitForChildExit(child, killTimeoutMs, log) {
|
|
98973
|
+
if (childHasExited(child)) return Promise.resolve();
|
|
98974
|
+
return new Promise((resolve3) => {
|
|
98975
|
+
let settled = false;
|
|
98976
|
+
const timers = [];
|
|
98977
|
+
const done = () => {
|
|
98978
|
+
if (settled) return;
|
|
98979
|
+
settled = true;
|
|
98980
|
+
for (const t2 of timers) clearTimeout(t2);
|
|
98981
|
+
resolve3();
|
|
98982
|
+
};
|
|
98983
|
+
child.on("exit", done);
|
|
98984
|
+
timers.push(
|
|
98985
|
+
setTimeout(() => {
|
|
98986
|
+
log(`grok child ignored SIGTERM after ${killTimeoutMs}ms \u2014 escalating to SIGKILL`);
|
|
98987
|
+
try {
|
|
98988
|
+
child.kill?.("SIGKILL");
|
|
98989
|
+
} catch {
|
|
98990
|
+
}
|
|
98991
|
+
timers.push(setTimeout(done, SIGKILL_GRACE_MS));
|
|
98992
|
+
}, killTimeoutMs)
|
|
98993
|
+
);
|
|
98994
|
+
});
|
|
98995
|
+
}
|
|
98996
|
+
async function reap(r2, opts = {}) {
|
|
98997
|
+
const log = opts.log ?? (() => {
|
|
98998
|
+
});
|
|
98999
|
+
const killTimeoutMs = opts.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS;
|
|
99000
|
+
const failures = [];
|
|
99001
|
+
const attempt = async (label, fn) => {
|
|
99002
|
+
try {
|
|
99003
|
+
await fn();
|
|
99004
|
+
} catch (e) {
|
|
99005
|
+
const msg = `${label}: ${e instanceof Error ? e.message : String(e)}`;
|
|
99006
|
+
failures.push(msg);
|
|
99007
|
+
log(`reap: ${msg}`);
|
|
99008
|
+
}
|
|
99009
|
+
};
|
|
99010
|
+
if (r2.channel) await attempt("channel.stop", () => r2.channel.stop());
|
|
99011
|
+
if (r2.driver) await attempt("driver.stop", () => r2.driver.stop());
|
|
99012
|
+
if (r2.child) await waitForChildExit(r2.child, killTimeoutMs, log);
|
|
99013
|
+
if (r2.mcp) await attempt("mcp.close", () => r2.mcp.close());
|
|
99014
|
+
return { ok: failures.length === 0, failures };
|
|
99015
|
+
}
|
|
99016
|
+
|
|
98849
99017
|
// src/index.ts
|
|
98850
99018
|
var shuttingDown = false;
|
|
99019
|
+
var exiting = false;
|
|
98851
99020
|
var driver;
|
|
98852
99021
|
var mcp;
|
|
98853
|
-
var
|
|
98854
|
-
|
|
99022
|
+
var channelRef;
|
|
99023
|
+
var childRef;
|
|
99024
|
+
var reapAll = async () => {
|
|
98855
99025
|
shuttingDown = true;
|
|
99026
|
+
return reap(
|
|
99027
|
+
{ channel: channelRef, driver, mcp, child: childRef },
|
|
99028
|
+
{ log: (m2) => logLine("[grok-bridge] " + m2) }
|
|
99029
|
+
);
|
|
99030
|
+
};
|
|
99031
|
+
var shutdown = async (code = 0) => {
|
|
99032
|
+
if (exiting) return;
|
|
99033
|
+
exiting = true;
|
|
98856
99034
|
try {
|
|
98857
|
-
await
|
|
98858
|
-
await mcp?.close();
|
|
99035
|
+
await reapAll();
|
|
98859
99036
|
} finally {
|
|
98860
99037
|
process.exit(code);
|
|
98861
99038
|
}
|
|
@@ -98868,6 +99045,11 @@ async function main() {
|
|
|
98868
99045
|
process.exit(process.exitCode ?? 0);
|
|
98869
99046
|
}
|
|
98870
99047
|
const cfg = loadConfig(process.env, process.argv.slice(2));
|
|
99048
|
+
if (cfg.installAfterEnroll) {
|
|
99049
|
+
assertInstallableEntrypoint(process.argv[1]);
|
|
99050
|
+
const { selectBackend: selectBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
|
99051
|
+
selectBackend2(process.platform);
|
|
99052
|
+
}
|
|
98871
99053
|
mkdirSync5(cfg.workspaceDir, { recursive: true });
|
|
98872
99054
|
mkdirSync5(cfg.fenceHome, { recursive: true });
|
|
98873
99055
|
seedFenceCredential({
|
|
@@ -98882,9 +99064,10 @@ async function main() {
|
|
|
98882
99064
|
agentName: cfg.agentName,
|
|
98883
99065
|
platform: "node",
|
|
98884
99066
|
clientVersion: buildClientVersion2(
|
|
98885
|
-
true ? "0.
|
|
99067
|
+
true ? "0.2.0" : void 0
|
|
98886
99068
|
)
|
|
98887
99069
|
});
|
|
99070
|
+
channelRef = channel;
|
|
98888
99071
|
let session;
|
|
98889
99072
|
mcp = await startRoomMcpServer({
|
|
98890
99073
|
// `session` is assigned further down. The current ordering is safe — grok
|
|
@@ -98902,7 +99085,7 @@ async function main() {
|
|
|
98902
99085
|
},
|
|
98903
99086
|
log: (m2) => logLine(m2)
|
|
98904
99087
|
});
|
|
98905
|
-
const pluginDir =
|
|
99088
|
+
const pluginDir = join13(cfg.fenceHome, "av-room-plugin");
|
|
98906
99089
|
writeRoomPluginHttp(pluginDir, mcp.url);
|
|
98907
99090
|
const spawnSpec = grokSpawnArgs({
|
|
98908
99091
|
grokPath: cfg.grokPath,
|
|
@@ -98917,6 +99100,7 @@ async function main() {
|
|
|
98917
99100
|
env: spawnSpec.env,
|
|
98918
99101
|
stdio: ["pipe", "pipe", "pipe"]
|
|
98919
99102
|
});
|
|
99103
|
+
childRef = child;
|
|
98920
99104
|
child.on("error", (err) => {
|
|
98921
99105
|
logLine(`[grok-bridge] fatal: ${describeSpawnFailure(err, spawnSpec)}`);
|
|
98922
99106
|
process.exit(1);
|
|
@@ -98966,6 +99150,23 @@ async function main() {
|
|
|
98966
99150
|
log: (m2) => logLine("[grok-bridge] " + m2),
|
|
98967
99151
|
onTerminal: (_reason, o2) => void shutdown(o2.restart ? 1 : 0)
|
|
98968
99152
|
});
|
|
99153
|
+
if (cfg.installAfterEnroll) {
|
|
99154
|
+
const { installOnReady: installOnReady2 } = await Promise.resolve().then(() => (init_install_on_ready(), install_on_ready_exports));
|
|
99155
|
+
installOnReady2(channel, cfg, {
|
|
99156
|
+
entrypoint: process.argv[1],
|
|
99157
|
+
env: process.env,
|
|
99158
|
+
// FOUND LIVE 2026-08-16: the owner's activation greeting is usually already
|
|
99159
|
+
// being answered when `ready` fires, and reaping mid-turn killed the reply
|
|
99160
|
+
// for good — the message was acked, so the service never saw it again.
|
|
99161
|
+
// GrokSession.idle() resolves when the queue drains.
|
|
99162
|
+
waitForIdle: () => session.idle(),
|
|
99163
|
+
// The one shared teardown. `exiting` is deliberately NOT set here: Ctrl-C
|
|
99164
|
+
// must keep working for the whole multi-second launchctl window.
|
|
99165
|
+
reap: reapAll,
|
|
99166
|
+
exit: (code) => process.exit(code),
|
|
99167
|
+
log: (m2) => logLine("[grok-bridge] " + m2)
|
|
99168
|
+
});
|
|
99169
|
+
}
|
|
98969
99170
|
await channel.start();
|
|
98970
99171
|
}
|
|
98971
99172
|
main().catch(async (err) => {
|
package/dist/reap.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ONE teardown path. Both `shutdown()` and the `--install` hand-off call this;
|
|
3
|
+
* nothing copies its body.
|
|
4
|
+
*
|
|
5
|
+
* WHY IT IS SHARED. An earlier design inlined a second copy inside the install
|
|
6
|
+
* flow. That copy stopped only the driver and the MCP listener — it never closed
|
|
7
|
+
* the AgentVault WebSocket, and it did not wait for the grok child to die. Both
|
|
8
|
+
* omissions are invisible in a unit test that substitutes the teardown closure,
|
|
9
|
+
* and both matter: `LaunchdBackend.install` runs bootout -> poll-until-gone ->
|
|
10
|
+
* bootstrap -> kickstart synchronously, so the supervised service is alive the
|
|
11
|
+
* instant install returns. A half-reaped foreground process means two ACP sessions
|
|
12
|
+
* and two WebSockets for one device.
|
|
13
|
+
*/
|
|
14
|
+
export interface Reapable {
|
|
15
|
+
/** SecureChannel. `stop()` closes the WS, flushes pending ACKs, and sets the
|
|
16
|
+
* stopped flag so it will not reconnect. */
|
|
17
|
+
channel?: {
|
|
18
|
+
stop(): Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
driver?: {
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
mcp?: {
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
/** The grok child. `exitCode`/`signalCode` are read to detect an ALREADY-dead
|
|
27
|
+
* child; both are non-null once it has exited.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ The signal parameter is `NodeJS.Signals`, NOT `string`. A real
|
|
30
|
+
* `ChildProcess.kill` is `(signal?: NodeJS.Signals | number) => boolean`, and
|
|
31
|
+
* parameter types are contravariant — declaring `string` here makes a real
|
|
32
|
+
* ChildProcess UNASSIGNABLE to this shape and fails the strict build at the
|
|
33
|
+
* index.ts call site. `signalCode` is `NodeJS.Signals | null` for the same
|
|
34
|
+
* reason. Verified: widening it to `string` reintroduces TS2322. */
|
|
35
|
+
child?: {
|
|
36
|
+
kill?: (signal: NodeJS.Signals) => unknown;
|
|
37
|
+
on(event: "exit", listener: () => void): unknown;
|
|
38
|
+
exitCode?: number | null;
|
|
39
|
+
signalCode?: NodeJS.Signals | null;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export interface ReapResult {
|
|
43
|
+
/** False if any stage failed. Callers that treat a complete teardown as a
|
|
44
|
+
* PRECONDITION (the --install hand-off does) must refuse to continue on false
|
|
45
|
+
* rather than install over a socket that may still be open. */
|
|
46
|
+
ok: boolean;
|
|
47
|
+
failures: string[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Tear everything down, in the one order that is safe.
|
|
51
|
+
*
|
|
52
|
+
* CHANNEL FIRST. Closing it stops new inbound before anything else is dismantled.
|
|
53
|
+
* With the channel last, the WebSocket keeps delivering room and DM traffic into a
|
|
54
|
+
* session whose driver is already dying, for as long as the child takes to exit —
|
|
55
|
+
* turn failures and dropped messages inside the very window this function exists
|
|
56
|
+
* to make empty. `stop()` also flushes pending ACKs, which it can only do over a
|
|
57
|
+
* socket that is still up.
|
|
58
|
+
*
|
|
59
|
+
* Every stage is guarded separately: one failure must not strand the others. A
|
|
60
|
+
* driver that throws must never leave the WebSocket open.
|
|
61
|
+
*/
|
|
62
|
+
export declare function reap(r: Reapable, opts?: {
|
|
63
|
+
killTimeoutMs?: number;
|
|
64
|
+
log?: (m: string) => void;
|
|
65
|
+
}): Promise<ReapResult>;
|
|
66
|
+
//# sourceMappingURL=reap.d.ts.map
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type BridgeConfig } from "../config.js";
|
|
2
|
+
import type { ReapResult } from "../reap.js";
|
|
3
|
+
import { type ServiceBackend } from "./backend.js";
|
|
4
|
+
export interface InstallOnReadyDeps {
|
|
5
|
+
entrypoint: string;
|
|
6
|
+
env: NodeJS.ProcessEnv;
|
|
7
|
+
backend?: ServiceBackend;
|
|
8
|
+
hasCreds?: (dataDir: string) => boolean;
|
|
9
|
+
/** Contents of the installed launcher, or undefined when none is installed. */
|
|
10
|
+
readLauncher?: (path: string) => string | undefined;
|
|
11
|
+
fenceAuthExists?: (fenceHome: string) => boolean;
|
|
12
|
+
/** Resolves when no turn is in flight (GrokSession.idle). See rule 2. */
|
|
13
|
+
waitForIdle?: () => Promise<void>;
|
|
14
|
+
/** Bound on that wait. The terminal coming back is the product promise, so a
|
|
15
|
+
* wedged turn must never hold the customer's shell open. */
|
|
16
|
+
idleTimeoutMs?: number;
|
|
17
|
+
/** The ONE teardown (src/reap.ts). Must complete before launchctl runs. */
|
|
18
|
+
reap: () => Promise<ReapResult>;
|
|
19
|
+
exit: (code: number) => void;
|
|
20
|
+
log: (msg: string) => void;
|
|
21
|
+
}
|
|
22
|
+
interface ReadyChannel {
|
|
23
|
+
on(ev: "ready", cb: () => void): unknown;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One-paste install: enrol in the foreground, hand off to the supervisor, exit —
|
|
27
|
+
* so the customer's terminal comes back and the agent survives a reboot.
|
|
28
|
+
*
|
|
29
|
+
* Four rules this module exists to enforce.
|
|
30
|
+
*
|
|
31
|
+
* 1. REFUSE BEFORE REAPING. Everything that can say no does so before anything is
|
|
32
|
+
* torn down. Refusing afterwards leaves the customer with an approved device,
|
|
33
|
+
* a spent invite token, and no running process — strictly worse than never
|
|
34
|
+
* having guarded at all.
|
|
35
|
+
*
|
|
36
|
+
* 2. REAP COMPLETELY, THEN BOOTSTRAP. `LaunchdBackend.install` runs bootout →
|
|
37
|
+
* poll-until-gone → bootstrap → kickstart synchronously, so the service is
|
|
38
|
+
* live the instant install returns. A partial teardown means two ACP sessions
|
|
39
|
+
* and two WebSockets on one device. `reap()` reports failure rather than
|
|
40
|
+
* swallowing it, and a failed reap is a refusal.
|
|
41
|
+
*
|
|
42
|
+
* 3. COMPARE, DON'T DETECT. `status().loaded` means only that a plist with this
|
|
43
|
+
* label exists. The old `install` subcommand never guarded its entrypoint
|
|
44
|
+
* (subcommand.ts), so a customer can hold a LOADED plist baked against a
|
|
45
|
+
* pruned npx cache; restarting that repairs nothing and reports success.
|
|
46
|
+
* Restart only when a service is loaded AND its baked entrypoint is the one we
|
|
47
|
+
* would install. Compare the ENTRYPOINT, never the whole launcher script —
|
|
48
|
+
* that embeds the install-time PATH and is volatile across shells.
|
|
49
|
+
*
|
|
50
|
+
* 4. VERIFY BEFORE CLAIMING. `backend.install` returns void, and launchd's
|
|
51
|
+
* implementation runs `kickstart` regardless of every `bootstrap` exit code.
|
|
52
|
+
* This log line is the customer's only feedback, so it comes after a
|
|
53
|
+
* `status()` that returned loaded:true.
|
|
54
|
+
*/
|
|
55
|
+
export declare function installOnReady(channel: ReadyChannel, cfg: BridgeConfig, deps: InstallOnReadyDeps): void;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=install-on-ready.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install-on-ready.d.ts","sourceRoot":"","sources":["../../src/service/install-on-ready.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4C,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAElE,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC;IACxC,+EAA+E;IAC/E,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACpD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC;IACjD,yEAAyE;IACzE,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC;iEAC6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,2EAA2E;IAC3E,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAChC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAMD,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,kBAAkB,GACvB,IAAI,CAgHN"}
|
|
@@ -13,4 +13,23 @@ export declare function renderLauncher(opts: {
|
|
|
13
13
|
/** Write the launcher executable (0755) into the data-dir before the backend
|
|
14
14
|
* points the service at it. Shared by both platform backends. */
|
|
15
15
|
export declare function writeLauncher(deps: BackendDeps, spec: ServiceSpec): void;
|
|
16
|
+
/**
|
|
17
|
+
* Read back the entrypoint `renderLauncher` baked into an installed launcher.
|
|
18
|
+
*
|
|
19
|
+
* THE READER LIVES BESIDE THE WRITER ON PURPOSE. This pair decides
|
|
20
|
+
* restart-versus-reinstall, and a parser that drifts from the renderer fails
|
|
21
|
+
* SILENTLY — it just answers `undefined` forever, which the caller reads as
|
|
22
|
+
* "stale", which means every run re-installs and silently re-bakes the plist.
|
|
23
|
+
*
|
|
24
|
+
* WHY PARSE RATHER THAN BYTE-COMPARE THE SCRIPT. `renderLauncher` embeds the
|
|
25
|
+
* INSTALL-TIME PATH (`export PATH='<installPath>':"$PATH"`), taken from whichever
|
|
26
|
+
* shell ran the install. A whole-file comparison therefore reports "different"
|
|
27
|
+
* whenever the customer opens a new terminal, installs a brew formula, or
|
|
28
|
+
* switches node version manager — making the config-losing re-install the DEFAULT
|
|
29
|
+
* path. The entrypoint is the durable half, and the only half that can go stale
|
|
30
|
+
* (an npm cache gets pruned; the launcher keeps pointing at it).
|
|
31
|
+
*
|
|
32
|
+
* Returns undefined when there is no file, or when it is not one of ours.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseLauncherEntrypoint(script: string | undefined): string | undefined;
|
|
16
35
|
//# sourceMappingURL=launcher.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/service/launcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD,eAAO,MAAM,cAAc,UAO1B,CAAC;AAcF;;;wEAGwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,MAAM,CAoBT;AAED;kEACkE;AAClE,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAIxE"}
|
|
1
|
+
{"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/service/launcher.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAKhD,eAAO,MAAM,cAAc,UAO1B,CAAC;AAcF;;;wEAGwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,EAAE;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,MAAM,CAoBT;AAED;kEACkE;AAClE,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CAIxE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOtF"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentvault/grok-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgentVault Grok Bridge — daemon bridging a Grok agent into E2E-encrypted AgentVault direct messages and rooms.",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
-
"bin": {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
"bin": {
|
|
9
|
+
"agentvault-grok-bridge": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"!dist/*.map"
|
|
18
|
+
],
|
|
11
19
|
"scripts": {
|
|
12
20
|
"build": "npm --prefix ../plugin run build && tsc --emitDeclarationOnly && node build.mjs",
|
|
13
21
|
"start": "node dist/index.js",
|
|
@@ -16,7 +24,10 @@
|
|
|
16
24
|
"typecheck": "tsc -p tsconfig.test.json",
|
|
17
25
|
"prepublishOnly": "npm run build"
|
|
18
26
|
},
|
|
19
|
-
"dependencies": {
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"ws": "^8.16.0",
|
|
29
|
+
"zod": "^4.3.6"
|
|
30
|
+
},
|
|
20
31
|
"devDependencies": {
|
|
21
32
|
"@agentvault/agentvault": "file:../plugin",
|
|
22
33
|
"esbuild": "^0.27.3",
|
|
@@ -24,6 +35,8 @@
|
|
|
24
35
|
"vitest": "^2.0.0",
|
|
25
36
|
"@types/node": "^20.0.0"
|
|
26
37
|
},
|
|
27
|
-
"publishConfig": {
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
28
41
|
"license": "MIT"
|
|
29
42
|
}
|