@openchambery/web 2.0.0-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 (296) hide show
  1. package/README.md +15 -11
  2. package/bin/cli-opencode.test.js +4 -4
  3. package/bin/cli.js +1 -1
  4. package/bin/lib/cli-startup.js +1 -1
  5. package/bin/lib/cli-startup.test.js +18 -4
  6. package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-CzTtVMnI.js} +1 -1
  7. package/dist/assets/AssistantView-Cg1qQvVe.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-hOi72Wdx.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.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-SKi4oG0c.js → MarkdownRendererImpl-YM7iRmzK.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-BDy_oGJQ.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-iX_hb_Xt.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-CAMUupwR.js} +2 -2
  16. package/dist/assets/MobileSurface-CHbdPcLH.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-Ci1ODFFX.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.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-DfnsFa8y.js → SettingsWindow-B8d8qIQ7.js} +1 -1
  22. package/dist/assets/TerminalView-CMiw3s_c.js +8 -0
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.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-D-rATN7w.js → button-DIIrsMjW.js} +1 -1
  27. package/dist/assets/{collapsible-BXRd5_Hr.js → collapsible-CnvUeYst.js} +1 -1
  28. package/dist/assets/{context-menu-jGtH6fR9.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-DQf7Y_uR.js → diagram-rvn7_qBM.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.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-8r6F_Fza.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-CCE_4y5F.js → main-B7t9lavz.js} +2 -2
  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-1Qo6y6IJ.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-CPElzJ0n.js +3 -0
  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-BMvqa5we.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-gh-hVRIc.js → sessionLookup-Bss8M20i.js} +1 -1
  66. package/dist/assets/{sortable-tabs-strip-CudoSgV4.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-DiF6JmBP.js → terminalApi-mDR2jXTo.js} +1 -1
  70. package/dist/assets/textarea-XD2oX_Ee.js +1 -0
  71. package/dist/assets/{tooltip-CQvpK-UV.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-Cpmpo7Wb.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-DcbXQOkc.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-Bqn4AiBL.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/zh-CN-BhqSLgUY.js +15 -0
  86. package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
  87. package/dist/index.html +12 -12
  88. package/dist/mini-chat.html +12 -12
  89. package/dist/mobile.html +12 -12
  90. package/package.json +3 -2
  91. package/server/index.js +359 -63
  92. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  93. package/server/lib/assistants/contracts.js +11 -1
  94. package/server/lib/assistants/projection-history.test.js +265 -18
  95. package/server/lib/assistants/routes.js +6 -0
  96. package/server/lib/assistants/service.js +417 -231
  97. package/server/lib/assistants/service.test.js +392 -148
  98. package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
  99. package/server/lib/browser-provider/contract.js +312 -0
  100. package/server/lib/browser-provider/host.js +421 -0
  101. package/server/lib/browser-provider/host.test.js +304 -0
  102. package/server/lib/browser-provider/routes.js +122 -0
  103. package/server/lib/browser-provider/routes.test.js +120 -0
  104. package/server/lib/browser-provider/selection-store.js +43 -0
  105. package/server/lib/browser-provider/selection-store.test.js +35 -0
  106. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  107. package/server/lib/conversations/service.js +1 -0
  108. package/server/lib/conversations/service.test.js +16 -0
  109. package/server/lib/conversations/validation.js +11 -1
  110. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  111. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  112. package/server/lib/event-stream/global-hub.js +49 -4
  113. package/server/lib/event-stream/reasoning-projection.js +16 -1
  114. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  115. package/server/lib/event-stream/runtime.js +57 -20
  116. package/server/lib/event-stream/runtime.test.js +112 -0
  117. package/server/lib/llm/DOCUMENTATION.md +33 -9
  118. package/server/lib/llm/catalog.js +1 -1
  119. package/server/lib/llm/completions.js +5 -1
  120. package/server/lib/llm/completions.test.js +3 -3
  121. package/server/lib/llm/generate.js +237 -145
  122. package/server/lib/llm/generate.test.js +201 -46
  123. package/server/lib/llm/routes.js +2 -0
  124. package/server/lib/message-queue/runtime.js +15 -2
  125. package/server/lib/notifications/DOCUMENTATION.md +3 -1
  126. package/server/lib/notifications/runtime.js +133 -31
  127. package/server/lib/notifications/runtime.test.js +103 -0
  128. package/server/lib/notifications/template-runtime.js +130 -38
  129. package/server/lib/notifications/template-runtime.test.js +96 -0
  130. package/server/lib/opencode/DOCUMENTATION.md +89 -17
  131. package/server/lib/opencode/agent-document.d.ts +28 -0
  132. package/server/lib/opencode/agent-document.js +280 -0
  133. package/server/lib/opencode/agent-document.test.js +137 -0
  134. package/server/lib/opencode/agents.js +96 -0
  135. package/server/lib/opencode/auth-state-runtime.js +5 -0
  136. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  137. package/server/lib/opencode/config-entity-routes.js +42 -99
  138. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  139. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  140. package/server/lib/opencode/ensure-cli.d.ts +8 -0
  141. package/server/lib/opencode/ensure-cli.js +1 -2
  142. package/server/lib/opencode/env-runtime.js +40 -26
  143. package/server/lib/opencode/env-runtime.test.js +10 -17
  144. package/server/lib/opencode/feature-routes-runtime.js +53 -4
  145. package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
  146. package/server/lib/opencode/http-server-connections.js +98 -0
  147. package/server/lib/opencode/http-server-connections.test.js +321 -0
  148. package/server/lib/opencode/lifecycle.js +349 -39
  149. package/server/lib/opencode/lifecycle.test.js +322 -0
  150. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  151. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  152. package/server/lib/opencode/openchamber-routes.js +7 -1
  153. package/server/lib/opencode/opencode2-pin.js +4 -4
  154. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  155. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  156. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  157. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  158. package/server/lib/opencode/plugin-routes.js +3 -18
  159. package/server/lib/opencode/plugin-routes.test.js +9 -9
  160. package/server/lib/opencode/provider-catalog.js +58 -12
  161. package/server/lib/opencode/provider-catalog.test.js +48 -0
  162. package/server/lib/opencode/proxy.js +254 -46
  163. package/server/lib/opencode/proxy.test.js +238 -5
  164. package/server/lib/opencode/routes.js +326 -62
  165. package/server/lib/opencode/routes.test.js +278 -12
  166. package/server/lib/opencode/runtime-contract.js +401 -0
  167. package/server/lib/opencode/runtime-contract.test.js +214 -0
  168. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  169. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  170. package/server/lib/opencode/server-utils-runtime.js +33 -0
  171. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  172. package/server/lib/opencode/shared-service.js +147 -0
  173. package/server/lib/opencode/shared-service.test.js +93 -0
  174. package/server/lib/opencode/shared.js +3 -3
  175. package/server/lib/opencode/shutdown-runtime.js +15 -6
  176. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  177. package/server/lib/opencode/skill-routes.js +20 -33
  178. package/server/lib/opencode/skill-routes.test.js +35 -2
  179. package/server/lib/opencode/skills.test.js +15 -0
  180. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  181. package/server/lib/opencode/static-routes-runtime.js +3 -1
  182. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  183. package/server/lib/opencode/upgrade-screen.d.ts +30 -0
  184. package/server/lib/opencode/upgrade-screen.js +299 -0
  185. package/server/lib/opencode/upgrade-screen.test.js +196 -0
  186. package/server/lib/opencode/v2-client.js +14 -3
  187. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  188. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  189. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  190. package/server/lib/permission-auto-accept/runtime.js +14 -7
  191. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  192. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  193. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
  194. package/server/lib/scheduled-tasks/runtime.js +52 -4
  195. package/server/lib/scheduled-tasks/runtime.test.js +80 -1
  196. package/server/lib/session-goal/DOCUMENTATION.md +113 -13
  197. package/server/lib/session-goal/routes.js +9 -0
  198. package/server/lib/session-goal/runtime.js +1579 -117
  199. package/server/lib/session-goal/runtime.test.js +1453 -84
  200. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  201. package/server/lib/session-index/event-ingest.js +89 -16
  202. package/server/lib/session-index/event-ingest.test.js +73 -0
  203. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  204. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  205. package/server/lib/session-index/routes.js +63 -10
  206. package/server/lib/session-index/routes.test.js +8 -8
  207. package/server/lib/session-index/service.js +7 -1
  208. package/server/lib/session-index/sync-runtime.js +161 -34
  209. package/server/lib/session-index/sync-runtime.test.js +155 -1
  210. package/server/lib/session-metadata/DOCUMENTATION.md +73 -18
  211. package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
  212. package/server/lib/session-metadata/opencode-session-record.js +316 -0
  213. package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
  214. package/server/lib/session-metadata/routes.js +112 -9
  215. package/server/lib/session-metadata/routes.test.js +47 -2
  216. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  217. package/server/lib/session-metadata/session-archive.js +654 -0
  218. package/server/lib/session-metadata/session-archive.test.js +359 -0
  219. package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
  220. package/server/lib/session-metadata/session-metadata-store.js +270 -83
  221. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  222. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  223. package/server/lib/session-metadata/session-projection.js +279 -0
  224. package/server/lib/session-metadata/session-projection.test.js +64 -0
  225. package/server/lib/session-metadata/system-session.js +55 -0
  226. package/server/lib/session-metadata/system-session.test.js +55 -0
  227. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  228. package/server/lib/session-title/native-runtime.test.js +167 -0
  229. package/server/lib/session-title/runtime.js +69 -44
  230. package/server/lib/session-title/session-access.js +46 -0
  231. package/server/lib/session-title/session-access.test.js +91 -0
  232. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  233. package/server/lib/session-turn-pages/routes.js +17 -102
  234. package/server/lib/session-turn-pages/routes.test.js +281 -0
  235. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  236. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  237. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  238. package/server/lib/small-model/catalog.js +31 -51
  239. package/server/lib/small-model/catalog.test.js +65 -10
  240. package/server/lib/small-model/custom-api.js +68 -0
  241. package/server/lib/small-model/custom-api.test.js +64 -0
  242. package/server/lib/small-model/index.js +124 -240
  243. package/server/lib/small-model/resolve.js +35 -120
  244. package/server/lib/small-model/resolve.test.js +20 -110
  245. package/server/lib/small-model/routes.js +1 -3
  246. package/server/lib/small-model/summary-settings.test.js +157 -253
  247. package/server/opencode-proxy.test.js +2 -0
  248. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  249. package/dist/assets/Icon-DD-BuMfZ.js +0 -1
  250. package/dist/assets/MarkdownRenderer-bc_p1H7l.js +0 -2
  251. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  252. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  253. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  254. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  255. package/dist/assets/TerminalView-Dh9MWCpS.js +0 -8
  256. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  257. package/dist/assets/es-c52LTHHe.js +0 -15
  258. package/dist/assets/fileMentionTouchSelection-BvrNCroG.js +0 -4
  259. package/dist/assets/fr-PisFbUE8.js +0 -13
  260. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  261. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  262. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  263. package/dist/assets/ko-ClFys5o2.js +0 -15
  264. package/dist/assets/main-Dn9rpsgl.js +0 -169
  265. package/dist/assets/main-qSeeJRW1.css +0 -1
  266. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  267. package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
  268. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  269. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  270. package/dist/assets/multirun-BvSG94mW.js +0 -4
  271. package/dist/assets/persistence-Ce3AnpEr.js +0 -3
  272. package/dist/assets/pl-CnaKWN5F.js +0 -15
  273. package/dist/assets/platform-CWTIp02q.js +0 -4
  274. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  275. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  276. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  277. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  278. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  279. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  280. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  281. package/dist/assets/src-BzX5UI-c.css +0 -2
  282. package/dist/assets/store-DJczmczv.js +0 -15
  283. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  284. package/dist/assets/uk-CaX0iqkB.js +0 -15
  285. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  286. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  287. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  288. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  289. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  290. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +0 -12
  291. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  292. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  293. package/server/lib/small-model/call.js +0 -427
  294. package/server/lib/small-model/call.test.js +0 -382
  295. package/server/lib/small-model/opencode-session.js +0 -183
  296. package/server/lib/small-model/opencode-session.test.js +0 -147
package/server/index.js CHANGED
@@ -39,6 +39,7 @@ import { resolveOpenCodeEnvConfig } from './lib/opencode/env-config.js';
39
39
  import { createHmrStateRuntime } from './lib/opencode/hmr-state-runtime.js';
40
40
  import { createOpenCodeNetworkRuntime } from './lib/opencode/network-runtime.js';
41
41
  import { createOpenCodeAuthStateRuntime } from './lib/opencode/auth-state-runtime.js';
42
+ import { createSharedOpenCodeService, isSharedServiceEnabled } from './lib/opencode/shared-service.js';
42
43
  import { createProjectDirectoryRuntime } from './lib/opencode/project-directory-runtime.js';
43
44
  import { createSettingsNormalizationRuntime } from './lib/opencode/settings-normalization-runtime.js';
44
45
  import { createSettingsHelpers } from './lib/opencode/settings-helpers.js';
@@ -52,6 +53,11 @@ import {
52
53
  } from './lib/opencode/core-routes.js';
53
54
  import { registerOpenChamberRoutes } from './lib/opencode/openchamber-routes.js';
54
55
  import { createServerUtilsRuntime } from './lib/opencode/server-utils-runtime.js';
56
+ import {
57
+ configureServerOpenCodeFetchGate,
58
+ createServerOpenCodeFetch,
59
+ wrapFetchWithRuntimeContractGate,
60
+ } from './lib/opencode/server-opencode-fetch.js';
55
61
  import { createStaticRoutesRuntime } from './lib/opencode/static-routes-runtime.js';
56
62
  import { createSettingsRuntime } from './lib/opencode/settings-runtime.js';
57
63
  import { createOpenCodeResolutionRuntime } from './lib/opencode/opencode-resolution-runtime.js';
@@ -59,6 +65,7 @@ import { createBootstrapRuntime } from './lib/opencode/bootstrap-runtime.js';
59
65
  import { createSessionRuntime } from './lib/opencode/session-runtime.js';
60
66
  import { createOpenCodeWatcherRuntime } from './lib/opencode/watcher.js';
61
67
  import { createSessionTitleRuntime } from './lib/session-title/runtime.js';
68
+ import { createTitleSessionAccess } from './lib/session-title/session-access.js';
62
69
  import { createSessionIndexService } from './lib/session-index/service.js';
63
70
  import { createSessionIndexSyncRuntime } from './lib/session-index/sync-runtime.js';
64
71
  import { resolveSessionIndexDbPath } from './lib/session-index/resolve-db-path.js';
@@ -68,8 +75,24 @@ import { resolveTranscriptCacheDbPath } from './lib/transcript-cache/resolve-db-
68
75
  import { createMessageQueueRuntime } from './lib/message-queue/runtime.js';
69
76
  import { resolveMessageQueueDbPath } from './lib/message-queue/resolve-db-path.js';
70
77
  import { createOpenChamberEventBroadcaster } from './lib/opencode/feature-routes-runtime.js';
71
- import { createSessionGoalRuntime } from './lib/session-goal/runtime.js';
78
+ import {
79
+ createSessionGoalRuntime,
80
+ withGoalExecutionGeneration,
81
+ } from './lib/session-goal/runtime.js';
72
82
  import { createSessionMetadataStore } from './lib/session-metadata/session-metadata-store.js';
83
+ import {
84
+ createHttpSessionRecordClient,
85
+ retainOpenCodeSessions,
86
+ startSideStoreMigration,
87
+ } from './lib/session-metadata/opencode-session-record.js';
88
+ import {
89
+ createSessionArchiveService,
90
+ createUpstreamSessionFetcher,
91
+ } from './lib/session-metadata/session-archive.js';
92
+ import {
93
+ projectSessionLifecyclePayload,
94
+ projectSessionWithStoredMap,
95
+ } from './lib/session-metadata/session-projection.js';
73
96
  import { createScheduledTasksRuntime } from './lib/scheduled-tasks/runtime.js';
74
97
  import { createScheduledTaskRunHistoryStore } from './lib/scheduled-tasks/run-history-store.js';
75
98
  import { createServerStartupRuntime } from './lib/opencode/server-startup-runtime.js';
@@ -89,6 +112,7 @@ import { createNotificationTemplateRuntime } from './lib/notifications/template-
89
112
  import { createPermissionAutoAcceptRuntime } from './lib/permission-auto-accept/runtime.js';
90
113
  import { createQuestionAutoDelegateRuntime } from './lib/question-auto-delegate/runtime.js';
91
114
  import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
115
+ import { attachHttpServerConnectionTracker } from './lib/opencode/http-server-connections.js';
92
116
  import { createProjectConfigRuntime } from './lib/projects/project-config.js';
93
117
  import { createRemoteClientAuthRuntime } from './lib/client-auth/remote-clients.js';
94
118
  import { applyRuntimeCorsHeaders } from './lib/request-cors.js';
@@ -447,6 +471,11 @@ let lastOpenCodeError = null;
447
471
  let lastOpenCodeLaunchDiagnostics = null;
448
472
  let isOpenCodeReady = false;
449
473
  let v1Migration = null;
474
+ let openCodeServeVersion = null;
475
+ let openCodeCliVersion = null;
476
+ let runtimeContract = null;
477
+ /** Bumps on start/restart so stale async health cannot re-permit an old instance. */
478
+ let runtimeContractGeneration = 0;
450
479
  let openCodeNotReadySince = 0;
451
480
  let isExternalOpenCode = false;
452
481
  let exitOnShutdown = true;
@@ -551,6 +580,7 @@ const openCodeAuthStateRuntime = createOpenCodeAuthStateRuntime({
551
580
  const getOpenCodeAuthHeaders = (...args) => openCodeAuthStateRuntime.getOpenCodeAuthHeaders(...args);
552
581
  const isOpenCodeConnectionSecure = (...args) => openCodeAuthStateRuntime.isOpenCodeConnectionSecure(...args);
553
582
  const ensureLocalOpenCodeServerPassword = (...args) => openCodeAuthStateRuntime.ensureLocalOpenCodeServerPassword(...args);
583
+ const adoptOpenCodeServerPassword = (...args) => openCodeAuthStateRuntime.adoptOpenCodeServerPassword(...args);
554
584
 
555
585
  const openCodeNetworkState = {};
556
586
  Object.defineProperties(openCodeNetworkState, {
@@ -574,6 +604,15 @@ const buildOpenCodeUrl = (...args) => openCodeNetworkRuntime.buildOpenCodeUrl(..
574
604
  const ensureOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.ensureOpenCodeApiPrefix(...args);
575
605
  const scheduleOpenCodeApiDetection = (...args) => openCodeNetworkRuntime.scheduleOpenCodeApiDetection(...args);
576
606
 
607
+ // Host OpenCode transport gate (queue/goal/scheduled/assistants + v2-client).
608
+ // Registered before background runtimes so direct OpenCode writes share proxy admission.
609
+ configureServerOpenCodeFetchGate(() => runtimeContract);
610
+ const serverOpenCodeFetch = createServerOpenCodeFetch({
611
+ buildOpenCodeUrl,
612
+ getOpenCodeAuthHeaders,
613
+ getRuntimeContract: () => runtimeContract,
614
+ });
615
+
577
616
  const ENV_CONFIGURED_API_PREFIX = normalizeApiPrefix(
578
617
  process.env.OPENCODE_API_PREFIX || process.env.OPENCHAMBER_API_PREFIX || ''
579
618
  );
@@ -624,6 +663,7 @@ const searchPathFor = (...args) => openCodeEnvRuntime.searchPathFor(...args);
624
663
  const resolveGitBinaryForSpawn = (...args) => openCodeEnvRuntime.resolveGitBinaryForSpawn(...args);
625
664
  const resolveManagedOpenCodeLaunchSpec = (...args) => openCodeEnvRuntime.resolveManagedOpenCodeLaunchSpec(...args);
626
665
  const clearResolvedOpenCodeBinary = (...args) => openCodeEnvRuntime.clearResolvedOpenCodeBinary(...args);
666
+ const forceResolvedOpenCodeBinary = (...args) => openCodeEnvRuntime.forceResolvedOpenCodeBinary(...args);
627
667
  const openCodeResolutionRuntime = createOpenCodeResolutionRuntime({
628
668
  path,
629
669
  resolveOpencodeCliPath,
@@ -699,6 +739,8 @@ const getSmallModelService = async () => {
699
739
  smallModelServiceInstance = createSmallModelService({
700
740
  buildOpenCodeUrl,
701
741
  getOpenCodeAuthHeaders,
742
+ persistSessionMetadata: persistSessionMetadataToStore,
743
+ onSystemSessionPersisted: hideSystemSessionFromIndex,
702
744
  });
703
745
  }
704
746
  return smallModelServiceInstance;
@@ -708,6 +750,19 @@ const sessionTitleRuntime = createSessionTitleRuntime({
708
750
  buildOpenCodeUrl,
709
751
  getOpenCodeAuthHeaders,
710
752
  getSmallModelService,
753
+ serverOpenCodeFetch,
754
+ sessionAccess: createTitleSessionAccess({
755
+ buildOpenCodeUrl,
756
+ getOpenCodeAuthHeaders,
757
+ readSessionMetadata: (sessionID) => sessionMetadataStore.get(sessionID),
758
+ persistSessionMetadata: (sessionID, patch) => persistSessionMetadataToStore(sessionID, patch),
759
+ publishSession: (session, directory) => {
760
+ const payload = { type: 'session.updated', properties: { info: session, directory } };
761
+ broadcastGlobalUiEvent(payload);
762
+ const changed = sessionIndexServiceRef?.upsertAndReportChange(session);
763
+ if (changed) sessionIndexSyncRuntimeRef?.publishChange();
764
+ },
765
+ }),
711
766
  });
712
767
 
713
768
  // Forward declaration: question auto-delegate is created after the event hub;
@@ -716,11 +771,141 @@ let questionAutoDelegateRuntime = null;
716
771
  /** Filled inside main() once the SQLite session index is constructed. */
717
772
  let sessionIndexServiceRef = null;
718
773
 
719
- const sessionMetadataStore = createSessionMetadataStore({ dataDir: OPENCHAMBER_DATA_DIR });
774
+ const sessionRecordClient = createHttpSessionRecordClient({
775
+ buildSessionUrl: (sessionID, directory) => {
776
+ const url = new URL(buildOpenCodeUrl(`/session/${encodeURIComponent(sessionID)}`));
777
+ if (directory) url.searchParams.set('directory', directory);
778
+ return url.toString();
779
+ },
780
+ getHeaders: () => getOpenCodeAuthHeaders(),
781
+ fetchFn: wrapFetchWithRuntimeContractGate(fetch),
782
+ });
783
+ const sessionMetadataStore = createSessionMetadataStore({
784
+ dataDir: OPENCHAMBER_DATA_DIR,
785
+ recordReader: (sessionID, directory) => sessionRecordClient.read(sessionID, directory),
786
+ recordWriter: (sessionID, metadata, directory) => sessionRecordClient.write({
787
+ sessionID,
788
+ metadata,
789
+ directory,
790
+ }),
791
+ });
792
+ const stopSessionRecordMigration = startSideStoreMigration({
793
+ load: () => sessionMetadataStore.load(),
794
+ readSession: (sessionID) => sessionRecordClient.read(sessionID),
795
+ writeMetadata: async (sessionID, metadata, record) => {
796
+ const directory = record?.location?.directory || record?.directory || undefined;
797
+ await sessionRecordClient.write({ sessionID, metadata, directory });
798
+ },
799
+ });
800
+ /**
801
+ * load() resolves `{ ok:false }` on read/corrupt failure (does not reject).
802
+ * Callers must check ok / isLoaded — never treat unresolved as empty success.
803
+ */
804
+ const ensureSessionMetadataReady = async () => {
805
+ try {
806
+ const result = await sessionMetadataStore.load();
807
+ return result?.ok === true && sessionMetadataStore.isLoaded();
808
+ } catch (error) {
809
+ console.warn('[session-metadata] load threw:', error?.message ?? error);
810
+ return false;
811
+ }
812
+ };
813
+ // Prime without blocking module init; production gates (hub/SSE/routes) await ready.
814
+ void ensureSessionMetadataReady().then((ok) => {
815
+ if (!ok) {
816
+ console.warn(
817
+ '[session-metadata] initial load not ready:',
818
+ sessionMetadataStore.getLoadFailureReason?.() || 'unknown',
819
+ );
820
+ }
821
+ });
822
+
823
+ /**
824
+ * Goal write through the Host metadata store.
825
+ * Advances executionGeneration on pause/resume/condition changes so late audits
826
+ * cannot commit against a prior execution permit. Broadcasts only after commit.
827
+ */
828
+ const persistSessionGoalToStore = async (sessionId, _directory, goal) => {
829
+ const result = await sessionMetadataStore.mutateSessionMetadata(sessionId, (current) => {
830
+ const previousGoal = current?.openchamber?.goal && typeof current.openchamber.goal === 'object'
831
+ ? current.openchamber.goal
832
+ : null;
833
+ const nextGoal = withGoalExecutionGeneration(previousGoal, goal);
834
+ return { ok: true, patch: { openchamber: { goal: nextGoal } } };
835
+ });
836
+ if (!result?.committed) {
837
+ throw new Error(result?.reason || 'session goal persist was rejected');
838
+ }
839
+ const metadata = result.metadata;
840
+ try {
841
+ broadcastGlobalUiEvent({
842
+ type: 'openchamber:session-metadata',
843
+ properties: { sessionID: sessionId, metadata },
844
+ });
845
+ } catch (error) {
846
+ console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
847
+ }
848
+ return metadata;
849
+ };
850
+
851
+ /**
852
+ * Isolation / Host metadata write. Same store + broadcast seam as goal persist.
853
+ * Callers pass an RFC 7386 merge patch; the store returns the committed object.
854
+ */
855
+ const persistSessionMetadataToStore = async (sessionId, patch) => {
856
+ const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, patch);
857
+ try {
858
+ broadcastGlobalUiEvent({
859
+ type: 'openchamber:session-metadata',
860
+ properties: { sessionID: sessionId, metadata },
861
+ });
862
+ } catch (error) {
863
+ console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
864
+ }
865
+ return metadata;
866
+ };
867
+
868
+ /**
869
+ * After Host isolation metadata is committed, drop the session from the
870
+ * sidebar index immediately. Do not wait for the next directory list sync.
871
+ * Reads refs at call time — scheduled-task runtime is constructed before main().
872
+ */
873
+ const hideSystemSessionFromIndex = ({ sessionID, directory, metadata } = {}) => {
874
+ if (!sessionIndexServiceRef || typeof sessionID !== 'string' || !sessionID) return;
875
+ const changed = sessionIndexServiceRef.upsertAndReportChange({
876
+ id: sessionID,
877
+ directory: directory || '',
878
+ metadata,
879
+ });
880
+ if (changed) {
881
+ sessionIndexSyncRuntimeRef?.publishChange();
882
+ }
883
+ };
884
+
885
+ /** Filled in main() once the session index + broadcast sinks exist. */
886
+ let sessionArchiveServiceRef = null;
887
+ let sessionIndexSyncRuntimeRef = null;
888
+
889
+ const readHostMetadataForSession = (sessionID) => {
890
+ const snap = sessionMetadataStore.getSnapshotSync();
891
+ if (!snap || typeof sessionID !== 'string') return null;
892
+ const host = snap[sessionID];
893
+ return host && typeof host === 'object' && !Array.isArray(host) ? host : null;
894
+ };
895
+
896
+ const projectHostSessionPayload = (payload) => {
897
+ const ready = sessionMetadataStore.isLoaded();
898
+ return projectSessionLifecyclePayload(
899
+ payload,
900
+ readHostMetadataForSession,
901
+ { hostReady: ready },
902
+ );
903
+ };
720
904
 
721
905
  const sessionGoalRuntime = createSessionGoalRuntime({
722
906
  buildOpenCodeUrl,
723
907
  getOpenCodeAuthHeaders,
908
+ serverOpenCodeFetch,
724
909
  getSmallModelService,
725
910
  shouldKeepGoalActiveForQuestion: (sessionId) => (
726
911
  questionAutoDelegateRuntime?.isAutoHandling(sessionId) === true
@@ -732,21 +917,26 @@ const sessionGoalRuntime = createSessionGoalRuntime({
732
917
  questionAutoDelegateRuntime?.pauseForSessionTree?.(sessionId, directory)
733
918
  ),
734
919
  readSessionMetadata: (sessionId) => sessionMetadataStore.get(sessionId),
735
- persistSessionGoal: async (sessionId, _directory, goal) => {
736
- const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
737
- openchamber: { goal },
738
- });
739
- // Broadcast only. Feeding this write back into processPayload would re-arm
740
- // the loop the runtime is already inside.
741
- try {
742
- broadcastGlobalUiEvent({
743
- type: 'openchamber:session-metadata',
744
- properties: { sessionID: sessionId, metadata },
745
- });
746
- } catch (error) {
747
- console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
920
+ // Conditional commit on the store's exclusive serial boundary (generation +
921
+ // status preconditions share the lock with persist). Broadcast-only on
922
+ // success — do not feed progress writes back into processPayload.
923
+ mutateSessionMetadata: async (sessionId, decide) => {
924
+ const result = await sessionMetadataStore.mutateSessionMetadata(sessionId, decide);
925
+ if (result?.committed) {
926
+ try {
927
+ broadcastGlobalUiEvent({
928
+ type: 'openchamber:session-metadata',
929
+ properties: { sessionID: sessionId, metadata: result.metadata },
930
+ });
931
+ } catch (error) {
932
+ console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
933
+ }
748
934
  }
749
- return goal;
935
+ return result;
936
+ },
937
+ persistSessionGoal: async (sessionId, directory, goal) => {
938
+ const metadata = await persistSessionGoalToStore(sessionId, directory, goal);
939
+ return metadata?.openchamber?.goal ?? goal;
750
940
  },
751
941
  emitGoalNotification: async ({ sessionId, directory, status, goal }) => {
752
942
  // The goal settle notification replaces the per-turn ready notifications
@@ -788,6 +978,7 @@ const globalMessageStreamHub = createGlobalMessageStreamHub({
788
978
  buildOpenCodeUrl,
789
979
  getOpenCodeAuthHeaders,
790
980
  upstreamStallTimeoutMs: getUpstreamStallTimeoutMs,
981
+ projectOutboundSessionPayload: projectHostSessionPayload,
791
982
  });
792
983
 
793
984
  const permissionAutoAcceptRuntime = createPermissionAutoAcceptRuntime({
@@ -930,6 +1121,9 @@ const serverUtilsRuntime = createServerUtilsRuntime({
930
1121
  openCodeNotReadySince,
931
1122
  isOpenCodeReady,
932
1123
  isRestartingOpenCode,
1124
+ v1Migration,
1125
+ runtimeContract,
1126
+ openCodeServeVersion,
933
1127
  }),
934
1128
  getOpenCodeAuthHeaders,
935
1129
  buildOpenCodeUrl,
@@ -957,6 +1151,12 @@ const serverUtilsRuntime = createServerUtilsRuntime({
957
1151
  return snapshot.PATH;
958
1152
  },
959
1153
  getStoredSessionMetadata: () => sessionMetadataStore.getAll(),
1154
+ getStoredSessionMetadataSync: () => sessionMetadataStore.getSnapshotSync(),
1155
+ ensureSessionMetadataReady,
1156
+ onSessionDeleted: (sessionID) => {
1157
+ if (!sessionArchiveServiceRef) return sessionMetadataStore.removeSession(sessionID);
1158
+ return sessionArchiveServiceRef.forgetSession(sessionID);
1159
+ },
960
1160
  });
961
1161
 
962
1162
  const setOpenCodePort = (...args) => serverUtilsRuntime.setOpenCodePort(...args);
@@ -1027,6 +1227,13 @@ Object.defineProperties(openCodeLifecycleState, {
1027
1227
  lastOpenCodeLaunchDiagnostics: { get: () => lastOpenCodeLaunchDiagnostics, set: (value) => { lastOpenCodeLaunchDiagnostics = value; } },
1028
1228
  isOpenCodeReady: { get: () => isOpenCodeReady, set: (value) => { isOpenCodeReady = value; } },
1029
1229
  v1Migration: { get: () => v1Migration, set: (value) => { v1Migration = value; } },
1230
+ openCodeServeVersion: { get: () => openCodeServeVersion, set: (value) => { openCodeServeVersion = value; } },
1231
+ openCodeCliVersion: { get: () => openCodeCliVersion, set: (value) => { openCodeCliVersion = value; } },
1232
+ runtimeContract: { get: () => runtimeContract, set: (value) => { runtimeContract = value; } },
1233
+ runtimeContractGeneration: {
1234
+ get: () => runtimeContractGeneration,
1235
+ set: (value) => { runtimeContractGeneration = value; },
1236
+ },
1030
1237
  openCodeNotReadySince: { get: () => openCodeNotReadySince, set: (value) => { openCodeNotReadySince = value; } },
1031
1238
  isExternalOpenCode: { get: () => isExternalOpenCode, set: (value) => { isExternalOpenCode = value; } },
1032
1239
  isShuttingDown: { get: () => isShuttingDown, set: (value) => { isShuttingDown = value; } },
@@ -1073,6 +1280,8 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
1073
1280
  ensureOpencodeCliEnv,
1074
1281
  ensurePinnedOpenCode2CliEnv,
1075
1282
  ensureLocalOpenCodeServerPassword,
1283
+ adoptOpenCodeServerPassword,
1284
+ sharedService: isSharedServiceEnabled() ? createSharedOpenCodeService() : null,
1076
1285
  resolveManagedOpenCodeLaunchSpec,
1077
1286
  setOpenCodePort,
1078
1287
  setDetectedOpenCodeApiPrefix,
@@ -1128,20 +1337,9 @@ const scheduledTasksRuntime = createScheduledTasksRuntime({
1128
1337
  runHistoryStore: scheduledTaskRunHistoryStore,
1129
1338
  // Same Host store seam as session-goal / manual UI metadata writes.
1130
1339
  readSessionMetadata: (sessionId) => sessionMetadataStore.get(sessionId),
1131
- persistSessionGoal: async (sessionId, _directory, goal) => {
1132
- const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
1133
- openchamber: { goal },
1134
- });
1135
- try {
1136
- broadcastGlobalUiEvent({
1137
- type: 'openchamber:session-metadata',
1138
- properties: { sessionID: sessionId, metadata },
1139
- });
1140
- } catch (error) {
1141
- console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
1142
- }
1143
- return metadata;
1144
- },
1340
+ persistSessionMetadata: persistSessionMetadataToStore,
1341
+ onSystemSessionPersisted: hideSystemSessionFromIndex,
1342
+ persistSessionGoal: persistSessionGoalToStore,
1145
1343
  // Arm the goal loop after first persist only — do not feed progress writes
1146
1344
  // back through processPayload (session-goal runtime already broadcasts those).
1147
1345
  onGoalPersisted: ({ sessionID, directory, metadata }) => {
@@ -1183,7 +1381,19 @@ const ensureGlobalWatcherStarted = async () => {
1183
1381
  return globalWatcherStartPromise;
1184
1382
  }
1185
1383
 
1186
- globalWatcherStartPromise = openCodeWatcherRuntime.start().catch((error) => {
1384
+ // Await Host metadata ready before the internal hub attaches upstream SSE so
1385
+ // session lifecycle frames are projected (not dropped) from the first event.
1386
+ globalWatcherStartPromise = (async () => {
1387
+ const ready = await ensureSessionMetadataReady();
1388
+ if (!ready) {
1389
+ const reason = sessionMetadataStore.getLoadFailureReason?.() || 'not ready';
1390
+ const error = new Error(`session metadata unavailable before global watcher start: ${reason}`);
1391
+ error.code = 'session_metadata_unavailable';
1392
+ error.retryable = true;
1393
+ throw error;
1394
+ }
1395
+ return openCodeWatcherRuntime.start();
1396
+ })().catch((error) => {
1187
1397
  globalWatcherStartPromise = null;
1188
1398
  throw error;
1189
1399
  });
@@ -1193,15 +1403,30 @@ const ensureGlobalWatcherStarted = async () => {
1193
1403
 
1194
1404
  const completeOpenCodeStartup = () => {
1195
1405
  scheduleOpenCodeApiDetection();
1196
- if (openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode) {
1406
+ if (
1407
+ (openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode)
1408
+ || openCodeLifecycleRuntime.isSharedOpenCodeService()
1409
+ ) {
1197
1410
  startHealthMonitoring();
1198
1411
  }
1199
1412
  // The global watcher used to start only for desktop notifications; session-title
1200
1413
  // and session-goal also ride its event hub, so it now starts unconditionally
1201
- // once OpenCode is up.
1202
- void ensureGlobalWatcherStarted().catch((error) => {
1203
- console.warn(`Global event watcher startup failed: ${error?.message || error}`);
1204
- });
1414
+ // once OpenCode is up. Controlled retry when metadata is not yet ready.
1415
+ const startWatcherWithRetry = (attempt = 0) => {
1416
+ void ensureGlobalWatcherStarted().catch((error) => {
1417
+ const retryable = error?.retryable === true || error?.code === 'session_metadata_unavailable';
1418
+ const delays = [500, 1_500, 3_000, 6_000, 12_000];
1419
+ if (retryable && attempt < delays.length) {
1420
+ console.warn(
1421
+ `Global event watcher deferred (metadata): ${error?.message || error}; retry ${attempt + 1}/${delays.length}`,
1422
+ );
1423
+ setTimeout(() => startWatcherWithRetry(attempt + 1), delays[attempt]);
1424
+ return;
1425
+ }
1426
+ console.warn(`Global event watcher startup failed: ${error?.message || error}`);
1427
+ });
1428
+ };
1429
+ startWatcherWithRetry();
1205
1430
  };
1206
1431
 
1207
1432
  const bootstrapOpenCodeAtStartup = async (...args) => {
@@ -1213,7 +1438,6 @@ const retryOpenCodeStartup = async (...args) => {
1213
1438
  await openCodeLifecycleRuntime.retryOpenCodeStartup(...args);
1214
1439
  completeOpenCodeStartup();
1215
1440
  };
1216
- const killProcessOnPort = (...args) => openCodeLifecycleRuntime.killProcessOnPort(...args);
1217
1441
  const waitForPortRelease = (...args) => openCodeLifecycleRuntime.waitForPortRelease(...args);
1218
1442
 
1219
1443
  const fetchAgentsSnapshot = (...args) => serverUtilsRuntime.fetchAgentsSnapshot(...args);
@@ -1250,7 +1474,6 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
1250
1474
  setOpenCodeProcess: (value) => {
1251
1475
  openCodeProcess = value;
1252
1476
  },
1253
- killProcessOnPort,
1254
1477
  waitForPortRelease,
1255
1478
  getServer: () => server,
1256
1479
  getUiAuthController: () => uiAuthController,
@@ -1425,6 +1648,15 @@ async function main(options = {}) {
1425
1648
  buildOpenCodeUrl,
1426
1649
  getOpenCodeAuthHeaders,
1427
1650
  waitForOpenCodeReady,
1651
+ projectSessions: (sessions) => {
1652
+ const snap = sessionMetadataStore.getSnapshotSync();
1653
+ // Not ready / unavailable — throw so sync keeps prior directory rows.
1654
+ // A missing side-store row must not drop a session OpenCode still has.
1655
+ if (!snap) {
1656
+ throw new Error('session metadata is unavailable: Host store not ready');
1657
+ }
1658
+ return retainOpenCodeSessions(sessions, snap);
1659
+ },
1428
1660
  onRevisionTip: (tip) => {
1429
1661
  broadcastOpenChamberEvent({
1430
1662
  type: 'openchamber:session-index-changed',
@@ -1432,8 +1664,43 @@ async function main(options = {}) {
1432
1664
  });
1433
1665
  },
1434
1666
  });
1667
+ sessionIndexSyncRuntimeRef = sessionIndexSyncRuntime;
1668
+
1669
+ sessionArchiveServiceRef = createSessionArchiveService({
1670
+ sessionMetadataStore,
1671
+ fetchUpstreamSession: createUpstreamSessionFetcher({
1672
+ buildOpenCodeUrl,
1673
+ getOpenCodeAuthHeaders,
1674
+ }),
1675
+ sessionIndexService,
1676
+ broadcastSessionEvent: (payload, options = {}) => {
1677
+ try {
1678
+ broadcastGlobalUiEvent(payload, options);
1679
+ } catch (error) {
1680
+ console.warn('[session-archive] global UI broadcast failed:', error?.message ?? error);
1681
+ }
1682
+ try {
1683
+ broadcastOpenChamberEvent(payload);
1684
+ } catch (error) {
1685
+ console.warn('[session-archive] openchamber event broadcast failed:', error?.message ?? error);
1686
+ }
1687
+ },
1688
+ onIndexChanged: () => {
1689
+ sessionIndexSyncRuntimeRef?.publishChange();
1690
+ },
1691
+ });
1692
+
1435
1693
  const unsubscribeSessionIndexEvents = globalMessageStreamHub.subscribeEvent((event) => {
1436
- if (applySessionIndexEvent(sessionIndexService, event)) {
1694
+ if (applySessionIndexEvent(sessionIndexService, event, Date.now(), {
1695
+ isHostReady: () => sessionMetadataStore.isLoaded(),
1696
+ projectSession: (session) => {
1697
+ if (!sessionMetadataStore.isLoaded()) return null;
1698
+ const snap = sessionMetadataStore.getSnapshotSync();
1699
+ if (!snap) return null;
1700
+ return projectSessionWithStoredMap(session, snap);
1701
+ },
1702
+ onSessionDeleted: (sessionID) => sessionArchiveServiceRef?.forgetSession(sessionID),
1703
+ })) {
1437
1704
  sessionIndexSyncRuntime?.publishChange();
1438
1705
  }
1439
1706
  });
@@ -1499,6 +1766,9 @@ async function main(options = {}) {
1499
1766
  }));
1500
1767
  expressApp = app;
1501
1768
  server = http.createServer(app);
1769
+ // Track server-owned sockets so forceCloseConnections can destroy upgraded
1770
+ // WS/SSE leftovers that closeAllConnections does not release.
1771
+ attachHttpServerConnectionTracker(server);
1502
1772
  let realtimeProxyRuntime = { stop: () => {} };
1503
1773
 
1504
1774
  // The relay service is constructed further below (it depends on the active
@@ -1526,6 +1796,9 @@ async function main(options = {}) {
1526
1796
  openCodeApiPrefixDetected: true,
1527
1797
  isOpenCodeReady,
1528
1798
  v1Migration,
1799
+ openCodeServeVersion,
1800
+ openCodeCliVersion,
1801
+ runtimeContract,
1529
1802
  lastOpenCodeError,
1530
1803
  lastOpenCodeLaunchDiagnostics,
1531
1804
  opencodeBinaryResolved: resolvedOpencodeBinary || null,
@@ -1636,6 +1909,8 @@ async function main(options = {}) {
1636
1909
  setAutoAcceptSession,
1637
1910
  sessionIndexService,
1638
1911
  sessionIndexSyncRuntime,
1912
+ // Production snapshot/lookup filter: committed Host archive authority.
1913
+ getCommittedHostMetadata: async () => sessionMetadataStore.getAll(),
1639
1914
  transcriptCacheService,
1640
1915
  });
1641
1916
  uiAuthController = bootstrapResult.uiAuthController;
@@ -1731,24 +2006,45 @@ async function main(options = {}) {
1731
2006
  buildOpenCodeUrl,
1732
2007
  getOpenCodeAuthHeaders,
1733
2008
  getIsExternalOpenCode: () => isExternalOpenCode,
2009
+ getIsSharedOpenCodeService: () => openCodeLifecycleRuntime.isSharedOpenCodeService(),
2010
+ forceResolvedOpenCodeBinary,
2011
+ restartOpenCode,
2012
+ waitForOpenCodeReady,
2013
+ getRuntimeContract: () => runtimeContract,
2014
+ getOpenCodeServeVersion: () => openCodeServeVersion,
2015
+ getOpenCodeCliVersion: () => openCodeCliVersion,
2016
+ getResolvedOpenCodeBinary: () => resolvedOpencodeBinary,
2017
+ getResolvedOpenCodeBinarySource: () => resolvedOpencodeBinarySource,
2018
+ getActiveSessionCount,
1734
2019
  sessionMetadataStore,
2020
+ sessionArchiveService: {
2021
+ setArchive: (...args) => {
2022
+ if (!sessionArchiveServiceRef) {
2023
+ const error = new Error('session archive is not configured');
2024
+ error.status = 503;
2025
+ throw error;
2026
+ }
2027
+ return sessionArchiveServiceRef.setArchive(...args);
2028
+ },
2029
+ archiveSession: (...args) => {
2030
+ if (!sessionArchiveServiceRef) {
2031
+ const error = new Error('session archive is not configured');
2032
+ error.status = 503;
2033
+ throw error;
2034
+ }
2035
+ return sessionArchiveServiceRef.archiveSession(...args);
2036
+ },
2037
+ },
1735
2038
  // Same store seam as sessionGoalRuntime / scheduledTasksRuntime.
1736
2039
  readSessionMetadata: (sessionId) => sessionMetadataStore.get(sessionId),
1737
- persistSessionGoal: async (sessionId, _directory, goal) => {
1738
- const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
1739
- openchamber: { goal },
1740
- });
1741
- try {
1742
- broadcastGlobalUiEvent({
1743
- type: 'openchamber:session-metadata',
1744
- properties: { sessionID: sessionId, metadata },
1745
- });
1746
- } catch (error) {
1747
- console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
1748
- }
1749
- return metadata;
1750
- },
2040
+ persistSessionMetadata: persistSessionMetadataToStore,
2041
+ onSystemSessionPersisted: hideSystemSessionFromIndex,
2042
+ persistSessionGoal: persistSessionGoalToStore,
1751
2043
  onSessionMetadataWritten: ({ sessionID, directory, metadata }) => {
2044
+ sessionTitleRuntime.processPayload({
2045
+ type: 'openchamber:session-metadata',
2046
+ properties: { sessionID, directory, metadata },
2047
+ }, directory || '');
1752
2048
  sessionGoalRuntime.processPayload({
1753
2049
  type: 'session.updated',
1754
2050
  properties: {
@@ -1811,6 +2107,8 @@ async function main(options = {}) {
1811
2107
  getOpenCodeAuthHeaders,
1812
2108
  globalEventHub: globalMessageStreamHub,
1813
2109
  processForwardedEventPayload,
2110
+ projectOutboundSessionPayload: projectHostSessionPayload,
2111
+ ensureSessionMetadataReady,
1814
2112
  messageStreamWsClients: uiNotificationWsClients,
1815
2113
  upstreamStallTimeoutMs: getUpstreamStallTimeoutMs,
1816
2114
  terminalHeartbeatIntervalMs: TERMINAL_INPUT_WS_HEARTBEAT_INTERVAL_MS,
@@ -1880,12 +2178,9 @@ async function main(options = {}) {
1880
2178
  restartOpenCode: () => restartOpenCode(),
1881
2179
  getOpenCodeProcessInfo: () => {
1882
2180
  const managed = Boolean((openCodeProcess || openCodePort) && !ENV_SKIP_OPENCODE_START && !isExternalOpenCode);
1883
- // Only ever expose pid/port for a server WE manage. The Electron-side
1884
- // killer kills by port (lsof + kill -KILL), so returning a port we don't
1885
- // own — e.g. an external/desktop OpenCode on 4096 we attached to — would
1886
- // let a single miscomputed `managed` flag take down the user's separate
1887
- // server. Structurally withhold what isn't ours so the killer has no
1888
- // target, instead of relying on the flag check alone.
2181
+ // Only ever expose pid/port for a server WE manage. Desktop no longer runs a
2182
+ // post-stop detached killer of a pre-captured pid; still withhold non-owned
2183
+ // process identity so diagnostics cannot imply we may kill an external serve.
1889
2184
  return {
1890
2185
  managed,
1891
2186
  pid: managed && typeof openCodeProcess?.pid === 'number' ? openCodeProcess.pid : null,
@@ -1899,6 +2194,8 @@ async function main(options = {}) {
1899
2194
  unsubscribeScheduledTaskEvents();
1900
2195
  sessionIndexSyncRuntime?.stop();
1901
2196
  sessionIndexService?.close();
2197
+ sessionArchiveServiceRef?.stop?.();
2198
+ stopSessionRecordMigration();
1902
2199
  } catch {
1903
2200
  // The index is a local cache; a failed close must not block shutdown.
1904
2201
  }
@@ -1926,11 +2223,10 @@ async function main(options = {}) {
1926
2223
  // best-effort shutdown of the dictation worker
1927
2224
  }
1928
2225
  try {
1929
- if (smallModelServiceInstance?.stop) {
1930
- await smallModelServiceInstance.stop();
1931
- }
2226
+ const { stopLlmTempDirectory } = await import('./lib/llm/temp-directory.js');
2227
+ await stopLlmTempDirectory();
1932
2228
  } catch {
1933
- // best-effort cleanup of the small-model temp OpenCode directory
2229
+ // best-effort cleanup of the LLM generator temp OpenCode directory
1934
2230
  }
1935
2231
  return gracefulShutdown({
1936
2232
  exitProcess: shutdownOptions.exitProcess ?? false,