@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
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildSharedServiceStartEnv,
|
|
7
|
+
isSharedServiceEnabled,
|
|
8
|
+
parseSharedServiceRegistration,
|
|
9
|
+
readSharedServiceRegistration,
|
|
10
|
+
resolveSharedServiceRegistrationPath,
|
|
11
|
+
startSharedOpenCodeService,
|
|
12
|
+
} from './shared-service.js';
|
|
13
|
+
|
|
14
|
+
const fakeChild = () => {
|
|
15
|
+
const child = new EventEmitter();
|
|
16
|
+
child.stderr = new EventEmitter();
|
|
17
|
+
child.kill = vi.fn();
|
|
18
|
+
return child;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe('shared OpenCode service registration', () => {
|
|
22
|
+
it('resolves the stable-channel registration under the XDG state root', () => {
|
|
23
|
+
expect(resolveSharedServiceRegistrationPath({}, '/home/u')).toBe(path.join('/home/u', '.local', 'state', 'opencode', 'service.json'));
|
|
24
|
+
expect(resolveSharedServiceRegistrationPath({ XDG_STATE_HOME: '/xdg' }, '/home/u')).toBe(path.join('/xdg', 'opencode', 'service.json'));
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('parses a registration and rejects malformed ones', () => {
|
|
28
|
+
expect(parseSharedServiceRegistration(JSON.stringify({
|
|
29
|
+
id: 'a', version: '2.0.12', url: 'http://127.0.0.1:49374', pid: 42, password: 'pw',
|
|
30
|
+
}))).toEqual({ origin: 'http://127.0.0.1:49374', port: 49374, pid: 42, password: 'pw', version: '2.0.12' });
|
|
31
|
+
expect(parseSharedServiceRegistration(JSON.stringify({ url: 'http://127.0.0.1:49374' }))?.password).toBeNull();
|
|
32
|
+
expect(parseSharedServiceRegistration('not json')).toBeNull();
|
|
33
|
+
expect(parseSharedServiceRegistration(JSON.stringify({ url: 'ftp://x:1' }))).toBeNull();
|
|
34
|
+
expect(parseSharedServiceRegistration(JSON.stringify({ url: 'http://127.0.0.1' }))).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('treats a missing registration file as absent, not as a service', async () => {
|
|
38
|
+
const fsLike = { readFile: vi.fn(async () => { throw Object.assign(new Error('nope'), { code: 'ENOENT' }); }) };
|
|
39
|
+
await expect(readSharedServiceRegistration({ file: '/x/service.json', fsLike })).resolves.toBeNull();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('is enabled by default and can be opted out', () => {
|
|
43
|
+
expect(isSharedServiceEnabled({})).toBe(true);
|
|
44
|
+
expect(isSharedServiceEnabled({ OPENCHAMBER_OPENCODE_SHARED_SERVICE: '0' })).toBe(false);
|
|
45
|
+
expect(isSharedServiceEnabled({ OPENCHAMBER_OPENCODE_SHARED_SERVICE: 'false' })).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe('shared OpenCode service start', () => {
|
|
50
|
+
it('never hands OpenChamber credentials to the long-lived service', () => {
|
|
51
|
+
expect(buildSharedServiceStartEnv({
|
|
52
|
+
PATH: '/bin',
|
|
53
|
+
HOME: '/home/u',
|
|
54
|
+
OPENCODE_SERVER_PASSWORD: 'managed',
|
|
55
|
+
OPENCODE_PASSWORD: 'managed',
|
|
56
|
+
OPENCHAMBER_UI_PASSWORD: 'ui',
|
|
57
|
+
OPENCHAMBER_DATA_DIR: '/data',
|
|
58
|
+
})).toEqual({ PATH: '/bin', HOME: '/home/u' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('runs `service start` and resolves on a clean exit', async () => {
|
|
62
|
+
const child = fakeChild();
|
|
63
|
+
const spawnImpl = vi.fn(() => {
|
|
64
|
+
queueMicrotask(() => child.emit('exit', 0));
|
|
65
|
+
return child;
|
|
66
|
+
});
|
|
67
|
+
await startSharedOpenCodeService({ binary: '/bin/opencode', args: ['--x'], env: { PATH: '/bin' }, spawnImpl });
|
|
68
|
+
expect(spawnImpl).toHaveBeenCalledWith('/bin/opencode', ['--x', 'service', 'start'], expect.objectContaining({ env: { PATH: '/bin' } }));
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('surfaces the CLI error when `service start` fails', async () => {
|
|
72
|
+
const child = fakeChild();
|
|
73
|
+
const spawnImpl = vi.fn(() => {
|
|
74
|
+
queueMicrotask(() => {
|
|
75
|
+
child.stderr.emit('data', 'Background service failed to start');
|
|
76
|
+
child.emit('exit', 1);
|
|
77
|
+
});
|
|
78
|
+
return child;
|
|
79
|
+
});
|
|
80
|
+
await expect(startSharedOpenCodeService({ binary: 'opencode', env: {}, spawnImpl }))
|
|
81
|
+
.rejects.toThrow(/exited with code 1: Background service failed to start/);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('replaces the registered service only for an explicit upgrade', async () => {
|
|
85
|
+
const child = fakeChild();
|
|
86
|
+
const spawnImpl = vi.fn(() => {
|
|
87
|
+
queueMicrotask(() => child.emit('exit', 0));
|
|
88
|
+
return child;
|
|
89
|
+
});
|
|
90
|
+
await startSharedOpenCodeService({ binary: '/cache/opencode', env: {}, replace: true, spawnImpl });
|
|
91
|
+
expect(spawnImpl).toHaveBeenCalledWith('/cache/opencode', ['service', 'restart'], expect.objectContaining({ windowsHide: true }));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -371,9 +371,9 @@ function addSkillFromMdFile(skillsMap, skillMdPath, scope, source) {
|
|
|
371
371
|
return;
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
-
const name =
|
|
375
|
-
?
|
|
376
|
-
: '';
|
|
374
|
+
const name = path.basename(skillMdPath) === 'SKILL.md'
|
|
375
|
+
? path.basename(path.dirname(skillMdPath))
|
|
376
|
+
: path.basename(skillMdPath, '.md');
|
|
377
377
|
const description = typeof parsed.frontmatter?.description === 'string'
|
|
378
378
|
? parsed.frontmatter.description
|
|
379
379
|
: '';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { forceCloseHttpServerConnections } from './http-server-connections.js';
|
|
2
|
+
|
|
1
3
|
export const createGracefulShutdownRuntime = (dependencies) => {
|
|
2
4
|
const {
|
|
3
5
|
process,
|
|
@@ -22,7 +24,6 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
22
24
|
getOpenCodePort,
|
|
23
25
|
getOpenCodeProcess,
|
|
24
26
|
setOpenCodeProcess,
|
|
25
|
-
killProcessOnPort,
|
|
26
27
|
waitForPortRelease,
|
|
27
28
|
getServer,
|
|
28
29
|
getUiAuthController,
|
|
@@ -78,12 +79,14 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (!shouldSkipOpenCodeStop()) {
|
|
81
|
-
const
|
|
82
|
+
const portToRelease = getOpenCodePort();
|
|
82
83
|
const openCodeProcess = getOpenCodeProcess();
|
|
83
84
|
|
|
84
85
|
if (openCodeProcess) {
|
|
85
86
|
console.log('Stopping OpenCode process...');
|
|
86
87
|
try {
|
|
88
|
+
// close() owns SIGTERM→SIGKILL on this child handle. Never re-kill a
|
|
89
|
+
// numeric pid captured before close — it may already be recycled.
|
|
87
90
|
await openCodeProcess.close();
|
|
88
91
|
} catch (error) {
|
|
89
92
|
console.warn('Error closing OpenCode process:', error);
|
|
@@ -91,9 +94,9 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
91
94
|
setOpenCodeProcess(null);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
if (!(await waitForPortRelease(
|
|
96
|
-
console.warn(`Timed out waiting for OpenCode port ${
|
|
97
|
+
// Probe only after owned close. Do not mass-kill by port (clients / external).
|
|
98
|
+
if (!(await waitForPortRelease(portToRelease, 5000))) {
|
|
99
|
+
console.warn(`Timed out waiting for OpenCode port ${portToRelease} to be released during shutdown`);
|
|
97
100
|
}
|
|
98
101
|
} else {
|
|
99
102
|
console.log('Skipping OpenCode shutdown (external server)');
|
|
@@ -105,12 +108,18 @@ export const createGracefulShutdownRuntime = (dependencies) => {
|
|
|
105
108
|
try {
|
|
106
109
|
await Promise.race([
|
|
107
110
|
new Promise((resolve) => {
|
|
111
|
+
// Stop accepting first. closeAllConnections drops plain HTTP sockets
|
|
112
|
+
// but leaves upgraded WS/SSE; destroy tracked server-owned leftovers
|
|
113
|
+
// so the close callback can fire without waiting SHUTDOWN_TIMEOUT.
|
|
108
114
|
server.close(() => {
|
|
109
115
|
console.log('HTTP server closed');
|
|
110
116
|
resolve();
|
|
111
117
|
});
|
|
112
118
|
if (options.forceCloseConnections === true) {
|
|
113
|
-
server
|
|
119
|
+
const { destroyedTracked } = forceCloseHttpServerConnections(server);
|
|
120
|
+
if (destroyedTracked > 0) {
|
|
121
|
+
console.log(`Forced close of ${destroyedTracked} remaining server socket(s)`);
|
|
122
|
+
}
|
|
114
123
|
}
|
|
115
124
|
}),
|
|
116
125
|
new Promise((resolve) => {
|
|
@@ -22,7 +22,6 @@ const createRuntime = (server, shutdownTimeoutMs = 1000, overrides = {}) => crea
|
|
|
22
22
|
getOpenCodePort: () => null,
|
|
23
23
|
getOpenCodeProcess: () => null,
|
|
24
24
|
setOpenCodeProcess: vi.fn(),
|
|
25
|
-
killProcessOnPort: vi.fn(),
|
|
26
25
|
waitForPortRelease: vi.fn(async () => true),
|
|
27
26
|
getServer: () => server,
|
|
28
27
|
getUiAuthController: () => null,
|
|
@@ -96,4 +95,53 @@ describe('graceful shutdown runtime', () => {
|
|
|
96
95
|
expect(scheduledTasksRuntime.stop).toHaveBeenCalledOnce();
|
|
97
96
|
expect(runHistoryStore.close).not.toHaveBeenCalled();
|
|
98
97
|
});
|
|
98
|
+
|
|
99
|
+
it('awaits managed OpenCode close with zero extra kill, then probes port release for 5s', async () => {
|
|
100
|
+
const close = vi.fn(async () => undefined);
|
|
101
|
+
const waitForPortRelease = vi.fn(async (_port, timeoutMs) => {
|
|
102
|
+
expect(timeoutMs).toBe(5000);
|
|
103
|
+
return true;
|
|
104
|
+
});
|
|
105
|
+
const setOpenCodeProcess = vi.fn();
|
|
106
|
+
const server = {
|
|
107
|
+
close: vi.fn((callback) => {
|
|
108
|
+
callback();
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const runtime = createRuntime(server, 1000, {
|
|
113
|
+
shouldSkipOpenCodeStop: () => false,
|
|
114
|
+
getOpenCodePort: () => 45678,
|
|
115
|
+
getOpenCodeProcess: () => ({ close, pid: 99 }),
|
|
116
|
+
setOpenCodeProcess,
|
|
117
|
+
waitForPortRelease,
|
|
118
|
+
});
|
|
119
|
+
await runtime.gracefulShutdown({ exitProcess: false });
|
|
120
|
+
|
|
121
|
+
expect(close).toHaveBeenCalledOnce();
|
|
122
|
+
expect(setOpenCodeProcess).toHaveBeenCalledWith(null);
|
|
123
|
+
expect(waitForPortRelease).toHaveBeenCalledWith(45678, 5000);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('does not kill anything when OpenCode close already finished and a later server stage fails', async () => {
|
|
127
|
+
const close = vi.fn(async () => undefined);
|
|
128
|
+
const waitForPortRelease = vi.fn(async () => true);
|
|
129
|
+
const server = {
|
|
130
|
+
close: vi.fn(() => {
|
|
131
|
+
throw new Error('server close blew up after opencode stop');
|
|
132
|
+
}),
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const runtime = createRuntime(server, 1000, {
|
|
136
|
+
shouldSkipOpenCodeStop: () => false,
|
|
137
|
+
getOpenCodePort: () => 45678,
|
|
138
|
+
getOpenCodeProcess: () => ({ close, pid: 99 }),
|
|
139
|
+
setOpenCodeProcess: vi.fn(),
|
|
140
|
+
waitForPortRelease,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await expect(runtime.gracefulShutdown({ exitProcess: false })).rejects.toThrow(/server close blew up/);
|
|
144
|
+
expect(close).toHaveBeenCalledOnce();
|
|
145
|
+
expect(waitForPortRelease).toHaveBeenCalledWith(45678, 5000);
|
|
146
|
+
});
|
|
99
147
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OpenCode } from '@opencode/client';
|
|
2
2
|
|
|
3
3
|
const summarizeDescription = (value) => {
|
|
4
4
|
const normalized = typeof value === 'string' ? value.replace(/\s+/gu, ' ').trim() : '';
|
|
@@ -16,8 +16,6 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
16
16
|
readSettingsFromDisk,
|
|
17
17
|
sanitizeSkillCatalogs,
|
|
18
18
|
isUnsafeSkillRelativePath,
|
|
19
|
-
refreshOpenCodeAfterConfigChange,
|
|
20
|
-
clientReloadDelayMs,
|
|
21
19
|
buildOpenCodeUrl,
|
|
22
20
|
getOpenCodeAuthHeaders,
|
|
23
21
|
getOpenCodePort,
|
|
@@ -127,25 +125,26 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
127
125
|
}
|
|
128
126
|
|
|
129
127
|
try {
|
|
130
|
-
const client =
|
|
128
|
+
const client = OpenCode.make({
|
|
131
129
|
baseUrl: buildOpenCodeUrl('/', '').replace(/\/$/, ''),
|
|
132
|
-
directory: workingDirectory || undefined,
|
|
133
130
|
headers: getOpenCodeAuthHeaders(),
|
|
134
|
-
fetch:
|
|
131
|
+
fetch: globalThis.fetch,
|
|
135
132
|
});
|
|
136
133
|
|
|
137
|
-
const response = await client.
|
|
138
|
-
workingDirectory ? { directory: workingDirectory } : undefined,
|
|
134
|
+
const response = await client.skill.list(
|
|
135
|
+
workingDirectory ? { location: { directory: workingDirectory } } : undefined,
|
|
136
|
+
{ signal: AbortSignal.timeout(8_000) },
|
|
139
137
|
);
|
|
140
138
|
const payload = response?.data;
|
|
141
139
|
if (!Array.isArray(payload)) {
|
|
142
|
-
|
|
140
|
+
throw new Error('OpenCode skill catalog is unavailable');
|
|
143
141
|
}
|
|
144
142
|
|
|
145
143
|
return payload
|
|
146
144
|
.map((item) => {
|
|
147
|
-
|
|
148
|
-
const
|
|
145
|
+
// OpenChamber's name is the invocation key, not the V2 display label.
|
|
146
|
+
const name = typeof item?.id === 'string' ? item.id : '';
|
|
147
|
+
const location = typeof item?.path === 'string' ? item.path : '';
|
|
149
148
|
const description = typeof item?.description === 'string' ? item.description : '';
|
|
150
149
|
const content = typeof item?.content === 'string' ? item.content : '';
|
|
151
150
|
if (!name || !location) {
|
|
@@ -177,7 +176,7 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
177
176
|
.filter(Boolean);
|
|
178
177
|
} catch (error) {
|
|
179
178
|
console.error('Failed to list OpenCode skills:', error);
|
|
180
|
-
|
|
179
|
+
throw error;
|
|
181
180
|
}
|
|
182
181
|
};
|
|
183
182
|
|
|
@@ -462,19 +461,14 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
462
461
|
|
|
463
462
|
const installed = result.installed || [];
|
|
464
463
|
const skipped = result.skipped || [];
|
|
465
|
-
const requiresReload = installed.length > 0;
|
|
466
|
-
|
|
467
|
-
if (requiresReload) {
|
|
468
|
-
await refreshOpenCodeAfterConfigChange('skills install');
|
|
469
|
-
}
|
|
470
464
|
|
|
471
465
|
return res.json({
|
|
472
466
|
ok: true,
|
|
473
467
|
installed,
|
|
474
468
|
skipped,
|
|
475
|
-
requiresReload,
|
|
476
|
-
|
|
477
|
-
|
|
469
|
+
requiresReload: false,
|
|
470
|
+
application: 'watch',
|
|
471
|
+
message: installed.length > 0 ? 'Skills installed successfully.' : 'No skills were installed',
|
|
478
472
|
});
|
|
479
473
|
}
|
|
480
474
|
|
|
@@ -513,19 +507,14 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
513
507
|
|
|
514
508
|
const installed = result.installed || [];
|
|
515
509
|
const skipped = result.skipped || [];
|
|
516
|
-
const requiresReload = installed.length > 0;
|
|
517
|
-
|
|
518
|
-
if (requiresReload) {
|
|
519
|
-
await refreshOpenCodeAfterConfigChange('skills install');
|
|
520
|
-
}
|
|
521
510
|
|
|
522
511
|
res.json({
|
|
523
512
|
ok: true,
|
|
524
513
|
installed,
|
|
525
514
|
skipped,
|
|
526
|
-
requiresReload,
|
|
527
|
-
|
|
528
|
-
|
|
515
|
+
requiresReload: false,
|
|
516
|
+
application: 'watch',
|
|
517
|
+
message: installed.length > 0 ? 'Skills installed successfully.' : 'No skills were installed',
|
|
529
518
|
});
|
|
530
519
|
} catch (error) {
|
|
531
520
|
console.error('Failed to install skills:', error);
|
|
@@ -606,13 +595,12 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
606
595
|
console.log('[Server] Scope:', scope, 'Working directory:', directory);
|
|
607
596
|
|
|
608
597
|
createSkill(skillName, { ...config, source: skillSource }, directory, scope);
|
|
609
|
-
await refreshOpenCodeAfterConfigChange('skill creation');
|
|
610
598
|
|
|
611
599
|
res.json({
|
|
612
600
|
success: true,
|
|
613
|
-
requiresReload:
|
|
614
|
-
|
|
615
|
-
|
|
601
|
+
requiresReload: false,
|
|
602
|
+
application: 'watch',
|
|
603
|
+
message: `Skill ${skillName} created successfully.`,
|
|
616
604
|
});
|
|
617
605
|
} catch (error) {
|
|
618
606
|
console.error('Failed to create skill:', error);
|
|
@@ -633,13 +621,12 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
633
621
|
console.log('[Server] Working directory:', directory);
|
|
634
622
|
|
|
635
623
|
updateSkill(skillName, updates, directory, updates?.targetPath);
|
|
636
|
-
await refreshOpenCodeAfterConfigChange('skill update');
|
|
637
624
|
|
|
638
625
|
res.json({
|
|
639
626
|
success: true,
|
|
640
|
-
requiresReload:
|
|
641
|
-
|
|
642
|
-
|
|
627
|
+
requiresReload: false,
|
|
628
|
+
application: 'watch',
|
|
629
|
+
message: `Skill ${skillName} updated successfully.`,
|
|
643
630
|
});
|
|
644
631
|
} catch (error) {
|
|
645
632
|
console.error('[Server] Failed to update skill:', error);
|
|
@@ -725,13 +712,12 @@ export const registerSkillRoutes = (app, dependencies) => {
|
|
|
725
712
|
}
|
|
726
713
|
|
|
727
714
|
deleteSkill(skillName, directory);
|
|
728
|
-
await refreshOpenCodeAfterConfigChange('skill deletion');
|
|
729
715
|
|
|
730
716
|
res.json({
|
|
731
717
|
success: true,
|
|
732
|
-
requiresReload:
|
|
733
|
-
|
|
734
|
-
|
|
718
|
+
requiresReload: false,
|
|
719
|
+
application: 'watch',
|
|
720
|
+
message: `Skill ${skillName} deleted successfully.`,
|
|
735
721
|
});
|
|
736
722
|
} catch (error) {
|
|
737
723
|
console.error('Failed to delete skill:', error);
|
|
@@ -2,18 +2,51 @@ import { describe, expect, it, vi } from 'vitest';
|
|
|
2
2
|
import express from 'express';
|
|
3
3
|
import request from 'supertest';
|
|
4
4
|
|
|
5
|
-
vi.mock('@opencode
|
|
5
|
+
vi.mock('@opencode/client', () => ({ OpenCode: { make: vi.fn() } }));
|
|
6
6
|
|
|
7
|
-
const {
|
|
7
|
+
const { OpenCode } = await import('@opencode/client');
|
|
8
8
|
const { registerSkillRoutes } = await import('./skill-routes.js');
|
|
9
9
|
|
|
10
10
|
describe('skill summary route', () => {
|
|
11
|
+
it('preserves authentication and directory through the real V2 SDK transport', async () => {
|
|
12
|
+
const { OpenCode: RealOpenCode } = await vi.importActual('@opencode/client');
|
|
13
|
+
const upstreamRequests = [];
|
|
14
|
+
const originalFetch = globalThis.fetch;
|
|
15
|
+
globalThis.fetch = async (url, init) => {
|
|
16
|
+
upstreamRequests.push(new Request(url, init));
|
|
17
|
+
return Response.json({ location: { directory: '/repo' }, data: [{
|
|
18
|
+
id: 'agent-tracker-api', name: 'Runmark', path: '/skills/agent-tracker-api/SKILL.md',
|
|
19
|
+
description: 'Query Runmark API', content: 'Instructions',
|
|
20
|
+
}] });
|
|
21
|
+
};
|
|
22
|
+
OpenCode.make.mockImplementation(RealOpenCode.make);
|
|
23
|
+
try {
|
|
24
|
+
const app = express();
|
|
25
|
+
registerSkillRoutes(app, {
|
|
26
|
+
fs: { existsSync: () => false }, path: await import('node:path'), os: await import('node:os'),
|
|
27
|
+
resolveOptionalProjectDirectory: async () => ({ directory: '/repo' }),
|
|
28
|
+
buildOpenCodeUrl: () => 'http://opencode-upstream:4096/',
|
|
29
|
+
getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic example' }), getOpenCodePort: () => 4096,
|
|
30
|
+
SKILL_SCOPE: { PROJECT: 'project', USER: 'user' }, discoverSkills: () => [], mergeDiscoveredSkills: (skills) => skills,
|
|
31
|
+
});
|
|
32
|
+
const response = await request(app).get('/api/config/skills?summary=true').expect(200);
|
|
33
|
+
expect(response.body.skills[0].name).toBe('agent-tracker-api');
|
|
34
|
+
expect(upstreamRequests).toHaveLength(1);
|
|
35
|
+
expect(upstreamRequests[0].headers.get('authorization')).toBe('Basic example');
|
|
36
|
+
expect(upstreamRequests[0].url).toContain(encodeURIComponent('/repo'));
|
|
37
|
+
} finally {
|
|
38
|
+
globalThis.fetch = originalFetch;
|
|
39
|
+
OpenCode.make.mockReset();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
11
43
|
it('returns compact normalized skill data without content or sources', async () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
44
|
+
OpenCode.make.mockReturnValue({
|
|
45
|
+
skill: {
|
|
46
|
+
list: vi.fn(async () => ({ data: [{
|
|
47
|
+
id: 'skill',
|
|
48
|
+
name: 'Skill Display Name',
|
|
49
|
+
path: '/repo/.opencode/skills/skill/SKILL.md',
|
|
17
50
|
description: ` ${'😀'.repeat(161)}\nnext `,
|
|
18
51
|
content: 'secret skill content',
|
|
19
52
|
}] })),
|
|
@@ -47,5 +80,36 @@ describe('skill summary route', () => {
|
|
|
47
80
|
});
|
|
48
81
|
expect(response.body.skills[0]).not.toHaveProperty('content');
|
|
49
82
|
expect(response.body.skills[0]).not.toHaveProperty('sources');
|
|
83
|
+
expect(OpenCode.make).toHaveBeenCalledWith(expect.objectContaining({
|
|
84
|
+
baseUrl: 'http://opencode-upstream:4096',
|
|
85
|
+
headers: { Authorization: 'Basic example' },
|
|
86
|
+
}));
|
|
87
|
+
expect(OpenCode.make.mock.calls[0][0].directory).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('returns 500 when OpenCode skill list fails instead of an empty success', async () => {
|
|
91
|
+
OpenCode.make.mockReturnValue({
|
|
92
|
+
skill: {
|
|
93
|
+
list: vi.fn(async () => { throw new Error('upstream sentinel'); }),
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
const app = express();
|
|
97
|
+
registerSkillRoutes(app, {
|
|
98
|
+
fs: { existsSync: () => false },
|
|
99
|
+
path: await import('node:path'),
|
|
100
|
+
os: await import('node:os'),
|
|
101
|
+
resolveProjectDirectory: async () => ({ directory: '/repo' }),
|
|
102
|
+
resolveOptionalProjectDirectory: async () => ({ directory: '/repo' }),
|
|
103
|
+
buildOpenCodeUrl: () => 'http://opencode-upstream:4096/',
|
|
104
|
+
getOpenCodeAuthHeaders: () => ({ Authorization: 'Basic example' }),
|
|
105
|
+
getOpenCodePort: () => 4096,
|
|
106
|
+
SKILL_SCOPE: { PROJECT: 'project', USER: 'user' },
|
|
107
|
+
discoverSkills: () => [],
|
|
108
|
+
mergeDiscoveredSkills: (primary) => primary,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const response = await request(app).get('/api/config/skills?summary=true&directory=%2Frepo').expect(500);
|
|
112
|
+
expect(JSON.stringify(response.body)).not.toContain('sentinel');
|
|
113
|
+
expect(response.body).toEqual({ error: 'Failed to list skills' });
|
|
50
114
|
});
|
|
51
115
|
});
|
|
@@ -3,8 +3,23 @@ import fsPromises from 'fs/promises';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { getSkillSources, mergeDiscoveredSkills } from './skills.js';
|
|
6
|
+
import { addSkillFromMdFile } from './shared.js';
|
|
6
7
|
|
|
7
8
|
describe('skills', () => {
|
|
9
|
+
it('uses the path-derived id even when frontmatter has a different display name', async () => {
|
|
10
|
+
const root = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'oc-skill-id-'));
|
|
11
|
+
try {
|
|
12
|
+
const dir = path.join(root, 'release');
|
|
13
|
+
await fsPromises.mkdir(dir);
|
|
14
|
+
const file = path.join(dir, 'SKILL.md');
|
|
15
|
+
await fsPromises.writeFile(file, '---\nname: Git Release\ndescription: Release notes\n---\nInstructions');
|
|
16
|
+
const skills = new Map();
|
|
17
|
+
addSkillFromMdFile(skills, file, 'project', 'opencode');
|
|
18
|
+
expect([...skills.keys()]).toEqual(['release']);
|
|
19
|
+
} finally {
|
|
20
|
+
await fsPromises.rm(root, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
8
23
|
it('merges locally discovered skills missing from OpenCode live discovery', () => {
|
|
9
24
|
const merged = mergeDiscoveredSkills(
|
|
10
25
|
[
|
|
@@ -23,6 +23,8 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
23
23
|
getOpenCodeAuthHeaders,
|
|
24
24
|
globalEventHub,
|
|
25
25
|
processForwardedEventPayload,
|
|
26
|
+
projectOutboundSessionPayload = null,
|
|
27
|
+
ensureSessionMetadataReady = null,
|
|
26
28
|
messageStreamWsClients,
|
|
27
29
|
triggerHealthCheck,
|
|
28
30
|
upstreamStallTimeoutMs,
|
|
@@ -83,6 +85,8 @@ export const createStartupPipelineRuntime = (dependencies) => {
|
|
|
83
85
|
getOpenCodeAuthHeaders,
|
|
84
86
|
globalEventHub,
|
|
85
87
|
processForwardedEventPayload,
|
|
88
|
+
projectOutboundSessionPayload,
|
|
89
|
+
ensureSessionMetadataReady,
|
|
86
90
|
wsClients: messageStreamWsClients,
|
|
87
91
|
triggerHealthCheck,
|
|
88
92
|
upstreamStallTimeoutMs,
|
|
@@ -47,8 +47,10 @@ export const createStaticRoutesRuntime = (dependencies) => {
|
|
|
47
47
|
normalizePwaOrientation,
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
// `root` limits send's dotfile check to `index.html`; an absolute path
|
|
51
|
+
// would 404 whenever the install lives under a dot directory (~/.bun, ~/.local).
|
|
50
52
|
app.get(/^(?!\/api|.*\.(js|css|svg|png|jpg|jpeg|gif|ico|woff|woff2|ttf|eot|map)).*$/, (_req, res) => {
|
|
51
|
-
res.sendFile(
|
|
53
|
+
res.sendFile('index.html', { root: distPath });
|
|
52
54
|
});
|
|
53
55
|
return;
|
|
54
56
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'bun:test';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
2
5
|
import express from 'express';
|
|
3
6
|
import request from 'supertest';
|
|
4
7
|
import { createStaticRoutesRuntime } from './static-routes-runtime.js';
|
|
@@ -57,4 +60,34 @@ describe('static routes runtime', () => {
|
|
|
57
60
|
expect(auth.body).not.toEqual({ ok: true, mode: 'api-only', message: 'OpenChamber is running in API-only mode' });
|
|
58
61
|
expect(health.body).not.toEqual({ ok: true, mode: 'api-only', message: 'OpenChamber is running in API-only mode' });
|
|
59
62
|
});
|
|
63
|
+
|
|
64
|
+
it('serves index.html for deep links when dist lives under a dot directory', async () => {
|
|
65
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openchamber-static-'));
|
|
66
|
+
const distPath = path.join(root, '.bun', 'install', 'dist');
|
|
67
|
+
fs.mkdirSync(distPath, { recursive: true });
|
|
68
|
+
fs.writeFileSync(path.join(distPath, 'index.html'), '<!doctype html><title>OpenChamber</title>');
|
|
69
|
+
try {
|
|
70
|
+
const app = express();
|
|
71
|
+
createStaticRoutesRuntime({
|
|
72
|
+
fs,
|
|
73
|
+
path,
|
|
74
|
+
process: { env: { OPENCHAMBER_DIST_DIR: distPath } },
|
|
75
|
+
__dirname: '/server',
|
|
76
|
+
express,
|
|
77
|
+
resolveProjectDirectory: () => '',
|
|
78
|
+
buildOpenCodeUrl: () => '',
|
|
79
|
+
getOpenCodeAuthHeaders: () => ({}),
|
|
80
|
+
readSettingsFromDiskMigrated: async () => ({}),
|
|
81
|
+
normalizePwaAppName: (value) => value,
|
|
82
|
+
normalizePwaOrientation: (value) => value,
|
|
83
|
+
}).registerStaticRoutes(app);
|
|
84
|
+
|
|
85
|
+
const response = await request(app).get('/session/ses_abc');
|
|
86
|
+
|
|
87
|
+
expect(response.status).toBe(200);
|
|
88
|
+
expect(response.text).toContain('<title>OpenChamber</title>');
|
|
89
|
+
} finally {
|
|
90
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
60
93
|
});
|