@vantran-se/openchamber-web 1.24.4 → 2.0.2

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 (367) hide show
  1. package/README.md +2 -8
  2. package/bin/lib/commands-connect-url.js +3 -8
  3. package/bin/lib/commands-tunnel.js +3 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-B4a0JkUz.js → CodeMirrorCommentWidgets-B82Oye3V.js} +1 -1
  5. package/dist/assets/DiffView-DJLmWn6Z.js +4 -0
  6. package/dist/assets/FilesView-DjksmZRo.js +17 -0
  7. package/dist/assets/{GitView-Byu2DY7s.js → GitView-DiUsUIgr.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-CYwWYLr3.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-CmctyRI4.js +1 -0
  10. package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-DOHHcbgJ.js} +1 -1
  11. package/dist/assets/LinearIssuesView-DaqnylcV.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-HJNvWt7L.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-nDZRGg2I.js +12 -0
  14. package/dist/assets/OnboardingScreen-C2B5Bsof.js +1 -0
  15. package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-C4FLBjpt.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-C_1S9c9n.js} +1 -1
  17. package/dist/assets/PlanView-_eZvb-G5.js +5 -0
  18. package/dist/assets/PluginPane-sM8fcZIV.js +3 -0
  19. package/dist/assets/SettingsView-BRfr46hb.js +29 -0
  20. package/dist/assets/SettingsWindow-CcUDwQHg.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-CrN1ZGwl.js +1 -0
  22. package/dist/assets/ToolOutputDialog-Cij4jI09.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-BXzwQ-JY.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-DSWTLJNH.js} +4 -4
  25. package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-CpUUgY8H.js} +2 -2
  26. package/dist/assets/appThemeRegistry-B5tHTkR8.js +1 -0
  27. package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-D7D8qV8p.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-cq9uQ30i.js} +1 -1
  29. package/dist/assets/de-CUO1-KqF.js +10 -0
  30. package/dist/assets/es-BlbC6sGJ.js +16 -0
  31. package/dist/assets/fr-D2nwe_0W.js +14 -0
  32. package/dist/assets/index-BQOoYIOu.js +1 -0
  33. package/dist/assets/index-CLcM8HKx.css +1 -0
  34. package/dist/assets/index-CvLgnuNI.js +41 -0
  35. package/dist/assets/{isDiffPlainText-CO4e5J_S.js → isDiffPlainText-D0obJ2p8.js} +1 -1
  36. package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-DB3_rjyW.js} +1 -1
  37. package/dist/assets/ja-CA7vnDsZ.js +16 -0
  38. package/dist/assets/ko-Chy6a9l-.js +16 -0
  39. package/dist/assets/main-B9JFK3vF.js +896 -0
  40. package/dist/assets/main-Bhiqnrhd.js +2 -0
  41. package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-tfqOFaBd.js} +4 -4
  42. package/dist/assets/miniChat-B0UOL5zA.js +2 -0
  43. package/dist/assets/mobile-BJppFIEo.js +2 -0
  44. package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-Db6KuwCP.js} +3 -3
  45. package/dist/assets/pl-CTR5A_1P.js +16 -0
  46. package/dist/assets/pt-BR-BE1O1KEx.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-Yz5sfoFh.js +2 -0
  48. package/dist/assets/renderMobileApp-QQ-yB8aY.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-DKDdzRzm.js +27 -0
  50. package/dist/assets/scheduledTasksApi-C5sCuNqf.js +1 -0
  51. package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-xURoo5t_.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-BZLGuYTU.js} +1 -1
  53. package/dist/assets/tr-Bh5vK6cW.js +7 -0
  54. package/dist/assets/uk-CLDkZFy_.js +16 -0
  55. package/dist/assets/useAppFontEffects-BxrtCnfR.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CeXuR-SR.js} +2 -2
  57. package/dist/assets/useRootScrollLock-DgWxezc1.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-DOuaQUZ0.js} +2 -2
  59. package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
  60. package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
  61. package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
  62. package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
  63. package/dist/assets/{web-components-DxX8bZ1o.js → web-components-63H1h21j.js} +2 -2
  64. package/dist/assets/zh-CN-DODgcmYj.js +16 -0
  65. package/dist/assets/zh-TW-ByNfIwLA.js +16 -0
  66. package/dist/index.html +13 -7
  67. package/dist/mini-chat.html +5 -5
  68. package/dist/mobile.html +5 -5
  69. package/package.json +6 -5
  70. package/server/index.d.ts +1 -0
  71. package/server/index.js +246 -91
  72. package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
  73. package/server/lib/agent-tool/runtime.js +135 -54
  74. package/server/lib/agent-tool/runtime.test.js +155 -87
  75. package/server/lib/browser-control/DOCUMENTATION.md +37 -2
  76. package/server/lib/browser-control/provider.js +217 -0
  77. package/server/lib/browser-control/provider.test.js +251 -0
  78. package/server/lib/context-obligatory/runtime.js +61 -50
  79. package/server/lib/context-obligatory/runtime.test.js +62 -162
  80. package/server/lib/event-stream/DOCUMENTATION.md +12 -1
  81. package/server/lib/event-stream/delta-coalescer.js +0 -0
  82. package/server/lib/event-stream/delta-coalescer.test.js +68 -57
  83. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  84. package/server/lib/event-stream/global-hub.js +10 -1
  85. package/server/lib/event-stream/global-hub.test.js +27 -26
  86. package/server/lib/event-stream/index.js +6 -0
  87. package/server/lib/event-stream/protocol.js +26 -19
  88. package/server/lib/event-stream/protocol.test.js +18 -7
  89. package/server/lib/event-stream/rebind.test.js +5 -5
  90. package/server/lib/event-stream/runtime.test.js +17 -15
  91. package/server/lib/event-stream/translate-v2.js +313 -0
  92. package/server/lib/event-stream/translate-v2.test.js +209 -0
  93. package/server/lib/event-stream/upstream-reader.test.js +16 -16
  94. package/server/lib/fs/DOCUMENTATION.md +4 -2
  95. package/server/lib/fs/byte-range.js +35 -0
  96. package/server/lib/fs/byte-range.test.js +31 -0
  97. package/server/lib/fs/routes.js +47 -42
  98. package/server/lib/fs/routes.test.js +123 -18
  99. package/server/lib/git/service.js +12 -0
  100. package/server/lib/github/DOCUMENTATION.md +1 -1
  101. package/server/lib/github/pr-status.js +22 -2
  102. package/server/lib/github/pr-status.test.js +27 -14
  103. package/server/lib/guests/DOCUMENTATION.md +13 -0
  104. package/server/lib/guests/catalog.js +12 -0
  105. package/server/lib/guests/catalog.test.js +33 -0
  106. package/server/lib/guests/routes.js +10 -1
  107. package/server/lib/guests/service.js +192 -14
  108. package/server/lib/guests/service.test.js +201 -1
  109. package/server/lib/guests/surface.js +630 -0
  110. package/server/lib/guests/surface.test.js +486 -0
  111. package/server/lib/markdown-image-grants/routes.js +18 -5
  112. package/server/lib/markdown-image-grants/routes.test.js +9 -6
  113. package/server/lib/message-queue/DOCUMENTATION.md +33 -22
  114. package/server/lib/message-queue/runtime.js +155 -100
  115. package/server/lib/message-queue/runtime.test.js +168 -82
  116. package/server/lib/notifications/DOCUMENTATION.md +28 -0
  117. package/server/lib/notifications/control-stream.http.test.js +68 -0
  118. package/server/lib/notifications/emitter-runtime.js +15 -0
  119. package/server/lib/notifications/emitter-runtime.test.js +32 -0
  120. package/server/lib/notifications/push-runtime.js +5 -1
  121. package/server/lib/notifications/push-runtime.test.js +2 -2
  122. package/server/lib/notifications/routes.js +1 -1
  123. package/server/lib/notifications/runtime.js +115 -26
  124. package/server/lib/notifications/template-runtime.js +13 -15
  125. package/server/lib/notifications/template-runtime.test.js +16 -9
  126. package/server/lib/notifications/trigger-runtime.test.js +187 -0
  127. package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
  128. package/server/lib/openchamber-control/actions.js +3 -2
  129. package/server/lib/openchamber-control/error.js +1 -1
  130. package/server/lib/openchamber-control/file-open.js +59 -0
  131. package/server/lib/openchamber-control/file-open.test.js +57 -0
  132. package/server/lib/openchamber-control/routes.test.js +12 -0
  133. package/server/lib/openchamber-control/service.js +102 -49
  134. package/server/lib/openchamber-control/service.test.js +84 -44
  135. package/server/lib/openchamber-sessions/archive-store.js +224 -0
  136. package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
  137. package/server/lib/openchamber-sessions/opencode-client.js +22 -0
  138. package/server/lib/openchamber-sessions/routes.js +398 -328
  139. package/server/lib/openchamber-sessions/routes.test.js +777 -740
  140. package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
  141. package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
  142. package/server/lib/opencode/DOCUMENTATION.md +449 -29
  143. package/server/lib/opencode/agents.js +233 -327
  144. package/server/lib/opencode/auth.js +41 -44
  145. package/server/lib/opencode/auth.test.js +92 -0
  146. package/server/lib/opencode/cli-upgrade.d.ts +4 -0
  147. package/server/lib/opencode/cli-upgrade.js +18 -0
  148. package/server/lib/opencode/cli-upgrade.test.js +36 -0
  149. package/server/lib/opencode/commands.js +164 -145
  150. package/server/lib/opencode/compatibility.d.ts +16 -0
  151. package/server/lib/opencode/compatibility.js +88 -0
  152. package/server/lib/opencode/compatibility.test.js +62 -0
  153. package/server/lib/opencode/config-entity-routes.js +58 -29
  154. package/server/lib/opencode/config-entity-v2.test.js +290 -0
  155. package/server/lib/opencode/config-mutation-response.js +17 -7
  156. package/server/lib/opencode/config-mutation-response.test.js +4 -7
  157. package/server/lib/opencode/config-paths.test.js +2 -2
  158. package/server/lib/opencode/config-v2.d.ts +183 -0
  159. package/server/lib/opencode/config-v2.js +801 -0
  160. package/server/lib/opencode/config-v2.test.js +466 -0
  161. package/server/lib/opencode/credential-db.js +144 -0
  162. package/server/lib/opencode/credential-db.test.js +104 -0
  163. package/server/lib/opencode/env-runtime.js +48 -9
  164. package/server/lib/opencode/env-runtime.test.js +45 -0
  165. package/server/lib/opencode/feature-routes-runtime.js +15 -5
  166. package/server/lib/opencode/lifecycle-process.test.js +7 -4
  167. package/server/lib/opencode/lifecycle.js +85 -30
  168. package/server/lib/opencode/lifecycle.test.js +80 -8
  169. package/server/lib/opencode/login-shell-env.js +27 -0
  170. package/server/lib/opencode/managed-config-file.js +119 -0
  171. package/server/lib/opencode/managed-config-file.test.js +152 -0
  172. package/server/lib/opencode/managed-plugin-config.js +33 -13
  173. package/server/lib/opencode/mcp.js +43 -149
  174. package/server/lib/opencode/network-runtime.js +6 -7
  175. package/server/lib/opencode/openchamber-routes.test.js +2 -5
  176. package/server/lib/opencode/plugin-routes.js +3 -3
  177. package/server/lib/opencode/plugin-routes.test.js +6 -24
  178. package/server/lib/opencode/plugins.js +91 -40
  179. package/server/lib/opencode/plugins.test.js +28 -15
  180. package/server/lib/opencode/providers.js +63 -83
  181. package/server/lib/opencode/providers.test.js +128 -59
  182. package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
  183. package/server/lib/opencode/proxy.js +179 -129
  184. package/server/lib/opencode/pwa-manifest-routes.js +4 -2
  185. package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
  186. package/server/lib/opencode/response-envelope.js +14 -0
  187. package/server/lib/opencode/response-envelope.test.js +23 -0
  188. package/server/lib/opencode/routes-upgrade.test.js +73 -116
  189. package/server/lib/opencode/routes.js +121 -428
  190. package/server/lib/opencode/server-utils-runtime.js +10 -4
  191. package/server/lib/opencode/session-activity.js +78 -0
  192. package/server/lib/opencode/session-runtime.js +21 -10
  193. package/server/lib/opencode/session-runtime.test.js +9 -9
  194. package/server/lib/opencode/settings-helpers.js +9 -9
  195. package/server/lib/opencode/settings-helpers.test.js +7 -12
  196. package/server/lib/opencode/settings-registry.json +6 -8
  197. package/server/lib/opencode/settings-runtime.js +18 -1
  198. package/server/lib/opencode/settings-runtime.test.js +31 -2
  199. package/server/lib/opencode/shared.js +54 -22
  200. package/server/lib/opencode/shared.test.js +8 -4
  201. package/server/lib/opencode/shutdown-runtime.js +54 -7
  202. package/server/lib/opencode/shutdown-runtime.test.js +224 -30
  203. package/server/lib/opencode/skill-routes.js +23 -24
  204. package/server/lib/opencode/skills.js +7 -5
  205. package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
  206. package/server/lib/opencode/upgrade-capability.test.js +1 -1
  207. package/server/lib/opencode/v1-migration-topup.js +288 -0
  208. package/server/lib/opencode/v1-migration-topup.test.js +266 -0
  209. package/server/lib/opencode/v2-install.d.ts +8 -0
  210. package/server/lib/opencode/v2-install.js +158 -0
  211. package/server/lib/opencode/v2-install.test.js +124 -0
  212. package/server/lib/opencode/watcher.js +10 -3
  213. package/server/lib/opencode/watcher.test.js +17 -21
  214. package/server/lib/opencode/websearch-config.js +66 -0
  215. package/server/lib/package-manager.js +55 -68
  216. package/server/lib/package-manager.test.js +241 -75
  217. package/server/lib/permission-auto-accept/runtime.js +38 -22
  218. package/server/lib/permission-auto-accept/runtime.test.js +39 -17
  219. package/server/lib/quota/DOCUMENTATION.md +4 -0
  220. package/server/lib/quota/providers/xai.js +4 -4
  221. package/server/lib/relay/tunnel-host.js +4 -1
  222. package/server/lib/relay/tunnel-host.test.js +7 -0
  223. package/server/lib/routing/DOCUMENTATION.md +52 -21
  224. package/server/lib/routing/defaults.js +23 -6
  225. package/server/lib/routing/jev.js +17 -7
  226. package/server/lib/routing/routes.http.test.js +68 -33
  227. package/server/lib/routing/routes.js +65 -22
  228. package/server/lib/routing/runtime.js +145 -48
  229. package/server/lib/routing/runtime.test.js +79 -55
  230. package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
  231. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
  232. package/server/lib/scheduled-tasks/runtime.js +81 -74
  233. package/server/lib/scheduled-tasks/runtime.test.js +53 -1
  234. package/server/lib/session-assist/DOCUMENTATION.md +8 -3
  235. package/server/lib/session-assist/context.js +122 -46
  236. package/server/lib/session-assist/context.test.js +117 -49
  237. package/server/lib/session-assist/runtime.js +52 -22
  238. package/server/lib/session-assist/runtime.test.js +145 -173
  239. package/server/lib/session-goal/DOCUMENTATION.md +23 -15
  240. package/server/lib/session-goal/create.js +9 -12
  241. package/server/lib/session-goal/create.test.js +27 -28
  242. package/server/lib/session-goal/runtime.js +164 -77
  243. package/server/lib/session-goal/runtime.test.js +271 -519
  244. package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
  245. package/server/lib/session-knowledge/runtime.js +59 -41
  246. package/server/lib/session-knowledge/runtime.test.js +90 -20
  247. package/server/lib/small-model/DOCUMENTATION.md +154 -208
  248. package/server/lib/small-model/client.js +113 -0
  249. package/server/lib/small-model/index.js +296 -158
  250. package/server/lib/small-model/index.test.js +423 -256
  251. package/server/lib/small-model/routes.js +1 -1
  252. package/server/lib/spaces/DOCUMENTATION.md +807 -0
  253. package/server/lib/spaces/code-in-bait.js +185 -0
  254. package/server/lib/spaces/code-in.js +641 -0
  255. package/server/lib/spaces/code-in.test.js +1048 -0
  256. package/server/lib/spaces/errors.js +8 -0
  257. package/server/lib/spaces/exec-http.js +94 -0
  258. package/server/lib/spaces/gatekeeper-channel.js +202 -0
  259. package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
  260. package/server/lib/spaces/gatekeeper-program.cjs +914 -0
  261. package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
  262. package/server/lib/spaces/hardening.js +457 -0
  263. package/server/lib/spaces/hardening.test.js +368 -0
  264. package/server/lib/spaces/host-git.js +127 -0
  265. package/server/lib/spaces/host-git.test.js +137 -0
  266. package/server/lib/spaces/labels.js +234 -0
  267. package/server/lib/spaces/labels.test.js +219 -0
  268. package/server/lib/spaces/layout.js +184 -0
  269. package/server/lib/spaces/layout.test.js +30 -0
  270. package/server/lib/spaces/manager.js +82 -0
  271. package/server/lib/spaces/manager.test.js +114 -0
  272. package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
  273. package/server/lib/spaces/places/contract-suite.js +156 -0
  274. package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
  275. package/server/lib/spaces/places/contract.test.js +6 -0
  276. package/server/lib/spaces/places/docker-engine.js +126 -0
  277. package/server/lib/spaces/places/docker-live-support.js +202 -0
  278. package/server/lib/spaces/places/docker-tools.js +186 -0
  279. package/server/lib/spaces/places/docker.js +587 -0
  280. package/server/lib/spaces/places/docker.test.js +1610 -0
  281. package/server/lib/spaces/places/escape-suite.js +1018 -0
  282. package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
  283. package/server/lib/spaces/places/fake-docker.js +375 -0
  284. package/server/lib/spaces/places/memory-place.js +103 -0
  285. package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
  286. package/server/lib/spaces/places/registry.js +43 -0
  287. package/server/lib/spaces/places/registry.test.js +50 -0
  288. package/server/lib/spaces/places/server.docker.live.test.js +225 -0
  289. package/server/lib/spaces/run-command.js +261 -0
  290. package/server/lib/spaces/run-command.test.js +396 -0
  291. package/server/lib/spaces/space-server.js +190 -0
  292. package/server/lib/spaces/space-server.test.js +335 -0
  293. package/server/lib/spaces/tools-filler.js +82 -0
  294. package/server/lib/spaces/tools-filler.test.js +148 -0
  295. package/server/lib/spaces/tools-pack.js +49 -0
  296. package/server/lib/spaces/tools-pack.test.js +57 -0
  297. package/server/lib/spaces/tools.js +138 -0
  298. package/server/lib/spaces/tools.test.js +153 -0
  299. package/server/lib/tts/openai-sdk.js +11 -0
  300. package/server/lib/tts/service.js +7 -5
  301. package/server/lib/tts/stt.js +3 -2
  302. package/server/lib/tunnels/DOCUMENTATION.md +2 -0
  303. package/server/lib/tunnels/routes.js +7 -0
  304. package/server/lib/tunnels/routes.test.js +55 -0
  305. package/server/lib/ui-auth/ui-auth.js +38 -2
  306. package/server/lib/ui-auth/ui-auth.test.js +42 -0
  307. package/server/lib/ui-auth/ui-passkeys.js +12 -10
  308. package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
  309. package/server/lib/walkthrough/pull-request.js +61 -8
  310. package/server/lib/walkthrough/pull-request.test.js +54 -1
  311. package/server/lib/walkthrough/routes.js +20 -0
  312. package/server/lib/walkthrough/routes.test.js +19 -0
  313. package/server/opencode-proxy.test.js +190 -166
  314. package/server/opencode-worktree-readiness.test.js +5 -4
  315. package/dist/assets/DiffView-B2CD9wC8.js +0 -4
  316. package/dist/assets/FilesView-C4vMFclc.js +0 -13
  317. package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
  320. package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
  321. package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
  322. package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
  323. package/dist/assets/SettingsView-CSinuH-1.js +0 -29
  324. package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
  326. package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
  328. package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
  329. package/dist/assets/de-Bro6_zyl.js +0 -10
  330. package/dist/assets/es-Bb9XQZvd.js +0 -16
  331. package/dist/assets/fr-Dgfzn-IL.js +0 -14
  332. package/dist/assets/index-CtQNmqUL.js +0 -1
  333. package/dist/assets/index-DqaMWNrC.css +0 -1
  334. package/dist/assets/index-Dqc6USl3.js +0 -40
  335. package/dist/assets/ja-BYsakGFh.js +0 -16
  336. package/dist/assets/ko-BSaQz9aN.js +0 -16
  337. package/dist/assets/main-B62_pjL5.js +0 -2
  338. package/dist/assets/main-BEdcOI2P.js +0 -896
  339. package/dist/assets/miniChat-LT8Rqofr.js +0 -2
  340. package/dist/assets/mobile-CqarEynh.js +0 -2
  341. package/dist/assets/pl-OIviPdoj.js +0 -16
  342. package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
  344. package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
  346. package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
  347. package/dist/assets/tr-CQI-KPre.js +0 -7
  348. package/dist/assets/uk-CqW7R4DA.js +0 -16
  349. package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
  350. package/dist/assets/useRootScrollLock-Cpf1qoGp.js +0 -2
  351. package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
  352. package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
  353. package/dist/assets/zh-CN-CVZG_o_J.js +0 -16
  354. package/dist/assets/zh-TW-D5ZxzCZp.js +0 -16
  355. package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
  356. package/server/lib/routing/feature-flag.js +0 -11
  357. package/server/lib/small-model/call.js +0 -851
  358. package/server/lib/small-model/call.test.js +0 -1292
  359. package/server/lib/small-model/catalog.js +0 -13
  360. package/server/lib/small-model/resolve.js +0 -131
  361. package/server/lib/small-model/resolve.test.js +0 -197
  362. package/server/lib/small-model/runtime-providers.js +0 -164
  363. package/server/lib/small-model/runtime-providers.test.js +0 -118
  364. package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
  365. package/server/lib/system-prompt/runtime.js +0 -49
  366. package/server/lib/system-prompt/runtime.test.js +0 -76
  367. package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
