@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
|
@@ -3,17 +3,21 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import yaml from 'yaml';
|
|
5
5
|
import { parse as parseJsonc, printParseErrorCode } from 'jsonc-parser';
|
|
6
|
+
import { readSectionEntry, readMcpEntry } from './config-v2.js';
|
|
6
7
|
|
|
7
8
|
// ============== PATH CONSTANTS ==============
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
)
|
|
10
|
+
// OpenCode 2 resolves its global config directory as `OPENCODE_CONFIG_DIR`
|
|
11
|
+
// when set, else `$XDG_CONFIG_HOME/opencode`, else `~/.config/opencode`.
|
|
12
|
+
const OPENCODE_CONFIG_DIR = process.env.OPENCODE_CONFIG_DIR?.trim()
|
|
13
|
+
? path.resolve(process.env.OPENCODE_CONFIG_DIR.trim())
|
|
14
|
+
: path.join(process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config'), 'opencode');
|
|
13
15
|
const AGENT_DIR = path.join(OPENCODE_CONFIG_DIR, 'agents');
|
|
14
16
|
const COMMAND_DIR = path.join(OPENCODE_CONFIG_DIR, 'commands');
|
|
15
17
|
const SKILL_DIR = path.join(OPENCODE_CONFIG_DIR, 'skills');
|
|
16
|
-
|
|
18
|
+
// OpenCode 2 reads only `opencode.json(c)`; the v1-era `config.json` is not
|
|
19
|
+
// discovered any more, so it is neither read nor written here.
|
|
20
|
+
const CONFIG_FILE = path.join(OPENCODE_CONFIG_DIR, 'opencode.json');
|
|
17
21
|
const PROMPT_FILE_PATTERN = /^\{file:(.+)\}$/i;
|
|
18
22
|
|
|
19
23
|
// ============== SCOPE TYPE CONSTANTS ==============
|
|
@@ -122,13 +126,21 @@ function writeMdFile(filePath, frontmatter, body) {
|
|
|
122
126
|
|
|
123
127
|
// ============== CONFIG FILE OPERATIONS ==============
|
|
124
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Project config files in the order OpenCode 2 lets them win: a file under
|
|
131
|
+
* `.opencode/` overrides the one beside it at the project root, and
|
|
132
|
+
* `opencode.json` overrides `opencode.jsonc`. When several exist, the
|
|
133
|
+
* highest-priority one is the file OpenChamber reads and writes for the
|
|
134
|
+
* project scope (OpenCode merges them all; an entry that lives only in a
|
|
135
|
+
* lower file is visible through the resolved catalog but not editable here).
|
|
136
|
+
*/
|
|
125
137
|
function getProjectConfigCandidates(workingDirectory) {
|
|
126
138
|
if (!workingDirectory) return [];
|
|
127
139
|
return [
|
|
128
|
-
path.join(workingDirectory, 'opencode.json'),
|
|
129
|
-
path.join(workingDirectory, 'opencode.jsonc'),
|
|
130
140
|
path.join(workingDirectory, '.opencode', 'opencode.json'),
|
|
131
141
|
path.join(workingDirectory, '.opencode', 'opencode.jsonc'),
|
|
142
|
+
path.join(workingDirectory, 'opencode.json'),
|
|
143
|
+
path.join(workingDirectory, 'opencode.jsonc'),
|
|
132
144
|
];
|
|
133
145
|
}
|
|
134
146
|
|
|
@@ -143,13 +155,13 @@ function getProjectConfigPath(workingDirectory) {
|
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
|
|
158
|
+
// A new project config goes beside the project's other `.opencode/` files.
|
|
146
159
|
return candidates[0];
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
function getConfigPaths(workingDirectory) {
|
|
150
163
|
return {
|
|
151
164
|
userPaths: [
|
|
152
|
-
path.join(OPENCODE_CONFIG_DIR, 'config.json'),
|
|
153
165
|
path.join(OPENCODE_CONFIG_DIR, 'opencode.json'),
|
|
154
166
|
path.join(OPENCODE_CONFIG_DIR, 'opencode.jsonc'),
|
|
155
167
|
],
|
|
@@ -357,30 +369,49 @@ function throwIfLayerError(layers, filePath) {
|
|
|
357
369
|
throw error;
|
|
358
370
|
}
|
|
359
371
|
|
|
360
|
-
|
|
372
|
+
/**
|
|
373
|
+
* Look one entry up in a config layer, accepting both OpenCode 2 section keys
|
|
374
|
+
* and the v1 keys v2 still decodes. `sectionKind` is `agents`, `commands`,
|
|
375
|
+
* `providers`, or `mcp`.
|
|
376
|
+
*/
|
|
377
|
+
function lookupSectionEntry(config, sectionKind, entryName) {
|
|
378
|
+
if (sectionKind === 'mcp') {
|
|
379
|
+
return readMcpEntry(config, entryName);
|
|
380
|
+
}
|
|
381
|
+
return readSectionEntry(config, sectionKind, entryName);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function getJsonEntrySource(layers, sectionKind, entryName) {
|
|
361
385
|
const { userConfig, projectConfig, customConfig, paths } = layers;
|
|
386
|
+
const found = (config, filePath) => {
|
|
387
|
+
const entry = lookupSectionEntry(config, sectionKind, entryName);
|
|
388
|
+
if (entry.value === undefined) return null;
|
|
389
|
+
return {
|
|
390
|
+
section: entry.value,
|
|
391
|
+
config,
|
|
392
|
+
path: filePath,
|
|
393
|
+
exists: true,
|
|
394
|
+
sectionKey: entry.key,
|
|
395
|
+
legacy: entry.legacy,
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
|
|
362
399
|
if (paths.customPath) {
|
|
363
400
|
throwIfLayerError(layers, paths.customPath);
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
return { section: customSection, config: customConfig, path: paths.customPath, exists: true };
|
|
367
|
-
}
|
|
401
|
+
const custom = found(customConfig, paths.customPath);
|
|
402
|
+
if (custom) return custom;
|
|
368
403
|
}
|
|
369
404
|
|
|
370
405
|
if (paths.projectPath && !getLayerError(layers, paths.projectPath)) {
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
return { section: projectSection, config: projectConfig, path: paths.projectPath, exists: true };
|
|
374
|
-
}
|
|
406
|
+
const project = found(projectConfig, paths.projectPath);
|
|
407
|
+
if (project) return project;
|
|
375
408
|
}
|
|
376
409
|
|
|
377
410
|
throwIfLayerError(layers, paths.userPath);
|
|
378
|
-
const
|
|
379
|
-
if (
|
|
380
|
-
return { section: userSection, config: userConfig, path: paths.userPath, exists: true };
|
|
381
|
-
}
|
|
411
|
+
const user = found(userConfig, paths.userPath);
|
|
412
|
+
if (user) return user;
|
|
382
413
|
|
|
383
|
-
return { section: null, config: null, path: null, exists: false };
|
|
414
|
+
return { section: null, config: null, path: null, exists: false, sectionKey: null, legacy: false };
|
|
384
415
|
}
|
|
385
416
|
|
|
386
417
|
function getJsonWriteTarget(layers, preferredScope) {
|
|
@@ -659,6 +690,7 @@ export {
|
|
|
659
690
|
readConfig,
|
|
660
691
|
getConfigForPath,
|
|
661
692
|
writeConfig,
|
|
693
|
+
lookupSectionEntry,
|
|
662
694
|
getJsonEntrySource,
|
|
663
695
|
getJsonWriteTarget,
|
|
664
696
|
getAncestors,
|
|
@@ -165,10 +165,12 @@ describe('updateAgent frontmatter preservation', () => {
|
|
|
165
165
|
expect(content.match(/^---\r?\n/g)).toHaveLength(1);
|
|
166
166
|
|
|
167
167
|
const parsed = parseMdFile(agentPath);
|
|
168
|
+
// `temperature` is a v1 field: reading accepts it, writing moves it under
|
|
169
|
+
// the v2 `request.body` overlay.
|
|
168
170
|
expect(parsed.frontmatter).toEqual({
|
|
169
171
|
description: 'Strategy agent',
|
|
170
172
|
model: 'openai/gpt-5',
|
|
171
|
-
temperature: 0.7,
|
|
173
|
+
request: { body: { temperature: 0.7 } },
|
|
172
174
|
});
|
|
173
175
|
expect(parsed.body).toBe('');
|
|
174
176
|
});
|
|
@@ -196,7 +198,7 @@ describe('updateAgent frontmatter preservation', () => {
|
|
|
196
198
|
expect(parsed.frontmatter).toEqual({
|
|
197
199
|
description: 'Updated strategy agent',
|
|
198
200
|
mode: 'primary',
|
|
199
|
-
temperature: 0.7,
|
|
201
|
+
request: { body: { temperature: 0.7 } },
|
|
200
202
|
});
|
|
201
203
|
expect(parsed.body).toBe('Body of strateg.');
|
|
202
204
|
});
|
|
@@ -372,10 +374,12 @@ describe('readConfigFile / writeConfig JSONC safety (issue #2923)', () => {
|
|
|
372
374
|
}),
|
|
373
375
|
]);
|
|
374
376
|
|
|
375
|
-
updateMcpConfig('openproject', {
|
|
377
|
+
updateMcpConfig('openproject', { disabled: true }, projectDir);
|
|
376
378
|
const rewritten = JSON.parse(fs.readFileSync(custom, 'utf8'));
|
|
377
379
|
expect(rewritten.plugin).toEqual(['opencode-see-image']);
|
|
378
|
-
|
|
380
|
+
// The v1 `mcp.<name>` entry is rewritten in place into `mcp.servers`.
|
|
381
|
+
expect(rewritten.mcp.openproject).toBeUndefined();
|
|
382
|
+
expect(rewritten.mcp.servers.openproject.disabled).toBe(true);
|
|
379
383
|
expect(fs.readFileSync(projectFile, 'utf8')).toBe(PARTIAL_PARSE_CONFIG);
|
|
380
384
|
expect(fs.existsSync(`${projectFile}.openchamber.backup`)).toBe(false);
|
|
381
385
|
} finally {
|
|
@@ -31,25 +31,67 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
31
31
|
getActiveTunnelController,
|
|
32
32
|
setActiveTunnelController,
|
|
33
33
|
tunnelAuthController,
|
|
34
|
+
beginGuestServiceShutdown,
|
|
35
|
+
stopAllGuestServices,
|
|
36
|
+
getGuestSurfaceRuntime,
|
|
37
|
+
getRealtimeProxyRuntime,
|
|
38
|
+
getDictationRuntime,
|
|
39
|
+
getRelayService,
|
|
40
|
+
getRelayReconcileTimer,
|
|
34
41
|
} = dependencies;
|
|
35
42
|
|
|
36
43
|
let shutdownPromise = null;
|
|
44
|
+
const serverConnections = new Set();
|
|
45
|
+
let closingHttpServer = false;
|
|
46
|
+
|
|
47
|
+
// Track TCP sockets before listen(): HTTP stops tracking them on upgrade,
|
|
48
|
+
// even when no WebSocket handler completes the handshake.
|
|
49
|
+
const trackServerConnections = (server) => {
|
|
50
|
+
const onConnection = (socket) => {
|
|
51
|
+
if (closingHttpServer) {
|
|
52
|
+
socket.destroy();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
serverConnections.add(socket);
|
|
56
|
+
socket.once('close', () => serverConnections.delete(socket));
|
|
57
|
+
};
|
|
58
|
+
server.on('connection', onConnection);
|
|
59
|
+
server.once('close', () => server.off('connection', onConnection));
|
|
60
|
+
};
|
|
37
61
|
|
|
38
62
|
const runShutdown = async (options = {}) => {
|
|
39
63
|
if (getIsShuttingDown()) return;
|
|
40
64
|
|
|
41
65
|
setIsShuttingDown(true);
|
|
66
|
+
beginGuestServiceShutdown();
|
|
42
67
|
syncToHmrState();
|
|
43
68
|
console.log('Starting graceful shutdown...');
|
|
44
69
|
const exitProcess = typeof options.exitProcess === 'boolean' ? options.exitProcess : getExitOnShutdown();
|
|
45
70
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
71
|
+
// Both embedded stop() and daemon exits use this sequence. Close admission
|
|
72
|
+
// synchronously above, then stop viewers before draining their services.
|
|
73
|
+
const cleanupOperations = [
|
|
74
|
+
() => clearInterval(getRelayReconcileTimer()),
|
|
75
|
+
() => getGuestSurfaceRuntime()?.stop(),
|
|
76
|
+
() => getRealtimeProxyRuntime()?.stop(),
|
|
77
|
+
() => getRelayService()?.stop(),
|
|
78
|
+
() => getDictationRuntime()?.stop(),
|
|
79
|
+
() => openCodeWatcherRuntime.stop(),
|
|
80
|
+
() => sessionRuntime.dispose(),
|
|
81
|
+
() => sessionAssistRuntime?.stop?.(),
|
|
82
|
+
() => sessionGoalRuntime?.stop?.(),
|
|
83
|
+
() => contextObligatoryRuntime?.stop?.(),
|
|
84
|
+
() => messageQueueRuntime?.stop?.(),
|
|
85
|
+
() => scheduledTasksRuntime?.stop?.(),
|
|
86
|
+
stopAllGuestServices,
|
|
87
|
+
];
|
|
88
|
+
for (const cleanup of cleanupOperations) {
|
|
89
|
+
try {
|
|
90
|
+
await cleanup();
|
|
91
|
+
} catch {
|
|
92
|
+
// One failed runtime must not skip the rest of host teardown.
|
|
93
|
+
}
|
|
94
|
+
}
|
|
53
95
|
|
|
54
96
|
const healthCheckInterval = getHealthCheckInterval();
|
|
55
97
|
if (healthCheckInterval) {
|
|
@@ -100,6 +142,7 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
100
142
|
|
|
101
143
|
const server = getServer();
|
|
102
144
|
if (server) {
|
|
145
|
+
closingHttpServer = true;
|
|
103
146
|
let closeTimeout = null;
|
|
104
147
|
try {
|
|
105
148
|
await Promise.race([
|
|
@@ -111,6 +154,9 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
111
154
|
// The backend has stopped. Active SSE/HTTP clients must not keep
|
|
112
155
|
// Desktop waiting for the outer shutdown deadline.
|
|
113
156
|
server.closeAllConnections?.();
|
|
157
|
+
// Includes upgraded sockets and reconnects accepted while the
|
|
158
|
+
// services above were draining. No child-process grace is cut short.
|
|
159
|
+
for (const socket of serverConnections) socket.destroy();
|
|
114
160
|
}),
|
|
115
161
|
new Promise((resolve) => {
|
|
116
162
|
closeTimeout = setTimeout(() => {
|
|
@@ -154,5 +200,6 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
154
200
|
|
|
155
201
|
return {
|
|
156
202
|
gracefulShutdown,
|
|
203
|
+
trackServerConnections,
|
|
157
204
|
};
|
|
158
205
|
};
|
|
@@ -1,37 +1,51 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import http from 'node:http';
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import { once } from 'node:events';
|
|
3
5
|
|
|
4
6
|
import { createGracefulShutdownRuntime } from './shutdown-runtime.js';
|
|
5
7
|
|
|
6
|
-
const createRuntime = (server) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
8
|
+
const createRuntime = (server, overrides = {}) => {
|
|
9
|
+
const runtime = createGracefulShutdownRuntime({
|
|
10
|
+
process: { exit: vi.fn() },
|
|
11
|
+
shutdownTimeoutMs: 1000,
|
|
12
|
+
getExitOnShutdown: () => false,
|
|
13
|
+
getIsShuttingDown: () => false,
|
|
14
|
+
setIsShuttingDown: vi.fn(),
|
|
15
|
+
syncToHmrState: vi.fn(),
|
|
16
|
+
openCodeWatcherRuntime: { stop: vi.fn() },
|
|
17
|
+
sessionRuntime: { dispose: vi.fn() },
|
|
18
|
+
scheduledTasksRuntime: { stop: vi.fn() },
|
|
19
|
+
getHealthCheckInterval: () => null,
|
|
20
|
+
clearHealthCheckInterval: vi.fn(),
|
|
21
|
+
getTerminalRuntime: () => null,
|
|
22
|
+
setTerminalRuntime: vi.fn(),
|
|
23
|
+
getMessageStreamRuntime: () => null,
|
|
24
|
+
setMessageStreamRuntime: vi.fn(),
|
|
25
|
+
shouldSkipOpenCodeStop: () => true,
|
|
26
|
+
getOpenCodePort: () => null,
|
|
27
|
+
getOpenCodeProcess: () => null,
|
|
28
|
+
setOpenCodeProcess: vi.fn(),
|
|
29
|
+
killProcessOnPort: vi.fn(),
|
|
30
|
+
waitForPortRelease: vi.fn(async () => true),
|
|
31
|
+
getServer: () => server,
|
|
32
|
+
getUiAuthController: () => null,
|
|
33
|
+
setUiAuthController: vi.fn(),
|
|
34
|
+
getActiveTunnelController: () => null,
|
|
35
|
+
setActiveTunnelController: vi.fn(),
|
|
36
|
+
tunnelAuthController: { clearActiveTunnel: vi.fn() },
|
|
37
|
+
beginGuestServiceShutdown: vi.fn(),
|
|
38
|
+
stopAllGuestServices: vi.fn(),
|
|
39
|
+
getGuestSurfaceRuntime: () => null,
|
|
40
|
+
getRealtimeProxyRuntime: () => null,
|
|
41
|
+
getDictationRuntime: () => null,
|
|
42
|
+
getRelayService: () => null,
|
|
43
|
+
getRelayReconcileTimer: () => null,
|
|
44
|
+
...overrides,
|
|
45
|
+
});
|
|
46
|
+
if (server instanceof http.Server) runtime.trackServerConnections(server);
|
|
47
|
+
return runtime;
|
|
48
|
+
};
|
|
35
49
|
|
|
36
50
|
describe('graceful shutdown runtime', () => {
|
|
37
51
|
afterEach(() => {
|
|
@@ -62,6 +76,7 @@ describe('graceful shutdown runtime', () => {
|
|
|
62
76
|
response.writeHead(200, { 'content-type': 'text/event-stream' });
|
|
63
77
|
response.write('data: fixture\n\n');
|
|
64
78
|
});
|
|
79
|
+
const runtime = createRuntime(server);
|
|
65
80
|
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
66
81
|
const request = http.get(`http://127.0.0.1:${server.address().port}`);
|
|
67
82
|
request.on('error', () => {});
|
|
@@ -70,7 +85,7 @@ describe('graceful shutdown runtime', () => {
|
|
|
70
85
|
const closed = new Promise((resolve) => response.once('close', resolve));
|
|
71
86
|
const warning = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
72
87
|
try {
|
|
73
|
-
await
|
|
88
|
+
await runtime.gracefulShutdown({ exitProcess: false });
|
|
74
89
|
expect(warning).not.toHaveBeenCalledWith('Server close timeout reached, forcing shutdown');
|
|
75
90
|
await closed;
|
|
76
91
|
} finally {
|
|
@@ -79,4 +94,183 @@ describe('graceful shutdown runtime', () => {
|
|
|
79
94
|
await new Promise((resolve) => server.close(resolve));
|
|
80
95
|
}
|
|
81
96
|
});
|
|
97
|
+
|
|
98
|
+
it.each([false, true])('closes upgraded sockets without the HTTP timeout (handshake completed: %s)', async (completeHandshake) => {
|
|
99
|
+
const server = http.createServer();
|
|
100
|
+
const runtime = createRuntime(server);
|
|
101
|
+
const sockets = new Set();
|
|
102
|
+
server.on('upgrade', (_request, socket) => {
|
|
103
|
+
sockets.add(socket);
|
|
104
|
+
if (completeHandshake) {
|
|
105
|
+
socket.write('HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n');
|
|
106
|
+
}
|
|
107
|
+
// A listener for another WS route can leave the upgrade unanswered.
|
|
108
|
+
});
|
|
109
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
110
|
+
const client = net.connect(server.address().port, '127.0.0.1');
|
|
111
|
+
client.on('error', () => {});
|
|
112
|
+
client.resume();
|
|
113
|
+
const clientClosed = once(client, 'close');
|
|
114
|
+
const upgraded = once(server, 'upgrade');
|
|
115
|
+
client.write('GET /api/global/event/ws HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n');
|
|
116
|
+
await upgraded;
|
|
117
|
+
const warning = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
118
|
+
try {
|
|
119
|
+
await runtime.gracefulShutdown({ exitProcess: false });
|
|
120
|
+
expect(warning).not.toHaveBeenCalledWith('Server close timeout reached, forcing shutdown');
|
|
121
|
+
await clientClosed;
|
|
122
|
+
const connections = await new Promise((resolve, reject) => {
|
|
123
|
+
server.getConnections((error, count) => error ? reject(error) : resolve(count));
|
|
124
|
+
});
|
|
125
|
+
expect(connections).toBe(0);
|
|
126
|
+
} finally {
|
|
127
|
+
for (const socket of sockets) socket.destroy();
|
|
128
|
+
client.destroy();
|
|
129
|
+
await new Promise((resolve) => server.close(resolve));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('drains processes before closing sockets accepted during cleanup, including after a cleanup failure', async () => {
|
|
134
|
+
const server = http.createServer();
|
|
135
|
+
const cleanupStarted = Promise.withResolvers();
|
|
136
|
+
const finishCleanup = Promise.withResolvers();
|
|
137
|
+
const sockets = new Set();
|
|
138
|
+
const clients = [];
|
|
139
|
+
server.on('upgrade', (_request, socket) => sockets.add(socket));
|
|
140
|
+
const terminalShutdown = vi.fn(async () => {
|
|
141
|
+
expect([...sockets].every((socket) => !socket.destroyed)).toBe(true);
|
|
142
|
+
});
|
|
143
|
+
const processClose = vi.fn(async () => {
|
|
144
|
+
expect(terminalShutdown).toHaveBeenCalledOnce();
|
|
145
|
+
expect([...sockets].every((socket) => !socket.destroyed)).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
const runtime = createRuntime(server, {
|
|
148
|
+
stopAllGuestServices: async () => {
|
|
149
|
+
cleanupStarted.resolve();
|
|
150
|
+
await finishCleanup.promise;
|
|
151
|
+
throw new Error('fixture cleanup failure');
|
|
152
|
+
},
|
|
153
|
+
getTerminalRuntime: () => ({ shutdown: terminalShutdown }),
|
|
154
|
+
shouldSkipOpenCodeStop: () => false,
|
|
155
|
+
getOpenCodeProcess: () => ({ close: processClose }),
|
|
156
|
+
});
|
|
157
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
158
|
+
const shutdown = runtime.gracefulShutdown({ exitProcess: false });
|
|
159
|
+
expect(runtime.gracefulShutdown({ exitProcess: false })).toBe(shutdown);
|
|
160
|
+
await cleanupStarted.promise;
|
|
161
|
+
const warning = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
162
|
+
try {
|
|
163
|
+
const closed = [];
|
|
164
|
+
for (let index = 0; index < 8; index += 1) {
|
|
165
|
+
const client = net.connect(server.address().port, '127.0.0.1');
|
|
166
|
+
clients.push(client);
|
|
167
|
+
client.resume();
|
|
168
|
+
closed.push(once(client, 'close'));
|
|
169
|
+
const upgraded = once(server, 'upgrade');
|
|
170
|
+
client.write('GET /api/global/event/ws HTTP/1.1\r\nHost: localhost\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n');
|
|
171
|
+
await upgraded;
|
|
172
|
+
}
|
|
173
|
+
expect(sockets.size).toBe(8);
|
|
174
|
+
expect(processClose).not.toHaveBeenCalled();
|
|
175
|
+
finishCleanup.resolve();
|
|
176
|
+
await shutdown;
|
|
177
|
+
expect(processClose).toHaveBeenCalledOnce();
|
|
178
|
+
expect(warning).not.toHaveBeenCalledWith('Server close timeout reached, forcing shutdown');
|
|
179
|
+
await Promise.all(closed);
|
|
180
|
+
expect(server.listening).toBe(false);
|
|
181
|
+
} finally {
|
|
182
|
+
finishCleanup.resolve();
|
|
183
|
+
for (const socket of sockets) socket.destroy();
|
|
184
|
+
for (const client of clients) client.destroy();
|
|
185
|
+
await shutdown;
|
|
186
|
+
await new Promise((resolve) => server.close(resolve));
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('stops guest services during shutdown', async () => {
|
|
191
|
+
const server = {
|
|
192
|
+
close: vi.fn((callback) => {
|
|
193
|
+
callback();
|
|
194
|
+
}),
|
|
195
|
+
};
|
|
196
|
+
const stopAllGuestServices = vi.fn(async () => {});
|
|
197
|
+
|
|
198
|
+
const runtime = createRuntime(server, { stopAllGuestServices });
|
|
199
|
+
await runtime.gracefulShutdown({ exitProcess: false });
|
|
200
|
+
|
|
201
|
+
expect(stopAllGuestServices).toHaveBeenCalledTimes(1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('continues shutdown when stopping guest services fails', async () => {
|
|
205
|
+
const server = {
|
|
206
|
+
close: vi.fn((callback) => {
|
|
207
|
+
callback();
|
|
208
|
+
}),
|
|
209
|
+
};
|
|
210
|
+
const stopAllGuestServices = vi.fn(async () => {
|
|
211
|
+
throw new Error('guest teardown failed');
|
|
212
|
+
});
|
|
213
|
+
const terminalRuntime = { shutdown: vi.fn(async () => {}) };
|
|
214
|
+
|
|
215
|
+
const runtime = createRuntime(server, {
|
|
216
|
+
stopAllGuestServices,
|
|
217
|
+
getTerminalRuntime: () => terminalRuntime,
|
|
218
|
+
});
|
|
219
|
+
await runtime.gracefulShutdown({ exitProcess: false });
|
|
220
|
+
|
|
221
|
+
expect(stopAllGuestServices).toHaveBeenCalledTimes(1);
|
|
222
|
+
expect(terminalRuntime.shutdown).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(server.close).toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('closes guest admission synchronously and cleans every runtime once across repeated shutdown calls', async () => {
|
|
227
|
+
vi.useFakeTimers();
|
|
228
|
+
const order = [];
|
|
229
|
+
const cleanup = (name) => vi.fn(() => { order.push(name); });
|
|
230
|
+
const viewers = { stop: cleanup('viewers') };
|
|
231
|
+
const proxy = { stop: cleanup('proxy') };
|
|
232
|
+
const dictation = { stop: cleanup('dictation') };
|
|
233
|
+
const relay = { stop: cleanup('relay') };
|
|
234
|
+
const gate = cleanup('gate');
|
|
235
|
+
const guests = cleanup('guests');
|
|
236
|
+
const reconcile = vi.fn();
|
|
237
|
+
const timer = setInterval(reconcile, 100);
|
|
238
|
+
const runtime = createRuntime(null, {
|
|
239
|
+
beginGuestServiceShutdown: gate,
|
|
240
|
+
stopAllGuestServices: guests,
|
|
241
|
+
getGuestSurfaceRuntime: () => viewers,
|
|
242
|
+
getRealtimeProxyRuntime: () => proxy,
|
|
243
|
+
getDictationRuntime: () => dictation,
|
|
244
|
+
getRelayService: () => relay,
|
|
245
|
+
getRelayReconcileTimer: () => timer,
|
|
246
|
+
});
|
|
247
|
+
const first = runtime.gracefulShutdown();
|
|
248
|
+
expect(gate).toHaveBeenCalledTimes(1);
|
|
249
|
+
expect(runtime.gracefulShutdown()).toBe(first);
|
|
250
|
+
await first;
|
|
251
|
+
await runtime.gracefulShutdown();
|
|
252
|
+
expect(order).toEqual(['gate', 'viewers', 'proxy', 'relay', 'dictation', 'guests']);
|
|
253
|
+
await vi.advanceTimersByTimeAsync(200);
|
|
254
|
+
expect(reconcile).not.toHaveBeenCalled();
|
|
255
|
+
expect(vi.getTimerCount()).toBe(0);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('isolates failed viewer and relay cleanup and still drains guests and exits', async () => {
|
|
259
|
+
const stop = vi.fn(() => { throw new Error('stop failed'); });
|
|
260
|
+
const stopAllGuestServices = vi.fn(async () => {});
|
|
261
|
+
const dictation = { stop: vi.fn() };
|
|
262
|
+
const process = { exit: vi.fn() };
|
|
263
|
+
const runtime = createRuntime(null, {
|
|
264
|
+
process,
|
|
265
|
+
getGuestSurfaceRuntime: () => ({ stop }),
|
|
266
|
+
getRelayService: () => ({ stop }),
|
|
267
|
+
getDictationRuntime: () => dictation,
|
|
268
|
+
stopAllGuestServices,
|
|
269
|
+
});
|
|
270
|
+
await runtime.gracefulShutdown({ exitProcess: true });
|
|
271
|
+
expect(stop).toHaveBeenCalledTimes(2);
|
|
272
|
+
expect(dictation.stop).toHaveBeenCalledTimes(1);
|
|
273
|
+
expect(stopAllGuestServices).toHaveBeenCalledTimes(1);
|
|
274
|
+
expect(process.exit).toHaveBeenCalledWith(0);
|
|
275
|
+
});
|
|
82
276
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { OpenCode } from '@opencode/client';
|
|
2
|
+
import { buildAppliedResponse } from './config-mutation-response.js';
|
|
3
3
|
import { OPENCODE_CONFIG_DIR } from './shared.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -131,16 +131,18 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
try {
|
|
134
|
-
const client =
|
|
134
|
+
const client = OpenCode.make({
|
|
135
135
|
baseUrl: buildOpenCodeUrl('/', '').replace(/\/$/, ''),
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
headers: {
|
|
137
|
+
...getOpenCodeAuthHeaders(),
|
|
138
|
+
// v2 scopes a request with a header, not a `directory` option, and
|
|
139
|
+
// rejects non-ASCII header values, so the path is percent-encoded.
|
|
140
|
+
...(workingDirectory ? { 'x-opencode-directory': encodeURIComponent(workingDirectory) } : {}),
|
|
141
|
+
},
|
|
142
|
+
fetch: (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(8_000) }),
|
|
139
143
|
});
|
|
140
144
|
|
|
141
|
-
const response = await client.
|
|
142
|
-
workingDirectory ? { directory: workingDirectory } : undefined,
|
|
143
|
-
);
|
|
145
|
+
const response = await client.skill.list();
|
|
144
146
|
const payload = response?.data;
|
|
145
147
|
if (!Array.isArray(payload)) {
|
|
146
148
|
return [];
|
|
@@ -510,14 +512,14 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
510
512
|
|
|
511
513
|
const installed = result.installed || [];
|
|
512
514
|
const skipped = result.skipped || [];
|
|
513
|
-
const
|
|
515
|
+
const installedAny = installed.length > 0;
|
|
514
516
|
|
|
515
517
|
res.json({
|
|
516
518
|
ok: true,
|
|
517
519
|
installed,
|
|
518
520
|
skipped,
|
|
519
|
-
...(
|
|
520
|
-
?
|
|
521
|
+
...(installedAny
|
|
522
|
+
? buildAppliedResponse('Skills installed successfully.')
|
|
521
523
|
: {
|
|
522
524
|
requiresReload: false,
|
|
523
525
|
message: 'No skills were installed',
|
|
@@ -602,8 +604,8 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
602
604
|
console.log('[Server] Scope:', scope, 'Working directory:', directory);
|
|
603
605
|
|
|
604
606
|
createSkill(skillName, { ...config, source: skillSource }, directory, scope);
|
|
605
|
-
res.json(
|
|
606
|
-
`Skill ${skillName} created successfully
|
|
607
|
+
res.json(buildAppliedResponse(
|
|
608
|
+
`Skill ${skillName} created successfully.`,
|
|
607
609
|
));
|
|
608
610
|
} catch (error) {
|
|
609
611
|
console.error('Failed to create skill:', error);
|
|
@@ -625,14 +627,11 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
625
627
|
console.log(`[Server] Renaming skill: ${skillName} -> ${newName}`);
|
|
626
628
|
console.log('[Server] Working directory:', directory);
|
|
627
629
|
renameSkill(skillName, newName, directory);
|
|
628
|
-
|
|
629
|
-
|
|
630
|
+
// OpenCode 2 watches the skills directories: the renamed folder is
|
|
631
|
+
// picked up like any other write, no restart and no client reload.
|
|
630
632
|
return res.json({
|
|
631
|
-
|
|
633
|
+
...buildAppliedResponse(`Skill renamed to ${newName} successfully.`),
|
|
632
634
|
name: newName,
|
|
633
|
-
requiresReload: true,
|
|
634
|
-
message: `Skill renamed to ${newName} successfully. Reloading interface…`,
|
|
635
|
-
reloadDelayMs: clientReloadDelayMs,
|
|
636
635
|
});
|
|
637
636
|
}
|
|
638
637
|
|
|
@@ -640,8 +639,8 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
640
639
|
console.log('[Server] Working directory:', directory);
|
|
641
640
|
|
|
642
641
|
updateSkill(skillName, updates, directory, updates?.targetPath);
|
|
643
|
-
res.json(
|
|
644
|
-
`Skill ${skillName} updated successfully
|
|
642
|
+
res.json(buildAppliedResponse(
|
|
643
|
+
`Skill ${skillName} updated successfully.`,
|
|
645
644
|
));
|
|
646
645
|
} catch (error) {
|
|
647
646
|
console.error('[Server] Failed to update skill:', error);
|
|
@@ -727,8 +726,8 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
727
726
|
}
|
|
728
727
|
|
|
729
728
|
deleteSkill(skillName, directory);
|
|
730
|
-
res.json(
|
|
731
|
-
`Skill ${skillName} deleted successfully
|
|
729
|
+
res.json(buildAppliedResponse(
|
|
730
|
+
`Skill ${skillName} deleted successfully.`,
|
|
732
731
|
));
|
|
733
732
|
} catch (error) {
|
|
734
733
|
console.error('Failed to delete skill:', error);
|