@vantran-se/openchamber-web 1.24.4 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (367) hide show
  1. package/README.md +2 -8
  2. package/bin/lib/commands-connect-url.js +3 -8
  3. package/bin/lib/commands-tunnel.js +3 -0
  4. package/dist/assets/{CodeMirrorCommentWidgets-B4a0JkUz.js → CodeMirrorCommentWidgets-B82Oye3V.js} +1 -1
  5. package/dist/assets/DiffView-DJLmWn6Z.js +4 -0
  6. package/dist/assets/FilesView-DjksmZRo.js +17 -0
  7. package/dist/assets/{GitView-Byu2DY7s.js → GitView-DiUsUIgr.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-CYwWYLr3.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-CmctyRI4.js +1 -0
  10. package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-DOHHcbgJ.js} +1 -1
  11. package/dist/assets/LinearIssuesView-DaqnylcV.js +1 -0
  12. package/dist/assets/MarkdownImageGallery-HJNvWt7L.js +1 -0
  13. package/dist/assets/MarkdownRendererImpl-nDZRGg2I.js +12 -0
  14. package/dist/assets/OnboardingScreen-C2B5Bsof.js +1 -0
  15. package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-C4FLBjpt.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-C_1S9c9n.js} +1 -1
  17. package/dist/assets/PlanView-_eZvb-G5.js +5 -0
  18. package/dist/assets/PluginPane-sM8fcZIV.js +3 -0
  19. package/dist/assets/SettingsView-BRfr46hb.js +29 -0
  20. package/dist/assets/SettingsWindow-CcUDwQHg.js +1 -0
  21. package/dist/assets/SubmoduleDiffSummary-CrN1ZGwl.js +1 -0
  22. package/dist/assets/ToolOutputDialog-Cij4jI09.js +15 -0
  23. package/dist/assets/ToolPartDiffPreview-BXzwQ-JY.js +8 -0
  24. package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-DSWTLJNH.js} +4 -4
  25. package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-CpUUgY8H.js} +2 -2
  26. package/dist/assets/appThemeRegistry-B5tHTkR8.js +1 -0
  27. package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-D7D8qV8p.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-cq9uQ30i.js} +1 -1
  29. package/dist/assets/de-CUO1-KqF.js +10 -0
  30. package/dist/assets/es-BlbC6sGJ.js +16 -0
  31. package/dist/assets/fr-D2nwe_0W.js +14 -0
  32. package/dist/assets/index-BQOoYIOu.js +1 -0
  33. package/dist/assets/index-CLcM8HKx.css +1 -0
  34. package/dist/assets/index-CvLgnuNI.js +41 -0
  35. package/dist/assets/{isDiffPlainText-CO4e5J_S.js → isDiffPlainText-D0obJ2p8.js} +1 -1
  36. package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-DB3_rjyW.js} +1 -1
  37. package/dist/assets/ja-CA7vnDsZ.js +16 -0
  38. package/dist/assets/ko-Chy6a9l-.js +16 -0
  39. package/dist/assets/main-B9JFK3vF.js +896 -0
  40. package/dist/assets/main-Bhiqnrhd.js +2 -0
  41. package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-tfqOFaBd.js} +4 -4
  42. package/dist/assets/miniChat-B0UOL5zA.js +2 -0
  43. package/dist/assets/mobile-BJppFIEo.js +2 -0
  44. package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-Db6KuwCP.js} +3 -3
  45. package/dist/assets/pl-CTR5A_1P.js +16 -0
  46. package/dist/assets/pt-BR-BE1O1KEx.js +16 -0
  47. package/dist/assets/renderElectronMiniChatApp-Yz5sfoFh.js +2 -0
  48. package/dist/assets/renderMobileApp-QQ-yB8aY.js +25 -0
  49. package/dist/assets/runtimeEndpointReset-DKDdzRzm.js +27 -0
  50. package/dist/assets/scheduledTasksApi-C5sCuNqf.js +1 -0
  51. package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-xURoo5t_.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-BZLGuYTU.js} +1 -1
  53. package/dist/assets/tr-Bh5vK6cW.js +7 -0
  54. package/dist/assets/uk-CLDkZFy_.js +16 -0
  55. package/dist/assets/useAppFontEffects-BxrtCnfR.js +7337 -0
  56. package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CeXuR-SR.js} +2 -2
  57. package/dist/assets/useRootScrollLock-DgWxezc1.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-DOuaQUZ0.js} +2 -2
  59. package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
  60. package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
  61. package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
  62. package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
  63. package/dist/assets/{web-components-DxX8bZ1o.js → web-components-63H1h21j.js} +2 -2
  64. package/dist/assets/zh-CN-DODgcmYj.js +16 -0
  65. package/dist/assets/zh-TW-ByNfIwLA.js +16 -0
  66. package/dist/index.html +13 -7
  67. package/dist/mini-chat.html +5 -5
  68. package/dist/mobile.html +5 -5
  69. package/package.json +6 -5
  70. package/server/index.d.ts +1 -0
  71. package/server/index.js +246 -91
  72. package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
  73. package/server/lib/agent-tool/runtime.js +135 -54
  74. package/server/lib/agent-tool/runtime.test.js +155 -87
  75. package/server/lib/browser-control/DOCUMENTATION.md +37 -2
  76. package/server/lib/browser-control/provider.js +217 -0
  77. package/server/lib/browser-control/provider.test.js +251 -0
  78. package/server/lib/context-obligatory/runtime.js +61 -50
  79. package/server/lib/context-obligatory/runtime.test.js +62 -162
  80. package/server/lib/event-stream/DOCUMENTATION.md +12 -1
  81. package/server/lib/event-stream/delta-coalescer.js +0 -0
  82. package/server/lib/event-stream/delta-coalescer.test.js +68 -57
  83. package/server/lib/event-stream/directory-ws-bridge.js +1 -1
  84. package/server/lib/event-stream/global-hub.js +10 -1
  85. package/server/lib/event-stream/global-hub.test.js +27 -26
  86. package/server/lib/event-stream/index.js +6 -0
  87. package/server/lib/event-stream/protocol.js +26 -19
  88. package/server/lib/event-stream/protocol.test.js +18 -7
  89. package/server/lib/event-stream/rebind.test.js +5 -5
  90. package/server/lib/event-stream/runtime.test.js +17 -15
  91. package/server/lib/event-stream/translate-v2.js +313 -0
  92. package/server/lib/event-stream/translate-v2.test.js +209 -0
  93. package/server/lib/event-stream/upstream-reader.test.js +16 -16
  94. package/server/lib/fs/DOCUMENTATION.md +4 -2
  95. package/server/lib/fs/byte-range.js +35 -0
  96. package/server/lib/fs/byte-range.test.js +31 -0
  97. package/server/lib/fs/routes.js +47 -42
  98. package/server/lib/fs/routes.test.js +123 -18
  99. package/server/lib/git/service.js +12 -0
  100. package/server/lib/github/DOCUMENTATION.md +1 -1
  101. package/server/lib/github/pr-status.js +22 -2
  102. package/server/lib/github/pr-status.test.js +27 -14
  103. package/server/lib/guests/DOCUMENTATION.md +13 -0
  104. package/server/lib/guests/catalog.js +12 -0
  105. package/server/lib/guests/catalog.test.js +33 -0
  106. package/server/lib/guests/routes.js +10 -1
  107. package/server/lib/guests/service.js +192 -14
  108. package/server/lib/guests/service.test.js +201 -1
  109. package/server/lib/guests/surface.js +630 -0
  110. package/server/lib/guests/surface.test.js +486 -0
  111. package/server/lib/markdown-image-grants/routes.js +18 -5
  112. package/server/lib/markdown-image-grants/routes.test.js +9 -6
  113. package/server/lib/message-queue/DOCUMENTATION.md +33 -22
  114. package/server/lib/message-queue/runtime.js +155 -100
  115. package/server/lib/message-queue/runtime.test.js +168 -82
  116. package/server/lib/notifications/DOCUMENTATION.md +28 -0
  117. package/server/lib/notifications/control-stream.http.test.js +68 -0
  118. package/server/lib/notifications/emitter-runtime.js +15 -0
  119. package/server/lib/notifications/emitter-runtime.test.js +32 -0
  120. package/server/lib/notifications/push-runtime.js +5 -1
  121. package/server/lib/notifications/push-runtime.test.js +2 -2
  122. package/server/lib/notifications/routes.js +1 -1
  123. package/server/lib/notifications/runtime.js +115 -26
  124. package/server/lib/notifications/template-runtime.js +13 -15
  125. package/server/lib/notifications/template-runtime.test.js +16 -9
  126. package/server/lib/notifications/trigger-runtime.test.js +187 -0
  127. package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
  128. package/server/lib/openchamber-control/actions.js +3 -2
  129. package/server/lib/openchamber-control/error.js +1 -1
  130. package/server/lib/openchamber-control/file-open.js +59 -0
  131. package/server/lib/openchamber-control/file-open.test.js +57 -0
  132. package/server/lib/openchamber-control/routes.test.js +12 -0
  133. package/server/lib/openchamber-control/service.js +102 -49
  134. package/server/lib/openchamber-control/service.test.js +84 -44
  135. package/server/lib/openchamber-sessions/archive-store.js +224 -0
  136. package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
  137. package/server/lib/openchamber-sessions/opencode-client.js +22 -0
  138. package/server/lib/openchamber-sessions/routes.js +398 -328
  139. package/server/lib/openchamber-sessions/routes.test.js +777 -740
  140. package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
  141. package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
  142. package/server/lib/opencode/DOCUMENTATION.md +449 -29
  143. package/server/lib/opencode/agents.js +233 -327
  144. package/server/lib/opencode/auth.js +41 -44
  145. package/server/lib/opencode/auth.test.js +92 -0
  146. package/server/lib/opencode/cli-upgrade.d.ts +4 -0
  147. package/server/lib/opencode/cli-upgrade.js +18 -0
  148. package/server/lib/opencode/cli-upgrade.test.js +36 -0
  149. package/server/lib/opencode/commands.js +164 -145
  150. package/server/lib/opencode/compatibility.d.ts +16 -0
  151. package/server/lib/opencode/compatibility.js +88 -0
  152. package/server/lib/opencode/compatibility.test.js +62 -0
  153. package/server/lib/opencode/config-entity-routes.js +58 -29
  154. package/server/lib/opencode/config-entity-v2.test.js +290 -0
  155. package/server/lib/opencode/config-mutation-response.js +17 -7
  156. package/server/lib/opencode/config-mutation-response.test.js +4 -7
  157. package/server/lib/opencode/config-paths.test.js +2 -2
  158. package/server/lib/opencode/config-v2.d.ts +183 -0
  159. package/server/lib/opencode/config-v2.js +801 -0
  160. package/server/lib/opencode/config-v2.test.js +466 -0
  161. package/server/lib/opencode/credential-db.js +144 -0
  162. package/server/lib/opencode/credential-db.test.js +104 -0
  163. package/server/lib/opencode/env-runtime.js +48 -9
  164. package/server/lib/opencode/env-runtime.test.js +45 -0
  165. package/server/lib/opencode/feature-routes-runtime.js +15 -5
  166. package/server/lib/opencode/lifecycle-process.test.js +7 -4
  167. package/server/lib/opencode/lifecycle.js +85 -30
  168. package/server/lib/opencode/lifecycle.test.js +80 -8
  169. package/server/lib/opencode/login-shell-env.js +27 -0
  170. package/server/lib/opencode/managed-config-file.js +119 -0
  171. package/server/lib/opencode/managed-config-file.test.js +152 -0
  172. package/server/lib/opencode/managed-plugin-config.js +33 -13
  173. package/server/lib/opencode/mcp.js +43 -149
  174. package/server/lib/opencode/network-runtime.js +6 -7
  175. package/server/lib/opencode/openchamber-routes.test.js +2 -5
  176. package/server/lib/opencode/plugin-routes.js +3 -3
  177. package/server/lib/opencode/plugin-routes.test.js +6 -24
  178. package/server/lib/opencode/plugins.js +91 -40
  179. package/server/lib/opencode/plugins.test.js +28 -15
  180. package/server/lib/opencode/providers.js +63 -83
  181. package/server/lib/opencode/providers.test.js +128 -59
  182. package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
  183. package/server/lib/opencode/proxy.js +179 -129
  184. package/server/lib/opencode/pwa-manifest-routes.js +4 -2
  185. package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
  186. package/server/lib/opencode/response-envelope.js +14 -0
  187. package/server/lib/opencode/response-envelope.test.js +23 -0
  188. package/server/lib/opencode/routes-upgrade.test.js +73 -116
  189. package/server/lib/opencode/routes.js +121 -428
  190. package/server/lib/opencode/server-utils-runtime.js +10 -4
  191. package/server/lib/opencode/session-activity.js +78 -0
  192. package/server/lib/opencode/session-runtime.js +21 -10
  193. package/server/lib/opencode/session-runtime.test.js +9 -9
  194. package/server/lib/opencode/settings-helpers.js +9 -9
  195. package/server/lib/opencode/settings-helpers.test.js +7 -12
  196. package/server/lib/opencode/settings-registry.json +6 -8
  197. package/server/lib/opencode/settings-runtime.js +18 -1
  198. package/server/lib/opencode/settings-runtime.test.js +31 -2
  199. package/server/lib/opencode/shared.js +54 -22
  200. package/server/lib/opencode/shared.test.js +8 -4
  201. package/server/lib/opencode/shutdown-runtime.js +54 -7
  202. package/server/lib/opencode/shutdown-runtime.test.js +224 -30
  203. package/server/lib/opencode/skill-routes.js +23 -24
  204. package/server/lib/opencode/skills.js +7 -5
  205. package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
  206. package/server/lib/opencode/upgrade-capability.test.js +1 -1
  207. package/server/lib/opencode/v1-migration-topup.js +288 -0
  208. package/server/lib/opencode/v1-migration-topup.test.js +266 -0
  209. package/server/lib/opencode/v2-install.d.ts +8 -0
  210. package/server/lib/opencode/v2-install.js +158 -0
  211. package/server/lib/opencode/v2-install.test.js +124 -0
  212. package/server/lib/opencode/watcher.js +10 -3
  213. package/server/lib/opencode/watcher.test.js +17 -21
  214. package/server/lib/opencode/websearch-config.js +66 -0
  215. package/server/lib/package-manager.js +55 -68
  216. package/server/lib/package-manager.test.js +241 -75
  217. package/server/lib/permission-auto-accept/runtime.js +38 -22
  218. package/server/lib/permission-auto-accept/runtime.test.js +39 -17
  219. package/server/lib/quota/DOCUMENTATION.md +4 -0
  220. package/server/lib/quota/providers/xai.js +4 -4
  221. package/server/lib/relay/tunnel-host.js +4 -1
  222. package/server/lib/relay/tunnel-host.test.js +7 -0
  223. package/server/lib/routing/DOCUMENTATION.md +52 -21
  224. package/server/lib/routing/defaults.js +23 -6
  225. package/server/lib/routing/jev.js +17 -7
  226. package/server/lib/routing/routes.http.test.js +68 -33
  227. package/server/lib/routing/routes.js +65 -22
  228. package/server/lib/routing/runtime.js +145 -48
  229. package/server/lib/routing/runtime.test.js +79 -55
  230. package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
  231. package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
  232. package/server/lib/scheduled-tasks/runtime.js +81 -74
  233. package/server/lib/scheduled-tasks/runtime.test.js +53 -1
  234. package/server/lib/session-assist/DOCUMENTATION.md +8 -3
  235. package/server/lib/session-assist/context.js +122 -46
  236. package/server/lib/session-assist/context.test.js +117 -49
  237. package/server/lib/session-assist/runtime.js +52 -22
  238. package/server/lib/session-assist/runtime.test.js +145 -173
  239. package/server/lib/session-goal/DOCUMENTATION.md +23 -15
  240. package/server/lib/session-goal/create.js +9 -12
  241. package/server/lib/session-goal/create.test.js +27 -28
  242. package/server/lib/session-goal/runtime.js +164 -77
  243. package/server/lib/session-goal/runtime.test.js +271 -519
  244. package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
  245. package/server/lib/session-knowledge/runtime.js +59 -41
  246. package/server/lib/session-knowledge/runtime.test.js +90 -20
  247. package/server/lib/small-model/DOCUMENTATION.md +154 -208
  248. package/server/lib/small-model/client.js +113 -0
  249. package/server/lib/small-model/index.js +296 -158
  250. package/server/lib/small-model/index.test.js +423 -256
  251. package/server/lib/small-model/routes.js +1 -1
  252. package/server/lib/spaces/DOCUMENTATION.md +807 -0
  253. package/server/lib/spaces/code-in-bait.js +185 -0
  254. package/server/lib/spaces/code-in.js +641 -0
  255. package/server/lib/spaces/code-in.test.js +1048 -0
  256. package/server/lib/spaces/errors.js +8 -0
  257. package/server/lib/spaces/exec-http.js +94 -0
  258. package/server/lib/spaces/gatekeeper-channel.js +202 -0
  259. package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
  260. package/server/lib/spaces/gatekeeper-program.cjs +914 -0
  261. package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
  262. package/server/lib/spaces/hardening.js +457 -0
  263. package/server/lib/spaces/hardening.test.js +368 -0
  264. package/server/lib/spaces/host-git.js +127 -0
  265. package/server/lib/spaces/host-git.test.js +137 -0
  266. package/server/lib/spaces/labels.js +234 -0
  267. package/server/lib/spaces/labels.test.js +219 -0
  268. package/server/lib/spaces/layout.js +184 -0
  269. package/server/lib/spaces/layout.test.js +30 -0
  270. package/server/lib/spaces/manager.js +82 -0
  271. package/server/lib/spaces/manager.test.js +114 -0
  272. package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
  273. package/server/lib/spaces/places/contract-suite.js +156 -0
  274. package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
  275. package/server/lib/spaces/places/contract.test.js +6 -0
  276. package/server/lib/spaces/places/docker-engine.js +126 -0
  277. package/server/lib/spaces/places/docker-live-support.js +202 -0
  278. package/server/lib/spaces/places/docker-tools.js +186 -0
  279. package/server/lib/spaces/places/docker.js +587 -0
  280. package/server/lib/spaces/places/docker.test.js +1610 -0
  281. package/server/lib/spaces/places/escape-suite.js +1018 -0
  282. package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
  283. package/server/lib/spaces/places/fake-docker.js +375 -0
  284. package/server/lib/spaces/places/memory-place.js +103 -0
  285. package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
  286. package/server/lib/spaces/places/registry.js +43 -0
  287. package/server/lib/spaces/places/registry.test.js +50 -0
  288. package/server/lib/spaces/places/server.docker.live.test.js +225 -0
  289. package/server/lib/spaces/run-command.js +261 -0
  290. package/server/lib/spaces/run-command.test.js +396 -0
  291. package/server/lib/spaces/space-server.js +190 -0
  292. package/server/lib/spaces/space-server.test.js +335 -0
  293. package/server/lib/spaces/tools-filler.js +82 -0
  294. package/server/lib/spaces/tools-filler.test.js +148 -0
  295. package/server/lib/spaces/tools-pack.js +49 -0
  296. package/server/lib/spaces/tools-pack.test.js +57 -0
  297. package/server/lib/spaces/tools.js +138 -0
  298. package/server/lib/spaces/tools.test.js +153 -0
  299. package/server/lib/tts/openai-sdk.js +11 -0
  300. package/server/lib/tts/service.js +7 -5
  301. package/server/lib/tts/stt.js +3 -2
  302. package/server/lib/tunnels/DOCUMENTATION.md +2 -0
  303. package/server/lib/tunnels/routes.js +7 -0
  304. package/server/lib/tunnels/routes.test.js +55 -0
  305. package/server/lib/ui-auth/ui-auth.js +38 -2
  306. package/server/lib/ui-auth/ui-auth.test.js +42 -0
  307. package/server/lib/ui-auth/ui-passkeys.js +12 -10
  308. package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
  309. package/server/lib/walkthrough/pull-request.js +61 -8
  310. package/server/lib/walkthrough/pull-request.test.js +54 -1
  311. package/server/lib/walkthrough/routes.js +20 -0
  312. package/server/lib/walkthrough/routes.test.js +19 -0
  313. package/server/opencode-proxy.test.js +190 -166
  314. package/server/opencode-worktree-readiness.test.js +5 -4
  315. package/dist/assets/DiffView-B2CD9wC8.js +0 -4
  316. package/dist/assets/FilesView-C4vMFclc.js +0 -13
  317. package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
  318. package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
  319. package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
  320. package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
  321. package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
  322. package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
  323. package/dist/assets/SettingsView-CSinuH-1.js +0 -29
  324. package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
  325. package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
  326. package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
  327. package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
  328. package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
  329. package/dist/assets/de-Bro6_zyl.js +0 -10
  330. package/dist/assets/es-Bb9XQZvd.js +0 -16
  331. package/dist/assets/fr-Dgfzn-IL.js +0 -14
  332. package/dist/assets/index-CtQNmqUL.js +0 -1
  333. package/dist/assets/index-DqaMWNrC.css +0 -1
  334. package/dist/assets/index-Dqc6USl3.js +0 -40
  335. package/dist/assets/ja-BYsakGFh.js +0 -16
  336. package/dist/assets/ko-BSaQz9aN.js +0 -16
  337. package/dist/assets/main-B62_pjL5.js +0 -2
  338. package/dist/assets/main-BEdcOI2P.js +0 -896
  339. package/dist/assets/miniChat-LT8Rqofr.js +0 -2
  340. package/dist/assets/mobile-CqarEynh.js +0 -2
  341. package/dist/assets/pl-OIviPdoj.js +0 -16
  342. package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
  343. package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
  344. package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
  345. package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
  346. package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
  347. package/dist/assets/tr-CQI-KPre.js +0 -7
  348. package/dist/assets/uk-CqW7R4DA.js +0 -16
  349. package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
  350. package/dist/assets/useRootScrollLock-Cpf1qoGp.js +0 -2
  351. package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
  352. package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
  353. package/dist/assets/zh-CN-CVZG_o_J.js +0 -16
  354. package/dist/assets/zh-TW-D5ZxzCZp.js +0 -16
  355. package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
  356. package/server/lib/routing/feature-flag.js +0 -11
  357. package/server/lib/small-model/call.js +0 -851
  358. package/server/lib/small-model/call.test.js +0 -1292
  359. package/server/lib/small-model/catalog.js +0 -13
  360. package/server/lib/small-model/resolve.js +0 -131
  361. package/server/lib/small-model/resolve.test.js +0 -197
  362. package/server/lib/small-model/runtime-providers.js +0 -164
  363. package/server/lib/small-model/runtime-providers.test.js +0 -118
  364. package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
  365. package/server/lib/system-prompt/runtime.js +0 -49
  366. package/server/lib/system-prompt/runtime.test.js +0 -76
  367. package/server/lib/walkthrough/reproduce-2607.test.js +0 -148
