@vantran-se/openchamber-web 1.24.3 → 2.0.1

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-BjJHrzmU.js → CodeMirrorCommentWidgets-ntXIbhV2.js} +1 -1
  5. package/dist/assets/DiffView-CjxUovbM.js +4 -0
  6. package/dist/assets/FilesView-DFWD0RQR.js +17 -0
  7. package/dist/assets/{GitView-CDOJy5Ay.js → GitView-BWa7e5vw.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-D3S0Rjic.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
  10. package/dist/assets/{JsonTreeView-D0C9nU7K.js → JsonTreeView-3sGwqexh.js} +1 -1
  11. package/dist/assets/LinearIssuesView-C6dpIdjw.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-Q7YR_tl4.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-DxDV56bL.js +12 -0
  14. package/dist/assets/OnboardingScreen-rK4Stg__.js +1 -0
  15. package/dist/assets/{PatchDiff-DnEhjXcC.js → PatchDiff-DS-IMU1j.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-DyxZVVVR.js → PierreDiffViewer-DW23KWKZ.js} +1 -1
  17. package/dist/assets/PlanView-BCk_VobP.js +5 -0
  18. package/dist/assets/PluginPane-BPP1dbck.js +3 -0
  19. package/dist/assets/SettingsView-kMmrpn43.js +29 -0
  20. package/dist/assets/SettingsWindow-BpvD6IYQ.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-BLttSS1X.js +1 -0
  22. package/dist/assets/ToolOutputDialog-CtqDqbyr.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-5FcKdyRy.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-0pyr_mHY.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
  25. package/dist/assets/{WalkthroughView-yNj4nOP5.js → WalkthroughView-lap5btCF.js} +2 -2
  26. package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
  27. package/dist/assets/{commitAndPush-DvJKa3Ks.js → commitAndPush-CwGJfJG-.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DxTBMuu-.js → computeVirtualFileMetrics-DW5jjX4n.js} +1 -1
  29. package/dist/assets/de-CndEg67T.js +10 -0
  30. package/dist/assets/es-DglvPlaC.js +16 -0
  31. package/dist/assets/fr-DejPCpbS.js +14 -0
  32. package/dist/assets/index-C2r64QfD.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-D7FkBMP6.js → isDiffPlainText-D47a6hMP.js} +1 -1
  36. package/dist/assets/{isFilePlainText-CL2vsrIZ.js → isFilePlainText-B-mGgeKl.js} +1 -1
  37. package/dist/assets/ja-CVpQawCd.js +16 -0
  38. package/dist/assets/ko-Bw446h9j.js +16 -0
  39. package/dist/assets/main-Bz4WWXbr.js +2 -0
  40. package/dist/assets/main-oreqDsOV.js +896 -0
  41. package/dist/assets/{markdownCore-Dh7WEsdM.js → markdownCore-D2e5sp9y.js} +4 -4
  42. package/dist/assets/miniChat-D1v2gsBI.js +2 -0
  43. package/dist/assets/mobile-DBj3lCf2.js +2 -0
  44. package/dist/assets/{patchFileDiff-Bsba8KFp.js → patchFileDiff-BpeUOV91.js} +3 -3
  45. package/dist/assets/pl-BYNlPdhm.js +16 -0
  46. package/dist/assets/pt-BR-CL-Yfv9H.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-DVtfEB2z.js +2 -0
  48. package/dist/assets/renderMobileApp-DFp9-xbG.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-CsIpUep1.js +27 -0
  50. package/dist/assets/scheduledTasksApi-B5OHVpPM.js +1 -0
  51. package/dist/assets/{shikiHighlight-8_3csmr6.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-CHo2fCgW.js → textMateThemeFromAppTheme-DGF2kG7g.js} +1 -1
  53. package/dist/assets/tr-DzmbxLy-.js +7 -0
  54. package/dist/assets/uk-C7vQnwro.js +16 -0
  55. package/dist/assets/useAppFontEffects-Ci3FEaPf.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-DkQ_gqpn.js → useInlineCommentController-CuUuakrt.js} +2 -2
  57. package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-CdzUmUyZ.js → vendor-codemirror-language-data-CQa7_1cr.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-B8IcVQAA.js → web-components-B6tKQyJN.js} +2 -2
  64. package/dist/assets/zh-CN-Cl0srS-z.js +16 -0
  65. package/dist/assets/zh-TW-DtII5bDb.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-COVQhWGd.js +0 -4
  316. package/dist/assets/FilesView-B1V5JxFX.js +0 -13
  317. package/dist/assets/LinearIssuesView-D7FKg1XJ.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-BrFjAVEf.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-DWepTafh.js +0 -12
  320. package/dist/assets/OnboardingScreen-Dyxxiqdt.js +0 -1
  321. package/dist/assets/PlanView-LW2z_EAP.js +0 -5
  322. package/dist/assets/PluginPane-B4tKno2h.js +0 -3
  323. package/dist/assets/SettingsView-DEaMulN9.js +0 -29
  324. package/dist/assets/SettingsWindow-FLDJXOgQ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +0 -1
  326. package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CkR4msKP.js +0 -8
  328. package/dist/assets/appThemeRegistry-BuUKhbYQ.js +0 -1
  329. package/dist/assets/de-DgxgFyGG.js +0 -10
  330. package/dist/assets/es-BFsZj5F_.js +0 -16
  331. package/dist/assets/fr-jdZ5KKQi.js +0 -14
  332. package/dist/assets/index-CKHjPKbq.js +0 -1
  333. package/dist/assets/index-Cbci4i1W.js +0 -40
  334. package/dist/assets/index-DqaMWNrC.css +0 -1
  335. package/dist/assets/ja-C5kKrhO4.js +0 -16
  336. package/dist/assets/ko-DXDTT_0O.js +0 -16
  337. package/dist/assets/main-CZALtVek.js +0 -896
  338. package/dist/assets/main-CgXMBNvG.js +0 -2
  339. package/dist/assets/miniChat-CwTmDixi.js +0 -2
  340. package/dist/assets/mobile-CLf2vCtO.js +0 -2
  341. package/dist/assets/pl-D-7x4Y8Q.js +0 -16
  342. package/dist/assets/pt-BR-CSBZrHa8.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +0 -2
  344. package/dist/assets/renderMobileApp-V4hYd32H.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +0 -25
  346. package/dist/assets/scheduledTasksApi-DzjbLd8P.js +0 -1
  347. package/dist/assets/tr-Dr-oJ22L.js +0 -7
  348. package/dist/assets/uk-9Nx83UcI.js +0 -16
  349. package/dist/assets/useAppFontEffects-khZXpFL_.js +0 -7333
  350. package/dist/assets/useRootScrollLock-DdvHf9Pt.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-Bxy_Hj3r.js +0 -16
  354. package/dist/assets/zh-TW-0BB4gpHM.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
