@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":"tool-recovery-log-records.d.ts","sourceRoot":"","sources":["../../src/core/tool-recovery-log-records.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,mBAAmB,CAAC;AAS9E,eAAO,MAAM,4BAA4B,+BAA+B,CAAC;AACzE,eAAO,MAAM,iCAAiC,6BAA6B,CAAC;AAQ5E,MAAM,WAAW,+BAAgC,SAAQ,oCAAoC;IAC5F,IAAI,EAAE,OAAO,iCAAiC,CAAC;IAC/C,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,+BAA+B,CAAC;CACxC;AAyBD,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC3D,KAAK,EAAE,oCAAoC,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACX,GAAG,+BAA+B,CAmBlC;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,+BAA+B,CAc1G;AAsBD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,2BAA2B,GAAG,IAAI,CAuBnF","sourcesContent":["import { appendFileSync, existsSync, readFileSync, statSync } from \"node:fs\";\nimport type { ToolArgumentValidationTelemetryEvent } from \"@caupulican/pi-ai\";\nimport {\n\tcreateToolValidationFailureCorpusRecord,\n\ttype ToolValidationFailureShapeEntry,\n\twriteFailureCorpusRecord,\n} from \"./failure-corpus.ts\";\nimport { updatePersistedToolRecoveryStats } from \"./tool-recovery-stats.ts\";\nimport { withFileLockSync, writeFileAtomicSync } from \"./util/atomic-file.ts\";\n\nexport const TOOL_RECOVERY_EVENT_LOG_FILE = \"tool-recovery-events.jsonl\";\nexport const TOOL_ARGUMENT_VALIDATION_LOG_KIND = \"tool_argument_validation\";\n\nconst MAX_EVENT_LOG_BYTES = 4 * 1024 * 1024;\nconst ROTATED_EVENT_LOG_TARGET_BYTES = Math.floor(MAX_EVENT_LOG_BYTES * 0.75);\nconst MAX_ROTATED_EVENT_LOG_RECORDS = 5_000;\nconst MAX_TELEMETRY_LIST_ITEMS = 50;\nconst MAX_TELEMETRY_TEXT_CHARS = 256;\n\nexport interface ToolArgumentValidationLogRecord extends ToolArgumentValidationTelemetryEvent {\n\tkind: typeof TOOL_ARGUMENT_VALIDATION_LOG_KIND;\n\tversion: 1;\n\trecordId: string;\n\tts: string;\n\tsessionId: string;\n}\n\nexport interface ToolRecoveryLogWorkerRecord {\n\teventLogPath: string;\n\tfailureCorpusPath: string;\n\trecord: ToolArgumentValidationLogRecord;\n}\n\nfunction boundedText(value: string): string {\n\treturn value.slice(0, MAX_TELEMETRY_TEXT_CHARS);\n}\n\nfunction boundedList<T extends string>(values: readonly T[] | undefined): T[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS);\n}\n\nfunction boundedTextList(values: readonly string[] | undefined): string[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS).map(boundedText);\n}\n\nfunction copyFailureShape(\n\tshape: readonly ToolValidationFailureShapeEntry[] | undefined,\n): ToolValidationFailureShapeEntry[] {\n\treturn (shape ?? []).slice(0, MAX_TELEMETRY_LIST_ITEMS).map((entry) => ({\n\t\tpath: boundedText(entry.path),\n\t\texpectedType: boundedText(entry.expectedType),\n\t\treceivedType: boundedText(entry.receivedType),\n\t\t...(entry.keyword ? { keyword: boundedText(entry.keyword) } : {}),\n\t}));\n}\n\nexport function createToolArgumentValidationLogRecord(args: {\n\tevent: ToolArgumentValidationTelemetryEvent;\n\trecordId: string;\n\tsessionId: string;\n\tts: string;\n}): ToolArgumentValidationLogRecord {\n\treturn {\n\t\tkind: TOOL_ARGUMENT_VALIDATION_LOG_KIND,\n\t\tversion: 1,\n\t\trecordId: args.recordId,\n\t\tts: args.ts,\n\t\tsessionId: args.sessionId,\n\t\toutcome: args.event.outcome,\n\t\tprovider: args.event.provider,\n\t\tmodel: args.event.model,\n\t\ttool: args.event.tool,\n\t\tsource: args.event.source,\n\t\tfailureModes: boundedList(args.event.failureModes) ?? [],\n\t\trepairsApplied: boundedList(args.event.repairsApplied) ?? [],\n\t\tfailureShape: copyFailureShape(args.event.failureShape),\n\t\terrorKeywords: boundedTextList(args.event.errorKeywords),\n\t\ttaught: args.event.taught,\n\t\texecutionOutcome: args.event.executionOutcome,\n\t};\n}\n\nexport function isToolArgumentValidationLogRecord(value: unknown): value is ToolArgumentValidationLogRecord {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Partial<ToolArgumentValidationLogRecord>;\n\treturn (\n\t\trecord.kind === TOOL_ARGUMENT_VALIDATION_LOG_KIND &&\n\t\trecord.version === 1 &&\n\t\ttypeof record.recordId === \"string\" &&\n\t\ttypeof record.ts === \"string\" &&\n\t\ttypeof record.sessionId === \"string\" &&\n\t\t(record.outcome === \"repaired\" || record.outcome === \"bounced\") &&\n\t\ttypeof record.tool === \"string\" &&\n\t\tArray.isArray(record.failureModes) &&\n\t\tArray.isArray(record.repairsApplied)\n\t);\n}\n\nfunction rotateToolRecoveryEventLogIfNeeded(filePath: string): void {\n\tif (!existsSync(filePath) || statSync(filePath).size <= MAX_EVENT_LOG_BYTES) return;\n\tconst lines = readFileSync(filePath, \"utf-8\")\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.trim().length > 0);\n\tconst retained: string[] = [];\n\tlet retainedBytes = 0;\n\tfor (let index = lines.length - 1; index >= 0 && retained.length < MAX_ROTATED_EVENT_LOG_RECORDS; index--) {\n\t\tconst line = lines[index];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + 1;\n\t\tif (lineBytes > MAX_EVENT_LOG_BYTES) continue;\n\t\tif (retained.length > 0 && retainedBytes + lineBytes > ROTATED_EVENT_LOG_TARGET_BYTES) break;\n\t\tretained.push(line);\n\t\tretainedBytes += lineBytes;\n\t\tif (retainedBytes >= ROTATED_EVENT_LOG_TARGET_BYTES) break;\n\t}\n\tretained.reverse();\n\twriteFileAtomicSync(filePath, retained.length > 0 ? `${retained.join(\"\\n\")}\\n` : \"\");\n}\n\n/**\n * Append + rotate under a single exclusive lock on `eventLogPath`. Without the lock, a concurrent\n * writer (e.g. another session's tool-recovery worker) could append between this call's read and its\n * rotated rewrite, and that append would be silently discarded when the rewrite lands; the atomic\n * tmp+rename in {@link rotateToolRecoveryEventLogIfNeeded} additionally ensures a reader never observes\n * a partially-rewritten (torn) log file.\n */\nexport function writeToolRecoveryLogRecord(entry: ToolRecoveryLogWorkerRecord): void {\n\twithFileLockSync(entry.eventLogPath, () => {\n\t\tappendFileSync(entry.eventLogPath, `${JSON.stringify(entry.record)}\\n`, \"utf-8\");\n\t\trotateToolRecoveryEventLogIfNeeded(entry.eventLogPath);\n\t});\n\ttry {\n\t\tupdatePersistedToolRecoveryStats(entry.eventLogPath, entry.record);\n\t} catch {\n\t\t// The bounded event log remains the recovery source if the cumulative summary cannot be updated.\n\t}\n\tif (entry.record.outcome !== \"bounced\") return;\n\twriteFailureCorpusRecord(\n\t\tentry.failureCorpusPath,\n\t\tcreateToolValidationFailureCorpusRecord({\n\t\t\tts: entry.record.ts,\n\t\t\tprovider: entry.record.provider,\n\t\t\tmodelId: entry.record.model,\n\t\t\ttool: entry.record.tool,\n\t\t\tfailureModes: entry.record.failureModes,\n\t\t\tshape: copyFailureShape(entry.record.failureShape),\n\t\t\terrorKeywords: entry.record.errorKeywords,\n\t\t}),\n\t);\n}\n"]}
1
+ {"version":3,"file":"tool-recovery-log-records.d.ts","sourceRoot":"","sources":["../../src/core/tool-recovery-log-records.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oCAAoC,EAAE,MAAM,mBAAmB,CAAC;AAS9E,eAAO,MAAM,4BAA4B,+BAA+B,CAAC;AACzE,eAAO,MAAM,iCAAiC,6BAA6B,CAAC;AAU5E,MAAM,WAAW,+BAAgC,SAAQ,oCAAoC;IAC5F,IAAI,EAAE,OAAO,iCAAiC,CAAC;IAC/C,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,2BAA2B;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,+BAA+B,CAAC;CACxC;AAyBD,wBAAgB,qCAAqC,CAAC,IAAI,EAAE;IAC3D,KAAK,EAAE,oCAAoC,CAAC;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACX,GAAG,+BAA+B,CAmBlC;AAED,wBAAgB,iCAAiC,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,+BAA+B,CAc1G;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,2BAA2B,GAAG,IAAI,CAoBnF","sourcesContent":["import type { ToolArgumentValidationTelemetryEvent } from \"@caupulican/pi-ai\";\nimport {\n\tcreateToolValidationFailureCorpusRecord,\n\ttype ToolValidationFailureShapeEntry,\n\twriteFailureCorpusRecord,\n} from \"./failure-corpus.ts\";\nimport { updatePersistedToolRecoveryStats } from \"./tool-recovery-stats.ts\";\nimport { appendBoundedJsonLineSync, type BoundedJsonlLimits } from \"./util/bounded-jsonl.ts\";\n\nexport const TOOL_RECOVERY_EVENT_LOG_FILE = \"tool-recovery-events.jsonl\";\nexport const TOOL_ARGUMENT_VALIDATION_LOG_KIND = \"tool_argument_validation\";\n\nconst TOOL_RECOVERY_EVENT_LIMITS: BoundedJsonlLimits = {\n\tmaxBytes: 4 * 1024 * 1024,\n\ttargetBytes: Math.floor(4 * 1024 * 1024 * 0.75),\n\tmaxRecords: 5_000,\n};\nconst MAX_TELEMETRY_LIST_ITEMS = 50;\nconst MAX_TELEMETRY_TEXT_CHARS = 256;\n\nexport interface ToolArgumentValidationLogRecord extends ToolArgumentValidationTelemetryEvent {\n\tkind: typeof TOOL_ARGUMENT_VALIDATION_LOG_KIND;\n\tversion: 1;\n\trecordId: string;\n\tts: string;\n\tsessionId: string;\n}\n\nexport interface ToolRecoveryLogWorkerRecord {\n\teventLogPath: string;\n\tfailureCorpusPath: string;\n\trecord: ToolArgumentValidationLogRecord;\n}\n\nfunction boundedText(value: string): string {\n\treturn value.slice(0, MAX_TELEMETRY_TEXT_CHARS);\n}\n\nfunction boundedList<T extends string>(values: readonly T[] | undefined): T[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS);\n}\n\nfunction boundedTextList(values: readonly string[] | undefined): string[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS).map(boundedText);\n}\n\nfunction copyFailureShape(\n\tshape: readonly ToolValidationFailureShapeEntry[] | undefined,\n): ToolValidationFailureShapeEntry[] {\n\treturn (shape ?? []).slice(0, MAX_TELEMETRY_LIST_ITEMS).map((entry) => ({\n\t\tpath: boundedText(entry.path),\n\t\texpectedType: boundedText(entry.expectedType),\n\t\treceivedType: boundedText(entry.receivedType),\n\t\t...(entry.keyword ? { keyword: boundedText(entry.keyword) } : {}),\n\t}));\n}\n\nexport function createToolArgumentValidationLogRecord(args: {\n\tevent: ToolArgumentValidationTelemetryEvent;\n\trecordId: string;\n\tsessionId: string;\n\tts: string;\n}): ToolArgumentValidationLogRecord {\n\treturn {\n\t\tkind: TOOL_ARGUMENT_VALIDATION_LOG_KIND,\n\t\tversion: 1,\n\t\trecordId: args.recordId,\n\t\tts: args.ts,\n\t\tsessionId: args.sessionId,\n\t\toutcome: args.event.outcome,\n\t\tprovider: args.event.provider,\n\t\tmodel: args.event.model,\n\t\ttool: args.event.tool,\n\t\tsource: args.event.source,\n\t\tfailureModes: boundedList(args.event.failureModes) ?? [],\n\t\trepairsApplied: boundedList(args.event.repairsApplied) ?? [],\n\t\tfailureShape: copyFailureShape(args.event.failureShape),\n\t\terrorKeywords: boundedTextList(args.event.errorKeywords),\n\t\ttaught: args.event.taught,\n\t\texecutionOutcome: args.event.executionOutcome,\n\t};\n}\n\nexport function isToolArgumentValidationLogRecord(value: unknown): value is ToolArgumentValidationLogRecord {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Partial<ToolArgumentValidationLogRecord>;\n\treturn (\n\t\trecord.kind === TOOL_ARGUMENT_VALIDATION_LOG_KIND &&\n\t\trecord.version === 1 &&\n\t\ttypeof record.recordId === \"string\" &&\n\t\ttypeof record.ts === \"string\" &&\n\t\ttypeof record.sessionId === \"string\" &&\n\t\t(record.outcome === \"repaired\" || record.outcome === \"bounced\") &&\n\t\ttypeof record.tool === \"string\" &&\n\t\tArray.isArray(record.failureModes) &&\n\t\tArray.isArray(record.repairsApplied)\n\t);\n}\n\n/**\n * Append + rotate under a single exclusive lock on `eventLogPath`. Without the lock, a concurrent\n * writer (e.g. another session's tool-recovery worker) could append between this call's read and its\n * rotated rewrite, and that append would be silently discarded when the rewrite lands. The shared\n * bounded JSONL sink also uses atomic tmp+rename rotation so readers never observe torn log files.\n */\nexport function writeToolRecoveryLogRecord(entry: ToolRecoveryLogWorkerRecord): void {\n\tappendBoundedJsonLineSync(entry.eventLogPath, entry.record, TOOL_RECOVERY_EVENT_LIMITS);\n\ttry {\n\t\tupdatePersistedToolRecoveryStats(entry.eventLogPath, entry.record);\n\t} catch {\n\t\t// The bounded event log remains the recovery source if the cumulative summary cannot be updated.\n\t}\n\tif (entry.record.outcome !== \"bounced\") return;\n\twriteFailureCorpusRecord(\n\t\tentry.failureCorpusPath,\n\t\tcreateToolValidationFailureCorpusRecord({\n\t\t\tts: entry.record.ts,\n\t\t\tprovider: entry.record.provider,\n\t\t\tmodelId: entry.record.model,\n\t\t\ttool: entry.record.tool,\n\t\t\tfailureModes: entry.record.failureModes,\n\t\t\tshape: copyFailureShape(entry.record.failureShape),\n\t\t\terrorKeywords: entry.record.errorKeywords,\n\t\t}),\n\t);\n}\n"]}
@@ -1,12 +1,13 @@
1
- import { appendFileSync, existsSync, readFileSync, statSync } from "node:fs";
2
1
  import { createToolValidationFailureCorpusRecord, writeFailureCorpusRecord, } from "./failure-corpus.js";
3
2
  import { updatePersistedToolRecoveryStats } from "./tool-recovery-stats.js";
4
- import { withFileLockSync, writeFileAtomicSync } from "./util/atomic-file.js";
3
+ import { appendBoundedJsonLineSync } from "./util/bounded-jsonl.js";
5
4
  export const TOOL_RECOVERY_EVENT_LOG_FILE = "tool-recovery-events.jsonl";
6
5
  export const TOOL_ARGUMENT_VALIDATION_LOG_KIND = "tool_argument_validation";
7
- const MAX_EVENT_LOG_BYTES = 4 * 1024 * 1024;
8
- const ROTATED_EVENT_LOG_TARGET_BYTES = Math.floor(MAX_EVENT_LOG_BYTES * 0.75);
9
- const MAX_ROTATED_EVENT_LOG_RECORDS = 5_000;
6
+ const TOOL_RECOVERY_EVENT_LIMITS = {
7
+ maxBytes: 4 * 1024 * 1024,
8
+ targetBytes: Math.floor(4 * 1024 * 1024 * 0.75),
9
+ maxRecords: 5_000,
10
+ };
10
11
  const MAX_TELEMETRY_LIST_ITEMS = 50;
11
12
  const MAX_TELEMETRY_TEXT_CHARS = 256;
12
13
  function boundedText(value) {
@@ -60,41 +61,14 @@ export function isToolArgumentValidationLogRecord(value) {
60
61
  Array.isArray(record.failureModes) &&
61
62
  Array.isArray(record.repairsApplied));
62
63
  }
63
- function rotateToolRecoveryEventLogIfNeeded(filePath) {
64
- if (!existsSync(filePath) || statSync(filePath).size <= MAX_EVENT_LOG_BYTES)
65
- return;
66
- const lines = readFileSync(filePath, "utf-8")
67
- .split("\n")
68
- .filter((line) => line.trim().length > 0);
69
- const retained = [];
70
- let retainedBytes = 0;
71
- for (let index = lines.length - 1; index >= 0 && retained.length < MAX_ROTATED_EVENT_LOG_RECORDS; index--) {
72
- const line = lines[index];
73
- const lineBytes = Buffer.byteLength(line, "utf-8") + 1;
74
- if (lineBytes > MAX_EVENT_LOG_BYTES)
75
- continue;
76
- if (retained.length > 0 && retainedBytes + lineBytes > ROTATED_EVENT_LOG_TARGET_BYTES)
77
- break;
78
- retained.push(line);
79
- retainedBytes += lineBytes;
80
- if (retainedBytes >= ROTATED_EVENT_LOG_TARGET_BYTES)
81
- break;
82
- }
83
- retained.reverse();
84
- writeFileAtomicSync(filePath, retained.length > 0 ? `${retained.join("\n")}\n` : "");
85
- }
86
64
  /**
87
65
  * Append + rotate under a single exclusive lock on `eventLogPath`. Without the lock, a concurrent
88
66
  * writer (e.g. another session's tool-recovery worker) could append between this call's read and its
89
- * rotated rewrite, and that append would be silently discarded when the rewrite lands; the atomic
90
- * tmp+rename in {@link rotateToolRecoveryEventLogIfNeeded} additionally ensures a reader never observes
91
- * a partially-rewritten (torn) log file.
67
+ * rotated rewrite, and that append would be silently discarded when the rewrite lands. The shared
68
+ * bounded JSONL sink also uses atomic tmp+rename rotation so readers never observe torn log files.
92
69
  */
93
70
  export function writeToolRecoveryLogRecord(entry) {
94
- withFileLockSync(entry.eventLogPath, () => {
95
- appendFileSync(entry.eventLogPath, `${JSON.stringify(entry.record)}\n`, "utf-8");
96
- rotateToolRecoveryEventLogIfNeeded(entry.eventLogPath);
97
- });
71
+ appendBoundedJsonLineSync(entry.eventLogPath, entry.record, TOOL_RECOVERY_EVENT_LIMITS);
98
72
  try {
99
73
  updatePersistedToolRecoveryStats(entry.eventLogPath, entry.record);
100
74
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool-recovery-log-records.js","sourceRoot":"","sources":["../../src/core/tool-recovery-log-records.ts"],"names":[],"mappings":"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","sourcesContent":["import { appendFileSync, existsSync, readFileSync, statSync } from \"node:fs\";\nimport type { ToolArgumentValidationTelemetryEvent } from \"@caupulican/pi-ai\";\nimport {\n\tcreateToolValidationFailureCorpusRecord,\n\ttype ToolValidationFailureShapeEntry,\n\twriteFailureCorpusRecord,\n} from \"./failure-corpus.ts\";\nimport { updatePersistedToolRecoveryStats } from \"./tool-recovery-stats.ts\";\nimport { withFileLockSync, writeFileAtomicSync } from \"./util/atomic-file.ts\";\n\nexport const TOOL_RECOVERY_EVENT_LOG_FILE = \"tool-recovery-events.jsonl\";\nexport const TOOL_ARGUMENT_VALIDATION_LOG_KIND = \"tool_argument_validation\";\n\nconst MAX_EVENT_LOG_BYTES = 4 * 1024 * 1024;\nconst ROTATED_EVENT_LOG_TARGET_BYTES = Math.floor(MAX_EVENT_LOG_BYTES * 0.75);\nconst MAX_ROTATED_EVENT_LOG_RECORDS = 5_000;\nconst MAX_TELEMETRY_LIST_ITEMS = 50;\nconst MAX_TELEMETRY_TEXT_CHARS = 256;\n\nexport interface ToolArgumentValidationLogRecord extends ToolArgumentValidationTelemetryEvent {\n\tkind: typeof TOOL_ARGUMENT_VALIDATION_LOG_KIND;\n\tversion: 1;\n\trecordId: string;\n\tts: string;\n\tsessionId: string;\n}\n\nexport interface ToolRecoveryLogWorkerRecord {\n\teventLogPath: string;\n\tfailureCorpusPath: string;\n\trecord: ToolArgumentValidationLogRecord;\n}\n\nfunction boundedText(value: string): string {\n\treturn value.slice(0, MAX_TELEMETRY_TEXT_CHARS);\n}\n\nfunction boundedList<T extends string>(values: readonly T[] | undefined): T[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS);\n}\n\nfunction boundedTextList(values: readonly string[] | undefined): string[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS).map(boundedText);\n}\n\nfunction copyFailureShape(\n\tshape: readonly ToolValidationFailureShapeEntry[] | undefined,\n): ToolValidationFailureShapeEntry[] {\n\treturn (shape ?? []).slice(0, MAX_TELEMETRY_LIST_ITEMS).map((entry) => ({\n\t\tpath: boundedText(entry.path),\n\t\texpectedType: boundedText(entry.expectedType),\n\t\treceivedType: boundedText(entry.receivedType),\n\t\t...(entry.keyword ? { keyword: boundedText(entry.keyword) } : {}),\n\t}));\n}\n\nexport function createToolArgumentValidationLogRecord(args: {\n\tevent: ToolArgumentValidationTelemetryEvent;\n\trecordId: string;\n\tsessionId: string;\n\tts: string;\n}): ToolArgumentValidationLogRecord {\n\treturn {\n\t\tkind: TOOL_ARGUMENT_VALIDATION_LOG_KIND,\n\t\tversion: 1,\n\t\trecordId: args.recordId,\n\t\tts: args.ts,\n\t\tsessionId: args.sessionId,\n\t\toutcome: args.event.outcome,\n\t\tprovider: args.event.provider,\n\t\tmodel: args.event.model,\n\t\ttool: args.event.tool,\n\t\tsource: args.event.source,\n\t\tfailureModes: boundedList(args.event.failureModes) ?? [],\n\t\trepairsApplied: boundedList(args.event.repairsApplied) ?? [],\n\t\tfailureShape: copyFailureShape(args.event.failureShape),\n\t\terrorKeywords: boundedTextList(args.event.errorKeywords),\n\t\ttaught: args.event.taught,\n\t\texecutionOutcome: args.event.executionOutcome,\n\t};\n}\n\nexport function isToolArgumentValidationLogRecord(value: unknown): value is ToolArgumentValidationLogRecord {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Partial<ToolArgumentValidationLogRecord>;\n\treturn (\n\t\trecord.kind === TOOL_ARGUMENT_VALIDATION_LOG_KIND &&\n\t\trecord.version === 1 &&\n\t\ttypeof record.recordId === \"string\" &&\n\t\ttypeof record.ts === \"string\" &&\n\t\ttypeof record.sessionId === \"string\" &&\n\t\t(record.outcome === \"repaired\" || record.outcome === \"bounced\") &&\n\t\ttypeof record.tool === \"string\" &&\n\t\tArray.isArray(record.failureModes) &&\n\t\tArray.isArray(record.repairsApplied)\n\t);\n}\n\nfunction rotateToolRecoveryEventLogIfNeeded(filePath: string): void {\n\tif (!existsSync(filePath) || statSync(filePath).size <= MAX_EVENT_LOG_BYTES) return;\n\tconst lines = readFileSync(filePath, \"utf-8\")\n\t\t.split(\"\\n\")\n\t\t.filter((line) => line.trim().length > 0);\n\tconst retained: string[] = [];\n\tlet retainedBytes = 0;\n\tfor (let index = lines.length - 1; index >= 0 && retained.length < MAX_ROTATED_EVENT_LOG_RECORDS; index--) {\n\t\tconst line = lines[index];\n\t\tconst lineBytes = Buffer.byteLength(line, \"utf-8\") + 1;\n\t\tif (lineBytes > MAX_EVENT_LOG_BYTES) continue;\n\t\tif (retained.length > 0 && retainedBytes + lineBytes > ROTATED_EVENT_LOG_TARGET_BYTES) break;\n\t\tretained.push(line);\n\t\tretainedBytes += lineBytes;\n\t\tif (retainedBytes >= ROTATED_EVENT_LOG_TARGET_BYTES) break;\n\t}\n\tretained.reverse();\n\twriteFileAtomicSync(filePath, retained.length > 0 ? `${retained.join(\"\\n\")}\\n` : \"\");\n}\n\n/**\n * Append + rotate under a single exclusive lock on `eventLogPath`. Without the lock, a concurrent\n * writer (e.g. another session's tool-recovery worker) could append between this call's read and its\n * rotated rewrite, and that append would be silently discarded when the rewrite lands; the atomic\n * tmp+rename in {@link rotateToolRecoveryEventLogIfNeeded} additionally ensures a reader never observes\n * a partially-rewritten (torn) log file.\n */\nexport function writeToolRecoveryLogRecord(entry: ToolRecoveryLogWorkerRecord): void {\n\twithFileLockSync(entry.eventLogPath, () => {\n\t\tappendFileSync(entry.eventLogPath, `${JSON.stringify(entry.record)}\\n`, \"utf-8\");\n\t\trotateToolRecoveryEventLogIfNeeded(entry.eventLogPath);\n\t});\n\ttry {\n\t\tupdatePersistedToolRecoveryStats(entry.eventLogPath, entry.record);\n\t} catch {\n\t\t// The bounded event log remains the recovery source if the cumulative summary cannot be updated.\n\t}\n\tif (entry.record.outcome !== \"bounced\") return;\n\twriteFailureCorpusRecord(\n\t\tentry.failureCorpusPath,\n\t\tcreateToolValidationFailureCorpusRecord({\n\t\t\tts: entry.record.ts,\n\t\t\tprovider: entry.record.provider,\n\t\t\tmodelId: entry.record.model,\n\t\t\ttool: entry.record.tool,\n\t\t\tfailureModes: entry.record.failureModes,\n\t\t\tshape: copyFailureShape(entry.record.failureShape),\n\t\t\terrorKeywords: entry.record.errorKeywords,\n\t\t}),\n\t);\n}\n"]}
1
+ {"version":3,"file":"tool-recovery-log-records.js","sourceRoot":"","sources":["../../src/core/tool-recovery-log-records.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ToolArgumentValidationTelemetryEvent } from \"@caupulican/pi-ai\";\nimport {\n\tcreateToolValidationFailureCorpusRecord,\n\ttype ToolValidationFailureShapeEntry,\n\twriteFailureCorpusRecord,\n} from \"./failure-corpus.ts\";\nimport { updatePersistedToolRecoveryStats } from \"./tool-recovery-stats.ts\";\nimport { appendBoundedJsonLineSync, type BoundedJsonlLimits } from \"./util/bounded-jsonl.ts\";\n\nexport const TOOL_RECOVERY_EVENT_LOG_FILE = \"tool-recovery-events.jsonl\";\nexport const TOOL_ARGUMENT_VALIDATION_LOG_KIND = \"tool_argument_validation\";\n\nconst TOOL_RECOVERY_EVENT_LIMITS: BoundedJsonlLimits = {\n\tmaxBytes: 4 * 1024 * 1024,\n\ttargetBytes: Math.floor(4 * 1024 * 1024 * 0.75),\n\tmaxRecords: 5_000,\n};\nconst MAX_TELEMETRY_LIST_ITEMS = 50;\nconst MAX_TELEMETRY_TEXT_CHARS = 256;\n\nexport interface ToolArgumentValidationLogRecord extends ToolArgumentValidationTelemetryEvent {\n\tkind: typeof TOOL_ARGUMENT_VALIDATION_LOG_KIND;\n\tversion: 1;\n\trecordId: string;\n\tts: string;\n\tsessionId: string;\n}\n\nexport interface ToolRecoveryLogWorkerRecord {\n\teventLogPath: string;\n\tfailureCorpusPath: string;\n\trecord: ToolArgumentValidationLogRecord;\n}\n\nfunction boundedText(value: string): string {\n\treturn value.slice(0, MAX_TELEMETRY_TEXT_CHARS);\n}\n\nfunction boundedList<T extends string>(values: readonly T[] | undefined): T[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS);\n}\n\nfunction boundedTextList(values: readonly string[] | undefined): string[] | undefined {\n\treturn values?.slice(0, MAX_TELEMETRY_LIST_ITEMS).map(boundedText);\n}\n\nfunction copyFailureShape(\n\tshape: readonly ToolValidationFailureShapeEntry[] | undefined,\n): ToolValidationFailureShapeEntry[] {\n\treturn (shape ?? []).slice(0, MAX_TELEMETRY_LIST_ITEMS).map((entry) => ({\n\t\tpath: boundedText(entry.path),\n\t\texpectedType: boundedText(entry.expectedType),\n\t\treceivedType: boundedText(entry.receivedType),\n\t\t...(entry.keyword ? { keyword: boundedText(entry.keyword) } : {}),\n\t}));\n}\n\nexport function createToolArgumentValidationLogRecord(args: {\n\tevent: ToolArgumentValidationTelemetryEvent;\n\trecordId: string;\n\tsessionId: string;\n\tts: string;\n}): ToolArgumentValidationLogRecord {\n\treturn {\n\t\tkind: TOOL_ARGUMENT_VALIDATION_LOG_KIND,\n\t\tversion: 1,\n\t\trecordId: args.recordId,\n\t\tts: args.ts,\n\t\tsessionId: args.sessionId,\n\t\toutcome: args.event.outcome,\n\t\tprovider: args.event.provider,\n\t\tmodel: args.event.model,\n\t\ttool: args.event.tool,\n\t\tsource: args.event.source,\n\t\tfailureModes: boundedList(args.event.failureModes) ?? [],\n\t\trepairsApplied: boundedList(args.event.repairsApplied) ?? [],\n\t\tfailureShape: copyFailureShape(args.event.failureShape),\n\t\terrorKeywords: boundedTextList(args.event.errorKeywords),\n\t\ttaught: args.event.taught,\n\t\texecutionOutcome: args.event.executionOutcome,\n\t};\n}\n\nexport function isToolArgumentValidationLogRecord(value: unknown): value is ToolArgumentValidationLogRecord {\n\tif (!value || typeof value !== \"object\") return false;\n\tconst record = value as Partial<ToolArgumentValidationLogRecord>;\n\treturn (\n\t\trecord.kind === TOOL_ARGUMENT_VALIDATION_LOG_KIND &&\n\t\trecord.version === 1 &&\n\t\ttypeof record.recordId === \"string\" &&\n\t\ttypeof record.ts === \"string\" &&\n\t\ttypeof record.sessionId === \"string\" &&\n\t\t(record.outcome === \"repaired\" || record.outcome === \"bounced\") &&\n\t\ttypeof record.tool === \"string\" &&\n\t\tArray.isArray(record.failureModes) &&\n\t\tArray.isArray(record.repairsApplied)\n\t);\n}\n\n/**\n * Append + rotate under a single exclusive lock on `eventLogPath`. Without the lock, a concurrent\n * writer (e.g. another session's tool-recovery worker) could append between this call's read and its\n * rotated rewrite, and that append would be silently discarded when the rewrite lands. The shared\n * bounded JSONL sink also uses atomic tmp+rename rotation so readers never observe torn log files.\n */\nexport function writeToolRecoveryLogRecord(entry: ToolRecoveryLogWorkerRecord): void {\n\tappendBoundedJsonLineSync(entry.eventLogPath, entry.record, TOOL_RECOVERY_EVENT_LIMITS);\n\ttry {\n\t\tupdatePersistedToolRecoveryStats(entry.eventLogPath, entry.record);\n\t} catch {\n\t\t// The bounded event log remains the recovery source if the cumulative summary cannot be updated.\n\t}\n\tif (entry.record.outcome !== \"bounced\") return;\n\twriteFailureCorpusRecord(\n\t\tentry.failureCorpusPath,\n\t\tcreateToolValidationFailureCorpusRecord({\n\t\t\tts: entry.record.ts,\n\t\t\tprovider: entry.record.provider,\n\t\t\tmodelId: entry.record.model,\n\t\t\ttool: entry.record.tool,\n\t\t\tfailureModes: entry.record.failureModes,\n\t\t\tshape: copyFailureShape(entry.record.failureShape),\n\t\t\terrorKeywords: entry.record.errorKeywords,\n\t\t}),\n\t);\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { type HostFingerprint } from "../models/fitness-store.ts";
1
+ import { type HostFingerprint } from "../models/host-state-store.ts";
2
2
  export type ToolSelectionIntentClass = "read" | "search" | "execute" | "write" | "retrieve" | "explain" | "other";
3
3
  export interface ToolPerformanceKey {
4
4
  modelRef: string;
@@ -90,9 +90,7 @@ export interface ToolExecutionObservation {
90
90
  hintActiveAtCallTime?: boolean;
91
91
  }
92
92
  export declare class ToolPerformanceStore {
93
- private readonly filePath;
94
- private readonly fingerprint;
95
- private readonly readOnly;
93
+ private readonly storage;
96
94
  constructor(filePath: string, options?: {
97
95
  fingerprint?: () => HostFingerprint;
98
96
  readOnly?: boolean;
@@ -101,9 +99,7 @@ export declare class ToolPerformanceStore {
101
99
  fingerprint?: () => HostFingerprint;
102
100
  readOnly?: boolean;
103
101
  }): ToolPerformanceStore;
104
- private load;
105
- private save;
106
- private hostData;
102
+ private createHostData;
107
103
  get(key: ToolPerformanceKey): ToolPerformanceStats;
108
104
  /** Every per-tool track record recorded for a (model,intent) bucket — the promotion.ts input. */
109
105
  getStatsForIntent(modelRef: string, intentClass: ToolSelectionIntentClass): ToolPerformanceStats[];
@@ -1 +1 @@
1
- {"version":3,"file":"tool-performance-store.d.ts","sourceRoot":"","sources":["../../../src/core/tool-selection/tool-performance-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { stateFile } from \"../agent-paths.ts\";\nimport { currentHostFingerprint, type HostFingerprint } from \"../models/fitness-store.ts\";\nimport { isWorkerSession } from \"../session-role.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_STATS_PER_HOST = 500;\nconst MAX_OBSERVATIONS_PER_HOST = 1_000;\nconst MAX_INTENT_AGREEMENT_PER_HOST = 500;\nconst EWMA_ALPHA = 0.25;\n\nexport type ToolSelectionIntentClass = \"read\" | \"search\" | \"execute\" | \"write\" | \"retrieve\" | \"explain\" | \"other\";\n\nexport interface ToolPerformanceKey {\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\ttool: string;\n}\n\nexport interface ToolPerformanceStats extends ToolPerformanceKey {\n\talpha: number;\n\tbeta: number;\n\tsampleCount: number;\n\tlatencyEwmaMs?: number;\n\tlatencyDeviationEwmaMs?: number;\n\tinputTokenEstimateEwma?: number;\n\toutputTokenEstimateEwma?: number;\n\trepairCount: number;\n\tbounceCount: number;\n\tfailureCount: number;\n\tlastUsedAt: string;\n}\n\nexport interface ToolSelectionObservation {\n\tat: string;\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\tactualTool: string;\n\tfirstTool: boolean;\n\tsucceeded: boolean;\n\tdisposition: \"recommend\" | \"shortlist\" | \"abstain\";\n\trecommendation?: string;\n\tshortlist: string[];\n\tentropy: number;\n\tmargin: number;\n\t/** Redacted ranking: names and numeric scores only, never prompts, arguments, paths, or output. */\n\tranked: Array<{ tool: string; utility: number; probability: number }>;\n\tlatencyMs?: number;\n\tinputTokenEstimate?: number;\n\toutputTokenEstimate?: number;\n}\n\nexport interface ToolSelectionMetrics {\n\tfirstToolAttempts: number;\n\tfirstToolSuccesses: number;\n\twrongToolOrFailureCount: number;\n\trecommendationCount: number;\n\trecommendationMatchedCount: number;\n\tshortlistCount: number;\n\tabstentionCount: number;\n\taverageLatencyMs?: number;\n\taverageInputTokenEstimate?: number;\n\taverageOutputTokenEstimate?: number;\n}\n\n/**\n * Durable, per-(model,intent) aggregate of the observe-mode loop: does the raw expected-utility\n * ranking's top pick (`ToolSelectionObservation.ranked[0]`) match what the model actually called\n * (`ToolSelectionObservation.actualTool`)? Tracked separately from the capped, rolling\n * `observations` log so evidence for a given (model,intent) pair survives that log's trimming.\n * `hintActive*` fields are the SAME agreement measure, but restricted to calls made while an\n * evidence-gated promotion hint (see promotion.ts) was active for this bucket — the hint's own\n * efficacy trace: it never gates activation directly (that is always\n * recomputed live from `ToolPerformanceStats`), but it is the durable evidence a report can show\n * for \"is the hint still earning its keep\".\n */\nexport interface ToolSelectionIntentAgreement {\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\tsampleCount: number;\n\tagreementCount: number;\n\thintActiveSampleCount: number;\n\thintActiveAgreementCount: number;\n\tlastUpdatedAt: string;\n}\n\ninterface HostToolPerformanceData {\n\thost: HostFingerprint;\n\tstats: Record<string, ToolPerformanceStats>;\n\tobservations: ToolSelectionObservation[];\n\tintentAgreement: Record<string, ToolSelectionIntentAgreement>;\n}\n\ninterface ToolPerformanceStoreFile {\n\tversion: 1;\n\thosts: Record<string, HostToolPerformanceData>;\n}\n\nexport interface ToolExecutionObservation {\n\tkey: ToolPerformanceKey;\n\tsuccess: boolean;\n\tlatencyMs: number;\n\tinputTokenEstimate?: number;\n\toutputTokenEstimate?: number;\n\tselection: Omit<ToolSelectionObservation, \"at\" | \"modelRef\" | \"intentClass\" | \"actualTool\" | \"succeeded\">;\n\tat?: string;\n\t/**\n\t * Whether an evidence-gated promotion hint (promotion.ts) was already active for this\n\t * (model,intent) bucket BEFORE this call was recorded — captured by the controller at\n\t * `begin()` time, so it reflects evidence up to but not including this observation. Used only\n\t * to bucket the durable agreement stats (`ToolSelectionIntentAgreement.hintActive*`); never\n\t * changes what gets recorded, only how it is split for the efficacy report.\n\t */\n\thintActiveAtCallTime?: boolean;\n}\n\nfunction statKey(key: ToolPerformanceKey): string {\n\treturn `${key.modelRef}\\0${key.intentClass}\\0${key.tool}`;\n}\n\nfunction intentAgreementKey(modelRef: string, intentClass: ToolSelectionIntentClass): string {\n\treturn `${modelRef}\\0${intentClass}`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isIntentClass(value: unknown): value is ToolSelectionIntentClass {\n\treturn (\n\t\tvalue === \"read\" ||\n\t\tvalue === \"search\" ||\n\t\tvalue === \"execute\" ||\n\t\tvalue === \"write\" ||\n\t\tvalue === \"retrieve\" ||\n\t\tvalue === \"explain\" ||\n\t\tvalue === \"other\"\n\t);\n}\n\nfunction isStats(value: unknown): value is ToolPerformanceStats {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.modelRef === \"string\" &&\n\t\tisIntentClass(value.intentClass) &&\n\t\ttypeof value.tool === \"string\" &&\n\t\ttypeof value.alpha === \"number\" &&\n\t\ttypeof value.beta === \"number\" &&\n\t\ttypeof value.sampleCount === \"number\" &&\n\t\ttypeof value.repairCount === \"number\" &&\n\t\ttypeof value.bounceCount === \"number\" &&\n\t\ttypeof value.failureCount === \"number\" &&\n\t\ttypeof value.lastUsedAt === \"string\"\n\t);\n}\n\nfunction isObservation(value: unknown): value is ToolSelectionObservation {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.at === \"string\" &&\n\t\ttypeof value.modelRef === \"string\" &&\n\t\tisIntentClass(value.intentClass) &&\n\t\ttypeof value.actualTool === \"string\" &&\n\t\ttypeof value.firstTool === \"boolean\" &&\n\t\ttypeof value.succeeded === \"boolean\" &&\n\t\t(value.disposition === \"recommend\" || value.disposition === \"shortlist\" || value.disposition === \"abstain\") &&\n\t\tArray.isArray(value.shortlist) &&\n\t\tvalue.shortlist.every((tool) => typeof tool === \"string\") &&\n\t\ttypeof value.entropy === \"number\" &&\n\t\ttypeof value.margin === \"number\" &&\n\t\tArray.isArray(value.ranked) &&\n\t\tvalue.ranked.every(\n\t\t\t(entry) =>\n\t\t\t\tisRecord(entry) &&\n\t\t\t\ttypeof entry.tool === \"string\" &&\n\t\t\t\ttypeof entry.utility === \"number\" &&\n\t\t\t\ttypeof entry.probability === \"number\",\n\t\t) &&\n\t\t(value.latencyMs === undefined || typeof value.latencyMs === \"number\") &&\n\t\t(value.inputTokenEstimate === undefined || typeof value.inputTokenEstimate === \"number\") &&\n\t\t(value.outputTokenEstimate === undefined || typeof value.outputTokenEstimate === \"number\")\n\t);\n}\n\nfunction isIntentAgreement(value: unknown): value is ToolSelectionIntentAgreement {\n\treturn (\n\t\tisRecord(value) &&\n\t\ttypeof value.modelRef === \"string\" &&\n\t\tisIntentClass(value.intentClass) &&\n\t\ttypeof value.sampleCount === \"number\" &&\n\t\ttypeof value.agreementCount === \"number\" &&\n\t\ttypeof value.hintActiveSampleCount === \"number\" &&\n\t\ttypeof value.hintActiveAgreementCount === \"number\" &&\n\t\ttypeof value.lastUpdatedAt === \"string\"\n\t);\n}\n\nfunction emptyStats(key: ToolPerformanceKey, at: string): ToolPerformanceStats {\n\treturn {\n\t\t...key,\n\t\talpha: 1,\n\t\tbeta: 1,\n\t\tsampleCount: 0,\n\t\trepairCount: 0,\n\t\tbounceCount: 0,\n\t\tfailureCount: 0,\n\t\tlastUsedAt: at,\n\t};\n}\n\nfunction emptyIntentAgreement(\n\tmodelRef: string,\n\tintentClass: ToolSelectionIntentClass,\n\tat: string,\n): ToolSelectionIntentAgreement {\n\treturn {\n\t\tmodelRef,\n\t\tintentClass,\n\t\tsampleCount: 0,\n\t\tagreementCount: 0,\n\t\thintActiveSampleCount: 0,\n\t\thintActiveAgreementCount: 0,\n\t\tlastUpdatedAt: at,\n\t};\n}\n\nfunction finiteNonNegative(value: number | undefined): number | undefined {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n\nfunction updateEwma(previous: number | undefined, next: number | undefined): number | undefined {\n\tif (next === undefined) return previous;\n\treturn previous === undefined ? next : previous + EWMA_ALPHA * (next - previous);\n}\n\nfunction updateDeviation(\n\tpreviousValue: number | undefined,\n\tpreviousDeviation: number | undefined,\n\tnext: number | undefined,\n): number | undefined {\n\tif (next === undefined) return previousDeviation;\n\tconst deviation = previousValue === undefined ? 0 : Math.abs(next - previousValue);\n\treturn updateEwma(previousDeviation, deviation);\n}\n\nfunction parseHost(value: unknown): HostToolPerformanceData | undefined {\n\tif (!isRecord(value) || !isRecord(value.host) || !isRecord(value.stats) || !Array.isArray(value.observations))\n\t\treturn undefined;\n\tconst host = value.host;\n\tif (\n\t\ttypeof host.id !== \"string\" ||\n\t\ttypeof host.cpu !== \"string\" ||\n\t\ttypeof host.cores !== \"number\" ||\n\t\ttypeof host.totalMemGb !== \"number\"\n\t)\n\t\treturn undefined;\n\t// intentAgreement is a purely additive field (older store files predate it) — tolerate absence\n\t// rather than bumping STORE_VERSION, same as any other backward-compatible default-empty field.\n\tconst intentAgreementRaw = isRecord(value.intentAgreement) ? value.intentAgreement : {};\n\treturn {\n\t\thost: { id: host.id, cpu: host.cpu, cores: host.cores, totalMemGb: host.totalMemGb },\n\t\tstats: Object.fromEntries(\n\t\t\tObject.entries(value.stats).filter((entry): entry is [string, ToolPerformanceStats] => isStats(entry[1])),\n\t\t),\n\t\tobservations: value.observations.filter(isObservation),\n\t\tintentAgreement: Object.fromEntries(\n\t\t\tObject.entries(intentAgreementRaw).filter((entry): entry is [string, ToolSelectionIntentAgreement] =>\n\t\t\t\tisIntentAgreement(entry[1]),\n\t\t\t),\n\t\t),\n\t};\n}\n\nfunction parseFile(value: unknown): ToolPerformanceStoreFile {\n\tif (!isRecord(value) || value.version !== STORE_VERSION || !isRecord(value.hosts)) {\n\t\treturn { version: STORE_VERSION, hosts: {} };\n\t}\n\treturn {\n\t\tversion: STORE_VERSION,\n\t\thosts: Object.fromEntries(\n\t\t\tObject.entries(value.hosts)\n\t\t\t\t.map(([hostId, host]) => [hostId, parseHost(host)] as const)\n\t\t\t\t.filter((entry): entry is readonly [string, HostToolPerformanceData] => entry[1] !== undefined),\n\t\t),\n\t};\n}\n\nfunction trimStats(stats: Record<string, ToolPerformanceStats>): Record<string, ToolPerformanceStats> {\n\tconst entries = Object.entries(stats);\n\tif (entries.length <= MAX_STATS_PER_HOST) return stats;\n\treturn Object.fromEntries(\n\t\tentries\n\t\t\t.sort(([, left], [, right]) => Date.parse(right.lastUsedAt) - Date.parse(left.lastUsedAt))\n\t\t\t.slice(0, MAX_STATS_PER_HOST),\n\t);\n}\n\nfunction trimIntentAgreement(\n\trecords: Record<string, ToolSelectionIntentAgreement>,\n): Record<string, ToolSelectionIntentAgreement> {\n\tconst entries = Object.entries(records);\n\tif (entries.length <= MAX_INTENT_AGREEMENT_PER_HOST) return records;\n\treturn Object.fromEntries(\n\t\tentries\n\t\t\t.sort(([, left], [, right]) => Date.parse(right.lastUpdatedAt) - Date.parse(left.lastUpdatedAt))\n\t\t\t.slice(0, MAX_INTENT_AGREEMENT_PER_HOST),\n\t);\n}\n\nexport class ToolPerformanceStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\tprivate readonly readOnly: boolean;\n\n\tconstructor(filePath: string, options: { fingerprint?: () => HostFingerprint; readOnly?: boolean } = {}) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options.fingerprint ?? currentHostFingerprint;\n\t\tthis.readOnly = options.readOnly ?? isWorkerSession();\n\t}\n\n\tstatic forAgentDir(\n\t\tagentDir: string,\n\t\toptions: { fingerprint?: () => HostFingerprint; readOnly?: boolean } = {},\n\t): ToolPerformanceStore {\n\t\treturn new ToolPerformanceStore(stateFile(agentDir, \"tool-performance.json\"), options);\n\t}\n\n\tprivate load(): ToolPerformanceStoreFile {\n\t\ttry {\n\t\t\tif (!existsSync(this.filePath)) return { version: STORE_VERSION, hosts: {} };\n\t\t\treturn parseFile(JSON.parse(readFileSync(this.filePath, \"utf8\")));\n\t\t} catch {\n\t\t\treturn { version: STORE_VERSION, hosts: {} };\n\t\t}\n\t}\n\n\tprivate save(file: ToolPerformanceStoreFile): void {\n\t\t// Zero-footprint (worker session): never create the state dir, lock, or file -- the caller\n\t\t// still gets its normally-computed return value from the in-memory `file`.\n\t\tif (this.readOnly) return;\n\t\tmkdirSync(dirname(this.filePath), { recursive: true });\n\t\twriteFileSync(this.filePath, `${JSON.stringify(file, null, \"\\t\")}\\n`, \"utf8\");\n\t}\n\n\tprivate hostData(file: ToolPerformanceStoreFile): HostToolPerformanceData {\n\t\tconst fingerprint = this.fingerprint();\n\t\tconst existing = file.hosts[fingerprint.id];\n\t\tif (existing) return existing;\n\t\tconst created: HostToolPerformanceData = { host: fingerprint, stats: {}, observations: [], intentAgreement: {} };\n\t\tfile.hosts[fingerprint.id] = created;\n\t\treturn created;\n\t}\n\n\tget(key: ToolPerformanceKey): ToolPerformanceStats {\n\t\tconst file = this.load();\n\t\tconst host = file.hosts[this.fingerprint().id];\n\t\tconst stats = host?.stats[statKey(key)];\n\t\treturn stats ? { ...stats } : emptyStats(key, new Date(0).toISOString());\n\t}\n\n\t/** Every per-tool track record recorded for a (model,intent) bucket — the promotion.ts input. */\n\tgetStatsForIntent(modelRef: string, intentClass: ToolSelectionIntentClass): ToolPerformanceStats[] {\n\t\tconst host = this.load().hosts[this.fingerprint().id];\n\t\tif (!host) return [];\n\t\treturn Object.values(host.stats)\n\t\t\t.filter((stats) => stats.modelRef === modelRef && stats.intentClass === intentClass)\n\t\t\t.map((stats) => ({ ...stats }));\n\t}\n\n\t/** Durable observe-mode agreement for one (model,intent) bucket (see {@link ToolSelectionIntentAgreement}). */\n\tgetIntentAgreement(modelRef: string, intentClass: ToolSelectionIntentClass): ToolSelectionIntentAgreement {\n\t\tconst host = this.load().hosts[this.fingerprint().id];\n\t\tconst record = host?.intentAgreement[intentAgreementKey(modelRef, intentClass)];\n\t\treturn record ? { ...record } : emptyIntentAgreement(modelRef, intentClass, new Date(0).toISOString());\n\t}\n\n\t/** All recorded (model,intent) agreement buckets, optionally scoped to one model — report input. */\n\tgetAllIntentAgreements(modelRef?: string): ToolSelectionIntentAgreement[] {\n\t\tconst host = this.load().hosts[this.fingerprint().id];\n\t\treturn Object.values(host?.intentAgreement ?? {})\n\t\t\t.filter((record) => modelRef === undefined || record.modelRef === modelRef)\n\t\t\t.map((record) => ({ ...record }));\n\t}\n\n\trecordValidation(\n\t\tkey: ToolPerformanceKey,\n\t\toutcome: \"repaired\" | \"bounced\",\n\t\tat = new Date().toISOString(),\n\t): ToolPerformanceStats {\n\t\tconst file = this.load();\n\t\tconst host = this.hostData(file);\n\t\tconst storageKey = statKey(key);\n\t\tconst current = host.stats[storageKey] ?? emptyStats(key, at);\n\t\tconst next: ToolPerformanceStats = {\n\t\t\t...current,\n\t\t\trepairCount: current.repairCount + (outcome === \"repaired\" ? 1 : 0),\n\t\t\tbounceCount: current.bounceCount + (outcome === \"bounced\" ? 1 : 0),\n\t\t\tlastUsedAt: at,\n\t\t};\n\t\thost.stats[storageKey] = next;\n\t\thost.stats = trimStats(host.stats);\n\t\tthis.save(file);\n\t\treturn { ...next };\n\t}\n\n\trecordExecution(observation: ToolExecutionObservation): ToolPerformanceStats {\n\t\tconst at = observation.at ?? new Date().toISOString();\n\t\tconst file = this.load();\n\t\tconst host = this.hostData(file);\n\t\tconst storageKey = statKey(observation.key);\n\t\tconst current = host.stats[storageKey] ?? emptyStats(observation.key, at);\n\t\tconst latencyMs = finiteNonNegative(observation.latencyMs);\n\t\tconst inputTokenEstimate = finiteNonNegative(observation.inputTokenEstimate);\n\t\tconst outputTokenEstimate = finiteNonNegative(observation.outputTokenEstimate);\n\t\tconst next: ToolPerformanceStats = {\n\t\t\t...current,\n\t\t\talpha: current.alpha + (observation.success ? 1 : 0),\n\t\t\tbeta: current.beta + (observation.success ? 0 : 1),\n\t\t\tsampleCount: current.sampleCount + 1,\n\t\t\tlatencyEwmaMs: updateEwma(current.latencyEwmaMs, latencyMs),\n\t\t\tlatencyDeviationEwmaMs: updateDeviation(current.latencyEwmaMs, current.latencyDeviationEwmaMs, latencyMs),\n\t\t\tinputTokenEstimateEwma: updateEwma(current.inputTokenEstimateEwma, inputTokenEstimate),\n\t\t\toutputTokenEstimateEwma: updateEwma(current.outputTokenEstimateEwma, outputTokenEstimate),\n\t\t\tfailureCount: current.failureCount + (observation.success ? 0 : 1),\n\t\t\tlastUsedAt: at,\n\t\t};\n\t\thost.stats[storageKey] = next;\n\t\thost.stats = trimStats(host.stats);\n\t\thost.observations.push({\n\t\t\t...observation.selection,\n\t\t\tat,\n\t\t\tmodelRef: observation.key.modelRef,\n\t\t\tintentClass: observation.key.intentClass,\n\t\t\tactualTool: observation.key.tool,\n\t\t\tsucceeded: observation.success,\n\t\t\tranked: observation.selection.ranked.slice(0, 6),\n\t\t\tshortlist: observation.selection.shortlist.slice(0, 3),\n\t\t\tlatencyMs,\n\t\t\tinputTokenEstimate,\n\t\t\toutputTokenEstimate,\n\t\t});\n\t\tif (host.observations.length > MAX_OBSERVATIONS_PER_HOST) {\n\t\t\thost.observations = host.observations.slice(-MAX_OBSERVATIONS_PER_HOST);\n\t\t}\n\n\t\t// Observe-mode agreement: did the RAW ranking's top pick (before any actual-tool-only\n\t\t// eligibility gating) match what the model actually called? Recorded durably per\n\t\t// (model,intent), separate from the capped `observations` log above, so it survives trimming.\n\t\tconst predictedBest = observation.selection.ranked[0]?.tool;\n\t\tconst agreed = predictedBest !== undefined && predictedBest === observation.key.tool;\n\t\tconst agreementKey = intentAgreementKey(observation.key.modelRef, observation.key.intentClass);\n\t\tconst currentAgreement =\n\t\t\thost.intentAgreement[agreementKey] ??\n\t\t\temptyIntentAgreement(observation.key.modelRef, observation.key.intentClass, at);\n\t\thost.intentAgreement[agreementKey] = {\n\t\t\t...currentAgreement,\n\t\t\tsampleCount: currentAgreement.sampleCount + 1,\n\t\t\tagreementCount: currentAgreement.agreementCount + (agreed ? 1 : 0),\n\t\t\thintActiveSampleCount: currentAgreement.hintActiveSampleCount + (observation.hintActiveAtCallTime ? 1 : 0),\n\t\t\thintActiveAgreementCount:\n\t\t\t\tcurrentAgreement.hintActiveAgreementCount + (observation.hintActiveAtCallTime && agreed ? 1 : 0),\n\t\t\tlastUpdatedAt: at,\n\t\t};\n\t\thost.intentAgreement = trimIntentAgreement(host.intentAgreement);\n\n\t\tthis.save(file);\n\t\treturn { ...next };\n\t}\n\n\tgetMetrics(modelRef?: string): ToolSelectionMetrics {\n\t\tconst host = this.load().hosts[this.fingerprint().id];\n\t\tconst observations = (host?.observations ?? []).filter(\n\t\t\t(observation) => modelRef === undefined || observation.modelRef === modelRef,\n\t\t);\n\t\tconst firstTools = observations.filter((observation) => observation.firstTool);\n\t\tconst recommended = observations.filter((observation) => observation.disposition === \"recommend\");\n\t\tconst average = (values: Array<number | undefined>): number | undefined => {\n\t\t\tconst present = values.filter((value): value is number => value !== undefined);\n\t\t\treturn present.length > 0 ? present.reduce((sum, value) => sum + value, 0) / present.length : undefined;\n\t\t};\n\t\treturn {\n\t\t\tfirstToolAttempts: firstTools.length,\n\t\t\tfirstToolSuccesses: firstTools.filter((observation) => observation.succeeded).length,\n\t\t\twrongToolOrFailureCount: observations.filter(\n\t\t\t\t(observation) =>\n\t\t\t\t\t!observation.succeeded ||\n\t\t\t\t\t(observation.disposition === \"recommend\" && observation.recommendation !== observation.actualTool),\n\t\t\t).length,\n\t\t\trecommendationCount: recommended.length,\n\t\t\trecommendationMatchedCount: recommended.filter(\n\t\t\t\t(observation) => observation.recommendation === observation.actualTool,\n\t\t\t).length,\n\t\t\tshortlistCount: observations.filter((observation) => observation.disposition === \"shortlist\").length,\n\t\t\tabstentionCount: observations.filter((observation) => observation.disposition === \"abstain\").length,\n\t\t\taverageLatencyMs: average(observations.map((observation) => observation.latencyMs)),\n\t\t\taverageInputTokenEstimate: average(observations.map((observation) => observation.inputTokenEstimate)),\n\t\t\taverageOutputTokenEstimate: average(observations.map((observation) => observation.outputTokenEstimate)),\n\t\t};\n\t}\n\n\tgetObservations(modelRef?: string): ToolSelectionObservation[] {\n\t\tconst host = this.load().hosts[this.fingerprint().id];\n\t\treturn (host?.observations ?? [])\n\t\t\t.filter((observation) => modelRef === undefined || observation.modelRef === modelRef)\n\t\t\t.map((observation) => ({\n\t\t\t\t...observation,\n\t\t\t\tshortlist: [...observation.shortlist],\n\t\t\t\tranked: observation.ranked.map((candidate) => ({ ...candidate })),\n\t\t\t}));\n\t}\n}\n"]}
1
+ {"version":3,"file":"tool-performance-store.d.ts","sourceRoot":"","sources":["../../../src/core/tool-selection/tool-performance-store.ts"],"names":[],"mappings":"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","sourcesContent":["import { stateFile } from \"../agent-paths.ts\";\nimport { type HostFingerprint, HostStateStore, isHostFingerprint } from \"../models/host-state-store.ts\";\nimport { isWorkerSession } from \"../session-role.ts\";\nimport { isRecordObject } from \"../util/value-guards.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_STATS_PER_HOST = 500;\nconst MAX_OBSERVATIONS_PER_HOST = 1_000;\nconst MAX_INTENT_AGREEMENT_PER_HOST = 500;\nconst EWMA_ALPHA = 0.25;\n\nexport type ToolSelectionIntentClass = \"read\" | \"search\" | \"execute\" | \"write\" | \"retrieve\" | \"explain\" | \"other\";\n\nexport interface ToolPerformanceKey {\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\ttool: string;\n}\n\nexport interface ToolPerformanceStats extends ToolPerformanceKey {\n\talpha: number;\n\tbeta: number;\n\tsampleCount: number;\n\tlatencyEwmaMs?: number;\n\tlatencyDeviationEwmaMs?: number;\n\tinputTokenEstimateEwma?: number;\n\toutputTokenEstimateEwma?: number;\n\trepairCount: number;\n\tbounceCount: number;\n\tfailureCount: number;\n\tlastUsedAt: string;\n}\n\nexport interface ToolSelectionObservation {\n\tat: string;\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\tactualTool: string;\n\tfirstTool: boolean;\n\tsucceeded: boolean;\n\tdisposition: \"recommend\" | \"shortlist\" | \"abstain\";\n\trecommendation?: string;\n\tshortlist: string[];\n\tentropy: number;\n\tmargin: number;\n\t/** Redacted ranking: names and numeric scores only, never prompts, arguments, paths, or output. */\n\tranked: Array<{ tool: string; utility: number; probability: number }>;\n\tlatencyMs?: number;\n\tinputTokenEstimate?: number;\n\toutputTokenEstimate?: number;\n}\n\nexport interface ToolSelectionMetrics {\n\tfirstToolAttempts: number;\n\tfirstToolSuccesses: number;\n\twrongToolOrFailureCount: number;\n\trecommendationCount: number;\n\trecommendationMatchedCount: number;\n\tshortlistCount: number;\n\tabstentionCount: number;\n\taverageLatencyMs?: number;\n\taverageInputTokenEstimate?: number;\n\taverageOutputTokenEstimate?: number;\n}\n\n/**\n * Durable, per-(model,intent) aggregate of the observe-mode loop: does the raw expected-utility\n * ranking's top pick (`ToolSelectionObservation.ranked[0]`) match what the model actually called\n * (`ToolSelectionObservation.actualTool`)? Tracked separately from the capped, rolling\n * `observations` log so evidence for a given (model,intent) pair survives that log's trimming.\n * `hintActive*` fields are the SAME agreement measure, but restricted to calls made while an\n * evidence-gated promotion hint (see promotion.ts) was active for this bucket — the hint's own\n * efficacy trace: it never gates activation directly (that is always\n * recomputed live from `ToolPerformanceStats`), but it is the durable evidence a report can show\n * for \"is the hint still earning its keep\".\n */\nexport interface ToolSelectionIntentAgreement {\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\tsampleCount: number;\n\tagreementCount: number;\n\thintActiveSampleCount: number;\n\thintActiveAgreementCount: number;\n\tlastUpdatedAt: string;\n}\n\ninterface HostToolPerformanceData {\n\thost: HostFingerprint;\n\tstats: Record<string, ToolPerformanceStats>;\n\tobservations: ToolSelectionObservation[];\n\tintentAgreement: Record<string, ToolSelectionIntentAgreement>;\n}\n\nexport interface ToolExecutionObservation {\n\tkey: ToolPerformanceKey;\n\tsuccess: boolean;\n\tlatencyMs: number;\n\tinputTokenEstimate?: number;\n\toutputTokenEstimate?: number;\n\tselection: Omit<ToolSelectionObservation, \"at\" | \"modelRef\" | \"intentClass\" | \"actualTool\" | \"succeeded\">;\n\tat?: string;\n\t/**\n\t * Whether an evidence-gated promotion hint (promotion.ts) was already active for this\n\t * (model,intent) bucket BEFORE this call was recorded — captured by the controller at\n\t * `begin()` time, so it reflects evidence up to but not including this observation. Used only\n\t * to bucket the durable agreement stats (`ToolSelectionIntentAgreement.hintActive*`); never\n\t * changes what gets recorded, only how it is split for the efficacy report.\n\t */\n\thintActiveAtCallTime?: boolean;\n}\n\nfunction statKey(key: ToolPerformanceKey): string {\n\treturn `${key.modelRef}\\0${key.intentClass}\\0${key.tool}`;\n}\n\nfunction intentAgreementKey(modelRef: string, intentClass: ToolSelectionIntentClass): string {\n\treturn `${modelRef}\\0${intentClass}`;\n}\n\nfunction isIntentClass(value: unknown): value is ToolSelectionIntentClass {\n\treturn (\n\t\tvalue === \"read\" ||\n\t\tvalue === \"search\" ||\n\t\tvalue === \"execute\" ||\n\t\tvalue === \"write\" ||\n\t\tvalue === \"retrieve\" ||\n\t\tvalue === \"explain\" ||\n\t\tvalue === \"other\"\n\t);\n}\n\nfunction isStats(value: unknown): value is ToolPerformanceStats {\n\treturn (\n\t\tisRecordObject(value) &&\n\t\ttypeof value.modelRef === \"string\" &&\n\t\tisIntentClass(value.intentClass) &&\n\t\ttypeof value.tool === \"string\" &&\n\t\ttypeof value.alpha === \"number\" &&\n\t\ttypeof value.beta === \"number\" &&\n\t\ttypeof value.sampleCount === \"number\" &&\n\t\ttypeof value.repairCount === \"number\" &&\n\t\ttypeof value.bounceCount === \"number\" &&\n\t\ttypeof value.failureCount === \"number\" &&\n\t\ttypeof value.lastUsedAt === \"string\"\n\t);\n}\n\nfunction isObservation(value: unknown): value is ToolSelectionObservation {\n\treturn (\n\t\tisRecordObject(value) &&\n\t\ttypeof value.at === \"string\" &&\n\t\ttypeof value.modelRef === \"string\" &&\n\t\tisIntentClass(value.intentClass) &&\n\t\ttypeof value.actualTool === \"string\" &&\n\t\ttypeof value.firstTool === \"boolean\" &&\n\t\ttypeof value.succeeded === \"boolean\" &&\n\t\t(value.disposition === \"recommend\" || value.disposition === \"shortlist\" || value.disposition === \"abstain\") &&\n\t\tArray.isArray(value.shortlist) &&\n\t\tvalue.shortlist.every((tool) => typeof tool === \"string\") &&\n\t\ttypeof value.entropy === \"number\" &&\n\t\ttypeof value.margin === \"number\" &&\n\t\tArray.isArray(value.ranked) &&\n\t\tvalue.ranked.every(\n\t\t\t(entry) =>\n\t\t\t\tisRecordObject(entry) &&\n\t\t\t\ttypeof entry.tool === \"string\" &&\n\t\t\t\ttypeof entry.utility === \"number\" &&\n\t\t\t\ttypeof entry.probability === \"number\",\n\t\t) &&\n\t\t(value.latencyMs === undefined || typeof value.latencyMs === \"number\") &&\n\t\t(value.inputTokenEstimate === undefined || typeof value.inputTokenEstimate === \"number\") &&\n\t\t(value.outputTokenEstimate === undefined || typeof value.outputTokenEstimate === \"number\")\n\t);\n}\n\nfunction isIntentAgreement(value: unknown): value is ToolSelectionIntentAgreement {\n\treturn (\n\t\tisRecordObject(value) &&\n\t\ttypeof value.modelRef === \"string\" &&\n\t\tisIntentClass(value.intentClass) &&\n\t\ttypeof value.sampleCount === \"number\" &&\n\t\ttypeof value.agreementCount === \"number\" &&\n\t\ttypeof value.hintActiveSampleCount === \"number\" &&\n\t\ttypeof value.hintActiveAgreementCount === \"number\" &&\n\t\ttypeof value.lastUpdatedAt === \"string\"\n\t);\n}\n\nfunction emptyStats(key: ToolPerformanceKey, at: string): ToolPerformanceStats {\n\treturn {\n\t\t...key,\n\t\talpha: 1,\n\t\tbeta: 1,\n\t\tsampleCount: 0,\n\t\trepairCount: 0,\n\t\tbounceCount: 0,\n\t\tfailureCount: 0,\n\t\tlastUsedAt: at,\n\t};\n}\n\nfunction emptyIntentAgreement(\n\tmodelRef: string,\n\tintentClass: ToolSelectionIntentClass,\n\tat: string,\n): ToolSelectionIntentAgreement {\n\treturn {\n\t\tmodelRef,\n\t\tintentClass,\n\t\tsampleCount: 0,\n\t\tagreementCount: 0,\n\t\thintActiveSampleCount: 0,\n\t\thintActiveAgreementCount: 0,\n\t\tlastUpdatedAt: at,\n\t};\n}\n\nfunction finiteNonNegative(value: number | undefined): number | undefined {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : undefined;\n}\n\nfunction updateEwma(previous: number | undefined, next: number | undefined): number | undefined {\n\tif (next === undefined) return previous;\n\treturn previous === undefined ? next : previous + EWMA_ALPHA * (next - previous);\n}\n\nfunction updateDeviation(\n\tpreviousValue: number | undefined,\n\tpreviousDeviation: number | undefined,\n\tnext: number | undefined,\n): number | undefined {\n\tif (next === undefined) return previousDeviation;\n\tconst deviation = previousValue === undefined ? 0 : Math.abs(next - previousValue);\n\treturn updateEwma(previousDeviation, deviation);\n}\n\nfunction parseHost(value: unknown, hostId: string): HostToolPerformanceData | undefined {\n\tif (\n\t\t!isRecordObject(value) ||\n\t\t!isHostFingerprint(value.host) ||\n\t\tvalue.host.id !== hostId ||\n\t\t!isRecordObject(value.stats) ||\n\t\t!Array.isArray(value.observations)\n\t)\n\t\treturn undefined;\n\tconst host = value.host;\n\t// intentAgreement is a purely additive field (older store files predate it) — tolerate absence\n\t// rather than bumping STORE_VERSION, same as any other backward-compatible default-empty field.\n\tconst intentAgreementRaw = isRecordObject(value.intentAgreement) ? value.intentAgreement : {};\n\treturn {\n\t\thost,\n\t\tstats: Object.fromEntries(\n\t\t\tObject.entries(value.stats).filter((entry): entry is [string, ToolPerformanceStats] => isStats(entry[1])),\n\t\t),\n\t\tobservations: value.observations.filter(isObservation),\n\t\tintentAgreement: Object.fromEntries(\n\t\t\tObject.entries(intentAgreementRaw).filter((entry): entry is [string, ToolSelectionIntentAgreement] =>\n\t\t\t\tisIntentAgreement(entry[1]),\n\t\t\t),\n\t\t),\n\t};\n}\n\nfunction trimStats(stats: Record<string, ToolPerformanceStats>): Record<string, ToolPerformanceStats> {\n\tconst entries = Object.entries(stats);\n\tif (entries.length <= MAX_STATS_PER_HOST) return stats;\n\treturn Object.fromEntries(\n\t\tentries\n\t\t\t.sort(([, left], [, right]) => Date.parse(right.lastUsedAt) - Date.parse(left.lastUsedAt))\n\t\t\t.slice(0, MAX_STATS_PER_HOST),\n\t);\n}\n\nfunction trimIntentAgreement(\n\trecords: Record<string, ToolSelectionIntentAgreement>,\n): Record<string, ToolSelectionIntentAgreement> {\n\tconst entries = Object.entries(records);\n\tif (entries.length <= MAX_INTENT_AGREEMENT_PER_HOST) return records;\n\treturn Object.fromEntries(\n\t\tentries\n\t\t\t.sort(([, left], [, right]) => Date.parse(right.lastUpdatedAt) - Date.parse(left.lastUpdatedAt))\n\t\t\t.slice(0, MAX_INTENT_AGREEMENT_PER_HOST),\n\t);\n}\n\nexport class ToolPerformanceStore {\n\tprivate readonly storage: HostStateStore<HostToolPerformanceData>;\n\n\tconstructor(filePath: string, options: { fingerprint?: () => HostFingerprint; readOnly?: boolean } = {}) {\n\t\tthis.storage = new HostStateStore({\n\t\t\tfilePath,\n\t\t\tversion: STORE_VERSION,\n\t\t\tfingerprint: options.fingerprint,\n\t\t\treadOnly: options.readOnly ?? isWorkerSession(),\n\t\t\tparseHost,\n\t\t});\n\t}\n\n\tstatic forAgentDir(\n\t\tagentDir: string,\n\t\toptions: { fingerprint?: () => HostFingerprint; readOnly?: boolean } = {},\n\t): ToolPerformanceStore {\n\t\treturn new ToolPerformanceStore(stateFile(agentDir, \"tool-performance.json\"), options);\n\t}\n\n\tprivate createHostData(host: HostFingerprint): HostToolPerformanceData {\n\t\treturn { host, stats: {}, observations: [], intentAgreement: {} };\n\t}\n\n\tget(key: ToolPerformanceKey): ToolPerformanceStats {\n\t\tconst host = this.storage.getHost();\n\t\tconst stats = host?.stats[statKey(key)];\n\t\treturn stats ? { ...stats } : emptyStats(key, new Date(0).toISOString());\n\t}\n\n\t/** Every per-tool track record recorded for a (model,intent) bucket — the promotion.ts input. */\n\tgetStatsForIntent(modelRef: string, intentClass: ToolSelectionIntentClass): ToolPerformanceStats[] {\n\t\tconst host = this.storage.getHost();\n\t\tif (!host) return [];\n\t\treturn Object.values(host.stats)\n\t\t\t.filter((stats) => stats.modelRef === modelRef && stats.intentClass === intentClass)\n\t\t\t.map((stats) => ({ ...stats }));\n\t}\n\n\t/** Durable observe-mode agreement for one (model,intent) bucket (see {@link ToolSelectionIntentAgreement}). */\n\tgetIntentAgreement(modelRef: string, intentClass: ToolSelectionIntentClass): ToolSelectionIntentAgreement {\n\t\tconst host = this.storage.getHost();\n\t\tconst record = host?.intentAgreement[intentAgreementKey(modelRef, intentClass)];\n\t\treturn record ? { ...record } : emptyIntentAgreement(modelRef, intentClass, new Date(0).toISOString());\n\t}\n\n\t/** All recorded (model,intent) agreement buckets, optionally scoped to one model — report input. */\n\tgetAllIntentAgreements(modelRef?: string): ToolSelectionIntentAgreement[] {\n\t\tconst host = this.storage.getHost();\n\t\treturn Object.values(host?.intentAgreement ?? {})\n\t\t\t.filter((record) => modelRef === undefined || record.modelRef === modelRef)\n\t\t\t.map((record) => ({ ...record }));\n\t}\n\n\trecordValidation(\n\t\tkey: ToolPerformanceKey,\n\t\toutcome: \"repaired\" | \"bounced\",\n\t\tat = new Date().toISOString(),\n\t): ToolPerformanceStats {\n\t\treturn this.storage.mutateCurrentHost(\n\t\t\t(host) => this.createHostData(host),\n\t\t\t(host) => {\n\t\t\t\tconst storageKey = statKey(key);\n\t\t\t\tconst current = host.stats[storageKey] ?? emptyStats(key, at);\n\t\t\t\tconst next: ToolPerformanceStats = {\n\t\t\t\t\t...current,\n\t\t\t\t\trepairCount: current.repairCount + (outcome === \"repaired\" ? 1 : 0),\n\t\t\t\t\tbounceCount: current.bounceCount + (outcome === \"bounced\" ? 1 : 0),\n\t\t\t\t\tlastUsedAt: at,\n\t\t\t\t};\n\t\t\t\thost.stats[storageKey] = next;\n\t\t\t\thost.stats = trimStats(host.stats);\n\t\t\t\treturn { result: { ...next }, changed: true };\n\t\t\t},\n\t\t);\n\t}\n\n\trecordExecution(observation: ToolExecutionObservation): ToolPerformanceStats {\n\t\tconst at = observation.at ?? new Date().toISOString();\n\t\treturn this.storage.mutateCurrentHost(\n\t\t\t(host) => this.createHostData(host),\n\t\t\t(host) => {\n\t\t\t\tconst storageKey = statKey(observation.key);\n\t\t\t\tconst current = host.stats[storageKey] ?? emptyStats(observation.key, at);\n\t\t\t\tconst latencyMs = finiteNonNegative(observation.latencyMs);\n\t\t\t\tconst inputTokenEstimate = finiteNonNegative(observation.inputTokenEstimate);\n\t\t\t\tconst outputTokenEstimate = finiteNonNegative(observation.outputTokenEstimate);\n\t\t\t\tconst next: ToolPerformanceStats = {\n\t\t\t\t\t...current,\n\t\t\t\t\talpha: current.alpha + (observation.success ? 1 : 0),\n\t\t\t\t\tbeta: current.beta + (observation.success ? 0 : 1),\n\t\t\t\t\tsampleCount: current.sampleCount + 1,\n\t\t\t\t\tlatencyEwmaMs: updateEwma(current.latencyEwmaMs, latencyMs),\n\t\t\t\t\tlatencyDeviationEwmaMs: updateDeviation(\n\t\t\t\t\t\tcurrent.latencyEwmaMs,\n\t\t\t\t\t\tcurrent.latencyDeviationEwmaMs,\n\t\t\t\t\t\tlatencyMs,\n\t\t\t\t\t),\n\t\t\t\t\tinputTokenEstimateEwma: updateEwma(current.inputTokenEstimateEwma, inputTokenEstimate),\n\t\t\t\t\toutputTokenEstimateEwma: updateEwma(current.outputTokenEstimateEwma, outputTokenEstimate),\n\t\t\t\t\tfailureCount: current.failureCount + (observation.success ? 0 : 1),\n\t\t\t\t\tlastUsedAt: at,\n\t\t\t\t};\n\t\t\t\thost.stats[storageKey] = next;\n\t\t\t\thost.stats = trimStats(host.stats);\n\t\t\t\thost.observations.push({\n\t\t\t\t\t...observation.selection,\n\t\t\t\t\tat,\n\t\t\t\t\tmodelRef: observation.key.modelRef,\n\t\t\t\t\tintentClass: observation.key.intentClass,\n\t\t\t\t\tactualTool: observation.key.tool,\n\t\t\t\t\tsucceeded: observation.success,\n\t\t\t\t\tranked: observation.selection.ranked.slice(0, 6),\n\t\t\t\t\tshortlist: observation.selection.shortlist.slice(0, 3),\n\t\t\t\t\tlatencyMs,\n\t\t\t\t\tinputTokenEstimate,\n\t\t\t\t\toutputTokenEstimate,\n\t\t\t\t});\n\t\t\t\tif (host.observations.length > MAX_OBSERVATIONS_PER_HOST) {\n\t\t\t\t\thost.observations = host.observations.slice(-MAX_OBSERVATIONS_PER_HOST);\n\t\t\t\t}\n\n\t\t\t\t// Observe-mode agreement: did the RAW ranking's top pick (before any actual-tool-only\n\t\t\t\t// eligibility gating) match what the model actually called? Recorded durably per\n\t\t\t\t// (model,intent), separate from the capped `observations` log above, so it survives trimming.\n\t\t\t\tconst predictedBest = observation.selection.ranked[0]?.tool;\n\t\t\t\tconst agreed = predictedBest !== undefined && predictedBest === observation.key.tool;\n\t\t\t\tconst agreementKey = intentAgreementKey(observation.key.modelRef, observation.key.intentClass);\n\t\t\t\tconst currentAgreement =\n\t\t\t\t\thost.intentAgreement[agreementKey] ??\n\t\t\t\t\temptyIntentAgreement(observation.key.modelRef, observation.key.intentClass, at);\n\t\t\t\thost.intentAgreement[agreementKey] = {\n\t\t\t\t\t...currentAgreement,\n\t\t\t\t\tsampleCount: currentAgreement.sampleCount + 1,\n\t\t\t\t\tagreementCount: currentAgreement.agreementCount + (agreed ? 1 : 0),\n\t\t\t\t\thintActiveSampleCount:\n\t\t\t\t\t\tcurrentAgreement.hintActiveSampleCount + (observation.hintActiveAtCallTime ? 1 : 0),\n\t\t\t\t\thintActiveAgreementCount:\n\t\t\t\t\t\tcurrentAgreement.hintActiveAgreementCount + (observation.hintActiveAtCallTime && agreed ? 1 : 0),\n\t\t\t\t\tlastUpdatedAt: at,\n\t\t\t\t};\n\t\t\t\thost.intentAgreement = trimIntentAgreement(host.intentAgreement);\n\n\t\t\t\treturn { result: { ...next }, changed: true };\n\t\t\t},\n\t\t);\n\t}\n\n\tgetMetrics(modelRef?: string): ToolSelectionMetrics {\n\t\tconst host = this.storage.getHost();\n\t\tconst observations = (host?.observations ?? []).filter(\n\t\t\t(observation) => modelRef === undefined || observation.modelRef === modelRef,\n\t\t);\n\t\tconst firstTools = observations.filter((observation) => observation.firstTool);\n\t\tconst recommended = observations.filter((observation) => observation.disposition === \"recommend\");\n\t\tconst average = (values: Array<number | undefined>): number | undefined => {\n\t\t\tconst present = values.filter((value): value is number => value !== undefined);\n\t\t\treturn present.length > 0 ? present.reduce((sum, value) => sum + value, 0) / present.length : undefined;\n\t\t};\n\t\treturn {\n\t\t\tfirstToolAttempts: firstTools.length,\n\t\t\tfirstToolSuccesses: firstTools.filter((observation) => observation.succeeded).length,\n\t\t\twrongToolOrFailureCount: observations.filter(\n\t\t\t\t(observation) =>\n\t\t\t\t\t!observation.succeeded ||\n\t\t\t\t\t(observation.disposition === \"recommend\" && observation.recommendation !== observation.actualTool),\n\t\t\t).length,\n\t\t\trecommendationCount: recommended.length,\n\t\t\trecommendationMatchedCount: recommended.filter(\n\t\t\t\t(observation) => observation.recommendation === observation.actualTool,\n\t\t\t).length,\n\t\t\tshortlistCount: observations.filter((observation) => observation.disposition === \"shortlist\").length,\n\t\t\tabstentionCount: observations.filter((observation) => observation.disposition === \"abstain\").length,\n\t\t\taverageLatencyMs: average(observations.map((observation) => observation.latencyMs)),\n\t\t\taverageInputTokenEstimate: average(observations.map((observation) => observation.inputTokenEstimate)),\n\t\t\taverageOutputTokenEstimate: average(observations.map((observation) => observation.outputTokenEstimate)),\n\t\t};\n\t}\n\n\tgetObservations(modelRef?: string): ToolSelectionObservation[] {\n\t\tconst host = this.storage.getHost();\n\t\treturn (host?.observations ?? [])\n\t\t\t.filter((observation) => modelRef === undefined || observation.modelRef === modelRef)\n\t\t\t.map((observation) => ({\n\t\t\t\t...observation,\n\t\t\t\tshortlist: [...observation.shortlist],\n\t\t\t\tranked: observation.ranked.map((candidate) => ({ ...candidate })),\n\t\t\t}));\n\t}\n}\n"]}