@vantran-se/openchamber-web 1.23.1 → 1.24.4

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 (313) hide show
  1. package/bin/cli.test.js +29 -6
  2. package/bin/lib/cli-http.js +5 -1
  3. package/bin/lib/commands-status.js +5 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-BOpqXpFU.js → CodeMirrorCommentWidgets-B4a0JkUz.js} +1 -1
  5. package/dist/assets/DiffView-B2CD9wC8.js +4 -0
  6. package/dist/assets/FilesView-C4vMFclc.js +13 -0
  7. package/dist/assets/GitView-Byu2DY7s.js +6 -0
  8. package/dist/assets/GuestAttachDialog-B808E0-d.js +1 -0
  9. package/dist/assets/{JsonTreeView-nd_E8eVM.js → JsonTreeView-VW_7czo3.js} +1 -1
  10. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  11. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  12. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  13. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  14. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  15. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  19. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  20. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  21. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  25. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  26. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  27. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  28. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  30. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  31. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  32. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  33. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  34. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  35. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  37. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  38. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  39. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  40. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  41. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  42. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  43. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  44. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  45. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  52. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  53. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  54. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  55. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  56. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  57. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  58. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  59. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  60. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  61. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  62. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  63. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  64. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  65. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  66. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  67. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  68. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  69. package/dist/assets/LinearIssuesView-EzGNCGrd.js +1 -0
  70. package/dist/assets/{MarkdownImageGallery-CVYds3sQ.js → MarkdownImageGallery-B2YUDFaB.js} +1 -1
  71. package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +12 -0
  72. package/dist/assets/{OnboardingScreen-Dd2IbPTn.js → OnboardingScreen-CQHBSTEi.js} +1 -1
  73. package/dist/assets/{PatchDiff-nUu5i4bG.js → PatchDiff-DCjGhlYV.js} +1 -1
  74. package/dist/assets/PierreDiffViewer-CEDZNkU2.js +210 -0
  75. package/dist/assets/PlanView-D3x-pTkZ.js +5 -0
  76. package/dist/assets/PluginPane-DEcgDQ9h.js +3 -0
  77. package/dist/assets/SettingsView-CSinuH-1.js +29 -0
  78. package/dist/assets/{SettingsWindow-BccxY81J.js → SettingsWindow-B5CHoJFZ.js} +1 -1
  79. package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +1 -0
  80. package/dist/assets/ToolOutputDialog-tLNWmfcW.js +15 -0
  81. package/dist/assets/{ToolPartDiffPreview-BFVgk0UK.js → ToolPartDiffPreview-CflzmK-x.js} +2 -2
  82. package/dist/assets/{VirtualizedFileDiff-z3_H74UR.js → VirtualizedFileDiff-NU0FHFhT.js} +3 -3
  83. package/dist/assets/{WalkthroughView-BqM9GFbJ.js → WalkthroughView-BZyjhqHv.js} +2 -2
  84. package/dist/assets/appThemeRegistry-DLicw4dH.js +1 -0
  85. package/dist/assets/commitAndPush-BDftrjr6.js +1 -0
  86. package/dist/assets/{computeVirtualFileMetrics-Bdazp9Qo.js → computeVirtualFileMetrics-DlWlEM3x.js} +1 -1
  87. package/dist/assets/de-Bro6_zyl.js +10 -0
  88. package/dist/assets/es-Bb9XQZvd.js +16 -0
  89. package/dist/assets/fr-Dgfzn-IL.js +14 -0
  90. package/dist/assets/{index-eo1-D_7z.js → index-CtQNmqUL.js} +1 -1
  91. package/dist/assets/index-DqaMWNrC.css +1 -0
  92. package/dist/assets/index-Dqc6USl3.js +40 -0
  93. package/dist/assets/{isDiffPlainText-BvKH712-.js → isDiffPlainText-CO4e5J_S.js} +1 -1
  94. package/dist/assets/{isFilePlainText-BdVVPukf.js → isFilePlainText-ue6kCou7.js} +1 -1
  95. package/dist/assets/ja-BYsakGFh.js +16 -0
  96. package/dist/assets/ko-BSaQz9aN.js +16 -0
  97. package/dist/assets/main-B62_pjL5.js +2 -0
  98. package/dist/assets/main-BEdcOI2P.js +896 -0
  99. package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
  100. package/dist/assets/markdownCore-CK48LG0z.js +5 -0
  101. package/dist/assets/miniChat-LT8Rqofr.js +2 -0
  102. package/dist/assets/mobile-CqarEynh.js +2 -0
  103. package/dist/assets/{patchFileDiff-B7LjppSS.js → patchFileDiff-D7QVlovD.js} +3 -3
  104. package/dist/assets/pl-OIviPdoj.js +16 -0
  105. package/dist/assets/pt-BR-Cltg-E6s.js +16 -0
  106. package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +2 -0
  107. package/dist/assets/renderMobileApp-BC1MHg6g.js +25 -0
  108. package/dist/assets/runtimeEndpointReset-DdrazOVH.js +25 -0
  109. package/dist/assets/{scheduledTasksApi-hceovDei.js → scheduledTasksApi-B2SWMGEU.js} +1 -1
  110. package/dist/assets/shikiHighlight-BPu3JiQs.js +1 -0
  111. package/dist/assets/textMateThemeFromAppTheme-DWqRPo9A.js +1 -0
  112. package/dist/assets/tr-CQI-KPre.js +7 -0
  113. package/dist/assets/uk-CqW7R4DA.js +16 -0
  114. package/dist/assets/useAppFontEffects-ne84JBVL.js +7333 -0
  115. package/dist/assets/useInlineCommentController-CgS0fnw_.js +2 -0
  116. package/dist/assets/useRootScrollLock-Cpf1qoGp.js +2 -0
  117. package/dist/assets/{vendor-base-ui-BY_5qla_.js → vendor-base-ui-CsXYnR5c.js} +1 -1
  118. package/dist/assets/{vendor-codemirror-language-data-DWA1bm1F.js → vendor-codemirror-language-data-Bx2EHc1O.js} +2 -2
  119. package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
  120. package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
  121. package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
  122. package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
  123. package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
  124. package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
  125. package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
  126. package/dist/assets/{vendor-zod-B6551dwG.js → vendor-zod-CgAdXWZO.js} +4 -4
  127. package/dist/assets/{web-components-B_Z64So5.js → web-components-DxX8bZ1o.js} +2 -2
  128. package/dist/assets/zh-CN-CVZG_o_J.js +16 -0
  129. package/dist/assets/zh-TW-D5ZxzCZp.js +16 -0
  130. package/dist/index.html +6 -4
  131. package/dist/mini-chat.html +6 -4
  132. package/dist/mobile.html +6 -4
  133. package/package.json +12 -9
  134. package/server/built-in-extensions/registry.json +4 -0
  135. package/server/index.d.ts +2 -0
  136. package/server/index.js +44 -10
  137. package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
  138. package/server/lib/agent-tool/runtime.js +62 -15
  139. package/server/lib/agent-tool/runtime.test.js +88 -2
  140. package/server/lib/changelog/update-notes.js +2 -1
  141. package/server/lib/changelog/update-notes.test.js +19 -0
  142. package/server/lib/event-stream/DOCUMENTATION.md +23 -0
  143. package/server/lib/event-stream/delta-coalescer.js +0 -0
  144. package/server/lib/event-stream/delta-coalescer.test.js +292 -0
  145. package/server/lib/event-stream/global-hub.js +53 -23
  146. package/server/lib/event-stream/global-hub.test.js +160 -0
  147. package/server/lib/event-stream/global-ws-bridge.js +1 -0
  148. package/server/lib/event-stream/index.js +4 -0
  149. package/server/lib/fs/DOCUMENTATION.md +10 -1
  150. package/server/lib/fs/git-process.test.js +77 -0
  151. package/server/lib/fs/routes.js +58 -5
  152. package/server/lib/fs/routes.test.js +116 -0
  153. package/server/lib/fs/search.js +2 -1
  154. package/server/lib/git/DOCUMENTATION.md +20 -5
  155. package/server/lib/git/routes.js +16 -3
  156. package/server/lib/git/serial-refresh.js +93 -0
  157. package/server/lib/git/serial-refresh.test.js +112 -0
  158. package/server/lib/git/service.js +429 -70
  159. package/server/lib/git/service.test.js +418 -0
  160. package/server/lib/github/pr-status.js +5 -5
  161. package/server/lib/github/pr-status.test.js +1 -1
  162. package/server/lib/github/routes.js +4 -4
  163. package/server/lib/guests/DOCUMENTATION.md +68 -0
  164. package/server/lib/guests/auth-store.js +184 -0
  165. package/server/lib/guests/auth-store.test.js +112 -0
  166. package/server/lib/guests/background.test.js +83 -0
  167. package/server/lib/guests/builtins.js +34 -0
  168. package/server/lib/guests/builtins.test.js +174 -0
  169. package/server/lib/guests/catalog.js +477 -0
  170. package/server/lib/guests/catalog.test.js +390 -0
  171. package/server/lib/guests/clone.js +297 -0
  172. package/server/lib/guests/extract-zip.js +136 -0
  173. package/server/lib/guests/extract-zip.test.js +109 -0
  174. package/server/lib/guests/files.js +308 -0
  175. package/server/lib/guests/files.test.js +262 -0
  176. package/server/lib/guests/grant-scope.js +81 -0
  177. package/server/lib/guests/grant-scope.test.js +75 -0
  178. package/server/lib/guests/host-session.js +73 -0
  179. package/server/lib/guests/host-session.test.js +30 -0
  180. package/server/lib/guests/html-styles.js +6 -0
  181. package/server/lib/guests/html-styles.test.js +45 -0
  182. package/server/lib/guests/html-tokens.js +73 -0
  183. package/server/lib/guests/html-tokens.test.js +53 -0
  184. package/server/lib/guests/install.js +406 -0
  185. package/server/lib/guests/install.test.js +338 -0
  186. package/server/lib/guests/oauth.js +501 -0
  187. package/server/lib/guests/oauth.test.js +481 -0
  188. package/server/lib/guests/persist.js +336 -0
  189. package/server/lib/guests/persist.test.js +288 -0
  190. package/server/lib/guests/request.js +112 -0
  191. package/server/lib/guests/request.test.js +206 -0
  192. package/server/lib/guests/routes.js +838 -0
  193. package/server/lib/guests/service.js +538 -0
  194. package/server/lib/guests/service.test.js +277 -0
  195. package/server/lib/guests/sockets.js +129 -0
  196. package/server/lib/guests/sockets.test.js +110 -0
  197. package/server/lib/guests/ssh-install.test.js +188 -0
  198. package/server/lib/guests/storage.js +68 -0
  199. package/server/lib/guests/storage.test.js +59 -0
  200. package/server/lib/guests/updates.js +294 -0
  201. package/server/lib/guests/updates.test.js +334 -0
  202. package/server/lib/guests/upload.js +50 -0
  203. package/server/lib/guests/upload.test.js +131 -0
  204. package/server/lib/linear/DOCUMENTATION.md +1 -1
  205. package/server/lib/message-queue/DOCUMENTATION.md +10 -1
  206. package/server/lib/message-queue/runtime.js +21 -1
  207. package/server/lib/message-queue/runtime.test.js +52 -5
  208. package/server/lib/notifications/routes.js +5 -0
  209. package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
  210. package/server/lib/openchamber-sessions/routes.js +62 -29
  211. package/server/lib/openchamber-sessions/routes.test.js +96 -0
  212. package/server/lib/opencode/DOCUMENTATION.md +55 -1
  213. package/server/lib/opencode/bootstrap-runtime.js +1 -0
  214. package/server/lib/opencode/core-routes.js +39 -1
  215. package/server/lib/opencode/feature-routes-runtime.js +15 -0
  216. package/server/lib/opencode/lifecycle-process.test.js +84 -0
  217. package/server/lib/opencode/lifecycle.js +54 -67
  218. package/server/lib/opencode/openchamber-routes.js +72 -14
  219. package/server/lib/opencode/openchamber-routes.test.js +71 -3
  220. package/server/lib/opencode/session-runtime.js +68 -0
  221. package/server/lib/opencode/session-runtime.test.js +43 -0
  222. package/server/lib/opencode/settings-helpers.js +8 -0
  223. package/server/lib/opencode/settings-helpers.test.js +13 -1
  224. package/server/lib/opencode/settings-normalization-runtime.js +2 -0
  225. package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
  226. package/server/lib/opencode/settings-registry.json +15 -0
  227. package/server/lib/opencode/shutdown-runtime.js +3 -0
  228. package/server/lib/opencode/shutdown-runtime.test.js +24 -0
  229. package/server/lib/opencode/theme-archive.js +53 -0
  230. package/server/lib/opencode/theme-catalog.js +171 -0
  231. package/server/lib/opencode/theme-catalog.test.js +125 -0
  232. package/server/lib/opencode/theme-import.test.js +144 -0
  233. package/server/lib/opencode/theme-runtime.js +100 -28
  234. package/server/lib/opencode/theme-runtime.test.js +15 -0
  235. package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
  236. package/server/lib/permission-auto-accept/runtime.js +13 -0
  237. package/server/lib/permission-auto-accept/runtime.test.js +37 -1
  238. package/server/lib/projects/project-config.js +2 -2
  239. package/server/lib/quota/DOCUMENTATION.md +0 -1
  240. package/server/lib/quota/providers/index.js +0 -7
  241. package/server/lib/realtime-proxy-terminal.test.js +150 -0
  242. package/server/lib/realtime-proxy.js +2 -0
  243. package/server/lib/routing/DOCUMENTATION.md +83 -0
  244. package/server/lib/routing/defaults.js +98 -0
  245. package/server/lib/routing/feature-flag.js +11 -0
  246. package/server/lib/routing/history.js +34 -0
  247. package/server/lib/routing/jev.js +98 -0
  248. package/server/lib/routing/routes.http.test.js +100 -0
  249. package/server/lib/routing/routes.js +81 -0
  250. package/server/lib/routing/runtime.js +217 -0
  251. package/server/lib/routing/runtime.test.js +188 -0
  252. package/server/lib/routing/store.js +249 -0
  253. package/server/lib/routing/store.test.js +96 -0
  254. package/server/lib/scheduled-tasks/runtime.js +2 -2
  255. package/server/lib/scheduled-tasks/runtime.test.js +14 -0
  256. package/server/lib/security/request-security.js +9 -5
  257. package/server/lib/security/request-security.test.js +37 -0
  258. package/server/lib/small-model/DOCUMENTATION.md +14 -2
  259. package/server/lib/small-model/call.js +37 -6
  260. package/server/lib/small-model/call.test.js +182 -0
  261. package/server/lib/terminal/DOCUMENTATION.md +23 -2
  262. package/server/lib/terminal/runtime.js +27 -5
  263. package/server/lib/terminal/runtime.test.js +67 -0
  264. package/server/lib/terminal/shutdown.js +106 -0
  265. package/server/lib/terminal/shutdown.test.js +84 -0
  266. package/server/lib/tts/service.test.js +78 -0
  267. package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
  268. package/server/lib/ui-auth/session-cookie.js +51 -0
  269. package/server/lib/ui-auth/session-cookie.test.js +46 -0
  270. package/server/lib/ui-auth/ui-auth.js +56 -17
  271. package/server/lib/ui-auth/ui-auth.test.js +96 -0
  272. package/dist/assets/DiffView-CyLmb-wF.js +0 -4
  273. package/dist/assets/DirtyBranchSwitchDialog-pu5kcoir.js +0 -1
  274. package/dist/assets/FilesView-WtJB0y_l.js +0 -13
  275. package/dist/assets/GitView-DkNrV0BK.js +0 -6
  276. package/dist/assets/LinearIssuesView-B0H0Otii.js +0 -1
  277. package/dist/assets/MarkdownRendererImpl-DSqVN2d8.js +0 -12
  278. package/dist/assets/PierreDiffViewer-Bsq9z-sw.js +0 -161
  279. package/dist/assets/PlanView-CAFziKH5.js +0 -5
  280. package/dist/assets/SettingsView-BRittavZ.js +0 -28
  281. package/dist/assets/ToolOutputDialog-B0uYaJio.js +0 -15
  282. package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
  283. package/dist/assets/de-c7CYLCAW.js +0 -10
  284. package/dist/assets/es-BKAVPQmQ.js +0 -16
  285. package/dist/assets/fr-Bj1glqfV.js +0 -14
  286. package/dist/assets/index-BYFyOu_D.css +0 -1
  287. package/dist/assets/index-BfwRClDe.js +0 -40
  288. package/dist/assets/ja-5jwSIbK0.js +0 -16
  289. package/dist/assets/ko-BaDbg-Eb.js +0 -16
  290. package/dist/assets/main-CGG2_uHq.js +0 -2
  291. package/dist/assets/main-CcrSaL4J.js +0 -930
  292. package/dist/assets/markdownCore-DsmQr3bt.js +0 -5
  293. package/dist/assets/miniChat-DEaAhP4b.js +0 -2
  294. package/dist/assets/mobile-BC3rsvv0.js +0 -2
  295. package/dist/assets/pl-BFKVBDFu.js +0 -16
  296. package/dist/assets/pt-BR-qKfHvkDL.js +0 -16
  297. package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
  298. package/dist/assets/renderElectronMiniChatApp-CR9d1Mr0.js +0 -2
  299. package/dist/assets/renderMobileApp-Da5dwobZ.js +0 -25
  300. package/dist/assets/runtimeEndpointReset-CLYVRw2H.js +0 -41
  301. package/dist/assets/shikiHighlight-D1Z9G58W.js +0 -1
  302. package/dist/assets/tr-DYXPoEDG.js +0 -7
  303. package/dist/assets/uk-CwAoloR8.js +0 -16
  304. package/dist/assets/useAppFontEffects-DeStGcUT.js +0 -7282
  305. package/dist/assets/useGitComparison-CpxioduU.js +0 -1
  306. package/dist/assets/useInlineCommentController-gmUnJtrb.js +0 -2
  307. package/dist/assets/useRootScrollLock--a3cPz8s.js +0 -1
  308. package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
  309. package/dist/assets/zh-CN-D9MG0lqF.js +0 -16
  310. package/dist/assets/zh-TW-1HNK7shh.js +0 -16
  311. package/server/lib/quota/providers/crof.js +0 -96
  312. package/server/lib/quota/providers/crof.test.js +0 -98
  313. /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
