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.
Files changed (191) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CETaO3s8.js → AIOps-DbZft8w0.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-CAe7E9TW.js → ActionButton-DoOZpwfN.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BGr2mzwO.js → Analytics-B0J0KLTW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BEoY-NcT.js → AppLayout-D9eIR0qq.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-DUZivCgt.js → Artifacts-BnZXEVeo.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-C3yBs68k.js → Audit-X1ce4Bjn.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-C9KwiY3O.js → BackgroundAgents-BvWR-y5v.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-BoSWD_C8.js → Backup-BublSGEd.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-Dp09GkxL.js → BaseInput-CR_77Hso.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-5t9d-Ja0.js → Chat-DixW52tq.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DmVku4GH.js → ChatBubbleRenderer-DDHDpBBA.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-8QJFlkqI.js → Checkbox-CCv1C87K.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-BJVFQ3lL.js → Codegen-B03Cc3dd.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-6hdLD0oB.js → Col-CA06-6pU.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B8opo0uY.js → Community-Bj881J52.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Dv8HudiN.js → Compact-UZLTPoaL.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DhxuCS5N.js → Compliance-kGwzRb_0.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Dggy6VIL.js → Cowork-KundoYM8.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-DSmDqJTT.js → Cron-BYZyoqDe.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-7bZq0j5f.js → Crosschain-BhrrppBX.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BWY4kh-H.js → DID-CdC6EguZ.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-bOzPZxlu.js → Dashboard-CcQx3J4y.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BTFJHqQu.js → Dropdown-BpijtEzQ.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-BC3MOdtT.js → EmailListRenderer-DfZjeZTe.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CFHAdTTY.js → FamilyGuardDashboard-BFYSf69C.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-DZ_4aF_n.js → Federation-Okdi3pJm.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-BZje6UTj.js → FormItemContext-mH-HebKW.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-D7QfCvjN.js → GenericCardRenderer-xivV69x7.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DuG7qANG.js → Git-1rh_YBwg.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BDmYAgTh.js → Governance-eAXkNxdY.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-DSXAfc1Q.js → Inference-CS6dMVTN.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DusleniP.js → KnowledgeGraph-Bq_TnkvD.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-CpImmA8p.js → Logs-KchHtiHt.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BZ6LJBkk.js → MarkdownRenderer-TabqHW9B.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-DXS8CXkE.js → Marketplace-CsmGcX5X.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-D151UfUI.js → McpTools-BaPFnnyz.js} +5 -5
  38. package/src/assets/web-panel/assets/{Memory-DKDxnoA1.js → Memory-2m-sUUf7.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-BFZ95iKM.js → MobileBridge-D5nVZBRu.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-CDAR-grF.js → MobileProjects-Bo8MLZvT.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-OGJ95xJs.js → Mtc-3GDvamGa.js} +6 -6
  42. package/src/assets/web-panel/assets/{MtcAudit-BmGTo1XC.js → MtcAudit-D2zGuZCH.js} +4 -4
  43. package/src/assets/web-panel/assets/{Multisig-XumJRqOa.js → Multisig-BBVQ4t6t.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-DHhhRZed.js → NLProgramming-BIOCyOGp.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-B9rGxNNr.js → Notes-DVwBGpzc.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-DClLbMYV.js → NotificationSettings-BDnHfIfX.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-I8OohT7N.js → OrderTableRenderer-C7Ek20ag.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-M6WzaYVa.js → Organization-D3SRKaa7.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-Cb3B5KtM.js → Overflow-C2B8ahyn.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BGk57kya.js → P2P-2iwQzG65.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-CXcKyPqK.js → PdhVaultBrowser-CoPiWqMN.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-hsfJ-Y0e.js → Permissions-yV5XNXo5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BSBii4js.js → PersonalDataHub-uCDJhMUF.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-BNw52rey.js → Pipeline-hN7ucMOA.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-DUpfue4E.js → Privacy-CgU0ptN4.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C68NsP3h.js → ProjectInit-CfYPiT6j.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BJ2It9v4.js → ProjectSettings-B0A2C4nw.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-BE5C0ajn.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-EnU4VWwS.js → Providers-CstQPf8W.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-BRt5-B_I.js → QrScannerModal-Co3z_ynk.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-Jd5L9UL2.js → QuickAsk-DMTS6pQ6.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BNDhoVIu.js → Recommend-ETw4odUa.js} +1 -1
  63. package/src/assets/web-panel/assets/RemoteSession-B0rkE09d.js +5 -0
  64. package/src/assets/web-panel/assets/{Reputation-D1wQPUNv.js → Reputation-BTVqQgKq.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-DnjRegul.js → Row-CBKKwwtO.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-C_T4I-nF.js → RssFeed-BHUQ438I.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-C4lrxlLh.js → Search-B4NqeUsF.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-ByYT-VzP.js → Security-BRjoYG5_.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-BHpyEqB5.js → Services-CxQzu3p3.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C0I23vG-.js → Skeleton-BKzwwmWO.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DGZXz22K.js → Skills-DrsFcgth.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DL_urP9A.js → Sla-D21tmXCH.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-6x-hUNQ_.js → SpeechSettings-CjyjLfp8.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-Crk5TCXq.js → SyncSettings-Cu45-tnx.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-rZGq5PZs.js → Tasks-oMcILYOj.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-DT5el8Q8.js → Templates-Bh-Zcr5R.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-mlgeyMK8.js → Tenant-68ns63aI.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-C8HEA-lM.js → Terminal-BLJkFykE.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-DvlqBayB.js → TimelineRenderer-xcrJLq-O.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C7lDcNmT.js → Tokens-C6PpFJtf.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-CM8nCnIJ.js → Trigger-BaKA7tdM.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-BQDLUQ9x.js → Trust-BzhA57Jc.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CVqM0OFE.js → UkeySign-4_U3JkIR.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BvAcQpzT.js → VideoEditing-162AANxX.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-zFVFaAZ2.js → Wallet-B1viuql_.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Cc522ums.js → WebAuthn-ZVWazhCy.js} +4 -4
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Bg_wxpDo.js → WorkflowEditor-tzdyUk-1.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-jg0QwC8K.js → chat-DmrHNuwq.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-ALzYz1gO.js → colors-DAJ_M-PK.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-DXHYUoqi.js → compact-item-uIzmYwLs.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BsGsBM5m.js → createContext-D_ZmuLwb.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DIrncrV1.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-BccMLzxA.js → hasIn-CDt_shTL.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Cg9cOVe9.js → index-BAvSmJXW.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BTTeAiOP.js → index-BDF6EQBs.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-C3k3ZwkQ.js → index-BHvlm0In.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CQ1ORdp-.js → index-BRa4kJ_S.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BcJzphrP.js → index-BTCXf1t-.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-rIy96PeM.js → index-Bbxjfstq.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CSFESurl.js → index-Bx4d6c1k.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Cmg_Npx5.js → index-ByzGvpR9.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CahDve8M.js → index-C16fYKFD.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-2VstcImq.js → index-C5WcdJH2.js} +3 -3
  104. package/src/assets/web-panel/assets/{index-C3Rf8wIM.js → index-CG0lY4h3.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-uzykuL_R.js → index-CKyd472S.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CBVIFX-x.js → index-CaelyMgF.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-TeCof1s3.js → index-CbD_WW_6.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BfF0OGKU.js → index-CcZ9xJI9.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-oWltafBn.js → index-CeUpKjvi.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CWLWBgfe.js → index-Cvak6Oe2.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-C0AbXsDy.js → index-CzXqvMuP.js} +1 -1
  112. package/src/assets/web-panel/assets/index-D-Swwqld.js +1 -0
  113. package/src/assets/web-panel/assets/{index-BfV8RDlg.js → index-D4n1lGTB.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Dxqiu9-F.js → index-D9OE8B3d.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BdCmJ_bG.js → index-DBFmcaCQ.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DgjQ-kdT.js → index-DdUMcwRB.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BBbbr1YB.js → index-DeGBoLQA.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dmk7dD-T.js → index-DfSSy9uy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-JS2LXdd0.js → index-DqDSF2RW.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-D9oV2iRR.js → index-DvFhhe6r.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BvsII10i.js → index-DwfvPe-j.js} +1 -1
  122. package/src/assets/web-panel/assets/index-FMZAMKy8.js +1 -0
  123. package/src/assets/web-panel/assets/{index-CrZHUsOZ.js → index-FyplHuNt.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CLzPzN75.js → index-GcKOHD_c.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-koT8Q86Z.js → index-OzTxFIJP.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-08diKcTm.js → index-QsgfSZrV.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CJg6Wsjc.js → index-Z8P4BexE.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-srqW5_-X.js → index-clSX4PEc.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-mIQUoUx1.js → index-ifxmu8SY.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Cz_s25m3.js → index-rS3gl_68.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-UQXedWQz.js → index-rXdwUeE3.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-rcwgNMc0.js → index-saf3VNeP.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-xafkqrW3.js → initDefaultProps-pmfaD-aK.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BrtyQXLk.js → motion-CaQ2CpYF.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-BunUyg59.js → move-CkKTqNbI.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-BPwjBeuE.js → mtc-parser-Dd4xsgRI.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BpyqZlNO.js → omit-BFuA5YEs.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-DDrV0XjE.js → pickAttrs-DDfxOAJc.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-1A18Pcl2.js → placementArrow-Be595JCp.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-B8Fnlmso.js → responsiveObserve-7byljUM3.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-x-Fwb2Zm.js → slide-CTWfOTaY.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-aAG5yJl_.js → statusUtils-CNZOuIxM.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-BRQSTMRL.js → styleChecker-DwOcBV78.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-D5X_KozY.js → useFlexGapSupport-B6MNwAO9.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DN_YIjP-.js → useFs-CzyQjH9r.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-BizxAvjw.js → usePersonalDataHub-Dxh3KiVA.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-B5oAxN93.js → vnode-DVfZiS5N.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DJhvyzKv.js → zoom-BYme7fbY.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agent.js +22 -0
  151. package/src/commands/auto-mode.js +47 -0
  152. package/src/commands/checkpoint-managed.js +1059 -0
  153. package/src/commands/checkpoint.js +3 -0
  154. package/src/commands/team-distributed.js +3324 -0
  155. package/src/commands/team.js +1130 -74
  156. package/src/data/auto-mode-safety-eval-v1.json +3294 -0
  157. package/src/data/changelog.json +26 -0
  158. package/src/lib/agent-core.js +1 -0
  159. package/src/lib/agent-team/task-lease.js +271 -3
  160. package/src/lib/agent-team/team-adjudication.js +1501 -0
  161. package/src/lib/agent-team/team-budget.js +4 -1
  162. package/src/lib/agent-team/team-control-store.js +582 -0
  163. package/src/lib/agent-team/team-distributed-queue.js +5246 -0
  164. package/src/lib/agent-team/team-process-checkpoint.js +211 -0
  165. package/src/lib/agent-team/team-runner.js +319 -22
  166. package/src/lib/agent-team/team-worktree.js +1403 -95
  167. package/src/lib/auto-mode-safety-classifier.js +2034 -0
  168. package/src/lib/auto-mode-safety-eval.js +1022 -0
  169. package/src/lib/managed-checkpoint-render.js +60 -0
  170. package/src/lib/managed-tool-checkpoint.js +194 -0
  171. package/src/lib/process-execution-broker/index.js +438 -5
  172. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
  173. package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
  174. package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
  175. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  176. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  177. package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
  178. package/src/lib/process-execution-broker/workspace-transaction.js +3556 -0
  179. package/src/lib/secure-file-identity.js +433 -0
  180. package/src/lib/turn-binding.js +160 -2
  181. package/src/lib/with-file-lock.js +140 -23
  182. package/src/repl/agent-repl.js +19 -1
  183. package/src/runtime/agent-core.js +366 -122
  184. package/src/runtime/headless-runner.js +12 -0
  185. package/src/runtime/headless-stream.js +3 -0
  186. package/src/runtime/policies/agent-policy.js +14 -0
  187. package/src/assets/web-panel/assets/Projects-D2FHLUa-.js +0 -1
  188. package/src/assets/web-panel/assets/RemoteSession-BII41Mel.js +0 -5
  189. package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +0 -1
  190. package/src/assets/web-panel/assets/index-8zbMbbuf.js +0 -1
  191. package/src/assets/web-panel/assets/index-C97f71oi.js +0 -1
@@ -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) + (Number(usage?.output_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 = teamAgentError(
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: process.platform !== "win32",
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(() => settle(terminatingError), graceMs);
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
- teamAgentError(
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
- settle(
901
- teamAgentError(
902
- "TEAM_AGENT_SPAWN_FAILED",
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
- const stateBytes = fs.statSync(options.state).size;
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 !== 5 ||
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 ((options.exec || options.worktree) && !options.agent) {
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
- const ambiguous = (priorCollaborationRun?.units || []).find(
1492
- (unit) =>
1493
- Number(unit.sideEffects?.unsettled) > 0 ||
1494
- Number(unit.sideEffects?.unknown) > 0 ||
1495
- (unit.status === "completed" &&
1496
- String(reg.getTask(unit.key)?.status || "").toLowerCase() !==
1497
- "completed") ||
1498
- (unit.startedAt != null &&
1499
- unit.status !== "completed" &&
1500
- unit.status !== "cancelled"),
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
- if (ambiguous) {
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 task "${ambiguous.key}"`,
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: options.agent === true,
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
- // v5 also records execution authority, recovery cursors, bounded mailbox,
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
- // Atomic write (tmp + rename): a crash mid-write must not truncate
1614
- // the ONLY resume file a torn snapshot makes --resume throw and
1615
- // loses all progress.
1616
- const tmp = `${options.state}.${process.pid}.${Date.now()}.tmp`;
1617
- const contents = JSON.stringify(
1618
- {
1619
- version: 5,
1620
- registry: reg.snapshot(),
1621
- mailbox: mailbox.snapshot(),
1622
- budget: budget.snapshot(),
1623
- scopeLock: scopeLock?.snapshot() || null,
1624
- worktrees: coord?.snapshot?.() || null,
1625
- members: runnerRef ? runnerRef.members() : priorMembers,
1626
- collaborationRunId: collaborationRun.id,
1627
- collaborationCursor: _deps.collaborationStore.readCursor(
1628
- collaborationRun.id,
1629
- ),
1630
- execution: executionContract,
1631
- },
1632
- null,
1633
- 2,
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
- summary = await runner.run();
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 {