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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (287) hide show
  1. package/README.md +15 -11
  2. package/bin/cli-opencode.test.js +4 -4
  3. package/bin/cli.js +1 -1
  4. package/bin/lib/cli-startup.js +1 -1
  5. package/bin/lib/cli-startup.test.js +18 -4
  6. package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
  7. package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
  8. package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
  9. package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
  10. package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
  11. package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
  12. package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
  13. package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
  14. package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
  15. package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
  16. package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
  17. package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
  18. package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
  19. package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
  20. package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
  21. package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
  22. package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
  23. package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
  24. package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
  25. package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
  26. package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
  27. package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
  28. package/dist/assets/context-menu-B2LXJgXL.js +1 -0
  29. package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
  30. package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
  31. package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
  32. package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
  33. package/dist/assets/es-CN3E_zXj.js +15 -0
  34. package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
  35. package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
  36. package/dist/assets/fr-9zZvQZD2.js +13 -0
  37. package/dist/assets/gitApi-yuWg9BN4.js +10 -0
  38. package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
  39. package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
  40. package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
  41. package/dist/assets/ja-Dnjk_82N.js +15 -0
  42. package/dist/assets/ko-BxCEE99C.js +15 -0
  43. package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
  44. package/dist/assets/main-DiL6w6KF.js +169 -0
  45. package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
  46. package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
  47. package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
  48. package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
  49. package/dist/assets/miniChat-CwtUaElU.js +2 -0
  50. package/dist/assets/mobile-DB-6--ws.js +2 -0
  51. package/dist/assets/multirun-TAsxFI8e.js +4 -0
  52. package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
  53. package/dist/assets/pl-CNNsWnPM.js +15 -0
  54. package/dist/assets/platform-C13fMVEs.js +6 -0
  55. package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
  56. package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
  57. package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
  58. package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
  59. package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
  60. package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
  61. package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
  62. package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
  63. package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
  64. package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
  65. package/dist/assets/src-ubCWXZhR.css +2 -0
  66. package/dist/assets/store-DLCiZaA6.js +15 -0
  67. package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
  68. package/dist/assets/textarea-BBOlRI9O.js +1 -0
  69. package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
  70. package/dist/assets/uk-rwQrxeYM.js +15 -0
  71. package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
  72. package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
  73. package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
  74. package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
  75. package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
  76. package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
  77. package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
  78. package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
  79. package/dist/assets/useSessionAutoCleanup-DtrfbxAp.js +2 -0
  80. package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
  81. package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
  82. package/dist/assets/{useWorkerHighlightedLines-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.js} +2 -2
  83. package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
  84. package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
  85. package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
  86. package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
  87. package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
  88. package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
  89. package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
  90. package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
  91. package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
  92. package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
  93. package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
  94. package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
  95. package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
  96. package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
  97. package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
  98. package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
  99. package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
  100. package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
  101. package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
  102. package/dist/index.html +13 -13
  103. package/dist/mini-chat.html +13 -13
  104. package/dist/mobile.html +13 -13
  105. package/package.json +1 -1
  106. package/server/index.js +327 -62
  107. package/server/lib/assistants/DOCUMENTATION.md +14 -9
  108. package/server/lib/assistants/contracts.js +11 -1
  109. package/server/lib/assistants/projection-history.test.js +265 -18
  110. package/server/lib/assistants/routes.js +6 -0
  111. package/server/lib/assistants/service.js +417 -231
  112. package/server/lib/assistants/service.test.js +392 -148
  113. package/server/lib/conversations/DOCUMENTATION.md +1 -1
  114. package/server/lib/conversations/service.js +1 -0
  115. package/server/lib/conversations/service.test.js +16 -0
  116. package/server/lib/conversations/validation.js +11 -1
  117. package/server/lib/event-stream/DOCUMENTATION.md +3 -1
  118. package/server/lib/event-stream/directory-ws-bridge.js +13 -2
  119. package/server/lib/event-stream/global-hub.js +49 -4
  120. package/server/lib/event-stream/reasoning-projection.js +16 -1
  121. package/server/lib/event-stream/reasoning-projection.test.js +15 -0
  122. package/server/lib/event-stream/runtime.js +57 -20
  123. package/server/lib/event-stream/runtime.test.js +112 -0
  124. package/server/lib/llm/DOCUMENTATION.md +33 -9
  125. package/server/lib/llm/catalog.js +1 -1
  126. package/server/lib/llm/completions.js +5 -1
  127. package/server/lib/llm/completions.test.js +3 -3
  128. package/server/lib/llm/generate.js +237 -145
  129. package/server/lib/llm/generate.test.js +201 -46
  130. package/server/lib/llm/routes.js +2 -0
  131. package/server/lib/message-queue/runtime.js +15 -2
  132. package/server/lib/notifications/runtime.js +23 -5
  133. package/server/lib/notifications/runtime.test.js +11 -0
  134. package/server/lib/opencode/DOCUMENTATION.md +85 -17
  135. package/server/lib/opencode/agent-document.d.ts +28 -0
  136. package/server/lib/opencode/agent-document.js +280 -0
  137. package/server/lib/opencode/agent-document.test.js +137 -0
  138. package/server/lib/opencode/agents.js +96 -0
  139. package/server/lib/opencode/auth-state-runtime.js +5 -0
  140. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  141. package/server/lib/opencode/config-entity-routes.js +42 -99
  142. package/server/lib/opencode/config-entity-routes.test.js +107 -0
  143. package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
  144. package/server/lib/opencode/ensure-cli.js +1 -2
  145. package/server/lib/opencode/env-runtime.js +40 -26
  146. package/server/lib/opencode/env-runtime.test.js +10 -17
  147. package/server/lib/opencode/feature-routes-runtime.js +43 -4
  148. package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
  149. package/server/lib/opencode/http-server-connections.js +98 -0
  150. package/server/lib/opencode/http-server-connections.test.js +321 -0
  151. package/server/lib/opencode/lifecycle.js +349 -39
  152. package/server/lib/opencode/lifecycle.test.js +322 -0
  153. package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
  154. package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
  155. package/server/lib/opencode/openchamber-routes.js +7 -1
  156. package/server/lib/opencode/opencode2-pin.js +4 -4
  157. package/server/lib/opencode/opencode2-pin.test.js +1 -0
  158. package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
  159. package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
  160. package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
  161. package/server/lib/opencode/plugin-routes.js +3 -18
  162. package/server/lib/opencode/plugin-routes.test.js +9 -9
  163. package/server/lib/opencode/provider-catalog.js +58 -12
  164. package/server/lib/opencode/provider-catalog.test.js +48 -0
  165. package/server/lib/opencode/proxy.js +252 -46
  166. package/server/lib/opencode/proxy.test.js +235 -2
  167. package/server/lib/opencode/routes.js +296 -62
  168. package/server/lib/opencode/routes.test.js +278 -12
  169. package/server/lib/opencode/runtime-contract.js +401 -0
  170. package/server/lib/opencode/runtime-contract.test.js +214 -0
  171. package/server/lib/opencode/server-opencode-fetch.js +242 -0
  172. package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
  173. package/server/lib/opencode/server-utils-runtime.js +33 -0
  174. package/server/lib/opencode/shared-service.integration.test.js +66 -0
  175. package/server/lib/opencode/shared-service.js +147 -0
  176. package/server/lib/opencode/shared-service.test.js +93 -0
  177. package/server/lib/opencode/shared.js +3 -3
  178. package/server/lib/opencode/shutdown-runtime.js +15 -6
  179. package/server/lib/opencode/shutdown-runtime.test.js +49 -1
  180. package/server/lib/opencode/skill-routes.js +20 -33
  181. package/server/lib/opencode/skill-routes.test.js +35 -2
  182. package/server/lib/opencode/skills.test.js +15 -0
  183. package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
  184. package/server/lib/opencode/static-routes-runtime.js +3 -1
  185. package/server/lib/opencode/static-routes-runtime.test.js +33 -0
  186. package/server/lib/opencode/v2-client.js +14 -3
  187. package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
  188. package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
  189. package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
  190. package/server/lib/permission-auto-accept/runtime.js +14 -7
  191. package/server/lib/permission-auto-accept/runtime.test.js +27 -12
  192. package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
  193. package/server/lib/scheduled-tasks/DOCUMENTATION.md +8 -0
  194. package/server/lib/scheduled-tasks/runtime.js +52 -4
  195. package/server/lib/scheduled-tasks/runtime.test.js +80 -1
  196. package/server/lib/session-goal/DOCUMENTATION.md +113 -13
  197. package/server/lib/session-goal/routes.js +9 -0
  198. package/server/lib/session-goal/runtime.js +1579 -117
  199. package/server/lib/session-goal/runtime.test.js +1453 -84
  200. package/server/lib/session-index/DOCUMENTATION.md +18 -15
  201. package/server/lib/session-index/event-ingest.js +89 -16
  202. package/server/lib/session-index/event-ingest.test.js +73 -0
  203. package/server/lib/session-index/host-authority-snapshot.js +92 -0
  204. package/server/lib/session-index/routes.host-archive.test.js +201 -0
  205. package/server/lib/session-index/routes.js +63 -10
  206. package/server/lib/session-index/routes.test.js +8 -8
  207. package/server/lib/session-index/service.js +7 -1
  208. package/server/lib/session-index/sync-runtime.js +161 -34
  209. package/server/lib/session-index/sync-runtime.test.js +155 -1
  210. package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
  211. package/server/lib/session-metadata/routes.js +108 -9
  212. package/server/lib/session-metadata/routes.test.js +47 -2
  213. package/server/lib/session-metadata/session-archive.d.ts +73 -0
  214. package/server/lib/session-metadata/session-archive.js +654 -0
  215. package/server/lib/session-metadata/session-archive.test.js +359 -0
  216. package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
  217. package/server/lib/session-metadata/session-metadata-store.js +212 -72
  218. package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
  219. package/server/lib/session-metadata/session-projection.d.ts +41 -0
  220. package/server/lib/session-metadata/session-projection.js +255 -0
  221. package/server/lib/session-metadata/session-projection.test.js +64 -0
  222. package/server/lib/session-metadata/system-session.js +55 -0
  223. package/server/lib/session-metadata/system-session.test.js +55 -0
  224. package/server/lib/session-title/DOCUMENTATION.md +40 -7
  225. package/server/lib/session-title/native-runtime.test.js +167 -0
  226. package/server/lib/session-title/runtime.js +67 -42
  227. package/server/lib/session-title/session-access.js +46 -0
  228. package/server/lib/session-title/session-access.test.js +91 -0
  229. package/server/lib/session-turn-pages/DOCUMENTATION.md +9 -8
  230. package/server/lib/session-turn-pages/routes.js +17 -102
  231. package/server/lib/session-turn-pages/routes.test.js +281 -0
  232. package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
  233. package/server/lib/session-turn-pages/session-message-projection.js +281 -0
  234. package/server/lib/small-model/DOCUMENTATION.md +101 -147
  235. package/server/lib/small-model/catalog.js +31 -51
  236. package/server/lib/small-model/catalog.test.js +65 -10
  237. package/server/lib/small-model/custom-api.js +68 -0
  238. package/server/lib/small-model/custom-api.test.js +64 -0
  239. package/server/lib/small-model/index.js +124 -240
  240. package/server/lib/small-model/resolve.js +35 -120
  241. package/server/lib/small-model/resolve.test.js +20 -110
  242. package/server/lib/small-model/routes.js +1 -3
  243. package/server/lib/small-model/summary-settings.test.js +157 -253
  244. package/server/opencode-proxy.test.js +2 -0
  245. package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
  246. package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
  247. package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
  248. package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
  249. package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
  250. package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
  251. package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
  252. package/dist/assets/context-menu-jGtH6fR9.js +0 -1
  253. package/dist/assets/es-c52LTHHe.js +0 -15
  254. package/dist/assets/fr-PisFbUE8.js +0 -13
  255. package/dist/assets/gitApi-ButwzQ0N.js +0 -9
  256. package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
  257. package/dist/assets/ja-C8Ikun3Z.js +0 -15
  258. package/dist/assets/ko-ClFys5o2.js +0 -15
  259. package/dist/assets/main-Dn9rpsgl.js +0 -169
  260. package/dist/assets/markdown-worker-CptoICmq.js +0 -2
  261. package/dist/assets/miniChat-BpjxVqgg.js +0 -2
  262. package/dist/assets/mobile-nMJCaNq1.js +0 -2
  263. package/dist/assets/multirun-BvSG94mW.js +0 -4
  264. package/dist/assets/pl-CnaKWN5F.js +0 -15
  265. package/dist/assets/platform-CWTIp02q.js +0 -4
  266. package/dist/assets/projectMeta-DOe49dM_.js +0 -3
  267. package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
  268. package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
  269. package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
  270. package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
  271. package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
  272. package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
  273. package/dist/assets/src-BzX5UI-c.css +0 -2
  274. package/dist/assets/store-DJczmczv.js +0 -15
  275. package/dist/assets/textarea-3XR8sUfy.js +0 -1
  276. package/dist/assets/uk-CaX0iqkB.js +0 -15
  277. package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
  278. package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
  279. package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
  280. package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
  281. package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
  282. package/dist/assets/zh-CN-20MYEjha.js +0 -15
  283. package/dist/assets/zh-TW-DiN04qTw.js +0 -15
  284. package/server/lib/small-model/call.js +0 -427
  285. package/server/lib/small-model/call.test.js +0 -382
  286. package/server/lib/small-model/opencode-session.js +0 -183
  287. package/server/lib/small-model/opencode-session.test.js +0 -147
