@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) 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-Q1SmFRx2.js} +1 -1
  7. package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
  10. package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
  11. package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
  12. package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
  16. package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
  20. package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
  21. package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
  22. package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
  24. package/dist/assets/appThemeRegistry-DK7q2juD.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-CcNJUxUd.js} +1 -1
  27. package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
  28. package/dist/assets/context-menu-B2LXJgXL.js +1 -0
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
  31. package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
  33. package/dist/assets/es-CN3E_zXj.js +15 -0
  34. package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
  36. package/dist/assets/fr-9zZvQZD2.js +13 -0
  37. package/dist/assets/gitApi-yuWg9BN4.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
  39. package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
  40. package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
  41. package/dist/assets/ja-Dnjk_82N.js +15 -0
  42. package/dist/assets/ko-BxCEE99C.js +15 -0
  43. package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
  44. package/dist/assets/main-DiL6w6KF.js +169 -0
  45. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  46. package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
  47. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  48. package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
  49. package/dist/assets/miniChat-CwtUaElU.js +2 -0
  50. package/dist/assets/mobile-DB-6--ws.js +2 -0
  51. package/dist/assets/multirun-TAsxFI8e.js +4 -0
  52. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
  53. package/dist/assets/pl-CNNsWnPM.js +15 -0
  54. package/dist/assets/platform-C13fMVEs.js +6 -0
  55. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  56. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  57. package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
  58. package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
  59. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  60. package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
  61. package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
  62. package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
  63. package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
  64. package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
  65. package/dist/assets/src-ubCWXZhR.css +2 -0
  66. package/dist/assets/store-DLCiZaA6.js +15 -0
  67. package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
  68. package/dist/assets/textarea-BBOlRI9O.js +1 -0
  69. package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
  70. package/dist/assets/uk-rwQrxeYM.js +15 -0
  71. package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
  72. package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
  73. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  74. package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
  75. package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
  76. package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
  77. package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
  78. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  79. package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
  80. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  81. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
  83. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  84. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  87. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  88. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  89. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  90. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  91. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  92. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  99. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  100. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  101. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  102. package/dist/index.html +13 -13
  103. package/dist/mini-chat.html +13 -13
  104. package/dist/mobile.html +13 -13
  105. package/package.json +1 -1
  106. package/server/index.js +327 -62
  107. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  108. package/server/lib/assistants/contracts.js +11 -1
  109. package/server/lib/assistants/projection-history.test.js +265 -18
  110. package/server/lib/assistants/routes.js +6 -0
  111. package/server/lib/assistants/service.js +417 -231
  112. package/server/lib/assistants/service.test.js +392 -148
  113. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  114. package/server/lib/conversations/service.js +1 -0
  115. package/server/lib/conversations/service.test.js +16 -0
  116. package/server/lib/conversations/validation.js +11 -1
  117. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +49 -4
  120. package/server/lib/event-stream/reasoning-projection.js +16 -1
  121. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  122. package/server/lib/event-stream/runtime.js +57 -20
  123. package/server/lib/event-stream/runtime.test.js +112 -0
  124. package/server/lib/llm/DOCUMENTATION.md +33 -9
  125. package/server/lib/llm/catalog.js +1 -1
  126. package/server/lib/llm/completions.js +5 -1
  127. package/server/lib/llm/completions.test.js +3 -3
  128. package/server/lib/llm/generate.js +237 -145
  129. package/server/lib/llm/generate.test.js +201 -46
  130. package/server/lib/llm/routes.js +2 -0
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/notifications/runtime.js +23 -5
  133. package/server/lib/notifications/runtime.test.js +11 -0
  134. package/server/lib/opencode/DOCUMENTATION.md +85 -17
  135. package/server/lib/opencode/agent-document.d.ts +28 -0
  136. package/server/lib/opencode/agent-document.js +280 -0
  137. package/server/lib/opencode/agent-document.test.js +137 -0
  138. package/server/lib/opencode/agents.js +96 -0
  139. package/server/lib/opencode/auth-state-runtime.js +5 -0
  140. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  141. package/server/lib/opencode/config-entity-routes.js +42 -99
  142. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  143. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  144. package/server/lib/opencode/ensure-cli.js +1 -2
  145. package/server/lib/opencode/env-runtime.js +40 -26
  146. package/server/lib/opencode/env-runtime.test.js +10 -17
  147. package/server/lib/opencode/feature-routes-runtime.js +43 -4
  148. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  149. package/server/lib/opencode/http-server-connections.js +98 -0
  150. package/server/lib/opencode/http-server-connections.test.js +321 -0
  151. package/server/lib/opencode/lifecycle.js +349 -39
  152. package/server/lib/opencode/lifecycle.test.js +322 -0
  153. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  154. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  155. package/server/lib/opencode/openchamber-routes.js +7 -1
  156. package/server/lib/opencode/opencode2-pin.js +4 -4
  157. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  158. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  159. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  160. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  161. package/server/lib/opencode/plugin-routes.js +3 -18
  162. package/server/lib/opencode/plugin-routes.test.js +9 -9
  163. package/server/lib/opencode/provider-catalog.js +58 -12
  164. package/server/lib/opencode/provider-catalog.test.js +48 -0
  165. package/server/lib/opencode/proxy.js +252 -46
  166. package/server/lib/opencode/proxy.test.js +235 -2
  167. package/server/lib/opencode/routes.js +296 -62
  168. package/server/lib/opencode/routes.test.js +278 -12
  169. package/server/lib/opencode/runtime-contract.js +401 -0
  170. package/server/lib/opencode/runtime-contract.test.js +214 -0
  171. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  172. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  173. package/server/lib/opencode/server-utils-runtime.js +33 -0
  174. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  175. package/server/lib/opencode/shared-service.js +147 -0
  176. package/server/lib/opencode/shared-service.test.js +93 -0
  177. package/server/lib/opencode/shared.js +3 -3
  178. package/server/lib/opencode/shutdown-runtime.js +15 -6
  179. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  180. package/server/lib/opencode/skill-routes.js +20 -33
  181. package/server/lib/opencode/skill-routes.test.js +35 -2
  182. package/server/lib/opencode/skills.test.js +15 -0
  183. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  184. package/server/lib/opencode/static-routes-runtime.js +3 -1
  185. package/server/lib/opencode/static-routes-runtime.test.js +33 -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 +61 -16
  211. package/server/lib/session-metadata/routes.js +108 -9
  212. package/server/lib/session-metadata/routes.test.js +47 -2
  213. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  214. package/server/lib/session-metadata/session-archive.js +654 -0
  215. package/server/lib/session-metadata/session-archive.test.js +359 -0
  216. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  217. package/server/lib/session-metadata/session-metadata-store.js +212 -72
  218. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  219. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  220. package/server/lib/session-metadata/session-projection.js +255 -0
  221. package/server/lib/session-metadata/session-projection.test.js +64 -0
  222. package/server/lib/session-metadata/system-session.js +55 -0
  223. package/server/lib/session-metadata/system-session.test.js +55 -0
  224. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  225. package/server/lib/session-title/native-runtime.test.js +167 -0
  226. package/server/lib/session-title/runtime.js +67 -42
  227. package/server/lib/session-title/session-access.js +46 -0
  228. package/server/lib/session-title/session-access.test.js +91 -0
  229. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  230. package/server/lib/session-turn-pages/routes.js +17 -102
  231. package/server/lib/session-turn-pages/routes.test.js +281 -0
  232. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  233. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  234. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  235. package/server/lib/small-model/catalog.js +31 -51
  236. package/server/lib/small-model/catalog.test.js +65 -10
  237. package/server/lib/small-model/custom-api.js +68 -0
  238. package/server/lib/small-model/custom-api.test.js +64 -0
  239. package/server/lib/small-model/index.js +124 -240
  240. package/server/lib/small-model/resolve.js +35 -120
  241. package/server/lib/small-model/resolve.test.js +20 -110
  242. package/server/lib/small-model/routes.js +1 -3
  243. package/server/lib/small-model/summary-settings.test.js +157 -253
  244. package/server/opencode-proxy.test.js +2 -0
  245. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  246. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  247. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  248. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  249. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  250. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  251. package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
  252. package/dist/assets/context-menu-jGtH6fR9.js +0 -1
  253. package/dist/assets/es-c52LTHHe.js +0 -15
  254. package/dist/assets/fr-PisFbUE8.js +0 -13
  255. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  256. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  257. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  258. package/dist/assets/ko-ClFys5o2.js +0 -15
  259. package/dist/assets/main-Dn9rpsgl.js +0 -169
  260. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  261. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  262. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  263. package/dist/assets/multirun-BvSG94mW.js +0 -4
  264. package/dist/assets/pl-CnaKWN5F.js +0 -15
  265. package/dist/assets/platform-CWTIp02q.js +0 -4
  266. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  267. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  268. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  269. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  270. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  271. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  272. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  273. package/dist/assets/src-BzX5UI-c.css +0 -2
  274. package/dist/assets/store-DJczmczv.js +0 -15
  275. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  276. package/dist/assets/uk-CaX0iqkB.js +0 -15
  277. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  278. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  279. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  280. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  281. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  282. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  283. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  284. package/server/lib/small-model/call.js +0 -427
  285. package/server/lib/small-model/call.test.js +0 -382
  286. package/server/lib/small-model/opencode-session.js +0 -183
  287. 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,10 @@ 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
