@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
@@ -0,0 +1,359 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+
6
+ import { createSessionMetadataStore } from './session-metadata-store.js';
7
+ import {
8
+ createSessionArchiveService,
9
+ } from './session-archive.js';
10
+ import {
11
+ projectSessionWithHostMetadata,
12
+ projectSessionWithStoredMap,
13
+ readHostArchivedAt,
14
+ } from './session-projection.js';
15
+
16
+ const tempDirs = [];
17
+
18
+ const makeDataDir = () => {
19
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-archive-'));
20
+ tempDirs.push(dir);
21
+ return dir;
22
+ };
23
+
24
+ afterEach(() => {
25
+ while (tempDirs.length > 0) {
26
+ fs.rmSync(tempDirs.pop(), { recursive: true, force: true });
27
+ }
28
+ vi.restoreAllMocks();
29
+ });
30
+
31
+ const upstreamSession = (overrides = {}) => ({
32
+ id: 'ses_1',
33
+ title: 'Alpha',
34
+ directory: '/repo',
35
+ time: { created: 100, updated: 200 },
36
+ ...overrides,
37
+ });
38
+
39
+ describe('session archive host loop', () => {
40
+ it('archives via Host store, projects list/get, survives store reopen, then unarchives', async () => {
41
+ const dataDir = makeDataDir();
42
+ const store = createSessionMetadataStore({ dataDir });
43
+ const upstream = {
44
+ ses_1: upstreamSession({
45
+ // Historical upstream archive stamp must yield to Host authority.
46
+ time: { created: 100, updated: 200, archived: 50 },
47
+ }),
48
+ };
49
+
50
+ const indexUpserts = [];
51
+ const broadcasts = [];
52
+ const archive = createSessionArchiveService({
53
+ sessionMetadataStore: store,
54
+ fetchUpstreamSession: async ({ sessionID }) => upstream[sessionID] ?? null,
55
+ sessionIndexService: {
56
+ upsertAndReportChange: (session) => {
57
+ indexUpserts.push(session);
58
+ return true;
59
+ },
60
+ },
61
+ broadcastSessionEvent: (event) => {
62
+ broadcasts.push(event);
63
+ },
64
+ now: () => 1_700_000_000_000,
65
+ });
66
+
67
+ const archived = await archive.setArchive({ sessionID: 'ses_1', archivedAt: 1_700_000_000_123 });
68
+ expect(archived.session.time.archived).toBe(1_700_000_000_123);
69
+ expect(archived.session.time.created).toBe(100);
70
+ expect(archived.session.time.updated).toBe(200);
71
+ expect(archived.session.metadata.openchamber.archive.archivedAt).toBe(1_700_000_000_123);
72
+ expect(indexUpserts.at(-1)?.time?.archived).toBe(1_700_000_000_123);
73
+ expect(broadcasts.at(-1)).toMatchObject({
74
+ type: 'session.updated',
75
+ properties: { info: { id: 'ses_1', time: { archived: 1_700_000_000_123 } } },
76
+ });
77
+
78
+ const storedMap = await store.getAll();
79
+ const listProjected = projectSessionWithStoredMap(upstream.ses_1, storedMap);
80
+ const getProjected = projectSessionWithHostMetadata(
81
+ upstream.ses_1,
82
+ await store.get('ses_1'),
83
+ );
84
+ expect(listProjected.time.archived).toBe(1_700_000_000_123);
85
+ expect(getProjected.time.archived).toBe(1_700_000_000_123);
86
+
87
+ // Fresh process: rebuild store from disk and still project Host archive.
88
+ const reopened = createSessionMetadataStore({ dataDir });
89
+ await expect(reopened.get('ses_1')).resolves.toEqual({
90
+ openchamber: { archive: { archivedAt: 1_700_000_000_123 } },
91
+ });
92
+ expect(
93
+ projectSessionWithHostMetadata(upstream.ses_1, await reopened.get('ses_1')).time.archived,
94
+ ).toBe(1_700_000_000_123);
95
+
96
+ const unarchived = await archive.setArchive({ sessionID: 'ses_1', archivedAt: 0 });
97
+ // Explicit 0 clears archive even when upstream still carries historical time.archived.
98
+ expect(unarchived.session.time.archived).toBeUndefined();
99
+ expect(readHostArchivedAt(unarchived.metadata)).toBe(0);
100
+ expect(indexUpserts.at(-1)?.time?.archived).toBeUndefined();
101
+
102
+ const afterCancel = projectSessionWithHostMetadata(
103
+ upstream.ses_1,
104
+ await store.get('ses_1'),
105
+ );
106
+ expect(afterCancel.time.archived).toBeUndefined();
107
+ });
108
+
109
+ it('does not report post-commit index/broadcast failure as write failure', async () => {
110
+ const store = createSessionMetadataStore({ dataDir: makeDataDir() });
111
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
112
+ const archive = createSessionArchiveService({
113
+ sessionMetadataStore: store,
114
+ fetchUpstreamSession: async () => upstreamSession(),
115
+ sessionIndexService: {
116
+ upsertAndReportChange: () => {
117
+ throw new Error('index down');
118
+ },
119
+ },
120
+ broadcastSessionEvent: () => {
121
+ throw new Error('broadcast down');
122
+ },
123
+ });
124
+
125
+ await expect(archive.setArchive({ sessionID: 'ses_1', archivedAt: 99 })).resolves.toMatchObject({
126
+ session: { id: 'ses_1', time: { archived: 99 } },
127
+ });
128
+ await expect(store.get('ses_1')).resolves.toEqual({
129
+ openchamber: { archive: { archivedAt: 99 } },
130
+ });
131
+ });
132
+
133
+ it('refuses archive when upstream session is missing', async () => {
134
+ const archive = createSessionArchiveService({
135
+ sessionMetadataStore: createSessionMetadataStore({ dataDir: makeDataDir() }),
136
+ fetchUpstreamSession: async () => null,
137
+ });
138
+ await expect(archive.setArchive({ sessionID: 'ses_missing', archivedAt: 1 }))
139
+ .rejects.toMatchObject({ code: 'not_found', status: 404 });
140
+ });
141
+
142
+ it('refuses directory mismatch before persisting', async () => {
143
+ const store = createSessionMetadataStore({ dataDir: makeDataDir() });
144
+ const archive = createSessionArchiveService({
145
+ sessionMetadataStore: store,
146
+ fetchUpstreamSession: async () => upstreamSession({ directory: '/repo-a' }),
147
+ });
148
+ await expect(archive.setArchive({
149
+ sessionID: 'ses_1',
150
+ archivedAt: 1,
151
+ directory: '/repo-b',
152
+ })).rejects.toMatchObject({ code: 'directory_mismatch', status: 403 });
153
+ await expect(store.get('ses_1')).resolves.toEqual({});
154
+ });
155
+
156
+ it('prefers location.directory and rejects id mismatch', async () => {
157
+ const store = createSessionMetadataStore({ dataDir: makeDataDir() });
158
+ const archive = createSessionArchiveService({
159
+ sessionMetadataStore: store,
160
+ fetchUpstreamSession: async () => ({
161
+ id: 'ses_1',
162
+ location: { directory: '/from-location' },
163
+ directory: '/legacy',
164
+ time: { created: 1, updated: 2 },
165
+ }),
166
+ });
167
+ const result = await archive.setArchive({ sessionID: 'ses_1', archivedAt: 5 });
168
+ expect(result.session.directory).toBe('/from-location');
169
+
170
+ const bad = createSessionArchiveService({
171
+ sessionMetadataStore: store,
172
+ fetchUpstreamSession: async () => ({ id: 'ses_other', directory: '/repo', time: {} }),
173
+ });
174
+ await expect(bad.setArchive({ sessionID: 'ses_1', archivedAt: 1 }))
175
+ .rejects.toMatchObject({ code: 'id_mismatch' });
176
+ });
177
+
178
+ it('reports retryable index failure after metadata commit and schedules repair', async () => {
179
+ const store = createSessionMetadataStore({ dataDir: makeDataDir() });
180
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
181
+ const timers = [];
182
+ const archive = createSessionArchiveService({
183
+ sessionMetadataStore: store,
184
+ fetchUpstreamSession: async () => upstreamSession(),
185
+ sessionIndexService: {
186
+ upsertAndReportChange: () => {
187
+ throw new Error('sqlite locked');
188
+ },
189
+ },
190
+ setTimer: (fn, ms) => {
191
+ const h = { fn, ms };
192
+ timers.push(h);
193
+ return h;
194
+ },
195
+ clearTimer: () => {},
196
+ });
197
+ const result = await archive.setArchive({ sessionID: 'ses_1', archivedAt: 7 });
198
+ expect(result.session.time.archived).toBe(7);
199
+ expect(result.index).toMatchObject({ ok: false, retryable: true });
200
+ await expect(store.get('ses_1')).resolves.toMatchObject({
201
+ openchamber: { archive: { archivedAt: 7 } },
202
+ });
203
+ expect(archive.getIndexRepairStatus('ses_1')).toMatchObject({ pending: true, retryable: true });
204
+ expect(timers.length).toBeGreaterThan(0);
205
+ archive.stop();
206
+ });
207
+
208
+ it('stale repair after hung GET cannot remove index after unarchive', async () => {
209
+ const store = createSessionMetadataStore({ dataDir: makeDataDir() });
210
+ const indexWrites = [];
211
+ let hangNextFetch = false;
212
+ let releaseFetch;
213
+ const hungFetch = new Promise((resolve) => { releaseFetch = resolve; });
214
+ const removes = [];
215
+
216
+ const timers = [];
217
+ const archive = createSessionArchiveService({
218
+ sessionMetadataStore: store,
219
+ fetchUpstreamSession: async ({ sessionID }) => {
220
+ if (hangNextFetch) {
221
+ hangNextFetch = false;
222
+ await hungFetch;
223
+ }
224
+ return {
225
+ id: sessionID,
226
+ directory: '/repo',
227
+ title: 'S',
228
+ time: { created: 1, updated: 2 },
229
+ };
230
+ },
231
+ sessionIndexService: {
232
+ upsertAndReportChange: (session) => {
233
+ indexWrites.push({
234
+ id: session.id,
235
+ archived: session.time?.archived,
236
+ });
237
+ // First archive index apply fails → schedules repair.
238
+ if (indexWrites.length === 1) throw new Error('sqlite locked');
239
+ return true;
240
+ },
241
+ remove: (id) => {
242
+ removes.push(id);
243
+ return true;
244
+ },
245
+ },
246
+ setTimer: (fn) => {
247
+ timers.push(fn);
248
+ return fn;
249
+ },
250
+ clearTimer: () => {},
251
+ now: () => 1_000,
252
+ });
253
+
254
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
255
+
256
+ // Archive: metadata commits, index fails, repair scheduled.
257
+ const archived = await archive.setArchive({ sessionID: 'ses_1', archivedAt: 99 });
258
+ expect(archived.index.ok).toBe(false);
259
+ expect(timers).toHaveLength(1);
260
+
261
+ // Fire repair timer → GET hangs.
262
+ hangNextFetch = true;
263
+ const repairPromise = timers[0]();
264
+ await Promise.resolve();
265
+ await Promise.resolve();
266
+
267
+ // Unarchive completes while repair GET is still hanging.
268
+ const unarchived = await archive.setArchive({ sessionID: 'ses_1', archivedAt: 0 });
269
+ expect(unarchived.session.time.archived).toBeUndefined();
270
+ expect(readHostArchivedAt(await store.get('ses_1'))).toBe(0);
271
+ expect(indexWrites.at(-1)?.archived).toBeUndefined();
272
+ const writesAfterUnarchive = indexWrites.length;
273
+
274
+ // Old hung GET returns — must not remove active session or re-apply archive.
275
+ releaseFetch();
276
+ await repairPromise;
277
+
278
+ expect(readHostArchivedAt(await store.get('ses_1'))).toBe(0);
279
+ expect(indexWrites.length).toBe(writesAfterUnarchive);
280
+ expect(indexWrites.at(-1)?.archived).toBeUndefined();
281
+ expect(removes).toEqual([]);
282
+ archive.stop();
283
+ });
284
+
285
+ it('stop supersedes in-flight repair so late GET does not write', async () => {
286
+ const store = createSessionMetadataStore({ dataDir: makeDataDir() });
287
+ let releaseFetch;
288
+ const hung = new Promise((resolve) => { releaseFetch = resolve; });
289
+ let hangRepairFetch = false;
290
+ const writes = [];
291
+ const timers = [];
292
+ const archive = createSessionArchiveService({
293
+ sessionMetadataStore: store,
294
+ fetchUpstreamSession: async ({ sessionID }) => {
295
+ if (hangRepairFetch) {
296
+ hangRepairFetch = false;
297
+ await hung;
298
+ }
299
+ return { id: sessionID, directory: '/repo', time: { created: 1, updated: 2 } };
300
+ },
301
+ sessionIndexService: {
302
+ upsertAndReportChange: (s) => {
303
+ writes.push({ id: s.id, archived: s.time?.archived });
304
+ if (writes.length === 1) throw new Error('fail');
305
+ return true;
306
+ },
307
+ },
308
+ setTimer: (fn) => { timers.push(fn); return fn; },
309
+ clearTimer: () => {},
310
+ });
311
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
312
+ await archive.setArchive({ sessionID: 'ses_stop', archivedAt: 1 });
313
+ hangRepairFetch = true;
314
+ const repair = timers[0]();
315
+ await Promise.resolve();
316
+ await Promise.resolve();
317
+ archive.stop();
318
+ releaseFetch();
319
+ await repair;
320
+ // setArchive failed write only — stop made late repair stale (no second write).
321
+ expect(writes).toEqual([{ id: 'ses_stop', archived: 1 }]);
322
+ });
323
+
324
+ it('forgetSession treats missing as success and retries write failures', async () => {
325
+ const dataDir = makeDataDir();
326
+ const store = createSessionMetadataStore({ dataDir });
327
+ await store.setSessionMetadata('ses_1', { a: 1 });
328
+ const timers = [];
329
+ let failWrite = true;
330
+ const fsPromises = {
331
+ ...fs.promises,
332
+ writeFile: async (...args) => {
333
+ if (failWrite) throw new Error('disk full');
334
+ return fs.promises.writeFile(...args);
335
+ },
336
+ };
337
+ const failingStore = createSessionMetadataStore({ dataDir, fsPromises });
338
+ await failingStore.load();
339
+ const archive = createSessionArchiveService({
340
+ sessionMetadataStore: failingStore,
341
+ fetchUpstreamSession: async () => upstreamSession(),
342
+ setTimer: (fn) => { timers.push(fn); return fn; },
343
+ clearTimer: () => {},
344
+ });
345
+ const failed = await archive.forgetSession('ses_1');
346
+ expect(failed).toMatchObject({ ok: false, retryable: true });
347
+ expect(archive.getCleanupStatus('ses_1')).toMatchObject({ pending: true });
348
+ expect(timers).toHaveLength(1);
349
+
350
+ failWrite = false;
351
+ await timers[0]();
352
+ expect(archive.getCleanupStatus('ses_1')).toMatchObject({ ok: true, pending: false });
353
+
354
+ // Already gone is success.
355
+ const gone = await archive.forgetSession('ses_missing');
356
+ expect(gone).toMatchObject({ ok: true });
357
+ archive.stop();
358
+ });
359
+ });
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Type surface for session-metadata-store.js (consumed by VS Code Extension Host facades).
3
+ * Runtime implementation stays in the .js module.
4
+ */
5
+
6
+ export type SessionMetadataLoadResult =
7
+ | { ok: true; entries: Record<string, Record<string, unknown>>; reason?: null }
8
+ | { ok: false; entries: Record<string, unknown>; reason: string | null };
9
+
10
+ export type SessionMetadataStore = {
11
+ load: () => Promise<SessionMetadataLoadResult>;
12
+ get: (sessionID: string) => Promise<Record<string, unknown>>;
13
+ getAll: () => Promise<Record<string, Record<string, unknown>>>;
14
+ getSnapshotSync: () => Record<string, Record<string, unknown>> | null;
15
+ isLoaded: () => boolean;
16
+ getLoadFailureReason: () => string | null;
17
+ setSessionMetadata: (
18
+ sessionID: string,
19
+ patch: object,
20
+ options?: { allowArchive?: boolean },
21
+ ) => Promise<Record<string, unknown>>;
22
+ removeSession: (sessionID: string) => Promise<boolean>;
23
+ filePath: string;
24
+ };
25
+
26
+ export function mergeMetadataPatch(
27
+ current: unknown,
28
+ patch: unknown,
29
+ ): Record<string, unknown>;
30
+
31
+ export function createSessionMetadataStore(options: {
32
+ dataDir: string;
33
+ fsPromises?: typeof import('node:fs').promises;
34
+ path?: typeof import('node:path');
35
+ now?: () => number;
36
+ }): SessionMetadataStore;