chainlesschain 0.166.102 → 0.166.104

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 (213) hide show
  1. package/package.json +1 -1
  2. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  3. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  4. package/src/assets/web-panel/assets/{AIOps-CyHIMEd8.js → AIOps-Bah3HREy.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-vuNxDpHm.js → ActionButton-DY2aeZMc.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-D0quEM0q.js → Analytics-BFly8Xaa.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-xRSVpbCs.js → AppLayout-dp0Zz-JM.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-ktfVAZBC.js → Artifacts-DcIg09ai.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-UJW6maH4.js → Audit-DkN-lsjZ.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-BYrfdCoT.js → BackgroundAgents-BjzgusLx.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-IBgsJ7BK.js → Backup-CU8S6qu9.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-BmP8_zxy.js → BaseInput-B1pC2gjL.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-urkw8_BF.js → Chat-B6xFp6gD.js} +6 -6
  14. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Cw44zZbh.js → ChatBubbleRenderer-DxNCLZ_X.js} +1 -1
  15. package/src/assets/web-panel/assets/{Checkbox-CYboo-__.js → Checkbox-QHw7Vc4U.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-DaqR_Ay8.js → Codegen-gdecsfjg.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-04BSIj_z.js → Col-B0_JmAmc.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-DxFk7Trv.js → Community-DRCcP5xR.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-mjgWlMCB.js → Compact-DZtoGzhX.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-CC5ee7QV.js → Compliance-BfNTvEnT.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-BK4BhHZd.js → Cowork-CDXTLm84.js} +3 -3
  22. package/src/assets/web-panel/assets/{Cron-CP6jAxq0.js → Cron-Big35CbN.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-DJPaViZS.js → Crosschain-C8ArrAVr.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-BTLxkuOj.js → DID-D_77BI2O.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Djkiqh3d.js → Dashboard-D2SxTzEp.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-t_eUhqom.js → Dropdown-mR1-b9ud.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-7w6USx_J.js → EmailListRenderer-DPl7TE1c.js} +1 -1
  28. package/src/assets/web-panel/assets/{EvolutionSettings-mU5IHEk5.js → EvolutionSettings-BxRYLRSH.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BCh-VBBC.js → FamilyGuardDashboard-rnGtXJ5Z.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-Be_VpjI2.js → Federation-BpxenYK8.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-D_lzQ7PI.js → FormItemContext-Cz1kDf6l.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-LYu3KapV.js → GenericCardRenderer-CGV3KN6U.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-BBeF-PP4.js → Git-CIRP4UTx.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-CoF8Kzg7.js → Governance-DX4r8Vmt.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-C4DLMnXS.js → Inference-DQKk02Hl.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-B2mlNewk.js → KnowledgeGraph-DdD6J-By.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-DMZ_44Ya.js → Logs-DA2w-1_m.js} +2 -2
  38. package/src/assets/web-panel/assets/{MarkdownRenderer-Ck7Iv_9e.js → MarkdownRenderer-C9aSGETh.js} +1 -1
  39. package/src/assets/web-panel/assets/{Marketplace-BqDzTew_.js → Marketplace-BsJ6_6KW.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-BbwSbDN9.js → McpTools-_6k30z6u.js} +2 -2
  41. package/src/assets/web-panel/assets/{Memory-DAaTWgrm.js → Memory-C0_dYY0A.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-BjbTGxoR.js → MobileBridge-CCTnVqiC.js} +1 -1
  43. package/src/assets/web-panel/assets/{MobileProjects-DMODOBr-.js → MobileProjects-RZdxDSwC.js} +1 -1
  44. package/src/assets/web-panel/assets/{Mtc-DT9N2rCs.js → Mtc-D4Ys8lvX.js} +2 -2
  45. package/src/assets/web-panel/assets/{MtcAudit-DagMfUVv.js → MtcAudit-BKTC--a2.js} +2 -2
  46. package/src/assets/web-panel/assets/{Multisig-C1BKP6nM.js → Multisig-CGR1qOe1.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-B5LFh72i.js → NLProgramming-hKKD4qWb.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-Q77V3ltW.js → Notes-pCn7Ec40.js} +3 -3
  49. package/src/assets/web-panel/assets/{NotificationSettings-HxdpNIBs.js → NotificationSettings-B9eEJXEs.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-Dc0DpvBH.js → OrderTableRenderer-CmJuhaT2.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-QL3NZ7uW.js → Organization-CcQbrnwW.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-B_gqOKho.js → Overflow-HmD4Mn72.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-BBJubU8Z.js → P2P-B7LIgx5H.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-CFnGd-2E.js → PdhVaultBrowser-CXiHnBfX.js} +3 -3
  55. package/src/assets/web-panel/assets/{Permissions-BKkznIUM.js → Permissions-CQRI3y_l.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub-DYK7ZAcd.js → PersonalDataHub-1EeHmsy5.js} +4 -4
  57. package/src/assets/web-panel/assets/{Pipeline-CoeKpjoN.js → Pipeline-BFyoIdn8.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-Diw4G-92.js → Privacy-BxWthXMF.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-CHBOFkma.js → ProjectInit-DfigBilu.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-BJIsgdOf.js → ProjectSettings-CFmIcVc-.js} +2 -2
  61. package/src/assets/web-panel/assets/Projects-CR7H59v3.js +1 -0
  62. package/src/assets/web-panel/assets/{Providers-o8QCimor.js → Providers-2ulom-76.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-z2W-zvhW.js → QrScannerModal-BwhO07b_.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-_yubhsna.js → QuickAsk-_Wg34OwF.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-CucgJwih.js → Recommend-D_jq6Svg.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-EzfIIRg1.js → RemoteSession-BlzvEfsa.js} +2 -2
  67. package/src/assets/web-panel/assets/{Reputation-_shttE67.js → Reputation-9CTXHfL4.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-DQA_S8-9.js → Row-DlCYCN-t.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-BOqWawXT.js → RssFeed-BvlInFCH.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-Dx1v2xOq.js → Search-CfaUzKuL.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-Bt4HHsT4.js → Security-bAWgLyve.js} +4 -4
  72. package/src/assets/web-panel/assets/{Services-sruHiQ-r.js → Services-C4LOGACW.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-CiBxvmOJ.js → Skeleton-Dd0Uwh3_.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-De9qKODU.js → Skills-ZYF1kYCU.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-B_6yn8Wp.js → Sla-DpsmIaEo.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-C2CafExS.js → SpeechSettings-BBXoJ4yc.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-Dhh3Qmb0.js → SyncSettings-SOnROi2Y.js} +2 -2
  78. package/src/assets/web-panel/assets/{Tasks-Mvo5mTWm.js → Tasks-HFA_ZlDk.js} +1 -1
  79. package/src/assets/web-panel/assets/{Templates-D7WVTQQy.js → Templates-fsK-RCEc.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-7ur9Yfxd.js → Tenant-CuqYxamb.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-BgzrOA6k.js → Terminal-GKlI2R5F.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-Dco20pT1.js → TimelineRenderer-DMDMHLeY.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-hN6qBPag.js → Tokens-BoV60rh3.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-C4f_twAM.js → Trigger-CE344-Ws.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-Wf1SMypp.js → Trust-DENwgkVc.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-DUXSyg3N.js → UkeySign-FrcM7yfm.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-BvqsJaL0.js → VideoEditing-DAY9WaC2.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-CgAbcjKl.js → Wallet-BuUR5i82.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-DaLT0W3s.js → WebAuthn-nfSQuq85.js} +2 -2
  90. package/src/assets/web-panel/assets/{WorkflowEditor-CQqAXoaS.js → WorkflowEditor-DHhPOp9l.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-BhlohFTB.js → chat-C4nsLzYy.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-D7HqXIL8.js → colors-BwXH0Wjn.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-Bqk4FCLY.js → compact-item-uApFG30S.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-CEmWrWf0.js → createContext-BpyYs4fe.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-Bk6HGn3N.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-CDY0pFi2.js → hasIn-MXCLi00p.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-e9oCy7_4.js → index-1LxX2IwJ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DEzL7s7P.js → index-61CBBrHE.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-D0nfNmbq.js → index-B0P1Z_oa.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BiLu-ImR.js → index-B7jojVc7.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-CqK0J4Sj.js → index-BEGMfaOo.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-aBHlgMrq.js → index-BOhjapd5.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DPBoWNwq.js → index-BaBwXpAp.js} +1 -1
  104. package/src/assets/web-panel/assets/index-Bb1LPYdi.js +1 -0
  105. package/src/assets/web-panel/assets/{index-BxXFRLTZ.js → index-Btv--Nnt.js} +1 -1
  106. package/src/assets/web-panel/assets/index-C-FsTuFG.js +1 -0
  107. package/src/assets/web-panel/assets/{index-BxYO6nde.js → index-CF0bUOP4.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DGKRAO61.js → index-CNjUPfJP.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CkjgSHVz.js → index-CRmNdEk_.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CrsM-oiD.js → index-C_oOpXhP.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BNrvBWDS.js → index-ChUTKrOq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CYcjOnDX.js → index-CiIpIYhs.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-C02yDu4f.js → index-CwVvk2i7.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DmKdCWq1.js → index-D3_tj06B.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-CkqBoCiu.js → index-DE4W4vLO.js} +3 -3
  116. package/src/assets/web-panel/assets/{index-B8L3_-sG.js → index-DLxKx5_J.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Dwob5XoD.js → index-DTXi_gQh.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DuMt3ewl.js → index-Daco83me.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DpI1LqJX.js → index-DffWVpZv.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-lBAbMOzg.js → index-Dkncxel3.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DRcDWMwP.js → index-Dn12gJJU.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CVrwozwH.js → index-Dt-nq2eF.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-C3opQg47.js → index-Du01X9N9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-o_lc3eww.js → index-DucLR6YX.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cur88hoN.js → index-Gz2rGWef.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-XErJQ1yF.js → index-IvgszJ3C.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-BuKGuH_y.js → index-S80LLjdx.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-Chxt0t-A.js → index-WKbnwbKa.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-1HBam9t1.js → index-XHBXtvqp.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BXRooi-O.js → index-nn22NSDK.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-BHGOdvOQ.js → index-pCIAE2yS.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-Cf1ha8l0.js → index-pRpMLhMt.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-6f6n0KFR.js → index-pgHGBAoF.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-BPmRkxeX.js → index-ugIUt5TL.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-C9mvnSii.js → index-xLGf7f9K.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-CIeEUxIN.js → initDefaultProps-C58CkKEo.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-IPIj1ZtP.js → motion-BLWPO2NV.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-BfSeXZ-f.js → move-BgrFMwYp.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-DX03-Xp4.js → mtc-parser-Cq2AuGzl.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-z9EannfX.js → omit-Bb-9L7if.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-DXKmRkqZ.js → pickAttrs-vLmOxYr1.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-k2HV7cIU.js → placementArrow-BNySU_dJ.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-bX7CsyWy.js → responsiveObserve-xnbwagnm.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-NskjQLbJ.js → slide-BccbY5Cn.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-B3IoG-Er.js → statusUtils-DOp9d2-w.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-DHv_UilX.js → styleChecker-Bu1w6ho9.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-DEqGB5Ng.js → useFlexGapSupport-ZsUOrVdL.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-Bl9CbrqZ.js → useFs-C0zpzKtO.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-O-Ukv93M.js → usePersonalDataHub-DO1OdgXK.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-hGCHTaZt.js → vnode-jTIRyhR5.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-BWhIVeKQ.js → zoom-DKvS7-Qs.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/commands/agent.js +56 -20
  154. package/src/commands/ask.js +6 -4
  155. package/src/commands/batch.js +17 -12
  156. package/src/commands/eval.js +24 -8
  157. package/src/commands/team.js +28 -19
  158. package/src/data/changelog.json +26 -0
  159. package/src/harness/mcp-client.js +187 -26
  160. package/src/harness/prompt-compressor.js +2 -0
  161. package/src/lib/advisor-runtime.js +4 -2
  162. package/src/lib/agent-ipc-bus.js +2 -0
  163. package/src/lib/background-agent-keeper-client.js +3 -0
  164. package/src/lib/background-session-transport.js +21 -5
  165. package/src/lib/browser-automation.js +4 -0
  166. package/src/lib/cc-android-bridge.js +4 -0
  167. package/src/lib/chat-core.js +11 -9
  168. package/src/lib/cloud/cloud-client.js +10 -1
  169. package/src/lib/cowork-adapter.js +4 -3
  170. package/src/lib/decision-layer/providers.js +16 -11
  171. package/src/lib/decision-layer/runtime.js +180 -10
  172. package/src/lib/direct-model-usage.js +34 -0
  173. package/src/lib/evolution/agent-evolution-ingress.js +63 -7
  174. package/src/lib/evolution/evolution-evidence-artifact-adapter.js +10 -0
  175. package/src/lib/evolution/evolution-evidence-projector.js +46 -7
  176. package/src/lib/evolution/governed-hub-llm.js +31 -16
  177. package/src/lib/evolution/governed-hub-resolver.js +4 -0
  178. package/src/lib/evolution/governed-skill-synthesis-process-grader.js +4 -0
  179. package/src/lib/evolution/skill-decision-projection.js +211 -0
  180. package/src/lib/evolution/skill-retrieval-revocation-authority.js +17 -2
  181. package/src/lib/evomap-client.js +14 -2
  182. package/src/lib/formal-budget-authority.js +191 -59
  183. package/src/lib/formal-budget-contract.js +7 -13
  184. package/src/lib/formal-budget-errors.js +31 -0
  185. package/src/lib/formal-budget-lineage-registry.js +612 -0
  186. package/src/lib/formal-budget-provider-scope.js +143 -0
  187. package/src/lib/formal-budget-transport.js +235 -100
  188. package/src/lib/gateway-client-resilience.js +5 -0
  189. package/src/lib/hub-llm-client.js +25 -16
  190. package/src/lib/llm-connection-probe.js +17 -10
  191. package/src/lib/llm-providers.js +4 -1
  192. package/src/lib/model-failure-policy.js +2 -0
  193. package/src/lib/plugin-runtime/marketplace-network.js +9 -5
  194. package/src/lib/process-execution-broker/credential-agent.js +7 -2
  195. package/src/lib/process-execution-broker/credential-transport.js +4 -0
  196. package/src/lib/process-execution-broker/index.js +8 -0
  197. package/src/lib/provider-stream.js +28 -13
  198. package/src/lib/sandbox-egress-worker.js +3 -0
  199. package/src/lib/skill-loader.js +16 -0
  200. package/src/lib/stream-retry.js +2 -0
  201. package/src/lib/sub-agent-context.js +6 -3
  202. package/src/lib/web-fetch.js +2 -0
  203. package/src/lib/web-search.js +5 -0
  204. package/src/repl/agent-repl.js +86 -7
  205. package/src/runtime/agent-core.js +141 -22
  206. package/src/runtime/headless-runner.js +212 -22
  207. package/src/runtime/headless-stream.js +189 -25
  208. package/src/runtime/mcp-config.js +26 -17
  209. package/src/runtime/mcp-tool-search.js +44 -14
  210. package/src/assets/web-panel/assets/Projects-GJjItXIk.js +0 -1
  211. package/src/assets/web-panel/assets/devWarning-YPX_TU7w.js +0 -1
  212. package/src/assets/web-panel/assets/index-D53szVZQ.js +0 -1
  213. package/src/assets/web-panel/assets/index-Ub2nJbV2.js +0 -1
@@ -1,3 +1,4 @@
1
+ import { providerFetch } from "./formal-budget-provider-scope.js";
1
2
  /**
2
3
  * Chat Core — transport-independent streaming chat logic
3
4
  *
@@ -21,6 +22,7 @@ import {
21
22
  waitForStreamRetry,
22
23
  } from "./stream-retry.js";
23
24
  import { throwIfAborted } from "./abort-utils.js";
25
+ import { isFormalBudgetError } from "./formal-budget-errors.js";
24
26
 
25
27
  // A streaming chat call must not hang forever if the API accepts the connection
26
28
  // but then goes silent (TCP up, no bytes). Abort the request if no data arrives
@@ -132,7 +134,7 @@ export async function streamOllama(
132
134
  guard.bump();
133
135
  let response;
134
136
  try {
135
- response = await fetch(`${baseUrl}/api/chat`, {
137
+ response = await providerFetch(`${baseUrl}/api/chat`, {
136
138
  method: "POST",
137
139
  headers: { "Content-Type": "application/json" },
138
140
  body: JSON.stringify({
@@ -146,7 +148,7 @@ export async function streamOllama(
146
148
  });
147
149
  } catch (e) {
148
150
  guard.stop();
149
- throw guard.stalled()
151
+ throw guard.stalled() && !isFormalBudgetError(e)
150
152
  ? new Error(
151
153
  `Ollama request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
152
154
  )
@@ -213,7 +215,7 @@ export async function streamOllama(
213
215
  }
214
216
  } catch (e) {
215
217
  throw attachStreamRetryMetadata(
216
- guard.stalled()
218
+ guard.stalled() && !isFormalBudgetError(e)
217
219
  ? new Error(
218
220
  `Ollama stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
219
221
  )
@@ -247,7 +249,7 @@ export async function streamOpenAI(
247
249
  guard.bump();
248
250
  let response;
249
251
  try {
250
- response = await fetch(`${baseUrl}/chat/completions`, {
252
+ response = await providerFetch(`${baseUrl}/chat/completions`, {
251
253
  method: "POST",
252
254
  headers: {
253
255
  "Content-Type": "application/json",
@@ -267,7 +269,7 @@ export async function streamOpenAI(
267
269
  });
268
270
  } catch (e) {
269
271
  guard.stop();
270
- throw guard.stalled()
272
+ throw guard.stalled() && !isFormalBudgetError(e)
271
273
  ? new Error(
272
274
  `API request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
273
275
  )
@@ -344,7 +346,7 @@ export async function streamOpenAI(
344
346
  }
345
347
  } catch (e) {
346
348
  throw attachStreamRetryMetadata(
347
- guard.stalled()
349
+ guard.stalled() && !isFormalBudgetError(e)
348
350
  ? new Error(
349
351
  `API stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
350
352
  )
@@ -392,7 +394,7 @@ export async function streamAnthropic(
392
394
  guard.bump();
393
395
  let response;
394
396
  try {
395
- response = await fetch(`${baseUrl}/messages`, {
397
+ response = await providerFetch(`${baseUrl}/messages`, {
396
398
  method: "POST",
397
399
  headers: {
398
400
  "Content-Type": "application/json",
@@ -412,7 +414,7 @@ export async function streamAnthropic(
412
414
  });
413
415
  } catch (e) {
414
416
  guard.stop();
415
- throw guard.stalled()
417
+ throw guard.stalled() && !isFormalBudgetError(e)
416
418
  ? new Error(
417
419
  `Anthropic request stalled (no response in ${STREAM_STALL_MS / 1000}s)`,
418
420
  )
@@ -486,7 +488,7 @@ export async function streamAnthropic(
486
488
  }
487
489
  } catch (e) {
488
490
  throw attachStreamRetryMetadata(
489
- guard.stalled()
491
+ guard.stalled() && !isFormalBudgetError(e)
490
492
  ? new Error(
491
493
  `Anthropic stream stalled (no data in ${STREAM_STALL_MS / 1000}s)`,
492
494
  )
@@ -28,6 +28,7 @@ import {
28
28
  import { join } from "node:path";
29
29
  import { getHomeDir } from "../paths.js";
30
30
  import { ResilientGatewayClient } from "../gateway-client-resilience.js";
31
+ import { requireUnscopedDescendantControl } from "../formal-budget-provider-scope.js";
31
32
 
32
33
  export const _deps = { fetch: (...a) => globalThis.fetch(...a) };
33
34
 
@@ -78,6 +79,7 @@ export class CloudClient {
78
79
  * @param {object} opts { baseUrl, token, deps? }
79
80
  */
80
81
  constructor(opts = {}) {
82
+ requireUnscopedDescendantControl();
81
83
  this.baseUrl = String(opts.baseUrl || "").replace(/\/+$/, "");
82
84
  if (!this.baseUrl) {
83
85
  throw new Error(
@@ -109,6 +111,7 @@ export class CloudClient {
109
111
  }
110
112
 
111
113
  async _json(method, path, body) {
114
+ requireUnscopedDescendantControl();
112
115
  const res = await this.deps.fetch(`${this.baseUrl}${path}`, {
113
116
  method,
114
117
  headers: this._headers(),
@@ -121,7 +124,9 @@ export class CloudClient {
121
124
  }
122
125
 
123
126
  /** Submit a job. `bundle` is a base64 git bundle of the branch. */
124
- async submit({ task, bundle, branch, baseSha }) {
127
+ async submit(payload) {
128
+ requireUnscopedDescendantControl();
129
+ const { task, bundle, branch, baseSha } = payload;
125
130
  const data = await this._json("POST", "/v1/jobs", {
126
131
  task,
127
132
  bundle,
@@ -133,10 +138,12 @@ export class CloudClient {
133
138
  }
134
139
 
135
140
  async status(jobId) {
141
+ requireUnscopedDescendantControl();
136
142
  return this._json("GET", `/v1/jobs/${encodeURIComponent(jobId)}`);
137
143
  }
138
144
 
139
145
  async result(jobId) {
146
+ requireUnscopedDescendantControl();
140
147
  return this._json("GET", `/v1/jobs/${encodeURIComponent(jobId)}/result`);
141
148
  }
142
149
 
@@ -146,6 +153,7 @@ export class CloudClient {
146
153
  * runners remain compatible with status()/result().
147
154
  */
148
155
  async events(jobId, options = {}) {
156
+ requireUnscopedDescendantControl();
149
157
  if (!/^[\w.-]+$/.test(String(jobId || ""))) {
150
158
  throw new Error(`invalid job id: ${jobId}`);
151
159
  }
@@ -175,6 +183,7 @@ export const TERMINAL_CLOUD_STATUS = new Set(["done", "failed"]);
175
183
  * `sleep`/`now` injected for tests.
176
184
  */
177
185
  export async function pollUntilDone(client, jobId, opts = {}) {
186
+ requireUnscopedDescendantControl();
178
187
  const intervalMs = opts.intervalMs ?? 3000;
179
188
  const timeoutMs = opts.timeoutMs ?? 30 * 60_000;
180
189
  const sleep = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
@@ -7,6 +7,7 @@
7
7
  * - Module initialization helper
8
8
  */
9
9
 
10
+ import { providerFetch } from "./formal-budget-provider-scope.js";
10
11
  import { LLMProviderRegistry, BUILT_IN_PROVIDERS } from "./llm-providers.js";
11
12
  import { loadConfig } from "./config-manager.js";
12
13
  import { applyConfigLlmDefaults } from "./llm-config-defaults.js";
@@ -174,7 +175,7 @@ export function createChatFn(options = {}) {
174
175
  currentModel,
175
176
  async ({ signal = opts.signal || resolved.signal } = {}) => {
176
177
  const modelMessages = await prepareMessages();
177
- const res = await fetch(`${baseUrl}/api/chat`, {
178
+ const res = await providerFetch(`${baseUrl}/api/chat`, {
178
179
  method: "POST",
179
180
  signal: requestSignal(signal),
180
181
  headers: { "Content-Type": "application/json" },
@@ -214,7 +215,7 @@ export function createChatFn(options = {}) {
214
215
  ? { system: systemMsgs.map((m) => m.content).join("\n") }
215
216
  : {}),
216
217
  };
217
- const res = await fetch(`${baseUrl}/messages`, {
218
+ const res = await providerFetch(`${baseUrl}/messages`, {
218
219
  method: "POST",
219
220
  signal: requestSignal(signal),
220
221
  headers: {
@@ -245,7 +246,7 @@ export function createChatFn(options = {}) {
245
246
  currentModel,
246
247
  async ({ signal = opts.signal || resolved.signal } = {}) => {
247
248
  const modelMessages = await prepareMessages();
248
- const res = await fetch(`${baseUrl}/chat/completions`, {
249
+ const res = await providerFetch(`${baseUrl}/chat/completions`, {
249
250
  method: "POST",
250
251
  signal: requestSignal(signal),
251
252
  headers: {
@@ -1,4 +1,5 @@
1
1
  import { openDecisionProviderAuthority } from "./provider-authority.js";
2
+ import { providerFetch } from "../formal-budget-provider-scope.js";
2
3
 
3
4
  const PROVIDERS = Object.freeze({
4
5
  typesafe: {
@@ -195,18 +196,22 @@ export function createDecisionProvider({
195
196
  if (Buffer.byteLength(body, "utf8") > MAX_DECISION_REQUEST_BYTES) {
196
197
  throw bodyTooLarge("request", MAX_DECISION_REQUEST_BYTES);
197
198
  }
198
- const response = await fetchImpl(endpoint, {
199
- method: "POST",
200
- headers: {
201
- ...(secret === undefined
202
- ? {}
203
- : { authorization: `Bearer ${secret}` }),
204
- "content-type": "application/json",
199
+ const response = await providerFetch(
200
+ endpoint,
201
+ {
202
+ method: "POST",
203
+ headers: {
204
+ ...(secret === undefined
205
+ ? {}
206
+ : { authorization: `Bearer ${secret}` }),
207
+ "content-type": "application/json",
208
+ },
209
+ body,
210
+ signal,
211
+ redirect: "error",
205
212
  },
206
- body,
207
- signal,
208
- redirect: "error",
209
- });
213
+ fetchImpl,
214
+ );
210
215
  if (!response?.ok) {
211
216
  throw providerError(response?.status ?? 0, options.provider);
212
217
  }
@@ -12,12 +12,17 @@ import {
12
12
  SKILL_DECISION_OBSERVATION_SCHEMA,
13
13
  } from "./contracts.js";
14
14
  import { captureDecisionProviderAuthority } from "./provider-authority.js";
15
+ import {
16
+ AGENT_EVOLUTION_INGRESS_FAILED_CODE,
17
+ captureAgentEvolutionIngress,
18
+ } from "../evolution/agent-evolution-ingress.js";
15
19
 
16
20
  export const SKILL_DECISION_RUNTIME_SCHEMA =
17
21
  "chainlesschain.skill-decision-runtime/v1";
18
22
 
19
23
  const RUNTIMES = new WeakSet();
20
24
  const DEFAULT_TIMEOUT_MS = 800;
25
+ const DEFAULT_BOUNDARY_TIMEOUT_MS = 30_000;
21
26
 
22
27
  function requiredText(value, label, maximum = 256) {
23
28
  if (
@@ -36,6 +41,53 @@ function timeoutSignal(signal, timeoutMs) {
36
41
  return signal ? AbortSignal.any([signal, deadline]) : deadline;
37
42
  }
38
43
 
44
+ function settleBeforeAbort(value, signal) {
45
+ const pending = Promise.resolve(value);
46
+ if (!signal) return pending;
47
+ return new Promise((resolve, reject) => {
48
+ let settled = false;
49
+ const finish = (settle, result) => {
50
+ if (settled) return;
51
+ settled = true;
52
+ signal.removeEventListener("abort", onAbort);
53
+ settle(result);
54
+ };
55
+ const onAbort = () => finish(reject, signal.reason);
56
+ pending.then(
57
+ (result) => finish(resolve, result),
58
+ (error) => finish(reject, error),
59
+ );
60
+ if (signal.aborted) {
61
+ onAbort();
62
+ return;
63
+ }
64
+ signal.addEventListener("abort", onAbort, { once: true });
65
+ });
66
+ }
67
+
68
+ async function waitForGovernedPhase(value, signal, timeoutMs, onTimeout) {
69
+ const phaseSignal = timeoutSignal(signal, timeoutMs);
70
+ try {
71
+ return await settleBeforeAbort(value, phaseSignal);
72
+ } catch (error) {
73
+ if (signal?.aborted) throw signal.reason;
74
+ if (
75
+ phaseSignal.aborted &&
76
+ phaseSignal.reason === error &&
77
+ error?.name === "TimeoutError"
78
+ ) {
79
+ throw onTimeout(error);
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+
85
+ function admissionTimeoutError(cause) {
86
+ const error = new Error("Skill decision admission timed out", { cause });
87
+ error.code = AGENT_EVOLUTION_INGRESS_FAILED_CODE;
88
+ return error;
89
+ }
90
+
39
91
  function projectUnavailable(error) {
40
92
  if (error?.name === "TimeoutError") return "provider-timeout";
41
93
  if (error?.code === "CC_DECISION_USAGE_UNKNOWN_BLOCKED") {
@@ -66,7 +118,7 @@ function asPersistenceError(error) {
66
118
  return markRuntimeLedgerPersistenceError(error);
67
119
  } catch {
68
120
  return markRuntimeLedgerPersistenceError(
69
- new Error("decision observation persistence failed", { cause: error }),
121
+ new Error("decision persistence failed", { cause: error }),
70
122
  );
71
123
  }
72
124
  }
@@ -76,12 +128,14 @@ export function createSkillDecisionRuntime({
76
128
  tenantId = null,
77
129
  sessionId = null,
78
130
  provider = null,
131
+ evolutionIngress = null,
79
132
  persist = null,
80
133
  observe = null,
81
134
  sessionBudget = null,
82
135
  initialUsageUnknown = false,
83
136
  thresholds = {},
84
137
  timeoutMs = DEFAULT_TIMEOUT_MS,
138
+ boundaryTimeoutMs = DEFAULT_BOUNDARY_TIMEOUT_MS,
85
139
  idGenerator = () => randomUUID(),
86
140
  now = () => new Date(),
87
141
  } = {}) {
@@ -95,17 +149,36 @@ export function createSkillDecisionRuntime({
95
149
  "decision timeout must be an integer from 50 to 30000 ms",
96
150
  );
97
151
  }
152
+ if (
153
+ !Number.isSafeInteger(boundaryTimeoutMs) ||
154
+ boundaryTimeoutMs < 50 ||
155
+ boundaryTimeoutMs > 30_000
156
+ ) {
157
+ throw new TypeError(
158
+ "decision boundary timeout must be an integer from 50 to 30000 ms",
159
+ );
160
+ }
98
161
  if (typeof idGenerator !== "function" || typeof now !== "function") {
99
162
  throw new TypeError("decision runtime generators must be functions");
100
163
  }
101
164
  let providerAuthority = null;
102
165
  let boundTenantId = null;
103
166
  let boundSessionId = null;
167
+ let ingress = null;
104
168
  let unknownUsageSeen = initialUsageUnknown === true;
169
+ const unsettledUsageCalls = new Set();
170
+ let usageCallInFlight = false;
171
+ let usagePersistenceFailure = null;
172
+ let observationPersistenceFailure = null;
105
173
  if (normalizedMode !== "off") {
106
174
  providerAuthority = captureDecisionProviderAuthority(provider);
107
175
  boundTenantId = requiredText(tenantId, "tenantId");
108
176
  boundSessionId = requiredText(sessionId, "sessionId");
177
+ if (evolutionIngress !== null) {
178
+ ingress = captureAgentEvolutionIngress(evolutionIngress, {
179
+ tenantId: boundTenantId,
180
+ });
181
+ }
109
182
  if (typeof persist !== "function" || typeof observe !== "function") {
110
183
  throw new TypeError(
111
184
  "enabled decision runtime requires usage and observation persistence",
@@ -118,13 +191,33 @@ export function createSkillDecisionRuntime({
118
191
  try {
119
192
  await observe(observation);
120
193
  } catch (error) {
121
- throw asPersistenceError(error);
194
+ observationPersistenceFailure ??= asPersistenceError(error);
195
+ throw observationPersistenceFailure;
122
196
  }
123
197
  };
124
198
 
125
199
  const persistDecisionUsage = async (type, event) => {
126
- await persist(type, event);
127
- if (type === "model_usage_unknown") unknownUsageSeen = true;
200
+ if (usagePersistenceFailure !== null) throw usagePersistenceFailure;
201
+ try {
202
+ await waitForGovernedPhase(
203
+ persist(type, event),
204
+ null,
205
+ boundaryTimeoutMs,
206
+ (cause) =>
207
+ new Error("Skill decision usage persistence timed out", { cause }),
208
+ );
209
+ } catch (error) {
210
+ usagePersistenceFailure ??= asPersistenceError(error);
211
+ throw usagePersistenceFailure;
212
+ }
213
+ if (type === "model_usage_started") {
214
+ unsettledUsageCalls.add(event.callId);
215
+ } else if (type === "model_usage_unknown") {
216
+ unknownUsageSeen = true;
217
+ unsettledUsageCalls.delete(event.callId);
218
+ } else if (type === "token_usage") {
219
+ unsettledUsageCalls.delete(event.callId);
220
+ }
128
221
  };
129
222
 
130
223
  const runtime = Object.freeze({
@@ -148,6 +241,10 @@ export function createSkillDecisionRuntime({
148
241
  });
149
242
  }
150
243
  if (signal?.aborted) throw signal.reason;
244
+ if (usagePersistenceFailure !== null) throw usagePersistenceFailure;
245
+ if (observationPersistenceFailure !== null) {
246
+ throw observationPersistenceFailure;
247
+ }
151
248
  const decisionId = `skill-${requiredText(idGenerator(), "decisionId")}`;
152
249
  const request = createSkillDecisionRequest({
153
250
  decisionId,
@@ -167,11 +264,41 @@ export function createSkillDecisionRuntime({
167
264
  }
168
265
  let outcome;
169
266
  let requestSignal = null;
170
- if (unknownUsageSeen) {
267
+ let admissionReceipt = null;
268
+ if (unknownUsageSeen || unsettledUsageCalls.size > 0) {
171
269
  outcome = unavailableOutcome("provider-usage-unknown-blocked");
172
270
  } else {
173
271
  try {
174
- const { result: raw, settlement } = await runMeteredDirectModelCall({
272
+ // Admission is not provider work: a refusal must not create an
273
+ // unknown-charge ledger row or consume the provider's budget turn.
274
+ const admission =
275
+ ingress === null
276
+ ? null
277
+ : await waitForGovernedPhase(
278
+ ingress.prepareSkillDecisionRequest(request),
279
+ signal,
280
+ boundaryTimeoutMs,
281
+ admissionTimeoutError,
282
+ );
283
+ signal?.throwIfAborted();
284
+ admissionReceipt = admission?.receipt ?? null;
285
+ // Keep local content identities out of the provider capability. Only
286
+ // the authenticated projection is available for governed dispatch.
287
+ const providerRequest =
288
+ admission === null
289
+ ? request
290
+ : Object.freeze({ payload: admission.payload });
291
+ if (
292
+ unknownUsageSeen ||
293
+ unsettledUsageCalls.size > 0 ||
294
+ usageCallInFlight
295
+ ) {
296
+ const error = new Error("decision usage recovery is required");
297
+ error.code = "CC_DECISION_USAGE_UNKNOWN_BLOCKED";
298
+ throw error;
299
+ }
300
+ usageCallInFlight = true;
301
+ const meteredCall = runMeteredDirectModelCall({
175
302
  sessionId: boundSessionId,
176
303
  persist: persistDecisionUsage,
177
304
  provider: providerAuthority.provider,
@@ -180,18 +307,36 @@ export function createSkillDecisionRuntime({
180
307
  operationId: `decision:${decisionId}`,
181
308
  sessionBudget,
182
309
  includeSettlement: true,
310
+ dispatchSignal: signal ?? null,
183
311
  call: () => {
184
312
  if (unknownUsageSeen) {
185
313
  const error = new Error("decision usage recovery is required");
186
314
  error.code = "CC_DECISION_USAGE_UNKNOWN_BLOCKED";
187
315
  throw error;
188
316
  }
317
+ // timeoutMs remains the provider-call deadline. Admission uses
318
+ // its own authority lifecycle and checks caller cancellation.
189
319
  requestSignal = timeoutSignal(signal, timeoutMs);
190
- return providerAuthority.decide(request, {
191
- signal: requestSignal,
192
- });
320
+ requestSignal.throwIfAborted();
321
+ return settleBeforeAbort(
322
+ providerAuthority.decide(providerRequest, {
323
+ signal: requestSignal,
324
+ }),
325
+ requestSignal,
326
+ );
193
327
  },
194
328
  });
329
+ let meteredResult;
330
+ try {
331
+ meteredResult = await meteredCall;
332
+ } finally {
333
+ usageCallInFlight = false;
334
+ }
335
+ const { result: raw, settlement } = meteredResult;
336
+ // A provider may resolve successfully after ignoring cancellation or
337
+ // its deadline. Usage is settled above; the late answer is unusable.
338
+ signal?.throwIfAborted();
339
+ requestSignal?.throwIfAborted();
195
340
  if (settlement !== "known") {
196
341
  // The metered call already persisted model_usage_unknown. Without a
197
342
  // budget root it may return the answer; never expose that answer.
@@ -254,6 +399,7 @@ export function createSkillDecisionRuntime({
254
399
  reasonCode: outcome.reasonCode,
255
400
  selectedDigest: outcome.selectedDigest,
256
401
  resultDigest: outcome.resultDigest,
402
+ ...(admissionReceipt === null ? {} : { admissionReceipt }),
257
403
  startedAt: startedAt.toISOString(),
258
404
  finishedAt: finishedAt.toISOString(),
259
405
  latencyMs: Math.max(0, finishedAt.getTime() - startedAt.getTime()),
@@ -265,7 +411,31 @@ export function createSkillDecisionRuntime({
265
411
  observationCore,
266
412
  ),
267
413
  });
268
- await writeObservation(observation);
414
+ try {
415
+ await waitForGovernedPhase(
416
+ writeObservation(observation),
417
+ signal,
418
+ boundaryTimeoutMs,
419
+ (cause) =>
420
+ asPersistenceError(
421
+ new Error("Skill decision observation timed out", { cause }),
422
+ ),
423
+ );
424
+ } catch (error) {
425
+ if (error?.runtimeLedgerPersistence === true) {
426
+ observationPersistenceFailure ??= error;
427
+ } else if (signal?.aborted) {
428
+ // The caller stops waiting before the durable observer acknowledges
429
+ // the write. Its eventual outcome is unknown to this runtime.
430
+ observationPersistenceFailure ??= asPersistenceError(
431
+ new Error("Skill decision observation completion is unknown", {
432
+ cause: error,
433
+ }),
434
+ );
435
+ }
436
+ throw error;
437
+ }
438
+ signal?.throwIfAborted();
269
439
  return Object.freeze({
270
440
  schema: SKILL_DECISION_OBSERVATION_SCHEMA,
271
441
  decisionId,
@@ -54,6 +54,8 @@ function unknownUsageEvent({
54
54
  * Meter one direct provider call that runs outside agentLoop. The started row is
55
55
  * durable before the callback resumes into provider work; every outcome then
56
56
  * settles known or unknown without persisting prompts, responses, or errors.
57
+ * A dispatch signal closes an acknowledged, canceled pre-dispatch intent with
58
+ * locally proven zero usage; cancellation after dispatch remains unknown.
57
59
  * Opt-in includeSettlement returns that persisted status with the raw result.
58
60
  */
59
61
  export async function runMeteredDirectModelCall({
@@ -65,11 +67,20 @@ export async function runMeteredDirectModelCall({
65
67
  operationId = undefined,
66
68
  sessionBudget = null,
67
69
  includeSettlement = false,
70
+ dispatchSignal = null,
68
71
  call,
69
72
  }) {
70
73
  if (typeof call !== "function") {
71
74
  throw new TypeError("metered direct model call requires a callback");
72
75
  }
76
+ if (
77
+ dispatchSignal !== null &&
78
+ (typeof dispatchSignal.aborted !== "boolean" ||
79
+ typeof dispatchSignal.throwIfAborted !== "function")
80
+ ) {
81
+ throw new TypeError("metered direct model dispatch signal is invalid");
82
+ }
83
+ dispatchSignal?.throwIfAborted();
73
84
  const callId = `direct-${randomUUID()}`;
74
85
  if (typeof sessionBudget?.consumeTurn === "function") {
75
86
  const admission = sessionBudget.consumeTurn({ id: callId });
@@ -84,6 +95,7 @@ export async function runMeteredDirectModelCall({
84
95
  if (includeSettlement) {
85
96
  throw new TypeError("direct model settlement requires a session");
86
97
  }
98
+ dispatchSignal?.throwIfAborted();
87
99
  return call();
88
100
  }
89
101
  if (typeof persist !== "function") {
@@ -102,6 +114,28 @@ export async function runMeteredDirectModelCall({
102
114
  { callId, provider, model, source, operationId },
103
115
  `direct model call ${source}`,
104
116
  );
117
+ if (dispatchSignal?.aborted) {
118
+ // A durable intent exists, but no provider callback has run. Close it as
119
+ // a locally proven zero-charge call instead of inventing unknown usage.
120
+ const notDispatched = {
121
+ ...projectRuntimeTokenUsage({
122
+ callId,
123
+ provider,
124
+ model,
125
+ source,
126
+ operationId,
127
+ usage: { input_tokens: 0, output_tokens: 0 },
128
+ }),
129
+ settlementOrigin: "local-no-dispatch",
130
+ };
131
+ await persistUsageEvent(persist, "token_usage", notDispatched);
132
+ recordSessionBudgetUsage(
133
+ sessionBudget,
134
+ { callId, usage: null },
135
+ `direct model call ${source} canceled before dispatch`,
136
+ );
137
+ throw dispatchSignal.reason;
138
+ }
105
139
  let result;
106
140
  let settlement = "unknown";
107
141
  const returnSettled = (status) =>