@opentig/cli 0.2.0 → 0.2.1

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 (115) hide show
  1. package/dist/bin.mjs +1 -1
  2. package/dist/chunks/{cli-Bi6ET5UT.mjs → cli-B6JTojx_.mjs} +1 -1
  3. package/dist/client/assets/App-Dfm5F199.js +74 -0
  4. package/dist/client/assets/CreatePullRequestDialog-CAeykZ72.js +2 -0
  5. package/dist/client/assets/{DiffWorkspace-RDG_Y70N.js → DiffWorkspace-BRicWHvw.js} +13 -13
  6. package/dist/client/assets/{EditableFileViewer-CsbifqBO.js → EditableFileViewer-DWeSDnZn.js} +1 -1
  7. package/dist/client/assets/{File-CHjvYpb2.js → File-D65shwRR.js} +1 -1
  8. package/dist/client/assets/FilesView-EfoU7sF-.js +1 -0
  9. package/dist/client/assets/GitHubAvatar-C8R3iNoM.js +1 -0
  10. package/dist/client/assets/HistoryView-C6T2dsBC.js +1 -0
  11. package/dist/client/assets/{HtmlFileViewer-h8ULU8Gy.js → HtmlFileViewer-JXLHTNPm.js} +1 -1
  12. package/dist/client/assets/{IconEdit-Cg8S2wqW.js → IconEdit-3fgBTCQ4.js} +1 -1
  13. package/dist/client/assets/{IconFolderSymlink-BZSLX1ez.js → IconFolderSymlink-C4ydD1eS.js} +1 -1
  14. package/dist/client/assets/{ImageFileViewer-Cjw58ckv.js → ImageFileViewer-YH2xhkkk.js} +1 -1
  15. package/dist/client/assets/LocalRefsDialog--1NiYKYj.js +1 -0
  16. package/dist/client/assets/{MarkdownFileViewer-kLiduag6.js → MarkdownFileViewer-BsB8WkIH.js} +2 -2
  17. package/dist/client/assets/PairingPage-DXRyAYHn.js +1 -0
  18. package/dist/client/assets/{PierreDiffViewer-eAcugmoF.js → PierreDiffViewer-DoV3V0IW.js} +1 -1
  19. package/dist/client/assets/{PierreEditBoundary-DlGd1pqO.js → PierreEditBoundary-BnYZbHfi.js} +1 -1
  20. package/dist/client/assets/{PierreWorkerPool-Col4k7qz.js → PierreWorkerPool-Cg9zdxiI.js} +1 -1
  21. package/dist/client/assets/PullRequestDiff-D6QG5Anx.js +1 -0
  22. package/dist/client/assets/PullRequestViewer-jC3LVmiO.js +2 -0
  23. package/dist/client/assets/PullRequestsView-GStKxG7k.js +1 -0
  24. package/dist/client/assets/{QuickOpenDialog-BFDDZVCL.js → QuickOpenDialog-HvslpGQl.js} +1 -1
  25. package/dist/client/assets/{RepositoryProjectsDialog-CvB8KcFp.js → RepositoryProjectsDialog-Biqv5GO_.js} +1 -1
  26. package/dist/client/assets/SearchView-D5XB6wdu.js +1 -0
  27. package/dist/client/assets/SettingsDialog-DWAunOQB.js +72 -0
  28. package/dist/client/assets/{Viewer-o1hDU33O.js → Viewer-Gfy3Ejyo.js} +3 -3
  29. package/dist/client/assets/{abnfDiagram-N423BO3Z-BvrnK_R-.js → abnfDiagram-N423BO3Z-DdAfHevw.js} +1 -1
  30. package/dist/client/assets/{arc-DLa3_7ua.js → arc-C43uoeEi.js} +1 -1
  31. package/dist/client/assets/{architectureDiagram-T3A2C74G-BexwhnyX.js → architectureDiagram-T3A2C74G-C7q0hhRO.js} +1 -1
  32. package/dist/client/assets/{blockDiagram-VBNYF7ZC-CoOttUUo.js → blockDiagram-VBNYF7ZC-C1x9Iap4.js} +1 -1
  33. package/dist/client/assets/{c4Diagram-5PPSVZJV-Bgp6ppUh.js → c4Diagram-5PPSVZJV-BmKaVW6q.js} +1 -1
  34. package/dist/client/assets/channel-Diwpee6E.js +1 -0
  35. package/dist/client/assets/checkbox-XfIUFPxh.js +1 -0
  36. package/dist/client/assets/{chunk-2GRJ4B5K-C_kzp38w.js → chunk-2GRJ4B5K-kzy_H3BQ.js} +1 -1
  37. package/dist/client/assets/{chunk-2Q5K7J3B-CysYnRkJ.js → chunk-2Q5K7J3B-BMj-FT4X.js} +1 -1
  38. package/dist/client/assets/{chunk-5RXB4S5H-oMS6x_ag.js → chunk-5RXB4S5H-DmPGiCCb.js} +1 -1
  39. package/dist/client/assets/{chunk-5VM5RSS4-C5-dW5wf.js → chunk-5VM5RSS4-DYFL8__L.js} +1 -1
  40. package/dist/client/assets/{chunk-6Q2QTUOP-DxahInnA.js → chunk-6Q2QTUOP-CI98oVdT.js} +1 -1
  41. package/dist/client/assets/{chunk-GF5L2VYU-D7V-3eRg.js → chunk-GF5L2VYU-D9JiiMlX.js} +1 -1
  42. package/dist/client/assets/{chunk-JWPE2WC7-BrLmjbVP.js → chunk-JWPE2WC7-D0THYpZl.js} +1 -1
  43. package/dist/client/assets/{chunk-KBJHAD2P-CyOvijwM.js → chunk-KBJHAD2P-Z1tJfyAr.js} +1 -1
  44. package/dist/client/assets/{chunk-RYQCIY6F-BvEjVjQs.js → chunk-RYQCIY6F-31aS4cD3.js} +1 -1
  45. package/dist/client/assets/{chunk-XXDRQBXY-C_IY9A64.js → chunk-XXDRQBXY-ClWDHmEp.js} +1 -1
  46. package/dist/client/assets/classDiagram-JCYQIIEL-BDuo9kiS.js +1 -0
  47. package/dist/client/assets/classDiagram-v2-OCEON4UE-BDuo9kiS.js +1 -0
  48. package/dist/client/assets/{cose-bilkent-JH36ORCC-Cu7qZi7h.js → cose-bilkent-JH36ORCC-Btg2MYLh.js} +1 -1
  49. package/dist/client/assets/{cynefin-VYW2F7L2-B4M1t9lL.js → cynefin-VYW2F7L2-Cbh4Pk6j.js} +1 -1
  50. package/dist/client/assets/{cynefinDiagram-MW4NZA55-CAosOLmT.js → cynefinDiagram-MW4NZA55-BBqorl0u.js} +1 -1
  51. package/dist/client/assets/{dagre-VZM6K2ZE-q354V538.js → dagre-VZM6K2ZE-DugEItZ0.js} +1 -1
  52. package/dist/client/assets/{diagram-7IWD3JNH-C_CzkBPt.js → diagram-7IWD3JNH-Biu7dmH8.js} +1 -1
  53. package/dist/client/assets/{diagram-B4RE2ZJO-VvvYHE9c.js → diagram-B4RE2ZJO-UDm4DXFS.js} +1 -1
  54. package/dist/client/assets/{diagram-LBJQPF4R-D8vRQwb5.js → diagram-LBJQPF4R-Bcku4gb-.js} +1 -1
  55. package/dist/client/assets/{diagram-Q27KOJAE-Bul4aDKg.js → diagram-Q27KOJAE-DFRjglV-.js} +1 -1
  56. package/dist/client/assets/{diagram-UB23O5K3-DNadEE58.js → diagram-UB23O5K3-CgfMWtd0.js} +1 -1
  57. package/dist/client/assets/{ebnfDiagram-BXEA7PRR-Bf-68MMZ.js → ebnfDiagram-BXEA7PRR-CZzYOqs0.js} +1 -1
  58. package/dist/client/assets/{erDiagram-JOGREHBK-CJ-jHFsf.js → erDiagram-JOGREHBK-Cdlbxgs6.js} +1 -1
  59. package/dist/client/assets/{flowDiagram-UKHOOZJN-DMOdinBU.js → flowDiagram-UKHOOZJN-BgImDwpP.js} +1 -1
  60. package/dist/client/assets/{ganttDiagram-PKOTCBZU-DzezS3Zy.js → ganttDiagram-PKOTCBZU-SFhtiyL4.js} +1 -1
  61. package/dist/client/assets/{gitGraphDiagram-DS77QQ5N-Dftpvns8.js → gitGraphDiagram-DS77QQ5N-Bkm5ueys.js} +1 -1
  62. package/dist/client/assets/{index-DAUK3rnz.js → index-B5RC4THs.js} +4 -4
  63. package/dist/client/assets/index-Dc9xVluy.css +1 -0
  64. package/dist/client/assets/{infoDiagram-6WML65LV-DmgUBq17.js → infoDiagram-6WML65LV-pCYc-VX2.js} +1 -1
  65. package/dist/client/assets/{ishikawaDiagram-WSZJBQD7-CL0PQhpQ.js → ishikawaDiagram-WSZJBQD7-BtNbG3Y6.js} +1 -1
  66. package/dist/client/assets/{journeyDiagram-NVQOT4AX-Ck4pQx_A.js → journeyDiagram-NVQOT4AX-CPW2qT3y.js} +1 -1
  67. package/dist/client/assets/{kanban-definition-27J2QSJJ-DuRZxBql.js → kanban-definition-27J2QSJJ-wD6JXQFs.js} +1 -1
  68. package/dist/client/assets/{linear-C2JDCaz4.js → linear-sj8MOkmS.js} +1 -1
  69. package/dist/client/assets/{mermaid.core-9dt2ugQA.js → mermaid.core-CGJtfmwb.js} +4 -4
  70. package/dist/client/assets/{mindmap-definition-FAOFIHXS-vy0fdDF0.js → mindmap-definition-FAOFIHXS-BQwLHBVs.js} +1 -1
  71. package/dist/client/assets/{pegDiagram-VL7TDLO6-DjVJy-Gm.js → pegDiagram-VL7TDLO6-DtDsb-Z8.js} +1 -1
  72. package/dist/client/assets/{pieDiagram-7S7Q4E2Y-ATbd_8vw.js → pieDiagram-7S7Q4E2Y-B3WhqNuS.js} +1 -1
  73. package/dist/client/assets/{quadrantDiagram-CIZ2JOQS-C8o6ofcl.js → quadrantDiagram-CIZ2JOQS-CPi38pp_.js} +1 -1
  74. package/dist/client/assets/{railroadDiagram-AXF67PYL-Qvij9Ubo.js → railroadDiagram-AXF67PYL-Btac44Ux.js} +1 -1
  75. package/dist/client/assets/{render-markdown-BtCybfvQ.js → render-markdown-hAIWRfn0.js} +6 -6
  76. package/dist/client/assets/{requirementDiagram-LRYGKXZP-yLfrba2h.js → requirementDiagram-LRYGKXZP-CHJgDNdg.js} +1 -1
  77. package/dist/client/assets/{sankeyDiagram-W5VNT64P-BTXRtTyG.js → sankeyDiagram-W5VNT64P-OYJHfmtd.js} +1 -1
  78. package/dist/client/assets/{sequenceDiagram-SI44F4Z6-BN1CCZIX.js → sequenceDiagram-SI44F4Z6-9ypa9W08.js} +1 -1
  79. package/dist/client/assets/{sizeCapture-X5ZJPWSS-BfXjBBYo.js → sizeCapture-X5ZJPWSS-5ZDUTERI.js} +1 -1
  80. package/dist/client/assets/{stateDiagram-OKZ733FA-DMAWiBsP.js → stateDiagram-OKZ733FA-nGHE1hN7.js} +1 -1
  81. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-CkCpE1Yu.js +1 -0
  82. package/dist/client/assets/{swimlanes-SLNWSIFB-BD20m5cw.js → swimlanes-SLNWSIFB-BZFv9Zp8.js} +2 -2
  83. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-Dxlb7bju.js +8 -0
  84. package/dist/client/assets/tanstack-CcfAJoQ7.js +1 -0
  85. package/dist/client/assets/{timeline-definition-Z64GVDOM-gtc0-AHB.js → timeline-definition-Z64GVDOM-pUIcaCIE.js} +1 -1
  86. package/dist/client/assets/{vennDiagram-T6HMQDX7-Cdr4AXTp.js → vennDiagram-T6HMQDX7-D6dEIJmP.js} +1 -1
  87. package/dist/client/assets/{viewer-tabs-D-T0xQaq.js → viewer-tabs-BjG3oKQ9.js} +1 -1
  88. package/dist/client/assets/{wardleyDiagram-T6FBY63Y-BcU4vxO8.js → wardleyDiagram-T6FBY63Y-DZ8ro_-y.js} +1 -1
  89. package/dist/client/assets/{xychartDiagram-ELKLHX3M-B3Zuadj8.js → xychartDiagram-ELKLHX3M-WJ2Zfmlk.js} +1 -1
  90. package/dist/client/index.html +3 -3
  91. package/dist/dev.mjs +2 -2
  92. package/dist/manifest.json +2 -2
  93. package/dist/server.mjs +1616 -473
  94. package/dist/utility.mjs +11 -5
  95. package/package.json +1 -1
  96. package/dist/client/assets/App-BXZLGT86.js +0 -74
  97. package/dist/client/assets/CreatePullRequestDialog-DFonMYEx.js +0 -2
  98. package/dist/client/assets/FilesView-Dl8QFbFA.js +0 -1
  99. package/dist/client/assets/GitHubAvatar-CnBaloV0.js +0 -1
  100. package/dist/client/assets/HistoryView-BgCZU2JO.js +0 -1
  101. package/dist/client/assets/LocalRefsDialog-DNgrd7Qp.js +0 -1
  102. package/dist/client/assets/PairingPage-C97sv-8s.js +0 -1
  103. package/dist/client/assets/PullRequestDiff-DhKTXyIF.js +0 -1
  104. package/dist/client/assets/PullRequestViewer-BgI8HFMe.js +0 -2
  105. package/dist/client/assets/PullRequestsView-PYWLy8We.js +0 -1
  106. package/dist/client/assets/SearchView-BwF_MsxK.js +0 -1
  107. package/dist/client/assets/SettingsDialog-DIcLmXSE.js +0 -9
  108. package/dist/client/assets/channel-1k1AYpyV.js +0 -1
  109. package/dist/client/assets/checkbox-BRH0ZNIC.js +0 -1
  110. package/dist/client/assets/classDiagram-JCYQIIEL-ChyNFpay.js +0 -1
  111. package/dist/client/assets/classDiagram-v2-OCEON4UE-ChyNFpay.js +0 -1
  112. package/dist/client/assets/index-BfA4tAc1.css +0 -1
  113. package/dist/client/assets/stateDiagram-v2-UEYNNEHI-D_ofZeaa.js +0 -1
  114. package/dist/client/assets/swimlanesDiagram-ULZ7WXOC-CF9m5JSB.js +0 -8
  115. package/dist/client/assets/tanstack-B0Sf7wNU.js +0 -1
package/dist/server.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { fileURLToPath, pathToFileURL } from "node:url";
2
2
  import path, { extname, delimiter, resolve, join, isAbsolute, dirname } from "node:path";
3
- import { randomUUID, randomBytes, createHash, createHmac, timingSafeEqual } from "node:crypto";
3
+ import { randomUUID, randomBytes, createHmac, createHash, timingSafeEqual } from "node:crypto";
4
4
  import { stat, access, realpath, mkdir, open, rename, chmod, rm, appendFile, readFile, mkdtemp, writeFile, lstat, readdir, cp } from "node:fs/promises";
5
5
  import os, { constants, homedir, networkInterfaces } from "node:os";
6
6
  import { ChildProcess, execFile, spawnSync, spawn } from "node:child_process";
@@ -19,6 +19,9 @@ import path$1 from "node:path/win32";
19
19
  import { createServer, isIP } from "node:net";
20
20
  import { createServer as createServer$1 } from "node:http";
21
21
  import WebSocket, { WebSocketServer } from "ws";
