chainlesschain 0.166.101 → 0.166.103

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 (219) hide show
  1. package/package.json +2 -2
  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-BjBS_29I.js → AIOps-BLrXpdnP.js} +1 -1
  5. package/src/assets/web-panel/assets/{ActionButton-CUnnQUaj.js → ActionButton-C0xy9uFy.js} +1 -1
  6. package/src/assets/web-panel/assets/{Analytics-BqYZxgnY.js → Analytics-C6EephQP.js} +3 -3
  7. package/src/assets/web-panel/assets/{AppLayout-m8B886ka.js → AppLayout-ZCXdOD9y.js} +5 -5
  8. package/src/assets/web-panel/assets/{Artifacts-JEOxU3Ug.js → Artifacts-D4MfZ-pl.js} +1 -1
  9. package/src/assets/web-panel/assets/{Audit-DAXS5c9J.js → Audit-BQ8c3bBu.js} +1 -1
  10. package/src/assets/web-panel/assets/{BackgroundAgents-e3a9XHw3.js → BackgroundAgents-BOA7_ljM.js} +1 -1
  11. package/src/assets/web-panel/assets/{Backup-D3Qmp68h.js → Backup-60uiQjGT.js} +1 -1
  12. package/src/assets/web-panel/assets/{BaseInput-C7CXyiUl.js → BaseInput-BDvEbGs_.js} +1 -1
  13. package/src/assets/web-panel/assets/{Chat-2AHlAGi8.js → Chat-B3IkAuFQ.js} +6 -6
  14. package/src/assets/web-panel/assets/ChatBubbleRenderer-n6BVStHf.js +1 -0
  15. package/src/assets/web-panel/assets/{Checkbox-YfBuQlBH.js → Checkbox-njGbfJWq.js} +1 -1
  16. package/src/assets/web-panel/assets/{Codegen-DCCQDfTL.js → Codegen-CF7DtdxC.js} +1 -1
  17. package/src/assets/web-panel/assets/{Col-03i2WfrV.js → Col-C_3Zix40.js} +1 -1
  18. package/src/assets/web-panel/assets/{Community-CJQIpng6.js → Community-CzU-ZC2d.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compact-CCtcQ2pq.js → Compact-BgeqsPRh.js} +1 -1
  20. package/src/assets/web-panel/assets/{Compliance-Vxl7ZszJ.js → Compliance-6l7cwiqp.js} +1 -1
  21. package/src/assets/web-panel/assets/{Cowork-DN-BwA8I.js → Cowork-D4s905fI.js} +2 -2
  22. package/src/assets/web-panel/assets/{Cron-Dc2Q46X7.js → Cron-CGTYSxRS.js} +2 -2
  23. package/src/assets/web-panel/assets/{Crosschain-CQCblsl-.js → Crosschain-BnSo-Yt3.js} +1 -1
  24. package/src/assets/web-panel/assets/{DID-YYvqUR8C.js → DID-Dm4vk2X1.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dashboard-Cafy3DZT.js → Dashboard-D7H-PZ_N.js} +2 -2
  26. package/src/assets/web-panel/assets/{Dropdown-BOyYpIpv.js → Dropdown-COslkgxn.js} +1 -1
  27. package/src/assets/web-panel/assets/{EmailListRenderer-B7NYjc1j.js → EmailListRenderer-CJWEetmk.js} +1 -1
  28. package/src/assets/web-panel/assets/{EvolutionSettings-C5wfxztf.js → EvolutionSettings-BbzebAWe.js} +1 -1
  29. package/src/assets/web-panel/assets/{FamilyGuardDashboard-aA7KnDGK.js → FamilyGuardDashboard-BsoTOXwk.js} +1 -1
  30. package/src/assets/web-panel/assets/{Federation-DlgXUu1L.js → Federation-Ck5kM_5H.js} +1 -1
  31. package/src/assets/web-panel/assets/{FormItemContext-B5YsrLDg.js → FormItemContext-gShxCqtF.js} +1 -1
  32. package/src/assets/web-panel/assets/{GenericCardRenderer-DaWojPFw.js → GenericCardRenderer-DarN1G-z.js} +1 -1
  33. package/src/assets/web-panel/assets/{Git-dkhh6Fx2.js → Git-CJfpLyP5.js} +2 -2
  34. package/src/assets/web-panel/assets/{Governance-Cz6vmkjc.js → Governance-CVcJmpQz.js} +1 -1
  35. package/src/assets/web-panel/assets/{Inference-DWt0hbh8.js → Inference-ComU_ig8.js} +1 -1
  36. package/src/assets/web-panel/assets/{KnowledgeGraph-BZJ0Z-nL.js → KnowledgeGraph-35hprFJy.js} +1 -1
  37. package/src/assets/web-panel/assets/{Logs-BaBBmL1A.js → Logs-BQDqBTz0.js} +2 -2
  38. package/src/assets/web-panel/assets/MarkdownRenderer-BpdGAVCC.js +1 -0
  39. package/src/assets/web-panel/assets/{Marketplace-ct8sgQ0s.js → Marketplace-B9O9OKnn.js} +1 -1
  40. package/src/assets/web-panel/assets/{McpTools-CXKCRGYQ.js → McpTools-VMKxA4Wc.js} +3 -3
  41. package/src/assets/web-panel/assets/{Memory-O7ZwlS6a.js → Memory-CcBbf3Bc.js} +2 -2
  42. package/src/assets/web-panel/assets/{MobileBridge-By12C-eC.js → MobileBridge-D7myz46N.js} +1 -1
  43. package/src/assets/web-panel/assets/{MobileProjects-Ba-EVdex.js → MobileProjects-Da5KBXyx.js} +1 -1
  44. package/src/assets/web-panel/assets/{Mtc-g-932zV2.js → Mtc-x3NPZfuL.js} +5 -5
  45. package/src/assets/web-panel/assets/{MtcAudit-BW8hB0ly.js → MtcAudit-CwnzptEC.js} +4 -4
  46. package/src/assets/web-panel/assets/{Multisig-zspLaMbT.js → Multisig-CCAxf4Ye.js} +3 -3
  47. package/src/assets/web-panel/assets/{NLProgramming-dJeUPDoq.js → NLProgramming-CptHKz-Q.js} +1 -1
  48. package/src/assets/web-panel/assets/{Notes-C4msiRQL.js → Notes-0uEp4DCJ.js} +3 -3
  49. package/src/assets/web-panel/assets/{NotificationSettings-MOCHM-qg.js → NotificationSettings-DthvrOLd.js} +1 -1
  50. package/src/assets/web-panel/assets/{OrderTableRenderer-CX0T66NM.js → OrderTableRenderer-CxXuPzZg.js} +1 -1
  51. package/src/assets/web-panel/assets/{Organization-DOZurBfk.js → Organization-BWf2tmQQ.js} +4 -4
  52. package/src/assets/web-panel/assets/{Overflow-qOT9BhUp.js → Overflow-BbvAtEth.js} +1 -1
  53. package/src/assets/web-panel/assets/{P2P-BMoy7Eo4.js → P2P-zBo6enit.js} +2 -2
  54. package/src/assets/web-panel/assets/{PdhVaultBrowser-ByyV1q-D.js → PdhVaultBrowser-D_LMQ4A4.js} +3 -3
  55. package/src/assets/web-panel/assets/{Permissions-6Qyro4ye.js → Permissions-C1ucb2ck.js} +4 -4
  56. package/src/assets/web-panel/assets/{PersonalDataHub-DR3DRNSX.js → PersonalDataHub-Bz64RddJ.js} +5 -5
  57. package/src/assets/web-panel/assets/{Pipeline-Cr6TTdud.js → Pipeline-DXJt3y3_.js} +1 -1
  58. package/src/assets/web-panel/assets/{Privacy-B70otG0l.js → Privacy-DiZ8b_ut.js} +1 -1
  59. package/src/assets/web-panel/assets/{ProjectInit-BHCqxoSk.js → ProjectInit-DfLKKrJ4.js} +2 -2
  60. package/src/assets/web-panel/assets/{ProjectSettings-B80GZpZk.js → ProjectSettings-Ct6kJuIY.js} +2 -2
  61. package/src/assets/web-panel/assets/{Projects-cq-b9-fQ.js → Projects-BBUMlZ3m.js} +1 -1
  62. package/src/assets/web-panel/assets/{Providers-dsVTimHa.js → Providers-DYVQPHvc.js} +1 -1
  63. package/src/assets/web-panel/assets/{QrScannerModal-DRJ8YepZ.js → QrScannerModal-23Il7iPp.js} +1 -1
  64. package/src/assets/web-panel/assets/{QuickAsk-BSXdEjIB.js → QuickAsk-WgOZKrv1.js} +1 -1
  65. package/src/assets/web-panel/assets/{Recommend-CQLJGyKC.js → Recommend-CRIka6xT.js} +1 -1
  66. package/src/assets/web-panel/assets/{RemoteSession-BF9WUFVZ.js → RemoteSession-DOy3GjVZ.js} +2 -2
  67. package/src/assets/web-panel/assets/{Reputation-Cy9o5tvW.js → Reputation-BXyi6xeR.js} +1 -1
  68. package/src/assets/web-panel/assets/{Row-BP346_3b.js → Row-CoMxuIcR.js} +1 -1
  69. package/src/assets/web-panel/assets/{RssFeed-BKulxXf3.js → RssFeed-87hPkoM6.js} +3 -3
  70. package/src/assets/web-panel/assets/{Search-EemQj2Dv.js → Search-B1EqFCO4.js} +1 -1
  71. package/src/assets/web-panel/assets/{Security-DcWPAwwk.js → Security-HcC41eGs.js} +4 -4
  72. package/src/assets/web-panel/assets/{Services-Bltqciay.js → Services-BqLwmvCM.js} +2 -2
  73. package/src/assets/web-panel/assets/{Skeleton-BwRyOGY8.js → Skeleton-CIMmjW4r.js} +1 -1
  74. package/src/assets/web-panel/assets/{Skills-D98qlvpJ.js → Skills-CgurezJU.js} +1 -1
  75. package/src/assets/web-panel/assets/{Sla-BUTtBMY0.js → Sla-CJuRuqPE.js} +1 -1
  76. package/src/assets/web-panel/assets/{SpeechSettings-C7kcY_98.js → SpeechSettings-BUwsbd1o.js} +1 -1
  77. package/src/assets/web-panel/assets/{SyncSettings-Bsk6lKvT.js → SyncSettings-qo04TOX-.js} +2 -2
  78. package/src/assets/web-panel/assets/Tasks-QEFENVsp.js +1 -0
  79. package/src/assets/web-panel/assets/{Templates-BUM6LwPs.js → Templates-D1PTczPW.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tenant-D9TkOm7z.js → Tenant-Bi0FkEPR.js} +1 -1
  81. package/src/assets/web-panel/assets/{Terminal-CP8ZVErN.js → Terminal-DfSt3bIF.js} +2 -2
  82. package/src/assets/web-panel/assets/{TimelineRenderer-V3SoKZ1T.js → TimelineRenderer-B7bIVs4R.js} +1 -1
  83. package/src/assets/web-panel/assets/{Tokens-CCRlUTOA.js → Tokens-B6w5XrMb.js} +1 -1
  84. package/src/assets/web-panel/assets/{Trigger-CznUcPme.js → Trigger-XXVSDWiF.js} +1 -1
  85. package/src/assets/web-panel/assets/{Trust-D1GIk_NF.js → Trust-DYvlgcK-.js} +1 -1
  86. package/src/assets/web-panel/assets/{UkeySign-DHtvtyC-.js → UkeySign-XyYwJMBM.js} +1 -1
  87. package/src/assets/web-panel/assets/{VideoEditing-CVxdaJIq.js → VideoEditing-C47WbtHH.js} +1 -1
  88. package/src/assets/web-panel/assets/{Wallet-D221OjyE.js → Wallet-CYfqTj_C.js} +4 -4
  89. package/src/assets/web-panel/assets/{WebAuthn-Cw0-ESsq.js → WebAuthn-3AduFn1l.js} +3 -3
  90. package/src/assets/web-panel/assets/{WorkflowEditor-ZYScrBx3.js → WorkflowEditor-DHYEHjMJ.js} +1 -1
  91. package/src/assets/web-panel/assets/{chat-BU0e1obR.js → chat-AeehErSw.js} +1 -1
  92. package/src/assets/web-panel/assets/{colors-gzYweuha.js → colors-DKXJ8paX.js} +1 -1
  93. package/src/assets/web-panel/assets/{compact-item-ChAXal_k.js → compact-item-BOlCiomD.js} +1 -1
  94. package/src/assets/web-panel/assets/{createContext-of3FolD7.js → createContext-BAP6U85b.js} +1 -1
  95. package/src/assets/web-panel/assets/devWarning-rGldFhxo.js +1 -0
  96. package/src/assets/web-panel/assets/{hasIn-4PsK_Kx8.js → hasIn-BLmeL1Xe.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-Cn1clrxx.js → index-B0IjlB9m.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BOeX7BzS.js → index-BAS2QqLn.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-Cx4Z6nUl.js → index-BRcJlKSa.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DjRAIRX7.js → index-BSOenF5s.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Djfgsjfy.js → index-BTnp4Nt3.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BfI4dXbi.js → index-BhcpWl5z.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D_5LTPMH.js → index-Bn54FU3i.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CyDQNprx.js → index-Bpx54vfs.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DEXBckNR.js → index-Bs2O3f81.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Ef5YkVAn.js → index-Bt-FCVzh.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-E8oDMSAo.js → index-BuUwKmEI.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-BdxRVNPY.js → index-C5AKY6bJ.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-Wr91cnWr.js → index-C8qi53Ki.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B5L7zddz.js → index-CFKzWIo8.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dy0ojH6G.js → index-CRa4pAaM.js} +3 -3
  112. package/src/assets/web-panel/assets/{index-wo0De0iG.js → index-CaHhfpBi.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D-Zss5KL.js → index-CdNSmvog.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DEzjUgkl.js → index-CgtaOzMo.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DDYj14Q4.js → index-CiqeF0Pf.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-D_9k680X.js → index-ClpwxOXo.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-D1PGrfHE.js → index-Co7pjF53.js} +1 -1
  118. package/src/assets/web-panel/assets/index-Cw-lfvvV.js +1 -0
  119. package/src/assets/web-panel/assets/{index-CeEE_SGZ.js → index-DE7cbDDe.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CxzBcXOa.js → index-DQLQuwpA.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-DEB5fQqG.js → index-DWYqOI1A.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CO2owKe0.js → index-De2fPxdb.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-1dzYRBbg.js → index-DpZqbc4w.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CGFL59q4.js → index-DsQ38fVY.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DsoV9rzn.js → index-Dv8-g1pY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-yMf7zmWv.js → index-EKNODw4P.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CHv_63Cp.js → index-FPXLIXoy.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-CCLsbul3.js → index-IlzfIRNh.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-cFkMIx1x.js → index-OLKhib9T.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Bftq5rJR.js → index-PEe9OWEQ.js} +1 -1
  131. package/src/assets/web-panel/assets/index-QBXRSyoZ.js +1 -0
  132. package/src/assets/web-panel/assets/{index-DJVrzXtk.js → index-T28naovw.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-By-pper5.js → index-ZMbXl5lo.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-DZ0oKtBg.js → index-urPvyUVk.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-D_ialOHx.js → index-wyKpTsGM.js} +1 -1
  136. package/src/assets/web-panel/assets/{initDefaultProps-D0Bx6pgr.js → initDefaultProps-CDWa1rnn.js} +1 -1
  137. package/src/assets/web-panel/assets/{motion-BnLuJqJ5.js → motion-CX2Cnsd5.js} +1 -1
  138. package/src/assets/web-panel/assets/{move-Bxpq9xgR.js → move-Bfe1NcEF.js} +1 -1
  139. package/src/assets/web-panel/assets/{mtc-parser-BdmrNhOs.js → mtc-parser-DbdqleHE.js} +1 -1
  140. package/src/assets/web-panel/assets/{omit-tjJD34BZ.js → omit-CE2K7NVv.js} +1 -1
  141. package/src/assets/web-panel/assets/{pickAttrs-CMFzLt7b.js → pickAttrs-y_bZgxWG.js} +1 -1
  142. package/src/assets/web-panel/assets/{placementArrow-BZf6O3cd.js → placementArrow-BOHR4zwr.js} +1 -1
  143. package/src/assets/web-panel/assets/{responsiveObserve-DFDLazsV.js → responsiveObserve-B8Jwq05-.js} +1 -1
  144. package/src/assets/web-panel/assets/{slide-CMbTvp4Y.js → slide-BD7RJ3Kn.js} +1 -1
  145. package/src/assets/web-panel/assets/{statusUtils-FOan8MI7.js → statusUtils-BuxDJeDx.js} +1 -1
  146. package/src/assets/web-panel/assets/{styleChecker-DTABspd4.js → styleChecker-DgYQ2Q0o.js} +1 -1
  147. package/src/assets/web-panel/assets/{useFlexGapSupport-CNHXWBpY.js → useFlexGapSupport-DAHF0Fwi.js} +1 -1
  148. package/src/assets/web-panel/assets/{useFs-5K-c_RoL.js → useFs-D4kDyLFp.js} +1 -1
  149. package/src/assets/web-panel/assets/{usePersonalDataHub-Di0hL4G-.js → usePersonalDataHub-BHtSxW4A.js} +1 -1
  150. package/src/assets/web-panel/assets/{vnode-DTyDv4ja.js → vnode-CHKtdqW0.js} +1 -1
  151. package/src/assets/web-panel/assets/{zoom-ontRt8ft.js → zoom-CIZAMma9.js} +1 -1
  152. package/src/assets/web-panel/index.html +1 -1
  153. package/src/command-help-index.json +1 -1
  154. package/src/commands/agent.js +64 -40
  155. package/src/commands/ask.js +6 -4
  156. package/src/commands/team.js +28 -19
  157. package/src/data/changelog.json +26 -0
  158. package/src/harness/prompt-compressor.js +2 -0
  159. package/src/lib/advisor-runtime.js +4 -2
  160. package/src/lib/agent-ipc-bus.js +2 -0
  161. package/src/lib/background-agent-keeper-client.js +3 -0
  162. package/src/lib/browser-automation.js +4 -0
  163. package/src/lib/cc-android-bridge.js +4 -0
  164. package/src/lib/chat-core.js +11 -9
  165. package/src/lib/cloud/cloud-client.js +10 -1
  166. package/src/lib/config-schema.js +18 -5
  167. package/src/lib/context-memory-kernel/memory-query-index.js +5 -2
  168. package/src/lib/context-memory-kernel/runtime.js +4 -0
  169. package/src/lib/context-memory-kernel/segmented-memory-port.js +28 -3
  170. package/src/lib/context-source-ledger.js +52 -9
  171. package/src/lib/cowork-adapter.js +4 -3
  172. package/src/lib/decision-layer/providers.js +16 -11
  173. package/src/lib/evolution/governed-hub-llm.js +31 -16
  174. package/src/lib/evolution/governed-hub-resolver.js +4 -0
  175. package/src/lib/evolution/governed-skill-synthesis-process-grader.js +4 -0
  176. package/src/lib/evomap-client.js +14 -2
  177. package/src/lib/formal-budget-authority.js +191 -59
  178. package/src/lib/formal-budget-contract.js +7 -13
  179. package/src/lib/formal-budget-errors.js +31 -0
  180. package/src/lib/formal-budget-lineage-registry.js +612 -0
  181. package/src/lib/formal-budget-provider-scope.js +143 -0
  182. package/src/lib/formal-budget-transport.js +235 -100
  183. package/src/lib/gateway-client-resilience.js +5 -0
  184. package/src/lib/goal-condition-engine.js +25 -2
  185. package/src/lib/hub-llm-client.js +25 -16
  186. package/src/lib/llm-config-defaults.js +10 -4
  187. package/src/lib/llm-connection-probe.js +17 -10
  188. package/src/lib/llm-pricing.js +16 -4
  189. package/src/lib/llm-providers.js +4 -1
  190. package/src/lib/model-capabilities.js +26 -0
  191. package/src/lib/model-catalog-review.js +18 -1
  192. package/src/lib/model-context-catalog.js +41 -1
  193. package/src/lib/model-failure-policy.js +2 -0
  194. package/src/lib/openai-responses.js +8 -0
  195. package/src/lib/plugin-runtime/marketplace-network.js +9 -5
  196. package/src/lib/process-execution-broker/credential-agent.js +7 -2
  197. package/src/lib/process-execution-broker/credential-transport.js +4 -0
  198. package/src/lib/process-execution-broker/index.js +8 -0
  199. package/src/lib/provider-stream.js +28 -13
  200. package/src/lib/sandbox-egress-worker.js +3 -0
  201. package/src/lib/skill-retrieval-router.js +1 -0
  202. package/src/lib/stream-retry.js +2 -0
  203. package/src/lib/sub-agent-context.js +6 -3
  204. package/src/lib/usage-pricing-context.js +33 -2
  205. package/src/lib/web-fetch.js +2 -0
  206. package/src/lib/web-search.js +5 -0
  207. package/src/repl/agent-repl.js +247 -153
  208. package/src/runtime/agent-core.js +228 -96
  209. package/src/runtime/coding-agent-contract-shared.cjs +14 -1
  210. package/src/runtime/headless-runner.js +1 -0
  211. package/src/runtime/headless-stream.js +1 -0
  212. package/src/runtime/mcp-tool-search.js +0 -0
  213. package/src/runtime/provider-stream-state.js +14 -1
  214. package/src/assets/web-panel/assets/ChatBubbleRenderer-D3KsyCUV.js +0 -1
  215. package/src/assets/web-panel/assets/MarkdownRenderer-C1yByobo.js +0 -1
  216. package/src/assets/web-panel/assets/Tasks-DvSrtz0P.js +0 -1
  217. package/src/assets/web-panel/assets/devWarning-Pa13TVrO.js +0 -1
  218. package/src/assets/web-panel/assets/index-BhQg-v6V.js +0 -1
  219. package/src/assets/web-panel/assets/index-DUD5o-vW.js +0 -1
