github-router 0.3.131 → 0.3.136

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.
@@ -1,6 +1,6 @@
1
- import { t as PATHS } from "./paths-CNgpeaWd.js";
2
- import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-DTJ2Ugqf.js";
3
- import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-Cyxwmj1c.js";
1
+ import { t as PATHS } from "./paths-Cn5OzmYL.js";
2
+ import { d as runCommandCapture, l as parseBoolEnv, n as isPidAlive, o as trackChild, p as runManagedExeCapture, r as registerColbertExitHandlers, t as getColbertInstanceUuid, u as resolveExecutable } from "./lifecycle-Cqe8OQVX.js";
3
+ import { i as registerExitHandlers, n as getInstanceUuid, r as recordWorkerRepo, t as WorktreeRegistry } from "./lifecycle-CeVDX6av.js";
4
4
  import { createRequire } from "node:module";
5
5
  import consola from "consola";
6
6
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -1047,6 +1047,24 @@ function collapsePathKeys(env) {
1047
1047
  return env;
1048
1048
  }
1049
1049
 
1050
+ //#endregion
1051
+ //#region src/lib/insecure-tls.ts
1052
+ const IS_BUN = typeof globalThis.Bun !== "undefined";
1053
+ let sharedInsecureDispatcher;
1054
+ function insecureDispatcher() {
1055
+ return sharedInsecureDispatcher ??= new Agent({ connect: { rejectUnauthorized: false } });
1056
+ }
1057
+ /**
1058
+ * Attach the runtime-correct TLS-verification-off mechanism to a fetch init for a
1059
+ * single self-signed direct-HTTPS instance: Bun → `tls`, Node → an undici
1060
+ * `dispatcher`. Exported so BOTH runtime branches are unit-testable under one
1061
+ * interpreter (the untested Node branch is exactly what shipped broken).
1062
+ */
1063
+ function applyInsecureTls(init, isBun = IS_BUN) {
1064
+ if (isBun) init.tls = { rejectUnauthorized: false };
1065
+ else init.dispatcher = insecureDispatcher();
1066
+ }
1067
+
1050
1068
  //#endregion
1051
1069
  //#region src/lib/artifact/client.ts
