chainlesschain 0.163.6 → 0.163.8
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 +2 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-CavM7W3k.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-CUvZGabQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-DeBnWnbe.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-DbI5kxvd.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-D93bigKm.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-CBNvfUZa.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-CJLyh_YU.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-D_0yoehD.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-TJxmbObW.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-CE0dto1q.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CZ5-yYiD.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-Ck1RVIVo.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-qpoyf6DK.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-DUyPWjIE.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-D_S1BemS.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-BjxeK-Gc.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-Dd3CEJsu.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-B4QG7-ad.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-BtZnids5.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-CHPfCBFD.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-DT8DCEOK.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-COQab5_U.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-BY1PsQuU.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer-CsuAuaq3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-BqpinD-j.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-D0gkfQFv.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-BqgE-axK.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-D0hJ2KvC.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-CmMB0uPY.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-BnVEI0Dk.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference-YTAMihRP.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-0ax7VHop.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-B9TRcokL.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-yySN5YNM.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-B6CSuD_H.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CjMXvOVU.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-CXRF71U3.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-DQgNp_oS.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-DlPwcCbq.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-6hDTRPe1.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-BlVHU1DS.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-DP9pzEbV.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-Djorh8EV.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-oLZ-JkUg.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-Cg288VH3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-Bb1O3Ofr.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-CBdo-bHs.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-qZdnVumx.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-D4kNfGNQ.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DnlMemM7.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-DHucHFBJ.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-BovlOIPR.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-Deru3CQY.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-Bq5oTTbU.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-BfzxVWAz.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-CKC6XBtH.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CMolGhae.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-C_jx4TWX.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-BdIHeVGC.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-BwZwAcPD.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-BNTt0L-m.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-Biyn2-Ro.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-D6VpG32B.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-D_74OvQQ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-BTr0a-BQ.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-C3MCFoNU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-CqW9aJ6s.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-Cw5GNaHF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DZps5qeY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-C6NQ1znT.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-DFP8G2Sh.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-ESt6MB6v.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-BHnN98aM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-NiSCaOf-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-DsKFhW2C.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-CQ0E0ze5.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-D_rBu_by.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D3Vm4Po4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-DNTxRH6q.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-Dgprz1ms.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-B2rUnVBD.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-DEGQNc6_.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-jlsAx3Eh.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-8jQih1ij.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-XLeO6xIB.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-De_dgDAG.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-CI19R_Sk.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DsYTOYN3.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-CM3-t_Qr.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BjxlBRZv.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-S1ld2LwU.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-RDaWX_4i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-9R6BxAr0.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-B-Z8eFUJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B0PMlbMs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-B5REnKt8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-B7WBt_4T.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-B9O9YyWE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-BCnZyIMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-BGqqJfL0.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-BHNYPO0b.js} +3 -3
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-BKJr-TZp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BN6UPD2E.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-Bi46Xu4u.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-BlT2VAEK.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-C0yx-jtQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-C4gkzxkw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CVQMeiwH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-Cd3euP_4.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-Cd9OJhHG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-CiECJi-Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-CjTrRt13.js} +1 -1
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-CnHH7bnD.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-Cpqm1qfU.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-CyOGN6a5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-D0zcy54x.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-D6sm5-Xq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-DIUnNOjG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-DX7tuWVJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-DXXtGMUJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DcPFB8qJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-DoQA0-52.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-Drlnwr8F.js} +1 -1
- package/src/assets/web-panel/assets/index-Drz6yq0m.js +1 -0
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-Dsij_KsN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-Dxge-xWl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-DyIWssPC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-IeiL9RKc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-nAH87mQP.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-pPL66J85.js} +1 -1
- package/src/assets/web-panel/assets/index-uWiUs7S_.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-BTL88of4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BUB2pXwB.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-egCkkOMm.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-DAWhZC8c.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-CvOxe_6d.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-DZUSLZ5Z.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BERxGNMY.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DZuDfZ9n.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DO756sgI.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-BRQlIz28.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-DCSfdybl.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-LD8dWVve.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-BX9uW2LK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-BPSlPuHU.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-BmOILC9V.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-1iKowKGg.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +5 -5
- package/src/commands/agent.js +90 -1
- package/src/commands/automation.js +103 -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/commands/team-merge-review.js +937 -0
- package/src/commands/team.js +2 -0
- package/src/data/changelog.json +27 -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 +549 -28
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/agent-team/team-merge-review-store.js +765 -0
- package/src/lib/agent-team/team-merge-review-transaction.js +1223 -0
- package/src/lib/agent-team/team-merge-review.js +1847 -0
- package/src/lib/automation-center-incidents.js +314 -0
- package/src/lib/automation-center-runtime.js +448 -0
- package/src/lib/automation-center.js +182 -38
- package/src/lib/automation-engine.js +535 -0
- package/src/lib/automation-execution-authority.js +241 -12
- package/src/lib/automation-execution-incident.js +737 -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 +442 -15
- 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 +325 -10
- package/src/lib/scheduler-kernel/automation-adapter.js +514 -18
- package/src/lib/scheduler-kernel/automation-event-adapter.js +123 -11
- package/src/lib/scheduler-kernel/contract.js +91 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +297 -4
- package/src/lib/scheduler-kernel/loop-adapter.js +547 -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 +400 -20
- package/src/lib/scheduler-kernel/runtime-control-capabilities.js +31 -0
- package/src/lib/scheduler-kernel/runtime.js +237 -2
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +3957 -248
- 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 +1282 -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/mcp-config.js +81 -10
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/MarkdownRenderer-aMtgN-R6.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-DKTf5xIP.js +0 -7
- 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,30 @@
|
|
|
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 { CHECKPOINT_V1_RUNTIME_CONTROL } from "./runtime-control-capabilities.js";
|
|
11
|
+
import {
|
|
12
|
+
schedulerJobDefinitionDigest,
|
|
13
|
+
schedulerMigrationSourceDigest,
|
|
14
|
+
} from "./store.js";
|
|
10
15
|
import {
|
|
11
16
|
bindSchedulerAuthorityPolicy,
|
|
12
17
|
createSchedulerAuthorityResolver,
|
|
13
18
|
} from "./authority-resolver.js";
|
|
19
|
+
import {
|
|
20
|
+
canonicalSchedulerSourcePath,
|
|
21
|
+
schedulerSourcePathDirname,
|
|
22
|
+
} from "./source-locator-path.js";
|
|
14
23
|
|
|
15
24
|
export const LOOP_SCHEDULER_KIND = "loop-iteration";
|
|
16
25
|
export const LOOP_PROCESS_CAPABILITY = "process.execute";
|
|
17
26
|
export const LOOP_AGENT_CAPABILITY = "agent.execute";
|
|
27
|
+
export const LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION = 1;
|
|
18
28
|
|
|
19
29
|
function loopSchedulerError(
|
|
20
30
|
code,
|
|
@@ -37,8 +47,7 @@ function normalizedCwd(cwd) {
|
|
|
37
47
|
"Loop scheduler requires a workspace path",
|
|
38
48
|
);
|
|
39
49
|
}
|
|
40
|
-
|
|
41
|
-
return process.platform === "win32" ? absolute.toLowerCase() : absolute;
|
|
50
|
+
return canonicalSchedulerSourcePath(cwd);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
function normalizeOperands(operands) {
|
|
@@ -83,6 +92,513 @@ export function loopExecutionDigest(definition) {
|
|
|
83
92
|
.digest("hex");
|
|
84
93
|
}
|
|
85
94
|
|
|
95
|
+
export function loopMigrationSourceSnapshot({ sessionId, config }) {
|
|
96
|
+
return normalizeJson(
|
|
97
|
+
{
|
|
98
|
+
schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
|
|
99
|
+
sessionId: normalizeIdentifier(sessionId, "loop.sessionId"),
|
|
100
|
+
config: normalizeJson(config, "loop.config"),
|
|
101
|
+
},
|
|
102
|
+
"loopMigration.source",
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function latestLoopMigrationMarker(events) {
|
|
107
|
+
return [...events]
|
|
108
|
+
.reverse()
|
|
109
|
+
.find((event) => event?.type === "loop_scheduler_migration")?.data;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function latestLoopConfig(events) {
|
|
113
|
+
return [...events].reverse().find((event) => event?.type === "loop_config")
|
|
114
|
+
?.data;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function loopMigrationEvidenceMatches(value, migration, entry) {
|
|
118
|
+
return (
|
|
119
|
+
value?.schemaVersion === LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
|
|
120
|
+
value.state === "retired" &&
|
|
121
|
+
value.migrationId === migration.id &&
|
|
122
|
+
typeof entry.retirementToken === "string" &&
|
|
123
|
+
value.retirementToken === entry.retirementToken &&
|
|
124
|
+
value.targetJobId === entry.targetJobId
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function inspectLoopMigrationSource({ events, sessionId, migration, entry }) {
|
|
129
|
+
const current = latestLoopConfig(events);
|
|
130
|
+
const fence = current?.schedulerMigrationFence;
|
|
131
|
+
const marker = latestLoopMigrationMarker(events);
|
|
132
|
+
const fenceMatches = loopMigrationEvidenceMatches(fence, migration, entry);
|
|
133
|
+
const markerMatches = loopMigrationEvidenceMatches(marker, migration, entry);
|
|
134
|
+
let sourceDigest = null;
|
|
135
|
+
try {
|
|
136
|
+
sourceDigest = schedulerMigrationSourceDigest(
|
|
137
|
+
loopMigrationSourceSnapshot({
|
|
138
|
+
sessionId,
|
|
139
|
+
config: fenceMatches ? fence.originalConfig : current,
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
} catch {
|
|
143
|
+
// A missing or malformed live config is not evidence for binding a durable
|
|
144
|
+
// locator. The caller turns this into the adapter's fail-closed error.
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
sourceDigest,
|
|
148
|
+
hasDurableMigrationEvidence: fenceMatches || markerMatches,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function assertLoopMigrationPlanMatches({
|
|
153
|
+
migration,
|
|
154
|
+
entry,
|
|
155
|
+
snapshot,
|
|
156
|
+
desired,
|
|
157
|
+
}) {
|
|
158
|
+
if (
|
|
159
|
+
entry.sourceDigest !== schedulerMigrationSourceDigest(snapshot) ||
|
|
160
|
+
entry.targetJobId !== desired.id ||
|
|
161
|
+
entry.targetJobDigest !== schedulerJobDefinitionDigest(desired)
|
|
162
|
+
) {
|
|
163
|
+
throw loopSchedulerError(
|
|
164
|
+
"LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
165
|
+
`Saved loop changed after migration started: ${snapshot.sessionId}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (["rolling_back", "rolled_back"].includes(migration.state)) {
|
|
169
|
+
throw loopSchedulerError(
|
|
170
|
+
"LOOP_SCHEDULER_MIGRATION_STATE_CONFLICT",
|
|
171
|
+
`Loop migration cannot resume from state ${migration.state}`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function assertLoopRollbackContext(migration, sessionId, expectedEntry) {
|
|
177
|
+
if (
|
|
178
|
+
migration.entries.length !== 1 ||
|
|
179
|
+
migration.entries[0].domain !== "loop-iteration" ||
|
|
180
|
+
migration.entries[0].sourceId !== sessionId
|
|
181
|
+
) {
|
|
182
|
+
throw loopSchedulerError(
|
|
183
|
+
"LOOP_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
|
|
184
|
+
"Loop rollback cannot operate on this migration",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
const entry = migration.entries[0];
|
|
188
|
+
const locator = entry.sourceLocator;
|
|
189
|
+
let canonicalDirectory = null;
|
|
190
|
+
try {
|
|
191
|
+
canonicalDirectory = canonicalSchedulerSourcePath(locator?.directory, {
|
|
192
|
+
allowRelative: false,
|
|
193
|
+
});
|
|
194
|
+
} catch {
|
|
195
|
+
// Report all absent, malformed, or non-canonical locators through the
|
|
196
|
+
// adapter boundary without exposing the recorded filesystem path.
|
|
197
|
+
}
|
|
198
|
+
if (
|
|
199
|
+
locator?.schemaVersion !== 1 ||
|
|
200
|
+
locator.type !== "jsonl-session" ||
|
|
201
|
+
locator.sessionId !== sessionId ||
|
|
202
|
+
canonicalDirectory === null ||
|
|
203
|
+
canonicalDirectory !== locator.directory ||
|
|
204
|
+
(expectedEntry &&
|
|
205
|
+
(entry.entryId !== expectedEntry.entryId ||
|
|
206
|
+
canonicalJson(entry.sourceLocator) !==
|
|
207
|
+
canonicalJson(expectedEntry.sourceLocator)))
|
|
208
|
+
) {
|
|
209
|
+
throw loopSchedulerError(
|
|
210
|
+
"LOOP_SCHEDULER_MIGRATION_LOCATOR_MISMATCH",
|
|
211
|
+
"Loop rollback source locator does not match the saved session",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return entry;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function appendLoopConfig({
|
|
218
|
+
sessionId,
|
|
219
|
+
config,
|
|
220
|
+
matches,
|
|
221
|
+
canReplace,
|
|
222
|
+
readEvents,
|
|
223
|
+
appendEventIfHead,
|
|
224
|
+
}) {
|
|
225
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
226
|
+
const events = readEvents(sessionId);
|
|
227
|
+
const current = latestLoopConfig(events);
|
|
228
|
+
if (matches(current)) return current;
|
|
229
|
+
if (!canReplace(current)) {
|
|
230
|
+
throw loopSchedulerError(
|
|
231
|
+
"LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
232
|
+
`Saved loop changed during migration: ${sessionId}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
appendEventIfHead(
|
|
237
|
+
sessionId,
|
|
238
|
+
"loop_config",
|
|
239
|
+
config,
|
|
240
|
+
events[events.length - 1]?.hash || null,
|
|
241
|
+
);
|
|
242
|
+
return latestLoopConfig(readEvents(sessionId));
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (error?.code !== "SESSION_REVISION_STALE" || attempt === 2) {
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
throw loopSchedulerError(
|
|
250
|
+
"LOOP_SCHEDULER_MIGRATION_SESSION_BUSY",
|
|
251
|
+
`Saved loop changed repeatedly during migration: ${sessionId}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function loopRetirementConfig(snapshot, migration, entry) {
|
|
256
|
+
return {
|
|
257
|
+
// Prior clients resolve this as a Loop with no operands and stop before
|
|
258
|
+
// creating a child process. New clients unwrap originalConfig above.
|
|
259
|
+
execMode: true,
|
|
260
|
+
operands: [],
|
|
261
|
+
dynamic: false,
|
|
262
|
+
every: snapshot.config.every ?? "5m",
|
|
263
|
+
maxIterations: snapshot.config.maxIterations ?? null,
|
|
264
|
+
untilExitZero: false,
|
|
265
|
+
until: null,
|
|
266
|
+
cwd: snapshot.config.cwd ?? null,
|
|
267
|
+
schedulerMigrationFence: {
|
|
268
|
+
schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
|
|
269
|
+
state: "retired",
|
|
270
|
+
migrationId: migration.id,
|
|
271
|
+
retirementToken: entry.retirementToken,
|
|
272
|
+
targetJobId: entry.targetJobId,
|
|
273
|
+
originalConfig: snapshot.config,
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function appendLoopMigrationMarker({
|
|
279
|
+
sessionId,
|
|
280
|
+
marker,
|
|
281
|
+
readEvents,
|
|
282
|
+
appendEventIfHead,
|
|
283
|
+
}) {
|
|
284
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
285
|
+
const events = readEvents(sessionId);
|
|
286
|
+
const current = latestLoopMigrationMarker(events);
|
|
287
|
+
if (
|
|
288
|
+
current?.state === marker.state &&
|
|
289
|
+
current?.migrationId === marker.migrationId &&
|
|
290
|
+
current?.retirementToken === marker.retirementToken &&
|
|
291
|
+
current?.targetJobId === marker.targetJobId
|
|
292
|
+
) {
|
|
293
|
+
return current;
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
appendEventIfHead(
|
|
297
|
+
sessionId,
|
|
298
|
+
"loop_scheduler_migration",
|
|
299
|
+
marker,
|
|
300
|
+
events[events.length - 1]?.hash || null,
|
|
301
|
+
);
|
|
302
|
+
return latestLoopMigrationMarker(readEvents(sessionId));
|
|
303
|
+
} catch (error) {
|
|
304
|
+
if (error?.code !== "SESSION_REVISION_STALE" || attempt === 2) {
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
throw loopSchedulerError(
|
|
310
|
+
"LOOP_SCHEDULER_MIGRATION_SESSION_BUSY",
|
|
311
|
+
`Saved loop changed repeatedly during migration: ${sessionId}`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export function migrateSavedLoopSession({
|
|
316
|
+
schedulerStore,
|
|
317
|
+
sessionId,
|
|
318
|
+
config,
|
|
319
|
+
definition,
|
|
320
|
+
readEvents,
|
|
321
|
+
appendEventIfHead,
|
|
322
|
+
sessionFilePath,
|
|
323
|
+
}) {
|
|
324
|
+
if (
|
|
325
|
+
typeof readEvents !== "function" ||
|
|
326
|
+
typeof appendEventIfHead !== "function"
|
|
327
|
+
) {
|
|
328
|
+
throw loopSchedulerError(
|
|
329
|
+
"LOOP_SCHEDULER_MIGRATION_SESSION_STORE_REQUIRED",
|
|
330
|
+
"Loop migration requires session read and compare-append functions",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const snapshot = loopMigrationSourceSnapshot({ sessionId, config });
|
|
334
|
+
const desired = buildLoopSchedulerJob(definition);
|
|
335
|
+
desired.authority = bindSchedulerAuthorityPolicy(
|
|
336
|
+
schedulerStore,
|
|
337
|
+
desired.authority,
|
|
338
|
+
);
|
|
339
|
+
const sourceScope = { store: "jsonl-session", sessionId: snapshot.sessionId };
|
|
340
|
+
const sourceLocator = {
|
|
341
|
+
schemaVersion: 1,
|
|
342
|
+
type: "jsonl-session",
|
|
343
|
+
sessionId: snapshot.sessionId,
|
|
344
|
+
directory:
|
|
345
|
+
typeof sessionFilePath === "function"
|
|
346
|
+
? schedulerSourcePathDirname(sessionFilePath(snapshot.sessionId))
|
|
347
|
+
: null,
|
|
348
|
+
};
|
|
349
|
+
if (!sourceLocator.directory) {
|
|
350
|
+
throw loopSchedulerError(
|
|
351
|
+
"LOOP_SCHEDULER_MIGRATION_SESSION_LOCATOR_REQUIRED",
|
|
352
|
+
"Loop migration requires the canonical session file path",
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
let prepared = schedulerStore.getActiveDomainMigrationBySource({
|
|
356
|
+
domain: "loop-iteration",
|
|
357
|
+
sourceScope,
|
|
358
|
+
sourceId: snapshot.sessionId,
|
|
359
|
+
});
|
|
360
|
+
if (prepared) {
|
|
361
|
+
const migrationEntries = prepared.entries.filter(
|
|
362
|
+
(candidate) =>
|
|
363
|
+
candidate.domain === "loop-iteration" &&
|
|
364
|
+
candidate.sourceId === snapshot.sessionId,
|
|
365
|
+
);
|
|
366
|
+
if (prepared.entries.length !== 1 || migrationEntries.length !== 1) {
|
|
367
|
+
throw loopSchedulerError(
|
|
368
|
+
"LOOP_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
|
|
369
|
+
"Loop session belongs to an invalid mixed-domain migration",
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
const [migrationEntry] = migrationEntries;
|
|
373
|
+
assertLoopMigrationPlanMatches({
|
|
374
|
+
migration: prepared,
|
|
375
|
+
entry: migrationEntry,
|
|
376
|
+
snapshot,
|
|
377
|
+
desired,
|
|
378
|
+
});
|
|
379
|
+
const liveSource = inspectLoopMigrationSource({
|
|
380
|
+
events: readEvents(snapshot.sessionId),
|
|
381
|
+
sessionId: snapshot.sessionId,
|
|
382
|
+
migration: prepared,
|
|
383
|
+
entry: migrationEntry,
|
|
384
|
+
});
|
|
385
|
+
if (liveSource.sourceDigest !== migrationEntry.sourceDigest) {
|
|
386
|
+
throw loopSchedulerError(
|
|
387
|
+
"LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
388
|
+
`Saved loop changed after migration started: ${snapshot.sessionId}`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
if (
|
|
392
|
+
migrationEntry.sourceLocator === null &&
|
|
393
|
+
!liveSource.hasDurableMigrationEvidence
|
|
394
|
+
) {
|
|
395
|
+
throw loopSchedulerError(
|
|
396
|
+
"LOOP_SCHEDULER_MIGRATION_LOCATOR_UNPROVEN",
|
|
397
|
+
"Legacy Loop migration has no durable marker proving its session directory",
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
schedulerStore.bindDomainMigrationSourceLocator({
|
|
401
|
+
migrationId: prepared.id,
|
|
402
|
+
entryId: migrationEntry.entryId,
|
|
403
|
+
sourceLocator,
|
|
404
|
+
expectedSourceDigest: migrationEntry.sourceDigest,
|
|
405
|
+
expectedTargetJobId: migrationEntry.targetJobId,
|
|
406
|
+
});
|
|
407
|
+
prepared = schedulerStore.getDomainMigration(prepared.id);
|
|
408
|
+
} else {
|
|
409
|
+
prepared = schedulerStore.prepareDomainMigration({
|
|
410
|
+
entries: [
|
|
411
|
+
{
|
|
412
|
+
domain: "loop-iteration",
|
|
413
|
+
sourceId: snapshot.sessionId,
|
|
414
|
+
sourceScope,
|
|
415
|
+
sourceLocator,
|
|
416
|
+
source: snapshot,
|
|
417
|
+
targetJob: desired,
|
|
418
|
+
rollbackStrategy: "disable",
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
if (
|
|
424
|
+
prepared.entries.length !== 1 ||
|
|
425
|
+
prepared.entries[0].domain !== "loop-iteration"
|
|
426
|
+
) {
|
|
427
|
+
throw loopSchedulerError(
|
|
428
|
+
"LOOP_SCHEDULER_MIGRATION_DOMAIN_MISMATCH",
|
|
429
|
+
"Loop session belongs to an invalid mixed-domain migration",
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
if (
|
|
433
|
+
prepared.entries[0].sourceDigest !==
|
|
434
|
+
schedulerMigrationSourceDigest(snapshot) ||
|
|
435
|
+
prepared.entries[0].targetJobId !== desired.id ||
|
|
436
|
+
prepared.entries[0].targetJobDigest !==
|
|
437
|
+
schedulerJobDefinitionDigest(desired)
|
|
438
|
+
) {
|
|
439
|
+
throw loopSchedulerError(
|
|
440
|
+
"LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
441
|
+
`Saved loop changed after migration started: ${snapshot.sessionId}`,
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
let migration = prepared;
|
|
445
|
+
if (["rolling_back", "rolled_back"].includes(migration.state)) {
|
|
446
|
+
throw loopSchedulerError(
|
|
447
|
+
"LOOP_SCHEDULER_MIGRATION_STATE_CONFLICT",
|
|
448
|
+
`Loop migration cannot resume from state ${migration.state}`,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
if (migration.state === "prepared") {
|
|
452
|
+
migration = schedulerStore.applyDomainMigration(migration.id);
|
|
453
|
+
}
|
|
454
|
+
if (migration.state === "applied") {
|
|
455
|
+
migration = schedulerStore.verifyDomainMigration(migration.id, {
|
|
456
|
+
sources: [{ entryId: migration.entries[0].entryId, source: snapshot }],
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
const retiring =
|
|
460
|
+
migration.state === "retiring"
|
|
461
|
+
? migration
|
|
462
|
+
: migration.state === "retired"
|
|
463
|
+
? migration
|
|
464
|
+
: schedulerStore.beginDomainMigrationRetirement(migration.id);
|
|
465
|
+
const entry = retiring.entries[0];
|
|
466
|
+
appendLoopConfig({
|
|
467
|
+
sessionId: snapshot.sessionId,
|
|
468
|
+
readEvents,
|
|
469
|
+
appendEventIfHead,
|
|
470
|
+
config: loopRetirementConfig(snapshot, migration, entry),
|
|
471
|
+
matches: (current) => {
|
|
472
|
+
const fence = current?.schedulerMigrationFence;
|
|
473
|
+
return (
|
|
474
|
+
fence?.schemaVersion === LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
|
|
475
|
+
fence.state === "retired" &&
|
|
476
|
+
fence.migrationId === migration.id &&
|
|
477
|
+
fence.retirementToken === entry.retirementToken &&
|
|
478
|
+
fence.targetJobId === entry.targetJobId &&
|
|
479
|
+
schedulerMigrationSourceDigest(
|
|
480
|
+
loopMigrationSourceSnapshot({
|
|
481
|
+
sessionId: snapshot.sessionId,
|
|
482
|
+
config: fence.originalConfig,
|
|
483
|
+
}),
|
|
484
|
+
) === entry.sourceDigest
|
|
485
|
+
);
|
|
486
|
+
},
|
|
487
|
+
canReplace: (current) =>
|
|
488
|
+
!current?.schedulerMigrationFence &&
|
|
489
|
+
schedulerMigrationSourceDigest(
|
|
490
|
+
loopMigrationSourceSnapshot({
|
|
491
|
+
sessionId: snapshot.sessionId,
|
|
492
|
+
config: current,
|
|
493
|
+
}),
|
|
494
|
+
) === entry.sourceDigest,
|
|
495
|
+
});
|
|
496
|
+
const retiredMarker = appendLoopMigrationMarker({
|
|
497
|
+
sessionId: snapshot.sessionId,
|
|
498
|
+
readEvents,
|
|
499
|
+
appendEventIfHead,
|
|
500
|
+
marker: {
|
|
501
|
+
schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
|
|
502
|
+
state: "retired",
|
|
503
|
+
migrationId: migration.id,
|
|
504
|
+
retirementToken: entry.retirementToken,
|
|
505
|
+
targetJobId: entry.targetJobId,
|
|
506
|
+
compatibility: "legacy-config-fenced",
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
schedulerStore.confirmDomainMigrationEntryRetired({
|
|
510
|
+
migrationId: migration.id,
|
|
511
|
+
entryId: entry.entryId,
|
|
512
|
+
retirementToken: entry.retirementToken,
|
|
513
|
+
source: retiredMarker,
|
|
514
|
+
});
|
|
515
|
+
return schedulerStore.getDomainMigration(migration.id);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export function rollbackSavedLoopMigration({
|
|
519
|
+
schedulerStore,
|
|
520
|
+
sessionId,
|
|
521
|
+
config,
|
|
522
|
+
migrationId,
|
|
523
|
+
readEvents,
|
|
524
|
+
appendEventIfHead,
|
|
525
|
+
}) {
|
|
526
|
+
const normalizedSessionId = normalizeIdentifier(sessionId, "loop.sessionId");
|
|
527
|
+
const before = schedulerStore.getDomainMigration(migrationId);
|
|
528
|
+
const beforeEntry = assertLoopRollbackContext(before, normalizedSessionId);
|
|
529
|
+
const restoredSnapshot = loopMigrationSourceSnapshot({
|
|
530
|
+
sessionId: normalizedSessionId,
|
|
531
|
+
config,
|
|
532
|
+
});
|
|
533
|
+
if (
|
|
534
|
+
schedulerMigrationSourceDigest(restoredSnapshot) !==
|
|
535
|
+
beforeEntry.sourceDigest
|
|
536
|
+
) {
|
|
537
|
+
throw loopSchedulerError(
|
|
538
|
+
"LOOP_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
539
|
+
`Saved loop rollback config does not match the migration: ${normalizedSessionId}`,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
if (before.state === "rolled_back") return before;
|
|
543
|
+
const migration = schedulerStore.beginDomainMigrationRollback(migrationId);
|
|
544
|
+
const entry = assertLoopRollbackContext(
|
|
545
|
+
migration,
|
|
546
|
+
normalizedSessionId,
|
|
547
|
+
beforeEntry,
|
|
548
|
+
);
|
|
549
|
+
if (migration.state === "rolled_back") return migration;
|
|
550
|
+
schedulerStore.rollbackDomainMigrationTargets(migrationId);
|
|
551
|
+
schedulerStore.restoreDomainMigrationEntrySource({
|
|
552
|
+
migrationId,
|
|
553
|
+
entryId: entry.entryId,
|
|
554
|
+
retirementToken: entry.retirementToken,
|
|
555
|
+
restoreSource: () => {
|
|
556
|
+
appendLoopConfig({
|
|
557
|
+
sessionId: normalizedSessionId,
|
|
558
|
+
config: restoredSnapshot.config,
|
|
559
|
+
readEvents,
|
|
560
|
+
appendEventIfHead,
|
|
561
|
+
matches: (current) =>
|
|
562
|
+
!current?.schedulerMigrationFence &&
|
|
563
|
+
schedulerMigrationSourceDigest(
|
|
564
|
+
loopMigrationSourceSnapshot({ sessionId, config: current }),
|
|
565
|
+
) === entry.sourceDigest,
|
|
566
|
+
canReplace: (current) => {
|
|
567
|
+
const fence = current?.schedulerMigrationFence;
|
|
568
|
+
return (
|
|
569
|
+
fence?.schemaVersion === LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION &&
|
|
570
|
+
fence.state === "retired" &&
|
|
571
|
+
fence.migrationId === migrationId &&
|
|
572
|
+
fence.retirementToken === entry.retirementToken &&
|
|
573
|
+
fence.targetJobId === entry.targetJobId &&
|
|
574
|
+
schedulerMigrationSourceDigest(
|
|
575
|
+
loopMigrationSourceSnapshot({
|
|
576
|
+
sessionId,
|
|
577
|
+
config: fence.originalConfig,
|
|
578
|
+
}),
|
|
579
|
+
) === entry.sourceDigest
|
|
580
|
+
);
|
|
581
|
+
},
|
|
582
|
+
});
|
|
583
|
+
appendLoopMigrationMarker({
|
|
584
|
+
sessionId: normalizedSessionId,
|
|
585
|
+
readEvents,
|
|
586
|
+
appendEventIfHead,
|
|
587
|
+
marker: {
|
|
588
|
+
schemaVersion: LOOP_SCHEDULER_MIGRATION_SCHEMA_VERSION,
|
|
589
|
+
state: "rolled_back",
|
|
590
|
+
migrationId,
|
|
591
|
+
retirementToken: entry.retirementToken,
|
|
592
|
+
targetJobId: entry.targetJobId,
|
|
593
|
+
compatibility: "legacy-config-restored",
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
return restoredSnapshot;
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
return schedulerStore.getDomainMigration(migrationId);
|
|
600
|
+
}
|
|
601
|
+
|
|
86
602
|
export function loopWorkspaceId(cwd) {
|
|
87
603
|
return createHash("sha256")
|
|
88
604
|
.update("chainlesschain.scheduler.loop-workspace.v1\0", "utf8")
|
|
@@ -187,7 +703,10 @@ export function enqueueLoopIteration(
|
|
|
187
703
|
}
|
|
188
704
|
const job = syncLoopSchedulerJob(schedulerStore, definition);
|
|
189
705
|
const scheduled = normalizeEpochMs(scheduledFor, "loop.scheduledFor");
|
|
190
|
-
|
|
706
|
+
// An iteration remains the same logical firing when a saved Loop session is
|
|
707
|
+
// migrated and the target job revision advances. Reuse the durable terminal
|
|
708
|
+
// outcome instead of deriving a second occurrence from the new revision.
|
|
709
|
+
return schedulerStore.enqueueOccurrenceOncePerTrigger({
|
|
191
710
|
jobId: job.id,
|
|
192
711
|
scheduledFor: scheduled,
|
|
193
712
|
availableAt: normalizeEpochMs(availableAt ?? scheduled, "loop.availableAt"),
|
|
@@ -267,7 +786,22 @@ export function createLoopSchedulerAdapter({ runIteration } = {}) {
|
|
|
267
786
|
const transientResults = new Map();
|
|
268
787
|
return {
|
|
269
788
|
kind: LOOP_SCHEDULER_KIND,
|
|
270
|
-
|
|
789
|
+
runtimeControl: CHECKPOINT_V1_RUNTIME_CONTROL,
|
|
790
|
+
async adjudicate({ occurrence, adjudication }) {
|
|
791
|
+
if (adjudication.decision === "confirmed_applied") {
|
|
792
|
+
const match = /^iteration:(\d+)$/.exec(occurrence.triggerKey);
|
|
793
|
+
return {
|
|
794
|
+
settled: true,
|
|
795
|
+
result: {
|
|
796
|
+
iteration: match ? Number(match[1]) : null,
|
|
797
|
+
status: "adjudicated-applied",
|
|
798
|
+
adjudicationRequestId: adjudication.requestId,
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
}
|
|
802
|
+
return { continue: true };
|
|
803
|
+
},
|
|
804
|
+
async execute({ occurrence, signal, adjudication, checkpoint }) {
|
|
271
805
|
const definition = loopExecutionSnapshot(occurrence?.payload?.definition);
|
|
272
806
|
if (
|
|
273
807
|
occurrence.payload.snapshotDigest !== loopExecutionDigest(definition)
|
|
@@ -285,12 +819,16 @@ export function createLoopSchedulerAdapter({ runIteration } = {}) {
|
|
|
285
819
|
`Loop occurrence iteration key is invalid: ${occurrence.id}`,
|
|
286
820
|
);
|
|
287
821
|
}
|
|
288
|
-
|
|
822
|
+
const authorizedRetry =
|
|
823
|
+
adjudication?.decision === "confirmed_not_applied" &&
|
|
824
|
+
adjudication.expectedAttempt + 1 === occurrence.attempt;
|
|
825
|
+
if (occurrence.attempt > 1 && !authorizedRetry) {
|
|
289
826
|
throw loopSchedulerError(
|
|
290
827
|
"LOOP_SCHEDULER_OUTCOME_UNKNOWN",
|
|
291
828
|
`Loop iteration may already have produced side effects; refusing replay: ${occurrence.id}`,
|
|
292
829
|
);
|
|
293
830
|
}
|
|
831
|
+
checkpoint({ iteration, phase: "before_runner" });
|
|
294
832
|
const result =
|
|
295
833
|
(await runIteration(iteration, { signal, definition })) || {};
|
|
296
834
|
const persisted = persistedIterationResult(iteration, result);
|
|
@@ -300,6 +838,9 @@ export function createLoopSchedulerAdapter({ runIteration } = {}) {
|
|
|
300
838
|
});
|
|
301
839
|
return persisted;
|
|
302
840
|
},
|
|
841
|
+
async resume(context) {
|
|
842
|
+
return this.execute(context);
|
|
843
|
+
},
|
|
303
844
|
classifyError() {
|
|
304
845
|
return { retryable: false };
|
|
305
846
|
},
|