chainlesschain 0.164.0 → 0.165.1
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/package.json +4 -4
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-CYxFDrcm.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-CPBtdiU2.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-Dayq7Dp7.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-6U7bPb2s.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-D2Iqg_3D.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-CdoOB7WV.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-DrnAVB8w.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-CesMi5cy.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-DaX9ptax.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-OrwOPxDS.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-DiHbcysC.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-CbgCkaBJ.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-Du5hgrh4.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-DsPOBW-p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-BwHIFkNC.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BvrDQSgd.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-gwAE6arx.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DW-0l4uF.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-j-NsLesB.js} +1 -1
- package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-CibVEBOI.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-W4z9vSfe.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-DAcrkKxG.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-6GXah-89.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-BXBLlcNm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-BoJJBkG3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-okjsl46v.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Rvs7i6nn.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-CHSUxXXS.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-V3BlfxsB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-Nn4LNxhA.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-GmB6DOr6.js → MarkdownRenderer-SVAgNnkt.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-DEmjFcg-.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-DlopXipI.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-BG3ynX60.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-DeRzWOfT.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-BoPbOv1C.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-un-rTHp7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-Ds0oveEB.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-D09JI-yY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-DTRhiKJn.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BAnnNCim.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-Yr-1fSYi.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-S9EAolpj.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-CqcLKqJ8.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-Dw0F9EcU.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BDRwUevN.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-D3_c3nKB.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-CxdMV98x.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-CgjNQFLI.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-BHghIIht.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-De6pqaKM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DDAjkUkT.js → Projects-CP8vPvzs.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CUnHbNf-.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-DA2f3Wts.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C1FeHDqp.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-B5guaUMN.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-eTlRhgvK.js} +1 -1
- package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-D7AqSGY0.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-JiuLm4Ja.js} +3 -3
- package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-CwruahW3.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-CdhqaR0b.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CU0ZIlcT.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-CtduPfAb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-CROXm6AY.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-DiZiABFD.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-D6UL3dA5.js → SpeechSettings-B5pLcXUw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-DBX6J1Oe.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-BLyRJd3-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-BKa5qzmk.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-DnD3a4M6.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-DEitQeTb.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-CjHggTDq.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-CsGT7sfl.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-yr4rFDrW.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-CLvu1Sjp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-B0m7yUP8.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-C0VvP4lc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-kllBAG1M.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-C54UQNLx.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-B7y8qSvU.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DRzTh8CJ.js} +1 -1
- package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-BSMwSnCx.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTg602H5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-B6EGsdvp.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-DDKqJ8RD.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-323Hhxxt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-BICn3Qh1.js} +1 -1
- package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BMuGd2--.js} +1 -1
- package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-BRqt4fO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BVTAl5N2.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-BsR7Y2NG.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-Bx044qDj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-C1GFfuqN.js} +1 -1
- package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
- package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-C66NpWy5.js} +1 -1
- package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-CDTvsX8t.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-CGNklbP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-CHfLYwcW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-CNxg61YP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CQlHFN_g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-CUEU48o1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-CWGax2QW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-C_JzIXUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-Cf23wVMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C484EJiS.js → index-CmAInUkX.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-CmAubjDi.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-Co7oM1qr.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CwPUZbMI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-D1naNj4U.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-D5CsBShd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DBT3YeQ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-DLdrHhML.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-Dc77VR3H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-Dfv2lKu3.js} +1 -1
- package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-DiW0sDkz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-DpTHIRfZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DqFXGzzC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-XFe-1twF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-p2HYn89m.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-pqfux8vZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-rjzk9z1Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-th2ihDc0.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtobShro.js → index-u2YVld9W.js} +1 -1
- package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-C3Djh9fa.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-BzqFmbSe.js} +1 -1
- package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-DJo_FFaW.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-_tlTiBvk.js} +1 -1
- package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-CB2ZyxUg.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DZwIFD4u.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-DxZY0dgk.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-CzzGt7GL.js} +1 -1
- package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-BaIK52yz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-3-FHu9_M.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-BkRT68fK.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-DVVUhV8p.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-BuBLFz0c.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-kpm6LtXU.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/cowork.js +283 -61
- package/src/data/changelog.json +15 -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/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-protocol.js +154 -10
- package/src/lib/background-agent-supervisor.js +840 -48
- package/src/lib/background-session-transport.js +23 -6
- package/src/lib/background-turn-bootstrap-protocol.js +64 -0
- package/src/lib/cowork-workflow.js +853 -61
- package/src/lib/delivery-production-adapter.js +38 -2
- package/src/lib/dynamic-workflow-facade.js +1105 -17
- package/src/lib/mcp-stdio-package-materialization.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 +604 -40
- 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/with-file-lock.js +73 -38
- package/src/repl/agent-repl.js +70 -29
- package/src/repl/remote-approval.js +29 -7
- package/src/runtime/agent-core.js +1099 -152
- package/src/runtime/headless-runner.js +22 -5
- package/src/runtime/headless-stream.js +19 -3
- package/src/workers/background-agent-keeper.js +249 -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/MobileProjects-BL0Czcoj.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
- 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,508 @@ 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
|
+
|
|
70
|
+
const DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA =
|
|
71
|
+
"cc-dynamic-workflow-run-admission/v1";
|
|
72
|
+
const EXECUTION_LOCATION_AUTHORITY_SCHEMA =
|
|
73
|
+
"cc-session-execution-location-authority/v1";
|
|
74
|
+
const EXECUTION_LOCATION_BINDING_SCHEMA = "cc-execution-location-binding/v1";
|
|
75
|
+
const COWORK_WORKFLOW_RECORD_SCHEMA = "cc-cowork-workflow-record/v1";
|
|
76
|
+
const RUN_ADMISSION_FIELDS = new Set([
|
|
77
|
+
"schema",
|
|
78
|
+
"definition",
|
|
79
|
+
"executionLocation",
|
|
80
|
+
"executionPolicy",
|
|
81
|
+
"definitionDigest",
|
|
82
|
+
"executionLocationDigest",
|
|
83
|
+
"preflightDigest",
|
|
84
|
+
"maxParallel",
|
|
85
|
+
"credentialValuesTransferred",
|
|
86
|
+
"admissionDigest",
|
|
87
|
+
]);
|
|
88
|
+
const RUN_ADMISSION_DEFINITION_FIELDS = new Set([
|
|
89
|
+
"schema",
|
|
90
|
+
"definitionDigest",
|
|
91
|
+
"authority",
|
|
92
|
+
]);
|
|
93
|
+
const RUN_ADMISSION_DEFINITION_AUTHORITY_FIELDS = new Set([
|
|
94
|
+
"status",
|
|
95
|
+
"recordSchema",
|
|
96
|
+
"definitionSchema",
|
|
97
|
+
"definitionDigest",
|
|
98
|
+
]);
|
|
99
|
+
const RUN_ADMISSION_EXECUTION_LOCATION_FIELDS = new Set([
|
|
100
|
+
"authoritySchema",
|
|
101
|
+
"authority",
|
|
102
|
+
"session",
|
|
103
|
+
"bindingSchema",
|
|
104
|
+
"location",
|
|
105
|
+
]);
|
|
106
|
+
const RUN_ADMISSION_SESSION_FIELDS = new Set([
|
|
107
|
+
"sessionId",
|
|
108
|
+
"headHash",
|
|
109
|
+
"eventCount",
|
|
110
|
+
]);
|
|
111
|
+
const RUN_ADMISSION_EXECUTION_POLICY_FIELDS = new Set([
|
|
112
|
+
"cwd",
|
|
113
|
+
"continueOnError",
|
|
114
|
+
"pipeline",
|
|
115
|
+
"provider",
|
|
116
|
+
"model",
|
|
117
|
+
]);
|
|
118
|
+
const RUN_ADMISSION_LLM_OPTION_FIELDS = new Set(["provider", "model"]);
|
|
119
|
+
const ADMITTED_TASK_ENTRY_REQUIRED_FIELDS = new Set([
|
|
120
|
+
"taskId",
|
|
121
|
+
"status",
|
|
122
|
+
"result",
|
|
123
|
+
]);
|
|
124
|
+
const ADMITTED_STEP_STATUSES = new Set([
|
|
125
|
+
"completed",
|
|
126
|
+
"failed",
|
|
127
|
+
"partial",
|
|
128
|
+
"skipped",
|
|
129
|
+
]);
|
|
130
|
+
const MAX_ADMITTED_RUN_STEPS = 64;
|
|
131
|
+
const MAX_ADMITTED_SNAPSHOT_NODES = 25_000;
|
|
132
|
+
const MAX_ADMITTED_SNAPSHOT_BYTES = 1024 * 1024;
|
|
133
|
+
const STEP_TIMEOUT_CODE = "COWORK_WORKFLOW_STEP_TIMEOUT";
|
|
134
|
+
|
|
135
|
+
function runAdmissionError(message) {
|
|
136
|
+
const error = new Error(message);
|
|
137
|
+
error.code = COWORK_WORKFLOW_RUN_ADMISSION_INVALID_CODE;
|
|
138
|
+
return error;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function runResultError(message) {
|
|
142
|
+
const error = new Error(message);
|
|
143
|
+
error.code = COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE;
|
|
144
|
+
return error;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Snapshot an admitted runner value without invoking accessors or accepting
|
|
149
|
+
* proxies/non-JSON values. The returned graph is detached and deeply frozen,
|
|
150
|
+
* so callbacks and a hostile runner cannot mutate executor control state.
|
|
151
|
+
*/
|
|
152
|
+
function snapshotAdmittedCanonicalValue(
|
|
153
|
+
value,
|
|
154
|
+
name,
|
|
155
|
+
{
|
|
156
|
+
maxNodes = MAX_ADMITTED_SNAPSHOT_NODES,
|
|
157
|
+
maxBytes = MAX_ADMITTED_SNAPSHOT_BYTES,
|
|
158
|
+
} = {},
|
|
159
|
+
) {
|
|
160
|
+
const seen = new WeakSet();
|
|
161
|
+
let nodes = 0;
|
|
162
|
+
let roughCharacters = 0;
|
|
163
|
+
const invalid = (reason) => {
|
|
164
|
+
throw runResultError(`${name} ${reason}`);
|
|
165
|
+
};
|
|
166
|
+
const account = (characters = 1) => {
|
|
167
|
+
nodes += 1;
|
|
168
|
+
roughCharacters += characters;
|
|
169
|
+
if (nodes > maxNodes || roughCharacters > maxBytes) {
|
|
170
|
+
invalid("exceeds snapshot limits");
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const visit = (current, depth) => {
|
|
174
|
+
if (depth > 64) invalid("exceeds snapshot depth");
|
|
175
|
+
if (current === null || typeof current === "boolean") {
|
|
176
|
+
account();
|
|
177
|
+
return current;
|
|
178
|
+
}
|
|
179
|
+
if (typeof current === "string") {
|
|
180
|
+
account(current.length);
|
|
181
|
+
return current;
|
|
182
|
+
}
|
|
183
|
+
if (typeof current === "number") {
|
|
184
|
+
if (!Number.isFinite(current)) invalid("contains a non-finite number");
|
|
185
|
+
account();
|
|
186
|
+
return current;
|
|
187
|
+
}
|
|
188
|
+
if (!current || typeof current !== "object" || utilTypes.isProxy(current)) {
|
|
189
|
+
invalid("must contain only non-proxy canonical JSON values");
|
|
190
|
+
}
|
|
191
|
+
if (seen.has(current)) invalid("contains a cycle or repeated reference");
|
|
192
|
+
seen.add(current);
|
|
193
|
+
account();
|
|
194
|
+
|
|
195
|
+
if (Array.isArray(current)) {
|
|
196
|
+
if (Object.getPrototypeOf(current) !== Array.prototype) {
|
|
197
|
+
invalid("contains a non-plain array");
|
|
198
|
+
}
|
|
199
|
+
if (current.length > maxNodes) invalid("contains an oversized array");
|
|
200
|
+
const keys = Reflect.ownKeys(current);
|
|
201
|
+
if (keys.length !== current.length + 1) {
|
|
202
|
+
invalid("contains a sparse or extended array");
|
|
203
|
+
}
|
|
204
|
+
const output = [];
|
|
205
|
+
for (let index = 0; index < current.length; index += 1) {
|
|
206
|
+
const descriptor = Object.getOwnPropertyDescriptor(
|
|
207
|
+
current,
|
|
208
|
+
String(index),
|
|
209
|
+
);
|
|
210
|
+
if (
|
|
211
|
+
!descriptor ||
|
|
212
|
+
descriptor.enumerable !== true ||
|
|
213
|
+
!Object.hasOwn(descriptor, "value")
|
|
214
|
+
) {
|
|
215
|
+
invalid("contains an accessor or sparse array entry");
|
|
216
|
+
}
|
|
217
|
+
output.push(visit(descriptor.value, depth + 1));
|
|
218
|
+
}
|
|
219
|
+
return Object.freeze(output);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const prototype = Object.getPrototypeOf(current);
|
|
223
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
224
|
+
invalid("contains a non-plain object");
|
|
225
|
+
}
|
|
226
|
+
const output = {};
|
|
227
|
+
for (const key of Reflect.ownKeys(current)) {
|
|
228
|
+
if (typeof key !== "string") invalid("contains a symbol key");
|
|
229
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, key);
|
|
230
|
+
if (
|
|
231
|
+
!descriptor ||
|
|
232
|
+
descriptor.enumerable !== true ||
|
|
233
|
+
!Object.hasOwn(descriptor, "value")
|
|
234
|
+
) {
|
|
235
|
+
invalid("contains an accessor or non-enumerable property");
|
|
236
|
+
}
|
|
237
|
+
roughCharacters += key.length;
|
|
238
|
+
if (roughCharacters > maxBytes) invalid("exceeds snapshot limits");
|
|
239
|
+
output[key] = visit(descriptor.value, depth + 1);
|
|
240
|
+
}
|
|
241
|
+
return Object.freeze(output);
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const snapshot = visit(value, 0);
|
|
245
|
+
let serialized;
|
|
246
|
+
try {
|
|
247
|
+
serialized = JSON.stringify(snapshot);
|
|
248
|
+
} catch {
|
|
249
|
+
invalid("cannot be serialized");
|
|
250
|
+
}
|
|
251
|
+
if (Buffer.byteLength(serialized, "utf8") > maxBytes) {
|
|
252
|
+
invalid("exceeds snapshot limits");
|
|
253
|
+
}
|
|
254
|
+
return snapshot;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function safeRunErrorMessage(error) {
|
|
258
|
+
if (!error || typeof error !== "object" || utilTypes.isProxy(error)) {
|
|
259
|
+
return "task failed";
|
|
260
|
+
}
|
|
261
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, "message");
|
|
262
|
+
if (
|
|
263
|
+
descriptor &&
|
|
264
|
+
Object.hasOwn(descriptor, "value") &&
|
|
265
|
+
typeof descriptor.value === "string"
|
|
266
|
+
) {
|
|
267
|
+
return descriptor.value.slice(0, 4096);
|
|
268
|
+
}
|
|
269
|
+
return "task failed";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function normalizeAdmittedTaskEntry(value) {
|
|
273
|
+
const entry = snapshotAdmittedCanonicalValue(value, "task result");
|
|
274
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
275
|
+
throw runResultError("task result must be a plain object");
|
|
276
|
+
}
|
|
277
|
+
if (
|
|
278
|
+
[...ADMITTED_TASK_ENTRY_REQUIRED_FIELDS].some(
|
|
279
|
+
(field) => !Object.hasOwn(entry, field),
|
|
280
|
+
) ||
|
|
281
|
+
(entry.status !== "completed" && entry.status !== "failed") ||
|
|
282
|
+
typeof entry.taskId !== "string" ||
|
|
283
|
+
entry.taskId.length === 0 ||
|
|
284
|
+
entry.taskId.length > 512 ||
|
|
285
|
+
!entry.result ||
|
|
286
|
+
typeof entry.result !== "object" ||
|
|
287
|
+
Array.isArray(entry.result)
|
|
288
|
+
) {
|
|
289
|
+
throw runResultError("task result has an invalid schema");
|
|
290
|
+
}
|
|
291
|
+
return Object.freeze({
|
|
292
|
+
taskId: entry.taskId,
|
|
293
|
+
status: entry.status,
|
|
294
|
+
result: entry.result,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizeAdmittedOutcome(value, name = "workflow step outcome") {
|
|
299
|
+
const outcome = snapshotAdmittedCanonicalValue(value, name);
|
|
300
|
+
if (
|
|
301
|
+
!outcome ||
|
|
302
|
+
typeof outcome !== "object" ||
|
|
303
|
+
Array.isArray(outcome) ||
|
|
304
|
+
Reflect.ownKeys(outcome).length !== 4 ||
|
|
305
|
+
!["id", "status", "taskId", "result"].every((field) =>
|
|
306
|
+
Object.hasOwn(outcome, field),
|
|
307
|
+
) ||
|
|
308
|
+
typeof outcome.id !== "string" ||
|
|
309
|
+
outcome.id.length === 0 ||
|
|
310
|
+
outcome.id.length > 512 ||
|
|
311
|
+
!ADMITTED_STEP_STATUSES.has(outcome.status) ||
|
|
312
|
+
(outcome.taskId !== null &&
|
|
313
|
+
(typeof outcome.taskId !== "string" ||
|
|
314
|
+
outcome.taskId.length === 0 ||
|
|
315
|
+
outcome.taskId.length > 512)) ||
|
|
316
|
+
!outcome.result ||
|
|
317
|
+
typeof outcome.result !== "object" ||
|
|
318
|
+
Array.isArray(outcome.result)
|
|
319
|
+
) {
|
|
320
|
+
throw runResultError(`${name} has an invalid schema`);
|
|
321
|
+
}
|
|
322
|
+
return outcome;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function notifyWorkflowCallback(callback, value, admitted, name) {
|
|
326
|
+
if (typeof callback !== "function") return;
|
|
327
|
+
const view = admitted
|
|
328
|
+
? snapshotAdmittedCanonicalValue(value, `${name} callback view`)
|
|
329
|
+
: value;
|
|
330
|
+
try {
|
|
331
|
+
callback(view);
|
|
332
|
+
} catch {
|
|
333
|
+
// Observation callbacks must not mutate or steer workflow control flow.
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function assertAdmissionObject(value, fields, name) {
|
|
338
|
+
if (
|
|
339
|
+
!value ||
|
|
340
|
+
typeof value !== "object" ||
|
|
341
|
+
Array.isArray(value) ||
|
|
342
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value)) ||
|
|
343
|
+
Object.keys(value).length !== fields.size ||
|
|
344
|
+
Object.keys(value).some((key) => !fields.has(key))
|
|
345
|
+
) {
|
|
346
|
+
throw runAdmissionError(`${name} is invalid`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function normalizeAdmittedLlmOptions(value) {
|
|
351
|
+
if (
|
|
352
|
+
!value ||
|
|
353
|
+
typeof value !== "object" ||
|
|
354
|
+
Array.isArray(value) ||
|
|
355
|
+
utilTypes.isProxy(value) ||
|
|
356
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(value))
|
|
357
|
+
) {
|
|
358
|
+
throw runAdmissionError("admitted workflow llmOptions is invalid");
|
|
359
|
+
}
|
|
360
|
+
const keys = Reflect.ownKeys(value);
|
|
361
|
+
if (
|
|
362
|
+
keys.length !== RUN_ADMISSION_LLM_OPTION_FIELDS.size ||
|
|
363
|
+
keys.some(
|
|
364
|
+
(key) =>
|
|
365
|
+
typeof key !== "string" || !RUN_ADMISSION_LLM_OPTION_FIELDS.has(key),
|
|
366
|
+
)
|
|
367
|
+
) {
|
|
368
|
+
throw runAdmissionError("admitted workflow llmOptions is not exact");
|
|
369
|
+
}
|
|
370
|
+
const snapshot = {};
|
|
371
|
+
for (const key of RUN_ADMISSION_LLM_OPTION_FIELDS) {
|
|
372
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
373
|
+
if (
|
|
374
|
+
!descriptor ||
|
|
375
|
+
!Object.hasOwn(descriptor, "value") ||
|
|
376
|
+
descriptor.enumerable !== true
|
|
377
|
+
) {
|
|
378
|
+
throw runAdmissionError(
|
|
379
|
+
"admitted workflow llmOptions must use enumerable data properties",
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
snapshot[key] = descriptor.value;
|
|
383
|
+
}
|
|
384
|
+
return Object.freeze(snapshot);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function normalizeAdmissionDigest(value, name) {
|
|
388
|
+
const digest = normalizeWorkflowDefinitionDigest(value);
|
|
389
|
+
if (!digest || digest !== value) {
|
|
390
|
+
throw runAdmissionError(`${name} is invalid`);
|
|
391
|
+
}
|
|
392
|
+
return digest;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function admissionDigest(material) {
|
|
396
|
+
return `sha256:${createHash("sha256")
|
|
397
|
+
.update("chainlesschain.dynamic-workflow.admission.v1\0", "utf8")
|
|
398
|
+
.update(canonicalJson(material, "dynamicWorkflowAdmission"), "utf8")
|
|
399
|
+
.digest("hex")}`;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function normalizeWorkflowRunAdmission(
|
|
403
|
+
value,
|
|
404
|
+
definitionAuthority,
|
|
405
|
+
maxParallel,
|
|
406
|
+
execution,
|
|
407
|
+
) {
|
|
408
|
+
try {
|
|
409
|
+
value = structuredClone(value);
|
|
410
|
+
} catch {
|
|
411
|
+
throw runAdmissionError("run admission could not be safely snapshotted");
|
|
412
|
+
}
|
|
413
|
+
assertAdmissionObject(value, RUN_ADMISSION_FIELDS, "run admission");
|
|
414
|
+
assertAdmissionObject(
|
|
415
|
+
value.definition,
|
|
416
|
+
RUN_ADMISSION_DEFINITION_FIELDS,
|
|
417
|
+
"run admission definition",
|
|
418
|
+
);
|
|
419
|
+
assertAdmissionObject(
|
|
420
|
+
value.definition.authority,
|
|
421
|
+
RUN_ADMISSION_DEFINITION_AUTHORITY_FIELDS,
|
|
422
|
+
"run admission definition authority",
|
|
423
|
+
);
|
|
424
|
+
assertAdmissionObject(
|
|
425
|
+
value.executionLocation,
|
|
426
|
+
RUN_ADMISSION_EXECUTION_LOCATION_FIELDS,
|
|
427
|
+
"run admission execution location",
|
|
428
|
+
);
|
|
429
|
+
assertAdmissionObject(
|
|
430
|
+
value.executionLocation.session,
|
|
431
|
+
RUN_ADMISSION_SESSION_FIELDS,
|
|
432
|
+
"run admission session proof",
|
|
433
|
+
);
|
|
434
|
+
assertAdmissionObject(
|
|
435
|
+
value.executionPolicy,
|
|
436
|
+
RUN_ADMISSION_EXECUTION_POLICY_FIELDS,
|
|
437
|
+
"run admission execution policy",
|
|
438
|
+
);
|
|
439
|
+
assertAdmissionObject(
|
|
440
|
+
execution.llmOptions,
|
|
441
|
+
RUN_ADMISSION_LLM_OPTION_FIELDS,
|
|
442
|
+
"admitted workflow llmOptions",
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const definitionDigest = normalizeAdmissionDigest(
|
|
446
|
+
value.definitionDigest,
|
|
447
|
+
"run admission definition digest",
|
|
448
|
+
);
|
|
449
|
+
const executionLocationDigest = normalizeAdmissionDigest(
|
|
450
|
+
value.executionLocationDigest,
|
|
451
|
+
"run admission execution-location digest",
|
|
452
|
+
);
|
|
453
|
+
const preflightDigest = normalizeAdmissionDigest(
|
|
454
|
+
value.preflightDigest,
|
|
455
|
+
"run admission preflight digest",
|
|
456
|
+
);
|
|
457
|
+
const declaredAdmissionDigest = normalizeAdmissionDigest(
|
|
458
|
+
value.admissionDigest,
|
|
459
|
+
"run admission digest",
|
|
460
|
+
);
|
|
461
|
+
const authority = value.definition.authority;
|
|
462
|
+
const session = value.executionLocation.session;
|
|
463
|
+
const executionPolicy = value.executionPolicy;
|
|
464
|
+
const hasControlCharacters = (input) =>
|
|
465
|
+
[...input].some((character) => {
|
|
466
|
+
const codePoint = character.codePointAt(0);
|
|
467
|
+
return codePoint <= 0x1f || codePoint === 0x7f;
|
|
468
|
+
});
|
|
469
|
+
const validOptionalString = (input) =>
|
|
470
|
+
input === null ||
|
|
471
|
+
(typeof input === "string" &&
|
|
472
|
+
input.length > 0 &&
|
|
473
|
+
input.trim() === input &&
|
|
474
|
+
!hasControlCharacters(input));
|
|
475
|
+
|
|
476
|
+
if (
|
|
477
|
+
value.schema !== DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA ||
|
|
478
|
+
value.definition.schema !== definitionAuthority.schema ||
|
|
479
|
+
value.definition.definitionDigest !== definitionDigest ||
|
|
480
|
+
definitionDigest !== definitionAuthority.definitionDigest ||
|
|
481
|
+
authority.status !== "versioned" ||
|
|
482
|
+
authority.recordSchema !== COWORK_WORKFLOW_RECORD_SCHEMA ||
|
|
483
|
+
authority.definitionSchema !== definitionAuthority.schema ||
|
|
484
|
+
authority.definitionDigest !== definitionDigest ||
|
|
485
|
+
value.executionLocation.authoritySchema !==
|
|
486
|
+
EXECUTION_LOCATION_AUTHORITY_SCHEMA ||
|
|
487
|
+
value.executionLocation.authority !== "verified-session-start" ||
|
|
488
|
+
value.executionLocation.bindingSchema !==
|
|
489
|
+
EXECUTION_LOCATION_BINDING_SCHEMA ||
|
|
490
|
+
typeof value.executionLocation.location !== "string" ||
|
|
491
|
+
value.executionLocation.location.trim() !==
|
|
492
|
+
value.executionLocation.location ||
|
|
493
|
+
value.executionLocation.location.length === 0 ||
|
|
494
|
+
typeof session.sessionId !== "string" ||
|
|
495
|
+
session.sessionId.trim() !== session.sessionId ||
|
|
496
|
+
session.sessionId.length === 0 ||
|
|
497
|
+
!/^[a-f0-9]{64}$/u.test(session.headHash) ||
|
|
498
|
+
!Number.isSafeInteger(session.eventCount) ||
|
|
499
|
+
session.eventCount < 1 ||
|
|
500
|
+
typeof executionPolicy.cwd !== "string" ||
|
|
501
|
+
executionPolicy.cwd.length === 0 ||
|
|
502
|
+
hasControlCharacters(executionPolicy.cwd) ||
|
|
503
|
+
typeof executionPolicy.continueOnError !== "boolean" ||
|
|
504
|
+
typeof executionPolicy.pipeline !== "boolean" ||
|
|
505
|
+
!validOptionalString(executionPolicy.provider) ||
|
|
506
|
+
!validOptionalString(executionPolicy.model) ||
|
|
507
|
+
executionPolicy.cwd !== execution.cwd ||
|
|
508
|
+
executionPolicy.continueOnError !== execution.continueOnError ||
|
|
509
|
+
executionPolicy.pipeline !== execution.pipeline ||
|
|
510
|
+
executionPolicy.provider !== execution.llmOptions.provider ||
|
|
511
|
+
executionPolicy.model !== execution.llmOptions.model ||
|
|
512
|
+
!Number.isSafeInteger(maxParallel) ||
|
|
513
|
+
maxParallel < 1 ||
|
|
514
|
+
maxParallel > 64 ||
|
|
515
|
+
value.maxParallel !== maxParallel ||
|
|
516
|
+
value.credentialValuesTransferred !== false
|
|
517
|
+
) {
|
|
518
|
+
throw runAdmissionError(
|
|
519
|
+
"run admission does not match the workflow definition or execution options",
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const normalized = Object.freeze({
|
|
524
|
+
schema: DYNAMIC_WORKFLOW_RUN_ADMISSION_SCHEMA,
|
|
525
|
+
definition: Object.freeze({
|
|
526
|
+
schema: definitionAuthority.schema,
|
|
527
|
+
definitionDigest,
|
|
528
|
+
authority: Object.freeze({
|
|
529
|
+
status: "versioned",
|
|
530
|
+
recordSchema: COWORK_WORKFLOW_RECORD_SCHEMA,
|
|
531
|
+
definitionSchema: definitionAuthority.schema,
|
|
532
|
+
definitionDigest,
|
|
533
|
+
}),
|
|
534
|
+
}),
|
|
535
|
+
executionLocation: Object.freeze({
|
|
536
|
+
authoritySchema: EXECUTION_LOCATION_AUTHORITY_SCHEMA,
|
|
537
|
+
authority: "verified-session-start",
|
|
538
|
+
session: Object.freeze({
|
|
539
|
+
sessionId: session.sessionId,
|
|
540
|
+
headHash: session.headHash,
|
|
541
|
+
eventCount: session.eventCount,
|
|
542
|
+
}),
|
|
543
|
+
bindingSchema: EXECUTION_LOCATION_BINDING_SCHEMA,
|
|
544
|
+
location: value.executionLocation.location,
|
|
545
|
+
}),
|
|
546
|
+
executionPolicy: Object.freeze({
|
|
547
|
+
cwd: executionPolicy.cwd,
|
|
548
|
+
continueOnError: executionPolicy.continueOnError,
|
|
549
|
+
pipeline: executionPolicy.pipeline,
|
|
550
|
+
provider: executionPolicy.provider,
|
|
551
|
+
model: executionPolicy.model,
|
|
552
|
+
}),
|
|
553
|
+
definitionDigest,
|
|
554
|
+
executionLocationDigest,
|
|
555
|
+
preflightDigest,
|
|
556
|
+
maxParallel,
|
|
557
|
+
credentialValuesTransferred: false,
|
|
558
|
+
});
|
|
559
|
+
if (admissionDigest(normalized) !== declaredAdmissionDigest) {
|
|
560
|
+
throw runAdmissionError("run admission digest does not match its material");
|
|
561
|
+
}
|
|
562
|
+
return Object.freeze({
|
|
563
|
+
...normalized,
|
|
564
|
+
admissionDigest: declaredAdmissionDigest,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
62
567
|
|
|
63
568
|
export const _deps = {
|
|
64
569
|
existsSync,
|
|
@@ -751,7 +1256,9 @@ export async function withTimeout(factory, timeoutMs) {
|
|
|
751
1256
|
let timer = null;
|
|
752
1257
|
const timeout = new Promise((_resolve, reject) => {
|
|
753
1258
|
timer = _deps.setTimeout(() => {
|
|
754
|
-
|
|
1259
|
+
const error = new Error(`step timed out after ${timeoutMs}ms`);
|
|
1260
|
+
error.code = STEP_TIMEOUT_CODE;
|
|
1261
|
+
reject(error);
|
|
755
1262
|
}, timeoutMs);
|
|
756
1263
|
});
|
|
757
1264
|
try {
|
|
@@ -762,36 +1269,102 @@ export async function withTimeout(factory, timeoutMs) {
|
|
|
762
1269
|
}
|
|
763
1270
|
}
|
|
764
1271
|
|
|
1272
|
+
function createWorkflowTaskSemaphore(limit) {
|
|
1273
|
+
let active = 0;
|
|
1274
|
+
const waiters = [];
|
|
1275
|
+
return Object.freeze({
|
|
1276
|
+
acquire() {
|
|
1277
|
+
return new Promise((resolve) => {
|
|
1278
|
+
const grant = () => {
|
|
1279
|
+
active += 1;
|
|
1280
|
+
let released = false;
|
|
1281
|
+
resolve(() => {
|
|
1282
|
+
if (released) return;
|
|
1283
|
+
released = true;
|
|
1284
|
+
active -= 1;
|
|
1285
|
+
const next = waiters.shift();
|
|
1286
|
+
if (next) next();
|
|
1287
|
+
});
|
|
1288
|
+
};
|
|
1289
|
+
if (active < limit) grant();
|
|
1290
|
+
else waiters.push(grant);
|
|
1291
|
+
});
|
|
1292
|
+
},
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
|
|
765
1296
|
/**
|
|
766
1297
|
* Run one step's task with optional `timeoutMs` and `retries` (with `fixed` or
|
|
767
1298
|
* `exponential` `retryDelayMs` backoff). An attempt counts as a failure if the
|
|
768
1299
|
* task throws, times out, or returns a non-`completed` status. Returns
|
|
769
1300
|
* `{ ok, entry?, error?, attempts }`. `attempts` is the total number of tries.
|
|
770
1301
|
*/
|
|
771
|
-
export async function runStepWithRetry({
|
|
1302
|
+
export async function runStepWithRetry({
|
|
1303
|
+
step,
|
|
1304
|
+
message,
|
|
1305
|
+
cwd,
|
|
1306
|
+
llmOptions,
|
|
1307
|
+
taskSemaphore,
|
|
1308
|
+
runTask = _deps.runTask,
|
|
1309
|
+
recordId = step.id,
|
|
1310
|
+
onTaskStart,
|
|
1311
|
+
admitted = false,
|
|
1312
|
+
}) {
|
|
772
1313
|
const maxRetries = Math.max(0, Math.floor(Number(step.retries) || 0));
|
|
773
1314
|
const timeoutMs = Number(step.timeoutMs) || 0;
|
|
774
1315
|
let lastEntry = null;
|
|
775
1316
|
let lastErr = null;
|
|
776
1317
|
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
1318
|
+
let taskPromise = null;
|
|
1319
|
+
let abortController = null;
|
|
1320
|
+
let release = null;
|
|
777
1321
|
try {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
1322
|
+
release = taskSemaphore ? await taskSemaphore.acquire() : () => {};
|
|
1323
|
+
if (admitted && timeoutMs > 0) abortController = new AbortController();
|
|
1324
|
+
if (attempt === 1) {
|
|
1325
|
+
notifyWorkflowCallback(
|
|
1326
|
+
onTaskStart,
|
|
1327
|
+
Object.freeze({ stepId: recordId, message }),
|
|
1328
|
+
admitted,
|
|
1329
|
+
"step start",
|
|
1330
|
+
);
|
|
1331
|
+
}
|
|
1332
|
+
taskPromise = Promise.resolve().then(() =>
|
|
1333
|
+
runTask({
|
|
1334
|
+
templateId: step.templateId || null,
|
|
1335
|
+
userMessage: message,
|
|
1336
|
+
files: step.files || [],
|
|
1337
|
+
cwd,
|
|
1338
|
+
llmOptions,
|
|
1339
|
+
...(abortController ? { signal: abortController.signal } : {}),
|
|
1340
|
+
}),
|
|
788
1341
|
);
|
|
1342
|
+
// A timeout abandons only the caller's wait. Keep the physical task's
|
|
1343
|
+
// permit until its promise really settles; retries and parallel children
|
|
1344
|
+
// must queue behind that late task instead of exceeding maxParallel.
|
|
1345
|
+
taskPromise.then(release, release);
|
|
1346
|
+
release = null; // the physical task now owns the permit
|
|
1347
|
+
const rawEntry = await withTimeout(() => taskPromise, timeoutMs);
|
|
1348
|
+
const entry = admitted ? normalizeAdmittedTaskEntry(rawEntry) : rawEntry;
|
|
789
1349
|
if (entry && entry.status === "completed") {
|
|
790
1350
|
return { ok: true, entry, attempts: attempt };
|
|
791
1351
|
}
|
|
792
1352
|
lastEntry = entry; // ran but not completed → retry-eligible
|
|
793
1353
|
lastErr = null;
|
|
794
1354
|
} catch (err) {
|
|
1355
|
+
if (release) release();
|
|
1356
|
+
if (err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE) throw err;
|
|
1357
|
+
if (admitted && err?.code === STEP_TIMEOUT_CODE && taskPromise != null) {
|
|
1358
|
+
abortController.abort(err);
|
|
1359
|
+
// A terminal admitted record must never be written while the physical
|
|
1360
|
+
// task can still produce side effects. Confirm settlement before a
|
|
1361
|
+
// retry or final failure; a runner that ignores abort safely blocks.
|
|
1362
|
+
try {
|
|
1363
|
+
await taskPromise;
|
|
1364
|
+
} catch {
|
|
1365
|
+
// The timeout remains the attempt's externally visible failure.
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
795
1368
|
lastErr = err; // threw or timed out → retry-eligible
|
|
796
1369
|
lastEntry = null;
|
|
797
1370
|
}
|
|
@@ -813,24 +1386,27 @@ function _withAttempts(result, attempts) {
|
|
|
813
1386
|
}
|
|
814
1387
|
|
|
815
1388
|
/** Build a single-step outcome object from a `runStepWithRetry` result. */
|
|
816
|
-
function outcomeFromRetry(recordId, r) {
|
|
1389
|
+
function outcomeFromRetry(recordId, r, admitted = false) {
|
|
1390
|
+
let outcome;
|
|
817
1391
|
if (r.ok || r.entry) {
|
|
818
|
-
|
|
1392
|
+
outcome = {
|
|
819
1393
|
id: recordId,
|
|
820
1394
|
status: r.entry.status,
|
|
821
1395
|
taskId: r.entry.taskId,
|
|
822
1396
|
result: _withAttempts(r.entry.result, r.attempts),
|
|
823
1397
|
};
|
|
1398
|
+
} else {
|
|
1399
|
+
outcome = {
|
|
1400
|
+
id: recordId,
|
|
1401
|
+
status: "failed",
|
|
1402
|
+
taskId: null,
|
|
1403
|
+
result: _withAttempts(
|
|
1404
|
+
{ summary: `Step threw: ${safeRunErrorMessage(r.error)}` },
|
|
1405
|
+
r.attempts,
|
|
1406
|
+
),
|
|
1407
|
+
};
|
|
824
1408
|
}
|
|
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
|
-
};
|
|
1409
|
+
return admitted ? normalizeAdmittedOutcome(outcome) : outcome;
|
|
834
1410
|
}
|
|
835
1411
|
|
|
836
1412
|
// ─── while / until loop nodes ──────────────────────────────────────────────────
|
|
@@ -901,11 +1477,16 @@ export async function runLoopStep({
|
|
|
901
1477
|
cwd,
|
|
902
1478
|
llmOptions,
|
|
903
1479
|
resultsById,
|
|
1480
|
+
taskSemaphore,
|
|
1481
|
+
runTask = _deps.runTask,
|
|
1482
|
+
onStepStart,
|
|
1483
|
+
admitted = false,
|
|
904
1484
|
}) {
|
|
905
1485
|
const cap = loopIterationCap(step);
|
|
906
1486
|
let last = null;
|
|
907
1487
|
let iterations = 0;
|
|
908
1488
|
let stopReason = "cap";
|
|
1489
|
+
let startAnnounced = false;
|
|
909
1490
|
for (let iter = 1; iter <= cap; iter++) {
|
|
910
1491
|
iterations = iter;
|
|
911
1492
|
const withSelf = substituteLoopVars(step.message, {
|
|
@@ -914,8 +1495,22 @@ export async function runLoopStep({
|
|
|
914
1495
|
resultsById,
|
|
915
1496
|
});
|
|
916
1497
|
const message = substitutePlaceholders(withSelf, resultsById);
|
|
917
|
-
const r = await runStepWithRetry({
|
|
918
|
-
|
|
1498
|
+
const r = await runStepWithRetry({
|
|
1499
|
+
step,
|
|
1500
|
+
message,
|
|
1501
|
+
cwd,
|
|
1502
|
+
llmOptions,
|
|
1503
|
+
taskSemaphore,
|
|
1504
|
+
runTask,
|
|
1505
|
+
recordId,
|
|
1506
|
+
onTaskStart: (event) => {
|
|
1507
|
+
if (startAnnounced) return;
|
|
1508
|
+
startAnnounced = true;
|
|
1509
|
+
notifyWorkflowCallback(onStepStart, event, admitted, "step start");
|
|
1510
|
+
},
|
|
1511
|
+
admitted,
|
|
1512
|
+
});
|
|
1513
|
+
last = outcomeFromRetry(recordId, r, admitted);
|
|
919
1514
|
resultsById.set(recordId, last);
|
|
920
1515
|
if (last.status === "failed") {
|
|
921
1516
|
stopReason = "failed";
|
|
@@ -942,7 +1537,7 @@ export async function runLoopStep({
|
|
|
942
1537
|
break;
|
|
943
1538
|
}
|
|
944
1539
|
}
|
|
945
|
-
|
|
1540
|
+
const outcome = {
|
|
946
1541
|
id: recordId,
|
|
947
1542
|
status: last ? last.status : "skipped",
|
|
948
1543
|
taskId: last?.taskId ?? null,
|
|
@@ -953,6 +1548,7 @@ export async function runLoopStep({
|
|
|
953
1548
|
loopStop: stopReason,
|
|
954
1549
|
},
|
|
955
1550
|
};
|
|
1551
|
+
return admitted ? normalizeAdmittedOutcome(outcome) : outcome;
|
|
956
1552
|
}
|
|
957
1553
|
|
|
958
1554
|
// ─── Step node + no-barrier pipeline ──────────────────────────────────────────
|
|
@@ -966,7 +1562,16 @@ export async function runLoopStep({
|
|
|
966
1562
|
* is not "completed". Used by the no-barrier pipeline scheduler.
|
|
967
1563
|
*/
|
|
968
1564
|
export async function runStepNode(step, ctx) {
|
|
969
|
-
const {
|
|
1565
|
+
const {
|
|
1566
|
+
resultsById,
|
|
1567
|
+
cwd,
|
|
1568
|
+
llmOptions,
|
|
1569
|
+
onStepStart,
|
|
1570
|
+
onStepComplete,
|
|
1571
|
+
taskSemaphore,
|
|
1572
|
+
runTask,
|
|
1573
|
+
admitted = false,
|
|
1574
|
+
} = ctx;
|
|
970
1575
|
const recordId = step.id;
|
|
971
1576
|
const single = (status, summary) => {
|
|
972
1577
|
const o = { id: recordId, status, taskId: null, result: { summary } };
|
|
@@ -986,16 +1591,19 @@ export async function runStepNode(step, ctx) {
|
|
|
986
1591
|
if (!runThis) return single("skipped", "when-condition false");
|
|
987
1592
|
|
|
988
1593
|
if (isLoopStep(step)) {
|
|
989
|
-
if (onStepStart) onStepStart({ stepId: recordId, message: step.message });
|
|
990
1594
|
const o = await runLoopStep({
|
|
991
1595
|
step,
|
|
992
1596
|
recordId,
|
|
993
1597
|
cwd,
|
|
994
1598
|
llmOptions,
|
|
995
1599
|
resultsById,
|
|
1600
|
+
taskSemaphore,
|
|
1601
|
+
runTask,
|
|
1602
|
+
onStepStart,
|
|
1603
|
+
admitted,
|
|
996
1604
|
});
|
|
997
1605
|
resultsById.set(recordId, o);
|
|
998
|
-
|
|
1606
|
+
notifyWorkflowCallback(onStepComplete, o, admitted, "step complete");
|
|
999
1607
|
return {
|
|
1000
1608
|
outcomes: [o],
|
|
1001
1609
|
failed: o.status !== "completed" && o.status !== "skipped",
|
|
@@ -1015,16 +1623,20 @@ export async function runStepNode(step, ctx) {
|
|
|
1015
1623
|
const childId = `${recordId}[${k}]`;
|
|
1016
1624
|
const withItem = substituteItem(step.message, item);
|
|
1017
1625
|
const msg = substitutePlaceholders(withItem, resultsById);
|
|
1018
|
-
if (onStepStart) onStepStart({ stepId: childId, message: msg });
|
|
1019
1626
|
const r = await runStepWithRetry({
|
|
1020
1627
|
step,
|
|
1021
1628
|
message: msg,
|
|
1022
1629
|
cwd,
|
|
1023
1630
|
llmOptions,
|
|
1631
|
+
taskSemaphore,
|
|
1632
|
+
runTask,
|
|
1633
|
+
recordId: childId,
|
|
1634
|
+
onTaskStart: onStepStart,
|
|
1635
|
+
admitted,
|
|
1024
1636
|
});
|
|
1025
|
-
const co = outcomeFromRetry(childId, r);
|
|
1637
|
+
const co = outcomeFromRetry(childId, r, admitted);
|
|
1026
1638
|
resultsById.set(childId, co);
|
|
1027
|
-
|
|
1639
|
+
notifyWorkflowCallback(onStepComplete, co, admitted, "step complete");
|
|
1028
1640
|
return co;
|
|
1029
1641
|
}),
|
|
1030
1642
|
);
|
|
@@ -1046,11 +1658,20 @@ export async function runStepNode(step, ctx) {
|
|
|
1046
1658
|
}
|
|
1047
1659
|
|
|
1048
1660
|
const message = substitutePlaceholders(step.message, resultsById);
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1661
|
+
const r = await runStepWithRetry({
|
|
1662
|
+
step,
|
|
1663
|
+
message,
|
|
1664
|
+
cwd,
|
|
1665
|
+
llmOptions,
|
|
1666
|
+
taskSemaphore,
|
|
1667
|
+
runTask,
|
|
1668
|
+
recordId,
|
|
1669
|
+
onTaskStart: onStepStart,
|
|
1670
|
+
admitted,
|
|
1671
|
+
});
|
|
1672
|
+
const o = outcomeFromRetry(recordId, r, admitted);
|
|
1052
1673
|
resultsById.set(recordId, o);
|
|
1053
|
-
|
|
1674
|
+
notifyWorkflowCallback(onStepComplete, o, admitted, "step complete");
|
|
1054
1675
|
return { outcomes: [o], failed: o.status !== "completed" };
|
|
1055
1676
|
}
|
|
1056
1677
|
|
|
@@ -1071,6 +1692,9 @@ export async function runPipeline({
|
|
|
1071
1692
|
llmOptions = {},
|
|
1072
1693
|
onStepStart,
|
|
1073
1694
|
onStepComplete,
|
|
1695
|
+
taskSemaphore,
|
|
1696
|
+
runTask = _deps.runTask,
|
|
1697
|
+
admitted = false,
|
|
1074
1698
|
}) {
|
|
1075
1699
|
const limit = Math.max(1, Math.floor(maxParallel) || 1);
|
|
1076
1700
|
const remainingDeps = new Map(
|
|
@@ -1087,8 +1711,9 @@ export async function runPipeline({
|
|
|
1087
1711
|
let anyFailure = false;
|
|
1088
1712
|
let halted = false;
|
|
1089
1713
|
let active = 0;
|
|
1714
|
+
let fatalError = null;
|
|
1090
1715
|
|
|
1091
|
-
await new Promise((resolve) => {
|
|
1716
|
+
await new Promise((resolve, reject) => {
|
|
1092
1717
|
function pump() {
|
|
1093
1718
|
if (!halted) {
|
|
1094
1719
|
for (const s of steps) {
|
|
@@ -1101,6 +1726,10 @@ export async function runPipeline({
|
|
|
1101
1726
|
}
|
|
1102
1727
|
}
|
|
1103
1728
|
if (active === 0 && (halted || scheduled.size === steps.length)) {
|
|
1729
|
+
if (fatalError) {
|
|
1730
|
+
reject(fatalError);
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1104
1733
|
for (const s of steps) {
|
|
1105
1734
|
if (scheduled.has(s.id)) continue;
|
|
1106
1735
|
const o = {
|
|
@@ -1126,13 +1755,23 @@ export async function runPipeline({
|
|
|
1126
1755
|
llmOptions,
|
|
1127
1756
|
onStepStart,
|
|
1128
1757
|
onStepComplete,
|
|
1758
|
+
taskSemaphore,
|
|
1759
|
+
runTask,
|
|
1760
|
+
admitted,
|
|
1129
1761
|
});
|
|
1130
1762
|
} catch (err) {
|
|
1763
|
+
if (err?.code === COWORK_WORKFLOW_RUN_RESULT_INVALID_CODE) {
|
|
1764
|
+
fatalError = err;
|
|
1765
|
+
halted = true;
|
|
1766
|
+
active--;
|
|
1767
|
+
pump();
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1131
1770
|
const o = {
|
|
1132
1771
|
id: step.id,
|
|
1133
1772
|
status: "failed",
|
|
1134
1773
|
taskId: null,
|
|
1135
|
-
result: { summary: `Step threw: ${err
|
|
1774
|
+
result: { summary: `Step threw: ${safeRunErrorMessage(err)}` },
|
|
1136
1775
|
};
|
|
1137
1776
|
resultsById.set(step.id, o);
|
|
1138
1777
|
res = { outcomes: [o], failed: true };
|
|
@@ -1157,6 +1796,98 @@ export async function runPipeline({
|
|
|
1157
1796
|
|
|
1158
1797
|
// ─── Execution ───────────────────────────────────────────────────────────────
|
|
1159
1798
|
|
|
1799
|
+
function normalizeAdmittedRunRecord(
|
|
1800
|
+
value,
|
|
1801
|
+
runtimeWorkflow,
|
|
1802
|
+
definitionAuthority,
|
|
1803
|
+
runAdmission,
|
|
1804
|
+
) {
|
|
1805
|
+
const record = snapshotAdmittedCanonicalValue(value, "workflow run record");
|
|
1806
|
+
const expectedFields = new Set([
|
|
1807
|
+
"schema",
|
|
1808
|
+
"workflowId",
|
|
1809
|
+
"workflowName",
|
|
1810
|
+
"definitionSchema",
|
|
1811
|
+
"definitionDigest",
|
|
1812
|
+
"runAdmission",
|
|
1813
|
+
"status",
|
|
1814
|
+
"steps",
|
|
1815
|
+
"startedAt",
|
|
1816
|
+
"finishedAt",
|
|
1817
|
+
]);
|
|
1818
|
+
if (
|
|
1819
|
+
!record ||
|
|
1820
|
+
typeof record !== "object" ||
|
|
1821
|
+
Array.isArray(record) ||
|
|
1822
|
+
Reflect.ownKeys(record).length !== expectedFields.size ||
|
|
1823
|
+
Reflect.ownKeys(record).some(
|
|
1824
|
+
(field) => typeof field !== "string" || !expectedFields.has(field),
|
|
1825
|
+
) ||
|
|
1826
|
+
record.schema !== COWORK_WORKFLOW_RUN_RECORD_SCHEMA ||
|
|
1827
|
+
record.workflowId !== runtimeWorkflow.id ||
|
|
1828
|
+
record.workflowName !== runtimeWorkflow.name ||
|
|
1829
|
+
record.definitionSchema !== definitionAuthority.schema ||
|
|
1830
|
+
record.definitionDigest !== definitionAuthority.definitionDigest ||
|
|
1831
|
+
!["completed", "failed", "partial"].includes(record.status) ||
|
|
1832
|
+
!Array.isArray(record.steps) ||
|
|
1833
|
+
record.steps.length === 0 ||
|
|
1834
|
+
record.steps.length > MAX_ADMITTED_RUN_STEPS ||
|
|
1835
|
+
typeof record.startedAt !== "string" ||
|
|
1836
|
+
typeof record.finishedAt !== "string" ||
|
|
1837
|
+
!Number.isFinite(Date.parse(record.startedAt)) ||
|
|
1838
|
+
!Number.isFinite(Date.parse(record.finishedAt)) ||
|
|
1839
|
+
new Date(record.startedAt).toISOString() !== record.startedAt ||
|
|
1840
|
+
new Date(record.finishedAt).toISOString() !== record.finishedAt ||
|
|
1841
|
+
Date.parse(record.finishedAt) < Date.parse(record.startedAt) ||
|
|
1842
|
+
canonicalJson(record.runAdmission, "recordRunAdmission") !==
|
|
1843
|
+
canonicalJson(runAdmission, "admittedRunAdmission")
|
|
1844
|
+
) {
|
|
1845
|
+
throw runResultError("workflow run record has an invalid schema");
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
const outcomeIds = new Set();
|
|
1849
|
+
for (const [index, outcome] of record.steps.entries()) {
|
|
1850
|
+
normalizeAdmittedOutcome(outcome, `workflow run record step ${index}`);
|
|
1851
|
+
if (outcomeIds.has(outcome.id)) {
|
|
1852
|
+
throw runResultError("workflow run record contains duplicate step ids");
|
|
1853
|
+
}
|
|
1854
|
+
outcomeIds.add(outcome.id);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
for (const step of runtimeWorkflow.steps) {
|
|
1858
|
+
if (step.forEach === undefined) {
|
|
1859
|
+
if (!outcomeIds.has(step.id)) {
|
|
1860
|
+
throw runResultError("workflow run record is missing a step outcome");
|
|
1861
|
+
}
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
const childPrefix = `${step.id}[`;
|
|
1865
|
+
if (
|
|
1866
|
+
!outcomeIds.has(step.id) &&
|
|
1867
|
+
![...outcomeIds].some(
|
|
1868
|
+
(outcomeId) =>
|
|
1869
|
+
outcomeId.startsWith(childPrefix) && outcomeId.endsWith("]"),
|
|
1870
|
+
)
|
|
1871
|
+
) {
|
|
1872
|
+
throw runResultError("workflow run record is missing a forEach outcome");
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
const expectedStatus = record.steps.every(
|
|
1877
|
+
(outcome) => outcome.status === "completed",
|
|
1878
|
+
)
|
|
1879
|
+
? "completed"
|
|
1880
|
+
: record.steps.some((outcome) => outcome.status === "completed")
|
|
1881
|
+
? "partial"
|
|
1882
|
+
: "failed";
|
|
1883
|
+
if (record.status !== expectedStatus) {
|
|
1884
|
+
throw runResultError(
|
|
1885
|
+
"workflow run record status does not match its step outcomes",
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
return record;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1160
1891
|
/**
|
|
1161
1892
|
* Execute a workflow. The runner for individual tasks must be injected via
|
|
1162
1893
|
* `_deps.runTask` (signature matches `runCoworkTask`).
|
|
@@ -1186,6 +1917,9 @@ export async function runPipeline({
|
|
|
1186
1917
|
* @param {object} [options.llmOptions] - Forwarded to each task
|
|
1187
1918
|
* @param {function} [options.onStepStart]
|
|
1188
1919
|
* @param {function} [options.onStepComplete]
|
|
1920
|
+
* @param {object} [options.runAdmission] - Secret-free, digest-bound dynamic
|
|
1921
|
+
* workflow admission. Production entry points require this even though the
|
|
1922
|
+
* low-level executor keeps it optional for existing library callers.
|
|
1189
1923
|
* @returns {Promise<{
|
|
1190
1924
|
* workflowId: string,
|
|
1191
1925
|
* status: "completed"|"failed"|"partial",
|
|
@@ -1205,11 +1939,22 @@ export async function executeWorkflow(options = {}) {
|
|
|
1205
1939
|
onStepStart,
|
|
1206
1940
|
onStepComplete,
|
|
1207
1941
|
pipeline,
|
|
1942
|
+
runAdmission,
|
|
1943
|
+
runTask: requestedRunTask,
|
|
1208
1944
|
} = options;
|
|
1209
1945
|
|
|
1210
|
-
const { valid, errors } = validateWorkflow(workflow);
|
|
1211
|
-
if (!valid) throw new Error(`Invalid workflow: ${errors.join("; ")}`);
|
|
1212
1946
|
const definitionAuthority = createWorkflowDefinitionAuthority(workflow);
|
|
1947
|
+
const runtimeWorkflow = definitionAuthority.definition;
|
|
1948
|
+
const { valid, errors } = validateWorkflow(runtimeWorkflow);
|
|
1949
|
+
if (!valid) throw new Error(`Invalid workflow: ${errors.join("; ")}`);
|
|
1950
|
+
const numericMaxParallel = Number(maxParallel);
|
|
1951
|
+
const concurrencyLimit =
|
|
1952
|
+
Number.isSafeInteger(numericMaxParallel) && numericMaxParallel > 0
|
|
1953
|
+
? numericMaxParallel
|
|
1954
|
+
: 1;
|
|
1955
|
+
const usePipeline = pipeline ?? runtimeWorkflow.pipeline ?? false;
|
|
1956
|
+
const runtimeLlmOptions =
|
|
1957
|
+
runAdmission == null ? llmOptions : normalizeAdmittedLlmOptions(llmOptions);
|
|
1213
1958
|
if (requestedDefinitionDigest != null) {
|
|
1214
1959
|
const normalizedDigest = normalizeWorkflowDefinitionDigest(
|
|
1215
1960
|
requestedDefinitionDigest,
|
|
@@ -1221,13 +1966,28 @@ export async function executeWorkflow(options = {}) {
|
|
|
1221
1966
|
throw new Error("workflow definition digest mismatch");
|
|
1222
1967
|
}
|
|
1223
1968
|
}
|
|
1224
|
-
|
|
1969
|
+
const verifiedRunAdmission =
|
|
1970
|
+
runAdmission == null
|
|
1971
|
+
? null
|
|
1972
|
+
: normalizeWorkflowRunAdmission(
|
|
1973
|
+
runAdmission,
|
|
1974
|
+
definitionAuthority,
|
|
1975
|
+
concurrencyLimit,
|
|
1976
|
+
{
|
|
1977
|
+
cwd,
|
|
1978
|
+
continueOnError,
|
|
1979
|
+
pipeline: usePipeline,
|
|
1980
|
+
llmOptions: runtimeLlmOptions,
|
|
1981
|
+
},
|
|
1982
|
+
);
|
|
1983
|
+
const runtimeRunTask = requestedRunTask ?? _deps.runTask;
|
|
1984
|
+
if (typeof runtimeRunTask !== "function") {
|
|
1225
1985
|
throw new Error(
|
|
1226
|
-
"cowork-workflow:
|
|
1986
|
+
"cowork-workflow: runTask is not available (wire runCoworkTask before executing)",
|
|
1227
1987
|
);
|
|
1228
1988
|
}
|
|
1229
1989
|
|
|
1230
|
-
const
|
|
1990
|
+
const taskSemaphore = createWorkflowTaskSemaphore(concurrencyLimit);
|
|
1231
1991
|
const resultsById = new Map();
|
|
1232
1992
|
const startedAt = new Date(_deps.now()).toISOString();
|
|
1233
1993
|
let stepOutcomes;
|
|
@@ -1235,24 +1995,27 @@ export async function executeWorkflow(options = {}) {
|
|
|
1235
1995
|
|
|
1236
1996
|
if (usePipeline) {
|
|
1237
1997
|
({ stepOutcomes, anyFailure } = await runPipeline({
|
|
1238
|
-
steps:
|
|
1998
|
+
steps: runtimeWorkflow.steps,
|
|
1239
1999
|
resultsById,
|
|
1240
|
-
maxParallel,
|
|
2000
|
+
maxParallel: concurrencyLimit,
|
|
1241
2001
|
continueOnError,
|
|
1242
2002
|
cwd,
|
|
1243
|
-
llmOptions,
|
|
2003
|
+
llmOptions: runtimeLlmOptions,
|
|
1244
2004
|
onStepStart,
|
|
1245
2005
|
onStepComplete,
|
|
2006
|
+
taskSemaphore,
|
|
2007
|
+
runTask: runtimeRunTask,
|
|
2008
|
+
admitted: verifiedRunAdmission != null,
|
|
1246
2009
|
}));
|
|
1247
2010
|
} else {
|
|
1248
2011
|
stepOutcomes = [];
|
|
1249
2012
|
anyFailure = false;
|
|
1250
|
-
const batches = planBatches(
|
|
2013
|
+
const batches = planBatches(runtimeWorkflow.steps);
|
|
1251
2014
|
for (const batch of batches) {
|
|
1252
2015
|
// Respect maxParallel by slicing batch into chunks
|
|
1253
2016
|
const chunks = [];
|
|
1254
|
-
for (let i = 0; i < batch.length; i +=
|
|
1255
|
-
chunks.push(batch.slice(i, i +
|
|
2017
|
+
for (let i = 0; i < batch.length; i += concurrencyLimit) {
|
|
2018
|
+
chunks.push(batch.slice(i, i + concurrencyLimit));
|
|
1256
2019
|
}
|
|
1257
2020
|
|
|
1258
2021
|
for (const chunk of chunks) {
|
|
@@ -1357,33 +2120,55 @@ export async function executeWorkflow(options = {}) {
|
|
|
1357
2120
|
|
|
1358
2121
|
const promises = runnable.map(
|
|
1359
2122
|
async ({ step, message, recordId, isLoop }) => {
|
|
1360
|
-
if (onStepStart) onStepStart({ stepId: recordId, message });
|
|
1361
2123
|
let outcome;
|
|
1362
2124
|
if (isLoop) {
|
|
1363
2125
|
outcome = await runLoopStep({
|
|
1364
2126
|
step,
|
|
1365
2127
|
recordId,
|
|
1366
2128
|
cwd,
|
|
1367
|
-
llmOptions,
|
|
2129
|
+
llmOptions: runtimeLlmOptions,
|
|
1368
2130
|
resultsById,
|
|
2131
|
+
taskSemaphore,
|
|
2132
|
+
runTask: runtimeRunTask,
|
|
2133
|
+
onStepStart,
|
|
2134
|
+
admitted: verifiedRunAdmission != null,
|
|
1369
2135
|
});
|
|
1370
2136
|
} else {
|
|
1371
2137
|
const r = await runStepWithRetry({
|
|
1372
2138
|
step,
|
|
1373
2139
|
message,
|
|
1374
2140
|
cwd,
|
|
1375
|
-
llmOptions,
|
|
2141
|
+
llmOptions: runtimeLlmOptions,
|
|
2142
|
+
taskSemaphore,
|
|
2143
|
+
runTask: runtimeRunTask,
|
|
2144
|
+
recordId,
|
|
2145
|
+
onTaskStart: onStepStart,
|
|
2146
|
+
admitted: verifiedRunAdmission != null,
|
|
1376
2147
|
});
|
|
1377
|
-
outcome = outcomeFromRetry(
|
|
2148
|
+
outcome = outcomeFromRetry(
|
|
2149
|
+
recordId,
|
|
2150
|
+
r,
|
|
2151
|
+
verifiedRunAdmission != null,
|
|
2152
|
+
);
|
|
1378
2153
|
}
|
|
1379
2154
|
if (outcome.status !== "completed") anyFailure = true;
|
|
1380
2155
|
resultsById.set(recordId, outcome);
|
|
1381
|
-
|
|
2156
|
+
notifyWorkflowCallback(
|
|
2157
|
+
onStepComplete,
|
|
2158
|
+
outcome,
|
|
2159
|
+
verifiedRunAdmission != null,
|
|
2160
|
+
"step complete",
|
|
2161
|
+
);
|
|
1382
2162
|
return outcome;
|
|
1383
2163
|
},
|
|
1384
2164
|
);
|
|
1385
2165
|
|
|
1386
|
-
const
|
|
2166
|
+
const settledResults = await Promise.allSettled(promises);
|
|
2167
|
+
const rejected = settledResults.find(
|
|
2168
|
+
(settled) => settled.status === "rejected",
|
|
2169
|
+
);
|
|
2170
|
+
if (rejected) throw rejected.reason;
|
|
2171
|
+
const results = settledResults.map((settled) => settled.value);
|
|
1387
2172
|
stepOutcomes.push(...preOutcomes, ...results);
|
|
1388
2173
|
|
|
1389
2174
|
// Aggregate forEach children into a parent entry so downstream
|
|
@@ -1410,8 +2195,6 @@ export async function executeWorkflow(options = {}) {
|
|
|
1410
2195
|
});
|
|
1411
2196
|
}
|
|
1412
2197
|
}
|
|
1413
|
-
|
|
1414
|
-
if (anyFailure && !continueOnError) break;
|
|
1415
2198
|
}
|
|
1416
2199
|
}
|
|
1417
2200
|
|
|
@@ -1425,17 +2208,26 @@ export async function executeWorkflow(options = {}) {
|
|
|
1425
2208
|
|
|
1426
2209
|
const record = {
|
|
1427
2210
|
schema: COWORK_WORKFLOW_RUN_RECORD_SCHEMA,
|
|
1428
|
-
workflowId:
|
|
1429
|
-
workflowName:
|
|
2211
|
+
workflowId: runtimeWorkflow.id,
|
|
2212
|
+
workflowName: runtimeWorkflow.name,
|
|
1430
2213
|
definitionSchema: definitionAuthority.schema,
|
|
1431
2214
|
definitionDigest: definitionAuthority.definitionDigest,
|
|
2215
|
+
...(verifiedRunAdmission ? { runAdmission: verifiedRunAdmission } : {}),
|
|
1432
2216
|
status,
|
|
1433
2217
|
steps: stepOutcomes,
|
|
1434
2218
|
startedAt,
|
|
1435
2219
|
finishedAt,
|
|
1436
2220
|
};
|
|
1437
|
-
|
|
1438
|
-
|
|
2221
|
+
const finalRecord = verifiedRunAdmission
|
|
2222
|
+
? normalizeAdmittedRunRecord(
|
|
2223
|
+
record,
|
|
2224
|
+
runtimeWorkflow,
|
|
2225
|
+
definitionAuthority,
|
|
2226
|
+
verifiedRunAdmission,
|
|
2227
|
+
)
|
|
2228
|
+
: record;
|
|
2229
|
+
_appendHistory(cwd, finalRecord);
|
|
2230
|
+
return finalRecord;
|
|
1439
2231
|
}
|
|
1440
2232
|
|
|
1441
2233
|
function _appendHistory(cwd, record) {
|