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
@@ -23,6 +23,12 @@ import {
23
23
  import { isGitRepo } from "./git-integration.js";
24
24
  import { markRuntimeLedgerPersistenceError } from "./runtime-usage-ledger.js";
25
25
  import { IterationBudget } from "./iteration-budget.js";
26
+ import {
27
+ beginSessionBudgetUsage,
28
+ markSessionBudgetUsageUnknown,
29
+ recordSessionBudgetUsage,
30
+ rejectSessionBudgetUsageUnknown,
31
+ } from "./session-budget-usage.js";
26
32
 
27
33
  // ─── Constants ──────────────────────────────────────────────────────────────
28
34
 
@@ -162,6 +168,8 @@ export class SubAgentContext {
162
168
  * work cannot begin without a durable parent boundary.
163
169
  * @param {function} [options.onUsageSettlement] - Synchronous observer for
164
170
  * real child token-usage/model-usage-unknown settlements.
171
+ * @param {function} [options.onProviderReceipt] - Synchronous observer for
172
+ * a provider-returned receipt before the matching usage settlement.
165
173
  * @param {function} [options.onToolCallBoundary] - Synchronous observer for
166
174
  * real child tool-executing boundaries.
167
175
  * @param {function} [options.onToolCallSettlement] - Synchronous observer for
@@ -284,6 +292,10 @@ export class SubAgentContext {
284
292
  typeof options.onUsageSettlement === "function"
285
293
  ? options.onUsageSettlement
286
294
  : null;
295
+ this._onProviderReceipt =
296
+ typeof options.onProviderReceipt === "function"
297
+ ? options.onProviderReceipt
298
+ : null;
287
299
  this._onToolCallBoundary =
288
300
  typeof options.onToolCallBoundary === "function"
289
301
  ? options.onToolCallBoundary
@@ -690,6 +702,7 @@ export class SubAgentContext {
690
702
  const pendingToolCalls = new Map();
691
703
  let observeUsageBoundary = null;
692
704
  let observeUsageSettlement = null;
705
+ let observeProviderReceipt = null;
693
706
  let observeToolBoundary = null;
694
707
  let observeToolSettlement = null;
695
708
  const providerReceiptForSettlement = (event) => {
@@ -739,6 +752,17 @@ export class SubAgentContext {
739
752
  requireSyncUsageObserver(this._onUsageSettlement, event, "settlement");
740
753
  pendingUsageCalls.delete(callId);
741
754
  };
755
+ observeProviderReceipt = (event) => {
756
+ const callId = requireUsageCallId(event, "receipt");
757
+ if (!pendingUsageCalls.has(callId)) {
758
+ throw runtimeUsageObserverFailure(
759
+ null,
760
+ "CC_USAGE_RECEIPT_BOUNDARY_MISSING",
761
+ `Child provider receipt has no matching boundary: ${callId}`,
762
+ );
763
+ }
764
+ requireSyncUsageObserver(this._onProviderReceipt, event, "receipt");
765
+ };
742
766
 
743
767
  // agentLoop yields model-usage-started before provider work. Its consumer
744
768
  // runs synchronously before requesting the next event, so a durable host
@@ -747,6 +771,9 @@ export class SubAgentContext {
747
771
  // directly, before its provider query, rather than yielding a start event.
748
772
  options.onUsageBoundary = observeUsageBoundary;
749
773
  options.onUsageSettlement = observeUsageSettlement;
774
+ if (this._onProviderReceipt) {
775
+ options.onProviderReceipt = observeProviderReceipt;
776
+ }
750
777
  options.strictUsageTelemetry = true;
751
778
 
752
779
  observeToolBoundary = (event) => {
@@ -869,21 +896,28 @@ export class SubAgentContext {
869
896
  }
870
897
  }
871
898
  if (event.type === "provider-request-receipt") {
872
- this._providerRequestReceipts.push(
873
- Object.freeze({
874
- protocol: event.protocol,
875
- provider: event.provider,
876
- workflowEffectId: event.workflowEffectId,
877
- callId: event.callId,
878
- callSequence: event.callSequence,
879
- source: event.source,
880
- clientRequestId: event.clientRequestId,
881
- requestId: event.requestId || null,
882
- responseId: event.responseId || null,
883
- requestIdentitySemantics: event.requestIdentitySemantics,
884
- independentlyReadable: event.independentlyReadable,
885
- }),
886
- );
899
+ const receipt = Object.freeze({
900
+ protocol: event.protocol,
901
+ provider: event.provider,
902
+ workflowEffectId: event.workflowEffectId,
903
+ callId: event.callId,
904
+ callSequence: event.callSequence,
905
+ source: event.source,
906
+ clientRequestId: event.clientRequestId,
907
+ requestId: event.requestId || null,
908
+ responseId: event.responseId || null,
909
+ requestIdentitySemantics: event.requestIdentitySemantics,
910
+ independentlyReadable: event.independentlyReadable,
911
+ });
912
+ this._providerRequestReceipts.push(receipt);
913
+ if (strictUsageTelemetry && this._onProviderReceipt) {
914
+ observeProviderReceipt({
915
+ type: "provider-request-receipt",
916
+ ...receipt,
917
+ workflowRequestSource: receipt.source,
918
+ providerReceipt: { ...receipt },
919
+ });
920
+ }
887
921
  }
888
922
 
889
923
  if (event.type === "token-usage") {
@@ -914,13 +948,11 @@ export class SubAgentContext {
914
948
  // call into a permanently open `started` row.
915
949
  // Attributed usage was already charged by the nested child sharing
916
950
  // this same authority, so only direct usage is folded here.
917
- if (!event.attribution && this.sessionBudget?.recordUsage) {
918
- this.sessionBudget.recordUsage({
919
- provider: event.provider || null,
920
- model: event.model || null,
921
- usage: event.usage || null,
922
- });
923
- }
951
+ recordSessionBudgetUsage(
952
+ this.sessionBudget,
953
+ event,
954
+ "sub-agent usage settlement",
955
+ );
924
956
  if (this._onUsage) {
925
957
  // Forward real usage to the spawner. A nested child's event already
926
958
  // carries its own attribution frame — preserve it (deepest wins).
@@ -954,6 +986,11 @@ export class SubAgentContext {
954
986
  observeUsageBoundary(event);
955
987
  }
956
988
  }
989
+ beginSessionBudgetUsage(
990
+ this.sessionBudget,
991
+ event,
992
+ "sub-agent provider call",
993
+ );
957
994
  }
958
995
 
959
996
  if (event.type === "tool-executing" && strictUsageTelemetry) {
@@ -990,6 +1027,10 @@ export class SubAgentContext {
990
1027
  observeUsageSettlement(forwardedUnknown);
991
1028
  forwardedUnknown.ledgerPersisted = true;
992
1029
  }
1030
+ const budgetUsageUnknown = markSessionBudgetUsageUnknown(
1031
+ this.sessionBudget,
1032
+ event,
1033
+ );
993
1034
  if (this._onUsage) {
994
1035
  if (strictUsageTelemetry) {
995
1036
  requireSyncUsageObserver(
@@ -1005,6 +1046,12 @@ export class SubAgentContext {
1005
1046
  }
1006
1047
  }
1007
1048
  }
1049
+ if (budgetUsageUnknown) {
1050
+ rejectSessionBudgetUsageUnknown(
1051
+ event,
1052
+ "sub-agent provider call",
1053
+ );
1054
+ }
1008
1055
  }
1009
1056
 
1010
1057
  if (event.type === "tool-executing") {
@@ -23,6 +23,7 @@ import crypto from "crypto";
23
23
  import { fileURLToPath } from "url";
24
24
  import { getInlineSource as getEnvelopeInlineSource } from "./web-ui-envelope.js";
25
25
  import { CLISkillLoader } from "./skill-loader.js";
26
+ import { authorizeArtifactContentAccess } from "./artifact-access-ledger.js";
26
27
 
27
28
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
29
 
@@ -1333,6 +1334,15 @@ export function handleApiRequest(req, res, ctx = {}) {
1333
1334
  res.end(JSON.stringify({ error: "Stored copy missing" }));
1334
1335
  return;
1335
1336
  }
1337
+ const accessIdHeader = req.headers["x-chainlesschain-access-id"];
1338
+ const authorization = authorizeArtifactContentAccess(store, {
1339
+ artifactId: entry.id,
1340
+ client: "web",
1341
+ action: "download",
1342
+ ...(typeof accessIdHeader === "string"
1343
+ ? { accessId: accessIdHeader }
1344
+ : {}),
1345
+ });
1336
1346
  const downloadName =
1337
1347
  entry.title && path.extname(entry.title)
1338
1348
  ? entry.title
@@ -1343,8 +1353,9 @@ export function handleApiRequest(req, res, ctx = {}) {
1343
1353
  "Content-Disposition": encodeDispositionFilename(downloadName),
1344
1354
  "Cache-Control": "no-store",
1345
1355
  "X-Content-Type-Options": "nosniff",
1356
+ "X-ChainlessChain-Access-Digest": authorization.access.eventDigest,
1346
1357
  });
1347
- const stream = fs.createReadStream(storedPath);
1358
+ const stream = fs.createReadStream(authorization.storedPath);
1348
1359
  stream.on("error", () => res.destroy());
1349
1360
  stream.pipe(res);
1350
1361
  } catch (err) {
@@ -14,14 +14,18 @@
14
14
  * reclaimed merely because the directory is old, and corrupt ownership fails
15
15
  * closed. The default filesystem publishes a fully populated, uniquely staged
16
16
  * directory in one rename so a process killed during acquisition cannot expose
17
- * an ownerless lock. Confirmed-dead owners are reclaimed with an exact-token
18
- * marker inside the lock directory so a delayed contender cannot delete a
19
- * replacement lock. Strict callers may use bounded jittered retries and an
17
+ * an ownerless lock. Confirmed-dead owners are reclaimed with an exact-token,
18
+ * process-owned claim inside the lock directory so a delayed contender cannot
19
+ * delete a replacement lock and another contender can take over if the first
20
+ * reclaimer is killed. Strict callers may use bounded jittered retries and an
20
21
  * after-release yield to avoid Windows sharing transients and fixed-interval
21
22
  * lock convoys.
22
23
  *
23
24
  * @param {string} targetPath the file being guarded (lock is `${targetPath}.lock`)
24
- * @param {(ctx:{locked:boolean})=>T} fn critical section; receives whether the lock was held
25
+ * @param {(ctx:{locked:boolean,publishReleaseAfterPathRemoved:(pendingPath:string)=>boolean})=>T} fn
26
+ * critical section; `publishReleaseAfterPathRemoved` lets a transaction
27
+ * publish an exact, contender-completable handoff before its final atomic
28
+ * rename removes a uniquely named staging path
25
29
  * @returns {T} whatever `fn` returns
26
30
  */
27
31
  export function withFileLock(targetPath, fn, opts = {}) {
@@ -54,7 +58,9 @@ export function withFileLock(targetPath, fn, opts = {}) {
54
58
  };
55
59
  let held = false;
56
60
  let acquisitionError = null;
61
+ let lastOwnerObservation = null;
57
62
  let retryAttempt = 0;
63
+ let releasePublished = false;
58
64
  const deadline = _now() + timeoutMs;
59
65
 
60
66
  for (;;) {
@@ -88,6 +94,17 @@ export function withFileLock(targetPath, fn, opts = {}) {
88
94
  const incumbent = ownerRead.owner;
89
95
  acquisitionError = ownerRead.error;
90
96
  if (incumbent) {
97
+ let incumbentAlive = true;
98
+ try {
99
+ incumbentAlive = isOwnerAlive(incumbent);
100
+ } catch {
101
+ // An unavailable identity probe must retain the lock.
102
+ }
103
+ lastOwnerObservation = {
104
+ pid: incumbent.pid,
105
+ startedAt: incumbent.startedAt,
106
+ alive: incumbentAlive,
107
+ };
91
108
  // The owner writes this exact-token marker only after its critical
92
109
  // section has finished. A Windows sharing violation can then prevent
93
110
  // the final recursive removal while the owner process remains alive,
@@ -102,11 +119,19 @@ export function withFileLock(targetPath, fn, opts = {}) {
102
119
  owner,
103
120
  isOwnerAlive,
104
121
  );
122
+ lastOwnerObservation.releasePublished = publishedRelease.published;
105
123
  if (publishedRelease.completed) continue;
106
- if (!publishedRelease.published && !isOwnerAlive(incumbent)) {
107
- if (reclaimOwnedDirectory(_fs, lockDir, incumbent, isOwnerAlive)) {
124
+ if (!incumbentAlive) {
125
+ // A published handoff can still retain its unique staging path when
126
+ // the owner is killed between marker publication and the atomic
127
+ // replacement. No process can commit that staging file after the
128
+ // exact owner is proven dead, so the lock directory is reclaimable
129
+ // whether the transaction committed or not. Leave the private
130
+ // staging file untouched; it cannot affect the guarded target.
131
+ if (
132
+ reclaimOwnedDirectory(_fs, lockDir, incumbent, owner, isOwnerAlive)
133
+ )
108
134
  continue;
109
- }
110
135
  }
111
136
  } else if (!failIfUnavailable) {
112
137
  // Compatibility for legacy best-effort locks that predate owner.json.
@@ -139,12 +164,21 @@ export function withFileLock(targetPath, fn, opts = {}) {
139
164
  }
140
165
 
141
166
  if (!held && failIfUnavailable) {
167
+ const ownerDetails = lastOwnerObservation
168
+ ? `; owner pid=${lastOwnerObservation.pid}, ageMs=${Math.max(
169
+ 0,
170
+ _now() - lastOwnerObservation.startedAt,
171
+ )}, alive=${lastOwnerObservation.alive}, releasePublished=${Boolean(lastOwnerObservation.releasePublished)}`
172
+ : "";
142
173
  const error = new Error(
143
- `Could not acquire state lock: ${targetPath}`,
174
+ `Could not acquire state lock: ${targetPath}${ownerDetails}`,
144
175
  acquisitionError ? { cause: acquisitionError } : undefined,
145
176
  );
146
177
  error.code = "STATE_LOCK_UNAVAILABLE";
147
178
  error.attempts = retryAttempt + 1;
179
+ error.lockOwner = lastOwnerObservation
180
+ ? Object.freeze({ ...lastOwnerObservation })
181
+ : null;
148
182
  throw error;
149
183
  }
150
184
 
@@ -152,7 +186,32 @@ export function withFileLock(targetPath, fn, opts = {}) {
152
186
  let bodyThrew = false;
153
187
  let result;
154
188
  try {
155
- result = fn({ locked: held });
189
+ result = fn({
190
+ locked: held,
191
+ publishReleaseAfterPathRemoved(pendingPath) {
192
+ if (!held || typeof pendingPath !== "string" || !pendingPath) {
193
+ return false;
194
+ }
195
+ const current = readOwner(_fs, ownerPath);
196
+ if (!sameOwner(current, owner)) return false;
197
+ const markerPath = path.join(lockDir, `.release-${owner.token}`);
198
+ const marker = { ...owner, releaseAfterPathRemoved: pendingPath };
199
+ try {
200
+ writeOwnerMarker(_fs, markerPath, marker);
201
+ } catch (error) {
202
+ if (error?.code !== "EEXIST") throw error;
203
+ const existing = readOwner(_fs, markerPath);
204
+ if (
205
+ !sameOwner(existing, owner) ||
206
+ existing?.releaseAfterPathRemoved !== pendingPath
207
+ ) {
208
+ return false;
209
+ }
210
+ }
211
+ releasePublished = true;
212
+ return true;
213
+ },
214
+ });
156
215
  } catch (error) {
157
216
  bodyThrew = true;
158
217
  bodyError = error;
@@ -161,7 +220,13 @@ export function withFileLock(targetPath, fn, opts = {}) {
161
220
  let releaseError = null;
162
221
  if (held) {
163
222
  try {
164
- released = releaseOwnedDirectory(_fs, lockDir, owner, isOwnerAlive);
223
+ released = releaseOwnedDirectory(
224
+ _fs,
225
+ lockDir,
226
+ owner,
227
+ isOwnerAlive,
228
+ releasePublished,
229
+ );
165
230
  } catch (error) {
166
231
  releaseError = error;
167
232
  }
@@ -251,6 +316,15 @@ function directoryExists(_fs, target) {
251
316
  }
252
317
  }
253
318
 
319
+ function pathStatus(_fs, target) {
320
+ try {
321
+ _fs.statSync(target);
322
+ return "present";
323
+ } catch (error) {
324
+ return error?.code === "ENOENT" ? "absent" : "unknown";
325
+ }
326
+ }
327
+
254
328
  function isProcessAlive(pid) {
255
329
  try {
256
330
  process.kill(pid, 0);
@@ -361,21 +435,65 @@ function writeOwnerMarker(_fs, markerPath, owner) {
361
435
  });
362
436
  }
363
437
 
364
- function reclaimOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
365
- const markerPath = path.join(lockDir, `.reclaim-${owner.token}`);
366
- try {
367
- writeOwnerMarker(_fs, markerPath, owner);
368
- } catch (error) {
369
- if (error?.code === "ENOENT" || error?.code === "EEXIST") return false;
370
- throw error;
438
+ function reclaimOwnedDirectory(_fs, lockDir, incumbent, claimant, ownerAlive) {
439
+ const claimPath = path.join(lockDir, `.reclaim-${incumbent.token}`);
440
+ for (;;) {
441
+ const existingClaim = readOwner(_fs, claimPath);
442
+ if (existingClaim) {
443
+ if (sameOwner(existingClaim, claimant)) break;
444
+ if (!ownerAlive(existingClaim)) {
445
+ if (removeOwnMarker(_fs, claimPath, existingClaim)) continue;
446
+ }
447
+ return false;
448
+ }
449
+ try {
450
+ // The claim belongs to the contender, not the dead incumbent. If this
451
+ // contender is killed before detaching the directory, another process
452
+ // can prove that exact claimant dead and safely take over.
453
+ writeOwnerMarker(_fs, claimPath, claimant);
454
+ break;
455
+ } catch (error) {
456
+ if (error?.code === "ENOENT") {
457
+ return !directoryExists(_fs, lockDir);
458
+ }
459
+ if (error?.code === "EEXIST" || isTransientLockError(error)) {
460
+ return false;
461
+ }
462
+ throw error;
463
+ }
371
464
  }
465
+
372
466
  const current = readOwner(_fs, path.join(lockDir, "owner.json"));
373
- if (!sameOwner(current, owner) || ownerAlive(owner)) {
374
- removeOwnMarker(_fs, markerPath, owner);
467
+ if (!sameOwner(current, incumbent) || ownerAlive(incumbent)) {
468
+ removeOwnMarker(_fs, claimPath, claimant);
375
469
  return false;
376
470
  }
377
- _fs.rmSync(lockDir, { recursive: true, force: true });
378
- return true;
471
+
472
+ if (typeof _fs.renameSync !== "function") {
473
+ _fs.rmSync(lockDir, { recursive: true, force: true });
474
+ return true;
475
+ }
476
+
477
+ const cleanupDir = `${lockDir}.reclaimed-${incumbent.token}-${claimant.token}`;
478
+ try {
479
+ // Free the acquisition path atomically before recursive cleanup. A killed
480
+ // reclaimer can therefore leave only uniquely named debris, never a
481
+ // claimant marker that permanently fences the shared lock path.
482
+ _fs.renameSync(lockDir, cleanupDir);
483
+ try {
484
+ _fs.rmSync(cleanupDir, { recursive: true, force: true });
485
+ } catch {
486
+ /* private reclaim debris is harmless after the lock path is detached */
487
+ }
488
+ return true;
489
+ } catch (error) {
490
+ removeOwnMarker(_fs, claimPath, claimant);
491
+ if (error?.code === "ENOENT") {
492
+ return !directoryExists(_fs, lockDir);
493
+ }
494
+ if (isTransientLockError(error)) return false;
495
+ throw error;
496
+ }
379
497
  }
380
498
 
381
499
  function reclaimLegacyDirectory(_fs, lockDir, stat) {
@@ -401,9 +519,23 @@ function reclaimLegacyDirectory(_fs, lockDir, stat) {
401
519
 
402
520
  function completePublishedRelease(_fs, lockDir, owner, claimant, ownerAlive) {
403
521
  const markerPath = path.join(lockDir, `.release-${owner.token}`);
404
- if (!sameOwner(readOwner(_fs, markerPath), owner)) {
522
+ const marker = readOwner(_fs, markerPath);
523
+ if (!sameOwner(marker, owner)) {
405
524
  return { published: false, completed: false };
406
525
  }
526
+ // A state transaction can stage its exact replacement, publish this marker,
527
+ // and only then atomically rename the staging file over the guarded target.
528
+ // Contenders may complete the release only after that unique staging path is
529
+ // gone. This closes the otherwise unbounded scheduler-preemption window
530
+ // between a committed rename and the owner's first release syscall without
531
+ // ever exposing an in-progress write to a competing critical section.
532
+ if (
533
+ typeof marker.releaseAfterPathRemoved === "string" &&
534
+ marker.releaseAfterPathRemoved &&
535
+ pathStatus(_fs, marker.releaseAfterPathRemoved) !== "absent"
536
+ ) {
537
+ return { published: true, completed: false };
538
+ }
407
539
  const claimPath = path.join(lockDir, `.release-claim-${owner.token}`);
408
540
  for (;;) {
409
541
  const existingClaim = readOwner(_fs, claimPath);
@@ -461,9 +593,20 @@ function completePublishedRelease(_fs, lockDir, owner, claimant, ownerAlive) {
461
593
  }
462
594
  }
463
595
 
464
- function releaseOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
596
+ function releaseOwnedDirectory(
597
+ _fs,
598
+ lockDir,
599
+ owner,
600
+ ownerAlive,
601
+ releaseWasPublished = false,
602
+ ) {
465
603
  if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {
466
- return false;
604
+ // Once this exact owner published a guarded handoff, a contender is allowed
605
+ // to detach the old directory as soon as the final staging path disappears.
606
+ // A missing or replacement lock therefore proves successful release, not
607
+ // ownership loss. Without that publication the strict legacy verdict stays
608
+ // fail-closed.
609
+ return releaseWasPublished;
467
610
  }
468
611
  // Atomically move the exact owned directory out of the acquisition path,
469
612
  // then remove that uniquely-tokened directory. A replacement owner may
@@ -482,7 +625,7 @@ function releaseOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
482
625
  }
483
626
  return true;
484
627
  } catch (error) {
485
- if (error?.code === "ENOENT") return false;
628
+ if (error?.code === "ENOENT") return releaseWasPublished;
486
629
  // Windows sharing transients fall back to the marker-based release.
487
630
  }
488
631
  }
@@ -490,7 +633,13 @@ function releaseOwnedDirectory(_fs, lockDir, owner, ownerAlive) {
490
633
  try {
491
634
  writeOwnerMarker(_fs, markerPath, owner);
492
635
  } catch (error) {
493
- if (error?.code === "ENOENT" || error?.code === "EEXIST") return false;
636
+ if (error?.code === "ENOENT") return releaseWasPublished;
637
+ if (error?.code === "EEXIST") {
638
+ const existing = readOwner(_fs, markerPath);
639
+ if (!sameOwner(existing, owner)) return false;
640
+ return completePublishedRelease(_fs, lockDir, owner, owner, ownerAlive)
641
+ .published;
642
+ }
494
643
  throw error;
495
644
  }
496
645
  if (!sameOwner(readOwner(_fs, path.join(lockDir, "owner.json")), owner)) {