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
@@ -70,7 +70,11 @@ export function _computeNextTriggers(schedules, from, limit = 5) {
70
70
  const matchers = [];
71
71
  for (const s of enabled) {
72
72
  try {
73
- matchers.push({ id: s.id, cron: s.cron, match: parseCron(s.cron) });
73
+ matchers.push({
74
+ id: s.id,
75
+ cron: s.cron,
76
+ match: parseCron(s.cron, { timeZone: s.timeZone }),
77
+ });
74
78
  } catch (_e) {
75
79
  // skip invalid cron
76
80
  }
package/src/lib/loop.js CHANGED
@@ -86,17 +86,49 @@ export function summarizeLoopEvents(events) {
86
86
  let config = null;
87
87
  let completedIterations = 0;
88
88
  let lastExitCode = null;
89
+ const scheduled = new Map();
90
+ const completed = new Set();
89
91
  for (const e of events || []) {
90
92
  if (e?.type === "loop_config") {
91
93
  config = e.data || null;
94
+ } else if (
95
+ e?.type === "loop_iteration_scheduled" &&
96
+ Number.isSafeInteger(e.data?.n) &&
97
+ e.data.n > 0 &&
98
+ Number.isSafeInteger(e.data?.scheduledFor) &&
99
+ e.data.scheduledFor >= 0
100
+ ) {
101
+ scheduled.set(e.data.n, {
102
+ n: e.data.n,
103
+ scheduledFor: e.data.scheduledFor,
104
+ });
92
105
  } else if (e?.type === "loop_iteration") {
93
106
  completedIterations += 1;
107
+ if (Number.isSafeInteger(e.data?.n) && e.data.n > 0) {
108
+ completed.add(e.data.n);
109
+ }
94
110
  if (e.data && typeof e.data.exitCode !== "undefined") {
95
111
  lastExitCode = e.data.exitCode;
96
112
  }
97
113
  }
98
114
  }
99
- return { config, completedIterations, lastExitCode };
115
+ const pendingIteration =
116
+ [...scheduled.values()]
117
+ .filter((entry) => !completed.has(entry.n))
118
+ .sort((a, b) => a.n - b.n)[0] || null;
119
+ return { config, completedIterations, lastExitCode, pendingIteration };
120
+ }
121
+
122
+ /** Return durable terminal data for one loop iteration, if already recorded. */
123
+ export function findLoopIterationResult(events, iteration) {
124
+ if (!Number.isSafeInteger(iteration) || iteration < 1) return null;
125
+ for (let index = (events || []).length - 1; index >= 0; index -= 1) {
126
+ const event = events[index];
127
+ if (event?.type === "loop_iteration" && event.data?.n === iteration) {
128
+ return event.data || null;
129
+ }
130
+ }
131
+ return null;
100
132
  }
101
133
 
102
134
  /** Default abortable sleep — resolves early if the signal aborts. */
@@ -187,7 +219,10 @@ export async function runLoop({
187
219
  if (untilExitZero && res.exitCode === 0) {
188
220
  return { iterations: i, stoppedBy: "exit-zero", results };
189
221
  }
190
- if (untilRegex && untilRegex.test(res.output || "")) {
222
+ if (
223
+ untilRegex &&
224
+ (res.matchedUntil === true || untilRegex.test(res.output || ""))
225
+ ) {
191
226
  return { iterations: i, stoppedBy: "match", results };
192
227
  }
193
228
  if (maxIterations && i >= maxIterations) {
@@ -23,10 +23,10 @@ export function microCompact(messages, opts = {}) {
23
23
  return { messages, stats: { trimmed: 0, saved: 0, kept: 0 } };
24
24
  }
25
25
  const keepRecent = Number.isFinite(opts.keepRecent)
26
- ? opts.keepRecent
26
+ ? Math.max(0, Math.trunc(opts.keepRecent))
27
27
  : DEFAULT_KEEP_RECENT;
28
28
  const maxChars = Number.isFinite(opts.maxToolChars)
29
- ? opts.maxToolChars
29
+ ? Math.max(0, Math.trunc(opts.maxToolChars))
30
30
  : DEFAULT_MAX_TOOL_CHARS;
31
31
  // messages at index >= cutoff are "recent" and never touched.
32
32
  const cutoff = Math.max(0, messages.length - keepRecent);
@@ -38,12 +38,18 @@ export function microCompact(messages, opts = {}) {
38
38
  if (!m || m.role !== "tool") return m; // only old tool results
39
39
  const content = typeof m.content === "string" ? m.content : "";
40
40
  if (content.length <= maxChars) return m; // small enough already
41
+ const fullMarker = `\n… [tool result trimmed — ${content.length} chars]`;
42
+ const marker =
43
+ fullMarker.length <= maxChars
44
+ ? fullMarker
45
+ : "…[trimmed]".slice(0, maxChars);
46
+ const head = content.slice(0, Math.max(0, maxChars - marker.length));
47
+ const compactedContent = `${head}${marker}`;
41
48
  trimmed += 1;
42
- saved += content.length - maxChars;
43
- const head = content.slice(0, maxChars);
49
+ saved += content.length - compactedContent.length;
44
50
  return {
45
51
  ...m,
46
- content: `${head}\n… [tool result trimmed — ${content.length} chars; older context dropped to save space]`,
52
+ content: compactedContent,
47
53
  _microCompacted: true,
48
54
  };
49
55
  });
@@ -91,6 +91,8 @@ export const PERMISSION_SCOPES = [
91
91
  "audit:write",
92
92
  "config:read",
93
93
  "config:write",
94
+ "automation:execute",
95
+ "automation:connector:*",
94
96
  "system:admin",
95
97
  ];
96
98
 
@@ -296,7 +298,7 @@ function _grantActive(expiresAt, nowMs) {
296
298
  /**
297
299
  * Get all roles and direct permissions for a user.
298
300
  */
299
- export function getUserPermissions(db, userDid) {
301
+ export function getUserPermissions(db, userDid, { nowMs = Date.now() } = {}) {
300
302
  ensurePermissionTables(db);
301
303
 
302
304
  // Get active role grants
@@ -305,7 +307,6 @@ export function getUserPermissions(db, userDid) {
305
307
  .all(userDid);
306
308
 
307
309
  // Filter out expired grants
308
- const nowMs = Date.now();
309
310
  const grants = allGrants.filter((g) => _grantActive(g.expires_at, nowMs));
310
311
 
311
312
  // Get direct permissions (not expired)
@@ -347,8 +348,8 @@ export function getUserPermissions(db, userDid) {
347
348
  /**
348
349
  * Check if a user has a specific permission.
349
350
  */
350
- export function checkPermission(db, userDid, permission) {
351
- const userPerms = getUserPermissions(db, userDid);
351
+ export function checkPermission(db, userDid, permission, options) {
352
+ const userPerms = getUserPermissions(db, userDid, options);
352
353
 
353
354
  // Admin wildcard
354
355
  if (userPerms.isAdmin) return true;
@@ -356,9 +357,15 @@ export function checkPermission(db, userDid, permission) {
356
357
  // Exact match
357
358
  if (userPerms.effectivePermissions.includes(permission)) return true;
358
359
 
359
- // Wildcard match (e.g., "note:*" matches "note:read")
360
- const [scope] = permission.split(":");
361
- if (userPerms.effectivePermissions.includes(`${scope}:*`)) return true;
360
+ // Hierarchical wildcard match. In addition to `note:*`, this allows an
361
+ // explicitly narrow grant such as `automation:connector:*` to authorize
362
+ // `automation:connector:slack` without granting unrelated automation
363
+ // capabilities.
364
+ const segments = permission.split(":");
365
+ for (let length = segments.length - 1; length >= 1; length -= 1) {
366
+ const wildcard = `${segments.slice(0, length).join(":")}:*`;
367
+ if (userPerms.effectivePermissions.includes(wildcard)) return true;
368
+ }
362
369
 
363
370
  return false;
364
371
  }
@@ -66,7 +66,8 @@ const SUPPORTED_SANDBOX_PROFILES = new Set([
66
66
  // evidence, not authority to invoke a privileged post-spawn closure.
67
67
  const admittedWindowsMcpCodeSnapshotPlans = new WeakSet();
68
68
 
69
- // 延迟导入避免循环依赖
69
+ // Keep the broker on the shared CommonJS trace singleton so CommonJS and ESM
70
+ // consumers observe the same AsyncLocalStorage without loader warnings.
70
71
  let _traceCtx = null;
71
72
  const _ipcBus = null;
72
73
 
@@ -3354,7 +3355,12 @@ class ProcessExecutionBroker extends EventEmitter {
3354
3355
  delete spawnOpts.mcpStdioExecutableIdentityDigest;
3355
3356
  if (traceCtx) {
3356
3357
  spawnOpts.env = { ...(spawnOpts.env || process.env) };
3357
- spawnOpts.env.TRACEPARENT = traceCtx.traceparent;
3358
+ Object.assign(
3359
+ spawnOpts.env,
3360
+ getTraceCtx()?.traceContext?.getPropagationEnv?.() || {
3361
+ TRACEPARENT: traceCtx.traceparent,
3362
+ },
3363
+ );
3358
3364
  }
3359
3365
 
3360
3366
  // P0-1: Credential filtering (default-on) — strip secrets from env/args
@@ -3658,7 +3664,12 @@ class ProcessExecutionBroker extends EventEmitter {
3658
3664
  this._stripAuditControlOptions(spawnOpts);
3659
3665
  if (traceCtx) {
3660
3666
  spawnOpts.env = { ...(spawnOpts.env || process.env) };
3661
- spawnOpts.env.TRACEPARENT = traceCtx.traceparent;
3667
+ Object.assign(
3668
+ spawnOpts.env,
3669
+ getTraceCtx()?.traceContext?.getPropagationEnv?.() || {
3670
+ TRACEPARENT: traceCtx.traceparent,
3671
+ },
3672
+ );
3662
3673
  }
3663
3674
 
3664
3675
  // P0-1: Credential filtering agent — strip secrets from env/args before spawn
@@ -50,6 +50,7 @@ const VSCODE_COMMANDS = [
50
50
  "chainlesschain.session.prStatus",
51
51
  "chainlesschain.background.agents",
52
52
  "chainlesschain.sessions.workbench",
53
+ "chainlesschain.automation.center",
53
54
  "chainlesschain.remote.control",
54
55
  "chainlesschain.usage.show",
55
56
  "chainlesschain.plugins.manage",
@@ -101,6 +102,7 @@ const JETBRAINS_ACTIONS = [
101
102
  "chainlesschain.bg.Agents",
102
103
  "chainlesschain.remote.Control",
103
104
  "chainlesschain.sessions.Workbench",
105
+ "chainlesschain.automation.Center",
104
106
  "chainlesschain.usage.Show",
105
107
  "chainlesschain.artifacts.Browse",
106
108
  "chainlesschain.policy.Viewer",
@@ -27,6 +27,11 @@ import path from "node:path";
27
27
  import os from "node:os";
28
28
  import { randomBytes } from "node:crypto";
29
29
  import { parseCron, nextCronTime } from "./agent-schedule-store.js";
30
+ import {
31
+ readSecurityStore,
32
+ writeSecurityStore,
33
+ } from "./durable-security-store.js";
34
+ import { withFileLock } from "./with-file-lock.js";
30
35
 
31
36
  export const ROUTINE_TRIGGER_KINDS = Object.freeze([
32
37
  "cron",
@@ -45,11 +50,75 @@ function shortId() {
45
50
 
46
51
  function parseWhen(value) {
47
52
  if (value == null) return NaN;
53
+ if (typeof value === "string" && value.trim() === "") return NaN;
54
+ if (typeof value !== "string" && typeof value !== "number") return NaN;
48
55
  const n = Number(value);
49
56
  if (Number.isFinite(n)) return n;
50
57
  return Date.parse(String(value));
51
58
  }
52
59
 
60
+ function boundedDefinitionText(value, field, maximum) {
61
+ const text = String(value || "").trim();
62
+ if (!text) throw new Error(`routine needs a ${field}`);
63
+ if (Buffer.byteLength(text, "utf8") > maximum) {
64
+ throw new Error(`routine ${field} exceeds ${maximum} UTF-8 bytes`);
65
+ }
66
+ return text;
67
+ }
68
+
69
+ export function normalizeRoutineDefinition(def = {}) {
70
+ const name = boundedDefinitionText(def.name, "name", 512);
71
+ const prompt = boundedDefinitionText(def.prompt, "prompt", 64 * 1024);
72
+ const raw = def.trigger;
73
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
74
+ throw new Error("routine trigger is required");
75
+ }
76
+ if (!ROUTINE_TRIGGER_KINDS.includes(raw.kind)) {
77
+ throw new Error(
78
+ `routine trigger kind must be one of: ${ROUTINE_TRIGGER_KINDS.join(", ")}`,
79
+ );
80
+ }
81
+ let trigger;
82
+ if (raw.kind === "cron") {
83
+ const cron = String(raw.cron || "").trim();
84
+ if (!parseCron(cron)) throw new Error(`invalid cron expression: ${cron}`);
85
+ trigger = { kind: "cron", cron };
86
+ } else if (raw.kind === "once") {
87
+ const at = parseWhen(raw.at);
88
+ if (!Number.isFinite(at) || !Number.isSafeInteger(at) || at < 0) {
89
+ throw new Error(`invalid --at time: ${raw.at}`);
90
+ }
91
+ trigger = { kind: "once", at };
92
+ } else if (raw.kind === "github") {
93
+ const repo = String(raw.repo || "").trim();
94
+ const parts = repo.split("/");
95
+ if (
96
+ !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repo) ||
97
+ parts.some((part) => part === "." || part === "..")
98
+ ) {
99
+ throw new Error("github trigger needs --repo <owner/name>");
100
+ }
101
+ const events = Array.isArray(raw.events)
102
+ ? [...new Set(raw.events.map((value) => String(value || "").trim()))]
103
+ .filter(Boolean)
104
+ .sort()
105
+ : [];
106
+ if (
107
+ events.length > 64 ||
108
+ events.some(
109
+ (event) =>
110
+ event.length > 128 || !/^[A-Za-z][A-Za-z0-9_.-]*$/u.test(event),
111
+ )
112
+ ) {
113
+ throw new Error("github trigger events are invalid");
114
+ }
115
+ trigger = { kind: "github", repo, events };
116
+ } else {
117
+ trigger = { kind: "webhook" };
118
+ }
119
+ return { name, prompt, trigger };
120
+ }
121
+
53
122
  export class RoutineStore {
54
123
  constructor({ dir = null, now = () => Date.now() } = {}) {
55
124
  this.dir = dir || routinesDir();
@@ -70,68 +139,58 @@ export class RoutineStore {
70
139
  }
71
140
 
72
141
  _readRoutines() {
73
- try {
74
- const parsed = JSON.parse(fs.readFileSync(this._routinesFile(), "utf-8"));
75
- return parsed && typeof parsed === "object" ? parsed : {};
76
- } catch {
77
- return {};
78
- }
142
+ return readSecurityStore(this._routinesFile(), "routine definitions");
79
143
  }
80
144
  _writeRoutines(map) {
145
+ writeSecurityStore(this._routinesFile(), "routine definitions", map);
146
+ }
147
+
148
+ _withDefinitionLock(callback) {
81
149
  this._ensureDir();
82
- fs.writeFileSync(
150
+ return withFileLock(
83
151
  this._routinesFile(),
84
- JSON.stringify(map, null, 2),
85
- "utf-8",
152
+ () => callback(this._readRoutines()),
153
+ { failIfUnavailable: true },
86
154
  );
87
155
  }
88
156
 
89
- /**
90
- * @param {object} def { name, prompt, trigger: {kind, cron?, at?, repo?, events?} }
91
- */
92
- create(def = {}) {
93
- const name = String(def.name || "").trim();
94
- const prompt = String(def.prompt || "").trim();
95
- const trigger = def.trigger || {};
96
- if (!name) throw new Error("routine needs a name");
97
- if (!prompt) throw new Error("routine needs a prompt");
98
- if (!ROUTINE_TRIGGER_KINDS.includes(trigger.kind)) {
99
- throw new Error(
100
- `routine trigger kind must be one of: ${ROUTINE_TRIGGER_KINDS.join(", ")}`,
101
- );
102
- }
103
- if (trigger.kind === "cron") {
104
- if (!parseCron(trigger.cron || "")) {
105
- throw new Error(`invalid cron expression: ${trigger.cron}`);
106
- }
107
- }
108
- if (trigger.kind === "once") {
109
- const at = parseWhen(trigger.at);
110
- if (!Number.isFinite(at)) {
111
- throw new Error(`invalid --at time: ${trigger.at}`);
112
- }
113
- trigger.at = at;
114
- }
115
- if (trigger.kind === "github" && !trigger.repo) {
116
- throw new Error("github trigger needs --repo <owner/name>");
157
+ _resolveFromMap(map, id) {
158
+ if (map[id]) return map[id];
159
+ const matches = Object.values(map).filter(
160
+ (routine) => routine.id.startsWith(id) || routine.name === id,
161
+ );
162
+ if (matches.length === 1) return matches[0];
163
+ if (matches.length > 1) {
164
+ throw new Error(`routine "${id}" is ambiguous (${matches.length} match)`);
117
165
  }
166
+ return null;
167
+ }
118
168
 
169
+ _createInMap(map, def) {
170
+ const definition = normalizeRoutineDefinition(def);
119
171
  const routine = {
120
172
  id: shortId(),
121
- name,
122
- prompt,
123
- trigger,
173
+ ...definition,
124
174
  enabled: true,
125
175
  createdAt: this._now(),
126
176
  lastFiredAt: null,
127
177
  lastSeenGithubEventId: null,
128
178
  };
129
- const map = this._readRoutines();
130
179
  map[routine.id] = routine;
131
- this._writeRoutines(map);
132
180
  return routine;
133
181
  }
134
182
 
183
+ /**
184
+ * @param {object} def { name, prompt, trigger: {kind, cron?, at?, repo?, events?} }
185
+ */
186
+ create(def = {}) {
187
+ return this._withDefinitionLock((map) => {
188
+ const routine = this._createInMap(map, def);
189
+ this._writeRoutines(map);
190
+ return routine;
191
+ });
192
+ }
193
+
135
194
  list() {
136
195
  return Object.values(this._readRoutines()).sort(
137
196
  (a, b) => (b.createdAt || 0) - (a.createdAt || 0),
@@ -140,26 +199,26 @@ export class RoutineStore {
140
199
 
141
200
  get(id) {
142
201
  const map = this._readRoutines();
143
- if (map[id]) return map[id];
144
- // prefix / name match for CLI ergonomics
145
- const matches = Object.values(map).filter(
146
- (r) => r.id.startsWith(id) || r.name === id,
147
- );
148
- if (matches.length === 1) return matches[0];
149
- if (matches.length > 1) {
150
- throw new Error(`routine "${id}" is ambiguous (${matches.length} match)`);
151
- }
152
- return null;
202
+ return this._resolveFromMap(map, id);
153
203
  }
154
204
 
155
205
  update(id, patch) {
156
- const map = this._readRoutines();
157
- const routine = this.get(id);
158
- if (!routine) throw new Error(`routine not found: ${id}`);
159
- const next = { ...routine, ...patch, id: routine.id };
160
- map[routine.id] = next;
161
- this._writeRoutines(map);
162
- return next;
206
+ return this._withDefinitionLock((map) => {
207
+ const routine = this._resolveFromMap(map, id);
208
+ if (!routine) throw new Error(`routine not found: ${id}`);
209
+ const next = { ...routine, ...patch, id: routine.id };
210
+ map[routine.id] = next;
211
+ this._writeRoutines(map);
212
+ return next;
213
+ });
214
+ }
215
+
216
+ updateDefinition(id, definition) {
217
+ const normalized = normalizeRoutineDefinition(definition);
218
+ return this.update(id, {
219
+ ...normalized,
220
+ lastSeenGithubEventId: null,
221
+ });
163
222
  }
164
223
 
165
224
  setEnabled(id, enabled) {
@@ -167,12 +226,54 @@ export class RoutineStore {
167
226
  }
168
227
 
169
228
  remove(id) {
170
- const map = this._readRoutines();
171
- const routine = this.get(id);
172
- if (!routine) throw new Error(`routine not found: ${id}`);
173
- delete map[routine.id];
174
- this._writeRoutines(map);
175
- return routine;
229
+ return this._withDefinitionLock((map) => {
230
+ const routine = this._resolveFromMap(map, id);
231
+ if (!routine) throw new Error(`routine not found: ${id}`);
232
+ delete map[routine.id];
233
+ this._writeRoutines(map);
234
+ return routine;
235
+ });
236
+ }
237
+
238
+ createIfRevision(expectedRevision, revisionOfMap, definition) {
239
+ return this._withDefinitionLock((map) => {
240
+ if (revisionOfMap(map) !== expectedRevision) {
241
+ const error = new Error("routine collection changed before create");
242
+ error.code = "ROUTINE_REVISION_CONFLICT";
243
+ throw error;
244
+ }
245
+ const routine = this._createInMap(map, definition);
246
+ this._writeRoutines(map);
247
+ return routine;
248
+ });
249
+ }
250
+
251
+ readIfRevision(id, expectedRevision, revisionOfRoutine) {
252
+ return this._withDefinitionLock((map) => {
253
+ const routine = map[id];
254
+ if (!routine) throw new Error(`routine not found: ${id}`);
255
+ if (revisionOfRoutine(routine) !== expectedRevision) {
256
+ const error = new Error(`routine changed before action: ${id}`);
257
+ error.code = "ROUTINE_REVISION_CONFLICT";
258
+ throw error;
259
+ }
260
+ return structuredClone(routine);
261
+ });
262
+ }
263
+
264
+ mutateIfRevision(id, expectedRevision, revisionOfRoutine, mutation) {
265
+ return this._withDefinitionLock((map) => {
266
+ const routine = map[id];
267
+ if (!routine) throw new Error(`routine not found: ${id}`);
268
+ if (revisionOfRoutine(routine) !== expectedRevision) {
269
+ const error = new Error(`routine changed before mutation: ${id}`);
270
+ error.code = "ROUTINE_REVISION_CONFLICT";
271
+ throw error;
272
+ }
273
+ const result = mutation(routine, map);
274
+ this._writeRoutines(map);
275
+ return result;
276
+ });
176
277
  }
177
278
 
178
279
  /** Routines the driver should fire now (cron due / once reached). */
@@ -204,13 +305,24 @@ export class RoutineStore {
204
305
  }
205
306
 
206
307
  recordRunStart(routineId, meta = {}) {
207
- const runId = `run-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
308
+ const runId =
309
+ typeof meta.runId === "string" && meta.runId.trim()
310
+ ? meta.runId.trim()
311
+ : `run-${Date.now().toString(36)}-${randomBytes(3).toString("hex")}`;
312
+ if (
313
+ runId.length > 512 ||
314
+ [...runId].some((char) => char.codePointAt(0) < 32)
315
+ ) {
316
+ throw new Error("routine runId is invalid");
317
+ }
208
318
  this._appendRun({
209
319
  type: "start",
210
320
  runId,
211
321
  routineId,
212
322
  trigger: meta.trigger || null,
213
323
  pid: meta.pid || null,
324
+ schedulerOccurrenceId: meta.schedulerOccurrenceId || null,
325
+ schedulerSnapshotDigest: meta.schedulerSnapshotDigest || null,
214
326
  startedAt: this._now(),
215
327
  });
216
328
  return runId;
@@ -263,6 +375,15 @@ export class RoutineStore {
263
375
  .map(({ type: _t, ...rest }) => rest);
264
376
  }
265
377
 
378
+ getRun(runId) {
379
+ if (typeof runId !== "string" || runId.length === 0) return null;
380
+ return (
381
+ this.listRuns({ limit: Number.MAX_SAFE_INTEGER }).find(
382
+ (run) => run.runId === runId,
383
+ ) || null
384
+ );
385
+ }
386
+
266
387
  /** Per-routine aggregate for `cc routine list`: runs, ok/failed, cost. */
267
388
  summarize(routineId) {
268
389
  const runs = this.listRuns({ routineId, limit: 1000 });
@@ -286,10 +407,51 @@ export class RoutineStore {
286
407
  * lastFiredAt (and disable a fired `once`). `runAgent({prompt})` must resolve
287
408
  * `{ exitCode, output, usage?, costUsd?, pid? }`.
288
409
  */
289
- export async function fireRoutine(store, routine, runAgent, meta = {}) {
410
+ export async function executeRoutine(store, routine, runAgent, meta = {}) {
411
+ const requestedRunId =
412
+ typeof meta.runId === "string" && meta.runId.trim()
413
+ ? meta.runId.trim()
414
+ : null;
415
+ const existing = requestedRunId ? store.getRun(requestedRunId) : null;
416
+ if (existing) {
417
+ if (existing.routineId !== routine.id) {
418
+ const error = new Error("routine runId is bound to another routine");
419
+ error.code = "ROUTINE_RUN_ID_CONFLICT";
420
+ error.retryable = false;
421
+ throw error;
422
+ }
423
+ if (existing.status === "running") {
424
+ const error = new Error(
425
+ "routine run outcome is unknown; refusing duplicate execution",
426
+ );
427
+ error.code = "ROUTINE_RUN_OUTCOME_UNKNOWN";
428
+ error.retryable = false;
429
+ throw error;
430
+ }
431
+ const current = store.get(routine.id);
432
+ if (current) {
433
+ store.update(routine.id, {
434
+ lastFiredAt: existing.startedAt,
435
+ ...(current.trigger.kind === "once" ? { enabled: false } : {}),
436
+ });
437
+ }
438
+ return {
439
+ runId: existing.runId,
440
+ status: existing.status,
441
+ exitCode: existing.exitCode,
442
+ usage: existing.usage || null,
443
+ costUsd: Number.isFinite(existing.costUsd) ? existing.costUsd : null,
444
+ durationMs: existing.durationMs ?? null,
445
+ recovered: true,
446
+ };
447
+ }
448
+
290
449
  const startedAt = store._now();
291
450
  const runId = store.recordRunStart(routine.id, {
292
451
  trigger: meta.trigger || routine.trigger.kind,
452
+ ...(requestedRunId ? { runId: requestedRunId } : {}),
453
+ schedulerOccurrenceId: meta.schedulerOccurrenceId || null,
454
+ schedulerSnapshotDigest: meta.schedulerSnapshotDigest || null,
293
455
  });
294
456
  let result;
295
457
  try {
@@ -316,7 +478,19 @@ export async function fireRoutine(store, routine, runAgent, meta = {}) {
316
478
  lastFiredAt: startedAt,
317
479
  ...(routine.trigger.kind === "once" ? { enabled: false } : {}),
318
480
  });
319
- return runId;
481
+ return {
482
+ runId,
483
+ status: result.exitCode === 0 ? "ok" : "failed",
484
+ exitCode: result.exitCode,
485
+ usage: result.usage || null,
486
+ costUsd: Number.isFinite(result.costUsd) ? result.costUsd : null,
487
+ durationMs: store._now() - startedAt,
488
+ };
489
+ }
490
+
491
+ export async function fireRoutine(store, routine, runAgent, meta = {}) {
492
+ const execution = await executeRoutine(store, routine, runAgent, meta);
493
+ return execution.runId;
320
494
  }
321
495
 
322
496
  /**