chainlesschain 0.164.0 → 0.165.2
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 +248 -2
- package/package.json +7 -4
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
- package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
- package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
- package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
- package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
- package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
- package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
- package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
- package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
- package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
- package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
- package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/cowork.js +559 -62
- package/src/commands/plugin.js +819 -64
- package/src/commands/session-location.js +318 -7
- package/src/constants.js +2 -2
- package/src/data/changelog.json +28 -2
- package/src/gateways/ws/action-protocol.js +293 -19
- package/src/gateways/ws/message-dispatcher.js +30 -0
- package/src/gateways/ws/remote-session-protocol.js +542 -35
- package/src/gateways/ws/ws-approval-gate.js +5 -0
- package/src/gateways/ws/ws-server.js +462 -38
- package/src/harness/jsonl-session-store.js +638 -2
- package/src/harness/prompt-compressor.js +4 -0
- package/src/harness/remote-session-audit.js +1 -0
- package/src/harness/remote-session-crypto.js +750 -50
- package/src/harness/remote-session-registry.js +795 -27
- package/src/lib/auto-mode-config.js +98 -3
- package/src/lib/background-agent-keeper-client.js +29 -0
- package/src/lib/background-agent-keeper-protocol.js +158 -10
- package/src/lib/background-agent-supervisor.js +870 -48
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +64 -0
- package/src/lib/cowork-task-runner.js +70 -0
- package/src/lib/cowork-workflow.js +887 -62
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/doctor-checkup.js +34 -10
- package/src/lib/dynamic-workflow-draft.js +535 -0
- package/src/lib/dynamic-workflow-facade.js +1105 -17
- package/src/lib/dynamic-workflow-runtime.js +2974 -0
- package/src/lib/execution-location-contract.js +111 -7
- package/src/lib/execution-location-runtime.js +3 -1
- package/src/lib/execution-location-target.js +910 -0
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/mcp-call-ledger-store.js +35 -0
- package/src/lib/mcp-call-ledger.js +30 -0
- package/src/lib/mcp-stdio-package-materialization.js +1 -1
- package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
- package/src/lib/plugin-runtime/install.js +4956 -657
- package/src/lib/plugin-runtime/manifest.js +3 -1
- package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
- package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
- package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
- package/src/lib/plugin-runtime/marketplace-network.js +317 -0
- package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
- package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
- package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
- package/src/lib/plugin-runtime/policy.js +21 -6
- package/src/lib/plugin-runtime/publisher-trust.js +332 -0
- package/src/lib/plugin-runtime/remote-source.js +323 -27
- package/src/lib/plugin-runtime/scopes.js +317 -41
- package/src/lib/plugin-runtime/transaction-journal.js +806 -0
- package/src/lib/public-ide-capability-manifest.js +1 -1
- package/src/lib/remote-approval-bridge.js +745 -108
- package/src/lib/remote-control.js +2 -0
- package/src/lib/remote-membership-coordinator.js +612 -46
- package/src/lib/remote-membership-host-store.js +59 -10
- package/src/lib/runtime-provenance-ledger.js +171 -46
- package/src/lib/shell-approval.js +169 -6
- package/src/lib/sub-agent-context.js +268 -2
- package/src/lib/task-model-selector.js +1 -1
- package/src/lib/with-file-lock.js +167 -42
- package/src/repl/agent-repl.js +70 -29
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1695 -194
- package/src/runtime/headless-runner.js +22 -5
- package/src/runtime/headless-stream.js +19 -3
- package/src/workers/background-agent-keeper.js +279 -58
- package/src/workers/background-agent-worker.js +7 -0
- package/src/workers/background-turn-bootstrap.js +29 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
- package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
- package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
- package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
- package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* @module cowork-workflow
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { createHash } from "node:crypto";
|
|
26
|
+
import { types as utilTypes } from "node:util";
|
|
25
27
|
import fs, {
|
|
26
28
|
existsSync,
|
|
27
29
|
mkdirSync,
|
|
@@ -52,6 +54,7 @@ import {
|
|
|
52
54
|
normalizeWorkflowDefinitionDigest,
|
|
53
55
|
verifyCoworkWorkflowRecord,
|
|
54
56
|
} from "./workflow-definition-contract.js";
|
|
57
|
+
import { canonicalJson } from "./scheduler-kernel/contract.js";
|
|
55
58
|
|
|
56
59
|
/** Maximum number of items a single forEach step can expand into. */
|
|
57
60
|
export const MAX_FAN_OUT = 500;
|
|
@@ -59,6 +62,512 @@ export const MAX_FAN_OUT = 500;
|
|
|
59
62
|
/** Absolute ceiling on a single loop step's iterations (infinite-loop guard). */
|
|
60
63
|
export const MAX_LOOP_ITERATIONS = 100;
|
|
61
64
|
export const COWORK_WORKFLOW_RUN_RECORD_SCHEMA = "cc-cowork-workflow-run/v1";
|
|
65
|
+
export const COWORK_WORKFLOW_RUN_ADMISSION_INVALID_CODE =
|
|
66
|
+
"COWORK_WORKFLOW_RUN_ADMISSION_INVALID";
|
|
67
|
+
export const COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE =
|
|
68
|
+
"COWORK_WORKFLOW_RUN_RESULT_INVALID";
|
|
69
|
+
export const COWORK_WORKFLOW_CONTROL_SIGNAL_CODE =
|
|
70
|
+
"COWORK_WORKFLOW_CONTROL_SIGNAL";
|
|
71
|
+
|
|
72
|
+
const DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA =
|
|
73
|
+
"cc-dynamic-workflow-run-admission/v1";
|
|
74
|
+
const EXECUTION_LOCATION_AUTHORITY_SCHEMA =
|
|
75
|
+
"cc-session-execution-location-authority/v1";
|
|
76
|
+
const EXECUTION_LOCATION_BINDING_SCHEMA = "cc-execution-location-binding/v1";
|
|
77
|
+
const COWORK_WORKFLOW_RECORD_SCHEMA = "cc-cowork-workflow-record/v1";
|
|
78
|
+
const RUN_ADMISSION_FIELDS = new Set([
|
|
79
|
+
"schema",
|
|
80
|
+
"definition",
|
|
81
|
+
"executionLocation",
|
|
82
|
+
"executionPolicy",
|
|
83
|
+
"definitionDigest",
|
|
84
|
+
"executionLocationDigest",
|
|
85
|
+
"preflightDigest",
|
|
86
|
+
"maxParallel",
|
|
87
|
+
"credentialValuesTransferred",
|
|
88
|
+
"admissionDigest",
|
|
89
|
+
]);
|
|
90
|
+
const RUN_ADMISSION_DEFINITION_FIELDS = new Set([
|
|
91
|
+
"schema",
|
|
92
|
+
"definitionDigest",
|
|
93
|
+
"authority",
|
|
94
|
+
]);
|
|
95
|
+
const RUN_ADMISSION_DEFINITION_AUTHORITY_FIELDS = new Set([
|
|
96
|
+
"status",
|
|
97
|
+
"recordSchema",
|
|
98
|
+
"definitionSchema",
|
|
99
|
+
"definitionDigest",
|
|
100
|
+
]);
|
|
101
|
+
const RUN_ADMISSION_EXECUTION_LOCATION_FIELDS = new Set([
|
|
102
|
+
"authoritySchema",
|
|
103
|
+
"authority",
|
|
104
|
+
"session",
|
|
105
|
+
"bindingSchema",
|
|
106
|
+
"location",
|
|
107
|
+
]);
|
|
108
|
+
const RUN_ADMISSION_SESSION_FIELDS = new Set([
|
|
109
|
+
"sessionId",
|
|
110
|
+
"headHash",
|
|
111
|
+
"eventCount",
|
|
112
|
+
]);
|
|
113
|
+
const RUN_ADMISSION_EXECUTION_POLICY_FIELDS = new Set([
|
|
114
|
+
"cwd",
|
|
115
|
+
"continueOnError",
|
|
116
|
+
"pipeline",
|
|
117
|
+
"provider",
|
|
118
|
+
"model",
|
|
119
|
+
]);
|
|
120
|
+
const RUN_ADMISSION_LLM_OPTION_FIELDS = new Set(["provider", "model"]);
|
|
121
|
+
const ADMITTED_TASK_ENTRY_REQUIRED_FIELDS = new Set([
|
|
122
|
+
"taskId",
|
|
123
|
+
"status",
|
|
124
|
+
"result",
|
|
125
|
+
]);
|
|
126
|
+
const ADMITTED_STEP_STATUSES = new Set([
|
|
127
|
+
"completed",
|
|
128
|
+
"failed",
|
|
129
|
+
"partial",
|
|
130
|
+
"skipped",
|
|
131
|
+
]);
|
|
132
|
+
const MAX_ADMITTED_RUN_STEPS = 64;
|
|
133
|
+
const MAX_ADMITTED_SNAPSHOT_NODES = 25_000;
|
|
134
|
+
const MAX_ADMITTED_SNAPSHOT_BYTES = 1024 * 1024;
|
|
135
|
+
const STEP_TIMEOUT_CODE = "COWORK_WORKFLOW_STEP_TIMEOUT";
|
|
136
|
+
|
|
137
|
+
function runAdmissionError(message) {
|
|
138
|
+
const error = new Error(message);
|
|
139
|
+
error.code = COWORK_WORKFLOW_RUN_ADMISSION_INVALID_CODE;
|
|
140
|
+
return error;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function runResultError(message) {
|
|
144
|
+
const error = new Error(message);
|
|
145
|
+
error.code = COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE;
|
|
146
|
+
return error;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Snapshot an admitted runner value without invoking accessors or accepting
|
|
151
|
+
* proxies/non-JSON values. The returned graph is detached and deeply frozen,
|
|
152
|
+
* so callbacks and a hostile runner cannot mutate executor control state.
|
|
153
|
+
*/
|
|
154
|
+
function snapshotAdmittedCanonicalValue(
|
|
155
|
+
value,
|
|
156
|
+
name,
|
|
157
|
+
{
|
|
158
|
+
maxNodes = MAX_ADMITTED_SNAPSHOT_NODES,
|
|
159
|
+
maxBytes = MAX_ADMITTED_SNAPSHOT_BYTES,
|
|
160
|
+
} = {},
|
|
161
|
+
) {
|
|
162
|
+
const seen = new WeakSet();
|
|
163
|
+
let nodes = 0;
|
|
164
|
+
let roughCharacters = 0;
|
|
165
|
+
const invalid = (reason) => {
|
|
166
|
+
throw runResultError(`${name} ${reason}`);
|
|
167
|
+
};
|
|
168
|
+
const account = (characters = 1) => {
|
|
169
|
+
nodes += 1;
|
|
170
|
+
roughCharacters += characters;
|
|
171
|
+
if (nodes > maxNodes || roughCharacters > maxBytes) {
|
|
172
|
+
invalid("exceeds snapshot limits");
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const visit = (current, depth) => {
|
|
176
|
+
if (depth > 64) invalid("exceeds snapshot depth");
|
|
177
|
+
if (current === null || typeof current === "boolean") {
|
|
178
|
+
account();
|
|
179
|
+
return current;
|
|
180
|
+
}
|
|
181
|
+
if (typeof current === "string") {
|
|
182
|
+
account(current.length);
|
|
183
|
+
return current;
|
|
184
|
+
}
|
|
185
|
+
if (typeof current === "number") {
|
|
186
|
+
if (!Number.isFinite(current)) invalid("contains a non-finite number");
|
|
187
|
+
account();
|
|
188
|
+
return current;
|
|
189
|
+
}
|
|
190
|
+
if (!current || typeof current !== "object" || utilTypes.isProxy(current)) {
|
|
191
|
+
invalid("must contain only non-proxy canonical JSON values");
|
|
192
|
+
}
|
|
193
|
+
if (seen.has(current)) invalid("contains a cycle or repeated reference");
|
|
194
|
+
seen.add(current);
|
|
195
|
+
account();
|
|
196
|
+
|
|
197
|
+
if (Array.isArray(current)) {
|
|
198
|
+
if (Object.getPrototypeOf(current) !== Array.prototype) {
|
|
199
|
+
invalid("contains a non-plain array");
|
|
200
|
+
}
|
|
201
|
+
if (current.length > maxNodes) invalid("contains an oversized array");
|
|
202
|
+
const keys = Reflect.ownKeys(current);
|
|
203
|
+
if (keys.length !== current.length + 1) {
|
|
204
|
+
invalid("contains a sparse or extended array");
|
|
205
|
+
}
|
|
206
|
+
const output = [];
|
|
207
|
+
for (let index = 0; index < current.length; index += 1) {
|
|
208
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
209
|
+
current,
|
|
210
|
+
String(index),
|
|
211
|
+
);
|
|
212
|
+
if (
|
|
213
|
+
!descriptor ||
|
|
214
|
+
descriptor.enumerable !== true ||
|
|
215
|
+
!Object.hasOwn(descriptor, "value")
|
|
216
|
+
) {
|
|
217
|
+
invalid("contains an accessor or sparse array entry");
|
|
218
|
+
}
|
|
219
|
+
output.push(visit(descriptor.value, depth + 1));
|
|
220
|
+
}
|
|
221
|
+
return Object.freeze(output);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const prototype = Object.getPrototypeOf(current);
|
|
225
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
226
|
+
invalid("contains a non-plain object");
|
|
227
|
+
}
|
|
228
|
+
const output = {};
|
|
229
|
+
for (const key of Reflect.ownKeys(current)) {
|
|
230
|
+
if (typeof key !== "string") invalid("contains a symbol key");
|
|
231
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
232
|
+
if (
|
|
233
|
+
!descriptor ||
|
|
234
|
+
descriptor.enumerable !== true ||
|
|
235
|
+
!Object.hasOwn(descriptor, "value")
|
|
236
|
+
) {
|
|
237
|
+
invalid("contains an accessor or non-enumerable property");
|
|
238
|
+
}
|
|
239
|
+
roughCharacters += key.length;
|
|
240
|
+
if (roughCharacters > maxBytes) invalid("exceeds snapshot limits");
|
|
241
|
+
output[key] = visit(descriptor.value, depth + 1);
|
|
242
|
+
}
|
|
243
|
+
return Object.freeze(output);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const snapshot = visit(value, 0);
|
|
247
|
+
let serialized;
|
|
248
|
+
try {
|
|
249
|
+
serialized = JSON.stringify(snapshot);
|
|
250
|
+
} catch {
|
|
251
|
+
invalid("cannot be serialized");
|
|
252
|
+
}
|
|
253
|
+
if (Buffer.byteLength(serialized, "utf8") > maxBytes) {
|
|
254
|
+
invalid("exceeds snapshot limits");
|
|
255
|
+
}
|
|
256
|
+
return snapshot;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function safeRunErrorMessage(error) {
|
|
260
|
+
if (!error || typeof error !== "object" || utilTypes.isProxy(error)) {
|
|
261
|
+
return "task failed";
|
|
262
|
+
}
|
|
263
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "message");
|
|
264
|
+
if (
|
|
265
|
+
descriptor &&
|
|
266
|
+
Object.hasOwn(descriptor, "value") &&
|
|
267
|
+
typeof descriptor.value === "string"
|
|
268
|
+
) {
|
|
269
|
+
return descriptor.value.slice(0, 4096);
|
|
270
|
+
}
|
|
271
|
+
return "task failed";
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function normalizeAdmittedTaskEntry(value) {
|
|
275
|
+
const entry = snapshotAdmittedCanonicalValue(value, "task result");
|
|
276
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
277
|
+
throw runResultError("task result must be a plain object");
|
|
278
|
+
}
|
|
279
|
+
if (
|
|
280
|
+
[...ADMITTED_TASK_ENTRY_REQUIRED_FIELDS].some(
|
|
281
|
+
(field) => !Object.hasOwn(entry, field),
|
|
282
|
+
) ||
|
|
283
|
+
(entry.status !== "completed" && entry.status !== "failed") ||
|
|
284
|
+
typeof entry.taskId !== "string" ||
|
|
285
|
+
entry.taskId.length === 0 ||
|
|
286
|
+
entry.taskId.length > 512 ||
|
|
287
|
+
!entry.result ||
|
|
288
|
+
typeof entry.result !== "object" ||
|
|
289
|
+
Array.isArray(entry.result)
|
|
290
|
+
) {
|
|
291
|
+
throw runResultError("task result has an invalid schema");
|
|
292
|
+
}
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
taskId: entry.taskId,
|
|
295
|
+
status: entry.status,
|
|
296
|
+
result: entry.result,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function normalizeAdmittedOutcome(value, name = "workflow step outcome") {
|
|
301
|
+
const outcome = snapshotAdmittedCanonicalValue(value, name);
|
|
302
|
+
if (
|
|
303
|
+
!outcome ||
|
|
304
|
+
typeof outcome !== "object" ||
|
|
305
|
+
Array.isArray(outcome) ||
|
|
306
|
+
Reflect.ownKeys(outcome).length !== 4 ||
|
|
307
|
+
!["id", "status", "taskId", "result"].every((field) =>
|
|
308
|
+
Object.hasOwn(outcome, field),
|
|
309
|
+
) ||
|
|
310
|
+
typeof outcome.id !== "string" ||
|
|
311
|
+
outcome.id.length === 0 ||
|
|
312
|
+
outcome.id.length > 512 ||
|
|
313
|
+
!ADMITTED_STEP_STATUSES.has(outcome.status) ||
|
|
314
|
+
(outcome.taskId !== null &&
|
|
315
|
+
(typeof outcome.taskId !== "string" ||
|
|
316
|
+
outcome.taskId.length === 0 ||
|
|
317
|
+
outcome.taskId.length > 512)) ||
|
|
318
|
+
!outcome.result ||
|
|
319
|
+
typeof outcome.result !== "object" ||
|
|
320
|
+
Array.isArray(outcome.result)
|
|
321
|
+
) {
|
|
322
|
+
throw runResultError(`${name} has an invalid schema`);
|
|
323
|
+
}
|
|
324
|
+
return outcome;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function notifyWorkflowCallback(callback, value, admitted, name) {
|
|
328
|
+
if (typeof callback !== "function") return;
|
|
329
|
+
const view = admitted
|
|
330
|
+
? snapshotAdmittedCanonicalValue(value, `${name} callback view`)
|
|
331
|
+
: value;
|
|
332
|
+
try {
|
|
333
|
+
callback(view);
|
|
334
|
+
} catch {
|
|
335
|
+
// Observation callbacks must not mutate or steer workflow control flow.
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function assertAdmissionObject(value, fields, name) {
|
|
340
|
+
if (
|
|
341
|
+
!value ||
|
|
342
|
+
typeof value !== "object" ||
|
|
343
|
+
Array.isArray(value) ||
|
|
344
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value)) ||
|
|
345
|
+
Object.keys(value).length !== fields.size ||
|
|
346
|
+
Object.keys(value).some((key) => !fields.has(key))
|
|
347
|
+
) {
|
|
348
|
+
throw runAdmissionError(`${name} is invalid`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function normalizeAdmittedLlmOptions(value) {
|
|
353
|
+
if (
|
|
354
|
+
!value ||
|
|
355
|
+
typeof value !== "object" ||
|
|
356
|
+
Array.isArray(value) ||
|
|
357
|
+
utilTypes.isProxy(value) ||
|
|
358
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
359
|
+
) {
|
|
360
|
+
throw runAdmissionError("admitted workflow llmOptions is invalid");
|
|
361
|
+
}
|
|
362
|
+
const keys = Reflect.ownKeys(value);
|
|
363
|
+
if (
|
|
364
|
+
keys.length !== RUN_ADMISSION_LLM_OPTION_FIELDS.size ||
|
|
365
|
+
keys.some(
|
|
366
|
+
(key) =>
|
|
367
|
+
typeof key !== "string" || !RUN_ADMISSION_LLM_OPTION_FIELDS.has(key),
|
|
368
|
+
)
|
|
369
|
+
) {
|
|
370
|
+
throw runAdmissionError("admitted workflow llmOptions is not exact");
|
|
371
|
+
}
|
|
372
|
+
const snapshot = {};
|
|
373
|
+
for (const key of RUN_ADMISSION_LLM_OPTION_FIELDS) {
|
|
374
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
375
|
+
if (
|
|
376
|
+
!descriptor ||
|
|
377
|
+
!Object.hasOwn(descriptor, "value") ||
|
|
378
|
+
descriptor.enumerable !== true
|
|
379
|
+
) {
|
|
380
|
+
throw runAdmissionError(
|
|
381
|
+
"admitted workflow llmOptions must use enumerable data properties",
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
snapshot[key] = descriptor.value;
|
|
385
|
+
}
|
|
386
|
+
return Object.freeze(snapshot);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function normalizeAdmissionDigest(value, name) {
|
|
390
|
+
const digest = normalizeWorkflowDefinitionDigest(value);
|
|
391
|
+
if (!digest || digest !== value) {
|
|
392
|
+
throw runAdmissionError(`${name} is invalid`);
|
|
393
|
+
}
|
|
394
|
+
return digest;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function admissionDigest(material) {
|
|
398
|
+
return `sha256:${createHash("sha256")
|
|
399
|
+
.update("chainlesschain.dynamic-workflow.admission.v1\0", "utf8")
|
|
400
|
+
.update(canonicalJson(material, "dynamicWorkflowAdmission"), "utf8")
|
|
401
|
+
.digest("hex")}`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function normalizeWorkflowRunAdmission(
|
|
405
|
+
value,
|
|
406
|
+
definitionAuthority,
|
|
407
|
+
maxParallel,
|
|
408
|
+
execution,
|
|
409
|
+
) {
|
|
410
|
+
try {
|
|
411
|
+
value = structuredClone(value);
|
|
412
|
+
} catch {
|
|
413
|
+
throw runAdmissionError("run admission could not be safely snapshotted");
|
|
414
|
+
}
|
|
415
|
+
assertAdmissionObject(value, RUN_ADMISSION_FIELDS, "run admission");
|
|
416
|
+
assertAdmissionObject(
|
|
417
|
+
value.definition,
|
|
418
|
+
RUN_ADMISSION_DEFINITION_FIELDS,
|
|
419
|
+
"run admission definition",
|
|
420
|
+
);
|
|
421
|
+
assertAdmissionObject(
|
|
422
|
+
value.definition.authority,
|
|
423
|
+
RUN_ADMISSION_DEFINITION_AUTHORITY_FIELDS,
|
|
424
|
+
"run admission definition authority",
|
|
425
|
+
);
|
|
426
|
+
assertAdmissionObject(
|
|
427
|
+
value.executionLocation,
|
|
428
|
+
RUN_ADMISSION_EXECUTION_LOCATION_FIELDS,
|
|
429
|
+
"run admission execution location",
|
|
430
|
+
);
|
|
431
|
+
assertAdmissionObject(
|
|
432
|
+
value.executionLocation.session,
|
|
433
|
+
RUN_ADMISSION_SESSION_FIELDS,
|
|
434
|
+
"run admission session proof",
|
|
435
|
+
);
|
|
436
|
+
assertAdmissionObject(
|
|
437
|
+
value.executionPolicy,
|
|
438
|
+
RUN_ADMISSION_EXECUTION_POLICY_FIELDS,
|
|
439
|
+
"run admission execution policy",
|
|
440
|
+
);
|
|
441
|
+
assertAdmissionObject(
|
|
442
|
+
execution.llmOptions,
|
|
443
|
+
RUN_ADMISSION_LLM_OPTION_FIELDS,
|
|
444
|
+
"admitted workflow llmOptions",
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
const definitionDigest = normalizeAdmissionDigest(
|
|
448
|
+
value.definitionDigest,
|
|
449
|
+
"run admission definition digest",
|
|
450
|
+
);
|
|
451
|
+
const executionLocationDigest = normalizeAdmissionDigest(
|
|
452
|
+
value.executionLocationDigest,
|
|
453
|
+
"run admission execution-location digest",
|
|
454
|
+
);
|
|
455
|
+
const preflightDigest = normalizeAdmissionDigest(
|
|
456
|
+
value.preflightDigest,
|
|
457
|
+
"run admission preflight digest",
|
|
458
|
+
);
|
|
459
|
+
const declaredAdmissionDigest = normalizeAdmissionDigest(
|
|
460
|
+
value.admissionDigest,
|
|
461
|
+
"run admission digest",
|
|
462
|
+
);
|
|
463
|
+
const authority = value.definition.authority;
|
|
464
|
+
const session = value.executionLocation.session;
|
|
465
|
+
const executionPolicy = value.executionPolicy;
|
|
466
|
+
const hasControlCharacters = (input) =>
|
|
467
|
+
[...input].some((character) => {
|
|
468
|
+
const codePoint = character.codePointAt(0);
|
|
469
|
+
return codePoint <= 0x1f || codePoint === 0x7f;
|
|
470
|
+
});
|
|
471
|
+
const validOptionalString = (input) =>
|
|
472
|
+
input === null ||
|
|
473
|
+
(typeof input === "string" &&
|
|
474
|
+
input.length > 0 &&
|
|
475
|
+
input.trim() === input &&
|
|
476
|
+
!hasControlCharacters(input));
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
value.schema !== DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA ||
|
|
480
|
+
value.definition.schema !== definitionAuthority.schema ||
|
|
481
|
+
value.definition.definitionDigest !== definitionDigest ||
|
|
482
|
+
definitionDigest !== definitionAuthority.definitionDigest ||
|
|
483
|
+
authority.status !== "versioned" ||
|
|
484
|
+
authority.recordSchema !== COWORK_WORKFLOW_RECORD_SCHEMA ||
|
|
485
|
+
authority.definitionSchema !== definitionAuthority.schema ||
|
|
486
|
+
authority.definitionDigest !== definitionDigest ||
|
|
487
|
+
value.executionLocation.authoritySchema !==
|
|
488
|
+
EXECUTION_LOCATION_AUTHORITY_SCHEMA ||
|
|
489
|
+
!["verified-session-start", "verified-session-location-handoff"].includes(
|
|
490
|
+
value.executionLocation.authority,
|
|
491
|
+
) ||
|
|
492
|
+
value.executionLocation.bindingSchema !==
|
|
493
|
+
EXECUTION_LOCATION_BINDING_SCHEMA ||
|
|
494
|
+
typeof value.executionLocation.location !== "string" ||
|
|
495
|
+
value.executionLocation.location.trim() !==
|
|
496
|
+
value.executionLocation.location ||
|
|
497
|
+
value.executionLocation.location.length === 0 ||
|
|
498
|
+
typeof session.sessionId !== "string" ||
|
|
499
|
+
session.sessionId.trim() !== session.sessionId ||
|
|
500
|
+
session.sessionId.length === 0 ||
|
|
501
|
+
!/^[a-f0-9]{64}$/u.test(session.headHash) ||
|
|
502
|
+
!Number.isSafeInteger(session.eventCount) ||
|
|
503
|
+
session.eventCount < 1 ||
|
|
504
|
+
typeof executionPolicy.cwd !== "string" ||
|
|
505
|
+
executionPolicy.cwd.length === 0 ||
|
|
506
|
+
hasControlCharacters(executionPolicy.cwd) ||
|
|
507
|
+
typeof executionPolicy.continueOnError !== "boolean" ||
|
|
508
|
+
typeof executionPolicy.pipeline !== "boolean" ||
|
|
509
|
+
!validOptionalString(executionPolicy.provider) ||
|
|
510
|
+
!validOptionalString(executionPolicy.model) ||
|
|
511
|
+
executionPolicy.cwd !== execution.cwd ||
|
|
512
|
+
executionPolicy.continueOnError !== execution.continueOnError ||
|
|
513
|
+
executionPolicy.pipeline !== execution.pipeline ||
|
|
514
|
+
executionPolicy.provider !== execution.llmOptions.provider ||
|
|
515
|
+
executionPolicy.model !== execution.llmOptions.model ||
|
|
516
|
+
!Number.isSafeInteger(maxParallel) ||
|
|
517
|
+
maxParallel < 1 ||
|
|
518
|
+
maxParallel > 64 ||
|
|
519
|
+
value.maxParallel !== maxParallel ||
|
|
520
|
+
value.credentialValuesTransferred !== false
|
|
521
|
+
) {
|
|
522
|
+
throw runAdmissionError(
|
|
523
|
+
"run admission does not match the workflow definition or execution options",
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const normalized = Object.freeze({
|
|
528
|
+
schema: DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA,
|
|
529
|
+
definition: Object.freeze({
|
|
530
|
+
schema: definitionAuthority.schema,
|
|
531
|
+
definitionDigest,
|
|
532
|
+
authority: Object.freeze({
|
|
533
|
+
status: "versioned",
|
|
534
|
+
recordSchema: COWORK_WORKFLOW_RECORD_SCHEMA,
|
|
535
|
+
definitionSchema: definitionAuthority.schema,
|
|
536
|
+
definitionDigest,
|
|
537
|
+
}),
|
|
538
|
+
}),
|
|
539
|
+
executionLocation: Object.freeze({
|
|
540
|
+
authoritySchema: EXECUTION_LOCATION_AUTHORITY_SCHEMA,
|
|
541
|
+
authority: value.executionLocation.authority,
|
|
542
|
+
session: Object.freeze({
|
|
543
|
+
sessionId: session.sessionId,
|
|
544
|
+
headHash: session.headHash,
|
|
545
|
+
eventCount: session.eventCount,
|
|
546
|
+
}),
|
|
547
|
+
bindingSchema: EXECUTION_LOCATION_BINDING_SCHEMA,
|
|
548
|
+
location: value.executionLocation.location,
|
|
549
|
+
}),
|
|
550
|
+
executionPolicy: Object.freeze({
|
|
551
|
+
cwd: executionPolicy.cwd,
|
|
552
|
+
continueOnError: executionPolicy.continueOnError,
|
|
553
|
+
pipeline: executionPolicy.pipeline,
|
|
554
|
+
provider: executionPolicy.provider,
|
|
555
|
+
model: executionPolicy.model,
|
|
556
|
+
}),
|
|
557
|
+
definitionDigest,
|
|
558
|
+
executionLocationDigest,
|
|
559
|
+
preflightDigest,
|
|
560
|
+
maxParallel,
|
|
561
|
+
credentialValuesTransferred: false,
|
|
562
|
+
});
|
|
563
|
+
if (admissionDigest(normalized) !== declaredAdmissionDigest) {
|
|
564
|
+
throw runAdmissionError("run admission digest does not match its material");
|
|
565
|
+
}
|
|
566
|
+
return Object.freeze({
|
|
567
|
+
...normalized,
|
|
568
|
+
admissionDigest: declaredAdmissionDigest,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
62
571
|
|
|
63
572
|
export const _deps = {
|
|
64
573
|
existsSync,
|
|
@@ -751,7 +1260,9 @@ export async function withTimeout(factory, timeoutMs) {
|
|
|
751
1260
|
let timer = null;
|
|
752
1261
|
const timeout = new Promise((_resolve, reject) => {
|
|
753
1262
|
timer = _deps.setTimeout(() => {
|
|
754
|
-
|
|
1263
|
+
const error = new Error(`step timed out after ${timeoutMs}ms`);
|
|
1264
|
+
error.code = STEP_TIMEOUT_CODE;
|
|
1265
|
+
reject(error);
|
|
755
1266
|
}, timeoutMs);
|
|
756
1267
|
});
|
|
757
1268
|
try {
|
|
@@ -762,38 +1273,129 @@ export async function withTimeout(factory, timeoutMs) {
|
|
|
762
1273
|
}
|
|
763
1274
|
}
|
|
764
1275
|
|
|
1276
|
+
function createWorkflowTaskSemaphore(limit) {
|
|
1277
|
+
let active = 0;
|
|
1278
|
+
const waiters = [];
|
|
1279
|
+
return Object.freeze({
|
|
1280
|
+
acquire() {
|
|
1281
|
+
return new Promise((resolve) => {
|
|
1282
|
+
const grant = () => {
|
|
1283
|
+
active += 1;
|
|
1284
|
+
let released = false;
|
|
1285
|
+
resolve(() => {
|
|
1286
|
+
if (released) return;
|
|
1287
|
+
released = true;
|
|
1288
|
+
active -= 1;
|
|
1289
|
+
const next = waiters.shift();
|
|
1290
|
+
if (next) next();
|
|
1291
|
+
});
|
|
1292
|
+
};
|
|
1293
|
+
if (active < limit) grant();
|
|
1294
|
+
else waiters.push(grant);
|
|
1295
|
+
});
|
|
1296
|
+
},
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
765
1300
|
/**
|
|
766
1301
|
* Run one step's task with optional `timeoutMs` and `retries` (with `fixed` or
|
|
767
1302
|
* `exponential` `retryDelayMs` backoff). An attempt counts as a failure if the
|
|
768
1303
|
* task throws, times out, or returns a non-`completed` status. Returns
|
|
769
1304
|
* `{ ok, entry?, error?, attempts }`. `attempts` is the total number of tries.
|
|
770
1305
|
*/
|
|
771
|
-
export async function runStepWithRetry({
|
|
1306
|
+
export async function runStepWithRetry({
|
|
1307
|
+
step,
|
|
1308
|
+
message,
|
|
1309
|
+
cwd,
|
|
1310
|
+
llmOptions,
|
|
1311
|
+
taskSemaphore,
|
|
1312
|
+
runTask = _deps.runTask,
|
|
1313
|
+
recordId = step.id,
|
|
1314
|
+
iteration = 1,
|
|
1315
|
+
onTaskStart,
|
|
1316
|
+
admitted = false,
|
|
1317
|
+
}) {
|
|
772
1318
|
const maxRetries = Math.max(0, Math.floor(Number(step.retries) || 0));
|
|
773
1319
|
const timeoutMs = Number(step.timeoutMs) || 0;
|
|
774
1320
|
let lastEntry = null;
|
|
775
1321
|
let lastErr = null;
|
|
776
1322
|
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
1323
|
+
let taskPromise = null;
|
|
1324
|
+
let abortController = null;
|
|
1325
|
+
let release = null;
|
|
777
1326
|
try {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1327
|
+
release = taskSemaphore ? await taskSemaphore.acquire() : () => {};
|
|
1328
|
+
if (admitted && timeoutMs > 0) abortController = new AbortController();
|
|
1329
|
+
if (attempt === 1) {
|
|
1330
|
+
notifyWorkflowCallback(
|
|
1331
|
+
onTaskStart,
|
|
1332
|
+
Object.freeze({ stepId: recordId, message }),
|
|
1333
|
+
admitted,
|
|
1334
|
+
"step start",
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
taskPromise = Promise.resolve().then(() =>
|
|
1338
|
+
runTask({
|
|
1339
|
+
templateId: step.templateId || null,
|
|
1340
|
+
userMessage: message,
|
|
1341
|
+
files: step.files || [],
|
|
1342
|
+
cwd,
|
|
1343
|
+
llmOptions,
|
|
1344
|
+
workflowEffect: Object.freeze({
|
|
1345
|
+
stepId: recordId,
|
|
1346
|
+
iteration,
|
|
1347
|
+
attempt,
|
|
1348
|
+
timeoutMs,
|
|
786
1349
|
}),
|
|
787
|
-
|
|
1350
|
+
...(abortController ? { signal: abortController.signal } : {}),
|
|
1351
|
+
}),
|
|
788
1352
|
);
|
|
1353
|
+
// A timeout abandons only the caller's wait. Keep the physical task's
|
|
1354
|
+
// permit until its promise really settles; retries and parallel children
|
|
1355
|
+
// must queue behind that late task instead of exceeding maxParallel.
|
|
1356
|
+
taskPromise.then(release, release);
|
|
1357
|
+
release = null; // the physical task now owns the permit
|
|
1358
|
+
const rawEntry = await withTimeout(() => taskPromise, timeoutMs);
|
|
1359
|
+
const entry = admitted ? normalizeAdmittedTaskEntry(rawEntry) : rawEntry;
|
|
789
1360
|
if (entry && entry.status === "completed") {
|
|
790
1361
|
return { ok: true, entry, attempts: attempt };
|
|
791
1362
|
}
|
|
792
1363
|
lastEntry = entry; // ran but not completed → retry-eligible
|
|
793
1364
|
lastErr = null;
|
|
794
1365
|
} catch (err) {
|
|
795
|
-
|
|
796
|
-
|
|
1366
|
+
if (release) release();
|
|
1367
|
+
if (err?.code === COWORK_WORKFLOW_CONTROL_SIGNAL_CODE) throw err;
|
|
1368
|
+
if (err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE) throw err;
|
|
1369
|
+
if (admitted && err?.code === STEP_TIMEOUT_CODE && taskPromise != null) {
|
|
1370
|
+
abortController.abort(err);
|
|
1371
|
+
// A terminal admitted record must never be written while the physical
|
|
1372
|
+
// task can still produce side effects. Confirm settlement before a
|
|
1373
|
+
// retry or final failure; a runner that ignores abort safely blocks.
|
|
1374
|
+
// A late completed result is authoritative and must not be replayed.
|
|
1375
|
+
try {
|
|
1376
|
+
const lateRawEntry = await taskPromise;
|
|
1377
|
+
const lateEntry = normalizeAdmittedTaskEntry(lateRawEntry);
|
|
1378
|
+
if (lateEntry.status === "completed") {
|
|
1379
|
+
return { ok: true, entry: lateEntry, attempts: attempt };
|
|
1380
|
+
}
|
|
1381
|
+
lastEntry = lateEntry;
|
|
1382
|
+
lastErr = null;
|
|
1383
|
+
} catch (lateError) {
|
|
1384
|
+
if (
|
|
1385
|
+
lateError?.code === COWORK_WORKFLOW_CONTROL_SIGNAL_CODE ||
|
|
1386
|
+
lateError?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE
|
|
1387
|
+
) {
|
|
1388
|
+
throw lateError;
|
|
1389
|
+
}
|
|
1390
|
+
// The timeout remains the attempt's externally visible failure when
|
|
1391
|
+
// a non-authoritative runner error arrives after the deadline.
|
|
1392
|
+
lastErr = err;
|
|
1393
|
+
lastEntry = null;
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
lastErr = err; // threw or timed out → retry-eligible
|
|
1397
|
+
lastEntry = null;
|
|
1398
|
+
}
|
|
797
1399
|
}
|
|
798
1400
|
if (attempt <= maxRetries) {
|
|
799
1401
|
const delay = retryDelayFor(step, attempt);
|
|
@@ -813,24 +1415,27 @@ function _withAttempts(result, attempts) {
|
|
|
813
1415
|
}
|
|
814
1416
|
|
|
815
1417
|
/** Build a single-step outcome object from a `runStepWithRetry` result. */
|
|
816
|
-
function outcomeFromRetry(recordId, r) {
|
|
1418
|
+
function outcomeFromRetry(recordId, r, admitted = false) {
|
|
1419
|
+
let outcome;
|
|
817
1420
|
if (r.ok || r.entry) {
|
|
818
|
-
|
|
1421
|
+
outcome = {
|
|
819
1422
|
id: recordId,
|
|
820
1423
|
status: r.entry.status,
|
|
821
1424
|
taskId: r.entry.taskId,
|
|
822
1425
|
result: _withAttempts(r.entry.result, r.attempts),
|
|
823
1426
|
};
|
|
1427
|
+
} else {
|
|
1428
|
+
outcome = {
|
|
1429
|
+
id: recordId,
|
|
1430
|
+
status: "failed",
|
|
1431
|
+
taskId: null,
|
|
1432
|
+
result: _withAttempts(
|
|
1433
|
+
{ summary: `Step threw: ${safeRunErrorMessage(r.error)}` },
|
|
1434
|
+
r.attempts,
|
|
1435
|
+
),
|
|
1436
|
+
};
|
|
824
1437
|
}
|
|
825
|
-
return
|
|
826
|
-
id: recordId,
|
|
827
|
-
status: "failed",
|
|
828
|
-
taskId: null,
|
|
829
|
-
result: _withAttempts(
|
|
830
|
-
{ summary: `Step threw: ${r.error.message}` },
|
|
831
|
-
r.attempts,
|
|
832
|
-
),
|
|
833
|
-
};
|
|
1438
|
+
return admitted ? normalizeAdmittedOutcome(outcome) : outcome;
|
|
834
1439
|
}
|
|
835
1440
|
|
|
836
1441
|
// ─── while / until loop nodes ──────────────────────────────────────────────────
|
|
@@ -901,11 +1506,16 @@ export async function runLoopStep({
|
|
|
901
1506
|
cwd,
|
|
902
1507
|
llmOptions,
|
|
903
1508
|
resultsById,
|
|
1509
|
+
taskSemaphore,
|
|
1510
|
+
runTask = _deps.runTask,
|
|
1511
|
+
onStepStart,
|
|
1512
|
+
admitted = false,
|
|
904
1513
|
}) {
|
|
905
1514
|
const cap = loopIterationCap(step);
|
|
906
1515
|
let last = null;
|
|
907
1516
|
let iterations = 0;
|
|
908
1517
|
let stopReason = "cap";
|
|
1518
|
+
let startAnnounced = false;
|
|
909
1519
|
for (let iter = 1; iter <= cap; iter++) {
|
|
910
1520
|
iterations = iter;
|
|
911
1521
|
const withSelf = substituteLoopVars(step.message, {
|
|
@@ -914,8 +1524,23 @@ export async function runLoopStep({
|
|
|
914
1524
|
resultsById,
|
|
915
1525
|
});
|
|
916
1526
|
const message = substitutePlaceholders(withSelf, resultsById);
|
|
917
|
-
const r = await runStepWithRetry({
|
|
918
|
-
|
|
1527
|
+
const r = await runStepWithRetry({
|
|
1528
|
+
step,
|
|
1529
|
+
message,
|
|
1530
|
+
cwd,
|
|
1531
|
+
llmOptions,
|
|
1532
|
+
taskSemaphore,
|
|
1533
|
+
runTask,
|
|
1534
|
+
recordId,
|
|
1535
|
+
iteration: iter,
|
|
1536
|
+
onTaskStart: (event) => {
|
|
1537
|
+
if (startAnnounced) return;
|
|
1538
|
+
startAnnounced = true;
|
|
1539
|
+
notifyWorkflowCallback(onStepStart, event, admitted, "step start");
|
|
1540
|
+
},
|
|
1541
|
+
admitted,
|
|
1542
|
+
});
|
|
1543
|
+
last = outcomeFromRetry(recordId, r, admitted);
|
|
919
1544
|
resultsById.set(recordId, last);
|
|
920
1545
|
if (last.status === "failed") {
|
|
921
1546
|
stopReason = "failed";
|
|
@@ -942,7 +1567,7 @@ export async function runLoopStep({
|
|
|
942
1567
|
break;
|
|
943
1568
|
}
|
|
944
1569
|
}
|
|
945
|
-
|
|
1570
|
+
const outcome = {
|
|
946
1571
|
id: recordId,
|
|
947
1572
|
status: last ? last.status : "skipped",
|
|
948
1573
|
taskId: last?.taskId ?? null,
|
|
@@ -953,6 +1578,7 @@ export async function runLoopStep({
|
|
|
953
1578
|
loopStop: stopReason,
|
|
954
1579
|
},
|
|
955
1580
|
};
|
|
1581
|
+
return admitted ? normalizeAdmittedOutcome(outcome) : outcome;
|
|
956
1582
|
}
|
|
957
1583
|
|
|
958
1584
|
// ─── Step node + no-barrier pipeline ──────────────────────────────────────────
|
|
@@ -966,7 +1592,16 @@ export async function runLoopStep({
|
|
|
966
1592
|
* is not "completed". Used by the no-barrier pipeline scheduler.
|
|
967
1593
|
*/
|
|
968
1594
|
export async function runStepNode(step, ctx) {
|
|
969
|
-
const {
|
|
1595
|
+
const {
|
|
1596
|
+
resultsById,
|
|
1597
|
+
cwd,
|
|
1598
|
+
llmOptions,
|
|
1599
|
+
onStepStart,
|
|
1600
|
+
onStepComplete,
|
|
1601
|
+
taskSemaphore,
|
|
1602
|
+
runTask,
|
|
1603
|
+
admitted = false,
|
|
1604
|
+
} = ctx;
|
|
970
1605
|
const recordId = step.id;
|
|
971
1606
|
const single = (status, summary) => {
|
|
972
1607
|
const o = { id: recordId, status, taskId: null, result: { summary } };
|
|
@@ -986,16 +1621,19 @@ export async function runStepNode(step, ctx) {
|
|
|
986
1621
|
if (!runThis) return single("skipped", "when-condition false");
|
|
987
1622
|
|
|
988
1623
|
if (isLoopStep(step)) {
|
|
989
|
-
if (onStepStart) onStepStart({ stepId: recordId, message: step.message });
|
|
990
1624
|
const o = await runLoopStep({
|
|
991
1625
|
step,
|
|
992
1626
|
recordId,
|
|
993
1627
|
cwd,
|
|
994
1628
|
llmOptions,
|
|
995
1629
|
resultsById,
|
|
1630
|
+
taskSemaphore,
|
|
1631
|
+
runTask,
|
|
1632
|
+
onStepStart,
|
|
1633
|
+
admitted,
|
|
996
1634
|
});
|
|
997
1635
|
resultsById.set(recordId, o);
|
|
998
|
-
|
|
1636
|
+
notifyWorkflowCallback(onStepComplete, o, admitted, "step complete");
|
|
999
1637
|
return {
|
|
1000
1638
|
outcomes: [o],
|
|
1001
1639
|
failed: o.status !== "completed" && o.status !== "skipped",
|
|
@@ -1015,16 +1653,20 @@ export async function runStepNode(step, ctx) {
|
|
|
1015
1653
|
const childId = `${recordId}[${k}]`;
|
|
1016
1654
|
const withItem = substituteItem(step.message, item);
|
|
1017
1655
|
const msg = substitutePlaceholders(withItem, resultsById);
|
|
1018
|
-
if (onStepStart) onStepStart({ stepId: childId, message: msg });
|
|
1019
1656
|
const r = await runStepWithRetry({
|
|
1020
1657
|
step,
|
|
1021
1658
|
message: msg,
|
|
1022
1659
|
cwd,
|
|
1023
1660
|
llmOptions,
|
|
1661
|
+
taskSemaphore,
|
|
1662
|
+
runTask,
|
|
1663
|
+
recordId: childId,
|
|
1664
|
+
onTaskStart: onStepStart,
|
|
1665
|
+
admitted,
|
|
1024
1666
|
});
|
|
1025
|
-
const co = outcomeFromRetry(childId, r);
|
|
1667
|
+
const co = outcomeFromRetry(childId, r, admitted);
|
|
1026
1668
|
resultsById.set(childId, co);
|
|
1027
|
-
|
|
1669
|
+
notifyWorkflowCallback(onStepComplete, co, admitted, "step complete");
|
|
1028
1670
|
return co;
|
|
1029
1671
|
}),
|
|
1030
1672
|
);
|
|
@@ -1046,11 +1688,20 @@ export async function runStepNode(step, ctx) {
|
|
|
1046
1688
|
}
|
|
1047
1689
|
|
|
1048
1690
|
const message = substitutePlaceholders(step.message, resultsById);
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1691
|
+
const r = await runStepWithRetry({
|
|
1692
|
+
step,
|
|
1693
|
+
message,
|
|
1694
|
+
cwd,
|
|
1695
|
+
llmOptions,
|
|
1696
|
+
taskSemaphore,
|
|
1697
|
+
runTask,
|
|
1698
|
+
recordId,
|
|
1699
|
+
onTaskStart: onStepStart,
|
|
1700
|
+
admitted,
|
|
1701
|
+
});
|
|
1702
|
+
const o = outcomeFromRetry(recordId, r, admitted);
|
|
1052
1703
|
resultsById.set(recordId, o);
|
|
1053
|
-
|
|
1704
|
+
notifyWorkflowCallback(onStepComplete, o, admitted, "step complete");
|
|
1054
1705
|
return { outcomes: [o], failed: o.status !== "completed" };
|
|
1055
1706
|
}
|
|
1056
1707
|
|
|
@@ -1071,6 +1722,9 @@ export async function runPipeline({
|
|
|
1071
1722
|
llmOptions = {},
|
|
1072
1723
|
onStepStart,
|
|
1073
1724
|
onStepComplete,
|
|
1725
|
+
taskSemaphore,
|
|
1726
|
+
runTask = _deps.runTask,
|
|
1727
|
+
admitted = false,
|
|
1074
1728
|
}) {
|
|
1075
1729
|
const limit = Math.max(1, Math.floor(maxParallel) || 1);
|
|
1076
1730
|
const remainingDeps = new Map(
|
|
@@ -1087,8 +1741,9 @@ export async function runPipeline({
|
|
|
1087
1741
|
let anyFailure = false;
|
|
1088
1742
|
let halted = false;
|
|
1089
1743
|
let active = 0;
|
|
1744
|
+
let fatalError = null;
|
|
1090
1745
|
|
|
1091
|
-
await new Promise((resolve) => {
|
|
1746
|
+
await new Promise((resolve, reject) => {
|
|
1092
1747
|
function pump() {
|
|
1093
1748
|
if (!halted) {
|
|
1094
1749
|
for (const s of steps) {
|
|
@@ -1101,6 +1756,10 @@ export async function runPipeline({
|
|
|
1101
1756
|
}
|
|
1102
1757
|
}
|
|
1103
1758
|
if (active === 0 && (halted || scheduled.size === steps.length)) {
|
|
1759
|
+
if (fatalError) {
|
|
1760
|
+
reject(fatalError);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1104
1763
|
for (const s of steps) {
|
|
1105
1764
|
if (scheduled.has(s.id)) continue;
|
|
1106
1765
|
const o = {
|
|
@@ -1126,13 +1785,26 @@ export async function runPipeline({
|
|
|
1126
1785
|
llmOptions,
|
|
1127
1786
|
onStepStart,
|
|
1128
1787
|
onStepComplete,
|
|
1788
|
+
taskSemaphore,
|
|
1789
|
+
runTask,
|
|
1790
|
+
admitted,
|
|
1129
1791
|
});
|
|
1130
1792
|
} catch (err) {
|
|
1793
|
+
if (
|
|
1794
|
+
err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE ||
|
|
1795
|
+
err?.code === COWORK_WORKFLOW_CONTROL_SIGNAL_CODE
|
|
1796
|
+
) {
|
|
1797
|
+
fatalError ||= err;
|
|
1798
|
+
halted = true;
|
|
1799
|
+
active--;
|
|
1800
|
+
pump();
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1131
1803
|
const o = {
|
|
1132
1804
|
id: step.id,
|
|
1133
1805
|
status: "failed",
|
|
1134
1806
|
taskId: null,
|
|
1135
|
-
result: { summary: `Step threw: ${err
|
|
1807
|
+
result: { summary: `Step threw: ${safeRunErrorMessage(err)}` },
|
|
1136
1808
|
};
|
|
1137
1809
|
resultsById.set(step.id, o);
|
|
1138
1810
|
res = { outcomes: [o], failed: true };
|
|
@@ -1157,6 +1829,98 @@ export async function runPipeline({
|
|
|
1157
1829
|
|
|
1158
1830
|
// ─── Execution ───────────────────────────────────────────────────────────────
|
|
1159
1831
|
|
|
1832
|
+
function normalizeAdmittedRunRecord(
|
|
1833
|
+
value,
|
|
1834
|
+
runtimeWorkflow,
|
|
1835
|
+
definitionAuthority,
|
|
1836
|
+
runAdmission,
|
|
1837
|
+
) {
|
|
1838
|
+
const record = snapshotAdmittedCanonicalValue(value, "workflow run record");
|
|
1839
|
+
const expectedFields = new Set([
|
|
1840
|
+
"schema",
|
|
1841
|
+
"workflowId",
|
|
1842
|
+
"workflowName",
|
|
1843
|
+
"definitionSchema",
|
|
1844
|
+
"definitionDigest",
|
|
1845
|
+
"runAdmission",
|
|
1846
|
+
"status",
|
|
1847
|
+
"steps",
|
|
1848
|
+
"startedAt",
|
|
1849
|
+
"finishedAt",
|
|
1850
|
+
]);
|
|
1851
|
+
if (
|
|
1852
|
+
!record ||
|
|
1853
|
+
typeof record !== "object" ||
|
|
1854
|
+
Array.isArray(record) ||
|
|
1855
|
+
Reflect.ownKeys(record).length !== expectedFields.size ||
|
|
1856
|
+
Reflect.ownKeys(record).some(
|
|
1857
|
+
(field) => typeof field !== "string" || !expectedFields.has(field),
|
|
1858
|
+
) ||
|
|
1859
|
+
record.schema !== COWORK_WORKFLOW_RUN_RECORD_SCHEMA ||
|
|
1860
|
+
record.workflowId !== runtimeWorkflow.id ||
|
|
1861
|
+
record.workflowName !== runtimeWorkflow.name ||
|
|
1862
|
+
record.definitionSchema !== definitionAuthority.schema ||
|
|
1863
|
+
record.definitionDigest !== definitionAuthority.definitionDigest ||
|
|
1864
|
+
!["completed", "failed", "partial"].includes(record.status) ||
|
|
1865
|
+
!Array.isArray(record.steps) ||
|
|
1866
|
+
record.steps.length === 0 ||
|
|
1867
|
+
record.steps.length > MAX_ADMITTED_RUN_STEPS ||
|
|
1868
|
+
typeof record.startedAt !== "string" ||
|
|
1869
|
+
typeof record.finishedAt !== "string" ||
|
|
1870
|
+
!Number.isFinite(Date.parse(record.startedAt)) ||
|
|
1871
|
+
!Number.isFinite(Date.parse(record.finishedAt)) ||
|
|
1872
|
+
new Date(record.startedAt).toISOString() !== record.startedAt ||
|
|
1873
|
+
new Date(record.finishedAt).toISOString() !== record.finishedAt ||
|
|
1874
|
+
Date.parse(record.finishedAt) < Date.parse(record.startedAt) ||
|
|
1875
|
+
canonicalJson(record.runAdmission, "recordRunAdmission") !==
|
|
1876
|
+
canonicalJson(runAdmission, "admittedRunAdmission")
|
|
1877
|
+
) {
|
|
1878
|
+
throw runResultError("workflow run record has an invalid schema");
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
const outcomeIds = new Set();
|
|
1882
|
+
for (const [index, outcome] of record.steps.entries()) {
|
|
1883
|
+
normalizeAdmittedOutcome(outcome, `workflow run record step ${index}`);
|
|
1884
|
+
if (outcomeIds.has(outcome.id)) {
|
|
1885
|
+
throw runResultError("workflow run record contains duplicate step ids");
|
|
1886
|
+
}
|
|
1887
|
+
outcomeIds.add(outcome.id);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
for (const step of runtimeWorkflow.steps) {
|
|
1891
|
+
if (step.forEach === undefined) {
|
|
1892
|
+
if (!outcomeIds.has(step.id)) {
|
|
1893
|
+
throw runResultError("workflow run record is missing a step outcome");
|
|
1894
|
+
}
|
|
1895
|
+
continue;
|
|
1896
|
+
}
|
|
1897
|
+
const childPrefix = `${step.id}[`;
|
|
1898
|
+
if (
|
|
1899
|
+
!outcomeIds.has(step.id) &&
|
|
1900
|
+
![...outcomeIds].some(
|
|
1901
|
+
(outcomeId) =>
|
|
1902
|
+
outcomeId.startsWith(childPrefix) && outcomeId.endsWith("]"),
|
|
1903
|
+
)
|
|
1904
|
+
) {
|
|
1905
|
+
throw runResultError("workflow run record is missing a forEach outcome");
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
const expectedStatus = record.steps.every(
|
|
1910
|
+
(outcome) => outcome.status === "completed",
|
|
1911
|
+
)
|
|
1912
|
+
? "completed"
|
|
1913
|
+
: record.steps.some((outcome) => outcome.status === "completed")
|
|
1914
|
+
? "partial"
|
|
1915
|
+
: "failed";
|
|
1916
|
+
if (record.status !== expectedStatus) {
|
|
1917
|
+
throw runResultError(
|
|
1918
|
+
"workflow run record status does not match its step outcomes",
|
|
1919
|
+
);
|
|
1920
|
+
}
|
|
1921
|
+
return record;
|
|
1922
|
+
}
|
|
1923
|
+
|
|
1160
1924
|
/**
|
|
1161
1925
|
* Execute a workflow. The runner for individual tasks must be injected via
|
|
1162
1926
|
* `_deps.runTask` (signature matches `runCoworkTask`).
|
|
@@ -1186,6 +1950,9 @@ export async function runPipeline({
|
|
|
1186
1950
|
* @param {object} [options.llmOptions] - Forwarded to each task
|
|
1187
1951
|
* @param {function} [options.onStepStart]
|
|
1188
1952
|
* @param {function} [options.onStepComplete]
|
|
1953
|
+
* @param {object} [options.runAdmission] - Secret-free, digest-bound dynamic
|
|
1954
|
+
* workflow admission. Production entry points require this even though the
|
|
1955
|
+
* low-level executor keeps it optional for existing library callers.
|
|
1189
1956
|
* @returns {Promise<{
|
|
1190
1957
|
* workflowId: string,
|
|
1191
1958
|
* status: "completed"|"failed"|"partial",
|
|
@@ -1205,11 +1972,22 @@ export async function executeWorkflow(options = {}) {
|
|
|
1205
1972
|
onStepStart,
|
|
1206
1973
|
onStepComplete,
|
|
1207
1974
|
pipeline,
|
|
1975
|
+
runAdmission,
|
|
1976
|
+
runTask: requestedRunTask,
|
|
1208
1977
|
} = options;
|
|
1209
1978
|
|
|
1210
|
-
const { valid, errors } = validateWorkflow(workflow);
|
|
1211
|
-
if (!valid) throw new Error(`Invalid workflow: ${errors.join("; ")}`);
|
|
1212
1979
|
const definitionAuthority = createWorkflowDefinitionAuthority(workflow);
|
|
1980
|
+
const runtimeWorkflow = definitionAuthority.definition;
|
|
1981
|
+
const { valid, errors } = validateWorkflow(runtimeWorkflow);
|
|
1982
|
+
if (!valid) throw new Error(`Invalid workflow: ${errors.join("; ")}`);
|
|
1983
|
+
const numericMaxParallel = Number(maxParallel);
|
|
1984
|
+
const concurrencyLimit =
|
|
1985
|
+
Number.isSafeInteger(numericMaxParallel) && numericMaxParallel > 0
|
|
1986
|
+
? numericMaxParallel
|
|
1987
|
+
: 1;
|
|
1988
|
+
const usePipeline = pipeline ?? runtimeWorkflow.pipeline ?? false;
|
|
1989
|
+
const runtimeLlmOptions =
|
|
1990
|
+
runAdmission == null ? llmOptions : normalizeAdmittedLlmOptions(llmOptions);
|
|
1213
1991
|
if (requestedDefinitionDigest != null) {
|
|
1214
1992
|
const normalizedDigest = normalizeWorkflowDefinitionDigest(
|
|
1215
1993
|
requestedDefinitionDigest,
|
|
@@ -1221,13 +1999,28 @@ export async function executeWorkflow(options = {}) {
|
|
|
1221
1999
|
throw new Error("workflow definition digest mismatch");
|
|
1222
2000
|
}
|
|
1223
2001
|
}
|
|
1224
|
-
|
|
2002
|
+
const verifiedRunAdmission =
|
|
2003
|
+
runAdmission == null
|
|
2004
|
+
? null
|
|
2005
|
+
: normalizeWorkflowRunAdmission(
|
|
2006
|
+
runAdmission,
|
|
2007
|
+
definitionAuthority,
|
|
2008
|
+
concurrencyLimit,
|
|
2009
|
+
{
|
|
2010
|
+
cwd,
|
|
2011
|
+
continueOnError,
|
|
2012
|
+
pipeline: usePipeline,
|
|
2013
|
+
llmOptions: runtimeLlmOptions,
|
|
2014
|
+
},
|
|
2015
|
+
);
|
|
2016
|
+
const runtimeRunTask = requestedRunTask ?? _deps.runTask;
|
|
2017
|
+
if (typeof runtimeRunTask !== "function") {
|
|
1225
2018
|
throw new Error(
|
|
1226
|
-
"cowork-workflow:
|
|
2019
|
+
"cowork-workflow: runTask is not available (wire runCoworkTask before executing)",
|
|
1227
2020
|
);
|
|
1228
2021
|
}
|
|
1229
2022
|
|
|
1230
|
-
const
|
|
2023
|
+
const taskSemaphore = createWorkflowTaskSemaphore(concurrencyLimit);
|
|
1231
2024
|
const resultsById = new Map();
|
|
1232
2025
|
const startedAt = new Date(_deps.now()).toISOString();
|
|
1233
2026
|
let stepOutcomes;
|
|
@@ -1235,24 +2028,27 @@ export async function executeWorkflow(options = {}) {
|
|
|
1235
2028
|
|
|
1236
2029
|
if (usePipeline) {
|
|
1237
2030
|
({ stepOutcomes, anyFailure } = await runPipeline({
|
|
1238
|
-
steps:
|
|
2031
|
+
steps: runtimeWorkflow.steps,
|
|
1239
2032
|
resultsById,
|
|
1240
|
-
maxParallel,
|
|
2033
|
+
maxParallel: concurrencyLimit,
|
|
1241
2034
|
continueOnError,
|
|
1242
2035
|
cwd,
|
|
1243
|
-
llmOptions,
|
|
2036
|
+
llmOptions: runtimeLlmOptions,
|
|
1244
2037
|
onStepStart,
|
|
1245
2038
|
onStepComplete,
|
|
2039
|
+
taskSemaphore,
|
|
2040
|
+
runTask: runtimeRunTask,
|
|
2041
|
+
admitted: verifiedRunAdmission != null,
|
|
1246
2042
|
}));
|
|
1247
2043
|
} else {
|
|
1248
2044
|
stepOutcomes = [];
|
|
1249
2045
|
anyFailure = false;
|
|
1250
|
-
const batches = planBatches(
|
|
2046
|
+
const batches = planBatches(runtimeWorkflow.steps);
|
|
1251
2047
|
for (const batch of batches) {
|
|
1252
2048
|
// Respect maxParallel by slicing batch into chunks
|
|
1253
2049
|
const chunks = [];
|
|
1254
|
-
for (let i = 0; i < batch.length; i +=
|
|
1255
|
-
chunks.push(batch.slice(i, i +
|
|
2050
|
+
for (let i = 0; i < batch.length; i += concurrencyLimit) {
|
|
2051
|
+
chunks.push(batch.slice(i, i + concurrencyLimit));
|
|
1256
2052
|
}
|
|
1257
2053
|
|
|
1258
2054
|
for (const chunk of chunks) {
|
|
@@ -1357,33 +2153,55 @@ export async function executeWorkflow(options = {}) {
|
|
|
1357
2153
|
|
|
1358
2154
|
const promises = runnable.map(
|
|
1359
2155
|
async ({ step, message, recordId, isLoop }) => {
|
|
1360
|
-
if (onStepStart) onStepStart({ stepId: recordId, message });
|
|
1361
2156
|
let outcome;
|
|
1362
2157
|
if (isLoop) {
|
|
1363
2158
|
outcome = await runLoopStep({
|
|
1364
2159
|
step,
|
|
1365
2160
|
recordId,
|
|
1366
2161
|
cwd,
|
|
1367
|
-
llmOptions,
|
|
2162
|
+
llmOptions: runtimeLlmOptions,
|
|
1368
2163
|
resultsById,
|
|
2164
|
+
taskSemaphore,
|
|
2165
|
+
runTask: runtimeRunTask,
|
|
2166
|
+
onStepStart,
|
|
2167
|
+
admitted: verifiedRunAdmission != null,
|
|
1369
2168
|
});
|
|
1370
2169
|
} else {
|
|
1371
2170
|
const r = await runStepWithRetry({
|
|
1372
2171
|
step,
|
|
1373
2172
|
message,
|
|
1374
2173
|
cwd,
|
|
1375
|
-
llmOptions,
|
|
2174
|
+
llmOptions: runtimeLlmOptions,
|
|
2175
|
+
taskSemaphore,
|
|
2176
|
+
runTask: runtimeRunTask,
|
|
2177
|
+
recordId,
|
|
2178
|
+
onTaskStart: onStepStart,
|
|
2179
|
+
admitted: verifiedRunAdmission != null,
|
|
1376
2180
|
});
|
|
1377
|
-
outcome = outcomeFromRetry(
|
|
2181
|
+
outcome = outcomeFromRetry(
|
|
2182
|
+
recordId,
|
|
2183
|
+
r,
|
|
2184
|
+
verifiedRunAdmission != null,
|
|
2185
|
+
);
|
|
1378
2186
|
}
|
|
1379
2187
|
if (outcome.status !== "completed") anyFailure = true;
|
|
1380
2188
|
resultsById.set(recordId, outcome);
|
|
1381
|
-
|
|
2189
|
+
notifyWorkflowCallback(
|
|
2190
|
+
onStepComplete,
|
|
2191
|
+
outcome,
|
|
2192
|
+
verifiedRunAdmission != null,
|
|
2193
|
+
"step complete",
|
|
2194
|
+
);
|
|
1382
2195
|
return outcome;
|
|
1383
2196
|
},
|
|
1384
2197
|
);
|
|
1385
2198
|
|
|
1386
|
-
const
|
|
2199
|
+
const settledResults = await Promise.allSettled(promises);
|
|
2200
|
+
const rejected = settledResults.find(
|
|
2201
|
+
(settled) => settled.status === "rejected",
|
|
2202
|
+
);
|
|
2203
|
+
if (rejected) throw rejected.reason;
|
|
2204
|
+
const results = settledResults.map((settled) => settled.value);
|
|
1387
2205
|
stepOutcomes.push(...preOutcomes, ...results);
|
|
1388
2206
|
|
|
1389
2207
|
// Aggregate forEach children into a parent entry so downstream
|
|
@@ -1410,8 +2228,6 @@ export async function executeWorkflow(options = {}) {
|
|
|
1410
2228
|
});
|
|
1411
2229
|
}
|
|
1412
2230
|
}
|
|
1413
|
-
|
|
1414
|
-
if (anyFailure && !continueOnError) break;
|
|
1415
2231
|
}
|
|
1416
2232
|
}
|
|
1417
2233
|
|
|
@@ -1425,17 +2241,26 @@ export async function executeWorkflow(options = {}) {
|
|
|
1425
2241
|
|
|
1426
2242
|
const record = {
|
|
1427
2243
|
schema: COWORK_WORKFLOW_RUN_RECORD_SCHEMA,
|
|
1428
|
-
workflowId:
|
|
1429
|
-
workflowName:
|
|
2244
|
+
workflowId: runtimeWorkflow.id,
|
|
2245
|
+
workflowName: runtimeWorkflow.name,
|
|
1430
2246
|
definitionSchema: definitionAuthority.schema,
|
|
1431
2247
|
definitionDigest: definitionAuthority.definitionDigest,
|
|
2248
|
+
...(verifiedRunAdmission ? { runAdmission: verifiedRunAdmission } : {}),
|
|
1432
2249
|
status,
|
|
1433
2250
|
steps: stepOutcomes,
|
|
1434
2251
|
startedAt,
|
|
1435
2252
|
finishedAt,
|
|
1436
2253
|
};
|
|
1437
|
-
|
|
1438
|
-
|
|
2254
|
+
const finalRecord = verifiedRunAdmission
|
|
2255
|
+
? normalizeAdmittedRunRecord(
|
|
2256
|
+
record,
|
|
2257
|
+
runtimeWorkflow,
|
|
2258
|
+
definitionAuthority,
|
|
2259
|
+
verifiedRunAdmission,
|
|
2260
|
+
)
|
|
2261
|
+
: record;
|
|
2262
|
+
_appendHistory(cwd, finalRecord);
|
|
2263
|
+
return finalRecord;
|
|
1439
2264
|
}
|
|
1440
2265
|
|
|
1441
2266
|
function _appendHistory(cwd, record) {
|