@caupulican/pi-adaptative 0.81.39 → 0.81.40

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 (667) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +2 -0
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +2 -0
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -1
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +2 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -1
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +3 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +9 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +290 -107
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +357 -122
  13. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +2 -2
  14. package/dist/cli/args.d.ts +2 -0
  15. package/dist/cli/args.d.ts.map +1 -1
  16. package/dist/cli/args.js +9 -0
  17. package/dist/cli/args.js.map +1 -1
  18. package/dist/core/agent-directory-layout.d.ts +15 -0
  19. package/dist/core/agent-directory-layout.d.ts.map +1 -0
  20. package/dist/core/agent-directory-layout.js +72 -0
  21. package/dist/core/agent-directory-layout.js.map +1 -0
  22. package/dist/core/agent-paths.d.ts +19 -2
  23. package/dist/core/agent-paths.d.ts.map +1 -1
  24. package/dist/core/agent-paths.js +67 -4
  25. package/dist/core/agent-paths.js.map +1 -1
  26. package/dist/core/agent-session-runtime.d.ts +52 -8
  27. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  28. package/dist/core/agent-session-runtime.js +397 -79
  29. package/dist/core/agent-session-runtime.js.map +1 -1
  30. package/dist/core/agent-session-services.d.ts +1 -0
  31. package/dist/core/agent-session-services.d.ts.map +1 -1
  32. package/dist/core/agent-session-services.js +1 -0
  33. package/dist/core/agent-session-services.js.map +1 -1
  34. package/dist/core/agent-session.d.ts +71 -136
  35. package/dist/core/agent-session.d.ts.map +1 -1
  36. package/dist/core/agent-session.js +477 -1318
  37. package/dist/core/agent-session.js.map +1 -1
  38. package/dist/core/autonomy/approval-gate.d.ts +3 -2
  39. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  40. package/dist/core/autonomy/approval-gate.js +6 -28
  41. package/dist/core/autonomy/approval-gate.js.map +1 -1
  42. package/dist/core/autonomy/bounded-completion.d.ts +1 -1
  43. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  44. package/dist/core/autonomy/bounded-completion.js +1 -1
  45. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  46. package/dist/core/autonomy/contracts.d.ts +20 -12
  47. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  48. package/dist/core/autonomy/contracts.js.map +1 -1
  49. package/dist/core/autonomy/foreground-envelope.d.ts +8 -2
  50. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  51. package/dist/core/autonomy/foreground-envelope.js +8 -25
  52. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  53. package/dist/core/autonomy/gates.d.ts.map +1 -1
  54. package/dist/core/autonomy/gates.js +8 -10
  55. package/dist/core/autonomy/gates.js.map +1 -1
  56. package/dist/core/autonomy/lane-private-paths.d.ts +2 -0
  57. package/dist/core/autonomy/lane-private-paths.d.ts.map +1 -0
  58. package/dist/core/autonomy/lane-private-paths.js +16 -0
  59. package/dist/core/autonomy/lane-private-paths.js.map +1 -0
  60. package/dist/core/autonomy/lane-tool-surface.d.ts +10 -0
  61. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  62. package/dist/core/autonomy/lane-tool-surface.js +46 -8
  63. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  64. package/dist/core/autonomy/lane-tracker.d.ts +14 -0
  65. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  66. package/dist/core/autonomy/lane-tracker.js +38 -0
  67. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  68. package/dist/core/autonomy/risk-assessment.d.ts +1 -2
  69. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  70. package/dist/core/autonomy/risk-assessment.js +1 -123
  71. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  72. package/dist/core/autonomy/session-lane-record.d.ts +4 -4
  73. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
  74. package/dist/core/autonomy/session-lane-record.js +16 -29
  75. package/dist/core/autonomy/session-lane-record.js.map +1 -1
  76. package/dist/core/autonomy/telemetry-events.d.ts +1 -1
  77. package/dist/core/autonomy/telemetry-events.d.ts.map +1 -1
  78. package/dist/core/autonomy/telemetry-events.js +1 -1
  79. package/dist/core/autonomy/telemetry-events.js.map +1 -1
  80. package/dist/core/autonomy-telemetry.d.ts +7 -7
  81. package/dist/core/autonomy-telemetry.d.ts.map +1 -1
  82. package/dist/core/autonomy-telemetry.js +9 -9
  83. package/dist/core/autonomy-telemetry.js.map +1 -1
  84. package/dist/core/background-lane-controller.d.ts +54 -236
  85. package/dist/core/background-lane-controller.d.ts.map +1 -1
  86. package/dist/core/background-lane-controller.js +140 -1273
  87. package/dist/core/background-lane-controller.js.map +1 -1
  88. package/dist/core/capability-contract.d.ts +4 -0
  89. package/dist/core/capability-contract.d.ts.map +1 -0
  90. package/dist/core/capability-contract.js +28 -0
  91. package/dist/core/capability-contract.js.map +1 -0
  92. package/dist/core/compaction-controller.d.ts +90 -0
  93. package/dist/core/compaction-controller.d.ts.map +1 -0
  94. package/dist/core/compaction-controller.js +452 -0
  95. package/dist/core/compaction-controller.js.map +1 -0
  96. package/dist/core/compaction-support.d.ts +12 -11
  97. package/dist/core/compaction-support.d.ts.map +1 -1
  98. package/dist/core/compaction-support.js.map +1 -1
  99. package/dist/core/context/context-artifacts.d.ts +6 -2
  100. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  101. package/dist/core/context/context-artifacts.js +34 -2
  102. package/dist/core/context/context-artifacts.js.map +1 -1
  103. package/dist/core/context/context-store-retention.d.ts +8 -2
  104. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  105. package/dist/core/context/context-store-retention.js +158 -35
  106. package/dist/core/context/context-store-retention.js.map +1 -1
  107. package/dist/core/context/okf-memory.d.ts.map +1 -1
  108. package/dist/core/context/okf-memory.js +9 -11
  109. package/dist/core/context/okf-memory.js.map +1 -1
  110. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  111. package/dist/core/context/sqlite-runtime-index.js +6 -8
  112. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  113. package/dist/core/context-gc.d.ts +3 -0
  114. package/dist/core/context-gc.d.ts.map +1 -1
  115. package/dist/core/context-gc.js +10 -5
  116. package/dist/core/context-gc.js.map +1 -1
  117. package/dist/core/context-pipeline.d.ts.map +1 -1
  118. package/dist/core/context-pipeline.js +9 -4
  119. package/dist/core/context-pipeline.js.map +1 -1
  120. package/dist/core/default-tool-surface.d.ts.map +1 -1
  121. package/dist/core/default-tool-surface.js +1 -0
  122. package/dist/core/default-tool-surface.js.map +1 -1
  123. package/dist/core/delegation/managed-lane-controller.d.ts +34 -0
  124. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -0
  125. package/dist/core/delegation/managed-lane-controller.js +217 -0
  126. package/dist/core/delegation/managed-lane-controller.js.map +1 -0
  127. package/dist/core/delegation/session-worker-claim.d.ts +44 -0
  128. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -0
  129. package/dist/core/delegation/session-worker-claim.js +98 -0
  130. package/dist/core/delegation/session-worker-claim.js.map +1 -0
  131. package/dist/core/delegation/worker-actions.d.ts +7 -5
  132. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  133. package/dist/core/delegation/worker-actions.js +20 -4
  134. package/dist/core/delegation/worker-actions.js.map +1 -1
  135. package/dist/core/delegation/{worker-result.d.ts → worker-claim.d.ts} +13 -13
  136. package/dist/core/delegation/worker-claim.d.ts.map +1 -0
  137. package/dist/core/delegation/{worker-result.js → worker-claim.js} +61 -51
  138. package/dist/core/delegation/worker-claim.js.map +1 -0
  139. package/dist/core/delegation/worker-delegation-controller.d.ts +110 -0
  140. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -0
  141. package/dist/core/delegation/worker-delegation-controller.js +861 -0
  142. package/dist/core/delegation/worker-delegation-controller.js.map +1 -0
  143. package/dist/core/delegation/worker-delegation-request.d.ts +11 -0
  144. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -0
  145. package/dist/core/delegation/worker-delegation-request.js +2 -0
  146. package/dist/core/delegation/worker-delegation-request.js.map +1 -0
  147. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +40 -0
  148. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -0
  149. package/dist/core/delegation/worker-dispatch-scheduler.js +93 -0
  150. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -0
  151. package/dist/core/delegation/worker-execution-policy.d.ts +62 -0
  152. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -0
  153. package/dist/core/delegation/worker-execution-policy.js +160 -0
  154. package/dist/core/delegation/worker-execution-policy.js.map +1 -0
  155. package/dist/core/delegation/worker-lane-projection.d.ts +14 -0
  156. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -0
  157. package/dist/core/delegation/worker-lane-projection.js +100 -0
  158. package/dist/core/delegation/worker-lane-projection.js.map +1 -0
  159. package/dist/core/delegation/worker-lifecycle.d.ts +97 -0
  160. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -0
  161. package/dist/core/delegation/worker-lifecycle.js +339 -0
  162. package/dist/core/delegation/worker-lifecycle.js.map +1 -0
  163. package/dist/core/delegation/worker-notification-coordinator.d.ts +36 -0
  164. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -0
  165. package/dist/core/delegation/worker-notification-coordinator.js +90 -0
  166. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -0
  167. package/dist/core/delegation/worker-profile-resolver.d.ts +55 -0
  168. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -0
  169. package/dist/core/delegation/worker-profile-resolver.js +113 -0
  170. package/dist/core/delegation/worker-profile-resolver.js.map +1 -0
  171. package/dist/core/delegation/worker-runner.d.ts +25 -14
  172. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  173. package/dist/core/delegation/worker-runner.js +122 -53
  174. package/dist/core/delegation/worker-runner.js.map +1 -1
  175. package/dist/core/delegation/worker-task-label.d.ts +3 -0
  176. package/dist/core/delegation/worker-task-label.d.ts.map +1 -0
  177. package/dist/core/delegation/worker-task-label.js +11 -0
  178. package/dist/core/delegation/worker-task-label.js.map +1 -0
  179. package/dist/core/doctor.d.ts +8 -0
  180. package/dist/core/doctor.d.ts.map +1 -1
  181. package/dist/core/doctor.js +32 -1
  182. package/dist/core/doctor.js.map +1 -1
  183. package/dist/core/extensions/index.d.ts +1 -1
  184. package/dist/core/extensions/index.d.ts.map +1 -1
  185. package/dist/core/extensions/index.js.map +1 -1
  186. package/dist/core/extensions/loader.d.ts +5 -1
  187. package/dist/core/extensions/loader.d.ts.map +1 -1
  188. package/dist/core/extensions/loader.js +50 -15
  189. package/dist/core/extensions/loader.js.map +1 -1
  190. package/dist/core/extensions/runner.d.ts.map +1 -1
  191. package/dist/core/extensions/runner.js +6 -0
  192. package/dist/core/extensions/runner.js.map +1 -1
  193. package/dist/core/extensions/storage.d.ts +8 -0
  194. package/dist/core/extensions/storage.d.ts.map +1 -0
  195. package/dist/core/extensions/storage.js +28 -0
  196. package/dist/core/extensions/storage.js.map +1 -0
  197. package/dist/core/extensions/types.d.ts +70 -21
  198. package/dist/core/extensions/types.d.ts.map +1 -1
  199. package/dist/core/extensions/types.js.map +1 -1
  200. package/dist/core/failure-corpus.d.ts +1 -15
  201. package/dist/core/failure-corpus.d.ts.map +1 -1
  202. package/dist/core/failure-corpus.js +9 -37
  203. package/dist/core/failure-corpus.js.map +1 -1
  204. package/dist/core/foreground-recovery-controller.d.ts +52 -0
  205. package/dist/core/foreground-recovery-controller.d.ts.map +1 -0
  206. package/dist/core/foreground-recovery-controller.js +111 -0
  207. package/dist/core/foreground-recovery-controller.js.map +1 -0
  208. package/dist/core/gateways/channel-provider.d.ts +3 -0
  209. package/dist/core/gateways/channel-provider.d.ts.map +1 -1
  210. package/dist/core/gateways/channel-provider.js +31 -6
  211. package/dist/core/gateways/channel-provider.js.map +1 -1
  212. package/dist/core/goals/goal-acceptance.d.ts +11 -0
  213. package/dist/core/goals/goal-acceptance.d.ts.map +1 -0
  214. package/dist/core/goals/goal-acceptance.js +25 -0
  215. package/dist/core/goals/goal-acceptance.js.map +1 -0
  216. package/dist/core/goals/goal-auto-continue-controller.d.ts +27 -0
  217. package/dist/core/goals/goal-auto-continue-controller.d.ts.map +1 -0
  218. package/dist/core/goals/goal-auto-continue-controller.js +93 -0
  219. package/dist/core/goals/goal-auto-continue-controller.js.map +1 -0
  220. package/dist/core/goals/goal-continuation-controller.d.ts +1 -1
  221. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  222. package/dist/core/goals/goal-continuation-controller.js +10 -0
  223. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  224. package/dist/core/goals/goal-continuation-prompt.d.ts +1 -1
  225. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  226. package/dist/core/goals/goal-continuation-prompt.js +6 -6
  227. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  228. package/dist/core/goals/goal-lifecycle.d.ts +20 -0
  229. package/dist/core/goals/goal-lifecycle.d.ts.map +1 -0
  230. package/dist/core/goals/goal-lifecycle.js +17 -0
  231. package/dist/core/goals/goal-lifecycle.js.map +1 -0
  232. package/dist/core/goals/goal-runtime-snapshot.d.ts +11 -9
  233. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  234. package/dist/core/goals/goal-runtime-snapshot.js +10 -8
  235. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  236. package/dist/core/goals/goal-state.d.ts +1 -0
  237. package/dist/core/goals/goal-state.d.ts.map +1 -1
  238. package/dist/core/goals/goal-state.js +7 -10
  239. package/dist/core/goals/goal-state.js.map +1 -1
  240. package/dist/core/goals/goal-tool-core.d.ts +8 -6
  241. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  242. package/dist/core/goals/goal-tool-core.js +11 -41
  243. package/dist/core/goals/goal-tool-core.js.map +1 -1
  244. package/dist/core/goals/session-goal-state.d.ts +2 -4
  245. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  246. package/dist/core/goals/session-goal-state.js +10 -33
  247. package/dist/core/goals/session-goal-state.js.map +1 -1
  248. package/dist/core/human-input.d.ts +100 -0
  249. package/dist/core/human-input.d.ts.map +1 -0
  250. package/dist/core/human-input.js +362 -0
  251. package/dist/core/human-input.js.map +1 -0
  252. package/dist/core/index.d.ts +9 -1
  253. package/dist/core/index.d.ts.map +1 -1
  254. package/dist/core/index.js +9 -1
  255. package/dist/core/index.js.map +1 -1
  256. package/dist/core/{telemetry.d.ts → install-telemetry.d.ts} +1 -1
  257. package/dist/core/install-telemetry.d.ts.map +1 -0
  258. package/dist/core/{telemetry.js → install-telemetry.js} +1 -1
  259. package/dist/core/install-telemetry.js.map +1 -0
  260. package/dist/core/keybindings.d.ts +17 -1
  261. package/dist/core/keybindings.d.ts.map +1 -1
  262. package/dist/core/keybindings.js +24 -6
  263. package/dist/core/keybindings.js.map +1 -1
  264. package/dist/core/learning/learning-audit.d.ts +2 -4
  265. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  266. package/dist/core/learning/learning-audit.js +9 -29
  267. package/dist/core/learning/learning-audit.js.map +1 -1
  268. package/dist/core/learning/learning-gate.d.ts.map +1 -1
  269. package/dist/core/learning/learning-gate.js +1 -6
  270. package/dist/core/learning/learning-gate.js.map +1 -1
  271. package/dist/core/learning/session-learning-decision.d.ts +2 -4
  272. package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
  273. package/dist/core/learning/session-learning-decision.js +9 -29
  274. package/dist/core/learning/session-learning-decision.js.map +1 -1
  275. package/dist/core/memory-controller.d.ts.map +1 -1
  276. package/dist/core/memory-controller.js +4 -4
  277. package/dist/core/memory-controller.js.map +1 -1
  278. package/dist/core/model-capability.d.ts.map +1 -1
  279. package/dist/core/model-capability.js +1 -0
  280. package/dist/core/model-capability.js.map +1 -1
  281. package/dist/core/model-resolver.d.ts +1 -1
  282. package/dist/core/model-resolver.d.ts.map +1 -1
  283. package/dist/core/model-resolver.js +6 -9
  284. package/dist/core/model-resolver.js.map +1 -1
  285. package/dist/core/model-router/intent-classifier.d.ts +1 -4
  286. package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
  287. package/dist/core/model-router/intent-classifier.js +1 -156
  288. package/dist/core/model-router/intent-classifier.js.map +1 -1
  289. package/dist/core/model-router-controller.d.ts.map +1 -1
  290. package/dist/core/model-router-controller.js +2 -2
  291. package/dist/core/model-router-controller.js.map +1 -1
  292. package/dist/core/model-selection-controller.d.ts +4 -4
  293. package/dist/core/model-selection-controller.d.ts.map +1 -1
  294. package/dist/core/model-selection-controller.js.map +1 -1
  295. package/dist/core/model-tool-protocol.d.ts +23 -0
  296. package/dist/core/model-tool-protocol.d.ts.map +1 -0
  297. package/dist/core/model-tool-protocol.js +43 -0
  298. package/dist/core/model-tool-protocol.js.map +1 -0
  299. package/dist/core/models/adaptation-store.d.ts +4 -6
  300. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  301. package/dist/core/models/adaptation-store.js +95 -83
  302. package/dist/core/models/adaptation-store.js.map +1 -1
  303. package/dist/core/models/fitness-store.d.ts +2 -12
  304. package/dist/core/models/fitness-store.d.ts.map +1 -1
  305. package/dist/core/models/fitness-store.js +84 -57
  306. package/dist/core/models/fitness-store.js.map +1 -1
  307. package/dist/core/models/host-state-store.d.ts +34 -0
  308. package/dist/core/models/host-state-store.d.ts.map +1 -0
  309. package/dist/core/models/host-state-store.js +86 -0
  310. package/dist/core/models/host-state-store.js.map +1 -0
  311. package/dist/core/orchestration/agent-resume.d.ts +24 -0
  312. package/dist/core/orchestration/agent-resume.d.ts.map +1 -0
  313. package/dist/core/orchestration/agent-resume.js +90 -0
  314. package/dist/core/orchestration/agent-resume.js.map +1 -0
  315. package/dist/core/orchestration/capability-gateway.d.ts +61 -0
  316. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -0
  317. package/dist/core/orchestration/capability-gateway.js +152 -0
  318. package/dist/core/orchestration/capability-gateway.js.map +1 -0
  319. package/dist/core/orchestration/contracts.d.ts +343 -0
  320. package/dist/core/orchestration/contracts.d.ts.map +1 -0
  321. package/dist/core/orchestration/contracts.js +135 -0
  322. package/dist/core/orchestration/contracts.js.map +1 -0
  323. package/dist/core/orchestration/delegation-ledger.d.ts +56 -0
  324. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -0
  325. package/dist/core/orchestration/delegation-ledger.js +197 -0
  326. package/dist/core/orchestration/delegation-ledger.js.map +1 -0
  327. package/dist/core/orchestration/event-store.d.ts +83 -0
  328. package/dist/core/orchestration/event-store.d.ts.map +1 -0
  329. package/dist/core/orchestration/event-store.js +560 -0
  330. package/dist/core/orchestration/event-store.js.map +1 -0
  331. package/dist/core/orchestration/lane-tool-manifests.d.ts +9 -0
  332. package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -0
  333. package/dist/core/orchestration/lane-tool-manifests.js +37 -0
  334. package/dist/core/orchestration/lane-tool-manifests.js.map +1 -0
  335. package/dist/core/orchestration/model-binding.d.ts +15 -0
  336. package/dist/core/orchestration/model-binding.d.ts.map +1 -0
  337. package/dist/core/orchestration/model-binding.js +29 -0
  338. package/dist/core/orchestration/model-binding.js.map +1 -0
  339. package/dist/core/orchestration/policy-compiler.d.ts +49 -0
  340. package/dist/core/orchestration/policy-compiler.d.ts.map +1 -0
  341. package/dist/core/orchestration/policy-compiler.js +191 -0
  342. package/dist/core/orchestration/policy-compiler.js.map +1 -0
  343. package/dist/core/orchestration/policy-gate.d.ts +18 -0
  344. package/dist/core/orchestration/policy-gate.d.ts.map +1 -0
  345. package/dist/core/orchestration/policy-gate.js +24 -0
  346. package/dist/core/orchestration/policy-gate.js.map +1 -0
  347. package/dist/core/orchestration/profile-registry.d.ts +51 -0
  348. package/dist/core/orchestration/profile-registry.d.ts.map +1 -0
  349. package/dist/core/orchestration/profile-registry.js +393 -0
  350. package/dist/core/orchestration/profile-registry.js.map +1 -0
  351. package/dist/core/orchestration/profile-store.d.ts +31 -0
  352. package/dist/core/orchestration/profile-store.d.ts.map +1 -0
  353. package/dist/core/orchestration/profile-store.js +136 -0
  354. package/dist/core/orchestration/profile-store.js.map +1 -0
  355. package/dist/core/orchestration/risk-budget.d.ts +9 -0
  356. package/dist/core/orchestration/risk-budget.d.ts.map +1 -0
  357. package/dist/core/orchestration/risk-budget.js +48 -0
  358. package/dist/core/orchestration/risk-budget.js.map +1 -0
  359. package/dist/core/orchestration/task-runtime.d.ts +168 -0
  360. package/dist/core/orchestration/task-runtime.d.ts.map +1 -0
  361. package/dist/core/orchestration/task-runtime.js +1600 -0
  362. package/dist/core/orchestration/task-runtime.js.map +1 -0
  363. package/dist/core/orchestration/work-state-projection.d.ts +32 -0
  364. package/dist/core/orchestration/work-state-projection.d.ts.map +1 -0
  365. package/dist/core/orchestration/work-state-projection.js +116 -0
  366. package/dist/core/orchestration/work-state-projection.js.map +1 -0
  367. package/dist/core/orchestration/worker-execution-contract.d.ts +21 -0
  368. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -0
  369. package/dist/core/orchestration/worker-execution-contract.js +179 -0
  370. package/dist/core/orchestration/worker-execution-contract.js.map +1 -0
  371. package/dist/core/orchestration/worker-result-adapter.d.ts +21 -0
  372. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -0
  373. package/dist/core/orchestration/worker-result-adapter.js +109 -0
  374. package/dist/core/orchestration/worker-result-adapter.js.map +1 -0
  375. package/dist/core/process-identity.d.ts +11 -0
  376. package/dist/core/process-identity.d.ts.map +1 -0
  377. package/dist/core/process-identity.js +26 -0
  378. package/dist/core/process-identity.js.map +1 -0
  379. package/dist/core/process-matrix/codes.d.ts +14 -3
  380. package/dist/core/process-matrix/codes.d.ts.map +1 -1
  381. package/dist/core/process-matrix/codes.js.map +1 -1
  382. package/dist/core/process-matrix/resume-launcher.d.ts +26 -0
  383. package/dist/core/process-matrix/resume-launcher.d.ts.map +1 -0
  384. package/dist/core/process-matrix/resume-launcher.js +62 -0
  385. package/dist/core/process-matrix/resume-launcher.js.map +1 -0
  386. package/dist/core/process-matrix/runtime.d.ts +37 -22
  387. package/dist/core/process-matrix/runtime.d.ts.map +1 -1
  388. package/dist/core/process-matrix/runtime.js +399 -76
  389. package/dist/core/process-matrix/runtime.js.map +1 -1
  390. package/dist/core/process-matrix/store.d.ts +5 -2
  391. package/dist/core/process-matrix/store.d.ts.map +1 -1
  392. package/dist/core/process-matrix/store.js +123 -14
  393. package/dist/core/process-matrix/store.js.map +1 -1
  394. package/dist/core/process-matrix/supervisor.d.ts +10 -7
  395. package/dist/core/process-matrix/supervisor.d.ts.map +1 -1
  396. package/dist/core/process-matrix/supervisor.js +45 -14
  397. package/dist/core/process-matrix/supervisor.js.map +1 -1
  398. package/dist/core/profile-registry.d.ts.map +1 -1
  399. package/dist/core/profile-registry.js +6 -8
  400. package/dist/core/profile-registry.js.map +1 -1
  401. package/dist/core/reflection-controller.d.ts +4 -4
  402. package/dist/core/reflection-controller.d.ts.map +1 -1
  403. package/dist/core/reflection-controller.js +2 -1
  404. package/dist/core/reflection-controller.js.map +1 -1
  405. package/dist/core/reload-blockers.d.ts.map +1 -1
  406. package/dist/core/reload-blockers.js +6 -8
  407. package/dist/core/reload-blockers.js.map +1 -1
  408. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  409. package/dist/core/research/evidence-bundle.js +1 -6
  410. package/dist/core/research/evidence-bundle.js.map +1 -1
  411. package/dist/core/research/lane-model-resolver.d.ts +33 -0
  412. package/dist/core/research/lane-model-resolver.d.ts.map +1 -0
  413. package/dist/core/research/lane-model-resolver.js +65 -0
  414. package/dist/core/research/lane-model-resolver.js.map +1 -0
  415. package/dist/core/research/model-fitness-controller.d.ts +38 -0
  416. package/dist/core/research/model-fitness-controller.d.ts.map +1 -0
  417. package/dist/core/research/model-fitness-controller.js +106 -0
  418. package/dist/core/research/model-fitness-controller.js.map +1 -0
  419. package/dist/core/research/model-fitness.d.ts +1 -1
  420. package/dist/core/research/model-fitness.d.ts.map +1 -1
  421. package/dist/core/research/model-fitness.js +4 -4
  422. package/dist/core/research/model-fitness.js.map +1 -1
  423. package/dist/core/research/research-gate.d.ts.map +1 -1
  424. package/dist/core/research/research-gate.js +7 -7
  425. package/dist/core/research/research-gate.js.map +1 -1
  426. package/dist/core/research/research-lane-controller.d.ts +62 -0
  427. package/dist/core/research/research-lane-controller.d.ts.map +1 -0
  428. package/dist/core/research/research-lane-controller.js +292 -0
  429. package/dist/core/research/research-lane-controller.js.map +1 -0
  430. package/dist/core/research/session-evidence-bundle.d.ts +2 -4
  431. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
  432. package/dist/core/research/session-evidence-bundle.js +10 -46
  433. package/dist/core/research/session-evidence-bundle.js.map +1 -1
  434. package/dist/core/resource-loader.d.ts.map +1 -1
  435. package/dist/core/resource-loader.js +7 -2
  436. package/dist/core/resource-loader.js.map +1 -1
  437. package/dist/core/risk-classifier.d.ts +6 -0
  438. package/dist/core/risk-classifier.d.ts.map +1 -0
  439. package/dist/core/risk-classifier.js +225 -0
  440. package/dist/core/risk-classifier.js.map +1 -0
  441. package/dist/core/runtime-builder.d.ts +26 -20
  442. package/dist/core/runtime-builder.d.ts.map +1 -1
  443. package/dist/core/runtime-builder.js +237 -178
  444. package/dist/core/runtime-builder.js.map +1 -1
  445. package/dist/core/sdk.d.ts +6 -3
  446. package/dist/core/sdk.d.ts.map +1 -1
  447. package/dist/core/sdk.js +61 -24
  448. package/dist/core/sdk.js.map +1 -1
  449. package/dist/core/session-analytics.d.ts.map +1 -1
  450. package/dist/core/session-analytics.js +28 -1
  451. package/dist/core/session-analytics.js.map +1 -1
  452. package/dist/core/session-image-store.d.ts +33 -0
  453. package/dist/core/session-image-store.d.ts.map +1 -0
  454. package/dist/core/session-image-store.js +283 -0
  455. package/dist/core/session-image-store.js.map +1 -0
  456. package/dist/core/session-role.d.ts +6 -4
  457. package/dist/core/session-role.d.ts.map +1 -1
  458. package/dist/core/session-role.js +10 -4
  459. package/dist/core/session-role.js.map +1 -1
  460. package/dist/core/session-snapshot.d.ts +34 -0
  461. package/dist/core/session-snapshot.d.ts.map +1 -0
  462. package/dist/core/session-snapshot.js +68 -0
  463. package/dist/core/session-snapshot.js.map +1 -0
  464. package/dist/core/session-supervision-runtime.d.ts +31 -0
  465. package/dist/core/session-supervision-runtime.d.ts.map +1 -0
  466. package/dist/core/session-supervision-runtime.js +97 -0
  467. package/dist/core/session-supervision-runtime.js.map +1 -0
  468. package/dist/core/settings-manager.d.ts +9 -4
  469. package/dist/core/settings-manager.d.ts.map +1 -1
  470. package/dist/core/settings-manager.js +80 -28
  471. package/dist/core/settings-manager.js.map +1 -1
  472. package/dist/core/tasks/session-task-state.d.ts +2 -4
  473. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  474. package/dist/core/tasks/session-task-state.js +10 -28
  475. package/dist/core/tasks/session-task-state.js.map +1 -1
  476. package/dist/core/tasks/task-projection.d.ts +20 -0
  477. package/dist/core/tasks/task-projection.d.ts.map +1 -0
  478. package/dist/core/tasks/task-projection.js +47 -0
  479. package/dist/core/tasks/task-projection.js.map +1 -0
  480. package/dist/core/tasks/task-state.d.ts +4 -0
  481. package/dist/core/tasks/task-state.d.ts.map +1 -1
  482. package/dist/core/tasks/task-state.js +28 -11
  483. package/dist/core/tasks/task-state.js.map +1 -1
  484. package/dist/core/tool-capability-policy.d.ts +13 -0
  485. package/dist/core/tool-capability-policy.d.ts.map +1 -0
  486. package/dist/core/tool-capability-policy.js +85 -0
  487. package/dist/core/tool-capability-policy.js.map +1 -0
  488. package/dist/core/tool-protocol-controller.d.ts +91 -0
  489. package/dist/core/tool-protocol-controller.d.ts.map +1 -0
  490. package/dist/core/tool-protocol-controller.js +512 -0
  491. package/dist/core/tool-protocol-controller.js.map +1 -0
  492. package/dist/core/tool-recovery-log-records.d.ts +2 -3
  493. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  494. package/dist/core/tool-recovery-log-records.js +9 -35
  495. package/dist/core/tool-recovery-log-records.js.map +1 -1
  496. package/dist/core/tool-selection/tool-performance-store.d.ts +3 -7
  497. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  498. package/dist/core/tool-selection/tool-performance-store.js +97 -138
  499. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  500. package/dist/core/tools/ask-question.d.ts +124 -0
  501. package/dist/core/tools/ask-question.d.ts.map +1 -0
  502. package/dist/core/tools/ask-question.js +693 -0
  503. package/dist/core/tools/ask-question.js.map +1 -0
  504. package/dist/core/tools/delegate-status.d.ts +31 -3
  505. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  506. package/dist/core/tools/delegate-status.js +149 -29
  507. package/dist/core/tools/delegate-status.js.map +1 -1
  508. package/dist/core/tools/delegate.d.ts +15 -13
  509. package/dist/core/tools/delegate.d.ts.map +1 -1
  510. package/dist/core/tools/delegate.js +108 -23
  511. package/dist/core/tools/delegate.js.map +1 -1
  512. package/dist/core/tools/extensionify.d.ts.map +1 -1
  513. package/dist/core/tools/extensionify.js +1 -0
  514. package/dist/core/tools/extensionify.js.map +1 -1
  515. package/dist/core/tools/goal.d.ts +5 -5
  516. package/dist/core/tools/goal.d.ts.map +1 -1
  517. package/dist/core/tools/goal.js +10 -10
  518. package/dist/core/tools/goal.js.map +1 -1
  519. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  520. package/dist/core/tools/model-fitness.js +2 -2
  521. package/dist/core/tools/model-fitness.js.map +1 -1
  522. package/dist/core/tools/orchestration-panel.d.ts +43 -0
  523. package/dist/core/tools/orchestration-panel.d.ts.map +1 -0
  524. package/dist/core/tools/orchestration-panel.js +168 -0
  525. package/dist/core/tools/orchestration-panel.js.map +1 -0
  526. package/dist/core/tools/run-process.d.ts +26 -0
  527. package/dist/core/tools/run-process.d.ts.map +1 -0
  528. package/dist/core/tools/run-process.js +138 -0
  529. package/dist/core/tools/run-process.js.map +1 -0
  530. package/dist/core/tools/task-steps.d.ts +2 -0
  531. package/dist/core/tools/task-steps.d.ts.map +1 -1
  532. package/dist/core/tools/task-steps.js +102 -0
  533. package/dist/core/tools/task-steps.js.map +1 -1
  534. package/dist/core/tools/tmux-dispatch.d.ts +3 -5
  535. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -1
  536. package/dist/core/tools/tmux-dispatch.js +4 -4
  537. package/dist/core/tools/tmux-dispatch.js.map +1 -1
  538. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -1
  539. package/dist/core/tools/windows-shell-engine.js +4 -6
  540. package/dist/core/tools/windows-shell-engine.js.map +1 -1
  541. package/dist/core/util/bounded-jsonl.d.ts +10 -0
  542. package/dist/core/util/bounded-jsonl.d.ts.map +1 -0
  543. package/dist/core/util/bounded-jsonl.js +75 -0
  544. package/dist/core/util/bounded-jsonl.js.map +1 -0
  545. package/dist/core/util/value-guards.d.ts +5 -0
  546. package/dist/core/util/value-guards.d.ts.map +1 -0
  547. package/dist/core/util/value-guards.js +12 -0
  548. package/dist/core/util/value-guards.js.map +1 -0
  549. package/dist/core/worktree-sync/store.d.ts +3 -3
  550. package/dist/core/worktree-sync/store.d.ts.map +1 -1
  551. package/dist/core/worktree-sync/store.js +10 -5
  552. package/dist/core/worktree-sync/store.js.map +1 -1
  553. package/dist/index.d.ts +13 -3
  554. package/dist/index.d.ts.map +1 -1
  555. package/dist/index.js +12 -2
  556. package/dist/index.js.map +1 -1
  557. package/dist/main.d.ts.map +1 -1
  558. package/dist/main.js +128 -44
  559. package/dist/main.js.map +1 -1
  560. package/dist/migrations.d.ts +4 -6
  561. package/dist/migrations.d.ts.map +1 -1
  562. package/dist/migrations.js +186 -28
  563. package/dist/migrations.js.map +1 -1
  564. package/dist/modes/interactive/clipboard-input.d.ts +7 -1
  565. package/dist/modes/interactive/clipboard-input.d.ts.map +1 -1
  566. package/dist/modes/interactive/clipboard-input.js +44 -6
  567. package/dist/modes/interactive/clipboard-input.js.map +1 -1
  568. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  569. package/dist/modes/interactive/components/settings-selector.js +2 -2
  570. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  571. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  572. package/dist/modes/interactive/config-backup.js +5 -4
  573. package/dist/modes/interactive/config-backup.js.map +1 -1
  574. package/dist/modes/interactive/extension-ui-host.d.ts +14 -0
  575. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  576. package/dist/modes/interactive/extension-ui-host.js +76 -15
  577. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  578. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  579. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  580. package/dist/modes/interactive/interactive-mode.js +63 -1
  581. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  582. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  583. package/dist/modes/interactive/key-handlers.js +1 -1
  584. package/dist/modes/interactive/key-handlers.js.map +1 -1
  585. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  586. package/dist/modes/interactive/profile-menu-controller.js +7 -7
  587. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  588. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  589. package/dist/modes/interactive/report-commands.js +21 -0
  590. package/dist/modes/interactive/report-commands.js.map +1 -1
  591. package/dist/modes/interactive/session-flow-commands.d.ts +17 -2
  592. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  593. package/dist/modes/interactive/session-flow-commands.js +50 -17
  594. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  595. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  596. package/dist/modes/interactive/session-io-commands.js +16 -6
  597. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  598. package/dist/modes/interactive/session-replacement-errors.d.ts +7 -0
  599. package/dist/modes/interactive/session-replacement-errors.d.ts.map +1 -0
  600. package/dist/modes/interactive/session-replacement-errors.js +15 -0
  601. package/dist/modes/interactive/session-replacement-errors.js.map +1 -0
  602. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  603. package/dist/modes/interactive/startup-checks.js +1 -1
  604. package/dist/modes/interactive/startup-checks.js.map +1 -1
  605. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  606. package/dist/modes/rpc/rpc-mode.js +82 -23
  607. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  608. package/dist/modes/rpc/rpc-types.d.ts +15 -0
  609. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  610. package/dist/modes/rpc/rpc-types.js +138 -1
  611. package/dist/modes/rpc/rpc-types.js.map +1 -1
  612. package/dist/utils/clipboard-image.d.ts.map +1 -1
  613. package/dist/utils/clipboard-image.js +2 -13
  614. package/dist/utils/clipboard-image.js.map +1 -1
  615. package/dist/utils/clipboard-native.d.ts +1 -0
  616. package/dist/utils/clipboard-native.d.ts.map +1 -1
  617. package/dist/utils/clipboard-native.js.map +1 -1
  618. package/dist/utils/clipboard.d.ts +1 -0
  619. package/dist/utils/clipboard.d.ts.map +1 -1
  620. package/dist/utils/clipboard.js +52 -1
  621. package/dist/utils/clipboard.js.map +1 -1
  622. package/dist/utils/platform.d.ts +3 -0
  623. package/dist/utils/platform.d.ts.map +1 -0
  624. package/dist/utils/platform.js +8 -0
  625. package/dist/utils/platform.js.map +1 -0
  626. package/dist/utils/work-directory.d.ts +11 -0
  627. package/dist/utils/work-directory.d.ts.map +1 -1
  628. package/dist/utils/work-directory.js +30 -9
  629. package/dist/utils/work-directory.js.map +1 -1
  630. package/docs/UI_POLISH_ORCHESTRATION.md +44 -0
  631. package/docs/UI_QUALITY_ORCHESTRATION.md +30 -0
  632. package/docs/docs.json +4 -0
  633. package/docs/extensions.md +62 -2
  634. package/docs/harness-architecture.md +156 -0
  635. package/docs/keybindings.md +11 -1
  636. package/docs/process-matrix.md +59 -26
  637. package/docs/rpc.md +45 -2
  638. package/docs/sessions.md +15 -0
  639. package/docs/settings.md +125 -11
  640. package/docs/task-steps.md +8 -0
  641. package/docs/telemetry.md +19 -0
  642. package/docs/tmux-agent-manager.md +10 -3
  643. package/docs/ui-reference/README.md +9 -0
  644. package/docs/ui-reference/orchestration-after.txt +7 -0
  645. package/docs/ui-reference/orchestration-before.txt +7 -0
  646. package/docs/ui-reference/orchestration-reference.txt +6 -0
  647. package/docs/usage.md +14 -2
  648. package/docs/work-directory.md +19 -8
  649. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  650. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  651. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  652. package/examples/extensions/question.ts +34 -245
  653. package/examples/extensions/questionnaire.ts +41 -407
  654. package/examples/extensions/sandbox/package-lock.json +2 -2
  655. package/examples/extensions/sandbox/package.json +1 -1
  656. package/examples/extensions/with-deps/package-lock.json +2 -2
  657. package/examples/extensions/with-deps/package.json +1 -1
  658. package/npm-shrinkwrap.json +12 -12
  659. package/package.json +4 -4
  660. package/dist/core/delegation/session-worker-result.d.ts +0 -46
  661. package/dist/core/delegation/session-worker-result.d.ts.map +0 -1
  662. package/dist/core/delegation/session-worker-result.js +0 -113
  663. package/dist/core/delegation/session-worker-result.js.map +0 -1
  664. package/dist/core/delegation/worker-result.d.ts.map +0 -1
  665. package/dist/core/delegation/worker-result.js.map +0 -1
  666. package/dist/core/telemetry.d.ts.map +0 -1
  667. package/dist/core/telemetry.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"session-analytics.d.ts","sourceRoot":"","sources":["../../src/core/session-analytics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session usage / cost / stats accounting, context-window usage, and session export.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Read-only over the session\n * except for its two owned memo caches (`_spawnedUsageCache`, `_dailyUsageCache`) — it never mutates\n * agent or session state. Single source of truth for \"how much did this session and its spawned\n * subtree spend\" (footer roll-up, print-mode child reporting), the daily cross-session totals, the\n * /context window estimate, and HTML/JSONL export of the current branch.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentState } from \"@caupulican/pi-agent-core\";\nimport {\n\tCURRENT_SESSION_VERSION,\n\tcalculateContextTokens,\n\testimateContextTokens,\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionManager,\n} from \"@caupulican/pi-agent-core/node\";\nimport type {\n\tAssistantMessage,\n\tModel,\n\tToolArgumentExecutionOutcome,\n\tToolArgumentTeachState,\n\tToolArgumentValidationTelemetryEvent,\n\tUsage,\n} from \"@caupulican/pi-ai\";\nimport { getSessionsDir } from \"../config.ts\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport {\n\ttype SessionStats,\n\tSPAWNED_USAGE_CUSTOM_TYPE,\n\ttype SpawnedUsageReport,\n\ttype SpawnedUsageTotals,\n} from \"./agent-session.ts\";\nimport {\n\taccumulateCurrentSessionCostsFromEntries,\n\ttype CurrentSessionCostAccumulator,\n\tcreateCurrentSessionCostAccumulator,\n\tcreateSessionCostSummary,\n\ttype SessionCostSummary,\n} from \"./cost/cost-summary.ts\";\nimport {\n\taggregateDailyUsageFromSessionFiles,\n\taggregateDailyUsageFromSessionRoot,\n\ttype DailyUsageTotals,\n\tformatDailyUsageBreakdown,\n\tgetLocalDayWindow,\n} from \"./cost/daily-usage.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport type { ContextUsage, ToolDefinition } from \"./extensions/index.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport {\n\tisToolArgumentValidationLogRecord,\n\ttype ToolArgumentValidationLogRecord,\n} from \"./tool-recovery-log-records.ts\";\nimport {\n\tconsumeToolArgumentValidationRecord,\n\tcreateEmptyToolArgumentValidationStats,\n\tgetToolRecoveryRecordSequence,\n\treadPersistedToolRecoveryStats,\n} from \"./tool-recovery-stats.ts\";\n\nexport const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = \"tool_argument_validation\";\n\nexport interface ToolArgumentValidationTeachEfficacy {\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n\trepairedThenSucceeded: number;\n\trepairedThenFailed: number;\n\trepairedThenNotRun: number;\n}\n\nexport interface ToolArgumentValidationStats {\n\tclean: number;\n\trepaired: number;\n\tbounced: number;\n\tfailureModes: Record<string, number>;\n\trepairsApplied: Record<string, number>;\n\ttaught: Record<ToolArgumentTeachState, number>;\n\texecutionOutcome: Record<ToolArgumentExecutionOutcome, number>;\n\tteachEfficacy: Record<string, ToolArgumentValidationTeachEfficacy>;\n}\n\nexport interface ToolArgumentValidationRecord extends ToolArgumentValidationTelemetryEvent {\n\tversion: 1;\n}\n\nexport interface SessionAnalyticsDeps {\n\t/** Live agent state — assistant-message usage and message counts are read from here. */\n\tgetState(): AgentState;\n\t/** All messages (agent state view) — used for context-window estimation and last-assistant text. */\n\tgetMessages(): AgentMessage[];\n\t/** Current session model — its context window bounds the /context estimate. */\n\tgetModel(): Model<any> | undefined;\n\t/** Session log — entries feed spawned-usage roll-up, daily totals, branch export. */\n\tgetSessionManager(): SessionManager;\n\t/** Settings — the export theme is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Resolve a tool definition for the HTML export's custom-tool renderer. */\n\tgetToolDefinition(name: string): ToolDefinition | undefined;\n\t/** Sidecar recovery telemetry log; read on demand so turn handling never writes session custom entries. */\n\tgetToolRecoveryEventLogPath(): string;\n}\n\nexport class SessionAnalytics {\n\t/** Incremental aggregate over append-ordered session entries. */\n\tprivate _currentSessionCostCache?: {\n\t\tentryCount: number;\n\t\taccumulator: CurrentSessionCostAccumulator;\n\t};\n\t/** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */\n\tprivate _dailyUsageCache?: {\n\t\tsessionDir: string;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\texpiresAt: number;\n\t\ttotals: DailyUsageTotals;\n\t};\n\t/** Memoized full cost summary. The footer renders on every streamed delta and keystroke, so\n\t * rescanning a long session log there turns redraw into O(entries) work per frame. */\n\tprivate _costSummaryCache?: {\n\t\tentryCount: number;\n\t\tdailyTotals: DailyUsageTotals;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\tsummary: SessionCostSummary;\n\t};\n\t/** Cumulative stats initialized once from persisted telemetry, then updated without retaining record details. */\n\tprivate _toolArgumentValidationStats: ToolArgumentValidationStats | undefined;\n\t/** Incremental context-usage state keyed by the append-only branch leaf. */\n\tprivate _postCompactionUsageCache?: {\n\t\tleafId: string | null;\n\t\thasCompaction: boolean;\n\t\thasPostCompactionUsage: boolean;\n\t};\n\n\tprivate readonly deps: SessionAnalyticsDeps;\n\n\tconstructor(deps: SessionAnalyticsDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.deps.getState();\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst toolArgumentValidation = this.getToolArgumentValidationStats();\n\t\tconst compactionGates = this.getCompactionGateStats();\n\n\t\treturn {\n\t\t\tsessionFile: this.deps.getSessionManager().getSessionFile(),\n\t\t\tsessionId: this.deps.getSessionManager().getSessionId(),\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: totalInput,\n\t\t\t\toutput: totalOutput,\n\t\t\t\tcacheRead: totalCacheRead,\n\t\t\t\tcacheWrite: totalCacheWrite,\n\t\t\t\ttotal: totalInput + totalOutput + totalCacheRead + totalCacheWrite,\n\t\t\t},\n\t\t\tcost: totalCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t\ttoolArgumentValidation,\n\t\t\tcompactionGates,\n\t\t};\n\t}\n\n\tgetCompactionGateStats(): SessionStats[\"compactionGates\"] {\n\t\tlet gateFailures = 0;\n\t\tlet deterministicGapFills = 0;\n\t\tlet compactionsWithGateFailures = 0;\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"compaction\") continue;\n\t\t\tconst details = entry.details;\n\t\t\tif (!details || typeof details !== \"object\") continue;\n\t\t\tconst rawGateFailures = (details as { verificationGateFailures?: unknown }).verificationGateFailures;\n\t\t\tconst rawGapFills = (details as { deterministicGapFills?: unknown }).deterministicGapFills;\n\t\t\tconst entryGateFailures =\n\t\t\t\ttypeof rawGateFailures === \"number\" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;\n\t\t\tconst entryGapFills = typeof rawGapFills === \"number\" && Number.isFinite(rawGapFills) ? rawGapFills : 0;\n\t\t\tgateFailures += entryGateFailures;\n\t\t\tdeterministicGapFills += entryGapFills;\n\t\t\tif (entryGateFailures > 0) compactionsWithGateFailures++;\n\t\t}\n\t\treturn { gateFailures, deterministicGapFills, compactionsWithGateFailures };\n\t}\n\n\trecordToolArgumentValidation(record: ToolArgumentValidationLogRecord): void {\n\t\tconst stats = this.getToolArgumentValidationStats();\n\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\tthis._toolArgumentValidationStats = stats;\n\t}\n\n\tprivate readToolArgumentValidationSidecarRecords(): ToolArgumentValidationLogRecord[] {\n\t\tconst filePath = this.deps.getToolRecoveryEventLogPath();\n\t\tif (!existsSync(filePath)) return [];\n\t\tconst records: ToolArgumentValidationLogRecord[] = [];\n\t\ttry {\n\t\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\t\tfor (const line of readFileSync(filePath, \"utf-8\").split(\"\\n\")) {\n\t\t\t\tif (line.trim().length === 0) continue;\n\t\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\t\tif (!isToolArgumentValidationLogRecord(parsed) || parsed.sessionId !== sessionId) continue;\n\t\t\t\trecords.push(parsed);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\treturn records;\n\t}\n\n\tgetToolArgumentValidationStats(): ToolArgumentValidationStats {\n\t\tif (this._toolArgumentValidationStats) return structuredClone(this._toolArgumentValidationStats);\n\t\tconst eventLogPath = this.deps.getToolRecoveryEventLogPath();\n\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\tconst persisted = readPersistedToolRecoveryStats(eventLogPath, sessionId);\n\t\tconst stats = persisted ? structuredClone(persisted.stats) : createEmptyToolArgumentValidationStats();\n\t\tconst seen = new Set<string>();\n\t\tconst consume = (record: ToolArgumentValidationRecord, key: string): void => {\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\t};\n\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE) continue;\n\t\t\tconst record = entry.data as ToolArgumentValidationRecord | undefined;\n\t\t\tif (!record || record.version !== 1) continue;\n\t\t\tconsume(record, `legacy:${entry.id}`);\n\t\t}\n\t\tfor (const record of this.readToolArgumentValidationSidecarRecords()) {\n\t\t\tif (\n\t\t\t\tpersisted &&\n\t\t\t\trecord.sessionId === sessionId &&\n\t\t\t\tgetToolRecoveryRecordSequence(record) <= persisted.lastRecordSequence\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\n\t\tthis._toolArgumentValidationStats = stats;\n\t\treturn structuredClone(stats);\n\t}\n\n\t/**\n\t * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own\n\t * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of\n\t * truth for \"how much did this session and everything it spawned spend\" — used by print-mode\n\t * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.\n\t *\n\t * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child\n\t * that itself spawned grandchildren must report own + sub-usage in one number, or the parent\n\t * silently under-counts the grandchildren.\n\t */\n\tgetCumulativeUsage(): Usage {\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costInput = 0;\n\t\tlet costOutput = 0;\n\t\tlet costCacheRead = 0;\n\t\tlet costCacheWrite = 0;\n\t\tlet costTotal = 0;\n\t\tconst add = (usage: Usage) => {\n\t\t\tinput += usage.input;\n\t\t\toutput += usage.output;\n\t\t\tcacheRead += usage.cacheRead;\n\t\t\tcacheWrite += usage.cacheWrite;\n\t\t\ttotalTokens += usage.totalTokens;\n\t\t\tcostInput += usage.cost.input;\n\t\t\tcostOutput += usage.cost.output;\n\t\t\tcostCacheRead += usage.cost.cacheRead;\n\t\t\tcostCacheWrite += usage.cost.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t};\n\t\tfor (const message of this.deps.getState().messages) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst usage = (message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\tadd(usage);\n\t\t}\n\t\t// Roll up usage this session attributed to its own spawned children (single-hop).\n\t\tconst seenSpawnedReportIds = new Set<string>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\tadd(data.usage);\n\t\t}\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotalTokens,\n\t\t\tcost: {\n\t\t\t\tinput: costInput,\n\t\t\t\toutput: costOutput,\n\t\t\t\tcacheRead: costCacheRead,\n\t\t\t\tcacheWrite: costCacheWrite,\n\t\t\t\ttotal: costTotal,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Record usage spent by a spawned/subagent session so the footer can roll it into the\n\t * displayed cost. Persisted as a `CustomEntry` (`customType: \"spawned_usage\"`, Model A) so\n\t * it survives reload and is reconstructed exactly like main usage; a new/forked session\n\t * starts fresh because it owns a new log file.\n\t *\n\t * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a\n\t * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop\n\t * invariant documented on {@link SpawnedUsageReport}.\n\t *\n\t * @returns the id of the appended entry, or `undefined` if the report was a duplicate.\n\t */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined {\n\t\tconst reportId = opts?.reportId;\n\t\tif (reportId && this.getCurrentSessionCostTotals().seenSubagentReportIds.has(reportId)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst report: SpawnedUsageReport = {\n\t\t\tusage,\n\t\t\tlabel: opts?.label,\n\t\t\tsourceSessionId: opts?.sourceSessionId,\n\t\t\treportId,\n\t\t};\n\t\tconst entryId = this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);\n\t\tif (reportId) this.getCurrentSessionCostTotals();\n\t\treturn entryId;\n\t}\n\n\tprivate getCurrentSessionCostTotals(): CurrentSessionCostAccumulator {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tlet cache = this._currentSessionCostCache;\n\t\tconst getEntriesSince = sessionManager.getEntriesSince?.bind(sessionManager);\n\t\tif (!cache || entryCount < cache.entryCount || !getEntriesSince) {\n\t\t\tcache = {\n\t\t\t\tentryCount,\n\t\t\t\taccumulator: accumulateCurrentSessionCostsFromEntries(\n\t\t\t\t\tcreateCurrentSessionCostAccumulator(),\n\t\t\t\t\tsessionManager.getEntries(),\n\t\t\t\t),\n\t\t\t};\n\t\t\tthis._currentSessionCostCache = cache;\n\t\t} else if (entryCount > cache.entryCount) {\n\t\t\taccumulateCurrentSessionCostsFromEntries(cache.accumulator, getEntriesSince(cache.entryCount));\n\t\t\tcache.entryCount = entryCount;\n\t\t}\n\t\treturn cache.accumulator;\n\t}\n\n\t/**\n\t * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). The append-ordered\n\t * accumulator processes only new entries, so repeated turns do not rescan the full session.\n\t */\n\tgetSpawnedUsage(): SpawnedUsageTotals {\n\t\tconst current = this.getCurrentSessionCostTotals();\n\t\treturn { cost: current.subagentCost, reports: current.subagentReports };\n\t}\n\n\tgetCostSummary(now = new Date()): SessionCostSummary {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tconst window = getLocalDayWindow(now);\n\t\tconst dailyTotals = this.getDailyUsageTotals(now);\n\t\tconst cached = this._costSummaryCache;\n\t\tif (\n\t\t\tcached?.entryCount === entryCount &&\n\t\t\tcached.dailyTotals === dailyTotals &&\n\t\t\tcached.windowStartMs === window.startMs &&\n\t\t\tcached.windowEndMs === window.endMs\n\t\t) {\n\t\t\treturn cached.summary;\n\t\t}\n\t\tconst summary = createSessionCostSummary({\n\t\t\tcurrentTotals: this.getCurrentSessionCostTotals(),\n\t\t\tdailyTotals,\n\t\t\ttodayWindow: window,\n\t\t});\n\t\tthis._costSummaryCache = {\n\t\t\tentryCount,\n\t\t\tdailyTotals,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\tsummary,\n\t\t};\n\t\treturn summary;\n\t}\n\n\tgetDailyUsageTotals(now = new Date()): DailyUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst sessionDir = sessionManager.getSessionDir();\n\t\tconst scope = sessionManager.usesDefaultSessionDir() ? getSessionsDir() : sessionDir;\n\t\tconst nowMs = now.getTime();\n\t\tconst window = getLocalDayWindow(now);\n\t\tif (\n\t\t\tthis._dailyUsageCache?.sessionDir === scope &&\n\t\t\tthis._dailyUsageCache.windowStartMs === window.startMs &&\n\t\t\tthis._dailyUsageCache.windowEndMs === window.endMs &&\n\t\t\tthis._dailyUsageCache.expiresAt > nowMs\n\t\t) {\n\t\t\treturn this._dailyUsageCache.totals;\n\t\t}\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst liveSession = sessionFile ? { filePath: sessionFile, entries: sessionManager.getEntries() } : undefined;\n\t\tconst totals = sessionManager.usesDefaultSessionDir()\n\t\t\t? aggregateDailyUsageFromSessionRoot(scope, window, liveSession)\n\t\t\t: aggregateDailyUsageFromSessionFiles(sessionDir, window, liveSession);\n\t\tthis._dailyUsageCache = {\n\t\t\tsessionDir: scope,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\texpiresAt: Math.min(nowMs + 10_000, window.endMs),\n\t\t\ttotals,\n\t\t};\n\t\treturn totals;\n\t}\n\n\tgetDailyUsageBreakdown(formatLabel?: (label: string) => string, now = new Date()): string {\n\t\treturn formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);\n\t}\n\n\tprivate getPostCompactionUsageState(): { hasCompaction: boolean; hasPostCompactionUsage: boolean } {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst indexedSessionManager = sessionManager as unknown as {\n\t\t\tgetLeafId?: () => string | null;\n\t\t\tgetEntry?: (id: string) => SessionEntry | undefined;\n\t\t};\n\t\tconst getLeafId = indexedSessionManager.getLeafId?.bind(sessionManager);\n\t\tconst getEntry = indexedSessionManager.getEntry?.bind(sessionManager);\n\t\tconst leafId = getLeafId?.();\n\t\tif (getLeafId && getEntry && leafId !== undefined) {\n\t\t\tif (this._postCompactionUsageCache?.leafId === leafId) {\n\t\t\t\treturn { ...this._postCompactionUsageCache };\n\t\t\t}\n\t\t\tconst appended: SessionEntry[] = [];\n\t\t\tconst cachedLeafId = this._postCompactionUsageCache?.leafId;\n\t\t\tlet currentId = leafId;\n\t\t\tconst seen = new Set<string>();\n\t\t\twhile (currentId !== null && currentId !== cachedLeafId && !seen.has(currentId)) {\n\t\t\t\tseen.add(currentId);\n\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\tif (!entry) break;\n\t\t\t\tappended.push(entry);\n\t\t\t\tcurrentId = entry.parentId;\n\t\t\t}\n\t\t\tconst extendsCachedBranch = this._postCompactionUsageCache !== undefined && currentId === cachedLeafId;\n\t\t\tconst state = extendsCachedBranch\n\t\t\t\t? { ...this._postCompactionUsageCache! }\n\t\t\t\t: { leafId: null, hasCompaction: false, hasPostCompactionUsage: false };\n\t\t\tif (!extendsCachedBranch && currentId !== null) {\n\t\t\t\twhile (currentId !== null && !seen.has(currentId)) {\n\t\t\t\t\tseen.add(currentId);\n\t\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\t\tif (!entry) break;\n\t\t\t\t\tappended.push(entry);\n\t\t\t\t\tcurrentId = entry.parentId;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let index = appended.length - 1; index >= 0; index--) {\n\t\t\t\tconst entry = appended[index];\n\t\t\t\tif (entry.type === \"compaction\") {\n\t\t\t\t\tstate.hasCompaction = true;\n\t\t\t\t\tstate.hasPostCompactionUsage = false;\n\t\t\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\" && state.hasCompaction) {\n\t\t\t\t\t\tstate.hasPostCompactionUsage = calculateContextTokens(assistant.usage) > 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate.leafId = leafId;\n\t\t\tthis._postCompactionUsageCache = state;\n\t\t\treturn { ...state };\n\t\t}\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tif (!latestCompaction) return { hasCompaction: false, hasPostCompactionUsage: false };\n\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn { hasCompaction: true, hasPostCompactionUsage: calculateContextTokens(assistant.usage) > 0 };\n\t\t}\n\t\treturn { hasCompaction: true, hasPostCompactionUsage: false };\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// Walk backward only to the nearest compaction instead of rebuilding the whole branch on\n\t\t// every footer invalidation; the distance is bounded by the live post-compaction context.\n\t\tconst compactionState = this.getPostCompactionUsageState();\n\t\tif (compactionState.hasCompaction && !compactionState.hasPostCompactionUsage) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.deps.getMessages());\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst themeName = this.deps.getSettingsManager().getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.deps.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(sessionManager, this.deps.getState(), {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.deps\n\t\t\t.getMessages()\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-analytics.d.ts","sourceRoot":"","sources":["../../src/core/session-analytics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session usage / cost / stats accounting, context-window usage, and session export.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Read-only over the session\n * except for its two owned memo caches (`_spawnedUsageCache`, `_dailyUsageCache`) — it never mutates\n * agent or session state. Single source of truth for \"how much did this session and its spawned\n * subtree spend\" (footer roll-up, print-mode child reporting), the daily cross-session totals, the\n * /context window estimate, and HTML/JSONL export of the current branch.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentState } from \"@caupulican/pi-agent-core\";\nimport {\n\tCURRENT_SESSION_VERSION,\n\tcalculateContextTokens,\n\testimateContextTokens,\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionManager,\n} from \"@caupulican/pi-agent-core/node\";\nimport type {\n\tAssistantMessage,\n\tModel,\n\tToolArgumentExecutionOutcome,\n\tToolArgumentTeachState,\n\tToolArgumentValidationTelemetryEvent,\n\tUsage,\n} from \"@caupulican/pi-ai\";\nimport { getSessionsDir } from \"../config.ts\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport {\n\ttype CompactionGateCheckStats,\n\ttype SessionStats,\n\tSPAWNED_USAGE_CUSTOM_TYPE,\n\ttype SpawnedUsageReport,\n\ttype SpawnedUsageTotals,\n} from \"./agent-session.ts\";\nimport {\n\taccumulateCurrentSessionCostsFromEntries,\n\ttype CurrentSessionCostAccumulator,\n\tcreateCurrentSessionCostAccumulator,\n\tcreateSessionCostSummary,\n\ttype SessionCostSummary,\n} from \"./cost/cost-summary.ts\";\nimport {\n\taggregateDailyUsageFromSessionFiles,\n\taggregateDailyUsageFromSessionRoot,\n\ttype DailyUsageTotals,\n\tformatDailyUsageBreakdown,\n\tgetLocalDayWindow,\n} from \"./cost/daily-usage.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport type { ContextUsage, ToolDefinition } from \"./extensions/index.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport {\n\tisToolArgumentValidationLogRecord,\n\ttype ToolArgumentValidationLogRecord,\n} from \"./tool-recovery-log-records.ts\";\nimport {\n\tconsumeToolArgumentValidationRecord,\n\tcreateEmptyToolArgumentValidationStats,\n\tgetToolRecoveryRecordSequence,\n\treadPersistedToolRecoveryStats,\n} from \"./tool-recovery-stats.ts\";\n\nexport const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = \"tool_argument_validation\";\n\nexport interface ToolArgumentValidationTeachEfficacy {\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n\trepairedThenSucceeded: number;\n\trepairedThenFailed: number;\n\trepairedThenNotRun: number;\n}\n\nexport interface ToolArgumentValidationStats {\n\tclean: number;\n\trepaired: number;\n\tbounced: number;\n\tfailureModes: Record<string, number>;\n\trepairsApplied: Record<string, number>;\n\ttaught: Record<ToolArgumentTeachState, number>;\n\texecutionOutcome: Record<ToolArgumentExecutionOutcome, number>;\n\tteachEfficacy: Record<string, ToolArgumentValidationTeachEfficacy>;\n}\n\nexport interface ToolArgumentValidationRecord extends ToolArgumentValidationTelemetryEvent {\n\tversion: 1;\n}\n\nexport interface SessionAnalyticsDeps {\n\t/** Live agent state — assistant-message usage and message counts are read from here. */\n\tgetState(): AgentState;\n\t/** All messages (agent state view) — used for context-window estimation and last-assistant text. */\n\tgetMessages(): AgentMessage[];\n\t/** Current session model — its context window bounds the /context estimate. */\n\tgetModel(): Model<any> | undefined;\n\t/** Session log — entries feed spawned-usage roll-up, daily totals, branch export. */\n\tgetSessionManager(): SessionManager;\n\t/** Settings — the export theme is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Resolve a tool definition for the HTML export's custom-tool renderer. */\n\tgetToolDefinition(name: string): ToolDefinition | undefined;\n\t/** Sidecar recovery telemetry log; read on demand so turn handling never writes session custom entries. */\n\tgetToolRecoveryEventLogPath(): string;\n}\n\nexport class SessionAnalytics {\n\t/** Incremental aggregate over append-ordered session entries. */\n\tprivate _currentSessionCostCache?: {\n\t\tentryCount: number;\n\t\taccumulator: CurrentSessionCostAccumulator;\n\t};\n\t/** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */\n\tprivate _dailyUsageCache?: {\n\t\tsessionDir: string;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\texpiresAt: number;\n\t\ttotals: DailyUsageTotals;\n\t};\n\t/** Memoized full cost summary. The footer renders on every streamed delta and keystroke, so\n\t * rescanning a long session log there turns redraw into O(entries) work per frame. */\n\tprivate _costSummaryCache?: {\n\t\tentryCount: number;\n\t\tdailyTotals: DailyUsageTotals;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\tsummary: SessionCostSummary;\n\t};\n\t/** Cumulative stats initialized once from persisted telemetry, then updated without retaining record details. */\n\tprivate _toolArgumentValidationStats: ToolArgumentValidationStats | undefined;\n\t/** Incremental context-usage state keyed by the append-only branch leaf. */\n\tprivate _postCompactionUsageCache?: {\n\t\tleafId: string | null;\n\t\thasCompaction: boolean;\n\t\thasPostCompactionUsage: boolean;\n\t};\n\n\tprivate readonly deps: SessionAnalyticsDeps;\n\n\tconstructor(deps: SessionAnalyticsDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.deps.getState();\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst toolArgumentValidation = this.getToolArgumentValidationStats();\n\t\tconst compactionGates = this.getCompactionGateStats();\n\n\t\treturn {\n\t\t\tsessionFile: this.deps.getSessionManager().getSessionFile(),\n\t\t\tsessionId: this.deps.getSessionManager().getSessionId(),\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: totalInput,\n\t\t\t\toutput: totalOutput,\n\t\t\t\tcacheRead: totalCacheRead,\n\t\t\t\tcacheWrite: totalCacheWrite,\n\t\t\t\ttotal: totalInput + totalOutput + totalCacheRead + totalCacheWrite,\n\t\t\t},\n\t\t\tcost: totalCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t\ttoolArgumentValidation,\n\t\t\tcompactionGates,\n\t\t};\n\t}\n\n\tgetCompactionGateStats(): SessionStats[\"compactionGates\"] {\n\t\tlet gateFailures = 0;\n\t\tlet deterministicGapFills = 0;\n\t\tlet compactionsWithGateFailures = 0;\n\t\tconst checks = new Map<string, CompactionGateCheckStats>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"compaction\") continue;\n\t\t\tconst details = entry.details;\n\t\t\tif (!details || typeof details !== \"object\") continue;\n\t\t\tconst rawGateFailures = (details as { verificationGateFailures?: unknown }).verificationGateFailures;\n\t\t\tconst rawGapFills = (details as { deterministicGapFills?: unknown }).deterministicGapFills;\n\t\t\tconst rawChecks = (details as { verificationGateChecks?: unknown }).verificationGateChecks;\n\t\t\tconst entryGateFailures =\n\t\t\t\ttypeof rawGateFailures === \"number\" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;\n\t\t\tconst entryGapFills = typeof rawGapFills === \"number\" && Number.isFinite(rawGapFills) ? rawGapFills : 0;\n\t\t\tgateFailures += entryGateFailures;\n\t\t\tdeterministicGapFills += entryGapFills;\n\t\t\tif (entryGateFailures > 0) compactionsWithGateFailures++;\n\t\t\tif (rawChecks && typeof rawChecks === \"object\" && !Array.isArray(rawChecks)) {\n\t\t\t\tfor (const [check, rawStats] of Object.entries(rawChecks)) {\n\t\t\t\t\tif (!rawStats || typeof rawStats !== \"object\" || Array.isArray(rawStats)) continue;\n\t\t\t\t\tconst stats = rawStats as Record<string, unknown>;\n\t\t\t\t\tconst failures = stats.failures;\n\t\t\t\t\tif (typeof failures !== \"number\" || !Number.isFinite(failures) || failures <= 0) continue;\n\t\t\t\t\tconst current = checks.get(check) ?? { failures: 0 };\n\t\t\t\t\tcurrent.failures += failures;\n\t\t\t\t\tif (typeof stats.minScore === \"number\" && Number.isFinite(stats.minScore)) {\n\t\t\t\t\t\tcurrent.minScore = Math.min(current.minScore ?? stats.minScore, stats.minScore);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof stats.maxScore === \"number\" && Number.isFinite(stats.maxScore)) {\n\t\t\t\t\t\tcurrent.maxScore = Math.max(current.maxScore ?? stats.maxScore, stats.maxScore);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof stats.threshold === \"number\" && Number.isFinite(stats.threshold)) {\n\t\t\t\t\t\tcurrent.threshold = stats.threshold;\n\t\t\t\t\t}\n\t\t\t\t\tif (stats.comparator === \"minimum\" || stats.comparator === \"maximum\") {\n\t\t\t\t\t\tcurrent.comparator = stats.comparator;\n\t\t\t\t\t}\n\t\t\t\t\tchecks.set(check, current);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { gateFailures, deterministicGapFills, compactionsWithGateFailures, checks: Object.fromEntries(checks) };\n\t}\n\n\trecordToolArgumentValidation(record: ToolArgumentValidationLogRecord): void {\n\t\tconst stats = this.getToolArgumentValidationStats();\n\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\tthis._toolArgumentValidationStats = stats;\n\t}\n\n\tprivate readToolArgumentValidationSidecarRecords(): ToolArgumentValidationLogRecord[] {\n\t\tconst filePath = this.deps.getToolRecoveryEventLogPath();\n\t\tif (!existsSync(filePath)) return [];\n\t\tconst records: ToolArgumentValidationLogRecord[] = [];\n\t\ttry {\n\t\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\t\tfor (const line of readFileSync(filePath, \"utf-8\").split(\"\\n\")) {\n\t\t\t\tif (line.trim().length === 0) continue;\n\t\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\t\tif (!isToolArgumentValidationLogRecord(parsed) || parsed.sessionId !== sessionId) continue;\n\t\t\t\trecords.push(parsed);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\treturn records;\n\t}\n\n\tgetToolArgumentValidationStats(): ToolArgumentValidationStats {\n\t\tif (this._toolArgumentValidationStats) return structuredClone(this._toolArgumentValidationStats);\n\t\tconst eventLogPath = this.deps.getToolRecoveryEventLogPath();\n\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\tconst persisted = readPersistedToolRecoveryStats(eventLogPath, sessionId);\n\t\tconst stats = persisted ? structuredClone(persisted.stats) : createEmptyToolArgumentValidationStats();\n\t\tconst seen = new Set<string>();\n\t\tconst consume = (record: ToolArgumentValidationRecord, key: string): void => {\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\t};\n\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE) continue;\n\t\t\tconst record = entry.data as ToolArgumentValidationRecord | undefined;\n\t\t\tif (!record || record.version !== 1) continue;\n\t\t\tconsume(record, `legacy:${entry.id}`);\n\t\t}\n\t\tfor (const record of this.readToolArgumentValidationSidecarRecords()) {\n\t\t\tif (\n\t\t\t\tpersisted &&\n\t\t\t\trecord.sessionId === sessionId &&\n\t\t\t\tgetToolRecoveryRecordSequence(record) <= persisted.lastRecordSequence\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\n\t\tthis._toolArgumentValidationStats = stats;\n\t\treturn structuredClone(stats);\n\t}\n\n\t/**\n\t * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own\n\t * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of\n\t * truth for \"how much did this session and everything it spawned spend\" — used by print-mode\n\t * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.\n\t *\n\t * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child\n\t * that itself spawned grandchildren must report own + sub-usage in one number, or the parent\n\t * silently under-counts the grandchildren.\n\t */\n\tgetCumulativeUsage(): Usage {\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costInput = 0;\n\t\tlet costOutput = 0;\n\t\tlet costCacheRead = 0;\n\t\tlet costCacheWrite = 0;\n\t\tlet costTotal = 0;\n\t\tconst add = (usage: Usage) => {\n\t\t\tinput += usage.input;\n\t\t\toutput += usage.output;\n\t\t\tcacheRead += usage.cacheRead;\n\t\t\tcacheWrite += usage.cacheWrite;\n\t\t\ttotalTokens += usage.totalTokens;\n\t\t\tcostInput += usage.cost.input;\n\t\t\tcostOutput += usage.cost.output;\n\t\t\tcostCacheRead += usage.cost.cacheRead;\n\t\t\tcostCacheWrite += usage.cost.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t};\n\t\tfor (const message of this.deps.getState().messages) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst usage = (message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\tadd(usage);\n\t\t}\n\t\t// Roll up usage this session attributed to its own spawned children (single-hop).\n\t\tconst seenSpawnedReportIds = new Set<string>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\tadd(data.usage);\n\t\t}\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotalTokens,\n\t\t\tcost: {\n\t\t\t\tinput: costInput,\n\t\t\t\toutput: costOutput,\n\t\t\t\tcacheRead: costCacheRead,\n\t\t\t\tcacheWrite: costCacheWrite,\n\t\t\t\ttotal: costTotal,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Record usage spent by a spawned/subagent session so the footer can roll it into the\n\t * displayed cost. Persisted as a `CustomEntry` (`customType: \"spawned_usage\"`, Model A) so\n\t * it survives reload and is reconstructed exactly like main usage; a new/forked session\n\t * starts fresh because it owns a new log file.\n\t *\n\t * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a\n\t * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop\n\t * invariant documented on {@link SpawnedUsageReport}.\n\t *\n\t * @returns the id of the appended entry, or `undefined` if the report was a duplicate.\n\t */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined {\n\t\tconst reportId = opts?.reportId;\n\t\tif (reportId && this.getCurrentSessionCostTotals().seenSubagentReportIds.has(reportId)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst report: SpawnedUsageReport = {\n\t\t\tusage,\n\t\t\tlabel: opts?.label,\n\t\t\tsourceSessionId: opts?.sourceSessionId,\n\t\t\treportId,\n\t\t};\n\t\tconst entryId = this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);\n\t\tif (reportId) this.getCurrentSessionCostTotals();\n\t\treturn entryId;\n\t}\n\n\tprivate getCurrentSessionCostTotals(): CurrentSessionCostAccumulator {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tlet cache = this._currentSessionCostCache;\n\t\tconst getEntriesSince = sessionManager.getEntriesSince?.bind(sessionManager);\n\t\tif (!cache || entryCount < cache.entryCount || !getEntriesSince) {\n\t\t\tcache = {\n\t\t\t\tentryCount,\n\t\t\t\taccumulator: accumulateCurrentSessionCostsFromEntries(\n\t\t\t\t\tcreateCurrentSessionCostAccumulator(),\n\t\t\t\t\tsessionManager.getEntries(),\n\t\t\t\t),\n\t\t\t};\n\t\t\tthis._currentSessionCostCache = cache;\n\t\t} else if (entryCount > cache.entryCount) {\n\t\t\taccumulateCurrentSessionCostsFromEntries(cache.accumulator, getEntriesSince(cache.entryCount));\n\t\t\tcache.entryCount = entryCount;\n\t\t}\n\t\treturn cache.accumulator;\n\t}\n\n\t/**\n\t * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). The append-ordered\n\t * accumulator processes only new entries, so repeated turns do not rescan the full session.\n\t */\n\tgetSpawnedUsage(): SpawnedUsageTotals {\n\t\tconst current = this.getCurrentSessionCostTotals();\n\t\treturn { cost: current.subagentCost, reports: current.subagentReports };\n\t}\n\n\tgetCostSummary(now = new Date()): SessionCostSummary {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tconst window = getLocalDayWindow(now);\n\t\tconst dailyTotals = this.getDailyUsageTotals(now);\n\t\tconst cached = this._costSummaryCache;\n\t\tif (\n\t\t\tcached?.entryCount === entryCount &&\n\t\t\tcached.dailyTotals === dailyTotals &&\n\t\t\tcached.windowStartMs === window.startMs &&\n\t\t\tcached.windowEndMs === window.endMs\n\t\t) {\n\t\t\treturn cached.summary;\n\t\t}\n\t\tconst summary = createSessionCostSummary({\n\t\t\tcurrentTotals: this.getCurrentSessionCostTotals(),\n\t\t\tdailyTotals,\n\t\t\ttodayWindow: window,\n\t\t});\n\t\tthis._costSummaryCache = {\n\t\t\tentryCount,\n\t\t\tdailyTotals,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\tsummary,\n\t\t};\n\t\treturn summary;\n\t}\n\n\tgetDailyUsageTotals(now = new Date()): DailyUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst sessionDir = sessionManager.getSessionDir();\n\t\tconst scope = sessionManager.usesDefaultSessionDir() ? getSessionsDir() : sessionDir;\n\t\tconst nowMs = now.getTime();\n\t\tconst window = getLocalDayWindow(now);\n\t\tif (\n\t\t\tthis._dailyUsageCache?.sessionDir === scope &&\n\t\t\tthis._dailyUsageCache.windowStartMs === window.startMs &&\n\t\t\tthis._dailyUsageCache.windowEndMs === window.endMs &&\n\t\t\tthis._dailyUsageCache.expiresAt > nowMs\n\t\t) {\n\t\t\treturn this._dailyUsageCache.totals;\n\t\t}\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst liveSession = sessionFile ? { filePath: sessionFile, entries: sessionManager.getEntries() } : undefined;\n\t\tconst totals = sessionManager.usesDefaultSessionDir()\n\t\t\t? aggregateDailyUsageFromSessionRoot(scope, window, liveSession)\n\t\t\t: aggregateDailyUsageFromSessionFiles(sessionDir, window, liveSession);\n\t\tthis._dailyUsageCache = {\n\t\t\tsessionDir: scope,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\texpiresAt: Math.min(nowMs + 10_000, window.endMs),\n\t\t\ttotals,\n\t\t};\n\t\treturn totals;\n\t}\n\n\tgetDailyUsageBreakdown(formatLabel?: (label: string) => string, now = new Date()): string {\n\t\treturn formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);\n\t}\n\n\tprivate getPostCompactionUsageState(): { hasCompaction: boolean; hasPostCompactionUsage: boolean } {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst indexedSessionManager = sessionManager as unknown as {\n\t\t\tgetLeafId?: () => string | null;\n\t\t\tgetEntry?: (id: string) => SessionEntry | undefined;\n\t\t};\n\t\tconst getLeafId = indexedSessionManager.getLeafId?.bind(sessionManager);\n\t\tconst getEntry = indexedSessionManager.getEntry?.bind(sessionManager);\n\t\tconst leafId = getLeafId?.();\n\t\tif (getLeafId && getEntry && leafId !== undefined) {\n\t\t\tif (this._postCompactionUsageCache?.leafId === leafId) {\n\t\t\t\treturn { ...this._postCompactionUsageCache };\n\t\t\t}\n\t\t\tconst appended: SessionEntry[] = [];\n\t\t\tconst cachedLeafId = this._postCompactionUsageCache?.leafId;\n\t\t\tlet currentId = leafId;\n\t\t\tconst seen = new Set<string>();\n\t\t\twhile (currentId !== null && currentId !== cachedLeafId && !seen.has(currentId)) {\n\t\t\t\tseen.add(currentId);\n\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\tif (!entry) break;\n\t\t\t\tappended.push(entry);\n\t\t\t\tcurrentId = entry.parentId;\n\t\t\t}\n\t\t\tconst extendsCachedBranch = this._postCompactionUsageCache !== undefined && currentId === cachedLeafId;\n\t\t\tconst state = extendsCachedBranch\n\t\t\t\t? { ...this._postCompactionUsageCache! }\n\t\t\t\t: { leafId: null, hasCompaction: false, hasPostCompactionUsage: false };\n\t\t\tif (!extendsCachedBranch && currentId !== null) {\n\t\t\t\twhile (currentId !== null && !seen.has(currentId)) {\n\t\t\t\t\tseen.add(currentId);\n\t\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\t\tif (!entry) break;\n\t\t\t\t\tappended.push(entry);\n\t\t\t\t\tcurrentId = entry.parentId;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let index = appended.length - 1; index >= 0; index--) {\n\t\t\t\tconst entry = appended[index];\n\t\t\t\tif (entry.type === \"compaction\") {\n\t\t\t\t\tstate.hasCompaction = true;\n\t\t\t\t\tstate.hasPostCompactionUsage = false;\n\t\t\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\" && state.hasCompaction) {\n\t\t\t\t\t\tstate.hasPostCompactionUsage = calculateContextTokens(assistant.usage) > 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate.leafId = leafId;\n\t\t\tthis._postCompactionUsageCache = state;\n\t\t\treturn { ...state };\n\t\t}\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tif (!latestCompaction) return { hasCompaction: false, hasPostCompactionUsage: false };\n\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn { hasCompaction: true, hasPostCompactionUsage: calculateContextTokens(assistant.usage) > 0 };\n\t\t}\n\t\treturn { hasCompaction: true, hasPostCompactionUsage: false };\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// Walk backward only to the nearest compaction instead of rebuilding the whole branch on\n\t\t// every footer invalidation; the distance is bounded by the live post-compaction context.\n\t\tconst compactionState = this.getPostCompactionUsageState();\n\t\tif (compactionState.hasCompaction && !compactionState.hasPostCompactionUsage) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.deps.getMessages());\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst themeName = this.deps.getSettingsManager().getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.deps.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(sessionManager, this.deps.getState(), {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.deps\n\t\t\t.getMessages()\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n}\n"]}
