@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.10
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 +13 -7
- package/bin/cli-opencode.test.js +208 -0
- package/bin/cli.js +62 -9
- package/bin/lib/cli-startup.js +105 -2
- package/bin/lib/cli-startup.test.js +179 -0
- package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-CzTtVMnI.js} +1 -1
- package/dist/assets/AssistantView-Cg1qQvVe.js +3 -0
- package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-hOi72Wdx.js} +1 -1
- package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-DCqrQkkz.js} +1 -1
- package/dist/assets/Icon-B8fSTLdP.js +1 -0
- package/dist/assets/MarkdownRenderer-Cw74oVxV.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-YM7iRmzK.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-BDy_oGJQ.js +3 -0
- package/dist/assets/MobileDetailNavigation-iX_hb_Xt.js +1 -0
- package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-CAMUupwR.js} +2 -2
- package/dist/assets/MobileSurface-CHbdPcLH.js +2 -0
- package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-Ci1ODFFX.js} +1 -1
- package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-O4cGdz5p.js} +1 -1
- package/dist/assets/OverlayScrollbar-CRfGtcmG.js +2 -0
- package/dist/assets/SettingsView-Dg8BtTV1.js +25 -0
- package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-B8d8qIQ7.js} +1 -1
- package/dist/assets/TerminalView-CMiw3s_c.js +8 -0
- package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-ByaQYRTp.js} +2 -2
- package/dist/assets/appThemeRegistry-BGGHio8T.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-CI7Q-Zw_.js → button-DIIrsMjW.js} +1 -1
- package/dist/assets/{collapsible-D5Phaykh.js → collapsible-CnvUeYst.js} +1 -1
- package/dist/assets/{context-menu-CubSDW2g.js → context-menu-BGBUpZEQ.js} +1 -1
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-JGFqhlaM.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-CHEwrI93.js} +1 -1
- package/dist/assets/{diagram-DdLO8HZ9.js → diagram-rvn7_qBM.js} +1 -1
- package/dist/assets/{dialog-B6pnIYmp.js → dialog-CQ63sc9E.js} +1 -1
- package/dist/assets/es-Dsmc0A_3.js +15 -0
- package/dist/assets/fileMentionTouchSelection-BzzcWJCi.js +3 -0
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-DN6gbLhM.js} +1 -1
- package/dist/assets/fr-C_ZlZDI2.js +13 -0
- package/dist/assets/gitApi-BF8vuoZc.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-BMztRu0c.js} +1 -1
- package/dist/assets/{input-D-__fwRi.js → input-BVu-iykl.js} +1 -1
- package/dist/assets/insertionBoundaries-EcAfGT2D.js +1 -0
- package/dist/assets/ja-BqqTaWTi.js +15 -0
- package/dist/assets/ko-DP2XPM37.js +15 -0
- package/dist/assets/main-B7t9lavz.js +2 -0
- package/dist/assets/main-D2BzpnUt.css +1 -0
- package/dist/assets/main-DWP-prNB.js +169 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-D9-B1wdY.js} +1 -1
- package/dist/assets/markdownTheme-DM5h1i18.js +1 -0
- package/dist/assets/mcpQueries-CfXrAkuU.js +1 -0
- package/dist/assets/miniChat-DgOrEQbN.js +2 -0
- package/dist/assets/mobile-Co_ZOHkZ.js +2 -0
- package/dist/assets/multirun-DelyUO6k.js +4 -0
- package/dist/assets/persistence-D0y7226-.js +3 -0
- package/dist/assets/pl-BCf5uLlK.js +15 -0
- package/dist/assets/platform-CurqO28D.js +6 -0
- package/dist/assets/{projectMeta-S1hJ7oBp.js → projectMeta-CPElzJ0n.js} +2 -2
- package/dist/assets/pt-BR-W09Q2pvn.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-pOE93w86.js +2 -0
- package/dist/assets/renderMobileApp-F7ZSLcw8.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-DSS0KxG3.js} +1 -1
- package/dist/assets/runtimeEndpointReset-x2Wgx57R.js +149 -0
- package/dist/assets/sessionChangeSummary-BUGSYcHp.js +2 -0
- package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-Bss8M20i.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-CE7W7lJ9.js} +1 -1
- package/dist/assets/src-lS1keDVV.css +2 -0
- package/dist/assets/store-jaP79mqI.js +15 -0
- package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-mDR2jXTo.js} +1 -1
- package/dist/assets/textarea-XD2oX_Ee.js +1 -0
- package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-DYeQ_Hvy.js} +1 -1
- package/dist/assets/uk-BgFzz8m2.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-CGbJbiJq.js} +1 -1
- package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-BAWHZ-t0.js} +64 -45
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-Bz4tRflF.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-hN_nLb7T.js +1 -0
- package/dist/assets/useEffectiveDirectory-Do9DQ453.js +1 -0
- package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-Bjx8zkGM.js} +1 -1
- package/dist/assets/useMobileNavigationStore-BsY_gi_K.js +414 -0
- package/dist/assets/useSessionAutoCleanup-C4g5KmDf.js +2 -0
- package/dist/assets/useUIStore-syVmbuDA.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-B5QXxBok.js} +2 -2
- package/dist/assets/vendor-opencode-client-C12gfjag.js +12 -0
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-Dq5ocTF8.js} +1 -1
- package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
- package/dist/assets/zh-CN-BhqSLgUY.js +15 -0
- package/dist/assets/zh-TW-BsaBgY-p.js +15 -0
- package/dist/index.html +12 -12
- package/dist/mini-chat.html +12 -12
- package/dist/mobile.html +12 -12
- package/package.json +3 -2
- package/server/index.js +423 -20
- package/server/lib/assistants/DOCUMENTATION.md +26 -21
- package/server/lib/assistants/assign.js +2 -1
- package/server/lib/assistants/contact-store.js +37 -19
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +454 -0
- package/server/lib/assistants/routes.js +11 -5
- package/server/lib/assistants/service.js +850 -294
- package/server/lib/assistants/service.test.js +588 -274
- package/server/lib/browser-provider/DOCUMENTATION.md +141 -0
- package/server/lib/browser-provider/contract.js +312 -0
- package/server/lib/browser-provider/host.js +421 -0
- package/server/lib/browser-provider/host.test.js +304 -0
- package/server/lib/browser-provider/routes.js +122 -0
- package/server/lib/browser-provider/routes.test.js +120 -0
- package/server/lib/browser-provider/selection-store.js +43 -0
- package/server/lib/browser-provider/selection-store.test.js +35 -0
- package/server/lib/conversations/DOCUMENTATION.md +5 -5
- package/server/lib/conversations/service.js +87 -57
- package/server/lib/conversations/service.test.js +132 -100
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +51 -5
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +95 -84
- package/server/lib/llm/catalog.js +111 -45
- package/server/lib/llm/catalog.test.js +121 -59
- package/server/lib/llm/completions.js +41 -12
- package/server/lib/llm/completions.test.js +45 -4
- package/server/lib/llm/generate.js +516 -453
- package/server/lib/llm/generate.test.js +705 -484
- package/server/lib/llm/routes.js +8 -4
- package/server/lib/message-queue/DOCUMENTATION.md +5 -5
- package/server/lib/message-queue/opencode-adapter.js +191 -104
- package/server/lib/message-queue/opencode-adapter.test.js +328 -58
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/message-queue/runtime.test.js +6 -6
- package/server/lib/message-queue/worker.js +7 -1
- package/server/lib/notifications/DOCUMENTATION.md +3 -1
- package/server/lib/notifications/runtime.js +133 -31
- package/server/lib/notifications/runtime.test.js +103 -0
- package/server/lib/notifications/template-runtime.js +130 -38
- package/server/lib/notifications/template-runtime.test.js +96 -0
- package/server/lib/opencode/DOCUMENTATION.md +111 -16
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +117 -103
- package/server/lib/opencode/config-entity-routes.test.js +148 -10
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.d.ts +8 -0
- package/server/lib/opencode/ensure-cli.js +238 -0
- package/server/lib/opencode/ensure-cli.test.js +119 -0
- package/server/lib/opencode/env-runtime.js +208 -111
- package/server/lib/opencode/env-runtime.test.js +211 -14
- package/server/lib/opencode/feature-routes-runtime.js +90 -5
- package/server/lib/opencode/feature-routes-runtime.test.js +25 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +539 -95
- package/server/lib/opencode/lifecycle.test.js +698 -6
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/network-runtime.js +28 -16
- package/server/lib/opencode/network-runtime.test.js +68 -4
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +139 -0
- package/server/lib/opencode/opencode2-pin.test.js +88 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-compatibility.js +15 -0
- package/server/lib/opencode/plugin-compatibility.test.js +21 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/plugins.js +4 -3
- package/server/lib/opencode/plugins.test.js +12 -0
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +373 -28
- package/server/lib/opencode/proxy.test.js +353 -0
- package/server/lib/opencode/routes.js +333 -76
- package/server/lib/opencode/routes.test.js +315 -50
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +35 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +26 -40
- package/server/lib/opencode/skill-routes.test.js +71 -7
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/upgrade-screen.d.ts +30 -0
- package/server/lib/opencode/upgrade-screen.js +299 -0
- package/server/lib/opencode/upgrade-screen.test.js +196 -0
- package/server/lib/opencode/v1-migration-gate.js +157 -0
- package/server/lib/opencode/v1-migration-gate.test.js +111 -0
- package/server/lib/opencode/v2-client.js +60 -0
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/opencode/watcher.js +2 -1
- package/server/lib/opencode/watcher.test.js +1 -1
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
- package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
- package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
- package/server/lib/scheduled-tasks/routes.js +27 -0
- package/server/lib/scheduled-tasks/routes.test.js +94 -0
- package/server/lib/scheduled-tasks/runtime.js +381 -310
- package/server/lib/scheduled-tasks/runtime.test.js +600 -309
- package/server/lib/session-goal/DOCUMENTATION.md +173 -22
- package/server/lib/session-goal/capability.js +57 -0
- package/server/lib/session-goal/capability.test.js +33 -0
- package/server/lib/session-goal/routes.js +240 -2
- package/server/lib/session-goal/routes.test.js +211 -0
- package/server/lib/session-goal/runtime.js +1617 -113
- package/server/lib/session-goal/runtime.test.js +1500 -70
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +171 -41
- package/server/lib/session-index/sync-runtime.test.js +164 -9
- package/server/lib/session-metadata/DOCUMENTATION.md +96 -0
- package/server/lib/session-metadata/opencode-session-record.d.ts +57 -0
- package/server/lib/session-metadata/opencode-session-record.js +316 -0
- package/server/lib/session-metadata/opencode-session-record.test.js +242 -0
- package/server/lib/session-metadata/routes.js +197 -0
- package/server/lib/session-metadata/routes.test.js +156 -0
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +38 -0
- package/server/lib/session-metadata/session-metadata-store.js +422 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +279 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +69 -44
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
- package/server/lib/session-turn-pages/reconcile.service.js +1 -1
- package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
- package/server/lib/session-turn-pages/routes.js +123 -75
- package/server/lib/session-turn-pages/routes.test.js +566 -0
- package/server/lib/session-turn-pages/service.js +6 -2
- package/server/lib/session-turn-pages/service.test.js +34 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -146
- package/server/lib/small-model/catalog.js +84 -54
- package/server/lib/small-model/catalog.test.js +111 -9
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +125 -241
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +63 -12
- package/dist/assets/AssistantView-BUveAgc4.js +0 -3
- package/dist/assets/Icon-BgdfPhNs.js +0 -1
- package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
- package/dist/assets/MarkstreamRendererImpl-D2dsW5Lg.js +0 -3
- package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
- package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
- package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
- package/dist/assets/SettingsView-PkXIavr7.js +0 -25
- package/dist/assets/TerminalView-CDdzF4yi.js +0 -8
- package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
- package/dist/assets/es-DA3MqOo0.js +0 -15
- package/dist/assets/fileMentionTouchSelection-DNeiqns-.js +0 -4
- package/dist/assets/fr-Dbz0CdIA.js +0 -13
- package/dist/assets/gitApi-DXyC4MiV.js +0 -7
- package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
- package/dist/assets/ja-DumanVQA.js +0 -15
- package/dist/assets/ko-C0IkiUjQ.js +0 -15
- package/dist/assets/main-BPS5ylib.js +0 -2
- package/dist/assets/main-Cpd9VWYs.js +0 -169
- package/dist/assets/main-qSeeJRW1.css +0 -1
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/markdownTheme-DOfGg-A7.js +0 -1
- package/dist/assets/miniChat-B8_KLWiY.js +0 -2
- package/dist/assets/mobile-CtktKrXn.js +0 -2
- package/dist/assets/multirun-CYfP9Cfm.js +0 -4
- package/dist/assets/persistence-Ce3AnpEr.js +0 -3
- package/dist/assets/pl-B9No6MQp.js +0 -15
- package/dist/assets/platform-0w_Q9_ji.js +0 -2
- package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
- package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-ChOxt-J7.js +0 -147
- package/dist/assets/sessionChangeSummary-NnxcqCTI.js +0 -2
- package/dist/assets/src-rS3zCcsC.css +0 -2
- package/dist/assets/store-5B_LDnLM.js +0 -15
- package/dist/assets/textarea-bHYxZtfH.js +0 -1
- package/dist/assets/uk-C_1KkqyP.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
- package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
- package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
- package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
- package/dist/assets/zh-CN-uWILgBdt.js +0 -15
- package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -339
- package/server/lib/small-model/opencode-session.test.js +0 -177
|
@@ -10,6 +10,7 @@ const originalPath = process.env.PATH;
|
|
|
10
10
|
const originalLocalAppData = process.env.LOCALAPPDATA;
|
|
11
11
|
const originalSystemRoot = process.env.SystemRoot;
|
|
12
12
|
const originalBundledOpencodeCliDir = process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
13
|
+
const originalOpenChamberDataDir = process.env.OPENCHAMBER_DATA_DIR;
|
|
13
14
|
const originalResourcesPath = process.resourcesPath;
|
|
14
15
|
const originalWslBinary = process.env.WSL_BINARY;
|
|
15
16
|
const originalOpenChamberWslBinary = process.env.OPENCHAMBER_WSL_BINARY;
|
|
@@ -23,6 +24,16 @@ const createTempDir = (prefix) => {
|
|
|
23
24
|
return dir;
|
|
24
25
|
};
|
|
25
26
|
|
|
27
|
+
const writeVersionBinary = (filePath, version) => {
|
|
28
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
29
|
+
if (process.platform === 'win32') {
|
|
30
|
+
fs.writeFileSync(filePath, `@echo off\r\necho ${version}\r\n`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
fs.writeFileSync(filePath, `#!${process.execPath}\nconsole.log(${JSON.stringify(version)});\n`);
|
|
34
|
+
fs.chmodSync(filePath, 0o755);
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
const setPlatform = (platform) => {
|
|
27
38
|
Object.defineProperty(process, 'platform', {
|
|
28
39
|
value: platform,
|
|
@@ -74,6 +85,12 @@ afterEach(() => {
|
|
|
74
85
|
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
75
86
|
}
|
|
76
87
|
|
|
88
|
+
if (typeof originalOpenChamberDataDir === 'string') {
|
|
89
|
+
process.env.OPENCHAMBER_DATA_DIR = originalOpenChamberDataDir;
|
|
90
|
+
} else {
|
|
91
|
+
delete process.env.OPENCHAMBER_DATA_DIR;
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
Object.defineProperty(process, 'resourcesPath', {
|
|
78
95
|
configurable: true,
|
|
79
96
|
value: originalResourcesPath,
|
|
@@ -112,6 +129,7 @@ const createRuntime = (settings, options = {}) => {
|
|
|
112
129
|
readSettingsFromDiskMigrated: async () => settings,
|
|
113
130
|
spawnSync: options.spawnSync,
|
|
114
131
|
homedir: options.homedir,
|
|
132
|
+
ensurePinnedOpenCode2Cli: options.ensurePinnedOpenCode2Cli,
|
|
115
133
|
});
|
|
116
134
|
|
|
117
135
|
return { runtime, state };
|
|
@@ -119,11 +137,11 @@ const createRuntime = (settings, options = {}) => {
|
|
|
119
137
|
|
|
120
138
|
describe('OpenCode env runtime', () => {
|
|
121
139
|
it('throws a specific error for a missing configured OpenCode binary in strict mode', async () => {
|
|
122
|
-
const { runtime } = createRuntime({ opencodeBinary: '/missing/
|
|
140
|
+
const { runtime } = createRuntime({ opencodeBinary: '/missing/opencode2' });
|
|
123
141
|
|
|
124
142
|
await expect(runtime.applyOpencodeBinaryFromSettings({ strict: true })).rejects.toMatchObject({
|
|
125
143
|
code: 'OPENCODE_BINARY_INVALID',
|
|
126
|
-
message: expect.stringContaining('Configured OpenCode binary not found: /missing/
|
|
144
|
+
message: expect.stringContaining('Configured OpenCode binary not found: /missing/opencode2'),
|
|
127
145
|
});
|
|
128
146
|
});
|
|
129
147
|
|
|
@@ -139,7 +157,7 @@ describe('OpenCode env runtime', () => {
|
|
|
139
157
|
|
|
140
158
|
it('applies a valid configured executable OpenCode binary', async () => {
|
|
141
159
|
const dir = createTempDir('openchamber-opencode-bin-');
|
|
142
|
-
const binary = path.join(dir, '
|
|
160
|
+
const binary = path.join(dir, 'opencode2');
|
|
143
161
|
fs.writeFileSync(binary, '#!/bin/sh\nexit 0\n');
|
|
144
162
|
fs.chmodSync(binary, 0o755);
|
|
145
163
|
const { runtime, state } = createRuntime({ opencodeBinary: binary });
|
|
@@ -150,16 +168,71 @@ describe('OpenCode env runtime', () => {
|
|
|
150
168
|
expect(state.resolvedOpencodeBinarySource).toBe('settings');
|
|
151
169
|
});
|
|
152
170
|
|
|
171
|
+
it('skips a configured 1.x opencode version instead of aborting startup', async () => {
|
|
172
|
+
const dir = createTempDir('openchamber-opencode-1x-');
|
|
173
|
+
const binary = path.join(dir, 'opencode');
|
|
174
|
+
writeVersionBinary(binary, '1.18.4');
|
|
175
|
+
const { runtime } = createRuntime({ opencodeBinary: binary });
|
|
176
|
+
|
|
177
|
+
await expect(runtime.applyOpencodeBinaryFromSettings({ strict: true })).resolves.toBeNull();
|
|
178
|
+
expect(process.env.OPENCODE_BINARY).not.toBe(binary);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('skips an explicit OPENCODE_BINARY that reports a 1.x version', () => {
|
|
182
|
+
const dir = createTempDir('openchamber-env-opencode-1x-');
|
|
183
|
+
const binary = path.join(dir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
184
|
+
writeVersionBinary(binary, '1.18.4');
|
|
185
|
+
process.env.OPENCODE_BINARY = binary;
|
|
186
|
+
const { runtime } = createRuntime({});
|
|
187
|
+
|
|
188
|
+
expect(runtime.resolveOpencodeCliPath()).not.toBe(binary);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('discovers opencode from a home-directory install location', () => {
|
|
192
|
+
const home = createTempDir('openchamber-home-opencode-');
|
|
193
|
+
const binary = path.join(home, '.bun', 'bin', 'opencode');
|
|
194
|
+
writeVersionBinary(binary, '2.0.12');
|
|
195
|
+
process.env.PATH = createTempDir('openchamber-empty-path-home-');
|
|
196
|
+
delete process.env.OPENCODE_BINARY;
|
|
197
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
198
|
+
const { runtime, state } = createRuntime({}, {
|
|
199
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
200
|
+
homedir: () => home,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(runtime.resolveOpencodeCliPath()).toBe(binary);
|
|
204
|
+
expect(state.resolvedOpencodeBinarySource).toBe('fallback');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('does not treat a PATH 1.x opencode binary as a resolved CLI', () => {
|
|
208
|
+
const pathDir = createTempDir('openchamber-path-opencode-1x-');
|
|
209
|
+
const pathBinary = path.join(pathDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
210
|
+
fs.writeFileSync(pathBinary, '#!/bin/sh\nexit 0\n');
|
|
211
|
+
if (process.platform !== 'win32') {
|
|
212
|
+
fs.chmodSync(pathBinary, 0o755);
|
|
213
|
+
}
|
|
214
|
+
process.env.PATH = pathDir;
|
|
215
|
+
delete process.env.OPENCODE_BINARY;
|
|
216
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
217
|
+
const emptyHome = createTempDir('openchamber-empty-home-1x-');
|
|
218
|
+
const { runtime, state } = createRuntime({}, {
|
|
219
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
220
|
+
homedir: () => emptyHome,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
expect(runtime.resolveOpencodeCliPath()).toBeNull();
|
|
224
|
+
expect(state.resolvedOpencodeBinarySource).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
|
|
153
227
|
it('prefers a user-installed OpenCode from PATH over the bundled CLI', () => {
|
|
154
228
|
const bundledDir = createTempDir('openchamber-bundled-opencode-');
|
|
155
229
|
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
156
230
|
const pathDir = createTempDir('openchamber-path-opencode-');
|
|
157
231
|
const pathBinary = path.join(pathDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
158
232
|
fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
|
|
159
|
-
|
|
233
|
+
writeVersionBinary(pathBinary, '2.0.12');
|
|
160
234
|
if (process.platform !== 'win32') {
|
|
161
235
|
fs.chmodSync(bundledBinary, 0o755);
|
|
162
|
-
fs.chmodSync(pathBinary, 0o755);
|
|
163
236
|
}
|
|
164
237
|
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
165
238
|
process.env.PATH = pathDir;
|
|
@@ -174,12 +247,11 @@ describe('OpenCode env runtime', () => {
|
|
|
174
247
|
const bundledDir = createTempDir('openchamber-bundled-opencode-');
|
|
175
248
|
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
176
249
|
const explicitDir = createTempDir('openchamber-explicit-opencode-');
|
|
177
|
-
const explicitBinary = path.join(explicitDir, process.platform === 'win32' ? '
|
|
250
|
+
const explicitBinary = path.join(explicitDir, process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
178
251
|
fs.writeFileSync(bundledBinary, '#!/bin/sh\nexit 0\n');
|
|
179
|
-
|
|
252
|
+
writeVersionBinary(explicitBinary, '2.0.12');
|
|
180
253
|
if (process.platform !== 'win32') {
|
|
181
254
|
fs.chmodSync(bundledBinary, 0o755);
|
|
182
|
-
fs.chmodSync(explicitBinary, 0o755);
|
|
183
255
|
}
|
|
184
256
|
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
185
257
|
process.env.OPENCODE_BINARY = explicitBinary;
|
|
@@ -189,7 +261,7 @@ describe('OpenCode env runtime', () => {
|
|
|
189
261
|
expect(state.resolvedOpencodeBinarySource).toBe('env');
|
|
190
262
|
});
|
|
191
263
|
|
|
192
|
-
it('
|
|
264
|
+
it('does not use a bundled OpenCode CLI from Electron resourcesPath', () => {
|
|
193
265
|
const resourcesPath = createTempDir('openchamber-resources-');
|
|
194
266
|
const bundledDir = path.join(resourcesPath, 'opencode-cli');
|
|
195
267
|
const bundledBinary = path.join(bundledDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
@@ -203,18 +275,142 @@ describe('OpenCode env runtime', () => {
|
|
|
203
275
|
value: resourcesPath,
|
|
204
276
|
});
|
|
205
277
|
process.env.PATH = createTempDir('openchamber-empty-path-');
|
|
206
|
-
|
|
278
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
207
279
|
delete process.env.OPENCODE_BINARY;
|
|
208
|
-
// The bundled CLI is the LAST resort now — hide the machine's own installs
|
|
209
|
-
// from the home-directory fallbacks and shell discovery.
|
|
210
280
|
const emptyHome = createTempDir('openchamber-empty-home-');
|
|
211
281
|
const { runtime, state } = createRuntime({}, {
|
|
212
282
|
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
213
283
|
homedir: () => emptyHome,
|
|
214
284
|
});
|
|
215
285
|
|
|
216
|
-
expect(runtime.resolveOpencodeCliPath()).
|
|
286
|
+
expect(runtime.resolveOpencodeCliPath()).toBeNull();
|
|
287
|
+
expect(state.resolvedOpencodeBinarySource).toBeNull();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('discovers a previously installed pin from the OpenChamber data dir', () => {
|
|
291
|
+
const dataDir = createTempDir('openchamber-installed-cli-');
|
|
292
|
+
const installedDir = path.join(dataDir, 'opencode-cli', '2.0.12');
|
|
293
|
+
const installedBinary = path.join(installedDir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
294
|
+
fs.mkdirSync(installedDir, { recursive: true });
|
|
295
|
+
fs.writeFileSync(installedBinary, '#!/bin/sh\nexit 0\n');
|
|
296
|
+
if (process.platform !== 'win32') {
|
|
297
|
+
fs.chmodSync(installedBinary, 0o755);
|
|
298
|
+
}
|
|
299
|
+
process.env.OPENCHAMBER_DATA_DIR = dataDir;
|
|
300
|
+
process.env.PATH = createTempDir('openchamber-empty-path-installed-');
|
|
301
|
+
delete process.env.OPENCODE_BINARY;
|
|
302
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
303
|
+
const emptyHome = createTempDir('openchamber-empty-home-installed-');
|
|
304
|
+
const { runtime, state } = createRuntime({}, {
|
|
305
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
306
|
+
homedir: () => emptyHome,
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
expect(runtime.resolveOpencodeCliPath()).toBe(installedBinary);
|
|
310
|
+
expect(state.resolvedOpencodeBinarySource).toBe('installed');
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('installs the pinned CLI when discovery finds nothing', async () => {
|
|
314
|
+
const installed = path.join(createTempDir('openchamber-ensure-install-'), process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
315
|
+
fs.writeFileSync(installed, '#!/bin/sh\nexit 0\n');
|
|
316
|
+
if (process.platform !== 'win32') {
|
|
317
|
+
fs.chmodSync(installed, 0o755);
|
|
318
|
+
}
|
|
319
|
+
process.env.PATH = createTempDir('openchamber-empty-path-ensure-');
|
|
320
|
+
delete process.env.OPENCODE_BINARY;
|
|
321
|
+
delete process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR;
|
|
322
|
+
const emptyHome = createTempDir('openchamber-empty-home-ensure-');
|
|
323
|
+
const { runtime, state } = createRuntime({}, {
|
|
324
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
325
|
+
homedir: () => emptyHome,
|
|
326
|
+
ensurePinnedOpenCode2Cli: async () => ({
|
|
327
|
+
path: installed,
|
|
328
|
+
version: '2.0.12',
|
|
329
|
+
source: 'installed',
|
|
330
|
+
installed: true,
|
|
331
|
+
}),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(installed);
|
|
335
|
+
expect(state.resolvedOpencodeBinary).toBe(installed);
|
|
336
|
+
expect(state.resolvedOpencodeBinarySource).toBe('installed');
|
|
337
|
+
expect(process.env.OPENCODE_BINARY).toBe(installed);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('falls back to a bundled 2.x CLI only after pin install fails', async () => {
|
|
341
|
+
const bundledDir = createTempDir('openchamber-bundled-fallback-');
|
|
342
|
+
const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
|
|
343
|
+
const bundledBinary = path.join(bundledDir, bundledName);
|
|
344
|
+
writeVersionBinary(bundledBinary, '2.0.12');
|
|
345
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
346
|
+
process.env.PATH = createTempDir('openchamber-empty-path-bundled-fallback-');
|
|
347
|
+
process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-fallback-');
|
|
348
|
+
delete process.env.OPENCODE_BINARY;
|
|
349
|
+
const emptyHome = createTempDir('openchamber-empty-home-bundled-fallback-');
|
|
350
|
+
const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
|
|
351
|
+
const { runtime, state } = createRuntime({}, {
|
|
352
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
353
|
+
homedir: () => emptyHome,
|
|
354
|
+
ensurePinnedOpenCode2Cli: async () => {
|
|
355
|
+
throw missingError;
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(bundledBinary);
|
|
360
|
+
expect(state.resolvedOpencodeBinary).toBe(bundledBinary);
|
|
361
|
+
expect(state.resolvedOpencodeBinarySource).toBe('bundled');
|
|
362
|
+
expect(process.env.OPENCODE_BINARY).toBe(bundledBinary);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('falls back to bundled opencode name when pin install fails', async () => {
|
|
366
|
+
const bundledDir = createTempDir('openchamber-bundled-opencode-name-');
|
|
367
|
+
const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
|
|
368
|
+
const bundledBinary = path.join(bundledDir, bundledName);
|
|
369
|
+
writeVersionBinary(bundledBinary, '2.0.12');
|
|
370
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
371
|
+
process.env.PATH = createTempDir('openchamber-empty-path-bundled-opencode-');
|
|
372
|
+
process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-opencode-');
|
|
373
|
+
delete process.env.OPENCODE_BINARY;
|
|
374
|
+
const emptyHome = createTempDir('openchamber-empty-home-bundled-opencode-');
|
|
375
|
+
const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
|
|
376
|
+
const { runtime, state } = createRuntime({}, {
|
|
377
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
378
|
+
homedir: () => emptyHome,
|
|
379
|
+
ensurePinnedOpenCode2Cli: async () => {
|
|
380
|
+
throw missingError;
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).resolves.toBe(bundledBinary);
|
|
217
385
|
expect(state.resolvedOpencodeBinarySource).toBe('bundled');
|
|
386
|
+
expect(process.env.OPENCODE_BINARY).toBe(bundledBinary);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it('does not adopt a bundled 1.x CLI after pin install fails', async () => {
|
|
390
|
+
const bundledDir = createTempDir('openchamber-bundled-1x-');
|
|
391
|
+
const bundledName = process.platform === 'win32' ? 'opencode.exe' : 'opencode';
|
|
392
|
+
const bundledBinary = path.join(bundledDir, bundledName);
|
|
393
|
+
writeVersionBinary(bundledBinary, '1.18.4');
|
|
394
|
+
process.env.OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR = bundledDir;
|
|
395
|
+
process.env.PATH = createTempDir('openchamber-empty-path-bundled-1x-');
|
|
396
|
+
process.env.OPENCHAMBER_DATA_DIR = createTempDir('openchamber-empty-data-bundled-1x-');
|
|
397
|
+
delete process.env.OPENCODE_BINARY;
|
|
398
|
+
const emptyHome = createTempDir('openchamber-empty-home-bundled-1x-');
|
|
399
|
+
const missingError = Object.assign(new Error('OpenCode CLI missing'), { code: 'OPENCODE_CLI_MISSING' });
|
|
400
|
+
const { runtime, state } = createRuntime({}, {
|
|
401
|
+
spawnSync: () => ({ status: 1, stdout: '', stderr: '' }),
|
|
402
|
+
homedir: () => emptyHome,
|
|
403
|
+
ensurePinnedOpenCode2Cli: async () => {
|
|
404
|
+
throw missingError;
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
await expect(runtime.ensurePinnedOpenCode2CliEnv()).rejects.toMatchObject({
|
|
409
|
+
code: 'OPENCODE_CLI_MISSING',
|
|
410
|
+
});
|
|
411
|
+
expect(state.resolvedOpencodeBinary).toBeNull();
|
|
412
|
+
expect(state.resolvedOpencodeBinarySource).toBeNull();
|
|
413
|
+
expect(process.env.OPENCODE_BINARY).toBeUndefined();
|
|
218
414
|
});
|
|
219
415
|
|
|
220
416
|
itIf(process.platform === 'darwin')('rejects known macOS OpenCode app bundle executable paths', async () => {
|
|
@@ -265,7 +461,8 @@ describe('OpenCode env runtime', () => {
|
|
|
265
461
|
const cliBinary = path.join(createTempDir('openchamber-cli-'), 'opencode.exe');
|
|
266
462
|
fs.mkdirSync(path.dirname(desktopBinary), { recursive: true });
|
|
267
463
|
fs.writeFileSync(desktopBinary, '');
|
|
268
|
-
fs.writeFileSync(cliBinary, '');
|
|
464
|
+
fs.writeFileSync(cliBinary, `#!${process.execPath}\nconsole.log('2.0.12');\n`);
|
|
465
|
+
fs.chmodSync(cliBinary, 0o755);
|
|
269
466
|
process.env.LOCALAPPDATA = localAppData;
|
|
270
467
|
process.env.PATH = createTempDir('openchamber-empty-path-');
|
|
271
468
|
process.env.SystemRoot = createTempDir('openchamber-empty-systemroot-');
|
|
@@ -2,6 +2,7 @@ import { registerFsRoutes } from '../fs/routes.js';
|
|
|
2
2
|
import { registerQuotaRoutes } from '../quota/routes.js';
|
|
3
3
|
import { registerSmallModelRoutes } from '../small-model/routes.js';
|
|
4
4
|
import { registerSessionGoalRoutes } from '../session-goal/routes.js';
|
|
5
|
+
import { registerSessionMetadataRoutes } from '../session-metadata/routes.js';
|
|
5
6
|
import { registerGitHubRoutes } from '../github/routes.js';
|
|
6
7
|
import { registerGitRoutes } from '../git/routes.js';
|
|
7
8
|
import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
|
|
@@ -18,13 +19,16 @@ import { registerSessionTurnPageRoutes } from '../session-turn-pages/routes.js';
|
|
|
18
19
|
import { registerAssistantRoutes } from '../assistants/routes.js';
|
|
19
20
|
import { registerLlmRoutes } from '../llm/routes.js';
|
|
20
21
|
import { registerMessageQueueRoutes } from '../message-queue/routes.js';
|
|
22
|
+
import { getBrowserProviderHost } from '../browser-provider/host.js';
|
|
23
|
+
import { registerBrowserProviderRoutes } from '../browser-provider/routes.js';
|
|
24
|
+
import { createBrowserProviderSelectionStore } from '../browser-provider/selection-store.js';
|
|
21
25
|
import { registerSkillRoutes } from './skill-routes.js';
|
|
22
26
|
import { registerPluginRoutes } from './plugin-routes.js';
|
|
23
27
|
import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
|
|
24
28
|
import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
|
|
25
29
|
import { registerOpenCodeRoutes } from './routes.js';
|
|
26
30
|
import { getProviderSources, removeProviderConfig } from './providers.js';
|
|
27
|
-
import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
|
|
31
|
+
import { getAgentSources, getAgentConfig, listDisabledAgentOverrides, createAgent, updateAgent, deleteAgent } from './agents.js';
|
|
28
32
|
import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
|
|
29
33
|
import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
|
|
30
34
|
import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
|
|
@@ -113,6 +117,16 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
113
117
|
isUnsafeSkillRelativePath,
|
|
114
118
|
buildOpenCodeUrl,
|
|
115
119
|
getOpenCodeAuthHeaders,
|
|
120
|
+
getIsExternalOpenCode = () => false,
|
|
121
|
+
getIsSharedOpenCodeService = () => false,
|
|
122
|
+
forceResolvedOpenCodeBinary = null,
|
|
123
|
+
restartOpenCode = null,
|
|
124
|
+
getRuntimeContract = () => null,
|
|
125
|
+
getOpenCodeServeVersion = () => null,
|
|
126
|
+
getOpenCodeCliVersion = () => null,
|
|
127
|
+
getResolvedOpenCodeBinary = () => null,
|
|
128
|
+
getResolvedOpenCodeBinarySource = () => null,
|
|
129
|
+
getActiveSessionCount = () => 0,
|
|
116
130
|
getSmallModelService: routeGetSmallModelService,
|
|
117
131
|
getOpenCodePort,
|
|
118
132
|
buildAugmentedPath,
|
|
@@ -132,6 +146,13 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
132
146
|
getServerId,
|
|
133
147
|
sessionIndexService,
|
|
134
148
|
notifyContactTurnComplete,
|
|
149
|
+
sessionMetadataStore = null,
|
|
150
|
+
sessionArchiveService = null,
|
|
151
|
+
onSessionMetadataWritten = null,
|
|
152
|
+
persistSessionGoal = null,
|
|
153
|
+
persistSessionMetadata = null,
|
|
154
|
+
onSystemSessionPersisted = null,
|
|
155
|
+
readSessionMetadata = null,
|
|
135
156
|
} = routeDependencies;
|
|
136
157
|
|
|
137
158
|
registerSettingsUtilityRoutes(app, {
|
|
@@ -164,6 +185,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
164
185
|
refreshOpenCodeAfterConfigChange,
|
|
165
186
|
buildOpenCodeUrl,
|
|
166
187
|
getOpenCodeAuthHeaders,
|
|
188
|
+
getIsExternalOpenCode,
|
|
189
|
+
getIsSharedOpenCodeService,
|
|
190
|
+
forceResolvedOpenCodeBinary,
|
|
191
|
+
restartOpenCode,
|
|
192
|
+
waitForOpenCodeReady,
|
|
193
|
+
getRuntimeContract,
|
|
194
|
+
getOpenCodeServeVersion,
|
|
195
|
+
getOpenCodeCliVersion,
|
|
196
|
+
getResolvedOpenCodeBinary,
|
|
197
|
+
getResolvedOpenCodeBinarySource,
|
|
198
|
+
getActiveSessionCount,
|
|
199
|
+
openchamberDataDir,
|
|
167
200
|
onSettingsPersisted: (updated, changes) => {
|
|
168
201
|
if (!questionAutoDelegateRuntime) return;
|
|
169
202
|
if (!Object.prototype.hasOwnProperty.call(changes ?? {}, 'questionAutoDelegateEnabled')) return;
|
|
@@ -247,6 +280,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
247
280
|
registerLlmRoutes(app, {
|
|
248
281
|
buildOpenCodeUrl,
|
|
249
282
|
getOpenCodeAuthHeaders,
|
|
283
|
+
persistSessionMetadata,
|
|
284
|
+
onSystemSessionPersisted,
|
|
250
285
|
});
|
|
251
286
|
assistantRoutesRuntime = registerAssistantRoutes(app, {
|
|
252
287
|
openchamberDataDir,
|
|
@@ -273,6 +308,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
273
308
|
},
|
|
274
309
|
listScheduledTasks: (projectID) => projectConfigRuntime.listScheduledTasks(projectID),
|
|
275
310
|
sessionIndexService,
|
|
311
|
+
archiveSessionHost: sessionArchiveService
|
|
312
|
+
? async ({ sessionID, directory, archivedAt }) => sessionArchiveService.archiveSession(
|
|
313
|
+
sessionID,
|
|
314
|
+
directory,
|
|
315
|
+
archivedAt,
|
|
316
|
+
)
|
|
317
|
+
: null,
|
|
318
|
+
forgetSessionHost: sessionArchiveService
|
|
319
|
+
? (sessionID) => sessionArchiveService.forgetSession(sessionID)
|
|
320
|
+
: null,
|
|
321
|
+
persistSessionMetadata,
|
|
322
|
+
onSystemSessionPersisted,
|
|
276
323
|
upsertScheduledTask: (projectID, task) => projectConfigRuntime.upsertScheduledTask(projectID, task),
|
|
277
324
|
syncScheduledTaskProject: (projectID) => scheduledTasksRuntime.syncProject(projectID),
|
|
278
325
|
refreshAllowedRoots: refreshAssistantAllowedRoots,
|
|
@@ -293,9 +340,11 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
293
340
|
resolveOptionalProjectDirectory,
|
|
294
341
|
refreshOpenCodeAfterConfigChange,
|
|
295
342
|
clientReloadDelayMs,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
343
|
+
waitForOpenCodeReady,
|
|
344
|
+
getAgentSources,
|
|
345
|
+
getAgentConfig,
|
|
346
|
+
listDisabledAgentOverrides,
|
|
347
|
+
createAgent,
|
|
299
348
|
updateAgent,
|
|
300
349
|
deleteAgent,
|
|
301
350
|
getCommandSources,
|
|
@@ -387,7 +436,36 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
387
436
|
throw new Error('Small model service is not configured');
|
|
388
437
|
});
|
|
389
438
|
registerSmallModelRoutes(app, { getSmallModelService });
|
|
390
|
-
registerSessionGoalRoutes(app
|
|
439
|
+
registerSessionGoalRoutes(app, {
|
|
440
|
+
// Reuse the index-provided store seam — do not invent a second store.
|
|
441
|
+
persistSessionGoal: typeof persistSessionGoal === 'function'
|
|
442
|
+
? persistSessionGoal
|
|
443
|
+
: (sessionMetadataStore
|
|
444
|
+
? async (sessionId, _directory, goal) => {
|
|
445
|
+
const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
|
|
446
|
+
openchamber: { goal },
|
|
447
|
+
});
|
|
448
|
+
return metadata;
|
|
449
|
+
}
|
|
450
|
+
: null),
|
|
451
|
+
readSessionMetadata: typeof readSessionMetadata === 'function'
|
|
452
|
+
? readSessionMetadata
|
|
453
|
+
: (sessionMetadataStore
|
|
454
|
+
? (sessionId) => sessionMetadataStore.get(sessionId)
|
|
455
|
+
: null),
|
|
456
|
+
// Same arm path as PUT /api/openchamber/sessions/:id/metadata.
|
|
457
|
+
onGoalPersisted: typeof onSessionMetadataWritten === 'function'
|
|
458
|
+
? onSessionMetadataWritten
|
|
459
|
+
: null,
|
|
460
|
+
});
|
|
461
|
+
if (sessionMetadataStore) {
|
|
462
|
+
registerSessionMetadataRoutes(app, {
|
|
463
|
+
sessionMetadataStore,
|
|
464
|
+
sessionArchiveService,
|
|
465
|
+
broadcastGlobalUiEvent,
|
|
466
|
+
onMetadataWritten: onSessionMetadataWritten,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
391
469
|
registerGitHubRoutes(app);
|
|
392
470
|
const broadcastOpenChamberEvent = createOpenChamberEventBroadcaster({
|
|
393
471
|
getOpenChamberEventClients,
|
|
@@ -438,6 +516,13 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
438
516
|
openchamberUserConfigRoot,
|
|
439
517
|
openchamberDataDir,
|
|
440
518
|
});
|
|
519
|
+
const browserProviderHost = getBrowserProviderHost();
|
|
520
|
+
if (typeof openchamberDataDir === 'string' && openchamberDataDir) {
|
|
521
|
+
browserProviderHost.attachSelectionStore(createBrowserProviderSelectionStore(
|
|
522
|
+
path.join(openchamberDataDir, 'browser-provider-selection.json'),
|
|
523
|
+
));
|
|
524
|
+
}
|
|
525
|
+
registerBrowserProviderRoutes(app, { host: browserProviderHost });
|
|
441
526
|
};
|
|
442
527
|
|
|
443
528
|
return {
|
|
@@ -3,6 +3,20 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
3
3
|
import { createWorktreeTopologyBroadcaster } from './feature-routes-runtime.js';
|
|
4
4
|
|
|
5
5
|
describe('feature routes runtime composition', () => {
|
|
6
|
+
it('passes live shared-service ownership from the host to upgrade routes', async () => {
|
|
7
|
+
const host = await fs.readFile(new URL('../../index.js', import.meta.url), 'utf8');
|
|
8
|
+
const routes = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
9
|
+
expect(host).toContain('getIsSharedOpenCodeService: () => openCodeLifecycleRuntime.isSharedOpenCodeService()');
|
|
10
|
+
expect(routes).toMatch(/getIsSharedOpenCodeService = \(\) => false,[\s\S]*\} = routeDependencies;/);
|
|
11
|
+
expect(routes).toMatch(/registerOpenCodeRoutes\(app, \{[^}]*getIsSharedOpenCodeService,/);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('wires Host isolation persist into scheduled-task runtime from the composition root', async () => {
|
|
15
|
+
const source = await fs.readFile(new URL('../../index.js', import.meta.url), 'utf8');
|
|
16
|
+
expect(source).toMatch(/createScheduledTasksRuntime\(\{[\s\S]*persistSessionMetadata: persistSessionMetadataToStore/);
|
|
17
|
+
expect(source).toMatch(/createScheduledTasksRuntime\(\{[\s\S]*onSystemSessionPersisted: hideSystemSessionFromIndex/);
|
|
18
|
+
});
|
|
19
|
+
|
|
6
20
|
it('wires scheduled-task upsert into assistant contact tools', async () => {
|
|
7
21
|
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
8
22
|
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*upsertScheduledTask:[\s\S]*projectConfigRuntime\.upsertScheduledTask/);
|
|
@@ -10,6 +24,10 @@ describe('feature routes runtime composition', () => {
|
|
|
10
24
|
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*listProjects:/);
|
|
11
25
|
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*listScheduledTasks:[\s\S]*projectConfigRuntime\.listScheduledTasks/);
|
|
12
26
|
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*sessionIndexService/);
|
|
27
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*persistSessionMetadata/);
|
|
28
|
+
expect(source).toMatch(/registerAssistantRoutes\(app, \{[\s\S]*onSystemSessionPersisted/);
|
|
29
|
+
expect(source).toMatch(/registerLlmRoutes\(app, \{[\s\S]*persistSessionMetadata/);
|
|
30
|
+
expect(source).toMatch(/registerLlmRoutes\(app, \{[\s\S]*onSystemSessionPersisted/);
|
|
13
31
|
expect(source).toMatch(/label: project\.label\.trim\(\)/);
|
|
14
32
|
});
|
|
15
33
|
|
|
@@ -36,6 +54,13 @@ describe('feature routes runtime composition', () => {
|
|
|
36
54
|
expect(source).toMatch(/registerScheduledTaskRoutes\(app, \{[\s\S]*runHistoryStore,/);
|
|
37
55
|
});
|
|
38
56
|
|
|
57
|
+
it('registers browser provider routes on the shared host before proxy composition', async () => {
|
|
58
|
+
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
59
|
+
expect(source).toContain("import { registerBrowserProviderRoutes } from '../browser-provider/routes.js';");
|
|
60
|
+
expect(source).toContain('registerBrowserProviderRoutes(app, { host: browserProviderHost });');
|
|
61
|
+
expect(source).toContain('createBrowserProviderSelectionStore(');
|
|
62
|
+
});
|
|
63
|
+
|
|
39
64
|
it('registers message queue routes with the injected service before proxy composition', async () => {
|
|
40
65
|
const source = await fs.readFile(new URL('./feature-routes-runtime.js', import.meta.url), 'utf8');
|
|
41
66
|
expect(source).toContain("import { registerMessageQueueRoutes } from '../message-queue/routes.js';");
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track sockets owned by an HTTP server so force-close can destroy upgraded
|
|
3
|
+
* WebSocket/SSE sockets that Node's `server.closeAllConnections()` leaves open
|
|
4
|
+
* (those keep `server.close()` from firing until SHUTDOWN_TIMEOUT).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const HTTP_SERVER_CONNECTIONS = Symbol.for('openchamber.httpServerConnections');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('node:http').Server} server
|
|
11
|
+
* @returns {{
|
|
12
|
+
* getOpenSocketCount: () => number,
|
|
13
|
+
* destroyRemainingSockets: () => number,
|
|
14
|
+
* dispose: () => void,
|
|
15
|
+
* }}
|
|
16
|
+
*/
|
|
17
|
+
export const attachHttpServerConnectionTracker = (server) => {
|
|
18
|
+
if (!server || typeof server.on !== 'function') {
|
|
19
|
+
return {
|
|
20
|
+
getOpenSocketCount: () => 0,
|
|
21
|
+
destroyRemainingSockets: () => 0,
|
|
22
|
+
dispose: () => {},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const existing = server[HTTP_SERVER_CONNECTIONS];
|
|
27
|
+
if (existing && typeof existing.destroyRemainingSockets === 'function') {
|
|
28
|
+
return existing;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @type {Set<import('node:net').Socket>} */
|
|
32
|
+
const sockets = new Set();
|
|
33
|
+
|
|
34
|
+
const track = (socket) => {
|
|
35
|
+
if (!socket || sockets.has(socket)) return;
|
|
36
|
+
sockets.add(socket);
|
|
37
|
+
const release = () => {
|
|
38
|
+
sockets.delete(socket);
|
|
39
|
+
};
|
|
40
|
+
socket.once('close', release);
|
|
41
|
+
socket.once('error', release);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Plain HTTP and pre-upgrade TCP both emit `connection`. After `upgrade`, the
|
|
45
|
+
// same socket object remains; tracking it here covers WS/SSE leftovers.
|
|
46
|
+
const onConnection = (socket) => {
|
|
47
|
+
track(socket);
|
|
48
|
+
};
|
|
49
|
+
server.on('connection', onConnection);
|
|
50
|
+
|
|
51
|
+
const api = {
|
|
52
|
+
getOpenSocketCount: () => sockets.size,
|
|
53
|
+
destroyRemainingSockets: () => {
|
|
54
|
+
let destroyed = 0;
|
|
55
|
+
for (const socket of [...sockets]) {
|
|
56
|
+
try {
|
|
57
|
+
if (!socket.destroyed) {
|
|
58
|
+
socket.destroy();
|
|
59
|
+
destroyed += 1;
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
sockets.delete(socket);
|
|
64
|
+
}
|
|
65
|
+
return destroyed;
|
|
66
|
+
},
|
|
67
|
+
dispose: () => {
|
|
68
|
+
server.off?.('connection', onConnection);
|
|
69
|
+
sockets.clear();
|
|
70
|
+
try {
|
|
71
|
+
delete server[HTTP_SERVER_CONNECTIONS];
|
|
72
|
+
} catch {
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
server[HTTP_SERVER_CONNECTIONS] = api;
|
|
78
|
+
return api;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Stop accepting new connections, drop Node-tracked HTTP connections, then
|
|
83
|
+
* destroy any remaining server-owned sockets (including upgraded ones).
|
|
84
|
+
* @param {import('node:http').Server | null | undefined} server
|
|
85
|
+
* @returns {{ destroyedTracked: number }}
|
|
86
|
+
*/
|
|
87
|
+
export const forceCloseHttpServerConnections = (server) => {
|
|
88
|
+
if (!server) return { destroyedTracked: 0 };
|
|
89
|
+
try {
|
|
90
|
+
server.closeAllConnections?.();
|
|
91
|
+
} catch {
|
|
92
|
+
}
|
|
93
|
+
const tracker = server[HTTP_SERVER_CONNECTIONS];
|
|
94
|
+
const destroyedTracked = typeof tracker?.destroyRemainingSockets === 'function'
|
|
95
|
+
? tracker.destroyRemainingSockets()
|
|
96
|
+
: 0;
|
|
97
|
+
return { destroyedTracked };
|
|
98
|
+
};
|