@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.10

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 (328) hide show
  1. package/README.md +13 -7
  2. package/bin/cli-opencode.test.js +208 -0
  3. package/bin/cli.js +62 -9
  4. package/bin/lib/cli-startup.js +105 -2
  5. package/bin/lib/cli-startup.test.js +179 -0
  6. package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-CzTtVMnI.js} +1 -1
  7. package/dist/assets/AssistantView-Cg1qQvVe.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-hOi72Wdx.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-DCqrQkkz.js} +1 -1
  10. package/dist/assets/Icon-B8fSTLdP.js +1 -0
  11. package/dist/assets/MarkdownRenderer-Cw74oVxV.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-YM7iRmzK.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-BDy_oGJQ.js +3 -0
  14. package/dist/assets/MobileDetailNavigation-iX_hb_Xt.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-CAMUupwR.js} +2 -2
  16. package/dist/assets/MobileSurface-CHbdPcLH.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-Ci1ODFFX.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-O4cGdz5p.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-CRfGtcmG.js +2 -0
  20. package/dist/assets/SettingsView-Dg8BtTV1.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-B8d8qIQ7.js} +1 -1
  22. package/dist/assets/TerminalView-CMiw3s_c.js +8 -0
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-ByaQYRTp.js} +2 -2
  24. package/dist/assets/appThemeRegistry-BGGHio8T.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-CI7Q-Zw_.js → button-DIIrsMjW.js} +1 -1
  27. package/dist/assets/{collapsible-D5Phaykh.js → collapsible-CnvUeYst.js} +1 -1
  28. package/dist/assets/{context-menu-CubSDW2g.js → context-menu-BGBUpZEQ.js} +1 -1
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-JGFqhlaM.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-CHEwrI93.js} +1 -1
  31. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-rvn7_qBM.js} +1 -1
  32. package/dist/assets/{dialog-B6pnIYmp.js → dialog-CQ63sc9E.js} +1 -1
  33. package/dist/assets/es-Dsmc0A_3.js +15 -0
  34. package/dist/assets/fileMentionTouchSelection-BzzcWJCi.js +3 -0
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-DN6gbLhM.js} +1 -1
  36. package/dist/assets/fr-C_ZlZDI2.js +13 -0
  37. package/dist/assets/gitApi-BF8vuoZc.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-BMztRu0c.js} +1 -1
  39. package/dist/assets/{input-D-__fwRi.js → input-BVu-iykl.js} +1 -1
  40. package/dist/assets/insertionBoundaries-EcAfGT2D.js +1 -0
  41. package/dist/assets/ja-BqqTaWTi.js +15 -0
  42. package/dist/assets/ko-DP2XPM37.js +15 -0
  43. package/dist/assets/main-B7t9lavz.js +2 -0
  44. package/dist/assets/main-D2BzpnUt.css +1 -0
  45. package/dist/assets/main-DWP-prNB.js +169 -0
  46. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  47. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-D9-B1wdY.js} +1 -1
  48. package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
  49. package/dist/assets/mcpQueries-CfXrAkuU.js +1 -0
  50. package/dist/assets/miniChat-DgOrEQbN.js +2 -0
  51. package/dist/assets/mobile-Co_ZOHkZ.js +2 -0
  52. package/dist/assets/multirun-DelyUO6k.js +4 -0
  53. package/dist/assets/persistence-D0y7226-.js +3 -0
  54. package/dist/assets/pl-BCf5uLlK.js +15 -0
  55. package/dist/assets/platform-CurqO28D.js +6 -0
  56. package/dist/assets/{projectMeta-S1hJ7oBp.js → projectMeta-CPElzJ0n.js} +2 -2
  57. package/dist/assets/pt-BR-W09Q2pvn.js +15 -0
  58. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  59. package/dist/assets/renderElectronMiniChatApp-pOE93w86.js +2 -0
  60. package/dist/assets/renderMobileApp-F7ZSLcw8.js +28 -0
  61. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  62. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-DSS0KxG3.js} +1 -1
  63. package/dist/assets/runtimeEndpointReset-x2Wgx57R.js +149 -0
  64. package/dist/assets/sessionChangeSummary-BUGSYcHp.js +2 -0
  65. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-Bss8M20i.js} +1 -1
  66. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-CE7W7lJ9.js} +1 -1
  67. package/dist/assets/src-lS1keDVV.css +2 -0
  68. package/dist/assets/store-jaP79mqI.js +15 -0
  69. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-mDR2jXTo.js} +1 -1
  70. package/dist/assets/textarea-XD2oX_Ee.js +1 -0
  71. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-DYeQ_Hvy.js} +1 -1
  72. package/dist/assets/uk-BgFzz8m2.js +15 -0
  73. package/dist/assets/{url-_z7evzdK.js → url-CGbJbiJq.js} +1 -1
  74. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-BAWHZ-t0.js} +64 -45
  75. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
  76. package/dist/assets/useDesktopWindowControlsLayout-hN_nLb7T.js +1 -0
  77. package/dist/assets/useEffectiveDirectory-Do9DQ453.js +1 -0
  78. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-Bjx8zkGM.js} +1 -1
  79. package/dist/assets/useMobileNavigationStore-BsY_gi_K.js +414 -0
  80. package/dist/assets/useSessionAutoCleanup-C4g5KmDf.js +2 -0
  81. package/dist/assets/useUIStore-syVmbuDA.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-B5QXxBok.js} +2 -2
  83. package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
  84. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
  85. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  86. package/dist/assets/zh-CN-BhqSLgUY.js +15 -0
  87. package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
  88. package/dist/index.html +12 -12
  89. package/dist/mini-chat.html +12 -12
  90. package/dist/mobile.html +12 -12
  91. package/package.json +3 -2
  92. package/server/index.js +423 -20
  93. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  94. package/server/lib/assistants/assign.js +2 -1
  95. package/server/lib/assistants/contact-store.js +37 -19
  96. package/server/lib/assistants/contracts.js +11 -1
  97. package/server/lib/assistants/projection-history.test.js +454 -0
  98. package/server/lib/assistants/routes.js +11 -5
  99. package/server/lib/assistants/service.js +850 -294
  100. package/server/lib/assistants/service.test.js +588 -274
  101. package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
  102. package/server/lib/browser-provider/contract.js +312 -0
  103. package/server/lib/browser-provider/host.js +421 -0
  104. package/server/lib/browser-provider/host.test.js +304 -0
  105. package/server/lib/browser-provider/routes.js +122 -0
  106. package/server/lib/browser-provider/routes.test.js +120 -0
  107. package/server/lib/browser-provider/selection-store.js +43 -0
  108. package/server/lib/browser-provider/selection-store.test.js +35 -0
  109. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  110. package/server/lib/conversations/service.js +87 -57
  111. package/server/lib/conversations/service.test.js +132 -100
  112. package/server/lib/conversations/validation.js +11 -1
  113. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  114. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  115. package/server/lib/event-stream/global-hub.js +51 -5
  116. package/server/lib/event-stream/reasoning-projection.js +16 -1
  117. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  118. package/server/lib/event-stream/runtime.js +57 -20
  119. package/server/lib/event-stream/runtime.test.js +112 -0
  120. package/server/lib/llm/DOCUMENTATION.md +95 -84
  121. package/server/lib/llm/catalog.js +111 -45
  122. package/server/lib/llm/catalog.test.js +121 -59
  123. package/server/lib/llm/completions.js +41 -12
  124. package/server/lib/llm/completions.test.js +45 -4
  125. package/server/lib/llm/generate.js +516 -453
  126. package/server/lib/llm/generate.test.js +705 -484
  127. package/server/lib/llm/routes.js +8 -4
  128. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  129. package/server/lib/message-queue/opencode-adapter.js +191 -104
  130. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/message-queue/runtime.test.js +6 -6
  133. package/server/lib/message-queue/worker.js +7 -1
  134. package/server/lib/notifications/DOCUMENTATION.md +3 -1
  135. package/server/lib/notifications/runtime.js +133 -31
  136. package/server/lib/notifications/runtime.test.js +103 -0
  137. package/server/lib/notifications/template-runtime.js +130 -38
  138. package/server/lib/notifications/template-runtime.test.js +96 -0
  139. package/server/lib/opencode/DOCUMENTATION.md +111 -16
  140. package/server/lib/opencode/agent-document.d.ts +28 -0
  141. package/server/lib/opencode/agent-document.js +280 -0
  142. package/server/lib/opencode/agent-document.test.js +137 -0
  143. package/server/lib/opencode/agents.js +96 -0
  144. package/server/lib/opencode/auth-state-runtime.js +5 -0
  145. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  146. package/server/lib/opencode/config-entity-routes.js +117 -103
  147. package/server/lib/opencode/config-entity-routes.test.js +148 -10
  148. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  149. package/server/lib/opencode/ensure-cli.d.ts +8 -0
  150. package/server/lib/opencode/ensure-cli.js +238 -0
  151. package/server/lib/opencode/ensure-cli.test.js +119 -0
  152. package/server/lib/opencode/env-runtime.js +208 -111
  153. package/server/lib/opencode/env-runtime.test.js +211 -14
  154. package/server/lib/opencode/feature-routes-runtime.js +90 -5
  155. package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
  156. package/server/lib/opencode/http-server-connections.js +98 -0
  157. package/server/lib/opencode/http-server-connections.test.js +321 -0
  158. package/server/lib/opencode/lifecycle.js +539 -95
  159. package/server/lib/opencode/lifecycle.test.js +698 -6
  160. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  161. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  162. package/server/lib/opencode/network-runtime.js +28 -16
  163. package/server/lib/opencode/network-runtime.test.js +68 -4
  164. package/server/lib/opencode/openchamber-routes.js +7 -1
  165. package/server/lib/opencode/opencode2-pin.js +139 -0
  166. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  167. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  168. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  169. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  170. package/server/lib/opencode/plugin-compatibility.js +15 -0
  171. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  172. package/server/lib/opencode/plugin-routes.js +3 -18
  173. package/server/lib/opencode/plugin-routes.test.js +9 -9
  174. package/server/lib/opencode/plugins.js +4 -3
  175. package/server/lib/opencode/plugins.test.js +12 -0
  176. package/server/lib/opencode/provider-catalog.js +58 -12
  177. package/server/lib/opencode/provider-catalog.test.js +48 -0
  178. package/server/lib/opencode/proxy.js +373 -28
  179. package/server/lib/opencode/proxy.test.js +353 -0
  180. package/server/lib/opencode/routes.js +333 -76
  181. package/server/lib/opencode/routes.test.js +315 -50
  182. package/server/lib/opencode/runtime-contract.js +401 -0
  183. package/server/lib/opencode/runtime-contract.test.js +214 -0
  184. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  185. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  186. package/server/lib/opencode/server-utils-runtime.js +35 -0
  187. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  188. package/server/lib/opencode/shared-service.js +147 -0
  189. package/server/lib/opencode/shared-service.test.js +93 -0
  190. package/server/lib/opencode/shared.js +3 -3
  191. package/server/lib/opencode/shutdown-runtime.js +15 -6
  192. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  193. package/server/lib/opencode/skill-routes.js +26 -40
  194. package/server/lib/opencode/skill-routes.test.js +71 -7
  195. package/server/lib/opencode/skills.test.js +15 -0
  196. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  197. package/server/lib/opencode/static-routes-runtime.js +3 -1
  198. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  199. package/server/lib/opencode/upgrade-screen.d.ts +30 -0
  200. package/server/lib/opencode/upgrade-screen.js +299 -0
  201. package/server/lib/opencode/upgrade-screen.test.js +196 -0
  202. package/server/lib/opencode/v1-migration-gate.js +157 -0
  203. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  204. package/server/lib/opencode/v2-client.js +60 -0
  205. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  206. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  207. package/server/lib/opencode/watcher.js +2 -1
  208. package/server/lib/opencode/watcher.test.js +1 -1
  209. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  210. package/server/lib/permission-auto-accept/runtime.js +14 -7
  211. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  212. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  213. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  214. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  215. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  216. package/server/lib/scheduled-tasks/routes.js +27 -0
  217. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  218. package/server/lib/scheduled-tasks/runtime.js +381 -310
  219. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  220. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  221. package/server/lib/session-goal/capability.js +57 -0
  222. package/server/lib/session-goal/capability.test.js +33 -0
  223. package/server/lib/session-goal/routes.js +240 -2
  224. package/server/lib/session-goal/routes.test.js +211 -0
  225. package/server/lib/session-goal/runtime.js +1617 -113
  226. package/server/lib/session-goal/runtime.test.js +1500 -70
  227. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  228. package/server/lib/session-index/event-ingest.js +89 -16
  229. package/server/lib/session-index/event-ingest.test.js +73 -0
  230. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  231. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  232. package/server/lib/session-index/routes.js +63 -10
  233. package/server/lib/session-index/routes.test.js +8 -8
  234. package/server/lib/session-index/service.js +7 -1
  235. package/server/lib/session-index/sync-runtime.js +171 -41
  236. package/server/lib/session-index/sync-runtime.test.js +164 -9
  237. package/server/lib/session-metadata/DOCUMENTATION.md +96 -0
  238. package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
  239. package/server/lib/session-metadata/opencode-session-record.js +316 -0
  240. package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
  241. package/server/lib/session-metadata/routes.js +197 -0
  242. package/server/lib/session-metadata/routes.test.js +156 -0
  243. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  244. package/server/lib/session-metadata/session-archive.js +654 -0
  245. package/server/lib/session-metadata/session-archive.test.js +359 -0
  246. package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
  247. package/server/lib/session-metadata/session-metadata-store.js +422 -0
  248. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  249. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  250. package/server/lib/session-metadata/session-projection.js +279 -0
  251. package/server/lib/session-metadata/session-projection.test.js +64 -0
  252. package/server/lib/session-metadata/system-session.js +55 -0
  253. package/server/lib/session-metadata/system-session.test.js +55 -0
  254. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  255. package/server/lib/session-title/native-runtime.test.js +167 -0
  256. package/server/lib/session-title/runtime.js +69 -44
  257. package/server/lib/session-title/session-access.js +46 -0
  258. package/server/lib/session-title/session-access.test.js +91 -0
  259. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  260. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  261. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  262. package/server/lib/session-turn-pages/routes.js +123 -75
  263. package/server/lib/session-turn-pages/routes.test.js +566 -0
  264. package/server/lib/session-turn-pages/service.js +6 -2
  265. package/server/lib/session-turn-pages/service.test.js +34 -0
  266. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  267. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  268. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  269. package/server/lib/small-model/catalog.js +84 -54
  270. package/server/lib/small-model/catalog.test.js +111 -9
  271. package/server/lib/small-model/custom-api.js +68 -0
  272. package/server/lib/small-model/custom-api.test.js +64 -0
  273. package/server/lib/small-model/index.js +125 -241
  274. package/server/lib/small-model/resolve.js +35 -120
  275. package/server/lib/small-model/resolve.test.js +20 -110
  276. package/server/lib/small-model/routes.js +1 -3
  277. package/server/lib/small-model/summary-settings.test.js +157 -253
  278. package/server/opencode-proxy.test.js +63 -12
  279. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  280. package/dist/assets/Icon-BgdfPhNs.js +0 -1
  281. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  282. package/dist/assets/MarkstreamRendererImpl-D2dsW5Lg.js +0 -3
  283. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  284. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  285. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  286. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  287. package/dist/assets/TerminalView-CDdzF4yi.js +0 -8
  288. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  289. package/dist/assets/es-DA3MqOo0.js +0 -15
  290. package/dist/assets/fileMentionTouchSelection-DNeiqns-.js +0 -4
  291. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  292. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  293. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  294. package/dist/assets/ja-DumanVQA.js +0 -15
  295. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  296. package/dist/assets/main-BPS5ylib.js +0 -2
  297. package/dist/assets/main-Cpd9VWYs.js +0 -169
  298. package/dist/assets/main-qSeeJRW1.css +0 -1
  299. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  300. package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
  301. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  302. package/dist/assets/mobile-CtktKrXn.js +0 -2
  303. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  304. package/dist/assets/persistence-Ce3AnpEr.js +0 -3
  305. package/dist/assets/pl-B9No6MQp.js +0 -15
  306. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  307. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  308. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  309. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  310. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  311. package/dist/assets/runtimeEndpointReset-ChOxt-J7.js +0 -147
  312. package/dist/assets/sessionChangeSummary-NnxcqCTI.js +0 -2
  313. package/dist/assets/src-rS3zCcsC.css +0 -2
  314. package/dist/assets/store-5B_LDnLM.js +0 -15
  315. package/dist/assets/textarea-bHYxZtfH.js +0 -1
  316. package/dist/assets/uk-C_1KkqyP.js +0 -15
  317. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  318. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  319. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  320. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  321. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  322. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  323. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  324. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  325. package/server/lib/small-model/call.js +0 -427
  326. package/server/lib/small-model/call.test.js +0 -382
  327. package/server/lib/small-model/opencode-session.js +0 -339
  328. package/server/lib/small-model/opencode-session.test.js +0 -177
