@vantran-se/openchamber-web 1.24.4 → 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-B4a0JkUz.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-Byu2DY7s.js → GitView-BWa7e5vw.js} +4 -4
  8. package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
  9. package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
  10. package/dist/assets/{JsonTreeView-VW_7czo3.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-DCjGhlYV.js → PatchDiff-DS-IMU1j.js} +1 -1
  16. package/dist/assets/{PierreDiffViewer-CEDZNkU2.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-NU0FHFhT.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
  25. package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-lap5btCF.js} +2 -2
  26. package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
  27. package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-CwGJfJG-.js} +1 -1
  28. package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.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-CO4e5J_S.js → isDiffPlainText-D47a6hMP.js} +1 -1
  36. package/dist/assets/{isFilePlainText-ue6kCou7.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-CK48LG0z.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-D7QVlovD.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-BPu3JiQs.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
  52. package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.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-CgS0fnw_.js → useInlineCommentController-CuUuakrt.js} +2 -2
  57. package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
  58. package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.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-DxX8bZ1o.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-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
@@ -1,19 +1,25 @@
1
+ import { readOpenCodeInfo, isSupportedOpenCodeVersion } from './compatibility.js';
1
2
  import express from 'express';
2
3
  import { createProjectIdFromPath } from '../projects/project-id.js';
3
4
  import fs from 'fs';
4
5
  import path from 'path';
5
6
  import {
6
- buildDeferredRestartResponse,
7
+ buildAppliedResponse,
7
8
  } from './config-mutation-response.js';
8
9
  import { getClaudeCliAuthStatus } from './claude-cli-auth.js';
9
10
  import { OPENCODE_CONFIG_DIR } from './shared.js';
10
11
  import { settingsSurfaceOf } from './settings-files.js';
12
+ import { parseWebSearchSelection } from './config-v2.js';
13
+ import { getWebSearchSource, setWebSearchSelection } from './websearch-config.js';
11
14
 
