github-router 0.3.261 → 0.3.269

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.
Files changed (78) hide show
  1. package/dist/{attribution-settings-Bl0HHJQF.js → attribution-settings-B1txv_Lk.js} +4 -4
  2. package/dist/{attribution-settings-Bl0HHJQF.js.map → attribution-settings-B1txv_Lk.js.map} +1 -1
  3. package/dist/{auth-I8k2OOvA.js → auth-r-my7efo.js} +3 -3
  4. package/dist/{auth-I8k2OOvA.js.map → auth-r-my7efo.js.map} +1 -1
  5. package/dist/browser-bridge/index.js +67 -27
  6. package/dist/browser-ext/manifest.json +1 -1
  7. package/dist/{check-usage-SOn7-cWc.js → check-usage-CZr2ZkG0.js} +4 -4
  8. package/dist/{check-usage-SOn7-cWc.js.map → check-usage-CZr2ZkG0.js.map} +1 -1
  9. package/dist/{claude-CEaI47j7.js → claude-BjNY0J6e.js} +15 -15
  10. package/dist/{claude-CEaI47j7.js.map → claude-BjNY0J6e.js.map} +1 -1
  11. package/dist/{codex-C6Eyz3EV.js → codex-CoRWpkHL.js} +5 -6
  12. package/dist/{codex-C6Eyz3EV.js.map → codex-CoRWpkHL.js.map} +1 -1
  13. package/dist/{debug-vshv4nIv.js → debug-CuuxCk_J.js} +2 -2
  14. package/dist/{debug-vshv4nIv.js.map → debug-CuuxCk_J.js.map} +1 -1
  15. package/dist/engine-DpVC7HRL.js +2 -0
  16. package/dist/{exec-DTO2oin0.js → exec-y8C_MU8A.js} +89 -18
  17. package/dist/exec-y8C_MU8A.js.map +1 -0
  18. package/dist/{gate-discovery-BnPhH_AC.js → gate-discovery-D-7oua8b.js} +6 -6
  19. package/dist/{gate-discovery-BnPhH_AC.js.map → gate-discovery-D-7oua8b.js.map} +1 -1
  20. package/dist/{get-copilot-usage-CYpAsYrM.js → get-copilot-usage-CFEpeA7E.js} +2 -2
  21. package/dist/{get-copilot-usage-CYpAsYrM.js.map → get-copilot-usage-CFEpeA7E.js.map} +1 -1
  22. package/dist/{internal-artifact-open-CZGO831M.js → internal-artifact-open-CDqPfegE.js} +2 -2
  23. package/dist/{internal-artifact-open-CZGO831M.js.map → internal-artifact-open-CDqPfegE.js.map} +1 -1
  24. package/dist/{internal-plan-review-6gkcOAjT.js → internal-plan-review-CH9qTHoZ.js} +3 -3
  25. package/dist/{internal-plan-review-6gkcOAjT.js.map → internal-plan-review-CH9qTHoZ.js.map} +1 -1
  26. package/dist/{internal-prompt-submit-CSJR24-y.js → internal-prompt-submit-Bve1s4z_.js} +5 -5
  27. package/dist/{internal-prompt-submit-CSJR24-y.js.map → internal-prompt-submit-Bve1s4z_.js.map} +1 -1
  28. package/dist/{internal-session-bind-DtSBTkQe.js → internal-session-bind-Cc8bMXHK.js} +2 -2
  29. package/dist/{internal-session-bind-DtSBTkQe.js.map → internal-session-bind-Cc8bMXHK.js.map} +1 -1
  30. package/dist/{internal-stop-hook-CiflCJO-.js → internal-stop-hook-Db5o9RcZ.js} +6 -6
  31. package/dist/{internal-stop-hook-CiflCJO-.js.map → internal-stop-hook-Db5o9RcZ.js.map} +1 -1
  32. package/dist/{internal-stop-review-_1Mqz7eT.js → internal-stop-review-CWE7_QVv.js} +2 -2
  33. package/dist/{internal-stop-review-_1Mqz7eT.js.map → internal-stop-review-CWE7_QVv.js.map} +1 -1
  34. package/dist/{lifecycle-DjC9tC1A.js → lifecycle-0XrazT05.js} +2 -2
  35. package/dist/{lifecycle-DjC9tC1A.js.map → lifecycle-0XrazT05.js.map} +1 -1
  36. package/dist/lifecycle-2M0IThRr.js +2 -0
  37. package/dist/{lifecycle-Bd1Fyf4B.js → lifecycle-AgNOtFRv.js} +3 -3
  38. package/dist/{lifecycle-Bd1Fyf4B.js.map → lifecycle-AgNOtFRv.js.map} +1 -1
  39. package/dist/lifecycle-C8kFsAj-.js +2 -0
  40. package/dist/main.js +14 -14
  41. package/dist/{models-uQgsKNNC.js → models-CJLt4XjP.js} +35 -30
  42. package/dist/models-CJLt4XjP.js.map +1 -0
  43. package/dist/{orchestration-BiZviyam.js → orchestration-B_Q3ncXI.js} +3 -3
  44. package/dist/{orchestration-BiZviyam.js.map → orchestration-B_Q3ncXI.js.map} +1 -1
  45. package/dist/paths-BbHtQAOs.js +2 -0
  46. package/dist/{paths-BXrbboAS.js → paths-Bg1_DWhi.js} +3 -3
  47. package/dist/{paths-BXrbboAS.js.map → paths-Bg1_DWhi.js.map} +1 -1
  48. package/dist/{peer-mcp-personas-V4AXhm9B.js → peer-mcp-personas-DSbBlgFt.js} +119 -45
  49. package/dist/peer-mcp-personas-DSbBlgFt.js.map +1 -0
  50. package/dist/{plan-review-hook-Bo4y1r3j.js → plan-review-hook-BKOcW5s4.js} +3 -3
  51. package/dist/{plan-review-hook-Bo4y1r3j.js.map → plan-review-hook-BKOcW5s4.js.map} +1 -1
  52. package/dist/{prompt-submit-hook-BFRFLSk5.js → prompt-submit-hook-F7Zih_au.js} +2 -2
  53. package/dist/{prompt-submit-hook-BFRFLSk5.js.map → prompt-submit-hook-F7Zih_au.js.map} +1 -1
  54. package/dist/{provision-BLwsOFCI.js → provision-CA-xwnFu.js} +5 -5
  55. package/dist/{provision-BLwsOFCI.js.map → provision-CA-xwnFu.js.map} +1 -1
  56. package/dist/{serve-De-kqWXM.js → serve-DHny4ewF.js} +20 -15
  57. package/dist/{serve-De-kqWXM.js.map → serve-DHny4ewF.js.map} +1 -1
  58. package/dist/{server-setup-BeeT8m-w.js → server-setup-Dd293T1O.js} +479 -75
  59. package/dist/server-setup-Dd293T1O.js.map +1 -0
  60. package/dist/{start-DEC0M8UH.js → start-P2rmHBkL.js} +3 -3
  61. package/dist/{start-DEC0M8UH.js.map → start-P2rmHBkL.js.map} +1 -1
  62. package/dist/{stop-gate-hook-DoYD9Wt8.js → stop-gate-hook-BqpumJtl.js} +12 -7
  63. package/dist/{stop-gate-hook-DoYD9Wt8.js.map → stop-gate-hook-BqpumJtl.js.map} +1 -1
  64. package/dist/{stop-gate-policy-CoMKWSsS.js → stop-gate-policy-C0XOBZhB.js} +3 -3
  65. package/dist/{stop-gate-policy-CoMKWSsS.js.map → stop-gate-policy-C0XOBZhB.js.map} +1 -1
  66. package/dist/{token-DWknHe9M.js → token-C_q95GF7.js} +124 -14
  67. package/dist/{token-DWknHe9M.js.map → token-C_q95GF7.js.map} +1 -1
  68. package/package.json +2 -2
  69. package/dist/engine-B4Ohhv5i.js +0 -2
  70. package/dist/exec-DTO2oin0.js.map +0 -1
  71. package/dist/file-log-reporter-vI8qCFNM.js +0 -158
  72. package/dist/file-log-reporter-vI8qCFNM.js.map +0 -1
  73. package/dist/lifecycle-CRTWEbiP.js +0 -2
  74. package/dist/lifecycle-DUPXAOQS.js +0 -2
  75. package/dist/models-uQgsKNNC.js.map +0 -1
  76. package/dist/paths-Dz1WyRJG.js +0 -2
  77. package/dist/peer-mcp-personas-V4AXhm9B.js.map +0 -1
  78. package/dist/server-setup-BeeT8m-w.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { t as PATHS } from "./paths-BXrbboAS.js";
