chainlesschain 0.162.187 → 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-CETaO3s8.js → AIOps-DbZft8w0.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CAe7E9TW.js → ActionButton-DoOZpwfN.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BGr2mzwO.js → Analytics-B0J0KLTW.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BEoY-NcT.js → AppLayout-D9eIR0qq.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-DUZivCgt.js → Artifacts-BnZXEVeo.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C3yBs68k.js → Audit-X1ce4Bjn.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-C9KwiY3O.js → BackgroundAgents-BvWR-y5v.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BoSWD_C8.js → Backup-BublSGEd.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Dp09GkxL.js → BaseInput-CR_77Hso.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-5t9d-Ja0.js → Chat-DixW52tq.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DmVku4GH.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-8QJFlkqI.js → Checkbox-CCv1C87K.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BJVFQ3lL.js → Codegen-B03Cc3dd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-6hdLD0oB.js → Col-CA06-6pU.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B8opo0uY.js → Community-Bj881J52.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dv8HudiN.js → Compact-UZLTPoaL.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DhxuCS5N.js → Compliance-kGwzRb_0.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Dggy6VIL.js → Cowork-KundoYM8.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DSmDqJTT.js → Cron-BYZyoqDe.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-7bZq0j5f.js → Crosschain-BhrrppBX.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BWY4kh-H.js → DID-CdC6EguZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-bOzPZxlu.js → Dashboard-CcQx3J4y.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BTFJHqQu.js → Dropdown-BpijtEzQ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BC3MOdtT.js → EmailListRenderer-DfZjeZTe.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-CFHAdTTY.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DZ_4aF_n.js → Federation-Okdi3pJm.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BZje6UTj.js → FormItemContext-mH-HebKW.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-D7QfCvjN.js → GenericCardRenderer-xivV69x7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DuG7qANG.js → Git-1rh_YBwg.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BDmYAgTh.js → Governance-eAXkNxdY.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DSXAfc1Q.js → Inference-CS6dMVTN.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DusleniP.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CpImmA8p.js → Logs-KchHtiHt.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BZ6LJBkk.js → MarkdownRenderer-TabqHW9B.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DXS8CXkE.js → Marketplace-CsmGcX5X.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-D151UfUI.js → McpTools-BaPFnnyz.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DKDxnoA1.js → Memory-2m-sUUf7.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BFZ95iKM.js → MobileBridge-D5nVZBRu.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-CDAR-grF.js → MobileProjects-Bo8MLZvT.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-OGJ95xJs.js → Mtc-3GDvamGa.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-BmGTo1XC.js → MtcAudit-D2zGuZCH.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-XumJRqOa.js → Multisig-BBVQ4t6t.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DHhhRZed.js → NLProgramming-BIOCyOGp.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-B9rGxNNr.js → Notes-DVwBGpzc.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-DClLbMYV.js → NotificationSettings-BDnHfIfX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-I8OohT7N.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-M6WzaYVa.js → Organization-D3SRKaa7.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cb3B5KtM.js → Overflow-C2B8ahyn.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BGk57kya.js → P2P-2iwQzG65.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CXcKyPqK.js → PdhVaultBrowser-CoPiWqMN.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-hsfJ-Y0e.js → Permissions-yV5XNXo5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BSBii4js.js → PersonalDataHub-uCDJhMUF.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BNw52rey.js → Pipeline-hN7ucMOA.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DUpfue4E.js → Privacy-CgU0ptN4.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C68NsP3h.js → ProjectInit-CfYPiT6j.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BJ2It9v4.js → ProjectSettings-B0A2C4nw.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BE5C0ajn.js +1 -0
- package/src/assets/web-panel/assets/{Providers-EnU4VWwS.js → Providers-CstQPf8W.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BRt5-B_I.js → QrScannerModal-Co3z_ynk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Jd5L9UL2.js → QuickAsk-DMTS6pQ6.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BNDhoVIu.js → Recommend-ETw4odUa.js} +1 -1
- package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
- package/src/assets/web-panel/assets/{Reputation-D1wQPUNv.js → Reputation-BTVqQgKq.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DnjRegul.js → Row-CBKKwwtO.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C_T4I-nF.js → RssFeed-BHUQ438I.js} +2 -2
- package/src/assets/web-panel/assets/{Search-C4lrxlLh.js → Search-B4NqeUsF.js} +1 -1
- package/src/assets/web-panel/assets/{Security-ByYT-VzP.js → Security-BRjoYG5_.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BHpyEqB5.js → Services-CxQzu3p3.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C0I23vG-.js → Skeleton-BKzwwmWO.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DGZXz22K.js → Skills-DrsFcgth.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DL_urP9A.js → Sla-D21tmXCH.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-6x-hUNQ_.js → SpeechSettings-CjyjLfp8.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Crk5TCXq.js → SyncSettings-Cu45-tnx.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-rZGq5PZs.js → Tasks-oMcILYOj.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DT5el8Q8.js → Templates-Bh-Zcr5R.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-mlgeyMK8.js → Tenant-68ns63aI.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-C8HEA-lM.js → Terminal-BLJkFykE.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DvlqBayB.js → TimelineRenderer-xcrJLq-O.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C7lDcNmT.js → Tokens-C6PpFJtf.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CM8nCnIJ.js → Trigger-BaKA7tdM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BQDLUQ9x.js → Trust-BzhA57Jc.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CVqM0OFE.js → UkeySign-4_U3JkIR.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BvAcQpzT.js → VideoEditing-162AANxX.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-zFVFaAZ2.js → Wallet-B1viuql_.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Cc522ums.js → WebAuthn-ZVWazhCy.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Bg_wxpDo.js → WorkflowEditor-tzdyUk-1.js} +1 -1
- package/src/assets/web-panel/assets/{chat-jg0QwC8K.js → chat-DmrHNuwq.js} +1 -1
- package/src/assets/web-panel/assets/{colors-ALzYz1gO.js → colors-DAJ_M-PK.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DXHYUoqi.js → compact-item-uIzmYwLs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BsGsBM5m.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-BccMLzxA.js → hasIn-CDt_shTL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cg9cOVe9.js → index-BAvSmJXW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTTeAiOP.js → index-BDF6EQBs.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3k3ZwkQ.js → index-BHvlm0In.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQ1ORdp-.js → index-BRa4kJ_S.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcJzphrP.js → index-BTCXf1t-.js} +1 -1
- package/src/assets/web-panel/assets/{index-rIy96PeM.js → index-Bbxjfstq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSFESurl.js → index-Bx4d6c1k.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cmg_Npx5.js → index-ByzGvpR9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CahDve8M.js → index-C16fYKFD.js} +1 -1
- package/src/assets/web-panel/assets/{index-2VstcImq.js → index-C5WcdJH2.js} +3 -3
- package/src/assets/web-panel/assets/{index-C3Rf8wIM.js → index-CG0lY4h3.js} +1 -1
- package/src/assets/web-panel/assets/{index-uzykuL_R.js → index-CKyd472S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBVIFX-x.js → index-CaelyMgF.js} +1 -1
- package/src/assets/web-panel/assets/{index-TeCof1s3.js → index-CbD_WW_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfF0OGKU.js → index-CcZ9xJI9.js} +1 -1
- package/src/assets/web-panel/assets/{index-oWltafBn.js → index-CeUpKjvi.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWLWBgfe.js → index-Cvak6Oe2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0AbXsDy.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-BfV8RDlg.js → index-D4n1lGTB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dxqiu9-F.js → index-D9OE8B3d.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdCmJ_bG.js → index-DBFmcaCQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgjQ-kdT.js → index-DdUMcwRB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBbbr1YB.js → index-DeGBoLQA.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dmk7dD-T.js → index-DfSSy9uy.js} +1 -1
- package/src/assets/web-panel/assets/{index-JS2LXdd0.js → index-DqDSF2RW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9oV2iRR.js → index-DvFhhe6r.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvsII10i.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-CrZHUsOZ.js → index-FyplHuNt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CLzPzN75.js → index-GcKOHD_c.js} +1 -1
- package/src/assets/web-panel/assets/{index-koT8Q86Z.js → index-OzTxFIJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-08diKcTm.js → index-QsgfSZrV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJg6Wsjc.js → index-Z8P4BexE.js} +1 -1
- package/src/assets/web-panel/assets/{index-srqW5_-X.js → index-clSX4PEc.js} +1 -1
- package/src/assets/web-panel/assets/{index-mIQUoUx1.js → index-ifxmu8SY.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz_s25m3.js → index-rS3gl_68.js} +1 -1
- package/src/assets/web-panel/assets/{index-UQXedWQz.js → index-rXdwUeE3.js} +1 -1
- package/src/assets/web-panel/assets/{index-rcwgNMc0.js → index-saf3VNeP.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-xafkqrW3.js → initDefaultProps-pmfaD-aK.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BrtyQXLk.js → motion-CaQ2CpYF.js} +1 -1
- package/src/assets/web-panel/assets/{move-BunUyg59.js → move-CkKTqNbI.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BPwjBeuE.js → mtc-parser-Dd4xsgRI.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BpyqZlNO.js → omit-BFuA5YEs.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DDrV0XjE.js → pickAttrs-DDfxOAJc.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-1A18Pcl2.js → placementArrow-Be595JCp.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B8Fnlmso.js → responsiveObserve-7byljUM3.js} +1 -1
- package/src/assets/web-panel/assets/{slide-x-Fwb2Zm.js → slide-CTWfOTaY.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-aAG5yJl_.js → statusUtils-CNZOuIxM.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BRQSTMRL.js → styleChecker-DwOcBV78.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D5X_KozY.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DN_YIjP-.js → useFs-CzyQjH9r.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BizxAvjw.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B5oAxN93.js → vnode-DVfZiS5N.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DJhvyzKv.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/auto-mode.js +47 -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/auto-mode-safety-eval-v1.json +3294 -0
- package/src/data/changelog.json +26 -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/auto-mode-safety-classifier.js +2034 -0
- package/src/lib/auto-mode-safety-eval.js +1022 -0
- 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/Projects-D2FHLUa-.js +0 -1
- package/src/assets/web-panel/assets/RemoteSession-BII41Mel.js +0 -5
- package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +0 -1
- package/src/assets/web-panel/assets/index-8zbMbbuf.js +0 -1
- package/src/assets/web-panel/assets/index-C97f71oi.js +0 -1
package/src/commands/team.js
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
import fs from "fs";
|
|
19
19
|
import path from "path";
|
|
20
|
+
import { randomUUID } from "node:crypto";
|
|
20
21
|
import { fileURLToPath } from "url";
|
|
21
22
|
import { TaskLeaseRegistry } from "../lib/agent-team/task-lease.js";
|
|
22
23
|
import { TeamRunner } from "../lib/agent-team/team-runner.js";
|
|
@@ -45,6 +46,23 @@ import { stopBackgroundAgentChildTree } from "../lib/background-agent-supervisor
|
|
|
45
46
|
import { redactSecrets } from "../lib/secret-scan.js";
|
|
46
47
|
import { tightenPermissionMode } from "../lib/subagent-contract.js";
|
|
47
48
|
import { TeamRunStateLock } from "../lib/agent-team/team-run-state-lock.js";
|
|
49
|
+
import {
|
|
50
|
+
computeTeamAdjudicationEvidenceDigest,
|
|
51
|
+
TeamAdjudicationStore,
|
|
52
|
+
} from "../lib/agent-team/team-adjudication.js";
|
|
53
|
+
import {
|
|
54
|
+
computeTeamControlAdjudicationDigest,
|
|
55
|
+
computeTeamControlAttemptDigest,
|
|
56
|
+
TeamControlStore,
|
|
57
|
+
} from "../lib/agent-team/team-control-store.js";
|
|
58
|
+
import { TeamProcessCheckpointBroker } from "../lib/agent-team/team-process-checkpoint.js";
|
|
59
|
+
import { registerTeamDistributedCommands } from "./team-distributed.js";
|
|
60
|
+
import {
|
|
61
|
+
sameFileStatIdentity,
|
|
62
|
+
samePathHandleFileIdentity,
|
|
63
|
+
SecureFileIdentityError,
|
|
64
|
+
withTrustedFileParentSync,
|
|
65
|
+
} from "../lib/secure-file-identity.js";
|
|
48
66
|
|
|
49
67
|
export const _deps = {
|
|
50
68
|
spawn: (...args) => executionBroker.spawn(...args),
|
|
@@ -80,6 +98,211 @@ export const MAX_TEAM_AGENT_STDERR_BYTES = 64 * 1024;
|
|
|
80
98
|
export const MAX_TEAM_AGENT_INBOX_MESSAGES = 16;
|
|
81
99
|
export const MAX_TEAM_AGENT_INBOX_BODY_CHARS = 1024;
|
|
82
100
|
export const MAX_TEAM_STATE_BYTES = 64 * 1024 * 1024;
|
|
101
|
+
export const TEAM_STATE_VERSION = 6;
|
|
102
|
+
|
|
103
|
+
function teamStateUnsafeParentError(statePath, cause) {
|
|
104
|
+
return new Error(`Team state parent identity is unsafe: ${statePath}`, {
|
|
105
|
+
cause,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function readTeamStateSnapshot(statePath) {
|
|
110
|
+
try {
|
|
111
|
+
return withTrustedFileParentSync(
|
|
112
|
+
fs,
|
|
113
|
+
statePath,
|
|
114
|
+
({ canonicalPath, parentDevice }) => {
|
|
115
|
+
let before;
|
|
116
|
+
try {
|
|
117
|
+
before = fs.lstatSync(canonicalPath, { bigint: true });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error?.code === "ENOENT") {
|
|
120
|
+
throw new Error(`Team state not found: ${statePath}`);
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
before.isSymbolicLink() ||
|
|
126
|
+
!before.isFile() ||
|
|
127
|
+
Number(before.nlink) !== 1
|
|
128
|
+
) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Team state must be a regular, single-link file: ${statePath}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
process.platform !== "win32" &&
|
|
135
|
+
(Number(before.mode) & 0o077) !== 0
|
|
136
|
+
) {
|
|
137
|
+
throw new Error(`Team state permissions must be 0600: ${statePath}`);
|
|
138
|
+
}
|
|
139
|
+
const bytes = Number(before.size);
|
|
140
|
+
if (bytes <= 0 || bytes > MAX_TEAM_STATE_BYTES) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Team state exceeds the safe ${MAX_TEAM_STATE_BYTES}-byte limit`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
let descriptor = null;
|
|
146
|
+
try {
|
|
147
|
+
descriptor = fs.openSync(
|
|
148
|
+
canonicalPath,
|
|
149
|
+
fs.constants.O_RDONLY | Number(fs.constants.O_NOFOLLOW || 0),
|
|
150
|
+
);
|
|
151
|
+
const opened = fs.fstatSync(descriptor, { bigint: true });
|
|
152
|
+
if (
|
|
153
|
+
!opened.isFile() ||
|
|
154
|
+
Number(opened.nlink) !== 1 ||
|
|
155
|
+
!samePathHandleFileIdentity(before, opened, parentDevice)
|
|
156
|
+
) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`Team state identity changed while opening: ${statePath}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
const body = fs.readFileSync(descriptor);
|
|
162
|
+
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
163
|
+
if (
|
|
164
|
+
Number(after.size) !== body.length ||
|
|
165
|
+
!sameFileStatIdentity(opened, after)
|
|
166
|
+
) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Team state changed while being read: ${statePath}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
const text = new TextDecoder("utf-8", { fatal: true }).decode(body);
|
|
172
|
+
return JSON.parse(text);
|
|
173
|
+
} finally {
|
|
174
|
+
if (descriptor != null) fs.closeSync(descriptor);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
} catch (cause) {
|
|
179
|
+
if (cause instanceof SecureFileIdentityError) {
|
|
180
|
+
throw teamStateUnsafeParentError(statePath, cause);
|
|
181
|
+
}
|
|
182
|
+
throw cause;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function writeTeamStateSnapshot(statePath, snapshot) {
|
|
187
|
+
const contents = `${JSON.stringify(snapshot, null, 2)}\n`;
|
|
188
|
+
const bytes = Buffer.byteLength(contents, "utf8");
|
|
189
|
+
if (bytes > MAX_TEAM_STATE_BYTES) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Team state exceeds the safe ${MAX_TEAM_STATE_BYTES}-byte limit`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Security precondition: the canonical parent and its ancestors cannot be
|
|
196
|
+
// renamed by an untrusted concurrent writer during this callback. Node's
|
|
197
|
+
// path-based primitives are not an openat-style authority boundary.
|
|
198
|
+
try {
|
|
199
|
+
return withTrustedFileParentSync(
|
|
200
|
+
fs,
|
|
201
|
+
statePath,
|
|
202
|
+
({ canonicalPath, parentPath, parentDescriptor }) => {
|
|
203
|
+
const temporary = path.join(
|
|
204
|
+
parentPath,
|
|
205
|
+
`${path.basename(canonicalPath)}.${process.pid}.${randomUUID()}.tmp`,
|
|
206
|
+
);
|
|
207
|
+
let descriptor = null;
|
|
208
|
+
let renamed = false;
|
|
209
|
+
let temporaryCreated = false;
|
|
210
|
+
try {
|
|
211
|
+
descriptor = fs.openSync(temporary, "wx", 0o600);
|
|
212
|
+
temporaryCreated = true;
|
|
213
|
+
fs.writeFileSync(descriptor, contents, "utf8");
|
|
214
|
+
fs.fsyncSync(descriptor);
|
|
215
|
+
fs.closeSync(descriptor);
|
|
216
|
+
descriptor = null;
|
|
217
|
+
fs.renameSync(temporary, canonicalPath);
|
|
218
|
+
renamed = true;
|
|
219
|
+
try {
|
|
220
|
+
fs.chmodSync(canonicalPath, 0o600);
|
|
221
|
+
} catch {
|
|
222
|
+
/* Windows does not expose POSIX mode semantics */
|
|
223
|
+
}
|
|
224
|
+
if (process.platform !== "win32") {
|
|
225
|
+
fs.fsyncSync(parentDescriptor);
|
|
226
|
+
}
|
|
227
|
+
} finally {
|
|
228
|
+
if (descriptor != null) {
|
|
229
|
+
try {
|
|
230
|
+
fs.closeSync(descriptor);
|
|
231
|
+
} catch {
|
|
232
|
+
/* preserve the authoritative persistence failure */
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (!renamed && temporaryCreated) {
|
|
236
|
+
try {
|
|
237
|
+
fs.unlinkSync(temporary);
|
|
238
|
+
} catch {
|
|
239
|
+
/* clean only this attempt's canonical temporary path */
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
} catch (cause) {
|
|
246
|
+
if (cause instanceof SecureFileIdentityError) {
|
|
247
|
+
throw teamStateUnsafeParentError(statePath, cause);
|
|
248
|
+
}
|
|
249
|
+
throw cause;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function ensureTeamStateV6(snapshot) {
|
|
254
|
+
if (!snapshot || ![5, TEAM_STATE_VERSION].includes(snapshot.version)) {
|
|
255
|
+
throw new Error("Team state is not a supported v5/v6 authority contract");
|
|
256
|
+
}
|
|
257
|
+
if (snapshot.version === TEAM_STATE_VERSION) {
|
|
258
|
+
if (
|
|
259
|
+
typeof snapshot.stateId !== "string" ||
|
|
260
|
+
!snapshot.stateId.startsWith("team_state_") ||
|
|
261
|
+
typeof snapshot.adjudicationRunId !== "string"
|
|
262
|
+
) {
|
|
263
|
+
throw new Error("Team state is missing its v6 control authority");
|
|
264
|
+
}
|
|
265
|
+
return snapshot;
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
...snapshot,
|
|
269
|
+
version: TEAM_STATE_VERSION,
|
|
270
|
+
stateId: `team_state_${randomUUID()}`,
|
|
271
|
+
controlCursor: null,
|
|
272
|
+
adjudicationRunId: snapshot.collaborationRunId,
|
|
273
|
+
adjudicationCursor: null,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function adjudicationBindingFor(task, unit = null) {
|
|
278
|
+
const existing = task?.metadata?.adjudication?.case;
|
|
279
|
+
if (
|
|
280
|
+
existing?.caseId &&
|
|
281
|
+
existing?.registryDigest &&
|
|
282
|
+
existing?.sideEffectDigest
|
|
283
|
+
) {
|
|
284
|
+
return {
|
|
285
|
+
taskKey: task.key,
|
|
286
|
+
registryDigest: existing.registryDigest,
|
|
287
|
+
sideEffectDigest: existing.sideEffectDigest,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
taskKey: task.key,
|
|
292
|
+
registryDigest: computeTeamAdjudicationEvidenceDigest({
|
|
293
|
+
key: task.key,
|
|
294
|
+
status: task.status,
|
|
295
|
+
attempts: task.attempts,
|
|
296
|
+
dependsOn: task.dependsOn,
|
|
297
|
+
lastError: task.metadata?.lastError || null,
|
|
298
|
+
adjudication: task.metadata?.adjudication || null,
|
|
299
|
+
}),
|
|
300
|
+
sideEffectDigest: computeTeamAdjudicationEvidenceDigest({
|
|
301
|
+
collaborationUnit: unit || null,
|
|
302
|
+
interruptEvidence: task.metadata?.adjudication?.evidenceDigest || null,
|
|
303
|
+
}),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
83
306
|
|
|
84
307
|
function safeProcessError(value, fallback) {
|
|
85
308
|
const text = redactSecrets(String(value || "")).trim();
|
|
@@ -133,6 +356,29 @@ function assertTrustedStateLocation(statePath, repoRoot) {
|
|
|
133
356
|
}
|
|
134
357
|
}
|
|
135
358
|
|
|
359
|
+
function assertExternalManagedCheckpointStateDir(stateDir, repoRoot) {
|
|
360
|
+
const target = path.resolve(stateDir);
|
|
361
|
+
if (isPathWithin(target, repoRoot)) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
"Managed checkpoint state must be outside the agent-writable repository",
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
let ancestor = target;
|
|
367
|
+
while (!fs.existsSync(ancestor)) {
|
|
368
|
+
const parent = path.dirname(ancestor);
|
|
369
|
+
if (parent === ancestor) break;
|
|
370
|
+
ancestor = parent;
|
|
371
|
+
}
|
|
372
|
+
const realAncestor = fs.realpathSync.native(ancestor);
|
|
373
|
+
const projected = path.resolve(realAncestor, path.relative(ancestor, target));
|
|
374
|
+
if (isPathWithin(projected, repoRoot)) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
"Managed checkpoint state resolves inside the agent-writable repository",
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
return target;
|
|
380
|
+
}
|
|
381
|
+
|
|
136
382
|
export function restoreTeamExecutionContract(options, command, stored) {
|
|
137
383
|
const allowedModes = new Set([
|
|
138
384
|
"dry-run",
|
|
@@ -213,6 +459,28 @@ export function restoreTeamExecutionContract(options, command, stored) {
|
|
|
213
459
|
);
|
|
214
460
|
}
|
|
215
461
|
}
|
|
462
|
+
const storedManagedCheckpoint = stored.managedCheckpoint === true;
|
|
463
|
+
if (
|
|
464
|
+
Object.prototype.hasOwnProperty.call(stored, "managedCheckpoint") &&
|
|
465
|
+
typeof stored.managedCheckpoint !== "boolean"
|
|
466
|
+
) {
|
|
467
|
+
throw new Error(
|
|
468
|
+
"Team resume state has an invalid managed checkpoint authority",
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
const storedCheckpointStateDir =
|
|
472
|
+
stored.checkpointStateDir == null ? null : stored.checkpointStateDir;
|
|
473
|
+
if (
|
|
474
|
+
storedManagedCheckpoint
|
|
475
|
+
? typeof storedCheckpointStateDir !== "string" ||
|
|
476
|
+
storedCheckpointStateDir.length === 0 ||
|
|
477
|
+
storedCheckpointStateDir.includes("\0")
|
|
478
|
+
: storedCheckpointStateDir !== null
|
|
479
|
+
) {
|
|
480
|
+
throw new Error(
|
|
481
|
+
"Team resume state has an invalid checkpoint state directory",
|
|
482
|
+
);
|
|
483
|
+
}
|
|
216
484
|
if (
|
|
217
485
|
stored.worktree
|
|
218
486
|
? typeof stored.worktreeRunId !== "string" ||
|
|
@@ -341,6 +609,27 @@ export function restoreTeamExecutionContract(options, command, stored) {
|
|
|
341
609
|
} else {
|
|
342
610
|
options.merge = stored.merge === true;
|
|
343
611
|
}
|
|
612
|
+
if (optionWasProvided(command, "managedCheckpoint")) {
|
|
613
|
+
if ((options.managedCheckpoint === true) !== storedManagedCheckpoint) {
|
|
614
|
+
throw new Error(
|
|
615
|
+
"--managed-checkpoint must match its persisted resume authority",
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
} else {
|
|
619
|
+
options.managedCheckpoint = storedManagedCheckpoint;
|
|
620
|
+
}
|
|
621
|
+
if (optionWasProvided(command, "checkpointStateDir")) {
|
|
622
|
+
if (
|
|
623
|
+
storedCheckpointStateDir === null ||
|
|
624
|
+
!sameFilesystemPath(options.checkpointStateDir, storedCheckpointStateDir)
|
|
625
|
+
) {
|
|
626
|
+
throw new Error(
|
|
627
|
+
"--checkpoint-state-dir must match its persisted resume authority",
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
options.checkpointStateDir = storedCheckpointStateDir || undefined;
|
|
632
|
+
}
|
|
344
633
|
}
|
|
345
634
|
|
|
346
635
|
export function parseTeammateCount(value, { max = MAX_TEAMMATES } = {}) {
|
|
@@ -380,9 +669,35 @@ function teamAgentError(code, message, details = {}) {
|
|
|
380
669
|
return error;
|
|
381
670
|
}
|
|
382
671
|
|
|
672
|
+
function teamAbortReason(signal, code, message) {
|
|
673
|
+
const reason = signal?.reason;
|
|
674
|
+
if (
|
|
675
|
+
reason instanceof Error &&
|
|
676
|
+
(reason.name !== "AbortError" ||
|
|
677
|
+
typeof reason.code === "string" ||
|
|
678
|
+
reason.adjudication ||
|
|
679
|
+
reason.interruptEvidence)
|
|
680
|
+
) {
|
|
681
|
+
return reason;
|
|
682
|
+
}
|
|
683
|
+
const failure = teamAgentError(
|
|
684
|
+
code,
|
|
685
|
+
reason == null ? message : safeProcessError(reason, message),
|
|
686
|
+
);
|
|
687
|
+
if (reason && (typeof reason === "object" || typeof reason === "function")) {
|
|
688
|
+
for (const field of ["adjudication", "interruptEvidence", "requestId"]) {
|
|
689
|
+
if (reason[field] !== undefined) failure[field] = reason[field];
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return failure;
|
|
693
|
+
}
|
|
694
|
+
|
|
383
695
|
function usageTokens(usage) {
|
|
384
696
|
return (
|
|
385
|
-
(Number(usage?.input_tokens) || 0) +
|
|
697
|
+
(Number(usage?.input_tokens) || 0) +
|
|
698
|
+
(Number(usage?.output_tokens) || 0) +
|
|
699
|
+
(Number(usage?.cache_read_input_tokens) || 0) +
|
|
700
|
+
(Number(usage?.cache_creation_input_tokens) || 0)
|
|
386
701
|
);
|
|
387
702
|
}
|
|
388
703
|
|
|
@@ -645,7 +960,8 @@ export function makeShellRunTask() {
|
|
|
645
960
|
};
|
|
646
961
|
const terminate = () => {
|
|
647
962
|
if (settled || terminationError) return;
|
|
648
|
-
terminationError =
|
|
963
|
+
terminationError = teamAbortReason(
|
|
964
|
+
signal,
|
|
649
965
|
"TEAM_SHELL_ABORTED",
|
|
650
966
|
"Team shell task was cancelled",
|
|
651
967
|
);
|
|
@@ -727,7 +1043,13 @@ export function spawnAgent(prompt, cwd, opts = {}) {
|
|
|
727
1043
|
cwd,
|
|
728
1044
|
env: { ...process.env, CLAUDECODE: "1" },
|
|
729
1045
|
windowsHide: true,
|
|
730
|
-
detached:
|
|
1046
|
+
detached:
|
|
1047
|
+
opts.managedCheckpoint === true
|
|
1048
|
+
? false
|
|
1049
|
+
: process.platform !== "win32",
|
|
1050
|
+
...(opts.managedCheckpoint === true
|
|
1051
|
+
? { requiredBoundaries: ["process-tree"] }
|
|
1052
|
+
: {}),
|
|
731
1053
|
origin: "team:agent",
|
|
732
1054
|
policy: "allow",
|
|
733
1055
|
scope: "team",
|
|
@@ -777,7 +1099,17 @@ export function spawnAgent(prompt, cwd, opts = {}) {
|
|
|
777
1099
|
Number(opts.terminationGraceMs) > 0
|
|
778
1100
|
? Number(opts.terminationGraceMs)
|
|
779
1101
|
: 5000;
|
|
780
|
-
terminationTimer = setTimeout(() =>
|
|
1102
|
+
terminationTimer = setTimeout(() => {
|
|
1103
|
+
if (opts.managedCheckpoint === true) {
|
|
1104
|
+
try {
|
|
1105
|
+
_deps.killProcessTree(child);
|
|
1106
|
+
} catch {
|
|
1107
|
+
/* keep waiting for close; the transaction remains fail-closed */
|
|
1108
|
+
}
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
settle(terminatingError);
|
|
1112
|
+
}, graceMs);
|
|
781
1113
|
};
|
|
782
1114
|
const attachUsage = (error, summary) => {
|
|
783
1115
|
if (summary?.usage) error.usage = summary.usage;
|
|
@@ -864,7 +1196,8 @@ export function spawnAgent(prompt, cwd, opts = {}) {
|
|
|
864
1196
|
if (opts.signal) {
|
|
865
1197
|
abortListener = () =>
|
|
866
1198
|
terminate(
|
|
867
|
-
|
|
1199
|
+
teamAbortReason(
|
|
1200
|
+
opts.signal,
|
|
868
1201
|
"TEAM_AGENT_ABORTED",
|
|
869
1202
|
"Teammate execution was cancelled",
|
|
870
1203
|
),
|
|
@@ -897,12 +1230,12 @@ export function spawnAgent(prompt, cwd, opts = {}) {
|
|
|
897
1230
|
}
|
|
898
1231
|
child.on("error", (error) => {
|
|
899
1232
|
if (terminatingError) return;
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
safeProcessError(error?.message, "Failed to spawn teammate agent"),
|
|
904
|
-
),
|
|
1233
|
+
const failure = teamAgentError(
|
|
1234
|
+
"TEAM_AGENT_SPAWN_FAILED",
|
|
1235
|
+
safeProcessError(error?.message, "Failed to spawn teammate agent"),
|
|
905
1236
|
);
|
|
1237
|
+
if (opts.managedCheckpoint === true) terminatingError = failure;
|
|
1238
|
+
else settle(failure);
|
|
906
1239
|
});
|
|
907
1240
|
child.on("close", (code) => {
|
|
908
1241
|
if (settled) return;
|
|
@@ -1028,6 +1361,84 @@ export function makeAgentRunTask(opts = {}) {
|
|
|
1028
1361
|
};
|
|
1029
1362
|
}
|
|
1030
1363
|
|
|
1364
|
+
export function dispatchTeamControlInterrupt(runner, request) {
|
|
1365
|
+
const interrupted = runner.interruptTask(request.taskKey, {
|
|
1366
|
+
holder: request.holder,
|
|
1367
|
+
leaseId: request.leaseId,
|
|
1368
|
+
fencingToken: request.fencingToken,
|
|
1369
|
+
requestId: request.requestId,
|
|
1370
|
+
actor: request.actor,
|
|
1371
|
+
reason: request.reason,
|
|
1372
|
+
evidenceDigest: request.digest,
|
|
1373
|
+
});
|
|
1374
|
+
const outcome = interrupted.ok
|
|
1375
|
+
? "accepted"
|
|
1376
|
+
: interrupted.reason === "not_active"
|
|
1377
|
+
? "not_active"
|
|
1378
|
+
: interrupted.reason === "stale_attempt"
|
|
1379
|
+
? "stale_attempt"
|
|
1380
|
+
: "rejected";
|
|
1381
|
+
return { interrupted, outcome };
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
export function buildTeamControlBindings(snapshot) {
|
|
1385
|
+
if (
|
|
1386
|
+
snapshot?.version !== TEAM_STATE_VERSION ||
|
|
1387
|
+
typeof snapshot?.stateId !== "string"
|
|
1388
|
+
) {
|
|
1389
|
+
throw new Error(
|
|
1390
|
+
"Team control bindings require a v6 state (resume an older state first)",
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
const registry = TaskLeaseRegistry.restore(snapshot.registry);
|
|
1394
|
+
return {
|
|
1395
|
+
version: snapshot.version,
|
|
1396
|
+
stateId: snapshot.stateId,
|
|
1397
|
+
tasks: registry.list().map((task) => {
|
|
1398
|
+
let attempt = null;
|
|
1399
|
+
if (task.status === "in_progress") {
|
|
1400
|
+
const holder = task.lease?.holder;
|
|
1401
|
+
const leaseId = task.lease?.leaseId;
|
|
1402
|
+
const fencingToken =
|
|
1403
|
+
task.lease?.fencingToken ?? task.lease?.leaseId ?? null;
|
|
1404
|
+
attempt = {
|
|
1405
|
+
holder,
|
|
1406
|
+
leaseId,
|
|
1407
|
+
fencingToken,
|
|
1408
|
+
digest: computeTeamControlAttemptDigest({
|
|
1409
|
+
holder,
|
|
1410
|
+
leaseId,
|
|
1411
|
+
fencingToken,
|
|
1412
|
+
}),
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
let adjudication = null;
|
|
1416
|
+
if (task.metadata?.adjudication?.required === true) {
|
|
1417
|
+
const binding = task.metadata.adjudication.case;
|
|
1418
|
+
if (!binding?.caseId || !binding.sideEffectDigest) {
|
|
1419
|
+
throw new Error(
|
|
1420
|
+
`Team task "${task.key}" has an incomplete adjudication binding`,
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
adjudication = {
|
|
1424
|
+
caseId: binding.caseId,
|
|
1425
|
+
evidenceDigest: binding.sideEffectDigest,
|
|
1426
|
+
digest: computeTeamControlAdjudicationDigest({
|
|
1427
|
+
caseId: binding.caseId,
|
|
1428
|
+
evidenceDigest: binding.sideEffectDigest,
|
|
1429
|
+
}),
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
key: task.key,
|
|
1434
|
+
status: task.status,
|
|
1435
|
+
attempt,
|
|
1436
|
+
adjudication,
|
|
1437
|
+
};
|
|
1438
|
+
}),
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1031
1442
|
export function registerTeamCommand(program, { logger } = {}) {
|
|
1032
1443
|
const log = logger || console;
|
|
1033
1444
|
const team = program
|
|
@@ -1035,6 +1446,46 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1035
1446
|
.description(
|
|
1036
1447
|
"Run a declared task graph across N teammates (exclusive leases + dependency DAG)",
|
|
1037
1448
|
);
|
|
1449
|
+
registerTeamDistributedCommands(team, {
|
|
1450
|
+
logger: log,
|
|
1451
|
+
agentExecutor: spawnAgent,
|
|
1452
|
+
buildAgentPrompt: buildTeamAgentPrompt,
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
team
|
|
1456
|
+
.command("control-bindings")
|
|
1457
|
+
.description(
|
|
1458
|
+
"Show state/attempt/adjudication CAS values for durable human control",
|
|
1459
|
+
)
|
|
1460
|
+
.requiredOption("--state <file>", "Trusted team state file")
|
|
1461
|
+
.option("--json", "Output bindings as JSON")
|
|
1462
|
+
.action((options) => {
|
|
1463
|
+
try {
|
|
1464
|
+
const result = buildTeamControlBindings(
|
|
1465
|
+
readTeamStateSnapshot(path.resolve(options.state)),
|
|
1466
|
+
);
|
|
1467
|
+
if (options.json) {
|
|
1468
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
(log.log || console.log)(`stateId: ${result.stateId}`);
|
|
1472
|
+
for (const task of result.tasks) {
|
|
1473
|
+
const bindings = [];
|
|
1474
|
+
if (task.attempt) bindings.push(`attempt=${task.attempt.digest}`);
|
|
1475
|
+
if (task.adjudication) {
|
|
1476
|
+
bindings.push(`adjudication=${task.adjudication.digest}`);
|
|
1477
|
+
}
|
|
1478
|
+
if (bindings.length > 0) {
|
|
1479
|
+
(log.log || console.log)(
|
|
1480
|
+
` ${task.key} [${task.status}] ${bindings.join(" ")}`,
|
|
1481
|
+
);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
(log.error || console.error)(error.message);
|
|
1486
|
+
process.exitCode = 1;
|
|
1487
|
+
}
|
|
1488
|
+
});
|
|
1038
1489
|
|
|
1039
1490
|
team
|
|
1040
1491
|
.command("plan")
|
|
@@ -1065,6 +1516,353 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1065
1516
|
});
|
|
1066
1517
|
});
|
|
1067
1518
|
|
|
1519
|
+
team
|
|
1520
|
+
.command("adjudications")
|
|
1521
|
+
.description("List durable side-effect adjudication cases for a team state")
|
|
1522
|
+
.requiredOption("--state <file>", "Trusted team state file")
|
|
1523
|
+
.option("--json", "Output cases as JSON")
|
|
1524
|
+
.action((options) => {
|
|
1525
|
+
try {
|
|
1526
|
+
const snapshot = readTeamStateSnapshot(path.resolve(options.state));
|
|
1527
|
+
if (
|
|
1528
|
+
snapshot.version !== TEAM_STATE_VERSION ||
|
|
1529
|
+
typeof snapshot.stateId !== "string" ||
|
|
1530
|
+
typeof snapshot.adjudicationRunId !== "string"
|
|
1531
|
+
) {
|
|
1532
|
+
throw new Error("Resume the v5 state once before listing cases");
|
|
1533
|
+
}
|
|
1534
|
+
const store = new TeamAdjudicationStore({
|
|
1535
|
+
statePath: path.resolve(options.state),
|
|
1536
|
+
collaborationRunId: snapshot.adjudicationRunId,
|
|
1537
|
+
});
|
|
1538
|
+
const result = store.read({
|
|
1539
|
+
anchor: snapshot.adjudicationCursor || null,
|
|
1540
|
+
});
|
|
1541
|
+
if (options.json) {
|
|
1542
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
if (result.cases.length === 0) {
|
|
1546
|
+
(log.log || console.log)("No team adjudication cases.");
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
for (const item of result.cases) {
|
|
1550
|
+
(log.log || console.log)(
|
|
1551
|
+
`${item.taskKey}: ${item.status} (${item.recovery.state})` +
|
|
1552
|
+
(item.decision ? ` decision=${item.decision.value}` : ""),
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
(log.error || console.error)(error.message);
|
|
1557
|
+
process.exitCode = 1;
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
team
|
|
1562
|
+
.command("interrupt")
|
|
1563
|
+
.description("Request durable human takeover of an active team task")
|
|
1564
|
+
.requiredOption("--state <file>", "Trusted team state file")
|
|
1565
|
+
.requiredOption("--task <key>", "Task key to take over")
|
|
1566
|
+
.requiredOption(
|
|
1567
|
+
"--expected-state-id <id>",
|
|
1568
|
+
"Exact state authority shown by the latest monitor snapshot",
|
|
1569
|
+
)
|
|
1570
|
+
.requiredOption(
|
|
1571
|
+
"--expected-attempt-digest <digest>",
|
|
1572
|
+
"Exact holder/lease/fencing attempt digest shown by the latest snapshot",
|
|
1573
|
+
)
|
|
1574
|
+
.option("--actor <identity>", "Operator identity", "cli-operator")
|
|
1575
|
+
.option(
|
|
1576
|
+
"--reason <text>",
|
|
1577
|
+
"Reason for takeover",
|
|
1578
|
+
"human takeover requested",
|
|
1579
|
+
)
|
|
1580
|
+
.option("--request-id <id>", "Idempotency key")
|
|
1581
|
+
.option("--json", "Output the durable request as JSON")
|
|
1582
|
+
.action((options) => {
|
|
1583
|
+
try {
|
|
1584
|
+
const statePath = path.resolve(options.state);
|
|
1585
|
+
const snapshot = readTeamStateSnapshot(statePath);
|
|
1586
|
+
if (
|
|
1587
|
+
snapshot.version !== TEAM_STATE_VERSION ||
|
|
1588
|
+
typeof snapshot.stateId !== "string"
|
|
1589
|
+
) {
|
|
1590
|
+
throw new Error(
|
|
1591
|
+
"Team interrupt requires a v6 state (resume an older state first)",
|
|
1592
|
+
);
|
|
1593
|
+
}
|
|
1594
|
+
if (snapshot.stateId !== options.expectedStateId) {
|
|
1595
|
+
throw new Error(
|
|
1596
|
+
"Team state authority changed; refresh before requesting takeover",
|
|
1597
|
+
);
|
|
1598
|
+
}
|
|
1599
|
+
const registry = TaskLeaseRegistry.restore(snapshot.registry);
|
|
1600
|
+
const task = registry.getTask(options.task);
|
|
1601
|
+
if (!task) throw new Error(`Team task not found: ${options.task}`);
|
|
1602
|
+
if (task.status !== "in_progress") {
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
`Team task "${options.task}" is not in progress (${task.status})`,
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
const holder = task.lease?.holder;
|
|
1608
|
+
const leaseId = task.lease?.leaseId;
|
|
1609
|
+
const fencingToken =
|
|
1610
|
+
task.lease?.fencingToken ?? task.lease?.leaseId ?? null;
|
|
1611
|
+
const currentAttemptDigest = computeTeamControlAttemptDigest({
|
|
1612
|
+
holder,
|
|
1613
|
+
leaseId,
|
|
1614
|
+
fencingToken,
|
|
1615
|
+
});
|
|
1616
|
+
if (currentAttemptDigest !== options.expectedAttemptDigest) {
|
|
1617
|
+
throw new Error(
|
|
1618
|
+
"Team task attempt changed; refresh before requesting takeover",
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
const store = new TeamControlStore({
|
|
1622
|
+
statePath,
|
|
1623
|
+
stateId: snapshot.stateId,
|
|
1624
|
+
});
|
|
1625
|
+
const request = store.requestInterrupt({
|
|
1626
|
+
...(options.requestId ? { requestId: options.requestId } : {}),
|
|
1627
|
+
taskKey: options.task,
|
|
1628
|
+
holder,
|
|
1629
|
+
leaseId,
|
|
1630
|
+
fencingToken,
|
|
1631
|
+
actor: options.actor,
|
|
1632
|
+
reason: options.reason,
|
|
1633
|
+
});
|
|
1634
|
+
if (options.json) {
|
|
1635
|
+
console.log(JSON.stringify(request, null, 2));
|
|
1636
|
+
} else {
|
|
1637
|
+
(log.log || console.log)(
|
|
1638
|
+
`Human takeover requested for "${options.task}" (${request.request.requestId})`,
|
|
1639
|
+
);
|
|
1640
|
+
}
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
(log.error || console.error)(error.message);
|
|
1643
|
+
process.exitCode = 1;
|
|
1644
|
+
}
|
|
1645
|
+
});
|
|
1646
|
+
|
|
1647
|
+
team
|
|
1648
|
+
.command("adjudicate")
|
|
1649
|
+
.description(
|
|
1650
|
+
"Apply a one-shot retry/accept/cancel decision to an ambiguous task",
|
|
1651
|
+
)
|
|
1652
|
+
.requiredOption("--state <file>", "Trusted team state file")
|
|
1653
|
+
.requiredOption("--task <key>", "Task key requiring adjudication")
|
|
1654
|
+
.requiredOption(
|
|
1655
|
+
"--decision <decision>",
|
|
1656
|
+
"Decision: retry, accept, or cancel",
|
|
1657
|
+
)
|
|
1658
|
+
.requiredOption(
|
|
1659
|
+
"--expected-state-id <id>",
|
|
1660
|
+
"Exact state authority shown by the latest monitor snapshot",
|
|
1661
|
+
)
|
|
1662
|
+
.requiredOption(
|
|
1663
|
+
"--expected-adjudication-digest <digest>",
|
|
1664
|
+
"Exact adjudication case/evidence digest shown by the latest snapshot",
|
|
1665
|
+
)
|
|
1666
|
+
.option("--authority <identity>", "Operator authority", "cli-operator")
|
|
1667
|
+
.requiredOption("--reason <text>", "Reason/evidence summary")
|
|
1668
|
+
.option("--json", "Output the applied decision as JSON")
|
|
1669
|
+
.action((options) => {
|
|
1670
|
+
let stateLock = null;
|
|
1671
|
+
try {
|
|
1672
|
+
const decision = String(options.decision || "").toLowerCase();
|
|
1673
|
+
if (!["retry", "accept", "cancel"].includes(decision)) {
|
|
1674
|
+
throw new Error("--decision must be retry, accept, or cancel");
|
|
1675
|
+
}
|
|
1676
|
+
stateLock = TeamRunStateLock.acquire(path.resolve(options.state));
|
|
1677
|
+
const statePath = stateLock.statePath;
|
|
1678
|
+
const snapshot = readTeamStateSnapshot(statePath);
|
|
1679
|
+
if (
|
|
1680
|
+
snapshot.version !== TEAM_STATE_VERSION ||
|
|
1681
|
+
typeof snapshot.stateId !== "string" ||
|
|
1682
|
+
typeof snapshot.adjudicationRunId !== "string"
|
|
1683
|
+
) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
"Team adjudication requires a v6 state prepared by --resume",
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
if (snapshot.stateId !== options.expectedStateId) {
|
|
1689
|
+
throw new Error(
|
|
1690
|
+
"Team state authority changed; refresh before adjudicating",
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
const registry = TaskLeaseRegistry.restore(snapshot.registry);
|
|
1694
|
+
const task = registry.getTask(options.task);
|
|
1695
|
+
if (!task) throw new Error(`Team task not found: ${options.task}`);
|
|
1696
|
+
const storedBinding = task.metadata?.adjudication?.case;
|
|
1697
|
+
if (
|
|
1698
|
+
!storedBinding?.caseId ||
|
|
1699
|
+
!storedBinding.registryDigest ||
|
|
1700
|
+
!storedBinding.sideEffectDigest
|
|
1701
|
+
) {
|
|
1702
|
+
throw new Error(
|
|
1703
|
+
`Team task "${options.task}" has no durable adjudication case`,
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1706
|
+
const currentAdjudicationDigest = computeTeamControlAdjudicationDigest({
|
|
1707
|
+
caseId: storedBinding.caseId,
|
|
1708
|
+
evidenceDigest: storedBinding.sideEffectDigest,
|
|
1709
|
+
});
|
|
1710
|
+
if (currentAdjudicationDigest !== options.expectedAdjudicationDigest) {
|
|
1711
|
+
throw new Error(
|
|
1712
|
+
"Team adjudication authority changed; refresh before deciding",
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
const binding = {
|
|
1716
|
+
taskKey: task.key,
|
|
1717
|
+
registryDigest: storedBinding.registryDigest,
|
|
1718
|
+
sideEffectDigest: storedBinding.sideEffectDigest,
|
|
1719
|
+
};
|
|
1720
|
+
const store = new TeamAdjudicationStore({
|
|
1721
|
+
statePath,
|
|
1722
|
+
collaborationRunId: snapshot.adjudicationRunId,
|
|
1723
|
+
});
|
|
1724
|
+
let adjudicationCase = store.getCase(binding, {
|
|
1725
|
+
anchor: snapshot.adjudicationCursor || null,
|
|
1726
|
+
});
|
|
1727
|
+
if (!adjudicationCase) {
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`Durable adjudication case not found for "${options.task}"`,
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
if (adjudicationCase.caseId !== storedBinding.caseId) {
|
|
1733
|
+
throw new Error("Team adjudication case binding mismatch");
|
|
1734
|
+
}
|
|
1735
|
+
const reasonDigest = computeTeamAdjudicationEvidenceDigest({
|
|
1736
|
+
reason: options.reason,
|
|
1737
|
+
});
|
|
1738
|
+
const decided = store.decideCase(
|
|
1739
|
+
{
|
|
1740
|
+
...binding,
|
|
1741
|
+
decision,
|
|
1742
|
+
authority: options.authority,
|
|
1743
|
+
reasonDigest,
|
|
1744
|
+
expectedRevision: adjudicationCase.revision,
|
|
1745
|
+
},
|
|
1746
|
+
{ anchor: snapshot.adjudicationCursor || null },
|
|
1747
|
+
);
|
|
1748
|
+
adjudicationCase = decided.case;
|
|
1749
|
+
const consumer = `team-state:${snapshot.stateId}:${task.key}`;
|
|
1750
|
+
const claimed = store.claimDecision(
|
|
1751
|
+
{
|
|
1752
|
+
...binding,
|
|
1753
|
+
decisionDigest: adjudicationCase.decision.decisionDigest,
|
|
1754
|
+
consumer,
|
|
1755
|
+
expectedRevision: adjudicationCase.revision,
|
|
1756
|
+
},
|
|
1757
|
+
{ anchor: decided.cursor },
|
|
1758
|
+
);
|
|
1759
|
+
adjudicationCase = claimed.case;
|
|
1760
|
+
const claimedDecisionId = adjudicationCase.claim?.claimId || null;
|
|
1761
|
+
const appliedDecision = task.metadata?.adjudication?.decision || null;
|
|
1762
|
+
const retryAlreadyPersisted =
|
|
1763
|
+
decision === "retry" &&
|
|
1764
|
+
task.metadata?.adjudication?.required === false &&
|
|
1765
|
+
appliedDecision?.id === claimedDecisionId &&
|
|
1766
|
+
appliedDecision?.action === decision;
|
|
1767
|
+
if (
|
|
1768
|
+
!claimed.authorization &&
|
|
1769
|
+
adjudicationCase.recovery.state === "retry_outcome_unknown" &&
|
|
1770
|
+
!retryAlreadyPersisted
|
|
1771
|
+
) {
|
|
1772
|
+
throw new Error(
|
|
1773
|
+
"Retry authorization was already consumed but its outcome is unknown; automatic replay is forbidden",
|
|
1774
|
+
);
|
|
1775
|
+
}
|
|
1776
|
+
if (
|
|
1777
|
+
!claimed.authorization &&
|
|
1778
|
+
adjudicationCase.recovery.state === "complete"
|
|
1779
|
+
) {
|
|
1780
|
+
snapshot.adjudicationCursor = claimed.cursor;
|
|
1781
|
+
writeTeamStateSnapshot(statePath, snapshot);
|
|
1782
|
+
const result = {
|
|
1783
|
+
ok: true,
|
|
1784
|
+
idempotent: true,
|
|
1785
|
+
task: registry.getTask(task.key),
|
|
1786
|
+
case: adjudicationCase,
|
|
1787
|
+
};
|
|
1788
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1789
|
+
else
|
|
1790
|
+
(log.log || console.log)(
|
|
1791
|
+
`Decision already applied for "${task.key}"`,
|
|
1792
|
+
);
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
const claimId =
|
|
1796
|
+
claimed.authorization?.claimId || adjudicationCase.claim?.claimId;
|
|
1797
|
+
const claimDigest =
|
|
1798
|
+
claimed.authorization?.claimDigest ||
|
|
1799
|
+
adjudicationCase.claim?.claimDigest;
|
|
1800
|
+
if (!claimId || !claimDigest) {
|
|
1801
|
+
throw new Error("Team adjudication claim authority is unavailable");
|
|
1802
|
+
}
|
|
1803
|
+
const applied = registry.resolveAdjudication(task.key, {
|
|
1804
|
+
decision,
|
|
1805
|
+
decisionId: claimId,
|
|
1806
|
+
actor: options.authority,
|
|
1807
|
+
reason: options.reason,
|
|
1808
|
+
evidenceDigest: task.metadata?.adjudication?.evidenceDigest || null,
|
|
1809
|
+
result:
|
|
1810
|
+
decision === "accept"
|
|
1811
|
+
? {
|
|
1812
|
+
adjudicated: true,
|
|
1813
|
+
caseId: adjudicationCase.caseId,
|
|
1814
|
+
claimId,
|
|
1815
|
+
}
|
|
1816
|
+
: null,
|
|
1817
|
+
});
|
|
1818
|
+
if (!applied.ok) {
|
|
1819
|
+
throw new Error(
|
|
1820
|
+
`Could not apply team adjudication: ${applied.reason}`,
|
|
1821
|
+
);
|
|
1822
|
+
}
|
|
1823
|
+
snapshot.registry = registry.snapshot();
|
|
1824
|
+
snapshot.adjudicationCursor = claimed.cursor;
|
|
1825
|
+
writeTeamStateSnapshot(statePath, snapshot);
|
|
1826
|
+
const outcomeDigest = computeTeamAdjudicationEvidenceDigest({
|
|
1827
|
+
stateId: snapshot.stateId,
|
|
1828
|
+
task: registry.getTask(task.key),
|
|
1829
|
+
claimId,
|
|
1830
|
+
});
|
|
1831
|
+
const completed = store.completeCase(
|
|
1832
|
+
{
|
|
1833
|
+
...binding,
|
|
1834
|
+
claimDigest,
|
|
1835
|
+
outcomeDigest,
|
|
1836
|
+
expectedRevision: adjudicationCase.revision,
|
|
1837
|
+
},
|
|
1838
|
+
{ anchor: claimed.cursor },
|
|
1839
|
+
);
|
|
1840
|
+
snapshot.adjudicationCursor = completed.cursor;
|
|
1841
|
+
writeTeamStateSnapshot(statePath, snapshot);
|
|
1842
|
+
const result = {
|
|
1843
|
+
ok: true,
|
|
1844
|
+
decision,
|
|
1845
|
+
task: registry.getTask(task.key),
|
|
1846
|
+
case: completed.case,
|
|
1847
|
+
};
|
|
1848
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
1849
|
+
else
|
|
1850
|
+
(log.log || console.log)(
|
|
1851
|
+
`Applied ${decision} decision to "${task.key}"`,
|
|
1852
|
+
);
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
(log.error || console.error)(error.message);
|
|
1855
|
+
process.exitCode = 1;
|
|
1856
|
+
} finally {
|
|
1857
|
+
if (stateLock && stateLock.release() !== true) {
|
|
1858
|
+
(log.error || console.error)(
|
|
1859
|
+
"Lost team state lock ownership before adjudication release",
|
|
1860
|
+
);
|
|
1861
|
+
process.exitCode = 1;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1068
1866
|
team
|
|
1069
1867
|
.command("run")
|
|
1070
1868
|
.description("Run a task graph with N cooperating teammates")
|
|
@@ -1093,6 +1891,14 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1093
1891
|
"--worktree",
|
|
1094
1892
|
"Run each task's `command` in its own git worktree (parallel isolation)",
|
|
1095
1893
|
)
|
|
1894
|
+
.option(
|
|
1895
|
+
"--managed-checkpoint",
|
|
1896
|
+
"With --worktree: capture task writes/commit in a fail-closed Process Broker checkpoint",
|
|
1897
|
+
)
|
|
1898
|
+
.option(
|
|
1899
|
+
"--checkpoint-state-dir <dir>",
|
|
1900
|
+
"External managed checkpoint store (defaults beside --state)",
|
|
1901
|
+
)
|
|
1096
1902
|
.option(
|
|
1097
1903
|
"--merge",
|
|
1098
1904
|
"With --worktree: merge each clean branch back to base (conflicts reported, not forced)",
|
|
@@ -1155,16 +1961,10 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1155
1961
|
if (!fs.existsSync(options.state)) {
|
|
1156
1962
|
throw new Error(`Team resume state not found: ${options.state}`);
|
|
1157
1963
|
}
|
|
1158
|
-
|
|
1159
|
-
if (stateBytes <= 0 || stateBytes > MAX_TEAM_STATE_BYTES) {
|
|
1160
|
-
throw new Error(
|
|
1161
|
-
`Team resume state exceeds the safe ${MAX_TEAM_STATE_BYTES}-byte limit`,
|
|
1162
|
-
);
|
|
1163
|
-
}
|
|
1164
|
-
resumeSnapshot = JSON.parse(fs.readFileSync(options.state, "utf8"));
|
|
1964
|
+
resumeSnapshot = readTeamStateSnapshot(options.state);
|
|
1165
1965
|
if (
|
|
1166
1966
|
!resumeSnapshot ||
|
|
1167
|
-
resumeSnapshot.version
|
|
1967
|
+
![5, TEAM_STATE_VERSION].includes(resumeSnapshot.version) ||
|
|
1168
1968
|
!resumeSnapshot.registry ||
|
|
1169
1969
|
!resumeSnapshot.execution ||
|
|
1170
1970
|
!resumeSnapshot.budget ||
|
|
@@ -1174,9 +1974,10 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1174
1974
|
!resumeSnapshot.collaborationCursor
|
|
1175
1975
|
) {
|
|
1176
1976
|
throw new Error(
|
|
1177
|
-
"Team resume state is missing the v5 authority contract",
|
|
1977
|
+
"Team resume state is missing the v5/v6 authority contract",
|
|
1178
1978
|
);
|
|
1179
1979
|
}
|
|
1980
|
+
resumeSnapshot = ensureTeamStateV6(resumeSnapshot);
|
|
1180
1981
|
restoreTeamExecutionContract(
|
|
1181
1982
|
options,
|
|
1182
1983
|
command,
|
|
@@ -1210,6 +2011,27 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1210
2011
|
process.exitCode = 1;
|
|
1211
2012
|
return;
|
|
1212
2013
|
}
|
|
2014
|
+
if (options.managedCheckpoint && !options.worktree) {
|
|
2015
|
+
(log.error || console.error)(
|
|
2016
|
+
"--managed-checkpoint requires --worktree",
|
|
2017
|
+
);
|
|
2018
|
+
process.exitCode = 1;
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
if (options.checkpointStateDir && !options.managedCheckpoint) {
|
|
2022
|
+
(log.error || console.error)(
|
|
2023
|
+
"--checkpoint-state-dir requires --managed-checkpoint",
|
|
2024
|
+
);
|
|
2025
|
+
process.exitCode = 1;
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
if (options.managedCheckpoint && !options.state) {
|
|
2029
|
+
(log.error || console.error)(
|
|
2030
|
+
"--managed-checkpoint requires trusted --state outside the repository",
|
|
2031
|
+
);
|
|
2032
|
+
process.exitCode = 1;
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
1213
2035
|
if (teamExecutionMode(options) !== "dry-run" && options.state) {
|
|
1214
2036
|
try {
|
|
1215
2037
|
assertTrustedStateLocation(options.state, canonicalRepoRoot());
|
|
@@ -1219,6 +2041,20 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1219
2041
|
return;
|
|
1220
2042
|
}
|
|
1221
2043
|
}
|
|
2044
|
+
let managedCheckpointStateDir = null;
|
|
2045
|
+
if (options.managedCheckpoint) {
|
|
2046
|
+
try {
|
|
2047
|
+
managedCheckpointStateDir = assertExternalManagedCheckpointStateDir(
|
|
2048
|
+
options.checkpointStateDir ||
|
|
2049
|
+
`${options.state}.workspace-transactions`,
|
|
2050
|
+
canonicalRepoRoot(),
|
|
2051
|
+
);
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
(log.error || console.error)(error.message);
|
|
2054
|
+
process.exitCode = 1;
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
1222
2058
|
if (
|
|
1223
2059
|
teammates > 1 &&
|
|
1224
2060
|
(options.exec || options.agent) &&
|
|
@@ -1365,6 +2201,8 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1365
2201
|
exec: options.exec === true,
|
|
1366
2202
|
agent: options.agent === true,
|
|
1367
2203
|
worktree: options.worktree === true,
|
|
2204
|
+
managedCheckpoint: options.managedCheckpoint === true,
|
|
2205
|
+
checkpointStateDir: managedCheckpointStateDir,
|
|
1368
2206
|
merge: options.merge === true,
|
|
1369
2207
|
teammates,
|
|
1370
2208
|
permissionMode,
|
|
@@ -1404,7 +2242,11 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1404
2242
|
return;
|
|
1405
2243
|
}
|
|
1406
2244
|
}
|
|
1407
|
-
if (
|
|
2245
|
+
if (
|
|
2246
|
+
(options.exec || options.worktree) &&
|
|
2247
|
+
!options.agent &&
|
|
2248
|
+
!options.managedCheckpoint
|
|
2249
|
+
) {
|
|
1408
2250
|
const checkpointTask = reg
|
|
1409
2251
|
.list()
|
|
1410
2252
|
.find((task) => taskContracts.get(task.key)?.checkpointRequired);
|
|
@@ -1455,6 +2297,12 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1455
2297
|
);
|
|
1456
2298
|
}
|
|
1457
2299
|
}
|
|
2300
|
+
const stateId = resumeSnapshot?.stateId || `team_state_${randomUUID()}`;
|
|
2301
|
+
let controlCursor = resumeSnapshot?.controlCursor || null;
|
|
2302
|
+
let adjudicationCursor = resumeSnapshot?.adjudicationCursor || null;
|
|
2303
|
+
let adjudicationRunId = resumeSnapshot?.adjudicationRunId || null;
|
|
2304
|
+
let adjudicationStore = null;
|
|
2305
|
+
let controlStore = null;
|
|
1458
2306
|
let priorCollaborationRun = null;
|
|
1459
2307
|
if (priorCollaborationRunId) {
|
|
1460
2308
|
const recovery = _deps.collaborationStore.readRecovery(
|
|
@@ -1488,20 +2336,110 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1488
2336
|
);
|
|
1489
2337
|
}
|
|
1490
2338
|
if (resumeSnapshot && executionContract.mode !== "dry-run") {
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
2339
|
+
adjudicationRunId = adjudicationRunId || priorCollaborationRunId;
|
|
2340
|
+
adjudicationStore = new TeamAdjudicationStore({
|
|
2341
|
+
statePath: options.state,
|
|
2342
|
+
collaborationRunId: adjudicationRunId,
|
|
2343
|
+
});
|
|
2344
|
+
// Always prove the persisted cursor before trusting a valid-looking
|
|
2345
|
+
// adjudication file. This detects rollback even when no case is
|
|
2346
|
+
// currently pending.
|
|
2347
|
+
adjudicationCursor = adjudicationStore.read({
|
|
2348
|
+
anchor: adjudicationCursor,
|
|
2349
|
+
}).cursor;
|
|
2350
|
+
const unitsByKey = new Map(
|
|
2351
|
+
(priorCollaborationRun?.units || []).map((unit) => [
|
|
2352
|
+
unit.key,
|
|
2353
|
+
unit,
|
|
2354
|
+
]),
|
|
1501
2355
|
);
|
|
1502
|
-
|
|
2356
|
+
const ambiguousKeys = new Set(
|
|
2357
|
+
(priorCollaborationRun?.units || [])
|
|
2358
|
+
.filter((unit) => {
|
|
2359
|
+
const task = reg.getTask(unit.key);
|
|
2360
|
+
const resolved =
|
|
2361
|
+
task?.metadata?.adjudication?.required === false &&
|
|
2362
|
+
task?.metadata?.adjudication?.decision;
|
|
2363
|
+
if (resolved) return false;
|
|
2364
|
+
return (
|
|
2365
|
+
Number(unit.sideEffects?.unsettled) > 0 ||
|
|
2366
|
+
Number(unit.sideEffects?.unknown) > 0 ||
|
|
2367
|
+
(unit.status === "completed" &&
|
|
2368
|
+
String(task?.status || "").toLowerCase() !== "completed") ||
|
|
2369
|
+
(unit.startedAt != null &&
|
|
2370
|
+
unit.status !== "completed" &&
|
|
2371
|
+
unit.status !== "cancelled")
|
|
2372
|
+
);
|
|
2373
|
+
})
|
|
2374
|
+
.map((unit) => unit.key),
|
|
2375
|
+
);
|
|
2376
|
+
for (const pending of reg.pendingAdjudications()) {
|
|
2377
|
+
ambiguousKeys.add(pending.key);
|
|
2378
|
+
}
|
|
2379
|
+
for (const key of ambiguousKeys) {
|
|
2380
|
+
let task = reg.getTask(key);
|
|
2381
|
+
if (!task) {
|
|
2382
|
+
throw new Error(
|
|
2383
|
+
`Collaboration recovery references unknown task "${key}"`,
|
|
2384
|
+
);
|
|
2385
|
+
}
|
|
2386
|
+
if (task.metadata?.adjudication?.required !== true) {
|
|
2387
|
+
const required = reg.requireAdjudication(key, {
|
|
2388
|
+
code: "TEAM_RESUME_ADJUDICATION_REQUIRED",
|
|
2389
|
+
reason:
|
|
2390
|
+
"prior real execution side effects require adjudication",
|
|
2391
|
+
evidenceDigest: computeTeamAdjudicationEvidenceDigest({
|
|
2392
|
+
unit: unitsByKey.get(key) || null,
|
|
2393
|
+
}),
|
|
2394
|
+
});
|
|
2395
|
+
if (!required.ok) {
|
|
2396
|
+
throw new Error(
|
|
2397
|
+
`Could not fail task "${key}" closed: ${required.reason}`,
|
|
2398
|
+
);
|
|
2399
|
+
}
|
|
2400
|
+
task = reg.getTask(key);
|
|
2401
|
+
}
|
|
2402
|
+
const binding = adjudicationBindingFor(
|
|
2403
|
+
task,
|
|
2404
|
+
unitsByKey.get(key) || null,
|
|
2405
|
+
);
|
|
2406
|
+
const opened = adjudicationStore.openCase(binding, {
|
|
2407
|
+
anchor: adjudicationCursor,
|
|
2408
|
+
expectedCursor: adjudicationCursor,
|
|
2409
|
+
});
|
|
2410
|
+
adjudicationCursor = opened.cursor;
|
|
2411
|
+
const bound = reg.bindAdjudicationCase(key, {
|
|
2412
|
+
caseId: opened.case.caseId,
|
|
2413
|
+
registryDigest: binding.registryDigest,
|
|
2414
|
+
sideEffectDigest: binding.sideEffectDigest,
|
|
2415
|
+
});
|
|
2416
|
+
if (!bound.ok) {
|
|
2417
|
+
throw new Error(
|
|
2418
|
+
`Could not bind adjudication case for "${key}": ${bound.reason}`,
|
|
2419
|
+
);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
const pending = reg.pendingAdjudications();
|
|
2423
|
+
if (pending.length > 0) {
|
|
2424
|
+
const upgraded = {
|
|
2425
|
+
...resumeSnapshot,
|
|
2426
|
+
version: TEAM_STATE_VERSION,
|
|
2427
|
+
stateId,
|
|
2428
|
+
registry: reg.snapshot(),
|
|
2429
|
+
mailbox: mailbox.snapshot(),
|
|
2430
|
+
budget: budget.snapshot(),
|
|
2431
|
+
members: priorMembers,
|
|
2432
|
+
controlCursor,
|
|
2433
|
+
adjudicationRunId,
|
|
2434
|
+
adjudicationCursor,
|
|
2435
|
+
};
|
|
2436
|
+
writeTeamStateSnapshot(options.state, upgraded);
|
|
1503
2437
|
const error = new Error(
|
|
1504
|
-
`Team resume requires side-effect adjudication for
|
|
2438
|
+
`Team resume requires side-effect adjudication for ${pending
|
|
2439
|
+
.map((item) => `"${item.key}"`)
|
|
2440
|
+
.join(
|
|
2441
|
+
", ",
|
|
2442
|
+
)}; run \`cc team adjudicate --state "${options.state}" --task <key> --decision <retry|accept|cancel> --reason <text>\``,
|
|
1505
2443
|
);
|
|
1506
2444
|
error.code = "TEAM_RESUME_ADJUDICATION_REQUIRED";
|
|
1507
2445
|
throw error;
|
|
@@ -1511,6 +2449,7 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1511
2449
|
(priorCollaborationRun?.units || []).map((unit) => [unit.key, unit]),
|
|
1512
2450
|
);
|
|
1513
2451
|
const runId = _deps.collaborationStore.createRunId("team");
|
|
2452
|
+
adjudicationRunId = adjudicationRunId || runId;
|
|
1514
2453
|
const worktreeRunId = resumeSnapshot?.execution?.worktreeRunId || runId;
|
|
1515
2454
|
executionContract.worktreeRunId = options.worktree
|
|
1516
2455
|
? worktreeRunId
|
|
@@ -1534,7 +2473,8 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1534
2473
|
maxTokens: limits.maxTokens,
|
|
1535
2474
|
maxWallMs: limits.maxWallMs,
|
|
1536
2475
|
},
|
|
1537
|
-
checkpointRequired:
|
|
2476
|
+
checkpointRequired:
|
|
2477
|
+
options.agent === true || options.managedCheckpoint === true,
|
|
1538
2478
|
worktreeRequired: options.worktree === true,
|
|
1539
2479
|
units: reg.list().map((task) => ({
|
|
1540
2480
|
key: task.key,
|
|
@@ -1561,7 +2501,8 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1561
2501
|
maxWallMs: taskContracts.get(task.key)?.maxWallMs,
|
|
1562
2502
|
},
|
|
1563
2503
|
checkpointRequired:
|
|
1564
|
-
taskContracts.get(task.key)?.checkpointRequired === true
|
|
2504
|
+
taskContracts.get(task.key)?.checkpointRequired === true ||
|
|
2505
|
+
options.managedCheckpoint === true,
|
|
1565
2506
|
worktreeRequired:
|
|
1566
2507
|
taskContracts.get(task.key)?.worktreeRequired === true,
|
|
1567
2508
|
scopePaths: task.metadata?.scopePaths || [],
|
|
@@ -1604,50 +2545,33 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1604
2545
|
|
|
1605
2546
|
// Persist a snapshot after each task settles so a crash mid-run is
|
|
1606
2547
|
// resumable (persist-after-run alone would lose everything on a crash).
|
|
1607
|
-
//
|
|
2548
|
+
// v6 also records execution authority, recovery cursors, bounded mailbox,
|
|
1608
2549
|
// scope ownership, and worktree integration state. Persistence is
|
|
1609
2550
|
// fail-closed when it gates a claim, rather than a best-effort event sink.
|
|
1610
2551
|
let runnerRef = null;
|
|
1611
2552
|
const persist = () => {
|
|
1612
2553
|
if (!options.state) return;
|
|
1613
|
-
//
|
|
1614
|
-
//
|
|
1615
|
-
//
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
);
|
|
1635
|
-
const bytes = Buffer.byteLength(contents, "utf8");
|
|
1636
|
-
if (bytes > MAX_TEAM_STATE_BYTES) {
|
|
1637
|
-
throw new Error(
|
|
1638
|
-
`Team state exceeds the safe ${MAX_TEAM_STATE_BYTES}-byte limit`,
|
|
1639
|
-
);
|
|
1640
|
-
}
|
|
1641
|
-
try {
|
|
1642
|
-
fs.writeFileSync(tmp, contents, { encoding: "utf8", mode: 0o600 });
|
|
1643
|
-
fs.renameSync(tmp, options.state);
|
|
1644
|
-
} finally {
|
|
1645
|
-
try {
|
|
1646
|
-
if (fs.existsSync(tmp)) fs.rmSync(tmp, { force: true });
|
|
1647
|
-
} catch {
|
|
1648
|
-
/* preserve the authoritative write/rename failure */
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
2554
|
+
// Reuse the v6 authority writer so every persistence path gets the
|
|
2555
|
+
// same byte cap, exclusive temporary file, fsync, atomic replace,
|
|
2556
|
+
// and private final permissions.
|
|
2557
|
+
writeTeamStateSnapshot(options.state, {
|
|
2558
|
+
version: TEAM_STATE_VERSION,
|
|
2559
|
+
stateId,
|
|
2560
|
+
registry: reg.snapshot(),
|
|
2561
|
+
mailbox: mailbox.snapshot(),
|
|
2562
|
+
budget: budget.snapshot(),
|
|
2563
|
+
scopeLock: scopeLock?.snapshot() || null,
|
|
2564
|
+
worktrees: coord?.snapshot?.() || null,
|
|
2565
|
+
members: runnerRef ? runnerRef.members() : priorMembers,
|
|
2566
|
+
collaborationRunId: collaborationRun.id,
|
|
2567
|
+
collaborationCursor: _deps.collaborationStore.readCursor(
|
|
2568
|
+
collaborationRun.id,
|
|
2569
|
+
),
|
|
2570
|
+
controlCursor,
|
|
2571
|
+
adjudicationRunId,
|
|
2572
|
+
adjudicationCursor,
|
|
2573
|
+
execution: executionContract,
|
|
2574
|
+
});
|
|
1651
2575
|
};
|
|
1652
2576
|
|
|
1653
2577
|
// Pick the executor. Default is a dry-run (validate + schedule, no side
|
|
@@ -1663,11 +2587,21 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1663
2587
|
.filter(Boolean)
|
|
1664
2588
|
: null;
|
|
1665
2589
|
try {
|
|
2590
|
+
const checkpointBroker = options.managedCheckpoint
|
|
2591
|
+
? new TeamProcessCheckpointBroker({
|
|
2592
|
+
broker: executionBroker,
|
|
2593
|
+
stateDir: managedCheckpointStateDir,
|
|
2594
|
+
coverageTarget: "partial",
|
|
2595
|
+
writerIsolation: "unknown",
|
|
2596
|
+
externalSideEffects: true,
|
|
2597
|
+
})
|
|
2598
|
+
: null;
|
|
1666
2599
|
coord = new TeamWorktreeCoordinator(process.cwd(), {
|
|
1667
2600
|
runId: worktreeRunId,
|
|
1668
2601
|
snapshot: resumeSnapshot?.worktrees || undefined,
|
|
1669
2602
|
sparsePaths: splitList(options.sparsePaths),
|
|
1670
2603
|
symlinkDirectories: splitList(options.symlinkDirs),
|
|
2604
|
+
checkpointBroker,
|
|
1671
2605
|
onWorktree: ({ key, branch, path: worktreePath }) => {
|
|
1672
2606
|
settleGovernance(key, "running", {
|
|
1673
2607
|
branch,
|
|
@@ -1680,6 +2614,14 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1680
2614
|
// otherwise collide with.
|
|
1681
2615
|
persist();
|
|
1682
2616
|
},
|
|
2617
|
+
onCheckpoint: ({ key, checkpoint }) => {
|
|
2618
|
+
settleGovernance(key, "running", {
|
|
2619
|
+
workspaceCheckpoint: checkpoint,
|
|
2620
|
+
});
|
|
2621
|
+
// Prepared/running/final checkpoint authority must reach the
|
|
2622
|
+
// trusted team state before execution advances.
|
|
2623
|
+
persist();
|
|
2624
|
+
},
|
|
1683
2625
|
});
|
|
1684
2626
|
} catch (error) {
|
|
1685
2627
|
try {
|
|
@@ -1719,6 +2661,7 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1719
2661
|
inbox = [],
|
|
1720
2662
|
budgetReservation = null,
|
|
1721
2663
|
signal = null,
|
|
2664
|
+
managedCheckpoint = false,
|
|
1722
2665
|
}) => {
|
|
1723
2666
|
const prompt = task.metadata?.prompt || task?.prompt;
|
|
1724
2667
|
if (!prompt) {
|
|
@@ -1733,6 +2676,13 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1733
2676
|
cwd,
|
|
1734
2677
|
{
|
|
1735
2678
|
...contract,
|
|
2679
|
+
// The outer worktree transaction is the workspace
|
|
2680
|
+
// checkpoint authority. Avoid nesting an agent checkpoint
|
|
2681
|
+
// against the same writable tree.
|
|
2682
|
+
checkpointRequired: managedCheckpoint
|
|
2683
|
+
? false
|
|
2684
|
+
: contract.checkpointRequired,
|
|
2685
|
+
managedCheckpoint,
|
|
1736
2686
|
sessionId: collaborationUnits.get(key)?.sessionId,
|
|
1737
2687
|
signal,
|
|
1738
2688
|
},
|
|
@@ -1803,6 +2753,36 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1803
2753
|
});
|
|
1804
2754
|
}
|
|
1805
2755
|
|
|
2756
|
+
const ensureTaskAdjudicationCase = (key) => {
|
|
2757
|
+
const task = reg.getTask(key);
|
|
2758
|
+
if (
|
|
2759
|
+
!adjudicationStore ||
|
|
2760
|
+
task?.metadata?.adjudication?.required !== true
|
|
2761
|
+
) {
|
|
2762
|
+
return null;
|
|
2763
|
+
}
|
|
2764
|
+
const binding = adjudicationBindingFor(
|
|
2765
|
+
task,
|
|
2766
|
+
collaborationUnits.get(key) || null,
|
|
2767
|
+
);
|
|
2768
|
+
const opened = adjudicationStore.openCase(binding, {
|
|
2769
|
+
anchor: adjudicationCursor,
|
|
2770
|
+
expectedCursor: adjudicationCursor,
|
|
2771
|
+
});
|
|
2772
|
+
adjudicationCursor = opened.cursor;
|
|
2773
|
+
const bound = reg.bindAdjudicationCase(key, {
|
|
2774
|
+
caseId: opened.case.caseId,
|
|
2775
|
+
registryDigest: binding.registryDigest,
|
|
2776
|
+
sideEffectDigest: binding.sideEffectDigest,
|
|
2777
|
+
});
|
|
2778
|
+
if (!bound.ok) {
|
|
2779
|
+
throw new Error(
|
|
2780
|
+
`Could not bind adjudication case for "${key}": ${bound.reason}`,
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2783
|
+
return opened.case;
|
|
2784
|
+
};
|
|
2785
|
+
|
|
1806
2786
|
const runner = new TeamRunner(reg, {
|
|
1807
2787
|
teammates,
|
|
1808
2788
|
maxTasks: limits.maxTasks || undefined,
|
|
@@ -1843,6 +2823,10 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1843
2823
|
settleGovernance(key, governanceStatus, {
|
|
1844
2824
|
endedAt: governanceStatus === "pending" ? null : Date.now(),
|
|
1845
2825
|
});
|
|
2826
|
+
ensureTaskAdjudicationCase(key);
|
|
2827
|
+
persist();
|
|
2828
|
+
},
|
|
2829
|
+
onLeaseChanged: () => {
|
|
1846
2830
|
persist();
|
|
1847
2831
|
},
|
|
1848
2832
|
onEvent: (e) => {
|
|
@@ -1864,10 +2848,70 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1864
2848
|
runner.seedMembers(priorMembers);
|
|
1865
2849
|
runnerRef = runner;
|
|
1866
2850
|
|
|
2851
|
+
const controlWorkerId = `coordinator-${process.pid}-${stateId}`;
|
|
2852
|
+
let controlTimer = null;
|
|
2853
|
+
let controlFailed = false;
|
|
2854
|
+
const pollControls = () => {
|
|
2855
|
+
if (!controlStore || controlFailed) return;
|
|
2856
|
+
try {
|
|
2857
|
+
const pending = controlStore.pending({
|
|
2858
|
+
anchor: controlCursor,
|
|
2859
|
+
});
|
|
2860
|
+
for (const request of pending) {
|
|
2861
|
+
const { outcome } = dispatchTeamControlInterrupt(runner, request);
|
|
2862
|
+
controlStore.acknowledge({
|
|
2863
|
+
requestId: request.requestId,
|
|
2864
|
+
outcome,
|
|
2865
|
+
workerId: controlWorkerId,
|
|
2866
|
+
});
|
|
2867
|
+
controlCursor = controlStore.cursor();
|
|
2868
|
+
persist();
|
|
2869
|
+
}
|
|
2870
|
+
} catch (error) {
|
|
2871
|
+
controlFailed = true;
|
|
2872
|
+
if (controlTimer) {
|
|
2873
|
+
clearInterval(controlTimer);
|
|
2874
|
+
controlTimer = null;
|
|
2875
|
+
}
|
|
2876
|
+
runner.abortRun(error, { requireAdjudication: true });
|
|
2877
|
+
}
|
|
2878
|
+
};
|
|
2879
|
+
|
|
1867
2880
|
let summary;
|
|
1868
2881
|
try {
|
|
1869
2882
|
persist();
|
|
1870
|
-
|
|
2883
|
+
if (options.state) {
|
|
2884
|
+
adjudicationStore =
|
|
2885
|
+
adjudicationStore ||
|
|
2886
|
+
new TeamAdjudicationStore({
|
|
2887
|
+
statePath: options.state,
|
|
2888
|
+
collaborationRunId: adjudicationRunId,
|
|
2889
|
+
});
|
|
2890
|
+
adjudicationCursor = adjudicationStore.read({
|
|
2891
|
+
anchor: adjudicationCursor,
|
|
2892
|
+
}).cursor;
|
|
2893
|
+
controlStore = new TeamControlStore({
|
|
2894
|
+
statePath: options.state,
|
|
2895
|
+
stateId,
|
|
2896
|
+
});
|
|
2897
|
+
controlStore.pending({ anchor: controlCursor });
|
|
2898
|
+
controlCursor = controlStore.cursor();
|
|
2899
|
+
persist();
|
|
2900
|
+
}
|
|
2901
|
+
const running = runner.run();
|
|
2902
|
+
if (controlStore) {
|
|
2903
|
+
pollControls();
|
|
2904
|
+
controlTimer = setInterval(pollControls, 100);
|
|
2905
|
+
controlTimer.unref?.();
|
|
2906
|
+
}
|
|
2907
|
+
try {
|
|
2908
|
+
summary = await running;
|
|
2909
|
+
} finally {
|
|
2910
|
+
if (controlTimer) {
|
|
2911
|
+
clearInterval(controlTimer);
|
|
2912
|
+
controlTimer = null;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
1871
2915
|
} catch (err) {
|
|
1872
2916
|
try {
|
|
1873
2917
|
_deps.collaborationStore.finalizeRun(collaborationRun.id, "failed");
|
|
@@ -1876,6 +2920,18 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
1876
2920
|
}
|
|
1877
2921
|
throw err;
|
|
1878
2922
|
}
|
|
2923
|
+
if (controlStore) {
|
|
2924
|
+
for (const request of controlStore.pending({
|
|
2925
|
+
anchor: controlCursor,
|
|
2926
|
+
})) {
|
|
2927
|
+
controlStore.acknowledge({
|
|
2928
|
+
requestId: request.requestId,
|
|
2929
|
+
outcome: "not_active",
|
|
2930
|
+
workerId: controlWorkerId,
|
|
2931
|
+
});
|
|
2932
|
+
controlCursor = controlStore.cursor();
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
1879
2935
|
persist();
|
|
1880
2936
|
let governanceFailed = false;
|
|
1881
2937
|
try {
|