@caupulican/pi-adaptative 0.81.38 → 0.81.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (881) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +2 -0
  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 +133 -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 +198 -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 +311 -0
  9. package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +31 -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 +823 -79
  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 +1043 -86
  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 +7 -7
  32. package/dist/bundled-resources/skills/tool-call-repair/references/text-protocol-grammar.md +28 -7
  33. package/dist/cli/args.d.ts +5 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +24 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/core/agent-directory-layout.d.ts +15 -0
  38. package/dist/core/agent-directory-layout.d.ts.map +1 -0
  39. package/dist/core/agent-directory-layout.js +72 -0
  40. package/dist/core/agent-directory-layout.js.map +1 -0
  41. package/dist/core/agent-paths.d.ts +66 -0
  42. package/dist/core/agent-paths.d.ts.map +1 -0
  43. package/dist/core/agent-paths.js +170 -0
  44. package/dist/core/agent-paths.js.map +1 -0
  45. package/dist/core/agent-session-runtime.d.ts +52 -8
  46. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  47. package/dist/core/agent-session-runtime.js +397 -79
  48. package/dist/core/agent-session-runtime.js.map +1 -1
  49. package/dist/core/agent-session-services.d.ts +1 -0
  50. package/dist/core/agent-session-services.d.ts.map +1 -1
  51. package/dist/core/agent-session-services.js +4 -3
  52. package/dist/core/agent-session-services.js.map +1 -1
  53. package/dist/core/agent-session.d.ts +255 -103
  54. package/dist/core/agent-session.d.ts.map +1 -1
  55. package/dist/core/agent-session.js +754 -1173
  56. package/dist/core/agent-session.js.map +1 -1
  57. package/dist/core/autonomy/approval-gate.d.ts +3 -2
  58. package/dist/core/autonomy/approval-gate.d.ts.map +1 -1
  59. package/dist/core/autonomy/approval-gate.js +6 -28
  60. package/dist/core/autonomy/approval-gate.js.map +1 -1
  61. package/dist/core/autonomy/bounded-completion.d.ts +1 -1
  62. package/dist/core/autonomy/bounded-completion.d.ts.map +1 -1
  63. package/dist/core/autonomy/bounded-completion.js +1 -1
  64. package/dist/core/autonomy/bounded-completion.js.map +1 -1
  65. package/dist/core/autonomy/contracts.d.ts +26 -9
  66. package/dist/core/autonomy/contracts.d.ts.map +1 -1
  67. package/dist/core/autonomy/contracts.js.map +1 -1
  68. package/dist/core/autonomy/foreground-envelope.d.ts +8 -2
  69. package/dist/core/autonomy/foreground-envelope.d.ts.map +1 -1
  70. package/dist/core/autonomy/foreground-envelope.js +8 -25
  71. package/dist/core/autonomy/foreground-envelope.js.map +1 -1
  72. package/dist/core/autonomy/gates.d.ts.map +1 -1
  73. package/dist/core/autonomy/gates.js +8 -10
  74. package/dist/core/autonomy/gates.js.map +1 -1
  75. package/dist/core/autonomy/lane-private-paths.d.ts +2 -0
  76. package/dist/core/autonomy/lane-private-paths.d.ts.map +1 -0
  77. package/dist/core/autonomy/lane-private-paths.js +16 -0
  78. package/dist/core/autonomy/lane-private-paths.js.map +1 -0
  79. package/dist/core/autonomy/lane-tool-surface.d.ts +10 -0
  80. package/dist/core/autonomy/lane-tool-surface.d.ts.map +1 -1
  81. package/dist/core/autonomy/lane-tool-surface.js +46 -8
  82. package/dist/core/autonomy/lane-tool-surface.js.map +1 -1
  83. package/dist/core/autonomy/lane-tracker.d.ts +24 -1
  84. package/dist/core/autonomy/lane-tracker.d.ts.map +1 -1
  85. package/dist/core/autonomy/lane-tracker.js +43 -1
  86. package/dist/core/autonomy/lane-tracker.js.map +1 -1
  87. package/dist/core/autonomy/risk-assessment.d.ts +1 -2
  88. package/dist/core/autonomy/risk-assessment.d.ts.map +1 -1
  89. package/dist/core/autonomy/risk-assessment.js +1 -123
  90. package/dist/core/autonomy/risk-assessment.js.map +1 -1
  91. package/dist/core/autonomy/session-lane-record.d.ts +4 -4
  92. package/dist/core/autonomy/session-lane-record.d.ts.map +1 -1
  93. package/dist/core/autonomy/session-lane-record.js +16 -29
  94. package/dist/core/autonomy/session-lane-record.js.map +1 -1
  95. package/dist/core/autonomy/telemetry-events.d.ts +1 -1
  96. package/dist/core/autonomy/telemetry-events.d.ts.map +1 -1
  97. package/dist/core/autonomy/telemetry-events.js +1 -1
  98. package/dist/core/autonomy/telemetry-events.js.map +1 -1
  99. package/dist/core/autonomy-telemetry.d.ts +7 -7
  100. package/dist/core/autonomy-telemetry.d.ts.map +1 -1
  101. package/dist/core/autonomy-telemetry.js +9 -9
  102. package/dist/core/autonomy-telemetry.js.map +1 -1
  103. package/dist/core/background-lane-controller.d.ts +90 -156
  104. package/dist/core/background-lane-controller.d.ts.map +1 -1
  105. package/dist/core/background-lane-controller.js +192 -968
  106. package/dist/core/background-lane-controller.js.map +1 -1
  107. package/dist/core/bash-execution-controller.d.ts +4 -0
  108. package/dist/core/bash-execution-controller.d.ts.map +1 -1
  109. package/dist/core/bash-execution-controller.js +7 -1
  110. package/dist/core/bash-execution-controller.js.map +1 -1
  111. package/dist/core/capability-contract.d.ts +4 -0
  112. package/dist/core/capability-contract.d.ts.map +1 -0
  113. package/dist/core/capability-contract.js +28 -0
  114. package/dist/core/capability-contract.js.map +1 -0
  115. package/dist/core/compaction-controller.d.ts +90 -0
  116. package/dist/core/compaction-controller.d.ts.map +1 -0
  117. package/dist/core/compaction-controller.js +452 -0
  118. package/dist/core/compaction-controller.js.map +1 -0
  119. package/dist/core/compaction-support.d.ts +24 -13
  120. package/dist/core/compaction-support.d.ts.map +1 -1
  121. package/dist/core/compaction-support.js +43 -7
  122. package/dist/core/compaction-support.js.map +1 -1
  123. package/dist/core/context/context-artifacts.d.ts +6 -2
  124. package/dist/core/context/context-artifacts.d.ts.map +1 -1
  125. package/dist/core/context/context-artifacts.js +34 -2
  126. package/dist/core/context/context-artifacts.js.map +1 -1
  127. package/dist/core/context/context-audit.d.ts +33 -1
  128. package/dist/core/context/context-audit.d.ts.map +1 -1
  129. package/dist/core/context/context-audit.js +31 -5
  130. package/dist/core/context/context-audit.js.map +1 -1
  131. package/dist/core/context/context-store-retention.d.ts +8 -2
  132. package/dist/core/context/context-store-retention.d.ts.map +1 -1
  133. package/dist/core/context/context-store-retention.js +158 -35
  134. package/dist/core/context/context-store-retention.js.map +1 -1
  135. package/dist/core/context/okf-memory.d.ts.map +1 -1
  136. package/dist/core/context/okf-memory.js +9 -11
  137. package/dist/core/context/okf-memory.js.map +1 -1
  138. package/dist/core/context/sqlite-runtime-index.d.ts.map +1 -1
  139. package/dist/core/context/sqlite-runtime-index.js +6 -8
  140. package/dist/core/context/sqlite-runtime-index.js.map +1 -1
  141. package/dist/core/context-gc.d.ts +3 -0
  142. package/dist/core/context-gc.d.ts.map +1 -1
  143. package/dist/core/context-gc.js +26 -6
  144. package/dist/core/context-gc.js.map +1 -1
  145. package/dist/core/context-pipeline.d.ts +26 -4
  146. package/dist/core/context-pipeline.d.ts.map +1 -1
  147. package/dist/core/context-pipeline.js +146 -18
  148. package/dist/core/context-pipeline.js.map +1 -1
  149. package/dist/core/cost-guard.d.ts +19 -3
  150. package/dist/core/cost-guard.d.ts.map +1 -1
  151. package/dist/core/cost-guard.js +18 -3
  152. package/dist/core/cost-guard.js.map +1 -1
  153. package/dist/core/default-tool-surface.d.ts.map +1 -1
  154. package/dist/core/default-tool-surface.js +1 -0
  155. package/dist/core/default-tool-surface.js.map +1 -1
  156. package/dist/core/delegation/managed-lane-controller.d.ts +34 -0
  157. package/dist/core/delegation/managed-lane-controller.d.ts.map +1 -0
  158. package/dist/core/delegation/managed-lane-controller.js +217 -0
  159. package/dist/core/delegation/managed-lane-controller.js.map +1 -0
  160. package/dist/core/delegation/session-worker-claim.d.ts +44 -0
  161. package/dist/core/delegation/session-worker-claim.d.ts.map +1 -0
  162. package/dist/core/delegation/session-worker-claim.js +98 -0
  163. package/dist/core/delegation/session-worker-claim.js.map +1 -0
  164. package/dist/core/delegation/worker-actions.d.ts +7 -5
  165. package/dist/core/delegation/worker-actions.d.ts.map +1 -1
  166. package/dist/core/delegation/worker-actions.js +20 -4
  167. package/dist/core/delegation/worker-actions.js.map +1 -1
  168. package/dist/core/delegation/worker-claim.d.ts +56 -0
  169. package/dist/core/delegation/worker-claim.d.ts.map +1 -0
  170. package/dist/core/delegation/worker-claim.js +239 -0
  171. package/dist/core/delegation/worker-claim.js.map +1 -0
  172. package/dist/core/delegation/worker-delegation-controller.d.ts +110 -0
  173. package/dist/core/delegation/worker-delegation-controller.d.ts.map +1 -0
  174. package/dist/core/delegation/worker-delegation-controller.js +861 -0
  175. package/dist/core/delegation/worker-delegation-controller.js.map +1 -0
  176. package/dist/core/delegation/worker-delegation-request.d.ts +11 -0
  177. package/dist/core/delegation/worker-delegation-request.d.ts.map +1 -0
  178. package/dist/core/delegation/worker-delegation-request.js +2 -0
  179. package/dist/core/delegation/worker-delegation-request.js.map +1 -0
  180. package/dist/core/delegation/worker-dispatch-scheduler.d.ts +40 -0
  181. package/dist/core/delegation/worker-dispatch-scheduler.d.ts.map +1 -0
  182. package/dist/core/delegation/worker-dispatch-scheduler.js +93 -0
  183. package/dist/core/delegation/worker-dispatch-scheduler.js.map +1 -0
  184. package/dist/core/delegation/worker-execution-policy.d.ts +62 -0
  185. package/dist/core/delegation/worker-execution-policy.d.ts.map +1 -0
  186. package/dist/core/delegation/worker-execution-policy.js +160 -0
  187. package/dist/core/delegation/worker-execution-policy.js.map +1 -0
  188. package/dist/core/delegation/worker-lane-projection.d.ts +14 -0
  189. package/dist/core/delegation/worker-lane-projection.d.ts.map +1 -0
  190. package/dist/core/delegation/worker-lane-projection.js +100 -0
  191. package/dist/core/delegation/worker-lane-projection.js.map +1 -0
  192. package/dist/core/delegation/worker-lifecycle.d.ts +97 -0
  193. package/dist/core/delegation/worker-lifecycle.d.ts.map +1 -0
  194. package/dist/core/delegation/worker-lifecycle.js +339 -0
  195. package/dist/core/delegation/worker-lifecycle.js.map +1 -0
  196. package/dist/core/delegation/worker-notification-coordinator.d.ts +36 -0
  197. package/dist/core/delegation/worker-notification-coordinator.d.ts.map +1 -0
  198. package/dist/core/delegation/worker-notification-coordinator.js +90 -0
  199. package/dist/core/delegation/worker-notification-coordinator.js.map +1 -0
  200. package/dist/core/delegation/worker-profile-resolver.d.ts +55 -0
  201. package/dist/core/delegation/worker-profile-resolver.d.ts.map +1 -0
  202. package/dist/core/delegation/worker-profile-resolver.js +113 -0
  203. package/dist/core/delegation/worker-profile-resolver.js.map +1 -0
  204. package/dist/core/delegation/worker-runner.d.ts +25 -14
  205. package/dist/core/delegation/worker-runner.d.ts.map +1 -1
  206. package/dist/core/delegation/worker-runner.js +122 -53
  207. package/dist/core/delegation/worker-runner.js.map +1 -1
  208. package/dist/core/delegation/worker-task-label.d.ts +3 -0
  209. package/dist/core/delegation/worker-task-label.d.ts.map +1 -0
  210. package/dist/core/delegation/worker-task-label.js +11 -0
  211. package/dist/core/delegation/worker-task-label.js.map +1 -0
  212. package/dist/core/doctor.d.ts +8 -0
  213. package/dist/core/doctor.d.ts.map +1 -1
  214. package/dist/core/doctor.js +32 -1
  215. package/dist/core/doctor.js.map +1 -1
  216. package/dist/core/extensions/index.d.ts +1 -1
  217. package/dist/core/extensions/index.d.ts.map +1 -1
  218. package/dist/core/extensions/index.js.map +1 -1
  219. package/dist/core/extensions/loader.d.ts +5 -1
  220. package/dist/core/extensions/loader.d.ts.map +1 -1
  221. package/dist/core/extensions/loader.js +68 -13
  222. package/dist/core/extensions/loader.js.map +1 -1
  223. package/dist/core/extensions/runner.d.ts.map +1 -1
  224. package/dist/core/extensions/runner.js +7 -0
  225. package/dist/core/extensions/runner.js.map +1 -1
  226. package/dist/core/extensions/storage.d.ts +8 -0
  227. package/dist/core/extensions/storage.d.ts.map +1 -0
  228. package/dist/core/extensions/storage.js +28 -0
  229. package/dist/core/extensions/storage.js.map +1 -0
  230. package/dist/core/extensions/types.d.ts +101 -1
  231. package/dist/core/extensions/types.d.ts.map +1 -1
  232. package/dist/core/extensions/types.js.map +1 -1
  233. package/dist/core/failure-corpus.d.ts +1 -15
  234. package/dist/core/failure-corpus.d.ts.map +1 -1
  235. package/dist/core/failure-corpus.js +9 -37
  236. package/dist/core/failure-corpus.js.map +1 -1
  237. package/dist/core/foreground-recovery-controller.d.ts +52 -0
  238. package/dist/core/foreground-recovery-controller.d.ts.map +1 -0
  239. package/dist/core/foreground-recovery-controller.js +111 -0
  240. package/dist/core/foreground-recovery-controller.js.map +1 -0
  241. package/dist/core/gateways/channel-provider.d.ts +3 -0
  242. package/dist/core/gateways/channel-provider.d.ts.map +1 -1
  243. package/dist/core/gateways/channel-provider.js +31 -6
  244. package/dist/core/gateways/channel-provider.js.map +1 -1
  245. package/dist/core/goal-loop-controller.d.ts +17 -1
  246. package/dist/core/goal-loop-controller.d.ts.map +1 -1
  247. package/dist/core/goal-loop-controller.js +79 -11
  248. package/dist/core/goal-loop-controller.js.map +1 -1
  249. package/dist/core/goals/goal-acceptance.d.ts +11 -0
  250. package/dist/core/goals/goal-acceptance.d.ts.map +1 -0
  251. package/dist/core/goals/goal-acceptance.js +25 -0
  252. package/dist/core/goals/goal-acceptance.js.map +1 -0
  253. package/dist/core/goals/goal-auto-continue-controller.d.ts +27 -0
  254. package/dist/core/goals/goal-auto-continue-controller.d.ts.map +1 -0
  255. package/dist/core/goals/goal-auto-continue-controller.js +93 -0
  256. package/dist/core/goals/goal-auto-continue-controller.js.map +1 -0
  257. package/dist/core/goals/goal-continuation-controller.d.ts +48 -2
  258. package/dist/core/goals/goal-continuation-controller.d.ts.map +1 -1
  259. package/dist/core/goals/goal-continuation-controller.js +87 -0
  260. package/dist/core/goals/goal-continuation-controller.js.map +1 -1
  261. package/dist/core/goals/goal-continuation-defaults.d.ts +39 -0
  262. package/dist/core/goals/goal-continuation-defaults.d.ts.map +1 -1
  263. package/dist/core/goals/goal-continuation-defaults.js +42 -0
  264. package/dist/core/goals/goal-continuation-defaults.js.map +1 -1
  265. package/dist/core/goals/goal-continuation-prompt.d.ts +5 -1
  266. package/dist/core/goals/goal-continuation-prompt.d.ts.map +1 -1
  267. package/dist/core/goals/goal-continuation-prompt.js +56 -15
  268. package/dist/core/goals/goal-continuation-prompt.js.map +1 -1
  269. package/dist/core/goals/goal-lifecycle.d.ts +20 -0
  270. package/dist/core/goals/goal-lifecycle.d.ts.map +1 -0
  271. package/dist/core/goals/goal-lifecycle.js +17 -0
  272. package/dist/core/goals/goal-lifecycle.js.map +1 -0
  273. package/dist/core/goals/goal-runtime-snapshot.d.ts +105 -4
  274. package/dist/core/goals/goal-runtime-snapshot.d.ts.map +1 -1
  275. package/dist/core/goals/goal-runtime-snapshot.js +91 -6
  276. package/dist/core/goals/goal-runtime-snapshot.js.map +1 -1
  277. package/dist/core/goals/goal-state.d.ts +90 -1
  278. package/dist/core/goals/goal-state.d.ts.map +1 -1
  279. package/dist/core/goals/goal-state.js +78 -15
  280. package/dist/core/goals/goal-state.js.map +1 -1
  281. package/dist/core/goals/goal-tool-core.d.ts +58 -2
  282. package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
  283. package/dist/core/goals/goal-tool-core.js +82 -6
  284. package/dist/core/goals/goal-tool-core.js.map +1 -1
  285. package/dist/core/goals/session-goal-state.d.ts +13 -6
  286. package/dist/core/goals/session-goal-state.d.ts.map +1 -1
  287. package/dist/core/goals/session-goal-state.js +20 -28
  288. package/dist/core/goals/session-goal-state.js.map +1 -1
  289. package/dist/core/human-input.d.ts +100 -0
  290. package/dist/core/human-input.d.ts.map +1 -0
  291. package/dist/core/human-input.js +362 -0
  292. package/dist/core/human-input.js.map +1 -0
  293. package/dist/core/index.d.ts +9 -1
  294. package/dist/core/index.d.ts.map +1 -1
  295. package/dist/core/index.js +9 -1
  296. package/dist/core/index.js.map +1 -1
  297. package/dist/core/{telemetry.d.ts → install-telemetry.d.ts} +1 -1
  298. package/dist/core/install-telemetry.d.ts.map +1 -0
  299. package/dist/core/{telemetry.js → install-telemetry.js} +1 -1
  300. package/dist/core/install-telemetry.js.map +1 -0
  301. package/dist/core/keybindings.d.ts +27 -1
  302. package/dist/core/keybindings.d.ts.map +1 -1
  303. package/dist/core/keybindings.js +34 -8
  304. package/dist/core/keybindings.js.map +1 -1
  305. package/dist/core/learning/learning-audit.d.ts +2 -4
  306. package/dist/core/learning/learning-audit.d.ts.map +1 -1
  307. package/dist/core/learning/learning-audit.js +9 -29
  308. package/dist/core/learning/learning-audit.js.map +1 -1
  309. package/dist/core/learning/learning-gate.d.ts.map +1 -1
  310. package/dist/core/learning/learning-gate.js +1 -6
  311. package/dist/core/learning/learning-gate.js.map +1 -1
  312. package/dist/core/learning/observation-store.d.ts +12 -3
  313. package/dist/core/learning/observation-store.d.ts.map +1 -1
  314. package/dist/core/learning/observation-store.js +30 -15
  315. package/dist/core/learning/observation-store.js.map +1 -1
  316. package/dist/core/learning/session-learning-decision.d.ts +2 -4
  317. package/dist/core/learning/session-learning-decision.d.ts.map +1 -1
  318. package/dist/core/learning/session-learning-decision.js +9 -29
  319. package/dist/core/learning/session-learning-decision.js.map +1 -1
  320. package/dist/core/learning/skill-curator.d.ts +5 -1
  321. package/dist/core/learning/skill-curator.d.ts.map +1 -1
  322. package/dist/core/learning/skill-curator.js +21 -19
  323. package/dist/core/learning/skill-curator.js.map +1 -1
  324. package/dist/core/local-runtime-controller.d.ts +65 -3
  325. package/dist/core/local-runtime-controller.d.ts.map +1 -1
  326. package/dist/core/local-runtime-controller.js +186 -26
  327. package/dist/core/local-runtime-controller.js.map +1 -1
  328. package/dist/core/memory/providers/file-store.d.ts +1 -1
  329. package/dist/core/memory/providers/file-store.d.ts.map +1 -1
  330. package/dist/core/memory/providers/file-store.js +6 -6
  331. package/dist/core/memory/providers/file-store.js.map +1 -1
  332. package/dist/core/memory-controller.d.ts.map +1 -1
  333. package/dist/core/memory-controller.js +4 -4
  334. package/dist/core/memory-controller.js.map +1 -1
  335. package/dist/core/model-capability.d.ts +34 -0
  336. package/dist/core/model-capability.d.ts.map +1 -1
  337. package/dist/core/model-capability.js +43 -1
  338. package/dist/core/model-capability.js.map +1 -1
  339. package/dist/core/model-resolver.d.ts +1 -1
  340. package/dist/core/model-resolver.d.ts.map +1 -1
  341. package/dist/core/model-resolver.js +6 -9
  342. package/dist/core/model-resolver.js.map +1 -1
  343. package/dist/core/model-router/intent-classifier.d.ts +1 -4
  344. package/dist/core/model-router/intent-classifier.d.ts.map +1 -1
  345. package/dist/core/model-router/intent-classifier.js +1 -156
  346. package/dist/core/model-router/intent-classifier.js.map +1 -1
  347. package/dist/core/model-router/tool-escalation.d.ts +15 -0
  348. package/dist/core/model-router/tool-escalation.d.ts.map +1 -1
  349. package/dist/core/model-router/tool-escalation.js +23 -1
  350. package/dist/core/model-router/tool-escalation.js.map +1 -1
  351. package/dist/core/model-router-controller.d.ts +34 -7
  352. package/dist/core/model-router-controller.d.ts.map +1 -1
  353. package/dist/core/model-router-controller.js +95 -16
  354. package/dist/core/model-router-controller.js.map +1 -1
  355. package/dist/core/model-selection-controller.d.ts +4 -4
  356. package/dist/core/model-selection-controller.d.ts.map +1 -1
  357. package/dist/core/model-selection-controller.js.map +1 -1
  358. package/dist/core/model-tool-protocol.d.ts +23 -0
  359. package/dist/core/model-tool-protocol.d.ts.map +1 -0
  360. package/dist/core/model-tool-protocol.js +43 -0
  361. package/dist/core/model-tool-protocol.js.map +1 -0
  362. package/dist/core/models/adaptation-store.d.ts +10 -5
  363. package/dist/core/models/adaptation-store.d.ts.map +1 -1
  364. package/dist/core/models/adaptation-store.js +129 -81
  365. package/dist/core/models/adaptation-store.js.map +1 -1
  366. package/dist/core/models/default-model-suggestions.d.ts.map +1 -1
  367. package/dist/core/models/default-model-suggestions.js +17 -0
  368. package/dist/core/models/default-model-suggestions.js.map +1 -1
  369. package/dist/core/models/fitness-store.d.ts +4 -11
  370. package/dist/core/models/fitness-store.d.ts.map +1 -1
  371. package/dist/core/models/fitness-store.js +87 -51
  372. package/dist/core/models/fitness-store.js.map +1 -1
  373. package/dist/core/models/host-state-store.d.ts +34 -0
  374. package/dist/core/models/host-state-store.d.ts.map +1 -0
  375. package/dist/core/models/host-state-store.js +86 -0
  376. package/dist/core/models/host-state-store.js.map +1 -0
  377. package/dist/core/models/llamacpp-runtime.d.ts +180 -0
  378. package/dist/core/models/llamacpp-runtime.d.ts.map +1 -0
  379. package/dist/core/models/llamacpp-runtime.js +475 -0
  380. package/dist/core/models/llamacpp-runtime.js.map +1 -0
  381. package/dist/core/models/local-registration.d.ts +40 -0
  382. package/dist/core/models/local-registration.d.ts.map +1 -1
  383. package/dist/core/models/local-registration.js +94 -5
  384. package/dist/core/models/local-registration.js.map +1 -1
  385. package/dist/core/models/local-runtime.d.ts.map +1 -1
  386. package/dist/core/models/local-runtime.js +6 -5
  387. package/dist/core/models/local-runtime.js.map +1 -1
  388. package/dist/core/models/model-ref.d.ts +7 -0
  389. package/dist/core/models/model-ref.d.ts.map +1 -1
  390. package/dist/core/models/model-ref.js +26 -0
  391. package/dist/core/models/model-ref.js.map +1 -1
  392. package/dist/core/models/needle-runtime.d.ts +257 -0
  393. package/dist/core/models/needle-runtime.d.ts.map +1 -0
  394. package/dist/core/models/needle-runtime.js +519 -0
  395. package/dist/core/models/needle-runtime.js.map +1 -0
  396. package/dist/core/models/prism-llamacpp-lifecycle.d.ts +89 -0
  397. package/dist/core/models/prism-llamacpp-lifecycle.d.ts.map +1 -0
  398. package/dist/core/models/prism-llamacpp-lifecycle.js +121 -0
  399. package/dist/core/models/prism-llamacpp-lifecycle.js.map +1 -0
  400. package/dist/core/orchestration/agent-resume.d.ts +24 -0
  401. package/dist/core/orchestration/agent-resume.d.ts.map +1 -0
  402. package/dist/core/orchestration/agent-resume.js +90 -0
  403. package/dist/core/orchestration/agent-resume.js.map +1 -0
  404. package/dist/core/orchestration/capability-gateway.d.ts +61 -0
  405. package/dist/core/orchestration/capability-gateway.d.ts.map +1 -0
  406. package/dist/core/orchestration/capability-gateway.js +152 -0
  407. package/dist/core/orchestration/capability-gateway.js.map +1 -0
  408. package/dist/core/orchestration/contracts.d.ts +343 -0
  409. package/dist/core/orchestration/contracts.d.ts.map +1 -0
  410. package/dist/core/orchestration/contracts.js +135 -0
  411. package/dist/core/orchestration/contracts.js.map +1 -0
  412. package/dist/core/orchestration/delegation-ledger.d.ts +56 -0
  413. package/dist/core/orchestration/delegation-ledger.d.ts.map +1 -0
  414. package/dist/core/orchestration/delegation-ledger.js +197 -0
  415. package/dist/core/orchestration/delegation-ledger.js.map +1 -0
  416. package/dist/core/orchestration/event-store.d.ts +83 -0
  417. package/dist/core/orchestration/event-store.d.ts.map +1 -0
  418. package/dist/core/orchestration/event-store.js +560 -0
  419. package/dist/core/orchestration/event-store.js.map +1 -0
  420. package/dist/core/orchestration/lane-tool-manifests.d.ts +9 -0
  421. package/dist/core/orchestration/lane-tool-manifests.d.ts.map +1 -0
  422. package/dist/core/orchestration/lane-tool-manifests.js +37 -0
  423. package/dist/core/orchestration/lane-tool-manifests.js.map +1 -0
  424. package/dist/core/orchestration/model-binding.d.ts +15 -0
  425. package/dist/core/orchestration/model-binding.d.ts.map +1 -0
  426. package/dist/core/orchestration/model-binding.js +29 -0
  427. package/dist/core/orchestration/model-binding.js.map +1 -0
  428. package/dist/core/orchestration/policy-compiler.d.ts +49 -0
  429. package/dist/core/orchestration/policy-compiler.d.ts.map +1 -0
  430. package/dist/core/orchestration/policy-compiler.js +191 -0
  431. package/dist/core/orchestration/policy-compiler.js.map +1 -0
  432. package/dist/core/orchestration/policy-gate.d.ts +18 -0
  433. package/dist/core/orchestration/policy-gate.d.ts.map +1 -0
  434. package/dist/core/orchestration/policy-gate.js +24 -0
  435. package/dist/core/orchestration/policy-gate.js.map +1 -0
  436. package/dist/core/orchestration/profile-registry.d.ts +51 -0
  437. package/dist/core/orchestration/profile-registry.d.ts.map +1 -0
  438. package/dist/core/orchestration/profile-registry.js +393 -0
  439. package/dist/core/orchestration/profile-registry.js.map +1 -0
  440. package/dist/core/orchestration/profile-store.d.ts +31 -0
  441. package/dist/core/orchestration/profile-store.d.ts.map +1 -0
  442. package/dist/core/orchestration/profile-store.js +136 -0
  443. package/dist/core/orchestration/profile-store.js.map +1 -0
  444. package/dist/core/orchestration/risk-budget.d.ts +9 -0
  445. package/dist/core/orchestration/risk-budget.d.ts.map +1 -0
  446. package/dist/core/orchestration/risk-budget.js +48 -0
  447. package/dist/core/orchestration/risk-budget.js.map +1 -0
  448. package/dist/core/orchestration/task-runtime.d.ts +168 -0
  449. package/dist/core/orchestration/task-runtime.d.ts.map +1 -0
  450. package/dist/core/orchestration/task-runtime.js +1600 -0
  451. package/dist/core/orchestration/task-runtime.js.map +1 -0
  452. package/dist/core/orchestration/work-state-projection.d.ts +32 -0
  453. package/dist/core/orchestration/work-state-projection.d.ts.map +1 -0
  454. package/dist/core/orchestration/work-state-projection.js +116 -0
  455. package/dist/core/orchestration/work-state-projection.js.map +1 -0
  456. package/dist/core/orchestration/worker-execution-contract.d.ts +21 -0
  457. package/dist/core/orchestration/worker-execution-contract.d.ts.map +1 -0
  458. package/dist/core/orchestration/worker-execution-contract.js +179 -0
  459. package/dist/core/orchestration/worker-execution-contract.js.map +1 -0
  460. package/dist/core/orchestration/worker-result-adapter.d.ts +21 -0
  461. package/dist/core/orchestration/worker-result-adapter.d.ts.map +1 -0
  462. package/dist/core/orchestration/worker-result-adapter.js +109 -0
  463. package/dist/core/orchestration/worker-result-adapter.js.map +1 -0
  464. package/dist/core/package-manager.d.ts.map +1 -1
  465. package/dist/core/package-manager.js +11 -10
  466. package/dist/core/package-manager.js.map +1 -1
  467. package/dist/core/process-identity.d.ts +11 -0
  468. package/dist/core/process-identity.d.ts.map +1 -0
  469. package/dist/core/process-identity.js +26 -0
  470. package/dist/core/process-identity.js.map +1 -0
  471. package/dist/core/process-matrix/codes.d.ts +83 -0
  472. package/dist/core/process-matrix/codes.d.ts.map +1 -0
  473. package/dist/core/process-matrix/codes.js +15 -0
  474. package/dist/core/process-matrix/codes.js.map +1 -0
  475. package/dist/core/process-matrix/resume-launcher.d.ts +26 -0
  476. package/dist/core/process-matrix/resume-launcher.d.ts.map +1 -0
  477. package/dist/core/process-matrix/resume-launcher.js +62 -0
  478. package/dist/core/process-matrix/resume-launcher.js.map +1 -0
  479. package/dist/core/process-matrix/runtime.d.ts +78 -0
  480. package/dist/core/process-matrix/runtime.d.ts.map +1 -0
  481. package/dist/core/process-matrix/runtime.js +633 -0
  482. package/dist/core/process-matrix/runtime.js.map +1 -0
  483. package/dist/core/process-matrix/store.d.ts +25 -0
  484. package/dist/core/process-matrix/store.d.ts.map +1 -0
  485. package/dist/core/process-matrix/store.js +189 -0
  486. package/dist/core/process-matrix/store.js.map +1 -0
  487. package/dist/core/process-matrix/supervisor.d.ts +75 -0
  488. package/dist/core/process-matrix/supervisor.d.ts.map +1 -0
  489. package/dist/core/process-matrix/supervisor.js +161 -0
  490. package/dist/core/process-matrix/supervisor.js.map +1 -0
  491. package/dist/core/profile-registry.d.ts +7 -0
  492. package/dist/core/profile-registry.d.ts.map +1 -1
  493. package/dist/core/profile-registry.js +25 -7
  494. package/dist/core/profile-registry.js.map +1 -1
  495. package/dist/core/python-runtime.d.ts.map +1 -1
  496. package/dist/core/python-runtime.js +3 -3
  497. package/dist/core/python-runtime.js.map +1 -1
  498. package/dist/core/reflection-controller.d.ts +33 -9
  499. package/dist/core/reflection-controller.d.ts.map +1 -1
  500. package/dist/core/reflection-controller.js +217 -127
  501. package/dist/core/reflection-controller.js.map +1 -1
  502. package/dist/core/reload-blockers.d.ts +36 -0
  503. package/dist/core/reload-blockers.d.ts.map +1 -1
  504. package/dist/core/reload-blockers.js +50 -8
  505. package/dist/core/reload-blockers.js.map +1 -1
  506. package/dist/core/research/evidence-bundle.d.ts.map +1 -1
  507. package/dist/core/research/evidence-bundle.js +1 -6
  508. package/dist/core/research/evidence-bundle.js.map +1 -1
  509. package/dist/core/research/lane-model-resolver.d.ts +33 -0
  510. package/dist/core/research/lane-model-resolver.d.ts.map +1 -0
  511. package/dist/core/research/lane-model-resolver.js +65 -0
  512. package/dist/core/research/lane-model-resolver.js.map +1 -0
  513. package/dist/core/research/model-fitness-controller.d.ts +38 -0
  514. package/dist/core/research/model-fitness-controller.d.ts.map +1 -0
  515. package/dist/core/research/model-fitness-controller.js +106 -0
  516. package/dist/core/research/model-fitness-controller.js.map +1 -0
  517. package/dist/core/research/model-fitness.d.ts +1 -1
  518. package/dist/core/research/model-fitness.d.ts.map +1 -1
  519. package/dist/core/research/model-fitness.js +4 -4
  520. package/dist/core/research/model-fitness.js.map +1 -1
  521. package/dist/core/research/research-gate.d.ts.map +1 -1
  522. package/dist/core/research/research-gate.js +7 -7
  523. package/dist/core/research/research-gate.js.map +1 -1
  524. package/dist/core/research/research-lane-controller.d.ts +62 -0
  525. package/dist/core/research/research-lane-controller.d.ts.map +1 -0
  526. package/dist/core/research/research-lane-controller.js +292 -0
  527. package/dist/core/research/research-lane-controller.js.map +1 -0
  528. package/dist/core/research/session-evidence-bundle.d.ts +2 -4
  529. package/dist/core/research/session-evidence-bundle.d.ts.map +1 -1
  530. package/dist/core/research/session-evidence-bundle.js +10 -46
  531. package/dist/core/research/session-evidence-bundle.js.map +1 -1
  532. package/dist/core/resource-loader.d.ts.map +1 -1
  533. package/dist/core/resource-loader.js +15 -9
  534. package/dist/core/resource-loader.js.map +1 -1
  535. package/dist/core/risk-classifier.d.ts +6 -0
  536. package/dist/core/risk-classifier.d.ts.map +1 -0
  537. package/dist/core/risk-classifier.js +225 -0
  538. package/dist/core/risk-classifier.js.map +1 -0
  539. package/dist/core/runtime-builder.d.ts +76 -17
  540. package/dist/core/runtime-builder.d.ts.map +1 -1
  541. package/dist/core/runtime-builder.js +367 -124
  542. package/dist/core/runtime-builder.js.map +1 -1
  543. package/dist/core/scout-controller.d.ts +6 -0
  544. package/dist/core/scout-controller.d.ts.map +1 -1
  545. package/dist/core/scout-controller.js +66 -52
  546. package/dist/core/scout-controller.js.map +1 -1
  547. package/dist/core/sdk.d.ts +6 -3
  548. package/dist/core/sdk.d.ts.map +1 -1
  549. package/dist/core/sdk.js +64 -27
  550. package/dist/core/sdk.js.map +1 -1
  551. package/dist/core/session-analytics.d.ts.map +1 -1
  552. package/dist/core/session-analytics.js +28 -1
  553. package/dist/core/session-analytics.js.map +1 -1
  554. package/dist/core/session-image-store.d.ts +33 -0
  555. package/dist/core/session-image-store.d.ts.map +1 -0
  556. package/dist/core/session-image-store.js +283 -0
  557. package/dist/core/session-image-store.js.map +1 -0
  558. package/dist/core/session-role.d.ts +33 -0
  559. package/dist/core/session-role.d.ts.map +1 -0
  560. package/dist/core/session-role.js +58 -0
  561. package/dist/core/session-role.js.map +1 -0
  562. package/dist/core/session-snapshot.d.ts +34 -0
  563. package/dist/core/session-snapshot.d.ts.map +1 -0
  564. package/dist/core/session-snapshot.js +68 -0
  565. package/dist/core/session-snapshot.js.map +1 -0
  566. package/dist/core/session-supervision-runtime.d.ts +31 -0
  567. package/dist/core/session-supervision-runtime.d.ts.map +1 -0
  568. package/dist/core/session-supervision-runtime.js +97 -0
  569. package/dist/core/session-supervision-runtime.js.map +1 -0
  570. package/dist/core/settings-manager.d.ts +51 -7
  571. package/dist/core/settings-manager.d.ts.map +1 -1
  572. package/dist/core/settings-manager.js +168 -82
  573. package/dist/core/settings-manager.js.map +1 -1
  574. package/dist/core/system-prompt-builder.d.ts +12 -0
  575. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  576. package/dist/core/system-prompt-builder.js +6 -0
  577. package/dist/core/system-prompt-builder.js.map +1 -1
  578. package/dist/core/system-prompt.d.ts +14 -1
  579. package/dist/core/system-prompt.d.ts.map +1 -1
  580. package/dist/core/system-prompt.js +20 -3
  581. package/dist/core/system-prompt.js.map +1 -1
  582. package/dist/core/tasks/session-task-state.d.ts +13 -6
  583. package/dist/core/tasks/session-task-state.d.ts.map +1 -1
  584. package/dist/core/tasks/session-task-state.js +20 -22
  585. package/dist/core/tasks/session-task-state.js.map +1 -1
  586. package/dist/core/tasks/task-contract-monitor.d.ts +45 -0
  587. package/dist/core/tasks/task-contract-monitor.d.ts.map +1 -0
  588. package/dist/core/tasks/task-contract-monitor.js +56 -0
  589. package/dist/core/tasks/task-contract-monitor.js.map +1 -0
  590. package/dist/core/tasks/task-projection.d.ts +20 -0
  591. package/dist/core/tasks/task-projection.d.ts.map +1 -0
  592. package/dist/core/tasks/task-projection.js +47 -0
  593. package/dist/core/tasks/task-projection.js.map +1 -0
  594. package/dist/core/tasks/task-state.d.ts +12 -0
  595. package/dist/core/tasks/task-state.d.ts.map +1 -1
  596. package/dist/core/tasks/task-state.js +56 -12
  597. package/dist/core/tasks/task-state.js.map +1 -1
  598. package/dist/core/tool-capability-policy.d.ts +13 -0
  599. package/dist/core/tool-capability-policy.d.ts.map +1 -0
  600. package/dist/core/tool-capability-policy.js +85 -0
  601. package/dist/core/tool-capability-policy.js.map +1 -0
  602. package/dist/core/tool-gate-controller.d.ts.map +1 -1
  603. package/dist/core/tool-gate-controller.js +5 -0
  604. package/dist/core/tool-gate-controller.js.map +1 -1
  605. package/dist/core/tool-protocol-controller.d.ts +91 -0
  606. package/dist/core/tool-protocol-controller.d.ts.map +1 -0
  607. package/dist/core/tool-protocol-controller.js +512 -0
  608. package/dist/core/tool-protocol-controller.js.map +1 -0
  609. package/dist/core/tool-recovery-log-records.d.ts +6 -0
  610. package/dist/core/tool-recovery-log-records.d.ts.map +1 -1
  611. package/dist/core/tool-recovery-log-records.js +13 -31
  612. package/dist/core/tool-recovery-log-records.js.map +1 -1
  613. package/dist/core/tool-selection/promotion.d.ts +54 -0
  614. package/dist/core/tool-selection/promotion.d.ts.map +1 -0
  615. package/dist/core/tool-selection/promotion.js +81 -0
  616. package/dist/core/tool-selection/promotion.js.map +1 -0
  617. package/dist/core/tool-selection/tool-performance-store.d.ts +39 -6
  618. package/dist/core/tool-selection/tool-performance-store.d.ts.map +1 -1
  619. package/dist/core/tool-selection/tool-performance-store.js +153 -110
  620. package/dist/core/tool-selection/tool-performance-store.js.map +1 -1
  621. package/dist/core/tool-selection/tool-selection-controller.d.ts +45 -0
  622. package/dist/core/tool-selection/tool-selection-controller.d.ts.map +1 -1
  623. package/dist/core/tool-selection/tool-selection-controller.js +96 -0
  624. package/dist/core/tool-selection/tool-selection-controller.js.map +1 -1
  625. package/dist/core/tools/ask-question.d.ts +124 -0
  626. package/dist/core/tools/ask-question.d.ts.map +1 -0
  627. package/dist/core/tools/ask-question.js +693 -0
  628. package/dist/core/tools/ask-question.js.map +1 -0
  629. package/dist/core/tools/bash.d.ts +18 -0
  630. package/dist/core/tools/bash.d.ts.map +1 -1
  631. package/dist/core/tools/bash.js +95 -16
  632. package/dist/core/tools/bash.js.map +1 -1
  633. package/dist/core/tools/delegate-status.d.ts +44 -2
  634. package/dist/core/tools/delegate-status.d.ts.map +1 -1
  635. package/dist/core/tools/delegate-status.js +214 -19
  636. package/dist/core/tools/delegate-status.js.map +1 -1
  637. package/dist/core/tools/delegate.d.ts +15 -13
  638. package/dist/core/tools/delegate.d.ts.map +1 -1
  639. package/dist/core/tools/delegate.js +124 -22
  640. package/dist/core/tools/delegate.js.map +1 -1
  641. package/dist/core/tools/extensionify.d.ts.map +1 -1
  642. package/dist/core/tools/extensionify.js +1 -0
  643. package/dist/core/tools/extensionify.js.map +1 -1
  644. package/dist/core/tools/goal.d.ts +94 -3
  645. package/dist/core/tools/goal.d.ts.map +1 -1
  646. package/dist/core/tools/goal.js +165 -15
  647. package/dist/core/tools/goal.js.map +1 -1
  648. package/dist/core/tools/model-fitness.d.ts +7 -0
  649. package/dist/core/tools/model-fitness.d.ts.map +1 -1
  650. package/dist/core/tools/model-fitness.js +4 -4
  651. package/dist/core/tools/model-fitness.js.map +1 -1
  652. package/dist/core/tools/orchestration-panel.d.ts +43 -0
  653. package/dist/core/tools/orchestration-panel.d.ts.map +1 -0
  654. package/dist/core/tools/orchestration-panel.js +168 -0
  655. package/dist/core/tools/orchestration-panel.js.map +1 -0
  656. package/dist/core/tools/run-process.d.ts +26 -0
  657. package/dist/core/tools/run-process.d.ts.map +1 -0
  658. package/dist/core/tools/run-process.js +138 -0
  659. package/dist/core/tools/run-process.js.map +1 -0
  660. package/dist/core/tools/shell-contract-router.d.ts +6 -1
  661. package/dist/core/tools/shell-contract-router.d.ts.map +1 -1
  662. package/dist/core/tools/shell-contract-router.js +23 -2
  663. package/dist/core/tools/shell-contract-router.js.map +1 -1
  664. package/dist/core/tools/shell-session.d.ts +89 -0
  665. package/dist/core/tools/shell-session.d.ts.map +1 -0
  666. package/dist/core/tools/shell-session.js +432 -0
  667. package/dist/core/tools/shell-session.js.map +1 -0
  668. package/dist/core/tools/task-steps.d.ts +6 -0
  669. package/dist/core/tools/task-steps.d.ts.map +1 -1
  670. package/dist/core/tools/task-steps.js +165 -8
  671. package/dist/core/tools/task-steps.js.map +1 -1
  672. package/dist/core/tools/tmux-dispatch.d.ts +84 -0
  673. package/dist/core/tools/tmux-dispatch.d.ts.map +1 -0
  674. package/dist/core/tools/tmux-dispatch.js +91 -0
  675. package/dist/core/tools/tmux-dispatch.js.map +1 -0
  676. package/dist/core/tools/windows-shell-engine.d.ts +42 -0
  677. package/dist/core/tools/windows-shell-engine.d.ts.map +1 -0
  678. package/dist/core/tools/windows-shell-engine.js +151 -0
  679. package/dist/core/tools/windows-shell-engine.js.map +1 -0
  680. package/dist/core/tools/windows-shell-state.d.ts +40 -0
  681. package/dist/core/tools/windows-shell-state.d.ts.map +1 -0
  682. package/dist/core/tools/windows-shell-state.js +59 -0
  683. package/dist/core/tools/windows-shell-state.js.map +1 -0
  684. package/dist/core/tools/worktree-sync.d.ts +24 -0
  685. package/dist/core/tools/worktree-sync.d.ts.map +1 -0
  686. package/dist/core/tools/worktree-sync.js +338 -0
  687. package/dist/core/tools/worktree-sync.js.map +1 -0
  688. package/dist/core/trust-manager.d.ts +4 -1
  689. package/dist/core/trust-manager.d.ts.map +1 -1
  690. package/dist/core/trust-manager.js +20 -2
  691. package/dist/core/trust-manager.js.map +1 -1
  692. package/dist/core/util/atomic-file.d.ts +55 -0
  693. package/dist/core/util/atomic-file.d.ts.map +1 -0
  694. package/dist/core/util/atomic-file.js +255 -0
  695. package/dist/core/util/atomic-file.js.map +1 -0
  696. package/dist/core/util/bounded-jsonl.d.ts +10 -0
  697. package/dist/core/util/bounded-jsonl.d.ts.map +1 -0
  698. package/dist/core/util/bounded-jsonl.js +75 -0
  699. package/dist/core/util/bounded-jsonl.js.map +1 -0
  700. package/dist/core/util/minimatch-cache.d.ts +33 -0
  701. package/dist/core/util/minimatch-cache.d.ts.map +1 -0
  702. package/dist/core/util/minimatch-cache.js +0 -0
  703. package/dist/core/util/minimatch-cache.js.map +1 -0
  704. package/dist/core/util/value-guards.d.ts +5 -0
  705. package/dist/core/util/value-guards.d.ts.map +1 -0
  706. package/dist/core/util/value-guards.js +12 -0
  707. package/dist/core/util/value-guards.js.map +1 -0
  708. package/dist/core/worktree-sync/codes.d.ts +227 -0
  709. package/dist/core/worktree-sync/codes.d.ts.map +1 -0
  710. package/dist/core/worktree-sync/codes.js +14 -0
  711. package/dist/core/worktree-sync/codes.js.map +1 -0
  712. package/dist/core/worktree-sync/git-engine.d.ts +156 -0
  713. package/dist/core/worktree-sync/git-engine.d.ts.map +1 -0
  714. package/dist/core/worktree-sync/git-engine.js +1191 -0
  715. package/dist/core/worktree-sync/git-engine.js.map +1 -0
  716. package/dist/core/worktree-sync/lane-gate.d.ts +75 -0
  717. package/dist/core/worktree-sync/lane-gate.d.ts.map +1 -0
  718. package/dist/core/worktree-sync/lane-gate.js +360 -0
  719. package/dist/core/worktree-sync/lane-gate.js.map +1 -0
  720. package/dist/core/worktree-sync/runtime.d.ts +47 -0
  721. package/dist/core/worktree-sync/runtime.d.ts.map +1 -0
  722. package/dist/core/worktree-sync/runtime.js +96 -0
  723. package/dist/core/worktree-sync/runtime.js.map +1 -0
  724. package/dist/core/worktree-sync/store.d.ts +69 -0
  725. package/dist/core/worktree-sync/store.d.ts.map +1 -0
  726. package/dist/core/worktree-sync/store.js +252 -0
  727. package/dist/core/worktree-sync/store.js.map +1 -0
  728. package/dist/core/worktree-sync/watcher.d.ts +29 -0
  729. package/dist/core/worktree-sync/watcher.d.ts.map +1 -0
  730. package/dist/core/worktree-sync/watcher.js +93 -0
  731. package/dist/core/worktree-sync/watcher.js.map +1 -0
  732. package/dist/index.d.ts +13 -3
  733. package/dist/index.d.ts.map +1 -1
  734. package/dist/index.js +12 -2
  735. package/dist/index.js.map +1 -1
  736. package/dist/main.d.ts.map +1 -1
  737. package/dist/main.js +179 -1
  738. package/dist/main.js.map +1 -1
  739. package/dist/migrations.d.ts +7 -0
  740. package/dist/migrations.d.ts.map +1 -1
  741. package/dist/migrations.js +197 -1
  742. package/dist/migrations.js.map +1 -1
  743. package/dist/modes/interactive/auto-learn-controller.d.ts +16 -1
  744. package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
  745. package/dist/modes/interactive/auto-learn-controller.js +50 -8
  746. package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
  747. package/dist/modes/interactive/clipboard-input.d.ts +7 -1
  748. package/dist/modes/interactive/clipboard-input.d.ts.map +1 -1
  749. package/dist/modes/interactive/clipboard-input.js +44 -6
  750. package/dist/modes/interactive/clipboard-input.js.map +1 -1
  751. package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
  752. package/dist/modes/interactive/components/profile-resource-editor.js +23 -1
  753. package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
  754. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  755. package/dist/modes/interactive/components/settings-selector.js +2 -2
  756. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  757. package/dist/modes/interactive/config-backup.d.ts.map +1 -1
  758. package/dist/modes/interactive/config-backup.js +5 -4
  759. package/dist/modes/interactive/config-backup.js.map +1 -1
  760. package/dist/modes/interactive/extension-ui-host.d.ts +14 -0
  761. package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
  762. package/dist/modes/interactive/extension-ui-host.js +76 -15
  763. package/dist/modes/interactive/extension-ui-host.js.map +1 -1
  764. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  765. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  766. package/dist/modes/interactive/interactive-mode.js +67 -1
  767. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  768. package/dist/modes/interactive/key-handlers.d.ts.map +1 -1
  769. package/dist/modes/interactive/key-handlers.js +1 -1
  770. package/dist/modes/interactive/key-handlers.js.map +1 -1
  771. package/dist/modes/interactive/local-model-commands.d.ts +43 -0
  772. package/dist/modes/interactive/local-model-commands.d.ts.map +1 -1
  773. package/dist/modes/interactive/local-model-commands.js +290 -3
  774. package/dist/modes/interactive/local-model-commands.js.map +1 -1
  775. package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
  776. package/dist/modes/interactive/profile-menu-controller.js +7 -7
  777. package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
  778. package/dist/modes/interactive/report-commands.d.ts.map +1 -1
  779. package/dist/modes/interactive/report-commands.js +21 -0
  780. package/dist/modes/interactive/report-commands.js.map +1 -1
  781. package/dist/modes/interactive/session-flow-commands.d.ts +17 -2
  782. package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
  783. package/dist/modes/interactive/session-flow-commands.js +74 -21
  784. package/dist/modes/interactive/session-flow-commands.js.map +1 -1
  785. package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
  786. package/dist/modes/interactive/session-io-commands.js +16 -6
  787. package/dist/modes/interactive/session-io-commands.js.map +1 -1
  788. package/dist/modes/interactive/session-replacement-errors.d.ts +7 -0
  789. package/dist/modes/interactive/session-replacement-errors.d.ts.map +1 -0
  790. package/dist/modes/interactive/session-replacement-errors.js +15 -0
  791. package/dist/modes/interactive/session-replacement-errors.js.map +1 -0
  792. package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
  793. package/dist/modes/interactive/startup-checks.js +1 -1
  794. package/dist/modes/interactive/startup-checks.js.map +1 -1
  795. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  796. package/dist/modes/rpc/rpc-mode.js +82 -23
  797. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  798. package/dist/modes/rpc/rpc-types.d.ts +15 -0
  799. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  800. package/dist/modes/rpc/rpc-types.js +138 -1
  801. package/dist/modes/rpc/rpc-types.js.map +1 -1
  802. package/dist/utils/clipboard-image.d.ts.map +1 -1
  803. package/dist/utils/clipboard-image.js +2 -13
  804. package/dist/utils/clipboard-image.js.map +1 -1
  805. package/dist/utils/clipboard-native.d.ts +1 -0
  806. package/dist/utils/clipboard-native.d.ts.map +1 -1
  807. package/dist/utils/clipboard-native.js.map +1 -1
  808. package/dist/utils/clipboard.d.ts +1 -0
  809. package/dist/utils/clipboard.d.ts.map +1 -1
  810. package/dist/utils/clipboard.js +52 -1
  811. package/dist/utils/clipboard.js.map +1 -1
  812. package/dist/utils/fs-watch.d.ts +11 -0
  813. package/dist/utils/fs-watch.d.ts.map +1 -1
  814. package/dist/utils/fs-watch.js +20 -2
  815. package/dist/utils/fs-watch.js.map +1 -1
  816. package/dist/utils/highlight-js-languages.d.ts +4 -0
  817. package/dist/utils/highlight-js-languages.d.ts.map +1 -0
  818. package/dist/utils/highlight-js-languages.js +573 -0
  819. package/dist/utils/highlight-js-languages.js.map +1 -0
  820. package/dist/utils/platform.d.ts +3 -0
  821. package/dist/utils/platform.d.ts.map +1 -0
  822. package/dist/utils/platform.js +8 -0
  823. package/dist/utils/platform.js.map +1 -0
  824. package/dist/utils/shell.d.ts +7 -1
  825. package/dist/utils/shell.d.ts.map +1 -1
  826. package/dist/utils/shell.js +21 -6
  827. package/dist/utils/shell.js.map +1 -1
  828. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  829. package/dist/utils/syntax-highlight.js +53 -5
  830. package/dist/utils/syntax-highlight.js.map +1 -1
  831. package/dist/utils/tools-manager.d.ts.map +1 -1
  832. package/dist/utils/tools-manager.js +112 -1
  833. package/dist/utils/tools-manager.js.map +1 -1
  834. package/dist/utils/work-directory.d.ts +11 -0
  835. package/dist/utils/work-directory.d.ts.map +1 -1
  836. package/dist/utils/work-directory.js +30 -9
  837. package/dist/utils/work-directory.js.map +1 -1
  838. package/docs/UI_POLISH_ORCHESTRATION.md +44 -0
  839. package/docs/UI_QUALITY_ORCHESTRATION.md +30 -0
  840. package/docs/docs.json +4 -0
  841. package/docs/extensions.md +62 -2
  842. package/docs/harness-architecture.md +156 -0
  843. package/docs/keybindings.md +11 -1
  844. package/docs/process-matrix.md +153 -0
  845. package/docs/rpc.md +45 -2
  846. package/docs/sessions.md +15 -0
  847. package/docs/settings.md +128 -11
  848. package/docs/task-steps.md +8 -0
  849. package/docs/telemetry.md +19 -0
  850. package/docs/tmux-agent-manager.md +92 -2
  851. package/docs/ui-reference/README.md +9 -0
  852. package/docs/ui-reference/orchestration-after.txt +7 -0
  853. package/docs/ui-reference/orchestration-before.txt +7 -0
  854. package/docs/ui-reference/orchestration-reference.txt +6 -0
  855. package/docs/usage.md +14 -2
  856. package/docs/windows.md +52 -3
  857. package/docs/work-directory.md +42 -2
  858. package/docs/worktree-sync.md +250 -0
  859. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  860. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  861. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  862. package/examples/extensions/question.ts +34 -245
  863. package/examples/extensions/questionnaire.ts +41 -407
  864. package/examples/extensions/sandbox/package-lock.json +2 -2
  865. package/examples/extensions/sandbox/package.json +1 -1
  866. package/examples/extensions/with-deps/package-lock.json +2 -2
  867. package/examples/extensions/with-deps/package.json +1 -1
  868. package/npm-shrinkwrap.json +12 -12
  869. package/package.json +10 -4
  870. package/dist/core/delegation/session-worker-result.d.ts +0 -16
  871. package/dist/core/delegation/session-worker-result.d.ts.map +0 -1
  872. package/dist/core/delegation/session-worker-result.js +0 -53
  873. package/dist/core/delegation/session-worker-result.js.map +0 -1
  874. package/dist/core/delegation/worker-result.d.ts +0 -15
  875. package/dist/core/delegation/worker-result.d.ts.map +0 -1
  876. package/dist/core/delegation/worker-result.js +0 -161
  877. package/dist/core/delegation/worker-result.js.map +0 -1
  878. package/dist/core/telemetry.d.ts.map +0 -1
  879. package/dist/core/telemetry.js.map +0 -1
  880. package/docs/integration-sweep-builder-blueprint-2026-07-09.md +0 -365
  881. package/docs/integration-sweep-resume-2026-07-09.md +0 -407
