chainlesschain 0.163.7 → 0.163.8

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 (181) hide show
  1. package/package.json +2 -1
  2. package/src/assets/web-panel/assets/{AIOps-axqQfwv8.js → AIOps-CavM7W3k.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DV-Ro0By.js → ActionButton-CUvZGabQ.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-Bt72WEVu.js → Analytics-DeBnWnbe.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BtepG55E.js → AppLayout-DbI5kxvd.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-B7KaJ7S_.js → Artifacts-D93bigKm.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-FNFaUg7A.js → Audit-CBNvfUZa.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DFb6J2wQ.js → BackgroundAgents-CJLyh_YU.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-CC7YBhyj.js → Backup-D_0yoehD.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-RkgTNVtj.js → BaseInput-TJxmbObW.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-hHEmGZy-.js → Chat-CE0dto1q.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CAP9YhHv.js → ChatBubbleRenderer-CZ5-yYiD.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-z5wXOQP1.js → Checkbox-Ck1RVIVo.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DVaDJnxy.js → Codegen-qpoyf6DK.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CJmaRDR5.js → Col-DUyPWjIE.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-DF29_2bI.js → Community-D_S1BemS.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DtkQkBzn.js → Compact-BjxeK-Gc.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-xoHDxp7I.js → Compliance-Dd3CEJsu.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BUm-R7ht.js → Cowork-B4QG7-ad.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-Cgl14ZkB.js → Cron-BtZnids5.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BNcbmoqh.js → Crosschain-CHPfCBFD.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Bwja0Zo9.js → DID-DT8DCEOK.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-Bk5XjEdJ.js → Dashboard-COQab5_U.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-Ci36Oitq.js → Dropdown-BY1PsQuU.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer--U04ZN4q.js → EmailListRenderer-CsuAuaq3.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-B2gNPR2s.js → FamilyGuardDashboard-BqpinD-j.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-C4KghYn7.js → Federation-D0gkfQFv.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-QL7x_Q_0.js → FormItemContext-BqgE-axK.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-F3Ryltwm.js → GenericCardRenderer-D0hJ2KvC.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-2HassK5k.js → Git-CmMB0uPY.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-DoBIp7oe.js → Governance-BnVEI0Dk.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference--W01QhZ-.js → Inference-YTAMihRP.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DmvwqGpT.js → KnowledgeGraph-0ax7VHop.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BBJ4yAel.js → Logs-B9TRcokL.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-D5lNngSI.js → Marketplace-B6CSuD_H.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-CkEVOMKH.js → McpTools-CjMXvOVU.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-B1Ow1xVE.js → Memory-CXRF71U3.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-SgYbUfb6.js → MobileBridge-DQgNp_oS.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-BWdOvQAj.js → MobileProjects-DlPwcCbq.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-DcTXSSma.js → Mtc-6hDTRPe1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Dt3WG4oj.js → MtcAudit-BlVHU1DS.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-B9B38UZx.js → Multisig-DP9pzEbV.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-B0S4GGvG.js → NLProgramming-Djorh8EV.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-DZ5U58Ng.js → Notes-oLZ-JkUg.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-PjL5ZW34.js → NotificationSettings-Cg288VH3.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DrAX2ESt.js → OrderTableRenderer-Bb1O3Ofr.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-Cv1GYs-c.js → Organization-CBdo-bHs.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Bbkp_PL3.js → Overflow-qZdnVumx.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BD8QUgoZ.js → P2P-D4kNfGNQ.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-BO-2PeaB.js → PdhVaultBrowser-DnlMemM7.js} +5 -5
  52. package/src/assets/web-panel/assets/{Permissions-D_OPVa02.js → Permissions-DHucHFBJ.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-mh-pEmaB.js → PersonalDataHub-BovlOIPR.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-6JC2EDdb.js → Pipeline-Deru3CQY.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-CUshzUAG.js → Privacy-Bq5oTTbU.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Cs2Aalri.js → ProjectInit-BfzxVWAz.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-1ak49tcj.js → ProjectSettings-CKC6XBtH.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BV4oApsm.js → Projects-CMolGhae.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-CXXxvafH.js → Providers-C_jx4TWX.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-PTcDsF58.js → QrScannerModal-BdIHeVGC.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CDwucrJw.js → QuickAsk-BwZwAcPD.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CJlLVRXR.js → Recommend-BNTt0L-m.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-DlN-ETsO.js → RemoteSession-Biyn2-Ro.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-DI23LR6m.js → Reputation-D6VpG32B.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-Cl7UQMrM.js → Row-D_74OvQQ.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-CexHNfh8.js → RssFeed-BTr0a-BQ.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-BneBWvRN.js → Search-C3MCFoNU.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-DX7Phgrp.js → Security-CqW9aJ6s.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-BWXO8Mte.js → Services-Cw5GNaHF.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DCyOjKPJ.js → Skeleton-DZps5qeY.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CXqERsTB.js → Skills-C6NQ1znT.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-bhbrbz8E.js → Sla-DFP8G2Sh.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-3IPU0HBZ.js → SpeechSettings-ESt6MB6v.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-DBNTQLbM.js → SyncSettings-BHnN98aM.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-quP0bkN-.js → Tasks-NiSCaOf-.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BouWmgho.js → Templates-DsKFhW2C.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-qv0D9s7z.js → Tenant-CQ0E0ze5.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BxvbE8BB.js → Terminal-D_rBu_by.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D-IeLnJ2.js → TimelineRenderer-D3Vm4Po4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CFMKKyVG.js → Tokens-DNTxRH6q.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-L8as3UuJ.js → Trigger-Dgprz1ms.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-MxmA4VZG.js → Trust-B2rUnVBD.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-C0VvJYRi.js → UkeySign-DEGQNc6_.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-Bf5QZj8N.js → VideoEditing-jlsAx3Eh.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-BIJ9riRz.js → Wallet-8jQih1ij.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-B8p9Mhkw.js → WebAuthn-XLeO6xIB.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-BVsuaPen.js → WorkflowEditor-De_dgDAG.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-BfkjoKpV.js → chat-CI19R_Sk.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-DeAlsWK8.js → colors-DsYTOYN3.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Cs97bz5j.js → compact-item-CM3-t_Qr.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BqbG60LH.js → createContext-BjxlBRZv.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-i_RY0b1X.js → hasIn-RDaWX_4i.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-U4f5izHz.js → index-9R6BxAr0.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-0-agYlWL.js → index-B-Z8eFUJ.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-B1CbOH1i.js → index-B0PMlbMs.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DqBgBpgp.js → index-B5REnKt8.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BcUXAB2s.js → index-B7WBt_4T.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CIG8fQ2_.js → index-B9O9YyWE.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-gyFpvL7l.js → index-BCnZyIMA.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-yObj1O4n.js → index-BGqqJfL0.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DiSkx4UB.js → index-BHNYPO0b.js} +3 -3
  103. package/src/assets/web-panel/assets/{index-Bm7lLR4c.js → index-BKJr-TZp.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BOVkf1K3.js → index-BN6UPD2E.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BESLFt4q.js → index-Bi46Xu4u.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CE2VtTlp.js → index-BlT2VAEK.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DmbfhJUP.js → index-C0yx-jtQ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BYUVuwKt.js → index-C4gkzxkw.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CeWoNHau.js → index-CVQMeiwH.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-PT_5sBMA.js → index-Cd3euP_4.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-CgsqdnRV.js → index-Cd9OJhHG.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B-3tEpTc.js → index-CiECJi-Q.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BkBi5TmC.js → index-CjTrRt13.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-D2B4VPyP.js → index-CnHH7bnD.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-dIHr4B_u.js → index-Cpqm1qfU.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-B5UkyA3c.js → index-CyOGN6a5.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Dk7-v4hP.js → index-D0zcy54x.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-ZxO_-E8a.js → index-D6sm5-Xq.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-B-JVTrpr.js → index-DIUnNOjG.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Dk2qouAz.js → index-DX7tuWVJ.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BuLQobzW.js → index-DXXtGMUJ.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DUUrv141.js → index-DcPFB8qJ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-D9evtvg2.js → index-DoQA0-52.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-COdGA-8n.js → index-Drlnwr8F.js} +1 -1
  125. package/src/assets/web-panel/assets/index-Drz6yq0m.js +1 -0
  126. package/src/assets/web-panel/assets/{index-CED2PL_y.js → index-Dsij_KsN.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-79V_zv1s.js → index-Dxge-xWl.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CG3PRyAL.js → index-DyIWssPC.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-YXgLng0K.js → index-IeiL9RKc.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-s1opAL-M.js → index-nAH87mQP.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cf3WCLmy.js → index-pPL66J85.js} +1 -1
  132. package/src/assets/web-panel/assets/index-uWiUs7S_.js +1 -0
  133. package/src/assets/web-panel/assets/{initDefaultProps-C9h9yKL3.js → initDefaultProps-BTL88of4.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BvmLsPdu.js → motion-BUB2pXwB.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-CoL4ekU1.js → move-egCkkOMm.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-C4pMy6xV.js → mtc-parser-DAWhZC8c.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-DPuJfNxx.js → omit-CvOxe_6d.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-D0KEeaK2.js → pickAttrs-DZUSLZ5Z.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BpkyINxt.js → placementArrow-BERxGNMY.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-DQI_wOq8.js → responsiveObserve-DZuDfZ9n.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-DYfdDiRg.js → slide-DO756sgI.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-B5dtvb0D.js → statusUtils-BRQlIz28.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-CF310mSj.js → styleChecker-DCSfdybl.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-D1Qp4BLp.js → useFlexGapSupport-LD8dWVve.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-C7_iyvJK.js → useFs-BX9uW2LK.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-DZUt_fIm.js → usePersonalDataHub-BPSlPuHU.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-D3dsi0lZ.js → vnode-BmOILC9V.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-WdbOlvdQ.js → zoom-1iKowKGg.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +3 -3
  151. package/src/commands/automation.js +103 -1
  152. package/src/commands/team-merge-review.js +937 -0
  153. package/src/commands/team.js +2 -0
  154. package/src/data/changelog.json +14 -1
  155. package/src/lib/agent-schedule-store.js +139 -28
  156. package/src/lib/agent-team/team-merge-review-store.js +765 -0
  157. package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
  158. package/src/lib/agent-team/team-merge-review.js +1847 -0
  159. package/src/lib/automation-center-incidents.js +314 -0
  160. package/src/lib/automation-center-runtime.js +448 -0
  161. package/src/lib/automation-center.js +182 -38
  162. package/src/lib/automation-engine.js +41 -0
  163. package/src/lib/automation-execution-authority.js +241 -12
  164. package/src/lib/automation-execution-incident.js +737 -0
  165. package/src/lib/cowork-cron.js +85 -14
  166. package/src/lib/scheduler-kernel/agenda-adapter.js +17 -7
  167. package/src/lib/scheduler-kernel/automation-adapter.js +105 -11
  168. package/src/lib/scheduler-kernel/automation-event-adapter.js +87 -11
  169. package/src/lib/scheduler-kernel/contract.js +91 -0
  170. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +12 -1
  171. package/src/lib/scheduler-kernel/loop-adapter.js +7 -1
  172. package/src/lib/scheduler-kernel/routine-adapter.js +7 -1
  173. package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
  174. package/src/lib/scheduler-kernel/runtime.js +176 -3
  175. package/src/lib/scheduler-kernel/store.js +1253 -45
  176. package/src/runtime/agent-core.js +10 -0
  177. package/src/runtime/mcp-config.js +81 -10
  178. package/src/assets/web-panel/assets/MarkdownRenderer-BLCZMzhJ.js +0 -1
  179. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +0 -1
  180. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +0 -1
  181. package/src/assets/web-panel/assets/index-ChVT0p9u.js +0 -1
@@ -31,12 +31,14 @@
31
31
 
32
32
  import crypto from "node:crypto";
33
33
  import {
34
- existsSync,
34
+ closeSync,
35
+ fsyncSync,
35
36
  mkdirSync,
37
+ openSync,
36
38
  readFileSync,
37
39
  renameSync,
38
40
  unlinkSync,
39
- writeFileSync,
41
+ writeSync,
40
42
  } from "node:fs";
41
43
  import { join } from "node:path";
42
44
  import {
@@ -47,12 +49,14 @@ import {
47
49
  import { withFileLock } from "./with-file-lock.js";
48
50
 
49
51
  export const _deps = {
50
- existsSync,
52
+ closeSync,
53
+ fsyncSync,
51
54
  mkdirSync,
55
+ openSync,
52
56
  readFileSync,
53
57
  renameSync,
54
58
  unlinkSync,
55
- writeFileSync,
59
+ writeSync,
56
60
  withFileLock,
57
61
  now: () => new Date(),
58
62
  runTask: null, // injected at runtime to avoid circular import
@@ -543,14 +547,33 @@ function _scheduleFile(cwd) {
543
547
  /** Load all schedules from disk. Returns [] if the file doesn't exist. */
544
548
  export function loadSchedules(cwd, { failOnMalformed = false } = {}) {
545
549
  const file = _scheduleFile(cwd);
546
- if (!_deps.existsSync(file)) return [];
547
- const raw = _deps.readFileSync(file, "utf-8");
550
+ let raw;
551
+ try {
552
+ raw = _deps.readFileSync(file, "utf-8");
553
+ } catch (cause) {
554
+ if (cause?.code === "ENOENT") return [];
555
+ const error = new Error(`cowork schedule store read failed: ${file}`, {
556
+ cause,
557
+ });
558
+ error.code = "COWORK_SCHEDULE_STORE_READ_FAILED";
559
+ error.filePath = file;
560
+ if (typeof cause?.code === "string") error.fsCode = cause.code;
561
+ throw error;
562
+ }
548
563
  const out = [];
549
564
  for (const line of raw.split("\n")) {
550
565
  const trimmed = line.trim();
551
566
  if (!trimmed) continue;
552
567
  try {
553
- out.push(JSON.parse(trimmed));
568
+ const schedule = JSON.parse(trimmed);
569
+ if (
570
+ !schedule ||
571
+ typeof schedule !== "object" ||
572
+ Array.isArray(schedule)
573
+ ) {
574
+ throw new TypeError("schedule entry must be an object");
575
+ }
576
+ out.push(schedule);
554
577
  } catch (cause) {
555
578
  if (failOnMalformed) {
556
579
  const error = new Error(
@@ -573,20 +596,68 @@ function _prepareScheduleDir(cwd) {
573
596
  }
574
597
 
575
598
  function _saveSchedulesUnlocked(cwd, schedules) {
576
- _prepareScheduleDir(cwd);
599
+ const directory = _prepareScheduleDir(cwd);
577
600
  const file = _scheduleFile(cwd);
578
601
  const body = schedules.map((s) => JSON.stringify(s)).join("\n");
602
+ const contents = Buffer.from(body ? `${body}\n` : "", "utf8");
579
603
  const tmp = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
604
+ let descriptor = null;
605
+ let renamed = false;
580
606
  try {
581
- _deps.writeFileSync(tmp, body ? body + "\n" : "", "utf-8");
607
+ descriptor = _deps.openSync(tmp, "wx", 0o600);
608
+ let offset = 0;
609
+ while (offset < contents.length) {
610
+ const written = _deps.writeSync(
611
+ descriptor,
612
+ contents,
613
+ offset,
614
+ contents.length - offset,
615
+ null,
616
+ );
617
+ if (!Number.isSafeInteger(written) || written <= 0) {
618
+ const cause = new Error("write returned no progress");
619
+ cause.code = "EIO";
620
+ throw cause;
621
+ }
622
+ offset += written;
623
+ }
624
+ _deps.fsyncSync(descriptor);
625
+ _deps.closeSync(descriptor);
626
+ descriptor = null;
582
627
  _deps.renameSync(tmp, file);
628
+ renamed = true;
629
+
630
+ if (process.platform !== "win32") {
631
+ const directoryDescriptor = _deps.openSync(directory, "r");
632
+ try {
633
+ _deps.fsyncSync(directoryDescriptor);
634
+ } finally {
635
+ _deps.closeSync(directoryDescriptor);
636
+ }
637
+ }
583
638
  } catch (error) {
584
- try {
585
- _deps.unlinkSync(tmp);
586
- } catch {
587
- // Preserve the original persistence error.
639
+ if (descriptor !== null) {
640
+ try {
641
+ _deps.closeSync(descriptor);
642
+ } catch {
643
+ // Preserve the original persistence error.
644
+ }
588
645
  }
589
- throw error;
646
+ if (!renamed) {
647
+ try {
648
+ _deps.unlinkSync(tmp);
649
+ } catch {
650
+ // Best-effort orphan cleanup; the authoritative file is unchanged.
651
+ }
652
+ }
653
+ const persistenceError =
654
+ error && (typeof error === "object" || typeof error === "function")
655
+ ? error
656
+ : new Error(`Cowork schedule persistence failed: ${String(error)}`, {
657
+ cause: error,
658
+ });
659
+ persistenceError.commitState = renamed ? "unknown" : "not-committed";
660
+ throw persistenceError;
590
661
  }
591
662
  }
592
663
 
@@ -15,6 +15,7 @@ import {
15
15
  normalizeJson,
16
16
  } from "./contract.js";
17
17
  import { SchedulerRuntime } from "./runtime.js";
18
+ import { CHECKPOINT_V1_RUNTIME_CONTROL } from "./runtime-control-capabilities.js";
18
19
  import { schedulerMigrationSourceDigest } from "./store.js";
19
20
  import {
20
21
  bindSchedulerAuthorityPolicy,
@@ -677,6 +678,7 @@ export function createAgendaSchedulerAdapter({
677
678
  }
678
679
  return {
679
680
  kind: AGENDA_SCHEDULER_KIND,
681
+ runtimeControl: CHECKPOINT_V1_RUNTIME_CONTROL,
680
682
  async adjudicate({ occurrence, adjudication }) {
681
683
  const payload = occurrence.payload;
682
684
  const expected = agendaEntrySnapshot(payload?.entry);
@@ -711,7 +713,7 @@ export function createAgendaSchedulerAdapter({
711
713
  }
712
714
  return { continue: true };
713
715
  },
714
- async execute({ occurrence }) {
716
+ async execute({ occurrence, checkpoint }) {
715
717
  const payload = occurrence.payload;
716
718
  const expected = agendaEntrySnapshot(payload?.entry);
717
719
  const expectedDigest = agendaEntrySnapshotDigest(expected);
@@ -757,6 +759,17 @@ export function createAgendaSchedulerAdapter({
757
759
  `Agenda entry changed after occurrence enqueue: ${expected.id}`,
758
760
  );
759
761
  }
762
+ if (expected.kind === "monitor" && typeof runMonitor !== "function") {
763
+ throw agendaError(
764
+ "AGENDA_SCHEDULER_MONITOR_RUNNER_REQUIRED",
765
+ "Agenda scheduler monitor execution requires a monitor runner",
766
+ );
767
+ }
768
+
769
+ checkpoint({
770
+ entryId: expected.id,
771
+ phase: "before_execution_bind",
772
+ });
760
773
 
761
774
  current = agendaStore.bindSchedulerExecution(expected.id, {
762
775
  occurrenceId: occurrence.id,
@@ -784,12 +797,6 @@ export function createAgendaSchedulerAdapter({
784
797
  let action = null;
785
798
  try {
786
799
  if (expected.kind === "monitor") {
787
- if (typeof runMonitor !== "function") {
788
- throw agendaError(
789
- "AGENDA_SCHEDULER_MONITOR_RUNNER_REQUIRED",
790
- "Agenda scheduler monitor execution requires a monitor runner",
791
- );
792
- }
793
800
  observation = normalizeMonitorObservation(
794
801
  expected,
795
802
  await runMonitor({
@@ -944,6 +951,9 @@ export function createAgendaSchedulerAdapter({
944
951
  );
945
952
  }
946
953
  },
954
+ async resume(context) {
955
+ return this.execute(context);
956
+ },
947
957
  classifyError(error) {
948
958
  return {
949
959
  retryable: error?.retryable === true,
@@ -1,12 +1,18 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { nextCronTime } from "../agent-schedule-store.js";
3
3
  import {
4
+ assertAutomationRuntimeBoundary,
4
5
  automationExecutionAuthorityDigest,
5
6
  automationExecutionAuthoritySnapshot,
6
7
  automationSchedulerAuthority,
8
+ classifyAutomationBoundaryError,
7
9
  normalizeAutomationExecutionAuthoritySnapshot,
8
10
  reserveAutomationExecutionAuthority,
9
11
  } from "../automation-execution-authority.js";
12
+ import {
13
+ resolveAutomationExecutionIncidentsForSucceededRun,
14
+ upsertAutomationExecutionIncident,
15
+ } from "../automation-execution-incident.js";
10
16
  import {
11
17
  EXECUTION_STATUS,
12
18
  FLOW_STATUS,
@@ -33,6 +39,7 @@ import {
33
39
  normalizeJson,
34
40
  } from "./contract.js";
35
41
  import { SchedulerRuntime } from "./runtime.js";
42
+ import { CHECKPOINT_V1_RUNTIME_CONTROL } from "./runtime-control-capabilities.js";
36
43
  import {
37
44
  schedulerJobDefinitionDigest,
38
45
  schedulerMigrationSourceDigest,
@@ -178,6 +185,54 @@ export function automationSchedulerExecutionId(occurrenceId) {
178
185
  return `exec-scheduler-${digest}`;
179
186
  }
180
187
 
188
+ function automationBoundaryIncidentEvidence(error, snapshot) {
189
+ const classified = classifyAutomationBoundaryError(error);
190
+ if (!classified) return null;
191
+ const deniedPermissions = Array.isArray(error?.details?.permissions)
192
+ ? error.details.permissions.map(String).slice(0, 32)
193
+ : [];
194
+ return {
195
+ classified,
196
+ boundary: {
197
+ ...(deniedPermissions.length > 0 ? { deniedPermissions } : {}),
198
+ ...(snapshot?.budget?.revision === undefined
199
+ ? {}
200
+ : { budgetRevision: snapshot.budget.revision }),
201
+ ...(Array.isArray(snapshot?.connectors)
202
+ ? { connectors: snapshot.connectors }
203
+ : {}),
204
+ },
205
+ details: { reasonCode: classified.code, retryable: false },
206
+ };
207
+ }
208
+
209
+ function persistAutomationSchedulerIncident(
210
+ db,
211
+ occurrence,
212
+ flow,
213
+ snapshot,
214
+ error,
215
+ now,
216
+ ) {
217
+ const evidence = automationBoundaryIncidentEvidence(error, snapshot);
218
+ if (!evidence) return null;
219
+ return upsertAutomationExecutionIncident(
220
+ db,
221
+ {
222
+ runId: automationSchedulerExecutionId(occurrence.id),
223
+ flowId: flow.id,
224
+ occurrenceId: occurrence.id,
225
+ triggerType: TRIGGER_TYPE.SCHEDULE,
226
+ category: evidence.classified.category,
227
+ code: evidence.classified.code,
228
+ authorityDigest: automationExecutionAuthorityDigest(snapshot, flow),
229
+ boundary: evidence.boundary,
230
+ details: evidence.details,
231
+ },
232
+ { now },
233
+ );
234
+ }
235
+
181
236
  export function buildAutomationSchedulerJob(flow, executionAuthority) {
182
237
  const snapshot = automationFlowSnapshot(flow);
183
238
  const normalizedExecutionAuthority =
@@ -702,7 +757,7 @@ export function authorizeAutomationOccurrence({ job, occurrence }) {
702
757
  }
703
758
  }
704
759
 
705
- function existingExecutionResult(db, occurrence, expected) {
760
+ function existingExecutionResult(db, occurrence, expected, now) {
706
761
  const executionId = automationSchedulerExecutionId(occurrence.id);
707
762
  const execution = getExecution(db, executionId);
708
763
  if (!execution) return null;
@@ -729,6 +784,9 @@ function existingExecutionResult(db, occurrence, expected) {
729
784
  { executionId: execution.id, status: execution.status },
730
785
  );
731
786
  }
787
+ resolveAutomationExecutionIncidentsForSucceededRun(db, execution.id, {
788
+ now,
789
+ });
732
790
  return execution;
733
791
  }
734
792
 
@@ -741,6 +799,7 @@ export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
741
799
  }
742
800
  return {
743
801
  kind: AUTOMATION_SCHEDULER_KIND,
802
+ runtimeControl: CHECKPOINT_V1_RUNTIME_CONTROL,
744
803
  async adjudicate({ occurrence, adjudication }) {
745
804
  const payload = occurrence?.payload;
746
805
  const expected = automationFlowSnapshot(payload?.flow);
@@ -772,7 +831,7 @@ export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
772
831
  }
773
832
  return { continue: true };
774
833
  },
775
- async execute({ occurrence }) {
834
+ async execute({ occurrence, checkpoint }) {
776
835
  const payload = occurrence?.payload;
777
836
  const expected = automationFlowSnapshot(payload?.flow);
778
837
  const expectedDigest = automationFlowSnapshotDigest(expected);
@@ -789,7 +848,7 @@ export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
789
848
  // A prior process may have committed the flow result and died before the
790
849
  // scheduler settlement. Durable success is authoritative even if the flow
791
850
  // was subsequently paused or edited.
792
- const recovered = existingExecutionResult(db, occurrence, expected);
851
+ const recovered = existingExecutionResult(db, occurrence, expected, now);
793
852
  if (recovered) return recovered;
794
853
 
795
854
  const current = getFlow(db, expected.id);
@@ -813,14 +872,31 @@ export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
813
872
  );
814
873
  }
815
874
 
816
- reserveAutomationExecutionAuthority(
817
- db,
818
- effectiveCurrent,
819
- occurrence.id,
820
- payload.executionAuthority,
821
- payload.executionAuthorityDigest,
822
- { now },
823
- );
875
+ checkpoint({
876
+ executionId: automationSchedulerExecutionId(occurrence.id),
877
+ phase: "before_execution_reservation",
878
+ });
879
+
880
+ try {
881
+ reserveAutomationExecutionAuthority(
882
+ db,
883
+ effectiveCurrent,
884
+ occurrence.id,
885
+ payload.executionAuthority,
886
+ payload.executionAuthorityDigest,
887
+ { now },
888
+ );
889
+ } catch (error) {
890
+ persistAutomationSchedulerIncident(
891
+ db,
892
+ occurrence,
893
+ effectiveCurrent,
894
+ payload.executionAuthority,
895
+ error,
896
+ now,
897
+ );
898
+ throw error;
899
+ }
824
900
 
825
901
  let execution;
826
902
  try {
@@ -828,8 +904,20 @@ export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
828
904
  inputData: {},
829
905
  triggerType: TRIGGER_TYPE.SCHEDULE,
830
906
  executionId: automationSchedulerExecutionId(occurrence.id),
907
+ executionAuthority: payload.executionAuthority,
908
+ assertRuntimeBoundary: assertAutomationRuntimeBoundary,
831
909
  });
832
910
  } catch (cause) {
911
+ if (classifyAutomationBoundaryError(cause)) {
912
+ persistAutomationSchedulerIncident(
913
+ db,
914
+ occurrence,
915
+ effectiveCurrent,
916
+ payload.executionAuthority,
917
+ cause,
918
+ now,
919
+ );
920
+ }
833
921
  if (cause?.code === "AUTOMATION_EXECUTION_OUTCOME_UNKNOWN") {
834
922
  throw automationError(
835
923
  "AUTOMATION_SCHEDULER_OUTCOME_UNKNOWN",
@@ -847,8 +935,14 @@ export function createAutomationSchedulerAdapter({ db, now = Date.now } = {}) {
847
935
  { executionId: execution.id, error: execution.error },
848
936
  );
849
937
  }
938
+ resolveAutomationExecutionIncidentsForSucceededRun(db, execution.id, {
939
+ now,
940
+ });
850
941
  return execution;
851
942
  },
943
+ async resume(context) {
944
+ return this.execute(context);
945
+ },
852
946
  classifyError() {
853
947
  return { retryable: false };
854
948
  },
@@ -1,12 +1,18 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Buffer } from "node:buffer";
3
3
  import {
4
+ assertAutomationRuntimeBoundary,
4
5
  automationExecutionAuthorityDigest,
5
6
  automationExecutionAuthoritySnapshot,
6
7
  automationSchedulerAuthority,
8
+ classifyAutomationBoundaryError,
7
9
  normalizeAutomationExecutionAuthoritySnapshot,
8
10
  reserveAutomationExecutionAuthority,
9
11
  } from "../automation-execution-authority.js";
12
+ import {
13
+ resolveAutomationExecutionIncidentsForSucceededRun,
14
+ upsertAutomationExecutionIncident,
15
+ } from "../automation-execution-incident.js";
10
16
  import {
11
17
  EXECUTION_STATUS,
12
18
  FLOW_STATUS,
@@ -26,6 +32,7 @@ import {
26
32
  normalizeJson,
27
33
  } from "./contract.js";
28
34
  import { SchedulerRuntime } from "./runtime.js";
35
+ import { CHECKPOINT_V1_RUNTIME_CONTROL } from "./runtime-control-capabilities.js";
29
36
 
30
37
  export const AUTOMATION_EVENT_KIND = "automation-event";
31
38
  export const AUTOMATION_EVENT_CAPABILITY = "automation.execute";
@@ -294,6 +301,39 @@ export function automationEventExecutionId(occurrenceId) {
294
301
  .digest("hex")}`;
295
302
  }
296
303
 
304
+ function persistAutomationEventIncident(db, occurrence, flow, error, now) {
305
+ const classified = classifyAutomationBoundaryError(error);
306
+ if (!classified) return null;
307
+ const snapshot = occurrence.payload.executionAuthority;
308
+ const deniedPermissions = Array.isArray(error?.details?.permissions)
309
+ ? error.details.permissions.map(String).slice(0, 32)
310
+ : [];
311
+ return upsertAutomationExecutionIncident(
312
+ db,
313
+ {
314
+ runId: automationEventExecutionId(occurrence.id),
315
+ flowId: flow.id,
316
+ occurrenceId: occurrence.id,
317
+ triggerType: TRIGGER_TYPE.EVENT,
318
+ triggerId: occurrence.payload?.trigger?.id || null,
319
+ category: classified.category,
320
+ code: classified.code,
321
+ authorityDigest: automationExecutionAuthorityDigest(snapshot, flow),
322
+ boundary: {
323
+ ...(deniedPermissions.length > 0 ? { deniedPermissions } : {}),
324
+ ...(snapshot?.budget?.revision === undefined
325
+ ? {}
326
+ : { budgetRevision: snapshot.budget.revision }),
327
+ ...(Array.isArray(snapshot?.connectors)
328
+ ? { connectors: snapshot.connectors }
329
+ : {}),
330
+ },
331
+ details: { reasonCode: classified.code, retryable: false },
332
+ },
333
+ { now },
334
+ );
335
+ }
336
+
297
337
  export function automationEventTriggerKey(event) {
298
338
  const normalized = normalizeAutomationChannelEvent(event);
299
339
  return `channel:${createHash("sha256")
@@ -514,7 +554,7 @@ export function authorizeAutomationEventOccurrence({ job, occurrence }) {
514
554
  }
515
555
  }
516
556
 
517
- function existingExecutionResult(db, occurrence, expectedFlow) {
557
+ function existingExecutionResult(db, occurrence, expectedFlow, now) {
518
558
  const executionId = automationEventExecutionId(occurrence.id);
519
559
  const execution = getExecution(db, executionId);
520
560
  if (!execution) return null;
@@ -541,6 +581,9 @@ function existingExecutionResult(db, occurrence, expectedFlow) {
541
581
  { executionId: execution.id, status: execution.status },
542
582
  );
543
583
  }
584
+ resolveAutomationExecutionIncidentsForSucceededRun(db, execution.id, {
585
+ now,
586
+ });
544
587
  return execution;
545
588
  }
546
589
 
@@ -553,6 +596,7 @@ export function createAutomationEventAdapter({ db, now = Date.now } = {}) {
553
596
  }
554
597
  return {
555
598
  kind: AUTOMATION_EVENT_KIND,
599
+ runtimeControl: CHECKPOINT_V1_RUNTIME_CONTROL,
556
600
  async adjudicate({ occurrence, adjudication }) {
557
601
  const payload = occurrence?.payload;
558
602
  const expectedFlow = automationEventFlowSnapshot(payload?.flow);
@@ -588,7 +632,7 @@ export function createAutomationEventAdapter({ db, now = Date.now } = {}) {
588
632
  }
589
633
  return { continue: true };
590
634
  },
591
- async execute({ occurrence }) {
635
+ async execute({ occurrence, checkpoint }) {
592
636
  const payload = occurrence?.payload;
593
637
  const expectedFlow = automationEventFlowSnapshot(payload?.flow);
594
638
  const expectedTrigger = automationEventTriggerSnapshot(payload?.trigger);
@@ -605,7 +649,12 @@ export function createAutomationEventAdapter({ db, now = Date.now } = {}) {
605
649
  );
606
650
  }
607
651
 
608
- const recovered = existingExecutionResult(db, occurrence, expectedFlow);
652
+ const recovered = existingExecutionResult(
653
+ db,
654
+ occurrence,
655
+ expectedFlow,
656
+ now,
657
+ );
609
658
  if (recovered) return recovered;
610
659
 
611
660
  const currentFlow = getFlow(db, expectedFlow.id);
@@ -630,14 +679,24 @@ export function createAutomationEventAdapter({ db, now = Date.now } = {}) {
630
679
  );
631
680
  }
632
681
 
633
- reserveAutomationExecutionAuthority(
634
- db,
635
- currentFlow,
636
- occurrence.id,
637
- payload.executionAuthority,
638
- payload.executionAuthorityDigest,
639
- { now },
640
- );
682
+ checkpoint({
683
+ executionId: automationEventExecutionId(occurrence.id),
684
+ phase: "before_execution_reservation",
685
+ });
686
+
687
+ try {
688
+ reserveAutomationExecutionAuthority(
689
+ db,
690
+ currentFlow,
691
+ occurrence.id,
692
+ payload.executionAuthority,
693
+ payload.executionAuthorityDigest,
694
+ { now },
695
+ );
696
+ } catch (error) {
697
+ persistAutomationEventIncident(db, occurrence, currentFlow, error, now);
698
+ throw error;
699
+ }
641
700
 
642
701
  let execution;
643
702
  try {
@@ -645,8 +704,19 @@ export function createAutomationEventAdapter({ db, now = Date.now } = {}) {
645
704
  inputData: { event: expectedEvent },
646
705
  triggerType: TRIGGER_TYPE.EVENT,
647
706
  executionId: automationEventExecutionId(occurrence.id),
707
+ executionAuthority: payload.executionAuthority,
708
+ assertRuntimeBoundary: assertAutomationRuntimeBoundary,
648
709
  });
649
710
  } catch (cause) {
711
+ if (classifyAutomationBoundaryError(cause)) {
712
+ persistAutomationEventIncident(
713
+ db,
714
+ occurrence,
715
+ currentFlow,
716
+ cause,
717
+ now,
718
+ );
719
+ }
650
720
  if (cause?.code === "AUTOMATION_EXECUTION_OUTCOME_UNKNOWN") {
651
721
  throw eventError(
652
722
  "AUTOMATION_EVENT_OUTCOME_UNKNOWN",
@@ -664,8 +734,14 @@ export function createAutomationEventAdapter({ db, now = Date.now } = {}) {
664
734
  { executionId: execution.id, error: execution.error },
665
735
  );
666
736
  }
737
+ resolveAutomationExecutionIncidentsForSucceededRun(db, execution.id, {
738
+ now,
739
+ });
667
740
  return execution;
668
741
  },
742
+ async resume(context) {
743
+ return this.execute(context);
744
+ },
669
745
  classifyError() {
670
746
  return { retryable: false };
671
747
  },
@@ -3,6 +3,26 @@ import { Buffer } from "node:buffer";
3
3
 
4
4
  export const SCHEDULER_SCHEMA_VERSION = 1;
5
5
  export const AUTHORITY_ENVELOPE_VERSION = 1;
6
+ export const RUNTIME_CONTROL_SCHEMA_VERSION = 1;
7
+
8
+ export const RUNTIME_PAUSE_RESUME = Object.freeze({
9
+ NONE: "none",
10
+ CHECKPOINT_V1: "checkpoint_v1",
11
+ });
12
+
13
+ export const RUNTIME_CONTROL_SAFE_POINTS = Object.freeze({
14
+ BEFORE_EXECUTE: "before_execute",
15
+ ADAPTER_CHECKPOINT: "adapter_checkpoint",
16
+ });
17
+
18
+ const RUNTIME_CONTROL_FIELDS = new Set([
19
+ "schemaVersion",
20
+ "pauseResume",
21
+ "safePoints",
22
+ ]);
23
+ const RUNTIME_CONTROL_SAFE_POINT_SET = new Set(
24
+ Object.values(RUNTIME_CONTROL_SAFE_POINTS),
25
+ );
6
26
 
7
27
  export const OCCURRENCE_STATUS = Object.freeze({
8
28
  QUEUED: "queued",
@@ -286,6 +306,77 @@ export function normalizeHistoryLimit(value = DEFAULT_HISTORY_LIMIT) {
286
306
  return Math.min(value, MAX_HISTORY_LIMIT);
287
307
  }
288
308
 
309
+ /**
310
+ * Normalize the durable runtime-control capability advertised by an adapter.
311
+ *
312
+ * Missing declarations are deliberately treated as unsupported. A checkpoint
313
+ * declaration is evidence, not a hint: only the two kernel-owned safe points
314
+ * are accepted and every persisted pause request is bound to these canonical
315
+ * bytes.
316
+ */
317
+ export function normalizeRuntimeControlCapability(input) {
318
+ if (input === undefined || input === null) {
319
+ return Object.freeze({
320
+ schemaVersion: RUNTIME_CONTROL_SCHEMA_VERSION,
321
+ pauseResume: RUNTIME_PAUSE_RESUME.NONE,
322
+ safePoints: Object.freeze([]),
323
+ });
324
+ }
325
+ const capability = assertPlainObject(input, "runtimeControl");
326
+ assertKnownFields(capability, RUNTIME_CONTROL_FIELDS, "runtimeControl");
327
+ if (
328
+ capability.schemaVersion !== undefined &&
329
+ capability.schemaVersion !== RUNTIME_CONTROL_SCHEMA_VERSION
330
+ ) {
331
+ throw invalidArgument(
332
+ `runtimeControl.schemaVersion must be ${RUNTIME_CONTROL_SCHEMA_VERSION}`,
333
+ );
334
+ }
335
+ const pauseResume = capability.pauseResume ?? RUNTIME_PAUSE_RESUME.NONE;
336
+ if (!Object.values(RUNTIME_PAUSE_RESUME).includes(pauseResume)) {
337
+ throw invalidArgument(
338
+ "runtimeControl.pauseResume must be none or checkpoint_v1",
339
+ );
340
+ }
341
+ const inputSafePoints = capability.safePoints ?? [];
342
+ if (!Array.isArray(inputSafePoints)) {
343
+ throw invalidArgument("runtimeControl.safePoints must be an array");
344
+ }
345
+ const safePoints = [
346
+ ...new Set(
347
+ inputSafePoints.map((safePoint, index) =>
348
+ normalizeIdentifier(safePoint, `runtimeControl.safePoints[${index}]`, {
349
+ maxLength: 64,
350
+ }),
351
+ ),
352
+ ),
353
+ ].sort();
354
+ const unknownSafePoints = safePoints.filter(
355
+ (safePoint) => !RUNTIME_CONTROL_SAFE_POINT_SET.has(safePoint),
356
+ );
357
+ if (unknownSafePoints.length > 0) {
358
+ throw invalidArgument("runtimeControl.safePoints contains unknown values", {
359
+ safePoints: unknownSafePoints,
360
+ });
361
+ }
362
+ if (
363
+ (pauseResume === RUNTIME_PAUSE_RESUME.NONE && safePoints.length !== 0) ||
364
+ (pauseResume === RUNTIME_PAUSE_RESUME.CHECKPOINT_V1 &&
365
+ safePoints.length === 0)
366
+ ) {
367
+ throw invalidArgument(
368
+ pauseResume === RUNTIME_PAUSE_RESUME.NONE
369
+ ? "runtimeControl.safePoints must be empty when pauseResume is none"
370
+ : "runtimeControl.safePoints must declare at least one checkpoint",
371
+ );
372
+ }
373
+ return Object.freeze({
374
+ schemaVersion: RUNTIME_CONTROL_SCHEMA_VERSION,
375
+ pauseResume,
376
+ safePoints: Object.freeze(safePoints),
377
+ });
378
+ }
379
+
289
380
  /**
290
381
  * Stable identity for one logical firing. All adapters must supply the same
291
382
  * triggerKey for the same logical schedule. Including jobRevision prevents an