@openchambery/web 1.19.22-beta.1 → 2.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -7
- package/bin/cli-opencode.test.js +208 -0
- package/bin/cli.js +62 -9
- package/bin/lib/cli-startup.js +105 -2
- package/bin/lib/cli-startup.test.js +179 -0
- package/dist/assets/{AgentAvatar-CRaBJXKs.js → AgentAvatar-B_JQoreg.js} +1 -1
- package/dist/assets/AssistantView-ClQAfGOT.js +3 -0
- package/dist/assets/{DiagramView-D1LGK_s2.js → DiagramView-BNnda8qO.js} +1 -1
- package/dist/assets/{FadeInOnReveal-B0kJo9Ii.js → FadeInOnReveal-VYQTSAkr.js} +1 -1
- package/dist/assets/{Icon-BgdfPhNs.js → Icon-BiEQEcih.js} +1 -1
- package/dist/assets/MarkdownRenderer-CJHSsWin.js +2 -0
- package/dist/assets/{MarkdownRendererImpl-DfoCJ1Mq.js → MarkdownRendererImpl-DwrHFGUp.js} +3 -3
- package/dist/assets/{MarkstreamRendererImpl-D2dsW5Lg.js → MarkstreamRendererImpl-CSnbQMu3.js} +1 -1
- package/dist/assets/MobileDetailNavigation-BXr6AGLT.js +1 -0
- package/dist/assets/{MobileShareBridge-2Yjn6wXg.js → MobileShareBridge-D7OE9OP_.js} +2 -2
- package/dist/assets/MobileSurface-D2jUkX9l.js +2 -0
- package/dist/assets/{MultiRunWindow-tTbMFowS.js → MultiRunWindow-DF3iIFCd.js} +1 -1
- package/dist/assets/{OnboardingScreen-CD2KtlF3.js → OnboardingScreen-BjHmlwlw.js} +1 -1
- package/dist/assets/OverlayScrollbar-DLtWO_Gp.js +2 -0
- package/dist/assets/SettingsView-BRxELjow.js +25 -0
- package/dist/assets/{SettingsWindow-zIa76s4t.js → SettingsWindow-BI5swspb.js} +1 -1
- package/dist/assets/{TerminalView-CDdzF4yi.js → TerminalView-BU3x8-fr.js} +1 -1
- package/dist/assets/{ToolOutputDialog-DNqA4uBl.js → ToolOutputDialog-BEWhBcRd.js} +2 -2
- package/dist/assets/appThemeRegistry-4MR0F7Jz.js +1 -0
- package/dist/assets/{button-CI7Q-Zw_.js → button-DKuz3L8l.js} +1 -1
- package/dist/assets/collapsible-DXbDynN4.js +3 -0
- package/dist/assets/context-menu-CqolsLCm.js +1 -0
- package/dist/assets/{desktopHosts-B1ixEylr.js → desktopHosts-CoDkOnyS.js} +1 -1
- package/dist/assets/{desktopRelayRestore-4JmFUw8r.js → desktopRelayRestore-DDlVKyOE.js} +1 -1
- package/dist/assets/{diagram-DdLO8HZ9.js → diagram-D7T8HQgj.js} +1 -1
- package/dist/assets/{dialog-B6pnIYmp.js → dialog-DQYl3W_o.js} +1 -1
- package/dist/assets/es-CN3E_zXj.js +15 -0
- package/dist/assets/{fileMentionTouchSelection-DNeiqns-.js → fileMentionTouchSelection-BZ6ovBYH.js} +1 -1
- package/dist/assets/{fileQueries-DQc8EJUf.js → fileQueries-JrpcsOCs.js} +1 -1
- package/dist/assets/fr-9zZvQZD2.js +13 -0
- package/dist/assets/gitApi-CM42qUed.js +10 -0
- package/dist/assets/{imageSource-BlAJJUEI.js → imageSource-CIl0FL10.js} +1 -1
- package/dist/assets/{input-D-__fwRi.js → input-CzQy8UhV.js} +1 -1
- package/dist/assets/insertionBoundaries-CtkZ9720.js +1 -0
- package/dist/assets/ja-Dnjk_82N.js +15 -0
- package/dist/assets/ko-BxCEE99C.js +15 -0
- package/dist/assets/main-BILgBI3B.js +169 -0
- package/dist/assets/main-BeveiW6F.js +2 -0
- package/dist/assets/markdown-worker-bS5OgGO3.js +2 -0
- package/dist/assets/{markdownHeightCache-BhxqJOvf.js → markdownHeightCache-Cj1I5NDw.js} +1 -1
- package/dist/assets/{markdownTheme-DOfGg-A7.js → markdownTheme-CKac3wKC.js} +1 -1
- package/dist/assets/mcpQueries-D5mQymgM.js +1 -0
- package/dist/assets/miniChat-BgrzM3u7.js +2 -0
- package/dist/assets/mobile-DtYoQE8C.js +2 -0
- package/dist/assets/multirun-jy8dEI96.js +4 -0
- package/dist/assets/{persistence-Ce3AnpEr.js → persistence-C9LQpiOS.js} +2 -2
- package/dist/assets/pl-CNNsWnPM.js +15 -0
- package/dist/assets/platform-BQghHRfp.js +6 -0
- package/dist/assets/pt-BR-BZB2DJMp.js +15 -0
- package/dist/assets/renderElectronMiniChatApp-BGrrmeME.js +2 -0
- package/dist/assets/renderMobileApp-u3c4GtFC.js +28 -0
- package/dist/assets/{runtimeConfig-CMkwoQ_X.js → runtimeConfig-BIGBRGQI.js} +1 -1
- package/dist/assets/{runtimeEndpointReset-ChOxt-J7.js → runtimeEndpointReset-D2savkuw.js} +12 -12
- package/dist/assets/{sessionChangeSummary-NnxcqCTI.js → sessionChangeSummary-eIbpSfrO.js} +1 -1
- package/dist/assets/{sessionLookup-BrU3rFHJ.js → sessionLookup-myHe5ltu.js} +1 -1
- package/dist/assets/{sortable-tabs-strip-Fnfhf3K9.js → sortable-tabs-strip-BQtmHx9r.js} +1 -1
- package/dist/assets/src-CLN7Z-zx.css +2 -0
- package/dist/assets/store-DLCiZaA6.js +15 -0
- package/dist/assets/{terminalApi-xjqssKke.js → terminalApi-BxcdiduX.js} +1 -1
- package/dist/assets/{textarea-bHYxZtfH.js → textarea-CakKvqFw.js} +1 -1
- package/dist/assets/{tooltip-0T5jv0cu.js → tooltip-Cx5gTbdY.js} +1 -1
- package/dist/assets/uk-rwQrxeYM.js +15 -0
- package/dist/assets/{url-_z7evzdK.js → url-CDHnjlsm.js} +1 -1
- package/dist/assets/{useAppFontEffects-C02YFyAv.js → useAppFontEffects-CNeo0aZs.js} +59 -45
- package/dist/assets/{useAssistantUIStore-CMutVh9B.js → useAssistantUIStore-asNG6K7n.js} +1 -1
- package/dist/assets/useDesktopWindowControlsLayout-DBJ56qIo.js +1 -0
- package/dist/assets/useEffectiveDirectory-B4LvR8Gt.js +1 -0
- package/dist/assets/{useFontPreferences-B1JYyzpK.js → useFontPreferences-CUnQ4TVm.js} +1 -1
- package/dist/assets/useMobileNavigationStore-ZHJPxlHt.js +414 -0
- package/dist/assets/{useRuntimeAPIs-DSLXJybW.js → useRuntimeAPIs-DwLtO0c_.js} +1 -1
- package/dist/assets/useSessionAutoCleanup-BIVmrnYU.js +2 -0
- package/dist/assets/{useThemeSystem-DMzWVJaW.js → useThemeSystem-BlNUWH47.js} +1 -1
- package/dist/assets/useUIStore-CQYKJnrg.js +1 -0
- package/dist/assets/{useWorkerHighlightedLines-C9z0ML1z.js → useWorkerHighlightedLines-CJZbJDW3.js} +2 -2
- package/dist/assets/{vendor-base-ui-CnHfUmDN.js → vendor-base-ui-CDjyofeD.js} +1 -1
- package/dist/assets/{vendor-cmdk-CeAGfrLo.js → vendor-cmdk-BFH0UpXX.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-accessibility-BY5LhhUB.js → vendor-dnd-kit-accessibility-BrR3AKvM.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-core-CvWxUPdb.js → vendor-dnd-kit-core-CNfdvk6H.js} +1 -1
- package/dist/assets/{vendor-dnd-kit-sortable-hHdP9lLb.js → vendor-dnd-kit-sortable-DQUaTeGj.js} +1 -1
- package/dist/assets/{vendor-framer-motion-DFy_BSVd.js → vendor-framer-motion-4dogsUk3.js} +1 -1
- package/dist/assets/{vendor-legendapp-list-WE32lwNk.js → vendor-legendapp-list-BU7MdPZR.js} +1 -1
- package/dist/assets/{vendor-markstream-react-DX55XLGO.js → vendor-markstream-react-qHC2FykU.js} +3 -3
- package/dist/assets/vendor-opencode-client-BOe2qpQb.js +12 -0
- package/dist/assets/{vendor-pierre-diffs-DX7XTPG3.js → vendor-pierre-diffs-CUfqC003.js} +1 -1
- package/dist/assets/{vendor-react-DLPElpoQ.js → vendor-react-C94A0AHl.js} +1 -1
- package/dist/assets/{vendor-react-drawio-ECtEwvbc.js → vendor-react-drawio-FVlWqKVE.js} +1 -1
- package/dist/assets/{vendor-reactuses-core-BnW6GxJt.js → vendor-reactuses-core-C2A3EjUi.js} +1 -1
- package/dist/assets/{vendor-sonner-DVzioF3d.js → vendor-sonner-DNqHmUQs.js} +1 -1
- package/dist/assets/{vendor-stream-diffs-DWy3xSmp.js → vendor-stream-diffs-CwbKsmlv.js} +2 -2
- package/dist/assets/{vendor-tanstack-react-query-D7pys_c9.js → vendor-tanstack-react-query-D0iFK57C.js} +1 -1
- package/dist/assets/{vendor-tanstack-react-virtual-tlDgH6PG.js → vendor-tanstack-react-virtual-B2kV6cnT.js} +1 -1
- package/dist/assets/{vendor-yaml-DIcAK3EC.js → vendor-yaml-C9KlJAlj.js} +37 -37
- package/dist/assets/{vendor-zustand-B4dlGEM2.js → vendor-zustand-BqLKf6ZY.js} +1 -1
- package/dist/assets/zh-CN-BvmBDnkD.js +15 -0
- package/dist/assets/zh-TW-9wcHTX0y.js +15 -0
- package/dist/index.html +13 -13
- package/dist/mini-chat.html +13 -13
- package/dist/mobile.html +13 -13
- package/package.json +2 -2
- package/server/index.js +392 -20
- package/server/lib/assistants/DOCUMENTATION.md +26 -21
- package/server/lib/assistants/assign.js +2 -1
- package/server/lib/assistants/contact-store.js +37 -19
- package/server/lib/assistants/contracts.js +11 -1
- package/server/lib/assistants/projection-history.test.js +454 -0
- package/server/lib/assistants/routes.js +11 -5
- package/server/lib/assistants/service.js +850 -294
- package/server/lib/assistants/service.test.js +588 -274
- package/server/lib/conversations/DOCUMENTATION.md +5 -5
- package/server/lib/conversations/service.js +87 -57
- package/server/lib/conversations/service.test.js +132 -100
- package/server/lib/conversations/validation.js +11 -1
- package/server/lib/event-stream/directory-ws-bridge.js +13 -2
- package/server/lib/event-stream/global-hub.js +51 -5
- package/server/lib/event-stream/runtime.js +57 -20
- package/server/lib/event-stream/runtime.test.js +112 -0
- package/server/lib/llm/DOCUMENTATION.md +95 -84
- package/server/lib/llm/catalog.js +111 -45
- package/server/lib/llm/catalog.test.js +121 -59
- package/server/lib/llm/completions.js +41 -12
- package/server/lib/llm/completions.test.js +45 -4
- package/server/lib/llm/generate.js +516 -453
- package/server/lib/llm/generate.test.js +705 -484
- package/server/lib/llm/routes.js +8 -4
- package/server/lib/message-queue/DOCUMENTATION.md +5 -5
- package/server/lib/message-queue/opencode-adapter.js +191 -104
- package/server/lib/message-queue/opencode-adapter.test.js +328 -58
- package/server/lib/message-queue/runtime.js +15 -2
- package/server/lib/message-queue/runtime.test.js +6 -6
- package/server/lib/message-queue/worker.js +7 -1
- package/server/lib/notifications/runtime.js +5 -0
- package/server/lib/opencode/DOCUMENTATION.md +103 -16
- package/server/lib/opencode/agent-document.d.ts +28 -0
- package/server/lib/opencode/agent-document.js +280 -0
- package/server/lib/opencode/agent-document.test.js +137 -0
- package/server/lib/opencode/agents.js +96 -0
- package/server/lib/opencode/auth-state-runtime.js +5 -0
- package/server/lib/opencode/bootstrap-runtime.js +2 -0
- package/server/lib/opencode/config-entity-routes.js +111 -102
- package/server/lib/opencode/config-entity-routes.test.js +123 -10
- package/server/lib/opencode/config-hot-reload.integration.test.js +105 -0
- package/server/lib/opencode/ensure-cli.js +238 -0
- package/server/lib/opencode/ensure-cli.test.js +119 -0
- package/server/lib/opencode/env-runtime.js +208 -111
- package/server/lib/opencode/env-runtime.test.js +211 -14
- package/server/lib/opencode/feature-routes-runtime.js +80 -5
- package/server/lib/opencode/feature-routes-runtime.test.js +18 -0
- package/server/lib/opencode/http-server-connections.js +98 -0
- package/server/lib/opencode/http-server-connections.test.js +321 -0
- package/server/lib/opencode/lifecycle.js +539 -95
- package/server/lib/opencode/lifecycle.test.js +698 -6
- package/server/lib/opencode/managed-capabilities-runtime.js +15 -2
- package/server/lib/opencode/managed-capabilities-runtime.test.js +1 -0
- package/server/lib/opencode/network-runtime.js +28 -16
- package/server/lib/opencode/network-runtime.test.js +68 -4
- package/server/lib/opencode/openchamber-routes.js +7 -1
- package/server/lib/opencode/opencode2-pin.js +139 -0
- package/server/lib/opencode/opencode2-pin.test.js +88 -0
- package/server/lib/opencode/opencode2-sidecar.e2e.test.js +326 -0
- package/server/lib/opencode/owned-runtime-upgrade.js +431 -0
- package/server/lib/opencode/owned-runtime-upgrade.test.js +160 -0
- package/server/lib/opencode/plugin-compatibility.js +15 -0
- package/server/lib/opencode/plugin-compatibility.test.js +21 -0
- package/server/lib/opencode/plugin-routes.js +3 -18
- package/server/lib/opencode/plugin-routes.test.js +9 -9
- package/server/lib/opencode/plugins.js +4 -3
- package/server/lib/opencode/plugins.test.js +12 -0
- package/server/lib/opencode/provider-catalog.js +58 -12
- package/server/lib/opencode/provider-catalog.test.js +48 -0
- package/server/lib/opencode/proxy.js +368 -28
- package/server/lib/opencode/proxy.test.js +338 -0
- package/server/lib/opencode/routes.js +303 -76
- package/server/lib/opencode/routes.test.js +315 -50
- package/server/lib/opencode/runtime-contract.js +401 -0
- package/server/lib/opencode/runtime-contract.test.js +214 -0
- package/server/lib/opencode/server-opencode-fetch.js +242 -0
- package/server/lib/opencode/server-opencode-fetch.test.js +130 -0
- package/server/lib/opencode/server-utils-runtime.js +35 -0
- package/server/lib/opencode/shared-service.integration.test.js +66 -0
- package/server/lib/opencode/shared-service.js +147 -0
- package/server/lib/opencode/shared-service.test.js +93 -0
- package/server/lib/opencode/shared.js +3 -3
- package/server/lib/opencode/shutdown-runtime.js +15 -6
- package/server/lib/opencode/shutdown-runtime.test.js +49 -1
- package/server/lib/opencode/skill-routes.js +26 -40
- package/server/lib/opencode/skill-routes.test.js +71 -7
- package/server/lib/opencode/skills.test.js +15 -0
- package/server/lib/opencode/startup-pipeline-runtime.js +4 -0
- package/server/lib/opencode/static-routes-runtime.js +3 -1
- package/server/lib/opencode/static-routes-runtime.test.js +33 -0
- package/server/lib/opencode/v1-migration-gate.js +157 -0
- package/server/lib/opencode/v1-migration-gate.test.js +111 -0
- package/server/lib/opencode/v2-client.js +60 -0
- package/server/lib/opencode/v2-plugin-host-shim/index.mjs +155 -0
- package/server/lib/opencode/v2-plugin-host-shim/package.json +4 -0
- package/server/lib/opencode/watcher.js +2 -1
- package/server/lib/opencode/watcher.test.js +1 -1
- package/server/lib/permission-auto-accept/DOCUMENTATION.md +5 -5
- package/server/lib/permission-auto-accept/runtime.js +14 -7
- package/server/lib/permission-auto-accept/runtime.test.js +27 -12
- package/server/lib/relay/reasoning-projection.e2e.test.ts +22 -7
- package/server/lib/scheduled-tasks/DOCUMENTATION.md +79 -39
- package/server/lib/scheduled-tasks/managed-tool-route.js +31 -1
- package/server/lib/scheduled-tasks/managed-tool-route.test.js +23 -0
- package/server/lib/scheduled-tasks/routes.js +27 -0
- package/server/lib/scheduled-tasks/routes.test.js +94 -0
- package/server/lib/scheduled-tasks/runtime.js +381 -310
- package/server/lib/scheduled-tasks/runtime.test.js +600 -309
- package/server/lib/session-goal/DOCUMENTATION.md +173 -22
- package/server/lib/session-goal/capability.js +57 -0
- package/server/lib/session-goal/capability.test.js +33 -0
- package/server/lib/session-goal/routes.js +240 -2
- package/server/lib/session-goal/routes.test.js +211 -0
- package/server/lib/session-goal/runtime.js +1617 -113
- package/server/lib/session-goal/runtime.test.js +1500 -70
- package/server/lib/session-index/DOCUMENTATION.md +18 -15
- package/server/lib/session-index/event-ingest.js +89 -16
- package/server/lib/session-index/event-ingest.test.js +73 -0
- package/server/lib/session-index/host-authority-snapshot.js +92 -0
- package/server/lib/session-index/routes.host-archive.test.js +201 -0
- package/server/lib/session-index/routes.js +63 -10
- package/server/lib/session-index/routes.test.js +8 -8
- package/server/lib/session-index/service.js +7 -1
- package/server/lib/session-index/sync-runtime.js +171 -41
- package/server/lib/session-index/sync-runtime.test.js +164 -9
- package/server/lib/session-metadata/DOCUMENTATION.md +86 -0
- package/server/lib/session-metadata/routes.js +193 -0
- package/server/lib/session-metadata/routes.test.js +156 -0
- package/server/lib/session-metadata/session-archive.d.ts +73 -0
- package/server/lib/session-metadata/session-archive.js +654 -0
- package/server/lib/session-metadata/session-archive.test.js +359 -0
- package/server/lib/session-metadata/session-metadata-store.d.ts +36 -0
- package/server/lib/session-metadata/session-metadata-store.js +375 -0
- package/server/lib/session-metadata/session-metadata-store.test.js +379 -0
- package/server/lib/session-metadata/session-projection.d.ts +41 -0
- package/server/lib/session-metadata/session-projection.js +255 -0
- package/server/lib/session-metadata/session-projection.test.js +64 -0
- package/server/lib/session-metadata/system-session.js +55 -0
- package/server/lib/session-metadata/system-session.test.js +55 -0
- package/server/lib/session-title/DOCUMENTATION.md +40 -7
- package/server/lib/session-title/native-runtime.test.js +167 -0
- package/server/lib/session-title/runtime.js +67 -42
- package/server/lib/session-title/session-access.js +46 -0
- package/server/lib/session-title/session-access.test.js +91 -0
- package/server/lib/session-turn-pages/DOCUMENTATION.md +12 -11
- package/server/lib/session-turn-pages/reconcile.service.js +1 -1
- package/server/lib/session-turn-pages/reconcile.service.test.js +16 -0
- package/server/lib/session-turn-pages/routes.js +123 -75
- package/server/lib/session-turn-pages/routes.test.js +566 -0
- package/server/lib/session-turn-pages/service.js +6 -2
- package/server/lib/session-turn-pages/service.test.js +34 -0
- package/server/lib/session-turn-pages/session-message-projection.d.ts +13 -0
- package/server/lib/session-turn-pages/session-message-projection.js +281 -0
- package/server/lib/small-model/DOCUMENTATION.md +101 -146
- package/server/lib/small-model/catalog.js +84 -54
- package/server/lib/small-model/catalog.test.js +111 -9
- package/server/lib/small-model/custom-api.js +68 -0
- package/server/lib/small-model/custom-api.test.js +64 -0
- package/server/lib/small-model/index.js +125 -241
- package/server/lib/small-model/resolve.js +35 -120
- package/server/lib/small-model/resolve.test.js +20 -110
- package/server/lib/small-model/routes.js +1 -3
- package/server/lib/small-model/summary-settings.test.js +157 -253
- package/server/opencode-proxy.test.js +63 -12
- package/dist/assets/AssistantView-BUveAgc4.js +0 -3
- package/dist/assets/MarkdownRenderer-BbrbDH6-.js +0 -2
- package/dist/assets/MobileDetailNavigation-B0aqKXd7.js +0 -1
- package/dist/assets/MobileSurface-DW7WfPsy.js +0 -2
- package/dist/assets/OverlayScrollbar-DrA0Vsg5.js +0 -2
- package/dist/assets/SettingsView-PkXIavr7.js +0 -25
- package/dist/assets/appThemeRegistry-D5Yxg4YF.js +0 -1
- package/dist/assets/collapsible-D5Phaykh.js +0 -1
- package/dist/assets/context-menu-CubSDW2g.js +0 -1
- package/dist/assets/es-DA3MqOo0.js +0 -15
- package/dist/assets/fr-Dbz0CdIA.js +0 -13
- package/dist/assets/gitApi-DXyC4MiV.js +0 -7
- package/dist/assets/insertionBoundaries-KlOVrHHt.js +0 -1
- package/dist/assets/ja-DumanVQA.js +0 -15
- package/dist/assets/ko-C0IkiUjQ.js +0 -15
- package/dist/assets/main-BPS5ylib.js +0 -2
- package/dist/assets/main-Cpd9VWYs.js +0 -169
- package/dist/assets/markdown-worker-CptoICmq.js +0 -2
- package/dist/assets/miniChat-B8_KLWiY.js +0 -2
- package/dist/assets/mobile-CtktKrXn.js +0 -2
- package/dist/assets/multirun-CYfP9Cfm.js +0 -4
- package/dist/assets/pl-B9No6MQp.js +0 -15
- package/dist/assets/platform-0w_Q9_ji.js +0 -2
- package/dist/assets/projectMeta-S1hJ7oBp.js +0 -3
- package/dist/assets/pt-BR-DyQ0WTDh.js +0 -15
- package/dist/assets/renderElectronMiniChatApp-iK14LWm8.js +0 -2
- package/dist/assets/renderMobileApp-DORLPWnb.js +0 -28
- package/dist/assets/src-rS3zCcsC.css +0 -2
- package/dist/assets/store-5B_LDnLM.js +0 -15
- package/dist/assets/uk-C_1KkqyP.js +0 -15
- package/dist/assets/useDesktopWindowControlsLayout-Dnr-jX0C.js +0 -1
- package/dist/assets/useEffectiveDirectory-CZLLyarc.js +0 -1
- package/dist/assets/useMobileNavigationStore-CoCiqNgm.js +0 -413
- package/dist/assets/useSessionAutoCleanup-xQyETQkG.js +0 -2
- package/dist/assets/useUIStore-Bxv3f23A.js +0 -1
- package/dist/assets/vendor-opencode-sdk-C07HQypa.js +0 -7
- package/dist/assets/zh-CN-uWILgBdt.js +0 -15
- package/dist/assets/zh-TW-MYfdvhbD.js +0 -15
- package/server/lib/small-model/call.js +0 -427
- package/server/lib/small-model/call.test.js +0 -382
- package/server/lib/small-model/opencode-session.js +0 -339
- package/server/lib/small-model/opencode-session.test.js +0 -177
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import crypto from 'node:crypto';
|
|
2
|
-
import { createOpencodeClient } from '@opencode-ai/sdk/v2';
|
|
3
2
|
import { DateTime } from 'luxon';
|
|
4
3
|
import parser from 'cron-parser';
|
|
4
|
+
import { makeOpenCodeV2Client } from '../opencode/v2-client.js';
|
|
5
5
|
import { expandSnippets } from '../opencode/snippets.js';
|
|
6
|
+
import {
|
|
7
|
+
getSessionGoalCapability,
|
|
8
|
+
isSessionGoalSupported,
|
|
9
|
+
sessionGoalUnavailableMessage,
|
|
10
|
+
} from '../session-goal/capability.js';
|
|
11
|
+
import { buildScheduledTaskMetadata } from '../session-metadata/system-session.js';
|
|
6
12
|
import { SCHEDULED_SLOT_CLAIMED_CODE } from './run-history-store.js';
|
|
7
13
|
|
|
8
14
|
const DEFAULT_GLOBAL_CONCURRENCY = 4;
|
|
@@ -15,16 +21,20 @@ const TASK_DUE_SLACK_MS = 5_000;
|
|
|
15
21
|
const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
16
22
|
const PROJECT_SYNC_RETRY_DELAY_MS = 1_000;
|
|
17
23
|
const MAX_PROJECT_SYNC_RETRIES = 3;
|
|
18
|
-
const ARCHIVE_404_RETRY_DELAY_MS = 250;
|
|
19
24
|
/** Poll interval while waiting for the OpenCode session turn (or goal) to settle. */
|
|
20
25
|
const SESSION_SETTLEMENT_POLL_MS = 1_000;
|
|
21
26
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
27
|
+
* Latest-side message page for settlement. order=desc + bounded limit — never
|
|
28
|
+
* order=asc page-0 + at(-1), which reads an old page once the session exceeds
|
|
29
|
+
* the limit.
|
|
24
30
|
*/
|
|
25
|
-
const
|
|
31
|
+
const SESSION_SETTLEMENT_MESSAGE_LIMIT = 50;
|
|
26
32
|
/** Goal terminal statuses — active/paused mean the run is still open. */
|
|
27
33
|
const GOAL_TERMINAL_STATUSES = new Set(['complete', 'blocked', 'budgetLimited']);
|
|
34
|
+
/** Assistant finish values that are real terminal success (not tool-calls mid-loop). */
|
|
35
|
+
const ASSISTANT_SUCCESS_FINISH = new Set(['stop']);
|
|
36
|
+
/** Assistant finish values that are terminal failure without an error object. */
|
|
37
|
+
const ASSISTANT_ERROR_FINISH = new Set(['error', 'length', 'content-filter']);
|
|
28
38
|
|
|
29
39
|
const buildTaskKey = (projectID, taskID) => `${projectID}:${taskID}`;
|
|
30
40
|
|
|
@@ -235,22 +245,6 @@ export const formatScheduledSessionTitle = (task, nowMs = Date.now()) => {
|
|
|
235
245
|
return `${SCHEDULED_TITLE_PREFIX}${trimmedName}${suffix}`;
|
|
236
246
|
};
|
|
237
247
|
|
|
238
|
-
const buildScheduledTaskMetadata = ({ projectID, taskID, runID, name }) => ({
|
|
239
|
-
openchamber: {
|
|
240
|
-
scheduledTask: {
|
|
241
|
-
projectID,
|
|
242
|
-
taskID,
|
|
243
|
-
runID,
|
|
244
|
-
name,
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const isMissingSessionError = (result) => {
|
|
250
|
-
const status = result?.error?.status ?? result?.error?.statusCode ?? result?.status;
|
|
251
|
-
return status === 404 || result?.error?.code === 'not_found';
|
|
252
|
-
};
|
|
253
|
-
|
|
254
248
|
const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
255
249
|
if (signal?.aborted) {
|
|
256
250
|
reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));
|
|
@@ -273,9 +267,11 @@ const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
|
273
267
|
|
|
274
268
|
const readMessageInfo = (entry) => {
|
|
275
269
|
if (!entry || typeof entry !== 'object') return null;
|
|
276
|
-
|
|
277
|
-
if (
|
|
278
|
-
|
|
270
|
+
// SessionMessageInfo is the entry itself; tolerate projected { info, parts }.
|
|
271
|
+
if (entry.info && typeof entry.info === 'object') {
|
|
272
|
+
return entry.info;
|
|
273
|
+
}
|
|
274
|
+
return entry;
|
|
279
275
|
};
|
|
280
276
|
|
|
281
277
|
const formatAssistantError = (error) => {
|
|
@@ -291,6 +287,79 @@ const formatAssistantError = (error) => {
|
|
|
291
287
|
return 'assistant error';
|
|
292
288
|
};
|
|
293
289
|
|
|
290
|
+
const isAssistantMessage = (info) => (
|
|
291
|
+
info
|
|
292
|
+
&& typeof info === 'object'
|
|
293
|
+
&& (info.type === 'assistant' || info.role === 'assistant')
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const isUserMessage = (info) => (
|
|
297
|
+
info
|
|
298
|
+
&& typeof info === 'object'
|
|
299
|
+
&& (info.type === 'user' || info.role === 'user')
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Classify the newest message on a latest-side page.
|
|
304
|
+
* - finish tool-calls is mid-loop, never success
|
|
305
|
+
* - incomplete (no time.completed) is never success
|
|
306
|
+
* - model-switched / non-assistant tails are not success
|
|
307
|
+
* - only a completed assistant with a real terminal finish succeeds
|
|
308
|
+
*/
|
|
309
|
+
const classifyMessageTail = (messages) => {
|
|
310
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
311
|
+
return { outcome: 'unknown' };
|
|
312
|
+
}
|
|
313
|
+
// order=desc contract: index 0 is the newest message on this page.
|
|
314
|
+
// Do not re-rank by time.created — that reintroduces the old-page bug when
|
|
315
|
+
// a bounded page mixes ages (and fixtures / clock skew can invert created).
|
|
316
|
+
const latest = readMessageInfo(messages[0]);
|
|
317
|
+
if (!latest) {
|
|
318
|
+
return { outcome: 'unknown' };
|
|
319
|
+
}
|
|
320
|
+
if (isAssistantMessage(latest)) {
|
|
321
|
+
if (latest.error) {
|
|
322
|
+
return {
|
|
323
|
+
outcome: 'error',
|
|
324
|
+
error: formatAssistantError(latest.error),
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
const finish = typeof latest.finish === 'string' ? latest.finish.trim() : '';
|
|
328
|
+
if (finish === 'tool-calls') {
|
|
329
|
+
// Model paused for tools — not a successful run terminal.
|
|
330
|
+
return { outcome: 'busy' };
|
|
331
|
+
}
|
|
332
|
+
if (ASSISTANT_ERROR_FINISH.has(finish)) {
|
|
333
|
+
return {
|
|
334
|
+
outcome: 'error',
|
|
335
|
+
error: finish === 'error' ? 'assistant error' : `assistant finish ${finish}`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (latest.time?.completed && (!finish || ASSISTANT_SUCCESS_FINISH.has(finish))) {
|
|
339
|
+
return { outcome: 'success' };
|
|
340
|
+
}
|
|
341
|
+
// Incomplete assistant (no completed, or unknown finish) — not success.
|
|
342
|
+
return { outcome: 'busy' };
|
|
343
|
+
}
|
|
344
|
+
if (isUserMessage(latest)) {
|
|
345
|
+
return { outcome: 'awaiting_assistant', lastInfo: latest };
|
|
346
|
+
}
|
|
347
|
+
// model-switched, compaction, system, etc. — not a success terminal.
|
|
348
|
+
return { outcome: 'busy' };
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const listLatestMessages = async (client, sessionID, requestOptions) => {
|
|
352
|
+
if (typeof client?.message?.list !== 'function') {
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
const messagesResult = await client.message.list({
|
|
356
|
+
sessionID,
|
|
357
|
+
limit: SESSION_SETTLEMENT_MESSAGE_LIMIT,
|
|
358
|
+
order: 'desc',
|
|
359
|
+
}, requestOptions);
|
|
360
|
+
return Array.isArray(messagesResult?.data) ? messagesResult.data : null;
|
|
361
|
+
};
|
|
362
|
+
|
|
294
363
|
const extractGoalFromSession = (session) => {
|
|
295
364
|
const metadata = session?.metadata;
|
|
296
365
|
if (!metadata || typeof metadata !== 'object') return null;
|
|
@@ -308,9 +377,13 @@ const extractGoalFromSession = (session) => {
|
|
|
308
377
|
|
|
309
378
|
/**
|
|
310
379
|
* Single-shot session outcome for post-run continuation (no polling).
|
|
311
|
-
* Goal-enabled: terminal complete → success; blocked/budgetLimited
|
|
312
|
-
* otherwise not-yet-success.
|
|
313
|
-
*
|
|
380
|
+
* Goal-enabled + supported: terminal complete → success; blocked/budgetLimited
|
|
381
|
+
* → error; otherwise not-yet-success. Goal unsupported: never invent success.
|
|
382
|
+
* Prefer Host `readSessionMetadata` over OpenCode `session.get` (direct clients
|
|
383
|
+
* do not see the Host store). Store read failure is never empty-goal success.
|
|
384
|
+
* Non-goal: session.active failure stays unknown; latest-side assistant tail
|
|
385
|
+
* must be a real terminal (finish tool-calls / incomplete / model-switch ≠
|
|
386
|
+
* success). History rows are never rewritten here.
|
|
314
387
|
*/
|
|
315
388
|
const snapshotSessionOutcome = async ({
|
|
316
389
|
client,
|
|
@@ -318,74 +391,90 @@ const snapshotSessionOutcome = async ({
|
|
|
318
391
|
projectPath,
|
|
319
392
|
goalEnabled,
|
|
320
393
|
signal,
|
|
394
|
+
readSessionMetadata = null,
|
|
321
395
|
}) => {
|
|
322
396
|
const requestOptions = signal ? { signal } : undefined;
|
|
323
397
|
|
|
324
|
-
if (goalEnabled
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (goal
|
|
333
|
-
|
|
334
|
-
return { outcome: 'success' };
|
|
335
|
-
}
|
|
336
|
-
return {
|
|
337
|
-
outcome: 'error',
|
|
338
|
-
error: goal.note || `goal ${goal.status}`,
|
|
339
|
-
};
|
|
398
|
+
if (goalEnabled) {
|
|
399
|
+
if (!isSessionGoalSupported()) {
|
|
400
|
+
// Host goal state unavailable — do not promote assistant-tail idle to success.
|
|
401
|
+
return { outcome: 'busy' };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const classifyGoal = (goal) => {
|
|
405
|
+
if (goal && GOAL_TERMINAL_STATUSES.has(goal.status)) {
|
|
406
|
+
if (goal.status === 'complete') {
|
|
407
|
+
return { outcome: 'success' };
|
|
340
408
|
}
|
|
409
|
+
return {
|
|
410
|
+
outcome: 'error',
|
|
411
|
+
error: goal.note || `goal ${goal.status}`,
|
|
412
|
+
};
|
|
341
413
|
}
|
|
342
|
-
|
|
343
|
-
|
|
414
|
+
return null;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// Prefer Host store: OpenCode session.get cannot see openchamber.goal.
|
|
418
|
+
if (typeof readSessionMetadata === 'function') {
|
|
419
|
+
try {
|
|
420
|
+
const metadata = await readSessionMetadata(sessionID);
|
|
421
|
+
const classified = classifyGoal(extractGoalFromSession({ metadata }));
|
|
422
|
+
if (classified) return classified;
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (signal?.aborted) throw error;
|
|
425
|
+
// Store read failure must not masquerade as empty goal / success.
|
|
426
|
+
return { outcome: 'busy' };
|
|
427
|
+
}
|
|
428
|
+
return { outcome: 'busy' };
|
|
344
429
|
}
|
|
345
|
-
return { outcome: 'busy' };
|
|
346
|
-
}
|
|
347
430
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
431
|
+
if (typeof client?.session?.get === 'function') {
|
|
432
|
+
try {
|
|
433
|
+
const sessionResult = await client.session.get({
|
|
434
|
+
sessionID,
|
|
435
|
+
directory: projectPath,
|
|
436
|
+
}, requestOptions);
|
|
437
|
+
if (!sessionResult?.error) {
|
|
438
|
+
const classified = classifyGoal(
|
|
439
|
+
extractGoalFromSession(sessionResult?.data ?? sessionResult),
|
|
440
|
+
);
|
|
441
|
+
if (classified) return classified;
|
|
358
442
|
}
|
|
443
|
+
} catch (error) {
|
|
444
|
+
if (signal?.aborted) throw error;
|
|
359
445
|
}
|
|
360
|
-
} catch (error) {
|
|
361
|
-
if (signal?.aborted) throw error;
|
|
362
446
|
}
|
|
447
|
+
return { outcome: 'busy' };
|
|
363
448
|
}
|
|
364
449
|
|
|
365
|
-
if (typeof client?.session?.
|
|
450
|
+
if (typeof client?.session?.active === 'function') {
|
|
366
451
|
try {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
if (
|
|
373
|
-
|
|
374
|
-
if (lastInfo?.role === 'assistant') {
|
|
375
|
-
if (lastInfo.error) {
|
|
376
|
-
return {
|
|
377
|
-
outcome: 'error',
|
|
378
|
-
error: formatAssistantError(lastInfo.error),
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
if (lastInfo.time?.completed) {
|
|
382
|
-
return { outcome: 'success' };
|
|
383
|
-
}
|
|
384
|
-
}
|
|
452
|
+
const activeMap = await client.session.active(requestOptions);
|
|
453
|
+
if (!(activeMap && typeof activeMap === 'object' && !Array.isArray(activeMap))) {
|
|
454
|
+
// Malformed active payload is unknown — never idle-success.
|
|
455
|
+
return { outcome: 'busy' };
|
|
456
|
+
}
|
|
457
|
+
if (Object.prototype.hasOwnProperty.call(activeMap, sessionID)) {
|
|
458
|
+
return { outcome: 'busy' };
|
|
385
459
|
}
|
|
386
460
|
} catch (error) {
|
|
387
461
|
if (signal?.aborted) throw error;
|
|
462
|
+
// active failure = unknown; do not fall through to message.list success.
|
|
463
|
+
return { outcome: 'busy' };
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
const messages = await listLatestMessages(client, sessionID, requestOptions);
|
|
469
|
+
if (!messages) {
|
|
470
|
+
return { outcome: 'busy' };
|
|
471
|
+
}
|
|
472
|
+
const classified = classifyMessageTail(messages);
|
|
473
|
+
if (classified.outcome === 'success' || classified.outcome === 'error') {
|
|
474
|
+
return classified;
|
|
388
475
|
}
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (signal?.aborted) throw error;
|
|
389
478
|
}
|
|
390
479
|
|
|
391
480
|
return { outcome: 'busy' };
|
|
@@ -393,12 +482,26 @@ const snapshotSessionOutcome = async ({
|
|
|
393
482
|
|
|
394
483
|
const parseSessionEventPhase = (event) => {
|
|
395
484
|
const payload = event?.payload?.payload ?? event?.payload;
|
|
396
|
-
|
|
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);
|
|
397
489
|
const sessionID = typeof properties?.sessionID === 'string' ? properties.sessionID : '';
|
|
398
490
|
if (!sessionID) {
|
|
399
491
|
return { phase: '', sessionID: '' };
|
|
400
492
|
}
|
|
401
|
-
|
|
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.
|
|
402
505
|
return { phase: 'idle', sessionID };
|
|
403
506
|
}
|
|
404
507
|
if (payload?.type === 'session.status') {
|
|
@@ -428,6 +531,25 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
428
531
|
maxGlobalConcurrency = DEFAULT_GLOBAL_CONCURRENCY,
|
|
429
532
|
maxProjectConcurrency = DEFAULT_PROJECT_CONCURRENCY,
|
|
430
533
|
maxRunDurationMs = DEFAULT_MAX_RUN_MS,
|
|
534
|
+
/** Host store write: (sessionId, directory, goal) => Promise */
|
|
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,
|
|
544
|
+
/** Host store read: (sessionId) => Promise<metadata|null> */
|
|
545
|
+
readSessionMetadata = null,
|
|
546
|
+
/**
|
|
547
|
+
* After a scheduled goal is first persisted, arm the session-goal loop.
|
|
548
|
+
* (session-goal runtime's own progress writes only broadcast — do not
|
|
549
|
+
* feed those back here.)
|
|
550
|
+
* @type {null | ((event: { sessionID: string, directory: string, metadata: object }) => void)}
|
|
551
|
+
*/
|
|
552
|
+
onGoalPersisted = null,
|
|
431
553
|
} = deps;
|
|
432
554
|
|
|
433
555
|
let started = false;
|
|
@@ -702,44 +824,56 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
702
824
|
+ '\n</system-reminder>';
|
|
703
825
|
};
|
|
704
826
|
|
|
705
|
-
const
|
|
827
|
+
const buildSessionCreateInput = (task, projectPath, title, metadata) => ({
|
|
828
|
+
title,
|
|
829
|
+
location: { directory: projectPath },
|
|
830
|
+
metadata,
|
|
831
|
+
...(task.execution.agent ? { agent: task.execution.agent } : {}),
|
|
706
832
|
model: {
|
|
833
|
+
id: task.execution.modelID,
|
|
707
834
|
providerID: task.execution.providerID,
|
|
708
|
-
|
|
835
|
+
...(task.execution.variant ? { variant: task.execution.variant } : {}),
|
|
709
836
|
},
|
|
710
|
-
...(task.execution.agent ? { agent: task.execution.agent } : {}),
|
|
711
|
-
...(task.execution.variant ? { variant: task.execution.variant } : {}),
|
|
712
|
-
parts: [
|
|
713
|
-
{
|
|
714
|
-
type: 'text',
|
|
715
|
-
text: expandSnippets(task.execution.prompt, projectPath),
|
|
716
|
-
},
|
|
717
|
-
...(task.execution.goalEnabled
|
|
718
|
-
? [{ type: 'text', text: buildGoalIntroText(task.execution.goalTokenBudget), synthetic: true }]
|
|
719
|
-
: []),
|
|
720
|
-
],
|
|
721
837
|
});
|
|
722
838
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
+
|
|
851
|
+
const buildPromptText = (task, projectPath) => {
|
|
852
|
+
const promptText = expandSnippets(task.execution.prompt, projectPath);
|
|
853
|
+
if (!task.execution.goalEnabled) {
|
|
854
|
+
return promptText;
|
|
855
|
+
}
|
|
856
|
+
return `${promptText}\n${buildGoalIntroText(task.execution.goalTokenBudget)}`;
|
|
857
|
+
};
|
|
858
|
+
|
|
859
|
+
// Scheduled goal runs: write the file-backed objective, then persist a
|
|
860
|
+
// trackable goal record into the Host session-metadata store (same seam as
|
|
861
|
+
// manual UI goals). OpenCode SessionInfo has no openchamber.goal channel.
|
|
726
862
|
const createTaskGoal = async ({
|
|
727
|
-
baseUrl,
|
|
728
|
-
authHeaders,
|
|
729
863
|
sessionID,
|
|
730
864
|
projectPath,
|
|
731
865
|
task,
|
|
732
|
-
scheduledTaskMarker,
|
|
733
866
|
signal,
|
|
734
867
|
}) => {
|
|
735
868
|
signal?.throwIfAborted?.();
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
//
|
|
742
|
-
//
|
|
869
|
+
if (typeof persistSessionGoal !== 'function') {
|
|
870
|
+
throw new Error('session goal persist is not configured');
|
|
871
|
+
}
|
|
872
|
+
// File-backed objective keyed by session id: the full expanded prompt
|
|
873
|
+
// lives under the OpenChamber data dir. If the file write fails, fall
|
|
874
|
+
// back to an inline objective so parseGoalMetadata still accepts the
|
|
875
|
+
// record. Oversized prompts are distilled into audit criteria by the
|
|
876
|
+
// small model; on distill failure a head+tail excerpt keeps intent.
|
|
743
877
|
let objectiveText = expandSnippets(task.execution.prompt, projectPath);
|
|
744
878
|
if (objectiveText.length > 5000) {
|
|
745
879
|
let distilled = null;
|
|
@@ -788,205 +922,142 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
788
922
|
try {
|
|
789
923
|
const { writeObjective } = await import('../session-goal/objectives.js');
|
|
790
924
|
await writeObjective(sessionID, objectiveText);
|
|
791
|
-
signal?.throwIfAborted?.();
|
|
792
925
|
objectiveFile = true;
|
|
926
|
+
signal?.throwIfAborted?.();
|
|
793
927
|
} catch (error) {
|
|
794
928
|
if (signal?.aborted) {
|
|
795
929
|
throw error;
|
|
796
930
|
}
|
|
797
|
-
console.warn('[scheduled-tasks] goal objective file write failed,
|
|
931
|
+
console.warn('[scheduled-tasks] goal objective file write failed, continuing with inline objective:', error?.message || error);
|
|
798
932
|
}
|
|
799
933
|
signal?.throwIfAborted?.();
|
|
934
|
+
|
|
935
|
+
const budgetRaw = task.execution?.goalTokenBudget;
|
|
936
|
+
const tokenBudget = Number.isFinite(budgetRaw) && budgetRaw > 0
|
|
937
|
+
? Math.floor(budgetRaw)
|
|
938
|
+
: null;
|
|
939
|
+
const now = Date.now();
|
|
800
940
|
const goal = {
|
|
801
|
-
id:
|
|
802
|
-
objective: objectiveFile ? '' : objectiveText.slice(0, 5000),
|
|
803
|
-
objectiveFile,
|
|
941
|
+
id: crypto.randomUUID(),
|
|
804
942
|
status: 'active',
|
|
805
|
-
|
|
943
|
+
// File-backed: flag only (text lives under goals/<sessionId>.md).
|
|
944
|
+
// Inline fallback: full objective text when the file write failed.
|
|
945
|
+
objective: objectiveFile ? '' : objectiveText,
|
|
946
|
+
objectiveFile,
|
|
947
|
+
tokenBudget,
|
|
806
948
|
tokensUsed: 0,
|
|
807
949
|
turnsUsed: 0,
|
|
808
950
|
blockedStreak: 0,
|
|
809
951
|
note: '',
|
|
810
952
|
statusReason: '',
|
|
811
953
|
lastAccountedMessageID: '',
|
|
954
|
+
executionGeneration: 0,
|
|
812
955
|
createdAt: now,
|
|
813
956
|
updatedAt: now,
|
|
814
957
|
};
|
|
815
|
-
const url = new URL(`${baseUrl}/session/${encodeURIComponent(sessionID)}`);
|
|
816
|
-
url.searchParams.set('directory', projectPath);
|
|
817
|
-
// Preserve the scheduledTask marker when goal metadata is patched; a full
|
|
818
|
-
// openchamber replace would otherwise drop ownership for history/UI.
|
|
819
|
-
const response = await fetch(url.toString(), {
|
|
820
|
-
method: 'PATCH',
|
|
821
|
-
headers: {
|
|
822
|
-
...authHeaders,
|
|
823
|
-
'content-type': 'application/json',
|
|
824
|
-
accept: 'application/json',
|
|
825
|
-
},
|
|
826
|
-
body: JSON.stringify({
|
|
827
|
-
metadata: {
|
|
828
|
-
openchamber: {
|
|
829
|
-
...(scheduledTaskMarker ? { scheduledTask: scheduledTaskMarker } : {}),
|
|
830
|
-
goal,
|
|
831
|
-
},
|
|
832
|
-
},
|
|
833
|
-
}),
|
|
834
|
-
...(signal ? { signal } : {}),
|
|
835
|
-
});
|
|
836
|
-
if (!response.ok) {
|
|
837
|
-
throw new Error(`goal metadata patch failed (${response.status})`);
|
|
838
|
-
}
|
|
839
|
-
};
|
|
840
958
|
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
959
|
+
// Persist failure must throw — never prompt as if the goal was registered.
|
|
960
|
+
const persisted = await persistSessionGoal(sessionID, projectPath, goal);
|
|
961
|
+
signal?.throwIfAborted?.();
|
|
962
|
+
|
|
963
|
+
if (typeof onGoalPersisted === 'function') {
|
|
964
|
+
try {
|
|
965
|
+
// Prefer full metadata when the seam returns the store object; otherwise
|
|
966
|
+
// synthesize the openchamber.goal shape the session-goal loop expects.
|
|
967
|
+
const metadata = persisted
|
|
968
|
+
&& typeof persisted === 'object'
|
|
969
|
+
&& !Array.isArray(persisted)
|
|
970
|
+
&& persisted.openchamber
|
|
971
|
+
? persisted
|
|
972
|
+
: { openchamber: { goal } };
|
|
973
|
+
onGoalPersisted({
|
|
974
|
+
sessionID,
|
|
975
|
+
directory: projectPath,
|
|
976
|
+
metadata,
|
|
977
|
+
});
|
|
978
|
+
} catch (error) {
|
|
979
|
+
console.warn('[scheduled-tasks] onGoalPersisted failed:', error?.message || error);
|
|
980
|
+
}
|
|
858
981
|
}
|
|
859
982
|
};
|
|
860
983
|
|
|
861
|
-
const
|
|
984
|
+
const runSessionPrompt = async ({ client, sessionID, projectPath, task, signal }) => {
|
|
862
985
|
signal?.throwIfAborted?.();
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
...authHeaders,
|
|
869
|
-
'content-type': 'application/json',
|
|
870
|
-
accept: 'application/json',
|
|
871
|
-
},
|
|
872
|
-
body: JSON.stringify(buildPromptAsyncPayload(task, projectPath)),
|
|
873
|
-
...(signal ? { signal } : {}),
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
if (!response.ok) {
|
|
877
|
-
const body = await response.text().catch(() => '');
|
|
878
|
-
throw new Error(`prompt_async failed (${response.status})${body ? `: ${body}` : ''}`);
|
|
879
|
-
}
|
|
986
|
+
const requestOptions = signal ? { signal } : undefined;
|
|
987
|
+
await client.session.prompt({
|
|
988
|
+
sessionID,
|
|
989
|
+
text: buildPromptText(task, projectPath),
|
|
990
|
+
}, requestOptions);
|
|
880
991
|
};
|
|
881
992
|
|
|
882
993
|
/**
|
|
883
|
-
*
|
|
884
|
-
* agent finishes. History must reflect the real session
|
|
885
|
-
* wall-clock duration — poll
|
|
994
|
+
* session.prompt / command return when the turn is *admitted*, not when the
|
|
995
|
+
* agent finishes. History must reflect the real session outcome and
|
|
996
|
+
* wall-clock duration — poll until idle+settled on a latest-side message
|
|
997
|
+
* page (order=desc). finish tool-calls and incomplete assistants never
|
|
998
|
+
* succeed; two incomplete idle probes must not invent success.
|
|
999
|
+
* session.active / message.list failures stay unknown — never idle or empty
|
|
1000
|
+
* success. Goal-enabled runs that passed admission still settle on the
|
|
1001
|
+
* assistant tail here when Host goal state is unavailable.
|
|
886
1002
|
*/
|
|
887
1003
|
const waitForRunOutcome = async ({
|
|
888
1004
|
client,
|
|
889
1005
|
sessionID,
|
|
890
|
-
projectPath,
|
|
891
|
-
goalEnabled,
|
|
892
1006
|
signal,
|
|
893
1007
|
}) => {
|
|
894
1008
|
const requestOptions = signal ? { signal } : undefined;
|
|
895
|
-
let incompleteAssistantProbes = 0;
|
|
896
1009
|
let emptyIdleProbes = 0;
|
|
897
1010
|
|
|
898
1011
|
for (;;) {
|
|
899
1012
|
signal?.throwIfAborted?.();
|
|
900
1013
|
|
|
901
|
-
|
|
1014
|
+
let sessionBusy = null;
|
|
1015
|
+
if (typeof client?.session?.active === 'function') {
|
|
902
1016
|
try {
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
}, requestOptions);
|
|
907
|
-
if (!sessionResult?.error) {
|
|
908
|
-
const goal = extractGoalFromSession(sessionResult?.data);
|
|
909
|
-
if (goal && GOAL_TERMINAL_STATUSES.has(goal.status)) {
|
|
910
|
-
if (goal.status === 'complete') {
|
|
911
|
-
return { outcome: 'success' };
|
|
912
|
-
}
|
|
913
|
-
return {
|
|
914
|
-
outcome: 'error',
|
|
915
|
-
error: goal.note || `goal ${goal.status}`,
|
|
916
|
-
};
|
|
917
|
-
}
|
|
1017
|
+
const activeMap = await client.session.active(requestOptions);
|
|
1018
|
+
if (activeMap && typeof activeMap === 'object' && !Array.isArray(activeMap)) {
|
|
1019
|
+
sessionBusy = Object.prototype.hasOwnProperty.call(activeMap, sessionID);
|
|
918
1020
|
}
|
|
1021
|
+
// Malformed / thrown active stays null → unknown, keep polling.
|
|
919
1022
|
} catch (error) {
|
|
920
1023
|
if (signal?.aborted) throw error;
|
|
921
|
-
// Transient get failures: keep polling until watchdog aborts.
|
|
922
1024
|
}
|
|
923
1025
|
}
|
|
924
1026
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const statusResult = await client.session.status({
|
|
929
|
-
directory: projectPath,
|
|
930
|
-
}, requestOptions);
|
|
931
|
-
if (!statusResult?.error && statusResult?.data && typeof statusResult.data === 'object') {
|
|
932
|
-
const statusValue = statusResult.data[sessionID];
|
|
933
|
-
const type = statusValue?.type ?? statusValue?.status;
|
|
934
|
-
sessionBusy = type === 'busy' || type === 'retry';
|
|
935
|
-
}
|
|
936
|
-
} catch (error) {
|
|
937
|
-
if (signal?.aborted) throw error;
|
|
1027
|
+
if (sessionBusy !== false) {
|
|
1028
|
+
if (sessionBusy === true) {
|
|
1029
|
+
emptyIdleProbes = 0;
|
|
938
1030
|
}
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
if (sessionBusy) {
|
|
942
|
-
incompleteAssistantProbes = 0;
|
|
943
|
-
emptyIdleProbes = 0;
|
|
944
1031
|
await sleep(SESSION_SETTLEMENT_POLL_MS, signal);
|
|
945
1032
|
continue;
|
|
946
1033
|
}
|
|
947
1034
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
if (
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
error: formatAssistantError(lastInfo.error),
|
|
965
|
-
};
|
|
966
|
-
}
|
|
967
|
-
if (lastInfo.time?.completed) {
|
|
968
|
-
return { outcome: 'success' };
|
|
969
|
-
}
|
|
970
|
-
incompleteAssistantProbes += 1;
|
|
971
|
-
if (incompleteAssistantProbes >= INCOMPLETE_ASSISTANT_SETTLE_PROBES) {
|
|
972
|
-
return { outcome: 'success' };
|
|
973
|
-
}
|
|
974
|
-
} else {
|
|
975
|
-
incompleteAssistantProbes = 0;
|
|
976
|
-
emptyIdleProbes += 1;
|
|
977
|
-
// Prompt admitted but no assistant yet, or aborted before reply.
|
|
978
|
-
// Allow several idle probes so we don't race the first token.
|
|
979
|
-
if (emptyIdleProbes >= 5 && lastInfo?.role === 'user') {
|
|
980
|
-
return {
|
|
981
|
-
outcome: 'error',
|
|
982
|
-
error: 'session ended without assistant response',
|
|
983
|
-
};
|
|
984
|
-
}
|
|
1035
|
+
try {
|
|
1036
|
+
const messages = await listLatestMessages(client, sessionID, requestOptions);
|
|
1037
|
+
if (messages) {
|
|
1038
|
+
const classified = classifyMessageTail(messages);
|
|
1039
|
+
if (classified.outcome === 'success' || classified.outcome === 'error') {
|
|
1040
|
+
return classified;
|
|
1041
|
+
}
|
|
1042
|
+
if (classified.outcome === 'awaiting_assistant') {
|
|
1043
|
+
emptyIdleProbes += 1;
|
|
1044
|
+
// Prompt admitted but no assistant yet, or aborted before reply.
|
|
1045
|
+
// Allow several idle probes so we don't race the first token.
|
|
1046
|
+
if (emptyIdleProbes >= 5) {
|
|
1047
|
+
return {
|
|
1048
|
+
outcome: 'error',
|
|
1049
|
+
error: 'session ended without assistant response',
|
|
1050
|
+
};
|
|
985
1051
|
}
|
|
1052
|
+
} else {
|
|
1053
|
+
// busy / unknown tails (tool-calls, incomplete, model-switch, …)
|
|
1054
|
+
// keep polling — never promote incomplete idle to success.
|
|
1055
|
+
emptyIdleProbes = 0;
|
|
986
1056
|
}
|
|
987
|
-
} catch (error) {
|
|
988
|
-
if (signal?.aborted) throw error;
|
|
989
1057
|
}
|
|
1058
|
+
} catch (error) {
|
|
1059
|
+
if (signal?.aborted) throw error;
|
|
1060
|
+
// message.list failure stays unknown — keep polling under the watchdog.
|
|
990
1061
|
}
|
|
991
1062
|
|
|
992
1063
|
await sleep(SESSION_SETTLEMENT_POLL_MS, signal);
|
|
@@ -1003,7 +1074,9 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1003
1074
|
const requestOptions = signal ? { signal } : undefined;
|
|
1004
1075
|
let commands = [];
|
|
1005
1076
|
try {
|
|
1006
|
-
const response = await client.command.list({
|
|
1077
|
+
const response = await client.command.list({
|
|
1078
|
+
location: { directory: projectPath },
|
|
1079
|
+
}, requestOptions);
|
|
1007
1080
|
commands = Array.isArray(response?.data) ? response.data : [];
|
|
1008
1081
|
} catch (error) {
|
|
1009
1082
|
if (signal?.aborted) {
|
|
@@ -1020,28 +1093,30 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1020
1093
|
|
|
1021
1094
|
await client.session.command({
|
|
1022
1095
|
sessionID,
|
|
1023
|
-
directory: projectPath,
|
|
1024
1096
|
command: parsed.command,
|
|
1025
1097
|
arguments: parsed.arguments,
|
|
1026
1098
|
...(task.execution.agent ? { agent: task.execution.agent } : {}),
|
|
1027
|
-
model:
|
|
1028
|
-
|
|
1099
|
+
model: {
|
|
1100
|
+
id: task.execution.modelID,
|
|
1101
|
+
providerID: task.execution.providerID,
|
|
1102
|
+
...(task.execution.variant ? { variant: task.execution.variant } : {}),
|
|
1103
|
+
},
|
|
1029
1104
|
}, requestOptions);
|
|
1030
1105
|
|
|
1031
1106
|
return true;
|
|
1032
1107
|
};
|
|
1033
1108
|
|
|
1034
|
-
const
|
|
1035
|
-
if (!sessionID ||
|
|
1109
|
+
const interruptCreatedSessionBestEffort = async ({ client, sessionID }) => {
|
|
1110
|
+
if (!sessionID || typeof client?.session?.interrupt !== 'function') {
|
|
1036
1111
|
return;
|
|
1037
1112
|
}
|
|
1038
1113
|
try {
|
|
1039
|
-
await client.session.
|
|
1114
|
+
await client.session.interrupt({
|
|
1040
1115
|
sessionID,
|
|
1041
|
-
|
|
1116
|
+
continue: false,
|
|
1042
1117
|
});
|
|
1043
1118
|
} catch {
|
|
1044
|
-
// Never let upstream
|
|
1119
|
+
// Never let upstream interrupt failure replace the watchdog timeout error.
|
|
1045
1120
|
}
|
|
1046
1121
|
};
|
|
1047
1122
|
|
|
@@ -1055,6 +1130,24 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1055
1130
|
|
|
1056
1131
|
signal?.throwIfAborted?.();
|
|
1057
1132
|
|
|
1133
|
+
// Goal-enabled tasks: refuse before session create / objective write /
|
|
1134
|
+
// prompt when Host goal state is unavailable OR persist is not wired.
|
|
1135
|
+
// Config + run-history stay; only this run is recorded as error (no
|
|
1136
|
+
// false first-turn success / no create·distill·prompt side effects).
|
|
1137
|
+
if (task.execution?.goalEnabled) {
|
|
1138
|
+
const capability = getSessionGoalCapability();
|
|
1139
|
+
if (!capability.supported) {
|
|
1140
|
+
throw new Error(sessionGoalUnavailableMessage(capability));
|
|
1141
|
+
}
|
|
1142
|
+
if (typeof persistSessionGoal !== 'function') {
|
|
1143
|
+
throw new Error('session goal persist is not configured');
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
if (typeof persistSessionMetadata !== 'function') {
|
|
1148
|
+
throw new Error('scheduled-task session metadata persist is not configured');
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1058
1151
|
if (typeof waitForOpenCodeReady === 'function') {
|
|
1059
1152
|
await waitForOpenCodeReady(10_000, 250);
|
|
1060
1153
|
signal?.throwIfAborted?.();
|
|
@@ -1062,26 +1155,16 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1062
1155
|
|
|
1063
1156
|
const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
|
|
1064
1157
|
const authHeaders = getOpenCodeAuthHeaders();
|
|
1065
|
-
const client =
|
|
1158
|
+
const client = makeOpenCodeV2Client({
|
|
1066
1159
|
baseUrl,
|
|
1067
|
-
|
|
1160
|
+
authHeaders,
|
|
1068
1161
|
});
|
|
1069
1162
|
const requestOptions = signal ? { signal } : undefined;
|
|
1070
1163
|
|
|
1071
|
-
const taskName = typeof task?.name === 'string' && task.name.trim().length > 0
|
|
1072
|
-
? task.name.trim()
|
|
1073
|
-
: 'Schedule';
|
|
1074
|
-
const scheduledTaskMarker = {
|
|
1075
|
-
projectID,
|
|
1076
|
-
taskID: task.id,
|
|
1077
|
-
runID,
|
|
1078
|
-
name: taskName,
|
|
1079
|
-
};
|
|
1080
|
-
|
|
1081
1164
|
let sessionID;
|
|
1082
|
-
// Watchdog abort must fire session.
|
|
1083
|
-
// work (small-model distill / writeObjective). Register
|
|
1084
|
-
// never on ordinary non-timeout failures or success.
|
|
1165
|
+
// Watchdog abort must fire session.interrupt even while awaiting
|
|
1166
|
+
// non-cancellable work (small-model distill / writeObjective). Register
|
|
1167
|
+
// once after create; never on ordinary non-timeout failures or success.
|
|
1085
1168
|
let sessionAbortStarted = false;
|
|
1086
1169
|
let onWatchdogAbort = null;
|
|
1087
1170
|
const triggerSessionAbortOnce = () => {
|
|
@@ -1089,7 +1172,7 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1089
1172
|
return;
|
|
1090
1173
|
}
|
|
1091
1174
|
sessionAbortStarted = true;
|
|
1092
|
-
void
|
|
1175
|
+
void interruptCreatedSessionBestEffort({ client, sessionID });
|
|
1093
1176
|
};
|
|
1094
1177
|
const clearWatchdogAbortListener = () => {
|
|
1095
1178
|
if (onWatchdogAbort && signal) {
|
|
@@ -1099,15 +1182,21 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1099
1182
|
};
|
|
1100
1183
|
|
|
1101
1184
|
try {
|
|
1102
|
-
const
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1185
|
+
const isolationMetadata = buildScheduledTaskMetadata({
|
|
1186
|
+
projectID,
|
|
1187
|
+
taskID: task.id,
|
|
1188
|
+
runID,
|
|
1189
|
+
name: task.name,
|
|
1190
|
+
});
|
|
1191
|
+
const session = await client.session.create(
|
|
1192
|
+
buildSessionCreateInput(task, projectPath, title, isolationMetadata),
|
|
1193
|
+
requestOptions,
|
|
1194
|
+
);
|
|
1195
|
+
sessionID = session?.id;
|
|
1108
1196
|
if (!sessionID) {
|
|
1109
1197
|
throw new Error('failed to create session');
|
|
1110
1198
|
}
|
|
1199
|
+
await persistScheduledTaskIsolation(sessionID, projectPath, isolationMetadata);
|
|
1111
1200
|
|
|
1112
1201
|
if (signal) {
|
|
1113
1202
|
if (signal.aborted) {
|
|
@@ -1130,19 +1219,6 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1130
1219
|
}
|
|
1131
1220
|
}
|
|
1132
1221
|
|
|
1133
|
-
try {
|
|
1134
|
-
// Archive must succeed before any goal/prompt/command work. A 404 gets
|
|
1135
|
-
// one short bounded retry; any other failure aborts without prompting.
|
|
1136
|
-
await archiveSessionBeforePrompt({ client, sessionID, projectPath, signal });
|
|
1137
|
-
} catch (archiveError) {
|
|
1138
|
-
if (attachFailed) {
|
|
1139
|
-
throw new Error(
|
|
1140
|
-
`attachSession failed (${safeErrorMessage(attachFailed)}); archive also failed: ${safeErrorMessage(archiveError)}`,
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
throw archiveError;
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
1222
|
if (attachFailed) {
|
|
1147
1223
|
throw new Error(`attachSession failed: ${safeErrorMessage(attachFailed)}`);
|
|
1148
1224
|
}
|
|
@@ -1162,12 +1238,9 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1162
1238
|
|
|
1163
1239
|
if (task.execution.goalEnabled) {
|
|
1164
1240
|
await createTaskGoal({
|
|
1165
|
-
baseUrl,
|
|
1166
|
-
authHeaders,
|
|
1167
1241
|
sessionID,
|
|
1168
1242
|
projectPath,
|
|
1169
1243
|
task,
|
|
1170
|
-
scheduledTaskMarker,
|
|
1171
1244
|
signal,
|
|
1172
1245
|
});
|
|
1173
1246
|
}
|
|
@@ -1182,9 +1255,8 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1182
1255
|
signal,
|
|
1183
1256
|
});
|
|
1184
1257
|
if (!executedAsCommand) {
|
|
1185
|
-
await
|
|
1186
|
-
|
|
1187
|
-
authHeaders,
|
|
1258
|
+
await runSessionPrompt({
|
|
1259
|
+
client,
|
|
1188
1260
|
sessionID,
|
|
1189
1261
|
projectPath,
|
|
1190
1262
|
task,
|
|
@@ -1192,13 +1264,11 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1192
1264
|
});
|
|
1193
1265
|
}
|
|
1194
1266
|
|
|
1195
|
-
// Do not finalize on admission alone — wait for the real agent turn
|
|
1196
|
-
//
|
|
1267
|
+
// Do not finalize on admission alone — wait for the real agent turn so
|
|
1268
|
+
// history status/duration match what the user sees in chat.
|
|
1197
1269
|
const settlement = await waitForRunOutcome({
|
|
1198
1270
|
client,
|
|
1199
1271
|
sessionID,
|
|
1200
|
-
projectPath,
|
|
1201
|
-
goalEnabled: Boolean(task.execution?.goalEnabled),
|
|
1202
1272
|
signal,
|
|
1203
1273
|
});
|
|
1204
1274
|
|
|
@@ -1369,8 +1439,8 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1369
1439
|
|
|
1370
1440
|
if (raced.timedOut) {
|
|
1371
1441
|
// Bound: do not await uncancellable steps (distill / writeObjective).
|
|
1372
|
-
// session.
|
|
1373
|
-
// throwIfAborted gates stop goal
|
|
1442
|
+
// session.interrupt is started by the signal listener on create;
|
|
1443
|
+
// later throwIfAborted gates stop goal write / command / prompt.
|
|
1374
1444
|
throw timeoutError;
|
|
1375
1445
|
}
|
|
1376
1446
|
|
|
@@ -1701,15 +1771,16 @@ export const createScheduledTasksRuntime = (deps) => {
|
|
|
1701
1771
|
|
|
1702
1772
|
const baseUrl = buildOpenCodeUrl('/', '').replace(/\/$/, '');
|
|
1703
1773
|
const authHeaders = getOpenCodeAuthHeaders();
|
|
1704
|
-
const client =
|
|
1774
|
+
const client = makeOpenCodeV2Client({
|
|
1705
1775
|
baseUrl,
|
|
1706
|
-
|
|
1776
|
+
authHeaders,
|
|
1707
1777
|
});
|
|
1708
1778
|
const snapshot = await snapshotSessionOutcome({
|
|
1709
1779
|
client,
|
|
1710
1780
|
sessionID,
|
|
1711
1781
|
projectPath,
|
|
1712
1782
|
goalEnabled: Boolean(task.execution?.goalEnabled),
|
|
1783
|
+
readSessionMetadata,
|
|
1713
1784
|
});
|
|
1714
1785
|
if (snapshot.outcome !== 'success') {
|
|
1715
1786
|
return;
|