chainlesschain 0.163.5 → 0.163.7

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 (236) 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-axqQfwv8.js} +1 -1
  7. package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-DV-Ro0By.js} +1 -1
  8. package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-Bt72WEVu.js} +3 -3
  9. package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-BtepG55E.js} +5 -5
  10. package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-B7KaJ7S_.js} +1 -1
  11. package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-FNFaUg7A.js} +1 -1
  12. package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
  13. package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-CC7YBhyj.js} +1 -1
  14. package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-RkgTNVtj.js} +1 -1
  15. package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-hHEmGZy-.js} +5 -5
  16. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
  17. package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-z5wXOQP1.js} +1 -1
  18. package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DVaDJnxy.js} +1 -1
  19. package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-CJmaRDR5.js} +1 -1
  20. package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-DF29_2bI.js} +1 -1
  21. package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-DtkQkBzn.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-xoHDxp7I.js} +1 -1
  23. package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-BUm-R7ht.js} +4 -4
  24. package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-Cgl14ZkB.js} +2 -2
  25. package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BNcbmoqh.js} +1 -1
  26. package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-Bwja0Zo9.js} +2 -2
  27. package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-Bk5XjEdJ.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-Ci36Oitq.js} +1 -1
  29. package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer--U04ZN4q.js} +1 -1
  30. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
  31. package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-C4KghYn7.js} +1 -1
  32. package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-QL7x_Q_0.js} +1 -1
  33. package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
  34. package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-2HassK5k.js} +2 -2
  35. package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-DoBIp7oe.js} +1 -1
  36. package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference--W01QhZ-.js} +1 -1
  37. package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
  38. package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BBJ4yAel.js} +2 -2
  39. package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
  40. package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-D5lNngSI.js} +1 -1
  41. package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-CkEVOMKH.js} +5 -5
  42. package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-B1Ow1xVE.js} +2 -2
  43. package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-SgYbUfb6.js} +1 -1
  44. package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-BWdOvQAj.js} +1 -1
  45. package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-DcTXSSma.js} +2 -2
  46. package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-Dt3WG4oj.js} +4 -4
  47. package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-B9B38UZx.js} +3 -3
  48. package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-B0S4GGvG.js} +1 -1
  49. package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-DZ5U58Ng.js} +4 -4
  50. package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-PjL5ZW34.js} +1 -1
  51. package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-Cv1GYs-c.js} +4 -4
  53. package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-Bbkp_PL3.js} +1 -1
  54. package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-BD8QUgoZ.js} +2 -2
  55. package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-BO-2PeaB.js} +5 -5
  56. package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-D_OPVa02.js} +4 -4
  57. package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-mh-pEmaB.js} +4 -4
  58. package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-6JC2EDdb.js} +1 -1
  59. package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-CUshzUAG.js} +1 -1
  60. package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Cs2Aalri.js} +2 -2
  61. package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-1ak49tcj.js} +2 -2
  62. package/src/assets/web-panel/assets/{Projects-B_wQadhX.js → Projects-BV4oApsm.js} +1 -1
  63. package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-CXXxvafH.js} +1 -1
  64. package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-PTcDsF58.js} +1 -1
  65. package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CDwucrJw.js} +1 -1
  66. package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CJlLVRXR.js} +1 -1
  67. package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-DlN-ETsO.js} +2 -2
  68. package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-DI23LR6m.js} +1 -1
  69. package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-Cl7UQMrM.js} +1 -1
  70. package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-CexHNfh8.js} +3 -3
  71. package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-BneBWvRN.js} +1 -1
  72. package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-DX7Phgrp.js} +4 -4
  73. package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-BWXO8Mte.js} +2 -2
  74. package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-DCyOjKPJ.js} +1 -1
  75. package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CXqERsTB.js} +1 -1
  76. package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.js → Sla-bhbrbz8E.js} +1 -1
  77. package/src/assets/web-panel/assets/{SpeechSettings-Dexb1f_6.js → SpeechSettings-3IPU0HBZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{SyncSettings-BJsSFGYy.js → SyncSettings-DBNTQLbM.js} +2 -2
  79. package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-quP0bkN-.js} +1 -1
  80. package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-BouWmgho.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-qv0D9s7z.js} +1 -1
  82. package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-BxvbE8BB.js} +2 -2
  83. package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
  84. package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-CFMKKyVG.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-L8as3UuJ.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-MxmA4VZG.js} +1 -1
  87. package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-C0VvJYRi.js} +1 -1
  88. package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-Bf5QZj8N.js} +1 -1
  89. package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-BIJ9riRz.js} +4 -4
  90. package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-B8p9Mhkw.js} +4 -4
  91. package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-BVsuaPen.js} +1 -1
  92. package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BfkjoKpV.js} +1 -1
  93. package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-DeAlsWK8.js} +1 -1
  94. package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-Cs97bz5j.js} +1 -1
  95. package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-BqbG60LH.js} +1 -1
  96. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
  97. package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-i_RY0b1X.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-0-agYlWL.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-79V_zv1s.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-B-3tEpTc.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-B-JVTrpr.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-B1CbOH1i.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-B5UkyA3c.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-BESLFt4q.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-BOVkf1K3.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-BYUVuwKt.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-BcUXAB2s.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-BkBi5TmC.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-Bm7lLR4c.js} +1 -1
  110. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
  111. package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-BuLQobzW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CE2VtTlp.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-CED2PL_y.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-CG3PRyAL.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-togc_uYn.js → index-CIG8fQ2_.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-COdGA-8n.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-0Auljb10.js → index-CeWoNHau.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Cd0hmPxD.js → index-Cf3WCLmy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-CgsqdnRV.js} +1 -1
  120. package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
  121. package/src/assets/web-panel/assets/{index-h31MwrME.js → index-D2B4VPyP.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C1oZReaY.js → index-D9evtvg2.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DUUrv141.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-DiSkx4UB.js} +3 -3
  125. package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-Dk2qouAz.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-Dk7-v4hP.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-DmbfhJUP.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-DqBgBpgp.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-PT_5sBMA.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-U4f5izHz.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-YXgLng0K.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-ZxO_-E8a.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-dIHr4B_u.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-gyFpvL7l.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-s1opAL-M.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-yObj1O4n.js} +1 -1
  137. package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-C9h9yKL3.js} +1 -1
  138. package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BvmLsPdu.js} +1 -1
  139. package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-CoL4ekU1.js} +1 -1
  140. package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-C4pMy6xV.js} +1 -1
  141. package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-DPuJfNxx.js} +1 -1
  142. package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-D0KEeaK2.js} +1 -1
  143. package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BpkyINxt.js} +1 -1
  144. package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-DQI_wOq8.js} +1 -1
  145. package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DYfdDiRg.js} +1 -1
  146. package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-B5dtvb0D.js} +1 -1
  147. package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-CF310mSj.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-C7_iyvJK.js} +1 -1
  150. package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
  151. package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-D3dsi0lZ.js} +1 -1
  152. package/src/assets/web-panel/assets/{zoom-DmNrEtgf.js → zoom-WdbOlvdQ.js} +1 -1
  153. package/src/assets/web-panel/index.html +1 -1
  154. package/src/command-help-index.json +5 -5
  155. package/src/command-manifest.json +1 -1
  156. package/src/commands/agenda.js +76 -4
  157. package/src/commands/agent.js +90 -1
  158. package/src/commands/automation.js +462 -3
  159. package/src/commands/background-session.js +102 -1
  160. package/src/commands/chat.js +11 -2
  161. package/src/commands/compact.js +106 -23
  162. package/src/commands/cowork.js +6 -1
  163. package/src/commands/loop.js +187 -38
  164. package/src/commands/routine.js +12 -5
  165. package/src/commands/scheduler-daemon.js +807 -0
  166. package/src/commands/session-observability.js +305 -0
  167. package/src/commands/session.js +38 -0
  168. package/src/data/changelog.json +28 -2
  169. package/src/gateways/ws/ws-agent-handler.js +439 -5
  170. package/src/harness/jsonl-session-store.js +463 -125
  171. package/src/harness/prompt-compressor.js +86 -13
  172. package/src/harness/provider-backed-compaction.js +100 -27
  173. package/src/lib/advisor-runtime.js +3 -1
  174. package/src/lib/agent-schedule-store.js +737 -47
  175. package/src/lib/agent-team/team-adjudication.js +40 -1
  176. package/src/lib/automation-center-routines.js +363 -0
  177. package/src/lib/automation-center.js +435 -0
  178. package/src/lib/automation-engine.js +568 -19
  179. package/src/lib/automation-execution-authority.js +736 -0
  180. package/src/lib/background-agent-supervisor.js +871 -215
  181. package/src/lib/background-command-argv.js +24 -0
  182. package/src/lib/background-interaction-journal.js +361 -52
  183. package/src/lib/background-interaction-resolver.js +32 -0
  184. package/src/lib/background-launch-profile.js +2 -0
  185. package/src/lib/background-needs-input-incident.js +221 -0
  186. package/src/lib/causal-observability.js +2661 -0
  187. package/src/lib/chat-session-admission.js +76 -0
  188. package/src/lib/cost-budget.js +5 -7
  189. package/src/lib/cowork/ab-comparator-cli.js +11 -0
  190. package/src/lib/cowork/debate-review-cli.js +12 -0
  191. package/src/lib/cowork-adapter.js +159 -38
  192. package/src/lib/cowork-cron.js +673 -19
  193. package/src/lib/cowork-observe.js +5 -1
  194. package/src/lib/cowork-task-runner.js +112 -17
  195. package/src/lib/delivery-coordinator.js +142 -0
  196. package/src/lib/direct-model-usage.js +121 -0
  197. package/src/lib/file-lines.js +183 -47
  198. package/src/lib/llm-pricing.js +12 -17
  199. package/src/lib/loop.js +62 -3
  200. package/src/lib/permission-engine.js +14 -7
  201. package/src/lib/public-ide-capability-manifest.js +2 -0
  202. package/src/lib/routine-store.js +546 -86
  203. package/src/lib/runtime-usage-ledger.js +182 -0
  204. package/src/lib/scheduler-kernel/agenda-adapter.js +604 -29
  205. package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
  206. package/src/lib/scheduler-kernel/automation-adapter.js +994 -0
  207. package/src/lib/scheduler-kernel/automation-event-adapter.js +757 -0
  208. package/src/lib/scheduler-kernel/contract.js +11 -3
  209. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +343 -15
  210. package/src/lib/scheduler-kernel/loop-adapter.js +913 -0
  211. package/src/lib/scheduler-kernel/migration-admin.js +346 -0
  212. package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
  213. package/src/lib/scheduler-kernel/routine-adapter.js +691 -22
  214. package/src/lib/scheduler-kernel/runtime.js +63 -1
  215. package/src/lib/scheduler-kernel/service.js +328 -0
  216. package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
  217. package/src/lib/scheduler-kernel/store.js +3439 -206
  218. package/src/lib/session-host-lease.js +171 -2
  219. package/src/lib/session-host-snapshot.js +14 -3
  220. package/src/lib/session-persistence-failure.js +1 -0
  221. package/src/lib/session-tail.js +179 -34
  222. package/src/lib/session-transcript-structure.js +96 -0
  223. package/src/lib/sub-agent-context.js +328 -22
  224. package/src/lib/ws-agent-handler.js +4 -1
  225. package/src/repl/agent-repl.js +732 -91
  226. package/src/repl/chat-repl.js +5 -1
  227. package/src/runtime/agent-core.js +1274 -208
  228. package/src/runtime/coding-agent-contract-shared.cjs +5 -0
  229. package/src/runtime/fallback-model.js +5 -0
  230. package/src/runtime/headless-runner.js +324 -82
  231. package/src/runtime/headless-stream.js +232 -26
  232. package/src/runtime/policies/agent-policy.js +4 -0
  233. package/src/workers/background-agent-worker.js +468 -98
  234. package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
  235. package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
  236. package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
