@vantran-se/openchamber-web 1.24.4 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-B4a0JkUz.js → CodeMirrorCommentWidgets-B82Oye3V.js} +1 -1
- package/dist/assets/DiffView-DJLmWn6Z.js +4 -0
- package/dist/assets/FilesView-DjksmZRo.js +17 -0
- package/dist/assets/{GitView-Byu2DY7s.js → GitView-DiUsUIgr.js} +4 -4
- package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-CYwWYLr3.js} +1 -1
- package/dist/assets/GuestSurfacePane-CmctyRI4.js +1 -0
- package/dist/assets/{JsonTreeView-VW_7czo3.js → JsonTreeView-DOHHcbgJ.js} +1 -1
- package/dist/assets/LinearIssuesView-DaqnylcV.js +1 -0
- package/dist/assets/MarkdownImageGallery-HJNvWt7L.js +1 -0
- package/dist/assets/MarkdownRendererImpl-nDZRGg2I.js +12 -0
- package/dist/assets/OnboardingScreen-C2B5Bsof.js +1 -0
- package/dist/assets/{PatchDiff-DCjGhlYV.js → PatchDiff-C4FLBjpt.js} +1 -1
- package/dist/assets/{PierreDiffViewer-CEDZNkU2.js → PierreDiffViewer-C_1S9c9n.js} +1 -1
- package/dist/assets/PlanView-_eZvb-G5.js +5 -0
- package/dist/assets/PluginPane-sM8fcZIV.js +3 -0
- package/dist/assets/SettingsView-BRfr46hb.js +29 -0
- package/dist/assets/SettingsWindow-CcUDwQHg.js +1 -0
- package/dist/assets/SubmoduleDiffSummary-CrN1ZGwl.js +1 -0
- package/dist/assets/ToolOutputDialog-Cij4jI09.js +15 -0
- package/dist/assets/ToolPartDiffPreview-BXzwQ-JY.js +8 -0
- package/dist/assets/{VirtualizedFileDiff-NU0FHFhT.js → VirtualizedFileDiff-DSWTLJNH.js} +4 -4
- package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-CpUUgY8H.js} +2 -2
- package/dist/assets/appThemeRegistry-B5tHTkR8.js +1 -0
- package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-D7D8qV8p.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.js → computeVirtualFileMetrics-cq9uQ30i.js} +1 -1
- package/dist/assets/de-CUO1-KqF.js +10 -0
- package/dist/assets/es-BlbC6sGJ.js +16 -0
- package/dist/assets/fr-D2nwe_0W.js +14 -0
- package/dist/assets/index-BQOoYIOu.js +1 -0
- package/dist/assets/index-CLcM8HKx.css +1 -0
- package/dist/assets/index-CvLgnuNI.js +41 -0
- package/dist/assets/{isDiffPlainText-CO4e5J_S.js → isDiffPlainText-D0obJ2p8.js} +1 -1
- package/dist/assets/{isFilePlainText-ue6kCou7.js → isFilePlainText-DB3_rjyW.js} +1 -1
- package/dist/assets/ja-CA7vnDsZ.js +16 -0
- package/dist/assets/ko-Chy6a9l-.js +16 -0
- package/dist/assets/main-B9JFK3vF.js +896 -0
- package/dist/assets/main-Bhiqnrhd.js +2 -0
- package/dist/assets/{markdownCore-CK48LG0z.js → markdownCore-tfqOFaBd.js} +4 -4
- package/dist/assets/miniChat-B0UOL5zA.js +2 -0
- package/dist/assets/mobile-BJppFIEo.js +2 -0
- package/dist/assets/{patchFileDiff-D7QVlovD.js → patchFileDiff-Db6KuwCP.js} +3 -3
- package/dist/assets/pl-CTR5A_1P.js +16 -0
- package/dist/assets/pt-BR-BE1O1KEx.js +16 -0
- package/dist/assets/renderElectronMiniChatApp-Yz5sfoFh.js +2 -0
- package/dist/assets/renderMobileApp-QQ-yB8aY.js +25 -0
- package/dist/assets/runtimeEndpointReset-DKDdzRzm.js +27 -0
- package/dist/assets/scheduledTasksApi-C5sCuNqf.js +1 -0
- package/dist/assets/{shikiHighlight-BPu3JiQs.js → shikiHighlight-xURoo5t_.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.js → textMateThemeFromAppTheme-BZLGuYTU.js} +1 -1
- package/dist/assets/tr-Bh5vK6cW.js +7 -0
- package/dist/assets/uk-CLDkZFy_.js +16 -0
- package/dist/assets/useAppFontEffects-BxrtCnfR.js +7337 -0
- package/dist/assets/{useInlineCommentController-CgS0fnw_.js → useInlineCommentController-CeXuR-SR.js} +2 -2
- package/dist/assets/useRootScrollLock-DgWxezc1.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.js → vendor-codemirror-language-data-DOuaQUZ0.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-DxX8bZ1o.js → web-components-63H1h21j.js} +2 -2
- package/dist/assets/zh-CN-DODgcmYj.js +16 -0
- package/dist/assets/zh-TW-ByNfIwLA.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-B2CD9wC8.js +0 -4
- package/dist/assets/FilesView-C4vMFclc.js +0 -13
- package/dist/assets/LinearIssuesView-EzGNCGrd.js +0 -1
- package/dist/assets/MarkdownImageGallery-B2YUDFaB.js +0 -1
- package/dist/assets/MarkdownRendererImpl-B4ikIzR3.js +0 -12
- package/dist/assets/OnboardingScreen-CQHBSTEi.js +0 -1
- package/dist/assets/PlanView-D3x-pTkZ.js +0 -5
- package/dist/assets/PluginPane-DEcgDQ9h.js +0 -3
- package/dist/assets/SettingsView-CSinuH-1.js +0 -29
- package/dist/assets/SettingsWindow-B5CHoJFZ.js +0 -1
- package/dist/assets/SubmoduleDiffSummary-oxeACCWs.js +0 -1
- package/dist/assets/ToolOutputDialog-tLNWmfcW.js +0 -15
- package/dist/assets/ToolPartDiffPreview-CflzmK-x.js +0 -8
- package/dist/assets/appThemeRegistry-DLicw4dH.js +0 -1
- package/dist/assets/de-Bro6_zyl.js +0 -10
- package/dist/assets/es-Bb9XQZvd.js +0 -16
- package/dist/assets/fr-Dgfzn-IL.js +0 -14
- package/dist/assets/index-CtQNmqUL.js +0 -1
- package/dist/assets/index-DqaMWNrC.css +0 -1
- package/dist/assets/index-Dqc6USl3.js +0 -40
- package/dist/assets/ja-BYsakGFh.js +0 -16
- package/dist/assets/ko-BSaQz9aN.js +0 -16
- package/dist/assets/main-B62_pjL5.js +0 -2
- package/dist/assets/main-BEdcOI2P.js +0 -896
- package/dist/assets/miniChat-LT8Rqofr.js +0 -2
- package/dist/assets/mobile-CqarEynh.js +0 -2
- package/dist/assets/pl-OIviPdoj.js +0 -16
- package/dist/assets/pt-BR-Cltg-E6s.js +0 -16
- package/dist/assets/renderElectronMiniChatApp-COyLTtZx.js +0 -2
- package/dist/assets/renderMobileApp-BC1MHg6g.js +0 -25
- package/dist/assets/runtimeEndpointReset-DdrazOVH.js +0 -25
- package/dist/assets/scheduledTasksApi-B2SWMGEU.js +0 -1
- package/dist/assets/tr-CQI-KPre.js +0 -7
- package/dist/assets/uk-CqW7R4DA.js +0 -16
- package/dist/assets/useAppFontEffects-ne84JBVL.js +0 -7333
- package/dist/assets/useRootScrollLock-Cpf1qoGp.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-CVZG_o_J.js +0 -16
- package/dist/assets/zh-TW-D5ZxzCZp.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
|
@@ -9,8 +9,19 @@ import {
|
|
|
9
9
|
getJsonWriteTarget,
|
|
10
10
|
writeConfig,
|
|
11
11
|
} from './shared.js';
|
|
12
|
+
import {
|
|
13
|
+
toMcpEntity,
|
|
14
|
+
readLayeredMcpEntries,
|
|
15
|
+
writeMcpEntry,
|
|
16
|
+
deleteMcpEntry,
|
|
17
|
+
} from './config-v2.js';
|
|
12
18
|
|
|
13
19
|
// ============== MCP CONFIG HELPERS ==============
|
|
20
|
+
//
|
|
21
|
+
// OpenCode 2 keeps servers under `mcp.servers`; v1 kept them directly under
|
|
22
|
+
// `mcp`. OpenChamber reads both and always writes `mcp.servers` with the v2
|
|
23
|
+
// entry shape (`disabled`, `timeout: {catalog, execution}`, snake_case OAuth,
|
|
24
|
+
// required `type`).
|
|
14
25
|
|
|
15
26
|
/**
|
|
16
27
|
* Validate MCP server name
|
|
@@ -24,9 +35,6 @@ function validateMcpName(name) {
|
|
|
24
35
|
}
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
/**
|
|
28
|
-
* List all MCP server configs from user-level opencode.json
|
|
29
|
-
*/
|
|
30
38
|
function resolveMcpScopeFromPath(layers, sourcePath) {
|
|
31
39
|
if (!sourcePath) return null;
|
|
32
40
|
return sourcePath === layers.paths.projectPath ? AGENT_SCOPE.PROJECT : AGENT_SCOPE.USER;
|
|
@@ -40,20 +48,23 @@ function ensureProjectMcpConfigPath(workingDirectory) {
|
|
|
40
48
|
return path.join(configDir, 'opencode.json');
|
|
41
49
|
}
|
|
42
50
|
|
|
51
|
+
/** Same precedence as `getJsonEntrySource`: custom > project > user. */
|
|
52
|
+
function readMcpEntriesAcrossLayers(layers) {
|
|
53
|
+
return readLayeredMcpEntries([layers?.userConfig, layers?.projectConfig, layers?.customConfig]);
|
|
54
|
+
}
|
|
55
|
+
|
|
43
56
|
function listMcpConfigs(workingDirectory) {
|
|
44
57
|
const layers = readConfigLayers(workingDirectory);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
};
|
|
56
|
-
});
|
|
58
|
+
return Array.from(readMcpEntriesAcrossLayers(layers).entries()).map(([name, entry]) => {
|
|
59
|
+
const source = getJsonEntrySource(layers, 'mcp', name);
|
|
60
|
+
return {
|
|
61
|
+
name,
|
|
62
|
+
...toMcpEntity(entry.value),
|
|
63
|
+
scope: resolveMcpScopeFromPath(layers, source.path),
|
|
64
|
+
sectionKey: source.sectionKey,
|
|
65
|
+
legacy: Boolean(source.legacy),
|
|
66
|
+
};
|
|
67
|
+
});
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
/**
|
|
@@ -61,7 +72,7 @@ function listMcpConfigs(workingDirectory) {
|
|
|
61
72
|
*/
|
|
62
73
|
function getMcpConfig(name, workingDirectory) {
|
|
63
74
|
const layers = readConfigLayers(workingDirectory);
|
|
64
|
-
const entry = layers
|
|
75
|
+
const entry = readMcpEntriesAcrossLayers(layers).get(name);
|
|
65
76
|
|
|
66
77
|
if (!entry) {
|
|
67
78
|
return null;
|
|
@@ -69,8 +80,10 @@ function getMcpConfig(name, workingDirectory) {
|
|
|
69
80
|
const source = getJsonEntrySource(layers, 'mcp', name);
|
|
70
81
|
return {
|
|
71
82
|
name,
|
|
72
|
-
...
|
|
83
|
+
...toMcpEntity(entry.value),
|
|
73
84
|
scope: resolveMcpScopeFromPath(layers, source.path),
|
|
85
|
+
sectionKey: source.sectionKey,
|
|
86
|
+
legacy: Boolean(source.legacy),
|
|
74
87
|
};
|
|
75
88
|
}
|
|
76
89
|
|
|
@@ -81,8 +94,7 @@ function createMcpConfig(name, mcpConfig, workingDirectory, scope) {
|
|
|
81
94
|
validateMcpName(name);
|
|
82
95
|
|
|
83
96
|
const layers = readConfigLayers(workingDirectory);
|
|
84
|
-
|
|
85
|
-
if (source.exists) {
|
|
97
|
+
if (getJsonEntrySource(layers, 'mcp', name).exists) {
|
|
86
98
|
throw new Error(`MCP server "${name}" already exists`);
|
|
87
99
|
}
|
|
88
100
|
|
|
@@ -101,19 +113,17 @@ function createMcpConfig(name, mcpConfig, workingDirectory, scope) {
|
|
|
101
113
|
config = jsonTarget.config || {};
|
|
102
114
|
}
|
|
103
115
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const { name: _ignoredName, ...entryData } = mcpConfig;
|
|
109
|
-
config.mcp[name] = buildMcpEntry(entryData);
|
|
116
|
+
const { name: _ignoredName, scope: _ignoredScope, ...entryData } = mcpConfig || {};
|
|
117
|
+
writeMcpEntry(config, name, toMcpEntity(entryData));
|
|
110
118
|
|
|
111
119
|
writeConfig(config, targetPath);
|
|
112
120
|
console.log(`Created MCP server config: ${name}`);
|
|
121
|
+
return { path: targetPath };
|
|
113
122
|
}
|
|
114
123
|
|
|
115
124
|
/**
|
|
116
|
-
* Update an existing MCP server config entry
|
|
125
|
+
* Update an existing MCP server config entry. A server still stored under the
|
|
126
|
+
* v1 `mcp.<name>` key is rewritten into `mcp.servers` in the same file.
|
|
117
127
|
*/
|
|
118
128
|
function updateMcpConfig(name, updates, workingDirectory) {
|
|
119
129
|
const layers = readConfigLayers(workingDirectory);
|
|
@@ -126,17 +136,13 @@ function updateMcpConfig(name, updates, workingDirectory) {
|
|
|
126
136
|
const targetPath = source.path || CONFIG_FILE;
|
|
127
137
|
const config = source.config || (fs.existsSync(targetPath) ? readConfigFile(targetPath) : {});
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const existing = config.mcp[name];
|
|
134
|
-
const { name: _ignoredName, ...updateData } = updates;
|
|
135
|
-
|
|
136
|
-
config.mcp[name] = buildMcpEntry({ ...existing, ...updateData });
|
|
139
|
+
const existing = toMcpEntity(source.section);
|
|
140
|
+
const { name: _ignoredName, scope: _ignoredScope, ...updateData } = updates || {};
|
|
141
|
+
writeMcpEntry(config, name, toMcpEntity({ ...existing, ...updateData }));
|
|
137
142
|
|
|
138
143
|
writeConfig(config, targetPath);
|
|
139
|
-
console.log(`Updated MCP server config: ${name}`);
|
|
144
|
+
console.log(`Updated MCP server config: ${name} (${targetPath})`);
|
|
145
|
+
return { path: targetPath };
|
|
140
146
|
}
|
|
141
147
|
|
|
142
148
|
/**
|
|
@@ -148,125 +154,13 @@ function deleteMcpConfig(name, workingDirectory) {
|
|
|
148
154
|
const targetPath = source.path || CONFIG_FILE;
|
|
149
155
|
const config = source.config || (fs.existsSync(targetPath) ? readConfigFile(targetPath) : {});
|
|
150
156
|
|
|
151
|
-
if (!config
|
|
157
|
+
if (!deleteMcpEntry(config, name)) {
|
|
152
158
|
throw new Error(`MCP server "${name}" not found`);
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
delete config.mcp[name];
|
|
156
|
-
|
|
157
|
-
if (Object.keys(config.mcp).length === 0) {
|
|
158
|
-
delete config.mcp;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
161
|
writeConfig(config, targetPath);
|
|
162
162
|
console.log(`Deleted MCP server config: ${name}`);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Build a clean MCP entry object, omitting undefined/null values
|
|
167
|
-
*/
|
|
168
|
-
function buildMcpEntry(data) {
|
|
169
|
-
const entry = (data && typeof data === 'object' && !Array.isArray(data))
|
|
170
|
-
? { ...data }
|
|
171
|
-
: {};
|
|
172
|
-
|
|
173
|
-
delete entry.name;
|
|
174
|
-
delete entry.scope;
|
|
175
|
-
|
|
176
|
-
// type is required
|
|
177
|
-
entry.type = data.type === 'remote' ? 'remote' : 'local';
|
|
178
|
-
|
|
179
|
-
if (entry.type === 'local') {
|
|
180
|
-
// command must be a non-empty array of strings
|
|
181
|
-
if (Array.isArray(data.command) && data.command.length > 0) {
|
|
182
|
-
entry.command = data.command.map(String);
|
|
183
|
-
} else {
|
|
184
|
-
delete entry.command;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
delete entry.url;
|
|
188
|
-
delete entry.headers;
|
|
189
|
-
delete entry.oauth;
|
|
190
|
-
delete entry.timeout;
|
|
191
|
-
} else {
|
|
192
|
-
// remote: url required
|
|
193
|
-
if (data.url && typeof data.url === 'string') {
|
|
194
|
-
entry.url = data.url.trim();
|
|
195
|
-
} else {
|
|
196
|
-
delete entry.url;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
delete entry.command;
|
|
200
|
-
|
|
201
|
-
if (data.headers && typeof data.headers === 'object' && !Array.isArray(data.headers)) {
|
|
202
|
-
const cleaned = {};
|
|
203
|
-
for (const [k, v] of Object.entries(data.headers)) {
|
|
204
|
-
if (k && v !== undefined && v !== null) {
|
|
205
|
-
cleaned[k] = String(v);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (Object.keys(cleaned).length > 0) {
|
|
209
|
-
entry.headers = cleaned;
|
|
210
|
-
} else {
|
|
211
|
-
delete entry.headers;
|
|
212
|
-
}
|
|
213
|
-
} else if (data.headers === undefined) {
|
|
214
|
-
delete entry.headers;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (data.oauth === false) {
|
|
218
|
-
entry.oauth = false;
|
|
219
|
-
} else if (data.oauth && typeof data.oauth === 'object' && !Array.isArray(data.oauth)) {
|
|
220
|
-
const oauth = {};
|
|
221
|
-
if (typeof data.oauth.clientId === 'string' && data.oauth.clientId.trim()) {
|
|
222
|
-
oauth.clientId = data.oauth.clientId.trim();
|
|
223
|
-
}
|
|
224
|
-
if (typeof data.oauth.clientSecret === 'string' && data.oauth.clientSecret.trim()) {
|
|
225
|
-
oauth.clientSecret = data.oauth.clientSecret.trim();
|
|
226
|
-
}
|
|
227
|
-
if (typeof data.oauth.scope === 'string' && data.oauth.scope.trim()) {
|
|
228
|
-
oauth.scope = data.oauth.scope.trim();
|
|
229
|
-
}
|
|
230
|
-
if (typeof data.oauth.redirectUri === 'string' && data.oauth.redirectUri.trim()) {
|
|
231
|
-
oauth.redirectUri = data.oauth.redirectUri.trim();
|
|
232
|
-
}
|
|
233
|
-
if (Object.keys(oauth).length > 0) {
|
|
234
|
-
entry.oauth = oauth;
|
|
235
|
-
} else {
|
|
236
|
-
delete entry.oauth;
|
|
237
|
-
}
|
|
238
|
-
} else if (data.oauth === undefined) {
|
|
239
|
-
delete entry.oauth;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (typeof data.timeout === 'number' && Number.isFinite(data.timeout) && data.timeout > 0) {
|
|
243
|
-
entry.timeout = data.timeout;
|
|
244
|
-
} else if (data.timeout === undefined || data.timeout === null || data.timeout === '') {
|
|
245
|
-
delete entry.timeout;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// environment: flat Record<string, string>
|
|
250
|
-
if (data.environment && typeof data.environment === 'object' && !Array.isArray(data.environment)) {
|
|
251
|
-
const cleaned = {};
|
|
252
|
-
for (const [k, v] of Object.entries(data.environment)) {
|
|
253
|
-
if (k && v !== undefined && v !== null) {
|
|
254
|
-
cleaned[k] = String(v);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (Object.keys(cleaned).length > 0) {
|
|
258
|
-
entry.environment = cleaned;
|
|
259
|
-
} else {
|
|
260
|
-
delete entry.environment;
|
|
261
|
-
}
|
|
262
|
-
} else if (data.environment === undefined) {
|
|
263
|
-
delete entry.environment;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// enabled defaults to true
|
|
267
|
-
entry.enabled = data.enabled !== false;
|
|
268
|
-
|
|
269
|
-
return entry;
|
|
163
|
+
return { path: targetPath };
|
|
270
164
|
}
|
|
271
165
|
|
|
272
166
|
export {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readOpenCodeInfo, isSupportedOpenCodeVersion } from './compatibility.js';
|
|
1
2
|
export const createOpenCodeNetworkRuntime = (deps) => {
|
|
2
3
|
const {
|
|
3
4
|
state,
|
|
@@ -46,7 +47,9 @@ export const createOpenCodeNetworkRuntime = (deps) => {
|
|
|
46
47
|
try {
|
|
47
48
|
const controller = new AbortController();
|
|
48
49
|
timeout = setTimeout(() => controller.abort(), 3000);
|
|
49
|
-
|
|
50
|
+
// OpenCode 2.0.8 replaced `/api/health` with `/api/info`: a 200 is the
|
|
51
|
+
// readiness signal, the payload carries no `healthy` field.
|
|
52
|
+
const response = await fetch(`${url.replace(/\/+$/, '')}/api/info`, {
|
|
50
53
|
method: 'GET',
|
|
51
54
|
headers: {
|
|
52
55
|
Accept: 'application/json',
|
|
@@ -57,12 +60,8 @@ export const createOpenCodeNetworkRuntime = (deps) => {
|
|
|
57
60
|
clearTimeout(timeout);
|
|
58
61
|
timeout = null;
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (body?.healthy === true) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
63
|
+
const info = await readOpenCodeInfo(response);
|
|
64
|
+
if (info && isSupportedOpenCodeVersion(info.version)) return true;
|
|
66
65
|
} catch {
|
|
67
66
|
} finally {
|
|
68
67
|
if (timeout) {
|
|
@@ -68,9 +68,7 @@ beforeEach(() => {
|
|
|
68
68
|
packageManager.detectPackageManagerDetails.mockReturnValue({
|
|
69
69
|
packageManager: 'npm',
|
|
70
70
|
});
|
|
71
|
-
packageManager.getUpdateCommand.mockReturnValue(
|
|
72
|
-
'npm install -g @vantran-se/openchamber-web@latest --registry=https://registry.npmjs.org',
|
|
73
|
-
);
|
|
71
|
+
packageManager.getUpdateCommand.mockReturnValue('npm install -g @openchamber/web@latest');
|
|
74
72
|
});
|
|
75
73
|
|
|
76
74
|
afterEach(() => {
|
|
@@ -266,7 +264,7 @@ describe('OpenChamber foreground update route', () => {
|
|
|
266
264
|
'--setenv=PATH=/home/syu/.npm-global/bin:/usr/bin:/bin',
|
|
267
265
|
'/bin/sh',
|
|
268
266
|
'-c',
|
|
269
|
-
"set -eu\nnpm install -g @
|
|
267
|
+
"set -eu\nnpm install -g @openchamber/web@latest\nsystemctl --user restart 'openchamber@wsl.service'",
|
|
270
268
|
], {
|
|
271
269
|
encoding: 'utf8',
|
|
272
270
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -335,4 +333,3 @@ describe('OpenChamber web update route on Windows', () => {
|
|
|
335
333
|
expect(logError).toHaveBeenCalledOnce();
|
|
336
334
|
});
|
|
337
335
|
});
|
|
338
|
-
|
|
@@ -3,7 +3,7 @@ import os from 'os';
|
|
|
3
3
|
|
|
4
4
|
import { getNpmInfo as defaultGetNpmInfo } from './npm-registry.js';
|
|
5
5
|
import { isExactSemver as defaultIsExactSemver, isPathSpec as defaultIsPathSpec, parseNpmSpec as defaultParseNpmSpec, parsePathSpec as defaultParsePathSpec } from './plugin-spec.js';
|
|
6
|
-
import {
|
|
6
|
+
import { buildAppliedResponse } from './config-mutation-response.js';
|
|
7
7
|
|
|
8
8
|
const ENTRY_EXISTS_CODES = new Set(['ENTRY_EXISTS', 'EEXIST']);
|
|
9
9
|
const FILE_EXISTS_CODES = new Set(['FILE_EXISTS', 'EEXIST']);
|
|
@@ -46,8 +46,8 @@ export const registerPluginRoutes = (app, dependencies) => {
|
|
|
46
46
|
applyChange();
|
|
47
47
|
|
|
48
48
|
const pastTense = operation.replace(/ion$/, 'ed').replace(/update$/, 'updated');
|
|
49
|
-
return res.json(
|
|
50
|
-
`Plugin ${pastTense}
|
|
49
|
+
return res.json(buildAppliedResponse(
|
|
50
|
+
`Plugin ${pastTense}.`,
|
|
51
51
|
));
|
|
52
52
|
};
|
|
53
53
|
|
|
@@ -268,10 +268,7 @@ describe('opencode plugin routes', () => {
|
|
|
268
268
|
|
|
269
269
|
expect(response.body).toMatchObject({
|
|
270
270
|
success: true,
|
|
271
|
-
|
|
272
|
-
requiresRestart: true,
|
|
273
|
-
restartDeferred: true,
|
|
274
|
-
message: 'Plugin entry created. Restart OpenCode to apply.',
|
|
271
|
+
message: 'Plugin entry created.',
|
|
275
272
|
});
|
|
276
273
|
expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
|
|
277
274
|
});
|
|
@@ -307,10 +304,7 @@ describe('opencode plugin routes', () => {
|
|
|
307
304
|
|
|
308
305
|
expect(response.body.success).toBe(true);
|
|
309
306
|
expect(response.body).toMatchObject({
|
|
310
|
-
|
|
311
|
-
requiresRestart: true,
|
|
312
|
-
restartDeferred: true,
|
|
313
|
-
message: 'Plugin entry updated. Restart OpenCode to apply.',
|
|
307
|
+
message: 'Plugin entry updated.',
|
|
314
308
|
});
|
|
315
309
|
const after = await request(app).get('/api/config/plugins').expect(200);
|
|
316
310
|
expect(after.body.entries[0]).toEqual(expect.objectContaining({ spec: 'b', scope: 'user' }));
|
|
@@ -329,10 +323,7 @@ describe('opencode plugin routes', () => {
|
|
|
329
323
|
expect(readJson(userConfigPath).plugin).toBeUndefined();
|
|
330
324
|
expect(response.body).toMatchObject({
|
|
331
325
|
success: true,
|
|
332
|
-
|
|
333
|
-
requiresRestart: true,
|
|
334
|
-
restartDeferred: true,
|
|
335
|
-
message: 'Plugin entry deleted. Restart OpenCode to apply.',
|
|
326
|
+
message: 'Plugin entry deleted.',
|
|
336
327
|
});
|
|
337
328
|
expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
|
|
338
329
|
});
|
|
@@ -342,10 +333,7 @@ describe('opencode plugin routes', () => {
|
|
|
342
333
|
|
|
343
334
|
expect(response.body).toMatchObject({
|
|
344
335
|
success: true,
|
|
345
|
-
|
|
346
|
-
requiresRestart: true,
|
|
347
|
-
restartDeferred: true,
|
|
348
|
-
message: 'Plugin file created. Restart OpenCode to apply.',
|
|
336
|
+
message: 'Plugin file created.',
|
|
349
337
|
});
|
|
350
338
|
expect(fs.readFileSync(path.join(rootDir, 'plugins', 'test.js'), 'utf8')).toBe('//x');
|
|
351
339
|
expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
|
|
@@ -375,10 +363,7 @@ describe('opencode plugin routes', () => {
|
|
|
375
363
|
expect(fs.readFileSync(path.join(rootDir, 'plugins', 'test.js'), 'utf8')).toBe('//y');
|
|
376
364
|
expect(response.body).toMatchObject({
|
|
377
365
|
success: true,
|
|
378
|
-
|
|
379
|
-
requiresRestart: true,
|
|
380
|
-
restartDeferred: true,
|
|
381
|
-
message: 'Plugin file updated. Restart OpenCode to apply.',
|
|
366
|
+
message: 'Plugin file updated.',
|
|
382
367
|
});
|
|
383
368
|
expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
|
|
384
369
|
});
|
|
@@ -393,10 +378,7 @@ describe('opencode plugin routes', () => {
|
|
|
393
378
|
expect(fs.existsSync(path.join(rootDir, 'plugins', 'test.js'))).toBe(false);
|
|
394
379
|
expect(response.body).toMatchObject({
|
|
395
380
|
success: true,
|
|
396
|
-
|
|
397
|
-
requiresRestart: true,
|
|
398
|
-
restartDeferred: true,
|
|
399
|
-
message: 'Plugin file deleted. Restart OpenCode to apply.',
|
|
381
|
+
message: 'Plugin file deleted.',
|
|
400
382
|
});
|
|
401
383
|
expect(refreshOpenCodeAfterConfigChange).not.toHaveBeenCalled();
|
|
402
384
|
});
|
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
writeConfig,
|
|
9
9
|
} from './shared.js';
|
|
10
10
|
import { isPathSpec } from './plugin-spec.js';
|
|
11
|
+
import { PLUGIN_SECTION, toPluginEntity, fromPluginEntity, readPluginList } from './config-v2.js';
|
|
12
|
+
|
|
13
|
+
// OpenCode 2 reads `plugins` (bare string or `{package, options}`) and still
|
|
14
|
+
// decodes the v1 `plugin` array with its `[spec, options]` tuples. OpenChamber
|
|
15
|
+
// reads both and always writes `plugins` with v2 entries.
|
|
11
16
|
|
|
12
17
|
const PLUGIN_FILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-_.]*\.(js|ts|mjs|cjs)$/;
|
|
13
18
|
|
|
@@ -77,7 +82,6 @@ function getActiveOpencodeConfigDir() {
|
|
|
77
82
|
function getActiveUserConfigPaths() {
|
|
78
83
|
const configDir = getActiveOpencodeConfigDir();
|
|
79
84
|
return [
|
|
80
|
-
path.join(configDir, 'config.json'),
|
|
81
85
|
path.join(configDir, 'opencode.json'),
|
|
82
86
|
path.join(configDir, 'opencode.jsonc'),
|
|
83
87
|
];
|
|
@@ -184,9 +188,12 @@ function getPluginTarget(id, workingDirectory) {
|
|
|
184
188
|
validateScope(scope);
|
|
185
189
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
186
190
|
const source = configSources(layers).find((candidate) => candidate.scope === scope);
|
|
187
|
-
|
|
191
|
+
if (!source) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
const plugin = pluginArrayForWrite(source.config);
|
|
188
195
|
const index = plugin.findIndex((raw) => parsePluginRaw(raw).spec === spec);
|
|
189
|
-
if (
|
|
196
|
+
if (index === -1) {
|
|
190
197
|
return null;
|
|
191
198
|
}
|
|
192
199
|
return { source, plugin, index };
|
|
@@ -203,6 +210,16 @@ function pluginDirForScope(scope, workingDirectory) {
|
|
|
203
210
|
return path.join(getActiveOpencodeConfigDir(), 'plugins');
|
|
204
211
|
}
|
|
205
212
|
|
|
213
|
+
/**
|
|
214
|
+
* The directories OpenCode 2 scans for local plugins in a scope: `plugins/`
|
|
215
|
+
* (where OpenChamber writes) and the v1 `plugin/`. Each holds `.ts`/`.js`
|
|
216
|
+
* files and plugin package directories.
|
|
217
|
+
*/
|
|
218
|
+
function pluginDirsForScope(scope, workingDirectory) {
|
|
219
|
+
const preferred = pluginDirForScope(scope, workingDirectory);
|
|
220
|
+
return [preferred, path.join(path.dirname(preferred), 'plugin')];
|
|
221
|
+
}
|
|
222
|
+
|
|
206
223
|
function fileTargetFromId(id, workingDirectory) {
|
|
207
224
|
const decoded = decodePluginId(id);
|
|
208
225
|
if (decoded.prefix !== 'file') {
|
|
@@ -231,41 +248,71 @@ function decodePluginId(id) {
|
|
|
231
248
|
return { prefix: decoded.slice(0, separator), value: decoded.slice(separator + 1) };
|
|
232
249
|
}
|
|
233
250
|
|
|
251
|
+
/** Accept a v1 string/tuple or a v2 `{package, options}` object. */
|
|
234
252
|
function parsePluginRaw(raw) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
if (Array.isArray(raw) && raw.length === 2 && isRecord(raw[1])) {
|
|
239
|
-
return { spec: validatePluginSpec(raw[0]), options: { ...raw[1] } };
|
|
253
|
+
const entity = toPluginEntity(raw);
|
|
254
|
+
if (!entity) {
|
|
255
|
+
throw codedError('Plugin spec must be a string, [string, object], or {package, options}', 'INVALID_SPEC');
|
|
240
256
|
}
|
|
241
|
-
|
|
257
|
+
const parsed = { spec: validatePluginSpec(entity.package) };
|
|
258
|
+
if (hasOptions(entity.options)) parsed.options = { ...entity.options };
|
|
259
|
+
return parsed;
|
|
242
260
|
}
|
|
243
261
|
|
|
262
|
+
/** Always v2: a bare string, or `{package, options}`. Never a tuple. */
|
|
244
263
|
function serializePluginEntry(entry) {
|
|
245
264
|
const spec = validatePluginSpec(entry?.spec);
|
|
246
|
-
|
|
247
|
-
|
|
265
|
+
const serialized = fromPluginEntity({ package: spec, options: hasOptions(entry?.options) ? entry.options : undefined });
|
|
266
|
+
if (serialized === null) {
|
|
267
|
+
throw codedError('Plugin spec must be a non-empty string', 'INVALID_SPEC');
|
|
248
268
|
}
|
|
249
|
-
return
|
|
269
|
+
return serialized;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function migratePluginRaw(raw) {
|
|
273
|
+
try {
|
|
274
|
+
return serializePluginEntry(parsePluginRaw(raw));
|
|
275
|
+
} catch {
|
|
276
|
+
return raw;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The plugin array a write should target. Prefer the v2 `plugins` key; when the
|
|
282
|
+
* file only has the v1 `plugin` array, migrate the whole array into `plugins`
|
|
283
|
+
* in place so the two spellings cannot disagree.
|
|
284
|
+
*/
|
|
285
|
+
function pluginArrayForWrite(config) {
|
|
286
|
+
const legacy = Array.isArray(config[PLUGIN_SECTION.v1]) ? config[PLUGIN_SECTION.v1] : null;
|
|
287
|
+
if (!Array.isArray(config[PLUGIN_SECTION.v2])) config[PLUGIN_SECTION.v2] = [];
|
|
288
|
+
if (legacy) {
|
|
289
|
+
config[PLUGIN_SECTION.v2] = [
|
|
290
|
+
// An entry we cannot parse is carried over untouched rather than dropped:
|
|
291
|
+
// it is the user's config, and losing it would be worse than leaving it
|
|
292
|
+
// in a shape OpenCode already refuses.
|
|
293
|
+
...legacy.map((raw) => migratePluginRaw(raw)),
|
|
294
|
+
...config[PLUGIN_SECTION.v2],
|
|
295
|
+
];
|
|
296
|
+
delete config[PLUGIN_SECTION.v1];
|
|
297
|
+
}
|
|
298
|
+
return config[PLUGIN_SECTION.v2];
|
|
250
299
|
}
|
|
251
300
|
|
|
252
301
|
function listPluginEntries(workingDirectory) {
|
|
253
302
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
254
303
|
return configSources(layers).flatMap((source) => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const parsed = parsePluginRaw(raw);
|
|
260
|
-
return {
|
|
261
|
-
id: encodePluginId('config', `${source.scope}:${parsed.spec}`),
|
|
262
|
-
spec: parsed.spec,
|
|
263
|
-
...(parsed.options !== undefined ? { options: parsed.options } : {}),
|
|
304
|
+
return readPluginList(source.config).map((item) => {
|
|
305
|
+
const entry = {
|
|
306
|
+
id: encodePluginId('config', `${source.scope}:${item.entry.package}`),
|
|
307
|
+
spec: item.entry.package,
|
|
264
308
|
scope: source.scope,
|
|
265
309
|
kind: 'config',
|
|
266
|
-
parsedKind: parsedKindForSpec(
|
|
310
|
+
parsedKind: parsedKindForSpec(item.entry.package),
|
|
267
311
|
sourcePath: source.filePath,
|
|
312
|
+
legacy: item.legacy,
|
|
268
313
|
};
|
|
314
|
+
if (item.entry.options !== undefined) entry.options = item.entry.options;
|
|
315
|
+
return entry;
|
|
269
316
|
});
|
|
270
317
|
});
|
|
271
318
|
}
|
|
@@ -282,8 +329,7 @@ function createPluginEntry(entry, workingDirectory) {
|
|
|
282
329
|
const layers = readPluginConfigLayers(workingDirectory);
|
|
283
330
|
const existing = configSources(layers).find((source) => (
|
|
284
331
|
source.scope === scope
|
|
285
|
-
&&
|
|
286
|
-
&& source.config.plugin.some((raw) => parsePluginRaw(raw).spec === spec)
|
|
332
|
+
&& readPluginList(source.config).some((item) => item.entry.package === spec)
|
|
287
333
|
));
|
|
288
334
|
if (existing) {
|
|
289
335
|
throw codedError(`Plugin "${spec}" already exists`, 'ENTRY_EXISTS');
|
|
@@ -299,10 +345,7 @@ function createPluginEntry(entry, workingDirectory) {
|
|
|
299
345
|
config = layers.paths.customPath ? layers.customConfig : layers.userConfig;
|
|
300
346
|
}
|
|
301
347
|
|
|
302
|
-
|
|
303
|
-
config.plugin = [];
|
|
304
|
-
}
|
|
305
|
-
config.plugin.push(serializePluginEntry({ spec, options: entry.options }));
|
|
348
|
+
pluginArrayForWrite(config).push(serializePluginEntry({ spec, options: entry.options }));
|
|
306
349
|
writeConfig(config, targetPath);
|
|
307
350
|
}
|
|
308
351
|
|
|
@@ -325,7 +368,7 @@ function deletePluginEntry(id, workingDirectory) {
|
|
|
325
368
|
}
|
|
326
369
|
target.plugin.splice(target.index, 1);
|
|
327
370
|
if (target.plugin.length === 0) {
|
|
328
|
-
delete target.source.config.
|
|
371
|
+
delete target.source.config[PLUGIN_SECTION.v2];
|
|
329
372
|
}
|
|
330
373
|
writeConfig(target.source.config, target.source.filePath);
|
|
331
374
|
}
|
|
@@ -335,21 +378,29 @@ function listPluginDirFiles(workingDirectory) {
|
|
|
335
378
|
if (workingDirectory) {
|
|
336
379
|
scopes.push(AGENT_SCOPE.PROJECT);
|
|
337
380
|
}
|
|
338
|
-
return scopes.flatMap((scope) => {
|
|
339
|
-
const dir = pluginDirForScope(scope, workingDirectory);
|
|
381
|
+
return scopes.flatMap((scope) => pluginDirsForScope(scope, workingDirectory).flatMap((dir) => {
|
|
340
382
|
if (!fs.existsSync(dir)) {
|
|
341
383
|
return [];
|
|
342
384
|
}
|
|
385
|
+
const editable = dir === pluginDirForScope(scope, workingDirectory);
|
|
343
386
|
return fs.readdirSync(dir, { withFileTypes: true })
|
|
344
|
-
.filter((entry) => entry.
|
|
345
|
-
.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
387
|
+
.filter((entry) => !entry.name.includes('..') && !entry.name.startsWith('.'))
|
|
388
|
+
.flatMap((entry) => {
|
|
389
|
+
if (entry.isFile() && PLUGIN_FILE_NAME_PATTERN.test(entry.name)) {
|
|
390
|
+
// Only files in the v2 `plugins/` directory are editable through
|
|
391
|
+
// the file id; a v1 `plugin/` file is listed for what it is.
|
|
392
|
+
return editable
|
|
393
|
+
? [{ id: encodePluginId('file', `${scope}:${entry.name}`), fileName: entry.name, scope, kind: 'file', absolutePath: path.join(dir, entry.name) }]
|
|
394
|
+
: [{ id: encodePluginId('legacy', `${scope}:${entry.name}`), fileName: entry.name, scope, kind: 'package', absolutePath: path.join(dir, entry.name) }];
|
|
395
|
+
}
|
|
396
|
+
if (entry.isDirectory()) {
|
|
397
|
+
// A plugin package (a directory with its own entry point): OpenCode
|
|
398
|
+
// loads it, OpenChamber only lists it.
|
|
399
|
+
return [{ id: encodePluginId('package', `${scope}:${entry.name}`), fileName: entry.name, scope, kind: 'package', absolutePath: path.join(dir, entry.name) }];
|
|
400
|
+
}
|
|
401
|
+
return [];
|
|
402
|
+
});
|
|
403
|
+
}));
|
|
353
404
|
}
|
|
354
405
|
|
|
355
406
|
function readPluginDirFile(id, workingDirectory) {
|