chainlesschain 0.163.5 → 0.163.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/completions/_cc +1 -1
- package/completions/cc.bash +1 -1
- package/completions/cc.fish +1 -1
- package/completions/cc.ps1 +1 -1
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-Cv2EE1Zy.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CcpxMLuW.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DE8mJoMI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DKn9dk9L.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Cp3mG3Py.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-zvsQfF2C.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Bi85SsjN.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cw47Dj6s.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-C5Ip2afH.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C0iAFkJv.js → Chat-hHEmGZy-.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BV9Wv7wO.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-3KZZCiAF.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-C8OMbBAz.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-B-MB84LJ.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DpR7Dxao.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CyDtz0OS.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-5tiRa2An.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DnQaKZ5x.js → Cowork-BUm-R7ht.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-BaWfoEDf.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Bu0HoHhb.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-31zk8upX.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BmVne-e2.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B4PkoOWd.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BZ6tyFtI.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Osozn4Ds.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DaH8TOAP.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-tbU0uwis.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Df8LQBrL.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DsMB6-14.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BnH0dZzd.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DhnN1N_P.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DnNshAgn.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BmBFX3_N.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DCslfQU3.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-CPEgWDc3.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-C36qjgF9.js → McpTools-CkEVOMKH.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-tNHvrgqN.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Xaf6cKjc.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D2E5TTeO.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-D4AJ2dwV.js → Mtc-DcTXSSma.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Df-gtn2_.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-DTCHLWld.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DruZ_1Jb.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Dx3pdGDs.js → Notes-DZ5U58Ng.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-Bi3L69oL.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BHY33m7C.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BG2ZVEV-.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Viwtg4Mn.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Clm_yc1Z.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-NKmmDiOw.js → PdhVaultBrowser-BO-2PeaB.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-DIL8u9Nk.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B-jM4wt-.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-627I__H5.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-2DN2qrkW.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CN4wC0sF.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-K35vsh5H.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-B_wQadhX.js → Projects-BV4oApsm.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-MckaQUhQ.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BS3mtA_O.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BHp_nofL.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DlmnPuZj.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-tsyFbvpy.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-Ba1iWv2D.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-NnlGC--f.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-CjThSIRv.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CojUyC5x.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-c-sxeJEc.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CtcKEEmF.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CIBBXflN.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-D1XOOK9n.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-hRfEp5Sn.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Dexb1f_6.js → SpeechSettings-3IPU0HBZ.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BJsSFGYy.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CfSZ8A1L.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-idDf47W-.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C-UsX6w6.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BgJtX5n1.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DP0BLKTl.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-LwCzqETp.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-D_9tVch0.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BH2UlEWh.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CIpD0nYb.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9ShhYxK_.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C7afGNIG.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-bUKn6i5R.js → WebAuthn-B8p9Mhkw.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-CxgQaGyp.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DMVkmJwx.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CDzQh5nS.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C51kEF4D.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BTk9bXBn.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CMje33rb.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpcrFxMI.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BW4F5YDF.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRG-aqOs.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCRvG9lq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgivOKHA.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzCkNylB.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-x53Vf7la.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCLj9hcD.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CypsfcEq.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtlEqHLP.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ld_4mFmC.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DqU9njjT.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CwGPwjUu.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5IWTMVl.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJfnCuMq.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-hg_emBsi.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-togc_uYn.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4rQCS8T.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-0Auljb10.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cd0hmPxD.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXQBPLyE.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-h31MwrME.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1oZReaY.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfC-piWZ.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9rC-fGW.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-CvSeVzqe.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtM8OT_7.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBz9LIMS.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAfY7m7i.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVePe_Rf.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDLkNkLj.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTlsE8Df.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-BeEaAEIp.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0-zMtKA.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1oZQmAA.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRLQyp2A.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-OS-vfbTA.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-vrA8K-ac.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ByEzwIAl.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-Ck4AWjw-.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-6p0l9SrB.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B1CUPQ6R.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B1STg1iW.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DTTllkBV.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-AIrrCfBb.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C_fPMjdR.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-D5ow2yIP.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Dy3hEA--.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-WfzcfSh4.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CKSejfas.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-od3zpPBN.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DRD_2DFA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DmNrEtgf.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +5 -5
- package/src/command-manifest.json +1 -1
- package/src/commands/agenda.js +76 -4
- package/src/commands/agent.js +90 -1
- package/src/commands/automation.js +462 -3
- package/src/commands/background-session.js +102 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/cowork.js +6 -1
- package/src/commands/loop.js +187 -38
- package/src/commands/routine.js +12 -5
- package/src/commands/scheduler-daemon.js +807 -0
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +28 -2
- 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 +737 -47
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-center-routines.js +363 -0
- package/src/lib/automation-center.js +435 -0
- package/src/lib/automation-engine.js +568 -19
- package/src/lib/automation-execution-authority.js +736 -0
- package/src/lib/background-agent-supervisor.js +871 -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/background-needs-input-incident.js +221 -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 +673 -19
- package/src/lib/cowork-observe.js +5 -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 +62 -3
- package/src/lib/permission-engine.js +14 -7
- package/src/lib/public-ide-capability-manifest.js +2 -0
- package/src/lib/routine-store.js +546 -86
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +604 -29
- package/src/lib/scheduler-kernel/authority-resolver.js +163 -0
- package/src/lib/scheduler-kernel/automation-adapter.js +994 -0
- package/src/lib/scheduler-kernel/automation-event-adapter.js +757 -0
- package/src/lib/scheduler-kernel/contract.js +11 -3
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +343 -15
- package/src/lib/scheduler-kernel/loop-adapter.js +913 -0
- 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 +691 -22
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/service.js +328 -0
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +3439 -206
- 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 +1274 -208
- package/src/runtime/coding-agent-contract-shared.cjs +5 -0
- 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 +468 -98
- package/src/assets/web-panel/assets/devWarning-CdwPwYj8.js +0 -1
- package/src/assets/web-panel/assets/index-BEeOdfEe.js +0 -1
- package/src/assets/web-panel/assets/index-BTe0TOu6.js +0 -1
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { hostname, userInfo } from "node:os";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { logger } from "../lib/logger.js";
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_SERVICE_INTERVAL_MS,
|
|
7
|
+
MAX_SERVICE_INTERVAL_MS,
|
|
8
|
+
MIN_SERVICE_INTERVAL_MS,
|
|
9
|
+
createSchedulerService,
|
|
10
|
+
} from "../lib/scheduler-kernel/service.js";
|
|
11
|
+
import {
|
|
12
|
+
createSchedulerMigrationAdminRepository,
|
|
13
|
+
listSchedulerMigrations,
|
|
14
|
+
} from "../lib/scheduler-kernel/migration-admin.js";
|
|
15
|
+
import {
|
|
16
|
+
rollbackSchedulerMigration,
|
|
17
|
+
showSchedulerMigrationRollback,
|
|
18
|
+
} from "../lib/scheduler-kernel/migration-rollback-admin.js";
|
|
19
|
+
import {
|
|
20
|
+
schedulerAdjudicationOperatorDigest,
|
|
21
|
+
schedulerAdjudicationReasonDigest,
|
|
22
|
+
} from "../lib/scheduler-kernel/store.js";
|
|
23
|
+
|
|
24
|
+
export const SCHEDULER_DAEMON_DOMAINS = Object.freeze(["agenda", "cowork"]);
|
|
25
|
+
|
|
26
|
+
function schedulerDaemonError(code, message, details = undefined) {
|
|
27
|
+
const error = new Error(message);
|
|
28
|
+
error.code = code;
|
|
29
|
+
if (details !== undefined) error.details = details;
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function positiveInteger(value, field) {
|
|
34
|
+
const normalized = Number(value);
|
|
35
|
+
if (!Number.isSafeInteger(normalized) || normalized < 1) {
|
|
36
|
+
throw schedulerDaemonError(
|
|
37
|
+
"SCHEDULER_DAEMON_INVALID_POLICY",
|
|
38
|
+
`${field} must be a positive integer`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return normalized;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function nonnegativeInteger(value, field) {
|
|
45
|
+
const normalized = Number(value);
|
|
46
|
+
if (!Number.isSafeInteger(normalized) || normalized < 0) {
|
|
47
|
+
throw schedulerDaemonError(
|
|
48
|
+
"SCHEDULER_DAEMON_INVALID_POLICY",
|
|
49
|
+
`${field} must be a non-negative integer`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return normalized;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function parseSchedulerCapabilities(value) {
|
|
56
|
+
if (typeof value !== "string") {
|
|
57
|
+
throw schedulerDaemonError(
|
|
58
|
+
"SCHEDULER_DAEMON_INVALID_POLICY",
|
|
59
|
+
"Scheduler capabilities must be a comma-separated string",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const capabilities = [
|
|
63
|
+
...new Set(
|
|
64
|
+
value
|
|
65
|
+
.split(",")
|
|
66
|
+
.map((entry) => entry.trim())
|
|
67
|
+
.filter(Boolean),
|
|
68
|
+
),
|
|
69
|
+
].sort();
|
|
70
|
+
if (capabilities.length === 0 || capabilities.includes("*")) {
|
|
71
|
+
throw schedulerDaemonError(
|
|
72
|
+
"SCHEDULER_DAEMON_INVALID_POLICY",
|
|
73
|
+
"Scheduler policy requires one or more exact capabilities",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return capabilities;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function withSchedulerStore(dependencies, operation) {
|
|
80
|
+
if (dependencies.schedulerStore) {
|
|
81
|
+
return operation(dependencies.schedulerStore);
|
|
82
|
+
}
|
|
83
|
+
const openStore =
|
|
84
|
+
dependencies.openSchedulerStore ||
|
|
85
|
+
(await import("../lib/scheduler-kernel/store.js")).openSchedulerStore;
|
|
86
|
+
const store = openStore();
|
|
87
|
+
try {
|
|
88
|
+
return await operation(store);
|
|
89
|
+
} finally {
|
|
90
|
+
store.close();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function withSchedulerMigrationRepository(dependencies, operation) {
|
|
95
|
+
if (dependencies.migrationRepository) {
|
|
96
|
+
return operation(dependencies.migrationRepository);
|
|
97
|
+
}
|
|
98
|
+
return withSchedulerStore(dependencies, (store) =>
|
|
99
|
+
operation(createSchedulerMigrationAdminRepository(store)),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function listSchedulerMigrationJournal(
|
|
104
|
+
options = {},
|
|
105
|
+
dependencies = {},
|
|
106
|
+
) {
|
|
107
|
+
return withSchedulerMigrationRepository(dependencies, (repository) =>
|
|
108
|
+
listSchedulerMigrations(repository, options),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function showSchedulerMigrationJournal(
|
|
113
|
+
migrationId,
|
|
114
|
+
dependencies = {},
|
|
115
|
+
) {
|
|
116
|
+
return withSchedulerMigrationRepository(dependencies, (repository) =>
|
|
117
|
+
showSchedulerMigrationRollback(repository, migrationId),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export async function rollbackSchedulerMigrationJournal(
|
|
122
|
+
migrationId,
|
|
123
|
+
options = {},
|
|
124
|
+
dependencies = {},
|
|
125
|
+
) {
|
|
126
|
+
return withSchedulerStore(dependencies, (schedulerStore) => {
|
|
127
|
+
const repository =
|
|
128
|
+
dependencies.migrationRepository ||
|
|
129
|
+
createSchedulerMigrationAdminRepository(schedulerStore);
|
|
130
|
+
return rollbackSchedulerMigration({
|
|
131
|
+
schedulerStore,
|
|
132
|
+
repository,
|
|
133
|
+
migrationId,
|
|
134
|
+
options,
|
|
135
|
+
dependencies,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function buildSchedulerAdjudicationChallenge({
|
|
141
|
+
occurrenceId,
|
|
142
|
+
decision,
|
|
143
|
+
evidenceDigest,
|
|
144
|
+
expectedAttempt,
|
|
145
|
+
expectedFence,
|
|
146
|
+
} = {}) {
|
|
147
|
+
if (
|
|
148
|
+
typeof occurrenceId !== "string" ||
|
|
149
|
+
!["confirmed_applied", "confirmed_not_applied"].includes(decision) ||
|
|
150
|
+
typeof evidenceDigest !== "string" ||
|
|
151
|
+
!/^sha256:[a-f0-9]{64}$/.test(evidenceDigest) ||
|
|
152
|
+
!Number.isSafeInteger(expectedAttempt) ||
|
|
153
|
+
expectedAttempt < 1 ||
|
|
154
|
+
!Number.isSafeInteger(expectedFence) ||
|
|
155
|
+
expectedFence < 1
|
|
156
|
+
) {
|
|
157
|
+
throw schedulerDaemonError(
|
|
158
|
+
"SCHEDULER_ADJUDICATION_INPUT_INVALID",
|
|
159
|
+
"Scheduler adjudication challenge input is invalid",
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return (
|
|
163
|
+
`HOST STOPPED AND SCHEDULER DISPATCH DRAINED; ADJUDICATE ${occurrenceId} ` +
|
|
164
|
+
`${decision} ${evidenceDigest} ATTEMPT ${expectedAttempt} FENCE ${expectedFence}`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function requireSchedulerAdjudicationTTY(request, dependencies = {}) {
|
|
169
|
+
const stdin = dependencies.stdin || process.stdin;
|
|
170
|
+
const stdout = dependencies.stdout || process.stdout;
|
|
171
|
+
if (stdin?.isTTY !== true || stdout?.isTTY !== true) {
|
|
172
|
+
throw schedulerDaemonError(
|
|
173
|
+
"SCHEDULER_ADJUDICATION_NON_INTERACTIVE",
|
|
174
|
+
"Scheduler adjudication requires an interactive TTY",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const readReason =
|
|
178
|
+
dependencies.readReason ||
|
|
179
|
+
(async () => {
|
|
180
|
+
const { input } = await import("@inquirer/prompts");
|
|
181
|
+
return input({
|
|
182
|
+
message: "Reason (only its digest is stored; do not enter secrets)",
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
const reason = await readReason();
|
|
186
|
+
const challenge = buildSchedulerAdjudicationChallenge(request);
|
|
187
|
+
const readChallenge =
|
|
188
|
+
dependencies.readChallenge ||
|
|
189
|
+
(async (expected) => {
|
|
190
|
+
const { input } = await import("@inquirer/prompts");
|
|
191
|
+
return input({
|
|
192
|
+
message:
|
|
193
|
+
"Stop every scheduler host, drain already-dispatched work, and verify the external outcome. " +
|
|
194
|
+
`Type this authorization exactly:\n${expected}`,
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
if ((await readChallenge(challenge)) !== challenge) {
|
|
198
|
+
throw schedulerDaemonError(
|
|
199
|
+
"SCHEDULER_ADJUDICATION_CHALLENGE_FAILED",
|
|
200
|
+
"Scheduler adjudication challenge did not match; no change was made",
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return { reasonDigest: schedulerAdjudicationReasonDigest(reason), challenge };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function listSchedulerAdjudicationCases(
|
|
207
|
+
options = {},
|
|
208
|
+
dependencies = {},
|
|
209
|
+
) {
|
|
210
|
+
return withSchedulerStore(dependencies, (store) =>
|
|
211
|
+
store.listAdjudicationCases({ limit: options.limit }),
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export async function getSchedulerAdjudicationCase(
|
|
216
|
+
occurrenceId,
|
|
217
|
+
dependencies = {},
|
|
218
|
+
) {
|
|
219
|
+
return withSchedulerStore(dependencies, (store) => {
|
|
220
|
+
const adjudicationCase = store.getAdjudicationCase(occurrenceId);
|
|
221
|
+
if (!adjudicationCase) {
|
|
222
|
+
throw schedulerDaemonError(
|
|
223
|
+
"SCHEDULER_NOT_FOUND",
|
|
224
|
+
`Scheduler occurrence does not exist: ${occurrenceId}`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return adjudicationCase;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function adjudicateSchedulerOccurrence(
|
|
232
|
+
occurrenceId,
|
|
233
|
+
options,
|
|
234
|
+
dependencies = {},
|
|
235
|
+
) {
|
|
236
|
+
const request = {
|
|
237
|
+
occurrenceId,
|
|
238
|
+
decision: options.decision,
|
|
239
|
+
evidenceDigest: options.expectedEvidenceDigest,
|
|
240
|
+
expectedAttempt: positiveInteger(
|
|
241
|
+
options.expectedAttempt,
|
|
242
|
+
"expectedAttempt",
|
|
243
|
+
),
|
|
244
|
+
expectedFence: positiveInteger(options.expectedFence, "expectedFence"),
|
|
245
|
+
};
|
|
246
|
+
const confirmation = await requireSchedulerAdjudicationTTY(
|
|
247
|
+
request,
|
|
248
|
+
dependencies,
|
|
249
|
+
);
|
|
250
|
+
const operatorIdentity =
|
|
251
|
+
dependencies.operatorIdentity ||
|
|
252
|
+
(() => {
|
|
253
|
+
let username = "unknown";
|
|
254
|
+
try {
|
|
255
|
+
username = userInfo().username || username;
|
|
256
|
+
} catch {
|
|
257
|
+
// The digest still binds the host and uid when username lookup fails.
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
username,
|
|
261
|
+
hostname: hostname(),
|
|
262
|
+
uid: typeof process.getuid === "function" ? process.getuid() : null,
|
|
263
|
+
};
|
|
264
|
+
})();
|
|
265
|
+
return withSchedulerStore(dependencies, (store) =>
|
|
266
|
+
store.adjudicateOccurrence({
|
|
267
|
+
occurrenceId,
|
|
268
|
+
decision: request.decision,
|
|
269
|
+
expectedEvidenceDigest: request.evidenceDigest,
|
|
270
|
+
expectedAttempt: request.expectedAttempt,
|
|
271
|
+
expectedFence: request.expectedFence,
|
|
272
|
+
reasonDigest: confirmation.reasonDigest,
|
|
273
|
+
operatorDigest: schedulerAdjudicationOperatorDigest(operatorIdentity),
|
|
274
|
+
}),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function getSchedulerAuthorityPolicy(
|
|
279
|
+
principalType,
|
|
280
|
+
principalId,
|
|
281
|
+
dependencies = {},
|
|
282
|
+
) {
|
|
283
|
+
return withSchedulerStore(dependencies, (store) => {
|
|
284
|
+
const policy = store.getAuthorityPolicy({
|
|
285
|
+
type: principalType,
|
|
286
|
+
id: principalId,
|
|
287
|
+
});
|
|
288
|
+
if (!policy) {
|
|
289
|
+
throw schedulerDaemonError(
|
|
290
|
+
"SCHEDULER_AUTHORITY_POLICY_NOT_FOUND",
|
|
291
|
+
`Scheduler authority policy does not exist: ${principalType}:${principalId}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return policy;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function setSchedulerAuthorityPolicy(
|
|
299
|
+
principalType,
|
|
300
|
+
principalId,
|
|
301
|
+
options,
|
|
302
|
+
dependencies = {},
|
|
303
|
+
) {
|
|
304
|
+
const windowSeconds = positiveInteger(options.windowSeconds, "windowSeconds");
|
|
305
|
+
const windowMs = windowSeconds * 1_000;
|
|
306
|
+
if (!Number.isSafeInteger(windowMs)) {
|
|
307
|
+
throw schedulerDaemonError(
|
|
308
|
+
"SCHEDULER_DAEMON_INVALID_POLICY",
|
|
309
|
+
"windowSeconds is too large",
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
return withSchedulerStore(dependencies, (store) =>
|
|
313
|
+
store.setAuthorityPolicy(
|
|
314
|
+
{ type: principalType, id: principalId },
|
|
315
|
+
{
|
|
316
|
+
capabilities: parseSchedulerCapabilities(options.capabilities),
|
|
317
|
+
windowMs,
|
|
318
|
+
maxRuns: positiveInteger(options.maxRuns, "maxRuns"),
|
|
319
|
+
maxUnits: positiveInteger(options.maxUnits, "maxUnits"),
|
|
320
|
+
enabled: options.disable !== true,
|
|
321
|
+
expectedRevision: nonnegativeInteger(
|
|
322
|
+
options.expectedRevision,
|
|
323
|
+
"expectedRevision",
|
|
324
|
+
),
|
|
325
|
+
},
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function parseSchedulerDomains(
|
|
331
|
+
value = SCHEDULER_DAEMON_DOMAINS.join(","),
|
|
332
|
+
) {
|
|
333
|
+
if (typeof value !== "string") {
|
|
334
|
+
throw schedulerDaemonError(
|
|
335
|
+
"SCHEDULER_DAEMON_INVALID_DOMAINS",
|
|
336
|
+
"Scheduler domains must be a comma-separated string",
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const requested = [
|
|
340
|
+
...new Set(
|
|
341
|
+
value
|
|
342
|
+
.split(",")
|
|
343
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
344
|
+
.filter(Boolean),
|
|
345
|
+
),
|
|
346
|
+
];
|
|
347
|
+
const unknown = requested.filter(
|
|
348
|
+
(entry) => !SCHEDULER_DAEMON_DOMAINS.includes(entry),
|
|
349
|
+
);
|
|
350
|
+
if (requested.length === 0 || unknown.length > 0) {
|
|
351
|
+
throw schedulerDaemonError(
|
|
352
|
+
"SCHEDULER_DAEMON_INVALID_DOMAINS",
|
|
353
|
+
`Scheduler domains must be selected from: ${SCHEDULER_DAEMON_DOMAINS.join(", ")}`,
|
|
354
|
+
{ unknown },
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return SCHEDULER_DAEMON_DOMAINS.filter((entry) => requested.includes(entry));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function parseSchedulerIntervalMs(
|
|
361
|
+
value = DEFAULT_SERVICE_INTERVAL_MS / 1000,
|
|
362
|
+
) {
|
|
363
|
+
const seconds = Number(value);
|
|
364
|
+
const intervalMs = Math.round(seconds * 1000);
|
|
365
|
+
if (
|
|
366
|
+
!Number.isFinite(seconds) ||
|
|
367
|
+
seconds <= 0 ||
|
|
368
|
+
!Number.isSafeInteger(intervalMs) ||
|
|
369
|
+
intervalMs < MIN_SERVICE_INTERVAL_MS ||
|
|
370
|
+
intervalMs > MAX_SERVICE_INTERVAL_MS
|
|
371
|
+
) {
|
|
372
|
+
throw schedulerDaemonError(
|
|
373
|
+
"SCHEDULER_DAEMON_INVALID_INTERVAL",
|
|
374
|
+
`Scheduler interval must be between ${MIN_SERVICE_INTERVAL_MS / 1000} and ${MAX_SERVICE_INTERVAL_MS / 1000} seconds`,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
return intervalMs;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async function loadSchedulerRuntime() {
|
|
381
|
+
const [
|
|
382
|
+
{ AgentScheduleStore },
|
|
383
|
+
{ runAgendaRun },
|
|
384
|
+
{ CoworkCronSchedulerBridge },
|
|
385
|
+
{ openSchedulerStore },
|
|
386
|
+
{ runCoworkTask },
|
|
387
|
+
] = await Promise.all([
|
|
388
|
+
import("../lib/agent-schedule-store.js"),
|
|
389
|
+
import("./agenda.js"),
|
|
390
|
+
import("../lib/scheduler-kernel/cowork-cron-adapter.js"),
|
|
391
|
+
import("../lib/scheduler-kernel/store.js"),
|
|
392
|
+
import("../lib/cowork-task-runner.js"),
|
|
393
|
+
]);
|
|
394
|
+
return {
|
|
395
|
+
AgentScheduleStore,
|
|
396
|
+
CoworkCronSchedulerBridge,
|
|
397
|
+
openSchedulerStore,
|
|
398
|
+
runAgendaRun,
|
|
399
|
+
runCoworkTask,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function parseAgendaSummary(lines) {
|
|
404
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
405
|
+
try {
|
|
406
|
+
return JSON.parse(lines[index]);
|
|
407
|
+
} catch {
|
|
408
|
+
// Keep looking for the machine-readable summary.
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { due: 0, retired: [], actions: [] };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export async function createDefaultSchedulerService({
|
|
415
|
+
cwd = process.cwd(),
|
|
416
|
+
domains = SCHEDULER_DAEMON_DOMAINS,
|
|
417
|
+
onEvent,
|
|
418
|
+
runtime,
|
|
419
|
+
createService = createSchedulerService,
|
|
420
|
+
} = {}) {
|
|
421
|
+
const selected = Array.isArray(domains)
|
|
422
|
+
? parseSchedulerDomains(domains.join(","))
|
|
423
|
+
: parseSchedulerDomains(domains);
|
|
424
|
+
const loaded = runtime || (await loadSchedulerRuntime());
|
|
425
|
+
const schedulerStore = loaded.openSchedulerStore();
|
|
426
|
+
const drivers = [];
|
|
427
|
+
try {
|
|
428
|
+
if (selected.includes("agenda")) {
|
|
429
|
+
const agendaStore = new loaded.AgentScheduleStore();
|
|
430
|
+
const ownerId = `scheduler-service:agenda:${process.pid}:${randomUUID()}`;
|
|
431
|
+
drivers.push({
|
|
432
|
+
name: "agenda",
|
|
433
|
+
async run({ signal }) {
|
|
434
|
+
const lines = [];
|
|
435
|
+
const code = await loaded.runAgendaRun(
|
|
436
|
+
{ json: true, signal },
|
|
437
|
+
{
|
|
438
|
+
store: agendaStore,
|
|
439
|
+
schedulerStore,
|
|
440
|
+
schedulerOwnerId: ownerId,
|
|
441
|
+
log: (line) => lines.push(String(line)),
|
|
442
|
+
},
|
|
443
|
+
);
|
|
444
|
+
const summary = parseAgendaSummary(lines);
|
|
445
|
+
if (code !== 0) {
|
|
446
|
+
throw schedulerDaemonError(
|
|
447
|
+
"SCHEDULER_DAEMON_AGENDA_FAILED",
|
|
448
|
+
"Agenda scheduler tick completed with one or more failed actions",
|
|
449
|
+
summary,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
return summary;
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (selected.includes("cowork")) {
|
|
458
|
+
const bridge = new loaded.CoworkCronSchedulerBridge({
|
|
459
|
+
cwd,
|
|
460
|
+
schedulerStore,
|
|
461
|
+
runTask: loaded.runCoworkTask,
|
|
462
|
+
ownerId: `scheduler-service:cowork:${process.pid}:${randomUUID()}`,
|
|
463
|
+
});
|
|
464
|
+
drivers.push({
|
|
465
|
+
name: "cowork",
|
|
466
|
+
run: ({ signal }) => bridge.runDue({ signal }),
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return createService({
|
|
471
|
+
drivers,
|
|
472
|
+
onEvent,
|
|
473
|
+
dispose: () => schedulerStore.close(),
|
|
474
|
+
});
|
|
475
|
+
} catch (error) {
|
|
476
|
+
schedulerStore.close();
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function humanEvent(event, log) {
|
|
482
|
+
if (event.type === "scheduler-service-started") {
|
|
483
|
+
log(
|
|
484
|
+
chalk.green(
|
|
485
|
+
`Scheduler service running (${event.drivers.join(", ")}); press Ctrl-C to stop.`,
|
|
486
|
+
),
|
|
487
|
+
);
|
|
488
|
+
} else if (event.type === "scheduler-driver-failed") {
|
|
489
|
+
log(
|
|
490
|
+
chalk.red(
|
|
491
|
+
`${event.driver} scheduler failed (${event.error.code}): ${event.error.message}`,
|
|
492
|
+
),
|
|
493
|
+
);
|
|
494
|
+
} else if (event.type === "scheduler-service-stopped") {
|
|
495
|
+
log(chalk.gray(`Scheduler service stopped after ${event.ticks} tick(s).`));
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export async function runSchedulerDaemon(options = {}, dependencies = {}) {
|
|
500
|
+
const domains = parseSchedulerDomains(options.domains);
|
|
501
|
+
const intervalMs = parseSchedulerIntervalMs(options.interval);
|
|
502
|
+
const log = dependencies.log || ((line) => logger.log(line));
|
|
503
|
+
const processRef = dependencies.processRef || process;
|
|
504
|
+
const controller = new AbortController();
|
|
505
|
+
const externalSignal = options.signal || dependencies.signal;
|
|
506
|
+
const abort = () => controller.abort(externalSignal?.reason);
|
|
507
|
+
if (externalSignal?.aborted) abort();
|
|
508
|
+
else externalSignal?.addEventListener?.("abort", abort, { once: true });
|
|
509
|
+
const onProcessSignal = () => controller.abort();
|
|
510
|
+
processRef.once?.("SIGINT", onProcessSignal);
|
|
511
|
+
processRef.once?.("SIGTERM", onProcessSignal);
|
|
512
|
+
|
|
513
|
+
const emit =
|
|
514
|
+
dependencies.onEvent ||
|
|
515
|
+
((event) => {
|
|
516
|
+
if (options.json) log(JSON.stringify(event));
|
|
517
|
+
else humanEvent(event, log);
|
|
518
|
+
});
|
|
519
|
+
const createDefault =
|
|
520
|
+
dependencies.createDefaultService || createDefaultSchedulerService;
|
|
521
|
+
let service;
|
|
522
|
+
try {
|
|
523
|
+
service = await createDefault({
|
|
524
|
+
cwd: options.cwd || process.cwd(),
|
|
525
|
+
domains,
|
|
526
|
+
onEvent: emit,
|
|
527
|
+
...(dependencies.runtime === undefined
|
|
528
|
+
? {}
|
|
529
|
+
: { runtime: dependencies.runtime }),
|
|
530
|
+
...(dependencies.createService === undefined
|
|
531
|
+
? {}
|
|
532
|
+
: { createService: dependencies.createService }),
|
|
533
|
+
});
|
|
534
|
+
const result = await service.run({
|
|
535
|
+
intervalMs,
|
|
536
|
+
once: options.once === true,
|
|
537
|
+
signal: controller.signal,
|
|
538
|
+
...(dependencies.maxTicks === undefined
|
|
539
|
+
? {}
|
|
540
|
+
: { maxTicks: dependencies.maxTicks }),
|
|
541
|
+
});
|
|
542
|
+
if (options.once === true) {
|
|
543
|
+
if (options.json) {
|
|
544
|
+
log(JSON.stringify({ type: "scheduler-run-summary", ...result }));
|
|
545
|
+
} else {
|
|
546
|
+
log(
|
|
547
|
+
result.status === "degraded"
|
|
548
|
+
? chalk.yellow("Scheduler tick completed with failures.")
|
|
549
|
+
: chalk.green("Scheduler tick completed."),
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
return result.status === "degraded" ? 1 : 0;
|
|
554
|
+
} finally {
|
|
555
|
+
externalSignal?.removeEventListener?.("abort", abort);
|
|
556
|
+
processRef.removeListener?.("SIGINT", onProcessSignal);
|
|
557
|
+
processRef.removeListener?.("SIGTERM", onProcessSignal);
|
|
558
|
+
await service?.close();
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export function registerSchedulerDaemonCommands(daemon, dependencies = {}) {
|
|
563
|
+
const scheduler = daemon
|
|
564
|
+
.command("scheduler")
|
|
565
|
+
.description("Run durable unattended scheduler domains in one process");
|
|
566
|
+
|
|
567
|
+
scheduler
|
|
568
|
+
.command("run", { isDefault: true })
|
|
569
|
+
.description("Run Agenda and Cowork scheduler domains")
|
|
570
|
+
.option("--once", "Run one tick and exit")
|
|
571
|
+
.option(
|
|
572
|
+
"--interval <seconds>",
|
|
573
|
+
"Polling interval in seconds",
|
|
574
|
+
String(DEFAULT_SERVICE_INTERVAL_MS / 1000),
|
|
575
|
+
)
|
|
576
|
+
.option(
|
|
577
|
+
"--domains <list>",
|
|
578
|
+
"Comma-separated domains (agenda,cowork)",
|
|
579
|
+
SCHEDULER_DAEMON_DOMAINS.join(","),
|
|
580
|
+
)
|
|
581
|
+
.option("--json", "Emit newline-delimited scheduler events")
|
|
582
|
+
.action(async (options) => {
|
|
583
|
+
try {
|
|
584
|
+
process.exitCode = await runSchedulerDaemon(options);
|
|
585
|
+
} catch (error) {
|
|
586
|
+
logger.error(chalk.red(error.message));
|
|
587
|
+
process.exitCode = 1;
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const policy = scheduler
|
|
592
|
+
.command("policy")
|
|
593
|
+
.description("Inspect or update scheduler permission and budget policies");
|
|
594
|
+
|
|
595
|
+
policy
|
|
596
|
+
.command("get")
|
|
597
|
+
.description("Read one scheduler authority policy")
|
|
598
|
+
.argument("<principal-type>", "Authority principal type")
|
|
599
|
+
.argument("<principal-id>", "Authority principal id")
|
|
600
|
+
.action(async (principalType, principalId) => {
|
|
601
|
+
try {
|
|
602
|
+
logger.log(
|
|
603
|
+
JSON.stringify(
|
|
604
|
+
await getSchedulerAuthorityPolicy(principalType, principalId),
|
|
605
|
+
null,
|
|
606
|
+
2,
|
|
607
|
+
),
|
|
608
|
+
);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
logger.error(chalk.red(error.message));
|
|
611
|
+
process.exitCode = 1;
|
|
612
|
+
}
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
policy
|
|
616
|
+
.command("set")
|
|
617
|
+
.description("CAS-update exact capabilities and a bounded usage budget")
|
|
618
|
+
.argument("<principal-type>", "Authority principal type")
|
|
619
|
+
.argument("<principal-id>", "Authority principal id")
|
|
620
|
+
.requiredOption(
|
|
621
|
+
"--capabilities <list>",
|
|
622
|
+
"Comma-separated exact capabilities",
|
|
623
|
+
)
|
|
624
|
+
.requiredOption("--window-seconds <n>", "Budget window in seconds")
|
|
625
|
+
.requiredOption("--max-runs <n>", "Maximum runs in the window")
|
|
626
|
+
.requiredOption("--max-units <n>", "Maximum units in the window")
|
|
627
|
+
.requiredOption(
|
|
628
|
+
"--expected-revision <n>",
|
|
629
|
+
"Current policy revision for compare-and-swap",
|
|
630
|
+
)
|
|
631
|
+
.option("--disable", "Disable unattended execution for this principal")
|
|
632
|
+
.action(async (principalType, principalId, options) => {
|
|
633
|
+
try {
|
|
634
|
+
logger.log(
|
|
635
|
+
JSON.stringify(
|
|
636
|
+
await setSchedulerAuthorityPolicy(
|
|
637
|
+
principalType,
|
|
638
|
+
principalId,
|
|
639
|
+
options,
|
|
640
|
+
),
|
|
641
|
+
null,
|
|
642
|
+
2,
|
|
643
|
+
),
|
|
644
|
+
);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
logger.error(chalk.red(error.message));
|
|
647
|
+
process.exitCode = 1;
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
const migration = scheduler
|
|
652
|
+
.command("migration")
|
|
653
|
+
.description("Inspect scheduler domain migration journals");
|
|
654
|
+
|
|
655
|
+
migration
|
|
656
|
+
.command("list", { isDefault: true })
|
|
657
|
+
.description("List scheduler domain migration journals")
|
|
658
|
+
.option("--state <state>", "Filter by migration state")
|
|
659
|
+
.option("--domain <domain>", "Filter by scheduler source domain")
|
|
660
|
+
.option("--limit <n>", "Maximum migrations to return", "50")
|
|
661
|
+
.action(async (options) => {
|
|
662
|
+
try {
|
|
663
|
+
logger.log(
|
|
664
|
+
JSON.stringify(
|
|
665
|
+
await listSchedulerMigrationJournal(options, dependencies),
|
|
666
|
+
null,
|
|
667
|
+
2,
|
|
668
|
+
),
|
|
669
|
+
);
|
|
670
|
+
} catch (error) {
|
|
671
|
+
logger.error(chalk.red(error.message));
|
|
672
|
+
process.exitCode = 1;
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
migration
|
|
677
|
+
.command("rollback")
|
|
678
|
+
.description(
|
|
679
|
+
"Rollback one scheduler migration after source, target, CAS and TTY checks",
|
|
680
|
+
)
|
|
681
|
+
.argument("<migration-id>", "Scheduler domain migration ID")
|
|
682
|
+
.requiredOption(
|
|
683
|
+
"--expected-evidence-digest <digest>",
|
|
684
|
+
"Exact rollback.evidenceDigest from the latest migration show",
|
|
685
|
+
)
|
|
686
|
+
.option("--source-directory <path>", "Agenda or routine source directory")
|
|
687
|
+
.option("--workspace <path>", "Cowork source workspace")
|
|
688
|
+
.option("--session-id <id>", "Saved loop session ID")
|
|
689
|
+
.option("--automation-database <identity>", "Automation database identity")
|
|
690
|
+
.action(async (migrationId, options) => {
|
|
691
|
+
try {
|
|
692
|
+
logger.log(
|
|
693
|
+
JSON.stringify(
|
|
694
|
+
await rollbackSchedulerMigrationJournal(
|
|
695
|
+
migrationId,
|
|
696
|
+
options,
|
|
697
|
+
dependencies,
|
|
698
|
+
),
|
|
699
|
+
null,
|
|
700
|
+
2,
|
|
701
|
+
),
|
|
702
|
+
);
|
|
703
|
+
} catch (error) {
|
|
704
|
+
logger.error(chalk.red(error.message));
|
|
705
|
+
process.exitCode = 1;
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
migration
|
|
710
|
+
.command("show")
|
|
711
|
+
.description("Show a sanitized migration journal and rollback readiness")
|
|
712
|
+
.argument("<migration-id>", "Scheduler domain migration ID")
|
|
713
|
+
.action(async (migrationId) => {
|
|
714
|
+
try {
|
|
715
|
+
logger.log(
|
|
716
|
+
JSON.stringify(
|
|
717
|
+
await showSchedulerMigrationJournal(migrationId, dependencies),
|
|
718
|
+
null,
|
|
719
|
+
2,
|
|
720
|
+
),
|
|
721
|
+
);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
logger.error(chalk.red(error.message));
|
|
724
|
+
process.exitCode = 1;
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
const adjudication = scheduler
|
|
729
|
+
.command("adjudication")
|
|
730
|
+
.description("Inspect or resolve scheduler outcome-unknown dead letters");
|
|
731
|
+
|
|
732
|
+
adjudication
|
|
733
|
+
.command("list", { isDefault: true })
|
|
734
|
+
.description("List unadjudicated outcome-unknown dead letters")
|
|
735
|
+
.option("--limit <n>", "Maximum cases to return", "100")
|
|
736
|
+
.action(async (options) => {
|
|
737
|
+
try {
|
|
738
|
+
logger.log(
|
|
739
|
+
JSON.stringify(
|
|
740
|
+
await listSchedulerAdjudicationCases({
|
|
741
|
+
limit: positiveInteger(options.limit, "limit"),
|
|
742
|
+
}),
|
|
743
|
+
null,
|
|
744
|
+
2,
|
|
745
|
+
),
|
|
746
|
+
);
|
|
747
|
+
} catch (error) {
|
|
748
|
+
logger.error(chalk.red(error.message));
|
|
749
|
+
process.exitCode = 1;
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
adjudication
|
|
754
|
+
.command("show")
|
|
755
|
+
.description("Show one case and its current CAS evidence")
|
|
756
|
+
.argument("<occurrence-id>", "Scheduler occurrence ID")
|
|
757
|
+
.action(async (occurrenceId) => {
|
|
758
|
+
try {
|
|
759
|
+
logger.log(
|
|
760
|
+
JSON.stringify(
|
|
761
|
+
await getSchedulerAdjudicationCase(occurrenceId),
|
|
762
|
+
null,
|
|
763
|
+
2,
|
|
764
|
+
),
|
|
765
|
+
);
|
|
766
|
+
} catch (error) {
|
|
767
|
+
logger.error(chalk.red(error.message));
|
|
768
|
+
process.exitCode = 1;
|
|
769
|
+
}
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
adjudication
|
|
773
|
+
.command("decide")
|
|
774
|
+
.description(
|
|
775
|
+
"After host stop, dispatch drain and external verification, record one monotonic decision",
|
|
776
|
+
)
|
|
777
|
+
.argument("<occurrence-id>", "Scheduler occurrence ID")
|
|
778
|
+
.requiredOption(
|
|
779
|
+
"--decision <decision>",
|
|
780
|
+
"confirmed_applied or confirmed_not_applied",
|
|
781
|
+
)
|
|
782
|
+
.requiredOption(
|
|
783
|
+
"--expected-evidence-digest <digest>",
|
|
784
|
+
"Exact evidenceDigest from the latest show",
|
|
785
|
+
)
|
|
786
|
+
.requiredOption(
|
|
787
|
+
"--expected-attempt <n>",
|
|
788
|
+
"Exact attempt from the latest show",
|
|
789
|
+
)
|
|
790
|
+
.requiredOption("--expected-fence <n>", "Exact fence from the latest show")
|
|
791
|
+
.action(async (occurrenceId, options) => {
|
|
792
|
+
try {
|
|
793
|
+
const result = await adjudicateSchedulerOccurrence(
|
|
794
|
+
occurrenceId,
|
|
795
|
+
options,
|
|
796
|
+
);
|
|
797
|
+
logger.log(JSON.stringify(result, null, 2));
|
|
798
|
+
logger.warn(
|
|
799
|
+
"The typed challenge is operational evidence, not a machine-wide process lease. " +
|
|
800
|
+
"Restart a scheduler host to apply the durable decision; confirmed_not_applied permits one bounded claim.",
|
|
801
|
+
);
|
|
802
|
+
} catch (error) {
|
|
803
|
+
logger.error(chalk.red(error.message));
|
|
804
|
+
process.exitCode = 1;
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
}
|