@@ -11,12 +11,10 @@ const __dirname = path.dirname(__filename);
11
11
 
12
12
  const PACKAGE_NAME = '@vantran-se/openchamber-web';
13
13
  const PACKAGE_PATH_SEGMENTS = PACKAGE_NAME.split('/');
14
- const PACKAGE_REGISTRY_URL = 'https://registry.npmjs.org';
15
- const UPSTREAM_NPM_REGISTRY_URL = 'https://registry.npmjs.org/@openchamber%2Fweb';
14
+ const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME.replace('/', '%2F')}`;
15
+ const GITHUB_RELEASES_URL = 'https://github.com/openchamber/openchamber/releases';
16
+ const GITHUB_RELEASES_API_URL = 'https://api.github.com/repos/openchamber/openchamber/releases';
16
17
  const FORK_RELEASES_URL = 'https://github.com/vantran-se/openchamber/releases';
17
- const FORK_LATEST_RELEASE_API_URL = 'https://api.github.com/repos/vantran-se/openchamber/releases/latest';
18
- const UPSTREAM_RELEASES_URL = 'https://github.com/openchamber/openchamber/releases';
19
- const UPSTREAM_RELEASES_API_URL = 'https://api.github.com/repos/openchamber/openchamber/releases';
20
18
  let cachedDetectedPm = null;
21
19
 
22
20
  function getSpawnSyncBaseOptions() {
@@ -99,7 +97,7 @@ async function resolveAndroidApkUrl(version, candidateUrl) {
99
97
  }
100
98
 
101
99
  try {
102
- const response = await fetch(`${UPSTREAM_RELEASES_API_URL}/tags/v${version}`, {
100
+ const response = await fetch(`${GITHUB_RELEASES_API_URL}/tags/v${version}`, {
103
101
  headers: {
104
102
  Accept: 'application/vnd.github+json',
105
103
  'User-Agent': 'openchamber-update-check',
@@ -161,7 +159,7 @@ async function checkForUpdatesFromApi(currentVersion, options = {}) {
161
159
  const versionComparison = compareVersions(data.latestVersion, currentVersion);
162
160
  if (versionComparison < 0) return null;
163
161
 
164
- const releaseUrl = `${UPSTREAM_RELEASES_URL}/tag/v${data.latestVersion}`;
162
+ const releaseUrl = `${GITHUB_RELEASES_URL}/tag/v${data.latestVersion}`;
165
163
  const downloadUrl = typeof data.downloadUrl === 'string'
166
164
  ? data.downloadUrl
167
165
  : typeof data.download?.url === 'string'
@@ -661,13 +659,27 @@ export function getUpdateCommand(pm = detectPackageManager()) {
661
659
  const pmCommand = quoteCommand(resolvePackageManagerCommand(pm));
662
660
  switch (pm) {
663
661
  case 'pnpm':
664
- return `${pmCommand} add -g ${PACKAGE_NAME}@latest --registry=${PACKAGE_REGISTRY_URL}`;
662
+ return `${pmCommand} add -g ${PACKAGE_NAME}@latest`;
665
663
  case 'yarn':
666
- return `${pmCommand} global add ${PACKAGE_NAME}@latest --registry ${PACKAGE_REGISTRY_URL}`;
664
+ return `${pmCommand} global add ${PACKAGE_NAME}@latest`;
667
665
  case 'bun':
668
- return `${pmCommand} add -g ${PACKAGE_NAME}@latest --registry=${PACKAGE_REGISTRY_URL}`;
666
+ return `${pmCommand} add -g ${PACKAGE_NAME}@latest`;
669
667
  default:
670
- return `${pmCommand} install -g ${PACKAGE_NAME}@latest --registry=${PACKAGE_REGISTRY_URL}`;
668
+ return `${pmCommand} install -g ${PACKAGE_NAME}@latest`;
669
+ }
670
+ }
671
+
672
+ function getUpdateInvocation(pm = detectPackageManager()) {
673
+ const command = resolvePackageManagerCommand(pm);
674
+ switch (pm) {
675
+ case 'pnpm':
676
+ return { command, args: ['add', '-g', `${PACKAGE_NAME}@latest`] };
677
+ case 'yarn':
678
+ return { command, args: ['global', 'add', `${PACKAGE_NAME}@latest`] };
679
+ case 'bun':
680
+ return { command, args: ['add', '-g', `${PACKAGE_NAME}@latest`] };
681
+ default:
682
+ return { command, args: ['install', '-g', `${PACKAGE_NAME}@latest`] };
671
683
  }
672
684
  }
673
685
 
@@ -687,9 +699,9 @@ export function getCurrentVersion() {
687
699
  /**
688
700
  * Fetch latest version from npm registry
689
701
  */
690
- async function getLatestUpstreamVersion() {
702
+ async function getLatestVersion() {
691
703
  try {
692
- const response = await fetch(UPSTREAM_NPM_REGISTRY_URL, {
704
+ const response = await fetch(NPM_REGISTRY_URL, {
693
705
  headers: { Accept: 'application/json' },
694
706
  signal: AbortSignal.timeout(10000),
695
707
  });
@@ -700,34 +712,7 @@ async function getLatestUpstreamVersion() {
700
712
 
701
713
  const data = await response.json();
702
714
  return data['dist-tags']?.latest || null;
703
- } catch {
704
- return null;
705
- }
706
- }
707
-
708
- async function getLatestForkRelease() {
709
- try {
710
- const response = await fetch(FORK_LATEST_RELEASE_API_URL, {
711
- headers: {
712
- Accept: 'application/vnd.github+json',
713
- 'User-Agent': 'openchamber-update-check',
714
- },
715
- signal: AbortSignal.timeout(10000),
716
- });
717
- if (!response.ok) return null;
718
-
719
- const release = await response.json();
720
- const tagName = String(release?.tag_name || '');
721
- const versionMatch = tagName.match(/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/);
722
- if (!versionMatch) return null;
723
-
724
- const releaseBody = String(release?.body || '').trim();
725
- return {
726
- version: versionMatch[1],
727
- body: releaseBody || undefined,
728
- releaseUrl: `${FORK_RELEASES_URL}/tag/v${versionMatch[1]}`,
729
- };
730
- } catch {
715
+ } catch (error) {
731
716
  return null;
732
717
  }
733
718
  }
@@ -778,39 +763,40 @@ export async function checkForUpdates(options = {}) {
778
763
  const appType = normalizeAppType(options.appType);
779
764
  const platform = normalizePlatform(options.platform);
780
765
 
781
- if (appType !== 'web' && currentVersion !== 'unknown') {
766
+ if (currentVersion !== 'unknown') {
782
767
  const remote = await checkForUpdatesFromApi(currentVersion, options);
783
768
  if (remote) {
784
- return {
785
- ...remote,
786
- packageManager: pm,
787
- updateCommand: 'openchamber update',
788
- };
789
- }
790
- }
769
+ if (appType !== 'web') {
770
+ return {
771
+ ...remote,
772
+ packageManager: pm,
773
+ updateCommand: 'openchamber update',
774
+ };
775
+ }
776
+
777
+ const latestVersion = await getLatestVersion();
778
+ if (!latestVersion) {
779
+ return {
780
+ available: false,
781
+ currentVersion,
782
+ error: 'Unable to determine versions',
783
+ };
784
+ }
791
785
 
792
- if (appType === 'web') {
793
- const release = await getLatestForkRelease();
794
- if (!release || currentVersion === 'unknown') {
786
+ const available = compareVersions(latestVersion, currentVersion) > 0;
795
787
  return {
796
- available: false,
788
+ available,
789
+ version: latestVersion,
797
790
  currentVersion,
798
- error: 'Unable to determine versions',
791
+ body: available && remote.version === latestVersion ? remote.body : undefined,
792
+ releaseUrl: `${FORK_RELEASES_URL}/tag/v${latestVersion}`,
793
+ packageManager: pm,
794
+ updateCommand: 'openchamber update',
799
795
  };
800
796
  }
801
-
802
- return {
803
- available: compareVersions(release.version, currentVersion) > 0,
804
- version: release.version,
805
- currentVersion,
806
- body: release.body,
807
- releaseUrl: release.releaseUrl,
808
- packageManager: pm,
809
- updateCommand: 'openchamber update',
810
- };
811
797
  }
812
798
 
813
- const latestVersion = await getLatestUpstreamVersion();
799
+ const latestVersion = await getLatestVersion();
814
800
 
815
801
  if (!latestVersion || currentVersion === 'unknown') {
816
802
  return {
@@ -835,9 +821,10 @@ export async function checkForUpdates(options = {}) {
835
821
  version: latestVersion,
836
822
  currentVersion,
837
823
  body: changelog,
838
- releaseUrl: `${UPSTREAM_RELEASES_URL}/tag/v${latestVersion}`,
824
+ releaseUrl: `${FORK_RELEASES_URL}/tag/v${latestVersion}`,
839
825
  downloadUrl,
840
826
  packageManager: pm,
827
+ // Show our CLI command, not raw package manager command
841
828
  updateCommand: 'openchamber update',
842
829
  };
843
830
  }
@@ -847,14 +834,14 @@ export async function checkForUpdates(options = {}) {
847
834
  */
848
835
  export function executeUpdate(pm = detectPackageManager(), options = {}) {
849
836
  const command = getUpdateCommand(pm);
837
+ const invocation = getUpdateInvocation(pm);
850
838
  if (!options?.silent) {
851
839
  console.log(`Updating ${PACKAGE_NAME} using ${pm}...`);
852
840
  console.log(`Running: ${command}`);
853
841
  }
854
842
 
855
- const result = spawnSync(command, {
843
+ const result = spawnSync(invocation.command, invocation.args, {
856
844
  stdio: 'inherit',
857
- shell: true,
858
845
  ...getSpawnSyncBaseOptions(),
859
846
  });
860
847
 
@@ -1,5 +1,7 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { spawnSync } from 'node:child_process';
2
3
 
4
+ // Mock child_process to prevent real spawnSync calls that would hang in tests
3
5
  vi.mock('node:child_process', () => ({
4
6
  spawn: vi.fn(),
5
7
  spawnSync: vi.fn(() => ({ status: 0, stdout: '/usr/local/bin', stderr: '' })),
@@ -13,22 +15,46 @@ const {
13
15
  getUpdateCommand,
14
16
  } = await import('./package-manager.js');
15
17
 
18
+ /** Helper: create a fetch mock that routes by URL pattern */
16
19
  function createFetchMock() {
17
20
  const handlers = new Map();
18
- const mock = vi.fn((url) => {
19
- const urlString = String(url);
21
+
22
+ const mock = vi.fn((url, options) => {
23
+ const urlStr = typeof url === 'string' ? url : url.toString();
24
+
20
25
  for (const [pattern, response] of handlers) {
21
- if (urlString.includes(pattern)) return Promise.resolve(response);
26
+ if (urlStr.includes(pattern)) {
27
+ return Promise.resolve(response);
28
+ }
22
29
  }
23
- return Promise.reject(new Error(`Unexpected fetch call: ${urlString}`));
30
+
31
+ return Promise.reject(new Error(`Unexpected fetch call: ${urlStr}`));
24
32
  });
33
+
25
34
  mock.when = (pattern, response) => {
26
35
  handlers.set(pattern, response);
27
36
  return mock;
28
37
  };
38
+
29
39
  return mock;
30
40
  }
31
41
 
42
+ describe('fork package update command', () => {
43
+ it('targets the published fork without a runtime environment override', () => {
44
+ expect(getUpdateCommand('npm')).toBe('npm install -g @vantran-se/openchamber-web@latest');
45
+ });
46
+
47
+ it('executes the package manager without a shell', () => {
48
+ spawnSync.mockClear();
49
+ executeUpdate('npm', { silent: true });
50
+ expect(spawnSync).toHaveBeenCalledWith(
51
+ 'npm',
52
+ ['install', '-g', '@vantran-se/openchamber-web@latest'],
53
+ expect.not.objectContaining({ shell: true }),
54
+ );
55
+ });
56
+ });
57
+
32
58
  describe('checkForUpdates', () => {
33
59
  let fetchMock;
34
60
  let originalFetch;
@@ -43,86 +69,97 @@ describe('checkForUpdates', () => {
43
69
  globalThis.fetch = originalFetch;
44
70
  });
45
71
 
46
- it('uses the latest fork release for web updates', async () => {
47
- fetchMock.when('api.github.com/repos/vantran-se/openchamber/releases/latest', {
48
- ok: true,
49
- json: async () => ({
50
- tag_name: 'v1.22.1',
51
- body: 'Fork release notes',
52
- }),
53
- });
54
-
55
- const result = await checkForUpdates({ currentVersion: '1.22.0' });
72
+ // --- Scenario: API says update available, npm confirms ---
56
73
 
57
- expect(result).toMatchObject({
58
- available: true,
59
- version: '1.22.1',
60
- currentVersion: '1.22.0',
61
- body: 'Fork release notes',
62
- releaseUrl: 'https://github.com/vantran-se/openchamber/releases/tag/v1.22.1',
63
- updateCommand: 'openchamber update',
64
- });
65
- expect(fetchMock).toHaveBeenCalledTimes(1);
66
- });
67
-
68
- it('reports no web update when the fork release matches the installed version', async () => {
69
- fetchMock.when('api.github.com/repos/vantran-se/openchamber/releases/latest', {
70
- ok: true,
71
- json: async () => ({ tag_name: 'v1.22.0' }),
72
- });
74
+ it('returns available=true when both API and npm confirm a newer version', async () => {
75
+ fetchMock
76
+ .when('api.openchamber.dev', {
77
+ ok: true,
78
+ json: async () => ({
79
+ latestVersion: '1.10.0',
80
+ updateAvailable: true,
81
+ releaseNotes: '## [1.10.0] - 2026-05-01\n\n- Great new feature',
82
+ }),
83
+ })
84
+ .when('registry.npmjs.org', {
85
+ ok: true,
86
+ json: async () => ({
87
+ 'dist-tags': { latest: '1.10.0' },
88
+ }),
89
+ })
90
+ .when('raw.githubusercontent.com', {
91
+ ok: true,
92
+ text: async () => '## [1.10.0] - 2026-05-01\n\n- Great new feature',
93
+ });
73
94
 
74
- const result = await checkForUpdates({ currentVersion: '1.22.0' });
95
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
75
96
 
76
- expect(result.available).toBe(false);
77
- expect(result.version).toBe('1.22.0');
97
+ expect(result.available).toBe(true);
98
+ expect(result.version).toBe('1.10.0');
99
+ expect(result.currentVersion).toBe('1.9.10');
78
100
  });
79
101
 
80
- it('does not offer a prerelease over the matching stable version', async () => {
81
- fetchMock.when('api.github.com/repos/vantran-se/openchamber/releases/latest', {
82
- ok: true,
83
- json: async () => ({ tag_name: 'v1.22.0-beta.1' }),
84
- });
102
+ // --- Scenario (THE FIX): API says update available, npm does NOT have it ---
85
103
 
86
- const result = await checkForUpdates({ currentVersion: '1.22.0' });
104
+ it('returns available=false when API claims update but npm has same version', async () => {
105
+ fetchMock
106
+ .when('api.openchamber.dev', {
107
+ ok: true,
108
+ json: async () => ({
109
+ latestVersion: '1.10.0',
110
+ updateAvailable: true,
111
+ releaseNotes: '## [1.10.0] - 2026-05-01\n\n- Great new feature',
112
+ }),
113
+ })
114
+ .when('registry.npmjs.org', {
115
+ ok: true,
116
+ json: async () => ({
117
+ 'dist-tags': { latest: '1.9.10' },
118
+ }),
119
+ });
120
+
121
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
87
122
 
88
123
  expect(result.available).toBe(false);
89
124
  });
90
125
 
91
- it('fails explicitly when the fork release cannot be resolved', async () => {
92
- fetchMock.when('api.github.com/repos/vantran-se/openchamber/releases/latest', {
93
- ok: false,
94
- status: 404,
95
- });
126
+ it('returns available=false when npm only has a prerelease of the current version', async () => {
127
+ fetchMock
128
+ .when('api.openchamber.dev', Promise.reject(new Error('Network error')))
129
+ .when('registry.npmjs.org', {
130
+ ok: true,
131
+ json: async () => ({
132
+ 'dist-tags': { latest: '1.10.0-beta.1' },
133
+ }),
134
+ });
96
135
 
97
- const result = await checkForUpdates({ currentVersion: '1.22.0' });
136
+ const result = await checkForUpdates({ currentVersion: '1.10.0' });
98
137
 
99
- expect(result).toMatchObject({
100
- available: false,
101
- currentVersion: '1.22.0',
102
- error: 'Unable to determine versions',
103
- });
138
+ expect(result.available).toBe(false);
104
139
  });
105
140
 
106
- it('keeps the central update API for desktop runtimes', async () => {
107
- fetchMock.when('api.openchamber.dev', {
108
- ok: true,
109
- json: async () => ({
110
- latestVersion: '1.22.1',
111
- updateAvailable: true,
112
- releaseNotes: 'Desktop release notes',
113
- }),
114
- });
141
+ it('accepts electron desktop update claims without npm cross-checking', async () => {
142
+ fetchMock
143
+ .when('api.openchamber.dev', {
144
+ ok: true,
145
+ json: async () => ({
146
+ latestVersion: '1.10.0',
147
+ updateAvailable: true,
148
+ releaseNotes: '## [1.10.0] - 2026-05-01\n\n- Great new feature',
149
+ }),
150
+ });
115
151
 
116
152
  const result = await checkForUpdates({
117
153
  appType: 'desktop-electron',
118
- currentVersion: '1.22.0',
154
+ currentVersion: '1.9.10',
119
155
  installId: '4f4dfead-9688-4c4f-97d7-4607fbbfc3ab',
120
156
  platform: 'windows',
121
157
  arch: 'arm64',
122
158
  });
123
159
 
124
160
  expect(result.available).toBe(true);
125
- expect(result.version).toBe('1.22.1');
161
+ expect(result.version).toBe('1.10.0');
162
+ expect(fetchMock).toHaveBeenCalledTimes(1);
126
163
  expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toMatchObject({
127
164
  installId: '4f4dfead-9688-4c4f-97d7-4607fbbfc3ab',
128
165
  platform: 'windows',
@@ -130,23 +167,31 @@ describe('checkForUpdates', () => {
130
167
  });
131
168
  });
132
169
 
133
- it('resolves an Android APK asset for mobile updates', async () => {
170
+ it('resolves an Android APK asset when the update API returns an AAB', async () => {
134
171
  fetchMock
135
172
  .when('api.openchamber.dev', {
136
173
  ok: true,
137
174
  json: async () => ({
138
- latestVersion: '1.22.1',
175
+ latestVersion: '1.10.0',
139
176
  updateAvailable: true,
140
- downloadUrl: 'https://github.com/openchamber/openchamber/releases/download/v1.22.1/OpenChamber-1.22.1-42-android.aab',
177
+ downloadUrl: 'https://github.com/openchamber/openchamber/releases/download/v1.10.0/OpenChamber-1.10.0-42-android.aab',
141
178
  }),
142
179
  })
143
- .when('api.github.com/repos/openchamber/openchamber/releases/tags/v1.22.1', {
180
+ .when('api.github.com/repos/openchamber/openchamber/releases/tags/v1.10.0', {
144
181
  ok: true,
145
182
  json: async () => ({
146
183
  assets: [
147
184
  {
148
- name: 'OpenChamber-1.22.1-42-android.apk',
149
- browser_download_url: 'https://downloads.example/OpenChamber-1.22.1-42-android.apk',
185
+ name: 'OpenChamber-1.10.0-42-android.aab',
186
+ browser_download_url: 'https://downloads.example/OpenChamber-1.10.0-42-android.aab',
187
+ },
188
+ {
189
+ name: 'app-release.apk',
190
+ browser_download_url: 'https://downloads.example/app-release.apk',
191
+ },
192
+ {
193
+ name: 'OpenChamber-1.10.0-42-android.apk',
194
+ browser_download_url: 'https://downloads.example/OpenChamber-1.10.0-42-android.apk',
150
195
  },
151
196
  ],
152
197
  }),
@@ -155,18 +200,139 @@ describe('checkForUpdates', () => {
155
200
  const result = await checkForUpdates({
156
201
  appType: 'mobile-capacitor',
157
202
  platform: 'android',
158
- currentVersion: '1.22.0',
203
+ currentVersion: '1.9.10',
159
204
  });
160
205
 
161
- expect(result.downloadUrl).toBe('https://downloads.example/OpenChamber-1.22.1-42-android.apk');
206
+ expect(result.downloadUrl).toBe('https://downloads.example/OpenChamber-1.10.0-42-android.apk');
162
207
  });
163
- });
164
208
 
165
- describe('getUpdateCommand', () => {
166
- it('installs the public fork package from npm', () => {
167
- expect(getUpdateCommand('npm')).toBe(
168
- 'npm install -g @vantran-se/openchamber-web@latest --registry=https://registry.npmjs.org',
169
- );
209
+ it('keeps a direct Android APK URL from the update API', async () => {
210
+ const apkUrl = 'https://github.com/openchamber/openchamber/releases/download/v1.10.0/OpenChamber-1.10.0-42-android.apk';
211
+ fetchMock.when('api.openchamber.dev', {
212
+ ok: true,
213
+ json: async () => ({
214
+ latestVersion: '1.10.0',
215
+ updateAvailable: true,
216
+ downloadUrl: apkUrl,
217
+ }),
218
+ });
219
+
220
+ const result = await checkForUpdates({
221
+ appType: 'mobile-capacitor',
222
+ platform: 'android',
223
+ currentVersion: '1.9.10',
224
+ });
225
+
226
+ expect(result.downloadUrl).toBe(apkUrl);
227
+ expect(fetchMock).toHaveBeenCalledTimes(1);
228
+ });
229
+
230
+ it('returns available=false when API claims update but npm is behind', async () => {
231
+ fetchMock
232
+ .when('api.openchamber.dev', {
233
+ ok: true,
234
+ json: async () => ({
235
+ latestVersion: '1.10.0',
236
+ updateAvailable: true,
237
+ releaseNotes: '## [1.10.0] - 2026-05-01\n\n- Great new feature',
238
+ }),
239
+ })
240
+ .when('registry.npmjs.org', {
241
+ ok: true,
242
+ json: async () => ({
243
+ 'dist-tags': { latest: '1.9.9' },
244
+ }),
245
+ });
246
+
247
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
248
+
249
+ expect(result.available).toBe(false);
250
+ });
251
+
252
+ // --- Scenario: API says no update, npm agrees ---
253
+
254
+ it('returns available=false when API says no update and versions match', async () => {
255
+ fetchMock.when('api.openchamber.dev', {
256
+ ok: true,
257
+ json: async () => ({
258
+ latestVersion: '1.9.10',
259
+ updateAvailable: false,
260
+ }),
261
+ });
262
+
263
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
264
+
265
+ expect(result.available).toBe(false);
266
+ });
267
+
268
+ // --- Scenario: API unreachable, npm fallback ---
269
+
270
+ it('returns available=true from npm fallback when API is unreachable and npm has newer version', async () => {
271
+ fetchMock
272
+ .when('api.openchamber.dev', Promise.reject(new Error('Network error')))
273
+ .when('registry.npmjs.org', {
274
+ ok: true,
275
+ json: async () => ({
276
+ 'dist-tags': { latest: '1.10.0' },
277
+ }),
278
+ })
279
+ .when('raw.githubusercontent.com', {
280
+ ok: true,
281
+ text: async () => '## [1.10.0] - 2026-05-01\n\n- Great new feature',
282
+ });
283
+
284
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
285
+
286
+ expect(result.available).toBe(true);
287
+ expect(result.version).toBe('1.10.0');
288
+ });
289
+
290
+ it('returns available=false from npm fallback when API is unreachable and versions match', async () => {
291
+ fetchMock
292
+ .when('api.openchamber.dev', Promise.reject(new Error('Network error')))
293
+ .when('registry.npmjs.org', {
294
+ ok: true,
295
+ json: async () => ({
296
+ 'dist-tags': { latest: '1.9.10' },
297
+ }),
298
+ });
299
+
300
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
301
+
302
+ expect(result.available).toBe(false);
303
+ });
304
+
305
+ // --- Scenario: API returns null (bad response), npm fallback ---
306
+
307
+ it('returns available=false when API returns non-ok status and versions match on npm', async () => {
308
+ fetchMock
309
+ .when('api.openchamber.dev', {
310
+ ok: false,
311
+ status: 500,
312
+ json: async () => ({}),
313
+ })
314
+ .when('registry.npmjs.org', {
315
+ ok: true,
316
+ json: async () => ({
317
+ 'dist-tags': { latest: '1.9.10' },
318
+ }),
319
+ });
320
+
321
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
322
+
323
+ expect(result.available).toBe(false);
324
+ });
325
+
326
+ // --- Scenario: Both API and npm are unreachable ---
327
+
328
+ it('returns available=false when both sources are unreachable', async () => {
329
+ fetchMock
330
+ .when('api.openchamber.dev', Promise.reject(new Error('Network error')))
331
+ .when('registry.npmjs.org', Promise.reject(new Error('Registry unreachable')));
332
+
333
+ const result = await checkForUpdates({ currentVersion: '1.9.10' });
334
+
335
+ expect(result.available).toBe(false);
170
336
  });
171
337
  });
172
338