chainlesschain 0.163.6 → 0.163.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DDIUofA8.js → AIOps-axqQfwv8.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Bm-5xYRn.js → ActionButton-DV-Ro0By.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D5UH4ljI.js → Analytics-Bt72WEVu.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D_ifjOOP.js → AppLayout-BtepG55E.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BY1mXdC7.js → Artifacts-B7KaJ7S_.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bujr4Ml3.js → Audit-FNFaUg7A.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-sioKSTcC.js → BackgroundAgents-DFb6J2wQ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DDOAK8RK.js → Backup-CC7YBhyj.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CtCsVVke.js → BaseInput-RkgTNVtj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DpNj0vnd.js → Chat-hHEmGZy-.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-CZta3Ftr.js → ChatBubbleRenderer-CAP9YhHv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DoHVNSXq.js → Checkbox-z5wXOQP1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DlVl7Qmv.js → Codegen-DVaDJnxy.js} +1 -1
- package/src/assets/web-panel/assets/{Col-GIqoPYty.js → Col-CJmaRDR5.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CIAPEVji.js → Community-DF29_2bI.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BN7JiI_h.js → Compact-DtkQkBzn.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CqUWCM9B.js → Compliance-xoHDxp7I.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-A4iPp7A4.js → Cowork-BUm-R7ht.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B2zeXXWb.js → Cron-Cgl14ZkB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BR2xmWr8.js → Crosschain-BNcbmoqh.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BzFDjrIv.js → DID-Bwja0Zo9.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DKtDo3yA.js → Dashboard-Bk5XjEdJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-RNx43_YG.js → Dropdown-Ci36Oitq.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-SwMt4Rcy.js → EmailListRenderer--U04ZN4q.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CsYebZ4X.js → FamilyGuardDashboard-B2gNPR2s.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-BmRKvBTy.js → Federation-C4KghYn7.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CVxGTA-0.js → FormItemContext-QL7x_Q_0.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-x2iXpyB7.js → GenericCardRenderer-F3Ryltwm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DfqbkiOP.js → Git-2HassK5k.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-RuHyyj5O.js → Governance-DoBIp7oe.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Bn7uZ7rI.js → Inference--W01QhZ-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DlP7XefZ.js → KnowledgeGraph-DmvwqGpT.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BYNdOiCd.js → Logs-BBJ4yAel.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-aMtgN-R6.js → MarkdownRenderer-BLCZMzhJ.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-JQYmDqM8.js → Marketplace-D5lNngSI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Cyx4yZ-9.js → McpTools-CkEVOMKH.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-VDuGZTN2.js → Memory-B1Ow1xVE.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-_yKo97gi.js → MobileBridge-SgYbUfb6.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CXRXW3q7.js → MobileProjects-BWdOvQAj.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-acTGtfLH.js → Mtc-DcTXSSma.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-ClpjXfkY.js → MtcAudit-Dt3WG4oj.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-_6Tn3o96.js → Multisig-B9B38UZx.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CK7pbZzP.js → NLProgramming-B0S4GGvG.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Czy6V_P8.js → Notes-DZ5U58Ng.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cqx-UVv3.js → NotificationSettings-PjL5ZW34.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C69WDxGy.js → OrderTableRenderer-DrAX2ESt.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DFnA9vLQ.js → Organization-Cv1GYs-c.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ReZkk2yT.js → Overflow-Bbkp_PL3.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DzjSI0aI.js → P2P-BD8QUgoZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DKTf5xIP.js → PdhVaultBrowser-BO-2PeaB.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DhQ5klhR.js → Permissions-D_OPVa02.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CUL1iHrW.js → PersonalDataHub-mh-pEmaB.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DU88iu4N.js → Pipeline-6JC2EDdb.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bw-sYfyK.js → Privacy-CUshzUAG.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bq3Q5y0z.js → ProjectInit-Cs2Aalri.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C8spzpho.js → ProjectSettings-1ak49tcj.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BV4oApsm.js +1 -0
- package/src/assets/web-panel/assets/{Providers-Bf4eTP9W.js → Providers-CXXxvafH.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-Dme9BDU1.js → QrScannerModal-PTcDsF58.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CIXfv1rE.js → QuickAsk-CDwucrJw.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CIxvdeZ-.js → Recommend-CJlLVRXR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C6k7MExI.js → RemoteSession-DlN-ETsO.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CISDz2bl.js → Reputation-DI23LR6m.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CdnDxXyf.js → Row-Cl7UQMrM.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C-QpRHsl.js → RssFeed-CexHNfh8.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DBjaGzSn.js → Search-BneBWvRN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CQKukOeE.js → Security-DX7Phgrp.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DZz86jWb.js → Services-BWXO8Mte.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-jb8RMK0d.js → Skeleton-DCyOjKPJ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CdY76fZG.js → Skills-CXqERsTB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-C_CSiZX9.js → Sla-bhbrbz8E.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-3IPU0HBZ.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-qvimGO0O.js → SyncSettings-DBNTQLbM.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Dng5LPJC.js → Tasks-quP0bkN-.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bwqq5Wll.js → Templates-BouWmgho.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DNWuwP7Z.js → Tenant-qv0D9s7z.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B-6Othpp.js → Terminal-BxvbE8BB.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DKpKZfzx.js → TimelineRenderer-D-IeLnJ2.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DzfGMd2J.js → Tokens-CFMKKyVG.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BXwUi74J.js → Trigger-L8as3UuJ.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BNzUq4ui.js → Trust-MxmA4VZG.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BYugxww4.js → UkeySign-C0VvJYRi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-DdauhtSJ.js → VideoEditing-Bf5QZj8N.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CSC9bCe_.js → Wallet-BIJ9riRz.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Q8AN3bTO.js → WebAuthn-B8p9Mhkw.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-d-LBGCuA.js → WorkflowEditor-BVsuaPen.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLVDCgsZ.js → chat-BfkjoKpV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BZlPSEHl.js → colors-DeAlsWK8.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-IWseP6R6.js → compact-item-Cs97bz5j.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DnVRV3Ao.js → createContext-BqbG60LH.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DPty0mGN.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DLQZA_Wn.js → hasIn-i_RY0b1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_N4-Gq9.js → index-0-agYlWL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DB0MJgF_.js → index-79V_zv1s.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOZJBTWZ.js → index-B-3tEpTc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUq0c7gq.js → index-B-JVTrpr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dy0R5ZED.js → index-B1CbOH1i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxp7uDD8.js → index-B5UkyA3c.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpVrK6fr.js → index-BESLFt4q.js} +1 -1
- package/src/assets/web-panel/assets/{index-CT9vVN-X.js → index-BOVkf1K3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOoQpuQY.js → index-BYUVuwKt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CINJQEj6.js → index-BcUXAB2s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CarMTIyQ.js → index-BkBi5TmC.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5dgnilC.js → index-Bm7lLR4c.js} +1 -1
- package/src/assets/web-panel/assets/index-Bt7g0Vq_.js +1 -0
- package/src/assets/web-panel/assets/{index-CbQNL0ue.js → index-BuLQobzW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpBaJ3TD.js → index-CE2VtTlp.js} +1 -1
- package/src/assets/web-panel/assets/{index-jWb70ZgZ.js → index-CED2PL_y.js} +1 -1
- package/src/assets/web-panel/assets/{index-BEu2lMBy.js → index-CG3PRyAL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DocEhinm.js → index-CIG8fQ2_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D_LQodeJ.js → index-COdGA-8n.js} +1 -1
- package/src/assets/web-panel/assets/{index-DZpYeqrh.js → index-CeWoNHau.js} +1 -1
- package/src/assets/web-panel/assets/{index-gt8bNefW.js → index-Cf3WCLmy.js} +1 -1
- package/src/assets/web-panel/assets/{index-N_GoWZX4.js → index-CgsqdnRV.js} +1 -1
- package/src/assets/web-panel/assets/index-ChVT0p9u.js +1 -0
- package/src/assets/web-panel/assets/{index-CZDekj7T.js → index-D2B4VPyP.js} +1 -1
- package/src/assets/web-panel/assets/{index-YSw_gxlZ.js → index-D9evtvg2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTbxp4UH.js → index-DUUrv141.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bbj2NS_I.js → index-DiSkx4UB.js} +3 -3
- package/src/assets/web-panel/assets/{index-BZs5bBnk.js → index-Dk2qouAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DalA0TKC.js → index-Dk7-v4hP.js} +1 -1
- package/src/assets/web-panel/assets/{index-1jeAudHd.js → index-DmbfhJUP.js} +1 -1
- package/src/assets/web-panel/assets/{index-C1ifZtXo.js → index-DqBgBpgp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhA3QEni.js → index-PT_5sBMA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSjilZ9L.js → index-U4f5izHz.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2FqfMvU.js → index-YXgLng0K.js} +1 -1
- package/src/assets/web-panel/assets/{index-COev0W2g.js → index-ZxO_-E8a.js} +1 -1
- package/src/assets/web-panel/assets/{index-M7wxfAbm.js → index-dIHr4B_u.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjWnONdy.js → index-gyFpvL7l.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dkwsi1Kn.js → index-s1opAL-M.js} +1 -1
- package/src/assets/web-panel/assets/{index-hwVDeoC0.js → index-yObj1O4n.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Co67Kqmb.js → initDefaultProps-C9h9yKL3.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BMOxQSF_.js → motion-BvmLsPdu.js} +1 -1
- package/src/assets/web-panel/assets/{move-C5fWDw-N.js → move-CoL4ekU1.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BJevpL0e.js → mtc-parser-C4pMy6xV.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCzNU8s6.js → omit-DPuJfNxx.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CybWvDPk.js → pickAttrs-D0KEeaK2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BMWS9HnW.js → placementArrow-BpkyINxt.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoL2UnGY.js → responsiveObserve-DQI_wOq8.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DWNfXraU.js → slide-DYfdDiRg.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BquTM3tX.js → statusUtils-B5dtvb0D.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BEkCleim.js → styleChecker-CF310mSj.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-Oecnq7EA.js → useFlexGapSupport-D1Qp4BLp.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CkDffJ4F.js → useFs-C7_iyvJK.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-qbvKXesB.js → usePersonalDataHub-DZUt_fIm.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C6v8n6mA.js → vnode-D3dsi0lZ.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-4CTeFILV.js → zoom-WdbOlvdQ.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +90 -1
- package/src/commands/chat.js +11 -2
- package/src/commands/compact.js +106 -23
- package/src/commands/loop.js +28 -10
- package/src/commands/routine.js +1 -1
- package/src/commands/scheduler-daemon.js +359 -2
- package/src/commands/session-observability.js +305 -0
- package/src/commands/session.js +38 -0
- package/src/data/changelog.json +14 -1
- package/src/gateways/ws/ws-agent-handler.js +439 -5
- package/src/harness/jsonl-session-store.js +463 -125
- package/src/harness/prompt-compressor.js +86 -13
- package/src/harness/provider-backed-compaction.js +100 -27
- package/src/lib/advisor-runtime.js +3 -1
- package/src/lib/agent-schedule-store.js +414 -4
- package/src/lib/agent-team/team-adjudication.js +40 -1
- package/src/lib/automation-engine.js +494 -0
- package/src/lib/background-agent-supervisor.js +793 -215
- package/src/lib/background-command-argv.js +24 -0
- package/src/lib/background-interaction-journal.js +361 -52
- package/src/lib/background-interaction-resolver.js +32 -0
- package/src/lib/background-launch-profile.js +2 -0
- package/src/lib/causal-observability.js +2661 -0
- package/src/lib/chat-session-admission.js +76 -0
- package/src/lib/cost-budget.js +5 -7
- package/src/lib/cowork/ab-comparator-cli.js +11 -0
- package/src/lib/cowork/debate-review-cli.js +12 -0
- package/src/lib/cowork-adapter.js +159 -38
- package/src/lib/cowork-cron.js +357 -1
- package/src/lib/cowork-task-runner.js +112 -17
- package/src/lib/delivery-coordinator.js +142 -0
- package/src/lib/direct-model-usage.js +121 -0
- package/src/lib/file-lines.js +183 -47
- package/src/lib/llm-pricing.js +12 -17
- package/src/lib/loop.js +26 -2
- package/src/lib/routine-store.js +380 -21
- package/src/lib/runtime-usage-ledger.js +182 -0
- package/src/lib/scheduler-kernel/agenda-adapter.js +308 -3
- package/src/lib/scheduler-kernel/automation-adapter.js +410 -8
- package/src/lib/scheduler-kernel/automation-event-adapter.js +36 -0
- package/src/lib/scheduler-kernel/cowork-cron-adapter.js +285 -3
- package/src/lib/scheduler-kernel/loop-adapter.js +541 -6
- package/src/lib/scheduler-kernel/migration-admin.js +346 -0
- package/src/lib/scheduler-kernel/migration-rollback-admin.js +932 -0
- package/src/lib/scheduler-kernel/routine-adapter.js +394 -20
- package/src/lib/scheduler-kernel/runtime.js +63 -1
- package/src/lib/scheduler-kernel/source-locator-path.js +142 -0
- package/src/lib/scheduler-kernel/store.js +2586 -85
- package/src/lib/session-host-lease.js +171 -2
- package/src/lib/session-host-snapshot.js +14 -3
- package/src/lib/session-persistence-failure.js +1 -0
- package/src/lib/session-tail.js +179 -34
- package/src/lib/session-transcript-structure.js +96 -0
- package/src/lib/sub-agent-context.js +328 -22
- package/src/lib/ws-agent-handler.js +4 -1
- package/src/repl/agent-repl.js +732 -91
- package/src/repl/chat-repl.js +5 -1
- package/src/runtime/agent-core.js +1272 -207
- package/src/runtime/fallback-model.js +5 -0
- package/src/runtime/headless-runner.js +324 -82
- package/src/runtime/headless-stream.js +232 -26
- package/src/runtime/policies/agent-policy.js +4 -0
- package/src/workers/background-agent-worker.js +403 -96
- package/src/assets/web-panel/assets/Projects-Dd-xuPIc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-C7tT9iad.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Jzu62CLC.js +0 -1
- package/src/assets/web-panel/assets/index-Zb2MeDSy.js +0 -1
- package/src/assets/web-panel/assets/index-fikqml4k.js +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { hostname, userInfo } from "node:os";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import { logger } from "../lib/logger.js";
|
|
4
5
|
import {
|
|
@@ -7,6 +8,18 @@ import {
|
|
|
7
8
|
MIN_SERVICE_INTERVAL_MS,
|
|
8
9
|
createSchedulerService,
|
|
9
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";
|
|
10
23
|
|
|
11
24
|
export const SCHEDULER_DAEMON_DOMAINS = Object.freeze(["agenda", "cowork"]);
|
|
12
25
|
|
|
@@ -64,17 +77,204 @@ export function parseSchedulerCapabilities(value) {
|
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
async function withSchedulerStore(dependencies, operation) {
|
|
80
|
+
if (dependencies.schedulerStore) {
|
|
81
|
+
return operation(dependencies.schedulerStore);
|
|
82
|
+
}
|
|
67
83
|
const openStore =
|
|
68
84
|
dependencies.openSchedulerStore ||
|
|
69
85
|
(await import("../lib/scheduler-kernel/store.js")).openSchedulerStore;
|
|
70
86
|
const store = openStore();
|
|
71
87
|
try {
|
|
72
|
-
return operation(store);
|
|
88
|
+
return await operation(store);
|
|
73
89
|
} finally {
|
|
74
90
|
store.close();
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
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
|
+
|
|
78
278
|
export async function getSchedulerAuthorityPolicy(
|
|
79
279
|
principalType,
|
|
80
280
|
principalId,
|
|
@@ -359,7 +559,7 @@ export async function runSchedulerDaemon(options = {}, dependencies = {}) {
|
|
|
359
559
|
}
|
|
360
560
|
}
|
|
361
561
|
|
|
362
|
-
export function registerSchedulerDaemonCommands(daemon) {
|
|
562
|
+
export function registerSchedulerDaemonCommands(daemon, dependencies = {}) {
|
|
363
563
|
const scheduler = daemon
|
|
364
564
|
.command("scheduler")
|
|
365
565
|
.description("Run durable unattended scheduler domains in one process");
|
|
@@ -447,4 +647,161 @@ export function registerSchedulerDaemonCommands(daemon) {
|
|
|
447
647
|
process.exitCode = 1;
|
|
448
648
|
}
|
|
449
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
|
+
});
|
|
450
807
|
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { TextDecoder } from "node:util";
|
|
5
|
+
import { numericOption } from "../lib/cli-numeric.js";
|
|
6
|
+
import { loadConfig } from "../lib/config-manager.js";
|
|
7
|
+
import { mergePricing } from "../lib/llm-pricing.js";
|
|
8
|
+
import {
|
|
9
|
+
buildCausalObservabilityReport,
|
|
10
|
+
causalSessionIds,
|
|
11
|
+
createCausalObservabilityLimitTracker,
|
|
12
|
+
createVerifiedSessionObservabilityProjection,
|
|
13
|
+
normalizeCausalRequest,
|
|
14
|
+
projectVerifiedDelivery,
|
|
15
|
+
selectCausalDeliveries,
|
|
16
|
+
} from "../lib/causal-observability.js";
|
|
17
|
+
import { restoreDeliveryFlow } from "../lib/delivery-coordinator.js";
|
|
18
|
+
import { readVerifiedProjection } from "../harness/jsonl-session-store.js";
|
|
19
|
+
import { ensurePrivateFile } from "../lib/secure-fs.js";
|
|
20
|
+
import {
|
|
21
|
+
sameFileStatIdentity,
|
|
22
|
+
samePathHandleFileIdentity,
|
|
23
|
+
withTrustedFileParentSync,
|
|
24
|
+
} from "../lib/secure-file-identity.js";
|
|
25
|
+
|
|
26
|
+
const MAX_INPUT_BYTES = 16 * 1024 * 1024;
|
|
27
|
+
|
|
28
|
+
function readTrustedJson(filePath, deps = {}) {
|
|
29
|
+
const runtimeFs = deps.fs || fs;
|
|
30
|
+
const trustedParent =
|
|
31
|
+
deps.withTrustedFileParentSync || withTrustedFileParentSync;
|
|
32
|
+
return trustedParent(
|
|
33
|
+
runtimeFs,
|
|
34
|
+
filePath,
|
|
35
|
+
({ canonicalPath, parentDevice }) => {
|
|
36
|
+
const before = runtimeFs.lstatSync(canonicalPath, { bigint: true });
|
|
37
|
+
if (
|
|
38
|
+
before.isSymbolicLink() ||
|
|
39
|
+
!before.isFile() ||
|
|
40
|
+
Number(before.nlink) !== 1
|
|
41
|
+
) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`input must be a regular, single-link file: ${filePath}`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const size = Number(before.size);
|
|
47
|
+
if (size <= 0 || size > MAX_INPUT_BYTES) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`input exceeds the safe ${MAX_INPUT_BYTES}-byte limit: ${filePath}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
let descriptor = null;
|
|
53
|
+
try {
|
|
54
|
+
descriptor = runtimeFs.openSync(
|
|
55
|
+
canonicalPath,
|
|
56
|
+
runtimeFs.constants.O_RDONLY |
|
|
57
|
+
Number(runtimeFs.constants.O_NOFOLLOW || 0),
|
|
58
|
+
);
|
|
59
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
60
|
+
if (
|
|
61
|
+
!opened.isFile() ||
|
|
62
|
+
Number(opened.nlink) !== 1 ||
|
|
63
|
+
!samePathHandleFileIdentity(before, opened, parentDevice)
|
|
64
|
+
) {
|
|
65
|
+
throw new Error(`input identity changed while opening: ${filePath}`);
|
|
66
|
+
}
|
|
67
|
+
const bounded = Buffer.allocUnsafe(MAX_INPUT_BYTES + 1);
|
|
68
|
+
let bytesRead = 0;
|
|
69
|
+
while (bytesRead < bounded.length) {
|
|
70
|
+
const count = runtimeFs.readSync(
|
|
71
|
+
descriptor,
|
|
72
|
+
bounded,
|
|
73
|
+
bytesRead,
|
|
74
|
+
bounded.length - bytesRead,
|
|
75
|
+
null,
|
|
76
|
+
);
|
|
77
|
+
if (count === 0) break;
|
|
78
|
+
bytesRead += count;
|
|
79
|
+
}
|
|
80
|
+
if (bytesRead > MAX_INPUT_BYTES) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`input exceeds the safe ${MAX_INPUT_BYTES}-byte limit: ${filePath}`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const body = bounded.subarray(0, bytesRead);
|
|
86
|
+
const after = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
87
|
+
if (
|
|
88
|
+
Number(after.size) !== body.length ||
|
|
89
|
+
!sameFileStatIdentity(opened, after)
|
|
90
|
+
) {
|
|
91
|
+
throw new Error(`input changed while being read: ${filePath}`);
|
|
92
|
+
}
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
canonicalPath,
|
|
95
|
+
value: JSON.parse(
|
|
96
|
+
new TextDecoder("utf-8", { fatal: true }).decode(body),
|
|
97
|
+
),
|
|
98
|
+
});
|
|
99
|
+
} finally {
|
|
100
|
+
if (descriptor !== null) runtimeFs.closeSync(descriptor);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function atomicWritePrivate(outputPath, contents, deps = {}) {
|
|
107
|
+
const runtimeFs = deps.fs || fs;
|
|
108
|
+
const absolute = path.resolve(outputPath);
|
|
109
|
+
const trustedParent =
|
|
110
|
+
deps.withTrustedFileParentSync || withTrustedFileParentSync;
|
|
111
|
+
return trustedParent(runtimeFs, absolute, ({ canonicalPath, parentPath }) => {
|
|
112
|
+
if (runtimeFs.existsSync(canonicalPath)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`output already exists; refusing to overwrite: ${absolute}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const temporary = path.join(
|
|
118
|
+
parentPath,
|
|
119
|
+
`.${path.basename(canonicalPath)}.${randomUUID()}.tmp`,
|
|
120
|
+
);
|
|
121
|
+
let published = false;
|
|
122
|
+
try {
|
|
123
|
+
runtimeFs.writeFileSync(temporary, contents, {
|
|
124
|
+
encoding: "utf8",
|
|
125
|
+
mode: 0o600,
|
|
126
|
+
flag: "wx",
|
|
127
|
+
});
|
|
128
|
+
(deps.ensurePrivateFile || ensurePrivateFile)(temporary, {
|
|
129
|
+
applyWindowsAcl: true,
|
|
130
|
+
failIfUnavailable: true,
|
|
131
|
+
});
|
|
132
|
+
// Publishing a fully-written hard link is atomic and, unlike rename,
|
|
133
|
+
// fails closed if another writer created the requested output first.
|
|
134
|
+
runtimeFs.linkSync(temporary, canonicalPath);
|
|
135
|
+
published = true;
|
|
136
|
+
runtimeFs.rmSync(temporary);
|
|
137
|
+
(deps.ensurePrivateFile || ensurePrivateFile)(canonicalPath, {
|
|
138
|
+
applyWindowsAcl: true,
|
|
139
|
+
failIfUnavailable: true,
|
|
140
|
+
});
|
|
141
|
+
return canonicalPath;
|
|
142
|
+
} catch (error) {
|
|
143
|
+
try {
|
|
144
|
+
runtimeFs.rmSync(temporary, { force: true });
|
|
145
|
+
if (published) runtimeFs.rmSync(canonicalPath, { force: true });
|
|
146
|
+
} catch {
|
|
147
|
+
// Preserve the authoritative write error.
|
|
148
|
+
}
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function budgetOverrides(options) {
|
|
155
|
+
const parse = (value, name, extra = {}) =>
|
|
156
|
+
value == null
|
|
157
|
+
? undefined
|
|
158
|
+
: numericOption(value, { name, min: 0, ...extra });
|
|
159
|
+
return {
|
|
160
|
+
maxTokens: parse(options.maxTokens, "--max-tokens", { integer: true }),
|
|
161
|
+
maxUsd: parse(options.maxUsd, "--max-usd"),
|
|
162
|
+
maxRetries: parse(options.maxRetries, "--max-retries", { integer: true }),
|
|
163
|
+
maxRetryRatio: parse(options.maxRetryRatio, "--max-retry-ratio", {
|
|
164
|
+
max: 1,
|
|
165
|
+
}),
|
|
166
|
+
maxToolP95Ms: parse(options.maxToolP95Ms, "--max-tool-p95-ms"),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function nonEmptyFilterOption(value, name) {
|
|
171
|
+
if (value == null) return undefined;
|
|
172
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
173
|
+
throw new Error(`${name} must be a non-empty string`);
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function optionFilter(options) {
|
|
179
|
+
return {
|
|
180
|
+
workspaceId: nonEmptyFilterOption(options.workspace, "--workspace"),
|
|
181
|
+
teamId: nonEmptyFilterOption(options.team, "--team"),
|
|
182
|
+
policyId: nonEmptyFilterOption(options.policy, "--policy"),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function mergeDefined(base, overrides) {
|
|
187
|
+
const definedOverrides = Object.fromEntries(
|
|
188
|
+
Object.entries(overrides).filter(([, value]) => value !== undefined),
|
|
189
|
+
);
|
|
190
|
+
return Object.fromEntries(
|
|
191
|
+
Object.entries({ ...base, ...definedOverrides }).filter(
|
|
192
|
+
([, value]) => value !== undefined,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function runSessionObservability(requestPath, options = {}, deps = {}) {
|
|
198
|
+
try {
|
|
199
|
+
const trustedRequest = readTrustedJson(requestPath, deps);
|
|
200
|
+
const request = normalizeCausalRequest(trustedRequest.value);
|
|
201
|
+
const requestDir = path.dirname(trustedRequest.canonicalPath);
|
|
202
|
+
const deliveries = request.deliveryStates.map((statePath) => {
|
|
203
|
+
const resolved = path.resolve(requestDir, statePath);
|
|
204
|
+
const state = (deps.restoreDeliveryFlow || restoreDeliveryFlow)(
|
|
205
|
+
readTrustedJson(resolved, deps).value,
|
|
206
|
+
);
|
|
207
|
+
return projectVerifiedDelivery(state);
|
|
208
|
+
});
|
|
209
|
+
const filter = mergeDefined(request.filter, optionFilter(options));
|
|
210
|
+
const selectedDeliveries = (
|
|
211
|
+
deps.selectCausalDeliveries || selectCausalDeliveries
|
|
212
|
+
)(deliveries, filter);
|
|
213
|
+
const selectedSessionIds = (deps.causalSessionIds || causalSessionIds)(
|
|
214
|
+
selectedDeliveries,
|
|
215
|
+
);
|
|
216
|
+
const limitTracker = (
|
|
217
|
+
deps.createCausalObservabilityLimitTracker ||
|
|
218
|
+
createCausalObservabilityLimitTracker
|
|
219
|
+
)();
|
|
220
|
+
for (const delivery of selectedDeliveries) {
|
|
221
|
+
limitTracker.acceptDelivery(delivery);
|
|
222
|
+
}
|
|
223
|
+
const sessionsById = new Map();
|
|
224
|
+
const pricingTable = (deps.mergePricing || mergePricing)(
|
|
225
|
+
(deps.loadConfig || loadConfig)().llm?.pricing,
|
|
226
|
+
);
|
|
227
|
+
for (const sessionId of selectedSessionIds) {
|
|
228
|
+
const session = (deps.readVerifiedProjection || readVerifiedProjection)(
|
|
229
|
+
sessionId,
|
|
230
|
+
() =>
|
|
231
|
+
(
|
|
232
|
+
deps.createVerifiedSessionObservabilityProjection ||
|
|
233
|
+
createVerifiedSessionObservabilityProjection
|
|
234
|
+
)(sessionId, { pricingTable }),
|
|
235
|
+
);
|
|
236
|
+
limitTracker.acceptSession(session);
|
|
237
|
+
sessionsById.set(sessionId, session);
|
|
238
|
+
}
|
|
239
|
+
const budgets = mergeDefined(request.budgets, budgetOverrides(options));
|
|
240
|
+
const report = (
|
|
241
|
+
deps.buildCausalObservabilityReport || buildCausalObservabilityReport
|
|
242
|
+
)({
|
|
243
|
+
deliveries: selectedDeliveries,
|
|
244
|
+
sessionsById,
|
|
245
|
+
filter,
|
|
246
|
+
budgets,
|
|
247
|
+
pricingTable,
|
|
248
|
+
generatedAt: deps.now ? deps.now() : new Date().toISOString(),
|
|
249
|
+
});
|
|
250
|
+
const output = `${JSON.stringify(report, null, 2)}\n`;
|
|
251
|
+
if (options.output) {
|
|
252
|
+
const written = atomicWritePrivate(options.output, output, deps);
|
|
253
|
+
if (!options.json) process.stdout.write(`${written}\n`);
|
|
254
|
+
} else {
|
|
255
|
+
process.stdout.write(output);
|
|
256
|
+
}
|
|
257
|
+
if (
|
|
258
|
+
options.strictBudget === true &&
|
|
259
|
+
["exceeded", "unknown"].includes(report.budget.status)
|
|
260
|
+
) {
|
|
261
|
+
return 2;
|
|
262
|
+
}
|
|
263
|
+
return 0;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
process.stderr.write(`Causal observability failed: ${error.message}\n`);
|
|
266
|
+
return 1;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function registerSessionObservabilitySubcommand(session) {
|
|
271
|
+
session
|
|
272
|
+
.command("observability <request>")
|
|
273
|
+
.description(
|
|
274
|
+
"Export verified token/USD, retry, tool latency, diff, gate, artifact and PR causality",
|
|
275
|
+
)
|
|
276
|
+
.option("--workspace <id>", "Only delivery authority for this workspace")
|
|
277
|
+
.option("--team <id>", "Only delivery authority for this team")
|
|
278
|
+
.option("--policy <id>", "Only delivery authority for this policy")
|
|
279
|
+
.option("--max-tokens <n>", "Alert when aggregate tokens exceed n")
|
|
280
|
+
.option(
|
|
281
|
+
"--max-usd <amount>",
|
|
282
|
+
"Alert when estimated priced USD exceeds amount",
|
|
283
|
+
)
|
|
284
|
+
.option("--max-retries <n>", "Alert when automatic LLM retries exceed n")
|
|
285
|
+
.option(
|
|
286
|
+
"--max-retry-ratio <ratio>",
|
|
287
|
+
"Alert when approximate retry/call ratio exceeds 0..1",
|
|
288
|
+
)
|
|
289
|
+
.option(
|
|
290
|
+
"--max-tool-p95-ms <ms>",
|
|
291
|
+
"Alert when fully observed tool P95 exceeds ms",
|
|
292
|
+
)
|
|
293
|
+
.option(
|
|
294
|
+
"--strict-budget",
|
|
295
|
+
"Exit 2 when a budget is exceeded or cannot be evaluated",
|
|
296
|
+
)
|
|
297
|
+
.option(
|
|
298
|
+
"--output <path>",
|
|
299
|
+
"Atomically write owner-only JSON instead of stdout",
|
|
300
|
+
)
|
|
301
|
+
.option("--json", "Machine-readable output (default when stdout is used)")
|
|
302
|
+
.action((requestPath, options) => {
|
|
303
|
+
process.exitCode = runSessionObservability(requestPath, options);
|
|
304
|
+
});
|
|
305
|
+
}
|