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
@@ -168,6 +168,81 @@ function hasLiveExecutionLease(entry, now) {
168
168
  return Number(entry.executionLease?.expiresAt) > Number(now);
169
169
  }
170
170
 
171
+ function agendaStoreError(code, message, { retryable = false, retryAt } = {}) {
172
+ const error = new Error(message);
173
+ error.code = code;
174
+ error.retryable = retryable;
175
+ if (retryAt !== undefined) error.retryAt = retryAt;
176
+ return error;
177
+ }
178
+
179
+ function applyScheduledAgentSuccess(entry, atMs) {
180
+ if (entry.kind === "wakeup") {
181
+ entry.status = "fired";
182
+ entry.firedAt = atMs;
183
+ return;
184
+ }
185
+ if (entry.kind === "cron") {
186
+ entry.lastRunAt = atMs;
187
+ entry.runs = (entry.runs || 0) + 1;
188
+ const next = nextCronTime(entry.cron, atMs, {
189
+ timeZone: entry.timeZone || null,
190
+ });
191
+ if (next == null) entry.status = "exhausted";
192
+ else entry.nextAt = next;
193
+ return;
194
+ }
195
+ throw agendaStoreError(
196
+ "AGENDA_SCHEDULER_KIND_UNSUPPORTED",
197
+ `Scheduler execution is not supported for agenda kind: ${entry.kind}`,
198
+ );
199
+ }
200
+
201
+ function applyScheduledMonitorSuccess(entry, monitorCheck, atMs) {
202
+ if (
203
+ entry.kind !== "monitor" ||
204
+ !monitorCheck ||
205
+ typeof monitorCheck !== "object" ||
206
+ Array.isArray(monitorCheck) ||
207
+ typeof monitorCheck.matched !== "boolean"
208
+ ) {
209
+ throw agendaStoreError(
210
+ "AGENDA_SCHEDULER_MONITOR_RESULT_INVALID",
211
+ `Scheduler monitor result is invalid: ${entry.id}`,
212
+ );
213
+ }
214
+ const mtimeMs =
215
+ monitorCheck.mtimeMs === null || monitorCheck.mtimeMs === undefined
216
+ ? null
217
+ : Number(monitorCheck.mtimeMs);
218
+ if (mtimeMs !== null && (!Number.isFinite(mtimeMs) || mtimeMs < 0)) {
219
+ throw agendaStoreError(
220
+ "AGENDA_SCHEDULER_MONITOR_RESULT_INVALID",
221
+ `Scheduler monitor mtime is invalid: ${entry.id}`,
222
+ );
223
+ }
224
+ entry.checks = (entry.checks || 0) + 1;
225
+ entry.lastCheckAt = atMs;
226
+ if (mtimeMs !== null && entry.lastMtimeMs == null) {
227
+ entry.lastMtimeMs = mtimeMs;
228
+ }
229
+ if (monitorCheck.matched) {
230
+ entry.status = "matched";
231
+ entry.matchedAt = atMs;
232
+ if (monitorCheck.eventId != null) {
233
+ entry.lastEventId = String(monitorCheck.eventId);
234
+ entry.lastEventAt = atMs;
235
+ }
236
+ if (monitorCheck.authority != null) {
237
+ entry.lastAuthority = String(monitorCheck.authority);
238
+ }
239
+ } else if (entry.maxChecks != null && entry.checks >= entry.maxChecks) {
240
+ entry.status = "exhausted";
241
+ } else {
242
+ entry.nextAt = atMs + entry.intervalMs;
243
+ }
244
+ }
245
+
171
246
  /** Statuses an entry can never leave — safe to prune. */