12
15
  export const registerOpenCodeRoutes = (app, dependencies) => {
13
16
  const {
14
17
  crypto,
15
18
  getOpenCodeResolutionSnapshot,
16
19
  getOpenCodeUpgradeCapability,
20
+ upgradeOpenCodeCli,
21
+ getOpenCodeCompatibility,
22
+ installOpenCodeV2,
17
23
  formatSettingsResponse,
18
24
  readSettingsFromDisk,
19
25
  readSettingsFromDiskMigrated,
@@ -31,8 +37,6 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
31
37
  } = dependencies;
32
38
 
33
39
  let authLibrary = null;
34
- const pendingMcpAuthContextByState = new Map();
35
- const PENDING_MCP_AUTH_TTL_MS = 30 * 60 * 1000;
36
40
  const getAuthLibrary = async () => {
37
41
  if (!authLibrary) {
38
42
  authLibrary = await import('./auth.js');
@@ -40,68 +44,6 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
40
44
  return authLibrary;
41
45
  };
42
46
 
43
- const normalizePendingString = (value) => {
44
- if (typeof value !== 'string') {
45
- return null;
46
- }
47
-
48
- const trimmed = value.trim();
49
- return trimmed || null;
50
- };
51
-
52
- const escapeHtml = (value) => String(value)
53
- .replace(/&/g, '&')
54
- .replace(/</g, '&lt;')
55
- .replace(/>/g, '&gt;')
56
- .replace(/"/g, '&quot;')
57
- .replace(/'/g, '&#39;');
58
-
59
- // Self-contained page for the OAuth return leg: the system browser has no UI
60
- // session, so it cannot load the SPA behind the auth gate — everything it
61
- // needs ships inline. `openchamber://focus/mcp-auth` raises the desktop app;
62
- // the link stays visible because some browsers only follow custom-protocol
63
- // URLs from a user gesture.
64
- const renderMcpOAuthCallbackPage = ({ title, message, desktopReturn }) => `<!doctype html>
65
- <html lang="en">
66
- <head>
67
- <meta charset="utf-8">
68
- <meta name="viewport" content="width=device-width, initial-scale=1">
69
- <title>${escapeHtml(title)} — OpenChamber</title>
70
- <style>
71
- :root { color-scheme: light dark; }
72
- body { margin: 0; min-height: 100vh; display: flex; align-items: center; justify-content: center;
73
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
74
- background: Canvas; color: CanvasText; }
75
- main { max-width: 34rem; padding: 2.5rem 2rem; text-align: center; }
76
- h1 { font-size: 1.25rem; margin: 0 0 0.75rem; }
77
- p { margin: 0; line-height: 1.5; opacity: 0.85; }
78
- a.return { display: inline-block; margin-top: 1.5rem; padding: 0.5rem 1.25rem; border-radius: 0.5rem;
79
- border: 1px solid color-mix(in srgb, CanvasText 25%, transparent); color: inherit; text-decoration: none; }
80
- </style>
81
- </head>
82
- <body>
83
- <main>
84
- <h1>${escapeHtml(title)}</h1>
85
- <p>${escapeHtml(message)}</p>
86
- ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return to OpenChamber</a>
87
- <script>window.location.href = 'openchamber://focus/mcp-auth';</script>` : ''}
88
- </main>
89
- </body>
90
- </html>`;
91
-
92
- const readOpenCodeCurrentVersion = async () => {
93
- const healthResponse = await fetch(buildOpenCodeUrl('/global/health', ''), {
94
- method: 'GET',
95
- headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
96
- });
97
- const health = await healthResponse.json().catch(() => null);
98
- if (!healthResponse.ok) {
99
- return { ok: false, status: healthResponse.status, error: health?.error || healthResponse.statusText };
100
- }
101
- const currentVersion = typeof health?.version === 'string' ? health.version.replace(/^v/, '') : null;
102
- return { ok: true, currentVersion };
103
- };
104
-
105
47
  const parseVersionForComparison = (value) => {
106
48
  const normalized = String(value || '').replace(/^v/, '').split('+')[0];
107
49
  const prereleaseIndex = normalized.indexOf('-');
@@ -125,21 +67,10 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
125
67
  return 0;
126
68
  };
127
69
 
128
- const fetchLatestOpenCodeVersionFromGithub = async () => {
129
- const response = await fetch('https://api.github.com/repos/anomalyco/opencode/releases/latest', {
130
- headers: { Accept: 'application/json' },
131
- signal: AbortSignal.timeout(10_000),
132
- });
133
- if (!response.ok) {
134
- throw new Error(`OpenCode releases responded with ${response.status}`);
135
- }
136
- const payload = await response.json();
137
- const tag = typeof payload?.tag_name === 'string' ? payload.tag_name.trim() : '';
138
- return tag.replace(/^v/, '');
139
- };
140
-
141
- const fetchLatestOpenCodeVersionFromNpm = async () => {
142
- const response = await fetch('https://registry.npmjs.org/opencode-ai/latest', {
70
+ // OpenCode 2.x publishes as `@opencode/cli` on npm and has no GitHub
71
+ // release assets, so the registry is the one source of "latest".
72
+ const fetchLatestOpenCodeVersion = async () => {
73
+ const response = await fetch('https://registry.npmjs.org/@opencode%2Fcli/latest', {
143
74
  headers: { Accept: 'application/json' },
144
75
  signal: AbortSignal.timeout(10_000),
145
76
  });
@@ -147,66 +78,9 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
147
78
  throw new Error(`OpenCode npm registry responded with ${response.status}`);
148
79
  }
149
80
  const payload = await response.json();
150
- return typeof payload?.version === 'string' ? payload.version.trim().replace(/^v/, '') : '';
151
- };
152
-
153
- const fetchLatestOpenCodeVersion = async () => {
154
- const results = await Promise.allSettled([
155
- fetchLatestOpenCodeVersionFromNpm(),
156
- fetchLatestOpenCodeVersionFromGithub(),
157
- ]);
158
- const versions = results
159
- .filter((result) => result.status === 'fulfilled' && result.value)
160
- .map((result) => result.value);
161
- if (versions.length === 0) {
162
- const failure = results.find((result) => result.status === 'rejected');
163
- throw failure?.reason instanceof Error ? failure.reason : new Error('Failed to resolve latest OpenCode version');
164
- }
165
- return versions.sort((left, right) => compareVersions(right, left))[0];
166
- };
167
-
168
- // OpenCode's `/global/upgrade` requires an explicit semver target and rejects
169
- // a bodyless call, so "update to the latest" has to name the version. The
170
- // release lookup is the same one the upgrade-status check already uses to
171
- // decide there is anything to offer.
172
- const resolveOpenCodeUpgradeTarget = async (requestedTarget) => {
173
- if (typeof requestedTarget === 'string' && requestedTarget.trim().length > 0) {
174
- return { resolved: true, target: requestedTarget.trim() };
175
- }
176
- try {
177
- const latest = await fetchLatestOpenCodeVersion();
178
- if (!latest) {
179
- return { resolved: false, reason: 'The latest OpenCode version could not be determined.' };
180
- }
181
- return { resolved: true, target: latest };
182
- } catch (error) {
183
- return {
184
- resolved: false,
185
- reason: error instanceof Error ? error.message : 'The latest OpenCode version could not be determined.',
186
- };
187
- }
188
- };
189
-
190
- // OpenCode reports a rejected upgrade as `{ name, data: { message, kind } }`,
191
- // which carries no `error` field. Reading only `error` left the user with the
192
- // bare HTTP status text ("Bad Request") and nothing to act on.
193
- const readOpenCodeUpgradeErrorMessage = (payload, response) => {
194
- const candidates = [payload?.error, payload?.data?.message, payload?.message];
195
- for (const candidate of candidates) {
196
- if (typeof candidate === 'string' && candidate.trim().length > 0) {
197
- return candidate.trim();
198
- }
199
- }
200
- return response.statusText || 'Failed to upgrade OpenCode';
201
- };
202
-
203
- const pruneExpiredPendingMcpAuthContexts = () => {
204
- const now = Date.now();
205
- for (const [state, entry] of pendingMcpAuthContextByState.entries()) {
206
- if (!entry || typeof entry.expiresAt !== 'number' || entry.expiresAt <= now) {
207
- pendingMcpAuthContextByState.delete(state);
208
- }
209
- }
81
+ const version = typeof payload?.version === 'string' ? payload.version.trim().replace(/^v/, '') : '';
82
+ if (!version) throw new Error('Failed to resolve latest OpenCode version');
83
+ return version;
210
84
  };
211
85
 
212
86
  app.get('/api/config/settings', async (req, res) => {
@@ -231,138 +105,83 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
231
105
  }
232
106
  });
233
107
 
234
- let openCodeUpgradePromise = null;
108
+ app.get('/api/opencode/compatibility', async (_req, res) => {
109
+ try { res.json(await getOpenCodeCompatibility()); }
110
+ catch { res.status(503).json({ error: 'Could not check OpenCode compatibility.' }); }
111
+ });
235
112
 
236
- app.post('/api/opencode/upgrade', async (req, res) => {
113
+ let installInFlight = null;
114
+ app.post('/api/opencode/install-v2', async (_req, res) => {
237
115
  try {
238
- const capability = getOpenCodeUpgradeCapability();
239
- if (!capability.supported) {
240
- return res.status(409).json({
241
- success: false,
242
- code: capability.reason === 'bundled'
243
- ? 'OPENCODE_UPGRADE_MANAGED_BY_OPENCHAMBER'
244
- : 'OPENCODE_UPGRADE_UNSUPPORTED',
245
- error: capability.reason === 'bundled'
246
- ? 'OpenCode is bundled with OpenChamber Desktop and updates with the app.'
247
- : 'This OpenCode runtime cannot be upgraded by OpenChamber.',
248
- });
249
- }
250
- if (openCodeUpgradePromise) {
251
- return res.status(409).json({
252
- success: false,
253
- code: 'OPENCODE_UPGRADE_IN_PROGRESS',
254
- error: 'An OpenCode upgrade is already in progress.',
255
- });
256
- }
257
-
258
- const requestedTarget = req.body?.target;
259
- // The target lookup reaches the network, so it runs inside the operation:
260
- // the in-flight lock is taken synchronously above, and a second click
261
- // cannot slip past while the release version is being resolved.
262
- const upgradeOperation = (async () => {
263
- const targetResolution = await resolveOpenCodeUpgradeTarget(requestedTarget);
264
- if (!targetResolution.resolved) {
265
- return {
266
- status: 502,
267
- body: {
268
- success: false,
269
- code: 'OPENCODE_UPGRADE_TARGET_UNRESOLVED',
270
- error: `Could not determine which OpenCode version to install: ${targetResolution.reason}`,
271
- },
272
- };
273
- }
274
-
275
- const response = await fetch(buildOpenCodeUrl('/global/upgrade', ''), {
276
- method: 'POST',
277
- headers: {
278
- 'Content-Type': 'application/json',
279
- Accept: 'application/json',
280
- ...getOpenCodeAuthHeaders(),
281
- },
282
- body: JSON.stringify({ target: targetResolution.target }),
283
- });
284
- const payload = await response.json().catch(() => null);
285
- if (!response.ok) {
286
- return {
287
- status: response.status,
288
- body: {
289
- success: false,
290
- error: readOpenCodeUpgradeErrorMessage(payload, response),
291
- },
292
- };
293
- }
294
-
295
- try {
296
- await refreshOpenCodeAfterConfigChange('OpenCode upgrade');
297
- } catch (restartError) {
298
- return {
299
- status: 500,
300
- body: {
301
- success: false,
302
- upgraded: true,
303
- error: restartError instanceof Error
304
- ? `OpenCode upgraded, but restart failed: ${restartError.message}`
305
- : 'OpenCode upgraded, but restart failed',
306
- },
307
- };
308
- }
309
-
310
- return {
311
- status: 200,
312
- body: { ...(payload ?? { success: true }), restarted: true },
313
- };
314
- })();
315
- openCodeUpgradePromise = upgradeOperation;
116
+ if (!installInFlight) {
117
+ installInFlight = (async () => {
118
+ const compatibility = await getOpenCodeCompatibility();
119
+ if (!compatibility.canInstall) return false;
120
+ await installOpenCodeV2();
121
+ return true;
122
+ })().finally(() => { installInFlight = null; });
123
+ }
124
+ const installed = await installInFlight;
125
+ if (!installed) return res.status(409).json({ success: false, error: 'Automatic OpenCode v2 installation is unavailable for this runtime.' });
126
+ return res.json({ success: true });
127
+ } catch {
128
+ return res.status(500).json({ success: false, error: 'OpenCode v2 installation or restart failed. Retry or use the installation guide.' });
129
+ }
130
+ });
316
131
 
317
- try {
318
- const result = await upgradeOperation;
319
- return res.status(result.status).json(result.body);
320
- } finally {
321
- if (openCodeUpgradePromise === upgradeOperation) {
322
- openCodeUpgradePromise = null;
323
- }
324
- }
325
- } catch (error) {
326
- console.error('Failed to upgrade OpenCode:', error);
327
- return res.status(500).json({
132
+ let upgradeInFlight = null;
133
+ app.post('/api/opencode/upgrade', async (_req, res) => {
134
+ const capability = getOpenCodeUpgradeCapability();
135
+ if (!capability.supported) {
136
+ const bundled = capability.reason === 'bundled';
137
+ return res.status(409).json({
328
138
  success: false,
329
- error: error instanceof Error ? error.message : 'Failed to upgrade OpenCode',
139
+ code: bundled ? 'OPENCODE_UPGRADE_MANAGED_BY_OPENCHAMBER' : 'OPENCODE_UPGRADE_UNSUPPORTED',
140
+ error: bundled
141
+ ? 'OpenCode is bundled with OpenChamber Desktop and updates with the app.'
142
+ : 'This OpenCode runtime cannot be upgraded by OpenChamber.',
330
143
  });
331
144
  }
145
+ try {
146
+ // Multiple tabs share one installation. Clear both success and failure so
147
+ // a later explicit attempt can run again.
148
+ if (!upgradeInFlight) {
149
+ upgradeInFlight = upgradeOpenCodeCli().finally(() => { upgradeInFlight = null; });
150
+ }
151
+ await upgradeInFlight;
152
+ return res.json({ success: true });
153
+ } catch (error) {
154
+ return res.status(500).json({ success: false, error: error.message });
155
+ }
332
156
  });
333
157
 
334
158
  app.get('/api/opencode/upgrade-status', async (_req, res) => {
335
159
  try {
160
+ // Whether a newer OpenCode exists and whether OpenChamber can install it
161
+ // are two answers: the UI announces the version either way and offers
162
+ // the Update action only when `upgrade.supported` is true.
336
163
  const capability = getOpenCodeUpgradeCapability();
337
- if (!capability.supported) {
338
- const current = await readOpenCodeCurrentVersion().catch(() => ({ ok: false, currentVersion: null }));
339
- return res.json({
340
- available: false,
341
- currentVersion: current.ok ? current.currentVersion : null,
342
- latestVersion: null,
343
- upgrade: capability,
344
- });
345
- }
346
-
347
164
  const [healthResponse, latestVersion] = await Promise.all([
348
- fetch(buildOpenCodeUrl('/global/health', ''), {
165
+ fetch(buildOpenCodeUrl('/api/info', ''), {
349
166
  method: 'GET',
350
167
  headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
351
168
  }),
352
169
  fetchLatestOpenCodeVersion(),
353
170
  ]);
354
- const health = await healthResponse.json().catch(() => null);
171
+ const info = await healthResponse.json().catch(() => null);
355
172
  if (!healthResponse.ok) {
356
173
  return res.status(healthResponse.status).json({
357
174
  available: null,
358
- error: health?.error || healthResponse.statusText || 'Failed to read OpenCode version',
175
+ error: info?.error || healthResponse.statusText || 'Failed to read OpenCode version',
359
176
  });
360
177
  }
361
- const currentVersion = typeof health?.version === 'string' ? health.version.replace(/^v/, '') : null;
178
+ const currentVersion = typeof info?.version === 'string' ? info.version.replace(/^v/, '') : null;
362
179
  if (!currentVersion || !latestVersion) {
363
- return res.json({ available: null, currentVersion, latestVersion: latestVersion || null });
180
+ return res.json({ available: null, currentVersion, latestVersion: latestVersion || null, upgrade: capability });
364
181
  }
365
- const available = compareVersions(latestVersion, currentVersion) > 0;
182
+ // A bundled binary updates together with the desktop app, so a newer
183
+ // OpenCode is not something the user can act on: never announce it.
184
+ const available = capability.reason === 'bundled' ? false : compareVersions(latestVersion, currentVersion) > 0;
366
185
  return res.json({
367
186
  available,
368
187
  currentVersion,
@@ -377,20 +196,24 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
377
196
  }
378
197
  });
379
198
 
199
+ // OpenCode 2.0.8 removed `GET /api/health`; `GET /api/info` replaces it and a
200
+ // 200 from it is the readiness signal (there is no `healthy` field any more).
201
+ // OpenChamber's own `{ healthy }` response shape stays as its clients know it.
380
202
  app.get('/api/opencode/health', async (_req, res) => {
381
203
  try {
382
- const healthResponse = await fetch(buildOpenCodeUrl('/global/health', ''), {
204
+ const healthResponse = await fetch(buildOpenCodeUrl('/api/info', ''), {
383
205
  method: 'GET',
384
206
  headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
385
207
  });
386
- const health = await healthResponse.json().catch(() => null);
208
+ const info = await healthResponse.json().catch(() => null);
387
209
  if (!healthResponse.ok) {
388
210
  return res.status(healthResponse.status).json({
389
211
  healthy: false,
390
- error: health?.error || healthResponse.statusText || 'OpenCode health check failed',
212
+ error: info?.error || healthResponse.statusText || 'OpenCode health check failed',
391
213
  });
392
214
  }
393
- return res.json({ healthy: health?.healthy === true });
215
+ const parsed = await readOpenCodeInfo(Response.json(info));
216
+ return res.json({ healthy: parsed !== null && isSupportedOpenCodeVersion(parsed.version) });
394
217
  } catch (error) {
395
218
  return res.status(503).json({
396
219
  healthy: false,
@@ -401,18 +224,18 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
401
224
 
402
225
  app.get('/api/opencode/version', async (_req, res) => {
403
226
  try {
404
- const healthResponse = await fetch(buildOpenCodeUrl('/global/health', ''), {
227
+ const healthResponse = await fetch(buildOpenCodeUrl('/api/info', ''), {
405
228
  method: 'GET',
406
229
  headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
407
230
  });
408
- const health = await healthResponse.json().catch(() => null);
231
+ const info = await healthResponse.json().catch(() => null);
409
232
  if (!healthResponse.ok) {
410
233
  return res.status(healthResponse.status).json({
411
234
  version: null,
412
- error: health?.error || healthResponse.statusText || 'Failed to read OpenCode version',
235
+ error: info?.error || healthResponse.statusText || 'Failed to read OpenCode version',
413
236
  });
414
237
  }
415
- const version = typeof health?.version === 'string' ? health.version.replace(/^v/, '') : null;
238
+ const version = typeof info?.version === 'string' ? info.version.replace(/^v/, '') : null;
416
239
  return res.json({ version });
417
240
  } catch (error) {
418
241
  return res.status(500).json({
@@ -433,168 +256,6 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
433
256
  }
434
257
  });
435
258
 
436
- // The body parser is per-route on this server; without it req.body is
437
- // undefined here, the state read as absent, and the "parked" context was
438
- // silently never stored — the callback then always failed as unknown.
439
- app.post('/api/mcp/auth/pending', express.json({ limit: '16kb' }), async (req, res) => {
440
- try {
441
- pruneExpiredPendingMcpAuthContexts();
442
-
443
- const state = normalizePendingString(req.body?.state);
444
- if (!state) {
445
- return res.json({ success: true, context: null });
446
- }
447
-
448
- const name = normalizePendingString(req.body?.name);
449
- if (!name) {
450
- return res.status(400).json({ error: 'MCP server name is required' });
451
- }
452
-
453
- const entry = {
454
- name,
455
- directory: normalizePendingString(req.body?.directory),
456
- // Which surface started the flow. It belongs here rather than in the
457
- // redirect URI: that URI is written into the server's config once and
458
- // deliberately never rewritten, so anything encoded in it would be
459
- // frozen at whatever runtime authorised first.
460
- origin: normalizePendingString(req.body?.origin),
461
- expiresAt: Date.now() + PENDING_MCP_AUTH_TTL_MS,
462
- };
463
- pendingMcpAuthContextByState.set(state, entry);
464
-
465
- return res.json({
466
- success: true,
467
- context: {
468
- name: entry.name,
469
- directory: entry.directory,
470
- origin: entry.origin,
471
- },
472
- });
473
- } catch (error) {
474
- console.error('Failed to store pending MCP auth context:', error);
475
- return res.status(500).json({ error: error.message || 'Failed to store pending MCP auth context' });
476
- }
477
- });
478
-
479
- app.get('/api/mcp/auth/pending', async (req, res) => {
480
- try {
481
- pruneExpiredPendingMcpAuthContexts();
482
-
483
- const state = normalizePendingString(Array.isArray(req.query?.state) ? req.query.state[0] : req.query?.state);
484
- if (!state) {
485
- return res.json(null);
486
- }
487
-
488
- const pendingMcpAuthContext = pendingMcpAuthContextByState.get(state) ?? null;
489
- if (!pendingMcpAuthContext) {
490
- return res.status(404).json({ error: 'No pending MCP auth context' });
491
- }
492
-
493
- return res.json(pendingMcpAuthContext);
494
- } catch (error) {
495
- console.error('Failed to read pending MCP auth context:', error);
496
- return res.status(500).json({ error: error.message || 'Failed to read pending MCP auth context' });
497
- }
498
- });
499
-
500
- app.delete('/api/mcp/auth/pending', async (req, res) => {
501
- try {
502
- const state = normalizePendingString(Array.isArray(req.query?.state) ? req.query.state[0] : req.query?.state);
503
- if (!state) {
504
- return res.json({ success: true });
505
- }
506
-
507
- pendingMcpAuthContextByState.delete(state);
508
- return res.json({ success: true });
509
- } catch (error) {
510
- console.error('Failed to clear pending MCP auth context:', error);
511
- return res.status(500).json({ error: error.message || 'Failed to clear pending MCP auth context' });
512
- }
513
- });
514
-
515
- // Browser return leg of the MCP OAuth flow, completed entirely server-side.
516
- //
517
- // The provider redirects the SYSTEM browser here, and that browser has no
518
- // OpenChamber UI session — the SPA route this path used to land on sits
519
- // behind the client-side auth gate, so the user saw a login page instead of
520
- // a finished authorization. No session can be required on this path.
521
- //
522
- // Safe without auth because it acts only on a code+state pair whose `state`
523
- // matches a context parked by an authenticated start call: `state` is the
524
- // OAuth CSRF secret, generated per flow and known only to the initiating
525
- // client and the provider. Without a match the code is NOT forwarded, so an
526
- // unauthenticated caller cannot bind this server's MCP entry to a foreign
527
- // account by fabricating a callback. The endpoint reads nothing and mutates
528
- // nothing else.
529
- app.get('/mcp/oauth/callback', async (req, res) => {
530
- const queryValue = (key) => normalizePendingString(Array.isArray(req.query?.[key]) ? req.query[key][0] : req.query?.[key]);
531
- const state = queryValue('state');
532
- const code = queryValue('code');
533
- const providerError = queryValue('error');
534
- const providerErrorDescription = queryValue('error_description');
535
-
536
- pruneExpiredPendingMcpAuthContexts();
537
- const context = state ? pendingMcpAuthContextByState.get(state) ?? null : null;
538
- const startedFromDesktop = context?.origin === 'desktop';
539
-
540
- const finish = (status, { title, message }) => {
541
- if (state) pendingMcpAuthContextByState.delete(state);
542
- res.status(status).type('html').send(renderMcpOAuthCallbackPage({
543
- title,
544
- message,
545
- // Browsers only follow custom-protocol links from a user gesture in
546
- // some configurations, so the page both tries the jump and keeps a
547
- // visible link as the fallback.
548
- desktopReturn: startedFromDesktop,
549
- }));
550
- };
551
-
552
- if (providerError) {
553
- return finish(400, {
554
- title: 'Authorization Failed',
555
- message: providerErrorDescription || providerError,
556
- });
557
- }
558
- if (!code) {
559
- return finish(400, {
560
- title: 'Authorization Failed',
561
- message: 'The provider did not return an authorization code. Start authorization again from MCP Settings.',
562
- });
563
- }
564
- if (!context?.name) {
565
- return finish(400, {
566
- title: 'Authorization Failed',
567
- message: 'This authorization session has expired or is unknown to the running app. Return to OpenChamber and click Authorize again.',
568
- });
569
- }
570
-
571
- try {
572
- const callbackUrl = new URL(buildOpenCodeUrl(`/mcp/${encodeURIComponent(context.name)}/auth/callback`, ''));
573
- if (context.directory) callbackUrl.searchParams.set('directory', context.directory);
574
- const upstream = await fetch(callbackUrl, {
575
- method: 'POST',
576
- headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...getOpenCodeAuthHeaders() },
577
- body: JSON.stringify({ code }),
578
- });
579
- if (!upstream.ok) {
580
- const payload = await upstream.json().catch(() => null);
581
- return finish(502, {
582
- title: 'Authorization Failed',
583
- message: payload?.error || payload?.message || `OpenCode rejected the authorization code (${upstream.status}). Start authorization again from MCP Settings.`,
584
- });
585
- }
586
- return finish(200, {
587
- title: 'Authorization Complete',
588
- message: 'You can close this tab and return to OpenChamber.',
589
- });
590
- } catch (error) {
591
- return finish(502, {
592
- title: 'Authorization Failed',
593
- message: error?.message || 'Failed to complete MCP authorization.',
594
- });
595
- }
596
- });
597
-
598
259
  app.get('/api/provider/:providerId/source', async (req, res) => {
599
260
  try {
600
261
  const { providerId } = req.params;
@@ -676,8 +337,8 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
676
337
  const upsertResult = upsertProviderConfig(providerID, config, directory, scope, { hasStoredAuth });
677
338
 
678
339
  return res.json({
679
- ...buildDeferredRestartResponse(
680
- `Provider ${providerID} saved. Restart OpenCode to apply.`,
340
+ ...buildAppliedResponse(
341
+ `Provider ${providerID} saved.`,
681
342
  ),
682
343
  providerId: upsertResult.providerId,
683
344
  path: upsertResult.path,
@@ -690,6 +351,34 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
690
351
  }
691
352
  });
692
353
 
354
+ // The web search choice (`websearch` in OpenCode config). OpenCode watches
355
+ // the file and announces `config.updated`, so nothing restarts.
356
+ // Whether a project config decides `websearch` for the directory, so
357
+ // Settings can say so instead of letting a write snap back.
358
+ app.get('/api/config/websearch', async (req, res) => {
359
+ try {
360
+ const resolved = await resolveProjectDirectory(req);
361
+ return res.json(getWebSearchSource(resolved.directory || null));
362
+ } catch (error) {
363
+ console.error('Failed to read the web search config source:', error);
364
+ return res.status(500).json({ error: error.message || 'Failed to read the web search config source' });
365
+ }
366
+ });
367
+
368
+ app.put('/api/config/websearch', (req, res) => {
369
+ const selection = parseWebSearchSelection(req.body?.selection);
370
+ if (selection === undefined) {
371
+ return res.status(400).json({ error: 'selection must be false, null, "random" or a provider id' });
372
+ }
373
+ try {
374
+ const result = setWebSearchSelection(selection);
375
+ return res.json({ success: true, changed: result.changed });
376
+ } catch (error) {
377
+ console.error('Failed to save the web search choice:', error);
378
+ return res.status(500).json({ error: error.message || 'Failed to save the web search choice' });
379
+ }
380
+ });
381
+
693
382
  app.delete('/api/provider/:providerId/auth', async (req, res) => {
694
383
  try {
695
384
  const { providerId } = req.params;
@@ -718,19 +407,23 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
718
407
  }
719
408
  }
720
409
 
410
+ // OpenCode 2.x owns credentials: it imported `auth.json` once and now
411
+ // keeps them in its own store behind `/api/credential`. OpenChamber can
412
+ // still remove a provider's CONFIG (those files are ours), but a
413
+ // credential has to be removed where it lives.
721
414
  let removed = false;
722
415
  if (scope === 'auth') {
723
- const { removeProviderAuth } = await getAuthLibrary();
724
- removed = removeProviderAuth(providerId);
416
+ return res.status(409).json({
417
+ error: 'OpenCode 2 stores provider credentials itself. Disconnect the provider in Settings, which asks OpenCode to remove it.',
418
+ code: 'PROVIDER_CREDENTIAL_OWNED_BY_OPENCODE',
419
+ });
725
420
  } else if (scope === 'user' || scope === 'project' || scope === 'custom') {
726
421
  removed = removeProviderConfig(providerId, directory, scope);
727
422
  } else if (scope === 'all') {
728
- const { removeProviderAuth } = await getAuthLibrary();
729
- const authRemoved = removeProviderAuth(providerId);
730
423
  const userRemoved = removeProviderConfig(providerId, directory, 'user');
731
424
  const projectRemoved = directory ? removeProviderConfig(providerId, directory, 'project') : false;
732
425
  const customRemoved = removeProviderConfig(providerId, directory, 'custom');
733
- removed = authRemoved || userRemoved || projectRemoved || customRemoved;
426
+ removed = userRemoved || projectRemoved || customRemoved;
734
427
  } else {
735
428
  return res.status(400).json({ error: 'Invalid scope' });
736
429
  }
@@ -739,7 +432,7 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
739
432
  return res.json({
740
433
  success: true,
741
434
  removed,
742
- ...buildDeferredRestartResponse('Provider disconnected successfully. Restart OpenCode to apply.'),
435
+ ...buildAppliedResponse('Provider disconnected successfully.'),
743
436
  });
744
437
  }
745
438
 
@@ -845,8 +538,8 @@ ${desktopReturn ? `<a class="return" href="openchamber://focus/mcp-auth">Return
845
538
 
846
539
  await fs.promises.writeFile(AGENTS_MD_PATH, content, 'utf8');
847
540
 
848
- return res.json(buildDeferredRestartResponse(
849
- 'AGENTS.md saved. Restart OpenCode to apply.',
541
+ return res.json(buildAppliedResponse(
542
+ 'AGENTS.md saved.',
850
543
  ));
851
544
  } catch (error) {
852
545
  console.error('Failed to write AGENTS.md:', error);