@@ -28,10 +28,6 @@ function ensureProjectSkillDir(workingDirectory) {
28
28
  if (!fs.existsSync(projectSkillDir)) {
29
29
  fs.mkdirSync(projectSkillDir, { recursive: true });
30
30
  }
31
- const legacyProjectSkillDir = path.join(workingDirectory, '.opencode', 'skill');
32
- if (!fs.existsSync(legacyProjectSkillDir)) {
33
- fs.mkdirSync(legacyProjectSkillDir, { recursive: true });
34
- }
35
31
  return projectSkillDir;
36
32
  }
37
33
 
@@ -199,12 +195,18 @@ function discoverSkills(workingDirectory) {
199
195
  let configuredPaths = [];
200
196
  try {
201
197
  const config = readConfig(workingDirectory);
202
- configuredPaths = Array.isArray(config?.skills?.paths) ? config.skills.paths : [];
198
+ // OpenCode 2 stores extra skill sources as one ordered `skills` array; v1
199
+ // split them into `skills.paths` and `skills.urls`. URLs are not scanned
200
+ // from disk either way.
201
+ configuredPaths = Array.isArray(config?.skills)
202
+ ? config.skills
203
+ : (Array.isArray(config?.skills?.paths) ? config.skills.paths : []);
203
204
  } catch {
204
205
  configuredPaths = [];
205
206
  }
206
207
  for (const skillPath of configuredPaths) {
207
208
  if (typeof skillPath !== 'string' || !skillPath.trim()) continue;
209
+ if (/^https?:\/\//i.test(skillPath.trim())) continue;
208
210
  const expanded = skillPath.startsWith('~/')
209
211
  ? path.join(os.homedir(), skillPath.slice(2))
210
212
  : skillPath;
@@ -32,7 +32,7 @@ export const createTunnelWiringRuntime = (dependencies) => {
32
32
  setRuntimeManagedRemoteTunnelToken,
33
33
  } = dependencies;
34
34
 
35
- const initialize = (app, initialPort) => {
35
+ const initialize = (app, initialPort, hasUiPassword) => {
36
36
  let activePort = initialPort;
37
37
 
38
38
  const tunnelService = createTunnelService({
@@ -51,6 +51,7 @@ export const createTunnelWiringRuntime = (dependencies) => {
51
51
  tunnelService,
52
52
  tunnelProviderRegistry,
53
53
  tunnelAuthController,
54
+ hasUiPassword,
54
55
  readSettingsFromDiskMigrated,
55
56
  readManagedRemoteTunnelConfigFromDisk,
56
57
  normalizeTunnelProvider,
@@ -42,7 +42,7 @@ describe('OpenCode upgrade capability', () => {
42
42
  });
43
43
  });
44
44
 
45
- it('allows OpenCode to upgrade a managed non-bundled binary', () => {
45
+ it('offers CLI upgrades for a managed non-bundled binary', () => {
46
46
  expect(resolveOpenCodeUpgradeCapability({
47
47
  isExternal: false,
48
48
  hasManagedProcess: true,
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Re-arms OpenCode's own V1 -> V2 session migration for sessions that were
3
+ * created after it already finished.
4
+ *
5
+ * OpenCode 2.x migrates the legacy `session`/`message`/`part` tables into
6
+ * `session_v2`/`session_message` once and then writes `{"phase":"completed"}`
7
+ * under `migration.v1-v2` in `kv`. Users who kept running OpenChamber's
8
+ * bundled OpenCode 1.x beside a v2 install created more V1 sessions after that
9
+ * point, and OpenCode never looks at them again: they are simply invisible in
10
+ * v2. This module hands OpenCode a resume cursor before OpenChamber spawns the
11
+ * MANAGED OpenCode, so OpenCode's own `run()` imports them on startup with its
12
+ * own transform. OpenChamber never writes session rows itself.
13
+ *
14
+ * Two hard rules, both verified against v2.0.8
15
+ * `packages/core/src/database/v1-migration.bun.ts`:
16
+ *
17
+ * 1. NEVER delete or clear the `migration.v1-v2` row. With no row at all,
18
+ * OpenCode treats the database as pre-migration and DELETEs the whole
19
+ * `event` table — v2's durable event log — before starting over.
20
+ * 2. NEVER schedule a cursor that makes OpenCode revisit a session which has
21
+ * seen v2 activity. Every visited session gets its `session_message` rows
22
+ * deleted and replaced by the V1 transform, so a migrated session that was
23
+ * continued in v2 would lose that conversation.
24
+ * 3. A session deleted in v2 comes back. A v2 delete removes the `session_v2`
25
+ * row but leaves the legacy `session` row behind, so the loop imports it
26
+ * again. Decided by the maintainer (2026-09-22): people run 1.x and 2.x
27
+ * side by side while switching, and OpenChamber's own time-based cleanup
28
+ * may have deleted sessions the user never touched, so importing again is
29
+ * better than importing nothing. Deleting again is cheap; a lost 1.x
30
+ * session is not. This holds until OpenCode ships an import route that
31
+ * takes an explicit list of sessions.
32
+ *
33
+ * The loop OpenCode runs is `SELECT id FROM session WHERE id < cursor ORDER BY
34
+ * id DESC LIMIT 1`, one session at a time, so the cursor has to be strictly
35
+ * greater than the largest id we want imported. Session ids encode time
36
+ * descending in a fixed-width field that wraps, so id order is NOT time order:
37
+ * a 2026-08 session can sort far below a newer one. Everything here compares
38
+ * ids the way SQLite does (BINARY / memcmp — neither `session.id` nor
39
+ * `session_v2.id` declares a collation) and never assumes id order means age.
40
+ */
41
+
42
+ import fs from 'node:fs';
43
+ import path from 'node:path';
44
+ import { createRequire } from 'node:module';
45
+ import { z } from 'zod';
46
+
47
+ import { resolveCredentialDbPath } from './credential-db.js';
48
+ import { OPENCODE_DATA_DIR } from './auth.js';
49
+
50
+ /** OpenCode's key for the migration state row in `kv`. */
51
+ const MIGRATION_STATE_KEY = 'migration.v1-v2';
52
+
53
+ /**
54
+ * The `session_message.type` values OpenCode's V1 transform can produce
55
+ * (`transformSession` emits only these four). Any other type on a session is
56
+ * proof the session was used in v2 after it was migrated.
57
+ */
58
+ const V1_IMPORT_MESSAGE_TYPES = ['user', 'assistant', 'synthetic', 'compaction'];
59
+
60
+ /**
61
+ * Appended to the largest missing id to build the cursor. SQLite compares TEXT
62
+ * byte by byte, so `<id><anything>` sorts immediately after `<id>`, and since
63
+ * every session id has the same length no real id can fall between the two.
64
+ * The cursor therefore selects exactly "this id and everything below it".
65
+ */
66
+ const CURSOR_SUFFIX = '￿';
67
+
68
+ /**
69
+ * The smallest cursor that still makes OpenCode's `id < cursor` loop visit
70
+ * `maxMissingId` itself.
71
+ * @param {string} maxMissingId
72
+ */
73
+ export const computeResumeCursor = (maxMissingId) => `${maxMissingId}${CURSOR_SUFFIX}`;
74
+
75
+ const kvRowSchema = z.object({ value: z.string(), time_updated: z.number() });
76
+ const idRowSchema = z.object({ id: z.string() });
77
+ const nameRowSchema = z.object({ name: z.string() });
78
+ const countRowSchema = z.object({ value: z.number() });
79
+ const nullableIdRowSchema = z.object({ id: z.string().nullable() });
80
+ const migrationStateSchema = z.object({ phase: z.string() });
81
+
82
+ /**
83
+ * A minimal read-write connection: `all(sql, params)`, `run(sql, params)` and
84
+ * `close()`. Node provides it through `node:sqlite`, Bun through `bun:sqlite`;
85
+ * both are built in and no dependency is added. Neither being available
86
+ * yields null and the top-up is skipped.
87
+ */
88
+ let openConnection;
89
+ const loadSqlite = () => {
90
+ if (openConnection !== undefined) return openConnection;
91
+ const require = createRequire(import.meta.url);
92
+ openConnection = null;
93
+ try {
94
+ const { DatabaseSync } = process.getBuiltinModule?.('node:sqlite') ?? require('node:sqlite');
95
+ // Opening an in-memory database proves the builtin is usable before we
96
+ // commit to it; a Node without `node:sqlite` throws here and Bun is tried.
97
+ new DatabaseSync(':memory:').close();
98
+ openConnection = (dbPath) => {
99
+ const db = new DatabaseSync(dbPath);
100
+ return {
101
+ all: (sql, params = []) => db.prepare(sql).all(...params),
102
+ run: (sql, params = []) => {
103
+ db.prepare(sql).run(...params);
104
+ },
105
+ close: () => db.close(),
106
+ };
107
+ };
108
+ return openConnection;
109
+ } catch {
110
+ // Not Node, or a Node without node:sqlite; try Bun next.
111
+ }
112
+ try {
113
+ const { Database } = require('bun:sqlite');
114
+ new Database(':memory:').close();
115
+ openConnection = (dbPath) => {
116
+ const db = new Database(dbPath);
117
+ return {
118
+ all: (sql, params = []) => db.query(sql).all(...params),
119
+ run: (sql, params = []) => {
120
+ db.query(sql).run(...params);
121
+ },
122
+ close: () => db.close(),
123
+ };
124
+ };
125
+ } catch {
126
+ // No sqlite runtime at all.
127
+ }
128
+ return openConnection;
129
+ };
130
+
131
+ const outcome = (status, reason, missing = 0, revisited = 0) =>
132
+ reason === undefined ? { status, missing, revisited } : { status, missing, revisited, reason };
133
+
134
+ const firstRow = (schema, rows) => {
135
+ const parsed = schema.safeParse(rows[0]);
136
+ return parsed.success ? parsed.data : null;
137
+ };
138
+
139
+ const hasTables = (db, names) => {
140
+ const rows = db.all(
141
+ `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${names.map(() => '?').join(', ')})`,
142
+ names,
143
+ );
144
+ const found = new Set(
145
+ rows.flatMap((row) => {
146
+ const parsed = nameRowSchema.safeParse(row);
147
+ return parsed.success ? [parsed.data.name] : [];
148
+ }),
149
+ );
150
+ return names.every((name) => found.has(name));
151
+ };
152
+
153
+ /**
154
+ * The completion timestamp of a `{"phase":"completed"}` migration row, or null
155
+ * when the row is missing, unreadable, or the migration is not finished.
156
+ */
157
+ const readCompletedMigration = (db) => {
158
+ const row = firstRow(kvRowSchema, db.all('SELECT value, time_updated FROM kv WHERE key = ?', [MIGRATION_STATE_KEY]));
159
+ if (!row) return null;
160
+ let decoded;
161
+ try {
162
+ decoded = JSON.parse(row.value);
163
+ } catch {
164
+ return null;
165
+ }
166
+ const state = migrationStateSchema.safeParse(decoded);
167
+ if (!state.success || state.data.phase !== 'completed') return null;
168
+ return { completedAt: row.time_updated };
169
+ };
170
+
171
+ /**
172
+ * Sessions OpenCode will revisit at this cursor that carry evidence of v2 use
173
+ * after the migration finished: a message created after completion, a session
174
+ * row touched after completion, or a message type the V1 transform never
175
+ * emits. Any hit means the top-up must not run.
176
+ */
177
+ const findRevisitedSessionsWithV2Activity = (db, cursor, completedAt) =>
178
+ db
179
+ .all(
180
+ `SELECT s.id AS id FROM session s
181
+ WHERE s.id < ?
182
+ AND s.id IN (SELECT id FROM session_v2)
183
+ AND (
184
+ EXISTS (SELECT 1 FROM session_v2 v WHERE v.id = s.id AND v.time_updated > ?)
185
+ OR EXISTS (
186
+ SELECT 1 FROM session_message m
187
+ WHERE m.session_id = s.id
188
+ AND (m.time_created > ? OR m.type NOT IN (${V1_IMPORT_MESSAGE_TYPES.map(() => '?').join(', ')}))
189
+ )
190
+ )
191
+ ORDER BY s.id DESC`,
192
+ [cursor, completedAt, completedAt, ...V1_IMPORT_MESSAGE_TYPES],
193
+ )
194
+ .flatMap((row) => {
195
+ const parsed = idRowSchema.safeParse(row);
196
+ return parsed.success ? [parsed.data.id] : [];
197
+ });
198
+
199
+ /** A legacy session with no `session_v2` twin, whether never imported or deleted in v2 (see rule 3). */
200
+ const MISSING_CONDITION = 'id NOT IN (SELECT id FROM session_v2)';
201
+
202
+ const countMissingSessions = (db) =>
203
+ firstRow(countRowSchema, db.all(`SELECT COUNT(*) AS value FROM session WHERE ${MISSING_CONDITION}`))?.value ?? 0;
204
+
205
+ const countRevisitedSessions = (db, cursor) =>
206
+ firstRow(
207
+ countRowSchema,
208
+ db.all('SELECT COUNT(*) AS value FROM session WHERE id < ? AND id IN (SELECT id FROM session_v2)', [cursor]),
209
+ )?.value ?? 0;
210
+
211
+ /**
212
+ * Schedule OpenCode's own V1 migration to pick up the V1 sessions that are
213
+ * missing from v2, when that can be done without touching a session that has
214
+ * v2 activity.
215
+ *
216
+ * Call this only for a MANAGED OpenCode, before it is spawned: writing the
217
+ * migration state under a running OpenCode would race its own loop, and an
218
+ * external OpenCode is not OpenChamber's to steer.
219
+ *
220
+ * @param {{ dbPath?: string, fileSystem?: typeof fs, logger?: Pick<Console, 'log' | 'warn'>, now?: () => number }} [options]
221
+ * `unsafe` names the one refusal, `revisited-sessions-have-v2-activity`: an
222
+ * already-migrated session under the cursor was used in v2 since.
223
+ *
224
+ * @returns {{ status: 'skipped' | 'scheduled' | 'unsafe' | 'unavailable', missing: number, revisited: number, reason?: string }}
225
+ */
226
+ export const topUpV1Migration = (options = {}) => {
227
+ const {
228
+ dbPath = resolveCredentialDbPath({ dataDir: OPENCODE_DATA_DIR, path }),
229
+ fileSystem = fs,
230
+ logger = console,
231
+ now = Date.now,
232
+ } = options;
233
+
234
+ const open = loadSqlite();
235
+ if (!open) return outcome('unavailable', 'no-sqlite-runtime');
236
+ if (!fileSystem.existsSync(dbPath)) return outcome('skipped', 'no-database');
237
+
238
+ let db;
239
+ try {
240
+ db = open(dbPath);
241
+ if (!hasTables(db, ['session', 'session_v2', 'session_message', 'kv'])) {
242
+ return outcome('skipped', 'no-v1-sessions');
243
+ }
244
+ const migration = readCompletedMigration(db);
245
+ if (!migration) return outcome('skipped', 'migration-not-completed');
246
+
247
+ const missingCount = countMissingSessions(db);
248
+ if (missingCount === 0) return outcome('skipped', 'nothing-missing');
249
+
250
+ const maxMissing = firstRow(
251
+ nullableIdRowSchema,
252
+ db.all(`SELECT MAX(id) AS id FROM session WHERE ${MISSING_CONDITION}`),
253
+ )?.id;
254
+ if (!maxMissing) return outcome('skipped', 'nothing-missing');
255
+
256
+ const cursor = computeResumeCursor(maxMissing);
257
+ const revisitedCount = countRevisitedSessions(db, cursor);
258
+ const unsafe = findRevisitedSessionsWithV2Activity(db, cursor, migration.completedAt);
259
+ if (unsafe.length > 0) {
260
+ logger.warn(
261
+ `[OpenCode] ${missingCount} OpenCode 1.x session(s) are missing from the v2 database, but importing them ` +
262
+ `would make OpenCode rewrite ${unsafe.length} already-migrated session(s) that have been used in v2 ` +
263
+ `since. Leaving the migration state untouched.`,
264
+ );
265
+ return outcome('unsafe', 'revisited-sessions-have-v2-activity', missingCount, revisitedCount);
266
+ }
267
+
268
+ db.run('UPDATE kv SET value = ?, time_updated = ? WHERE key = ?', [
269
+ JSON.stringify({ phase: 'sessions', cursor }),
270
+ now(),
271
+ MIGRATION_STATE_KEY,
272
+ ]);
273
+ logger.log(
274
+ `[OpenCode] Scheduled the import of ${missingCount} OpenCode 1.x session(s) created after the v2 migration; ` +
275
+ `OpenCode will re-import ${revisitedCount} untouched session(s) on the way.`,
276
+ );
277
+ return outcome('scheduled', undefined, missingCount, revisitedCount);
278
+ } catch (error) {
279
+ logger.warn('[OpenCode] Could not check the v1 session migration:', error instanceof Error ? error.message : error);
280
+ return outcome('unavailable', 'database-error');
281
+ } finally {
282
+ try {
283
+ db?.close();
284
+ } catch {
285
+ // Closing a database we are done with must never break startup.
286
+ }
287
+ }
288
+ };
@@ -0,0 +1,266 @@
1
+ import { afterEach, beforeEach, describe, expect, it } 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 { computeResumeCursor, topUpV1Migration } from './v1-migration-topup.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 MIGRATION_KEY = 'migration.v1-v2';
18
+ /** Stands in for "when OpenCode finished the migration". */
19
+ const COMPLETED_AT = 1_000_000;
20
+
21
+ let dir;
22
+ let dbPath;
23
+
24
+ const openDb = () => new sqlite.DatabaseSync(dbPath);
25
+
26
+ /**
27
+ * Build a database with the tables the top-up reads, in OpenCode's shape.
28
+ * `options.tables` drops tables to cover half-built databases.
29
+ */
30
+ const createDatabase = ({ tables = ['session', 'session_v2', 'session_message', 'kv', 'event'] } = {}) => {
31
+ const db = openDb();
32
+ if (tables.includes('session')) {
33
+ db.exec('CREATE TABLE session (id TEXT PRIMARY KEY, project_id TEXT, time_created INTEGER, time_updated INTEGER)');
34
+ }
35
+ if (tables.includes('session_v2')) {
36
+ db.exec('CREATE TABLE session_v2 (id TEXT PRIMARY KEY, time_created INTEGER, time_updated INTEGER)');
37
+ }
38
+ if (tables.includes('session_message')) {
39
+ db.exec(
40
+ 'CREATE TABLE session_message (id TEXT PRIMARY KEY, session_id TEXT, type TEXT, seq INTEGER, time_created INTEGER, time_updated INTEGER)',
41
+ );
42
+ }
43
+ if (tables.includes('kv')) {
44
+ db.exec('CREATE TABLE kv (key TEXT PRIMARY KEY, value TEXT NOT NULL, time_created INTEGER, time_updated INTEGER)');
45
+ }
46
+ if (tables.includes('event')) {
47
+ db.exec('CREATE TABLE event (id TEXT PRIMARY KEY, payload TEXT)');
48
+ }
49
+ db.close();
50
+ };
51
+
52
+ /**
53
+ * `v1` entries are ids or `{ id, timeCreated }`. A bare id stands for a session
54
+ * created after the migration completed (the case the top-up exists for); a
55
+ * session the migration already walked needs `timeCreated` before COMPLETED_AT.
56
+ */
57
+ const seed = ({ v1 = [], v2 = [], messages = [], migration = { phase: 'completed' } } = {}) => {
58
+ const db = openDb();
59
+ for (const entry of v1) {
60
+ const { id, timeCreated = COMPLETED_AT + 1 } = typeof entry === 'string' ? { id: entry } : entry;
61
+ db.prepare('INSERT INTO session (id, project_id, time_created, time_updated) VALUES (?, ?, ?, ?)').run(
62
+ id,
63
+ 'prj',
64
+ timeCreated,
65
+ timeCreated,
66
+ );
67
+ }
68
+ for (const session of v2) {
69
+ db.prepare('INSERT INTO session_v2 (id, time_created, time_updated) VALUES (?, ?, ?)').run(
70
+ session.id,
71
+ 1,
72
+ session.timeUpdated ?? COMPLETED_AT - 500,
73
+ );
74
+ }
75
+ for (const [index, message] of messages.entries()) {
76
+ db.prepare(
77
+ 'INSERT INTO session_message (id, session_id, type, seq, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?)',
78
+ ).run(`msg_${index}`, message.session, message.type, index, message.timeCreated ?? COMPLETED_AT - 500, 1);
79
+ }
80
+ if (migration) {
81
+ db.prepare('INSERT INTO kv (key, value, time_created, time_updated) VALUES (?, ?, ?, ?)').run(
82
+ MIGRATION_KEY,
83
+ JSON.stringify(migration),
84
+ 1,
85
+ COMPLETED_AT,
86
+ );
87
+ }
88
+ db.close();
89
+ };
90
+
91
+ const readMigrationRow = () => {
92
+ const db = openDb();
93
+ const row = db.prepare('SELECT value, time_updated FROM kv WHERE key = ?').all(MIGRATION_KEY)[0];
94
+ db.close();
95
+ return row ? { state: JSON.parse(row.value), timeUpdated: row.time_updated } : null;
96
+ };
97
+
98
+ const countEvents = () => {
99
+ const db = openDb();
100
+ const count = db.prepare('SELECT COUNT(*) AS value FROM event').all()[0].value;
101
+ db.close();
102
+ return count;
103
+ };
104
+
105
+ const run = () => topUpV1Migration({ dbPath, logger: { log: () => {}, warn: () => {} }, now: () => 2_000_000 });
106
+
107
+ beforeEach(() => {
108
+ dir = fs.mkdtempSync(path.join(os.tmpdir(), 'oc-topup-'));
109
+ dbPath = path.join(dir, 'opencode.db');
110
+ });
111
+
112
+ afterEach(() => {
113
+ fs.rmSync(dir, { recursive: true, force: true });
114
+ });
115
+
116
+ describe('computeResumeCursor', () => {
117
+ it('sorts immediately above the id it must reach', () => {
118
+ const cursor = computeResumeCursor('ses_b');
119
+ expect(cursor > 'ses_b').toBe(true);
120
+ // No same-length id can fall between the id and the cursor.
121
+ expect(cursor > 'ses_c').toBe(false);
122
+ });
123
+ });
124
+
125
+ describe.skipIf(!sqlite)('topUpV1Migration', () => {
126
+ it('skips when the database file does not exist', () => {
127
+ expect(run()).toEqual({ status: 'skipped', missing: 0, revisited: 0, reason: 'no-database' });
128
+ });
129
+
130
+ it('skips a database without the v1 session table', () => {
131
+ createDatabase({ tables: ['session_v2', 'session_message', 'kv', 'event'] });
132
+ seed({ v1: [] });
133
+ expect(run()).toEqual({ status: 'skipped', missing: 0, revisited: 0, reason: 'no-v1-sessions' });
134
+ });
135
+
136
+ it('skips when the migration row is absent, and never clears it', () => {
137
+ createDatabase();
138
+ seed({ v1: ['ses_a'], migration: null });
139
+ expect(run()).toEqual({ status: 'skipped', missing: 0, revisited: 0, reason: 'migration-not-completed' });
140
+ expect(readMigrationRow()).toBeNull();
141
+ });
142
+
143
+ it('skips while OpenCode is still migrating', () => {
144
+ createDatabase();
145
+ seed({ v1: ['ses_a'], migration: { phase: 'sessions', cursor: 'ses_b' } });
146
+ expect(run()).toEqual({ status: 'skipped', missing: 0, revisited: 0, reason: 'migration-not-completed' });
147
+ expect(readMigrationRow().state).toEqual({ phase: 'sessions', cursor: 'ses_b' });
148
+ });
149
+
150
+ it('does nothing when every v1 session is already in v2', () => {
151
+ createDatabase();
152
+ seed({ v1: ['ses_a', 'ses_b'], v2: [{ id: 'ses_a' }, { id: 'ses_b' }] });
153
+ expect(run()).toEqual({ status: 'skipped', missing: 0, revisited: 0, reason: 'nothing-missing' });
154
+ expect(readMigrationRow().state).toEqual({ phase: 'completed' });
155
+ });
156
+
157
+ it('schedules a cursor for missing sessions when nothing would be revisited', () => {
158
+ createDatabase();
159
+ seed({ v1: ['ses_c', 'ses_d'], v2: [] });
160
+ expect(run()).toEqual({ status: 'scheduled', missing: 2, revisited: 0 });
161
+ expect(readMigrationRow()).toEqual({
162
+ state: { phase: 'sessions', cursor: computeResumeCursor('ses_d') },
163
+ timeUpdated: 2_000_000,
164
+ });
165
+ });
166
+
167
+ it('schedules when the sessions it will revisit were never touched in v2', () => {
168
+ createDatabase();
169
+ seed({
170
+ v1: ['ses_a', 'ses_d'],
171
+ v2: [{ id: 'ses_a' }],
172
+ messages: [{ session: 'ses_a', type: 'user' }, { session: 'ses_a', type: 'assistant' }],
173
+ });
174
+ expect(run()).toEqual({ status: 'scheduled', missing: 1, revisited: 1 });
175
+ expect(readMigrationRow().state).toEqual({ phase: 'sessions', cursor: computeResumeCursor('ses_d') });
176
+ });
177
+
178
+ it('refuses when a revisited session gained a message after the migration finished', () => {
179
+ createDatabase();
180
+ seed({
181
+ v1: ['ses_a', 'ses_d'],
182
+ v2: [{ id: 'ses_a' }],
183
+ messages: [{ session: 'ses_a', type: 'user', timeCreated: COMPLETED_AT + 1 }],
184
+ });
185
+ expect(run()).toEqual({
186
+ status: 'unsafe',
187
+ missing: 1,
188
+ revisited: 1,
189
+ reason: 'revisited-sessions-have-v2-activity',
190
+ });
191
+ expect(readMigrationRow().state).toEqual({ phase: 'completed' });
192
+ });
193
+
194
+ it('refuses when a revisited session carries a message type the v1 import never produces', () => {
195
+ createDatabase();
196
+ seed({
197
+ v1: ['ses_a', 'ses_d'],
198
+ v2: [{ id: 'ses_a' }],
199
+ messages: [{ session: 'ses_a', type: 'shell' }],
200
+ });
201
+ expect(run().status).toBe('unsafe');
202
+ expect(readMigrationRow().state).toEqual({ phase: 'completed' });
203
+ });
204
+
205
+ it('refuses when a revisited session row was updated after the migration finished', () => {
206
+ createDatabase();
207
+ seed({ v1: ['ses_a', 'ses_d'], v2: [{ id: 'ses_a', timeUpdated: COMPLETED_AT + 10 }] });
208
+ expect(run().status).toBe('unsafe');
209
+ expect(readMigrationRow().state).toEqual({ phase: 'completed' });
210
+ });
211
+
212
+ it('handles wrapped session ids: the cursor comes from the largest missing id', () => {
213
+ // Real ids wrap, so a 2026-08 session (`ses_002f…`) sorts far below a
214
+ // newer one (`ses_f4b7…`). The migrated low session is below the cursor
215
+ // and therefore counted as revisited.
216
+ createDatabase();
217
+ seed({
218
+ v1: ['ses_002fb287fffe', 'ses_f4b70000aaaa'],
219
+ v2: [{ id: 'ses_002fb287fffe' }],
220
+ messages: [{ session: 'ses_002fb287fffe', type: 'user' }],
221
+ });
222
+ expect(run()).toEqual({ status: 'scheduled', missing: 1, revisited: 1 });
223
+ expect(readMigrationRow().state).toEqual({
224
+ phase: 'sessions',
225
+ cursor: computeResumeCursor('ses_f4b70000aaaa'),
226
+ });
227
+ });
228
+
229
+ it('imports a session deleted in v2 again rather than leaving newer 1.x sessions behind', () => {
230
+ // ses_a was walked by the migration and deleted in v2 (only the legacy row
231
+ // is left); ses_d was created after. The maintainer chose to bring ses_a
232
+ // back over losing ses_d: deleting again is cheap, a lost session is not.
233
+ createDatabase();
234
+ seed({ v1: [{ id: 'ses_a', timeCreated: COMPLETED_AT - 1 }, 'ses_d'] });
235
+ expect(run()).toEqual({ status: 'scheduled', missing: 2, revisited: 0 });
236
+ expect(readMigrationRow().state).toEqual({ phase: 'sessions', cursor: computeResumeCursor('ses_d') });
237
+ });
238
+
239
+ it('never touches the durable event log', () => {
240
+ createDatabase();
241
+ const db = openDb();
242
+ db.prepare('INSERT INTO event (id, payload) VALUES (?, ?)').run('evt_1', '{}');
243
+ db.close();
244
+ seed({ v1: ['ses_d'] });
245
+ expect(run().status).toBe('scheduled');
246
+ expect(countEvents()).toBe(1);
247
+ });
248
+
249
+ it('skips on a second run, once OpenCode has imported the sessions', () => {
250
+ createDatabase();
251
+ seed({ v1: ['ses_d'] });
252
+ expect(run().status).toBe('scheduled');
253
+
254
+ // OpenCode runs, imports ses_d and marks the migration completed again.
255
+ const db = openDb();
256
+ db.prepare('INSERT INTO session_v2 (id, time_created, time_updated) VALUES (?, ?, ?)').run('ses_d', 1, 2_000_100);
257
+ db.prepare('UPDATE kv SET value = ?, time_updated = ? WHERE key = ?').run(
258
+ JSON.stringify({ phase: 'completed' }),
259
+ 2_000_200,
260
+ MIGRATION_KEY,
261
+ );
262
+ db.close();
263
+
264
+ expect(run()).toEqual({ status: 'skipped', missing: 0, revisited: 0, reason: 'nothing-missing' });
265
+ });
266
+ });
@@ -0,0 +1,8 @@
1
+ export function supportsOpenCodeV2Install(platform?: NodeJS.Platform): boolean;
2
+ export function installOpenCodeV2(options?: {
3
+ homeDirectory?: string;
4
+ env?: NodeJS.ProcessEnv;
5
+ fetchImpl?: typeof fetch;
6
+ platform?: NodeJS.Platform;
7
+ tarCommand?: string;
8
+ }): Promise<string>;