@ironbee-ai/cli 0.15.0 → 0.16.0

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 (454) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/analytics/{emit.d.ts → claude/emit.d.ts} +1 -1
  3. package/dist/analytics/claude/emit.d.ts.map +1 -0
  4. package/dist/analytics/{emit.js → claude/emit.js} +34 -7
  5. package/dist/analytics/claude/emit.js.map +1 -0
  6. package/dist/analytics/{hook-trigger.d.ts → claude/hook-trigger.d.ts} +1 -1
  7. package/dist/analytics/claude/hook-trigger.d.ts.map +1 -0
  8. package/dist/analytics/{hook-trigger.js → claude/hook-trigger.js} +2 -2
  9. package/dist/analytics/claude/hook-trigger.js.map +1 -0
  10. package/dist/analytics/claude/log.d.ts.map +1 -0
  11. package/dist/analytics/{log.js → claude/log.js} +1 -1
  12. package/dist/analytics/claude/log.js.map +1 -0
  13. package/dist/analytics/{merge.d.ts → claude/merge.d.ts} +2 -1
  14. package/dist/analytics/claude/merge.d.ts.map +1 -0
  15. package/dist/analytics/{merge.js → claude/merge.js} +13 -1
  16. package/dist/analytics/claude/merge.js.map +1 -0
  17. package/dist/analytics/{pricing.d.ts → claude/pricing.d.ts} +1 -13
  18. package/dist/analytics/claude/pricing.d.ts.map +1 -0
  19. package/dist/analytics/{pricing.js → claude/pricing.js} +6 -14
  20. package/dist/analytics/claude/pricing.js.map +1 -0
  21. package/dist/analytics/{projection.d.ts → claude/projection.d.ts} +31 -7
  22. package/dist/analytics/claude/projection.d.ts.map +1 -0
  23. package/dist/analytics/{projection.js → claude/projection.js} +631 -327
  24. package/dist/analytics/claude/projection.js.map +1 -0
  25. package/dist/analytics/{spawn.d.ts → claude/spawn.d.ts} +4 -4
  26. package/dist/analytics/claude/spawn.d.ts.map +1 -0
  27. package/dist/analytics/{spawn.js → claude/spawn.js} +4 -3
  28. package/dist/analytics/claude/spawn.js.map +1 -0
  29. package/dist/analytics/{state.d.ts → claude/state.d.ts} +1 -1
  30. package/dist/analytics/claude/state.d.ts.map +1 -0
  31. package/dist/analytics/{state.js → claude/state.js} +2 -2
  32. package/dist/analytics/claude/state.js.map +1 -0
  33. package/dist/analytics/claude/transcript.d.ts.map +1 -0
  34. package/dist/analytics/{transcript.js → claude/transcript.js} +1 -1
  35. package/dist/analytics/claude/transcript.js.map +1 -0
  36. package/dist/analytics/codex/api-request.d.ts +108 -0
  37. package/dist/analytics/codex/api-request.d.ts.map +1 -0
  38. package/dist/analytics/codex/api-request.js +155 -0
  39. package/dist/analytics/codex/api-request.js.map +1 -0
  40. package/dist/analytics/codex/apply-patch.d.ts +21 -0
  41. package/dist/analytics/codex/apply-patch.d.ts.map +1 -0
  42. package/dist/analytics/codex/apply-patch.js +49 -0
  43. package/dist/analytics/codex/apply-patch.js.map +1 -0
  44. package/dist/analytics/codex/classifier.d.ts +28 -0
  45. package/dist/analytics/codex/classifier.d.ts.map +1 -0
  46. package/dist/analytics/codex/classifier.js +111 -0
  47. package/dist/analytics/codex/classifier.js.map +1 -0
  48. package/dist/analytics/codex/emit.d.ts +47 -0
  49. package/dist/analytics/codex/emit.d.ts.map +1 -0
  50. package/dist/analytics/codex/emit.js +158 -0
  51. package/dist/analytics/codex/emit.js.map +1 -0
  52. package/dist/analytics/codex/events-emit.d.ts +62 -0
  53. package/dist/analytics/codex/events-emit.d.ts.map +1 -0
  54. package/dist/analytics/codex/events-emit.js +555 -0
  55. package/dist/analytics/codex/events-emit.js.map +1 -0
  56. package/dist/analytics/codex/pricing.d.ts +57 -0
  57. package/dist/analytics/codex/pricing.d.ts.map +1 -0
  58. package/dist/analytics/codex/pricing.js +125 -0
  59. package/dist/analytics/codex/pricing.js.map +1 -0
  60. package/dist/analytics/codex/projection.d.ts +51 -0
  61. package/dist/analytics/codex/projection.d.ts.map +1 -0
  62. package/dist/analytics/codex/projection.js +1477 -0
  63. package/dist/analytics/codex/projection.js.map +1 -0
  64. package/dist/analytics/codex/spawn.d.ts +27 -0
  65. package/dist/analytics/codex/spawn.d.ts.map +1 -0
  66. package/dist/analytics/codex/spawn.js +64 -0
  67. package/dist/analytics/codex/spawn.js.map +1 -0
  68. package/dist/analytics/codex/status-snapshot.d.ts +80 -0
  69. package/dist/analytics/codex/status-snapshot.d.ts.map +1 -0
  70. package/dist/analytics/codex/status-snapshot.js +206 -0
  71. package/dist/analytics/codex/status-snapshot.js.map +1 -0
  72. package/dist/analytics/codex/transcript.d.ts +51 -0
  73. package/dist/analytics/codex/transcript.d.ts.map +1 -0
  74. package/dist/analytics/codex/transcript.js +134 -0
  75. package/dist/analytics/codex/transcript.js.map +1 -0
  76. package/dist/analytics/codex/types.d.ts +253 -0
  77. package/dist/analytics/codex/types.d.ts.map +1 -0
  78. package/dist/analytics/codex/types.js +29 -0
  79. package/dist/analytics/codex/types.js.map +1 -0
  80. package/dist/analytics/shared/classifier.d.ts.map +1 -0
  81. package/dist/analytics/{classifier.js → shared/classifier.js} +9 -0
  82. package/dist/analytics/shared/classifier.js.map +1 -0
  83. package/dist/analytics/shared/errors.d.ts.map +1 -0
  84. package/dist/analytics/shared/errors.js.map +1 -0
  85. package/dist/analytics/shared/tokens.d.ts +14 -0
  86. package/dist/analytics/shared/tokens.d.ts.map +1 -0
  87. package/dist/analytics/shared/tokens.js +17 -0
  88. package/dist/analytics/shared/tokens.js.map +1 -0
  89. package/dist/analytics/{types.d.ts → shared/types.d.ts} +42 -9
  90. package/dist/analytics/shared/types.d.ts.map +1 -0
  91. package/dist/analytics/shared/types.js.map +1 -0
  92. package/dist/clients/base.d.ts +9 -0
  93. package/dist/clients/base.d.ts.map +1 -1
  94. package/dist/clients/claude/hooks/activity-end.js +1 -1
  95. package/dist/clients/claude/hooks/activity-end.js.map +1 -1
  96. package/dist/clients/claude/hooks/activity-start.js +1 -1
  97. package/dist/clients/claude/hooks/activity-start.js.map +1 -1
  98. package/dist/clients/claude/hooks/clear-verdict.d.ts.map +1 -1
  99. package/dist/clients/claude/hooks/clear-verdict.js +14 -0
  100. package/dist/clients/claude/hooks/clear-verdict.js.map +1 -1
  101. package/dist/clients/claude/hooks/session-end.d.ts.map +1 -1
  102. package/dist/clients/claude/hooks/session-end.js +7 -1
  103. package/dist/clients/claude/hooks/session-end.js.map +1 -1
  104. package/dist/clients/claude/hooks/session-start.d.ts.map +1 -1
  105. package/dist/clients/claude/hooks/session-start.js +7 -1
  106. package/dist/clients/claude/hooks/session-start.js.map +1 -1
  107. package/dist/clients/claude/hooks/session-status.d.ts.map +1 -1
  108. package/dist/clients/claude/hooks/session-status.js +13 -9
  109. package/dist/clients/claude/hooks/session-status.js.map +1 -1
  110. package/dist/clients/claude/hooks/track-action.d.ts.map +1 -1
  111. package/dist/clients/claude/hooks/track-action.js +26 -1
  112. package/dist/clients/claude/hooks/track-action.js.map +1 -1
  113. package/dist/clients/claude/hooks/verify-gate.d.ts.map +1 -1
  114. package/dist/clients/claude/hooks/verify-gate.js +8 -1
  115. package/dist/clients/claude/hooks/verify-gate.js.map +1 -1
  116. package/dist/clients/claude/index.d.ts +1 -0
  117. package/dist/clients/claude/index.d.ts.map +1 -1
  118. package/dist/clients/claude/index.js +7 -0
  119. package/dist/clients/claude/index.js.map +1 -1
  120. package/dist/clients/claude/util.d.ts.map +1 -1
  121. package/dist/clients/claude/util.js +55 -0
  122. package/dist/clients/claude/util.js.map +1 -1
  123. package/dist/clients/codex/commands/ironbee-verify/SKILL.md +58 -0
  124. package/dist/clients/codex/hooks/activity-end.d.ts +9 -0
  125. package/dist/clients/codex/hooks/activity-end.d.ts.map +1 -0
  126. package/dist/clients/codex/hooks/activity-end.js +65 -0
  127. package/dist/clients/codex/hooks/activity-end.js.map +1 -0
  128. package/dist/clients/codex/hooks/activity-start.d.ts +17 -0
  129. package/dist/clients/codex/hooks/activity-start.d.ts.map +1 -0
  130. package/dist/clients/codex/hooks/activity-start.js +38 -0
  131. package/dist/clients/codex/hooks/activity-start.js.map +1 -0
  132. package/dist/clients/codex/hooks/clear-verdict.d.ts +55 -0
  133. package/dist/clients/codex/hooks/clear-verdict.d.ts.map +1 -0
  134. package/dist/clients/codex/hooks/clear-verdict.js +299 -0
  135. package/dist/clients/codex/hooks/clear-verdict.js.map +1 -0
  136. package/dist/clients/codex/hooks/require-verdict.d.ts +30 -0
  137. package/dist/clients/codex/hooks/require-verdict.d.ts.map +1 -0
  138. package/dist/clients/codex/hooks/require-verdict.js +109 -0
  139. package/dist/clients/codex/hooks/require-verdict.js.map +1 -0
  140. package/dist/clients/codex/hooks/require-verification.d.ts +12 -0
  141. package/dist/clients/codex/hooks/require-verification.d.ts.map +1 -0
  142. package/dist/clients/codex/hooks/require-verification.js +136 -0
  143. package/dist/clients/codex/hooks/require-verification.js.map +1 -0
  144. package/dist/clients/codex/hooks/session-start.d.ts +10 -0
  145. package/dist/clients/codex/hooks/session-start.d.ts.map +1 -0
  146. package/dist/clients/codex/hooks/session-start.js +94 -0
  147. package/dist/clients/codex/hooks/session-start.js.map +1 -0
  148. package/dist/clients/codex/hooks/track-action-monitor.d.ts +10 -0
  149. package/dist/clients/codex/hooks/track-action-monitor.d.ts.map +1 -0
  150. package/dist/clients/codex/hooks/track-action-monitor.js +168 -0
  151. package/dist/clients/codex/hooks/track-action-monitor.js.map +1 -0
  152. package/dist/clients/codex/hooks/track-action-pre.d.ts +18 -0
  153. package/dist/clients/codex/hooks/track-action-pre.d.ts.map +1 -0
  154. package/dist/clients/codex/hooks/track-action-pre.js +35 -0
  155. package/dist/clients/codex/hooks/track-action-pre.js.map +1 -0
  156. package/dist/clients/codex/hooks/track-action.d.ts +22 -0
  157. package/dist/clients/codex/hooks/track-action.d.ts.map +1 -0
  158. package/dist/clients/codex/hooks/track-action.js +350 -0
  159. package/dist/clients/codex/hooks/track-action.js.map +1 -0
  160. package/dist/clients/codex/hooks/verify-gate.d.ts +15 -0
  161. package/dist/clients/codex/hooks/verify-gate.d.ts.map +1 -0
  162. package/dist/clients/codex/hooks/verify-gate.js +105 -0
  163. package/dist/clients/codex/hooks/verify-gate.js.map +1 -0
  164. package/dist/clients/codex/index.d.ts +42 -0
  165. package/dist/clients/codex/index.d.ts.map +1 -0
  166. package/dist/clients/codex/index.js +427 -0
  167. package/dist/clients/codex/index.js.map +1 -0
  168. package/dist/clients/codex/platforms/command-verify.backend.md +108 -0
  169. package/dist/clients/codex/platforms/command-verify.browser.md +108 -0
  170. package/dist/clients/codex/platforms/command-verify.node.md +61 -0
  171. package/dist/clients/codex/platforms/rule.backend.md +32 -0
  172. package/dist/clients/codex/platforms/rule.browser.md +17 -0
  173. package/dist/clients/codex/platforms/rule.node.md +28 -0
  174. package/dist/clients/codex/platforms/skill.backend.md +95 -0
  175. package/dist/clients/codex/platforms/skill.browser.md +28 -0
  176. package/dist/clients/codex/platforms/skill.node.md +62 -0
  177. package/dist/clients/codex/rules/ironbee-verification.md +48 -0
  178. package/dist/clients/codex/skills/ironbee-verification.md +80 -0
  179. package/dist/clients/codex/util.d.ts +193 -0
  180. package/dist/clients/codex/util.d.ts.map +1 -0
  181. package/dist/clients/codex/util.js +784 -0
  182. package/dist/clients/codex/util.js.map +1 -0
  183. package/dist/clients/cursor/hooks/activity-end.js +1 -1
  184. package/dist/clients/cursor/hooks/activity-end.js.map +1 -1
  185. package/dist/clients/cursor/hooks/clear-verdict.d.ts +5 -2
  186. package/dist/clients/cursor/hooks/clear-verdict.d.ts.map +1 -1
  187. package/dist/clients/cursor/hooks/clear-verdict.js +12 -3
  188. package/dist/clients/cursor/hooks/clear-verdict.js.map +1 -1
  189. package/dist/clients/cursor/hooks/session-end.js +1 -1
  190. package/dist/clients/cursor/hooks/session-end.js.map +1 -1
  191. package/dist/clients/cursor/hooks/verify-gate.d.ts.map +1 -1
  192. package/dist/clients/cursor/hooks/verify-gate.js +6 -1
  193. package/dist/clients/cursor/hooks/verify-gate.js.map +1 -1
  194. package/dist/clients/cursor/index.d.ts +1 -0
  195. package/dist/clients/cursor/index.d.ts.map +1 -1
  196. package/dist/clients/cursor/index.js +13 -0
  197. package/dist/clients/cursor/index.js.map +1 -1
  198. package/dist/clients/registry.d.ts.map +1 -1
  199. package/dist/clients/registry.js +2 -1
  200. package/dist/clients/registry.js.map +1 -1
  201. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  202. package/dist/commands/claude/index.d.ts.map +1 -0
  203. package/dist/commands/{claude.js → claude/index.js} +12 -6
  204. package/dist/commands/claude/index.js.map +1 -0
  205. package/dist/commands/{otel.d.ts → claude/otel.d.ts} +5 -1
  206. package/dist/commands/claude/otel.d.ts.map +1 -0
  207. package/dist/commands/{otel.js → claude/otel.js} +9 -5
  208. package/dist/commands/claude/otel.js.map +1 -0
  209. package/dist/commands/claude/process-analytics.d.ts +19 -0
  210. package/dist/commands/claude/process-analytics.d.ts.map +1 -0
  211. package/dist/commands/{process-analytics.js → claude/process-analytics.js} +16 -15
  212. package/dist/commands/claude/process-analytics.js.map +1 -0
  213. package/dist/commands/{statusline-toggle.d.ts → claude/statusline-toggle.d.ts} +2 -2
  214. package/dist/commands/claude/statusline-toggle.d.ts.map +1 -0
  215. package/dist/commands/{statusline-toggle.js → claude/statusline-toggle.js} +8 -8
  216. package/dist/commands/claude/statusline-toggle.js.map +1 -0
  217. package/dist/commands/{statusline.d.ts → claude/statusline.d.ts} +1 -1
  218. package/dist/commands/claude/statusline.d.ts.map +1 -0
  219. package/dist/commands/{statusline.js → claude/statusline.js} +4 -4
  220. package/dist/commands/claude/statusline.js.map +1 -0
  221. package/dist/commands/codex/index.d.ts +11 -0
  222. package/dist/commands/codex/index.d.ts.map +1 -0
  223. package/dist/commands/codex/index.js +17 -0
  224. package/dist/commands/codex/index.js.map +1 -0
  225. package/dist/commands/codex/process-analytics.d.ts +14 -0
  226. package/dist/commands/codex/process-analytics.d.ts.map +1 -0
  227. package/dist/commands/codex/process-analytics.js +111 -0
  228. package/dist/commands/codex/process-analytics.js.map +1 -0
  229. package/dist/commands/hook.js +12 -0
  230. package/dist/commands/hook.js.map +1 -1
  231. package/dist/commands/import.js +3 -3
  232. package/dist/commands/import.js.map +1 -1
  233. package/dist/commands/queue.js +3 -1
  234. package/dist/commands/queue.js.map +1 -1
  235. package/dist/hooks/core/actions.d.ts +17 -1
  236. package/dist/hooks/core/actions.d.ts.map +1 -1
  237. package/dist/hooks/core/actions.js +13 -0
  238. package/dist/hooks/core/actions.js.map +1 -1
  239. package/dist/hooks/core/activity-end.d.ts.map +1 -1
  240. package/dist/hooks/core/activity-end.js +4 -0
  241. package/dist/hooks/core/activity-end.js.map +1 -1
  242. package/dist/hooks/core/session-state.d.ts +15 -1
  243. package/dist/hooks/core/session-state.d.ts.map +1 -1
  244. package/dist/hooks/core/session-state.js +102 -7
  245. package/dist/hooks/core/session-state.js.map +1 -1
  246. package/dist/import/claude/analytics-runner.d.ts +1 -1
  247. package/dist/import/claude/analytics-runner.d.ts.map +1 -1
  248. package/dist/import/claude/analytics-runner.js +5 -5
  249. package/dist/import/claude/analytics-runner.js.map +1 -1
  250. package/dist/import/claude/auth-mode.d.ts +1 -1
  251. package/dist/import/claude/auth-mode.d.ts.map +1 -1
  252. package/dist/import/claude/discovery.js +1 -1
  253. package/dist/import/claude/discovery.js.map +1 -1
  254. package/dist/import/claude/encoding.js +1 -1
  255. package/dist/import/claude/encoding.js.map +1 -1
  256. package/dist/import/claude/events/file-change.d.ts +10 -1
  257. package/dist/import/claude/events/file-change.d.ts.map +1 -1
  258. package/dist/import/claude/events/file-change.js +79 -5
  259. package/dist/import/claude/events/file-change.js.map +1 -1
  260. package/dist/import/claude/events/tool-call.d.ts +16 -1
  261. package/dist/import/claude/events/tool-call.d.ts.map +1 -1
  262. package/dist/import/claude/events/tool-call.js +122 -15
  263. package/dist/import/claude/events/tool-call.js.map +1 -1
  264. package/dist/import/claude/runner.d.ts.map +1 -1
  265. package/dist/import/claude/runner.js +45 -3
  266. package/dist/import/claude/runner.js.map +1 -1
  267. package/dist/import/claude/summary.js +1 -1
  268. package/dist/import/claude/summary.js.map +1 -1
  269. package/dist/import/claude/transcript-walk.d.ts +1 -1
  270. package/dist/import/claude/transcript-walk.d.ts.map +1 -1
  271. package/dist/import/claude/transcript-walk.js +11 -4
  272. package/dist/import/claude/transcript-walk.js.map +1 -1
  273. package/dist/import/codex/analytics-runner.d.ts +46 -0
  274. package/dist/import/codex/analytics-runner.d.ts.map +1 -0
  275. package/dist/import/codex/analytics-runner.js +116 -0
  276. package/dist/import/codex/analytics-runner.js.map +1 -0
  277. package/dist/import/codex/discovery.d.ts +33 -0
  278. package/dist/import/codex/discovery.d.ts.map +1 -0
  279. package/dist/import/codex/discovery.js +202 -0
  280. package/dist/import/codex/discovery.js.map +1 -0
  281. package/dist/import/codex/events/file-change.d.ts +42 -0
  282. package/dist/import/codex/events/file-change.d.ts.map +1 -0
  283. package/dist/import/codex/events/file-change.js +125 -0
  284. package/dist/import/codex/events/file-change.js.map +1 -0
  285. package/dist/import/codex/events/tool-call.d.ts +49 -0
  286. package/dist/import/codex/events/tool-call.d.ts.map +1 -0
  287. package/dist/import/codex/events/tool-call.js +151 -0
  288. package/dist/import/codex/events/tool-call.js.map +1 -0
  289. package/dist/import/codex/runner.d.ts +34 -0
  290. package/dist/import/codex/runner.d.ts.map +1 -0
  291. package/dist/import/codex/runner.js +456 -0
  292. package/dist/import/codex/runner.js.map +1 -0
  293. package/dist/import/codex/summary.d.ts +20 -0
  294. package/dist/import/codex/summary.d.ts.map +1 -0
  295. package/dist/import/codex/summary.js +206 -0
  296. package/dist/import/codex/summary.js.map +1 -0
  297. package/dist/import/events/activity.d.ts.map +1 -1
  298. package/dist/import/events/activity.js +17 -2
  299. package/dist/import/events/activity.js.map +1 -1
  300. package/dist/import/events/session.d.ts +11 -1
  301. package/dist/import/events/session.d.ts.map +1 -1
  302. package/dist/import/events/session.js +19 -1
  303. package/dist/import/events/session.js.map +1 -1
  304. package/dist/import/ids.js +3 -3
  305. package/dist/import/ids.js.map +1 -1
  306. package/dist/import/pipeline.d.ts +22 -15
  307. package/dist/import/pipeline.d.ts.map +1 -1
  308. package/dist/import/pipeline.js +99 -18
  309. package/dist/import/pipeline.js.map +1 -1
  310. package/dist/import/types.d.ts +4 -0
  311. package/dist/import/types.d.ts.map +1 -1
  312. package/dist/import/types.js.map +1 -1
  313. package/dist/index.js +9 -11
  314. package/dist/index.js.map +1 -1
  315. package/dist/lib/collector.d.ts +2 -1
  316. package/dist/lib/collector.d.ts.map +1 -1
  317. package/dist/lib/collector.js +28 -3
  318. package/dist/lib/collector.js.map +1 -1
  319. package/dist/lib/config.d.ts.map +1 -1
  320. package/dist/lib/config.js.map +1 -1
  321. package/dist/lib/event.d.ts +18 -1
  322. package/dist/lib/event.d.ts.map +1 -1
  323. package/dist/lib/event.js +25 -1
  324. package/dist/lib/event.js.map +1 -1
  325. package/dist/lib/platform-section.d.ts.map +1 -1
  326. package/dist/lib/platform-section.js +8 -0
  327. package/dist/lib/platform-section.js.map +1 -1
  328. package/dist/otel/{context → claude/context}/build.d.ts +1 -1
  329. package/dist/otel/claude/context/build.d.ts.map +1 -0
  330. package/dist/otel/{context → claude/context}/build.js +3 -7
  331. package/dist/otel/claude/context/build.js.map +1 -0
  332. package/dist/otel/claude/context/classify.d.ts.map +1 -0
  333. package/dist/otel/claude/context/classify.js.map +1 -0
  334. package/dist/otel/{context → claude/context}/extract.d.ts +1 -1
  335. package/dist/otel/claude/context/extract.d.ts.map +1 -0
  336. package/dist/otel/claude/context/extract.js.map +1 -0
  337. package/dist/otel/claude/context/markers.d.ts.map +1 -0
  338. package/dist/otel/{context → claude/context}/markers.js +22 -3
  339. package/dist/otel/claude/context/markers.js.map +1 -0
  340. package/dist/otel/claude/context/util.d.ts.map +1 -0
  341. package/dist/otel/claude/context/util.js.map +1 -0
  342. package/dist/otel/{daemon → claude/daemon}/ensure.d.ts +1 -1
  343. package/dist/otel/claude/daemon/ensure.d.ts.map +1 -0
  344. package/dist/otel/{daemon → claude/daemon}/ensure.js +6 -6
  345. package/dist/otel/claude/daemon/ensure.js.map +1 -0
  346. package/dist/otel/{daemon → claude/daemon}/forward.d.ts +1 -1
  347. package/dist/otel/claude/daemon/forward.d.ts.map +1 -0
  348. package/dist/otel/{daemon → claude/daemon}/forward.js +0 -0
  349. package/dist/otel/claude/daemon/forward.js.map +1 -0
  350. package/dist/otel/claude/daemon/paths.d.ts.map +1 -0
  351. package/dist/otel/claude/daemon/paths.js.map +1 -0
  352. package/dist/otel/{daemon → claude/daemon}/process.d.ts +1 -1
  353. package/dist/otel/claude/daemon/process.d.ts.map +1 -0
  354. package/dist/otel/{daemon → claude/daemon}/process.js +1 -1
  355. package/dist/otel/claude/daemon/process.js.map +1 -0
  356. package/dist/otel/claude/daemon/reprocess.d.ts.map +1 -0
  357. package/dist/otel/{daemon → claude/daemon}/reprocess.js +2 -2
  358. package/dist/otel/claude/daemon/reprocess.js.map +1 -0
  359. package/dist/otel/claude/log-handler.d.ts.map +1 -0
  360. package/dist/otel/{log-handler.js → claude/log-handler.js} +1 -1
  361. package/dist/otel/claude/log-handler.js.map +1 -0
  362. package/dist/otel/collector.js +4 -4
  363. package/dist/otel/collector.js.map +1 -1
  364. package/dist/queue/flush.d.ts +23 -0
  365. package/dist/queue/flush.d.ts.map +1 -1
  366. package/dist/queue/flush.js +44 -0
  367. package/dist/queue/flush.js.map +1 -1
  368. package/dist/queue/handlers/send-event.d.ts.map +1 -1
  369. package/dist/queue/handlers/send-event.js +5 -4
  370. package/dist/queue/handlers/send-event.js.map +1 -1
  371. package/dist/queue/index.d.ts +2 -2
  372. package/dist/queue/index.d.ts.map +1 -1
  373. package/dist/queue/index.js +4 -1
  374. package/dist/queue/index.js.map +1 -1
  375. package/dist/queue/spawn.d.ts +20 -0
  376. package/dist/queue/spawn.d.ts.map +1 -1
  377. package/dist/queue/spawn.js +37 -0
  378. package/dist/queue/spawn.js.map +1 -1
  379. package/dist/tui/import/area.js +3 -3
  380. package/dist/tui/import/area.js.map +1 -1
  381. package/package.json +2 -1
  382. package/dist/analytics/classifier.d.ts.map +0 -1
  383. package/dist/analytics/classifier.js.map +0 -1
  384. package/dist/analytics/emit.d.ts.map +0 -1
  385. package/dist/analytics/emit.js.map +0 -1
  386. package/dist/analytics/errors.d.ts.map +0 -1
  387. package/dist/analytics/errors.js.map +0 -1
  388. package/dist/analytics/hook-trigger.d.ts.map +0 -1
  389. package/dist/analytics/hook-trigger.js.map +0 -1
  390. package/dist/analytics/log.d.ts.map +0 -1
  391. package/dist/analytics/log.js.map +0 -1
  392. package/dist/analytics/merge.d.ts.map +0 -1
  393. package/dist/analytics/merge.js.map +0 -1
  394. package/dist/analytics/pricing.d.ts.map +0 -1
  395. package/dist/analytics/pricing.js.map +0 -1
  396. package/dist/analytics/projection.d.ts.map +0 -1
  397. package/dist/analytics/projection.js.map +0 -1
  398. package/dist/analytics/spawn.d.ts.map +0 -1
  399. package/dist/analytics/spawn.js.map +0 -1
  400. package/dist/analytics/state.d.ts.map +0 -1
  401. package/dist/analytics/state.js.map +0 -1
  402. package/dist/analytics/transcript.d.ts.map +0 -1
  403. package/dist/analytics/transcript.js.map +0 -1
  404. package/dist/analytics/types.d.ts.map +0 -1
  405. package/dist/analytics/types.js.map +0 -1
  406. package/dist/commands/claude.d.ts.map +0 -1
  407. package/dist/commands/claude.js.map +0 -1
  408. package/dist/commands/otel.d.ts.map +0 -1
  409. package/dist/commands/otel.js.map +0 -1
  410. package/dist/commands/process-analytics.d.ts +0 -18
  411. package/dist/commands/process-analytics.d.ts.map +0 -1
  412. package/dist/commands/process-analytics.js.map +0 -1
  413. package/dist/commands/statusline-toggle.d.ts.map +0 -1
  414. package/dist/commands/statusline-toggle.js.map +0 -1
  415. package/dist/commands/statusline.d.ts.map +0 -1
  416. package/dist/commands/statusline.js.map +0 -1
  417. package/dist/otel/context/build.d.ts.map +0 -1
  418. package/dist/otel/context/build.js.map +0 -1
  419. package/dist/otel/context/classify.d.ts.map +0 -1
  420. package/dist/otel/context/classify.js.map +0 -1
  421. package/dist/otel/context/extract.d.ts.map +0 -1
  422. package/dist/otel/context/extract.js.map +0 -1
  423. package/dist/otel/context/markers.d.ts.map +0 -1
  424. package/dist/otel/context/markers.js.map +0 -1
  425. package/dist/otel/context/util.d.ts.map +0 -1
  426. package/dist/otel/context/util.js.map +0 -1
  427. package/dist/otel/daemon/ensure.d.ts.map +0 -1
  428. package/dist/otel/daemon/ensure.js.map +0 -1
  429. package/dist/otel/daemon/forward.d.ts.map +0 -1
  430. package/dist/otel/daemon/forward.js.map +0 -1
  431. package/dist/otel/daemon/paths.d.ts.map +0 -1
  432. package/dist/otel/daemon/paths.js.map +0 -1
  433. package/dist/otel/daemon/process.d.ts.map +0 -1
  434. package/dist/otel/daemon/process.js.map +0 -1
  435. package/dist/otel/daemon/reprocess.d.ts.map +0 -1
  436. package/dist/otel/daemon/reprocess.js.map +0 -1
  437. package/dist/otel/log-handler.d.ts.map +0 -1
  438. package/dist/otel/log-handler.js.map +0 -1
  439. /package/dist/analytics/{log.d.ts → claude/log.d.ts} +0 -0
  440. /package/dist/analytics/{transcript.d.ts → claude/transcript.d.ts} +0 -0
  441. /package/dist/analytics/{classifier.d.ts → shared/classifier.d.ts} +0 -0
  442. /package/dist/analytics/{errors.d.ts → shared/errors.d.ts} +0 -0
  443. /package/dist/analytics/{errors.js → shared/errors.js} +0 -0
  444. /package/dist/analytics/{types.js → shared/types.js} +0 -0
  445. /package/dist/otel/{context → claude/context}/classify.d.ts +0 -0
  446. /package/dist/otel/{context → claude/context}/classify.js +0 -0
  447. /package/dist/otel/{context → claude/context}/extract.js +0 -0
  448. /package/dist/otel/{context → claude/context}/markers.d.ts +0 -0
  449. /package/dist/otel/{context → claude/context}/util.d.ts +0 -0
  450. /package/dist/otel/{context → claude/context}/util.js +0 -0
  451. /package/dist/otel/{daemon → claude/daemon}/paths.d.ts +0 -0
  452. /package/dist/otel/{daemon → claude/daemon}/paths.js +0 -0
  453. /package/dist/otel/{daemon → claude/daemon}/reprocess.d.ts +0 -0
  454. /package/dist/otel/{log-handler.d.ts → claude/log-handler.d.ts} +0 -0
