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
@@ -9,6 +9,7 @@ import {
9
9
  } from "../lib/collaboration-run-store.js";
10
10
  import { readSessionHostResumeState } from "../lib/session-host-snapshot.js";
11
11
  import executionBroker from "../lib/process-execution-broker/index.js";
12
+ import { registerSchedulerDaemonCommands } from "./scheduler-daemon.js";
12
13
 
13
14
  export function buildBackgroundDispatchArgs(prompt) {
14
15
  return ["agent", "--bg", `--print=${prompt}`];
@@ -100,6 +101,21 @@ export function formatBackgroundAgentDetails(
100
101
  );
101
102
  }
102
103
  if (session.phase) lines.push(` phase: ${session.phase}`);
104
+ if (session.pendingQuestion) {
105
+ const prompt = String(
106
+ session.pendingQuestion.prompt || session.pendingQuestion.question || "",
107
+ )
108
+ .replace(/\s+/gu, " ")
109
+ .trim()
110
+ .slice(0, 240);
111
+ lines.push(` needsInput: ${prompt || "question pending"}`);
112
+ }
113
+ if (session.needsInputIncident) {
114
+ const incident = session.needsInputIncident;
115
+ lines.push(
116
+ ` needsInputIncident: ${incident.incidentId || "-"} status=${incident.status || "?"} notification=${incident.notification?.status || "?"}`,
117
+ );
118
+ }
103
119
  if (session.pr?.number) {
104
120
  lines.push(
105
121
  ` pr: #${session.pr.number}${session.pr.state ? ` ${session.pr.state}` : ""}${session.pr.url ? ` ${session.pr.url}` : ""}`,
@@ -120,6 +136,19 @@ export function formatBackgroundAgentDetails(
120
136
  lines.push("", "Commands:");
121
137
  lines.push(` cc attach ${session.id}`);
122
138
  lines.push(` cc logs ${session.id} -n 100`);
139
+ if (
140
+ ["failed", "unconfigured"].includes(
141
+ session.needsInputIncident?.notification?.status,
142
+ )
143
+ ) {
144
+ lines.push(` cc daemon notify-needs-input ${session.id}`);
145
+ } else if (
146
+ ["delivering", "outcome_unknown", "partial"].includes(
147
+ session.needsInputIncident?.notification?.status,
148
+ )
149
+ ) {
150
+ lines.push(` cc daemon notify-needs-input ${session.id} --force`);
151
+ }
123
152
  if (session.status === "running") {
124
153
  lines.push(` cc daemon stop ${session.id}`);
125
154
  }
@@ -275,6 +304,49 @@ export async function replyBackgroundAgent(id, prompt, dependencies = {}) {
275
304
  }
276
305
  }
277
306
 
307
+ export async function retryBackgroundNeedsInputNotification(
308
+ id,
309
+ options = {},
310
+ dependencies = {},
311
+ ) {
312
+ const sessionId = String(id || "").trim();
313
+ if (!sessionId) throw new Error("background agent id is required");
314
+ const supervisor = dependencies.supervisor || (await loadSupervisor());
315
+ const state = supervisor.readBackgroundAgentState(sessionId);
316
+ if (!state) throw new Error(`Background agent not found: ${sessionId}`);
317
+ if (!state.needsInputIncident) {
318
+ throw new Error(
319
+ `Background agent ${sessionId} has no needs-input incident`,
320
+ );
321
+ }
322
+ const result = await supervisor.deliverBackgroundNeedsInputNotification(
323
+ sessionId,
324
+ {
325
+ retry: true,
326
+ force: options.force === true,
327
+ ...(dependencies.notify ? { notify: dependencies.notify } : {}),
328
+ },
329
+ );
330
+ if (!result.applied) {
331
+ const error = new Error(
332
+ result.reason === "delivery_in_progress"
333
+ ? `Needs-input notification is already being delivered for ${sessionId}`
334
+ : result.reason === "delivery_not_retryable"
335
+ ? `Needs-input notification for ${sessionId} is not safely retryable; use --force only after accepting possible duplicate delivery`
336
+ : `Background agent ${sessionId} no longer has a pending needs-input incident`,
337
+ );
338
+ error.code = "BACKGROUND_NEEDS_INPUT_NOTIFICATION_NOT_RETRYABLE";
339
+ error.reason = result.reason;
340
+ throw error;
341
+ }
342
+ return {
343
+ id: sessionId,
344
+ incidentId: result.incident?.incidentId || null,
345
+ incidentStatus: result.incident?.status || null,
346
+ notification: result.incident?.notification || null,
347
+ };
348
+ }
349
+
278
350
  export const LOG_TRUNCATION_NOTICE =
279
351
  "--- log truncated/rotated, resuming from tail ---";
280
352
  const TRUNCATION_RESUME_TAIL_BYTES = 4096;
@@ -825,7 +897,36 @@ export function registerBackgroundSessionCommands(program) {
825
897
 
826
898
  const daemon = program
827
899
  .command("daemon")
828
- .description("Inspect and stop background agent supervisor sessions");
900
+ .description("Manage background agent and scheduler services");
901
+
902
+ registerSchedulerDaemonCommands(daemon);
903
+
904
+ daemon
905
+ .command("notify-needs-input <id>")
906
+ .description(
907
+ "Retry a durable needs-input notification for a background run",
908
+ )
909
+ .option(
910
+ "--force",
911
+ "Retry an ambiguous or already-delivered notification (may duplicate)",
912
+ )
913
+ .option("--json", "Output as JSON")
914
+ .action(async (id, options) => {
915
+ try {
916
+ const result = await retryBackgroundNeedsInputNotification(id, options);
917
+ if (options.json) console.log(JSON.stringify(result, null, 2));
918
+ else {
919
+ logger.log(
920
+ chalk.green(
921
+ `Needs-input notification ${result.notification?.status || "settled"} for ${id}`,
922
+ ),
923
+ );
924
+ }
925
+ } catch (error) {
926
+ logger.error(chalk.red(error.message));
927
+ process.exitCode = 1;
928
+ }
929
+ });
829
930
 
830
931
  daemon
831
932
  .command("status")
@@ -475,7 +475,7 @@ export function registerCoworkCommand(program) {
475
475
  for (const s of schedules) {
476
476
  const flag = s.enabled ? chalk.green("✓") : chalk.gray("✗");
477
477
  logger.log(
478
- ` ${flag} ${chalk.cyan(s.id)} ${chalk.yellow(s.cron)} [${s.templateId || "free"}]`,
478
+ ` ${flag} ${chalk.cyan(s.id)} ${chalk.yellow(s.cron)}${s.timeZone ? ` [${s.timeZone}]` : ""} [${s.templateId || "free"}]`,
479
479
  );
480
480
  logger.log(chalk.gray(` ${s.userMessage.slice(0, 80)}`));
481
481
  if (s.lastRunAt) {
@@ -495,6 +495,7 @@ export function registerCoworkCommand(program) {
495
495
  "5-field cron expression (e.g. '0 9 * * 1-5')",
496
496
  )
497
497
  .requiredOption("--message <text>", "Task prompt / user message")
498
+ .option("--timezone <iana>", "IANA time zone (for example Asia/Shanghai)")
498
499
  .option(
499
500
  "--template <id>",
500
501
  "Template id (e.g. doc-convert); omit for free mode",
@@ -505,6 +506,7 @@ export function registerCoworkCommand(program) {
505
506
  try {
506
507
  const entry = addSchedule(process.cwd(), {
507
508
  cron: options.cron,
509
+ timeZone: options.timezone || null,
508
510
  templateId: options.template || null,
509
511
  userMessage: options.message,
510
512
  files: options.files
@@ -516,6 +518,9 @@ export function registerCoworkCommand(program) {
516
518
  });
517
519
  logger.log(chalk.green(`✓ Added schedule ${entry.id}`));
518
520
  logger.log(chalk.gray(` cron: ${entry.cron}`));
521
+ if (entry.timeZone) {
522
+ logger.log(chalk.gray(` timezone: ${entry.timeZone}`));
523
+ }
519
524
  logger.log(chalk.gray(` template: ${entry.templateId || "free"}`));
520
525
  } catch (err) {
521
526
  logger.error(err.message);
@@ -565,19 +570,30 @@ export function registerCoworkCommand(program) {
565
570
  cron
566
571
  .command("run")
567
572
  .description("Start the cron scheduler in the foreground (Ctrl-C to stop)")
568
- .option("--interval <ms>", "Tick interval in ms (default 60000)", "60000")
573
+ .option(
574
+ "--interval <ms>",
575
+ "Tick interval in ms (auto: 1000 for six-field cron, otherwise 60000)",
576
+ )
569
577
  .action(async (options) => {
570
- const [{ CoworkCronScheduler, _deps: cronDeps }, { runCoworkTask }] =
571
- await Promise.all([
572
- import("../lib/cowork-cron.js"),
573
- import("../lib/cowork-task-runner.js"),
574
- ]);
575
- // Inject the runner so the scheduler doesn't require a circular import
576
- cronDeps.runTask = runCoworkTask;
577
-
578
- const scheduler = new CoworkCronScheduler({
578
+ const [
579
+ { CoworkCronKernelScheduler },
580
+ { openSchedulerStore },
581
+ { runCoworkTask },
582
+ ] = await Promise.all([
583
+ import("../lib/scheduler-kernel/cowork-cron-adapter.js"),
584
+ import("../lib/scheduler-kernel/store.js"),
585
+ import("../lib/cowork-task-runner.js"),
586
+ ]);
587
+ const schedulerStore = openSchedulerStore();
588
+ const intervalMs =
589
+ options.interval === undefined
590
+ ? undefined
591
+ : parseInt(options.interval, 10) || 60_000;
592
+ const scheduler = new CoworkCronKernelScheduler({
579
593
  cwd: process.cwd(),
580
- intervalMs: parseInt(options.interval, 10) || 60_000,
594
+ schedulerStore,
595
+ runTask: runCoworkTask,
596
+ ...(intervalMs === undefined ? {} : { intervalMs }),
581
597
  onEvent: (e) => {
582
598
  const ts = new Date().toISOString();
583
599
  logger.log(chalk.gray(`[${ts}] ${JSON.stringify(e)}`));
@@ -587,8 +603,9 @@ export function registerCoworkCommand(program) {
587
603
  logger.log(
588
604
  chalk.green("Cowork cron scheduler running. Press Ctrl-C to stop."),
589
605
  );
590
- process.on("SIGINT", () => {
606
+ process.once("SIGINT", () => {
591
607
  scheduler.stop();
608
+ schedulerStore.close();
592
609
  process.exit(0);
593
610
  });
594
611
  });
@@ -2,8 +2,9 @@
2
2
  * cc loop — repeatedly run a command or agent prompt on a fixed interval
3
3
  * (Claude-Code `/loop` parity, MVP). Lightweight by design: unlike `cc ccron`
4
4
  * (in-memory profile governance, runs nothing) or `cc automation` (DB-backed
5
- * flow/trigger engine), this just re-runs ONE thing on a timer until a stop
6
- * condition fires or you Ctrl-C.
5
+ * flow/trigger engine), this re-runs ONE thing until a stop condition fires or
6
+ * you Ctrl-C. Each iteration is claimed and settled by the durable scheduler
7
+ * kernel; the foreground command remains the lifecycle owner between rounds.
7
8
  *
8
9
  * cc loop "check if CI passed, summarize failures" # wraps `cc agent -p`
9
10
  * cc loop --every 30s -- npm test # external command
@@ -20,9 +21,11 @@
20
21
  * - with `--` → the operands after it are an EXTERNAL command (shell-resolved)
21
22
  *
22
23
  * The loop driver lives in src/lib/loop.js (pure, clock-injected). This layer
23
- * only builds the concrete iteration (spawn + tee output) and wires SIGINT.
24
+ * builds the concrete iteration (spawn + tee output), binds scheduler identity,
25
+ * persists resumable iteration plans, and wires SIGINT.
24
26
  */
25
27
 
28
+ import { randomUUID } from "node:crypto";
26
29
  import { fileURLToPath } from "node:url";
27
30
  import chalk from "chalk";
28
31
  import { logger } from "../lib/logger.js";
@@ -34,16 +37,22 @@ import {
34
37
  makeSleep,
35
38
  parseLoopDirectives,
36
39
  summarizeLoopEvents,
40
+ findLoopIterationResult,
37
41
  } from "../lib/loop.js";
38
42
  import {
39
43
  startSession,
40
44
  appendEvent,
45
+ appendEventIfHead,
41
46
  readEvents,
42
47
  sessionExists,
43
48
  } from "../harness/jsonl-session-store.js";
49
+ import { openSchedulerStore } from "../lib/scheduler-kernel/store.js";
50
+ import { LoopSchedulerBridge } from "../lib/scheduler-kernel/loop-adapter.js";
44
51
 
45
52
  export const _deps = {
46
53
  spawn: executionBroker.spawn.bind(executionBroker),
54
+ openSchedulerStore,
55
+ createLoopSchedulerBridge: (options) => new LoopSchedulerBridge(options),
47
56
  };
48
57
 
49
58
  /**
@@ -68,10 +77,11 @@ const BIN_PATH = fileURLToPath(
68
77
  * the user sees live output) while capturing it, so `--until <regex>` can match
69
78
  * against what was printed. Resolves with { exitCode, output }.
70
79
  */
71
- function spawnIteration(cmd, args, { shell, onChild, capture }) {
80
+ function spawnIteration(cmd, args, { shell, cwd, onChild, capture, signal }) {
72
81
  return new Promise((resolve) => {
73
82
  const child = _deps.spawn(cmd, args, {
74
83
  shell,
84
+ cwd,
75
85
  stdio: capture ? ["inherit", "pipe", "pipe"] : "inherit",
76
86
  env: process.env,
77
87
  origin: "loop:iteration",
@@ -80,6 +90,17 @@ function spawnIteration(cmd, args, { shell, onChild, capture }) {
80
90
  });
81
91
  if (onChild) onChild(child);
82
92
 
93
+ const onAbort = () => {
94
+ if (child.exitCode == null) {
95
+ try {
96
+ child.kill("SIGINT");
97
+ } catch {
98
+ // The child may have exited between the state check and kill.
99
+ }
100
+ }
101
+ };
102
+ signal?.addEventListener("abort", onAbort, { once: true });
103
+
83
104
  const outChunks = [];
84
105
  if (capture) {
85
106
  child.stdout?.on("data", (d) => {
@@ -93,16 +114,22 @@ function spawnIteration(cmd, args, { shell, onChild, capture }) {
93
114
  }
94
115
 
95
116
  // `close` (not `exit`) so piped stdio is fully drained before we resolve.
96
- child.on("close", (code, signal) => {
117
+ child.on("close", (code, childSignal) => {
118
+ signal?.removeEventListener?.("abort", onAbort);
97
119
  // Decode the captured bytes once, at the end. Accumulating with
98
120
  // `output += chunk` would decode each stdout chunk independently, so a
99
121
  // multi-byte UTF-8 character split across a chunk boundary corrupts the
100
122
  // captured string that `--until <regex>` / `--dynamic` directive parsing
101
123
  // reads. (The raw passthrough writes above are unaffected.)
102
124
  const output = Buffer.concat(outChunks).toString("utf-8");
103
- resolve({ exitCode: code == null ? null : code, output, signal });
125
+ resolve({
126
+ exitCode: code == null ? null : code,
127
+ output,
128
+ signal: childSignal,
129
+ });
104
130
  });
105
131
  child.on("error", (err) => {
132
+ signal?.removeEventListener?.("abort", onAbort);
106
133
  resolve({
107
134
  exitCode: 127,
108
135
  output: String(err.message || err),
@@ -112,6 +139,73 @@ function spawnIteration(cmd, args, { shell, onChild, capture }) {
112
139
  });
113
140
  }
114
141
 
142
+ function recoveredIterationResult(data) {
143
+ return {
144
+ exitCode: data?.exitCode ?? null,
145
+ signal: data?.signal ?? null,
146
+ durationMs: data?.durationMs ?? null,
147
+ done: data?.done === true,
148
+ nextDelayMs: data?.nextDelayMs ?? null,
149
+ matchedUntil: data?.matchedUntil === true,
150
+ outputBytes: data?.outputBytes ?? 0,
151
+ outputDigest: data?.outputDigest ?? null,
152
+ schedulerOccurrenceId: data?.schedulerOccurrenceId ?? null,
153
+ recovered: true,
154
+ output: "",
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Publish one stable iteration schedule under the canonical session writer
160
+ * lock. A concurrent resumer either observes the same schedule or the already
161
+ * completed terminal record; it never invents a second scheduledFor identity.
162
+ */
163
+ function claimPersistedIteration(sessionId, iteration, now = Date.now()) {
164
+ for (let attempt = 0; attempt < 32; attempt += 1) {
165
+ const events = readEvents(sessionId);
166
+ const completed = findLoopIterationResult(events, iteration);
167
+ if (completed) return { completed, scheduledFor: null };
168
+ const summary = summarizeLoopEvents(events);
169
+ if (summary.pendingIteration?.n === iteration) {
170
+ return {
171
+ completed: null,
172
+ scheduledFor: summary.pendingIteration.scheduledFor,
173
+ };
174
+ }
175
+ if (summary.pendingIteration) {
176
+ const error = new Error(
177
+ `loop session has unresolved iteration ${summary.pendingIteration.n}`,
178
+ );
179
+ error.code = "LOOP_PENDING_ITERATION_CONFLICT";
180
+ throw error;
181
+ }
182
+ const expectedHeadHash = events[events.length - 1]?.hash || null;
183
+ try {
184
+ appendEventIfHead(
185
+ sessionId,
186
+ "loop_iteration_scheduled",
187
+ { n: iteration, scheduledFor: now },
188
+ expectedHeadHash,
189
+ );
190
+ return { completed: null, scheduledFor: now };
191
+ } catch (error) {
192
+ if (error?.code === "SESSION_REVISION_STALE") continue;
193
+ // An append classified as outcome-unknown may still have committed.
194
+ const after = readEvents(sessionId);
195
+ const recovered = findLoopIterationResult(after, iteration);
196
+ if (recovered) return { completed: recovered, scheduledFor: null };
197
+ const pending = summarizeLoopEvents(after).pendingIteration;
198
+ if (pending?.n === iteration) {
199
+ return { completed: null, scheduledFor: pending.scheduledFor };
200
+ }
201
+ throw error;
202
+ }
203
+ }
204
+ const error = new Error("loop iteration schedule changed too many times");
205
+ error.code = "LOOP_ITERATION_SCHEDULE_CONTENTION";
206
+ throw error;
207
+ }
208
+
115
209
  /**
116
210
  * Build the concrete child invocation from the resolved operands + mode.
117
211
  * Shared by fresh runs and `--resume` (which reconstructs it from saved config).
@@ -283,6 +377,7 @@ export function registerLoopCommand(program) {
283
377
  savedConfig && !fromCli("untilExitZero")
284
378
  ? Boolean(savedConfig.untilExitZero)
285
379
  : Boolean(options.untilExitZero);
380
+ const executionCwd = savedConfig?.cwd || process.cwd();
286
381
 
287
382
  // --- build the child invocation (shared with resume) ---
288
383
  const { cmd, args, shell, label } = buildInvocation({
@@ -308,6 +403,7 @@ export function registerLoopCommand(program) {
308
403
  maxIterations: maxIterations ?? null,
309
404
  untilExitZero,
310
405
  until: untilRaw || null,
406
+ cwd: executionCwd,
311
407
  });
312
408
  }
313
409
 
@@ -346,7 +442,54 @@ export function registerLoopCommand(program) {
346
442
 
347
443
  const startedAt = Date.now();
348
444
  let summary;
445
+ let schedulerStore;
349
446
  try {
447
+ const executionId = sessionId || `loop-${randomUUID()}`;
448
+ schedulerStore = _deps.openSchedulerStore();
449
+ const loopScheduler = _deps.createLoopSchedulerBridge({
450
+ schedulerStore,
451
+ definition: {
452
+ executionId,
453
+ cwd: executionCwd,
454
+ execMode,
455
+ operands,
456
+ dynamic,
457
+ },
458
+ runIteration: async (n, { signal } = {}) => {
459
+ logger.log(chalk.gray(`\n▸ iteration ${n} — ${label}`));
460
+ const t0 = Date.now();
461
+ const res = await spawnIteration(cmd, args, {
462
+ shell,
463
+ cwd: executionCwd,
464
+ capture,
465
+ signal,
466
+ onChild: (c) => {
467
+ activeChild = c;
468
+ },
469
+ });
470
+ res.durationMs = Date.now() - t0;
471
+ res.matchedUntil = Boolean(
472
+ untilRegex && untilRegex.test(res.output || ""),
473
+ );
474
+ if (dynamic) {
475
+ const directive = parseLoopDirectives(res.output);
476
+ res.done = directive.done;
477
+ if (directive.nextDelayMs != null) {
478
+ res.nextDelayMs = directive.nextDelayMs;
479
+ }
480
+ if (directive.done) {
481
+ logger.log(chalk.gray(" directive: stop"));
482
+ } else if (directive.nextDelayMs != null) {
483
+ logger.log(
484
+ chalk.gray(
485
+ ` directive: next in ${formatDuration(directive.nextDelayMs)}`,
486
+ ),
487
+ );
488
+ }
489
+ }
490
+ return res;
491
+ },
492
+ });
350
493
  summary = await runLoop({
351
494
  intervalMs,
352
495
  maxIterations,
@@ -370,46 +513,34 @@ export function registerLoopCommand(program) {
370
513
  durationMs: res.durationMs ?? null,
371
514
  done: Boolean(res.done),
372
515
  nextDelayMs: res.nextDelayMs ?? null,
516
+ matchedUntil: Boolean(res.matchedUntil),
517
+ signal: res.signal ?? null,
518
+ outputBytes: res.outputBytes ?? 0,
519
+ outputDigest: res.outputDigest ?? null,
520
+ schedulerOccurrenceId: res.schedulerOccurrenceId ?? null,
521
+ recovered: Boolean(res.recovered),
373
522
  });
374
523
  }
375
524
  },
376
525
  runIteration: async (n) => {
377
- logger.log(chalk.gray(`\n▸ iteration ${n} — ${label}`));
378
- const t0 = Date.now();
379
- const res = await spawnIteration(cmd, args, {
380
- shell,
381
- capture,
382
- onChild: (c) => {
383
- activeChild = c;
384
- },
385
- });
386
- res.durationMs = Date.now() - t0;
387
- // --dynamic: read the iteration's [[loop:next]] / [[loop:stop]]
388
- // directive and surface it to runLoop as done / nextDelayMs.
389
- // Use the RESOLVED `dynamic` (not raw options.dynamic): on
390
- // `cc loop --resume`, dynamic is rehydrated from saved config while
391
- // options.dynamic is false — gating on the raw flag would skip
392
- // directive parsing and the model's [[loop:stop]] would be ignored,
393
- // running the loop forever.
394
- if (dynamic) {
395
- const d = parseLoopDirectives(res.output);
396
- res.done = d.done;
397
- if (d.nextDelayMs != null) res.nextDelayMs = d.nextDelayMs;
398
- if (d.done) {
399
- logger.log(chalk.gray(` ↺ directive: stop`));
400
- } else if (d.nextDelayMs != null) {
401
- logger.log(
402
- chalk.gray(
403
- ` ↺ directive: next in ${formatDuration(d.nextDelayMs)}`,
404
- ),
405
- );
526
+ let scheduledFor = Date.now();
527
+ if (persist) {
528
+ const claim = claimPersistedIteration(
529
+ sessionId,
530
+ n,
531
+ scheduledFor,
532
+ );
533
+ if (claim.completed) {
534
+ return recoveredIterationResult(claim.completed);
406
535
  }
536
+ scheduledFor = claim.scheduledFor;
407
537
  }
408
- return res;
538
+ return loopScheduler.runIteration(n, { scheduledFor });
409
539
  },
410
540
  });
411
541
  } finally {
412
542
  process.removeListener("SIGINT", onSigint);
543
+ schedulerStore?.close();
413
544
  }
414
545
 
415
546
  const elapsed = formatDuration(Date.now() - startedAt);
@@ -18,6 +18,11 @@ import { fileURLToPath } from "node:url";
18
18
  import chalk from "chalk";
19
19
  import { logger } from "../lib/logger.js";
20
20
  import { executionBroker } from "../lib/process-execution-broker/index.js";
21
+ import { openSchedulerStore } from "../lib/scheduler-kernel/store.js";
22
+ import {
23
+ RoutineSchedulerBridge,
24
+ routineBridgeRunId,
25
+ } from "../lib/scheduler-kernel/routine-adapter.js";
21
26
 
22
27
  export const _deps = {
23
28
  spawn: (...args) => executionBroker.spawn(...args),
@@ -107,6 +112,7 @@ export function defaultFetchEvents(repo) {
107
112
  (Array.isArray(rows) ? rows : []).map((r) => ({
108
113
  id: r.id,
109
114
  type: r.type,
115
+ created_at: r.created_at,
110
116
  })),
111
117
  );
112
118
  } catch {
@@ -281,14 +287,24 @@ export function registerRoutineCommand(program) {
281
287
  .description("Fire a routine NOW (the webhook/API entry point)")
282
288
  .option("--json", "Output as JSON")
283
289
  .action(async (id, options) => {
290
+ let schedulerStore;
284
291
  try {
285
292
  const store = await loadStore();
286
293
  const r = store.get(id);
287
294
  if (!r) throw new Error(`routine not found: ${id}`);
288
- const { fireRoutine } = await import("../lib/routine-store.js");
289
- const runId = await fireRoutine(store, r, defaultRunAgent, {
290
- trigger: "manual",
295
+ schedulerStore = openSchedulerStore();
296
+ const bridge = new RoutineSchedulerBridge({
297
+ routineStore: store,
298
+ schedulerStore,
299
+ runAgent: defaultRunAgent,
291
300
  });
301
+ const fired = await bridge.trigger(r);
302
+ const runId = routineBridgeRunId(fired);
303
+ if (!runId) {
304
+ throw new Error(
305
+ `routine occurrence did not execute: ${fired.result?.status || "unknown"}`,
306
+ );
307
+ }
292
308
  const run = store.listRuns({ routineId: r.id, limit: 1 })[0];
293
309
  if (options.json) return console.log(JSON.stringify(run, null, 2));
294
310
  logger.success(`Fired ${r.id} → ${runId} (${run?.status})`);
@@ -296,6 +312,8 @@ export function registerRoutineCommand(program) {
296
312
  } catch (err) {
297
313
  logger.error(err.message);
298
314
  process.exitCode = 1;
315
+ } finally {
316
+ schedulerStore?.close();
299
317
  }
300
318
  });
301
319
 
@@ -304,25 +322,42 @@ export function registerRoutineCommand(program) {
304
322
  .description("Driver: fire due cron/once routines and poll github triggers")
305
323
  .option("--json", "Output as JSON")
306
324
  .action(async (options) => {
325
+ let schedulerStore;
307
326
  try {
308
327
  const store = await loadStore();
309
- const { fireRoutine, pollGithubRoutine } =
310
- await import("../lib/routine-store.js");
328
+ schedulerStore = openSchedulerStore();
329
+ const bridge = new RoutineSchedulerBridge({
330
+ routineStore: store,
331
+ schedulerStore,
332
+ runAgent: defaultRunAgent,
333
+ });
311
334
  const fired = [];
312
- for (const r of store.due()) {
335
+ for (const scheduled of await bridge.runDue()) {
336
+ const runId = routineBridgeRunId(scheduled);
337
+ if (!runId) {
338
+ if (scheduled.result?.status === "busy") continue;
339
+ throw new Error(
340
+ `routine occurrence did not execute: ${scheduled.routine} (${scheduled.result?.status || "unknown"})`,
341
+ );
342
+ }
313
343
  fired.push({
314
- routine: r.id,
315
- runId: await fireRoutine(store, r, defaultRunAgent, {
316
- trigger: r.trigger.kind,
317
- }),
344
+ routine: scheduled.routine,
345
+ runId,
318
346
  });
319
347
  }
320
348
  for (const r of store.githubRoutines()) {
321
- const runs = await pollGithubRoutine(store, r, {
349
+ const github = await bridge.pollGithub(r, {
322
350
  fetchEvents: defaultFetchEvents,
323
- runAgent: defaultRunAgent,
324
351
  });
325
- for (const runId of runs) fired.push({ routine: r.id, runId });
352
+ if (!github) continue;
353
+ const runId = routineBridgeRunId(github);
354
+ if (!runId) {
355
+ if (github.result?.status === "busy") continue;
356
+ throw new Error(
357
+ `routine GitHub occurrence did not execute: ${github.routine} (${github.result?.status || "unknown"})`,
358
+ );
359
+ }
360
+ fired.push({ routine: github.routine, runId });
326
361
  }
327
362
  if (options.json)
328
363
  return console.log(JSON.stringify({ fired }, null, 2));
@@ -334,6 +369,8 @@ export function registerRoutineCommand(program) {
334
369
  } catch (err) {
335
370
  logger.error(err.message);
336
371
  process.exitCode = 1;
372
+ } finally {
373
+ schedulerStore?.close();
337
374
  }
338
375
  });
339
376