@vantran-se/openchamber-web 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/README.md +32 -24
  2. package/bin/cli.js +1 -3
  3. package/bin/cli.test.js +40 -16
  4. package/bin/lib/DOCUMENTATION.md +6 -5
  5. package/bin/lib/cli-args.js +6 -1
  6. package/bin/lib/cli-args.test.js +34 -0
  7. package/bin/lib/cli-lifecycle.js +0 -1
  8. package/bin/lib/cli-network.js +8 -2
  9. package/bin/lib/cli-process.js +1 -1
  10. package/bin/lib/cli-settings-accessors.test.js +21 -13
  11. package/bin/lib/cli-startup.js +17 -11
  12. package/bin/lib/cli-startup.test.js +92 -2
  13. package/bin/lib/commands-connect-url.js +8 -1
  14. package/bin/lib/commands-schedule.js +8 -2
  15. package/bin/lib/commands-serve.js +15 -21
  16. package/bin/lib/commands-update.js +12 -24
  17. package/bin/lib/commands-update.test.js +14 -46
  18. package/dist/assets/DiffView-Cv9MSXl1.js +4 -0
  19. package/dist/assets/FilePreviewCommentMenu-D4kLseHj.js +4 -0
  20. package/dist/assets/FilesView-BVoD8I2x.js +14 -0
  21. package/dist/assets/{GitView-DzuzhIPx.js → GitView-Bzb8p-ew.js} +4 -4
  22. package/dist/assets/GuestSurfacePane-Crdbvsxo.js +1 -0
  23. package/dist/assets/InProgressOperationBanner-CwMjNQgk.js +2 -0
  24. package/dist/assets/{JsonTreeView-B_EPRjzy.js → JsonTreeView-DRKHZfsn.js} +1 -1
  25. package/dist/assets/MarkdownImageGallery-DZtqT1Zn.js +1 -0
  26. package/dist/assets/MarkdownRendererImpl-CqG7HCNd.js +10 -0
  27. package/dist/assets/OnboardingScreen-DMIxwYK5.js +1 -0
  28. package/dist/assets/{PatchDiff-6yh1csza.js → PatchDiff-CUcCg1jk.js} +1 -1
  29. package/dist/assets/PierreDiffViewer-CnnR-dnD.js +228 -0
  30. package/dist/assets/PlanView-RCBadcY7.js +5 -0
  31. package/dist/assets/SettingsView-CjBjZFRO.js +30 -0
  32. package/dist/assets/SettingsWindow-DyM7QuhI.js +1 -0
  33. package/dist/assets/SubmoduleDiffSummary-BWIfgMED.js +1 -0
  34. package/dist/assets/ToolOutputDialog-Q0P3FuAi.js +15 -0
  35. package/dist/assets/ToolPartDiffPreview-p7qR9FpU.js +8 -0
  36. package/dist/assets/{VirtualizedFileDiff-BTJVRkyo.js → VirtualizedFileDiff-CFpjL8Ns.js} +3 -3
  37. package/dist/assets/{Virtualizer-xCEYz34p.js → Virtualizer-C0IsclZn.js} +1 -1
  38. package/dist/assets/WalkthroughView-DI31zD02.js +4 -0
  39. package/dist/assets/appThemeRegistry-B9gkFq9i.js +1 -0
  40. package/dist/assets/{computeVirtualFileMetrics-CmPe29DM.js → computeVirtualFileMetrics-C2WM6JC2.js} +2 -2
  41. package/dist/assets/de-YKfk5MmC.js +14 -0
  42. package/dist/assets/es-_gwT0kr-.js +20 -0
  43. package/dist/assets/fileTreeStatus-D438yi8M.js +1 -0
  44. package/dist/assets/fr-DqzvXmIm.js +18 -0
  45. package/dist/assets/index-BLaHKdtx.js +1 -0
  46. package/dist/assets/index-BkMXRrVQ.css +1 -0
  47. package/dist/assets/index-DxNa7xYB.js +65 -0
  48. package/dist/assets/initializeSharedPreferences-Bck5EAWP.js +1 -0
  49. package/dist/assets/{isDiffPlainText-CQmDCnEv.js → isDiffPlainText-Dgbw8VKk.js} +1 -1
  50. package/dist/assets/{isFilePlainText-CYBUzW4I.js → isFilePlainText-_ZS5UYRj.js} +1 -1
  51. package/dist/assets/ja-B-GFYhQE.js +20 -0
  52. package/dist/assets/ko-mzksfqDC.js +20 -0
  53. package/dist/assets/main-C5Xx8dn_.js +2 -0
  54. package/dist/assets/main-CfBSQM1H.js +990 -0
  55. package/dist/assets/markdownCore-BmWwW1ll.js +7 -0
  56. package/dist/assets/miniChat-mnKwy4vQ.js +2 -0
  57. package/dist/assets/mobile-DeEQd0wZ.js +2 -0
  58. package/dist/assets/nl-CWQr6NSq.js +20 -0
  59. package/dist/assets/{parseDiffFromFile-Cy0PNxun.js → parseDiffFromFile-BUgn-T9p.js} +2 -2
  60. package/dist/assets/patchFileDiff-Dt8LhgBh.js +13 -0
  61. package/dist/assets/pl-UmRcDUX2.js +20 -0
  62. package/dist/assets/pt-BR-BSLybvVF.js +20 -0
  63. package/dist/assets/relocateRestoredSession-B5E9xb3T.js +1 -0
  64. package/dist/assets/renderElectronMiniChatApp-C0a9A5_J.js +2 -0
  65. package/dist/assets/renderMobileApp-BNncg3Pk.js +25 -0
  66. package/dist/assets/runtimeEndpointReset-CZ5aafpl.js +62 -0
  67. package/dist/assets/{selectionMarkdown-TkYk5h9l.js → selectionMarkdown-CUN3wUld.js} +9 -9
  68. package/dist/assets/{sessionOwnership-Bs1xbRfB.js → sessionOwnership-BSuxCEhb.js} +1 -1
  69. package/dist/assets/shikiHighlight-DHDx-6eM.js +1 -0
  70. package/dist/assets/space-apply-DM_2pRW7.js +1 -0
  71. package/dist/assets/space-close-Bpomt8z0.js +1 -0
  72. package/dist/assets/{surface-B6n7wJtt.js → surface-C-jQaGnL.js} +1 -1
  73. package/dist/assets/{textMateThemeFromAppTheme-CDlKffWz.js → textMateThemeFromAppTheme-DlsTphbY.js} +1 -1
  74. package/dist/assets/tr-gaTrX_HK.js +11 -0
  75. package/dist/assets/uk-CnJF6ykl.js +20 -0
  76. package/dist/assets/{useAppFontEffects-DCqCVGlT.js → useAppFontEffects-DGlvOqP_.js} +194 -233
  77. package/dist/assets/useInlineCommentController-kMN4Wg_D.js +2 -0
  78. package/dist/assets/{useRootScrollLock-BowwS8kq.js → useRootScrollLock-DMPpvpNP.js} +2 -2
  79. package/dist/assets/{vendor-base-ui-DX96QhTP.js → vendor-base-ui-Det7U81h.js} +1 -1
  80. package/dist/assets/{vendor-codemirror-autocomplete-DW1FQz9_.js → vendor-codemirror-autocomplete-BSHQHbKu.js} +1 -1
  81. package/dist/assets/{vendor-codemirror-commands-DpmhalwN.js → vendor-codemirror-commands-C4G_dPWe.js} +1 -1
  82. package/dist/assets/{vendor-codemirror-lang-angular-BqExbN1M.js → vendor-codemirror-lang-angular-B6LbBGHB.js} +1 -1
  83. package/dist/assets/{vendor-codemirror-lang-cpp-DZA36HfS.js → vendor-codemirror-lang-cpp-BWWXBx7W.js} +1 -1
  84. package/dist/assets/{vendor-codemirror-lang-css-B07blnnZ.js → vendor-codemirror-lang-css-Cayfb29q.js} +1 -1
  85. package/dist/assets/{vendor-codemirror-lang-go-BM8K9D1-.js → vendor-codemirror-lang-go-BoKlCNKw.js} +1 -1
  86. package/dist/assets/{vendor-codemirror-lang-html-DaH9adfE.js → vendor-codemirror-lang-html-BQWerHsJ.js} +1 -1
  87. package/dist/assets/{vendor-codemirror-lang-java-CsdC0viz.js → vendor-codemirror-lang-java-DWX7Fq_K.js} +1 -1
  88. package/dist/assets/{vendor-codemirror-lang-javascript-CpGYXGuo.js → vendor-codemirror-lang-javascript-DbpEcZM0.js} +2 -2
  89. package/dist/assets/{vendor-codemirror-lang-jinja-BBeGzXw5.js → vendor-codemirror-lang-jinja-D1JIJRA7.js} +1 -1
  90. package/dist/assets/{vendor-codemirror-lang-json-58T70Hqs.js → vendor-codemirror-lang-json-BzgckzmB.js} +1 -1
  91. package/dist/assets/{vendor-codemirror-lang-less-BI-xCA59.js → vendor-codemirror-lang-less-B9EpDXFi.js} +1 -1
  92. package/dist/assets/{vendor-codemirror-lang-liquid-zEcMa-zy.js → vendor-codemirror-lang-liquid-9U7kRbSf.js} +1 -1
  93. package/dist/assets/{vendor-codemirror-lang-markdown-BLKnlLP4.js → vendor-codemirror-lang-markdown-D2TEBIvZ.js} +1 -1
  94. package/dist/assets/{vendor-codemirror-lang-php-9f35M83-.js → vendor-codemirror-lang-php-BT6sDVzE.js} +1 -1
  95. package/dist/assets/vendor-codemirror-lang-python-jAMtYiA9.js +3 -0
  96. package/dist/assets/{vendor-codemirror-lang-rust-MNDL_jZQ.js → vendor-codemirror-lang-rust-CVO56EiL.js} +1 -1
  97. package/dist/assets/{vendor-codemirror-lang-sass-p9epsSNm.js → vendor-codemirror-lang-sass-DZlOoUWu.js} +1 -1
  98. package/dist/assets/{vendor-codemirror-lang-sql-Bqzh0EXX.js → vendor-codemirror-lang-sql-fZ6t1DPw.js} +1 -1
  99. package/dist/assets/{vendor-codemirror-lang-vue-BnNUWV-p.js → vendor-codemirror-lang-vue-DxbqrLiE.js} +1 -1
  100. package/dist/assets/{vendor-codemirror-lang-wast-C2a-oLmk.js → vendor-codemirror-lang-wast-DyU1Gt-I.js} +1 -1
  101. package/dist/assets/vendor-codemirror-lang-xml-C9a_K5_p.js +1 -0
  102. package/dist/assets/{vendor-codemirror-lang-yaml-C0sjswB0.js → vendor-codemirror-lang-yaml-Cptq8l0e.js} +1 -1
  103. package/dist/assets/{vendor-codemirror-language-ByBn5zIW.js → vendor-codemirror-language-DufXoQvE.js} +2 -2
  104. package/dist/assets/vendor-codemirror-language-data-CtwSWKtX.js +2 -0
  105. package/dist/assets/{vendor-codemirror-search-ib-sHD5W.js → vendor-codemirror-search-CvXM_pZg.js} +1 -1
  106. package/dist/assets/{vendor-codemirror-state-G3UuR-be.js → vendor-codemirror-state-Dek9sMtw.js} +1 -1
  107. package/dist/assets/{vendor-codemirror-view-Bniqp7p5.js → vendor-codemirror-view-1e0FQ6Wn.js} +1 -1
  108. package/dist/assets/vendor-framer-motion-DxDVhOLc.js +9 -0
  109. package/dist/assets/vendor-katex-CuQvj9-L.js +257 -0
  110. package/dist/assets/vendor-katex-DdZNUVNG.css +1 -0
  111. package/dist/assets/{vendor-motion-dom-DcCgeG69.js → vendor-motion-dom-HXPOMxb7.js} +1 -1
  112. package/dist/assets/{vendor-opencode-client-DHzhmLwV.js → vendor-opencode-client-BmFcUkbQ.js} +1 -1
  113. package/dist/assets/{vendor-replit-codemirror-vim-DkzqxN2H.js → vendor-replit-codemirror-vim-BlJRn-cW.js} +2 -2
  114. package/dist/assets/{vendor-tanstack-react-virtual-H_JmXOFV.js → vendor-tanstack-react-virtual-CvuLsk7D.js} +1 -1
  115. package/dist/assets/{vendor-tanstack-virtual-core-Dxc8hSTB.js → vendor-tanstack-virtual-core-DPaNcJcT.js} +1 -1
  116. package/dist/assets/zh-CN-t2vigSB1.js +20 -0
  117. package/dist/assets/zh-TW-jAgqBokB.js +20 -0
  118. package/dist/index.html +6 -6
  119. package/dist/mini-chat.html +6 -6
  120. package/dist/mobile.html +6 -6
  121. package/package.json +8 -5
  122. package/server/index.js +80 -36
  123. package/server/lib/agent-memory/actions.js +1 -6
  124. package/server/lib/agent-memory/routes.js +1 -2
  125. package/server/lib/agent-memory/runtime.js +1 -8
  126. package/server/lib/agent-tool/DOCUMENTATION.md +7 -3
  127. package/server/lib/agent-tool/runtime.js +3 -6
  128. package/server/lib/agent-tool/runtime.test.js +0 -2
  129. package/server/lib/browser-control/DOCUMENTATION.md +6 -0
  130. package/server/lib/changelog/update-notes.js +1 -1
  131. package/server/lib/context-obligatory/runtime.js +1 -2
  132. package/server/lib/dev-tunnel/DOCUMENTATION.md +8 -0
  133. package/server/lib/dev-tunnel/client.js +21 -10
  134. package/server/lib/dev-tunnel/tunnel.test.js +34 -0
  135. package/server/lib/dispatch-results/runtime.js +1 -1
  136. package/server/lib/enterprise-mode.d.ts +1 -0
  137. package/server/lib/enterprise-mode.js +13 -2
  138. package/server/lib/enterprise-mode.test.js +21 -0
  139. package/server/lib/event-stream/DOCUMENTATION.md +2 -2
  140. package/server/lib/event-stream/index.js +1 -3
  141. package/server/lib/event-stream/translate-v2.js +1 -9
  142. package/server/lib/event-stream/translate-v2.test.js +0 -11
  143. package/server/lib/fs/DOCUMENTATION.md +5 -2
  144. package/server/lib/fs/routes.js +62 -15
  145. package/server/lib/fs/routes.test.js +278 -48
  146. package/server/lib/fs/workspace-file-names.js +104 -0
  147. package/server/lib/fs/workspace-file-names.test.js +62 -0
  148. package/server/lib/git/DOCUMENTATION.md +17 -5
  149. package/server/lib/git/contributor-provenance-storage.js +9 -6
  150. package/server/lib/git/contributor-provenance-storage.test.js +29 -0
  151. package/server/lib/git/credential-broker.js +1 -1
  152. package/server/lib/git/credential-resolver.js +1 -4
  153. package/server/lib/git/credentials.js +1 -0
  154. package/server/lib/git/discovery-endpoint.js +1 -1
  155. package/server/lib/git/identity-provisioning.js +2 -1
  156. package/server/lib/git/identity-storage.js +3 -4
  157. package/server/lib/git/lfs-discovery.js +14 -14
  158. package/server/lib/git/lfs-discovery.test.js +16 -6
  159. package/server/lib/git/network-operation-plan.js +50 -62
  160. package/server/lib/git/network-operation-plan.test.js +103 -1
  161. package/server/lib/git/network-operation-registry.js +4 -6
  162. package/server/lib/git/network-operation-storage.js +4 -6
  163. package/server/lib/git/network-operations.js +12 -4
  164. package/server/lib/git/network-operations.test.js +19 -6
  165. package/server/lib/git/redaction.js +2 -1
  166. package/server/lib/git/repository-credential-runtime.js +5 -3
  167. package/server/lib/git/repository-credential-runtime.test.js +22 -0
  168. package/server/lib/git/routes.js +31 -137
  169. package/server/lib/git/routes.test.js +2 -2
  170. package/server/lib/git/service.js +143 -27
  171. package/server/lib/git/service.test.js +215 -1
  172. package/server/lib/git/ssh-credential-storage.js +1 -4
  173. package/server/lib/git/submodule-discovery.js +1 -1
  174. package/server/lib/git/worktree-bootstrap-storage.js +3 -4
  175. package/server/lib/git/worktree-bootstrap-storage.test.js +15 -0
  176. package/server/lib/git/worktree-creation.js +194 -0
  177. package/server/lib/git/worktree-creation.test.js +155 -0
  178. package/server/lib/github/DOCUMENTATION.md +11 -6
  179. package/server/lib/github/auth.js +2 -4
  180. package/server/lib/github/device-flow.js +2 -2
  181. package/server/lib/github/gh-cli-credential.js +1 -0
  182. package/server/lib/github/octokit.js +31 -0
  183. package/server/lib/github/pr-status.js +35 -18
  184. package/server/lib/github/pr-status.test.js +271 -3
  185. package/server/lib/github/reference-search.js +102 -13
  186. package/server/lib/github/reference-search.test.js +48 -7
  187. package/server/lib/github/routes.auth.test.js +18 -0
  188. package/server/lib/github/routes.js +360 -6
  189. package/server/lib/github/routes.mutations.test.js +239 -0
  190. package/server/lib/github/routes.test.js +134 -2
  191. package/server/lib/gitlab/DOCUMENTATION.md +5 -1
  192. package/server/lib/gitlab/auth-storage.test.js +1 -0
  193. package/server/lib/gitlab/mappers.js +30 -1
  194. package/server/lib/gitlab/resources.js +230 -6
  195. package/server/lib/gitlab/resources.test.js +179 -0
  196. package/server/lib/gitlab/routes.js +200 -6
  197. package/server/lib/gitlab/routes.test.js +186 -2
  198. package/server/lib/gitlab/validation.js +1 -5
  199. package/server/lib/guests/DOCUMENTATION.md +1 -1
  200. package/server/lib/guests/oauth.js +2 -2
  201. package/server/lib/guests/service.js +1 -1
  202. package/server/lib/guests/sockets.js +1 -1
  203. package/server/lib/injected-env.d.ts +8 -0
  204. package/server/lib/injected-env.js +50 -0
  205. package/server/lib/injected-env.test.js +56 -0
  206. package/server/lib/linear/DOCUMENTATION.md +5 -6
  207. package/server/lib/linear/issues.js +62 -4
  208. package/server/lib/linear/issues.test.js +68 -0
  209. package/server/lib/linear/oauth.js +5 -5
  210. package/server/lib/linear/parse.js +2 -9
  211. package/server/lib/message-queue/DOCUMENTATION.md +23 -1
  212. package/server/lib/message-queue/runtime.js +85 -12
  213. package/server/lib/message-queue/runtime.test.js +172 -2
  214. package/server/lib/notifications/apns-runtime.js +5 -6
  215. package/server/lib/notifications/apns-runtime.test.js +20 -4
  216. package/server/lib/openchamber-control/DOCUMENTATION.md +19 -1
  217. package/server/lib/openchamber-control/actions.js +2 -1
  218. package/server/lib/openchamber-control/service.js +119 -20
  219. package/server/lib/openchamber-control/service.test.js +166 -0
  220. package/server/lib/openchamber-sessions/archive-store.js +1 -6
  221. package/server/lib/openchamber-sessions/routes.js +47 -12
  222. package/server/lib/openchamber-sessions/routes.test.js +65 -2
  223. package/server/lib/openchamber-sessions/session-metadata-store.js +1 -6
  224. package/server/lib/opencode/DOCUMENTATION.md +22 -8
  225. package/server/lib/opencode/agents.js +62 -0
  226. package/server/lib/opencode/auth-state-runtime.js +3 -1
  227. package/server/lib/opencode/auth.d.ts +1 -1
  228. package/server/lib/opencode/auth.js +8 -0
  229. package/server/lib/opencode/auth.test.js +31 -0
  230. package/server/lib/opencode/config-entity-routes.js +16 -0
  231. package/server/lib/opencode/config-entity-v2.test.js +16 -1
  232. package/server/lib/opencode/config-paths.test.js +2 -2
  233. package/server/lib/opencode/config-v2.js +2 -4
  234. package/server/lib/opencode/feature-routes-runtime.js +89 -49
  235. package/server/lib/opencode/lifecycle.js +41 -21
  236. package/server/lib/opencode/lifecycle.test.js +50 -2
  237. package/server/lib/opencode/model-discovery.js +1 -1
  238. package/server/lib/opencode/model-discovery.test.js +17 -0
  239. package/server/lib/opencode/models-metadata.js +0 -2
  240. package/server/lib/opencode/openchamber-routes.test.js +3 -3
  241. package/server/lib/opencode/plugins.js +6 -9
  242. package/server/lib/opencode/provider-source-route.test.js +101 -3
  243. package/server/lib/opencode/providers-disabled.test.js +59 -0
  244. package/server/lib/opencode/providers.js +34 -0
  245. package/server/lib/opencode/proxy.js +11 -3
  246. package/server/lib/opencode/routes.js +40 -2
  247. package/server/lib/opencode/settings-helpers.js +27 -1
  248. package/server/lib/opencode/settings-helpers.test.js +19 -7
  249. package/server/lib/opencode/settings-registry.json +27 -16
  250. package/server/lib/opencode/settings-runtime.test.js +18 -0
  251. package/server/lib/opencode/shared-service-runtime.js +23 -104
  252. package/server/lib/opencode/shared.js +77 -46
  253. package/server/lib/opencode/shared.test.js +12 -12
  254. package/server/lib/opencode/shutdown-runtime.test.js +2 -3
  255. package/server/lib/opencode/skill-routes.js +123 -38
  256. package/server/lib/opencode/skill-routes.test.js +201 -0
  257. package/server/lib/opencode/skills.js +81 -34
  258. package/server/lib/opencode/skills.test.js +176 -21
  259. package/server/lib/opencode/startup-pipeline-runtime.js +6 -3
  260. package/server/lib/opencode/startup-pipeline-runtime.test.js +2 -1
  261. package/server/lib/opencode/watcher.test.js +0 -54
  262. package/server/lib/outbound-proxy.js +52 -0
  263. package/server/lib/outbound-proxy.test.js +55 -0
  264. package/server/lib/package-manager.js +23 -17
  265. package/server/lib/package-manager.test.js +5 -18
  266. package/server/lib/permission-auto-accept/DOCUMENTATION.md +1 -1
  267. package/server/lib/permission-auto-accept/runtime.js +19 -47
  268. package/server/lib/permission-auto-accept/runtime.test.js +2 -2
  269. package/server/lib/project-context/DOCUMENTATION.md +7 -1
  270. package/server/lib/project-context/routes.js +1 -2
  271. package/server/lib/project-context/runtime.js +12 -14
  272. package/server/lib/project-context/runtime.test.js +78 -2
  273. package/server/lib/projects/project-config.js +30 -15
  274. package/server/lib/projects/project-config.test.js +30 -0
  275. package/server/lib/projects/project-setup.js +1 -2
  276. package/server/lib/projects/routes.js +1 -2
  277. package/server/lib/quota/DOCUMENTATION.md +35 -7
  278. package/server/lib/quota/index.js +1 -0
  279. package/server/lib/quota/providers/claude/index.js +1 -1
  280. package/server/lib/quota/providers/cline-pass.js +1 -1
  281. package/server/lib/quota/providers/exe-dev.js +1 -1
  282. package/server/lib/quota/providers/google/index.js +1 -1
  283. package/server/lib/quota/providers/hyper.js +1 -1
  284. package/server/lib/quota/providers/kimi.js +116 -36
  285. package/server/lib/quota/providers/kimi.test.js +128 -3
  286. package/server/lib/quota/providers/opencode-go.js +144 -21
  287. package/server/lib/quota/providers/opencode-go.test.js +229 -6
  288. package/server/lib/quota/providers/openrouter.js +1 -1
  289. package/server/lib/quota/providers/zenmux.js +1 -1
  290. package/server/lib/quota/utils/auth.js +1 -0
  291. package/server/lib/quota/utils/transformers.js +2 -6
  292. package/server/lib/relay/DOCUMENTATION.md +18 -3
  293. package/server/lib/relay/e2ee.js +7 -7
  294. package/server/lib/relay/host-client.js +84 -13
  295. package/server/lib/relay/host-client.test.js +201 -1
  296. package/server/lib/relay/identity.js +6 -29
  297. package/server/lib/relay/identity.test.js +18 -28
  298. package/server/lib/relay/key-store.js +141 -0
  299. package/server/lib/relay/key-store.test.js +138 -0
  300. package/server/lib/relay/service.js +8 -8
  301. package/server/lib/relay/service.test.js +10 -5
  302. package/server/lib/relay/signing-key.js +13 -36
  303. package/server/lib/relay/tunnel-codec.js +10 -10
  304. package/server/lib/route-module-exports.test.js +22 -0
  305. package/server/lib/routing/DOCUMENTATION.md +23 -3
  306. package/server/lib/routing/cache.js +87 -0
  307. package/server/lib/routing/defaults.js +11 -0
  308. package/server/lib/routing/history.js +12 -4
  309. package/server/lib/routing/runtime.js +39 -3
  310. package/server/lib/routing/runtime.test.js +81 -2
  311. package/server/lib/routing/store.js +12 -1
  312. package/server/lib/routing/store.test.js +13 -0
  313. package/server/lib/scheduled-tasks/DOCUMENTATION.md +59 -1
  314. package/server/lib/scheduled-tasks/existing-session.js +119 -0
  315. package/server/lib/scheduled-tasks/existing-session.test.js +350 -0
  316. package/server/lib/scheduled-tasks/loops.js +1 -8
  317. package/server/lib/scheduled-tasks/routes.js +8 -8
  318. package/server/lib/scheduled-tasks/runtime.js +103 -13
  319. package/server/lib/scheduled-tasks/runtime.test.js +48 -0
  320. package/server/lib/scheduled-tasks/service.js +33 -11
  321. package/server/lib/scheduled-tasks/service.test.js +128 -2
  322. package/server/lib/scheduled-tasks/session-defaults.js +27 -0
  323. package/server/lib/scheduled-tasks/session-defaults.test.js +19 -0
  324. package/server/lib/session-assist/context.js +1 -0
  325. package/server/lib/session-folders/routes.js +2 -2
  326. package/server/lib/session-goal/objectives.js +1 -1
  327. package/server/lib/session-knowledge/routes.js +2 -2
  328. package/server/lib/session-knowledge/runtime.js +1 -2
  329. package/server/lib/session-work/DOCUMENTATION.md +39 -14
  330. package/server/lib/session-work/questions.js +31 -2
  331. package/server/lib/session-work/runtime.js +22 -13
  332. package/server/lib/session-work/runtime.test.js +43 -26
  333. package/server/lib/session-work/state.js +10 -4
  334. package/server/lib/session-work/state.test.js +8 -1
  335. package/server/lib/shared/DOCUMENTATION.md +89 -0
  336. package/server/lib/shared/guards.js +53 -0
  337. package/server/lib/shared/guards.test.js +112 -0
  338. package/server/lib/skills-catalog/DOCUMENTATION.md +10 -8
  339. package/server/lib/skills-catalog/cache.js +3 -2
  340. package/server/lib/skills-catalog/curated-sources.js +5 -1
  341. package/server/lib/skills-catalog/git.js +107 -20
  342. package/server/lib/skills-catalog/git.test.js +78 -0
  343. package/server/lib/skills-catalog/install.js +21 -12
  344. package/server/lib/skills-catalog/scan.js +25 -15
  345. package/server/lib/skills-catalog/source.js +26 -1
  346. package/server/lib/skills-catalog/source.test.js +27 -0
  347. package/server/lib/source-control/DOCUMENTATION.md +2 -1
  348. package/server/lib/source-control/audit-storage.js +7 -6
  349. package/server/lib/source-control/audit-storage.test.js +11 -0
  350. package/server/lib/source-control/binding-service.js +2 -5
  351. package/server/lib/source-control/change-request-worktree.js +90 -0
  352. package/server/lib/source-control/change-request-worktree.test.js +121 -0
  353. package/server/lib/source-control/mutation-executor.js +1 -1
  354. package/server/lib/source-control/mutation-storage.js +11 -5
  355. package/server/lib/source-control/provider-instance.js +1 -2
  356. package/server/lib/source-control/repository-identity.js +1 -1
  357. package/server/lib/source-control/routes.js +14 -2
  358. package/server/lib/spaces/DOCUMENTATION.md +42 -17
  359. package/server/lib/spaces/command-stream.test.js +15 -0
  360. package/server/lib/spaces/dispatcher.js +7 -0
  361. package/server/lib/spaces/dispatcher.test.js +17 -0
  362. package/server/lib/spaces/hardening.js +2 -2
  363. package/server/lib/spaces/hardening.test.js +1 -1
  364. package/server/lib/spaces/host.js +2 -0
  365. package/server/lib/spaces/journey.js +66 -5
  366. package/server/lib/spaces/journey.test.js +95 -4
  367. package/server/lib/spaces/layout.js +1 -1
  368. package/server/lib/spaces/places/docker-disk.js +38 -14
  369. package/server/lib/spaces/places/docker-disk.test.js +80 -1
  370. package/server/lib/spaces/places/docker.js +35 -11
  371. package/server/lib/spaces/places/fake-docker.js +22 -3
  372. package/server/lib/spaces/places/memory-place.js +3 -0
  373. package/server/lib/spaces/places/packed.docker.live.test.js +1 -1
  374. package/server/lib/spaces/routes.js +5 -0
  375. package/server/lib/spaces/routes.test.js +4 -2
  376. package/server/lib/spaces/run-command.js +9 -2
  377. package/server/lib/spaces/tools.js +4 -5
  378. package/server/lib/spaces/tools.test.js +2 -2
  379. package/server/lib/terminal/runtime.js +18 -6
  380. package/server/lib/terminal/runtime.test.js +66 -0
  381. package/server/lib/tts/index.js +3 -0
  382. package/server/lib/tts/service.js +4 -2
  383. package/server/lib/walkthrough/DOCUMENTATION.md +3 -1
  384. package/server/lib/walkthrough/index.js +2 -3
  385. package/server/lib/walkthrough/pr-source.test.js +22 -0
  386. package/server/lib/walkthrough/sources.js +6 -1
  387. package/server/opencode-proxy.test.js +127 -0
  388. package/bin/lib/commands-startup.test.js +0 -15
  389. package/dist/assets/CodeMirrorCommentWidgets-CVDSjcrP.js +0 -1
  390. package/dist/assets/DiffView-Ct7vL3fD.js +0 -4
  391. package/dist/assets/FilesView-DT0uEYgT.js +0 -17
  392. package/dist/assets/GuestSurfacePane-DVvk1VPu.js +0 -1
  393. package/dist/assets/LinearIssuesView-C10MwA9h.js +0 -1
  394. package/dist/assets/MarkdownImageGallery-BbN_K_3d.js +0 -1
  395. package/dist/assets/MarkdownRendererImpl-BLt2Tkk9.js +0 -10
  396. package/dist/assets/OnboardingScreen-Bgp5A3WB.js +0 -1
  397. package/dist/assets/PierreDiffViewer-CWJLj-S-.js +0 -210
  398. package/dist/assets/PlanView-DsRWnsOI.js +0 -5
  399. package/dist/assets/PublishDialog-CCeKloRu.js +0 -2
  400. package/dist/assets/SettingsView-BuPPsNPp.js +0 -30
  401. package/dist/assets/SettingsWindow-CM5fL8Ot.js +0 -1
  402. package/dist/assets/SubmoduleDiffSummary-CfsMcfiQ.js +0 -1
  403. package/dist/assets/ToolOutputDialog-mZrfSofz.js +0 -15
  404. package/dist/assets/ToolPartDiffPreview-BUGCRYEL.js +0 -8
  405. package/dist/assets/WalkthroughView-ClBo7PPd.js +0 -4
  406. package/dist/assets/appThemeRegistry-CxzMxR-H.js +0 -1
  407. package/dist/assets/de-CBla4hjd.js +0 -14
  408. package/dist/assets/es-BmnZtxTD.js +0 -20
  409. package/dist/assets/fileTreeStatus-BSHFIGEg.js +0 -1
  410. package/dist/assets/fr-OVKIBUZO.js +0 -18
  411. package/dist/assets/index-CKCFKmjF.js +0 -1
  412. package/dist/assets/index-Dbx4V4GN.css +0 -1
  413. package/dist/assets/index-vQ1Pg1cr.js +0 -61
  414. package/dist/assets/ja-6v-yr-gE.js +0 -20
  415. package/dist/assets/ko-I_ZLvgQ9.js +0 -20
  416. package/dist/assets/main-CmO4dRtR.js +0 -2
  417. package/dist/assets/main-b9vqhlYM.js +0 -999
  418. package/dist/assets/markdownCore-BPEZRQfP.js +0 -7
  419. package/dist/assets/miniChat-B5Oi7B-6.js +0 -2
  420. package/dist/assets/mobile-CVtShif9.js +0 -2
  421. package/dist/assets/nl-DcvIJf46.js +0 -20
  422. package/dist/assets/patchFileDiff-CW3wCH1O.js +0 -13
  423. package/dist/assets/pl-JxCUl0sd.js +0 -20
  424. package/dist/assets/pt-BR-L89mjja2.js +0 -20
  425. package/dist/assets/relocateRestoredSession-DYh_9oBa.js +0 -1
  426. package/dist/assets/renderElectronMiniChatApp-BD-1JHSg.js +0 -2
  427. package/dist/assets/renderMobileApp-7s5InXt1.js +0 -25
  428. package/dist/assets/runtimeEndpointReset-DqLJC6L1.js +0 -53
  429. package/dist/assets/shikiHighlight-CXgZnvHh.js +0 -1
  430. package/dist/assets/space-apply-1trGVIl8.js +0 -1
  431. package/dist/assets/space-close-y7j-NuBv.js +0 -1
  432. package/dist/assets/tr-ka5J1flt.js +0 -11
  433. package/dist/assets/uk-PyGe6Q_u.js +0 -20
  434. package/dist/assets/useInlineCommentController-CrjSGCpi.js +0 -2
  435. package/dist/assets/vendor-codemirror-lang-python-B7ClrMIT.js +0 -3
  436. package/dist/assets/vendor-codemirror-lang-xml-C8tGnS64.js +0 -1
  437. package/dist/assets/vendor-codemirror-language-data-q2bKO1EM.js +0 -2
  438. package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +0 -1
  439. package/dist/assets/vendor-katex-CHD9BdDO.js +0 -257
  440. package/dist/assets/vendor-katex-Cp579UoJ.css +0 -1
  441. package/dist/assets/zh-CN-B0P2tybJ.js +0 -20
  442. package/dist/assets/zh-TW-Bs9C5cN8.js +0 -20
