github-router 0.3.276 → 0.3.285
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/{attribution-settings-VOPvSn6v.js → attribution-settings-B8M2fvhz.js} +44 -45
- package/dist/attribution-settings-B8M2fvhz.js.map +1 -0
- package/dist/{auth-CYoRwhC9.js → auth-VUL2Zxvw.js} +3 -3
- package/dist/{auth-CYoRwhC9.js.map → auth-VUL2Zxvw.js.map} +1 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/{check-usage-DDUyg8ZB.js → check-usage-CcLdFPGr.js} +4 -4
- package/dist/{check-usage-DDUyg8ZB.js.map → check-usage-CcLdFPGr.js.map} +1 -1
- package/dist/{claude-OTHh7iKz.js → claude-e-JIQGhR.js} +55 -38
- package/dist/claude-e-JIQGhR.js.map +1 -0
- package/dist/{codex-tCxK30Su.js → codex-DSnVZ9oK.js} +5 -5
- package/dist/{codex-tCxK30Su.js.map → codex-DSnVZ9oK.js.map} +1 -1
- package/dist/{debug-CJgsxoVw.js → debug-CtzYWxpJ.js} +2 -2
- package/dist/{debug-CJgsxoVw.js.map → debug-CtzYWxpJ.js.map} +1 -1
- package/dist/engine-DMveEa9x.js +2 -0
- package/dist/{gate-discovery-nf7PGgnS.js → gate-discovery-BCFwLm0q.js} +5 -5
- package/dist/{gate-discovery-nf7PGgnS.js.map → gate-discovery-BCFwLm0q.js.map} +1 -1
- package/dist/{get-copilot-usage-D1OAH0EU.js → get-copilot-usage-B-EDAqQb.js} +2 -2
- package/dist/{get-copilot-usage-D1OAH0EU.js.map → get-copilot-usage-B-EDAqQb.js.map} +1 -1
- package/dist/hooks.mjs +37870 -0
- package/dist/hooks.sha256 +1 -0
- package/dist/{internal-artifact-open-BCz9lnq0.js → internal-artifact-open-Dj5Nlq0L.js} +2 -2
- package/dist/{internal-artifact-open-BCz9lnq0.js.map → internal-artifact-open-Dj5Nlq0L.js.map} +1 -1
- package/dist/{internal-first-mate-guard-7v2CtMA6.js → internal-first-mate-guard-B7p4NttK.js} +1 -1
- package/dist/{internal-first-mate-guard-La0tIjTU.js → internal-first-mate-guard-DOgFVki5.js} +5 -4
- package/dist/{internal-first-mate-guard-La0tIjTU.js.map → internal-first-mate-guard-DOgFVki5.js.map} +1 -1
- package/dist/{internal-plan-review-jRMpHu68.js → internal-plan-review-DRFHET_B.js} +11 -4
- package/dist/internal-plan-review-DRFHET_B.js.map +1 -0
- package/dist/{internal-prompt-submit-CqWRFCuv.js → internal-prompt-submit-f1LR2P2G.js} +4 -4
- package/dist/{internal-prompt-submit-CqWRFCuv.js.map → internal-prompt-submit-f1LR2P2G.js.map} +1 -1
- package/dist/{internal-session-bind-BiHWDxuC.js → internal-session-bind-DhZhxJ3T.js} +2 -2
- package/dist/{internal-session-bind-BiHWDxuC.js.map → internal-session-bind-DhZhxJ3T.js.map} +1 -1
- package/dist/{internal-stop-hook-Ds9LwmZA.js → internal-stop-hook-Cf-7w3RH.js} +68 -20
- package/dist/internal-stop-hook-Cf-7w3RH.js.map +1 -0
- package/dist/{internal-stop-review-BOFj7R3q.js → internal-stop-review-BBsLcbPG.js} +2 -2
- package/dist/{internal-stop-review-BOFj7R3q.js.map → internal-stop-review-BBsLcbPG.js.map} +1 -1
- package/dist/{internal-worker-guard-Lu8VHj5k.js → internal-worker-guard-CKgYFiFO.js} +2 -2
- package/dist/{internal-worker-guard-Lu8VHj5k.js.map → internal-worker-guard-CKgYFiFO.js.map} +1 -1
- package/dist/{internal-workspace-header-BRMz0Yql.js → internal-workspace-header-OYgHEnFt.js} +2 -2
- package/dist/{internal-workspace-header-BRMz0Yql.js.map → internal-workspace-header-OYgHEnFt.js.map} +1 -1
- package/dist/lib/tree-sitter-pool/lifecycle-C7W8D2lx.js +165 -0
- package/dist/lib/tree-sitter-pool/lifecycle-Dx0SnP7Z.js +157 -0
- package/dist/lib/tree-sitter-pool/worker.js +287 -14
- package/dist/{lifecycle-LA4rAuAL.js → lifecycle-B7CHqKlF.js} +2 -2
- package/dist/{lifecycle-LA4rAuAL.js.map → lifecycle-B7CHqKlF.js.map} +1 -1
- package/dist/lifecycle-CbmHMMSD.js +2 -0
- package/dist/{lifecycle-AGXnd-ZY.js → lifecycle-D-rL81tT.js} +2 -2
- package/dist/{lifecycle-AGXnd-ZY.js.map → lifecycle-D-rL81tT.js.map} +1 -1
- package/dist/lifecycle-DTcZwa7U.js +2 -0
- package/dist/lifecycle-K9oVtGde.mjs +16 -0
- package/dist/main.js +18 -18
- package/dist/{mcp-workspace-header-CGJbNeHb.js → mcp-workspace-header-ucs2SDST.js} +5 -6
- package/dist/mcp-workspace-header-ucs2SDST.js.map +1 -0
- package/dist/{models-4Q45Q2Dw.js → models-C16mBK2M.js} +3 -3
- package/dist/{models-4Q45Q2Dw.js.map → models-C16mBK2M.js.map} +1 -1
- package/dist/{orchestration-CpkG8ScN.js → orchestration-CtM6FYNx.js} +2 -2
- package/dist/{orchestration-CpkG8ScN.js.map → orchestration-CtM6FYNx.js.map} +1 -1
- package/dist/package-root-B-osctCk.js +29 -0
- package/dist/package-root-B-osctCk.js.map +1 -0
- package/dist/paths-CV9K7Xqm.js +2 -0
- package/dist/{paths-j2B7b0DZ.js → paths-wLC0InjX.js} +28 -4
- package/dist/paths-wLC0InjX.js.map +1 -0
- package/dist/{peer-mcp-personas-DRL_xT4h.js → peer-mcp-personas-V6stFvpq.js} +1158 -297
- package/dist/peer-mcp-personas-V6stFvpq.js.map +1 -0
- package/dist/{plan-review-hook-Dk5zTNke.js → plan-review-hook-Lf9ISdF6.js} +5 -6
- package/dist/plan-review-hook-Lf9ISdF6.js.map +1 -0
- package/dist/{prompt-submit-hook-lvTWwaTV.js → prompt-submit-hook-BlijaOn7.js} +6 -11
- package/dist/prompt-submit-hook-BlijaOn7.js.map +1 -0
- package/dist/{provision-HzZ547dW.js → provision-BpL6gZIt.js} +4 -4
- package/dist/{provision-HzZ547dW.js.map → provision-BpL6gZIt.js.map} +1 -1
- package/dist/self-invocation-CKMjcA5F.js +380 -0
- package/dist/self-invocation-CKMjcA5F.js.map +1 -0
- package/dist/{serve-CM3OmF4I.js → serve-CXUf7RtJ.js} +34 -27
- package/dist/serve-CXUf7RtJ.js.map +1 -0
- package/dist/{server-setup-C9r7jYnz.js → server-setup-Bppjt9xO.js} +81 -207
- package/dist/server-setup-Bppjt9xO.js.map +1 -0
- package/dist/{start-KAUCsDao.js → start-C1-jrHfU.js} +3 -3
- package/dist/{start-KAUCsDao.js.map → start-C1-jrHfU.js.map} +1 -1
- package/dist/{stop-gate-hook-CaUrldLh.js → stop-gate-hook-DgJ6sW8N.js} +100 -52
- package/dist/stop-gate-hook-DgJ6sW8N.js.map +1 -0
- package/dist/{stop-gate-policy-lAabj_pP.js → stop-gate-policy-DG5yYWGn.js} +2 -2
- package/dist/{stop-gate-policy-lAabj_pP.js.map → stop-gate-policy-DG5yYWGn.js.map} +1 -1
- package/dist/{token-CxPaBEUS.js → token-CnlB0884.js} +2 -2
- package/dist/token-CnlB0884.js.map +1 -0
- package/dist/{version-_Q1WpsQp.js → version-C8x2hQrZ.js} +14 -2
- package/dist/version-C8x2hQrZ.js.map +1 -0
- package/dist/{worker-dispatch-Bj1uYyG9.js → worker-dispatch-zW8Zi69V.js} +11 -7
- package/dist/worker-dispatch-zW8Zi69V.js.map +1 -0
- package/package.json +2 -2
- package/dist/attribution-settings-VOPvSn6v.js.map +0 -1
- package/dist/claude-OTHh7iKz.js.map +0 -1
- package/dist/engine-BKJKMAzh.js +0 -2
- package/dist/internal-plan-review-jRMpHu68.js.map +0 -1
- package/dist/internal-stop-hook-Ds9LwmZA.js.map +0 -1
- package/dist/lifecycle-DCrKbaIU.js +0 -2
- package/dist/lifecycle-KKSTKwd6.js +0 -2
- package/dist/mcp-workspace-header-CGJbNeHb.js.map +0 -1
- package/dist/paths-DN3Nio42.js +0 -2
- package/dist/paths-j2B7b0DZ.js.map +0 -1
- package/dist/peer-mcp-personas-DRL_xT4h.js.map +0 -1
- package/dist/plan-review-hook-Dk5zTNke.js.map +0 -1
- package/dist/prompt-submit-hook-lvTWwaTV.js.map +0 -1
- package/dist/serve-CM3OmF4I.js.map +0 -1
- package/dist/server-setup-C9r7jYnz.js.map +0 -1
- package/dist/stop-gate-hook-CaUrldLh.js.map +0 -1
- package/dist/token-CxPaBEUS.js.map +0 -1
- package/dist/version-_Q1WpsQp.js.map +0 -1
- package/dist/worker-dispatch-Bj1uYyG9.js.map +0 -1
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { t as PACKAGE_ROOT_FLAG } from "./package-root-B-osctCk.js";
|
|
2
|
+
import { t as PATHS } from "./paths-wLC0InjX.js";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { chmodSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, utimesSync, writeFileSync } from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
//#region src/lib/hook-launcher/provision.ts
|
|
10
|
+
/**
|
|
11
|
+
* Publish the self-contained hook launcher (`dist/hooks.mjs`) to a stable,
|
|
12
|
+
* content-addressed path under `<APP_DIR>/hooks/`.
|
|
13
|
+
*
|
|
14
|
+
* Why this exists: `github-router claude` persists Claude Code hook commands
|
|
15
|
+
* into a settings.json that outlives the process that wrote it, built from
|
|
16
|
+
* `process.argv[1]`. Under `bunx` that path is inside `$TMPDIR`, and macOS's
|
|
17
|
+
* per-user temp reaper deletes files there (keeping the directory skeleton) —
|
|
18
|
+
* so `node <tmp>/…/dist/main.js` died on its first bare import with
|
|
19
|
+
* `ERR_MODULE_NOT_FOUND` and EVERY hook in a long-running session failed until
|
|
20
|
+
* the tree happened to be re-extracted. `bunx pkg@latest` also re-extracts in
|
|
21
|
+
* place whenever npm's `latest` advances, so a live session's hooks could
|
|
22
|
+
* silently start executing a different version's code.
|
|
23
|
+
*
|
|
24
|
+
* Deliberate departures from `provisionBrowserAssets()`, which this otherwise
|
|
25
|
+
* mirrors:
|
|
26
|
+
*
|
|
27
|
+
* - **Content-addressed, immutable names.** The digest IS the filename, so a
|
|
28
|
+
* new build never overwrites an old launcher and there is nothing to
|
|
29
|
+
* garbage-collect. Version-keyed names would need GC, and GC across
|
|
30
|
+
* concurrent proxies is a race: proxy N deleting N+1's launcher (or the
|
|
31
|
+
* reverse) breaks a live session's hooks. At ~1.5 MB, keeping stale
|
|
32
|
+
* launchers costs less than the race would.
|
|
33
|
+
*
|
|
34
|
+
* - **The source is verified before it is published.** Hashing the bytes
|
|
35
|
+
* read from `dist/hooks.mjs` only identifies those bytes; it does not
|
|
36
|
+
* prove they are a complete artifact. A concurrent in-place `bunx`
|
|
37
|
+
* extraction can hand over a truncated file, which content-addressing
|
|
38
|
+
* would happily preserve forever under its own hash. The build writes a
|
|
39
|
+
* `dist/hooks.sha256` sidecar and we publish only on a match.
|
|
40
|
+
*
|
|
41
|
+
* - **Failures are not latched.** A transient `EBUSY` (Windows antivirus, a
|
|
42
|
+
* concurrent reader) must not disable provisioning for the process
|
|
43
|
+
* lifetime, so only success is cached.
|
|
44
|
+
*
|
|
45
|
+
* Never throws: the caller degrades to `process.argv[1]` with a visible
|
|
46
|
+
* warning rather than failing the launch.
|
|
47
|
+
*/
|
|
48
|
+
/** Sidecar written by `scripts/stamp-hooks-digest.ts` next to the bundle. */
|
|
49
|
+
const DIGEST_SIDECAR = "hooks.sha256";
|
|
50
|
+
/** Basename of the built launcher bundle. */
|
|
51
|
+
const BUNDLE_NAME = "hooks.mjs";
|
|
52
|
+
/**
|
|
53
|
+
* Publish retries. A rename can lose a race with another proxy publishing the
|
|
54
|
+
* same digest, or hit a Windows sharing violation while an antivirus scanner
|
|
55
|
+
* holds the temp file. Both clear in milliseconds.
|
|
56
|
+
*/
|
|
57
|
+
const PUBLISH_ATTEMPTS = 3;
|
|
58
|
+
let _published;
|
|
59
|
+
let _inFlight;
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the built bundle that sits beside the running code.
|
|
62
|
+
*
|
|
63
|
+
* In a built layout every chunk lives in `dist/`, so a sibling lookup finds
|
|
64
|
+
* `dist/hooks.mjs`. Running from source it resolves to
|
|
65
|
+
* `src/lib/hook-launcher/hooks.mjs`, which does not exist — which is exactly
|
|
66
|
+
* the wanted behaviour: a `bun run dev` session must keep using
|
|
67
|
+
* `process.argv[1]` rather than silently adopting a stale bundle from some
|
|
68
|
+
* earlier build.
|
|
69
|
+
*/
|
|
70
|
+
function bundledLauncherPath() {
|
|
71
|
+
try {
|
|
72
|
+
return path.join(path.dirname(fileURLToPath(import.meta.url)), BUNDLE_NAME);
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function sha256(buf) {
|
|
78
|
+
return createHash("sha256").update(buf).digest("hex");
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Read the build's expected digest. Absent or malformed means "cannot verify",
|
|
82
|
+
* which is treated as a refusal to publish rather than as permission to skip
|
|
83
|
+
* the check — an unverified source is the one thing content-addressing cannot
|
|
84
|
+
* recover from.
|
|
85
|
+
*/
|
|
86
|
+
function expectedDigest(bundlePath) {
|
|
87
|
+
try {
|
|
88
|
+
const raw = readFileSync(path.join(path.dirname(bundlePath), DIGEST_SIDECAR), "utf8").trim();
|
|
89
|
+
return /^[0-9a-f]{64}$/.test(raw) ? raw : void 0;
|
|
90
|
+
} catch {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** True when `target` already holds exactly the bytes its name claims. */
|
|
95
|
+
function targetIsValid(target, digest) {
|
|
96
|
+
try {
|
|
97
|
+
return sha256(readFileSync(target)) === digest;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Write `bytes` to `target` via a unique same-directory temp file plus a
|
|
104
|
+
* rename, so a crash mid-write can never leave a partial file under the name
|
|
105
|
+
* a hook command points at.
|
|
106
|
+
*
|
|
107
|
+
* The target is never deleted first: on Windows that would open a window where
|
|
108
|
+
* a concurrently-launching session finds no launcher at all. A rename onto an
|
|
109
|
+
* existing path can fail there, so a losing racer validates what the winner
|
|
110
|
+
* wrote and accepts it.
|
|
111
|
+
*/
|
|
112
|
+
function publish(target, bytes, digest) {
|
|
113
|
+
if (targetIsValid(target, digest)) return true;
|
|
114
|
+
for (let attempt = 0; attempt < PUBLISH_ATTEMPTS; attempt++) {
|
|
115
|
+
const tmp = `${target}.${process.pid}-${attempt}.tmp`;
|
|
116
|
+
try {
|
|
117
|
+
writeFileSync(tmp, bytes, { mode: 384 });
|
|
118
|
+
renameSync(tmp, target);
|
|
119
|
+
return true;
|
|
120
|
+
} catch {
|
|
121
|
+
try {
|
|
122
|
+
rmSync(tmp, { force: true });
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return targetIsValid(target, digest);
|
|
127
|
+
}
|
|
128
|
+
/** A stranded publish temp is dead well inside this. */
|
|
129
|
+
const TMP_MAX_AGE_MS = 864e5;
|
|
130
|
+
/**
|
|
131
|
+
* Launchers older than this are reaped. Generous on purpose — see the residual
|
|
132
|
+
* risk in `sweepStaleLaunchers`.
|
|
133
|
+
*/
|
|
134
|
+
const LAUNCHER_MAX_AGE_MS = 2592e6;
|
|
135
|
+
/**
|
|
136
|
+
* Reap stranded publish temps and long-unused launchers.
|
|
137
|
+
*
|
|
138
|
+
* Without this the directory grows by ~1.5 MB per shipped version forever, and
|
|
139
|
+
* this project ships a patch release per PR with self-update on by default.
|
|
140
|
+
*
|
|
141
|
+
* The immutable content-addressed naming exists to avoid a GC race, so the
|
|
142
|
+
* sweep is built to not reintroduce one: it never touches the digest THIS
|
|
143
|
+
* launch resolved, and every launch refreshes its own launcher's mtime, so a
|
|
144
|
+
* file survives for as long as some proxy keeps launching against it. Residual
|
|
145
|
+
* risk, stated plainly: a single proxy running continuously for more than 30
|
|
146
|
+
* days without relaunching could have its launcher reaped by a newer proxy.
|
|
147
|
+
* That is bounded and far rarer than the unbounded growth it prevents, but it
|
|
148
|
+
* is not zero — a PID-liveness marker would close it if it ever bites.
|
|
149
|
+
*/
|
|
150
|
+
function sweepStaleLaunchers(dir, keep) {
|
|
151
|
+
const now = Date.now();
|
|
152
|
+
let entries;
|
|
153
|
+
try {
|
|
154
|
+
entries = readdirSync(dir);
|
|
155
|
+
} catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
for (const name of entries) {
|
|
159
|
+
if (name === path.basename(keep)) continue;
|
|
160
|
+
const isTmp = name.endsWith(".tmp");
|
|
161
|
+
const isLauncher = /^hooks-[0-9a-f]{64}\.mjs$/.test(name);
|
|
162
|
+
if (!isTmp && !isLauncher) continue;
|
|
163
|
+
const target = path.join(dir, name);
|
|
164
|
+
try {
|
|
165
|
+
if (now - statSync(target).mtimeMs > (isTmp ? TMP_MAX_AGE_MS : LAUNCHER_MAX_AGE_MS)) rmSync(target, { force: true });
|
|
166
|
+
} catch {}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Materialize the launcher and return its stable absolute path, or undefined
|
|
171
|
+
* when it could not be published (running from source, bundle missing or
|
|
172
|
+
* unverifiable, filesystem refusing the write).
|
|
173
|
+
*
|
|
174
|
+
* Single-flight so the startup call and any lazy caller collapse to one run.
|
|
175
|
+
*/
|
|
176
|
+
function provisionHookLauncher() {
|
|
177
|
+
if (_published) return Promise.resolve(_published);
|
|
178
|
+
if (_inFlight) return _inFlight;
|
|
179
|
+
_inFlight = Promise.resolve().then(() => provisionImpl()).then((result) => {
|
|
180
|
+
if (result) _published = result;
|
|
181
|
+
return result;
|
|
182
|
+
}).finally(() => {
|
|
183
|
+
_inFlight = void 0;
|
|
184
|
+
});
|
|
185
|
+
return _inFlight;
|
|
186
|
+
}
|
|
187
|
+
function provisionImpl() {
|
|
188
|
+
const bundlePath = bundledLauncherPath();
|
|
189
|
+
if (!bundlePath) return void 0;
|
|
190
|
+
return publishLauncherFrom(bundlePath);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Verify and publish one specific bundle. Split out from the source resolution
|
|
194
|
+
* above so the publication semantics are testable: running from source,
|
|
195
|
+
* `bundledLauncherPath()` deliberately points at a file that does not exist,
|
|
196
|
+
* which is the right behaviour but leaves nothing to exercise.
|
|
197
|
+
*
|
|
198
|
+
* @internal — production callers go through `provisionHookLauncher()`.
|
|
199
|
+
*/
|
|
200
|
+
function publishLauncherFrom(bundlePath) {
|
|
201
|
+
try {
|
|
202
|
+
let bytes;
|
|
203
|
+
try {
|
|
204
|
+
bytes = readFileSync(bundlePath);
|
|
205
|
+
} catch {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const digest = sha256(bytes);
|
|
209
|
+
const expected = expectedDigest(bundlePath);
|
|
210
|
+
if (!expected) {
|
|
211
|
+
consola.debug("[hook-launcher] no digest sidecar beside the bundle; not publishing");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (expected !== digest) {
|
|
215
|
+
consola.debug("[hook-launcher] bundle digest does not match the build; not publishing");
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const dir = PATHS.HOOK_LAUNCHER_DIR;
|
|
219
|
+
mkdirSync(dir, {
|
|
220
|
+
recursive: true,
|
|
221
|
+
mode: 448
|
|
222
|
+
});
|
|
223
|
+
try {
|
|
224
|
+
chmodSync(dir, 448);
|
|
225
|
+
} catch {}
|
|
226
|
+
const target = path.join(dir, `hooks-${digest}.mjs`);
|
|
227
|
+
if (!publish(target, bytes, digest)) return void 0;
|
|
228
|
+
try {
|
|
229
|
+
const now = /* @__PURE__ */ new Date();
|
|
230
|
+
utimesSync(target, now, now);
|
|
231
|
+
} catch {}
|
|
232
|
+
sweepStaleLaunchers(dir, target);
|
|
233
|
+
return target;
|
|
234
|
+
} catch (err) {
|
|
235
|
+
consola.debug("[hook-launcher] provisioning skipped:", err);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/lib/hook-launcher/self-invocation.ts
|
|
241
|
+
/**
|
|
242
|
+
* How this process names ITSELF in a command string that gets persisted to
|
|
243
|
+
* disk and executed later by someone else.
|
|
244
|
+
*
|
|
245
|
+
* Every Claude Code hook, the MCP `headersHelper`, and the worker-guard are
|
|
246
|
+
* registered as shell commands inside a settings.json that outlives the
|
|
247
|
+
* process that wrote it. They were built from `(process.execPath,
|
|
248
|
+
* process.argv[1])`, which under `bunx` points into `$TMPDIR` — a directory
|
|
249
|
+
* macOS reaps and `bunx pkg@latest` re-extracts in place. A persisted command
|
|
250
|
+
* pointing there stops working mid-session (see
|
|
251
|
+
* `src/lib/hook-launcher/provision.ts` for the failure in full).
|
|
252
|
+
*
|
|
253
|
+
* So the pair is resolved ONCE per launch, through the stable relocated
|
|
254
|
+
* launcher when one can be published, and every builder composes its command
|
|
255
|
+
* from that single answer.
|
|
256
|
+
*/
|
|
257
|
+
function quote(s) {
|
|
258
|
+
return `"${s}"`;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Compose a persisted command string: the binary, the script when it differs
|
|
262
|
+
* from the binary, the baked package root when there is one, then the args.
|
|
263
|
+
*
|
|
264
|
+
* Every builder in the codebase routes through here so the three parts cannot
|
|
265
|
+
* drift apart — and so a future part is added in one place, not eleven.
|
|
266
|
+
*/
|
|
267
|
+
function buildSelfCommand(inv, args) {
|
|
268
|
+
const parts = [quote(inv.execPath)];
|
|
269
|
+
if (inv.scriptPath && inv.scriptPath !== inv.execPath) parts.push(quote(inv.scriptPath));
|
|
270
|
+
if (inv.packageRoot) parts.push(PACKAGE_ROOT_FLAG, quote(inv.packageRoot));
|
|
271
|
+
if (args) parts.push(args);
|
|
272
|
+
return parts.join(" ");
|
|
273
|
+
}
|
|
274
|
+
/** The invocation describing the currently running entrypoint, unrelocated. */
|
|
275
|
+
function currentInvocation() {
|
|
276
|
+
return {
|
|
277
|
+
execPath: process.execPath,
|
|
278
|
+
scriptPath: process.argv[1]
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
let _resolved;
|
|
282
|
+
/**
|
|
283
|
+
* Resolve the invocation to bake into every persisted command this launch
|
|
284
|
+
* writes. Awaited BEFORE the settings are written, never fire-and-forget: a
|
|
285
|
+
* command persisted while provisioning is still pending would name the very
|
|
286
|
+
* temp path this exists to avoid, and the session would stay broken however
|
|
287
|
+
* well provisioning later went.
|
|
288
|
+
*
|
|
289
|
+
* Falls back to the running entrypoint if the launcher cannot be published, so
|
|
290
|
+
* a provisioning failure degrades rather than breaking a launch. The caller is
|
|
291
|
+
* responsible for surfacing that fallback — see `hookLauncherDegradedWarning`.
|
|
292
|
+
*/
|
|
293
|
+
async function resolveSelfInvocation() {
|
|
294
|
+
if (_resolved) return _resolved;
|
|
295
|
+
const launcher = await provisionHookLauncher();
|
|
296
|
+
_resolved = launcher ? {
|
|
297
|
+
execPath: process.execPath,
|
|
298
|
+
scriptPath: launcher,
|
|
299
|
+
packageRoot: currentPackageRoot()
|
|
300
|
+
} : currentInvocation();
|
|
301
|
+
return _resolved;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* A human-facing warning when this launch is about to persist hook commands
|
|
305
|
+
* pointing into a directory the OS may reap — the exact configuration that
|
|
306
|
+
* produced the original bug.
|
|
307
|
+
*
|
|
308
|
+
* Returns the message rather than logging it, mirroring
|
|
309
|
+
* `colbertDegradedWarning`: `claude` and `codex` call `enableFileLogging()`,
|
|
310
|
+
* which redirects `consola.warn` to the error log, so a warning logged here
|
|
311
|
+
* would be invisible on the one path where it matters most. The caller writes
|
|
312
|
+
* it to stderr next to the readiness line instead.
|
|
313
|
+
*
|
|
314
|
+
* Silent when running from source, which has no bundle to publish and is not a
|
|
315
|
+
* degraded state.
|
|
316
|
+
*/
|
|
317
|
+
function hookLauncherDegradedWarning(inv) {
|
|
318
|
+
if (!inv.scriptPath) return null;
|
|
319
|
+
if (inv.scriptPath.startsWith(PATHS.HOOK_LAUNCHER_DIR)) {
|
|
320
|
+
if (inv.packageRoot) return null;
|
|
321
|
+
return "Hook launcher published without a package root; hook version reporting will be degraded. This usually means the install tree is partially missing — reinstall github-router.";
|
|
322
|
+
}
|
|
323
|
+
if (isUnderVolatileRoot(inv.scriptPath)) return "Hook commands point into a temporary directory and will stop working when the OS reaps it (the stable launcher could not be published). Reinstall with `npm install -g github-router` to avoid this.";
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* The package root of the RUNNING code, derived while we are still inside the
|
|
328
|
+
* package tree — the one moment it is knowable without guessing.
|
|
329
|
+
*
|
|
330
|
+
* The name is checked, not just the presence of a package.json: this value is
|
|
331
|
+
* baked into a persisted command and consumed by `packageRoot()` as the
|
|
332
|
+
* highest-priority source, so handing it the WRONG root would be worse than
|
|
333
|
+
* handing it none. Matches `findPackageRoot()` in
|
|
334
|
+
* `browser-mcp/native-host-installer.ts`, whose fallbacks this supersedes.
|
|
335
|
+
*/
|
|
336
|
+
function currentPackageRoot() {
|
|
337
|
+
const entry = typeof process.argv[1] === "string" ? process.argv[1] : void 0;
|
|
338
|
+
if (!entry) return void 0;
|
|
339
|
+
let cur = path.dirname(entry);
|
|
340
|
+
for (let i = 0; i < 10; i++) {
|
|
341
|
+
try {
|
|
342
|
+
const pkg = JSON.parse(readFileSync(path.join(cur, "package.json"), "utf8"));
|
|
343
|
+
if (typeof pkg.name === "string" && pkg.name.includes("github-router")) return cur;
|
|
344
|
+
} catch {}
|
|
345
|
+
const parent = path.dirname(cur);
|
|
346
|
+
if (parent === cur) break;
|
|
347
|
+
cur = parent;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Whether a path sits under a directory the OS is entitled to clean out from
|
|
352
|
+
* under a running session: the per-user temp dir on every platform, which is
|
|
353
|
+
* where `bunx` (unlike `npx`, which uses a cache under $HOME) installs.
|
|
354
|
+
*
|
|
355
|
+
* Both sides are compared in resolved AND unresolved form. macOS reports
|
|
356
|
+
* `$TMPDIR` as `/var/folders/…` while a process sees `/private/var/folders/…`,
|
|
357
|
+
* so a single-form prefix test silently never matches; and `realpathSync`
|
|
358
|
+
* fails on a path that does not exist, which would otherwise make the answer
|
|
359
|
+
* depend on whether the file happens to be there.
|
|
360
|
+
*/
|
|
361
|
+
function isUnderVolatileRoot(target) {
|
|
362
|
+
try {
|
|
363
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
364
|
+
for (const root of [os.tmpdir(), realpathIfPossible(os.tmpdir())]) prefixes.add(root.endsWith(path.sep) ? root : root + path.sep);
|
|
365
|
+
return [target, realpathIfPossible(target)].some((t) => [...prefixes].some((p) => t.startsWith(p)));
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function realpathIfPossible(p) {
|
|
371
|
+
try {
|
|
372
|
+
return realpathSync(p);
|
|
373
|
+
} catch {
|
|
374
|
+
return p;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
//#endregion
|
|
378
|
+
export { hookLauncherDegradedWarning as n, resolveSelfInvocation as r, buildSelfCommand as t };
|
|
379
|
+
|
|
380
|
+
//# sourceMappingURL=self-invocation-CKMjcA5F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-invocation-CKMjcA5F.js","names":[],"sources":["../src/lib/hook-launcher/provision.ts","../src/lib/hook-launcher/self-invocation.ts"],"sourcesContent":["/**\n * Publish the self-contained hook launcher (`dist/hooks.mjs`) to a stable,\n * content-addressed path under `<APP_DIR>/hooks/`.\n *\n * Why this exists: `github-router claude` persists Claude Code hook commands\n * into a settings.json that outlives the process that wrote it, built from\n * `process.argv[1]`. Under `bunx` that path is inside `$TMPDIR`, and macOS's\n * per-user temp reaper deletes files there (keeping the directory skeleton) —\n * so `node <tmp>/…/dist/main.js` died on its first bare import with\n * `ERR_MODULE_NOT_FOUND` and EVERY hook in a long-running session failed until\n * the tree happened to be re-extracted. `bunx pkg@latest` also re-extracts in\n * place whenever npm's `latest` advances, so a live session's hooks could\n * silently start executing a different version's code.\n *\n * Deliberate departures from `provisionBrowserAssets()`, which this otherwise\n * mirrors:\n *\n * - **Content-addressed, immutable names.** The digest IS the filename, so a\n * new build never overwrites an old launcher and there is nothing to\n * garbage-collect. Version-keyed names would need GC, and GC across\n * concurrent proxies is a race: proxy N deleting N+1's launcher (or the\n * reverse) breaks a live session's hooks. At ~1.5 MB, keeping stale\n * launchers costs less than the race would.\n *\n * - **The source is verified before it is published.** Hashing the bytes\n * read from `dist/hooks.mjs` only identifies those bytes; it does not\n * prove they are a complete artifact. A concurrent in-place `bunx`\n * extraction can hand over a truncated file, which content-addressing\n * would happily preserve forever under its own hash. The build writes a\n * `dist/hooks.sha256` sidecar and we publish only on a match.\n *\n * - **Failures are not latched.** A transient `EBUSY` (Windows antivirus, a\n * concurrent reader) must not disable provisioning for the process\n * lifetime, so only success is cached.\n *\n * Never throws: the caller degrades to `process.argv[1]` with a visible\n * warning rather than failing the launch.\n */\n\nimport { createHash } from \"node:crypto\"\nimport {\n chmodSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n utimesSync,\n writeFileSync,\n} from \"node:fs\"\nimport path from \"node:path\"\nimport { fileURLToPath } from \"node:url\"\n\nimport consola from \"consola\"\n\nimport { PATHS } from \"../paths\"\n\n/** Sidecar written by `scripts/stamp-hooks-digest.ts` next to the bundle. */\nconst DIGEST_SIDECAR = \"hooks.sha256\"\n\n/** Basename of the built launcher bundle. */\nconst BUNDLE_NAME = \"hooks.mjs\"\n\n/**\n * Publish retries. A rename can lose a race with another proxy publishing the\n * same digest, or hit a Windows sharing violation while an antivirus scanner\n * holds the temp file. Both clear in milliseconds.\n */\nconst PUBLISH_ATTEMPTS = 3\n\nlet _published: string | undefined\nlet _inFlight: Promise<string | undefined> | undefined\n\n/** @internal — reset module state between test cases. */\nexport function __resetHookLauncherForTests(): void {\n _published = undefined\n _inFlight = undefined\n}\n\n/**\n * Resolve the built bundle that sits beside the running code.\n *\n * In a built layout every chunk lives in `dist/`, so a sibling lookup finds\n * `dist/hooks.mjs`. Running from source it resolves to\n * `src/lib/hook-launcher/hooks.mjs`, which does not exist — which is exactly\n * the wanted behaviour: a `bun run dev` session must keep using\n * `process.argv[1]` rather than silently adopting a stale bundle from some\n * earlier build.\n */\nfunction bundledLauncherPath(): string | undefined {\n try {\n return path.join(path.dirname(fileURLToPath(import.meta.url)), BUNDLE_NAME)\n } catch {\n return undefined\n }\n}\n\nfunction sha256(buf: Buffer): string {\n return createHash(\"sha256\").update(buf).digest(\"hex\")\n}\n\n/**\n * Read the build's expected digest. Absent or malformed means \"cannot verify\",\n * which is treated as a refusal to publish rather than as permission to skip\n * the check — an unverified source is the one thing content-addressing cannot\n * recover from.\n */\nfunction expectedDigest(bundlePath: string): string | undefined {\n try {\n const raw = readFileSync(\n path.join(path.dirname(bundlePath), DIGEST_SIDECAR),\n \"utf8\",\n ).trim()\n return /^[0-9a-f]{64}$/.test(raw) ? raw : undefined\n } catch {\n return undefined\n }\n}\n\n/** True when `target` already holds exactly the bytes its name claims. */\nfunction targetIsValid(target: string, digest: string): boolean {\n try {\n return sha256(readFileSync(target)) === digest\n } catch {\n return false\n }\n}\n\n/**\n * Write `bytes` to `target` via a unique same-directory temp file plus a\n * rename, so a crash mid-write can never leave a partial file under the name\n * a hook command points at.\n *\n * The target is never deleted first: on Windows that would open a window where\n * a concurrently-launching session finds no launcher at all. A rename onto an\n * existing path can fail there, so a losing racer validates what the winner\n * wrote and accepts it.\n */\nfunction publish(target: string, bytes: Buffer, digest: string): boolean {\n // Checked once up front rather than per attempt: validation re-hashes ~1.5 MB\n // and the retries exist for rename contention, which revalidating cannot fix.\n if (targetIsValid(target, digest)) return true\n for (let attempt = 0; attempt < PUBLISH_ATTEMPTS; attempt++) {\n const tmp = `${target}.${process.pid}-${attempt}.tmp`\n try {\n writeFileSync(tmp, bytes, { mode: 0o600 })\n renameSync(tmp, target)\n return true\n } catch {\n try {\n rmSync(tmp, { force: true })\n } catch {\n // Best-effort. A Windows sharing violation can strand the temp file;\n // `sweepStaleLaunchers` reaps it later rather than leaking 1.5 MB.\n }\n }\n }\n return targetIsValid(target, digest)\n}\n\n/** A stranded publish temp is dead well inside this. */\nconst TMP_MAX_AGE_MS = 24 * 60 * 60 * 1000\n\n/**\n * Launchers older than this are reaped. Generous on purpose — see the residual\n * risk in `sweepStaleLaunchers`.\n */\nconst LAUNCHER_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/**\n * Reap stranded publish temps and long-unused launchers.\n *\n * Without this the directory grows by ~1.5 MB per shipped version forever, and\n * this project ships a patch release per PR with self-update on by default.\n *\n * The immutable content-addressed naming exists to avoid a GC race, so the\n * sweep is built to not reintroduce one: it never touches the digest THIS\n * launch resolved, and every launch refreshes its own launcher's mtime, so a\n * file survives for as long as some proxy keeps launching against it. Residual\n * risk, stated plainly: a single proxy running continuously for more than 30\n * days without relaunching could have its launcher reaped by a newer proxy.\n * That is bounded and far rarer than the unbounded growth it prevents, but it\n * is not zero — a PID-liveness marker would close it if it ever bites.\n */\nfunction sweepStaleLaunchers(dir: string, keep: string): void {\n const now = Date.now()\n let entries: Array<string>\n try {\n entries = readdirSync(dir)\n } catch {\n return\n }\n for (const name of entries) {\n if (name === path.basename(keep)) continue\n const isTmp = name.endsWith(\".tmp\")\n // Strict shape match, like sweepAgedWorkerDiffs: this directory is ours,\n // but a name-shape check means a false positive can only ever cost a file\n // the next launch regenerates.\n const isLauncher = /^hooks-[0-9a-f]{64}\\.mjs$/.test(name)\n if (!isTmp && !isLauncher) continue\n const target = path.join(dir, name)\n try {\n const age = now - statSync(target).mtimeMs\n if (age > (isTmp ? TMP_MAX_AGE_MS : LAUNCHER_MAX_AGE_MS)) {\n rmSync(target, { force: true })\n }\n } catch {\n /* best-effort */\n }\n }\n}\n\n/**\n * Materialize the launcher and return its stable absolute path, or undefined\n * when it could not be published (running from source, bundle missing or\n * unverifiable, filesystem refusing the write).\n *\n * Single-flight so the startup call and any lazy caller collapse to one run.\n */\nexport function provisionHookLauncher(): Promise<string | undefined> {\n if (_published) return Promise.resolve(_published)\n if (_inFlight) return _inFlight\n _inFlight = Promise.resolve()\n .then(() => provisionImpl())\n .then((result) => {\n // Cache SUCCESS only. Latching a transient failure would disable the\n // stable launcher for the whole process lifetime.\n if (result) _published = result\n return result\n })\n .finally(() => {\n _inFlight = undefined\n })\n return _inFlight\n}\n\nfunction provisionImpl(): string | undefined {\n const bundlePath = bundledLauncherPath()\n if (!bundlePath) return undefined\n return publishLauncherFrom(bundlePath)\n}\n\n/**\n * Verify and publish one specific bundle. Split out from the source resolution\n * above so the publication semantics are testable: running from source,\n * `bundledLauncherPath()` deliberately points at a file that does not exist,\n * which is the right behaviour but leaves nothing to exercise.\n *\n * @internal — production callers go through `provisionHookLauncher()`.\n */\nexport function publishLauncherFrom(bundlePath: string): string | undefined {\n try {\n let bytes: Buffer\n try {\n bytes = readFileSync(bundlePath)\n } catch {\n // Running from source, or the package tree was reaped out from under us.\n // Either way there is nothing to publish; a launcher published by an\n // earlier launch still stands.\n return undefined\n }\n\n const digest = sha256(bytes)\n const expected = expectedDigest(bundlePath)\n if (!expected) {\n // \"Cannot verify\" and \"verified bad\" both mean do-not-publish. Treating\n // an absent sidecar as permission would delete the integrity check in\n // exactly the degraded conditions it exists for.\n consola.debug(\n \"[hook-launcher] no digest sidecar beside the bundle; not publishing\",\n )\n return undefined\n }\n if (expected !== digest) {\n // The classic cause is reading mid-`bunx`-re-extraction. Publishing here\n // would freeze a torn bundle under a stable name forever, because\n // content-addressing gives a truncated file its own perfectly valid hash.\n consola.debug(\n \"[hook-launcher] bundle digest does not match the build; not publishing\",\n )\n return undefined\n }\n\n const dir = PATHS.HOOK_LAUNCHER_DIR\n // 0o700 / 0o600, matching the convention for the runtime dir in paths.ts.\n // This is not incidental hygiene: the published file is EXECUTED by node on\n // every prompt of every session, so a group- or world-writable path here\n // would let a local attacker swap the launcher and run code inside the\n // user's agent. chmod is best-effort — it is a no-op on Windows, where the\n // ACL inherited from APP_DIR governs.\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n try {\n chmodSync(dir, 0o700)\n } catch {\n /* best-effort: Windows, or a dir we do not own */\n }\n const target = path.join(dir, `hooks-${digest}.mjs`)\n if (!publish(target, bytes, digest)) return undefined\n\n // Mark this launcher as still in use before sweeping, so a long-lived\n // install that keeps relaunching against an old version is never reaped by\n // a newer proxy's sweep.\n try {\n const now = new Date()\n utimesSync(target, now, now)\n } catch {\n /* best-effort */\n }\n sweepStaleLaunchers(dir, target)\n return target\n } catch (err) {\n consola.debug(\"[hook-launcher] provisioning skipped:\", err)\n return undefined\n }\n}\n","/**\n * How this process names ITSELF in a command string that gets persisted to\n * disk and executed later by someone else.\n *\n * Every Claude Code hook, the MCP `headersHelper`, and the worker-guard are\n * registered as shell commands inside a settings.json that outlives the\n * process that wrote it. They were built from `(process.execPath,\n * process.argv[1])`, which under `bunx` points into `$TMPDIR` — a directory\n * macOS reaps and `bunx pkg@latest` re-extracts in place. A persisted command\n * pointing there stops working mid-session (see\n * `src/lib/hook-launcher/provision.ts` for the failure in full).\n *\n * So the pair is resolved ONCE per launch, through the stable relocated\n * launcher when one can be published, and every builder composes its command\n * from that single answer.\n */\n\nimport { readFileSync, realpathSync } from \"node:fs\"\nimport os from \"node:os\"\nimport path from \"node:path\"\n\nimport { PACKAGE_ROOT_FLAG } from \"../package-root\"\nimport { PATHS } from \"../paths\"\nimport { provisionHookLauncher } from \"./provision\"\n\n/** The binary + script + package root a persisted command should name. */\nexport interface SelfInvocation {\n /** The node/bun binary to exec. */\n execPath: string\n /** Script to pass to it, omitted for a packaged single-file build. */\n scriptPath?: string\n /**\n * The github-router package root, baked in only when `scriptPath` is the\n * relocated launcher. From `<APP_DIR>/hooks/` nothing can derive it: the\n * pre-existing walk in `browser-mcp/native-host-installer.ts` falls back to\n * `process.cwd()`, which would silently resolve to the USER'S WORKSPACE.\n */\n packageRoot?: string\n}\n\nfunction quote(s: string): string {\n return `\"${s}\"`\n}\n\n/**\n * Compose a persisted command string: the binary, the script when it differs\n * from the binary, the baked package root when there is one, then the args.\n *\n * Every builder in the codebase routes through here so the three parts cannot\n * drift apart — and so a future part is added in one place, not eleven.\n */\nexport function buildSelfCommand(inv: SelfInvocation, args: string): string {\n const parts = [quote(inv.execPath)]\n if (inv.scriptPath && inv.scriptPath !== inv.execPath) {\n parts.push(quote(inv.scriptPath))\n }\n if (inv.packageRoot) {\n parts.push(PACKAGE_ROOT_FLAG, quote(inv.packageRoot))\n }\n if (args) parts.push(args)\n return parts.join(\" \")\n}\n\n/** The invocation describing the currently running entrypoint, unrelocated. */\nexport function currentInvocation(): SelfInvocation {\n return { execPath: process.execPath, scriptPath: process.argv[1] }\n}\n\nlet _resolved: SelfInvocation | undefined\n\n/** @internal — reset module state between test cases. */\nexport function __resetSelfInvocationForTests(): void {\n _resolved = undefined\n}\n\n/**\n * Resolve the invocation to bake into every persisted command this launch\n * writes. Awaited BEFORE the settings are written, never fire-and-forget: a\n * command persisted while provisioning is still pending would name the very\n * temp path this exists to avoid, and the session would stay broken however\n * well provisioning later went.\n *\n * Falls back to the running entrypoint if the launcher cannot be published, so\n * a provisioning failure degrades rather than breaking a launch. The caller is\n * responsible for surfacing that fallback — see `hookLauncherDegradedWarning`.\n */\nexport async function resolveSelfInvocation(): Promise<SelfInvocation> {\n if (_resolved) return _resolved\n const launcher = await provisionHookLauncher()\n _resolved =\n launcher ?\n {\n execPath: process.execPath,\n scriptPath: launcher,\n packageRoot: currentPackageRoot(),\n }\n : currentInvocation()\n return _resolved\n}\n\n/**\n * A human-facing warning when this launch is about to persist hook commands\n * pointing into a directory the OS may reap — the exact configuration that\n * produced the original bug.\n *\n * Returns the message rather than logging it, mirroring\n * `colbertDegradedWarning`: `claude` and `codex` call `enableFileLogging()`,\n * which redirects `consola.warn` to the error log, so a warning logged here\n * would be invisible on the one path where it matters most. The caller writes\n * it to stderr next to the readiness line instead.\n *\n * Silent when running from source, which has no bundle to publish and is not a\n * degraded state.\n */\nexport function hookLauncherDegradedWarning(\n inv: SelfInvocation,\n): string | null {\n if (!inv.scriptPath) return null\n // Our own published launcher is checked FIRST, and is authoritative: we know\n // we published it, so it can never warrant the \"could not publish\" message\n // below even if APP_DIR itself sits somewhere unusual.\n if (inv.scriptPath.startsWith(PATHS.HOOK_LAUNCHER_DIR)) {\n if (inv.packageRoot) return null\n // Relocated but rootless. Today this only degrades `--version` inside the\n // launcher, because nothing in the hook graph consumes the package root for\n // anything else. It is reported anyway because it is a trap rather than a\n // bug: the day a hook subcommand pulls in code_search or the browser\n // installer, `packageRoot()` falls through to `process.cwd()` and starts\n // resolving against the user's REPO with no signal at all.\n return (\n \"Hook launcher published without a package root; hook version reporting \"\n + \"will be degraded. This usually means the install tree is partially \"\n + \"missing — reinstall github-router.\"\n )\n }\n if (isUnderVolatileRoot(inv.scriptPath)) {\n return (\n \"Hook commands point into a temporary directory and will stop working \"\n + \"when the OS reaps it (the stable launcher could not be published). \"\n + \"Reinstall with `npm install -g github-router` to avoid this.\"\n )\n }\n return null\n}\n\n/**\n * The package root of the RUNNING code, derived while we are still inside the\n * package tree — the one moment it is knowable without guessing.\n *\n * The name is checked, not just the presence of a package.json: this value is\n * baked into a persisted command and consumed by `packageRoot()` as the\n * highest-priority source, so handing it the WRONG root would be worse than\n * handing it none. Matches `findPackageRoot()` in\n * `browser-mcp/native-host-installer.ts`, whose fallbacks this supersedes.\n */\nfunction currentPackageRoot(): string | undefined {\n const entry = typeof process.argv[1] === \"string\" ? process.argv[1] : undefined\n if (!entry) return undefined\n let cur = path.dirname(entry)\n for (let i = 0; i < 10; i++) {\n try {\n const pkg = JSON.parse(\n readFileSync(path.join(cur, \"package.json\"), \"utf8\"),\n ) as { name?: unknown }\n if (typeof pkg.name === \"string\" && pkg.name.includes(\"github-router\")) {\n return cur\n }\n } catch {\n // Not here, or unreadable; walk up.\n }\n const parent = path.dirname(cur)\n if (parent === cur) break\n cur = parent\n }\n return undefined\n}\n\n/**\n * Whether a path sits under a directory the OS is entitled to clean out from\n * under a running session: the per-user temp dir on every platform, which is\n * where `bunx` (unlike `npx`, which uses a cache under $HOME) installs.\n *\n * Both sides are compared in resolved AND unresolved form. macOS reports\n * `$TMPDIR` as `/var/folders/…` while a process sees `/private/var/folders/…`,\n * so a single-form prefix test silently never matches; and `realpathSync`\n * fails on a path that does not exist, which would otherwise make the answer\n * depend on whether the file happens to be there.\n */\nexport function isUnderVolatileRoot(target: string): boolean {\n try {\n const prefixes = new Set<string>()\n for (const root of [os.tmpdir(), realpathIfPossible(os.tmpdir())]) {\n prefixes.add(root.endsWith(path.sep) ? root : root + path.sep)\n }\n const targets = [target, realpathIfPossible(target)]\n return targets.some((t) => [...prefixes].some((p) => t.startsWith(p)))\n } catch {\n return false\n }\n}\n\nfunction realpathIfPossible(p: string): string {\n try {\n return realpathSync(p)\n } catch {\n return p\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
import { t as getPackageVersion } from "./version-
|
|
3
|
-
import { o as removeOwnClaudeConfigMirror, r as ensureClaudeConfigMirror, t as PATHS } from "./paths-
|
|
4
|
-
import { A as state, d as resolveModel, m as getGitHubUser } from "./token-
|
|
1
|
+
import { $t as resolveLeadSlugArg, C as availableToolCommands, Mt as provisionAndIndexColbert, Qt as pickClaudeDefault, S as buildEnv, T as toolbeltEnabled, Wt as DEFAULT_CLAUDE_MODEL_FALLBACKS, a as buildPeerAwarenessSnippet, at as browserCompoundToolsEnabled, ct as geminiAvailable, dt as nativeSubagentModel, ft as reviewerModel, gt as workerToolsEnabled, ht as standInToolEnabled, it as browseAgentEnabled, kt as provisionBrowserAssets, lt as generalPurposeFastModel, mt as scribeModel, ot as browserToolsEnabled, pt as scoutModel, rt as brainstormModel, s as enumerateInjectedMcpToolNames, st as fleetToolsEnabled, tt as agentToolsEnabled, ut as implementerFastModel, w as buildToolbeltAwareness } from "./peer-mcp-personas-V6stFvpq.js";
|
|
2
|
+
import { t as getPackageVersion } from "./version-C8x2hQrZ.js";
|
|
3
|
+
import { o as removeOwnClaudeConfigMirror, r as ensureClaudeConfigMirror, t as PATHS } from "./paths-wLC0InjX.js";
|
|
4
|
+
import { A as state, d as resolveModel, m as getGitHubUser } from "./token-CnlB0884.js";
|
|
5
5
|
import { a as resolveExecutable, o as runCommandCapture, t as killChildProcessTree } from "./exec-y8C_MU8A.js";
|
|
6
|
-
import { a as sharedServerArgs, i as setupAndServe, o as startKeepAwake, r as parseSharedArgs, s as stopKeepAwake, t as getClaudeCodeEnvVars, u as runSelfUpdate } from "./server-setup-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
6
|
+
import { a as sharedServerArgs, i as setupAndServe, o as startKeepAwake, r as parseSharedArgs, s as stopKeepAwake, t as getClaudeCodeEnvVars, u as runSelfUpdate } from "./server-setup-Bppjt9xO.js";
|
|
7
|
+
import { n as hookLauncherDegradedWarning, r as resolveSelfInvocation } from "./self-invocation-CKMjcA5F.js";
|
|
8
|
+
import { r as getCodexVersion, t as provisionToolbelt } from "./provision-BpL6gZIt.js";
|
|
9
|
+
import { s as injectStopHookIntoSettingsFile } from "./stop-gate-hook-DgJ6sW8N.js";
|
|
10
|
+
import { l as guardToolMatcher, n as activeDispatchModes, r as buildWorkerGuardHookCommand } from "./worker-dispatch-zW8Zi69V.js";
|
|
11
|
+
import { _ as injectPeerMcpIntoMirror, a as appendPeerAwarenessToMirroredClaudeMd, b as workersKeyOf, d as SEAMLESS_BUILTIN_TOOLS, f as configureServeDefaultPermissionMode, g as BUILTIN_SUBAGENT_DEFINITIONS, h as sanitizeServeSettingsEnv, l as prependOperatingDefaultsToMirroredClaudeMd, m as planModeAllowRules, n as INJECTED_SKILLS, o as appendToolbeltAwarenessToMirroredClaudeMd, p as injectAllowRules, r as writeInjectedSkill, s as buildOperatingDefaultsDirective, t as injectAttributionSuppressionIntoSettingsFile, u as prependStyleDirectiveToMirroredClaudeMd, v as resolveCodexCliBackend, x as writePeerMcpRuntimeFiles, y as resolveGroupKeysFromMirror } from "./attribution-settings-B8M2fvhz.js";
|
|
12
|
+
import { i as planReviewEnabled, t as buildPlanReviewHookCommand } from "./plan-review-hook-Lf9ISdF6.js";
|
|
13
|
+
import { t as buildPromptSubmitHookCommand } from "./prompt-submit-hook-BlijaOn7.js";
|
|
13
14
|
import { defineCommand } from "citty";
|
|
14
15
|
import consola from "consola";
|
|
15
16
|
import fs from "node:fs";
|
|
@@ -523,7 +524,7 @@ const NOOP = { cleanup: async () => {} };
|
|
|
523
524
|
* Tab-specific bits (ai-or-die hooks, artifact review) are intentionally left
|
|
524
525
|
* out — they don't apply to the serve control plane.
|
|
525
526
|
*/
|
|
526
|
-
async function provisionServeEnhancements(serverUrl, opts
|
|
527
|
+
async function provisionServeEnhancements(serverUrl, opts) {
|
|
527
528
|
try {
|
|
528
529
|
const tunnelExposed = opts.tunnelExposed === true;
|
|
529
530
|
const browseAllowed = browserToolsEnabled() && (!tunnelExposed || opts.browseOverTunnel === true);
|
|
@@ -543,12 +544,12 @@ async function provisionServeEnhancements(serverUrl, opts = {}) {
|
|
|
543
544
|
const { keys: groupKeys } = await resolveGroupKeysFromMirror(enabledGroups);
|
|
544
545
|
const nativeAvailability = {
|
|
545
546
|
scoutAvailable: scoutModel() != null,
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
genericCheapAvailable: genericCheapModel() != null
|
|
547
|
+
implementerFastAvailable: implementerFastModel() != null,
|
|
548
|
+
generalPurposeFastAvailable: generalPurposeFastModel() != null
|
|
549
549
|
};
|
|
550
550
|
const runtime = await writePeerMcpRuntimeFiles(serverUrl, {
|
|
551
551
|
codexCli: opts.codexCli === true,
|
|
552
|
+
selfInvocation: opts.selfInvocation,
|
|
552
553
|
geminiAvailable: gem,
|
|
553
554
|
groupKeys,
|
|
554
555
|
workerToolsAvailable: workerToolsEnabled(),
|
|
@@ -558,14 +559,14 @@ async function provisionServeEnhancements(serverUrl, opts = {}) {
|
|
|
558
559
|
brainstormModel: brainstormModel(),
|
|
559
560
|
scoutModel: scoutModel(),
|
|
560
561
|
scribeModel: scribeModel(),
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
genericCheapModel: genericCheapModel(),
|
|
562
|
+
implementerFastModel: implementerFastModel(),
|
|
563
|
+
generalPurposeFastModel: generalPurposeFastModel(),
|
|
564
564
|
builtinSubagents: BUILTIN_SUBAGENT_DEFINITIONS
|
|
565
565
|
});
|
|
566
566
|
state.peerMcpNonce = runtime.nonce;
|
|
567
567
|
const injected = await injectPeerMcpIntoMirror(serverUrl, {
|
|
568
568
|
codexCli: opts.codexCli === true,
|
|
569
|
+
selfInvocation: opts.selfInvocation,
|
|
569
570
|
geminiAvailable: gem,
|
|
570
571
|
groupKeys,
|
|
571
572
|
nonce: runtime.nonce
|
|
@@ -605,19 +606,19 @@ async function provisionServeEnhancements(serverUrl, opts = {}) {
|
|
|
605
606
|
const injectedServerKeys = injected.ok ? injected.serversAdded : [...Object.values(groupKeys).filter((k) => Boolean(k)), ...opts.codexCli === true ? ["codex-cli"] : []];
|
|
606
607
|
await injectAllowRules(settingsPath, planModeAllowRules(injectedServerKeys)).catch((err) => consola.warn(`Could not auto-approve injected tools: ${String(err)}`));
|
|
607
608
|
if (workerToolsEnabled()) {
|
|
608
|
-
const promptCmd = buildPromptSubmitHookCommand(
|
|
609
|
+
const promptCmd = buildPromptSubmitHookCommand(opts.selfInvocation);
|
|
609
610
|
await injectStopHookIntoSettingsFile(settingsPath, promptCmd, "UserPromptSubmit", 45).catch((err) => consola.warn(`Could not register the UserPromptSubmit hook: ${String(err)}`));
|
|
610
611
|
if (!injected.ok) consola.warn("Workers non-blocking guard NOT registered: subagent MCP injection fell back to parent-only, so worker-* dispatchers cannot reach the workers server. Raw worker tools remain usable on the main thread.");
|
|
611
612
|
else if (process.env.GH_ROUTER_DISABLE_WORKER_GUARD !== "1") {
|
|
612
613
|
const workersKey = workersKeyOf(groupKeys);
|
|
613
614
|
const modes = activeDispatchModes({ browse: browseAllowed && browseAgentEnabled() });
|
|
614
|
-
const cmd = buildWorkerGuardHookCommand(
|
|
615
|
+
const cmd = buildWorkerGuardHookCommand(opts.selfInvocation, workersKey, modes);
|
|
615
616
|
const matcher = guardToolMatcher(workersKey, modes);
|
|
616
617
|
await injectStopHookIntoSettingsFile(settingsPath, cmd, "PreToolUse", 10, matcher).catch((err) => consola.warn(`Could not register the workers PreToolUse guard hook: ${String(err)}`));
|
|
617
618
|
} else consola.info("Workers non-blocking guard disabled via GH_ROUTER_DISABLE_WORKER_GUARD=1; raw worker tools remain callable on the main thread.");
|
|
618
619
|
}
|
|
619
620
|
if (planReviewEnabled()) try {
|
|
620
|
-
const command = buildPlanReviewHookCommand(
|
|
621
|
+
const command = buildPlanReviewHookCommand(opts.selfInvocation);
|
|
621
622
|
await injectStopHookIntoSettingsFile(settingsPath, command, "PostToolUse", void 0, "ExitPlanMode");
|
|
622
623
|
} catch (err) {
|
|
623
624
|
consola.warn(`Could not register the advisory plan-review hook: ${String(err)}`);
|
|
@@ -1346,14 +1347,14 @@ const serve = defineCommand({
|
|
|
1346
1347
|
provisionToolbelt().catch((err) => consola.debug("Toolbelt provisioning failed:", err));
|
|
1347
1348
|
startKeepAwake();
|
|
1348
1349
|
if (browserToolsEnabled()) provisionBrowserAssets().catch((err) => consola.debug("Browser extension provisioning failed:", err));
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
if (!args.model && state.models) {
|
|
1350
|
+
let chosenSlug = resolveLeadSlugArg(args.model);
|
|
1351
|
+
if (!args.model?.trim() && state.models) {
|
|
1352
1352
|
const inCache = (slug) => state.models?.data.some((m) => m.id === resolveModel(slug)) ?? false;
|
|
1353
1353
|
if (!inCache(chosenSlug)) {
|
|
1354
1354
|
for (const fb of DEFAULT_CLAUDE_MODEL_FALLBACKS) if (inCache(fb)) {
|
|
1355
|
-
|
|
1356
|
-
chosenSlug
|
|
1355
|
+
const fallbackSlug = pickClaudeDefault(fb.replace(/^claude-opus-/, ""));
|
|
1356
|
+
consola.info(`Default model "${chosenSlug}" not in your Copilot model list; falling back to "${fallbackSlug}".`);
|
|
1357
|
+
chosenSlug = fallbackSlug;
|
|
1357
1358
|
break;
|
|
1358
1359
|
}
|
|
1359
1360
|
}
|
|
@@ -1367,7 +1368,13 @@ const serve = defineCommand({
|
|
|
1367
1368
|
if (tunnelExposed && browserToolsEnabled() && args["browse-over-tunnel"] !== true) consola.info("Browser MCP is disabled over the tunnel (session-hijack/SSRF risk). Pass --browse-over-tunnel to enable it for fully-trusted access.");
|
|
1368
1369
|
if (tunnelExposed && state.agentsEnabled && args["agents-over-tunnel"] !== true) consola.info("first-mate is disabled over the tunnel (it mints a GitHub write token). Pass --agents-over-tunnel to enable it for fully-trusted access.");
|
|
1369
1370
|
if (tunnelExposed && state.fleetEnabled && args["fleet-over-tunnel"] !== true) consola.info("Fleet MCP is disabled over the tunnel (it drives remote coding sessions with your fleet credentials). Pass --fleet-over-tunnel to enable it for fully-trusted access.");
|
|
1371
|
+
const selfInvocation = await resolveSelfInvocation();
|
|
1372
|
+
{
|
|
1373
|
+
const warning = hookLauncherDegradedWarning(selfInvocation);
|
|
1374
|
+
if (warning) process.stderr.write(`${warning}\n`);
|
|
1375
|
+
}
|
|
1370
1376
|
const enhancements = await provisionServeEnhancements(serverUrl, {
|
|
1377
|
+
selfInvocation,
|
|
1371
1378
|
codexCli: backend === "cli",
|
|
1372
1379
|
tunnelExposed,
|
|
1373
1380
|
browseOverTunnel: args["browse-over-tunnel"] === true,
|
|
@@ -1522,4 +1529,4 @@ const serve = defineCommand({
|
|
|
1522
1529
|
//#endregion
|
|
1523
1530
|
export { serve };
|
|
1524
1531
|
|
|
1525
|
-
//# sourceMappingURL=serve-
|
|
1532
|
+
//# sourceMappingURL=serve-CXUf7RtJ.js.map
|