@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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, expect, it, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
|
|
7
|
+
import { projectCredentialValue, readCredentialsFromDb, resolveCredentialDbPath } from './credential-db.js';
|
|
8
|
+
|
|
9
|
+
const sqlite = (() => {
|
|
10
|
+
try {
|
|
11
|
+
return createRequire(import.meta.url)('node:sqlite');
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
})();
|
|
16
|
+
|
|
17
|
+
const seed = (dbPath, rows) => {
|
|
18
|
+
const db = new sqlite.DatabaseSync(dbPath);
|
|
19
|
+
db.exec(
|
|
20
|
+
'CREATE TABLE credential (id TEXT PRIMARY KEY, integration_id TEXT, label TEXT NOT NULL, value TEXT NOT NULL, ' +
|
|
21
|
+
'connector_id TEXT, method_id TEXT, active INTEGER, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL)',
|
|
22
|
+
);
|
|
23
|
+
const insert = db.prepare(
|
|
24
|
+
'INSERT INTO credential (id, integration_id, label, value, active, time_created, time_updated) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
|
25
|
+
);
|
|
26
|
+
for (const row of rows) {
|
|
27
|
+
insert.run(row.id, row.integration, row.label ?? 'default', JSON.stringify(row.value), row.active ?? null, row.time ?? 1, row.time ?? 1);
|
|
28
|
+
}
|
|
29
|
+
db.close();
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
describe('projectCredentialValue', () => {
|
|
33
|
+
it('maps a stored key to the legacy api entry', () => {
|
|
34
|
+
expect(projectCredentialValue({ type: 'key', key: 'sk-1', metadata: { region: 'eu' } })).toEqual({
|
|
35
|
+
type: 'api',
|
|
36
|
+
key: 'sk-1',
|
|
37
|
+
metadata: { region: 'eu' },
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('maps a stored oauth credential, including the account and enterprise fields', () => {
|
|
42
|
+
expect(
|
|
43
|
+
projectCredentialValue({
|
|
44
|
+
type: 'oauth',
|
|
45
|
+
methodID: 'chatgpt-browser',
|
|
46
|
+
access: 'a',
|
|
47
|
+
refresh: 'r',
|
|
48
|
+
expires: 42,
|
|
49
|
+
metadata: { accountID: 'acc', enterpriseUrl: 'https://ghe.example' },
|
|
50
|
+
}),
|
|
51
|
+
).toEqual({ type: 'oauth', access: 'a', refresh: 'r', expires: 42, accountId: 'acc', enterpriseUrl: 'https://ghe.example' });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('rejects shapes it does not understand instead of guessing', () => {
|
|
55
|
+
expect(projectCredentialValue('not json')).toBeNull();
|
|
56
|
+
expect(projectCredentialValue({ type: 'oauth', access: 'a' })).toBeNull();
|
|
57
|
+
expect(projectCredentialValue({ type: 'something-new', key: 'x' })).toBeNull();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('resolveCredentialDbPath', () => {
|
|
62
|
+
it('defaults to opencode.db in the data dir and honours OPENCODE_DB', () => {
|
|
63
|
+
expect(resolveCredentialDbPath({ dataDir: '/data', env: {}, path })).toBe(path.join('/data', 'opencode.db'));
|
|
64
|
+
expect(resolveCredentialDbPath({ dataDir: '/data', env: { OPENCODE_DB: 'other.db' }, path })).toBe(path.resolve('/data', 'other.db'));
|
|
65
|
+
expect(resolveCredentialDbPath({ dataDir: '/data', env: { OPENCODE_DB: '/abs/x.db' }, path })).toBe(path.resolve('/abs/x.db'));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe.skipIf(!sqlite)('readCredentialsFromDb', () => {
|
|
70
|
+
let dir;
|
|
71
|
+
beforeEach(() => {
|
|
72
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-cred-'));
|
|
73
|
+
});
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('returns null when there is no database, so the caller can fall back', () => {
|
|
79
|
+
expect(readCredentialsFromDb({ dbPath: path.join(dir, 'opencode.db'), fs })).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('picks the active credential per integration, else the newest one', () => {
|
|
83
|
+
const dbPath = path.join(dir, 'opencode.db');
|
|
84
|
+
seed(dbPath, [
|
|
85
|
+
{ id: 'c1', integration: 'openai', value: { type: 'oauth', access: 'old', refresh: 'r1', expires: 1 }, active: 0, time: 5 },
|
|
86
|
+
{ id: 'c2', integration: 'openai', value: { type: 'oauth', access: 'new', refresh: 'r2', expires: 2 }, active: 1, time: 2 },
|
|
87
|
+
{ id: 'c3', integration: 'opencode-go', value: { type: 'key', key: 'go-old' }, time: 1 },
|
|
88
|
+
{ id: 'c4', integration: 'opencode-go', value: { type: 'key', key: 'go-new' }, time: 9 },
|
|
89
|
+
{ id: 'c5', integration: 'weird', value: { type: 'future' }, time: 1 },
|
|
90
|
+
]);
|
|
91
|
+
expect(readCredentialsFromDb({ dbPath, fs })).toEqual({
|
|
92
|
+
openai: { type: 'oauth', access: 'new', refresh: 'r2', expires: 2 },
|
|
93
|
+
'opencode-go': { type: 'api', key: 'go-new' },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('returns null rather than throwing when the schema is not what it expects', () => {
|
|
98
|
+
const dbPath = path.join(dir, 'opencode.db');
|
|
99
|
+
const db = new sqlite.DatabaseSync(dbPath);
|
|
100
|
+
db.exec('CREATE TABLE credential (id TEXT PRIMARY KEY)');
|
|
101
|
+
db.close();
|
|
102
|
+
expect(readCredentialsFromDb({ dbPath, fs })).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -18,6 +18,9 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
18
18
|
readSettingsFromDiskMigrated,
|
|
19
19
|
} = deps;
|
|
20
20
|
const runSpawnSync = typeof deps.spawnSync === 'function' ? deps.spawnSync : spawnSync;
|
|
21
|
+
const readProvidedLoginShellEnvSnapshot = typeof deps.providedLoginShellEnvSnapshot === 'function'
|
|
22
|
+
? deps.providedLoginShellEnvSnapshot
|
|
23
|
+
: () => undefined;
|
|
21
24
|
const resolveHomeDir = typeof deps.homedir === 'function' ? deps.homedir : () => os.homedir();
|
|
22
25
|
|
|
23
26
|
const parseNullSeparatedEnvSnapshot = (raw) => {
|
|
@@ -195,6 +198,14 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
195
198
|
return state.cachedLoginShellEnvSnapshot;
|
|
196
199
|
}
|
|
197
200
|
|
|
201
|
+
// An embedding host (Desktop) that already probed the login shell hands
|
|
202
|
+
// its snapshot over; see login-shell-env.js.
|
|
203
|
+
const provided = readProvidedLoginShellEnvSnapshot();
|
|
204
|
+
if (provided !== undefined) {
|
|
205
|
+
state.cachedLoginShellEnvSnapshot = provided;
|
|
206
|
+
return provided;
|
|
207
|
+
}
|
|
208
|
+
|
|
198
209
|
if (process.platform === 'win32') {
|
|
199
210
|
const windowsSnapshot = getWindowsShellEnvSnapshot();
|
|
200
211
|
state.cachedLoginShellEnvSnapshot = windowsSnapshot;
|
|
@@ -678,6 +689,14 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
678
689
|
return isExecutable(trimmed) ? trimmed : null;
|
|
679
690
|
};
|
|
680
691
|
|
|
692
|
+
// OpenCode 2.x platform packages first, then the 1.x names.
|
|
693
|
+
const WINDOWS_X64_NATIVE_PACKAGES = [
|
|
694
|
+
path.join('@opencode', 'cli-windows-x64-baseline'),
|
|
695
|
+
path.join('@opencode', 'cli-windows-x64'),
|
|
696
|
+
'opencode-windows-x64-baseline',
|
|
697
|
+
'opencode-windows-x64',
|
|
698
|
+
];
|
|
699
|
+
|
|
681
700
|
const getWindowsNativeOpencodePackageNames = () => {
|
|
682
701
|
// TEMPORARY WORKAROUND — Windows ARM64: native opencode.exe fails with a Bun
|
|
683
702
|
// FFI/TinyCC dlopen error (https://github.com/anomalyco/opencode/issues/19130).
|
|
@@ -686,31 +705,41 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
686
705
|
// arm64 branch below when the upstream issue is resolved.
|
|
687
706
|
if (process.arch === 'arm64') {
|
|
688
707
|
// --- ORIGINAL (restore when ARM64 is fixed) ---
|
|
689
|
-
// return ['opencode-windows-arm64'];
|
|
690
|
-
return
|
|
708
|
+
// return ['@opencode/cli-windows-arm64', 'opencode-windows-arm64'];
|
|
709
|
+
return WINDOWS_X64_NATIVE_PACKAGES;
|
|
691
710
|
}
|
|
692
711
|
if (process.arch === 'x64') {
|
|
693
712
|
// Prefer the baseline build when bypassing package-manager wrappers so the
|
|
694
713
|
// direct binary still runs on hosts without AVX2 support.
|
|
695
|
-
return
|
|
714
|
+
return WINDOWS_X64_NATIVE_PACKAGES;
|
|
696
715
|
}
|
|
697
716
|
return [];
|
|
698
717
|
};
|
|
699
718
|
|
|
719
|
+
// An npm-installed OpenCode lives under one of these package directories:
|
|
720
|
+
// `@opencode/cli` (OpenCode 2.x) or `opencode-ai` (1.x). Both ship a
|
|
721
|
+
// `bin/opencode.exe` that postinstall replaces with the platform binary from
|
|
722
|
+
// the matching optional dependency (`@opencode/cli-windows-x64` or
|
|
723
|
+
// `opencode-windows-x64`).
|
|
724
|
+
const OPENCODE_NPM_PACKAGE_DIRS = [path.join('@opencode', 'cli'), 'opencode-ai'];
|
|
725
|
+
|
|
700
726
|
const resolveNativeOpencodeBinaryFromNodeModules = (nodeModulesDir) => {
|
|
701
727
|
if (typeof nodeModulesDir !== 'string' || nodeModulesDir.trim().length === 0) {
|
|
702
728
|
return null;
|
|
703
729
|
}
|
|
704
730
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
731
|
+
for (const packageDir of OPENCODE_NPM_PACKAGE_DIRS) {
|
|
732
|
+
const packageShim = path.join(nodeModulesDir, packageDir, 'bin', 'opencode.exe');
|
|
733
|
+
if (isExecutable(packageShim)) {
|
|
734
|
+
return packageShim;
|
|
735
|
+
}
|
|
708
736
|
}
|
|
709
737
|
|
|
710
738
|
for (const packageName of getWindowsNativeOpencodePackageNames()) {
|
|
711
739
|
const candidates = [
|
|
712
740
|
path.join(nodeModulesDir, packageName, 'bin', 'opencode.exe'),
|
|
713
|
-
|
|
741
|
+
...OPENCODE_NPM_PACKAGE_DIRS.map((packageDir) =>
|
|
742
|
+
path.join(nodeModulesDir, packageDir, 'node_modules', packageName, 'bin', 'opencode.exe')),
|
|
714
743
|
];
|
|
715
744
|
for (const candidate of candidates) {
|
|
716
745
|
if (isExecutable(candidate)) {
|
|
@@ -747,13 +776,18 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
747
776
|
|
|
748
777
|
try {
|
|
749
778
|
const content = fs.readFileSync(wrapperPath, 'utf8');
|
|
750
|
-
const launcherMatch = content.match(/node_modules[\\/]+opencode-ai[\\/]+bin[\\/]+opencode/i);
|
|
779
|
+
const launcherMatch = content.match(/node_modules[\\/]+(?:@opencode[\\/]+cli|opencode-ai)[\\/]+bin[\\/]+opencode/i);
|
|
751
780
|
if (!launcherMatch) {
|
|
752
781
|
return null;
|
|
753
782
|
}
|
|
754
783
|
|
|
755
784
|
const launcherPath = path.resolve(path.dirname(wrapperPath), launcherMatch[0].replace(/[\\/]+/g, path.sep));
|
|
756
|
-
|
|
785
|
+
// Walk back up to `node_modules`: past `bin`, the package directory and,
|
|
786
|
+
// for the scoped 2.x package, its scope.
|
|
787
|
+
const depth = /[\\/]@opencode[\\/]/i.test(launcherMatch[0]) ? 4 : 3;
|
|
788
|
+
let nodeModulesDir = launcherPath;
|
|
789
|
+
for (let index = 0; index < depth; index += 1) nodeModulesDir = path.dirname(nodeModulesDir);
|
|
790
|
+
return nodeModulesDir;
|
|
757
791
|
} catch {
|
|
758
792
|
return null;
|
|
759
793
|
}
|
|
@@ -793,6 +827,11 @@ export const createOpenCodeEnvRuntime = (deps) => {
|
|
|
793
827
|
pushCandidate(path.dirname(path.dirname(fileDir)));
|
|
794
828
|
}
|
|
795
829
|
|
|
830
|
+
if (lower.endsWith(`${path.sep}node_modules${path.sep}@opencode${path.sep}cli${path.sep}bin${path.sep}opencode`)
|
|
831
|
+
|| lower.endsWith(`${path.sep}node_modules${path.sep}@opencode${path.sep}cli${path.sep}bin${path.sep}opencode.exe`)) {
|
|
832
|
+
pushCandidate(path.dirname(path.dirname(path.dirname(fileDir))));
|
|
833
|
+
}
|
|
834
|
+
|
|
796
835
|
if (path.basename(fileDir).toLowerCase() === 'npm') {
|
|
797
836
|
pushCandidate(path.join(fileDir, 'node_modules'));
|
|
798
837
|
}
|
|
@@ -115,6 +115,7 @@ const createRuntime = (settings, options = {}) => {
|
|
|
115
115
|
readSettingsFromDiskMigrated: async () => settings,
|
|
116
116
|
spawnSync: options.spawnSync,
|
|
117
117
|
homedir: options.homedir,
|
|
118
|
+
providedLoginShellEnvSnapshot: options.providedLoginShellEnvSnapshot,
|
|
118
119
|
});
|
|
119
120
|
|
|
120
121
|
return { runtime, state };
|
|
@@ -225,6 +226,33 @@ describe('OpenCode env runtime', () => {
|
|
|
225
226
|
}
|
|
226
227
|
});
|
|
227
228
|
|
|
229
|
+
it('uses a login-shell snapshot provided by the host instead of probing the shell', () => {
|
|
230
|
+
let probes = 0;
|
|
231
|
+
const spawnSyncSpy = () => { probes += 1; return { status: 0, stdout: 'PATH=/from/probe\0' }; };
|
|
232
|
+
const { runtime, state } = createRuntime({}, {
|
|
233
|
+
spawnSync: spawnSyncSpy,
|
|
234
|
+
providedLoginShellEnvSnapshot: () => ({ PATH: '/from/host' }),
|
|
235
|
+
});
|
|
236
|
+
state.cachedLoginShellEnvSnapshot = undefined;
|
|
237
|
+
|
|
238
|
+
expect(runtime.getLoginShellEnvSnapshot()).toEqual({ PATH: '/from/host' });
|
|
239
|
+
expect(state.cachedLoginShellEnvSnapshot).toEqual({ PATH: '/from/host' });
|
|
240
|
+
expect(probes).toBe(0);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('does not probe the shell when the host provided an empty snapshot', () => {
|
|
244
|
+
let probes = 0;
|
|
245
|
+
const spawnSyncSpy = () => { probes += 1; return { status: 0, stdout: 'PATH=/from/probe\0' }; };
|
|
246
|
+
const { runtime, state } = createRuntime({}, {
|
|
247
|
+
spawnSync: spawnSyncSpy,
|
|
248
|
+
providedLoginShellEnvSnapshot: () => null,
|
|
249
|
+
});
|
|
250
|
+
state.cachedLoginShellEnvSnapshot = undefined;
|
|
251
|
+
|
|
252
|
+
expect(runtime.getLoginShellEnvSnapshot()).toBeNull();
|
|
253
|
+
expect(probes).toBe(0);
|
|
254
|
+
});
|
|
255
|
+
|
|
228
256
|
it('clears AppImage ARGV0 even when no login-shell snapshot is available', () => {
|
|
229
257
|
const previousArgv0 = process.env.ARGV0;
|
|
230
258
|
process.env.ARGV0 = '/path/to/OpenChamber.AppImage';
|
|
@@ -528,6 +556,23 @@ describe('OpenCode env runtime', () => {
|
|
|
528
556
|
});
|
|
529
557
|
});
|
|
530
558
|
|
|
559
|
+
it('resolves an npm-installed OpenCode 2.x cmd shim to its packaged Windows executable', () => {
|
|
560
|
+
setPlatform('win32');
|
|
561
|
+
const npmDir = createTempDir('openchamber-opencode-npm-v2-');
|
|
562
|
+
const shim = path.join(npmDir, 'opencode.cmd');
|
|
563
|
+
const nativeBinary = path.join(npmDir, 'node_modules', '@opencode', 'cli', 'bin', 'opencode.exe');
|
|
564
|
+
fs.mkdirSync(path.dirname(nativeBinary), { recursive: true });
|
|
565
|
+
fs.writeFileSync(nativeBinary, '');
|
|
566
|
+
fs.writeFileSync(shim, '@ECHO off\r\n"%dp0%\\node_modules\\@opencode\\cli\\bin\\opencode.exe" %*\r\n');
|
|
567
|
+
const { runtime } = createRuntime({});
|
|
568
|
+
|
|
569
|
+
expect(runtime.resolveManagedOpenCodeLaunchSpec(shim)).toEqual({
|
|
570
|
+
binary: nativeBinary,
|
|
571
|
+
args: [],
|
|
572
|
+
wrapperType: 'native-wrapper',
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
531
576
|
it('resolves npm OpenCode cmd shims to the packaged Windows executable', () => {
|
|
532
577
|
setPlatform('win32');
|
|
533
578
|
const npmDir = createTempDir('openchamber-opencode-npm-');
|
|
@@ -32,8 +32,8 @@ import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
|
|
|
32
32
|
import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
|
|
33
33
|
import { registerOpenCodeRoutes } from './routes.js';
|
|
34
34
|
import { getProviderSources, removeProviderConfig, upsertProviderConfig } from './providers.js';
|
|
35
|
-
import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
|
|
36
|
-
import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
|
|
35
|
+
import { getAgentSources, getAgentConfig, getAgentPermissions, createAgent, updateAgent, deleteAgent } from './agents.js';
|
|
36
|
+
import { getCommandSources, getCommandConfig, createCommand, updateCommand, deleteCommand } from './commands.js';
|
|
37
37
|
import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
|
|
38
38
|
import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
|
|
39
39
|
import {
|
|
@@ -86,7 +86,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
86
86
|
import('../walkthrough/index.js'),
|
|
87
87
|
import('../walkthrough/pull-request.js'),
|
|
88
88
|
]);
|
|
89
|
-
walkthroughService = { ...service, getPullRequestDiff: pullRequest.getPullRequestDiff };
|
|
89
|
+
walkthroughService = { ...service, getPullRequestDiff: pullRequest.getPullRequestDiff, getPullRequestFileContents: pullRequest.getPullRequestFileContents };
|
|
90
90
|
}
|
|
91
91
|
return walkthroughService;
|
|
92
92
|
};
|
|
@@ -102,6 +102,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
102
102
|
resolveGitBinaryForSpawn,
|
|
103
103
|
createFsSearchRuntime,
|
|
104
104
|
openchamberDataDir,
|
|
105
|
+
onGuestDeactivated,
|
|
105
106
|
openchamberUserConfigRoot,
|
|
106
107
|
managedChatsRoot,
|
|
107
108
|
normalizeDirectoryPath,
|
|
@@ -114,6 +115,9 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
114
115
|
refreshOpenCodeAfterConfigChange,
|
|
115
116
|
getOpenCodeResolutionSnapshot,
|
|
116
117
|
getOpenCodeUpgradeCapability,
|
|
118
|
+
upgradeOpenCodeCli,
|
|
119
|
+
getOpenCodeCompatibility,
|
|
120
|
+
installOpenCodeV2,
|
|
117
121
|
formatSettingsResponse,
|
|
118
122
|
readSettingsFromDisk,
|
|
119
123
|
readSettingsFromDiskMigrated,
|
|
@@ -157,7 +161,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
157
161
|
registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
|
|
158
162
|
registerMessageQueueRoutes(app, messageQueueRuntime);
|
|
159
163
|
registerRoutingRoutes(app, routingRuntime);
|
|
160
|
-
// Before the generic OpenCode proxy:
|
|
164
|
+
// Before the generic OpenCode proxy: swallows the `openchamber/auto` model
|
|
165
|
+
// switch and routes the sends that follow it.
|
|
161
166
|
registerRoutingPromptRewrite(app, routingRuntime);
|
|
162
167
|
|
|
163
168
|
registerOpenCodeRoutes(app, {
|
|
@@ -165,6 +170,9 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
165
170
|
clientReloadDelayMs,
|
|
166
171
|
getOpenCodeResolutionSnapshot,
|
|
167
172
|
getOpenCodeUpgradeCapability,
|
|
173
|
+
upgradeOpenCodeCli,
|
|
174
|
+
getOpenCodeCompatibility,
|
|
175
|
+
installOpenCodeV2,
|
|
168
176
|
formatSettingsResponse,
|
|
169
177
|
readSettingsFromDisk,
|
|
170
178
|
readSettingsFromDiskMigrated,
|
|
@@ -233,10 +241,12 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
233
241
|
clientReloadDelayMs,
|
|
234
242
|
getAgentSources,
|
|
235
243
|
getAgentConfig,
|
|
244
|
+
getAgentPermissions,
|
|
236
245
|
createAgent,
|
|
237
246
|
updateAgent,
|
|
238
247
|
deleteAgent,
|
|
239
248
|
getCommandSources,
|
|
249
|
+
getCommandConfig,
|
|
240
250
|
createCommand,
|
|
241
251
|
updateCommand,
|
|
242
252
|
deleteCommand,
|
|
@@ -321,7 +331,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
321
331
|
registerGitHubRoutes(app);
|
|
322
332
|
registerLinearRoutes(app);
|
|
323
333
|
await registerBuiltInGuests({ persistPath: extensionsPersistPath(openchamberDataDir), root: routeDependencies.builtInExtensionsDir });
|
|
324
|
-
registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService });
|
|
334
|
+
registerGuestRoutes(app, { openchamberDataDir, openchamberVersion, resolveGitBinaryForSpawn, resolveOptionalProjectDirectory, getSmallModelService, onGuestDeactivated });
|
|
325
335
|
registerGitRoutes(app, {
|
|
326
336
|
emitWorktreeChanged: ({ directories, at }) => {
|
|
327
337
|
const clients = getOpenChamberEventClients();
|
|
@@ -12,6 +12,7 @@ const createRuntime = (waitForReady, state) => createOpenCodeLifecycleRuntime({
|
|
|
12
12
|
state,
|
|
13
13
|
env: { ENV_CONFIGURED_OPENCODE_PORT: 45678, ENV_CONFIGURED_OPENCODE_HOSTNAME: '127.0.0.1' },
|
|
14
14
|
syncToHmrState() {}, syncFromHmrState() {},
|
|
15
|
+
checkOpenCodeBinary: async () => '2.0.14',
|
|
15
16
|
ensureOpencodeCliEnv: () => process.execPath,
|
|
16
17
|
applyOpencodeBinaryFromSettings: async () => {},
|
|
17
18
|
ensureLocalOpenCodeServerPassword: async () => 'fixture-only',
|
|
@@ -30,8 +31,10 @@ describe('managed process lifecycle with real children', () => {
|
|
|
30
31
|
process.env.OPENCHAMBER_MANAGED_PROCESS_REGISTRY = path.join(root, 'registry');
|
|
31
32
|
const marker = path.join(root, 'pids');
|
|
32
33
|
const childScript = `process.on('SIGTERM', () => {}); require('node:fs').appendFileSync(${JSON.stringify(marker)}, process.pid + '\\n'); process.stdout.write('ready\\n'); setInterval(() => {}, 1000);`;
|
|
33
|
-
let readinessMessage = '
|
|
34
|
-
|
|
34
|
+
let readinessMessage = 'server listening on http://127.0.0.1:45678\n';
|
|
35
|
+
// A line that looks like the readiness line but carries no URL is noise,
|
|
36
|
+
// not readiness: the start must time out rather than connect to nothing.
|
|
37
|
+
if (failure === 'invalid-readiness') readinessMessage = 'server listening without a URL\n';
|
|
35
38
|
if (failure === 'startup-timeout' || failure === 'shutdown-during-startup') readinessMessage = '';
|
|
36
39
|
// Node is an isolated stand-in for the native OpenCode binary. Lifecycle
|
|
37
40
|
// still launches its real `serve --hostname ... --port ...` command.
|
|
@@ -60,10 +63,10 @@ describe('managed process lifecycle with real children', () => {
|
|
|
60
63
|
state.isShuttingDown = true;
|
|
61
64
|
await state.openCodeProcess.close();
|
|
62
65
|
await rejected;
|
|
63
|
-
} else if (failure === 'startup-timeout') {
|
|
66
|
+
} else if (failure === 'startup-timeout' || failure === 'invalid-readiness') {
|
|
64
67
|
await expect(runtime.startOpenCode()).rejects.toThrow('Timeout waiting for OpenCode');
|
|
65
68
|
} else {
|
|
66
|
-
await expect(runtime.startOpenCode()).rejects.toThrow(
|
|
69
|
+
await expect(runtime.startOpenCode()).rejects.toThrow('fixture health failure');
|
|
67
70
|
}
|
|
68
71
|
const pids = (await fs.readFile(marker, 'utf8')).trim().split('\n').map(Number);
|
|
69
72
|
expect(pids).toHaveLength(failure === 'none' || failure === 'shutdown-during-startup' ? 2 : 4);
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { readOpenCodeInfo, isSupportedOpenCodeVersion, requireOpenCodeV2, UnsupportedOpenCodeVersionError } from './compatibility.js';
|
|
1
2
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
3
|
import net from 'node:net';
|
|
3
4
|
import { stripAppImageArgv0Leak } from '../inherited-env.js';
|
|
4
5
|
import { registerManagedProcess, unregisterManagedProcess, reapOrphanedProcesses } from './managed-process-registry.js';
|
|
5
6
|
import { applyProviderEnvAliases } from './provider-env-aliases.js';
|
|
6
7
|
import { recordStartupPerformance } from './startup-performance.js';
|
|
8
|
+
import { topUpV1Migration } from './v1-migration-topup.js';
|
|
7
9
|
|
|
8
10
|
const parsePositiveInt = (value, fallback) => {
|
|
9
11
|
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
@@ -17,7 +19,35 @@ const HEALTH_CHECK_MAX_CONSECUTIVE_FAILURES = parsePositiveInt(
|
|
|
17
19
|
);
|
|
18
20
|
const HEALTH_CHECK_INTERVAL_OVERRIDE_MS = parsePositiveInt(process.env.OPENCHAMBER_OPENCODE_HEALTH_INTERVAL_MS, 0);
|
|
19
21
|
const HEALTH_CHECK_RESULT_CACHE_MS = parsePositiveInt(process.env.OPENCHAMBER_OPENCODE_HEALTH_CACHE_MS, 750);
|
|
20
|
-
const OPENCODE_HEALTH_PATH = '/
|
|
22
|
+
const OPENCODE_HEALTH_PATH = '/api/info';
|
|
23
|
+
const OPENCODE_REQUIRED_MAJOR_VERSION = 2;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* OpenChamber talks to OpenCode 2.x only. v1 serves its routes without the
|
|
27
|
+
* `/api` prefix, publishes a different event vocabulary and has no `plugins`
|
|
28
|
+
* config key, so an older binary fails in a hundred small ways instead of one
|
|
29
|
+
* clear one.
|
|
30
|
+
*
|
|
31
|
+
* The version comes from the health payload rather than from `opencode
|
|
32
|
+
* --version`: it costs no extra process, and it also covers an external
|
|
33
|
+
* OpenCode the user started themselves. `/api/info` only exists in 2.x (2.0.8
|
|
34
|
+
* removed the older `/api/health`), so a 404 there is the same answer by
|
|
35
|
+
* another route. A 200 is the readiness signal; the payload has no `healthy`
|
|
36
|
+
* field, only `{ version, pid, urls, paths }`.
|
|
37
|
+
*/
|
|
38
|
+
const OPENCODE_VERSION_REQUIREMENT_DETAIL =
|
|
39
|
+
`OpenChamber requires OpenCode ${OPENCODE_REQUIRED_MAJOR_VERSION}.x`;
|
|
40
|
+
|
|
41
|
+
const classifyOpenCodeVersion = (version) => {
|
|
42
|
+
if (typeof version !== 'string') return { ok: true };
|
|
43
|
+
const match = version.match(/v?(\d+)\./);
|
|
44
|
+
if (!match) return { ok: true };
|
|
45
|
+
if (Number(match[1]) >= OPENCODE_REQUIRED_MAJOR_VERSION) return { ok: true };
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
detail: `${OPENCODE_VERSION_REQUIREMENT_DETAIL}, found ${version.trim()}. Update OpenCode and start OpenChamber again.`,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
21
51
|
// Last-used directory plus the three most recently opened projects — deeper
|
|
22
52
|
// tails are unlikely to be the user's first click and just add background work.
|
|
23
53
|
const WARMUP_DIRECTORY_LIMIT = 4;
|
|
@@ -111,8 +141,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
111
141
|
onOpenCodeRestarted = null,
|
|
112
142
|
managedStartupTimeoutMs = 30_000,
|
|
113
143
|
now = Date.now,
|
|
144
|
+
topUpV1SessionMigration = topUpV1Migration,
|
|
145
|
+
checkOpenCodeBinary = requireOpenCodeV2,
|
|
114
146
|
} = deps;
|
|
115
147
|
|
|
148
|
+
let managedPreflight = null;
|
|
149
|
+
|
|
116
150
|
const killProcessOnPortWin32 = (port) => {
|
|
117
151
|
try {
|
|
118
152
|
// Get-NetTCPConnection reads the same locale-independent WinNT API
|
|
@@ -472,12 +506,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
472
506
|
stdout += chunk.toString();
|
|
473
507
|
const lines = stdout.split('\n');
|
|
474
508
|
for (const line of lines) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
509
|
+
// OpenCode 2.x prints `server listening on http://host:port` with no
|
|
510
|
+
// "opencode" prefix.
|
|
511
|
+
const match = line.match(/server listening on\s+(https?:\/\/\S+)/);
|
|
512
|
+
if (!match) continue;
|
|
481
513
|
attachRuntimeStderrCapture();
|
|
482
514
|
finish(resolve, match[1]);
|
|
483
515
|
return;
|
|
@@ -584,7 +616,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
584
616
|
healthy: false,
|
|
585
617
|
failure: {
|
|
586
618
|
class: 'invalid_response',
|
|
587
|
-
detail:
|
|
619
|
+
detail: response.status === 404
|
|
620
|
+
? `${OPENCODE_VERSION_REQUIREMENT_DETAIL}: this server has no /api/info, which every 2.x server serves.`
|
|
621
|
+
: `Info endpoint returned HTTP ${response.status ?? 'unknown'}`,
|
|
588
622
|
},
|
|
589
623
|
};
|
|
590
624
|
}
|
|
@@ -596,17 +630,15 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
596
630
|
healthy: false,
|
|
597
631
|
failure: {
|
|
598
632
|
class: 'invalid_response',
|
|
599
|
-
detail: '
|
|
633
|
+
detail: 'Info endpoint returned invalid JSON',
|
|
600
634
|
},
|
|
601
635
|
};
|
|
602
636
|
}
|
|
603
|
-
|
|
637
|
+
const version = classifyOpenCodeVersion(body?.version);
|
|
638
|
+
if (!version.ok) {
|
|
604
639
|
return {
|
|
605
640
|
healthy: false,
|
|
606
|
-
failure: {
|
|
607
|
-
class: 'invalid_response',
|
|
608
|
-
detail: 'Health endpoint did not report healthy=true',
|
|
609
|
-
},
|
|
641
|
+
failure: { class: 'invalid_response', detail: version.detail },
|
|
610
642
|
};
|
|
611
643
|
}
|
|
612
644
|
return { healthy: true, failure: null };
|
|
@@ -638,9 +670,8 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
638
670
|
signal: controller.signal,
|
|
639
671
|
});
|
|
640
672
|
clearTimeout(timeout);
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
return body?.healthy === true;
|
|
673
|
+
const info = await readOpenCodeInfo(response);
|
|
674
|
+
return info !== null && isSupportedOpenCodeVersion(info.version);
|
|
644
675
|
} catch {
|
|
645
676
|
return false;
|
|
646
677
|
}
|
|
@@ -680,6 +711,9 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
680
711
|
|
|
681
712
|
await applyOpencodeBinaryFromSettings({ strict: true });
|
|
682
713
|
const resolvedBinary = ensureOpencodeCliEnv();
|
|
714
|
+
const preflight = checkOpenCodeBinary(resolveManagedOpenCodeLaunchSpec(resolvedBinary));
|
|
715
|
+
managedPreflight = preflight.then(() => true, () => false);
|
|
716
|
+
await preflight;
|
|
683
717
|
recordStartupPerformance('opencode.binary.ready', {
|
|
684
718
|
attempt,
|
|
685
719
|
durationMs: performance.now() - phaseStartedAt,
|
|
@@ -704,6 +738,18 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
704
738
|
});
|
|
705
739
|
phaseStartedAt = performance.now();
|
|
706
740
|
|
|
741
|
+
// Re-arm OpenCode's own V1 -> V2 session import for sessions a bundled
|
|
742
|
+
// OpenCode 1.x created after the migration already completed. Only for the
|
|
743
|
+
// managed process, only while it is not running, and never fatal.
|
|
744
|
+
try {
|
|
745
|
+
const topUp = topUpV1SessionMigration();
|
|
746
|
+
if (topUp && topUp.status !== 'skipped') {
|
|
747
|
+
console.log('[OpenCode] V1 session migration top-up:', topUp);
|
|
748
|
+
}
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.warn('[OpenCode] V1 session migration top-up failed:', error instanceof Error ? error.message : error);
|
|
751
|
+
}
|
|
752
|
+
|
|
707
753
|
let serverInstance;
|
|
708
754
|
try {
|
|
709
755
|
if (state.isShuttingDown) throw new Error('OpenCode startup cancelled during shutdown');
|
|
@@ -776,13 +822,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
776
822
|
};
|
|
777
823
|
|
|
778
824
|
const startOpenCode = async () => {
|
|
825
|
+
managedPreflight = null;
|
|
779
826
|
let lastError = null;
|
|
780
827
|
for (let attempt = 1; attempt <= START_OPEN_CODE_MAX_ATTEMPTS; attempt += 1) {
|
|
781
828
|
try {
|
|
782
829
|
return await startOpenCodeOnce(attempt);
|
|
783
830
|
} catch (error) {
|
|
784
831
|
lastError = error;
|
|
785
|
-
if (state.isShuttingDown || error?.code === 'OPENCODE_BINARY_INVALID') {
|
|
832
|
+
if (state.isShuttingDown || error instanceof UnsupportedOpenCodeVersionError || error?.code === 'OPENCODE_BINARY_INVALID') {
|
|
786
833
|
break;
|
|
787
834
|
}
|
|
788
835
|
if (attempt >= START_OPEN_CODE_MAX_ATTEMPTS) {
|
|
@@ -810,6 +857,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
810
857
|
}
|
|
811
858
|
|
|
812
859
|
state.currentRestartPromise = (async () => {
|
|
860
|
+
managedPreflight = null;
|
|
813
861
|
state.isRestartingOpenCode = true;
|
|
814
862
|
state.isOpenCodeReady = false;
|
|
815
863
|
state.openCodeNotReadySince = Date.now();
|
|
@@ -937,18 +985,14 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
937
985
|
timeout = null;
|
|
938
986
|
|
|
939
987
|
if (!response.ok) {
|
|
940
|
-
lastError = new Error(`OpenCode
|
|
941
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
942
|
-
continue;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
const body = await response.json().catch(() => null);
|
|
946
|
-
if (body?.healthy !== true) {
|
|
947
|
-
lastError = new Error('OpenCode health endpoint returned unhealthy response');
|
|
988
|
+
lastError = new Error(`OpenCode info endpoint responded with status ${response.status}`);
|
|
948
989
|
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
949
990
|
continue;
|
|
950
991
|
}
|
|
951
992
|
|
|
993
|
+
const info = await readOpenCodeInfo(response);
|
|
994
|
+
if (!info) throw new Error('OpenCode did not return valid version information.');
|
|
995
|
+
if (!isSupportedOpenCodeVersion(info.version)) throw new UnsupportedOpenCodeVersionError(info.version);
|
|
952
996
|
state.isOpenCodeReady = true;
|
|
953
997
|
state.lastOpenCodeError = null;
|
|
954
998
|
return;
|
|
@@ -981,14 +1025,16 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
981
1025
|
const deadline = Date.now() + timeoutMs;
|
|
982
1026
|
while (Date.now() < deadline) {
|
|
983
1027
|
try {
|
|
984
|
-
const response = await fetch(buildOpenCodeUrl('/agent'), {
|
|
1028
|
+
const response = await fetch(buildOpenCodeUrl('/api/agent'), {
|
|
985
1029
|
method: 'GET',
|
|
986
1030
|
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
|
|
987
1031
|
});
|
|
988
1032
|
|
|
989
1033
|
if (response.ok) {
|
|
990
|
-
|
|
991
|
-
|
|
1034
|
+
// OpenCode 2.x answers `/api/*` with `{ location, data }`.
|
|
1035
|
+
const body = await response.json();
|
|
1036
|
+
const agents = Array.isArray(body) ? body : body?.data;
|
|
1037
|
+
if (Array.isArray(agents) && agents.some((agent) => agent?.id === agentName)) {
|
|
992
1038
|
return;
|
|
993
1039
|
}
|
|
994
1040
|
}
|
|
@@ -1155,7 +1201,10 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
1155
1201
|
try {
|
|
1156
1202
|
const controller = new AbortController();
|
|
1157
1203
|
timeout = setTimeout(() => controller.abort(), WARMUP_REQUEST_TIMEOUT_MS);
|
|
1158
|
-
|
|
1204
|
+
// Warming a directory is the point, not the answer: any directory-scoped
|
|
1205
|
+
// read makes OpenCode initialise it. `/api/session` is the cheapest one
|
|
1206
|
+
// that takes a directory (`/api/session/active` is global).
|
|
1207
|
+
const url = `${buildOpenCodeUrl('/api/session', '')}?directory=${encodeURIComponent(directory)}&limit=1`;
|
|
1159
1208
|
await fetch(url, {
|
|
1160
1209
|
method: 'GET',
|
|
1161
1210
|
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
|
|
@@ -1321,6 +1370,12 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
|
|
|
1321
1370
|
};
|
|
1322
1371
|
|
|
1323
1372
|
return {
|
|
1373
|
+
getManagedOpenCodePreflight: async () => {
|
|
1374
|
+
const preflight = managedPreflight;
|
|
1375
|
+
if (!preflight || state.isExternalOpenCode || state.isShuttingDown) return false;
|
|
1376
|
+
const compatible = await preflight;
|
|
1377
|
+
return compatible && preflight === managedPreflight && !state.isExternalOpenCode && !state.isShuttingDown;
|
|
1378
|
+
},
|
|
1324
1379
|
killProcessOnPort,
|
|
1325
1380
|
startOpenCode,
|
|
1326
1381
|
restartOpenCode,
|