172
247
  export const TERMINAL_STATUSES = Object.freeze([
173
248
  "fired",
@@ -204,6 +279,61 @@ const CRON_RANGES = [
204
279
  [0, 6], // day of week (0/7 = Sunday)
205
280
  ];
206
281
 
282
+ const CRON_TIME_ZONE_FORMATTERS = new Map();
283
+
284
+ export function normalizeTimeZone(value) {
285
+ if (value == null || value === "") return null;
286
+ if (typeof value !== "string") {
287
+ throw new Error("cron timeZone must be an IANA time zone string");
288
+ }
289
+ const requested = value.trim();
290
+ if (!requested) return null;
291
+ if (requested.length > 128 || /\p{Cc}/u.test(requested)) {
292
+ throw new Error("invalid IANA time zone");
293
+ }
294
+ try {
295
+ return new Intl.DateTimeFormat("en-US", {
296
+ timeZone: requested,
297
+ }).resolvedOptions().timeZone;
298
+ } catch {
299
+ throw new Error(`invalid IANA time zone: ${requested}`);
300
+ }
301
+ }
302
+
303
+ function timeZoneFormatter(timeZone) {
304
+ let formatter = CRON_TIME_ZONE_FORMATTERS.get(timeZone);
305
+ if (!formatter) {
306
+ formatter = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
307
+ timeZone,
308
+ year: "numeric",
309
+ month: "2-digit",
310
+ day: "2-digit",
311
+ hour: "2-digit",
312
+ minute: "2-digit",
313
+ hourCycle: "h23",
314
+ });
315
+ CRON_TIME_ZONE_FORMATTERS.set(timeZone, formatter);
316
+ }
317
+ return formatter;
318
+ }
319
+
320
+ function timeZoneParts(epochMs, timeZone) {
321
+ const values = {};
322
+ for (const part of timeZoneFormatter(timeZone).formatToParts(epochMs)) {
323
+ if (part.type !== "literal") values[part.type] = Number(part.value);
324
+ }
325
+ return {
326
+ year: values.year,
327
+ minute: values.minute,
328
+ hour: values.hour,
329
+ day: values.day,
330
+ month: values.month,
331
+ dayOfWeek: new Date(
332
+ Date.UTC(values.year, values.month - 1, values.day),
333
+ ).getUTCDay(),
334
+ };
335
+ }
336
+
207
337
  function parseCronField(field, [min, max]) {
208
338
  const values = new Set();
209
339
  for (const part of String(field).split(",")) {
@@ -252,13 +382,175 @@ function cronMatches(sets, date) {
252
382
  );
253
383
  }
254
384
 
385
+ function timeZoneOffsetAt(epochMs, timeZone) {
386
+ const minuteEpoch = Math.floor(epochMs / 60_000) * 60_000;
387
+ const parts = timeZoneParts(minuteEpoch, timeZone);
388
+ return (
389
+ Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute) -
390
+ minuteEpoch
391
+ );
392
+ }
393
+
394
+ function localDayOffsets(year, month, day, timeZone) {
395
+ const reference = Date.UTC(year, month - 1, day, 12, 0, 0);
396
+ return [
397
+ ...new Set(
398
+ [-36, -12, 0, 12, 36].map((hours) =>
399
+ timeZoneOffsetAt(reference + hours * 3_600_000, timeZone),
400
+ ),
401
+ ),
402
+ ];
403
+ }
404
+
405
+ function epochCandidatesForCivilMinute(
406
+ { year, month, day, hour, minute },
407
+ timeZone,
408
+ offsets,
409
+ ) {
410
+ const civilEpoch = Date.UTC(year, month - 1, day, hour, minute, 0);
411
+ const matches = [];
412
+ for (const offset of offsets) {
413
+ const epoch = civilEpoch - offset;
414
+ const actual = timeZoneParts(epoch, timeZone);
415
+ if (
416
+ actual.year === year &&
417
+ actual.month === month &&
418
+ actual.day === day &&
419
+ actual.hour === hour &&
420
+ actual.minute === minute
421
+ ) {
422
+ matches.push(epoch);
423
+ }
424
+ }
425
+ return [...new Set(matches)].sort((a, b) => a - b);
426
+ }
427
+
428
+ function nextCronTimeInTimeZone(sets, fromMs, timeZone) {
429
+ const from = Number(fromMs);
430
+ const firstLocal = timeZoneParts(from, timeZone);
431
+ const firstLocalDay = Date.UTC(
432
+ firstLocal.year,
433
+ firstLocal.month - 1,
434
+ firstLocal.day,
435
+ );
436
+ const minuteLimit = 366 * 24 * 60;
437
+ const lastEpoch = Math.floor(from / 60_000) * 60_000 + minuteLimit * 60_000;
438
+ const hours = [...sets[1]].sort((a, b) => a - b);
439
+ const minutes = [...sets[0]].sort((a, b) => a - b);
440
+ let earliest = null;
441
+
442
+ // Iterate civil dates instead of formatting every real minute. At most two
443
+ // UTC offsets can map a civil minute around a DST transition; verifying each
444
+ // reconstructed candidate preserves missing and repeated wall-time behavior.
445
+ // Start one civil day earlier because a rare midnight rollback can make a
446
+ // future real instant display as the previous local date. Once a later civil
447
+ // date is more than 36 hours beyond the best epoch, no supported IANA offset
448
+ // can map that date back before the current best candidate.
449
+ for (let dayOffset = -1; dayOffset <= 367; dayOffset += 1) {
450
+ const civilDay = firstLocalDay + dayOffset * 86_400_000;
451
+ if (earliest != null && civilDay - 36 * 3_600_000 > earliest) break;
452
+ const date = new Date(civilDay);
453
+ const year = date.getUTCFullYear();
454
+ const month = date.getUTCMonth() + 1;
455
+ const day = date.getUTCDate();
456
+ const dayOfWeek = date.getUTCDay();
457
+ if (
458
+ !sets[2].has(day) ||
459
+ !sets[3].has(month) ||
460
+ (!sets[4].has(dayOfWeek) && !(sets[4].has(7) && dayOfWeek === 0))
461
+ ) {
462
+ continue;
463
+ }
464
+ const offsets = localDayOffsets(year, month, day, timeZone);
465
+ for (const hour of hours) {
466
+ for (const minute of minutes) {
467
+ for (const epoch of epochCandidatesForCivilMinute(
468
+ { year, month, day, hour, minute },
469
+ timeZone,
470
+ offsets,
471
+ )) {
472
+ if (
473
+ epoch > from &&
474
+ epoch <= lastEpoch &&
475
+ (earliest == null || epoch < earliest)
476
+ ) {
477
+ earliest = epoch;
478
+ }
479
+ }
480
+ }
481
+ }
482
+ }
483
+ return earliest;
484
+ }
485
+
486
+ function previousCronTimeInTimeZone(sets, fromMs, timeZone) {
487
+ const from = Number(fromMs);
488
+ const firstLocal = timeZoneParts(from, timeZone);
489
+ const firstLocalDay = Date.UTC(
490
+ firstLocal.year,
491
+ firstLocal.month - 1,
492
+ firstLocal.day,
493
+ );
494
+ const minuteLimit = 366 * 24 * 60;
495
+ const firstEpoch = Math.floor(from / 60_000) * 60_000 - minuteLimit * 60_000;
496
+ const hours = [...sets[1]].sort((a, b) => b - a);
497
+ const minutes = [...sets[0]].sort((a, b) => b - a);
498
+ let latest = null;
499
+
500
+ // Start one civil day ahead because a rare midnight transition can make a
501
+ // prior real instant display as the following local date.
502
+ for (let dayOffset = 1; dayOffset >= -367; dayOffset -= 1) {
503
+ const civilDay = firstLocalDay + dayOffset * 86_400_000;
504
+ if (latest != null && civilDay + 36 * 3_600_000 < latest) break;
505
+ const date = new Date(civilDay);
506
+ const year = date.getUTCFullYear();
507
+ const month = date.getUTCMonth() + 1;
508
+ const day = date.getUTCDate();
509
+ const dayOfWeek = date.getUTCDay();
510
+ if (
511
+ !sets[2].has(day) ||
512
+ !sets[3].has(month) ||
513
+ (!sets[4].has(dayOfWeek) && !(sets[4].has(7) && dayOfWeek === 0))
514
+ ) {
515
+ continue;
516
+ }
517
+ const offsets = localDayOffsets(year, month, day, timeZone);
518
+ for (const hour of hours) {
519
+ for (const minute of minutes) {
520
+ for (const epoch of epochCandidatesForCivilMinute(
521
+ { year, month, day, hour, minute },
522
+ timeZone,
523
+ offsets,
524
+ )) {
525
+ if (
526
+ epoch < from &&
527
+ epoch >= firstEpoch &&
528
+ (latest == null || epoch > latest)
529
+ ) {
530
+ latest = epoch;
531
+ }
532
+ }
533
+ }
534
+ }
535
+ }
536
+ return latest;
537
+ }
538
+
255
539
  /**
256
- * Next epoch-ms at which `expr` fires strictly after `fromMs` (local time,
257
- * minute granularity). Scans up to ~366 days then gives up (returns null) so a
258
- * never-matching expression can't loop forever.
540
+ * Next epoch-ms at which `expr` fires strictly after `fromMs`, at minute
541
+ * granularity. By default this preserves host-local cron behavior; an explicit
542
+ * IANA `timeZone` evaluates the same epoch minutes against that civil clock.
543
+ * A repeated fall-back wall minute therefore maps to both real instants, while
544
+ * a skipped spring-forward wall minute does not exist. Scans up to ~366 days
545
+ * then gives up (returns null) so a never-matching expression can't loop
546
+ * forever.
259
547
  */
