@vantran-se/openchamber-web 1.23.0 → 1.24.3

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 (362) 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-DFg2wnpo.js → CodeMirrorCommentWidgets-BjJHrzmU.js} +1 -1
  5. package/dist/assets/DiffView-COVQhWGd.js +4 -0
  6. package/dist/assets/FilesView-B1V5JxFX.js +13 -0
  7. package/dist/assets/GitView-CDOJy5Ay.js +6 -0
  8. package/dist/assets/GuestAttachDialog-D3S0Rjic.js +1 -0
  9. package/dist/assets/{JsonTreeView-BKdsjg5B.js → JsonTreeView-D0C9nU7K.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-D7FKg1XJ.js +1 -0
  70. package/dist/assets/{MarkdownImageGallery-_5gEVr0X.js → MarkdownImageGallery-BrFjAVEf.js} +1 -1
  71. package/dist/assets/MarkdownRendererImpl-DWepTafh.js +12 -0
  72. package/dist/assets/{OnboardingScreen-DdP4_nuc.js → OnboardingScreen-Dyxxiqdt.js} +1 -1
  73. package/dist/assets/{PatchDiff-am3SAss2.js → PatchDiff-DnEhjXcC.js} +1 -1
  74. package/dist/assets/PierreDiffViewer-DyxZVVVR.js +210 -0
  75. package/dist/assets/{PlanView-BoJxdRZe.js → PlanView-LW2z_EAP.js} +4 -4
  76. package/dist/assets/PluginPane-B4tKno2h.js +3 -0
  77. package/dist/assets/SettingsView-DEaMulN9.js +29 -0
  78. package/dist/assets/{SettingsWindow-DVTdDSol.js → SettingsWindow-FLDJXOgQ.js} +1 -1
  79. package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +1 -0
  80. package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +15 -0
  81. package/dist/assets/{ToolPartDiffPreview-C7WLk5i9.js → ToolPartDiffPreview-CkR4msKP.js} +2 -2
  82. package/dist/assets/{VirtualizedFileDiff-DcdgkH-x.js → VirtualizedFileDiff-0pyr_mHY.js} +4 -4
  83. package/dist/assets/WalkthroughView-yNj4nOP5.js +4 -0
  84. package/dist/assets/appThemeRegistry-BuUKhbYQ.js +1 -0
  85. package/dist/assets/commitAndPush-DvJKa3Ks.js +1 -0
  86. package/dist/assets/{computeVirtualFileMetrics-C8byMnRh.js → computeVirtualFileMetrics-DxTBMuu-.js} +2 -2
  87. package/dist/assets/de-DgxgFyGG.js +10 -0
  88. package/dist/assets/es-BFsZj5F_.js +16 -0
  89. package/dist/assets/fr-jdZ5KKQi.js +14 -0
  90. package/dist/assets/{index-BB7ryz8y.js → index-CKHjPKbq.js} +1 -1
  91. package/dist/assets/index-Cbci4i1W.js +40 -0
  92. package/dist/assets/index-DqaMWNrC.css +1 -0
  93. package/dist/assets/{isDiffPlainText-45ujMPGb.js → isDiffPlainText-D7FkBMP6.js} +1 -1
  94. package/dist/assets/{isFilePlainText-Cc4KFSYi.js → isFilePlainText-CL2vsrIZ.js} +1 -1
  95. package/dist/assets/ja-C5kKrhO4.js +16 -0
  96. package/dist/assets/ko-DXDTT_0O.js +16 -0
  97. package/dist/assets/main-CZALtVek.js +896 -0
  98. package/dist/assets/main-CgXMBNvG.js +2 -0
  99. package/dist/assets/{markdown-shiki.worker-D5fBU-lw.js → markdown-shiki.worker-CahLvHIq.js} +5 -5
  100. package/dist/assets/markdownCore-Dh7WEsdM.js +5 -0
  101. package/dist/assets/miniChat-CwTmDixi.js +2 -0
  102. package/dist/assets/mobile-CLf2vCtO.js +2 -0
  103. package/dist/assets/patchFileDiff-Bsba8KFp.js +13 -0
  104. package/dist/assets/pl-D-7x4Y8Q.js +16 -0
  105. package/dist/assets/pt-BR-CSBZrHa8.js +16 -0
  106. package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +2 -0
  107. package/dist/assets/renderMobileApp-V4hYd32H.js +25 -0
  108. package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +25 -0
  109. package/dist/assets/scheduledTasksApi-DzjbLd8P.js +1 -0
  110. package/dist/assets/shikiHighlight-8_3csmr6.js +1 -0
  111. package/dist/assets/surface-B6n7wJtt.js +1 -0
  112. package/dist/assets/textMateThemeFromAppTheme-CHo2fCgW.js +1 -0
  113. package/dist/assets/tr-Dr-oJ22L.js +7 -0
  114. package/dist/assets/uk-9Nx83UcI.js +16 -0
  115. package/dist/assets/useAppFontEffects-khZXpFL_.js +7333 -0
  116. package/dist/assets/useInlineCommentController-DkQ_gqpn.js +2 -0
  117. package/dist/assets/useRootScrollLock-DdvHf9Pt.js +2 -0
  118. package/dist/assets/{vendor-base-ui-DdV8vhHn.js → vendor-base-ui-CsXYnR5c.js} +1 -1
  119. package/dist/assets/{vendor-codemirror-language-data-DzlSIu_W.js → vendor-codemirror-language-data-CdzUmUyZ.js} +2 -2
  120. package/dist/assets/vendor-framer-motion-Bq3iTZ6r.js +1 -0
  121. package/dist/assets/vendor-katex-Cp579UoJ.css +1 -0
  122. package/dist/assets/{vendor-motion-dom-D_o3BTI3.js → vendor-motion-dom-DcCgeG69.js} +1 -1
  123. package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
  124. package/dist/assets/vendor-strip-json-comments-5dJlOkP1.js +4 -0
  125. package/dist/assets/{vendor-tanstack-react-virtual-g05Cx3z9.js → vendor-tanstack-react-virtual-ClYN8CzB.js} +1 -1
  126. package/dist/assets/{vendor-tanstack-virtual-core-Dubg10g5.js → vendor-tanstack-virtual-core-BewVQryB.js} +1 -1
  127. package/dist/assets/vendor-zod-CgAdXWZO.js +39 -0
  128. package/dist/assets/{web-components-CdGfR2XJ.js → web-components-B8IcVQAA.js} +2 -2
  129. package/dist/assets/zh-CN-Bxy_Hj3r.js +16 -0
  130. package/dist/assets/zh-TW-0BB4gpHM.js +16 -0
  131. package/dist/index.html +6 -4
  132. package/dist/mini-chat.html +6 -4
  133. package/dist/mobile.html +6 -4
  134. package/package.json +12 -9
  135. package/server/built-in-extensions/registry.json +4 -0
  136. package/server/index.d.ts +2 -0
  137. package/server/index.js +47 -23
  138. package/server/lib/agent-memory/runtime.js +7 -2
  139. package/server/lib/agent-memory/runtime.test.js +22 -0
  140. package/server/lib/agent-tool/DOCUMENTATION.md +17 -4
  141. package/server/lib/agent-tool/runtime.js +62 -15
  142. package/server/lib/agent-tool/runtime.test.js +88 -2
  143. package/server/lib/changelog/update-notes.js +2 -1
  144. package/server/lib/changelog/update-notes.test.js +19 -0
  145. package/server/lib/event-stream/DOCUMENTATION.md +26 -0
  146. package/server/lib/event-stream/delta-coalescer.js +0 -0
  147. package/server/lib/event-stream/delta-coalescer.test.js +292 -0
  148. package/server/lib/event-stream/global-hub.js +68 -13
  149. package/server/lib/event-stream/global-hub.test.js +182 -0
  150. package/server/lib/event-stream/global-ws-bridge.js +16 -18
  151. package/server/lib/event-stream/index.js +4 -0
  152. package/server/lib/event-stream/protocol.js +21 -7
  153. package/server/lib/event-stream/runtime.js +9 -6
  154. package/server/lib/event-stream/runtime.test.js +60 -0
  155. package/server/lib/fs/DOCUMENTATION.md +10 -1
  156. package/server/lib/fs/git-process.test.js +77 -0
  157. package/server/lib/fs/routes.js +58 -5
  158. package/server/lib/fs/routes.test.js +116 -0
  159. package/server/lib/fs/search.js +2 -1
  160. package/server/lib/git/DOCUMENTATION.md +30 -6
  161. package/server/lib/git/routes.js +37 -11
  162. package/server/lib/git/routes.test.js +78 -0
  163. package/server/lib/git/serial-refresh.js +93 -0
  164. package/server/lib/git/serial-refresh.test.js +112 -0
  165. package/server/lib/git/service.js +588 -74
  166. package/server/lib/git/service.test.js +492 -0
  167. package/server/lib/github/DOCUMENTATION.md +5 -0
  168. package/server/lib/github/pr-status.js +5 -5
  169. package/server/lib/github/pr-status.test.js +1 -1
  170. package/server/lib/github/routes.js +6 -6
  171. package/server/lib/guests/DOCUMENTATION.md +68 -0
  172. package/server/lib/guests/auth-store.js +184 -0
  173. package/server/lib/guests/auth-store.test.js +112 -0
  174. package/server/lib/guests/background.test.js +83 -0
  175. package/server/lib/guests/builtins.js +34 -0
  176. package/server/lib/guests/builtins.test.js +174 -0
  177. package/server/lib/guests/catalog.js +477 -0
  178. package/server/lib/guests/catalog.test.js +390 -0
  179. package/server/lib/guests/clone.js +297 -0
  180. package/server/lib/guests/extract-zip.js +136 -0
  181. package/server/lib/guests/extract-zip.test.js +109 -0
  182. package/server/lib/guests/files.js +308 -0
  183. package/server/lib/guests/files.test.js +262 -0
  184. package/server/lib/guests/grant-scope.js +81 -0
  185. package/server/lib/guests/grant-scope.test.js +75 -0
  186. package/server/lib/guests/host-session.js +73 -0
  187. package/server/lib/guests/host-session.test.js +30 -0
  188. package/server/lib/guests/html-styles.js +6 -0
  189. package/server/lib/guests/html-styles.test.js +45 -0
  190. package/server/lib/guests/html-tokens.js +73 -0
  191. package/server/lib/guests/html-tokens.test.js +53 -0
  192. package/server/lib/guests/install.js +406 -0
  193. package/server/lib/guests/install.test.js +338 -0
  194. package/server/lib/guests/oauth.js +501 -0
  195. package/server/lib/guests/oauth.test.js +481 -0
  196. package/server/lib/guests/persist.js +336 -0
  197. package/server/lib/guests/persist.test.js +288 -0
  198. package/server/lib/guests/request.js +112 -0
  199. package/server/lib/guests/request.test.js +206 -0
  200. package/server/lib/guests/routes.js +838 -0
  201. package/server/lib/guests/service.js +538 -0
  202. package/server/lib/guests/service.test.js +277 -0
  203. package/server/lib/guests/sockets.js +129 -0
  204. package/server/lib/guests/sockets.test.js +110 -0
  205. package/server/lib/guests/ssh-install.test.js +188 -0
  206. package/server/lib/guests/storage.js +68 -0
  207. package/server/lib/guests/storage.test.js +59 -0
  208. package/server/lib/guests/updates.js +294 -0
  209. package/server/lib/guests/updates.test.js +334 -0
  210. package/server/lib/guests/upload.js +50 -0
  211. package/server/lib/guests/upload.test.js +131 -0
  212. package/server/lib/linear/DOCUMENTATION.md +1 -1
  213. package/server/lib/message-queue/DOCUMENTATION.md +17 -1
  214. package/server/lib/message-queue/runtime.js +34 -1
  215. package/server/lib/message-queue/runtime.test.js +83 -6
  216. package/server/lib/notifications/DOCUMENTATION.md +1 -1
  217. package/server/lib/notifications/emitter-runtime.js +4 -3
  218. package/server/lib/notifications/routes.js +5 -0
  219. package/server/lib/openchamber-control/DOCUMENTATION.md +6 -0
  220. package/server/lib/openchamber-sessions/routes.js +62 -29
  221. package/server/lib/openchamber-sessions/routes.test.js +96 -0
  222. package/server/lib/opencode/DOCUMENTATION.md +73 -5
  223. package/server/lib/opencode/bootstrap-runtime.js +1 -0
  224. package/server/lib/opencode/core-routes.js +39 -1
  225. package/server/lib/opencode/env-runtime.js +5 -1
  226. package/server/lib/opencode/env-runtime.test.js +72 -0
  227. package/server/lib/opencode/feature-routes-runtime.js +30 -1
  228. package/server/lib/opencode/lifecycle-process.test.js +84 -0
  229. package/server/lib/opencode/lifecycle.js +58 -70
  230. package/server/lib/opencode/lifecycle.test.js +29 -0
  231. package/server/lib/opencode/managed-plugin-config.js +1 -1
  232. package/server/lib/opencode/openchamber-routes.js +72 -14
  233. package/server/lib/opencode/openchamber-routes.test.js +71 -3
  234. package/server/lib/opencode/proxy.js +33 -0
  235. package/server/lib/opencode/session-runtime.js +68 -0
  236. package/server/lib/opencode/session-runtime.test.js +43 -0
  237. package/server/lib/opencode/settings-helpers.js +11 -0
  238. package/server/lib/opencode/settings-helpers.test.js +24 -2
  239. package/server/lib/opencode/settings-normalization-runtime.js +2 -0
  240. package/server/lib/opencode/settings-normalization-runtime.test.js +13 -0
  241. package/server/lib/opencode/settings-registry.json +18 -0
  242. package/server/lib/opencode/settings-runtime.js +36 -9
  243. package/server/lib/opencode/settings-runtime.test.js +158 -2
  244. package/server/lib/opencode/shutdown-runtime.js +3 -0
  245. package/server/lib/opencode/shutdown-runtime.test.js +24 -0
  246. package/server/lib/opencode/theme-archive.js +53 -0
  247. package/server/lib/opencode/theme-catalog.js +171 -0
  248. package/server/lib/opencode/theme-catalog.test.js +125 -0
  249. package/server/lib/opencode/theme-import.test.js +144 -0
  250. package/server/lib/opencode/theme-runtime.js +100 -28
  251. package/server/lib/opencode/theme-runtime.test.js +15 -0
  252. package/server/lib/permission-auto-accept/DOCUMENTATION.md +8 -0
  253. package/server/lib/permission-auto-accept/runtime.js +13 -0
  254. package/server/lib/permission-auto-accept/runtime.test.js +37 -1
  255. package/server/lib/project-context/DOCUMENTATION.md +13 -3
  256. package/server/lib/project-context/runtime.js +8 -2
  257. package/server/lib/project-context/runtime.test.js +54 -0
  258. package/server/lib/projects/DOCUMENTATION.md +2 -1
  259. package/server/lib/projects/project-config.js +40 -11
  260. package/server/lib/projects/project-config.test.js +97 -0
  261. package/server/lib/projects/project-id.js +26 -0
  262. package/server/lib/quota/DOCUMENTATION.md +0 -1
  263. package/server/lib/quota/providers/index.js +0 -7
  264. package/server/lib/realtime-proxy-terminal.test.js +150 -0
  265. package/server/lib/realtime-proxy.js +2 -0
  266. package/server/lib/relay/DOCUMENTATION.md +20 -0
  267. package/server/lib/relay/host-client.js +7 -0
  268. package/server/lib/relay/host-client.test.js +14 -0
  269. package/server/lib/routing/DOCUMENTATION.md +83 -0
  270. package/server/lib/routing/defaults.js +98 -0
  271. package/server/lib/routing/feature-flag.js +11 -0
  272. package/server/lib/routing/history.js +34 -0
  273. package/server/lib/routing/jev.js +98 -0
  274. package/server/lib/routing/routes.http.test.js +100 -0
  275. package/server/lib/routing/routes.js +81 -0
  276. package/server/lib/routing/runtime.js +217 -0
  277. package/server/lib/routing/runtime.test.js +188 -0
  278. package/server/lib/routing/store.js +249 -0
  279. package/server/lib/routing/store.test.js +96 -0
  280. package/server/lib/scheduled-tasks/DOCUMENTATION.md +9 -1
  281. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +28 -0
  282. package/server/lib/scheduled-tasks/runtime.js +14 -4
  283. package/server/lib/scheduled-tasks/runtime.test.js +67 -0
  284. package/server/lib/security/request-security.js +9 -5
  285. package/server/lib/security/request-security.test.js +37 -0
  286. package/server/lib/session-assist/DOCUMENTATION.md +131 -66
  287. package/server/lib/session-assist/context.js +127 -0
  288. package/server/lib/session-assist/context.test.js +132 -0
  289. package/server/lib/session-assist/prompt.js +54 -0
  290. package/server/lib/session-assist/runtime.js +111 -257
  291. package/server/lib/session-assist/runtime.test.js +203 -0
  292. package/server/lib/small-model/DOCUMENTATION.md +14 -2
  293. package/server/lib/small-model/call.js +37 -6
  294. package/server/lib/small-model/call.test.js +182 -0
  295. package/server/lib/terminal/DOCUMENTATION.md +23 -2
  296. package/server/lib/terminal/runtime.js +27 -5
  297. package/server/lib/terminal/runtime.test.js +67 -0
  298. package/server/lib/terminal/shutdown.js +106 -0
  299. package/server/lib/terminal/shutdown.test.js +84 -0
  300. package/server/lib/tts/service.test.js +78 -0
  301. package/server/lib/ui-auth/DOCUMENTATION.md +8 -0
  302. package/server/lib/ui-auth/session-cookie.js +51 -0
  303. package/server/lib/ui-auth/session-cookie.test.js +46 -0
  304. package/server/lib/ui-auth/ui-auth.js +56 -17
  305. package/server/lib/ui-auth/ui-auth.test.js +96 -0
  306. package/server/lib/walkthrough/DOCUMENTATION.md +15 -5
  307. package/server/lib/walkthrough/pr-source.test.js +21 -0
  308. package/server/lib/walkthrough/pull-request.js +7 -4
  309. package/server/lib/walkthrough/pull-request.test.js +16 -0
  310. package/server/lib/walkthrough/routes.js +19 -0
  311. package/server/lib/walkthrough/routes.test.js +26 -0
  312. package/server/lib/walkthrough/sources.js +13 -2
  313. package/server/opencode-worktree-readiness.test.js +130 -0
  314. package/dist/assets/DiffView-3Dn4QHbu.js +0 -4
  315. package/dist/assets/DirtyBranchSwitchDialog-CwOerqVi.js +0 -1
  316. package/dist/assets/FilesView-BdTOpew9.js +0 -17
  317. package/dist/assets/GitView-Dqf9vHJh.js +0 -6
  318. package/dist/assets/LinearIssuesView-CmbeL7I6.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-wVZlx4dd.js +0 -12
  320. package/dist/assets/PierreDiffViewer-DCsydlKs.js +0 -161
  321. package/dist/assets/SettingsView-CcM8pF5O.js +0 -28
  322. package/dist/assets/ToolOutputDialog-BgfTbDZ4.js +0 -15
  323. package/dist/assets/WalkthroughView-BEVUDxuw.js +0 -4
  324. package/dist/assets/appThemeRegistry-BK0dHKa6.js +0 -1
  325. package/dist/assets/de-C8jV7J9y.js +0 -10
  326. package/dist/assets/es-BI6J1M5R.js +0 -16
  327. package/dist/assets/fr-D6oDFt7E.js +0 -14
  328. package/dist/assets/index-BqhiCmYS.js +0 -39
  329. package/dist/assets/index-FqSOBz0J.css +0 -1
  330. package/dist/assets/ja-C_4i5EQu.js +0 -16
  331. package/dist/assets/ko-BFbOTpbq.js +0 -16
  332. package/dist/assets/main-C9ETLKFc.js +0 -2
  333. package/dist/assets/main-CJmb2dF8.js +0 -930
  334. package/dist/assets/markdownCore-DVuZpnli.js +0 -5
  335. package/dist/assets/miniChat-B4Ytoq2n.js +0 -2
  336. package/dist/assets/mobile-BUT6-lo1.js +0 -2
  337. package/dist/assets/patchFileDiff-8j1apICU.js +0 -13
  338. package/dist/assets/pl-COFvT56l.js +0 -16
  339. package/dist/assets/pt-BR-DtVOO2Lj.js +0 -16
  340. package/dist/assets/registerCustomTheme-BBXUCRnG.js +0 -1
  341. package/dist/assets/renderElectronMiniChatApp-B6vUG-vF.js +0 -2
  342. package/dist/assets/renderMobileApp-BNgHraq6.js +0 -25
  343. package/dist/assets/runtimeEndpointReset-B8LYpWvr.js +0 -17
  344. package/dist/assets/scheduledTasksApi-GC4epIYA.js +0 -1
  345. package/dist/assets/shikiHighlight-BJISeZsQ.js +0 -1
  346. package/dist/assets/surface-BvRzS58U.js +0 -1
  347. package/dist/assets/tr-B8Q0Go2l.js +0 -7
  348. package/dist/assets/uk-Dk4a-Fn-.js +0 -16
  349. package/dist/assets/useAppFontEffects-DpFNefUA.js +0 -7284
  350. package/dist/assets/useGitComparison-Dfxaqd0s.js +0 -1
  351. package/dist/assets/useInlineCommentController-CscsE4t-.js +0 -2
  352. package/dist/assets/useRootScrollLock-BaCvuRlT.js +0 -1
  353. package/dist/assets/vendor-framer-motion-livSKyyV.js +0 -1
  354. package/dist/assets/vendor-zod-BPAANqvH.js +0 -39
  355. package/dist/assets/zh-CN-B_BXxG1Q.js +0 -16
  356. package/dist/assets/zh-TW-DLrl1SPZ.js +0 -16
  357. package/server/lib/mcp-reconnect/DOCUMENTATION.md +0 -55
  358. package/server/lib/mcp-reconnect/runtime.js +0 -122
  359. package/server/lib/mcp-reconnect/runtime.test.js +0 -140
  360. package/server/lib/quota/providers/crof.js +0 -96
  361. package/server/lib/quota/providers/crof.test.js +0 -98
  362. /package/dist/assets/{vendor-katex-k3Ppq1Ha.js → vendor-katex-CHD9BdDO.js} +0 -0
package/dist/index.html CHANGED
@@ -509,16 +509,18 @@
509
509
  pointer-events: none;
510
510
  }
