@caupulican/pi-adaptative 0.81.37 → 0.81.39

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 (451) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +1 -1
  3. package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +18 -1
  4. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts +131 -0
  5. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.d.ts.map +1 -0
  6. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js +196 -0
  7. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.js.map +1 -0
  8. package/dist/bundled-resources/extensions/tmux-agent-manager/dispatch-grant.ts +308 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +22 -2
  10. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -1
  11. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +585 -24
  12. package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -1
  13. package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +749 -27
  14. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/__init__.py +43 -0
  15. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/fs.py +270 -0
  16. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/search.py +252 -0
  17. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/strings.py +399 -0
  18. package/dist/bundled-resources/runtimes/pi-shell-engine/commands/text.py +575 -0
  19. package/dist/bundled-resources/runtimes/pi-shell-engine/context.py +52 -0
  20. package/dist/bundled-resources/runtimes/pi-shell-engine/errors.py +49 -0
  21. package/dist/bundled-resources/runtimes/pi-shell-engine/exec.py +734 -0
  22. package/dist/bundled-resources/runtimes/pi-shell-engine/expand.py +238 -0
  23. package/dist/bundled-resources/runtimes/pi-shell-engine/main.py +132 -0
  24. package/dist/bundled-resources/runtimes/pi-shell-engine/nodes.py +116 -0
  25. package/dist/bundled-resources/runtimes/pi-shell-engine/parser.py +287 -0
  26. package/dist/bundled-resources/runtimes/pi-shell-engine/proc.py +137 -0
  27. package/dist/bundled-resources/runtimes/pi-shell-engine/state.py +100 -0
  28. package/dist/bundled-resources/runtimes/pi-shell-engine/tokens.py +579 -0
  29. package/dist/bundled-resources/skills/tool-call-repair/SKILL.md +19 -9
  30. package/dist/bundled-resources/skills/tool-call-repair/references/failure-grammar.md +22 -5
  31. package/dist/bundled-resources/skills/tool-call-repair/references/repair-catalogue.md +5 -5
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +3 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +15 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-paths.d.ts +49 -0
  38. package/dist/core/agent-paths.d.ts.map +1 -0
  39. package/dist/core/agent-paths.js +107 -0
  40. package/dist/core/agent-paths.js.map +1 -0
  41. package/dist/core/agent-session-services.d.ts.map +1 -1
  42. package/dist/core/agent-session-services.js +3 -3
  43. package/dist/core/agent-session-services.js.map +1 -1
  44. package/dist/core/agent-session.d.ts +234 -17
  45. package/dist/core/agent-session.d.ts.map +1 -1
  46. package/dist/core/agent-session.js +484 -62
  47. package/dist/core/agent-session.js.map +1 -1
  48. package/dist/core/autonomy/contracts.d.ts +9 -0
  49. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  50. package/dist/core/autonomy/contracts.js.map +1 -1
  51. package/dist/core/autonomy/lane-tracker.d.ts +10 -1
  52. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  53. package/dist/core/autonomy/lane-tracker.js +5 -1
  54. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  55. package/dist/core/background-lane-controller.d.ts +122 -6
  56. package/dist/core/background-lane-controller.d.ts.map +1 -1
  57. package/dist/core/background-lane-controller.js +447 -90
  58. package/dist/core/background-lane-controller.js.map +1 -1
  59. package/dist/core/bash-execution-controller.d.ts +4 -0
  60. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  61. package/dist/core/bash-execution-controller.js +7 -1
  62. package/dist/core/bash-execution-controller.js.map +1 -1
  63. package/dist/core/compaction-support.d.ts +13 -3
  64. package/dist/core/compaction-support.d.ts.map +1 -1
  65. package/dist/core/compaction-support.js +43 -7
  66. package/dist/core/compaction-support.js.map +1 -1
  67. package/dist/core/context/context-audit.d.ts +33 -1
  68. package/dist/core/context/context-audit.d.ts.map +1 -1
  69. package/dist/core/context/context-audit.js +31 -5
  70. package/dist/core/context/context-audit.js.map +1 -1
  71. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  72. package/dist/core/context/sqlite-runtime-index.js +13 -7
  73. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  74. package/dist/core/context-gc.d.ts.map +1 -1
  75. package/dist/core/context-gc.js +16 -1
  76. package/dist/core/context-gc.js.map +1 -1
  77. package/dist/core/context-pipeline.d.ts +26 -4
  78. package/dist/core/context-pipeline.d.ts.map +1 -1
  79. package/dist/core/context-pipeline.js +137 -14
  80. package/dist/core/context-pipeline.js.map +1 -1
  81. package/dist/core/cost-guard.d.ts +19 -3
  82. package/dist/core/cost-guard.d.ts.map +1 -1
  83. package/dist/core/cost-guard.js +18 -3
  84. package/dist/core/cost-guard.js.map +1 -1
  85. package/dist/core/delegation/session-worker-result.d.ts +34 -4
  86. package/dist/core/delegation/session-worker-result.d.ts.map +1 -1
  87. package/dist/core/delegation/session-worker-result.js +64 -4
  88. package/dist/core/delegation/session-worker-result.js.map +1 -1
  89. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  90. package/dist/core/delegation/worker-actions.js +1 -1
  91. package/dist/core/delegation/worker-actions.js.map +1 -1
  92. package/dist/core/delegation/worker-result.d.ts +42 -1
  93. package/dist/core/delegation/worker-result.d.ts.map +1 -1
  94. package/dist/core/delegation/worker-result.js +68 -0
  95. package/dist/core/delegation/worker-result.js.map +1 -1
  96. package/dist/core/extensions/loader.d.ts.map +1 -1
  97. package/dist/core/extensions/loader.js +23 -7
  98. package/dist/core/extensions/loader.js.map +1 -1
  99. package/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/dist/core/extensions/runner.js +1 -0
  101. package/dist/core/extensions/runner.js.map +1 -1
  102. package/dist/core/extensions/types.d.ts +51 -0
  103. package/dist/core/extensions/types.d.ts.map +1 -1
  104. package/dist/core/extensions/types.js.map +1 -1
  105. package/dist/core/goal-loop-controller.d.ts +17 -1
  106. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  107. package/dist/core/goal-loop-controller.js +79 -11
  108. package/dist/core/goal-loop-controller.js.map +1 -1
  109. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  110. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  111. package/dist/core/goals/goal-continuation-controller.js +77 -0
  112. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  113. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  114. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  115. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  116. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  117. package/dist/core/goals/goal-continuation-prompt.d.ts +4 -0
  118. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  119. package/dist/core/goals/goal-continuation-prompt.js +51 -10
  120. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  121. package/dist/core/goals/goal-runtime-snapshot.d.ts +101 -2
  122. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  123. package/dist/core/goals/goal-runtime-snapshot.js +87 -4
  124. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  125. package/dist/core/goals/goal-state.d.ts +89 -1
  126. package/dist/core/goals/goal-state.d.ts.map +1 -1
  127. package/dist/core/goals/goal-state.js +71 -5
  128. package/dist/core/goals/goal-state.js.map +1 -1
  129. package/dist/core/goals/goal-tool-core.d.ts +56 -2
  130. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  131. package/dist/core/goals/goal-tool-core.js +111 -5
  132. package/dist/core/goals/goal-tool-core.js.map +1 -1
  133. package/dist/core/goals/session-goal-state.d.ts +11 -2
  134. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  135. package/dist/core/goals/session-goal-state.js +32 -17
  136. package/dist/core/goals/session-goal-state.js.map +1 -1
  137. package/dist/core/keybindings.d.ts +10 -0
  138. package/dist/core/keybindings.d.ts.map +1 -1
  139. package/dist/core/keybindings.js +10 -2
  140. package/dist/core/keybindings.js.map +1 -1
  141. package/dist/core/learning/observation-store.d.ts +12 -3
  142. package/dist/core/learning/observation-store.d.ts.map +1 -1
  143. package/dist/core/learning/observation-store.js +30 -15
  144. package/dist/core/learning/observation-store.js.map +1 -1
  145. package/dist/core/learning/skill-curator.d.ts +5 -1
  146. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  147. package/dist/core/learning/skill-curator.js +21 -19
  148. package/dist/core/learning/skill-curator.js.map +1 -1
  149. package/dist/core/local-runtime-controller.d.ts +65 -3
  150. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  151. package/dist/core/local-runtime-controller.js +186 -26
  152. package/dist/core/local-runtime-controller.js.map +1 -1
  153. package/dist/core/memory/providers/file-store.d.ts +1 -1
  154. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  155. package/dist/core/memory/providers/file-store.js +6 -6
  156. package/dist/core/memory/providers/file-store.js.map +1 -1
  157. package/dist/core/model-capability.d.ts +34 -0
  158. package/dist/core/model-capability.d.ts.map +1 -1
  159. package/dist/core/model-capability.js +42 -1
  160. package/dist/core/model-capability.js.map +1 -1
  161. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  162. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  163. package/dist/core/model-router/tool-escalation.js +23 -1
  164. package/dist/core/model-router/tool-escalation.js.map +1 -1
  165. package/dist/core/model-router-controller.d.ts +34 -7
  166. package/dist/core/model-router-controller.d.ts.map +1 -1
  167. package/dist/core/model-router-controller.js +95 -16
  168. package/dist/core/model-router-controller.js.map +1 -1
  169. package/dist/core/models/adaptation-store.d.ts +7 -0
  170. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  171. package/dist/core/models/adaptation-store.js +47 -11
  172. package/dist/core/models/adaptation-store.js.map +1 -1
  173. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  174. package/dist/core/models/default-model-suggestions.js +17 -0
  175. package/dist/core/models/default-model-suggestions.js.map +1 -1
  176. package/dist/core/models/fitness-store.d.ts +3 -0
  177. package/dist/core/models/fitness-store.d.ts.map +1 -1
  178. package/dist/core/models/fitness-store.js +11 -2
  179. package/dist/core/models/fitness-store.js.map +1 -1
  180. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  181. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  182. package/dist/core/models/llamacpp-runtime.js +475 -0
  183. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  184. package/dist/core/models/local-registration.d.ts +40 -0
  185. package/dist/core/models/local-registration.d.ts.map +1 -1
  186. package/dist/core/models/local-registration.js +94 -5
  187. package/dist/core/models/local-registration.js.map +1 -1
  188. package/dist/core/models/local-runtime.d.ts.map +1 -1
  189. package/dist/core/models/local-runtime.js +9 -7
  190. package/dist/core/models/local-runtime.js.map +1 -1
  191. package/dist/core/models/model-ref.d.ts +7 -0
  192. package/dist/core/models/model-ref.d.ts.map +1 -1
  193. package/dist/core/models/model-ref.js +26 -0
  194. package/dist/core/models/model-ref.js.map +1 -1
  195. package/dist/core/models/needle-runtime.d.ts +257 -0
  196. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  197. package/dist/core/models/needle-runtime.js +519 -0
  198. package/dist/core/models/needle-runtime.js.map +1 -0
  199. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  200. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  201. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  202. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  203. package/dist/core/package-manager.d.ts.map +1 -1
  204. package/dist/core/package-manager.js +11 -10
  205. package/dist/core/package-manager.js.map +1 -1
  206. package/dist/core/process-matrix/codes.d.ts +72 -0
  207. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  208. package/dist/core/process-matrix/codes.js +15 -0
  209. package/dist/core/process-matrix/codes.js.map +1 -0
  210. package/dist/core/process-matrix/runtime.d.ts +63 -0
  211. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  212. package/dist/core/process-matrix/runtime.js +310 -0
  213. package/dist/core/process-matrix/runtime.js.map +1 -0
  214. package/dist/core/process-matrix/store.d.ts +22 -0
  215. package/dist/core/process-matrix/store.d.ts.map +1 -0
  216. package/dist/core/process-matrix/store.js +80 -0
  217. package/dist/core/process-matrix/store.js.map +1 -0
  218. package/dist/core/process-matrix/supervisor.d.ts +72 -0
  219. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  220. package/dist/core/process-matrix/supervisor.js +130 -0
  221. package/dist/core/process-matrix/supervisor.js.map +1 -0
  222. package/dist/core/profile-registry.d.ts +7 -0
  223. package/dist/core/profile-registry.d.ts.map +1 -1
  224. package/dist/core/profile-registry.js +20 -0
  225. package/dist/core/profile-registry.js.map +1 -1
  226. package/dist/core/python-runtime.d.ts.map +1 -1
  227. package/dist/core/python-runtime.js +3 -3
  228. package/dist/core/python-runtime.js.map +1 -1
  229. package/dist/core/reflection-controller.d.ts +29 -5
  230. package/dist/core/reflection-controller.d.ts.map +1 -1
  231. package/dist/core/reflection-controller.js +215 -126
  232. package/dist/core/reflection-controller.js.map +1 -1
  233. package/dist/core/reload-blockers.d.ts +36 -0
  234. package/dist/core/reload-blockers.d.ts.map +1 -1
  235. package/dist/core/reload-blockers.js +44 -0
  236. package/dist/core/reload-blockers.js.map +1 -1
  237. package/dist/core/resource-loader.d.ts.map +1 -1
  238. package/dist/core/resource-loader.js +8 -7
  239. package/dist/core/resource-loader.js.map +1 -1
  240. package/dist/core/runtime-builder.d.ts +58 -5
  241. package/dist/core/runtime-builder.d.ts.map +1 -1
  242. package/dist/core/runtime-builder.js +209 -25
  243. package/dist/core/runtime-builder.js.map +1 -1
  244. package/dist/core/scout-controller.d.ts +6 -0
  245. package/dist/core/scout-controller.d.ts.map +1 -1
  246. package/dist/core/scout-controller.js +66 -52
  247. package/dist/core/scout-controller.js.map +1 -1
  248. package/dist/core/sdk.d.ts.map +1 -1
  249. package/dist/core/sdk.js +3 -3
  250. package/dist/core/sdk.js.map +1 -1
  251. package/dist/core/session-role.d.ts +31 -0
  252. package/dist/core/session-role.d.ts.map +1 -0
  253. package/dist/core/session-role.js +52 -0
  254. package/dist/core/session-role.js.map +1 -0
  255. package/dist/core/settings-manager.d.ts +42 -3
  256. package/dist/core/settings-manager.d.ts.map +1 -1
  257. package/dist/core/settings-manager.js +94 -60
  258. package/dist/core/settings-manager.js.map +1 -1
  259. package/dist/core/system-prompt-builder.d.ts +12 -0
  260. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  261. package/dist/core/system-prompt-builder.js +6 -0
  262. package/dist/core/system-prompt-builder.js.map +1 -1
  263. package/dist/core/system-prompt.d.ts +14 -1
  264. package/dist/core/system-prompt.d.ts.map +1 -1
  265. package/dist/core/system-prompt.js +20 -3
  266. package/dist/core/system-prompt.js.map +1 -1
  267. package/dist/core/tasks/session-task-state.d.ts +11 -2
  268. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  269. package/dist/core/tasks/session-task-state.js +27 -11
  270. package/dist/core/tasks/session-task-state.js.map +1 -1
  271. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  272. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  273. package/dist/core/tasks/task-contract-monitor.js +56 -0
  274. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  275. package/dist/core/tasks/task-state.d.ts +8 -0
  276. package/dist/core/tasks/task-state.d.ts.map +1 -1
  277. package/dist/core/tasks/task-state.js +28 -1
  278. package/dist/core/tasks/task-state.js.map +1 -1
  279. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  280. package/dist/core/tool-gate-controller.js +5 -0
  281. package/dist/core/tool-gate-controller.js.map +1 -1
  282. package/dist/core/tool-recovery-log-records.d.ts +7 -0
  283. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  284. package/dist/core/tool-recovery-log-records.js +14 -6
  285. package/dist/core/tool-recovery-log-records.js.map +1 -1
  286. package/dist/core/tool-selection/promotion.d.ts +54 -0
  287. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  288. package/dist/core/tool-selection/promotion.js +81 -0
  289. package/dist/core/tool-selection/promotion.js.map +1 -0
  290. package/dist/core/tool-selection/tool-performance-store.d.ts +37 -0
  291. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  292. package/dist/core/tool-selection/tool-performance-store.js +87 -3
  293. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  294. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  295. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  296. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  297. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  298. package/dist/core/tools/bash.d.ts +18 -0
  299. package/dist/core/tools/bash.d.ts.map +1 -1
  300. package/dist/core/tools/bash.js +97 -18
  301. package/dist/core/tools/bash.js.map +1 -1
  302. package/dist/core/tools/delegate-status.d.ts +14 -0
  303. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  304. package/dist/core/tools/delegate-status.js +82 -7
  305. package/dist/core/tools/delegate-status.js.map +1 -1
  306. package/dist/core/tools/delegate.d.ts.map +1 -1
  307. package/dist/core/tools/delegate.js +25 -8
  308. package/dist/core/tools/delegate.js.map +1 -1
  309. package/dist/core/tools/find.d.ts.map +1 -1
  310. package/dist/core/tools/find.js +52 -44
  311. package/dist/core/tools/find.js.map +1 -1
  312. package/dist/core/tools/goal.d.ts +94 -3
  313. package/dist/core/tools/goal.d.ts.map +1 -1
  314. package/dist/core/tools/goal.js +165 -15
  315. package/dist/core/tools/goal.js.map +1 -1
  316. package/dist/core/tools/grep.d.ts.map +1 -1
  317. package/dist/core/tools/grep.js +5 -4
  318. package/dist/core/tools/grep.js.map +1 -1
  319. package/dist/core/tools/model-fitness.d.ts +7 -0
  320. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  321. package/dist/core/tools/model-fitness.js +2 -2
  322. package/dist/core/tools/model-fitness.js.map +1 -1
  323. package/dist/core/tools/render-utils.d.ts.map +1 -1
  324. package/dist/core/tools/render-utils.js +1 -1
  325. package/dist/core/tools/render-utils.js.map +1 -1
  326. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  327. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  328. package/dist/core/tools/shell-contract-router.js +69 -13
  329. package/dist/core/tools/shell-contract-router.js.map +1 -1
  330. package/dist/core/tools/shell-session.d.ts +89 -0
  331. package/dist/core/tools/shell-session.d.ts.map +1 -0
  332. package/dist/core/tools/shell-session.js +432 -0
  333. package/dist/core/tools/shell-session.js.map +1 -0
  334. package/dist/core/tools/task-steps.d.ts +4 -0
  335. package/dist/core/tools/task-steps.d.ts.map +1 -1
  336. package/dist/core/tools/task-steps.js +63 -8
  337. package/dist/core/tools/task-steps.js.map +1 -1
  338. package/dist/core/tools/tmux-dispatch.d.ts +86 -0
  339. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  340. package/dist/core/tools/tmux-dispatch.js +91 -0
  341. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  342. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  343. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  344. package/dist/core/tools/windows-shell-engine.js +153 -0
  345. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  346. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  347. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  348. package/dist/core/tools/windows-shell-state.js +59 -0
  349. package/dist/core/tools/windows-shell-state.js.map +1 -0
  350. package/dist/core/tools/worktree-sync.d.ts +24 -0
  351. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  352. package/dist/core/tools/worktree-sync.js +338 -0
  353. package/dist/core/tools/worktree-sync.js.map +1 -0
  354. package/dist/core/trust-manager.d.ts +4 -1
  355. package/dist/core/trust-manager.d.ts.map +1 -1
  356. package/dist/core/trust-manager.js +20 -2
  357. package/dist/core/trust-manager.js.map +1 -1
  358. package/dist/core/util/atomic-file.d.ts +55 -0
  359. package/dist/core/util/atomic-file.d.ts.map +1 -0
  360. package/dist/core/util/atomic-file.js +255 -0
  361. package/dist/core/util/atomic-file.js.map +1 -0
  362. package/dist/core/util/minimatch-cache.d.ts +33 -0
  363. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  364. package/dist/core/util/minimatch-cache.js +0 -0
  365. package/dist/core/util/minimatch-cache.js.map +1 -0
  366. package/dist/core/worktree-sync/codes.d.ts +227 -0
  367. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  368. package/dist/core/worktree-sync/codes.js +14 -0
  369. package/dist/core/worktree-sync/codes.js.map +1 -0
  370. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  371. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  372. package/dist/core/worktree-sync/git-engine.js +1191 -0
  373. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  374. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  375. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  376. package/dist/core/worktree-sync/lane-gate.js +360 -0
  377. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  378. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  379. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  380. package/dist/core/worktree-sync/runtime.js +96 -0
  381. package/dist/core/worktree-sync/runtime.js.map +1 -0
  382. package/dist/core/worktree-sync/store.d.ts +69 -0
  383. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  384. package/dist/core/worktree-sync/store.js +247 -0
  385. package/dist/core/worktree-sync/store.js.map +1 -0
  386. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  387. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  388. package/dist/core/worktree-sync/watcher.js +93 -0
  389. package/dist/core/worktree-sync/watcher.js.map +1 -0
  390. package/dist/main.d.ts.map +1 -1
  391. package/dist/main.js +94 -0
  392. package/dist/main.js.map +1 -1
  393. package/dist/migrations.d.ts +9 -0
  394. package/dist/migrations.d.ts.map +1 -1
  395. package/dist/migrations.js +38 -0
  396. package/dist/migrations.js.map +1 -1
  397. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  398. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  399. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  400. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  401. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  402. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  403. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  404. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  405. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  406. package/dist/modes/interactive/interactive-mode.js +4 -0
  407. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  408. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  409. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  410. package/dist/modes/interactive/local-model-commands.js +290 -3
  411. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  412. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  413. package/dist/modes/interactive/session-flow-commands.js +24 -4
  414. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  415. package/dist/utils/fs-watch.d.ts +11 -0
  416. package/dist/utils/fs-watch.d.ts.map +1 -1
  417. package/dist/utils/fs-watch.js +20 -2
  418. package/dist/utils/fs-watch.js.map +1 -1
  419. package/dist/utils/highlight-js-languages.d.ts +4 -0
  420. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  421. package/dist/utils/highlight-js-languages.js +573 -0
  422. package/dist/utils/highlight-js-languages.js.map +1 -0
  423. package/dist/utils/shell.d.ts +7 -1
  424. package/dist/utils/shell.d.ts.map +1 -1
  425. package/dist/utils/shell.js +39 -9
  426. package/dist/utils/shell.js.map +1 -1
  427. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  428. package/dist/utils/syntax-highlight.js +53 -5
  429. package/dist/utils/syntax-highlight.js.map +1 -1
  430. package/dist/utils/tools-manager.d.ts.map +1 -1
  431. package/dist/utils/tools-manager.js +112 -1
  432. package/dist/utils/tools-manager.js.map +1 -1
  433. package/docs/development.md +2 -0
  434. package/docs/packages.md +1 -1
  435. package/docs/process-matrix.md +120 -0
  436. package/docs/settings.md +5 -2
  437. package/docs/tmux-agent-manager.md +85 -2
  438. package/docs/windows.md +52 -3
  439. package/docs/work-directory.md +29 -0
  440. package/docs/worktree-sync.md +250 -0
  441. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  442. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  443. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  444. package/examples/extensions/sandbox/package-lock.json +2 -2
  445. package/examples/extensions/sandbox/package.json +1 -1
  446. package/examples/extensions/with-deps/package-lock.json +2 -2
  447. package/examples/extensions/with-deps/package.json +1 -1
  448. package/npm-shrinkwrap.json +12 -12
  449. package/package.json +10 -4
  450. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  451. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -1,13 +1,19 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