@@ -86,6 +86,7 @@ export class SessionAnalytics {
86
86
  let gateFailures = 0;
87
87
  let deterministicGapFills = 0;
88
88
  let compactionsWithGateFailures = 0;
89
+ const checks = new Map();
89
90
  for (const entry of this.deps.getSessionManager().getEntries()) {
90
91
  if (entry.type !== "compaction")
91
92
  continue;
@@ -94,14 +95,40 @@ export class SessionAnalytics {
94
95
  continue;
95
96
  const rawGateFailures = details.verificationGateFailures;
96
97
  const rawGapFills = details.deterministicGapFills;
98
+ const rawChecks = details.verificationGateChecks;
97
99
  const entryGateFailures = typeof rawGateFailures === "number" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;
98
100
  const entryGapFills = typeof rawGapFills === "number" && Number.isFinite(rawGapFills) ? rawGapFills : 0;
99
101
  gateFailures += entryGateFailures;
100
102
  deterministicGapFills += entryGapFills;
101
103
  if (entryGateFailures > 0)
102
104
  compactionsWithGateFailures++;
105
+ if (rawChecks && typeof rawChecks === "object" && !Array.isArray(rawChecks)) {
106
+ for (const [check, rawStats] of Object.entries(rawChecks)) {
107
+ if (!rawStats || typeof rawStats !== "object" || Array.isArray(rawStats))
108
+ continue;
109
+ const stats = rawStats;
110
+ const failures = stats.failures;
111
+ if (typeof failures !== "number" || !Number.isFinite(failures) || failures <= 0)
112
+ continue;
113
+ const current = checks.get(check) ?? { failures: 0 };
114
+ current.failures += failures;
115
+ if (typeof stats.minScore === "number" && Number.isFinite(stats.minScore)) {
116
+ current.minScore = Math.min(current.minScore ?? stats.minScore, stats.minScore);
117
+ }
118
+ if (typeof stats.maxScore === "number" && Number.isFinite(stats.maxScore)) {
119
+ current.maxScore = Math.max(current.maxScore ?? stats.maxScore, stats.maxScore);
120
+ }
121
+ if (typeof stats.threshold === "number" && Number.isFinite(stats.threshold)) {
122
+ current.threshold = stats.threshold;
123
+ }
124
+ if (stats.comparator === "minimum" || stats.comparator === "maximum") {
125
+ current.comparator = stats.comparator;
126
+ }
127
+ checks.set(check, current);
128
+ }
129
+ }
103
130
  }
104
- return { gateFailures, deterministicGapFills, compactionsWithGateFailures };
131
+ return { gateFailures, deterministicGapFills, compactionsWithGateFailures, checks: Object.fromEntries(checks) };
105
132
  }
106
133
  recordToolArgumentValidation(record) {
107
134
  const stats = this.getToolArgumentValidationStats();
@@ -1 +1 @@
1
- {"version":3,"file":"session-analytics.js","sourceRoot":"","sources":["../../src/core/session-analytics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session usage / cost / stats accounting, context-window usage, and session export.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Read-only over the session\n * except for its two owned memo caches (`_spawnedUsageCache`, `_dailyUsageCache`) — it never mutates\n * agent or session state. Single source of truth for \"how much did this session and its spawned\n * subtree spend\" (footer roll-up, print-mode child reporting), the daily cross-session totals, the\n * /context window estimate, and HTML/JSONL export of the current branch.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentState } from \"@caupulican/pi-agent-core\";\nimport {\n\tCURRENT_SESSION_VERSION,\n\tcalculateContextTokens,\n\testimateContextTokens,\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionManager,\n} from \"@caupulican/pi-agent-core/node\";\nimport type {\n\tAssistantMessage,\n\tModel,\n\tToolArgumentExecutionOutcome,\n\tToolArgumentTeachState,\n\tToolArgumentValidationTelemetryEvent,\n\tUsage,\n} from \"@caupulican/pi-ai\";\nimport { getSessionsDir } from \"../config.ts\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport {\n\ttype SessionStats,\n\tSPAWNED_USAGE_CUSTOM_TYPE,\n\ttype SpawnedUsageReport,\n\ttype SpawnedUsageTotals,\n} from \"./agent-session.ts\";\nimport {\n\taccumulateCurrentSessionCostsFromEntries,\n\ttype CurrentSessionCostAccumulator,\n\tcreateCurrentSessionCostAccumulator,\n\tcreateSessionCostSummary,\n\ttype SessionCostSummary,\n} from \"./cost/cost-summary.ts\";\nimport {\n\taggregateDailyUsageFromSessionFiles,\n\taggregateDailyUsageFromSessionRoot,\n\ttype DailyUsageTotals,\n\tformatDailyUsageBreakdown,\n\tgetLocalDayWindow,\n} from \"./cost/daily-usage.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport type { ContextUsage, ToolDefinition } from \"./extensions/index.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport {\n\tisToolArgumentValidationLogRecord,\n\ttype ToolArgumentValidationLogRecord,\n} from \"./tool-recovery-log-records.ts\";\nimport {\n\tconsumeToolArgumentValidationRecord,\n\tcreateEmptyToolArgumentValidationStats,\n\tgetToolRecoveryRecordSequence,\n\treadPersistedToolRecoveryStats,\n} from \"./tool-recovery-stats.ts\";\n\nexport const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = \"tool_argument_validation\";\n\nexport interface ToolArgumentValidationTeachEfficacy {\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n\trepairedThenSucceeded: number;\n\trepairedThenFailed: number;\n\trepairedThenNotRun: number;\n}\n\nexport interface ToolArgumentValidationStats {\n\tclean: number;\n\trepaired: number;\n\tbounced: number;\n\tfailureModes: Record<string, number>;\n\trepairsApplied: Record<string, number>;\n\ttaught: Record<ToolArgumentTeachState, number>;\n\texecutionOutcome: Record<ToolArgumentExecutionOutcome, number>;\n\tteachEfficacy: Record<string, ToolArgumentValidationTeachEfficacy>;\n}\n\nexport interface ToolArgumentValidationRecord extends ToolArgumentValidationTelemetryEvent {\n\tversion: 1;\n}\n\nexport interface SessionAnalyticsDeps {\n\t/** Live agent state — assistant-message usage and message counts are read from here. */\n\tgetState(): AgentState;\n\t/** All messages (agent state view) — used for context-window estimation and last-assistant text. */\n\tgetMessages(): AgentMessage[];\n\t/** Current session model — its context window bounds the /context estimate. */\n\tgetModel(): Model<any> | undefined;\n\t/** Session log — entries feed spawned-usage roll-up, daily totals, branch export. */\n\tgetSessionManager(): SessionManager;\n\t/** Settings — the export theme is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Resolve a tool definition for the HTML export's custom-tool renderer. */\n\tgetToolDefinition(name: string): ToolDefinition | undefined;\n\t/** Sidecar recovery telemetry log; read on demand so turn handling never writes session custom entries. */\n\tgetToolRecoveryEventLogPath(): string;\n}\n\nexport class SessionAnalytics {\n\t/** Incremental aggregate over append-ordered session entries. */\n\tprivate _currentSessionCostCache?: {\n\t\tentryCount: number;\n\t\taccumulator: CurrentSessionCostAccumulator;\n\t};\n\t/** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */\n\tprivate _dailyUsageCache?: {\n\t\tsessionDir: string;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\texpiresAt: number;\n\t\ttotals: DailyUsageTotals;\n\t};\n\t/** Memoized full cost summary. The footer renders on every streamed delta and keystroke, so\n\t * rescanning a long session log there turns redraw into O(entries) work per frame. */\n\tprivate _costSummaryCache?: {\n\t\tentryCount: number;\n\t\tdailyTotals: DailyUsageTotals;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\tsummary: SessionCostSummary;\n\t};\n\t/** Cumulative stats initialized once from persisted telemetry, then updated without retaining record details. */\n\tprivate _toolArgumentValidationStats: ToolArgumentValidationStats | undefined;\n\t/** Incremental context-usage state keyed by the append-only branch leaf. */\n\tprivate _postCompactionUsageCache?: {\n\t\tleafId: string | null;\n\t\thasCompaction: boolean;\n\t\thasPostCompactionUsage: boolean;\n\t};\n\n\tprivate readonly deps: SessionAnalyticsDeps;\n\n\tconstructor(deps: SessionAnalyticsDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.deps.getState();\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst toolArgumentValidation = this.getToolArgumentValidationStats();\n\t\tconst compactionGates = this.getCompactionGateStats();\n\n\t\treturn {\n\t\t\tsessionFile: this.deps.getSessionManager().getSessionFile(),\n\t\t\tsessionId: this.deps.getSessionManager().getSessionId(),\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: totalInput,\n\t\t\t\toutput: totalOutput,\n\t\t\t\tcacheRead: totalCacheRead,\n\t\t\t\tcacheWrite: totalCacheWrite,\n\t\t\t\ttotal: totalInput + totalOutput + totalCacheRead + totalCacheWrite,\n\t\t\t},\n\t\t\tcost: totalCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t\ttoolArgumentValidation,\n\t\t\tcompactionGates,\n\t\t};\n\t}\n\n\tgetCompactionGateStats(): SessionStats[\"compactionGates\"] {\n\t\tlet gateFailures = 0;\n\t\tlet deterministicGapFills = 0;\n\t\tlet compactionsWithGateFailures = 0;\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"compaction\") continue;\n\t\t\tconst details = entry.details;\n\t\t\tif (!details || typeof details !== \"object\") continue;\n\t\t\tconst rawGateFailures = (details as { verificationGateFailures?: unknown }).verificationGateFailures;\n\t\t\tconst rawGapFills = (details as { deterministicGapFills?: unknown }).deterministicGapFills;\n\t\t\tconst entryGateFailures =\n\t\t\t\ttypeof rawGateFailures === \"number\" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;\n\t\t\tconst entryGapFills = typeof rawGapFills === \"number\" && Number.isFinite(rawGapFills) ? rawGapFills : 0;\n\t\t\tgateFailures += entryGateFailures;\n\t\t\tdeterministicGapFills += entryGapFills;\n\t\t\tif (entryGateFailures > 0) compactionsWithGateFailures++;\n\t\t}\n\t\treturn { gateFailures, deterministicGapFills, compactionsWithGateFailures };\n\t}\n\n\trecordToolArgumentValidation(record: ToolArgumentValidationLogRecord): void {\n\t\tconst stats = this.getToolArgumentValidationStats();\n\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\tthis._toolArgumentValidationStats = stats;\n\t}\n\n\tprivate readToolArgumentValidationSidecarRecords(): ToolArgumentValidationLogRecord[] {\n\t\tconst filePath = this.deps.getToolRecoveryEventLogPath();\n\t\tif (!existsSync(filePath)) return [];\n\t\tconst records: ToolArgumentValidationLogRecord[] = [];\n\t\ttry {\n\t\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\t\tfor (const line of readFileSync(filePath, \"utf-8\").split(\"\\n\")) {\n\t\t\t\tif (line.trim().length === 0) continue;\n\t\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\t\tif (!isToolArgumentValidationLogRecord(parsed) || parsed.sessionId !== sessionId) continue;\n\t\t\t\trecords.push(parsed);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\treturn records;\n\t}\n\n\tgetToolArgumentValidationStats(): ToolArgumentValidationStats {\n\t\tif (this._toolArgumentValidationStats) return structuredClone(this._toolArgumentValidationStats);\n\t\tconst eventLogPath = this.deps.getToolRecoveryEventLogPath();\n\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\tconst persisted = readPersistedToolRecoveryStats(eventLogPath, sessionId);\n\t\tconst stats = persisted ? structuredClone(persisted.stats) : createEmptyToolArgumentValidationStats();\n\t\tconst seen = new Set<string>();\n\t\tconst consume = (record: ToolArgumentValidationRecord, key: string): void => {\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\t};\n\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE) continue;\n\t\t\tconst record = entry.data as ToolArgumentValidationRecord | undefined;\n\t\t\tif (!record || record.version !== 1) continue;\n\t\t\tconsume(record, `legacy:${entry.id}`);\n\t\t}\n\t\tfor (const record of this.readToolArgumentValidationSidecarRecords()) {\n\t\t\tif (\n\t\t\t\tpersisted &&\n\t\t\t\trecord.sessionId === sessionId &&\n\t\t\t\tgetToolRecoveryRecordSequence(record) <= persisted.lastRecordSequence\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\n\t\tthis._toolArgumentValidationStats = stats;\n\t\treturn structuredClone(stats);\n\t}\n\n\t/**\n\t * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own\n\t * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of\n\t * truth for \"how much did this session and everything it spawned spend\" — used by print-mode\n\t * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.\n\t *\n\t * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child\n\t * that itself spawned grandchildren must report own + sub-usage in one number, or the parent\n\t * silently under-counts the grandchildren.\n\t */\n\tgetCumulativeUsage(): Usage {\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costInput = 0;\n\t\tlet costOutput = 0;\n\t\tlet costCacheRead = 0;\n\t\tlet costCacheWrite = 0;\n\t\tlet costTotal = 0;\n\t\tconst add = (usage: Usage) => {\n\t\t\tinput += usage.input;\n\t\t\toutput += usage.output;\n\t\t\tcacheRead += usage.cacheRead;\n\t\t\tcacheWrite += usage.cacheWrite;\n\t\t\ttotalTokens += usage.totalTokens;\n\t\t\tcostInput += usage.cost.input;\n\t\t\tcostOutput += usage.cost.output;\n\t\t\tcostCacheRead += usage.cost.cacheRead;\n\t\t\tcostCacheWrite += usage.cost.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t};\n\t\tfor (const message of this.deps.getState().messages) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst usage = (message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\tadd(usage);\n\t\t}\n\t\t// Roll up usage this session attributed to its own spawned children (single-hop).\n\t\tconst seenSpawnedReportIds = new Set<string>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\tadd(data.usage);\n\t\t}\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotalTokens,\n\t\t\tcost: {\n\t\t\t\tinput: costInput,\n\t\t\t\toutput: costOutput,\n\t\t\t\tcacheRead: costCacheRead,\n\t\t\t\tcacheWrite: costCacheWrite,\n\t\t\t\ttotal: costTotal,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Record usage spent by a spawned/subagent session so the footer can roll it into the\n\t * displayed cost. Persisted as a `CustomEntry` (`customType: \"spawned_usage\"`, Model A) so\n\t * it survives reload and is reconstructed exactly like main usage; a new/forked session\n\t * starts fresh because it owns a new log file.\n\t *\n\t * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a\n\t * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop\n\t * invariant documented on {@link SpawnedUsageReport}.\n\t *\n\t * @returns the id of the appended entry, or `undefined` if the report was a duplicate.\n\t */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined {\n\t\tconst reportId = opts?.reportId;\n\t\tif (reportId && this.getCurrentSessionCostTotals().seenSubagentReportIds.has(reportId)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst report: SpawnedUsageReport = {\n\t\t\tusage,\n\t\t\tlabel: opts?.label,\n\t\t\tsourceSessionId: opts?.sourceSessionId,\n\t\t\treportId,\n\t\t};\n\t\tconst entryId = this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);\n\t\tif (reportId) this.getCurrentSessionCostTotals();\n\t\treturn entryId;\n\t}\n\n\tprivate getCurrentSessionCostTotals(): CurrentSessionCostAccumulator {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tlet cache = this._currentSessionCostCache;\n\t\tconst getEntriesSince = sessionManager.getEntriesSince?.bind(sessionManager);\n\t\tif (!cache || entryCount < cache.entryCount || !getEntriesSince) {\n\t\t\tcache = {\n\t\t\t\tentryCount,\n\t\t\t\taccumulator: accumulateCurrentSessionCostsFromEntries(\n\t\t\t\t\tcreateCurrentSessionCostAccumulator(),\n\t\t\t\t\tsessionManager.getEntries(),\n\t\t\t\t),\n\t\t\t};\n\t\t\tthis._currentSessionCostCache = cache;\n\t\t} else if (entryCount > cache.entryCount) {\n\t\t\taccumulateCurrentSessionCostsFromEntries(cache.accumulator, getEntriesSince(cache.entryCount));\n\t\t\tcache.entryCount = entryCount;\n\t\t}\n\t\treturn cache.accumulator;\n\t}\n\n\t/**\n\t * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). The append-ordered\n\t * accumulator processes only new entries, so repeated turns do not rescan the full session.\n\t */\n\tgetSpawnedUsage(): SpawnedUsageTotals {\n\t\tconst current = this.getCurrentSessionCostTotals();\n\t\treturn { cost: current.subagentCost, reports: current.subagentReports };\n\t}\n\n\tgetCostSummary(now = new Date()): SessionCostSummary {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tconst window = getLocalDayWindow(now);\n\t\tconst dailyTotals = this.getDailyUsageTotals(now);\n\t\tconst cached = this._costSummaryCache;\n\t\tif (\n\t\t\tcached?.entryCount === entryCount &&\n\t\t\tcached.dailyTotals === dailyTotals &&\n\t\t\tcached.windowStartMs === window.startMs &&\n\t\t\tcached.windowEndMs === window.endMs\n\t\t) {\n\t\t\treturn cached.summary;\n\t\t}\n\t\tconst summary = createSessionCostSummary({\n\t\t\tcurrentTotals: this.getCurrentSessionCostTotals(),\n\t\t\tdailyTotals,\n\t\t\ttodayWindow: window,\n\t\t});\n\t\tthis._costSummaryCache = {\n\t\t\tentryCount,\n\t\t\tdailyTotals,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\tsummary,\n\t\t};\n\t\treturn summary;\n\t}\n\n\tgetDailyUsageTotals(now = new Date()): DailyUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst sessionDir = sessionManager.getSessionDir();\n\t\tconst scope = sessionManager.usesDefaultSessionDir() ? getSessionsDir() : sessionDir;\n\t\tconst nowMs = now.getTime();\n\t\tconst window = getLocalDayWindow(now);\n\t\tif (\n\t\t\tthis._dailyUsageCache?.sessionDir === scope &&\n\t\t\tthis._dailyUsageCache.windowStartMs === window.startMs &&\n\t\t\tthis._dailyUsageCache.windowEndMs === window.endMs &&\n\t\t\tthis._dailyUsageCache.expiresAt > nowMs\n\t\t) {\n\t\t\treturn this._dailyUsageCache.totals;\n\t\t}\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst liveSession = sessionFile ? { filePath: sessionFile, entries: sessionManager.getEntries() } : undefined;\n\t\tconst totals = sessionManager.usesDefaultSessionDir()\n\t\t\t? aggregateDailyUsageFromSessionRoot(scope, window, liveSession)\n\t\t\t: aggregateDailyUsageFromSessionFiles(sessionDir, window, liveSession);\n\t\tthis._dailyUsageCache = {\n\t\t\tsessionDir: scope,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\texpiresAt: Math.min(nowMs + 10_000, window.endMs),\n\t\t\ttotals,\n\t\t};\n\t\treturn totals;\n\t}\n\n\tgetDailyUsageBreakdown(formatLabel?: (label: string) => string, now = new Date()): string {\n\t\treturn formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);\n\t}\n\n\tprivate getPostCompactionUsageState(): { hasCompaction: boolean; hasPostCompactionUsage: boolean } {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst indexedSessionManager = sessionManager as unknown as {\n\t\t\tgetLeafId?: () => string | null;\n\t\t\tgetEntry?: (id: string) => SessionEntry | undefined;\n\t\t};\n\t\tconst getLeafId = indexedSessionManager.getLeafId?.bind(sessionManager);\n\t\tconst getEntry = indexedSessionManager.getEntry?.bind(sessionManager);\n\t\tconst leafId = getLeafId?.();\n\t\tif (getLeafId && getEntry && leafId !== undefined) {\n\t\t\tif (this._postCompactionUsageCache?.leafId === leafId) {\n\t\t\t\treturn { ...this._postCompactionUsageCache };\n\t\t\t}\n\t\t\tconst appended: SessionEntry[] = [];\n\t\t\tconst cachedLeafId = this._postCompactionUsageCache?.leafId;\n\t\t\tlet currentId = leafId;\n\t\t\tconst seen = new Set<string>();\n\t\t\twhile (currentId !== null && currentId !== cachedLeafId && !seen.has(currentId)) {\n\t\t\t\tseen.add(currentId);\n\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\tif (!entry) break;\n\t\t\t\tappended.push(entry);\n\t\t\t\tcurrentId = entry.parentId;\n\t\t\t}\n\t\t\tconst extendsCachedBranch = this._postCompactionUsageCache !== undefined && currentId === cachedLeafId;\n\t\t\tconst state = extendsCachedBranch\n\t\t\t\t? { ...this._postCompactionUsageCache! }\n\t\t\t\t: { leafId: null, hasCompaction: false, hasPostCompactionUsage: false };\n\t\t\tif (!extendsCachedBranch && currentId !== null) {\n\t\t\t\twhile (currentId !== null && !seen.has(currentId)) {\n\t\t\t\t\tseen.add(currentId);\n\t\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\t\tif (!entry) break;\n\t\t\t\t\tappended.push(entry);\n\t\t\t\t\tcurrentId = entry.parentId;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let index = appended.length - 1; index >= 0; index--) {\n\t\t\t\tconst entry = appended[index];\n\t\t\t\tif (entry.type === \"compaction\") {\n\t\t\t\t\tstate.hasCompaction = true;\n\t\t\t\t\tstate.hasPostCompactionUsage = false;\n\t\t\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\" && state.hasCompaction) {\n\t\t\t\t\t\tstate.hasPostCompactionUsage = calculateContextTokens(assistant.usage) > 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate.leafId = leafId;\n\t\t\tthis._postCompactionUsageCache = state;\n\t\t\treturn { ...state };\n\t\t}\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tif (!latestCompaction) return { hasCompaction: false, hasPostCompactionUsage: false };\n\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn { hasCompaction: true, hasPostCompactionUsage: calculateContextTokens(assistant.usage) > 0 };\n\t\t}\n\t\treturn { hasCompaction: true, hasPostCompactionUsage: false };\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// Walk backward only to the nearest compaction instead of rebuilding the whole branch on\n\t\t// every footer invalidation; the distance is bounded by the live post-compaction context.\n\t\tconst compactionState = this.getPostCompactionUsageState();\n\t\tif (compactionState.hasCompaction && !compactionState.hasPostCompactionUsage) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.deps.getMessages());\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst themeName = this.deps.getSettingsManager().getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.deps.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(sessionManager, this.deps.getState(), {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.deps\n\t\t\t.getMessages()\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-analytics.js","sourceRoot":"","sources":["../../src/core/session-analytics.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Session usage / cost / stats accounting, context-window usage, and session export.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Read-only over the session\n * except for its two owned memo caches (`_spawnedUsageCache`, `_dailyUsageCache`) — it never mutates\n * agent or session state. Single source of truth for \"how much did this session and its spawned\n * subtree spend\" (footer roll-up, print-mode child reporting), the daily cross-session totals, the\n * /context window estimate, and HTML/JSONL export of the current branch.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport type { AgentMessage, AgentState } from \"@caupulican/pi-agent-core\";\nimport {\n\tCURRENT_SESSION_VERSION,\n\tcalculateContextTokens,\n\testimateContextTokens,\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionHeader,\n\ttype SessionManager,\n} from \"@caupulican/pi-agent-core/node\";\nimport type {\n\tAssistantMessage,\n\tModel,\n\tToolArgumentExecutionOutcome,\n\tToolArgumentTeachState,\n\tToolArgumentValidationTelemetryEvent,\n\tUsage,\n} from \"@caupulican/pi-ai\";\nimport { getSessionsDir } from \"../config.ts\";\nimport { theme } from \"../modes/interactive/theme/theme.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport {\n\ttype CompactionGateCheckStats,\n\ttype SessionStats,\n\tSPAWNED_USAGE_CUSTOM_TYPE,\n\ttype SpawnedUsageReport,\n\ttype SpawnedUsageTotals,\n} from \"./agent-session.ts\";\nimport {\n\taccumulateCurrentSessionCostsFromEntries,\n\ttype CurrentSessionCostAccumulator,\n\tcreateCurrentSessionCostAccumulator,\n\tcreateSessionCostSummary,\n\ttype SessionCostSummary,\n} from \"./cost/cost-summary.ts\";\nimport {\n\taggregateDailyUsageFromSessionFiles,\n\taggregateDailyUsageFromSessionRoot,\n\ttype DailyUsageTotals,\n\tformatDailyUsageBreakdown,\n\tgetLocalDayWindow,\n} from \"./cost/daily-usage.ts\";\nimport { exportSessionToHtml, type ToolHtmlRenderer } from \"./export-html/index.ts\";\nimport { createToolHtmlRenderer } from \"./export-html/tool-renderer.ts\";\nimport type { ContextUsage, ToolDefinition } from \"./extensions/index.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport {\n\tisToolArgumentValidationLogRecord,\n\ttype ToolArgumentValidationLogRecord,\n} from \"./tool-recovery-log-records.ts\";\nimport {\n\tconsumeToolArgumentValidationRecord,\n\tcreateEmptyToolArgumentValidationStats,\n\tgetToolRecoveryRecordSequence,\n\treadPersistedToolRecoveryStats,\n} from \"./tool-recovery-stats.ts\";\n\nexport const TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE = \"tool_argument_validation\";\n\nexport interface ToolArgumentValidationTeachEfficacy {\n\trecurrenceBefore: number;\n\trecurrenceAfter: number;\n\trepairedThenSucceeded: number;\n\trepairedThenFailed: number;\n\trepairedThenNotRun: number;\n}\n\nexport interface ToolArgumentValidationStats {\n\tclean: number;\n\trepaired: number;\n\tbounced: number;\n\tfailureModes: Record<string, number>;\n\trepairsApplied: Record<string, number>;\n\ttaught: Record<ToolArgumentTeachState, number>;\n\texecutionOutcome: Record<ToolArgumentExecutionOutcome, number>;\n\tteachEfficacy: Record<string, ToolArgumentValidationTeachEfficacy>;\n}\n\nexport interface ToolArgumentValidationRecord extends ToolArgumentValidationTelemetryEvent {\n\tversion: 1;\n}\n\nexport interface SessionAnalyticsDeps {\n\t/** Live agent state — assistant-message usage and message counts are read from here. */\n\tgetState(): AgentState;\n\t/** All messages (agent state view) — used for context-window estimation and last-assistant text. */\n\tgetMessages(): AgentMessage[];\n\t/** Current session model — its context window bounds the /context estimate. */\n\tgetModel(): Model<any> | undefined;\n\t/** Session log — entries feed spawned-usage roll-up, daily totals, branch export. */\n\tgetSessionManager(): SessionManager;\n\t/** Settings — the export theme is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Resolve a tool definition for the HTML export's custom-tool renderer. */\n\tgetToolDefinition(name: string): ToolDefinition | undefined;\n\t/** Sidecar recovery telemetry log; read on demand so turn handling never writes session custom entries. */\n\tgetToolRecoveryEventLogPath(): string;\n}\n\nexport class SessionAnalytics {\n\t/** Incremental aggregate over append-ordered session entries. */\n\tprivate _currentSessionCostCache?: {\n\t\tentryCount: number;\n\t\taccumulator: CurrentSessionCostAccumulator;\n\t};\n\t/** Memoized daily usage totals with a short TTL, keyed by the resolved scope dir and local-day window. */\n\tprivate _dailyUsageCache?: {\n\t\tsessionDir: string;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\texpiresAt: number;\n\t\ttotals: DailyUsageTotals;\n\t};\n\t/** Memoized full cost summary. The footer renders on every streamed delta and keystroke, so\n\t * rescanning a long session log there turns redraw into O(entries) work per frame. */\n\tprivate _costSummaryCache?: {\n\t\tentryCount: number;\n\t\tdailyTotals: DailyUsageTotals;\n\t\twindowStartMs: number;\n\t\twindowEndMs: number;\n\t\tsummary: SessionCostSummary;\n\t};\n\t/** Cumulative stats initialized once from persisted telemetry, then updated without retaining record details. */\n\tprivate _toolArgumentValidationStats: ToolArgumentValidationStats | undefined;\n\t/** Incremental context-usage state keyed by the append-only branch leaf. */\n\tprivate _postCompactionUsageCache?: {\n\t\tleafId: string | null;\n\t\thasCompaction: boolean;\n\t\thasPostCompactionUsage: boolean;\n\t};\n\n\tprivate readonly deps: SessionAnalyticsDeps;\n\n\tconstructor(deps: SessionAnalyticsDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tgetSessionStats(): SessionStats {\n\t\tconst state = this.deps.getState();\n\t\tconst userMessages = state.messages.filter((m) => m.role === \"user\").length;\n\t\tconst assistantMessages = state.messages.filter((m) => m.role === \"assistant\").length;\n\t\tconst toolResults = state.messages.filter((m) => m.role === \"toolResult\").length;\n\n\t\tlet toolCalls = 0;\n\t\tlet totalInput = 0;\n\t\tlet totalOutput = 0;\n\t\tlet totalCacheRead = 0;\n\t\tlet totalCacheWrite = 0;\n\t\tlet totalCost = 0;\n\n\t\tfor (const message of state.messages) {\n\t\t\tif (message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = message as AssistantMessage;\n\t\t\t\ttoolCalls += assistantMsg.content.filter((c) => c.type === \"toolCall\").length;\n\t\t\t\ttotalInput += assistantMsg.usage.input;\n\t\t\t\ttotalOutput += assistantMsg.usage.output;\n\t\t\t\ttotalCacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\ttotalCacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\ttotalCost += assistantMsg.usage.cost.total;\n\t\t\t}\n\t\t}\n\n\t\tconst toolArgumentValidation = this.getToolArgumentValidationStats();\n\t\tconst compactionGates = this.getCompactionGateStats();\n\n\t\treturn {\n\t\t\tsessionFile: this.deps.getSessionManager().getSessionFile(),\n\t\t\tsessionId: this.deps.getSessionManager().getSessionId(),\n\t\t\tuserMessages,\n\t\t\tassistantMessages,\n\t\t\ttoolCalls,\n\t\t\ttoolResults,\n\t\t\ttotalMessages: state.messages.length,\n\t\t\ttokens: {\n\t\t\t\tinput: totalInput,\n\t\t\t\toutput: totalOutput,\n\t\t\t\tcacheRead: totalCacheRead,\n\t\t\t\tcacheWrite: totalCacheWrite,\n\t\t\t\ttotal: totalInput + totalOutput + totalCacheRead + totalCacheWrite,\n\t\t\t},\n\t\t\tcost: totalCost,\n\t\t\tcontextUsage: this.getContextUsage(),\n\t\t\ttoolArgumentValidation,\n\t\t\tcompactionGates,\n\t\t};\n\t}\n\n\tgetCompactionGateStats(): SessionStats[\"compactionGates\"] {\n\t\tlet gateFailures = 0;\n\t\tlet deterministicGapFills = 0;\n\t\tlet compactionsWithGateFailures = 0;\n\t\tconst checks = new Map<string, CompactionGateCheckStats>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"compaction\") continue;\n\t\t\tconst details = entry.details;\n\t\t\tif (!details || typeof details !== \"object\") continue;\n\t\t\tconst rawGateFailures = (details as { verificationGateFailures?: unknown }).verificationGateFailures;\n\t\t\tconst rawGapFills = (details as { deterministicGapFills?: unknown }).deterministicGapFills;\n\t\t\tconst rawChecks = (details as { verificationGateChecks?: unknown }).verificationGateChecks;\n\t\t\tconst entryGateFailures =\n\t\t\t\ttypeof rawGateFailures === \"number\" && Number.isFinite(rawGateFailures) ? rawGateFailures : 0;\n\t\t\tconst entryGapFills = typeof rawGapFills === \"number\" && Number.isFinite(rawGapFills) ? rawGapFills : 0;\n\t\t\tgateFailures += entryGateFailures;\n\t\t\tdeterministicGapFills += entryGapFills;\n\t\t\tif (entryGateFailures > 0) compactionsWithGateFailures++;\n\t\t\tif (rawChecks && typeof rawChecks === \"object\" && !Array.isArray(rawChecks)) {\n\t\t\t\tfor (const [check, rawStats] of Object.entries(rawChecks)) {\n\t\t\t\t\tif (!rawStats || typeof rawStats !== \"object\" || Array.isArray(rawStats)) continue;\n\t\t\t\t\tconst stats = rawStats as Record<string, unknown>;\n\t\t\t\t\tconst failures = stats.failures;\n\t\t\t\t\tif (typeof failures !== \"number\" || !Number.isFinite(failures) || failures <= 0) continue;\n\t\t\t\t\tconst current = checks.get(check) ?? { failures: 0 };\n\t\t\t\t\tcurrent.failures += failures;\n\t\t\t\t\tif (typeof stats.minScore === \"number\" && Number.isFinite(stats.minScore)) {\n\t\t\t\t\t\tcurrent.minScore = Math.min(current.minScore ?? stats.minScore, stats.minScore);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof stats.maxScore === \"number\" && Number.isFinite(stats.maxScore)) {\n\t\t\t\t\t\tcurrent.maxScore = Math.max(current.maxScore ?? stats.maxScore, stats.maxScore);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof stats.threshold === \"number\" && Number.isFinite(stats.threshold)) {\n\t\t\t\t\t\tcurrent.threshold = stats.threshold;\n\t\t\t\t\t}\n\t\t\t\t\tif (stats.comparator === \"minimum\" || stats.comparator === \"maximum\") {\n\t\t\t\t\t\tcurrent.comparator = stats.comparator;\n\t\t\t\t\t}\n\t\t\t\t\tchecks.set(check, current);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { gateFailures, deterministicGapFills, compactionsWithGateFailures, checks: Object.fromEntries(checks) };\n\t}\n\n\trecordToolArgumentValidation(record: ToolArgumentValidationLogRecord): void {\n\t\tconst stats = this.getToolArgumentValidationStats();\n\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\tthis._toolArgumentValidationStats = stats;\n\t}\n\n\tprivate readToolArgumentValidationSidecarRecords(): ToolArgumentValidationLogRecord[] {\n\t\tconst filePath = this.deps.getToolRecoveryEventLogPath();\n\t\tif (!existsSync(filePath)) return [];\n\t\tconst records: ToolArgumentValidationLogRecord[] = [];\n\t\ttry {\n\t\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\t\tfor (const line of readFileSync(filePath, \"utf-8\").split(\"\\n\")) {\n\t\t\t\tif (line.trim().length === 0) continue;\n\t\t\t\tconst parsed = JSON.parse(line) as unknown;\n\t\t\t\tif (!isToolArgumentValidationLogRecord(parsed) || parsed.sessionId !== sessionId) continue;\n\t\t\t\trecords.push(parsed);\n\t\t\t}\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\treturn records;\n\t}\n\n\tgetToolArgumentValidationStats(): ToolArgumentValidationStats {\n\t\tif (this._toolArgumentValidationStats) return structuredClone(this._toolArgumentValidationStats);\n\t\tconst eventLogPath = this.deps.getToolRecoveryEventLogPath();\n\t\tconst sessionId = this.deps.getSessionManager().getSessionId();\n\t\tconst persisted = readPersistedToolRecoveryStats(eventLogPath, sessionId);\n\t\tconst stats = persisted ? structuredClone(persisted.stats) : createEmptyToolArgumentValidationStats();\n\t\tconst seen = new Set<string>();\n\t\tconst consume = (record: ToolArgumentValidationRecord, key: string): void => {\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tconsumeToolArgumentValidationRecord(stats, record);\n\t\t};\n\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== TOOL_ARGUMENT_VALIDATION_CUSTOM_TYPE) continue;\n\t\t\tconst record = entry.data as ToolArgumentValidationRecord | undefined;\n\t\t\tif (!record || record.version !== 1) continue;\n\t\t\tconsume(record, `legacy:${entry.id}`);\n\t\t}\n\t\tfor (const record of this.readToolArgumentValidationSidecarRecords()) {\n\t\t\tif (\n\t\t\t\tpersisted &&\n\t\t\t\trecord.sessionId === sessionId &&\n\t\t\t\tgetToolRecoveryRecordSequence(record) <= persisted.lastRecordSequence\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconsume(record, record.recordId);\n\t\t}\n\n\t\tthis._toolArgumentValidationStats = stats;\n\t\treturn structuredClone(stats);\n\t}\n\n\t/**\n\t * Cumulative usage (full breakdown) for this session's entire spawn subtree: its own\n\t * assistant messages PLUS every `spawned_usage` report it has rolled up. Single source of\n\t * truth for \"how much did this session and everything it spawned spend\" — used by print-mode\n\t * to emit a child's total so a spawner can roll it up via {@link addSpawnedUsage}.\n\t *\n\t * Including the `spawned_usage` reports is what keeps the single-hop invariant intact: a child\n\t * that itself spawned grandchildren must report own + sub-usage in one number, or the parent\n\t * silently under-counts the grandchildren.\n\t */\n\tgetCumulativeUsage(): Usage {\n\t\tlet input = 0;\n\t\tlet output = 0;\n\t\tlet cacheRead = 0;\n\t\tlet cacheWrite = 0;\n\t\tlet totalTokens = 0;\n\t\tlet costInput = 0;\n\t\tlet costOutput = 0;\n\t\tlet costCacheRead = 0;\n\t\tlet costCacheWrite = 0;\n\t\tlet costTotal = 0;\n\t\tconst add = (usage: Usage) => {\n\t\t\tinput += usage.input;\n\t\t\toutput += usage.output;\n\t\t\tcacheRead += usage.cacheRead;\n\t\t\tcacheWrite += usage.cacheWrite;\n\t\t\ttotalTokens += usage.totalTokens;\n\t\t\tcostInput += usage.cost.input;\n\t\t\tcostOutput += usage.cost.output;\n\t\t\tcostCacheRead += usage.cost.cacheRead;\n\t\t\tcostCacheWrite += usage.cost.cacheWrite;\n\t\t\tcostTotal += usage.cost.total;\n\t\t};\n\t\tfor (const message of this.deps.getState().messages) {\n\t\t\tif (message.role !== \"assistant\") continue;\n\t\t\tconst usage = (message as AssistantMessage).usage;\n\t\t\tif (!usage) continue;\n\t\t\tadd(usage);\n\t\t}\n\t\t// Roll up usage this session attributed to its own spawned children (single-hop).\n\t\tconst seenSpawnedReportIds = new Set<string>();\n\t\tfor (const entry of this.deps.getSessionManager().getEntries()) {\n\t\t\tif (entry.type !== \"custom\" || entry.customType !== SPAWNED_USAGE_CUSTOM_TYPE) continue;\n\t\t\tconst data = entry.data as SpawnedUsageReport | undefined;\n\t\t\tif (!data?.usage) continue;\n\t\t\tif (data.reportId) {\n\t\t\t\tif (seenSpawnedReportIds.has(data.reportId)) continue;\n\t\t\t\tseenSpawnedReportIds.add(data.reportId);\n\t\t\t}\n\t\t\tadd(data.usage);\n\t\t}\n\t\treturn {\n\t\t\tinput,\n\t\t\toutput,\n\t\t\tcacheRead,\n\t\t\tcacheWrite,\n\t\t\ttotalTokens,\n\t\t\tcost: {\n\t\t\t\tinput: costInput,\n\t\t\t\toutput: costOutput,\n\t\t\t\tcacheRead: costCacheRead,\n\t\t\t\tcacheWrite: costCacheWrite,\n\t\t\t\ttotal: costTotal,\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Record usage spent by a spawned/subagent session so the footer can roll it into the\n\t * displayed cost. Persisted as a `CustomEntry` (`customType: \"spawned_usage\"`, Model A) so\n\t * it survives reload and is reconstructed exactly like main usage; a new/forked session\n\t * starts fresh because it owns a new log file.\n\t *\n\t * Idempotent on `opts.reportId`: a re-report (retry, duplicate `agent_end`) with a\n\t * previously-seen id is ignored, so cost cannot be double-counted. Honors the single-hop\n\t * invariant documented on {@link SpawnedUsageReport}.\n\t *\n\t * @returns the id of the appended entry, or `undefined` if the report was a duplicate.\n\t */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined {\n\t\tconst reportId = opts?.reportId;\n\t\tif (reportId && this.getCurrentSessionCostTotals().seenSubagentReportIds.has(reportId)) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst report: SpawnedUsageReport = {\n\t\t\tusage,\n\t\t\tlabel: opts?.label,\n\t\t\tsourceSessionId: opts?.sourceSessionId,\n\t\t\treportId,\n\t\t};\n\t\tconst entryId = this.deps.getSessionManager().appendCustomEntry(SPAWNED_USAGE_CUSTOM_TYPE, report);\n\t\tif (reportId) this.getCurrentSessionCostTotals();\n\t\treturn entryId;\n\t}\n\n\tprivate getCurrentSessionCostTotals(): CurrentSessionCostAccumulator {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tlet cache = this._currentSessionCostCache;\n\t\tconst getEntriesSince = sessionManager.getEntriesSince?.bind(sessionManager);\n\t\tif (!cache || entryCount < cache.entryCount || !getEntriesSince) {\n\t\t\tcache = {\n\t\t\t\tentryCount,\n\t\t\t\taccumulator: accumulateCurrentSessionCostsFromEntries(\n\t\t\t\t\tcreateCurrentSessionCostAccumulator(),\n\t\t\t\t\tsessionManager.getEntries(),\n\t\t\t\t),\n\t\t\t};\n\t\t\tthis._currentSessionCostCache = cache;\n\t\t} else if (entryCount > cache.entryCount) {\n\t\t\taccumulateCurrentSessionCostsFromEntries(cache.accumulator, getEntriesSince(cache.entryCount));\n\t\t\tcache.entryCount = entryCount;\n\t\t}\n\t\treturn cache.accumulator;\n\t}\n\n\t/**\n\t * Aggregate all recorded spawned-usage reports (see {@link addSpawnedUsage}). The append-ordered\n\t * accumulator processes only new entries, so repeated turns do not rescan the full session.\n\t */\n\tgetSpawnedUsage(): SpawnedUsageTotals {\n\t\tconst current = this.getCurrentSessionCostTotals();\n\t\treturn { cost: current.subagentCost, reports: current.subagentReports };\n\t}\n\n\tgetCostSummary(now = new Date()): SessionCostSummary {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst entryCount = sessionManager.getEntryCount?.() ?? sessionManager.getEntries().length;\n\t\tconst window = getLocalDayWindow(now);\n\t\tconst dailyTotals = this.getDailyUsageTotals(now);\n\t\tconst cached = this._costSummaryCache;\n\t\tif (\n\t\t\tcached?.entryCount === entryCount &&\n\t\t\tcached.dailyTotals === dailyTotals &&\n\t\t\tcached.windowStartMs === window.startMs &&\n\t\t\tcached.windowEndMs === window.endMs\n\t\t) {\n\t\t\treturn cached.summary;\n\t\t}\n\t\tconst summary = createSessionCostSummary({\n\t\t\tcurrentTotals: this.getCurrentSessionCostTotals(),\n\t\t\tdailyTotals,\n\t\t\ttodayWindow: window,\n\t\t});\n\t\tthis._costSummaryCache = {\n\t\t\tentryCount,\n\t\t\tdailyTotals,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\tsummary,\n\t\t};\n\t\treturn summary;\n\t}\n\n\tgetDailyUsageTotals(now = new Date()): DailyUsageTotals {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst sessionDir = sessionManager.getSessionDir();\n\t\tconst scope = sessionManager.usesDefaultSessionDir() ? getSessionsDir() : sessionDir;\n\t\tconst nowMs = now.getTime();\n\t\tconst window = getLocalDayWindow(now);\n\t\tif (\n\t\t\tthis._dailyUsageCache?.sessionDir === scope &&\n\t\t\tthis._dailyUsageCache.windowStartMs === window.startMs &&\n\t\t\tthis._dailyUsageCache.windowEndMs === window.endMs &&\n\t\t\tthis._dailyUsageCache.expiresAt > nowMs\n\t\t) {\n\t\t\treturn this._dailyUsageCache.totals;\n\t\t}\n\t\tconst sessionFile = sessionManager.getSessionFile();\n\t\tconst liveSession = sessionFile ? { filePath: sessionFile, entries: sessionManager.getEntries() } : undefined;\n\t\tconst totals = sessionManager.usesDefaultSessionDir()\n\t\t\t? aggregateDailyUsageFromSessionRoot(scope, window, liveSession)\n\t\t\t: aggregateDailyUsageFromSessionFiles(sessionDir, window, liveSession);\n\t\tthis._dailyUsageCache = {\n\t\t\tsessionDir: scope,\n\t\t\twindowStartMs: window.startMs,\n\t\t\twindowEndMs: window.endMs,\n\t\t\texpiresAt: Math.min(nowMs + 10_000, window.endMs),\n\t\t\ttotals,\n\t\t};\n\t\treturn totals;\n\t}\n\n\tgetDailyUsageBreakdown(formatLabel?: (label: string) => string, now = new Date()): string {\n\t\treturn formatDailyUsageBreakdown(this.getDailyUsageTotals(now), formatLabel);\n\t}\n\n\tprivate getPostCompactionUsageState(): { hasCompaction: boolean; hasPostCompactionUsage: boolean } {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst indexedSessionManager = sessionManager as unknown as {\n\t\t\tgetLeafId?: () => string | null;\n\t\t\tgetEntry?: (id: string) => SessionEntry | undefined;\n\t\t};\n\t\tconst getLeafId = indexedSessionManager.getLeafId?.bind(sessionManager);\n\t\tconst getEntry = indexedSessionManager.getEntry?.bind(sessionManager);\n\t\tconst leafId = getLeafId?.();\n\t\tif (getLeafId && getEntry && leafId !== undefined) {\n\t\t\tif (this._postCompactionUsageCache?.leafId === leafId) {\n\t\t\t\treturn { ...this._postCompactionUsageCache };\n\t\t\t}\n\t\t\tconst appended: SessionEntry[] = [];\n\t\t\tconst cachedLeafId = this._postCompactionUsageCache?.leafId;\n\t\t\tlet currentId = leafId;\n\t\t\tconst seen = new Set<string>();\n\t\t\twhile (currentId !== null && currentId !== cachedLeafId && !seen.has(currentId)) {\n\t\t\t\tseen.add(currentId);\n\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\tif (!entry) break;\n\t\t\t\tappended.push(entry);\n\t\t\t\tcurrentId = entry.parentId;\n\t\t\t}\n\t\t\tconst extendsCachedBranch = this._postCompactionUsageCache !== undefined && currentId === cachedLeafId;\n\t\t\tconst state = extendsCachedBranch\n\t\t\t\t? { ...this._postCompactionUsageCache! }\n\t\t\t\t: { leafId: null, hasCompaction: false, hasPostCompactionUsage: false };\n\t\t\tif (!extendsCachedBranch && currentId !== null) {\n\t\t\t\twhile (currentId !== null && !seen.has(currentId)) {\n\t\t\t\t\tseen.add(currentId);\n\t\t\t\t\tconst entry = getEntry(currentId);\n\t\t\t\t\tif (!entry) break;\n\t\t\t\t\tappended.push(entry);\n\t\t\t\t\tcurrentId = entry.parentId;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (let index = appended.length - 1; index >= 0; index--) {\n\t\t\t\tconst entry = appended[index];\n\t\t\t\tif (entry.type === \"compaction\") {\n\t\t\t\t\tstate.hasCompaction = true;\n\t\t\t\t\tstate.hasPostCompactionUsage = false;\n\t\t\t\t} else if (entry.type === \"message\" && entry.message.role === \"assistant\") {\n\t\t\t\t\tconst assistant = entry.message;\n\t\t\t\t\tif (assistant.stopReason !== \"aborted\" && assistant.stopReason !== \"error\" && state.hasCompaction) {\n\t\t\t\t\t\tstate.hasPostCompactionUsage = calculateContextTokens(assistant.usage) > 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstate.leafId = leafId;\n\t\t\tthis._postCompactionUsageCache = state;\n\t\t\treturn { ...state };\n\t\t}\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst latestCompaction = getLatestCompactionEntry(branchEntries);\n\t\tif (!latestCompaction) return { hasCompaction: false, hasPostCompactionUsage: false };\n\t\tconst compactionIndex = branchEntries.lastIndexOf(latestCompaction);\n\t\tfor (let index = branchEntries.length - 1; index > compactionIndex; index--) {\n\t\t\tconst entry = branchEntries[index];\n\t\t\tif (entry.type !== \"message\" || entry.message.role !== \"assistant\") continue;\n\t\t\tconst assistant = entry.message;\n\t\t\tif (assistant.stopReason === \"aborted\" || assistant.stopReason === \"error\") continue;\n\t\t\treturn { hasCompaction: true, hasPostCompactionUsage: calculateContextTokens(assistant.usage) > 0 };\n\t\t}\n\t\treturn { hasCompaction: true, hasPostCompactionUsage: false };\n\t}\n\n\tgetContextUsage(): ContextUsage | undefined {\n\t\tconst model = this.deps.getModel();\n\t\tif (!model) return undefined;\n\n\t\tconst contextWindow = model.contextWindow ?? 0;\n\t\tif (contextWindow <= 0) return undefined;\n\n\t\t// After compaction, the last assistant usage reflects pre-compaction context size.\n\t\t// Walk backward only to the nearest compaction instead of rebuilding the whole branch on\n\t\t// every footer invalidation; the distance is bounded by the live post-compaction context.\n\t\tconst compactionState = this.getPostCompactionUsageState();\n\t\tif (compactionState.hasCompaction && !compactionState.hasPostCompactionUsage) {\n\t\t\treturn { tokens: null, contextWindow, percent: null };\n\t\t}\n\n\t\tconst estimate = estimateContextTokens(this.deps.getMessages());\n\t\tconst percent = (estimate.tokens / contextWindow) * 100;\n\n\t\treturn {\n\t\t\ttokens: estimate.tokens,\n\t\t\tcontextWindow,\n\t\t\tpercent,\n\t\t};\n\t}\n\n\t/**\n\t * Export session to HTML.\n\t * @param outputPath Optional output path (defaults to session directory)\n\t * @returns Path to exported file\n\t */\n\tasync exportToHtml(outputPath?: string): Promise<string> {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst themeName = this.deps.getSettingsManager().getTheme();\n\n\t\t// Create tool renderer if we have an extension runner (for custom tool HTML rendering)\n\t\tconst toolRenderer: ToolHtmlRenderer = createToolHtmlRenderer({\n\t\t\tgetToolDefinition: (name) => this.deps.getToolDefinition(name),\n\t\t\ttheme,\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t});\n\n\t\treturn await exportSessionToHtml(sessionManager, this.deps.getState(), {\n\t\t\toutputPath,\n\t\t\tthemeName,\n\t\t\ttoolRenderer,\n\t\t});\n\t}\n\n\t/**\n\t * Export the current session branch to a JSONL file.\n\t * Writes the session header followed by all entries on the current branch path.\n\t * @param outputPath Target file path. If omitted, generates a timestamped file in cwd.\n\t * @returns The resolved output file path.\n\t */\n\texportToJsonl(outputPath?: string): string {\n\t\tconst sessionManager = this.deps.getSessionManager();\n\t\tconst filePath = resolvePath(\n\t\t\toutputPath ?? `session-${new Date().toISOString().replace(/[:.]/g, \"-\")}.jsonl`,\n\t\t\tprocess.cwd(),\n\t\t);\n\t\tconst dir = dirname(filePath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t}\n\n\t\tconst header: SessionHeader = {\n\t\t\ttype: \"session\",\n\t\t\tversion: CURRENT_SESSION_VERSION,\n\t\t\tid: sessionManager.getSessionId(),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t\tcwd: sessionManager.getCwd(),\n\t\t};\n\n\t\tconst branchEntries = sessionManager.getBranch();\n\t\tconst lines = [JSON.stringify(header)];\n\n\t\t// Re-chain parentIds to form a linear sequence\n\t\tlet prevId: string | null = null;\n\t\tfor (const entry of branchEntries) {\n\t\t\tconst linear = { ...entry, parentId: prevId };\n\t\t\tlines.push(JSON.stringify(linear));\n\t\t\tprevId = entry.id;\n\t\t}\n\n\t\twriteFileSync(filePath, `${lines.join(\"\\n\")}\\n`);\n\t\treturn filePath;\n\t}\n\n\t/**\n\t * Get text content of last assistant message.\n\t * Useful for /copy command.\n\t * @returns Text content, or undefined if no assistant message exists\n\t */\n\tgetLastAssistantText(): string | undefined {\n\t\tconst lastAssistant = this.deps\n\t\t\t.getMessages()\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.find((m) => {\n\t\t\t\tif (m.role !== \"assistant\") return false;\n\t\t\t\tconst msg = m as AssistantMessage;\n\t\t\t\t// Skip aborted messages with no content\n\t\t\t\tif (msg.stopReason === \"aborted\" && msg.content.length === 0) return false;\n\t\t\t\treturn true;\n\t\t\t});\n\n\t\tif (!lastAssistant) return undefined;\n\n\t\tlet text = \"\";\n\t\tfor (const content of (lastAssistant as AssistantMessage).content) {\n\t\t\tif (content.type === \"text\") {\n\t\t\t\ttext += content.text;\n\t\t\t}\n\t\t}\n\n\t\treturn text.trim() || undefined;\n\t}\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import type { ImageContent } from "@caupulican/pi-ai";
2
+ export interface StoredSessionImage {
3
+ readonly sequence: number;
4
+ readonly path: string;
5
+ readonly mimeType: string;
6
+ readonly bytes: Uint8Array;
7
+ }
8
+ export interface SessionImageStoreOptions {
9
+ agentDir: string;
10
+ cwd: string;
11
+ sessionId: string;
12
+ directory?: string;
13
+ now?: () => number;
14
+ }
15
+ export declare function resolveSessionImageDirectory(options: SessionImageStoreOptions): string;
16
+ export declare class SessionImageStore {
17
+ readonly directory: string;
18
+ private readonly sessionKey;
19
+ private readonly now;
20
+ constructor(options: SessionImageStoreOptions);
21
+ /** Validate and durably retain provider-neutral image content, reusing its claimed sequence only
22
+ * when that sequence already contains the exact same bytes and normalized MIME type. */
23
+ retainContent(content: ImageContent, preferredSequence?: number): StoredSessionImage;
24
+ write(bytes: Uint8Array, mimeType: string): StoredSessionImage;
25
+ read(sequence: number): StoredSessionImage | undefined;
26
+ readLatest(): StoredSessionImage | undefined;
27
+ resolveReferences(text: string): ImageContent[];
28
+ private referencesLatestImage;
29
+ private readEntry;
30
+ private scan;
31
+ private prune;
32
+ }
33
+ //# sourceMappingURL=session-image-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-image-store.d.ts","sourceRoot":"","sources":["../../src/core/session-image-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAYtD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAiDD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAItF;AAED,qBAAa,iBAAiB;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC,YAAY,OAAO,EAAE,wBAAwB,EAI5C;IAED;4FACwF;IACxF,aAAa,CAAC,OAAO,EAAE,YAAY,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAoBnF;IAED,KAAK,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAiD7D;IAED,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAQrD;IAED,UAAU,IAAI,kBAAkB,GAAG,SAAS,CAW3C;IAED,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,EAAE,CAwB9C;IAED,OAAO,CAAC,qBAAqB;IAW7B,OAAO,CAAC,SAAS;IAmBjB,OAAO,CAAC,IAAI;IAuCZ,OAAO,CAAC,KAAK;CAkCb","sourcesContent":["import { createHash } from \"node:crypto\";\nimport { existsSync, lstatSync, mkdirSync, opendirSync, readFileSync, unlinkSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, resolve } from \"node:path\";\nimport type { ImageContent } from \"@caupulican/pi-ai\";\nimport { attachmentsDir } from \"./agent-paths.ts\";\n\nconst FILE_PREFIX = \"pi-clip\";\nconst MAX_SCANNED_ENTRIES = 10_000;\nconst MAX_STORED_FILES = 512;\nconst MAX_TOTAL_BYTES = 512 * 1024 * 1024;\nconst MAX_IMAGE_BYTES = 50 * 1024 * 1024;\nconst MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;\nconst MAX_SEQUENCE = 999_999;\nconst FILE_PATTERN = /^pi-clip-([0-9a-f]{16})-(\\d{6})-([0-9a-f]{12})\\.(png|jpg|webp|gif)$/;\n\nexport interface StoredSessionImage {\n\treadonly sequence: number;\n\treadonly path: string;\n\treadonly mimeType: string;\n\treadonly bytes: Uint8Array;\n}\n\nexport interface SessionImageStoreOptions {\n\tagentDir: string;\n\tcwd: string;\n\tsessionId: string;\n\tdirectory?: string;\n\tnow?: () => number;\n}\n\ninterface StoredImageEntry {\n\tpath: string;\n\tsessionKey: string;\n\tsequence: number;\n\tdigest: string;\n\tmimeType: string;\n\tbytes: number;\n\tmtimeMs: number;\n}\n\ninterface StoreScan {\n\tentries: StoredImageEntry[];\n\tcomplete: boolean;\n}\n\nfunction extensionForMimeType(mimeType: string): \"png\" | \"jpg\" | \"webp\" | \"gif\" {\n\tswitch (mimeType.split(\";\", 1)[0]?.trim().toLowerCase()) {\n\t\tcase \"image/jpeg\":\n\t\t\treturn \"jpg\";\n\t\tcase \"image/webp\":\n\t\t\treturn \"webp\";\n\t\tcase \"image/gif\":\n\t\t\treturn \"gif\";\n\t\tdefault:\n\t\t\treturn \"png\";\n\t}\n}\n\nfunction mimeTypeForExtension(extension: string): string {\n\tswitch (extension) {\n\t\tcase \"jpg\":\n\t\t\treturn \"image/jpeg\";\n\t\tcase \"webp\":\n\t\t\treturn \"image/webp\";\n\t\tcase \"gif\":\n\t\t\treturn \"image/gif\";\n\t\tdefault:\n\t\t\treturn \"image/png\";\n\t}\n}\n\nfunction resolveConfiguredDirectory(directory: string, cwd: string): string {\n\tconst trimmed = directory.trim();\n\tconst expanded = trimmed === \"~\" ? homedir() : trimmed.replace(/^~(?=[/\\\\])/, homedir());\n\treturn resolve(isAbsolute(expanded) ? expanded : resolve(cwd, expanded));\n}\n\nexport function resolveSessionImageDirectory(options: SessionImageStoreOptions): string {\n\treturn options.directory?.trim()\n\t\t? resolveConfiguredDirectory(options.directory, options.cwd)\n\t\t: attachmentsDir(options.agentDir);\n}\n\nexport class SessionImageStore {\n\treadonly directory: string;\n\tprivate readonly sessionKey: string;\n\tprivate readonly now: () => number;\n\n\tconstructor(options: SessionImageStoreOptions) {\n\t\tthis.directory = resolveSessionImageDirectory(options);\n\t\tthis.sessionKey = createHash(\"sha256\").update(options.sessionId).digest(\"hex\").slice(0, 16);\n\t\tthis.now = options.now ?? Date.now;\n\t}\n\n\t/** Validate and durably retain provider-neutral image content, reusing its claimed sequence only\n\t * when that sequence already contains the exact same bytes and normalized MIME type. */\n\tretainContent(content: ImageContent, preferredSequence?: number): StoredSessionImage {\n\t\tconst compactBase64 = content.data.trim();\n\t\tif (!/^image\\/(?:png|jpeg|webp|gif)(?:;|$)/iu.test(content.mimeType.trim())) {\n\t\t\tthrow new Error(`Unsupported image MIME type: ${content.mimeType}`);\n\t\t}\n\t\tif (!/^[A-Za-z0-9+/]*={0,2}$/.test(compactBase64) || compactBase64.length % 4 === 1) {\n\t\t\tthrow new Error(\"Image content is not valid base64\");\n\t\t}\n\t\tconst bytes = Buffer.from(compactBase64, \"base64\");\n\t\tif (bytes.toString(\"base64\").replace(/=+$/u, \"\") !== compactBase64.replace(/=+$/u, \"\")) {\n\t\t\tthrow new Error(\"Image content is not canonical base64\");\n\t\t}\n\t\tconst normalizedMimeType = mimeTypeForExtension(extensionForMimeType(content.mimeType));\n\t\tif (preferredSequence !== undefined) {\n\t\t\tconst existing = this.read(preferredSequence);\n\t\t\tif (existing && existing.mimeType === normalizedMimeType && Buffer.from(existing.bytes).equals(bytes)) {\n\t\t\t\treturn existing;\n\t\t\t}\n\t\t}\n\t\treturn this.write(bytes, normalizedMimeType);\n\t}\n\n\twrite(bytes: Uint8Array, mimeType: string): StoredSessionImage {\n\t\tif (bytes.byteLength === 0 || bytes.byteLength > MAX_IMAGE_BYTES) {\n\t\t\tthrow new Error(`Clipboard image must be between 1 byte and ${MAX_IMAGE_BYTES} bytes`);\n\t\t}\n\t\tmkdirSync(this.directory, { recursive: true });\n\t\tconst directoryStats = lstatSync(this.directory);\n\t\tif (!directoryStats.isDirectory()) {\n\t\t\tthrow new Error(`Attachment path is not a directory: ${this.directory}`);\n\t\t}\n\n\t\tconst before = this.scan();\n\t\tif (!before.complete) {\n\t\t\tthrow new Error(`Attachment directory exceeds the ${MAX_SCANNED_ENTRIES}-entry inspection bound`);\n\t\t}\n\t\tlet sequence = Math.max(\n\t\t\t0,\n\t\t\t...before.entries.filter((entry) => entry.sessionKey === this.sessionKey).map((entry) => entry.sequence),\n\t\t);\n\t\tconst digest = createHash(\"sha256\").update(bytes).digest(\"hex\").slice(0, 12);\n\t\tconst extension = extensionForMimeType(mimeType);\n\t\tlet path = \"\";\n\t\tlet written = false;\n\t\twhile (sequence < MAX_SEQUENCE) {\n\t\t\tsequence++;\n\t\t\tpath = resolve(\n\t\t\t\tthis.directory,\n\t\t\t\t`${FILE_PREFIX}-${this.sessionKey}-${String(sequence).padStart(6, \"0\")}-${digest}.${extension}`,\n\t\t\t);\n\t\t\ttry {\n\t\t\t\twriteFileSync(path, bytes, { flag: \"wx\" });\n\t\t\t\twritten = true;\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\tif ((error as NodeJS.ErrnoException).code !== \"EEXIST\") throw error;\n\t\t\t}\n\t\t}\n\t\tif (!written) {\n\t\t\tthrow new Error(\"Clipboard image sequence is exhausted\");\n\t\t}\n\n\t\ttry {\n\t\t\tthis.prune(path);\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tunlinkSync(path);\n\t\t\t} catch {}\n\t\t\tthrow error;\n\t\t}\n\t\treturn { sequence, path, mimeType: mimeTypeForExtension(extension), bytes: new Uint8Array(bytes) };\n\t}\n\n\tread(sequence: number): StoredSessionImage | undefined {\n\t\tif (!Number.isSafeInteger(sequence) || sequence < 1 || sequence > MAX_SEQUENCE) return undefined;\n\t\tconst scan = this.scan();\n\t\tif (!scan.complete) return undefined;\n\t\tconst entry = scan.entries.find(\n\t\t\t(candidate) => candidate.sessionKey === this.sessionKey && candidate.sequence === sequence,\n\t\t);\n\t\treturn entry ? this.readEntry(entry) : undefined;\n\t}\n\n\treadLatest(): StoredSessionImage | undefined {\n\t\tconst scan = this.scan();\n\t\tif (!scan.complete) return undefined;\n\t\tconst entries = scan.entries\n\t\t\t.filter((entry) => entry.sessionKey === this.sessionKey)\n\t\t\t.sort((left, right) => right.sequence - left.sequence);\n\t\tfor (const entry of entries) {\n\t\t\tconst image = this.readEntry(entry);\n\t\t\tif (image) return image;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tresolveReferences(text: string): ImageContent[] {\n\t\ttry {\n\t\t\tconst explicitSequences: number[] = [];\n\t\t\tconst explicitPattern = /(?:\\[\\s*)?(?:image|screenshot|picture|photo)\\s*#?\\s*(\\d{1,6})(?:\\s*\\])?/gi;\n\t\t\tfor (const match of text.matchAll(explicitPattern)) {\n\t\t\t\tconst sequence = Number(match[1]);\n\t\t\t\tif (!explicitSequences.includes(sequence)) explicitSequences.push(sequence);\n\t\t\t}\n\n\t\t\tconst resolved = explicitSequences\n\t\t\t\t.map((sequence) => this.read(sequence))\n\t\t\t\t.filter((image): image is StoredSessionImage => image !== undefined);\n\t\t\tif (resolved.length === 0 && this.referencesLatestImage(text)) {\n\t\t\t\tconst latest = this.readLatest();\n\t\t\t\tif (latest) resolved.push(latest);\n\t\t\t}\n\t\t\treturn resolved.map((image) => ({\n\t\t\t\ttype: \"image\",\n\t\t\t\tdata: Buffer.from(image.bytes).toString(\"base64\"),\n\t\t\t\tmimeType: image.mimeType,\n\t\t\t}));\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate referencesLatestImage(text: string): boolean {\n\t\treturn (\n\t\t\t/\\b(?:the|this|that|latest|last|pasted|attached|clipboard)\\s+(?:image|screenshot|picture|photo)\\b/i.test(\n\t\t\t\ttext,\n\t\t\t) ||\n\t\t\t/\\b(?:look at|inspect|review|describe|analy[sz]e|check)\\s+(?:(?:the|this|that|latest|last|pasted|attached|clipboard)\\s+)?(?:image|screenshot|picture|photo)\\b/i.test(\n\t\t\t\ttext,\n\t\t\t)\n\t\t);\n\t}\n\n\tprivate readEntry(entry: StoredImageEntry): StoredSessionImage | undefined {\n\t\ttry {\n\t\t\tconst stats = lstatSync(entry.path);\n\t\t\tif (!stats.isFile() || stats.isSymbolicLink() || stats.size < 1 || stats.size > MAX_IMAGE_BYTES)\n\t\t\t\treturn undefined;\n\t\t\tconst bytes = readFileSync(entry.path);\n\t\t\tconst digest = createHash(\"sha256\").update(bytes).digest(\"hex\").slice(0, 12);\n\t\t\tif (digest !== entry.digest) return undefined;\n\t\t\treturn {\n\t\t\t\tsequence: entry.sequence,\n\t\t\t\tpath: entry.path,\n\t\t\t\tmimeType: entry.mimeType,\n\t\t\t\tbytes: new Uint8Array(bytes),\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tprivate scan(): StoreScan {\n\t\tif (!existsSync(this.directory)) return { entries: [], complete: true };\n\t\tconst entries: StoredImageEntry[] = [];\n\t\tlet scanned = 0;\n\t\tlet complete = true;\n\t\tconst handle = opendirSync(this.directory);\n\t\ttry {\n\t\t\twhile (true) {\n\t\t\t\tconst directoryEntry = handle.readSync();\n\t\t\t\tif (!directoryEntry) break;\n\t\t\t\tif (scanned >= MAX_SCANNED_ENTRIES) {\n\t\t\t\t\tcomplete = false;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tscanned++;\n\t\t\t\tif (!directoryEntry.isFile() || directoryEntry.isSymbolicLink()) continue;\n\t\t\t\tconst match = FILE_PATTERN.exec(directoryEntry.name);\n\t\t\t\tif (!match) continue;\n\t\t\t\tconst path = resolve(this.directory, directoryEntry.name);\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = lstatSync(path);\n\t\t\t\t\tif (!stats.isFile() || stats.isSymbolicLink()) continue;\n\t\t\t\t\tentries.push({\n\t\t\t\t\t\tpath,\n\t\t\t\t\t\tsessionKey: match[1]!,\n\t\t\t\t\t\tsequence: Number(match[2]),\n\t\t\t\t\t\tdigest: match[3]!,\n\t\t\t\t\t\tmimeType: mimeTypeForExtension(match[4]!),\n\t\t\t\t\t\tbytes: stats.size,\n\t\t\t\t\t\tmtimeMs: stats.mtimeMs,\n\t\t\t\t\t});\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t} finally {\n\t\t\thandle.closeSync();\n\t\t}\n\t\treturn { entries, complete };\n\t}\n\n\tprivate prune(protectedPath: string): void {\n\t\tconst scan = this.scan();\n\t\tif (!scan.complete) {\n\t\t\tthrow new Error(`Attachment directory exceeds the ${MAX_SCANNED_ENTRIES}-entry inspection bound`);\n\t\t}\n\t\tconst now = this.now();\n\t\tfor (const entry of scan.entries) {\n\t\t\tif (entry.path !== protectedPath && now - entry.mtimeMs > MAX_AGE_MS) {\n\t\t\t\ttry {\n\t\t\t\t\tunlinkSync(entry.path);\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\n\t\tconst retained = this.scan();\n\t\tif (!retained.complete) {\n\t\t\tthrow new Error(`Attachment directory exceeds the ${MAX_SCANNED_ENTRIES}-entry inspection bound`);\n\t\t}\n\t\tconst oldestFirst = retained.entries.sort((left, right) => left.mtimeMs - right.mtimeMs);\n\t\tlet totalBytes = oldestFirst.reduce((total, entry) => total + entry.bytes, 0);\n\t\tlet totalFiles = oldestFirst.length;\n\t\tfor (const entry of oldestFirst) {\n\t\t\tif (totalFiles <= MAX_STORED_FILES && totalBytes <= MAX_TOTAL_BYTES) break;\n\t\t\tif (entry.path === protectedPath) continue;\n\t\t\ttry {\n\t\t\t\tunlinkSync(entry.path);\n\t\t\t\ttotalFiles--;\n\t\t\t\ttotalBytes -= entry.bytes;\n\t\t\t} catch {}\n\t\t}\n\t\tif (totalFiles > MAX_STORED_FILES || totalBytes > MAX_TOTAL_BYTES) {\n\t\t\tthrow new Error(\"Attachment retention bounds could not be enforced\");\n\t\t}\n\t}\n}\n"]}