1
+ import { t as PATHS } from "./paths-Bg1_DWhi.js";
2
2
  import consola from "consola";
3
3
  import path from "node:path";
4
4
  import { randomBytes, randomUUID } from "node:crypto";
@@ -876,20 +876,46 @@ const cacheCopilotVersion = async () => {
876
876
  };
877
877
  //#endregion
878
878
  //#region src/services/github/poll-access-token.ts
879
+ /**
880
+ * Per-attempt cap for a single device-code poll.
881
+ *
882
+ * Deliberately NOT the shared transient-retry layer: a non-OK response here is
883
+ * the EXPECTED steady state (`authorization_pending` until the user finishes in
884
+ * their browser), so retrying it as a transient fault would just hammer GitHub
885
+ * inside a loop that is already a retry loop with its own interval.
886
+ *
887
+ * What was genuinely missing is a bound on a single attempt. Without a signal a
888
+ * hung socket blocks the loop forever: the `expiresAt` check only runs between
889
+ * attempts, so it can never fire while one attempt is stuck. This caps each
890
+ * attempt so the loop keeps ticking and the expiry bound stays real.
891
+ */
892
+ const POLL_ATTEMPT_TIMEOUT_MS = 3e4;
879
893
  async function pollAccessToken(deviceCode, clientId = GITHUB_CLIENT_ID) {
880
894
  const sleepDuration = (deviceCode.interval + 1) * 1e3;
881
895
  consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
882
896
  const expiresAt = Date.now() + deviceCode.expires_in * 1e3;
897
+ let lastFetchError;
883
898
  while (Date.now() < expiresAt) {
884
- const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
885
- method: "POST",
886
- headers: standardHeaders(),
887
- body: JSON.stringify({
888
- client_id: clientId,
889
- device_code: deviceCode.device_code,
890
- grant_type: "urn:ietf:params:oauth:grant-type:device_code"
891
- })
892
- });
899
+ let response;
900
+ try {
901
+ response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
902
+ method: "POST",
903
+ headers: standardHeaders(),
904
+ body: JSON.stringify({
905
+ client_id: clientId,
906
+ device_code: deviceCode.device_code,
907
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
908
+ }),
909
+ signal: AbortSignal.timeout(POLL_ATTEMPT_TIMEOUT_MS)
910
+ });
911
+ } catch (err) {
912
+ lastFetchError = err;
913
+ consola.debug("Access-token poll attempt failed; retrying:", err);
914
+ if (Date.now() >= expiresAt) break;
915
+ await sleep(sleepDuration);
916
+ continue;
917
+ }
918
+ lastFetchError = void 0;
893
919
  if (!response.ok) {
894
920
  consola.error("Failed to poll access token:", await response.text());
895
921
  if (Date.now() >= expiresAt) break;
@@ -903,23 +929,107 @@ async function pollAccessToken(deviceCode, clientId = GITHUB_CLIENT_ID) {
903
929
  if (Date.now() >= expiresAt) break;
904
930
  await sleep(sleepDuration);
905
931
  }
932
+ if (lastFetchError !== void 0) throw new Error("Could not reach GitHub to complete device-code authorization. Check network/proxy/TLS settings and run auth again.", { cause: lastFetchError });
906
933
  throw new Error("Device code expired. Please run auth again.");
907
934
  }