@@ -39,6 +39,11 @@ import {
39
39
  writeFileSync,
40
40
  } from "node:fs";
41
41
  import { join } from "node:path";
42
+ import {
43
+ nextCronTime as nextAgentCronTime,
44
+ normalizeTimeZone,
45
+ previousCronTime as previousAgentCronTime,
46
+ } from "./agent-schedule-store.js";
42
47
  import { withFileLock } from "./with-file-lock.js";
43
48
 
44
49
  export const _deps = {
@@ -55,6 +60,7 @@ export const _deps = {
55
60
 
56
61
  export const COWORK_SCHEDULER_UNKNOWN_LEASE_EXPIRES_AT =
57
62
  "9999-12-31T23:59:59.999Z";
63
+ export const COWORK_SCHEDULER_MIGRATION_SCHEMA_VERSION = 1;
58
64
 
59
65
  function coworkSchedulerError(
60
66
  code,
@@ -80,6 +86,81 @@ function normalizedDate(value, label) {
80
86
  return date;
81
87
  }
82
88
 
89
+ function stableJsonValue(value) {
90
+ if (Array.isArray(value)) return value.map(stableJsonValue);
91
+ if (value && typeof value === "object") {
92
+ return Object.fromEntries(
93
+ Object.keys(value)
94
+ .sort()
95
+ .map((key) => [key, stableJsonValue(value[key])]),
96
+ );
97
+ }
98
+ return value;
99
+ }
100
+
101
+ export function coworkCronMigrationSourceSnapshot(schedule) {
102
+ const source = { ...schedule };
103
+ delete source.activeDelivery;
104
+ delete source.deliveryFence;
105
+ delete source.schedulerExecution;
106
+ delete source.schedulerMigration;
107
+ delete source.lastDeliveryId;
108
+ delete source.lastRunAt;
109
+ delete source.lastStatus;
110
+ delete source.nextAt;
111
+ delete source.missedRunPolicy;
112
+ return stableJsonValue(source);
113
+ }
114
+
115
+ export function coworkCronMigrationSourceDigest(schedule) {
116
+ return crypto
117
+ .createHash("sha256")
118
+ .update(
119
+ "chainlesschain.cowork-cron.scheduler-migration-source.v1\0",
120
+ "utf8",
121
+ )
122
+ .update(JSON.stringify(coworkCronMigrationSourceSnapshot(schedule)), "utf8")
123
+ .digest("hex");
124
+ }
125
+
126
+ function migrationField(value, field) {
127
+ if (typeof value !== "string" || value.trim().length === 0) {
128
+ throw coworkSchedulerError(
129
+ "COWORK_SCHEDULER_MIGRATION_INVALID",
130
+ `Cowork scheduler migration ${field} is required`,
131
+ );
132
+ }
133
+ return value.trim();
134
+ }
135
+
136
+ function coworkMigrationFence(schedule, nowMs) {
137
+ const migration = schedule.schedulerMigration;
138
+ const expiresAt = Date.parse(schedule.activeDelivery?.leaseExpiresAt || "");
139
+ return (
140
+ migration?.schemaVersion === COWORK_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
141
+ typeof migration.retirementToken === "string" &&
142
+ schedule.activeDelivery?.ownerId ===
143
+ `scheduler-migration:${migration.retirementToken}` &&
144
+ Number.isFinite(expiresAt) &&
145
+ expiresAt > nowMs
146
+ );
147
+ }
148
+
149
+ function applyCoworkMigrationFence(schedule, now) {
150
+ const token = schedule.schedulerMigration?.retirementToken;
151
+ if (typeof token !== "string" || token.length === 0) return false;
152
+ const fence = Math.max(Number(schedule.deliveryFence) || 0, 0) + 1;
153
+ schedule.deliveryFence = fence;
154
+ schedule.activeDelivery = {
155
+ deliveryId: `scheduler-migration:${token}`,
156
+ ownerId: `scheduler-migration:${token}`,
157
+ fence,
158
+ claimedAt: now.toISOString(),
159
+ leaseExpiresAt: COWORK_SCHEDULER_UNKNOWN_LEASE_EXPIRES_AT,
160
+ };
161
+ return true;
162
+ }
163
+
83
164
  // ─── Cron parser ─────────────────────────────────────────────────────────────
84
165
 
85
166
  const FIELD_RANGES = [
@@ -91,6 +172,56 @@ const FIELD_RANGES = [
91
172
  ];
92
173
 
93
174
  const SECOND_RANGE = [0, 59];
175
+ const CRON_TIME_ZONE_FORMATTERS = new Map();
176
+
177
+ function timeZoneFormatter(timeZone) {
178
+ let formatter = CRON_TIME_ZONE_FORMATTERS.get(timeZone);
179
+ if (!formatter) {
180
+ formatter = new Intl.DateTimeFormat("en-US-u-ca-gregory-nu-latn", {
181
+ timeZone,
182
+ year: "numeric",
183
+ month: "2-digit",
184
+ day: "2-digit",
185
+ hour: "2-digit",
186
+ minute: "2-digit",
187
+ second: "2-digit",
188
+ hourCycle: "h23",
189
+ });
190
+ CRON_TIME_ZONE_FORMATTERS.set(timeZone, formatter);
191
+ }
192
+ return formatter;
193
+ }
194
+
195
+ function timeZoneParts(epochMs, timeZone) {
196
+ const values = {};
197
+ for (const part of timeZoneFormatter(timeZone).formatToParts(epochMs)) {
198
+ if (part.type !== "literal") values[part.type] = Number(part.value);
199
+ }
200
+ return {
201
+ year: values.year,
202
+ month: values.month,
203
+ day: values.day,
204
+ hour: values.hour,
205
+ minute: values.minute,
206
+ second: values.second,
207
+ dayOfWeek: new Date(
208
+ Date.UTC(values.year, values.month - 1, values.day),
209
+ ).getUTCDay(),
210
+ };
211
+ }
212
+
213
+ function cronDateParts(date, timeZone) {
214
+ if (timeZone) return timeZoneParts(date.getTime(), timeZone);
215
+ return {
216
+ year: date.getFullYear(),
217
+ month: date.getMonth() + 1,
218
+ day: date.getDate(),
219
+ hour: date.getHours(),
220
+ minute: date.getMinutes(),
221
+ second: date.getSeconds(),
222
+ dayOfWeek: date.getDay(),
223
+ };
224
+ }
94
225
 
95
226
  /**
96
227
  * Non-standard alias → 5-field expression. Aliases never carry seconds.
@@ -182,7 +313,7 @@ export function parseCronField(field, [min, max]) {
182
313
  * @param {string} expr
183
314
  * @returns {((date: Date) => boolean) & { hasSeconds: boolean }}
184
315
  */
185
- export function parseCron(expr) {
316
+ export function parseCron(expr, options = {}) {
186
317
  if (typeof expr !== "string") {
187
318
  throw new Error("cron expression must be a string");
188
319
  }
@@ -194,6 +325,9 @@ export function parseCron(expr) {
194
325
  );
195
326
  }
196
327
  const hasSeconds = parts.length === 6;
328
+ const timeZone = normalizeTimeZone(
329
+ typeof options === "string" ? options : options?.timeZone,
330
+ );
197
331
  let second = null;
198
332
  let minute, hour, dom, month, dow;
199
333
  if (hasSeconds) {
@@ -214,12 +348,15 @@ export function parseCron(expr) {
214
348
  const dowRestricted = dowField !== "*";
215
349
 
216
350
  function matches(date) {
217
- const s = date.getSeconds();
218
- const m = date.getMinutes();
219
- const h = date.getHours();
220
- const D = date.getDate();
221
- const M = date.getMonth() + 1; // JS month is 0-based
222
- const W = date.getDay();
351
+ const value = date instanceof Date ? date : new Date(date);
352
+ if (!Number.isFinite(value.getTime())) return false;
353
+ const civil = cronDateParts(value, timeZone);
354
+ const s = civil.second;
355
+ const m = civil.minute;
356
+ const h = civil.hour;
357
+ const D = civil.day;
358
+ const M = civil.month;
359
+ const W = civil.dayOfWeek;
223
360
  if (hasSeconds && !second.has(s)) return false;
224
361
  if (!minute.has(m)) return false;
225
362
  if (!hour.has(h)) return false;
@@ -233,6 +370,10 @@ export function parseCron(expr) {
233
370
  return true;
234
371
  }
235
372
  matches.hasSeconds = hasSeconds;
373
+ matches.timeZone = timeZone;
374
+ matches.fields = { second, minute, hour, dom, month, dow };
375
+ matches.domRestricted = domRestricted;
376
+ matches.dowRestricted = dowRestricted;
236
377
  return matches;
237
378
  }
238
379
 
@@ -246,15 +387,153 @@ export function hasSecondsResolution(expr) {
246
387
  }
247
388
 
248
389
  /** Validate a cron expression — returns null if valid, error string otherwise. */
249
- export function validateCron(expr) {
390
+ export function validateCron(expr, options = {}) {
250
391
  try {
251
- parseCron(expr);
392
+ parseCron(expr, options);
252
393
  return null;
253
394
  } catch (err) {
254
395
  return err.message;
255
396
  }
256
397
  }
257
398
 
399
+ /** Return the next real instant for a Cowork 5/6-field cron. */
400
+ export function nextCoworkCronTime(expr, fromMs, options = {}) {
401
+ const matcher = parseCron(expr, options);
402
+ const from = Number(fromMs);
403
+ if (!Number.isFinite(from)) throw new Error("cron start time is invalid");
404
+ const expanded = _expandExpr(expr);
405
+ const parts = expanded.split(/\s+/);
406
+ const minuteFields = matcher.hasSeconds ? parts.slice(1) : parts;
407
+ const minuteExpressions =
408
+ matcher.domRestricted && matcher.dowRestricted
409
+ ? [
410
+ [
411
+ minuteFields[0],
412
+ minuteFields[1],
413
+ minuteFields[2],
414
+ minuteFields[3],
415
+ "*",
416
+ ].join(" "),
417
+ [
418
+ minuteFields[0],
419
+ minuteFields[1],
420
+ "*",
421
+ minuteFields[3],
422
+ minuteFields[4],
423
+ ].join(" "),
424
+ ]
425
+ : [minuteFields.join(" ")];
426
+ const nextMinute = (after) => {
427
+ const candidates = minuteExpressions
428
+ .map((candidate) =>
429
+ nextAgentCronTime(candidate, after, { timeZone: matcher.timeZone }),
430
+ )
431
+ .filter((candidate) => candidate !== null);
432
+ return candidates.length > 0 ? Math.min(...candidates) : null;
433
+ };
434
+ if (!matcher.hasSeconds) return nextMinute(from);
435
+
436
+ const seconds = [...matcher.fields.second].sort((a, b) => a - b);
437
+ let minute = nextMinute(Math.floor(from / 60_000) * 60_000 - 60_000);
438
+ while (minute !== null) {
439
+ for (const second of seconds) {
440
+ const candidate = minute + second * 1_000;
441
+ if (candidate > from) return candidate;
442
+ }
443
+ minute = nextMinute(minute);
444
+ }
445
+ return null;
446
+ }
447
+
448
+ /**
449
+ * Select the latest due instant in `[firstMs, throughMs]`. Six-field schedules
450
+ * advance by matching civil minutes, not every elapsed second, so a long
451
+ * outage cannot turn an every-second schedule into millions of iterations.
452
+ */
453
+ export function latestCoworkCronTime(expr, firstMs, throughMs, options = {}) {
454
+ const first = Number(firstMs);
455
+ const through = Number(throughMs);
456
+ if (!Number.isSafeInteger(first) || !Number.isSafeInteger(through)) {
457
+ throw new Error("cron catch-up bounds are invalid");
458
+ }
459
+ if (first > through) return null;
460
+ const matcher = parseCron(expr, options);
461
+ const seconds = matcher.hasSeconds
462
+ ? [...matcher.fields.second].sort((a, b) => b - a)
463
+ : [0];
464
+ const expanded = _expandExpr(expr);
465
+ const fields = expanded.split(/\s+/);
466
+ const minuteFields = matcher.hasSeconds ? fields.slice(1) : fields;
467
+ const minuteExpressions =
468
+ matcher.domRestricted && matcher.dowRestricted
469
+ ? [
470
+ [
471
+ minuteFields[0],
472
+ minuteFields[1],
473
+ minuteFields[2],
474
+ minuteFields[3],
475
+ "*",
476
+ ].join(" "),
477
+ [
478
+ minuteFields[0],
479
+ minuteFields[1],
480
+ "*",
481
+ minuteFields[3],
482
+ minuteFields[4],
483
+ ].join(" "),
484
+ ]
485
+ : [minuteFields.join(" ")];
486
+
487
+ const throughMinute = Math.floor(through / 60_000) * 60_000;
488
+ for (const second of seconds) {
489
+ const candidate = throughMinute + second * 1_000;
490
+ if (
491
+ candidate >= first &&
492
+ candidate <= through &&
493
+ matcher(new Date(candidate))
494
+ ) {
495
+ return candidate;
496
+ }
497
+ }
498
+
499
+ const previousMinute = Math.max(
500
+ ...minuteExpressions
501
+ .map((candidate) =>
502
+ previousAgentCronTime(candidate, throughMinute, {
503
+ timeZone: matcher.timeZone,
504
+ }),
505
+ )
506
+ .filter((candidate) => candidate !== null),
507
+ );
508
+ if (Number.isFinite(previousMinute)) {
509
+ for (const second of seconds) {
510
+ const candidate = previousMinute + second * 1_000;
511
+ if (candidate >= first && candidate <= through) return candidate;
512
+ }
513
+ }
514
+ return first;
515
+ }
516
+
517
+ function currentCoworkCronTime(schedule, at) {
518
+ const date = normalizedDate(at, "cowork cron observation time");
519
+ const matcher = parseCron(schedule.cron, { timeZone: schedule.timeZone });
520
+ if (!matcher(date)) return null;
521
+ const resolution = matcher.hasSeconds ? 1_000 : 60_000;
522
+ return Math.floor(date.getTime() / resolution) * resolution;
523
+ }
524
+
525
+ function initialCoworkCronNextAt(schedule, at) {
526
+ const current = currentCoworkCronTime(schedule, at);
527
+ if (current !== null) return current;
528
+ return nextCoworkCronTime(
529
+ schedule.cron,
530
+ normalizedDate(at, "now").getTime(),
531
+ {
532
+ timeZone: schedule.timeZone,
533
+ },
534
+ );
535
+ }
536
+
258
537
  // ─── Persistence ─────────────────────────────────────────────────────────────
259
538
 
260
539
  function _scheduleFile(cwd) {
@@ -326,12 +605,20 @@ export function saveSchedules(cwd, schedules) {
326
605
  // ─── CRUD ────────────────────────────────────────────────────────────────────
327
606
 
328
607
  export function addSchedule(cwd, input) {
329
- const { cron, templateId = null, userMessage, files = [] } = input || {};
608
+ const {
609
+ cron,
610
+ timeZone = null,
611
+ templateId = null,
612
+ userMessage,
613
+ files = [],
614
+ } = input || {};
330
615
  if (!userMessage || typeof userMessage !== "string") {
331
616
  throw new Error("userMessage is required");
332
617
  }
333
- const err = validateCron(cron);
618
+ const normalizedTimeZone = normalizeTimeZone(timeZone);
619
+ const err = validateCron(cron, { timeZone: normalizedTimeZone });
334
620
  if (err) throw new Error(`invalid cron: ${err}`);
621
+ const createdAt = _deps.now();
335
622
 
336
623
  // Serialize the read-modify-write across processes (cc+cc / cc+desktop share
337
624
  // the cowork dir) so concurrently-added schedules don't clobber each other.
@@ -340,11 +627,17 @@ export function addSchedule(cwd, input) {
340
627
  const entry = {
341
628
  id: `sch-${crypto.randomUUID().slice(0, 12)}`,
342
629
  cron: cron.trim(),
630
+ ...(normalizedTimeZone ? { timeZone: normalizedTimeZone } : {}),
343
631
  templateId,
344
632
  userMessage,
345
633
  files: Array.isArray(files) ? files : [],
346
634
  enabled: true,
347
- createdAt: _deps.now().toISOString(),
635
+ createdAt: createdAt.toISOString(),
636
+ nextAt: initialCoworkCronNextAt(
637
+ { cron: cron.trim(), timeZone: normalizedTimeZone },
638
+ createdAt,
639
+ ),
640
+ missedRunPolicy: "collapse",
348
641
  lastRunAt: null,
349
642
  lastStatus: null,
350
643
  };
@@ -354,6 +647,73 @@ export function addSchedule(cwd, input) {
354
647
  });
355
648
  }
356
649
 
650
+ /**
651
+ * Upgrade a legacy schedule to durable next-fire state. Upgrade never replays
652
+ * pre-upgrade history: a matching current window is retained, otherwise the
653
+ * first future occurrence becomes authoritative.
654
+ */
655
+ export function ensureScheduleNextAt(cwd, id, at = _deps.now()) {
656
+ return _withScheduleLock(cwd, () => {
657
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
658
+ const schedule = schedules.find((entry) => entry.id === id);
659
+ if (!schedule) return null;
660
+ if (Object.hasOwn(schedule, "nextAt")) {
661
+ if (
662
+ schedule.nextAt === null ||
663
+ (typeof schedule.nextAt === "number" &&
664
+ Number.isSafeInteger(schedule.nextAt) &&
665
+ schedule.nextAt >= 0)
666
+ ) {
667
+ return schedule;
668
+ }
669
+ throw coworkSchedulerError(
670
+ "COWORK_SCHEDULER_CURSOR_INVALID",
671
+ `Cowork schedule nextAt is invalid: ${id}`,
672
+ );
673
+ }
674
+ const lastRunAt = Date.parse(schedule.lastRunAt || "");
675
+ const observedAt = normalizedDate(at, "cowork cron migration time");
676
+ schedule.nextAt = Number.isSafeInteger(lastRunAt)
677
+ ? nextCoworkCronTime(schedule.cron, lastRunAt, {
678
+ timeZone: schedule.timeZone,
679
+ })
680
+ : initialCoworkCronNextAt(schedule, observedAt);
681
+ schedule.missedRunPolicy = "collapse";
682
+ _saveSchedulesUnlocked(cwd, schedules);
683
+ return schedule;
684
+ });
685
+ }
686
+
687
+ /**
688
+ * CAS-advance one due schedule beyond `from`, collapsing any number of missed
689
+ * periods to the single occurrence that was just durably enqueued.
690
+ */
691
+ export function advanceScheduleNextAt(
692
+ cwd,
693
+ id,
694
+ { expectedNextAt, from = _deps.now() } = {},
695
+ ) {
696
+ return _withScheduleLock(cwd, () => {
697
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
698
+ const schedule = schedules.find((entry) => entry.id === id);
699
+ if (!schedule) return null;
700
+ if (
701
+ typeof schedule.nextAt !== "number" ||
702
+ schedule.nextAt !== Number(expectedNextAt)
703
+ ) {
704
+ return schedule;
705
+ }
706
+ schedule.nextAt = nextCoworkCronTime(
707
+ schedule.cron,
708
+ normalizedDate(from, "cowork cron advance time").getTime(),
709
+ { timeZone: schedule.timeZone },
710
+ );
711
+ schedule.missedRunPolicy = "collapse";
712
+ _saveSchedulesUnlocked(cwd, schedules);
713
+ return schedule;
714
+ });
715
+ }
716
+
357
717
  export function removeSchedule(cwd, id) {
358
718
  return _withScheduleLock(cwd, () => {
359
719
  const schedules = loadSchedules(cwd, { failOnMalformed: true });
@@ -370,7 +730,12 @@ export function setScheduleEnabled(cwd, id, enabled) {
370
730
  const schedules = loadSchedules(cwd, { failOnMalformed: true });
371
731
  const s = schedules.find((x) => x.id === id);
372
732
  if (!s) return false;
373
- s.enabled = !!enabled;
733
+ const nextEnabled = !!enabled;
734
+ if (nextEnabled && !s.enabled) {
735
+ s.nextAt = initialCoworkCronNextAt(s, _deps.now());
736
+ s.missedRunPolicy = "collapse";
737
+ }
738
+ s.enabled = nextEnabled;
374
739
  _saveSchedulesUnlocked(cwd, schedules);
375
740
  return true;
376
741
  });
@@ -398,6 +763,205 @@ export function getSchedule(cwd, id) {
398
763
  );
399
764
  }
400
765
 
766
+ export function prepareCoworkSchedulerMigration(
767
+ cwd,
768
+ id,
769
+ { migrationId, sourceDigest, targetJobId } = {},
770
+ ) {
771
+ const migration = migrationField(migrationId, "migrationId");
772
+ const digest = migrationField(sourceDigest, "sourceDigest");
773
+ const target = migrationField(targetJobId, "targetJobId");
774
+ return _withScheduleLock(cwd, () => {
775
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
776
+ const schedule = schedules.find((entry) => entry.id === id);
777
+ if (!schedule) {
778
+ throw coworkSchedulerError(
779
+ "COWORK_SCHEDULER_SCHEDULE_NOT_FOUND",
780
+ `Cowork schedule does not exist: ${id}`,
781
+ );
782
+ }
783
+ if (schedule.enabled !== true) {
784
+ throw coworkSchedulerError(
785
+ "COWORK_SCHEDULER_SCHEDULE_DISABLED",
786
+ `Cowork schedule is disabled: ${id}`,
787
+ );
788
+ }
789
+ if (coworkCronMigrationSourceDigest(schedule) !== digest) {
790
+ throw coworkSchedulerError(
791
+ "COWORK_SCHEDULER_MIGRATION_SOURCE_CHANGED",
792
+ `Cowork schedule changed before migration: ${id}`,
793
+ );
794
+ }
795
+ const prior = schedule.schedulerMigration;
796
+ if (prior) {
797
+ if (
798
+ prior.schemaVersion !== COWORK_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
799
+ prior.migrationId !== migration ||
800
+ prior.sourceDigest !== digest ||
801
+ prior.targetJobId !== target ||
802
+ prior.state !== "prepared"
803
+ ) {
804
+ throw coworkSchedulerError(
805
+ "COWORK_SCHEDULER_MIGRATION_CONFLICT",
806
+ `Cowork schedule already belongs to another migration: ${id}`,
807
+ );
808
+ }
809
+ return schedule;
810
+ }
811
+ schedule.schedulerMigration = {
812
+ schemaVersion: COWORK_SCHEDULER_MIGRATION_SCHEMA_VERSION,
813
+ state: "prepared",
814
+ migrationId: migration,
815
+ sourceDigest: digest,
816
+ targetJobId: target,
817
+ preparedAt: _deps.now().toISOString(),
818
+ };
819
+ _saveSchedulesUnlocked(cwd, schedules);
820
+ return schedule;
821
+ });
822
+ }
823
+
824
+ export function retireCoworkSchedulerMigration(
825
+ cwd,
826
+ id,
827
+ {
828
+ migrationId,
829
+ sourceDigest,
830
+ targetJobId,
831
+ retirementToken,
832
+ at = _deps.now(),
833
+ } = {},
834
+ ) {
835
+ const migration = migrationField(migrationId, "migrationId");
836
+ const digest = migrationField(sourceDigest, "sourceDigest");
837
+ const target = migrationField(targetJobId, "targetJobId");
838
+ const token = migrationField(retirementToken, "retirementToken");
839
+ const now = normalizedDate(at, "cowork scheduler migration retirement");
840
+ return _withScheduleLock(cwd, () => {
841
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
842
+ const schedule = schedules.find((entry) => entry.id === id);
843
+ if (!schedule) {
844
+ throw coworkSchedulerError(
845
+ "COWORK_SCHEDULER_SCHEDULE_NOT_FOUND",
846
+ `Cowork schedule does not exist: ${id}`,
847
+ );
848
+ }
849
+ const prior = schedule.schedulerMigration;
850
+ if (
851
+ prior?.schemaVersion !== COWORK_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
852
+ prior.migrationId !== migration ||
853
+ prior.sourceDigest !== digest ||
854
+ prior.targetJobId !== target ||
855
+ !["prepared", "retired"].includes(prior.state)
856
+ ) {
857
+ throw coworkSchedulerError(
858
+ "COWORK_SCHEDULER_MIGRATION_CONFLICT",
859
+ `Cowork migration preparation changed before retirement: ${id}`,
860
+ );
861
+ }
862
+ if (prior.state === "retired") {
863
+ if (
864
+ prior.retirementToken !== token ||
865
+ !coworkMigrationFence(schedule, now.getTime())
866
+ ) {
867
+ throw coworkSchedulerError(
868
+ "COWORK_SCHEDULER_MIGRATION_CONFLICT",
869
+ `Cowork migration retirement fence changed: ${id}`,
870
+ );
871
+ }
872
+ return schedule;
873
+ }
874
+ if (coworkCronMigrationSourceDigest(schedule) !== digest) {
875
+ throw coworkSchedulerError(
876
+ "COWORK_SCHEDULER_MIGRATION_SOURCE_CHANGED",
877
+ `Cowork schedule changed before retirement: ${id}`,
878
+ );
879
+ }
880
+ const activeExpiresAt = Date.parse(
881
+ schedule.activeDelivery?.leaseExpiresAt || "",
882
+ );
883
+ if (
884
+ schedule.activeDelivery &&
885
+ Number.isFinite(activeExpiresAt) &&
886
+ activeExpiresAt > now.getTime()
887
+ ) {
888
+ throw coworkSchedulerError(
889
+ "COWORK_SCHEDULER_LEGACY_CLAIM_ACTIVE",
890
+ `Cowork schedule is already claimed: ${id}`,
891
+ { retryable: true, retryAt: activeExpiresAt },
892
+ );
893
+ }
894
+ schedule.schedulerMigration = {
895
+ ...prior,
896
+ state: "retired",
897
+ retirementToken: token,
898
+ retiredAt: now.toISOString(),
899
+ };
900
+ applyCoworkMigrationFence(schedule, now);
901
+ _saveSchedulesUnlocked(cwd, schedules);
902
+ return schedule;
903
+ });
904
+ }
905
+
906
+ export function restoreCoworkSchedulerMigration(
907
+ cwd,
908
+ id,
909
+ { migrationId, sourceDigest, targetJobId, retirementToken } = {},
910
+ ) {
911
+ const migration = migrationField(migrationId, "migrationId");
912
+ const expectedDigest = migrationField(sourceDigest, "sourceDigest");
913
+ const target = migrationField(targetJobId, "targetJobId");
914
+ const token =
915
+ retirementToken === null || retirementToken === undefined
916
+ ? null
917
+ : migrationField(retirementToken, "retirementToken");
918
+ return _withScheduleLock(cwd, () => {
919
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
920
+ const schedule = schedules.find((entry) => entry.id === id);
921
+ if (!schedule) {
922
+ throw coworkSchedulerError(
923
+ "COWORK_SCHEDULER_SCHEDULE_NOT_FOUND",
924
+ `Cowork schedule does not exist: ${id}`,
925
+ );
926
+ }
927
+ const current = schedule.schedulerMigration;
928
+ if (!current) {
929
+ if (
930
+ coworkCronMigrationSourceDigest(schedule) !== expectedDigest ||
931
+ String(schedule.activeDelivery?.ownerId || "").startsWith(
932
+ "scheduler-migration:",
933
+ )
934
+ ) {
935
+ throw coworkSchedulerError(
936
+ "COWORK_SCHEDULER_MIGRATION_CONFLICT",
937
+ `Cowork source changed after rollback restoration: ${id}`,
938
+ );
939
+ }
940
+ return schedule;
941
+ }
942
+ if (
943
+ current?.schemaVersion !== COWORK_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
944
+ current.migrationId !== migration ||
945
+ current.sourceDigest !== expectedDigest ||
946
+ current.targetJobId !== target ||
947
+ !["prepared", "retired"].includes(current.state) ||
948
+ coworkCronMigrationSourceDigest(schedule) !== expectedDigest ||
949
+ (current.state === "retired" &&
950
+ (current.retirementToken !== token ||
951
+ schedule.activeDelivery?.ownerId !== `scheduler-migration:${token}`))
952
+ ) {
953
+ throw coworkSchedulerError(
954
+ "COWORK_SCHEDULER_MIGRATION_CONFLICT",
955
+ `Cowork migration fence changed before rollback: ${id}`,
956
+ );
957
+ }
958
+ delete schedule.schedulerMigration;
959
+ if (current.state === "retired") schedule.activeDelivery = null;
960
+ _saveSchedulesUnlocked(cwd, schedules);
961
+ return schedule;
962
+ });
963
+ }
964
+
401
965
  /**
402
966
  * Bind a scheduler-kernel occurrence before starting the Cowork task.
403
967
  * The same JSONL lock used by the legacy driver closes both old-first and
@@ -456,7 +1020,13 @@ export function bindSchedulerScheduleFire(
456
1020
 
457
1021
  const active = schedule.activeDelivery;
458
1022
  const activeExpiresAt = Date.parse(active?.leaseExpiresAt || "");
459
- if (active && Number.isFinite(activeExpiresAt) && activeExpiresAt > nowMs) {
1023
+ const migrationFence = coworkMigrationFence(schedule, nowMs);
1024
+ if (
1025
+ active &&
1026
+ Number.isFinite(activeExpiresAt) &&
1027
+ activeExpiresAt > nowMs &&
1028
+ !migrationFence
1029
+ ) {
460
1030
  throw coworkSchedulerError(
461
1031
  "COWORK_SCHEDULER_LEGACY_CLAIM_ACTIVE",
462
1032
  `Cowork schedule is already claimed: ${id}`,
@@ -568,6 +1138,81 @@ export function completeSchedulerScheduleFire(
568
1138
  } else {
569
1139
  schedule.activeDelivery = null;
570
1140
  }
1141
+ if (schedule.enabled === true) applyCoworkMigrationFence(schedule, now);
1142
+ _saveSchedulesUnlocked(cwd, schedules);
1143
+ return schedule;
1144
+ });
1145
+ }
1146
+
1147
+ export function adjudicateSchedulerScheduleFire(
1148
+ cwd,
1149
+ id,
1150
+ {
1151
+ deliveryId,
1152
+ occurrenceId,
1153
+ snapshotDigest,
1154
+ attempt,
1155
+ decision,
1156
+ requestId,
1157
+ at = _deps.now(),
1158
+ } = {},
1159
+ ) {
1160
+ if (!["confirmed_applied", "confirmed_not_applied"].includes(decision)) {
1161
+ throw coworkSchedulerError(
1162
+ "COWORK_SCHEDULER_ADJUDICATION_INVALID",
1163
+ "Cowork scheduler adjudication decision is invalid",
1164
+ );
1165
+ }
1166
+ const now = normalizedDate(at, "cowork scheduler adjudication time");
1167
+ return _withScheduleLock(cwd, () => {
1168
+ const schedules = loadSchedules(cwd, { failOnMalformed: true });
1169
+ const schedule = schedules.find((entry) => entry.id === id);
1170
+ if (!schedule) {
1171
+ throw coworkSchedulerError(
1172
+ "COWORK_SCHEDULER_SCHEDULE_NOT_FOUND",
1173
+ `Cowork schedule does not exist: ${id}`,
1174
+ );
1175
+ }
1176
+ const evidence = schedule.schedulerExecution;
1177
+ const targetStatus =
1178
+ decision === "confirmed_applied"
1179
+ ? "adjudicated_applied"
1180
+ : "adjudicated_not_applied";
1181
+ if (
1182
+ evidence?.status === targetStatus &&
1183
+ evidence.adjudication?.requestId === requestId &&
1184
+ evidence.adjudication.decision === decision
1185
+ ) {
1186
+ return schedule;
1187
+ }
1188
+ if (
1189
+ !evidence ||
1190
+ evidence.deliveryId !== deliveryId ||
1191
+ evidence.occurrenceId !== occurrenceId ||
1192
+ evidence.snapshotDigest !== snapshotDigest ||
1193
+ !Number.isSafeInteger(evidence.attempt) ||
1194
+ evidence.attempt < 1 ||
1195
+ evidence.attempt > attempt ||
1196
+ evidence.status !== "running"
1197
+ ) {
1198
+ throw coworkSchedulerError(
1199
+ "COWORK_SCHEDULER_ADJUDICATION_EVIDENCE_MISMATCH",
1200
+ `Cowork scheduler adjudication evidence does not match: ${id}`,
1201
+ );
1202
+ }
1203
+ schedule.schedulerExecution = {
1204
+ ...evidence,
1205
+ status: targetStatus,
1206
+ endedAt: now.toISOString(),
1207
+ adjudication: { requestId, decision },
1208
+ };
1209
+ if (decision === "confirmed_applied") {
1210
+ schedule.lastDeliveryId = deliveryId;
1211
+ schedule.lastRunAt = now.toISOString();
1212
+ schedule.lastStatus = "adjudicated-applied";
1213
+ }
1214
+ schedule.activeDelivery = null;
1215
+ if (schedule.enabled === true) applyCoworkMigrationFence(schedule, now);
571
1216
  _saveSchedulesUnlocked(cwd, schedules);
572
1217
  return schedule;
573
1218
  });
@@ -769,7 +1414,7 @@ export class CoworkCronScheduler {
769
1414
  if (!s.enabled) continue;
770
1415
  let matcher;
771
1416
  try {
772
- matcher = parseCron(s.cron);
1417
+ matcher = parseCron(s.cron, { timeZone: s.timeZone });
773
1418
  } catch (err) {
774
1419
  this._emit({ type: "invalid-cron", id: s.id, error: err.message });
775
1420
  continue;
@@ -938,10 +1583,19 @@ export function coworkCronSecondKey(date) {
938
1583
  }
939
1584
 
940
1585
  export function coworkCronFireKey(schedule, date) {
941
- const matcher = parseCron(schedule.cron);
942
- return `${schedule.id}:${
943
- matcher.hasSeconds ? coworkCronSecondKey(date) : coworkCronMinuteKey(date)
944
- }`;
1586
+ const matcher = parseCron(schedule.cron, { timeZone: schedule.timeZone });
1587
+ // Keep the old host-local key while old and new CLIs may share this store.
1588
+ // Zoned schedules need epoch identity so repeated DST wall times stay distinct.
1589
+ if (!matcher.timeZone) {
1590
+ const wallKey = matcher.hasSeconds
1591
+ ? coworkCronSecondKey(date)
1592
+ : coworkCronMinuteKey(date);
1593
+ return `${schedule.id}:${wallKey}`;
1594
+ }
1595
+ const epoch = matcher.hasSeconds
1596
+ ? Math.floor(date.getTime() / 1_000) * 1_000
1597
+ : Math.floor(date.getTime() / 60_000) * 60_000;
1598
+ return `${schedule.id}:${epoch}`;
945
1599
  }
946
1600
 
947
1601
  // =====================================================================