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.
Files changed (189) hide show
  1. package/package.json +4 -4
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-CYxFDrcm.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-CPBtdiU2.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-Dayq7Dp7.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-6U7bPb2s.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-D2Iqg_3D.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-CdoOB7WV.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-DrnAVB8w.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-CesMi5cy.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-DaX9ptax.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-OrwOPxDS.js} +6 -6
  13. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CvPZFRxM.js} +1 -1
  14. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-DiHbcysC.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-CbgCkaBJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-Du5hgrh4.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-DsPOBW-p.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-BwHIFkNC.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BvrDQSgd.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-gwAE6arx.js} +2 -2
  21. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DW-0l4uF.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-j-NsLesB.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-CibVEBOI.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-W4z9vSfe.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-DAcrkKxG.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-6GXah-89.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-TKDCq8Ku.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-BXBLlcNm.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-BoJJBkG3.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-okjsl46v.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Rvs7i6nn.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-CHSUxXXS.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-V3BlfxsB.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-C_NlD4aR.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-Nn4LNxhA.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-GmB6DOr6.js → MarkdownRenderer-SVAgNnkt.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-DEmjFcg-.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-DlopXipI.js} +3 -3
  39. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-BG3ynX60.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-DeRzWOfT.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-Tyd3o2A0.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-BoPbOv1C.js} +2 -2
  43. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-un-rTHp7.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-Ds0oveEB.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-D09JI-yY.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-DTRhiKJn.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BAnnNCim.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-Yr-1fSYi.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-S9EAolpj.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-CqcLKqJ8.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-Dw0F9EcU.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BDRwUevN.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-D3_c3nKB.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-CxdMV98x.js} +4 -4
  55. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-CgjNQFLI.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-BHghIIht.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-De6pqaKM.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-Bg4ZkNYQ.js} +2 -2
  59. package/src/assets/web-panel/assets/{Projects-DDAjkUkT.js → Projects-CP8vPvzs.js} +1 -1
  60. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CUnHbNf-.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-DA2f3Wts.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C1FeHDqp.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-B5guaUMN.js} +1 -1
  64. package/src/assets/web-panel/assets/RemoteSession-PDADSds3.js +5 -0
  65. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-eTlRhgvK.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-D7AqSGY0.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-JiuLm4Ja.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-CwruahW3.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-CdhqaR0b.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CU0ZIlcT.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-CtduPfAb.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-CROXm6AY.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-DiZiABFD.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-D6UL3dA5.js → SpeechSettings-B5pLcXUw.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-DBX6J1Oe.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-BLyRJd3-.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-BKa5qzmk.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-DnD3a4M6.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-DEitQeTb.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-CjHggTDq.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-CsGT7sfl.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-yr4rFDrW.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-CLvu1Sjp.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-B0m7yUP8.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-C0VvP4lc.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-kllBAG1M.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-C54UQNLx.js} +4 -4
  88. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-B7y8qSvU.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DRzTh8CJ.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-BSMwSnCx.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTg602H5.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-B6EGsdvp.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Dol8jLEP.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-DDKqJ8RD.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-323Hhxxt.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-BICn3Qh1.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BMuGd2--.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-BRqt4fO-.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BVTAl5N2.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-BsR7Y2NG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-Bx044qDj.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-C1GFfuqN.js} +1 -1
  103. package/src/assets/web-panel/assets/index-C3QX5tOl.js +1 -0
  104. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-C66NpWy5.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-CDTvsX8t.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-CGNklbP6.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-CHfLYwcW.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-CNxg61YP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CQlHFN_g.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-CUEU48o1.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-CWGax2QW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-C_JzIXUq.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-Cf23wVMs.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-CmAInUkX.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-CmAubjDi.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-Co7oM1qr.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CwPUZbMI.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-D1naNj4U.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-D5CsBShd.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DBT3YeQ9.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-DLdrHhML.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-Dc77VR3H.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-Dfv2lKu3.js} +1 -1
  124. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-DiW0sDkz.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-DpTHIRfZ.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DqFXGzzC.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-XFe-1twF.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-p2HYn89m.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-pqfux8vZ.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-rjzk9z1Q.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-th2ihDc0.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BtobShro.js → index-u2YVld9W.js} +1 -1
  133. package/src/assets/web-panel/assets/index-xYybvEic.js +1 -0
  134. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-C3Djh9fa.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-BzqFmbSe.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-DJo_FFaW.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-_tlTiBvk.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-CB2ZyxUg.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DZwIFD4u.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-DxZY0dgk.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-CzzGt7GL.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-BaIK52yz.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-3-FHu9_M.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-BkRT68fK.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DHYwKS-s.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-DVVUhV8p.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-CkrFKAzQ.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-BuBLFz0c.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-kpm6LtXU.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/cowork.js +283 -61
  152. package/src/data/changelog.json +15 -2
  153. package/src/gateways/ws/action-protocol.js +293 -19
  154. package/src/gateways/ws/message-dispatcher.js +30 -0
  155. package/src/gateways/ws/remote-session-protocol.js +542 -35
  156. package/src/gateways/ws/ws-approval-gate.js +5 -0
  157. package/src/gateways/ws/ws-server.js +462 -38
  158. package/src/harness/remote-session-audit.js +1 -0
  159. package/src/harness/remote-session-crypto.js +750 -50
  160. package/src/harness/remote-session-registry.js +795 -27
  161. package/src/lib/auto-mode-config.js +98 -3
  162. package/src/lib/background-agent-keeper-protocol.js +154 -10
  163. package/src/lib/background-agent-supervisor.js +840 -48
  164. package/src/lib/background-session-transport.js +23 -6
  165. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  166. package/src/lib/cowork-workflow.js +853 -61
  167. package/src/lib/delivery-production-adapter.js +38 -2
  168. package/src/lib/dynamic-workflow-facade.js +1105 -17
  169. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  170. package/src/lib/remote-approval-bridge.js +745 -108
  171. package/src/lib/remote-control.js +2 -0
  172. package/src/lib/remote-membership-coordinator.js +604 -40
  173. package/src/lib/remote-membership-host-store.js +59 -10
  174. package/src/lib/runtime-provenance-ledger.js +171 -46
  175. package/src/lib/shell-approval.js +169 -6
  176. package/src/lib/with-file-lock.js +73 -38
  177. package/src/repl/agent-repl.js +70 -29
  178. package/src/repl/remote-approval.js +29 -7
  179. package/src/runtime/agent-core.js +1099 -152
  180. package/src/runtime/headless-runner.js +22 -5
  181. package/src/runtime/headless-stream.js +19 -3
  182. package/src/workers/background-agent-keeper.js +249 -58
  183. package/src/workers/background-agent-worker.js +7 -0
  184. package/src/workers/background-turn-bootstrap.js +29 -2
  185. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  186. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  187. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  188. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  189. 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