260
- export function nextCronTime(expr, fromMs) {
548
+ export function nextCronTime(expr, fromMs, options = {}) {
261
549
  const sets = parseCron(expr);
550
+ const timeZone = normalizeTimeZone(
551
+ typeof options === "string" ? options : options?.timeZone,
552
+ );
553
+ if (timeZone) return nextCronTimeInTimeZone(sets, fromMs, timeZone);
262
554
  const cursor = new Date(fromMs);
263
555
  cursor.setSeconds(0, 0);
264
556
  cursor.setMinutes(cursor.getMinutes() + 1);
@@ -270,6 +562,25 @@ export function nextCronTime(expr, fromMs) {
270
562
  return null;
271
563
  }
272
564
 
565
+ /** Previous cron occurrence strictly before `fromMs`, bounded to one year. */
566
+ export function previousCronTime(expr, fromMs, options = {}) {
567
+ const sets = parseCron(expr);
568
+ const timeZone = normalizeTimeZone(
569
+ typeof options === "string" ? options : options?.timeZone,
570
+ );
571
+ if (timeZone) return previousCronTimeInTimeZone(sets, fromMs, timeZone);
572
+ const cursor = new Date(fromMs);
573
+ cursor.setSeconds(0, 0);
574
+ if (cursor.getTime() >= Number(fromMs))
575
+ cursor.setMinutes(cursor.getMinutes() - 1);
576
+ const limit = 366 * 24 * 60;
577
+ for (let i = 0; i < limit; i += 1) {
578
+ if (cronMatches(sets, cursor)) return cursor.getTime();
579
+ cursor.setMinutes(cursor.getMinutes() - 1);
580
+ }
581
+ return null;
582
+ }
583
+
273
584
  // ─── store ───────────────────────────────────────────────────────────────
274
585
 
275
586
  export class AgentScheduleStore {
@@ -362,12 +673,16 @@ export class AgentScheduleStore {
362
673
  expiresAt = null,
363
674
  expiresInMs = null,
364
675
  jitterMs = 0,
676
+ timeZone = null,
365
677
  } = options;
366
678
  if (!prompt || typeof prompt !== "string") {
367
679
  throw new Error("cron requires a prompt");
368
680
  }
369
681
  const now = this._now();
370
- const nextAt = nextCronTime(cron, now); // validates the expression too
682
+ const normalizedTimeZone = normalizeTimeZone(timeZone);
683
+ const nextAt = nextCronTime(cron, now, {
684
+ timeZone: normalizedTimeZone,
685
+ }); // validates the expression and optional IANA time zone
371
686
  if (nextAt == null) {
372
687
  throw new Error(`cron "${cron}" has no upcoming match`);
373
688
  }
@@ -378,6 +693,7 @@ export class AgentScheduleStore {
378
693
  kind: "cron",
379
694
  prompt,
380
695
  cron,
696
+ ...(normalizedTimeZone ? { timeZone: normalizedTimeZone } : {}),
381
697
  label: label || null,
382
698
  nextAt,
383
699
  expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
@@ -499,6 +815,11 @@ export class AgentScheduleStore {
499
815
  return out;
500
816
  }
501
817
 
818
+ get(id) {
819
+ if (typeof id !== "string" || id.length === 0) return null;
820
+ return this.list().find((entry) => entry.id === id) || null;
821
+ }
822
+
502
823
  /** Entries whose time has come (pending wakeup / active cron|monitor). */
503
824
  due(kind = null, atMs = null) {
504
825
  const now = atMs != null ? atMs : this._now();
@@ -524,16 +845,20 @@ export class AgentScheduleStore {
524
845
  * lease expires. This is deliberately separate from `due()` for backwards
525
846
  * compatibility with read-only callers and injected test stores.
526
847
  */
527
- claimDue(atMs = null, leaseMs = 120000) {
848
+ claimDue(atMs = null, leaseMs = 120000, kind = null) {
528
849
  const now = atMs != null ? Number(atMs) : this._now();
529
850
  const ttl = Math.max(1000, Number(leaseMs) || 120000);
851
+ const kinds = kind == null ? SCHEDULE_KINDS : [kind];
852
+ if (kinds.some((value) => !SCHEDULE_KINDS.includes(value))) {
853
+ throw new Error(`unknown schedule kind: ${kind}`);
854
+ }
530
855
  this._ensureDir();
531
856
  return withFileLock(
532
857
  path.join(this.dir, ".agenda-claims"),
533
858
  () => {
534
859
  const claimed = [];
535
- for (const kind of SCHEDULE_KINDS) {
536
- const entries = this._readAll(kind);
860
+ for (const scheduleKind of kinds) {
861
+ const entries = this._readAll(scheduleKind);
537
862
  let changed = false;
538
863
  for (const entry of entries) {
539
864
  if (!isSchedulableStatus(entry) || isEntryExpired(entry, now))
@@ -552,7 +877,7 @@ export class AgentScheduleStore {
552
877
  });
553
878
  changed = true;
554
879
  }
555
- if (changed) this._writeAll(kind, entries);
880
+ if (changed) this._writeAll(scheduleKind, entries);
556
881
  }
557
882
  return claimed;
558
883
  },
@@ -571,6 +896,173 @@ export class AgentScheduleStore {
571
896
  return null;
572
897
  }
573
898
 
899
+ /**
900
+ * Bind a scheduler claim to the legacy Agenda entry before execution.
901
+ * The same lock used by legacy `claimDue()` prevents an old/new driver from
902
+ * starting the entry concurrently during migration.
903
+ */
904
+ bindSchedulerExecution(
905
+ id,
906
+ { occurrenceId, snapshotDigest, attempt, atMs = null } = {},
907
+ ) {
908
+ const now = atMs != null ? Number(atMs) : this._now();
909
+ if (typeof occurrenceId !== "string" || occurrenceId.length === 0) {
910
+ throw agendaStoreError(
911
+ "AGENDA_SCHEDULER_BINDING_INVALID",
912
+ "Agenda scheduler occurrenceId is required",
913
+ );
914
+ }
915
+ if (typeof snapshotDigest !== "string" || snapshotDigest.length === 0) {
916
+ throw agendaStoreError(
917
+ "AGENDA_SCHEDULER_BINDING_INVALID",
918
+ "Agenda scheduler snapshotDigest is required",
919
+ );
920
+ }
921
+ if (!Number.isSafeInteger(attempt) || attempt < 1) {
922
+ throw agendaStoreError(
923
+ "AGENDA_SCHEDULER_BINDING_INVALID",
924
+ "Agenda scheduler attempt must be a positive integer",
925
+ );
926
+ }
927
+ this._ensureDir();
928
+ return withFileLock(
929
+ path.join(this.dir, ".agenda-claims"),
930
+ () => {
931
+ for (const kind of SCHEDULE_KINDS) {
932
+ const entries = this._readAll(kind);
933
+ const entry = entries.find((candidate) => candidate.id === id);
934
+ if (!entry) continue;
935
+ const prior = entry.schedulerExecution;
936
+ if (prior?.status === "running") {
937
+ throw agendaStoreError(
938
+ "AGENDA_SCHEDULER_OUTCOME_UNKNOWN",
939
+ `Agenda scheduler execution outcome is unknown: ${id}`,
940
+ );
941
+ }
942
+ if (
943
+ prior?.status === "succeeded" &&
944
+ prior.occurrenceId === occurrenceId &&
945
+ prior.snapshotDigest === snapshotDigest
946
+ ) {
947
+ return entry;
948
+ }
949
+ if (hasLiveExecutionLease(entry, now)) {
950
+ throw agendaStoreError(
951
+ "AGENDA_SCHEDULER_LEGACY_CLAIM_ACTIVE",
952
+ `Agenda entry is already claimed by a legacy runner: ${id}`,
953
+ {
954
+ retryable: true,
955
+ retryAt: Number(entry.executionLease.expiresAt),
956
+ },
957
+ );
958
+ }
959
+ entry.schedulerExecution = {
960
+ occurrenceId,
961
+ snapshotDigest,
962
+ attempt,
963
+ status: "running",
964
+ startedAt: now,
965
+ };
966
+ // Fence an older CLI that only understands the legacy claim field.
967
+ // Terminal scheduler evidence clears this lease; an outcome-unknown
968
+ // execution intentionally keeps it non-expiring until adjudicated so
969
+ // an old runner cannot duplicate an uncertain external observation or
970
+ // agent side effect.
971
+ entry.executionLease = {
972
+ owner: `scheduler:${occurrenceId}:${attempt}`,
973
+ claimedAt: now,
974
+ expiresAt: Number.MAX_SAFE_INTEGER,
975
+ };
976
+ this._writeAll(kind, entries);
977
+ return entry;
978
+ }
979
+ throw agendaStoreError(
980
+ "AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
981
+ `Agenda entry does not exist: ${id}`,
982
+ );
983
+ },
984
+ { failIfUnavailable: true },
985
+ );
986
+ }
987
+
988
+ /**
989
+ * Atomically publish scheduler evidence and the legacy Agenda state. A
990
+ * successful cron advances and a successful monitor records/re-arms exactly
991
+ * once in the same JSONL replacement that records terminal evidence;
992
+ * failures keep the entry schedulable for the kernel's bounded retry policy.
993
+ */
994
+ completeSchedulerExecution(
995
+ id,
996
+ {
997
+ occurrenceId,
998
+ snapshotDigest,
999
+ attempt,
1000
+ outcome,
1001
+ result = null,
1002
+ error = null,
1003
+ monitorCheck = null,
1004
+ atMs = null,
1005
+ } = {},
1006
+ ) {
1007
+ if (!["succeeded", "failed"].includes(outcome)) {
1008
+ throw agendaStoreError(
1009
+ "AGENDA_SCHEDULER_OUTCOME_INVALID",
1010
+ "Agenda scheduler outcome must be succeeded or failed",
1011
+ );
1012
+ }
1013
+ const now = atMs != null ? Number(atMs) : this._now();
1014
+ this._ensureDir();
1015
+ return withFileLock(
1016
+ path.join(this.dir, ".agenda-claims"),
1017
+ () => {
1018
+ for (const kind of SCHEDULE_KINDS) {
1019
+ const entries = this._readAll(kind);
1020
+ const entry = entries.find((candidate) => candidate.id === id);
1021
+ if (!entry) continue;
1022
+ const evidence = entry.schedulerExecution;
1023
+ if (
1024
+ !evidence ||
1025
+ evidence.occurrenceId !== occurrenceId ||
1026
+ evidence.snapshotDigest !== snapshotDigest ||
1027
+ evidence.attempt !== attempt ||
1028
+ evidence.status !== "running"
1029
+ ) {
1030
+ throw agendaStoreError(
1031
+ "AGENDA_SCHEDULER_BINDING_MISMATCH",
1032
+ `Agenda scheduler evidence does not match the active claim: ${id}`,
1033
+ );
1034
+ }
1035
+ let storedResult = result;
1036
+ if (outcome === "succeeded") {
1037
+ if (entry.kind === "monitor") {
1038
+ applyScheduledMonitorSuccess(entry, monitorCheck, now);
1039
+ storedResult =
1040
+ result && typeof result === "object" && !Array.isArray(result)
1041
+ ? { ...result, status: entry.status }
1042
+ : result;
1043
+ } else {
1044
+ applyScheduledAgentSuccess(entry, now);
1045
+ }
1046
+ }
1047
+ entry.schedulerExecution = {
1048
+ ...evidence,
1049
+ status: outcome,
1050
+ endedAt: now,
1051
+ ...(outcome === "succeeded" ? { result: storedResult } : { error }),
1052
+ };
1053
+ delete entry.executionLease;
1054
+ this._writeAll(kind, entries);
1055
+ return entry;
1056
+ }
1057
+ throw agendaStoreError(
1058
+ "AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
1059
+ `Agenda entry does not exist: ${id}`,
1060
+ );
1061
+ },
1062
+ { failIfUnavailable: true },
1063
+ );
1064
+ }
1065
+
574
1066
  /**
575
1067
  * Retire every schedulable entry whose `expiresAt` has passed (status →
576
1068
  * "expired", stamping `expiredAt`), across all kinds, so it never fires again.
@@ -648,7 +1140,9 @@ export class AgentScheduleStore {
648
1140
  return this._mutate("cron", id, (entry) => {
649
1141
  entry.lastRunAt = now;
650
1142
  entry.runs = (entry.runs || 0) + 1;
651
- const next = nextCronTime(entry.cron, now);
1143
+ const next = nextCronTime(entry.cron, now, {
1144
+ timeZone: entry.timeZone || null,
1145
+ });
652
1146
  if (next == null) {
653
1147
  entry.status = "exhausted";
654
1148
  } else {
@@ -673,32 +1167,11 @@ export class AgentScheduleStore {
673
1167
  ) {
674
1168
  const now = atMs != null ? atMs : this._now();
675
1169
  return this._mutate("monitor", id, (entry) => {
676
- entry.checks = (entry.checks || 0) + 1;
677
- entry.lastCheckAt = now;
678
- // Record the mtime baseline the first time we see the file (for a
679
- // watchChange monitor); leave it fixed thereafter so "changed" always
680
- // means "changed since we started watching".
681
- if (mtimeMs != null && entry.lastMtimeMs == null) {
682
- entry.lastMtimeMs = mtimeMs;
683
- }
684
- if (matched) {
685
- entry.status = "matched";
686
- entry.matchedAt = now;
687
- // Audit record of the firing: the deterministic monitor-event id + the
688
- // (always SYSTEM/steer) authority the event was delivered under. Durable
689
- // on the entry so `cc agenda list --json` and a future resident daemon
690
- // can recognise / dedup the exact observation. Only stamped when the
691
- // caller supplies them, so legacy callers stay byte-identical.
692
- if (eventId != null) {
693
- entry.lastEventId = String(eventId);
694
- entry.lastEventAt = now;
695
- }
696
- if (authority != null) entry.lastAuthority = String(authority);
697
- } else if (entry.maxChecks != null && entry.checks >= entry.maxChecks) {
698
- entry.status = "exhausted";
699
- } else {
700
- entry.nextAt = now + entry.intervalMs;
701
- }
1170
+ applyScheduledMonitorSuccess(
1171
+ entry,
1172
+ { matched: Boolean(matched), mtimeMs, eventId, authority },
1173
+ now,
1174
+ );
702
1175
  });
703
1176
  }
704
1177