chainlesschain 0.162.182 → 0.162.184

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 (202) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/.build-hash +1 -1
  3. package/src/assets/web-panel/assets/{AIOps-DzZ5_xoj.js → AIOps-DzAl5fGx.js} +1 -1
  4. package/src/assets/web-panel/assets/{ActionButton-DeLXddIg.js → ActionButton-DAHtMGeC.js} +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-rvDCmet1.js → Analytics-BHQaexv5.js} +3 -3
  6. package/src/assets/web-panel/assets/{AppLayout-Yt6QVDnp.js → AppLayout-Phz_pD0n.js} +5 -5
  7. package/src/assets/web-panel/assets/{Artifacts-BS112K5L.js → Artifacts-DHt47kCA.js} +1 -1
  8. package/src/assets/web-panel/assets/{Audit-0wJuQrNu.js → Audit--Y_8FULl.js} +1 -1
  9. package/src/assets/web-panel/assets/{BackgroundAgents-B90PMgbn.js → BackgroundAgents-N1zchH7D.js} +1 -1
  10. package/src/assets/web-panel/assets/{Backup-TkgMglQQ.js → Backup-B_BoyN30.js} +1 -1
  11. package/src/assets/web-panel/assets/{BaseInput-CQVS9p1l.js → BaseInput-Gl4uQHcT.js} +1 -1
  12. package/src/assets/web-panel/assets/{Chat-YTjQHoC7.js → Chat-Cg-eIyAZ.js} +5 -5
  13. package/src/assets/web-panel/assets/ChatBubbleRenderer-BzeUi3JH.js +1 -0
  14. package/src/assets/web-panel/assets/{Checkbox-5bwaOabq.js → Checkbox-D0MGR2wA.js} +1 -1
  15. package/src/assets/web-panel/assets/{Codegen-BFmNQ_Xe.js → Codegen-BdObJEFn.js} +1 -1
  16. package/src/assets/web-panel/assets/{Col-BT_u4Dzp.js → Col--ghxXT3z.js} +1 -1
  17. package/src/assets/web-panel/assets/{Community-CJP52u5b.js → Community-BUFeXOqz.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compact-BpzhJE9i.js → Compact-DhQO2_Mr.js} +1 -1
  19. package/src/assets/web-panel/assets/{Compliance-Cg7rfslp.js → Compliance-3HoTqKYd.js} +1 -1
  20. package/src/assets/web-panel/assets/{Cowork-JisxL6Yy.js → Cowork-B3zmO4VW.js} +3 -3
  21. package/src/assets/web-panel/assets/{Cron-Bu7Ug_MC.js → Cron-CJDrZOcc.js} +2 -2
  22. package/src/assets/web-panel/assets/{Crosschain-CvuqoLdj.js → Crosschain-SMl15obd.js} +1 -1
  23. package/src/assets/web-panel/assets/{DID-C2u9G8Pu.js → DID-CgTrTWrl.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dashboard-BFt5Yv3d.js → Dashboard-dXFKaKol.js} +2 -2
  25. package/src/assets/web-panel/assets/{Dropdown-DIaT9Wxq.js → Dropdown-qQHEhCvI.js} +1 -1
  26. package/src/assets/web-panel/assets/{EmailListRenderer-D_A601Y4.js → EmailListRenderer-C_ej0MWH.js} +1 -1
  27. package/src/assets/web-panel/assets/{FamilyGuardDashboard--IyQ8I8w.js → FamilyGuardDashboard-D6ajoE7I.js} +1 -1
  28. package/src/assets/web-panel/assets/{Federation-BihWHxuY.js → Federation-Cio2y49h.js} +1 -1
  29. package/src/assets/web-panel/assets/{FormItemContext-BB8dOXc_.js → FormItemContext-ClJz1cYp.js} +1 -1
  30. package/src/assets/web-panel/assets/{GenericCardRenderer-DQYhj1HH.js → GenericCardRenderer-Dr_8Maca.js} +1 -1
  31. package/src/assets/web-panel/assets/{Git-B-2QZxGf.js → Git-CevLwp5u.js} +2 -2
  32. package/src/assets/web-panel/assets/{Governance-CusSb-nG.js → Governance-CW9YyQUY.js} +1 -1
  33. package/src/assets/web-panel/assets/{Inference-BAjmvPPL.js → Inference-CQqeAyER.js} +1 -1
  34. package/src/assets/web-panel/assets/{KnowledgeGraph-DoNYWKYq.js → KnowledgeGraph-cuGVxHrw.js} +1 -1
  35. package/src/assets/web-panel/assets/{Logs-B8fKWD-n.js → Logs-DIuHri5d.js} +2 -2
  36. package/src/assets/web-panel/assets/{MarkdownRenderer-BlJm1J7E.js → MarkdownRenderer-R91Qw5JA.js} +1 -1
  37. package/src/assets/web-panel/assets/{Marketplace-CyOscbSL.js → Marketplace-CY1VhFGx.js} +1 -1
  38. package/src/assets/web-panel/assets/{McpTools-Ex6Om5da.js → McpTools-BBAbi1vt.js} +5 -5
  39. package/src/assets/web-panel/assets/{Memory-CDcD8l8r.js → Memory-BWYGHxJA.js} +2 -2
  40. package/src/assets/web-panel/assets/{MobileBridge-xfBqoFDg.js → MobileBridge-CPIFkW7A.js} +1 -1
  41. package/src/assets/web-panel/assets/{MobileProjects-BwObMMA2.js → MobileProjects-DQyWxCho.js} +1 -1
  42. package/src/assets/web-panel/assets/{Mtc-CIWd36OV.js → Mtc-Bo3GnQbx.js} +4 -4
  43. package/src/assets/web-panel/assets/{MtcAudit-CPX0ouuA.js → MtcAudit-C8-c4rSW.js} +4 -4
  44. package/src/assets/web-panel/assets/{Multisig-BDJsIUuW.js → Multisig-ChqMTtoq.js} +3 -3
  45. package/src/assets/web-panel/assets/{NLProgramming-DEkbuD3N.js → NLProgramming-C48TyhvI.js} +1 -1
  46. package/src/assets/web-panel/assets/{Notes-DJpHRhH1.js → Notes-zogUFWxp.js} +3 -3
  47. package/src/assets/web-panel/assets/{NotificationSettings-C1UZ-Qd5.js → NotificationSettings-v-K9XpSB.js} +1 -1
  48. package/src/assets/web-panel/assets/OrderTableRenderer-P94JGGNz.js +1 -0
  49. package/src/assets/web-panel/assets/{Organization-Bbhj0fac.js → Organization-Boi1dQfR.js} +4 -4
  50. package/src/assets/web-panel/assets/{Overflow-1H04x3kx.js → Overflow-DM5YWwgA.js} +1 -1
  51. package/src/assets/web-panel/assets/{P2P-CDpPe3et.js → P2P-C57LKpKK.js} +2 -2
  52. package/src/assets/web-panel/assets/{PdhVaultBrowser-MUX0Bpvw.js → PdhVaultBrowser-BFkPqu6Q.js} +3 -3
  53. package/src/assets/web-panel/assets/{Permissions-A07-0Ci1.js → Permissions-D6fB9YSp.js} +4 -4
  54. package/src/assets/web-panel/assets/{PersonalDataHub-DhhDWxAZ.js → PersonalDataHub-Bp7EefEo.js} +5 -5
  55. package/src/assets/web-panel/assets/{Pipeline-CyfbJfe-.js → Pipeline-BVln3hZf.js} +1 -1
  56. package/src/assets/web-panel/assets/{Privacy-CNFps3ED.js → Privacy-Bf8P3nF6.js} +1 -1
  57. package/src/assets/web-panel/assets/{ProjectInit--T-u3Dr5.js → ProjectInit-DM6wA2E2.js} +2 -2
  58. package/src/assets/web-panel/assets/{ProjectSettings-DJmrK2fC.js → ProjectSettings-CGbc5Iho.js} +2 -2
  59. package/src/assets/web-panel/assets/Projects-D0p9EOxm.js +1 -0
  60. package/src/assets/web-panel/assets/{Providers-DLzzbOZd.js → Providers-B2kJcxS6.js} +1 -1
  61. package/src/assets/web-panel/assets/{QrScannerModal-CexOomKS.js → QrScannerModal-Cxu7dXYy.js} +1 -1
  62. package/src/assets/web-panel/assets/{QuickAsk-QklGi19_.js → QuickAsk-DTlrTPms.js} +1 -1
  63. package/src/assets/web-panel/assets/{Recommend-Bb9kwSw8.js → Recommend-Cm3X-cCj.js} +1 -1
  64. package/src/assets/web-panel/assets/{RemoteSession-DZuVNNN5.js → RemoteSession-DNb2kjvt.js} +2 -2
  65. package/src/assets/web-panel/assets/{Reputation-Dr5cLP0N.js → Reputation-CZUFFjc1.js} +1 -1
  66. package/src/assets/web-panel/assets/{Row-oCQHYv5t.js → Row-DAPgloxA.js} +1 -1
  67. package/src/assets/web-panel/assets/{RssFeed-IFDEykIR.js → RssFeed-dsx9S793.js} +2 -2
  68. package/src/assets/web-panel/assets/{Search-CspVIxDc.js → Search-DnnC8kz-.js} +1 -1
  69. package/src/assets/web-panel/assets/{Security-Bm66o_DH.js → Security-VuZknRMK.js} +4 -4
  70. package/src/assets/web-panel/assets/{Services-DvanNqXl.js → Services-B3XNX8Vx.js} +2 -2
  71. package/src/assets/web-panel/assets/{Skeleton-CHjEe9_O.js → Skeleton-Dl1nVtCj.js} +1 -1
  72. package/src/assets/web-panel/assets/Skills-CY09CZ_W.js +1 -0
  73. package/src/assets/web-panel/assets/{Sla-DxZm1491.js → Sla-HzU56n1z.js} +1 -1
  74. package/src/assets/web-panel/assets/{SpeechSettings-DWxEEclC.js → SpeechSettings-CRle7Jl2.js} +1 -1
  75. package/src/assets/web-panel/assets/{SyncSettings-FIvOFw5p.js → SyncSettings-DWadz7Sl.js} +2 -2
  76. package/src/assets/web-panel/assets/Tasks-CNVTDGg8.js +1 -0
  77. package/src/assets/web-panel/assets/{Templates-CaNjzV_t.js → Templates-BBAgvrlM.js} +1 -1
  78. package/src/assets/web-panel/assets/{Tenant-DjlP6TmH.js → Tenant-BsbbvGN8.js} +1 -1
  79. package/src/assets/web-panel/assets/Terminal-uscpZI4q.js +3 -0
  80. package/src/assets/web-panel/assets/{TimelineRenderer-DoZ8y7y8.js → TimelineRenderer-mTyjNE6r.js} +1 -1
  81. package/src/assets/web-panel/assets/{Tokens-390cg2TC.js → Tokens-Bz97oBgs.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trigger-CLrtCj5_.js → Trigger-BTRKC140.js} +1 -1
  83. package/src/assets/web-panel/assets/{Trust-DpESfSPn.js → Trust-B4A_KuyX.js} +1 -1
  84. package/src/assets/web-panel/assets/{UkeySign-CN6279O0.js → UkeySign-DES9TmLU.js} +1 -1
  85. package/src/assets/web-panel/assets/{VideoEditing-DPtAUlSe.js → VideoEditing-ClXs7Qxp.js} +1 -1
  86. package/src/assets/web-panel/assets/{Wallet-nKOMV_1n.js → Wallet-IzZqFz-7.js} +4 -4
  87. package/src/assets/web-panel/assets/{WebAuthn-HuXlSOc4.js → WebAuthn-BtOP04g_.js} +5 -5
  88. package/src/assets/web-panel/assets/{WorkflowEditor-CUfFjARi.js → WorkflowEditor-_0cqnopY.js} +1 -1
  89. package/src/assets/web-panel/assets/{chat-6Eq0PcZJ.js → chat-ax7193uU.js} +1 -1
  90. package/src/assets/web-panel/assets/{colors-BP_Sf6Iy.js → colors-Bu4WGJah.js} +1 -1
  91. package/src/assets/web-panel/assets/{compact-item-BufMKIh5.js → compact-item-BX1dlTW0.js} +1 -1
  92. package/src/assets/web-panel/assets/{createContext-BBMXxWSW.js → createContext-CktA_Dj7.js} +1 -1
  93. package/src/assets/web-panel/assets/devWarning-x877iKKS.js +1 -0
  94. package/src/assets/web-panel/assets/{hasIn-fUK_tRO2.js → hasIn-D5wd817l.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-t8N1tSeX.js → index-5PYYkr8S.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DLpIs_P_.js → index-A4uBPQCz.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DVsqvI4M.js → index-BAtBNfkr.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Bkg4ZePE.js → index-BLLsUNDc.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-cpEli44X.js → index-BQtoRyTO.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-wKTLbKyc.js → index-BRfXoRuh.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B7Etk0Te.js → index-Bad2CYeH.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CwSf5pnf.js → index-BcGdL3sG.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-3FuwBewR.js → index-C3HmzCLt.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BHy6yNiF.js → index-C8DkWyNr.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-CgiECHu2.js → index-C8tLR2TG.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-C44atv4k.js → index-CP8ELJBc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CW_G-MXw.js → index-CUForzsG.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-pum7HYBA.js → index-CYRE0NJA.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BWKoBU8R.js → index-CeVZaJSC.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-D4_MuGkd.js → index-CfzxR61U.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dyngbd29.js → index-Ci3y1ZAv.js} +19 -19
  112. package/src/assets/web-panel/assets/{index-fzaMtSJ8.js → index-CjEJjl5C.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Cp8vhyZy.js → index-Cv7bVWJ5.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DPdUNk1i.js → index-D-o4Rwjk.js} +1 -1
  115. package/src/assets/web-panel/assets/index-D7pfPouB.js +1 -0
  116. package/src/assets/web-panel/assets/{index-CeASi4fr.js → index-D9op8poE.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DASluSqY.js → index-DF1TysIF.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-DocdRcbd.js → index-DYo9S5OT.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-2A5ph8b2.js → index-D_cycOET.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-wV6iGdpY.js → index-Db3CpWTr.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BMLHv389.js → index-DkD1K-fF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-B9WYSn4m.js → index-DmPJ6T6F.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-uCbEN7cY.js → index-DmVUeWUk.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DmkMXwnR.js → index-Dzdr0zDf.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CZ6Yt17L.js → index-PBLzwYnu.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CE2DdTP2.js → index-bFCaM7Eq.js} +1 -1
  127. package/src/assets/web-panel/assets/index-e4i-VLBd.js +1 -0
  128. package/src/assets/web-panel/assets/{index-GwdRUZ-x.js → index-h3KJZr95.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-B2vS66eI.js → index-iHcPtQ2e.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-6GmBa4Kg.js → index-jzlzm4VK.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-D9vU25ij.js → index-o14NNnxw.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-CGjmILDm.js → index-tYPSfGbd.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-LvCBfTLo.js → index-ytp2H5Zx.js} +1 -1
  134. package/src/assets/web-panel/assets/{initDefaultProps-V5Fo95FM.js → initDefaultProps-D2Iut83B.js} +1 -1
  135. package/src/assets/web-panel/assets/{motion-BYWU0hUT.js → motion-BuaJVdK9.js} +1 -1
  136. package/src/assets/web-panel/assets/{move-Cr1M-J2h.js → move-CaEuM92y.js} +1 -1
  137. package/src/assets/web-panel/assets/{mtc-parser-BysQKi2N.js → mtc-parser-wuKNBwys.js} +1 -1
  138. package/src/assets/web-panel/assets/{omit-D1zHU8GC.js → omit-7a3eklaq.js} +1 -1
  139. package/src/assets/web-panel/assets/{pickAttrs-CUArxtfi.js → pickAttrs-Dn4z7KvJ.js} +1 -1
  140. package/src/assets/web-panel/assets/{placementArrow-ChoYEys0.js → placementArrow-DVn_qT4-.js} +1 -1
  141. package/src/assets/web-panel/assets/{responsiveObserve-BQRs3Mo7.js → responsiveObserve-Dodz1OMQ.js} +1 -1
  142. package/src/assets/web-panel/assets/{slide-B8zbsCkg.js → slide-CxoGskbp.js} +1 -1
  143. package/src/assets/web-panel/assets/{statusUtils-C2U8Gs6h.js → statusUtils-DBh_epQH.js} +1 -1
  144. package/src/assets/web-panel/assets/{styleChecker-Dd8MgL_V.js → styleChecker-RXQ4gG5P.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFlexGapSupport-hknqY5NH.js → useFlexGapSupport-1Wxm0HEG.js} +1 -1
  146. package/src/assets/web-panel/assets/{useFs-CLTAmcfw.js → useFs-CI_ySNxJ.js} +1 -1
  147. package/src/assets/web-panel/assets/{usePersonalDataHub-ByegHbiB.js → usePersonalDataHub-C6973rZP.js} +1 -1
  148. package/src/assets/web-panel/assets/{vnode-DqlAy5DP.js → vnode-CalynwKr.js} +1 -1
  149. package/src/assets/web-panel/assets/{zoom-DiXiztQn.js → zoom-CM1cd-Q0.js} +1 -1
  150. package/src/assets/web-panel/index.html +1 -1
  151. package/src/commands/agenda.js +74 -1
  152. package/src/commands/agent.js +9 -0
  153. package/src/commands/background-session.js +60 -1
  154. package/src/commands/batch.js +160 -5
  155. package/src/commands/plugin.js +318 -43
  156. package/src/commands/session.js +44 -1
  157. package/src/commands/team.js +212 -6
  158. package/src/data/changelog.json +26 -0
  159. package/src/harness/jsonl-session-store.js +59 -6
  160. package/src/harness/mcp-client.js +65 -43
  161. package/src/lib/agent-batch.js +4 -1
  162. package/src/lib/agent-team/team-runner.js +19 -1
  163. package/src/lib/agent-team/team-worktree.js +3 -0
  164. package/src/lib/async-hook-supervisor.cjs +82 -10
  165. package/src/lib/async-hook-supervisor.js +50 -4
  166. package/src/lib/background-agent-supervisor.js +49 -0
  167. package/src/lib/collaboration-run-store.js +362 -0
  168. package/src/lib/hook-runner.cjs +98 -17
  169. package/src/lib/hook-runner.js +77 -6
  170. package/src/lib/hook-shell-command.cjs +148 -0
  171. package/src/lib/hooks-v2-runtime.js +188 -77
  172. package/src/lib/jsonl-session-store.js +2 -0
  173. package/src/lib/lsp/__tests__/lsp-client.test.js +18 -0
  174. package/src/lib/lsp/lsp-client.js +12 -1
  175. package/src/lib/plugin-monitor-supervisor.js +53 -16
  176. package/src/lib/plugin-runtime/bin.js +1 -1
  177. package/src/lib/plugin-runtime/install.js +465 -54
  178. package/src/lib/plugin-runtime/mcp.js +21 -2
  179. package/src/lib/plugin-runtime/monitors.js +19 -3
  180. package/src/lib/plugin-runtime/sandbox-policy.js +99 -0
  181. package/src/lib/plugin-runtime/scopes.js +13 -2
  182. package/src/lib/plugin-usage-attribution.js +44 -0
  183. package/src/lib/process-execution-broker/index.js +705 -13
  184. package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +1347 -0
  185. package/src/lib/process-execution-broker/linux-generic-bwrap.js +1260 -0
  186. package/src/lib/process-execution-broker/platform-sandbox.js +1831 -157
  187. package/src/lib/session-usage.js +278 -1
  188. package/src/lib/stream-retry.js +29 -0
  189. package/src/repl/agent-repl.js +38 -9
  190. package/src/runtime/agent-core.js +32 -4
  191. package/src/runtime/headless-runner.js +46 -1
  192. package/src/runtime/headless-stream.js +54 -3
  193. package/src/runtime/mcp-config.js +15 -5
  194. package/src/assets/web-panel/assets/ChatBubbleRenderer-PR074hUN.js +0 -1
  195. package/src/assets/web-panel/assets/OrderTableRenderer-GlJtqLjT.js +0 -1
  196. package/src/assets/web-panel/assets/Projects-Qx_MdX9k.js +0 -1
  197. package/src/assets/web-panel/assets/Skills-Cb8jWjMX.js +0 -1
  198. package/src/assets/web-panel/assets/Tasks-CyuLnDFR.js +0 -1
  199. package/src/assets/web-panel/assets/Terminal-DwzGMniO.js +0 -3
  200. package/src/assets/web-panel/assets/devWarning-DgVKduH2.js +0 -1
  201. package/src/assets/web-panel/assets/index-DOZTSoS7.js +0 -1
  202. package/src/assets/web-panel/assets/index-DhpvjpqI.js +0 -1