@@ -13,6 +13,7 @@ import { EventEmitter } from "node:events";
13
13
  import crypto from "node:crypto";
14
14
  import { EventRuntimeProducer } from "./event-runtime-producer.js";
15
15
  import { EventRuntimeStore } from "./event-runtime-store.js";
16
+ import { requireUnscopedDescendantControl } from "./formal-budget-provider-scope.js";
16
17
  import executionBroker from "./process-execution-broker/index.js";
17
18
  import {
18
19
  AGENT_IPC_DEFAULT_LIMITS,
@@ -284,6 +285,7 @@ class AgentIPCBus extends EventEmitter {
284
285
  * @returns {Promise<{process: ChildProcess, agentId: string}>}
285
286
  */
286
287
  async spawnAgentProcess(command, args = [], options = {}) {
288
+ requireUnscopedDescendantControl();
287
289
  const agentId = options.agentId || crypto.randomUUID();
288
290
  if (
289
291
  this._childTransports.has(agentId) ||
@@ -1,6 +1,7 @@
1
1
  import net from "node:net";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { createNdjsonReader } from "./background-session-transport.js";
4
+ import { requireUnscopedDescendantControl } from "./formal-budget-provider-scope.js";
4
5
  import {
5
6
  BACKGROUND_AGENT_KEEPER_ARM,
6
7
  BACKGROUND_AGENT_KEEPER_ARMED,
@@ -67,6 +68,7 @@ async function connectWithDeadline(pipePath, timeoutMs) {
67
68
  }
68
69
 
69
70
  export async function connectBackgroundAgentKeeper(options = {}) {
71
+ requireUnscopedDescendantControl();
70
72
  const hello = normalizeBackgroundAgentKeeperHello(options);
71
73
  const connectTimeoutMs = Math.max(
72
74
  1,
@@ -234,6 +236,7 @@ export async function connectBackgroundAgentKeeper(options = {}) {
234
236
 
235
237
  return Object.freeze({
236
238
  async arm(turn) {
239
+ requireUnscopedDescendantControl();
237
240
  const normalized = normalizeBackgroundAgentKeeperTurn(turn);
238
241
  if (activeTurn) {
239
242
  throw keeperError(
@@ -3,10 +3,13 @@
3
3
  * Uses built-in fetch for basic operations, optional playwright for screenshots.
4
4
  */
5
5
 
6
+ import { requireUnscopedExternalToolAccess } from "./formal-budget-provider-scope.js";
7
+
6
8
  /**
7
9
  * Fetch a URL and return the raw HTML.
8
10
  */
9
11
  export async function fetchPage(url, options = {}) {
12
+ requireUnscopedExternalToolAccess();
10
13
  const timeout = options.timeout || 30000;
11
14
  const controller = new AbortController();
12
15
  const timer = setTimeout(() => controller.abort(), timeout);
@@ -179,6 +182,7 @@ export function extractLinks(html, baseUrl) {
179
182
  * Returns null if playwright is not installed.
180
183
  */
181
184
  export async function takeScreenshot(url, outputPath, options = {}) {
185
+ requireUnscopedExternalToolAccess();
182
186
  try {
183
187
  const { chromium } = await import("playwright");
184
188
  const browser = await chromium.launch({ headless: true });
@@ -32,6 +32,7 @@
32
32
 
33
33
  import { readFileSync, existsSync } from "node:fs";
34
34
  import { join, dirname } from "node:path";
35
+ import { requireUnscopedExternalToolAccess } from "./formal-budget-provider-scope.js";
35
36
 
36
37
  const ANDROID_PREFIX = "/data/data/com.chainlesschain.android/";
37
38
 
@@ -72,6 +73,7 @@ function resolveBridgeConfigDir() {
72
73
  * (server not started yet, or running off-device without override).
73
74
  */
74
75
  export function loadBridgeConfig() {
76
+ requireUnscopedExternalToolAccess();
75
77
  const dir = resolveBridgeConfigDir();
76
78
  if (!dir) return null;
77
79
  try {
@@ -109,6 +111,7 @@ export class AndroidBridgeUnavailableError extends Error {
109
111
  * - Method name unknown (server returns {"error": "UNKNOWN_METHOD"})
110
112
  */
111
113
  export async function invoke(method, params = {}) {
114
+ requireUnscopedExternalToolAccess();
112
115
  if (typeof method !== "string" || method.length === 0) {
113
116
  throw new TypeError(
114
117
  "cc-android-bridge.invoke: method must be non-empty string",
@@ -170,6 +173,7 @@ export async function invoke(method, params = {}) {
170
173
  * Returns `{ available: boolean, reason?: string, port?: number }`.
171
174
  */
172
175
  export function caps() {
176
+ requireUnscopedExternalToolAccess();
173
177
  if (process.env.CC_ANDROID_BRIDGE_OVERRIDE === "1") {
174
178
  return { available: true, reason: "test-override" };
175
179
  }
@@ -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)));
@@ -69,6 +69,19 @@ const DESCRIPTORS = [
69
69
  default: DEFAULT_CONFIG.llm.model,
70
70
  description: "Default model id",
71
71
  }),
72
+ descriptor("llm.serviceTier", "string", {
73
+ enum: [
74
+ "standard",
75
+ "default",
76
+ "auto",
77
+ "fast",
78
+ "priority",
79
+ "flex",
80
+ "ultrafast",
81
+ ],
82
+ description:
83
+ "Requested live service tier; model and endpoint support are checked before dispatch",
84
+ }),
72
85
  descriptor("llm.visionModel", ["string", "null"], {
73
86
  description: "Model used for image turns",
74
87
  }),
@@ -502,11 +515,11 @@ function valueIssue(key, code, message) {
502
515
  function isPersistedSecretRef(value) {
503
516
  return Boolean(
504
517
  value &&
505
- typeof value === "object" &&
506
- !Array.isArray(value) &&
507
- Object.keys(value).length === 1 &&
508
- typeof value.__cc_secret_ref === "string" &&
509
- value.__cc_secret_ref.length > 0,
518
+ typeof value === "object" &&
519
+ !Array.isArray(value) &&
520
+ Object.keys(value).length === 1 &&
521
+ typeof value.__cc_secret_ref === "string" &&
522
+ value.__cc_secret_ref.length > 0,
510
523
  );
511
524
  }
512
525
 
@@ -2,6 +2,9 @@ import { createHash } from "node:crypto";
2
2
 
3
3
  export const MEMORY_QUERY_INDEX_SCHEMA = "chainlesschain.memory-query-index/v1";
4
4
  const CURSOR_SCHEMA = "chainlesschain.memory-list-cursor/v1";
5
+ // Preserve the existing English collation while avoiding constructing locale
6
+ // options for every comparison during shard sorts, cursor search and merging.
7
+ const memoryRowCollator = new Intl.Collator("en");
5
8
 
6
9
  export function queryDigest(bytes) {
7
10
  return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
@@ -10,8 +13,8 @@ export function queryDigest(bytes) {
10
13
  export function compareMemoryRows(left, right) {
11
14
  return (
12
15
  right.importance - left.importance ||
13
- right.updatedAt.localeCompare(left.updatedAt, "en") ||
14
- left.memoryId.localeCompare(right.memoryId, "en")
16
+ memoryRowCollator.compare(right.updatedAt, left.updatedAt) ||
17
+ memoryRowCollator.compare(left.memoryId, right.memoryId)
15
18
  );
16
19
  }
17
20
 
@@ -22,6 +22,10 @@ export function createCliContextMemoryRuntime(options = {}) {
22
22
  const memoryPort =
23
23
  options.memoryPort ||
24
24
  new SegmentedMemoryPort({
25
+ queryIndexCacheMode:
26
+ options.memoryQueryIndexCacheMode ||
27
+ (options.env || process.env).CC_CONTEXT_MEMORY_QUERY_CACHE ||
28
+ "validate-disk",
25
29
  readOnly: !decision.canonical,
26
30
  ...(options.memoryFilePath ? { filePath: options.memoryFilePath } : {}),
27
31
  });
@@ -170,6 +170,7 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
170
170
  maxTotalBytes = DEFAULT_MAX_TOTAL_BYTES,
171
171
  maxEvents = DEFAULT_SEGMENTED_MAX_EVENTS,
172
172
  readOnly = false,
173
+ queryIndexCacheMode = "validate-disk",
173
174
  ...options
174
175
  } = {}) {
175
176
  super({ ...options, maxEvents });
@@ -177,6 +178,9 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
177
178
  throw new TypeError("maxTotalBytes must be a positive safe integer");
178
179
  this.maxTotalBytes = maxTotalBytes;
179
180
  this.readOnly = Boolean(readOnly);
181
+ if (!["validate-disk", "verified-memory"].includes(queryIndexCacheMode))
182
+ throw new TypeError("Unknown query index cache mode");
183
+ this.queryIndexCacheMode = queryIndexCacheMode;
180
184
  this.filePath = resolve(this.filePath);
181
185
  this.shardDirectory = `${this.filePath}.shards`;
182
186
  this._queryIndexes = new Map();
@@ -656,6 +660,27 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
656
660
 
657
661
  _readQueryIndex(manifest, bucket) {
658
662
  const descriptor = manifest.shards[bucket];
663
+ const binding = JSON.stringify([
664
+ manifest.digest,
665
+ manifest.storeRevision,
666
+ descriptor.digest,
667
+ descriptor.queryDigest,
668
+ descriptor.queryBytes,
669
+ descriptor.recordCount,
670
+ ]);
671
+ const cached = this._queryIndexes.get(bucket);
672
+ // This is an already verified, host-owned decoded snapshot, never a trust
673
+ // decision based on filesystem timestamps. Re-read and verify the current
674
+ // manifest on every operation; any generation/authority change invalidates
675
+ // this binding. Selected authority shards are still read and verified.
676
+ // Disk repair remains eager in validate-disk mode; verified-memory repairs
677
+ // a derived index on the next cache miss, rather than every warm query.
678
+ if (
679
+ this.queryIndexCacheMode === "verified-memory" &&
680
+ descriptor.queryDigest &&
681
+ cached?.binding === binding
682
+ )
683
+ return cached.index;
659
684
  let bytes;
660
685
  if (descriptor.queryDigest) {
661
686
  const file = this._queryIndexPath(bucket, descriptor);
@@ -705,8 +730,8 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
705
730
  bytes = encodeQueryIndex(this._readShard(manifest, bucket));
706
731
  }
707
732
  const digest = descriptor.queryDigest || queryDigest(bytes);
708
- const cached = this._queryIndexes.get(bucket);
709
- if (cached?.digest === digest) return cached.index;
733
+ if (cached?.digest === digest && cached.binding === binding)
734
+ return cached.index;
710
735
  let index;
711
736
  try {
712
737
  index = compileQueryIndex(bytes, descriptor);
@@ -717,7 +742,7 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
717
742
  cause,
718
743
  );
719
744
  }
720
- this._queryIndexes.set(bucket, { digest, index });
745
+ this._queryIndexes.set(bucket, { digest, binding, index });
721
746
  return index;
722
747
  }
723
748
 
@@ -40,7 +40,28 @@ class ContextSourceLedger {
40
40
  * @returns {string} entryId
41
41
  */
42
42
  record(entry) {
43
+ if (
44
+ entry.tokenCount != null &&
45
+ (!Number.isSafeInteger(entry.tokenCount) || entry.tokenCount < 0)
46
+ ) {
47
+ throw new TypeError(
48
+ "Context tokenCount must be a non-negative safe integer",
49
+ );
50
+ }
43
51
  const entryId = entry.entryId || crypto.randomUUID();
52
+ const previous = this._entries.get(entryId);
53
+ if (previous) {
54
+ for (const [index, key] of [
55
+ [this._byTurn, previous.turnId],
56
+ [this._bySession, previous.sessionId],
57
+ ]) {
58
+ const remaining = (index.get(key) || []).filter(
59
+ (item) => item.entryId !== entryId,
60
+ );
61
+ if (remaining.length) index.set(key, remaining);
62
+ else index.delete(key);
63
+ }
64
+ }
44
65
  const full = {
45
66
  entryId,
46
67
  traceId: entry.traceId || null,
@@ -53,6 +74,7 @@ class ContextSourceLedger {
53
74
  summary: entry.summary,
54
75
  metadata: entry.metadata,
55
76
  tokenCount: entry.tokenCount || 0,
77
+ tokenBasis: entry.tokenBasis === "observed" ? "observed" : "estimated",
56
78
  insertedAt: new Date(),
57
79
  tags: entry.tags || [],
58
80
  };
@@ -64,7 +86,8 @@ class ContextSourceLedger {
64
86
  this._byTurn.get(full.turnId).push(full);
65
87
 
66
88
  // Index by session
67
- if (!this._bySession.has(full.sessionId)) this._bySession.set(full.sessionId, []);
89
+ if (!this._bySession.has(full.sessionId))
90
+ this._bySession.set(full.sessionId, []);
68
91
  this._bySession.get(full.sessionId).push(full);
69
92
 
70
93
  return entryId;
@@ -82,6 +105,7 @@ class ContextSourceLedger {
82
105
  summary: entry.summary || entry.content,
83
106
  metadata: entry.metadata,
84
107
  tokenCount: entry.tokenCount ?? entry.tokens ?? 0,
108
+ tokenBasis: entry.tokenBasis,
85
109
  traceId: entry.traceId,
86
110
  });
87
111
  }
@@ -95,14 +119,31 @@ class ContextSourceLedger {
95
119
  }
96
120
 
97
121
  /** Aggregate token usage by source type. */
98
- getTokenBreakdown() {
99
- const bySource = {};
122
+ getTokenBreakdown(options = {}) {
123
+ const sources = new Map();
100
124
  let total = 0;
101
- for (const entry of this._entries.values()) {
125
+ for (const entry of this.getProvenance(options)) {
102
126
  total += entry.tokenCount;
103
- bySource[entry.sourceType] = (bySource[entry.sourceType] || 0) + entry.tokenCount;
127
+ sources.set(
128
+ entry.sourceType,
129
+ (sources.get(entry.sourceType) || 0) + entry.tokenCount,
130
+ );
104
131
  }
105
- return { total, bySource };
132
+ return { total, bySource: Object.fromEntries(sources) };
133
+ }
134
+
135
+ /** Source attribution is separate from provider request usage and billing. */
136
+ getTokenReport(options = {}) {
137
+ const entries = this.getProvenance(options);
138
+ const byBasis = { estimated: 0, observed: 0 };
139
+ for (const entry of entries) byBasis[entry.tokenBasis] += entry.tokenCount;
140
+ return {
141
+ ...this.getTokenBreakdown(options),
142
+ byBasis,
143
+ entries: entries.length,
144
+ currentRequestVerified: false,
145
+ billingVerified: false,
146
+ };
106
147
  }
107
148
 
108
149
  /** Clear the in-memory ledger (test/runtime lifecycle boundary). */
@@ -146,7 +187,8 @@ class ContextSourceLedger {
146
187
 
147
188
  for (const e of entries) {
148
189
  byType[e.sourceType] = (byType[e.sourceType] || 0) + 1;
149
- byPermission[e.permissionMode] = (byPermission[e.permissionMode] || 0) + 1;
190
+ byPermission[e.permissionMode] =
191
+ (byPermission[e.permissionMode] || 0) + 1;
150
192
  bySource[e.sourceId] = (bySource[e.sourceId] || 0) + 1;
151
193
  totalTokens += e.tokenCount;
152
194
  avgConfidence += e.confidence;
@@ -162,7 +204,7 @@ class ContextSourceLedger {
162
204
  byType,
163
205
  byPermission,
164
206
  bySource,
165
- entries: entries.map(e => ({
207
+ entries: entries.map((e) => ({
166
208
  sourceType: e.sourceType,
167
209
  sourceId: e.sourceId,
168
210
  permissionMode: e.permissionMode,
@@ -184,8 +226,9 @@ class ContextSourceLedger {
184
226
  this._entries.delete(e.entryId);
185
227
  const turnEntries = this._byTurn.get(e.turnId);
186
228
  if (turnEntries) {
187
- const idx = turnEntries.findIndex(x => x.entryId === e.entryId);
229
+ const idx = turnEntries.findIndex((x) => x.entryId === e.entryId);
188
230
  if (idx >= 0) turnEntries.splice(idx, 1);
231
+ if (!turnEntries.length) this._byTurn.delete(e.turnId);
189
232
  }
190
233
  }
191
234
  this._bySession.delete(sessionId);
@@ -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
  }
@@ -1,6 +1,10 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { types } from "node:util";
3
3
  import { prepareGovernedModelTurn } from "./governed-model-turn.js";
4
+ import {
5
+ providerFetch,
6
+ requireUnscopedModelTransport,
7
+ } from "../formal-budget-provider-scope.js";
4
8
 
5
9
  const { createAuthenticatedEvolutionModelClient } = createRequire(
6
10
  import.meta.url,
@@ -97,21 +101,25 @@ function captureHubTransport(llm, sdk) {
97
101
  const signal = options.signal
98
102
  ? AbortSignal.any([controller.signal, options.signal])
99
103
  : controller.signal;
100
- const response = await fetchImpl(`${baseUrl}/api/chat`, {
101
- method: "POST",
102
- headers: { "Content-Type": "application/json" },
103
- redirect: "error",
104
- signal,
105
- body: JSON.stringify({
106
- model,
107
- messages,
108
- stream: false,
109
- options: {
110
- temperature: options.temperature ?? 0.2,
111
- ...(options.numCtx ? { num_ctx: options.numCtx } : {}),
112
- },
113
- }),
114
- });
104
+ const response = await providerFetch(
105
+ `${baseUrl}/api/chat`,
106
+ {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json" },
109
+ redirect: "error",
110
+ signal,
111
+ body: JSON.stringify({
112
+ model,
113
+ messages,
114
+ stream: false,
115
+ options: {
116
+ temperature: options.temperature ?? 0.2,
117
+ ...(options.numCtx ? { num_ctx: options.numCtx } : {}),
118
+ },
119
+ }),
120
+ },
121
+ fetchImpl,
122
+ );
115
123
  if (!response.ok)
116
124
  throw new Error(
117
125
  `Hub Ollama request failed: HTTP ${response.status}`,
@@ -202,6 +210,7 @@ function captureHubTransport(llm, sdk) {
202
210
  );
203
211
  },
204
212
  async chat(messages, options) {
213
+ requireUnscopedModelTransport();
205
214
  const result = await chat(messages, options);
206
215
  const text =
207
216
  result?.content ??
@@ -233,7 +242,13 @@ function captureHubTransport(llm, sdk) {
233
242
  },
234
243
  };
235
244
  }
236
- return { assertIdentity() {}, chat: llm.chat.bind(llm) };
245
+ return {
246
+ assertIdentity() {},
247
+ chat(messages, options) {
248
+ requireUnscopedModelTransport();
249
+ return llm.chat(messages, options);
250
+ },
251
+ };
237
252
  }
238
253
 
239
254
  // A scoped wrapper preserves the Hub's independent local/cloud consent check.
@@ -1,4 +1,5 @@
1
1
  import { prepareGovernedModelTurn } from "./governed-model-turn.js";
2
+ import { requireUnscopedModelTransport } from "../formal-budget-provider-scope.js";
2
3
  import {
3
4
  captureGovernedHubSdkPorts,
4
5
  createGovernedHubLlm,
@@ -48,6 +49,9 @@ function captureEmbeddingTransport(stage, sdk) {
48
49
  );
49
50
  },
50
51
  async embed(profile) {
52
+ // Neither arbitrary embedding callbacks nor the native embeddings
53
+ // protocol have a verified Chat billing contract in this scope.
54
+ requireUnscopedModelTransport();
51
55
  if (!defaultOllama) return await embed.call(stage, profile);
52
56
  // The raw SDK ollamaEmbed remains closed. This private transport is only
53
57
  // invoked below, after the scoped Run projected the profile.