@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
@@ -2,9 +2,9 @@ import { describe, expect, it, vi } from 'vitest';
2
2
  import { createRoutingRuntime, requestTextOf } from './runtime.js';
3
3
  import { resolveEffectiveConfig } from './store.js';
4
4
  import { excerptHead, excerptHeadTail, turnsToHistory } from './history.js';
5
- import { decidePermission, decideRouting } from './jev.js';
5
+ import { createJevClient, decidePermission, decideRouting } from './jev.js';
6
6
 
7
- const AUTO = { providerID: 'openchamber', modelID: 'auto' };
7
+ const AUTO = { providerID: 'openchamber', id: 'auto' };
8
8
  const FALLBACK = { model: { providerID: 'anthropic', modelID: 'claude-sonnet-5' }, variant: 'medium' };
9
9
 
10
10
  const readyConfig = () => {
@@ -18,8 +18,7 @@ const readyConfig = () => {
18
18
  return config;
19
19
  };
20
20
 
21
- const makeRuntime = ({ config = readyConfig(), token = 'key', answers, askError, flag = '1' } = {}) => {
22
- process.env.OPENCHAMBER_ROUTING_ENABLE = flag;
21
+ const makeRuntime = ({ config = readyConfig(), token = 'key', answers, askError } = {}) => {
23
22
  const events = [];
24
23
  const store = {
25
24
  readConfig: vi.fn(async () => config),
@@ -41,16 +40,15 @@ const makeRuntime = ({ config = readyConfig(), token = 'key', answers, askError,
41
40
  };
42
41
 
43
42
  describe('requestTextOf', () => {
44
- it('joins authored text parts and ignores synthetic ones and files', () => {
45
- expect(requestTextOf({ parts: [
46
- { type: 'text', text: 'fix the typo' },
47
- { type: 'text', text: 'injected', synthetic: true },
48
- { type: 'file', url: 'data:...' },
49
- { type: 'text', text: 'in README' },
50
- ] })).toBe('fix the typo\n\nin README');
43
+ it('reads the prompt text a v2 send carries', () => {
44
+ expect(requestTextOf({ text: ' fix the typo in README ', files: [{ uri: 'data:...' }] })).toBe('fix the typo in README');
51
45
  });
52
- it('renders a slash command with its arguments', () => {
53
- expect(requestTextOf({ command: 'review', arguments: ' 3650 ' })).toBe('/review 3650');
46
+ it('renders a v2 command body (`name` plus `text`) as the slash command', () => {
47
+ expect(requestTextOf({ name: 'review', text: ' src ' })).toBe('/review src');
48
+ });
49
+ it('keeps the command name when it has no arguments', () => {
50
+ expect(requestTextOf({ name: 'review', text: '' })).toBe('/review');
51
+ expect(requestTextOf({ name: 'review' })).toBe('/review');
54
52
  });
55
53
  });
56
54
 
@@ -85,63 +83,91 @@ describe('decisions', () => {
85
83
  });
86
84
  });
87
85
 
88
- describe('resolvePromptBody', () => {
86
+ describe('resolveAutoSelection', () => {
87
+ const send = (extra = {}) => ({ sessionId: 's1', model: AUTO, requestText: 'find the root cause', ...extra });
88
+
89
89
  it('leaves a real model untouched and does not consult Jev', async () => {
90
90
  const { runtime, jev } = makeRuntime({ answers: {} });
91
- const body = { model: { providerID: 'anthropic', modelID: 'claude-opus-5' }, parts: [] };
92
- expect(await runtime.resolvePromptBody(body, { sessionId: 's1' })).toBeNull();
93
- expect(body.model.modelID).toBe('claude-opus-5');
91
+ expect(await runtime.resolveAutoSelection(send({ model: { providerID: 'anthropic', id: 'claude-opus-5' } }))).toBeNull();
94
92
  expect(jev.ask).not.toHaveBeenCalled();
95
93
  });
96
94
 
97
- it('rewrites the sentinel with the routed category model, variant and agent', async () => {
95
+ it('answers with the routed category model, variant and agent', async () => {
98
96
  const { runtime, events } = makeRuntime({ answers: { category: { choice: 'hard', confidence: 0.97 } } });
99
- const body = { model: AUTO, variant: 'low', agent: 'build', parts: [{ type: 'text', text: 'find the root cause' }] };
100
- const decision = await runtime.resolvePromptBody(body, { sessionId: 's1' });
101
- expect(body).toMatchObject({ model: { providerID: 'openai', modelID: 'gpt-6-astra' }, variant: 'high', agent: 'plan' });
102
- expect(decision).toMatchObject({ category: 'hard', reason: 'routed', confidence: 0.97 });
97
+ const resolved = await runtime.resolveAutoSelection(send({ agent: 'build' }));
98
+ expect(resolved.model).toEqual({ providerID: 'openai', id: 'gpt-6-astra', variant: 'high' });
99
+ expect(resolved.agent).toBe('plan');
100
+ expect(resolved.decision).toMatchObject({ category: 'hard', reason: 'routed', confidence: 0.97 });
103
101
  expect(events.at(-1)).toMatchObject({ type: 'openchamber:routing.decision', properties: { sessionId: 's1', category: 'hard' } });
104
102
  });
105
103
 
106
- it('rewrites the string sentinel the command route sends, keeping the string shape', async () => {
107
- const { runtime } = makeRuntime({ answers: { category: { choice: 'hard', confidence: 0.97 } } });
108
- const body = { model: 'openchamber/auto', command: 'review', arguments: '3650' };
109
- await runtime.resolvePromptBody(body, { sessionId: 's1' });
110
- expect(body.model).toBe('openai/gpt-6-astra');
111
- expect(body.agent).toBe('plan');
112
- });
113
-
114
- it('uses the fallback and keeps the composer agent when the category has no model', async () => {
104
+ it('uses the fallback pair and keeps the composer agent when the category has no model', async () => {
115
105
  const { runtime } = makeRuntime({ answers: { category: { choice: 'trivial', confidence: 0.99 } } });
116
- const body = { model: AUTO, variant: 'high', agent: 'build', parts: [{ type: 'text', text: 'fix typo' }] };
117
- await runtime.resolvePromptBody(body, { sessionId: 's1' });
118
- expect(body).toMatchObject({ model: FALLBACK.model, variant: 'medium', agent: 'build' });
106
+ const resolved = await runtime.resolveAutoSelection(send({ agent: 'build', requestText: 'fix typo' }));
107
+ expect(resolved.model).toEqual({ providerID: 'anthropic', id: 'claude-sonnet-5', variant: 'medium' });
108
+ expect(resolved.agent).toBe('build');
119
109
  });
120
110
 
121
111
  it('falls back on low confidence and on a Jev failure, and records why', async () => {
122
112
  const low = makeRuntime({ answers: { category: { choice: 'hard', confidence: 0.3 } } });
123
- const body = { model: AUTO, parts: [] };
124
- expect((await low.runtime.resolvePromptBody(body, { sessionId: 's1' })).reason).toBe('low-confidence');
125
- expect(body.model).toEqual(FALLBACK.model);
113
+ const lowResolved = await low.runtime.resolveAutoSelection(send());
114
+ expect(lowResolved.decision.reason).toBe('low-confidence');
115
+ expect(lowResolved.model).toMatchObject({ providerID: 'anthropic', id: 'claude-sonnet-5' });
126
116
 
127
117
  const failing = makeRuntime({ askError: Object.assign(new Error('Jev responded 401'), { status: 401 }) });
128
- const body2 = { model: AUTO, parts: [] };
129
- const decision = await failing.runtime.resolvePromptBody(body2, { sessionId: 's1' });
130
- expect(decision).toMatchObject({ reason: 'error', error: 'Jev responded 401' });
131
- expect(body2.model).toEqual(FALLBACK.model);
118
+ const resolved = await failing.runtime.resolveAutoSelection(send());
119
+ expect(resolved.decision).toMatchObject({ reason: 'error', error: 'Jev responded 401' });
120
+ expect(resolved.model).toMatchObject({ providerID: 'anthropic', id: 'claude-sonnet-5' });
132
121
  });
133
122
 
134
123
  it('falls back without asking Jev while Auto is not ready, and refuses without a fallback', async () => {
135
124
  const config = readyConfig();
136
125
  config.enabled = false;
137
126
  const notReady = makeRuntime({ config, answers: {} });
138
- const body = { model: AUTO, parts: [] };
139
- expect((await notReady.runtime.resolvePromptBody(body, { sessionId: 's1' })).reason).toBe('not-ready');
140
- expect(body.model).toEqual(FALLBACK.model);
127
+ const resolved = await notReady.runtime.resolveAutoSelection(send());
128
+ expect(resolved.decision.reason).toBe('not-ready');
129
+ expect(resolved.model).toMatchObject({ providerID: 'anthropic', id: 'claude-sonnet-5' });
141
130
  expect(notReady.jev.ask).not.toHaveBeenCalled();
142
131
 
143
132
  const noFallback = makeRuntime({ config: { ...readyConfig(), fallback: null }, answers: {} });
144
- await expect(noFallback.runtime.resolvePromptBody({ model: AUTO, parts: [] }, { sessionId: 's1' })).rejects.toMatchObject({ status: 400 });
133
+ await expect(noFallback.runtime.resolveAutoSelection(send())).rejects.toMatchObject({ status: 400 });
134
+ });
135
+ });
136
+
137
+ describe('jev endpoint', () => {
138
+ const capture = async (token) => {
139
+ let call = null;
140
+ const fetchImpl = async (url, init) => {
141
+ call = { url, init };
142
+ return { ok: true, status: 200, text: async () => JSON.stringify({ answers: {} }) };
143
+ };
144
+ await createJevClient({ fetchImpl }).ask({ state: 'x', questions: {} }, token);
145
+ return { url: call.url, headers: call.init.headers, body: JSON.parse(call.init.body) };
146
+ };
147
+
148
+ it('sends a saved key to TypeSafe and falls back to the free model zen serves without one', async () => {
149
+ const keyed = await capture('secret');
150
+ expect(keyed.url).toBe('https://api.typesafe.ai/v1/systemone');
151
+ expect(keyed.headers.authorization).toBe('Bearer secret');
152
+ expect(keyed.body.model).toBe('jev-latest');
153
+
154
+ const free = await capture(null);
155
+ expect(free.url).toBe('https://opencode.ai/zen/v1/systemone');
156
+ expect(free.headers.authorization).toBeUndefined();
157
+ // Zen counts our calls by this header, and does not know the `jev-latest` alias.
158
+ expect(free.headers['x-opencode-client']).toBe('openchamber');
159
+ expect(free.body.model).toBe('jev-1.13-free');
160
+ });
161
+ });
162
+
163
+ describe('auto sessions', () => {
164
+ it('remembers the sentinel selection and forgets it when a real model is chosen', () => {
165
+ const { runtime } = makeRuntime({ answers: {} });
166
+ expect(runtime.isAutoSession('s1')).toBe(false);
167
+ expect(runtime.noteModelSelection('s1', AUTO, '/repo')).toBe(true);
168
+ expect(runtime.isAutoSession('s1')).toBe(true);
169
+ expect(runtime.noteModelSelection('s1', { providerID: 'anthropic', id: 'claude-opus-5' }, '/repo')).toBe(false);
170
+ expect(runtime.isAutoSession('s1')).toBe(false);
145
171
  });
146
172
  });
147
173
 
@@ -165,24 +191,22 @@ describe('evaluatePermission', () => {
165
191
  expect(events.at(-1)).toMatchObject({ type: 'openchamber:routing.safety-skipped', properties: { permissionId: 'p1', error: 'Jev timed out after 4000ms' } });
166
192
  });
167
193
 
168
- it('accepts without asking when the safety net is off, the key is missing, or the flag is unset', async () => {
194
+ it('accepts without asking when the safety net is off', async () => {
169
195
  const off = readyConfig();
170
196
  off.safetyNet.enabled = false;
171
- for (const setup of [{ config: off }, { token: null }, { flag: '' }]) {
172
- const { runtime, jev } = makeRuntime({ ...setup, answers: {} });
173
- expect(await runtime.evaluatePermission(permission, '/repo')).toEqual({ action: 'accept' });
174
- expect(jev.ask).not.toHaveBeenCalled();
175
- }
197
+ const { runtime, jev } = makeRuntime({ config: off, answers: {} });
198
+ expect(await runtime.evaluatePermission(permission, '/repo')).toEqual({ action: 'accept' });
199
+ expect(jev.ask).not.toHaveBeenCalled();
176
200
  });
177
201
  });
178
202
 
179
203
  describe('describe', () => {
180
- it('reports Auto ready only with the flag, enabled config, key, fallback and two categories', async () => {
181
- expect((await makeRuntime({ answers: {} }).runtime.describe()).autoReady).toBe(true);
182
- expect((await makeRuntime({ token: null, answers: {} }).runtime.describe())).toMatchObject({ autoReady: false, tokenPresent: false });
204
+ it('reports Auto ready with an enabled config, a fallback and two categories, key or no key', async () => {
205
+ expect((await makeRuntime({ answers: {} }).runtime.describe())).toMatchObject({ autoReady: true, tokenPresent: true, jevSource: 'typesafe' });
206
+ // Without a key the free Jev model on zen answers, so Auto stays available.
207
+ expect((await makeRuntime({ token: null, answers: {} }).runtime.describe())).toMatchObject({ autoReady: true, tokenPresent: false, jevSource: 'zen-free' });
183
208
  const one = readyConfig();
184
209
  one.categories = one.categories.map((c, i) => ({ ...c, enabled: i === 0 }));
185
210
  expect((await makeRuntime({ config: one, answers: {} }).runtime.describe()).autoReady).toBe(false);
186
- expect(await makeRuntime({ flag: '', answers: {} }).runtime.describe()).toEqual({ available: false, autoReady: false, tokenPresent: false, config: null, builtins: [] });
187
211
  });
188
212
  });
@@ -188,3 +188,7 @@ project write lock on every `syncProject` when the project path is known:
188
188
  - `POST /api/projects/:projectId/scheduled-tasks/:taskId/run`
189
189
  - `GET /api/openchamber/scheduled-tasks/status`
190
190
  - `GET /api/openchamber/events`
191
+
192
+ The shared `/api/openchamber/events` stream also carries web notifications.
193
+ Its connection ownership and browser capability flag stay unchanged. Delivery
194
+ and duplicate handling are documented in `../notifications/DOCUMENTATION.md`.
@@ -14,25 +14,33 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
14
14
  * before creating a session, so the second instance skips.
15
15
  */
16
16
 
17
- const sdk = vi.hoisted(() => ({
18
- sessionCreates: [],
19
- createOpencodeClient: () => ({
20
- session: {
21
- create: async () => {
22
- sdk.sessionCreates.push(Date.now());
23
- return { data: { id: `sess-${sdk.sessionCreates.length}` } };
24
- },
25
- },
26
- command: { list: async () => ({ data: [] }) },
27
- }),
28
- }));
29
-
30
- vi.mock('@opencode-ai/sdk/v2', () => ({
31
- createOpencodeClient: sdk.createOpencodeClient,
32
- }));
33
-
34
17
  import { createScheduledTasksRuntime } from './runtime.js';
35
18
 
19
+ const opencode = { sessionCreates: [] };
20
+
21
+ const jsonResponse = (body) => new Response(JSON.stringify(body), {
22
+ status: 200,
23
+ headers: { 'content-type': 'application/json' },
24
+ });
25
+
26
+ /**
27
+ * Serves the few v2 routes a scheduled run touches so the test drives the real
28
+ * @opencode/client over its own transport instead of replacing the module.
29
+ * `gate` holds the prompt open the way a long-running dispatch would.
30
+ */
31
+ const installOpenCodeFetch = (gate = null) => {
32
+ globalThis.fetch = vi.fn(async (input) => {
33
+ const { pathname } = new URL(input);
34
+ if (pathname === '/api/session') {
35
+ opencode.sessionCreates.push(Date.now());
36
+ return jsonResponse({ data: { id: `sess-${opencode.sessionCreates.length}` } });
37
+ }
38
+ if (pathname === '/api/command') return jsonResponse({ data: [] });
39
+ if (gate) await gate;
40
+ return jsonResponse({ data: {} });
41
+ });
42
+ };
43
+
36
44
  const UTC = (y, mo, d, h, mi, s = 0) => Date.UTC(y, mo, d, h, mi, s);
37
45
  const MINUTE = 60_000;
38
46
  const HOUR = 3_600_000;
@@ -118,8 +126,8 @@ const startInstances = async (count, task) => {
118
126
  describe('issue 2710: daily scheduled task double execution at the configured time', () => {
119
127
  beforeEach(() => {
120
128
  vi.useFakeTimers();
121
- sdk.sessionCreates.length = 0;
122
- globalThis.fetch = vi.fn(async () => ({ ok: true, text: async () => '' }));
129
+ opencode.sessionCreates.length = 0;
130
+ installOpenCodeFetch();
123
131
  });
124
132
 
125
133
  afterEach(() => {
@@ -132,8 +140,8 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
132
140
  const { runtimes } = await startInstances(1, makeTask({ kind: 'daily', times: ['15:00'] }));
133
141
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
134
142
 
135
- expect(sdk.sessionCreates.length).toBe(1);
136
- const firedAt = new Date(sdk.sessionCreates[0]);
143
+ expect(opencode.sessionCreates.length).toBe(1);
144
+ const firedAt = new Date(opencode.sessionCreates[0]);
137
145
  expect(firedAt.getUTCHours()).toBe(15);
138
146
 
139
147
  runtimes.forEach((runtime) => runtime.stop());
@@ -144,9 +152,9 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
144
152
  const { runtimes } = await startInstances(1, makeTask({ kind: 'daily', times: ['15:00'] }));
145
153
  await vi.advanceTimersByTimeAsync((4 * 24 * HOUR) + 3_000);
146
154
 
147
- expect(sdk.sessionCreates.length).toBe(4);
155
+ expect(opencode.sessionCreates.length).toBe(4);
148
156
  const byHourBucket = new Map();
149
- for (const timestamp of sdk.sessionCreates) {
157
+ for (const timestamp of opencode.sessionCreates) {
150
158
  const date = new Date(timestamp);
151
159
  expect(date.getUTCHours()).toBe(15);
152
160
  expect(date.getUTCMinutes()).toBe(0);
@@ -168,8 +176,8 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
168
176
  );
169
177
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
170
178
 
171
- expect(sdk.sessionCreates.length).toBe(1);
172
- const firedAt = new Date(sdk.sessionCreates[0]);
179
+ expect(opencode.sessionCreates.length).toBe(1);
180
+ const firedAt = new Date(opencode.sessionCreates[0]);
173
181
  expect(firedAt.getUTCHours()).toBe(15);
174
182
  expect(firedAt.getUTCMinutes()).toBe(0);
175
183
  expect(projectConfigRuntime.updateScheduledTaskStateIf).toHaveBeenCalled();
@@ -187,7 +195,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
187
195
  }));
188
196
  await vi.advanceTimersByTimeAsync((25 * HOUR) + 3_000);
189
197
 
190
- expect(sdk.sessionCreates.length).toBe(1);
198
+ expect(opencode.sessionCreates.length).toBe(1);
191
199
 
192
200
  runtimes.forEach((runtime) => runtime.stop());
193
201
  });
@@ -197,7 +205,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
197
205
  const { runtimes } = await startInstances(2, makeTask({ kind: 'cron', cron: '*/5 * * * *' }));
198
206
  await vi.advanceTimersByTimeAsync(3 * MINUTE);
199
207
 
200
- expect(sdk.sessionCreates.length).toBe(1);
208
+ expect(opencode.sessionCreates.length).toBe(1);
201
209
 
202
210
  runtimes.forEach((runtime) => runtime.stop());
203
211
  });
@@ -211,7 +219,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
211
219
  }));
212
220
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
213
221
 
214
- expect(sdk.sessionCreates.length).toBe(1);
222
+ expect(opencode.sessionCreates.length).toBe(1);
215
223
 
216
224
  runtimes.forEach((runtime) => runtime.stop());
217
225
  });
@@ -229,14 +237,14 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
229
237
 
230
238
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
231
239
 
232
- expect(sdk.sessionCreates.length).toBe(0);
240
+ expect(opencode.sessionCreates.length).toBe(0);
233
241
  expect(runtime.getStatus().runningScheduledTasksCount).toBe(0);
234
242
  expect(runtime.getStatus().hasRunningScheduledTasks).toBe(false);
235
243
 
236
244
  // Manual runNow must not be stuck behind a permanently "running" claim failure.
237
245
  const manual = await runtime.runNow('p1', 'task-1');
238
246
  expect(manual.ok).toBe(true);
239
- expect(sdk.sessionCreates.length).toBe(1);
247
+ expect(opencode.sessionCreates.length).toBe(1);
240
248
 
241
249
  runtime.stop();
242
250
  });
@@ -263,7 +271,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
263
271
 
264
272
  // Initial completion write + best-effort retry.
265
273
  expect(completionWrites).toBeGreaterThanOrEqual(2);
266
- expect(sdk.sessionCreates.length).toBe(1);
274
+ expect(opencode.sessionCreates.length).toBe(1);
267
275
  expect(runtime.getStatus().runningScheduledTasksCount).toBe(0);
268
276
 
269
277
  const manual = await runtime.runNow('p1', 'task-1');
@@ -316,7 +324,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
316
324
  const manual = await runtime.runNow('p1', 'task-1');
317
325
  expect(manual.ok).toBe(false);
318
326
  expect(manual.reason).toBe('start-state-failed');
319
- expect(sdk.sessionCreates.length).toBe(0);
327
+ expect(opencode.sessionCreates.length).toBe(0);
320
328
  expect(runtime.getStatus().runningScheduledTasksCount).toBe(0);
321
329
 
322
330
  runtime.stop();
@@ -340,7 +348,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
340
348
 
341
349
  await vi.advanceTimersByTimeAsync(5_000);
342
350
 
343
- expect(sdk.sessionCreates.length).toBe(1);
351
+ expect(opencode.sessionCreates.length).toBe(1);
344
352
 
345
353
  runtimeA.stop();
346
354
  runtimeB.stop();
@@ -356,7 +364,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
356
364
  const runtimeA = createScheduledTasksRuntime(createRuntimeDeps(projectConfigRuntime));
357
365
  await runtimeA.start();
358
366
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
359
- expect(sdk.sessionCreates.length).toBe(1);
367
+ expect(opencode.sessionCreates.length).toBe(1);
360
368
 
361
369
  // Advance to day-2 afternoon, still outside slack, so A re-arms for 15:00.
362
370
  await vi.advanceTimersByTimeAsync((23 * HOUR) - 3_000);
@@ -367,7 +375,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
367
375
 
368
376
  await vi.advanceTimersByTimeAsync(5_000);
369
377
 
370
- expect(sdk.sessionCreates.length).toBe(2);
378
+ expect(opencode.sessionCreates.length).toBe(2);
371
379
 
372
380
  runtimeA.stop();
373
381
  runtimeB.stop();
@@ -380,10 +388,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
380
388
  const fetchGate = new Promise((resolve) => {
381
389
  releaseFetch = resolve;
382
390
  });
383
- globalThis.fetch = vi.fn(async () => {
384
- await fetchGate;
385
- return { ok: true, text: async () => '' };
386
- });
391
+ installOpenCodeFetch(fetchGate);
387
392
 
388
393
  const { runtimes, projectConfigRuntime } = await startInstances(2, makeTask({
389
394
  kind: 'once',
@@ -393,21 +398,21 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
393
398
 
394
399
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
395
400
 
396
- // Winner claimed and is blocked in prompt_async; exactly one session.
397
- expect(sdk.sessionCreates.length).toBe(1);
401
+ // Winner claimed and is blocked in the prompt dispatch; exactly one session.
402
+ expect(opencode.sessionCreates.length).toBe(1);
398
403
  const claimsAfterFire = projectConfigRuntime.updateScheduledTaskStateIf.mock.calls.length;
399
404
  expect(claimsAfterFire).toBeGreaterThanOrEqual(1);
400
405
 
401
406
  // Advance through many jitter windows. Loser must not keep re-entering claim.
402
407
  await vi.advanceTimersByTimeAsync(60_000);
403
408
  expect(projectConfigRuntime.updateScheduledTaskStateIf.mock.calls.length).toBe(claimsAfterFire);
404
- expect(sdk.sessionCreates.length).toBe(1);
409
+ expect(opencode.sessionCreates.length).toBe(1);
405
410
 
406
411
  releaseFetch();
407
412
  await Promise.resolve();
408
413
  await vi.advanceTimersByTimeAsync(5_000);
409
414
 
410
- expect(sdk.sessionCreates.length).toBe(1);
415
+ expect(opencode.sessionCreates.length).toBe(1);
411
416
  expect(runtimes.every((runtime) => runtime.getStatus().runningScheduledTasksCount === 0)).toBe(true);
412
417
 
413
418
  runtimes.forEach((runtime) => runtime.stop());
@@ -467,7 +472,7 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
467
472
  await runtime.start();
468
473
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
469
474
 
470
- expect(sdk.sessionCreates.length).toBe(0);
475
+ expect(opencode.sessionCreates.length).toBe(0);
471
476
  const tasks = await projectConfigRuntime.listScheduledTasks('p1');
472
477
  expect(tasks[0].state.lastStatus).toBe('error');
473
478
  expect(tasks[0].state.lastError).toMatch(/Scheduled claim failed/);
@@ -496,12 +501,12 @@ describe('issue 2710: daily scheduled task double execution at the configured ti
496
501
 
497
502
  // The scheduler must not fire a disabled task on its own.
498
503
  await vi.advanceTimersByTimeAsync(HOUR + 3_000);
499
- expect(sdk.sessionCreates.length).toBe(0);
504
+ expect(opencode.sessionCreates.length).toBe(0);
500
505
 
501
506
  const manual = await runtime.runNow('p1', 'task-1');
502
507
  expect(manual.ok).toBe(true);
503
508
  expect(manual.sessionID).toBeTruthy();
504
- expect(sdk.sessionCreates.length).toBe(1);
509
+ expect(opencode.sessionCreates.length).toBe(1);
505
510
  expect(runtime.getStatus().runningScheduledTasksCount).toBe(0);
506
511
 
507
512
  // Completion records state but leaves the task paused with no next run.