@vantran-se/openchamber-web 1.24.4 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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-ntXIbhV2.js} +1 -1
- package/dist/assets/DiffView-CjxUovbM.js +4 -0
- package/dist/assets/FilesView-DFWD0RQR.js +17 -0
- package/dist/assets/{GitView-Byu2DY7s.js → GitView-BWa7e5vw.js} +4 -4
- package/dist/assets/{GuestAttachDialog-B808E0-d.js → GuestAttachDialog-DdOuyZzp.js} +1 -1
- package/dist/assets/GuestSurfacePane-DDfzid3U.js +1 -0
- package/dist/assets/{JsonTreeView-VW_7czo3.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-DCjGhlYV.js → PatchDiff-DS-IMU1j.js} +1 -1
- package/dist/assets/{PierreDiffViewer-CEDZNkU2.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-NU0FHFhT.js → VirtualizedFileDiff-C763hNCR.js} +4 -4
- package/dist/assets/{WalkthroughView-BZyjhqHv.js → WalkthroughView-lap5btCF.js} +2 -2
- package/dist/assets/appThemeRegistry-u4zw1l0B.js +1 -0
- package/dist/assets/{commitAndPush-BDftrjr6.js → commitAndPush-CwGJfJG-.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-DlWlEM3x.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-CO4e5J_S.js → isDiffPlainText-D47a6hMP.js} +1 -1
- package/dist/assets/{isFilePlainText-ue6kCou7.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-CK48LG0z.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-D7QVlovD.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-BPu3JiQs.js → shikiHighlight-DNm2Z_Jc.js} +1 -1
- package/dist/assets/{textMateThemeFromAppTheme-DWqRPo9A.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-CgS0fnw_.js → useInlineCommentController-CuUuakrt.js} +2 -2
- package/dist/assets/useRootScrollLock-CQIsyERq.js +2 -0
- package/dist/assets/{vendor-codemirror-language-data-Bx2EHc1O.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-DxX8bZ1o.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-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
|
@@ -102,7 +102,8 @@ const createRuntime = (overrides = {}, stateOverrides = {}, envOverrides = {}) =
|
|
|
102
102
|
waitForReady: vi.fn(async () => true),
|
|
103
103
|
normalizeApiPrefix: vi.fn(() => ''),
|
|
104
104
|
applyOpencodeBinaryFromSettings: vi.fn(async () => null),
|
|
105
|
-
|
|
105
|
+
checkOpenCodeBinary: async () => '2.0.14',
|
|
106
|
+
ensureOpencodeCliEnv: vi.fn(),
|
|
106
107
|
ensureLocalOpenCodeServerPassword: vi.fn(async () => 'password'),
|
|
107
108
|
resolveManagedOpenCodeLaunchSpec: vi.fn((binary) => ({ binary, args: [], wrapperType: null })),
|
|
108
109
|
setOpenCodePort: vi.fn((port) => {
|
|
@@ -114,6 +115,8 @@ const createRuntime = (overrides = {}, stateOverrides = {}, envOverrides = {}) =
|
|
|
114
115
|
clearResolvedOpenCodeBinary: vi.fn(),
|
|
115
116
|
buildAugmentedPath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
|
|
116
117
|
buildManagedOpenCodePath: vi.fn(() => '/home/user/.bun/bin:/usr/local/bin:/usr/bin'),
|
|
118
|
+
// Never let a test touch the real `~/.local/share/opencode/opencode.db`.
|
|
119
|
+
topUpV1SessionMigration: vi.fn(() => ({ status: 'skipped', missing: 0, revisited: 0, reason: 'no-database' })),
|
|
117
120
|
getManagedOpenCodeShellEnvSnapshot: vi.fn(() => ({
|
|
118
121
|
PATH: '/home/user/.bun/bin:/usr/local/bin:/usr/bin',
|
|
119
122
|
SHELL_ONLY: 'yes',
|
|
@@ -158,7 +161,7 @@ describe('OpenCode lifecycle', () => {
|
|
|
158
161
|
it('records an authoritative ready terminal event for external startup', async () => {
|
|
159
162
|
globalThis.fetch = vi.fn(async () => ({
|
|
160
163
|
ok: true,
|
|
161
|
-
json: async () => ({
|
|
164
|
+
json: async () => ({ version: '2.0.15', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
|
|
162
165
|
}));
|
|
163
166
|
const runtime = createRuntime({
|
|
164
167
|
env: {
|
|
@@ -190,7 +193,7 @@ describe('OpenCode lifecycle', () => {
|
|
|
190
193
|
it('recovers an external OPENCODE_HOST connection using its configured endpoint', async () => {
|
|
191
194
|
const fetchMock = vi.fn(async () => ({
|
|
192
195
|
ok: true,
|
|
193
|
-
json: async () => ({
|
|
196
|
+
json: async () => ({ version: '2.0.15', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
|
|
194
197
|
}));
|
|
195
198
|
globalThis.fetch = fetchMock;
|
|
196
199
|
const runtime = createRuntime({}, {
|
|
@@ -206,7 +209,7 @@ describe('OpenCode lifecycle', () => {
|
|
|
206
209
|
await runtime.restartOpenCode();
|
|
207
210
|
|
|
208
211
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
209
|
-
'http://seamus:4095/
|
|
212
|
+
'http://seamus:4095/api/info',
|
|
210
213
|
expect.objectContaining({ method: 'GET' }),
|
|
211
214
|
);
|
|
212
215
|
expect(runtime.testState.openCodePort).toBe(4095);
|
|
@@ -240,7 +243,7 @@ describe('OpenCode lifecycle', () => {
|
|
|
240
243
|
it('warms recently used directories after a successful bootstrap', async () => {
|
|
241
244
|
const fetchMock = vi.fn(async () => ({
|
|
242
245
|
ok: true,
|
|
243
|
-
json: async () => ({
|
|
246
|
+
json: async () => ({ version: '2.0.15', pid: 1, urls: [], paths: { tmp: '/tmp' } }),
|
|
244
247
|
}));
|
|
245
248
|
globalThis.fetch = fetchMock;
|
|
246
249
|
const runtime = createRuntime({
|
|
@@ -260,10 +263,10 @@ describe('OpenCode lifecycle', () => {
|
|
|
260
263
|
|
|
261
264
|
const warmupUrls = fetchMock.mock.calls
|
|
262
265
|
.map(([url]) => String(url))
|
|
263
|
-
.filter((url) => url.includes('/session
|
|
266
|
+
.filter((url) => url.includes('/api/session?'));
|
|
264
267
|
expect(warmupUrls).toEqual([
|
|
265
|
-
'http://127.0.0.1:45678/session
|
|
266
|
-
'http://127.0.0.1:45678/session
|
|
268
|
+
'http://127.0.0.1:45678/api/session?directory=%2Ftmp%2Fworktree-a&limit=1',
|
|
269
|
+
'http://127.0.0.1:45678/api/session?directory=%2Ftmp%2Fproject-b&limit=1',
|
|
267
270
|
]);
|
|
268
271
|
});
|
|
269
272
|
|
|
@@ -876,6 +879,28 @@ describe('OpenCode lifecycle', () => {
|
|
|
876
879
|
expect(spawnMock).toHaveBeenCalledTimes(2);
|
|
877
880
|
await server.close();
|
|
878
881
|
});
|
|
882
|
+
|
|
883
|
+
it('tops up the v1 session migration before spawning managed OpenCode', async () => {
|
|
884
|
+
const calls = [];
|
|
885
|
+
const topUpV1SessionMigration = vi.fn(() => {
|
|
886
|
+
calls.push('top-up');
|
|
887
|
+
return { status: 'scheduled', missing: 3, revisited: 0 };
|
|
888
|
+
});
|
|
889
|
+
spawnMock.mockImplementation(() => {
|
|
890
|
+
calls.push('spawn');
|
|
891
|
+
const child = createMockChild();
|
|
892
|
+
queueMicrotask(() => child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n'));
|
|
893
|
+
return child;
|
|
894
|
+
});
|
|
895
|
+
globalThis.fetch = vi.fn(async () => ({ ok: false }));
|
|
896
|
+
|
|
897
|
+
const runtime = createRuntime({ topUpV1SessionMigration });
|
|
898
|
+
await runtime.startOpenCode();
|
|
899
|
+
|
|
900
|
+
expect(topUpV1SessionMigration).toHaveBeenCalledTimes(1);
|
|
901
|
+
expect(calls).toEqual(['top-up', 'spawn']);
|
|
902
|
+
});
|
|
903
|
+
|
|
879
904
|
});
|
|
880
905
|
|
|
881
906
|
describe('killProcessOnPort on Windows', () => {
|
|
@@ -944,3 +969,50 @@ describe('killProcessOnPort on Windows', () => {
|
|
|
944
969
|
expect(spawnSyncMock).not.toHaveBeenCalledWith('taskkill', expect.anything(), expect.anything());
|
|
945
970
|
});
|
|
946
971
|
});
|
|
972
|
+
|
|
973
|
+
it('shares the managed CLI preflight with desktop while startup is pending', async () => {
|
|
974
|
+
let finish;
|
|
975
|
+
let entered;
|
|
976
|
+
const checking = new Promise(resolve => { entered = resolve; });
|
|
977
|
+
const check = new Promise(resolve => { finish = resolve; });
|
|
978
|
+
let checks = 0;
|
|
979
|
+
const runtime = createRuntime({
|
|
980
|
+
checkOpenCodeBinary: () => { checks += 1; entered(); return check; },
|
|
981
|
+
ensureOpencodeCliEnv: () => '/tmp/opencode',
|
|
982
|
+
});
|
|
983
|
+
spawnMock.mockImplementation(() => {
|
|
984
|
+
const child = createMockChild();
|
|
985
|
+
queueMicrotask(() => child.stdout.emit('data', 'opencode server listening on http://127.0.0.1:45678\n'));
|
|
986
|
+
return child;
|
|
987
|
+
});
|
|
988
|
+
expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
|
|
989
|
+
const starting = runtime.startOpenCode();
|
|
990
|
+
await checking;
|
|
991
|
+
const desktopVerdict = runtime.getManagedOpenCodePreflight();
|
|
992
|
+
expect(runtime.testState.isOpenCodeReady).toBe(false);
|
|
993
|
+
finish('2.0.15');
|
|
994
|
+
expect(await desktopVerdict).toBe(true);
|
|
995
|
+
expect(checks).toBe(1);
|
|
996
|
+
const server = await starting;
|
|
997
|
+
try {
|
|
998
|
+
expect(await runtime.getManagedOpenCodePreflight()).toBe(true);
|
|
999
|
+
runtime.testState.isExternalOpenCode = true;
|
|
1000
|
+
expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
|
|
1001
|
+
runtime.testState.isExternalOpenCode = false;
|
|
1002
|
+
runtime.testState.isShuttingDown = true;
|
|
1003
|
+
expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
|
|
1004
|
+
} finally {
|
|
1005
|
+
await server.close();
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
it('a rejected CLI preflight never permits desktop bootstrap', async () => {
|
|
1010
|
+
const runtime = createRuntime({
|
|
1011
|
+
checkOpenCodeBinary: async () => {
|
|
1012
|
+
throw Object.assign(new Error('Unsupported CLI'), { code: 'OPENCODE_BINARY_INVALID' });
|
|
1013
|
+
},
|
|
1014
|
+
});
|
|
1015
|
+
await expect(runtime.startOpenCode()).rejects.toThrow('Unsupported CLI');
|
|
1016
|
+
expect(await runtime.getManagedOpenCodePreflight()).toBe(false);
|
|
1017
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
1018
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handoff of a login-shell environment snapshot from an embedding host.
|
|
3
|
+
*
|
|
4
|
+
* The server snapshots the user's login shell (`$SHELL -lic 'env -0'`) at
|
|
5
|
+
* import time to learn the PATH a Finder- or Dock-launched process never
|
|
6
|
+
* inherits. Desktop already runs the same probe asynchronously while its
|
|
7
|
+
* window comes up; running it again here would block the Electron main
|
|
8
|
+
* thread for as long as the user's shell startup files take, a second time.
|
|
9
|
+
* A host that has a snapshot hands it over before importing the server, and
|
|
10
|
+
* the server uses it instead of probing. `null` means the host probed and got
|
|
11
|
+
* nothing: the server does not retry, because that retry would be
|
|
12
|
+
* synchronous and bounded only by the probe timeout.
|
|
13
|
+
*
|
|
14
|
+
* Kept in this module rather than in the environment on purpose: the
|
|
15
|
+
* snapshot is the user's full shell environment, secrets included, and
|
|
16
|
+
* process.env is inherited by every child the server spawns.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
let provided;
|
|
20
|
+
|
|
21
|
+
// `snapshot` is the parsed `env -0` map, or null when the host's probe failed.
|
|
22
|
+
export const provideLoginShellEnvSnapshot = (snapshot) => {
|
|
23
|
+
provided = snapshot ? { ...snapshot } : null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// `undefined` when no host provided one, so the server probes itself.
|
|
27
|
+
export const providedLoginShellEnvSnapshot = () => provided;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { appendManagedPlugin } from './managed-plugin-config.js';
|
|
2
|
+
|
|
3
|
+
export const MANAGED_CONFIG_FILE_NAME = 'opencode.managed.json';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The managed OpenCode config layer.
|
|
7
|
+
*
|
|
8
|
+
* OpenCode 2 watches every config source it reads, including the file named by
|
|
9
|
+
* the `OPENCODE_CONFIG` environment variable, and reloads its plugin list when
|
|
10
|
+
* that file changes. Writing OpenChamber's own plugin entries into a file we
|
|
11
|
+
* own therefore makes toggling a managed tool take effect in the running
|
|
12
|
+
* process — where `OPENCODE_CONFIG_CONTENT` (an environment variable) could
|
|
13
|
+
* only ever change across a restart.
|
|
14
|
+
*
|
|
15
|
+
* The file is OpenChamber-owned and holds nothing but `plugins`. It sits above
|
|
16
|
+
* the user's global `opencode.json` and below their project config, so it never
|
|
17
|
+
* shadows a project-level choice.
|
|
18
|
+
*
|
|
19
|
+
* When the user's own environment already sets `OPENCODE_CONFIG`, that file is
|
|
20
|
+
* theirs: OpenChamber does not hijack it and falls back to merging its plugins
|
|
21
|
+
* into `OPENCODE_CONFIG_CONTENT`, which keeps the old restart requirement for
|
|
22
|
+
* managed-tool toggles.
|
|
23
|
+
*
|
|
24
|
+
* @param {object} dependencies
|
|
25
|
+
* @param {typeof import('node:fs/promises')} dependencies.fsPromises
|
|
26
|
+
* @param {typeof import('node:path')} dependencies.path
|
|
27
|
+
* @param {string} dependencies.dataDir OpenChamber data directory
|
|
28
|
+
* @param {NodeJS.ProcessEnv} [dependencies.env] environment the managed child inherits
|
|
29
|
+
* @param {object|null} [dependencies.agentToolRuntime]
|
|
30
|
+
* @param {() => Promise<object|null>} dependencies.readSettings
|
|
31
|
+
* @param {() => boolean} dependencies.isAgentMemoryAvailable
|
|
32
|
+
*/
|
|
33
|
+
export const createManagedConfigRuntime = ({
|
|
34
|
+
fsPromises,
|
|
35
|
+
path,
|
|
36
|
+
dataDir,
|
|
37
|
+
env = process.env,
|
|
38
|
+
agentToolRuntime = null,
|
|
39
|
+
readSettings,
|
|
40
|
+
isAgentMemoryAvailable,
|
|
41
|
+
}) => {
|
|
42
|
+
const filePath = path.join(dataDir, MANAGED_CONFIG_FILE_NAME);
|
|
43
|
+
|
|
44
|
+
/** The user owns `OPENCODE_CONFIG` when their environment already set it. */
|
|
45
|
+
const ownsConfigFile = () => (env.OPENCODE_CONFIG ?? '').trim().length === 0;
|
|
46
|
+
|
|
47
|
+
const writeConfigFile = async (pluginDirectories) => {
|
|
48
|
+
await fsPromises.mkdir(dataDir, { recursive: true });
|
|
49
|
+
const text = `${JSON.stringify({ plugins: pluginDirectories }, null, 2)}\n`;
|
|
50
|
+
// Temp + rename: OpenCode reacts to the write immediately, and a partial
|
|
51
|
+
// read would make it drop every managed plugin until the next change.
|
|
52
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
53
|
+
try {
|
|
54
|
+
await fsPromises.writeFile(tmp, text, { encoding: 'utf8', mode: 0o600 });
|
|
55
|
+
await fsPromises.rename(tmp, filePath);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
await fsPromises.rm(tmp, { force: true }).catch(() => {});
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Materialize the plugins the current settings ask for and return their
|
|
64
|
+
* directories in load order. Directories exist on disk before the config
|
|
65
|
+
* file names them.
|
|
66
|
+
*/
|
|
67
|
+
const materializeEnabledPlugins = async () => {
|
|
68
|
+
const settings = await Promise.resolve(readSettings()).catch(() => null);
|
|
69
|
+
const includeControl = settings?.agentControlToolEnabled !== false;
|
|
70
|
+
const includeWeb = settings?.agentWebToolEnabled !== false;
|
|
71
|
+
const includeMemory = isAgentMemoryAvailable() && settings?.agentMemoryToolEnabled === true;
|
|
72
|
+
|
|
73
|
+
const directories = [];
|
|
74
|
+
if (agentToolRuntime && (includeControl || includeWeb || includeMemory)) {
|
|
75
|
+
directories.push(await agentToolRuntime.materializePlugin({ includeControl, includeWeb, includeMemory }));
|
|
76
|
+
}
|
|
77
|
+
return directories;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Environment for a managed OpenCode child: the callback URL and per-child
|
|
82
|
+
* token are always present, so a tool enabled later in the session can still
|
|
83
|
+
* call back without a restart.
|
|
84
|
+
*/
|
|
85
|
+
const buildManagedChildEnv = async () => {
|
|
86
|
+
const directories = await materializeEnabledPlugins();
|
|
87
|
+
const childEnv = agentToolRuntime ? agentToolRuntime.createChildEnv() : {};
|
|
88
|
+
if (ownsConfigFile()) {
|
|
89
|
+
await writeConfigFile(directories);
|
|
90
|
+
return { ...childEnv, OPENCODE_CONFIG: filePath };
|
|
91
|
+
}
|
|
92
|
+
if (directories.length === 0) return childEnv;
|
|
93
|
+
let content = env.OPENCODE_CONFIG_CONTENT;
|
|
94
|
+
for (const directory of directories) {
|
|
95
|
+
content = appendManagedPlugin(content, directory, 'managed plugin');
|
|
96
|
+
}
|
|
97
|
+
return { ...childEnv, OPENCODE_CONFIG_CONTENT: content };
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Rewrite the managed config file after a managed-plugin setting changed, so
|
|
102
|
+
* the running OpenCode picks the new plugin list up. A no-op when the user
|
|
103
|
+
* owns `OPENCODE_CONFIG`; those installs still need a restart.
|
|
104
|
+
*
|
|
105
|
+
* @returns {Promise<{updated: boolean, reason?: string}>}
|
|
106
|
+
*/
|
|
107
|
+
const refreshManagedConfigFile = async () => {
|
|
108
|
+
if (!ownsConfigFile()) return { updated: false, reason: 'external-config' };
|
|
109
|
+
await writeConfigFile(await materializeEnabledPlugins());
|
|
110
|
+
return { updated: true };
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
filePath,
|
|
115
|
+
ownsConfigFile,
|
|
116
|
+
buildManagedChildEnv,
|
|
117
|
+
refreshManagedConfigFile,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { createManagedConfigRuntime, MANAGED_CONFIG_FILE_NAME } from './managed-config-file.js';
|
|
7
|
+
|
|
8
|
+
const temporaryDirectories = [];
|
|
9
|
+
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await Promise.all(temporaryDirectories.splice(0).map((directory) => fs.rm(directory, { recursive: true, force: true })));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const createHarness = async ({ settings = {}, env = {}, memoryAvailable = true } = {}) => {
|
|
15
|
+
const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openchamber-managed-config-'));
|
|
16
|
+
temporaryDirectories.push(dataDir);
|
|
17
|
+
const agentToolRuntime = {
|
|
18
|
+
pluginDirectory: path.join(dataDir, 'agent-tool', 'openchamber-agent-tool'),
|
|
19
|
+
materializePlugin: vi.fn(async (flags) => {
|
|
20
|
+
await fs.mkdir(agentToolRuntime.pluginDirectory, { recursive: true });
|
|
21
|
+
await fs.writeFile(path.join(agentToolRuntime.pluginDirectory, 'flags.json'), JSON.stringify(flags));
|
|
22
|
+
return agentToolRuntime.pluginDirectory;
|
|
23
|
+
}),
|
|
24
|
+
createChildEnv: vi.fn(() => ({
|
|
25
|
+
OPENCHAMBER_AGENT_TOOL_URL: 'http://127.0.0.1:3901/api/openchamber/agent-tool',
|
|
26
|
+
OPENCHAMBER_AGENT_TOOL_TOKEN: 'token',
|
|
27
|
+
})),
|
|
28
|
+
};
|
|
29
|
+
const current = { settings };
|
|
30
|
+
const runtime = createManagedConfigRuntime({
|
|
31
|
+
fsPromises: fs,
|
|
32
|
+
path,
|
|
33
|
+
dataDir,
|
|
34
|
+
env,
|
|
35
|
+
agentToolRuntime,
|
|
36
|
+
readSettings: () => current.settings,
|
|
37
|
+
isAgentMemoryAvailable: () => memoryAvailable,
|
|
38
|
+
});
|
|
39
|
+
const readConfigFile = async () => JSON.parse(await fs.readFile(path.join(dataDir, MANAGED_CONFIG_FILE_NAME), 'utf8'));
|
|
40
|
+
return { dataDir, runtime, agentToolRuntime, current, readConfigFile };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('managed OpenCode config file', () => {
|
|
44
|
+
it('points the child at a config file listing the enabled plugins', async () => {
|
|
45
|
+
const { dataDir, runtime, readConfigFile } = await createHarness({
|
|
46
|
+
settings: {},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const childEnv = await runtime.buildManagedChildEnv();
|
|
50
|
+
|
|
51
|
+
expect(childEnv.OPENCODE_CONFIG).toBe(path.join(dataDir, MANAGED_CONFIG_FILE_NAME));
|
|
52
|
+
expect(childEnv.OPENCODE_CONFIG_CONTENT).toBeUndefined();
|
|
53
|
+
expect(childEnv.OPENCHAMBER_AGENT_TOOL_TOKEN).toBe('token');
|
|
54
|
+
expect(await readConfigFile()).toEqual({
|
|
55
|
+
plugins: [path.join(dataDir, 'agent-tool', 'openchamber-agent-tool')],
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('materializes every listed plugin directory before naming it', async () => {
|
|
60
|
+
const { runtime, readConfigFile } = await createHarness({ settings: {} });
|
|
61
|
+
|
|
62
|
+
await runtime.buildManagedChildEnv();
|
|
63
|
+
|
|
64
|
+
for (const directory of (await readConfigFile()).plugins) {
|
|
65
|
+
expect((await fs.stat(directory)).isDirectory()).toBe(true);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('keeps the callback token in the child env while every tool is off', async () => {
|
|
70
|
+
const { runtime, agentToolRuntime, readConfigFile } = await createHarness({
|
|
71
|
+
settings: { agentControlToolEnabled: false, agentWebToolEnabled: false, agentMemoryToolEnabled: false },
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const childEnv = await runtime.buildManagedChildEnv();
|
|
75
|
+
|
|
76
|
+
expect(agentToolRuntime.materializePlugin).not.toHaveBeenCalled();
|
|
77
|
+
expect(await readConfigFile()).toEqual({ plugins: [] });
|
|
78
|
+
// A tool switched on later reaches a process that can already call back.
|
|
79
|
+
expect(childEnv.OPENCHAMBER_AGENT_TOOL_TOKEN).toBe('token');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('rewrites the file when a tool is turned off, without a new token', async () => {
|
|
83
|
+
const { dataDir, runtime, current, agentToolRuntime, readConfigFile } = await createHarness({
|
|
84
|
+
settings: {},
|
|
85
|
+
});
|
|
86
|
+
await runtime.buildManagedChildEnv();
|
|
87
|
+
|
|
88
|
+
current.settings = { agentControlToolEnabled: false, agentWebToolEnabled: false };
|
|
89
|
+
expect(await runtime.refreshManagedConfigFile()).toEqual({ updated: true });
|
|
90
|
+
|
|
91
|
+
const { plugins } = await readConfigFile();
|
|
92
|
+
expect(plugins).toEqual([]);
|
|
93
|
+
expect(agentToolRuntime.createChildEnv).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('turns a tool back on without a restart', async () => {
|
|
97
|
+
const { runtime, current, agentToolRuntime, readConfigFile } = await createHarness({
|
|
98
|
+
settings: { agentControlToolEnabled: false, agentWebToolEnabled: false },
|
|
99
|
+
});
|
|
100
|
+
await runtime.buildManagedChildEnv();
|
|
101
|
+
|
|
102
|
+
current.settings = { agentControlToolEnabled: false, agentWebToolEnabled: true };
|
|
103
|
+
await runtime.refreshManagedConfigFile();
|
|
104
|
+
|
|
105
|
+
expect((await readConfigFile()).plugins).toHaveLength(1);
|
|
106
|
+
expect(agentToolRuntime.materializePlugin).toHaveBeenCalledWith({
|
|
107
|
+
includeControl: false,
|
|
108
|
+
includeWeb: true,
|
|
109
|
+
includeMemory: false,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('leaves a config file the user owns alone and keeps the restart requirement', async () => {
|
|
114
|
+
const { dataDir, runtime } = await createHarness({
|
|
115
|
+
settings: {},
|
|
116
|
+
env: { OPENCODE_CONFIG: '/home/user/opencode.json', OPENCODE_CONFIG_CONTENT: '{"model":"test/model"}' },
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const childEnv = await runtime.buildManagedChildEnv();
|
|
120
|
+
|
|
121
|
+
expect(childEnv.OPENCODE_CONFIG).toBeUndefined();
|
|
122
|
+
expect(JSON.parse(childEnv.OPENCODE_CONFIG_CONTENT)).toEqual({
|
|
123
|
+
model: 'test/model',
|
|
124
|
+
plugins: [path.join(dataDir, 'agent-tool', 'openchamber-agent-tool')],
|
|
125
|
+
});
|
|
126
|
+
await expect(fs.stat(path.join(dataDir, MANAGED_CONFIG_FILE_NAME))).rejects.toThrow();
|
|
127
|
+
expect(await runtime.refreshManagedConfigFile()).toEqual({ updated: false, reason: 'external-config' });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('leaves no temp file behind and never publishes a partial list', async () => {
|
|
131
|
+
const { dataDir, runtime } = await createHarness({ settings: {} });
|
|
132
|
+
|
|
133
|
+
await runtime.buildManagedChildEnv();
|
|
134
|
+
await runtime.refreshManagedConfigFile();
|
|
135
|
+
|
|
136
|
+
const entries = await fs.readdir(dataDir);
|
|
137
|
+
expect(entries.filter((entry) => entry.includes('.tmp-'))).toEqual([]);
|
|
138
|
+
expect(entries).toContain(MANAGED_CONFIG_FILE_NAME);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('ignores the memory tool while the feature is unavailable', async () => {
|
|
142
|
+
const { runtime, agentToolRuntime, readConfigFile } = await createHarness({
|
|
143
|
+
settings: { agentControlToolEnabled: false, agentWebToolEnabled: false, agentMemoryToolEnabled: true },
|
|
144
|
+
memoryAvailable: false,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await runtime.buildManagedChildEnv();
|
|
148
|
+
|
|
149
|
+
expect(agentToolRuntime.materializePlugin).not.toHaveBeenCalled();
|
|
150
|
+
expect(await readConfigFile()).toEqual({ plugins: [] });
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -2,34 +2,54 @@ import { parse as parseJsonc } from 'jsonc-parser';
|
|
|
2
2
|
|
|
3
3
|
const isJsonObject = (value) => value !== null && value !== undefined && Object.getPrototypeOf(value) === Object.prototype;
|
|
4
4
|
|
|
5
|
+
const matchesEntry = (value, pluginDirectory) => (
|
|
6
|
+
value === pluginDirectory || (Array.isArray(value) && value[0] === pluginDirectory)
|
|
7
|
+
);
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
|
-
* Append a managed OpenChamber plugin to the `
|
|
10
|
+
* Append a managed OpenChamber plugin to the `plugins` list of an
|
|
7
11
|
* `OPENCODE_CONFIG_CONTENT` value.
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* OpenCode 2 loads a configured plugin from a DIRECTORY that carries a
|
|
14
|
+
* `package.json` resolving an entrypoint; a path to a single `.js` file is
|
|
15
|
+
* rejected with "configured plugin path must be a directory". So the value
|
|
16
|
+
* merged here is an absolute directory path, not a `file://` URL.
|
|
17
|
+
*
|
|
18
|
+
* Existing entries are preserved and an earlier entry for the same directory is
|
|
19
|
+
* dropped, so a restart never registers the same plugin twice. A legacy
|
|
20
|
+
* `plugin` key is folded into `plugins` and removed: OpenCode still decodes it,
|
|
21
|
+
* but keeping two keys would mean two places to dedupe against.
|
|
22
|
+
*
|
|
23
|
+
* This is the FALLBACK path only, taken when the user's own environment owns
|
|
24
|
+
* `OPENCODE_CONFIG`. Otherwise managed plugins are published through the
|
|
25
|
+
* watched config file in `managed-config-file.js`, which is what makes a
|
|
26
|
+
* managed-tool toggle apply without a restart.
|
|
13
27
|
*
|
|
14
28
|
* @param {string | undefined} rawConfig the current `OPENCODE_CONFIG_CONTENT`, JSONC or unset
|
|
15
|
-
* @param {string}
|
|
29
|
+
* @param {string} pluginDirectory absolute path of the materialized plugin directory
|
|
16
30
|
* @param {string} purpose short noun phrase for the error message, e.g. "managed tool"
|
|
17
31
|
* @returns {string} the merged config as JSON
|
|
18
32
|
*/
|
|
19
|
-
export const appendManagedPlugin = (rawConfig,
|
|
33
|
+
export const appendManagedPlugin = (rawConfig, pluginDirectory, purpose) => {
|
|
20
34
|
const errors = [];
|
|
21
35
|
const text = (rawConfig ?? '').trim();
|
|
22
36
|
const parsed = text ? parseJsonc(text, errors, { allowTrailingComma: true }) : {};
|
|
23
37
|
if (errors.length > 0 || !isJsonObject(parsed)) {
|
|
24
38
|
throw new Error(`OPENCODE_CONFIG_CONTENT must contain a valid JSON object before OpenChamber can inject its ${purpose}`);
|
|
25
39
|
}
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
for (const key of ['plugin', 'plugins']) {
|
|
41
|
+
if (parsed[key] !== undefined && !Array.isArray(parsed[key])) {
|
|
42
|
+
throw new Error(`OPENCODE_CONFIG_CONTENT ${key} must be an array before OpenChamber can inject its ${purpose}`);
|
|
43
|
+
}
|
|
28
44
|
}
|
|
29
|
-
const configured =
|
|
30
|
-
|
|
31
|
-
...
|
|
32
|
-
|
|
45
|
+
const configured = [
|
|
46
|
+
...(Array.isArray(parsed.plugin) ? parsed.plugin : []),
|
|
47
|
+
...(Array.isArray(parsed.plugins) ? parsed.plugins : []),
|
|
48
|
+
];
|
|
49
|
+
delete parsed.plugin;
|
|
50
|
+
parsed.plugins = [
|
|
51
|
+
...configured.filter((value) => !matchesEntry(value, pluginDirectory)),
|
|
52
|
+
pluginDirectory,
|
|
33
53
|
];
|
|
34
54
|
return JSON.stringify(parsed);
|
|
35
55
|
};
|