1052
1070
  var ArtifactError = class extends Error {
@@ -1068,11 +1086,13 @@ var ArtifactClient = class {
1068
1086
  token;
1069
1087
  sessionId;
1070
1088
  fetchFn;
1089
+ insecureTLS;
1071
1090
  constructor(options) {
1072
1091
  this.baseUrl = options.baseUrl.replace(/\/+$/, "");
1073
1092
  this.token = options.token;
1074
1093
  this.sessionId = options.sessionId;
1075
1094
  this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
1095
+ this.insecureTLS = options.insecureTLS ?? false;
1076
1096
  }
1077
1097
  open(file, signal) {
1078
1098
  return this.request("POST", `/api/artifact/${encodeURIComponent(this.sessionId)}/open`, { file }, signal);
@@ -1101,7 +1121,7 @@ var ArtifactClient = class {
1101
1121
  const timeout = combineSignalAndTimeout(signal, timeoutMsHint);
1102
1122
  let response;
1103
1123
  try {
1104
- response = await this.fetchFn(url.toString(), {
1124
+ const init = {
1105
1125
  method,
1106
1126
  headers: {
1107
1127
  Authorization: `Bearer ${this.token}`,
@@ -1110,7 +1130,9 @@ var ArtifactClient = class {
1110
1130
  body: body === void 0 ? void 0 : JSON.stringify(body),
1111
1131
  redirect: "error",
1112
1132
  signal: timeout.signal
1113
- });
1133
+ };
1134
+ if (this.insecureTLS) applyInsecureTls(init);
1135
+ response = await this.fetchFn(url.toString(), init);
1114
1136
  } catch (err) {
1115
1137
  throw mapNetworkError$1(err);
1116
1138
  } finally {
@@ -1308,10 +1330,29 @@ function readArtifactEnv() {
1308
1330
  return {
1309
1331
  baseUrl,
1310
1332
  token,
1311
- sessionId
1333
+ sessionId,
1334
+ insecureTLS: shouldUseInsecureTls(baseUrl)
1312
1335
  };
1313
1336
  }
1337
+ function shouldUseInsecureTls(baseUrl) {
1338
+ let url;
1339
+ try {
1340
+ url = new URL(baseUrl);
1341
+ } catch {
1342
+ return false;
1343
+ }
1344
+ if (url.protocol !== "https:") return false;
1345
+ const explicit = (process.env.AIORDIE_INSECURE_TLS ?? "").trim().toLowerCase();
1346
+ if (explicit === "0" || explicit === "false" || explicit === "off") return false;
1347
+ if (isLoopbackIp(url.hostname)) return true;
1348
+ return url.hostname === "localhost" && (explicit === "1" || explicit === "true");
1349
+ }
1350
+ function isLoopbackIp(hostname) {
1351
+ const host = hostname.replace(/^\[|\]$/g, "");
1352
+ return host === "::1" || /^127(?:\.\d{1,3}){3}$/.test(host);
1353
+ }
1314
1354
  function clientFromEnv(env) {
1355
+ consola.debug(`ARTIFACT_ENV: token present=${env.token.length > 0}, insecureTLS=${env.insecureTLS}`);
1315
1356
  return new ArtifactClient(env);
1316
1357
  }
1317
1358
  async function pollUntilReady(client, signal) {
@@ -1644,21 +1685,6 @@ function createTunnelTokenProvider(runner = realDevtunnelRunner()) {
1644
1685
 
1645
1686
  //#endregion
1646
1687
  //#region src/lib/fleet/client.ts
1647
- const IS_BUN = typeof globalThis.Bun !== "undefined";
1648
- let sharedInsecureDispatcher;
1649
- function insecureDispatcher() {
1650
- return sharedInsecureDispatcher ??= new Agent({ connect: { rejectUnauthorized: false } });
1651
- }
1652
- /**
1653
- * Attach the runtime-correct TLS-verification-off mechanism to a fetch init for a
1654
- * single self-signed direct-HTTPS instance: Bun → `tls`, Node → an undici
1655
- * `dispatcher`. Exported so BOTH runtime branches are unit-testable under one
1656
- * interpreter (the untested Node branch is exactly what shipped broken).
1657
- */
1658
- function applyInsecureTls(init, isBun = IS_BUN) {
1659
- if (isBun) init.tls = { rejectUnauthorized: false };
1660
- else init.dispatcher = insecureDispatcher();
1661
- }
1662
1688
  var FleetError = class extends Error {
1663
1689
  code;
1664
1690
  retryable;
@@ -1691,7 +1717,7 @@ function decodeSessionId(globalId) {
1691
1717
  var FleetClient = class {
1692
1718
  baseUrl;
1693
1719
  origin;
1694
- token;
1720
+ auth;
1695
1721
  fetchFn;
1696
1722
  getTunnelToken;
1697
1723
  onTunnelAuthInvalidate;
@@ -1699,7 +1725,10 @@ var FleetClient = class {
1699
1725
  constructor(options) {
1700
1726
  this.baseUrl = options.url.replace(/\/+$/, "");
1701
1727
  this.origin = new URL(this.baseUrl).origin;
1702
- this.token = options.token;
1728
+ this.auth = options.auth ?? {
1729
+ type: "bearer",
1730
+ token: options.token ?? ""
1731
+ };
1703
1732
  this.fetchFn = options.fetchFn ?? globalThis.fetch.bind(globalThis);
1704
1733
  this.getTunnelToken = options.getTunnelToken;
1705
1734
  this.onTunnelAuthInvalidate = options.onTunnelAuthInvalidate;
@@ -1793,7 +1822,7 @@ var FleetClient = class {
1793
1822
  const attachTunnel = tunnelToken !== void 0 && tunnelToken !== "";
1794
1823
  const canRetry = attachTunnel && !!this.onTunnelAuthInvalidate && attempt === 0;
1795
1824
  const headers = {
1796
- Authorization: `Bearer ${this.token}`,
1825
+ ...this.auth.type === "bearer" ? { Authorization: `Bearer ${this.auth.token}` } : {},
1797
1826
  ...devtunnelHost ? { "X-Tunnel-Skip-Anti-Phishing-Page": "true" } : {},
1798
1827
  ...attachTunnel ? { "X-Tunnel-Authorization": `tunnel ${tunnelToken}` } : {},
1799
1828
  ...body === void 0 ? {} : { "Content-Type": "application/json" }
@@ -1814,7 +1843,7 @@ var FleetClient = class {
1814
1843
  this.onTunnelAuthInvalidate();
1815
1844
  continue;
1816
1845
  }
1817
- throw mapNetworkError(err, devtunnelHost);
1846
+ throw this.auth.type === "mesh" ? mapMeshUnreachable(err) : mapNetworkError(err, devtunnelHost);
1818
1847
  }
1819
1848
  if (!response.ok) {
1820
1849
  if ((response.status === 401 || response.status === 403) && canRetry) {
@@ -1946,6 +1975,20 @@ function detailToSearchString(detail) {
1946
1975
  return String(detail);
1947
1976
  }
1948
1977
  }
1978
+ function mapMeshUnreachable(err) {
1979
+ if (isAbortLike$1(err)) return new FleetError({
1980
+ code: "TIMEOUT",
1981
+ message: "fleet mesh peer request timed out or was aborted",
1982
+ retryable: true,
1983
+ detail: err
1984
+ });
1985
+ return new FleetError({
1986
+ code: "TAILNET_UNREACHABLE",
1987
+ message: `fleet mesh peer unreachable: ${err instanceof Error ? err.message : String(err)} — the peer is a tailnet node but the request did not land. A mesh ACL drops blocked traffic SILENTLY, so the likely cause is the \`tag:aiordie\` ACL (verify the peer permits this node), not a dead instance; also confirm the peer's mesh sidecar is up and serving HTTPS on the tailnet.`,
1988
+ retryable: true,
1989
+ detail: err
1990
+ });
1991
+ }
1949
1992
  function mapNetworkError(err, devtunnelHost = false) {
1950
1993
  if (isAbortLike$1(err)) return new FleetError({
1951
1994
  code: "TIMEOUT",
@@ -2031,20 +2074,11 @@ var FleetRegistry = class {
2031
2074
  }
2032
2075
  }
2033
2076
  async resolveInstance(arg) {
2034
- const instances = await this.instancesWithTokens();
2035
- const wanted = typeof arg === "string" ? arg.trim() : "";
2036
- if (wanted) {
2037
- const byId = instances.find((instance) => instance.id === wanted);
2038
- if (byId) return resolvedInstance(byId);
2039
- const labelMatches = instances.filter((instance) => instance.label.toLocaleLowerCase() === wanted.toLocaleLowerCase());
2040
- if (labelMatches.length > 1) throw new FleetRegistryError("AMBIGUOUS_LABEL", `fleet instance label ${JSON.stringify(wanted)} matches ${labelMatches.length} instances; use an id`);
2041
- if (labelMatches.length === 1) return resolvedInstance(labelMatches[0]);
2042
- throw new FleetRegistryError("INSTANCE_NOT_FOUND", `fleet instance ${JSON.stringify(wanted)} was not found`);
2043
- }
2044
- const defaultInstance = instances.find((instance) => instance.default === true);
2045
- if (defaultInstance) return resolvedInstance(defaultInstance);
2046
- if (instances.length === 1) return resolvedInstance(instances[0]);
2047
- throw new FleetRegistryError("INSTANCE_REQUIRED", instances.length === 0 ? "fleet instance is required; registry is empty" : "fleet instance is required; specify an instance id or label");
2077
+ return selectInstance((await this.instancesWithTokens()).map(resolvedInstance), arg);
2078
+ }
2079
+ /** All static instances, fully resolved (with tokens). Used to build the merged static∪discovered set. */
2080
+ async resolveAll() {
2081
+ return (await this.instancesWithTokens()).map(resolvedInstance);
2048
2082
  }
2049
2083
  async listInstances() {
2050
2084
  return (await this.instancesWithTokens()).map((instance) => ({
@@ -2060,6 +2094,27 @@ var FleetRegistry = class {
2060
2094
  return this.loaded;
2061
2095
  }
2062
2096
  };
2097
+ /**
2098
+ * Pure instance selection over an already-resolved set: id (exact) → label
2099
+ * (case-insensitive, ambiguity-checked) → default → single. Shared by the static
2100
+ * registry and the merged static∪discovered registry so both apply identical
2101
+ * matching + error semantics.
2102
+ */
2103
+ function selectInstance(instances, arg) {
2104
+ const wanted = typeof arg === "string" ? arg.trim() : "";
2105
+ if (wanted) {
2106
+ const byId = instances.find((instance) => instance.id === wanted);
2107
+ if (byId) return byId;
2108
+ const labelMatches = instances.filter((instance) => instance.label.toLocaleLowerCase() === wanted.toLocaleLowerCase());
2109
+ if (labelMatches.length > 1) throw new FleetRegistryError("AMBIGUOUS_LABEL", `fleet instance label ${JSON.stringify(wanted)} matches ${labelMatches.length} instances; use an id`);
2110
+ if (labelMatches.length === 1) return labelMatches[0];
2111
+ throw new FleetRegistryError("INSTANCE_NOT_FOUND", `fleet instance ${JSON.stringify(wanted)} was not found`);
2112
+ }
2113
+ const defaultInstance = instances.find((instance) => instance.default === true);
2114
+ if (defaultInstance) return defaultInstance;
2115
+ if (instances.length === 1) return instances[0];
2116
+ throw new FleetRegistryError("INSTANCE_REQUIRED", instances.length === 0 ? "fleet instance is required; registry is empty" : "fleet instance is required; specify an instance id or label");
2117
+ }
2063
2118
  function normalizeConfig(config) {
2064
2119
  if (!isObject(config)) throw new FleetRegistryError("INVALID_CONFIG", "fleet registry config must be an object");
2065
2120
  const instances = config.instances ?? [];
@@ -2181,6 +2236,11 @@ function resolvedInstance(instance) {
2181
2236
  label: instance.label,
2182
2237
  url: instance.url,
2183
2238
  token: instance.token,
2239
+ auth: {
2240
+ type: "bearer",
2241
+ token: instance.token
2242
+ },
2243
+ default: instance.default,
2184
2244
  allowExec: instance.allowExec,
2185
2245
  tunnelId: instance.tunnelId,
2186
2246
  tunnelToken: instance.tunnelToken,
@@ -2194,6 +2254,158 @@ function isNodeErrorCode(err, code) {
2194
2254
  return isObject(err) && err.code === code;
2195
2255
  }
2196
2256
 
2257
+ //#endregion
2258
+ //#region src/lib/fleet/discovery.ts
2259
+ /**
2260
+ * Mesh fleet discovery: read the local ai-or-die instance's `mesh/peers.json`
2261
+ * (written by its MeshManager from the sidecar's tailnet `Status()`) off disk and
2262
+ * synthesize token-less, mesh-auth fleet instances. No `AIORDIE_*` env, no HTTP,
2263
+ * no token — filesystem permissions are the gate. Discovered peers are driven
2264
+ * over the tailnet with NO Authorization header; each peer's own sidecar injects
2265
+ * the bearer (ACL-gated by `tag:aiordie`). See docs / the fleet plan.
2266
+ */
2267
+ const DISCOVERY_CACHE_TTL_MS = 5e3;
2268
+ const PEERS_JSON_MAX_BYTES = 256 * 1024;
2269
+ /** The ai-or-die app data dir (NOT github-router's) — mirrors MeshManager's `base`. */
2270
+ function aiordieAppDir() {
2271
+ if (process.platform === "win32") {
2272
+ const localApp = process.env.LOCALAPPDATA || nodePath.join(os.homedir(), "AppData", "Local");
2273
+ return nodePath.join(localApp, "ai-or-die");
2274
+ }
2275
+ return nodePath.join(os.homedir(), ".ai-or-die");
2276
+ }
2277
+ function meshPeersFilePath() {
2278
+ const override = process.env.GH_ROUTER_FLEET_PEERS_FILE;
2279
+ if (override && override.trim() !== "") return override.trim();
2280
+ return nodePath.join(aiordieAppDir(), "mesh", "peers.json");
2281
+ }
2282
+ function meshDiscoveryDisabled() {
2283
+ return process.env.GH_ROUTER_FLEET_DISCOVERY === "0";
2284
+ }
2285
+ const TS_NET_DNS_RE = /^(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+ts\.net$/;
2286
+ function validDnsName(raw) {
2287
+ if (typeof raw !== "string") return void 0;
2288
+ const name = raw.trim().replace(/\.$/, "").toLowerCase();
2289
+ if (name.length === 0 || name.length > 253) return void 0;
2290
+ if (!TS_NET_DNS_RE.test(name)) return void 0;
2291
+ return name;
2292
+ }
2293
+ function nonEmptyString(raw) {
2294
+ return typeof raw === "string" && raw.trim() !== "" ? raw.trim() : void 0;
2295
+ }
2296
+ /**
2297
+ * Read + validate the discovery file into resolved mesh instances. Pure I/O +
2298
+ * validation; never throws — a missing/unreadable/oversized/malformed file
2299
+ * yields `[]` (discovery is best-effort and must never break static fleet use).
2300
+ */
2301
+ async function readMeshPeers(readFileFn = (p) => fs.readFile(p, "utf8")) {
2302
+ if (meshDiscoveryDisabled()) return [];
2303
+ let raw;
2304
+ try {
2305
+ raw = await readFileFn(meshPeersFilePath());
2306
+ } catch {
2307
+ return [];
2308
+ }
2309
+ if (Buffer.byteLength(raw, "utf8") > PEERS_JSON_MAX_BYTES) return [];
2310
+ let parsed;
2311
+ try {
2312
+ parsed = JSON.parse(raw);
2313
+ } catch {
2314
+ return [];
2315
+ }
2316
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return [];
2317
+ if (!Array.isArray(parsed.peers)) return [];
2318
+ const selfDnsName = validDnsName(parsed.self?.dnsName);
2319
+ if (selfDnsName === void 0) return [];
2320
+ const tailnetSuffix = selfDnsName.slice(selfDnsName.indexOf(".") + 1);
2321
+ const seen = /* @__PURE__ */ new Set();
2322
+ const out = [];
2323
+ for (const peer of parsed.peers) {
2324
+ if (typeof peer !== "object" || peer === null) continue;
2325
+ const record = peer;
2326
+ const dnsName = validDnsName(record.dnsName);
2327
+ if (dnsName === void 0) continue;
2328
+ if (dnsName === selfDnsName) continue;
2329
+ if (!dnsName.endsWith(`.${tailnetSuffix}`)) continue;
2330
+ if (seen.has(dnsName)) continue;
2331
+ seen.add(dnsName);
2332
+ const hostname = nonEmptyString(record.hostname);
2333
+ out.push({
2334
+ id: dnsName,
2335
+ label: hostname ?? dnsName,
2336
+ url: `https://${dnsName}`,
2337
+ token: "",
2338
+ auth: { type: "mesh" }
2339
+ });
2340
+ }
2341
+ return out;
2342
+ }
2343
+ function toInfo(instance) {
2344
+ return {
2345
+ id: instance.id,
2346
+ label: instance.label,
2347
+ url: instance.url
2348
+ };
2349
+ }
2350
+ /**
2351
+ * Registry that merges a static `fleet.json` registry with mesh discovery. Static
2352
+ * ALWAYS wins on an id collision (a discovered peer sharing a static id is dropped
2353
+ * — never overwrites a configured instance, never merges auth across sources).
2354
+ * Discovery is cached briefly so a fan-out doesn't re-read the file per call, and a
2355
+ * failed discovery read leaves the static set untouched.
2356
+ */
2357
+ var MergedFleetRegistry = class {
2358
+ staticRegistry;
2359
+ discover;
2360
+ cache;
2361
+ inflight;
2362
+ ttlMs;
2363
+ now;
2364
+ constructor(options = {}) {
2365
+ this.staticRegistry = options.staticRegistry ?? new FleetRegistry();
2366
+ this.discover = options.discover ?? (() => readMeshPeers());
2367
+ this.ttlMs = options.ttlMs ?? DISCOVERY_CACHE_TTL_MS;
2368
+ this.now = options.now ?? (() => Date.now());
2369
+ }
2370
+ async discoverCached() {
2371
+ const now = this.now();
2372
+ if (this.cache && now - this.cache.at < this.ttlMs) return this.cache.peers;
2373
+ if (this.inflight) return this.inflight;
2374
+ this.inflight = (async () => {
2375
+ let peers;
2376
+ try {
2377
+ peers = await this.discover();
2378
+ } catch {
2379
+ peers = [];
2380
+ }
2381
+ this.cache = {
2382
+ at: this.now(),
2383
+ peers
2384
+ };
2385
+ return peers;
2386
+ })().finally(() => {
2387
+ this.inflight = void 0;
2388
+ });
2389
+ return this.inflight;
2390
+ }
2391
+ /** Static∪discovered with static winning on id collision. */
2392
+ async union() {
2393
+ const staticResolved = await this.staticRegistry.resolveAll();
2394
+ const staticIds = new Set(staticResolved.map((instance) => instance.id));
2395
+ const discovered = (await this.discoverCached()).filter((peer) => !staticIds.has(peer.id));
2396
+ return [...staticResolved, ...discovered];
2397
+ }
2398
+ async resolveInstance(arg) {
2399
+ return selectInstance(await this.union(), arg);
2400
+ }
2401
+ async listInstances() {
2402
+ const staticInfos = await this.staticRegistry.listInstances();
2403
+ const staticIds = new Set(staticInfos.map((info) => info.id));
2404
+ const discovered = (await this.discoverCached()).filter((peer) => !staticIds.has(peer.id));
2405
+ return [...staticInfos, ...discovered.map(toInfo)];
2406
+ }
2407
+ };
2408
+
2197
2409
  //#endregion
2198
2410
  //#region src/lib/fleet/tools.ts
2199
2411
  const FLEET_GROUP = "fleet";
@@ -2229,16 +2441,16 @@ function createFleetTools(options = {}) {
2229
2441
  const awaitTurnDeadlineSlackMs = nonNegativeNumberOrDefault(options.awaitTurnDeadlineSlackMs, AWAIT_TURN_TIMEOUT_SLACK_MS);
2230
2442
  function getRegistry() {
2231
2443
  if (registry) return registry;
2232
- defaultRegistry ??= new FleetRegistry();
2444
+ defaultRegistry ??= new MergedFleetRegistry();
2233
2445
  return defaultRegistry;
2234
2446
  }
2235
2447
  function clientFor(instance) {
2236
- const key = `${instance.id}\0${instance.url}\0${instance.token}\0${instance.tunnelId ?? ""}\0${instance.tunnelToken ?? ""}\0${instance.insecureTLS === true ? "1" : "0"}`;
2448
+ const key = `${instance.id}\0${instance.url}\0${instance.auth.type}\0${instance.token}\0${instance.tunnelId ?? ""}\0${instance.tunnelToken ?? ""}\0${instance.insecureTLS === true ? "1" : "0"}`;
2237
2449
  const existing = clients.get(key);
2238
2450
  if (existing) return existing;
2239
2451
  const created = options.createClient ? options.createClient(instance) : new FleetClient({
2240
2452
  url: instance.url,
2241
- token: instance.token,
2453
+ auth: instance.auth,
2242
2454
  fetchFn: options.fetchFn,
2243
2455
  insecureTLS: instance.insecureTLS,
2244
2456
  ...tunnelClientOptions(instance, tunnelProvider)
@@ -2393,18 +2605,14 @@ function createFleetTools(options = {}) {
2393
2605
  sessionId: stringProp("Global session id in the form instanceId:localSessionId."),
2394
2606
  instance: stringProp("Optional instance id/label; when supplied it must agree with sessionId."),
2395
2607
  message: stringProp("Message text to deliver to the session."),
2396
- idempotencyKey: stringProp("Caller-generated idempotency key. Reuse the same key on retry; the upstream dedupes so a retry never re-types."),
2608
+ idempotencyKey: stringProp("Optional caller idempotency key; AUTO-GENERATED when omitted, so you normally never pass it. Supply your OWN stable key only when you will retry the SAME send and need the upstream to dedupe it."),
2397
2609
  awaitMs: numberProp("Optional best-effort confirmation wait (ms) — NOT a deadline. Prefer awaitMs:0 plus await_turn; a turn that outruns awaitMs returns confirmationPending, not an error.")
2398
- }, [
2399
- "sessionId",
2400
- "message",
2401
- "idempotencyKey"
2402
- ]), async (args, signal) => {
2610
+ }, ["sessionId", "message"]), async (args, signal) => {
2403
2611
  const { instance, localId, globalId } = await resolveSession(args);
2404
2612
  const awaitMs = optionalNumber(args, "awaitMs");
2405
2613
  const response = await clientFor(instance).sendMessage(localId, {
2406
2614
  message: requiredString(args, "message"),
2407
- idempotencyKey: requiredString(args, "idempotencyKey"),
2615
+ idempotencyKey: optionalString(args, "idempotencyKey") ?? randomUUID(),
2408
2616
  ...awaitMs === void 0 ? {} : { awaitMs }
2409
2617
  }, signal);
2410
2618
  const delivered = !(response.delivered === false || response.delivery?.status === "failed" || response.delivery?.status === "error");
@@ -2428,18 +2636,14 @@ function createFleetTools(options = {}) {
2428
2636
  sessionId: stringProp("Global session id in the form instanceId:localSessionId."),
2429
2637
  instance: stringProp("Optional instance id/label; when supplied it must agree with sessionId."),
2430
2638
  keys: stringProp("Key sequence to send."),
2431
- idempotencyKey: stringProp("Caller-generated idempotency key."),
2639
+ idempotencyKey: stringProp("Optional caller idempotency key; auto-generated when omitted."),
2432
2640
  raw: booleanProp("Pass keys through as raw input when the instance supports it.")
2433
- }, [
2434
- "sessionId",
2435
- "keys",
2436
- "idempotencyKey"
2437
- ]), async (args, signal) => {
2641
+ }, ["sessionId", "keys"]), async (args, signal) => {
2438
2642
  const { instance, localId, globalId } = await resolveSession(args);
2439
2643
  const raw = optionalBoolean(args, "raw");
2440
2644
  const response = await clientFor(instance).sendKeys(localId, {
2441
2645
  keys: requiredString(args, "keys"),
2442
- idempotencyKey: requiredString(args, "idempotencyKey"),
2646
+ idempotencyKey: optionalString(args, "idempotencyKey") ?? randomUUID(),
2443
2647
  ...raw === void 0 ? {} : { raw }
2444
2648
  }, signal);
2445
2649
  return ok({
@@ -2454,14 +2658,14 @@ function createFleetTools(options = {}) {
2454
2658
  choice: stringProp("Named or numbered choice to select."),
2455
2659
  optionValue: stringProp("Exact option value to select."),
2456
2660
  keys: stringProp("Explicit key override to send instead of a mapped choice."),
2457
- idempotencyKey: stringProp("Caller-generated idempotency key.")
2458
- }, ["sessionId", "idempotencyKey"]), async (args, signal) => {
2661
+ idempotencyKey: stringProp("Optional caller idempotency key; auto-generated when omitted.")
2662
+ }, ["sessionId"]), async (args, signal) => {
2459
2663
  const { instance, localId, globalId } = await resolveSession(args);
2460
2664
  const input = definedObject({
2461
2665
  choice: optionalString(args, "choice"),
2462
2666
  optionValue: optionalString(args, "optionValue"),
2463
2667
  keys: optionalString(args, "keys"),
2464
- idempotencyKey: requiredString(args, "idempotencyKey")
2668
+ idempotencyKey: optionalString(args, "idempotencyKey") ?? randomUUID()
2465
2669
  });
2466
2670
  const response = await clientFor(instance).respond(localId, input, signal);
2467
2671
  return ok({
@@ -2475,19 +2679,15 @@ function createFleetTools(options = {}) {
2475
2679
  agent: stringProp("Agent/runtime to create on the instance."),
2476
2680
  name: stringProp("Optional display name for the session."),
2477
2681
  workingDir: stringProp("Optional working directory on the remote instance."),
2478
- idempotencyKey: stringProp("Caller-generated idempotency key."),
2682
+ idempotencyKey: stringProp("Optional caller idempotency key; auto-generated when omitted."),
2479
2683
  start: booleanProp("Whether the remote instance should start the session immediately."),
2480
2684
  readyTimeoutMs: numberProp("F17: bounded ms to wait for the agent to become driveable before returning. The response carries ready/bound/blocker."),
2481
2685
  permissionMode: stringProp("F10 (claude only): permission mode the launched agent starts in — one of plan | acceptEdits | default | bypassPermissions. Rejected with BAD_REQUEST if unknown or if agentArgs also sets it."),
2482
2686
  agentArgs: arrayProp("F10 (claude only): extra launcher args appended after the github-router prefix. Must NOT include --permission-mode or --dangerously-skip-permissions (use permissionMode) — rejected with BAD_REQUEST.")
2483
- }, [
2484
- "instance",
2485
- "agent",
2486
- "idempotencyKey"
2487
- ]), async (args, signal) => {
2687
+ }, ["instance", "agent"]), async (args, signal) => {
2488
2688
  const instance = await resolve(requiredString(args, "instance"));
2489
2689
  const agent = requiredString(args, "agent");
2490
- const idempotencyKey = requiredString(args, "idempotencyKey");
2690
+ const idempotencyKey = optionalString(args, "idempotencyKey") ?? randomUUID();
2491
2691
  const permissionMode = optionalString(args, "permissionMode");
2492
2692
  const agentArgs = optionalStringArray(args, "agentArgs");
2493
2693
  if (permissionMode !== void 0) await assertCapability(instance, "permission_mode", "permissionMode", signal);
@@ -2512,11 +2712,11 @@ function createFleetTools(options = {}) {
2512
2712
  tool$1("stop_session", "Stop a fleet session.", objectSchema({
2513
2713
  sessionId: stringProp("Global session id in the form instanceId:localSessionId."),
2514
2714
  instance: stringProp("Optional instance id/label; when supplied it must agree with sessionId."),
2515
- idempotencyKey: stringProp("Caller-generated idempotency key."),
2715
+ idempotencyKey: stringProp("Optional caller idempotency key; auto-generated when omitted."),
2516
2716
  mode: stringProp("Optional stop mode understood by the remote instance.")
2517
- }, ["sessionId", "idempotencyKey"]), async (args, signal) => {
2717
+ }, ["sessionId"]), async (args, signal) => {
2518
2718
  const { instance, localId, globalId } = await resolveSession(args);
2519
- const idempotencyKey = requiredString(args, "idempotencyKey");
2719
+ const idempotencyKey = optionalString(args, "idempotencyKey") ?? randomUUID();
2520
2720
  const response = await clientFor(instance).stopSession(localId, definedObject({
2521
2721
  mode: optionalString(args, "mode"),
2522
2722
  idempotencyKey
@@ -8420,7 +8620,7 @@ function logAudit$1(record) {
8420
8620
  try {
8421
8621
  const fs$2 = await import("node:fs/promises");
8422
8622
  const path$1 = await import("node:path");
8423
- const { PATHS: PATHS$1 } = await import("./paths-B-ATynF7.js");
8623
+ const { PATHS: PATHS$1 } = await import("./paths-Bljq3UJC.js");
8424
8624
  const dir = path$1.join(PATHS$1.APP_DIR, "browser-mcp");
8425
8625
  await fs$2.mkdir(dir, { recursive: true });
8426
8626
  const line = JSON.stringify({
@@ -20699,7 +20899,7 @@ function entryHasCommand(entry, command) {
20699
20899
  * other entries. Returns a new object (never mutates the input). Re-running the
20700
20900
  * launcher with the same command+event does not duplicate the hook.
20701
20901
  */
20702
- function mergeStopHookIntoSettings(existing, command, event = "Stop", timeoutSec) {
20902
+ function mergeStopHookIntoSettings(existing, command, event = "Stop", timeoutSec, matcher) {
20703
20903
  const base = existing && typeof existing === "object" ? { ...existing } : {};
20704
20904
  const hooks = base.hooks && typeof base.hooks === "object" ? { ...base.hooks } : {};
20705
20905
  const arr = Array.isArray(hooks[event]) ? [...hooks[event]] : [];
@@ -20709,7 +20909,10 @@ function mergeStopHookIntoSettings(existing, command, event = "Stop", timeoutSec
20709
20909
  command
20710
20910
  };
20711
20911
  if (typeof timeoutSec === "number" && Number.isFinite(timeoutSec) && timeoutSec > 0) hook.timeout = timeoutSec;
20712
- arr.push({ hooks: [hook] });
20912
+ arr.push(matcher ? {
20913
+ matcher,
20914
+ hooks: [hook]
20915
+ } : { hooks: [hook] });
20713
20916
  }
20714
20917
  hooks[event] = arr;
20715
20918
  base.hooks = hooks;
@@ -20915,6 +21118,12 @@ function buildSessionBindHookCommand(execPath, scriptPath, outPath) {
20915
21118
  const q = (s) => `"${s}"`;
20916
21119
  return `${scriptPath && scriptPath !== execPath ? `${q(execPath)} ${q(scriptPath)}` : q(execPath)} internal-session-bind --out ${q(outPath)}`;
20917
21120
  }
21121
+ /** Command for the `internal-artifact-open` hook (no args — token comes from the
21122
+ * mirror creds file, plan from the plans dir; nothing secret in argv). */
21123
+ function buildArtifactOpenHookCommand(execPath, scriptPath) {
21124
+ const q = (s) => `"${s}"`;
21125
+ return `${scriptPath && scriptPath !== execPath ? `${q(execPath)} ${q(scriptPath)}` : q(execPath)} internal-artifact-open`;
21126
+ }
20918
21127
  /**
20919
21128
  * Read-merge-atomic-write the Stop hook into a Claude Code `settings.json` file
20920
21129
  * (the mirrored one). A MISSING file (ENOENT) starts from `{}`; any OTHER read or
@@ -20923,7 +21132,7 @@ function buildSessionBindHookCommand(execPath, scriptPath, outPath) {
20923
21132
  * other setting, is idempotent, and uses temp+rename so Claude Code's mtime
20924
21133
  * watcher never sees a half-written file. Returns the merged object.
20925
21134
  */
20926
- async function injectStopHookIntoSettingsFile(settingsPath, command, event = "Stop", timeoutSec) {
21135
+ async function injectStopHookIntoSettingsFile(settingsPath, command, event = "Stop", timeoutSec, matcher) {
20927
21136
  let existing = {};
20928
21137
  let raw;
20929
21138
  try {
@@ -20937,7 +21146,7 @@ async function injectStopHookIntoSettingsFile(settingsPath, command, event = "St
20937
21146
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) existing = parsed;
20938
21147
  else throw new Error(`settings.json at ${settingsPath} is not a JSON object; refusing to overwrite`);
20939
21148
  }
20940
- const merged = mergeStopHookIntoSettings(existing, command, event, timeoutSec);
21149
+ const merged = mergeStopHookIntoSettings(existing, command, event, timeoutSec, matcher);
20941
21150
  const tmp = `${settingsPath}.${process.pid}.tmp`;
20942
21151
  await promises.writeFile(tmp, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
20943
21152
  await promises.rename(tmp, settingsPath);
@@ -22705,5 +22914,5 @@ async function runStandInToolCall(args, signal) {
22705
22914
  }
22706
22915
 
22707
22916
  //#endregion
22708
- export { relayAnthropicStream as $, IMPLEMENT_DEFAULT_MODEL as A, withInstallLock as At, vscodeRipgrepPath as B, resolveModel as Bt, stopGateEnabledForRepo as C, DEFAULT_CODEX_MODEL_FALLBACKS as Ct, liveExec as D, generateRandomPort as Dt, resolveSealedGate as E, UPSTREAM_INACTIVITY_TIMEOUT_MS as Et, withNoOutputRetry as F, cacheModels as Ft, ADVISOR_TOOL_INSTRUCTIONS as G, forwardError as Gt, assetFor as H, getModels as Ht, availableToolCommands as I, cacheVSCodeVersion as It, isAdvisorRequested as J, copilotHeaders as Jt, buildAdvisorStream as K, GITHUB_API_BASE_URL as Kt, buildToolbeltAwareness as L, filterBetaHeader as Lt, REVIEW_DEFAULT_MODEL as M, setupGitHubToken as Mt, appendPlanReminder as N, tryRefreshAndRetry as Nt, BROWSE_DEFAULT_MODEL as O, pickClaudeDefault as Ot, runWorkerAgent as P, cacheCopilotVersion as Pt, readIteratorWithTimeout as Q, toolbeltEnabled as R, isNullish as Rt, repoRoot as S, DEFAULT_CODEX_MODEL as St, trustRepo as T, UPSTREAM_FETCH_TIMEOUT_MS as Tt, searchWeb as U, fetchWithTransientRetry as Ut, TOOLBELT_TOOLS$1 as V, sleep as Vt, ADVISOR_INTERNAL_TOOL_NAME as W, HTTPError as Wt, isControllerClosedError as X, state as Xt, buildOpenAIErrorEvent as Y, githubHeaders as Yt, logStreamError as Z, fileFindingsStore as _, extractTarGzMember as _t, buildPeerAwarenessSnippet as a, workerToolsEnabled as at, isSubagentContext as b, toolbeltPathOverride as bt, buildStopHookCommand as c, getTokenCount as ct, fileBlockBudget as d, MAX_RESPONSE_BODY_BYTES as dt, handleMcpDelete as et, injectStopHookIntoSettingsFile as f, readResponseBodyCapped as ft, fileBaselineStore as g, provisionAndIndexColbert as gt, stopReviewEnabled as h, hasSupportedBrowserInstalled as ht, buildAgentPrompt as i, standInToolEnabled as it, PLAN_DEFAULT_MODEL as j, setupCopilotToken as jt, DEFAULT_MODEL as k, getPackageVersion as kt, captureLaunchBaseline as l, createResponses as lt, stopGateId as m, provisionBrowserAssets as mt, MCP_GROUPS as n, browserToolsEnabled as nt, personasFor as o, countTokens as ot, launchBaselineKey as p, parseJsonOrDiagnose as pt, injectAdvisorTool as q, copilotBaseUrl as qt, assertMcpToolSurfaceConsistent as r, fleetToolsEnabled as rt, buildSessionBindHookCommand as s, createMessages as st, GROUP_META as t, handleMcpPost as tt, decideStopHook as u, createChatCompletions as ut, fileLastPromptStore as v, extractZipMember as vt, stopReviewStateDir as w, DEFAULT_PORT as wt, repoFingerprint as x, DEFAULT_CLAUDE_MODEL_FALLBACKS as xt, fileReviewDebounce as y, collapsePathKeys as yt, toolbeltSkipSet as z, resolveCodexModel as zt };
22709
- //# sourceMappingURL=peer-mcp-personas-CH2gmdPN.js.map
22917
+ export { readIteratorWithTimeout as $, state as $t, DEFAULT_MODEL as A, generateRandomPort as At, toolbeltSkipSet as B, filterBetaHeader as Bt, repoRoot as C, toolbeltPathOverride as Ct, resolveSealedGate as D, DEFAULT_PORT as Dt, trustRepo as E, DEFAULT_CODEX_MODEL_FALLBACKS as Et, runWorkerAgent as F, setupGitHubToken as Ft, ADVISOR_INTERNAL_TOOL_NAME as G, getModels as Gt, TOOLBELT_TOOLS$1 as H, resolveCodexModel as Ht, withNoOutputRetry as I, tryRefreshAndRetry as It, injectAdvisorTool as J, forwardError as Jt, ADVISOR_TOOL_INSTRUCTIONS as K, fetchWithTransientRetry as Kt, availableToolCommands as L, cacheCopilotVersion as Lt, PLAN_DEFAULT_MODEL as M, getPackageVersion as Mt, REVIEW_DEFAULT_MODEL as N, withInstallLock as Nt, liveExec as O, UPSTREAM_FETCH_TIMEOUT_MS as Ot, appendPlanReminder as P, setupCopilotToken as Pt, logStreamError as Q, githubHeaders as Qt, buildToolbeltAwareness as R, cacheModels as Rt, repoFingerprint as S, collapsePathKeys as St, stopReviewStateDir as T, DEFAULT_CODEX_MODEL as Tt, assetFor as U, resolveModel as Ut, vscodeRipgrepPath as V, isNullish as Vt, searchWeb as W, sleep as Wt, buildOpenAIErrorEvent as X, copilotBaseUrl as Xt, isAdvisorRequested as Y, GITHUB_API_BASE_URL as Yt, isControllerClosedError as Z, copilotHeaders as Zt, fileBaselineStore as _, provisionAndIndexColbert as _t, buildPeerAwarenessSnippet as a, standInToolEnabled as at, fileReviewDebounce as b, shouldUseInsecureTls as bt, buildSessionBindHookCommand as c, createMessages as ct, decideStopHook as d, createChatCompletions as dt, relayAnthropicStream as et, fileBlockBudget as f, MAX_RESPONSE_BODY_BYTES as ft, stopReviewEnabled as g, hasSupportedBrowserInstalled as gt, stopGateId as h, provisionBrowserAssets as ht, buildAgentPrompt as i, fleetToolsEnabled as it, IMPLEMENT_DEFAULT_MODEL as j, pickClaudeDefault as jt, BROWSE_DEFAULT_MODEL as k, UPSTREAM_INACTIVITY_TIMEOUT_MS as kt, buildStopHookCommand as l, getTokenCount as lt, launchBaselineKey as m, parseJsonOrDiagnose as mt, MCP_GROUPS as n, handleMcpPost as nt, personasFor as o, workerToolsEnabled as ot, injectStopHookIntoSettingsFile as p, readResponseBodyCapped as pt, buildAdvisorStream as q, HTTPError as qt, assertMcpToolSurfaceConsistent as r, browserToolsEnabled as rt, buildArtifactOpenHookCommand as s, countTokens as st, GROUP_META as t, handleMcpDelete as tt, captureLaunchBaseline as u, createResponses as ut, fileFindingsStore as v, extractTarGzMember as vt, stopGateEnabledForRepo as w, DEFAULT_CLAUDE_MODEL_FALLBACKS as wt, isSubagentContext as x, ArtifactClient as xt, fileLastPromptStore as y, extractZipMember as yt, toolbeltEnabled as z, cacheVSCodeVersion as zt };
22918
+ //# sourceMappingURL=peer-mcp-personas-Dmx4S0_5.js.map