@@ -12,6 +12,7 @@ import {
12
12
  readEvents,
13
13
  listJsonlSessions,
14
14
  } from "../harness/jsonl-session-store.js";
15
+ import { extractPluginUsageAttribution } from "./plugin-usage-attribution.js";
15
16
 
16
17
  // Injectable disk seams (tests override). readEvents skips malformed JSON lines
17
18
  // but does NOT guard readFileSync, so an unreadable session file (EACCES /
@@ -31,6 +32,33 @@ function toNumber(v) {
31
32
  return Number.isFinite(n) ? n : 0;
32
33
  }
33
34
 
35
+ const MAX_OBSERVED_DURATION_MS = 7 * 24 * 60 * 60 * 1000;
36
+
37
+ function observedDuration(value) {
38
+ if (value === null || value === undefined || value === "") return null;
39
+ const number = Number(value);
40
+ if (!Number.isFinite(number) || number < 0) return null;
41
+ return Math.min(MAX_OBSERVED_DURATION_MS, Math.round(number));
42
+ }
43
+
44
+ function eventDuration(event) {
45
+ return observedDuration(
46
+ event?.data?.duration_ms ??
47
+ event?.data?.durationMs ??
48
+ event?.data?.result?.toolTelemetryRecord?.durationMs,
49
+ );
50
+ }
51
+
52
+ function addObservedDuration(row, durationMs) {
53
+ if (durationMs === null) return;
54
+ row.durationMs = (row.durationMs || 0) + durationMs;
55
+ row.timedCalls = (row.timedCalls || 0) + 1;
56
+ }
57
+
58
+ function addObservedRetry(row) {
59
+ row.retries = (row.retries || 0) + 1;
60
+ }
61
+
34
62
  /**
35
63
  * Extract the optional usage-attribution frame (用量归因) persisted alongside
36
64
  * a token_usage event's data: `{ origin, skill?, subagentId?, role?,
@@ -208,6 +236,56 @@ function _isToolResultError(event) {
208
236
  );
209
237
  }
210
238
 
239
+ /**
240
+ * Aggregate compact `llm_retry` events. Each event represents one failed
241
+ * streaming attempt that the runtime actually retried and includes only a
242
+ * controlled reason plus its elapsed time — never the raw provider error.
243
+ */
244
+ export function aggregateLlmRetries(events) {
245
+ const byReason = new Map();
246
+ const byModel = new Map();
247
+ let totalRetries = 0;
248
+ let durationMs = 0;
249
+
250
+ const add = (map, key, seed, elapsed) => {
251
+ const row = map.get(key) || { ...seed, retries: 0, durationMs: 0 };
252
+ row.retries++;
253
+ row.durationMs += elapsed;
254
+ map.set(key, row);
255
+ };
256
+
257
+ for (const event of events || []) {
258
+ if (event?.type !== "llm_retry") continue;
259
+ const data = event.data || {};
260
+ const elapsed = eventDuration(event) ?? 0;
261
+ const reason =
262
+ typeof data.reason === "string" && data.reason ? data.reason : "unknown";
263
+ const provider =
264
+ typeof data.provider === "string" && data.provider ? data.provider : null;
265
+ const model =
266
+ typeof data.model === "string" && data.model ? data.model : null;
267
+ totalRetries++;
268
+ durationMs += elapsed;
269
+ add(byReason, reason, { reason }, elapsed);
270
+ add(
271
+ byModel,
272
+ `${provider || "?"}/${model || "?"}`,
273
+ { provider, model },
274
+ elapsed,
275
+ );
276
+ }
277
+
278
+ if (totalRetries === 0) return null;
279
+ const byRetriesDesc = (a, b) =>
280
+ b.retries - a.retries || b.durationMs - a.durationMs;
281
+ return {
282
+ totalRetries,
283
+ durationMs,
284
+ byReason: Array.from(byReason.values()).sort(byRetriesDesc),
285
+ byModel: Array.from(byModel.values()).sort(byRetriesDesc),
286
+ };
287
+ }
288
+
211
289
  /**
212
290
  * Aggregate the transcript's tool events: per-tool call/error counts, an MCP
213
291
  * server bucket (`mcp__<server>__*`), and a turn-associated token figure.
@@ -228,8 +306,12 @@ function _isToolResultError(event) {
228
306
  export function aggregateToolCalls(events) {
229
307
  const byTool = new Map();
230
308
  const byServer = new Map();
309
+ const byPlugin = new Map();
231
310
  let totalCalls = 0;
232
311
  let totalErrors = 0;
312
+ let totalDurationMs = 0;
313
+ let timedCalls = 0;
314
+ let retryCalls = 0;
233
315
 
234
316
  const bumpTool = (name) => {
235
317
  let t = byTool.get(name);
@@ -253,19 +335,46 @@ export function aggregateToolCalls(events) {
253
335
  }
254
336
  return s;
255
337
  };
338
+ const bumpPlugin = (plugin, version = null) => {
339
+ const key = `${plugin}@${version || "?"}`;
340
+ let row = byPlugin.get(key);
341
+ if (!row) {
342
+ row = {
343
+ plugin,
344
+ version: version || null,
345
+ calls: 0,
346
+ errors: 0,
347
+ turnTokens: 0,
348
+ };
349
+ byPlugin.set(key, row);
350
+ }
351
+ return row;
352
+ };
256
353
 
257
354
  let turnTools = new Set();
258
355
  let turnServers = new Set();
356
+ let turnPlugins = new Set();
259
357
  let turnTokens = 0;
358
+ let pendingPlugin = null;
359
+ let pendingObservation = null;
360
+ let failedToolsInTurn = new Set();
260
361
  const flushTurn = () => {
261
362
  if (turnTokens > 0) {
262
363
  for (const name of turnTools) bumpTool(name).turnTokens += turnTokens;
263
364
  for (const server of turnServers)
264
365
  bumpServer(server).turnTokens += turnTokens;
366
+ for (const key of turnPlugins) {
367
+ const row = byPlugin.get(key);
368
+ if (row) row.turnTokens += turnTokens;
369
+ }
265
370
  }
266
371
  turnTools = new Set();
267
372
  turnServers = new Set();
373
+ turnPlugins = new Set();
268
374
  turnTokens = 0;
375
+ pendingPlugin = null;
376
+ pendingObservation = null;
377
+ failedToolsInTurn = new Set();
269
378
  };
270
379
 
271
380
  for (const evt of events || []) {
@@ -276,34 +385,107 @@ export function aggregateToolCalls(events) {
276
385
  }
277
386
  if (evt.type === "tool_call") {
278
387
  const name = evt.data?.tool || "?";
388
+ const durationMs = eventDuration(evt);
389
+ const isRetry = failedToolsInTurn.has(name);
279
390
  totalCalls++;
280
391
  const t = bumpTool(name);
281
392
  t.calls++;
282
393
  const isErr = evt.data?.is_error === true;
394
+ if (durationMs !== null) {
395
+ totalDurationMs += durationMs;
396
+ timedCalls++;
397
+ addObservedDuration(t, durationMs);
398
+ }
399
+ if (isRetry) {
400
+ retryCalls++;
401
+ addObservedRetry(t);
402
+ }
283
403
  if (isErr) {
284
404
  totalErrors++;
285
405
  t.errors++;
286
406
  }
407
+ if (isErr) failedToolsInTurn.add(name);
408
+ else failedToolsInTurn.delete(name);
287
409
  turnTools.add(name);
288
410
  const server = mcpServerOf(name);
289
411
  if (server) {
290
412
  const s = bumpServer(server);
291
413
  s.calls++;
292
414
  if (isErr) s.errors++;
415
+ addObservedDuration(s, durationMs);
416
+ if (isRetry) addObservedRetry(s);
293
417
  turnServers.add(server);
294
418
  }
419
+ const plugin =
420
+ typeof evt.data?.plugin === "string" && evt.data.plugin
421
+ ? evt.data.plugin
422
+ : null;
423
+ const pluginVersion =
424
+ typeof evt.data?.plugin_version === "string"
425
+ ? evt.data.plugin_version
426
+ : null;
427
+ pendingPlugin = { tool: name, plugin, version: pluginVersion };
428
+ pendingObservation = {
429
+ tool: name,
430
+ durationCounted: durationMs !== null,
431
+ errorCounted: isErr,
432
+ };
433
+ if (plugin) {
434
+ const p = bumpPlugin(plugin, pluginVersion);
435
+ p.calls++;
436
+ if (isErr) p.errors++;
437
+ addObservedDuration(p, durationMs);
438
+ if (isRetry) addObservedRetry(p);
439
+ turnPlugins.add(`${plugin}@${pluginVersion || "?"}`);
440
+ }
295
441
  continue;
296
442
  }
297
443
  if (evt.type === "tool_result") {
298
444
  const name = evt.data?.tool || "?";
445
+ const durationMs = eventDuration(evt);
299
446
  turnTools.add(name);
300
447
  const server = mcpServerOf(name);
301
448
  if (server) turnServers.add(server);
302
- if (_isToolResultError(evt)) {
449
+ const isErr = _isToolResultError(evt);
450
+ const pairedObservation =
451
+ pendingObservation?.tool === name ? pendingObservation : null;
452
+ if (isErr && !pairedObservation?.errorCounted) {
303
453
  totalErrors++;
304
454
  bumpTool(name).errors++;
305
455
  if (server) bumpServer(server).errors++;
306
456
  }
457
+ if (isErr) failedToolsInTurn.add(name);
458
+ else failedToolsInTurn.delete(name);
459
+ if (durationMs !== null && !pairedObservation?.durationCounted) {
460
+ totalDurationMs += durationMs;
461
+ timedCalls++;
462
+ addObservedDuration(bumpTool(name), durationMs);
463
+ if (server) addObservedDuration(bumpServer(server), durationMs);
464
+ }
465
+ const extracted = extractPluginUsageAttribution(evt.data?.result);
466
+ const plugin =
467
+ typeof evt.data?.plugin === "string" && evt.data.plugin
468
+ ? evt.data.plugin
469
+ : extracted.plugin || null;
470
+ const pluginVersion =
471
+ typeof evt.data?.plugin_version === "string"
472
+ ? evt.data.plugin_version
473
+ : extracted.pluginVersion || null;
474
+ if (plugin) {
475
+ const p = bumpPlugin(plugin, pluginVersion);
476
+ const countedByCall =
477
+ pendingPlugin?.tool === name &&
478
+ pendingPlugin?.plugin === plugin &&
479
+ (pendingPlugin?.version || null) === (pluginVersion || null);
480
+ if (!countedByCall) p.calls++;
481
+ if (isErr && !countedByCall) p.errors++;
482
+ if (!countedByCall || !pairedObservation?.durationCounted) {
483
+ addObservedDuration(p, durationMs);
484
+ }
485
+ turnPlugins.add(`${plugin}@${pluginVersion || "?"}`);
486
+ }
487
+ pendingPlugin = null;
488
+ pendingObservation = null;
307
489
  continue;
308
490
  }
309
491
  const u = extractUsage(evt);
@@ -315,8 +497,11 @@ export function aggregateToolCalls(events) {
315
497
  return {
316
498
  totalCalls,
317
499
  totalErrors,
500
+ ...(timedCalls > 0 ? { totalDurationMs, timedCalls } : {}),
501
+ ...(retryCalls > 0 ? { retryCalls } : {}),
318
502
  byTool: Array.from(byTool.values()).sort(byCallsDesc),
319
503
  byMcpServer: Array.from(byServer.values()).sort(byCallsDesc),
504
+ byPlugin: Array.from(byPlugin.values()).sort(byCallsDesc),
320
505
  };
321
506
  }
322
507
 
@@ -325,9 +510,11 @@ export function aggregateToolCalls(events) {
325
510
  * roll-ups + the tool/MCP call aggregation. Pure.
326
511
  */
