chainlesschain 0.163.5 → 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 (194) 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-Cv2EE1Zy.js → AIOps-DDIUofA8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-Bm-5xYRn.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-D5UH4ljI.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-D_ifjOOP.js} +4 -4
  10. package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-BY1mXdC7.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-Bujr4Ml3.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-sioKSTcC.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-DDOAK8RK.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-CtCsVVke.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-DpNj0vnd.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CZta3Ftr.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-DoHVNSXq.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DlVl7Qmv.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-GIqoPYty.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-CIAPEVji.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-BN7JiI_h.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-CqUWCM9B.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-A4iPp7A4.js} +4 -4
  24. package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-B2zeXXWb.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BR2xmWr8.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-BzFDjrIv.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-DKtDo3yA.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-RNx43_YG.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer-SwMt4Rcy.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-CsYebZ4X.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-BmRKvBTy.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-CVxGTA-0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-x2iXpyB7.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-DfqbkiOP.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-RuHyyj5O.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference-Bn7uZ7rI.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DlP7XefZ.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BYNdOiCd.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-aMtgN-R6.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-JQYmDqM8.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-Cyx4yZ-9.js} +5 -5
  42. package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-VDuGZTN2.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-_yKo97gi.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-CXRXW3q7.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-acTGtfLH.js} +5 -5
  46. package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-ClpjXfkY.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-_6Tn3o96.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-CK7pbZzP.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-Czy6V_P8.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-Cqx-UVv3.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-C69WDxGy.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-DFnA9vLQ.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-ReZkk2yT.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-DzjSI0aI.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-DKTf5xIP.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-DhQ5klhR.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-CUL1iHrW.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-DU88iu4N.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-Bw-sYfyK.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Bq3Q5y0z.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-C8spzpho.js} +2 -2
  62. package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +1 -0
  63. package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-Bf4eTP9W.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-Dme9BDU1.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CIXfv1rE.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CIxvdeZ-.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-C6k7MExI.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-CISDz2bl.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-CdnDxXyf.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-C-QpRHsl.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-DBjaGzSn.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-CQKukOeE.js} +3 -3
  73. package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-DZz86jWb.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-jb8RMK0d.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CdY76fZG.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.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-BJsSFGYy.js → SyncSettings-qvimGO0O.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-Dng5LPJC.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-Bwqq5Wll.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-DNWuwP7Z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-B-6Othpp.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-DKpKZfzx.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-DzfGMd2J.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-BXwUi74J.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-BNzUq4ui.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-BYugxww4.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-DdauhtSJ.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-CSC9bCe_.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-Q8AN3bTO.js} +5 -5
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-d-LBGCuA.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BLVDCgsZ.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-BZlPSEHl.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-IWseP6R6.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BTk9bXBn.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-CMje33rb.js → hasIn-DLQZA_Wn.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-1jeAudHd.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-BEu2lMBy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-BOZJBTWZ.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-BSjilZ9L.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-BZs5bBnk.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-B_N4-Gq9.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-Bbj2NS_I.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-BhA3QEni.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-Bxp7uDD8.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-C1ifZtXo.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-C2FqfMvU.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-C5dgnilC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-CINJQEj6.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-COev0W2g.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-CT9vVN-X.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-h31MwrME.js → index-CZDekj7T.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-CarMTIyQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-CbQNL0ue.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CpBaJ3TD.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-DB0MJgF_.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-DOoQpuQY.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DTbxp4UH.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-DUq0c7gq.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-0Auljb10.js → index-DZpYeqrh.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-D_LQodeJ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-DalA0TKC.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-DjWnONdy.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-Dkwsi1Kn.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-togc_uYn.js → index-DocEhinm.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-DpVrK6fr.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-Dy0R5ZED.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-M7wxfAbm.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-N_GoWZX4.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C1oZReaY.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-Cd0hmPxD.js → index-gt8bNefW.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-hwVDeoC0.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-jWb70ZgZ.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-Co67Kqmb.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BMOxQSF_.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-C5fWDw-N.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-BJevpL0e.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-BCzNU8s6.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-CybWvDPk.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BMWS9HnW.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-BoL2UnGY.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DWNfXraU.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-BquTM3tX.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-BEkCleim.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-Oecnq7EA.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-CkDffJ4F.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-qbvKXesB.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-C6v8n6mA.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DmNrEtgf.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 +76 -4
  157. package/src/commands/automation.js +462 -3
  158. package/src/commands/background-session.js +102 -1
  159. package/src/commands/cowork.js +6 -1
  160. package/src/commands/loop.js +167 -36
  161. package/src/commands/routine.js +11 -4
  162. package/src/commands/scheduler-daemon.js +450 -0
  163. package/src/data/changelog.json +15 -2
  164. package/src/lib/agent-schedule-store.js +323 -43
  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 +316 -18
  172. package/src/lib/cowork-observe.js +5 -1
  173. package/src/lib/loop.js +37 -2
  174. package/src/lib/permission-engine.js +14 -7
  175. package/src/lib/public-ide-capability-manifest.js +2 -0
  176. package/src/lib/routine-store.js +166 -65
  177. package/src/lib/scheduler-kernel/agenda-adapter.js +297 -27
  178. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  179. package/src/lib/scheduler-kernel/automation-adapter.js +592 -0
  180. package/src/lib/scheduler-kernel/automation-event-adapter.js +721 -0
  181. package/src/lib/scheduler-kernel/contract.js +11 -3
  182. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +58 -12
  183. package/src/lib/scheduler-kernel/loop-adapter.js +378 -0
  184. package/src/lib/scheduler-kernel/routine-adapter.js +303 -8
  185. package/src/lib/scheduler-kernel/service.js +328 -0
  186. package/src/lib/scheduler-kernel/store.js +828 -96
  187. package/src/runtime/agent-core.js +2 -1
  188. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  189. package/src/workers/background-agent-worker.js +91 -28
  190. package/src/assets/web-panel/assets/Projects-B_wQadhX.js +0 -1
  191. package/src/assets/web-panel/assets/SpeechSettings-Dexb1f_6.js +0 -1
  192. package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
  193. package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
  194. package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
