chainlesschain 0.164.0 → 0.165.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/README.md +248 -2
  2. package/package.json +7 -4
  3. package/src/assets/web-panel/.build-hash +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-D2MEn7fI.js → AIOps-lS1R0Cza.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-lCW_4Gf0.js → ActionButton-BmlgBPsj.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-Bs7i-nsS.js → Analytics-CNKczv1S.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-CK7B6S_j.js → AppLayout-BuZGJARQ.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-C8ILjpyo.js → Artifacts-Dm73Sl-O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-Bb9cNaWc.js → Audit-BGMW7A5e.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-sC9ooXIf.js → BackgroundAgents-BQ6M54JR.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D-DrsplT.js → Backup-DBzGP0bR.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-uCNZpwSK.js → BaseInput-D1mLCoOV.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-CsDK1uE9.js → Chat-BUfcN7p8.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-OqSDt50w.js → ChatBubbleRenderer-CY5m8ZHg.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-JhTXM1-K.js → Checkbox-Du0Yg5Pi.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-BbzZLnyD.js → Codegen-B2RXziHW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-D4VW4m4j.js → Col-DWhekOrR.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-BXrYawxV.js → Community-HQhrkon4.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-DCihGy1u.js → Compact-izHNb4uz.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-DlyZ-lk8.js → Compliance-BDwLy41-.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DvGZrhbO.js → Cowork-DFJhLa7K.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-DUncZi8h.js → Cron-DYY9RG7-.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-C_hLuUcm.js → Crosschain-rr7tJTtv.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-b6YSUqxr.js → DID-D2sfHRLi.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-uvM15Cfb.js → Dashboard-q_eGdhha.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-D1-Nd4qY.js → Dropdown-Cufrh2Bz.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-8ApvL6Zc.js → EmailListRenderer-B-kdv10f.js} +1 -1
  28. package/src/assets/web-panel/assets/{FamilyGuardDashboard-LYJt8811.js → FamilyGuardDashboard-D21XjgfG.js} +1 -1
  29. package/src/assets/web-panel/assets/{Federation-BbI3HGpv.js → Federation-CAwRTLZH.js} +1 -1
  30. package/src/assets/web-panel/assets/{FormItemContext-DSoEKuFu.js → FormItemContext-vx3daNDF.js} +1 -1
  31. package/src/assets/web-panel/assets/{GenericCardRenderer-B0S9OWd5.js → GenericCardRenderer-CRWY2wU4.js} +1 -1
  32. package/src/assets/web-panel/assets/{Git-DIs9Zhf3.js → Git-Q6SwauyD.js} +2 -2
  33. package/src/assets/web-panel/assets/{Governance-jzZXrxcl.js → Governance-BJ1q4IQZ.js} +1 -1
  34. package/src/assets/web-panel/assets/{Inference-JUPjf2ZI.js → Inference-CSWjROgD.js} +1 -1
  35. package/src/assets/web-panel/assets/{KnowledgeGraph-DxnfJvlP.js → KnowledgeGraph-fzuQsWuY.js} +1 -1
  36. package/src/assets/web-panel/assets/{Logs-iXiz8BEE.js → Logs-DVF8Mnns.js} +2 -2
  37. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +1 -0
  38. package/src/assets/web-panel/assets/{Marketplace-Y3Xch312.js → Marketplace-BQP7LOA4.js} +1 -1
  39. package/src/assets/web-panel/assets/{McpTools-D7kGGbO7.js → McpTools-BNlJCmNc.js} +4 -4
  40. package/src/assets/web-panel/assets/{Memory-Do_hVxzh.js → Memory-DLLg5WQ4.js} +2 -2
  41. package/src/assets/web-panel/assets/{MobileBridge-CtrR0Tas.js → MobileBridge-CR2skNAF.js} +1 -1
  42. package/src/assets/web-panel/assets/MobileProjects-BxVavObl.js +1 -0
  43. package/src/assets/web-panel/assets/{Mtc-qo5QCTGd.js → Mtc-B17pgqMI.js} +4 -4
  44. package/src/assets/web-panel/assets/{MtcAudit-cq5mnjYj.js → MtcAudit-4UmV5cIL.js} +2 -2
  45. package/src/assets/web-panel/assets/{Multisig-Bq0MAQHw.js → Multisig-DoMEXEcU.js} +3 -3
  46. package/src/assets/web-panel/assets/{NLProgramming-DdWE6Vi2.js → NLProgramming-h9DMXFX4.js} +1 -1
  47. package/src/assets/web-panel/assets/{Notes-BxMBFcIp.js → Notes-Cihn-xX8.js} +3 -3
  48. package/src/assets/web-panel/assets/{NotificationSettings-RTrAcbUe.js → NotificationSettings-BE6ZGa3R.js} +1 -1
  49. package/src/assets/web-panel/assets/{OrderTableRenderer-DJbpGqzT.js → OrderTableRenderer-CnwojoSI.js} +1 -1
  50. package/src/assets/web-panel/assets/{Organization-SQzIPdqC.js → Organization-B2vpwmjb.js} +4 -4
  51. package/src/assets/web-panel/assets/{Overflow-02yECh22.js → Overflow-C4z28R0-.js} +1 -1
  52. package/src/assets/web-panel/assets/{P2P-BNg4pNCM.js → P2P-BVlFkwhW.js} +2 -2
  53. package/src/assets/web-panel/assets/{PdhVaultBrowser-BxPJZsD3.js → PdhVaultBrowser-BXbEdL5B.js} +3 -3
  54. package/src/assets/web-panel/assets/{Permissions-CLnDnPAz.js → Permissions-C28xs4lp.js} +4 -4
  55. package/src/assets/web-panel/assets/{PersonalDataHub-C5m0AjGf.js → PersonalDataHub-Dgl_rBjM.js} +4 -4
  56. package/src/assets/web-panel/assets/{Pipeline-VwOf5Dhv.js → Pipeline-3QUIhrKI.js} +1 -1
  57. package/src/assets/web-panel/assets/{Privacy-CC0W2hZd.js → Privacy-8Gh33OZ5.js} +1 -1
  58. package/src/assets/web-panel/assets/{ProjectInit-Dz893Bob.js → ProjectInit-BNmg4tZi.js} +2 -2
  59. package/src/assets/web-panel/assets/{ProjectSettings-CxQkvl0k.js → ProjectSettings-vgMfeCf9.js} +2 -2
  60. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +1 -0
  61. package/src/assets/web-panel/assets/{Providers-DeB9IkxK.js → Providers-CXoOa8eX.js} +1 -1
  62. package/src/assets/web-panel/assets/{QrScannerModal-OCeoaswn.js → QrScannerModal-BhfTF0oS.js} +1 -1
  63. package/src/assets/web-panel/assets/{QuickAsk-DenotaAj.js → QuickAsk-C746HEAj.js} +1 -1
  64. package/src/assets/web-panel/assets/{Recommend-Cies_jjI.js → Recommend-BSej8Eo0.js} +1 -1
  65. package/src/assets/web-panel/assets/RemoteSession-f3gmnlG3.js +5 -0
  66. package/src/assets/web-panel/assets/{Reputation-CW0mvxgZ.js → Reputation-CyvybpkN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Row-SEd32qZU.js → Row-DwV2PeYn.js} +1 -1
  68. package/src/assets/web-panel/assets/{RssFeed-BSwnJ3Lm.js → RssFeed-11GrzjZy.js} +3 -3
  69. package/src/assets/web-panel/assets/{Search-xe2m8bkC.js → Search-BjKXkrAY.js} +1 -1
  70. package/src/assets/web-panel/assets/{Security-Mq2F9Nro.js → Security-eWiFb9Yk.js} +4 -4
  71. package/src/assets/web-panel/assets/{Services-BKGw2V3P.js → Services-CdCsMAtt.js} +2 -2
  72. package/src/assets/web-panel/assets/{Skeleton-DW95-Rxf.js → Skeleton-4Q2UTKQ5.js} +1 -1
  73. package/src/assets/web-panel/assets/{Skills-CHgChCv_.js → Skills-v4rtYrRj.js} +1 -1
  74. package/src/assets/web-panel/assets/{Sla-tKh6k3R7.js → Sla-CRhsFvd_.js} +1 -1
  75. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +1 -0
  76. package/src/assets/web-panel/assets/{SyncSettings-D0Fw8fVL.js → SyncSettings-D16zonl7.js} +2 -2
  77. package/src/assets/web-panel/assets/{Tasks-20x1GB3D.js → Tasks-Xxozz0i_.js} +1 -1
  78. package/src/assets/web-panel/assets/{Templates-DJ-lts-a.js → Templates-CXJpPtCT.js} +1 -1
  79. package/src/assets/web-panel/assets/{Tenant-BFdGpbs7.js → Tenant-XRV0L1ZF.js} +1 -1
  80. package/src/assets/web-panel/assets/{Terminal-Xpx61vHL.js → Terminal-CYuO1i_w.js} +2 -2
  81. package/src/assets/web-panel/assets/{TimelineRenderer-C6UIvyZQ.js → TimelineRenderer-BXqitWsu.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tokens-Dellq08f.js → Tokens-BvPrDHIy.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trigger-VKw8d0J-.js → Trigger-DhCrSTf5.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trust-DNftdKT5.js → Trust-DmsuZD6K.js} +1 -1
  85. package/src/assets/web-panel/assets/{UkeySign-DX9VAKJI.js → UkeySign-D_yrts5C.js} +1 -1
  86. package/src/assets/web-panel/assets/{VideoEditing-B2h3jO8q.js → VideoEditing-B01ihPrm.js} +1 -1
  87. package/src/assets/web-panel/assets/{Wallet-CjGkof7f.js → Wallet-DpUjKa-N.js} +4 -4
  88. package/src/assets/web-panel/assets/{WebAuthn-2Zfcu55P.js → WebAuthn-CL07yON6.js} +4 -4
  89. package/src/assets/web-panel/assets/{WorkflowEditor-B9eZ8UxZ.js → WorkflowEditor-CLMflnRr.js} +1 -1
  90. package/src/assets/web-panel/assets/{chat-CQNfD-YO.js → chat-DtUqe8h_.js} +1 -1
  91. package/src/assets/web-panel/assets/{colors-D4usyFT2.js → colors-DPKrgVfy.js} +1 -1
  92. package/src/assets/web-panel/assets/{compact-item-s4TpjvjX.js → compact-item-BTt2GtOm.js} +1 -1
  93. package/src/assets/web-panel/assets/{createContext-BRCSvYxj.js → createContext-D_VeUc1J.js} +1 -1
  94. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +1 -0
  95. package/src/assets/web-panel/assets/{hasIn-AdkUbkV-.js → hasIn-1kndiylj.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BIoG5yYf.js → index-2eyAXE3d.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-nXyk8GZ9.js → index-4bna2_SR.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B2V-eKfK.js → index-8nz_fmlj.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D_XBHopw.js → index-B9KvcS7R.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-s_buFMEG.js → index-BAXfRKfG.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DZT36ZYq.js → index-BHpBMWCh.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BMcimU-2.js → index-BaZAbw9A.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DzRjHxEv.js → index-BepLZwgB.js} +1 -1
  104. package/src/assets/web-panel/assets/{index--00U6vXZ.js → index-Bhudc_Ez.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C7Y8Aluc.js → index-BlDNVgBt.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BtobShro.js → index-By5n8up9.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-Cvyt-zhm.js → index-CHG-KcS4.js} +1 -1
  108. package/src/assets/web-panel/assets/index-CPcM-eqo.js +1 -0
  109. package/src/assets/web-panel/assets/{index-MMN9V6JF.js → index-CX81qSM4.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bad4VNvz.js → index-CauoBQkH.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CCsM_Ybx.js → index-CcO04eDn.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DARsAyUp.js → index-CtF7EN49.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C5KAQFPQ.js → index-CzjkKUmR.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-RfMr79MW.js → index-D95OFht7.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DdCB70SS.js → index-DFIZ_1Sf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CYdoQsa1.js → index-DG2MZQ3f.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BPhkFZ1I.js → index-DJ8vXGOa.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-_IRVsV9X.js → index-Dbbymxpj.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DVKfQ0Ux.js → index-DdkplL9R.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CkwiSLhU.js → index-DfGl8CqG.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CJVEOa4i.js → index-DfKBRdSS.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1mJtE6q.js → index-DsJCDUJG.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BgiXR_-_.js → index-Dwqmtk65.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CJCFe2Qf.js → index-DxdTdBix.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-C484EJiS.js → index-FzdwcSy2.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B4JJCfQ2.js → index-GsoUQ2M7.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CQhAVU5t.js → index-brCInrzq.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CKC1t3Ic.js → index-dVc-CXY4.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BQ3DXBtD.js → index-fk2Rjacw.js} +3 -3
  130. package/src/assets/web-panel/assets/{index-Cduu2Hhr.js → index-l1BETrDb.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Bw2MEnUk.js → index-m31Ka8hZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D2GjI2oe.js → index-ocf06EVQ.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-CJdQrZth.js → index-sDoMHTNA.js} +1 -1
  134. package/src/assets/web-panel/assets/index-xeSyVoRe.js +1 -0
  135. package/src/assets/web-panel/assets/{initDefaultProps-Blmpk7HL.js → initDefaultProps-CvyYJd7z.js} +1 -1
  136. package/src/assets/web-panel/assets/{motion-DPBJMv1n.js → motion-DYZkhUdz.js} +1 -1
  137. package/src/assets/web-panel/assets/{move-ButdzCM7.js → move-slnJRVT1.js} +1 -1
  138. package/src/assets/web-panel/assets/{mtc-parser-cyRC_QCe.js → mtc-parser-DVc0CKmy.js} +1 -1
  139. package/src/assets/web-panel/assets/{omit-1epSV7hz.js → omit-BCXAtHCq.js} +1 -1
  140. package/src/assets/web-panel/assets/{pickAttrs-DuPY8xJs.js → pickAttrs-DBsemz_K.js} +1 -1
  141. package/src/assets/web-panel/assets/{placementArrow-6XZqarhe.js → placementArrow-BRtkJ-nv.js} +1 -1
  142. package/src/assets/web-panel/assets/{responsiveObserve-B40pXBWM.js → responsiveObserve-Cebq7EZn.js} +1 -1
  143. package/src/assets/web-panel/assets/{slide-jbZ9Wu4n.js → slide-DT7qvId2.js} +1 -1
  144. package/src/assets/web-panel/assets/{statusUtils-BKQQcVy4.js → statusUtils-pQiITLkG.js} +1 -1
  145. package/src/assets/web-panel/assets/{styleChecker-NiKfWjUT.js → styleChecker-c-19BVKQ.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFlexGapSupport-bQUCvPeb.js → useFlexGapSupport-DsS5-0bE.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFs-CUGji7ph.js → useFs-CxGiMe5n.js} +1 -1
  148. package/src/assets/web-panel/assets/{usePersonalDataHub-4veyN2N2.js → usePersonalDataHub-D9btVID5.js} +1 -1
  149. package/src/assets/web-panel/assets/{vnode-1-nLug-Z.js → vnode-CUYFsFEN.js} +1 -1
  150. package/src/assets/web-panel/assets/{zoom-dhCeRDMo.js → zoom-CXmn_35j.js} +1 -1
  151. package/src/assets/web-panel/index.html +1 -1
  152. package/src/command-help-index.json +1 -1
  153. package/src/commands/cowork.js +559 -62
  154. package/src/commands/plugin.js +819 -64
  155. package/src/commands/session-location.js +318 -7
  156. package/src/constants.js +2 -2
  157. package/src/data/changelog.json +28 -2
  158. package/src/gateways/ws/action-protocol.js +293 -19
  159. package/src/gateways/ws/message-dispatcher.js +30 -0
  160. package/src/gateways/ws/remote-session-protocol.js +542 -35
  161. package/src/gateways/ws/ws-approval-gate.js +5 -0
  162. package/src/gateways/ws/ws-server.js +462 -38
  163. package/src/harness/jsonl-session-store.js +638 -2
  164. package/src/harness/prompt-compressor.js +4 -0
  165. package/src/harness/remote-session-audit.js +1 -0
  166. package/src/harness/remote-session-crypto.js +750 -50
  167. package/src/harness/remote-session-registry.js +795 -27
  168. package/src/lib/auto-mode-config.js +98 -3
  169. package/src/lib/background-agent-keeper-client.js +29 -0
  170. package/src/lib/background-agent-keeper-protocol.js +158 -10
  171. package/src/lib/background-agent-supervisor.js +870 -48
  172. package/src/lib/background-session-transport.js +23 -6
  173. package/src/lib/background-turn-bootstrap-protocol.js +64 -0
  174. package/src/lib/cowork-task-runner.js +70 -0
  175. package/src/lib/cowork-workflow.js +887 -62
  176. package/src/lib/delivery-production-adapter.js +38 -2
  177. package/src/lib/doctor-checkup.js +34 -10
  178. package/src/lib/dynamic-workflow-draft.js +535 -0
  179. package/src/lib/dynamic-workflow-facade.js +1105 -17
  180. package/src/lib/dynamic-workflow-runtime.js +2974 -0
  181. package/src/lib/execution-location-contract.js +111 -7
  182. package/src/lib/execution-location-runtime.js +3 -1
  183. package/src/lib/execution-location-target.js +910 -0
  184. package/src/lib/llm-providers.js +1 -0
  185. package/src/lib/mcp-call-ledger-store.js +35 -0
  186. package/src/lib/mcp-call-ledger.js +30 -0
  187. package/src/lib/mcp-stdio-package-materialization.js +1 -1
  188. package/src/lib/plugin-runtime/__tests__/remote-source.test.js +34 -16
  189. package/src/lib/plugin-runtime/install.js +4956 -657
  190. package/src/lib/plugin-runtime/manifest.js +3 -1
  191. package/src/lib/plugin-runtime/marketplace-artifact-readback.js +671 -31
  192. package/src/lib/plugin-runtime/marketplace-catalog.js +68 -2
  193. package/src/lib/plugin-runtime/marketplace-impact.js +208 -15
  194. package/src/lib/plugin-runtime/marketplace-network.js +317 -0
  195. package/src/lib/plugin-runtime/marketplace-pac-worker.js +15 -0
  196. package/src/lib/plugin-runtime/marketplace-remote-artifacts.js +25 -2
  197. package/src/lib/plugin-runtime/marketplace-source-cache.js +359 -0
  198. package/src/lib/plugin-runtime/policy.js +21 -6
  199. package/src/lib/plugin-runtime/publisher-trust.js +332 -0
  200. package/src/lib/plugin-runtime/remote-source.js +323 -27
  201. package/src/lib/plugin-runtime/scopes.js +317 -41
  202. package/src/lib/plugin-runtime/transaction-journal.js +806 -0
  203. package/src/lib/public-ide-capability-manifest.js +1 -1
  204. package/src/lib/remote-approval-bridge.js +745 -108
  205. package/src/lib/remote-control.js +2 -0
  206. package/src/lib/remote-membership-coordinator.js +612 -46
  207. package/src/lib/remote-membership-host-store.js +59 -10
  208. package/src/lib/runtime-provenance-ledger.js +171 -46
  209. package/src/lib/shell-approval.js +169 -6
  210. package/src/lib/sub-agent-context.js +268 -2
  211. package/src/lib/task-model-selector.js +1 -1
  212. package/src/lib/with-file-lock.js +167 -42
  213. package/src/repl/agent-repl.js +70 -29
  214. package/src/repl/remote-approval.js +29 -7
  215. package/src/runtime/agent-core.js +1695 -194
  216. package/src/runtime/headless-runner.js +22 -5
  217. package/src/runtime/headless-stream.js +19 -3
  218. package/src/workers/background-agent-keeper.js +279 -58
  219. package/src/workers/background-agent-worker.js +7 -0
  220. package/src/workers/background-turn-bootstrap.js +29 -2
  221. package/src/assets/web-panel/assets/MarkdownRenderer-GmB6DOr6.js +0 -1
  222. package/src/assets/web-panel/assets/MobileProjects-BL0Czcoj.js +0 -1
  223. package/src/assets/web-panel/assets/Projects-DDAjkUkT.js +0 -1
  224. package/src/assets/web-panel/assets/RemoteSession-Dxzl5n2T.js +0 -5
  225. package/src/assets/web-panel/assets/SpeechSettings-D6UL3dA5.js +0 -1
  226. package/src/assets/web-panel/assets/devWarning-DKJjSWRn.js +0 -1
  227. package/src/assets/web-panel/assets/index-C61cnl3K.js +0 -1
  228. package/src/assets/web-panel/assets/index-D4XFHxBr.js +0 -1