@@ -58,7 +58,7 @@ const createOpenCode = () => {
58
58
  return { state, fetchImpl };
59
59
  };
60
60
 
61
- const createRuntime = ({ dataDir = makeDataDir(), openCode = createOpenCode(), knowledge = null, retryDelayMs } = {}) => {
61
+ const createRuntime = ({ dataDir = makeDataDir(), openCode = createOpenCode(), knowledge = null, retryDelayMs, resolvePromptBody, now } = {}) => {
62
62
  let eventHandler = () => {};
63
63
  let statusHandler = () => {};
64
64
  const broadcasts = [];
@@ -79,6 +79,8 @@ const createRuntime = ({ dataDir = makeDataDir(), openCode = createOpenCode(), k
79
79
  abortHoldMs: 50,
80
80
  };
81
81
  if (retryDelayMs) options.retryDelayMs = retryDelayMs;
82
+ if (resolvePromptBody) options.resolvePromptBody = resolvePromptBody;
83
+ if (now) options.now = now;
82
84
  const runtime = createMessageQueueRuntime(options);
83
85
  return {
84
86
  runtime,
@@ -95,6 +97,36 @@ const settle = async (ms = 30) => {
95
97
  await new Promise((resolve) => setTimeout(resolve, ms));
96
98
  };
97
99
 
100
+ describe('auto routing', () => {
101
+ it('lets the routing hook rewrite the model of a queued prompt and a queued command', async () => {
102
+ const resolvePromptBody = vi.fn(async (body) => {
103
+ if (body.model?.modelID === 'auto') body.model = { providerID: 'openai', modelID: 'gpt-6-astra' };
104
+ if (body.model === 'openchamber/auto') body.model = 'openai/gpt-6-astra';
105
+ return null;
106
+ });
107
+ const { runtime, openCode, emit } = createRuntime({ resolvePromptBody });
108
+ runtime.start();
109
+ openCode.state.statuses = { [SESSION]: { type: 'busy' } };
110
+ openCode.state.commands = [{ name: 'review', template: 'Review $ARGUMENTS' }];
111
+ const auto = { providerID: 'openchamber', modelID: 'auto', agent: 'build' };
112
+ await runtime.enqueue(SESSION, DIRECTORY, item({ content: 'plain', text: 'plain', sendConfig: auto }));
113
+ await runtime.enqueue(SESSION, DIRECTORY, item({ content: '/review src', text: '/review src', sendConfig: auto }));
114
+
115
+ openCode.state.statuses = {};
116
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
117
+ await settle();
118
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
119
+ await settle();
120
+
121
+ expect(openCode.state.sent.map((entry) => entry.body.model)).toEqual([
122
+ { providerID: 'openai', modelID: 'gpt-6-astra' },
123
+ 'openai/gpt-6-astra',
124
+ ]);
125
+ expect(resolvePromptBody).toHaveBeenCalledTimes(2);
126
+ expect(resolvePromptBody.mock.calls[0][1]).toEqual({ sessionId: SESSION, directory: DIRECTORY });
127
+ });
128
+ });
129
+
98
130
  describe('parseQueuedItemInput', () => {
99
131
  it('rejects an item the server could not deliver later', () => {
100
132
  expect(() => parseQueuedItemInput({ content: 'x' })).toThrow(TypeError);
@@ -175,9 +207,10 @@ describe('message queue runtime', () => {
175
207
  });
176
208
 
177
209
  it('does not send into a running turn even when the status event says idle', async () => {
178
- const { runtime, openCode, emit } = createRuntime();
210
+ const { runtime, openCode, emit } = createRuntime({ now: () => 10_000 });
179
211
  runtime.start();
180
- openCode.state.tail = [{ info: { role: 'assistant', time: { created: 1 } } }];
212
+ // Live unfinished turn: created after this runtime started.
213
+ openCode.state.tail = [{ info: { role: 'assistant', time: { created: 10_001 } } }];
181
214
  await runtime.enqueue(SESSION, DIRECTORY, item());
182
215
  emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
183
216
  await settle();
@@ -185,10 +218,24 @@ describe('message queue runtime', () => {
185
218
 
186
219
  // The reply completes: that alone drains the queue (a missed idle event
187
220
  // must not strand it).
188
- openCode.state.tail = [{ info: { role: 'assistant', time: { created: 1, completed: 2 } } }];
189
- emit({ type: 'message.updated', properties: { info: { role: 'assistant', sessionID: SESSION, time: { created: 1, completed: 2 } } } });
221
+ openCode.state.tail = [{ info: { role: 'assistant', time: { created: 10_001, completed: 10_002 } } }];
222
+ emit({ type: 'message.updated', properties: { info: { role: 'assistant', sessionID: SESSION, time: { created: 10_001, completed: 10_002 } } } });
223
+ await settle();
224
+ expect(openCode.state.sent).toHaveLength(1);
225
+ });
226
+
227
+ it('delivers past an unfinished tail that predates this runtime (dead pre-restart run)', async () => {
228
+ const { runtime, openCode, emit } = createRuntime({ now: () => 10_000 });
229
+ runtime.start();
230
+ // Assistant reply interrupted by a server restart: unfinished, but older
231
+ // than this runtime — no completion event will ever arrive for it, so it
232
+ // must not block a restored queue forever.
233
+ openCode.state.tail = [{ info: { role: 'assistant', time: { created: 1 } } }];
234
+ await runtime.enqueue(SESSION, DIRECTORY, item());
235
+ emit({ type: 'session.status', properties: { sessionID: SESSION, status: { type: 'idle' } } });
190
236
  await settle();
191
237
  expect(openCode.state.sent).toHaveLength(1);
238
+ expect(openCode.state.sent[0].path).toBe(`/session/${SESSION}/prompt_async`);
192
239
  });
193
240
 
194
241
  it('treats an unreachable OpenCode as unknown, not idle', async () => {
@@ -44,6 +44,7 @@ export const registerNotificationRoutes = (app, dependencies) => {
44
44
  writeSseEvent,
45
45
  getSessionActivitySnapshot,
46
46
  getSessionStateSnapshot,
47
+ getPendingBlockingRequestsSnapshot,
47
48
  getSessionAttentionSnapshot,
48
49
  getSessionState,
49
50
  getSessionAttentionState,
@@ -295,11 +296,15 @@ export const registerNotificationRoutes = (app, dependencies) => {
295
296
  });
296
297
  });
297
298
 
299
+ // Cross-project seed for clients that do not initialize every directory:
300
+ // live status per session plus the permission and question requests still
301
+ // waiting for an answer. Both come from the server's single upstream stream.
298
302
  app.get('/api/sessions/status', async (_req, res) => {
299
303
  await ensureSessionWatcher();
300
304
  const snapshot = getSessionStateSnapshot();
301
305
  res.json({
302
306
  sessions: snapshot,
307
+ pending: typeof getPendingBlockingRequestsSnapshot === 'function' ? getPendingBlockingRequestsSnapshot() : {},
303
308
  serverTime: Date.now(),
304
309
  });
305
310
  });
@@ -44,6 +44,12 @@ other.
44
44
  - Send and fork dispatches without an explicit model/agent/variant reuse the
45
45
  target session's last user-message selection before falling back to the
46
46
  configured defaults; only session creation resolves defaults directly.
47
+ - Default agents resolve from the owning project before global settings and
48
+ OpenCode defaults. Directory-based requests identify the project before
49
+ creating a worktree; existing linked worktrees resolve through Git's primary
50
+ worktree root. Send/fork fallback uses that same owner. Configured model IDs
51
+ and effort preferences survive missing catalog entries rather than silently
52
+ dispatching with a different model.
47
53
  - Usage errors name the missing or conflicting input so CLI and agent-tool
48
54
  callers can correct an invalid request without an upfront usage manual.
49
55
  - Explicit `projectId` or `directory` scope takes precedence over the managed
@@ -1,6 +1,6 @@
1
1
  import express from 'express';
2
2
  import { createOpencodeClient } from '@opencode-ai/sdk/v2';
3
- import { createWorktree, getWorktreeBootstrapStatus } from '../git/index.js';
3
+ import { createWorktree, getWorktreeBootstrapStatus, resolvePrimaryWorktreeRoot } from '../git/index.js';
4
4
  import { expandSnippets } from '../opencode/snippets.js';
5
5
  import { expandCommandGoalObjective, parseScheduledCommandPrompt } from '../scheduled-tasks/runtime.js';
6
6
  import { buildGoalIntroText, createSessionGoal } from '../session-goal/create.js';
@@ -75,6 +75,7 @@ const resolveVariant = (providers, providerID, modelID, variant) => {
75
75
  if (!normalized) return undefined;
76
76
  const provider = providers.find((entry) => entry?.id === providerID);
77
77
  const model = providerModels(provider).find((entry) => entry?.id === modelID);
78
+ if (!model) return normalized;
78
79
  return model?.variants && Object.prototype.hasOwnProperty.call(model.variants, normalized)
79
80
  ? normalized
80
81
  : undefined;
@@ -82,6 +83,18 @@ const resolveVariant = (providers, providerID, modelID, variant) => {
82
83
 
83
84
  const parseConfigModel = (value) => splitModel(value);
84
85
 
86
+ const resolveProjectDefaults = (settings, directory, projectId) => {
87
+ const projects = Array.isArray(settings?.projects) ? settings.projects : [];
88
+ const matchedProject = projectId
89
+ ? projects.find((entry) => entry?.id === projectId) || null
90
+ : projects.find((entry) => entry?.path === directory) || null;
91
+ return {
92
+ defaultAgent: asNonEmptyString(matchedProject?.defaultAgent),
93
+ defaultModel: asNonEmptyString(matchedProject?.defaultModel),
94
+ defaultVariant: asNonEmptyString(matchedProject?.defaultVariant),
95
+ };
96
+ };
97
+
85
98
  const buildDirectoryHeaders = (directory) => ({
86
99
  // OpenCode rejects non-ASCII header values; the official SDK sends this
87
100
  // header percent-encoded, so match that wire format (non-ASCII checkout
@@ -121,11 +134,15 @@ const fetchSelectionInputs = async ({ buildOpenCodeUrl, authHeaders, directory,
121
134
  };
122
135
  };
123
136
 
124
- const resolveDefaultSelection = ({ agents, providers, settings, opencodeDefaultAgent, opencodeDefaultModel }) => {
137
+ const resolveDefaultSelection = ({ agents, providers, settings, projectDefaults, opencodeDefaultAgent, opencodeDefaultModel }) => {
125
138
  const primaryAgents = agents.filter((agent) => isPrimaryAgentMode(agent?.mode) && agent?.hidden !== true);
126
139
  let resolvedAgent = null;
140
+ const projectDefaultAgent = asNonEmptyString(projectDefaults?.defaultAgent);
127
141
  const settingsDefaultAgent = asNonEmptyString(settings?.defaultAgent);
128
- if (settingsDefaultAgent) {
142
+ if (projectDefaultAgent) {
143
+ resolvedAgent = agents.find((agent) => agent?.name === projectDefaultAgent) || null;
144
+ }
145
+ if (!resolvedAgent && settingsDefaultAgent) {
129
146
  resolvedAgent = agents.find((agent) => agent?.name === settingsDefaultAgent) || null;
130
147
  }
131
148
  if (!resolvedAgent && opencodeDefaultAgent) {
@@ -140,20 +157,24 @@ const resolveDefaultSelection = ({ agents, providers, settings, opencodeDefaultA
140
157
 
141
158
  let model = null;
142
159
  let variant;
160
+ const projectDefaultModel = parseConfigModel(projectDefaults?.defaultModel);
143
161
  const settingsDefaultModel = parseConfigModel(settings?.defaultModel);
144
- if (settingsDefaultModel && hasProviderModel(providers, settingsDefaultModel.providerID, settingsDefaultModel.modelID)) {
162
+ if (projectDefaultModel) {
163
+ model = projectDefaultModel;
164
+ variant = resolveVariant(providers, model.providerID, model.modelID, projectDefaults?.defaultVariant);
165
+ }
166
+ if (!model && settingsDefaultModel) {
145
167
  model = settingsDefaultModel;
146
168
  variant = resolveVariant(providers, model.providerID, model.modelID, settings?.defaultVariant);
147
169
  }
148
170
 
149
- if (!model && resolvedAgent?.model?.providerID && resolvedAgent?.model?.modelID
150
- && hasProviderModel(providers, resolvedAgent.model.providerID, resolvedAgent.model.modelID)) {
171
+ if (!model && resolvedAgent?.model?.providerID && resolvedAgent?.model?.modelID) {
151
172
  model = { providerID: resolvedAgent.model.providerID, modelID: resolvedAgent.model.modelID };
152
173
  variant = resolveVariant(providers, model.providerID, model.modelID, resolvedAgent.variant);
153
174
  }
154
175
 
155
176
  const opencodeModel = parseConfigModel(opencodeDefaultModel);
156
- if (!model && opencodeModel && hasProviderModel(providers, opencodeModel.providerID, opencodeModel.modelID)) {
177
+ if (!model && opencodeModel) {
157
178
  model = opencodeModel;
158
179
  }
159
180
 
@@ -304,9 +325,15 @@ const resolveRequestedDirectory = async ({ payload, readSettingsFromDiskMigrated
304
325
 
305
326
  const directory = asNonEmptyString(payload?.directory);
306
327
  const validated = await validateDirectoryPath(directory);
307
- return validated.ok
308
- ? { ok: true, directory: validated.directory }
309
- : { ok: false, status: 400, error: validated.error || 'Invalid directory' };
328
+ if (!validated.ok) return { ok: false, status: 400, error: validated.error || 'Invalid directory' };
329
+ const settings = await readSettingsFromDiskMigrated();
330
+ const projects = sanitizeProjects(settings?.projects || []);
331
+ let project = projects.find((entry) => entry.path === validated.directory);
332
+ if (!project && projects.length > 0) {
333
+ const { root } = await resolvePrimaryWorktreeRoot(validated.directory);
334
+ project = projects.find((entry) => entry.path === root);
335
+ }
336
+ return { ok: true, directory: validated.directory, ...(project ? { projectId: project.id } : {}) };
310
337
  };
311
338
 
312
339
  const PROMPT_LANDED_TIMEOUT_MS = 5_000;
@@ -395,6 +422,10 @@ export const createOpenChamberSessionService = (dependencies) => {
395
422
  emitSessionCreatedEvent,
396
423
  createSessionGoal: createSessionGoalOverride,
397
424
  sessionKnowledgeRuntime = null,
425
+ // Auto routing. Prompts dispatched here go straight to OpenCode, not
426
+ // through the proxy that rewrites the Auto sentinel, so the same hook runs
427
+ // on the body before it is sent. Null when routing is not wired in.
428
+ resolvePromptBody = null,
398
429
  } = dependencies;
399
430
 
400
431
  // Last user message of an existing session, as a selection to reuse. Returns
@@ -468,6 +499,7 @@ export const createOpenChamberSessionService = (dependencies) => {
468
499
  authHeaders,
469
500
  sessionID,
470
501
  directory,
502
+ projectId,
471
503
  prompt,
472
504
  goalInput,
473
505
  requestedModel,
@@ -495,7 +527,10 @@ export const createOpenChamberSessionService = (dependencies) => {
495
527
  directory,
496
528
  readSettingsFromDiskMigrated,
497
529
  });
498
- const defaults = resolveDefaultSelection(inputs);
530
+ const defaults = resolveDefaultSelection({
531
+ ...inputs,
532
+ projectDefaults: resolveProjectDefaults(inputs.settings, directory, projectId),
533
+ });
499
534
  if (!model) {
500
535
  model = defaults.model;
501
536
  if (variant == null) variant = defaults.variant;
@@ -565,25 +600,21 @@ export const createOpenChamberSessionService = (dependencies) => {
565
600
  ? await sessionKnowledgeRuntime.resolvePendingForSession(sessionID, directory)
566
601
  .catch(() => ({ text: '', signature: '' }))
567
602
  : { text: '', signature: '' };
603
+ const payload = {
604
+ model,
605
+ ...(agent ? { agent } : {}),
606
+ ...(variant ? { variant } : {}),
607
+ parts: [
608
+ ...(knowledge.text ? [{ type: 'text', text: knowledge.text, synthetic: true }] : []),
609
+ { type: 'text', text: expandedPrompt },
610
+ ...(goalInput.enabled
611
+ ? [{ type: 'text', text: buildGoalIntroText(goalInput.tokenBudget), synthetic: true }]
612
+ : []),
613
+ ],
614
+ };
615
+ await resolvePromptBody?.(payload, { sessionId: sessionID, directory });
568
616
  try {
569
- await runPromptAsync({
570
- baseUrl,
571
- authHeaders,
572
- sessionID,
573
- directory,
574
- payload: {
575
- model,
576
- ...(agent ? { agent } : {}),
577
- ...(variant ? { variant } : {}),
578
- parts: [
579
- ...(knowledge.text ? [{ type: 'text', text: knowledge.text, synthetic: true }] : []),
580
- { type: 'text', text: expandedPrompt },
581
- ...(goalInput.enabled
582
- ? [{ type: 'text', text: buildGoalIntroText(goalInput.tokenBudget), synthetic: true }]
583
- : []),
584
- ],
585
- },
586
- });
617
+ await runPromptAsync({ baseUrl, authHeaders, sessionID, directory, payload });
587
618
  } catch (error) {
588
619
  throw markGoalPartial(error);
589
620
  }
@@ -735,6 +766,7 @@ export const createOpenChamberSessionService = (dependencies) => {
735
766
  authHeaders,
736
767
  sessionID,
737
768
  directory: sessionDirectory,
769
+ projectId: resolvedDirectory.projectId,
738
770
  prompt,
739
771
  goalInput,
740
772
  requestedModel: model,
@@ -838,6 +870,7 @@ export const createOpenChamberSessionService = (dependencies) => {
838
870
  authHeaders,
839
871
  sessionID: targetSessionID,
840
872
  directory,
873
+ projectId: resolvedDirectory.projectId,
841
874
  prompt,
842
875
  goalInput,
843
876
  requestedModel,
@@ -90,6 +90,7 @@ vi.mock('@opencode-ai/sdk/v2', () => ({
90
90
  vi.mock('../git/index.js', () => ({
91
91
  createWorktree: (...args) => globalThis.__openchamberCreateWorktreeMock(...args),
92
92
  getWorktreeBootstrapStatus: (...args) => globalThis.__openchamberGetWorktreeBootstrapStatusMock(...args),
93
+ resolvePrimaryWorktreeRoot: async (directory) => ({ root: directory === '/repo/worktrees/side-task' ? '/repo/app' : directory }),
93
94
  }));
94
95
 
95
96
  const createApp = (overrides = {}, options = {}) => {
@@ -360,6 +361,101 @@ describe('openchamber session routes', () => {
360
361
  }
361
362
  });
362
363
 
364
+ it('lets the routing hook replace an Auto default before the prompt leaves', async () => {
365
+ const originalFetch = globalThis.fetch;
366
+ const fetchMock = vi.fn(async (url) => {
367
+ const text = String(url);
368
+ if (text.includes('/prompt_async')) {
369
+ return { ok: true, text: async () => '' };
370
+ }
371
+ if (text.includes('/config/providers')) {
372
+ return { ok: true, json: async () => ({ providers: [{ id: 'openai', models: { 'gpt-5.5': { id: 'gpt-5.5' } } }] }) };
373
+ }
374
+ if (text.includes('/agent')) {
375
+ return { ok: true, json: async () => [{ name: 'build', mode: 'primary' }] };
376
+ }
377
+ if (text.includes('/config')) {
378
+ return { ok: true, json: async () => ({}) };
379
+ }
380
+ return { ok: true, json: async () => ({ id: 'ses_123' }) };
381
+ });
382
+ globalThis.fetch = fetchMock;
383
+ const seen = [];
384
+ const resolvePromptBody = vi.fn(async (body, target) => {
385
+ seen.push({ model: body.model, target });
386
+ body.model = { providerID: 'openai', modelID: 'gpt-5.5' };
387
+ });
388
+ const { app } = createApp({
389
+ readSettingsFromDiskMigrated: async () => ({
390
+ defaultModel: 'openchamber/auto',
391
+ defaultAgent: 'build',
392
+ projects: [{ id: 'proj_1', path: '/repo/app' }],
393
+ }),
394
+ resolvePromptBody,
395
+ });
396
+ try {
397
+ await request(app)
398
+ .post('/api/openchamber/sessions')
399
+ .send({ directory: '/repo/app', prompt: 'Run this' })
400
+ .expect(200);
401
+
402
+ expect(seen).toEqual([{
403
+ model: { providerID: 'openchamber', modelID: 'auto' },
404
+ target: { sessionId: 'ses_123', directory: '/repo/app' },
405
+ }]);
406
+ const promptCall = fetchMock.mock.calls.find(([url]) => String(url).includes('/prompt_async'));
407
+ expect(JSON.parse(promptCall?.[1]?.body).model).toEqual({ providerID: 'openai', modelID: 'gpt-5.5' });
408
+ } finally {
409
+ globalThis.fetch = originalFetch;
410
+ }
411
+ });
412
+
413
+ it.each([
414
+ ['', { projectId: 'proj_1' }],
415
+ ['', { directory: '/repo/app', worktree: { name: 'side-task' } }],
416
+ ['', { directory: '/repo/worktrees/side-task' }],
417
+ ['/ses_existing/send', { directory: '/repo/worktrees/side-task' }],
418
+ ['/ses_existing/fork', { directory: '/repo/worktrees/side-task' }],
419
+ ])('prefers project defaults for %s with %j', async (endpoint, scope) => {
420
+ const originalFetch = globalThis.fetch;
421
+ const fetchMock = vi.fn(async (url) => {
422
+ const text = String(url);
423
+ if (text.includes('/prompt_async')) {
424
+ return { ok: true, text: async () => '' };
425
+ }
426
+ if (text.includes('/config/providers')) {
427
+ return { ok: true, json: async () => ({ providers: [{ id: 'openai', models: { 'gpt-5.5': { id: 'gpt-5.5' } } }] }) };
428
+ }
429
+ if (text.includes('/agent')) {
430
+ return { ok: true, json: async () => [{ name: 'build', mode: 'primary' }, { name: 'plan', mode: 'primary' }] };
431
+ }
432
+ if (text.includes('/config')) {
433
+ return { ok: true, json: async () => ({}) };
434
+ }
435
+ return { ok: true, json: async () => ({ id: 'ses_123' }) };
436
+ });
437
+ globalThis.fetch = fetchMock;
438
+ const { app } = createApp({
439
+ readSettingsFromDiskMigrated: async () => ({
440
+ defaultModel: 'openai/gpt-5.5',
441
+ defaultAgent: 'build',
442
+ projects: [{ id: 'proj_1', path: '/repo/app', defaultAgent: 'plan' }],
443
+ }),
444
+ });
445
+ try {
446
+ const response = await request(app)
447
+ .post(`/api/openchamber/sessions${endpoint}`)
448
+ .send({ ...scope, prompt: 'Run this' })
449
+ .expect(200);
450
+
451
+ expect(response.body.agent).toBe('plan');
452
+ const promptCall = fetchMock.mock.calls.find(([url]) => String(url).includes('/prompt_async'));
453
+ expect(JSON.parse(promptCall?.[1]?.body)).toMatchObject({ agent: 'plan' });
454
+ } finally {
455
+ globalThis.fetch = originalFetch;
456
+ }
457
+ });
458
+
363
459
  it('dispatches an initial prompt when model is provided', async () => {
364
460
  const originalFetch = globalThis.fetch;
365
461
  const fetchMock = vi.fn(async (url) => {
@@ -32,7 +32,7 @@ This module provides OpenCode server integration utilities for the web server ru
32
32
  - `packages/web/server/lib/opencode/tunnel-wiring-runtime.js`: tunnel service/routes composition runtime and active-port wiring for main server startup.
33
33
  - `packages/web/server/lib/opencode/startup-pipeline-runtime.js`: server startup tail orchestration runtime for terminal/proxy/static/start-listen flow.
34
34
  - `packages/web/server/lib/opencode/startup-performance.js`: opt-in startup phase diagnostics with fixed labels and numeric metadata allowlists.
35
- - `packages/web/server/lib/agent-tool/runtime.js`: managed OpenCode custom-tool materialization, environment injection, loopback authentication, and fixed CLI action dispatch.
35
+ - `packages/web/server/lib/agent-tool/runtime.js`: managed OpenCode custom-tool materialization, environment injection, same-machine authentication (loopback, or the bound address for a concrete bind), and fixed CLI action dispatch.
36
36
  - `packages/web/server/lib/system-prompt/runtime.js`: opt-in managed OpenCode system-prompt optimizer materialization and plugin injection.
37
37
  - `packages/web/server/lib/opencode/managed-plugin-config.js`: the one `OPENCODE_CONFIG_CONTENT` merge every managed plugin (agent tools, system prompt optimizer) appends itself through.
38
38
  - `packages/web/server/lib/opencode/server-utils-runtime.js`: shared server runtime utilities for OpenCode proxy wiring, OpenCode port/readiness helpers, and snapshot fetchers.
@@ -44,6 +44,28 @@ This module provides OpenCode server integration utilities for the web server ru
44
44
  - `packages/web/server/lib/opencode/settings-helpers.js`: Settings payload sanitization/format helpers runtime for response shaping and persisted merge prep.
45
45
  - `packages/web/server/lib/opencode/settings-normalization-runtime.js`: path/settings/tunnel normalization and sanitization helpers runtime used by settings/routes/config wiring.
46
46
  - `packages/web/server/lib/opencode/theme-runtime.js`: custom theme JSON validation and theme directory loading runtime for settings utility routes.
47
+
48
+ `POST /api/config/themes` saves a converted VS Code palette. The runtime validates
49
+ literal colors and required authored roles, assigns a content-derived filename,
50
+ and publishes through a same-directory hard link so partial files and overwrites
51
+ are impossible. Identical retries reuse the existing file; a manually edited
52
+ collision returns 409. Temporary files are ignored by the loader and removed
53
+ after publication or failure. Non-missing-directory read failures propagate to
54
+ the route instead of returning an authoritative empty library.
55
+
56
+ The common request middleware parses theme POST bodies before these routes;
57
+ integration tests must use that middleware rather than an unrestricted test parser.
58
+ `DELETE /api/config/themes/:id` finds a valid regular JSON file by its metadata ID
59
+ inside the custom themes directory. IDs are never used as filenames. Hand-added
60
+ themes are supported; symlinks and bundled themes are outside deletion ownership.
61
+ Duplicate matching IDs fail explicitly. Missing themes are an idempotent success;
62
+ filesystem failures remain errors.
63
+ `theme-catalog.js` owns POST catalog search/package routes under
64
+ `/api/config/themes/catalog/`. It fetches only Open VSX and its Eclipse CDN over
65
+ HTTPS, validates redirects and checksums, and verifies packaged identity.
66
+ `theme-archive.js` reads selected JSON entries in memory with bounded decompression.
67
+ JSON includes and token references stay inside the package. Each failed variant
68
+ is reported separately so valid siblings remain available. No extension code runs.
47
69
  - `packages/web/server/lib/opencode/proxy.js`: OpenCode API/SSE forwarding and readiness-gate route registration.
48
70
  - `packages/web/server/lib/opencode/session-runtime.js`: session status/attention/activity runtime for OpenCode SSE events.
49
71
  - `packages/web/server/lib/opencode/watcher.js`: global SSE watcher runtime for push/session event fanout.
@@ -156,6 +178,23 @@ Transport-triggered health checks share the periodic monitor's failure accountin
156
178
 
157
179
  Managed health failures are classified as `timeout`, `connection_refused`, `connection_reset`, `invalid_response`, or `error`. The lifecycle retains the latest counted failure with a bounded detail string and source. Managed process wrappers continue capturing a sanitized, bounded stderr tail after readiness and retain exit code/signal. Before replacing a managed process, lifecycle snapshots the reason, latest health failure, process diagnostics/aliveness, busy-session count, and timestamp into `lastOpenCodeRestartDiagnostics`; successful startup does not clear this snapshot, and `/health` exposes it for post-restart diagnosis without process environment or credentials.
158
180
 
181
+ Managed process ownership starts at spawn. The registry and runtime process
182
+ handle include children that have not announced readiness yet, so shutdown can
183
+ stop an in-flight startup. Readiness timeout, malformed startup output, and
184
+ health-probe errors close that child before retrying. Shutdown cancels further
185
+ startup attempts. Closing a process is single-flight and unregisters it only
186
+ after it exits.
187
+
188
+ On Windows, managed teardown invokes the existing tree termination command
189
+ before terminating the root. Calling `child.kill()` first loses the ancestry
190
+ needed to find Git, shell, and MCP descendants. On POSIX, the managed child
191
+ starts in its own process group and teardown escalates against that group even
192
+ if the root has already exited. A tool ignoring SIGTERM must not survive just
193
+ because the server closed its own pipes. The
194
+ `lifecycle-process.test.js` regressions launch real parent/child fixtures and
195
+ check PID exit plus registry cleanup. macOS results do not validate Windows
196
+ ConPTY or Console Window Host behavior.
197
+
159
198
  ## Public exports (env-runtime.js)
160
199
  - `createOpenCodeEnvRuntime(dependencies)`: creates runtime that owns OpenCode CLI environment and binary discovery state.
161
200
  - OpenCode CLI resolution order is persisted settings, environment overrides, bundled Desktop CLI when available, PATH, known install locations, then platform shell discovery.
@@ -332,6 +371,7 @@ Managed health failures are classified as `timeout`, `connection_refused`, `conn
332
371
 
333
372
  ## Public exports (shutdown-runtime.js)
334
373
  - `createGracefulShutdownRuntime(dependencies)`: creates graceful shutdown runtime for managed OpenCode and web server teardown sequencing.
374
+ - After stopping owned runtimes and OpenCode, HTTP shutdown closes active connections as well as the listener. A remaining SSE response must not hold Desktop open until its fallback deadline. Upgraded sockets remain the responsibility of their owning runtime.
335
375
  - Returned API:
336
376
  - `gracefulShutdown(options?)`
337
377
 
@@ -389,6 +429,20 @@ within a ten-minute overall deadline.
389
429
  - Foreground servers running under a systemd user unit queue installation in
390
430
  a separate transient unit and restart the configured service afterwards.
391
431
  `OPENCHAMBER_SYSTEMD_UNIT` overrides the default `openchamber.service`.
432
+ - On Windows the install-and-restart script is written to
433
+ `<data dir>/update-install.cmd` before the response and run with
434
+ `cmd.exe /c <file>`. A newline ends a `cmd.exe /c` command line, so the
435
+ same script passed as an argument ran nothing and exited 0; the batch
436
+ file keeps every line. The package-manager line is `call`ed because
437
+ npm, pnpm and yarn are `.cmd` shims that would otherwise end the script,
438
+ the pre-install pause is a loopback `ping` because `timeout` rejects a
439
+ detached child's stdin, and the file deletes itself on its last line
440
+ because the restart command carries the server's flags. If the file
441
+ cannot be written the route answers 500 and the server keeps running.
442
+ The listener is closed before the batch is spawned: on Windows the
443
+ detached child inherits the listening socket and would hold the port
444
+ for the whole batch, so the restart inside it failed with "port already
445
+ in use" and the update ended with no server.
392
446
  - `GET /api/openchamber/models-metadata`
393
447
  - `GET /api/zen/models`
394
448
 
@@ -131,6 +131,7 @@ export const createBootstrapRuntime = (dependencies) => {
131
131
  writeSseEvent,
132
132
  getSessionActivitySnapshot: sessionRuntime.getSessionActivitySnapshot,
133
133
  getSessionStateSnapshot: sessionRuntime.getSessionStateSnapshot,
134
+ getPendingBlockingRequestsSnapshot: sessionRuntime.getPendingBlockingRequestsSnapshot,
134
135
  getSessionAttentionSnapshot: sessionRuntime.getSessionAttentionSnapshot,
135
136
  getSessionState: sessionRuntime.getSessionState,
136
137
  getSessionAttentionState: sessionRuntime.getSessionAttentionState,
@@ -1,4 +1,6 @@
1
1
  import { buildExternalManualRestartResponse } from './config-mutation-response.js';
2
+ import { ThemeImportStorageError } from './theme-runtime.js';
3
+ import { registerThemeCatalogRoutes } from './theme-catalog.js';
2
4
 
3
5
  const parseLoopbackUrl = (rawUrl) => {
4
6
  if (typeof rawUrl !== 'string') {
@@ -589,7 +591,15 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
589
591
  res.status(statusCode).json({ error: 'Invalid or expired pairing session' });
590
592
  };
591
593
 
594
+ const isGuestOauthCallback = (req) => (
595
+ req.method === 'GET'
596
+ && /^\/guests\/[a-z][a-z0-9-]*\/oauth\/callback$/.test(req.path || '')
597
+ );
598
+
592
599
  const requireApiAuth = async (req, res, next) => {
600
+ if (isGuestOauthCallback(req)) {
601
+ return next();
602
+ }
593
603
  const requestScope = tunnelAuthController.classifyRequestScope(req);
594
604
  if (requestScope === 'tunnel' || requestScope === 'unknown-public') {
595
605
  return tunnelAuthController.requireTunnelSession(req, res, next);
@@ -995,6 +1005,8 @@ export const registerAuthAndAccessRoutes = (app, dependencies) => {
995
1005
  export const registerSettingsUtilityRoutes = (app, dependencies) => {
996
1006
  const {
997
1007
  readCustomThemesFromDisk,
1008
+ saveImportedTheme,
1009
+ deleteImportedTheme,
998
1010
  refreshOpenCodeAfterConfigChange,
999
1011
  clientReloadDelayMs,
1000
1012
  } = dependencies;
@@ -1009,6 +1021,30 @@ export const registerSettingsUtilityRoutes = (app, dependencies) => {
1009
1021
  }
1010
1022
  });
1011
1023
 
1024
+ app.post('/api/config/themes', async (req, res) => {
1025
+ try {
1026
+ const theme = await saveImportedTheme(req.body?.theme);
1027
+ res.status(201).json({ theme });
1028
+ } catch (error) {
1029
+ if (error instanceof ThemeImportStorageError) {
1030
+ res.status(error.status).json({ error: error.code });
1031
+ return;
1032
+ }
1033
+ console.error('[themes] Failed to save imported theme');
1034
+ res.status(500).json({ error: 'save' });
1035
+ }
1036
+ });
1037
+
1038
+ registerThemeCatalogRoutes(app);
1039
+ app.delete('/api/config/themes/:id', async (req, res) => {
1040
+ try {
1041
+ await deleteImportedTheme(req.params.id);
1042
+ res.json({ success: true });
1043
+ } catch (error) {
1044
+ res.status(error instanceof ThemeImportStorageError ? error.status : 500).json({ error: 'delete' });
1045
+ }
1046
+ });
1047
+
1012
1048
  app.post('/api/config/reload', async (_req, res) => {
1013
1049
  try {
1014
1050
  console.log('[Server] Manual configuration reload requested');
@@ -1041,7 +1077,9 @@ export const registerCommonRequestMiddleware = (app, dependencies) => {
1041
1077
  const { express, verboseRequestLogs = false } = dependencies;
1042
1078
 
1043
1079
  app.use((req, res, next) => {
1044
- if (req.path.startsWith('/api/behavior')) {
1080
+ if (req.path === '/api/config/themes' || req.path.startsWith('/api/config/themes/')) {
1081
+ express.json({ limit: '1mb' })(req, res, next);
1082
+ } else if (req.path.startsWith('/api/behavior')) {
1045
1083
  const contentLength = parseInt(req.headers['content-length'] || '0', 10);
1046
1084
  if (contentLength > 1024 * 1024) {
1047
1085
  return res.status(413).json({ error: 'Content exceeds maximum size of 1048576 bytes' });
@@ -5,6 +5,9 @@ import { registerWalkthroughRoutes } from '../walkthrough/routes.js';
5
5
  import { registerSessionGoalRoutes } from '../session-goal/routes.js';
6
6
  import { registerGitHubRoutes } from '../github/routes.js';
7
7
  import { registerLinearRoutes } from '../linear/routes.js';
8
+ import { registerGuestRoutes } from '../guests/routes.js';
9
+ import { registerBuiltInGuests } from '../guests/catalog.js';
10
+ import { extensionsPersistPath } from '../guests/persist.js';
8
11
  import { registerGitRoutes } from '../git/routes.js';
9
12
  import { registerDevServerRoutes } from '../dev-servers/routes.js';
10
13
  import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
@@ -15,6 +18,7 @@ import { registerAgentMemoryRoutes } from '../agent-memory/routes.js';
15
18
  import { registerSessionKnowledgeRoutes } from '../session-knowledge/routes.js';
16
19
  import { registerPermissionAutoAcceptRoutes } from '../permission-auto-accept/runtime.js';
17
20
  import { registerMessageQueueRoutes } from '../message-queue/runtime.js';
21
+ import { registerRoutingPromptRewrite, registerRoutingRoutes } from '../routing/routes.js';
18
22
  import { registerConfigEntityRoutes } from './config-entity-routes.js';
19
23
  import { registerSettingsUtilityRoutes } from './core-routes.js';
20
24
  import { registerProjectIconRoutes } from './project-icon-routes.js';
@@ -105,6 +109,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
105
109
  resolveOptionalProjectDirectory,
106
110
  validateDirectoryPath,
107
111
  readCustomThemesFromDisk,
112
+ saveImportedTheme,
113
+ deleteImportedTheme,
108
114
  refreshOpenCodeAfterConfigChange,
109
115
  getOpenCodeResolutionSnapshot,
110
116
  getOpenCodeUpgradeCapability,
@@ -136,16 +142,23 @@ export const createFeatureRoutesRuntime = (dependencies) => {
136
142
  emitSessionCreatedEvent,
137
143
  permissionAutoAcceptRuntime,
138
144
  messageQueueRuntime,
145
+ routingRuntime,
146
+ openchamberVersion,
139
147
  } = routeDependencies;
140
148
 
141
149
  registerSettingsUtilityRoutes(app, {
142
150
  readCustomThemesFromDisk,
151
+ saveImportedTheme,
152
+ deleteImportedTheme,
143
153
  refreshOpenCodeAfterConfigChange,
144
154
  clientReloadDelayMs,
145
155
  });
146
156
 
147
157
  registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
148
158
  registerMessageQueueRoutes(app, messageQueueRuntime);
159
+ registerRoutingRoutes(app, routingRuntime);
160
+ // Before the generic OpenCode proxy: turns `openchamber/auto` into a real model.
161
+ registerRoutingPromptRewrite(app, routingRuntime);
149
162
 
150
163
  registerOpenCodeRoutes(app, {
151
164
  crypto,
@@ -307,6 +320,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
307
320
  registerSessionGoalRoutes(app);
308
321
  registerGitHubRoutes(app);
309
322
  registerLinearRoutes(app);
323
+ await registerBuiltInGuests({ persistPath: extensionsPersistPath(openchamberDataDir), root: routeDependencies.builtInExtensionsDir });
324
+ registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService });
310
325
  registerGitRoutes(app, {
311
326
  emitWorktreeChanged: ({ directories, at }) => {
312
327
  const clients = getOpenChamberEventClients();