@@ -0,0 +1,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
+ });
@@ -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) => {
@@ -263,11 +263,11 @@ describe('opencode plugin routes', () => {
263
263
  expect(response.body.results[0]).toMatchObject({ kind: 'npm-ok', spec: '@scope/foo@1.0.0', name: '@scope/foo' });
264
264
  });
265
265
 
266
- test('POST /entry creates entry and requires reload', async () => {
266
+ test('POST /entry persists for V2 hot reload without restarting', async () => {
267
267
  const response = await createEntry('a');
268
268
 
269
- expect(response.body).toMatchObject({ success: true, requiresReload: true, reloadDelayMs: 25 });
270
- expect(refreshOpenCodeAfterConfigChange).toHaveBeenCalledWith('plugin entry creation');
269
+ expect(response.body).toMatchObject({ success: true, requiresReload: false, application: 'watch' });
270
+ expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
271
271
  });
272
272
 
273
273
  test('GET after POST returns created entry', async () => {
@@ -302,7 +302,7 @@ describe('opencode plugin routes', () => {
302
302
  expect(response.body.success).toBe(true);
303
303
  const after = await request(app).get('/api/config/plugins').expect(200);
304
304
  expect(after.body.entries[0]).toEqual(expect.objectContaining({ spec: 'b', scope: 'user' }));
305
- expect(refreshOpenCodeAfterConfigChange).toHaveBeenCalledWith('plugin entry update');
305
+ expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
306
306
  });
307
307
 
308
308
  test('DELETE /entry/:id removes entry and prunes plugin key', async () => {
@@ -315,15 +315,15 @@ describe('opencode plugin routes', () => {
315
315
  const after = await request(app).get('/api/config/plugins').expect(200);
316
316
  expect(after.body.entries).toEqual([]);
317
317
  expect(readJson(userConfigPath).plugin).toBeUndefined();
318
- expect(refreshOpenCodeAfterConfigChange).toHaveBeenCalledWith('plugin entry deletion');
318
+ expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
319
319
  });
320
320
 
321
321
  test('POST /file writes plugin dir file', async () => {
322
322
  const response = await createFile('test.js', '//x');
323
323
 
324
- expect(response.body).toMatchObject({ success: true, requiresReload: true });
324
+ expect(response.body).toMatchObject({ success: true, requiresReload: false, application: 'watch' });
325
325
  expect(fs.readFileSync(path.join(rootDir, 'plugins', 'test.js'), 'utf8')).toBe('//x');
326
- expect(refreshOpenCodeAfterConfigChange).toHaveBeenCalledWith('plugin file creation');
326
+ expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
327
327
  });
328
328
 
329
329
  test('POST duplicate file returns 409', async () => {
@@ -348,7 +348,7 @@ describe('opencode plugin routes', () => {
348
348
  .expect(200);
349
349
 
350
350
  expect(fs.readFileSync(path.join(rootDir, 'plugins', 'test.js'), 'utf8')).toBe('//y');
351
- expect(refreshOpenCodeAfterConfigChange).toHaveBeenCalledWith('plugin file update');
351
+ expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
352
352
  });
353
353
 
354
354
  test('DELETE /file/:id unlinks file', async () => {
@@ -359,7 +359,7 @@ describe('opencode plugin routes', () => {
359
359
  await request(app).delete(`/api/config/plugins/file/${encodeURIComponent(id)}`).expect(200);
360
360
 
361
361
  expect(fs.existsSync(path.join(rootDir, 'plugins', 'test.js'))).toBe(false);
362
- expect(refreshOpenCodeAfterConfigChange).toHaveBeenCalledWith('plugin file deletion');
362
+ expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
363
363
  });
364
364
 
365
365
  test('PATCH unknown entry id returns 404', async () => {