@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,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenChamber-owned session metadata.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode 2.x accepts `metadata` only when a session is created; the v1
|
|
5
|
+
* `PATCH /session/{id}` route is gone and nothing replaces it. Session goal
|
|
6
|
+
* progress (and later assist / pinned notes) live here: one JSON file per data
|
|
7
|
+
* dir, `{ [sessionID]: metadata }`, folded back onto the sessions the proxy
|
|
8
|
+
* serves so clients keep reading `session.metadata` where they always did.
|
|
9
|
+
*
|
|
10
|
+
* Writes are a JSON Merge Patch (RFC 7386): nested objects merge key by key and
|
|
11
|
+
* a `null` deletes. That is what the old PATCH did, and it is what keeps two
|
|
12
|
+
* features writing into the same `openchamber` namespace from erasing each
|
|
13
|
+
* other — goal mode saving progress must not drop an assist recap.
|
|
14
|
+
*
|
|
15
|
+
* Mutations are fully serialized. Committed `entries` update only after a
|
|
16
|
+
* successful persist so concurrent readers never observe uncommitted drafts.
|
|
17
|
+
* A failed or corrupt file read is never empty success: the store stays
|
|
18
|
+
* unwritable and reports the error instead of overwriting unknown disk state.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fsDefault from 'node:fs';
|
|
22
|
+
import pathDefault from 'node:path';
|
|
23
|
+
|
|
24
|
+
const METADATA_FILE_NAME = 'sessions-metadata.json';
|
|
25
|
+
const HOST_ARCHIVE_KEY = 'archive';
|
|
26
|
+
|
|
27
|
+
const asNonEmptyString = (value) => {
|
|
28
|
+
if (typeof value !== 'string') return null;
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* RFC 7386 merge. Returns a new object; `null` in the patch removes the key,
|
|
37
|
+
* and a non-object patch value replaces whatever was there.
|
|
38
|
+
*/
|
|
39
|
+
export const mergeMetadataPatch = (current, patch) => {
|
|
40
|
+
const base = isPlainObject(current) ? { ...current } : {};
|
|
41
|
+
if (!isPlainObject(patch)) return base;
|
|
42
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
43
|
+
if (value === null) {
|
|
44
|
+
delete base[key];
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
base[key] = isPlainObject(value) ? mergeMetadataPatch(base[key], value) : value;
|
|
48
|
+
}
|
|
49
|
+
return base;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const readPriorArchivedAt = (metadata) => {
|
|
53
|
+
if (!isPlainObject(metadata?.openchamber)) return null;
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(metadata.openchamber, HOST_ARCHIVE_KEY)) return null;
|
|
55
|
+
const archive = metadata.openchamber[HOST_ARCHIVE_KEY];
|
|
56
|
+
if (!isPlainObject(archive) || !Object.prototype.hasOwnProperty.call(archive, 'archivedAt')) return null;
|
|
57
|
+
const value = archive.archivedAt;
|
|
58
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Strip Host archive writes from generic metadata patches. */
|
|
62
|
+
const stripArchiveFromMetadataPatch = (patch) => {
|
|
63
|
+
if (!isPlainObject(patch) || !isPlainObject(patch.openchamber)) return patch;
|
|
64
|
+
if (!Object.prototype.hasOwnProperty.call(patch.openchamber, HOST_ARCHIVE_KEY)) return patch;
|
|
65
|
+
const openchamber = { ...patch.openchamber };
|
|
66
|
+
delete openchamber[HOST_ARCHIVE_KEY];
|
|
67
|
+
return { ...patch, openchamber };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Restore reserved archive after a generic merge so null ancestor deletes
|
|
72
|
+
* (`openchamber: null` / `archive: null`) cannot wipe Host archive state.
|
|
73
|
+
*/
|
|
74
|
+
const applyReservedArchiveProtection = (previous, patch, merged, { allowArchive = false } = {}) => {
|
|
75
|
+
if (allowArchive) return merged;
|
|
76
|
+
const priorArchive = readPriorArchivedAt(previous);
|
|
77
|
+
const result = isPlainObject(merged) ? { ...merged } : {};
|
|
78
|
+
const openchamber = isPlainObject(result.openchamber) ? { ...result.openchamber } : {};
|
|
79
|
+
|
|
80
|
+
if (isPlainObject(patch?.openchamber) && Object.prototype.hasOwnProperty.call(patch.openchamber, HOST_ARCHIVE_KEY)) {
|
|
81
|
+
delete openchamber[HOST_ARCHIVE_KEY];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (priorArchive !== null) {
|
|
85
|
+
openchamber[HOST_ARCHIVE_KEY] = {
|
|
86
|
+
...(isPlainObject(previous?.openchamber?.[HOST_ARCHIVE_KEY])
|
|
87
|
+
? previous.openchamber[HOST_ARCHIVE_KEY]
|
|
88
|
+
: {}),
|
|
89
|
+
archivedAt: priorArchive,
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
delete openchamber[HOST_ARCHIVE_KEY];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (Object.keys(openchamber).length > 0) result.openchamber = openchamber;
|
|
96
|
+
else delete result.openchamber;
|
|
97
|
+
return result;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {object} options
|
|
102
|
+
* @param {string} options.dataDir OpenChamber data directory for this instance.
|
|
103
|
+
* @param {typeof fsDefault.promises} [options.fsPromises]
|
|
104
|
+
* @param {typeof pathDefault} [options.path]
|
|
105
|
+
* @param {() => number} [options.now]
|
|
106
|
+
*/
|
|
107
|
+
export const createSessionMetadataStore = ({
|
|
108
|
+
dataDir,
|
|
109
|
+
fsPromises = fsDefault.promises,
|
|
110
|
+
path = pathDefault,
|
|
111
|
+
now = Date.now,
|
|
112
|
+
}) => {
|
|
113
|
+
const filePath = path.join(dataDir, METADATA_FILE_NAME);
|
|
114
|
+
|
|
115
|
+
/** sessionID → metadata. Committed only — never holds an in-flight draft. */
|
|
116
|
+
const entries = new Map();
|
|
117
|
+
let loaded = false;
|
|
118
|
+
let loadPromise = null;
|
|
119
|
+
/**
|
|
120
|
+
* Writes stay disabled until one load has told us what is already on disk.
|
|
121
|
+
* A failed/corrupt read is not evidence that nothing is stored.
|
|
122
|
+
*/
|
|
123
|
+
let writable = false;
|
|
124
|
+
let loadFailureReason = null;
|
|
125
|
+
/** Full RMW + persist chain — concurrent writers cannot interleave merges. */
|
|
126
|
+
let mutationChain = Promise.resolve();
|
|
127
|
+
|
|
128
|
+
const snapshotFrom = (map) => Object.fromEntries(map);
|
|
129
|
+
const snapshot = () => snapshotFrom(entries);
|
|
130
|
+
|
|
131
|
+
const unavailableError = (detail = loadFailureReason) => {
|
|
132
|
+
const suffix = detail ? `: ${detail}` : ': its file could not be read';
|
|
133
|
+
return new Error(`session metadata is unavailable${suffix.startsWith(':') ? suffix : `: ${suffix}`}`);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const parseStored = (raw) => {
|
|
137
|
+
const parsed = JSON.parse(raw);
|
|
138
|
+
if (!isPlainObject(parsed)) {
|
|
139
|
+
throw new Error('session metadata file is not a JSON object');
|
|
140
|
+
}
|
|
141
|
+
const result = new Map();
|
|
142
|
+
for (const [sessionID, value] of Object.entries(parsed)) {
|
|
143
|
+
const id = asNonEmptyString(sessionID);
|
|
144
|
+
if (id && isPlainObject(value)) result.set(id, value);
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const readFile = async () => {
|
|
150
|
+
let raw;
|
|
151
|
+
try {
|
|
152
|
+
raw = await fsPromises.readFile(filePath, 'utf8');
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error?.code === 'ENOENT') return { ok: true, stored: new Map() };
|
|
155
|
+
console.warn('[session-metadata] could not read the session metadata file:', error?.message ?? error);
|
|
156
|
+
return { ok: false, stored: null, reason: error?.message || 'read failed' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
return { ok: true, stored: parseStored(raw) };
|
|
161
|
+
} catch (error) {
|
|
162
|
+
// Corrupt bytes must not become empty success. Keep the file, stay
|
|
163
|
+
// unwritable, and surface the error so callers can report/retry.
|
|
164
|
+
console.warn(
|
|
165
|
+
`[session-metadata] session metadata file is corrupt and will not be overwritten: ${error?.message ?? error}`,
|
|
166
|
+
);
|
|
167
|
+
return { ok: false, stored: null, reason: error?.message || 'corrupt metadata file' };
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const load = () => {
|
|
172
|
+
if (!loadPromise) {
|
|
173
|
+
loadPromise = readFile().then((result) => {
|
|
174
|
+
if (result.ok) {
|
|
175
|
+
for (const [id, metadata] of result.stored) {
|
|
176
|
+
if (!entries.has(id)) entries.set(id, metadata);
|
|
177
|
+
}
|
|
178
|
+
loaded = true;
|
|
179
|
+
writable = true;
|
|
180
|
+
loadFailureReason = null;
|
|
181
|
+
} else {
|
|
182
|
+
loadPromise = null;
|
|
183
|
+
loaded = false;
|
|
184
|
+
writable = false;
|
|
185
|
+
loadFailureReason = result.reason || 'its file could not be read';
|
|
186
|
+
}
|
|
187
|
+
return { ok: result.ok, entries: snapshot(), reason: result.reason || null };
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return loadPromise;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const ensureReadable = async () => {
|
|
194
|
+
const result = await load();
|
|
195
|
+
if (!result.ok || !writable) throw unavailableError(result.reason);
|
|
196
|
+
return result;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const persistMap = async (map) => {
|
|
200
|
+
const payload = JSON.stringify(snapshotFrom(map));
|
|
201
|
+
await fsPromises.mkdir(dataDir, { recursive: true });
|
|
202
|
+
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
203
|
+
await fsPromises.writeFile(tmpPath, payload, 'utf8');
|
|
204
|
+
await fsPromises.rename(tmpPath, filePath);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @template T
|
|
209
|
+
* @param {() => Promise<T>} work
|
|
210
|
+
* @returns {Promise<T>}
|
|
211
|
+
*/
|
|
212
|
+
const runExclusive = (work) => {
|
|
213
|
+
const next = mutationChain.then(work, work);
|
|
214
|
+
mutationChain = next.then(
|
|
215
|
+
() => undefined,
|
|
216
|
+
() => undefined,
|
|
217
|
+
);
|
|
218
|
+
return next;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const get = async (sessionID) => {
|
|
222
|
+
const id = asNonEmptyString(sessionID);
|
|
223
|
+
if (!id) return {};
|
|
224
|
+
await ensureReadable();
|
|
225
|
+
return entries.get(id) ?? {};
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const has = async (sessionID) => {
|
|
229
|
+
const id = asNonEmptyString(sessionID);
|
|
230
|
+
if (!id) return false;
|
|
231
|
+
await ensureReadable();
|
|
232
|
+
return entries.has(id);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Shared exclusive commit path: merge → persist draft → publish committed.
|
|
237
|
+
* Readers never observe the draft; persist failure leaves prior committed state.
|
|
238
|
+
*
|
|
239
|
+
* @param {string} id
|
|
240
|
+
* @param {object | undefined} previous
|
|
241
|
+
* @param {object} patch
|
|
242
|
+
* @param {{ allowArchive?: boolean }} options
|
|
243
|
+
*/
|
|
244
|
+
const commitMergedPatch = async (id, previous, patch, options = {}) => {
|
|
245
|
+
const allowArchive = options.allowArchive === true;
|
|
246
|
+
const effectivePatch = allowArchive ? patch : stripArchiveFromMetadataPatch(patch);
|
|
247
|
+
|
|
248
|
+
let merged = mergeMetadataPatch(previous, effectivePatch);
|
|
249
|
+
merged = applyReservedArchiveProtection(previous, patch, merged, { allowArchive });
|
|
250
|
+
|
|
251
|
+
const nextMap = new Map(entries);
|
|
252
|
+
if (Object.keys(merged).length === 0) nextMap.delete(id);
|
|
253
|
+
else nextMap.set(id, merged);
|
|
254
|
+
|
|
255
|
+
// Persist draft snapshot first; publish to committed entries only on success.
|
|
256
|
+
await persistMap(nextMap);
|
|
257
|
+
|
|
258
|
+
if (Object.keys(merged).length === 0) entries.delete(id);
|
|
259
|
+
else entries.set(id, merged);
|
|
260
|
+
|
|
261
|
+
return merged;
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Applies a merge patch and returns the session's full metadata afterwards.
|
|
266
|
+
* Committed map updates only after persist succeeds — readers never see drafts.
|
|
267
|
+
*
|
|
268
|
+
* @param {string} sessionID
|
|
269
|
+
* @param {object} patch
|
|
270
|
+
* @param {{ allowArchive?: boolean }} [options]
|
|
271
|
+
*/
|
|
272
|
+
const setSessionMetadata = async (sessionID, patch, options = {}) => {
|
|
273
|
+
const id = asNonEmptyString(sessionID);
|
|
274
|
+
if (!id) throw new Error('a session id is required to store session metadata');
|
|
275
|
+
if (!isPlainObject(patch)) throw new Error('a session metadata patch must be an object');
|
|
276
|
+
|
|
277
|
+
return runExclusive(async () => {
|
|
278
|
+
await ensureReadable();
|
|
279
|
+
return commitMergedPatch(id, entries.get(id), patch, options);
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Conditional metadata mutation on the same exclusive serial boundary as
|
|
285
|
+
* setSessionMetadata. `decide(committedMetadata)` runs while the lock is held
|
|
286
|
+
* and must return synchronously:
|
|
287
|
+
* - `{ ok: false, reason? }` — no persist; readers keep prior committed state
|
|
288
|
+
* - `{ ok: true, patch }` — merge-patch, persist, then publish
|
|
289
|
+
*
|
|
290
|
+
* Use this for goal generation / status preconditions so a stale auditor
|
|
291
|
+
* cannot commit after a user pause that already landed.
|
|
292
|
+
*
|
|
293
|
+
* @param {string} sessionID
|
|
294
|
+
* @param {(current: object) => { ok: false, reason?: string } | { ok: true, patch: object }} decide
|
|
295
|
+
* @param {{ allowArchive?: boolean }} [options]
|
|
296
|
+
* @returns {Promise<{ committed: boolean, reason?: string, metadata: object }>}
|
|
297
|
+
*/
|
|
298
|
+
const mutateSessionMetadata = async (sessionID, decide, options = {}) => {
|
|
299
|
+
const id = asNonEmptyString(sessionID);
|
|
300
|
+
if (!id) throw new Error('a session id is required to store session metadata');
|
|
301
|
+
if (typeof decide !== 'function') throw new Error('a decide function is required to mutate session metadata');
|
|
302
|
+
|
|
303
|
+
return runExclusive(async () => {
|
|
304
|
+
await ensureReadable();
|
|
305
|
+
|
|
306
|
+
const previous = entries.get(id);
|
|
307
|
+
const current = previous ?? {};
|
|
308
|
+
const decision = decide(current);
|
|
309
|
+
if (!decision || decision.ok !== true) {
|
|
310
|
+
return {
|
|
311
|
+
committed: false,
|
|
312
|
+
reason: typeof decision?.reason === 'string' && decision.reason
|
|
313
|
+
? decision.reason
|
|
314
|
+
: 'rejected',
|
|
315
|
+
metadata: current,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (!isPlainObject(decision.patch)) {
|
|
319
|
+
throw new Error('a successful mutate decision must include an object patch');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const merged = await commitMergedPatch(id, previous, decision.patch, options);
|
|
323
|
+
return { committed: true, metadata: merged };
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Drop a session's metadata.
|
|
329
|
+
* @returns {Promise<boolean>} true if a row was removed; false if already absent
|
|
330
|
+
* (idempotent). Disk/write failures **throw** so callers can retry — never
|
|
331
|
+
* conflate not-found with write failure.
|
|
332
|
+
*/
|
|
333
|
+
const removeSession = async (sessionID) => {
|
|
334
|
+
const id = asNonEmptyString(sessionID);
|
|
335
|
+
if (!id) return false;
|
|
336
|
+
|
|
337
|
+
return runExclusive(async () => {
|
|
338
|
+
await ensureReadable();
|
|
339
|
+
if (!entries.has(id)) return false;
|
|
340
|
+
|
|
341
|
+
const nextMap = new Map(entries);
|
|
342
|
+
nextMap.delete(id);
|
|
343
|
+
// Write failures propagate — do not report as "already gone".
|
|
344
|
+
await persistMap(nextMap);
|
|
345
|
+
entries.delete(id);
|
|
346
|
+
return true;
|
|
347
|
+
});
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const getAll = async () => {
|
|
351
|
+
await ensureReadable();
|
|
352
|
+
return snapshot();
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Synchronous committed snapshot for event hot paths.
|
|
357
|
+
* Returns `null` when not successfully loaded (unknown — never empty).
|
|
358
|
+
*/
|
|
359
|
+
const getSnapshotSync = () => (loaded && writable ? snapshot() : null);
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
load,
|
|
363
|
+
get,
|
|
364
|
+
getAll,
|
|
365
|
+
list: getAll,
|
|
366
|
+
getSnapshotSync,
|
|
367
|
+
isLoaded: () => loaded && writable,
|
|
368
|
+
getLoadFailureReason: () => loadFailureReason,
|
|
369
|
+
has,
|
|
370
|
+
setSessionMetadata,
|
|
371
|
+
mutateSessionMetadata,
|
|
372
|
+
removeSession,
|
|
373
|
+
filePath,
|
|
374
|
+
};
|
|
375
|
+
};
|