@@ -0,0 +1,104 @@
1
+ import { describe, expect, it, beforeEach, afterEach } from 'vitest';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { createRequire } from 'node:module';
6
+
7
+ import { projectCredentialValue, readCredentialsFromDb, resolveCredentialDbPath } from './credential-db.js';
8
+
9
+ const sqlite = (() => {
10
+ try {
11
+ return createRequire(import.meta.url)('node:sqlite');
12
+ } catch {
13
+ return null;
14
+ }
15
+ })();
16
+
17
+ const seed = (dbPath, rows) => {
18
+ const db = new sqlite.DatabaseSync(dbPath);
19
+ db.exec(
20
+ 'CREATE TABLE credential (id TEXT PRIMARY KEY, integration_id TEXT, label TEXT NOT NULL, value TEXT NOT NULL, ' +
21
+ 'connector_id TEXT, method_id TEXT, active INTEGER, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL)',
22
+ );
23
+ const insert = db.prepare(
24
+ 'INSERT INTO credential (id, integration_id, label, value, active, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)',
25
+ );
26
+ for (const row of rows) {
27
+ insert.run(row.id, row.integration, row.label ?? 'default', JSON.stringify(row.value), row.active ?? null, row.time ?? 1, row.time ?? 1);
28
+ }
29
+ db.close();
30
+ };
31
+
32
+ describe('projectCredentialValue', () => {
33
+ it('maps a stored key to the legacy api entry', () => {
34
+ expect(projectCredentialValue({ type: 'key', key: 'sk-1', metadata: { region: 'eu' } })).toEqual({
35
+ type: 'api',
36
+ key: 'sk-1',
37
+ metadata: { region: 'eu' },
38
+ });
39
+ });
40
+
41
+ it('maps a stored oauth credential, including the account and enterprise fields', () => {
42
+ expect(
43
+ projectCredentialValue({
44
+ type: 'oauth',
45
+ methodID: 'chatgpt-browser',
46
+ access: 'a',
47
+ refresh: 'r',
48
+ expires: 42,
49
+ metadata: { accountID: 'acc', enterpriseUrl: 'https://ghe.example' },
50
+ }),
51
+ ).toEqual({ type: 'oauth', access: 'a', refresh: 'r', expires: 42, accountId: 'acc', enterpriseUrl: 'https://ghe.example' });
52
+ });
53
+
54
+ it('rejects shapes it does not understand instead of guessing', () => {
55
+ expect(projectCredentialValue('not json')).toBeNull();
56
+ expect(projectCredentialValue({ type: 'oauth', access: 'a' })).toBeNull();
57
+ expect(projectCredentialValue({ type: 'something-new', key: 'x' })).toBeNull();
58
+ });
59
+ });
60
+
61
+ describe('resolveCredentialDbPath', () => {
62
+ it('defaults to opencode.db in the data dir and honours OPENCODE_DB', () => {
63
+ expect(resolveCredentialDbPath({ dataDir: '/data', env: {}, path })).toBe(path.join('/data', 'opencode.db'));
64
+ expect(resolveCredentialDbPath({ dataDir: '/data', env: { OPENCODE_DB: 'other.db' }, path })).toBe(path.resolve('/data', 'other.db'));
65
+ expect(resolveCredentialDbPath({ dataDir: '/data', env: { OPENCODE_DB: '/abs/x.db' }, path })).toBe(path.resolve('/abs/x.db'));
66
+ });
67
+ });
68
+
69
+ describe.skipIf(!sqlite)('readCredentialsFromDb', () => {
70
+ let dir;
71
+ beforeEach(() => {
72
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-cred-'));
73
+ });
74
+ afterEach(() => {
75
+ fs.rmSync(dir, { recursive: true, force: true });
76
+ });
77
+
78
+ it('returns null when there is no database, so the caller can fall back', () => {
79
+ expect(readCredentialsFromDb({ dbPath: path.join(dir, 'opencode.db'), fs })).toBeNull();
80
+ });
81
+
82
+ it('picks the active credential per integration, else the newest one', () => {
83
+ const dbPath = path.join(dir, 'opencode.db');
84
+ seed(dbPath, [
85
+ { id: 'c1', integration: 'openai', value: { type: 'oauth', access: 'old', refresh: 'r1', expires: 1 }, active: 0, time: 5 },
86
+ { id: 'c2', integration: 'openai', value: { type: 'oauth', access: 'new', refresh: 'r2', expires: 2 }, active: 1, time: 2 },
87
+ { id: 'c3', integration: 'opencode-go', value: { type: 'key', key: 'go-old' }, time: 1 },
88
+ { id: 'c4', integration: 'opencode-go', value: { type: 'key', key: 'go-new' }, time: 9 },
89
+ { id: 'c5', integration: 'weird', value: { type: 'future' }, time: 1 },
90
+ ]);
91
+ expect(readCredentialsFromDb({ dbPath, fs })).toEqual({
92
+ openai: { type: 'oauth', access: 'new', refresh: 'r2', expires: 2 },
93
+ 'opencode-go': { type: 'api', key: 'go-new' },
94
+ });
95
+ });
96
+
97
+ it('returns null rather than throwing when the schema is not what it expects', () => {
98
+ const dbPath = path.join(dir, 'opencode.db');
99
+ const db = new sqlite.DatabaseSync(dbPath);
100
+ db.exec('CREATE TABLE credential (id TEXT PRIMARY KEY)');
101
+ db.close();
102
+ expect(readCredentialsFromDb({ dbPath, fs })).toBeNull();
103
+ });
104
+ });
@@ -18,6 +18,9 @@ export const createOpenCodeEnvRuntime = (deps) => {
18
18
  readSettingsFromDiskMigrated,
19
19
  } = deps;