- reject(new Error(`step timed out after ${timeoutMs}ms`));
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({ step, message, cwd, llmOptions }) {
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
- const entry = await withTimeout(
779
- () =>
780
- _deps.runTask({
781
- templateId: step.templateId || null,
782
- userMessage: message,
783
- files: step.files || [],
784
- cwd,
785
- llmOptions,
786
- }),
787
- timeoutMs,
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
- return {
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({ step, message, cwd, llmOptions });
918
- last = outcomeFromRetry(recordId, r);
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
- return {
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 { resultsById, cwd, llmOptions, onStepStart, onStepComplete } = ctx;
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
- if (onStepComplete) onStepComplete(o);
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
- if (onStepComplete) onStepComplete(co);
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
- if (onStepStart) onStepStart({ stepId: recordId, message });
1050
- const r = await runStepWithRetry({ step, message, cwd, llmOptions });
1051
- const o = outcomeFromRetry(recordId, r);
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
- if (onStepComplete) onStepComplete(o);
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.message}` },
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
- if (typeof _deps.runTask !== "function") {
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: _deps.runTask is not injected (wire runCoworkTask in CLI before executing)",
1986
+ "cowork-workflow: runTask is not available (wire runCoworkTask before executing)",
1227
1987
  );
1228
1988
  }
1229
1989
 
1230
- const usePipeline = pipeline ?? workflow.pipeline ?? false;
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: workflow.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(workflow.steps);
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 += maxParallel) {
1255
- chunks.push(batch.slice(i, i + maxParallel));
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(recordId, r);
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
- if (onStepComplete) onStepComplete(outcome);
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 results = await Promise.all(promises);
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: workflow.id,
1429
- workflowName: workflow.name,
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
- _appendHistory(cwd, record);
1438
- return record;
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) {