@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/server/lib/llm/routes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createChatCompletion, LlmError } from './completions.js';
|
|
2
2
|
import { loadConnectedCatalog } from './catalog.js';
|
|
3
|
-
import {
|
|
3
|
+
import { OpenCode } from '@opencode/client';
|
|
4
4
|
import { ensureLlmTempDirectory } from './temp-directory.js';
|
|
5
5
|
|
|
6
6
|
const fail = (res, error) => {
|
|
@@ -11,14 +11,16 @@ const fail = (res, error) => {
|
|
|
11
11
|
? 400
|
|
12
12
|
: code === 'validation_error'
|
|
13
13
|
? 400
|
|
14
|
-
: code === '
|
|
14
|
+
: code === 'llm_attachment_generation_unavailable'
|
|
15
15
|
? 502
|
|
16
|
-
:
|
|
16
|
+
: code === 'upstream_error'
|
|
17
|
+
? 502
|
|
18
|
+
: 500;
|
|
17
19
|
res.status(status).json({ ok: false, error: code, message: error?.message || code });
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
export const registerLlmRoutes = (app, dependencies) => {
|
|
21
|
-
const client = () =>
|
|
23
|
+
const client = () => OpenCode.make({
|
|
22
24
|
baseUrl: dependencies.buildOpenCodeUrl('/', '').replace(/\/$/, ''),
|
|
23
25
|
headers: dependencies.getOpenCodeAuthHeaders(),
|
|
24
26
|
});
|
|
@@ -38,6 +40,8 @@ export const registerLlmRoutes = (app, dependencies) => {
|
|
|
38
40
|
getOpenCodeAuthHeaders: dependencies.getOpenCodeAuthHeaders,
|
|
39
41
|
clientFactory: client,
|
|
40
42
|
ensureTempDirectory: ensureLlmTempDirectory,
|
|
43
|
+
persistSessionMetadata: dependencies.persistSessionMetadata,
|
|
44
|
+
onSystemSessionPersisted: dependencies.onSystemSessionPersisted,
|
|
41
45
|
}))
|
|
42
46
|
.then(({ completion }) => {
|
|
43
47
|
res.status(200).json(completion);
|
|
@@ -44,12 +44,12 @@ Claim selection and update fence a scope with every `sending` row and every vali
|
|
|
44
44
|
|
|
45
45
|
The worker starts paused. Start, timer, and wake calls share one launched run flight; their public wake promise settles with status, while rejected flights emit a worker diagnostic and later wakes may retry. Stop aborts controllers and waits for every active task to settle before the runtime closes SQLite. Each run captures one runtime key and adapter runtime snapshot, **starts dispatch probe tasks first** (up to concurrency), then claims and awaits due reconciliation work, and finally joins active probes. That ordering keeps a hung `findMessage` (bounded by the 10s reconcile timeout) from delaying a waiting manual intent's POST while same-scope `sending` and same-row leases still prevent a second concurrent POST; automatic candidates remain gated on `available` + `idle` + `settled`. It reserves a queued candidate with an expiring probe token, and promotes that token only after the bounded eligibility probe permits dispatch. Stateless Assistant items deliberately bypass OpenCode eligibility and automatic admission tokens because their synthetic scope is not an execution Session; same-Assistant scope claiming still serializes them, while different Assistant scopes use normal worker concurrency. A successful stateless Assistant `promptAsync` admission first persists its user message in Assistant SQLite and advances the Assistant binding revision, then completes and deletes the queue row. The resulting Assistant revision tip refreshes clients that were still displaying the previous disposable binding. An ambiguous stateless delivery becomes a client-mutable `failed` row instead of an indefinitely active reconciliation row, and terminal rows do not block later dispatchable rows in their scope. Upgrade recovery also settles legacy stateless reconciliation rows without querying their disposable Session: null-error rows represent the old accepted path and complete, while rows carrying a transport error become failed. Busy, unsettled, timed-out, and unavailable candidates remain queued without row-version, scope-revision, or revision-tip churn. Every lease mutation carries `{ queueItemID, leaseToken, fenceGeneration, runtimeKey }`. `claimNext` returns `{ item, leaseToken, leaseExpiresAt, fenceGeneration }`; the item includes scope ID, directory, and session ID. The service receives the durable queue runtime identity, while the adapter receives an independently injected upstream OpenCode runtime snapshot.
|
|
46
46
|
|
|
47
|
-
OpenCode adapter (
|
|
47
|
+
OpenCode adapter (`@opencode/client` / v2) hardening:
|
|
48
48
|
|
|
49
|
-
- `
|
|
50
|
-
- `send`
|
|
51
|
-
-
|
|
52
|
-
- `findMessage`
|
|
49
|
+
- `send` builds the official SessionPrompt body only (`id` / `text` / `files` / `delivery`). Captured per-item `sendConfig` (`providerID`/`modelID`/`agent`/`variant`) is **not** stuffed into the prompt body — real prompt rejects that shape.
|
|
50
|
+
- At the session serial submit boundary, `send` applies config first: `POST .../agent` then `POST .../model` (model uses `{ id, providerID, variant? }`), then a single `POST .../prompt`. Switch failure or session busy returns **without** posting prompt (no wrong-model send / no double admission). Busy → `{ kind: 'retry', code: 'session_busy' }` (worker schedules retry; not marked accepted). Definitive switch 4xx → `{ kind: 'failed', code: 'config_switch_failed' }`.
|
|
51
|
+
- `send` treats only explicit 2xx (including empty 200/202/204) as `ok`. `undefined`/malformed results are never success. Prompt 409 → retry/`session_busy`; definitive other 4xx (except 408/429) is `failed`; 408/429/5xx and transport throws are `ambiguous`.
|
|
52
|
+
- `findMessage` uses inbox then `session.message` projection. Real client not-found is `_tag: MessageNotFoundError` / `SessionNotFoundError` (not only HTTP 404 status) → `found: false`. Projection is `await`ed so rejections stay inside the adapter catch (`unavailable`) instead of escaping. Non-404 failures stay `unavailable` without re-POSTing.
|
|
53
53
|
|
|
54
54
|
## Confirmation and reconciliation
|
|
55
55
|
|
|
@@ -1,24 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
|
|
2
2
|
import { createAscendingMessageID } from './message-id.js';
|
|
3
3
|
import { createSessionTurnGate } from './session-turn-gate.js';
|
|
4
4
|
|
|
5
5
|
// SDK 1.18 result shapes vary: success may be 2xx with empty body (200/202/204),
|
|
6
6
|
// failures may place status on response, error, or the top-level result.
|
|
7
|
-
const
|
|
8
|
-
|
|
7
|
+
const isSuccessStatus = (status) => Number.isInteger(status) && status >= 200 && status < 300;
|
|
8
|
+
const runtimeToken = (config, generation) => JSON.stringify([generation ?? null, config?.apiBaseUrl ?? config?.baseUrl ?? null]);
|
|
9
|
+
const messageIdentity = (message) => message?.info?.id ?? message?.id;
|
|
10
|
+
const inboxItemIdentity = (item) => item?.id ?? item?.info?.id ?? messageIdentity(item);
|
|
11
|
+
const httpStatus = (error) => {
|
|
12
|
+
for (const candidate of [error?.cause?.status, error?.status, error?.response?.status, error?.statusCode]) {
|
|
9
13
|
if (Number.isInteger(candidate)) return candidate;
|
|
10
14
|
}
|
|
11
15
|
return undefined;
|
|
12
16
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
if (!error) return false;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Real `@opencode/client` throws declared JSON with `_tag` (no status).
|
|
19
|
+
* MessageNotFoundError / SessionNotFoundError must map to found:false, not unavailable.
|
|
20
|
+
*/
|
|
21
|
+
const isNotFoundError = (error) => {
|
|
22
|
+
if (!error || typeof error !== 'object') return false;
|
|
23
|
+
const tag = error._tag ?? error.name;
|
|
24
|
+
if (tag === 'MessageNotFoundError' || tag === 'SessionNotFoundError') return true;
|
|
25
|
+
if (httpStatus(error) === 404) return true;
|
|
26
|
+
const code = error.code ?? error.type ?? error.error?.code ?? error.error?.type ?? error.error?._tag;
|
|
27
|
+
return code === 'not_found'
|
|
28
|
+
|| code === 'NotFound'
|
|
29
|
+
|| code === 'MessageNotFoundError'
|
|
30
|
+
|| code === 'SessionNotFoundError';
|
|
31
|
+
};
|
|
32
|
+
const isSessionBusyError = (error) => {
|
|
33
|
+
if (!error || typeof error !== 'object') return false;
|
|
34
|
+
if ((error._tag ?? error.name) === 'SessionBusyError') return true;
|
|
35
|
+
const code = error.code ?? error.type ?? error.error?._tag;
|
|
36
|
+
return code === 'session_busy' || code === 'SessionBusyError';
|
|
37
|
+
};
|
|
38
|
+
const isAbortError = (error) => (
|
|
39
|
+
error?.name === 'AbortError'
|
|
40
|
+
|| error?.code === 'aborted'
|
|
41
|
+
|| error?.cause?.name === 'AbortError'
|
|
42
|
+
);
|
|
43
|
+
const messageType = (message) => {
|
|
44
|
+
const info = message?.info ?? message;
|
|
45
|
+
if (info?.type === 'assistant' || info?.type === 'user') return info.type;
|
|
46
|
+
if (info?.role === 'assistant' || info?.role === 'user') return info.role;
|
|
47
|
+
return message ? 'unknown' : null;
|
|
22
48
|
};
|
|
23
49
|
|
|
24
50
|
export const createOpenCodeMessageQueueAdapter = ({
|
|
@@ -35,7 +61,7 @@ export const createOpenCodeMessageQueueAdapter = ({
|
|
|
35
61
|
} = {}) => {
|
|
36
62
|
const captureRuntime = () => { const config = getRuntimeConfig(); const generation = getRuntimeGeneration(); return { config: { ...config, apiBaseUrl: config?.apiBaseUrl ?? config?.baseUrl ?? buildOpenCodeUrl('/', ''), authHeaders: { ...getOpenCodeAuthHeaders() } }, generation, token: runtimeToken(config, generation) }; };
|
|
37
63
|
const isCurrent = (runtime) => !runtime || runtime.token === runtimeToken(getRuntimeConfig(), getRuntimeGeneration());
|
|
38
|
-
const client = (runtime) =>
|
|
64
|
+
const client = (runtime) => makeOpenCodeV2Client({ baseUrl: (runtime?.config?.apiBaseUrl ?? buildOpenCodeUrl('/', '')).replace(/\/$/, ''), authHeaders: runtime?.config?.authHeaders ?? getOpenCodeAuthHeaders() });
|
|
39
65
|
const turnKey = (scope, runtime) => JSON.stringify([runtime?.token ?? runtimeToken(getRuntimeConfig(), getRuntimeGeneration()), scope.directory, scope.sessionID]);
|
|
40
66
|
const checkEligibility = async (scope, runtime, { signal } = {}) => {
|
|
41
67
|
const key = turnKey(scope, runtime);
|
|
@@ -44,24 +70,25 @@ export const createOpenCodeMessageQueueAdapter = ({
|
|
|
44
70
|
return { available: false, idle: false, settled: false };
|
|
45
71
|
};
|
|
46
72
|
try {
|
|
47
|
-
const api = client(runtime);
|
|
48
|
-
const
|
|
73
|
+
const api = client(runtime);
|
|
74
|
+
const status = getSessionEligibility ? await getSessionEligibility(scope, { signal }) : await api.session.active({ signal });
|
|
75
|
+
const listed = getLatestMessageID ? null : await api.message.list({ sessionID: scope.sessionID, limit: 1, order: 'desc' }, { signal });
|
|
76
|
+
const messages = getLatestMessageID ? null : listed?.data;
|
|
49
77
|
const injectedStatus = getSessionEligibility && status && typeof status === 'object' && typeof status.idle === 'boolean' && typeof status.settled === 'boolean';
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
|
|
78
|
+
const activeMap = !getSessionEligibility && status && typeof status === 'object' && !Array.isArray(status);
|
|
79
|
+
if (!injectedStatus && !activeMap) return unavailable();
|
|
80
|
+
if (!getLatestMessageID && !Array.isArray(messages)) return unavailable();
|
|
81
|
+
const latest = Array.isArray(messages) ? messages[0] : null;
|
|
82
|
+
const latestMessageID = getLatestMessageID ? await getLatestMessageID(scope, { signal }) : latest?.id ?? latest?.info?.id;
|
|
53
83
|
if (latestMessageID !== undefined && latestMessageID !== null && typeof latestMessageID !== 'string') return unavailable();
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const statusValue = statusMap && typeof statusMap === 'object' && !Array.isArray(statusMap) ? statusMap[scope.sessionID] : statusMap;
|
|
57
|
-
const missingSessionStatus = statusMap && typeof statusMap === 'object' && !Array.isArray(statusMap) && !Object.hasOwn(statusMap, scope.sessionID);
|
|
58
|
-
const idle = getSessionEligibility ? status.idle : missingSessionStatus || statusValue?.type === 'idle' || statusValue?.status === 'idle' || status?.idle === true;
|
|
84
|
+
const lastInfo = latest?.info ?? latest;
|
|
85
|
+
const idle = getSessionEligibility ? status.idle : !Object.hasOwn(status, scope.sessionID);
|
|
59
86
|
if (getSessionEligibility) return { available: true, idle, settled: status?.settled === true, latestMessageID };
|
|
60
87
|
const settlement = turnGate.evaluate(key, {
|
|
61
88
|
available: true,
|
|
62
89
|
idle,
|
|
63
90
|
tailID: typeof lastInfo?.id === 'string' ? lastInfo.id : null,
|
|
64
|
-
tailRole:
|
|
91
|
+
tailRole: messageType(latest),
|
|
65
92
|
tailCompleted: Boolean(lastInfo?.time?.completed),
|
|
66
93
|
});
|
|
67
94
|
return { available: true, idle, settled: settlement.ready, latestMessageID, settlementReason: settlement.reason, ...(settlement.nextCheckAt === undefined ? {} : { nextCheckAt: settlement.nextCheckAt }) };
|
|
@@ -84,95 +111,156 @@ export const createOpenCodeMessageQueueAdapter = ({
|
|
|
84
111
|
return { type: 'file', mime: part.mime, url: file.url };
|
|
85
112
|
}));
|
|
86
113
|
};
|
|
114
|
+
const openCodeUrl = (pathname, directory, runtime) => {
|
|
115
|
+
const base = (runtime?.config?.apiBaseUrl ?? buildOpenCodeUrl('/', '')).replace(/\/$/, '');
|
|
116
|
+
const url = new URL(`${base}${pathname.startsWith('/') ? pathname : `/${pathname}`}`);
|
|
117
|
+
if (directory) url.searchParams.set('directory', directory);
|
|
118
|
+
return url;
|
|
119
|
+
};
|
|
120
|
+
const authHeaders = (runtime) => runtime?.config?.authHeaders ?? getOpenCodeAuthHeaders();
|
|
121
|
+
/**
|
|
122
|
+
* Official v2 SessionPrompt body: id/text/files/delivery only.
|
|
123
|
+
* model/agent/variant are NOT accepted on prompt — applied via
|
|
124
|
+
* session.switchAgent / session.switchModel at the serial submit boundary.
|
|
125
|
+
*/
|
|
126
|
+
const promptBodyFromContext = (context) => {
|
|
127
|
+
const parts = Array.isArray(context.parts) ? context.parts : [];
|
|
128
|
+
const text = typeof context.content === 'string' && context.content
|
|
129
|
+
? context.content
|
|
130
|
+
: parts.filter((part) => part?.type === 'text' && typeof part.text === 'string').map((part) => part.text).join('\n');
|
|
131
|
+
const files = parts
|
|
132
|
+
.filter((part) => part?.type === 'file' && typeof part.url === 'string')
|
|
133
|
+
.map((part) => ({
|
|
134
|
+
uri: part.url,
|
|
135
|
+
...(part.filename || part.name ? { name: part.filename || part.name } : {}),
|
|
136
|
+
...(part.mime ? { mime: part.mime } : {}),
|
|
137
|
+
}));
|
|
138
|
+
return {
|
|
139
|
+
id: context.messageID,
|
|
140
|
+
text,
|
|
141
|
+
delivery: context.delivery === 'queue' ? 'queue' : 'steer',
|
|
142
|
+
...(files.length ? { files } : {}),
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const classifyHttpStatus = (status, { ok } = {}) => {
|
|
146
|
+
if (ok || isSuccessStatus(status)) return { ok: true, status };
|
|
147
|
+
// 409 conflict / busy: definitive rejection before admission — retry later,
|
|
148
|
+
// do not mark accepted or re-POST as a new success path.
|
|
149
|
+
if (status === 409) return { ok: false, kind: 'retry', code: 'session_busy' };
|
|
150
|
+
if (status === 408 || status === 429 || (Number.isInteger(status) && status >= 500)) {
|
|
151
|
+
return { ok: false, status, kind: 'ambiguous' };
|
|
152
|
+
}
|
|
153
|
+
if (Number.isInteger(status) && status >= 400 && status < 500) {
|
|
154
|
+
return { ok: false, status, kind: 'failed' };
|
|
155
|
+
}
|
|
156
|
+
return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
|
|
157
|
+
};
|
|
158
|
+
const classifySwitchStatus = (status, { ok } = {}) => {
|
|
159
|
+
if (ok || isSuccessStatus(status)) return { ok: true, status };
|
|
160
|
+
if (status === 409) return { ok: false, kind: 'retry', code: 'session_busy' };
|
|
161
|
+
if (status === 408 || status === 429 || (Number.isInteger(status) && status >= 500)) {
|
|
162
|
+
return { ok: false, status, kind: 'ambiguous', code: 'config_switch_ambiguous' };
|
|
163
|
+
}
|
|
164
|
+
if (Number.isInteger(status) && status >= 400 && status < 500) {
|
|
165
|
+
return { ok: false, status, kind: 'failed', code: 'config_switch_failed' };
|
|
166
|
+
}
|
|
167
|
+
return { ok: false, kind: 'ambiguous', code: 'config_switch_ambiguous' };
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Apply captured per-item sendConfig before prompt on the session serial
|
|
171
|
+
* boundary. Failure returns without POSTing prompt (no accidental re-send
|
|
172
|
+
* under the wrong model/agent). Order: agent → model(+variant).
|
|
173
|
+
*/
|
|
174
|
+
const applySendConfig = async (sessionID, directory, runtime, sendConfig, { signal } = {}) => {
|
|
175
|
+
if (!sendConfig || typeof sendConfig !== 'object') return { ok: true };
|
|
176
|
+
const headers = { 'Content-Type': 'application/json', Accept: 'application/json', ...authHeaders(runtime) };
|
|
177
|
+
if (typeof sendConfig.agent === 'string' && sendConfig.agent) {
|
|
178
|
+
const response = await fetch(openCodeUrl(`/api/session/${encodeURIComponent(sessionID)}/agent`, directory, runtime), {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers,
|
|
181
|
+
body: JSON.stringify({ agent: sendConfig.agent }),
|
|
182
|
+
signal,
|
|
183
|
+
});
|
|
184
|
+
if (!response || typeof response !== 'object') {
|
|
185
|
+
return { ok: false, kind: 'ambiguous', code: 'config_switch_ambiguous' };
|
|
186
|
+
}
|
|
187
|
+
const classified = classifySwitchStatus(response.status, { ok: response.ok });
|
|
188
|
+
if (!classified.ok) return classified;
|
|
189
|
+
}
|
|
190
|
+
if (typeof sendConfig.providerID === 'string' && sendConfig.providerID
|
|
191
|
+
&& typeof sendConfig.modelID === 'string' && sendConfig.modelID) {
|
|
192
|
+
const model = {
|
|
193
|
+
id: sendConfig.modelID,
|
|
194
|
+
providerID: sendConfig.providerID,
|
|
195
|
+
...(typeof sendConfig.variant === 'string' && sendConfig.variant
|
|
196
|
+
? { variant: sendConfig.variant }
|
|
197
|
+
: {}),
|
|
198
|
+
};
|
|
199
|
+
const response = await fetch(openCodeUrl(`/api/session/${encodeURIComponent(sessionID)}/model`, directory, runtime), {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers,
|
|
202
|
+
body: JSON.stringify({ model }),
|
|
203
|
+
signal,
|
|
204
|
+
});
|
|
205
|
+
if (!response || typeof response !== 'object') {
|
|
206
|
+
return { ok: false, kind: 'ambiguous', code: 'config_switch_ambiguous' };
|
|
207
|
+
}
|
|
208
|
+
const classified = classifySwitchStatus(response.status, { ok: response.ok });
|
|
209
|
+
if (!classified.ok) return classified;
|
|
210
|
+
}
|
|
211
|
+
return { ok: true };
|
|
212
|
+
};
|
|
87
213
|
const send = async (context, { signal } = {}) => {
|
|
88
214
|
if (!isCurrent(context.runtime)) return { ok: false, kind: 'retry', code: 'runtime_stale' };
|
|
89
215
|
try {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
216
|
+
const parts = context.parts ?? await materializeAttachments(context, { signal });
|
|
217
|
+
const sessionID = context.scope?.sessionID ?? context.sessionID;
|
|
218
|
+
const directory = context.scope?.directory ?? context.directory;
|
|
219
|
+
const sendConfig = context.sendConfig && typeof context.sendConfig === 'object'
|
|
220
|
+
? context.sendConfig
|
|
221
|
+
: null;
|
|
222
|
+
// Serial session boundary: switch captured config, then prompt once.
|
|
223
|
+
if (sendConfig) {
|
|
224
|
+
const switched = await applySendConfig(sessionID, directory, context.runtime, sendConfig, { signal });
|
|
225
|
+
if (!switched.ok) return switched;
|
|
226
|
+
}
|
|
227
|
+
const response = await fetch(openCodeUrl(`/api/session/${encodeURIComponent(sessionID)}/prompt`, directory, context.runtime), {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...authHeaders(context.runtime) },
|
|
230
|
+
body: JSON.stringify(promptBodyFromContext({ ...context, parts })),
|
|
231
|
+
signal,
|
|
232
|
+
});
|
|
100
233
|
// Only an explicit 2xx (incl. empty 200/202/204) with no error is success.
|
|
101
234
|
// undefined/malformed results must not be treated as accepted POSTs.
|
|
102
|
-
if (!
|
|
103
|
-
|
|
104
|
-
if (result.error) {
|
|
105
|
-
return {
|
|
106
|
-
ok: false,
|
|
107
|
-
status,
|
|
108
|
-
kind: status === 408 || status === 429 || (Number.isInteger(status) && status >= 500) || status === undefined
|
|
109
|
-
? 'ambiguous'
|
|
110
|
-
: Number.isInteger(status) && status >= 400 && status < 500
|
|
111
|
-
? 'failed'
|
|
112
|
-
: 'ambiguous',
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
if (isSuccessStatus(status)) return { ok: true, status };
|
|
116
|
-
if (Number.isInteger(status) && status >= 400 && status < 500 && status !== 408 && status !== 429) {
|
|
117
|
-
return { ok: false, status, kind: 'failed' };
|
|
118
|
-
}
|
|
119
|
-
if (status === 408 || status === 429 || (Number.isInteger(status) && status >= 500)) {
|
|
120
|
-
return { ok: false, status, kind: 'ambiguous' };
|
|
121
|
-
}
|
|
122
|
-
// Missing status without error is ambiguous (empty/malformed transport), not ok.
|
|
123
|
-
return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
|
|
235
|
+
if (!response || typeof response !== 'object') return { ok: false, kind: 'ambiguous', code: 'malformed_result' };
|
|
236
|
+
return classifyHttpStatus(response.status, { ok: response.ok });
|
|
124
237
|
} catch (error) {
|
|
125
|
-
if (error
|
|
238
|
+
if (isAbortError(error)) return { ok: false, kind: 'ambiguous', code: 'aborted' };
|
|
239
|
+
if (isSessionBusyError(error)) return { ok: false, kind: 'retry', code: 'session_busy' };
|
|
126
240
|
return { ok: false, kind: 'ambiguous', code: 'transport' };
|
|
127
241
|
}
|
|
128
242
|
};
|
|
129
|
-
const
|
|
130
|
-
const result = await api.session.messages({ sessionID: scope.sessionID, directory: scope.directory, limit: 100 }, { signal });
|
|
131
|
-
if (result?.error) return { unavailable: true };
|
|
132
|
-
const found = (result?.data ?? []).some((message) => messageIdentity(message) === messageID);
|
|
133
|
-
if (found) return { found: true };
|
|
134
|
-
if (result?.data?.length >= 100 || result?.nextCursor || result?.hasMore) return { unavailable: true };
|
|
135
|
-
return { found: false };
|
|
136
|
-
};
|
|
137
|
-
const findViaLegacySessionMessage = async (api, scope, messageID, { signal } = {}) => {
|
|
138
|
-
const legacy = api?.session?.message;
|
|
139
|
-
if (typeof legacy !== 'function') return null;
|
|
243
|
+
const findViaInbox = async (scope, messageID, { signal, runtime } = {}) => {
|
|
140
244
|
try {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (status === 404) return { found: false };
|
|
145
|
-
if (isNotFoundOrUnsupported(status)) return null;
|
|
146
|
-
return { unavailable: true };
|
|
147
|
-
}
|
|
148
|
-
const record = result?.data ?? result;
|
|
149
|
-
const id = messageIdentity(record);
|
|
150
|
-
if (id === messageID || Boolean(record?.info ?? record?.id)) return { found: true };
|
|
151
|
-
return { found: false };
|
|
245
|
+
const list = await client(runtime).session.inbox.list({ sessionID: scope.sessionID }, { signal });
|
|
246
|
+
if (!Array.isArray(list)) throw Object.assign(new Error('upstream'), { code: 'upstream' });
|
|
247
|
+
return { found: list.some((item) => inboxItemIdentity(item) === messageID) };
|
|
152
248
|
} catch (error) {
|
|
153
|
-
if (
|
|
154
|
-
|
|
249
|
+
if (isNotFoundError(error)) return { found: false };
|
|
250
|
+
throw error;
|
|
155
251
|
}
|
|
156
252
|
};
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
if (typeof exact !== 'function') return { kind: 'unsupported' };
|
|
253
|
+
// Prefer client.v2.session.message when the 1.18 SDK surface exposes it.
|
|
254
|
+
// Ticket 12: after prompt admission, reconcile only asks inbox + projection.
|
|
255
|
+
const findViaProjection = async (scope, messageID, { signal, runtime } = {}) => {
|
|
161
256
|
try {
|
|
162
|
-
const
|
|
163
|
-
const status = safeStatus(result);
|
|
164
|
-
if (result?.error) {
|
|
165
|
-
if (status === 404) return { kind: 'result', value: { found: false } };
|
|
166
|
-
if (isNotFoundOrUnsupported(status)) return { kind: 'unsupported' };
|
|
167
|
-
return { kind: 'result', value: { unavailable: true } };
|
|
168
|
-
}
|
|
169
|
-
const record = result?.data ?? result;
|
|
257
|
+
const record = await client(runtime).session.message({ sessionID: scope.sessionID, messageID }, { signal });
|
|
170
258
|
const id = messageIdentity(record);
|
|
171
|
-
if (id === messageID || Boolean(record?.info ?? record?.id)) return {
|
|
172
|
-
return {
|
|
259
|
+
if (id === messageID || Boolean(record?.info ?? record?.id)) return { found: true };
|
|
260
|
+
return { found: false };
|
|
173
261
|
} catch (error) {
|
|
174
|
-
if (
|
|
175
|
-
|
|
262
|
+
if (isNotFoundError(error)) return { found: false };
|
|
263
|
+
throw error;
|
|
176
264
|
}
|
|
177
265
|
};
|
|
178
266
|
const findMessage = async (scope, messageID, { signal, runtime } = {}) => {
|
|
@@ -182,13 +270,12 @@ export const createOpenCodeMessageQueueAdapter = ({
|
|
|
182
270
|
if (exact?.unavailable) return { unavailable: true };
|
|
183
271
|
return { found: Boolean(exact?.found ?? exact?.data ?? exact?.id) };
|
|
184
272
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return findViaBoundedMessages(api, scope, messageID, { signal });
|
|
273
|
+
const inbox = await findViaInbox(scope, messageID, { signal, runtime });
|
|
274
|
+
if (inbox.unavailable) return { unavailable: true };
|
|
275
|
+
if (inbox.found) return { found: true };
|
|
276
|
+
// Must await so projection rejections stay inside this try/catch
|
|
277
|
+
// (bare return of a Promise lets rejections escape as unhandled).
|
|
278
|
+
return await findViaProjection(scope, messageID, { signal, runtime });
|
|
192
279
|
} catch { return { unavailable: true }; }
|
|
193
280
|
};
|
|
194
281
|
const observeSessionEvent = (scope, phase, runtime = captureRuntime()) => turnGate.observeEvent(turnKey(scope, runtime), phase);
|