511
511
  </style>
512
- <script type="module" crossorigin src="/assets/main-C9ETLKFc.js"></script>
512
+ <script type="module" crossorigin src="/assets/main-CgXMBNvG.js"></script>
513
513
  <link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
514
+ <link rel="modulepreload" crossorigin href="/assets/vendor-zod-CgAdXWZO.js">
514
515
  <link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
515
516
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
516
517
  <link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
517
- <link rel="modulepreload" crossorigin href="/assets/vendor-zod-BPAANqvH.js">
518
518
  <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
519
- <link rel="modulepreload" crossorigin href="/assets/index-BqhiCmYS.js">
519
+ <link rel="modulepreload" crossorigin href="/assets/index-Cbci4i1W.js">
520
520
  <link rel="modulepreload" crossorigin href="/assets/contextPanelEmbeddedChat-CirXEPJq.js">
521
- <link rel="stylesheet" crossorigin href="/assets/index-FqSOBz0J.css">
521
+ <link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
522
+ <link rel="stylesheet" crossorigin href="/assets/index-DqaMWNrC.css">
523
+ <link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
522
524
  </head>
523
525
  <body class="h-full bg-background text-foreground">
524
526
  <div id="root" class="h-full">
@@ -74,15 +74,17 @@
74
74
  }