+ } from './lib/opencode/server-opencode-fetch.js';
55
60
  import { createStaticRoutesRuntime } from './lib/opencode/static-routes-runtime.js';
56
61
  import { createSettingsRuntime } from './lib/opencode/settings-runtime.js';
57
62
  import { createOpenCodeResolutionRuntime } from './lib/opencode/opencode-resolution-runtime.js';
@@ -59,6 +64,7 @@ import { createBootstrapRuntime } from './lib/opencode/bootstrap-runtime.js';
59
64
  import { createSessionRuntime } from './lib/opencode/session-runtime.js';
60
65
  import { createOpenCodeWatcherRuntime } from './lib/opencode/watcher.js';
61
66
  import { createSessionTitleRuntime } from './lib/session-title/runtime.js';
67
+ import { createTitleSessionAccess } from './lib/session-title/session-access.js';
62
68
  import { createSessionIndexService } from './lib/session-index/service.js';
63
69
  import { createSessionIndexSyncRuntime } from './lib/session-index/sync-runtime.js';
64
70
  import { resolveSessionIndexDbPath } from './lib/session-index/resolve-db-path.js';
@@ -68,8 +74,20 @@ import { resolveTranscriptCacheDbPath } from './lib/transcript-cache/resolve-db-
68
74
  import { createMessageQueueRuntime } from './lib/message-queue/runtime.js';