@@ -324,6 +324,20 @@ export function registerGitHubRoutes(app, options = {}) {
324
324
  oauthFlowRegistry = createOAuthFlowRegistry();
325
325
  return oauthFlowRegistry;
326
326
  };
327
+ // A worktree checked out from a contributor's fork PR deliberately has no
328
+ // upstream, so the branch alone looks like it came from the primary remote.
329
+ // Its provenance names the fork remote the branch really came from. Without
330
+ // provenance (or when it cannot be read) status resolves as for any branch.
331
+ const readContributorSourceRemote = async (directory, branch) => {
332
+ if (!(options.readContributorProvenance instanceof Function)) return null;
333
+ try {
334
+ const { provenance } = await options.readContributorProvenance(directory);
335
+ return provenance?.kind === 'contributor-fork' && provenance.provider === 'github'
336
+ && provenance.sourceRef === `refs/heads/${branch}` ? provenance.remoteName : null;
337
+ } catch {
338
+ return null;
339
+ }
340
+ };
327
341
  const sendOAuthFlowError = (res, error) => {
328
342
  if (error?.code === 'SOURCE_CONTROL_OAUTH_FLOW_BUSY') {
329
343
  return res.status(409).json({ error: 'OAuth flow is busy', code: error.code });
@@ -441,6 +455,25 @@ export function registerGitHubRoutes(app, options = {}) {
441
455
  error.status = status;
442
456
  return error;
443
457
  };
458
+ // GitHub's own limit for a comment body.
459
+ const COMMENT_MAX_LENGTH = 65536;
460
+ const commentText = (value, required) => {
461
+ if (value === undefined && !required) return undefined;
462
+ if (!isProviderString(value) || !value.trim()) throw canonicalMutationError('body is required');
463
+ if (value.length > COMMENT_MAX_LENGTH) throw canonicalMutationError('body is too long');
464
+ return value;
465
+ };
466
+ const ISSUE_KINDS = new Set(['issue-comment', 'issue-state', 'issue-labels']);
467
+ // Labels or reviewers as one whole set, so a repeat asks for the same result.
468
+ const nameList = (value, name, limit) => {
469
+ if (!Array.isArray(value) || value.length > limit
470
+ || value.some((entry) => !isProviderString(entry) || !entry.trim() || entry.length > 256)) {
471
+ throw canonicalMutationError(`${name} are invalid`);
472
+ }
473
+ return [...new Set(value)].sort();
474
+ };
475
+ const sameNames = (left, right) => left.length === right.length && [...left].sort().every((entry, index) => entry === right[index]);
476
+ const mutationRecordKind = (kind) => (ISSUE_KINDS.has(kind) ? kind : `change-request-${kind}`);
444
477
  const requiredMutationText = (value, name) => {
445
478
  if (!isProviderString(value) || !value.trim()) throw canonicalMutationError(`${name} is required`);
446
479
  return value.trim();
@@ -517,7 +550,28 @@ export function registerGitHubRoutes(app, options = {}) {
517
550
  const { number, ...result } = receipt.result;
518
551
  return { ...receipt, target: { ...receipt.target, number }, result };
519
552
  };
520
- const reconcileExistingMutation = async (octokit, kind, target) => {
553
+ const reconcileExistingMutation = async (octokit, kind, target, operation) => {
554
+ // A comment or a review leaves nothing that tells a lost write from one
555
+ // never sent short of guessing from its text, so the outcome stays unknown.
556
+ if (kind === 'comment' || kind === 'review' || kind === 'issue-comment') return { state: 'outcome-unknown' };
557
+ if (kind === 'labels' || kind === 'issue-labels') {
558
+ const current = await octokit.rest.issues.listLabelsOnIssue({
559
+ owner: target.project.owner, repo: target.project.name, issue_number: target.number, per_page: 100,
560
+ });
561
+ const names = requireProviderArray(current?.data, 'GitHub returned an invalid label list').map((label) => label?.name);
562
+ // Another edit since may have changed them either way: only a match is certain.
563
+ return names.every(isProviderString) && sameNames(names, operation.labels) ? { state: 'succeeded', result: {} } : { state: 'outcome-unknown' };
564
+ }
565
+ if (kind === 'reviewers') {
566
+ const current = await requestedReviewers(octokit, target.project, target.number);
567
+ return sameNames(current, operation.reviewers) ? { state: 'succeeded', result: {} } : { state: 'outcome-unknown' };
568
+ }
569
+ if (kind === 'issue-state') {
570
+ const current = await octokit.rest.issues.get({ owner: target.project.owner, repo: target.project.name, issue_number: target.number });
571
+ const state = current?.data?.state;
572
+ if (state !== 'open' && state !== 'closed') return { state: 'outcome-unknown' };
573
+ return state === operation.state ? { state: 'succeeded', result: { state } } : definiteFailure('SOURCE_CONTROL_MUTATION_NOT_APPLIED');
574
+ }
521
575
  const current = await octokit.rest.pulls.get({
522
576
  owner: target.project.owner,
523
577
  repo: target.project.name,
@@ -531,6 +585,10 @@ export function registerGitHubRoutes(app, options = {}) {
531
585
  if (currentPr.state === 'open' && currentPr.merged === false) {
532
586
  return definiteFailure('SOURCE_CONTROL_MUTATION_NOT_APPLIED');
533
587
  }
588
+ } else if (kind === 'state') {
589
+ // A merged pull request reads `closed` too, and can no longer be reopened.
590
+ if (currentPr.state === operation.state) return { state: 'succeeded', result: { state: currentPr.state } };
591
+ if (currentPr.state === 'open' || currentPr.state === 'closed') return definiteFailure('SOURCE_CONTROL_MUTATION_NOT_APPLIED');
534
592
  } else if (kind === 'ready') {
535
593
  if (currentPr.draft === false) return { state: 'succeeded', result: { ready: true } };
536
594
  if (currentPr.draft === true && currentPr.state === 'open') {
@@ -548,6 +606,73 @@ export function registerGitHubRoutes(app, options = {}) {
548
606
  }
549
607
  return 'outcome-unknown';
550
608
  };
609
+ // GitHub's own reason for refusing a write (a locked conversation, a review
610
+ // of your own pull request) is what the user needs to read; the generic
611
+ // mutation error would hide it.
612
+ const providerRejection = (error) => {
613
+ const status = error?.response?.status ?? error?.status;
614
+ if (![403, 404, 422].includes(status)) return error;
615
+ const data = error?.response?.data;
616
+ const first = Array.isArray(data?.errors) ? data.errors[0] : undefined;
617
+ const detail = [first, first?.message, data?.message].find((value) => isProviderString(value) && value.trim());
618
+ return canonicalMutationError(detail ?? 'GitHub refused the change', 'SOURCE_CONTROL_MUTATION_REJECTED', status);
619
+ };
620
+ const postComment = async (octokit, project, number, body) => {
621
+ let response;
622
+ try {
623
+ response = await octokit.rest.issues.createComment({ owner: project.owner, repo: project.name, issue_number: number, body });
624
+ } catch (error) {
625
+ throw providerRejection(error);
626
+ }
627
+ if (!isPlainObject(response?.data) || !Number.isSafeInteger(response.data.id)) {
628
+ throw invalidProviderPayload('GitHub returned an invalid comment');
629
+ }
630
+ return {};
631
+ };
632
+ const setLabels = async (octokit, project, number, labels) => {
633
+ let response;
634
+ try {
635
+ response = await octokit.rest.issues.setLabels({ owner: project.owner, repo: project.name, issue_number: number, labels });
636
+ } catch (error) {
637
+ throw providerRejection(error);
638
+ }
639
+ requireProviderArray(response?.data, 'GitHub returned an invalid label list');
640
+ return {};
641
+ };
642
+ const requestedReviewers = async (octokit, project, number) => {
643
+ const response = await octokit.rest.pulls.listRequestedReviewers({ owner: project.owner, repo: project.name, pull_number: number });
644
+ const users = response?.data?.users;
645
+ if (!Array.isArray(users) || users.some((user) => !isProviderString(user?.login))) {
646
+ throw invalidProviderPayload('GitHub returned an invalid reviewer list');
647
+ }
648
+ return users.map((user) => user.login);
649
+ };
650
+ // GitHub adds and removes review requests separately. Asking again for the
651
+ // same set is safe, so a failure between the two is fixed by trying again.
652
+ const setReviewers = async (octokit, project, number, reviewers) => {
653
+ const current = await requestedReviewers(octokit, project, number);
654
+ const add = reviewers.filter((login) => !current.includes(login));
655
+ const remove = current.filter((login) => !reviewers.includes(login));
656
+ const pull = { owner: project.owner, repo: project.name, pull_number: number };
657
+ try {
658
+ if (add.length) await octokit.rest.pulls.requestReviewers({ ...pull, reviewers: add });
659
+ if (remove.length) await octokit.rest.pulls.removeRequestedReviewers({ ...pull, reviewers: remove });
660
+ } catch (error) {
661
+ throw providerRejection(error);
662
+ }
663
+ return {};
664
+ };
665
+ // Closing or reopening an issue or a pull request; GitHub refuses to reopen a merged one.
666
+ const setState = async (update, state) => {
667
+ let response;
668
+ try {
669
+ response = await update();
670
+ } catch (error) {
671
+ throw providerRejection(error);
672
+ }
673
+ if (response?.data?.state !== state) throw invalidProviderPayload('GitHub returned an unexpected state');
674
+ return { state };
675
+ };
551
676
  const sendCanonicalMutationError = async (res, error) => {
552
677
  if (error?.status === 401) await invalidateRequestAccount(error);
553
678
  const status = Number.isSafeInteger(error?.status) && error.status >= 400 && error.status <= 599
@@ -617,12 +742,45 @@ export function registerGitHubRoutes(app, options = {}) {
617
742
  return { method, digest: (target) => digestMutationInput({ kind, actor, credential, target, method }) };
618
743
  }
619
744
  if (kind === 'ready') return { digest: (target) => digestMutationInput({ kind, actor, credential, target }) };
745
+ if (kind === 'labels' || kind === 'issue-labels') {
746
+ const labels = nameList(req.body?.labels, 'labels', 100);
747
+ return { labels, digest: (target) => digestMutationInput({ kind, actor, credential, target, labels }) };
748
+ }
749
+ if (kind === 'reviewers') {
750
+ const reviewers = nameList(req.body?.reviewers, 'reviewers', 50);
751
+ return { reviewers, digest: (target) => digestMutationInput({ kind, actor, credential, target, reviewers }) };
752
+ }
753
+ if (kind === 'state' || kind === 'issue-state') {
754
+ const state = req.body?.state;
755
+ if (state !== 'open' && state !== 'closed') throw canonicalMutationError('state is invalid');
756
+ return { state, digest: (target) => digestMutationInput({ kind, actor, credential, target, state }) };
757
+ }
758
+ if (kind === 'comment' || kind === 'issue-comment') {
759
+ const body = commentText(req.body?.body, true);
760
+ return { body, digest: (target) => digestMutationInput({ kind, actor, credential, target, body }) };
761
+ }
762
+ if (kind === 'review') {
763
+ const verdict = req.body?.verdict;
764
+ if (verdict !== 'approve' && verdict !== 'request-changes') throw canonicalMutationError('review verdict is invalid');
765
+ // A verdict names the commit it is for; without one it would land on whatever was pushed last.
766
+ requiredMutationText(context.target.headSha, 'target headSha');
767
+ // GitHub asks for the reason when changes are requested.
768
+ const body = commentText(req.body?.body, verdict === 'request-changes');
769
+ return {
770
+ verdict, body,
771
+ digest(target) {
772
+ const input = { kind, actor, credential, target, verdict };
773
+ if (body !== undefined) input.body = body;
774
+ return digestMutationInput(input);
775
+ },
776
+ };
777
+ }
620
778
  throw canonicalMutationError('Mutation kind is invalid');
621
779
  };
622
780
  const storedMutationAuthorityMatches = (record, kind, actor, context) => {
623
781
  const target = record?.target;
624
782
  const expected = context.target;
625
- return record?.kind === `change-request-${kind}`
783
+ return record?.kind === mutationRecordKind(kind)
626
784
  && record.actor?.provider === actor.provider
627
785
  && record.actor?.instance === actor.instance
628
786
  && record.actor?.accountId === actor.accountId
@@ -702,7 +860,7 @@ export function registerGitHubRoutes(app, options = {}) {
702
860
  if (!sourceProject) return { state: 'outcome-unknown' };
703
861
  return reconcileCreateMutation(octokit, expectedProject, sourceProject, operation.head, operation.base);
704
862
  }
705
- return reconcileExistingMutation(octokit, kind, target);
863
+ return reconcileExistingMutation(octokit, kind, target, operation);
706
864
  };
707
865
  const execution = await options.mutationExecutor.execute({
708
866
  record: {
@@ -771,6 +929,34 @@ export function registerGitHubRoutes(app, options = {}) {
771
929
  return { number: created.number };
772
930
  };
773
931
  reconcile = () => reconcileCreateMutation(octokit, expectedProject, sourceProject, head, base);
932
+ } else if (ISSUE_KINDS.has(kind)) {
933
+ const expected = context.target;
934
+ if (!Number.isSafeInteger(expected.number) || expected.number < 1
935
+ || expected.head !== undefined || expected.base !== undefined || expected.headSha !== undefined) {
936
+ throw canonicalMutationError('Issue target is invalid');
937
+ }
938
+ const response = await octokit.rest.issues.get({
939
+ owner: expectedProject.owner, repo: expectedProject.name, issue_number: expected.number,
940
+ });
941
+ const issue = response?.data;
942
+ if (!isPlainObject(issue) || issue.number !== expected.number) throw invalidProviderPayload('GitHub returned an invalid issue');
943
+ // GitHub lists pull requests among issues; this write is for issues only.
944
+ if (issue.pull_request) {
945
+ throw canonicalMutationError('GitHub issue is a pull request', 'SOURCE_CONTROL_MUTATION_TARGET_INVALID', 409);
946
+ }
947
+ target = {
948
+ repositoryId: context.repositoryId,
949
+ bindingRevision: context.bindingRevision,
950
+ primaryRemote: context.primaryRemote,
951
+ project: { id: `${expectedProject.owner}/${expectedProject.name}`, owner: expectedProject.owner, name: expectedProject.name },
952
+ number: issue.number,
953
+ };
954
+ if (kind === 'issue-comment') perform = () => postComment(octokit, expectedProject, target.number, operation.body);
955
+ else if (kind === 'issue-labels') perform = () => setLabels(octokit, expectedProject, target.number, operation.labels);
956
+ else perform = () => setState(() => octokit.rest.issues.update({
957
+ owner: expectedProject.owner, repo: expectedProject.name, issue_number: target.number, state: operation.state,
958
+ }), operation.state);
959
+ reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
774
960
  } else {
775
961
  if (!Number.isSafeInteger(context.target.number) || context.target.number < 1) {
776
962
  throw canonicalMutationError('target number is required');
@@ -815,6 +1001,45 @@ export function registerGitHubRoutes(app, options = {}) {
815
1001
  return { merged: data.merged };
816
1002
  };
817
1003
  reconcile = () => reconcileExistingMutation(octokit, kind, target);
1004
+ } else if (kind === 'labels') {
1005
+ perform = () => setLabels(octokit, expectedProject, target.number, operation.labels);
1006
+ reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
1007
+ } else if (kind === 'reviewers') {
1008
+ perform = () => setReviewers(octokit, expectedProject, target.number, operation.reviewers);
1009
+ reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
1010
+ } else if (kind === 'state') {
1011
+ perform = () => setState(() => octokit.rest.pulls.update({
1012
+ owner: expectedProject.owner, repo: expectedProject.name, pull_number: target.number, state: operation.state,
1013
+ }), operation.state);
1014
+ reconcile = () => reconcileExistingMutation(octokit, kind, target, operation);
1015
+ } else if (kind === 'comment') {
1016
+ perform = () => postComment(octokit, expectedProject, target.number, operation.body);
1017
+ reconcile = () => reconcileExistingMutation(octokit, kind, target);
1018
+ } else if (kind === 'review') {
1019
+ const { verdict, body } = operation;
1020
+ perform = async () => {
1021
+ const reviewInput = {
1022
+ owner: expectedProject.owner,
1023
+ repo: expectedProject.name,
1024
+ pull_number: target.number,
1025
+ // The verdict is for the commit the user read.
1026
+ commit_id: target.headSha,
1027
+ event: verdict === 'approve' ? 'APPROVE' : 'REQUEST_CHANGES',
1028
+ };
1029
+ if (body !== undefined) reviewInput.body = body;
1030
+ let response;
1031
+ try {
1032
+ response = await octokit.rest.pulls.createReview(reviewInput);
1033
+ } catch (error) {
1034
+ throw providerRejection(error);
1035
+ }
1036
+ if (response?.data?.state !== (verdict === 'approve' ? 'APPROVED' : 'CHANGES_REQUESTED')) {
1037
+ throw invalidProviderPayload('GitHub returned an invalid review');
1038
+ }
1039
+ // GitHub posts the verdict and its text as one review.
1040
+ return { commented: body !== undefined };
1041
+ };
1042
+ reconcile = () => reconcileExistingMutation(octokit, kind, target);
818
1043
  } else {
819
1044
  const alreadyReady = pr.draft === false;
820
1045
  const nodeId = optionalMutationText(pr.node_id, 'pull request node id', true);
@@ -840,7 +1065,7 @@ export function registerGitHubRoutes(app, options = {}) {
840
1065
  record: {
841
1066
  key: context.idempotencyKey,
842
1067
  inputDigest: operation.digest(target),
843
- kind: `change-request-${kind}`,
1068
+ kind: mutationRecordKind(kind),
844
1069
  actor,
845
1070
  target,
846
1071
  },
@@ -1359,12 +1584,14 @@ export function registerGitHubRoutes(app, options = {}) {
1359
1584
 
1360
1585
  const resolveGitHubPrStatus = options.resolveGitHubPrStatus
1361
1586
  ?? (await import('./pr-status.js')).resolveGitHubPrStatus;
1587
+ const sourceRemoteName = await readContributorSourceRemote(directory, branch);
1362
1588
  const resolvedStatus = await withTimeout(
1363
1589
  resolveGitHubPrStatus({
1364
1590
  octokit,
1365
1591
  directory,
1366
1592
  branch,
1367
1593
  remoteName: remote,
1594
+ sourceRemoteName,
1368
1595
  force,
1369
1596
  }),
1370
1597
  PR_STATUS_RESOLVE_TIMEOUT_MS,
@@ -1537,6 +1764,22 @@ export function registerGitHubRoutes(app, options = {}) {
1537
1764
 
1538
1765
  app.post(canonicalGitHubRoutePath('/pr/ready'), (req, res) => runCanonicalMutation('ready', req, res));
1539
1766
 
1767
+ app.post(canonicalGitHubRoutePath('/pr/comment'), (req, res) => runCanonicalMutation('comment', req, res));
1768
+
1769
+ app.post(canonicalGitHubRoutePath('/pr/review'), (req, res) => runCanonicalMutation('review', req, res));
1770
+
1771
+ app.post(canonicalGitHubRoutePath('/issues/comment'), (req, res) => runCanonicalMutation('issue-comment', req, res));
1772
+
1773
+ app.post(canonicalGitHubRoutePath('/pr/state'), (req, res) => runCanonicalMutation('state', req, res));
1774
+
1775
+ app.post(canonicalGitHubRoutePath('/issues/state'), (req, res) => runCanonicalMutation('issue-state', req, res));
1776
+
1777
+ app.post(canonicalGitHubRoutePath('/pr/labels'), (req, res) => runCanonicalMutation('labels', req, res));
1778
+
1779
+ app.post(canonicalGitHubRoutePath('/issues/labels'), (req, res) => runCanonicalMutation('issue-labels', req, res));
1780
+
1781
+ app.post(canonicalGitHubRoutePath('/pr/reviewers'), (req, res) => runCanonicalMutation('reviewers', req, res));
1782
+
1540
1783
  app.get(canonicalGitHubRoutePath('/repo/upstream'), async (req, res) => {
1541
1784
  try {
1542
1785
  const directory = readQueryString(req, 'directory');
@@ -1701,7 +1944,7 @@ export function registerGitHubRoutes(app, options = {}) {
1701
1944
  // repository network. Failures are errors, never an empty page: the picker
1702
1945
  // keeps what it showed and offers a retry.
1703
1946
  app.get(canonicalGitHubRoutePath('/references'), async (req, res) => {
1704
- const { readReferenceFilter, readReferenceKind, searchGitHubReferences } = await import('./reference-search.js');
1947
+ const { readReferencePeople, readReferenceKind, readReferenceState, searchGitHubReferences } = await import('./reference-search.js');
1705
1948
  const directory = readQueryString(req, 'directory');
1706
1949
  const kind = readReferenceKind(req.query?.kind);
1707
1950
  if (!directory || !kind) {
@@ -1735,7 +1978,8 @@ export function registerGitHubRoutes(app, options = {}) {
1735
1978
  octokit,
1736
1979
  repos,
1737
1980
  kind,
1738
- filter: readReferenceFilter(req.query?.filter),
1981
+ state: readReferenceState(req.query?.state),
1982
+ people: readReferencePeople(req.query?.people),
1739
1983
  text: readQueryString(req, 'query'),
1740
1984
  cursor: readQueryString(req, 'cursor') || null,
1741
1985
  });
@@ -1825,6 +2069,60 @@ export function registerGitHubRoutes(app, options = {}) {
1825
2069
  }
1826
2070
  });
1827
2071
 
2072
+ // The repository's labels and the people who can be asked to review in it,
2073
+ // for the board's pickers. The repository must be in the bound project's
2074
+ // network, as for the detail above; one page of 100 is what the pickers show.
2075
+ const readRepositoryChoices = (suffix, read) => app.get(canonicalGitHubRoutePath(suffix), async (req, res) => {
2076
+ const directory = readQueryString(req, 'directory');
2077
+ const owner = readQueryString(req, 'owner');
2078
+ const repoName = readQueryString(req, 'repo');
2079
+ if (!directory || !owner || !repoName) {
2080
+ return res.status(400).json({ error: 'directory, owner and repo are required' });
2081
+ }
2082
+ try {
2083
+ const trustedContext = await validateReadContext(req, directory);
2084
+ if (!trustedContext) return res.status(501).json({ error: 'Bound source control read is unavailable' });
2085
+ const octokit = (await getOctokitForRead(req, trustedContext))?.octokit;
2086
+ if (!octokit) return res.json({ connected: false });
2087
+ const repo = await resolveRepoForRequest(octokit, directory, { owner, repo: repoName }, trustedContext.primaryRemote, {
2088
+ resolveGitHubRepoFromDirectory: options.resolveGitHubRepoFromDirectory,
2089
+ resolveRepoNetwork: options.resolveRepoNetwork,
2090
+ strictNetworkErrors: true,
2091
+ strictMetadataErrors: true,
2092
+ requireResolvedRepo: true,
2093
+ });
2094
+ if (!repo) return res.status(400).json({ error: 'Repository is not part of this project' });
2095
+ return res.json({ connected: true, items: await read(octokit, repo) });
2096
+ } catch (error) {
2097
+ if (isReadContextError(error)) return res.status(error.status ?? 400).json(readContextErrorBody(error));
2098
+ const accountError = sendExactAccountError(res, error);
2099
+ if (accountError) return accountError;
2100
+ if (error?.status === 401) {
2101
+ await invalidateRequestAccount(error);
2102
+ return res.json({ connected: false });
2103
+ }
2104
+ return res.status(500).json({ error: error.message || 'Failed to read the repository' });
2105
+ }
2106
+ });
2107
+
2108
+ readRepositoryChoices('/references/labels', async (octokit, repo) => {
2109
+ const response = await octokit.rest.issues.listLabelsForRepo({ owner: repo.owner, repo: repo.repo, per_page: 100 });
2110
+ return requireProviderArray(response?.data, 'GitHub returned an invalid label list').flatMap((label) => {
2111
+ if (!isProviderString(label?.name) || !label.name) return [];
2112
+ return [isProviderString(label.color) && label.color ? { name: label.name, color: label.color } : { name: label.name }];
2113
+ });
2114
+ });
2115
+
2116
+ readRepositoryChoices('/references/reviewers', async (octokit, repo) => {
2117
+ const response = await octokit.rest.issues.listAssignees({ owner: repo.owner, repo: repo.repo, per_page: 100 });
2118
+ return requireProviderArray(response?.data, 'GitHub returned an invalid user list').flatMap((user) => {
2119
+ if (!isProviderString(user?.login) || !user.login) return [];
2120
+ return [isProviderString(user.avatar_url) && user.avatar_url
2121
+ ? { id: user.login, login: user.login, avatarUrl: user.avatar_url }
2122
+ : { id: user.login, login: user.login }];
2123
+ });
2124
+ });
2125
+
1828
2126
  // What colours the PRs a picker page shows: checks and mergeability, from
1829
2127
  // the same summaries the sidebar reads. Every PR must be in the bound
1830
2128
  // repository's network. A PR GitHub could not resolve is left out, which
@@ -2889,6 +3187,62 @@ export function registerGitHubRoutes(app, options = {}) {
2889
3187
  });
2890
3188
 
2891
3189
  return Object.freeze({
3190
+ /**
3191
+ * The account GitHub reads use when none is named: the gh login while the
3192
+ * user has switched to it, else OpenChamber's active account. Null when
3193
+ * neither is there.
3194
+ */
3195
+ readCurrentAccountId: async () => {
3196
+ const { getGitHubAuth, githubCliAccountId, isGhCliActive, isGhCliDisabled, createOctokit } = await getGitHubLibraries();
3197
+ if (isGhCliActive() && !isGhCliDisabled()) {
3198
+ const { getGhCliToken } = await import('./gh-cli-credential.js');
3199
+ const token = getGhCliToken();
3200
+ if (token) {
3201
+ // Only a rejected token means "no account"; a network or server
3202
+ // failure must not read as one, or another host's account answers.
3203
+ try {
3204
+ return githubCliAccountId((await createOctokit(token).rest.users.getAuthenticated()).data.id);
3205
+ } catch (error) {
3206
+ if (error?.status === 401) return null;
3207
+ throw error;
3208
+ }
3209
+ }
3210
+ }
3211
+ return (await getGitHubAuth())?.accountId ?? null;
3212
+ },
3213
+ /** A pull request's head on the context's primary remote, by number, for a worktree made from it. */
3214
+ readChangeRequestHead: async ({ context, number }) => {
3215
+ const trusted = await options.validateReadContext(context);
3216
+ const account = await getOctokitForRequest({}, trusted.accountId);
3217
+ const repo = await resolveRepoForRequest(account.octokit, trusted.directory, null, trusted.primaryRemote, {
3218
+ resolveGitHubRepoFromDirectory: options.resolveGitHubRepoFromDirectory,
3219
+ resolveRepoNetwork: options.resolveRepoNetwork,
3220
+ strictNetworkErrors: true,
3221
+ strictMetadataErrors: true,
3222
+ requireResolvedRepo: true,
3223
+ });
3224
+ let response;
3225
+ try {
3226
+ response = await account.octokit.rest.pulls.get({ owner: repo.owner, repo: repo.repo, pull_number: number });
3227
+ } catch (error) {
3228
+ if (error?.status === 404) {
3229
+ throw canonicalMutationError(`Pull request #${number} was not found in ${repo.owner}/${repo.repo}`, 'SOURCE_CONTROL_CHANGE_REQUEST_NOT_FOUND', 404);
3230
+ }
3231
+ throw error;
3232
+ }
3233
+ const payload = requireGitHubPullRequest(response?.data, 'GitHub returned an invalid pull request');
3234
+ if (!payload.head?.repo) {
3235
+ throw canonicalMutationError(`Pull request #${number} has no branch to check out: its fork was deleted`, 'SOURCE_CONTROL_CHANGE_REQUEST_HEAD_GONE', 409);
3236
+ }
3237
+ const headSha = isProviderString(payload.head.sha) ? payload.head.sha.toLowerCase() : '';
3238
+ if (!/^[0-9a-f]{40}$/.test(headSha)) throw invalidProviderPayload('GitHub returned an invalid pull request head');
3239
+ return {
3240
+ project: { id: `${repo.owner}/${repo.repo}`, owner: repo.owner, name: repo.repo },
3241
+ headSha,
3242
+ headBranch: payload.head.ref,
3243
+ headOwner: payload.head.repo.owner.login,
3244
+ };
3245
+ },
2892
3246
  resolveChangeRequestSource: async ({ context, project, number, expectedHeadSha, requestedRemoteName }) => {
2893
3247
  const trusted = await options.validateReadContext(context);
2894
3248
  const account = await getOctokitForRequest({}, trusted.accountId);