2
+ import { dirname } from "node:path";
3
+ import { stateFile } from "../agent-paths.js";
3
4
  import { currentHostFingerprint } from "../models/fitness-store.js";
5
+ import { isWorkerSession } from "../session-role.js";
4
6
  const STORE_VERSION = 1;
5
7
  const MAX_STATS_PER_HOST = 500;
6
8
  const MAX_OBSERVATIONS_PER_HOST = 1_000;
9
+ const MAX_INTENT_AGREEMENT_PER_HOST = 500;
7
10
  const EWMA_ALPHA = 0.25;
8
11
  function statKey(key) {
9
12
  return `${key.modelRef}\0${key.intentClass}\0${key.tool}`;
10
13
  }
14
+ function intentAgreementKey(modelRef, intentClass) {
15
+ return `${modelRef}\0${intentClass}`;
16
+ }
11
17
  function isRecord(value) {
12
18
  return typeof value === "object" && value !== null && !Array.isArray(value);
13
19
  }
@@ -55,6 +61,16 @@ function isObservation(value) {
55
61
  (value.inputTokenEstimate === undefined || typeof value.inputTokenEstimate === "number") &&
56
62
  (value.outputTokenEstimate === undefined || typeof value.outputTokenEstimate === "number"));
57
63
  }
64
+ function isIntentAgreement(value) {
65
+ return (isRecord(value) &&
66
+ typeof value.modelRef === "string" &&
67
+ isIntentClass(value.intentClass) &&
68
+ typeof value.sampleCount === "number" &&
69
+ typeof value.agreementCount === "number" &&
70
+ typeof value.hintActiveSampleCount === "number" &&
71
+ typeof value.hintActiveAgreementCount === "number" &&
72
+ typeof value.lastUpdatedAt === "string");
73
+ }
58
74
  function emptyStats(key, at) {
59
75
  return {
60
76
  ...key,
@@ -67,6 +83,17 @@ function emptyStats(key, at) {
67
83
  lastUsedAt: at,
68
84
  };
69
85
  }
86
+ function emptyIntentAgreement(modelRef, intentClass, at) {
87
+ return {
88
+ modelRef,
89
+ intentClass,
90
+ sampleCount: 0,
91
+ agreementCount: 0,
92
+ hintActiveSampleCount: 0,
93
+ hintActiveAgreementCount: 0,
94
+ lastUpdatedAt: at,
95
+ };
96
+ }
70
97
  function finiteNonNegative(value) {
71
98
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
72
99
  }
@@ -90,10 +117,14 @@ function parseHost(value) {
90
117
  typeof host.cores !== "number" ||
91
118
  typeof host.totalMemGb !== "number")
92
119
  return undefined;
120
+ // intentAgreement is a purely additive field (older store files predate it) — tolerate absence
121
+ // rather than bumping STORE_VERSION, same as any other backward-compatible default-empty field.
122
+ const intentAgreementRaw = isRecord(value.intentAgreement) ? value.intentAgreement : {};
93
123
  return {
94
124
  host: { id: host.id, cpu: host.cpu, cores: host.cores, totalMemGb: host.totalMemGb },
95
125
  stats: Object.fromEntries(Object.entries(value.stats).filter((entry) => isStats(entry[1]))),
96
126
  observations: value.observations.filter(isObservation),
127
+ intentAgreement: Object.fromEntries(Object.entries(intentAgreementRaw).filter((entry) => isIntentAgreement(entry[1]))),
97
128
  };
98
129
  }
