@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.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 (311) 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-B_JQoreg.js} +1 -1
  7. package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
  10. package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
  11. package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
  13. package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
  14. package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
  16. package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
  20. package/dist/assets/SettingsView-BRxELjow.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
  22. package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
  24. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
  25. package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
  26. package/dist/assets/collapsible-DXbDynN4.js +3 -0
  27. package/dist/assets/context-menu-CqolsLCm.js +1 -0
  28. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
  29. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
  30. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
  31. package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
  32. package/dist/assets/es-CN3E_zXj.js +15 -0
  33. package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
  34. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
  35. package/dist/assets/fr-9zZvQZD2.js +13 -0
  36. package/dist/assets/gitApi-CM42qUed.js +10 -0
  37. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
  38. package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
  39. package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
  40. package/dist/assets/ja-Dnjk_82N.js +15 -0
  41. package/dist/assets/ko-BxCEE99C.js +15 -0
  42. package/dist/assets/main-BILgBI3B.js +169 -0
  43. package/dist/assets/main-BeveiW6F.js +2 -0
  44. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  45. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
  46. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  47. package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
  48. package/dist/assets/miniChat-BgrzM3u7.js +2 -0
  49. package/dist/assets/mobile-DtYoQE8C.js +2 -0
  50. package/dist/assets/multirun-jy8dEI96.js +4 -0
  51. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
  52. package/dist/assets/pl-CNNsWnPM.js +15 -0
  53. package/dist/assets/platform-BQghHRfp.js +6 -0
  54. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  55. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
  56. package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
  57. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
  58. package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
  59. package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
  60. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
  61. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
  62. package/dist/assets/src-CLN7Z-zx.css +2 -0
  63. package/dist/assets/store-DLCiZaA6.js +15 -0
  64. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
  65. package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
  66. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
  67. package/dist/assets/uk-rwQrxeYM.js +15 -0
  68. package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
  69. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
  70. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  71. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
  72. package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
  73. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
  74. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
  75. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  76. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
  77. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  78. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  79. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
  80. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  81. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  82. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  83. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  85. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  86. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  87. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  88. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
  89. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  90. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  91. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  92. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  93. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  94. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  95. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  96. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  97. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  98. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  99. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  100. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  101. package/dist/index.html +13 -13
  102. package/dist/mini-chat.html +13 -13
  103. package/dist/mobile.html +13 -13
  104. package/package.json +2 -2
  105. package/server/index.js +392 -20
  106. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  107. package/server/lib/assistants/assign.js +2 -1
  108. package/server/lib/assistants/contact-store.js +37 -19
  109. package/server/lib/assistants/contracts.js +11 -1
  110. package/server/lib/assistants/projection-history.test.js +454 -0
  111. package/server/lib/assistants/routes.js +11 -5
  112. package/server/lib/assistants/service.js +850 -294
  113. package/server/lib/assistants/service.test.js +588 -274
  114. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  115. package/server/lib/conversations/service.js +87 -57
  116. package/server/lib/conversations/service.test.js +132 -100
  117. package/server/lib/conversations/validation.js +11 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +51 -5
  120. package/server/lib/event-stream/runtime.js +57 -20
  121. package/server/lib/event-stream/runtime.test.js +112 -0
  122. package/server/lib/llm/DOCUMENTATION.md +95 -84
  123. package/server/lib/llm/catalog.js +111 -45
  124. package/server/lib/llm/catalog.test.js +121 -59
  125. package/server/lib/llm/completions.js +41 -12
  126. package/server/lib/llm/completions.test.js +45 -4
  127. package/server/lib/llm/generate.js +516 -453
  128. package/server/lib/llm/generate.test.js +705 -484
  129. package/server/lib/llm/routes.js +8 -4
  130. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  131. package/server/lib/message-queue/opencode-adapter.js +191 -104
  132. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  133. package/server/lib/message-queue/runtime.js +15 -2
  134. package/server/lib/message-queue/runtime.test.js +6 -6
  135. package/server/lib/message-queue/worker.js +7 -1
  136. package/server/lib/notifications/runtime.js +5 -0
  137. package/server/lib/opencode/DOCUMENTATION.md +103 -16
  138. package/server/lib/opencode/agent-document.d.ts +28 -0
  139. package/server/lib/opencode/agent-document.js +280 -0
  140. package/server/lib/opencode/agent-document.test.js +137 -0
  141. package/server/lib/opencode/agents.js +96 -0
  142. package/server/lib/opencode/auth-state-runtime.js +5 -0
  143. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  144. package/server/lib/opencode/config-entity-routes.js +111 -102
  145. package/server/lib/opencode/config-entity-routes.test.js +123 -10
  146. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  147. package/server/lib/opencode/ensure-cli.js +238 -0
  148. package/server/lib/opencode/ensure-cli.test.js +119 -0
  149. package/server/lib/opencode/env-runtime.js +208 -111
  150. package/server/lib/opencode/env-runtime.test.js +211 -14
  151. package/server/lib/opencode/feature-routes-runtime.js +80 -5
  152. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  153. package/server/lib/opencode/http-server-connections.js +98 -0
  154. package/server/lib/opencode/http-server-connections.test.js +321 -0
  155. package/server/lib/opencode/lifecycle.js +539 -95
  156. package/server/lib/opencode/lifecycle.test.js +698 -6
  157. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  158. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  159. package/server/lib/opencode/network-runtime.js +28 -16
  160. package/server/lib/opencode/network-runtime.test.js +68 -4
  161. package/server/lib/opencode/openchamber-routes.js +7 -1
  162. package/server/lib/opencode/opencode2-pin.js +139 -0
  163. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  164. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  165. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  166. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  167. package/server/lib/opencode/plugin-compatibility.js +15 -0
  168. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  169. package/server/lib/opencode/plugin-routes.js +3 -18
  170. package/server/lib/opencode/plugin-routes.test.js +9 -9
  171. package/server/lib/opencode/plugins.js +4 -3
  172. package/server/lib/opencode/plugins.test.js +12 -0
  173. package/server/lib/opencode/provider-catalog.js +58 -12
  174. package/server/lib/opencode/provider-catalog.test.js +48 -0
  175. package/server/lib/opencode/proxy.js +368 -28
  176. package/server/lib/opencode/proxy.test.js +338 -0
  177. package/server/lib/opencode/routes.js +303 -76
  178. package/server/lib/opencode/routes.test.js +315 -50
  179. package/server/lib/opencode/runtime-contract.js +401 -0
  180. package/server/lib/opencode/runtime-contract.test.js +214 -0
  181. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  182. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  183. package/server/lib/opencode/server-utils-runtime.js +35 -0
  184. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  185. package/server/lib/opencode/shared-service.js +147 -0
  186. package/server/lib/opencode/shared-service.test.js +93 -0
  187. package/server/lib/opencode/shared.js +3 -3
  188. package/server/lib/opencode/shutdown-runtime.js +15 -6
  189. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  190. package/server/lib/opencode/skill-routes.js +26 -40
  191. package/server/lib/opencode/skill-routes.test.js +71 -7
  192. package/server/lib/opencode/skills.test.js +15 -0
  193. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  194. package/server/lib/opencode/static-routes-runtime.js +3 -1
  195. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  196. package/server/lib/opencode/v1-migration-gate.js +157 -0
  197. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  198. package/server/lib/opencode/v2-client.js +60 -0
  199. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  200. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  201. package/server/lib/opencode/watcher.js +2 -1
  202. package/server/lib/opencode/watcher.test.js +1 -1
  203. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  204. package/server/lib/permission-auto-accept/runtime.js +14 -7
  205. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  206. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  207. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  208. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  209. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  210. package/server/lib/scheduled-tasks/routes.js +27 -0
  211. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  212. package/server/lib/scheduled-tasks/runtime.js +381 -310
  213. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  214. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  215. package/server/lib/session-goal/capability.js +57 -0
  216. package/server/lib/session-goal/capability.test.js +33 -0
  217. package/server/lib/session-goal/routes.js +240 -2
  218. package/server/lib/session-goal/routes.test.js +211 -0
  219. package/server/lib/session-goal/runtime.js +1617 -113
  220. package/server/lib/session-goal/runtime.test.js +1500 -70
  221. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  222. package/server/lib/session-index/event-ingest.js +89 -16
  223. package/server/lib/session-index/event-ingest.test.js +73 -0
  224. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  225. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  226. package/server/lib/session-index/routes.js +63 -10
  227. package/server/lib/session-index/routes.test.js +8 -8
  228. package/server/lib/session-index/service.js +7 -1
  229. package/server/lib/session-index/sync-runtime.js +171 -41
  230. package/server/lib/session-index/sync-runtime.test.js +164 -9
  231. package/server/lib/session-metadata/DOCUMENTATION.md +86 -0
  232. package/server/lib/session-metadata/routes.js +193 -0
  233. package/server/lib/session-metadata/routes.test.js +156 -0
  234. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  235. package/server/lib/session-metadata/session-archive.js +654 -0
  236. package/server/lib/session-metadata/session-archive.test.js +359 -0
  237. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  238. package/server/lib/session-metadata/session-metadata-store.js +375 -0
  239. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  240. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  241. package/server/lib/session-metadata/session-projection.js +255 -0
  242. package/server/lib/session-metadata/session-projection.test.js +64 -0
  243. package/server/lib/session-metadata/system-session.js +55 -0
  244. package/server/lib/session-metadata/system-session.test.js +55 -0
  245. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  246. package/server/lib/session-title/native-runtime.test.js +167 -0
  247. package/server/lib/session-title/runtime.js +67 -42
  248. package/server/lib/session-title/session-access.js +46 -0
  249. package/server/lib/session-title/session-access.test.js +91 -0
  250. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  251. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  252. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  253. package/server/lib/session-turn-pages/routes.js +123 -75
  254. package/server/lib/session-turn-pages/routes.test.js +566 -0
  255. package/server/lib/session-turn-pages/service.js +6 -2
  256. package/server/lib/session-turn-pages/service.test.js +34 -0
  257. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  258. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  259. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  260. package/server/lib/small-model/catalog.js +84 -54
  261. package/server/lib/small-model/catalog.test.js +111 -9
  262. package/server/lib/small-model/custom-api.js +68 -0
  263. package/server/lib/small-model/custom-api.test.js +64 -0
  264. package/server/lib/small-model/index.js +125 -241
  265. package/server/lib/small-model/resolve.js +35 -120
  266. package/server/lib/small-model/resolve.test.js +20 -110
  267. package/server/lib/small-model/routes.js +1 -3
  268. package/server/lib/small-model/summary-settings.test.js +157 -253
  269. package/server/opencode-proxy.test.js +63 -12
  270. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  271. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  272. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  273. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  274. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  275. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  276. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  277. package/dist/assets/collapsible-D5Phaykh.js +0 -1
  278. package/dist/assets/context-menu-CubSDW2g.js +0 -1
  279. package/dist/assets/es-DA3MqOo0.js +0 -15
  280. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  281. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  282. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  283. package/dist/assets/ja-DumanVQA.js +0 -15
  284. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  285. package/dist/assets/main-BPS5ylib.js +0 -2
  286. package/dist/assets/main-Cpd9VWYs.js +0 -169
  287. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  288. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  289. package/dist/assets/mobile-CtktKrXn.js +0 -2
  290. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  291. package/dist/assets/pl-B9No6MQp.js +0 -15
  292. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  293. package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
  294. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  295. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  296. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  297. package/dist/assets/src-rS3zCcsC.css +0 -2
  298. package/dist/assets/store-5B_LDnLM.js +0 -15
  299. package/dist/assets/uk-C_1KkqyP.js +0 -15
  300. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  301. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  302. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  303. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  304. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  305. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  306. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  307. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  308. package/server/lib/small-model/call.js +0 -427
  309. package/server/lib/small-model/call.test.js +0 -382
  310. package/server/lib/small-model/opencode-session.js +0 -339
  311. 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';
