@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (311) hide show
  1. package/README.md +13 -7
  2. package/bin/cli-opencode.test.js +208 -0
  3. package/bin/cli.js +62 -9
  4. package/bin/lib/cli-startup.js +105 -2
  5. package/bin/lib/cli-startup.test.js +179 -0
  6. package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-B_JQoreg.js} +1 -1
  7. package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
  8. package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
  10. package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
  11. package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
  12. package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
  13. package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
  14. package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
  15. package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
  16. package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
  17. package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
  20. package/dist/assets/SettingsView-BRxELjow.js +25 -0
  21. package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
  22. package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
  24. package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
  25. package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
  26. package/dist/assets/collapsible-DXbDynN4.js +3 -0
  27. package/dist/assets/context-menu-CqolsLCm.js +1 -0
  28. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
  29. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
  30. package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
  31. package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
  32. package/dist/assets/es-CN3E_zXj.js +15 -0
  33. package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
  34. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
  35. package/dist/assets/fr-9zZvQZD2.js +13 -0
  36. package/dist/assets/gitApi-CM42qUed.js +10 -0
  37. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
  38. package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
  39. package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
  40. package/dist/assets/ja-Dnjk_82N.js +15 -0
  41. package/dist/assets/ko-BxCEE99C.js +15 -0
  42. package/dist/assets/main-BILgBI3B.js +169 -0
  43. package/dist/assets/main-BeveiW6F.js +2 -0
  44. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  45. package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
  46. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  47. package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
  48. package/dist/assets/miniChat-BgrzM3u7.js +2 -0
  49. package/dist/assets/mobile-DtYoQE8C.js +2 -0
  50. package/dist/assets/multirun-jy8dEI96.js +4 -0
  51. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
  52. package/dist/assets/pl-CNNsWnPM.js +15 -0
  53. package/dist/assets/platform-BQghHRfp.js +6 -0
  54. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  55. package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
  56. package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
  57. package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
  58. package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
  59. package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
  60. package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
  61. package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
  62. package/dist/assets/src-CLN7Z-zx.css +2 -0
  63. package/dist/assets/store-DLCiZaA6.js +15 -0
  64. package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
  65. package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
  66. package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
  67. package/dist/assets/uk-rwQrxeYM.js +15 -0
  68. package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
  69. package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
  70. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  71. package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
  72. package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
  73. package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
  74. package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
  75. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  76. package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
  77. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  78. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  79. package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
  80. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  81. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  82. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  83. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  84. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  85. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  86. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  87. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  88. package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
  89. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  90. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  91. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  92. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  93. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  94. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  95. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  96. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  97. package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
  98. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  99. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  100. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  101. package/dist/index.html +13 -13
  102. package/dist/mini-chat.html +13 -13
  103. package/dist/mobile.html +13 -13
  104. package/package.json +2 -2
  105. package/server/index.js +392 -20
  106. package/server/lib/assistants/DOCUMENTATION.md +26 -21
  107. package/server/lib/assistants/assign.js +2 -1
  108. package/server/lib/assistants/contact-store.js +37 -19
  109. package/server/lib/assistants/contracts.js +11 -1
  110. package/server/lib/assistants/projection-history.test.js +454 -0
  111. package/server/lib/assistants/routes.js +11 -5
  112. package/server/lib/assistants/service.js +850 -294
  113. package/server/lib/assistants/service.test.js +588 -274
  114. package/server/lib/conversations/DOCUMENTATION.md +5 -5
  115. package/server/lib/conversations/service.js +87 -57
  116. package/server/lib/conversations/service.test.js +132 -100
  117. package/server/lib/conversations/validation.js +11 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +51 -5
  120. package/server/lib/event-stream/runtime.js +57 -20
  121. package/server/lib/event-stream/runtime.test.js +112 -0
  122. package/server/lib/llm/DOCUMENTATION.md +95 -84
  123. package/server/lib/llm/catalog.js +111 -45
  124. package/server/lib/llm/catalog.test.js +121 -59
  125. package/server/lib/llm/completions.js +41 -12
  126. package/server/lib/llm/completions.test.js +45 -4
  127. package/server/lib/llm/generate.js +516 -453
  128. package/server/lib/llm/generate.test.js +705 -484
  129. package/server/lib/llm/routes.js +8 -4
  130. package/server/lib/message-queue/DOCUMENTATION.md +5 -5
  131. package/server/lib/message-queue/opencode-adapter.js +191 -104
  132. package/server/lib/message-queue/opencode-adapter.test.js +328 -58
  133. package/server/lib/message-queue/runtime.js +15 -2
  134. package/server/lib/message-queue/runtime.test.js +6 -6
  135. package/server/lib/message-queue/worker.js +7 -1
  136. package/server/lib/notifications/runtime.js +5 -0
  137. package/server/lib/opencode/DOCUMENTATION.md +103 -16
  138. package/server/lib/opencode/agent-document.d.ts +28 -0
  139. package/server/lib/opencode/agent-document.js +280 -0
  140. package/server/lib/opencode/agent-document.test.js +137 -0
  141. package/server/lib/opencode/agents.js +96 -0
  142. package/server/lib/opencode/auth-state-runtime.js +5 -0
  143. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  144. package/server/lib/opencode/config-entity-routes.js +111 -102
  145. package/server/lib/opencode/config-entity-routes.test.js +123 -10
  146. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  147. package/server/lib/opencode/ensure-cli.js +238 -0
  148. package/server/lib/opencode/ensure-cli.test.js +119 -0
  149. package/server/lib/opencode/env-runtime.js +208 -111
  150. package/server/lib/opencode/env-runtime.test.js +211 -14
  151. package/server/lib/opencode/feature-routes-runtime.js +80 -5
  152. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  153. package/server/lib/opencode/http-server-connections.js +98 -0
  154. package/server/lib/opencode/http-server-connections.test.js +321 -0
  155. package/server/lib/opencode/lifecycle.js +539 -95
  156. package/server/lib/opencode/lifecycle.test.js +698 -6
  157. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  158. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  159. package/server/lib/opencode/network-runtime.js +28 -16
  160. package/server/lib/opencode/network-runtime.test.js +68 -4
  161. package/server/lib/opencode/openchamber-routes.js +7 -1
  162. package/server/lib/opencode/opencode2-pin.js +139 -0
  163. package/server/lib/opencode/opencode2-pin.test.js +88 -0
  164. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
  165. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  166. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  167. package/server/lib/opencode/plugin-compatibility.js +15 -0
  168. package/server/lib/opencode/plugin-compatibility.test.js +21 -0
  169. package/server/lib/opencode/plugin-routes.js +3 -18
  170. package/server/lib/opencode/plugin-routes.test.js +9 -9
  171. package/server/lib/opencode/plugins.js +4 -3
  172. package/server/lib/opencode/plugins.test.js +12 -0
  173. package/server/lib/opencode/provider-catalog.js +58 -12
  174. package/server/lib/opencode/provider-catalog.test.js +48 -0
  175. package/server/lib/opencode/proxy.js +368 -28
  176. package/server/lib/opencode/proxy.test.js +338 -0
  177. package/server/lib/opencode/routes.js +303 -76
  178. package/server/lib/opencode/routes.test.js +315 -50
  179. package/server/lib/opencode/runtime-contract.js +401 -0
  180. package/server/lib/opencode/runtime-contract.test.js +214 -0
  181. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  182. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  183. package/server/lib/opencode/server-utils-runtime.js +35 -0
  184. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  185. package/server/lib/opencode/shared-service.js +147 -0
  186. package/server/lib/opencode/shared-service.test.js +93 -0
  187. package/server/lib/opencode/shared.js +3 -3
  188. package/server/lib/opencode/shutdown-runtime.js +15 -6
  189. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  190. package/server/lib/opencode/skill-routes.js +26 -40
  191. package/server/lib/opencode/skill-routes.test.js +71 -7
  192. package/server/lib/opencode/skills.test.js +15 -0
  193. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  194. package/server/lib/opencode/static-routes-runtime.js +3 -1
  195. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  196. package/server/lib/opencode/v1-migration-gate.js +157 -0
  197. package/server/lib/opencode/v1-migration-gate.test.js +111 -0
  198. package/server/lib/opencode/v2-client.js +60 -0
  199. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  200. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  201. package/server/lib/opencode/watcher.js +2 -1
  202. package/server/lib/opencode/watcher.test.js +1 -1
  203. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  204. package/server/lib/permission-auto-accept/runtime.js +14 -7
  205. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  206. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  207. package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
  208. package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
  209. package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
  210. package/server/lib/scheduled-tasks/routes.js +27 -0
  211. package/server/lib/scheduled-tasks/routes.test.js +94 -0
  212. package/server/lib/scheduled-tasks/runtime.js +381 -310
  213. package/server/lib/scheduled-tasks/runtime.test.js +600 -309
  214. package/server/lib/session-goal/DOCUMENTATION.md +173 -22
  215. package/server/lib/session-goal/capability.js +57 -0
  216. package/server/lib/session-goal/capability.test.js +33 -0
  217. package/server/lib/session-goal/routes.js +240 -2
  218. package/server/lib/session-goal/routes.test.js +211 -0
  219. package/server/lib/session-goal/runtime.js +1617 -113
  220. package/server/lib/session-goal/runtime.test.js +1500 -70
  221. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  222. package/server/lib/session-index/event-ingest.js +89 -16
  223. package/server/lib/session-index/event-ingest.test.js +73 -0
  224. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  225. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  226. package/server/lib/session-index/routes.js +63 -10
  227. package/server/lib/session-index/routes.test.js +8 -8
  228. package/server/lib/session-index/service.js +7 -1
  229. package/server/lib/session-index/sync-runtime.js +171 -41
  230. package/server/lib/session-index/sync-runtime.test.js +164 -9
  231. package/server/lib/session-metadata/DOCUMENTATION.md +86 -0
  232. package/server/lib/session-metadata/routes.js +193 -0
  233. package/server/lib/session-metadata/routes.test.js +156 -0
  234. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  235. package/server/lib/session-metadata/session-archive.js +654 -0
  236. package/server/lib/session-metadata/session-archive.test.js +359 -0
  237. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  238. package/server/lib/session-metadata/session-metadata-store.js +375 -0
  239. package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
  240. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  241. package/server/lib/session-metadata/session-projection.js +255 -0
  242. package/server/lib/session-metadata/session-projection.test.js +64 -0
  243. package/server/lib/session-metadata/system-session.js +55 -0
  244. package/server/lib/session-metadata/system-session.test.js +55 -0
  245. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  246. package/server/lib/session-title/native-runtime.test.js +167 -0
  247. package/server/lib/session-title/runtime.js +67 -42
  248. package/server/lib/session-title/session-access.js +46 -0
  249. package/server/lib/session-title/session-access.test.js +91 -0
  250. package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
  251. package/server/lib/session-turn-pages/reconcile.service.js +1 -1
  252. package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
  253. package/server/lib/session-turn-pages/routes.js +123 -75
  254. package/server/lib/session-turn-pages/routes.test.js +566 -0
  255. package/server/lib/session-turn-pages/service.js +6 -2
  256. package/server/lib/session-turn-pages/service.test.js +34 -0
  257. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  258. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  259. package/server/lib/small-model/DOCUMENTATION.md +101 -146
  260. package/server/lib/small-model/catalog.js +84 -54
  261. package/server/lib/small-model/catalog.test.js +111 -9
  262. package/server/lib/small-model/custom-api.js +68 -0
  263. package/server/lib/small-model/custom-api.test.js +64 -0
  264. package/server/lib/small-model/index.js +125 -241
  265. package/server/lib/small-model/resolve.js +35 -120
  266. package/server/lib/small-model/resolve.test.js +20 -110
  267. package/server/lib/small-model/routes.js +1 -3
  268. package/server/lib/small-model/summary-settings.test.js +157 -253
  269. package/server/opencode-proxy.test.js +63 -12
  270. package/dist/assets/AssistantView-BUveAgc4.js +0 -3
  271. package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
  272. package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
  273. package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
  274. package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
  275. package/dist/assets/SettingsView-PkXIavr7.js +0 -25
  276. package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
  277. package/dist/assets/collapsible-D5Phaykh.js +0 -1
  278. package/dist/assets/context-menu-CubSDW2g.js +0 -1
  279. package/dist/assets/es-DA3MqOo0.js +0 -15
  280. package/dist/assets/fr-Dbz0CdIA.js +0 -13
  281. package/dist/assets/gitApi-DXyC4MiV.js +0 -7
  282. package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
  283. package/dist/assets/ja-DumanVQA.js +0 -15
  284. package/dist/assets/ko-C0IkiUjQ.js +0 -15
  285. package/dist/assets/main-BPS5ylib.js +0 -2
  286. package/dist/assets/main-Cpd9VWYs.js +0 -169
  287. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  288. package/dist/assets/miniChat-B8_KLWiY.js +0 -2
  289. package/dist/assets/mobile-CtktKrXn.js +0 -2
  290. package/dist/assets/multirun-CYfP9Cfm.js +0 -4
  291. package/dist/assets/pl-B9No6MQp.js +0 -15
  292. package/dist/assets/platform-0w_Q9_ji.js +0 -2
  293. package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
  294. package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
  295. package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
  296. package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
  297. package/dist/assets/src-rS3zCcsC.css +0 -2
  298. package/dist/assets/store-5B_LDnLM.js +0 -15
  299. package/dist/assets/uk-C_1KkqyP.js +0 -15
  300. package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
  301. package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
  302. package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
  303. package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
  304. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  305. package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
  306. package/dist/assets/zh-CN-uWILgBdt.js +0 -15
  307. package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
  308. package/server/lib/small-model/call.js +0 -427
  309. package/server/lib/small-model/call.test.js +0 -382
  310. package/server/lib/small-model/opencode-session.js +0 -339
  311. package/server/lib/small-model/opencode-session.test.js +0 -177
