@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
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://github.com/yee94/openchamber/releases/latest)
|
|
5
5
|
[](https://discord.gg/ZYRSdnwwKA)
|
|
6
6
|
|
|
7
|
-
Run [OpenCode](https://opencode.ai) in your browser. Install the CLI, open `localhost:3000`, done. Works on desktop browsers, tablets, and phones as a PWA.
|
|
7
|
+
Run [OpenCode](https://opencode.ai) in your browser. Install the `opencode` v2 CLI, open `localhost:3000`, done. Works on desktop browsers, tablets, and phones as a PWA.
|
|
8
8
|
|
|
9
9
|
Full project overview, screenshots, and all features: [github.com/yee94/openchamber](https://github.com/yee94/openchamber)
|
|
10
10
|
|
|
@@ -16,10 +16,14 @@ curl -fsSL https://raw.githubusercontent.com/yee94/openchamber/main/scripts/inst
|
|
|
16
16
|
|
|
17
17
|
Or install manually: `bun add -g @openchambery/web` (or npm, pnpm, yarn).
|
|
18
18
|
|
|
19
|
-
> **Prerequisites:** [
|
|
19
|
+
> **Prerequisites:** [`opencode`](https://opencode.ai/v2/docs/) v2 CLI installed (PATH or `OPENCODE_BINARY`; 1.x versions are rejected), Node.js 22+.
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
|
+
By default, OpenChamber connects to the official shared OpenCode background service used by Desktop and TUI. Closing OpenChamber leaves OpenCode running, and the next launch reuses it. If the service cannot start, OpenChamber reports the failure for retry instead of launching a private fallback. In-app OpenCode upgrades install into OpenChamber's own cache and replace the shared service through the official service command; this also reconnects other clients using that service. User-installed CLI files are not overwritten.
|
|
24
|
+
|
|
25
|
+
Explicit `OPENCODE_HOST` / `OPENCODE_PORT` connections keep their existing behavior. `OPENCHAMBER_OPENCODE_SHARED_SERVICE=0` selects the private managed lifecycle when required by an existing deployment.
|
|
26
|
+
|
|
23
27
|
```bash
|
|
24
28
|
openchamber # Start on port 3000
|
|
25
29
|
openchamber --port 8080 # Custom port
|
|
@@ -33,7 +37,7 @@ openchamber connect-url --port 3000 # Add this server to OpenChamber Desktop
|
|
|
33
37
|
openchamber connect-url --server http://host:3000 --qr
|
|
34
38
|
openchamber connect-url --port 3000 --qr
|
|
35
39
|
openchamber logs # Follow latest instance logs
|
|
36
|
-
OPENCODE_PORT=4096 OPENCODE_SKIP_START=true openchamber # Connect to external
|
|
40
|
+
OPENCODE_PORT=4096 OPENCODE_SKIP_START=true openchamber # Connect to external opencode v2 server
|
|
37
41
|
OPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber # Connect via custom host/HTTPS
|
|
38
42
|
openchamber stop # Stop server
|
|
39
43
|
openchamber update # Update to latest version
|
|
@@ -109,6 +113,8 @@ Generating a client token does not automatically password-protect the hosted bro
|
|
|
109
113
|
<details>
|
|
110
114
|
<summary>Connect to external OpenCode server</summary>
|
|
111
115
|
|
|
116
|
+
The external host must be **OpenCode v2**. OpenChamber runs v2 health (`GET /api/info`) and V1 migration (`GET /api/experimental/migration/v1`) checks; a 1.x server will not pass. Automatic discovery uses the official `opencode` command only. `OPENCODE_BINARY` and `settings.opencodeBinary` can override the executable path; admission is checked by version.
|
|
117
|
+
|
|
112
118
|
```bash
|
|
113
119
|
OPENCODE_PORT=4096 OPENCODE_SKIP_START=true openchamber
|
|
114
120
|
OPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber
|
|
@@ -116,8 +122,8 @@ OPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber
|
|
|
116
122
|
|
|
117
123
|
| Variable | Description |
|
|
118
124
|
|----------|-------------|
|
|
119
|
-
| `OPENCODE_HOST` | Full base URL of external server (overrides `OPENCODE_PORT`) |
|
|
120
|
-
| `OPENCODE_PORT` | Port of external server |
|
|
125
|
+
| `OPENCODE_HOST` | Full base URL of external OpenCode v2 server (overrides `OPENCODE_PORT`) |
|
|
126
|
+
| `OPENCODE_PORT` | Port of external OpenCode v2 server |
|
|
121
127
|
| `OPENCODE_SKIP_START` | Skip starting embedded OpenCode server |
|
|
122
128
|
| `OPENCHAMBER_OPENCODE_HOSTNAME` | Bind hostname for managed OpenCode server (default: `127.0.0.1`, use `0.0.0.0` for LAN/remote access — trusted networks only) |
|
|
123
129
|
| `OPENCHAMBER_HOST` | Bind hostname for the OpenChamber web server (default: `127.0.0.1`; use `0.0.0.0` for LAN/remote access — trusted networks only) |
|
|
@@ -166,12 +172,12 @@ openchamber stop # Stop background server
|
|
|
166
172
|
<details>
|
|
167
173
|
<summary>systemd service (VPN / LAN access)</summary>
|
|
168
174
|
|
|
169
|
-
Use `--foreground` to keep the CLI process alive so systemd (or any other process manager) can track and restart it. Combine with `OPENCODE_HOST` to connect to an OpenCode instance running as a separate service.
|
|
175
|
+
Use `--foreground` to keep the CLI process alive so systemd (or any other process manager) can track and restart it. Combine with `OPENCODE_HOST` to connect to an OpenCode v2 instance running as a separate service.
|
|
170
176
|
|
|
171
177
|
**`~/.config/systemd/user/opencode.service`**
|
|
172
178
|
```ini
|
|
173
179
|
[Unit]
|
|
174
|
-
Description=OpenCode Server
|
|
180
|
+
Description=OpenCode 2 Server
|
|
175
181
|
|
|
176
182
|
[Service]
|
|
177
183
|
Type=simple
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
import { checkOpenCodeCLI } from './cli.js';
|
|
7
|
+
|
|
8
|
+
function createTempDir(prefix) {
|
|
9
|
+
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function writeVersionBinary(filePath, version) {
|
|
13
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
14
|
+
if (process.platform === 'win32') {
|
|
15
|
+
fs.writeFileSync(filePath, `@echo off\r\necho ${version}\r\n`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
fs.writeFileSync(filePath, `#!${process.execPath}\nconsole.log(${JSON.stringify(version)});\n`);
|
|
19
|
+
fs.chmodSync(filePath, 0o755);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function withIsolatedOpenCodeEnv(fn) {
|
|
23
|
+
const previousBinary = process.env.OPENCODE_BINARY;
|
|
24
|
+
const previousPath = process.env.PATH;
|
|
25
|
+
const previousDataDir = process.env.OPENCHAMBER_DATA_DIR;
|
|
26
|
+
const previousAutoInstall = process.env.OPENCHAMBER_OPENCODE2_AUTO_INSTALL;
|
|
27
|
+
const dataDir = createTempDir('openchamber-cli-opencode-data-');
|
|
28
|
+
process.env.OPENCHAMBER_DATA_DIR = dataDir;
|
|
29
|
+
process.env.OPENCHAMBER_OPENCODE2_AUTO_INSTALL = '0';
|
|
30
|
+
try {
|
|
31
|
+
return await fn({ dataDir });
|
|
32
|
+
} finally {
|
|
33
|
+
if (typeof previousBinary === 'string') {
|
|
34
|
+
process.env.OPENCODE_BINARY = previousBinary;
|
|
35
|
+
} else {
|
|
36
|
+
delete process.env.OPENCODE_BINARY;
|
|
37
|
+
}
|
|
38
|
+
if (typeof previousPath === 'string') {
|
|
39
|
+
process.env.PATH = previousPath;
|
|
40
|
+
} else {
|
|
41
|
+
delete process.env.PATH;
|
|
42
|
+
}
|
|
43
|
+
if (typeof previousDataDir === 'string') {
|
|
44
|
+
process.env.OPENCHAMBER_DATA_DIR = previousDataDir;
|
|
45
|
+
} else {
|
|
46
|
+
delete process.env.OPENCHAMBER_DATA_DIR;
|
|
47
|
+
}
|
|
48
|
+
if (typeof previousAutoInstall === 'string') {
|
|
49
|
+
process.env.OPENCHAMBER_OPENCODE2_AUTO_INSTALL = previousAutoInstall;
|
|
50
|
+
} else {
|
|
51
|
+
delete process.env.OPENCHAMBER_OPENCODE2_AUTO_INSTALL;
|
|
52
|
+
}
|
|
53
|
+
fs.rmSync(dataDir, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe('checkOpenCodeCLI v2 gate', () => {
|
|
58
|
+
it('uses the same binary validation for TTY, non-TTY, --quiet, and --json notice styles', async () => {
|
|
59
|
+
await withIsolatedOpenCodeEnv(async () => {
|
|
60
|
+
const dir = createTempDir('openchamber-cli-mode-parity-');
|
|
61
|
+
const binary = path.join(dir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
62
|
+
writeVersionBinary(binary, '1.18.4');
|
|
63
|
+
process.env.OPENCODE_BINARY = binary;
|
|
64
|
+
const pathDir = createTempDir('openchamber-cli-mode-path-');
|
|
65
|
+
writeVersionBinary(path.join(pathDir, process.platform === 'win32' ? 'opencode2.cmd' : 'opencode2'), '2.0.12');
|
|
66
|
+
process.env.PATH = pathDir;
|
|
67
|
+
|
|
68
|
+
const notices = [];
|
|
69
|
+
const modes = [
|
|
70
|
+
{ json: true, quiet: false },
|
|
71
|
+
{ json: false, quiet: true },
|
|
72
|
+
{ json: false, quiet: false },
|
|
73
|
+
];
|
|
74
|
+
try {
|
|
75
|
+
for (const mode of modes) {
|
|
76
|
+
await expect(checkOpenCodeCLI((notice) => notices.push({ mode, notice }))).rejects.toMatchObject({
|
|
77
|
+
code: 'OPENCODE_BINARY_INVALID',
|
|
78
|
+
message: expect.stringMatching(/1\.x.*OpenCode v2/s),
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
expect(notices).toEqual([]);
|
|
82
|
+
} finally {
|
|
83
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
84
|
+
fs.rmSync(pathDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('fails closed for opencode / opencode.exe / opencode.cmd and does not fall back to PATH', async () => {
|
|
90
|
+
await withIsolatedOpenCodeEnv(async () => {
|
|
91
|
+
const names = process.platform === 'win32'
|
|
92
|
+
? ['opencode.exe', 'opencode.cmd']
|
|
93
|
+
: ['opencode', 'opencode.exe', 'opencode.cmd'];
|
|
94
|
+
const pathDir = createTempDir('openchamber-cli-legacy-path-');
|
|
95
|
+
const v2 = path.join(pathDir, process.platform === 'win32' ? 'opencode2.cmd' : 'opencode2');
|
|
96
|
+
writeVersionBinary(v2, '2.0.12');
|
|
97
|
+
process.env.PATH = pathDir;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
for (const name of names) {
|
|
101
|
+
const dir = createTempDir(`openchamber-cli-legacy-${name}-`);
|
|
102
|
+
const binary = path.join(dir, name);
|
|
103
|
+
writeVersionBinary(binary, '1.18.4');
|
|
104
|
+
process.env.OPENCODE_BINARY = binary;
|
|
105
|
+
try {
|
|
106
|
+
await expect(checkOpenCodeCLI()).rejects.toMatchObject({
|
|
107
|
+
code: 'OPENCODE_BINARY_INVALID',
|
|
108
|
+
message: expect.stringMatching(/1\.x.*OpenCode v2/s),
|
|
109
|
+
});
|
|
110
|
+
expect(process.env.OPENCODE_BINARY).toBe(binary);
|
|
111
|
+
} finally {
|
|
112
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} finally {
|
|
116
|
+
fs.rmSync(pathDir, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('discovers official opencode from PATH and ignores the opencode2 alias', async () => {
|
|
122
|
+
await withIsolatedOpenCodeEnv(async () => {
|
|
123
|
+
const pathDir = createTempDir('openchamber-cli-path-opencode2-');
|
|
124
|
+
const legacy = path.join(pathDir, process.platform === 'win32' ? 'opencode2.cmd' : 'opencode2');
|
|
125
|
+
const binary = path.join(pathDir, process.platform === 'win32' ? 'opencode.cmd' : 'opencode');
|
|
126
|
+
writeVersionBinary(legacy, '2.0.15');
|
|
127
|
+
writeVersionBinary(binary, '2.0.12');
|
|
128
|
+
process.env.PATH = pathDir;
|
|
129
|
+
delete process.env.OPENCODE_BINARY;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await expect(checkOpenCodeCLI()).resolves.toBe(binary);
|
|
133
|
+
expect(process.env.OPENCODE_BINARY).toBe(binary);
|
|
134
|
+
} finally {
|
|
135
|
+
fs.rmSync(pathDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('fails closed when settings.opencodeBinary is a 1.x basename', async () => {
|
|
141
|
+
await withIsolatedOpenCodeEnv(async ({ dataDir }) => {
|
|
142
|
+
const dir = createTempDir('openchamber-cli-settings-1x-');
|
|
143
|
+
const binary = path.join(dir, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
144
|
+
writeVersionBinary(binary, '1.18.4');
|
|
145
|
+
fs.writeFileSync(path.join(dataDir, 'settings.json'), JSON.stringify({ opencodeBinary: binary }));
|
|
146
|
+
delete process.env.OPENCODE_BINARY;
|
|
147
|
+
const pathDir = createTempDir('openchamber-cli-settings-path-');
|
|
148
|
+
writeVersionBinary(path.join(pathDir, process.platform === 'win32' ? 'opencode2.cmd' : 'opencode2'), '2.0.12');
|
|
149
|
+
process.env.PATH = pathDir;
|
|
150
|
+
try {
|
|
151
|
+
await expect(checkOpenCodeCLI()).rejects.toMatchObject({
|
|
152
|
+
code: 'OPENCODE_BINARY_INVALID',
|
|
153
|
+
message: expect.stringMatching(/1\.x.*OpenCode v2/s),
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
157
|
+
fs.rmSync(pathDir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('accepts a configured opencode2 path after the v2 version gate', async () => {
|
|
163
|
+
await withIsolatedOpenCodeEnv(async ({ dataDir }) => {
|
|
164
|
+
const dir = createTempDir('openchamber-cli-settings-v2-');
|
|
165
|
+
const binary = path.join(dir, process.platform === 'win32' ? 'opencode2.cmd' : 'opencode2');
|
|
166
|
+
writeVersionBinary(binary, '2.0.12');
|
|
167
|
+
fs.writeFileSync(path.join(dataDir, 'settings.json'), JSON.stringify({ opencodeBinary: binary }));
|
|
168
|
+
delete process.env.OPENCODE_BINARY;
|
|
169
|
+
process.env.PATH = createTempDir('openchamber-cli-settings-empty-');
|
|
170
|
+
try {
|
|
171
|
+
await expect(checkOpenCodeCLI()).resolves.toBe(binary);
|
|
172
|
+
expect(process.env.OPENCODE_BINARY).toBe(binary);
|
|
173
|
+
} finally {
|
|
174
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('names opencode2 when PATH lookup finds nothing', async () => {
|
|
180
|
+
await withIsolatedOpenCodeEnv(async () => {
|
|
181
|
+
delete process.env.OPENCODE_BINARY;
|
|
182
|
+
process.env.PATH = createTempDir('openchamber-cli-missing-');
|
|
183
|
+
await expect(checkOpenCodeCLI()).rejects.toThrow(/opencode/);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('installs pinned opencode2 when PATH has no usable binary', async () => {
|
|
188
|
+
await withIsolatedOpenCodeEnv(async ({ dataDir }) => {
|
|
189
|
+
delete process.env.OPENCODE_BINARY;
|
|
190
|
+
process.env.PATH = createTempDir('openchamber-cli-install-missing-');
|
|
191
|
+
const installed = path.join(dataDir, 'opencode-cli', '2.0.12', process.platform === 'win32' ? 'opencode2.exe' : 'opencode2');
|
|
192
|
+
writeVersionBinary(installed, '2.0.12');
|
|
193
|
+
const notices = [];
|
|
194
|
+
await expect(checkOpenCodeCLI((notice) => notices.push(notice), {
|
|
195
|
+
ensurePinnedOpenCode2Cli: async () => ({
|
|
196
|
+
path: installed,
|
|
197
|
+
version: '2.0.12',
|
|
198
|
+
source: 'installed',
|
|
199
|
+
installed: true,
|
|
200
|
+
}),
|
|
201
|
+
})).resolves.toBe(installed);
|
|
202
|
+
expect(process.env.OPENCODE_BINARY).toBe(installed);
|
|
203
|
+
expect(notices).toEqual([
|
|
204
|
+
expect.objectContaining({ code: 'OPENCODE_CLI_INSTALLED' }),
|
|
205
|
+
]);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
package/bin/cli.js
CHANGED
|
@@ -18,7 +18,12 @@ import {
|
|
|
18
18
|
findClosestMatch,
|
|
19
19
|
} from './lib/cli-args.js';
|
|
20
20
|
import { readDesktopLocalPortFromSettings } from './lib/cli-paths.js';
|
|
21
|
-
import {
|
|
21
|
+
import { searchPathFor } from './lib/cli-executables.js';
|
|
22
|
+
import {
|
|
23
|
+
assertOpenCode2Binary,
|
|
24
|
+
readConfiguredOpenCodeBinary,
|
|
25
|
+
} from './lib/cli-startup.js';
|
|
26
|
+
import { ensurePinnedOpenCode2Cli } from '../server/lib/opencode/ensure-cli.js';
|
|
22
27
|
import { startupCommand } from './lib/commands-startup.js';
|
|
23
28
|
import { logsCommand } from './lib/commands-logs.js';
|
|
24
29
|
import { statusCommand } from './lib/commands-status.js';
|
|
@@ -75,14 +80,16 @@ function importFromFilePath(filePath) {
|
|
|
75
80
|
return import(pathToFileURL(filePath).href);
|
|
76
81
|
}
|
|
77
82
|
|
|
78
|
-
|
|
83
|
+
// Binary validation is policy, not presentation: TTY, non-TTY, --quiet, and
|
|
84
|
+
// --json all run this same check before serve starts.
|
|
85
|
+
async function checkOpenCodeCLI(onNotice, options = {}) {
|
|
79
86
|
if (process.env.OPENCODE_BINARY) {
|
|
80
|
-
const override =
|
|
87
|
+
const override = assertOpenCode2Binary(process.env.OPENCODE_BINARY);
|
|
81
88
|
if (override) {
|
|
82
89
|
process.env.OPENCODE_BINARY = override;
|
|
83
90
|
return override;
|
|
84
91
|
}
|
|
85
|
-
const message = `OPENCODE_BINARY="${process.env.OPENCODE_BINARY}" is not
|
|
92
|
+
const message = `OPENCODE_BINARY="${process.env.OPENCODE_BINARY}" is not a usable OpenCode v2 CLI. Falling back to PATH lookup.`;
|
|
86
93
|
if (typeof onNotice === 'function') {
|
|
87
94
|
onNotice({ level: 'warning', code: 'OPENCODE_BINARY_INVALID', message });
|
|
88
95
|
} else {
|
|
@@ -90,15 +97,60 @@ async function checkOpenCodeCLI(onNotice) {
|
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
const configured = readConfiguredOpenCodeBinary();
|
|
101
|
+
if (configured) {
|
|
102
|
+
const fromConfig = assertOpenCode2Binary(configured);
|
|
103
|
+
if (fromConfig) {
|
|
104
|
+
process.env.OPENCODE_BINARY = fromConfig;
|
|
105
|
+
return fromConfig;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let discovered = '';
|
|
110
|
+
for (const name of ['opencode']) {
|
|
111
|
+
const resolvedFromPath = searchPathFor(name);
|
|
112
|
+
if (!resolvedFromPath) continue;
|
|
113
|
+
try {
|
|
114
|
+
const verified = assertOpenCode2Binary(resolvedFromPath);
|
|
115
|
+
if (verified) {
|
|
116
|
+
discovered = verified;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error?.code === 'OPENCODE_BINARY_INVALID') continue;
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const ensureCli = typeof options.ensurePinnedOpenCode2Cli === 'function'
|
|
125
|
+
? options.ensurePinnedOpenCode2Cli
|
|
126
|
+
: ensurePinnedOpenCode2Cli;
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const ensured = await ensureCli({ discoveredPath: discovered });
|
|
130
|
+
if (ensured?.path) {
|
|
131
|
+
if (typeof onNotice === 'function' && ensured.installed) {
|
|
132
|
+
onNotice({
|
|
133
|
+
level: 'info',
|
|
134
|
+
code: 'OPENCODE_CLI_INSTALLED',
|
|
135
|
+
message: `Installed opencode ${ensured.version} to ${ensured.path}`,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
process.env.OPENCODE_BINARY = ensured.path;
|
|
139
|
+
return ensured.path;
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error?.code === 'OPENCODE_CLI_MISSING') {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Unable to locate the opencode CLI on PATH (${process.env.PATH || '<empty>'}). ` +
|
|
145
|
+
'Ensure OpenCode v2 is installed and reachable, or set OPENCODE_BINARY to its full path.'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
97
149
|
}
|
|
98
150
|
|
|
99
151
|
throw new Error(
|
|
100
152
|
`Unable to locate the opencode CLI on PATH (${process.env.PATH || '<empty>'}). ` +
|
|
101
|
-
'Ensure
|
|
153
|
+
'Ensure OpenCode v2 is installed and reachable, or set OPENCODE_BINARY to its full path.'
|
|
102
154
|
);
|
|
103
155
|
}
|
|
104
156
|
|
|
@@ -304,4 +356,5 @@ export {
|
|
|
304
356
|
findClosestMatch,
|
|
305
357
|
TunnelCliError,
|
|
306
358
|
EXIT_CODE,
|
|
359
|
+
checkOpenCodeCLI,
|
|
307
360
|
};
|
package/bin/lib/cli-startup.js
CHANGED
|
@@ -6,10 +6,107 @@ import { DEFAULT_PORT } from './cli-args.js';
|
|
|
6
6
|
import { EXIT_CODE, TunnelCliError } from './cli-errors.js';
|
|
7
7
|
import { getDataDir } from './cli-paths.js';
|
|
8
8
|
import { hasUiPasswordConfigured } from './cli-network.js';
|
|
9
|
-
import { searchPathFor } from './cli-executables.js';
|
|
9
|
+
import { resolveExplicitBinary, searchPathFor } from './cli-executables.js';
|
|
10
|
+
import { isAcceptableOpenCode2HealthVersion, isOpenCode1xVersion } from '../../server/lib/opencode/opencode2-pin.js';
|
|
10
11
|
|
|
11
12
|
const STARTUP_SERVICE_ID = 'dev.openchamber.web';
|
|
12
13
|
|
|
14
|
+
function createOpenCode1xVersionError(candidate, version) {
|
|
15
|
+
const error = new Error(
|
|
16
|
+
`OpenCode 1.x is not supported (${candidate} reports ${version}). Install OpenCode v2 (opencode).`
|
|
17
|
+
);
|
|
18
|
+
error.code = 'OPENCODE_BINARY_INVALID';
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function extractOpenCodeVersion(output) {
|
|
23
|
+
if (typeof output !== 'string' || output.trim().length === 0) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const match = output.match(/v?\d+\.\d+[\w.+-]*/i);
|
|
27
|
+
return match ? match[0] : '';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function readOpenCodeBinaryVersion(binaryPath) {
|
|
31
|
+
const result = spawnSync(binaryPath, ['--version'], {
|
|
32
|
+
encoding: 'utf8',
|
|
33
|
+
timeout: 8000,
|
|
34
|
+
windowsHide: true,
|
|
35
|
+
});
|
|
36
|
+
return `${result.stdout || ''}\n${result.stderr || ''}`.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// OPENCODE_BINARY and settings.opencodeBinary share the v2 version gate.
|
|
40
|
+
function assertOpenCode2Binary(candidate) {
|
|
41
|
+
const trimmed = typeof candidate === 'string' ? candidate.trim() : '';
|
|
42
|
+
if (!trimmed) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const resolved = resolveExplicitBinary(trimmed) || trimmed;
|
|
46
|
+
const version = extractOpenCodeVersion(readOpenCodeBinaryVersion(resolved));
|
|
47
|
+
if (isOpenCode1xVersion(version)) {
|
|
48
|
+
throw createOpenCode1xVersionError(resolved, version);
|
|
49
|
+
}
|
|
50
|
+
if (!isAcceptableOpenCode2HealthVersion(version)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return resolveExplicitBinary(trimmed);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readConfiguredOpenCodeBinary() {
|
|
57
|
+
try {
|
|
58
|
+
const raw = fs.readFileSync(path.join(getDataDir(), 'settings.json'), 'utf8');
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
if (typeof parsed?.opencodeBinary !== 'string') {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
const trimmed = parsed.opencodeBinary.trim();
|
|
64
|
+
if (!trimmed) {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
if (fs.statSync(trimmed).isDirectory()) {
|
|
69
|
+
return path.join(trimmed, process.platform === 'win32' ? 'opencode.exe' : 'opencode');
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
return trimmed;
|
|
74
|
+
} catch {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveOpenCode2BinaryForStartup() {
|
|
80
|
+
const explicit = typeof process.env.OPENCODE_BINARY === 'string' ? process.env.OPENCODE_BINARY.trim() : '';
|
|
81
|
+
if (explicit) {
|
|
82
|
+
const resolved = assertOpenCode2Binary(explicit);
|
|
83
|
+
if (resolved) {
|
|
84
|
+
return resolved;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const configured = readConfiguredOpenCodeBinary();
|
|
89
|
+
if (configured) {
|
|
90
|
+
const resolved = assertOpenCode2Binary(configured);
|
|
91
|
+
if (resolved) {
|
|
92
|
+
return resolved;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (const name of ['opencode']) {
|
|
97
|
+
const resolvedFromPath = searchPathFor(name);
|
|
98
|
+
if (!resolvedFromPath) continue;
|
|
99
|
+
try {
|
|
100
|
+
const resolved = assertOpenCode2Binary(resolvedFromPath);
|
|
101
|
+
if (resolved) return resolved;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error?.code === 'OPENCODE_BINARY_INVALID') continue;
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
13
110
|
function getStartupServicePaths() {
|
|
14
111
|
if (process.platform === 'darwin') {
|
|
15
112
|
return {
|
|
@@ -82,9 +179,11 @@ function collectStartupEnv(options = {}) {
|
|
|
82
179
|
);
|
|
83
180
|
|
|
84
181
|
if (options.envSnapshot !== false) {
|
|
85
|
-
const opencodeBinary =
|
|
182
|
+
const opencodeBinary = resolveOpenCode2BinaryForStartup();
|
|
86
183
|
if (typeof opencodeBinary === 'string' && opencodeBinary.trim().length > 0) {
|
|
87
184
|
env.OPENCODE_BINARY = opencodeBinary.trim();
|
|
185
|
+
} else {
|
|
186
|
+
delete env.OPENCODE_BINARY;
|
|
88
187
|
}
|
|
89
188
|
}
|
|
90
189
|
const uiPassword = hasUiPasswordConfigured(options.uiPassword) ? options.uiPassword : undefined;
|
|
@@ -367,4 +466,8 @@ export {
|
|
|
367
466
|
getStartupStatus,
|
|
368
467
|
enableStartupService,
|
|
369
468
|
disableStartupService,
|
|
469
|
+
collectStartupEnv,
|
|
470
|
+
assertOpenCode2Binary,
|
|
471
|
+
readConfiguredOpenCodeBinary,
|
|
472
|
+
resolveOpenCode2BinaryForStartup,
|
|
370
473
|
};
|