327
512
  export function sessionAttribution(events) {
513
+ const retries = aggregateLlmRetries(events);
328
514
  return {
329
515
  ...aggregateUsageAttribution(events),
330
516
  tools: aggregateToolCalls(events),
517
+ ...(retries ? { retries } : {}),
331
518
  };
332
519
  }
333
520
 
@@ -457,8 +644,16 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
457
644
  const gSubagent = new Map();
458
645
  const gTool = new Map();
459
646
  const gServer = new Map();
647
+ const gPlugin = new Map();
648
+ const gRetryReason = new Map();
649
+ const gRetryModel = new Map();
460
650
  let gToolCalls = 0;
461
651
  let gToolErrors = 0;
652
+ let gToolDurationMs = 0;
653
+ let gTimedToolCalls = 0;
654
+ let gToolRetryCalls = 0;
655
+ let gLlmRetries = 0;
656
+ let gLlmRetryDurationMs = 0;
462
657
  const mergeRow = (map, key, row, seed) => {
463
658
  const entry = map.get(key) || { ...seed, ..._newSums() };
464
659
  _addSums(entry, row);
@@ -483,6 +678,9 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
483
678
  const tools = a.tools || {};
484
679
  gToolCalls += tools.totalCalls || 0;
485
680
  gToolErrors += tools.totalErrors || 0;
681
+ gToolDurationMs += tools.totalDurationMs || 0;
682
+ gTimedToolCalls += tools.timedCalls || 0;
683
+ gToolRetryCalls += tools.retryCalls || 0;
486
684
  for (const row of tools.byTool || []) {
487
685
  const entry = gTool.get(row.tool) || {
488
686
  tool: row.tool,
@@ -494,6 +692,12 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
494
692
  entry.calls += row.calls || 0;
495
693
  entry.errors += row.errors || 0;
496
694
  entry.turnTokens += row.turnTokens || 0;
695
+ if (row.durationMs !== undefined) {
696
+ entry.durationMs = (entry.durationMs || 0) + (row.durationMs || 0);
697
+ entry.timedCalls = (entry.timedCalls || 0) + (row.timedCalls || 0);
698
+ }
699
+ if (row.retries !== undefined)
700
+ entry.retries = (entry.retries || 0) + (row.retries || 0);
497
701
  gTool.set(row.tool, entry);
498
702
  }
499
703
  for (const row of tools.byMcpServer || []) {
@@ -506,10 +710,65 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
506
710
  entry.calls += row.calls || 0;
507
711
  entry.errors += row.errors || 0;
508
712
  entry.turnTokens += row.turnTokens || 0;
713
+ if (row.durationMs !== undefined) {
714
+ entry.durationMs = (entry.durationMs || 0) + (row.durationMs || 0);
715
+ entry.timedCalls = (entry.timedCalls || 0) + (row.timedCalls || 0);
716
+ }
717
+ if (row.retries !== undefined)
718
+ entry.retries = (entry.retries || 0) + (row.retries || 0);
509
719
  gServer.set(row.server, entry);
510
720
  }
721
+ for (const row of tools.byPlugin || []) {
722
+ const key = `${row.plugin}@${row.version || "?"}`;
723
+ const entry = gPlugin.get(key) || {
724
+ plugin: row.plugin,
725
+ version: row.version || null,
726
+ calls: 0,
727
+ errors: 0,
728
+ turnTokens: 0,
729
+ };
730
+ entry.calls += row.calls || 0;
731
+ entry.errors += row.errors || 0;
732
+ entry.turnTokens += row.turnTokens || 0;
733
+ if (row.durationMs !== undefined) {
734
+ entry.durationMs = (entry.durationMs || 0) + (row.durationMs || 0);
735
+ entry.timedCalls = (entry.timedCalls || 0) + (row.timedCalls || 0);
736
+ }
737
+ if (row.retries !== undefined)
738
+ entry.retries = (entry.retries || 0) + (row.retries || 0);
739
+ gPlugin.set(key, entry);
740
+ }
741
+ const retries = a.retries || null;
742
+ if (retries) {
743
+ gLlmRetries += retries.totalRetries || 0;
744
+ gLlmRetryDurationMs += retries.durationMs || 0;
745
+ for (const row of retries.byReason || []) {
746
+ const entry = gRetryReason.get(row.reason) || {
747
+ reason: row.reason,
748
+ retries: 0,
749
+ durationMs: 0,
750
+ };
751
+ entry.retries += row.retries || 0;
752
+ entry.durationMs += row.durationMs || 0;
753
+ gRetryReason.set(row.reason, entry);
754
+ }
755
+ for (const row of retries.byModel || []) {
756
+ const key = `${row.provider || "?"}/${row.model || "?"}`;
757
+ const entry = gRetryModel.get(key) || {
758
+ provider: row.provider || null,
759
+ model: row.model || null,
760
+ retries: 0,
761
+ durationMs: 0,
762
+ };
763
+ entry.retries += row.retries || 0;
764
+ entry.durationMs += row.durationMs || 0;
765
+ gRetryModel.set(key, entry);
766
+ }
767
+ }
511
768
  }
512
769
  const byCallsDesc = (a, b) => b.calls - a.calls;
770
+ const byRetriesDesc = (a, b) =>
771
+ b.retries - a.retries || b.durationMs - a.durationMs;
513
772
 
514
773
  return {
515
774
  sessions: perSession,
@@ -525,9 +784,27 @@ export function allSessionsUsage({ limit = 1000 } = {}) {
525
784
  tools: {
526
785
  totalCalls: gToolCalls,
527
786
  totalErrors: gToolErrors,
787
+ ...(gTimedToolCalls > 0
788
+ ? {
789
+ totalDurationMs: gToolDurationMs,
790
+ timedCalls: gTimedToolCalls,
791
+ }
792
+ : {}),
793
+ ...(gToolRetryCalls > 0 ? { retryCalls: gToolRetryCalls } : {}),
528
794
  byTool: Array.from(gTool.values()).sort(byCallsDesc),
529
795
  byMcpServer: Array.from(gServer.values()).sort(byCallsDesc),
796
+ byPlugin: Array.from(gPlugin.values()).sort(byCallsDesc),
530
797
  },
798
+ ...(gLlmRetries > 0
799
+ ? {
800
+ retries: {
801
+ totalRetries: gLlmRetries,
802
+ durationMs: gLlmRetryDurationMs,
803
+ byReason: Array.from(gRetryReason.values()).sort(byRetriesDesc),
804
+ byModel: Array.from(gRetryModel.values()).sort(byRetriesDesc),
805
+ },
806
+ }
807
+ : {}),
531
808
  },
532
809
  };
533
810
  }