@@ -10,6 +10,7 @@ const originalPath = process.env.PATH;
10
10
  const originalLocalAppData = process.env.LOCALAPPDATA;
11
11
  const originalSystemRoot = process.env.SystemRoot;
12
12
  const originalBundledOpencodeCliDir = process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
13
+ const originalOpenChamberDataDir = process.env.OPENCHAMBER_DATA_DIR;
13
14
  const originalResourcesPath = process.resourcesPath;
14
15
  const originalWslBinary = process.env.WSL_BINARY;
15
16
  const originalOpenChamberWslBinary = process.env.OPENCHAMBER_WSL_BINARY;
@@ -23,6 +24,16 @@ const createTempDir = (prefix) => {
23
24
  return dir;
24
25
  };
25
26
 
27
+ const writeVersionBinary = (filePath, version) => {
28
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
29
+ if (process.platform === 'win32') {
30
+ fs.writeFileSync(filePath, `@echo off\r\necho ${version}\r\n`);
31
+ return;
32
+ }
33
+ fs.writeFileSync(filePath, `#!${process.execPath}\nconsole.log(${JSON.stringify(version)});\n`);
34
+ fs.chmodSync(filePath, 0o755);
35
+ };
36
+
26
37
  const setPlatform = (platform) => {
27
38
  Object.defineProperty(process, 'platform', {
28
39
  value: platform,
@@ -74,6 +85,12 @@ afterEach(() => {
74
85
  delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
75
86
  }
76
87
 
88
+ if (typeof originalOpenChamberDataDir === 'string') {
89
+ process.env.OPENCHAMBER_DATA_DIR = originalOpenChamberDataDir;
90
+ } else {
91
+ delete process.env.OPENCHAMBER_DATA_DIR;
92
+ }
93
+
77
94
  Object.defineProperty(process, 'resourcesPath', {
78
95
  configurable: true,
79
96
  value: originalResourcesPath,
@@ -112,6 +129,7 @@ const createRuntime = (settings, options = {}) => {
112
129
  readSettingsFromDiskMigrated: async () => settings,
113
130
  spawnSync: options.spawnSync,
114
131
  homedir: options.homedir,
132
+ ensurePinnedOpenCode2Cli: options.ensurePinnedOpenCode2Cli,
115
133
  });
116
134
 
117
135
  return { runtime, state };
@@ -119,11 +137,11 @@ const createRuntime = (settings, options = {}) => {
119
137
 
120
138
  describe('OpenCode env runtime', () => {
121
139
  it('throws a specific error for a missing configured OpenCode binary in strict mode', async () => {
122
- const { runtime } = createRuntime({ opencodeBinary: '/missing/opencode' });
140
+ const { runtime } = createRuntime({ opencodeBinary: '/missing/opencode2' });
123
141
 
124
142
  await expect(runtime.applyOpencodeBinaryFromSettings({ strict: true })).rejects.toMatchObject({
125
143
  code: 'OPENCODE_BINARY_INVALID',
126
- message: expect.stringContaining('Configured OpenCode binary not found: /missing/opencode'),
144
+ message: expect.stringContaining('Configured OpenCode binary not found: /missing/opencode2'),
127
145
  });
128
146
  });
129
147
 
@@ -139,7 +157,7 @@ describe('OpenCode env runtime', () => {
139
157
 
140
158
  it('applies a valid configured executable OpenCode binary', async () => {
141
159
  const dir = createTempDir('openchamber-opencode-bin-');
142
- const binary = path.join(dir, 'opencode');
160
+ const binary = path.join(dir, 'opencode2');
143
161
  fs.writeFileSync(binary, '#!/bin/sh\nexit 0\n');
144
162
  fs.chmodSync(binary, 0o755);
145
163
  const { runtime, state } = createRuntime({ opencodeBinary: binary });
@@ -150,16 +168,71 @@ describe('OpenCode env runtime', () => {
150
168
  expect(state.resolvedOpencodeBinarySource).toBe('settings');
151
169
  });
152
170
 
171
+ it('skips a configured 1.x opencode version instead of aborting startup', async () => {
172
+ const dir = createTempDir('openchamber-opencode-1x-');
173
+ const binary = path.join(dir, 'opencode');
174
+ writeVersionBinary(binary, '1.18.4');
175
+ const { runtime } = createRuntime({ opencodeBinary: binary });
176
+
177
+ await expect(runtime.applyOpencodeBinaryFromSettings({ strict: true })).resolves.toBeNull();
178
+ expect(process.env.OPENCODE_BINARY).not.toBe(binary);
179
+ });
180
+
181
+ it('skips an explicit OPENCODE_BINARY that reports a 1.x version', () => {
182
+ const dir = createTempDir('openchamber-env-opencode-1x-');
183
+ const binary = path.join(dir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
184
+ writeVersionBinary(binary, '1.18.4');
185
+ process.env.OPENCODE_BINARY = binary;
186
+ const { runtime } = createRuntime({});
187
+
188
+ expect(runtime.resolveOpencodeCliPath()).not.toBe(binary);
189
+ });
190
+
191
+ it('discovers opencode from a home-directory install location', () => {
192
+ const home = createTempDir('openchamber-home-opencode-');
193
+ const binary = path.join(home, '.bun', 'bin', 'opencode');
194
+ writeVersionBinary(binary, '2.0.12');
195
+ process.env.PATH = createTempDir('openchamber-empty-path-home-');
196
+ delete process.env.OPENCODE_BINARY;
197
+ delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
198
+ const { runtime, state } = createRuntime({}, {
199
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
200
+ homedir: () => home,
201
+ });
202
+
203
+ expect(runtime.resolveOpencodeCliPath()).toBe(binary);
204
+ expect(state.resolvedOpencodeBinarySource).toBe('fallback');
205
+ });
206
+
207
+ it('does not treat a PATH 1.x opencode binary as a resolved CLI', () => {
208
+ const pathDir = createTempDir('openchamber-path-opencode-1x-');
209
+ const pathBinary = path.join(pathDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
210
+ fs.writeFileSync(pathBinary, '#!/bin/sh\nexit 0\n');
211
+ if (process.platform !== 'win32') {
212
+ fs.chmodSync(pathBinary, 0o755);
213
+ }
214
+ process.env.PATH = pathDir;
215
+ delete process.env.OPENCODE_BINARY;
216
+ delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
217
+ const emptyHome = createTempDir('openchamber-empty-home-1x-');
218
+ const { runtime, state } = createRuntime({}, {
219
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
220
+ homedir: () => emptyHome,
221
+ });
222
+
223
+ expect(runtime.resolveOpencodeCliPath()).toBeNull();
224
+ expect(state.resolvedOpencodeBinarySource).toBeNull();
225
+ });
226
+
153
227
  it('prefers a user-installed OpenCode from PATH over the bundled CLI', () => {
154
228
  const bundledDir = createTempDir('openchamber-bundled-opencode-');
155
229
  const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
156
230
  const pathDir = createTempDir('openchamber-path-opencode-');
157
231
  const pathBinary = path.join(pathDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
158
232
  fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
159
- fs.writeFileSync(pathBinary, '#!/bin/sh\nexit 0\n');
233
+ writeVersionBinary(pathBinary, '2.0.12');
160
234
  if (process.platform !== 'win32') {
161
235
  fs.chmodSync(bundledBinary, 0o755);
162
- fs.chmodSync(pathBinary, 0o755);
163
236
  }
164
237
  process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
165
238
  process.env.PATH = pathDir;
@@ -174,12 +247,11 @@ describe('OpenCode env runtime', () => {
174
247
  const bundledDir = createTempDir('openchamber-bundled-opencode-');
175
248
  const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
176
249
  const explicitDir = createTempDir('openchamber-explicit-opencode-');
177
- const explicitBinary = path.join(explicitDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
250
+ const explicitBinary = path.join(explicitDir, process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
178
251
  fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
179
- fs.writeFileSync(explicitBinary, '#!/bin/sh\nexit 0\n');
252
+ writeVersionBinary(explicitBinary, '2.0.12');
180
253
  if (process.platform !== 'win32') {
181
254
  fs.chmodSync(bundledBinary, 0o755);
182
- fs.chmodSync(explicitBinary, 0o755);
183
255
  }
184
256
  process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
185
257
  process.env.OPENCODE_BINARY = explicitBinary;
@@ -189,7 +261,7 @@ describe('OpenCode env runtime', () => {
189
261
  expect(state.resolvedOpencodeBinarySource).toBe('env');
190
262
  });
191
263
 
192
- it('falls back to the bundled OpenCode CLI from Electron resourcesPath when nothing else is installed', () => {
264
+ it('does not use a bundled OpenCode CLI from Electron resourcesPath', () => {
193
265
  const resourcesPath = createTempDir('openchamber-resources-');
194
266
  const bundledDir = path.join(resourcesPath, 'opencode-cli');
195
267
  const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
@@ -203,18 +275,142 @@ describe('OpenCode env runtime', () => {
203
275
  value: resourcesPath,
204
276
  });
205
277
  process.env.PATH = createTempDir('openchamber-empty-path-');
206
- delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
278
+ process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
207
279
  delete process.env.OPENCODE_BINARY;
208
- // The bundled CLI is the LAST resort now — hide the machine's own installs
209
- // from the home-directory fallbacks and shell discovery.
210
280
  const emptyHome = createTempDir('openchamber-empty-home-');
211
281
  const { runtime, state } = createRuntime({}, {
212
282
  spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
213
283
  homedir: () => emptyHome,
214
284
  });
215
285
 
216
- expect(runtime.resolveOpencodeCliPath()).toBe(bundledBinary);
286
+ expect(runtime.resolveOpencodeCliPath()).toBeNull();
287
+ expect(state.resolvedOpencodeBinarySource).toBeNull();
288
+ });
289
+
290
+ it('discovers a previously installed pin from the OpenChamber data dir', () => {
291
+ const dataDir = createTempDir('openchamber-installed-cli-');
292
+ const installedDir = path.join(dataDir, 'opencode-cli', '2.0.12');
293
+ const installedBinary = path.join(installedDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
294
+ fs.mkdirSync(installedDir, { recursive: true });
295
+ fs.writeFileSync(installedBinary, '#!/bin/sh\nexit 0\n');
296
+ if (process.platform !== 'win32') {
297
+ fs.chmodSync(installedBinary, 0o755);
298
+ }
299
+ process.env.OPENCHAMBER_DATA_DIR = dataDir;
300
+ process.env.PATH = createTempDir('openchamber-empty-path-installed-');
301
+ delete process.env.OPENCODE_BINARY;
302
+ delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
303
+ const emptyHome = createTempDir('openchamber-empty-home-installed-');
304
+ const { runtime, state } = createRuntime({}, {
305
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
306
+ homedir: () => emptyHome,
307
+ });
308
+
309
+ expect(runtime.resolveOpencodeCliPath()).toBe(installedBinary);
310
+ expect(state.resolvedOpencodeBinarySource).toBe('installed');
311
+ });
312
+
313
+ it('installs the pinned CLI when discovery finds nothing', async () => {
314
+ const installed = path.join(createTempDir('openchamber-ensure-install-'), process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
315
+ fs.writeFileSync(installed, '#!/bin/sh\nexit 0\n');
316
+ if (process.platform !== 'win32') {
317
+ fs.chmodSync(installed, 0o755);
318
+ }
319
+ process.env.PATH = createTempDir('openchamber-empty-path-ensure-');
320
+ delete process.env.OPENCODE_BINARY;
321
+ delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
322
+ const emptyHome = createTempDir('openchamber-empty-home-ensure-');
323
+ const { runtime, state } = createRuntime({}, {
324
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
325
+ homedir: () => emptyHome,
326
+ ensurePinnedOpenCode2Cli: async () => ({
327
+ path: installed,
328
+ version: '2.0.12',
329
+ source: 'installed',
330
+ installed: true,
331
+ }),
332
+ });
333
+
334
+ await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(installed);
335
+ expect(state.resolvedOpencodeBinary).toBe(installed);
336
+ expect(state.resolvedOpencodeBinarySource).toBe('installed');
337
+ expect(process.env.OPENCODE_BINARY).toBe(installed);
338
+ });
339
+
340
+ it('falls back to a bundled 2.x CLI only after pin install fails', async () => {
341
+ const bundledDir = createTempDir('openchamber-bundled-fallback-');
342
+ const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
343
+ const bundledBinary = path.join(bundledDir, bundledName);
344
+ writeVersionBinary(bundledBinary, '2.0.12');
345
+ process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
346
+ process.env.PATH = createTempDir('openchamber-empty-path-bundled-fallback-');
347
+ process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-fallback-');
348
+ delete process.env.OPENCODE_BINARY;
349
+ const emptyHome = createTempDir('openchamber-empty-home-bundled-fallback-');
350
+ const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
351
+ const { runtime, state } = createRuntime({}, {
352
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
353
+ homedir: () => emptyHome,
354
+ ensurePinnedOpenCode2Cli: async () => {
355
+ throw missingError;
356
+ },
357
+ });
358
+
359
+ await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(bundledBinary);
360
+ expect(state.resolvedOpencodeBinary).toBe(bundledBinary);
361
+ expect(state.resolvedOpencodeBinarySource).toBe('bundled');
362
+ expect(process.env.OPENCODE_BINARY).toBe(bundledBinary);
363
+ });
364
+
365
+ it('falls back to bundled opencode name when pin install fails', async () => {
366
+ const bundledDir = createTempDir('openchamber-bundled-opencode-name-');
367
+ const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
368
+ const bundledBinary = path.join(bundledDir, bundledName);
369
+ writeVersionBinary(bundledBinary, '2.0.12');
370
+ process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
371
+ process.env.PATH = createTempDir('openchamber-empty-path-bundled-opencode-');
372
+ process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-opencode-');
373
+ delete process.env.OPENCODE_BINARY;
374
+ const emptyHome = createTempDir('openchamber-empty-home-bundled-opencode-');
375
+ const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
376
+ const { runtime, state } = createRuntime({}, {
377
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
378
+ homedir: () => emptyHome,
379
+ ensurePinnedOpenCode2Cli: async () => {
380
+ throw missingError;
381
+ },
382
+ });
383
+
384
+ await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(bundledBinary);
217
385
  expect(state.resolvedOpencodeBinarySource).toBe('bundled');
386
+ expect(process.env.OPENCODE_BINARY).toBe(bundledBinary);
387
+ });
388
+
389
+ it('does not adopt a bundled 1.x CLI after pin install fails', async () => {
390
+ const bundledDir = createTempDir('openchamber-bundled-1x-');
391
+ const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
392
+ const bundledBinary = path.join(bundledDir, bundledName);
393
+ writeVersionBinary(bundledBinary, '1.18.4');
394
+ process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
395
+ process.env.PATH = createTempDir('openchamber-empty-path-bundled-1x-');
396
+ process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-1x-');
397
+ delete process.env.OPENCODE_BINARY;
398
+ const emptyHome = createTempDir('openchamber-empty-home-bundled-1x-');
399
+ const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
400
+ const { runtime, state } = createRuntime({}, {
401
+ spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
402
+ homedir: () => emptyHome,
403
+ ensurePinnedOpenCode2Cli: async () => {
404
+ throw missingError;
405
+ },
406
+ });
407
+
408
+ await expect(runtime.ensurePinnedOpenCode2CliEnv()).rejects.toMatchObject({
409
+ code: 'OPENCODE_CLI_MISSING',
410
+ });
411
+ expect(state.resolvedOpencodeBinary).toBeNull();
412
+ expect(state.resolvedOpencodeBinarySource).toBeNull();
413
+ expect(process.env.OPENCODE_BINARY).toBeUndefined();
218
414
  });
219
415
 
220
416
  itIf(process.platform === 'darwin')('rejects known macOS OpenCode app bundle executable paths', async () => {
@@ -265,7 +461,8 @@ describe('OpenCode env runtime', () => {
265
461
  const cliBinary = path.join(createTempDir('openchamber-cli-'), 'opencode.exe');
266
462
  fs.mkdirSync(path.dirname(desktopBinary), { recursive: true });
267
463
  fs.writeFileSync(desktopBinary, '');
268
- fs.writeFileSync(cliBinary, '');
464
+ fs.writeFileSync(cliBinary, `#!${process.execPath}\nconsole.log('2.0.12');\n`);
465
+ fs.chmodSync(cliBinary, 0o755);
269
466
  process.env.LOCALAPPDATA = localAppData;
270
467
  process.env.PATH = createTempDir('openchamber-empty-path-');
271
468
  process.env.SystemRoot = createTempDir('openchamber-empty-systemroot-');
@@ -2,6 +2,7 @@ import { registerFsRoutes } from '../fs/routes.js';
2
2
  import { registerQuotaRoutes } from '../quota/routes.js';
3
3
  import { registerSmallModelRoutes } from '../small-model/routes.js';
4
4
  import { registerSessionGoalRoutes } from '../session-goal/routes.js';
5
+ import { registerSessionMetadataRoutes } from '../session-metadata/routes.js';
5
6
  import { registerGitHubRoutes } from '../github/routes.js';
6
7
  import { registerGitRoutes } from '../git/routes.js';
7
8
  import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
@@ -18,13 +19,16 @@ import { registerSessionTurnPageRoutes } from '../session-turn-pages/routes.js';
18
19
  import { registerAssistantRoutes } from '../assistants/routes.js';
19
20
  import { registerLlmRoutes } from '../llm/routes.js';
20
21
  import { registerMessageQueueRoutes } from '../message-queue/routes.js';
22
+ import { getBrowserProviderHost } from '../browser-provider/host.js';
23
+ import { registerBrowserProviderRoutes } from '../browser-provider/routes.js';
24
+ import { createBrowserProviderSelectionStore } from '../browser-provider/selection-store.js';
21
25
  import { registerSkillRoutes } from './skill-routes.js';
22
26
  import { registerPluginRoutes } from './plugin-routes.js';
23
27
  import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
24
28
  import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
25
29
  import { registerOpenCodeRoutes } from './routes.js';
26
30
  import { getProviderSources, removeProviderConfig } from './providers.js';
27
- import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
31
+ import { getAgentSources, getAgentConfig, listDisabledAgentOverrides, createAgent, updateAgent, deleteAgent } from './agents.js';
28
32
  import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
29
33
  import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
30
34
  import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
@@ -113,6 +117,16 @@ export const createFeatureRoutesRuntime = (dependencies) => {
113
117
  isUnsafeSkillRelativePath,
114
118
  buildOpenCodeUrl,
115
119
  getOpenCodeAuthHeaders,
120
+ getIsExternalOpenCode = () => false,
121
+ getIsSharedOpenCodeService = () => false,
122
+ forceResolvedOpenCodeBinary = null,
123
+ restartOpenCode = null,
124
+ getRuntimeContract = () => null,
125
+ getOpenCodeServeVersion = () => null,
126
+ getOpenCodeCliVersion = () => null,
127
+ getResolvedOpenCodeBinary = () => null,
128
+ getResolvedOpenCodeBinarySource = () => null,
129
+ getActiveSessionCount = () => 0,
116
130
  getSmallModelService: routeGetSmallModelService,
117
131
  getOpenCodePort,
118
132
  buildAugmentedPath,
@@ -132,6 +146,13 @@ export const createFeatureRoutesRuntime = (dependencies) => {
132
146
  getServerId,
133
147
  sessionIndexService,
134
148
  notifyContactTurnComplete,
149
+ sessionMetadataStore = null,
150
+ sessionArchiveService = null,
151
+ onSessionMetadataWritten = null,
152
+ persistSessionGoal = null,
153
+ persistSessionMetadata = null,
154
+ onSystemSessionPersisted = null,
155
+ readSessionMetadata = null,
135
156
  } = routeDependencies;
136
157
 
137
158
  registerSettingsUtilityRoutes(app, {
@@ -164,6 +185,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
164
185
  refreshOpenCodeAfterConfigChange,
165
186
  buildOpenCodeUrl,
166
187
  getOpenCodeAuthHeaders,
188
+ getIsExternalOpenCode,
189
+ getIsSharedOpenCodeService,
190
+ forceResolvedOpenCodeBinary,
191
+ restartOpenCode,
192
+ waitForOpenCodeReady,
193
+ getRuntimeContract,
194
+ getOpenCodeServeVersion,
195
+ getOpenCodeCliVersion,
196
+ getResolvedOpenCodeBinary,
197
+ getResolvedOpenCodeBinarySource,
198
+ getActiveSessionCount,
199
+ openchamberDataDir,
167
200
  onSettingsPersisted: (updated, changes) => {
168
201
  if (!questionAutoDelegateRuntime) return;
169
202
  if (!Object.prototype.hasOwnProperty.call(changes ?? {}, 'questionAutoDelegateEnabled')) return;
@@ -247,6 +280,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
247
280
  registerLlmRoutes(app, {
248
281
  buildOpenCodeUrl,
249
282
  getOpenCodeAuthHeaders,
283
+ persistSessionMetadata,
284
+ onSystemSessionPersisted,
250
285
  });
251
286
  assistantRoutesRuntime = registerAssistantRoutes(app, {
252
287
  openchamberDataDir,
@@ -273,6 +308,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
273
308
  },
274
309
  listScheduledTasks: (projectID) => projectConfigRuntime.listScheduledTasks(projectID),
275
310
  sessionIndexService,
311
+ archiveSessionHost: sessionArchiveService
312
+ ? async ({ sessionID, directory, archivedAt }) => sessionArchiveService.archiveSession(
313
+ sessionID,
314
+ directory,
315
+ archivedAt,
316
+ )
317
+ : null,
318
+ forgetSessionHost: sessionArchiveService
319
+ ? (sessionID) => sessionArchiveService.forgetSession(sessionID)
320
+ : null,
321
+ persistSessionMetadata,
322
+ onSystemSessionPersisted,
276
323
  upsertScheduledTask: (projectID, task) => projectConfigRuntime.upsertScheduledTask(projectID, task),
277
324
  syncScheduledTaskProject: (projectID) => scheduledTasksRuntime.syncProject(projectID),
278
325
  refreshAllowedRoots: refreshAssistantAllowedRoots,
@@ -293,9 +340,11 @@ export const createFeatureRoutesRuntime = (dependencies) => {
293
340
  resolveOptionalProjectDirectory,
294
341
  refreshOpenCodeAfterConfigChange,
295
342
  clientReloadDelayMs,
296
- getAgentSources,
297
- getAgentConfig,
298
- createAgent,
343
+ waitForOpenCodeReady,
344
+ getAgentSources,
345
+ getAgentConfig,
346
+ listDisabledAgentOverrides,
347
+ createAgent,
299
348
  updateAgent,
300
349
  deleteAgent,
301
350
  getCommandSources,
@@ -387,7 +436,36 @@ export const createFeatureRoutesRuntime = (dependencies) => {
387
436
  throw new Error('Small model service is not configured');
388
437
  });
389
438
  registerSmallModelRoutes(app, { getSmallModelService });
390
- registerSessionGoalRoutes(app);
439
+ registerSessionGoalRoutes(app, {
440
+ // Reuse the index-provided store seam — do not invent a second store.
441
+ persistSessionGoal: typeof persistSessionGoal === 'function'
442
+ ? persistSessionGoal
443
+ : (sessionMetadataStore
444
+ ? async (sessionId, _directory, goal) => {
445
+ const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
446
+ openchamber: { goal },
447
+ });
448
+ return metadata;
449
+ }
450
+ : null),
451
+ readSessionMetadata: typeof readSessionMetadata === 'function'
452
+ ? readSessionMetadata
453
+ : (sessionMetadataStore
454
+ ? (sessionId) => sessionMetadataStore.get(sessionId)
455
+ : null),
456
+ // Same arm path as PUT /api/openchamber/sessions/:id/metadata.
457
+ onGoalPersisted: typeof onSessionMetadataWritten === 'function'
458
+ ? onSessionMetadataWritten
459
+ : null,
460
+ });
461
+ if (sessionMetadataStore) {
462
+ registerSessionMetadataRoutes(app, {
463
+ sessionMetadataStore,
464
+ sessionArchiveService,
465
+ broadcastGlobalUiEvent,
466
+ onMetadataWritten: onSessionMetadataWritten,
467
+ });
468
+ }
391
469
  registerGitHubRoutes(app);
392
470
  const broadcastOpenChamberEvent = createOpenChamberEventBroadcaster({
393
471
  getOpenChamberEventClients,
@@ -438,6 +516,13 @@ export const createFeatureRoutesRuntime = (dependencies) => {
438
516
  openchamberUserConfigRoot,
439
517
  openchamberDataDir,
440
518
  });
519
+ const browserProviderHost = getBrowserProviderHost();
520
+ if (typeof openchamberDataDir === 'string' && openchamberDataDir) {
521
+ browserProviderHost.attachSelectionStore(createBrowserProviderSelectionStore(
522
+ path.join(openchamberDataDir, 'browser-provider-selection.json'),
523
+ ));
524
+ }
525
+ registerBrowserProviderRoutes(app, { host: browserProviderHost });
441
526
  };
442
527
 
443
528
  return {
@@ -3,6 +3,20 @@ import { describe, expect, it, vi } from 'vitest';
3
3
  import { createWorktreeTopologyBroadcaster } from './feature-routes-runtime.js';
4
4
 
5
5
  describe('feature routes runtime composition', () => {
6
+ it('passes live shared-service ownership from the host to upgrade routes', async () => {
7
+ const host = await fs.readFile(new URL('../../index.js', import.meta.url), 'utf8');
8
+ const routes = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
9
+ expect(host).toContain('getIsSharedOpenCodeService: () => openCodeLifecycleRuntime.isSharedOpenCodeService()');
10
+ expect(routes).toMatch(/getIsSharedOpenCodeService = \(\) => false,[\s\S]*\} = routeDependencies;/);
11
+ expect(routes).toMatch(/registerOpenCodeRoutes\(app, \{[^}]*getIsSharedOpenCodeService,/);
12
+ });
13
+
14
+ it('wires Host isolation persist into scheduled-task runtime from the composition root', async () => {
15
+ const source = await fs.readFile(new URL('../../index.js', import.meta.url), 'utf8');
16
+ expect(source).toMatch(/createScheduledTasksRuntime\(\{[\s\S]*persistSessionMetadata: persistSessionMetadataToStore/);
17
+ expect(source).toMatch(/createScheduledTasksRuntime\(\{[\s\S]*onSystemSessionPersisted: hideSystemSessionFromIndex/);
18
+ });
19
+
6
20
  it('wires scheduled-task upsert into assistant contact tools', async () => {
7
21
  const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
8
22
  expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*upsertScheduledTask:[\s\S]*projectConfigRuntime\.upsertScheduledTask/);
@@ -10,6 +24,10 @@ describe('feature routes runtime composition', () => {
10
24
  expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*listProjects:/);
11
25
  expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*listScheduledTasks:[\s\S]*projectConfigRuntime\.listScheduledTasks/);
12
26
  expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*sessionIndexService/);
27
+ expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*persistSessionMetadata/);
28
+ expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*onSystemSessionPersisted/);
29
+ expect(source).toMatch(/registerLlmRoutes\(app, \{[\s\S]*persistSessionMetadata/);
30
+ expect(source).toMatch(/registerLlmRoutes\(app, \{[\s\S]*onSystemSessionPersisted/);
13
31
  expect(source).toMatch(/label: project\.label\.trim\(\)/);
14
32
  });
15
33
 
@@ -36,6 +54,13 @@ describe('feature routes runtime composition', () => {
36
54
  expect(source).toMatch(/registerScheduledTaskRoutes\(app, \{[\s\S]*runHistoryStore,/);
37
55
  });
38
56
 
57
+ it('registers browser provider routes on the shared host before proxy composition', async () => {
58
+ const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
59
+ expect(source).toContain("import { registerBrowserProviderRoutes } from '../browser-provider/routes.js';");
60
+ expect(source).toContain('registerBrowserProviderRoutes(app, { host: browserProviderHost });');
61
+ expect(source).toContain('createBrowserProviderSelectionStore(');
62
+ });
63
+
39
64
  it('registers message queue routes with the injected service before proxy composition', async () => {
40
65
  const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
41
66
  expect(source).toContain("import { registerMessageQueueRoutes } from '../message-queue/routes.js';");
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Track sockets owned by an HTTP server so force-close can destroy upgraded
3
+ * WebSocket/SSE sockets that Node's `server.closeAllConnections()` leaves open
4
+ * (those keep `server.close()` from firing until SHUTDOWN_TIMEOUT).
5
+ */
6
+
7
+ export const HTTP_SERVER_CONNECTIONS = Symbol.for('openchamber.httpServerConnections');
8
+
9
+ /**
10
+ * @param {import('node:http').Server} server
11
+ * @returns {{
12
+ * getOpenSocketCount: () => number,
13
+ * destroyRemainingSockets: () => number,
14
+ * dispose: () => void,
15
+ * }}
16
+ */
17
+ export const attachHttpServerConnectionTracker = (server) => {
18
+ if (!server || typeof server.on !== 'function') {
19
+ return {
20
+ getOpenSocketCount: () => 0,
21
+ destroyRemainingSockets: () => 0,
22
+ dispose: () => {},
23
+ };
24
+ }
25
+
26
+ const existing = server[HTTP_SERVER_CONNECTIONS];
27
+ if (existing && typeof existing.destroyRemainingSockets === 'function') {
28
+ return existing;
29
+ }
30
+
31
+ /** @type {Set<import('node:net').Socket>} */
32
+ const sockets = new Set();
33
+
34
+ const track = (socket) => {
35
+ if (!socket || sockets.has(socket)) return;
36
+ sockets.add(socket);
37
+ const release = () => {
38
+ sockets.delete(socket);
39
+ };
40
+ socket.once('close', release);
41
+ socket.once('error', release);
42
+ };
43
+
44
+ // Plain HTTP and pre-upgrade TCP both emit `connection`. After `upgrade`, the
45
+ // same socket object remains; tracking it here covers WS/SSE leftovers.
46
+ const onConnection = (socket) => {
47
+ track(socket);
48
+ };
49
+ server.on('connection', onConnection);
50
+
51
+ const api = {
52
+ getOpenSocketCount: () => sockets.size,
53
+ destroyRemainingSockets: () => {
54
+ let destroyed = 0;
55
+ for (const socket of [...sockets]) {
56
+ try {
57
+ if (!socket.destroyed) {
58
+ socket.destroy();
59
+ destroyed += 1;
60
+ }
61
+ } catch {
62
+ }
63
+ sockets.delete(socket);
64
+ }
65
+ return destroyed;
66
+ },
67
+ dispose: () => {
68
+ server.off?.('connection', onConnection);
69
+ sockets.clear();
70
+ try {
71
+ delete server[HTTP_SERVER_CONNECTIONS];
72
+ } catch {
73
+ }
74
+ },
75
+ };
76
+
77
+ server[HTTP_SERVER_CONNECTIONS] = api;
78
+ return api;
79
+ };
80
+
81
+ /**
82
+ * Stop accepting new connections, drop Node-tracked HTTP connections, then
83
+ * destroy any remaining server-owned sockets (including upgraded ones).
84
+ * @param {import('node:http').Server | null | undefined} server
85
+ * @returns {{ destroyedTracked: number }}
86
+ */
87
+ export const forceCloseHttpServerConnections = (server) => {
88
+ if (!server) return { destroyedTracked: 0 };
89
+ try {
90
+ server.closeAllConnections?.();
91
+ } catch {
92
+ }
93
+ const tracker = server[HTTP_SERVER_CONNECTIONS];
94
+ const destroyedTracked = typeof tracker?.destroyRemainingSockets === 'function'
95
+ ? tracker.destroyRemainingSockets()
96
+ : 0;
97
+ return { destroyedTracked };
98
+ };