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
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
import fs from "fs";
|
|
23
23
|
import os from "os";
|
|
24
24
|
import path from "path";
|
|
25
|
-
import { randomUUID } from "crypto";
|
|
25
|
+
import { createHash, randomUUID } from "crypto";
|
|
26
26
|
import { isEntryExpired, effectiveFireAt } from "./schedule-planner.js";
|
|
27
27
|
import { SUBAGENT_PERMISSION_MODES } from "./subagent-contract.js";
|
|
28
28
|
import { parseGoalCondition } from "./goal-condition-engine.js";
|
|
@@ -30,6 +30,7 @@ import { normalizeActionClass } from "./unattended-action-policy.js";
|
|
|
30
30
|
import { withFileLock } from "./with-file-lock.js";
|
|
31
31
|
|
|
32
32
|
export const SCHEDULE_KINDS = Object.freeze(["wakeup", "cron", "monitor"]);
|
|
33
|
+
export const AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION = 1;
|
|
33
34
|
|
|
34
35
|
/** A valid absolute-epoch-ms expiry, or null (never expires). */
|
|
35
36
|
function normalizeExpiresAt(v) {
|
|
@@ -168,6 +169,87 @@ function hasLiveExecutionLease(entry, now) {
|
|
|
168
169
|
return Number(entry.executionLease?.expiresAt) > Number(now);
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
function stableJsonValue(value) {
|
|
173
|
+
if (Array.isArray(value)) return value.map(stableJsonValue);
|
|
174
|
+
if (value && typeof value === "object") {
|
|
175
|
+
return Object.fromEntries(
|
|
176
|
+
Object.keys(value)
|
|
177
|
+
.sort()
|
|
178
|
+
.map((key) => [key, stableJsonValue(value[key])]),
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function agendaMigrationSource(entry) {
|
|
185
|
+
const source = { ...entry };
|
|
186
|
+
delete source.executionLease;
|
|
187
|
+
delete source.schedulerExecution;
|
|
188
|
+
delete source.schedulerMigration;
|
|
189
|
+
delete source.firedAt;
|
|
190
|
+
delete source.lastRunAt;
|
|
191
|
+
delete source.runs;
|
|
192
|
+
delete source.nextAt;
|
|
193
|
+
delete source.lastCheckAt;
|
|
194
|
+
delete source.checks;
|
|
195
|
+
delete source.lastMtimeMs;
|
|
196
|
+
delete source.lastEventId;
|
|
197
|
+
delete source.lastAuthority;
|
|
198
|
+
delete source.status;
|
|
199
|
+
return source;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function agendaMigrationSourceDigest(entry) {
|
|
203
|
+
return createHash("sha256")
|
|
204
|
+
.update("chainlesschain.agenda.scheduler-migration-source.v1\0", "utf8")
|
|
205
|
+
.update(
|
|
206
|
+
JSON.stringify(stableJsonValue(agendaMigrationSource(entry))),
|
|
207
|
+
"utf8",
|
|
208
|
+
)
|
|
209
|
+
.digest("hex");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function agendaMigrationSourceSnapshot(entry) {
|
|
213
|
+
return stableJsonValue(agendaMigrationSource(entry));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function schedulerMigrationFence(entry, now) {
|
|
217
|
+
const migration = entry.schedulerMigration;
|
|
218
|
+
if (
|
|
219
|
+
migration?.schemaVersion !== AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
220
|
+
typeof migration.retirementToken !== "string" ||
|
|
221
|
+
migration.retirementToken.length === 0
|
|
222
|
+
) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return (
|
|
226
|
+
hasLiveExecutionLease(entry, now) &&
|
|
227
|
+
entry.executionLease?.owner ===
|
|
228
|
+
`scheduler-migration:${migration.retirementToken}`
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function applySchedulerMigrationFence(entry, now) {
|
|
233
|
+
const token = entry.schedulerMigration?.retirementToken;
|
|
234
|
+
if (typeof token !== "string" || token.length === 0) return false;
|
|
235
|
+
entry.executionLease = {
|
|
236
|
+
owner: `scheduler-migration:${token}`,
|
|
237
|
+
claimedAt: now,
|
|
238
|
+
expiresAt: Number.MAX_SAFE_INTEGER,
|
|
239
|
+
};
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function normalizeMigrationField(value, field) {
|
|
244
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
245
|
+
throw agendaStoreError(
|
|
246
|
+
"AGENDA_SCHEDULER_MIGRATION_INVALID",
|
|
247
|
+
`Agenda scheduler migration ${field} is required`,
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return value.trim();
|
|
251
|
+
}
|
|
252
|
+
|
|
171
253
|
function agendaStoreError(code, message, { retryable = false, retryAt } = {}) {
|
|
172
254
|
const error = new Error(message);
|
|
173
255
|
error.code = code;
|
|
@@ -820,15 +902,246 @@ export class AgentScheduleStore {
|
|
|
820
902
|
return this.list().find((entry) => entry.id === id) || null;
|
|
821
903
|
}
|
|
822
904
|
|
|
905
|
+
prepareSchedulerMigration(
|
|
906
|
+
id,
|
|
907
|
+
{ migrationId, sourceDigest, targetJobId } = {},
|
|
908
|
+
) {
|
|
909
|
+
const migration = normalizeMigrationField(migrationId, "migrationId");
|
|
910
|
+
const expectedDigest = normalizeMigrationField(
|
|
911
|
+
sourceDigest,
|
|
912
|
+
"sourceDigest",
|
|
913
|
+
);
|
|
914
|
+
const target = normalizeMigrationField(targetJobId, "targetJobId");
|
|
915
|
+
this._ensureDir();
|
|
916
|
+
return withFileLock(
|
|
917
|
+
path.join(this.dir, ".agenda-claims"),
|
|
918
|
+
() => {
|
|
919
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
920
|
+
const entries = this._readAll(kind);
|
|
921
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
922
|
+
if (!entry) continue;
|
|
923
|
+
if (!isSchedulableStatus(entry)) {
|
|
924
|
+
throw agendaStoreError(
|
|
925
|
+
"AGENDA_SCHEDULER_ENTRY_TERMINAL",
|
|
926
|
+
`Agenda entry is not schedulable: ${id}`,
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
const actualDigest = agendaMigrationSourceDigest(entry);
|
|
930
|
+
if (actualDigest !== expectedDigest) {
|
|
931
|
+
throw agendaStoreError(
|
|
932
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
933
|
+
`Agenda entry changed before migration: ${id}`,
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const prior = entry.schedulerMigration;
|
|
937
|
+
if (prior) {
|
|
938
|
+
if (
|
|
939
|
+
prior.schemaVersion !==
|
|
940
|
+
AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
941
|
+
prior.sourceDigest !== expectedDigest ||
|
|
942
|
+
prior.targetJobId !== target ||
|
|
943
|
+
prior.migrationId !== migration ||
|
|
944
|
+
prior.state !== "prepared"
|
|
945
|
+
) {
|
|
946
|
+
throw agendaStoreError(
|
|
947
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
948
|
+
`Agenda entry already belongs to another scheduler migration: ${id}`,
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
return entry;
|
|
952
|
+
}
|
|
953
|
+
entry.schedulerMigration = {
|
|
954
|
+
schemaVersion: AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION,
|
|
955
|
+
state: "prepared",
|
|
956
|
+
migrationId: migration,
|
|
957
|
+
sourceDigest: expectedDigest,
|
|
958
|
+
targetJobId: target,
|
|
959
|
+
preparedAt: this._now(),
|
|
960
|
+
};
|
|
961
|
+
this._writeAll(kind, entries);
|
|
962
|
+
return entry;
|
|
963
|
+
}
|
|
964
|
+
throw agendaStoreError(
|
|
965
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
966
|
+
`Agenda entry does not exist: ${id}`,
|
|
967
|
+
);
|
|
968
|
+
},
|
|
969
|
+
{ failIfUnavailable: true },
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
retireForSchedulerMigration(
|
|
974
|
+
id,
|
|
975
|
+
{
|
|
976
|
+
migrationId,
|
|
977
|
+
sourceDigest,
|
|
978
|
+
targetJobId,
|
|
979
|
+
retirementToken,
|
|
980
|
+
atMs = null,
|
|
981
|
+
} = {},
|
|
982
|
+
) {
|
|
983
|
+
const migration = normalizeMigrationField(migrationId, "migrationId");
|
|
984
|
+
const expectedDigest = normalizeMigrationField(
|
|
985
|
+
sourceDigest,
|
|
986
|
+
"sourceDigest",
|
|
987
|
+
);
|
|
988
|
+
const target = normalizeMigrationField(targetJobId, "targetJobId");
|
|
989
|
+
const token = normalizeMigrationField(retirementToken, "retirementToken");
|
|
990
|
+
const now = atMs != null ? Number(atMs) : this._now();
|
|
991
|
+
this._ensureDir();
|
|
992
|
+
return withFileLock(
|
|
993
|
+
path.join(this.dir, ".agenda-claims"),
|
|
994
|
+
() => {
|
|
995
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
996
|
+
const entries = this._readAll(kind);
|
|
997
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
998
|
+
if (!entry) continue;
|
|
999
|
+
const prior = entry.schedulerMigration;
|
|
1000
|
+
if (
|
|
1001
|
+
prior?.schemaVersion !==
|
|
1002
|
+
AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
1003
|
+
prior.sourceDigest !== expectedDigest ||
|
|
1004
|
+
prior.targetJobId !== target ||
|
|
1005
|
+
prior.migrationId !== migration ||
|
|
1006
|
+
!["prepared", "retired"].includes(prior.state)
|
|
1007
|
+
) {
|
|
1008
|
+
throw agendaStoreError(
|
|
1009
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1010
|
+
`Agenda migration preparation changed before retirement: ${id}`,
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
if (prior.state === "retired") {
|
|
1014
|
+
if (
|
|
1015
|
+
prior.retirementToken !== token ||
|
|
1016
|
+
!schedulerMigrationFence(entry, now)
|
|
1017
|
+
) {
|
|
1018
|
+
throw agendaStoreError(
|
|
1019
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1020
|
+
`Agenda migration retirement fence changed: ${id}`,
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
return entry;
|
|
1024
|
+
}
|
|
1025
|
+
if (agendaMigrationSourceDigest(entry) !== expectedDigest) {
|
|
1026
|
+
throw agendaStoreError(
|
|
1027
|
+
"AGENDA_SCHEDULER_MIGRATION_SOURCE_CHANGED",
|
|
1028
|
+
`Agenda entry changed before retirement: ${id}`,
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
if (hasLiveExecutionLease(entry, now)) {
|
|
1032
|
+
throw agendaStoreError(
|
|
1033
|
+
"AGENDA_SCHEDULER_LEGACY_CLAIM_ACTIVE",
|
|
1034
|
+
`Agenda entry is already claimed by a legacy runner: ${id}`,
|
|
1035
|
+
{
|
|
1036
|
+
retryable: true,
|
|
1037
|
+
retryAt: Number(entry.executionLease.expiresAt),
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
entry.schedulerMigration = {
|
|
1042
|
+
...prior,
|
|
1043
|
+
state: "retired",
|
|
1044
|
+
retirementToken: token,
|
|
1045
|
+
retiredAt: now,
|
|
1046
|
+
};
|
|
1047
|
+
applySchedulerMigrationFence(entry, now);
|
|
1048
|
+
this._writeAll(kind, entries);
|
|
1049
|
+
return entry;
|
|
1050
|
+
}
|
|
1051
|
+
throw agendaStoreError(
|
|
1052
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1053
|
+
`Agenda entry does not exist: ${id}`,
|
|
1054
|
+
);
|
|
1055
|
+
},
|
|
1056
|
+
{ failIfUnavailable: true },
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
restoreFromSchedulerMigration(
|
|
1061
|
+
id,
|
|
1062
|
+
{ migrationId, sourceDigest, targetJobId, retirementToken } = {},
|
|
1063
|
+
) {
|
|
1064
|
+
const migrationIdValue = normalizeMigrationField(
|
|
1065
|
+
migrationId,
|
|
1066
|
+
"migrationId",
|
|
1067
|
+
);
|
|
1068
|
+
const expectedDigest = normalizeMigrationField(
|
|
1069
|
+
sourceDigest,
|
|
1070
|
+
"sourceDigest",
|
|
1071
|
+
);
|
|
1072
|
+
const target = normalizeMigrationField(targetJobId, "targetJobId");
|
|
1073
|
+
const token =
|
|
1074
|
+
retirementToken === null || retirementToken === undefined
|
|
1075
|
+
? null
|
|
1076
|
+
: normalizeMigrationField(retirementToken, "retirementToken");
|
|
1077
|
+
this._ensureDir();
|
|
1078
|
+
return withFileLock(
|
|
1079
|
+
path.join(this.dir, ".agenda-claims"),
|
|
1080
|
+
() => {
|
|
1081
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
1082
|
+
const entries = this._readAll(kind);
|
|
1083
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
1084
|
+
if (!entry) continue;
|
|
1085
|
+
const migration = entry.schedulerMigration;
|
|
1086
|
+
if (!migration) {
|
|
1087
|
+
if (
|
|
1088
|
+
agendaMigrationSourceDigest(entry) !== expectedDigest ||
|
|
1089
|
+
String(entry.executionLease?.owner || "").startsWith(
|
|
1090
|
+
"scheduler-migration:",
|
|
1091
|
+
)
|
|
1092
|
+
) {
|
|
1093
|
+
throw agendaStoreError(
|
|
1094
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1095
|
+
`Agenda source changed after rollback restoration: ${id}`,
|
|
1096
|
+
);
|
|
1097
|
+
}
|
|
1098
|
+
return entry;
|
|
1099
|
+
}
|
|
1100
|
+
if (
|
|
1101
|
+
migration?.schemaVersion !==
|
|
1102
|
+
AGENDA_SCHEDULER_MIGRATION_SCHEMA_VERSION ||
|
|
1103
|
+
migration.sourceDigest !== expectedDigest ||
|
|
1104
|
+
agendaMigrationSourceDigest(entry) !== expectedDigest ||
|
|
1105
|
+
migration.targetJobId !== target ||
|
|
1106
|
+
migration.migrationId !== migrationIdValue ||
|
|
1107
|
+
!["prepared", "retired"].includes(migration.state) ||
|
|
1108
|
+
(migration.state === "retired" &&
|
|
1109
|
+
(migration.retirementToken !== token ||
|
|
1110
|
+
entry.executionLease?.owner !== `scheduler-migration:${token}`))
|
|
1111
|
+
) {
|
|
1112
|
+
throw agendaStoreError(
|
|
1113
|
+
"AGENDA_SCHEDULER_MIGRATION_CONFLICT",
|
|
1114
|
+
`Agenda migration fence changed before rollback: ${id}`,
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
delete entry.schedulerMigration;
|
|
1118
|
+
if (migration.state === "retired") delete entry.executionLease;
|
|
1119
|
+
this._writeAll(kind, entries);
|
|
1120
|
+
return entry;
|
|
1121
|
+
}
|
|
1122
|
+
throw agendaStoreError(
|
|
1123
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1124
|
+
`Agenda entry does not exist: ${id}`,
|
|
1125
|
+
);
|
|
1126
|
+
},
|
|
1127
|
+
{ failIfUnavailable: true },
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
823
1131
|
/** Entries whose time has come (pending wakeup / active cron|monitor). */
|
|
824
|
-
due(kind = null, atMs = null) {
|
|
1132
|
+
due(kind = null, atMs = null, { schedulerMigration = false } = {}) {
|
|
825
1133
|
const now = atMs != null ? atMs : this._now();
|
|
826
1134
|
return this.list(kind).filter((entry) => {
|
|
827
1135
|
// An entry past its expiry never fires — even if it is not yet retired
|
|
828
1136
|
// (defense-in-depth: retireExpired need not have run first).
|
|
829
1137
|
if (isEntryExpired(entry, now)) return false;
|
|
830
1138
|
if (!isSchedulableStatus(entry)) return false;
|
|
831
|
-
if (
|
|
1139
|
+
if (
|
|
1140
|
+
hasLiveExecutionLease(entry, now) &&
|
|
1141
|
+
!(schedulerMigration && schedulerMigrationFence(entry, now))
|
|
1142
|
+
) {
|
|
1143
|
+
return false;
|
|
1144
|
+
}
|
|
832
1145
|
// The effective fire time applies the entry's OWN deterministic jitter
|
|
833
1146
|
// (`entry.jitterMs`, default 0 → base fire time, byte-identical to the
|
|
834
1147
|
// prior `dueAt`/`nextAt` comparison). A jittered entry fires slightly
|
|
@@ -946,7 +1259,8 @@ export class AgentScheduleStore {
|
|
|
946
1259
|
) {
|
|
947
1260
|
return entry;
|
|
948
1261
|
}
|
|
949
|
-
|
|
1262
|
+
const migrationFence = schedulerMigrationFence(entry, now);
|
|
1263
|
+
if (hasLiveExecutionLease(entry, now) && !migrationFence) {
|
|
950
1264
|
throw agendaStoreError(
|
|
951
1265
|
"AGENDA_SCHEDULER_LEGACY_CLAIM_ACTIVE",
|
|
952
1266
|
`Agenda entry is already claimed by a legacy runner: ${id}`,
|
|
@@ -1051,6 +1365,102 @@ export class AgentScheduleStore {
|
|
|
1051
1365
|
...(outcome === "succeeded" ? { result: storedResult } : { error }),
|
|
1052
1366
|
};
|
|
1053
1367
|
delete entry.executionLease;
|
|
1368
|
+
if (isSchedulableStatus(entry))
|
|
1369
|
+
applySchedulerMigrationFence(entry, now);
|
|
1370
|
+
this._writeAll(kind, entries);
|
|
1371
|
+
return entry;
|
|
1372
|
+
}
|
|
1373
|
+
throw agendaStoreError(
|
|
1374
|
+
"AGENDA_SCHEDULER_ENTRY_NOT_FOUND",
|
|
1375
|
+
`Agenda entry does not exist: ${id}`,
|
|
1376
|
+
);
|
|
1377
|
+
},
|
|
1378
|
+
{ failIfUnavailable: true },
|
|
1379
|
+
);
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/**
|
|
1383
|
+
* Resolve start-only scheduler evidence after an operator has verified the
|
|
1384
|
+
* external outcome. This is intentionally separate from normal completion:
|
|
1385
|
+
* it preserves the adjudication identity and can release the non-expiring
|
|
1386
|
+
* legacy fence without pretending that a machine observed a terminal result.
|
|
1387
|
+
*/
|
|
1388
|
+
adjudicateSchedulerExecution(
|
|
1389
|
+
id,
|
|
1390
|
+
{
|
|
1391
|
+
occurrenceId,
|
|
1392
|
+
snapshotDigest,
|
|
1393
|
+
attempt,
|
|
1394
|
+
decision,
|
|
1395
|
+
requestId,
|
|
1396
|
+
atMs = null,
|
|
1397
|
+
} = {},
|
|
1398
|
+
) {
|
|
1399
|
+
if (!["confirmed_applied", "confirmed_not_applied"].includes(decision)) {
|
|
1400
|
+
throw agendaStoreError(
|
|
1401
|
+
"AGENDA_SCHEDULER_ADJUDICATION_INVALID",
|
|
1402
|
+
"Agenda scheduler adjudication decision is invalid",
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
const now = atMs != null ? Number(atMs) : this._now();
|
|
1406
|
+
this._ensureDir();
|
|
1407
|
+
return withFileLock(
|
|
1408
|
+
path.join(this.dir, ".agenda-claims"),
|
|
1409
|
+
() => {
|
|
1410
|
+
for (const kind of SCHEDULE_KINDS) {
|
|
1411
|
+
const entries = this._readAll(kind);
|
|
1412
|
+
const entry = entries.find((candidate) => candidate.id === id);
|
|
1413
|
+
if (!entry) continue;
|
|
1414
|
+
const evidence = entry.schedulerExecution;
|
|
1415
|
+
if (
|
|
1416
|
+
evidence?.occurrenceId === occurrenceId &&
|
|
1417
|
+
evidence.snapshotDigest === snapshotDigest &&
|
|
1418
|
+
evidence.adjudication?.requestId === requestId &&
|
|
1419
|
+
evidence.adjudication.decision === decision &&
|
|
1420
|
+
evidence.status ===
|
|
1421
|
+
(decision === "confirmed_applied"
|
|
1422
|
+
? "adjudicated_applied"
|
|
1423
|
+
: "adjudicated_not_applied")
|
|
1424
|
+
) {
|
|
1425
|
+
return entry;
|
|
1426
|
+
}
|
|
1427
|
+
if (
|
|
1428
|
+
!evidence ||
|
|
1429
|
+
evidence.occurrenceId !== occurrenceId ||
|
|
1430
|
+
evidence.snapshotDigest !== snapshotDigest ||
|
|
1431
|
+
!Number.isSafeInteger(evidence.attempt) ||
|
|
1432
|
+
evidence.attempt < 1 ||
|
|
1433
|
+
evidence.attempt > attempt ||
|
|
1434
|
+
evidence.status !== "running"
|
|
1435
|
+
) {
|
|
1436
|
+
throw agendaStoreError(
|
|
1437
|
+
"AGENDA_SCHEDULER_ADJUDICATION_EVIDENCE_MISMATCH",
|
|
1438
|
+
`Agenda scheduler adjudication evidence does not match: ${id}`,
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
if (decision === "confirmed_applied") {
|
|
1442
|
+
if (entry.kind === "monitor") {
|
|
1443
|
+
// A generic operator decision proves the prior check ran, but
|
|
1444
|
+
// not whether its observation matched. Pause the monitor rather
|
|
1445
|
+
// than inventing a domain result or rearming a duplicate check.
|
|
1446
|
+
entry.status = "adjudicated";
|
|
1447
|
+
entry.lastCheckAt = now;
|
|
1448
|
+
} else {
|
|
1449
|
+
applyScheduledAgentSuccess(entry, now);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
entry.schedulerExecution = {
|
|
1453
|
+
...evidence,
|
|
1454
|
+
status:
|
|
1455
|
+
decision === "confirmed_applied"
|
|
1456
|
+
? "adjudicated_applied"
|
|
1457
|
+
: "adjudicated_not_applied",
|
|
1458
|
+
endedAt: now,
|
|
1459
|
+
adjudication: { requestId, decision },
|
|
1460
|
+
};
|
|
1461
|
+
delete entry.executionLease;
|
|
1462
|
+
if (isSchedulableStatus(entry))
|
|
1463
|
+
applySchedulerMigrationFence(entry, now);
|
|
1054
1464
|
this._writeAll(kind, entries);
|
|
1055
1465
|
return entry;
|
|
1056
1466
|
}
|
|
@@ -70,6 +70,10 @@ const EVENT_DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
|
70
70
|
const CASE_ID_PATTERN = /^tadj_[a-f0-9]{64}$/;
|
|
71
71
|
const CLAIM_ID_PATTERN =
|
|
72
72
|
/^tadj_claim_[a-zA-Z0-9](?:[a-zA-Z0-9._-]{0,126}[a-zA-Z0-9])?$/;
|
|
73
|
+
const WINDOWS_RENAME_TRANSIENT_CODES = new Set(["EACCES", "EBUSY", "EPERM"]);
|
|
74
|
+
const WINDOWS_RENAME_RETRY_DELAYS_MS = Object.freeze([
|
|
75
|
+
5, 10, 20, 40, 80, 100, 100, 100,
|
|
76
|
+
]);
|
|
73
77
|
const DECISIONS = new Set(Object.values(TEAM_ADJUDICATION_DECISIONS));
|
|
74
78
|
const HEADER_KEYS = new Set([
|
|
75
79
|
"schema",
|
|
@@ -130,6 +134,19 @@ function adjudicationError(code, message, details = {}, cause = null) {
|
|
|
130
134
|
return new TeamAdjudicationError(code, message, details, cause);
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
function sleepSync(milliseconds) {
|
|
138
|
+
const duration = Math.max(0, Number(milliseconds) || 0);
|
|
139
|
+
if (duration === 0) return;
|
|
140
|
+
try {
|
|
141
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, duration);
|
|
142
|
+
} catch {
|
|
143
|
+
const deadline = Date.now() + duration;
|
|
144
|
+
while (Date.now() < deadline) {
|
|
145
|
+
// SharedArrayBuffer is unavailable; retain a bounded synchronous wait.
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
133
150
|
function isAdjudicationError(error) {
|
|
134
151
|
return Object.values(TEAM_ADJUDICATION_ERROR_CODES).includes(error?.code);
|
|
135
152
|
}
|
|
@@ -892,11 +909,13 @@ export class TeamAdjudicationStore {
|
|
|
892
909
|
_lock = withFileLock,
|
|
893
910
|
_randomUUID = randomUUID,
|
|
894
911
|
_platform = process.platform,
|
|
912
|
+
_sleep = sleepSync,
|
|
895
913
|
} = {}) {
|
|
896
914
|
this._fs = _fs;
|
|
897
915
|
this._lock = _lock;
|
|
898
916
|
this._randomUUID = _randomUUID;
|
|
899
917
|
this._platform = _platform;
|
|
918
|
+
this._sleep = typeof _sleep === "function" ? _sleep : sleepSync;
|
|
900
919
|
this._now = typeof now === "function" ? now : () => Number(now);
|
|
901
920
|
this.lockTimeoutMs = Math.max(0, Number(lockTimeoutMs) || 0);
|
|
902
921
|
this.lockStaleMs = Math.max(1, Number(lockStaleMs) || 30_000);
|
|
@@ -1161,7 +1180,27 @@ export class TeamAdjudicationStore {
|
|
|
1161
1180
|
descriptor = null;
|
|
1162
1181
|
|
|
1163
1182
|
this._assertAuthorityPaths();
|
|
1164
|
-
|
|
1183
|
+
let renameAttempt = 0;
|
|
1184
|
+
for (;;) {
|
|
1185
|
+
try {
|
|
1186
|
+
this._fs.renameSync(temporaryPath, canonicalPath);
|
|
1187
|
+
break;
|
|
1188
|
+
} catch (cause) {
|
|
1189
|
+
const delay = WINDOWS_RENAME_RETRY_DELAYS_MS[renameAttempt];
|
|
1190
|
+
if (
|
|
1191
|
+
this._platform !== "win32" ||
|
|
1192
|
+
!WINDOWS_RENAME_TRANSIENT_CODES.has(cause?.code) ||
|
|
1193
|
+
delay === undefined
|
|
1194
|
+
) {
|
|
1195
|
+
throw cause;
|
|
1196
|
+
}
|
|
1197
|
+
renameAttempt += 1;
|
|
1198
|
+
this._sleep(delay);
|
|
1199
|
+
// Keep each retry bound to the same canonical authority after
|
|
1200
|
+
// yielding to a transient Windows file-sharing holder.
|
|
1201
|
+
this._assertAuthorityPaths();
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1165
1204
|
renamed = true;
|
|
1166
1205
|
this._fs.chmodSync(canonicalPath, 0o600);
|
|
1167
1206
|
const persisted = this._secureRead();
|