69
75
  import { resolveMessageQueueDbPath } from './lib/message-queue/resolve-db-path.js';
70
76
  import { createOpenChamberEventBroadcaster } from './lib/opencode/feature-routes-runtime.js';
71
- import { createSessionGoalRuntime } from './lib/session-goal/runtime.js';
77
+ import {
78
+ createSessionGoalRuntime,
79
+ withGoalExecutionGeneration,
80
+ } from './lib/session-goal/runtime.js';
72
81
  import { createSessionMetadataStore } from './lib/session-metadata/session-metadata-store.js';
82
+ import {
83
+ createSessionArchiveService,
84
+ createUpstreamSessionFetcher,
85
+ } from './lib/session-metadata/session-archive.js';
86
+ import {
87
+ projectSessionLifecyclePayload,
88
+ projectSessionWithHostMetadata,
89
+ projectSessionWithStoredMap,
90
+ } from './lib/session-metadata/session-projection.js';
73
91
  import { createScheduledTasksRuntime } from './lib/scheduled-tasks/runtime.js';
74
92
  import { createScheduledTaskRunHistoryStore } from './lib/scheduled-tasks/run-history-store.js';
75
93
  import { createServerStartupRuntime } from './lib/opencode/server-startup-runtime.js';
@@ -89,6 +107,7 @@ import { createNotificationTemplateRuntime } from './lib/notifications/template-
89
107
  import { createPermissionAutoAcceptRuntime } from './lib/permission-auto-accept/runtime.js';
90
108
  import { createQuestionAutoDelegateRuntime } from './lib/question-auto-delegate/runtime.js';
91
109
  import { createGracefulShutdownRuntime } from './lib/opencode/shutdown-runtime.js';
110
+ import { attachHttpServerConnectionTracker } from './lib/opencode/http-server-connections.js';
92
111
  import { createProjectConfigRuntime } from './lib/projects/project-config.js';
93
112
  import { createRemoteClientAuthRuntime } from './lib/client-auth/remote-clients.js';
94
113
  import { applyRuntimeCorsHeaders } from './lib/request-cors.js';
@@ -447,6 +466,11 @@ let lastOpenCodeError = null;
447
466
  let lastOpenCodeLaunchDiagnostics = null;
448
467
  let isOpenCodeReady = false;
449
468
  let v1Migration = null;
469
+ let openCodeServeVersion = null;
470
+ let openCodeCliVersion = null;
471
+ let runtimeContract = null;
472
+ /** Bumps on start/restart so stale async health cannot re-permit an old instance. */
473
+ let runtimeContractGeneration = 0;
450
474
  let openCodeNotReadySince = 0;
451
475
  let isExternalOpenCode = false;
452
476
  let exitOnShutdown = true;
