@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,157 @@
|
|
|
1
|
+
export const OPENCODE_V1_MIGRATION_PATH = '/api/experimental/migration/v1';
|
|
2
|
+
|
|
3
|
+
// Backfill notes must stay readable by UI and docs. Do not drop them when the
|
|
4
|
+
// gate blocks or later admits a migrated library.
|
|
5
|
+
export const V1_MIGRATION_USER_NOTICE = [
|
|
6
|
+
'V1 history is backfilled in the opencode2 process.',
|
|
7
|
+
'Message ids are reused.',
|
|
8
|
+
'In-progress tools become interrupted.',
|
|
9
|
+
'V1 subtasks do not appear in v2.',
|
|
10
|
+
].join(' ');
|
|
11
|
+
|
|
12
|
+
const MIGRATION_PHASES = new Set(['required', 'running', 'completed', 'error']);
|
|
13
|
+
|
|
14
|
+
const stringifyError = (error) => {
|
|
15
|
+
if (error instanceof Error && error.message) {
|
|
16
|
+
return error.message;
|
|
17
|
+
}
|
|
18
|
+
if (typeof error === 'string' && error.trim()) {
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
return 'V1 migration status request failed';
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const blocked = (phase, extra = {}) => ({
|
|
25
|
+
admitTranscript: false,
|
|
26
|
+
phase,
|
|
27
|
+
userNotice: V1_MIGRATION_USER_NOTICE,
|
|
28
|
+
...extra,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const admitted = (phase, extra = {}) => ({
|
|
32
|
+
admitTranscript: true,
|
|
33
|
+
phase,
|
|
34
|
+
...extra,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const normalizeProgress = (progress) => {
|
|
38
|
+
if (!progress || typeof progress !== 'object' || Array.isArray(progress)) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const result = {
|
|
42
|
+
label: typeof progress.label === 'string' ? progress.label : '',
|
|
43
|
+
};
|
|
44
|
+
if (Number.isFinite(progress.numerator) && progress.numerator >= 0) {
|
|
45
|
+
result.numerator = progress.numerator;
|
|
46
|
+
}
|
|
47
|
+
if (Number.isFinite(progress.denominator) && progress.denominator >= 0) {
|
|
48
|
+
result.denominator = progress.denominator;
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const resolveHttpStatus = (input) => {
|
|
54
|
+
if (typeof input.httpStatus === 'number') {
|
|
55
|
+
return input.httpStatus;
|
|
56
|
+
}
|
|
57
|
+
if (typeof input.status === 'number') {
|
|
58
|
+
return input.status;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const resolveBody = (input) => {
|
|
64
|
+
if (input.body !== undefined) {
|
|
65
|
+
return input.body;
|
|
66
|
+
}
|
|
67
|
+
if (typeof input.status === 'string') {
|
|
68
|
+
return input;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const resolveTransportError = (input) => {
|
|
74
|
+
if (input instanceof Error || typeof input === 'string') {
|
|
75
|
+
return input;
|
|
76
|
+
}
|
|
77
|
+
if (input?.error instanceof Error || typeof input?.error === 'string') {
|
|
78
|
+
if (input.body === undefined && typeof input.status !== 'string') {
|
|
79
|
+
return input.error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Pure admission decision for GET /api/experimental/migration/v1.
|
|
86
|
+
// required/running/error never become an empty-list success.
|
|
87
|
+
export const evaluateV1MigrationGate = (input = {}) => {
|
|
88
|
+
if (input == null) {
|
|
89
|
+
return blocked('error', { error: 'V1 migration status is missing or invalid' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const transportError = resolveTransportError(input);
|
|
93
|
+
if (transportError) {
|
|
94
|
+
return blocked('error', { error: stringifyError(transportError) });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const httpStatus = resolveHttpStatus(input);
|
|
98
|
+
if (httpStatus === 404) {
|
|
99
|
+
return admitted('absent');
|
|
100
|
+
}
|
|
101
|
+
if (Number.isInteger(httpStatus) && httpStatus >= 400) {
|
|
102
|
+
return blocked('error', { error: `V1 migration status HTTP ${httpStatus}` });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const body = resolveBody(input);
|
|
106
|
+
const phase = body && typeof body === 'object' ? body.status : undefined;
|
|
107
|
+
|
|
108
|
+
if (phase === 'required') {
|
|
109
|
+
return blocked('required');
|
|
110
|
+
}
|
|
111
|
+
if (phase === 'running') {
|
|
112
|
+
return blocked('running', { progress: normalizeProgress(body.progress) });
|
|
113
|
+
}
|
|
114
|
+
if (phase === 'completed') {
|
|
115
|
+
return admitted('completed', { userNotice: V1_MIGRATION_USER_NOTICE });
|
|
116
|
+
}
|
|
117
|
+
if (phase === 'error') {
|
|
118
|
+
return blocked('error', {
|
|
119
|
+
error: typeof body.error === 'string' && body.error.trim()
|
|
120
|
+
? body.error
|
|
121
|
+
: 'V1 migration failed',
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (MIGRATION_PHASES.has(phase) === false) {
|
|
126
|
+
return blocked('error', { error: 'V1 migration status is missing or invalid' });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return blocked('error', { error: 'V1 migration status is missing or invalid' });
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Official protocol only publishes GET status. Backfill is owned by opencode2;
|
|
133
|
+
// clients poll this helper and do not POST unless a later API requires it.
|
|
134
|
+
export const fetchV1MigrationGate = async ({
|
|
135
|
+
url,
|
|
136
|
+
headers = {},
|
|
137
|
+
signal,
|
|
138
|
+
fetchImpl = globalThis.fetch,
|
|
139
|
+
} = {}) => {
|
|
140
|
+
try {
|
|
141
|
+
const response = await fetchImpl(url, {
|
|
142
|
+
method: 'GET',
|
|
143
|
+
headers: {
|
|
144
|
+
Accept: 'application/json',
|
|
145
|
+
...headers,
|
|
146
|
+
},
|
|
147
|
+
signal,
|
|
148
|
+
});
|
|
149
|
+
const body = await response.json().catch(() => null);
|
|
150
|
+
return evaluateV1MigrationGate({
|
|
151
|
+
httpStatus: response.status,
|
|
152
|
+
body,
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return evaluateV1MigrationGate({ error });
|
|
156
|
+
}
|
|
157
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
OPENCODE_V1_MIGRATION_PATH,
|
|
5
|
+
V1_MIGRATION_USER_NOTICE,
|
|
6
|
+
evaluateV1MigrationGate,
|
|
7
|
+
fetchV1MigrationGate,
|
|
8
|
+
} from './v1-migration-gate.js';
|
|
9
|
+
|
|
10
|
+
describe('V1 migration gate', () => {
|
|
11
|
+
it('denies transcript while V1 migration is required', () => {
|
|
12
|
+
expect(evaluateV1MigrationGate({ status: 'required' })).toMatchObject({
|
|
13
|
+
admitTranscript: false,
|
|
14
|
+
phase: 'required',
|
|
15
|
+
userNotice: V1_MIGRATION_USER_NOTICE,
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('denies transcript while V1 migration is running and keeps progress', () => {
|
|
20
|
+
expect(evaluateV1MigrationGate({
|
|
21
|
+
status: 'running',
|
|
22
|
+
progress: { label: 'Sessions', numerator: 3, denominator: 10 },
|
|
23
|
+
})).toEqual({
|
|
24
|
+
admitTranscript: false,
|
|
25
|
+
phase: 'running',
|
|
26
|
+
progress: { label: 'Sessions', numerator: 3, denominator: 10 },
|
|
27
|
+
userNotice: V1_MIGRATION_USER_NOTICE,
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('admits transcript when V1 migration is completed', () => {
|
|
32
|
+
expect(evaluateV1MigrationGate({ status: 'completed' })).toMatchObject({
|
|
33
|
+
admitTranscript: true,
|
|
34
|
+
phase: 'completed',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('admits transcript when V1 library is absent (HTTP 404)', () => {
|
|
39
|
+
expect(evaluateV1MigrationGate({ httpStatus: 404 })).toEqual({
|
|
40
|
+
admitTranscript: true,
|
|
41
|
+
phase: 'absent',
|
|
42
|
+
});
|
|
43
|
+
expect(evaluateV1MigrationGate({ status: 404 })).toEqual({
|
|
44
|
+
admitTranscript: true,
|
|
45
|
+
phase: 'absent',
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('admits transcript when completed with no remaining work', () => {
|
|
50
|
+
expect(evaluateV1MigrationGate({
|
|
51
|
+
body: { status: 'completed' },
|
|
52
|
+
httpStatus: 200,
|
|
53
|
+
})).toMatchObject({
|
|
54
|
+
admitTranscript: true,
|
|
55
|
+
phase: 'completed',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('denies transcript on V1 migration error instead of treating an empty list as success', () => {
|
|
60
|
+
expect(evaluateV1MigrationGate({
|
|
61
|
+
status: 'error',
|
|
62
|
+
error: 'disk full',
|
|
63
|
+
})).toEqual({
|
|
64
|
+
admitTranscript: false,
|
|
65
|
+
phase: 'error',
|
|
66
|
+
error: 'disk full',
|
|
67
|
+
userNotice: V1_MIGRATION_USER_NOTICE,
|
|
68
|
+
});
|
|
69
|
+
expect(evaluateV1MigrationGate({})).toMatchObject({
|
|
70
|
+
admitTranscript: false,
|
|
71
|
+
phase: 'error',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('denies transcript on network failure instead of treating an empty list as success', () => {
|
|
76
|
+
expect(evaluateV1MigrationGate({ error: new Error('offline') })).toMatchObject({
|
|
77
|
+
admitTranscript: false,
|
|
78
|
+
phase: 'error',
|
|
79
|
+
error: 'offline',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('exposes backfill userNotice for UI and docs', () => {
|
|
84
|
+
expect(V1_MIGRATION_USER_NOTICE).toContain('Message ids are reused');
|
|
85
|
+
expect(V1_MIGRATION_USER_NOTICE).toContain('interrupted');
|
|
86
|
+
expect(V1_MIGRATION_USER_NOTICE).toContain('V1 subtasks do not appear in v2');
|
|
87
|
+
expect(evaluateV1MigrationGate({ status: 'required' }).userNotice).toBe(V1_MIGRATION_USER_NOTICE);
|
|
88
|
+
expect(evaluateV1MigrationGate({ status: 'completed' }).userNotice).toBe(V1_MIGRATION_USER_NOTICE);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('polls GET migration status and does not POST', async () => {
|
|
92
|
+
const fetchImpl = vi.fn(async () => new Response(JSON.stringify({ status: 'completed' }), {
|
|
93
|
+
status: 200,
|
|
94
|
+
headers: { 'content-type': 'application/json' },
|
|
95
|
+
}));
|
|
96
|
+
|
|
97
|
+
await expect(fetchV1MigrationGate({
|
|
98
|
+
url: `http://127.0.0.1:4096${OPENCODE_V1_MIGRATION_PATH}`,
|
|
99
|
+
headers: { Authorization: 'Basic test' },
|
|
100
|
+
fetchImpl,
|
|
101
|
+
})).resolves.toMatchObject({
|
|
102
|
+
admitTranscript: true,
|
|
103
|
+
phase: 'completed',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(fetchImpl).toHaveBeenCalledTimes(1);
|
|
107
|
+
expect(fetchImpl.mock.calls[0][0]).toContain(OPENCODE_V1_MIGRATION_PATH);
|
|
108
|
+
expect(fetchImpl.mock.calls[0][1].method).toBe('GET');
|
|
109
|
+
expect(fetchImpl.mock.calls[0][1].headers.Authorization).toBe('Basic test');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { OpenCode } from '@opencode/client';
|
|
2
|
+
import { wrapFetchWithRuntimeContractGate } from './server-opencode-fetch.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('@opencode/client').OpenCodeClient} OpenCodeV2ClientRaw
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Compatibility client: official 2.x nests exact message lookup under
|
|
10
|
+
* `session.message.get`, while OpenChamber callers still use
|
|
11
|
+
* `session.message(input, requestOptions)`.
|
|
12
|
+
*
|
|
13
|
+
* @typedef {Omit<OpenCodeV2ClientRaw, 'session'> & {
|
|
14
|
+
* session: Omit<OpenCodeV2ClientRaw['session'], 'message'> & {
|
|
15
|
+
* message: ((input: Parameters<OpenCodeV2ClientRaw['session']['message']['get']>[0], requestOptions?: Parameters<OpenCodeV2ClientRaw['session']['message']['get']>[1]) => ReturnType<OpenCodeV2ClientRaw['session']['message']['get']>)
|
|
16
|
+
* & OpenCodeV2ClientRaw['session']['message']
|
|
17
|
+
* }
|
|
18
|
+
* }} OpenCodeV2Client
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Create the single server-side client for official OpenCode v2 APIs.
|
|
23
|
+
* Fetch is always wrapped with runtime-contract write admission so Host
|
|
24
|
+
* background clients (queue / scheduled / assistants) cannot bypass the proxy gate.
|
|
25
|
+
*
|
|
26
|
+
* @param {{
|
|
27
|
+
* baseUrl: string,
|
|
28
|
+
* authHeaders?: Record<string, string>,
|
|
29
|
+
* fetchImpl?: typeof fetch,
|
|
30
|
+
* getRuntimeContract?: () => (object | null | undefined),
|
|
31
|
+
* }} input
|
|
32
|
+
* @returns {OpenCodeV2Client}
|
|
33
|
+
*/
|
|
34
|
+
export function makeOpenCodeV2Client({ baseUrl, authHeaders, fetchImpl, getRuntimeContract }) {
|
|
35
|
+
const gatedFetch = wrapFetchWithRuntimeContractGate(fetchImpl || fetch, {
|
|
36
|
+
...(typeof getRuntimeContract === 'function' ? { getRuntimeContract } : {}),
|
|
37
|
+
});
|
|
38
|
+
const client = OpenCode.make({
|
|
39
|
+
baseUrl: baseUrl.replace(/\/$/, ''),
|
|
40
|
+
...(authHeaders ? { headers: authHeaders } : {}),
|
|
41
|
+
fetch: gatedFetch,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const messageGet = client.session.message?.get?.bind(client.session.message);
|
|
45
|
+
if (typeof messageGet === 'function') {
|
|
46
|
+
const messageCompat = Object.assign(
|
|
47
|
+
(input, requestOptions) => messageGet(input, requestOptions),
|
|
48
|
+
client.session.message,
|
|
49
|
+
);
|
|
50
|
+
return {
|
|
51
|
+
...client,
|
|
52
|
+
session: {
|
|
53
|
+
...client.session,
|
|
54
|
+
message: messageCompat,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return client;
|
|
60
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs"
|
|
2
|
+
import { homedir } from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { pathToFileURL } from "node:url"
|
|
5
|
+
|
|
6
|
+
const stripJsonc = (text) => text
|
|
7
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
8
|
+
.replace(/(^|[^:\\])\/\/.*$/gm, "$1")
|
|
9
|
+
|
|
10
|
+
const readConfigPlugins = () => {
|
|
11
|
+
const specs = []
|
|
12
|
+
const files = []
|
|
13
|
+
if (process.env.OPENCODE_CONFIG) files.push(process.env.OPENCODE_CONFIG)
|
|
14
|
+
const dirs = [process.env.OPENCODE_CONFIG_DIR, path.join(homedir(), ".config", "opencode")].filter(Boolean)
|
|
15
|
+
for (const dir of dirs) {
|
|
16
|
+
files.push(path.join(dir, "opencode.jsonc"), path.join(dir, "opencode.json"), path.join(dir, "config.json"))
|
|
17
|
+
}
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(stripJsonc(readFileSync(file, "utf8")))
|
|
21
|
+
const plugin = parsed?.plugin
|
|
22
|
+
const list = Array.isArray(plugin) ? plugin : plugin ? [plugin] : []
|
|
23
|
+
for (const entry of list) {
|
|
24
|
+
if (typeof entry === "string") specs.push(entry)
|
|
25
|
+
else if (Array.isArray(entry) && typeof entry[0] === "string") specs.push(entry[0])
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// Missing or invalid config files are skipped; OpenCode still loads the ones it can parse.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [...new Set(specs)]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const packageName = (spec) => {
|
|
35
|
+
const slash = spec.startsWith("@") ? spec.indexOf("/", 1) : -1
|
|
36
|
+
const versionAt = spec.lastIndexOf("@")
|
|
37
|
+
if (versionAt > 0 && versionAt > slash) return spec.slice(0, versionAt)
|
|
38
|
+
return spec
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resolvePackageEntry = (spec) => {
|
|
42
|
+
const name = packageName(spec)
|
|
43
|
+
const roots = [
|
|
44
|
+
path.join(homedir(), ".cache", "opencode", "npm"),
|
|
45
|
+
path.join(homedir(), ".cache", "opencode", "packages"),
|
|
46
|
+
]
|
|
47
|
+
const candidates = []
|
|
48
|
+
for (const root of roots) {
|
|
49
|
+
let entries = []
|
|
50
|
+
try {
|
|
51
|
+
entries = readdirSync(root)
|
|
52
|
+
} catch {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (entry !== name && !entry.startsWith(`${name}@`)) continue
|
|
57
|
+
const base = path.join(root, entry)
|
|
58
|
+
const packageDirs = [path.join(base, "node_modules", name)]
|
|
59
|
+
try {
|
|
60
|
+
for (const child of readdirSync(base)) {
|
|
61
|
+
if (/^\d+$/.test(child)) packageDirs.push(path.join(base, child, "node_modules", name))
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// The cache entry may be the package directory itself.
|
|
65
|
+
}
|
|
66
|
+
for (const pkgDir of packageDirs) {
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8"))
|
|
69
|
+
if (pkg.name !== name) continue
|
|
70
|
+
const exported = pkg.exports?.["."]
|
|
71
|
+
const rel = typeof exported === "string" ? exported : pkg.module || pkg.main || "index.mjs"
|
|
72
|
+
candidates.push({
|
|
73
|
+
dir: pkgDir,
|
|
74
|
+
entry: path.resolve(pkgDir, rel),
|
|
75
|
+
mtime: statSync(path.join(pkgDir, "package.json")).mtimeMs,
|
|
76
|
+
})
|
|
77
|
+
} catch {
|
|
78
|
+
// Not a readable package directory.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
candidates.sort((left, right) => right.mtime - left.mtime)
|
|
84
|
+
return candidates[0] || null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const usesCatalog = (dir) => {
|
|
88
|
+
const stack = [dir]
|
|
89
|
+
let seen = 0
|
|
90
|
+
while (stack.length > 0 && seen < 80) {
|
|
91
|
+
const current = stack.pop()
|
|
92
|
+
let names = []
|
|
93
|
+
try {
|
|
94
|
+
names = readdirSync(current)
|
|
95
|
+
} catch {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
if (name === "node_modules" || name === ".git") continue
|
|
100
|
+
const full = path.join(current, name)
|
|
101
|
+
let info
|
|
102
|
+
try {
|
|
103
|
+
info = statSync(full)
|
|
104
|
+
} catch {
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (info.isDirectory()) {
|
|
108
|
+
stack.push(full)
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (!/\.(?:mjs|cjs|js)$/.test(name) || info.size > 2_000_000) continue
|
|
112
|
+
seen += 1
|
|
113
|
+
try {
|
|
114
|
+
if (readFileSync(full, "utf8").includes(".catalog")) return true
|
|
115
|
+
} catch {
|
|
116
|
+
// Unreadable files are not catalog callers.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const catalogFor = (ctx) => ({
|
|
124
|
+
// OpenCode 2.0.12 removed ctx.catalog. Model plugins still call
|
|
125
|
+
// draft.provider.update / draft.model.update inside catalog.transform.
|
|
126
|
+
transform: (fn) => ctx.provider?.transform?.((draft) => fn({
|
|
127
|
+
provider: {
|
|
128
|
+
update: (id, updater) => draft.update(id, updater),
|
|
129
|
+
add: (...args) => draft.add?.(...args),
|
|
130
|
+
},
|
|
131
|
+
model: {
|
|
132
|
+
update: (providerID, modelID, updater) => draft.models?.update?.(providerID, modelID, updater),
|
|
133
|
+
set: (providerID, modelID, value) => draft.models?.set?.(providerID, modelID, value),
|
|
134
|
+
},
|
|
135
|
+
})),
|
|
136
|
+
reload: () => ctx.provider?.reload?.() ?? ctx.model?.reload?.(),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
export default {
|
|
140
|
+
id: "openchamber-v2-plugin-host-shim",
|
|
141
|
+
setup: async (ctx) => {
|
|
142
|
+
if (typeof ctx?.catalog?.transform === "function") return
|
|
143
|
+
const patched = { ...ctx, catalog: catalogFor(ctx) }
|
|
144
|
+
for (const spec of readConfigPlugins()) {
|
|
145
|
+
const resolved = resolvePackageEntry(spec)
|
|
146
|
+
if (!resolved || !usesCatalog(resolved.dir)) continue
|
|
147
|
+
try {
|
|
148
|
+
const mod = await import(pathToFileURL(resolved.entry).href)
|
|
149
|
+
if (typeof mod.default?.setup === "function") await mod.default.setup(patched)
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`[openchamber] v2 plugin host shim skipped ${spec}: ${error instanceof Error ? error.message : String(error)}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
}
|
|
@@ -65,7 +65,8 @@ export const createOpenCodeWatcherRuntime = (deps) => {
|
|
|
65
65
|
|
|
66
66
|
reader = createUpstreamSseReader({
|
|
67
67
|
signal,
|
|
68
|
-
|
|
68
|
+
// v2 serves the event stream at `/api/event`; `/api/global/event` is gone.
|
|
69
|
+
buildUrl: () => buildOpenCodeUrl('/event', ''),
|
|
69
70
|
getHeaders: getOpenCodeAuthHeaders,
|
|
70
71
|
fetchImpl,
|
|
71
72
|
stallTimeoutMs: upstreamStallTimeoutMs,
|
|
@@ -8,13 +8,13 @@ This module owns the authoritative permission auto-accept policy for web, deskto
|
|
|
8
8
|
|
|
9
9
|
`permissionAutoAccept.sessions` contains explicit per-session boolean policies.
|
|
10
10
|
|
|
11
|
-
Policy inheritance uses the nearest explicit session value. A child `false` therefore overrides a parent `true
|
|
11
|
+
Policy inheritance uses the nearest explicit session value. A child `false` therefore overrides a parent `true`. Sessions with no explicit value, including a missing ancestor lookup, default to allow. A failed settings load still fails closed and does not approve.
|
|
12
12
|
|
|
13
13
|
## Runtime
|
|
14
14
|
|
|
15
|
-
`createPermissionAutoAcceptRuntime` loads and serializes policy writes, subscribes to the global OpenCode event hub, caches session lineage, retries transient replies, and reconciles pending permissions after startup, reconnect, and policy enablement.
|
|
15
|
+
`createPermissionAutoAcceptRuntime` loads and serializes policy writes, subscribes to the global OpenCode event hub, caches session lineage, retries transient replies, and reconciles pending permissions after startup, reconnect, and policy enablement. Pending requests are listed at `GET /permission/request` and approved with `POST /session/:sessionID/permission/:requestID/reply` body `{ decision: "always" }`. OpenCode 2.0.12 rejects the legacy `{ reply }` body and the unscoped `/permission/:id/reply` path.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
A failed settings load fails closed and does not approve. A failed pending-permission fetch is distinct from an empty successful response and never clears policy state.
|
|
18
18
|
|
|
19
19
|
## Routes
|
|
20
20
|
|
|
@@ -27,9 +27,9 @@ These are normal authenticated OpenChamber runtime routes. They must not be adde
|
|
|
27
27
|
|
|
28
28
|
`packages/ui/src/stores/permissionStore.ts` is a projection of server policy and does not persist an independent policy. The server is the sole responder and the UI renders pending requests until the authoritative `permission.replied` event arrives.
|
|
29
29
|
|
|
30
|
-
`ChatInput` derives control visibility from the persisted, directory-scoped `useConfigStore` agent snapshot. A selected agent with a final global `allow` or `deny` rule has no remaining prompt path and hides the control. Unknown snapshots, missing selected agents, and rules that can ask keep the control visible. This path issues no additional request.
|
|
30
|
+
`ChatInput` derives control visibility from the persisted, directory-scoped `useConfigStore` agent snapshot via `shouldShowPermissionAutoAcceptControl`. It reads the V2 wire `permissions` field when present (OpenCode `AgentInfo.permissions`), otherwise the legacy singular `permission` document. Rule normalization reuses `toPermissionRuleset` so both V2 `{ action, resource, effect }` and V1 `{ permission, pattern, action }` / tool-map shapes are recognized. A selected agent with a final global `allow` or `deny` rule has no remaining prompt path and hides the control. Unknown snapshots, missing selected agents, unparseable rules, and rules that can still ask keep the control visible. This path issues no additional request and does not change server auto-accept policy.
|
|
31
31
|
|
|
32
|
-
VS Code retains its foreground-only implementation because it does not run the web server runtime.
|
|
32
|
+
VS Code retains its foreground-only implementation because it does not run the web server runtime (`isVSCodeRuntime()` always shows the control).
|
|
33
33
|
|
|
34
34
|
## Tests
|
|
35
35
|
|
|
@@ -123,6 +123,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
123
123
|
|
|
124
124
|
const isSessionAutoAccepting = async (sessionId, directory) => {
|
|
125
125
|
await load();
|
|
126
|
+
if (!sessionId) return false;
|
|
126
127
|
const seen = new Set();
|
|
127
128
|
let current = sessionId;
|
|
128
129
|
let currentDirectory = directory;
|
|
@@ -133,22 +134,23 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
133
134
|
try {
|
|
134
135
|
info = await getSession(current, currentDirectory);
|
|
135
136
|
} catch {
|
|
136
|
-
|
|
137
|
+
// Lineage lookup failed. No explicit deny was found, so keep the default allow.
|
|
138
|
+
return true;
|
|
137
139
|
}
|
|
138
140
|
current = info?.parentID ?? null;
|
|
139
141
|
currentDirectory = info?.directory ?? currentDirectory;
|
|
140
142
|
}
|
|
141
|
-
return
|
|
143
|
+
return true;
|
|
142
144
|
};
|
|
143
145
|
|
|
144
146
|
const replyOnce = async (permission, directory) => {
|
|
145
147
|
if (!permission?.id || !permission?.sessionID) return false;
|
|
146
148
|
await load();
|
|
147
149
|
if (!(await isSessionAutoAccepting(permission.sessionID, directory))) return false;
|
|
148
|
-
await request(`/permission/${encodeURIComponent(permission.id)}/reply`, {
|
|
150
|
+
await request(`/session/${encodeURIComponent(permission.sessionID)}/permission/${encodeURIComponent(permission.id)}/reply`, {
|
|
149
151
|
directory,
|
|
150
152
|
method: 'POST',
|
|
151
|
-
body: {
|
|
153
|
+
body: { decision: 'always' },
|
|
152
154
|
});
|
|
153
155
|
return true;
|
|
154
156
|
};
|
|
@@ -187,7 +189,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
187
189
|
for (const directory of scopes) {
|
|
188
190
|
let payload;
|
|
189
191
|
try {
|
|
190
|
-
payload = await request('/permission', { directory });
|
|
192
|
+
payload = await request('/permission/request', { directory });
|
|
191
193
|
} catch {
|
|
192
194
|
continue;
|
|
193
195
|
}
|
|
@@ -209,12 +211,17 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
209
211
|
const raw = event?.payload;
|
|
210
212
|
const payload = raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw;
|
|
211
213
|
const directory = typeof event?.directory === 'string' && event.directory !== 'global' ? event.directory : undefined;
|
|
214
|
+
const body = payload?.properties && typeof payload.properties === 'object'
|
|
215
|
+
? payload.properties
|
|
216
|
+
: payload?.data && typeof payload.data === 'object'
|
|
217
|
+
? payload.data
|
|
218
|
+
: payload;
|
|
212
219
|
if (payload?.type === 'session.created' || payload?.type === 'session.updated') {
|
|
213
|
-
rememberSession(payload.properties?.info, directory);
|
|
220
|
+
rememberSession(body?.info ?? payload.properties?.info, directory);
|
|
214
221
|
return;
|
|
215
222
|
}
|
|
216
223
|
if (payload?.type === 'permission.asked') {
|
|
217
|
-
void processPermission(
|
|
224
|
+
void processPermission(body, directory);
|
|
218
225
|
}
|
|
219
226
|
};
|
|
220
227
|
|