22
+ function aiExecutablePathsKey(paths) {
23
+ return JSON.stringify(["codex", "claude", "opencode", "grok"].map((id) => paths[id] ?? ""));
24
+ }
22
25
  function isPlainObject$1(value) {
23
26
  if (typeof value !== "object" || value === null) {
24
27
  return false;
@@ -5709,7 +5712,7 @@ const createReadable = ({ subprocess, concurrentStreams, encoding }, { from, bin
5709
5712
  const binary = binaryOption || BINARY_ENCODINGS.has(encoding);
5710
5713
  const { subprocessStdout, waitReadableDestroy } = getSubprocessStdout(subprocess, from, concurrentStreams);
5711
5714
  const { readableEncoding, readableObjectMode, readableHighWaterMark } = getReadableOptions(subprocessStdout, binary);
5712
- const { read, onStdoutDataDone } = getReadableMethods({
5715
+ const { read: read2, onStdoutDataDone } = getReadableMethods({
5713
5716
  subprocessStdout,
5714
5717
  subprocess,
5715
5718
  binary,
@@ -5717,7 +5720,7 @@ const createReadable = ({ subprocess, concurrentStreams, encoding }, { from, bin
5717
5720
  preserveNewlines
5718
5721
  });
5719
5722
  const readable2 = new Readable({
5720
- read,
5723
+ read: read2,
5721
5724
  destroy: callbackify(onReadableDestroy.bind(void 0, { subprocessStdout, subprocess, waitReadableDestroy })),
5722
5725
  highWaterMark: readableHighWaterMark,
5723
5726
  objectMode: readableObjectMode,
@@ -6555,7 +6558,7 @@ const createDuplex = ({ subprocess, concurrentStreams, encoding }, { from, to, b
6555
6558
  const { subprocessStdout, waitReadableDestroy } = getSubprocessStdout(subprocess, from, concurrentStreams);
6556
6559
  const { subprocessStdin, waitWritableFinal, waitWritableDestroy } = getSubprocessStdin(subprocess, to, concurrentStreams);
6557
6560
  const { readableEncoding, readableObjectMode, readableHighWaterMark } = getReadableOptions(subprocessStdout, binary);
6558
- const { read, onStdoutDataDone } = getReadableMethods({
6561
+ const { read: read2, onStdoutDataDone } = getReadableMethods({
6559
6562
  subprocessStdout,
6560
6563
  subprocess,
6561
6564
  binary,
@@ -6563,7 +6566,7 @@ const createDuplex = ({ subprocess, concurrentStreams, encoding }, { from, to, b
6563
6566
  preserveNewlines
6564
6567
  });
6565
6568
  const duplex2 = new Duplex({
6566
- read,
6569
+ read: read2,
6567
6570
  ...getWritableMethods(subprocessStdin, subprocess, waitWritableFinal),
6568
6571
  destroy: callbackify(onDuplexDestroy.bind(void 0, {
6569
6572
  subprocessStdout,
@@ -6900,9 +6903,9 @@ function pathDirectories(value, platform2) {
6900
6903
  });
6901
6904
  }
6902
6905
  class CliEnvironment {
6903
- constructor(host = currentCliHost(), read = readUserEnvironment) {
6906
+ constructor(host = currentCliHost(), read2 = readUserEnvironment) {
6904
6907
  this.host = host;
6905
- this.read = read;
6908
+ this.read = read2;
6906
6909
  }
6907
6910
  host;
6908
6911
  read;
@@ -6947,7 +6950,7 @@ async function readUserEnvironment(host) {
6947
6950
  env: host.env,
6948
6951
  extendEnv: false,
6949
6952
  input: "",
6950
- timeout: 5e3,
6953
+ timeout: host.platform === "win32" ? 15e3 : 5e3,
6951
6954
  maxBuffer: 128 * 1024,
6952
6955
  cleanup: true,
6953
6956
  killDescendants: true,
@@ -6961,7 +6964,8 @@ async function readUserEnvironment(host) {
6961
6964
  const shell2 = path.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
6962
6965
  const script = '$ErrorActionPreference="Stop"; $r=@{}; foreach($scope in @("Machine","User")){ $r[$scope]=[Environment]::GetEnvironmentVariables($scope) }; ConvertTo-Json -InputObject $r -Compress';
6963
6966
  const result = await execa(shell2, ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script], options);
6964
- if (result.exitCode !== 0 || result.timedOut || result.isMaxBuffer) throw new Error("Environment query failed");
6967
+ if (result.timedOut) throw new Error("Environment query timed out");
6968
+ if (result.exitCode !== 0 || result.isMaxBuffer) throw new Error("Environment query failed");
6965
6969
  return windowsLocations(JSON.parse(result.stdout), host.env);
6966
6970
  }
6967
6971
  const shell = host.shell;
@@ -7529,14 +7533,14 @@ const NUMBER_FORMAT_RANGES = {
7529
7533
  float32: [-34028234663852886e22, 34028234663852886e22],
7530
7534
  float64: [-Number.MAX_VALUE, Number.MAX_VALUE]
7531
7535
  };
7532
- function pick(schema, mask) {
7533
- const currDef = schema._zod.def;
7536
+ function pick(schema2, mask) {
7537
+ const currDef = schema2._zod.def;
7534
7538
  const checks = currDef.checks;
7535
7539
  const hasChecks = checks && checks.length > 0;
7536
7540
  if (hasChecks) {
7537
7541
  throw new Error(".pick() cannot be used on object schemas containing refinements");
7538
7542
  }
7539
- const def = mergeDefs(schema._zod.def, {
7543
+ const def = mergeDefs(schema2._zod.def, {
7540
7544
  get shape() {
7541
7545
  const newShape = {};
7542
7546
  for (const key in mask) {
@@ -7552,18 +7556,18 @@ function pick(schema, mask) {
7552
7556
  },
7553
7557
  checks: []
7554
7558
  });
7555
- return clone(schema, def);
7559
+ return clone(schema2, def);
7556
7560
  }
7557
- function omit(schema, mask) {
7558
- const currDef = schema._zod.def;
7561
+ function omit(schema2, mask) {
7562
+ const currDef = schema2._zod.def;
7559
7563
  const checks = currDef.checks;
7560
7564
  const hasChecks = checks && checks.length > 0;
7561
7565
  if (hasChecks) {
7562
7566
  throw new Error(".omit() cannot be used on object schemas containing refinements");
7563
7567
  }
7564
- const def = mergeDefs(schema._zod.def, {
7568
+ const def = mergeDefs(schema2._zod.def, {
7565
7569
  get shape() {
7566
- const newShape = { ...schema._zod.def.shape };
7570
+ const newShape = { ...schema2._zod.def.shape };
7567
7571
  for (const key in mask) {
7568
7572
  if (!(key in currDef.shape)) {
7569
7573
  throw new Error(`Unrecognized key: "${key}"`);
@@ -7577,43 +7581,43 @@ function omit(schema, mask) {
7577
7581
  },
7578
7582
  checks: []
7579
7583
  });
7580
- return clone(schema, def);
7584
+ return clone(schema2, def);
7581
7585
  }
7582
- function extend(schema, shape) {
7586
+ function extend(schema2, shape) {
7583
7587
  if (!isPlainObject(shape)) {
7584
7588
  throw new Error("Invalid input to extend: expected a plain object");
7585
7589
  }
7586
- const checks = schema._zod.def.checks;
7590
+ const checks = schema2._zod.def.checks;
7587
7591
  const hasChecks = checks && checks.length > 0;
7588
7592
  if (hasChecks) {
7589
- const existingShape = schema._zod.def.shape;
7593
+ const existingShape = schema2._zod.def.shape;
7590
7594
  for (const key in shape) {
7591
7595
  if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) {
7592
7596
  throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
7593
7597
  }
7594
7598
  }
7595
7599
  }
7596
- const def = mergeDefs(schema._zod.def, {
7600
+ const def = mergeDefs(schema2._zod.def, {
7597
7601
  get shape() {
7598
- const _shape = { ...schema._zod.def.shape, ...shape };
7602
+ const _shape = { ...schema2._zod.def.shape, ...shape };
7599
7603
  assignProp(this, "shape", _shape);
7600
7604
  return _shape;
7601
7605
  }
7602
7606
  });
7603
- return clone(schema, def);
7607
+ return clone(schema2, def);
7604
7608
  }
7605
- function safeExtend(schema, shape) {
7609
+ function safeExtend(schema2, shape) {
7606
7610
  if (!isPlainObject(shape)) {
7607
7611
  throw new Error("Invalid input to safeExtend: expected a plain object");
7608
7612
  }
7609
- const def = mergeDefs(schema._zod.def, {
7613
+ const def = mergeDefs(schema2._zod.def, {
7610
7614
  get shape() {
7611
- const _shape = { ...schema._zod.def.shape, ...shape };
7615
+ const _shape = { ...schema2._zod.def.shape, ...shape };
7612
7616
  assignProp(this, "shape", _shape);
7613
7617
  return _shape;
7614
7618
  }
7615
7619
  });
7616
- return clone(schema, def);
7620
+ return clone(schema2, def);
7617
7621
  }
7618
7622
  function merge(a2, b) {
7619
7623
  if (a2._zod.def.checks?.length) {
@@ -7632,16 +7636,16 @@ function merge(a2, b) {
7632
7636
  });
7633
7637
  return clone(a2, def);
7634
7638
  }
7635
- function partial(Class, schema, mask) {
7636
- const currDef = schema._zod.def;
7639
+ function partial(Class, schema2, mask) {
7640
+ const currDef = schema2._zod.def;
7637
7641
  const checks = currDef.checks;
7638
7642
  const hasChecks = checks && checks.length > 0;
7639
7643
  if (hasChecks) {
7640
7644
  throw new Error(".partial() cannot be used on object schemas containing refinements");
7641
7645
  }
7642
- const def = mergeDefs(schema._zod.def, {
7646
+ const def = mergeDefs(schema2._zod.def, {
7643
7647
  get shape() {
7644
- const oldShape = schema._zod.def.shape;
7648
+ const oldShape = schema2._zod.def.shape;
7645
7649
  const shape = { ...oldShape };
7646
7650
  if (mask) {
7647
7651
  for (const key in mask) {
@@ -7668,12 +7672,12 @@ function partial(Class, schema, mask) {
7668
7672
  },
7669
7673
  checks: []
7670
7674
  });
7671
- return clone(schema, def);
7675
+ return clone(schema2, def);
7672
7676
  }
7673
- function required(Class, schema, mask) {
7674
- const def = mergeDefs(schema._zod.def, {
7677
+ function required(Class, schema2, mask) {
7678
+ const def = mergeDefs(schema2._zod.def, {
7675
7679
  get shape() {
7676
- const oldShape = schema._zod.def.shape;
7680
+ const oldShape = schema2._zod.def.shape;
7677
7681
  const shape = { ...oldShape };
7678
7682
  if (mask) {
7679
7683
  for (const key in mask) {
@@ -7699,7 +7703,7 @@ function required(Class, schema, mask) {
7699
7703
  return shape;
7700
7704
  }
7701
7705
  });
7702
- return clone(schema, def);
7706
+ return clone(schema2, def);
7703
7707
  }
7704
7708
  function aborted(x, startIndex = 0) {
7705
7709
  if (x.aborted === true)
@@ -7828,9 +7832,9 @@ function formatError(error, mapper = (issue2) => issue2.message) {
7828
7832
  processError(error);
7829
7833
  return fieldErrors;
7830
7834
  }
7831
- const _parse = (_Err) => (schema, value, _ctx, _params) => {
7835
+ const _parse = (_Err) => (schema2, value, _ctx, _params) => {
7832
7836
  const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
7833
- const result = schema._zod.run({ value, issues: [] }, ctx);
7837
+ const result = schema2._zod.run({ value, issues: [] }, ctx);
7834
7838
  if (result instanceof Promise) {
7835
7839
  throw new $ZodAsyncError();
7836
7840
  }
@@ -7841,9 +7845,9 @@ const _parse = (_Err) => (schema, value, _ctx, _params) => {
7841
7845
  }
7842
7846
  return result.value;
7843
7847
  };
7844
- const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
7848
+ const _parseAsync = (_Err) => async (schema2, value, _ctx, params) => {
7845
7849
  const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
7846
- let result = schema._zod.run({ value, issues: [] }, ctx);
7850
+ let result = schema2._zod.run({ value, issues: [] }, ctx);
7847
7851
  if (result instanceof Promise)
7848
7852
  result = await result;
7849
7853
  if (result.issues.length) {
@@ -7853,9 +7857,9 @@ const _parseAsync = (_Err) => async (schema, value, _ctx, params) => {
7853
7857
  }
7854
7858
  return result.value;
7855
7859
  };
7856
- const _safeParse = (_Err) => (schema, value, _ctx) => {
7860
+ const _safeParse = (_Err) => (schema2, value, _ctx) => {
7857
7861
  const ctx = _ctx ? { ..._ctx, async: false } : { async: false };
7858
- const result = schema._zod.run({ value, issues: [] }, ctx);
7862
+ const result = schema2._zod.run({ value, issues: [] }, ctx);
7859
7863
  if (result instanceof Promise) {
7860
7864
  throw new $ZodAsyncError();
7861
7865
  }
@@ -7865,9 +7869,9 @@ const _safeParse = (_Err) => (schema, value, _ctx) => {
7865
7869
  } : { success: true, data: result.value };
7866
7870
  };
7867
7871
  const safeParse$1 = /* @__PURE__ */ _safeParse($ZodRealError);
7868
- const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
7872
+ const _safeParseAsync = (_Err) => async (schema2, value, _ctx) => {
7869
7873
  const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
7870
- let result = schema._zod.run({ value, issues: [] }, ctx);
7874
+ let result = schema2._zod.run({ value, issues: [] }, ctx);
7871
7875
  if (result instanceof Promise)
7872
7876
  result = await result;
7873
7877
  return result.issues.length ? {
@@ -7876,33 +7880,33 @@ const _safeParseAsync = (_Err) => async (schema, value, _ctx) => {
7876
7880
  } : { success: true, data: result.value };
7877
7881
  };
7878
7882
  const safeParseAsync$1 = /* @__PURE__ */ _safeParseAsync($ZodRealError);
7879
- const _encode = (_Err) => (schema, value, _ctx) => {
7883
+ const _encode = (_Err) => (schema2, value, _ctx) => {
7880
7884
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7881
- return _parse(_Err)(schema, value, ctx);
7885
+ return _parse(_Err)(schema2, value, ctx);
7882
7886
  };
7883
- const _decode = (_Err) => (schema, value, _ctx) => {
7884
- return _parse(_Err)(schema, value, _ctx);
7887
+ const _decode = (_Err) => (schema2, value, _ctx) => {
7888
+ return _parse(_Err)(schema2, value, _ctx);
7885
7889
  };
7886
- const _encodeAsync = (_Err) => async (schema, value, _ctx) => {
7890
+ const _encodeAsync = (_Err) => async (schema2, value, _ctx) => {
7887
7891
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7888
- return _parseAsync(_Err)(schema, value, ctx);
7892
+ return _parseAsync(_Err)(schema2, value, ctx);
7889
7893
  };
7890
- const _decodeAsync = (_Err) => async (schema, value, _ctx) => {
7891
- return _parseAsync(_Err)(schema, value, _ctx);
7894
+ const _decodeAsync = (_Err) => async (schema2, value, _ctx) => {
7895
+ return _parseAsync(_Err)(schema2, value, _ctx);
7892
7896
  };
7893
- const _safeEncode = (_Err) => (schema, value, _ctx) => {
7897
+ const _safeEncode = (_Err) => (schema2, value, _ctx) => {
7894
7898
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7895
- return _safeParse(_Err)(schema, value, ctx);
7899
+ return _safeParse(_Err)(schema2, value, ctx);
7896
7900
  };
7897
- const _safeDecode = (_Err) => (schema, value, _ctx) => {
7898
- return _safeParse(_Err)(schema, value, _ctx);
7901
+ const _safeDecode = (_Err) => (schema2, value, _ctx) => {
7902
+ return _safeParse(_Err)(schema2, value, _ctx);
7899
7903
  };
7900
- const _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {
7904
+ const _safeEncodeAsync = (_Err) => async (schema2, value, _ctx) => {
7901
7905
  const ctx = _ctx ? { ..._ctx, direction: "backward" } : { direction: "backward" };
7902
- return _safeParseAsync(_Err)(schema, value, ctx);
7906
+ return _safeParseAsync(_Err)(schema2, value, ctx);
7903
7907
  };
7904
- const _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {
7905
- return _safeParseAsync(_Err)(schema, value, _ctx);
7908
+ const _safeDecodeAsync = (_Err) => async (schema2, value, _ctx) => {
7909
+ return _safeParseAsync(_Err)(schema2, value, _ctx);
7906
7910
  };
7907
7911
  const cuid = /^[cC][0-9a-z]{6,}$/;
7908
7912
  const cuid2 = /^[0-9a-z]+$/;
@@ -9072,9 +9076,9 @@ const $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def)
9072
9076
  for (const key of normalized.keys) {
9073
9077
  const id = ids[key];
9074
9078
  const k = esc(key);
9075
- const schema = shape[key];
9076
- const isOptionalIn = schema?._zod?.optin === "optional";
9077
- const isOptionalOut = schema?._zod?.optout === "optional";
9079
+ const schema2 = shape[key];
9080
+ const isOptionalIn = schema2?._zod?.optin === "optional";
9081
+ const isOptionalOut = schema2?._zod?.optout === "optional";
9078
9082
  doc.write(`const ${id} = ${parseStr(key)};`);
9079
9083
  if (isOptionalIn && isOptionalOut) {
9080
9084
  doc.write(`
@@ -9243,6 +9247,73 @@ const $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
9243
9247
  });
9244
9248
  };
9245
9249
  });
9250
+ const $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnion", (inst, def) => {
9251
+ def.inclusive = false;
9252
+ $ZodUnion.init(inst, def);
9253
+ const _super = inst._zod.parse;
9254
+ defineLazy(inst._zod, "propValues", () => {
9255
+ const propValues = {};
9256
+ for (const option of def.options) {
9257
+ const pv = option._zod.propValues;
9258
+ if (!pv || Object.keys(pv).length === 0)
9259
+ throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
9260
+ for (const [k, v] of Object.entries(pv)) {
9261
+ if (!propValues[k])
9262
+ propValues[k] = /* @__PURE__ */ new Set();
9263
+ for (const val of v) {
9264
+ propValues[k].add(val);
9265
+ }
9266
+ }
9267
+ }
9268
+ return propValues;
9269
+ });
9270
+ const disc = cached(() => {
9271
+ const opts = def.options;
9272
+ const map = /* @__PURE__ */ new Map();
9273
+ for (const o2 of opts) {
9274
+ const values = o2._zod.propValues?.[def.discriminator];
9275
+ if (!values || values.size === 0)
9276
+ throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(o2)}"`);
9277
+ for (const v of values) {
9278
+ if (map.has(v)) {
9279
+ throw new Error(`Duplicate discriminator value "${String(v)}"`);
9280
+ }
9281
+ map.set(v, o2);
9282
+ }
9283
+ }
9284
+ return map;
9285
+ });
9286
+ inst._zod.parse = (payload, ctx) => {
9287
+ const input = payload.value;
9288
+ if (!isObject(input)) {
9289
+ payload.issues.push({
9290
+ code: "invalid_type",
9291
+ expected: "object",
9292
+ input,
9293
+ inst
9294
+ });
9295
+ return payload;
9296
+ }
9297
+ const opt = disc.value.get(input?.[def.discriminator]);
9298
+ if (opt) {
9299
+ return opt._zod.run(payload, ctx);
9300
+ }
9301
+ if (def.unionFallback || ctx.direction === "backward") {
9302
+ return _super(payload, ctx);
9303
+ }
9304
+ payload.issues.push({
9305
+ code: "invalid_union",
9306
+ errors: [],
9307
+ note: "No matching discriminator",
9308
+ discriminator: def.discriminator,
9309
+ options: Array.from(disc.value.keys()),
9310
+ input,
9311
+ path: [def.discriminator],
9312
+ inst
9313
+ });
9314
+ return payload;
9315
+ };
9316
+ });
9246
9317
  const $ZodIntersection = /* @__PURE__ */ $constructor("$ZodIntersection", (inst, def) => {
9247
9318
  $ZodType.init(inst, def);
9248
9319
  inst._zod.parse = (payload, ctx) => {
@@ -9487,6 +9558,28 @@ const $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
9487
9558
  return payload;
9488
9559
  };
9489
9560
  });
9561
+ const $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
9562
+ $ZodType.init(inst, def);
9563
+ if (def.values.length === 0) {
9564
+ throw new Error("Cannot create literal schema with no valid values");
9565
+ }
9566
+ const values = new Set(def.values);
9567
+ inst._zod.values = values;
9568
+ inst._zod.pattern = new RegExp(`^(${def.values.map((o2) => typeof o2 === "string" ? escapeRegex(o2) : o2 ? escapeRegex(o2.toString()) : String(o2)).join("|")})$`);
9569
+ inst._zod.parse = (payload, _ctx) => {
9570
+ const input = payload.value;
9571
+ if (values.has(input)) {
9572
+ return payload;
9573
+ }
9574
+ payload.issues.push({
9575
+ code: "invalid_value",
9576
+ values: def.values,
9577
+ input,
9578
+ inst
9579
+ });
9580
+ return payload;
9581
+ };
9582
+ });
9490
9583
  const $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) => {
9491
9584
  $ZodType.init(inst, def);
9492
9585
  inst._zod.optin = "optional";
@@ -9761,11 +9854,11 @@ class $ZodRegistry {
9761
9854
  this._map = /* @__PURE__ */ new WeakMap();
9762
9855
  this._idmap = /* @__PURE__ */ new Map();
9763
9856
  }
9764
- add(schema, ..._meta) {
9857
+ add(schema2, ..._meta) {
9765
9858
  const meta = _meta[0];
9766
- this._map.set(schema, meta);
9859
+ this._map.set(schema2, meta);
9767
9860
  if (meta && typeof meta === "object" && "id" in meta) {
9768
- this._idmap.set(meta.id, schema);
9861
+ this._idmap.set(meta.id, schema2);
9769
9862
  }
9770
9863
  return this;
9771
9864
  }
@@ -9774,26 +9867,26 @@ class $ZodRegistry {
9774
9867
  this._idmap = /* @__PURE__ */ new Map();
9775
9868
  return this;
9776
9869
  }
9777
- remove(schema) {
9778
- const meta = this._map.get(schema);
9870
+ remove(schema2) {
9871
+ const meta = this._map.get(schema2);
9779
9872
  if (meta && typeof meta === "object" && "id" in meta) {
9780
9873
  this._idmap.delete(meta.id);
9781
9874
  }
9782
- this._map.delete(schema);
9875
+ this._map.delete(schema2);
9783
9876
  return this;
9784
9877
  }
9785
- get(schema) {
9786
- const p = schema._zod.parent;
9878
+ get(schema2) {
9879
+ const p = schema2._zod.parent;
9787
9880
  if (p) {
9788
9881
  const pm = { ...this.get(p) ?? {} };
9789
9882
  delete pm.id;
9790
- const f = { ...pm, ...this._map.get(schema) };
9883
+ const f = { ...pm, ...this._map.get(schema2) };
9791
9884
  return Object.keys(f).length ? f : void 0;
9792
9885
  }
9793
- return this._map.get(schema);
9886
+ return this._map.get(schema2);
9794
9887
  }
9795
- has(schema) {
9796
- return this._map.has(schema);
9888
+ has(schema2) {
9889
+ return this._map.has(schema2);
9797
9890
  }
9798
9891
  }
9799
9892
  function registry() {
@@ -10270,13 +10363,13 @@ function _array(Class, element, params) {
10270
10363
  }
10271
10364
  // @__NO_SIDE_EFFECTS__
10272
10365
  function _refine(Class, fn, _params) {
10273
- const schema = new Class({
10366
+ const schema2 = new Class({
10274
10367
  type: "custom",
10275
10368
  check: "custom",
10276
10369
  fn,
10277
10370
  ...normalizeParams(_params)
10278
10371
  });
10279
- return schema;
10372
+ return schema2;
10280
10373
  }
10281
10374
  // @__NO_SIDE_EFFECTS__
10282
10375
  function _superRefine(fn, params) {
@@ -10329,40 +10422,40 @@ function initializeContext(params) {
10329
10422
  external: params?.external ?? void 0
10330
10423
  };
10331
10424
  }
10332
- function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
10425
+ function process$1(schema2, ctx, _params = { path: [], schemaPath: [] }) {
10333
10426
  var _a2;
10334
- const def = schema._zod.def;
10335
- const seen = ctx.seen.get(schema);
10427
+ const def = schema2._zod.def;
10428
+ const seen = ctx.seen.get(schema2);
10336
10429
  if (seen) {
10337
10430
  seen.count++;
10338
- const isCycle = _params.schemaPath.includes(schema);
10431
+ const isCycle = _params.schemaPath.includes(schema2);
10339
10432
  if (isCycle) {
10340
10433
  seen.cycle = _params.path;
10341
10434
  }
10342
10435
  return seen.schema;
10343
10436
  }
10344
10437
  const result = { schema: {}, count: 1, cycle: void 0, path: _params.path };
10345
- ctx.seen.set(schema, result);
10346
- const overrideSchema = schema._zod.toJSONSchema?.();
10438
+ ctx.seen.set(schema2, result);
10439
+ const overrideSchema = schema2._zod.toJSONSchema?.();
10347
10440
  if (overrideSchema) {
10348
10441
  result.schema = overrideSchema;
10349
10442
  } else {
10350
10443
  const params = {
10351
10444
  ..._params,
10352
- schemaPath: [..._params.schemaPath, schema],
10445
+ schemaPath: [..._params.schemaPath, schema2],
10353
10446
  path: _params.path
10354
10447
  };
10355
- if (schema._zod.processJSONSchema) {
10356
- schema._zod.processJSONSchema(ctx, result.schema, params);
10448
+ if (schema2._zod.processJSONSchema) {
10449
+ schema2._zod.processJSONSchema(ctx, result.schema, params);
10357
10450
  } else {
10358
10451
  const _json = result.schema;
10359
10452
  const processor = ctx.processors[def.type];
10360
10453
  if (!processor) {
10361
10454
  throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);
10362
10455
  }
10363
- processor(schema, ctx, _json, params);
10456
+ processor(schema2, ctx, _json, params);
10364
10457
  }
10365
- const parent = schema._zod.parent;
10458
+ const parent = schema2._zod.parent;
10366
10459
  if (parent) {
10367
10460
  if (!result.ref)
10368
10461
  result.ref = parent;
@@ -10370,21 +10463,21 @@ function process$1(schema, ctx, _params = { path: [], schemaPath: [] }) {
10370
10463
  ctx.seen.get(parent).isParent = true;
10371
10464
  }
10372
10465
  }
10373
- const meta = ctx.metadataRegistry.get(schema);
10466
+ const meta = ctx.metadataRegistry.get(schema2);
10374
10467
  if (meta)
10375
10468
  Object.assign(result.schema, meta);
10376
- if (ctx.io === "input" && isTransforming(schema)) {
10469
+ if (ctx.io === "input" && isTransforming(schema2)) {
10377
10470
  delete result.schema.examples;
10378
10471
  delete result.schema.default;
10379
10472
  }
10380
10473
  if (ctx.io === "input" && "_prefault" in result.schema)
10381
10474
  (_a2 = result.schema).default ?? (_a2.default = result.schema._prefault);
10382
10475
  delete result.schema._prefault;
10383
- const _result = ctx.seen.get(schema);
10476
+ const _result = ctx.seen.get(schema2);
10384
10477
  return _result.schema;
10385
10478
  }
10386
- function extractDefs(ctx, schema) {
10387
- const root = ctx.seen.get(schema);
10479
+ function extractDefs(ctx, schema2) {
10480
+ const root = ctx.seen.get(schema2);
10388
10481
  if (!root)
10389
10482
  throw new Error("Unprocessed schema. This is a bug in Zod.");
10390
10483
  const idToSchema = /* @__PURE__ */ new Map();
@@ -10427,11 +10520,11 @@ function extractDefs(ctx, schema) {
10427
10520
  seen.def = { ...seen.schema };
10428
10521
  if (defId)
10429
10522
  seen.defId = defId;
10430
- const schema2 = seen.schema;
10431
- for (const key in schema2) {
10432
- delete schema2[key];
10523
+ const schema3 = seen.schema;
10524
+ for (const key in schema3) {
10525
+ delete schema3[key];
10433
10526
  }
10434
- schema2.$ref = ref;
10527
+ schema3.$ref = ref;
10435
10528
  };
10436
10529
  if (ctx.cycles === "throw") {
10437
10530
  for (const entry of ctx.seen.entries()) {
@@ -10445,13 +10538,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10445
10538
  }
10446
10539
  for (const entry of ctx.seen.entries()) {
10447
10540
  const seen = entry[1];
10448
- if (schema === entry[0]) {
10541
+ if (schema2 === entry[0]) {
10449
10542
  extractToDef(entry);
10450
10543
  continue;
10451
10544
  }
10452
10545
  if (ctx.external) {
10453
10546
  const ext = ctx.external.registry.get(entry[0])?.id;
10454
- if (schema !== entry[0] && ext) {
10547
+ if (schema2 !== entry[0] && ext) {
10455
10548
  extractToDef(entry);
10456
10549
  continue;
10457
10550
  }
@@ -10473,16 +10566,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10473
10566
  }
10474
10567
  }
10475
10568
  }
10476
- function finalize(ctx, schema) {
10477
- const root = ctx.seen.get(schema);
10569
+ function finalize(ctx, schema2) {
10570
+ const root = ctx.seen.get(schema2);
10478
10571
  if (!root)
10479
10572
  throw new Error("Unprocessed schema. This is a bug in Zod.");
10480
10573
  const flattenRef = (zodSchema) => {
10481
10574
  const seen = ctx.seen.get(zodSchema);
10482
10575
  if (seen.ref === null)
10483
10576
  return;
10484
- const schema2 = seen.def ?? seen.schema;
10485
- const _cached = { ...schema2 };
10577
+ const schema3 = seen.def ?? seen.schema;
10578
+ const _cached = { ...schema3 };
10486
10579
  const ref = seen.ref;
10487
10580
  seen.ref = null;
10488
10581
  if (ref) {
@@ -10490,28 +10583,28 @@ function finalize(ctx, schema) {
10490
10583
  const refSeen = ctx.seen.get(ref);
10491
10584
  const refSchema = refSeen.schema;
10492
10585
  if (refSchema.$ref && (ctx.target === "draft-07" || ctx.target === "draft-04" || ctx.target === "openapi-3.0")) {
10493
- schema2.allOf = schema2.allOf ?? [];
10494
- schema2.allOf.push(refSchema);
10586
+ schema3.allOf = schema3.allOf ?? [];
10587
+ schema3.allOf.push(refSchema);
10495
10588
  } else {
10496
- Object.assign(schema2, refSchema);
10589
+ Object.assign(schema3, refSchema);
10497
10590
  }
10498
- Object.assign(schema2, _cached);
10591
+ Object.assign(schema3, _cached);
10499
10592
  const isParentRef = zodSchema._zod.parent === ref;
10500
10593
  if (isParentRef) {
10501
- for (const key in schema2) {
10594
+ for (const key in schema3) {
10502
10595
  if (key === "$ref" || key === "allOf")
10503
10596
  continue;
10504
10597
  if (!(key in _cached)) {
10505
- delete schema2[key];
10598
+ delete schema3[key];
10506
10599
  }
10507
10600
  }
10508
10601
  }
10509
10602
  if (refSchema.$ref && refSeen.def) {
10510
- for (const key in schema2) {
10603
+ for (const key in schema3) {
10511
10604
  if (key === "$ref" || key === "allOf")
10512
10605
  continue;
10513
- if (key in refSeen.def && JSON.stringify(schema2[key]) === JSON.stringify(refSeen.def[key])) {
10514
- delete schema2[key];
10606
+ if (key in refSeen.def && JSON.stringify(schema3[key]) === JSON.stringify(refSeen.def[key])) {
10607
+ delete schema3[key];
10515
10608
  }
10516
10609
  }
10517
10610
  }
@@ -10521,13 +10614,13 @@ function finalize(ctx, schema) {
10521
10614
  flattenRef(parent);
10522
10615
  const parentSeen = ctx.seen.get(parent);
10523
10616
  if (parentSeen?.schema.$ref) {
10524
- schema2.$ref = parentSeen.schema.$ref;
10617
+ schema3.$ref = parentSeen.schema.$ref;
10525
10618
  if (parentSeen.def) {
10526
- for (const key in schema2) {
10619
+ for (const key in schema3) {
10527
10620
  if (key === "$ref" || key === "allOf")
10528
10621
  continue;
10529
- if (key in parentSeen.def && JSON.stringify(schema2[key]) === JSON.stringify(parentSeen.def[key])) {
10530
- delete schema2[key];
10622
+ if (key in parentSeen.def && JSON.stringify(schema3[key]) === JSON.stringify(parentSeen.def[key])) {
10623
+ delete schema3[key];
10531
10624
  }
10532
10625
  }
10533
10626
  }
@@ -10535,7 +10628,7 @@ function finalize(ctx, schema) {
10535
10628
  }
10536
10629
  ctx.override({
10537
10630
  zodSchema,
10538
- jsonSchema: schema2,
10631
+ jsonSchema: schema3,
10539
10632
  path: seen.path ?? []
10540
10633
  });
10541
10634
  };
@@ -10552,13 +10645,13 @@ function finalize(ctx, schema) {
10552
10645
  } else if (ctx.target === "openapi-3.0") ;
10553
10646
  else ;
10554
10647
  if (ctx.external?.uri) {
10555
- const id = ctx.external.registry.get(schema)?.id;
10648
+ const id = ctx.external.registry.get(schema2)?.id;
10556
10649
  if (!id)
10557
10650
  throw new Error("Schema is missing an `id` property");
10558
10651
  result.$id = ctx.external.uri(id);
10559
10652
  }
10560
10653
  Object.assign(result, root.def ?? root.schema);
10561
- const rootMetaId = ctx.metadataRegistry.get(schema)?.id;
10654
+ const rootMetaId = ctx.metadataRegistry.get(schema2)?.id;
10562
10655
  if (rootMetaId !== void 0 && result.id === rootMetaId)
10563
10656
  delete result.id;
10564
10657
  const defs = ctx.external?.defs ?? {};
@@ -10584,10 +10677,10 @@ function finalize(ctx, schema) {
10584
10677
  const finalized = JSON.parse(JSON.stringify(result));
10585
10678
  Object.defineProperty(finalized, "~standard", {
10586
10679
  value: {
10587
- ...schema["~standard"],
10680
+ ...schema2["~standard"],
10588
10681
  jsonSchema: {
10589
- input: createStandardJSONSchemaMethod(schema, "input", ctx.processors),
10590
- output: createStandardJSONSchemaMethod(schema, "output", ctx.processors)
10682
+ input: createStandardJSONSchemaMethod(schema2, "input", ctx.processors),
10683
+ output: createStandardJSONSchemaMethod(schema2, "output", ctx.processors)
10591
10684
  }
10592
10685
  },
10593
10686
  enumerable: false,
@@ -10651,18 +10744,18 @@ function isTransforming(_schema, _ctx) {
10651
10744
  }
10652
10745
  return false;
10653
10746
  }
10654
- const createToJSONSchemaMethod = (schema, processors = {}) => (params) => {
10747
+ const createToJSONSchemaMethod = (schema2, processors = {}) => (params) => {
10655
10748
  const ctx = initializeContext({ ...params, processors });
10656
- process$1(schema, ctx);
10657
- extractDefs(ctx, schema);
10658
- return finalize(ctx, schema);
10749
+ process$1(schema2, ctx);
10750
+ extractDefs(ctx, schema2);
10751
+ return finalize(ctx, schema2);
10659
10752
  };
10660
- const createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {
10753
+ const createStandardJSONSchemaMethod = (schema2, io, processors = {}) => (params) => {
10661
10754
  const { libraryOptions, target } = params ?? {};
10662
10755
  const ctx = initializeContext({ ...libraryOptions ?? {}, target, io, processors });
10663
- process$1(schema, ctx);
10664
- extractDefs(ctx, schema);
10665
- return finalize(ctx, schema);
10756
+ process$1(schema2, ctx);
10757
+ extractDefs(ctx, schema2);
10758
+ return finalize(ctx, schema2);
10666
10759
  };
10667
10760
  const formatMap = {
10668
10761
  guid: "uuid",
@@ -10672,10 +10765,10 @@ const formatMap = {
10672
10765
  regex: ""
10673
10766
  // do not set
10674
10767
  };
10675
- const stringProcessor = (schema, ctx, _json, _params) => {
10768
+ const stringProcessor = (schema2, ctx, _json, _params) => {
10676
10769
  const json2 = _json;
10677
10770
  json2.type = "string";
10678
- const { minimum, maximum, format: format2, patterns, contentEncoding } = schema._zod.bag;
10771
+ const { minimum, maximum, format: format2, patterns, contentEncoding } = schema2._zod.bag;
10679
10772
  if (typeof minimum === "number")
10680
10773
  json2.minLength = minimum;
10681
10774
  if (typeof maximum === "number")
@@ -10704,9 +10797,9 @@ const stringProcessor = (schema, ctx, _json, _params) => {
10704
10797
  }
10705
10798
  }
10706
10799
  };
10707
- const numberProcessor = (schema, ctx, _json, _params) => {
10800
+ const numberProcessor = (schema2, ctx, _json, _params) => {
10708
10801
  const json2 = _json;
10709
- const { minimum, maximum, format: format2, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema._zod.bag;
10802
+ const { minimum, maximum, format: format2, multipleOf, exclusiveMaximum, exclusiveMinimum } = schema2._zod.bag;
10710
10803
  if (typeof format2 === "string" && format2.includes("int"))
10711
10804
  json2.type = "integer";
10712
10805
  else
@@ -10781,8 +10874,8 @@ const dateProcessor = (_schema, ctx, _json, _params) => {
10781
10874
  throw new Error("Date cannot be represented in JSON Schema");
10782
10875
  }
10783
10876
  };
10784
- const enumProcessor = (schema, _ctx, json2, _params) => {
10785
- const def = schema._zod.def;
10877
+ const enumProcessor = (schema2, _ctx, json2, _params) => {
10878
+ const def = schema2._zod.def;
10786
10879
  const values = getEnumValues(def.entries);
10787
10880
  if (values.every((v) => typeof v === "number"))
10788
10881
  json2.type = "number";
@@ -10790,8 +10883,8 @@ const enumProcessor = (schema, _ctx, json2, _params) => {
10790
10883
  json2.type = "string";
10791
10884
  json2.enum = values;
10792
10885
  };
10793
- const literalProcessor = (schema, ctx, json2, _params) => {
10794
- const def = schema._zod.def;
10886
+ const literalProcessor = (schema2, ctx, json2, _params) => {
10887
+ const def = schema2._zod.def;
10795
10888
  const vals = [];
10796
10889
  for (const val of def.values) {
10797
10890
  if (val === void 0) {
@@ -10834,22 +10927,22 @@ const nanProcessor = (_schema, ctx, _json, _params) => {
10834
10927
  throw new Error("NaN cannot be represented in JSON Schema");
10835
10928
  }
10836
10929
  };
10837
- const templateLiteralProcessor = (schema, _ctx, json2, _params) => {
10930
+ const templateLiteralProcessor = (schema2, _ctx, json2, _params) => {
10838
10931
  const _json = json2;
10839
- const pattern = schema._zod.pattern;
10932
+ const pattern = schema2._zod.pattern;
10840
10933
  if (!pattern)
10841
10934
  throw new Error("Pattern not found in template literal");
10842
10935
  _json.type = "string";
10843
10936
  _json.pattern = pattern.source;
10844
10937
  };
10845
- const fileProcessor = (schema, _ctx, json2, _params) => {
10938
+ const fileProcessor = (schema2, _ctx, json2, _params) => {
10846
10939
  const _json = json2;
10847
10940
  const file = {
10848
10941
  type: "string",
10849
10942
  format: "binary",
10850
10943
  contentEncoding: "binary"
10851
10944
  };
10852
- const { minimum, maximum, mime } = schema._zod.bag;
10945
+ const { minimum, maximum, mime } = schema2._zod.bag;
10853
10946
  if (minimum !== void 0)
10854
10947
  file.minLength = minimum;
10855
10948
  if (maximum !== void 0)
@@ -10894,10 +10987,10 @@ const setProcessor = (_schema, ctx, _json, _params) => {
10894
10987
  throw new Error("Set cannot be represented in JSON Schema");
10895
10988
  }
10896
10989
  };
10897
- const arrayProcessor = (schema, ctx, _json, params) => {
10990
+ const arrayProcessor = (schema2, ctx, _json, params) => {
10898
10991
  const json2 = _json;
10899
- const def = schema._zod.def;
10900
- const { minimum, maximum } = schema._zod.bag;
10992
+ const def = schema2._zod.def;
10993
+ const { minimum, maximum } = schema2._zod.bag;
10901
10994
  if (typeof minimum === "number")
10902
10995
  json2.minItems = minimum;
10903
10996
  if (typeof maximum === "number")
@@ -10908,9 +11001,9 @@ const arrayProcessor = (schema, ctx, _json, params) => {
10908
11001
  path: [...params.path, "items"]
10909
11002
  });
10910
11003
  };
10911
- const objectProcessor = (schema, ctx, _json, params) => {
11004
+ const objectProcessor = (schema2, ctx, _json, params) => {
10912
11005
  const json2 = _json;
10913
- const def = schema._zod.def;
11006
+ const def = schema2._zod.def;
10914
11007
  json2.type = "object";
10915
11008
  json2.properties = {};
10916
11009
  const shape = def.shape;
@@ -10944,8 +11037,8 @@ const objectProcessor = (schema, ctx, _json, params) => {
10944
11037
  });
10945
11038
  }
10946
11039
  };
10947
- const unionProcessor = (schema, ctx, json2, params) => {
10948
- const def = schema._zod.def;
11040
+ const unionProcessor = (schema2, ctx, json2, params) => {
11041
+ const def = schema2._zod.def;
10949
11042
  const isExclusive = def.inclusive === false;
10950
11043
  const options = def.options.map((x, i2) => process$1(x, ctx, {
10951
11044
  ...params,
@@ -10957,8 +11050,8 @@ const unionProcessor = (schema, ctx, json2, params) => {
10957
11050
  json2.anyOf = options;
10958
11051
  }
10959
11052
  };
10960
- const intersectionProcessor = (schema, ctx, json2, params) => {
10961
- const def = schema._zod.def;
11053
+ const intersectionProcessor = (schema2, ctx, json2, params) => {
11054
+ const def = schema2._zod.def;
10962
11055
  const a2 = process$1(def.left, ctx, {
10963
11056
  ...params,
10964
11057
  path: [...params.path, "allOf", 0]
@@ -10974,9 +11067,9 @@ const intersectionProcessor = (schema, ctx, json2, params) => {
10974
11067
  ];
10975
11068
  json2.allOf = allOf;
10976
11069
  };
10977
- const tupleProcessor = (schema, ctx, _json, params) => {
11070
+ const tupleProcessor = (schema2, ctx, _json, params) => {
10978
11071
  const json2 = _json;
10979
- const def = schema._zod.def;
11072
+ const def = schema2._zod.def;
10980
11073
  json2.type = "array";
10981
11074
  const prefixPath = ctx.target === "draft-2020-12" ? "prefixItems" : "items";
10982
11075
  const restPath = ctx.target === "draft-2020-12" ? "items" : ctx.target === "openapi-3.0" ? "items" : "additionalItems";
@@ -11010,15 +11103,15 @@ const tupleProcessor = (schema, ctx, _json, params) => {
11010
11103
  json2.additionalItems = rest;
11011
11104
  }
11012
11105
  }
11013
- const { minimum, maximum } = schema._zod.bag;
11106
+ const { minimum, maximum } = schema2._zod.bag;
11014
11107
  if (typeof minimum === "number")
11015
11108
  json2.minItems = minimum;
11016
11109
  if (typeof maximum === "number")
11017
11110
  json2.maxItems = maximum;
11018
11111
  };
11019
- const recordProcessor = (schema, ctx, _json, params) => {
11112
+ const recordProcessor = (schema2, ctx, _json, params) => {
11020
11113
  const json2 = _json;
11021
- const def = schema._zod.def;
11114
+ const def = schema2._zod.def;
11022
11115
  json2.type = "object";
11023
11116
  const keyType = def.keyType;
11024
11117
  const keyBag = keyType._zod.bag;
@@ -11052,10 +11145,10 @@ const recordProcessor = (schema, ctx, _json, params) => {
11052
11145
  }
11053
11146
  }
11054
11147
  };
11055
- const nullableProcessor = (schema, ctx, json2, params) => {
11056
- const def = schema._zod.def;
11148
+ const nullableProcessor = (schema2, ctx, json2, params) => {
11149
+ const def = schema2._zod.def;
11057
11150
  const inner = process$1(def.innerType, ctx, params);
11058
- const seen = ctx.seen.get(schema);
11151
+ const seen = ctx.seen.get(schema2);
11059
11152
  if (ctx.target === "openapi-3.0") {
11060
11153
  seen.ref = def.innerType;
11061
11154
  json2.nullable = true;
@@ -11063,31 +11156,31 @@ const nullableProcessor = (schema, ctx, json2, params) => {
11063
11156
  json2.anyOf = [inner, { type: "null" }];
11064
11157
  }
11065
11158
  };
11066
- const nonoptionalProcessor = (schema, ctx, _json, params) => {
11067
- const def = schema._zod.def;
11159
+ const nonoptionalProcessor = (schema2, ctx, _json, params) => {
11160
+ const def = schema2._zod.def;
11068
11161
  process$1(def.innerType, ctx, params);
11069
- const seen = ctx.seen.get(schema);
11162
+ const seen = ctx.seen.get(schema2);
11070
11163
  seen.ref = def.innerType;
11071
11164
  };
11072
- const defaultProcessor = (schema, ctx, json2, params) => {
11073
- const def = schema._zod.def;
11165
+ const defaultProcessor = (schema2, ctx, json2, params) => {
11166
+ const def = schema2._zod.def;
11074
11167
  process$1(def.innerType, ctx, params);
11075
- const seen = ctx.seen.get(schema);
11168
+ const seen = ctx.seen.get(schema2);
11076
11169
  seen.ref = def.innerType;
11077
11170
  json2.default = JSON.parse(JSON.stringify(def.defaultValue));
11078
11171
  };
11079
- const prefaultProcessor = (schema, ctx, json2, params) => {
11080
- const def = schema._zod.def;
11172
+ const prefaultProcessor = (schema2, ctx, json2, params) => {
11173
+ const def = schema2._zod.def;
11081
11174
  process$1(def.innerType, ctx, params);
11082
- const seen = ctx.seen.get(schema);
11175
+ const seen = ctx.seen.get(schema2);
11083
11176
  seen.ref = def.innerType;
11084
11177
  if (ctx.io === "input")
11085
11178
  json2._prefault = JSON.parse(JSON.stringify(def.defaultValue));
11086
11179
  };
11087
- const catchProcessor = (schema, ctx, json2, params) => {
11088
- const def = schema._zod.def;
11180
+ const catchProcessor = (schema2, ctx, json2, params) => {
11181
+ const def = schema2._zod.def;
11089
11182
  process$1(def.innerType, ctx, params);
11090
- const seen = ctx.seen.get(schema);
11183
+ const seen = ctx.seen.get(schema2);
11091
11184
  seen.ref = def.innerType;
11092
11185
  let catchValue;
11093
11186
  try {
@@ -11097,37 +11190,37 @@ const catchProcessor = (schema, ctx, json2, params) => {
11097
11190
  }
11098
11191
  json2.default = catchValue;
11099
11192
  };
11100
- const pipeProcessor = (schema, ctx, _json, params) => {
11101
- const def = schema._zod.def;
11193
+ const pipeProcessor = (schema2, ctx, _json, params) => {
11194
+ const def = schema2._zod.def;
11102
11195
  const inIsTransform = def.in._zod.traits.has("$ZodTransform");
11103
11196
  const innerType = ctx.io === "input" ? inIsTransform ? def.out : def.in : def.out;
11104
11197
  process$1(innerType, ctx, params);
11105
- const seen = ctx.seen.get(schema);
11198
+ const seen = ctx.seen.get(schema2);
11106
11199
  seen.ref = innerType;
11107
11200
  };
11108
- const readonlyProcessor = (schema, ctx, json2, params) => {
11109
- const def = schema._zod.def;
11201
+ const readonlyProcessor = (schema2, ctx, json2, params) => {
11202
+ const def = schema2._zod.def;
11110
11203
  process$1(def.innerType, ctx, params);
11111
- const seen = ctx.seen.get(schema);
11204
+ const seen = ctx.seen.get(schema2);
11112
11205
  seen.ref = def.innerType;
11113
11206
  json2.readOnly = true;
11114
11207
  };
11115
- const promiseProcessor = (schema, ctx, _json, params) => {
11116
- const def = schema._zod.def;
11208
+ const promiseProcessor = (schema2, ctx, _json, params) => {
11209
+ const def = schema2._zod.def;
11117
11210
  process$1(def.innerType, ctx, params);
11118
- const seen = ctx.seen.get(schema);
11211
+ const seen = ctx.seen.get(schema2);
11119
11212
  seen.ref = def.innerType;
11120
11213
  };
11121
- const optionalProcessor = (schema, ctx, _json, params) => {
11122
- const def = schema._zod.def;
11214
+ const optionalProcessor = (schema2, ctx, _json, params) => {
11215
+ const def = schema2._zod.def;
11123
11216
  process$1(def.innerType, ctx, params);
11124
- const seen = ctx.seen.get(schema);
11217
+ const seen = ctx.seen.get(schema2);
11125
11218
  seen.ref = def.innerType;
11126
11219
  };
11127
- const lazyProcessor = (schema, ctx, _json, params) => {
11128
- const innerType = schema._zod.innerType;
11220
+ const lazyProcessor = (schema2, ctx, _json, params) => {
11221
+ const innerType = schema2._zod.innerType;
11129
11222
  process$1(innerType, ctx, params);
11130
- const seen = ctx.seen.get(schema);
11223
+ const seen = ctx.seen.get(schema2);
11131
11224
  seen.ref = innerType;
11132
11225
  };
11133
11226
  const allProcessors = {
@@ -11177,8 +11270,8 @@ function toJSONSchema(input, params) {
11177
11270
  const ctx2 = initializeContext({ ...params, processors: allProcessors });
11178
11271
  const defs = {};
11179
11272
  for (const entry of registry2._idmap.entries()) {
11180
- const [_, schema] = entry;
11181
- process$1(schema, ctx2);
11273
+ const [_, schema2] = entry;
11274
+ process$1(schema2, ctx2);
11182
11275
  }
11183
11276
  const schemas = {};
11184
11277
  const external = {
@@ -11188,9 +11281,9 @@ function toJSONSchema(input, params) {
11188
11281
  };
11189
11282
  ctx2.external = external;
11190
11283
  for (const entry of registry2._idmap.entries()) {
11191
- const [key, schema] = entry;
11192
- extractDefs(ctx2, schema);
11193
- schemas[key] = finalize(ctx2, schema);
11284
+ const [key, schema2] = entry;
11285
+ extractDefs(ctx2, schema2);
11286
+ schemas[key] = finalize(ctx2, schema2);
11194
11287
  }
11195
11288
  if (Object.keys(defs).length > 0) {
11196
11289
  const defsSegment = ctx2.target === "draft-2020-12" ? "$defs" : "definitions";
@@ -11814,6 +11907,18 @@ function union(options, params) {
11814
11907
  ...normalizeParams(params)
11815
11908
  });
11816
11909
  }
11910
+ const ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("ZodDiscriminatedUnion", (inst, def) => {
11911
+ ZodUnion.init(inst, def);
11912
+ $ZodDiscriminatedUnion.init(inst, def);
11913
+ });
11914
+ function discriminatedUnion(discriminator, options, params) {
11915
+ return new ZodDiscriminatedUnion({
11916
+ type: "union",
11917
+ options,
11918
+ discriminator,
11919
+ ...normalizeParams(params)
11920
+ });
11921
+ }
11817
11922
  const ZodIntersection = /* @__PURE__ */ $constructor("ZodIntersection", (inst, def) => {
11818
11923
  $ZodIntersection.init(inst, def);
11819
11924
  ZodType.init(inst, def);
@@ -11895,6 +12000,27 @@ function _enum(values, params) {
11895
12000
  ...normalizeParams(params)
11896
12001
  });
11897
12002
  }
12003
+ const ZodLiteral = /* @__PURE__ */ $constructor("ZodLiteral", (inst, def) => {
12004
+ $ZodLiteral.init(inst, def);
12005
+ ZodType.init(inst, def);
12006
+ inst._zod.processJSONSchema = (ctx, json2, params) => literalProcessor(inst, ctx, json2);
12007
+ inst.values = new Set(def.values);
12008
+ Object.defineProperty(inst, "value", {
12009
+ get() {
12010
+ if (def.values.length > 1) {
12011
+ throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
12012
+ }
12013
+ return def.values[0];
12014
+ }
12015
+ });
12016
+ });
12017
+ function literal(value, params) {
12018
+ return new ZodLiteral({
12019
+ type: "literal",
12020
+ values: Array.isArray(value) ? value : [value],
12021
+ ...normalizeParams(params)
12022
+ });
12023
+ }
11898
12024
  const ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
11899
12025
  $ZodTransform.init(inst, def);
11900
12026
  ZodType.init(inst, def);
@@ -12460,51 +12586,71 @@ function cleanText(value, maxLength) {
12460
12586
  }
12461
12587
  const DEFAULT_MODEL$1 = { id: "default", label: "Default (CLI)" };
12462
12588
  class CommitMessageService {
12463
- constructor(operations, providers, log, invalidateDiscovery) {
12589
+ constructor(operations, providers, log, invalidateDiscovery, statusPersistence) {
12464
12590
  this.operations = operations;
12465
12591
  this.log = log;
12466
12592
  this.invalidateDiscovery = invalidateDiscovery;
12593
+ this.statusPersistence = statusPersistence;
12467
12594
  for (const provider of providers) this.providers.set(provider.id, provider);
12468
12595
  }
12469
12596
  operations;
12470
12597
  log;
12471
12598
  invalidateDiscovery;
12599
+ statusPersistence;
12472
12600
  providers = /* @__PURE__ */ new Map();
12473
12601
  active = /* @__PURE__ */ new Map();
12474
12602
  statusCache = null;
12603
+ statusInvalidated = false;
12475
12604
  statusGeneration = 0;
12476
12605
  pendingStatus;
12477
12606
  invalidateStatuses() {
12478
12607
  this.statusGeneration++;
12479
12608
  this.statusCache = null;
12609
+ this.statusInvalidated = true;
12480
12610
  this.pendingStatus = void 0;
12481
12611
  this.invalidateDiscovery?.();
12482
12612
  }
12483
12613
  async statuses(forceRefresh = false) {
12484
- if (!forceRefresh && this.statusCache && Date.now() - this.statusCache.at < 3e4) return this.statusCache.value;
12485
- if (this.pendingStatus && (!forceRefresh || this.pendingStatus.force)) return this.pendingStatus.promise;
12614
+ const key = this.statusPersistence?.key() ?? "";
12615
+ if (!forceRefresh && this.statusCache?.key === key) return this.statusCache.value;
12616
+ if (this.pendingStatus?.key === key && (!forceRefresh || this.pendingStatus.force)) return this.pendingStatus.promise;
12486
12617
  const generation = ++this.statusGeneration;
12487
- const promise = Promise.all([...this.providers.values()].map(async (provider) => {
12488
- try {
12489
- return await provider.status(forceRefresh);
12490
- } catch {
12491
- return {
12492
- id: provider.id,
12493
- label: { codex: "Codex", claude: "Claude Code", opencode: "OpenCode", grok: "Grok Build" }[provider.id],
12494
- installed: false,
12495
- availability: "error",
12496
- authStatus: "unknown",
12497
- installationStatus: "inspection-failed",
12498
- message: "Could not inspect this CLI. Check again or review its executable path.",
12499
- models: [DEFAULT_MODEL$1],
12500
- checkedAt: (/* @__PURE__ */ new Date()).toISOString()
12501
- };
12618
+ const promise = (async () => {
12619
+ if (!forceRefresh && !this.statusInvalidated && this.statusPersistence) {
12620
+ const saved = await this.statusPersistence.store.load(key).catch(() => null);
12621
+ if (saved?.length === this.providers.size && saved.every((status) => this.providers.has(status.id))) {
12622
+ if (generation === this.statusGeneration) this.statusCache = { key, value: saved };
12623
+ return saved;
12624
+ }
12625
+ }
12626
+ const value = await Promise.all([...this.providers.values()].map(async (provider) => {
12627
+ try {
12628
+ return { ...await provider.status(forceRefresh), checkedAt: (/* @__PURE__ */ new Date()).toISOString() };
12629
+ } catch {
12630
+ return {
12631
+ id: provider.id,
12632
+ label: { codex: "Codex", claude: "Claude Code", opencode: "OpenCode", grok: "Grok Build" }[provider.id],
12633
+ installed: false,
12634
+ availability: "error",
12635
+ authStatus: "unknown",
12636
+ installationStatus: "inspection-failed",
12637
+ message: "Could not inspect this CLI. Check again or review its executable path.",
12638
+ models: [DEFAULT_MODEL$1],
12639
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
12640
+ };
12641
+ }
12642
+ }));
12643
+ if (generation === this.statusGeneration) {
12644
+ this.statusCache = { key, value };
12645
+ this.statusInvalidated = false;
12646
+ try {
12647
+ await this.statusPersistence?.store.save(key, value);
12648
+ } catch {
12649
+ }
12502
12650
  }
12503
- })).then((value) => {
12504
- if (generation === this.statusGeneration) this.statusCache = { at: Date.now(), value };
12505
12651
  return value;
12506
- });
12507
- this.pendingStatus = { force: forceRefresh, promise };
12652
+ })();
12653
+ this.pendingStatus = { force: forceRefresh, key, promise };
12508
12654
  try {
12509
12655
  return await promise;
12510
12656
  } finally {
@@ -12896,8 +13042,8 @@ function label(harness) {
12896
13042
  function stripAnsi(value) {
12897
13043
  return value.replace(new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g"), "");
12898
13044
  }
12899
- function toCodexOutputSchema(schema) {
12900
- return strictify(structuredClone(schema));
13045
+ function toCodexOutputSchema(schema2) {
13046
+ return strictify(structuredClone(schema2));
12901
13047
  }
12902
13048
  function strictify(value) {
12903
13049
  if (!value || typeof value !== "object") return value;
@@ -13051,9 +13197,9 @@ class CodexProvider {
13051
13197
  const failure2 = detectionFailure(detected, { id: this.id, label: "Codex", models: [DEFAULT_MODEL$1], checkedAt });
13052
13198
  if (failure2) return failure2;
13053
13199
  const executable = requireCandidate(detected, this.id);
13054
- const login = await runCandidate(this.runner, executable, ["login", "status"]);
13055
- if (login.exitCode !== 0 || !/logged in/i.test(`${login.stdout}
13056
- ${login.stderr}`)) {
13200
+ const login2 = await runCandidate(this.runner, executable, ["login", "status"]);
13201
+ if (login2.exitCode !== 0 || !/logged in/i.test(`${login2.stdout}
13202
+ ${login2.stderr}`)) {
13057
13203
  return { ...detectionFields(detected), id: this.id, label: "Codex", availability: "error", installed: true, authStatus: "unauthenticated", version: detected.version ?? "", message: "Run codex login.", models: [DEFAULT_MODEL$1], checkedAt };
13058
13204
  }
13059
13205
  const catalog = await runCandidate(this.runner, executable, ["debug", "models", "--bundled"]);
@@ -13547,11 +13693,10 @@ class OpenCodeProvider {
13547
13693
  const executable = requireCandidate(detected, this.id);
13548
13694
  const version2 = detected.version ?? "";
13549
13695
  const cliName = openCodeCliName(executable.executable);
13550
- const [auth, catalog] = await Promise.all([
13696
+ const [auth, models] = await Promise.all([
13551
13697
  runCandidate(this.runner, executable, ["auth", "list", "--format", "json", "--standalone"]),
13552
- runCandidate(this.runner, executable, ["models", "--standalone"], { timeoutMs: 3e4 })
13698
+ this.loadModels(executable)
13553
13699
  ]);
13554
- const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
13555
13700
  let authStatus = parseOpenCodeAuthList(auth.stdout, auth.exitCode);
13556
13701
  if (authStatus === "unauthenticated" && models.length > 1) authStatus = "unknown";
13557
13702
  const confirmed = authStatus === "authenticated";
@@ -13566,9 +13711,20 @@ class OpenCodeProvider {
13566
13711
  version: version2,
13567
13712
  models,
13568
13713
  checkedAt,
13569
- ...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
13714
+ ...authStatus === "unauthenticated" ? { message: `Run ${openCodeLoginCommand(cliName)}.` } : models.length === 1 ? { message: `Could not load the OpenCode model catalog. Check again or verify that ${cliName} models lists your models in a terminal.` } : !confirmed ? { message: "Could not confirm authentication for the selected provider." } : {}
13570
13715
  };
13571
13716
  }
13717
+ async loadModels(executable) {
13718
+ for (const args of [["models", "--standalone"], ["models"]]) {
13719
+ try {
13720
+ const catalog = await runCandidate(this.runner, executable, args, { timeoutMs: 3e4 });
13721
+ const models = catalog.exitCode === 0 ? parseOpenCodeModels(catalog.stdout) : [DEFAULT_MODEL$1];
13722
+ if (models.length > 1) return models;
13723
+ } catch {
13724
+ }
13725
+ }
13726
+ return [DEFAULT_MODEL$1];
13727
+ }
13572
13728
  async generate(input) {
13573
13729
  const detected = await this.detect(false, input.signal);
13574
13730
  const executable = requireCandidate(detected, this.id);
@@ -15140,6 +15296,313 @@ function parseWorktrees(buffer) {
15140
15296
  if (current) worktrees.push(current);
15141
15297
  return worktrees;
15142
15298
  }
15299
+ const commitAuthorshipInputSchema = object({
15300
+ name: string().trim().min(1).max(120).regex(/^[^<>]+$/).refine(noControls),
15301
+ email: string().trim().max(254).regex(/^[^\s<>@]+@[^\s<>@]+$/).refine(noControls),
15302
+ expectedRevision: string().regex(/^[a-f0-9]{64}$/)
15303
+ });
15304
+ function noControls(value) {
15305
+ return [...value].every((character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127);
15306
+ }
15307
+ const environmentKeys = ["GIT_AUTHOR_NAME", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_NAME", "GIT_COMMITTER_EMAIL", "EMAIL"];
15308
+ const identityPattern = "^(user|author|committer)\\.(name|email)$";
15309
+ class GitCommitAuthorship {
15310
+ constructor(git, repositories) {
15311
+ this.git = git;
15312
+ this.repositories = repositories;
15313
+ }
15314
+ git;
15315
+ repositories;
15316
+ revisionKey = randomBytes(32);
15317
+ async get(repositoryId) {
15318
+ const repository = this.repositories.get(repositoryId);
15319
+ return this.git.runWriteTask(repository.path, () => this.read(repositoryId), repository.commonDir);
15320
+ }
15321
+ async set(repositoryId, input) {
15322
+ const parsed = commitAuthorshipInputSchema.safeParse(input);
15323
+ if (!parsed.success) throw invalid$1("Enter a valid name, email and reviewed identity.");
15324
+ const { name, email: email2, expectedRevision } = parsed.data;
15325
+ const repository = this.repositories.get(repositoryId);
15326
+ const configPath = path.join(repository.commonDir, "config");
15327
+ const lockPath = `${configPath}.lock`;
15328
+ return this.git.runWriteTask(repository.path, async (run) => {
15329
+ const current = await this.read(repositoryId);
15330
+ if (!current.editable) throw invalid$1(current.blockers.join(" "));
15331
+ if (current.revision !== expectedRevision) throw invalid$1("Git identity or repository settings changed. Reopen the editor and review them again.");
15332
+ let locked = false;
15333
+ try {
15334
+ const file = await open(lockPath, "wx", 384);
15335
+ locked = true;
15336
+ try {
15337
+ await file.writeFile(await readFile(configPath));
15338
+ } finally {
15339
+ await file.close();
15340
+ }
15341
+ const reviewed = await this.read(repositoryId);
15342
+ if (!reviewed.editable || reviewed.revision !== expectedRevision) throw invalid$1("Git settings changed before saving. Reopen the editor and review them again.");
15343
+ const mode = (await lstat(configPath)).mode & 511;
15344
+ await run(["config", "--file", lockPath, "--replace-all", "--", "user.name", name], { operation: "commit-authorship-set" });
15345
+ await run(["config", "--file", lockPath, "--replace-all", "--", "user.email", email2], { operation: "commit-authorship-set" });
15346
+ const candidate = await this.identities(repository.path, ["-c", `include.path=${lockPath}`]);
15347
+ if (candidate.author?.name !== name || candidate.author.email !== email2 || candidate.committer?.name !== name || candidate.committer.email !== email2) {
15348
+ throw invalid$1("An included Git setting overrides this identity. Edit that setting outside OpenTig before applying a repository identity.");
15349
+ }
15350
+ const latest = await this.read(repositoryId);
15351
+ if (!latest.editable || latest.revision !== expectedRevision) throw invalid$1("Git settings changed while saving. Reopen the editor and review them again.");
15352
+ await chmod(lockPath, mode);
15353
+ await rename(lockPath, configPath);
15354
+ locked = false;
15355
+ } catch (error) {
15356
+ if (error.code === "EEXIST") {
15357
+ throw invalid$1("Another Git process is editing repository settings. Wait for it to finish and try again.");
15358
+ }
15359
+ throw error;
15360
+ } finally {
15361
+ if (locked) await rm(lockPath, { force: true });
15362
+ }
15363
+ return this.read(repositoryId);
15364
+ }, repository.commonDir);
15365
+ }
15366
+ async read(repositoryId) {
15367
+ const repository = this.repositories.get(repositoryId);
15368
+ const configPath = path.join(repository.commonDir, "config");
15369
+ const [entries, identities, config2, info] = await Promise.all([
15370
+ this.config(repository.path),
15371
+ this.identities(repository.path),
15372
+ readFile(configPath),
15373
+ lstat(configPath)
15374
+ ]);
15375
+ const environment = environmentKeys.filter((key) => process.env[key] !== void 0);
15376
+ const custom = entries.some((entry) => entry.key.startsWith("author.") || entry.key.startsWith("committer."));
15377
+ const worktree = entries.some((entry) => entry.scope === "worktree");
15378
+ const command2 = entries.some((entry) => entry.scope === "command");
15379
+ const blockers = [];
15380
+ if (environment.length) blockers.push("Backend environment variables override Git identity. Manage them outside OpenTig before editing repository authorship.");
15381
+ if (custom) blockers.push("Git author.* or committer.* settings override user.name/email. Manage those settings outside OpenTig first.");
15382
+ if (worktree) blockers.push("This worktree has its own identity. Manage its worktree settings outside OpenTig first.");
15383
+ if (command2) blockers.push("Backend command configuration overrides Git identity. Manage it outside OpenTig first.");
15384
+ if (!info.isFile() || info.nlink !== 1) blockers.push("The repository config is linked or shared. Edit it outside OpenTig to avoid changing another repository.");
15385
+ const scopes = ["user.name", "user.email"].map((key) => entries.filter((entry) => entry.key === key).at(-1)?.scope ?? "inferred");
15386
+ const source = environment.length || command2 ? "environment" : custom ? "custom" : worktree ? "worktree" : !identities.author ? "unset" : scopes[0] !== scopes[1] ? "mixed" : scopes[0] === "local" || scopes[0] === "global" || scopes[0] === "system" ? scopes[0] : "inferred";
15387
+ const revision2 = createHmac("sha256", this.revisionKey).update(repository.path).update(config2).update(JSON.stringify({ entries, identities, environment, mode: info.mode, linked: info.nlink, blockers })).digest("hex");
15388
+ return { ...identities, source, editable: blockers.length === 0, blockers, revision: revision2 };
15389
+ }
15390
+ async config(cwd) {
15391
+ try {
15392
+ const output = await this.git.run(cwd, ["config", "--null", "--show-scope", "--get-regexp", identityPattern], { operation: "commit-authorship-read", readOnly: true });
15393
+ const fields = output.stdout.toString("utf8").split("\0");
15394
+ const entries = [];
15395
+ for (let index = 0; index + 1 < fields.length; index += 2) {
15396
+ const field = fields[index + 1];
15397
+ const separator = field.indexOf("\n");
15398
+ if (separator >= 0) entries.push({ scope: fields[index], key: field.slice(0, separator), value: field.slice(separator + 1) });
15399
+ }
15400
+ return entries;
15401
+ } catch (error) {
15402
+ if (error instanceof GitOperationError && error.detail.exitCode === 1) return [];
15403
+ throw error;
15404
+ }
15405
+ }
15406
+ async identities(cwd, prefix = []) {
15407
+ const [author, committer] = await Promise.all(["GIT_AUTHOR_IDENT", "GIT_COMMITTER_IDENT"].map(async (variable) => {
15408
+ try {
15409
+ const output = await this.git.run(cwd, [...prefix, "var", variable], { operation: "commit-authorship-read", readOnly: true });
15410
+ const match = /^(.*?) <([^<>]*)> \d+ [+-]\d{4}$/.exec(output.stdout.toString("utf8").trim());
15411
+ if (!match) throw invalid$1("Git returned an unreadable commit identity.");
15412
+ return { name: match[1], email: match[2] };
15413
+ } catch (error) {
15414
+ if (error instanceof GitOperationError && error.detail.exitCode === 128 && /identity unknown|unable to auto-detect|empty ident|no email was given|no name was given/i.test(error.detail.stderr ?? "")) return null;
15415
+ throw error;
15416
+ }
15417
+ }));
15418
+ return { author, committer };
15419
+ }
15420
+ }
15421
+ function invalid$1(message) {
15422
+ return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "commit-authorship-set", message });
15423
+ }
15424
+ const read = { operation: "remote-branches", readOnly: true, maxOutputBytes: 4 * 1024 * 1024 };
15425
+ const network = { ...read, timeoutMs: 6e4 };
15426
+ class GitRemoteBranches {
15427
+ constructor(git, repositories) {
15428
+ this.git = git;
15429
+ this.repositories = repositories;
15430
+ }
15431
+ git;
15432
+ repositories;
15433
+ reader(root) {
15434
+ return (args, options) => this.git.run(root, args, options);
15435
+ }
15436
+ async remotes(run) {
15437
+ const names = (await run(["remote"], read)).stdout.toString("utf8").trim().split(/\r?\n/).filter(Boolean);
15438
+ return Promise.all(names.map(async (name) => ({ name, refspecs: (await this.config(run, `remote.${name}.fetch`)).split(/\r?\n/).filter(Boolean) })));
15439
+ }
15440
+ async config(run, key) {
15441
+ try {
15442
+ return (await run(["config", "--get-all", key], read)).stdout.toString("utf8").trim();
15443
+ } catch (error) {
15444
+ if (error instanceof GitOperationError && error.detail.exitCode === 1) return "";
15445
+ throw error;
15446
+ }
15447
+ }
15448
+ mappings(fullName, remotes) {
15449
+ return remotes.flatMap((remote) => remote.refspecs.flatMap((spec) => {
15450
+ if (spec.startsWith("^")) return [];
15451
+ const [source, destination] = spec.replace(/^\+/, "").split(":");
15452
+ if (!source?.startsWith("refs/heads/") || !destination?.startsWith("refs/remotes/")) return [];
15453
+ const match = refspecMatch(destination, fullName);
15454
+ if (match === null) return [];
15455
+ const sourceRef = source.replace("*", match);
15456
+ if (remote.refspecs.some((excluded) => excluded.startsWith("^") && refspecMatch(excluded.slice(1), sourceRef) !== null)) return [];
15457
+ return [{ remote: remote.name, sourceRef }];
15458
+ }));
15459
+ }
15460
+ async snapshotBranches(repositoryId, branches) {
15461
+ const root = this.repositories.get(repositoryId).path;
15462
+ const run = this.reader(root);
15463
+ const remotes = await this.remotes(run);
15464
+ const aliases = new Set((await run(["for-each-ref", "--format=%(refname)%09%(symref)", "refs/remotes"], read)).stdout.toString("utf8").split(/\r?\n/).filter((line) => line.split(" ")[1]).map((line) => line.split(" ")[0]));
15465
+ return { remotes: remotes.map((remote) => remote.name), branches: branches.filter((branch) => !aliases.has(branch.fullName)).map((branch) => {
15466
+ if (!branch.remote) return branch;
15467
+ const mappings = this.mappings(branch.fullName, remotes);
15468
+ if (mappings.length !== 1) return branch;
15469
+ return { ...branch, remoteName: mappings[0].remote, remoteBranchName: mappings[0].sourceRef.slice("refs/heads/".length) };
15470
+ }) };
15471
+ }
15472
+ async target(fullName, run) {
15473
+ const branches = parseRefs((await run(["for-each-ref", `--format=${REF_FORMAT}`, "refs/remotes"], read)).stdout);
15474
+ const branch = branches.find((item) => item.fullName === fullName);
15475
+ if (!branch) return null;
15476
+ const aliases = (await run(["for-each-ref", "--format=%(refname)%09%(symref)", fullName], read)).stdout.toString("utf8").split(/\r?\n/).some((line) => line.split(" ")[0] === fullName && Boolean(line.split(" ")[1]));
15477
+ const mappings = this.mappings(fullName, await this.remotes(run));
15478
+ if (aliases || mappings.length !== 1) throw new GitOperationError({
15479
+ code: "INVALID_ARGUMENT",
15480
+ operation: "remote-branch",
15481
+ message: "This reference does not map to one configured remote branch. Check the remote fetch settings."
15482
+ });
15483
+ return { branch, ...mappings[0], branchName: mappings[0].sourceRef.slice("refs/heads/".length) };
15484
+ }
15485
+ async destination(target, run) {
15486
+ const fetchUrls = (await run(["remote", "get-url", "--all", "--", target.remote], read)).stdout.toString("utf8").trim().split(/\r?\n/);
15487
+ const pushUrls = (await run(["remote", "get-url", "--push", "--all", "--", target.remote], read)).stdout.toString("utf8").trim().split(/\r?\n/);
15488
+ const id = createHash("sha256").update(JSON.stringify([target.remote, target.sourceRef, fetchUrls, pushUrls])).digest("hex");
15489
+ const blocked = fetchUrls.length !== 1 || pushUrls.length !== 1 || fetchUrls[0] !== pushUrls[0] ? "Fetch and push destinations differ or have multiple URLs. Manage remote deletion outside OpenTig." : null;
15490
+ return { id, blocked, url: fetchUrls[0] };
15491
+ }
15492
+ async advertised(target, run, url) {
15493
+ const lines = (await run(["ls-remote", "--symref", "--", url, "HEAD", target.sourceRef], network)).stdout.toString("utf8").split(/\r?\n/);
15494
+ const defaultRef = lines.find((line) => line.startsWith("ref: refs/heads/") && line.endsWith(" HEAD"))?.split(" ")[0]?.slice(5) ?? null;
15495
+ const oid = lines.find((line) => line.split(" ")[1] === target.sourceRef)?.split(" ")[0] ?? null;
15496
+ return { defaultRef, oid };
15497
+ }
15498
+ async details(repositoryId, fullName) {
15499
+ const run = this.reader(this.repositories.get(repositoryId).path);
15500
+ const target = await this.target(fullName, run);
15501
+ if (!target) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "remote-branch-details", message: "The remote reference no longer exists. Fetch branches again." });
15502
+ const destination = await this.destination(target, run);
15503
+ let blocked = destination.blocked;
15504
+ let defaultRef = null;
15505
+ let remoteState = "unchecked";
15506
+ if (!blocked) {
15507
+ try {
15508
+ const state = await this.advertised(target, run, destination.url);
15509
+ defaultRef = state.defaultRef;
15510
+ remoteState = !state.oid ? "missing" : state.oid === target.branch.oid ? "current" : "changed";
15511
+ blocked = !state.oid ? "The branch no longer exists on the remote. Fetch to remove the obsolete reference." : state.oid !== target.branch.oid ? "The remote branch changed since the last fetch. Fetch before deleting." : !state.defaultRef ? "The remote does not identify its default branch. Deletion is unavailable here." : state.defaultRef === target.sourceRef ? "This is the remote’s default branch and cannot be deleted here." : null;
15512
+ } catch {
15513
+ blocked = "Could not check the remote. Fetch again or check your connection and Git permissions.";
15514
+ }
15515
+ }
15516
+ const upstreams = (await run(["for-each-ref", "--format=%(refname)%00%(refname:short)%00%(upstream)%00%(worktreepath)", "refs/heads"], read)).stdout.toString("utf8");
15517
+ const localBranches = upstreams.split(/\r?\n/).map((line) => line.split("\0")).filter((fields) => fields[2] === fullName).map(([ref, name, , worktreePath]) => ({ fullName: ref, name, worktreePath: worktreePath || null }));
15518
+ return {
15519
+ ...target.branch,
15520
+ remote: target.remote,
15521
+ branchName: target.branchName,
15522
+ localBranches,
15523
+ defaultBranch: defaultRef?.slice("refs/heads/".length) ?? null,
15524
+ remoteState,
15525
+ destinationId: destination.id,
15526
+ deletionBlockedReason: blocked
15527
+ };
15528
+ }
15529
+ async create(request) {
15530
+ const repository = this.repositories.get(request.repositoryId);
15531
+ return this.git.runWriteTask(repository.path, async (run) => {
15532
+ const target = await this.target(request.fullName, run);
15533
+ if (!target) return { status: "missing" };
15534
+ if (target.branch.oid !== request.expectedOid) return { status: "stale" };
15535
+ if (request.localName.startsWith("-")) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "create-tracking-branch", message: "Invalid local branch name." });
15536
+ await run(["check-ref-format", `refs/heads/${request.localName}`], read);
15537
+ const localRef = `refs/heads/${request.localName}`;
15538
+ const existing = (await run(["for-each-ref", "--format=%(refname)", "refs/heads"], read)).stdout.toString("utf8").trim().split(/\r?\n/);
15539
+ if (existing.includes(localRef)) return { status: "exists" };
15540
+ await run(["branch", "--track", "--", request.localName, target.branch.fullName], { operation: "create-tracking-branch" });
15541
+ return { status: "created", fullName: localRef };
15542
+ }, repository.commonDir);
15543
+ }
15544
+ async delete(request) {
15545
+ const repository = this.repositories.get(request.repositoryId);
15546
+ return this.git.runWriteTask(repository.path, async (run) => {
15547
+ const target = await this.target(request.fullName, run);
15548
+ if (!target) return { status: "missing" };
15549
+ if (target.branch.oid !== request.expectedOid) return { status: "stale" };
15550
+ const destination = await this.destination(target, run);
15551
+ if (destination.id !== request.destinationId) return { status: "destination-changed" };
15552
+ if (destination.blocked) return { status: "rejected", message: destination.blocked };
15553
+ try {
15554
+ const state = await this.advertised(target, run, destination.url);
15555
+ if (!state.oid) return { status: "missing" };
15556
+ if (!state.defaultRef) return { status: "rejected", message: "Could not identify the remote default branch. Nothing was deleted." };
15557
+ if (state.defaultRef === target.sourceRef) return { status: "default" };
15558
+ if (state.oid !== request.expectedOid) return { status: "stale" };
15559
+ await run(
15560
+ [
15561
+ "-c",
15562
+ `remote.${target.remote}.mirror=false`,
15563
+ "push",
15564
+ "--porcelain",
15565
+ "--no-follow-tags",
15566
+ `--force-with-lease=${target.sourceRef}:${request.expectedOid}`,
15567
+ "--",
15568
+ destination.url,
15569
+ `:${target.sourceRef}`
15570
+ ],
15571
+ { operation: "delete-remote-branch", timeoutMs: 12e4, maxOutputBytes: 4 * 1024 * 1024 }
15572
+ );
15573
+ return { status: "deleted" };
15574
+ } catch {
15575
+ return { status: "rejected", message: "Git refused the remote deletion. Check permissions or branch protection, fetch, and try again. No local branch or worktree was removed." };
15576
+ }
15577
+ }, repository.commonDir);
15578
+ }
15579
+ async fetch(repositoryId) {
15580
+ const repository = this.repositories.get(repositoryId);
15581
+ try {
15582
+ await this.git.runWriteTask(repository.path, async (run) => {
15583
+ for (const remote of await this.remotes(run)) {
15584
+ const specs = remote.refspecs.filter((spec) => spec.startsWith("^refs/heads/") || /^\+?refs\/heads\/[^:]+:refs\/remotes\//.test(spec));
15585
+ if (!specs.some((spec) => !spec.startsWith("^"))) continue;
15586
+ await run(
15587
+ ["-c", "fetch.pruneTags=false", "fetch", "--prune", "--no-prune-tags", "--no-tags", "--no-recurse-submodules", "--", remote.name, ...specs],
15588
+ { operation: "fetch-branches", timeoutMs: 12e4, maxOutputBytes: 4 * 1024 * 1024 }
15589
+ );
15590
+ }
15591
+ }, repository.commonDir);
15592
+ } catch {
15593
+ return { status: "failed", message: "Could not fetch all remote branches. Check your connection and Git credentials, then fetch again." };
15594
+ }
15595
+ const status = await this.repositories.status(repositoryId, false);
15596
+ return { status: "success", ahead: status.ahead, behind: status.behind };
15597
+ }
15598
+ }
15599
+ function refspecMatch(pattern, ref) {
15600
+ const star = pattern.indexOf("*");
15601
+ if (star < 0) return pattern === ref ? "" : null;
15602
+ const prefix = pattern.slice(0, star);
15603
+ const suffix = pattern.slice(star + 1);
15604
+ return ref.startsWith(prefix) && ref.endsWith(suffix) && ref.length >= prefix.length + suffix.length ? ref.slice(prefix.length, suffix ? -suffix.length : void 0) : null;
15605
+ }
15143
15606
  const AI_PATCH_BUDGET = 4e5;
15144
15607
  const AI_SUMMARY_BUDGET = 24e3;
15145
15608
  class GitRepositoryOperations {
@@ -15147,11 +15610,33 @@ class GitRepositoryOperations {
15147
15610
  this.git = git;
15148
15611
  this.repositories = repositories;
15149
15612
  this.files = files;
15613
+ this.authorship = new GitCommitAuthorship(git, repositories);
15614
+ this.remoteBranches = new GitRemoteBranches(git, repositories);
15150
15615
  }
15151
15616
  git;
15152
15617
  repositories;
15153
15618
  files;
15154
15619
  knownOids = /* @__PURE__ */ new Map();
15620
+ authorship;
15621
+ remoteBranches;
15622
+ remoteBranchDetails(repositoryId, fullName) {
15623
+ return this.remoteBranches.details(repositoryId, fullName);
15624
+ }
15625
+ createTrackingBranch(request) {
15626
+ return this.remoteBranches.create(request);
15627
+ }
15628
+ deleteRemoteBranch(request) {
15629
+ return this.remoteBranches.delete(request);
15630
+ }
15631
+ fetchBranches(repositoryId) {
15632
+ return this.remoteBranches.fetch(repositoryId);
15633
+ }
15634
+ getCommitAuthorship(repositoryId) {
15635
+ return this.authorship.get(repositoryId);
15636
+ }
15637
+ setCommitAuthorship(repositoryId, input) {
15638
+ return this.authorship.set(repositoryId, input);
15639
+ }
15155
15640
  async diff(request) {
15156
15641
  const repository = this.repositories.get(request.repositoryId);
15157
15642
  const paths = this.repositories.validatePaths(request.repositoryId, [request.path]);
@@ -15736,15 +16221,16 @@ ${base}
15736
16221
  return this.repositories.openPath(worktree.path);
15737
16222
  }
15738
16223
  /**
15739
- * Cheap enumeration for the local refs manager. Deliberately runs no status
16224
+ * Cheap enumeration for the refs manager. Deliberately runs no status
15740
16225
  * scan per worktree so ordinary refreshes stay as fast as the selectors.
15741
16226
  */
15742
16227
  async localRefsSnapshot(repositoryId) {
15743
16228
  const repository = this.repositories.get(repositoryId);
15744
16229
  const [branches, worktrees] = await Promise.all([this.branches(repositoryId), this.worktrees(repositoryId)]);
15745
- const locals = branches.filter((branch) => !branch.remote).sort(compareBranches);
16230
+ const managed = await this.remoteBranches.snapshotBranches(repositoryId, branches);
15746
16231
  return {
15747
- branches: locals,
16232
+ branches: managed.branches.sort(compareBranches),
16233
+ remotes: managed.remotes,
15748
16234
  worktrees: await Promise.all(worktrees.map(async (worktree) => ({ ...worktree, current: await samePath(worktree.path, repository.path) })))
15749
16235
  };
15750
16236
  }
@@ -16692,72 +17178,14 @@ function revision(bytes) {
16692
17178
  function invalidReplacement(message) {
16693
17179
  return new GitOperationError({ code: "INVALID_ARGUMENT", operation: "replace-search", message });
16694
17180
  }
16695
- const positive = number().int().positive().safe();
16696
- const membershipSchema = object({
16697
- stack: object({ number: positive, size: positive, baseRefName: string().min(1) }).nullable(),
16698
- stackEntry: object({ position: positive }).nullable()
16699
- });
16700
- const stackSchema = object({
16701
- number: positive,
16702
- base: union([string().min(1), object({ ref: string().min(1) })]),
16703
- pull_requests: array(object({
16704
- number: positive,
16705
- title: string().optional(),
16706
- draft: boolean().optional(),
16707
- head: object({ ref: string().min(1) }),
16708
- state: _enum(["open", "closed", "merged"]),
16709
- merged_at: string().nullable().optional()
16710
- })).min(1)
16711
- });
16712
- function invalid() {
16713
- return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation: "gh-pr-stack", message: "GitHub returned unreadable stack information." });
16714
- }
16715
- function json(raw) {
16716
- try {
16717
- return JSON.parse(raw);
16718
- } catch {
16719
- throw invalid();
16720
- }
16721
- }
16722
- function stackMembershipQuery(numbers) {
16723
- if (!numbers.length || numbers.some((number2) => !positive.safeParse(number2).success)) throw invalid();
16724
- return `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${numbers.map((number2) => `pr${number2}:pullRequest(number:${number2}){stack{number size baseRefName} stackEntry{position}}`).join(" ")}}}`;
16725
- }
16726
- function parseStackMemberships(raw, numbers) {
16727
- const envelope = object({ data: object({ repository: record(string(), membershipSchema.nullable()) }), errors: array(unknown()).optional() }).safeParse(json(raw));
16728
- if (!envelope.success || envelope.data.errors?.length) throw invalid();
16729
- const result = /* @__PURE__ */ new Map();
16730
- for (const number2 of numbers) {
16731
- const entry = envelope.data.data.repository[`pr${number2}`];
16732
- if (entry === void 0) throw invalid();
16733
- if (!entry?.stack && !entry?.stackEntry) continue;
16734
- if (!entry?.stack || !entry.stackEntry || entry.stackEntry.position > entry.stack.size) throw invalid();
16735
- result.set(number2, { number: entry.stack.number, size: entry.stack.size, position: entry.stackEntry.position, base: entry.stack.baseRefName });
16736
- }
16737
- return result;
16738
- }
16739
- function parsePullRequestStack(raw, prNumber, listing = false) {
16740
- let value = json(raw);
16741
- if (listing) {
16742
- if (!Array.isArray(value) || value.length > 1) throw invalid();
16743
- if (!value.length) return null;
16744
- value = value[0];
16745
- }
16746
- const parsed = stackSchema.safeParse(value);
16747
- if (!parsed.success) throw invalid();
16748
- const stack = parsed.data;
16749
- if (!stack.pull_requests.some((pr) => pr.number === prNumber) || new Set(stack.pull_requests.map((pr) => pr.number)).size !== stack.pull_requests.length) throw invalid();
16750
- return {
16751
- number: stack.number,
16752
- base: typeof stack.base === "string" ? stack.base : stack.base.ref,
16753
- layers: stack.pull_requests.map((pr) => ({
16754
- number: pr.number,
16755
- title: pr.title || pr.head.ref,
16756
- headRefName: pr.head.ref,
16757
- state: pr.merged_at || pr.state === "merged" ? "MERGED" : pr.state === "closed" ? "CLOSED" : "OPEN",
16758
- isDraft: pr.draft ?? false
16759
- }))
16760
- };
17181
+ const githubAccountSelectionSchema = discriminatedUnion("mode", [
17182
+ object({ mode: literal("auto") }),
17183
+ object({ mode: literal("account"), host: literal("github.com"), login: string().regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/) })
17184
+ ]);
17185
+ const AUTOMATIC_GITHUB_ACCOUNT = { mode: "auto" };
17186
+ function githubRepositoryKey(commonDir, platform2 = process.platform) {
17187
+ const key = commonDir.replace(/\\/g, "/").replace(/\/+$/, "");
17188
+ return platform2 === "win32" ? key.toLowerCase() : key;
16761
17189
  }
16762
17190
  const OWNER_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?$/;
16763
17191
  const REPO_PATTERN = /^[A-Za-z0-9._-]+$/;
@@ -16781,15 +17209,466 @@ function parseGitHubRemote(url) {
16781
17209
  if (!OWNER_PATTERN.test(owner) || !repo || repo === "." || repo === ".." || !REPO_PATTERN.test(repo)) return null;
16782
17210
  return { nameWithOwner: `${owner}/${repo}` };
16783
17211
  }
16784
- const PR_SUMMARY_FIELDS = "number,title,state,isDraft,author,headRefName,baseRefName,updatedAt,url,reviewDecision,additions,deletions,statusCheckRollup";
16785
- const PR_DETAIL_FIELDS = `${PR_SUMMARY_FIELDS},body,changedFiles,labels,commits`;
16786
- const tolerantRecordSchema = looseObject({});
16787
- const pullRequestArraySchema = array(unknown());
16788
- const summaryRecordSchema = looseObject({
16789
- number: number().safe().int().positive(),
16790
- state: string().transform((value) => value.toUpperCase()).pipe(_enum(["OPEN", "CLOSED", "MERGED"]))
16791
- });
16792
- function parsePullRequestList(raw) {
17212
+ function parseSshRemote(url) {
17213
+ const trimmed = url.trim();
17214
+ const ssh = /^ssh:\/\/(?:(\w[\w.-]*)@)?([A-Za-z0-9][A-Za-z0-9._-]*)(?::(\d+))?\/(.+)$/i.exec(trimmed);
17215
+ const scp = /^(?:(\w[\w.-]*)@)?([A-Za-z0-9][A-Za-z0-9._-]*):(.+)$/.exec(trimmed);
17216
+ const match = ssh ?? scp;
17217
+ if (!match) return null;
17218
+ const parsed = parseGitHubRemote(`https://github.com/${ssh ? match[4] : match[3]}`);
17219
+ if (!parsed) return null;
17220
+ return { destination: `${match[1] ? `${match[1]}@` : ""}${match[2]}`, port: ssh ? match[3] ?? null : null, ...parsed };
17221
+ }
17222
+ const GITHUB_LOGIN = /^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/;
17223
+ function parseGitHubAuthStatus(raw) {
17224
+ const input = JSON.parse(raw);
17225
+ if (!input || typeof input !== "object" || !("hosts" in input) || !input.hosts || typeof input.hosts !== "object") throw new Error("Invalid gh status");
17226
+ const entries = input.hosts["github.com"] ?? [];
17227
+ if (!Array.isArray(entries) || entries.length > 100) throw new Error("Invalid gh accounts");
17228
+ const accounts = [];
17229
+ for (const entry of entries) {
17230
+ if (!entry || typeof entry !== "object" || typeof entry.login !== "string" || !GITHUB_LOGIN.test(entry.login)) continue;
17231
+ accounts.push({
17232
+ host: "github.com",
17233
+ login: entry.login,
17234
+ active: entry.active === true,
17235
+ state: entry.state === "success" ? "authenticated" : entry.state === "error" ? "invalid" : "unknown",
17236
+ storage: entry.tokenSource === "keyring" ? "keyring" : typeof entry.tokenSource === "string" && (entry.tokenSource === "oauth_token" || /(?:^|[/\\])hosts\.yml$/.test(entry.tokenSource)) ? "file" : "unknown",
17237
+ ...typeof entry.scopes === "string" && /^[A-Za-z0-9_:, -]{0,500}$/.test(entry.scopes) ? { scopes: entry.scopes } : {}
17238
+ });
17239
+ }
17240
+ return { accounts, activeLogin: accounts.find((account) => account.active)?.login ?? null };
17241
+ }
17242
+ const GH_ENV = { GH_HOST: "github.com", GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", NO_COLOR: "1" };
17243
+ const GH_PRIVATE_ENV = ["GITHUB_TOKEN", "GH_DEBUG", "DEBUG"];
17244
+ async function identifySshAccount(remote, runner) {
17245
+ const ssh = parseSshRemote(remote);
17246
+ if (!ssh) return null;
17247
+ try {
17248
+ const config2 = await runner.run("ssh", ["-G", ...ssh.port ? ["-p", ssh.port] : [], "--", ssh.destination], {
17249
+ cwd: os.homedir(),
17250
+ timeoutMs: 5e3,
17251
+ maxOutputBytes: 64 * 1024
17252
+ });
17253
+ const hostname = config2.stdout.split(/\r?\n/).find((line) => /^hostname\s/i.test(line))?.trim().split(/\s+/)[1];
17254
+ if (config2.exitCode !== 0 || hostname?.toLowerCase() !== "github.com") return null;
17255
+ const result = await runner.run("ssh", [
17256
+ "-T",
17257
+ "-o",
17258
+ "BatchMode=yes",
17259
+ "-o",
17260
+ "StrictHostKeyChecking=yes",
17261
+ "-o",
17262
+ "ConnectTimeout=5",
17263
+ "-o",
17264
+ "ConnectionAttempts=1",
17265
+ "-o",
17266
+ "RemoteCommand=none",
17267
+ "-o",
17268
+ "UpdateHostKeys=no",
17269
+ ...ssh.port ? ["-p", ssh.port] : [],
17270
+ "--",
17271
+ ssh.destination
17272
+ ], { cwd: os.homedir(), timeoutMs: 1e4, maxOutputBytes: 64 * 1024 });
17273
+ if (result.exitCode !== 0 && result.exitCode !== 1) return null;
17274
+ const login2 = /^Hi ([A-Za-z0-9-]+)! You've successfully authenticated, but GitHub does not provide shell access\.\s*$/m.exec(`${result.stdout}
17275
+ ${result.stderr}`)?.[1];
17276
+ return login2 && GITHUB_LOGIN.test(login2) ? login2 : null;
17277
+ } catch {
17278
+ return null;
17279
+ }
17280
+ }
17281
+ async function accountAuthentication(login2, executable, runner, operation) {
17282
+ try {
17283
+ const result = await runner.run(executable.executable, ["auth", "token", "--hostname", "github.com", ...login2 ? ["--user", login2] : []], {
17284
+ cwd: os.homedir(),
17285
+ env: { ...executable.env, ...GH_ENV },
17286
+ removeEnv: login2 ? ["GH_TOKEN", ...GH_PRIVATE_ENV] : ["GH_DEBUG", "DEBUG"],
17287
+ timeoutMs: 1e4,
17288
+ maxOutputBytes: 8 * 1024
17289
+ });
17290
+ const token = result.stdout.trim();
17291
+ if (result.exitCode === 0 && /^[A-Za-z0-9_]{20,4096}$/.test(token)) return { env: { GH_TOKEN: token }, removeEnv: GH_PRIVATE_ENV };
17292
+ if (/unknown flag.*user/i.test(result.stderr)) throw new GhOperationError({
17293
+ code: "GH_CLI_INCOMPATIBLE",
17294
+ operation,
17295
+ message: "Update GitHub CLI to a version supporting multiple accounts and auth token --user."
17296
+ });
17297
+ } catch (error) {
17298
+ if (error instanceof GhOperationError) throw error;
17299
+ }
17300
+ throw new GhOperationError({
17301
+ code: login2 ? "GH_ACCOUNT_MISSING" : "GH_AUTH_REQUIRED",
17302
+ operation,
17303
+ message: login2 ? `GitHub CLI has no usable saved credentials for @${login2}. Sign in as ${login2} on the backend host and check Settings → GitHub again. SSH and GitHub CLI authentication are separate.` : "GitHub CLI has no usable credentials. Sign in on the backend host and check Settings → GitHub again."
17304
+ });
17305
+ }
17306
+ function redactCommandToken(value, auth) {
17307
+ const token = auth.env?.GH_TOKEN;
17308
+ return token ? value.split(token).join("[redacted]") : value;
17309
+ }
17310
+ const CACHE_MS = 6e4;
17311
+ const emptyStatus = () => ({
17312
+ installationStatus: "unchecked",
17313
+ accounts: [],
17314
+ activeLogin: null,
17315
+ environment: { present: false, login: null, state: "unknown" },
17316
+ checkedAt: null
17317
+ });
17318
+ class GitHubAccountsService {
17319
+ constructor(resolver, runner, git, settings, store, changed = () => void 0) {
17320
+ this.resolver = resolver;
17321
+ this.runner = runner;
17322
+ this.git = git;
17323
+ this.settings = settings;
17324
+ this.store = store;
17325
+ this.changed = changed;
17326
+ }
17327
+ resolver;
17328
+ runner;
17329
+ git;
17330
+ settings;
17331
+ store;
17332
+ changed;
17333
+ inventory = null;
17334
+ loading = null;
17335
+ checking = null;
17336
+ detection = null;
17337
+ revision = 0;
17338
+ get authRevision() {
17339
+ return this.revision;
17340
+ }
17341
+ contexts = /* @__PURE__ */ new Map();
17342
+ ssh = /* @__PURE__ */ new Map();
17343
+ identities = /* @__PURE__ */ new Map();
17344
+ status(force = false) {
17345
+ if (force) {
17346
+ this.checking ??= this.check().finally(() => {
17347
+ this.checking = null;
17348
+ });
17349
+ return this.checking.then((value) => structuredClone(value));
17350
+ }
17351
+ this.loading ??= (async () => {
17352
+ this.inventory ??= await this.store?.load() ?? emptyStatus();
17353
+ return this.inventory;
17354
+ })();
17355
+ return this.loading.then(() => structuredClone(this.inventory ?? emptyStatus()));
17356
+ }
17357
+ selection(repository) {
17358
+ return this.settings?.githubAccount(repository.commonDir) ?? { ...AUTOMATIC_GITHUB_ACCOUNT };
17359
+ }
17360
+ context(repository, remote, nameWithOwner) {
17361
+ const cached2 = this.contexts.get(repository.id);
17362
+ if (cached2?.remote === remote && cached2.value.revision === this.revision) return structuredClone(cached2.value);
17363
+ const selection = this.selection(repository);
17364
+ return {
17365
+ selection,
17366
+ nameWithOwner,
17367
+ login: selection.mode === "account" ? selection.login : null,
17368
+ source: selection.mode === "account" ? "explicit" : null,
17369
+ state: "unchecked",
17370
+ checkedAt: null,
17371
+ revision: this.revision
17372
+ };
17373
+ }
17374
+ async setSelection(repository, selection) {
17375
+ if (!this.settings) throw new Error("GitHub account settings are unavailable.");
17376
+ await this.settings.setGitHubAccount(repository.commonDir, selection);
17377
+ this.invalidate();
17378
+ }
17379
+ invalidate() {
17380
+ this.revision++;
17381
+ this.contexts.clear();
17382
+ this.ssh.clear();
17383
+ this.identities.clear();
17384
+ this.changed();
17385
+ }
17386
+ refreshAuthentication() {
17387
+ this.ssh.clear();
17388
+ this.identities.clear();
17389
+ }
17390
+ async detect(force = false) {
17391
+ if (force || !this.detection || Date.now() - this.detection.at > CACHE_MS) {
17392
+ this.detection = { at: Date.now(), promise: selectCli(this.resolver, this.runner, "gh", {
17393
+ forceRefresh: force,
17394
+ runOptions: { cwd: os.homedir(), env: { ...GH_ENV }, removeEnv: ["GH_DEBUG", "DEBUG"] }
17395
+ }) };
17396
+ }
17397
+ return this.detection.promise;
17398
+ }
17399
+ async authenticate(repository, remote, nameWithOwner, operation) {
17400
+ const revision2 = this.revision;
17401
+ const selection = this.selection(repository);
17402
+ let login2 = selection.mode === "account" ? selection.login : null;
17403
+ let source = selection.mode === "account" ? "explicit" : null;
17404
+ try {
17405
+ const detection = await this.detect();
17406
+ if (detection.state !== "available" || !detection.candidate) throw new GhOperationError({
17407
+ code: detection.state === "not-found" ? "GH_CLI_NOT_FOUND" : "GH_PROCESS_FAILED",
17408
+ operation,
17409
+ message: detection.state === "not-found" ? "GitHub CLI is not installed on the backend host." : "GitHub CLI could not run. Check Settings → GitHub."
17410
+ });
17411
+ const executable = detection.candidate;
17412
+ if (selection.mode === "auto" && parseSshRemote(remote)) {
17413
+ const override = executable.env.GIT_SSH_COMMAND || executable.env.GIT_SSH || process.env.GIT_SSH_COMMAND || process.env.GIT_SSH;
17414
+ const configured = await this.git.run(repository.path, ["config", "--default", "", "--get", "core.sshCommand"], {
17415
+ operation: "github-ssh-configuration",
17416
+ readOnly: true,
17417
+ maxOutputBytes: 8 * 1024
17418
+ });
17419
+ if (override || configured.stdout.toString("utf8").trim()) throw new GhOperationError({
17420
+ code: "GH_ACCOUNT_UNRESOLVED",
17421
+ operation,
17422
+ message: "This repository uses a custom SSH command. Choose an explicit account in Settings → GitHub."
17423
+ });
17424
+ const key = `${repository.path}\0${remote}`;
17425
+ let cached2 = this.ssh.get(key);
17426
+ if (!cached2 || Date.now() - cached2.at > CACHE_MS) {
17427
+ cached2 = { at: Date.now(), promise: identifySshAccount(remote, this.runner) };
17428
+ this.ssh.set(key, cached2);
17429
+ }
17430
+ login2 = await cached2.promise;
17431
+ source = "ssh";
17432
+ if (!login2) throw new GhOperationError({
17433
+ code: "GH_ACCOUNT_UNRESOLVED",
17434
+ operation,
17435
+ message: "SSH could not verify a GitHub user account. Check again or choose an explicit account in Settings → GitHub. Deploy keys do not identify user accounts."
17436
+ });
17437
+ }
17438
+ if (!source) source = executable.env.GH_TOKEN || executable.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN ? "environment" : "global";
17439
+ const auth = await accountAuthentication(login2, executable, this.runner, operation);
17440
+ const verified = await this.identity(executable, auth, operation);
17441
+ if (login2 && login2.toLowerCase() !== verified.toLowerCase()) throw new GhOperationError({
17442
+ code: "GH_AUTH_REQUIRED",
17443
+ operation,
17444
+ message: `The saved credentials for @${login2} authenticate a different account. Authenticate @${login2} again and check Settings → GitHub.`
17445
+ });
17446
+ login2 = verified;
17447
+ const context = { selection, nameWithOwner, login: login2, source, state: "ready", checkedAt: (/* @__PURE__ */ new Date()).toISOString(), revision: revision2 };
17448
+ if (revision2 === this.revision) {
17449
+ const previous = this.contexts.get(repository.id)?.value;
17450
+ if (previous?.state === "ready" && (previous.login?.toLowerCase() !== login2.toLowerCase() || previous.source !== source)) {
17451
+ this.invalidate();
17452
+ context.revision = this.revision;
17453
+ }
17454
+ this.contexts.set(repository.id, { remote, value: context });
17455
+ }
17456
+ return { auth, executable, context };
17457
+ } catch (error) {
17458
+ const message = error instanceof GhOperationError ? error.message : "Could not inspect GitHub authentication. Check Settings → GitHub again.";
17459
+ if (revision2 === this.revision) this.contexts.set(repository.id, { remote, value: {
17460
+ selection,
17461
+ nameWithOwner,
17462
+ login: login2,
17463
+ source,
17464
+ state: "error",
17465
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
17466
+ message,
17467
+ revision: revision2
17468
+ } });
17469
+ if (error instanceof GhOperationError) throw error;
17470
+ throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message, retryable: true });
17471
+ }
17472
+ }
17473
+ identity(executable, auth, operation) {
17474
+ const fingerprint = createHash("sha256").update(auth.env?.GH_TOKEN ?? "").digest("hex");
17475
+ let cached2 = this.identities.get(fingerprint);
17476
+ if (!cached2 || Date.now() - cached2.at > CACHE_MS) {
17477
+ const promise = (async () => {
17478
+ const result = await this.runner.run(executable.executable, ["api", "user", "--jq", ".login"], {
17479
+ cwd: os.homedir(),
17480
+ env: { ...executable.env, ...GH_ENV, ...auth.env },
17481
+ ...auth.removeEnv ? { removeEnv: auth.removeEnv } : {},
17482
+ timeoutMs: 15e3,
17483
+ maxOutputBytes: 8 * 1024
17484
+ });
17485
+ const login2 = result.stdout.trim();
17486
+ if (result.exitCode === 0 && GITHUB_LOGIN.test(login2)) return login2;
17487
+ const invalid2 = /401|bad credentials|requires authentication/i.test(result.stderr);
17488
+ throw new GhOperationError({
17489
+ code: invalid2 ? "GH_AUTH_REQUIRED" : "GH_PROCESS_FAILED",
17490
+ operation,
17491
+ message: invalid2 ? "GitHub rejected these credentials. They may be expired or revoked. Authenticate this account again and check Settings → GitHub." : "Could not verify this GitHub account. Check your connection and try again.",
17492
+ retryable: !invalid2
17493
+ });
17494
+ })();
17495
+ cached2 = { at: Date.now(), promise };
17496
+ this.identities.set(fingerprint, cached2);
17497
+ void promise.catch(() => {
17498
+ if (this.identities.get(fingerprint)?.promise === promise) this.identities.delete(fingerprint);
17499
+ });
17500
+ }
17501
+ return cached2.promise;
17502
+ }
17503
+ async check() {
17504
+ await this.status();
17505
+ this.refreshAuthentication();
17506
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
17507
+ const detection = await this.detect(true);
17508
+ const result = {
17509
+ ...emptyStatus(),
17510
+ installationStatus: detection.state,
17511
+ checkedAt,
17512
+ ...detection.version ? { version: detection.version } : {}
17513
+ };
17514
+ if (detection.state === "available" && detection.candidate) {
17515
+ const executable = detection.candidate;
17516
+ try {
17517
+ const status = await this.runner.run(executable.executable, ["auth", "status", "--hostname", "github.com", "--json", "hosts"], {
17518
+ cwd: os.homedir(),
17519
+ env: { ...executable.env, ...GH_ENV },
17520
+ removeEnv: ["GH_TOKEN", ...GH_PRIVATE_ENV],
17521
+ timeoutMs: 6e4,
17522
+ maxOutputBytes: 256 * 1024
17523
+ });
17524
+ if (status.exitCode !== 0) {
17525
+ if (/unknown flag.*json/i.test(status.stderr)) result.installationStatus = "incompatible";
17526
+ throw new Error("Status failed");
17527
+ }
17528
+ Object.assign(result, parseGitHubAuthStatus(status.stdout));
17529
+ const envToken = executable.env.GH_TOKEN || executable.env.GITHUB_TOKEN || process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
17530
+ if (envToken) {
17531
+ result.environment.present = true;
17532
+ try {
17533
+ const auth = await accountAuthentication(null, executable, this.runner, "gh-auth-status");
17534
+ result.environment.login = await this.identity(executable, auth, "gh-auth-status");
17535
+ result.environment.state = "authenticated";
17536
+ } catch (error) {
17537
+ result.environment.state = error instanceof GhOperationError && error.detail.code === "GH_AUTH_REQUIRED" ? "invalid" : "unknown";
17538
+ }
17539
+ }
17540
+ } catch {
17541
+ result.message = result.installationStatus === "incompatible" ? "Update GitHub CLI to a version supporting auth status --json and multiple accounts." : "Could not check the saved GitHub accounts. Check your connection and CLI configuration, then try again.";
17542
+ result.accounts = this.inventory?.accounts.map((account) => ({ ...account, state: "unknown" })) ?? [];
17543
+ result.activeLogin = this.inventory?.activeLogin ?? null;
17544
+ }
17545
+ } else result.message = detection.state === "not-found" ? "Install GitHub CLI on the backend host." : "GitHub CLI was found but could not run. Check its installation and dependencies.";
17546
+ this.inventory = result;
17547
+ try {
17548
+ await this.store?.save(result);
17549
+ } finally {
17550
+ this.invalidate();
17551
+ }
17552
+ return result;
17553
+ }
17554
+ }
17555
+ const authState = _enum(["authenticated", "invalid", "unknown"]);
17556
+ const login = string().regex(/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/);
17557
+ const schema = object({
17558
+ installationStatus: _enum(["unchecked", "available", "not-found", "not-executable", "incompatible", "inspection-failed"]),
17559
+ version: string().max(200).optional(),
17560
+ accounts: array(object({
17561
+ host: literal("github.com"),
17562
+ login,
17563
+ active: boolean(),
17564
+ state: authState,
17565
+ storage: _enum(["keyring", "file", "unknown"]),
17566
+ scopes: string().max(500).optional()
17567
+ })).max(100),
17568
+ activeLogin: login.nullable(),
17569
+ environment: object({ present: boolean(), login: login.nullable(), state: authState }),
17570
+ checkedAt: string().datetime().nullable(),
17571
+ message: string().max(500).optional()
17572
+ });
17573
+ class GitHubStatusStore {
17574
+ constructor(filePath) {
17575
+ this.filePath = filePath;
17576
+ }
17577
+ filePath;
17578
+ pending = Promise.resolve();
17579
+ async load() {
17580
+ await this.pending;
17581
+ try {
17582
+ if ((await stat(this.filePath)).size > 256 * 1024) return null;
17583
+ const parsed = schema.safeParse(JSON.parse(await readFile(this.filePath, "utf8")));
17584
+ return parsed.success ? parsed.data : null;
17585
+ } catch {
17586
+ return null;
17587
+ }
17588
+ }
17589
+ save(value) {
17590
+ const snapshot = JSON.stringify(schema.parse(value));
17591
+ const write = this.pending.then(() => writeFileAtomically(this.filePath, snapshot, { parseJson: true }));
17592
+ this.pending = write.catch(() => void 0);
17593
+ return write;
17594
+ }
17595
+ }
17596
+ const positive = number().int().positive().safe();
17597
+ const membershipSchema = object({
17598
+ stack: object({ number: positive, size: positive, baseRefName: string().min(1) }).nullable(),
17599
+ stackEntry: object({ position: positive }).nullable()
17600
+ });
17601
+ const stackSchema = object({
17602
+ number: positive,
17603
+ base: union([string().min(1), object({ ref: string().min(1) })]),
17604
+ pull_requests: array(object({
17605
+ number: positive,
17606
+ title: string().optional(),
17607
+ draft: boolean().optional(),
17608
+ head: object({ ref: string().min(1) }),
17609
+ state: _enum(["open", "closed", "merged"]),
17610
+ merged_at: string().nullable().optional()
17611
+ })).min(1)
17612
+ });
17613
+ function invalid() {
17614
+ return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation: "gh-pr-stack", message: "GitHub returned unreadable stack information." });
17615
+ }
17616
+ function json(raw) {
17617
+ try {
17618
+ return JSON.parse(raw);
17619
+ } catch {
17620
+ throw invalid();
17621
+ }
17622
+ }
17623
+ function stackMembershipQuery(numbers) {
17624
+ if (!numbers.length || numbers.some((number2) => !positive.safeParse(number2).success)) throw invalid();
17625
+ return `query($owner:String!,$name:String!){repository(owner:$owner,name:$name){${numbers.map((number2) => `pr${number2}:pullRequest(number:${number2}){stack{number size baseRefName} stackEntry{position}}`).join(" ")}}}`;
17626
+ }
17627
+ function parseStackMemberships(raw, numbers) {
17628
+ const envelope = object({ data: object({ repository: record(string(), membershipSchema.nullable()) }), errors: array(unknown()).optional() }).safeParse(json(raw));
17629
+ if (!envelope.success || envelope.data.errors?.length) throw invalid();
17630
+ const result = /* @__PURE__ */ new Map();
17631
+ for (const number2 of numbers) {
17632
+ const entry = envelope.data.data.repository[`pr${number2}`];
17633
+ if (entry === void 0) throw invalid();
17634
+ if (!entry?.stack && !entry?.stackEntry) continue;
17635
+ if (!entry?.stack || !entry.stackEntry || entry.stackEntry.position > entry.stack.size) throw invalid();
17636
+ result.set(number2, { number: entry.stack.number, size: entry.stack.size, position: entry.stackEntry.position, base: entry.stack.baseRefName });
17637
+ }
17638
+ return result;
17639
+ }
17640
+ function parsePullRequestStack(raw, prNumber, listing = false) {
17641
+ let value = json(raw);
17642
+ if (listing) {
17643
+ if (!Array.isArray(value) || value.length > 1) throw invalid();
17644
+ if (!value.length) return null;
17645
+ value = value[0];
17646
+ }
17647
+ const parsed = stackSchema.safeParse(value);
17648
+ if (!parsed.success) throw invalid();
17649
+ const stack = parsed.data;
17650
+ if (!stack.pull_requests.some((pr) => pr.number === prNumber) || new Set(stack.pull_requests.map((pr) => pr.number)).size !== stack.pull_requests.length) throw invalid();
17651
+ return {
17652
+ number: stack.number,
17653
+ base: typeof stack.base === "string" ? stack.base : stack.base.ref,
17654
+ layers: stack.pull_requests.map((pr) => ({
17655
+ number: pr.number,
17656
+ title: pr.title || pr.head.ref,
17657
+ headRefName: pr.head.ref,
17658
+ state: pr.merged_at || pr.state === "merged" ? "MERGED" : pr.state === "closed" ? "CLOSED" : "OPEN",
17659
+ isDraft: pr.draft ?? false
17660
+ }))
17661
+ };
17662
+ }
17663
+ const PR_SUMMARY_FIELDS = "number,title,state,isDraft,author,headRefName,baseRefName,updatedAt,url,reviewDecision,additions,deletions,statusCheckRollup";
17664
+ const PR_DETAIL_FIELDS = `${PR_SUMMARY_FIELDS},body,changedFiles,labels,commits`;
17665
+ const tolerantRecordSchema = looseObject({});
17666
+ const pullRequestArraySchema = array(unknown());
17667
+ const summaryRecordSchema = looseObject({
17668
+ number: number().safe().int().positive(),
17669
+ state: string().transform((value) => value.toUpperCase()).pipe(_enum(["OPEN", "CLOSED", "MERGED"]))
17670
+ });
17671
+ function parsePullRequestList(raw) {
16793
17672
  const parsed = parseJson(raw, "gh-pr-list");
16794
17673
  const list = pullRequestArraySchema.safeParse(parsed);
16795
17674
  if (!list.success) throw invalidOutput("gh-pr-list");
@@ -16876,14 +17755,14 @@ function parseCommits(value) {
16876
17755
  if (typeof record2.oid !== "string" || !record2.oid) return [];
16877
17756
  const authors = Array.isArray(record2.authors) ? record2.authors : [];
16878
17757
  const authorRecord = authors.map((author) => tolerantRecordSchema.safeParse(author)).find((author) => author.success)?.data;
16879
- const login = typeof authorRecord?.login === "string" ? authorRecord.login : null;
16880
- const name = typeof authorRecord?.name === "string" && authorRecord.name ? authorRecord.name : login ?? "unknown";
17758
+ const login2 = typeof authorRecord?.login === "string" ? authorRecord.login : null;
17759
+ const name = typeof authorRecord?.name === "string" && authorRecord.name ? authorRecord.name : login2 ?? "unknown";
16881
17760
  return [{
16882
17761
  oid: record2.oid,
16883
17762
  messageHeadline: typeof record2.messageHeadline === "string" ? record2.messageHeadline : "",
16884
17763
  authoredAt: typeof record2.authoredDate === "string" ? record2.authoredDate : "",
16885
17764
  author: name,
16886
- authorAvatarUrl: login ? avatarUrl(login, login.endsWith("[bot]")) : null
17765
+ authorAvatarUrl: login2 ? avatarUrl(login2, login2.endsWith("[bot]")) : null
16887
17766
  }];
16888
17767
  });
16889
17768
  }
@@ -16902,11 +17781,11 @@ function parseChecksState(value) {
16902
17781
  }
16903
17782
  return pending ? "PENDING" : "PASSING";
16904
17783
  }
16905
- function avatarUrl(login, isBot) {
16906
- if (!login || login === "unknown") return null;
16907
- if (login === "app/dependabot" || login === "dependabot[bot]") return "https://avatars.githubusercontent.com/in/29110?s=40&v=4";
16908
- const botName = login.startsWith("app/") ? login.slice(4) : login.replace(/\[bot\]$/i, "");
16909
- const path2 = isBot || login.startsWith("app/") ? botName : login;
17784
+ function avatarUrl(login2, isBot) {
17785
+ if (!login2 || login2 === "unknown") return null;
17786
+ if (login2 === "app/dependabot" || login2 === "dependabot[bot]") return "https://avatars.githubusercontent.com/in/29110?s=40&v=4";
17787
+ const botName = login2.startsWith("app/") ? login2.slice(4) : login2.replace(/\[bot\]$/i, "");
17788
+ const path2 = isBot || login2.startsWith("app/") ? botName : login2;
16910
17789
  return `https://github.com/${encodeURIComponent(path2)}.png?size=40`;
16911
17790
  }
16912
17791
  function parseJson(raw, operation) {
@@ -16926,70 +17805,112 @@ function validTimestamp(value) {
16926
17805
  function invalidOutput(operation) {
16927
17806
  return new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "GitHub CLI returned an unexpected response." });
16928
17807
  }
16929
- const GH_ENV = { GH_PROMPT_DISABLED: "1", GH_NO_UPDATE_NOTIFIER: "1", GH_PAGER: "cat", NO_COLOR: "1" };
16930
17808
  const NETWORK_TIMEOUT_MS = 6e4;
16931
17809
  class GitHubService {
16932
- constructor(resolver, runner, git, repositories) {
16933
- this.resolver = resolver;
17810
+ constructor(resolver, runner, git, repositories, accounts) {
16934
17811
  this.runner = runner;
16935
17812
  this.git = git;
16936
17813
  this.repositories = repositories;
17814
+ this.accounts = accounts ?? new GitHubAccountsService(resolver, runner, git);
16937
17815
  }
16938
- resolver;
16939
17816
  runner;
16940
17817
  git;
16941
17818
  repositories;
16942
- statusCache = null;
17819
+ accounts;
16943
17820
  async status(forceRefresh = false) {
16944
- if (!forceRefresh && this.statusCache && Date.now() - this.statusCache.at < 3e4) return this.statusCache.value;
16945
- const value = await this.checkStatus(forceRefresh);
16946
- this.statusCache = { at: Date.now(), value };
16947
- return value;
17821
+ if (forceRefresh) this.accounts.refreshAuthentication();
17822
+ const detected = await this.accounts.detect(forceRefresh);
17823
+ return {
17824
+ installed: !!detected.candidate,
17825
+ availability: detected.state === "available" ? "ready" : "error",
17826
+ authStatus: "unknown",
17827
+ ...detected.version ? { version: detected.version } : {},
17828
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString()
17829
+ };
17830
+ }
17831
+ async repositoryAccount(repositoryId, forceRefresh = false) {
17832
+ const repository = this.repositories.get(repositoryId);
17833
+ const info = await this.repositoryInfo(repositoryId);
17834
+ const remote = await this.remote(repositoryId);
17835
+ if (forceRefresh && info.nameWithOwner) {
17836
+ this.accounts.refreshAuthentication();
17837
+ try {
17838
+ await this.accounts.authenticate(repository, remote, info.nameWithOwner, "gh-repository-account");
17839
+ } catch {
17840
+ }
17841
+ }
17842
+ return this.accounts.context(repository, remote, info.nameWithOwner);
17843
+ }
17844
+ async setRepositoryAccount(repositoryId, selection) {
17845
+ await this.accounts.setSelection(this.repositories.get(repositoryId), selection);
17846
+ return this.repositoryAccount(repositoryId);
17847
+ }
17848
+ async remote(repositoryId) {
17849
+ const repository = this.repositories.get(repositoryId);
17850
+ try {
17851
+ const output = await this.git.run(repository.path, ["config", "--get", "remote.origin.url"], { operation: "github-remote-url", readOnly: true, maxOutputBytes: 64 * 1024 });
17852
+ return output.stdout.toString("utf8").trim();
17853
+ } catch {
17854
+ return "";
17855
+ }
16948
17856
  }
16949
17857
  async repositoryInfo(repositoryId) {
16950
17858
  const repository = this.repositories.get(repositoryId);
16951
17859
  try {
16952
17860
  const output = await this.git.run(repository.path, ["config", "--get", "remote.origin.url"], { operation: "github-remote-url", readOnly: true, maxOutputBytes: 64 * 1024 });
16953
- const parsed = parseGitHubRemote(output.stdout.toString("utf8"));
17861
+ const url = output.stdout.toString("utf8");
17862
+ let parsed = parseGitHubRemote(url);
17863
+ if (!parsed) {
17864
+ const ssh = parseSshRemote(url);
17865
+ if (ssh) {
17866
+ const config2 = await this.runner.run("ssh", ["-G", ...ssh.port ? ["-p", ssh.port] : [], "--", ssh.destination], {
17867
+ cwd: os.homedir(),
17868
+ timeoutMs: 5e3,
17869
+ maxOutputBytes: 64 * 1024
17870
+ });
17871
+ const hostname = config2.stdout.split(/\r?\n/).find((line) => /^hostname\s/i.test(line))?.trim().split(/\s+/)[1];
17872
+ if (config2.exitCode === 0 && hostname?.toLowerCase() === "github.com") parsed = { nameWithOwner: ssh.nameWithOwner };
17873
+ }
17874
+ }
16954
17875
  return parsed ? { isGitHub: true, nameWithOwner: parsed.nameWithOwner } : { isGitHub: false, nameWithOwner: null };
16955
17876
  } catch {
16956
17877
  return { isGitHub: false, nameWithOwner: null };
16957
17878
  }
16958
17879
  }
16959
17880
  async listPullRequests(repositoryId, states) {
16960
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-list");
17881
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-list");
16961
17882
  const results = await Promise.all(states.map((state) => this.runGh(
16962
17883
  ["pr", "list", "-R", nameWithOwner, "--state", state.toLowerCase(), "--json", PR_SUMMARY_FIELDS, "--limit", "50"],
16963
- { cwd: repository.path, operation: "gh-pr-list", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
17884
+ { auth, executable, cwd: repository.path, operation: "gh-pr-list", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
16964
17885
  )));
16965
- return this.withStackMembership(repository.path, nameWithOwner, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
17886
+ return this.withStackMembership(repository.path, nameWithOwner, auth, executable, selectPullRequestsNewestFirst(results.flatMap((result) => parsePullRequestList(result.stdout)), states));
16966
17887
  }
16967
17888
  async findPullRequestForBranch(repositoryId, branchName) {
16968
17889
  const operation = "gh-pr-for-branch";
16969
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
17890
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
16970
17891
  const branch = validateRef(branchName, operation);
16971
17892
  const openResult = await this.runGh(
16972
17893
  ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "open", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
16973
- { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
17894
+ { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
16974
17895
  );
16975
17896
  const openPull = sortPullRequestsNewestFirst(parsePullRequestList(openResult.stdout)).find((pull) => pull.state === "OPEN");
16976
17897
  if (openPull) return openPull;
16977
17898
  const result = await this.runGh(
16978
17899
  ["pr", "list", "-R", nameWithOwner, "--head", branch, "--state", "all", "--json", PR_SUMMARY_FIELDS, "--limit", "10"],
16979
- { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
17900
+ { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 1024 * 1024 }
16980
17901
  );
16981
17902
  return sortPullRequestsNewestFirst(parsePullRequestList(result.stdout))[0] ?? null;
16982
17903
  }
16983
17904
  async getPullRequest(repositoryId, prNumber) {
16984
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-view");
17905
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-view");
16985
17906
  const result = await this.runGh(
16986
17907
  ["pr", "view", String(prNumber), "-R", nameWithOwner, "--json", PR_DETAIL_FIELDS],
16987
- { cwd: repository.path, operation: "gh-pr-view", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
17908
+ { auth, executable, cwd: repository.path, operation: "gh-pr-view", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 }
16988
17909
  );
16989
17910
  const details = parsePullRequestDetails(result.stdout);
16990
- return (await this.withStackMembership(repository.path, nameWithOwner, [details]))[0];
17911
+ return (await this.withStackMembership(repository.path, nameWithOwner, auth, executable, [details]))[0];
16991
17912
  }
16992
- async withStackMembership(cwd, nameWithOwner, pulls) {
17913
+ async withStackMembership(cwd, nameWithOwner, auth, executable, pulls) {
16993
17914
  const [owner, name] = nameWithOwner.split("/");
16994
17915
  const enriched = [];
16995
17916
  for (let start = 0; start < pulls.length; start += 25) {
@@ -16997,6 +17918,8 @@ class GitHubService {
16997
17918
  try {
16998
17919
  const numbers = batch.map((pr) => pr.number);
16999
17920
  const result = await this.runGh(["api", "graphql", "-f", `owner=${owner}`, "-f", `name=${name}`, "-f", `query=${stackMembershipQuery(numbers)}`], {
17921
+ auth,
17922
+ executable,
17000
17923
  cwd,
17001
17924
  operation: "gh-pr-stack-membership",
17002
17925
  timeoutMs: 1e4,
@@ -17015,28 +17938,28 @@ class GitHubService {
17015
17938
  }
17016
17939
  async getPullRequestStack(repositoryId, prNumber) {
17017
17940
  const operation = "gh-pr-stack";
17018
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
17019
- const options = { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 };
17020
- const read = async (endpoint) => this.runGh(["api", endpoint], options);
17941
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
17942
+ const options = { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 4 * 1024 * 1024 };
17943
+ const read2 = async (endpoint) => this.runGh(["api", endpoint], options);
17021
17944
  let listing;
17022
17945
  try {
17023
- listing = await read(`repos/${nameWithOwner}/stacks?pull_request=${prNumber}`);
17946
+ listing = await read2(`repos/${nameWithOwner}/stacks?pull_request=${prNumber}`);
17024
17947
  } catch (error) {
17025
17948
  if (error instanceof GhOperationError && error.detail.code === "GH_PROCESS_FAILED" && /HTTP 404/.test(error.message)) return null;
17026
17949
  throw error;
17027
17950
  }
17028
17951
  const stack = parsePullRequestStack(listing.stdout, prNumber, true);
17029
17952
  if (!stack) return null;
17030
- const details = await read(`repos/${nameWithOwner}/stacks/${stack.number}`);
17953
+ const details = await read2(`repos/${nameWithOwner}/stacks/${stack.number}`);
17031
17954
  const complete = parsePullRequestStack(details.stdout, prNumber);
17032
17955
  if (complete?.number !== stack.number) throw new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "The stack changed while loading. Refresh to try again." });
17033
17956
  return complete;
17034
17957
  }
17035
17958
  async getPullRequestDiff(repositoryId, prNumber) {
17036
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, "gh-pr-diff");
17959
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, "gh-pr-diff");
17037
17960
  const result = await this.runGh(
17038
17961
  ["pr", "diff", String(prNumber), "-R", nameWithOwner],
17039
- { cwd: repository.path, operation: "gh-pr-diff", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
17962
+ { auth, executable, cwd: repository.path, operation: "gh-pr-diff", timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
17040
17963
  );
17041
17964
  const patch = result.stdout;
17042
17965
  const binary = /Binary files .* differ|GIT binary patch/.test(patch);
@@ -17044,13 +17967,13 @@ class GitHubService {
17044
17967
  }
17045
17968
  async getPullRequestCommitDiff(repositoryId, oid) {
17046
17969
  const operation = "gh-pr-commit-diff";
17047
- const { repository, nameWithOwner } = await this.requireGitHub(repositoryId, operation);
17970
+ const { repository, nameWithOwner, auth, executable } = await this.requireGitHub(repositoryId, operation);
17048
17971
  if (!/^[0-9a-f]{40,64}$/i.test(oid)) {
17049
17972
  throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "The commit is not valid." });
17050
17973
  }
17051
17974
  const result = await this.runGh(
17052
17975
  ["api", `repos/${nameWithOwner}/commits/${oid}`, "--header", "Accept: application/vnd.github.diff"],
17053
- { cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
17976
+ { auth, executable, cwd: repository.path, operation, timeoutMs: NETWORK_TIMEOUT_MS, maxOutputBytes: 32 * 1024 * 1024 }
17054
17977
  );
17055
17978
  const patch = result.stdout;
17056
17979
  const binary = /Binary files .* differ|GIT binary patch/.test(patch);
@@ -17058,7 +17981,13 @@ class GitHubService {
17058
17981
  }
17059
17982
  async createPullRequest(input) {
17060
17983
  const operation = "gh-pr-create";
17061
- const { repository, nameWithOwner } = await this.requireGitHub(input.repositoryId, operation);
17984
+ const { repository, nameWithOwner, auth, executable, context } = await this.requireGitHub(input.repositoryId, operation);
17985
+ const verifyAccount = () => {
17986
+ if (input.expectedAccount && (input.expectedAccount.revision !== this.accounts.authRevision || input.expectedAccount.revision !== context.revision || input.expectedAccount.login.toLowerCase() !== context.login?.toLowerCase())) {
17987
+ throw new GhOperationError({ code: "GH_ACCOUNT_UNRESOLVED", operation, message: "The GitHub account changed. Review the current identity before creating the pull request." });
17988
+ }
17989
+ };
17990
+ verifyAccount();
17062
17991
  const status = await this.repositories.status(input.repositoryId, false);
17063
17992
  if (status.detached || status.unborn || !status.branch) {
17064
17993
  throw new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "Check out a branch before creating a pull request." });
@@ -17076,7 +18005,8 @@ class GitHubService {
17076
18005
  await writeFile(bodyPath, input.body, { encoding: "utf8", mode: 384 });
17077
18006
  const args = ["pr", "create", "-R", nameWithOwner, "--title", input.title, "--body-file", bodyPath, "--base", base, "--head", head];
17078
18007
  if (input.draft) args.push("--draft");
17079
- const result = await this.runGh(args, { cwd: repository.path, operation, timeoutMs: 12e4, maxOutputBytes: 1024 * 1024 });
18008
+ verifyAccount();
18009
+ const result = await this.runGh(args, { auth, executable, cwd: repository.path, operation, timeoutMs: 12e4, maxOutputBytes: 1024 * 1024 });
17080
18010
  const created = parseCreatedPullRequestUrl(result.stdout);
17081
18011
  if (!created) throw new GhOperationError({ code: "GH_INVALID_OUTPUT", operation, message: "GitHub CLI did not return the URL of the new pull request." });
17082
18012
  return created;
@@ -17084,51 +18014,32 @@ class GitHubService {
17084
18014
  await rm(temporary, { recursive: true, force: true });
17085
18015
  }
17086
18016
  }
17087
- async checkStatus(forceRefresh) {
17088
- const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
17089
- const detected = await selectCli(this.resolver, this.runner, "gh", { forceRefresh, runOptions: { cwd: os.tmpdir(), env: { ...GH_ENV } } });
17090
- const executable = detected.candidate;
17091
- if (!executable) {
17092
- return { installed: false, availability: "error", authStatus: "unknown", message: "Install GitHub CLI (cli.github.com) to work with pull requests.", checkedAt };
17093
- }
17094
- if (detected.state !== "available") return { installed: true, availability: "error", authStatus: "unknown", message: "GitHub CLI was found but could not run. Check its installation and dependencies.", checkedAt };
17095
- const versionLabel = detected.version ?? "";
17096
- const auth = await this.run(executable, ["auth", "status", "--hostname", "github.com", "--active"], { cwd: os.tmpdir(), operation: "gh-auth-status" });
17097
- if (auth.exitCode !== 0) {
17098
- return { installed: true, availability: "error", authStatus: "unauthenticated", version: versionLabel, message: "Run gh auth login.", checkedAt };
17099
- }
17100
- return { installed: true, availability: "ready", authStatus: "authenticated", version: versionLabel, checkedAt };
17101
- }
17102
18017
  async requireGitHub(repositoryId, operation) {
17103
18018
  const repository = this.repositories.get(repositoryId);
17104
18019
  const info = await this.repositoryInfo(repositoryId);
17105
- if (!info.isGitHub || !info.nameWithOwner) {
17106
- throw new GhOperationError({ code: "GH_NOT_GITHUB_REPO", operation, message: "The origin remote does not point to a GitHub repository." });
17107
- }
17108
- const status = await this.status();
17109
- if (!status.installed) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation, message: "GitHub CLI is not installed." });
17110
- if (status.authStatus === "unauthenticated") throw new GhOperationError({ code: "GH_AUTH_REQUIRED", operation, message: "Sign in with gh auth login to continue." });
17111
- return { repository, nameWithOwner: info.nameWithOwner };
18020
+ if (!info.isGitHub || !info.nameWithOwner) throw new GhOperationError({ code: "GH_NOT_GITHUB_REPO", operation, message: "The origin remote does not point to a GitHub repository." });
18021
+ const remote = await this.remote(repositoryId);
18022
+ const authenticated = await this.accounts.authenticate(repository, remote, info.nameWithOwner, operation);
18023
+ return { repository, nameWithOwner: info.nameWithOwner, ...authenticated };
17112
18024
  }
17113
18025
  async runGh(args, options) {
17114
- const detected = await selectCli(this.resolver, this.runner, "gh", { runOptions: { cwd: options.cwd, env: { ...GH_ENV } } });
17115
- const executable = detected.state === "available" ? detected.candidate : void 0;
17116
- if (!executable) throw new GhOperationError({ code: "GH_CLI_NOT_FOUND", operation: options.operation, message: "GitHub CLI is not installed." });
17117
- const result = await this.run(executable, args, options);
17118
- if (result.exitCode !== 0) throw classifyFailure(result, options.operation);
18026
+ const result = await this.run(options.executable, args, options);
18027
+ if (result.exitCode !== 0) throw classifyFailure({ ...result, stdout: redactCommandToken(result.stdout, options.auth), stderr: redactCommandToken(result.stderr, options.auth) }, options.operation);
17119
18028
  return result;
17120
18029
  }
17121
18030
  async run(executable, args, options) {
17122
18031
  try {
17123
18032
  return await this.runner.run(executable.executable, args, {
17124
18033
  cwd: options.cwd,
17125
- env: { ...executable.env, ...GH_ENV },
18034
+ env: { ...executable.env, ...GH_ENV, ...options.auth?.env },
18035
+ ...options.auth?.removeEnv ? { removeEnv: options.auth.removeEnv } : {},
17126
18036
  ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
17127
18037
  ...options.maxOutputBytes !== void 0 ? { maxOutputBytes: options.maxOutputBytes } : {},
17128
18038
  ...options.stdin !== void 0 ? { stdin: options.stdin } : {}
17129
18039
  });
17130
18040
  } catch (error) {
17131
- throw mapRunnerError(error, options.operation);
18041
+ const mapped = mapRunnerError(error, options.operation);
18042
+ throw new GhOperationError({ ...mapped.detail, message: redactCommandToken(mapped.message, options.auth ?? {}) });
17132
18043
  }
17133
18044
  }
17134
18045
  }
@@ -17152,6 +18063,8 @@ ${result.stdout}`.toLowerCase();
17152
18063
  if (/could not resolve|no such host|connection refused|connection reset|network|dial tcp|tls handshake/.test(raw)) {
17153
18064
  return new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: "Could not connect to GitHub. Check your network and try again.", exitCode: result.exitCode, retryable: true });
17154
18065
  }
18066
+ if (/403|resource not accessible|insufficient.*scope|saml|sso/.test(raw)) return new GhOperationError({ code: "GH_ACCESS_DENIED", operation, message: "This GitHub account cannot perform the operation. Check repository permissions, token permissions and organisation SSO policies.", exitCode: result.exitCode });
18067
+ if (/404|could not resolve to a repository/.test(raw) && operation !== "gh-pr-stack") return new GhOperationError({ code: "GH_ACCESS_DENIED", operation, message: "The repository was not found or is not accessible with this GitHub account.", exitCode: result.exitCode });
17155
18068
  const detail = firstLine(result.stderr) || firstLine(result.stdout);
17156
18069
  return new GhOperationError({ code: "GH_PROCESS_FAILED", operation, message: detail || "GitHub CLI could not complete the operation.", exitCode: result.exitCode });
17157
18070
  }
@@ -17167,6 +18080,50 @@ function mapRunnerError(error, operation) {
17167
18080
  function firstLine(value) {
17168
18081
  return (value.split(/\r?\n/).map((line) => line.trim()).find(Boolean) ?? "").slice(0, 300);
17169
18082
  }
18083
+ const statusSchema = object({
18084
+ id: _enum(["codex", "claude", "opencode", "grok"]),
18085
+ label: string(),
18086
+ availability: _enum(["ready", "warning", "error"]),
18087
+ installed: boolean(),
18088
+ authStatus: _enum(["authenticated", "unauthenticated", "unknown"]),
18089
+ installationStatus: _enum(["available", "not-found", "not-executable", "incompatible", "inspection-failed"]).optional(),
18090
+ executablePath: string().optional(),
18091
+ executableSource: _enum(["configured", "process-path", "user-path", "known-location"]).optional(),
18092
+ discoveryWarning: string().optional(),
18093
+ version: string().optional(),
18094
+ message: string().optional(),
18095
+ cliName: string().optional(),
18096
+ models: array(object({ id: string(), label: string(), description: string().optional() })),
18097
+ checkedAt: string().datetime()
18098
+ });
18099
+ const cacheSchema = object({
18100
+ version: literal(1),
18101
+ key: string(),
18102
+ statuses: array(statusSchema).min(1).max(4)
18103
+ }).refine(({ statuses }) => new Set(statuses.map((status) => status.id)).size === statuses.length);
18104
+ class AiStatusStore {
18105
+ constructor(filePath) {
18106
+ this.filePath = filePath;
18107
+ }
18108
+ filePath;
18109
+ pending = Promise.resolve();
18110
+ async load(key) {
18111
+ await this.pending;
18112
+ try {
18113
+ if ((await stat(this.filePath)).size > 4 * 1024 * 1024) return null;
18114
+ const parsed = cacheSchema.safeParse(JSON.parse(await readFile(this.filePath, "utf8")));
18115
+ return parsed.success && parsed.data.key === key ? parsed.data.statuses : null;
18116
+ } catch {
18117
+ return null;
18118
+ }
18119
+ }
18120
+ save(key, statuses) {
18121
+ const snapshot = cacheSchema.parse({ version: 1, key, statuses });
18122
+ const write = this.pending.then(() => writeFileAtomically(this.filePath, JSON.stringify(snapshot), { parseJson: true }));
18123
+ this.pending = write.catch(() => void 0);
18124
+ return write;
18125
+ }
18126
+ }
17170
18127
  const MAX_PROBLEM_LOG_ENTRIES = 500;
17171
18128
  const PROBLEM_LOG_COMPACTION_SLACK = 1.25;
17172
18129
  const LEVELS = ["error", "warn"];
@@ -17570,6 +18527,7 @@ function normalizeRemoteFetchIntervalSeconds(value) {
17570
18527
  const SETTINGS_SCHEMA_VERSION = 1;
17571
18528
  const defaults = {
17572
18529
  version: SETTINGS_SCHEMA_VERSION,
18530
+ githubAccounts: {},
17573
18531
  recentRepositories: [],
17574
18532
  repositoryProjects: [],
17575
18533
  filesTreeStates: [],
@@ -17596,6 +18554,7 @@ const defaults = {
17596
18554
  };
17597
18555
  const settingsRecordSchema = looseObject({
17598
18556
  version: unknown().optional(),
18557
+ githubAccounts: unknown().optional(),
17599
18558
  recentRepositories: unknown().optional(),
17600
18559
  repositoryProjects: unknown().optional(),
17601
18560
  filesTreeStates: unknown().optional(),
@@ -17672,6 +18631,25 @@ class SettingsStore {
17672
18631
  get windowBounds() {
17673
18632
  return { ...this.data.windowBounds };
17674
18633
  }
18634
+ githubAccount(commonDir) {
18635
+ return { ...this.data.githubAccounts[githubRepositoryKey(commonDir)] ?? AUTOMATIC_GITHUB_ACCOUNT };
18636
+ }
18637
+ async setGitHubAccount(commonDir, selection) {
18638
+ const parsed = githubAccountSelectionSchema.parse(selection);
18639
+ const key = githubRepositoryKey(commonDir);
18640
+ const previous = this.data.githubAccounts[key];
18641
+ if (parsed.mode === "auto") delete this.data.githubAccounts[key];
18642
+ else this.data.githubAccounts[key] = parsed;
18643
+ try {
18644
+ await this.save();
18645
+ } catch (error) {
18646
+ if (this.data.githubAccounts[key] === (parsed.mode === "auto" ? void 0 : parsed)) {
18647
+ if (previous) this.data.githubAccounts[key] = previous;
18648
+ else delete this.data.githubAccounts[key];
18649
+ }
18650
+ throw error;
18651
+ }
18652
+ }
17675
18653
  async touchRepository(repository) {
17676
18654
  const recent = { ...repository, lastOpenedAt: (/* @__PURE__ */ new Date()).toISOString() };
17677
18655
  this.data.recentRepositories = [recent, ...this.data.recentRepositories.filter((item) => item.id !== repository.id)];
@@ -17687,6 +18665,12 @@ class SettingsStore {
17687
18665
  async relocateRepository(previousId, repository) {
17688
18666
  const previous = this.data.recentRepositories.find((item) => item.id === previousId);
17689
18667
  if (!previous) throw projectError("Unknown repository.");
18668
+ const previousAccountKey = githubRepositoryKey(previous.commonDir);
18669
+ const nextAccountKey = githubRepositoryKey(repository.commonDir);
18670
+ if (previousAccountKey !== nextAccountKey && this.data.githubAccounts[previousAccountKey]) {
18671
+ this.data.githubAccounts[nextAccountKey] ??= this.data.githubAccounts[previousAccountKey];
18672
+ delete this.data.githubAccounts[previousAccountKey];
18673
+ }
17690
18674
  const previousKey = normalizeRepositoryKey(previous.commonDir);
17691
18675
  const nextKey = normalizeRepositoryKey(repository.commonDir);
17692
18676
  const destinationAlreadyAssigned = this.data.repositoryProjects.some((project) => project.repositoryKeys.some((key) => key === nextKey && key !== previousKey));
@@ -17711,6 +18695,9 @@ class SettingsStore {
17711
18695
  async forgetRepository(repositoryKey) {
17712
18696
  const key = normalizeRepositoryKey(repositoryKey);
17713
18697
  const removedIds = new Set(this.data.recentRepositories.filter((item) => normalizeRepositoryKey(item.commonDir) === key).map((item) => item.id));
18698
+ for (const item of this.data.recentRepositories) {
18699
+ if (removedIds.has(item.id)) delete this.data.githubAccounts[githubRepositoryKey(item.commonDir)];
18700
+ }
17714
18701
  this.data.recentRepositories = this.data.recentRepositories.filter((item) => !removedIds.has(item.id));
17715
18702
  this.data.repositoryProjects = this.data.repositoryProjects.map((project) => ({
17716
18703
  ...project,
@@ -17938,6 +18925,7 @@ function validate(value, defaultDoubleControlShortcutEnabled) {
17938
18925
  const windowBounds = bounds && typeof bounds.width === "number" && Number.isFinite(bounds.width) && typeof bounds.height === "number" && Number.isFinite(bounds.height) ? { width: Math.max(900, bounds.width), height: Math.max(600, bounds.height), ...typeof bounds.x === "number" && Number.isFinite(bounds.x) ? { x: bounds.x } : {}, ...typeof bounds.y === "number" && Number.isFinite(bounds.y) ? { y: bounds.y } : {} } : { ...defaults.windowBounds };
17939
18926
  return {
17940
18927
  version: SETTINGS_SCHEMA_VERSION,
18928
+ githubAccounts: normalizeGitHubAccounts(input.githubAccounts),
17941
18929
  recentRepositories,
17942
18930
  repositoryProjects,
17943
18931
  filesTreeStates,
@@ -18059,6 +19047,16 @@ function executablePaths(value, strict = false, platform2 = process.platform, ho
18059
19047
  }
18060
19048
  return result;
18061
19049
  }
19050
+ function normalizeGitHubAccounts(value) {
19051
+ const result = /* @__PURE__ */ Object.create(null);
19052
+ if (!value || typeof value !== "object" || Array.isArray(value)) return result;
19053
+ for (const [key, selection] of Object.entries(value).slice(0, 1e3)) {
19054
+ if (!key || key.length > MAX_REPOSITORY_KEY_LENGTH || hasControlCharacters$1(key) || !path.isAbsolute(key)) continue;
19055
+ const parsed = githubAccountSelectionSchema.safeParse(selection);
19056
+ if (parsed.success && parsed.data.mode === "account") result[githubRepositoryKey(key)] = parsed.data;
19057
+ }
19058
+ return result;
19059
+ }
18062
19060
  class SystemTrash {
18063
19061
  available;
18064
19062
  operation;
@@ -18129,6 +19127,9 @@ class OpenTigRuntime {
18129
19127
  this.lastNotifiedAt = Date.now();
18130
19128
  this.onEvent({ type: "repository.changed", repositoryId, scope });
18131
19129
  }
19130
+ publishGitHubAccountsChange() {
19131
+ if (!this.closePromise) this.onEvent({ type: "github.accounts-changed" });
19132
+ }
18132
19133
  publishActiveRepositoryChange(repository) {
18133
19134
  if (!this.closePromise) this.onEvent({ type: "repository.active-changed", repository });
18134
19135
  }
@@ -18187,10 +19188,21 @@ async function createOpenTigRuntime(options) {
18187
19188
  ];
18188
19189
  const aiLog = new AiLogStore(options.aiLogPath);
18189
19190
  await aiLog.load();
18190
- const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate());
19191
+ const ai = new CommitMessageService(operations, providers, aiLog, () => cliResolver.invalidate(), {
19192
+ store: new AiStatusStore(path.join(path.dirname(options.settingsPath), "ai-statuses.json")),
19193
+ key: () => aiExecutablePathsKey(settings.preferences.aiExecutablePaths)
19194
+ });
18191
19195
  const prDrafts = new PullRequestDraftService(operations, providers, aiLog);
18192
- const github = new GitHubService(cliResolver, cliRunner, git, repositories);
18193
19196
  let runtime = null;
19197
+ const githubAccounts = new GitHubAccountsService(
19198
+ cliResolver,
19199
+ cliRunner,
19200
+ git,
19201
+ settings,
19202
+ new GitHubStatusStore(path.join(path.dirname(options.settingsPath), "github-status.json")),
19203
+ () => runtime?.publishGitHubAccountsChange()
19204
+ );
19205
+ const github = new GitHubService(cliResolver, cliRunner, git, repositories, githubAccounts);
18194
19206
  const events = {
18195
19207
  repositoryChanged: (repositoryId, scope) => {
18196
19208
  runtime?.publishRepositoryChange(repositoryId, scope);
@@ -18399,6 +19411,8 @@ const IPC = {
18399
19411
  commitUndoLatest: "commit:undo-latest",
18400
19412
  commitsList: "commits:list",
18401
19413
  commitsFiles: "commits:files",
19414
+ commitAuthorship: "commits:authorship",
19415
+ commitSetAuthorship: "commits:set-authorship",
18402
19416
  branchesList: "refs:branches",
18403
19417
  branchSwitch: "refs:switch",
18404
19418
  worktreesList: "refs:worktrees",
@@ -18411,6 +19425,10 @@ const IPC = {
18411
19425
  worktreeDetails: "refs:worktree-details",
18412
19426
  branchDelete: "refs:delete-branch",
18413
19427
  worktreeRemove: "refs:remove-worktree",
19428
+ remoteBranchDetails: "refs:remote-branch-details",
19429
+ trackingBranchCreate: "refs:create-tracking-branch",
19430
+ remoteBranchDelete: "refs:delete-remote-branch",
19431
+ branchesFetch: "refs:fetch-branches",
18414
19432
  aiStatuses: "ai:statuses",
18415
19433
  aiGenerateCommitMessage: "ai:generate-commit-message",
18416
19434
  aiCancelGeneration: "ai:cancel-generation",
@@ -18419,6 +19437,9 @@ const IPC = {
18419
19437
  diagnosticsList: "diagnostics:list",
18420
19438
  diagnosticsClear: "diagnostics:clear",
18421
19439
  diagnosticsRecord: "diagnostics:record",
19440
+ githubAccountsStatus: "github:accounts-status",
19441
+ githubRepositoryAccount: "github:repository-account",
19442
+ githubSetRepositoryAccount: "github:set-repository-account",
18422
19443
  githubStatus: "github:status",
18423
19444
  githubRepositoryInfo: "github:repository-info",
18424
19445
  githubPrForBranch: "github:pr-for-branch",
@@ -18484,6 +19505,8 @@ const OPEN_TIG_SERVER_COMMANDS = {
18484
19505
  "index.updateConflict": command(IPC.indexUpdateConflict, "update-conflict", true, { maxRequestBytes: CONTENT_MAX_REQUEST_BYTES }),
18485
19506
  "index.resolveConflict": command(IPC.indexResolveConflict, "resolve-conflict", true, { maxRequestBytes: CONTENT_MAX_REQUEST_BYTES }),
18486
19507
  "commits.create": command(IPC.commitCreate, "commit", true),
19508
+ "commits.authorship": command(IPC.commitAuthorship, "commit-authorship-read", false),
19509
+ "commits.setAuthorship": command(IPC.commitSetAuthorship, "commit-authorship-set", true),
18487
19510
  "commits.undoLatest": command(IPC.commitUndoLatest, "undo-latest-commit", true),
18488
19511
  "commits.list": command(IPC.commitsList, "history", false),
18489
19512
  "commits.files": command(IPC.commitsFiles, "commit-files", false),
@@ -18496,6 +19519,10 @@ const OPEN_TIG_SERVER_COMMANDS = {
18496
19519
  "refs.fetch": command(IPC.refsFetch, "fetch", true),
18497
19520
  "refs.localRefsSnapshot": command(IPC.localRefsSnapshot, "local-refs-snapshot", false),
18498
19521
  "refs.branchDetails": command(IPC.branchDetails, "branch-details", false),
19522
+ "refs.remoteBranchDetails": command(IPC.remoteBranchDetails, "remote-branch-details", false, { timeoutMs: 12e4 }),
19523
+ "refs.createTrackingBranch": command(IPC.trackingBranchCreate, "create-tracking-branch", true),
19524
+ "refs.deleteRemoteBranch": command(IPC.remoteBranchDelete, "delete-remote-branch", true, { timeoutMs: 18e4 }),
19525
+ "refs.fetchBranches": command(IPC.branchesFetch, "fetch-branches", true, { timeoutMs: 18e4 }),
18499
19526
  "refs.worktreeDetails": command(IPC.worktreeDetails, "worktree-details", false),
18500
19527
  "refs.deleteBranch": command(IPC.branchDelete, "delete-branch", true),
18501
19528
  "refs.removeWorktree": command(IPC.worktreeRemove, "remove-worktree", true),
@@ -18504,6 +19531,9 @@ const OPEN_TIG_SERVER_COMMANDS = {
18504
19531
  "ai.log": command(IPC.aiLog, "ai-log", false),
18505
19532
  "ai.clearLog": command(IPC.aiClearLog, "ai-clear-log", true),
18506
19533
  "ai.cancelGeneration": command(IPC.aiCancelGeneration, "ai-cancel-generation", true),
19534
+ "github.accountsStatus": command(IPC.githubAccountsStatus, "gh-accounts-status", false, { timeoutMs: 9e4 }),
19535
+ "github.repositoryAccount": command(IPC.githubRepositoryAccount, "gh-repository-account", false, { timeoutMs: 6e4 }),
19536
+ "github.setRepositoryAccount": command(IPC.githubSetRepositoryAccount, "gh-set-repository-account", true),
18507
19537
  "github.status": command(IPC.githubStatus, "gh-status", false),
18508
19538
  "github.repositoryInfo": command(IPC.githubRepositoryInfo, "gh-repository-info", false),
18509
19539
  "github.findPullRequestForBranch": command(IPC.githubPrForBranch, "gh-pr-for-branch", false),
@@ -18730,10 +19760,10 @@ async function inspectRepository(path2, git) {
18730
19760
  }
18731
19761
  const requestRecordSchema = looseObject({});
18732
19762
  function boundedStringSchema(maxLength, options = {}) {
18733
- let schema = string().max(maxLength).refine((value) => !value.includes("\0"));
18734
- if (options.empty !== true) schema = schema.min(1);
18735
- if (options.controls === false) return schema;
18736
- return schema.refine((value) => ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127));
19763
+ let schema2 = string().max(maxLength).refine((value) => !value.includes("\0"));
19764
+ if (options.empty !== true) schema2 = schema2.min(1);
19765
+ if (options.controls === false) return schema2;
19766
+ return schema2.refine((value) => ![...value].some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127));
18737
19767
  }
18738
19768
  const booleanWithDefaultSchema = (fallback) => boolean().optional().default(fallback);
18739
19769
  function parseRecord(value) {
@@ -18884,6 +19914,22 @@ function deleteBranchArg(value, operation) {
18884
19914
  force: booleanArg(request.force, operation)
18885
19915
  };
18886
19916
  }
19917
+ function createTrackingBranchArg(value) {
19918
+ const operation = "create-tracking-branch";
19919
+ const request = requestObject(value, operation);
19920
+ return {
19921
+ ...branchDetailsArg(request, operation),
19922
+ expectedOid: oidArg(request.expectedOid, operation),
19923
+ localName: refsString(request.localName, operation, MAX_BRANCH_REF_LENGTH)
19924
+ };
19925
+ }
19926
+ function deleteRemoteBranchArg(value) {
19927
+ const operation = "delete-remote-branch";
19928
+ const request = requestObject(value, operation);
19929
+ const destinationId = refsString(request.destinationId, operation, 64);
19930
+ if (!/^[0-9a-f]{64}$/.test(destinationId)) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation, message: "Invalid remote destination." });
19931
+ return { ...branchDetailsArg(request, operation), expectedOid: oidArg(request.expectedOid, operation), destinationId };
19932
+ }
18887
19933
  function removeWorktreeArg(value, operation) {
18888
19934
  const request = requestObject(value, operation);
18889
19935
  return {
@@ -18981,7 +20027,16 @@ function createPullRequestArg(value) {
18981
20027
  if (typeof input.base !== "string" || !input.base || input.base.length > 300 || input.base.includes("\0")) throw invalidGh(operation);
18982
20028
  if (typeof input.draft !== "boolean") throw invalidGh(operation);
18983
20029
  if (typeof input.repositoryId !== "string" || !input.repositoryId || input.repositoryId.length > 64) throw invalidGh(operation);
18984
- return { repositoryId: input.repositoryId, title: input.title.trim(), body: input.body, base: input.base, draft: input.draft };
20030
+ const expected = input.expectedAccount;
20031
+ if (expected !== void 0 && (!expected || typeof expected.login !== "string" || !/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/.test(expected.login) || !Number.isSafeInteger(expected.revision) || expected.revision < 0)) throw invalidGh(operation);
20032
+ return {
20033
+ repositoryId: input.repositoryId,
20034
+ title: input.title.trim(),
20035
+ body: input.body,
20036
+ base: input.base,
20037
+ draft: input.draft,
20038
+ ...expected ? { expectedAccount: { login: expected.login, revision: expected.revision } } : {}
20039
+ };
18985
20040
  }
18986
20041
  function generatePullRequestDraftArg(value) {
18987
20042
  const operation = "ai-pr-draft";
@@ -19069,8 +20124,9 @@ function registerServerCommands(registry2, services, host) {
19069
20124
  };
19070
20125
  });
19071
20126
  handle(IPC.preferences, "preferences", async (partial2) => {
20127
+ const previousPaths = aiExecutablePathsKey(services.settings.preferences.aiExecutablePaths);
19072
20128
  const preferences = await services.settings.setPreferences(partial2 ?? {});
19073
- if (partial2 && typeof partial2 === "object" && "aiExecutablePaths" in partial2) services.ai.invalidateStatuses();
20129
+ if (previousPaths !== aiExecutablePathsKey(preferences.aiExecutablePaths)) services.ai.invalidateStatuses();
19074
20130
  return preferences;
19075
20131
  });
19076
20132
  handle(IPC.filesTreeStateUpdate, "files-tree-state", (repositoryId, expandedPaths) => {
@@ -19329,6 +20385,17 @@ function registerServerCommands(registry2, services, host) {
19329
20385
  textArg(content, "update-conflict", 8 * 1024 * 1024)
19330
20386
  ));
19331
20387
  handle(IPC.commitCreate, "commit", (id, message) => services.operations.createCommit(stringArg(id, "commit", 64), stringArg(message, "commit", 1e5)));
20388
+ handle(IPC.commitAuthorship, "commit-authorship-read", (id) => services.operations.getCommitAuthorship(stringArg(id, "commit-authorship-read", 64)));
20389
+ handle(IPC.commitSetAuthorship, "commit-authorship-set", async (id, input) => {
20390
+ const repositoryId = stringArg(id, "commit-authorship-set", 64);
20391
+ const parsed = commitAuthorshipInputSchema.safeParse(input);
20392
+ if (!parsed.success) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "commit-authorship-set", message: "Enter a valid name, email and reviewed identity." });
20393
+ const result = await services.operations.setCommitAuthorship(repositoryId, parsed.data);
20394
+ const repository = services.repositories.get(repositoryId);
20395
+ const ids = /* @__PURE__ */ new Set([repositoryId, ...services.repositories.recents().filter((item) => item.commonDir === repository.commonDir).map((item) => item.id)]);
20396
+ for (const siblingId of ids) services.events.repositoryChanged(siblingId, "unknown");
20397
+ return result;
20398
+ });
19332
20399
  handle(IPC.commitUndoLatest, "undo-latest-commit", (id, expectedOid) => services.operations.undoLatestCommit(
19333
20400
  stringArg(id, "undo-latest-commit", 64),
19334
20401
  oidArg(expectedOid, "undo-latest-commit")
@@ -19353,6 +20420,13 @@ function registerServerCommands(registry2, services, host) {
19353
20420
  const { repositoryId, fullName } = branchDetailsArg(request, "branch-details");
19354
20421
  return services.operations.branchDetails(repositoryId, fullName);
19355
20422
  });
20423
+ handle(IPC.remoteBranchDetails, "remote-branch-details", (request) => {
20424
+ const { repositoryId, fullName } = branchDetailsArg(request, "remote-branch-details");
20425
+ return services.operations.remoteBranchDetails(repositoryId, fullName);
20426
+ });
20427
+ handle(IPC.trackingBranchCreate, "create-tracking-branch", (request) => services.operations.createTrackingBranch(createTrackingBranchArg(request)));
20428
+ handle(IPC.remoteBranchDelete, "delete-remote-branch", (request) => services.operations.deleteRemoteBranch(deleteRemoteBranchArg(request)));
20429
+ handle(IPC.branchesFetch, "fetch-branches", (id) => services.operations.fetchBranches(stringArg(id, "fetch-branches", 64)));
19356
20430
  handle(IPC.worktreeDetails, "worktree-details", (request) => {
19357
20431
  const { repositoryId, path: path2 } = worktreeDetailsArg(request, "worktree-details");
19358
20432
  return services.operations.worktreeDetails(repositoryId, path2);
@@ -19390,6 +20464,13 @@ function registerServerCommands(registry2, services, host) {
19390
20464
  handle(IPC.aiCancelGeneration, "ai-cancel-generation", (requestId2) => {
19391
20465
  services.ai.cancel(aiString(requestId2, "ai-cancel-generation", 100, true));
19392
20466
  });
20467
+ handle(IPC.githubAccountsStatus, "gh-accounts-status", (forceRefresh) => services.github.accounts.status(booleanArg(forceRefresh, "gh-accounts-status")));
20468
+ handle(IPC.githubRepositoryAccount, "gh-repository-account", (id, forceRefresh) => services.github.repositoryAccount(stringArg(id, "gh-repository-account", 64), booleanArg(forceRefresh, "gh-repository-account")));
20469
+ handle(IPC.githubSetRepositoryAccount, "gh-set-repository-account", (id, selection) => {
20470
+ const parsed = githubAccountSelectionSchema.safeParse(selection);
20471
+ if (!parsed.success) throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "gh-set-repository-account", message: "Invalid GitHub account selection." });
20472
+ return services.github.setRepositoryAccount(stringArg(id, "gh-set-repository-account", 64), parsed.data);
20473
+ });
19393
20474
  handle(IPC.githubStatus, "gh-status", (forceRefresh) => services.github.status(booleanArg(forceRefresh, "gh-status")));
19394
20475
  handle(IPC.githubRepositoryInfo, "gh-repository-info", (id) => services.github.repositoryInfo(stringArg(id, "gh-repository-info", 64)));
19395
20476
  handle(IPC.githubPrForBranch, "gh-pr-for-branch", (id, branchName) => services.github.findPullRequestForBranch(
@@ -19439,7 +20520,7 @@ function clipboardImageArg(value) {
19439
20520
  if (value instanceof Uint8Array) return value;
19440
20521
  throw new GitOperationError({ code: "INVALID_ARGUMENT", operation: "paste-entries", message: "Invalid clipboard image." });
19441
20522
  }
19442
- const protocolVersion = 1;
20523
+ const protocolVersion = 4;
19443
20524
  const protocolMetadata = {
19444
20525
  protocolVersion
19445
20526
  };
@@ -19455,6 +20536,85 @@ function preferredServerPort(profile) {
19455
20536
  }
19456
20537
  const AUTH_DATA_VERSION = 4;
19457
20538
  const BROWSER_SESSION_MAX_AGE_SECONDS = 30 * 24 * 60 * 60;
20539
+ async function hasActiveBrowserSessions(directory, now = Date.now) {
20540
+ const { sessions } = await loadSessions(path.join(directory, "sessions.json"));
20541
+ return sessions.some((session) => session.kind !== "desktop" && session.expiresAt !== null && now() < Date.parse(session.expiresAt));
20542
+ }
20543
+ async function loadSessions(filePath) {
20544
+ try {
20545
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
20546
+ if (isLegacyAuthData(parsed)) {
20547
+ await chmod(filePath, 384);
20548
+ return {
20549
+ sessions: parsed.sessions.map((session) => ({
20550
+ ...session,
20551
+ kind: "legacy",
20552
+ expiresAt: sessionExpiry("legacy", session.createdAt),
20553
+ clientName: "Legacy browser session",
20554
+ deviceType: "unknown",
20555
+ os: null,
20556
+ browser: null,
20557
+ remoteAddress: null,
20558
+ viaProxy: false,
20559
+ lastConnectedAt: null
20560
+ })),
20561
+ migrated: true
20562
+ };
20563
+ }
20564
+ if (isVersionTwoAuthData(parsed)) {
20565
+ await chmod(filePath, 384);
20566
+ return {
20567
+ sessions: parsed.sessions.map((session) => ({
20568
+ ...session,
20569
+ expiresAt: sessionExpiry(session.kind, session.createdAt),
20570
+ clientName: session.kind === "legacy" ? "Legacy browser session" : session.clientName,
20571
+ deviceType: session.kind === "desktop" ? "desktop" : "unknown",
20572
+ os: null,
20573
+ browser: session.kind === "browser" ? session.clientName : null,
20574
+ viaProxy: false,
20575
+ lastConnectedAt: null
20576
+ })),
20577
+ migrated: true
20578
+ };
20579
+ }
20580
+ if (isVersionThreeAuthData(parsed)) {
20581
+ await chmod(filePath, 384);
20582
+ return {
20583
+ sessions: parsed.sessions.map((session) => ({ ...session, expiresAt: sessionExpiry(session.kind, session.createdAt) })),
20584
+ migrated: true
20585
+ };
20586
+ }
20587
+ if (!isAuthData(parsed)) throw corrupt$1("session data");
20588
+ await chmod(filePath, 384);
20589
+ return { sessions: parsed.sessions.map((session) => ({ ...session })), migrated: false };
20590
+ } catch (error) {
20591
+ if (error.code === "ENOENT") return { sessions: [], migrated: false };
20592
+ if (error instanceof SyntaxError) throw corrupt$1("session data");
20593
+ throw error;
20594
+ }
20595
+ }
20596
+ function isAuthData(value) {
20597
+ const data = value;
20598
+ return Boolean(data && data.version === AUTH_DATA_VERSION && isVersionThreeAuthData({ ...data, version: 3 }) && data.sessions?.every((session) => session.kind === "desktop" ? session.expiresAt === null : typeof session.expiresAt === "string" && Number.isFinite(Date.parse(session.expiresAt)) && Date.parse(session.expiresAt) > Date.parse(session.createdAt)));
20599
+ }
20600
+ function isVersionThreeAuthData(value) {
20601
+ const data = value;
20602
+ return Boolean(data && data.version === 3 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.deviceType === "desktop" || session.deviceType === "mobile" || session.deviceType === "tablet" || session.deviceType === "bot" || session.deviceType === "unknown") && (session.os === null || typeof session.os === "string" && session.os.length <= 80) && (session.browser === null || typeof session.browser === "string" && session.browser.length <= 80) && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.viaProxy === "boolean" && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt)) && (session.lastConnectedAt === null || typeof session.lastConnectedAt === "string" && !Number.isNaN(Date.parse(session.lastConnectedAt)))));
20603
+ }
20604
+ function isVersionTwoAuthData(value) {
20605
+ const data = value;
20606
+ return Boolean(data && data.version === 2 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
20607
+ }
20608
+ function isLegacyAuthData(value) {
20609
+ const data = value;
20610
+ return Boolean(data && data.version === 1 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
20611
+ }
20612
+ function corrupt$1(name) {
20613
+ return new Error(`OpenTig authentication ${name} is corrupt.`);
20614
+ }
20615
+ function sessionExpiry(kind, createdAt) {
20616
+ return kind === "desktop" ? null : new Date(Date.parse(createdAt) + BROWSER_SESSION_MAX_AGE_SECONDS * 1e3).toISOString();
20617
+ }
19458
20618
  const SECRET_BYTES = 32;
19459
20619
  const SECRET_FILE = "server-secret";
19460
20620
  const SESSIONS_FILE = "sessions.json";
@@ -19470,6 +20630,10 @@ class PersistentAuthStore {
19470
20630
  now;
19471
20631
  sessions;
19472
20632
  mutationQueue = Promise.resolve();
20633
+ /** Inspect an existing installation without issuing, revoking, or writing sessions. */
20634
+ static async hasActiveBrowserSessions(directory, now = Date.now) {
20635
+ return hasActiveBrowserSessions(directory, now);
20636
+ }
19473
20637
  static async open(directory, now = Date.now) {
19474
20638
  const resolved = path.resolve(directory);
19475
20639
  await mkdir(resolved, { recursive: true, mode: 448 });
@@ -19659,75 +20823,6 @@ async function loadOrCreateSecret(filePath) {
19659
20823
  throw error;
19660
20824
  }
19661
20825
  }
19662
- async function loadSessions(filePath) {
19663
- try {
19664
- const parsed = JSON.parse(await readFile(filePath, "utf8"));
19665
- if (isLegacyAuthData(parsed)) {
19666
- await chmod(filePath, 384);
19667
- return {
19668
- sessions: parsed.sessions.map((session) => ({
19669
- ...session,
19670
- kind: "legacy",
19671
- expiresAt: sessionExpiry("legacy", session.createdAt),
19672
- clientName: "Legacy browser session",
19673
- deviceType: "unknown",
19674
- os: null,
19675
- browser: null,
19676
- remoteAddress: null,
19677
- viaProxy: false,
19678
- lastConnectedAt: null
19679
- })),
19680
- migrated: true
19681
- };
19682
- }
19683
- if (isVersionTwoAuthData(parsed)) {
19684
- await chmod(filePath, 384);
19685
- return {
19686
- sessions: parsed.sessions.map((session) => ({
19687
- ...session,
19688
- expiresAt: sessionExpiry(session.kind, session.createdAt),
19689
- clientName: session.kind === "legacy" ? "Legacy browser session" : session.clientName,
19690
- deviceType: session.kind === "desktop" ? "desktop" : "unknown",
19691
- os: null,
19692
- browser: session.kind === "browser" ? session.clientName : null,
19693
- viaProxy: false,
19694
- lastConnectedAt: null
19695
- })),
19696
- migrated: true
19697
- };
19698
- }
19699
- if (isVersionThreeAuthData(parsed)) {
19700
- await chmod(filePath, 384);
19701
- return {
19702
- sessions: parsed.sessions.map((session) => ({ ...session, expiresAt: sessionExpiry(session.kind, session.createdAt) })),
19703
- migrated: true
19704
- };
19705
- }
19706
- if (!isAuthData(parsed)) throw corrupt("session data");
19707
- await chmod(filePath, 384);
19708
- return { sessions: parsed.sessions.map((session) => ({ ...session })), migrated: false };
19709
- } catch (error) {
19710
- if (error.code === "ENOENT") return { sessions: [], migrated: false };
19711
- if (error instanceof SyntaxError) throw corrupt("session data");
19712
- throw error;
19713
- }
19714
- }
19715
- function isAuthData(value) {
19716
- const data = value;
19717
- return Boolean(data && data.version === AUTH_DATA_VERSION && isVersionThreeAuthData({ ...data, version: 3 }) && data.sessions?.every((session) => session.kind === "desktop" ? session.expiresAt === null : typeof session.expiresAt === "string" && Number.isFinite(Date.parse(session.expiresAt)) && Date.parse(session.expiresAt) > Date.parse(session.createdAt)));
19718
- }
19719
- function isVersionThreeAuthData(value) {
19720
- const data = value;
19721
- return Boolean(data && data.version === 3 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.deviceType === "desktop" || session.deviceType === "mobile" || session.deviceType === "tablet" || session.deviceType === "bot" || session.deviceType === "unknown") && (session.os === null || typeof session.os === "string" && session.os.length <= 80) && (session.browser === null || typeof session.browser === "string" && session.browser.length <= 80) && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.viaProxy === "boolean" && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt)) && (session.lastConnectedAt === null || typeof session.lastConnectedAt === "string" && !Number.isNaN(Date.parse(session.lastConnectedAt)))));
19722
- }
19723
- function isVersionTwoAuthData(value) {
19724
- const data = value;
19725
- return Boolean(data && data.version === 2 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && (session.kind === "desktop" || session.kind === "browser" || session.kind === "legacy") && typeof session.clientName === "string" && session.clientName.length > 0 && session.clientName.length <= 160 && (session.remoteAddress === null || typeof session.remoteAddress === "string" && session.remoteAddress.length <= 128) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
19726
- }
19727
- function isLegacyAuthData(value) {
19728
- const data = value;
19729
- return Boolean(data && data.version === 1 && Array.isArray(data.sessions) && data.sessions.every((session) => session && typeof session.id === "string" && /^[A-Za-z0-9_-]{24}$/.test(session.id) && typeof session.digest === "string" && /^[A-Za-z0-9_-]{43}$/.test(session.digest) && typeof session.createdAt === "string" && !Number.isNaN(Date.parse(session.createdAt))));
19730
- }
19731
20826
  function constantTimeEqual(left, right) {
19732
20827
  const leftBytes = Buffer.from(left);
19733
20828
  const rightBytes = Buffer.from(right);
@@ -19745,17 +20840,15 @@ async function exists(filePath) {
19745
20840
  function corrupt(name) {
19746
20841
  return new Error(`OpenTig authentication ${name} is corrupt.`);
19747
20842
  }
19748
- function sessionExpiry(kind, createdAt) {
19749
- return kind === "desktop" ? null : new Date(Date.parse(createdAt) + BROWSER_SESSION_MAX_AGE_SECONDS * 1e3).toISOString();
19750
- }
19751
20843
  const DEFAULT_PAIRING_TTL_MS = 5 * 60 * 1e3;
19752
20844
  class OpenTigSessionAuth {
19753
- constructor(source, store, secureCookies, now, profile) {
20845
+ constructor(source, store, secureCookies, now, profile, browserAccessEnabled) {
19754
20846
  this.source = source;
19755
20847
  this.store = store;
19756
20848
  this.secureCookies = secureCookies;
19757
20849
  this.now = now;
19758
20850
  this.profile = profile;
20851
+ this.browserAccessEnabled = browserAccessEnabled;
19759
20852
  }
19760
20853
  source;
19761
20854
  store;
@@ -19763,15 +20856,30 @@ class OpenTigSessionAuth {
19763
20856
  now;
19764
20857
  profile;
19765
20858
  pairing = null;
20859
+ browserAccessEnabled;
20860
+ browserGeneration = 0;
20861
+ browserQueue = Promise.resolve();
19766
20862
  static async open(options) {
19767
20863
  const store = await PersistentAuthStore.open(options.dataDirectory, options.now);
19768
- return new OpenTigSessionAuth(options.source, store, options.secureCookies ?? false, options.now ?? Date.now, options.profile ?? "production");
20864
+ return new OpenTigSessionAuth(options.source, store, options.secureCookies ?? false, options.now ?? Date.now, options.profile ?? "production", options.browserAccessEnabled ?? true);
19769
20865
  }
19770
20866
  descriptor() {
19771
20867
  this.dropExpiredPairing();
19772
- return { authenticationRequired: true, pairingAvailable: this.pairing !== null };
20868
+ return { authenticationRequired: true, pairingAvailable: this.browserAccessEnabled && this.pairing !== null, browserAccessEnabled: this.browserAccessEnabled };
20869
+ }
20870
+ setBrowserAccessEnabled(enabled) {
20871
+ if (!enabled) {
20872
+ this.browserAccessEnabled = false;
20873
+ this.browserGeneration++;
20874
+ this.pairing = null;
20875
+ }
20876
+ const generation = this.browserGeneration;
20877
+ return this.queueBrowserMutation(async () => {
20878
+ this.browserAccessEnabled = enabled && generation === this.browserGeneration;
20879
+ });
19773
20880
  }
19774
20881
  createPairingToken(ttlMs = DEFAULT_PAIRING_TTL_MS) {
20882
+ if (!this.browserAccessEnabled) throw new Error("Web access is disabled.");
19775
20883
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 1e3 || ttlMs > 15 * 60 * 1e3) throw new Error("Invalid pairing token lifetime.");
19776
20884
  const token = randomBytes(32).toString("base64url");
19777
20885
  const expiresAt = this.now() + ttlMs;
@@ -19793,30 +20901,43 @@ class OpenTigSessionAuth {
19793
20901
  async exchangePairingToken(token, metadata, secure = false) {
19794
20902
  this.dropExpiredPairing();
19795
20903
  const pairing = this.pairing;
19796
- if (!pairing || !isCredential(token) || !this.store.matchesDigest(token, pairing.digest)) return null;
20904
+ if (!this.browserAccessEnabled || !pairing || !isCredential(token) || !this.store.matchesDigest(token, pairing.digest)) return null;
19797
20905
  this.pairing = null;
19798
- try {
19799
- return await this.issueCookie({
19800
- kind: "browser",
19801
- clientName: metadata?.clientName ?? "Browser",
19802
- deviceType: metadata?.deviceType ?? "unknown",
19803
- os: metadata?.os ?? null,
19804
- browser: metadata?.browser ?? null,
19805
- remoteAddress: metadata?.remoteAddress ?? null,
19806
- viaProxy: metadata?.viaProxy ?? false
19807
- }, secure);
19808
- } catch (error) {
19809
- if (this.now() < pairing.expiresAt && !this.pairing) this.pairing = pairing;
19810
- throw error;
19811
- }
20906
+ const generation = this.browserGeneration;
20907
+ return this.queueBrowserMutation(async () => {
20908
+ if (!this.browserAccessEnabled || generation !== this.browserGeneration) return null;
20909
+ try {
20910
+ const cookie = await this.issueCookie({
20911
+ kind: "browser",
20912
+ clientName: metadata?.clientName ?? "Browser",
20913
+ deviceType: metadata?.deviceType ?? "unknown",
20914
+ os: metadata?.os ?? null,
20915
+ browser: metadata?.browser ?? null,
20916
+ remoteAddress: metadata?.remoteAddress ?? null,
20917
+ viaProxy: metadata?.viaProxy ?? false
20918
+ }, secure);
20919
+ if (!this.browserAccessEnabled || generation !== this.browserGeneration) {
20920
+ await this.revoke({ cookie });
20921
+ return null;
20922
+ }
20923
+ return cookie;
20924
+ } catch (error) {
20925
+ if (this.browserAccessEnabled && generation === this.browserGeneration && this.now() < pairing.expiresAt && !this.pairing) this.pairing = pairing;
20926
+ throw error;
20927
+ }
20928
+ });
19812
20929
  }
19813
20930
  authenticate(headers) {
19814
20931
  const token = readCookie(headers.cookie, sessionCookieName(this.profile));
19815
- return token ? this.store.authenticate(token) : null;
20932
+ const sessionId = token ? this.store.authenticate(token) : null;
20933
+ return sessionId && this.hasSession(sessionId) ? sessionId : null;
19816
20934
  }
19817
20935
  async renewBrowserCookie(headers, secure = false) {
20936
+ if (!this.browserAccessEnabled) return null;
20937
+ const generation = this.browserGeneration;
19818
20938
  const token = readCookie(headers.cookie, sessionCookieName(this.profile));
19819
20939
  if (!token || !await this.store.renewBrowserSession(token)) return null;
20940
+ if (!this.browserAccessEnabled || generation !== this.browserGeneration) return null;
19820
20941
  return this.sessionCookie(token, true, secure);
19821
20942
  }
19822
20943
  async revoke(headers) {
@@ -19861,11 +20982,18 @@ class OpenTigSessionAuth {
19861
20982
  };
19862
20983
  }
19863
20984
  hasSession(sessionId) {
19864
- return this.store.hasSession(sessionId);
20985
+ return this.store.hasSession(sessionId) && (this.browserAccessEnabled || this.sessions().some((session) => session.id === sessionId && session.kind === "desktop"));
19865
20986
  }
19866
20987
  close() {
20988
+ this.browserAccessEnabled = false;
20989
+ this.browserGeneration++;
19867
20990
  this.pairing = null;
19868
- return this.store.close();
20991
+ return this.browserQueue.then(() => this.store.close());
20992
+ }
20993
+ queueBrowserMutation(operation) {
20994
+ const result = this.browserQueue.then(operation);
20995
+ this.browserQueue = result.then(() => void 0, () => void 0);
20996
+ return result;
19869
20997
  }
19870
20998
  expiredCookie(secure = false) {
19871
20999
  return `${sessionCookieName(this.profile)}=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0${this.secureCookies || secure ? "; Secure" : ""}`;
@@ -20153,6 +21281,7 @@ async function handleRequest(context, request, response) {
20153
21281
  return sendJson(response, 204, null, { "Set-Cookie": cookie });
20154
21282
  }
20155
21283
  if (rawPath === "/api/auth/pair" || rawPath === "/api/auth/desktop") {
21284
+ if (rawPath === "/api/auth/pair" && !context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
20156
21285
  const body = await readJsonObject(request, response);
20157
21286
  if (!body) return;
20158
21287
  const secure = requestOriginIsSecure(request);
@@ -20164,6 +21293,7 @@ async function handleRequest(context, request, response) {
20164
21293
  } else {
20165
21294
  cookie = await context.auth.exchangeDesktopSecret(body.secret);
20166
21295
  }
21296
+ if (!cookie && rawPath === "/api/auth/pair" && !context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
20167
21297
  if (!cookie) return sendJson(response, 401, { error: "Authentication failed." });
20168
21298
  return sendJson(response, 204, null, { "Set-Cookie": cookie });
20169
21299
  }
@@ -20324,6 +21454,7 @@ async function handleLocalAdminPair(context, request, response) {
20324
21454
  }
20325
21455
  const origin = normalizePublicOrigin(body.publicOrigin);
20326
21456
  if (!origin) return sendJson(response, 400, { error: "Invalid public origin." });
21457
+ if (!context.auth.descriptor().browserAccessEnabled) return sendJson(response, 403, { error: "Web access is disabled.", code: "WEB_ACCESS_DISABLED" });
20327
21458
  const pairing = admin.createPairingToken();
20328
21459
  const url = new URL("/pair", origin);
20329
21460
  url.hash = new URLSearchParams({ token: pairing.token }).toString();
@@ -20456,7 +21587,10 @@ class OpenTigWebSocketTransport {
20456
21587
  revokeSessions(sessionIds) {
20457
21588
  const revoked = new Set(sessionIds);
20458
21589
  for (const [socket, state] of this.clients) {
20459
- if (revoked.has(state.sessionId)) socket.close(1008, "Session revoked");
21590
+ if (revoked.has(state.sessionId)) {
21591
+ for (const controller of state.requests.values()) controller.abort();
21592
+ socket.close(1008, "Session revoked");
21593
+ }
20460
21594
  }
20461
21595
  }
20462
21596
  close() {
@@ -20499,6 +21633,7 @@ class OpenTigWebSocketTransport {
20499
21633
  socket.on("error", () => this.options.logger("warn", "WebSocket connection failed."));
20500
21634
  }
20501
21635
  async message(socket, state, data, isBinary) {
21636
+ if (!this.options.auth.hasSession(state.sessionId)) return socket.close(1008, "Session expired or revoked");
20502
21637
  if (isBinary) return socket.close(1003, "Text messages only");
20503
21638
  const bytes = rawBytes(data);
20504
21639
  if (bytes.byteLength > FILE_WRITE_FRAME_LIMIT) return socket.close(1009, "Message too large");
@@ -20696,7 +21831,13 @@ class OpenTigServer {
20696
21831
  return this.options.auth.createPairingToken();
20697
21832
  }
20698
21833
  getStatus() {
20699
- return { connectedSessionCount: this.webSockets.connectedSessionCount };
21834
+ return { connectedSessionCount: this.webSockets.connectedSessionCount, browserAccessEnabled: this.options.auth.descriptor().browserAccessEnabled };
21835
+ }
21836
+ async setBrowserAccessEnabled(enabled) {
21837
+ if (!this.ready) throw new Error("Server is not ready.");
21838
+ const pending = this.options.auth.setBrowserAccessEnabled(enabled);
21839
+ if (!enabled) this.disconnectSessions(this.options.auth.sessions().filter((session) => session.kind !== "desktop").map((session) => session.id));
21840
+ await pending;
20700
21841
  }
20701
21842
  async revokeAllSessions() {
20702
21843
  if (!this.ready) throw new Error("Server is not ready.");
@@ -20754,6 +21895,7 @@ async function runOpenTigServer(config2) {
20754
21895
  let transport = null;
20755
21896
  const auth = await OpenTigSessionAuth.open({
20756
21897
  source: config2.auth,
21898
+ browserAccessEnabled: config2.browserAccessEnabled ?? config2.mode === "web-access",
20757
21899
  ...config2.profile ? { profile: config2.profile } : {},
20758
21900
  dataDirectory: config2.serverDataPath ?? path.join(path.dirname(config2.settingsPath), "server"),
20759
21901
  ...config2.secureCookies === void 0 ? {} : { secureCookies: config2.secureCookies }
@@ -20807,6 +21949,7 @@ async function runOpenTigServer(config2) {
20807
21949
  createPairingLink: () => pairingLink(address.origin, transport.createPairingToken()),
20808
21950
  getStatus: () => transport.getStatus(),
20809
21951
  revokeAllSessions: () => transport.revokeAllSessions(),
21952
+ setBrowserAccessEnabled: (enabled) => transport.setBrowserAccessEnabled(enabled),
20810
21953
  close: () => transport.stop()
20811
21954
  };
20812
21955
  } catch (error) {