@@ -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
@@ -1 +1 @@
1
- {"version":3,"file":"tool-selection-controller.js","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 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.js","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"]}
@@ -0,0 +1,124 @@
1
+ import type { SessionManager } from "@caupulican/pi-agent-core/node";
2
+ import { type Component } from "@caupulican/pi-tui";
3
+ import { type Static, Type } from "typebox";
4
+ import type { ClipboardInputHost } from "../../modes/interactive/clipboard-input.ts";
5
+ import type { Theme } from "../../modes/interactive/theme/theme.ts";
6
+ import type { ArtifactStore } from "../context/context-artifacts.ts";
7
+ import { type HumanInputAnswer, type HumanInputAnswerImage, type HumanInputPresentationResult, type HumanInputQuestion, type HumanInputStopReason } from "../human-input.ts";
8
+ import type { KeybindingsManager } from "../keybindings.ts";
9
+ import type { SessionImageStore } from "../session-image-store.ts";
10
+ declare const askQuestionSchema: Type.TObject<{
11
+ questions: Type.TArray<Type.TObject<{
12
+ id: Type.TString;
13
+ header: Type.TString;
14
+ question: Type.TString;
15
+ options: Type.TArray<Type.TObject<{
16
+ label: Type.TString;
17
+ description: Type.TString;
18
+ }>>;
19
+ multiSelect: Type.TOptional<Type.TBoolean>;
20
+ }>>;
21
+ }>;
22
+ export type AskQuestionToolInput = Static<typeof askQuestionSchema>;
23
+ export type AskQuestion = HumanInputQuestion;
24
+ export type AskQuestionAnswer = HumanInputAnswer;
25
+ export type AskQuestionAnswerImage = HumanInputAnswerImage;
26
+ export type AskQuestionStopReason = HumanInputStopReason;
27
+ export interface AskQuestionToolDetails {
28
+ questions: readonly AskQuestion[];
29
+ answers: readonly AskQuestionAnswer[];
30
+ cancelled: boolean;
31
+ reason?: AskQuestionStopReason;
32
+ error?: string;
33
+ }
34
+ export interface AskQuestionToolOptions {
35
+ name?: string;
36
+ label?: string;
37
+ /** Production session sink. When present, requests and answers are checkpointed before continuation. */
38
+ sessionManager?: Pick<SessionManager, "appendCustomEntry">;
39
+ artifactStore?: ArtifactStore;
40
+ getImageStore?: () => Pick<SessionImageStore, "retainContent"> | undefined;
41
+ }
42
+ export interface AskQuestionClipboardOptions {
43
+ autoResizeImages: boolean;
44
+ blockImages: boolean;
45
+ blockImagesReason?: string;
46
+ imageStore?: Pick<SessionImageStore, "write">;
47
+ }
48
+ export type PasteClipboardImage = (host: ClipboardInputHost) => Promise<void>;
49
+ export interface AskQuestionAnswerEditor extends Component {
50
+ focused: boolean;
51
+ onSubmit?: (text: string) => void;
52
+ handleInput(data: string): void;
53
+ getText(): string;
54
+ setText(text: string): void;
55
+ insertTextAtCursor(text: string): void;
56
+ }
57
+ export type CreateAskQuestionAnswerEditor = () => AskQuestionAnswerEditor;
58
+ type AskQuestionDialogResult = HumanInputPresentationResult;
59
+ /** Native focused question interaction shared by every provider-facing ask_question call. */
60
+ export declare class AskQuestionDialog implements Component {
61
+ private readonly questions;
62
+ private readonly theme;
63
+ private readonly keybindings;
64
+ private readonly requestRender;
65
+ private readonly finish;
66
+ private readonly clipboard;
67
+ private readonly pasteClipboardImage;
68
+ private readonly createAnswerEditor;
69
+ private readonly selections;
70
+ private readonly cursors;
71
+ private pendingClipboardImages;
72
+ private clipboardImageCounter;
73
+ private currentIndex;
74
+ private input;
75
+ private inputError;
76
+ private inputStatus;
77
+ private pasteInFlight;
78
+ private submitAfterPaste;
79
+ private cachedWidth;
80
+ private cachedLines;
81
+ private settled;
82
+ constructor(options: {
83
+ questions: readonly AskQuestion[];
84
+ theme: Theme;
85
+ keybindings: KeybindingsManager;
86
+ requestRender: () => void;
87
+ finish: (result: AskQuestionDialogResult) => void;
88
+ clipboard?: AskQuestionClipboardOptions;
89
+ pasteClipboardImage?: PasteClipboardImage;
90
+ createAnswerEditor?: CreateAskQuestionAnswerEditor;
91
+ });
92
+ private refresh;
93
+ private result;
94
+ private complete;
95
+ cancel(reason: Extract<AskQuestionStopReason, "user_cancelled" | "interrupted">): void;
96
+ private move;
97
+ private advanceAfterSingleSelection;
98
+ private beginCustomAnswer;
99
+ private pasteIntoCustomAnswer;
100
+ private selectCurrent;
101
+ private submitReview;
102
+ handleInput(data: string): void;
103
+ private addWrapped;
104
+ private renderProgress;
105
+ private renderQuestion;
106
+ private renderReview;
107
+ private renderHelp;
108
+ render(width: number): string[];
109
+ invalidate(): void;
110
+ }
111
+ export declare function createAskQuestionToolDefinition(options?: AskQuestionToolOptions): import("../extensions/types.ts").ToolDefinition<Type.TObject<{
112
+ questions: Type.TArray<Type.TObject<{
113
+ id: Type.TString;
114
+ header: Type.TString;
115
+ question: Type.TString;
116
+ options: Type.TArray<Type.TObject<{
117
+ label: Type.TString;
118
+ description: Type.TString;
119
+ }>>;
120
+ multiSelect: Type.TOptional<Type.TBoolean>;
121
+ }>>;
122
+ }>, AskQuestionToolDetails, any> & import("../extensions/types.ts").ToolDefinition<any, any, any>;
123
+ export {};
124
+ //# sourceMappingURL=ask-question.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask-question.d.ts","sourceRoot":"","sources":["../../../src/core/tools/ask-question.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport {\n\ttype Component,\n\tCURSOR_MARKER,\n\tInput,\n\ttype Keybinding,\n\ttruncateToWidth,\n\tvisibleWidth,\n\twrapTextWithAnsi,\n} from \"@caupulican/pi-tui\";\nimport { type Static, Type } from \"typebox\";\nimport type { ClipboardInputHost, PendingClipboardImage } from \"../../modes/interactive/clipboard-input.ts\";\nimport { formatKeyText } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { ArtifactStore } from \"../context/context-artifacts.ts\";\nimport { defineTool } from \"../extensions/types.ts\";\nimport {\n\tbeginHumanInputRequest,\n\tcreateHumanInputRequest,\n\tformatHumanInputAnswerText,\n\ttype HumanInputAnswer,\n\ttype HumanInputAnswerImage,\n\ttype HumanInputPresentationResult,\n\ttype HumanInputQuestion,\n\ttype HumanInputSnapshot,\n\ttype HumanInputStopReason,\n\tresolveHumanInput,\n} from \"../human-input.ts\";\nimport type { KeybindingsManager } from \"../keybindings.ts\";\nimport type { SessionImageStore } from \"../session-image-store.ts\";\nimport {\n\temptyOrchestrationCall,\n\tOrchestrationPanelComponent,\n\ttype OrchestrationPanelModel,\n} from \"./orchestration-panel.ts\";\n\nconst optionSchema = Type.Object(\n\t{\n\t\tlabel: Type.String({ minLength: 1, maxLength: 120, description: \"Concise option label.\" }),\n\t\tdescription: Type.String({\n\t\t\tminLength: 1,\n\t\t\tmaxLength: 500,\n\t\t\tdescription: \"Concrete consequence or tradeoff of choosing this option.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst questionSchema = Type.Object(\n\t{\n\t\tid: Type.String({ minLength: 1, maxLength: 80, description: \"Stable unique answer identifier.\" }),\n\t\theader: Type.String({\n\t\t\tminLength: 1,\n\t\t\tmaxLength: 32,\n\t\t\tdescription: \"Short topic label used in question navigation.\",\n\t\t}),\n\t\tquestion: Type.String({ minLength: 1, maxLength: 1_000, description: \"The specific user-facing question.\" }),\n\t\toptions: Type.Array(optionSchema, {\n\t\t\tminItems: 2,\n\t\t\tmaxItems: 4,\n\t\t\tdescription: \"Two to four genuine choices. Other and Skip are supplied by the harness.\",\n\t\t}),\n\t\tmultiSelect: Type.Optional(Type.Boolean({ description: \"Allow more than one listed option to be selected.\" })),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst askQuestionSchema = Type.Object(\n\t{\n\t\tquestions: Type.Array(questionSchema, {\n\t\t\tminItems: 1,\n\t\t\tmaxItems: 4,\n\t\t\tdescription: \"One to four independent questions presented in one interaction.\",\n\t\t}),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst ANSWER_PREVIEW_CHARS = 240;\nconst ANSWER_EDITOR_VIEWPORT_LINES = 8;\n\nexport type AskQuestionToolInput = Static<typeof askQuestionSchema>;\nexport type AskQuestion = HumanInputQuestion;\nexport type AskQuestionAnswer = HumanInputAnswer;\nexport type AskQuestionAnswerImage = HumanInputAnswerImage;\nexport type AskQuestionStopReason = HumanInputStopReason;\n\nexport interface AskQuestionToolDetails {\n\tquestions: readonly AskQuestion[];\n\tanswers: readonly AskQuestionAnswer[];\n\tcancelled: boolean;\n\treason?: AskQuestionStopReason;\n\terror?: string;\n}\n\nexport interface AskQuestionToolOptions {\n\tname?: string;\n\tlabel?: string;\n\t/** Production session sink. When present, requests and answers are checkpointed before continuation. */\n\tsessionManager?: Pick<SessionManager, \"appendCustomEntry\">;\n\tartifactStore?: ArtifactStore;\n\tgetImageStore?: () => Pick<SessionImageStore, \"retainContent\"> | undefined;\n}\n\nexport interface AskQuestionClipboardOptions {\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tblockImagesReason?: string;\n\timageStore?: Pick<SessionImageStore, \"write\">;\n}\n\nexport type PasteClipboardImage = (host: ClipboardInputHost) => Promise<void>;\n\nexport interface AskQuestionAnswerEditor extends Component {\n\tfocused: boolean;\n\tonSubmit?: (text: string) => void;\n\thandleInput(data: string): void;\n\tgetText(): string;\n\tsetText(text: string): void;\n\tinsertTextAtCursor(text: string): void;\n}\n\nexport type CreateAskQuestionAnswerEditor = () => AskQuestionAnswerEditor;\n\nclass SingleLineAnswerEditor implements AskQuestionAnswerEditor {\n\tprivate readonly input = new Input();\n\tonSubmit?: (text: string) => void;\n\n\tconstructor() {\n\t\tthis.input.onSubmit = (value) => this.onSubmit?.(value);\n\t}\n\n\tget focused(): boolean {\n\t\treturn this.input.focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis.input.focused = value;\n\t}\n\n\tgetText(): string {\n\t\treturn this.input.getValue();\n\t}\n\n\tsetText(text: string): void {\n\t\tthis.input.setValue(\"\");\n\t\tthis.insertTextAtCursor(text);\n\t}\n\n\tinsertTextAtCursor(text: string): void {\n\t\tthis.input.handleInput(`\\x1b[200~${text}\\x1b[201~`);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.input.handleInput(data);\n\t}\n\n\trender(width: number): string[] {\n\t\treturn this.input.render(width);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.input.invalidate();\n\t}\n}\n\ninterface QuestionSelection {\n\toptionIndexes: Set<number>;\n\tcustom?: string;\n\tskipped: boolean;\n}\n\ntype AskQuestionDialogResult = HumanInputPresentationResult;\n\nfunction normalizedIdentity(value: string): string {\n\treturn value.trim().toLowerCase();\n}\n\nfunction validateQuestions(questions: readonly AskQuestion[]): string | undefined {\n\tif (questions.length < 1 || questions.length > 4) return \"Provide between one and four questions.\";\n\tconst ids = new Set<string>();\n\tconst prompts = new Set<string>();\n\tfor (const question of questions) {\n\t\tif (!question.id.trim() || !question.header.trim() || !question.question.trim()) {\n\t\t\treturn \"Question ids, headers, and prompts must not be blank.\";\n\t\t}\n\t\tconst id = normalizedIdentity(question.id);\n\t\tconst prompt = normalizedIdentity(question.question);\n\t\tif (ids.has(id)) return `Question id '${question.id}' is duplicated.`;\n\t\tif (prompts.has(prompt)) return `Question '${question.question}' is duplicated.`;\n\t\tids.add(id);\n\t\tprompts.add(prompt);\n\t\tif (question.options.length < 2 || question.options.length > 4) {\n\t\t\treturn `Question '${question.header}' requires between two and four options.`;\n\t\t}\n\t\tconst labels = new Set<string>();\n\t\tfor (const option of question.options) {\n\t\t\tconst label = normalizedIdentity(option.label);\n\t\t\tif (!label || !option.description.trim()) {\n\t\t\t\treturn `Question '${question.header}' has a blank option label or description.`;\n\t\t\t}\n\t\t\tif (label === \"other\" || label === \"skip\") {\n\t\t\t\treturn `Question '${question.header}' must not define the reserved '${option.label}' option.`;\n\t\t\t}\n\t\t\tif (labels.has(label)) return `Question '${question.header}' has duplicate option '${option.label}'.`;\n\t\t\tlabels.add(label);\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction displayKeys(keybindings: KeybindingsManager, keybinding: Keybinding, limit = 2): string {\n\treturn formatKeyText(keybindings.getKeys(keybinding).slice(0, limit).join(\"/\"), { capitalize: true });\n}\n\nfunction answerFor(\n\tquestion: AskQuestion,\n\tselection: QuestionSelection,\n\tattachments: readonly PendingClipboardImage[] = [],\n): AskQuestionAnswer {\n\tconst images = selection.custom\n\t\t? attachments\n\t\t\t\t.filter((attachment) => selection.custom?.includes(attachment.label))\n\t\t\t\t.map((attachment) => ({ label: attachment.label, mimeType: attachment.content.mimeType }))\n\t\t: [];\n\treturn {\n\t\tid: question.id,\n\t\theader: question.header,\n\t\tquestion: question.question,\n\t\tselected: [...selection.optionIndexes]\n\t\t\t.sort((left, right) => left - right)\n\t\t\t.map((index) => question.options[index]?.label)\n\t\t\t.filter((label): label is string => label !== undefined),\n\t\t...(selection.custom ? { custom: selection.custom } : {}),\n\t\t...(images.length > 0 ? { images } : {}),\n\t\tskipped: selection.skipped,\n\t};\n}\n\nfunction previewAnswer(value: string): string {\n\tif (value.length <= ANSWER_PREVIEW_CHARS) return value;\n\treturn `${value.slice(0, ANSWER_PREVIEW_CHARS)}… (${value.length} characters)`;\n}\n\nfunction isResolved(selection: QuestionSelection): boolean {\n\treturn selection.skipped || selection.optionIndexes.size > 0 || selection.custom !== undefined;\n}\n\n/** Native focused question interaction shared by every provider-facing ask_question call. */\nexport class AskQuestionDialog implements Component {\n\tprivate readonly questions: readonly AskQuestion[];\n\tprivate readonly theme: Theme;\n\tprivate readonly keybindings: KeybindingsManager;\n\tprivate readonly requestRender: () => void;\n\tprivate readonly finish: (result: AskQuestionDialogResult) => void;\n\tprivate readonly clipboard: AskQuestionClipboardOptions;\n\tprivate readonly pasteClipboardImage: PasteClipboardImage;\n\tprivate readonly createAnswerEditor: CreateAskQuestionAnswerEditor;\n\tprivate readonly selections: QuestionSelection[];\n\tprivate readonly cursors: number[];\n\tprivate pendingClipboardImages: PendingClipboardImage[] = [];\n\tprivate clipboardImageCounter = 0;\n\tprivate currentIndex = 0;\n\tprivate input: AskQuestionAnswerEditor | undefined;\n\tprivate inputError: string | undefined;\n\tprivate inputStatus: string | undefined;\n\tprivate pasteInFlight = false;\n\tprivate submitAfterPaste = false;\n\tprivate cachedWidth: number | undefined;\n\tprivate cachedLines: string[] | undefined;\n\tprivate settled = false;\n\n\tconstructor(options: {\n\t\tquestions: readonly AskQuestion[];\n\t\ttheme: Theme;\n\t\tkeybindings: KeybindingsManager;\n\t\trequestRender: () => void;\n\t\tfinish: (result: AskQuestionDialogResult) => void;\n\t\tclipboard?: AskQuestionClipboardOptions;\n\t\tpasteClipboardImage?: PasteClipboardImage;\n\t\tcreateAnswerEditor?: CreateAskQuestionAnswerEditor;\n\t}) {\n\t\tthis.questions = options.questions;\n\t\tthis.theme = options.theme;\n\t\tthis.keybindings = options.keybindings;\n\t\tthis.requestRender = options.requestRender;\n\t\tthis.finish = options.finish;\n\t\tthis.clipboard = options.clipboard ?? { autoResizeImages: true, blockImages: false };\n\t\tthis.pasteClipboardImage =\n\t\t\toptions.pasteClipboardImage ??\n\t\t\t(async (host) => {\n\t\t\t\thost.showWarning(\"Clipboard paste is unavailable in this UI host.\");\n\t\t\t});\n\t\tthis.createAnswerEditor = options.createAnswerEditor ?? (() => new SingleLineAnswerEditor());\n\t\tthis.selections = options.questions.map(() => ({ optionIndexes: new Set(), skipped: false }));\n\t\tthis.cursors = options.questions.map(() => 0);\n\t}\n\n\tprivate refresh(): void {\n\t\tthis.invalidate();\n\t\tthis.requestRender();\n\t}\n\n\tprivate result(cancelled: boolean, reason?: AskQuestionStopReason): AskQuestionDialogResult {\n\t\tconst answers = this.questions.map((question, index) =>\n\t\t\tanswerFor(question, this.selections[index]!, this.pendingClipboardImages),\n\t\t);\n\t\tconst referencedLabels = new Set(answers.flatMap((answer) => answer.images?.map((image) => image.label) ?? []));\n\t\treturn {\n\t\t\tanswers,\n\t\t\tcancelled,\n\t\t\timageContents: cancelled\n\t\t\t\t? []\n\t\t\t\t: this.pendingClipboardImages\n\t\t\t\t\t\t.filter((attachment) => referencedLabels.has(attachment.label))\n\t\t\t\t\t\t.map((attachment) => attachment.content),\n\t\t\t...(reason ? { reason } : {}),\n\t\t};\n\t}\n\n\tprivate complete(result: AskQuestionDialogResult): void {\n\t\tif (this.settled) return;\n\t\tthis.settled = true;\n\t\tif (this.input) this.input.focused = false;\n\t\tthis.finish(result);\n\t}\n\n\tcancel(reason: Extract<AskQuestionStopReason, \"user_cancelled\" | \"interrupted\">): void {\n\t\tthis.complete(this.result(true, reason));\n\t}\n\n\tprivate move(delta: -1 | 1): void {\n\t\tconst lastIndex = this.questions.length;\n\t\tthis.currentIndex = Math.max(0, Math.min(lastIndex, this.currentIndex + delta));\n\t\tthis.refresh();\n\t}\n\n\tprivate advanceAfterSingleSelection(): void {\n\t\tif (this.questions.length === 1) {\n\t\t\tthis.complete(this.result(false));\n\t\t\treturn;\n\t\t}\n\t\tthis.currentIndex = Math.min(this.questions.length, this.currentIndex + 1);\n\t\tthis.refresh();\n\t}\n\n\tprivate beginCustomAnswer(): void {\n\t\tconst questionIndex = this.currentIndex;\n\t\tconst question = this.questions[questionIndex];\n\t\tconst selection = this.selections[questionIndex];\n\t\tif (!question || !selection) return;\n\t\tconst input = this.createAnswerEditor();\n\t\tthis.inputError = undefined;\n\t\tthis.inputStatus = undefined;\n\t\tinput.focused = true;\n\t\tif (selection.custom) input.setText(selection.custom);\n\t\tinput.onSubmit = (value) => {\n\t\t\tconst answer = value.trim();\n\t\t\tif (!answer) return;\n\t\t\tselection.custom = answer;\n\t\t\tselection.skipped = false;\n\t\t\tinput.focused = false;\n\t\t\tthis.input = undefined;\n\t\t\tif (question.multiSelect) this.refresh();\n\t\t\telse this.advanceAfterSingleSelection();\n\t\t};\n\t\tthis.input = input;\n\t\tthis.refresh();\n\t}\n\n\tprivate async pasteIntoCustomAnswer(): Promise<void> {\n\t\tconst input = this.input;\n\t\tif (!input || this.pasteInFlight) return;\n\t\tthis.pasteInFlight = true;\n\t\tthis.inputError = undefined;\n\t\tthis.inputStatus = \"Reading clipboard…\";\n\t\tthis.refresh();\n\t\tlet reported = false;\n\t\tconst self = this;\n\t\tconst host: ClipboardInputHost = {\n\t\t\tget pendingClipboardImages() {\n\t\t\t\treturn self.pendingClipboardImages;\n\t\t\t},\n\t\t\tset pendingClipboardImages(value) {\n\t\t\t\tself.pendingClipboardImages = value;\n\t\t\t},\n\t\t\tget clipboardImageCounter() {\n\t\t\t\treturn self.clipboardImageCounter;\n\t\t\t},\n\t\t\tset clipboardImageCounter(value) {\n\t\t\t\tself.clipboardImageCounter = value;\n\t\t\t},\n\t\t\teditor: {\n\t\t\t\thandleInput: (data) => input.handleInput(data),\n\t\t\t\tinsertTextAtCursor: (text) => input.insertTextAtCursor(text),\n\t\t\t},\n\t\t\tui: { requestRender: () => this.refresh() },\n\t\t\tautoResizeImages: this.clipboard.autoResizeImages,\n\t\t\tblockImages: this.clipboard.blockImages,\n\t\t\tblockImagesReason: this.clipboard.blockImagesReason,\n\t\t\timageStore: this.clipboard.imageStore,\n\t\t\tshowStatus: (message) => {\n\t\t\t\treported = true;\n\t\t\t\tthis.inputStatus = message;\n\t\t\t\tthis.inputError = undefined;\n\t\t\t},\n\t\t\tshowWarning: (message) => {\n\t\t\t\treported = true;\n\t\t\t\tthis.inputStatus = undefined;\n\t\t\t\tthis.inputError = message;\n\t\t\t\tthis.refresh();\n\t\t\t},\n\t\t};\n\t\ttry {\n\t\t\tawait this.pasteClipboardImage(host);\n\t\t} finally {\n\t\t\tthis.pasteInFlight = false;\n\t\t\tconst shouldSubmit = this.submitAfterPaste && this.input === input && !this.settled;\n\t\t\tthis.submitAfterPaste = false;\n\t\t\tif (!reported) this.inputStatus = undefined;\n\t\t\tif (shouldSubmit) input.onSubmit?.(input.getText());\n\t\t\tif (this.input === input && !this.settled) this.refresh();\n\t\t}\n\t}\n\n\tprivate selectCurrent(): void {\n\t\tconst question = this.questions[this.currentIndex];\n\t\tconst selection = this.selections[this.currentIndex];\n\t\tif (!question || !selection) return;\n\t\tconst cursor = this.cursors[this.currentIndex] ?? 0;\n\t\tif (cursor === question.options.length) {\n\t\t\tthis.beginCustomAnswer();\n\t\t\treturn;\n\t\t}\n\t\tif (cursor === question.options.length + 1) {\n\t\t\tselection.optionIndexes.clear();\n\t\t\tselection.custom = undefined;\n\t\t\tselection.skipped = true;\n\t\t\tthis.advanceAfterSingleSelection();\n\t\t\treturn;\n\t\t}\n\t\tselection.skipped = false;\n\t\tif (question.multiSelect) {\n\t\t\tif (selection.optionIndexes.has(cursor)) selection.optionIndexes.delete(cursor);\n\t\t\telse selection.optionIndexes.add(cursor);\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\t\tselection.optionIndexes.clear();\n\t\tselection.optionIndexes.add(cursor);\n\t\tselection.custom = undefined;\n\t\tthis.advanceAfterSingleSelection();\n\t}\n\n\tprivate submitReview(): void {\n\t\tconst unresolved = this.selections.findIndex((selection) => !isResolved(selection));\n\t\tif (unresolved >= 0) {\n\t\t\tthis.currentIndex = unresolved;\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\t\tthis.complete(this.result(false));\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.settled) return;\n\t\tif (this.input) {\n\t\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\t\tthis.submitAfterPaste = false;\n\t\t\t\tthis.input.focused = false;\n\t\t\t\tthis.input = undefined;\n\t\t\t\tthis.refresh();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (this.keybindings.matches(data, \"app.clipboard.pasteImage\")) {\n\t\t\t\tvoid this.pasteIntoCustomAnswer();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (this.pasteInFlight && this.keybindings.matches(data, \"tui.input.submit\")) {\n\t\t\t\tthis.submitAfterPaste = true;\n\t\t\t\tthis.inputStatus = \"Finishing clipboard paste…\";\n\t\t\t\tthis.refresh();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst input = this.input;\n\t\t\tthis.inputError = undefined;\n\t\t\tthis.inputStatus = undefined;\n\t\t\tinput.handleInput(data);\n\t\t\tif (this.input === input) this.refresh();\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.cancel(\"user_cancelled\");\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"app.question.previous\")) {\n\t\t\tthis.move(-1);\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"app.question.next\")) {\n\t\t\tthis.move(1);\n\t\t\treturn;\n\t\t}\n\t\tif (this.currentIndex === this.questions.length) {\n\t\t\tif (this.keybindings.matches(data, \"tui.select.confirm\")) this.submitReview();\n\t\t\treturn;\n\t\t}\n\t\tconst question = this.questions[this.currentIndex];\n\t\tif (!question) return;\n\t\tif (this.keybindings.matches(data, \"tui.select.up\")) {\n\t\t\tthis.cursors[this.currentIndex] = Math.max(0, (this.cursors[this.currentIndex] ?? 0) - 1);\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"tui.select.down\")) {\n\t\t\tthis.cursors[this.currentIndex] = Math.min(\n\t\t\t\tquestion.options.length + 1,\n\t\t\t\t(this.cursors[this.currentIndex] ?? 0) + 1,\n\t\t\t);\n\t\t\tthis.refresh();\n\t\t\treturn;\n\t\t}\n\t\tif (\n\t\t\tthis.keybindings.matches(data, \"tui.select.confirm\") ||\n\t\t\t(question.multiSelect && this.keybindings.matches(data, \"app.question.toggle\"))\n\t\t) {\n\t\t\tthis.selectCurrent();\n\t\t}\n\t}\n\n\tprivate addWrapped(lines: string[], text: string, width: number, indent = \"\"): void {\n\t\tconst contentWidth = Math.max(1, width - visibleWidth(indent));\n\t\tfor (const line of wrapTextWithAnsi(text, contentWidth)) {\n\t\t\tlines.push(truncateToWidth(`${indent}${line}`, width, \"\"));\n\t\t}\n\t}\n\n\tprivate renderProgress(lines: string[], width: number): void {\n\t\tif (this.questions.length === 1 && !this.questions[0]?.multiSelect) return;\n\t\tconst chips = this.questions.map((question, index) => {\n\t\t\tconst resolved = isResolved(this.selections[index]!);\n\t\t\tconst text = ` ${resolved ? \"●\" : \"○\"} ${question.header} `;\n\t\t\treturn index === this.currentIndex\n\t\t\t\t? this.theme.bg(\"selectedBg\", this.theme.fg(\"text\", text))\n\t\t\t\t: this.theme.fg(resolved ? \"success\" : \"muted\", text);\n\t\t});\n\t\tconst review = \" Review \";\n\t\tchips.push(\n\t\t\tthis.currentIndex === this.questions.length\n\t\t\t\t? this.theme.bg(\"selectedBg\", this.theme.fg(\"text\", review))\n\t\t\t\t: this.theme.fg(\"dim\", review),\n\t\t);\n\t\tlines.push(truncateToWidth(chips.join(\" \"), width, \"\"));\n\t\tlines.push(\"\");\n\t}\n\n\tprivate renderQuestion(lines: string[], width: number, question: AskQuestion): void {\n\t\tconst questionIndex = this.currentIndex;\n\t\tconst selection = this.selections[questionIndex]!;\n\t\tconst cursor = this.cursors[questionIndex] ?? 0;\n\t\tthis.addWrapped(lines, this.theme.bold(question.question), width);\n\t\tif (question.multiSelect) {\n\t\t\tlines.push(this.theme.fg(\"muted\", \"Choose one or more, then continue to review.\"));\n\t\t}\n\t\tlines.push(\"\");\n\n\t\tquestion.options.forEach((option, index) => {\n\t\t\tconst active = index === cursor;\n\t\t\tconst chosen = selection.optionIndexes.has(index);\n\t\t\tconst prefix = `${active ? \"›\" : \" \"} ${chosen ? \"●\" : \"○\"} `;\n\t\t\tconst label = `${prefix}${option.label}`;\n\t\t\tlines.push(\n\t\t\t\ttruncateToWidth(\n\t\t\t\t\tactive ? this.theme.bg(\"selectedBg\", this.theme.fg(\"text\", ` ${label} `)) : this.theme.fg(\"text\", label),\n\t\t\t\t\twidth,\n\t\t\t\t\t\"\",\n\t\t\t\t),\n\t\t\t);\n\t\t\tthis.addWrapped(lines, this.theme.fg(\"muted\", option.description), width, \" \");\n\t\t});\n\n\t\tconst otherIndex = question.options.length;\n\t\tconst otherActive = cursor === otherIndex;\n\t\tconst otherLabel = selection.custom ? `Other: ${previewAnswer(selection.custom)}` : \"Other\";\n\t\tconst otherText = `${otherActive ? \"›\" : \" \"} ${selection.custom ? \"●\" : \"+\"} ${otherLabel}`;\n\t\tlines.push(\n\t\t\ttruncateToWidth(\n\t\t\t\totherActive\n\t\t\t\t\t? this.theme.bg(\"selectedBg\", this.theme.fg(\"text\", ` ${otherText} `))\n\t\t\t\t\t: this.theme.fg(\"muted\", otherText),\n\t\t\t\twidth,\n\t\t\t\t\"\",\n\t\t\t),\n\t\t);\n\t\tconst skipActive = cursor === otherIndex + 1;\n\t\tconst skipText = `${skipActive ? \"›\" : \" \"} ${selection.skipped ? \"●\" : \"–\"} Skip`;\n\t\tlines.push(\n\t\t\ttruncateToWidth(\n\t\t\t\tskipActive\n\t\t\t\t\t? this.theme.bg(\"selectedBg\", this.theme.fg(\"text\", ` ${skipText} `))\n\t\t\t\t\t: this.theme.fg(\"dim\", skipText),\n\t\t\t\twidth,\n\t\t\t\t\"\",\n\t\t\t),\n\t\t);\n\n\t\tif (this.input) {\n\t\t\tlines.push(\"\");\n\t\t\tlines.push(this.theme.fg(\"muted\", \"Your answer\"));\n\t\t\tconst renderedInput = this.input.render(Math.max(4, width - 2));\n\t\t\tconst cursorLine = Math.max(\n\t\t\t\t0,\n\t\t\t\trenderedInput.findIndex((line) => line.includes(CURSOR_MARKER)),\n\t\t\t);\n\t\t\tconst viewportStart = Math.max(\n\t\t\t\t0,\n\t\t\t\tMath.min(\n\t\t\t\t\tcursorLine - Math.floor(ANSWER_EDITOR_VIEWPORT_LINES / 2),\n\t\t\t\t\trenderedInput.length - ANSWER_EDITOR_VIEWPORT_LINES,\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst visibleInput = renderedInput.slice(viewportStart, viewportStart + ANSWER_EDITOR_VIEWPORT_LINES);\n\t\t\tif (viewportStart > 0) {\n\t\t\t\tlines.push(this.theme.fg(\"dim\", ` ↑ ${viewportStart} earlier line${viewportStart === 1 ? \"\" : \"s\"}`));\n\t\t\t}\n\t\t\tfor (const inputLine of visibleInput) {\n\t\t\t\tlines.push(truncateToWidth(` ${inputLine}`, width, \"\"));\n\t\t\t}\n\t\t\tconst remainingLines = renderedInput.length - viewportStart - visibleInput.length;\n\t\t\tif (remainingLines > 0) {\n\t\t\t\tlines.push(this.theme.fg(\"dim\", ` ↓ ${remainingLines} later line${remainingLines === 1 ? \"\" : \"s\"}`));\n\t\t\t}\n\t\t\tif (this.inputError) this.addWrapped(lines, this.theme.fg(\"warning\", this.inputError), width, \" \");\n\t\t\tif (this.inputStatus) this.addWrapped(lines, this.theme.fg(\"success\", this.inputStatus), width, \" \");\n\t\t}\n\t}\n\n\tprivate renderReview(lines: string[], width: number): void {\n\t\tlines.push(this.theme.bold(\"Review your answers\"));\n\t\tlines.push(\"\");\n\t\tlet unresolved = 0;\n\t\tthis.questions.forEach((question, index) => {\n\t\t\tconst answer = answerFor(question, this.selections[index]!);\n\t\t\tconst values = [...answer.selected, ...(answer.custom ? [previewAnswer(answer.custom)] : [])];\n\t\t\tconst value = answer.skipped ? \"Skipped\" : values.length > 0 ? values.join(\", \") : \"Unanswered\";\n\t\t\tif (!isResolved(this.selections[index]!)) unresolved++;\n\t\t\tthis.addWrapped(\n\t\t\t\tlines,\n\t\t\t\t`${this.theme.fg(\"muted\", `${question.header}:`)} ${this.theme.fg(value === \"Unanswered\" ? \"warning\" : \"text\", value)}`,\n\t\t\t\twidth,\n\t\t\t);\n\t\t});\n\t\tif (unresolved > 0) {\n\t\t\tlines.push(\"\");\n\t\t\tthis.addWrapped(\n\t\t\t\tlines,\n\t\t\t\tthis.theme.fg(\n\t\t\t\t\t\"warning\",\n\t\t\t\t\t`${unresolved} unanswered. Answer or explicitly skip each question before submitting.`,\n\t\t\t\t),\n\t\t\t\twidth,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate renderHelp(lines: string[], width: number): void {\n\t\tconst cancel = displayKeys(this.keybindings, \"tui.select.cancel\", 1);\n\t\tlet help: string;\n\t\tif (this.input) {\n\t\t\thelp = `${displayKeys(this.keybindings, \"tui.input.submit\", 1)} save · ${displayKeys(this.keybindings, \"tui.input.newLine\", 1)} newline · ${displayKeys(this.keybindings, \"app.clipboard.pasteImage\", 1)} paste · ${cancel} back`;\n\t\t} else if (this.currentIndex === this.questions.length) {\n\t\t\thelp = `${displayKeys(this.keybindings, \"tui.select.confirm\", 1)} submit · ${displayKeys(this.keybindings, \"app.question.previous\")} back · ${cancel} cancel`;\n\t\t} else if (this.questions[this.currentIndex]?.multiSelect) {\n\t\t\thelp = `${displayKeys(this.keybindings, \"tui.select.up\")}/${displayKeys(this.keybindings, \"tui.select.down\")} move · ${displayKeys(this.keybindings, \"app.question.toggle\", 1)}/${displayKeys(this.keybindings, \"tui.select.confirm\", 1)} toggle · ${displayKeys(this.keybindings, \"app.question.next\")} next · ${cancel} cancel`;\n\t\t} else {\n\t\t\thelp = `${displayKeys(this.keybindings, \"tui.select.up\")}/${displayKeys(this.keybindings, \"tui.select.down\")} move · ${displayKeys(this.keybindings, \"tui.select.confirm\", 1)} select · ${cancel} cancel`;\n\t\t}\n\t\tthis.addWrapped(lines, this.theme.fg(\"dim\", help), width);\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.cachedWidth === width && this.cachedLines) return this.cachedLines;\n\t\tconst safeWidth = Math.max(1, width);\n\t\tconst lines: string[] = [];\n\t\tconst header = this.currentIndex === this.questions.length ? \"review\" : this.questions[this.currentIndex]?.header;\n\t\tlines.push(\n\t\t\ttruncateToWidth(\n\t\t\t\t`${this.theme.fg(\"accent\", \"?\")} ${this.theme.bold(header ?? \"question\")} ${this.theme.fg(\"dim\", `${Math.min(this.currentIndex + 1, this.questions.length)}/${this.questions.length}`)}`,\n\t\t\t\tsafeWidth,\n\t\t\t\t\"\",\n\t\t\t),\n\t\t);\n\t\tlines.push(\"\");\n\t\tthis.renderProgress(lines, safeWidth);\n\t\tconst question = this.questions[this.currentIndex];\n\t\tif (question) this.renderQuestion(lines, safeWidth, question);\n\t\telse this.renderReview(lines, safeWidth);\n\t\tlines.push(\"\");\n\t\tthis.renderHelp(lines, safeWidth);\n\t\tthis.cachedWidth = width;\n\t\tthis.cachedLines = lines.map((line) => truncateToWidth(line, safeWidth, \"\"));\n\t\treturn this.cachedLines;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.cachedWidth = undefined;\n\t\tthis.cachedLines = undefined;\n\t}\n}\n\nfunction questionPanelModel(details: AskQuestionToolDetails | undefined): OrchestrationPanelModel {\n\tif (!details) {\n\t\treturn { label: \"question\", status: \"error\", emptyText: \"No structured answer was retained.\" };\n\t}\n\tif (details.reason === \"ui_unavailable\" || details.reason === \"invalid_questions\") {\n\t\treturn {\n\t\t\tlabel: \"question\",\n\t\t\taction: \"unavailable\",\n\t\t\tstatus: \"error\",\n\t\t\temptyText: details.error ?? \"User input is unavailable.\",\n\t\t};\n\t}\n\tconst rows = details.questions.map((question) => {\n\t\tconst answer = details.answers.find((candidate) => candidate.id === question.id);\n\t\tconst values = answer ? [...answer.selected, ...(answer.custom ? [previewAnswer(answer.custom)] : [])] : [];\n\t\tconst imageCount = answer?.images?.length ?? 0;\n\t\treturn {\n\t\t\tstatus: details.cancelled\n\t\t\t\t? (\"cancelled\" as const)\n\t\t\t\t: answer?.skipped\n\t\t\t\t\t? (\"cancelled\" as const)\n\t\t\t\t\t: (\"completed\" as const),\n\t\t\tlabel: question.header,\n\t\t\tmeta: details.cancelled\n\t\t\t\t? [details.reason === \"interrupted\" ? \"interrupted\" : \"cancelled\"]\n\t\t\t\t: answer?.skipped\n\t\t\t\t\t? [\"skipped\"]\n\t\t\t\t\t: [\n\t\t\t\t\t\t\tvalues.length > 1 ? `${values.length} selected` : undefined,\n\t\t\t\t\t\t\timageCount > 0 ? `${imageCount} image${imageCount === 1 ? \"\" : \"s\"}` : undefined,\n\t\t\t\t\t\t].filter((value): value is string => value !== undefined),\n\t\t\tdetails: values.length > 0 ? [`answer: ${values.join(\", \")}`] : undefined,\n\t\t};\n\t});\n\tconst skipped = details.answers.filter((answer) => answer.skipped).length;\n\treturn {\n\t\tlabel: \"question\",\n\t\taction: details.cancelled ? \"cancelled\" : \"answered\",\n\t\tstatus: details.cancelled ? \"warning\" : \"success\",\n\t\tsummary: details.cancelled\n\t\t\t? undefined\n\t\t\t: [`${details.answers.length - skipped} answered`, skipped ? `${skipped} skipped` : undefined].filter(\n\t\t\t\t\t(value): value is string => value !== undefined,\n\t\t\t\t),\n\t\trows,\n\t};\n}\n\nfunction stoppedResult(\n\tquestions: readonly AskQuestion[],\n\treason: AskQuestionStopReason,\n\terror?: string,\n): { content: Array<{ type: \"text\"; text: string }>; details: AskQuestionToolDetails } {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: error ?? `ask_question stopped: ${reason}` }],\n\t\tdetails: { questions, answers: [], cancelled: true, reason, ...(error ? { error } : {}) },\n\t};\n}\n\nexport function createAskQuestionToolDefinition(options: AskQuestionToolOptions = {}) {\n\tconst name = options.name ?? \"ask_question\";\n\treturn defineTool<typeof askQuestionSchema, AskQuestionToolDetails>({\n\t\tname,\n\t\tlabel: options.label ?? \"Ask Question\",\n\t\tdescription:\n\t\t\t\"Ask the human owner one to four concise questions when a missing choice materially changes the work. The harness supplies Other and Skip choices, supports single- and multi-select answers, and returns a typed answer set.\",\n\t\tpromptSnippet: \"Ask the human owner for a consequential missing choice through the native interactive UI.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool only when a missing owner choice materially changes the result, authority, risk, or acceptance criteria; otherwise proceed with a safe stated assumption.\",\n\t\t\t\"Batch independent questions into one call, with at most four questions and two to four genuine options each.\",\n\t\t\t\"Make every option label concise and describe its concrete consequence or tradeoff. Do not add Other, Skip, None, or filler options; the harness supplies Other and Skip.\",\n\t\t\t\"Set multiSelect only when choices are genuinely independent. Never use a question to delegate a decision already fixed by an owner-authored profile or policy.\",\n\t\t\t\"Respect skipped or cancelled input as owner intent. Do not immediately repeat the same question; proceed safely or report the unresolved boundary.\",\n\t\t],\n\t\tparameters: askQuestionSchema,\n\t\texecutionMode: \"sequential\",\n\t\trenderShell: \"self\",\n\t\trenderCall() {\n\t\t\treturn emptyOrchestrationCall();\n\t\t},\n\t\trenderResult(result, { expanded, isPartial }, theme) {\n\t\t\tif (isPartial) {\n\t\t\t\treturn new OrchestrationPanelComponent(theme, {\n\t\t\t\t\tlabel: \"question\",\n\t\t\t\t\taction: \"waiting for you\",\n\t\t\t\t\tstatus: \"running\",\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn new OrchestrationPanelComponent(theme, questionPanelModel(result.details), expanded);\n\t\t},\n\t\tasync execute(_toolCallId, input, signal, _onUpdate, ctx) {\n\t\t\tconst validationError = validateQuestions(input.questions);\n\t\t\tif (validationError) return stoppedResult(input.questions, \"invalid_questions\", validationError);\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\treturn stoppedResult(input.questions, \"ui_unavailable\", \"ask_question requires interactive UI.\");\n\t\t\t}\n\t\t\tif (signal?.aborted) return stoppedResult(input.questions, \"interrupted\");\n\n\t\t\tconst request = createHumanInputRequest({\n\t\t\t\tsource: \"tool\",\n\t\t\t\ttoolCallId: _toolCallId,\n\t\t\t\ttoolName: name,\n\t\t\t\tquestions: input.questions,\n\t\t\t\tacceptsImages: ctx.model?.input.includes(\"image\") ?? false,\n\t\t\t});\n\t\t\tif (options.sessionManager) beginHumanInputRequest(options.sessionManager, request);\n\t\t\tconst resolved = options.sessionManager\n\t\t\t\t? await resolveHumanInput({\n\t\t\t\t\t\tsessionManager: options.sessionManager,\n\t\t\t\t\t\trequest,\n\t\t\t\t\t\tpresent: (presentation, dialogOptions) => ctx.ui.askQuestions(presentation, dialogOptions),\n\t\t\t\t\t\tartifactStore: options.artifactStore,\n\t\t\t\t\t\tgetImageStore: options.getImageStore,\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t})\n\t\t\t\t: await (async (): Promise<{\n\t\t\t\t\t\tsnapshot: HumanInputSnapshot;\n\t\t\t\t\t\timageContents: AskQuestionDialogResult[\"imageContents\"];\n\t\t\t\t\t}> => {\n\t\t\t\t\t\tconst result = await ctx.ui.askQuestions(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\trequestId: request.requestId,\n\t\t\t\t\t\t\t\tquestions: request.questions,\n\t\t\t\t\t\t\t\tacceptsImages: request.acceptsImages,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t{ signal },\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tsnapshot: {\n\t\t\t\t\t\t\t\trequest,\n\t\t\t\t\t\t\t\tstatus: result.cancelled ? \"cancelled\" : \"answered\",\n\t\t\t\t\t\t\t\tanswers: result.answers,\n\t\t\t\t\t\t\t\t...(result.reason ? { reason: result.reason } : {}),\n\t\t\t\t\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\timageContents: result.imageContents,\n\t\t\t\t\t\t};\n\t\t\t\t\t})();\n\n\t\t\tconst details: AskQuestionToolDetails = {\n\t\t\t\tquestions: input.questions,\n\t\t\t\tanswers: resolved.snapshot.answers,\n\t\t\t\tcancelled: resolved.snapshot.status === \"cancelled\",\n\t\t\t\t...(resolved.snapshot.reason ? { reason: resolved.snapshot.reason } : {}),\n\t\t\t};\n\t\t\tif (details.cancelled) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\" as const, text: formatHumanInputAnswerText(resolved.snapshot) }],\n\t\t\t\t\tdetails,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [\n\t\t\t\t\t{ type: \"text\" as const, text: formatHumanInputAnswerText(resolved.snapshot) },\n\t\t\t\t\t...resolved.imageContents,\n\t\t\t\t],\n\t\t\t\tdetails,\n\t\t\t};\n\t\t},\n\t});\n}\n"]}