chainlesschain 0.165.2 → 0.165.4

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 (210) hide show
  1. package/README.md +151 -29
  2. package/package.json +2 -2
  3. package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
  7. package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
  36. package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
  37. package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
  39. package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
  44. package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
  48. package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
  49. package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
  55. package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
  65. package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
  68. package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
  70. package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
  72. package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
  73. package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
  74. package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
  75. package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
  76. package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
  77. package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
  79. package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
  80. package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
  87. package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
  98. package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
  104. package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
  105. package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
  128. package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
  129. package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/command-help-index.json +3 -3
  152. package/src/commands/agent.js +105 -35
  153. package/src/commands/artifacts.js +363 -33
  154. package/src/commands/cowork.js +202 -36
  155. package/src/commands/session-budget.js +155 -0
  156. package/src/commands/session-location.js +948 -0
  157. package/src/commands/session.js +72 -46
  158. package/src/data/changelog.json +26 -0
  159. package/src/gateways/ws/artifact-protocol.js +77 -13
  160. package/src/gateways/ws/session-protocol.js +391 -24
  161. package/src/gateways/ws/ws-agent-handler.js +191 -14
  162. package/src/gateways/ws/ws-server.js +14 -3
  163. package/src/gateways/ws/ws-session-gateway.js +101 -5
  164. package/src/harness/jsonl-session-store.js +1210 -0
  165. package/src/lib/agent-team/team-runner.js +43 -13
  166. package/src/lib/artifact-access-ledger.js +461 -0
  167. package/src/lib/artifact-cleanup-ledger.js +861 -0
  168. package/src/lib/artifact-deletion-ledger.js +677 -0
  169. package/src/lib/artifact-store.js +256 -51
  170. package/src/lib/background-agent-keeper-protocol.js +75 -4
  171. package/src/lib/background-agent-launch-barrier.js +74 -0
  172. package/src/lib/background-agent-supervisor.js +149 -5
  173. package/src/lib/background-turn-bootstrap-protocol.js +4 -0
  174. package/src/lib/command-lifecycle-report.js +8 -4
  175. package/src/lib/cowork-adapter.js +9 -5
  176. package/src/lib/cowork-task-runner.js +20 -0
  177. package/src/lib/cowork-workflow.js +178 -20
  178. package/src/lib/direct-model-usage.js +62 -22
  179. package/src/lib/dynamic-workflow-runtime.js +3184 -111
  180. package/src/lib/execution-location-result-apply.js +339 -0
  181. package/src/lib/execution-location-result-artifact.js +271 -0
  182. package/src/lib/execution-location-result-review.js +253 -0
  183. package/src/lib/execution-location-result-store.js +331 -0
  184. package/src/lib/execution-location-result.js +617 -0
  185. package/src/lib/execution-location-target.js +236 -1
  186. package/src/lib/managed-tool-checkpoint.js +47 -4
  187. package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
  188. package/src/lib/session-budget-production-root.js +376 -0
  189. package/src/lib/session-budget-runtime.js +109 -18
  190. package/src/lib/session-budget-usage.js +63 -0
  191. package/src/lib/session-host-snapshot.js +19 -2
  192. package/src/lib/session-resource-budget.js +766 -105
  193. package/src/lib/sub-agent-context.js +69 -22
  194. package/src/lib/web-ui-server.js +12 -1
  195. package/src/lib/with-file-lock.js +175 -26
  196. package/src/repl/agent-repl.js +237 -16
  197. package/src/runtime/agent-core.js +123 -0
  198. package/src/runtime/headless-runner.js +135 -46
  199. package/src/runtime/headless-stream.js +133 -25
  200. package/src/runtime/policies/agent-policy.js +10 -0
  201. package/src/workers/background-agent-keeper.js +112 -19
  202. package/src/workers/background-agent-worker.js +383 -134
  203. package/src/workers/background-turn-bootstrap.js +13 -8
  204. package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
  205. package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
  206. package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
  207. package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
  208. package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
  209. package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
  210. package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
