@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
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs"
|
|
2
|
+
import { homedir } from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
import { pathToFileURL } from "node:url"
|
|
5
|
+
|
|
6
|
+
const stripJsonc = (text) => text
|
|
7
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
8
|
+
.replace(/(^|[^:\\])\/\/.*$/gm, "$1")
|
|
9
|
+
|
|
10
|
+
const readConfigPlugins = () => {
|
|
11
|
+
const specs = []
|
|
12
|
+
const files = []
|
|
13
|
+
if (process.env.OPENCODE_CONFIG) files.push(process.env.OPENCODE_CONFIG)
|
|
14
|
+
const dirs = [process.env.OPENCODE_CONFIG_DIR, path.join(homedir(), ".config", "opencode")].filter(Boolean)
|
|
15
|
+
for (const dir of dirs) {
|
|
16
|
+
files.push(path.join(dir, "opencode.jsonc"), path.join(dir, "opencode.json"), path.join(dir, "config.json"))
|
|
17
|
+
}
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(stripJsonc(readFileSync(file, "utf8")))
|
|
21
|
+
const plugin = parsed?.plugin
|
|
22
|
+
const list = Array.isArray(plugin) ? plugin : plugin ? [plugin] : []
|
|
23
|
+
for (const entry of list) {
|
|
24
|
+
if (typeof entry === "string") specs.push(entry)
|
|
25
|
+
else if (Array.isArray(entry) && typeof entry[0] === "string") specs.push(entry[0])
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// Missing or invalid config files are skipped; OpenCode still loads the ones it can parse.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return [...new Set(specs)]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const packageName = (spec) => {
|
|
35
|
+
const slash = spec.startsWith("@") ? spec.indexOf("/", 1) : -1
|
|
36
|
+
const versionAt = spec.lastIndexOf("@")
|
|
37
|
+
if (versionAt > 0 && versionAt > slash) return spec.slice(0, versionAt)
|
|
38
|
+
return spec
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resolvePackageEntry = (spec) => {
|
|
42
|
+
const name = packageName(spec)
|
|
43
|
+
const roots = [
|
|
44
|
+
path.join(homedir(), ".cache", "opencode", "npm"),
|
|
45
|
+
path.join(homedir(), ".cache", "opencode", "packages"),
|
|
46
|
+
]
|
|
47
|
+
const candidates = []
|
|
48
|
+
for (const root of roots) {
|
|
49
|
+
let entries = []
|
|
50
|
+
try {
|
|
51
|
+
entries = readdirSync(root)
|
|
52
|
+
} catch {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (entry !== name && !entry.startsWith(`${name}@`)) continue
|
|
57
|
+
const base = path.join(root, entry)
|
|
58
|
+
const packageDirs = [path.join(base, "node_modules", name)]
|
|
59
|
+
try {
|
|
60
|
+
for (const child of readdirSync(base)) {
|
|
61
|
+
if (/^\d+$/.test(child)) packageDirs.push(path.join(base, child, "node_modules", name))
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// The cache entry may be the package directory itself.
|
|
65
|
+
}
|
|
66
|
+
for (const pkgDir of packageDirs) {
|
|
67
|
+
try {
|
|
68
|
+
const pkg = JSON.parse(readFileSync(path.join(pkgDir, "package.json"), "utf8"))
|
|
69
|
+
if (pkg.name !== name) continue
|
|
70
|
+
const exported = pkg.exports?.["."]
|
|
71
|
+
const rel = typeof exported === "string" ? exported : pkg.module || pkg.main || "index.mjs"
|
|
72
|
+
candidates.push({
|
|
73
|
+
dir: pkgDir,
|
|
74
|
+
entry: path.resolve(pkgDir, rel),
|
|
75
|
+
mtime: statSync(path.join(pkgDir, "package.json")).mtimeMs,
|
|
76
|
+
})
|
|
77
|
+
} catch {
|
|
78
|
+
// Not a readable package directory.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
candidates.sort((left, right) => right.mtime - left.mtime)
|
|
84
|
+
return candidates[0] || null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const usesCatalog = (dir) => {
|
|
88
|
+
const stack = [dir]
|
|
89
|
+
let seen = 0
|
|
90
|
+
while (stack.length > 0 && seen < 80) {
|
|
91
|
+
const current = stack.pop()
|
|
92
|
+
let names = []
|
|
93
|
+
try {
|
|
94
|
+
names = readdirSync(current)
|
|
95
|
+
} catch {
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
if (name === "node_modules" || name === ".git") continue
|
|
100
|
+
const full = path.join(current, name)
|
|
101
|
+
let info
|
|
102
|
+
try {
|
|
103
|
+
info = statSync(full)
|
|
104
|
+
} catch {
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (info.isDirectory()) {
|
|
108
|
+
stack.push(full)
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (!/\.(?:mjs|cjs|js)$/.test(name) || info.size > 2_000_000) continue
|
|
112
|
+
seen += 1
|
|
113
|
+
try {
|
|
114
|
+
if (readFileSync(full, "utf8").includes(".catalog")) return true
|
|
115
|
+
} catch {
|
|
116
|
+
// Unreadable files are not catalog callers.
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const catalogFor = (ctx) => ({
|
|
124
|
+
// OpenCode 2.0.12 removed ctx.catalog. Model plugins still call
|
|
125
|
+
// draft.provider.update / draft.model.update inside catalog.transform.
|
|
126
|
+
transform: (fn) => ctx.provider?.transform?.((draft) => fn({
|
|
127
|
+
provider: {
|
|
128
|
+
update: (id, updater) => draft.update(id, updater),
|
|
129
|
+
add: (...args) => draft.add?.(...args),
|
|
130
|
+
},
|
|
131
|
+
model: {
|
|
132
|
+
update: (providerID, modelID, updater) => draft.models?.update?.(providerID, modelID, updater),
|
|
133
|
+
set: (providerID, modelID, value) => draft.models?.set?.(providerID, modelID, value),
|
|
134
|
+
},
|
|
135
|
+
})),
|
|
136
|
+
reload: () => ctx.provider?.reload?.() ?? ctx.model?.reload?.(),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
export default {
|
|
140
|
+
id: "openchamber-v2-plugin-host-shim",
|
|
141
|
+
setup: async (ctx) => {
|
|
142
|
+
if (typeof ctx?.catalog?.transform === "function") return
|
|
143
|
+
const patched = { ...ctx, catalog: catalogFor(ctx) }
|
|
144
|
+
for (const spec of readConfigPlugins()) {
|
|
145
|
+
const resolved = resolvePackageEntry(spec)
|
|
146
|
+
if (!resolved || !usesCatalog(resolved.dir)) continue
|
|
147
|
+
try {
|
|
148
|
+
const mod = await import(pathToFileURL(resolved.entry).href)
|
|
149
|
+
if (typeof mod.default?.setup === "function") await mod.default.setup(patched)
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error(`[openchamber] v2 plugin host shim skipped ${spec}: ${error instanceof Error ? error.message : String(error)}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
}
|
|
@@ -8,13 +8,13 @@ This module owns the authoritative permission auto-accept policy for web, deskto
|
|
|
8
8
|
|
|
9
9
|
`permissionAutoAccept.sessions` contains explicit per-session boolean policies.
|
|
10
10
|
|
|
11
|
-
Policy inheritance uses the nearest explicit session value. A child `false` therefore overrides a parent `true
|
|
11
|
+
Policy inheritance uses the nearest explicit session value. A child `false` therefore overrides a parent `true`. Sessions with no explicit value, including a missing ancestor lookup, default to allow. A failed settings load still fails closed and does not approve.
|
|
12
12
|
|
|
13
13
|
## Runtime
|
|
14
14
|
|
|
15
|
-
`createPermissionAutoAcceptRuntime` loads and serializes policy writes, subscribes to the global OpenCode event hub, caches session lineage, retries transient replies, and reconciles pending permissions after startup, reconnect, and policy enablement.
|
|
15
|
+
`createPermissionAutoAcceptRuntime` loads and serializes policy writes, subscribes to the global OpenCode event hub, caches session lineage, retries transient replies, and reconciles pending permissions after startup, reconnect, and policy enablement. Pending requests are listed at `GET /permission/request` and approved with `POST /session/:sessionID/permission/:requestID/reply` body `{ decision: "always" }`. OpenCode 2.0.12 rejects the legacy `{ reply }` body and the unscoped `/permission/:id/reply` path.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
A failed settings load fails closed and does not approve. A failed pending-permission fetch is distinct from an empty successful response and never clears policy state.
|
|
18
18
|
|
|
19
19
|
## Routes
|
|
20
20
|
|
|
@@ -27,9 +27,9 @@ These are normal authenticated OpenChamber runtime routes. They must not be adde
|
|
|
27
27
|
|
|
28
28
|
`packages/ui/src/stores/permissionStore.ts` is a projection of server policy and does not persist an independent policy. The server is the sole responder and the UI renders pending requests until the authoritative `permission.replied` event arrives.
|
|
29
29
|
|
|
30
|
-
`ChatInput` derives control visibility from the persisted, directory-scoped `useConfigStore` agent snapshot. A selected agent with a final global `allow` or `deny` rule has no remaining prompt path and hides the control. Unknown snapshots, missing selected agents, and rules that can ask keep the control visible. This path issues no additional request.
|
|
30
|
+
`ChatInput` derives control visibility from the persisted, directory-scoped `useConfigStore` agent snapshot via `shouldShowPermissionAutoAcceptControl`. It reads the V2 wire `permissions` field when present (OpenCode `AgentInfo.permissions`), otherwise the legacy singular `permission` document. Rule normalization reuses `toPermissionRuleset` so both V2 `{ action, resource, effect }` and V1 `{ permission, pattern, action }` / tool-map shapes are recognized. A selected agent with a final global `allow` or `deny` rule has no remaining prompt path and hides the control. Unknown snapshots, missing selected agents, unparseable rules, and rules that can still ask keep the control visible. This path issues no additional request and does not change server auto-accept policy.
|
|
31
31
|
|
|
32
|
-
VS Code retains its foreground-only implementation because it does not run the web server runtime.
|
|
32
|
+
VS Code retains its foreground-only implementation because it does not run the web server runtime (`isVSCodeRuntime()` always shows the control).
|
|
33
33
|
|
|
34
34
|
## Tests
|
|
35
35
|
|
|
@@ -123,6 +123,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
123
123
|
|
|
124
124
|
const isSessionAutoAccepting = async (sessionId, directory) => {
|
|
125
125
|
await load();
|
|
126
|
+
if (!sessionId) return false;
|
|
126
127
|
const seen = new Set();
|
|
127
128
|
let current = sessionId;
|
|
128
129
|
let currentDirectory = directory;
|
|
@@ -133,22 +134,23 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
133
134
|
try {
|
|
134
135
|
info = await getSession(current, currentDirectory);
|
|
135
136
|
} catch {
|
|
136
|
-
|
|
137
|
+
// Lineage lookup failed. No explicit deny was found, so keep the default allow.
|
|
138
|
+
return true;
|
|
137
139
|
}
|
|
138
140
|
current = info?.parentID ?? null;
|
|
139
141
|
currentDirectory = info?.directory ?? currentDirectory;
|
|
140
142
|
}
|
|
141
|
-
return
|
|
143
|
+
return true;
|
|
142
144
|
};
|
|
143
145
|
|
|
144
146
|
const replyOnce = async (permission, directory) => {
|
|
145
147
|
if (!permission?.id || !permission?.sessionID) return false;
|
|
146
148
|
await load();
|
|
147
149
|
if (!(await isSessionAutoAccepting(permission.sessionID, directory))) return false;
|
|
148
|
-
await request(`/permission/${encodeURIComponent(permission.id)}/reply`, {
|
|
150
|
+
await request(`/session/${encodeURIComponent(permission.sessionID)}/permission/${encodeURIComponent(permission.id)}/reply`, {
|
|
149
151
|
directory,
|
|
150
152
|
method: 'POST',
|
|
151
|
-
body: {
|
|
153
|
+
body: { decision: 'always' },
|
|
152
154
|
});
|
|
153
155
|
return true;
|
|
154
156
|
};
|
|
@@ -187,7 +189,7 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
187
189
|
for (const directory of scopes) {
|
|
188
190
|
let payload;
|
|
189
191
|
try {
|
|
190
|
-
payload = await request('/permission', { directory });
|
|
192
|
+
payload = await request('/permission/request', { directory });
|
|
191
193
|
} catch {
|
|
192
194
|
continue;
|
|
193
195
|
}
|
|
@@ -209,12 +211,17 @@ export function createPermissionAutoAcceptRuntime({
|
|
|
209
211
|
const raw = event?.payload;
|
|
210
212
|
const payload = raw?.payload && typeof raw.payload === 'object' ? raw.payload : raw;
|
|
211
213
|
const directory = typeof event?.directory === 'string' && event.directory !== 'global' ? event.directory : undefined;
|
|
214
|
+
const body = payload?.properties && typeof payload.properties === 'object'
|
|
215
|
+
? payload.properties
|
|
216
|
+
: payload?.data && typeof payload.data === 'object'
|
|
217
|
+
? payload.data
|
|
218
|
+
: payload;
|
|
212
219
|
if (payload?.type === 'session.created' || payload?.type === 'session.updated') {
|
|
213
|
-
rememberSession(payload.properties?.info, directory);
|
|
220
|
+
rememberSession(body?.info ?? payload.properties?.info, directory);
|
|
214
221
|
return;
|
|
215
222
|
}
|
|
216
223
|
if (payload?.type === 'permission.asked') {
|
|
217
|
-
void processPermission(
|
|
224
|
+
void processPermission(body, directory);
|
|
218
225
|
}
|
|
219
226
|
};
|
|
220
227
|
|
|
@@ -31,6 +31,18 @@ const flush = async () => {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
describe('permission auto-accept runtime', () => {
|
|
34
|
+
it('approves a session with no explicit policy', async () => {
|
|
35
|
+
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
36
|
+
const path = new URL(url).pathname;
|
|
37
|
+
if (path === '/session/root') return Response.json({ id: 'root' });
|
|
38
|
+
if (init.method === 'POST') return Response.json({});
|
|
39
|
+
return new Response('', { status: 404 });
|
|
40
|
+
});
|
|
41
|
+
const { runtime } = createRuntime({ fetchImpl });
|
|
42
|
+
await expect(runtime.processPermission({ id: 'perm', sessionID: 'root' }, '/project')).resolves.toBe(true);
|
|
43
|
+
expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/session/root/permission/perm/reply' && JSON.parse(init.body).decision === 'always')).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
34
46
|
it('persists explicit session policies across runtime restarts', async () => {
|
|
35
47
|
const first = createRuntime();
|
|
36
48
|
await first.runtime.setSessionPolicy('root', true);
|
|
@@ -55,7 +67,7 @@ describe('permission auto-accept runtime', () => {
|
|
|
55
67
|
it('fetches missing subagent lineage before replying', async () => {
|
|
56
68
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
57
69
|
const path = new URL(url).pathname;
|
|
58
|
-
if (path === '/permission') return new Response('[]');
|
|
70
|
+
if (path === '/permission/request') return new Response('[]');
|
|
59
71
|
if (path === '/session/child') return Response.json({ id: 'child', parentID: 'root', directory: '/project' });
|
|
60
72
|
if (init.method === 'POST') return Response.json({});
|
|
61
73
|
return new Response('', { status: 404 });
|
|
@@ -65,15 +77,15 @@ describe('permission auto-accept runtime', () => {
|
|
|
65
77
|
fetchImpl,
|
|
66
78
|
});
|
|
67
79
|
await expect(runtime.processPermission({ id: 'perm', sessionID: 'child' }, '/project')).resolves.toBe(true);
|
|
68
|
-
expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/permission/perm/reply' && init.method === 'POST')).toBe(true);
|
|
80
|
+
expect(fetchImpl.mock.calls.some(([url, init]) => new URL(url).pathname === '/session/child/permission/perm/reply' && init.method === 'POST' && JSON.parse(init.body).decision === 'always')).toBe(true);
|
|
69
81
|
});
|
|
70
82
|
|
|
71
83
|
it('retries a transient reply failure and deduplicates concurrent events', async () => {
|
|
72
84
|
let replyAttempts = 0;
|
|
73
85
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
74
86
|
const path = new URL(url).pathname;
|
|
75
|
-
if (path === '/permission') return new Response('[]');
|
|
76
|
-
if (path === '/permission/perm/reply' && init.method === 'POST') {
|
|
87
|
+
if (path === '/permission/request') return new Response('[]');
|
|
88
|
+
if (path === '/session/root/permission/perm/reply' && init.method === 'POST') {
|
|
77
89
|
replyAttempts += 1;
|
|
78
90
|
return replyAttempts === 1 ? new Response('', { status: 503 }) : Response.json({});
|
|
79
91
|
}
|
|
@@ -94,8 +106,8 @@ describe('permission auto-accept runtime', () => {
|
|
|
94
106
|
it('reconciles pending permissions after reconnect', async () => {
|
|
95
107
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
96
108
|
const path = new URL(url).pathname;
|
|
97
|
-
if (path === '/permission') return Response.json([{ id: 'pending', sessionID: 'root' }]);
|
|
98
|
-
if (path === '/permission/pending/reply' && init.method === 'POST') return Response.json({});
|
|
109
|
+
if (path === '/permission/request') return Response.json([{ id: 'pending', sessionID: 'root' }]);
|
|
110
|
+
if (path === '/session/root/permission/pending/reply' && init.method === 'POST') return Response.json({});
|
|
99
111
|
return Response.json({ id: 'root' });
|
|
100
112
|
});
|
|
101
113
|
const { connect } = createRuntime({
|
|
@@ -104,14 +116,14 @@ describe('permission auto-accept runtime', () => {
|
|
|
104
116
|
});
|
|
105
117
|
connect();
|
|
106
118
|
await flush();
|
|
107
|
-
expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/permission/pending/reply')).toBe(true);
|
|
119
|
+
expect(fetchImpl.mock.calls.some(([url]) => new URL(url).pathname === '/session/root/permission/pending/reply')).toBe(true);
|
|
108
120
|
});
|
|
109
121
|
|
|
110
122
|
it('accepts existing pending permissions when a session policy is enabled', async () => {
|
|
111
123
|
const fetchImpl = vi.fn(async (url, init = {}) => {
|
|
112
124
|
const parsed = new URL(url);
|
|
113
125
|
const path = parsed.pathname;
|
|
114
|
-
if (path === '/permission') {
|
|
126
|
+
if (path === '/permission/request') {
|
|
115
127
|
return parsed.searchParams.get('directory') === '/project'
|
|
116
128
|
? Response.json([
|
|
117
129
|
{ id: 'root-pending', sessionID: 'root' },
|
|
@@ -119,19 +131,22 @@ describe('permission auto-accept runtime', () => {
|
|
|
119
131
|
])
|
|
120
132
|
: Response.json([]);
|
|
121
133
|
}
|
|
122
|
-
if (
|
|
134
|
+
if (init.method === 'POST' && path.endsWith('/reply')) return Response.json({});
|
|
123
135
|
if (path === '/session/other') return Response.json({ id: 'other' });
|
|
124
136
|
return new Response('', { status: 404 });
|
|
125
137
|
});
|
|
126
|
-
const { runtime } = createRuntime({
|
|
138
|
+
const { runtime } = createRuntime({
|
|
139
|
+
stored: { permissionAutoAccept: { sessions: { other: false } } },
|
|
140
|
+
fetchImpl,
|
|
141
|
+
});
|
|
127
142
|
|
|
128
143
|
await runtime.setSessionPolicy('root', true, '/project');
|
|
129
144
|
|
|
130
145
|
const replyPaths = fetchImpl.mock.calls
|
|
131
146
|
.filter(([, init]) => init?.method === 'POST')
|
|
132
147
|
.map(([url]) => new URL(url).pathname);
|
|
133
|
-
expect(replyPaths).toEqual(['/permission/root-pending/reply']);
|
|
148
|
+
expect(replyPaths).toEqual(['/session/root/permission/root-pending/reply']);
|
|
134
149
|
expect(fetchImpl.mock.calls.some(([url]) => new URL(url).searchParams.get('directory') === '/project')).toBe(true);
|
|
135
|
-
expect(await runtime.load()).toEqual({ sessions: { root: true } });
|
|
150
|
+
expect(await runtime.load()).toEqual({ sessions: { other: false, root: true } });
|
|
136
151
|
});
|
|
137
152
|
});
|
|
@@ -147,7 +147,11 @@ const startFakeOpenCode = async (): Promise<{
|
|
|
147
147
|
next();
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
-
|
|
150
|
+
// Production proxy forwards official v2 paths with `/api` restored
|
|
151
|
+
// (`toUpstreamOpenCodeApiPath`). Host mounts both `/api/global/event` and
|
|
152
|
+
// `/api/event`; fake upstream must match those upstream targets — not the
|
|
153
|
+
// pre-v2 bare `/global/event` or `/event`.
|
|
154
|
+
const writeSseFixture = (res: express.Response) => {
|
|
151
155
|
res.writeHead(200, {
|
|
152
156
|
'content-type': 'text/event-stream; charset=utf-8',
|
|
153
157
|
'cache-control': 'no-cache',
|
|
@@ -243,9 +247,16 @@ const startFakeOpenCode = async (): Promise<{
|
|
|
243
247
|
);
|
|
244
248
|
|
|
245
249
|
res.end();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
app.get('/api/global/event', (_req, res) => {
|
|
253
|
+
writeSseFixture(res);
|
|
254
|
+
});
|
|
255
|
+
app.get('/api/event', (_req, res) => {
|
|
256
|
+
writeSseFixture(res);
|
|
246
257
|
});
|
|
247
258
|
|
|
248
|
-
app.get('/session/:sessionID/message', (
|
|
259
|
+
app.get('/api/session/:sessionID/message', (_req, res) => {
|
|
249
260
|
res.json([
|
|
250
261
|
{
|
|
251
262
|
info: {
|
|
@@ -513,11 +524,15 @@ describe('relay reasoning projection e2e (Host filter over private relay)', () =
|
|
|
513
524
|
for (const url of upstream.observation.urls) {
|
|
514
525
|
expect(url).not.toContain('includeReasoning');
|
|
515
526
|
}
|
|
516
|
-
// Sanity: upstream still saw
|
|
517
|
-
expect(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
527
|
+
// Sanity: upstream still saw official v2 event + message routes.
|
|
528
|
+
expect(
|
|
529
|
+
upstream.observation.urls.some(
|
|
530
|
+
(u) => u.includes('/api/global/event') || u.includes('/api/event'),
|
|
531
|
+
),
|
|
532
|
+
).toBe(true);
|
|
533
|
+
expect(
|
|
534
|
+
upstream.observation.urls.some((u) => u.includes('/api/session/ses_1/message')),
|
|
535
|
+
).toBe(true);
|
|
521
536
|
|
|
522
537
|
// Unauthenticated tunnel request still hits real auth gate (not open).
|
|
523
538
|
const denied = await client.fetch('/api/global/event?includeReasoning=false', {
|
|
@@ -69,6 +69,14 @@ Server-owned scheduled task runtime and routes for OpenChamber-only automation.
|
|
|
69
69
|
- `runNow(projectId, taskId)`
|
|
70
70
|
- `observeSessionEvent(event)`
|
|
71
71
|
|
|
72
|
+
Isolation dependencies (required for sidebar hide):
|
|
73
|
+
|
|
74
|
+
- `persistSessionMetadata(sessionID, patch)` — Host store write. Missing
|
|
75
|
+
persist refuses the run **before** `session.create`. Persist failure after
|
|
76
|
+
create fails the run before prompt.
|
|
77
|
+
- `onSystemSessionPersisted({ sessionID, directory, metadata })` — drop the
|
|
78
|
+
session from the sidebar index immediately after Host commit.
|
|
79
|
+
|
|
72
80
|
Dependencies include optional `runHistoryStore` with:
|
|
73
81
|
|
|
74
82
|
- `startRun(record)`
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
isSessionGoalSupported,
|
|
9
9
|
sessionGoalUnavailableMessage,
|
|
10
10
|
} from '../session-goal/capability.js';
|
|
11
|
+
import { buildScheduledTaskMetadata } from '../session-metadata/system-session.js';
|
|
11
12
|
import { SCHEDULED_SLOT_CLAIMED_CODE } from './run-history-store.js';
|
|
12
13
|
|
|
13
14
|
const DEFAULT_GLOBAL_CONCURRENCY = 4;
|
|
@@ -481,12 +482,26 @@ const snapshotSessionOutcome = async ({
|
|
|
481
482
|
|
|
482
483
|
const parseSessionEventPhase = (event) => {
|
|
483
484
|
const payload = event?.payload?.payload ?? event?.payload;
|
|
484
|
-
|
|
485
|
+
// Accept bridge `{ properties }` and native v2 `{ data }` envelopes.
|
|
486
|
+
const properties = (payload?.properties && typeof payload.properties === 'object')
|
|
487
|
+
? payload.properties
|
|
488
|
+
: ((payload?.data && typeof payload.data === 'object') ? payload.data : null);
|
|
485
489
|
const sessionID = typeof properties?.sessionID === 'string' ? properties.sessionID : '';
|
|
486
490
|
if (!sessionID) {
|
|
487
491
|
return { phase: '', sessionID: '' };
|
|
488
492
|
}
|
|
489
|
-
|
|
493
|
+
// Ticket 07: shutdown interrupt is not an idle terminal — keep run open.
|
|
494
|
+
if (payload?.type === 'session.execution.interrupted' && properties?.reason === 'shutdown') {
|
|
495
|
+
return { phase: 'busy', sessionID };
|
|
496
|
+
}
|
|
497
|
+
if (payload?.type === 'session.idle' || payload?.type === 'session.execution.succeeded') {
|
|
498
|
+
return { phase: 'idle', sessionID };
|
|
499
|
+
}
|
|
500
|
+
if (payload?.type === 'session.execution.failed') {
|
|
501
|
+
return { phase: 'error', sessionID };
|
|
502
|
+
}
|
|
503
|
+
if (payload?.type === 'session.execution.interrupted') {
|
|
504
|
+
// user / superseded / inactivity — claim released; treat as idle for settlement.
|
|
490
505
|
return { phase: 'idle', sessionID };
|
|
491
506
|
}
|
|
492
507
|
if (payload?.type === 'session.status') {
|
|
@@ -518,6 +533,14 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
518
533
|
maxRunDurationMs = DEFAULT_MAX_RUN_MS,
|
|
519
534
|
/** Host store write: (sessionId, directory, goal) => Promise */
|
|
520
535
|
persistSessionGoal = null,
|
|
536
|
+
/** Host store write: (sessionId, patch) => Promise — required for sidebar isolation */
|
|
537
|
+
persistSessionMetadata = null,
|
|
538
|
+
/**
|
|
539
|
+
* After Host isolation metadata is committed. Used to drop the session
|
|
540
|
+
* from the sidebar index immediately (do not wait for the next list sync).
|
|
541
|
+
* @type {null | ((event: { sessionID: string, directory?: string, metadata: object }) => void)}
|
|
542
|
+
*/
|
|
543
|
+
onSystemSessionPersisted = null,
|
|
521
544
|
/** Host store read: (sessionId) => Promise<metadata|null> */
|
|
522
545
|
readSessionMetadata = null,
|
|
523
546
|
/**
|
|
@@ -801,9 +824,10 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
801
824
|
+ '\n</system-reminder>';
|
|
802
825
|
};
|
|
803
826
|
|
|
804
|
-
const buildSessionCreateInput = (task, projectPath, title) => ({
|
|
827
|
+
const buildSessionCreateInput = (task, projectPath, title, metadata) => ({
|
|
805
828
|
title,
|
|
806
829
|
location: { directory: projectPath },
|
|
830
|
+
metadata,
|
|
807
831
|
...(task.execution.agent ? { agent: task.execution.agent } : {}),
|
|
808
832
|
model: {
|
|
809
833
|
id: task.execution.modelID,
|
|
@@ -812,6 +836,18 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
812
836
|
},
|
|
813
837
|
});
|
|
814
838
|
|
|
839
|
+
const persistScheduledTaskIsolation = async (sessionID, directory, metadata) => {
|
|
840
|
+
if (typeof persistSessionMetadata !== 'function') {
|
|
841
|
+
throw new Error('scheduled-task session metadata persist is not configured');
|
|
842
|
+
}
|
|
843
|
+
await persistSessionMetadata(sessionID, metadata);
|
|
844
|
+
try {
|
|
845
|
+
onSystemSessionPersisted?.({ sessionID, directory, metadata });
|
|
846
|
+
} catch {
|
|
847
|
+
// Index/live hide is compensatory; Host persist is the authority.
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
|
|
815
851
|
const buildPromptText = (task, projectPath) => {
|
|
816
852
|
const promptText = expandSnippets(task.execution.prompt, projectPath);
|
|
817
853
|
if (!task.execution.goalEnabled) {
|
|
@@ -915,6 +951,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
915
951
|
note: '',
|
|
916
952
|
statusReason: '',
|
|
917
953
|
lastAccountedMessageID: '',
|
|
954
|
+
executionGeneration: 0,
|
|
918
955
|
createdAt: now,
|
|
919
956
|
updatedAt: now,
|
|
920
957
|
};
|
|
@@ -1107,6 +1144,10 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1107
1144
|
}
|
|
1108
1145
|
}
|
|
1109
1146
|
|
|
1147
|
+
if (typeof persistSessionMetadata !== 'function') {
|
|
1148
|
+
throw new Error('scheduled-task session metadata persist is not configured');
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1110
1151
|
if (typeof waitForOpenCodeReady === 'function') {
|
|
1111
1152
|
await waitForOpenCodeReady(10_000, 250);
|
|
1112
1153
|
signal?.throwIfAborted?.();
|
|
@@ -1141,14 +1182,21 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1141
1182
|
};
|
|
1142
1183
|
|
|
1143
1184
|
try {
|
|
1185
|
+
const isolationMetadata = buildScheduledTaskMetadata({
|
|
1186
|
+
projectID,
|
|
1187
|
+
taskID: task.id,
|
|
1188
|
+
runID,
|
|
1189
|
+
name: task.name,
|
|
1190
|
+
});
|
|
1144
1191
|
const session = await client.session.create(
|
|
1145
|
-
buildSessionCreateInput(task, projectPath, title),
|
|
1192
|
+
buildSessionCreateInput(task, projectPath, title, isolationMetadata),
|
|
1146
1193
|
requestOptions,
|
|
1147
1194
|
);
|
|
1148
1195
|
sessionID = session?.id;
|
|
1149
1196
|
if (!sessionID) {
|
|
1150
1197
|
throw new Error('failed to create session');
|
|
1151
1198
|
}
|
|
1199
|
+
await persistScheduledTaskIsolation(sessionID, projectPath, isolationMetadata);
|
|
1152
1200
|
|
|
1153
1201
|
if (signal) {
|
|
1154
1202
|
if (signal.aborted) {
|