99
130
  function parseFile(value) {
@@ -115,15 +146,25 @@ function trimStats(stats) {
115
146
  .sort(([, left], [, right]) => Date.parse(right.lastUsedAt) - Date.parse(left.lastUsedAt))
116
147
  .slice(0, MAX_STATS_PER_HOST));
117
148
  }
149
+ function trimIntentAgreement(records) {
150
+ const entries = Object.entries(records);
151
+ if (entries.length <= MAX_INTENT_AGREEMENT_PER_HOST)
152
+ return records;
153
+ return Object.fromEntries(entries
154
+ .sort(([, left], [, right]) => Date.parse(right.lastUpdatedAt) - Date.parse(left.lastUpdatedAt))
155
+ .slice(0, MAX_INTENT_AGREEMENT_PER_HOST));
156
+ }
118
157
  export class ToolPerformanceStore {
119
158
  filePath;
120
159
  fingerprint;
160
+ readOnly;
121
161
  constructor(filePath, options = {}) {
122
162
  this.filePath = filePath;
123
163
  this.fingerprint = options.fingerprint ?? currentHostFingerprint;
164
+ this.readOnly = options.readOnly ?? isWorkerSession();
124
165
  }
125
166
  static forAgentDir(agentDir, options = {}) {
126
- return new ToolPerformanceStore(join(agentDir, "state", "tool-performance.json"), options);
167
+ return new ToolPerformanceStore(stateFile(agentDir, "tool-performance.json"), options);
127
168
  }
128
169
  load() {
129
170
  try {
@@ -136,6 +177,10 @@ export class ToolPerformanceStore {
136
177
  }
137
178
  }
138
179
  save(file) {
180
+ // Zero-footprint (worker session): never create the state dir, lock, or file -- the caller
181
+ // still gets its normally-computed return value from the in-memory `file`.
182
+ if (this.readOnly)
183
+ return;
139
184
  mkdirSync(dirname(this.filePath), { recursive: true });
140
185
  writeFileSync(this.filePath, `${JSON.stringify(file, null, "\t")}\n`, "utf8");
141
186
  }
@@ -144,7 +189,7 @@ export class ToolPerformanceStore {
144
189
  const existing = file.hosts[fingerprint.id];
145
190
  if (existing)
146
191
  return existing;
147
- const created = { host: fingerprint, stats: {}, observations: [] };
192
+ const created = { host: fingerprint, stats: {}, observations: [], intentAgreement: {} };
148
193
  file.hosts[fingerprint.id] = created;
149
194
  return created;
150
195
  }
@@ -154,6 +199,28 @@ export class ToolPerformanceStore {
154
199
  const stats = host?.stats[statKey(key)];
155
200
  return stats ? { ...stats } : emptyStats(key, new Date(0).toISOString());
156
201
  }
202
+ /** Every per-tool track record recorded for a (model,intent) bucket — the promotion.ts input. */
203
+ getStatsForIntent(modelRef, intentClass) {
204
+ const host = this.load().hosts[this.fingerprint().id];
205
+ if (!host)
206
+ return [];
207
+ return Object.values(host.stats)
208
+ .filter((stats) => stats.modelRef === modelRef && stats.intentClass === intentClass)
209
+ .map((stats) => ({ ...stats }));
210
+ }
211
+ /** Durable observe-mode agreement for one (model,intent) bucket (see {@link ToolSelectionIntentAgreement}). */
212
+ getIntentAgreement(modelRef, intentClass) {
213
+ const host = this.load().hosts[this.fingerprint().id];
214
+ const record = host?.intentAgreement[intentAgreementKey(modelRef, intentClass)];
215
+ return record ? { ...record } : emptyIntentAgreement(modelRef, intentClass, new Date(0).toISOString());
216
+ }
217
+ /** All recorded (model,intent) agreement buckets, optionally scoped to one model — report input. */
218
+ getAllIntentAgreements(modelRef) {
219
+ const host = this.load().hosts[this.fingerprint().id];
220
+ return Object.values(host?.intentAgreement ?? {})
221
+ .filter((record) => modelRef === undefined || record.modelRef === modelRef)
222
+ .map((record) => ({ ...record }));
223
+ }
157
224
  recordValidation(key, outcome, at = new Date().toISOString()) {
158
225
  const file = this.load();
159
226
  const host = this.hostData(file);
@@ -209,6 +276,23 @@ export class ToolPerformanceStore {
209
276
  if (host.observations.length > MAX_OBSERVATIONS_PER_HOST) {
210
277
  host.observations = host.observations.slice(-MAX_OBSERVATIONS_PER_HOST);
211
278
  }
279
+ // Observe-mode agreement: did the RAW ranking's top pick (before any actual-tool-only
280
+ // eligibility gating) match what the model actually called? Recorded durably per
281
+ // (model,intent), separate from the capped `observations` log above, so it survives trimming.
282
+ const predictedBest = observation.selection.ranked[0]?.tool;
283
+ const agreed = predictedBest !== undefined && predictedBest === observation.key.tool;
284
+ const agreementKey = intentAgreementKey(observation.key.modelRef, observation.key.intentClass);
285
+ const currentAgreement = host.intentAgreement[agreementKey] ??
286
+ emptyIntentAgreement(observation.key.modelRef, observation.key.intentClass, at);
287
+ host.intentAgreement[agreementKey] = {
288
+ ...currentAgreement,
289
+ sampleCount: currentAgreement.sampleCount + 1,
290
+ agreementCount: currentAgreement.agreementCount + (agreed ? 1 : 0),
291
+ hintActiveSampleCount: currentAgreement.hintActiveSampleCount + (observation.hintActiveAtCallTime ? 1 : 0),
292
+ hintActiveAgreementCount: currentAgreement.hintActiveAgreementCount + (observation.hintActiveAtCallTime && agreed ? 1 : 0),
293
+ lastUpdatedAt: at,
294
+ };
295
+ host.intentAgreement = trimIntentAgreement(host.intentAgreement);
212
296
  this.save(file);
213
297
  return { ...next };
214
298
  }
@@ -1 +1 @@
1
- {"version":3,"file":"tool-performance-store.js","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, join } from \"node:path\";\nimport { currentHostFingerprint, type HostFingerprint } from \"../models/fitness-store.ts\";\n\nconst STORE_VERSION = 1;\nconst MAX_STATS_PER_HOST = 500;\nconst MAX_OBSERVATIONS_PER_HOST = 1_000;\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\ninterface HostToolPerformanceData {\n\thost: HostFingerprint;\n\tstats: Record<string, ToolPerformanceStats>;\n\tobservations: ToolSelectionObservation[];\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}\n\nfunction statKey(key: ToolPerformanceKey): string {\n\treturn `${key.modelRef}\\0${key.intentClass}\\0${key.tool}`;\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 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 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\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};\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\nexport class ToolPerformanceStore {\n\tprivate readonly filePath: string;\n\tprivate readonly fingerprint: () => HostFingerprint;\n\n\tconstructor(filePath: string, options: { fingerprint?: () => HostFingerprint } = {}) {\n\t\tthis.filePath = filePath;\n\t\tthis.fingerprint = options.fingerprint ?? currentHostFingerprint;\n\t}\n\n\tstatic forAgentDir(agentDir: string, options: { fingerprint?: () => HostFingerprint } = {}): ToolPerformanceStore {\n\t\treturn new ToolPerformanceStore(join(agentDir, \"state\", \"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\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: [] };\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\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\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.js","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,3 +1,4 @@
1
+ import { type ToolSelectionHint } from "./promotion.ts";
1
2
  import type { ToolPerformanceKey, ToolPerformanceStore, ToolSelectionIntentClass, ToolSelectionObservation } from "./tool-performance-store.ts";
2
3
  export interface ToolSelectionTool {
3
4
  name: string;
@@ -14,6 +15,13 @@ export interface ToolSelectionControllerDeps {
14
15
  getModelRef: () => string;
15
16
  getActiveTools: () => readonly ToolSelectionTool[];
16
17
  isCandidateAllowed?: (toolName: string) => boolean;
18
+ /**
19
+ * Env-var source for the kill switches, injected for hermetic testing; defaults to
20
+ * `process.env`. `PI_TOOL_SELECTION_OBSERVE=0` disables recording (the observe/stats layer,
21
+ * default ON); `PI_TOOL_SELECTION_HINTS=0` disables surfacing the evidence-gated prompt hint
22
+ * (default ON, but the hint itself only ever activates once evidence thresholds are met).
23
+ */
24
+ env?: Record<string, string | undefined>;
17
25
  }
18
26
  export interface ToolSelectionPendingObservation {
19
27
  id: string;
@@ -22,6 +30,18 @@ export interface ToolSelectionPendingObservation {
22
30
  startedAt: number;
23
31
  inputTokenEstimate?: number;
24
32
  selection: Omit<ToolSelectionObservation, "at" | "modelRef" | "intentClass" | "actualTool" | "succeeded">;
33
+ /** Was a promotion hint already active for this (model,intent) bucket before this call? */
34
+ hintActiveAtCallTime: boolean;
35
+ }
36
+ /** Report row for the observe/agreement/promotion loop — see {@link ToolSelectionController.getReport}. */
37
+ export interface ToolSelectionReportEntry {
38
+ modelRef: string;
39
+ intentClass: ToolSelectionIntentClass;
40
+ sampleCount: number;
41
+ agreementRate?: number;
42
+ hintTool?: string;
43
+ hintSampleCount: number;
44
+ hintAgreementRate?: number;
25
45
  }
26
46
  declare function classifyToolIntent(tool: ToolSelectionTool): ToolSelectionIntentClass;
27
47
  declare function estimateTokens(value: unknown): number | undefined;
@@ -30,12 +50,37 @@ export declare class ToolSelectionController {
30
50
  private readonly deps;
31
51
  private readonly pending;
32
52
  private firstToolInTurn;
53
+ /** Kill switch: observe/stats recording, default ON. `PI_TOOL_SELECTION_OBSERVE=0` disables it. */
54
+ private readonly observeEnabled;
33
55
  constructor(deps: ToolSelectionControllerDeps);
34
56
  startTurn(): void;
35
57
  begin(toolCallId: string, toolName: string, args: unknown): ToolSelectionPendingObservation;
36
58
  complete(toolCallId: string, succeeded: boolean, content?: readonly unknown[]): void;
37
59
  recordValidation(toolName: string, outcome: "repaired" | "bounced"): void;
60
+ /**
61
+ * Evidence-gated promotion for one (model,intent) bucket — see promotion.ts. Pure read, no
62
+ * mutation; used both to stamp `hintActiveAtCallTime` and to build the live hint list below.
63
+ */
64
+ private evaluatePromotion;
65
+ /**
66
+ * The currently active evidence-gated hints for the session's current model — one per
67
+ * (intentClass) at most, only where accumulated evidence clears the promotion gate. Consumed by
68
+ * `system-prompt-builder.ts` to render a compact, cache-stable prompt block. Empty when the
69
+ * hint kill switch (`PI_TOOL_SELECTION_HINTS=0`) is set, observing is disabled, or no bucket has
70
+ * cleared the gate yet.
71
+ */
72
+ getActiveHints(modelRef?: string): ToolSelectionHint[];
73
+ /**
74
+ * Report surface for the observe/agreement/promotion loop: per (model,intent), the durable
75
+ * agreement rate (did the raw ranking's top pick match what was actually called), plus the
76
+ * currently active hint (if any) and its own efficacy (agreement rate while it has been active).
77
+ * A read-only diagnostic — never used to gate behavior. Render with
78
+ * {@link formatToolSelectionReport}.
79
+ */
80
+ getReport(modelRef?: string): ToolSelectionReportEntry[];
38
81
  private selectionSnapshot;
39
82
  }
83
+ /** Renders {@link ToolSelectionController.getReport} rows into the /toolhealth-style diagnostic text. */
84
+ export declare function formatToolSelectionReport(entries: readonly ToolSelectionReportEntry[]): string;
40
85
  export { classifyToolIntent, estimateContentTokens, estimateTokens };
41
86
  //# sourceMappingURL=tool-selection-controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tool-selection-controller.d.ts","sourceRoot":"","sources":["../../../src/core/tool-selection/tool-selection-controller.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAEX,kBAAkB,EAClB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,EACxB,MAAM,6BAA6B,CAAC;AAErC,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC3C,KAAK,EAAE,oBAAoB,CAAC;IAC5B,WAAW,EAAE,MAAM,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,SAAS,iBAAiB,EAAE,CAAC;IACnD,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;CACnD;AAED,MAAM,WAAW,+BAA+B;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,kBAAkB,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,IAAI,CAAC,wBAAwB,EAAE,IAAI,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,WAAW,CAAC,CAAC;CAC1G;AA6BD,iBAAS,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CAa7E;AAyBD,iBAAS,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAQ1D;AAED,iBAAS,qBAAqB,CAAC,OAAO,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,GAAG,SAAS,CAS9E;AAiCD,qBAAa,uBAAuB;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA8B;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsD;IAC9E,OAAO,CAAC,eAAe,CAAQ;IAE/B,YAAY,IAAI,EAAE,2BAA2B,EAE5C;IAED,SAAS,IAAI,IAAI,CAEhB;IAED,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,+BAA+B,CAqD1F;IAED,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,GAAE,SAAS,OAAO,EAAO,GAAG,IAAI,CAavF;IAED,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAIxE;IAED,OAAO,CAAC,iBAAiB;CAkBzB;AAED,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,cAAc,EAAE,CAAC","sourcesContent":["import {\n\tdecideExpectedUtility,\n\ttype ExpectedUtilityCandidate,\n\ttype ToolSelectionDecision,\n} from \"./expected-utility.ts\";\nimport type {\n\tToolExecutionObservation,\n\tToolPerformanceKey,\n\tToolPerformanceStore,\n\tToolSelectionIntentClass,\n\tToolSelectionObservation,\n} from \"./tool-performance-store.ts\";\n\nexport interface ToolSelectionTool {\n\tname: string;\n\tdescription?: string;\n\tparameters?: unknown;\n\tprofileAllowed?: boolean;\n\tcapabilityAllowed?: boolean;\n\tpathValidated?: boolean;\n\triskCost?: number;\n\tcontextCost?: number;\n}\n\nexport interface ToolSelectionControllerDeps {\n\tstore: ToolPerformanceStore;\n\tgetModelRef: () => string;\n\tgetActiveTools: () => readonly ToolSelectionTool[];\n\tisCandidateAllowed?: (toolName: string) => boolean;\n}\n\nexport interface ToolSelectionPendingObservation {\n\tid: string;\n\tkey: ToolPerformanceKey;\n\tfirstTool: boolean;\n\tstartedAt: number;\n\tinputTokenEstimate?: number;\n\tselection: Omit<ToolSelectionObservation, \"at\" | \"modelRef\" | \"intentClass\" | \"actualTool\" | \"succeeded\">;\n}\n\nconst INTENT_CLASSES: readonly ToolSelectionIntentClass[] = [\n\t\"read\",\n\t\"search\",\n\t\"execute\",\n\t\"write\",\n\t\"retrieve\",\n\t\"explain\",\n\t\"other\",\n];\n\nconst INTENT_WORDS: Record<ToolSelectionIntentClass, readonly string[]> = {\n\tread: [\"read\", \"cat\", \"ls\", \"list\", \"stat\", \"head\", \"tail\"],\n\tsearch: [\"search\", \"grep\", \"find\", \"glob\", \"ripgrep\", \"query\"],\n\texecute: [\"bash\", \"python\", \"powershell\", \"shell\", \"exec\", \"run\", \"process\", \"command\", \"script\"],\n\twrite: [\"write\", \"edit\", \"patch\", \"apply\", \"delete\", \"mkdir\", \"move\", \"rename\"],\n\tretrieve: [\"retrieve\", \"artifact\", \"recall\", \"memory\", \"fetch\", \"download\"],\n\texplain: [\"explain\", \"help\", \"describe\", \"inspect\", \"context\"],\n\tother: [],\n};\n\nfunction tokenize(value: string): string[] {\n\treturn value\n\t\t.toLowerCase()\n\t\t.split(/[^a-z0-9]+/u)\n\t\t.filter(Boolean);\n}\n\nfunction classifyToolIntent(tool: ToolSelectionTool): ToolSelectionIntentClass {\n\tconst tokens = tokenize(`${tool.name} ${tool.description ?? \"\"}`);\n\tlet best: ToolSelectionIntentClass = \"other\";\n\tlet bestScore = 0;\n\tfor (const intent of INTENT_CLASSES) {\n\t\tif (intent === \"other\") continue;\n\t\tconst score = INTENT_WORDS[intent].reduce((total, word) => total + (tokens.includes(word) ? 1 : 0), 0);\n\t\tif (score > bestScore) {\n\t\t\tbest = intent;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best;\n}\n\nfunction intentValue(intent: ToolSelectionIntentClass, toolIntent: ToolSelectionIntentClass): number {\n\tif (toolIntent === intent) return 0.85;\n\tif (intent === \"other\") return 0.35;\n\tif (toolIntent === \"other\") return 0.2;\n\treturn 0.12;\n}\n\nfunction noToolValue(intent: ToolSelectionIntentClass): number {\n\tswitch (intent) {\n\t\tcase \"explain\":\n\t\t\treturn 0.35;\n\t\tcase \"read\":\n\t\tcase \"search\":\n\t\tcase \"retrieve\":\n\t\t\treturn 0.12;\n\t\tcase \"write\":\n\t\tcase \"execute\":\n\t\t\treturn 0.05;\n\t\tdefault:\n\t\t\treturn 0.2;\n\t}\n}\n\nfunction estimateTokens(value: unknown): number | undefined {\n\ttry {\n\t\tconst serialized = typeof value === \"string\" ? value : JSON.stringify(value);\n\t\tif (!serialized) return undefined;\n\t\treturn Math.max(1, Math.ceil(serialized.length / 4));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction estimateContentTokens(content: readonly unknown[]): number | undefined {\n\tconst text = content\n\t\t.map((block) => {\n\t\t\tif (!block || typeof block !== \"object\") return \"\";\n\t\t\tconst candidate = block as { type?: unknown; text?: unknown };\n\t\t\treturn candidate.type === \"text\" && typeof candidate.text === \"string\" ? candidate.text : \"\";\n\t\t})\n\t\t.join(\"\\n\");\n\treturn estimateTokens(text);\n}\n\nfunction modelToolKey(modelRef: string, intentClass: ToolSelectionIntentClass, tool: string): ToolPerformanceKey {\n\treturn { modelRef, intentClass, tool };\n}\n\nfunction candidateFor(\n\tstore: ToolPerformanceStore,\n\tmodelRef: string,\n\tintentClass: ToolSelectionIntentClass,\n\ttool: ToolSelectionTool,\n\tactualTool: string,\n): ExpectedUtilityCandidate {\n\tconst key = modelToolKey(modelRef, intentClass, tool.name);\n\tconst stats = store.get(key);\n\treturn {\n\t\ttool: tool.name,\n\t\tvalue: intentValue(intentClass, classifyToolIntent(tool)),\n\t\talpha: stats.alpha,\n\t\tbeta: stats.beta,\n\t\tsampleCount: stats.sampleCount,\n\t\tlatencyMs: stats.latencyEwmaMs,\n\t\ttokenEstimate:\n\t\t\tstats.inputTokenEstimateEwma === undefined && stats.outputTokenEstimateEwma === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (stats.inputTokenEstimateEwma ?? 0) + (stats.outputTokenEstimateEwma ?? 0),\n\t\triskCost: tool.riskCost,\n\t\tcontextCost: tool.contextCost,\n\t\tdeterministicMatch: tool.name === actualTool && tool.pathValidated !== false,\n\t\tautomaticEligible: tool.pathValidated !== false,\n\t};\n}\n\nexport class ToolSelectionController {\n\tprivate readonly deps: ToolSelectionControllerDeps;\n\tprivate readonly pending = new Map<string, ToolSelectionPendingObservation>();\n\tprivate firstToolInTurn = true;\n\n\tconstructor(deps: ToolSelectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\tstartTurn(): void {\n\t\tthis.firstToolInTurn = true;\n\t}\n\n\tbegin(toolCallId: string, toolName: string, args: unknown): ToolSelectionPendingObservation {\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst activeTools = this.deps\n\t\t\t.getActiveTools()\n\t\t\t.filter(\n\t\t\t\t(tool) =>\n\t\t\t\t\ttool.profileAllowed !== false &&\n\t\t\t\t\ttool.capabilityAllowed !== false &&\n\t\t\t\t\t(this.deps.isCandidateAllowed?.(tool.name) ?? true),\n\t\t\t);\n\t\tconst actualTool = activeTools.find((tool) => tool.name === toolName) ?? {\n\t\t\tname: toolName,\n\t\t\tpathValidated: true,\n\t\t};\n\t\tconst intentClass = classifyToolIntent(actualTool);\n\t\tconst candidates = activeTools.map((tool) =>\n\t\t\tcandidateFor(\n\t\t\t\tthis.deps.store,\n\t\t\t\tmodelRef,\n\t\t\t\tintentClass,\n\t\t\t\ttool.name === toolName ? tool : { ...tool, pathValidated: false },\n\t\t\t\ttoolName,\n\t\t\t),\n\t\t);\n\t\tif (!candidates.some((candidate) => candidate.tool === toolName)) {\n\t\t\tcandidates.push(candidateFor(this.deps.store, modelRef, intentClass, actualTool, toolName));\n\t\t}\n\t\tcandidates.push({\n\t\t\ttool: \"no_tool\",\n\t\t\tvalue: noToolValue(intentClass),\n\t\t\talpha: 1,\n\t\t\tbeta: 1,\n\t\t\tsampleCount: 0,\n\t\t\tsuccessProbability: 1,\n\t\t\tlatencyMs: 0,\n\t\t\ttokenEstimate: 0,\n\t\t\triskCost: 0,\n\t\t\tcontextCost: 0,\n\t\t});\n\t\tconst decision = decideExpectedUtility(candidates);\n\t\tconst firstTool = this.firstToolInTurn;\n\t\tconst selection = this.selectionSnapshot(decision, firstTool);\n\t\tconst pending: ToolSelectionPendingObservation = {\n\t\t\tid: toolCallId,\n\t\t\tkey: modelToolKey(modelRef, intentClass, toolName),\n\t\t\tfirstTool: this.firstToolInTurn,\n\t\t\tstartedAt: Date.now(),\n\t\t\tinputTokenEstimate: estimateTokens(args),\n\t\t\tselection,\n\t\t};\n\t\tthis.firstToolInTurn = false;\n\t\tthis.pending.set(toolCallId, pending);\n\t\treturn pending;\n\t}\n\n\tcomplete(toolCallId: string, succeeded: boolean, content: readonly unknown[] = []): void {\n\t\tconst pending = this.pending.get(toolCallId);\n\t\tif (!pending) return;\n\t\tthis.pending.delete(toolCallId);\n\t\tconst execution: ToolExecutionObservation = {\n\t\t\tkey: pending.key,\n\t\t\tsuccess: succeeded,\n\t\t\tlatencyMs: Math.max(0, Date.now() - pending.startedAt),\n\t\t\tinputTokenEstimate: pending.inputTokenEstimate,\n\t\t\toutputTokenEstimate: estimateContentTokens(content),\n\t\t\tselection: pending.selection,\n\t\t};\n\t\tthis.deps.store.recordExecution(execution);\n\t}\n\n\trecordValidation(toolName: string, outcome: \"repaired\" | \"bounced\"): void {\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst tool = this.deps.getActiveTools().find((candidate) => candidate.name === toolName) ?? { name: toolName };\n\t\tthis.deps.store.recordValidation(modelToolKey(modelRef, classifyToolIntent(tool), toolName), outcome);\n\t}\n\n\tprivate selectionSnapshot(\n\t\tdecision: ToolSelectionDecision,\n\t\tfirstTool: boolean,\n\t): ToolSelectionPendingObservation[\"selection\"] {\n\t\treturn {\n\t\t\tfirstTool,\n\t\t\tdisposition: decision.disposition,\n\t\t\trecommendation: decision.recommendation,\n\t\t\tshortlist: decision.shortlist,\n\t\t\tentropy: decision.entropy,\n\t\t\tmargin: decision.margin,\n\t\t\tranked: decision.ranked.map((candidate) => ({\n\t\t\t\ttool: candidate.tool,\n\t\t\t\tutility: candidate.utility,\n\t\t\t\tprobability: candidate.probability,\n\t\t\t})),\n\t\t};\n\t}\n}\n\nexport { classifyToolIntent, estimateContentTokens, estimateTokens };\n"]}
1
+ {"version":3,"file":"tool-selection-controller.d.ts","sourceRoot":"","sources":["../../../src/core/tool-selection/tool-selection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tdecideExpectedUtility,\n\ttype ExpectedUtilityCandidate,\n\ttype ToolSelectionDecision,\n} from \"./expected-utility.ts\";\nimport { evaluateToolPromotion, type ToolSelectionHint } from \"./promotion.ts\";\nimport type {\n\tToolExecutionObservation,\n\tToolPerformanceKey,\n\tToolPerformanceStore,\n\tToolSelectionIntentClass,\n\tToolSelectionObservation,\n} from \"./tool-performance-store.ts\";\n\nexport interface ToolSelectionTool {\n\tname: string;\n\tdescription?: string;\n\tparameters?: unknown;\n\tprofileAllowed?: boolean;\n\tcapabilityAllowed?: boolean;\n\tpathValidated?: boolean;\n\triskCost?: number;\n\tcontextCost?: number;\n}\n\nexport interface ToolSelectionControllerDeps {\n\tstore: ToolPerformanceStore;\n\tgetModelRef: () => string;\n\tgetActiveTools: () => readonly ToolSelectionTool[];\n\tisCandidateAllowed?: (toolName: string) => boolean;\n\t/**\n\t * Env-var source for the kill switches, injected for hermetic testing; defaults to\n\t * `process.env`. `PI_TOOL_SELECTION_OBSERVE=0` disables recording (the observe/stats layer,\n\t * default ON); `PI_TOOL_SELECTION_HINTS=0` disables surfacing the evidence-gated prompt hint\n\t * (default ON, but the hint itself only ever activates once evidence thresholds are met).\n\t */\n\tenv?: Record<string, string | undefined>;\n}\n\nexport interface ToolSelectionPendingObservation {\n\tid: string;\n\tkey: ToolPerformanceKey;\n\tfirstTool: boolean;\n\tstartedAt: number;\n\tinputTokenEstimate?: number;\n\tselection: Omit<ToolSelectionObservation, \"at\" | \"modelRef\" | \"intentClass\" | \"actualTool\" | \"succeeded\">;\n\t/** Was a promotion hint already active for this (model,intent) bucket before this call? */\n\thintActiveAtCallTime: boolean;\n}\n\n/** Report row for the observe/agreement/promotion loop — see {@link ToolSelectionController.getReport}. */\nexport interface ToolSelectionReportEntry {\n\tmodelRef: string;\n\tintentClass: ToolSelectionIntentClass;\n\tsampleCount: number;\n\tagreementRate?: number;\n\thintTool?: string;\n\thintSampleCount: number;\n\thintAgreementRate?: number;\n}\n\nconst INTENT_CLASSES: readonly ToolSelectionIntentClass[] = [\n\t\"read\",\n\t\"search\",\n\t\"execute\",\n\t\"write\",\n\t\"retrieve\",\n\t\"explain\",\n\t\"other\",\n];\n\nconst INTENT_WORDS: Record<ToolSelectionIntentClass, readonly string[]> = {\n\tread: [\"read\", \"cat\", \"ls\", \"list\", \"stat\", \"head\", \"tail\"],\n\tsearch: [\"search\", \"grep\", \"find\", \"glob\", \"ripgrep\", \"query\"],\n\texecute: [\"bash\", \"python\", \"powershell\", \"shell\", \"exec\", \"run\", \"process\", \"command\", \"script\"],\n\twrite: [\"write\", \"edit\", \"patch\", \"apply\", \"delete\", \"mkdir\", \"move\", \"rename\"],\n\tretrieve: [\"retrieve\", \"artifact\", \"recall\", \"memory\", \"fetch\", \"download\"],\n\texplain: [\"explain\", \"help\", \"describe\", \"inspect\", \"context\"],\n\tother: [],\n};\n\nfunction tokenize(value: string): string[] {\n\treturn value\n\t\t.toLowerCase()\n\t\t.split(/[^a-z0-9]+/u)\n\t\t.filter(Boolean);\n}\n\nfunction classifyToolIntent(tool: ToolSelectionTool): ToolSelectionIntentClass {\n\tconst tokens = tokenize(`${tool.name} ${tool.description ?? \"\"}`);\n\tlet best: ToolSelectionIntentClass = \"other\";\n\tlet bestScore = 0;\n\tfor (const intent of INTENT_CLASSES) {\n\t\tif (intent === \"other\") continue;\n\t\tconst score = INTENT_WORDS[intent].reduce((total, word) => total + (tokens.includes(word) ? 1 : 0), 0);\n\t\tif (score > bestScore) {\n\t\t\tbest = intent;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\treturn best;\n}\n\nfunction intentValue(intent: ToolSelectionIntentClass, toolIntent: ToolSelectionIntentClass): number {\n\tif (toolIntent === intent) return 0.85;\n\tif (intent === \"other\") return 0.35;\n\tif (toolIntent === \"other\") return 0.2;\n\treturn 0.12;\n}\n\nfunction noToolValue(intent: ToolSelectionIntentClass): number {\n\tswitch (intent) {\n\t\tcase \"explain\":\n\t\t\treturn 0.35;\n\t\tcase \"read\":\n\t\tcase \"search\":\n\t\tcase \"retrieve\":\n\t\t\treturn 0.12;\n\t\tcase \"write\":\n\t\tcase \"execute\":\n\t\t\treturn 0.05;\n\t\tdefault:\n\t\t\treturn 0.2;\n\t}\n}\n\nfunction estimateTokens(value: unknown): number | undefined {\n\ttry {\n\t\tconst serialized = typeof value === \"string\" ? value : JSON.stringify(value);\n\t\tif (!serialized) return undefined;\n\t\treturn Math.max(1, Math.ceil(serialized.length / 4));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction estimateContentTokens(content: readonly unknown[]): number | undefined {\n\tconst text = content\n\t\t.map((block) => {\n\t\t\tif (!block || typeof block !== \"object\") return \"\";\n\t\t\tconst candidate = block as { type?: unknown; text?: unknown };\n\t\t\treturn candidate.type === \"text\" && typeof candidate.text === \"string\" ? candidate.text : \"\";\n\t\t})\n\t\t.join(\"\\n\");\n\treturn estimateTokens(text);\n}\n\nfunction modelToolKey(modelRef: string, intentClass: ToolSelectionIntentClass, tool: string): ToolPerformanceKey {\n\treturn { modelRef, intentClass, tool };\n}\n\nfunction candidateFor(\n\tstore: ToolPerformanceStore,\n\tmodelRef: string,\n\tintentClass: ToolSelectionIntentClass,\n\ttool: ToolSelectionTool,\n\tactualTool: string,\n): ExpectedUtilityCandidate {\n\tconst key = modelToolKey(modelRef, intentClass, tool.name);\n\tconst stats = store.get(key);\n\treturn {\n\t\ttool: tool.name,\n\t\tvalue: intentValue(intentClass, classifyToolIntent(tool)),\n\t\talpha: stats.alpha,\n\t\tbeta: stats.beta,\n\t\tsampleCount: stats.sampleCount,\n\t\tlatencyMs: stats.latencyEwmaMs,\n\t\ttokenEstimate:\n\t\t\tstats.inputTokenEstimateEwma === undefined && stats.outputTokenEstimateEwma === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (stats.inputTokenEstimateEwma ?? 0) + (stats.outputTokenEstimateEwma ?? 0),\n\t\triskCost: tool.riskCost,\n\t\tcontextCost: tool.contextCost,\n\t\tdeterministicMatch: tool.name === actualTool && tool.pathValidated !== false,\n\t\tautomaticEligible: tool.pathValidated !== false,\n\t};\n}\n\nexport class ToolSelectionController {\n\tprivate readonly deps: ToolSelectionControllerDeps;\n\tprivate readonly pending = new Map<string, ToolSelectionPendingObservation>();\n\tprivate firstToolInTurn = true;\n\t/** Kill switch: observe/stats recording, default ON. `PI_TOOL_SELECTION_OBSERVE=0` disables it. */\n\tprivate readonly observeEnabled: boolean;\n\n\tconstructor(deps: ToolSelectionControllerDeps) {\n\t\tthis.deps = deps;\n\t\tthis.observeEnabled = (deps.env ?? process.env).PI_TOOL_SELECTION_OBSERVE !== \"0\";\n\t}\n\n\tstartTurn(): void {\n\t\tthis.firstToolInTurn = true;\n\t}\n\n\tbegin(toolCallId: string, toolName: string, args: unknown): ToolSelectionPendingObservation {\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst activeTools = this.deps\n\t\t\t.getActiveTools()\n\t\t\t.filter(\n\t\t\t\t(tool) =>\n\t\t\t\t\ttool.profileAllowed !== false &&\n\t\t\t\t\ttool.capabilityAllowed !== false &&\n\t\t\t\t\t(this.deps.isCandidateAllowed?.(tool.name) ?? true),\n\t\t\t);\n\t\tconst actualTool = activeTools.find((tool) => tool.name === toolName) ?? {\n\t\t\tname: toolName,\n\t\t\tpathValidated: true,\n\t\t};\n\t\tconst intentClass = classifyToolIntent(actualTool);\n\t\tconst candidates = activeTools.map((tool) =>\n\t\t\tcandidateFor(\n\t\t\t\tthis.deps.store,\n\t\t\t\tmodelRef,\n\t\t\t\tintentClass,\n\t\t\t\ttool.name === toolName ? tool : { ...tool, pathValidated: false },\n\t\t\t\ttoolName,\n\t\t\t),\n\t\t);\n\t\tif (!candidates.some((candidate) => candidate.tool === toolName)) {\n\t\t\tcandidates.push(candidateFor(this.deps.store, modelRef, intentClass, actualTool, toolName));\n\t\t}\n\t\tcandidates.push({\n\t\t\ttool: \"no_tool\",\n\t\t\tvalue: noToolValue(intentClass),\n\t\t\talpha: 1,\n\t\t\tbeta: 1,\n\t\t\tsampleCount: 0,\n\t\t\tsuccessProbability: 1,\n\t\t\tlatencyMs: 0,\n\t\t\ttokenEstimate: 0,\n\t\t\triskCost: 0,\n\t\t\tcontextCost: 0,\n\t\t});\n\t\tconst decision = decideExpectedUtility(candidates);\n\t\tconst firstTool = this.firstToolInTurn;\n\t\tconst selection = this.selectionSnapshot(decision, firstTool);\n\t\t// Evaluated BEFORE this call is recorded, so it reflects evidence up to (not including) this\n\t\t// observation — captured now because complete() (later, async) can no longer distinguish\n\t\t// \"before\" from \"after\" once the store has been written.\n\t\tconst hintActiveAtCallTime = this.observeEnabled\n\t\t\t? this.evaluatePromotion(modelRef, intentClass).tool !== undefined\n\t\t\t: false;\n\t\tconst pending: ToolSelectionPendingObservation = {\n\t\t\tid: toolCallId,\n\t\t\tkey: modelToolKey(modelRef, intentClass, toolName),\n\t\t\tfirstTool: this.firstToolInTurn,\n\t\t\tstartedAt: Date.now(),\n\t\t\tinputTokenEstimate: estimateTokens(args),\n\t\t\tselection,\n\t\t\thintActiveAtCallTime,\n\t\t};\n\t\tthis.firstToolInTurn = false;\n\t\tthis.pending.set(toolCallId, pending);\n\t\treturn pending;\n\t}\n\n\tcomplete(toolCallId: string, succeeded: boolean, content: readonly unknown[] = []): void {\n\t\tconst pending = this.pending.get(toolCallId);\n\t\tif (!pending) return;\n\t\tthis.pending.delete(toolCallId);\n\t\tif (!this.observeEnabled) return;\n\t\tconst execution: ToolExecutionObservation = {\n\t\t\tkey: pending.key,\n\t\t\tsuccess: succeeded,\n\t\t\tlatencyMs: Math.max(0, Date.now() - pending.startedAt),\n\t\t\tinputTokenEstimate: pending.inputTokenEstimate,\n\t\t\toutputTokenEstimate: estimateContentTokens(content),\n\t\t\tselection: pending.selection,\n\t\t\thintActiveAtCallTime: pending.hintActiveAtCallTime,\n\t\t};\n\t\tthis.deps.store.recordExecution(execution);\n\t}\n\n\trecordValidation(toolName: string, outcome: \"repaired\" | \"bounced\"): void {\n\t\tif (!this.observeEnabled) return;\n\t\tconst modelRef = this.deps.getModelRef();\n\t\tconst tool = this.deps.getActiveTools().find((candidate) => candidate.name === toolName) ?? { name: toolName };\n\t\tthis.deps.store.recordValidation(modelToolKey(modelRef, classifyToolIntent(tool), toolName), outcome);\n\t}\n\n\t/**\n\t * Evidence-gated promotion for one (model,intent) bucket — see promotion.ts. Pure read, no\n\t * mutation; used both to stamp `hintActiveAtCallTime` and to build the live hint list below.\n\t */\n\tprivate evaluatePromotion(modelRef: string, intentClass: ToolSelectionIntentClass) {\n\t\treturn evaluateToolPromotion(this.deps.store.getStatsForIntent(modelRef, intentClass));\n\t}\n\n\t/**\n\t * The currently active evidence-gated hints for the session's current model — one per\n\t * (intentClass) at most, only where accumulated evidence clears the promotion gate. Consumed by\n\t * `system-prompt-builder.ts` to render a compact, cache-stable prompt block. Empty when the\n\t * hint kill switch (`PI_TOOL_SELECTION_HINTS=0`) is set, observing is disabled, or no bucket has\n\t * cleared the gate yet.\n\t */\n\tgetActiveHints(modelRef: string = this.deps.getModelRef()): ToolSelectionHint[] {\n\t\tif ((this.deps.env ?? process.env).PI_TOOL_SELECTION_HINTS === \"0\") return [];\n\t\tif (!this.observeEnabled) return [];\n\t\tconst hints: ToolSelectionHint[] = [];\n\t\tfor (const intentClass of INTENT_CLASSES) {\n\t\t\tconst promotion = this.evaluatePromotion(modelRef, intentClass);\n\t\t\tif (!promotion.tool) continue;\n\t\t\thints.push({\n\t\t\t\tmodelRef,\n\t\t\t\tintentClass,\n\t\t\t\ttool: promotion.tool,\n\t\t\t\tsampleCount: promotion.sampleCount,\n\t\t\t\tmargin: promotion.margin,\n\t\t\t\tentropy: promotion.entropy,\n\t\t\t});\n\t\t}\n\t\treturn hints;\n\t}\n\n\t/**\n\t * Report surface for the observe/agreement/promotion loop: per (model,intent), the durable\n\t * agreement rate (did the raw ranking's top pick match what was actually called), plus the\n\t * currently active hint (if any) and its own efficacy (agreement rate while it has been active).\n\t * A read-only diagnostic — never used to gate behavior. Render with\n\t * {@link formatToolSelectionReport}.\n\t */\n\tgetReport(modelRef: string = this.deps.getModelRef()): ToolSelectionReportEntry[] {\n\t\tconst activeHints = new Map(this.getActiveHints(modelRef).map((hint) => [hint.intentClass, hint]));\n\t\treturn this.deps.store\n\t\t\t.getAllIntentAgreements(modelRef)\n\t\t\t.filter((agreement) => agreement.sampleCount > 0)\n\t\t\t.map((agreement) => ({\n\t\t\t\tmodelRef: agreement.modelRef,\n\t\t\t\tintentClass: agreement.intentClass,\n\t\t\t\tsampleCount: agreement.sampleCount,\n\t\t\t\tagreementRate: agreement.agreementCount / agreement.sampleCount,\n\t\t\t\thintTool: activeHints.get(agreement.intentClass)?.tool,\n\t\t\t\thintSampleCount: agreement.hintActiveSampleCount,\n\t\t\t\thintAgreementRate:\n\t\t\t\t\tagreement.hintActiveSampleCount > 0\n\t\t\t\t\t\t? agreement.hintActiveAgreementCount / agreement.hintActiveSampleCount\n\t\t\t\t\t\t: undefined,\n\t\t\t}));\n\t}\n\n\tprivate selectionSnapshot(\n\t\tdecision: ToolSelectionDecision,\n\t\tfirstTool: boolean,\n\t): ToolSelectionPendingObservation[\"selection\"] {\n\t\treturn {\n\t\t\tfirstTool,\n\t\t\tdisposition: decision.disposition,\n\t\t\trecommendation: decision.recommendation,\n\t\t\tshortlist: decision.shortlist,\n\t\t\tentropy: decision.entropy,\n\t\t\tmargin: decision.margin,\n\t\t\tranked: decision.ranked.map((candidate) => ({\n\t\t\t\ttool: candidate.tool,\n\t\t\t\tutility: candidate.utility,\n\t\t\t\tprobability: candidate.probability,\n\t\t\t})),\n\t\t};\n\t}\n}\n\n/** Renders {@link ToolSelectionController.getReport} rows into the /toolhealth-style diagnostic text. */\nexport function formatToolSelectionReport(entries: readonly ToolSelectionReportEntry[]): string {\n\tif (entries.length === 0) {\n\t\treturn \"Tool-selection loop: no observations recorded yet for this host.\";\n\t}\n\tconst sorted = [...entries].sort(\n\t\t(left, right) => left.modelRef.localeCompare(right.modelRef) || left.intentClass.localeCompare(right.intentClass),\n\t);\n\tconst lines = [\"Tool-selection loop (observe -> agreement -> evidence-gated hint)\"];\n\tfor (const entry of sorted) {\n\t\tconst agreementText =\n\t\t\tentry.agreementRate === undefined\n\t\t\t\t? \"n/a\"\n\t\t\t\t: `${Math.round(entry.agreementRate * 100)}% (n=${entry.sampleCount})`;\n\t\tlines.push(` ${entry.modelRef} / ${entry.intentClass}: agreement ${agreementText}`);\n\t\tif (entry.hintTool) {\n\t\t\tconst hintAgreementText =\n\t\t\t\tentry.hintAgreementRate === undefined\n\t\t\t\t\t? \"n/a\"\n\t\t\t\t\t: `${Math.round(entry.hintAgreementRate * 100)}% (n=${entry.hintSampleCount})`;\n\t\t\tlines.push(` hint active: prefer \\`${entry.hintTool}\\` — agreement while active ${hintAgreementText}`);\n\t\t}\n\t}\n\treturn lines.join(\"\\n\");\n}\n\nexport { classifyToolIntent, estimateContentTokens, estimateTokens };\n"]}
@@ -1,4 +1,5 @@
1
1
  import { decideExpectedUtility, } from "./expected-utility.js";
2
+ import { evaluateToolPromotion } from "./promotion.js";
2
3
  const INTENT_CLASSES = [
3
4
  "read",
4
5
  "search",
@@ -110,8 +111,11 @@ export class ToolSelectionController {
110
111
  deps;
111
112
  pending = new Map();
112
113
  firstToolInTurn = true;
114
+ /** Kill switch: observe/stats recording, default ON. `PI_TOOL_SELECTION_OBSERVE=0` disables it. */
115
+ observeEnabled;
113
116
  constructor(deps) {
114
117
  this.deps = deps;
118
+ this.observeEnabled = (deps.env ?? process.env).PI_TOOL_SELECTION_OBSERVE !== "0";
115
119
  }
116
120
  startTurn() {
117
121
  this.firstToolInTurn = true;
@@ -147,6 +151,12 @@ export class ToolSelectionController {
147
151
  const decision = decideExpectedUtility(candidates);
148
152
  const firstTool = this.firstToolInTurn;
149
153
  const selection = this.selectionSnapshot(decision, firstTool);
154
+ // Evaluated BEFORE this call is recorded, so it reflects evidence up to (not including) this
155
+ // observation — captured now because complete() (later, async) can no longer distinguish
156
+ // "before" from "after" once the store has been written.
157
+ const hintActiveAtCallTime = this.observeEnabled
158
+ ? this.evaluatePromotion(modelRef, intentClass).tool !== undefined
159
+ : false;
150
160
  const pending = {
151
161
  id: toolCallId,
152
162
  key: modelToolKey(modelRef, intentClass, toolName),
@@ -154,6 +164,7 @@ export class ToolSelectionController {
154
164
  startedAt: Date.now(),
155
165
  inputTokenEstimate: estimateTokens(args),
156
166
  selection,
167
+ hintActiveAtCallTime,
157
168
  };
158
169
  this.firstToolInTurn = false;
159
170
  this.pending.set(toolCallId, pending);
@@ -164,6 +175,8 @@ export class ToolSelectionController {
164
175
  if (!pending)
165
176
  return;
166
177
  this.pending.delete(toolCallId);
178
+ if (!this.observeEnabled)
179
+ return;
167
180
  const execution = {
168
181
  key: pending.key,
169
182
  success: succeeded,
@@ -171,14 +184,76 @@ export class ToolSelectionController {
171
184
  inputTokenEstimate: pending.inputTokenEstimate,
172
185
  outputTokenEstimate: estimateContentTokens(content),
173
186
  selection: pending.selection,
187
+ hintActiveAtCallTime: pending.hintActiveAtCallTime,
174
188
  };
175
189
  this.deps.store.recordExecution(execution);
176
190
  }
177
191
  recordValidation(toolName, outcome) {
192
+ if (!this.observeEnabled)
193
+ return;
178
194
  const modelRef = this.deps.getModelRef();
179
195
  const tool = this.deps.getActiveTools().find((candidate) => candidate.name === toolName) ?? { name: toolName };
180
196
  this.deps.store.recordValidation(modelToolKey(modelRef, classifyToolIntent(tool), toolName), outcome);
181
197
  }
198
+ /**
199
+ * Evidence-gated promotion for one (model,intent) bucket — see promotion.ts. Pure read, no
200
+ * mutation; used both to stamp `hintActiveAtCallTime` and to build the live hint list below.
201
+ */
202
+ evaluatePromotion(modelRef, intentClass) {
203
+ return evaluateToolPromotion(this.deps.store.getStatsForIntent(modelRef, intentClass));
204
+ }
205
+ /**
206
+ * The currently active evidence-gated hints for the session's current model — one per
207
+ * (intentClass) at most, only where accumulated evidence clears the promotion gate. Consumed by
208
+ * `system-prompt-builder.ts` to render a compact, cache-stable prompt block. Empty when the
209
+ * hint kill switch (`PI_TOOL_SELECTION_HINTS=0`) is set, observing is disabled, or no bucket has
210
+ * cleared the gate yet.
211
+ */
212
+ getActiveHints(modelRef = this.deps.getModelRef()) {
213
+ if ((this.deps.env ?? process.env).PI_TOOL_SELECTION_HINTS === "0")
214
+ return [];
215
+ if (!this.observeEnabled)
216
+ return [];
217
+ const hints = [];
218
+ for (const intentClass of INTENT_CLASSES) {
219
+ const promotion = this.evaluatePromotion(modelRef, intentClass);
220
+ if (!promotion.tool)
221
+ continue;
222
+ hints.push({
223
+ modelRef,
224
+ intentClass,
225
+ tool: promotion.tool,
226
+ sampleCount: promotion.sampleCount,
227
+ margin: promotion.margin,
228
+ entropy: promotion.entropy,
229
+ });
230
+ }
231
+ return hints;
232
+ }
233
+ /**
234
+ * Report surface for the observe/agreement/promotion loop: per (model,intent), the durable
235
+ * agreement rate (did the raw ranking's top pick match what was actually called), plus the
236
+ * currently active hint (if any) and its own efficacy (agreement rate while it has been active).
237
+ * A read-only diagnostic — never used to gate behavior. Render with
238
+ * {@link formatToolSelectionReport}.
239
+ */
240
+ getReport(modelRef = this.deps.getModelRef()) {
241
+ const activeHints = new Map(this.getActiveHints(modelRef).map((hint) => [hint.intentClass, hint]));
242
+ return this.deps.store
243
+ .getAllIntentAgreements(modelRef)
244
+ .filter((agreement) => agreement.sampleCount > 0)
245
+ .map((agreement) => ({
246
+ modelRef: agreement.modelRef,
247
+ intentClass: agreement.intentClass,
248
+ sampleCount: agreement.sampleCount,
249
+ agreementRate: agreement.agreementCount / agreement.sampleCount,
250
+ hintTool: activeHints.get(agreement.intentClass)?.tool,
251
+ hintSampleCount: agreement.hintActiveSampleCount,
252
+ hintAgreementRate: agreement.hintActiveSampleCount > 0
253
+ ? agreement.hintActiveAgreementCount / agreement.hintActiveSampleCount
254
+ : undefined,
255
+ }));
256
+ }
182
257
  selectionSnapshot(decision, firstTool) {
183
258
  return {
184
259
  firstTool,
@@ -195,5 +270,26 @@ export class ToolSelectionController {
195
270
  };
196
271
  }
197
272
  }
273
+ /** Renders {@link ToolSelectionController.getReport} rows into the /toolhealth-style diagnostic text. */
274
+ export function formatToolSelectionReport(entries) {
275
+ if (entries.length === 0) {
276
+ return "Tool-selection loop: no observations recorded yet for this host.";
277
+ }
278
+ const sorted = [...entries].sort((left, right) => left.modelRef.localeCompare(right.modelRef) || left.intentClass.localeCompare(right.intentClass));
279
+ const lines = ["Tool-selection loop (observe -> agreement -> evidence-gated hint)"];
280
+ for (const entry of sorted) {
281
+ const agreementText = entry.agreementRate === undefined
282
+ ? "n/a"
283
+ : `${Math.round(entry.agreementRate * 100)}% (n=${entry.sampleCount})`;
284
+ lines.push(` ${entry.modelRef} / ${entry.intentClass}: agreement ${agreementText}`);
285
+ if (entry.hintTool) {
286
+ const hintAgreementText = entry.hintAgreementRate === undefined
287
+ ? "n/a"
288
+ : `${Math.round(entry.hintAgreementRate * 100)}% (n=${entry.hintSampleCount})`;
289
+ lines.push(` hint active: prefer \`${entry.hintTool}\` — agreement while active ${hintAgreementText}`);
290
+ }
291
+ }
292
+ return lines.join("\n");
293
+ }
198
294
  export { classifyToolIntent, estimateContentTokens, estimateTokens };
199
295
  //# sourceMappingURL=tool-selection-controller.js.map