908
935
  //#endregion
909
936
  //#region src/lib/token.ts
910
937
  const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
911
- const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
938
+ /**
939
+ * Backoff for a transient Windows rename failure. On Windows `fs.rename` over
940
+ * an existing destination transiently fails with EPERM / EBUSY / EACCES when
941
+ * anything else holds the target open for a moment (antivirus, the search
942
+ * indexer, a backup agent). Nothing is wrong and the handle closes microseconds
943
+ * later — but without a retry, a scan landing on the wrong millisecond throws
944
+ * out of a credential write and costs the user a re-auth. Same delays as
945
+ * `renameWithRetry` in `~/lib/claude-md-injection`, which documents the
946
+ * underlying behavior at length.
947
+ */
948
+ const RENAME_RETRY_DELAYS_MS = [
949
+ 50,
950
+ 200,
951
+ 500
952
+ ];
953
+ /**
954
+ * Replace a credential file atomically: temp + rename, with bounded retry.
955
+ *
956
+ * A plain `fs.writeFile` truncates the destination before it writes, so a
957
+ * crash, a full disk, or a kill mid-write leaves a truncated token on disk and
958
+ * forces the user through a re-auth. `rename` within the same directory is
959
+ * atomic on both NTFS and POSIX, so a reader sees either the old token or the
960
+ * new one, never a partial.
961
+ *
962
+ * The temp name uses `randomBytes`, not `Math.random()`: this names a file that
963
+ * briefly holds a credential, and a predictable name in a shared directory
964
+ * invites a pre-creation race. Combined with `flag: "wx"` (fail if it exists)
965
+ * the write refuses to follow anything an attacker pre-placed, rather than
966
+ * silently writing the token through a planted symlink.
967
+ *
968
+ * Permissions: the temp file is created 0o600, and `rename` carries that mode
969
+ * with it on POSIX — so the destination's mode is preserved, not weakened.
970
+ * (On Windows `chmod` is a no-op anyway; see `chmodIfPossible` in `~/lib/paths`.
971
+ * Both token files live under `CLAUDE_RUNTIME_DIR`, which is chmod'd 0o700, so
972
+ * directory ACLs are what actually protect them there.)
973
+ */
974
+ async function writeTokenFileAtomic(filePath, token) {
975
+ const tmp = `${filePath}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`;
976
+ try {
977
+ await fs.writeFile(tmp, token, {
978
+ mode: 384,
979
+ flag: "wx"
980
+ });
981
+ let lastErr;
982
+ let renamed = false;
983
+ for (let attempt = 0; attempt <= RENAME_RETRY_DELAYS_MS.length; attempt++) try {
984
+ await fs.rename(tmp, filePath);
985
+ renamed = true;
986
+ break;
987
+ } catch (err) {
988
+ lastErr = err;
989
+ if (attempt < RENAME_RETRY_DELAYS_MS.length) await new Promise((resolve) => setTimeout(resolve, RENAME_RETRY_DELAYS_MS[attempt]));
990
+ }
991
+ if (!renamed) throw lastErr;
992
+ } catch (err) {
993
+ await fs.unlink(tmp).catch(() => {});
994
+ throw err;
995
+ }
996
+ }
997
+ const writeGithubToken = (token) => writeTokenFileAtomic(PATHS.GITHUB_TOKEN_PATH, token);
912
998
  const readGithubAgentToken = () => fs.readFile(PATHS.GITHUB_AGENT_TOKEN_PATH, "utf8");