@@ -122,15 +122,28 @@ export const createManagedCapabilitiesRuntime = ({
122
122
  bridgeOrigin = parsed.origin;
123
123
  };
124
124
 
125
+ const publishHostShim = async () => {
126
+ const sourceDir = pathLike.join(pathLike.dirname(fileURLToPath(import.meta.url)), 'v2-plugin-host-shim');
127
+ const target = pathLike.join(dataDir, 'managed-opencode-capabilities', 'v2-plugin-host-shim');
128
+ await fsLike.mkdir(target, { recursive: true });
129
+ await Promise.all(['package.json', 'index.mjs'].map((name) => fsLike.copyFile(pathLike.join(sourceDir, name), pathLike.join(target, name))));
130
+ return target;
131
+ };
132
+
125
133
  const prepareManagedChildEnv = async (baseEnv = {}) => {
126
134
  if (!bridgeOrigin) throw new Error('Managed scheduled-task bridge origin is unavailable');
127
- const { pluginPath, instructionsPath } = await publishResources();
135
+ const [{ pluginPath, instructionsPath }, shimDirectory] = await Promise.all([
136
+ publishResources(),
137
+ publishHostShim(),
138
+ ]);
128
139
  const token = cryptoLike.randomBytes(32).toString('hex');
129
140
  const pluginUrl = pathToFileURL(pluginPath).href;
130
141
  identity = { version: materialVersion, origin: bridgeOrigin, token, childPid: null };
142
+ const merged = JSON.parse(mergeManagedOpenCodeConfig({ configContent: baseEnv.OPENCODE_CONFIG_CONTENT, pluginUrl, instructionsUrl: instructionsPath }));
143
+ merged.plugin = stableDedupe([...(Array.isArray(merged.plugin) ? merged.plugin : []), shimDirectory]);
131
144
  return {
132
145
  ...baseEnv,
133
- OPENCODE_CONFIG_CONTENT: mergeManagedOpenCodeConfig({ configContent: baseEnv.OPENCODE_CONFIG_CONTENT, pluginUrl, instructionsUrl: instructionsPath }),
146
+ OPENCODE_CONFIG_CONTENT: JSON.stringify(merged),
134
147
  OPENCHAMBER_SCHEDULED_TASK_BRIDGE_ORIGIN: bridgeOrigin,
135
148
  OPENCHAMBER_SCHEDULED_TASK_BRIDGE_PATH: MANAGED_SCHEDULED_TASK_TOOL_PATH,
136
149
  OPENCHAMBER_SCHEDULED_TASK_TOKEN_HEADER: MANAGED_SCHEDULED_TASK_TOKEN_HEADER,
@@ -24,6 +24,7 @@ describe('managed OpenCode capabilities runtime', () => {
24
24
  runtime.recordManagedChildPid(44);
25
25
  const identity = runtime.getCapabilityIdentity();
26
26
  expect(childEnv.OPENCODE_CONFIG_CONTENT).toContain('scheduled-task.mjs');
27
+ expect(JSON.parse(childEnv.OPENCODE_CONFIG_CONTENT).plugin).toContain(path.join(dataDir, 'managed-opencode-capabilities', 'v2-plugin-host-shim'));
27
28
  expect(JSON.parse(childEnv.OPENCODE_CONFIG_CONTENT).instructions[0]).toMatch(/^\//);
28
29
  expect(childEnv.OPENCHAMBER_SCHEDULED_TASK_BRIDGE_PATH).toBe(MANAGED_SCHEDULED_TASK_TOOL_PATH);
29
30
  expect(childEnv.OPENCHAMBER_SCHEDULED_TASK_TOKEN_HEADER).toBe(MANAGED_SCHEDULED_TASK_TOKEN_HEADER);
@@ -1,3 +1,5 @@
1
+ import { evaluateOpenCodeHealthBody } from './opencode2-pin.js';
2
+
1
3
  export const createOpenCodeNetworkRuntime = (deps) => {
2
4
  const {
3
5
  state,
@@ -41,26 +43,29 @@ export const createOpenCodeNetworkRuntime = (deps) => {
41
43
 
42
44
  const waitForReady = async (url, timeoutMs = 10000) => {
43
45
  const start = Date.now();
46
+ const baseUrl = url.replace(/\/+$/, '');
44
47
  while (Date.now() - start < timeoutMs) {
45
48
  let timeout = null;
46
49
  try {
47
50
  const controller = new AbortController();
48
51
  timeout = setTimeout(() => controller.abort(), 3000);
49
- const response = await fetch(`${url.replace(/\/+$/, '')}/global/health`, {
50
- method: 'GET',
51
- headers: {
52
- Accept: 'application/json',
53
- ...getOpenCodeAuthHeaders(),
54
- },
55
- signal: controller.signal,
56
- });
57
- clearTimeout(timeout);
58
- timeout = null;
59
-
60
- if (response.ok) {
61
- const body = await response.json().catch(() => null);
62
- if (body?.healthy === true) {
63
- return true;
52
+ const headers = {
53
+ Accept: 'application/json',
54
+ ...getOpenCodeAuthHeaders(),
55
+ };
56
+ for (const healthPath of ['/api/info', '/global/health']) {
57
+ const response = await fetch(`${baseUrl}${healthPath}`, {
58
+ method: 'GET',
59
+ headers,
60
+ signal: controller.signal,
61
+ });
62
+ if (response.ok) {
63
+ const body = await response.json().catch(() => null);
64
+ // Official 2.x /api/info is ServerInfo (version, no healthy);
65
+ // /global/health may still return { healthy, version }.
66
+ if (evaluateOpenCodeHealthBody(body).ok) {
67
+ return true;
68
+ }
64
69
  }
65
70
  }
66
71
  } catch {
@@ -88,8 +93,15 @@ export const createOpenCodeNetworkRuntime = (deps) => {
88
93
  throw new Error('OpenCode port is not available');
89
94
  }
90
95
  const normalizedPath = path.startsWith('/') ? path : `/${path}`;
96
+ // v2 protocol mounts every route under `/api` (`@opencode-ai/protocol`).
97
+ // Callers pass unprefixed API paths (`/session`, `/event`, …); the bare
98
+ // upstream path falls through to the v2 Web UI HTML fallback. `/` stays the
99
+ // upstream origin for SDK base URLs, and already-prefixed paths are kept.
100
+ const apiPath = normalizedPath === '/' || normalizedPath.startsWith('/api')
101
+ ? normalizedPath
102
+ : `/api${normalizedPath}`;
91
103
  const prefix = normalizeApiPrefix(prefixOverride !== undefined ? prefixOverride : '');
92
- const fullPath = `${prefix}${normalizedPath}`;
104
+ const fullPath = `${prefix}${apiPath}`;
93
105
  const base = state.openCodeBaseUrl ?? `http://${resolveConnectHostname()}:${state.openCodePort}`;
94
106
  return `${base}${fullPath}`;
95
107
  };
@@ -23,6 +23,63 @@ describe('OpenCode network runtime', () => {
23
23
  globalThis.fetch = originalFetch;
24
24
  });
25
25
 
26
+ it('probes /api/info with Authorization and treats healthy true as ready', async () => {
27
+ const fetchMock = vi.fn(async (url, options) => {
28
+ expect(String(url)).toContain('/api/info');
29
+ expect(options.headers.Authorization).toMatch(/^Basic /);
30
+ return new Response(JSON.stringify({ version: '2.0.12', pid: 1, urls: [], paths: { tmp: '/tmp' } }), {
31
+ status: 200,
32
+ headers: { 'content-type': 'application/json' },
33
+ });
34
+ });
35
+ globalThis.fetch = fetchMock;
36
+
37
+ const runtime = createOpenCodeNetworkRuntime({
38
+ state: {
39
+ openCodePort: 4096,
40
+ openCodeBaseUrl: null,
41
+ openCodeApiPrefix: '',
42
+ openCodeApiPrefixDetected: false,
43
+ openCodeApiDetectionTimer: null,
44
+ },
45
+ getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic Zml4dHVyZQ==' }),
46
+ });
47
+
48
+ await expect(runtime.waitForReady('http://127.0.0.1:4096', 1000)).resolves.toBe(true);
49
+ expect(String(fetchMock.mock.calls[0][0])).toContain('/api/info');
50
+ });
51
+
52
+ it('falls back to /global/health when /api/info is not healthy', async () => {
53
+ const fetchMock = vi.fn(async (url) => {
54
+ if (String(url).includes('/api/info')) {
55
+ return new Response('not found', { status: 404 });
56
+ }
57
+ if (String(url).includes('/global/health')) {
58
+ return new Response(JSON.stringify({ healthy: true, version: '2.0.12' }), {
59
+ status: 200,
60
+ headers: { 'content-type': 'application/json' },
61
+ });
62
+ }
63
+ return new Response('no', { status: 500 });
64
+ });
65
+ globalThis.fetch = fetchMock;
66
+
67
+ const runtime = createOpenCodeNetworkRuntime({
68
+ state: {
69
+ openCodePort: 4096,
70
+ openCodeBaseUrl: null,
71
+ openCodeApiPrefix: '',
72
+ openCodeApiPrefixDetected: false,
73
+ openCodeApiDetectionTimer: null,
74
+ },
75
+ getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic Zml4dHVyZQ==' }),
76
+ });
77
+
78
+ await expect(runtime.waitForReady('http://127.0.0.1:4096', 1000)).resolves.toBe(true);
79
+ expect(String(fetchMock.mock.calls[0][0])).toContain('/api/info');
80
+ expect(String(fetchMock.mock.calls[1][0])).toContain('/global/health');
81
+ });
82
+
26
83
  it('returns false when readiness fetch rejects', async () => {
27
84
  globalThis.fetch = vi.fn(async () => {
28
85
  throw new Error('offline');
@@ -37,7 +94,7 @@ describe('OpenCode network runtime', () => {
37
94
  it('builds managed OpenCode URLs against IPv4 loopback by default', () => {
38
95
  const runtime = createRuntime();
39
96
 
40
- expect(runtime.buildOpenCodeUrl('/provider')).toBe('http://127.0.0.1:4096/provider');
97
+ expect(runtime.buildOpenCodeUrl('/provider')).toBe('http://127.0.0.1:4096/api/provider');
41
98
  });
42
99
 
43
100
  it('keeps external OpenCode base URLs authoritative', () => {
@@ -45,13 +102,20 @@ describe('OpenCode network runtime', () => {
45
102
  state: { openCodeBaseUrl: 'http://remote.example:4096' },
46
103
  });
47
104
 
48
- expect(runtime.buildOpenCodeUrl('/provider')).toBe('http://remote.example:4096/provider');
105
+ expect(runtime.buildOpenCodeUrl('/provider')).toBe('http://remote.example:4096/api/provider');
106
+ });
107
+
108
+ it('keeps SDK base URLs (/) and already-prefixed paths untouched', () => {
109
+ const runtime = createRuntime();
110
+
111
+ expect(runtime.buildOpenCodeUrl('/')).toBe('http://127.0.0.1:4096/');
112
+ expect(runtime.buildOpenCodeUrl('/api/info')).toBe('http://127.0.0.1:4096/api/info');
49
113
  });
50
114
 
51
115
  it('normalizes wildcard and IPv6 OpenCode bind hosts for local connects', () => {
52
116
  expect(createRuntime({ configuredOpenCodeHostname: '0.0.0.0' }).buildOpenCodeUrl('/provider'))
53
- .toBe('http://127.0.0.1:4096/provider');
117
+ .toBe('http://127.0.0.1:4096/api/provider');
54
118
  expect(createRuntime({ configuredOpenCodeHostname: '::1' }).buildOpenCodeUrl('/provider'))
55
- .toBe('http://[::1]:4096/provider');
119
+ .toBe('http://[::1]:4096/api/provider');
56
120
  });
57
121
  });
@@ -14,11 +14,17 @@ export const registerOpenChamberRoutes = (app, dependencies) => {
14
14
  getCachedZenModels,
15
15
  sessionIndexService,
16
16
  sessionIndexSyncRuntime,
17
+ /** Production: committed Host metadata for archive-aware snapshot/lookup. */
18
+ getCommittedHostMetadata = null,
17
19
  transcriptCacheService,
18
20
  express,
19
21
  } = dependencies;
20
22
 
21
- registerSessionIndexRoutes(app, { sessionIndexService, sessionIndexSyncRuntime });
23
+ registerSessionIndexRoutes(app, {
24
+ sessionIndexService,
25
+ sessionIndexSyncRuntime,
26
+ getCommittedHostMetadata,
27
+ });
22
28
  registerTranscriptCacheRoutes(app, { transcriptCacheService });
23
29
  registerConfigSyncRoutes(app, { express });
24
30
 
@@ -0,0 +1,139 @@
1
+ // Pinned desktop/runtime opencode2. Upgrade and prepare must not fall back to 1.18.x.
2
+ export const PINNED_OPENCODE2_VERSION = '2.0.12';
3
+
4
+ // Global npm/bun package that installs the opencode v2 binary (not 1.x opencode-ai).
5
+ export const OPENCODE2_NPM_PACKAGE = '@opencode/cli';
6
+
7
+ export function isOpenCode1xVersion(value) {
8
+ if (typeof value !== 'string') return false;
9
+ const normalized = value.trim().replace(/^v/i, '');
10
+ if (!normalized) return false;
11
+ return /^1(?:\.|$)/.test(normalized);
12
+ }
13
+
14
+ /**
15
+ * Health admission for managed/external opencode2.
16
+ * healthy alone is not enough: reject 1.x and missing/unknown version strings.
17
+ */
18
+ export function isAcceptableOpenCode2HealthVersion(value) {
19
+ if (typeof value !== 'string') return false;
20
+ const normalized = value.trim().replace(/^v/i, '');
21
+ if (!normalized) return false;
22
+ // Official CLI is 2.x. 1.x and pre-2 names like `opencode2 v0.0.0-beta-*` are not it.
23
+ if (!/^2(?:\.|$)/.test(normalized)) return false;
24
+ return /^\d+(?:\.\d+)*(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(normalized);
25
+ }
26
+
27
+ /**
28
+ * Pure health/info body gate used by lifecycle and VS Code sidecar.
29
+ * Accepts classic `{ healthy: true, version }` and official 2.x `ServerInfo`
30
+ * (`GET /api/info` → `{ version, pid, urls, paths }` without `healthy`).
31
+ * @param {{ healthy?: unknown, version?: unknown } | null | undefined} body
32
+ * @returns {{ ok: boolean, version: string | null, reason?: string }}
33
+ */
34
+ export function evaluateOpenCodeHealthBody(body) {
35
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
36
+ return { ok: false, version: null, reason: 'invalid-body' };
37
+ }
38
+ // Classic /api/health required healthy:true; /api/info omits healthy entirely.
39
+ if (Object.prototype.hasOwnProperty.call(body, 'healthy') && body.healthy !== true) {
40
+ return { ok: false, version: null, reason: 'unhealthy' };
41
+ }
42
+ const raw = typeof body.version === 'string' ? body.version.trim() : '';
43
+ const version = raw ? raw.replace(/^v/i, '') : null;
44
+ if (!isAcceptableOpenCode2HealthVersion(version)) {
45
+ if (isOpenCode1xVersion(version)) {
46
+ return { ok: false, version, reason: '1x-version' };
47
+ }
48
+ return { ok: false, version, reason: 'unknown-version' };
49
+ }
50
+ return { ok: true, version };
51
+ }
52
+
53
+ export function rejectOpenCode1xUpgradeTarget(target) {
54
+ if (isOpenCode1xVersion(target)) {
55
+ const error = new Error(`OpenCode upgrade refuses 1.x target: ${target}`);
56
+ error.code = 'OPENCODE_UPGRADE_1X_REFUSED';
57
+ throw error;
58
+ }
59
+ return target;
60
+ }
61
+
62
+ export function resolveOpenCode2UpgradeTarget(target) {
63
+ const trimmed = typeof target === 'string' ? target.trim() : '';
64
+ if (trimmed) {
65
+ rejectOpenCode1xUpgradeTarget(trimmed);
66
+ return trimmed;
67
+ }
68
+ return PINNED_OPENCODE2_VERSION;
69
+ }
70
+
71
+ /**
72
+ * Parse `opencode2 --version` / `opencode --version` stdout.
73
+ * v2: `opencode2 v2.0.12` (name then version). 1.x: `1.18.18`.
74
+ */
75
+ export function parseOpenCode2VersionOutput(stdout) {
76
+ const tokens = String(stdout || '').trim().split(/\s+/);
77
+ const versionToken = tokens.find((token) => /^v?\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(token));
78
+ if (!versionToken) return '';
79
+ return versionToken.replace(/^v/, '');
80
+ }
81
+
82
+ export function openCodeBinaryName(platform = process.platform) {
83
+ return platform === 'win32' ? 'opencode.exe' : 'opencode';
84
+ }
85
+
86
+ /** @deprecated Packaging leftover. Runtime install uses `openCodeBinaryName`. */
87
+ export function openCode2BinaryName(platform = process.platform) {
88
+ return openCodeBinaryName(platform);
89
+ }
90
+
91
+ export function resolveOpenCode2NpmArchitecture(arch = process.arch) {
92
+ const normalized = String(arch || '').trim().toLowerCase();
93
+ if (normalized === 'x64' || normalized === 'amd64' || normalized === 'x86_64') return 'x64';
94
+ if (normalized === 'arm64' || normalized === 'aarch64') return 'arm64';
95
+ throw new Error(`Unsupported OpenCode architecture ${JSON.stringify(arch)}`);
96
+ }
97
+
98
+ /**
99
+ * Official 2.x ships as `@opencode/cli-<os>-<arch>[-baseline]`.
100
+ * `targetArchitecture` may be `{ opencode }` (Electron prepare) or an arch string.
101
+ */
102
+ export function npmPackageForOpenCode2(platform = process.platform, targetArchitecture = process.arch) {
103
+ const rawArch = targetArchitecture && typeof targetArchitecture === 'object'
104
+ ? (targetArchitecture.opencode ?? targetArchitecture.node)
105
+ : targetArchitecture;
106
+ const arch = resolveOpenCode2NpmArchitecture(rawArch);
107
+ const os = { darwin: 'darwin', win32: 'windows', linux: 'linux' }[platform];
108
+ if (!os) throw new Error(`No opencode2 npm package mapping for platform ${platform}`);
109
+ const suffix = arch === 'x64' ? '-baseline' : '';
110
+ return `@opencode/cli-${os}-${arch}${suffix}`;
111
+ }
112
+
113
+ function parseVersionForComparison(value) {
114
+ const normalized = String(value || '').replace(/^v/i, '').split('+')[0];
115
+ const prereleaseIndex = normalized.indexOf('-');
116
+ const core = prereleaseIndex >= 0 ? normalized.slice(0, prereleaseIndex) : normalized;
117
+ const parts = core.split('.').map((part) => {
118
+ const parsed = Number.parseInt(part || '0', 10);
119
+ return Number.isFinite(parsed) ? parsed : 0;
120
+ });
121
+ return { parts, prerelease: prereleaseIndex >= 0 };
122
+ }
123
+
124
+ export function compareOpenCode2Versions(left, right) {
125
+ const a = parseVersionForComparison(left);
126
+ const b = parseVersionForComparison(right);
127
+ const length = Math.max(a.parts.length, b.parts.length);
128
+ for (let index = 0; index < length; index += 1) {
129
+ const diff = (a.parts[index] || 0) - (b.parts[index] || 0);
130
+ if (diff !== 0) return diff;
131
+ }
132
+ if (a.prerelease !== b.prerelease) return a.prerelease ? -1 : 1;
133
+ return 0;
134
+ }
135
+
136
+ export function isOpenCode2VersionAtLeast(version, minimum = PINNED_OPENCODE2_VERSION) {
137
+ if (!isAcceptableOpenCode2HealthVersion(version)) return false;
138
+ return compareOpenCode2Versions(version, minimum) >= 0;
139
+ }
@@ -0,0 +1,88 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ OPENCODE2_NPM_PACKAGE,
4
+ PINNED_OPENCODE2_VERSION,
5
+ compareOpenCode2Versions,
6
+ evaluateOpenCodeHealthBody,
7
+ isAcceptableOpenCode2HealthVersion,
8
+ isOpenCode1xVersion,
9
+ isOpenCode2VersionAtLeast,
10
+ npmPackageForOpenCode2,
11
+ parseOpenCode2VersionOutput,
12
+ rejectOpenCode1xUpgradeTarget,
13
+ resolveOpenCode2UpgradeTarget,
14
+ } from './opencode2-pin.js';
15
+
16
+ describe('opencode2 pin (ticket 12)', () => {
17
+ it('rejects 1.x version strings and accepts the pinned v2', () => {
18
+ expect(isOpenCode1xVersion('1.18.4')).toBe(true);
19
+ expect(isOpenCode1xVersion('v1.18.18')).toBe(true);
20
+ expect(isOpenCode1xVersion('1.18.x')).toBe(true);
21
+ expect(isOpenCode1xVersion('1')).toBe(true);
22
+ expect(isOpenCode1xVersion(PINNED_OPENCODE2_VERSION)).toBe(false);
23
+ expect(isOpenCode1xVersion('2.0.12')).toBe(false);
24
+ expect(isOpenCode1xVersion('')).toBe(false);
25
+ expect(isOpenCode1xVersion(null)).toBe(false);
26
+ });
27
+
28
+ it('throws on 1.x upgrade targets and defaults empty to the pin', () => {
29
+ expect(() => rejectOpenCode1xUpgradeTarget('1.18.4')).toThrow(/1\.x/);
30
+ expect(() => rejectOpenCode1xUpgradeTarget('1.18.4')).toThrow(expect.objectContaining({ code: 'OPENCODE_UPGRADE_1X_REFUSED' }));
31
+ expect(resolveOpenCode2UpgradeTarget(undefined)).toBe(PINNED_OPENCODE2_VERSION);
32
+ expect(resolveOpenCode2UpgradeTarget('')).toBe(PINNED_OPENCODE2_VERSION);
33
+ expect(resolveOpenCode2UpgradeTarget(PINNED_OPENCODE2_VERSION)).toBe(PINNED_OPENCODE2_VERSION);
34
+ expect(() => resolveOpenCode2UpgradeTarget('1.18.18')).toThrow(/1\.x/);
35
+ });
36
+
37
+ it('pins the opencode2 npm package name (not 1.x opencode-ai)', () => {
38
+ expect(OPENCODE2_NPM_PACKAGE).toBe('@opencode/cli');
39
+ expect(OPENCODE2_NPM_PACKAGE).not.toBe('opencode-ai');
40
+ expect(OPENCODE2_NPM_PACKAGE).not.toBe('@opencode-ai/cli');
41
+ });
42
+
43
+ it('accepts authoritative v2 health/info versions and rejects 1.x / missing / noise', () => {
44
+ expect(isAcceptableOpenCode2HealthVersion(PINNED_OPENCODE2_VERSION)).toBe(true);
45
+ expect(isAcceptableOpenCode2HealthVersion('v2.0.12')).toBe(true);
46
+ expect(isAcceptableOpenCode2HealthVersion('0.0.0-beta-18155')).toBe(false);
47
+ expect(isAcceptableOpenCode2HealthVersion('1.15.0')).toBe(false);
48
+ expect(isAcceptableOpenCode2HealthVersion('v1.18.18')).toBe(false);
49
+ expect(isAcceptableOpenCode2HealthVersion('')).toBe(false);
50
+ expect(isAcceptableOpenCode2HealthVersion(null)).toBe(false);
51
+ expect(isAcceptableOpenCode2HealthVersion('not-a-version')).toBe(false);
52
+
53
+ expect(evaluateOpenCodeHealthBody({ healthy: true, version: PINNED_OPENCODE2_VERSION })).toEqual({
54
+ ok: true,
55
+ version: PINNED_OPENCODE2_VERSION,
56
+ });
57
+ // Official 2.x ServerInfo from GET /api/info has version without healthy.
58
+ expect(evaluateOpenCodeHealthBody({ version: PINNED_OPENCODE2_VERSION, pid: 1 })).toEqual({
59
+ ok: true,
60
+ version: PINNED_OPENCODE2_VERSION,
61
+ });
62
+ expect(evaluateOpenCodeHealthBody({ healthy: true, version: '1.15.0' })).toMatchObject({
63
+ ok: false,
64
+ version: '1.15.0',
65
+ reason: '1x-version',
66
+ });
67
+ expect(evaluateOpenCodeHealthBody({ healthy: true })).toMatchObject({
68
+ ok: false,
69
+ reason: 'unknown-version',
70
+ });
71
+ expect(evaluateOpenCodeHealthBody({ healthy: false, version: PINNED_OPENCODE2_VERSION })).toMatchObject({
72
+ ok: false,
73
+ reason: 'unhealthy',
74
+ });
75
+ expect(evaluateOpenCodeHealthBody(null)).toMatchObject({ ok: false, reason: 'invalid-body' });
76
+ });
77
+
78
+ it('parses --version output and compares against the pin', () => {
79
+ expect(parseOpenCode2VersionOutput('opencode2 v2.0.12\n')).toBe('2.0.12');
80
+ expect(parseOpenCode2VersionOutput('1.18.18\n')).toBe('1.18.18');
81
+ expect(compareOpenCode2Versions('2.0.12', '2.0.1')).toBeGreaterThan(0);
82
+ expect(isOpenCode2VersionAtLeast('2.0.12', '2.0.12')).toBe(true);
83
+ expect(isOpenCode2VersionAtLeast('2.0.1', '2.0.12')).toBe(false);
84
+ expect(isOpenCode2VersionAtLeast('1.18.18', '2.0.12')).toBe(false);
85
+ expect(npmPackageForOpenCode2('darwin', 'arm64')).toBe('@opencode/cli-darwin-arm64');
86
+ expect(npmPackageForOpenCode2('darwin', { opencode: 'x64' })).toBe('@opencode/cli-darwin-x64-baseline');
87
+ });
88
+ });