@@ -24,9 +24,9 @@
24
24
  * read-once-locally pattern.
25
25
  */
26
26
  Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.formatHexAsUuid = void 0;
27
28
  exports.deriveStepId = deriveStepId;
28
29
  exports.deriveTurnId = deriveTurnId;
29
- exports.formatHexAsUuid = formatHexAsUuid;
30
30
  exports.deriveSessionAnalyticsEventId = deriveSessionAnalyticsEventId;
31
31
  exports.deriveTurnEventId = deriveTurnEventId;
32
32
  exports.deriveStepEventId = deriveStepEventId;
@@ -36,31 +36,40 @@ exports.projectDelta = projectDelta;
36
36
  exports.projectDeltaInternal = projectDeltaInternal;
37
37
  const node_crypto_1 = require("node:crypto");
38
38
  const diff_1 = require("diff");
39
- const logger_1 = require("../lib/logger");
40
- const types_1 = require("./types");
39
+ const event_1 = require("../../lib/event");
40
+ const logger_1 = require("../../lib/logger");
41
+ const types_1 = require("../shared/types");
41
42
  const pricing_1 = require("./pricing");
42
- const errors_1 = require("./errors");
43
- const classifier_1 = require("./classifier");
43
+ const errors_1 = require("../shared/errors");
44
+ const classifier_1 = require("../shared/classifier");
44
45
  // ─────────────────────────────────────────────────────────────────────────
