@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
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { validateConversationInput } from './validation.js';
|
|
3
3
|
|
|
4
|
-
// Mock the
|
|
5
|
-
vi.mock('@opencode
|
|
6
|
-
|
|
4
|
+
// Mock the official v2 client before any imports
|
|
5
|
+
vi.mock('@opencode/client', () => ({
|
|
6
|
+
OpenCode: { make: vi.fn() },
|
|
7
7
|
}));
|
|
8
8
|
|
|
9
9
|
// Lazy import to allow vi.mock to take effect
|
|
10
10
|
const { createConversationsService } = await import('./service.js');
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const mockMake = (await import('@opencode/client')).OpenCode.make;
|
|
13
|
+
|
|
14
|
+
const clientError = (reason, cause) => {
|
|
15
|
+
const error = new Error(reason);
|
|
16
|
+
error.name = 'ClientError';
|
|
17
|
+
error.reason = reason;
|
|
18
|
+
if (cause !== undefined) error.cause = cause;
|
|
19
|
+
return error;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const httpError = (status, message) => {
|
|
23
|
+
const error = new Error(message);
|
|
24
|
+
error.status = status;
|
|
25
|
+
return error;
|
|
26
|
+
};
|
|
13
27
|
|
|
14
28
|
describe('conversations validation', () => {
|
|
15
29
|
it('rejects non-object body', () => {
|
|
@@ -270,7 +284,7 @@ describe('conversations service', () => {
|
|
|
270
284
|
mockBuildUrl.mockReturnValue('http://127.0.0.1:4096/');
|
|
271
285
|
mockGetHeaders.mockReturnValue({ Authorization: 'Basic test' });
|
|
272
286
|
mockWaitReady.mockResolvedValue(undefined);
|
|
273
|
-
|
|
287
|
+
mockMake.mockReset();
|
|
274
288
|
});
|
|
275
289
|
|
|
276
290
|
const baseInput = (overrides = {}) => ({
|
|
@@ -295,9 +309,9 @@ describe('conversations service', () => {
|
|
|
295
309
|
// --- create failures ---
|
|
296
310
|
|
|
297
311
|
it('returns create failure with safe error when SDK returns error', async () => {
|
|
298
|
-
|
|
312
|
+
mockMake.mockReturnValue({
|
|
299
313
|
session: {
|
|
300
|
-
create: async () =>
|
|
314
|
+
create: async () => { throw clientError('UnexpectedStatus', { status: 500 }); },
|
|
301
315
|
},
|
|
302
316
|
});
|
|
303
317
|
const service = createService();
|
|
@@ -311,7 +325,7 @@ describe('conversations service', () => {
|
|
|
311
325
|
});
|
|
312
326
|
|
|
313
327
|
it('returns create failure on transport error during create', async () => {
|
|
314
|
-
|
|
328
|
+
mockMake.mockReturnValue({
|
|
315
329
|
session: {
|
|
316
330
|
create: async () => { throw new Error('Connection refused'); },
|
|
317
331
|
},
|
|
@@ -325,15 +339,19 @@ describe('conversations service', () => {
|
|
|
325
339
|
|
|
326
340
|
// --- create: no agent/model ---
|
|
327
341
|
|
|
328
|
-
it('
|
|
329
|
-
|
|
342
|
+
it('passes agent/model/variant to session.create', async () => {
|
|
343
|
+
mockMake.mockReturnValue({
|
|
330
344
|
session: {
|
|
331
345
|
create: async (params) => {
|
|
332
|
-
expect(params.agent).
|
|
333
|
-
expect(params.model).
|
|
334
|
-
|
|
346
|
+
expect(params.agent).toBe('builder');
|
|
347
|
+
expect(params.model).toEqual({
|
|
348
|
+
id: 'gpt-4o',
|
|
349
|
+
providerID: 'openai',
|
|
350
|
+
variant: 'v1',
|
|
351
|
+
});
|
|
352
|
+
return { id: 'ses_ok' };
|
|
335
353
|
},
|
|
336
|
-
|
|
354
|
+
prompt: async () => undefined,
|
|
337
355
|
},
|
|
338
356
|
});
|
|
339
357
|
const service = createService();
|
|
@@ -343,42 +361,71 @@ describe('conversations service', () => {
|
|
|
343
361
|
expect(result.ok).toBe(true);
|
|
344
362
|
});
|
|
345
363
|
|
|
346
|
-
// ---
|
|
364
|
+
// --- prompt usage ---
|
|
347
365
|
|
|
348
|
-
it('uses
|
|
349
|
-
let
|
|
350
|
-
|
|
366
|
+
it('uses prompt with delivery=steer and does not cache message body', async () => {
|
|
367
|
+
let promptCalled = false;
|
|
368
|
+
mockMake.mockReturnValue({
|
|
351
369
|
session: {
|
|
352
|
-
create: async () => ({
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
expect(params.delivery).
|
|
356
|
-
return {
|
|
370
|
+
create: async () => ({ id: 'ses_pa' }),
|
|
371
|
+
prompt: async (params) => {
|
|
372
|
+
promptCalled = true;
|
|
373
|
+
expect(params.delivery).toBe('steer');
|
|
374
|
+
return {
|
|
375
|
+
id: 'msg_test',
|
|
376
|
+
sessionID: 'ses_pa',
|
|
377
|
+
type: 'user',
|
|
378
|
+
delivery: 'steer',
|
|
379
|
+
payload: { text: 'hello' },
|
|
380
|
+
timeCreated: 1,
|
|
381
|
+
};
|
|
357
382
|
},
|
|
358
383
|
},
|
|
359
384
|
});
|
|
360
385
|
const service = createService();
|
|
361
386
|
const result = await service.createAndPrompt({ sanitizedInput: baseInput() });
|
|
362
|
-
expect(
|
|
387
|
+
expect(promptCalled).toBe(true);
|
|
363
388
|
expect(result.ok).toBe(true);
|
|
389
|
+
expect(result.inbox).toEqual(expect.objectContaining({ id: 'msg_test', delivery: 'steer' }));
|
|
390
|
+
expect(result.parts).toBeUndefined();
|
|
391
|
+
expect(result.text).toBeUndefined();
|
|
364
392
|
});
|
|
365
393
|
|
|
366
394
|
// --- success ---
|
|
367
395
|
|
|
368
|
-
it('
|
|
396
|
+
it('validates and forwards skill ids with the first prompt', async () => {
|
|
397
|
+
const validated = validateConversationInput({
|
|
398
|
+
input: { type: 'prompt' },
|
|
399
|
+
...baseInput({ parts: [{ type: 'text', text: '[skill:release] prepare notes' }], skills: [{ id: 'release', text: 'untrusted body' }] }),
|
|
400
|
+
});
|
|
401
|
+
expect(validated.valid).toBe(true);
|
|
402
|
+
expect(validated.sanitized.skills).toEqual([{ id: 'release' }]);
|
|
403
|
+
const prompt = vi.fn(async () => undefined);
|
|
404
|
+
mockMake.mockReturnValue({ session: { create: async () => ({ id: 'ses_skill' }), prompt } });
|
|
405
|
+
const result = await createService().createAndPrompt({ sanitizedInput: validated.sanitized });
|
|
406
|
+
expect(result.ok).toBe(true);
|
|
407
|
+
expect(prompt.mock.calls[0][0]).toMatchObject({
|
|
408
|
+
text: '[skill:release] prepare notes', skills: [{ id: 'release', name: 'release' }], delivery: 'steer',
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('returns success when create + prompt succeed', async () => {
|
|
369
413
|
const sessionData = { id: 'ses_created', title: 'Test' };
|
|
370
|
-
|
|
414
|
+
mockMake.mockReturnValue({
|
|
371
415
|
session: {
|
|
372
416
|
create: async (params) => {
|
|
373
|
-
expect(params.
|
|
374
|
-
|
|
417
|
+
expect(params.location).toEqual({ directory: '/test' });
|
|
418
|
+
expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai' });
|
|
419
|
+
return sessionData;
|
|
375
420
|
},
|
|
376
|
-
|
|
421
|
+
prompt: async (params) => {
|
|
377
422
|
expect(params.sessionID).toBe('ses_created');
|
|
378
|
-
expect(params.
|
|
379
|
-
expect(params.
|
|
380
|
-
expect(params.
|
|
381
|
-
|
|
423
|
+
expect(params.id).toBe('msg_test');
|
|
424
|
+
expect(params.text).toBe('hello');
|
|
425
|
+
expect(params.delivery).toBe('steer');
|
|
426
|
+
expect(params.model).toBeUndefined();
|
|
427
|
+
expect(params.parts).toBeUndefined();
|
|
428
|
+
return undefined;
|
|
382
429
|
},
|
|
383
430
|
},
|
|
384
431
|
});
|
|
@@ -390,14 +437,19 @@ describe('conversations service', () => {
|
|
|
390
437
|
expect(mockMarkSent).toHaveBeenCalledWith('ses_created');
|
|
391
438
|
});
|
|
392
439
|
|
|
393
|
-
it('passes agent, variant to
|
|
394
|
-
|
|
440
|
+
it('passes agent, variant to session.create and not prompt', async () => {
|
|
441
|
+
mockMake.mockReturnValue({
|
|
395
442
|
session: {
|
|
396
|
-
create: async () =>
|
|
397
|
-
promptAsync: async (params) => {
|
|
443
|
+
create: async (params) => {
|
|
398
444
|
expect(params.agent).toBe('builder');
|
|
399
|
-
expect(params.
|
|
400
|
-
return {
|
|
445
|
+
expect(params.model).toEqual({ id: 'gpt-4o', providerID: 'openai', variant: 'fast' });
|
|
446
|
+
return { id: 'ses_avd' };
|
|
447
|
+
},
|
|
448
|
+
prompt: async (params) => {
|
|
449
|
+
expect(params.agent).toBeUndefined();
|
|
450
|
+
expect(params.variant).toBeUndefined();
|
|
451
|
+
expect(params.delivery).toBe('steer');
|
|
452
|
+
return undefined;
|
|
401
453
|
},
|
|
402
454
|
},
|
|
403
455
|
});
|
|
@@ -412,14 +464,10 @@ describe('conversations service', () => {
|
|
|
412
464
|
|
|
413
465
|
it('returns prompt failure with ambiguous=false for 400 SDK error, safe error, no mark', async () => {
|
|
414
466
|
const sessionData = { id: 'ses_400' };
|
|
415
|
-
|
|
467
|
+
mockMake.mockReturnValue({
|
|
416
468
|
session: {
|
|
417
|
-
create: async () =>
|
|
418
|
-
|
|
419
|
-
data: null,
|
|
420
|
-
error: new Error('path /foo/bar does not exist'),
|
|
421
|
-
response: { status: 400 },
|
|
422
|
-
}),
|
|
469
|
+
create: async () => sessionData,
|
|
470
|
+
prompt: async () => { throw httpError(400, 'path /foo/bar does not exist'); },
|
|
423
471
|
},
|
|
424
472
|
});
|
|
425
473
|
const service = createService();
|
|
@@ -433,14 +481,10 @@ describe('conversations service', () => {
|
|
|
433
481
|
});
|
|
434
482
|
|
|
435
483
|
it('returns prompt failure with ambiguous=false for 404, no mark', async () => {
|
|
436
|
-
|
|
484
|
+
mockMake.mockReturnValue({
|
|
437
485
|
session: {
|
|
438
|
-
create: async () => ({
|
|
439
|
-
|
|
440
|
-
data: null,
|
|
441
|
-
error: new Error('Session not found'),
|
|
442
|
-
response: { status: 404 },
|
|
443
|
-
}),
|
|
486
|
+
create: async () => ({ id: 'ses_404' }),
|
|
487
|
+
prompt: async () => { throw httpError(404, 'Session not found'); },
|
|
444
488
|
},
|
|
445
489
|
});
|
|
446
490
|
const service = createService();
|
|
@@ -456,14 +500,10 @@ describe('conversations service', () => {
|
|
|
456
500
|
|
|
457
501
|
it('returns ambiguous=true for 503, safe error, marks activity', async () => {
|
|
458
502
|
const sessionData = { id: 'ses_503' };
|
|
459
|
-
|
|
503
|
+
mockMake.mockReturnValue({
|
|
460
504
|
session: {
|
|
461
|
-
create: async () =>
|
|
462
|
-
|
|
463
|
-
data: null,
|
|
464
|
-
error: new Error('Service Unavailable'),
|
|
465
|
-
response: { status: 503 },
|
|
466
|
-
}),
|
|
505
|
+
create: async () => sessionData,
|
|
506
|
+
prompt: async () => { throw clientError('UnexpectedStatus', { status: 503 }); },
|
|
467
507
|
},
|
|
468
508
|
});
|
|
469
509
|
const service = createService();
|
|
@@ -477,10 +517,10 @@ describe('conversations service', () => {
|
|
|
477
517
|
});
|
|
478
518
|
|
|
479
519
|
it('returns ambiguous=true for transport throw, marks activity', async () => {
|
|
480
|
-
|
|
520
|
+
mockMake.mockReturnValue({
|
|
481
521
|
session: {
|
|
482
|
-
create: async () => ({
|
|
483
|
-
|
|
522
|
+
create: async () => ({ id: 'ses_transport' }),
|
|
523
|
+
prompt: async () => { throw new TypeError('fetch failed'); },
|
|
484
524
|
},
|
|
485
525
|
});
|
|
486
526
|
const service = createService();
|
|
@@ -493,14 +533,10 @@ describe('conversations service', () => {
|
|
|
493
533
|
});
|
|
494
534
|
|
|
495
535
|
it('returns ambiguous=true for 408, marks activity', async () => {
|
|
496
|
-
|
|
536
|
+
mockMake.mockReturnValue({
|
|
497
537
|
session: {
|
|
498
|
-
create: async () => ({
|
|
499
|
-
|
|
500
|
-
data: null,
|
|
501
|
-
error: new Error('Request Timeout'),
|
|
502
|
-
response: { status: 408 },
|
|
503
|
-
}),
|
|
538
|
+
create: async () => ({ id: 'ses_408' }),
|
|
539
|
+
prompt: async () => { throw clientError('UnexpectedStatus', { status: 408 }); },
|
|
504
540
|
},
|
|
505
541
|
});
|
|
506
542
|
const service = createService();
|
|
@@ -511,14 +547,10 @@ describe('conversations service', () => {
|
|
|
511
547
|
});
|
|
512
548
|
|
|
513
549
|
it('returns ambiguous=true for 429, marks activity', async () => {
|
|
514
|
-
|
|
550
|
+
mockMake.mockReturnValue({
|
|
515
551
|
session: {
|
|
516
|
-
create: async () => ({
|
|
517
|
-
|
|
518
|
-
data: null,
|
|
519
|
-
error: new Error('Too Many Requests'),
|
|
520
|
-
response: { status: 429 },
|
|
521
|
-
}),
|
|
552
|
+
create: async () => ({ id: 'ses_429' }),
|
|
553
|
+
prompt: async () => { throw clientError('UnexpectedStatus', { status: 429 }); },
|
|
522
554
|
},
|
|
523
555
|
});
|
|
524
556
|
const service = createService();
|
|
@@ -529,10 +561,10 @@ describe('conversations service', () => {
|
|
|
529
561
|
});
|
|
530
562
|
|
|
531
563
|
it('does NOT mark on non-transport, non-ambiguous throw', async () => {
|
|
532
|
-
|
|
564
|
+
mockMake.mockReturnValue({
|
|
533
565
|
session: {
|
|
534
|
-
create: async () => ({
|
|
535
|
-
|
|
566
|
+
create: async () => ({ id: 'ses_perm' }),
|
|
567
|
+
prompt: async () => { throw new Error('Some permanent failure'); },
|
|
536
568
|
},
|
|
537
569
|
});
|
|
538
570
|
const service = createService();
|
|
@@ -552,10 +584,10 @@ describe('conversations service', () => {
|
|
|
552
584
|
markUserMessageSent: throwingMark,
|
|
553
585
|
waitForOpenCodeReady: mockWaitReady,
|
|
554
586
|
});
|
|
555
|
-
|
|
587
|
+
mockMake.mockReturnValue({
|
|
556
588
|
session: {
|
|
557
|
-
create: async () => ({
|
|
558
|
-
|
|
589
|
+
create: async () => ({ id: 'ses_safe' }),
|
|
590
|
+
prompt: async () => undefined,
|
|
559
591
|
},
|
|
560
592
|
});
|
|
561
593
|
const result = await svc.createAndPrompt({ sanitizedInput: baseInput() });
|
|
@@ -568,13 +600,13 @@ describe('conversations service', () => {
|
|
|
568
600
|
it('passes directory/headers through buildOpenCodeUrl and getOpenCodeAuthHeaders', async () => {
|
|
569
601
|
mockBuildUrl.mockReturnValue('http://custom:9999/');
|
|
570
602
|
mockGetHeaders.mockReturnValue({ Authorization: 'Bearer secret123' });
|
|
571
|
-
|
|
603
|
+
mockMake.mockImplementation((config) => {
|
|
572
604
|
expect(config.baseUrl).toBe('http://custom:9999');
|
|
573
605
|
expect(config.headers).toEqual({ Authorization: 'Bearer secret123' });
|
|
574
606
|
return {
|
|
575
607
|
session: {
|
|
576
|
-
create: async () => ({
|
|
577
|
-
|
|
608
|
+
create: async () => ({ id: 'ses_url' }),
|
|
609
|
+
prompt: async () => undefined,
|
|
578
610
|
},
|
|
579
611
|
};
|
|
580
612
|
});
|
|
@@ -585,10 +617,10 @@ describe('conversations service', () => {
|
|
|
585
617
|
|
|
586
618
|
it('awaits waitForOpenCodeReady before calling SDK', async () => {
|
|
587
619
|
let createCalled = false;
|
|
588
|
-
|
|
620
|
+
mockMake.mockReturnValue({
|
|
589
621
|
session: {
|
|
590
|
-
create: async () => { createCalled = true; return {
|
|
591
|
-
|
|
622
|
+
create: async () => { createCalled = true; return { id: 'ses_w' }; },
|
|
623
|
+
prompt: async () => undefined,
|
|
592
624
|
},
|
|
593
625
|
});
|
|
594
626
|
const service = createService();
|
|
@@ -600,16 +632,16 @@ describe('conversations service', () => {
|
|
|
600
632
|
// --- internal timeouts ---
|
|
601
633
|
|
|
602
634
|
it('uses internal timeout signal for session.create', async () => {
|
|
603
|
-
|
|
635
|
+
mockMake.mockReturnValue({
|
|
604
636
|
session: {
|
|
605
637
|
create: async (_params, opts) => {
|
|
606
638
|
expect(opts.signal).toBeDefined();
|
|
607
639
|
expect(opts.signal.aborted).toBe(false);
|
|
608
|
-
return {
|
|
640
|
+
return { id: 'ses_to' };
|
|
609
641
|
},
|
|
610
|
-
|
|
642
|
+
prompt: async (_params, opts) => {
|
|
611
643
|
expect(opts.signal).toBeDefined();
|
|
612
|
-
return
|
|
644
|
+
return undefined;
|
|
613
645
|
},
|
|
614
646
|
},
|
|
615
647
|
});
|
|
@@ -619,7 +651,7 @@ describe('conversations service', () => {
|
|
|
619
651
|
});
|
|
620
652
|
|
|
621
653
|
it('maps AbortError from create to create failure', async () => {
|
|
622
|
-
|
|
654
|
+
mockMake.mockReturnValue({
|
|
623
655
|
session: {
|
|
624
656
|
create: async () => { throw new DOMException('Aborted', 'AbortError'); },
|
|
625
657
|
},
|
|
@@ -631,11 +663,11 @@ describe('conversations service', () => {
|
|
|
631
663
|
expect(result.error).toBe('Failed to create session');
|
|
632
664
|
});
|
|
633
665
|
|
|
634
|
-
it('maps AbortError from
|
|
635
|
-
|
|
666
|
+
it('maps AbortError from prompt to ambiguous=true', async () => {
|
|
667
|
+
mockMake.mockReturnValue({
|
|
636
668
|
session: {
|
|
637
|
-
create: async () => ({
|
|
638
|
-
|
|
669
|
+
create: async () => ({ id: 'ses_pto' }),
|
|
670
|
+
prompt: async () => { throw new DOMException('Aborted', 'AbortError'); },
|
|
639
671
|
},
|
|
640
672
|
});
|
|
641
673
|
const service = createService();
|
|
@@ -649,9 +681,9 @@ describe('conversations service', () => {
|
|
|
649
681
|
// --- create failure no session ID ---
|
|
650
682
|
|
|
651
683
|
it('returns create failure when no session ID in response', async () => {
|
|
652
|
-
|
|
684
|
+
mockMake.mockReturnValue({
|
|
653
685
|
session: {
|
|
654
|
-
create: async () =>
|
|
686
|
+
create: async () => null,
|
|
655
687
|
},
|
|
656
688
|
});
|
|
657
689
|
const service = createService();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const ALLOWED_TOP_LEVEL_KEYS = new Set([
|
|
2
2
|
'input', 'directory', 'messageID', 'model', 'parts',
|
|
3
|
-
'title', 'parentID', 'agent', 'variant', 'metadata',
|
|
3
|
+
'title', 'parentID', 'agent', 'variant', 'metadata', 'skills',
|
|
4
4
|
]);
|
|
5
5
|
const ALLOWED_PART_TYPES = new Set(['text', 'file', 'agent']);
|
|
6
6
|
|
|
@@ -139,6 +139,15 @@ export const validateConversationInput = (body) => {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
// Optional fields
|
|
142
|
+
let skills;
|
|
143
|
+
if (body.skills !== undefined) {
|
|
144
|
+
if (!Array.isArray(body.skills) || body.skills.some((skill) =>
|
|
145
|
+
!skill || typeof skill.id !== 'string' || !skill.id.trim())) {
|
|
146
|
+
errors.push('skills must be an array of non-empty skill ids');
|
|
147
|
+
} else {
|
|
148
|
+
skills = body.skills.map((skill) => ({ id: skill.id }));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
142
151
|
const title = asOptionalNonEmptyString(body.title);
|
|
143
152
|
if (title === null) errors.push('title must be a non-empty string if provided');
|
|
144
153
|
|
|
@@ -168,6 +177,7 @@ export const validateConversationInput = (body) => {
|
|
|
168
177
|
modelID: body.model.modelID.trim(),
|
|
169
178
|
},
|
|
170
179
|
parts: sanitizedParts,
|
|
180
|
+
...(skills?.length ? { skills } : {}),
|
|
171
181
|
title,
|
|
172
182
|
parentID,
|
|
173
183
|
agent,
|
|
@@ -21,6 +21,8 @@ export function acceptDirectoryMessageStreamWsConnection({
|
|
|
21
21
|
buildOpenCodeUrl,
|
|
22
22
|
getOpenCodeAuthHeaders,
|
|
23
23
|
processForwardedEventPayload,
|
|
24
|
+
/** Optional Host archive/metadata projection for session lifecycle events. */
|
|
25
|
+
projectOutboundSessionPayload = null,
|
|
24
26
|
wsClients,
|
|
25
27
|
triggerHealthCheck,
|
|
26
28
|
heartbeatIntervalMs,
|
|
@@ -78,14 +80,23 @@ export function acceptDirectoryMessageStreamWsConnection({
|
|
|
78
80
|
const run = async () => {
|
|
79
81
|
const forwardEvent = ({ envelope, payload }) => {
|
|
80
82
|
const directory = requestedDirectory || envelope?.directory || 'global';
|
|
83
|
+
let outbound = payload;
|
|
84
|
+
if (typeof projectOutboundSessionPayload === 'function') {
|
|
85
|
+
try {
|
|
86
|
+
const projected = projectOutboundSessionPayload(payload);
|
|
87
|
+
if (projected != null) outbound = projected;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.warn('[event-stream] directory session projection failed:', error?.message ?? error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
81
92
|
|
|
82
|
-
sendMessageStreamWsEvent(socket,
|
|
93
|
+
sendMessageStreamWsEvent(socket, outbound, {
|
|
83
94
|
directory,
|
|
84
95
|
eventId: typeof envelope?.eventId === 'string' && envelope.eventId.length > 0 ? envelope.eventId : undefined,
|
|
85
96
|
reasoningFilter,
|
|
86
97
|
});
|
|
87
98
|
|
|
88
|
-
processForwardedEventPayload(
|
|
99
|
+
processForwardedEventPayload(outbound, (syntheticPayload) => {
|
|
89
100
|
sendMessageStreamWsEvent(socket, syntheticPayload, {
|
|
90
101
|
directory: 'global',
|
|
91
102
|
reasoningFilter,
|
|
@@ -64,6 +64,13 @@ export function createGlobalMessageStreamHub({
|
|
|
64
64
|
upstreamReconnectDelayMs,
|
|
65
65
|
replayLimit = MESSAGE_STREAM_GLOBAL_REPLAY_LIMIT,
|
|
66
66
|
getRuntimeIdentity = null,
|
|
67
|
+
/**
|
|
68
|
+
* Optional Host authority projection for session.created / session.updated
|
|
69
|
+
* (and GlobalEvent wrap). Applied before fan-out and replay so browser and
|
|
70
|
+
* server subscribers share the same archive view. Must be sync and cheap.
|
|
71
|
+
* @type {((payload: unknown) => unknown) | null}
|
|
72
|
+
*/
|
|
73
|
+
projectOutboundSessionPayload = null,
|
|
67
74
|
}) {
|
|
68
75
|
const eventSubscribers = new Set();
|
|
69
76
|
const statusSubscribers = new Set();
|
|
@@ -97,12 +104,19 @@ export function createGlobalMessageStreamHub({
|
|
|
97
104
|
}
|
|
98
105
|
};
|
|
99
106
|
|
|
107
|
+
let sessionPayloadProjector = typeof projectOutboundSessionPayload === 'function'
|
|
108
|
+
? projectOutboundSessionPayload
|
|
109
|
+
: null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Diff-summarize only. Host archive projection is applied at fan-out and
|
|
113
|
+
* replayAfter time against the *current* committed store — never baked into
|
|
114
|
+
* the replay buffer (stale archive stamps must not stick after Host writes).
|
|
115
|
+
*/
|
|
100
116
|
const normalizeEvent = ({ envelope, payload, connectionContext }) => {
|
|
101
117
|
const directory =
|
|
102
118
|
typeof envelope?.directory === 'string' && envelope.directory.length > 0 ? envelope.directory : 'global';
|
|
103
119
|
const eventId = typeof envelope?.eventId === 'string' && envelope.eventId.length > 0 ? envelope.eventId : undefined;
|
|
104
|
-
// Summarize FileDiff bodies before fan-out and replay buffering so reconnect
|
|
105
|
-
// replay cannot re-amplify full patch frames into >64KB WS payloads.
|
|
106
120
|
const outboundPayload = summarizeOutboundEventPayload(payload);
|
|
107
121
|
return {
|
|
108
122
|
envelope,
|
|
@@ -113,6 +127,19 @@ export function createGlobalMessageStreamHub({
|
|
|
113
127
|
};
|
|
114
128
|
};
|
|
115
129
|
|
|
130
|
+
const projectForOutbound = (payload) => {
|
|
131
|
+
if (typeof sessionPayloadProjector !== 'function') return payload;
|
|
132
|
+
try {
|
|
133
|
+
const projected = sessionPayloadProjector(payload);
|
|
134
|
+
// null = Host not ready; suppress this lifecycle frame.
|
|
135
|
+
if (projected == null) return null;
|
|
136
|
+
return projected;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn('[event-stream] session payload projection failed:', error?.message ?? error);
|
|
139
|
+
return payload;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
116
143
|
const start = () => {
|
|
117
144
|
if (reader) {
|
|
118
145
|
return;
|
|
@@ -127,7 +154,8 @@ export function createGlobalMessageStreamHub({
|
|
|
127
154
|
buildUrl: () => {
|
|
128
155
|
buildUrlFailed = false;
|
|
129
156
|
try {
|
|
130
|
-
|
|
157
|
+
// v2 serves the event stream at `/api/event`; `/api/global/event` is gone.
|
|
158
|
+
return new URL(buildOpenCodeUrl('/event', ''));
|
|
131
159
|
} catch {
|
|
132
160
|
buildUrlFailed = true;
|
|
133
161
|
throw new Error('OpenCode service unavailable');
|
|
@@ -160,6 +188,7 @@ export function createGlobalMessageStreamHub({
|
|
|
160
188
|
...event,
|
|
161
189
|
connectionContext: event?.connectionContext ?? activeConnectionRuntimeIdentity,
|
|
162
190
|
});
|
|
191
|
+
// Buffer the unprojected payload so reconnect replay uses current Host authority.
|
|
163
192
|
if (normalized.eventId) {
|
|
164
193
|
replay.push(normalized);
|
|
165
194
|
if (replay.length > replayLimit) {
|
|
@@ -167,8 +196,14 @@ export function createGlobalMessageStreamHub({
|
|
|
167
196
|
}
|
|
168
197
|
}
|
|
169
198
|
|
|
199
|
+
const projectedPayload = projectForOutbound(normalized.payload);
|
|
200
|
+
if (projectedPayload == null) return;
|
|
201
|
+
const outbound = projectedPayload === normalized.payload
|
|
202
|
+
? normalized
|
|
203
|
+
: { ...normalized, payload: projectedPayload };
|
|
204
|
+
|
|
170
205
|
for (const subscriber of Array.from(eventSubscribers)) {
|
|
171
|
-
notifySubscriber('event', subscriber,
|
|
206
|
+
notifySubscriber('event', subscriber, outbound);
|
|
172
207
|
}
|
|
173
208
|
},
|
|
174
209
|
onError(error) {
|
|
@@ -226,10 +261,21 @@ export function createGlobalMessageStreamHub({
|
|
|
226
261
|
}
|
|
227
262
|
|
|
228
263
|
const index = replay.findIndex((entry) => entry.eventId === eventId);
|
|
229
|
-
|
|
264
|
+
if (index === -1) return [];
|
|
265
|
+
// Re-project with current committed Host authority at replay time.
|
|
266
|
+
const out = [];
|
|
267
|
+
for (const entry of replay.slice(index + 1)) {
|
|
268
|
+
const projectedPayload = projectForOutbound(entry.payload);
|
|
269
|
+
if (projectedPayload == null) continue;
|
|
270
|
+
out.push(projectedPayload === entry.payload ? entry : { ...entry, payload: projectedPayload });
|
|
271
|
+
}
|
|
272
|
+
return out;
|
|
230
273
|
},
|
|
231
274
|
setRuntimeIdentityProvider(provider) {
|
|
232
275
|
runtimeIdentityProvider = typeof provider === 'function' ? provider : null;
|
|
233
276
|
},
|
|
277
|
+
setSessionPayloadProjector(projector) {
|
|
278
|
+
sessionPayloadProjector = typeof projector === 'function' ? projector : null;
|
|
279
|
+
},
|
|
234
280
|
};
|
|
235
281
|
}
|