75
75
  </style>
76
76
 
77
- <script type="module" crossorigin src="/assets/miniChat-B4Ytoq2n.js"></script>
77
+ <script type="module" crossorigin src="/assets/miniChat-CwTmDixi.js"></script>
78
78
  <link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
79
+ <link rel="modulepreload" crossorigin href="/assets/vendor-zod-CgAdXWZO.js">
79
80
  <link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
80
81
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
81
82
  <link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
82
- <link rel="modulepreload" crossorigin href="/assets/vendor-zod-BPAANqvH.js">
83
83
  <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
84
- <link rel="modulepreload" crossorigin href="/assets/index-BqhiCmYS.js">
85
- <link rel="stylesheet" crossorigin href="/assets/index-FqSOBz0J.css">
84
+ <link rel="modulepreload" crossorigin href="/assets/index-Cbci4i1W.js">
85
+ <link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
86
+ <link rel="stylesheet" crossorigin href="/assets/index-DqaMWNrC.css">
87
+ <link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
86
88
  </head>
87
89
  <body class="h-full bg-background text-foreground">
88
90
  <div id="root" class="h-full">
package/dist/mobile.html CHANGED
@@ -63,15 +63,17 @@
63
63
  })();
64
64
  </script>
65
65
 
66
- <script type="module" crossorigin src="/assets/mobile-BUT6-lo1.js"></script>
66
+ <script type="module" crossorigin src="/assets/mobile-CLf2vCtO.js"></script>
67
67
  <link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
