chainlesschain 0.163.4 → 0.163.6

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 (200) hide show
  1. package/completions/_cc +1 -1
  2. package/completions/cc.bash +1 -1
  3. package/completions/cc.fish +1 -1
  4. package/completions/cc.ps1 +1 -1
  5. package/package.json +1 -1
  6. package/src/assets/web-panel/assets/{AIOps-DB5X7qqV.js → AIOps-DDIUofA8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-DxHRtKLk.js → ActionButton-Bm-5xYRn.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DIH5QrxT.js → Analytics-D5UH4ljI.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-uu0oxVz7.js → AppLayout-D_ifjOOP.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-BNSMiW9Y.js → Artifacts-BY1mXdC7.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-DcAzB4_x.js → Audit-Bujr4Ml3.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-ClSsvjIc.js → BackgroundAgents-sioKSTcC.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-BMciY0js.js → Backup-DDOAK8RK.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-Hn3b57Sh.js → BaseInput-CtCsVVke.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-CoSlC53V.js → Chat-DpNj0vnd.js} +4 -4
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CFevJ3So.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-B4L4Eu4w.js → Checkbox-DoHVNSXq.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C77FgRxF.js → Codegen-DlVl7Qmv.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-BrLtJLH3.js → Col-GIqoPYty.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-Be7pXXhA.js → Community-CIAPEVji.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-DuGPecjo.js → Compact-BN7JiI_h.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-CzS66mjP.js → Compliance-CqUWCM9B.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-CEhjCoyK.js → Cowork-A4iPp7A4.js} +3 -3
  24. package/src/assets/web-panel/assets/{Cron-D7ir8Vja.js → Cron-B2zeXXWb.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-BcXmuLxf.js → Crosschain-BR2xmWr8.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-Cto1dtBy.js → DID-BzFDjrIv.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-B7KjgwOX.js → Dashboard-DKtDo3yA.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-COMqxijg.js → Dropdown-RNx43_YG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZrya-nf.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-AmVeMq0J.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-kZU8c-27.js → Federation-BmRKvBTy.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-sOV22v2-.js → FormItemContext-CVxGTA-0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-BUuLtGCz.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-K-ZxsLsH.js → Git-DfqbkiOP.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-D3OEnawI.js → Governance-RuHyyj5O.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-Bll0gRmP.js → Inference-Bn7uZ7rI.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DBXwjr53.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-CEzPwEfP.js → Logs-BYNdOiCd.js} +2 -2
  39. package/src/assets/web-panel/assets/MarkdownRenderer-aMtgN-R6.js +1 -0
  40. package/src/assets/web-panel/assets/{Marketplace-BYyRyOPz.js → Marketplace-JQYmDqM8.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-DVu8w_rM.js → McpTools-Cyx4yZ-9.js} +4 -4
  42. package/src/assets/web-panel/assets/{Memory-tenuZ_hO.js → Memory-VDuGZTN2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Bxgy4ozS.js → MobileBridge-_yKo97gi.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-Dgx1D6sc.js → MobileProjects-CXRXW3q7.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-CRfJ8r8j.js → Mtc-acTGtfLH.js} +5 -5
  46. package/src/assets/web-panel/assets/{MtcAudit-BBJq4Aiz.js → MtcAudit-ClpjXfkY.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-B3ii9lai.js → Multisig-_6Tn3o96.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-FIi-Cubv.js → NLProgramming-CK7pbZzP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-DIq8MPf1.js → Notes-Czy6V_P8.js} +3 -3
  50. package/src/assets/web-panel/assets/{NotificationSettings-BFOD9eGd.js → NotificationSettings-Cqx-UVv3.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer--41u7vre.js → OrderTableRenderer-C69WDxGy.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-B_0AWztW.js → Organization-DFnA9vLQ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-B6I7TuXv.js → Overflow-ReZkk2yT.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Cdf6qens.js → P2P-DzjSI0aI.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-B_0FcQJY.js → PdhVaultBrowser-DKTf5xIP.js} +3 -3
  56. package/src/assets/web-panel/assets/{Permissions-6nFBjsQp.js → Permissions-DhQ5klhR.js} +3 -3
  57. package/src/assets/web-panel/assets/{PersonalDataHub-nQYV7xpn.js → PersonalDataHub-CUL1iHrW.js} +3 -3
  58. package/src/assets/web-panel/assets/{Pipeline-CBVod5jm.js → Pipeline-DU88iu4N.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-D52jW1Nt.js → Privacy-Bw-sYfyK.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-Bdx3BYhf.js → ProjectInit-Bq3Q5y0z.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-b7xr4DUa.js → ProjectSettings-C8spzpho.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-DhfifCKx.js → Projects-Dd-xuPIc.js} +1 -1
  63. package/src/assets/web-panel/assets/Providers-Bf4eTP9W.js +1 -0
  64. package/src/assets/web-panel/assets/{QrScannerModal-FQKSmc3e.js → QrScannerModal-Dme9BDU1.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-CSTrhzcF.js → QuickAsk-CIXfv1rE.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-CxMABOUh.js → Recommend-CIxvdeZ-.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-Cv2T8yRa.js → RemoteSession-C6k7MExI.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-CE1k4Vvu.js → Reputation-CISDz2bl.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-BNgNcAV_.js → Row-CdnDxXyf.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-5ZiBZ3ph.js → RssFeed-C-QpRHsl.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-mDOIF1UV.js → Search-DBjaGzSn.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-CtuCTC-e.js → Security-CQKukOeE.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-DxFO2Zg7.js → Services-DZz86jWb.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CKmKkH9y.js → Skeleton-jb8RMK0d.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-Bmzl9qkk.js → Skills-CdY76fZG.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-9RqaWHOC.js → Sla-C_CSiZX9.js} +1 -1
  77. package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +1 -0
  78. package/src/assets/web-panel/assets/{SyncSettings-CvNuCeBT.js → SyncSettings-qvimGO0O.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-B_sLdcnw.js → Tasks-Dng5LPJC.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-D9_YijdK.js → Templates-Bwqq5Wll.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-Cuc7BUSP.js → Tenant-DNWuwP7Z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-6VYRnJgd.js → Terminal-B-6Othpp.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-GtK8xd8Y.js → TimelineRenderer-DKpKZfzx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-BZ94Lt5W.js → Tokens-DzfGMd2J.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-DEcZgDaX.js → Trigger-BXwUi74J.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-CC3z9THe.js → Trust-BNzUq4ui.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-Cvtw8cV3.js → UkeySign-BYugxww4.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-CL4e6Bn3.js → VideoEditing-DdauhtSJ.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-ycEemP27.js → Wallet-CSC9bCe_.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-DgZ4X_aw.js → WebAuthn-Q8AN3bTO.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-D0qMhSv7.js → WorkflowEditor-d-LBGCuA.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DsAg01Zn.js → chat-BLVDCgsZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-B0oqAhrQ.js → colors-BZlPSEHl.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-CmX0Rabz.js → compact-item-IWseP6R6.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-Be4cn1Ub.js → createContext-DnVRV3Ao.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-D5v12kVq.js → hasIn-DLQZA_Wn.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BA4oulaP.js → index-1jeAudHd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DM724NgU.js → index-BEu2lMBy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-5LGY0JbG.js → index-BOZJBTWZ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DikJ0Qqe.js → index-BSjilZ9L.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BqltWojQ.js → index-BZs5bBnk.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-7VKDn-t1.js → index-B_N4-Gq9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BXGSbelg.js → index-Bbj2NS_I.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-CGiIyTZw.js → index-BhA3QEni.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BKmsS2jj.js → index-Bxp7uDD8.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-B3mUHuVc.js → index-C1ifZtXo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BBvyHguy.js → index-C2FqfMvU.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-q_fQ94SE.js → index-C5dgnilC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D5YVopyr.js → index-CINJQEj6.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-0eWLkQKT.js → index-COev0W2g.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BoAIAZ5C.js → index-CT9vVN-X.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CqoCZ5vR.js → index-CZDekj7T.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-301ogenv.js → index-CarMTIyQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Dn2dSdXx.js → index-CbQNL0ue.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DKUBj36_.js → index-CpBaJ3TD.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BBREnn1V.js → index-DB0MJgF_.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BRIqLy1m.js → index-DOoQpuQY.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-jK3ax6Z6.js → index-DTbxp4UH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-B1YGjOWC.js → index-DUq0c7gq.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CyyGTMKd.js → index-DZpYeqrh.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-_JjRLSD0.js → index-D_LQodeJ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DyUaFWEk.js → index-DalA0TKC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BaE0Ui07.js → index-DjWnONdy.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CyWBir7N.js → index-Dkwsi1Kn.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-qMuZ-NRR.js → index-DocEhinm.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Ckrk3pIV.js → index-DpVrK6fr.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DQRsQen0.js → index-Dy0R5ZED.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-frSojpHj.js → index-M7wxfAbm.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-D0BSkxYQ.js → index-N_GoWZX4.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-8WvYGnON.js → index-YSw_gxlZ.js} +1 -1
  132. package/src/assets/web-panel/assets/index-Zb2MeDSy.js +1 -0
  133. package/src/assets/web-panel/assets/index-fikqml4k.js +1 -0
  134. package/src/assets/web-panel/assets/{index-Hfe_dEGx.js → index-gt8bNefW.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-CZ2wUEgr.js → index-hwVDeoC0.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-CZpfzcuy.js → index-jWb70ZgZ.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-D4VdhJ9_.js → initDefaultProps-Co67Kqmb.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-CZJA0MQW.js → motion-BMOxQSF_.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-BqEs7Sb1.js → move-C5fWDw-N.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-BpHEtsu1.js → mtc-parser-BJevpL0e.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-BW94tY9W.js → omit-BCzNU8s6.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-BnBjPWP9.js → pickAttrs-CybWvDPk.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-uUkjQ_hu.js → placementArrow-BMWS9HnW.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-B-klUqXK.js → responsiveObserve-BoL2UnGY.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-CvY1cNAs.js → slide-DWNfXraU.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-Ix5sEDT2.js → statusUtils-BquTM3tX.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-C8qcXOne.js → styleChecker-BEkCleim.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-KNF6Kj8f.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-DGJr345n.js → useFs-CkDffJ4F.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-BboN99rI.js → usePersonalDataHub-qbvKXesB.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-Bgt8Vgte.js → vnode-C6v8n6mA.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-BhJtErgt.js → zoom-4CTeFILV.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +3 -3
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agenda.js +125 -7
  157. package/src/commands/automation.js +462 -3
  158. package/src/commands/background-session.js +102 -1
  159. package/src/commands/cowork.js +30 -13
  160. package/src/commands/loop.js +167 -36
  161. package/src/commands/routine.js +50 -13
  162. package/src/commands/scheduler-daemon.js +450 -0
  163. package/src/data/changelog.json +27 -1
  164. package/src/lib/agent-schedule-store.js +509 -36
  165. package/src/lib/automation-center-routines.js +363 -0
  166. package/src/lib/automation-center.js +435 -0
  167. package/src/lib/automation-engine.js +74 -19
  168. package/src/lib/automation-execution-authority.js +736 -0
  169. package/src/lib/background-agent-supervisor.js +78 -0
  170. package/src/lib/background-needs-input-incident.js +221 -0
  171. package/src/lib/cowork-cron.js +555 -39
  172. package/src/lib/cowork-observe.js +5 -1
  173. package/src/lib/loop.js +37 -2
  174. package/src/lib/micro-compact.js +11 -5
  175. package/src/lib/permission-engine.js +14 -7
  176. package/src/lib/process-execution-broker/index.js +14 -3
  177. package/src/lib/public-ide-capability-manifest.js +2 -0
  178. package/src/lib/routine-store.js +242 -68
  179. package/src/lib/scheduler-kernel/agenda-adapter.js +812 -0
  180. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  181. package/src/lib/scheduler-kernel/automation-adapter.js +592 -0
  182. package/src/lib/scheduler-kernel/automation-event-adapter.js +721 -0
  183. package/src/lib/scheduler-kernel/contract.js +11 -3
  184. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +820 -0
  185. package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
  186. package/src/lib/scheduler-kernel/routine-adapter.js +812 -0
  187. package/src/lib/scheduler-kernel/runtime.js +527 -0
  188. package/src/lib/scheduler-kernel/service.js +328 -0
  189. package/src/lib/scheduler-kernel/store.js +940 -98
  190. package/src/lib/session-message-provenance.js +4 -1
  191. package/src/repl/agent-repl.js +24 -5
  192. package/src/runtime/agent-core.js +38 -17
  193. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  194. package/src/workers/background-agent-worker.js +91 -28
  195. package/src/assets/web-panel/assets/MarkdownRenderer-CLmCbHx6.js +0 -1
  196. package/src/assets/web-panel/assets/Providers-Dy_PgDMr.js +0 -1
  197. package/src/assets/web-panel/assets/SpeechSettings-CGdzFIYw.js +0 -1
  198. package/src/assets/web-panel/assets/devWarning-CP2qzG6D.js +0 -1
  199. package/src/assets/web-panel/assets/index-CKB0vbJU.js +0 -1
  200. package/src/assets/web-panel/assets/index-DgoXmwqB.js +0 -1
@@ -0,0 +1,820 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ advanceScheduleNextAt,
5
+ bindSchedulerScheduleFire,
6
+ completeSchedulerScheduleFire,
7
+ coworkCronFireKey,
8
+ ensureScheduleNextAt,
9
+ getSchedule,
10
+ hasSecondsResolution,
11
+ latestCoworkCronTime,
12
+ loadSchedules,
13
+ parseCron,
14
+ } from "../cowork-cron.js";
15
+ import {
16
+ SchedulerKernelError,
17
+ canonicalJson,
18
+ normalizeEpochMs,
19
+ normalizeIdentifier,
20
+ normalizeJson,
21
+ } from "./contract.js";
22
+ import { SchedulerRuntime } from "./runtime.js";
23
+ import {
24
+ bindSchedulerAuthorityPolicy,
25
+ createSchedulerAuthorityResolver,
26
+ } from "./authority-resolver.js";
27
+
28
+ export const COWORK_CRON_SCHEDULER_KIND = "cowork-cron";
29
+ export const COWORK_CRON_SCHEDULER_CAPABILITY = "cowork.task.execute";
30
+ export const COWORK_CRON_SCHEDULER_RETRY_DELAY_MS = 60_000;
31
+
32
+ function coworkCronError(
33
+ code,
34
+ message,
35
+ details = undefined,
36
+ cause = undefined,
37
+ { retryable = false, retryAt } = {},
38
+ ) {
39
+ const error = new SchedulerKernelError(
40
+ code,
41
+ message,
42
+ details,
43
+ cause ? { cause } : undefined,
44
+ );
45
+ error.retryable = retryable;
46
+ if (retryAt !== undefined) error.retryAt = retryAt;
47
+ return error;
48
+ }
49
+
50
+ function safeFailure(error) {
51
+ return {
52
+ code:
53
+ typeof error?.code === "string" && error.code
54
+ ? error.code.slice(0, 128)
55
+ : "COWORK_CRON_TASK_FAILED",
56
+ message:
57
+ typeof error?.message === "string" && error.message
58
+ ? error.message.slice(0, 2_000)
59
+ : "Cowork cron task failed",
60
+ };
61
+ }
62
+
63
+ function normalizedCwd(cwd) {
64
+ if (typeof cwd !== "string" || cwd.length === 0) {
65
+ throw coworkCronError(
66
+ "COWORK_CRON_CWD_INVALID",
67
+ "Cowork cron scheduler requires a workspace path",
68
+ );
69
+ }
70
+ const absolute = resolve(cwd);
71
+ return process.platform === "win32" ? absolute.toLowerCase() : absolute;
72
+ }
73
+
74
+ function normalizeCreatedAt(value) {
75
+ const epoch = Date.parse(value);
76
+ if (!Number.isSafeInteger(epoch)) {
77
+ throw coworkCronError(
78
+ "COWORK_CRON_SNAPSHOT_INVALID",
79
+ "Cowork cron schedule createdAt is invalid",
80
+ );
81
+ }
82
+ return new Date(epoch).toISOString();
83
+ }
84
+
85
+ export function coworkCronScheduleSnapshot(schedule) {
86
+ if (!schedule || typeof schedule !== "object" || Array.isArray(schedule)) {
87
+ throw coworkCronError(
88
+ "COWORK_CRON_SNAPSHOT_INVALID",
89
+ "Cowork cron scheduler requires a schedule object",
90
+ );
91
+ }
92
+ const id = normalizeIdentifier(schedule.id, "coworkCron.schedule.id");
93
+ if (typeof schedule.cron !== "string" || schedule.cron.length === 0) {
94
+ throw coworkCronError(
95
+ "COWORK_CRON_SNAPSHOT_INVALID",
96
+ `Cowork cron expression is missing: ${id}`,
97
+ );
98
+ }
99
+ try {
100
+ parseCron(schedule.cron, { timeZone: schedule.timeZone });
101
+ } catch (cause) {
102
+ throw coworkCronError(
103
+ "COWORK_CRON_SNAPSHOT_INVALID",
104
+ `Cowork cron expression is invalid: ${id}`,
105
+ undefined,
106
+ cause,
107
+ );
108
+ }
109
+ if (
110
+ typeof schedule.userMessage !== "string" ||
111
+ schedule.userMessage.length === 0
112
+ ) {
113
+ throw coworkCronError(
114
+ "COWORK_CRON_SNAPSHOT_INVALID",
115
+ `Cowork cron userMessage is missing: ${id}`,
116
+ );
117
+ }
118
+ const templateId =
119
+ schedule.templateId === null || schedule.templateId === undefined
120
+ ? null
121
+ : normalizeIdentifier(
122
+ String(schedule.templateId),
123
+ "coworkCron.schedule.templateId",
124
+ );
125
+ const files = normalizeJson(
126
+ Array.isArray(schedule.files) ? schedule.files : [],
127
+ "coworkCron.schedule.files",
128
+ );
129
+ if (files.some((file) => typeof file !== "string" || file.length === 0)) {
130
+ throw coworkCronError(
131
+ "COWORK_CRON_SNAPSHOT_INVALID",
132
+ `Cowork cron files are invalid: ${id}`,
133
+ );
134
+ }
135
+ const timeZone =
136
+ parseCron(schedule.cron, { timeZone: schedule.timeZone }).timeZone || null;
137
+ return {
138
+ id,
139
+ cron: schedule.cron.trim(),
140
+ ...(timeZone ? { timeZone } : {}),
141
+ missedRunPolicy: "collapse",
142
+ templateId,
143
+ userMessage: schedule.userMessage,
144
+ files,
145
+ enabled: schedule.enabled === true,
146
+ createdAt: normalizeCreatedAt(schedule.createdAt),
147
+ };
148
+ }
149
+
150
+ export function coworkCronScheduleDigest(schedule) {
151
+ return createHash("sha256")
152
+ .update("chainlesschain.scheduler.cowork-cron.v1\0", "utf8")
153
+ .update(
154
+ canonicalJson(
155
+ coworkCronScheduleSnapshot(schedule),
156
+ "coworkCronScheduleSnapshot",
157
+ ),
158
+ "utf8",
159
+ )
160
+ .digest("hex");
161
+ }
162
+
163
+ export function coworkCronWorkspaceId(cwd) {
164
+ return createHash("sha256")
165
+ .update("chainlesschain.scheduler.cowork-workspace.v1\0", "utf8")
166
+ .update(normalizedCwd(cwd), "utf8")
167
+ .digest("hex")
168
+ .slice(0, 32);
169
+ }
170
+
171
+ export function coworkCronSchedulerJobId(cwd, scheduleId) {
172
+ return `cowork-cron:${coworkCronWorkspaceId(cwd)}:${normalizeIdentifier(
173
+ scheduleId,
174
+ "coworkCron.scheduleId",
175
+ )}`;
176
+ }
177
+
178
+ export function buildCoworkCronSchedulerJob(cwd, schedule) {
179
+ const workspace = normalizedCwd(cwd);
180
+ const snapshot = coworkCronScheduleSnapshot(schedule);
181
+ return {
182
+ id: coworkCronSchedulerJobId(workspace, snapshot.id),
183
+ kind: COWORK_CRON_SCHEDULER_KIND,
184
+ trigger: {
185
+ source: "cowork-schedules-jsonl",
186
+ expression: snapshot.cron,
187
+ resolution: hasSecondsResolution(snapshot.cron) ? "second" : "minute",
188
+ ...(snapshot.timeZone ? { timeZone: snapshot.timeZone } : {}),
189
+ missedRunPolicy: snapshot.missedRunPolicy,
190
+ },
191
+ payload: {
192
+ cwd: workspace,
193
+ schedule: snapshot,
194
+ snapshotDigest: coworkCronScheduleDigest(snapshot),
195
+ },
196
+ authority: {
197
+ schemaVersion: 1,
198
+ principal: { type: "cowork-cron", id: snapshot.id },
199
+ tenantId: null,
200
+ workspaceId: coworkCronWorkspaceId(workspace),
201
+ requestedCapabilities: [COWORK_CRON_SCHEDULER_CAPABILITY],
202
+ authorizationRefs: {
203
+ decisionId: null,
204
+ policyRevision: null,
205
+ grantIds: [],
206
+ approvalIds: [],
207
+ delegationIds: [],
208
+ },
209
+ },
210
+ enabled: snapshot.enabled,
211
+ maxAttempts: 3,
212
+ };
213
+ }
214
+
215
+ function comparableJob(job) {
216
+ return {
217
+ kind: job.kind,
218
+ trigger: job.trigger,
219
+ payload: job.payload,
220
+ authority: job.authority,
221
+ enabled: job.enabled,
222
+ maxAttempts: job.maxAttempts,
223
+ };
224
+ }
225
+
226
+ function sameJob(current, desired) {
227
+ return (
228
+ current !== null &&
229
+ canonicalJson(comparableJob(current), "currentCoworkCronJob") ===
230
+ canonicalJson(comparableJob(desired), "desiredCoworkCronJob")
231
+ );
232
+ }
233
+
234
+ export function syncCoworkCronSchedulerJob(schedulerStore, cwd, schedule) {
235
+ const desired = buildCoworkCronSchedulerJob(cwd, schedule);
236
+ desired.authority = bindSchedulerAuthorityPolicy(
237
+ schedulerStore,
238
+ desired.authority,
239
+ );
240
+ let current = schedulerStore.getJob(desired.id);
241
+ if (!current) {
242
+ try {
243
+ return schedulerStore.createJob(desired);
244
+ } catch (error) {
245
+ if (error?.code !== "SCHEDULER_CONFLICT") throw error;
246
+ current = schedulerStore.getJob(desired.id);
247
+ }
248
+ }
249
+ if (sameJob(current, desired)) return current;
250
+ try {
251
+ return schedulerStore.updateJob(current.id, current.revision, {
252
+ kind: desired.kind,
253
+ trigger: desired.trigger,
254
+ payload: desired.payload,
255
+ authority: desired.authority,
256
+ enabled: desired.enabled,
257
+ maxAttempts: desired.maxAttempts,
258
+ });
259
+ } catch (error) {
260
+ if (error?.code !== "SCHEDULER_REVISION_CONFLICT") throw error;
261
+ const latest = schedulerStore.getJob(desired.id);
262
+ if (sameJob(latest, desired)) return latest;
263
+ throw error;
264
+ }
265
+ }
266
+
267
+ export function coworkCronScheduledFor(schedule, at) {
268
+ const date = at instanceof Date ? new Date(at.getTime()) : new Date(at);
269
+ if (!Number.isFinite(date.getTime())) {
270
+ throw coworkCronError(
271
+ "COWORK_CRON_FIRE_TIME_INVALID",
272
+ "Cowork cron fire time is invalid",
273
+ );
274
+ }
275
+ const matcher = parseCron(schedule.cron, { timeZone: schedule.timeZone });
276
+ if (!matcher(date)) {
277
+ throw coworkCronError(
278
+ "COWORK_CRON_NOT_DUE",
279
+ `Cowork cron schedule is not due: ${schedule.id}`,
280
+ );
281
+ }
282
+ if (matcher.hasSeconds) date.setMilliseconds(0);
283
+ else date.setSeconds(0, 0);
284
+ return normalizeEpochMs(date.getTime(), "coworkCron.scheduledFor");
285
+ }
286
+
287
+ export function enqueueCoworkCronSchedule(schedulerStore, cwd, schedule, at) {
288
+ const job = syncCoworkCronSchedulerJob(schedulerStore, cwd, schedule);
289
+ const scheduledFor = coworkCronScheduledFor(schedule, at);
290
+ return schedulerStore.enqueueOccurrence({
291
+ jobId: job.id,
292
+ scheduledFor,
293
+ triggerKey: coworkCronFireKey(schedule, new Date(scheduledFor)),
294
+ });
295
+ }
296
+
297
+ export function authorizeCoworkCronOccurrence({ job, occurrence }) {
298
+ try {
299
+ const payload = occurrence?.payload;
300
+ const schedule = payload?.schedule;
301
+ const authority = occurrence?.authority;
302
+ const expectedDigest = coworkCronScheduleDigest(schedule);
303
+ const allowed =
304
+ job?.kind === COWORK_CRON_SCHEDULER_KIND &&
305
+ authority?.principal?.type === "cowork-cron" &&
306
+ authority?.principal?.id === schedule.id &&
307
+ authority?.workspaceId === coworkCronWorkspaceId(payload.cwd) &&
308
+ Array.isArray(authority?.requestedCapabilities) &&
309
+ authority.requestedCapabilities.length === 1 &&
310
+ authority.requestedCapabilities[0] === COWORK_CRON_SCHEDULER_CAPABILITY &&
311
+ payload.snapshotDigest === expectedDigest;
312
+ return {
313
+ allowed,
314
+ reason: allowed
315
+ ? "cowork_cron_snapshot_bound"
316
+ : "cowork_cron_authority_mismatch",
317
+ };
318
+ } catch {
319
+ return { allowed: false, reason: "cowork_cron_authority_malformed" };
320
+ }
321
+ }
322
+
323
+ function matchingEvidence(
324
+ schedule,
325
+ { occurrenceId, snapshotDigest, attempt, status },
326
+ ) {
327
+ const evidence = schedule?.schedulerExecution;
328
+ return (
329
+ evidence?.occurrenceId === occurrenceId &&
330
+ evidence.snapshotDigest === snapshotDigest &&
331
+ evidence.attempt === attempt &&
332
+ evidence.status === status
333
+ );
334
+ }
335
+
336
+ function recoveredResult(schedule, evidence) {
337
+ const result =
338
+ evidence.result && typeof evidence.result === "object"
339
+ ? evidence.result
340
+ : {
341
+ scheduleId: schedule.id,
342
+ deliveryId: evidence.deliveryId,
343
+ status: schedule.lastStatus || "completed",
344
+ };
345
+ return { ...result, recovered: true };
346
+ }
347
+
348
+ function recoveredLegacyResult(schedule, deliveryId) {
349
+ return {
350
+ scheduleId: schedule.id,
351
+ deliveryId,
352
+ taskId: null,
353
+ status: schedule.lastStatus || "completed",
354
+ recovered: true,
355
+ legacyEvidence: true,
356
+ };
357
+ }
358
+
359
+ export function createCoworkCronSchedulerAdapter({ runTask, now } = {}) {
360
+ if (typeof runTask !== "function") {
361
+ throw coworkCronError(
362
+ "COWORK_CRON_RUNNER_REQUIRED",
363
+ "Cowork cron scheduler adapter requires a runTask function",
364
+ );
365
+ }
366
+ if (typeof now !== "function") {
367
+ throw coworkCronError(
368
+ "COWORK_CRON_CLOCK_REQUIRED",
369
+ "Cowork cron scheduler adapter requires a clock function",
370
+ );
371
+ }
372
+ return {
373
+ kind: COWORK_CRON_SCHEDULER_KIND,
374
+ async execute({ occurrence, signal }) {
375
+ const payload = occurrence.payload;
376
+ const expected = coworkCronScheduleSnapshot(payload?.schedule);
377
+ const expectedDigest = coworkCronScheduleDigest(expected);
378
+ if (payload.snapshotDigest !== expectedDigest) {
379
+ throw coworkCronError(
380
+ "COWORK_CRON_SNAPSHOT_INVALID",
381
+ `Cowork cron occurrence snapshot is invalid: ${occurrence.id}`,
382
+ );
383
+ }
384
+ const cwd = normalizedCwd(payload.cwd);
385
+ let current = getSchedule(cwd, expected.id);
386
+ if (!current) {
387
+ throw coworkCronError(
388
+ "COWORK_CRON_SCHEDULE_NOT_FOUND",
389
+ `Cowork cron schedule disappeared: ${expected.id}`,
390
+ );
391
+ }
392
+ const prior = current.schedulerExecution;
393
+ if (prior?.occurrenceId === occurrence.id) {
394
+ if (prior.snapshotDigest !== expectedDigest) {
395
+ throw coworkCronError(
396
+ "COWORK_CRON_BINDING_MISMATCH",
397
+ `Cowork cron evidence has a different snapshot: ${expected.id}`,
398
+ );
399
+ }
400
+ if (prior.status === "succeeded") {
401
+ return recoveredResult(current, prior);
402
+ }
403
+ if (prior.status === "running") {
404
+ throw coworkCronError(
405
+ "COWORK_CRON_OUTCOME_UNKNOWN",
406
+ `Cowork cron task outcome is unknown; refusing duplicate execution: ${expected.id}`,
407
+ );
408
+ }
409
+ } else if (prior?.status === "running") {
410
+ throw coworkCronError(
411
+ "COWORK_CRON_EXECUTION_CONFLICT",
412
+ `Cowork cron schedule has another unresolved execution: ${expected.id}`,
413
+ );
414
+ }
415
+ const deliveryId = occurrence.triggerKey;
416
+ if (current.lastDeliveryId === deliveryId) {
417
+ return recoveredLegacyResult(current, deliveryId);
418
+ }
419
+ if (coworkCronScheduleDigest(current) !== expectedDigest) {
420
+ throw coworkCronError(
421
+ "COWORK_CRON_STALE_SNAPSHOT",
422
+ `Cowork cron schedule changed after enqueue: ${expected.id}`,
423
+ );
424
+ }
425
+ if (current.enabled !== true) {
426
+ throw coworkCronError(
427
+ "COWORK_CRON_DISABLED",
428
+ `Cowork cron schedule was disabled: ${expected.id}`,
429
+ );
430
+ }
431
+
432
+ current = bindSchedulerScheduleFire(cwd, expected.id, {
433
+ deliveryId,
434
+ occurrenceId: occurrence.id,
435
+ snapshotDigest: expectedDigest,
436
+ attempt: occurrence.attempt,
437
+ at: now(),
438
+ });
439
+ if (current.lastDeliveryId === deliveryId) {
440
+ return recoveredLegacyResult(current, deliveryId);
441
+ }
442
+ if (coworkCronScheduleDigest(current) !== expectedDigest) {
443
+ const stale = coworkCronError(
444
+ "COWORK_CRON_STALE_SNAPSHOT",
445
+ `Cowork cron schedule changed while binding: ${expected.id}`,
446
+ );
447
+ completeSchedulerScheduleFire(cwd, expected.id, {
448
+ deliveryId,
449
+ occurrenceId: occurrence.id,
450
+ snapshotDigest: expectedDigest,
451
+ attempt: occurrence.attempt,
452
+ outcome: "failed",
453
+ error: safeFailure(stale),
454
+ at: now(),
455
+ });
456
+ throw stale;
457
+ }
458
+
459
+ let taskResult;
460
+ try {
461
+ taskResult = await runTask({
462
+ templateId: expected.templateId,
463
+ userMessage: expected.userMessage,
464
+ files: expected.files,
465
+ cwd,
466
+ scheduleId: expected.id,
467
+ deliveryId,
468
+ schedulerOccurrenceId: occurrence.id,
469
+ signal,
470
+ });
471
+ if (taskResult?.status !== "completed") {
472
+ throw coworkCronError(
473
+ "COWORK_CRON_TASK_REPORTED_FAILED",
474
+ typeof taskResult?.result?.summary === "string"
475
+ ? taskResult.result.summary
476
+ : `Cowork cron task returned a non-completed status: ${taskResult?.status || "missing"}`,
477
+ {
478
+ taskId:
479
+ typeof taskResult?.taskId === "string"
480
+ ? taskResult.taskId
481
+ : null,
482
+ status:
483
+ typeof taskResult?.status === "string"
484
+ ? taskResult.status
485
+ : null,
486
+ },
487
+ );
488
+ }
489
+ } catch (error) {
490
+ const retryAt = normalizeEpochMs(
491
+ new Date(now()).getTime() + COWORK_CRON_SCHEDULER_RETRY_DELAY_MS,
492
+ "coworkCron.retryAt",
493
+ );
494
+ let persisted = false;
495
+ try {
496
+ completeSchedulerScheduleFire(cwd, expected.id, {
497
+ deliveryId,
498
+ occurrenceId: occurrence.id,
499
+ snapshotDigest: expectedDigest,
500
+ attempt: occurrence.attempt,
501
+ outcome: "failed",
502
+ error: safeFailure(error),
503
+ retryAt,
504
+ at: now(),
505
+ });
506
+ persisted = true;
507
+ } catch (completionError) {
508
+ persisted = matchingEvidence(getSchedule(cwd, expected.id), {
509
+ occurrenceId: occurrence.id,
510
+ snapshotDigest: expectedDigest,
511
+ attempt: occurrence.attempt,
512
+ status: "failed",
513
+ });
514
+ if (!persisted) {
515
+ throw coworkCronError(
516
+ "COWORK_CRON_FAILURE_PERSIST_FAILED",
517
+ `Cowork cron failure evidence could not be persisted: ${expected.id}`,
518
+ undefined,
519
+ completionError,
520
+ );
521
+ }
522
+ }
523
+ throw coworkCronError(
524
+ typeof error?.code === "string"
525
+ ? error.code
526
+ : "COWORK_CRON_TASK_FAILED",
527
+ typeof error?.message === "string"
528
+ ? error.message
529
+ : `Cowork cron task failed: ${expected.id}`,
530
+ undefined,
531
+ error,
532
+ { retryable: true, retryAt },
533
+ );
534
+ }
535
+
536
+ const action = {
537
+ scheduleId: expected.id,
538
+ deliveryId,
539
+ taskId: taskResult?.taskId || null,
540
+ status: taskResult?.status || "completed",
541
+ };
542
+ try {
543
+ completeSchedulerScheduleFire(cwd, expected.id, {
544
+ deliveryId,
545
+ occurrenceId: occurrence.id,
546
+ snapshotDigest: expectedDigest,
547
+ attempt: occurrence.attempt,
548
+ outcome: "succeeded",
549
+ result: action,
550
+ at: now(),
551
+ });
552
+ return action;
553
+ } catch (completionError) {
554
+ const persisted = getSchedule(cwd, expected.id);
555
+ if (
556
+ matchingEvidence(persisted, {
557
+ occurrenceId: occurrence.id,
558
+ snapshotDigest: expectedDigest,
559
+ attempt: occurrence.attempt,
560
+ status: "succeeded",
561
+ })
562
+ ) {
563
+ return recoveredResult(persisted, persisted.schedulerExecution);
564
+ }
565
+ throw coworkCronError(
566
+ "COWORK_CRON_OUTCOME_UNKNOWN",
567
+ `Cowork cron task finished but durable outcome is unknown; refusing retry: ${expected.id}`,
568
+ undefined,
569
+ completionError,
570
+ );
571
+ }
572
+ },
573
+ classifyError(error) {
574
+ return {
575
+ retryable: error?.retryable === true,
576
+ ...(error?.retryAt === undefined ? {} : { retryAt: error.retryAt }),
577
+ };
578
+ },
579
+ };
580
+ }
581
+
582
+ export class CoworkCronSchedulerBridge {
583
+ constructor({
584
+ cwd,
585
+ schedulerStore,
586
+ runTask,
587
+ now = () => new Date(),
588
+ ownerId,
589
+ leaseMs,
590
+ renewIntervalMs,
591
+ authorityResolver,
592
+ } = {}) {
593
+ if (!schedulerStore) {
594
+ throw coworkCronError(
595
+ "COWORK_CRON_SCHEDULER_STORE_REQUIRED",
596
+ "Cowork cron bridge requires a scheduler store",
597
+ );
598
+ }
599
+ if (typeof now !== "function") {
600
+ throw coworkCronError(
601
+ "COWORK_CRON_CLOCK_REQUIRED",
602
+ "Cowork cron bridge requires a clock function",
603
+ );
604
+ }
605
+ this.cwd = normalizedCwd(cwd);
606
+ this.schedulerStore = schedulerStore;
607
+ this.now = now;
608
+ this.runtime = new SchedulerRuntime({
609
+ store: schedulerStore,
610
+ adapters: [createCoworkCronSchedulerAdapter({ runTask, now })],
611
+ authorize:
612
+ authorityResolver ||
613
+ createSchedulerAuthorityResolver({
614
+ store: schedulerStore,
615
+ validate: authorizeCoworkCronOccurrence,
616
+ }),
617
+ ...(ownerId === undefined ? {} : { ownerId }),
618
+ ...(leaseMs === undefined ? {} : { leaseMs }),
619
+ ...(renewIntervalMs === undefined ? {} : { renewIntervalMs }),
620
+ });
621
+ }
622
+
623
+ async runDue({ signal } = {}) {
624
+ const results = [];
625
+ const observed = new Set();
626
+ const recovered = await this.runtime.runUntilIdle({
627
+ limit: 10_000,
628
+ signal,
629
+ jobKind: COWORK_CRON_SCHEDULER_KIND,
630
+ workspaceId: coworkCronWorkspaceId(this.cwd),
631
+ });
632
+ for (const result of recovered.results) {
633
+ const occurrence = result.occurrence;
634
+ const schedule = occurrence?.payload?.schedule;
635
+ if (!schedule?.id) continue;
636
+ observed.add(occurrence.id);
637
+ results.push({
638
+ schedule: schedule.id,
639
+ occurrence: occurrence.id,
640
+ recovered: true,
641
+ result,
642
+ });
643
+ }
644
+ if (signal?.aborted) return results;
645
+
646
+ const at = this.now();
647
+ const date = at instanceof Date ? new Date(at.getTime()) : new Date(at);
648
+ if (!Number.isFinite(date.getTime())) {
649
+ throw coworkCronError(
650
+ "COWORK_CRON_FIRE_TIME_INVALID",
651
+ "Cowork cron bridge clock returned an invalid date",
652
+ );
653
+ }
654
+ const schedules = loadSchedules(this.cwd, { failOnMalformed: true });
655
+ for (const loadedSchedule of schedules) {
656
+ let schedule = loadedSchedule;
657
+ if (schedule.enabled !== true) continue;
658
+ if (!Number.isSafeInteger(Number(schedule.nextAt))) {
659
+ schedule = ensureScheduleNextAt(this.cwd, schedule.id, date);
660
+ }
661
+ if (schedule?.nextAt === null) continue;
662
+ const scheduledFor = schedule?.nextAt;
663
+ if (
664
+ typeof scheduledFor !== "number" ||
665
+ !Number.isSafeInteger(scheduledFor) ||
666
+ scheduledFor > date.getTime()
667
+ ) {
668
+ continue;
669
+ }
670
+ const collapsedFor = latestCoworkCronTime(
671
+ schedule.cron,
672
+ scheduledFor,
673
+ date.getTime(),
674
+ { timeZone: schedule.timeZone },
675
+ );
676
+ const scheduledDate = new Date(collapsedFor);
677
+ const deliveryId = coworkCronFireKey(schedule, scheduledDate);
678
+ if (schedule.lastDeliveryId === deliveryId) {
679
+ advanceScheduleNextAt(this.cwd, schedule.id, {
680
+ expectedNextAt: scheduledFor,
681
+ from: date,
682
+ });
683
+ continue;
684
+ }
685
+ const occurrence = enqueueCoworkCronSchedule(
686
+ this.schedulerStore,
687
+ this.cwd,
688
+ schedule,
689
+ scheduledDate,
690
+ );
691
+ advanceScheduleNextAt(this.cwd, schedule.id, {
692
+ expectedNextAt: scheduledFor,
693
+ from: scheduledDate,
694
+ });
695
+ if (observed.has(occurrence.id)) continue;
696
+ const result = await this.runtime.runOccurrence(occurrence.id, {
697
+ signal,
698
+ });
699
+ results.push({
700
+ schedule: schedule.id,
701
+ occurrence: occurrence.id,
702
+ deduplicated: occurrence.deduplicated,
703
+ result,
704
+ });
705
+ }
706
+ return results;
707
+ }
708
+ }
709
+
710
+ export class CoworkCronKernelScheduler {
711
+ constructor({
712
+ cwd = process.cwd(),
713
+ schedulerStore,
714
+ runTask,
715
+ now = () => new Date(),
716
+ intervalMs,
717
+ onEvent,
718
+ ownerId = `cowork-cron:${process.pid}:${randomUUID()}`,
719
+ leaseMs,
720
+ } = {}) {
721
+ this.cwd = normalizedCwd(cwd);
722
+ this.schedulerStore = schedulerStore;
723
+ this.now = now;
724
+ this.onEvent = onEvent;
725
+ this._intervalPinned = typeof intervalMs === "number";
726
+ this.intervalMs = intervalMs || 60_000;
727
+ this._timer = null;
728
+ this._tickPromise = null;
729
+ this.bridge = new CoworkCronSchedulerBridge({
730
+ cwd: this.cwd,
731
+ schedulerStore,
732
+ runTask,
733
+ now,
734
+ ownerId,
735
+ ...(leaseMs === undefined ? {} : { leaseMs }),
736
+ });
737
+ }
738
+
739
+ _emit(event) {
740
+ if (typeof this.onEvent !== "function") return;
741
+ try {
742
+ this.onEvent(event);
743
+ } catch {
744
+ // Observers cannot break the scheduler.
745
+ }
746
+ }
747
+
748
+ _adaptInterval() {
749
+ if (this._intervalPinned) return;
750
+ const schedules = loadSchedules(this.cwd);
751
+ const wantsSeconds = schedules.some(
752
+ (schedule) =>
753
+ schedule.enabled === true && hasSecondsResolution(schedule.cron),
754
+ );
755
+ const desired = wantsSeconds ? 1_000 : 60_000;
756
+ if (desired === this.intervalMs) return;
757
+ this.intervalMs = desired;
758
+ if (this._timer) {
759
+ clearInterval(this._timer);
760
+ this._timer = setInterval(() => void this.tick(), this.intervalMs);
761
+ this._emit({ type: "scheduler-retuned", intervalMs: this.intervalMs });
762
+ }
763
+ }
764
+
765
+ start() {
766
+ if (this._timer) return;
767
+ this._adaptInterval();
768
+ void this.tick();
769
+ this._timer = setInterval(() => void this.tick(), this.intervalMs);
770
+ this._emit({ type: "scheduler-started", intervalMs: this.intervalMs });
771
+ }
772
+
773
+ stop() {
774
+ if (this._timer) clearInterval(this._timer);
775
+ this._timer = null;
776
+ this._emit({ type: "scheduler-stopped" });
777
+ }
778
+
779
+ async tick({ signal } = {}) {
780
+ if (this._tickPromise) return this._tickPromise;
781
+ this._tickPromise = (async () => {
782
+ try {
783
+ this._adaptInterval();
784
+ const entries = await this.bridge.runDue({ signal });
785
+ for (const entry of entries) {
786
+ const result = entry.result;
787
+ if (result.status === "succeeded") {
788
+ this._emit({
789
+ type: "schedule-completed",
790
+ id: entry.schedule,
791
+ occurrenceId: entry.occurrence,
792
+ ...result.result,
793
+ });
794
+ } else if (result.status === "retry_wait") {
795
+ this._emit({
796
+ type: "schedule-retry",
797
+ id: entry.schedule,
798
+ occurrenceId: entry.occurrence,
799
+ error: result.error?.message,
800
+ });
801
+ } else if (!["busy", "idle"].includes(result.status)) {
802
+ this._emit({
803
+ type: "schedule-failed",
804
+ id: entry.schedule,
805
+ occurrenceId: entry.occurrence,
806
+ error: result.error?.message || result.status,
807
+ });
808
+ }
809
+ }
810
+ return entries;
811
+ } catch (error) {
812
+ this._emit({ type: "scheduler-error", error: error.message });
813
+ return [];
814
+ } finally {
815
+ this._tickPromise = null;
816
+ }
817
+ })();
818
+ return this._tickPromise;
819
+ }
820
+ }