@openchambery/web 2.0.0-beta.1 → 2.0.0-beta.4
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 +15 -11
- package/bin/cli-opencode.test.js +4 -4
- package/bin/cli.js +1 -1
- package/bin/lib/cli-startup.js +1 -1
- package/bin/lib/cli-startup.test.js +18 -4
- package/dist/assets/{AgentAvatar-CLWWNu_Q.js → AgentAvatar-Q1SmFRx2.js} +1 -1
- package/dist/assets/AssistantView-CwXkiCAd.js +3 -0
- package/dist/assets/{DiagramView-xuSJTnv7.js → DiagramView-BW92schO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-Ov0D96d0.js → FadeInOnReveal-BZmwl-zE.js} +1 -1
- package/dist/assets/{Icon-DD-BuMfZ.js → Icon-AQPvxMiU.js} +1 -1
- package/dist/assets/{MarkdownRenderer-bc_p1H7l.js → MarkdownRenderer-Bv05iDZZ.js} +2 -2
- package/dist/assets/{MarkdownRendererImpl-SKi4oG0c.js → MarkdownRendererImpl-C81xqYib.js} +3 -3
- package/dist/assets/MarkstreamRendererImpl-CXUCQuKJ.js +3 -0
- package/dist/assets/{MobileDetailNavigation-Bnx59U_J.js → MobileDetailNavigation-BMgIu7zc.js} +1 -1
- package/dist/assets/{MobileShareBridge-BQlP4J-F.js → MobileShareBridge-BOXGNHmC.js} +2 -2
- package/dist/assets/MobileSurface-xuc_z0_G.js +2 -0
- package/dist/assets/{MultiRunWindow-ChZGob0v.js → MultiRunWindow-B5At2Z95.js} +1 -1
- package/dist/assets/{OnboardingScreen-DtQJxZG-.js → OnboardingScreen-bsPLgz4r.js} +1 -1
- package/dist/assets/OverlayScrollbar-4wp4migh.js +2 -0
- package/dist/assets/SettingsView-CJ6tcH5_.js +25 -0
- package/dist/assets/{SettingsWindow-DfnsFa8y.js → SettingsWindow-CMtX2bMl.js} +1 -1
- package/dist/assets/{TerminalView-Dh9MWCpS.js → TerminalView-Dvr-ysTS.js} +1 -1
- package/dist/assets/{ToolOutputDialog-BejmjrRk.js → ToolOutputDialog-uH1ZZDSS.js} +2 -2
- package/dist/assets/appThemeRegistry-DK7q2juD.js +1 -0
- package/dist/assets/{assistant-attachment-cache-C0aRnFkj.js → assistant-attachment-cache-DphJleHr.js} +1 -1
- package/dist/assets/{button-D-rATN7w.js → button-CcNJUxUd.js} +1 -1
- package/dist/assets/collapsible-ZmZ6rLWs.js +3 -0
- package/dist/assets/context-menu-B2LXJgXL.js +1 -0
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-C6HrP0jq.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DdVBp45U.js} +1 -1
- package/dist/assets/{diagram-DQf7Y_uR.js → diagram-CxTs9hCQ.js} +1 -1
- package/dist/assets/{dialog-bfbvR4VH.js → dialog-KMVL9QKF.js} +1 -1
- package/dist/assets/es-CN3E_zXj.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-BvrNCroG.js → fileMentionTouchSelection-7RIwlpkB.js} +1 -1
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-CjdBRy8y.js} +1 -1
- package/dist/assets/fr-9zZvQZD2.js +13 -0
- package/dist/assets/gitApi-yuWg9BN4.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-9vQBq3zp.js} +1 -1
- package/dist/assets/{input-8r6F_Fza.js → input-CrIvk3HZ.js} +1 -1
- package/dist/assets/insertionBoundaries-BiUCSkx-.js +1 -0
- package/dist/assets/ja-Dnjk_82N.js +15 -0
- package/dist/assets/ko-BxCEE99C.js +15 -0
- package/dist/assets/{main-CCE_4y5F.js → main-C9mIBo77.js} +2 -2
- package/dist/assets/main-DiL6w6KF.js +169 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-1Qo6y6IJ.js → markdownHeightCache-BmXVY_gc.js} +1 -1
- package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
- package/dist/assets/mcpQueries-C7jknHV8.js +1 -0
- package/dist/assets/miniChat-CwtUaElU.js +2 -0
- package/dist/assets/mobile-DB-6--ws.js +2 -0
- package/dist/assets/multirun-TAsxFI8e.js +4 -0
- package/dist/assets/{persistence-Ce3AnpEr.js → persistence-56kq8Z4V.js} +2 -2
- package/dist/assets/pl-CNNsWnPM.js +15 -0
- package/dist/assets/platform-C13fMVEs.js +6 -0
- package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
- package/dist/assets/{relay-image-stream-DM7atqJs.js → relay-image-stream-DrdVj99c.js} +1 -1
- package/dist/assets/renderElectronMiniChatApp-CGj0MNRU.js +2 -0
- package/dist/assets/renderMobileApp-CGMMqbsO.js +28 -0
- package/dist/assets/runtime-fetch-Fs9ZV2V8.js +1 -0
- package/dist/assets/{runtimeConfig-BMvqa5we.js → runtimeConfig-BEANkvOd.js} +1 -1
- package/dist/assets/runtimeEndpointReset-A_ZBUJFS.js +147 -0
- package/dist/assets/sessionChangeSummary-qOLrP9fw.js +2 -0
- package/dist/assets/{sessionLookup-gh-hVRIc.js → sessionLookup-34XFmP9C.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-CudoSgV4.js → sortable-tabs-strip-DyEV0Heg.js} +1 -1
- package/dist/assets/src-ubCWXZhR.css +2 -0
- package/dist/assets/store-DLCiZaA6.js +15 -0
- package/dist/assets/{terminalApi-DiF6JmBP.js → terminalApi-CXaBEoa_.js} +1 -1
- package/dist/assets/textarea-BBOlRI9O.js +1 -0
- package/dist/assets/{tooltip-CQvpK-UV.js → tooltip-CPI4fr1a.js} +1 -1
- package/dist/assets/uk-rwQrxeYM.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-DRDQXjyX.js} +1 -1
- package/dist/assets/{useAppFontEffects-Cpmpo7Wb.js → useAppFontEffects-CiDe42I5.js} +57 -43
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-BlizAlOt.js +1 -0
- package/dist/assets/useEffectiveDirectory-C_CouL5v.js +1 -0
- package/dist/assets/{useFontPreferences-DcbXQOkc.js → useFontPreferences-B0oUZAiB.js} +1 -1
- package/dist/assets/useMobileNavigationStore-DfPClaW-.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-DtrfbxAp.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-Bqn4AiBL.js → useWorkerHighlightedLines-CPNYz6Wf.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-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-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 +1 -1
- package/server/index.js +327 -62
- package/server/lib/assistants/DOCUMENTATION.md +14 -9
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +265 -18
- package/server/lib/assistants/routes.js +6 -0
- package/server/lib/assistants/service.js +417 -231
- package/server/lib/assistants/service.test.js +392 -148
- package/server/lib/conversations/DOCUMENTATION.md +1 -1
- package/server/lib/conversations/service.js +1 -0
- package/server/lib/conversations/service.test.js +16 -0
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/DOCUMENTATION.md +3 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +49 -4
- package/server/lib/event-stream/reasoning-projection.js +16 -1
- package/server/lib/event-stream/reasoning-projection.test.js +15 -0
- 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 +33 -9
- package/server/lib/llm/catalog.js +1 -1
- package/server/lib/llm/completions.js +5 -1
- package/server/lib/llm/completions.test.js +3 -3
- package/server/lib/llm/generate.js +237 -145
- package/server/lib/llm/generate.test.js +201 -46
- package/server/lib/llm/routes.js +2 -0
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/notifications/runtime.js +23 -5
- package/server/lib/notifications/runtime.test.js +11 -0
- package/server/lib/opencode/DOCUMENTATION.md +85 -17
- 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 +42 -99
- package/server/lib/opencode/config-entity-routes.test.js +107 -0
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.js +1 -2
- package/server/lib/opencode/env-runtime.js +40 -26
- package/server/lib/opencode/env-runtime.test.js +10 -17
- package/server/lib/opencode/feature-routes-runtime.js +43 -4
- 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 +349 -39
- package/server/lib/opencode/lifecycle.test.js +322 -0
- 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/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +4 -4
- package/server/lib/opencode/opencode2-pin.test.js +1 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +127 -4
- 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-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- 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 +252 -46
- package/server/lib/opencode/proxy.test.js +235 -2
- package/server/lib/opencode/routes.js +296 -62
- package/server/lib/opencode/routes.test.js +278 -12
- 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 +33 -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 +20 -33
- package/server/lib/opencode/skill-routes.test.js +35 -2
- 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/v2-client.js +14 -3
- 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/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 +8 -0
- package/server/lib/scheduled-tasks/runtime.js +52 -4
- package/server/lib/scheduled-tasks/runtime.test.js +80 -1
- package/server/lib/session-goal/DOCUMENTATION.md +113 -13
- package/server/lib/session-goal/routes.js +9 -0
- package/server/lib/session-goal/runtime.js +1579 -117
- package/server/lib/session-goal/runtime.test.js +1453 -84
- 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 +161 -34
- package/server/lib/session-index/sync-runtime.test.js +155 -1
- package/server/lib/session-metadata/DOCUMENTATION.md +61 -16
- package/server/lib/session-metadata/routes.js +108 -9
- package/server/lib/session-metadata/routes.test.js +47 -2
- 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 +212 -72
- package/server/lib/session-metadata/session-metadata-store.test.js +205 -10
- 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 +9 -8
- package/server/lib/session-turn-pages/routes.js +17 -102
- package/server/lib/session-turn-pages/routes.test.js +281 -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 -147
- package/server/lib/small-model/catalog.js +31 -51
- package/server/lib/small-model/catalog.test.js +65 -10
- 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 +124 -240
- 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 +2 -0
- package/dist/assets/AssistantView-BTCkm8yk.js +0 -3
- package/dist/assets/MarkstreamRendererImpl-CnkodGJW.js +0 -3
- package/dist/assets/MobileSurface-BXLeiZ4d.js +0 -2
- package/dist/assets/OverlayScrollbar-pMwxaCca.js +0 -2
- package/dist/assets/SettingsView-B5I0KDTZ.js +0 -25
- package/dist/assets/appThemeRegistry-COGfxTFV.js +0 -1
- package/dist/assets/collapsible-BXRd5_Hr.js +0 -1
- package/dist/assets/context-menu-jGtH6fR9.js +0 -1
- package/dist/assets/es-c52LTHHe.js +0 -15
- package/dist/assets/fr-PisFbUE8.js +0 -13
- package/dist/assets/gitApi-ButwzQ0N.js +0 -9
- package/dist/assets/insertionBoundaries-DGs7S5zv.js +0 -1
- package/dist/assets/ja-C8Ikun3Z.js +0 -15
- package/dist/assets/ko-ClFys5o2.js +0 -15
- package/dist/assets/main-Dn9rpsgl.js +0 -169
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/miniChat-BpjxVqgg.js +0 -2
- package/dist/assets/mobile-nMJCaNq1.js +0 -2
- package/dist/assets/multirun-BvSG94mW.js +0 -4
- package/dist/assets/pl-CnaKWN5F.js +0 -15
- package/dist/assets/platform-CWTIp02q.js +0 -4
- package/dist/assets/projectMeta-DOe49dM_.js +0 -3
- package/dist/assets/pt-BR-s8OpfRI6.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-R6dKh79f.js +0 -2
- package/dist/assets/renderMobileApp-y-S8y8v_.js +0 -28
- package/dist/assets/runtime-fetch-zIalUnbb.js +0 -1
- package/dist/assets/runtimeEndpointReset-B6BxhvR0.js +0 -147
- package/dist/assets/sessionChangeSummary-VVBlEXuh.js +0 -2
- package/dist/assets/src-BzX5UI-c.css +0 -2
- package/dist/assets/store-DJczmczv.js +0 -15
- package/dist/assets/textarea-3XR8sUfy.js +0 -1
- package/dist/assets/uk-CaX0iqkB.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-B1GX59-L.js +0 -1
- package/dist/assets/useEffectiveDirectory-CN6JoCFw.js +0 -1
- package/dist/assets/useMobileNavigationStore-BVjFtXSy.js +0 -414
- package/dist/assets/useSessionAutoCleanup-BLjymaFt.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/zh-CN-20MYEjha.js +0 -15
- package/dist/assets/zh-TW-DiN04qTw.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 -183
- package/server/lib/small-model/opencode-session.test.js +0 -147
|
@@ -14,7 +14,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
14
14
|
- `packages/web/server/lib/opencode/v1-migration-gate.js`: pure V1→v2 migration admission gate (`GET /api/experimental/migration/v1` → whether transcript may be fetched).
|
|
15
15
|
- `packages/web/server/lib/opencode/managed-capabilities-runtime.js`: managed-child scheduled-task resources, config injection, rotating bridge identity, and bridge authorization.
|
|
16
16
|
- `packages/web/server/lib/opencode/env-runtime.js`: OpenCode CLI/binary resolution and shell environment runtime.
|
|
17
|
-
- `packages/web/server/lib/opencode/ensure-cli.js`: detect local `
|
|
17
|
+
- `packages/web/server/lib/opencode/ensure-cli.js`: detect local `opencode` version and install the pinned official V2 into the OpenChamber data dir when missing or too old. Never uses a packaged/bundled binary.
|
|
18
18
|
- `packages/web/server/lib/opencode/env-config.js`: OpenCode-related environment variable parsing and validation (host/port/hostname).
|
|
19
19
|
- `packages/web/server/lib/opencode/hmr-state-runtime.js`: HMR-persistent runtime state initialization, auth-state bootstrap, and HMR sync helpers.
|
|
20
20
|
- `packages/web/server/lib/opencode/bootstrap-runtime.js`: base app bootstrap runtime for status/auth/tts/notification/OpenChamber route wiring.
|
|
@@ -27,7 +27,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
27
27
|
- `packages/web/server/lib/opencode/core-routes.js`: server status/system routes, auth/access guard routes, and settings utility route registration.
|
|
28
28
|
- `packages/web/server/lib/opencode/shutdown-runtime.js`: graceful shutdown orchestration runtime for watcher/session/terminal/process/server teardown.
|
|
29
29
|
- `packages/web/server/lib/opencode/server-startup-runtime.js`: server listen/startup flow and process/signal handler orchestration runtime.
|
|
30
|
-
- `packages/web/server/lib/opencode/static-routes-runtime.js`: static asset/SPA fallback route registration and manifest route wiring.
|
|
30
|
+
- `packages/web/server/lib/opencode/static-routes-runtime.js`: static asset/SPA fallback route registration and manifest route wiring. The SPA fallback sends `index.html` relative to `root`, so deep links keep working when the install lives under a dot directory.
|
|
31
31
|
- `packages/web/server/lib/opencode/feature-routes-runtime.js`: feature route composition runtime for dynamic import-backed config/skill/provider route registration.
|
|
32
32
|
- `packages/web/server/lib/conversations/`: combined create-session-and-prompt orchestration (OpenChamber-owned, registered before generic proxy).
|
|
33
33
|
- `packages/web/server/lib/session-turn-pages/`: turn-window session message pagination (`GET /api/openchamber/sessions/:sessionID/messages`), anchor reconcile (`GET .../messages/reconcile`), Changes L2/L3 (`GET .../changes`), and exact message L1 projection (`GET /api/session/:sessionID/message/:messageID`); loops official OpenCode `session.messages` for turn pages and head→anchor gap recovery with continuation/budgets/`resetRequired`; all registered before generic proxy. L1 first-packet / SSE / exact-message paths keep `summary.diffs` as a slim `{ file, status?, additions, deletions }` list plus additive `diffCount`/`hasDiffs` (patch bodies stay on L3). See `session-turn-pages/DOCUMENTATION.md`.
|
|
@@ -85,8 +85,11 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
85
85
|
- `GET /api/config/opencode-resolution`
|
|
86
86
|
- `GET /api/behavior/agents-md`: returns an authoritative empty document only when the file is absent (`ENOENT`); permission and I/O failures return HTTP 500.
|
|
87
87
|
- `PUT /api/behavior/agents-md`
|
|
88
|
-
- `POST /api/opencode/upgrade` (
|
|
89
|
-
- `GET /api/opencode/upgrade-status`
|
|
88
|
+
- `POST /api/opencode/upgrade` (ticket 12): installs the pinned/target `@opencode/cli` into the **owned cache** at `~/.config/openchamber/opencode-cli/<version>/`. Managed owned-cache processes restart on that binary; official shared services use that exact binary's `service restart`, then adopt its registration again. Success requires a fresh running-serve version probe and runtime contract verification. Arbitrary external servers, private processes using global CLI, and private bundled binaries return HTTP 409; user installs are never overwritten. Concurrent upgrades are single-flight (`operation` state). Active sessions require `confirmActiveTasks`/`force`. Failure preserves cached binaries, restores the previous binary selection, and reports the failed phase; it does not claim the old process was restored. Shared-service recovery can subsequently re-ensure the registered service.
|
|
89
|
+
- `GET /api/opencode/upgrade-status`: returns `currentVersion`/`serveVersion`/`cliVersion`, `targetVersion`/`latestVersion` (pin), `supplySource`, `ownership`, `canManage`, `management`, `guidance`, `contract`, and `operation`. Official shared services report `ownership: 'shared-service'`, `processOwnership: 'shared'`, and `canManage: true`. `available` is true only when `canManage` and target > current; unmanaged global/external runtimes report `available: null` plus manual guidance.
|
|
90
|
+
- `GET /api/opencode/contract` (ticket 11): runtime contract admission for the **running serve** (not health alone). Dimensions: reachable / authenticated / protocolCompatible / executionAllowed / capabilities / migration / CLI↔serve mismatch. Floor `2.0.12` (acceptable OpenCode 2.x at or above); below-min 2.x and 1.x block execution with upgrade guidance; no upper version cap.
|
|
91
|
+
- `GET /api/opencode/health`: probes managed OpenCode `GET /api/info` (Basic auth, 4s timeout). `healthy` is reachability/version shape via `evaluateOpenCodeHealthBody` (admits official 2.x `ServerInfo` and classic `{ healthy: true, version }`; rejects 1.x / missing / `healthy: false`). Response also includes `executionAllowed`, `protocolCompatible`, and `contract` so clients do not treat health success as full execution semantics. Upstream non-OK responses pass through status with `{ healthy: false, error, contract }`; transport failures return HTTP 503.
|
|
92
|
+
- `GET /api/opencode/version`: serve + CLI versions, mismatch flag, and contract snapshot
|
|
90
93
|
- `POST /api/opencode/directory`
|
|
91
94
|
- `GET /api/provider/:providerId/source`
|
|
92
95
|
- `DELETE /api/provider/:providerId/auth`
|
|
@@ -116,14 +119,28 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
116
119
|
|
|
117
120
|
## Public exports (opencode2-pin.js)
|
|
118
121
|
- `PINNED_OPENCODE2_VERSION`: exact desktop/runtime pin (never 1.x).
|
|
119
|
-
- `OPENCODE2_NPM_PACKAGE`: `@opencode/cli` (global install that ships the `
|
|
122
|
+
- `OPENCODE2_NPM_PACKAGE`: `@opencode/cli` (global install that ships the official `opencode` v2 binary; not 1.x `opencode-ai`).
|
|
120
123
|
- `parseOpenCode2VersionOutput(stdout)` / `compareOpenCode2Versions(left, right)` / `isOpenCode2VersionAtLeast(version, minimum)`: CLI `--version` parse and pin comparison.
|
|
121
|
-
- `openCode2BinaryName(platform)` / `npmPackageForOpenCode2(platform, arch)`: staged binary name `
|
|
122
|
-
- `isOpenCode1xVersion(value)` / `isAcceptableOpenCode2HealthVersion(value)` / `evaluateOpenCodeHealthBody(body)`: health admission
|
|
124
|
+
- `openCode2BinaryName(platform)` / `npmPackageForOpenCode2(platform, arch)`: staged binary name `opencode` (`opencode.exe` on Windows) and official platform package `@opencode/cli-<os>-<arch>[-baseline]`.
|
|
125
|
+
- `isOpenCode1xVersion(value)` / `isAcceptableOpenCode2HealthVersion(value)` / `evaluateOpenCodeHealthBody(body)`: health admission accepts only 2.x. 1.x, `0.x` pre-2 binaries, and missing/unknown versions are rejected even when `healthy: true`.
|
|
123
126
|
- `resolveOpenCode2UpgradeTarget(target)` / `rejectOpenCode1xUpgradeTarget(target)`: upgrade targets default to the pin and refuse 1.x.
|
|
124
127
|
|
|
128
|
+
## Public exports (runtime-contract.js)
|
|
129
|
+
- Ticket 11 pure admission matrix. `RUNTIME_CONTRACT_MIN_VERIFIED` (`2.0.12`) is the documented 2.x floor; capabilities include `core.protocol`, `session.prompt`, `session.queuedInput`, `session.background`, `session.generationFallback`, `migration.v1Read`.
|
|
130
|
+
- `evaluateRuntimeContract(input)`: separate reachable / auth / protocol / execution / migration / CLI↔serve mismatch reasons for UI and diagnostics. **`executionAllowed` requires acceptable OpenCode 2.x at or above the floor** (`versionBand === 'verified'`).
|
|
131
|
+
- `formatRuntimeContractBlockedError(contract)` / `runtimeContractExecutionBlockedBody(contract)`: user-facing upgrade guidance on HTTP 409 execution blocks (below-min / 1.x), not raw JSON dumps.
|
|
132
|
+
- `isRuntimeContractExecutionPath` / `isRuntimeContractDiagnosticPath` / `isRuntimeContractStopPath` / `shouldBlockRuntimeContractExecution` / `createRevokedRuntimeContract`: proxy + Host transport gate helpers. **Execution paths require `executionAllowed === true` on the current contract**; null/pending/false all block (no silent bypass). Unit/DI factories may pass `{ allowMissingContract: true }` only when no lifecycle is wired. Interrupt/abort and reads stay available when execution is limited. Health success is not full execution semantics.
|
|
133
|
+
|
|
134
|
+
## Public exports (server-opencode-fetch.js)
|
|
135
|
+
- Host transport boundary so queue / goal / scheduled / assistants / session-title cannot bypass the proxy write gate.
|
|
136
|
+
- `configureServerOpenCodeFetchGate(getter, options?)` / `createServerOpenCodeFetch` / `wrapFetchWithRuntimeContractGate`: shared admission; `makeOpenCodeV2Client` always wraps fetch with the gate. Production must not set `allowMissingContract`.
|
|
137
|
+
|
|
138
|
+
## Public exports (owned-runtime-upgrade.js)
|
|
139
|
+
- Ticket 12 ownership model: `classifyRuntimeOwnership` separates process ownership (external vs managed) from binary ownership (owned-cache / global-cli / bundled / env / settings). External serve using an owned file stays external; managed process using a global file stays global-cli.
|
|
140
|
+
- `buildUpgradeStatusSnapshot` / `evaluateOwnedUpgradeResult` / `createUpgradeOperationState` / `resolveOwnedCacheBinaryPath`.
|
|
141
|
+
|
|
125
142
|
## Public exports (ensure-cli.js)
|
|
126
|
-
- `ensurePinnedOpenCode2Cli({ discoveredPath, pin, autoInstall, install, readVersion })`: reuse any discovered acceptable 2.x CLI (official name `opencode
|
|
143
|
+
- `ensurePinnedOpenCode2Cli({ discoveredPath, pin, autoInstall, install, readVersion })`: reuse any discovered acceptable 2.x CLI (official name `opencode`). Install the pin into `~/.config/openchamber/opencode-cli/<pin>/opencode` only when nothing usable is installed. `OPENCHAMBER_OPENCODE2_AUTO_INSTALL=0` disables download (`OPENCODE_CLI_MISSING`).
|
|
127
144
|
- `installPinnedOpenCode2Cli(options)`: download `@opencode/cli-<os>-<arch>@pin`, extract, copy as `opencode`, chmod, verify `--version`.
|
|
128
145
|
- `installedOpenCode2BinaryPath(version)` / `readOpenCode2BinaryVersion(path)` / `isOpenCode2AutoInstallEnabled()`.
|
|
129
146
|
|
|
@@ -135,20 +152,33 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
135
152
|
|
|
136
153
|
## Public exports (lifecycle.js)
|
|
137
154
|
- `createOpenCodeLifecycleRuntime(dependencies)`: creates lifecycle runtime for managed/external OpenCode process orchestration. Bootstrap reuses a healthy v2 `opencode serve` when present; otherwise starts managed `opencode serve`. Before managed spawn, `ensurePinnedOpenCode2CliEnv()` detects the local CLI version and installs the pinned official V2 when missing or too old (skipped for external skip-start). Startup accepts both `server listening on http://127.0.0.1:PORT` and the legacy `opencode server listening on …` line. Readiness/health probes `GET /api/info` first, then `/global/health`, with Basic auth (username `opencode`). A body is admitted only when `evaluateOpenCodeHealthBody` accepts the version (rejects 1.x and missing/unknown). After health ok, `startOpenCode` / `waitForOpenCodeReady` poll `GET /api/experimental/migration/v1` (no POST; backfill is owned by opencode2). Each readiness attempt uses one abort timer that covers **both** health and migration so a hung migration cannot outrun the per-attempt budget. External instances are a read-only mount: restart/refresh only re-probe health + V1 migration (never kill, spawn, clear port, or flip `isExternalOpenCode`); health monitoring skips them; upgrade routes return 409. After readiness admits, lifecycle warms recent directories via optional `getWarmupDirectories` (sequential best-effort `GET /api/session?directory=&limit=1`). External skip-start / auto-detect attach leave `isOpenCodeReady` false until the gate admits. `isOpenCodeReady` means transcript may be fetched only when the gate admits (`completed`, or no V1 library such as HTTP 404). `required` / `running` / `error` keep the ready gate closed; `error` is retried. The last gate result is stored on `state.v1Migration` and published on the OpenChamber `/health` snapshot so UI can render `phase` and running `progress` (`label` / `numerator` / `denominator`) plus `userNotice` (reuse message ids; in-progress tools become interrupted; V1 subtasks do not appear in v2).
|
|
155
|
+
- Shared official service (default for Web and Electron hosts, including mobile clients connected to those hosts): when `sharedService` is injected and neither an OpenCode port nor host is configured, bootstrap runs `opencode service start` before HMR/private-process reuse or any port-4096 probe. It attaches to the registered stable-channel service, adopts its password/base URL/port, and marks it external for shutdown. Closing OpenChamber leaves that service running; no managed capabilities (scheduled-task plugin, bridge token, `v2-plugin-host-shim`) are injected. Attach failure clears URL/port/password, reports an unavailable integration and permits explicit retry, without silently spawning a private server. Explicit host/port and shared-service opt-out retain their existing behavior; VS Code retains its extension-owned sidecar lifecycle.
|
|
156
|
+
- While attached, ordinary `restartOpenCode()` re-runs `service start` and adopts the latest registration before re-probing; a failed ensure propagates instead of probing stale credentials as success. Upgrade-only `restartOpenCode({ binaryPath })` bypasses settings rediscovery and runs the verified target's `service restart`. Configuration refresh uses `POST /api/location/reload` instead of process replacement (returns `{ reloaded: true, external: true }`). Health monitoring uses registration-PID liveness. Normal attachment/recovery does not version-pin or downgrade a healthy registered service; explicit in-app upgrade is the version-replacement operation.
|
|
138
157
|
- Returned API:
|
|
139
158
|
- `startOpenCode()`
|
|
140
159
|
- `restartOpenCode()`
|
|
160
|
+
- `isSharedOpenCodeService()`: true while attached to the shared official service.
|
|
141
161
|
- `waitForOpenCodeReady(timeoutMs?, intervalMs?)`
|
|
142
162
|
- `waitForAgentPresence(agentName, timeoutMs?, intervalMs?)`
|
|
143
163
|
- `refreshOpenCodeAfterConfigChange(reason, options?)`
|
|
144
164
|
- `bootstrapOpenCodeAtStartup()`
|
|
145
165
|
- `retryOpenCodeStartup()`
|
|
146
166
|
- `startHealthMonitoring(healthCheckIntervalMs)`
|
|
147
|
-
- `waitForPortRelease(port, timeoutMs, hostname?)
|
|
148
|
-
- `killProcessOnPort(port)`
|
|
167
|
+
- `waitForPortRelease(port, timeoutMs, hostname?)`: probe-only; reports whether the listener freed without killing occupants.
|
|
168
|
+
- `killProcessOnPort(port, ownedPid?)`: legacy helper retained for tests/compat; force-signals only an explicit owned pid/process group when `ownedPid` is provided. Restart and graceful shutdown **do not** call it after `child.close()` (close owns escalation; a second kill of a captured pid risks recycling). Never mass-kills by port via `lsof`.
|
|
169
|
+
|
|
170
|
+
## Public exports (shared-service.js)
|
|
171
|
+
- Discovery and start of the official `opencode serve --service` background service shared with official Desktop/TUI (one process, one database, so providers and plugins stay in sync across clients).
|
|
172
|
+
- `resolveSharedServiceRegistrationPath(env?, homeDir?)`: `${XDG_STATE_HOME || ~/.local/state}/opencode/service.json` (stable channels only).
|
|
173
|
+
- `isSharedServiceEnabled(env?)`: on by default; `OPENCHAMBER_OPENCODE_SHARED_SERVICE=0|false|off` opts out.
|
|
174
|
+
- `parseSharedServiceRegistration(text)` / `readSharedServiceRegistration({ file, fsLike })`: `{ origin, port, pid, password, version }` or `null`; missing/malformed files are absent, never a service.
|
|
175
|
+
- `buildSharedServiceStartEnv(env)`: strips `OPENCHAMBER_*`, `OPENCODE_SERVER_PASSWORD`, and `OPENCODE_PASSWORD` so the long-lived service never inherits OpenChamber credentials.
|
|
176
|
+
- `startSharedOpenCodeService({ binary, args, env, spawnImpl, timeoutMs, replace })`: runs `<binary> [...args] service start`, or `service restart` for explicit `replace: true`; rejects with the stderr tail on non-zero exit or timeout. The launcher has hidden Windows console and does not own the detached service lifetime.
|
|
177
|
+
- `isProcessAlive(pid)`, `createSharedOpenCodeService({ envLike, fsLike, spawnImpl })`: the lifecycle dependency (`start`, `readRegistration`, `buildStartEnv`).
|
|
178
|
+
- `shared-service.integration.test.js` is opt-in via `OPENCHAMBER_TEST_OPENCODE_BINARY`; it isolates HOME/XDG/config/database and verifies launcher exit survival, PID reuse, explicit replacement and authenticated health, then stops only its isolated service.
|
|
149
179
|
|
|
150
180
|
## Public exports (env-runtime.js)
|
|
151
|
-
- `createOpenCodeEnvRuntime(dependencies)`: creates runtime that owns OpenCode CLI environment and binary discovery state.
|
|
181
|
+
- `createOpenCodeEnvRuntime(dependencies)`: creates runtime that owns OpenCode CLI environment and binary discovery state. Discovery order: settings/env, then PATH `opencode`, then known install locations, then a previously installed pin under the OpenChamber data dir. Automatic discovery and bundled resources use only `opencode` (`opencode.exe` on Windows); 1.x binaries are skipped by `--version`, not by basename. `resolveOpencodeCliPath()` never prefers a packaged/bundled Electron extraResource. Managed startup calls `ensurePinnedOpenCode2CliEnv()` and installs the pin only when no usable 2.x CLI exists; if pin install fails (or auto-install is disabled), it may last-resort to a 2.x binary under `OPENCHAMBER_BUNDLED_OPENCODE_CLI_DIR` or `process.resourcesPath/opencode-cli` (source `bundled`). Explicit `OPENCODE_BINARY` that reports 1.x fails closed with `OPENCODE_BINARY_INVALID`. Bootstrap reuses a healthy v2 `opencode serve` on port 4096 when present; otherwise it starts generic `opencode serve`.
|
|
152
182
|
- VS Code keeps a copied sidecar (`packages/vscode/src/opencode-sidecar.ts`) with the same invariants: discovery order may differ (no Electron bundled fallback), but reject list, listening lines, health path order (`/api/health` then `/global/health`), `{ healthy: true }`, and Basic username `opencode` must stay aligned. Changing one copy requires changing the other.
|
|
153
183
|
- Returned API:
|
|
154
184
|
- `applyLoginShellEnvSnapshot()`
|
|
@@ -164,11 +194,12 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
164
194
|
- `isExecutable(filePath)`
|
|
165
195
|
- `searchPathFor(binaryName)`
|
|
166
196
|
- `clearResolvedOpenCodeBinary()`
|
|
197
|
+
- `forceResolvedOpenCodeBinary(path, source?)`: pin managed launch to an explicit binary (owned-cache upgrade) so restart does not rediscover a global CLI
|
|
167
198
|
|
|
168
199
|
## Public exports (managed-capabilities-runtime.js)
|
|
169
200
|
- `createManagedCapabilitiesRuntime(dependencies?)`: returns resource publishing, bridge-origin, child-environment, identity, and bridge-authorization APIs for managed OpenCode only.
|
|
170
201
|
- `mergeManagedOpenCodeConfig({ configContent, pluginUrl, instructionsUrl })`: merges injected plugin/instructions URLs with stable deduplication.
|
|
171
|
-
- Managed child config injects the plugin as a file URL and instructions as an absolute filesystem path. Capability identity validates the current managed PID and its liveness before HMR reuse or bridge authorization.
|
|
202
|
+
- Managed child config injects the scheduled-task plugin as a file URL and instructions as an absolute filesystem path, then appends the `v2-plugin-host-shim` directory. OpenCode 2.0.12 has no `ctx.catalog`; that shim re-invokes catalog-based user plugins against `provider.transform`. Capability identity validates the current managed PID and its liveness before HMR reuse or bridge authorization.
|
|
172
203
|
- `bootstrap-runtime.js` forwards managed bridge authorization to the API auth gate. The reserved bridge path accepts only the current managed-child capability and returns HTTP 403 before UI, tunnel, or client authentication for every other request. `feature-routes-runtime.js` registers the managed scheduled-task bridge before generic OpenCode proxy composition.
|
|
173
204
|
- Capability resources use an application-version and source-content SHA-256 fingerprint directory. The bridge token is a managed OpenCode process capability: every plugin loaded into that process belongs to the trusted-code boundary. External OpenCode receives no managed capability injection or bridge authorization.
|
|
174
205
|
|
|
@@ -256,11 +287,40 @@ This module provides OpenCode server integration utilities for the web server ru
|
|
|
256
287
|
- `resolveProjectDirectory(req)`
|
|
257
288
|
- `resolveOptionalProjectDirectory(req)`
|
|
258
289
|
|
|
290
|
+
## V2 configuration persistence and activation
|
|
291
|
+
|
|
292
|
+
Agent, Command, MCP, Plugin and Skill mutation routes write their existing local
|
|
293
|
+
configuration paths and return `{ success: true, requiresReload: false,
|
|
294
|
+
application: 'watch' }` (skill install retains its `ok` result). They do not call
|
|
295
|
+
the process lifecycle or `location.reload`. V2 Config watchers and plugin/domain
|
|
296
|
+
reconciliation apply watched changes; frontend domain events refresh catalogs.
|
|
297
|
+
The receipt means **persisted**, not confirmed activation. A remote external
|
|
298
|
+
OpenCode with a different filesystem cannot see Host-local writes merely because
|
|
299
|
+
the write succeeded; restarting that server does not transfer those files either.
|
|
300
|
+
|
|
301
|
+
Raw global `opencode` config saves no longer request a restart. Opaque plugin-owned
|
|
302
|
+
JSON targets retain `requiresManualRestart: true` because OpenCode's config watcher
|
|
303
|
+
does not guarantee those plugins reload their private configuration. Explicit UI
|
|
304
|
+
configuration reload uses official SDK `location.reload`. The historical
|
|
305
|
+
`/api/config/reload` host/bridge endpoint remains reserved for existing process
|
|
306
|
+
startup/recovery/binary-change callers, not ordinary Settings mutations.
|
|
307
|
+
|
|
308
|
+
Command catalog SDK fetch forwarding preserves the generated request options,
|
|
309
|
+
including authentication headers, and combines caller cancellation with its
|
|
310
|
+
eight-second request deadline.
|
|
311
|
+
|
|
312
|
+
`config-hot-reload.integration.test.js` is opt-in via
|
|
313
|
+
`OPENCHAMBER_TEST_OPENCODE_BINARY=<absolute-v2-cli>`. It starts a separate loopback
|
|
314
|
+
server with isolated HOME/XDG/config/database, checks global command changes,
|
|
315
|
+
plugin disable/re-enable, credentials and MCP config changes without reload or
|
|
316
|
+
restart, then terminates only its own child and removes its temporary files.
|
|
317
|
+
It makes no model requests and never reads real user credentials.
|
|
318
|
+
|
|
259
319
|
## Public exports (config-entity-routes.js)
|
|
260
320
|
- `registerConfigEntityRoutes(app, dependencies)`: registers configuration entity routes:
|
|
261
|
-
- `GET /api/config/catalog/providers` resolves the request project directory,
|
|
262
|
-
- Provider catalog responses include only provider `id`, `name`, and safe models. Safe models include `id`, `name`, fixed text/audio/image/video/pdf capability modalities, bounded cost/limit fields, `release_date`, and variant names with `{}` values. Identifiers are bounded to 512 characters, display names to 1024, release dates to 64, numeric values to absolute 1e9, and catalog collections to 200 providers, 500 models per provider, and 100 defaults or variants. Provider credentials/configuration and unallowlisted model fields stay server-side. Malformed catalog roots and SDK error envelopes return HTTP 502. `partial: true` is structural only: provider/model/default truncation, dropped providers/models/defaults, or truncated variant dictionaries. Soft allowlist stripping of optional metadata (unknown modalities, non-boolean capability flags, out-of-range cost/limit numbers, empty/null/invalid `release_date
|
|
263
|
-
- Agents: `/api/config/agents/:name` and `/api/config/agents/:name/config`
|
|
321
|
+
- `GET /api/config/catalog/providers` resolves the request project directory, composes official v2 `provider.list` + `model.list` (+ `model.default`), and returns schema version `1` with an allowlisted provider/model catalog. Catalog model ids and defaults use `ModelInfo.id` (the external generate/session id). `ModelInfo.modelID` is an internal field and is not the catalog key. The route is available through the shared web host used by Web, Electron, hosted mobile, and Capacitor mobile, before generic OpenCode proxy handling.
|
|
322
|
+
- Provider catalog responses include only provider `id`, `name`, and safe models. Safe models include `id`, `name`, fixed text/audio/image/video/pdf capability modalities, bounded cost/limit fields, `release_date`, and variant names with `{}` values. v2 `ModelInfo.variants` is an array of `{ id, settings?, headers?, body? }`; projection keeps ids in array order as empty-object keys and drops request payloads. v2 `capabilities.tools` maps to `toolcall`, and `input`/`output` modality name arrays map to boolean maps. v2 `cost` tier arrays keep the untiered base price. Identifiers are bounded to 512 characters, display names to 1024, release dates to 64, numeric values to absolute 1e9, and catalog collections to 200 providers, 500 models per provider, and 100 defaults or variants. Provider credentials/configuration and unallowlisted model fields stay server-side. Malformed catalog roots and SDK error envelopes return HTTP 502. `partial: true` is structural only: provider/model/default truncation, dropped providers/models/defaults, or truncated variant dictionaries. Soft allowlist stripping of optional metadata (unknown modalities, non-boolean capability flags, out-of-range cost/limit numbers, empty/null/invalid `release_date`, invalid variant ids) keeps valid models and must not set `partial: true`, or UI refresh retains a stale complete snapshot after OpenCode provider updates. The same input shapes are accepted by the VS Code projector and `packages/ui/src/lib/configCatalogParser.ts`.
|
|
323
|
+
- Agents: `/api/config/agents/:name` and `/api/config/agents/:name/config`. A `native` create or update writes only the fields in that object into the selected user or project markdown file. `scope` chooses the file. `confirmDrop` removes fields the current format cannot represent; every other field already in the file stays unchanged.
|
|
264
324
|
- Commands: batched metadata via `POST /api/config/commands/metadata`; `{ catalog: true }` returns the compact autocomplete catalog without templates, plus CRUD at `/api/config/commands/:name`
|
|
265
325
|
- Global raw configs: `GET /api/config/global` discovers existing config targets; `GET/PUT /api/config/global/:target` reads and writes `opencode`, `oh-my-opencode-slim`, and `oh-my-openagent` JSON or JSONC files
|
|
266
326
|
- MCP servers: `/api/config/mcp` and `/api/config/mcp/:name`
|
|
@@ -282,6 +342,7 @@ When adding or changing Host HTTP APIs that mobile/desktop clients reach over Pr
|
|
|
282
342
|
- `getOpenCodeAuthHeaders()`
|
|
283
343
|
- `isOpenCodeConnectionSecure()`
|
|
284
344
|
- `ensureLocalOpenCodeServerPassword(options?)`
|
|
345
|
+
- `adoptOpenCodeServerPassword(password, source)`: adopts a password owned by another process (the shared official service registration); `null` clears it on attach rollback.
|
|
285
346
|
|
|
286
347
|
## Public exports (core-routes.js)
|
|
287
348
|
- `registerServerStatusRoutes(app, dependencies)`: registers status/system endpoints:
|
|
@@ -336,9 +397,13 @@ When adding or changing Host HTTP APIs that mobile/desktop clients reach over Pr
|
|
|
336
397
|
- `setupProxy(app)`
|
|
337
398
|
|
|
338
399
|
## Public exports (shutdown-runtime.js)
|
|
339
|
-
- `createGracefulShutdownRuntime(dependencies)`: creates graceful shutdown runtime for managed OpenCode and web server teardown sequencing.
|
|
400
|
+
- `createGracefulShutdownRuntime(dependencies)`: creates graceful shutdown runtime for managed OpenCode and web server teardown sequencing. Stops managed OpenCode only via `openCodeProcess.close()` (that handle owns SIGTERM→SIGKILL). After close, `waitForPortRelease(port, 5000)` probes whether the listener freed — never re-kills a pre-captured numeric pid (recycle hazard) and never mass-kills by port. When `forceCloseConnections: true`, after `server.close()` it runs `forceCloseHttpServerConnections` (`closeAllConnections` plus destroy of tracked upgraded sockets) so the HTTP close callback can fire without waiting `SHUTDOWN_TIMEOUT` on leftover WS/SSE.
|
|
340
401
|
- Returned API:
|
|
341
|
-
- `gracefulShutdown(options?)`: accepts `forceCloseConnections: true` for runtimes that close remaining HTTP connections after initiating server close.
|
|
402
|
+
- `gracefulShutdown(options?)`: accepts `forceCloseConnections: true` for runtimes that close remaining HTTP connections after initiating server close (including upgraded sockets tracked by `attachHttpServerConnectionTracker`).
|
|
403
|
+
|
|
404
|
+
## Public exports (http-server-connections.js)
|
|
405
|
+
- `attachHttpServerConnectionTracker(server)`: tracks sockets from the server's `connection` event (covers later upgrades). Returns `{ getOpenSocketCount, destroyRemainingSockets, dispose }`.
|
|
406
|
+
- `forceCloseHttpServerConnections(server)`: `closeAllConnections` then destroys remaining tracked sockets; returns `{ destroyedTracked }`. Only the owning server's sockets — never an external server.
|
|
342
407
|
|
|
343
408
|
## Public exports (server-startup-runtime.js)
|
|
344
409
|
- `createServerStartupRuntime(dependencies)`: creates runtime for server bind/listen and process handler wiring.
|
|
@@ -393,6 +458,8 @@ When adding or changing Host HTTP APIs that mobile/desktop clients reach over Pr
|
|
|
393
458
|
- `POST /api/projects/:projectId/icon/discover`
|
|
394
459
|
|
|
395
460
|
## Public exports (skill-routes.js)
|
|
461
|
+
|
|
462
|
+
The V2 skill catalog uses `id` as the existing OpenChamber `name` invocation key and `path` as the file location. Frontmatter `name` is a display label, never an invocation key. Local fallback discovery uses the same path-derived ID, so live and local entries deduplicate correctly. Web, Electron and mobile share this route; VS Code mirrors the projection through `skill.list` in its extension host. Upstream failures remain errors rather than empty successful catalogs.
|
|
396
463
|
- `registerSkillRoutes(app, dependencies)`: registers skills-related routes:
|
|
397
464
|
- Skills config CRUD and metadata under `/api/config/skills*`; `summary=true` returns compact autocomplete fields without skill content or sources
|
|
398
465
|
- Skills catalog listing/source pagination, scan, and install routes
|
|
@@ -408,6 +475,7 @@ When adding or changing Host HTTP APIs that mobile/desktop clients reach over Pr
|
|
|
408
475
|
- Generic `/api/*` forwarding with hop-by-hop header filtering. v2 upstream routes live under `/api` (`@opencode-ai/protocol`); the proxy restores that prefix after Express strips the `/api` mount. Forwarding `/agent` (v1 root) hits the v2 Web UI HTML fallback.
|
|
409
476
|
- Windows `/session` merge fallback path behavior
|
|
410
477
|
- OpenCode readiness gate for proxied `/api` requests: process warmup still uses `OPEN_CODE_READY_GRACE_MS`; `state.v1Migration.admitTranscript !== true` holds with no grace until the gate admits or the client cancels, so a `running` migration past 12s cannot `next()` an empty session list through as success
|
|
478
|
+
- Runtime contract execution gate (ticket 11): mutating session/turn paths require **current-instance** `executionAllowed === true` (null/pending/false → HTTP 409 `RUNTIME_CONTRACT_EXECUTION_BLOCKED`). The gate runs before readiness hold and **again after hold** so a stale permit cannot forward once the new instance is incompatible. Lifecycle **revokes** the permit (generation bump + pending contract) on start/restart; async health probes only publish when their generation still matches. Diagnostic GET health/info/migration paths, ordinary reads, and **interrupt/abort (stop-task)** stay available. Host background clients share the same gate via `server-opencode-fetch` / gated `makeOpenCodeV2Client`.
|
|
411
479
|
|
|
412
480
|
## Public exports (watcher.js)
|
|
413
481
|
- `createOpenCodeWatcherRuntime(dependencies)`: creates global event watcher runtime backed by the shared upstream SSE reader.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class DropConfirmationRequired extends Error {
|
|
2
|
+
code: 'drop-confirmation';
|
|
3
|
+
dropped: Array<{ key: string; reason: string }>;
|
|
4
|
+
constructor(dropped: Array<{ key: string; reason: string }>);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function inspectAgentConfig(config: unknown): {
|
|
8
|
+
legacy: boolean;
|
|
9
|
+
dropped: Array<{ key: string; reason: string }>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function convertAgentConfig(config: unknown): {
|
|
13
|
+
frontmatter: Record<string, unknown>;
|
|
14
|
+
body: string;
|
|
15
|
+
legacy: boolean;
|
|
16
|
+
dropped: Array<{ key: string; reason: string }>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function applyNativePatch(
|
|
20
|
+
current: { frontmatter: Record<string, unknown>; body: string },
|
|
21
|
+
patch: Record<string, unknown>,
|
|
22
|
+
): { frontmatter: Record<string, unknown>; body: string };
|
|
23
|
+
|
|
24
|
+
export function writeAgentDocument(
|
|
25
|
+
existing: { frontmatter?: Record<string, unknown>; body?: string },
|
|
26
|
+
patch: Record<string, unknown>,
|
|
27
|
+
confirmDrop: boolean,
|
|
28
|
+
): { frontmatter: Record<string, unknown>; body: string };
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode v2 agent markdown.
|
|
3
|
+
*
|
|
4
|
+
* A file is native only when every frontmatter key is a current agent field.
|
|
5
|
+
* Any older key makes the loader migrate the whole file, and native keys in
|
|
6
|
+
* that file are then treated as request body. Saving therefore rewrites the
|
|
7
|
+
* whole file as native markdown. Fields the runtime does not apply are reported
|
|
8
|
+
* and kept until the caller confirms they can be dropped.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const NATIVE_KEYS = new Set([
|
|
12
|
+
'model',
|
|
13
|
+
'variant',
|
|
14
|
+
'request',
|
|
15
|
+
'system',
|
|
16
|
+
'description',
|
|
17
|
+
'mode',
|
|
18
|
+
'hidden',
|
|
19
|
+
'color',
|
|
20
|
+
'steps',
|
|
21
|
+
'disabled',
|
|
22
|
+
'permissions',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const ACTION_RENAMES = {
|
|
26
|
+
bash: 'shell',
|
|
27
|
+
task: 'subagent',
|
|
28
|
+
write: 'edit',
|
|
29
|
+
patch: 'edit',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
|
|
33
|
+
const PROMPT_FILE = /^\{file:.+\}$/i;
|
|
34
|
+
const EFFECTS = new Set(['allow', 'ask', 'deny']);
|
|
35
|
+
|
|
36
|
+
export class DropConfirmationRequired extends Error {
|
|
37
|
+
/**
|
|
38
|
+
* @param {Array<{ key: string, reason: string }>} dropped
|
|
39
|
+
*/
|
|
40
|
+
constructor(dropped) {
|
|
41
|
+
super('Drop confirmation required');
|
|
42
|
+
this.code = 'drop-confirmation';
|
|
43
|
+
this.dropped = dropped;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const isEffect = (value) => typeof value === 'string' && EFFECTS.has(value);
|
|
48
|
+
|
|
49
|
+
const renameAction = (action) => ACTION_RENAMES[action] || action;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {unknown} config
|
|
53
|
+
* @returns {{ legacy: boolean, dropped: Array<{ key: string, reason: string }> }}
|
|
54
|
+
*/
|
|
55
|
+
export function inspectAgentConfig(config) {
|
|
56
|
+
const source = config && typeof config === 'object' && !Array.isArray(config) ? config : {};
|
|
57
|
+
/** @type {Array<{ key: string, reason: string }>} */
|
|
58
|
+
const dropped = [];
|
|
59
|
+
const keys = Object.keys(source);
|
|
60
|
+
|
|
61
|
+
for (const key of keys) {
|
|
62
|
+
if (key === 'temperature' || key === 'top_p') {
|
|
63
|
+
dropped.push({ key, reason: 'generation' });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (key === 'options' && source.options && typeof source.options === 'object' && Object.keys(source.options).length > 0) {
|
|
67
|
+
dropped.push({ key, reason: 'unknown' });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (key === 'prompt' && typeof source.prompt === 'string' && PROMPT_FILE.test(source.prompt.trim())) {
|
|
71
|
+
dropped.push({ key, reason: 'prompt-file' });
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (key === 'color' && source.color != null && source.color !== '' && !HEX_COLOR.test(String(source.color))) {
|
|
75
|
+
dropped.push({ key, reason: 'color' });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (key === 'variant' && (typeof source.model !== 'string' || source.model.length === 0)) {
|
|
79
|
+
dropped.push({ key, reason: 'variant' });
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!NATIVE_KEYS.has(key) && !isConvertibleKey(key)) {
|
|
83
|
+
dropped.push({ key, reason: 'unknown' });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const legacy = keys.some((key) => !NATIVE_KEYS.has(key)) || dropped.length > 0;
|
|
88
|
+
return { legacy, dropped };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const isConvertibleKey = (key) => (
|
|
92
|
+
key === 'prompt'
|
|
93
|
+
|| key === 'tools'
|
|
94
|
+
|| key === 'disable'
|
|
95
|
+
|| key === 'maxSteps'
|
|
96
|
+
|| key === 'permission'
|
|
97
|
+
|| key === 'name'
|
|
98
|
+
|| key === 'options'
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {unknown} config
|
|
103
|
+
* @returns {{ frontmatter: Record<string, unknown>, body: string, legacy: boolean, dropped: Array<{ key: string, reason: string }> }}
|
|
104
|
+
*/
|
|
105
|
+
export function convertAgentConfig(config) {
|
|
106
|
+
const source = config && typeof config === 'object' && !Array.isArray(config) ? { ...config } : {};
|
|
107
|
+
const inspection = inspectAgentConfig(source);
|
|
108
|
+
/** @type {Record<string, unknown>} */
|
|
109
|
+
const frontmatter = {};
|
|
110
|
+
|
|
111
|
+
if (typeof source.description === 'string' && source.description.trim()) {
|
|
112
|
+
frontmatter.description = source.description;
|
|
113
|
+
}
|
|
114
|
+
if (source.mode === 'primary' || source.mode === 'subagent' || source.mode === 'all') {
|
|
115
|
+
frontmatter.mode = source.mode;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const model = encodeModel(source.model, source.variant);
|
|
119
|
+
if (model) frontmatter.model = model;
|
|
120
|
+
|
|
121
|
+
if (source.hidden === true) frontmatter.hidden = true;
|
|
122
|
+
if (source.disabled === true || source.disable === true) frontmatter.disabled = true;
|
|
123
|
+
if (typeof source.color === 'string' && HEX_COLOR.test(source.color)) frontmatter.color = source.color;
|
|
124
|
+
|
|
125
|
+
const steps = positiveInt(source.steps) ?? positiveInt(source.maxSteps);
|
|
126
|
+
if (steps !== undefined) frontmatter.steps = steps;
|
|
127
|
+
|
|
128
|
+
if (isRequest(source.request)) frontmatter.request = source.request;
|
|
129
|
+
|
|
130
|
+
const permissions = normalizePermissions(source.permissions ?? source.permission, source.tools);
|
|
131
|
+
if (permissions.length > 0) frontmatter.permissions = permissions;
|
|
132
|
+
|
|
133
|
+
const body = readSystem(source);
|
|
134
|
+
return { frontmatter, body, legacy: inspection.legacy, dropped: inspection.dropped };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {{ frontmatter: Record<string, unknown>, body: string }} current
|
|
139
|
+
* @param {Record<string, unknown>} patch
|
|
140
|
+
*/
|
|
141
|
+
export function applyNativePatch(current, patch) {
|
|
142
|
+
const frontmatter = { ...current.frontmatter };
|
|
143
|
+
let body = current.body;
|
|
144
|
+
const changes = patch && typeof patch === 'object' ? patch : {};
|
|
145
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
146
|
+
if (value === undefined || key === 'name' || key === 'scope' || key === 'confirmDrop') continue;
|
|
147
|
+
if (key === 'system') {
|
|
148
|
+
body = value === null ? '' : String(value);
|
|
149
|
+
delete frontmatter.system;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (value === null || value === false && (key === 'hidden' || key === 'disabled')) {
|
|
153
|
+
delete frontmatter[key];
|
|
154
|
+
if (key === 'model') delete frontmatter.variant;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (key === 'color' && (typeof value !== 'string' || !HEX_COLOR.test(value))) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (key === 'steps') {
|
|
161
|
+
const steps = positiveInt(value);
|
|
162
|
+
if (steps === undefined) delete frontmatter.steps;
|
|
163
|
+
else frontmatter.steps = steps;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (key === 'permissions') {
|
|
167
|
+
const permissions = normalizePermissions(value, undefined);
|
|
168
|
+
delete frontmatter.permission;
|
|
169
|
+
if (permissions.length === 0) delete frontmatter.permissions;
|
|
170
|
+
else frontmatter.permissions = permissions;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (key === 'model') delete frontmatter.variant;
|
|
174
|
+
frontmatter[key] = value;
|
|
175
|
+
}
|
|
176
|
+
return { frontmatter, body };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Write only the keys in `patch` onto the existing markdown document.
|
|
181
|
+
* `confirmDrop` removes fields the current format cannot represent.
|
|
182
|
+
* Unmentioned fields, including permissions, stay as they are.
|
|
183
|
+
*
|
|
184
|
+
* @param {{ frontmatter?: Record<string, unknown>, body?: string }} existing
|
|
185
|
+
* @param {Record<string, unknown>} patch
|
|
186
|
+
* @param {boolean} confirmDrop
|
|
187
|
+
*/
|
|
188
|
+
export function writeAgentDocument(existing, patch, confirmDrop) {
|
|
189
|
+
const frontmatter = { ...(existing?.frontmatter || {}) };
|
|
190
|
+
let body = typeof existing?.body === 'string' ? existing.body : '';
|
|
191
|
+
if (confirmDrop) {
|
|
192
|
+
const inspection = inspectAgentConfig({
|
|
193
|
+
...frontmatter,
|
|
194
|
+
...(body.trim() ? { prompt: body } : {}),
|
|
195
|
+
});
|
|
196
|
+
for (const item of inspection.dropped) {
|
|
197
|
+
if (item.key === 'prompt') {
|
|
198
|
+
body = '';
|
|
199
|
+
delete frontmatter.prompt;
|
|
200
|
+
} else {
|
|
201
|
+
delete frontmatter[item.key];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return applyNativePatch({ frontmatter, body }, patch);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const encodeModel = (model, variant) => {
|
|
209
|
+
if (typeof model !== 'string') return undefined;
|
|
210
|
+
const trimmed = model.trim();
|
|
211
|
+
if (!trimmed) return undefined;
|
|
212
|
+
if (trimmed.includes('#')) return trimmed;
|
|
213
|
+
if (typeof variant === 'string' && variant.trim() && !variant.includes('#')) {
|
|
214
|
+
return `${trimmed}#${variant.trim()}`;
|
|
215
|
+
}
|
|
216
|
+
return trimmed;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const positiveInt = (value) => {
|
|
220
|
+
const number = typeof value === 'number' ? value : typeof value === 'string' && value.trim() ? Number(value) : NaN;
|
|
221
|
+
if (!Number.isInteger(number) || number < 1) return undefined;
|
|
222
|
+
return number;
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const isRequest = (value) => {
|
|
226
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
227
|
+
const request = /** @type {Record<string, unknown>} */ (value);
|
|
228
|
+
return request.headers != null || request.body != null;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const readSystem = (source) => {
|
|
232
|
+
if (typeof source.system === 'string') return source.system;
|
|
233
|
+
if (typeof source.prompt === 'string' && !PROMPT_FILE.test(source.prompt.trim())) return source.prompt;
|
|
234
|
+
return '';
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const normalizePermissions = (permission, tools) => {
|
|
238
|
+
/** @type {Array<{ action: string, resource: string, effect: string }>} */
|
|
239
|
+
const rules = [];
|
|
240
|
+
if (tools && typeof tools === 'object' && !Array.isArray(tools)) {
|
|
241
|
+
for (const [tool, enabled] of Object.entries(tools)) {
|
|
242
|
+
if (typeof enabled !== 'boolean') continue;
|
|
243
|
+
pushRule(rules, renameAction(tool), '*', enabled ? 'allow' : 'deny');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (isEffect(permission)) {
|
|
247
|
+
pushRule(rules, '*', '*', permission);
|
|
248
|
+
} else if (Array.isArray(permission)) {
|
|
249
|
+
for (const rule of permission) {
|
|
250
|
+
if (!rule || typeof rule !== 'object') continue;
|
|
251
|
+
if (typeof rule.permission === 'string' && typeof rule.pattern === 'string') {
|
|
252
|
+
pushRule(rules, renameAction(rule.permission), rule.pattern, rule.action);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
pushRule(rules, renameAction(rule.action), rule.resource, rule.effect);
|
|
256
|
+
}
|
|
257
|
+
} else if (permission && typeof permission === 'object') {
|
|
258
|
+
for (const [key, rule] of Object.entries(permission)) {
|
|
259
|
+
const action = renameAction(key);
|
|
260
|
+
if (isEffect(rule)) {
|
|
261
|
+
pushRule(rules, action, '*', rule);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (rule && typeof rule === 'object' && !Array.isArray(rule)) {
|
|
265
|
+
for (const [resource, effect] of Object.entries(rule)) {
|
|
266
|
+
pushRule(rules, action, resource, effect);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return rules;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const pushRule = (rules, action, resource, effect) => {
|
|
275
|
+
if (!action || !resource || !isEffect(effect)) return;
|
|
276
|
+
const index = rules.findIndex((rule) => rule.action === action && rule.resource === resource);
|
|
277
|
+
const next = { action, resource, effect };
|
|
278
|
+
if (index >= 0) rules[index] = next;
|
|
279
|
+
else rules.push(next);
|
|
280
|
+
};
|