@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
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Ownership + upgrade status for OpenCode runtimes (ticket 12).
3
+ *
4
+ * Binary ownership and process ownership are modeled separately:
5
+ * - arbitrary external serve is never upgraded/restarted in-app
6
+ * - official shared service can be replaced using an owned-cache binary
7
+ * - managed process using a global CLI is "global-cli" (manual guidance)
8
+ * - managed process using OpenChamber cache under opencode-cli/ is "owned-cache"
9
+ * - bundled desktop binary cannot be upgraded separately
10
+ *
11
+ * In-app upgrade only mutates the owned cache, then launches that target and
12
+ * verifies the running serve identity/version/contract before success.
13
+ */
14
+
15
+ import path from 'node:path';
16
+ import {
17
+ PINNED_OPENCODE2_VERSION,
18
+ compareOpenCode2Versions,
19
+ isOpenCode1xVersion,
20
+ resolveOpenCode2UpgradeTarget,
21
+ } from './opencode2-pin.js';
22
+ import {
23
+ installedOpenCode2BinaryPath,
24
+ resolveOpenChamberDataDir,
25
+ } from './ensure-cli.js';
26
+ import { evaluateRuntimeContract, normalizeRuntimeVersion } from './runtime-contract.js';
27
+
28
+ /** @typedef {'shared-service' | 'external-serve' | 'owned-cache' | 'global-cli' | 'bundled' | 'settings' | 'env' | 'unknown'} RuntimeOwnership */
29
+
30
+ /**
31
+ * @param {string | null | undefined} binaryPath
32
+ * @param {string | null | undefined} dataDir
33
+ */
34
+ export function isOwnedOpenCodeCachePath(binaryPath, dataDir) {
35
+ if (typeof binaryPath !== 'string' || !binaryPath.trim()) return false;
36
+ const root = typeof dataDir === 'string' && dataDir.trim()
37
+ ? path.resolve(dataDir.trim())
38
+ : resolveOpenChamberDataDir();
39
+ const cacheRoot = path.resolve(root, 'opencode-cli');
40
+ const resolved = path.resolve(binaryPath.trim());
41
+ const rel = path.relative(cacheRoot, resolved);
42
+ return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
43
+ }
44
+
45
+ /**
46
+ * @param {{
47
+ * isExternal?: boolean,
48
+ * isSharedService?: boolean,
49
+ * binarySource?: string | null,
50
+ * binaryPath?: string | null,
51
+ * dataDir?: string | null,
52
+ * }} input
53
+ */
54
+ export function classifyRuntimeOwnership(input = {}) {
55
+ if (input.isSharedService === true) {
56
+ return {
57
+ ownership: /** @type {RuntimeOwnership} */ ('shared-service'),
58
+ processOwnership: 'shared',
59
+ binaryOwnership: isOwnedOpenCodeCachePath(input.binaryPath, input.dataDir) ? 'owned-cache' : 'external-or-global',
60
+ canUpgradeInApp: true,
61
+ supplySource: 'shared-service',
62
+ management: 'in-app',
63
+ reason: null,
64
+ guidance: null,
65
+ };
66
+ }
67
+ if (input.isExternal === true) {
68
+ return {
69
+ ownership: /** @type {RuntimeOwnership} */ ('external-serve'),
70
+ processOwnership: 'external',
71
+ binaryOwnership: isOwnedOpenCodeCachePath(input.binaryPath, input.dataDir) ? 'owned-cache' : 'external-or-global',
72
+ canUpgradeInApp: false,
73
+ supplySource: 'external-serve',
74
+ management: 'manual-external',
75
+ reason: 'external-serve',
76
+ guidance: 'This is an external OpenCode serve. Restart or upgrade it yourself; OpenChamber will not replace that process.',
77
+ };
78
+ }
79
+
80
+ const source = typeof input.binarySource === 'string' ? input.binarySource.trim() : '';
81
+ const ownedPath = isOwnedOpenCodeCachePath(input.binaryPath, input.dataDir);
82
+
83
+ if (source === 'bundled') {
84
+ return {
85
+ ownership: /** @type {RuntimeOwnership} */ ('bundled'),
86
+ processOwnership: 'managed',
87
+ binaryOwnership: 'bundled',
88
+ canUpgradeInApp: false,
89
+ supplySource: 'bundled',
90
+ management: 'bundled',
91
+ reason: 'bundled',
92
+ guidance: 'OpenCode is bundled with OpenChamber Desktop and cannot be upgraded separately from the app.',
93
+ };
94
+ }
95
+
96
+ if (source === 'installed' || ownedPath) {
97
+ return {
98
+ ownership: /** @type {RuntimeOwnership} */ ('owned-cache'),
99
+ processOwnership: 'managed',
100
+ binaryOwnership: 'owned-cache',
101
+ canUpgradeInApp: true,
102
+ supplySource: 'owned-cache',
103
+ management: 'in-app',
104
+ reason: null,
105
+ guidance: null,
106
+ };
107
+ }
108
+
109
+ if (source === 'settings') {
110
+ return {
111
+ ownership: /** @type {RuntimeOwnership} */ ('settings'),
112
+ processOwnership: 'managed',
113
+ binaryOwnership: 'user-configured',
114
+ canUpgradeInApp: false,
115
+ supplySource: 'settings',
116
+ management: 'manual-global',
117
+ reason: 'settings-binary',
118
+ guidance: 'OpenCode is launched from a user-configured binary path. Change or upgrade that binary yourself, or clear the setting to use OpenChamber-managed cache.',
119
+ };
120
+ }
121
+
122
+ if (source === 'env') {
123
+ return {
124
+ ownership: /** @type {RuntimeOwnership} */ ('env'),
125
+ processOwnership: 'managed',
126
+ binaryOwnership: 'env',
127
+ canUpgradeInApp: false,
128
+ supplySource: 'env',
129
+ management: 'manual-global',
130
+ reason: 'env-binary',
131
+ guidance: 'OpenCode is launched from OPENCODE_BINARY / related env. Upgrade that install yourself, or unset the env to allow OpenChamber-owned cache management.',
132
+ };
133
+ }
134
+
135
+ // path / fallback / where / discovered → treat as global CLI (read-only for upgrade).
136
+ if (source === 'path' || source === 'fallback' || source === 'where' || source === 'discovered') {
137
+ return {
138
+ ownership: /** @type {RuntimeOwnership} */ ('global-cli'),
139
+ processOwnership: 'managed',
140
+ binaryOwnership: 'global-cli',
141
+ canUpgradeInApp: false,
142
+ supplySource: 'global-cli',
143
+ management: 'manual-global',
144
+ reason: 'global-cli',
145
+ guidance: 'OpenChamber is using your global OpenCode CLI. Upgrade it with your package manager (for example npm/bun), or install an OpenChamber-owned cache and select that target explicitly.',
146
+ };
147
+ }
148
+
149
+ return {
150
+ ownership: /** @type {RuntimeOwnership} */ ('unknown'),
151
+ processOwnership: input.isExternal ? 'external' : 'managed',
152
+ binaryOwnership: ownedPath ? 'owned-cache' : 'unknown',
153
+ canUpgradeInApp: ownedPath,
154
+ supplySource: ownedPath ? 'owned-cache' : 'unknown',
155
+ management: ownedPath ? 'in-app' : 'none',
156
+ reason: ownedPath ? null : 'unknown-ownership',
157
+ guidance: ownedPath
158
+ ? null
159
+ : 'OpenCode supply source could not be classified. In-app upgrade is disabled until ownership is clear.',
160
+ };
161
+ }
162
+
163
+ /**
164
+ * @param {{
165
+ * ownership: ReturnType<typeof classifyRuntimeOwnership>,
166
+ * serveVersion?: string | null,
167
+ * cliVersion?: string | null,
168
+ * targetVersion?: string | null,
169
+ * contract?: ReturnType<typeof evaluateRuntimeContract> | null,
170
+ * operation?: Record<string, unknown> | null,
171
+ * hasActiveTasks?: boolean,
172
+ * }} input
173
+ */
174
+ export function buildUpgradeStatusSnapshot(input) {
175
+ const ownership = input.ownership;
176
+ const serveVersion = normalizeRuntimeVersion(input.serveVersion);
177
+ const cliVersion = normalizeRuntimeVersion(input.cliVersion);
178
+ const currentVersion = serveVersion || cliVersion;
179
+ let targetVersion = null;
180
+ try {
181
+ targetVersion = resolveOpenCode2UpgradeTarget(input.targetVersion ?? PINNED_OPENCODE2_VERSION);
182
+ } catch {
183
+ targetVersion = PINNED_OPENCODE2_VERSION;
184
+ }
185
+ if (isOpenCode1xVersion(targetVersion)) {
186
+ targetVersion = PINNED_OPENCODE2_VERSION;
187
+ }
188
+
189
+ const contract = input.contract || evaluateRuntimeContract({
190
+ serveVersion,
191
+ cliVersion,
192
+ reachable: serveVersion != null || cliVersion != null,
193
+ authenticated: true,
194
+ healthOk: serveVersion != null,
195
+ });
196
+
197
+ const canManage = ownership.canUpgradeInApp === true;
198
+ const newerTarget = Boolean(
199
+ currentVersion
200
+ && targetVersion
201
+ && !isOpenCode1xVersion(currentVersion)
202
+ && compareOpenCode2Versions(targetVersion, currentVersion) > 0,
203
+ );
204
+ const available = canManage && newerTarget ? true : (canManage ? false : null);
205
+
206
+ /** @type {string | null} */
207
+ let reason = ownership.reason;
208
+ if (canManage && available === false && currentVersion && targetVersion) {
209
+ reason = 'up-to-date';
210
+ }
211
+ if (!canManage && ownership.reason) {
212
+ reason = ownership.reason;
213
+ }
214
+ if (input.hasActiveTasks === true && canManage && available) {
215
+ reason = reason || 'active-tasks';
216
+ }
217
+
218
+ return {
219
+ schemaVersion: 1,
220
+ available,
221
+ currentVersion,
222
+ serveVersion,
223
+ cliVersion,
224
+ latestVersion: targetVersion,
225
+ targetVersion,
226
+ supplySource: ownership.supplySource,
227
+ ownership: ownership.ownership,
228
+ processOwnership: ownership.processOwnership,
229
+ binaryOwnership: ownership.binaryOwnership,
230
+ canManage,
231
+ management: ownership.management,
232
+ reason,
233
+ guidance: ownership.guidance,
234
+ hasActiveTasks: input.hasActiveTasks === true,
235
+ contract,
236
+ operation: input.operation && typeof input.operation === 'object'
237
+ ? input.operation
238
+ : { status: 'idle' },
239
+ };
240
+ }
241
+
242
+ /**
243
+ * In-memory upgrade operation state (single-flight).
244
+ * @returns {{
245
+ * getState: () => object,
246
+ * begin: (target: string) => { ok: true } | { ok: false, status: number, body: object },
247
+ * succeed: (result: object) => void,
248
+ * fail: (error: object) => void,
249
+ * resetIdle: () => void,
250
+ * }}
251
+ */
252
+ export function createUpgradeOperationState() {
253
+ /** @type {{
254
+ * status: 'idle' | 'running' | 'succeeded' | 'failed',
255
+ * targetVersion: string | null,
256
+ * startedAt: number | null,
257
+ * finishedAt: number | null,
258
+ * phase: string | null,
259
+ * error: string | null,
260
+ * errorCode: string | null,
261
+ * serveVersion: string | null,
262
+ * binaryPath: string | null,
263
+ * }} */
264
+ let state = {
265
+ status: 'idle',
266
+ targetVersion: null,
267
+ startedAt: null,
268
+ finishedAt: null,
269
+ phase: null,
270
+ error: null,
271
+ errorCode: null,
272
+ serveVersion: null,
273
+ binaryPath: null,
274
+ };
275
+
276
+ return {
277
+ getState: () => ({ ...state }),
278
+ begin: (target) => {
279
+ if (state.status === 'running') {
280
+ return {
281
+ ok: false,
282
+ status: 409,
283
+ body: {
284
+ success: false,
285
+ error: 'An OpenCode upgrade is already in progress',
286
+ errorCode: 'UPGRADE_IN_PROGRESS',
287
+ operation: { ...state },
288
+ },
289
+ };
290
+ }
291
+ state = {
292
+ status: 'running',
293
+ targetVersion: target,
294
+ startedAt: Date.now(),
295
+ finishedAt: null,
296
+ phase: 'starting',
297
+ error: null,
298
+ errorCode: null,
299
+ serveVersion: null,
300
+ binaryPath: null,
301
+ };
302
+ return { ok: true };
303
+ },
304
+ setPhase: (phase, extra = {}) => {
305
+ if (state.status !== 'running') return;
306
+ state = { ...state, phase, ...extra };
307
+ },
308
+ succeed: (result = {}) => {
309
+ state = {
310
+ ...state,
311
+ status: 'succeeded',
312
+ finishedAt: Date.now(),
313
+ phase: 'verified',
314
+ error: null,
315
+ errorCode: null,
316
+ serveVersion: result.serveVersion ?? state.serveVersion,
317
+ binaryPath: result.binaryPath ?? state.binaryPath,
318
+ };
319
+ },
320
+ fail: (error = {}) => {
321
+ state = {
322
+ ...state,
323
+ status: 'failed',
324
+ finishedAt: Date.now(),
325
+ phase: error.phase || state.phase || 'failed',
326
+ error: typeof error.error === 'string' ? error.error : (error.message || 'Upgrade failed'),
327
+ errorCode: error.errorCode || error.code || 'UPGRADE_FAILED',
328
+ serveVersion: error.serveVersion ?? state.serveVersion,
329
+ binaryPath: error.binaryPath ?? state.binaryPath,
330
+ };
331
+ },
332
+ resetIdle: () => {
333
+ state = {
334
+ status: 'idle',
335
+ targetVersion: null,
336
+ startedAt: null,
337
+ finishedAt: null,
338
+ phase: null,
339
+ error: null,
340
+ errorCode: null,
341
+ serveVersion: null,
342
+ binaryPath: null,
343
+ };
344
+ },
345
+ };
346
+ }
347
+
348
+ /**
349
+ * Resolve the on-disk path OpenChamber will use for an owned-cache target.
350
+ * @param {string} targetVersion
351
+ * @param {object} [options]
352
+ */
353
+ export function resolveOwnedCacheBinaryPath(targetVersion, options = {}) {
354
+ const version = resolveOpenCode2UpgradeTarget(targetVersion);
355
+ return installedOpenCode2BinaryPath(version, options);
356
+ }
357
+
358
+ /**
359
+ * Pure post-upgrade verification: running serve must match target and pass contract.
360
+ * @param {{
361
+ * targetVersion: string,
362
+ * serveVersion?: string | null,
363
+ * cliVersion?: string | null,
364
+ * binaryPath?: string | null,
365
+ * expectedBinaryPath?: string | null,
366
+ * contract?: ReturnType<typeof evaluateRuntimeContract> | null,
367
+ * }} input
368
+ */
369
+ export function evaluateOwnedUpgradeResult(input) {
370
+ const target = resolveOpenCode2UpgradeTarget(input.targetVersion);
371
+ const serveVersion = normalizeRuntimeVersion(input.serveVersion);
372
+ const cliVersion = normalizeRuntimeVersion(input.cliVersion);
373
+ const contract = input.contract || evaluateRuntimeContract({
374
+ serveVersion,
375
+ cliVersion,
376
+ reachable: true,
377
+ authenticated: true,
378
+ healthOk: Boolean(serveVersion),
379
+ });
380
+
381
+ if (!serveVersion) {
382
+ return {
383
+ ok: false,
384
+ errorCode: 'UPGRADE_SERVE_VERSION_MISSING',
385
+ error: 'Upgrade started but the running OpenCode serve did not report a version',
386
+ serveVersion,
387
+ targetVersion: target,
388
+ contract,
389
+ };
390
+ }
391
+ if (serveVersion !== target) {
392
+ return {
393
+ ok: false,
394
+ errorCode: 'UPGRADE_SERVE_VERSION_MISMATCH',
395
+ error: `Running serve version ${serveVersion} does not match upgrade target ${target}`,
396
+ serveVersion,
397
+ targetVersion: target,
398
+ contract,
399
+ };
400
+ }
401
+ if (input.expectedBinaryPath && input.binaryPath
402
+ && path.resolve(input.binaryPath) !== path.resolve(input.expectedBinaryPath)) {
403
+ return {
404
+ ok: false,
405
+ errorCode: 'UPGRADE_BINARY_IDENTITY_MISMATCH',
406
+ error: 'Managed process is not using the owned-cache binary selected for upgrade',
407
+ serveVersion,
408
+ targetVersion: target,
409
+ contract,
410
+ };
411
+ }
412
+ if (
413
+ !contract.executionAllowed
414
+ || !contract.protocolCompatible
415
+ ) {
416
+ return {
417
+ ok: false,
418
+ errorCode: 'UPGRADE_CONTRACT_FAILED',
419
+ error: 'Running serve version matches target but failed runtime contract admission',
420
+ serveVersion,
421
+ targetVersion: target,
422
+ contract,
423
+ };
424
+ }
425
+ return {
426
+ ok: true,
427
+ serveVersion,
428
+ targetVersion: target,
429
+ contract,
430
+ };
431
+ }
@@ -0,0 +1,160 @@
1
+ import path from 'node:path';
2
+ import { describe, expect, it } from 'vitest';
3
+ import {
4
+ buildUpgradeStatusSnapshot,
5
+ classifyRuntimeOwnership,
6
+ createUpgradeOperationState,
7
+ evaluateOwnedUpgradeResult,
8
+ isOwnedOpenCodeCachePath,
9
+ } from './owned-runtime-upgrade.js';
10
+ import { evaluateRuntimeContract } from './runtime-contract.js';
11
+
12
+ describe('owned runtime upgrade (ticket 12)', () => {
13
+ const dataDir = path.join('/tmp', 'openchamber-owned-upgrade-test');
14
+ const ownedBinary = path.join(dataDir, 'opencode-cli', '2.0.12', 'opencode');
15
+
16
+ it('models binary vs process ownership separately', () => {
17
+ expect(isOwnedOpenCodeCachePath(ownedBinary, dataDir)).toBe(true);
18
+ expect(isOwnedOpenCodeCachePath('/usr/local/bin/opencode', dataDir)).toBe(false);
19
+
20
+ const externalUsingOwnedFile = classifyRuntimeOwnership({
21
+ isExternal: true,
22
+ binaryPath: ownedBinary,
23
+ binarySource: 'installed',
24
+ dataDir,
25
+ });
26
+ expect(externalUsingOwnedFile.ownership).toBe('external-serve');
27
+ expect(externalUsingOwnedFile.canUpgradeInApp).toBe(false);
28
+ expect(externalUsingOwnedFile.binaryOwnership).toBe('owned-cache');
29
+
30
+ const managedGlobal = classifyRuntimeOwnership({
31
+ isExternal: false,
32
+ binarySource: 'path',
33
+ binaryPath: '/usr/local/bin/opencode',
34
+ dataDir,
35
+ });
36
+ expect(managedGlobal.ownership).toBe('global-cli');
37
+ expect(managedGlobal.canUpgradeInApp).toBe(false);
38
+ expect(managedGlobal.guidance).toMatch(/global/i);
39
+
40
+ const owned = classifyRuntimeOwnership({
41
+ isExternal: false,
42
+ binarySource: 'installed',
43
+ binaryPath: ownedBinary,
44
+ dataDir,
45
+ });
46
+ expect(owned.ownership).toBe('owned-cache');
47
+ expect(owned.canUpgradeInApp).toBe(true);
48
+
49
+ const bundled = classifyRuntimeOwnership({
50
+ isExternal: false,
51
+ binarySource: 'bundled',
52
+ binaryPath: '/app/resources/opencode-cli/opencode',
53
+ });
54
+ expect(bundled.canUpgradeInApp).toBe(false);
55
+ expect(bundled.management).toBe('bundled');
56
+ });
57
+
58
+ it('upgrade-status exposes manage rights and manual guidance', () => {
59
+ const globalStatus = buildUpgradeStatusSnapshot({
60
+ ownership: classifyRuntimeOwnership({ isExternal: false, binarySource: 'path' }),
61
+ serveVersion: '2.0.12',
62
+ cliVersion: '2.0.12',
63
+ targetVersion: '2.0.14',
64
+ });
65
+ expect(globalStatus.canManage).toBe(false);
66
+ expect(globalStatus.available).toBeNull();
67
+ expect(globalStatus.management).toBe('manual-global');
68
+ expect(globalStatus.guidance).toBeTruthy();
69
+ expect(globalStatus.currentVersion).toBe('2.0.12');
70
+ expect(globalStatus.targetVersion).toBe('2.0.14');
71
+
72
+ const ownedStatus = buildUpgradeStatusSnapshot({
73
+ ownership: classifyRuntimeOwnership({
74
+ isExternal: false,
75
+ binarySource: 'installed',
76
+ binaryPath: ownedBinary,
77
+ dataDir,
78
+ }),
79
+ serveVersion: '2.0.12',
80
+ targetVersion: '2.0.14',
81
+ });
82
+ expect(ownedStatus.canManage).toBe(true);
83
+ expect(ownedStatus.available).toBe(true);
84
+ expect(ownedStatus.management).toBe('in-app');
85
+ });
86
+
87
+ it('verifies running serve identity before upgrade success', () => {
88
+ const contractOk = evaluateRuntimeContract({
89
+ serveVersion: '2.0.14',
90
+ reachable: true,
91
+ authenticated: true,
92
+ healthOk: true,
93
+ migrationAdmitTranscript: true,
94
+ });
95
+ expect(evaluateOwnedUpgradeResult({
96
+ targetVersion: '2.0.14',
97
+ serveVersion: '2.0.14',
98
+ binaryPath: ownedBinary,
99
+ expectedBinaryPath: ownedBinary,
100
+ contract: contractOk,
101
+ }).ok).toBe(true);
102
+
103
+ expect(evaluateOwnedUpgradeResult({
104
+ targetVersion: '2.0.14',
105
+ serveVersion: '2.0.12',
106
+ contract: evaluateRuntimeContract({
107
+ serveVersion: '2.0.12',
108
+ reachable: true,
109
+ authenticated: true,
110
+ healthOk: true,
111
+ migrationAdmitTranscript: true,
112
+ }),
113
+ })).toMatchObject({
114
+ ok: false,
115
+ errorCode: 'UPGRADE_SERVE_VERSION_MISMATCH',
116
+ });
117
+
118
+ expect(evaluateOwnedUpgradeResult({
119
+ targetVersion: '2.0.14',
120
+ serveVersion: '2.0.14',
121
+ binaryPath: '/usr/bin/opencode',
122
+ expectedBinaryPath: ownedBinary,
123
+ contract: contractOk,
124
+ }).errorCode).toBe('UPGRADE_BINARY_IDENTITY_MISMATCH');
125
+
126
+ // Target match alone is not enough — below-minimum versions must not succeed.
127
+ expect(evaluateOwnedUpgradeResult({
128
+ targetVersion: '2.0.5',
129
+ serveVersion: '2.0.5',
130
+ contract: evaluateRuntimeContract({
131
+ serveVersion: '2.0.5',
132
+ reachable: true,
133
+ authenticated: true,
134
+ healthOk: true,
135
+ migrationAdmitTranscript: true,
136
+ }),
137
+ })).toMatchObject({
138
+ ok: false,
139
+ errorCode: 'UPGRADE_CONTRACT_FAILED',
140
+ });
141
+ });
142
+
143
+ it('keeps concurrent upgrade state stable and reconciles failures', () => {
144
+ const op = createUpgradeOperationState();
145
+ expect(op.begin('2.0.14').ok).toBe(true);
146
+ const second = op.begin('2.0.14');
147
+ expect(second.ok).toBe(false);
148
+ expect(second.status).toBe(409);
149
+ expect(second.body.errorCode).toBe('UPGRADE_IN_PROGRESS');
150
+
151
+ op.setPhase('download');
152
+ op.fail({ error: 'download failed', errorCode: 'UPGRADE_DOWNLOAD_FAILED', phase: 'download' });
153
+ expect(op.getState()).toMatchObject({
154
+ status: 'failed',
155
+ errorCode: 'UPGRADE_DOWNLOAD_FAILED',
156
+ phase: 'download',
157
+ targetVersion: '2.0.14',
158
+ });
159
+ });
160
+ });
@@ -0,0 +1,15 @@
1
+ // V1 plugin API cannot run on opencode2. Settings must mark this explicitly
2
+ // instead of letting load fail silently.
3
+ export const PLUGIN_COMPATIBILITY_V1_INCOMPATIBLE = 'v1-incompatible';
4
+
5
+ export function pluginCompatibilityForV2() {
6
+ return {
7
+ compatibility: PLUGIN_COMPATIBILITY_V1_INCOMPATIBLE,
8
+ compatible: false,
9
+ };
10
+ }
11
+
12
+ export function withV1PluginIncompatibility(record) {
13
+ if (!record || typeof record !== 'object') return record;
14
+ return { ...record, ...pluginCompatibilityForV2() };
15
+ }
@@ -0,0 +1,21 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ PLUGIN_COMPATIBILITY_V1_INCOMPATIBLE,
4
+ pluginCompatibilityForV2,
5
+ withV1PluginIncompatibility,
6
+ } from './plugin-compatibility.js';
7
+
8
+ describe('V1 plugin compatibility (ticket 12)', () => {
9
+ it('marks V1 plugins incompatible instead of a silent load failure', () => {
10
+ expect(pluginCompatibilityForV2()).toEqual({
11
+ compatibility: PLUGIN_COMPATIBILITY_V1_INCOMPATIBLE,
12
+ compatible: false,
13
+ });
14
+ expect(withV1PluginIncompatibility({ id: 'config:user:oh-my-opencode', spec: 'oh-my-opencode' })).toEqual({
15
+ id: 'config:user:oh-my-opencode',
16
+ spec: 'oh-my-opencode',
17
+ compatibility: 'v1-incompatible',
18
+ compatible: false,
19
+ });
20
+ });
21
+ });
@@ -12,8 +12,6 @@ const BAD_REQUEST_CODES = new Set(['INVALID_FILENAME', 'INVALID_SCOPE', 'INVALID
12
12
  export const registerPluginRoutes = (app, dependencies) => {
13
13
  const {
14
14
  resolveOptionalProjectDirectory,
15
- refreshOpenCodeAfterConfigChange,
16
- clientReloadDelayMs,
17
15
  listPluginEntries,
18
16
  getPluginEntry,
19
17
  createPluginEntry,
@@ -45,9 +43,9 @@ export const registerPluginRoutes = (app, dependencies) => {
45
43
 
46
44
  const successPayload = (message) => ({
47
45
  success: true,
48
- requiresReload: true,
46
+ requiresReload: false,
47
+ application: 'watch',
49
48
  message,
50
- reloadDelayMs: clientReloadDelayMs,
51
49
  reloadFailed: false,
52
50
  warning: undefined,
53
51
  });
@@ -57,20 +55,7 @@ export const registerPluginRoutes = (app, dependencies) => {
57
55
 
58
56
  const pastTense = operation.replace(/ion$/, 'ed').replace(/update$/, 'updated');
59
57
 
60
- try {
61
- await refreshOpenCodeAfterConfigChange(`plugin ${operation}`);
62
- return res.json(successPayload(`Plugin ${pastTense}. Reloading interface…`));
63
- } catch (error) {
64
- console.error(`[API:plugin ${operation}] Reload failed after config write:`, error);
65
- return res.json({
66
- success: true,
67
- requiresReload: false,
68
- message: `Plugin ${pastTense}, but OpenCode reload failed.`,
69
- reloadDelayMs: clientReloadDelayMs,
70
- reloadFailed: true,
71
- warning: error.message || 'OpenCode reload failed after plugin config changed',
72
- });
73
- }
58
+ return res.json(successPayload(`Plugin ${pastTense}.`));
74
59
  };
75
60
 
76
61
  const validateEntryId = (id) => {