@@ -81,3 +81,32 @@ export function isRetryableStreamError(err, signal) {
81
81
  msg,
82
82
  );
83
83
  }
84
+
85
+ /**
86
+ * Reduce a retryable stream failure to a small, secret-free reason vocabulary
87
+ * suitable for persisted usage telemetry. Never persist the provider's raw
88
+ * error message: it can contain a request URL, tenant id, or proxy details.
89
+ */
90
+ export function classifyStreamRetryReason(err) {
91
+ const code = String(err?.code || err?.cause?.code || "").toUpperCase();
92
+ if (code === "ETIMEDOUT") return "timeout";
93
+ if (["EAI_AGAIN", "ENOTFOUND"].includes(code)) return "dns";
94
+ if (code === "ECONNREFUSED") return "connection_refused";
95
+ if (code === "ENETUNREACH") return "network_unreachable";
96
+ if (["ECONNRESET", "EPIPE", "UND_ERR_SOCKET"].includes(code)) {
97
+ return "connection_reset";
98
+ }
99
+ const msg = String(err?.message || err || "").toLowerCase();
100
+ if (/timed?\s*out|etimedout/.test(msg)) return "timeout";
101
+ if (/eai_again|enotfound|dns/.test(msg)) return "dns";
102
+ if (/econnrefused/.test(msg)) return "connection_refused";
103
+ if (/network unreachable|enetunreach/.test(msg)) return "network_unreachable";
104
+ if (
105
+ /econnreset|socket hang ?up|terminated|fetch failed|network error|premature close/.test(
106
+ msg,
107
+ )
108
+ ) {
109
+ return "connection_reset";
110
+ }
111
+ return "unknown";
112
+ }
@@ -48,10 +48,12 @@ import {
48
48
  appendCompactEvent,
49
49
  appendTokenUsage,
50
50
  appendToolCallCompact,
51
+ appendLlmRetryCompact,
51
52
  appendEvent,
52
53
  rebuildMessages,
53
54
  sessionExists,
54
55
  } from "../harness/jsonl-session-store.js";
