chainlesschain 0.163.6 → 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 (218) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
  38. package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
  42. package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
  73. package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
  74. package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
  106. package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
  107. package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
  116. package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
  117. package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +2 -2
  151. package/src/commands/agent.js +90 -1
  152. package/src/commands/chat.js +11 -2
  153. package/src/commands/compact.js +106 -23
  154. package/src/commands/loop.js +28 -10
  155. package/src/commands/routine.js +1 -1
  156. package/src/commands/scheduler-daemon.js +359 -2
  157. package/src/commands/session-observability.js +305 -0
  158. package/src/commands/session.js +38 -0
  159. package/src/data/changelog.json +14 -1
  160. package/src/gateways/ws/ws-agent-handler.js +439 -5
  161. package/src/harness/jsonl-session-store.js +463 -125
  162. package/src/harness/prompt-compressor.js +86 -13
  163. package/src/harness/provider-backed-compaction.js +100 -27
  164. package/src/lib/advisor-runtime.js +3 -1
  165. package/src/lib/agent-schedule-store.js +414 -4
  166. package/src/lib/agent-team/team-adjudication.js +40 -1
  167. package/src/lib/automation-engine.js +494 -0
  168. package/src/lib/background-agent-supervisor.js +793 -215
  169. package/src/lib/background-command-argv.js +24 -0
  170. package/src/lib/background-interaction-journal.js +361 -52
  171. package/src/lib/background-interaction-resolver.js +32 -0
  172. package/src/lib/background-launch-profile.js +2 -0
  173. package/src/lib/causal-observability.js +2661 -0
  174. package/src/lib/chat-session-admission.js +76 -0
  175. package/src/lib/cost-budget.js +5 -7
  176. package/src/lib/cowork/ab-comparator-cli.js +11 -0
  177. package/src/lib/cowork/debate-review-cli.js +12 -0
  178. package/src/lib/cowork-adapter.js +159 -38
  179. package/src/lib/cowork-cron.js +357 -1
  180. package/src/lib/cowork-task-runner.js +112 -17
  181. package/src/lib/delivery-coordinator.js +142 -0
  182. package/src/lib/direct-model-usage.js +121 -0
  183. package/src/lib/file-lines.js +183 -47
  184. package/src/lib/llm-pricing.js +12 -17
  185. package/src/lib/loop.js +26 -2
  186. package/src/lib/routine-store.js +380 -21
  187. package/src/lib/runtime-usage-ledger.js +182 -0
  188. package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
  189. package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
  190. package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
  191. package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
  192. package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
  193. package/src/lib/scheduler-kernel/migration-admin.js +346 -0
  194. package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
  195. package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
  196. package/src/lib/scheduler-kernel/runtime.js +63 -1
  197. package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
  198. package/src/lib/scheduler-kernel/store.js +2586 -85
  199. package/src/lib/session-host-lease.js +171 -2
  200. package/src/lib/session-host-snapshot.js +14 -3
  201. package/src/lib/session-persistence-failure.js +1 -0
  202. package/src/lib/session-tail.js +179 -34
  203. package/src/lib/session-transcript-structure.js +96 -0
  204. package/src/lib/sub-agent-context.js +328 -22
  205. package/src/lib/ws-agent-handler.js +4 -1
  206. package/src/repl/agent-repl.js +732 -91
  207. package/src/repl/chat-repl.js +5 -1
  208. package/src/runtime/agent-core.js +1272 -207
  209. package/src/runtime/fallback-model.js +5 -0
  210. package/src/runtime/headless-runner.js +324 -82
  211. package/src/runtime/headless-stream.js +232 -26
  212. package/src/runtime/policies/agent-policy.js +4 -0
  213. package/src/workers/background-agent-worker.js +403 -96
  214. package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
  215. package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
  216. package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
  217. package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
  218. package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
@@ -1,20 +1,29 @@
1
1
  import { createHash } from "node:crypto";
2
- import { resolve } from "node:path";
3
2
  import {
4
3
  SchedulerKernelError,
5
4
  canonicalJson,
6
5
  normalizeEpochMs,
7
6
  normalizeIdentifier,
7
+ normalizeJson,
8
8
  } from "./contract.js";