@@ -551,6 +575,7 @@ const openCodeAuthStateRuntime = createOpenCodeAuthStateRuntime({
551
575
  const getOpenCodeAuthHeaders = (...args) => openCodeAuthStateRuntime.getOpenCodeAuthHeaders(...args);
552
576
  const isOpenCodeConnectionSecure = (...args) => openCodeAuthStateRuntime.isOpenCodeConnectionSecure(...args);
553
577
  const ensureLocalOpenCodeServerPassword = (...args) => openCodeAuthStateRuntime.ensureLocalOpenCodeServerPassword(...args);
578
+ const adoptOpenCodeServerPassword = (...args) => openCodeAuthStateRuntime.adoptOpenCodeServerPassword(...args);
554
579
 
555
580
  const openCodeNetworkState = {};
556
581
  Object.defineProperties(openCodeNetworkState, {
@@ -574,6 +599,15 @@ const buildOpenCodeUrl = (...args) => openCodeNetworkRuntime.buildOpenCodeUrl(..
574
599
  const ensureOpenCodeApiPrefix = (...args) => openCodeNetworkRuntime.ensureOpenCodeApiPrefix(...args);
575
600
  const scheduleOpenCodeApiDetection = (...args) => openCodeNetworkRuntime.scheduleOpenCodeApiDetection(...args);
576
601
 
602
+ // Host OpenCode transport gate (queue/goal/scheduled/assistants + v2-client).
603
+ // Registered before background runtimes so direct OpenCode writes share proxy admission.
604
+ configureServerOpenCodeFetchGate(() => runtimeContract);
605
+ const serverOpenCodeFetch = createServerOpenCodeFetch({
606
+ buildOpenCodeUrl,
607
+ getOpenCodeAuthHeaders,
608
+ getRuntimeContract: () => runtimeContract,
609
+ });
610
+
577
611
  const ENV_CONFIGURED_API_PREFIX = normalizeApiPrefix(
578
612
  process.env.OPENCODE_API_PREFIX || process.env.OPENCHAMBER_API_PREFIX || ''
579
613
  );
@@ -624,6 +658,7 @@ const searchPathFor = (...args) => openCodeEnvRuntime.searchPathFor(...args);
624
658
  const resolveGitBinaryForSpawn = (...args) => openCodeEnvRuntime.resolveGitBinaryForSpawn(...args);
625
659
  const resolveManagedOpenCodeLaunchSpec = (...args) => openCodeEnvRuntime.resolveManagedOpenCodeLaunchSpec(...args);
626
660
  const clearResolvedOpenCodeBinary = (...args) => openCodeEnvRuntime.clearResolvedOpenCodeBinary(...args);
661
+ const forceResolvedOpenCodeBinary = (...args) => openCodeEnvRuntime.forceResolvedOpenCodeBinary(...args);
627
662
  const openCodeResolutionRuntime = createOpenCodeResolutionRuntime({
628
663
  path,
629
664
  resolveOpencodeCliPath,
@@ -699,6 +734,8 @@ const getSmallModelService = async () => {
699
734
  smallModelServiceInstance = createSmallModelService({
700
735
  buildOpenCodeUrl,
701
736
  getOpenCodeAuthHeaders,
737
+ persistSessionMetadata: persistSessionMetadataToStore,
738
+ onSystemSessionPersisted: hideSystemSessionFromIndex,
702
739
  });
703
740
  }
704
741
  return smallModelServiceInstance;
@@ -708,6 +745,19 @@ const sessionTitleRuntime = createSessionTitleRuntime({
708
745
  buildOpenCodeUrl,
709
746
  getOpenCodeAuthHeaders,
710
747
  getSmallModelService,
748
+ serverOpenCodeFetch,
749
+ sessionAccess: createTitleSessionAccess({
750
+ buildOpenCodeUrl,
751
+ getOpenCodeAuthHeaders,
752
+ readSessionMetadata: (sessionID) => sessionMetadataStore.get(sessionID),
753
+ persistSessionMetadata: (sessionID, patch) => persistSessionMetadataToStore(sessionID, patch),
754
+ publishSession: (session, directory) => {
755
+ const payload = { type: 'session.updated', properties: { info: session, directory } };
756
+ broadcastGlobalUiEvent(payload);
757
+ const changed = sessionIndexServiceRef?.upsertAndReportChange(session);
758
+ if (changed) sessionIndexSyncRuntimeRef?.publishChange();
759
+ },
760
+ }),
711
761
  });
712
762
 
713
763
  // Forward declaration: question auto-delegate is created after the event hub;
@@ -717,10 +767,115 @@ let questionAutoDelegateRuntime = null;
717
767
  let sessionIndexServiceRef = null;
718
768
 
719
769
  const sessionMetadataStore = createSessionMetadataStore({ dataDir: OPENCHAMBER_DATA_DIR });
770
+ /**
771
+ * load() resolves `{ ok:false }` on read/corrupt failure (does not reject).
772
+ * Callers must check ok / isLoaded — never treat unresolved as empty success.
773
+ */
774
+ const ensureSessionMetadataReady = async () => {
775
+ try {
776
+ const result = await sessionMetadataStore.load();
777
+ return result?.ok === true && sessionMetadataStore.isLoaded();
778
+ } catch (error) {
779
+ console.warn('[session-metadata] load threw:', error?.message ?? error);
780
+ return false;
781
+ }
782
+ };
783
+ // Prime without blocking module init; production gates (hub/SSE/routes) await ready.
784
+ void ensureSessionMetadataReady().then((ok) => {
785
+ if (!ok) {
786
+ console.warn(
787
+ '[session-metadata] initial load not ready:',
788
+ sessionMetadataStore.getLoadFailureReason?.() || 'unknown',
789
+ );
790
+ }
791
+ });
792
+
793
+ /**
794
+ * Goal write through the Host metadata store.
795
+ * Advances executionGeneration on pause/resume/condition changes so late audits
796
+ * cannot commit against a prior execution permit. Broadcasts only after commit.
797
+ */
798
+ const persistSessionGoalToStore = async (sessionId, _directory, goal) => {
799
+ const result = await sessionMetadataStore.mutateSessionMetadata(sessionId, (current) => {
800
+ const previousGoal = current?.openchamber?.goal && typeof current.openchamber.goal === 'object'
801
+ ? current.openchamber.goal
802
+ : null;
803
+ const nextGoal = withGoalExecutionGeneration(previousGoal, goal);
804
+ return { ok: true, patch: { openchamber: { goal: nextGoal } } };
805
+ });
806
+ if (!result?.committed) {
807
+ throw new Error(result?.reason || 'session goal persist was rejected');
808
+ }
809
+ const metadata = result.metadata;
810
+ try {
811
+ broadcastGlobalUiEvent({
812
+ type: 'openchamber:session-metadata',
813
+ properties: { sessionID: sessionId, metadata },
814
+ });
815
+ } catch (error) {
816
+ console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
817
+ }
818
+ return metadata;
819
+ };
820
+
821
+ /**
822
+ * Isolation / Host metadata write. Same store + broadcast seam as goal persist.
823
+ * Callers pass an RFC 7386 merge patch; the store returns the committed object.
824
+ */
825
+ const persistSessionMetadataToStore = async (sessionId, patch) => {
826
+ const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, patch);
827
+ try {
828
+ broadcastGlobalUiEvent({
829
+ type: 'openchamber:session-metadata',
830
+ properties: { sessionID: sessionId, metadata },
831
+ });
832
+ } catch (error) {
833
+ console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
834
+ }
835
+ return metadata;
836
+ };
837
+
838
+ /**
839
+ * After Host isolation metadata is committed, drop the session from the
840
+ * sidebar index immediately. Do not wait for the next directory list sync.
841
+ * Reads refs at call time — scheduled-task runtime is constructed before main().
842
+ */
843
+ const hideSystemSessionFromIndex = ({ sessionID, directory, metadata } = {}) => {
844
+ if (!sessionIndexServiceRef || typeof sessionID !== 'string' || !sessionID) return;
845
+ const changed = sessionIndexServiceRef.upsertAndReportChange({
846
+ id: sessionID,
847
+ directory: directory || '',
848
+ metadata,
849
+ });
850
+ if (changed) {
851
+ sessionIndexSyncRuntimeRef?.publishChange();
852
+ }
853
+ };
854
+
855
+ /** Filled in main() once the session index + broadcast sinks exist. */
856
+ let sessionArchiveServiceRef = null;
857
+ let sessionIndexSyncRuntimeRef = null;
858
+
859
+ const readHostMetadataForSession = (sessionID) => {
860
+ const snap = sessionMetadataStore.getSnapshotSync();
861
+ if (!snap || typeof sessionID !== 'string') return null;
862
+ const host = snap[sessionID];
863
+ return host && typeof host === 'object' && !Array.isArray(host) ? host : null;
864
+ };
865
+
866
+ const projectHostSessionPayload = (payload) => {
867
+ const ready = sessionMetadataStore.isLoaded();
868
+ return projectSessionLifecyclePayload(
869
+ payload,
870
+ readHostMetadataForSession,
871
+ { hostReady: ready },
872
+ );
873
+ };
720
874
 
721
875
  const sessionGoalRuntime = createSessionGoalRuntime({
722
876
  buildOpenCodeUrl,
723
877
  getOpenCodeAuthHeaders,
878
+ serverOpenCodeFetch,
724
879
  getSmallModelService,
725
880
  shouldKeepGoalActiveForQuestion: (sessionId) => (
726
881
  questionAutoDelegateRuntime?.isAutoHandling(sessionId) === true
@@ -732,21 +887,26 @@ const sessionGoalRuntime = createSessionGoalRuntime({
732
887
  questionAutoDelegateRuntime?.pauseForSessionTree?.(sessionId, directory)
733
888
  ),
734
889
  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);
890
+ // Conditional commit on the store's exclusive serial boundary (generation +
891
+ // status preconditions share the lock with persist). Broadcast-only on
892
+ // success — do not feed progress writes back into processPayload.
893
+ mutateSessionMetadata: async (sessionId, decide) => {
894
+ const result = await sessionMetadataStore.mutateSessionMetadata(sessionId, decide);
895
+ if (result?.committed) {
896
+ try {
897
+ broadcastGlobalUiEvent({
898
+ type: 'openchamber:session-metadata',
899
+ properties: { sessionID: sessionId, metadata: result.metadata },
900
+ });
901
+ } catch (error) {
902
+ console.warn('[session-metadata] broadcast failed:', error?.message ?? error);
903
+ }
748
904
  }
749
- return goal;
905
+ return result;
906
+ },
907
+ persistSessionGoal: async (sessionId, directory, goal) => {
908
+ const metadata = await persistSessionGoalToStore(sessionId, directory, goal);
909
+ return metadata?.openchamber?.goal ?? goal;
750
910
  },
751
911
  emitGoalNotification: async ({ sessionId, directory, status, goal }) => {
752
912
  // The goal settle notification replaces the per-turn ready notifications
@@ -788,6 +948,7 @@ const globalMessageStreamHub = createGlobalMessageStreamHub({
788
948
  buildOpenCodeUrl,
789
949
  getOpenCodeAuthHeaders,
790
950
  upstreamStallTimeoutMs: getUpstreamStallTimeoutMs,
951
+ projectOutboundSessionPayload: projectHostSessionPayload,
791
952
  });
792
953
 
793
954
  const permissionAutoAcceptRuntime = createPermissionAutoAcceptRuntime({
@@ -930,6 +1091,9 @@ const serverUtilsRuntime = createServerUtilsRuntime({
930
1091
  openCodeNotReadySince,
931
1092
  isOpenCodeReady,
932
1093
  isRestartingOpenCode,
1094
+ v1Migration,
1095
+ runtimeContract,
1096
+ openCodeServeVersion,
933
1097
  }),
934
1098
  getOpenCodeAuthHeaders,
935
1099
  buildOpenCodeUrl,
@@ -957,6 +1121,12 @@ const serverUtilsRuntime = createServerUtilsRuntime({
957
1121
  return snapshot.PATH;
958
1122
  },
959
1123
  getStoredSessionMetadata: () => sessionMetadataStore.getAll(),
1124
+ getStoredSessionMetadataSync: () => sessionMetadataStore.getSnapshotSync(),
1125
+ ensureSessionMetadataReady,
1126
+ onSessionDeleted: (sessionID) => {
1127
+ if (!sessionArchiveServiceRef) return sessionMetadataStore.removeSession(sessionID);
1128
+ return sessionArchiveServiceRef.forgetSession(sessionID);
1129
+ },
960
1130
  });
961
1131
 
962
1132
  const setOpenCodePort = (...args) => serverUtilsRuntime.setOpenCodePort(...args);
@@ -1027,6 +1197,13 @@ Object.defineProperties(openCodeLifecycleState, {
1027
1197
  lastOpenCodeLaunchDiagnostics: { get: () => lastOpenCodeLaunchDiagnostics, set: (value) => { lastOpenCodeLaunchDiagnostics = value; } },
1028
1198
  isOpenCodeReady: { get: () => isOpenCodeReady, set: (value) => { isOpenCodeReady = value; } },
1029
1199
  v1Migration: { get: () => v1Migration, set: (value) => { v1Migration = value; } },
1200
+ openCodeServeVersion: { get: () => openCodeServeVersion, set: (value) => { openCodeServeVersion = value; } },
1201
+ openCodeCliVersion: { get: () => openCodeCliVersion, set: (value) => { openCodeCliVersion = value; } },
1202
+ runtimeContract: { get: () => runtimeContract, set: (value) => { runtimeContract = value; } },
1203
+ runtimeContractGeneration: {
1204
+ get: () => runtimeContractGeneration,
1205
+ set: (value) => { runtimeContractGeneration = value; },
1206
+ },
1030
1207
  openCodeNotReadySince: { get: () => openCodeNotReadySince, set: (value) => { openCodeNotReadySince = value; } },
1031
1208
  isExternalOpenCode: { get: () => isExternalOpenCode, set: (value) => { isExternalOpenCode = value; } },
1032
1209
  isShuttingDown: { get: () => isShuttingDown, set: (value) => { isShuttingDown = value; } },
@@ -1073,6 +1250,8 @@ const openCodeLifecycleRuntime = createOpenCodeLifecycleRuntime({
1073
1250
  ensureOpencodeCliEnv,
1074
1251
  ensurePinnedOpenCode2CliEnv,
1075
1252
  ensureLocalOpenCodeServerPassword,
1253
+ adoptOpenCodeServerPassword,
1254
+ sharedService: isSharedServiceEnabled() ? createSharedOpenCodeService() : null,
1076
1255
  resolveManagedOpenCodeLaunchSpec,
1077
1256
  setOpenCodePort,
1078
1257
  setDetectedOpenCodeApiPrefix,
@@ -1128,20 +1307,9 @@ const scheduledTasksRuntime = createScheduledTasksRuntime({
1128
1307
  runHistoryStore: scheduledTaskRunHistoryStore,
1129
1308
  // Same Host store seam as session-goal / manual UI metadata writes.
1130
1309
  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
- },
1310
+ persistSessionMetadata: persistSessionMetadataToStore,
1311
+ onSystemSessionPersisted: hideSystemSessionFromIndex,
1312
+ persistSessionGoal: persistSessionGoalToStore,
1145
1313
  // Arm the goal loop after first persist only — do not feed progress writes
1146
1314
  // back through processPayload (session-goal runtime already broadcasts those).
1147
1315
  onGoalPersisted: ({ sessionID, directory, metadata }) => {
@@ -1183,7 +1351,19 @@ const ensureGlobalWatcherStarted = async () => {
1183
1351
  return globalWatcherStartPromise;
1184
1352
  }
1185
1353
 
1186
- globalWatcherStartPromise = openCodeWatcherRuntime.start().catch((error) => {
1354
+ // Await Host metadata ready before the internal hub attaches upstream SSE so
1355
+ // session lifecycle frames are projected (not dropped) from the first event.
1356
+ globalWatcherStartPromise = (async () => {
1357
+ const ready = await ensureSessionMetadataReady();
1358
+ if (!ready) {
1359
+ const reason = sessionMetadataStore.getLoadFailureReason?.() || 'not ready';
1360
+ const error = new Error(`session metadata unavailable before global watcher start: ${reason}`);
1361
+ error.code = 'session_metadata_unavailable';
1362
+ error.retryable = true;
1363
+ throw error;
1364
+ }
1365
+ return openCodeWatcherRuntime.start();
1366
+ })().catch((error) => {
1187
1367
  globalWatcherStartPromise = null;
1188
1368
  throw error;
1189
1369
  });
@@ -1193,15 +1373,30 @@ const ensureGlobalWatcherStarted = async () => {
1193
1373
 
1194
1374
  const completeOpenCodeStartup = () => {
1195
1375
  scheduleOpenCodeApiDetection();
1196
- if (openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode) {
1376
+ if (
1377
+ (openCodeLifecycleState.openCodeProcess && !openCodeLifecycleState.isExternalOpenCode)
1378
+ || openCodeLifecycleRuntime.isSharedOpenCodeService()
1379
+ ) {
1197
1380
  startHealthMonitoring();
1198
1381
  }
1199
1382
  // The global watcher used to start only for desktop notifications; session-title
1200
1383
  // 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
- });
1384
+ // once OpenCode is up. Controlled retry when metadata is not yet ready.
1385
+ const startWatcherWithRetry = (attempt = 0) => {
1386
+ void ensureGlobalWatcherStarted().catch((error) => {
1387
+ const retryable = error?.retryable === true || error?.code === 'session_metadata_unavailable';
1388
+ const delays = [500, 1_500, 3_000, 6_000, 12_000];
1389
+ if (retryable && attempt < delays.length) {
1390
+ console.warn(
1391
+ `Global event watcher deferred (metadata): ${error?.message || error}; retry ${attempt + 1}/${delays.length}`,
1392
+ );
1393
+ setTimeout(() => startWatcherWithRetry(attempt + 1), delays[attempt]);
1394
+ return;
1395
+ }
1396
+ console.warn(`Global event watcher startup failed: ${error?.message || error}`);
1397
+ });
1398
+ };
1399
+ startWatcherWithRetry();
1205
1400
  };
1206
1401
 
1207
1402
  const bootstrapOpenCodeAtStartup = async (...args) => {
@@ -1213,7 +1408,6 @@ const retryOpenCodeStartup = async (...args) => {
1213
1408
  await openCodeLifecycleRuntime.retryOpenCodeStartup(...args);
1214
1409
  completeOpenCodeStartup();
1215
1410
  };
1216
- const killProcessOnPort = (...args) => openCodeLifecycleRuntime.killProcessOnPort(...args);
1217
1411
  const waitForPortRelease = (...args) => openCodeLifecycleRuntime.waitForPortRelease(...args);
1218
1412
 
1219
1413
  const fetchAgentsSnapshot = (...args) => serverUtilsRuntime.fetchAgentsSnapshot(...args);
@@ -1250,7 +1444,6 @@ const gracefulShutdownRuntime = createGracefulShutdownRuntime({
1250
1444
  setOpenCodeProcess: (value) => {
1251
1445
  openCodeProcess = value;
1252
1446
  },
1253
- killProcessOnPort,
1254
1447
  waitForPortRelease,
1255
1448
  getServer: () => server,
1256
1449
  getUiAuthController: () => uiAuthController,
@@ -1425,6 +1618,14 @@ async function main(options = {}) {
1425
1618
  buildOpenCodeUrl,
1426
1619
  getOpenCodeAuthHeaders,
1427
1620
  waitForOpenCodeReady,
1621
+ projectSessions: (sessions) => {
1622
+ const snap = sessionMetadataStore.getSnapshotSync();
1623
+ // Not ready / unavailable — throw so sync keeps prior directory rows.
1624
+ if (!snap) {
1625
+ throw new Error('session metadata is unavailable: Host store not ready');
1626
+ }
1627
+ return sessions.map((session) => projectSessionWithStoredMap(session, snap));
1628
+ },
1428
1629
  onRevisionTip: (tip) => {
1429
1630
  broadcastOpenChamberEvent({
1430
1631
  type: 'openchamber:session-index-changed',
@@ -1432,8 +1633,44 @@ async function main(options = {}) {
1432
1633
  });
1433
1634
  },
1434
1635
  });
1636
+ sessionIndexSyncRuntimeRef = sessionIndexSyncRuntime;
1637
+
1638
+ sessionArchiveServiceRef = createSessionArchiveService({
1639
+ sessionMetadataStore,
1640
+ fetchUpstreamSession: createUpstreamSessionFetcher({
1641
+ buildOpenCodeUrl,
1642
+ getOpenCodeAuthHeaders,
1643
+ }),
1644
+ sessionIndexService,
1645
+ broadcastSessionEvent: (payload, options = {}) => {
1646
+ try {
1647
+ broadcastGlobalUiEvent(payload, options);
1648
+ } catch (error) {
1649
+ console.warn('[session-archive] global UI broadcast failed:', error?.message ?? error);
1650
+ }
1651
+ try {
1652
+ broadcastOpenChamberEvent(payload);
1653
+ } catch (error) {
1654
+ console.warn('[session-archive] openchamber event broadcast failed:', error?.message ?? error);
1655
+ }
1656
+ },
1657
+ onIndexChanged: () => {
1658
+ sessionIndexSyncRuntimeRef?.publishChange();
1659
+ },
1660
+ });
1661
+
1435
1662
  const unsubscribeSessionIndexEvents = globalMessageStreamHub.subscribeEvent((event) => {
1436
- if (applySessionIndexEvent(sessionIndexService, event)) {
1663
+ if (applySessionIndexEvent(sessionIndexService, event, Date.now(), {
1664
+ isHostReady: () => sessionMetadataStore.isLoaded(),
1665
+ projectSession: (session) => {
1666
+ if (!sessionMetadataStore.isLoaded()) return null;
1667
+ return projectSessionWithHostMetadata(
1668
+ session,
1669
+ readHostMetadataForSession(session?.id),
1670
+ );
1671
+ },
1672
+ onSessionDeleted: (sessionID) => sessionArchiveServiceRef?.forgetSession(sessionID),
1673
+ })) {
1437
1674
  sessionIndexSyncRuntime?.publishChange();
1438
1675
  }
1439
1676
  });
@@ -1499,6 +1736,9 @@ async function main(options = {}) {
1499
1736
  }));
1500
1737
  expressApp = app;
1501
1738
  server = http.createServer(app);
1739
+ // Track server-owned sockets so forceCloseConnections can destroy upgraded
1740
+ // WS/SSE leftovers that closeAllConnections does not release.
1741
+ attachHttpServerConnectionTracker(server);
1502
1742
  let realtimeProxyRuntime = { stop: () => {} };
1503
1743
 
1504
1744
  // The relay service is constructed further below (it depends on the active
@@ -1526,6 +1766,9 @@ async function main(options = {}) {
1526
1766
  openCodeApiPrefixDetected: true,
1527
1767
  isOpenCodeReady,
1528
1768
  v1Migration,
1769
+ openCodeServeVersion,
1770
+ openCodeCliVersion,
1771
+ runtimeContract,
1529
1772
  lastOpenCodeError,
1530
1773
  lastOpenCodeLaunchDiagnostics,
1531
1774
  opencodeBinaryResolved: resolvedOpencodeBinary || null,
@@ -1636,6 +1879,8 @@ async function main(options = {}) {
1636
1879
  setAutoAcceptSession,
1637
1880
  sessionIndexService,
1638
1881
  sessionIndexSyncRuntime,
1882
+ // Production snapshot/lookup filter: committed Host archive authority.
1883
+ getCommittedHostMetadata: async () => sessionMetadataStore.getAll(),
1639
1884
  transcriptCacheService,
1640
1885
  });
1641
1886
  uiAuthController = bootstrapResult.uiAuthController;
@@ -1731,24 +1976,45 @@ async function main(options = {}) {
1731
1976
  buildOpenCodeUrl,
1732
1977
  getOpenCodeAuthHeaders,
1733
1978
  getIsExternalOpenCode: () => isExternalOpenCode,
1979
+ getIsSharedOpenCodeService: () => openCodeLifecycleRuntime.isSharedOpenCodeService(),
1980
+ forceResolvedOpenCodeBinary,
1981
+ restartOpenCode,
1982
+ waitForOpenCodeReady,
1983
+ getRuntimeContract: () => runtimeContract,
1984
+ getOpenCodeServeVersion: () => openCodeServeVersion,
1985
+ getOpenCodeCliVersion: () => openCodeCliVersion,
1986
+ getResolvedOpenCodeBinary: () => resolvedOpencodeBinary,
1987
+ getResolvedOpenCodeBinarySource: () => resolvedOpencodeBinarySource,
1988
+ getActiveSessionCount,
1734
1989
  sessionMetadataStore,
1990
+ sessionArchiveService: {
1991
+ setArchive: (...args) => {
1992
+ if (!sessionArchiveServiceRef) {
1993
+ const error = new Error('session archive is not configured');
1994
+ error.status = 503;
1995
+ throw error;
1996
+ }
1997
+ return sessionArchiveServiceRef.setArchive(...args);
1998
+ },
1999
+ archiveSession: (...args) => {
2000
+ if (!sessionArchiveServiceRef) {
2001
+ const error = new Error('session archive is not configured');
2002
+ error.status = 503;
2003
+ throw error;
2004
+ }
2005
+ return sessionArchiveServiceRef.archiveSession(...args);
2006
+ },
2007
+ },
1735
2008
  // Same store seam as sessionGoalRuntime / scheduledTasksRuntime.
1736
2009
  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
- },
2010
+ persistSessionMetadata: persistSessionMetadataToStore,
2011
+ onSystemSessionPersisted: hideSystemSessionFromIndex,
2012
+ persistSessionGoal: persistSessionGoalToStore,
1751
2013
  onSessionMetadataWritten: ({ sessionID, directory, metadata }) => {
2014
+ sessionTitleRuntime.processPayload({
2015
+ type: 'openchamber:session-metadata',
2016
+ properties: { sessionID, directory, metadata },
2017
+ }, directory || '');
1752
2018
  sessionGoalRuntime.processPayload({
1753
2019
  type: 'session.updated',
1754
2020
  properties: {
@@ -1811,6 +2077,8 @@ async function main(options = {}) {
1811
2077
  getOpenCodeAuthHeaders,
1812
2078
  globalEventHub: globalMessageStreamHub,
1813
2079
  processForwardedEventPayload,
2080
+ projectOutboundSessionPayload: projectHostSessionPayload,
2081
+ ensureSessionMetadataReady,
1814
2082
  messageStreamWsClients: uiNotificationWsClients,
1815
2083
  upstreamStallTimeoutMs: getUpstreamStallTimeoutMs,
1816
2084
  terminalHeartbeatIntervalMs: TERMINAL_INPUT_WS_HEARTBEAT_INTERVAL_MS,
@@ -1880,12 +2148,9 @@ async function main(options = {}) {
1880
2148
  restartOpenCode: () => restartOpenCode(),
1881
2149
  getOpenCodeProcessInfo: () => {
1882
2150
  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.
2151
+ // Only ever expose pid/port for a server WE manage. Desktop no longer runs a
2152
+ // post-stop detached killer of a pre-captured pid; still withhold non-owned
2153
+ // process identity so diagnostics cannot imply we may kill an external serve.
1889
2154
  return {
1890
2155
  managed,
1891
2156
  pid: managed && typeof openCodeProcess?.pid === 'number' ? openCodeProcess.pid : null,
@@ -1899,6 +2164,7 @@ async function main(options = {}) {
1899
2164
  unsubscribeScheduledTaskEvents();
1900
2165
  sessionIndexSyncRuntime?.stop();
1901
2166
  sessionIndexService?.close();
2167
+ sessionArchiveServiceRef?.stop?.();
1902
2168
  } catch {
1903
2169
  // The index is a local cache; a failed close must not block shutdown.
1904
2170
  }
@@ -1926,11 +2192,10 @@ async function main(options = {}) {
1926
2192
  // best-effort shutdown of the dictation worker
1927
2193
  }
1928
2194
  try {
1929
- if (smallModelServiceInstance?.stop) {
1930
- await smallModelServiceInstance.stop();
1931
- }
2195
+ const { stopLlmTempDirectory } = await import('./lib/llm/temp-directory.js');
2196
+ await stopLlmTempDirectory();
1932
2197
  } catch {
1933
- // best-effort cleanup of the small-model temp OpenCode directory
2198
+ // best-effort cleanup of the LLM generator temp OpenCode directory
1934
2199
  }
1935
2200
  return gracefulShutdown({
1936
2201
  exitProcess: shutdownOptions.exitProcess ?? false,