@@ -185,7 +185,9 @@ function applyScheduledAgentSuccess(entry, atMs) {
185
185
  if (entry.kind === "cron") {
186
186
  entry.lastRunAt = atMs;
187
187
  entry.runs = (entry.runs || 0) + 1;
188
- const next = nextCronTime(entry.cron, atMs);
188
+ const next = nextCronTime(entry.cron, atMs, {
189
+ timeZone: entry.timeZone || null,
190
+ });
189
191
  if (next == null) entry.status = "exhausted";
190
192
  else entry.nextAt = next;
191
193
  return;
@@ -196,6 +198,51 @@ function applyScheduledAgentSuccess(entry, atMs) {
196
198
  );
197
199
  }
198
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
+
199
246
  /** Statuses an entry can never leave — safe to prune. */
200
247
  export const TERMINAL_STATUSES = Object.freeze([
201
248
  "fired",
@@ -232,6 +279,61 @@ const CRON_RANGES = [
232
279
  [0, 6], // day of week (0/7 = Sunday)
233
280
  ];
234
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
+
235
337
  function parseCronField(field, [min, max]) {
236
338
  const values = new Set();
237
339
  for (const part of String(field).split(",")) {
@@ -280,13 +382,175 @@ function cronMatches(sets, date) {
280
382
  );
281
383
  }
282
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
+
283
539
  /**
284
- * Next epoch-ms at which `expr` fires strictly after `fromMs` (local time,
285
- * minute granularity). Scans up to ~366 days then gives up (returns null) so a
286
- * 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.
287
547
  */
288
- export function nextCronTime(expr, fromMs) {
548
+ export function nextCronTime(expr, fromMs, options = {}) {
289
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);
290
554
  const cursor = new Date(fromMs);
291
555
  cursor.setSeconds(0, 0);
292
556
  cursor.setMinutes(cursor.getMinutes() + 1);
@@ -298,6 +562,25 @@ export function nextCronTime(expr, fromMs) {
298
562
  return null;
299
563
  }
300
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
+
301
584
  // ─── store ───────────────────────────────────────────────────────────────
302
585
 
303
586
  export class AgentScheduleStore {
@@ -390,12 +673,16 @@ export class AgentScheduleStore {
390
673
  expiresAt = null,
391
674
  expiresInMs = null,
392
675
  jitterMs = 0,
676
+ timeZone = null,
393
677
  } = options;
394
678
  if (!prompt || typeof prompt !== "string") {
395
679
  throw new Error("cron requires a prompt");
396
680
  }
397
681
  const now = this._now();
398
- 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
399
686
  if (nextAt == null) {
400
687
  throw new Error(`cron "${cron}" has no upcoming match`);
401
688
  }
@@ -406,6 +693,7 @@ export class AgentScheduleStore {
406
693
  kind: "cron",
407
694
  prompt,
408
695
  cron,
696
+ ...(normalizedTimeZone ? { timeZone: normalizedTimeZone } : {}),
409
697
  label: label || null,
410
698
  nextAt,
411
699
  expiresAt: resolveExpiresAt(expiresAt, expiresInMs, now),
@@ -609,7 +897,7 @@ export class AgentScheduleStore {
609
897
  }
610
898
 
611
899
  /**
612
- * Bind a scheduler claim to the legacy Agenda entry before agent execution.
900
+ * Bind a scheduler claim to the legacy Agenda entry before execution.
613
901
  * The same lock used by legacy `claimDue()` prevents an old/new driver from
614
902
  * starting the entry concurrently during migration.
615
903
  */
@@ -640,7 +928,7 @@ export class AgentScheduleStore {
640
928
  return withFileLock(
641
929
  path.join(this.dir, ".agenda-claims"),
642
930
  () => {
643
- for (const kind of ["wakeup", "cron"]) {
931
+ for (const kind of SCHEDULE_KINDS) {
644
932
  const entries = this._readAll(kind);
645
933
  const entry = entries.find((candidate) => candidate.id === id);
646
934
  if (!entry) continue;
@@ -678,7 +966,8 @@ export class AgentScheduleStore {
678
966
  // Fence an older CLI that only understands the legacy claim field.
679
967
  // Terminal scheduler evidence clears this lease; an outcome-unknown
680
968
  // execution intentionally keeps it non-expiring until adjudicated so
681
- // an old runner cannot duplicate an uncertain agent side effect.
969
+ // an old runner cannot duplicate an uncertain external observation or
970
+ // agent side effect.
682
971
  entry.executionLease = {
683
972
  owner: `scheduler:${occurrenceId}:${attempt}`,
684
973
  claimedAt: now,
@@ -697,10 +986,10 @@ export class AgentScheduleStore {
697
986
  }
698
987
 
699
988
  /**
700
- * Atomically publish scheduler evidence and the legacy wakeup/cron state.
701
- * A successful cron advances exactly once in the same JSONL replacement that
702
- * records terminal evidence; failures keep the entry schedulable for the
703
- * scheduler kernel's bounded retry policy.
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.
704
993
  */
705
994
  completeSchedulerExecution(
706
995
  id,
@@ -711,6 +1000,7 @@ export class AgentScheduleStore {
711
1000
  outcome,
712
1001
  result = null,
713
1002
  error = null,
1003
+ monitorCheck = null,
714
1004
  atMs = null,
715
1005
  } = {},
716
1006
  ) {
@@ -725,7 +1015,7 @@ export class AgentScheduleStore {
725
1015
  return withFileLock(
726
1016
  path.join(this.dir, ".agenda-claims"),
727
1017
  () => {
728
- for (const kind of ["wakeup", "cron"]) {
1018
+ for (const kind of SCHEDULE_KINDS) {
729
1019
  const entries = this._readAll(kind);
730
1020
  const entry = entries.find((candidate) => candidate.id === id);
731
1021
  if (!entry) continue;
@@ -742,14 +1032,23 @@ export class AgentScheduleStore {
742
1032
  `Agenda scheduler evidence does not match the active claim: ${id}`,
743
1033
  );
744
1034
  }
1035
+ let storedResult = result;
745
1036
  if (outcome === "succeeded") {
746
- applyScheduledAgentSuccess(entry, now);
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
+ }
747
1046
  }
748
1047
  entry.schedulerExecution = {
749
1048
  ...evidence,
750
1049
  status: outcome,
751
1050
  endedAt: now,
752
- ...(outcome === "succeeded" ? { result } : { error }),
1051
+ ...(outcome === "succeeded" ? { result: storedResult } : { error }),
753
1052
  };
754
1053
  delete entry.executionLease;
755
1054
  this._writeAll(kind, entries);
@@ -841,7 +1140,9 @@ export class AgentScheduleStore {
841
1140
  return this._mutate("cron", id, (entry) => {
842
1141
  entry.lastRunAt = now;
843
1142
  entry.runs = (entry.runs || 0) + 1;
844
- const next = nextCronTime(entry.cron, now);
1143
+ const next = nextCronTime(entry.cron, now, {
1144
+ timeZone: entry.timeZone || null,
1145
+ });
845
1146
  if (next == null) {
846
1147
  entry.status = "exhausted";
847
1148
  } else {
@@ -866,32 +1167,11 @@ export class AgentScheduleStore {
866
1167
  ) {
867
1168
  const now = atMs != null ? atMs : this._now();
868
1169
  return this._mutate("monitor", id, (entry) => {
869
- entry.checks = (entry.checks || 0) + 1;
870
- entry.lastCheckAt = now;
871
- // Record the mtime baseline the first time we see the file (for a
872
- // watchChange monitor); leave it fixed thereafter so "changed" always
873
- // means "changed since we started watching".
874
- if (mtimeMs != null && entry.lastMtimeMs == null) {
875
- entry.lastMtimeMs = mtimeMs;
876
- }
877
- if (matched) {
878
- entry.status = "matched";
879
- entry.matchedAt = now;
880
- // Audit record of the firing: the deterministic monitor-event id + the
881
- // (always SYSTEM/steer) authority the event was delivered under. Durable
882
- // on the entry so `cc agenda list --json` and a future resident daemon
883
- // can recognise / dedup the exact observation. Only stamped when the
884
- // caller supplies them, so legacy callers stay byte-identical.
885
- if (eventId != null) {
886
- entry.lastEventId = String(eventId);
887
- entry.lastEventAt = now;
888
- }
889
- if (authority != null) entry.lastAuthority = String(authority);
890
- } else if (entry.maxChecks != null && entry.checks >= entry.maxChecks) {
891
- entry.status = "exhausted";
892
- } else {
893
- entry.nextAt = now + entry.intervalMs;
894
- }
1170
+ applyScheduledMonitorSuccess(
1171
+ entry,
1172
+ { matched: Boolean(matched), mtimeMs, eventId, authority },
1173
+ now,
1174
+ );
895
1175
  });
896
1176
  }
897
1177