9
9
  import { SchedulerRuntime } from "./runtime.js";
10
+ import {
11
+ schedulerJobDefinitionDigest,
12
+ schedulerMigrationSourceDigest,
13
+ } from "./store.js";
10
14
  import {
11
15
  bindSchedulerAuthorityPolicy,
12
16
  createSchedulerAuthorityResolver,
13
17
  } from "./authority-resolver.js";
18
+ import {
19
+ canonicalSchedulerSourcePath,
20
+ schedulerSourcePathDirname,
21
+ } from "./source-locator-path.js";
14
22
 
15
23
  export const LOOP_SCHEDULER_KIND = "loop-iteration";
16
24
  export const LOOP_PROCESS_CAPABILITY = "process.execute";
17
25
  export const LOOP_AGENT_CAPABILITY = "agent.execute";
26
+ export const LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION = 1;
18
27
 
19
28
  function loopSchedulerError(
20
29
  code,
@@ -37,8 +46,7 @@ function normalizedCwd(cwd) {
37
46
  "Loop scheduler requires a workspace path",
38
47
  );
39
48
  }
40
- const absolute = resolve(cwd);
41
- return process.platform === "win32" ? absolute.toLowerCase() : absolute;
49
+ return canonicalSchedulerSourcePath(cwd);
42
50
  }
43
51
 
