chainlesschain 0.162.185 → 0.162.187

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 (184) hide show
  1. package/bin/chainlesschain.js +21 -5
  2. package/package.json +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-Co4ipAKx.js → AIOps-CETaO3s8.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BpsoDrDq.js → ActionButton-CAe7E9TW.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-DMUqeBik.js → Analytics-BGr2mzwO.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-C6fD_dXr.js → AppLayout-BEoY-NcT.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-DW0YlQlV.js → Artifacts-DUZivCgt.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-DXtHiiFK.js → Audit-C3yBs68k.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BgmF62U-.js → BackgroundAgents-C9KwiY3O.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-D8vhVLQC.js → Backup-BoSWD_C8.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-D7nCtY2X.js → BaseInput-Dp09GkxL.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-CBZLEV2G.js → Chat-5t9d-Ja0.js} +4 -4
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-DmVku4GH.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-DJLkeD4M.js → Checkbox-8QJFlkqI.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-Cs6xO8Fd.js → Codegen-BJVFQ3lL.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-Dtx9wxfT.js → Col-6hdLD0oB.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-DiigJg4n.js → Community-B8opo0uY.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-B67oMDDL.js → Compact-Dv8HudiN.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BBDl-Jos.js → Compliance-DhxuCS5N.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-Ba98hSsa.js → Cowork-Dggy6VIL.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DBWQZIz7.js → Cron-DSmDqJTT.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-OcrKzxAt.js → Crosschain-7bZq0j5f.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-mW2eSbjM.js → DID-BWY4kh-H.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-BmsDPmKJ.js → Dashboard-bOzPZxlu.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-CVzmCyRx.js → Dropdown-BTFJHqQu.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-vVgqKzhh.js → EmailListRenderer-BC3MOdtT.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CWY5t_ZJ.js → FamilyGuardDashboard-CFHAdTTY.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-ZGomhjxr.js → Federation-DZ_4aF_n.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-DyaknbLr.js → FormItemContext-BZje6UTj.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-CmIvN1qr.js → GenericCardRenderer-D7QfCvjN.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-DQOJIYkG.js → Git-DuG7qANG.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CMiY-EEp.js → Governance-BDmYAgTh.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-D6IXuucf.js → Inference-DSXAfc1Q.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-6jkTL0hV.js → KnowledgeGraph-DusleniP.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-N0jD-349.js → Logs-CpImmA8p.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-BZ6LJBkk.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-WhYuhBxm.js → Marketplace-DXS8CXkE.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-DTeO28xy.js → McpTools-D151UfUI.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-MSHzPSE-.js → Memory-DKDxnoA1.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-Cyqs3J3W.js → MobileBridge-BFZ95iKM.js} +1 -1
  41. package/src/assets/web-panel/assets/MobileProjects-CDAR-grF.js +1 -0
  42. package/src/assets/web-panel/assets/{Mtc-MWBnezNM.js → Mtc-OGJ95xJs.js} +5 -5
  43. package/src/assets/web-panel/assets/{MtcAudit-DQu35Nu-.js → MtcAudit-BmGTo1XC.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-CQS_M-DP.js → Multisig-XumJRqOa.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-WD16vQvY.js → NLProgramming-DHhhRZed.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DqsiSwOq.js → Notes-B9rGxNNr.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-D3LoxO-P.js → NotificationSettings-DClLbMYV.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-C5jPoTd6.js → OrderTableRenderer-I8OohT7N.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-48aMBJso.js → Organization-M6WzaYVa.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-j-a2XkhR.js → Overflow-Cb3B5KtM.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BYpBNYnh.js → P2P-BGk57kya.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-CYCsrrLg.js → PdhVaultBrowser-CXcKyPqK.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-ZRXGXDFb.js → Permissions-hsfJ-Y0e.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-6OWUPHyL.js → PersonalDataHub-BSBii4js.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-FcjlDK9E.js → Pipeline-BNw52rey.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-vD_6Tldp.js → Privacy-DUpfue4E.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-B_CHOLXp.js → ProjectInit-C68NsP3h.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-rEiqQ2da.js → ProjectSettings-BJ2It9v4.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-D2FHLUa-.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-Dgtt97B4.js → Providers-EnU4VWwS.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-DaGVylZO.js → QrScannerModal-BRt5-B_I.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-DP2_2x3u.js → QuickAsk-Jd5L9UL2.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-D29yj4ki.js → Recommend-BNDhoVIu.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-Ue2wnBLp.js → RemoteSession-BII41Mel.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-CrxIHqeC.js → Reputation-D1wQPUNv.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-BgSzJPCn.js → Row-DnjRegul.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-AwhYOrP9.js → RssFeed-C_T4I-nF.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-D2shpEcm.js → Search-C4lrxlLh.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-BtKzhTbT.js → Security-ByYT-VzP.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-BY0DsUSm.js → Services-BHpyEqB5.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-B3Sb0UI9.js → Skeleton-C0I23vG-.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-Ne5w0bDs.js → Skills-DGZXz22K.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-C8VkaP4T.js → Sla-DL_urP9A.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-BQdIEYP9.js → SpeechSettings-6x-hUNQ_.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-CwFafrUg.js → SyncSettings-Crk5TCXq.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-yJuKoDlR.js → Tasks-rZGq5PZs.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-DYn9qCmx.js → Templates-DT5el8Q8.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DkKReHvn.js → Tenant-mlgeyMK8.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-r6bd-owt.js → Terminal-C8HEA-lM.js} +2 -2
  80. package/src/assets/web-panel/assets/TimelineRenderer-DvlqBayB.js +1 -0
  81. package/src/assets/web-panel/assets/{Tokens-BnMw7MRi.js → Tokens-C7lDcNmT.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-CTzKp4o9.js → Trigger-CM8nCnIJ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-BBrSZm9U.js → Trust-BQDLUQ9x.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-D9fDvEMq.js → UkeySign-CVqM0OFE.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DUi5ve-0.js → VideoEditing-BvAcQpzT.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-C8Fi1P8_.js → Wallet-zFVFaAZ2.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-s1LppYi2.js → WebAuthn-Cc522ums.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-DJL2C4lj.js → WorkflowEditor-Bg_wxpDo.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-D3F2ndOB.js → chat-jg0QwC8K.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-B7739-e3.js → colors-ALzYz1gO.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-CcJbFTF4.js → compact-item-DXHYUoqi.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-CuP_DEFS.js → createContext-BsGsBM5m.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-Dy1GJA4S.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-6YEXfIik.js → hasIn-BccMLzxA.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-fWgUQZ9d.js → index-08diKcTm.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CxradkS6.js → index-2VstcImq.js} +19 -19
  97. package/src/assets/web-panel/assets/index-8zbMbbuf.js +1 -0
  98. package/src/assets/web-panel/assets/{index-DDRlBaDt.js → index-BBbbr1YB.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dox44j29.js → index-BTTeAiOP.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BYYSLEjn.js → index-BcJzphrP.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CRuoRapN.js → index-BdCmJ_bG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BxSqVMGh.js → index-BfF0OGKU.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D6Ij_2uw.js → index-BfV8RDlg.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-Dsy7_x4s.js → index-BvsII10i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BsdSnHQE.js → index-C0AbXsDy.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-mfWQRSLn.js → index-C3Rf8wIM.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-BIID4XcP.js → index-C3k3ZwkQ.js} +1 -1
  108. package/src/assets/web-panel/assets/index-C97f71oi.js +1 -0
  109. package/src/assets/web-panel/assets/{index-4h3lpWJ3.js → index-CBVIFX-x.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DHvxXXNA.js → index-CJg6Wsjc.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DjI9MkId.js → index-CLzPzN75.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D0zdPyTD.js → index-CQ1ORdp-.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D0K_rFC1.js → index-CSFESurl.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-b9cuvFEv.js → index-CWLWBgfe.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Nz-MUHD1.js → index-CahDve8M.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DXdnfDVK.js → index-Cg9cOVe9.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bh4hcNWW.js → index-Cmg_Npx5.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Bru_kpZl.js → index-CrZHUsOZ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-yOZYSNmG.js → index-Cz_s25m3.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DlSvpNMf.js → index-D9oV2iRR.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-LfRlSmqe.js → index-DgjQ-kdT.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BOHUo3aM.js → index-Dmk7dD-T.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-D3haorUt.js → index-Dxqiu9-F.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DV11jpeO.js → index-JS2LXdd0.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CY0zd_jc.js → index-TeCof1s3.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DpEKmjoa.js → index-UQXedWQz.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C3pctdCb.js → index-koT8Q86Z.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-D_75jYWk.js → index-mIQUoUx1.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-Gxw09e9R.js → index-oWltafBn.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BhIKnQAW.js → index-rIy96PeM.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-C3ZA_h1j.js → index-rcwgNMc0.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DGsE7MQz.js → index-srqW5_-X.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-eR6H6NR7.js → index-uzykuL_R.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CJ1Bq2L3.js → initDefaultProps-xafkqrW3.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-rYJN_vzf.js → motion-BrtyQXLk.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-DnDK2gTP.js → move-BunUyg59.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-CKnXy7ES.js → mtc-parser-BPwjBeuE.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-3nRc3hWT.js → omit-BpyqZlNO.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-Ddjj3sLu.js → pickAttrs-DDrV0XjE.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-CbsbphQY.js → placementArrow-1A18Pcl2.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-D9aZ4VDJ.js → responsiveObserve-B8Fnlmso.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-D4pvdFj0.js → slide-x-Fwb2Zm.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-C2W-vY-S.js → statusUtils-aAG5yJl_.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-Cbq5150d.js → styleChecker-BRQSTMRL.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-BA2B7R82.js → useFlexGapSupport-D5X_KozY.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CLrwg3xg.js → useFs-DN_YIjP-.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-DQeAI5qs.js → usePersonalDataHub-BizxAvjw.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-8BDGy-YU.js → vnode-B5oAxN93.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-B2SzFprl.js → zoom-DJhvyzKv.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/eval.js +7 -0
  152. package/src/commands/status.js +55 -0
  153. package/src/commands/team.js +1805 -448
  154. package/src/data/changelog.json +26 -0
  155. package/src/harness/worktree-isolator.js +671 -79
  156. package/src/lazy-dispatch.js +12 -1
  157. package/src/lib/agent-team/task-lease.js +378 -78
  158. package/src/lib/agent-team/team-agent-stream.js +488 -0
  159. package/src/lib/agent-team/team-budget.js +216 -20
  160. package/src/lib/agent-team/team-mailbox.js +389 -15
  161. package/src/lib/agent-team/team-run-state-lock.js +209 -0
  162. package/src/lib/agent-team/team-runner.js +547 -63
  163. package/src/lib/agent-team/team-scope-lock.js +409 -0
  164. package/src/lib/agent-team/team-task-contract.js +277 -0
  165. package/src/lib/agent-team/team-worktree.js +1171 -62
  166. package/src/lib/agent-worktree.js +19 -20
  167. package/src/lib/collaboration-run-store.js +983 -68
  168. package/src/lib/observability/index.js +191 -19
  169. package/src/lib/observability/otlp-exporter.js +1209 -207
  170. package/src/lib/otlp-exporter.js +8 -36
  171. package/src/lib/with-file-lock.js +190 -24
  172. package/src/lib/worktree-isolator.js +3 -0
  173. package/src/lib/worktree-sparse.js +23 -0
  174. package/src/program-base.js +9 -2
  175. package/src/runtime/headless-runner.js +45 -21
  176. package/src/runtime/headless-stream.js +6 -1
  177. package/src/assets/web-panel/assets/ChatBubbleRenderer-CtDb-2Qu.js +0 -1
  178. package/src/assets/web-panel/assets/MarkdownRenderer-BL1IVXm-.js +0 -1
  179. package/src/assets/web-panel/assets/MobileProjects-7IN2hWHg.js +0 -1
  180. package/src/assets/web-panel/assets/Projects-Bg8obRAM.js +0 -1
  181. package/src/assets/web-panel/assets/TimelineRenderer-Mq2juUC3.js +0 -1
  182. package/src/assets/web-panel/assets/devWarning-Cj_36GHq.js +0 -1
  183. package/src/assets/web-panel/assets/index-CezFrAmD.js +0 -1
  184. package/src/assets/web-panel/assets/index-Cxgguez6.js +0 -1
@@ -11,11 +11,12 @@
11
11
  * (same pricing/cache-token rules as `--max-budget-usd`)
12
12
  * - maxWallMs wall-clock since the first task started (time cap)
13
13
  *
14
- * Because a task's cost is only known once it returns, the team may overshoot a
15
- * token/USD cap by at most one task like CostBudget, it never STARTS a task
16
- * once already over. A dimension left null is simply inactive. The clock is
17
- * injected so the wall-clock cap is deterministic in tests, and snapshot/restore
18
- * carries the running totals so `cc team run --resume` doesn't reset the budget.
14
+ * Concurrent agent tasks reserve a bounded slice of the remaining token/USD
15
+ * ceilings before they start. The slice becomes the child task's own hard stop,
16
+ * preventing a 64-worker frontier from each spending the full team remainder.
17
+ * A dimension left null is inactive. Snapshot/restore carries settled totals;
18
+ * in-flight reservations are intentionally discarded after a crash because the
19
+ * caller must adjudicate those executions before resuming.
19
20
  */
20
21
 
21
22
  import { CostBudget } from "../cost-budget.js";
@@ -25,6 +26,58 @@ const pos = (n) => {
25
26
  return Number.isFinite(v) && v > 0 ? v : null;
26
27
  };
27
28
 
29
+ function assertBudgetSnapshot(snap) {
30
+ if (
31
+ !snap ||
32
+ typeof snap !== "object" ||
33
+ Array.isArray(snap) ||
34
+ !snap.limits ||
35
+ typeof snap.limits !== "object" ||
36
+ !snap.totals ||
37
+ typeof snap.totals !== "object"
38
+ ) {
39
+ throw new TypeError("invalid team budget snapshot");
40
+ }
41
+ for (const field of ["maxTasks", "maxTokens", "maxUsd", "maxWallMs"]) {
42
+ const value = snap.limits[field];
43
+ if (value !== null && (!Number.isFinite(value) || value <= 0)) {
44
+ throw new TypeError(`invalid team budget limit: ${field}`);
45
+ }
46
+ }
47
+ for (const field of ["tasks", "tokens", "spentUsd", "elapsedMs"]) {
48
+ const value = snap.totals[field];
49
+ if (!Number.isFinite(value) || value < 0) {
50
+ throw new TypeError(`invalid team budget total: ${field}`);
51
+ }
52
+ }
53
+ for (const field of ["maxTasks", "maxTokens"]) {
54
+ const value = snap.limits[field];
55
+ if (value !== null && !Number.isSafeInteger(value)) {
56
+ throw new TypeError(`invalid team budget integer limit: ${field}`);
57
+ }
58
+ }
59
+ for (const field of ["tasks", "tokens"]) {
60
+ if (!Number.isSafeInteger(snap.totals[field])) {
61
+ throw new TypeError(`invalid team budget integer total: ${field}`);
62
+ }
63
+ }
64
+ if (
65
+ typeof snap.totals.started !== "boolean" ||
66
+ typeof snap.totals.unpricedUsage !== "boolean" ||
67
+ (snap.totals.startedAt !== null &&
68
+ (!Number.isFinite(snap.totals.startedAt) || snap.totals.startedAt < 0))
69
+ ) {
70
+ throw new TypeError("invalid team budget wall-clock state");
71
+ }
72
+ if (
73
+ (snap.totals.started && snap.totals.startedAt === null) ||
74
+ (!snap.totals.started &&
75
+ (snap.totals.startedAt !== null || snap.totals.elapsedMs !== 0))
76
+ ) {
77
+ throw new TypeError("inconsistent team budget wall-clock state");
78
+ }
79
+ }
80
+
28
81
  export class TeamBudget {
29
82
  constructor({
30
83
  maxTasks = null,
@@ -42,6 +95,10 @@ export class TeamBudget {
42
95
  this.tasks = 0;
43
96
  this.tokens = 0;
44
97
  this.startedAt = null; // set on the first recorded task
98
+ this.unpricedUsage = false;
99
+ this._reservations = new Map();
100
+ this.reservedTokens = 0;
101
+ this.reservedUsd = 0;
45
102
  }
46
103
 
47
104
  /** Any dimension active? (an all-null budget never stops anything). */
@@ -66,17 +123,132 @@ export class TeamBudget {
66
123
  * task-count / wall-clock dimensions move for it.
67
124
  * @returns {this}
68
125
  */
69
- record({ usage = null, provider, model } = {}, now = this._now()) {
126
+ record(
127
+ {
128
+ usage = null,
129
+ usageRecords = null,
130
+ provider,
131
+ model,
132
+ reservationId = null,
133
+ } = {},
134
+ now = this._now(),
135
+ ) {
70
136
  this.start(now);
137
+ if (reservationId) this.releaseReservation(reservationId);
71
138
  this.tasks += 1;
72
139
  if (usage) {
73
140
  this.tokens +=
74
141
  (Number(usage.input_tokens) || 0) + (Number(usage.output_tokens) || 0);
75
- this.cost.add({ provider, model, usage });
142
+ if (Array.isArray(usageRecords) && usageRecords.length > 0) {
143
+ for (const record of usageRecords) {
144
+ this._recordCost(record);
145
+ }
146
+ } else {
147
+ this._recordCost({ provider, model, usage });
148
+ }
76
149
  }
77
150
  return this;
78
151
  }
79
152
 
153
+ _recordCost(record) {
154
+ const estimate = this.cost.add(record);
155
+ if (!this.cost.enabled()) return estimate;
156
+ const value = record?.usage || {};
157
+ const tokens =
158
+ (Number(value.input_tokens) || 0) +
159
+ (Number(value.output_tokens) || 0) +
160
+ (Number(value.cache_read_input_tokens) || 0) +
161
+ (Number(value.cache_creation_input_tokens) || 0);
162
+ const price = Number(estimate?.totalCost);
163
+ if (
164
+ tokens > 0 &&
165
+ estimate?.free !== true &&
166
+ (estimate?.matched !== true || !Number.isFinite(price) || price < 0)
167
+ ) {
168
+ this.unpricedUsage = true;
169
+ }
170
+ return estimate;
171
+ }
172
+
173
+ /**
174
+ * Reserve one fair slice of the remaining token/USD ceilings.
175
+ *
176
+ * `slots` is the number of claim slots sharing the current remainder. A
177
+ * task-level cap can only tighten its slice. The returned limits are suitable
178
+ * for passing directly to the child agent.
179
+ */
180
+ reserve(id, { maxTokens = null, maxUsd = null, slots = 1 } = {}) {
181
+ const key = String(id || "");
182
+ if (!key) throw new Error("team budget reservation id is required");
183
+ const existing = this._reservations.get(key);
184
+ if (existing) return { ok: true, ...existing, reused: true };
185
+
186
+ const divisor =
187
+ Number.isSafeInteger(slots) && slots > 0 ? Math.max(1, slots) : 1;
188
+ const requestedTokens = pos(maxTokens);
189
+ const requestedUsd = pos(maxUsd);
190
+ let tokenSlice = requestedTokens;
191
+ let usdSlice = requestedUsd;
192
+
193
+ if (this.maxTokens != null) {
194
+ const remaining = Math.max(
195
+ 0,
196
+ Math.floor(this.maxTokens - this.tokens - this.reservedTokens),
197
+ );
198
+ if (remaining <= 0) {
199
+ return {
200
+ ok: false,
201
+ reason: "max-tokens",
202
+ temporary: this.reservedTokens > 0,
203
+ };
204
+ }
205
+ const fair = Math.max(1, Math.ceil(remaining / divisor));
206
+ tokenSlice =
207
+ requestedTokens == null ? fair : Math.min(requestedTokens, fair);
208
+ }
209
+
210
+ if (this.cost.limitUsd != null) {
211
+ const remaining = Math.max(
212
+ 0,
213
+ this.cost.limitUsd - this.cost.spentUsd - this.reservedUsd,
214
+ );
215
+ if (remaining <= Number.EPSILON) {
216
+ return {
217
+ ok: false,
218
+ reason: "max-usd",
219
+ temporary: this.reservedUsd > 0,
220
+ };
221
+ }
222
+ const fair = remaining / divisor;
223
+ usdSlice = requestedUsd == null ? fair : Math.min(requestedUsd, fair);
224
+ }
225
+
226
+ const reservation = {
227
+ id: key,
228
+ maxTokens: tokenSlice,
229
+ maxUsd: usdSlice,
230
+ reservedTokens: this.maxTokens == null ? 0 : tokenSlice || 0,
231
+ reservedUsd: this.cost.limitUsd == null ? 0 : usdSlice || 0,
232
+ };
233
+ this._reservations.set(key, reservation);
234
+ this.reservedTokens += reservation.reservedTokens;
235
+ this.reservedUsd += reservation.reservedUsd;
236
+ return { ok: true, ...reservation, reused: false };
237
+ }
238
+
239
+ releaseReservation(id) {
240
+ const key = String(id || "");
241
+ const reservation = this._reservations.get(key);
242
+ if (!reservation) return false;
243
+ this._reservations.delete(key);
244
+ this.reservedTokens = Math.max(
245
+ 0,
246
+ this.reservedTokens - reservation.reservedTokens,
247
+ );
248
+ this.reservedUsd = Math.max(0, this.reservedUsd - reservation.reservedUsd);
249
+ return true;
250
+ }
251
+
80
252
  /**
81
253
  * Which cap (if any) is now reached — checked BEFORE starting a new task.
82
254
  * @returns {null|"max-tasks"|"max-tokens"|"max-usd"|"max-wall-ms"}
@@ -88,6 +260,9 @@ export class TeamBudget {
88
260
  if (this.maxTokens != null && this.tokens >= this.maxTokens) {
89
261
  return "max-tokens";
90
262
  }
263
+ if (this.cost.enabled() && this.unpricedUsage) {
264
+ return "unpriced-usage";
265
+ }
91
266
  if (this.cost.exceeded()) return "max-usd";
92
267
  if (
93
268
  this.maxWallMs != null &&
@@ -110,15 +285,21 @@ export class TeamBudget {
110
285
  maxTasks: this.maxTasks,
111
286
  tokens: this.tokens,
112
287
  maxTokens: this.maxTokens,
288
+ reservedTokens: this.reservedTokens,
113
289
  spentUsd: this.cost.spentUsd,
114
290
  maxUsd: this.cost.limitUsd,
291
+ reservedUsd: this.reservedUsd,
292
+ reservations: this._reservations.size,
293
+ unpricedUsage: this.unpricedUsage,
115
294
  elapsedMs: this.startedAt == null ? 0 : now - this.startedAt,
116
295
  maxWallMs: this.maxWallMs,
117
296
  reason: this.reason(now),
118
297
  };
119
298
  }
120
299
 
121
- snapshot() {
300
+ snapshot(now = this._now()) {
301
+ const started = this.startedAt != null;
302
+ const elapsedMs = started ? Math.max(0, now - this.startedAt) : 0;
122
303
  return {
123
304
  limits: {
124
305
  maxTasks: this.maxTasks,
@@ -131,18 +312,26 @@ export class TeamBudget {
131
312
  tokens: this.tokens,
132
313
  spentUsd: this.cost.spentUsd,
133
314
  startedAt: this.startedAt,
315
+ started,
316
+ elapsedMs,
317
+ unpricedUsage: this.unpricedUsage,
134
318
  },
135
319
  };
136
320
  }
137
321
 
138
322
  /**
139
- * Restore running totals + caps from a snapshot. `overrides` lets the current
140
- * CLI invocation RAISE/lower a cap on resume a provided (non-nullish) value
141
- * wins over the snapshot's; an omitted one keeps the prior cap (so a resume
142
- * with no flags can't silently drop a safety cap).
323
+ * Restore running totals + caps from a snapshot. A provided override can only
324
+ * tighten a persisted cap; an omitted value keeps the prior cap so resume
325
+ * cannot silently widen its resource authority.
143
326
  */
144
327
  static restore(snap, { now = () => Date.now(), table, overrides = {} } = {}) {
145
- const pick = (o, s) => (o == null ? s : o);
328
+ assertBudgetSnapshot(snap);
329
+ const pick = (override, stored) => {
330
+ const next = pos(override);
331
+ const prior = pos(stored);
332
+ if (next == null) return prior;
333
+ return prior == null ? next : Math.min(prior, next);
334
+ };
146
335
  const b = new TeamBudget({
147
336
  maxTasks: pick(overrides.maxTasks, snap?.limits?.maxTasks),
148
337
  maxTokens: pick(overrides.maxTokens, snap?.limits?.maxTokens),
@@ -151,15 +340,22 @@ export class TeamBudget {
151
340
  table,
152
341
  now,
153
342
  });
154
- b.tasks = Number(snap?.totals?.tasks) || 0;
155
- b.tokens = Number(snap?.totals?.tokens) || 0;
343
+ b.tasks = snap.totals.tasks;
344
+ b.tokens = snap.totals.tokens;
156
345
  // Preserve prior USD spend so the cap keeps counting across a resume.
157
- const spent = Number(snap?.totals?.spentUsd);
158
- b.cost.spentUsd = Number.isFinite(spent) && spent >= 0 ? spent : 0;
346
+ const spent = snap.totals.spentUsd;
347
+ b.cost.spentUsd = spent;
159
348
  if (b.cost.spentUsd > 0) b.cost.priced = true;
160
- // A time cap must NOT keep counting wall-clock across a crash gap (the
161
- // process was down); restart the window on the next recorded task.
162
- b.startedAt = null;
349
+ b.unpricedUsage = snap.totals.unpricedUsage;
350
+ // Preserve active execution time without charging process downtime. This
351
+ // prevents repeated resume from refreshing the team-wide wall-clock cap.
352
+ const restoredAt = b._now();
353
+ const elapsed = snap.totals.elapsedMs;
354
+ const started = snap.totals.started;
355
+ b.startedAt =
356
+ started && Number.isFinite(elapsed) && elapsed >= 0
357
+ ? restoredAt - elapsed
358
+ : null;
163
359
  return b;
164
360
  }
165
361
  }