@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
@@ -36,6 +36,37 @@ describe('projectCredentialEntries', () => {
36
36
  custom: { type: 'api', key: 'k', metadata: { region: 'eu' } },
37
37
  });
38
38
  });
39
+
40
+ it('keeps only the Console server and organization from OpenCode Console metadata', () => {
41
+ const projected = projectCredentialEntries([
42
+ entry('opencode', true, {
43
+ type: 'oauth',
44
+ methodID: 'console',
45
+ access: 'console-access',
46
+ refresh: 'console-refresh',
47
+ expires: 42,
48
+ metadata: {
49
+ server: 'https://opencode.ai/console',
50
+ orgID: 'org_TESTORG123',
51
+ accountID: 'acc_console',
52
+ email: 'person@example.com',
53
+ orgName: 'Example',
54
+ },
55
+ }),
56
+ ]);
57
+
58
+ expect(projected).toEqual({
59
+ opencode: {
60
+ type: 'oauth',
61
+ access: 'console-access',
62
+ refresh: 'console-refresh',
63
+ expires: 42,
64
+ accountId: 'acc_console',
65
+ server: 'https://opencode.ai/console',
66
+ orgID: 'org_TESTORG123',
67
+ },
68
+ });
69
+ });
39
70
  });
40
71
 
41
72
  describe('projectEnvironmentKeys', () => {
@@ -19,6 +19,7 @@ export const registerConfigEntityRoutes = (app, dependencies) => {
19
19
  getAgentSources,
20
20
  getAgentConfig,
21
21
  getAgentPermissions,
22
+ listDisabledAgents,
22
23
  createAgent,
23
24
  updateAgent,
24
25
  deleteAgent,
@@ -58,6 +59,21 @@ export const registerConfigEntityRoutes = (app, dependencies) => {
58
59
  return next();
59
60
  });
60
61
 
62
+ // Its own path: under /api/config/agents/ it would shadow an agent named
63
+ // "disabled".
64
+ app.get('/api/config/disabled-agents', async (req, res) => {
65
+ try {
66
+ const { directory, error } = await resolveProjectDirectory(req);
67
+ if (!directory) {
68
+ return res.status(400).json({ error });
69
+ }
70
+ res.json({ agents: listDisabledAgents(directory) });
71
+ } catch (error) {
72
+ console.error('Failed to list disabled agents:', error);
73
+ res.status(500).json({ error: 'Failed to list disabled agents' });
74
+ }
75
+ });
76
+
61
77
  app.get('/api/config/agents/:name', async (req, res) => {
62
78
  try {
63
79
  const agentName = req.params.name;
@@ -11,7 +11,7 @@ process.env.XDG_CONFIG_HOME = path.join(root, 'xdg');
11
11
  delete process.env.OPENCODE_CONFIG;
12
12
 
13
13
  const { parseMdFile } = await import('./shared.js');
14
- const { getAgentConfig, getAgentPermissions, updateAgent, createAgent } = await import('./agents.js');
14
+ const { getAgentConfig, getAgentPermissions, updateAgent, createAgent, listDisabledAgents } = await import('./agents.js');
15
15
  const { getCommandConfig, updateCommand } = await import('./commands.js');
16
16
  const { getMcpConfig, listMcpConfigs, updateMcpConfig } = await import('./mcp.js');
17
17
 
@@ -314,4 +314,19 @@ describe('entity modules speak OpenCode 2 shapes', () => {
314
314
  timeout: { catalog: 30000, execution: 30000 },
315
315
  });
316
316
  });
317
+
318
+ it('lists agents switched off in files and config sections, and forgets them once enabled', () => {
319
+ write('.opencode/agents/incident.md', ['---', 'description: Incident response', 'disabled: true', '---', '', 'Handle incidents.'].join('\n'));
320
+ write('.opencode/agents/ops/oncall.md', ['---', 'disable: true', '---', '', 'On call.'].join('\n'));
321
+ write('.opencode/agents/reviewer.md', ['---', 'description: Reviewer', '---', '', 'Review.'].join('\n'));
322
+ write('opencode.json', JSON.stringify({ agents: { plan: { disabled: true }, build: { color: '#112233' } } }));
323
+
324
+ expect(listDisabledAgents(projectDir).map((agent) => agent.name)).toEqual(['incident', 'ops/oncall', 'plan']);
325
+ expect(listDisabledAgents(projectDir)[0]).toMatchObject({ name: 'incident', scope: 'project', description: 'Incident response' });
326
+
327
+ updateAgent('incident', { disabled: null }, projectDir);
328
+ updateAgent('plan', { disabled: null }, projectDir);
329
+ expect(listDisabledAgents(projectDir).map((agent) => agent.name)).toEqual(['ops/oncall']);
330
+ expect(getAgentConfig('incident', projectDir).config.description).toBe('Incident response');
331
+ });
317
332
  });
@@ -61,7 +61,7 @@ describe('OpenCode global config paths', () => {
61
61
  const [, agents, commands, skills, snippets, plugins] = await loadOpenCodeModules();
62
62
  agents.createAgent('global-agent', { description: 'Global', prompt: 'Global prompt' }, projectDir, 'user');
63
63
  commands.createCommand('global-command', { description: 'Global', template: 'Global template' }, projectDir, 'user');
64
- skills.createSkill('global-skill', { description: 'Global', instructions: 'Global instructions' }, projectDir, 'user');
64
+ await skills.createSkill('global-skill', { description: 'Global', instructions: 'Global instructions' }, projectDir, 'user');
65
65
  snippets.createSnippet('global-snippet', { content: 'Global snippet' }, projectDir, 'global');
66
66
  plugins.createPluginEntry({ spec: 'global-plugin', scope: 'user' }, projectDir);
67
67
 
@@ -74,7 +74,7 @@ describe('OpenCode global config paths', () => {
74
74
 
75
75
  agents.createAgent('project-agent', { description: 'Project', prompt: 'Project prompt' }, projectDir, 'project');
76
76
  commands.createCommand('project-command', { description: 'Project', template: 'Project template' }, projectDir, 'project');
77
- skills.createSkill('project-skill', { description: 'Project', instructions: 'Project instructions' }, projectDir, 'project');
77
+ await skills.createSkill('project-skill', { description: 'Project', instructions: 'Project instructions' }, projectDir, 'project');
78
78
 
79
79
  expect(fs.existsSync(path.join(projectDir, '.opencode', 'agents', 'project-agent.md'))).toBe(true);
80
80
  expect(fs.existsSync(path.join(projectDir, '.opencode', 'commands', 'project-command.md'))).toBe(true);
@@ -11,6 +11,7 @@
11
11
  * Reference: OpenCode `packages/core/src/config/normalize.ts`,
12
12
  * `packages/core/src/v1/config/migrate.ts`, and `services/www` migrate-v1 docs.
13
13
  */
14
+ import { isRecord } from '../shared/guards.js';
14
15
 
15
16
  // ============== SECTIONS ==============
16
17
 
@@ -18,16 +19,13 @@
18
19
  * Config section keys per entity. `v2` is what OpenChamber writes; `v1` is the
19
20
  * legacy key OpenCode still decodes and OpenChamber still reads.
20
21
  */
22
+
21
23
  const SECTIONS = {
22
24
  agents: { v2: 'agents', v1: 'agent' },
23
25
  commands: { v2: 'commands', v1: 'command' },
24
26
  providers: { v2: 'providers', v1: 'provider' },
25
27
  };
26
28
 
27
- function isRecord(value) {
28
- return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
29
- }
30
-
31
29
  function sectionKeys(kind) {
32
30
  const section = SECTIONS[kind];
33
31
  if (!section) throw new Error(`Unknown config section: ${kind}`);
@@ -39,7 +39,7 @@ import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
39
39
  import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
40
40
  import { registerOpenCodeRoutes } from './routes.js';
41
41
  import { getProviderSources, removeProviderConfig, upsertProviderConfig } from './providers.js';
42
- import { getAgentSources, getAgentConfig, getAgentPermissions, createAgent, updateAgent, deleteAgent } from './agents.js';
42
+ import { getAgentSources, getAgentConfig, getAgentPermissions, createAgent, updateAgent, deleteAgent, listDisabledAgents } from './agents.js';
43
43
  import { getCommandSources, getCommandConfig, createCommand, updateCommand, deleteCommand } from './commands.js';
44
44
  import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
45
45
  import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
@@ -66,6 +66,7 @@ import { installSkillsFromRepository } from '../skills-catalog/install.js';
66
66
  import { fetchGitHubRepoMetas } from '../skills-catalog/github-meta.js';
67
67
  import crypto from 'node:crypto';
68
68
  import { getGitHubAuthByAccountId } from '../github/auth.js';
69
+ import { getGitHubCliCredential } from '../github/octokit.js';
69
70
  import { createSourceControlAuthStore } from '../gitlab/auth-storage.js';
70
71
  import { refreshGitLabAccessToken, resolveGitLabClientId } from '../gitlab/device-flow.js';
71
72
  import { createGitCredentialResolver, createHttpsCredentialReference, parseGitCredentialReference } from '../git/credential-resolver.js';
@@ -110,6 +111,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
110
111
  let walkthroughBindingService = null;
111
112
  let gitRepositoryCredentialRuntime = null;
112
113
  let networkOperations = null;
114
+ let gitWorktreeCreation = null;
113
115
  const getWalkthroughService = async () => {
114
116
  if (!walkthroughService) {
115
117
  const [service, pullRequest] = await Promise.all([
@@ -171,6 +173,30 @@ export const createFeatureRoutesRuntime = (dependencies) => {
171
173
  });
172
174
  };
173
175
 
176
+ /**
177
+ * A worktree from a pull or merge request given by number, the same kind
178
+ * the New worktree dialog makes: the server reads the change request with
179
+ * the project's own account and creates the worktree through the Git
180
+ * routes' contributor pipeline. `name` and `branchName` default to the
181
+ * change request's head branch.
182
+ */
183
+ const createChangeRequestWorktree = async ({ directory, number, name, branchName }) => {
184
+ if (!(gitWorktreeCreation?.createWorktree instanceof Function)
185
+ || !(walkthroughBindingService?.resolveChangeRequestWorktreeSource instanceof Function)) {
186
+ throw Object.assign(new Error('Worktrees from pull requests are unavailable'), {
187
+ code: 'RUNTIME_UNSUPPORTED', status: 501,
188
+ });
189
+ }
190
+ const { headBranch, sourceRequest } = await walkthroughBindingService.resolveChangeRequestWorktreeSource({ directory, number });
191
+ const branch = branchName || headBranch;
192
+ return gitWorktreeCreation.createWorktree(directory, {
193
+ mode: 'existing',
194
+ branchName: branch,
195
+ worktreeName: name || branch,
196
+ changeRequestSource: sourceRequest,
197
+ });
198
+ };
199
+
174
200
  const registerRoutes = async (app, routeDependencies) => {
175
201
  const {
176
202
  messageSearchRuntime,
@@ -326,6 +352,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
326
352
  getAgentSources,
327
353
  getAgentConfig,
328
354
  getAgentPermissions,
355
+ listDisabledAgents,
329
356
  createAgent,
330
357
  updateAgent,
331
358
  deleteAgent,
@@ -370,44 +397,6 @@ export const createFeatureRoutesRuntime = (dependencies) => {
370
397
 
371
398
  const { getProfiles, getProfile, getGlobalIdentity, resolveRepositoryGitPaths } = await import('../git/index.js');
372
399
 
373
- registerSkillRoutes(app, {
374
- fs,
375
- path,
376
- os,
377
- resolveProjectDirectory,
378
- resolveOptionalProjectDirectory,
379
- readSettingsFromDisk,
380
- sanitizeSkillCatalogs,
381
- isUnsafeSkillRelativePath,
382
- refreshOpenCodeAfterConfigChange,
383
- clientReloadDelayMs,
384
- buildOpenCodeUrl,
385
- getOpenCodeAuthHeaders,
386
- getOpenCodePort,
387
- getSkillSources,
388
- discoverSkills,
389
- mergeDiscoveredSkills,
390
- createSkill,
391
- updateSkill,
392
- deleteSkill,
393
- renameSkill,
394
- isManagedSkillPath,
395
- readSkillSupportingFile,
396
- writeSkillSupportingFile,
397
- deleteSkillSupportingFile,
398
- SKILL_SCOPE,
399
- SKILL_DIR,
400
- getCuratedSkillsSources,
401
- getCacheKey,
402
- scanWithCache,
403
- parseSkillRepoSource,
404
- scanSkillsRepository,
405
- installSkillsFromRepository,
406
- fetchGitHubRepoMetas,
407
- getProfiles,
408
- getProfile,
409
- });
410
-
411
400
  registerQuotaRoutes(app, { getQuotaProviders });
412
401
  registerSmallModelRoutes(app, { getSmallModelService });
413
402
  registerSessionGoalRoutes(app);
@@ -419,12 +408,23 @@ export const createFeatureRoutesRuntime = (dependencies) => {
419
408
  origin, refreshToken, clientId: await resolveGitLabClientId(origin, readSettingsFromDisk),
420
409
  }),
421
410
  });
422
- const resolveSourceControlAccount = ({ provider, instance, accountId, credentialRevision }) => provider === 'github'
423
- ? getGitHubAuthByAccountId(accountId, credentialRevision)
424
- : gitlabAuthStore.readAccount(instance, accountId, credentialRevision);
411
+ // A GitHub account is either one OpenChamber stores or the gh CLI login
412
+ // the user switched to; reads already accept both, so Git transport
413
+ // (fork PR worktrees, clones) must too.
414
+ const resolveSourceControlAccount = async ({ provider, instance, accountId, credentialRevision }) => {
415
+ if (provider !== 'github') return gitlabAuthStore.readAccount(instance, accountId, credentialRevision);
416
+ return await getGitHubAuthByAccountId(accountId, credentialRevision)
417
+ ?? getGitHubCliCredential(accountId, credentialRevision);
418
+ };
425
419
  const resolveTransportRepository = createPrivateRepositoryIdentityResolver({
426
420
  getTransportRevision: (directory) => readEffectiveGitTransportRevision(directory, { gitBinary }),
427
421
  });
422
+ const contributorProvenance = createContributorProvenanceStore({
423
+ filePath: path.join(openchamberDataDir, 'git-contributor-provenance.json'),
424
+ resolveRepositoryIdentity: resolveTransportRepository,
425
+ resolveGitPaths: resolveRepositoryGitPaths,
426
+ fsImpl: fsPromises,
427
+ });
428
428
  const sourceControlAuditStore = createSourceControlAuditStore({
429
429
  filePath: path.join(openchamberDataDir, 'source-control-audit.json'),
430
430
  fsImpl: fsPromises,
@@ -479,6 +479,11 @@ export const createFeatureRoutesRuntime = (dependencies) => {
479
479
  });
480
480
  return inspection.requirements.find((entry) => entry.kind === kind && entry.path === checkoutPath) ?? null;
481
481
  },
482
+ github: {
483
+ // A worktree made from a fork PR has no upstream; its provenance says
484
+ // which remote the branch came from, so PR status can find the PR.
485
+ readContributorProvenance: contributorProvenance.read,
486
+ },
482
487
  gitlab: {
483
488
  configRoot: openchamberDataDir,
484
489
  store: gitlabAuthStore,
@@ -558,12 +563,6 @@ export const createFeatureRoutesRuntime = (dependencies) => {
558
563
  if (!profile) throw new Error('Git identity profile is unavailable');
559
564
  return profile;
560
565
  };
561
- const contributorProvenance = createContributorProvenanceStore({
562
- filePath: path.join(openchamberDataDir, 'git-contributor-provenance.json'),
563
- resolveRepositoryIdentity: resolveTransportRepository,
564
- resolveGitPaths: resolveRepositoryGitPaths,
565
- fsImpl: fsPromises,
566
- });
567
566
  const networkOperationStore = createGitNetworkOperationStore({
568
567
  filePath: path.join(openchamberDataDir, 'git-network-operations.json'),
569
568
  fsImpl: fsPromises,
@@ -579,6 +578,46 @@ export const createFeatureRoutesRuntime = (dependencies) => {
579
578
  fsImpl: fsPromises,
580
579
  snapshotRoot: path.join(openchamberDataDir, 'git-ssh-operation-keys'),
581
580
  });
581
+ registerSkillRoutes(app, {
582
+ fs,
583
+ path,
584
+ os,
585
+ resolveProjectDirectory,
586
+ resolveOptionalProjectDirectory,
587
+ readSettingsFromDisk,
588
+ sanitizeSkillCatalogs,
589
+ isUnsafeSkillRelativePath,
590
+ refreshOpenCodeAfterConfigChange,
591
+ clientReloadDelayMs,
592
+ buildOpenCodeUrl,
593
+ getOpenCodeAuthHeaders,
594
+ getOpenCodePort,
595
+ getSkillSources,
596
+ discoverSkills,
597
+ mergeDiscoveredSkills,
598
+ createSkill,
599
+ updateSkill,
600
+ deleteSkill,
601
+ renameSkill,
602
+ isManagedSkillPath,
603
+ readSkillSupportingFile,
604
+ writeSkillSupportingFile,
605
+ deleteSkillSupportingFile,
606
+ SKILL_SCOPE,
607
+ SKILL_DIR,
608
+ getCuratedSkillsSources,
609
+ getCacheKey,
610
+ scanWithCache,
611
+ parseSkillRepoSource,
612
+ scanSkillsRepository,
613
+ installSkillsFromRepository,
614
+ fetchGitHubRepoMetas,
615
+ getProfiles,
616
+ getProfile,
617
+ createHttpsCredentialReference,
618
+ resolveSourceControlAccount,
619
+ credentialResolver: gitCredentialResolver,
620
+ });
582
621
  gitRepositoryCredentialRuntime = createGitRepositoryCredentialRuntime({
583
622
  readBinding: (directory) => walkthroughBindingService.get(directory),
584
623
  credentialResolver: gitCredentialResolver,
@@ -612,7 +651,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
612
651
  });
613
652
  await registerBuiltInGuests({ persistPath: extensionsPersistPath(openchamberDataDir), root: routeDependencies.builtInExtensionsDir });
614
653
  registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService, onGuestDeactivated, surfaceViewerHeaders });
615
- registerGitRoutes(app, {
654
+ gitWorktreeCreation = registerGitRoutes(app, {
616
655
  // Identities for accounts connected before identities carried one are
617
656
  // made the first time identities are listed: a moment someone asked for,
618
657
  // not startup, where a development restart can kill a process holding a
@@ -689,6 +728,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
689
728
  return {
690
729
  registerRoutes,
691
730
  hydrateBoundCheckout,
731
+ createChangeRequestWorktree,
692
732
  /** Writes the Git credential helper's endpoint file; a no-op until the Git routes are registered. */
693
733
  publishRepositoryCredentialEndpoint: async () => { await gitRepositoryCredentialRuntime?.publish(); },
694
734
  };
@@ -3,6 +3,7 @@ import { spawn, spawnSync } from 'node:child_process';
3
3
  import { accessSync, constants as fsConstants, statSync } from 'node:fs';
4
4
  import net from 'node:net';
5
5
  import { stripAppImageArgv0Leak, stripAppImageLauncherEnv } from '../inherited-env.js';
6
+ import { assignInjectedEnv } from '../injected-env.js';
6
7
  import { registerManagedProcess, unregisterManagedProcess, reapOrphanedProcesses } from './managed-process-registry.js';
7
8
  import { applyProviderEnvAliases } from './provider-env-aliases.js';
8
9
  import { overlayEnvironment } from '../environment/variables.js';
@@ -137,12 +138,21 @@ export const createOpenCodeLifecycleControls = ({
137
138
  return Promise.reject(new Error('Shared OpenCode recovery is not configured'));
138
139
  }
139
140
  if (!sharedRecoveryPromise) {
140
- sharedRecoveryPromise = Promise.resolve()
141
- .then(() => recoverShared())
142
- .then(() => onSharedRecovered())
143
- .finally(() => {
144
- sharedRecoveryPromise = null;
145
- });
141
+ const recovery = Promise.resolve(recoverShared())
142
+ .then(() => onSharedRecovered());
143
+ sharedRecoveryPromise = recovery;
144
+ void recovery.then(
145
+ () => {
146
+ if (sharedRecoveryPromise === recovery) {
147
+ sharedRecoveryPromise = null;
148
+ }
149
+ },
150
+ () => {
151
+ if (sharedRecoveryPromise === recovery) {
152
+ sharedRecoveryPromise = null;
153
+ }
154
+ },
155
+ );
146
156
  }
147
157
  return sharedRecoveryPromise;
148
158
  };
@@ -269,6 +279,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
269
279
  getActiveSessionCount = () => 0,
270
280
  reapManagedOrphanedProcesses = reapOrphanedProcesses,
271
281
  getWarmupDirectories = async () => [],
282
+ // Scopes server-side reads while no project directory is usable (the
283
+ // managed chats root). Without it they name no directory, and OpenCode
284
+ // starts a location over its own working directory, the whole home.
285
+ noProjectDirectory = null,
272
286
  onOpenCodeRestarted = null,
273
287
  managedStartupTimeoutMs = 30_000,
274
288
  now = Date.now,
@@ -899,16 +913,20 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
899
913
  // in front of the managed PATH) and under everything OpenChamber itself
900
914
  // sets for OpenCode.
901
915
  const inheritedEnv = overlayEnvironment({ ...shellEnv, ...process.env, PATH: envPath }, getUserEnvironment());
902
- const processEnv = stripAppImageLauncherEnv(stripAppImageArgv0Leak(applyProviderEnvAliases({
903
- ...inheritedEnv,
904
- ...managedOpenCodeEnv,
905
- PATH: inheritedEnv.PATH,
906
- // OpenCode 2 reads OPENCODE_PASSWORD before the legacy name, so a
907
- // user's own OPENCODE_PASSWORD would otherwise win and every request
908
- // we send with openCodePassword would get 401.
909
- OPENCODE_PASSWORD: openCodePassword,
910
- OPENCODE_SERVER_PASSWORD: openCodePassword,
911
- })));
916
+ // What OpenChamber sets for OpenCode is recorded as injected, so a shell
917
+ // the agent opens can tell it from the user's own variables.
918
+ const processEnv = stripAppImageLauncherEnv(stripAppImageArgv0Leak(applyProviderEnvAliases(assignInjectedEnv(
919
+ { ...inheritedEnv },
920
+ {
921
+ ...managedOpenCodeEnv,
922
+ PATH: inheritedEnv.PATH,
923
+ // OpenCode 2 reads OPENCODE_PASSWORD before the legacy name, so a
924
+ // user's own OPENCODE_PASSWORD would otherwise win and every request
925
+ // we send with openCodePassword would get 401.
926
+ OPENCODE_PASSWORD: openCodePassword,
927
+ OPENCODE_SERVER_PASSWORD: openCodePassword,
928
+ },
929
+ ))));
912
930
  managedProcessEnv = processEnv;
913
931
 
914
932
  let serverInstance;
@@ -1416,8 +1434,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
1416
1434
  const usable = selectUsableDirectory(warmedOpenCodeDirectories);
1417
1435
  if (usable) defaultOpenCodeDirectory = usable;
1418
1436
  } catch {
1419
- // Best-effort: the caller falls back to OpenCode's working directory
1420
- // for this read and the refresh can succeed next time.
1437
+ // Best-effort: the caller falls back to the no-project directory or
1438
+ // OpenCode's working directory for this read, and the refresh can
1439
+ // succeed next time.
1421
1440
  } finally {
1422
1441
  defaultDirectoryRefresh = null;
1423
1442
  }
@@ -1426,8 +1445,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
1426
1445
  };
1427
1446
 
1428
1447
  // The warmed directory only while it is still usable; otherwise another
1429
- // usable directory (from this or a refreshed pass), or null so the caller
1430
- // sends no directory and OpenCode uses its own working directory.
1448
+ // usable directory (from this or a refreshed pass), else the no-project
1449
+ // directory, else null so the caller sends no directory and OpenCode uses
1450
+ // its own working directory.
1431
1451
  const getValidatedDefaultDirectory = () => {
1432
1452
  if (isUsableDirectory(defaultOpenCodeDirectory)) return defaultOpenCodeDirectory;
1433
1453
  const alternate = selectUsableDirectory(warmedOpenCodeDirectories);
@@ -1440,7 +1460,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
1440
1460
  defaultDirectoryRefreshAt = checkedAt;
1441
1461
  void refreshDefaultDirectories();
1442
1462
  }
1443
- return null;
1463
+ return isUsableDirectory(noProjectDirectory) ? noProjectDirectory : null;
1444
1464
  };
1445
1465
 
1446
1466
  // OpenCode initializes each project directory lazily on its first
@@ -4,6 +4,8 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { afterEach, describe, expect, it, vi } from 'vitest';
6
6
 
7
+ import { injectedEnvKeys } from '../injected-env.js';
8
+
7
9
  const spawnMock = vi.fn();
8
10
  const spawnSyncMock = vi.fn();
9
11
  const recordStartupPerformanceMock = vi.fn();
@@ -581,6 +583,45 @@ describe('OpenCode lifecycle', () => {
581
583
  await fs.rm(root, { recursive: true, force: true });
582
584
  });
583
585
 
586
+ it('scopes default reads to the chats root while no project directory is usable', async () => {
587
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-no-project-'));
588
+ const chats = path.join(root, 'chats');
589
+ const project = path.join(root, 'project');
590
+ await fs.mkdir(chats);
591
+ await fs.mkdir(project);
592
+ let warmup = [];
593
+ globalThis.fetch = vi.fn(async () => ({
594
+ ok: true,
595
+ json: async () => ({ version: '2.0.20', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
596
+ }));
597
+ const runtime = createRuntime({
598
+ env: {
599
+ ENV_CONFIGURED_OPENCODE_PORT: 45678,
600
+ ENV_CONFIGURED_OPENCODE_HOST: null,
601
+ ENV_EFFECTIVE_PORT: 45678,
602
+ ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1',
603
+ ENV_SKIP_OPENCODE_START: true,
604
+ },
605
+ reapManagedOrphanedProcesses: vi.fn(async () => ({ reaped: 0 })),
606
+ getWarmupDirectories: vi.fn(async () => warmup),
607
+ noProjectDirectory: chats,
608
+ });
609
+
610
+ await runtime.bootstrapOpenCodeAtStartup();
611
+ await new Promise((resolve) => setTimeout(resolve, 0));
612
+ // No project: a read without a directory would start OpenCode over the
613
+ // whole home, its working directory.
614
+ expect(runtime.getDefaultOpenCodeDirectory()).toBe(chats);
615
+ expect(globalThis.fetch.mock.calls.filter(([url]) => String(url).endsWith('/api/location'))).toEqual([]);
616
+
617
+ // A project opened later takes over once the warmup source names it.
618
+ warmup = [project];
619
+ await runtime.bootstrapOpenCodeAtStartup();
620
+ await new Promise((resolve) => setTimeout(resolve, 0));
621
+ expect(runtime.getDefaultOpenCodeDirectory()).toBe(project);
622
+ await fs.rm(root, { recursive: true, force: true });
623
+ });
624
+
584
625
  it('advances to a currently-valid warmup directory when the cached default goes stale', async () => {
585
626
  const root = await fs.mkdtemp(path.join(os.tmpdir(), 'oc-replace-'));
586
627
  const stale = path.join(root, 'gone');
@@ -776,12 +817,12 @@ describe('OpenCode lifecycle', () => {
776
817
  }
777
818
 
778
819
  expect(close).not.toHaveBeenCalled();
779
- expect(warn).toHaveBeenCalledTimes(1);
820
+ expect(warn).toHaveBeenCalledTimes(4);
780
821
 
781
822
  now += 15_000;
782
823
  await runtime.triggerHealthCheck();
783
824
 
784
- expect(warn).toHaveBeenCalledTimes(2);
825
+ expect(warn).toHaveBeenCalledTimes(5);
785
826
  expect(warn).toHaveBeenLastCalledWith(expect.stringContaining('(2/20)'));
786
827
  warn.mockRestore();
787
828
  });
@@ -1212,6 +1253,13 @@ describe('OpenCode lifecycle', () => {
1212
1253
  expect(options.env.OPENCODE_PASSWORD).toBe('password');
1213
1254
  expect(options.env.OPENCHAMBER_AGENT_TOOL_TOKEN).toBe('ephemeral');
1214
1255
  expect(runtime.getManagedOpenCodeProcessEnv().DATABASE_URL).toBe('postgres://db');
1256
+ // The replaced values are recorded as OpenChamber's; the user's are not.
1257
+ const injected = injectedEnvKeys(options.env);
1258
+ for (const key of ['OPENCODE_PASSWORD', 'OPENCODE_SERVER_PASSWORD', 'OPENCHAMBER_AGENT_TOOL_TOKEN']) {
1259
+ expect(injected.has(key)).toBe(true);
1260
+ }
1261
+ expect(injected.has('DATABASE_URL')).toBe(false);
1262
+ expect(injected.has('PATH')).toBe(false);
1215
1263
 
1216
1264
  await server.close();
1217
1265
  });
@@ -319,7 +319,7 @@ export async function discoverProviderModels(input, options = {}) {
319
319
  const storedApiKey = isSameEndpoint(options.storedBaseURL, body.baseURL)
320
320
  ? optionalString(options.storedApiKey)
321
321
  : null;
322
- const apiKey = requestedApiKey ?? storedApiKey;
322
+ const apiKey = requestedApiKey ?? resolveApiKey(storedApiKey, env);
323
323
  const response = await (options.fetch ?? fetch)(endpoint, {
324
324
  method: 'GET',
325
325
  headers: buildHeaders(body.headers, apiKey, env),
@@ -89,6 +89,23 @@ describe('provider model discovery', () => {
89
89
  expect(requests).toEqual(['Bearer stored-key', 'Bearer replacement-key']);
90
90
  });
91
91
 
92
+ test('resolves environment variables in stored keys', async () => {
93
+ const requests = [];
94
+ const fetchMock = async (_url, init) => {
95
+ requests.push(init.headers.Authorization);
96
+ return new Response(JSON.stringify({ data: [{ id: 'local' }] }), { status: 200 });
97
+ };
98
+
99
+ await discoverProviderModels({ baseURL: 'https://provider.test', enrich: false }, {
100
+ fetch: fetchMock,
101
+ storedApiKey: '{env:STORED_KEY}',
102
+ storedBaseURL: 'https://provider.test',
103
+ env: { STORED_KEY: 'resolved-secret' },
104
+ });
105
+
106
+ expect(requests).toEqual(['Bearer resolved-secret']);
107
+ });
108
+
92
109
  test('never sends the stored key to a base URL other than the saved one', async () => {
93
110
  const requests = [];
94
111
  const fetchMock = async (_url, init) => {
@@ -57,5 +57,3 @@ export async function getModelsMetadata({
57
57
  throw error;
58
58
  }
59
59
  }
60
-
61
- export { MODELS_DEV_API_URL };
@@ -83,7 +83,7 @@ beforeEach(() => {
83
83
  packageManager.detectPackageManagerDetails.mockReturnValue({
84
84
  packageManager: 'npm',
85
85
  });
86
- packageManager.getUpdateCommand.mockReturnValue('npm install -g @openchamber/web@latest');
86
+ packageManager.getUpdateCommand.mockReturnValue('npm install -g @vantran-se/openchamber-web@latest');
87
87
  });
88
88
 
89
89
  afterEach(() => {
@@ -308,7 +308,7 @@ describe('OpenChamber foreground update route', () => {
308
308
  '--setenv=PATH=/home/syu/.npm-global/bin:/usr/bin:/bin',
309
309
  '/bin/sh',
310
310
  '-c',
311
- "set -eu\nnpm install -g @openchamber/web@latest\nsystemctl --user restart 'openchamber@wsl.service'",
311
+ "set -eu\nnpm install -g @vantran-se/openchamber-web@latest\nsystemctl --user restart 'openchamber@wsl.service'",
312
312
  ], {
313
313
  encoding: 'utf8',
314
314
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -480,7 +480,7 @@ describe('OpenChamber web update route on Windows', () => {
480
480
  // The UI password is never part of the script or its log.
481
481
  expect(script).not.toContain('pa%');
482
482
  // A .cmd shim (npm, pnpm, yarn) must be `call`ed or the script ends there.
483
- expect(lines).toContain('call npm install -g @openchamber/web@latest');
483
+ expect(lines).toContain('call npm install -g @vantran-se/openchamber-web@latest');
484
484
  expect(lines).toContain('ping -n 3 127.0.0.1 >nul');
485
485
  expect(lines.some((line) => line.startsWith('timeout '))).toBe(false);
486
486
  expect(lines).toContain('if %ERRORLEVEL% EQU 0 (');