45
46
  // Constants — keep in sync with /insights:332-349 + 651-677 + 651-677
46
47
  // ─────────────────────────────────────────────────────────────────────────
48
+ // Kept in cross-client parity with Codex `languageFromPath`
49
+ // (`src/analytics/codex/projection.ts`). The two pipelines feed the same
50
+ // `code_changes.languages` map on the wire — a backend aggregating Claude +
51
+ // Codex sessions sees one key `"TypeScript"` regardless of source, NOT
52
+ // `"TypeScript"` (Codex side) + a silently dropped Claude `.kt` / `.swift`
53
+ // edit. Real corpus: Claude sessions editing `.php` / `.kt` / `.scala` /
54
+ // `.swift` etc. previously emitted zero `languages[...]` entries because the
55
+ // table didn't cover them — silent data loss vs the Codex side. Add new
56
+ // extensions to BOTH tables in lockstep.
47
57
  const EXTENSION_TO_LANGUAGE = {
48
- ".ts": "TypeScript",
49
- ".tsx": "TypeScript",
50
- ".js": "JavaScript",
51
- ".jsx": "JavaScript",
52
- ".py": "Python",
53
- ".rb": "Ruby",
54
- ".go": "Go",
55
- ".rs": "Rust",
56
- ".java": "Java",
57
- ".md": "Markdown",
58
- ".json": "JSON",
59
- ".yaml": "YAML",
60
- ".yml": "YAML",
61
- ".sh": "Shell",
62
- ".css": "CSS",
63
- ".html": "HTML",
58
+ ".ts": "TypeScript", ".tsx": "TypeScript",
59
+ ".js": "JavaScript", ".jsx": "JavaScript", ".mjs": "JavaScript", ".cjs": "JavaScript",
60
+ ".py": "Python", ".rb": "Ruby", ".go": "Go", ".rs": "Rust",
61
+ ".java": "Java", ".kt": "Kotlin", ".scala": "Scala",
62
+ ".swift": "Swift", ".m": "Objective-C", ".mm": "Objective-C",
63
+ ".c": "C", ".h": "C", ".cpp": "C++", ".cc": "C++", ".hpp": "C++",
64
+ ".cs": "C#", ".vb": "VB",
65
+ ".php": "PHP", ".html": "HTML", ".htm": "HTML",
66
+ ".css": "CSS", ".scss": "CSS", ".sass": "CSS", ".less": "CSS",
67
+ ".md": "Markdown", ".mdx": "Markdown",
68
+ ".json": "JSON", ".yaml": "YAML", ".yml": "YAML", ".toml": "TOML", ".xml": "XML",
69
+ ".sh": "Shell", ".bash": "Shell", ".zsh": "Shell",
70
+ ".sql": "SQL", ".graphql": "GraphQL", ".proto": "Protocol Buffer",
71
+ ".ex": "Elixir", ".exs": "Elixir", ".erl": "Erlang",
72
+ ".lua": "Lua", ".dart": "Dart", ".clj": "Clojure",
64
73
  };
65
74
  const INTERRUPT_MARKER = "[Request interrupted by user";
66
75
  // Response-time clamp per /insights:633.