@@ -0,0 +1,376 @@
1
+ import {
2
+ openSessionBudget,
3
+ SessionBudgetSidecarStore,
4
+ } from "./session-budget-runtime.js";
5
+ import {
6
+ normalizeSessionResourceBudgetSnapshot,
7
+ SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
8
+ } from "./session-resource-budget.js";
9
+
10
+ export const SESSION_BUDGET_ROOT_SCHEMA =
11
+ "chainlesschain.session-budget-root/v1";
12
+
13
+ const OPTION_FIELDS = Object.freeze([
14
+ ["sessionMaxConcurrent", "maxConcurrent", "--session-max-concurrent", true],
15
+ ["sessionMaxSpawns", "maxSpawns", "--session-max-spawns", true],
16
+ ["sessionMaxDepth", "maxDepth", "--session-max-depth", true],
17
+ ["sessionMaxTurns", "maxTurns", "--session-max-turns", true],
18
+ ["sessionMaxTokens", "maxTokens", "--session-max-tokens", true],
19
+ ["sessionMaxCostUsd", "maxUsd", "--session-max-cost-usd", false],
20
+ ["sessionMaxWallMs", "maxWallMs", "--session-max-wall-ms", true],
21
+ ["sessionMaxToolMs", "maxToolMs", "--session-max-tool-ms", true],
22
+ ]);
23
+
24
+ const LIMIT_FIELDS = Object.freeze(
25
+ OPTION_FIELDS.map(([, field, flag, integer]) => ({
26
+ field,
27
+ flag,
28
+ integer,
29
+ })),
30
+ );
31
+
32
+ function normalizeLimit(raw, flag, { integer, allowZero = false } = {}) {
33
+ if (raw === undefined || raw === null || raw === "") return null;
34
+ const value = Number(raw);
35
+ if (
36
+ !Number.isFinite(value) ||
37
+ (allowZero ? value < 0 : value <= 0) ||
38
+ (integer && !Number.isSafeInteger(value))
39
+ ) {
40
+ throw new TypeError(
41
+ `${flag} requires ${allowZero ? "a non-negative" : "a positive"}${integer ? " integer" : " number"}`,
42
+ );
43
+ }
44
+ return value;
45
+ }
46
+
47
+ export function resolveSessionBudgetRootOptions(options = {}) {
48
+ const limits = {};
49
+ let hasLimit = false;
50
+ for (const [optionName, field, flag, integer] of OPTION_FIELDS) {
51
+ const value = normalizeLimit(options[optionName], flag, {
52
+ integer,
53
+ allowZero: field === "maxDepth",
54
+ });
55
+ if (value !== null) {
56
+ limits[field] = value;
57
+ hasLimit = true;
58
+ }
59
+ }
60
+ return Object.freeze({
61
+ schema: SESSION_BUDGET_ROOT_SCHEMA,
62
+ enabled: options.sessionBudget === true || hasLimit,
63
+ limits: Object.freeze(limits),
64
+ });
65
+ }
66
+
67
+ export function normalizeSessionBudgetRootConfig(config) {
68
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
69
+ const error = new TypeError("session budget root config must be an object");
70
+ error.code = "CC_SESSION_BUDGET_CONFIG_INVALID";
71
+ throw error;
72
+ }
73
+ try {
74
+ if (config.schema !== SESSION_BUDGET_ROOT_SCHEMA) {
75
+ throw new TypeError("session budget root schema is unsupported");
76
+ }
77
+ if (typeof config.enabled !== "boolean") {
78
+ throw new TypeError("session budget root enabled flag is invalid");
79
+ }
80
+ if (
81
+ !config.limits ||
82
+ typeof config.limits !== "object" ||
83
+ Array.isArray(config.limits)
84
+ ) {
85
+ throw new TypeError("session budget root limits must be an object");
86
+ }
87
+ const allowed = new Set(LIMIT_FIELDS.map(({ field }) => field));
88
+ if (Object.keys(config.limits).some((field) => !allowed.has(field))) {
89
+ throw new TypeError("session budget root contains an unknown limit");
90
+ }
91
+ const limits = {};
92
+ for (const { field, flag, integer } of LIMIT_FIELDS) {
93
+ const value = normalizeLimit(config.limits[field], flag, {
94
+ integer,
95
+ allowZero: field === "maxDepth",
96
+ });
97
+ if (value !== null) limits[field] = value;
98
+ }
99
+ if (!config.enabled && Object.keys(limits).length > 0) {
100
+ throw new TypeError("disabled session budget root cannot contain limits");
101
+ }
102
+ return Object.freeze({
103
+ schema: SESSION_BUDGET_ROOT_SCHEMA,
104
+ enabled: config.enabled,
105
+ limits: Object.freeze(limits),
106
+ });
107
+ } catch (error) {
108
+ error.code ||= "CC_SESSION_BUDGET_CONFIG_INVALID";
109
+ throw error;
110
+ }
111
+ }
112
+
113
+ function combineSignals(...signals) {
114
+ const active = signals.filter(Boolean);
115
+ if (active.length === 0) return null;
116
+ if (active.length === 1) return active[0];
117
+ return AbortSignal.any(active);
118
+ }
119
+
120
+ function budgetRootError(code, message, extra = {}) {
121
+ const error = new Error(message);
122
+ error.code = code;
123
+ Object.assign(error, extra);
124
+ return error;
125
+ }
126
+
127
+ export function openProductionSessionBudgetRoot(
128
+ sessionId,
129
+ config,
130
+ {
131
+ persist = false,
132
+ signal = null,
133
+ table = undefined,
134
+ open = openSessionBudget,
135
+ store = undefined,
136
+ registry = undefined,
137
+ } = {},
138
+ ) {
139
+ if (config?.enabled !== true) {
140
+ return Object.freeze({
141
+ enabled: false,
142
+ options: Object.freeze({}),
143
+ close: () => false,
144
+ });
145
+ }
146
+ if (!persist || !sessionId) {
147
+ throw budgetRootError(
148
+ "CC_SESSION_BUDGET_REQUIRES_DURABLE_SESSION",
149
+ "session budget root requires a durable --session/--resume/--continue target",
150
+ );
151
+ }
152
+ if (typeof open !== "function") {
153
+ throw new TypeError("session budget root requires an opener");
154
+ }
155
+
156
+ const handle = open(String(sessionId), {
157
+ limits: config.limits || {},
158
+ table,
159
+ ...(store ? { store } : {}),
160
+ ...(registry ? { registry } : {}),
161
+ });
162
+ let closed = false;
163
+ try {
164
+ const status = handle.status();
165
+ if (status.recoveryRequired) {
166
+ throw budgetRootError(
167
+ "CC_SESSION_BUDGET_RECOVERY_REQUIRED",
168
+ `session budget recovery is required for ${sessionId}; inspect it with \`cc session budget status ${sessionId}\``,
169
+ {
170
+ sessionId: String(sessionId),
171
+ pendingRecovery: status.pendingRecovery,
172
+ },
173
+ );
174
+ }
175
+ const combinedSignal = combineSignals(signal, handle.budget.signal);
176
+ return Object.freeze({
177
+ enabled: true,
178
+ sessionId: String(sessionId),
179
+ handle,
180
+ budget: handle.budget,
181
+ options: Object.freeze({
182
+ sessionBudget: handle.budget,
183
+ ...(combinedSignal ? { signal: combinedSignal } : {}),
184
+ }),
185
+ close() {
186
+ if (closed) return false;
187
+ closed = true;
188
+ return handle.close();
189
+ },
190
+ });
191
+ } catch (error) {
192
+ if (!closed) {
193
+ closed = true;
194
+ try {
195
+ handle.close();
196
+ } catch {
197
+ // Preserve the admission/recovery failure that prevented dispatch.
198
+ }
199
+ }
200
+ throw error;
201
+ }
202
+ }
203
+
204
+ function projectPending(snapshot) {
205
+ return [
206
+ ...snapshot.inFlight.work.map((entry) => ({
207
+ authorityId: entry.id,
208
+ resourceType: "work",
209
+ kind: entry.kind,
210
+ ...(entry.depth !== undefined ? { depth: entry.depth } : {}),
211
+ ...(entry.elapsedMs !== undefined ? { elapsedMs: entry.elapsedMs } : {}),
212
+ })),
213
+ ...snapshot.inFlight.tools.map((entry) => ({
214
+ authorityId: entry.id,
215
+ resourceType: "tool",
216
+ kind: entry.kind,
217
+ ...(entry.elapsedMs !== undefined ? { elapsedMs: entry.elapsedMs } : {}),
218
+ })),
219
+ ].sort((left, right) => left.authorityId.localeCompare(right.authorityId));
220
+ }
221
+
222
+ function projectRecoveryAdjudication(value) {
223
+ if (!value) return null;
224
+ return Object.freeze({
225
+ schema: value.schema,
226
+ count: value.count,
227
+ headDigest: value.headDigest,
228
+ last: Object.freeze({ ...value.last }),
229
+ ...(value.history
230
+ ? {
231
+ history: Object.freeze({
232
+ schema: value.history.schema,
233
+ baseSequence: value.history.baseSequence,
234
+ baseDigest: value.history.baseDigest,
235
+ retainedCount: value.history.entries.length,
236
+ complete: value.history.baseSequence === 0,
237
+ }),
238
+ }
239
+ : {}),
240
+ });
241
+ }
242
+
243
+ export function readProductionSessionBudget(
244
+ sessionId,
245
+ { store = new SessionBudgetSidecarStore() } = {},
246
+ ) {
247
+ const record = store.read(String(sessionId));
248
+ if (!record) return null;
249
+ const snapshot = normalizeSessionResourceBudgetSnapshot(record.snapshot);
250
+ const pending = projectPending(snapshot);
251
+ return Object.freeze({
252
+ schema: SESSION_BUDGET_ROOT_SCHEMA,
253
+ sessionId: String(sessionId),
254
+ revision: record.revision,
255
+ usageUnknown: record.usageUnknown === true,
256
+ limits: Object.freeze({ ...snapshot.limits }),
257
+ totals: Object.freeze({ ...snapshot.totals }),
258
+ state: Object.freeze({
259
+ started: snapshot.state.started,
260
+ abort: snapshot.state.abort
261
+ ? Object.freeze({ ...snapshot.state.abort })
262
+ : null,
263
+ recoveryAdjudication: projectRecoveryAdjudication(
264
+ snapshot.state.recoveryAdjudication,
265
+ ),
266
+ }),
267
+ recoveryRequired: pending.length > 0,
268
+ pendingRecovery: Object.freeze(
269
+ pending.map((entry) => Object.freeze(entry)),
270
+ ),
271
+ });
272
+ }
273
+
274
+ export function readProductionSessionBudgetRecoveryReceipts(
275
+ sessionId,
276
+ { store = new SessionBudgetSidecarStore() } = {},
277
+ ) {
278
+ const normalizedSessionId = String(sessionId);
279
+ const record = store.read(normalizedSessionId);
280
+ if (!record) return null;
281
+ const snapshot = normalizeSessionResourceBudgetSnapshot(record.snapshot);
282
+ const adjudication = snapshot.state.recoveryAdjudication;
283
+ const history = adjudication?.history || null;
284
+ const entries = history?.entries || [];
285
+ return Object.freeze({
286
+ schema: SESSION_BUDGET_RECOVERY_RECEIPT_HISTORY_SCHEMA,
287
+ sessionId: normalizedSessionId,
288
+ revision: record.revision,
289
+ count: adjudication?.count || 0,
290
+ headDigest: adjudication?.headDigest || null,
291
+ baseSequence: history?.baseSequence ?? adjudication?.count ?? 0,
292
+ baseDigest: history
293
+ ? history.baseDigest
294
+ : adjudication?.headDigest || null,
295
+ complete: adjudication
296
+ ? Boolean(history && history.baseSequence === 0)
297
+ : true,
298
+ entries: Object.freeze(
299
+ entries.map((entry) => Object.freeze(structuredClone(entry))),
300
+ ),
301
+ });
302
+ }
303
+
304
+ export function adjudicateProductionSessionBudgetRecovery(
305
+ sessionId,
306
+ adjudication,
307
+ {
308
+ store = new SessionBudgetSidecarStore(),
309
+ registry = new Map(),
310
+ open = openSessionBudget,
311
+ } = {},
312
+ ) {
313
+ const request = Array.isArray(adjudication)
314
+ ? { abandoned: adjudication, settled: [] }
315
+ : adjudication && typeof adjudication === "object"
316
+ ? {
317
+ abandoned: adjudication.abandoned || [],
318
+ settled: adjudication.settled || [],
319
+ }
320
+ : { abandoned: [], settled: [] };
321
+ if (request.abandoned.length === 0 && request.settled.length === 0) {
322
+ throw budgetRootError(
323
+ "CC_SESSION_BUDGET_RECOVERY_IDS_REQUIRED",
324
+ "session budget recovery requires every exact authority id as --abandon or --settlement",
325
+ );
326
+ }
327
+ const handle = open(String(sessionId), { store, registry });
328
+ let result;
329
+ try {
330
+ const pending = handle.budget.pendingRecovery();
331
+ if (pending.length === 0) {
332
+ throw budgetRootError(
333
+ "CC_SESSION_BUDGET_RECOVERY_NOT_REQUIRED",
334
+ `session budget recovery is not required for ${sessionId}`,
335
+ );
336
+ }
337
+ result = handle.budget.adjudicateRecovery(request);
338
+ if (!result.ok) {
339
+ throw budgetRootError(
340
+ "CC_SESSION_BUDGET_RECOVERY_INCOMPLETE",
341
+ "session budget recovery requires the exact pending authority-id set",
342
+ { pending: result.pending },
343
+ );
344
+ }
345
+ return Object.freeze({
346
+ schema: SESSION_BUDGET_ROOT_SCHEMA,
347
+ sessionId: String(sessionId),
348
+ abandoned: Object.freeze([...result.abandoned]),
349
+ settled: Object.freeze([...result.settled]),
350
+ adjudication: Object.freeze({ ...result.adjudication }),
351
+ receipt: Object.freeze(structuredClone(result.receipt)),
352
+ status: Object.freeze({ ...handle.status() }),
353
+ });
354
+ } finally {
355
+ handle.close();
356
+ }
357
+ }
358
+
359
+ export function sessionBudgetAdmissionError(reason, operation) {
360
+ return budgetRootError(
361
+ "CC_SESSION_BUDGET_EXHAUSTED",
362
+ `session budget blocked ${operation}: ${reason || "session-aborted"}`,
363
+ { budgetReason: reason || "session-aborted" },
364
+ );
365
+ }
366
+
367
+ export function sessionBudgetUsageUnknownError(operation, callId = null) {
368
+ return budgetRootError(
369
+ "CC_SESSION_BUDGET_USAGE_UNKNOWN",
370
+ `session budget requires usage adjudication after ${operation}`,
371
+ {
372
+ budgetReason: "provider-usage-unknown",
373
+ ...(callId ? { callId: String(callId) } : {}),
374
+ },
375
+ );
376
+ }
@@ -206,12 +206,29 @@ function mergeUsageUnknownSnapshot(baseSnapshot, markerSnapshot, filePath) {
206
206
  const work = new Map(base.inFlight.work.map((entry) => [entry.id, entry]));
207
207
  for (const entry of markerEntries) {
208
208
  const existing = work.get(entry.id);
209
- if (existing && JSON.stringify(existing) !== JSON.stringify(entry)) {
210
- throw runtimeError(
211
- "corrupt",
212
- filePath,
213
- new TypeError("conflicting usage-unknown settlement identity"),
214
- );
209
+ if (existing) {
210
+ if (
211
+ existing.kind !== entry.kind ||
212
+ (existing.depth ?? null) !== (entry.depth ?? null)
213
+ ) {
214
+ throw runtimeError(
215
+ "corrupt",
216
+ filePath,
217
+ new TypeError("conflicting usage-unknown settlement identity"),
218
+ );
219
+ }
220
+ work.set(entry.id, {
221
+ ...existing,
222
+ ...(existing.elapsedMs !== undefined || entry.elapsedMs !== undefined
223
+ ? {
224
+ elapsedMs: Math.max(
225
+ Number(existing.elapsedMs || 0),
226
+ Number(entry.elapsedMs || 0),
227
+ ),
228
+ }
229
+ : {}),
230
+ });
231
+ continue;
215
232
  }
216
233
  work.set(entry.id, { ...entry });
217
234
  if (work.size + base.inFlight.tools.length > MAX_IN_FLIGHT_RESOURCES) {
@@ -879,14 +896,6 @@ export class SessionBudgetSidecarStore {
879
896
  "ERR_SESSION_BUDGET_MARKER_CONFLICT",
880
897
  );
881
898
  }
882
- if (resolveUsageUnknown && !clean) {
883
- throw runtimeError(
884
- "recovery",
885
- filePath,
886
- new Error("usage finalization snapshot must be clean"),
887
- "ERR_SESSION_BUDGET_RECOVERY_REQUIRED",
888
- );
889
- }
890
899
  const actualRevision = current?.revision ?? null;
891
900
  if (actualRevision !== expectedRevision) {
892
901
  throw runtimeError(
@@ -916,13 +925,24 @@ export class SessionBudgetSidecarStore {
916
925
  snapshot: canonicalSnapshot,
917
926
  };
918
927
  writeRecordAt(filePath, record, this._fs, this._ioOptions);
928
+ let nextUsageUnknownMarker = null;
919
929
  if (resolveUsageUnknown) {
920
- this._clearUsageUnknownLocked(id, expectedMarker);
930
+ if (clean) {
931
+ this._clearUsageUnknownLocked(id, expectedMarker);
932
+ } else {
933
+ nextUsageUnknownMarker = this._replaceUsageUnknownLocked(
934
+ id,
935
+ canonicalSnapshot,
936
+ expectedMarker,
937
+ );
938
+ }
921
939
  }
922
940
  return {
923
941
  revision,
924
942
  filePath,
925
- ...(resolveUsageUnknown ? { usageUnknownMarker: null } : {}),
943
+ ...(resolveUsageUnknown
944
+ ? { usageUnknownMarker: nextUsageUnknownMarker }
945
+ : {}),
926
946
  };
927
947
  },
928
948
  {
@@ -935,7 +955,11 @@ export class SessionBudgetSidecarStore {
935
955
  return result;
936
956
  }
937
957
 
938
- markUsageUnknown(sessionId, snapshot) {
958
+ markUsageUnknown(
959
+ sessionId,
960
+ snapshot,
961
+ { expectedUsageUnknownMarker = null } = {},
962
+ ) {
939
963
  const id = String(sessionId);
940
964
  const filePath = this.pathForSession(id);
941
965
  this._assertSupported(filePath);
@@ -945,6 +969,12 @@ export class SessionBudgetSidecarStore {
945
969
  markerPath,
946
970
  "snapshot",
947
971
  );
972
+ const expectedMarker = expectedUsageUnknownMarker
973
+ ? normalizeUsageUnknownMarkerIdentity(
974
+ expectedUsageUnknownMarker,
975
+ markerPath,
976
+ )
977
+ : null;
948
978
  if (usageSettlementEntries(canonicalSnapshot).length === 0) {
949
979
  throw runtimeError(
950
980
  "marker",
@@ -965,6 +995,18 @@ export class SessionBudgetSidecarStore {
965
995
  filePath,
966
996
  () => {
967
997
  const current = readRecordAt(markerPath, id, this._fs, this._ioOptions);
998
+ const actualMarker = usageUnknownMarkerIdentity(current);
999
+ const markerMatches = current
1000
+ ? sameUsageUnknownMarkerIdentity(expectedMarker, actualMarker)
1001
+ : expectedMarker === null;
1002
+ if (!current && !markerMatches) {
1003
+ throw runtimeError(
1004
+ "conflict",
1005
+ markerPath,
1006
+ new Error("usage-unknown marker disappeared after it was observed"),
1007
+ "ERR_SESSION_BUDGET_MARKER_CONFLICT",
1008
+ );
1009
+ }
968
1010
  if (current?.revision === Number.MAX_SAFE_INTEGER) {
969
1011
  throw runtimeError(
970
1012
  "revision",
@@ -997,7 +1039,7 @@ export class SessionBudgetSidecarStore {
997
1039
  this._ioOptions,
998
1040
  );
999
1041
  const usageUnknownMarker = usageUnknownMarkerIdentity(committed);
1000
- if (current) {
1042
+ if (!markerMatches) {
1001
1043
  // Preserve the newly reported settlement, but do not let a runtime
1002
1044
  // that did not observe the prior marker later finalize both. The
1003
1045
  // caller fails closed and a fresh recovery host must see the union.
@@ -1019,6 +1061,52 @@ export class SessionBudgetSidecarStore {
1019
1061
  );
1020
1062
  }
1021
1063
 
1064
+ _replaceUsageUnknownLocked(sessionId, snapshot, expectedMarker) {
1065
+ const id = String(sessionId);
1066
+ const markerPath = this.usageUnknownPathForSession(id);
1067
+ const marker = readRecordAt(markerPath, id, this._fs, this._ioOptions);
1068
+ const actualMarker = usageUnknownMarkerIdentity(marker);
1069
+ if (!sameUsageUnknownMarkerIdentity(expectedMarker, actualMarker)) {
1070
+ throw runtimeError(
1071
+ "conflict",
1072
+ markerPath,
1073
+ new Error("usage-unknown marker changed before settlement"),
1074
+ "ERR_SESSION_BUDGET_MARKER_CONFLICT",
1075
+ );
1076
+ }
1077
+ if (marker.revision === Number.MAX_SAFE_INTEGER) {
1078
+ throw runtimeError(
1079
+ "revision",
1080
+ markerPath,
1081
+ new Error("usage-unknown marker revision is exhausted"),
1082
+ "ERR_SESSION_BUDGET_UNSAFE_REVISION",
1083
+ );
1084
+ }
1085
+ const canonicalSnapshot = canonicalizeSnapshot(
1086
+ snapshot,
1087
+ markerPath,
1088
+ "snapshot",
1089
+ );
1090
+ if (usageSettlementEntries(canonicalSnapshot).length === 0) {
1091
+ throw runtimeError(
1092
+ "marker",
1093
+ markerPath,
1094
+ new TypeError("usage-unknown marker requires a pending settlement"),
1095
+ );
1096
+ }
1097
+ const record = {
1098
+ version: SESSION_BUDGET_SIDECAR_VERSION,
1099
+ sessionId: id,
1100
+ revision: marker.revision + 1,
1101
+ storedAt: new Date(this._now()).toISOString(),
1102
+ writer: { pid: process.pid },
1103
+ snapshot: canonicalSnapshot,
1104
+ };
1105
+ writeRecordAt(markerPath, record, this._fs, this._ioOptions);
1106
+ const committed = readRecordAt(markerPath, id, this._fs, this._ioOptions);
1107
+ return usageUnknownMarkerIdentity(committed);
1108
+ }
1109
+
1022
1110
  clearUsageUnknown(sessionId, { expectedUsageUnknownMarker = null } = {}) {
1023
1111
  const id = String(sessionId);
1024
1112
  const filePath = this.pathForSession(id);
@@ -1194,6 +1282,9 @@ class SharedSessionBudgetRuntime {
1194
1282
  const marker = this.store.markUsageUnknown(
1195
1283
  this.sessionId,
1196
1284
  change.snapshot,
1285
+ {
1286
+ expectedUsageUnknownMarker: this.usageUnknownMarker,
1287
+ },
1197
1288
  );
1198
1289
  const usageUnknownMarker = normalizeUsageUnknownMarkerIdentity(
1199
1290
  marker?.usageUnknownMarker,
@@ -0,0 +1,63 @@
1
+ import {
2
+ sessionBudgetAdmissionError,
3
+ sessionBudgetUsageUnknownError,
4
+ } from "./session-budget-production-root.js";
5
+
6
+ function isRootUsage(event) {
7
+ return !event?.attribution;
8
+ }
9
+
10
+ export function beginSessionBudgetUsage(
11
+ sessionBudget,
12
+ event,
13
+ operation = "provider usage",
14
+ ) {
15
+ if (
16
+ !isRootUsage(event) ||
17
+ typeof sessionBudget?.beginUsageSettlement !== "function"
18
+ ) {
19
+ return null;
20
+ }
21
+ const admission = sessionBudget.beginUsageSettlement({ id: event.callId });
22
+ if (!admission?.ok) {
23
+ throw sessionBudgetAdmissionError(admission?.reason, operation);
24
+ }
25
+ return admission;
26
+ }
27
+
28
+ export function recordSessionBudgetUsage(
29
+ sessionBudget,
30
+ event,
31
+ operation = "provider usage settlement",
32
+ ) {
33
+ if (!isRootUsage(event) || typeof sessionBudget?.recordUsage !== "function") {
34
+ return null;
35
+ }
36
+ const status = sessionBudget.recordUsage({
37
+ ...(event?.callId ? { callId: event.callId } : {}),
38
+ provider: event?.provider || null,
39
+ model: event?.model || null,
40
+ usage: event?.usage || null,
41
+ });
42
+ if (status?.aborted) {
43
+ throw sessionBudgetAdmissionError(status.reason, operation);
44
+ }
45
+ return status;
46
+ }
47
+
48
+ export function markSessionBudgetUsageUnknown(sessionBudget, event) {
49
+ if (
50
+ !isRootUsage(event) ||
51
+ typeof sessionBudget?.markUsageUnknown !== "function"
52
+ ) {
53
+ return null;
54
+ }
55
+ return sessionBudget.markUsageUnknown({ callId: event.callId });
56
+ }
57
+
58
+ export function rejectSessionBudgetUsageUnknown(
59
+ event,
60
+ operation = "provider usage settlement",
61
+ ) {
62
+ throw sessionBudgetUsageUnknownError(operation, event?.callId || null);
63
+ }
@@ -27,6 +27,7 @@ import {
27
27
  sanitizePersistedMessages,
28
28
  } from "./session-message-provenance.js";
29
29
  import { createSessionTranscriptStructureProjection } from "./session-transcript-structure.js";
30
+ import { normalizeSessionBudgetRootConfig } from "./session-budget-production-root.js";
30
31
 
31
32
  export const SESSION_HOST_SNAPSHOT_SCHEMA =
32
33
  "chainlesschain.session-host-snapshot/v1";
@@ -123,6 +124,14 @@ function titleFromEvents(events) {
123
124
  return title;
124
125
  }
125
126
 
127
+ function sessionBudgetRootFromEvents(events) {
128
+ const sessionStart = events.find((event) => event?.type === "session_start");
129
+ if (!Object.hasOwn(sessionStart?.data || {}, "sessionBudgetRoot")) {
130
+ return null;
131
+ }
132
+ return normalizeSessionBudgetRootConfig(sessionStart.data.sessionBudgetRoot);
133
+ }
134
+
126
135
  function validatePlainSessionEvents(events, sessionId) {
127
136
  // Validate the complete nested graph without invoking accessors/Proxy traps,
128
137
  // then retain the original in-process objects so an already-established
@@ -292,12 +301,14 @@ export function projectVerifiedSessionHostSnapshot(sessionId, events) {
292
301
  }),
293
302
  messages,
294
303
  recovery,
304
+ sessionBudgetRoot: sessionBudgetRootFromEvents(safeEvents),
295
305
  });
296
306
  }
297
307
 
298
308
  function createStreamingSessionHostProjection(sessionId) {
299
309
  let title = "Untitled";
300
310
  let lastEventType = null;
311
+ let sessionBudgetRoot = null;
301
312
  const structure = createSessionTranscriptStructureProjection(sessionId, {
302
313
  failFast: true,
303
314
  });
@@ -313,8 +324,13 @@ function createStreamingSessionHostProjection(sessionId) {
313
324
  throw new TypeError("Verified session event must be plain JSON data");
314
325
  }
315
326
  structure.accept(safeEvent);
316
- if (safeEvent.type === "session_start" && safeEvent.data?.title) {
317
- title = String(safeEvent.data.title);
327
+ if (safeEvent.type === "session_start") {
328
+ if (safeEvent.data?.title) title = String(safeEvent.data.title);
329
+ if (Object.hasOwn(safeEvent.data || {}, "sessionBudgetRoot")) {
330
+ sessionBudgetRoot = normalizeSessionBudgetRootConfig(
331
+ safeEvent.data.sessionBudgetRoot,
332
+ );
333
+ }
318
334
  } else if (safeEvent.type === "session_rename" && safeEvent.data?.title) {
319
335
  title = String(safeEvent.data.title);
320
336
  }
@@ -347,6 +363,7 @@ function createStreamingSessionHostProjection(sessionId) {
347
363
  }),
348
364
  messages,
349
365
  recovery,
366
+ sessionBudgetRoot,
350
367
  });
351
368
  },
352
369
  };