56
+ import { classifyStreamRetryReason } from "../lib/stream-retry.js";
55
57
  import { CLISkillLoader } from "../lib/skill-loader.js";
56
58
  import { storeMemory, consolidateMemory } from "../lib/hierarchical-memory.js";
57
59
  import { CLIContextEngineering } from "../lib/cli-context-engineering.js";
@@ -108,6 +110,7 @@ import {
108
110
  import { resolveSlashMacro } from "./slash-macro.js";
109
111
  import { expandMcpPrompt, renderMcpSurface } from "./mcp-prompt.js";
110
112
  import { newCostStore, addUsage } from "./session-cost.js";
113
+ import { extractPluginUsageAttribution } from "../lib/plugin-usage-attribution.js";
111
114
  import { parseThinkCommand, parseEffortCommand } from "./think-command.js";
112
115
  import { parseBtwCommand, buildAsideBlock, applyAside } from "./btw-command.js";
113
116
  import { shouldStreamLive } from "./stream-decision.js";
@@ -352,17 +355,29 @@ export async function agentLoop(messages, options) {
352
355
  chalk.gray(` ⎌ checkpoint ${event.id} (before ${event.tool})\n`),
353
356
  );
354
357
  } else if (event.type === "tool-executing") {
355
- _lastExec = { tool: event.tool, args: event.args };
358
+ _lastExec = {
359
+ tool: event.tool,
360
+ args: event.args,
361
+ startedAt: options.now ? options.now() : Date.now(),
362
+ };
356
363
  process.stdout.write(
357
364
  chalk.gray(
358
365
  ` [${event.tool}] ${formatToolArgs(event.tool, event.args)}\n`,
359
366
  ),
360
367
  );
361
368
  } else if (event.type === "tool-result") {
369
+ const durationMs =
370
+ event.result?.toolTelemetryRecord?.durationMs ??
371
+ (_lastExec?.tool === event.tool
372
+ ? Math.max(
373
+ 0,
374
+ (options.now ? options.now() : Date.now()) - _lastExec.startedAt,
375
+ )
376
+ : undefined);
362
377
  // 用量归因: persist a compact tool_call record (name + error flag +
363
- // skill hint — never args, which can carry whole file bodies) so
364
- // `cc session usage --by tool|mcp` and `cc insights` can aggregate
365
- // tool use for REPL sessions. Best-effort; opt out with
378
+ // skill/plugin hints — never args, which can carry whole file bodies)
379
+ // so `cc session usage --by tool|mcp|plugin` and `cc insights` can
380
+ // aggregate tool use for REPL sessions. Best-effort; opt out with
366
381
  // options.persistToolCalls === false.
367
382
  if (options.sessionId && options.persistToolCalls !== false) {
368
383
  try {
@@ -373,6 +388,8 @@ export async function agentLoop(messages, options) {
373
388
  event.tool === "run_skill" && _lastExec?.tool === "run_skill"
374
389
  ? _lastExec.args?.skill_name
375
390
  : undefined,
391
+ ...extractPluginUsageAttribution(event.result),
392
+ durationMs,
376
393
  });
377
394
  } catch (_e) {
378
395
  // persistence is best-effort — never break the turn
@@ -1844,9 +1861,7 @@ export async function startAgentRepl(options = {}) {
1844
1861
  " Open this secure page? [y/N] ",
1845
1862
  ),
1846
1863
  async (answer) => {
1847
- const approved = /^(y|yes)$/i.test(
1848
- String(answer || "").trim(),
1849
- );
1864
+ const approved = /^(y|yes)$/i.test(String(answer || "").trim());
1850
1865
  if (!approved) {
1851
1866
  resolve({ action: "decline" });
1852
1867
  rl.prompt();
@@ -5570,12 +5585,26 @@ export async function startAgentRepl(options = {}) {
5570
5585
  // streaming call hits a transient connection drop and retries, tell the
5571
5586
  // user instead of leaving them staring at a silent pause. To stderr so
5572
5587
  // it never corrupts the streamed answer on stdout.
5573
- onStreamRetry: (attempt) =>
5588
+ onStreamRetry: (attempt, error, telemetry = {}) => {
5574
5589
  process.stderr.write(
5575
5590
  chalk.dim(
5576
5591
  ` ⟳ connection dropped — retrying (attempt ${attempt})…\n`,
5577
5592
  ),
5578
- ),
5593
+ );
5594
+ if (useJsonl && sessionId) {
5595
+ try {
5596
+ appendLlmRetryCompact(sessionId, {
5597
+ attempt,
5598
+ durationMs: telemetry.durationMs,
5599
+ provider: telemetry.provider || provider,
5600
+ model: telemetry.model || activeModel,
5601
+ reason: classifyStreamRetryReason(error),
5602
+ });
5603
+ } catch {
5604
+ /* usage telemetry is best-effort */
5605
+ }
5606
+ }
5607
+ },
5579
5608
  // Stream-stall hint (Claude-Code 2.1.185): the connection is alive but
5580
5609
  // the API has gone silent mid-response — tell the user we're still
5581
5610
  // waiting instead of leaving a frozen spinner. stderr so it never
@@ -6742,7 +6742,15 @@ export async function chatWithTools(rawMessages, options) {
6742
6742
  options.streamStallMs,
6743
6743
  options.streamStallTimeoutMs,
6744
6744
  ),
6745
- { signal, onRetry: options.onStreamRetry },
6745
+ {
6746
+ signal,
6747
+ onRetry: (attempt, error, telemetry) =>
6748
+ options.onStreamRetry?.(attempt, error, {
6749
+ ...telemetry,
6750
+ provider: "ollama",
6751
+ model: model || null,
6752
+ }),
6753
+ },
6746
6754
  );
6747
6755
  }
6748
6756
  const response = await fetch(apiUrl, {
@@ -6868,7 +6876,15 @@ export async function chatWithTools(rawMessages, options) {
6868
6876
  options.streamStallMs,
6869
6877
  options.streamStallTimeoutMs,
6870
6878
  ),
6871
- { signal, onRetry: options.onStreamRetry },
6879
+ {
6880
+ signal,
6881
+ onRetry: (attempt, error, telemetry) =>
6882
+ options.onStreamRetry?.(attempt, error, {
6883
+ ...telemetry,
6884
+ provider: "anthropic",
6885
+ model: effModel,
6886
+ }),
6887
+ },
6872
6888
  );
6873
6889
  }
6874
6890
 
@@ -6972,7 +6988,15 @@ export async function chatWithTools(rawMessages, options) {
6972
6988
  options.streamStallMs,
6973
6989
  options.streamStallTimeoutMs,
6974
6990
  ),
6975
- { signal, onRetry: options.onStreamRetry },
6991
+ {
6992
+ signal,
6993
+ onRetry: (attempt, error, telemetry) =>
6994
+ options.onStreamRetry?.(attempt, error, {
6995
+ ...telemetry,
6996
+ provider,
6997
+ model: model || defaultModels[provider] || "gpt-4o-mini",
6998
+ }),
6999
+ },
6976
7000
  );
6977
7001
  }
6978
7002
 
@@ -7171,8 +7195,10 @@ export async function _retryStreamingChat(streamFn, opts = {}) {
7171
7195
  const base = opts.baseDelayMs ?? STREAM_RETRY_BASE_MS;
7172
7196
  const signal = opts.signal;
7173
7197
  const sleep = opts.sleep || ((ms) => new Promise((r) => setTimeout(r, ms)));
7198
+ const now = typeof opts.now === "function" ? opts.now : Date.now;
7174
7199
  let attempt = 0;
7175
7200
  for (;;) {
7201
+ const attemptStartedAt = now();
7176
7202
  try {
7177
7203
  return await streamFn();
7178
7204
  } catch (err) {
@@ -7181,7 +7207,9 @@ export async function _retryStreamingChat(streamFn, opts = {}) {
7181
7207
  attempt++;
7182
7208
  if (typeof opts.onRetry === "function") {
7183
7209
  try {
7184
- opts.onRetry(attempt, err);
7210
+ opts.onRetry(attempt, err, {
7211
+ durationMs: Math.max(0, now() - attemptStartedAt),
7212
+ });
7185
7213
  } catch {
7186
7214
  /* the retry notice is best-effort; never let it mask the retry */
7187
7215
  }