@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
@@ -2,6 +2,16 @@ import { afterEach, describe, expect, it, vi } from 'vitest';
2
2
  import express from 'express';
3
3
  import request from 'supertest';
4
4
  import fs from 'fs';
5
+ import { installPinnedOpenCode2Cli, readOpenCode2BinaryVersion } from './ensure-cli.js';
6
+
7
+ vi.mock('./ensure-cli.js', async (importOriginal) => {
8
+ const actual = await importOriginal();
9
+ return {
10
+ ...actual,
11
+ installPinnedOpenCode2Cli: vi.fn(actual.installPinnedOpenCode2Cli),
12
+ readOpenCode2BinaryVersion: vi.fn(actual.readOpenCode2BinaryVersion),
13
+ };
14
+ });
5
15
 
6
16
  import { registerOpenCodeRoutes } from './routes.js';
7
17
  import { isOpenCode1xVersion } from './opencode2-pin.js';
@@ -133,20 +143,35 @@ describe('opencode2 upgrade pin (ticket 12)', () => {
133
143
  app.use(express.json());
134
144
  const deps = {
135
145
  ...createDependencies({ formatSettingsResponse: vi.fn(() => ({})) }),
136
- getOpenCodeResolutionSnapshot: vi.fn(async () => ({ source: 'path' })),
146
+ getOpenCodeResolutionSnapshot: vi.fn(async () => ({ source: 'path', resolved: '/usr/local/bin/opencode' })),
137
147
  buildOpenCodeUrl: vi.fn((pathname) => `http://opencode.test${pathname}`),
138
148
  getOpenCodeAuthHeaders: vi.fn(() => ({ Authorization: 'Basic secret' })),
139
149
  refreshOpenCodeAfterConfigChange: vi.fn(async () => undefined),
150
+ getResolvedOpenCodeBinarySource: vi.fn(() => 'path'),
151
+ getResolvedOpenCodeBinary: vi.fn(() => '/usr/local/bin/opencode'),
152
+ getActiveSessionCount: vi.fn(() => 0),
140
153
  ...overrides,
141
154
  };
142
155
  registerOpenCodeRoutes(app, deps);
143
156
  return { app, deps };
144
157
  };
145
158
 
146
- it('rejects 1.x upgrade targets and never calls /global/upgrade', async () => {
159
+ afterEach(() => {
160
+ vi.unstubAllGlobals();
161
+ });
162
+
163
+ it('rejects 1.x upgrade targets even for owned cache', async () => {
147
164
  const fetchMock = vi.fn();
148
165
  vi.stubGlobal('fetch', fetchMock);
149
- const { app } = createUpgradeApp();
166
+ const { app } = createUpgradeApp({
167
+ getOpenCodeResolutionSnapshot: vi.fn(async () => ({
168
+ source: 'installed',
169
+ resolved: '/tmp/openchamber/opencode-cli/2.0.12/opencode',
170
+ })),
171
+ getResolvedOpenCodeBinarySource: vi.fn(() => 'installed'),
172
+ getResolvedOpenCodeBinary: vi.fn(() => '/tmp/openchamber/opencode-cli/2.0.12/opencode'),
173
+ openchamberDataDir: '/tmp/openchamber',
174
+ });
150
175
 
151
176
  const response = await request(app)
152
177
  .post('/api/opencode/upgrade')
@@ -156,21 +181,18 @@ describe('opencode2 upgrade pin (ticket 12)', () => {
156
181
  expect(response.body.success).toBe(false);
157
182
  expect(response.body.error).toMatch(/1\.x/);
158
183
  expect(fetchMock).not.toHaveBeenCalled();
159
- vi.unstubAllGlobals();
160
184
  });
161
185
 
162
- it('does not treat a 1.x latest string as an available upgrade', async () => {
186
+ it('upgrade-status reports manual guidance for global CLI and never claims in-app available', async () => {
163
187
  const fetchMock = vi.fn(async (url) => {
164
188
  const href = String(url);
165
189
  if (href.includes('/api/info')) {
166
190
  return {
167
191
  ok: true,
192
+ status: 200,
168
193
  json: async () => ({ version: '2.0.12', healthy: true }),
169
194
  };
170
195
  }
171
- if (href.includes('registry.npmjs.org/opencode-ai') || href.includes('github.com/repos/anomalyco/opencode')) {
172
- return { ok: true, json: async () => ({ version: '1.18.18', tag_name: 'v1.18.18' }) };
173
- }
174
196
  throw new Error(`unexpected fetch ${href}`);
175
197
  });
176
198
  vi.stubGlobal('fetch', fetchMock);
@@ -178,16 +200,21 @@ describe('opencode2 upgrade pin (ticket 12)', () => {
178
200
 
179
201
  const response = await request(app).get('/api/opencode/upgrade-status').expect(200);
180
202
 
203
+ expect(response.body.canManage).toBe(false);
204
+ expect(response.body.available).toBeNull();
205
+ expect(response.body.management).toBe('manual-global');
206
+ expect(response.body.guidance).toMatch(/global/i);
181
207
  expect(response.body.latestVersion).not.toMatch(/^v?1\./);
182
208
  expect(isOpenCode1xVersion(response.body.latestVersion)).toBe(false);
183
- expect(String(response.body.latestVersion || '')).not.toContain('1.18');
184
- vi.unstubAllGlobals();
209
+ expect(response.body.contract).toBeTruthy();
185
210
  });
186
211
 
187
- it('returns 409 for external OpenCode and does not refresh', async () => {
212
+ it('returns 409 for external OpenCode and does not restart', async () => {
213
+ const restartOpenCode = vi.fn(async () => undefined);
188
214
  const refreshOpenCodeAfterConfigChange = vi.fn(async () => undefined);
189
215
  const { app, deps } = createUpgradeApp({
190
216
  getIsExternalOpenCode: () => true,
217
+ restartOpenCode,
191
218
  refreshOpenCodeAfterConfigChange,
192
219
  });
193
220
 
@@ -197,7 +224,246 @@ describe('opencode2 upgrade pin (ticket 12)', () => {
197
224
  .expect(409);
198
225
 
199
226
  expect(response.body.success).toBe(false);
200
- expect(response.body.error).toMatch(/your own OpenCode serve/i);
227
+ expect(response.body.management).toBe('manual-external');
228
+ expect(response.body.error).toMatch(/external/i);
229
+ expect(deps.restartOpenCode).not.toHaveBeenCalled();
201
230
  expect(deps.refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
202
231
  });
232
+
233
+ it('upgrades a shared service using the owned cache and verifies the running version', async () => {
234
+ const binary = '/cache/opencode-cli/2.0.14/opencode';
235
+ vi.mocked(installPinnedOpenCode2Cli).mockResolvedValueOnce(binary);
236
+ vi.mocked(readOpenCode2BinaryVersion).mockReturnValueOnce('2.0.14');
237
+ vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({ version: '2.0.14', pid: 42 }))));
238
+ let resolvedBinary = '/usr/local/bin/opencode';
239
+ const restartOpenCode = vi.fn(async () => {});
240
+ const { app } = createUpgradeApp({
241
+ getIsExternalOpenCode: () => true,
242
+ getIsSharedOpenCodeService: () => true,
243
+ getResolvedOpenCodeBinary: () => resolvedBinary,
244
+ getOpenCodeCliVersion: () => '2.0.14',
245
+ forceResolvedOpenCodeBinary: (value) => { resolvedBinary = value; },
246
+ restartOpenCode,
247
+ waitForOpenCodeReady: vi.fn(),
248
+ openchamberDataDir: '/cache',
249
+ });
250
+
251
+ const response = await request(app).post('/api/opencode/upgrade').send({ target: '2.0.14' }).expect(200);
252
+ expect(restartOpenCode).toHaveBeenCalledWith({ binaryPath: binary });
253
+ expect(response.body).toMatchObject({ success: true, version: '2.0.14', ownership: 'shared-service' });
254
+ });
255
+
256
+ it('does not report a failed shared replacement as success and restores binary selection', async () => {
257
+ vi.mocked(installPinnedOpenCode2Cli).mockResolvedValueOnce('/cache/opencode-cli/2.0.14/opencode');
258
+ vi.mocked(readOpenCode2BinaryVersion).mockReturnValueOnce('2.0.14');
259
+ const forceResolvedOpenCodeBinary = vi.fn();
260
+ const { app } = createUpgradeApp({
261
+ getIsExternalOpenCode: () => true,
262
+ getIsSharedOpenCodeService: () => true,
263
+ forceResolvedOpenCodeBinary,
264
+ restartOpenCode: vi.fn(async () => { throw new Error('replacement failed'); }),
265
+ });
266
+ const response = await request(app).post('/api/opencode/upgrade').send({ target: '2.0.14' }).expect(500);
267
+ expect(response.body).toMatchObject({ upgraded: false, error: 'replacement failed' });
268
+ expect(forceResolvedOpenCodeBinary).toHaveBeenLastCalledWith('/usr/local/bin/opencode', 'path');
269
+ });
270
+
271
+ it('exposes shared upgrade availability while retaining active-task confirmation', async () => {
272
+ vi.stubGlobal('fetch', vi.fn(async () => new Response(JSON.stringify({ version: '2.0.10', pid: 42 }))));
273
+ const restartOpenCode = vi.fn();
274
+ const { app } = createUpgradeApp({
275
+ getIsExternalOpenCode: () => true,
276
+ getIsSharedOpenCodeService: () => true,
277
+ getActiveSessionCount: () => 1,
278
+ restartOpenCode,
279
+ });
280
+ const status = await request(app).get('/api/opencode/upgrade-status').expect(200);
281
+ expect(status.body).toMatchObject({ canManage: true, available: true, ownership: 'shared-service' });
282
+ const response = await request(app).post('/api/opencode/upgrade').send({}).expect(409);
283
+ expect(response.body.errorCode).toBe('UPGRADE_ACTIVE_TASKS');
284
+ expect(restartOpenCode).not.toHaveBeenCalled();
285
+ });
286
+
287
+ it.each(['unreachable', 'old-version'])('rejects shared upgrade verification when the running service is %s', async (mode) => {
288
+ const binary = '/cache/opencode-cli/2.0.14/opencode';
289
+ vi.mocked(installPinnedOpenCode2Cli).mockResolvedValueOnce(binary);
290
+ vi.mocked(readOpenCode2BinaryVersion).mockReturnValueOnce('2.0.14');
291
+ vi.stubGlobal('fetch', vi.fn(async () => {
292
+ if (mode === 'unreachable') throw new Error('offline');
293
+ return new Response(JSON.stringify({ version: '2.0.12', pid: 42 }));
294
+ }));
295
+ const { app } = createUpgradeApp({
296
+ getIsExternalOpenCode: () => true,
297
+ getIsSharedOpenCodeService: () => true,
298
+ getResolvedOpenCodeBinary: () => binary,
299
+ getOpenCodeServeVersion: () => '2.0.14',
300
+ getOpenCodeCliVersion: () => '2.0.14',
301
+ getRuntimeContract: () => ({ reachable: true, authenticated: true, healthOk: true }),
302
+ forceResolvedOpenCodeBinary: vi.fn(),
303
+ restartOpenCode: vi.fn(),
304
+ });
305
+ const response = await request(app).post('/api/opencode/upgrade').send({ target: '2.0.14' }).expect(500);
306
+ expect(response.body.upgraded).toBe(false);
307
+ });
308
+
309
+ it('refuses in-app upgrade when managed process uses global CLI', async () => {
310
+ const restartOpenCode = vi.fn(async () => undefined);
311
+ const { app, deps } = createUpgradeApp({ restartOpenCode });
312
+
313
+ const response = await request(app)
314
+ .post('/api/opencode/upgrade')
315
+ .send({ target: '2.0.14' })
316
+ .expect(409);
317
+
318
+ expect(response.body.success).toBe(false);
319
+ expect(response.body.ownership).toBe('global-cli');
320
+ expect(deps.restartOpenCode).not.toHaveBeenCalled();
321
+ });
322
+ });
323
+
324
+ describe('runtime contract routes (ticket 11)', () => {
325
+ const createContractApp = (overrides = {}) => {
326
+ const app = express();
327
+ const deps = {
328
+ ...createDependencies({ formatSettingsResponse: vi.fn(() => ({})) }),
329
+ getOpenCodeResolutionSnapshot: vi.fn(async () => ({ source: 'path' })),
330
+ buildOpenCodeUrl: vi.fn((pathname) => `http://opencode.test${pathname}`),
331
+ getOpenCodeAuthHeaders: vi.fn(() => ({ Authorization: 'Basic secret' })),
332
+ ...overrides,
333
+ };
334
+ registerOpenCodeRoutes(app, deps);
335
+ return { app, deps };
336
+ };
337
+
338
+ afterEach(() => {
339
+ vi.unstubAllGlobals();
340
+ });
341
+
342
+ it('exposes contract admission separate from bare healthy', async () => {
343
+ vi.stubGlobal('fetch', vi.fn(async () => ({
344
+ ok: true,
345
+ status: 200,
346
+ statusText: 'OK',
347
+ json: async () => ({ version: '2.0.12', pid: 1 }),
348
+ })));
349
+ const { app } = createContractApp();
350
+
351
+ const health = await request(app).get('/api/opencode/health').expect(200);
352
+ expect(health.body.healthy).toBe(true);
353
+ expect(health.body.contract.protocolCompatible).toBe(true);
354
+ expect(health.body.executionAllowed).toBe(true);
355
+
356
+ const contract = await request(app).get('/api/opencode/contract').expect(200);
357
+ expect(contract.body.serveVersion).toBe('2.0.12');
358
+ expect(contract.body.capabilities['core.protocol'].available).toBe(true);
359
+ });
360
+
361
+ it('marks older 2.x healthy-reachability may still fail execution admission', async () => {
362
+ vi.stubGlobal('fetch', vi.fn(async () => ({
363
+ ok: true,
364
+ status: 200,
365
+ statusText: 'OK',
366
+ json: async () => ({ version: '2.0.5', pid: 1 }),
367
+ })));
368
+ const { app } = createContractApp();
369
+ const health = await request(app).get('/api/opencode/health').expect(200);
370
+ // evaluateOpenCodeHealthBody still accepts any 2.x for reachability
371
+ expect(health.body.healthy).toBe(true);
372
+ expect(health.body.executionAllowed).toBe(false);
373
+ expect(health.body.contract.phase).toBe('incompatible');
374
+ });
375
+ });
376
+
377
+ describe('GET /api/opencode/health', () => {
378
+ const createHealthApp = () => {
379
+ const app = express();
380
+ const deps = {
381
+ ...createDependencies({ formatSettingsResponse: vi.fn(() => ({})) }),
382
+ buildOpenCodeUrl: vi.fn((pathname) => `http://opencode.test${pathname}`),
383
+ getOpenCodeAuthHeaders: vi.fn(() => ({ Authorization: 'Basic secret' })),
384
+ };
385
+ registerOpenCodeRoutes(app, deps);
386
+ return { app, deps };
387
+ };
388
+
389
+ afterEach(() => {
390
+ vi.unstubAllGlobals();
391
+ });
392
+
393
+ it('admits official 2.x ServerInfo from /api/info without a healthy field', async () => {
394
+ const fetchMock = vi.fn(async (url) => {
395
+ expect(String(url)).toContain('/api/info');
396
+ return {
397
+ ok: true,
398
+ status: 200,
399
+ statusText: 'OK',
400
+ json: async () => ({
401
+ version: '2.0.12',
402
+ pid: 4242,
403
+ urls: { api: 'http://127.0.0.1:4096' },
404
+ paths: { config: '/tmp/config', data: '/tmp/data' },
405
+ }),
406
+ };
407
+ });
408
+ vi.stubGlobal('fetch', fetchMock);
409
+ const { app } = createHealthApp();
410
+
411
+ const response = await request(app).get('/api/opencode/health').expect(200);
412
+
413
+ expect(response.body).toMatchObject({
414
+ healthy: true,
415
+ version: '2.0.12',
416
+ executionAllowed: true,
417
+ protocolCompatible: true,
418
+ });
419
+ expect(response.body.contract).toBeTruthy();
420
+ // buildLiveContract also probes /api/info
421
+ expect(fetchMock).toHaveBeenCalled();
422
+ });
423
+
424
+ it('rejects classic unhealthy bodies even when version is 2.x', async () => {
425
+ vi.stubGlobal('fetch', vi.fn(async () => ({
426
+ ok: true,
427
+ status: 200,
428
+ statusText: 'OK',
429
+ json: async () => ({ healthy: false, version: '2.0.12' }),
430
+ })));
431
+ const { app } = createHealthApp();
432
+
433
+ const response = await request(app).get('/api/opencode/health').expect(200);
434
+
435
+ expect(response.body).toMatchObject({ healthy: false });
436
+ });
437
+
438
+ it('rejects 1.x bodies even when healthy:true is present', async () => {
439
+ vi.stubGlobal('fetch', vi.fn(async () => ({
440
+ ok: true,
441
+ status: 200,
442
+ statusText: 'OK',
443
+ json: async () => ({ healthy: true, version: '1.18.18' }),
444
+ })));
445
+ const { app } = createHealthApp();
446
+
447
+ const response = await request(app).get('/api/opencode/health').expect(200);
448
+
449
+ expect(response.body).toMatchObject({
450
+ healthy: false,
451
+ executionAllowed: false,
452
+ });
453
+ });
454
+
455
+ it('returns healthy:false with error when upstream /api/info is not ok', async () => {
456
+ vi.stubGlobal('fetch', vi.fn(async () => ({
457
+ ok: false,
458
+ status: 502,
459
+ statusText: 'Bad Gateway',
460
+ json: async () => ({ error: 'upstream down' }),
461
+ })));
462
+ const { app } = createHealthApp();
463
+
464
+ const response = await request(app).get('/api/opencode/health').expect(502);
465
+
466
+ expect(response.body).toMatchObject({ healthy: false, error: 'upstream down' });
467
+ expect(response.body.contract).toBeTruthy();
468
+ });
203
469
  });