913
- const writeGithubAgentToken = (token) => fs.writeFile(PATHS.GITHUB_AGENT_TOKEN_PATH, token, { mode: 384 });
999
+ const writeGithubAgentToken = (token) => writeTokenFileAtomic(PATHS.GITHUB_AGENT_TOKEN_PATH, token);
1000
+ /**
1001
+ * Fetch the Copilot token and keep it fresh in the background.
1002
+ *
1003
+ * Returns a disposer that stops the refresh loop. **Long-lived callers**
1004
+ * (`start`/`claude`/`codex`, via `setupAndServe`) can ignore it — the interval
1005
+ * is `unref()`d, so it never holds the event loop open on its own. The one-shot
1006
+ * `models` command calls it, so ownership of the timer is explicit rather than
1007
+ * implied by a runtime flag. (`check-usage` does not call this function at all;
1008
+ * it only needs `setupGitHubToken`.)
1009
+ *
1010
+ * Both halves are load-bearing, for different failure modes. Without the
1011
+ * `unref()`, `github-router models` printed its full correct output and then
1012
+ * hung forever: its success path just returns (only the failure branches call
1013
+ * `process.exit`), and the un-unref'd interval pinned the event loop. Without
1014
+ * the disposer, that fix would depend on a property no caller can see, so the
1015
+ * next one-shot command would inherit the same trap.
1016
+ */
914
1017
  const setupCopilotToken = async () => {
915
1018
  const { token, refresh_in } = await getCopilotToken();
916
1019
  state.copilotToken = token;
917
1020
  consola.debug("GitHub Copilot Token fetched successfully!");
918
1021
  if (state.showToken) consola.info("Copilot token:", token);
919
1022
  const refreshInterval = Math.max((refresh_in - 60) * 1e3, 1e3);
920
- setInterval(() => {
1023
+ const handle = setInterval(() => {
921
1024
  refreshCopilotToken("interval");
922
1025
  }, refreshInterval);
1026
+ handle.unref?.();
1027
+ let stopped = false;
1028
+ return () => {
1029
+ if (stopped) return;
1030
+ stopped = true;
1031
+ clearInterval(handle);
1032
+ };
923
1033
  };
924
1034
  let inflightRefresh;
925
1035
  let lastRefreshSuccess = 0;
@@ -1072,4 +1182,4 @@ async function warnIfAgentScopesInsufficient() {
1072
1182
  //#endregion
1073
1183
  export { copilotHeaders as C, githubHeaders as D, githubAgentHeaders as E, state as O, copilotBaseUrl as S, githubAgentGraphQLHeaders as T, withTransientRetry as _, cacheCopilotVersion as a, GITHUB_API_BASE_URL as b, filterBetaHeader as c, resolveModel as d, sleep as f, fetchWithTransientRetry as g, isAllowedCopilotHost as h, tryRefreshAndRetry as i, isNullish as l, getGitHubUser as m, setupGitHubAgentToken as n, cacheModels as o, getModels as p, setupGitHubToken as r, cacheVSCodeVersion as s, setupCopilotToken as t, resolveCodexModel as u, HTTPError as v, copilotVersion as w, GITHUB_GRAPHQL_URL as x, forwardError as y };
1074
1184
 
1075
- //# sourceMappingURL=token-DWknHe9M.js.map
1185
+ //# sourceMappingURL=token-C_q95GF7.js.map