@@ -0,0 +1,2974 @@
1
+ import { createHash } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { types as utilTypes } from "node:util";
5
+ import {
6
+ COWORK_WORKFLOW_CONTROL_SIGNAL_CODE,
7
+ executeWorkflow as executeCoworkWorkflow,
8
+ } from "./cowork-workflow.js";
9
+ import { writeSecurityStore } from "./durable-security-store.js";
10
+ import { containsSecret } from "./secret-scan.js";
11
+ import {
12
+ sameFileStatIdentity,
13
+ samePathHandleFileIdentity,
14
+ withTrustedFileParentSync,
15
+ } from "./secure-file-identity.js";
16
+ import { canonicalJson } from "./scheduler-kernel/contract.js";
17
+ import { withFileLock } from "./with-file-lock.js";
18
+
19
+ export const DYNAMIC_WORKFLOW_RUNTIME_SCHEMA = "cc-dynamic-workflow-runtime/v1";
20
+ export const DYNAMIC_WORKFLOW_RUNTIME_VERSION = 1;
21
+ export const DYNAMIC_WORKFLOW_EFFECT_SCHEMA = "cc-dynamic-workflow-effect/v1";
22
+ export const DYNAMIC_WORKFLOW_CALL_SCHEMA = "cc-dynamic-workflow-call/v1";
23
+ export const DYNAMIC_WORKFLOW_OBSERVABILITY_SCHEMA =
24
+ "cc-dynamic-workflow-observability/v1";
25
+ export const DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE =
26
+ COWORK_WORKFLOW_CONTROL_SIGNAL_CODE;
27
+
28
+ const STATE_STATUSES = new Set([
29
+ "ready",
30
+ "running",
31
+ "pause_requested",
32
+ "paused",
33
+ "blocked",
34
+ "failed",
35
+ "stopped",
36
+ "completed",
37
+ ]);
38
+ const TERMINAL_STATUSES = new Set(["stopped", "completed"]);
39
+ const SHA256_RE = /^sha256:[a-f0-9]{64}$/u;
40
+ const MAX_STATE_BYTES = 4 * 1024 * 1024;
41
+ const MAX_EFFECTS = 64;
42
+ const MAX_EFFECT_BATCH_SIZE = 64;
43
+ const MAX_LINEAGE_EVENTS = 4096;
44
+ const MAX_CALLS_PER_EFFECT = 128;
45
+ const MAX_PROJECTED_ARTIFACTS_PER_EFFECT = 256;
46
+ const PROVIDER_CLIENT_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/u;
47
+ const PROVIDER_RECEIPT_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
48
+ const PROVIDER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
49
+ const WORKFLOW_TOOL_CALL_ID_RE = /^[\x21-\x7e]{1,512}$/u;
50
+ const WORKFLOW_TOOL_NAME_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
51
+ const WORKFLOW_CHILD_EFFECT_PROTOCOL = "cc-workflow-child-effect/v1";
52
+ const WORKFLOW_PROVIDER_ATTEMPT_PROTOCOL = "cc-provider-request-attempt/v1";
53
+ const WORKFLOW_PROVIDER_CALL_ID_RE = /^[A-Za-z0-9._:-]{1,256}$/u;
54
+ const WORKFLOW_PROVIDER_REQUEST_ID_RE = /^ccwf_[a-f0-9]{64}$/u;
55
+ const DESCENDANT_OWNER_TOOL_NAMES = new Set(["run_skill", "spawn_sub_agent"]);
56
+ const WORKFLOW_CALL_STATUSES = new Set([
57
+ "started",
58
+ "completed",
59
+ "failed",
60
+ "outcome_unknown",
61
+ "operator_reconciled",
62
+ ]);
63
+ const SETTLEMENT_AUTHORITIES = new Set([
64
+ "provider-return",
65
+ "operator-reconciled",
66
+ "runtime-not-dispatched",
67
+ ]);
68
+
69
+ function digest(domain, value) {
70
+ return `sha256:${createHash("sha256")
71
+ .update(domain, "utf8")
72
+ .update(canonicalJson(value, "dynamicWorkflowRuntime"), "utf8")
73
+ .digest("hex")}`;
74
+ }
75
+
76
+ function isoNow(now) {
77
+ const raw = typeof now === "function" ? now() : now;
78
+ const date = raw == null ? new Date() : new Date(raw);
79
+ if (!Number.isFinite(date.getTime())) {
80
+ throw new TypeError("dynamic workflow runtime time is invalid");
81
+ }
82
+ return date.toISOString();
83
+ }
84
+
85
+ function safeId(value, field, max = 256) {
86
+ if (
87
+ typeof value !== "string" ||
88
+ !new RegExp(`^[A-Za-z0-9][A-Za-z0-9._-]{0,${max - 1}}$`, "u").test(value) ||
89
+ value.includes("..")
90
+ ) {
91
+ throw new TypeError(`${field} is invalid`);
92
+ }
93
+ return value;
94
+ }
95
+
96
+ function snapshotJson(value, field, maxBytes = MAX_STATE_BYTES) {
97
+ let encoded;
98
+ try {
99
+ encoded = canonicalJson(value, field);
100
+ } catch {
101
+ throw new TypeError(`${field} must contain canonical JSON`);
102
+ }
103
+ if (Buffer.byteLength(encoded, "utf8") > maxBytes) {
104
+ throw new TypeError(`${field} exceeds its byte limit`);
105
+ }
106
+ return JSON.parse(encoded);
107
+ }
108
+
109
+ function nonNegativeSafeInteger(value) {
110
+ return Number.isSafeInteger(value) && value >= 0 ? value : null;
111
+ }
112
+
113
+ function effectResultDigest(result) {
114
+ return digest("chainlesschain.dynamic-workflow.effect-result.v1\0", result);
115
+ }
116
+
117
+ function effectBatchId(runId, effects) {
118
+ return digest("chainlesschain.dynamic-workflow.effect-batch.v1\0", {
119
+ runId,
120
+ effects: effects.map((effect) => ({
121
+ key: effect.key,
122
+ payloadDigest: effect.payloadDigest,
123
+ })),
124
+ });
125
+ }
126
+
127
+ function hasProviderDispatchMetadata(effect) {
128
+ return [
129
+ effect?.providerDispatchedAt,
130
+ effect?.timeoutMs,
131
+ effect?.timeoutObservedAt,
132
+ ].some((field) => field !== undefined);
133
+ }
134
+
135
+ function isKnownUndispatched(effect) {
136
+ return (
137
+ hasProviderDispatchMetadata(effect) && effect?.providerDispatchedAt === null
138
+ );
139
+ }
140
+
141
+ function stateMaterial(state) {
142
+ const material = { ...state };
143
+ delete material.stateDigest;
144
+ return material;
145
+ }
146
+
147
+ function finalizeState(state) {
148
+ const material = snapshotJson(stateMaterial(state), "workflow runtime state");
149
+ return Object.freeze({
150
+ ...material,
151
+ stateDigest: digest(
152
+ "chainlesschain.dynamic-workflow.runtime-state.v1\0",
153
+ material,
154
+ ),
155
+ });
156
+ }
157
+
158
+ function appendLineage(state, type, details, now) {
159
+ if (state.lineage.length >= MAX_LINEAGE_EVENTS) {
160
+ throw new Error("dynamic workflow runtime lineage limit exceeded");
161
+ }
162
+ const at = isoNow(now);
163
+ const previousDigest = state.lineage.at(-1)?.eventDigest || null;
164
+ state.revision += 1;
165
+ state.updatedAt = at;
166
+ const material = {
167
+ sequence: state.lineage.length + 1,
168
+ revision: state.revision,
169
+ at,
170
+ type,
171
+ details: snapshotJson(details || {}, "workflow runtime event", 64 * 1024),
172
+ previousDigest,
173
+ };
174
+ state.lineage.push({
175
+ ...material,
176
+ eventDigest: digest(
177
+ "chainlesschain.dynamic-workflow.runtime-event.v1\0",
178
+ material,
179
+ ),
180
+ });
181
+ }
182
+
183
+ function transition(snapshot, type, details, mutate, now) {
184
+ const state = snapshotJson(
185
+ stateMaterial(snapshot),
186
+ "workflow runtime transition",
187
+ );
188
+ mutate(state);
189
+ appendLineage(state, type, details, now);
190
+ return finalizeState(state);
191
+ }
192
+
193
+ function verifyLineage(state, issues) {
194
+ if (
195
+ !Array.isArray(state.lineage) ||
196
+ state.lineage.length > MAX_LINEAGE_EVENTS
197
+ ) {
198
+ issues.push("lineage-invalid");
199
+ return;
200
+ }
201
+ let previousDigest = null;
202
+ for (const [index, event] of state.lineage.entries()) {
203
+ const material = {
204
+ sequence: event?.sequence,
205
+ revision: event?.revision,
206
+ at: event?.at,
207
+ type: event?.type,
208
+ details: event?.details,
209
+ previousDigest: event?.previousDigest,
210
+ };
211
+ const expected = digest(
212
+ "chainlesschain.dynamic-workflow.runtime-event.v1\0",
213
+ material,
214
+ );
215
+ if (
216
+ event?.sequence !== index + 1 ||
217
+ event?.revision !== index + 1 ||
218
+ event?.previousDigest !== previousDigest ||
219
+ event?.eventDigest !== expected ||
220
+ isoNow(event?.at) !== event.at
221
+ ) {
222
+ issues.push(`lineage-${index}-invalid`);
223
+ }
224
+ previousDigest = event?.eventDigest || null;
225
+ }
226
+ }
227
+
228
+ function workflowCallId(
229
+ effectId,
230
+ kind,
231
+ callId,
232
+ childEffectId = null,
233
+ ownerEffectId = null,
234
+ ) {
235
+ const material = {
236
+ effectId,
237
+ kind,
238
+ callId,
239
+ childEffectId,
240
+ };
241
+ if (ownerEffectId !== null) material.ownerEffectId = ownerEffectId;
242
+ return digest("chainlesschain.dynamic-workflow.call-id.v1\0", material);
243
+ }
244
+
245
+ function expectedWorkflowProviderRequestId(effectId, source, sequence) {
246
+ return `ccwf_${createHash("sha256")
247
+ .update(`${effectId}\0${source}\0${String(sequence)}`, "utf8")
248
+ .digest("hex")}`;
249
+ }
250
+
251
+ function verifyEffectCalls(effect, effectIndex, issues) {
252
+ const calls = effect?.calls === undefined ? [] : effect.calls;
253
+ if (!Array.isArray(calls) || calls.length > MAX_CALLS_PER_EFFECT) {
254
+ issues.push(`effect-${effectIndex}-calls-invalid`);
255
+ return;
256
+ }
257
+ const ids = new Set();
258
+ const identities = new Set();
259
+ const authorizedOwnerEffectIds = new Set([effect.id]);
260
+ for (const [callIndex, call] of calls.entries()) {
261
+ const legacyOwner = call?.ownerEffectId === undefined;
262
+ const ownerEffectId = legacyOwner ? effect.id : call?.ownerEffectId;
263
+ const requestSource =
264
+ call?.requestSource === undefined
265
+ ? call?.kind === "provider"
266
+ ? call?.source
267
+ : null
268
+ : call.requestSource;
269
+ const terminal = call?.status !== "started";
270
+ const identityKey = `${call?.kind || ""}\0${
271
+ call?.kind === "tool" ? call?.childEffectId : call?.callId
272
+ }`;
273
+ let timestampsValid = false;
274
+ try {
275
+ timestampsValid =
276
+ isoNow(call?.startedAt) === call.startedAt &&
277
+ Date.parse(call.startedAt) >= Date.parse(effect.requestedAt) &&
278
+ (terminal
279
+ ? isoNow(call?.settledAt) === call.settledAt &&
280
+ Date.parse(call.settledAt) >= Date.parse(call.startedAt) &&
281
+ (effect.settledAt === null ||
282
+ Date.parse(call.settledAt) <= Date.parse(effect.settledAt))
283
+ : call?.settledAt === null);
284
+ } catch {
285
+ timestampsValid = false;
286
+ }
287
+ const commonValid =
288
+ call?.schema === DYNAMIC_WORKFLOW_CALL_SCHEMA &&
289
+ call.effectId === effect.id &&
290
+ SHA256_RE.test(call.id || "") &&
291
+ call.id ===
292
+ workflowCallId(
293
+ effect.id,
294
+ call.kind,
295
+ call.callId,
296
+ call.childEffectId,
297
+ legacyOwner ? null : ownerEffectId,
298
+ ) &&
299
+ SHA256_RE.test(ownerEffectId || "") &&
300
+ authorizedOwnerEffectIds.has(ownerEffectId) &&
301
+ ["provider", "tool"].includes(call.kind) &&
302
+ WORKFLOW_CALL_STATUSES.has(call.status) &&
303
+ Number.isSafeInteger(call.sequence) &&
304
+ call.sequence >= 1 &&
305
+ typeof call.name === "string" &&
306
+ call.name.length >= 1 &&
307
+ call.name.length <= 256 &&
308
+ typeof call.source === "string" &&
309
+ call.source.length >= 1 &&
310
+ call.source.length <= 64 &&
311
+ typeof call.identitySemantics === "string" &&
312
+ call.identitySemantics.length >= 1 &&
313
+ call.identitySemantics.length <= 64 &&
314
+ (call.settlementCode === null ||
315
+ (typeof call.settlementCode === "string" &&
316
+ call.settlementCode.length >= 1 &&
317
+ call.settlementCode.length <= 128)) &&
318
+ typeof call.outcomeUnknown === "boolean" &&
319
+ call.outcomeUnknown === (call.status === "outcome_unknown") &&
320
+ typeof call.providerReceiptPersisted === "boolean" &&
321
+ (call.providerReceiptRequestId === null ||
322
+ (typeof call.providerReceiptRequestId === "string" &&
323
+ PROVIDER_RECEIPT_ID_RE.test(call.providerReceiptRequestId))) &&
324
+ (call.providerReceiptResponseId === null ||
325
+ (typeof call.providerReceiptResponseId === "string" &&
326
+ PROVIDER_RECEIPT_ID_RE.test(call.providerReceiptResponseId))) &&
327
+ call.providerReceiptPersisted ===
328
+ Boolean(
329
+ call.providerReceiptRequestId || call.providerReceiptResponseId,
330
+ ) &&
331
+ (call.mcpLedgerId === null ||
332
+ PROVIDER_RECEIPT_ID_RE.test(call.mcpLedgerId || "")) &&
333
+ typeof call.mcpLedgerPrewritePersisted === "boolean" &&
334
+ typeof call.mcpLedgerSettlementPersisted === "boolean" &&
335
+ (!call.mcpLedgerSettlementPersisted || call.mcpLedgerPrewritePersisted) &&
336
+ timestampsValid &&
337
+ !ids.has(call.id) &&
338
+ !identities.has(identityKey);
339
+ const providerValid =
340
+ call?.kind !== "provider" ||
341
+ (call.protocol === WORKFLOW_PROVIDER_ATTEMPT_PROTOCOL &&
342
+ WORKFLOW_PROVIDER_CALL_ID_RE.test(call.callId || "") &&
343
+ call.childEffectId === null &&
344
+ PROVIDER_NAME_RE.test(call.name || "") &&
345
+ ["model", "semantic-compaction", "subagent"].includes(call.source) &&
346
+ ["model", "semantic-compaction"].includes(requestSource) &&
347
+ (call.source === "subagent"
348
+ ? requestSource === "model"
349
+ : requestSource === call.source) &&
350
+ WORKFLOW_PROVIDER_REQUEST_ID_RE.test(call.clientRequestId || "") &&
351
+ call.clientRequestId ===
352
+ expectedWorkflowProviderRequestId(
353
+ ownerEffectId,
354
+ requestSource,
355
+ call.sequence,
356
+ ) &&
357
+ call.identitySemantics === "trace-only" &&
358
+ (call.providerReceiptPersisted === false ||
359
+ ["completed", "outcome_unknown"].includes(call.status)) &&
360
+ call.mcpLedgerId === null &&
361
+ call.mcpLedgerPrewritePersisted === false &&
362
+ call.mcpLedgerSettlementPersisted === false);
363
+ const toolValid =
364
+ call?.kind !== "tool" ||
365
+ (call.protocol === WORKFLOW_CHILD_EFFECT_PROTOCOL &&
366
+ WORKFLOW_TOOL_CALL_ID_RE.test(call.callId || "") &&
367
+ SHA256_RE.test(call.childEffectId || "") &&
368
+ call.childEffectId ===
369
+ expectedNestedToolEffectId(
370
+ ownerEffectId,
371
+ call.sequence,
372
+ call.callId,
373
+ call.name,
374
+ ) &&
375
+ WORKFLOW_TOOL_NAME_RE.test(call.name || "") &&
376
+ call.source === "tool" &&
377
+ requestSource === null &&
378
+ call.clientRequestId === null &&
379
+ call.providerReceiptPersisted === false &&
380
+ call.providerReceiptRequestId === null &&
381
+ call.providerReceiptResponseId === null &&
382
+ call.identitySemantics === "runtime-derived");
383
+ const settlementValid = terminal
384
+ ? call.settlementCode !== null || call.status === "completed"
385
+ : call.settlementCode === null &&
386
+ call.outcomeUnknown === false &&
387
+ call.providerReceiptPersisted === false &&
388
+ call.providerReceiptRequestId === null &&
389
+ call.providerReceiptResponseId === null &&
390
+ call.mcpLedgerId === null &&
391
+ call.mcpLedgerPrewritePersisted === false &&
392
+ call.mcpLedgerSettlementPersisted === false;
393
+ const callValid =
394
+ commonValid && providerValid && toolValid && settlementValid;
395
+ if (!callValid) {
396
+ issues.push(`effect-${effectIndex}-call-${callIndex}-invalid`);
397
+ }
398
+ if (
399
+ callValid &&
400
+ call.kind === "tool" &&
401
+ DESCENDANT_OWNER_TOOL_NAMES.has(call.name)
402
+ ) {
403
+ authorizedOwnerEffectIds.add(call.childEffectId);
404
+ }
405
+ ids.add(call?.id);
406
+ identities.add(identityKey);
407
+ }
408
+ if (
409
+ effect?.status === "settled" &&
410
+ calls.some((call) => call?.status === "started")
411
+ ) {
412
+ issues.push(`effect-${effectIndex}-calls-unsettled`);
413
+ }
414
+ if (calls.length > 0 && typeof effect?.providerDispatchedAt !== "string") {
415
+ issues.push(`effect-${effectIndex}-calls-without-dispatch`);
416
+ }
417
+ }
418
+
419
+ export function verifyDynamicWorkflowRuntimeState(value) {
420
+ const state = snapshotJson(value, "workflow runtime state");
421
+ const issues = [];
422
+ if (
423
+ state.schema !== DYNAMIC_WORKFLOW_RUNTIME_SCHEMA ||
424
+ state.version !== DYNAMIC_WORKFLOW_RUNTIME_VERSION ||
425
+ !STATE_STATUSES.has(state.status)
426
+ ) {
427
+ issues.push("identity-invalid");
428
+ }
429
+ try {
430
+ safeId(state.runId, "runId");
431
+ safeId(state.workflowId, "workflowId", 128);
432
+ } catch {
433
+ issues.push("run-binding-invalid");
434
+ }
435
+ if (
436
+ !SHA256_RE.test(state.definitionDigest || "") ||
437
+ !SHA256_RE.test(state.admissionDigest || "") ||
438
+ typeof state.executionAuthoritySessionId !== "string" ||
439
+ state.executionAuthoritySessionId.length === 0 ||
440
+ state.executionAuthoritySessionId.length > 256
441
+ ) {
442
+ issues.push("authority-binding-invalid");
443
+ }
444
+ if (
445
+ !Number.isSafeInteger(state.revision) ||
446
+ state.revision < 1 ||
447
+ state.revision !== state.lineage?.length
448
+ ) {
449
+ issues.push("revision-invalid");
450
+ }
451
+ verifyLineage(state, issues);
452
+ if (!Array.isArray(state.effects) || state.effects.length > MAX_EFFECTS) {
453
+ issues.push("effects-invalid");
454
+ } else {
455
+ const ids = new Set();
456
+ const keys = new Set();
457
+ const batches = new Map();
458
+ for (const [index, effect] of state.effects.entries()) {
459
+ let expectedKey = null;
460
+ let expectedId = null;
461
+ try {
462
+ expectedKey = effectKey(effect);
463
+ expectedId = digest("chainlesschain.dynamic-workflow.effect-id.v1\0", {
464
+ runId: state.runId,
465
+ key: expectedKey,
466
+ payloadDigest: effect?.payloadDigest,
467
+ });
468
+ } catch {
469
+ // The common invalid-effect branch below records the stable issue.
470
+ }
471
+ const resultDigest =
472
+ effect?.result == null ? null : effectResultDigest(effect.result);
473
+ const batchFields = [
474
+ effect?.batchId,
475
+ effect?.batchIndex,
476
+ effect?.batchSize,
477
+ ];
478
+ const hasBatchMetadata = batchFields.some((field) => field !== undefined);
479
+ const batchMetadataValid =
480
+ !hasBatchMetadata ||
481
+ (batchFields.every((field) => field !== undefined) &&
482
+ SHA256_RE.test(effect.batchId || "") &&
483
+ Number.isSafeInteger(effect.batchIndex) &&
484
+ effect.batchIndex >= 0 &&
485
+ Number.isSafeInteger(effect.batchSize) &&
486
+ effect.batchSize >= 1 &&
487
+ effect.batchSize <= MAX_EFFECT_BATCH_SIZE &&
488
+ effect.batchIndex < effect.batchSize);
489
+ const hasDispatchMetadata = hasProviderDispatchMetadata(effect);
490
+ const dispatchFields = [
491
+ effect?.providerDispatchedAt,
492
+ effect?.timeoutMs,
493
+ effect?.timeoutObservedAt,
494
+ ];
495
+ const providerDispatchedAtValid =
496
+ effect?.providerDispatchedAt === null ||
497
+ (typeof effect?.providerDispatchedAt === "string" &&
498
+ isoNow(effect.providerDispatchedAt) === effect.providerDispatchedAt &&
499
+ Date.parse(effect.providerDispatchedAt) >=
500
+ Date.parse(effect.requestedAt));
501
+ const timeoutObservedAtValid =
502
+ effect?.timeoutObservedAt === null ||
503
+ (typeof effect?.timeoutObservedAt === "string" &&
504
+ Number(effect?.timeoutMs) > 0 &&
505
+ isoNow(effect.timeoutObservedAt) === effect.timeoutObservedAt &&
506
+ Date.parse(effect.timeoutObservedAt) >=
507
+ Date.parse(effect.requestedAt) &&
508
+ (effect.providerDispatchedAt === null ||
509
+ Date.parse(effect.timeoutObservedAt) >=
510
+ Date.parse(effect.providerDispatchedAt)));
511
+ const dispatchMetadataValid =
512
+ !hasDispatchMetadata ||
513
+ (dispatchFields.every((field) => field !== undefined) &&
514
+ Number.isFinite(effect.timeoutMs) &&
515
+ effect.timeoutMs >= 0 &&
516
+ providerDispatchedAtValid &&
517
+ timeoutObservedAtValid);
518
+ if (
519
+ effect?.schema !== DYNAMIC_WORKFLOW_EFFECT_SCHEMA ||
520
+ !SHA256_RE.test(effect.id || "") ||
521
+ !SHA256_RE.test(effect.payloadDigest || "") ||
522
+ !["pending", "settled"].includes(effect.status) ||
523
+ typeof effect.key !== "string" ||
524
+ effect.key.length === 0 ||
525
+ effect.key.length > 1024 ||
526
+ effect.key !== expectedKey ||
527
+ effect.id !== expectedId ||
528
+ isoNow(effect.requestedAt) !== effect.requestedAt ||
529
+ ids.has(effect.id) ||
530
+ keys.has(effect.key) ||
531
+ !batchMetadataValid ||
532
+ !dispatchMetadataValid ||
533
+ (effect.status === "pending" &&
534
+ (effect.result !== null ||
535
+ effect.resultDigest !== null ||
536
+ effect.settlementAuthority !== null ||
537
+ effect.settledAt !== null)) ||
538
+ (effect.status === "settled" &&
539
+ (effect.result == null ||
540
+ effect.resultDigest !== resultDigest ||
541
+ !SETTLEMENT_AUTHORITIES.has(effect.settlementAuthority) ||
542
+ (hasDispatchMetadata &&
543
+ effect.settlementAuthority === "runtime-not-dispatched" &&
544
+ (effect.providerDispatchedAt !== null ||
545
+ effect.result?.status !== "failed" ||
546
+ effect.result?.result?.providerDispatched !== false ||
547
+ ![
548
+ "timeout-before-dispatch",
549
+ "stopped-before-dispatch",
550
+ ].includes(effect.result?.result?.reason) ||
551
+ (effect.result?.result?.reason ===
552
+ "timeout-before-dispatch") !==
553
+ (typeof effect.timeoutObservedAt === "string"))) ||
554
+ (hasDispatchMetadata &&
555
+ effect.settlementAuthority !== "runtime-not-dispatched" &&
556
+ typeof effect.providerDispatchedAt !== "string") ||
557
+ (hasDispatchMetadata &&
558
+ typeof effect.providerDispatchedAt === "string" &&
559
+ Date.parse(effect.providerDispatchedAt) >
560
+ Date.parse(effect.settledAt)) ||
561
+ (hasDispatchMetadata &&
562
+ typeof effect.timeoutObservedAt === "string" &&
563
+ Date.parse(effect.timeoutObservedAt) >
564
+ Date.parse(effect.settledAt)) ||
565
+ Date.parse(effect.settledAt) < Date.parse(effect.requestedAt) ||
566
+ isoNow(effect.settledAt) !== effect.settledAt))
567
+ ) {
568
+ issues.push(`effect-${index}-invalid`);
569
+ }
570
+ ids.add(effect?.id);
571
+ keys.add(effect?.key);
572
+ verifyEffectCalls(effect, index, issues);
573
+ if (batchMetadataValid && hasBatchMetadata) {
574
+ if (!batches.has(effect.batchId)) batches.set(effect.batchId, []);
575
+ batches.get(effect.batchId).push(effect);
576
+ }
577
+ }
578
+ let batchIndex = 0;
579
+ for (const [batchId, entries] of batches) {
580
+ entries.sort((left, right) => left.batchIndex - right.batchIndex);
581
+ const expectedSize = entries[0]?.batchSize;
582
+ const complete =
583
+ entries.length === expectedSize &&
584
+ entries.every(
585
+ (entry, index) =>
586
+ entry.batchSize === expectedSize &&
587
+ entry.batchIndex === index &&
588
+ entry.requestedAt === entries[0].requestedAt,
589
+ );
590
+ const expectedBatchId = complete
591
+ ? effectBatchId(state.runId, entries)
592
+ : null;
593
+ if (!complete || batchId !== expectedBatchId) {
594
+ issues.push(`effect-batch-${batchIndex}-invalid`);
595
+ }
596
+ batchIndex += 1;
597
+ }
598
+ }
599
+ if (
600
+ (state.status === "completed") !== (state.finalRecord != null) ||
601
+ (state.status === "stopped" && state.finalRecord != null)
602
+ ) {
603
+ issues.push("terminal-record-invalid");
604
+ }
605
+ if (
606
+ isoNow(state.createdAt) !== state.createdAt ||
607
+ isoNow(state.updatedAt) !== state.updatedAt
608
+ ) {
609
+ issues.push("timestamp-invalid");
610
+ }
611
+ const expectedStateDigest = digest(
612
+ "chainlesschain.dynamic-workflow.runtime-state.v1\0",
613
+ stateMaterial(state),
614
+ );
615
+ if (state.stateDigest !== expectedStateDigest)
616
+ issues.push("state-digest-invalid");
617
+ if (issues.length > 0) {
618
+ const error = new Error(
619
+ `dynamic workflow runtime state is invalid: ${issues.join(", ")}`,
620
+ );
621
+ error.code = "CC_DYNAMIC_WORKFLOW_RUNTIME_STATE_INVALID";
622
+ error.issues = issues;
623
+ throw error;
624
+ }
625
+ return Object.freeze(state);
626
+ }
627
+
628
+ function parseStateBytes(raw) {
629
+ const text =
630
+ typeof raw === "string" ? raw : Buffer.from(raw).toString("utf8");
631
+ const bytes = Buffer.byteLength(text, "utf8");
632
+ if (bytes <= 0 || bytes > MAX_STATE_BYTES) {
633
+ throw new Error("dynamic workflow runtime state exceeds its byte limit");
634
+ }
635
+ let value;
636
+ try {
637
+ value = JSON.parse(text);
638
+ } catch {
639
+ throw new Error("dynamic workflow runtime state is corrupt");
640
+ }
641
+ return verifyDynamicWorkflowRuntimeState(value);
642
+ }
643
+
644
+ function readStateFile(statePath) {
645
+ try {
646
+ fs.lstatSync(statePath);
647
+ } catch (error) {
648
+ if (error?.code === "ENOENT") return null;
649
+ throw error;
650
+ }
651
+ return withTrustedFileParentSync(
652
+ fs,
653
+ statePath,
654
+ ({ canonicalPath, parentDevice }) => {
655
+ const before = fs.lstatSync(canonicalPath, { bigint: true });
656
+ if (
657
+ !before.isFile() ||
658
+ before.isSymbolicLink() ||
659
+ Number(before.nlink) !== 1 ||
660
+ Number(before.size) <= 0 ||
661
+ Number(before.size) > MAX_STATE_BYTES
662
+ ) {
663
+ throw new Error(
664
+ "dynamic workflow runtime state must be a bounded, regular, single-link file",
665
+ );
666
+ }
667
+ let descriptor;
668
+ try {
669
+ let flags = Number(fs.constants.O_RDONLY || 0);
670
+ if (typeof fs.constants.O_NOFOLLOW === "number") {
671
+ flags |= fs.constants.O_NOFOLLOW;
672
+ }
673
+ descriptor = fs.openSync(canonicalPath, flags);
674
+ const opened = fs.fstatSync(descriptor, { bigint: true });
675
+ if (
676
+ !opened.isFile() ||
677
+ Number(opened.nlink) !== 1 ||
678
+ !samePathHandleFileIdentity(before, opened, parentDevice)
679
+ ) {
680
+ throw new Error(
681
+ "dynamic workflow runtime state identity changed while opening",
682
+ );
683
+ }
684
+ const state = parseStateBytes(fs.readFileSync(descriptor, "utf8"));
685
+ const after = fs.fstatSync(descriptor, { bigint: true });
686
+ if (!sameFileStatIdentity(opened, after)) {
687
+ throw new Error(
688
+ "dynamic workflow runtime state changed while reading",
689
+ );
690
+ }
691
+ return state;
692
+ } finally {
693
+ if (descriptor !== undefined) fs.closeSync(descriptor);
694
+ }
695
+ },
696
+ );
697
+ }
698
+
699
+ function replaceObject(target, value) {
700
+ for (const key of Object.keys(target)) delete target[key];
701
+ Object.assign(target, value);
702
+ }
703
+
704
+ function withStateMutation(statePath, mutator) {
705
+ fs.mkdirSync(path.dirname(statePath), { recursive: true, mode: 0o700 });
706
+ return withFileLock(
707
+ statePath,
708
+ () => {
709
+ const current = readStateFile(statePath);
710
+ const result = mutator(current);
711
+ if (result?.state) {
712
+ const verified = verifyDynamicWorkflowRuntimeState(result.state);
713
+ writeSecurityStore(statePath, "dynamic workflow runtime", verified);
714
+ }
715
+ return result?.value;
716
+ },
717
+ { failIfUnavailable: true, timeoutMs: 5000, staleMs: 30000 },
718
+ );
719
+ }
720
+
721
+ export function dynamicWorkflowRunStatePath(cwd, runId) {
722
+ const safeRunId = safeId(runId, "runId");
723
+ return path.join(
724
+ path.resolve(cwd),
725
+ ".chainlesschain",
726
+ "cowork",
727
+ "workflow-runs",
728
+ `${safeRunId}.json`,
729
+ );
730
+ }
731
+
732
+ export function readDynamicWorkflowRuntimeState(statePath) {
733
+ const state = readStateFile(path.resolve(statePath));
734
+ if (!state) {
735
+ const error = new Error("dynamic workflow runtime state was not found");
736
+ error.code = "CC_DYNAMIC_WORKFLOW_RUNTIME_NOT_FOUND";
737
+ throw error;
738
+ }
739
+ return state;
740
+ }
741
+
742
+ export function readDynamicWorkflowEffectResultFile(filePath) {
743
+ const resolved = path.resolve(filePath);
744
+ return withTrustedFileParentSync(
745
+ fs,
746
+ resolved,
747
+ ({ canonicalPath, parentDevice }) => {
748
+ const before = fs.lstatSync(canonicalPath, { bigint: true });
749
+ if (
750
+ !before.isFile() ||
751
+ before.isSymbolicLink() ||
752
+ Number(before.nlink) !== 1 ||
753
+ Number(before.size) <= 0 ||
754
+ Number(before.size) > 1024 * 1024
755
+ ) {
756
+ throw new Error(
757
+ "workflow effect result must be a bounded, regular, single-link file",
758
+ );
759
+ }
760
+ let descriptor;
761
+ try {
762
+ let flags = Number(fs.constants.O_RDONLY || 0);
763
+ if (typeof fs.constants.O_NOFOLLOW === "number") {
764
+ flags |= fs.constants.O_NOFOLLOW;
765
+ }
766
+ descriptor = fs.openSync(canonicalPath, flags);
767
+ const opened = fs.fstatSync(descriptor, { bigint: true });
768
+ if (
769
+ !opened.isFile() ||
770
+ Number(opened.nlink) !== 1 ||
771
+ !samePathHandleFileIdentity(before, opened, parentDevice)
772
+ ) {
773
+ throw new Error(
774
+ "workflow effect result identity changed while opening",
775
+ );
776
+ }
777
+ let parsed;
778
+ try {
779
+ parsed = JSON.parse(fs.readFileSync(descriptor, "utf8"));
780
+ } catch {
781
+ throw new Error("workflow effect result is not valid JSON");
782
+ }
783
+ const result = snapshotJson(
784
+ parsed,
785
+ "workflow reconciled effect result",
786
+ 1024 * 1024,
787
+ );
788
+ if (containsSecret(JSON.stringify(result))) {
789
+ throw new Error("workflow effect result contains secret-shaped data");
790
+ }
791
+ const after = fs.fstatSync(descriptor, { bigint: true });
792
+ if (!sameFileStatIdentity(opened, after)) {
793
+ throw new Error("workflow effect result changed while being read");
794
+ }
795
+ return result;
796
+ } finally {
797
+ if (descriptor !== undefined) fs.closeSync(descriptor);
798
+ }
799
+ },
800
+ );
801
+ }
802
+
803
+ function stateBindings(execution, runId) {
804
+ const admission = execution.runAdmission;
805
+ if (!admission || typeof admission !== "object") {
806
+ throw new TypeError("durable workflow execution requires run admission");
807
+ }
808
+ if (
809
+ !Number.isSafeInteger(admission.maxParallel) ||
810
+ admission.maxParallel < 1 ||
811
+ admission.maxParallel > MAX_EFFECT_BATCH_SIZE
812
+ ) {
813
+ throw new TypeError("durable workflow maxParallel admission is invalid");
814
+ }
815
+ const generation = execution.workflow?.facade?.generation;
816
+ const review = execution.workflow?.facade?.review;
817
+ if (generation != null || review != null) {
818
+ if (
819
+ generation?.schema !== "cc-dynamic-workflow-generation/v1" ||
820
+ review?.schema !== "cc-dynamic-workflow-review/v1" ||
821
+ review?.decision !== "accepted" ||
822
+ !SHA256_RE.test(review?.draftDigest || "") ||
823
+ !SHA256_RE.test(review?.sourceDefinitionDigest || "") ||
824
+ typeof review?.reviewer !== "string" ||
825
+ review.reviewer.length === 0
826
+ ) {
827
+ const error = new Error(
828
+ "model-generated durable workflow requires accepted review authority",
829
+ );
830
+ error.code = "CC_DYNAMIC_WORKFLOW_REVIEW_AUTHORITY_REQUIRED";
831
+ throw error;
832
+ }
833
+ }
834
+ return {
835
+ runId: safeId(runId, "runId"),
836
+ workflowId: safeId(execution.workflow?.id, "workflowId", 128),
837
+ definitionDigest: execution.definitionDigest,
838
+ admissionDigest: admission.admissionDigest,
839
+ executionAuthoritySessionId:
840
+ admission.executionLocation?.session?.sessionId,
841
+ executionPolicy: snapshotJson(
842
+ admission.executionPolicy,
843
+ "workflow execution policy",
844
+ 64 * 1024,
845
+ ),
846
+ };
847
+ }
848
+
849
+ function bindingsMatch(state, bindings) {
850
+ return (
851
+ [
852
+ "runId",
853
+ "workflowId",
854
+ "definitionDigest",
855
+ "admissionDigest",
856
+ "executionAuthoritySessionId",
857
+ ].every((field) => state[field] === bindings[field]) &&
858
+ canonicalJson(state.executionPolicy) ===
859
+ canonicalJson(bindings.executionPolicy)
860
+ );
861
+ }
862
+
863
+ function createState(bindings, now) {
864
+ const at = isoNow(now);
865
+ const state = {
866
+ schema: DYNAMIC_WORKFLOW_RUNTIME_SCHEMA,
867
+ version: DYNAMIC_WORKFLOW_RUNTIME_VERSION,
868
+ ...bindings,
869
+ revision: 0,
870
+ status: "ready",
871
+ effects: [],
872
+ finalRecord: null,
873
+ createdAt: at,
874
+ updatedAt: at,
875
+ lineage: [],
876
+ };
877
+ appendLineage(
878
+ state,
879
+ "run-created",
880
+ { definitionDigest: bindings.definitionDigest },
881
+ at,
882
+ );
883
+ return finalizeState(state);
884
+ }
885
+
886
+ function controlError(message, reason, state, effect = null, cause = null) {
887
+ const error = new Error(message, cause ? { cause } : undefined);
888
+ error.code = DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE;
889
+ error.reason = reason;
890
+ error.runtimeState = state;
891
+ if (effect) error.pendingEffect = effect;
892
+ return error;
893
+ }
894
+
895
+ function startRun(statePath, bindings, now) {
896
+ return withStateMutation(statePath, (current) => {
897
+ let state = current || createState(bindings, now);
898
+ if (!bindingsMatch(state, bindings)) {
899
+ throw new Error(
900
+ "durable workflow run authority does not match stored state",
901
+ );
902
+ }
903
+ if (state.status === "completed") return { value: state };
904
+ if (state.status !== "ready") {
905
+ throw new Error(
906
+ `durable workflow run is ${state.status}; use explicit control or reconciliation`,
907
+ );
908
+ }
909
+ state = transition(
910
+ state,
911
+ "run-started",
912
+ {},
913
+ (draft) => {
914
+ draft.status = "running";
915
+ },
916
+ now,
917
+ );
918
+ return { state, value: state };
919
+ });
920
+ }
921
+
922
+ function effectKey(effect) {
923
+ const stepId = String(effect?.stepId || "");
924
+ const iteration = Number(effect?.iteration);
925
+ const attempt = Number(effect?.attempt);
926
+ if (
927
+ !stepId ||
928
+ stepId.length > 512 ||
929
+ !Number.isSafeInteger(iteration) ||
930
+ iteration < 1 ||
931
+ !Number.isSafeInteger(attempt) ||
932
+ attempt < 1
933
+ ) {
934
+ throw new TypeError("workflow effect context is invalid");
935
+ }
936
+ return `${stepId}\u0000${iteration}\u0000${attempt}`;
937
+ }
938
+
939
+ function effectPayload(args) {
940
+ const { signal: _signal, workflowEffect: _workflowEffect, ...payload } = args;
941
+ return snapshotJson(payload, "workflow effect payload", 1024 * 1024);
942
+ }
943
+
944
+ function prepareEffectRequest(args) {
945
+ const key = effectKey(args.workflowEffect);
946
+ const stepId = String(args.workflowEffect.stepId);
947
+ const iteration = Number(args.workflowEffect.iteration);
948
+ const attempt = Number(args.workflowEffect.attempt);
949
+ const timeoutMs = Number(args.workflowEffect.timeoutMs || 0);
950
+ if (!Number.isFinite(timeoutMs) || timeoutMs < 0) {
951
+ throw new TypeError("workflow effect timeout context is invalid");
952
+ }
953
+ const payload = effectPayload(args);
954
+ const payloadDigest = digest(
955
+ "chainlesschain.dynamic-workflow.effect-payload.v1\0",
956
+ payload,
957
+ );
958
+ return { key, stepId, iteration, attempt, timeoutMs, payloadDigest };
959
+ }
960
+
961
+ function requestEffectBatch(statePath, runId, argsList, now) {
962
+ if (!Array.isArray(argsList) || argsList.length === 0) {
963
+ throw new TypeError("workflow effect request batch is empty");
964
+ }
965
+ if (argsList.length > MAX_EFFECT_BATCH_SIZE) {
966
+ throw new Error("workflow effect request batch exceeds its hard limit");
967
+ }
968
+ const requests = argsList.map((args) => ({
969
+ ...prepareEffectRequest(args),
970
+ }));
971
+ const batchKeys = new Set(requests.map((request) => request.key));
972
+ if (batchKeys.size !== requests.length) {
973
+ throw new Error("workflow effect request batch contains duplicate keys");
974
+ }
975
+ return withStateMutation(statePath, (current) => {
976
+ if (!current || current.runId !== runId) {
977
+ throw new Error(
978
+ "durable workflow run disappeared before effect batch request",
979
+ );
980
+ }
981
+ if (current.status === "pause_requested") {
982
+ return {
983
+ value: { control: "paused", state: current, results: null },
984
+ };
985
+ }
986
+ if (current.status === "stopped") {
987
+ return {
988
+ value: { control: "stopped", state: current, results: null },
989
+ };
990
+ }
991
+ if (current.status !== "running") {
992
+ throw new Error(
993
+ `durable workflow run cannot request an effect batch while ${current.status}`,
994
+ );
995
+ }
996
+ const existingByKey = new Map(
997
+ current.effects.map((effect) => [effect.key, effect]),
998
+ );
999
+ for (const request of requests) {
1000
+ const existing = existingByKey.get(request.key);
1001
+ if (existing && existing.payloadDigest !== request.payloadDigest) {
1002
+ throw new Error("workflow effect payload drifted during replay");
1003
+ }
1004
+ }
1005
+ const existingPending = requests
1006
+ .map((request) => existingByKey.get(request.key))
1007
+ .find(
1008
+ (effect) =>
1009
+ effect?.status === "pending" && !isKnownUndispatched(effect),
1010
+ );
1011
+ if (existingPending) {
1012
+ const state = transition(
1013
+ current,
1014
+ "effect-batch-reconciliation-required",
1015
+ { effectId: existingPending.id },
1016
+ (draft) => {
1017
+ draft.status = "blocked";
1018
+ },
1019
+ now,
1020
+ );
1021
+ return {
1022
+ state,
1023
+ value: {
1024
+ state,
1025
+ results: requests.map(() => ({
1026
+ pending: true,
1027
+ effect: existingPending,
1028
+ state,
1029
+ })),
1030
+ },
1031
+ };
1032
+ }
1033
+ const newRequests = [];
1034
+ const resultsByKey = new Map();
1035
+ for (const request of requests) {
1036
+ const existing = existingByKey.get(request.key);
1037
+ if (!existing) {
1038
+ newRequests.push(request);
1039
+ continue;
1040
+ }
1041
+ if (existing.status === "pending" && isKnownUndispatched(existing)) {
1042
+ resultsByKey.set(request.key, {
1043
+ cached: false,
1044
+ effect: existing,
1045
+ });
1046
+ continue;
1047
+ }
1048
+ resultsByKey.set(request.key, {
1049
+ cached: true,
1050
+ effect: existing,
1051
+ result: existing.result,
1052
+ });
1053
+ }
1054
+ if (current.effects.length + newRequests.length > MAX_EFFECTS) {
1055
+ throw new Error("durable workflow effect limit exceeded");
1056
+ }
1057
+ if (newRequests.length === 0) {
1058
+ return {
1059
+ value: {
1060
+ state: current,
1061
+ results: requests.map((request) => resultsByKey.get(request.key)),
1062
+ },
1063
+ };
1064
+ }
1065
+ const ordered = [...newRequests].sort((left, right) =>
1066
+ left.key.localeCompare(right.key),
1067
+ );
1068
+ const batchId = effectBatchId(runId, ordered);
1069
+ const requestedAt = isoNow(now);
1070
+ const effects = ordered.map((request, batchIndex) => {
1071
+ const id = digest("chainlesschain.dynamic-workflow.effect-id.v1\0", {
1072
+ runId,
1073
+ key: request.key,
1074
+ payloadDigest: request.payloadDigest,
1075
+ });
1076
+ return {
1077
+ schema: DYNAMIC_WORKFLOW_EFFECT_SCHEMA,
1078
+ id,
1079
+ key: request.key,
1080
+ stepId: request.stepId,
1081
+ iteration: request.iteration,
1082
+ attempt: request.attempt,
1083
+ payloadDigest: request.payloadDigest,
1084
+ batchId,
1085
+ batchIndex,
1086
+ batchSize: ordered.length,
1087
+ timeoutMs: request.timeoutMs,
1088
+ timeoutObservedAt: null,
1089
+ providerDispatchedAt: null,
1090
+ calls: [],
1091
+ status: "pending",
1092
+ requestedAt,
1093
+ settledAt: null,
1094
+ settlementAuthority: null,
1095
+ resultDigest: null,
1096
+ result: null,
1097
+ };
1098
+ });
1099
+ for (const effect of effects) {
1100
+ resultsByKey.set(effect.key, { cached: false, effect });
1101
+ }
1102
+ const state = transition(
1103
+ current,
1104
+ "effect-batch-requested",
1105
+ {
1106
+ batchId,
1107
+ effectIds: effects.map((effect) => effect.id),
1108
+ effectCount: effects.length,
1109
+ },
1110
+ (draft) => {
1111
+ draft.effects.push(...effects);
1112
+ },
1113
+ now,
1114
+ );
1115
+ return {
1116
+ state,
1117
+ value: {
1118
+ state,
1119
+ results: requests.map((request) => resultsByKey.get(request.key)),
1120
+ },
1121
+ };
1122
+ });
1123
+ }
1124
+
1125
+ function createEffectRequestBatcher(statePath, runId, now) {
1126
+ let queue = [];
1127
+ let scheduled = false;
1128
+ const flush = () => {
1129
+ scheduled = false;
1130
+ const batch = queue;
1131
+ queue = [];
1132
+ try {
1133
+ const requested = requestEffectBatch(
1134
+ statePath,
1135
+ runId,
1136
+ batch.map((entry) => entry.args),
1137
+ now,
1138
+ );
1139
+ for (let index = 0; index < batch.length; index += 1) {
1140
+ if (requested.control) batch[index].resolve(requested);
1141
+ else batch[index].resolve(requested.results[index]);
1142
+ }
1143
+ } catch (error) {
1144
+ for (const entry of batch) entry.reject(error);
1145
+ }
1146
+ };
1147
+ return (args) =>
1148
+ new Promise((resolve, reject) => {
1149
+ queue.push({ args, resolve, reject });
1150
+ if (scheduled) return;
1151
+ scheduled = true;
1152
+ queueMicrotask(flush);
1153
+ });
1154
+ }
1155
+
1156
+ function markEffectProviderDispatched(statePath, runId, effectId, now) {
1157
+ return withStateMutation(statePath, (current) => {
1158
+ if (!current || current.runId !== runId) {
1159
+ throw new Error("durable workflow run disappeared before dispatch");
1160
+ }
1161
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
1162
+ const effect = current.effects[index];
1163
+ if (!effect || effect.status !== "pending") {
1164
+ throw new Error("workflow effect is not pending before dispatch");
1165
+ }
1166
+ if (!isKnownUndispatched(effect)) {
1167
+ throw new Error("workflow effect dispatch authority is unavailable");
1168
+ }
1169
+ if (current.status === "stopped") {
1170
+ return { value: { control: "stopped", state: current, effect } };
1171
+ }
1172
+ if (!["running", "pause_requested", "blocked"].includes(current.status)) {
1173
+ throw new Error(
1174
+ `workflow effect cannot be dispatched while ${current.status}`,
1175
+ );
1176
+ }
1177
+ const providerDispatchedAt = isoNow(now);
1178
+ const state = transition(
1179
+ current,
1180
+ "effect-provider-dispatched",
1181
+ { effectId, providerDispatchedAt },
1182
+ (draft) => {
1183
+ draft.effects[index] = {
1184
+ ...draft.effects[index],
1185
+ providerDispatchedAt,
1186
+ };
1187
+ },
1188
+ now,
1189
+ );
1190
+ return {
1191
+ state,
1192
+ value: {
1193
+ effect: state.effects[index],
1194
+ state,
1195
+ },
1196
+ };
1197
+ });
1198
+ }
1199
+
1200
+ function observeEffectTimeout(statePath, runId, effectId, now) {
1201
+ return withStateMutation(statePath, (current) => {
1202
+ if (!current || current.runId !== runId) return { value: current };
1203
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
1204
+ const effect = current.effects[index];
1205
+ if (
1206
+ !effect ||
1207
+ effect.status !== "pending" ||
1208
+ !hasProviderDispatchMetadata(effect) ||
1209
+ typeof effect.providerDispatchedAt !== "string" ||
1210
+ !(effect.timeoutMs > 0) ||
1211
+ effect.timeoutObservedAt !== null
1212
+ ) {
1213
+ return { value: current };
1214
+ }
1215
+ const timeoutObservedAt = isoNow(now);
1216
+ const state = transition(
1217
+ current,
1218
+ "effect-timeout-observed",
1219
+ { effectId, timeoutMs: effect.timeoutMs, timeoutObservedAt },
1220
+ (draft) => {
1221
+ draft.effects[index] = {
1222
+ ...draft.effects[index],
1223
+ timeoutObservedAt,
1224
+ };
1225
+ },
1226
+ now,
1227
+ );
1228
+ return { state, value: state };
1229
+ });
1230
+ }
1231
+
1232
+ function ownDataValue(value, property) {
1233
+ if (!value || (typeof value !== "object" && typeof value !== "function")) {
1234
+ return undefined;
1235
+ }
1236
+ try {
1237
+ const descriptor = Object.getOwnPropertyDescriptor(value, property);
1238
+ return descriptor && Object.hasOwn(descriptor, "value")
1239
+ ? descriptor.value
1240
+ : undefined;
1241
+ } catch {
1242
+ return undefined;
1243
+ }
1244
+ }
1245
+
1246
+ function workflowCallAttempt(effectId, kind, event, now) {
1247
+ const startedAt = isoNow(now);
1248
+ const ownerEffectId = event?.workflowEffectId;
1249
+ if (kind === "provider") {
1250
+ const requestSource =
1251
+ event?.workflowRequestSource ||
1252
+ (event?.source === "subagent" ? "model" : event?.source);
1253
+ if (
1254
+ event?.type !== "model-usage-started" ||
1255
+ !SHA256_RE.test(ownerEffectId || "") ||
1256
+ !WORKFLOW_PROVIDER_CALL_ID_RE.test(event.callId || "") ||
1257
+ !PROVIDER_NAME_RE.test(event.provider || "") ||
1258
+ !["model", "semantic-compaction", "subagent"].includes(event.source) ||
1259
+ !["model", "semantic-compaction"].includes(requestSource) ||
1260
+ (event.source === "subagent"
1261
+ ? requestSource !== "model"
1262
+ : requestSource !== event.source) ||
1263
+ !Number.isSafeInteger(event.callSequence) ||
1264
+ event.callSequence < 1 ||
1265
+ !WORKFLOW_PROVIDER_REQUEST_ID_RE.test(event.providerRequestId || "") ||
1266
+ event.providerRequestId !==
1267
+ expectedWorkflowProviderRequestId(
1268
+ ownerEffectId,
1269
+ requestSource,
1270
+ event.callSequence,
1271
+ ) ||
1272
+ event.requestIdentitySemantics !== "trace-only"
1273
+ ) {
1274
+ throw new TypeError("workflow provider call boundary is malformed");
1275
+ }
1276
+ return {
1277
+ schema: DYNAMIC_WORKFLOW_CALL_SCHEMA,
1278
+ id: workflowCallId(effectId, kind, event.callId, null, ownerEffectId),
1279
+ effectId,
1280
+ ownerEffectId,
1281
+ kind,
1282
+ protocol: WORKFLOW_PROVIDER_ATTEMPT_PROTOCOL,
1283
+ callId: event.callId,
1284
+ childEffectId: null,
1285
+ sequence: event.callSequence,
1286
+ name: event.provider,
1287
+ source: event.source,
1288
+ requestSource,
1289
+ clientRequestId: event.providerRequestId,
1290
+ identitySemantics: "trace-only",
1291
+ status: "started",
1292
+ startedAt,
1293
+ settledAt: null,
1294
+ outcomeUnknown: false,
1295
+ settlementCode: null,
1296
+ providerReceiptPersisted: false,
1297
+ providerReceiptRequestId: null,
1298
+ providerReceiptResponseId: null,
1299
+ mcpLedgerId: null,
1300
+ mcpLedgerPrewritePersisted: false,
1301
+ mcpLedgerSettlementPersisted: false,
1302
+ };
1303
+ }
1304
+ if (
1305
+ kind !== "tool" ||
1306
+ event?.type !== "tool-executing" ||
1307
+ !SHA256_RE.test(ownerEffectId || "") ||
1308
+ event.workflowEffectProtocol !== WORKFLOW_CHILD_EFFECT_PROTOCOL ||
1309
+ !SHA256_RE.test(event.workflowChildEffectId || "") ||
1310
+ !Number.isSafeInteger(event.workflowChildSequence) ||
1311
+ event.workflowChildSequence < 1 ||
1312
+ !WORKFLOW_TOOL_CALL_ID_RE.test(event.tool_use_id || "") ||
1313
+ !WORKFLOW_TOOL_NAME_RE.test(event.tool || "") ||
1314
+ event.workflowChildEffectId !==
1315
+ expectedNestedToolEffectId(
1316
+ ownerEffectId,
1317
+ event.workflowChildSequence,
1318
+ event.tool_use_id,
1319
+ event.tool,
1320
+ )
1321
+ ) {
1322
+ throw new TypeError("workflow tool call boundary is malformed");
1323
+ }
1324
+ return {
1325
+ schema: DYNAMIC_WORKFLOW_CALL_SCHEMA,
1326
+ id: workflowCallId(
1327
+ effectId,
1328
+ kind,
1329
+ event.tool_use_id,
1330
+ event.workflowChildEffectId,
1331
+ ownerEffectId,
1332
+ ),
1333
+ effectId,
1334
+ ownerEffectId,
1335
+ kind,
1336
+ protocol: WORKFLOW_CHILD_EFFECT_PROTOCOL,
1337
+ callId: event.tool_use_id,
1338
+ childEffectId: event.workflowChildEffectId,
1339
+ sequence: event.workflowChildSequence,
1340
+ name: event.tool,
1341
+ source: "tool",
1342
+ requestSource: null,
1343
+ clientRequestId: null,
1344
+ identitySemantics: "runtime-derived",
1345
+ status: "started",
1346
+ startedAt,
1347
+ settledAt: null,
1348
+ outcomeUnknown: false,
1349
+ settlementCode: null,
1350
+ providerReceiptPersisted: false,
1351
+ providerReceiptRequestId: null,
1352
+ providerReceiptResponseId: null,
1353
+ mcpLedgerId: null,
1354
+ mcpLedgerPrewritePersisted: false,
1355
+ mcpLedgerSettlementPersisted: false,
1356
+ };
1357
+ }
1358
+
1359
+ function beginEffectCall(statePath, runId, effectId, kind, event, now) {
1360
+ const attempt = workflowCallAttempt(effectId, kind, event, now);
1361
+ return withStateMutation(statePath, (current) => {
1362
+ if (!current || current.runId !== runId) {
1363
+ throw new Error("durable workflow run disappeared before call boundary");
1364
+ }
1365
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
1366
+ const effect = current.effects[index];
1367
+ const calls = effect?.calls || [];
1368
+ if (
1369
+ !effect ||
1370
+ effect.status !== "pending" ||
1371
+ typeof effect.providerDispatchedAt !== "string"
1372
+ ) {
1373
+ throw new Error("workflow effect cannot begin a durable child call");
1374
+ }
1375
+ if (calls.length >= MAX_CALLS_PER_EFFECT) {
1376
+ throw new Error("workflow effect child-call limit exceeded");
1377
+ }
1378
+ if (
1379
+ attempt.ownerEffectId !== effectId &&
1380
+ !calls.some(
1381
+ (call) =>
1382
+ call.kind === "tool" &&
1383
+ call.childEffectId === attempt.ownerEffectId &&
1384
+ DESCENDANT_OWNER_TOOL_NAMES.has(call.name),
1385
+ )
1386
+ ) {
1387
+ throw new Error("workflow descendant call owner is not authorized");
1388
+ }
1389
+ if (
1390
+ calls.some(
1391
+ (call) =>
1392
+ call.id === attempt.id ||
1393
+ (call.kind === attempt.kind &&
1394
+ (attempt.kind === "tool"
1395
+ ? call.childEffectId === attempt.childEffectId
1396
+ : call.callId === attempt.callId)),
1397
+ )
1398
+ ) {
1399
+ throw new Error("workflow child-call boundary is duplicated");
1400
+ }
1401
+ const state = transition(
1402
+ current,
1403
+ "effect-call-started",
1404
+ {
1405
+ effectId,
1406
+ ownerEffectId: attempt.ownerEffectId,
1407
+ callRecordId: attempt.id,
1408
+ kind,
1409
+ },
1410
+ (draft) => {
1411
+ draft.effects[index] = {
1412
+ ...draft.effects[index],
1413
+ calls: [...(draft.effects[index].calls || []), attempt],
1414
+ };
1415
+ },
1416
+ now,
1417
+ );
1418
+ return { state, value: state.effects[index].calls.at(-1) };
1419
+ });
1420
+ }
1421
+
1422
+ function providerReceiptSettlement(call, event) {
1423
+ const receipt = ownDataValue(event, "providerReceipt");
1424
+ if (receipt === null || receipt === undefined) {
1425
+ return {
1426
+ persisted: false,
1427
+ requestId: null,
1428
+ responseId: null,
1429
+ };
1430
+ }
1431
+ let descriptors;
1432
+ try {
1433
+ if (
1434
+ typeof receipt !== "object" ||
1435
+ Array.isArray(receipt) ||
1436
+ utilTypes.isProxy(receipt)
1437
+ ) {
1438
+ throw new TypeError();
1439
+ }
1440
+ descriptors = Object.getOwnPropertyDescriptors(receipt);
1441
+ } catch {
1442
+ throw new TypeError("workflow provider receipt settlement is malformed");
1443
+ }
1444
+ const expectedFields = [
1445
+ "callId",
1446
+ "callSequence",
1447
+ "clientRequestId",
1448
+ "independentlyReadable",
1449
+ "protocol",
1450
+ "provider",
1451
+ "requestId",
1452
+ "requestIdentitySemantics",
1453
+ "responseId",
1454
+ "source",
1455
+ "workflowEffectId",
1456
+ ];
1457
+ const ownFields = Reflect.ownKeys(descriptors);
1458
+ const fields = ownFields.filter((field) => typeof field === "string").sort();
1459
+ const hasOnlyDataFields =
1460
+ ownFields.length === expectedFields.length &&
1461
+ fields.length === expectedFields.length &&
1462
+ fields.every(
1463
+ (field, index) =>
1464
+ field === expectedFields[index] &&
1465
+ descriptors[field].enumerable === true &&
1466
+ Object.hasOwn(descriptors[field], "value"),
1467
+ );
1468
+ const value = (field) => descriptors?.[field]?.value;
1469
+ const requestId = value("requestId");
1470
+ const responseId = value("responseId");
1471
+ if (
1472
+ !hasOnlyDataFields ||
1473
+ value("protocol") !== "cc-provider-request-receipt/v1" ||
1474
+ value("provider") !== call.name ||
1475
+ value("workflowEffectId") !== (call.ownerEffectId ?? call.effectId) ||
1476
+ value("callId") !== call.callId ||
1477
+ value("callSequence") !== call.sequence ||
1478
+ value("source") !== call.requestSource ||
1479
+ value("clientRequestId") !== call.clientRequestId ||
1480
+ value("requestIdentitySemantics") !== "trace-only" ||
1481
+ value("independentlyReadable") !== false ||
1482
+ (requestId !== null &&
1483
+ (typeof requestId !== "string" ||
1484
+ !PROVIDER_RECEIPT_ID_RE.test(requestId))) ||
1485
+ (responseId !== null &&
1486
+ (typeof responseId !== "string" ||
1487
+ !PROVIDER_RECEIPT_ID_RE.test(responseId))) ||
1488
+ (!requestId && !responseId)
1489
+ ) {
1490
+ throw new TypeError("workflow provider receipt settlement is malformed");
1491
+ }
1492
+ return { persisted: true, requestId, responseId };
1493
+ }
1494
+
1495
+ function effectCallSettlement(call, event, now) {
1496
+ let status;
1497
+ let settlementCode = null;
1498
+ let providerReceiptPersisted = false;
1499
+ let providerReceiptRequestId = null;
1500
+ let providerReceiptResponseId = null;
1501
+ let mcpLedgerId = null;
1502
+ let mcpLedgerPrewritePersisted = false;
1503
+ let mcpLedgerSettlementPersisted = false;
1504
+ if (call.kind === "provider") {
1505
+ if (
1506
+ !["token-usage", "model-usage-unknown"].includes(event?.type) ||
1507
+ event.callId !== call.callId ||
1508
+ (event.provider && event.provider !== call.name) ||
1509
+ (event.source && event.source !== call.source)
1510
+ ) {
1511
+ throw new TypeError("workflow provider call settlement is malformed");
1512
+ }
1513
+ status = event.type === "token-usage" ? "completed" : "outcome_unknown";
1514
+ settlementCode =
1515
+ status === "outcome_unknown"
1516
+ ? String(event.code || "provider_outcome_unknown")
1517
+ : null;
1518
+ const providerReceipt = providerReceiptSettlement(call, event);
1519
+ providerReceiptPersisted = providerReceipt.persisted;
1520
+ providerReceiptRequestId = providerReceipt.requestId;
1521
+ providerReceiptResponseId = providerReceipt.responseId;
1522
+ } else {
1523
+ if (
1524
+ event?.type !== "tool-result" ||
1525
+ event.workflowEffectId !== (call.ownerEffectId ?? call.effectId) ||
1526
+ event.workflowEffectProtocol !== WORKFLOW_CHILD_EFFECT_PROTOCOL ||
1527
+ event.workflowChildEffectId !== call.childEffectId ||
1528
+ event.workflowChildSequence !== call.sequence ||
1529
+ event.tool_use_id !== call.callId ||
1530
+ event.tool !== call.name
1531
+ ) {
1532
+ throw new TypeError("workflow tool call settlement is malformed");
1533
+ }
1534
+ const outcomeUnknown =
1535
+ ownDataValue(event.result, "outcomeUnknown") === true;
1536
+ const failed = Boolean(event.error || ownDataValue(event.result, "error"));
1537
+ status = outcomeUnknown
1538
+ ? "outcome_unknown"
1539
+ : failed
1540
+ ? "failed"
1541
+ : "completed";
1542
+ settlementCode = outcomeUnknown
1543
+ ? "nested_tool_outcome_unknown"
1544
+ : failed
1545
+ ? "tool_failed"
1546
+ : null;
1547
+ mcpLedgerId = ownDataValue(event.result, "mcpLedgerId") ?? null;
1548
+ mcpLedgerPrewritePersisted =
1549
+ ownDataValue(event.result, "mcpLedgerPrewritePersisted") === true;
1550
+ mcpLedgerSettlementPersisted =
1551
+ ownDataValue(event.result, "mcpLedgerSettlementPersisted") === true;
1552
+ if (
1553
+ (mcpLedgerId !== null &&
1554
+ !PROVIDER_RECEIPT_ID_RE.test(String(mcpLedgerId))) ||
1555
+ (mcpLedgerSettlementPersisted && !mcpLedgerPrewritePersisted)
1556
+ ) {
1557
+ throw new TypeError("workflow MCP call settlement is malformed");
1558
+ }
1559
+ }
1560
+ if (
1561
+ settlementCode !== null &&
1562
+ (!/^[A-Za-z0-9._:-]{1,128}$/u.test(settlementCode) ||
1563
+ settlementCode.includes(".."))
1564
+ ) {
1565
+ throw new TypeError("workflow call settlement code is malformed");
1566
+ }
1567
+ return {
1568
+ ...call,
1569
+ status,
1570
+ settledAt: isoNow(now),
1571
+ outcomeUnknown: status === "outcome_unknown",
1572
+ settlementCode,
1573
+ providerReceiptPersisted,
1574
+ providerReceiptRequestId,
1575
+ providerReceiptResponseId,
1576
+ mcpLedgerId,
1577
+ mcpLedgerPrewritePersisted,
1578
+ mcpLedgerSettlementPersisted,
1579
+ };
1580
+ }
1581
+
1582
+ function settleEffectCall(statePath, runId, effectId, kind, event, now) {
1583
+ return withStateMutation(statePath, (current) => {
1584
+ if (!current || current.runId !== runId) {
1585
+ throw new Error(
1586
+ "durable workflow run disappeared before call settlement",
1587
+ );
1588
+ }
1589
+ const effectIndex = current.effects.findIndex(
1590
+ (effect) => effect.id === effectId,
1591
+ );
1592
+ const effect = current.effects[effectIndex];
1593
+ const callIndex = (effect?.calls || []).findIndex((call) =>
1594
+ kind === "tool"
1595
+ ? call.kind === kind &&
1596
+ call.childEffectId === event?.workflowChildEffectId
1597
+ : call.kind === kind && call.callId === event?.callId,
1598
+ );
1599
+ const call = effect?.calls?.[callIndex];
1600
+ if (
1601
+ !effect ||
1602
+ effect.status !== "pending" ||
1603
+ !call ||
1604
+ call.status !== "started"
1605
+ ) {
1606
+ throw new Error("workflow child call is not started at settlement");
1607
+ }
1608
+ const settlement = effectCallSettlement(call, event, now);
1609
+ const state = transition(
1610
+ current,
1611
+ "effect-call-settled",
1612
+ {
1613
+ effectId,
1614
+ callRecordId: call.id,
1615
+ kind,
1616
+ status: settlement.status,
1617
+ },
1618
+ (draft) => {
1619
+ const calls = [...(draft.effects[effectIndex].calls || [])];
1620
+ calls[callIndex] = settlement;
1621
+ draft.effects[effectIndex] = {
1622
+ ...draft.effects[effectIndex],
1623
+ calls,
1624
+ };
1625
+ },
1626
+ now,
1627
+ );
1628
+ return { state, value: state.effects[effectIndex].calls[callIndex] };
1629
+ });
1630
+ }
1631
+
1632
+ function createDurableCallObservers(statePath, runId, effectId, now) {
1633
+ return Object.freeze({
1634
+ strictUsageTelemetry: true,
1635
+ onUsageBoundary(event) {
1636
+ beginEffectCall(statePath, runId, effectId, "provider", event, now);
1637
+ },
1638
+ onUsageSettlement(event) {
1639
+ settleEffectCall(statePath, runId, effectId, "provider", event, now);
1640
+ },
1641
+ onToolCallBoundary(event) {
1642
+ beginEffectCall(statePath, runId, effectId, "tool", event, now);
1643
+ },
1644
+ onToolCallSettlement(event) {
1645
+ settleEffectCall(statePath, runId, effectId, "tool", event, now);
1646
+ },
1647
+ });
1648
+ }
1649
+
1650
+ function runtimeNotDispatchedResult(effect, reason) {
1651
+ if (reason !== "timeout" && reason !== "stopped") {
1652
+ throw new TypeError("undispatched workflow effect reason is invalid");
1653
+ }
1654
+ const timeout = reason === "timeout";
1655
+ return {
1656
+ taskId: `runtime-${effect.id.slice("sha256:".length, 39)}`,
1657
+ status: "failed",
1658
+ result: {
1659
+ summary: timeout
1660
+ ? `step timed out after ${effect.timeoutMs}ms before provider dispatch`
1661
+ : "workflow stopped before provider dispatch",
1662
+ providerDispatched: false,
1663
+ reason: timeout ? "timeout-before-dispatch" : "stopped-before-dispatch",
1664
+ },
1665
+ };
1666
+ }
1667
+
1668
+ function settleUndispatchedEffect(statePath, runId, effectId, reason, now) {
1669
+ return withStateMutation(statePath, (current) => {
1670
+ if (!current || current.runId !== runId) {
1671
+ throw new Error(
1672
+ "durable workflow run disappeared before undispatched settlement",
1673
+ );
1674
+ }
1675
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
1676
+ const effect = current.effects[index];
1677
+ if (
1678
+ !effect ||
1679
+ effect.status !== "pending" ||
1680
+ !isKnownUndispatched(effect) ||
1681
+ (reason === "timeout" && !(effect.timeoutMs > 0)) ||
1682
+ (reason === "stopped" && current.status !== "stopped")
1683
+ ) {
1684
+ throw new Error("workflow effect is not safely undispatched");
1685
+ }
1686
+ const result = runtimeNotDispatchedResult(effect, reason);
1687
+ const resultDigest = effectResultDigest(result);
1688
+ const timeoutObservedAt =
1689
+ reason === "timeout" && effect.timeoutObservedAt === null
1690
+ ? isoNow(now)
1691
+ : effect.timeoutObservedAt;
1692
+ const settledAt = isoNow(now);
1693
+ const state = transition(
1694
+ current,
1695
+ "effect-not-dispatched",
1696
+ {
1697
+ effectId,
1698
+ reason,
1699
+ resultDigest,
1700
+ settlementAuthority: "runtime-not-dispatched",
1701
+ },
1702
+ (draft) => {
1703
+ draft.effects[index] = {
1704
+ ...draft.effects[index],
1705
+ status: "settled",
1706
+ timeoutObservedAt,
1707
+ settledAt,
1708
+ settlementAuthority: "runtime-not-dispatched",
1709
+ resultDigest,
1710
+ result,
1711
+ };
1712
+ },
1713
+ now,
1714
+ );
1715
+ return { state, value: result };
1716
+ });
1717
+ }
1718
+
1719
+ function settleEffect(statePath, runId, effectId, rawResult, now) {
1720
+ const result = snapshotJson(rawResult, "workflow effect result", 1024 * 1024);
1721
+ const resultDigest = effectResultDigest(result);
1722
+ if (containsSecret(JSON.stringify(result))) {
1723
+ throw new Error("workflow effect result contains secret-shaped data");
1724
+ }
1725
+ return withStateMutation(statePath, (current) => {
1726
+ if (!current || current.runId !== runId) {
1727
+ throw new Error("durable workflow run disappeared before settlement");
1728
+ }
1729
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
1730
+ const effect = current.effects[index];
1731
+ if (!effect || effect.status !== "pending") {
1732
+ throw new Error("workflow effect is not pending at settlement");
1733
+ }
1734
+ if (
1735
+ hasProviderDispatchMetadata(effect) &&
1736
+ typeof effect.providerDispatchedAt !== "string"
1737
+ ) {
1738
+ throw new Error("workflow effect provider dispatch was not persisted");
1739
+ }
1740
+ if (
1741
+ (effect.calls || []).some((call) =>
1742
+ ["started", "outcome_unknown"].includes(call.status),
1743
+ )
1744
+ ) {
1745
+ throw new Error(
1746
+ "workflow effect has an unsettled or outcome-unknown durable child call",
1747
+ );
1748
+ }
1749
+ const settledAt = isoNow(now);
1750
+ const state = transition(
1751
+ current,
1752
+ "effect-settled",
1753
+ { effectId, resultDigest, settlementAuthority: "provider-return" },
1754
+ (draft) => {
1755
+ draft.effects[index] = {
1756
+ ...draft.effects[index],
1757
+ status: "settled",
1758
+ settledAt,
1759
+ settlementAuthority: "provider-return",
1760
+ resultDigest,
1761
+ result,
1762
+ };
1763
+ },
1764
+ now,
1765
+ );
1766
+ return { state, value: result };
1767
+ });
1768
+ }
1769
+
1770
+ function blockPendingEffect(statePath, runId, effectId, now) {
1771
+ return withStateMutation(statePath, (current) => {
1772
+ if (!current || current.runId !== runId) return { value: current };
1773
+ const effect = current.effects.find((entry) => entry.id === effectId);
1774
+ if (
1775
+ !effect ||
1776
+ effect.status !== "pending" ||
1777
+ current.status === "stopped"
1778
+ ) {
1779
+ return { value: current };
1780
+ }
1781
+ if (current.status === "blocked") return { value: current };
1782
+ const state = transition(
1783
+ current,
1784
+ "effect-reconciliation-required",
1785
+ { effectId },
1786
+ (draft) => {
1787
+ draft.status = "blocked";
1788
+ },
1789
+ now,
1790
+ );
1791
+ return { state, value: state };
1792
+ });
1793
+ }
1794
+
1795
+ function finalizePauseAfterSettlementBarrier(statePath, runId, now) {
1796
+ return withStateMutation(statePath, (current) => {
1797
+ if (!current || current.runId !== runId) return { value: current };
1798
+ if (current.status !== "pause_requested") return { value: current };
1799
+ if (current.effects.some((effect) => effect.status === "pending")) {
1800
+ return { value: current };
1801
+ }
1802
+ const state = transition(
1803
+ current,
1804
+ "run-paused",
1805
+ { barrier: "all-requested-effects-settled" },
1806
+ (draft) => {
1807
+ draft.status = "paused";
1808
+ },
1809
+ now,
1810
+ );
1811
+ return { state, value: state };
1812
+ });
1813
+ }
1814
+
1815
+ function completeRun(statePath, runId, record, now) {
1816
+ const finalRecord = snapshotJson(
1817
+ record,
1818
+ "workflow final record",
1819
+ 2 * 1024 * 1024,
1820
+ );
1821
+ return withStateMutation(statePath, (current) => {
1822
+ if (!current || current.runId !== runId) {
1823
+ throw new Error("durable workflow run disappeared before completion");
1824
+ }
1825
+ if (current.status === "pause_requested") {
1826
+ const state = transition(
1827
+ current,
1828
+ "run-paused",
1829
+ {},
1830
+ (draft) => {
1831
+ draft.status = "paused";
1832
+ },
1833
+ now,
1834
+ );
1835
+ return { state, value: { control: "paused", state } };
1836
+ }
1837
+ if (current.status === "stopped") {
1838
+ return { value: { control: "stopped", state: current } };
1839
+ }
1840
+ if (current.status !== "running") {
1841
+ throw new Error(
1842
+ `durable workflow run cannot complete while ${current.status}`,
1843
+ );
1844
+ }
1845
+ const state = transition(
1846
+ current,
1847
+ "run-completed",
1848
+ { recordStatus: finalRecord.status },
1849
+ (draft) => {
1850
+ draft.status = "completed";
1851
+ draft.finalRecord = finalRecord;
1852
+ },
1853
+ now,
1854
+ );
1855
+ return { state, value: { record: finalRecord, state } };
1856
+ });
1857
+ }
1858
+
1859
+ function markFailed(statePath, runId, error, now) {
1860
+ return withStateMutation(statePath, (current) => {
1861
+ if (
1862
+ !current ||
1863
+ current.runId !== runId ||
1864
+ TERMINAL_STATUSES.has(current.status) ||
1865
+ ["paused", "blocked", "pause_requested"].includes(current.status)
1866
+ ) {
1867
+ return { value: current };
1868
+ }
1869
+ const state = transition(
1870
+ current,
1871
+ "run-failed",
1872
+ { code: String(error?.code || "WORKFLOW_RUN_FAILED").slice(0, 256) },
1873
+ (draft) => {
1874
+ draft.status = "failed";
1875
+ },
1876
+ now,
1877
+ );
1878
+ return { state, value: state };
1879
+ });
1880
+ }
1881
+
1882
+ export async function executeDurableDynamicWorkflow(options = {}, deps = {}) {
1883
+ const statePath = path.resolve(options.statePath);
1884
+ const runId = safeId(options.runId, "runId");
1885
+ const execution = options.execution;
1886
+ const bindings = stateBindings(execution, runId);
1887
+ if (typeof deps.runTask !== "function") {
1888
+ throw new TypeError("durable workflow task provider is required");
1889
+ }
1890
+ const initial = startRun(statePath, bindings, deps.now);
1891
+ if (initial.status === "completed") return initial.finalRecord;
1892
+ const requestEffectForDispatch = createEffectRequestBatcher(
1893
+ statePath,
1894
+ runId,
1895
+ deps.now,
1896
+ );
1897
+ const durableRunTask = async (args) => {
1898
+ const requested = await requestEffectForDispatch(args);
1899
+ if (requested.control) {
1900
+ throw controlError(
1901
+ `dynamic workflow run ${requested.control}`,
1902
+ requested.control,
1903
+ requested.state,
1904
+ );
1905
+ }
1906
+ if (requested.pending) {
1907
+ throw controlError(
1908
+ `workflow effect ${requested.effect.id} requires reconciliation`,
1909
+ "reconciliation-required",
1910
+ requested.state,
1911
+ requested.effect,
1912
+ );
1913
+ }
1914
+ if (requested.cached) return requested.result;
1915
+ if (typeof deps.beforeProviderDispatch === "function") {
1916
+ try {
1917
+ await deps.beforeProviderDispatch(requested.effect, args);
1918
+ } catch (cause) {
1919
+ const state = readDynamicWorkflowRuntimeState(statePath);
1920
+ throw controlError(
1921
+ `workflow effect ${requested.effect.id} was persisted but not dispatched`,
1922
+ "undispatched-recovery-required",
1923
+ state,
1924
+ requested.effect,
1925
+ cause,
1926
+ );
1927
+ }
1928
+ }
1929
+ if (
1930
+ isKnownUndispatched(requested.effect) &&
1931
+ (requested.effect.timeoutObservedAt !== null || args.signal?.aborted)
1932
+ ) {
1933
+ return settleUndispatchedEffect(
1934
+ statePath,
1935
+ runId,
1936
+ requested.effect.id,
1937
+ "timeout",
1938
+ deps.now,
1939
+ );
1940
+ }
1941
+ const dispatched = markEffectProviderDispatched(
1942
+ statePath,
1943
+ runId,
1944
+ requested.effect.id,
1945
+ deps.now,
1946
+ );
1947
+ if (dispatched.control === "stopped") {
1948
+ settleUndispatchedEffect(
1949
+ statePath,
1950
+ runId,
1951
+ requested.effect.id,
1952
+ "stopped",
1953
+ deps.now,
1954
+ );
1955
+ const state = readDynamicWorkflowRuntimeState(statePath);
1956
+ throw controlError("dynamic workflow run stopped", "stopped", state);
1957
+ }
1958
+ const effect = dispatched.effect;
1959
+ let timeoutObservationError = null;
1960
+ const observeTimeout = () => {
1961
+ try {
1962
+ observeEffectTimeout(statePath, runId, effect.id, deps.now);
1963
+ } catch (error) {
1964
+ timeoutObservationError ||= error;
1965
+ }
1966
+ };
1967
+ if (args.signal) {
1968
+ args.signal.addEventListener("abort", observeTimeout, { once: true });
1969
+ if (args.signal.aborted) observeTimeout();
1970
+ }
1971
+ let result;
1972
+ try {
1973
+ const durableCallObservers = createDurableCallObservers(
1974
+ statePath,
1975
+ runId,
1976
+ effect.id,
1977
+ deps.now,
1978
+ );
1979
+ result = await deps.runTask({
1980
+ ...args,
1981
+ workflowEffectId: effect.id,
1982
+ ...durableCallObservers,
1983
+ });
1984
+ if (typeof deps.afterProvider === "function") {
1985
+ await deps.afterProvider(effect, result);
1986
+ }
1987
+ if (timeoutObservationError) throw timeoutObservationError;
1988
+ } catch (cause) {
1989
+ const state = blockPendingEffect(statePath, runId, effect.id, deps.now);
1990
+ throw controlError(
1991
+ `workflow effect ${effect.id} has an unknown outcome and requires reconciliation`,
1992
+ "reconciliation-required",
1993
+ state,
1994
+ effect,
1995
+ cause,
1996
+ );
1997
+ } finally {
1998
+ args.signal?.removeEventListener("abort", observeTimeout);
1999
+ }
2000
+ try {
2001
+ return settleEffect(statePath, runId, effect.id, result, deps.now);
2002
+ } catch (cause) {
2003
+ const state = blockPendingEffect(statePath, runId, effect.id, deps.now);
2004
+ throw controlError(
2005
+ `workflow effect ${effect.id} settlement is unknown and requires reconciliation`,
2006
+ "reconciliation-required",
2007
+ state,
2008
+ effect,
2009
+ cause,
2010
+ );
2011
+ }
2012
+ };
2013
+ try {
2014
+ const record = await (deps.executeWorkflow || executeCoworkWorkflow)({
2015
+ ...execution,
2016
+ maxParallel: execution.runAdmission.maxParallel,
2017
+ runTask: durableRunTask,
2018
+ });
2019
+ const completed = completeRun(statePath, runId, record, deps.now);
2020
+ if (completed.control) {
2021
+ throw controlError(
2022
+ `dynamic workflow run ${completed.control}`,
2023
+ completed.control,
2024
+ completed.state,
2025
+ );
2026
+ }
2027
+ return completed.record;
2028
+ } catch (error) {
2029
+ if (
2030
+ error?.code === DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE &&
2031
+ error.reason === "paused"
2032
+ ) {
2033
+ const state = finalizePauseAfterSettlementBarrier(
2034
+ statePath,
2035
+ runId,
2036
+ deps.now,
2037
+ );
2038
+ const pending = state?.effects?.find(
2039
+ (effect) => effect.status === "pending",
2040
+ );
2041
+ if (pending) {
2042
+ throw controlError(
2043
+ `workflow effect ${pending.id} requires reconciliation before pause can settle`,
2044
+ "reconciliation-required",
2045
+ state,
2046
+ pending,
2047
+ error,
2048
+ );
2049
+ }
2050
+ if (state?.status === "stopped") {
2051
+ throw controlError(
2052
+ "dynamic workflow run stopped while its pause barrier was settling",
2053
+ "stopped",
2054
+ state,
2055
+ null,
2056
+ error,
2057
+ );
2058
+ }
2059
+ throw controlError(
2060
+ "dynamic workflow run paused after its settlement barrier",
2061
+ "paused",
2062
+ state,
2063
+ null,
2064
+ error,
2065
+ );
2066
+ }
2067
+ if (error?.code !== DYNAMIC_WORKFLOW_RUNTIME_CONTROL_CODE) {
2068
+ markFailed(statePath, runId, error, deps.now);
2069
+ }
2070
+ throw error;
2071
+ }
2072
+ }
2073
+
2074
+ function requireRevision(state, expectedRevision) {
2075
+ const expected = Number(expectedRevision);
2076
+ if (!Number.isSafeInteger(expected) || expected < 1) {
2077
+ throw new TypeError("expected runtime revision is required");
2078
+ }
2079
+ if (state.revision !== expected) {
2080
+ throw new Error(
2081
+ `stale dynamic workflow runtime revision: expected ${expected}, found ${state.revision}`,
2082
+ );
2083
+ }
2084
+ }
2085
+
2086
+ export function requestDurableWorkflowPause(
2087
+ statePath,
2088
+ expectedRevision,
2089
+ deps = {},
2090
+ ) {
2091
+ return withStateMutation(path.resolve(statePath), (current) => {
2092
+ if (!current)
2093
+ throw new Error("dynamic workflow runtime state was not found");
2094
+ requireRevision(current, expectedRevision);
2095
+ if (TERMINAL_STATUSES.has(current.status)) {
2096
+ throw new Error(`cannot pause a ${current.status} workflow run`);
2097
+ }
2098
+ const immediate = ["ready", "paused", "failed"].includes(current.status);
2099
+ const state = transition(
2100
+ current,
2101
+ immediate ? "run-paused" : "pause-requested",
2102
+ {},
2103
+ (draft) => {
2104
+ draft.status = immediate ? "paused" : "pause_requested";
2105
+ },
2106
+ deps.now,
2107
+ );
2108
+ return { state, value: state };
2109
+ });
2110
+ }
2111
+
2112
+ export function requestDurableWorkflowStop(
2113
+ statePath,
2114
+ expectedRevision,
2115
+ deps = {},
2116
+ ) {
2117
+ return withStateMutation(path.resolve(statePath), (current) => {
2118
+ if (!current)
2119
+ throw new Error("dynamic workflow runtime state was not found");
2120
+ requireRevision(current, expectedRevision);
2121
+ if (current.status === "completed") {
2122
+ throw new Error("cannot stop a completed workflow run");
2123
+ }
2124
+ if (current.status === "stopped") return { value: current };
2125
+ const state = transition(
2126
+ current,
2127
+ "run-stopped",
2128
+ {},
2129
+ (draft) => {
2130
+ draft.status = "stopped";
2131
+ draft.finalRecord = null;
2132
+ },
2133
+ deps.now,
2134
+ );
2135
+ return { state, value: state };
2136
+ });
2137
+ }
2138
+
2139
+ export function prepareDurableWorkflowResume(
2140
+ statePath,
2141
+ expectedRevision,
2142
+ deps = {},
2143
+ ) {
2144
+ return withStateMutation(path.resolve(statePath), (current) => {
2145
+ if (!current)
2146
+ throw new Error("dynamic workflow runtime state was not found");
2147
+ requireRevision(current, expectedRevision);
2148
+ if (TERMINAL_STATUSES.has(current.status)) {
2149
+ throw new Error(`cannot resume a ${current.status} workflow run`);
2150
+ }
2151
+ const pending = current.effects.find(
2152
+ (effect) => effect.status === "pending" && !isKnownUndispatched(effect),
2153
+ );
2154
+ if (pending) {
2155
+ const error = new Error(
2156
+ `workflow effect ${pending.id} must be reconciled before resume`,
2157
+ );
2158
+ error.code = "CC_DYNAMIC_WORKFLOW_EFFECT_RECONCILIATION_REQUIRED";
2159
+ error.pendingEffect = pending;
2160
+ throw error;
2161
+ }
2162
+ const state = transition(
2163
+ current,
2164
+ "run-resume-authorized",
2165
+ {},
2166
+ (draft) => {
2167
+ draft.status = "ready";
2168
+ },
2169
+ deps.now,
2170
+ );
2171
+ return { state, value: state };
2172
+ });
2173
+ }
2174
+
2175
+ export function reconcileDurableWorkflowEffect(
2176
+ statePath,
2177
+ input = {},
2178
+ deps = {},
2179
+ ) {
2180
+ const effectId = String(input.effectId || "")
2181
+ .trim()
2182
+ .toLowerCase();
2183
+ if (!SHA256_RE.test(effectId)) throw new TypeError("effectId is invalid");
2184
+ const result = snapshotJson(
2185
+ input.result,
2186
+ "workflow reconciled effect result",
2187
+ 1024 * 1024,
2188
+ );
2189
+ if (containsSecret(JSON.stringify(result))) {
2190
+ throw new Error("workflow reconciled result contains secret-shaped data");
2191
+ }
2192
+ const resultDigest = effectResultDigest(result);
2193
+ return withStateMutation(path.resolve(statePath), (current) => {
2194
+ if (!current)
2195
+ throw new Error("dynamic workflow runtime state was not found");
2196
+ requireRevision(current, input.expectedRevision);
2197
+ const index = current.effects.findIndex((effect) => effect.id === effectId);
2198
+ const effect = current.effects[index];
2199
+ if (!effect || effect.status !== "pending") {
2200
+ throw new Error("workflow effect is not pending reconciliation");
2201
+ }
2202
+ if (isKnownUndispatched(effect)) {
2203
+ throw new Error(
2204
+ "workflow effect was not dispatched and must be resumed instead of reconciled",
2205
+ );
2206
+ }
2207
+ const firstPending = current.effects.find(
2208
+ (candidate) =>
2209
+ candidate.status === "pending" && !isKnownUndispatched(candidate),
2210
+ );
2211
+ if (firstPending?.id !== effectId) {
2212
+ const error = new Error(
2213
+ `workflow effect ${firstPending.id} must be reconciled before ${effectId}`,
2214
+ );
2215
+ error.code = "CC_DYNAMIC_WORKFLOW_EFFECT_RECONCILIATION_OUT_OF_ORDER";
2216
+ error.pendingEffect = firstPending;
2217
+ throw error;
2218
+ }
2219
+ const settledAt = isoNow(deps.now);
2220
+ const state = transition(
2221
+ current,
2222
+ "effect-reconciled",
2223
+ { effectId, resultDigest, settlementAuthority: "operator-reconciled" },
2224
+ (draft) => {
2225
+ const calls = (draft.effects[index].calls || []).map((call) =>
2226
+ call.status === "started"
2227
+ ? {
2228
+ ...call,
2229
+ status: "operator_reconciled",
2230
+ settledAt,
2231
+ outcomeUnknown: false,
2232
+ settlementCode: "operator_reconciled",
2233
+ }
2234
+ : call,
2235
+ );
2236
+ draft.effects[index] = {
2237
+ ...draft.effects[index],
2238
+ calls,
2239
+ status: "settled",
2240
+ settledAt,
2241
+ settlementAuthority: "operator-reconciled",
2242
+ resultDigest,
2243
+ result,
2244
+ };
2245
+ const unknownPendingRemain = draft.effects.some(
2246
+ (candidate) =>
2247
+ candidate.status === "pending" && !isKnownUndispatched(candidate),
2248
+ );
2249
+ if (draft.status === "blocked" && !unknownPendingRemain) {
2250
+ draft.status = "ready";
2251
+ }
2252
+ },
2253
+ deps.now,
2254
+ );
2255
+ return { state, value: state };
2256
+ });
2257
+ }
2258
+
2259
+ function reportedContentDigest(value) {
2260
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2261
+ for (const candidate of [value.sha256, value.digest, value.recordDigest]) {
2262
+ if (typeof candidate !== "string") continue;
2263
+ const normalized = candidate.startsWith("sha256:")
2264
+ ? candidate.toLowerCase()
2265
+ : `sha256:${candidate.toLowerCase()}`;
2266
+ if (SHA256_RE.test(normalized)) return normalized;
2267
+ }
2268
+ return null;
2269
+ }
2270
+
2271
+ function projectResultValues(effect, field, domain) {
2272
+ const values = effect.result?.result?.[field];
2273
+ if (!Array.isArray(values)) {
2274
+ return { count: 0, truncated: false, lineage: [] };
2275
+ }
2276
+ const visible = values.slice(0, MAX_PROJECTED_ARTIFACTS_PER_EFFECT);
2277
+ return {
2278
+ count: values.length,
2279
+ truncated: values.length > visible.length,
2280
+ lineage: visible.map((value, ordinal) => ({
2281
+ effectId: effect.id,
2282
+ ordinal,
2283
+ valueDigest: digest(domain, value),
2284
+ reportedContentDigest: reportedContentDigest(value),
2285
+ })),
2286
+ };
2287
+ }
2288
+
2289
+ function projectProviderRequestReceipts(effect) {
2290
+ const attemptValues = Array.isArray(effect.result?.providerRequestAttempts)
2291
+ ? effect.result.providerRequestAttempts
2292
+ : [];
2293
+ const receiptValues = Array.isArray(effect.result?.providerRequestReceipts)
2294
+ ? effect.result.providerRequestReceipts
2295
+ : [];
2296
+ const visibleAttempts = attemptValues.slice(
2297
+ 0,
2298
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
2299
+ );
2300
+ const visibleReceipts = receiptValues.slice(
2301
+ 0,
2302
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
2303
+ );
2304
+ const attemptLineage = [];
2305
+ const receiptLineage = [];
2306
+ const attemptsByCallId = new Map();
2307
+ const matchedAttemptCallIds = new Set();
2308
+ let invalidAttempts = 0;
2309
+ let invalidReceipts = 0;
2310
+
2311
+ for (let ordinal = 0; ordinal < visibleAttempts.length; ordinal += 1) {
2312
+ const attempt = visibleAttempts[ordinal];
2313
+ if (
2314
+ effect.settlementAuthority !== "provider-return" ||
2315
+ effect.result?.workflowEffectId !== effect.id ||
2316
+ !attempt ||
2317
+ typeof attempt !== "object" ||
2318
+ Array.isArray(attempt) ||
2319
+ attempt.protocol !== "cc-provider-request-attempt/v1" ||
2320
+ attempt.workflowEffectId !== effect.id ||
2321
+ !PROVIDER_NAME_RE.test(attempt.provider || "") ||
2322
+ !PROVIDER_RECEIPT_ID_RE.test(attempt.callId || "") ||
2323
+ !Number.isSafeInteger(attempt.callSequence) ||
2324
+ attempt.callSequence < 1 ||
2325
+ !["model", "semantic-compaction"].includes(attempt.source) ||
2326
+ !PROVIDER_CLIENT_REQUEST_ID_RE.test(attempt.clientRequestId || "") ||
2327
+ attempt.requestIdentitySemantics !== "trace-only" ||
2328
+ attemptsByCallId.has(attempt.callId)
2329
+ ) {
2330
+ invalidAttempts += 1;
2331
+ continue;
2332
+ }
2333
+ const projected = {
2334
+ effectId: effect.id,
2335
+ ordinal,
2336
+ provider: attempt.provider,
2337
+ callId: attempt.callId,
2338
+ callSequence: attempt.callSequence,
2339
+ source: attempt.source,
2340
+ clientRequestId: attempt.clientRequestId,
2341
+ requestIdentitySemantics: "trace-only",
2342
+ };
2343
+ attemptsByCallId.set(attempt.callId, projected);
2344
+ attemptLineage.push(projected);
2345
+ }
2346
+
2347
+ for (let ordinal = 0; ordinal < visibleReceipts.length; ordinal += 1) {
2348
+ const receipt = visibleReceipts[ordinal];
2349
+ const requestId = receipt?.requestId ?? null;
2350
+ const responseId = receipt?.responseId ?? null;
2351
+ const attempt = attemptsByCallId.get(receipt?.callId);
2352
+ if (
2353
+ effect.settlementAuthority !== "provider-return" ||
2354
+ effect.result?.workflowEffectId !== effect.id ||
2355
+ !receipt ||
2356
+ typeof receipt !== "object" ||
2357
+ Array.isArray(receipt) ||
2358
+ receipt.protocol !== "cc-provider-request-receipt/v1" ||
2359
+ receipt.workflowEffectId !== effect.id ||
2360
+ !PROVIDER_NAME_RE.test(receipt.provider || "") ||
2361
+ typeof receipt.callId !== "string" ||
2362
+ !PROVIDER_RECEIPT_ID_RE.test(receipt.callId) ||
2363
+ !Number.isSafeInteger(receipt.callSequence) ||
2364
+ receipt.callSequence < 1 ||
2365
+ !["model", "semantic-compaction"].includes(receipt.source) ||
2366
+ !PROVIDER_CLIENT_REQUEST_ID_RE.test(receipt.clientRequestId || "") ||
2367
+ (requestId !== null && !PROVIDER_RECEIPT_ID_RE.test(requestId)) ||
2368
+ (responseId !== null && !PROVIDER_RECEIPT_ID_RE.test(responseId)) ||
2369
+ (!requestId && !responseId) ||
2370
+ receipt.requestIdentitySemantics !== "trace-only" ||
2371
+ receipt.independentlyReadable !== false ||
2372
+ !attempt ||
2373
+ attempt.provider !== receipt.provider ||
2374
+ attempt.callSequence !== receipt.callSequence ||
2375
+ attempt.source !== receipt.source ||
2376
+ attempt.clientRequestId !== receipt.clientRequestId ||
2377
+ matchedAttemptCallIds.has(receipt.callId)
2378
+ ) {
2379
+ invalidReceipts += 1;
2380
+ continue;
2381
+ }
2382
+ matchedAttemptCallIds.add(receipt.callId);
2383
+ receiptLineage.push({
2384
+ effectId: effect.id,
2385
+ ordinal,
2386
+ provider: receipt.provider,
2387
+ callId: receipt.callId,
2388
+ callSequence: receipt.callSequence,
2389
+ source: receipt.source,
2390
+ clientRequestId: receipt.clientRequestId,
2391
+ requestId,
2392
+ responseId,
2393
+ requestIdentitySemantics: "trace-only",
2394
+ independentlyReadable: false,
2395
+ });
2396
+ }
2397
+ return {
2398
+ attemptCount: attemptValues.length,
2399
+ receiptCount: receiptValues.length,
2400
+ invalidAttempts,
2401
+ invalidReceipts,
2402
+ missingReceipts: attemptLineage.length - matchedAttemptCallIds.size,
2403
+ truncated:
2404
+ attemptValues.length > visibleAttempts.length ||
2405
+ receiptValues.length > visibleReceipts.length,
2406
+ attemptLineage,
2407
+ receiptLineage,
2408
+ };
2409
+ }
2410
+
2411
+ function expectedNestedToolEffectId(effectId, sequence, toolUseId, tool) {
2412
+ return `sha256:${createHash("sha256")
2413
+ .update(
2414
+ `${effectId}\0tool\0${String(sequence)}\0${toolUseId}\0${tool}`,
2415
+ "utf8",
2416
+ )
2417
+ .digest("hex")}`;
2418
+ }
2419
+
2420
+ function projectNestedToolEffects(effect) {
2421
+ const attemptValues = Array.isArray(effect.result?.nestedEffectAttempts)
2422
+ ? effect.result.nestedEffectAttempts
2423
+ : [];
2424
+ const settlementValues = Array.isArray(effect.result?.nestedEffectSettlements)
2425
+ ? effect.result.nestedEffectSettlements
2426
+ : [];
2427
+ const visibleAttempts = attemptValues.slice(
2428
+ 0,
2429
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
2430
+ );
2431
+ const visibleSettlements = settlementValues.slice(
2432
+ 0,
2433
+ MAX_PROJECTED_ARTIFACTS_PER_EFFECT,
2434
+ );
2435
+ const attemptsById = new Map();
2436
+ const settledIds = new Set();
2437
+ const attemptLineage = [];
2438
+ const settlementLineage = [];
2439
+ let invalidAttempts = 0;
2440
+ let invalidSettlements = 0;
2441
+ let durableMcpSettlements = 0;
2442
+
2443
+ for (let ordinal = 0; ordinal < visibleAttempts.length; ordinal += 1) {
2444
+ const attempt = visibleAttempts[ordinal];
2445
+ const expectedId =
2446
+ attempt &&
2447
+ expectedNestedToolEffectId(
2448
+ effect.id,
2449
+ attempt.childSequence,
2450
+ attempt.toolUseId,
2451
+ attempt.tool,
2452
+ );
2453
+ if (
2454
+ effect.settlementAuthority !== "provider-return" ||
2455
+ effect.result?.workflowEffectId !== effect.id ||
2456
+ !attempt ||
2457
+ typeof attempt !== "object" ||
2458
+ Array.isArray(attempt) ||
2459
+ attempt.protocol !== WORKFLOW_CHILD_EFFECT_PROTOCOL ||
2460
+ attempt.workflowEffectId !== effect.id ||
2461
+ !SHA256_RE.test(attempt.childEffectId || "") ||
2462
+ attempt.childEffectId !== expectedId ||
2463
+ !Number.isSafeInteger(attempt.childSequence) ||
2464
+ attempt.childSequence < 1 ||
2465
+ attempt.kind !== "tool" ||
2466
+ !WORKFLOW_TOOL_NAME_RE.test(attempt.tool || "") ||
2467
+ !WORKFLOW_TOOL_CALL_ID_RE.test(attempt.toolUseId || "") ||
2468
+ attempt.identitySemantics !== "runtime-derived" ||
2469
+ attemptsById.has(attempt.childEffectId) ||
2470
+ [...attemptsById.values()].some(
2471
+ (existing) => existing.childSequence === attempt.childSequence,
2472
+ )
2473
+ ) {
2474
+ invalidAttempts += 1;
2475
+ continue;
2476
+ }
2477
+ const projected = {
2478
+ effectId: effect.id,
2479
+ ordinal,
2480
+ childEffectId: attempt.childEffectId,
2481
+ childSequence: attempt.childSequence,
2482
+ kind: "tool",
2483
+ tool: attempt.tool,
2484
+ toolUseId: attempt.toolUseId,
2485
+ identitySemantics: "runtime-derived",
2486
+ };
2487
+ attemptsById.set(attempt.childEffectId, projected);
2488
+ attemptLineage.push(projected);
2489
+ }
2490
+
2491
+ for (let ordinal = 0; ordinal < visibleSettlements.length; ordinal += 1) {
2492
+ const settlement = visibleSettlements[ordinal];
2493
+ const attempt = attemptsById.get(settlement?.childEffectId);
2494
+ const mcpLedgerId = settlement?.mcpLedgerId ?? null;
2495
+ const validMcpLedgerId =
2496
+ mcpLedgerId === null || PROVIDER_RECEIPT_ID_RE.test(mcpLedgerId);
2497
+ const mcpPersistenceValid =
2498
+ mcpLedgerId === null
2499
+ ? settlement?.mcpLedgerPrewritePersisted === false &&
2500
+ settlement?.mcpLedgerSettlementPersisted === false
2501
+ : typeof settlement?.mcpLedgerPrewritePersisted === "boolean" &&
2502
+ typeof settlement?.mcpLedgerSettlementPersisted === "boolean" &&
2503
+ (!settlement.mcpLedgerSettlementPersisted ||
2504
+ settlement.mcpLedgerPrewritePersisted);
2505
+ if (
2506
+ effect.settlementAuthority !== "provider-return" ||
2507
+ effect.result?.workflowEffectId !== effect.id ||
2508
+ !settlement ||
2509
+ typeof settlement !== "object" ||
2510
+ Array.isArray(settlement) ||
2511
+ settlement.protocol !== WORKFLOW_CHILD_EFFECT_PROTOCOL ||
2512
+ settlement.workflowEffectId !== effect.id ||
2513
+ !attempt ||
2514
+ settlement.childSequence !== attempt.childSequence ||
2515
+ settlement.kind !== "tool" ||
2516
+ settlement.tool !== attempt.tool ||
2517
+ settlement.toolUseId !== attempt.toolUseId ||
2518
+ !["completed", "failed"].includes(settlement.status) ||
2519
+ settlement.outcomeUnknown !== false ||
2520
+ !validMcpLedgerId ||
2521
+ !mcpPersistenceValid ||
2522
+ settledIds.has(settlement.childEffectId)
2523
+ ) {
2524
+ invalidSettlements += 1;
2525
+ continue;
2526
+ }
2527
+ settledIds.add(settlement.childEffectId);
2528
+ if (
2529
+ mcpLedgerId &&
2530
+ settlement.mcpLedgerPrewritePersisted &&
2531
+ settlement.mcpLedgerSettlementPersisted
2532
+ ) {
2533
+ durableMcpSettlements += 1;
2534
+ }
2535
+ settlementLineage.push({
2536
+ effectId: effect.id,
2537
+ ordinal,
2538
+ childEffectId: settlement.childEffectId,
2539
+ childSequence: settlement.childSequence,
2540
+ kind: "tool",
2541
+ tool: settlement.tool,
2542
+ toolUseId: settlement.toolUseId,
2543
+ status: settlement.status,
2544
+ outcomeUnknown: false,
2545
+ mcpLedgerId,
2546
+ mcpLedgerPrewritePersisted:
2547
+ settlement.mcpLedgerPrewritePersisted === true,
2548
+ mcpLedgerSettlementPersisted:
2549
+ settlement.mcpLedgerSettlementPersisted === true,
2550
+ });
2551
+ }
2552
+
2553
+ return {
2554
+ attemptCount: attemptValues.length,
2555
+ settlementCount: settlementValues.length,
2556
+ invalidAttempts,
2557
+ invalidSettlements,
2558
+ missingSettlements: attemptLineage.length - settledIds.size,
2559
+ durableMcpSettlements,
2560
+ truncated:
2561
+ attemptValues.length > visibleAttempts.length ||
2562
+ settlementValues.length > visibleSettlements.length,
2563
+ attemptLineage,
2564
+ settlementLineage,
2565
+ };
2566
+ }
2567
+
2568
+ function projectDurableWorkflowCalls(state) {
2569
+ const lineage = state.effects.flatMap((effect) =>
2570
+ (effect.calls || []).map((call) => ({
2571
+ effectId: effect.id,
2572
+ callRecordId: call.id,
2573
+ kind: call.kind,
2574
+ protocol: call.protocol,
2575
+ ownerEffectId: call.ownerEffectId ?? effect.id,
2576
+ childEffectId: call.childEffectId,
2577
+ sequence: call.sequence,
2578
+ name: call.name,
2579
+ source: call.source,
2580
+ requestSource:
2581
+ call.requestSource ?? (call.kind === "provider" ? call.source : null),
2582
+ descendant: (call.ownerEffectId ?? effect.id) !== effect.id,
2583
+ status: call.status,
2584
+ startedAt: call.startedAt,
2585
+ settledAt: call.settledAt,
2586
+ outcomeUnknown: call.outcomeUnknown,
2587
+ settlementCode: call.settlementCode,
2588
+ providerReceipt: call.providerReceiptPersisted
2589
+ ? {
2590
+ protocol: "cc-provider-request-receipt/v1",
2591
+ requestId: call.providerReceiptRequestId,
2592
+ responseId: call.providerReceiptResponseId,
2593
+ requestIdentitySemantics: "trace-only",
2594
+ independentlyReadable: false,
2595
+ }
2596
+ : null,
2597
+ mcpLedgerId: call.mcpLedgerId,
2598
+ mcpLedgerPrewritePersisted: call.mcpLedgerPrewritePersisted,
2599
+ mcpLedgerSettlementPersisted: call.mcpLedgerSettlementPersisted,
2600
+ })),
2601
+ );
2602
+ return {
2603
+ authority: "runtime-state-hash-chain-fsync",
2604
+ count: lineage.length,
2605
+ started: lineage.filter((call) => call.status === "started").length,
2606
+ completed: lineage.filter((call) => call.status === "completed").length,
2607
+ failed: lineage.filter((call) => call.status === "failed").length,
2608
+ outcomeUnknown: lineage.filter((call) => call.status === "outcome_unknown")
2609
+ .length,
2610
+ operatorReconciled: lineage.filter(
2611
+ (call) => call.status === "operator_reconciled",
2612
+ ).length,
2613
+ descendants: lineage.filter((call) => call.descendant).length,
2614
+ providerReceipts: lineage.filter((call) => call.providerReceipt !== null)
2615
+ .length,
2616
+ providerNativeIdempotencyProven: false,
2617
+ providerReceiptsIndependentlyReadable: false,
2618
+ lineageDigest: digest(
2619
+ "chainlesschain.dynamic-workflow.durable-call-lineage.v1\0",
2620
+ lineage,
2621
+ ),
2622
+ lineage,
2623
+ };
2624
+ }
2625
+
2626
+ function projectDynamicWorkflowObservability(state) {
2627
+ const settled = state.effects.filter((effect) => effect.status === "settled");
2628
+ const effectLineage = [];
2629
+ const artifactLineage = [];
2630
+ const checkpointLineage = [];
2631
+ const providerRequestAttemptLineage = [];
2632
+ const providerReceiptLineage = [];
2633
+ const nestedEffectAttemptLineage = [];
2634
+ const nestedEffectSettlementLineage = [];
2635
+ const durableCalls = projectDurableWorkflowCalls(state);
2636
+ let requestToSettlementMs = 0;
2637
+ let maxRequestToSettlementMs = 0;
2638
+ let estimatedTokens = 0;
2639
+ let tokenEstimateEffects = 0;
2640
+ let completedTaskEffects = 0;
2641
+ let failedTaskEffects = 0;
2642
+ let invalidTaskResultEffects = 0;
2643
+ let artifactCount = 0;
2644
+ let checkpointCount = 0;
2645
+ let artifactTruncatedEffects = 0;
2646
+ let checkpointTruncatedEffects = 0;
2647
+ let providerRequestAttemptCount = 0;
2648
+ let providerRequestAttemptEffects = 0;
2649
+ let providerReceiptCount = 0;
2650
+ let providerReceiptEffects = 0;
2651
+ let invalidProviderRequestAttempts = 0;
2652
+ let invalidProviderReceipts = 0;
2653
+ let missingProviderRequestReceipts = 0;
2654
+ let providerReceiptTruncatedEffects = 0;
2655
+ let nestedEffectAttemptCount = 0;
2656
+ let nestedEffectSettlementCount = 0;
2657
+ let nestedEffectDurableMcpSettlements = 0;
2658
+ let invalidNestedEffectAttempts = 0;
2659
+ let invalidNestedEffectSettlements = 0;
2660
+ let missingNestedEffectSettlements = 0;
2661
+ let nestedEffectTruncatedEffects = 0;
2662
+
2663
+ for (const effect of settled) {
2664
+ const elapsed = Math.max(
2665
+ 0,
2666
+ Date.parse(effect.settledAt) - Date.parse(effect.requestedAt),
2667
+ );
2668
+ requestToSettlementMs += elapsed;
2669
+ maxRequestToSettlementMs = Math.max(maxRequestToSettlementMs, elapsed);
2670
+
2671
+ const taskStatus = effect.result?.status;
2672
+ if (taskStatus === "completed") completedTaskEffects += 1;
2673
+ else if (taskStatus === "failed") failedTaskEffects += 1;
2674
+ else invalidTaskResultEffects += 1;
2675
+
2676
+ const tokenCount = nonNegativeSafeInteger(
2677
+ effect.result?.result?.tokenCount,
2678
+ );
2679
+ if (tokenCount !== null) {
2680
+ estimatedTokens += tokenCount;
2681
+ tokenEstimateEffects += 1;
2682
+ }
2683
+
2684
+ const artifacts = projectResultValues(
2685
+ effect,
2686
+ "artifacts",
2687
+ "chainlesschain.dynamic-workflow.artifact-observation.v1\0",
2688
+ );
2689
+ artifactCount += artifacts.count;
2690
+ artifactLineage.push(...artifacts.lineage);
2691
+ if (artifacts.truncated) artifactTruncatedEffects += 1;
2692
+
2693
+ const checkpoints = projectResultValues(
2694
+ effect,
2695
+ "checkpoints",
2696
+ "chainlesschain.dynamic-workflow.checkpoint-observation.v1\0",
2697
+ );
2698
+ checkpointCount += checkpoints.count;
2699
+ checkpointLineage.push(...checkpoints.lineage);
2700
+ if (checkpoints.truncated) checkpointTruncatedEffects += 1;
2701
+
2702
+ const providerReceipts = projectProviderRequestReceipts(effect);
2703
+ providerRequestAttemptCount += providerReceipts.attemptCount;
2704
+ providerReceiptCount += providerReceipts.receiptCount;
2705
+ providerRequestAttemptLineage.push(...providerReceipts.attemptLineage);
2706
+ providerReceiptLineage.push(...providerReceipts.receiptLineage);
2707
+ invalidProviderRequestAttempts += providerReceipts.invalidAttempts;
2708
+ invalidProviderReceipts += providerReceipts.invalidReceipts;
2709
+ missingProviderRequestReceipts += providerReceipts.missingReceipts;
2710
+ if (providerReceipts.attemptLineage.length > 0) {
2711
+ providerRequestAttemptEffects += 1;
2712
+ }
2713
+ if (providerReceipts.receiptLineage.length > 0) {
2714
+ providerReceiptEffects += 1;
2715
+ }
2716
+ if (providerReceipts.truncated) providerReceiptTruncatedEffects += 1;
2717
+
2718
+ const nestedEffects = projectNestedToolEffects(effect);
2719
+ nestedEffectAttemptCount += nestedEffects.attemptCount;
2720
+ nestedEffectSettlementCount += nestedEffects.settlementCount;
2721
+ nestedEffectDurableMcpSettlements += nestedEffects.durableMcpSettlements;
2722
+ invalidNestedEffectAttempts += nestedEffects.invalidAttempts;
2723
+ invalidNestedEffectSettlements += nestedEffects.invalidSettlements;
2724
+ missingNestedEffectSettlements += nestedEffects.missingSettlements;
2725
+ nestedEffectAttemptLineage.push(...nestedEffects.attemptLineage);
2726
+ nestedEffectSettlementLineage.push(...nestedEffects.settlementLineage);
2727
+ if (nestedEffects.truncated) nestedEffectTruncatedEffects += 1;
2728
+
2729
+ effectLineage.push({
2730
+ effectId: effect.id,
2731
+ stepId: effect.stepId,
2732
+ iteration: effect.iteration,
2733
+ attempt: effect.attempt,
2734
+ status: effect.status,
2735
+ batchId: effect.batchId || null,
2736
+ batchIndex: effect.batchIndex ?? null,
2737
+ batchSize: effect.batchSize ?? null,
2738
+ timeoutMs: effect.timeoutMs ?? null,
2739
+ timeoutObservedAt: effect.timeoutObservedAt ?? null,
2740
+ providerDispatchedAt: effect.providerDispatchedAt ?? null,
2741
+ taskStatus:
2742
+ taskStatus === "completed" || taskStatus === "failed"
2743
+ ? taskStatus
2744
+ : "invalid",
2745
+ payloadDigest: effect.payloadDigest,
2746
+ resultDigest: effect.resultDigest,
2747
+ settlementAuthority: effect.settlementAuthority,
2748
+ requestedAt: effect.requestedAt,
2749
+ settledAt: effect.settledAt,
2750
+ requestToSettlementMs: elapsed,
2751
+ });
2752
+ }
2753
+
2754
+ const gaps = [
2755
+ "provider-token-usage-unavailable",
2756
+ "provider-cost-usd-unavailable",
2757
+ "provider-native-idempotency-unavailable",
2758
+ "provider-receipt-independent-readback-unavailable",
2759
+ "checkpoint-provider-readback-unavailable",
2760
+ "artifact-store-readback-unavailable",
2761
+ "nested-tool-independent-ledger-incomplete",
2762
+ ];
2763
+ if (tokenEstimateEffects !== settled.length) {
2764
+ gaps.push("cowork-token-estimate-incomplete");
2765
+ }
2766
+ if (artifactTruncatedEffects > 0) {
2767
+ gaps.push("artifact-lineage-projection-truncated");
2768
+ }
2769
+ if (checkpointTruncatedEffects > 0) {
2770
+ gaps.push("checkpoint-lineage-projection-truncated");
2771
+ }
2772
+ const providerReturnedEffects = settled.filter(
2773
+ (effect) => effect.settlementAuthority === "provider-return",
2774
+ ).length;
2775
+ if (
2776
+ providerReceiptEffects !== providerReturnedEffects ||
2777
+ missingProviderRequestReceipts > 0
2778
+ ) {
2779
+ gaps.push("provider-request-receipt-incomplete");
2780
+ }
2781
+ if (invalidProviderRequestAttempts > 0) {
2782
+ gaps.push("provider-request-attempt-invalid");
2783
+ }
2784
+ if (invalidProviderReceipts > 0) {
2785
+ gaps.push("provider-request-receipt-invalid");
2786
+ }
2787
+ if (providerReceiptTruncatedEffects > 0) {
2788
+ gaps.push("provider-request-receipt-projection-truncated");
2789
+ }
2790
+ if (invalidNestedEffectAttempts > 0) {
2791
+ gaps.push("nested-tool-effect-attempt-invalid");
2792
+ }
2793
+ if (invalidNestedEffectSettlements > 0) {
2794
+ gaps.push("nested-tool-effect-settlement-invalid");
2795
+ }
2796
+ if (missingNestedEffectSettlements > 0) {
2797
+ gaps.push("nested-tool-effect-settlement-incomplete");
2798
+ }
2799
+ if (nestedEffectTruncatedEffects > 0) {
2800
+ gaps.push("nested-tool-effect-projection-truncated");
2801
+ }
2802
+
2803
+ return snapshotJson(
2804
+ {
2805
+ schema: DYNAMIC_WORKFLOW_OBSERVABILITY_SCHEMA,
2806
+ complete: false,
2807
+ effects: {
2808
+ requested: state.effects.length,
2809
+ settled: settled.length,
2810
+ pending: state.effects.length - settled.length,
2811
+ providerReturned: settled.filter(
2812
+ (effect) => effect.settlementAuthority === "provider-return",
2813
+ ).length,
2814
+ operatorReconciled: settled.filter(
2815
+ (effect) => effect.settlementAuthority === "operator-reconciled",
2816
+ ).length,
2817
+ runtimeNotDispatched: settled.filter(
2818
+ (effect) => effect.settlementAuthority === "runtime-not-dispatched",
2819
+ ).length,
2820
+ providerDispatched: state.effects.filter(
2821
+ (effect) => typeof effect.providerDispatchedAt === "string",
2822
+ ).length,
2823
+ timeoutObserved: state.effects.filter(
2824
+ (effect) => typeof effect.timeoutObservedAt === "string",
2825
+ ).length,
2826
+ completedTasks: completedTaskEffects,
2827
+ failedTasks: failedTaskEffects,
2828
+ invalidTaskResults: invalidTaskResultEffects,
2829
+ lineageDigest: digest(
2830
+ "chainlesschain.dynamic-workflow.effect-observability.v1\0",
2831
+ effectLineage,
2832
+ ),
2833
+ lineage: effectLineage,
2834
+ },
2835
+ duration: {
2836
+ authority: "request-to-settlement-wall-clock",
2837
+ observedEffects: settled.length,
2838
+ totalMs: requestToSettlementMs,
2839
+ maxMs: maxRequestToSettlementMs,
2840
+ },
2841
+ tokens: {
2842
+ authority: "cowork-result-heuristic",
2843
+ estimated: estimatedTokens,
2844
+ observedEffects: tokenEstimateEffects,
2845
+ missingEffects: settled.length - tokenEstimateEffects,
2846
+ providerReported: null,
2847
+ },
2848
+ cost: {
2849
+ authority: "unavailable",
2850
+ reportedUsd: null,
2851
+ observedEffects: 0,
2852
+ },
2853
+ providerReceipts: {
2854
+ authority: "provider-returned-trace-only",
2855
+ count: providerReceiptCount,
2856
+ projectedRecords: providerReceiptLineage.length,
2857
+ requestAttempts: providerRequestAttemptCount,
2858
+ projectedRequestAttempts: providerRequestAttemptLineage.length,
2859
+ requestAttemptEffects: providerRequestAttemptEffects,
2860
+ observedEffects: providerReceiptEffects,
2861
+ missingProviderReturnedEffects:
2862
+ providerReturnedEffects - providerReceiptEffects,
2863
+ missingRequestReceipts: missingProviderRequestReceipts,
2864
+ invalidRequestAttempts: invalidProviderRequestAttempts,
2865
+ invalidRecords: invalidProviderReceipts,
2866
+ truncatedEffects: providerReceiptTruncatedEffects,
2867
+ nativeIdempotencyProven: false,
2868
+ independentlyReadable: false,
2869
+ requestAttemptLineageDigest: digest(
2870
+ "chainlesschain.dynamic-workflow.provider-request-attempt-lineage.v1\0",
2871
+ providerRequestAttemptLineage,
2872
+ ),
2873
+ lineageDigest: digest(
2874
+ "chainlesschain.dynamic-workflow.provider-request-receipt-lineage.v1\0",
2875
+ providerReceiptLineage,
2876
+ ),
2877
+ requestAttemptLineage: providerRequestAttemptLineage,
2878
+ lineage: providerReceiptLineage,
2879
+ },
2880
+ durableCalls,
2881
+ nestedEffects: {
2882
+ authority: "task-result-bound-with-mcp-session-ledger-flags",
2883
+ attempts: nestedEffectAttemptCount,
2884
+ settlements: nestedEffectSettlementCount,
2885
+ projectedAttempts: nestedEffectAttemptLineage.length,
2886
+ projectedSettlements: nestedEffectSettlementLineage.length,
2887
+ durableMcpSettlements: nestedEffectDurableMcpSettlements,
2888
+ missingSettlements: missingNestedEffectSettlements,
2889
+ invalidAttempts: invalidNestedEffectAttempts,
2890
+ invalidSettlements: invalidNestedEffectSettlements,
2891
+ truncatedEffects: nestedEffectTruncatedEffects,
2892
+ allEffectsIndependentlyDurable: false,
2893
+ attemptLineageDigest: digest(
2894
+ "chainlesschain.dynamic-workflow.nested-effect-attempt-lineage.v1\0",
2895
+ nestedEffectAttemptLineage,
2896
+ ),
2897
+ settlementLineageDigest: digest(
2898
+ "chainlesschain.dynamic-workflow.nested-effect-settlement-lineage.v1\0",
2899
+ nestedEffectSettlementLineage,
2900
+ ),
2901
+ attemptLineage: nestedEffectAttemptLineage,
2902
+ settlementLineage: nestedEffectSettlementLineage,
2903
+ },
2904
+ artifacts: {
2905
+ authority: "task-result-digest-only",
2906
+ count: artifactCount,
2907
+ projectedRecords: artifactLineage.length,
2908
+ truncatedEffects: artifactTruncatedEffects,
2909
+ lineageDigest: digest(
2910
+ "chainlesschain.dynamic-workflow.artifact-lineage.v1\0",
2911
+ artifactLineage,
2912
+ ),
2913
+ lineage: artifactLineage,
2914
+ },
2915
+ checkpoints: {
2916
+ authority: "task-result-digest-only",
2917
+ count: checkpointCount,
2918
+ projectedRecords: checkpointLineage.length,
2919
+ truncatedEffects: checkpointTruncatedEffects,
2920
+ lineageDigest: digest(
2921
+ "chainlesschain.dynamic-workflow.checkpoint-lineage.v1\0",
2922
+ checkpointLineage,
2923
+ ),
2924
+ lineage: checkpointLineage,
2925
+ },
2926
+ gaps,
2927
+ },
2928
+ "dynamic workflow observability projection",
2929
+ 2 * 1024 * 1024,
2930
+ );
2931
+ }
2932
+
2933
+ export function projectDynamicWorkflowRuntime(stateOrPath) {
2934
+ const state =
2935
+ typeof stateOrPath === "string"
2936
+ ? readDynamicWorkflowRuntimeState(stateOrPath)
2937
+ : verifyDynamicWorkflowRuntimeState(stateOrPath);
2938
+ return Object.freeze({
2939
+ schema: DYNAMIC_WORKFLOW_RUNTIME_SCHEMA,
2940
+ runId: state.runId,
2941
+ workflowId: state.workflowId,
2942
+ revision: state.revision,
2943
+ status: state.status,
2944
+ definitionDigest: state.definitionDigest,
2945
+ admissionDigest: state.admissionDigest,
2946
+ executionAuthoritySessionId: state.executionAuthoritySessionId,
2947
+ effectCount: state.effects.length,
2948
+ settledEffectCount: state.effects.filter(
2949
+ (effect) => effect.status === "settled",
2950
+ ).length,
2951
+ pendingEffects: Object.freeze(
2952
+ state.effects
2953
+ .filter((effect) => effect.status === "pending")
2954
+ .map((effect) =>
2955
+ Object.freeze({
2956
+ id: effect.id,
2957
+ key: effect.key,
2958
+ payloadDigest: effect.payloadDigest,
2959
+ batchId: effect.batchId || null,
2960
+ batchIndex: effect.batchIndex ?? null,
2961
+ batchSize: effect.batchSize ?? null,
2962
+ timeoutMs: effect.timeoutMs ?? null,
2963
+ timeoutObservedAt: effect.timeoutObservedAt ?? null,
2964
+ providerDispatchedAt: effect.providerDispatchedAt ?? null,
2965
+ requestedAt: effect.requestedAt,
2966
+ }),
2967
+ ),
2968
+ ),
2969
+ finalRecordStatus: state.finalRecord?.status || null,
2970
+ observability: projectDynamicWorkflowObservability(state),
2971
+ updatedAt: state.updatedAt,
2972
+ stateDigest: state.stateDigest,
2973
+ });
2974
+ }