chainlesschain 0.162.188 → 0.162.189
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-Brr1LVcE.js → AIOps-DbZft8w0.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-DoOZpwfN.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-B0J0KLTW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-D9eIR0qq.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-BnZXEVeo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-X1ce4Bjn.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-BvWR-y5v.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BublSGEd.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-CR_77Hso.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-DixW52tq.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CCv1C87K.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-B03Cc3dd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CA06-6pU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-Bj881J52.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-UZLTPoaL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-kGwzRb_0.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-KundoYM8.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-BYZyoqDe.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BhrrppBX.js} +1 -1
- package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-CdC6EguZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-CcQx3J4y.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-BpijtEzQ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-DfZjeZTe.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-Okdi3pJm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-mH-HebKW.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-xivV69x7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-1rh_YBwg.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-eAXkNxdY.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-CS6dMVTN.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-KchHtiHt.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-TabqHW9B.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-CsmGcX5X.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-BaPFnnyz.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-2m-sUUf7.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-D5nVZBRu.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bo8MLZvT.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-3GDvamGa.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-D2zGuZCH.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-BBVQ4t6t.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-BIOCyOGp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DVwBGpzc.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-BDnHfIfX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BU2Yqq5H.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D3SRKaa7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-C2B8ahyn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-2iwQzG65.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-69ca_WeR.js → PdhVaultBrowser-CoPiWqMN.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-yV5XNXo5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-uCDJhMUF.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-hN7ucMOA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-CgU0ptN4.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-CfYPiT6j.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-B0A2C4nw.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-BE5C0ajn.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-CstQPf8W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-Co3z_ynk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-DMTS6pQ6.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-ETw4odUa.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-BTVqQgKq.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CBKKwwtO.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BHUQ438I.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-B4NqeUsF.js} +1 -1
- package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-BRjoYG5_.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-CxQzu3p3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton-BKzwwmWO.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-DrsFcgth.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-D21tmXCH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-CjyjLfp8.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-Cu45-tnx.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-oMcILYOj.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-Bh-Zcr5R.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-68ns63aI.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BLJkFykE.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-xcrJLq-O.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-C6PpFJtf.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-BaKA7tdM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-BzhA57Jc.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-4_U3JkIR.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-162AANxX.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-B1viuql_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-ZVWazhCy.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-tzdyUk-1.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-DmrHNuwq.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-DAJ_M-PK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-uIzmYwLs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-D_ZmuLwb.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-CDt_shTL.js} +1 -1
- package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-BAvSmJXW.js} +1 -1
- package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-BDF6EQBs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-BHvlm0In.js} +1 -1
- package/src/assets/web-panel/assets/{index-COG61sRv.js → index-BRa4kJ_S.js} +1 -1
- package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-BTCXf1t-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Bbxjfstq.js} +1 -1
- package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-Bx4d6c1k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-ByzGvpR9.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-C16fYKFD.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-C5WcdJH2.js} +3 -3
- package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-CG0lY4h3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-CKyd472S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-CaelyMgF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CbD_WW_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-CcZ9xJI9.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CeUpKjvi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-Cvak6Oe2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-CzXqvMuP.js} +1 -1
- package/src/assets/web-panel/assets/index-D-Swwqld.js +1 -0
- package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-D4n1lGTB.js} +1 -1
- package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-D9OE8B3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-DBFmcaCQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-DdUMcwRB.js} +1 -1
- package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-DeGBoLQA.js} +1 -1
- package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-DfSSy9uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-DqDSF2RW.js} +1 -1
- package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-DvFhhe6r.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaslYch6.js → index-DwfvPe-j.js} +1 -1
- package/src/assets/web-panel/assets/index-FMZAMKy8.js +1 -0
- package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-FyplHuNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-GcKOHD_c.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-OzTxFIJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-QsgfSZrV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-Z8P4BexE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-clSX4PEc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-ifxmu8SY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-rS3gl_68.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-rXdwUeE3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-saf3VNeP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-pmfaD-aK.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-CaQ2CpYF.js} +1 -1
- package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-CkKTqNbI.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-Dd4xsgRI.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-BFuA5YEs.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-DDfxOAJc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-Be595JCp.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-7byljUM3.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-CTWfOTaY.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-CNZOuIxM.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-DwOcBV78.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-CzyQjH9r.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-DVfZiS5N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-BYme7fbY.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +22 -0
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint.js +3 -0
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/data/changelog.json +13 -0
- package/src/lib/agent-core.js +1 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +319 -22
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/process-execution-broker/index.js +438 -5
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +3556 -0
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +140 -23
- package/src/repl/agent-repl.js +19 -1
- package/src/runtime/agent-core.js +366 -122
- package/src/runtime/headless-runner.js +12 -0
- package/src/runtime/headless-stream.js +3 -0
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-COBbEOlj.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-oOVIv9wq.js +0 -5
- package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
- package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
- package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
import { emitHooksV2Event } from "../hooks-v2-producers.js";
|
|
18
18
|
|
|
19
|
+
function leaseFencingToken(lease) {
|
|
20
|
+
return lease?.fencingToken ?? lease?.leaseId ?? null;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
export class TeamRunner {
|
|
20
24
|
/**
|
|
21
25
|
* @param {TaskLeaseRegistry} registry
|
|
@@ -68,6 +72,11 @@ export class TeamRunner {
|
|
|
68
72
|
typeof opts.beforeTask === "function" ? opts.beforeTask : null;
|
|
69
73
|
this.afterTask =
|
|
70
74
|
typeof opts.afterTask === "function" ? opts.afterTask : null;
|
|
75
|
+
// A lease that expires and is reacquired receives a new fencing identity.
|
|
76
|
+
// Persist that identity synchronously before the executor can settle under
|
|
77
|
+
// it. Ordinary renewals keep the same identity and do not call this hook.
|
|
78
|
+
this.onLeaseChanged =
|
|
79
|
+
typeof opts.onLeaseChanged === "function" ? opts.onLeaseChanged : null;
|
|
71
80
|
this._executions = 0;
|
|
72
81
|
this._reservedExecutions = 0;
|
|
73
82
|
this._inFlight = 0;
|
|
@@ -78,6 +87,7 @@ export class TeamRunner {
|
|
|
78
87
|
this._activeKeys = new Set();
|
|
79
88
|
this._members = new Map(); // holder → lifecycle record
|
|
80
89
|
this._budgetStopped = false;
|
|
90
|
+
this._registryBudgetReason = null;
|
|
81
91
|
this._fatalError = null;
|
|
82
92
|
// Idle workers wait on graph progress instead of polling the full graph
|
|
83
93
|
// through setTimeout(0). This matters once a team has many workers but only
|
|
@@ -85,6 +95,7 @@ export class TeamRunner {
|
|
|
85
95
|
this._progressWaiters = new Set();
|
|
86
96
|
this._workerCount = 1;
|
|
87
97
|
this._claimControllers = new Set();
|
|
98
|
+
this._claimsByKey = new Map();
|
|
88
99
|
this._wallTimer = null;
|
|
89
100
|
}
|
|
90
101
|
|
|
@@ -152,6 +163,121 @@ export class TeamRunner {
|
|
|
152
163
|
}
|
|
153
164
|
}
|
|
154
165
|
|
|
166
|
+
/** Snapshot of local in-flight claims for an IDE/operator control surface. */
|
|
167
|
+
activeClaims() {
|
|
168
|
+
return Array.from(this._claimsByKey.values()).map((claim) => ({
|
|
169
|
+
key: claim.key,
|
|
170
|
+
holder: claim.holder,
|
|
171
|
+
leaseId: claim.leaseId,
|
|
172
|
+
fencingToken: claim.fencingToken,
|
|
173
|
+
interrupted: !!claim.interruption,
|
|
174
|
+
interruption: claim.interruption ? { ...claim.interruption } : null,
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Request human takeover of an executing task.
|
|
180
|
+
*
|
|
181
|
+
* Aborting the executor cannot prove whether an external side effect landed,
|
|
182
|
+
* so the task always settles fail-closed into adjudication rather than being
|
|
183
|
+
* silently retried. A durable control log supplies requestId/evidenceDigest
|
|
184
|
+
* in production and must bind the exact active holder/lease/fencing identity;
|
|
185
|
+
* keeping this method synchronous makes it safe to call from a file-control
|
|
186
|
+
* watcher or an embedded IDE host.
|
|
187
|
+
*/
|
|
188
|
+
interruptTask(
|
|
189
|
+
key,
|
|
190
|
+
{
|
|
191
|
+
holder = null,
|
|
192
|
+
leaseId = null,
|
|
193
|
+
fencingToken = null,
|
|
194
|
+
reason = "human takeover requested",
|
|
195
|
+
actor = "human",
|
|
196
|
+
requestId = null,
|
|
197
|
+
evidenceDigest = null,
|
|
198
|
+
} = {},
|
|
199
|
+
) {
|
|
200
|
+
const claim = this._claimsByKey.get(key);
|
|
201
|
+
if (!claim) return { ok: false, reason: "not_active" };
|
|
202
|
+
if (
|
|
203
|
+
holder !== claim.holder ||
|
|
204
|
+
leaseId !== claim.leaseId ||
|
|
205
|
+
fencingToken !== claim.fencingToken
|
|
206
|
+
) {
|
|
207
|
+
return { ok: false, reason: "stale_attempt" };
|
|
208
|
+
}
|
|
209
|
+
if (claim.interruption) {
|
|
210
|
+
if (
|
|
211
|
+
requestId &&
|
|
212
|
+
claim.interruption.requestId &&
|
|
213
|
+
requestId !== claim.interruption.requestId
|
|
214
|
+
) {
|
|
215
|
+
return { ok: false, reason: "already_interrupted" };
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
ok: true,
|
|
219
|
+
idempotent: true,
|
|
220
|
+
interruption: { ...claim.interruption },
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
claim.interruption = {
|
|
224
|
+
holder: claim.holder,
|
|
225
|
+
leaseId: claim.leaseId,
|
|
226
|
+
fencingToken: claim.fencingToken,
|
|
227
|
+
requestId: requestId || null,
|
|
228
|
+
actor: actor ? String(actor) : "human",
|
|
229
|
+
reason: String(reason || "human takeover requested"),
|
|
230
|
+
evidenceDigest: evidenceDigest || null,
|
|
231
|
+
requestedAt: this._now(),
|
|
232
|
+
};
|
|
233
|
+
const error = this._interruptionError(claim);
|
|
234
|
+
try {
|
|
235
|
+
claim.abortController.abort(error);
|
|
236
|
+
} catch {
|
|
237
|
+
/* settlement remains fenced by the task lease */
|
|
238
|
+
}
|
|
239
|
+
this._emit("task:interrupt-requested", {
|
|
240
|
+
key,
|
|
241
|
+
holder: claim.holder,
|
|
242
|
+
leaseId: claim.leaseId,
|
|
243
|
+
fencingToken: claim.fencingToken,
|
|
244
|
+
requestId: claim.interruption.requestId,
|
|
245
|
+
actor: claim.interruption.actor,
|
|
246
|
+
reason: claim.interruption.reason,
|
|
247
|
+
});
|
|
248
|
+
this._signalProgress();
|
|
249
|
+
return { ok: true, interruption: { ...claim.interruption } };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Fail the coordinator closed (for example when its durable control log is
|
|
254
|
+
* corrupt or rolled back). Active executors are aborted and settle as
|
|
255
|
+
* adjudication-required because their external outcome may be unknown.
|
|
256
|
+
*/
|
|
257
|
+
abortRun(error, { requireAdjudication = true } = {}) {
|
|
258
|
+
const failure =
|
|
259
|
+
error instanceof Error ? error : new Error(String(error || "team abort"));
|
|
260
|
+
if (requireAdjudication) {
|
|
261
|
+
failure.retryable = false;
|
|
262
|
+
failure.adjudication = {
|
|
263
|
+
code: failure.code || "TEAM_RUN_ABORTED_ADJUDICATION_REQUIRED",
|
|
264
|
+
reason: failure.message,
|
|
265
|
+
evidenceDigest: null,
|
|
266
|
+
requestedAt: this._now(),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
this._setFatal(failure, { phase: "external-control" });
|
|
270
|
+
for (const claim of this._claimsByKey.values()) {
|
|
271
|
+
claim.coordinatorAbort = failure;
|
|
272
|
+
try {
|
|
273
|
+
claim.abortController.abort(failure);
|
|
274
|
+
} catch {
|
|
275
|
+
/* settlement remains fenced by the task lease */
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { ok: true, activeClaims: this._claimsByKey.size };
|
|
279
|
+
}
|
|
280
|
+
|
|
155
281
|
/**
|
|
156
282
|
* Run the graph to completion (all tasks terminal) or until no teammate can
|
|
157
283
|
* make progress (everything remaining is blocked/leased). Returns a summary.
|
|
@@ -185,7 +311,9 @@ export class TeamRunner {
|
|
|
185
311
|
}
|
|
186
312
|
for (const controller of this._claimControllers) {
|
|
187
313
|
try {
|
|
188
|
-
|
|
314
|
+
const error = new Error("Team wall-clock budget exhausted");
|
|
315
|
+
error.code = "TEAM_WALL_BUDGET_EXHAUSTED";
|
|
316
|
+
controller.abort(error);
|
|
189
317
|
} catch {
|
|
190
318
|
/* every settlement remains fenced by its lease id */
|
|
191
319
|
}
|
|
@@ -244,7 +372,9 @@ export class TeamRunner {
|
|
|
244
372
|
requestedTeammates: this.teammates,
|
|
245
373
|
activeTeammates: workerCount,
|
|
246
374
|
budgetStopped: this._budgetStopped,
|
|
247
|
-
budgetReason: this.budget
|
|
375
|
+
budgetReason: this.budget
|
|
376
|
+
? this.budget.reason()
|
|
377
|
+
: this._registryBudgetReason,
|
|
248
378
|
members: this.members(),
|
|
249
379
|
messages: this.mailbox ? this.mailbox.size() : 0,
|
|
250
380
|
mailboxStatus: this.mailbox?.status?.() || null,
|
|
@@ -319,16 +449,26 @@ export class TeamRunner {
|
|
|
319
449
|
}
|
|
320
450
|
const acq = this.registry.acquire(key, { holder, ttlMs: this.ttlMs });
|
|
321
451
|
if (!acq.ok) {
|
|
452
|
+
if (
|
|
453
|
+
["max-tasks", "max-tokens", "max-usd", "max-wall-ms"].includes(
|
|
454
|
+
acq.reason,
|
|
455
|
+
)
|
|
456
|
+
) {
|
|
457
|
+
this._budgetStopped = true;
|
|
458
|
+
this._registryBudgetReason = acq.reason;
|
|
459
|
+
this._emit("run:budget-exhausted", { reason: acq.reason });
|
|
460
|
+
this._setState(holder, "shutdown", { reason: acq.reason });
|
|
461
|
+
this._signalProgress();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
322
464
|
// Lost the race to a peer (or it just got blocked) — try another.
|
|
323
465
|
this._setState(holder, "idle");
|
|
324
466
|
await this._tick();
|
|
325
467
|
continue;
|
|
326
468
|
}
|
|
327
|
-
const budgetReservation =
|
|
328
|
-
|
|
329
|
-
this.registry.getTask(key),
|
|
330
|
-
acq.lease,
|
|
331
|
-
);
|
|
469
|
+
const budgetReservation = acq.budgetReservation
|
|
470
|
+
? { ok: true, ...acq.budgetReservation }
|
|
471
|
+
: this._reserveTaskBudget(key, this.registry.getTask(key), acq.lease);
|
|
332
472
|
if (!budgetReservation.ok) {
|
|
333
473
|
this.registry.release(key, {
|
|
334
474
|
holder,
|
|
@@ -378,6 +518,29 @@ export class TeamRunner {
|
|
|
378
518
|
this._setState(holder, "shutdown", { reason: "fatal-error" });
|
|
379
519
|
return;
|
|
380
520
|
}
|
|
521
|
+
// `beforeTask` may persist a durable claim slowly enough to cross the
|
|
522
|
+
// wall-clock deadline. The timer's AbortSignal is advisory: this is the
|
|
523
|
+
// last authoritative fence before handing control to the executor.
|
|
524
|
+
// Preserve an explicit human interruption so `_execute` can settle it
|
|
525
|
+
// fail-closed for adjudication instead of silently abandoning it.
|
|
526
|
+
const preparedBudgetReason = this.budget?.reason?.();
|
|
527
|
+
if (
|
|
528
|
+
!claim.interruption &&
|
|
529
|
+
!claim.coordinatorAbort &&
|
|
530
|
+
preparedBudgetReason === "max-wall-ms"
|
|
531
|
+
) {
|
|
532
|
+
if (!this._budgetStopped) {
|
|
533
|
+
this._budgetStopped = true;
|
|
534
|
+
this._emit("run:budget-exhausted", {
|
|
535
|
+
reason: preparedBudgetReason,
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
this._abandonClaim(holder, key, claim);
|
|
539
|
+
this._setState(holder, "shutdown", {
|
|
540
|
+
reason: preparedBudgetReason,
|
|
541
|
+
});
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
381
544
|
await this._execute(holder, key, claim);
|
|
382
545
|
}
|
|
383
546
|
}
|
|
@@ -445,17 +608,21 @@ export class TeamRunner {
|
|
|
445
608
|
key,
|
|
446
609
|
lease,
|
|
447
610
|
leaseId: lease?.leaseId,
|
|
611
|
+
fencingToken: leaseFencingToken(lease),
|
|
448
612
|
heartbeat: null,
|
|
449
613
|
lost: false,
|
|
450
614
|
abortController: new AbortController(),
|
|
451
615
|
budgetReservation,
|
|
452
616
|
budgetSettled: false,
|
|
617
|
+
interruption: null,
|
|
618
|
+
coordinatorAbort: null,
|
|
453
619
|
};
|
|
454
620
|
// Fence the task immediately after acquire, before any async durable hook.
|
|
455
621
|
// Otherwise a slow beforeTask can outlive the TTL and a peer can run the
|
|
456
622
|
// same side effect concurrently.
|
|
457
623
|
this._activeKeys.add(key);
|
|
458
624
|
this._claimControllers.add(claim.abortController);
|
|
625
|
+
this._claimsByKey.set(key, claim);
|
|
459
626
|
const effectiveTtl =
|
|
460
627
|
this.ttlMs > 0 ? this.ttlMs : this.registry.defaultTtlMs || 60000;
|
|
461
628
|
const every = Math.max(
|
|
@@ -470,6 +637,71 @@ export class TeamRunner {
|
|
|
470
637
|
return claim;
|
|
471
638
|
}
|
|
472
639
|
|
|
640
|
+
_applyRenewedLease(claim, lease) {
|
|
641
|
+
const previousLease = claim.lease;
|
|
642
|
+
const previousLeaseId = claim.leaseId;
|
|
643
|
+
const previousFencingToken = claim.fencingToken;
|
|
644
|
+
claim.lease = lease;
|
|
645
|
+
claim.leaseId = lease?.leaseId;
|
|
646
|
+
claim.fencingToken = leaseFencingToken(lease);
|
|
647
|
+
const identityChanged =
|
|
648
|
+
claim.leaseId !== previousLeaseId ||
|
|
649
|
+
claim.fencingToken !== previousFencingToken;
|
|
650
|
+
if (!identityChanged || !this.onLeaseChanged) {
|
|
651
|
+
return { ok: true, identityChanged };
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const callbackResult = this.onLeaseChanged({
|
|
655
|
+
key: claim.key,
|
|
656
|
+
holder: claim.holder,
|
|
657
|
+
previousLease: previousLease ? { ...previousLease } : null,
|
|
658
|
+
lease: lease ? { ...lease } : null,
|
|
659
|
+
previousLeaseId,
|
|
660
|
+
leaseId: claim.leaseId,
|
|
661
|
+
previousFencingToken,
|
|
662
|
+
fencingToken: claim.fencingToken,
|
|
663
|
+
});
|
|
664
|
+
if (
|
|
665
|
+
callbackResult &&
|
|
666
|
+
(typeof callbackResult === "object" ||
|
|
667
|
+
typeof callbackResult === "function") &&
|
|
668
|
+
typeof callbackResult.then === "function"
|
|
669
|
+
) {
|
|
670
|
+
Promise.resolve(callbackResult).catch(() => {});
|
|
671
|
+
throw new Error("onLeaseChanged must be synchronous");
|
|
672
|
+
}
|
|
673
|
+
return { ok: true, identityChanged: true };
|
|
674
|
+
} catch (error) {
|
|
675
|
+
const cause =
|
|
676
|
+
error instanceof Error
|
|
677
|
+
? error
|
|
678
|
+
: new Error(String(error || "lease identity persistence failed"));
|
|
679
|
+
const failure = new Error(cause.message, { cause });
|
|
680
|
+
failure.code = "TEAM_LEASE_CHANGE_PERSIST_FAILED";
|
|
681
|
+
failure.retryable = false;
|
|
682
|
+
failure.adjudication = failure.adjudication || {
|
|
683
|
+
code: failure.code,
|
|
684
|
+
reason: failure.message,
|
|
685
|
+
evidenceDigest: null,
|
|
686
|
+
requestedAt: this._now(),
|
|
687
|
+
holder: claim.holder,
|
|
688
|
+
leaseId: claim.leaseId,
|
|
689
|
+
fencingToken: claim.fencingToken,
|
|
690
|
+
};
|
|
691
|
+
claim.coordinatorAbort = failure;
|
|
692
|
+
this._setFatal(failure, {
|
|
693
|
+
phase: "lease-change",
|
|
694
|
+
key: claim.key,
|
|
695
|
+
holder: claim.holder,
|
|
696
|
+
});
|
|
697
|
+
return {
|
|
698
|
+
ok: false,
|
|
699
|
+
reason: "lease_change_persist_failed",
|
|
700
|
+
error: failure,
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
473
705
|
_renewClaim(claim) {
|
|
474
706
|
if (!claim || claim.lost) {
|
|
475
707
|
return { ok: false, reason: "lease_lost" };
|
|
@@ -489,11 +721,12 @@ export class TeamRunner {
|
|
|
489
721
|
ttlMs: this.ttlMs,
|
|
490
722
|
});
|
|
491
723
|
if (result?.ok) {
|
|
492
|
-
|
|
493
|
-
|
|
724
|
+
const applied = this._applyRenewedLease(claim, result.lease);
|
|
725
|
+
if (!applied.ok) result = applied;
|
|
494
726
|
}
|
|
495
727
|
} else if (result?.ok) {
|
|
496
|
-
|
|
728
|
+
const applied = this._applyRenewedLease(claim, result.lease);
|
|
729
|
+
if (!applied.ok) result = applied;
|
|
497
730
|
}
|
|
498
731
|
} catch {
|
|
499
732
|
result = { ok: false, reason: "renew_failed" };
|
|
@@ -502,7 +735,7 @@ export class TeamRunner {
|
|
|
502
735
|
claim.lost = true;
|
|
503
736
|
try {
|
|
504
737
|
claim.abortController.abort(
|
|
505
|
-
new Error(`Lease for task "${claim.key}" was lost`),
|
|
738
|
+
result?.error || new Error(`Lease for task "${claim.key}" was lost`),
|
|
506
739
|
);
|
|
507
740
|
} catch {
|
|
508
741
|
/* the fencing token still prevents stale settlement */
|
|
@@ -517,6 +750,34 @@ export class TeamRunner {
|
|
|
517
750
|
if (claim.heartbeat) clearInterval(claim.heartbeat);
|
|
518
751
|
this._activeKeys.delete(claim.key);
|
|
519
752
|
this._claimControllers.delete(claim.abortController);
|
|
753
|
+
if (this._claimsByKey.get(claim.key) === claim) {
|
|
754
|
+
this._claimsByKey.delete(claim.key);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
_interruptionError(claim, executionResult = null) {
|
|
759
|
+
const interruption = claim?.interruption || {};
|
|
760
|
+
const error = new Error(interruption.reason || "human takeover requested");
|
|
761
|
+
error.code = "TEAM_TASK_HUMAN_INTERRUPTED";
|
|
762
|
+
error.retryable = false;
|
|
763
|
+
error.adjudication = {
|
|
764
|
+
code: error.code,
|
|
765
|
+
reason: error.message,
|
|
766
|
+
evidenceDigest: interruption.evidenceDigest || null,
|
|
767
|
+
requestedAt: interruption.requestedAt || this._now(),
|
|
768
|
+
requestId: interruption.requestId || null,
|
|
769
|
+
actor: interruption.actor || "human",
|
|
770
|
+
holder: interruption.holder || claim?.holder || null,
|
|
771
|
+
leaseId: interruption.leaseId || claim?.leaseId || null,
|
|
772
|
+
fencingToken: interruption.fencingToken ?? claim?.fencingToken ?? null,
|
|
773
|
+
};
|
|
774
|
+
if (executionResult && typeof executionResult === "object") {
|
|
775
|
+
error.usage = executionResult.usage || null;
|
|
776
|
+
error.usageRecords = executionResult.usageRecords || null;
|
|
777
|
+
error.provider = executionResult.provider;
|
|
778
|
+
error.model = executionResult.model;
|
|
779
|
+
}
|
|
780
|
+
return error;
|
|
520
781
|
}
|
|
521
782
|
|
|
522
783
|
_abandonClaim(holder, key, claim) {
|
|
@@ -683,6 +944,9 @@ export class TeamRunner {
|
|
|
683
944
|
})
|
|
684
945
|
: null;
|
|
685
946
|
try {
|
|
947
|
+
if (claim.interruption) {
|
|
948
|
+
throw this._interruptionError(claim);
|
|
949
|
+
}
|
|
686
950
|
const result = await this.runTask({
|
|
687
951
|
key,
|
|
688
952
|
task,
|
|
@@ -695,6 +959,33 @@ export class TeamRunner {
|
|
|
695
959
|
budgetReservation: claim.budgetReservation,
|
|
696
960
|
signal: claim.abortController.signal,
|
|
697
961
|
});
|
|
962
|
+
// The executor may ignore AbortSignal or finish concurrently with an IDE
|
|
963
|
+
// takeover request. Never turn that race into an automatic success:
|
|
964
|
+
// preserve usage, then fail closed for explicit adjudication.
|
|
965
|
+
if (claim.interruption) {
|
|
966
|
+
throw this._interruptionError(claim, result);
|
|
967
|
+
}
|
|
968
|
+
if (claim.coordinatorAbort) {
|
|
969
|
+
throw claim.coordinatorAbort;
|
|
970
|
+
}
|
|
971
|
+
// Executors are allowed to return after observing (or even ignoring) an
|
|
972
|
+
// aborted signal. Re-read the authoritative budget before recording or
|
|
973
|
+
// completing so a late result can never become a phantom success.
|
|
974
|
+
if (
|
|
975
|
+
this.budget?.reason?.() === "max-wall-ms" ||
|
|
976
|
+
claim.abortController.signal.reason?.code ===
|
|
977
|
+
"TEAM_WALL_BUDGET_EXHAUSTED"
|
|
978
|
+
) {
|
|
979
|
+
if (!this._budgetStopped) {
|
|
980
|
+
this._budgetStopped = true;
|
|
981
|
+
this._emit("run:budget-exhausted", {
|
|
982
|
+
reason: "max-wall-ms",
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
const error = new Error("Team wall-clock budget exhausted");
|
|
986
|
+
error.code = "TEAM_WALL_BUDGET_EXHAUSTED";
|
|
987
|
+
throw error;
|
|
988
|
+
}
|
|
698
989
|
// Fold usage/cost into the team budget regardless — the task DID execute
|
|
699
990
|
// and consumed resources. `--exec` shell tasks carry no usage → only the
|
|
700
991
|
// task-count / wall-clock dimensions move.
|
|
@@ -763,8 +1054,9 @@ export class TeamRunner {
|
|
|
763
1054
|
});
|
|
764
1055
|
}
|
|
765
1056
|
} catch (err) {
|
|
1057
|
+
const failure = claim.coordinatorAbort || err;
|
|
766
1058
|
if (span) {
|
|
767
|
-
span.recordException(
|
|
1059
|
+
span.recordException(failure, "task_failure");
|
|
768
1060
|
span.end({ status: "error" });
|
|
769
1061
|
}
|
|
770
1062
|
// A failed task still consumed a task-count slot (and any wall-clock) — fold
|
|
@@ -772,10 +1064,10 @@ export class TeamRunner {
|
|
|
772
1064
|
if (this.budget && !claim.budgetSettled) {
|
|
773
1065
|
this.budget.record(
|
|
774
1066
|
{
|
|
775
|
-
usage:
|
|
776
|
-
usageRecords:
|
|
777
|
-
provider:
|
|
778
|
-
model:
|
|
1067
|
+
usage: failure?.usage || null,
|
|
1068
|
+
usageRecords: failure?.usageRecords || null,
|
|
1069
|
+
provider: failure?.provider,
|
|
1070
|
+
model: failure?.model,
|
|
779
1071
|
reservationId: claim.budgetReservation?.id,
|
|
780
1072
|
},
|
|
781
1073
|
this._now(),
|
|
@@ -785,34 +1077,39 @@ export class TeamRunner {
|
|
|
785
1077
|
const outcome = this.registry.fail(key, {
|
|
786
1078
|
holder,
|
|
787
1079
|
leaseId: claim.leaseId,
|
|
788
|
-
error:
|
|
789
|
-
retryable:
|
|
1080
|
+
error: failure?.message || String(failure),
|
|
1081
|
+
retryable: failure?.retryable !== false,
|
|
1082
|
+
adjudication: failure?.adjudication || null,
|
|
790
1083
|
});
|
|
791
1084
|
if (outcome?.ok) {
|
|
792
1085
|
this._setState(holder, "failed-task", {
|
|
793
|
-
error:
|
|
1086
|
+
error: failure?.message || String(failure),
|
|
794
1087
|
});
|
|
795
1088
|
this._emit("task:failed", {
|
|
796
1089
|
key,
|
|
797
1090
|
holder,
|
|
798
|
-
error:
|
|
1091
|
+
error: failure?.message || String(failure),
|
|
799
1092
|
retry: outcome.retry === true,
|
|
800
1093
|
attempts: outcome.attempts,
|
|
1094
|
+
interrupted: failure?.code === "TEAM_TASK_HUMAN_INTERRUPTED",
|
|
1095
|
+
requestId: failure?.adjudication?.requestId || null,
|
|
801
1096
|
});
|
|
802
1097
|
await this._notifySettlement({
|
|
803
1098
|
key,
|
|
804
1099
|
holder,
|
|
805
1100
|
status: outcome.retry === true ? "pending" : "failed",
|
|
806
|
-
error:
|
|
1101
|
+
error: failure?.message || String(failure),
|
|
807
1102
|
retry: outcome.retry === true,
|
|
808
1103
|
attempts: outcome.attempts,
|
|
1104
|
+
interrupted: failure?.code === "TEAM_TASK_HUMAN_INTERRUPTED",
|
|
1105
|
+
requestId: failure?.adjudication?.requestId || null,
|
|
809
1106
|
});
|
|
810
1107
|
} else {
|
|
811
1108
|
this._emit("task:failure-discarded", {
|
|
812
1109
|
key,
|
|
813
1110
|
holder,
|
|
814
1111
|
reason: outcome?.reason || "lease_lost",
|
|
815
|
-
error:
|
|
1112
|
+
error: failure?.message || String(failure),
|
|
816
1113
|
ms: this._now() - startedAt,
|
|
817
1114
|
});
|
|
818
1115
|
await this._notifySettlement({
|