@@ -24,7 +25,7 @@ import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
24
25
  import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
25
26
  import { registerOpenCodeRoutes } from './routes.js';
26
27
  import { getProviderSources, removeProviderConfig } from './providers.js';
27
- import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
28
+ import { getAgentSources, getAgentConfig, listDisabledAgentOverrides, createAgent, updateAgent, deleteAgent } from './agents.js';
28
29
  import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
29
30
  import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
30
31
  import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
@@ -113,6 +114,16 @@ export const createFeatureRoutesRuntime = (dependencies) => {
113
114
  isUnsafeSkillRelativePath,
114
115
  buildOpenCodeUrl,
115
116
  getOpenCodeAuthHeaders,
117
+ getIsExternalOpenCode = () => false,
118
+ getIsSharedOpenCodeService = () => false,
119
+ forceResolvedOpenCodeBinary = null,
120
+ restartOpenCode = null,
121
+ getRuntimeContract = () => null,
122
+ getOpenCodeServeVersion = () => null,
123
+ getOpenCodeCliVersion = () => null,
124
+ getResolvedOpenCodeBinary = () => null,
125
+ getResolvedOpenCodeBinarySource = () => null,
126
+ getActiveSessionCount = () => 0,
116
127
  getSmallModelService: routeGetSmallModelService,
117
128
  getOpenCodePort,
118
129
  buildAugmentedPath,
@@ -132,6 +143,13 @@ export const createFeatureRoutesRuntime = (dependencies) => {
132
143
  getServerId,
133
144
  sessionIndexService,
134
145
  notifyContactTurnComplete,
146
+ sessionMetadataStore = null,
147
+ sessionArchiveService = null,
148
+ onSessionMetadataWritten = null,
149
+ persistSessionGoal = null,
150
+ persistSessionMetadata = null,
151
+ onSystemSessionPersisted = null,
152
+ readSessionMetadata = null,
135
153
  } = routeDependencies;
136
154
 
137
155
  registerSettingsUtilityRoutes(app, {
@@ -164,6 +182,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
164
182
  refreshOpenCodeAfterConfigChange,
165
183
  buildOpenCodeUrl,
166
184
  getOpenCodeAuthHeaders,
185
+ getIsExternalOpenCode,
186
+ getIsSharedOpenCodeService,
187
+ forceResolvedOpenCodeBinary,
188
+ restartOpenCode,
189
+ waitForOpenCodeReady,
190
+ getRuntimeContract,
191
+ getOpenCodeServeVersion,
192
+ getOpenCodeCliVersion,
193
+ getResolvedOpenCodeBinary,
194
+ getResolvedOpenCodeBinarySource,
195
+ getActiveSessionCount,
196
+ openchamberDataDir,
167
197
  onSettingsPersisted: (updated, changes) => {
168
198
  if (!questionAutoDelegateRuntime) return;
169
199
  if (!Object.prototype.hasOwnProperty.call(changes ?? {}, 'questionAutoDelegateEnabled')) return;
@@ -247,6 +277,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
247
277
  registerLlmRoutes(app, {
248
278
  buildOpenCodeUrl,
249
279
  getOpenCodeAuthHeaders,
280
+ persistSessionMetadata,
281
+ onSystemSessionPersisted,
250
282
  });
251
283
  assistantRoutesRuntime = registerAssistantRoutes(app, {
252
284
  openchamberDataDir,
@@ -273,6 +305,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
273
305
  },
274
306
  listScheduledTasks: (projectID) => projectConfigRuntime.listScheduledTasks(projectID),
275
307
  sessionIndexService,
308
+ archiveSessionHost: sessionArchiveService
309
+ ? async ({ sessionID, directory, archivedAt }) => sessionArchiveService.archiveSession(
310
+ sessionID,
311
+ directory,
312
+ archivedAt,
313
+ )
314
+ : null,
315
+ forgetSessionHost: sessionArchiveService
316
+ ? (sessionID) => sessionArchiveService.forgetSession(sessionID)
317
+ : null,
318
+ persistSessionMetadata,
319
+ onSystemSessionPersisted,
276
320
  upsertScheduledTask: (projectID, task) => projectConfigRuntime.upsertScheduledTask(projectID, task),
277
321
  syncScheduledTaskProject: (projectID) => scheduledTasksRuntime.syncProject(projectID),
278
322
  refreshAllowedRoots: refreshAssistantAllowedRoots,
@@ -293,9 +337,11 @@ export const createFeatureRoutesRuntime = (dependencies) => {
293
337
  resolveOptionalProjectDirectory,
294
338
  refreshOpenCodeAfterConfigChange,
295
339
  clientReloadDelayMs,
296
- getAgentSources,
297
- getAgentConfig,
298
- createAgent,
340
+ waitForOpenCodeReady,
341
+ getAgentSources,
342
+ getAgentConfig,
343
+ listDisabledAgentOverrides,
344
+ createAgent,
299
345
  updateAgent,
300
346
  deleteAgent,
301
347
  getCommandSources,
@@ -387,7 +433,36 @@ export const createFeatureRoutesRuntime = (dependencies) => {
387
433
  throw new Error('Small model service is not configured');
388
434
  });
389
435
  registerSmallModelRoutes(app, { getSmallModelService });
390
- registerSessionGoalRoutes(app);
436
+ registerSessionGoalRoutes(app, {
437
+ // Reuse the index-provided store seam — do not invent a second store.
438
+ persistSessionGoal: typeof persistSessionGoal === 'function'
439
+ ? persistSessionGoal
440
+ : (sessionMetadataStore
441
+ ? async (sessionId, _directory, goal) => {
442
+ const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
443
+ openchamber: { goal },
444
+ });
445
+ return metadata;
446
+ }
447
+ : null),
448
+ readSessionMetadata: typeof readSessionMetadata === 'function'
449
+ ? readSessionMetadata
450
+ : (sessionMetadataStore
451
+ ? (sessionId) => sessionMetadataStore.get(sessionId)
452
+ : null),
453
+ // Same arm path as PUT /api/openchamber/sessions/:id/metadata.
454
+ onGoalPersisted: typeof onSessionMetadataWritten === 'function'
455
+ ? onSessionMetadataWritten
456
+ : null,
457
+ });
458
+ if (sessionMetadataStore) {
459
+ registerSessionMetadataRoutes(app, {
460
+ sessionMetadataStore,
461
+ sessionArchiveService,
462
+ broadcastGlobalUiEvent,
463
+ onMetadataWritten: onSessionMetadataWritten,
464
+ });
465
+ }
391
466
  registerGitHubRoutes(app);
392
467
  const broadcastOpenChamberEvent = createOpenChamberEventBroadcaster({
393
468
  getOpenChamberEventClients,
@@ -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
 
@@ -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
+ };