@vantran-se/openchamber-web 1.24.3 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -8
- package/bin/lib/commands-connect-url.js +3 -8
- package/bin/lib/commands-tunnel.js +3 -0
- package/dist/assets/{CodeMirrorCommentWidgets-BjJHrzmU.js → CodeMirrorCommentWidgets-ntXIbhV2.js} +1 -1
- package/dist/assets/DiffView-CjxUovbM.js +4 -0
- package/dist/assets/FilesView-DFWD0RQR.js +17 -0
- package/dist/assets/{GitView-CDOJy5Ay.js → GitView-BWa7e5vw.js} +4 -4
- package/dist/assets/{GuestAttachDialog-D3S0Rjic.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
- package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
- package/dist/assets/{JsonTreeView-D0C9nU7K.js → JsonTreeView-3sGwqexh.js} +1 -1
- package/dist/assets/LinearIssuesView-C6dpIdjw.js +1 -0
- package/dist/assets/MarkdownImageGallery-Q7YR_tl4.js +1 -0
- package/dist/assets/MarkdownRendererImpl-DxDV56bL.js +12 -0
- package/dist/assets/OnboardingScreen-rK4Stg__.js +1 -0
- package/dist/assets/{PatchDiff-DnEhjXcC.js → PatchDiff-DS-IMU1j.js} +1 -1
- package/dist/assets/{PierreDiffViewer-DyxZVVVR.js → PierreDiffViewer-DW23KWKZ.js} +1 -1
- package/dist/assets/PlanView-BCk_VobP.js +5 -0
- package/dist/assets/PluginPane-BPP1dbck.js +3 -0
- package/dist/assets/SettingsView-kMmrpn43.js +29 -0
- package/dist/assets/SettingsWindow-BpvD6IYQ.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-BLttSS1X.js +1 -0
- package/dist/assets/ToolOutputDialog-CtqDqbyr.js +15 -0
- package/dist/assets/ToolPartDiffPreview-5FcKdyRy.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-0pyr_mHY.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
- package/dist/assets/{WalkthroughView-yNj4nOP5.js → WalkthroughView-lap5btCF.js} +2 -2
- package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
- package/dist/assets/{commitAndPush-DvJKa3Ks.js → commitAndPush-CwGJfJG-.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DxTBMuu-.js → computeVirtualFileMetrics-DW5jjX4n.js} +1 -1
- package/dist/assets/de-CndEg67T.js +10 -0
- package/dist/assets/es-DglvPlaC.js +16 -0
- package/dist/assets/fr-DejPCpbS.js +14 -0
- package/dist/assets/index-C2r64QfD.js +1 -0
- package/dist/assets/index-CLcM8HKx.css +1 -0
- package/dist/assets/index-CvLgnuNI.js +41 -0
- package/dist/assets/{isDiffPlainText-D7FkBMP6.js → isDiffPlainText-D47a6hMP.js} +1 -1
- package/dist/assets/{isFilePlainText-CL2vsrIZ.js → isFilePlainText-B-mGgeKl.js} +1 -1
- package/dist/assets/ja-CVpQawCd.js +16 -0
- package/dist/assets/ko-Bw446h9j.js +16 -0
- package/dist/assets/main-Bz4WWXbr.js +2 -0
- package/dist/assets/main-oreqDsOV.js +896 -0
- package/dist/assets/{markdownCore-Dh7WEsdM.js → markdownCore-D2e5sp9y.js} +4 -4
- package/dist/assets/miniChat-D1v2gsBI.js +2 -0
- package/dist/assets/mobile-DBj3lCf2.js +2 -0
- package/dist/assets/{patchFileDiff-Bsba8KFp.js → patchFileDiff-BpeUOV91.js} +3 -3
- package/dist/assets/pl-BYNlPdhm.js +16 -0
- package/dist/assets/pt-BR-CL-Yfv9H.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-DVtfEB2z.js +2 -0
- package/dist/assets/renderMobileApp-DFp9-xbG.js +25 -0
- package/dist/assets/runtimeEndpointReset-CsIpUep1.js +27 -0
- package/dist/assets/scheduledTasksApi-B5OHVpPM.js +1 -0
- package/dist/assets/{shikiHighlight-8_3csmr6.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-CHo2fCgW.js → textMateThemeFromAppTheme-DGF2kG7g.js} +1 -1
- package/dist/assets/tr-DzmbxLy-.js +7 -0
- package/dist/assets/uk-C7vQnwro.js +16 -0
- package/dist/assets/useAppFontEffects-Ci3FEaPf.js +7337 -0
- package/dist/assets/{useInlineCommentController-DkQ_gqpn.js → useInlineCommentController-CuUuakrt.js} +2 -2
- package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-CdzUmUyZ.js → vendor-codemirror-language-data-CQa7_1cr.js} +2 -2
- package/dist/assets/vendor-cron-parser-CjvYVigk.js +2 -0
- package/dist/assets/vendor-opencode-client-_BAHQo4R.js +12 -0
- package/dist/assets/{vendor-remixicon-react-BDmGmFCA.js → vendor-remixicon-react-D6dZ0UwF.js} +1 -1
- package/dist/assets/{vendor-zod-CgAdXWZO.js → vendor-zod-Gea9uBlY.js} +1 -1
- package/dist/assets/{web-components-B8IcVQAA.js → web-components-B6tKQyJN.js} +2 -2
- package/dist/assets/zh-CN-Cl0srS-z.js +16 -0
- package/dist/assets/zh-TW-DtII5bDb.js +16 -0
- package/dist/index.html +13 -7
- package/dist/mini-chat.html +5 -5
- package/dist/mobile.html +5 -5
- package/package.json +6 -5
- package/server/index.d.ts +1 -0
- package/server/index.js +246 -91
- package/server/lib/agent-tool/DOCUMENTATION.md +32 -15
- package/server/lib/agent-tool/runtime.js +135 -54
- package/server/lib/agent-tool/runtime.test.js +155 -87
- package/server/lib/browser-control/DOCUMENTATION.md +37 -2
- package/server/lib/browser-control/provider.js +217 -0
- package/server/lib/browser-control/provider.test.js +251 -0
- package/server/lib/context-obligatory/runtime.js +61 -50
- package/server/lib/context-obligatory/runtime.test.js +62 -162
- package/server/lib/event-stream/DOCUMENTATION.md +12 -1
- package/server/lib/event-stream/delta-coalescer.js +0 -0
- package/server/lib/event-stream/delta-coalescer.test.js +68 -57
- package/server/lib/event-stream/directory-ws-bridge.js +1 -1
- package/server/lib/event-stream/global-hub.js +10 -1
- package/server/lib/event-stream/global-hub.test.js +27 -26
- package/server/lib/event-stream/index.js +6 -0
- package/server/lib/event-stream/protocol.js +26 -19
- package/server/lib/event-stream/protocol.test.js +18 -7
- package/server/lib/event-stream/rebind.test.js +5 -5
- package/server/lib/event-stream/runtime.test.js +17 -15
- package/server/lib/event-stream/translate-v2.js +313 -0
- package/server/lib/event-stream/translate-v2.test.js +209 -0
- package/server/lib/event-stream/upstream-reader.test.js +16 -16
- package/server/lib/fs/DOCUMENTATION.md +4 -2
- package/server/lib/fs/byte-range.js +35 -0
- package/server/lib/fs/byte-range.test.js +31 -0
- package/server/lib/fs/routes.js +47 -42
- package/server/lib/fs/routes.test.js +123 -18
- package/server/lib/git/service.js +12 -0
- package/server/lib/github/DOCUMENTATION.md +1 -1
- package/server/lib/github/pr-status.js +22 -2
- package/server/lib/github/pr-status.test.js +27 -14
- package/server/lib/guests/DOCUMENTATION.md +13 -0
- package/server/lib/guests/catalog.js +12 -0
- package/server/lib/guests/catalog.test.js +33 -0
- package/server/lib/guests/routes.js +10 -1
- package/server/lib/guests/service.js +192 -14
- package/server/lib/guests/service.test.js +201 -1
- package/server/lib/guests/surface.js +630 -0
- package/server/lib/guests/surface.test.js +486 -0
- package/server/lib/markdown-image-grants/routes.js +18 -5
- package/server/lib/markdown-image-grants/routes.test.js +9 -6
- package/server/lib/message-queue/DOCUMENTATION.md +33 -22
- package/server/lib/message-queue/runtime.js +155 -100
- package/server/lib/message-queue/runtime.test.js +168 -82
- package/server/lib/notifications/DOCUMENTATION.md +28 -0
- package/server/lib/notifications/control-stream.http.test.js +68 -0
- package/server/lib/notifications/emitter-runtime.js +15 -0
- package/server/lib/notifications/emitter-runtime.test.js +32 -0
- package/server/lib/notifications/push-runtime.js +5 -1
- package/server/lib/notifications/push-runtime.test.js +2 -2
- package/server/lib/notifications/routes.js +1 -1
- package/server/lib/notifications/runtime.js +115 -26
- package/server/lib/notifications/template-runtime.js +13 -15
- package/server/lib/notifications/template-runtime.test.js +16 -9
- package/server/lib/notifications/trigger-runtime.test.js +187 -0
- package/server/lib/openchamber-control/DOCUMENTATION.md +10 -0
- package/server/lib/openchamber-control/actions.js +3 -2
- package/server/lib/openchamber-control/error.js +1 -1
- package/server/lib/openchamber-control/file-open.js +59 -0
- package/server/lib/openchamber-control/file-open.test.js +57 -0
- package/server/lib/openchamber-control/routes.test.js +12 -0
- package/server/lib/openchamber-control/service.js +102 -49
- package/server/lib/openchamber-control/service.test.js +84 -44
- package/server/lib/openchamber-sessions/archive-store.js +224 -0
- package/server/lib/openchamber-sessions/archive-store.test.js +187 -0
- package/server/lib/openchamber-sessions/opencode-client.js +22 -0
- package/server/lib/openchamber-sessions/routes.js +398 -328
- package/server/lib/openchamber-sessions/routes.test.js +777 -740
- package/server/lib/openchamber-sessions/session-metadata-store.js +285 -0
- package/server/lib/openchamber-sessions/session-metadata-store.test.js +259 -0
- package/server/lib/opencode/DOCUMENTATION.md +449 -29
- package/server/lib/opencode/agents.js +233 -327
- package/server/lib/opencode/auth.js +41 -44
- package/server/lib/opencode/auth.test.js +92 -0
- package/server/lib/opencode/cli-upgrade.d.ts +4 -0
- package/server/lib/opencode/cli-upgrade.js +18 -0
- package/server/lib/opencode/cli-upgrade.test.js +36 -0
- package/server/lib/opencode/commands.js +164 -145
- package/server/lib/opencode/compatibility.d.ts +16 -0
- package/server/lib/opencode/compatibility.js +88 -0
- package/server/lib/opencode/compatibility.test.js +62 -0
- package/server/lib/opencode/config-entity-routes.js +58 -29
- package/server/lib/opencode/config-entity-v2.test.js +290 -0
- package/server/lib/opencode/config-mutation-response.js +17 -7
- package/server/lib/opencode/config-mutation-response.test.js +4 -7
- package/server/lib/opencode/config-paths.test.js +2 -2
- package/server/lib/opencode/config-v2.d.ts +183 -0
- package/server/lib/opencode/config-v2.js +801 -0
- package/server/lib/opencode/config-v2.test.js +466 -0
- package/server/lib/opencode/credential-db.js +144 -0
- package/server/lib/opencode/credential-db.test.js +104 -0
- package/server/lib/opencode/env-runtime.js +48 -9
- package/server/lib/opencode/env-runtime.test.js +45 -0
- package/server/lib/opencode/feature-routes-runtime.js +15 -5
- package/server/lib/opencode/lifecycle-process.test.js +7 -4
- package/server/lib/opencode/lifecycle.js +85 -30
- package/server/lib/opencode/lifecycle.test.js +80 -8
- package/server/lib/opencode/login-shell-env.js +27 -0
- package/server/lib/opencode/managed-config-file.js +119 -0
- package/server/lib/opencode/managed-config-file.test.js +152 -0
- package/server/lib/opencode/managed-plugin-config.js +33 -13
- package/server/lib/opencode/mcp.js +43 -149
- package/server/lib/opencode/network-runtime.js +6 -7
- package/server/lib/opencode/openchamber-routes.test.js +2 -5
- package/server/lib/opencode/plugin-routes.js +3 -3
- package/server/lib/opencode/plugin-routes.test.js +6 -24
- package/server/lib/opencode/plugins.js +91 -40
- package/server/lib/opencode/plugins.test.js +28 -15
- package/server/lib/opencode/providers.js +63 -83
- package/server/lib/opencode/providers.test.js +128 -59
- package/server/lib/opencode/proxy-agent-wiring.test.js +4 -2
- package/server/lib/opencode/proxy.js +179 -129
- package/server/lib/opencode/pwa-manifest-routes.js +4 -2
- package/server/lib/opencode/pwa-manifest-routes.test.js +3 -2
- package/server/lib/opencode/response-envelope.js +14 -0
- package/server/lib/opencode/response-envelope.test.js +23 -0
- package/server/lib/opencode/routes-upgrade.test.js +73 -116
- package/server/lib/opencode/routes.js +121 -428
- package/server/lib/opencode/server-utils-runtime.js +10 -4
- package/server/lib/opencode/session-activity.js +78 -0
- package/server/lib/opencode/session-runtime.js +21 -10
- package/server/lib/opencode/session-runtime.test.js +9 -9
- package/server/lib/opencode/settings-helpers.js +9 -9
- package/server/lib/opencode/settings-helpers.test.js +7 -12
- package/server/lib/opencode/settings-registry.json +6 -8
- package/server/lib/opencode/settings-runtime.js +18 -1
- package/server/lib/opencode/settings-runtime.test.js +31 -2
- package/server/lib/opencode/shared.js +54 -22
- package/server/lib/opencode/shared.test.js +8 -4
- package/server/lib/opencode/shutdown-runtime.js +54 -7
- package/server/lib/opencode/shutdown-runtime.test.js +224 -30
- package/server/lib/opencode/skill-routes.js +23 -24
- package/server/lib/opencode/skills.js +7 -5
- package/server/lib/opencode/tunnel-wiring-runtime.js +2 -1
- package/server/lib/opencode/upgrade-capability.test.js +1 -1
- package/server/lib/opencode/v1-migration-topup.js +288 -0
- package/server/lib/opencode/v1-migration-topup.test.js +266 -0
- package/server/lib/opencode/v2-install.d.ts +8 -0
- package/server/lib/opencode/v2-install.js +158 -0
- package/server/lib/opencode/v2-install.test.js +124 -0
- package/server/lib/opencode/watcher.js +10 -3
- package/server/lib/opencode/watcher.test.js +17 -21
- package/server/lib/opencode/websearch-config.js +66 -0
- package/server/lib/package-manager.js +55 -68
- package/server/lib/package-manager.test.js +241 -75
- package/server/lib/permission-auto-accept/runtime.js +38 -22
- package/server/lib/permission-auto-accept/runtime.test.js +39 -17
- package/server/lib/quota/DOCUMENTATION.md +4 -0
- package/server/lib/quota/providers/xai.js +4 -4
- package/server/lib/relay/tunnel-host.js +4 -1
- package/server/lib/relay/tunnel-host.test.js +7 -0
- package/server/lib/routing/DOCUMENTATION.md +52 -21
- package/server/lib/routing/defaults.js +23 -6
- package/server/lib/routing/jev.js +17 -7
- package/server/lib/routing/routes.http.test.js +68 -33
- package/server/lib/routing/routes.js +65 -22
- package/server/lib/routing/runtime.js +145 -48
- package/server/lib/routing/runtime.test.js +79 -55
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +4 -0
- package/server/lib/scheduled-tasks/issue-2710-double-execution.test.js +51 -46
- package/server/lib/scheduled-tasks/runtime.js +81 -74
- package/server/lib/scheduled-tasks/runtime.test.js +53 -1
- package/server/lib/session-assist/DOCUMENTATION.md +8 -3
- package/server/lib/session-assist/context.js +122 -46
- package/server/lib/session-assist/context.test.js +117 -49
- package/server/lib/session-assist/runtime.js +52 -22
- package/server/lib/session-assist/runtime.test.js +145 -173
- package/server/lib/session-goal/DOCUMENTATION.md +23 -15
- package/server/lib/session-goal/create.js +9 -12
- package/server/lib/session-goal/create.test.js +27 -28
- package/server/lib/session-goal/runtime.js +164 -77
- package/server/lib/session-goal/runtime.test.js +271 -519
- package/server/lib/session-knowledge/DOCUMENTATION.md +5 -0
- package/server/lib/session-knowledge/runtime.js +59 -41
- package/server/lib/session-knowledge/runtime.test.js +90 -20
- package/server/lib/small-model/DOCUMENTATION.md +154 -208
- package/server/lib/small-model/client.js +113 -0
- package/server/lib/small-model/index.js +296 -158
- package/server/lib/small-model/index.test.js +423 -256
- package/server/lib/small-model/routes.js +1 -1
- package/server/lib/spaces/DOCUMENTATION.md +807 -0
- package/server/lib/spaces/code-in-bait.js +185 -0
- package/server/lib/spaces/code-in.js +641 -0
- package/server/lib/spaces/code-in.test.js +1048 -0
- package/server/lib/spaces/errors.js +8 -0
- package/server/lib/spaces/exec-http.js +94 -0
- package/server/lib/spaces/gatekeeper-channel.js +202 -0
- package/server/lib/spaces/gatekeeper-channel.test.js +215 -0
- package/server/lib/spaces/gatekeeper-program.cjs +914 -0
- package/server/lib/spaces/gatekeeper-program.test.js +1391 -0
- package/server/lib/spaces/hardening.js +457 -0
- package/server/lib/spaces/hardening.test.js +368 -0
- package/server/lib/spaces/host-git.js +127 -0
- package/server/lib/spaces/host-git.test.js +137 -0
- package/server/lib/spaces/labels.js +234 -0
- package/server/lib/spaces/labels.test.js +219 -0
- package/server/lib/spaces/layout.js +184 -0
- package/server/lib/spaces/layout.test.js +30 -0
- package/server/lib/spaces/manager.js +82 -0
- package/server/lib/spaces/manager.test.js +114 -0
- package/server/lib/spaces/places/code-in.docker.live.test.js +216 -0
- package/server/lib/spaces/places/contract-suite.js +156 -0
- package/server/lib/spaces/places/contract.docker.live.test.js +7 -0
- package/server/lib/spaces/places/contract.test.js +6 -0
- package/server/lib/spaces/places/docker-engine.js +126 -0
- package/server/lib/spaces/places/docker-live-support.js +202 -0
- package/server/lib/spaces/places/docker-tools.js +186 -0
- package/server/lib/spaces/places/docker.js +587 -0
- package/server/lib/spaces/places/docker.test.js +1610 -0
- package/server/lib/spaces/places/escape-suite.js +1018 -0
- package/server/lib/spaces/places/escape.docker.live.test.js +7 -0
- package/server/lib/spaces/places/fake-docker.js +375 -0
- package/server/lib/spaces/places/memory-place.js +103 -0
- package/server/lib/spaces/places/packed.docker.live.test.js +77 -0
- package/server/lib/spaces/places/registry.js +43 -0
- package/server/lib/spaces/places/registry.test.js +50 -0
- package/server/lib/spaces/places/server.docker.live.test.js +225 -0
- package/server/lib/spaces/run-command.js +261 -0
- package/server/lib/spaces/run-command.test.js +396 -0
- package/server/lib/spaces/space-server.js +190 -0
- package/server/lib/spaces/space-server.test.js +335 -0
- package/server/lib/spaces/tools-filler.js +82 -0
- package/server/lib/spaces/tools-filler.test.js +148 -0
- package/server/lib/spaces/tools-pack.js +49 -0
- package/server/lib/spaces/tools-pack.test.js +57 -0
- package/server/lib/spaces/tools.js +138 -0
- package/server/lib/spaces/tools.test.js +153 -0
- package/server/lib/tts/openai-sdk.js +11 -0
- package/server/lib/tts/service.js +7 -5
- package/server/lib/tts/stt.js +3 -2
- package/server/lib/tunnels/DOCUMENTATION.md +2 -0
- package/server/lib/tunnels/routes.js +7 -0
- package/server/lib/tunnels/routes.test.js +55 -0
- package/server/lib/ui-auth/ui-auth.js +38 -2
- package/server/lib/ui-auth/ui-auth.test.js +42 -0
- package/server/lib/ui-auth/ui-passkeys.js +12 -10
- package/server/lib/walkthrough/DOCUMENTATION.md +10 -1
- package/server/lib/walkthrough/pull-request.js +61 -8
- package/server/lib/walkthrough/pull-request.test.js +54 -1
- package/server/lib/walkthrough/routes.js +20 -0
- package/server/lib/walkthrough/routes.test.js +19 -0
- package/server/opencode-proxy.test.js +190 -166
- package/server/opencode-worktree-readiness.test.js +5 -4
- package/dist/assets/DiffView-COVQhWGd.js +0 -4
- package/dist/assets/FilesView-B1V5JxFX.js +0 -13
- package/dist/assets/LinearIssuesView-D7FKg1XJ.js +0 -1
- package/dist/assets/MarkdownImageGallery-BrFjAVEf.js +0 -1
- package/dist/assets/MarkdownRendererImpl-DWepTafh.js +0 -12
- package/dist/assets/OnboardingScreen-Dyxxiqdt.js +0 -1
- package/dist/assets/PlanView-LW2z_EAP.js +0 -5
- package/dist/assets/PluginPane-B4tKno2h.js +0 -3
- package/dist/assets/SettingsView-DEaMulN9.js +0 -29
- package/dist/assets/SettingsWindow-FLDJXOgQ.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-CvVwD-Vi.js +0 -1
- package/dist/assets/ToolOutputDialog-BiP0x2Rw.js +0 -15
- package/dist/assets/ToolPartDiffPreview-CkR4msKP.js +0 -8
- package/dist/assets/appThemeRegistry-BuUKhbYQ.js +0 -1
- package/dist/assets/de-DgxgFyGG.js +0 -10
- package/dist/assets/es-BFsZj5F_.js +0 -16
- package/dist/assets/fr-jdZ5KKQi.js +0 -14
- package/dist/assets/index-CKHjPKbq.js +0 -1
- package/dist/assets/index-Cbci4i1W.js +0 -40
- package/dist/assets/index-DqaMWNrC.css +0 -1
- package/dist/assets/ja-C5kKrhO4.js +0 -16
- package/dist/assets/ko-DXDTT_0O.js +0 -16
- package/dist/assets/main-CZALtVek.js +0 -896
- package/dist/assets/main-CgXMBNvG.js +0 -2
- package/dist/assets/miniChat-CwTmDixi.js +0 -2
- package/dist/assets/mobile-CLf2vCtO.js +0 -2
- package/dist/assets/pl-D-7x4Y8Q.js +0 -16
- package/dist/assets/pt-BR-CSBZrHa8.js +0 -16
- package/dist/assets/renderElectronMiniChatApp-C5gAFBk0.js +0 -2
- package/dist/assets/renderMobileApp-V4hYd32H.js +0 -25
- package/dist/assets/runtimeEndpointReset-DhlHCn-2.js +0 -25
- package/dist/assets/scheduledTasksApi-DzjbLd8P.js +0 -1
- package/dist/assets/tr-Dr-oJ22L.js +0 -7
- package/dist/assets/uk-9Nx83UcI.js +0 -16
- package/dist/assets/useAppFontEffects-khZXpFL_.js +0 -7333
- package/dist/assets/useRootScrollLock-DdvHf9Pt.js +0 -2
- package/dist/assets/vendor-cron-parser-CUcP7OfX.js +0 -2
- package/dist/assets/vendor-opencode-sdk-BJ73u-bH.js +0 -7
- package/dist/assets/zh-CN-Bxy_Hj3r.js +0 -16
- package/dist/assets/zh-TW-0BB4gpHM.js +0 -16
- package/server/lib/opencode/mcp-oauth-callback.test.js +0 -111
- package/server/lib/routing/feature-flag.js +0 -11
- package/server/lib/small-model/call.js +0 -851
- package/server/lib/small-model/call.test.js +0 -1292
- package/server/lib/small-model/catalog.js +0 -13
- package/server/lib/small-model/resolve.js +0 -131
- package/server/lib/small-model/resolve.test.js +0 -197
- package/server/lib/small-model/runtime-providers.js +0 -164
- package/server/lib/small-model/runtime-providers.test.js +0 -118
- package/server/lib/system-prompt/DOCUMENTATION.md +0 -36
- package/server/lib/system-prompt/runtime.js +0 -49
- package/server/lib/system-prompt/runtime.test.js +0 -76
- 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
|
-
|
|
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, '<')
|
|
55
|
-
.replace(/>/g, '>')
|
|
56
|
-
.replace(/"/g, '"')
|
|
57
|
-
.replace(/'/g, ''');
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
+
let installInFlight = null;
|
|
114
|
+
app.post('/api/opencode/install-v2', async (_req, res) => {
|
|
237
115
|
try {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
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('/
|
|
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
|
|
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:
|
|
175
|
+
error: info?.error || healthResponse.statusText || 'Failed to read OpenCode version',
|
|
359
176
|
});
|
|
360
177
|
}
|
|
361
|
-
const currentVersion = typeof
|
|
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
|
-
|
|
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('/
|
|
204
|
+
const healthResponse = await fetch(buildOpenCodeUrl('/api/info', ''), {
|
|
383
205
|
method: 'GET',
|
|
384
206
|
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
|
|
385
207
|
});
|
|
386
|
-
const
|
|
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:
|
|
212
|
+
error: info?.error || healthResponse.statusText || 'OpenCode health check failed',
|
|
391
213
|
});
|
|
392
214
|
}
|
|
393
|
-
|
|
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('/
|
|
227
|
+
const healthResponse = await fetch(buildOpenCodeUrl('/api/info', ''), {
|
|
405
228
|
method: 'GET',
|
|
406
229
|
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
|
|
407
230
|
});
|
|
408
|
-
const
|
|
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:
|
|
235
|
+
error: info?.error || healthResponse.statusText || 'Failed to read OpenCode version',
|
|
413
236
|
});
|
|
414
237
|
}
|
|
415
|
-
const version = typeof
|
|
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
|
-
...
|
|
680
|
-
`Provider ${providerID} saved
|
|
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
|
-
|
|
724
|
-
|
|
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 =
|
|
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
|
-
...
|
|
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(
|
|
849
|
-
'AGENTS.md saved.
|
|
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);
|