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.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +90 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/loop.js +28 -10
- package/src/commands/routine.js +1 -1
- package/src/commands/scheduler-daemon.js +359 -2
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +439 -5
- package/src/harness/jsonl-session-store.js +463 -125
- package/src/harness/prompt-compressor.js +86 -13
- package/src/harness/provider-backed-compaction.js +100 -27
- package/src/lib/advisor-runtime.js +3 -1
- package/src/lib/agent-schedule-store.js +414 -4
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-engine.js +494 -0
- package/src/lib/background-agent-supervisor.js +793 -215
- package/src/lib/background-command-argv.js +24 -0
- package/src/lib/background-interaction-journal.js +361 -52
- package/src/lib/background-interaction-resolver.js +32 -0
- package/src/lib/background-launch-profile.js +2 -0
- package/src/lib/causal-observability.js +2661 -0
- package/src/lib/chat-session-admission.js +76 -0
- package/src/lib/cost-budget.js +5 -7
- package/src/lib/cowork/ab-comparator-cli.js +11 -0
- package/src/lib/cowork/debate-review-cli.js +12 -0
- package/src/lib/cowork-adapter.js +159 -38
- package/src/lib/cowork-cron.js +357 -1
- package/src/lib/cowork-task-runner.js +112 -17
- package/src/lib/delivery-coordinator.js +142 -0
- package/src/lib/direct-model-usage.js +121 -0
- package/src/lib/file-lines.js +183 -47
- package/src/lib/llm-pricing.js +12 -17
- package/src/lib/loop.js +26 -2
- package/src/lib/routine-store.js +380 -21
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
- package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
- package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
- package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
- package/src/lib/scheduler-kernel/migration-admin.js +346 -0
- package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +2586 -85
- package/src/lib/session-host-lease.js +171 -2
- package/src/lib/session-host-snapshot.js +14 -3
- package/src/lib/session-persistence-failure.js +1 -0
- package/src/lib/session-tail.js +179 -34
- package/src/lib/session-transcript-structure.js +96 -0
- package/src/lib/sub-agent-context.js +328 -22
- package/src/lib/ws-agent-handler.js +4 -1
- package/src/repl/agent-repl.js +732 -91
- package/src/repl/chat-repl.js +5 -1
- package/src/runtime/agent-core.js +1272 -207
- package/src/runtime/fallback-model.js +5 -0
- package/src/runtime/headless-runner.js +324 -82
- package/src/runtime/headless-stream.js +232 -26
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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}`,
|