20
20
  const runSpawnSync = typeof deps.spawnSync === 'function' ? deps.spawnSync : spawnSync;
21
+ const readProvidedLoginShellEnvSnapshot = typeof deps.providedLoginShellEnvSnapshot === 'function'
22
+ ? deps.providedLoginShellEnvSnapshot
23
+ : () => undefined;
21
24
  const resolveHomeDir = typeof deps.homedir === 'function' ? deps.homedir : () => os.homedir();
22
25
 
23
26
  const parseNullSeparatedEnvSnapshot = (raw) => {
@@ -195,6 +198,14 @@ export const createOpenCodeEnvRuntime = (deps) => {
195
198
  return state.cachedLoginShellEnvSnapshot;
196
199
  }
197
200
 
201
+ // An embedding host (Desktop) that already probed the login shell hands
202
+ // its snapshot over; see login-shell-env.js.
203
+ const provided = readProvidedLoginShellEnvSnapshot();
204
+ if (provided !== undefined) {
205
+ state.cachedLoginShellEnvSnapshot = provided;
206
+ return provided;
207
+ }
208
+
198
209
  if (process.platform === 'win32') {
199
210
  const windowsSnapshot = getWindowsShellEnvSnapshot();
200
211
  state.cachedLoginShellEnvSnapshot = windowsSnapshot;
@@ -678,6 +689,14 @@ export const createOpenCodeEnvRuntime = (deps) => {
678
689
  return isExecutable(trimmed) ? trimmed : null;
679
690
  };
680
691
 
692
+ // OpenCode 2.x platform packages first, then the 1.x names.
693
+ const WINDOWS_X64_NATIVE_PACKAGES = [
694
+ path.join('@opencode', 'cli-windows-x64-baseline'),
695
+ path.join('@opencode', 'cli-windows-x64'),
696
+ 'opencode-windows-x64-baseline',
697
+ 'opencode-windows-x64',
698
+ ];
699
+
681
700
  const getWindowsNativeOpencodePackageNames = () => {
682
701
  // TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun
683
702
  // FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130).
@@ -686,31 +705,41 @@ export const createOpenCodeEnvRuntime = (deps) => {
686
705
  // arm64 branch below when the upstream issue is resolved.
687
706
  if (process.arch === 'arm64') {
688
707
  // --- ORIGINAL (restore when ARM64 is fixed) ---
689
- // return ['opencode-windows-arm64'];
690
- return ['opencode-windows-x64-baseline', 'opencode-windows-x64'];
708
+ // return ['@opencode/cli-windows-arm64', 'opencode-windows-arm64'];
709
+ return WINDOWS_X64_NATIVE_PACKAGES;
691
710
  }
692
711
  if (process.arch === 'x64') {
693
712
  // Prefer the baseline build when bypassing package-manager wrappers so the
694
713
  // direct binary still runs on hosts without AVX2 support.
695
- return ['opencode-windows-x64-baseline', 'opencode-windows-x64'];
714
+ return WINDOWS_X64_NATIVE_PACKAGES;
696
715
  }
697
716
  return [];
698
717
  };
699
718
 
719
+ // An npm-installed OpenCode lives under one of these package directories:
720
+ // `@opencode/cli` (OpenCode 2.x) or `opencode-ai` (1.x). Both ship a
721
+ // `bin/opencode.exe` that postinstall replaces with the platform binary from
722
+ // the matching optional dependency (`@opencode/cli-windows-x64` or
723
+ // `opencode-windows-x64`).
724
+ const OPENCODE_NPM_PACKAGE_DIRS = [path.join('@opencode', 'cli'), 'opencode-ai'];
725
+
700
726
  const resolveNativeOpencodeBinaryFromNodeModules = (nodeModulesDir) => {
701
727
  if (typeof nodeModulesDir !== 'string' || nodeModulesDir.trim().length === 0) {
702
728
  return null;
703
729
  }
704
730
 
705
- const packageShim = path.join(nodeModulesDir, 'opencode-ai', 'bin', 'opencode.exe');
706
- if (isExecutable(packageShim)) {
707
- return packageShim;
731
+ for (const packageDir of OPENCODE_NPM_PACKAGE_DIRS) {
732
+ const packageShim = path.join(nodeModulesDir, packageDir, 'bin', 'opencode.exe');
733
+ if (isExecutable(packageShim)) {
734
+ return packageShim;
735
+ }
708
736
  }
709
737
 
710
738
  for (const packageName of getWindowsNativeOpencodePackageNames()) {
711
739
  const candidates = [
712
740
  path.join(nodeModulesDir, packageName, 'bin', 'opencode.exe'),
713
- path.join(nodeModulesDir, 'opencode-ai', 'node_modules', packageName, 'bin', 'opencode.exe'),
741
+ ...OPENCODE_NPM_PACKAGE_DIRS.map((packageDir) =>
742
+ path.join(nodeModulesDir, packageDir, 'node_modules', packageName, 'bin', 'opencode.exe')),
714
743
  ];
715
744
  for (const candidate of candidates) {
716
745
  if (isExecutable(candidate)) {
@@ -747,13 +776,18 @@ export const createOpenCodeEnvRuntime = (deps) => {
747
776
 
748
777
  try {
749
778
  const content = fs.readFileSync(wrapperPath, 'utf8');
750
- const launcherMatch = content.match(/node_modules[\\/]+opencode-ai[\\/]+bin[\\/]+opencode/i);
779
+ const launcherMatch = content.match(/node_modules[\\/]+(?:@opencode[\\/]+cli|opencode-ai)[\\/]+bin[\\/]+opencode/i);
751
780
  if (!launcherMatch) {
752
781
  return null;
753
782
  }
754
783
 
755
784
  const launcherPath = path.resolve(path.dirname(wrapperPath), launcherMatch[0].replace(/[\\/]+/g, path.sep));
756
- return path.dirname(path.dirname(path.dirname(launcherPath)));
785
+ // Walk back up to `node_modules`: past `bin`, the package directory and,
786
+ // for the scoped 2.x package, its scope.
787
+ const depth = /[\\/]@opencode[\\/]/i.test(launcherMatch[0]) ? 4 : 3;
788
+ let nodeModulesDir = launcherPath;
789
+ for (let index = 0; index < depth; index += 1) nodeModulesDir = path.dirname(nodeModulesDir);
790
+ return nodeModulesDir;
757
791
  } catch {
758
792
  return null;
759
793
  }
@@ -793,6 +827,11 @@ export const createOpenCodeEnvRuntime = (deps) => {
793
827
  pushCandidate(path.dirname(path.dirname(fileDir)));
794
828
  }
795
829
 
830
+ if (lower.endsWith(`${path.sep}node_modules${path.sep}@opencode${path.sep}cli${path.sep}bin${path.sep}opencode`)
831
+ || lower.endsWith(`${path.sep}node_modules${path.sep}@opencode${path.sep}cli${path.sep}bin${path.sep}opencode.exe`)) {
832
+ pushCandidate(path.dirname(path.dirname(path.dirname(fileDir))));
833
+ }
834
+
796
835
  if (path.basename(fileDir).toLowerCase() === 'npm') {
797
836
  pushCandidate(path.join(fileDir, 'node_modules'));
798
837
  }
@@ -115,6 +115,7 @@ const createRuntime = (settings, options = {}) => {
115
115
  readSettingsFromDiskMigrated: async () => settings,
116
116
  spawnSync: options.spawnSync,
117
117
  homedir: options.homedir,
118
+ providedLoginShellEnvSnapshot: options.providedLoginShellEnvSnapshot,
118
119
  });
119
120
 
120
121
  return { runtime, state };
@@ -225,6 +226,33 @@ describe('OpenCode env runtime', () => {
225
226
  }
226
227
  });
227
228
 
229
+ it('uses a login-shell snapshot provided by the host instead of probing the shell', () => {
230
+ let probes = 0;
231
+ const spawnSyncSpy = () => { probes += 1; return { status: 0, stdout: 'PATH=/from/probe\0' }; };
232
+ const { runtime, state } = createRuntime({}, {
233
+ spawnSync: spawnSyncSpy,
234
+ providedLoginShellEnvSnapshot: () => ({ PATH: '/from/host' }),
235
+ });
236
+ state.cachedLoginShellEnvSnapshot = undefined;
237
+
238
+ expect(runtime.getLoginShellEnvSnapshot()).toEqual({ PATH: '/from/host' });
239
+ expect(state.cachedLoginShellEnvSnapshot).toEqual({ PATH: '/from/host' });
240
+ expect(probes).toBe(0);
241
+ });
242
+
243
+ it('does not probe the shell when the host provided an empty snapshot', () => {
244
+ let probes = 0;
245
+ const spawnSyncSpy = () => { probes += 1; return { status: 0, stdout: 'PATH=/from/probe\0' }; };
246
+ const { runtime, state } = createRuntime({}, {
247
+ spawnSync: spawnSyncSpy,
248
+ providedLoginShellEnvSnapshot: () => null,
249
+ });
250
+ state.cachedLoginShellEnvSnapshot = undefined;
251
+
252
+ expect(runtime.getLoginShellEnvSnapshot()).toBeNull();
253
+ expect(probes).toBe(0);
254
+ });
255
+
228
256
  it('clears AppImage ARGV0 even when no login-shell snapshot is available', () => {
229
257
  const previousArgv0 = process.env.ARGV0;
230
258
  process.env.ARGV0 = '/path/to/OpenChamber.AppImage';
@@ -528,6 +556,23 @@ describe('OpenCode env runtime', () => {
528
556
  });
529
557
  });
530
558
 
559
+ it('resolves an npm-installed OpenCode 2.x cmd shim to its packaged Windows executable', () => {
560
+ setPlatform('win32');
561
+ const npmDir = createTempDir('openchamber-opencode-npm-v2-');
562
+ const shim = path.join(npmDir, 'opencode.cmd');
563
+ const nativeBinary = path.join(npmDir, 'node_modules', '@opencode', 'cli', 'bin', 'opencode.exe');
564
+ fs.mkdirSync(path.dirname(nativeBinary), { recursive: true });
565
+ fs.writeFileSync(nativeBinary, '');
566
+ fs.writeFileSync(shim, '@ECHO off\r\n"%dp0%\\node_modules\\@opencode\\cli\\bin\\opencode.exe" %*\r\n');
567
+ const { runtime } = createRuntime({});
568
+
569
+ expect(runtime.resolveManagedOpenCodeLaunchSpec(shim)).toEqual({
570
+ binary: nativeBinary,
571
+ args: [],
572
+ wrapperType: 'native-wrapper',
573
+ });
574
+ });
575
+
531
576
  it('resolves npm OpenCode cmd shims to the packaged Windows executable', () => {
532
577
  setPlatform('win32');
533
578
  const npmDir = createTempDir('openchamber-opencode-npm-');
@@ -32,8 +32,8 @@ import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
32
32
  import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
33
33
  import { registerOpenCodeRoutes } from './routes.js';
34
34
  import { getProviderSources, removeProviderConfig, upsertProviderConfig } from './providers.js';
35
- import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
36
- import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
35
+ import { getAgentSources, getAgentConfig, getAgentPermissions, createAgent, updateAgent, deleteAgent } from './agents.js';
36
+ import { getCommandSources, getCommandConfig, createCommand, updateCommand, deleteCommand } from './commands.js';
37
37
  import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
38
38
  import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
39
39
  import {
@@ -86,7 +86,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
86
86
  import('../walkthrough/index.js'),
87
87
  import('../walkthrough/pull-request.js'),
88
88
  ]);
89
- walkthroughService = { ...service, getPullRequestDiff: pullRequest.getPullRequestDiff };
89
+ walkthroughService = { ...service, getPullRequestDiff: pullRequest.getPullRequestDiff, getPullRequestFileContents: pullRequest.getPullRequestFileContents };
90
90
  }
91
91
  return walkthroughService;
92
92
  };
@@ -102,6 +102,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
102
102
  resolveGitBinaryForSpawn,
103
103
  createFsSearchRuntime,
104
104
  openchamberDataDir,
105
+ onGuestDeactivated,
105
106
  openchamberUserConfigRoot,
106
107
  managedChatsRoot,
107
108
  normalizeDirectoryPath,
@@ -114,6 +115,9 @@ export const createFeatureRoutesRuntime = (dependencies) => {
114
115
  refreshOpenCodeAfterConfigChange,
115
116
  getOpenCodeResolutionSnapshot,
116
117
  getOpenCodeUpgradeCapability,
118
+ upgradeOpenCodeCli,
119
+ getOpenCodeCompatibility,
120
+ installOpenCodeV2,
117
121
  formatSettingsResponse,
118
122
  readSettingsFromDisk,
119
123
  readSettingsFromDiskMigrated,
@@ -157,7 +161,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
157
161
  registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
158
162
  registerMessageQueueRoutes(app, messageQueueRuntime);
159
163
  registerRoutingRoutes(app, routingRuntime);
160
- // Before the generic OpenCode proxy: turns `openchamber/auto` into a real model.
164
+ // Before the generic OpenCode proxy: swallows the `openchamber/auto` model
165
+ // switch and routes the sends that follow it.
161
166
  registerRoutingPromptRewrite(app, routingRuntime);
162
167
 
163
168
  registerOpenCodeRoutes(app, {
@@ -165,6 +170,9 @@ export const createFeatureRoutesRuntime = (dependencies) => {
165
170
  clientReloadDelayMs,
166
171
  getOpenCodeResolutionSnapshot,
167
172
  getOpenCodeUpgradeCapability,
173
+ upgradeOpenCodeCli,
174
+ getOpenCodeCompatibility,
175
+ installOpenCodeV2,
168
176
  formatSettingsResponse,
169
177
  readSettingsFromDisk,
170
178
  readSettingsFromDiskMigrated,
@@ -233,10 +241,12 @@ export const createFeatureRoutesRuntime = (dependencies) => {
233
241
  clientReloadDelayMs,
234
242
  getAgentSources,
235
243
  getAgentConfig,
244
+ getAgentPermissions,
236
245
  createAgent,
237
246
  updateAgent,
238
247
  deleteAgent,
239
248
  getCommandSources,
249
+ getCommandConfig,
240
250
  createCommand,
241
251
  updateCommand,
242
252
  deleteCommand,
@@ -321,7 +331,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
321
331
  registerGitHubRoutes(app);
322
332
  registerLinearRoutes(app);
323
333
  await registerBuiltInGuests({ persistPath: extensionsPersistPath(openchamberDataDir), root: routeDependencies.builtInExtensionsDir });
324
- registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService });
334
+ registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService, onGuestDeactivated });
325
335
  registerGitRoutes(app, {
326
336
  emitWorktreeChanged: ({ directories, at }) => {
327
337
  const clients = getOpenChamberEventClients();
@@ -12,6 +12,7 @@ const createRuntime = (waitForReady, state) => createOpenCodeLifecycleRuntime({
12
12
  state,
13
13
  env: { ENV_CONFIGURED_OPENCODE_PORT: 45678, ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1' },
14
14
  syncToHmrState() {}, syncFromHmrState() {},
15
+ checkOpenCodeBinary: async () => '2.0.14',
15
16
  ensureOpencodeCliEnv: () => process.execPath,
16
17
  applyOpencodeBinaryFromSettings: async () => {},
17
18
  ensureLocalOpenCodeServerPassword: async () => 'fixture-only',
@@ -30,8 +31,10 @@ describe('managed process lifecycle with real children', () => {
30
31
  process.env.OPENCHAMBER_MANAGED_PROCESS_REGISTRY = path.join(root, 'registry');
31
32
  const marker = path.join(root, 'pids');
32
33
  const childScript = `process.on('SIGTERM', () => {}); require('node:fs').appendFileSync(${JSON.stringify(marker)}, process.pid + '\\n'); process.stdout.write('ready\\n'); setInterval(() => {}, 1000);`;
33
- let readinessMessage = 'opencode server listening on http://127.0.0.1:45678\n';
34
- if (failure === 'invalid-readiness') readinessMessage = 'opencode server listening without a URL\n';
34
+ let readinessMessage = 'server listening on http://127.0.0.1:45678\n';
35
+ // A line that looks like the readiness line but carries no URL is noise,
36
+ // not readiness: the start must time out rather than connect to nothing.
37
+ if (failure === 'invalid-readiness') readinessMessage = 'server listening without a URL\n';
35
38
  if (failure === 'startup-timeout' || failure === 'shutdown-during-startup') readinessMessage = '';
36
39
  // Node is an isolated stand-in for the native OpenCode binary. Lifecycle
37
40
  // still launches its real `serve --hostname ... --port ...` command.
@@ -60,10 +63,10 @@ describe('managed process lifecycle with real children', () => {
60
63
  state.isShuttingDown = true;
61
64
  await state.openCodeProcess.close();
62
65
  await rejected;
63
- } else if (failure === 'startup-timeout') {
66
+ } else if (failure === 'startup-timeout' || failure === 'invalid-readiness') {
64
67
  await expect(runtime.startOpenCode()).rejects.toThrow('Timeout waiting for OpenCode');
65
68
  } else {
66
- await expect(runtime.startOpenCode()).rejects.toThrow(failure === 'health-error' ? 'fixture health failure' : 'Failed to parse server url');
69
+ await expect(runtime.startOpenCode()).rejects.toThrow('fixture health failure');
67
70
  }
68
71
  const pids = (await fs.readFile(marker, 'utf8')).trim().split('\n').map(Number);
69
72
  expect(pids).toHaveLength(failure === 'none' || failure === 'shutdown-during-startup' ? 2 : 4);
@@ -1,9 +1,11 @@
1
+ import { readOpenCodeInfo, isSupportedOpenCodeVersion, requireOpenCodeV2, UnsupportedOpenCodeVersionError } from './compatibility.js';
1
2
  import { spawn, spawnSync } from 'node:child_process';
2
3
  import net from 'node:net';
3
4
  import { stripAppImageArgv0Leak } from '../inherited-env.js';
4
5
  import { registerManagedProcess, unregisterManagedProcess, reapOrphanedProcesses } from './managed-process-registry.js';
5
6
  import { applyProviderEnvAliases } from './provider-env-aliases.js';
6
7
  import { recordStartupPerformance } from './startup-performance.js';
8
+ import { topUpV1Migration } from './v1-migration-topup.js';
7
9
 
8
10
  const parsePositiveInt = (value, fallback) => {
9
11
  const parsed = Number.parseInt(String(value ?? ''), 10);
@@ -17,7 +19,35 @@ const HEALTH_CHECK_MAX_CONSECUTIVE_FAILURES = parsePositiveInt(
17
19
  );
18
20
  const HEALTH_CHECK_INTERVAL_OVERRIDE_MS = parsePositiveInt(process.env.OPENCHAMBER_OPENCODE_HEALTH_INTERVAL_MS, 0);
19
21
  const HEALTH_CHECK_RESULT_CACHE_MS = parsePositiveInt(process.env.OPENCHAMBER_OPENCODE_HEALTH_CACHE_MS, 750);
20
- const OPENCODE_HEALTH_PATH = '/global/health';
22
+ const OPENCODE_HEALTH_PATH = '/api/info';
23
+ const OPENCODE_REQUIRED_MAJOR_VERSION = 2;
24
+
25
+ /**
26
+ * OpenChamber talks to OpenCode 2.x only. v1 serves its routes without the
27
+ * `/api` prefix, publishes a different event vocabulary and has no `plugins`
28
+ * config key, so an older binary fails in a hundred small ways instead of one
29
+ * clear one.
30
+ *
31
+ * The version comes from the health payload rather than from `opencode
32
+ * --version`: it costs no extra process, and it also covers an external
33
+ * OpenCode the user started themselves. `/api/info` only exists in 2.x (2.0.8
34
+ * removed the older `/api/health`), so a 404 there is the same answer by
35
+ * another route. A 200 is the readiness signal; the payload has no `healthy`
36
+ * field, only `{ version, pid, urls, paths }`.
37
+ */
38
+ const OPENCODE_VERSION_REQUIREMENT_DETAIL =
39
+ `OpenChamber requires OpenCode ${OPENCODE_REQUIRED_MAJOR_VERSION}.x`;
40
+
41
+ const classifyOpenCodeVersion = (version) => {
42
+ if (typeof version !== 'string') return { ok: true };
43
+ const match = version.match(/v?(\d+)\./);
44
+ if (!match) return { ok: true };
45
+ if (Number(match[1]) >= OPENCODE_REQUIRED_MAJOR_VERSION) return { ok: true };
46
+ return {
47
+ ok: false,
48
+ detail: `${OPENCODE_VERSION_REQUIREMENT_DETAIL}, found ${version.trim()}. Update OpenCode and start OpenChamber again.`,
49
+ };
50
+ };
21
51
  // Last-used directory plus the three most recently opened projects — deeper
22
52
  // tails are unlikely to be the user's first click and just add background work.
23
53
  const WARMUP_DIRECTORY_LIMIT = 4;
@@ -111,8 +141,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
111
141
  onOpenCodeRestarted = null,
112
142
  managedStartupTimeoutMs = 30_000,
113
143
  now = Date.now,
144
+ topUpV1SessionMigration = topUpV1Migration,
145
+ checkOpenCodeBinary = requireOpenCodeV2,
114
146
  } = deps;
115
147
 
148
+ let managedPreflight = null;
149
+
116
150
  const killProcessOnPortWin32 = (port) => {
117
151
  try {
118
152
  // Get-NetTCPConnection reads the same locale-independent WinNT API
@@ -472,12 +506,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
472
506
  stdout += chunk.toString();
473
507
  const lines = stdout.split('\n');
474
508
  for (const line of lines) {
475
- if (!line.startsWith('opencode server listening')) continue;
476
- const match = line.match(/on\s+(https?:\/\/[^\s]+)/);
477
- if (!match) {
478
- finish(reject, new Error(`Failed to parse server url from output: ${line}`));
479
- return;
480
- }
509
+ // OpenCode 2.x prints `server listening on http://host:port` with no
510
+ // "opencode" prefix.
511
+ const match = line.match(/server listening on\s+(https?:\/\/\S+)/);
512
+ if (!match) continue;
481
513
  attachRuntimeStderrCapture();
482
514
  finish(resolve, match[1]);
483
515
  return;
@@ -584,7 +616,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
584
616
  healthy: false,
585
617
  failure: {
586
618
  class: 'invalid_response',
587
- detail: `Health endpoint returned HTTP ${response.status ?? 'unknown'}`,
619
+ detail: response.status === 404
620
+ ? `${OPENCODE_VERSION_REQUIREMENT_DETAIL}: this server has no /api/info, which every 2.x server serves.`
621
+ : `Info endpoint returned HTTP ${response.status ?? 'unknown'}`,
588
622
  },
589
623
  };
590
624
  }
@@ -596,17 +630,15 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
596
630
  healthy: false,
597
631
  failure: {
598
632
  class: 'invalid_response',
599
- detail: 'Health endpoint returned invalid JSON',
633
+ detail: 'Info endpoint returned invalid JSON',
600
634
  },
601
635
  };
602
636
  }
603
- if (body?.healthy !== true) {
637
+ const version = classifyOpenCodeVersion(body?.version);
638
+ if (!version.ok) {
604
639
  return {
605
640
  healthy: false,
606
- failure: {
607
- class: 'invalid_response',
608
- detail: 'Health endpoint did not report healthy=true',
609
- },
641
+ failure: { class: 'invalid_response', detail: version.detail },
610
642
  };
611
643
  }
612
644
  return { healthy: true, failure: null };
@@ -638,9 +670,8 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
638
670
  signal: controller.signal,
639
671
  });
640
672
  clearTimeout(timeout);
641
- if (!response.ok) return false;
642
- const body = await response.json().catch(() => null);
643
- return body?.healthy === true;
673
+ const info = await readOpenCodeInfo(response);
674
+ return info !== null && isSupportedOpenCodeVersion(info.version);
644
675
  } catch {
645
676
  return false;
646
677
  }
@@ -680,6 +711,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
680
711
 
681
712
  await applyOpencodeBinaryFromSettings({ strict: true });
682
713
  const resolvedBinary = ensureOpencodeCliEnv();
714
+ const preflight = checkOpenCodeBinary(resolveManagedOpenCodeLaunchSpec(resolvedBinary));
715
+ managedPreflight = preflight.then(() => true, () => false);
716
+ await preflight;
683
717
  recordStartupPerformance('opencode.binary.ready', {
684
718
  attempt,
685
719
  durationMs: performance.now() - phaseStartedAt,
@@ -704,6 +738,18 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
704
738
  });
705
739
  phaseStartedAt = performance.now();
706
740
 
741
+ // Re-arm OpenCode's own V1 -> V2 session import for sessions a bundled
742
+ // OpenCode 1.x created after the migration already completed. Only for the
743
+ // managed process, only while it is not running, and never fatal.
744
+ try {
745
+ const topUp = topUpV1SessionMigration();
746
+ if (topUp && topUp.status !== 'skipped') {
747
+ console.log('[OpenCode] V1 session migration top-up:', topUp);
748
+ }
749
+ } catch (error) {
750
+ console.warn('[OpenCode] V1 session migration top-up failed:', error instanceof Error ? error.message : error);
751
+ }
752
+
707
753
  let serverInstance;
708
754
  try {
709
755
  if (state.isShuttingDown) throw new Error('OpenCode startup cancelled during shutdown');
@@ -776,13 +822,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
776
822
  };
777
823
 
778
824
  const startOpenCode = async () => {
825
+ managedPreflight = null;
779
826
  let lastError = null;
780
827
  for (let attempt = 1; attempt <= START_OPEN_CODE_MAX_ATTEMPTS; attempt += 1) {
781
828
  try {
782
829
  return await startOpenCodeOnce(attempt);
783
830
  } catch (error) {
784
831
  lastError = error;
785
- if (state.isShuttingDown || error?.code === 'OPENCODE_BINARY_INVALID') {
832
+ if (state.isShuttingDown || error instanceof UnsupportedOpenCodeVersionError || error?.code === 'OPENCODE_BINARY_INVALID') {
786
833
  break;
787
834
  }
788
835
  if (attempt >= START_OPEN_CODE_MAX_ATTEMPTS) {
@@ -810,6 +857,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
810
857
  }
811
858
 
812
859
  state.currentRestartPromise = (async () => {
860
+ managedPreflight = null;
813
861
  state.isRestartingOpenCode = true;
814
862
  state.isOpenCodeReady = false;
815
863
  state.openCodeNotReadySince = Date.now();
@@ -937,18 +985,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
937
985
  timeout = null;
938
986
 
939
987
  if (!response.ok) {
940
- lastError = new Error(`OpenCode health endpoint responded with status ${response.status}`);
941
- await new Promise((resolve) => setTimeout(resolve, intervalMs));
942
- continue;
943
- }
944
-
945
- const body = await response.json().catch(() => null);
946
- if (body?.healthy !== true) {
947
- lastError = new Error('OpenCode health endpoint returned unhealthy response');
988
+ lastError = new Error(`OpenCode info endpoint responded with status ${response.status}`);
948
989
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
949
990
  continue;
950
991
  }
951
992
 
993
+ const info = await readOpenCodeInfo(response);
994
+ if (!info) throw new Error('OpenCode did not return valid version information.');
995
+ if (!isSupportedOpenCodeVersion(info.version)) throw new UnsupportedOpenCodeVersionError(info.version);
952
996
  state.isOpenCodeReady = true;
953
997
  state.lastOpenCodeError = null;
954
998
  return;
@@ -981,14 +1025,16 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
981
1025
  const deadline = Date.now() + timeoutMs;
982
1026
  while (Date.now() < deadline) {
983
1027
  try {
984
- const response = await fetch(buildOpenCodeUrl('/agent'), {
1028
+ const response = await fetch(buildOpenCodeUrl('/api/agent'), {
985
1029
  method: 'GET',
986
1030
  headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
987
1031
  });
988
1032
 
989
1033
  if (response.ok) {
990
- const agents = await response.json();
991
- if (Array.isArray(agents) && agents.some((agent) => agent?.name === agentName)) {
1034
+ // OpenCode 2.x answers `/api/*` with `{ location, data }`.
1035
+ const body = await response.json();
1036
+ const agents = Array.isArray(body) ? body : body?.data;
1037
+ if (Array.isArray(agents) && agents.some((agent) => agent?.id === agentName)) {
992
1038
  return;
993
1039
  }
994
1040
  }
@@ -1155,7 +1201,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
1155
1201
  try {
1156
1202
  const controller = new AbortController();
1157
1203
  timeout = setTimeout(() => controller.abort(), WARMUP_REQUEST_TIMEOUT_MS);
1158
- const url = `${buildOpenCodeUrl('/session/status', '')}?directory=${encodeURIComponent(directory)}`;
1204
+ // Warming a directory is the point, not the answer: any directory-scoped
1205
+ // read makes OpenCode initialise it. `/api/session` is the cheapest one
1206
+ // that takes a directory (`/api/session/active` is global).
1207
+ const url = `${buildOpenCodeUrl('/api/session', '')}?directory=${encodeURIComponent(directory)}&limit=1`;
1159
1208
  await fetch(url, {
1160
1209
  method: 'GET',
1161
1210
  headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
@@ -1321,6 +1370,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
1321
1370
  };
1322
1371
 
1323
1372
  return {
1373
+ getManagedOpenCodePreflight: async () => {
1374
+ const preflight = managedPreflight;
1375
+ if (!preflight || state.isExternalOpenCode || state.isShuttingDown) return false;
1376
+ const compatible = await preflight;
1377
+ return compatible && preflight === managedPreflight && !state.isExternalOpenCode && !state.isShuttingDown;
1378
+ },
1324
1379
  killProcessOnPort,
1325
1380
  startOpenCode,
1326
1381
  restartOpenCode,