44
52
  function normalizeOperands(operands) {
@@ -83,6 +91,513 @@ export function loopExecutionDigest(definition) {
83
91
  .digest("hex");
84
92
  }
85
93
 
94
+ export function loopMigrationSourceSnapshot({ sessionId, config }) {
95
+ return normalizeJson(
96
+ {
97
+ schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
98
+ sessionId: normalizeIdentifier(sessionId, "loop.sessionId"),
99
+ config: normalizeJson(config, "loop.config"),
100
+ },
101
+ "loopMigration.source",
102
+ );
103
+ }
104
+
105
+ function latestLoopMigrationMarker(events) {
106
+ return [...events]
107
+ .reverse()
108
+ .find((event) => event?.type === "loop_scheduler_migration")?.data;
109
+ }
110
+
111
+ function latestLoopConfig(events) {
112
+ return [...events].reverse().find((event) => event?.type === "loop_config")
113
+ ?.data;
114
+ }
115
+
116
+ function loopMigrationEvidenceMatches(value, migration, entry) {
117
+ return (
118
+ value?.schemaVersion === LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
119
+ value.state === "retired" &&
120
+ value.migrationId === migration.id &&
121
+ typeof entry.retirementToken === "string" &&
122
+ value.retirementToken === entry.retirementToken &&
123
+ value.targetJobId === entry.targetJobId
124
+ );
125
+ }
126
+
127
+ function inspectLoopMigrationSource({ events, sessionId, migration, entry }) {
128
+ const current = latestLoopConfig(events);
129
+ const fence = current?.schedulerMigrationFence;
130
+ const marker = latestLoopMigrationMarker(events);
131
+ const fenceMatches = loopMigrationEvidenceMatches(fence, migration, entry);
132
+ const markerMatches = loopMigrationEvidenceMatches(marker, migration, entry);
133
+ let sourceDigest = null;
134
+ try {
135
+ sourceDigest = schedulerMigrationSourceDigest(
136
+ loopMigrationSourceSnapshot({
137
+ sessionId,
138
+ config: fenceMatches ? fence.originalConfig : current,
139
+ }),
140
+ );
141
+ } catch {
142
+ // A missing or malformed live config is not evidence for binding a durable
143
+ // locator. The caller turns this into the adapter's fail-closed error.
144
+ }
145
+ return {
146
+ sourceDigest,
147
+ hasDurableMigrationEvidence: fenceMatches || markerMatches,
148
+ };
149
+ }
150
+
151
+ function assertLoopMigrationPlanMatches({
152
+ migration,
153
+ entry,
154
+ snapshot,
155
+ desired,
156
+ }) {
157
+ if (
158
+ entry.sourceDigest !== schedulerMigrationSourceDigest(snapshot) ||
159
+ entry.targetJobId !== desired.id ||
160
+ entry.targetJobDigest !== schedulerJobDefinitionDigest(desired)
161
+ ) {
162
+ throw loopSchedulerError(
163
+ "LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
164
+ `Saved loop changed after migration started: ${snapshot.sessionId}`,
165
+ );
166
+ }
167
+ if (["rolling_back", "rolled_back"].includes(migration.state)) {
168
+ throw loopSchedulerError(
169
+ "LOOP_SCHEDULER_MIGRATION_STATE_CONFLICT",
170
+ `Loop migration cannot resume from state ${migration.state}`,
171
+ );
172
+ }
173
+ }
174
+
175
+ function assertLoopRollbackContext(migration, sessionId, expectedEntry) {
176
+ if (
177
+ migration.entries.length !== 1 ||
178
+ migration.entries[0].domain !== "loop-iteration" ||
179
+ migration.entries[0].sourceId !== sessionId
180
+ ) {
181
+ throw loopSchedulerError(
182
+ "LOOP_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
183
+ "Loop rollback cannot operate on this migration",
184
+ );
185
+ }
186
+ const entry = migration.entries[0];
187
+ const locator = entry.sourceLocator;
188
+ let canonicalDirectory = null;
189
+ try {
190
+ canonicalDirectory = canonicalSchedulerSourcePath(locator?.directory, {
191
+ allowRelative: false,
192
+ });
193
+ } catch {
194
+ // Report all absent, malformed, or non-canonical locators through the
195
+ // adapter boundary without exposing the recorded filesystem path.
196
+ }
197
+ if (
198
+ locator?.schemaVersion !== 1 ||
199
+ locator.type !== "jsonl-session" ||
200
+ locator.sessionId !== sessionId ||
201
+ canonicalDirectory === null ||
202
+ canonicalDirectory !== locator.directory ||
203
+ (expectedEntry &&
204
+ (entry.entryId !== expectedEntry.entryId ||
205
+ canonicalJson(entry.sourceLocator) !==
206
+ canonicalJson(expectedEntry.sourceLocator)))
207
+ ) {
208
+ throw loopSchedulerError(
209
+ "LOOP_SCHEDULER_MIGRATION_LOCATOR_MISMATCH",
210
+ "Loop rollback source locator does not match the saved session",
211
+ );
212
+ }
213
+ return entry;
214
+ }
215
+
216
+ function appendLoopConfig({
217
+ sessionId,
218
+ config,
219
+ matches,
220
+ canReplace,
221
+ readEvents,
222
+ appendEventIfHead,
223
+ }) {
224
+ for (let attempt = 0; attempt < 3; attempt += 1) {
225
+ const events = readEvents(sessionId);
226
+ const current = latestLoopConfig(events);
227
+ if (matches(current)) return current;
228
+ if (!canReplace(current)) {
229
+ throw loopSchedulerError(
230
+ "LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
231
+ `Saved loop changed during migration: ${sessionId}`,
232
+ );
233
+ }
234
+ try {
235
+ appendEventIfHead(
236
+ sessionId,
237
+ "loop_config",
238
+ config,
239
+ events[events.length - 1]?.hash || null,
240
+ );
241
+ return latestLoopConfig(readEvents(sessionId));
242
+ } catch (error) {
243
+ if (error?.code !== "SESSION_REVISION_STALE" || attempt === 2) {
244
+ throw error;
245
+ }
246
+ }
247
+ }
248
+ throw loopSchedulerError(
249
+ "LOOP_SCHEDULER_MIGRATION_SESSION_BUSY",
250
+ `Saved loop changed repeatedly during migration: ${sessionId}`,
251
+ );
252
+ }
253
+
254
+ function loopRetirementConfig(snapshot, migration, entry) {
255
+ return {
256
+ // Prior clients resolve this as a Loop with no operands and stop before
257
+ // creating a child process. New clients unwrap originalConfig above.
258
+ execMode: true,
259
+ operands: [],
260
+ dynamic: false,
261
+ every: snapshot.config.every ?? "5m",
262
+ maxIterations: snapshot.config.maxIterations ?? null,
263
+ untilExitZero: false,
264
+ until: null,
265
+ cwd: snapshot.config.cwd ?? null,
266
+ schedulerMigrationFence: {
267
+ schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
268
+ state: "retired",
269
+ migrationId: migration.id,
270
+ retirementToken: entry.retirementToken,
271
+ targetJobId: entry.targetJobId,
272
+ originalConfig: snapshot.config,
273
+ },
274
+ };
275
+ }
276
+
277
+ function appendLoopMigrationMarker({
278
+ sessionId,
279
+ marker,
280
+ readEvents,
281
+ appendEventIfHead,
282
+ }) {
283
+ for (let attempt = 0; attempt < 3; attempt += 1) {
284
+ const events = readEvents(sessionId);
285
+ const current = latestLoopMigrationMarker(events);
286
+ if (
287
+ current?.state === marker.state &&
288
+ current?.migrationId === marker.migrationId &&
289
+ current?.retirementToken === marker.retirementToken &&
290
+ current?.targetJobId === marker.targetJobId
291
+ ) {
292
+ return current;
293
+ }
294
+ try {
295
+ appendEventIfHead(
296
+ sessionId,
297
+ "loop_scheduler_migration",
298
+ marker,
299
+ events[events.length - 1]?.hash || null,
300
+ );
301
+ return latestLoopMigrationMarker(readEvents(sessionId));
302
+ } catch (error) {
303
+ if (error?.code !== "SESSION_REVISION_STALE" || attempt === 2) {
304
+ throw error;
305
+ }
306
+ }
307
+ }
308
+ throw loopSchedulerError(
309
+ "LOOP_SCHEDULER_MIGRATION_SESSION_BUSY",
310
+ `Saved loop changed repeatedly during migration: ${sessionId}`,
311
+ );
312
+ }
313
+
314
+ export function migrateSavedLoopSession({
315
+ schedulerStore,
316
+ sessionId,
317
+ config,
318
+ definition,
319
+ readEvents,
320
+ appendEventIfHead,
321
+ sessionFilePath,
322
+ }) {
323
+ if (
324
+ typeof readEvents !== "function" ||
325
+ typeof appendEventIfHead !== "function"
326
+ ) {
327
+ throw loopSchedulerError(
328
+ "LOOP_SCHEDULER_MIGRATION_SESSION_STORE_REQUIRED",
329
+ "Loop migration requires session read and compare-append functions",
330
+ );
331
+ }
332
+ const snapshot = loopMigrationSourceSnapshot({ sessionId, config });
333
+ const desired = buildLoopSchedulerJob(definition);
334
+ desired.authority = bindSchedulerAuthorityPolicy(
335
+ schedulerStore,
336
+ desired.authority,
337
+ );
338
+ const sourceScope = { store: "jsonl-session", sessionId: snapshot.sessionId };
339
+ const sourceLocator = {
340
+ schemaVersion: 1,
341
+ type: "jsonl-session",
342
+ sessionId: snapshot.sessionId,
343
+ directory:
344
+ typeof sessionFilePath === "function"
345
+ ? schedulerSourcePathDirname(sessionFilePath(snapshot.sessionId))
346
+ : null,
347
+ };
348
+ if (!sourceLocator.directory) {
349
+ throw loopSchedulerError(
350
+ "LOOP_SCHEDULER_MIGRATION_SESSION_LOCATOR_REQUIRED",
351
+ "Loop migration requires the canonical session file path",
352
+ );
353
+ }
354
+ let prepared = schedulerStore.getActiveDomainMigrationBySource({
355
+ domain: "loop-iteration",
356
+ sourceScope,
357
+ sourceId: snapshot.sessionId,
358
+ });
359
+ if (prepared) {
360
+ const migrationEntries = prepared.entries.filter(
361
+ (candidate) =>
362
+ candidate.domain === "loop-iteration" &&
363
+ candidate.sourceId === snapshot.sessionId,
364
+ );
365
+ if (prepared.entries.length !== 1 || migrationEntries.length !== 1) {
366
+ throw loopSchedulerError(
367
+ "LOOP_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
368
+ "Loop session belongs to an invalid mixed-domain migration",
369
+ );
370
+ }
371
+ const [migrationEntry] = migrationEntries;
372
+ assertLoopMigrationPlanMatches({
373
+ migration: prepared,
374
+ entry: migrationEntry,
375
+ snapshot,
376
+ desired,
377
+ });
378
+ const liveSource = inspectLoopMigrationSource({
379
+ events: readEvents(snapshot.sessionId),
380
+ sessionId: snapshot.sessionId,
381
+ migration: prepared,
382
+ entry: migrationEntry,
383
+ });
384
+ if (liveSource.sourceDigest !== migrationEntry.sourceDigest) {
385
+ throw loopSchedulerError(
386
+ "LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
387
+ `Saved loop changed after migration started: ${snapshot.sessionId}`,
388
+ );
389
+ }
390
+ if (
391
+ migrationEntry.sourceLocator === null &&
392
+ !liveSource.hasDurableMigrationEvidence
393
+ ) {
394
+ throw loopSchedulerError(
395
+ "LOOP_SCHEDULER_MIGRATION_LOCATOR_UNPROVEN",
396
+ "Legacy Loop migration has no durable marker proving its session directory",
397
+ );
398
+ }
399
+ schedulerStore.bindDomainMigrationSourceLocator({
400
+ migrationId: prepared.id,
401
+ entryId: migrationEntry.entryId,
402
+ sourceLocator,
403
+ expectedSourceDigest: migrationEntry.sourceDigest,
404
+ expectedTargetJobId: migrationEntry.targetJobId,
405
+ });
406
+ prepared = schedulerStore.getDomainMigration(prepared.id);
407
+ } else {
408
+ prepared = schedulerStore.prepareDomainMigration({
409
+ entries: [
410
+ {
411
+ domain: "loop-iteration",
412
+ sourceId: snapshot.sessionId,
413
+ sourceScope,
414
+ sourceLocator,
415
+ source: snapshot,
416
+ targetJob: desired,
417
+ rollbackStrategy: "disable",
418
+ },
419
+ ],
420
+ });
421
+ }
422
+ if (
423
+ prepared.entries.length !== 1 ||
424
+ prepared.entries[0].domain !== "loop-iteration"
425
+ ) {
426
+ throw loopSchedulerError(
427
+ "LOOP_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
428
+ "Loop session belongs to an invalid mixed-domain migration",
429
+ );
430
+ }
431
+ if (
432
+ prepared.entries[0].sourceDigest !==
433
+ schedulerMigrationSourceDigest(snapshot) ||
434
+ prepared.entries[0].targetJobId !== desired.id ||
435
+ prepared.entries[0].targetJobDigest !==
436
+ schedulerJobDefinitionDigest(desired)
437
+ ) {
438
+ throw loopSchedulerError(
439
+ "LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
440
+ `Saved loop changed after migration started: ${snapshot.sessionId}`,
441
+ );
442
+ }
443
+ let migration = prepared;
444
+ if (["rolling_back", "rolled_back"].includes(migration.state)) {
445
+ throw loopSchedulerError(
446
+ "LOOP_SCHEDULER_MIGRATION_STATE_CONFLICT",
447
+ `Loop migration cannot resume from state ${migration.state}`,
448
+ );
449
+ }
450
+ if (migration.state === "prepared") {
451
+ migration = schedulerStore.applyDomainMigration(migration.id);
452
+ }
453
+ if (migration.state === "applied") {
454
+ migration = schedulerStore.verifyDomainMigration(migration.id, {
455
+ sources: [{ entryId: migration.entries[0].entryId, source: snapshot }],
456
+ });
457
+ }
458
+ const retiring =
459
+ migration.state === "retiring"
460
+ ? migration
461
+ : migration.state === "retired"
462
+ ? migration
463
+ : schedulerStore.beginDomainMigrationRetirement(migration.id);
464
+ const entry = retiring.entries[0];
465
+ appendLoopConfig({
466
+ sessionId: snapshot.sessionId,
467
+ readEvents,
468
+ appendEventIfHead,
469
+ config: loopRetirementConfig(snapshot, migration, entry),
470
+ matches: (current) => {
471
+ const fence = current?.schedulerMigrationFence;
472
+ return (
473
+ fence?.schemaVersion === LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
474
+ fence.state === "retired" &&
475
+ fence.migrationId === migration.id &&
476
+ fence.retirementToken === entry.retirementToken &&
477
+ fence.targetJobId === entry.targetJobId &&
478
+ schedulerMigrationSourceDigest(
479
+ loopMigrationSourceSnapshot({
480
+ sessionId: snapshot.sessionId,
481
+ config: fence.originalConfig,
482
+ }),
483
+ ) === entry.sourceDigest
484
+ );
485
+ },
486
+ canReplace: (current) =>
487
+ !current?.schedulerMigrationFence &&
488
+ schedulerMigrationSourceDigest(
489
+ loopMigrationSourceSnapshot({
490
+ sessionId: snapshot.sessionId,
491
+ config: current,
492
+ }),
493
+ ) === entry.sourceDigest,
494
+ });
495
+ const retiredMarker = appendLoopMigrationMarker({
496
+ sessionId: snapshot.sessionId,
497
+ readEvents,
498
+ appendEventIfHead,
499
+ marker: {
500
+ schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
501
+ state: "retired",
502
+ migrationId: migration.id,
503
+ retirementToken: entry.retirementToken,
504
+ targetJobId: entry.targetJobId,
505
+ compatibility: "legacy-config-fenced",
506
+ },
507
+ });
508
+ schedulerStore.confirmDomainMigrationEntryRetired({
509
+ migrationId: migration.id,
510
+ entryId: entry.entryId,
511
+ retirementToken: entry.retirementToken,
512
+ source: retiredMarker,
513
+ });
514
+ return schedulerStore.getDomainMigration(migration.id);
515
+ }
516
+
517
+ export function rollbackSavedLoopMigration({
518
+ schedulerStore,
519
+ sessionId,
520
+ config,
521
+ migrationId,
522
+ readEvents,
523
+ appendEventIfHead,
524
+ }) {
525
+ const normalizedSessionId = normalizeIdentifier(sessionId, "loop.sessionId");
526
+ const before = schedulerStore.getDomainMigration(migrationId);
527
+ const beforeEntry = assertLoopRollbackContext(before, normalizedSessionId);
528
+ const restoredSnapshot = loopMigrationSourceSnapshot({
529
+ sessionId: normalizedSessionId,
530
+ config,
531
+ });
532
+ if (
533
+ schedulerMigrationSourceDigest(restoredSnapshot) !==
534
+ beforeEntry.sourceDigest
535
+ ) {
536
+ throw loopSchedulerError(
537
+ "LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
538
+ `Saved loop rollback config does not match the migration: ${normalizedSessionId}`,
539
+ );
540
+ }
541
+ if (before.state === "rolled_back") return before;
542
+ const migration = schedulerStore.beginDomainMigrationRollback(migrationId);
543
+ const entry = assertLoopRollbackContext(
544
+ migration,
545
+ normalizedSessionId,
546
+ beforeEntry,
547
+ );
548
+ if (migration.state === "rolled_back") return migration;
549
+ schedulerStore.rollbackDomainMigrationTargets(migrationId);
550
+ schedulerStore.restoreDomainMigrationEntrySource({
551
+ migrationId,
552
+ entryId: entry.entryId,
553
+ retirementToken: entry.retirementToken,
554
+ restoreSource: () => {
555
+ appendLoopConfig({
556
+ sessionId: normalizedSessionId,
557
+ config: restoredSnapshot.config,
558
+ readEvents,
559
+ appendEventIfHead,
560
+ matches: (current) =>
561
+ !current?.schedulerMigrationFence &&
562
+ schedulerMigrationSourceDigest(
563
+ loopMigrationSourceSnapshot({ sessionId, config: current }),
564
+ ) === entry.sourceDigest,
565
+ canReplace: (current) => {
566
+ const fence = current?.schedulerMigrationFence;
567
+ return (
568
+ fence?.schemaVersion === LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
569
+ fence.state === "retired" &&
570
+ fence.migrationId === migrationId &&
571
+ fence.retirementToken === entry.retirementToken &&
572
+ fence.targetJobId === entry.targetJobId &&
573
+ schedulerMigrationSourceDigest(
574
+ loopMigrationSourceSnapshot({
575
+ sessionId,
576
+ config: fence.originalConfig,
577
+ }),
578
+ ) === entry.sourceDigest
579
+ );
580
+ },
581
+ });
582
+ appendLoopMigrationMarker({
583
+ sessionId: normalizedSessionId,
584
+ readEvents,
585
+ appendEventIfHead,
586
+ marker: {
587
+ schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
588
+ state: "rolled_back",
589
+ migrationId,
590
+ retirementToken: entry.retirementToken,
591
+ targetJobId: entry.targetJobId,
592
+ compatibility: "legacy-config-restored",
593
+ },
594
+ });
595
+ return restoredSnapshot;
596
+ },
597
+ });
598
+ return schedulerStore.getDomainMigration(migrationId);
599
+ }
600
+
86
601
  export function loopWorkspaceId(cwd) {
87
602
  return createHash("sha256")
88
603
  .update("chainlesschain.scheduler.loop-workspace.v1\0", "utf8")
@@ -187,7 +702,10 @@ export function enqueueLoopIteration(
187
702
  }
188
703
  const job = syncLoopSchedulerJob(schedulerStore, definition);
189
704
  const scheduled = normalizeEpochMs(scheduledFor, "loop.scheduledFor");
190
- return schedulerStore.enqueueOccurrence({
705
+ // An iteration remains the same logical firing when a saved Loop session is
706
+ // migrated and the target job revision advances. Reuse the durable terminal
707
+ // outcome instead of deriving a second occurrence from the new revision.
708
+ return schedulerStore.enqueueOccurrenceOncePerTrigger({
191
709
  jobId: job.id,
192
710
  scheduledFor: scheduled,
193
711
  availableAt: normalizeEpochMs(availableAt ?? scheduled, "loop.availableAt"),
@@ -267,7 +785,21 @@ export function createLoopSchedulerAdapter({ runIteration } = {}) {
267
785
  const transientResults = new Map();
268
786
  return {
269
787
  kind: LOOP_SCHEDULER_KIND,
270
- async execute({ occurrence, signal }) {
788
+ async adjudicate({ occurrence, adjudication }) {
789
+ if (adjudication.decision === "confirmed_applied") {
790
+ const match = /^iteration:(\d+)$/.exec(occurrence.triggerKey);
791
+ return {
792
+ settled: true,
793
+ result: {
794
+ iteration: match ? Number(match[1]) : null,
795
+ status: "adjudicated-applied",
796
+ adjudicationRequestId: adjudication.requestId,
797
+ },
798
+ };
799
+ }
800
+ return { continue: true };
801
+ },
802
+ async execute({ occurrence, signal, adjudication }) {
271
803
  const definition = loopExecutionSnapshot(occurrence?.payload?.definition);
272
804
  if (
273
805
  occurrence.payload.snapshotDigest !== loopExecutionDigest(definition)
@@ -285,7 +817,10 @@ export function createLoopSchedulerAdapter({ runIteration } = {}) {
285
817
  `Loop occurrence iteration key is invalid: ${occurrence.id}`,
286
818
  );
287
819
  }
288
- if (occurrence.attempt > 1) {
820
+ const authorizedRetry =
821
+ adjudication?.decision === "confirmed_not_applied" &&
822
+ adjudication.expectedAttempt + 1 === occurrence.attempt;
823
+ if (occurrence.attempt > 1 && !authorizedRetry) {
289
824
  throw loopSchedulerError(
290
825
  "LOOP_SCHEDULER_OUTCOME_UNKNOWN",
291
826
  `Loop iteration may already have produced side effects; refusing replay: ${occurrence.id}`,