68
+ <link rel="modulepreload" crossorigin href="/assets/vendor-zod-CgAdXWZO.js">
68
69
  <link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
69
70
  <link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
70
71
  <link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
71
- <link rel="modulepreload" crossorigin href="/assets/vendor-zod-BPAANqvH.js">
72
72
  <link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
73
- <link rel="modulepreload" crossorigin href="/assets/index-BqhiCmYS.js">
74
- <link rel="stylesheet" crossorigin href="/assets/index-FqSOBz0J.css">
73
+ <link rel="modulepreload" crossorigin href="/assets/index-Cbci4i1W.js">
74
+ <link rel="modulepreload" crossorigin href="/assets/vendor-katex-CHD9BdDO.js">
75
+ <link rel="stylesheet" crossorigin href="/assets/index-DqaMWNrC.css">
76
+ <link rel="stylesheet" crossorigin href="/assets/vendor-katex-Cp579UoJ.css">
75
77
  </head>
76
78
  <body class="h-full bg-background text-foreground">
77
79
  <div id="root" class="h-full"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantran-se/openchamber-web",
3
- "version": "1.23.0",
3
+ "version": "1.24.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./server/index.js",
@@ -13,10 +13,11 @@
13
13
  },
14
14
  "scripts": {
15
15
  "dev": "bun run build:watch",
16
- "dev:server": "OPENCHAMBER_RELAY_HOST=${OPENCHAMBER_RELAY_HOST:-off} bun server/index.js --port ${OPENCHAMBER_PORT:-3001}",
17
- "dev:server:watch": "node ./scripts/dev-server-watch.mjs",
18
- "build": "vite build",
19
- "build:watch": "vite build --watch",
16
+ "dev:server": "bun ../../scripts/build-builtin-extensions.mjs && OPENCHAMBER_RELAY_HOST=${OPENCHAMBER_RELAY_HOST:-off} bun server/index.js --port ${OPENCHAMBER_PORT:-3001}",
17
+ "dev:server:watch": "bun ../../scripts/build-builtin-extensions.mjs && node ./scripts/dev-server-watch.mjs",
18
+ "build": "bun ../../scripts/build-builtin-extensions.mjs && vite build",
19
+ "build:watch": "bun ../../scripts/build-builtin-extensions.mjs && vite build --watch",
20
+ "prepack": "bun ../../scripts/build-builtin-extensions.mjs",
20
21
  "type-check": "tsc --noEmit",
21
22
  "lint": "eslint \"./src/**/*.{ts,tsx}\" --config ../../eslint.config.js",
22
23
  "test": "vitest run",
@@ -25,25 +26,27 @@
25
26
  "dependencies": {
26
27
  "@clack/prompts": "^1.1.0",
27
28
  "@octokit/rest": "^22.0.1",
28
- "@opencode-ai/sdk": "1.18.30",
29
+ "@openchamber/sdk": "workspace:*",
30
+ "@opencode-ai/sdk": "1.18.31",
29
31
  "@simplewebauthn/server": "13.3.1",
30
32
  "bun-pty": "^0.4.5",
31
33
  "compression": "^1.8.1",
32
- "cron-parser": "^4.9.0",
34
+ "cron-parser": "^5.10.1",
33
35
  "express": "^5.1.0",
34
36
  "http-proxy-middleware": "^3.0.5",
35
37
  "jose": "^6.1.3",
36
38
  "jsonc-parser": "^3.3.1",
37
39
  "luxon": "^3.5.0",
38
40
  "node-pty": "1.2.0-beta.12",
39
- "openai": "^4.79.0",
41
+ "openai": "^7.18.0",
40
42
  "qrcode-terminal": "^0.12.0",
41
43
  "reflect-metadata": "^0.2.2",
42
44
  "sherpa-onnx-node": "1.12.28",
43
45
  "simple-git": "^3.36.0",
44
46
  "web-push": "^3.6.7",
45
47
  "ws": "^8.18.3",
46
- "yaml": "^2.8.1"
48
+ "yaml": "^2.8.1",
49
+ "zod": "^4.3.6"
47
50
  },
48
51
  "devDependencies": {
49
52
  "@base-ui/react": "^1.4.0",
@@ -0,0 +1,4 @@
1
+ {
2
+ "version": 1,
3
+ "extensions": []
4
+ }
package/server/index.d.ts CHANGED
@@ -32,6 +32,8 @@ export interface StartWebUiServerOptions {
32
32
  exitOnShutdown?: boolean;
33
33
  uiPassword?: string | null;
34
34
  desktopUpdater?: DesktopUpdater;
35
+ /** App-owned built-in resources outside Electron's ASAR archive. */
36
+ builtInExtensionsDir?: string;
35
37
  }
36
38
 
37
39
  export declare function startWebUiServer(
package/server/index.js CHANGED
@@ -43,6 +43,7 @@ import {
43
43
  createGlobalUiEventBroadcaster,
44
44
  createGlobalMessageStreamHub,
45
45
  createMessageStreamWsRuntime,
46
+ resolveDeltaCoalesceWindowMs,
46
47
  DEFAULT_UPSTREAM_STALL_TIMEOUT_MS,
47
48
  UPSTREAM_STALL_TIMEOUT_CONCURRENT_MS,
48
49
  } from './lib/event-stream/index.js';
@@ -92,7 +93,9 @@ import { createApnsRuntime } from './lib/notifications/apns-runtime.js';
92
93
  import { createNotificationTemplateRuntime } from './lib/notifications/template-runtime.js';
93
94
  import { createPermissionAutoAcceptRuntime } from './lib/permission-auto-accept/runtime.js';
94
95
  import { createMessageQueueRuntime } from './lib/message-queue/runtime.js';
96
+ import { createRoutingRuntime } from './lib/routing/runtime.js';
95
97
  import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
98
+ import { stopAllGuestServices } from './lib/guests/service.js';
96
99
  import { createProjectConfigRuntime } from './lib/projects/project-config.js';
97
100
  import { migrateLegacyUserDirs } from './lib/data-dir-migration.js';
98
101
  import { createProjectContextRuntime } from './lib/project-context/runtime.js';
@@ -112,7 +115,6 @@ import { createDevServerScanner } from './lib/dev-servers/routes.js';
112
115
  import { createDevTunnelRuntime } from './lib/dev-tunnel/runtime.js';
113
116
  import { registerBrowserControlRoutes } from './lib/browser-control/routes.js';
114
117
  import { createSystemPromptRuntime } from './lib/system-prompt/runtime.js';
115
- import { createMcpReconnectRuntime } from './lib/mcp-reconnect/runtime.js';
116
118
  import { createOpenChamberSessionService } from './lib/openchamber-sessions/routes.js';
117
119
  import { createScheduledTaskService } from './lib/scheduled-tasks/service.js';
118
120
  import { createOpenChamberControlService } from './lib/openchamber-control/service.js';
@@ -298,11 +300,12 @@ const themeRuntime = createThemeRuntime({
298
300
  });
299
301
 
300
302
  const readCustomThemesFromDisk = (...args) => themeRuntime.readCustomThemesFromDisk(...args);
303
+ const saveImportedTheme = (...args) => themeRuntime.saveImportedTheme(...args);
304
+ const deleteImportedTheme = (...args) => themeRuntime.deleteImportedTheme(...args);
301
305
 
302
306
  let notificationTemplateRuntime = null;
303
307
  let agentToolRuntime = null;
304
308
  let systemPromptRuntime = null;
305
- let mcpReconnectRuntime = null;
306
309
 
307
310
  const createTimeoutSignal = (...args) => notificationTemplateRuntime.createTimeoutSignal(...args);
308
311
  const formatProjectLabel = (...args) => notificationTemplateRuntime.formatProjectLabel(...args);
@@ -891,6 +894,25 @@ const globalMessageStreamHub = createGlobalMessageStreamHub({
891
894
  buildOpenCodeUrl,
892
895
  getOpenCodeAuthHeaders,
893
896
  upstreamStallTimeoutMs: getUpstreamStallTimeoutMs,
897
+ deltaCoalesceWindowMs: resolveDeltaCoalesceWindowMs(),
898
+ });
899
+
900
+ // OpenChamber-owned events for the UI control stream (SSE) plus the WS fan-out.
901
+ // OpenCode's /global/event proxy cannot carry them.
902
+ const broadcastOpenChamberUiEvent = createGlobalUiEventBroadcaster({
903
+ sseClients: uiOpenChamberEventClients,
904
+ wsClients: uiNotificationWsClients,
905
+ writeSseEvent,
906
+ });
907
+
908
+ // Jev model routing and the permission safety net. Dark unless
909
+ // OPENCHAMBER_ROUTING_ENABLE is set; every failure keeps the user's own model
910
+ // or the auto-accept reply it was asked about.
911
+ const routingRuntime = createRoutingRuntime({
912
+ dataDir: OPENCHAMBER_DATA_DIR,
913
+ buildOpenCodeUrl,
914
+ getOpenCodeAuthHeaders,
915
+ broadcastGlobalUiEvent: broadcastOpenChamberUiEvent,
894
916
  });
895
917
 
896
918
  const permissionAutoAcceptRuntime = createPermissionAutoAcceptRuntime({
@@ -900,6 +922,8 @@ const permissionAutoAcceptRuntime = createPermissionAutoAcceptRuntime({
900
922
  readSettingsFromDiskMigrated,
901
923
  persistSettings,
902
924
  broadcastGlobalUiEvent,
925
+ evaluatePermission: (permission, directory) => routingRuntime.evaluatePermission(permission, directory),
926
+ onPermissionReplied: (permissionId) => routingRuntime.forgetPermission(permissionId),
903
927
  });
904
928
  permissionAutoAcceptRuntime.start();
905
929
  notificationTriggerRuntime.setGetIsSessionAutoAccepting(
@@ -913,13 +937,8 @@ const messageQueueRuntime = createMessageQueueRuntime({
913
937
  buildOpenCodeUrl,
914
938
  getOpenCodeAuthHeaders,
915
939
  sessionKnowledgeRuntime,
916
- // OpenCode's /global/event SSE proxy cannot carry OpenChamber-owned events.
917
- // Use the shared control stream for SSE clients and the existing WS fan-out.
918
- broadcastGlobalUiEvent: createGlobalUiEventBroadcaster({
919
- sseClients: uiOpenChamberEventClients,
920
- wsClients: uiNotificationWsClients,
921
- writeSseEvent,
922
- }),
940
+ broadcastGlobalUiEvent: broadcastOpenChamberUiEvent,
941
+ resolvePromptBody: (body, target) => routingRuntime.resolvePromptBody(body, target),
923
942
  onPromptSent: (sessionId) => sessionRuntime.markUserMessageSent(sessionId),
924
943
  dataDir: OPENCHAMBER_DATA_DIR,
925
944
  });
@@ -1256,14 +1275,11 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
1256
1275
  ? await (agentToolRuntime?.prepareManagedOpenCodeEnv({ includeControl, includeWeb, includeMemory }) || {})
1257
1276
  : {};
1258
1277
 
1259
- // Each managed plugin appends itself to the config the previous one produced.
1260
- let configContent = managedEnv.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT;
1261
1278
  if (settings?.optimizeSystemPrompt === true) {
1262
- ({ OPENCODE_CONFIG_CONTENT: configContent } = await systemPromptRuntime.prepareManagedOpenCodeEnv(configContent));
1279
+ const configContent = managedEnv.OPENCODE_CONFIG_CONTENT ?? process.env.OPENCODE_CONFIG_CONTENT;
1280
+ Object.assign(managedEnv, await systemPromptRuntime.prepareManagedOpenCodeEnv(configContent));
1263
1281
  }
1264
- // Always on for managed OpenCode: it only retries servers OpenCode gave up on.
1265
- const mcpReconnectEnv = await mcpReconnectRuntime.prepareManagedOpenCodeEnv(configContent);
1266
- return { ...managedEnv, ...mcpReconnectEnv };
1282
+ return managedEnv;
1267
1283
  },
1268
1284
  });
1269
1285
 
@@ -1350,14 +1366,14 @@ const resolveMemoryProjectId = createMemoryProjectResolver({
1350
1366
  });
1351
1367
 
1352
1368
  /**
1353
- * Tells open panels that the agent changed what it remembers, so what it just
1369
+ * Tells open panels that the service changed what it remembers, so what it just
1354
1370
  * stored is visible without reopening anything.
1355
1371
  */
1356
1372
  const emitAgentMemoryChangedEvent = (event) => {
1357
1373
  for (const client of uiOpenChamberEventClients) {
1358
1374
  try {
1359
1375
  writeSseEvent(client, {
1360
- type: 'openchamber:agent-memory-changed',
1376
+ type: 'openchamber:service-memory-changed',
1361
1377
  properties: {
1362
1378
  scope: event.scope,
1363
1379
  ...(event.projectId ? { projectId: event.projectId } : {}),
@@ -1383,6 +1399,7 @@ const openChamberSessionService = createOpenChamberSessionService({
1383
1399
  waitForOpenCodeReady,
1384
1400
  emitSessionCreatedEvent,
1385
1401
  sessionKnowledgeRuntime,
1402
+ resolvePromptBody: (body, target) => routingRuntime.resolvePromptBody(body, target),
1386
1403
  });
1387
1404
  // Browser actions are published to whichever OpenChamber clients are connected;
1388
1405
  // the one owning the browser panel answers. `emitRequest` returns the number of
@@ -1531,17 +1548,14 @@ async function main(options = {}) {
1531
1548
  const address = server?.address?.();
1532
1549
  return typeof address === 'object' && address ? address.port : null;
1533
1550
  },
1551
+ // A pipe listener reports a string here, which has no address to bind back to.
1552
+ getActiveHost: () => server?.address?.()?.address ?? null,
1534
1553
  });
1535
1554
  systemPromptRuntime = createSystemPromptRuntime({
1536
1555
  fsPromises,
1537
1556
  path,
1538
1557
  dataDir: OPENCHAMBER_DATA_DIR,
1539
1558
  });
1540
- mcpReconnectRuntime = createMcpReconnectRuntime({
1541
- fsPromises,
1542
- path,
1543
- dataDir: OPENCHAMBER_DATA_DIR,
1544
- });
1545
1559
 
1546
1560
  // Pairing transports advertised to the create-device dialog. LAN reachability is
1547
1561
  // derived from the SERVER's actual bind (a wildcard bind → the machine's LAN IP;
@@ -1943,6 +1957,8 @@ async function main(options = {}) {
1943
1957
  resolveGitBinaryForSpawn,
1944
1958
  createFsSearchRuntime: createFsSearchRuntimeFactory,
1945
1959
  openchamberDataDir: OPENCHAMBER_DATA_DIR,
1960
+ openchamberVersion: OPENCHAMBER_VERSION,
1961
+ builtInExtensionsDir: options.builtInExtensionsDir,
1946
1962
  openchamberUserConfigRoot: OPENCHAMBER_USER_CONFIG_ROOT,
1947
1963
  managedChatsRoot: OPENCHAMBER_CHATS_DIR,
1948
1964
  normalizeDirectoryPath,
@@ -1950,6 +1966,8 @@ async function main(options = {}) {
1950
1966
  resolveOptionalProjectDirectory,
1951
1967
  validateDirectoryPath,
1952
1968
  readCustomThemesFromDisk,
1969
+ saveImportedTheme,
1970
+ deleteImportedTheme,
1953
1971
  refreshOpenCodeAfterConfigChange,
1954
1972
  getOpenCodeResolutionSnapshot,
1955
1973
  getOpenCodeUpgradeCapability,
@@ -1983,6 +2001,7 @@ async function main(options = {}) {
1983
2001
  writeSseEvent,
1984
2002
  permissionAutoAcceptRuntime,
1985
2003
  messageQueueRuntime,
2004
+ routingRuntime,
1986
2005
  });
1987
2006
 
1988
2007
  const startupPipelineResult = await startupPipelineRuntime.run({
@@ -2087,7 +2106,7 @@ async function main(options = {}) {
2087
2106
  port: managed ? openCodePort : null,
2088
2107
  };
2089
2108
  },
2090
- stop: (shutdownOptions = {}) => {
2109
+ stop: async (shutdownOptions = {}) => {
2091
2110
  realtimeProxyRuntime.stop();
2092
2111
  clearInterval(relayReconcileTimer);
2093
2112
  try {
@@ -2100,6 +2119,11 @@ async function main(options = {}) {
2100
2119
  } catch {
2101
2120
  // best-effort shutdown of the dictation worker
2102
2121
  }
2122
+ // Guest services are child processes; leaving before SIGTERM lands
2123
+ // (and the SIGKILL fallback fires) orphans them on the user's machine.
2124
+ await stopAllGuestServices().catch(() => {
2125
+ // best-effort teardown of guest service processes
2126
+ });
2103
2127
  return gracefulShutdown({ exitProcess: shutdownOptions.exitProcess ?? false });
2104
2128
  }
2105
2129
  };
@@ -4,7 +4,10 @@
4
4
  * What the agent has learned and chose to keep, in two scopes:
5
5
  *
6
6
  * - **project** — `<projectsDir>/<projectId>/memory.json`. How this codebase
7
- * works, what was decided, where things live.
7
+ * works, what was decided, where things live. `<projectId>` is the bounded
8
+ * stem `projectConfigFileStemOf` gives the id (the id itself up to 200
9
+ * characters, `path_sha256_<digest>` beyond), the same folder that holds
10
+ * `project-context`'s `context.json` and sits beside the config file.
8
11
  * - **global** — `<userConfigRoot>/memory.json`. Who the user is and how they
9
12
  * want to be worked with. It belongs to no project, so it cannot live under
10
13
  * one.
@@ -51,6 +54,7 @@ const PROJECT_MEMORY_MAX_ITEMS = 200;
51
54
  */
52
55
  const MEMORY_TYPES = new Set(['fact', 'preference', 'reference']);
53
56
 
57
+ import { projectConfigFileStemOf } from '../projects/project-id.js';
54
58
  import { findThreatPattern } from './threat-patterns.js';
55
59
 
56
60
  const PROJECT_ID_PATTERN = /^[a-zA-Z0-9._:-]+$/;
@@ -211,7 +215,8 @@ export const createAgentMemoryRuntime = (deps) => {
211
215
  return {
212
216
  scope: 'project',
213
217
  key: `project:${projectId}`,
214
- filePath: path.join(projectsDirPath, projectId, 'memory.json'),
218
+ // Same bounded folder name as `project-context` and the config file.
219
+ filePath: path.join(projectsDirPath, projectConfigFileStemOf(projectId), 'memory.json'),
215
220
  };
216
221
  }
217
222
  throw new Error('scope is required');
@@ -3,6 +3,7 @@ import fsPromises from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
 
6
+ import { createProjectIdFromPath, projectConfigFileStemOf } from '../projects/project-id.js';
6
7
  import { createAgentMemoryRuntime } from './runtime.js';
7
8
 
8
9
  const PROJECT_ID = 'path_dGVzdA';
@@ -48,6 +49,27 @@ describe('scope resolution', () => {
48
49
  await fsPromises.access(projectPath());
49
50
  });
50
51
 
52
+ test('a project id too long for a folder name is stored under the bounded stem', async () => {
53
+ // A checkout nested deep enough that its `path_<base64url>` id is longer
54
+ // than a folder name may be; the store lands under the bounded stem the
55
+ // config file and project context use, instead of failing with ENAMETOOLONG.
56
+ const longProjectId = createProjectIdFromPath(`/private/tmp/claude-501/${'segment-'.repeat(18)}/scratchpad/evidence/demo-repo`);
57
+ const stem = projectConfigFileStemOf(longProjectId);
58
+ expect(longProjectId.length).toBeGreaterThan(240);
59
+ expect(stem.startsWith('path_sha256_')).toBe(true);
60
+ const target = { scope: 'project', projectId: longProjectId };
61
+
62
+ expect((await runtime.read(target)).entries).toEqual([]);
63
+ await runtime.create(target, { title: 'Uses bun', body: 'Tests run with bun test.' });
64
+
65
+ expect((await runtime.read(target)).entries.map((e) => e.title)).toEqual(['Uses bun']);
66
+ expect((await runtime.readAll(longProjectId)).project.map((e) => e.title)).toEqual(['Uses bun']);
67
+ await fsPromises.access(path.join(rootDir, 'config', 'projects', stem, 'memory.json'));
68
+ expect(await fsPromises.readdir(path.join(rootDir, 'config', 'projects'))).toEqual([stem]);
69
+ // A short id keeps naming its folder itself.
70
+ expect(runtime.resolveTarget(PROJECT).filePath).toBe(projectPath());
71
+ });
72
+
51
73
  test('rejects an unknown scope', async () => {
52
74
  await expect(runtime.read({ scope: 'nope' })).rejects.toThrow('scope is required');
53
75
  });
@@ -30,8 +30,10 @@ both settings are `false`.
30
30
  2. `prepareManagedOpenCodeEnv()` materializes the plugin under
31
31
  `<openchamber-data-dir>/agent-tool/` and appends its `file://` URL to
32
32
  `OPENCODE_CONFIG_CONTENT` without replacing existing plugin entries.
33
- 3. A random per-child token and loopback callback URL are added only to the
34
- managed OpenCode child environment.
33
+ 3. A random per-child token and callback URL are added only to the managed
34
+ OpenCode child environment. The URL points at loopback, except when the
35
+ listener is bound to one concrete address (`--host <ip>`): that socket does
36
+ not answer on loopback, so the URL uses the bound address instead.
35
37
  4. The plugin calls `POST /api/openchamber/agent-tool` with its typed input and
36
38
  OpenCode's authoritative session directory.
37
39
  5. The route delegates the fixed action allowlist directly to the shared
@@ -48,6 +50,9 @@ both settings are `false`.
48
50
  - The tool exposes one shared parameter object rather than repeating parameters
49
51
  in a large per-action union. Action descriptions carry only required inputs,
50
52
  defaults, or one non-obvious semantic detail.
53
+ - The action schema carries `oneOf` and no `enum`. A node combining `enum` and
54
+ `oneOf` is valid JSON Schema, but some OpenAI-compatible gateways reject it
55
+ and answer with an empty completion instead of an error.
51
56
  - Obvious fields rely on their names and JSON types. Parameter descriptions are
52
57
  reserved for formats, dependencies, scope, and behavior that cannot be safely
53
58
  inferred from the field name.
@@ -69,10 +74,18 @@ both settings are `false`.
69
74
 
70
75
  ## Security invariants
71
76
 
72
- - The callback accepts loopback requests only and requires the current
73
- per-child bearer token using a timing-safe comparison.
77
+ - The callback accepts same-machine requests only and requires the current
78
+ per-child bearer token using a timing-safe comparison. Same-machine means a
79
+ loopback source, or, for a listener bound to one concrete address, a source
80
+ equal to that address: the OS sources a local connection to `<ip>` from
81
+ `<ip>`. A wildcard bind keeps the loopback-only rule, and another machine on
82
+ the network always arrives with its own address.
74
83
  - The token is never persisted, logged, returned to the UI, or written into
75
84
  the materialized plugin.
85
+ - The plugin adds the callback host to `NO_PROXY`/`no_proxy` inside the managed
86
+ child when it loads. Without that, an `HTTP_PROXY` in the child's environment
87
+ would receive a non-loopback callback, token included, because `fetch` has no
88
+ per-request way to skip the environment proxy.
76
89
  - Inputs map to a fixed action and parameter allowlist. There is no arbitrary
77
90
  CLI, shell, route, or URL forwarding.
78
91
  - Session/worktree deletion and project-path registration are not exposed.
@@ -129,11 +129,24 @@ const createResult = ({ ok, action, data, error, exitCode }) => ({
129
129
  ...(Number.isInteger(exitCode) ? { exitCode } : {}),
130
130
  });
131
131
 
132
+ // Node reports an IPv4 peer on a dual-stack socket as `::ffff:<ipv4>`.
133
+ const normalizeAddress = (value) => {
134
+ const address = (asNonEmptyString(value) || '').toLowerCase();
135
+ return address.startsWith('::ffff:') ? address.slice('::ffff:'.length) : address;
136
+ };
137
+
132
138
  const isLoopbackAddress = (value) => {
133
- const address = typeof value === 'string' ? value.toLowerCase() : '';
134
- return address === '127.0.0.1'
135
- || address === '::1'
136
- || address === '::ffff:127.0.0.1';
139
+ const address = normalizeAddress(value);
140
+ return address === '127.0.0.1' || address === '::1';
141
+ };
142
+
143
+ const WILDCARD_ADDRESSES = new Set(['0.0.0.0', '::']);
144
+
145
+ // A wildcard listener answers on loopback. A listener bound to one concrete
146
+ // address answers only there, so that address is the only way back in.
147
+ const resolveConcreteBoundAddress = (value) => {
148
+ const address = normalizeAddress(value);
149
+ return address && !WILDCARD_ADDRESSES.has(address) ? address : null;
137
150
  };
138
151
 
139
152
  /**
@@ -143,11 +156,16 @@ const isLoopbackAddress = (value) => {
143
156
  * set, the inputs and the description differ. Generating them from one template
144
157
  * keeps the transport, metadata and failure handling identical, which is what
145
158
  * the caller depends on.
159
+ *
160
+ * The action schema carries `oneOf` only. A node combining `enum` and `oneOf`
161
+ * is valid JSON Schema, but some OpenAI-compatible gateways reject it and
162
+ * answer with an empty completion instead of an error, and the `oneOf` branches
163
+ * are what carry the per-action descriptions the model reads.
146
164
  */
147
- const createToolEntry = ({ name, description, actions, definitions, parameters }) => String.raw` ${name}: {
165
+ const createToolEntry = ({ name, description, definitions, parameters }) => String.raw` ${name}: {
148
166
  description: ${JSON.stringify(description)},
149
167
  args: {
150
- action: { type: "string", enum: ${JSON.stringify(actions)}, oneOf: ${JSON.stringify(definitions.map((entry) => ({ const: entry.action, description: entry.description })))}, description: "OpenChamber action to perform" },
168
+ action: { type: "string", oneOf: ${JSON.stringify(definitions.map((entry) => ({ const: entry.action, description: entry.description })))}, description: "OpenChamber action to perform" },
151
169
  parameters: { type: "object", properties: ${JSON.stringify(parameters)}, additionalProperties: false, description: "Inputs for the action; use an empty object when none are needed" },
152
170
  },
153
171
  async execute(input, context) {
@@ -221,7 +239,6 @@ const createPluginSource = ({ includeControl, includeWeb, includeMemory }) => {
221
239
  entries.push(createToolEntry({
222
240
  name: 'openchamber',
223
241
  description: CONTROL_TOOL_DESCRIPTION,
224
- actions: OPENCHAMBER_AGENT_TOOL_ACTIONS,
225
242
  definitions: OPENCHAMBER_AGENT_TOOL_ACTION_DEFINITIONS,
226
243
  parameters: CONTROL_PARAMETER_PROPERTIES,
227
244
  }));
@@ -230,7 +247,6 @@ const createPluginSource = ({ includeControl, includeWeb, includeMemory }) => {
230
247
  entries.push(createToolEntry({
231
248
  name: 'openchamber_web',
232
249
  description: WEB_TOOL_DESCRIPTION,
233
- actions: OPENCHAMBER_WEB_ACTIONS,
234
250
  definitions: OPENCHAMBER_WEB_ACTION_DEFINITIONS,
235
251
  parameters: WEB_PARAMETER_PROPERTIES,
236
252
  }));
@@ -239,16 +255,33 @@ const createPluginSource = ({ includeControl, includeWeb, includeMemory }) => {
239
255
  entries.push(createToolEntry({
240
256
  name: 'openchamber_memory',
241
257
  description: MEMORY_TOOL_DESCRIPTION,
242
- actions: OPENCHAMBER_MEMORY_ACTIONS,
243
258
  definitions: OPENCHAMBER_MEMORY_ACTION_DEFINITIONS,
244
259
  parameters: MEMORY_PARAMETER_PROPERTIES,
245
260
  }));
246
261
  }
247
262
 
248
- return `export const OpenChamberPlugin = async () => ({
249
- tool: {
250
- ${entries.join('')} },
251
- })
263
+ // The callback carries the per-child token over plain HTTP. With a proxy in
264
+ // the child's environment, fetch would hand a non-loopback callback, token
265
+ // included, to that proxy, and no per-request option turns that off. The
266
+ // exemption is added inside the child because only there is the final
267
+ // NO_PROXY, merged from the shell and server environments, visible.
268
+ return `const exemptCallbackFromProxy = () => {
269
+ const endpoint = process.env.OPENCHAMBER_AGENT_TOOL_URL
270
+ if (!endpoint || !URL.canParse(endpoint)) return
271
+ const host = new URL(endpoint).hostname.replace(/^\\[|\\]$/g, "")
272
+ for (const key of ["NO_PROXY", "no_proxy"]) {
273
+ const entries = (process.env[key] || "").split(",").map((entry) => entry.trim()).filter(Boolean)
274
+ if (!entries.includes(host)) process.env[key] = [...entries, host].join(",")
275
+ }
276
+ }
277
+
278
+ export const OpenChamberPlugin = async () => {
279
+ exemptCallbackFromProxy()
280
+ return {
281
+ tool: {
282
+ ${entries.join('')} },
283
+ }
284
+ }
252
285
  `;
253
286
  };
254
287
 
@@ -259,6 +292,7 @@ export const createAgentToolRuntime = (dependencies) => {
259
292
  path,
260
293
  dataDir,
261
294
  getActivePort,
295
+ getActiveHost = () => null,
262
296
  executeAction,
263
297
  env = process.env,
264
298
  } = dependencies;
@@ -266,6 +300,8 @@ export const createAgentToolRuntime = (dependencies) => {
266
300
  const pluginPath = path.join(pluginDirectory, 'openchamber-plugin.js');
267
301
  let activeToken = null;
268
302
 
303
+ const getConcreteBoundAddress = () => resolveConcreteBoundAddress(getActiveHost());
304
+
269
305
  const prepareManagedOpenCodeEnv = async ({ includeControl = true, includeWeb = true, includeMemory = true } = {}) => {
270
306
  const port = getActivePort();
271
307
  if (!Number.isInteger(port) || port <= 0) {
@@ -278,15 +314,26 @@ export const createAgentToolRuntime = (dependencies) => {
278
314
  await fsPromises.writeFile(pluginPath, createPluginSource({ includeControl, includeWeb, includeMemory }), { mode: 0o600 });
279
315
  activeToken = crypto.randomBytes(32).toString('base64url');
280
316
  const pluginUrl = pathToFileURL(pluginPath).href;
317
+ const callbackAddress = getConcreteBoundAddress() || '127.0.0.1';
318
+ const callbackHost = callbackAddress.includes(':') ? `[${callbackAddress}]` : callbackAddress;
281
319
  return {
282
320
  OPENCODE_CONFIG_CONTENT: appendManagedPlugin(env.OPENCODE_CONFIG_CONTENT, pluginUrl, 'managed tool'),
283
- OPENCHAMBER_AGENT_TOOL_URL: `http://127.0.0.1:${port}/api/openchamber/agent-tool`,
321
+ OPENCHAMBER_AGENT_TOOL_URL: `http://${callbackHost}:${port}/api/openchamber/agent-tool`,
284
322
  OPENCHAMBER_AGENT_TOOL_TOKEN: activeToken,
285
323
  };
286
324
  };
287
325
 
326
+ // The managed child runs on this machine. Reaching a listener bound to one
327
+ // concrete address makes the OS source the connection from that same address,
328
+ // so it stands in for loopback there; any other machine arrives as itself.
329
+ const isSameMachineAddress = (value) => {
330
+ if (isLoopbackAddress(value)) return true;
331
+ const boundAddress = getConcreteBoundAddress();
332
+ return boundAddress !== null && normalizeAddress(value) === boundAddress;
333
+ };
334
+
288
335
  const authorize = (req) => {
289
- if (!activeToken || !isLoopbackAddress(req.socket?.remoteAddress)) return false;
336
+ if (!activeToken || !isSameMachineAddress(req.socket?.remoteAddress)) return false;
290
337
  const header = asNonEmptyString(req.headers?.authorization);
291
338
  if (!header?.startsWith('Bearer ')) return false;
292
339
  const provided = Buffer.from(header.slice(7));