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
@@ -402,8 +402,9 @@ export class TeamRunner {
402
402
  this._setState(holder, "shutdown", { reason: "fatal-error" });
403
403
  return;
404
404
  }
405
- if (this.sessionBudget?.signal?.aborted) {
406
- const reason = this.sessionBudget.reason?.() || "session-aborted";
405
+ const sessionStopReason = this._sessionBudgetStopReason();
406
+ if (sessionStopReason) {
407
+ const reason = sessionStopReason;
407
408
  if (!this._budgetStopped) {
408
409
  this._budgetStopped = true;
409
410
  this._emit("run:budget-exhausted", { reason });
@@ -606,9 +607,7 @@ export class TeamRunner {
606
607
  // Preserve an explicit human interruption so `_execute` can settle it
607
608
  // fail-closed for adjudication instead of silently abandoning it.
608
609
  const preparedBudgetReason = this.budget?.reason?.();
609
- const preparedSessionReason = this.sessionBudget?.signal?.aborted
610
- ? this.sessionBudget.reason?.() || "session-aborted"
611
- : null;
610
+ const preparedSessionReason = this._sessionBudgetStopReason();
612
611
  if (
613
612
  !claim.interruption &&
614
613
  !claim.coordinatorAbort &&
@@ -718,6 +717,7 @@ export class TeamRunner {
718
717
  sessionWork,
719
718
  sessionBudgetSettled: false,
720
719
  sessionBudgetUsageHandledByExecutor: false,
720
+ sessionBudgetUsageUnknown: false,
721
721
  sessionBudgetView: null,
722
722
  unregisterSessionAbortable: null,
723
723
  interruption: null,
@@ -908,13 +908,18 @@ export class TeamRunner {
908
908
  if (typeof value !== "function") return value;
909
909
  if (methods.has(property)) return methods.get(property);
910
910
  const method =
911
- property === "recordUsage"
911
+ property === "recordUsage" || property === "markUsageUnknown"
912
912
  ? (...args) => {
913
+ const result = value.apply(target, args);
913
914
  // Usage charged through the inherited authority is already in
914
915
  // the global ledger. Remember that ownership so the task's
915
- // returned aggregate is not charged a second time.
916
+ // returned aggregate is not charged a second time. Unknown
917
+ // usage must also prevent this task becoming a phantom success.
916
918
  claim.sessionBudgetUsageHandledByExecutor = true;
917
- return value.apply(target, args);
919
+ if (property === "markUsageUnknown") {
920
+ claim.sessionBudgetUsageUnknown = true;
921
+ }
922
+ return result;
918
923
  }
919
924
  : value.bind(target);
920
925
  methods.set(property, method);
@@ -927,10 +932,25 @@ export class TeamRunner {
927
932
  return claim.sessionBudgetView;
928
933
  }
929
934
 
935
+ _sessionBudgetStopReason() {
936
+ if (!this.sessionBudget) return null;
937
+ const reason = this.sessionBudget.reason?.() || null;
938
+ if (this.sessionBudget.signal?.aborted) {
939
+ return reason || "session-aborted";
940
+ }
941
+ return reason === "recovery-required" ? reason : null;
942
+ }
943
+
930
944
  _recordSessionUsage(claim, source) {
931
945
  if (!this.sessionBudget || claim.sessionBudgetSettled) return;
932
946
  claim.sessionBudgetSettled = true;
933
947
  if (claim.sessionBudgetUsageHandledByExecutor) return;
948
+ if (
949
+ !source?.usage &&
950
+ (!Array.isArray(source?.usageRecords) || source.usageRecords.length === 0)
951
+ ) {
952
+ return;
953
+ }
934
954
  this.sessionBudget.recordUsage({
935
955
  usage: source?.usage || null,
936
956
  usageRecords: source?.usageRecords || null,
@@ -941,6 +961,10 @@ export class TeamRunner {
941
961
 
942
962
  _sessionBudgetFailure(executionResult = null) {
943
963
  const sharedReason = this.sessionBudget?.signal?.reason;
964
+ const budgetReason =
965
+ sharedReason?.budgetReason ||
966
+ this.sessionBudget?.reason?.() ||
967
+ "session-aborted";
944
968
  // Never attach per-task usage to the shared AbortSignal reason: every
945
969
  // sibling observes that same Error object and would then re-record the
946
970
  // triggering task's usage as its own failure.
@@ -949,9 +973,12 @@ export class TeamRunner {
949
973
  `Session budget exhausted: ${this.sessionBudget?.reason?.() || "session-aborted"}`,
950
974
  sharedReason instanceof Error ? { cause: sharedReason } : undefined,
951
975
  );
952
- failure.code = sharedReason?.code || "ERR_SESSION_RESOURCE_BUDGET";
953
- failure.budgetReason =
954
- sharedReason?.budgetReason || this.sessionBudget?.reason?.();
976
+ failure.code =
977
+ sharedReason?.code ||
978
+ (budgetReason === "recovery-required"
979
+ ? "CC_SESSION_BUDGET_USAGE_UNKNOWN"
980
+ : "ERR_SESSION_RESOURCE_BUDGET");
981
+ failure.budgetReason = budgetReason;
955
982
  failure.retryable = false;
956
983
  if (executionResult && typeof executionResult === "object") {
957
984
  failure.usage = executionResult.usage || null;
@@ -1160,7 +1187,7 @@ export class TeamRunner {
1160
1187
  // Synchronous observers above (`onEvent`, mailbox, tracing) may abort the
1161
1188
  // shared authority after the worker's post-beforeTask check. This is the
1162
1189
  // final fail-closed fence immediately before executor side effects.
1163
- if (this.sessionBudget?.signal?.aborted) {
1190
+ if (this._sessionBudgetStopReason()) {
1164
1191
  throw this._sessionBudgetFailure();
1165
1192
  }
1166
1193
  const result = await this.runTask({
@@ -1186,7 +1213,10 @@ export class TeamRunner {
1186
1213
  throw claim.coordinatorAbort;
1187
1214
  }
1188
1215
  this._recordSessionUsage(claim, result);
1189
- if (this.sessionBudget?.signal?.aborted) {
1216
+ if (
1217
+ claim.sessionBudgetUsageUnknown ||
1218
+ this.sessionBudget?.signal?.aborted
1219
+ ) {
1190
1220
  throw this._sessionBudgetFailure(result);
1191
1221
  }
1192
1222
  // Executors are allowed to return after observing (or even ignoring) an
@@ -0,0 +1,461 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { createHash, randomUUID } from "node:crypto";
4
+ import { TextDecoder } from "node:util";
5
+ import { canonicalJson } from "./scheduler-kernel/contract.js";
6
+ import {
7
+ sameFileStatIdentity,
8
+ samePathHandleFileIdentity,
9
+ withTrustedFileParentSync,
10
+ } from "./secure-file-identity.js";
11
+ import { withFileLock } from "./with-file-lock.js";
12
+
13
+ export const ARTIFACT_ACCESS_EVENT_SCHEMA = "cc-artifact-content-access/v1";
14
+ export const ARTIFACT_ACCESS_LEDGER_SCHEMA =
15
+ "cc-artifact-content-access-ledger/v1";
16
+
17
+ export const ARTIFACT_ACCESS_CLIENTS = Object.freeze([
18
+ "cli",
19
+ "vscode",
20
+ "jetbrains",
21
+ "websocket",
22
+ "web",
23
+ ]);
24
+ export const ARTIFACT_ACCESS_ACTIONS = Object.freeze([
25
+ "open",
26
+ "preview",
27
+ "download",
28
+ "copy-path",
29
+ "reveal",
30
+ "open-external",
31
+ ]);
32
+
33
+ const ACCESS_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,255}$/u;
34
+ const ARTIFACT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/u;
35
+ const SHA256_RE = /^sha256:[a-f0-9]{64}$/u;
36
+ const MAX_LEDGER_BYTES = 16 * 1024 * 1024;
37
+ const MAX_LEDGER_EVENTS = 100_000;
38
+
39
+ function digest(domain, value) {
40
+ return `sha256:${createHash("sha256")
41
+ .update(domain, "utf8")
42
+ .update(canonicalJson(value, "artifactAccessLedger"), "utf8")
43
+ .digest("hex")}`;
44
+ }
45
+
46
+ function exactObject(value, fields, label) {
47
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
48
+ throw new TypeError(`${label} must be an object`);
49
+ }
50
+ const actual = Object.keys(value).sort();
51
+ const expected = [...fields].sort();
52
+ if (
53
+ actual.length !== expected.length ||
54
+ actual.some((field, index) => field !== expected[index])
55
+ ) {
56
+ throw new TypeError(`${label} has an invalid schema`);
57
+ }
58
+ return value;
59
+ }
60
+
61
+ function normalizeOptionalDigest(value, label) {
62
+ if (value === null) return null;
63
+ const normalized = String(value || "");
64
+ if (!SHA256_RE.test(normalized)) {
65
+ throw new TypeError(`${label} is invalid`);
66
+ }
67
+ return normalized;
68
+ }
69
+
70
+ function normalizeOptionalId(value, label) {
71
+ if (value === null) return null;
72
+ const normalized = String(value || "");
73
+ if (!ACCESS_ID_RE.test(normalized)) {
74
+ throw new TypeError(`${label} is invalid`);
75
+ }
76
+ return normalized;
77
+ }
78
+
79
+ export function normalizeArtifactAccessEvent(input, previous = null) {
80
+ const value = exactObject(
81
+ input,
82
+ [
83
+ "schema",
84
+ "sequence",
85
+ "previousEventDigest",
86
+ "accessId",
87
+ "artifactId",
88
+ "artifactSha256",
89
+ "recordDigest",
90
+ "artifactSessionId",
91
+ "client",
92
+ "action",
93
+ "authorizedAt",
94
+ "authorization",
95
+ "eventDigest",
96
+ ],
97
+ "artifact access event",
98
+ );
99
+ const sequence = Number(value.sequence);
100
+ const accessId = String(value.accessId || "");
101
+ const artifactId = String(value.artifactId || "");
102
+ const artifactSha256 = String(value.artifactSha256 || "");
103
+ const client = String(value.client || "");
104
+ const action = String(value.action || "");
105
+ const authorizedAt = String(value.authorizedAt || "");
106
+ const previousEventDigest = normalizeOptionalDigest(
107
+ value.previousEventDigest,
108
+ "artifact access predecessor digest",
109
+ );
110
+ const recordDigest = normalizeOptionalDigest(
111
+ value.recordDigest,
112
+ "artifact record digest",
113
+ );
114
+ const artifactSessionId = normalizeOptionalId(
115
+ value.artifactSessionId,
116
+ "artifact session id",
117
+ );
118
+ if (
119
+ value.schema !== ARTIFACT_ACCESS_EVENT_SCHEMA ||
120
+ !Number.isSafeInteger(sequence) ||
121
+ sequence < 1 ||
122
+ !ACCESS_ID_RE.test(accessId) ||
123
+ !ARTIFACT_ID_RE.test(artifactId) ||
124
+ !SHA256_RE.test(artifactSha256) ||
125
+ !ARTIFACT_ACCESS_CLIENTS.includes(client) ||
126
+ !ARTIFACT_ACCESS_ACTIONS.includes(action) ||
127
+ !Number.isFinite(Date.parse(authorizedAt)) ||
128
+ value.authorization !== "current-artifact-index-and-byte-readback" ||
129
+ !SHA256_RE.test(String(value.eventDigest || ""))
130
+ ) {
131
+ throw new TypeError("artifact access event is invalid");
132
+ }
133
+ if (
134
+ sequence !== (previous?.sequence || 0) + 1 ||
135
+ previousEventDigest !== (previous?.eventDigest || null)
136
+ ) {
137
+ throw new Error("artifact access event chain is invalid");
138
+ }
139
+ const material = {
140
+ schema: value.schema,
141
+ sequence,
142
+ previousEventDigest,
143
+ accessId,
144
+ artifactId,
145
+ artifactSha256,
146
+ recordDigest,
147
+ artifactSessionId,
148
+ client,
149
+ action,
150
+ authorizedAt,
151
+ authorization: value.authorization,
152
+ };
153
+ if (
154
+ value.eventDigest !==
155
+ digest("chainlesschain.artifact.content-access-event.v1\0", material)
156
+ ) {
157
+ throw new Error("artifact access event digest is invalid");
158
+ }
159
+ return Object.freeze({ ...material, eventDigest: value.eventDigest });
160
+ }
161
+
162
+ function ledgerPath(store) {
163
+ return path.join(path.resolve(store.dir), "content-access.jsonl");
164
+ }
165
+
166
+ function readLedgerBytes(filePath, runtimeFs, runtime = undefined) {
167
+ return withTrustedFileParentSync(
168
+ runtimeFs,
169
+ filePath,
170
+ ({ canonicalPath, parentDevice }) => {
171
+ let before;
172
+ try {
173
+ before = runtimeFs.lstatSync(canonicalPath, { bigint: true });
174
+ } catch (error) {
175
+ if (error?.code === "ENOENT") return Buffer.alloc(0);
176
+ throw error;
177
+ }
178
+ if (
179
+ before.isSymbolicLink() ||
180
+ !before.isFile() ||
181
+ Number(before.nlink) !== 1 ||
182
+ Number(before.size) > MAX_LEDGER_BYTES
183
+ ) {
184
+ throw new Error("artifact access ledger identity is invalid");
185
+ }
186
+ let descriptor = null;
187
+ try {
188
+ descriptor = runtimeFs.openSync(
189
+ canonicalPath,
190
+ runtimeFs.constants.O_RDONLY |
191
+ Number(runtimeFs.constants.O_NOFOLLOW || 0),
192
+ );
193
+ const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
194
+ if (
195
+ !opened.isFile() ||
196
+ Number(opened.nlink) !== 1 ||
197
+ !samePathHandleFileIdentity(before, opened, parentDevice, runtime)
198
+ ) {
199
+ throw new Error("artifact access ledger handle is invalid");
200
+ }
201
+ const bytes = runtimeFs.readFileSync(descriptor);
202
+ const after = runtimeFs.fstatSync(descriptor, { bigint: true });
203
+ if (
204
+ bytes.length > MAX_LEDGER_BYTES ||
205
+ Number(after.size) !== bytes.length ||
206
+ !sameFileStatIdentity(opened, after)
207
+ ) {
208
+ throw new Error("artifact access ledger changed while reading");
209
+ }
210
+ return bytes;
211
+ } finally {
212
+ if (descriptor !== null) runtimeFs.closeSync(descriptor);
213
+ }
214
+ },
215
+ { runtime },
216
+ );
217
+ }
218
+
219
+ function parseLedger(bytes) {
220
+ if (bytes.length === 0) return [];
221
+ let text;
222
+ try {
223
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
224
+ } catch {
225
+ throw new Error("artifact access ledger is not strict UTF-8");
226
+ }
227
+ if (!text.endsWith("\n")) {
228
+ throw new Error("artifact access ledger has a truncated tail");
229
+ }
230
+ const lines = text.slice(0, -1).split("\n");
231
+ if (lines.length > MAX_LEDGER_EVENTS) {
232
+ throw new Error("artifact access ledger exceeds its event limit");
233
+ }
234
+ const events = [];
235
+ for (const line of lines) {
236
+ let parsed;
237
+ try {
238
+ parsed = JSON.parse(line);
239
+ } catch {
240
+ throw new Error("artifact access ledger contains invalid JSON");
241
+ }
242
+ events.push(normalizeArtifactAccessEvent(parsed, events.at(-1) || null));
243
+ }
244
+ return events;
245
+ }
246
+
247
+ export function readArtifactAccessLedger(store, options = {}) {
248
+ if (!store?.dir || typeof store.list !== "function") {
249
+ throw new TypeError("artifact access ledger requires an ArtifactStore");
250
+ }
251
+ const runtimeFs = options.fs || fs;
252
+ const events = parseLedger(
253
+ readLedgerBytes(ledgerPath(store), runtimeFs, options.runtime),
254
+ );
255
+ return Object.freeze({
256
+ schema: ARTIFACT_ACCESS_LEDGER_SCHEMA,
257
+ eventCount: events.length,
258
+ headDigest: events.at(-1)?.eventDigest || null,
259
+ events: Object.freeze(events),
260
+ });
261
+ }
262
+
263
+ function normalizeAccessRequest(input = {}) {
264
+ const accessId = String(
265
+ input.accessId || `access_${randomUUID().replaceAll("-", "")}`,
266
+ );
267
+ const artifactId = String(input.artifactId || "");
268
+ const client = String(input.client || "");
269
+ const action = String(input.action || "");
270
+ if (
271
+ !ACCESS_ID_RE.test(accessId) ||
272
+ !ARTIFACT_ID_RE.test(artifactId) ||
273
+ !ARTIFACT_ACCESS_CLIENTS.includes(client) ||
274
+ !ARTIFACT_ACCESS_ACTIONS.includes(action)
275
+ ) {
276
+ throw new TypeError("artifact content access request is invalid");
277
+ }
278
+ return Object.freeze({ accessId, artifactId, client, action });
279
+ }
280
+
281
+ function readCurrentArtifact(store, entries, artifactId, runtimeFs = fs) {
282
+ const matches = entries.filter((entry) => entry.id === artifactId);
283
+ if (matches.length !== 1) {
284
+ throw new Error(
285
+ matches.length === 0 ? "artifact not found" : "artifact id is ambiguous",
286
+ );
287
+ }
288
+ const entry = matches[0];
289
+ if (
290
+ !entry.file ||
291
+ entry.file !== path.basename(entry.file) ||
292
+ entry.file.includes("..")
293
+ ) {
294
+ throw new Error("artifact index row has an unsafe stored filename");
295
+ }
296
+ const storedPath = store.storedPath(entry);
297
+ const filesRoot = path.resolve(store.dir, "files");
298
+ if (
299
+ path.resolve(storedPath) !== path.join(filesRoot, entry.file) ||
300
+ !path.resolve(storedPath).startsWith(`${filesRoot}${path.sep}`)
301
+ ) {
302
+ throw new Error("artifact stored path escapes the managed files directory");
303
+ }
304
+ const storedStat = runtimeFs.lstatSync(storedPath, { bigint: true });
305
+ if (
306
+ storedStat.isSymbolicLink() ||
307
+ !storedStat.isFile() ||
308
+ Number(storedStat.nlink) !== 1 ||
309
+ Number(storedStat.size) !== Number(entry.size)
310
+ ) {
311
+ throw new Error("artifact stored byte identity is invalid");
312
+ }
313
+ const integrity = store.verifyIntegrity(entry);
314
+ if (
315
+ integrity.ok !== true ||
316
+ integrity.actualSha256 !== entry.sha256 ||
317
+ !/^[a-f0-9]{64}$/u.test(String(entry.sha256 || "")) ||
318
+ (entry.recordDigest != null &&
319
+ !SHA256_RE.test(String(entry.recordDigest))) ||
320
+ (entry.sessionId != null && !ACCESS_ID_RE.test(String(entry.sessionId)))
321
+ ) {
322
+ throw new Error("artifact current byte readback failed");
323
+ }
324
+ return Object.freeze({ entry, storedPath, integrity });
325
+ }
326
+
327
+ function appendEvent(filePath, event, expectedSize, runtimeFs) {
328
+ let descriptor = null;
329
+ try {
330
+ descriptor = runtimeFs.openSync(
331
+ filePath,
332
+ runtimeFs.constants.O_WRONLY |
333
+ runtimeFs.constants.O_APPEND |
334
+ runtimeFs.constants.O_CREAT |
335
+ Number(runtimeFs.constants.O_NOFOLLOW || 0),
336
+ 0o600,
337
+ );
338
+ const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
339
+ if (
340
+ !opened.isFile() ||
341
+ Number(opened.nlink) !== 1 ||
342
+ Number(opened.size) !== expectedSize
343
+ ) {
344
+ throw new Error("artifact access ledger changed before append");
345
+ }
346
+ runtimeFs.writeFileSync(descriptor, `${JSON.stringify(event)}\n`, "utf8");
347
+ runtimeFs.fsyncSync(descriptor);
348
+ } finally {
349
+ if (descriptor !== null) runtimeFs.closeSync(descriptor);
350
+ }
351
+ }
352
+
353
+ export function authorizeArtifactContentAccess(
354
+ store,
355
+ input = {},
356
+ options = {},
357
+ ) {
358
+ if (!store?.dir || typeof store.verifyIntegrity !== "function") {
359
+ throw new TypeError("artifact content access requires an ArtifactStore");
360
+ }
361
+ if (typeof store.withIndexSnapshot !== "function") {
362
+ throw new TypeError(
363
+ "artifact content access requires a locked ArtifactStore index snapshot",
364
+ );
365
+ }
366
+ const request = normalizeAccessRequest(input);
367
+ const runtimeFs = options.fs || fs;
368
+ const filePath = ledgerPath(store);
369
+ runtimeFs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
370
+ return store.withIndexSnapshot((entries) =>
371
+ (options.withFileLock || withFileLock)(
372
+ filePath,
373
+ () => {
374
+ const ledgerBytes = readLedgerBytes(
375
+ filePath,
376
+ runtimeFs,
377
+ options.runtime,
378
+ );
379
+ const events = parseLedger(ledgerBytes);
380
+ const prior = events.find(
381
+ (event) => event.accessId === request.accessId,
382
+ );
383
+ if (prior) {
384
+ if (
385
+ prior.artifactId !== request.artifactId ||
386
+ prior.client !== request.client ||
387
+ prior.action !== request.action
388
+ ) {
389
+ throw new Error(
390
+ "artifact access id is already bound to other inputs",
391
+ );
392
+ }
393
+ const current = readCurrentArtifact(
394
+ store,
395
+ entries,
396
+ request.artifactId,
397
+ runtimeFs,
398
+ );
399
+ if (
400
+ prior.artifactSha256 !== `sha256:${current.entry.sha256}` ||
401
+ prior.recordDigest !== (current.entry.recordDigest || null) ||
402
+ prior.artifactSessionId !== (current.entry.sessionId || null)
403
+ ) {
404
+ throw new Error("artifact changed after access authorization");
405
+ }
406
+ return Object.freeze({
407
+ access: prior,
408
+ recorded: false,
409
+ storedPath: current.storedPath,
410
+ integrity: Object.freeze({ ...current.integrity }),
411
+ });
412
+ }
413
+ const current = readCurrentArtifact(
414
+ store,
415
+ entries,
416
+ request.artifactId,
417
+ runtimeFs,
418
+ );
419
+ const previous = events.at(-1) || null;
420
+ const material = {
421
+ schema: ARTIFACT_ACCESS_EVENT_SCHEMA,
422
+ sequence: (previous?.sequence || 0) + 1,
423
+ previousEventDigest: previous?.eventDigest || null,
424
+ accessId: request.accessId,
425
+ artifactId: request.artifactId,
426
+ artifactSha256: `sha256:${current.entry.sha256}`,
427
+ recordDigest: current.entry.recordDigest || null,
428
+ artifactSessionId: current.entry.sessionId || null,
429
+ client: request.client,
430
+ action: request.action,
431
+ authorizedAt: new Date(
432
+ typeof options.now === "function" ? options.now() : Date.now(),
433
+ ).toISOString(),
434
+ authorization: "current-artifact-index-and-byte-readback",
435
+ };
436
+ const candidateEvent = {
437
+ ...material,
438
+ eventDigest: digest(
439
+ "chainlesschain.artifact.content-access-event.v1\0",
440
+ material,
441
+ ),
442
+ };
443
+ const event = normalizeArtifactAccessEvent(candidateEvent, previous);
444
+ appendEvent(filePath, event, ledgerBytes.length, runtimeFs);
445
+ return Object.freeze({
446
+ access: event,
447
+ recorded: true,
448
+ storedPath: current.storedPath,
449
+ integrity: Object.freeze({ ...current.integrity }),
450
+ });
451
+ },
452
+ {
453
+ failIfUnavailable: true,
454
+ timeoutMs: 30_000,
455
+ retryMs: 1,
456
+ maxRetryMs: 8,
457
+ retryJitterMs: 4,
458
+ },
459
+ ),
460
+ );
461
+ }