@@ -125,12 +134,33 @@ function extractBashSubcommand(cmd) {
125
134
  if (stripped.length === 0) {
126
135
  return undefined;
127
136
  }
128
- const binary = stripped[0];
137
+ // Path-strip the binary token before the multi-subcommand set lookup —
138
+ // without this, `/usr/bin/git status` misses
139
+ // `KNOWN_MULTI_SUBCOMMAND_BINARIES.has("/usr/bin/git")` and drops to the
140
+ // single-binary fallback, losing the "status" / "install" verb. Same
141
+ // agent activity (`git status` vs `/usr/bin/git status`) would then ship
142
+ // two different wire shapes — cross-session aggregation breaks even
143
+ // within a single Claude project. Mirrors the Codex-side fix at
144
+ // analytics/codex/projection.ts:425. `extractBashBinary` deliberately
145
+ // keeps the full path (documented Claude-vs-Codex divergence on
146
+ // `bash_binaries`), but `bash_subcommands` has no such divergence —
147
+ // verb granularity is the load-bearing signal here.
148
+ const rawBinary = stripped[0];
149
+ const binary = rawBinary.split(/[\\/]/).pop() ?? rawBinary;
150
+ // Guard against empty/whitespace-only commands ("", " ", env-vars-only).
151
+ // `"".trim().split(/\s+/)` returns `[""]` (single empty-string element),
152
+ // not `[]` — the env-var skip doesn't filter the empty token, so
153
+ // `stripped = [""]` and `binary = ""`. The caller checks `sub !== undefined`,
154
+ // so the empty string would slip through and ship `bash_subcommands[""]: N`
155
+ // on the wire. Codex side filters via `if (sub)` (truthy check) but
156
+ // Claude's caller uses `!== undefined` — defending at the function level
157
+ // covers both call sites and any future caller.
158
+ if (binary.length === 0) {
159
+ return undefined;
160
+ }
129
161
  if (stripped.length === 1) {
130
162
  return binary;
131
163
  }
132
- // Only include subcommand for known multi-subcommand CLIs AND when the
133
- // second token has a safe identifier shape. Otherwise just emit the binary.
134
164
  if (KNOWN_MULTI_SUBCOMMAND_BINARIES.has(binary)
135
165
  && SUBCOMMAND_TOKEN_RE.test(stripped[1])) {
136
166
  return `${binary} ${stripped[1]}`;
@@ -478,15 +508,13 @@ function deriveTurnId(sessionId, turnIndex, startTime) {
478
508
  .slice(0, 16);
479
509
  }
480
510
  /**
481
- * Format a 32-char hex string into UUID-shaped layout (8-4-4-4-12). NOT
482
- * RFC 4122-compliant (no version/variant bits) but matches the regex
483
- * shape collectors expect for `Event.id`. Pure presentation — collisions
484
- * impossible because the source hash space is the same 32 hex chars.
511
+ * Re-exported from `src/lib/event.ts` (single source of truth). Kept here
512
+ * for backwards compatibility with existing consumers (`src/import/ids.ts`,
513
+ * Codex `projection.ts`, status-snapshot etc.) that import the
514
+ * symbol from this module.
485
515
  */
486
- function formatHexAsUuid(hex32) {
487
- return `${hex32.slice(0, 8)}-${hex32.slice(8, 12)}-${hex32.slice(12, 16)}-`
488
- + `${hex32.slice(16, 20)}-${hex32.slice(20, 32)}`;
489
- }
516
+ var event_2 = require("../../lib/event");
517
+ Object.defineProperty(exports, "formatHexAsUuid", { enumerable: true, get: function () { return event_2.formatHexAsUuid; } });
490
518
  /**
491
519
  * Deterministic Event.id for `session_analytics` wire records.
492
520
  * `sha256("session_analytics:" + session_id)`, formatted as UUID. One id
@@ -506,7 +534,7 @@ function deriveSessionAnalyticsEventId(sessionId) {
506
534
  const hex = (0, node_crypto_1.createHash)("sha256")
507
535
  .update(`session_analytics:${sessionId}`)
508
536
  .digest("hex");
509
- return formatHexAsUuid(hex.slice(0, 32));
537
+ return (0, event_1.formatHexAsUuid)(hex.slice(0, 32));
510
538
  }
511
539
  /**
512
540
  * Deterministic Event.id for `session_turn_analytics` wire records.
@@ -517,7 +545,7 @@ function deriveTurnEventId(sessionId, turnId) {
517
545
  const hex = (0, node_crypto_1.createHash)("sha256")
518
546
  .update(`session_turn_analytics:${sessionId}:${turnId}`)
519
547
  .digest("hex");
520
- return formatHexAsUuid(hex.slice(0, 32));
548
+ return (0, event_1.formatHexAsUuid)(hex.slice(0, 32));
521
549
  }
522
550
  /**
523
551
  * Deterministic Event.id for `session_turn_step_analytics` wire records.
@@ -528,7 +556,7 @@ function deriveStepEventId(sessionId, stepId) {
528
556
  const hex = (0, node_crypto_1.createHash)("sha256")
529
557
  .update(`session_turn_step_analytics:${sessionId}:${stepId}`)
530
558
  .digest("hex");
531
- return formatHexAsUuid(hex.slice(0, 32));
559
+ return (0, event_1.formatHexAsUuid)(hex.slice(0, 32));
532
560
  }
533
561
  /**
534
562
  * Open a fresh turn from the human-user prompt that started it. Computes
@@ -1095,6 +1123,20 @@ function projectDelta(input) {
1095
1123
  // Reported back via DeltaInternal.seen_assistant_message_ids.
1096
1124
  const seenAssistantMessageIds = new Set(input.priorSeenAssistantMessageIds ?? []);
1097
1125
  const newAssistantMessageIdsThisSlice = [];
1126
+ // Anthropic API tool_use_id dedup. Claude Code 2.1+ transcript writer
1127
+ // splits a single API response's content blocks across multiple JSONL
1128
+ // lines (each carrying the SAME message.id but DIFFERENT tool_use
1129
+ // blocks per chunk — empirically verified against real transcripts:
1130
+ // 170 assistant lines / 84 distinct msg_ids on a 19-min session,
1131
+ // every multi-line msg_id had blocks of distinct types per chunk).
1132
+ // msg_id dedup alone would drop the streaming-split chunks entirely,
1133
+ // losing ~60% of tool_uses on real sessions. Instead, content-block
1134
+ // walks run on EVERY chunk and individual `tool_use` blocks are deduped
1135
+ // by their stable Anthropic-assigned `id`. This ALSO catches the
1136
+ // legacy "true duplicate re-emit" case (where the same block was
1137
+ // persisted multiple times verbatim — same id => skip).
1138
+ const seenToolUseIds = new Set(input.priorSeenToolUseIds ?? []);
1139
+ const newToolUseIdsThisSlice = [];
1098
1140
  // Idle attribution: walking the timeline, the gap BEFORE a human-user
1099
1141
  // message is "idle" (user typing/thinking/away). Every other gap is
1100
1142
  // "active" (model + tools + agent loop). Seed `priorTsMs` from the prior
@@ -1158,218 +1200,347 @@ function projectDelta(input) {
1158
1200
  priorTsMs = tsMillis;
1159
1201
  }
1160
1202
  if (line.type === "assistant" && line.message !== undefined) {
1161
- // Anthropic msg_id dedup: skip the entire assistant block
1162
- // (counters, tokens, cost, model, context, CONSUMER, EMITTER,
1163
- // tool counting, step open) when this API response was already
1164
- // counted via a prior line/slice. See MessageBody.id docstring
1165
- // for why duplicates exist in real transcripts.
1203
+ // Anthropic msg_id dedup: skip PER-MESSAGE work (counters,
1204
+ // tokens, cost, model, context, EMITTER, step open, models[*]
1205
+ // map, context-tokens bucket, api_request emit, end-of-msg
1206
+ // classifier's cost accumulation) when this API response was
1207
+ // already counted via a prior line/slice. Claude Code 2.1+
1208
+ // splits ONE API response's content blocks across multiple
1209
+ // JSONL lines that share the same msg_id, so the per-message
1210
+ // counters MUST run once per msg_id. CONTENT BLOCKS
1211
+ // (tool_use, text, thinking) are walked on EVERY chunk
1212
+ // regardless and deduped at block level via `seenToolUseIds`
1213
+ // (see below) — the older "continue on msg_id seen" gate
1214
+ // would drop 60% of tool_uses on real transcripts.
1166
1215
  const msgId = line.message.id;
1216
+ let isFirstChunkOfMsg = true;
1167
1217
  if (typeof msgId === "string" && msgId.length > 0) {
1168
1218
  if (seenAssistantMessageIds.has(msgId)) {
1169
- // Duplicate API response re-emit — silently skip.
1170
- continue;
1219
+ isFirstChunkOfMsg = false;
1220
+ }
1221
+ else {
1222
+ seenAssistantMessageIds.add(msgId);
1223
+ newAssistantMessageIdsThisSlice.push(msgId);
1171
1224
  }
1172
- seenAssistantMessageIds.add(msgId);
1173
- newAssistantMessageIdsThisSlice.push(msgId);
1174
- }
1175
- assistantResponseCount += 1;
1176
- assistantTurnIndex += 1;
1177
- // Track timestamp for response-time gap to the next user message.
1178
- if (tsMillis !== null) {
1179
- lastAssistantMs = tsMillis;
1180
1225
  }
1181
- // Per-turn: bump assistant_messages + last_activity_time.
1182
- // Per-step: every assistant message starts a new step. If a
1183
- // prior step was open in this turn, close it (push to
1184
- // completed_steps) before opening the new one. Step start_time
1185
- // is this assistant's timestamp (or lastTimestamp fallback for
1186
- // determinism when ts is missing same rule as openTurn).
1187
- if (currentTurn !== null) {
1188
- currentTurn.assistant_messages += 1;
1189
- if (typeof tsString === "string" && tsString.length > 0) {
1190
- currentTurn.last_activity_time = tsString;
1226
+ // msgCost + advisorCostThisMsg are computed inside the
1227
+ // per-message gate but consumed by the end-of-message
1228
+ // classifier which runs on every chunk (retry-detection
1229
+ // mutates msgBuckets-driven flags per chunk). Hoist to outer
1230
+ // scope so they retain their value across the gate; default
1231
+ // to 0 on subsequent chunks so `currentTurn.cost_usd += ...`
1232
+ // doesn't double-count.
1233
+ let msgCost = 0;
1234
+ let advisorCostThisMsg = 0;
1235
+ if (isFirstChunkOfMsg) {
1236
+ assistantResponseCount += 1;
1237
+ assistantTurnIndex += 1;
1238
+ // Track timestamp for response-time gap to the next user message.
1239
+ if (tsMillis !== null) {
1240
+ lastAssistantMs = tsMillis;
1191
1241
  }
1192
- if (currentTurn.current_step !== undefined) {
1193
- currentTurn.completed_steps.push(currentTurn.current_step);
1242
+ // Per-turn: bump assistant_messages + last_activity_time.
1243
+ // Per-step: every assistant message starts a new step. If a
1244
+ // prior step was open in this turn, close it (push to
1245
+ // completed_steps) before opening the new one. Step start_time
1246
+ // is this assistant's timestamp (or lastTimestamp fallback for
1247
+ // determinism when ts is missing — same rule as openTurn).
1248
+ if (currentTurn !== null) {
1249
+ currentTurn.assistant_messages += 1;
1250
+ if (typeof tsString === "string" && tsString.length > 0) {
1251
+ currentTurn.last_activity_time = tsString;
1252
+ }
1253
+ if (currentTurn.current_step !== undefined) {
1254
+ currentTurn.completed_steps.push(currentTurn.current_step);
1255
+ }
1256
+ const stepStart = typeof tsString === "string" && tsString.length > 0
1257
+ ? tsString
1258
+ : lastTimestamp;
1259
+ currentTurn.current_step = openStep(currentTurn.next_step_index, stepStart);
1260
+ currentTurn.next_step_index += 1;
1194
1261
  }
1195
- const stepStart = typeof tsString === "string" && tsString.length > 0
1196
- ? tsString
1197
- : lastTimestamp;
1198
- currentTurn.current_step = openStep(currentTurn.next_step_index, stepStart);
1199
- currentTurn.next_step_index += 1;
1200
- }
1201
- const model = line.message.model;
1202
- const usage = line.message.usage;
1203
- // Tokens session-wide totals
1204
- const msgInput = usage?.input_tokens ?? 0;
1205
- const msgOutput = usage?.output_tokens ?? 0;
1206
- const msgCacheCreation = usage?.cache_creation_input_tokens ?? 0;
1207
- const msgCacheRead = usage?.cache_read_input_tokens ?? 0;
1208
- // Cache-creation 5m / 1h split (Anthropic ephemeral tiers). When
1209
- // present, used in cost calc to bill 1h tokens at the higher
1210
- // rate; falls back to a single sum at 5m rate when absent.
1211
- const msgCC5m = usage?.cache_creation?.ephemeral_5m_input_tokens ?? 0;
1212
- const msgCC1h = usage?.cache_creation?.ephemeral_1h_input_tokens ?? 0;
1213
- const hasCcSplit = (msgCC5m + msgCC1h) > 0;
1214
- // Server tool use (web_search currently the only billable)
1215
- const msgWebSearchReqs = usage?.server_tool_use?.web_search_requests ?? 0;
1216
- // Fast mode tier (Opus 4.6 only — 6× regular rate)
1217
- const msgSpeed = usage?.speed;
1218
- inputTokens += msgInput;
1219
- outputTokens += msgOutput;
1220
- cacheCreationTokens += msgCacheCreation;
1221
- cacheReadTokens += msgCacheRead;
1222
- // Per-turn token totals same additive rule as session-level.
1223
- // Per-step: mirror onto the just-opened step.
1224
- if (currentTurn !== null) {
1225
- currentTurn.input_tokens += msgInput;
1226
- currentTurn.output_tokens += msgOutput;
1227
- currentTurn.cache_creation_tokens += msgCacheCreation;
1228
- currentTurn.cache_read_tokens += msgCacheRead;
1229
- if (currentTurn.current_step !== undefined) {
1230
- currentTurn.current_step.input_tokens += msgInput;
1231
- currentTurn.current_step.output_tokens += msgOutput;
1232
- currentTurn.current_step.cache_creation_tokens += msgCacheCreation;
1233
- currentTurn.current_step.cache_read_tokens += msgCacheRead;
1262
+ // Normalize bracketed runtime suffixes (e.g. `claude-opus-4-7[1m]`
1263
+ // → `claude-opus-4-7`) at the single capture site so every
1264
+ // downstream use ships a consistent canonical id: `api_request.model`,
1265
+ // `session_analytics.models[<key>]` map keys, `session_turn_analytics`
1266
+ // / `session_turn_step_analytics` per-turn / per-step `models`
1267
+ // map keys. Without this, `session_status.model` (already
1268
+ // normalized at clients/claude/hooks/session-status.ts:222
1269
+ // via `normalizeModelId`) emitted `claude-opus-4-7` while
1270
+ // `api_request.model` and `models[*]` keys shipped raw
1271
+ // `claude-opus-4-7[1m]` backend dashboards joining
1272
+ // `api_request` `session_status` on `model` saw two
1273
+ // strings for one model, and `Σ session_analytics.models[*]`
1274
+ // didn't reconcile cross-event. The `[…]` suffix is
1275
+ // redundant on the wire context-window size carries the
1276
+ // effective window through `session_status.context_window`.
1277
+ const rawModel = line.message.model;
1278
+ const model = rawModel === undefined
1279
+ ? undefined
1280
+ : rawModel.replace(/\[[^\]]*\]/g, "").trim();
1281
+ const usage = line.message.usage;
1282
+ // Tokens session-wide totals
1283
+ const msgInput = usage?.input_tokens ?? 0;
1284
+ const msgOutput = usage?.output_tokens ?? 0;
1285
+ const msgCacheCreation = usage?.cache_creation_input_tokens ?? 0;
1286
+ const msgCacheRead = usage?.cache_read_input_tokens ?? 0;
1287
+ // Cache-creation 5m / 1h split (Anthropic ephemeral tiers). When
1288
+ // present, used in cost calc to bill 1h tokens at the higher
1289
+ // rate; falls back to a single sum at 5m rate when absent.
1290
+ const msgCC5m = usage?.cache_creation?.ephemeral_5m_input_tokens ?? 0;
1291
+ const msgCC1h = usage?.cache_creation?.ephemeral_1h_input_tokens ?? 0;
1292
+ const hasCcSplit = (msgCC5m + msgCC1h) > 0;
1293
+ // Server tool use (web_search currently the only billable)
1294
+ const msgWebSearchReqs = usage?.server_tool_use?.web_search_requests ?? 0;
1295
+ // Fast mode tier (Opus 4.6 only — 6× regular rate)
1296
+ const msgSpeed = usage?.speed;
1297
+ inputTokens += msgInput;
1298
+ outputTokens += msgOutput;
1299
+ cacheCreationTokens += msgCacheCreation;
1300
+ cacheReadTokens += msgCacheRead;
1301
+ // Per-turn token totals — same additive rule as session-level.
1302
+ // Per-step: mirror onto the just-opened step.
1303
+ if (currentTurn !== null) {
1304
+ currentTurn.input_tokens += msgInput;
1305
+ currentTurn.output_tokens += msgOutput;
1306
+ currentTurn.cache_creation_tokens += msgCacheCreation;
1307
+ currentTurn.cache_read_tokens += msgCacheRead;
1308
+ if (currentTurn.current_step !== undefined) {
1309
+ currentTurn.current_step.input_tokens += msgInput;
1310
+ currentTurn.current_step.output_tokens += msgOutput;
1311
+ currentTurn.current_step.cache_creation_tokens += msgCacheCreation;
1312
+ currentTurn.current_step.cache_read_tokens += msgCacheRead;
1313
+ }
1234
1314
  }
1235
- }
1236
- // Per-message cost looked up by model id (family fallback +
1237
- // fast-mode tier when speed=="fast" on Opus 4.6). Unknown
1238
- // family contributes 0. computeMessageCostUsd handles the
1239
- // 5m/1h cache split + web_search add-on.
1240
- const pricing = typeof model === "string" && model.length > 0
1241
- ? (0, pricing_1.lookupPricingForUsage)(model, msgSpeed)
1242
- : null;
1243
- const msgCost = (0, pricing_1.computeMessageCostUsd)({
1244
- input_tokens: msgInput,
1245
- output_tokens: msgOutput,
1246
- cache_creation_tokens: msgCacheCreation,
1247
- cache_read_tokens: msgCacheRead,
1248
- ...(hasCcSplit ? {
1249
- cache_creation_5m_tokens: msgCC5m,
1250
- cache_creation_1h_tokens: msgCC1h,
1251
- } : {}),
1252
- web_search_requests: msgWebSearchReqs,
1253
- }, pricing);
1254
- costUsd += msgCost;
1255
- // Per-API-request event emission. One record per non-duplicate
1256
- // assistant line — covers both successful API responses and
1257
- // terminal failure synthetic placeholders (`isApiErrorMessage`).
1258
- // Body-only struct; emit wraps with the Event envelope.
1259
- //
1260
- // Field semantics verified against real transcripts:
1261
- // - request_id: top-level `requestId` ONLY on success lines
1262
- // (failure lines carry no join key → null).
1263
- // - error: top-level `error` ONLY on failure lines.
1264
- // - status_code: top-level `apiErrorStatus` rarely set even
1265
- // on failures (~5% only explicit HTTP errors like 429).
1266
- // - speed: `usage.speed` typically "standard"; null on
1267
- // synthetic-failure placeholder messages.
1268
- // - duration: NOT in transcript at all always null here
1269
- // (transcript-derived). Java-side runtime emitters fill
1270
- // this with real per-call latency.
1271
- // - cost_usd: derived via pricing × tokens (= 0 on failures
1272
- // since tokens are zero).
1273
- const isApiErrorLine = line.isApiErrorMessage === true;
1274
- const apiRequestId = typeof line.requestId === "string" && line.requestId.length > 0
1275
- ? line.requestId
1276
- : null;
1277
- const apiErrorStr = isApiErrorLine && typeof line.error === "string"
1278
- ? line.error
1279
- : null;
1280
- const apiStatusCode = isApiErrorLine && typeof line.apiErrorStatus === "number"
1281
- ? line.apiErrorStatus
1282
- : null;
1283
- const apiSpeed = typeof msgSpeed === "string" && msgSpeed.length > 0
1284
- ? msgSpeed
1285
- : null;
1286
- const apiTimestampMs = tsMillis ?? Date.now();
1287
- // Event.id source: transcript line's top-level `uuid`. Verified
1288
- // 100% presence on real transcripts (success + failure). Falls
1289
- // back to a deterministic UUID-shaped id derived from
1290
- // sha256(session_id|msg_id|timestamp) when uuid is missing
1291
- // (defensive never observed in production data; Cursor
1292
- // transcripts have no uuid either way and don't reach this
1293
- // path because they're skipped at emit-time).
1294
- const apiEventId = typeof line.uuid === "string" && line.uuid.length > 0
1295
- ? line.uuid
1296
- : formatHexAsUuid((0, node_crypto_1.createHash)("sha256")
1297
- .update(`api_request:${input.sessionId}:${msgId ?? "anon"}:${apiTimestampMs}`)
1298
- .digest("hex")
1299
- .slice(0, 32));
1300
- apiRequestEvents.push({
1301
- id: apiEventId,
1302
- timestamp_ms: apiTimestampMs,
1303
- request_id: apiRequestId,
1304
- success: !isApiErrorLine,
1305
- error: apiErrorStr,
1306
- status_code: apiStatusCode,
1307
- model: typeof model === "string" && model.length > 0 ? model : "<unknown>",
1308
- speed: apiSpeed,
1309
- input_tokens: msgInput,
1310
- output_tokens: msgOutput,
1311
- cache_read_tokens: msgCacheRead,
1312
- cache_creation_tokens: msgCacheCreation,
1313
- cost_usd: msgCost,
1314
- duration: null,
1315
- });
1316
- // Advisor sub-call recursion. `usage.iterations` may carry
1317
- // entries typed `"advisor_message"` whose tokens are billed
1318
- // separately — they're NOT included in the parent usage.
1319
- // Each advisor sub-call goes to unattributed_cost (no per-tool
1320
- // attribution; advisor is a server-side recursive call,
1321
- // priced at its own model's rate). Other iteration types
1322
- // (notably `"message"` which mirrors the parent) are skipped.
1323
- let advisorCostThisMsg = 0;
1324
- const iterations = usage?.iterations;
1325
- if (Array.isArray(iterations)) {
1326
- for (const it of iterations) {
1327
- if (it.type !== "advisor_message") {
1328
- continue;
1315
+ // Per-message cost — looked up by model id (family fallback +
1316
+ // fast-mode tier when speed=="fast" on Opus 4.6). Unknown
1317
+ // family contributes 0. computeMessageCostUsd handles the
1318
+ // 5m/1h cache split + web_search add-on.
1319
+ const pricing = typeof model === "string" && model.length > 0
1320
+ ? (0, pricing_1.lookupPricingForUsage)(model, msgSpeed)
1321
+ : null;
1322
+ // Assignment (not declaration) — msgCost was hoisted to outer
1323
+ // scope so the end-of-message classifier (which runs on every
1324
+ // chunk) can read it; on subsequent chunks the gate keeps
1325
+ // msgCost at 0 so cost isn't double-counted.
1326
+ msgCost = (0, pricing_1.computeMessageCostUsd)({
1327
+ input_tokens: msgInput,
1328
+ output_tokens: msgOutput,
1329
+ cache_creation_tokens: msgCacheCreation,
1330
+ cache_read_tokens: msgCacheRead,
1331
+ ...(hasCcSplit ? {
1332
+ cache_creation_5m_tokens: msgCC5m,
1333
+ cache_creation_1h_tokens: msgCC1h,
1334
+ } : {}),
1335
+ web_search_requests: msgWebSearchReqs,
1336
+ }, pricing);
1337
+ costUsd += msgCost;
1338
+ // Per-API-request event emission. One record per non-duplicate
1339
+ // assistant line — covers both successful API responses and
1340
+ // terminal failure synthetic placeholders (`isApiErrorMessage`).
1341
+ // Body-only struct; emit wraps with the Event envelope.
1342
+ //
1343
+ // Field semantics verified against real transcripts:
1344
+ // - request_id: top-level `requestId` ONLY on success lines
1345
+ // (failure lines carry no join key null).
1346
+ // - error: top-level `error` ONLY on failure lines.
1347
+ // - status_code: top-level `apiErrorStatus` rarely set even
1348
+ // on failures (~5% only explicit HTTP errors like 429).
1349
+ // - speed: `usage.speed` typically "standard"; null on
1350
+ // synthetic-failure placeholder messages.
1351
+ // - duration: NOT in transcript at all always null here
1352
+ // (transcript-derived). Server-side runtime emitters fill
1353
+ // this with real per-call latency.
1354
+ // - cost_usd: derived via pricing × tokens (= 0 on failures
1355
+ // since tokens are zero).
1356
+ const isApiErrorLine = line.isApiErrorMessage === true;
1357
+ const apiRequestId = typeof line.requestId === "string" && line.requestId.length > 0
1358
+ ? line.requestId
1359
+ : null;
1360
+ const apiErrorStr = isApiErrorLine && typeof line.error === "string"
1361
+ ? line.error
1362
+ : null;
1363
+ const apiStatusCode = isApiErrorLine && typeof line.apiErrorStatus === "number"
1364
+ ? line.apiErrorStatus
1365
+ : null;
1366
+ const apiSpeed = typeof msgSpeed === "string" && msgSpeed.length > 0
1367
+ ? msgSpeed
1368
+ : null;
1369
+ const apiTimestampMs = tsMillis ?? Date.now();
1370
+ // Event.id source: transcript line's top-level `uuid`. Verified
1371
+ // 100% presence on real transcripts (success + failure). Falls
1372
+ // back to a deterministic UUID-shaped id derived from
1373
+ // sha256(session_id|msg_id|timestamp) when uuid is missing
1374
+ // (defensive never observed in production data; Cursor
1375
+ // transcripts have no uuid either way and don't reach this
1376
+ // path because they're skipped at emit-time).
1377
+ const apiEventId = typeof line.uuid === "string" && line.uuid.length > 0
1378
+ ? line.uuid
1379
+ : (0, event_1.formatHexAsUuid)((0, node_crypto_1.createHash)("sha256")
1380
+ .update(`api_request:${input.sessionId}:${msgId ?? "anon"}:${apiTimestampMs}`)
1381
+ .digest("hex")
1382
+ .slice(0, 32));
1383
+ apiRequestEvents.push({
1384
+ id: apiEventId,
1385
+ timestamp_ms: apiTimestampMs,
1386
+ request_id: apiRequestId,
1387
+ success: !isApiErrorLine,
1388
+ error: apiErrorStr,
1389
+ status_code: apiStatusCode,
1390
+ model: typeof model === "string" && model.length > 0 ? model : "<unknown>",
1391
+ speed: apiSpeed,
1392
+ input_tokens: msgInput,
1393
+ output_tokens: msgOutput,
1394
+ cache_read_tokens: msgCacheRead,
1395
+ cache_creation_tokens: msgCacheCreation,
1396
+ cost_usd: msgCost,
1397
+ duration: null,
1398
+ });
1399
+ // Advisor sub-call recursion. `usage.iterations` may carry
1400
+ // entries typed `"advisor_message"` whose tokens are billed
1401
+ // separately they're NOT included in the parent usage.
1402
+ // Each advisor sub-call goes to unattributed_cost (no per-tool
1403
+ // attribution; advisor is a server-side recursive call,
1404
+ // priced at its own model's rate). Other iteration types
1405
+ // (notably `"message"` which mirrors the parent) are skipped.
1406
+ // `advisorCostThisMsg` is declared in the outer scope (above
1407
+ // the isFirstChunkOfMsg gate) for the same reason as
1408
+ // msgCost — end-of-message classifier reads it on every
1409
+ // chunk; subsequent chunks see 0 (gate not entered).
1410
+ // Track advisor token totals across iterations so they can
1411
+ // be folded into session/turn/step usage AFTER the loop —
1412
+ // mirrors how `advisorCostThisMsg` gets folded into
1413
+ // `costUsd` at line ~1820. Without this fold, per-model
1414
+ // sums diverge from session totals (round 86a fixed cost
1415
+ // parity but left token parity broken):
1416
+ // Σ models[*].input_tokens > session.usage.input_tokens
1417
+ // by exactly `advisorInputThisMsg`, same for output/cache.
1418
+ // Backend dashboards reconciling "tokens by model" vs
1419
+ // "session usage" see the divergence.
1420
+ let advisorInputThisMsg = 0;
1421
+ let advisorOutputThisMsg = 0;
1422
+ let advisorCacheCreationThisMsg = 0;
1423
+ let advisorCacheReadThisMsg = 0;
1424
+ const iterations = usage?.iterations;
1425
+ if (Array.isArray(iterations)) {
1426
+ for (const it of iterations) {
1427
+ if (it.type !== "advisor_message") {
1428
+ continue;
1429
+ }
1430
+ const advModel = it.model;
1431
+ const advPricing = typeof advModel === "string" && advModel.length > 0
1432
+ ? (0, pricing_1.lookupPricing)(advModel)
1433
+ : null;
1434
+ const advCC5m = it.cache_creation?.ephemeral_5m_input_tokens ?? 0;
1435
+ const advCC1h = it.cache_creation?.ephemeral_1h_input_tokens ?? 0;
1436
+ const advHasSplit = (advCC5m + advCC1h) > 0;
1437
+ const advInput = it.input_tokens ?? 0;
1438
+ const advOutput = it.output_tokens ?? 0;
1439
+ const advCacheCreation = it.cache_creation_input_tokens ?? 0;
1440
+ const advCacheRead = it.cache_read_input_tokens ?? 0;
1441
+ const advCost = (0, pricing_1.computeMessageCostUsd)({
1442
+ input_tokens: advInput,
1443
+ output_tokens: advOutput,
1444
+ cache_creation_tokens: advCacheCreation,
1445
+ cache_read_tokens: advCacheRead,
1446
+ ...(advHasSplit ? {
1447
+ cache_creation_5m_tokens: advCC5m,
1448
+ cache_creation_1h_tokens: advCC1h,
1449
+ } : {}),
1450
+ }, advPricing);
1451
+ advisorCostThisMsg += advCost;
1452
+ advisorInputThisMsg += advInput;
1453
+ advisorOutputThisMsg += advOutput;
1454
+ advisorCacheCreationThisMsg += advCacheCreation;
1455
+ advisorCacheReadThisMsg += advCacheRead;
1456
+ // Route advisor cost + tokens into the advisor's
1457
+ // OWN per-model breakdown. Without this, session
1458
+ // `usage.cost_usd` includes advisor (line below)
1459
+ // but `Σ models[*].cost_usd` excludes it — backend
1460
+ // "cost by model" SQL would underreport by
1461
+ // exactly `advisorCostThisMsg` on every session
1462
+ // that triggered advisor sub-calls.
1463
+ if (typeof advModel === "string" && advModel.length > 0) {
1464
+ const advSlot = models[advModel] ?? {
1465
+ count: 0,
1466
+ input_tokens: 0,
1467
+ output_tokens: 0,
1468
+ cache_creation_tokens: 0,
1469
+ cache_read_tokens: 0,
1470
+ cost_usd: 0,
1471
+ };
1472
+ advSlot.count += 1;
1473
+ advSlot.input_tokens += advInput;
1474
+ advSlot.output_tokens += advOutput;
1475
+ advSlot.cache_creation_tokens += advCacheCreation;
1476
+ advSlot.cache_read_tokens += advCacheRead;
1477
+ advSlot.cost_usd += advCost;
1478
+ models[advModel] = advSlot;
1479
+ if (currentTurn !== null) {
1480
+ const advTurnSlot = currentTurn.models[advModel] ?? {
1481
+ count: 0,
1482
+ input_tokens: 0,
1483
+ output_tokens: 0,
1484
+ cache_creation_tokens: 0,
1485
+ cache_read_tokens: 0,
1486
+ cost_usd: 0,
1487
+ };
1488
+ advTurnSlot.count += 1;
1489
+ advTurnSlot.input_tokens += advInput;
1490
+ advTurnSlot.output_tokens += advOutput;
1491
+ advTurnSlot.cache_creation_tokens += advCacheCreation;
1492
+ advTurnSlot.cache_read_tokens += advCacheRead;
1493
+ advTurnSlot.cost_usd += advCost;
1494
+ currentTurn.models[advModel] = advTurnSlot;
1495
+ if (currentTurn.current_step !== undefined) {
1496
+ const advStepSlot = currentTurn.current_step.models[advModel] ?? {
1497
+ count: 0,
1498
+ input_tokens: 0,
1499
+ output_tokens: 0,
1500
+ cache_creation_tokens: 0,
1501
+ cache_read_tokens: 0,
1502
+ cost_usd: 0,
1503
+ };
1504
+ advStepSlot.count += 1;
1505
+ advStepSlot.input_tokens += advInput;
1506
+ advStepSlot.output_tokens += advOutput;
1507
+ advStepSlot.cache_creation_tokens += advCacheCreation;
1508
+ advStepSlot.cache_read_tokens += advCacheRead;
1509
+ advStepSlot.cost_usd += advCost;
1510
+ currentTurn.current_step.models[advModel] = advStepSlot;
1511
+ }
1512
+ }
1513
+ }
1329
1514
  }
1330
- const advModel = it.model;
1331
- const advPricing = typeof advModel === "string" && advModel.length > 0
1332
- ? (0, pricing_1.lookupPricing)(advModel)
1333
- : null;
1334
- const advCC5m = it.cache_creation?.ephemeral_5m_input_tokens ?? 0;
1335
- const advCC1h = it.cache_creation?.ephemeral_1h_input_tokens ?? 0;
1336
- const advHasSplit = (advCC5m + advCC1h) > 0;
1337
- const advCost = (0, pricing_1.computeMessageCostUsd)({
1338
- input_tokens: it.input_tokens ?? 0,
1339
- output_tokens: it.output_tokens ?? 0,
1340
- cache_creation_tokens: it.cache_creation_input_tokens ?? 0,
1341
- cache_read_tokens: it.cache_read_input_tokens ?? 0,
1342
- ...(advHasSplit ? {
1343
- cache_creation_5m_tokens: advCC5m,
1344
- cache_creation_1h_tokens: advCC1h,
1345
- } : {}),
1346
- }, advPricing);
1347
- advisorCostThisMsg += advCost;
1348
1515
  }
1349
- }
1350
- costUsd += advisorCostThisMsg;
1351
- // Per-model usage breakdown element-wise additive across deltas
1352
- // in the merge step. Empty / missing model name skipped (no
1353
- // attribution possible).
1354
- if (typeof model === "string" && model.length > 0) {
1355
- const slot = models[model] ?? {
1356
- count: 0,
1357
- input_tokens: 0,
1358
- output_tokens: 0,
1359
- cache_creation_tokens: 0,
1360
- cache_read_tokens: 0,
1361
- cost_usd: 0,
1362
- };
1363
- slot.count += 1;
1364
- slot.input_tokens += msgInput;
1365
- slot.output_tokens += msgOutput;
1366
- slot.cache_creation_tokens += msgCacheCreation;
1367
- slot.cache_read_tokens += msgCacheRead;
1368
- slot.cost_usd += msgCost;
1369
- models[model] = slot;
1370
- // Per-turn parallel — same additive rule. Per-step too.
1516
+ costUsd += advisorCostThisMsg;
1517
+ // Fold advisor TOKEN totals into session/turn/step usage
1518
+ // alongside the cost fold above. Without this, per-model
1519
+ // sums diverge from session totals: Σ models[*].input_tokens
1520
+ // > session.usage.input_tokens by exactly the advisor
1521
+ // tokens (round 86a fixed cost parity but missed the
1522
+ // matching token parity).
1523
+ inputTokens += advisorInputThisMsg;
1524
+ outputTokens += advisorOutputThisMsg;
1525
+ cacheCreationTokens += advisorCacheCreationThisMsg;
1526
+ cacheReadTokens += advisorCacheReadThisMsg;
1371
1527
  if (currentTurn !== null) {
1372
- const turnSlot = currentTurn.models[model] ?? {
1528
+ currentTurn.input_tokens += advisorInputThisMsg;
1529
+ currentTurn.output_tokens += advisorOutputThisMsg;
1530
+ currentTurn.cache_creation_tokens += advisorCacheCreationThisMsg;
1531
+ currentTurn.cache_read_tokens += advisorCacheReadThisMsg;
1532
+ if (currentTurn.current_step !== undefined) {
1533
+ currentTurn.current_step.input_tokens += advisorInputThisMsg;
1534
+ currentTurn.current_step.output_tokens += advisorOutputThisMsg;
1535
+ currentTurn.current_step.cache_creation_tokens += advisorCacheCreationThisMsg;
1536
+ currentTurn.current_step.cache_read_tokens += advisorCacheReadThisMsg;
1537
+ }
1538
+ }
1539
+ // Per-model usage breakdown — element-wise additive across deltas
1540
+ // in the merge step. Empty / missing model name skipped (no
1541
+ // attribution possible).
1542
+ if (typeof model === "string" && model.length > 0) {
1543
+ const slot = models[model] ?? {
1373
1544
  count: 0,
1374
1545
  input_tokens: 0,
1375
1546
  output_tokens: 0,
@@ -1377,15 +1548,16 @@ function projectDelta(input) {
1377
1548
  cache_read_tokens: 0,
1378
1549
  cost_usd: 0,
1379
1550
  };
1380
- turnSlot.count += 1;
1381
- turnSlot.input_tokens += msgInput;
1382
- turnSlot.output_tokens += msgOutput;
1383
- turnSlot.cache_creation_tokens += msgCacheCreation;
1384
- turnSlot.cache_read_tokens += msgCacheRead;
1385
- turnSlot.cost_usd += msgCost;
1386
- currentTurn.models[model] = turnSlot;
1387
- if (currentTurn.current_step !== undefined) {
1388
- const stepSlot = currentTurn.current_step.models[model] ?? {
1551
+ slot.count += 1;
1552
+ slot.input_tokens += msgInput;
1553
+ slot.output_tokens += msgOutput;
1554
+ slot.cache_creation_tokens += msgCacheCreation;
1555
+ slot.cache_read_tokens += msgCacheRead;
1556
+ slot.cost_usd += msgCost;
1557
+ models[model] = slot;
1558
+ // Per-turn parallel — same additive rule. Per-step too.
1559
+ if (currentTurn !== null) {
1560
+ const turnSlot = currentTurn.models[model] ?? {
1389
1561
  count: 0,
1390
1562
  input_tokens: 0,
1391
1563
  output_tokens: 0,
@@ -1393,70 +1565,96 @@ function projectDelta(input) {
1393
1565
  cache_read_tokens: 0,
1394
1566
  cost_usd: 0,
1395
1567
  };
1396
- stepSlot.count += 1;
1397
- stepSlot.input_tokens += msgInput;
1398
- stepSlot.output_tokens += msgOutput;
1399
- stepSlot.cache_creation_tokens += msgCacheCreation;
1400
- stepSlot.cache_read_tokens += msgCacheRead;
1401
- stepSlot.cost_usd += msgCost;
1402
- currentTurn.current_step.models[model] = stepSlot;
1568
+ turnSlot.count += 1;
1569
+ turnSlot.input_tokens += msgInput;
1570
+ turnSlot.output_tokens += msgOutput;
1571
+ turnSlot.cache_creation_tokens += msgCacheCreation;
1572
+ turnSlot.cache_read_tokens += msgCacheRead;
1573
+ turnSlot.cost_usd += msgCost;
1574
+ currentTurn.models[model] = turnSlot;
1575
+ if (currentTurn.current_step !== undefined) {
1576
+ const stepSlot = currentTurn.current_step.models[model] ?? {
1577
+ count: 0,
1578
+ input_tokens: 0,
1579
+ output_tokens: 0,
1580
+ cache_creation_tokens: 0,
1581
+ cache_read_tokens: 0,
1582
+ cost_usd: 0,
1583
+ };
1584
+ stepSlot.count += 1;
1585
+ stepSlot.input_tokens += msgInput;
1586
+ stepSlot.output_tokens += msgOutput;
1587
+ stepSlot.cache_creation_tokens += msgCacheCreation;
1588
+ stepSlot.cache_read_tokens += msgCacheRead;
1589
+ stepSlot.cost_usd += msgCost;
1590
+ currentTurn.current_step.models[model] = stepSlot;
1591
+ }
1403
1592
  }
1404
1593
  }
1405
- }
1406
- // Context-tokens distribution (turn-bucketed)
1407
- if (usage !== undefined) {
1408
- const ctx = msgInput + msgCacheCreation + msgCacheRead;
1409
- if (ctx > 0) {
1410
- hasAssistantWithUsage = true;
1411
- const bucket = turnBucket(assistantTurnIndex);
1412
- const slot = contextTokensBuckets[bucket]
1413
- ?? { sum: 0, count: 0 };
1414
- slot.sum += ctx;
1415
- slot.count += 1;
1416
- contextTokensBuckets[bucket] = slot;
1417
- contextTokensLatest = ctx;
1418
- if (ctx > contextTokensPeak) {
1419
- contextTokensPeak = ctx;
1420
- }
1421
- // Per-turn samples — append in order; backend can
1422
- // reconstruct session-level buckets by concatenating
1423
- // every turn's samples in turn_index order.
1424
- // Per-step: single scalar (one sample per step since a
1425
- // step is bounded by exactly one assistant message).
1426
- if (currentTurn !== null) {
1427
- currentTurn.context_tokens_samples.push(ctx);
1428
- currentTurn.context_tokens_latest = ctx;
1429
- if (ctx > currentTurn.context_tokens_peak) {
1430
- currentTurn.context_tokens_peak = ctx;
1594
+ // Context-tokens distribution (turn-bucketed)
1595
+ if (usage !== undefined) {
1596
+ const ctx = msgInput + msgCacheCreation + msgCacheRead;
1597
+ if (ctx > 0) {
1598
+ hasAssistantWithUsage = true;
1599
+ const bucket = turnBucket(assistantTurnIndex);
1600
+ const slot = contextTokensBuckets[bucket]
1601
+ ?? { sum: 0, count: 0 };
1602
+ slot.sum += ctx;
1603
+ slot.count += 1;
1604
+ contextTokensBuckets[bucket] = slot;
1605
+ contextTokensLatest = ctx;
1606
+ if (ctx > contextTokensPeak) {
1607
+ contextTokensPeak = ctx;
1431
1608
  }
1432
- if (currentTurn.current_step !== undefined) {
1433
- currentTurn.current_step.context_tokens = ctx;
1609
+ // Per-turn samples append in order; backend can
1610
+ // reconstruct session-level buckets by concatenating
1611
+ // every turn's samples in turn_index order.
1612
+ // Per-step: single scalar (one sample per step since a
1613
+ // step is bounded by exactly one assistant message).
1614
+ if (currentTurn !== null) {
1615
+ currentTurn.context_tokens_samples.push(ctx);
1616
+ currentTurn.context_tokens_latest = ctx;
1617
+ if (ctx > currentTurn.context_tokens_peak) {
1618
+ currentTurn.context_tokens_peak = ctx;
1619
+ }
1620
+ if (currentTurn.current_step !== undefined) {
1621
+ currentTurn.current_step.context_tokens = ctx;
1622
+ }
1434
1623
  }
1435
1624
  }
1436
1625
  }
1437
- }
1438
- // ── Per-tool consumer: no-op ──────────────────────────────
1439
- // Per-tool token / cost attribution does not exist. tool_result
1440
- // bytes are recorded directly on `tools[T].output_size` at the
1441
- // tool_result handler in the user-msg branch (no consumer
1442
- // assistant N+1 attribution step needed). Anthropic-exact
1443
- // tokens (msgInput, msgCacheCreation, msgCacheRead, msgOutput)
1444
- // continue to flow into session/turn/step usage totals + per-
1445
- // model cost. priorUserMsgTextBytes / hasCcSplit / msgCC5m /
1446
- // msgCC1h / msgWebSearchReqs / advisorCostThisMsg references
1447
- // exist for backward-compat with state shape but are unused
1448
- // by per-tool attribution. msgWebSearchReqs cost still adds
1449
- // to the session msgCost via computeMessageCostUsd above.
1450
- void msgInput;
1451
- void msgCacheCreation;
1452
- void msgCacheRead;
1453
- void hasCcSplit;
1454
- void msgCC5m;
1455
- void msgCC1h;
1456
- void msgWebSearchReqs;
1457
- void advisorCostThisMsg;
1458
- void priorUserMsgTextBytes;
1459
- resultedToolUseIds = [];
1626
+ // ── Per-tool consumer: no-op ──────────────────────────────
1627
+ // Per-tool token / cost attribution does not exist. tool_result
1628
+ // bytes are recorded directly on `tools[T].output_size` at the
1629
+ // tool_result handler in the user-msg branch (no consumer
1630
+ // assistant N+1 attribution step needed). Anthropic-exact
1631
+ // tokens (msgInput, msgCacheCreation, msgCacheRead, msgOutput)
1632
+ // continue to flow into session/turn/step usage totals + per-
1633
+ // model cost. priorUserMsgTextBytes / hasCcSplit / msgCC5m /
1634
+ // msgCC1h / msgWebSearchReqs / advisorCostThisMsg references
1635
+ // exist for backward-compat with state shape but are unused
1636
+ // by per-tool attribution. msgWebSearchReqs cost still adds
1637
+ // to the session msgCost via computeMessageCostUsd above.
1638
+ void msgInput;
1639
+ void msgCacheCreation;
1640
+ void msgCacheRead;
1641
+ void hasCcSplit;
1642
+ void msgCC5m;
1643
+ void msgCC1h;
1644
+ void msgWebSearchReqs;
1645
+ void advisorCostThisMsg;
1646
+ void priorUserMsgTextBytes;
1647
+ resultedToolUseIds = [];
1648
+ } // end of `if (isFirstChunkOfMsg)` — per-message scalar work
1649
+ // Snapshot of `seenToolUseIds` taken at the start of this
1650
+ // chunk's content-block walks. Both Loop 1 (input_size
1651
+ // attribution) and Loop 2 (count + classifier flags) use
1652
+ // this snapshot to dedup — without it, Loop 1's add to the
1653
+ // running set would cause Loop 2 to skip the same id on
1654
+ // its own pass. New ids found in this chunk are committed
1655
+ // to the running set + `newToolUseIdsThisSlice` after both
1656
+ // loops finish.
1657
+ const seenToolUseIdsAtChunkStart = new Set(seenToolUseIds);
1460
1658
  // Walk content blocks for tool_use entries.
1461
1659
  // ── Per-tool emitter: byte counter ──────────────────────────
1462
1660
  // Each tool_use bumps the corresponding tool/mcp_server/skill/
@@ -1483,6 +1681,12 @@ function projectDelta(input) {
1483
1681
  || block.id.length === 0) {
1484
1682
  continue;
1485
1683
  }
1684
+ // tool_use_id dedup — skip if this tool_use was
1685
+ // already counted in a prior chunk of this msg_id
1686
+ // (or in a prior slice persisted via state).
1687
+ if (seenToolUseIdsAtChunkStart.has(block.id)) {
1688
+ continue;
1689
+ }
1486
1690
  const inputJsonBytes = Buffer.byteLength(JSON.stringify(block.input ?? {}), "utf-8");
1487
1691
  // Sub-classification — same mutual-exclusion rules as before.
1488
1692
  let bashBinary;
@@ -1527,7 +1731,9 @@ function projectDelta(input) {
1527
1731
  pendingToolUses[block.id] = pending;
1528
1732
  }
1529
1733
  }
1530
- void msgOutput; // output side is not per-tool attributed
1734
+ // Note: msgOutput is consumed inside the per-message gate
1735
+ // above (outputTokens accumulator). Its `void` no-op silencer
1736
+ // moved into the gate alongside the other locals.
1531
1737
  // Per-message tool-bucket flag accumulator for the classifier
1532
1738
  // retry state machine. Computed locally within this assistant
1533
1739
  // message; merged into currentTurn at the end.
@@ -1539,6 +1745,17 @@ function projectDelta(input) {
1539
1745
  }
1540
1746
  const block = blk;
1541
1747
  if (block.type === "tool_use" && typeof block.name === "string") {
1748
+ // tool_use_id dedup — skip if already counted in
1749
+ // a prior chunk of this msg_id (snapshot was taken
1750
+ // before Loop 1 ran so Loop 1's additions don't
1751
+ // pre-empt Loop 2's processing of the same id).
1752
+ // Empty/missing id falls through to count (current
1753
+ // behavior preserved: malformed ids still count).
1754
+ if (typeof block.id === "string"
1755
+ && block.id.length > 0
1756
+ && seenToolUseIdsAtChunkStart.has(block.id)) {
1757
+ continue;
1758
+ }
1542
1759
  const toolName = block.name;
1543
1760
  // Per-tool breakdown count — also update mcp_servers,
1544
1761
  // bash_binaries, skills, sub_agents in the same step.
@@ -1667,15 +1884,27 @@ function projectDelta(input) {
1667
1884
  (currentTurn.current_step.file_change_counts[filePath] ?? 0) + 1;
1668
1885
  }
1669
1886
  }
1670
- }
1671
- const lang = getLanguageFromPath(filePath);
1672
- if (lang !== null) {
1673
- languages[lang] = (languages[lang] ?? 0) + 1;
1674
- if (currentTurn !== null) {
1675
- currentTurn.languages[lang] = (currentTurn.languages[lang] ?? 0) + 1;
1676
- if (currentTurn.current_step !== undefined) {
1677
- currentTurn.current_step.languages[lang] =
1678
- (currentTurn.current_step.languages[lang] ?? 0) + 1;
1887
+ // Language counter is INSIDE the Edit/Write gate so it
1888
+ // tracks files actually MODIFIED (parity with Codex
1889
+ // `apply_patch` only-counts at projection.ts:863-870).
1890
+ // Without the gate, every `Read` (and any future
1891
+ // file_path-bearing tool) bumped `languages[lang]` —
1892
+ // same logical session shipped
1893
+ // `code_changes.{files_modified: 3, languages.TypeScript: 50}`
1894
+ // (47 Reads + 3 Edits) on Claude while Codex shipped
1895
+ // `{files_modified: 3, languages.TypeScript: 3}`.
1896
+ // Backend cross-client `Σ languages` joins broke and the
1897
+ // wire was internally inconsistent (50 ≠ 3 for the same
1898
+ // category aggregate).
1899
+ const lang = getLanguageFromPath(filePath);
1900
+ if (lang !== null) {
1901
+ languages[lang] = (languages[lang] ?? 0) + 1;
1902
+ if (currentTurn !== null) {
1903
+ currentTurn.languages[lang] = (currentTurn.languages[lang] ?? 0) + 1;
1904
+ if (currentTurn.current_step !== undefined) {
1905
+ currentTurn.current_step.languages[lang] =
1906
+ (currentTurn.current_step.languages[lang] ?? 0) + 1;
1907
+ }
1679
1908
  }
1680
1909
  }
1681
1910
  }
@@ -1767,8 +1996,37 @@ function projectDelta(input) {
1767
1996
  }
1768
1997
  }
1769
1998
  }
1999
+ // Commit tool_use_ids processed in this chunk to the
2000
+ // running set + slice output. Runs AFTER both Loop 1 and
2001
+ // Loop 2 so each loop saw the consistent pre-chunk snapshot.
2002
+ // Subsequent chunks of the same msg_id (and the next slice)
2003
+ // will then skip these ids via `seenToolUseIdsAtChunkStart`.
2004
+ if (Array.isArray(content)) {
2005
+ for (const blk of content) {
2006
+ if (blk === null || typeof blk !== "object") {
2007
+ continue;
2008
+ }
2009
+ const block = blk;
2010
+ if (block.type !== "tool_use"
2011
+ || typeof block.id !== "string"
2012
+ || block.id.length === 0) {
2013
+ continue;
2014
+ }
2015
+ if (!seenToolUseIds.has(block.id)) {
2016
+ seenToolUseIds.add(block.id);
2017
+ newToolUseIdsThisSlice.push(block.id);
2018
+ }
2019
+ }
2020
+ }
1770
2021
  // End-of-message classifier update: retry detection +
1771
- // per-turn cost accumulation. Only if we have an open turn.
2022
+ // per-turn cost accumulation. Retry detection runs on every
2023
+ // chunk (msgBuckets reflects this chunk's tool_uses); cost
2024
+ // accumulation is gated by `msgCost` being non-zero, which
2025
+ // only happens on the first chunk of a msg_id (subsequent
2026
+ // chunks keep msgCost at its outer-scope default of 0 since
2027
+ // the per-message gate is closed). Same effect as an explicit
2028
+ // `if (isFirstChunkOfMsg)` gate but without re-introducing
2029
+ // the dependency on the flag here.
1772
2030
  if (currentTurn !== null) {
1773
2031
  if (msgBuckets.edit) {
1774
2032
  if (currentTurn.saw_bash_after_edit) {
@@ -1780,9 +2038,18 @@ function projectDelta(input) {
1780
2038
  if (msgBuckets.bash && currentTurn.saw_edit_pending_bash) {
1781
2039
  currentTurn.saw_bash_after_edit = true;
1782
2040
  }
1783
- currentTurn.cost_usd += msgCost;
2041
+ // Include advisor sub-call cost (`advisorCostThisMsg`,
2042
+ // computed above with the parent's msgCost). Session-level
2043
+ // `costUsd` adds it at line ~1747; turn-level must mirror
2044
+ // or backend's reconstruction invariant breaks:
2045
+ // `Σ SessionTurnAnalytics.usage.cost_usd` would be less
2046
+ // than `SessionAnalytics.usage.cost_usd` for any session
2047
+ // that triggered server-side advisor sub-calls. On
2048
+ // subsequent chunks msgCost = advisorCostThisMsg = 0
2049
+ // (per-message gate not entered), so this is a no-op.
2050
+ currentTurn.cost_usd += msgCost + advisorCostThisMsg;
1784
2051
  if (currentTurn.current_step !== undefined) {
1785
- currentTurn.current_step.cost_usd += msgCost;
2052
+ currentTurn.current_step.cost_usd += msgCost + advisorCostThisMsg;
1786
2053
  }
1787
2054
  }
1788
2055
  }
@@ -2213,12 +2480,22 @@ function projectDeltaInternal(input) {
2213
2480
  // (set if a turn straddled the prior slice boundary).
2214
2481
  let nextTurnIndex = input.priorNextTurnIndex ?? 1;
2215
2482
  let hasOpenTurn = input.priorCurrentTurn !== undefined;
2216
- // Anthropic msg_id dedup — must mirror projectDelta exactly so file_path
2217
- // counts / distinct paths don't pick up duplicated assistant lines (the
2218
- // same API response can be persisted on multiple JSONL lines; see
2219
- // MessageBody.id docstring).
2483
+ // Anthropic msg_id dedup — mirrors projectDelta. Used to track WHICH
2484
+ // msg_ids are new in this slice for `new_assistant_message_ids`
2485
+ // emission (merge appends to state.internal.seen_assistant_message_ids).
2486
+ // Does NOT gate content-block processing — same streaming-split
2487
+ // semantic applies here: a single API response's content blocks may
2488
+ // be split across multiple JSONL lines sharing the same msg_id but
2489
+ // carrying DIFFERENT tool_use blocks per chunk. Per-block dedup uses
2490
+ // `seenToolUseIds` (below).
2220
2491
  const seenAssistantMessageIds = new Set(input.priorSeenAssistantMessageIds ?? []);
2221
2492
  const newAssistantMessageIdsThisSlice = [];
2493
+ // tool_use_id dedup — mirrors projectDelta. file_path counters
2494
+ // (file_path_change_counts, distinct_file_paths_seen) must not
2495
+ // double-count when a single Edit/Write tool_use re-appears across
2496
+ // streaming-split chunks or true-duplicate re-emits.
2497
+ const seenToolUseIds = new Set(input.priorSeenToolUseIds ?? []);
2498
+ const newToolUseIdsThisSlice = [];
2222
2499
  for (const line of input.lines) {
2223
2500
  const lineTsMs = tsMs(line.timestamp);
2224
2501
  if (lineTsMs !== null) {
@@ -2250,21 +2527,26 @@ function projectDeltaInternal(input) {
2250
2527
  if (line.type !== "assistant" || line.message === undefined) {
2251
2528
  continue;
2252
2529
  }
2253
- // Apply the same msg_id dedup as projectDelta so distinct/file_path
2254
- // counts stay consistent. We do this AFTER the latest-ts update
2255
- // because timestamp tracking is harmless to repeat (same ts).
2530
+ // msg_id tracking record on first occurrence per slice so
2531
+ // `new_assistant_message_ids` is emitted in the return. Unlike
2532
+ // the pre-fix code (which `continue`'d here, dropping content
2533
+ // blocks of subsequent chunks), we now walk content blocks on
2534
+ // EVERY chunk and let `seenToolUseIds` handle block-level dedup.
2256
2535
  const msgIdInternal = line.message.id;
2257
2536
  if (typeof msgIdInternal === "string" && msgIdInternal.length > 0) {
2258
- if (seenAssistantMessageIds.has(msgIdInternal)) {
2259
- continue;
2537
+ if (!seenAssistantMessageIds.has(msgIdInternal)) {
2538
+ seenAssistantMessageIds.add(msgIdInternal);
2539
+ newAssistantMessageIdsThisSlice.push(msgIdInternal);
2260
2540
  }
2261
- seenAssistantMessageIds.add(msgIdInternal);
2262
- newAssistantMessageIdsThisSlice.push(msgIdInternal);
2263
2541
  }
2264
2542
  const content = line.message.content;
2265
2543
  if (!Array.isArray(content)) {
2266
2544
  continue;
2267
2545
  }
2546
+ // Snapshot seenToolUseIds so loop iterations within this chunk
2547
+ // can dedup against the pre-chunk state — symmetric with
2548
+ // projectDelta's `seenToolUseIdsAtChunkStart`.
2549
+ const seenToolUseIdsAtChunkStart = new Set(seenToolUseIds);
2268
2550
  for (const blk of content) {
2269
2551
  if (blk === null || typeof blk !== "object") {
2270
2552
  continue;
@@ -2273,7 +2555,20 @@ function projectDeltaInternal(input) {
2273
2555
  if (block.type !== "tool_use" || typeof block.name !== "string") {
2274
2556
  continue;
2275
2557
  }
2558
+ if (typeof block.id !== "string" || block.id.length === 0) {
2559
+ continue;
2560
+ }
2561
+ // tool_use_id dedup — skip if seen in a prior chunk or slice.
2562
+ if (seenToolUseIdsAtChunkStart.has(block.id)) {
2563
+ continue;
2564
+ }
2276
2565
  if (block.name !== "Edit" && block.name !== "Write") {
2566
+ // Track the id even for non-file tool_uses so cross-slice
2567
+ // dedup catches them (symmetry with projectDelta).
2568
+ if (!seenToolUseIds.has(block.id)) {
2569
+ seenToolUseIds.add(block.id);
2570
+ newToolUseIdsThisSlice.push(block.id);
2571
+ }
2277
2572
  continue;
2278
2573
  }
2279
2574
  const fp = block.input?.file_path;
@@ -2281,6 +2576,12 @@ function projectDeltaInternal(input) {
2281
2576
  distinct.add(fp);
2282
2577
  filePathChangeCounts[fp] = (filePathChangeCounts[fp] ?? 0) + 1;
2283
2578
  }
2579
+ // Commit this Edit/Write tool_use_id to running set + slice
2580
+ // output so the next chunk/slice skips it.
2581
+ if (!seenToolUseIds.has(block.id)) {
2582
+ seenToolUseIds.add(block.id);
2583
+ newToolUseIdsThisSlice.push(block.id);
2584
+ }
2284
2585
  }
2285
2586
  }
2286
2587
  return {
@@ -2292,6 +2593,9 @@ function projectDeltaInternal(input) {
2292
2593
  ...(newAssistantMessageIdsThisSlice.length > 0
2293
2594
  ? { new_assistant_message_ids: newAssistantMessageIdsThisSlice }
2294
2595
  : {}),
2596
+ ...(newToolUseIdsThisSlice.length > 0
2597
+ ? { new_tool_use_ids: newToolUseIdsThisSlice }
2598
+ : {}),
2295
2599
  };
2296
2600
  }
2297
2601
  //# sourceMappingURL=projection.js.map