@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.3
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-B_JQoreg.js} +1 -1
- package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
- package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
- package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
- package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
- package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
- package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
- package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
- package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
- package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
- package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
- package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
- package/dist/assets/SettingsView-BRxELjow.js +25 -0
- package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
- package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
- package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
- package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
- package/dist/assets/collapsible-DXbDynN4.js +3 -0
- package/dist/assets/context-menu-CqolsLCm.js +1 -0
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
- package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
- package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
- package/dist/assets/es-CN3E_zXj.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
- package/dist/assets/fr-9zZvQZD2.js +13 -0
- package/dist/assets/gitApi-CM42qUed.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
- package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
- package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
- package/dist/assets/ja-Dnjk_82N.js +15 -0
- package/dist/assets/ko-BxCEE99C.js +15 -0
- package/dist/assets/main-BILgBI3B.js +169 -0
- package/dist/assets/main-BeveiW6F.js +2 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
- package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
- package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
- package/dist/assets/miniChat-BgrzM3u7.js +2 -0
- package/dist/assets/mobile-DtYoQE8C.js +2 -0
- package/dist/assets/multirun-jy8dEI96.js +4 -0
- package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
- package/dist/assets/pl-CNNsWnPM.js +15 -0
- package/dist/assets/platform-BQghHRfp.js +6 -0
- package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
- package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
- package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
- package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
- package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
- package/dist/assets/src-CLN7Z-zx.css +2 -0
- package/dist/assets/store-DLCiZaA6.js +15 -0
- package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
- package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
- package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
- package/dist/assets/uk-rwQrxeYM.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
- package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
- package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
- package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
- package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
- package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
- package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
- package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
- package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
- package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
- package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
- package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
- package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
- package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
- package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
- package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
- package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
- package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +2 -2
- package/server/index.js +392 -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/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/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +51 -5
- 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/runtime.js +5 -0
- package/server/lib/opencode/DOCUMENTATION.md +103 -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 +111 -102
- package/server/lib/opencode/config-entity-routes.test.js +123 -10
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -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 +80 -5
- package/server/lib/opencode/feature-routes-runtime.test.js +18 -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 +368 -28
- package/server/lib/opencode/proxy.test.js +338 -0
- package/server/lib/opencode/routes.js +303 -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/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 +86 -0
- package/server/lib/session-metadata/routes.js +193 -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 +36 -0
- package/server/lib/session-metadata/session-metadata-store.js +375 -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 +255 -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 +67 -42
- 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/MarkdownRenderer-BbrbDH6-.js +0 -2
- 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/appThemeRegistry-D5Yxg4YF.js +0 -1
- package/dist/assets/collapsible-D5Phaykh.js +0 -1
- package/dist/assets/context-menu-CubSDW2g.js +0 -1
- package/dist/assets/es-DA3MqOo0.js +0 -15
- 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/markdown-worker-CptoICmq.js +0 -2
- 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/pl-B9No6MQp.js +0 -15
- package/dist/assets/platform-0w_Q9_ji.js +0 -2
- package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
- 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/src-rS3zCcsC.css +0 -2
- package/dist/assets/store-5B_LDnLM.js +0 -15
- 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';
|
|
@@ -24,7 +25,7 @@ import { getNpmInfo, clearCache as clearNpmCache } from './npm-registry.js';
|
|
|
24
25
|
import { parseNpmSpec, parsePathSpec, isExactSemver } from './plugin-spec.js';
|
|
25
26
|
import { registerOpenCodeRoutes } from './routes.js';
|
|
26
27
|
import { getProviderSources, removeProviderConfig } from './providers.js';
|
|
27
|
-
import { getAgentSources, getAgentConfig, createAgent, updateAgent, deleteAgent } from './agents.js';
|
|
28
|
+
import { getAgentSources, getAgentConfig, listDisabledAgentOverrides, createAgent, updateAgent, deleteAgent } from './agents.js';
|
|
28
29
|
import { getCommandSources, createCommand, updateCommand, deleteCommand } from './commands.js';
|
|
29
30
|
import { listMcpConfigs, getMcpConfig, createMcpConfig, updateMcpConfig, deleteMcpConfig } from './mcp.js';
|
|
30
31
|
import { listSnippets, getSnippet, createSnippet, updateSnippet, deleteSnippet, expandSnippets } from './snippets.js';
|
|
@@ -113,6 +114,16 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
113
114
|
isUnsafeSkillRelativePath,
|
|
114
115
|
buildOpenCodeUrl,
|
|
115
116
|
getOpenCodeAuthHeaders,
|
|
117
|
+
getIsExternalOpenCode = () => false,
|
|
118
|
+
getIsSharedOpenCodeService = () => false,
|
|
119
|
+
forceResolvedOpenCodeBinary = null,
|
|
120
|
+
restartOpenCode = null,
|
|
121
|
+
getRuntimeContract = () => null,
|
|
122
|
+
getOpenCodeServeVersion = () => null,
|
|
123
|
+
getOpenCodeCliVersion = () => null,
|
|
124
|
+
getResolvedOpenCodeBinary = () => null,
|
|
125
|
+
getResolvedOpenCodeBinarySource = () => null,
|
|
126
|
+
getActiveSessionCount = () => 0,
|
|
116
127
|
getSmallModelService: routeGetSmallModelService,
|
|
117
128
|
getOpenCodePort,
|
|
118
129
|
buildAugmentedPath,
|
|
@@ -132,6 +143,13 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
132
143
|
getServerId,
|
|
133
144
|
sessionIndexService,
|
|
134
145
|
notifyContactTurnComplete,
|
|
146
|
+
sessionMetadataStore = null,
|
|
147
|
+
sessionArchiveService = null,
|
|
148
|
+
onSessionMetadataWritten = null,
|
|
149
|
+
persistSessionGoal = null,
|
|
150
|
+
persistSessionMetadata = null,
|
|
151
|
+
onSystemSessionPersisted = null,
|
|
152
|
+
readSessionMetadata = null,
|
|
135
153
|
} = routeDependencies;
|
|
136
154
|
|
|
137
155
|
registerSettingsUtilityRoutes(app, {
|
|
@@ -164,6 +182,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
164
182
|
refreshOpenCodeAfterConfigChange,
|
|
165
183
|
buildOpenCodeUrl,
|
|
166
184
|
getOpenCodeAuthHeaders,
|
|
185
|
+
getIsExternalOpenCode,
|
|
186
|
+
getIsSharedOpenCodeService,
|
|
187
|
+
forceResolvedOpenCodeBinary,
|
|
188
|
+
restartOpenCode,
|
|
189
|
+
waitForOpenCodeReady,
|
|
190
|
+
getRuntimeContract,
|
|
191
|
+
getOpenCodeServeVersion,
|
|
192
|
+
getOpenCodeCliVersion,
|
|
193
|
+
getResolvedOpenCodeBinary,
|
|
194
|
+
getResolvedOpenCodeBinarySource,
|
|
195
|
+
getActiveSessionCount,
|
|
196
|
+
openchamberDataDir,
|
|
167
197
|
onSettingsPersisted: (updated, changes) => {
|
|
168
198
|
if (!questionAutoDelegateRuntime) return;
|
|
169
199
|
if (!Object.prototype.hasOwnProperty.call(changes ?? {}, 'questionAutoDelegateEnabled')) return;
|
|
@@ -247,6 +277,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
247
277
|
registerLlmRoutes(app, {
|
|
248
278
|
buildOpenCodeUrl,
|
|
249
279
|
getOpenCodeAuthHeaders,
|
|
280
|
+
persistSessionMetadata,
|
|
281
|
+
onSystemSessionPersisted,
|
|
250
282
|
});
|
|
251
283
|
assistantRoutesRuntime = registerAssistantRoutes(app, {
|
|
252
284
|
openchamberDataDir,
|
|
@@ -273,6 +305,18 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
273
305
|
},
|
|
274
306
|
listScheduledTasks: (projectID) => projectConfigRuntime.listScheduledTasks(projectID),
|
|
275
307
|
sessionIndexService,
|
|
308
|
+
archiveSessionHost: sessionArchiveService
|
|
309
|
+
? async ({ sessionID, directory, archivedAt }) => sessionArchiveService.archiveSession(
|
|
310
|
+
sessionID,
|
|
311
|
+
directory,
|
|
312
|
+
archivedAt,
|
|
313
|
+
)
|
|
314
|
+
: null,
|
|
315
|
+
forgetSessionHost: sessionArchiveService
|
|
316
|
+
? (sessionID) => sessionArchiveService.forgetSession(sessionID)
|
|
317
|
+
: null,
|
|
318
|
+
persistSessionMetadata,
|
|
319
|
+
onSystemSessionPersisted,
|
|
276
320
|
upsertScheduledTask: (projectID, task) => projectConfigRuntime.upsertScheduledTask(projectID, task),
|
|
277
321
|
syncScheduledTaskProject: (projectID) => scheduledTasksRuntime.syncProject(projectID),
|
|
278
322
|
refreshAllowedRoots: refreshAssistantAllowedRoots,
|
|
@@ -293,9 +337,11 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
293
337
|
resolveOptionalProjectDirectory,
|
|
294
338
|
refreshOpenCodeAfterConfigChange,
|
|
295
339
|
clientReloadDelayMs,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
340
|
+
waitForOpenCodeReady,
|
|
341
|
+
getAgentSources,
|
|
342
|
+
getAgentConfig,
|
|
343
|
+
listDisabledAgentOverrides,
|
|
344
|
+
createAgent,
|
|
299
345
|
updateAgent,
|
|
300
346
|
deleteAgent,
|
|
301
347
|
getCommandSources,
|
|
@@ -387,7 +433,36 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
|
|
387
433
|
throw new Error('Small model service is not configured');
|
|
388
434
|
});
|
|
389
435
|
registerSmallModelRoutes(app, { getSmallModelService });
|
|
390
|
-
registerSessionGoalRoutes(app
|
|
436
|
+
registerSessionGoalRoutes(app, {
|
|
437
|
+
// Reuse the index-provided store seam — do not invent a second store.
|
|
438
|
+
persistSessionGoal: typeof persistSessionGoal === 'function'
|
|
439
|
+
? persistSessionGoal
|
|
440
|
+
: (sessionMetadataStore
|
|
441
|
+
? async (sessionId, _directory, goal) => {
|
|
442
|
+
const metadata = await sessionMetadataStore.setSessionMetadata(sessionId, {
|
|
443
|
+
openchamber: { goal },
|
|
444
|
+
});
|
|
445
|
+
return metadata;
|
|
446
|
+
}
|
|
447
|
+
: null),
|
|
448
|
+
readSessionMetadata: typeof readSessionMetadata === 'function'
|
|
449
|
+
? readSessionMetadata
|
|
450
|
+
: (sessionMetadataStore
|
|
451
|
+
? (sessionId) => sessionMetadataStore.get(sessionId)
|
|
452
|
+
: null),
|
|
453
|
+
// Same arm path as PUT /api/openchamber/sessions/:id/metadata.
|
|
454
|
+
onGoalPersisted: typeof onSessionMetadataWritten === 'function'
|
|
455
|
+
? onSessionMetadataWritten
|
|
456
|
+
: null,
|
|
457
|
+
});
|
|
458
|
+
if (sessionMetadataStore) {
|
|
459
|
+
registerSessionMetadataRoutes(app, {
|
|
460
|
+
sessionMetadataStore,
|
|
461
|
+
sessionArchiveService,
|
|
462
|
+
broadcastGlobalUiEvent,
|
|
463
|
+
onMetadataWritten: onSessionMetadataWritten,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
391
466
|
registerGitHubRoutes(app);
|
|
392
467
|
const broadcastOpenChamberEvent = createOpenChamberEventBroadcaster({
|
|
393
468
|
getOpenChamberEventClients,
|
|
@@ -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
|
|
|
@@ -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
|
+
};
|