@namzu/sdk 51.1.0 → 52.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (395) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts +1 -1
  6. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  7. package/dist/agents/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/examples/SupervisorAgent.d.ts +42 -0
  9. package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
  10. package/dist/agents/examples/SupervisorAgent.js +80 -4
  11. package/dist/agents/examples/SupervisorAgent.js.map +1 -1
  12. package/dist/agents/index.d.ts +1 -1
  13. package/dist/agents/index.d.ts.map +1 -1
  14. package/dist/agents/index.js.map +1 -1
  15. package/dist/authorization/protected-paths.d.ts +36 -0
  16. package/dist/authorization/protected-paths.d.ts.map +1 -0
  17. package/dist/authorization/protected-paths.js +107 -0
  18. package/dist/authorization/protected-paths.js.map +1 -0
  19. package/dist/authorization/readonly-commands.d.ts +39 -0
  20. package/dist/authorization/readonly-commands.d.ts.map +1 -0
  21. package/dist/authorization/readonly-commands.js +797 -0
  22. package/dist/authorization/readonly-commands.js.map +1 -0
  23. package/dist/authorization/readonly-flags.d.ts +19 -0
  24. package/dist/authorization/readonly-flags.d.ts.map +1 -0
  25. package/dist/authorization/readonly-flags.js +178 -0
  26. package/dist/authorization/readonly-flags.js.map +1 -0
  27. package/dist/authorization/removal.d.ts +52 -0
  28. package/dist/authorization/removal.d.ts.map +1 -0
  29. package/dist/authorization/removal.js +283 -0
  30. package/dist/authorization/removal.js.map +1 -0
  31. package/dist/authorization/shell-paths.d.ts +66 -0
  32. package/dist/authorization/shell-paths.d.ts.map +1 -0
  33. package/dist/authorization/shell-paths.js +697 -0
  34. package/dist/authorization/shell-paths.js.map +1 -0
  35. package/dist/bridge/acp/update.d.ts.map +1 -1
  36. package/dist/bridge/acp/update.js +1 -0
  37. package/dist/bridge/acp/update.js.map +1 -1
  38. package/dist/compaction/context-window.d.ts +24 -0
  39. package/dist/compaction/context-window.d.ts.map +1 -1
  40. package/dist/compaction/context-window.js +34 -0
  41. package/dist/compaction/context-window.js.map +1 -1
  42. package/dist/compaction/manual.d.ts.map +1 -1
  43. package/dist/compaction/manual.js +10 -2
  44. package/dist/compaction/manual.js.map +1 -1
  45. package/dist/compaction/plan.d.ts +4 -0
  46. package/dist/compaction/plan.d.ts.map +1 -1
  47. package/dist/compaction/plan.js +6 -2
  48. package/dist/compaction/plan.js.map +1 -1
  49. package/dist/compaction/verifier.d.ts +11 -1
  50. package/dist/compaction/verifier.d.ts.map +1 -1
  51. package/dist/compaction/verifier.js +75 -23
  52. package/dist/compaction/verifier.js.map +1 -1
  53. package/dist/config/runtime.d.ts +55 -7
  54. package/dist/config/runtime.d.ts.map +1 -1
  55. package/dist/config/runtime.js +23 -2
  56. package/dist/config/runtime.js.map +1 -1
  57. package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
  58. package/dist/connector/web/guarded-fetch.js +24 -0
  59. package/dist/connector/web/guarded-fetch.js.map +1 -1
  60. package/dist/connector/web/index.d.ts +1 -1
  61. package/dist/connector/web/index.d.ts.map +1 -1
  62. package/dist/connector/web/page.d.ts +59 -0
  63. package/dist/connector/web/page.d.ts.map +1 -0
  64. package/dist/connector/web/page.js +112 -0
  65. package/dist/connector/web/page.js.map +1 -0
  66. package/dist/connector/web/types.d.ts +47 -0
  67. package/dist/connector/web/types.d.ts.map +1 -1
  68. package/dist/connector/web/types.js.map +1 -1
  69. package/dist/constants/continuation.d.ts +8 -0
  70. package/dist/constants/continuation.d.ts.map +1 -1
  71. package/dist/constants/continuation.js +8 -0
  72. package/dist/constants/continuation.js.map +1 -1
  73. package/dist/constants/plugin/index.d.ts +1 -1
  74. package/dist/constants/plugin/index.d.ts.map +1 -1
  75. package/dist/constants/plugin/index.js +1 -1
  76. package/dist/constants/plugin/index.js.map +1 -1
  77. package/dist/contracts/a2a.d.ts +2 -2
  78. package/dist/manager/agent/lifecycle.d.ts +7 -0
  79. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  80. package/dist/manager/agent/lifecycle.js +11 -2
  81. package/dist/manager/agent/lifecycle.js.map +1 -1
  82. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  83. package/dist/manager/session/turn-recorder.js +4 -2
  84. package/dist/manager/session/turn-recorder.js.map +1 -1
  85. package/dist/plugin/index.d.ts +1 -1
  86. package/dist/plugin/index.d.ts.map +1 -1
  87. package/dist/plugin/index.js +1 -1
  88. package/dist/plugin/index.js.map +1 -1
  89. package/dist/plugin/lifecycle.d.ts +6 -0
  90. package/dist/plugin/lifecycle.d.ts.map +1 -1
  91. package/dist/plugin/lifecycle.js +98 -114
  92. package/dist/plugin/lifecycle.js.map +1 -1
  93. package/dist/plugin/shell-hook.d.ts +90 -22
  94. package/dist/plugin/shell-hook.d.ts.map +1 -1
  95. package/dist/plugin/shell-hook.js +434 -41
  96. package/dist/plugin/shell-hook.js.map +1 -1
  97. package/dist/provider/capabilities.d.ts +9 -0
  98. package/dist/provider/capabilities.d.ts.map +1 -1
  99. package/dist/provider/capabilities.js +15 -0
  100. package/dist/provider/capabilities.js.map +1 -1
  101. package/dist/provider/errors.d.ts.map +1 -1
  102. package/dist/provider/errors.js +13 -0
  103. package/dist/provider/errors.js.map +1 -1
  104. package/dist/provider/fallback.d.ts.map +1 -1
  105. package/dist/provider/fallback.js +10 -0
  106. package/dist/provider/fallback.js.map +1 -1
  107. package/dist/provider/idle-timeout.d.ts.map +1 -1
  108. package/dist/provider/idle-timeout.js +5 -0
  109. package/dist/provider/idle-timeout.js.map +1 -1
  110. package/dist/provider/image-input-refusal.d.ts +9 -0
  111. package/dist/provider/image-input-refusal.d.ts.map +1 -0
  112. package/dist/provider/image-input-refusal.js +54 -0
  113. package/dist/provider/image-input-refusal.js.map +1 -0
  114. package/dist/provider/index.d.ts +2 -1
  115. package/dist/provider/index.d.ts.map +1 -1
  116. package/dist/provider/index.js +2 -1
  117. package/dist/provider/index.js.map +1 -1
  118. package/dist/provider/retry.d.ts +7 -2
  119. package/dist/provider/retry.d.ts.map +1 -1
  120. package/dist/provider/retry.js +38 -4
  121. package/dist/provider/retry.js.map +1 -1
  122. package/dist/public-runtime.d.ts +5 -2
  123. package/dist/public-runtime.d.ts.map +1 -1
  124. package/dist/public-runtime.js +5 -2
  125. package/dist/public-runtime.js.map +1 -1
  126. package/dist/public-types.d.ts +2 -2
  127. package/dist/public-types.d.ts.map +1 -1
  128. package/dist/runtime/jobs/registry.d.ts +21 -0
  129. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  130. package/dist/runtime/jobs/registry.js +15 -2
  131. package/dist/runtime/jobs/registry.js.map +1 -1
  132. package/dist/runtime/query/deferred-sandbox.d.ts +54 -0
  133. package/dist/runtime/query/deferred-sandbox.d.ts.map +1 -0
  134. package/dist/runtime/query/deferred-sandbox.js +75 -0
  135. package/dist/runtime/query/deferred-sandbox.js.map +1 -0
  136. package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
  137. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  138. package/dist/runtime/query/executor/tool-call-admission.js +74 -13
  139. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  140. package/dist/runtime/query/executor.d.ts +51 -2
  141. package/dist/runtime/query/executor.d.ts.map +1 -1
  142. package/dist/runtime/query/executor.js +214 -9
  143. package/dist/runtime/query/executor.js.map +1 -1
  144. package/dist/runtime/query/finalize-turn.d.ts +6 -0
  145. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  146. package/dist/runtime/query/finalize-turn.js +1 -1
  147. package/dist/runtime/query/finalize-turn.js.map +1 -1
  148. package/dist/runtime/query/index.d.ts +29 -0
  149. package/dist/runtime/query/index.d.ts.map +1 -1
  150. package/dist/runtime/query/index.js +108 -28
  151. package/dist/runtime/query/index.js.map +1 -1
  152. package/dist/runtime/query/iteration/index.d.ts +57 -0
  153. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  154. package/dist/runtime/query/iteration/index.js +380 -28
  155. package/dist/runtime/query/iteration/index.js.map +1 -1
  156. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +61 -0
  157. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
  158. package/dist/runtime/query/iteration/phases/compaction-breaker.js +94 -0
  159. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
  160. package/dist/runtime/query/iteration/phases/compaction.d.ts +42 -1
  161. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  162. package/dist/runtime/query/iteration/phases/compaction.js +203 -23
  163. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  164. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  165. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  166. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  167. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  168. package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
  169. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  170. package/dist/runtime/query/iteration/provider-rejected-image.d.ts +21 -1
  171. package/dist/runtime/query/iteration/provider-rejected-image.d.ts.map +1 -1
  172. package/dist/runtime/query/iteration/provider-rejected-image.js +53 -7
  173. package/dist/runtime/query/iteration/provider-rejected-image.js.map +1 -1
  174. package/dist/runtime/query/iteration/request-repair.d.ts +108 -0
  175. package/dist/runtime/query/iteration/request-repair.d.ts.map +1 -0
  176. package/dist/runtime/query/iteration/request-repair.js +593 -0
  177. package/dist/runtime/query/iteration/request-repair.js.map +1 -0
  178. package/dist/runtime/query/iteration/stream-turn.d.ts +6 -0
  179. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  180. package/dist/runtime/query/iteration/stream-turn.js +2 -2
  181. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  182. package/dist/runtime/query/path-escalation.d.ts +36 -0
  183. package/dist/runtime/query/path-escalation.d.ts.map +1 -0
  184. package/dist/runtime/query/path-escalation.js +184 -0
  185. package/dist/runtime/query/path-escalation.js.map +1 -0
  186. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  187. package/dist/runtime/query/plugin-hooks.js +8 -0
  188. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  189. package/dist/runtime/query/reminders.d.ts +35 -0
  190. package/dist/runtime/query/reminders.d.ts.map +1 -0
  191. package/dist/runtime/query/reminders.js +7 -0
  192. package/dist/runtime/query/reminders.js.map +1 -0
  193. package/dist/runtime/query/request-rich-content.d.ts +5 -1
  194. package/dist/runtime/query/request-rich-content.d.ts.map +1 -1
  195. package/dist/runtime/query/request-rich-content.js +67 -25
  196. package/dist/runtime/query/request-rich-content.js.map +1 -1
  197. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  198. package/dist/runtime/query/resume-pending.js +26 -0
  199. package/dist/runtime/query/resume-pending.js.map +1 -1
  200. package/dist/runtime/query/review-policy.d.ts +26 -0
  201. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  202. package/dist/runtime/query/review-policy.js +79 -5
  203. package/dist/runtime/query/review-policy.js.map +1 -1
  204. package/dist/runtime/query/tool-output-budget.d.ts +46 -0
  205. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  206. package/dist/runtime/query/tool-output-budget.js +85 -1
  207. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  208. package/dist/runtime/query/tooling.d.ts +1 -0
  209. package/dist/runtime/query/tooling.d.ts.map +1 -1
  210. package/dist/runtime/query/tooling.js +5 -0
  211. package/dist/runtime/query/tooling.js.map +1 -1
  212. package/dist/session/paths.d.ts +5 -1
  213. package/dist/session/paths.d.ts.map +1 -1
  214. package/dist/session/paths.js +8 -0
  215. package/dist/session/paths.js.map +1 -1
  216. package/dist/store/memory/disk.d.ts.map +1 -1
  217. package/dist/store/memory/disk.js +2 -2
  218. package/dist/store/memory/disk.js.map +1 -1
  219. package/dist/store/memory/markdown.d.ts.map +1 -1
  220. package/dist/store/memory/markdown.js +2 -2
  221. package/dist/store/memory/markdown.js.map +1 -1
  222. package/dist/store/memory/naming.d.ts +9 -0
  223. package/dist/store/memory/naming.d.ts.map +1 -1
  224. package/dist/store/memory/naming.js +12 -0
  225. package/dist/store/memory/naming.js.map +1 -1
  226. package/dist/tools/builtins/bash.d.ts +30 -1
  227. package/dist/tools/builtins/bash.d.ts.map +1 -1
  228. package/dist/tools/builtins/bash.js +170 -26
  229. package/dist/tools/builtins/bash.js.map +1 -1
  230. package/dist/tools/builtins/computer-use.d.ts +1 -1
  231. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  232. package/dist/tools/builtins/computer-use.js +12 -5
  233. package/dist/tools/builtins/computer-use.js.map +1 -1
  234. package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
  235. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  236. package/dist/tools/builtins/content-fingerprint.js +4 -0
  237. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  238. package/dist/tools/builtins/edit.d.ts.map +1 -1
  239. package/dist/tools/builtins/edit.js +60 -18
  240. package/dist/tools/builtins/edit.js.map +1 -1
  241. package/dist/tools/builtins/grep.d.ts +8 -1
  242. package/dist/tools/builtins/grep.d.ts.map +1 -1
  243. package/dist/tools/builtins/grep.js +152 -33
  244. package/dist/tools/builtins/grep.js.map +1 -1
  245. package/dist/tools/builtins/read-file.d.ts +1 -0
  246. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  247. package/dist/tools/builtins/read-file.js +44 -5
  248. package/dist/tools/builtins/read-file.js.map +1 -1
  249. package/dist/tools/builtins/read-render.d.ts +52 -0
  250. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  251. package/dist/tools/builtins/read-render.js +143 -0
  252. package/dist/tools/builtins/read-render.js.map +1 -1
  253. package/dist/tools/builtins/web.d.ts +13 -2
  254. package/dist/tools/builtins/web.d.ts.map +1 -1
  255. package/dist/tools/builtins/web.js +225 -32
  256. package/dist/tools/builtins/web.js.map +1 -1
  257. package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
  258. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  259. package/dist/tools/coordinator/ask-user-question.js +256 -92
  260. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  261. package/dist/tools/coordinator/question-options.d.ts +1 -0
  262. package/dist/tools/coordinator/question-options.d.ts.map +1 -1
  263. package/dist/tools/coordinator/question-options.js +6 -1
  264. package/dist/tools/coordinator/question-options.js.map +1 -1
  265. package/dist/tools/defineTool.d.ts +4 -1
  266. package/dist/tools/defineTool.d.ts.map +1 -1
  267. package/dist/tools/defineTool.js +6 -1
  268. package/dist/tools/defineTool.js.map +1 -1
  269. package/dist/tools/paths.d.ts +6 -0
  270. package/dist/tools/paths.d.ts.map +1 -1
  271. package/dist/tools/paths.js +1 -1
  272. package/dist/tools/paths.js.map +1 -1
  273. package/dist/types/agent/query.d.ts +3 -0
  274. package/dist/types/agent/query.d.ts.map +1 -1
  275. package/dist/types/errors/catalog.js +1 -1
  276. package/dist/types/errors/catalog.js.map +1 -1
  277. package/dist/types/hitl/index.d.ts +104 -0
  278. package/dist/types/hitl/index.d.ts.map +1 -1
  279. package/dist/types/hitl/index.js.map +1 -1
  280. package/dist/types/message/index.d.ts +1 -1
  281. package/dist/types/message/index.d.ts.map +1 -1
  282. package/dist/types/message/index.js +2 -0
  283. package/dist/types/message/index.js.map +1 -1
  284. package/dist/types/plugin/index.d.ts +93 -19
  285. package/dist/types/plugin/index.d.ts.map +1 -1
  286. package/dist/types/plugin/index.js +3 -0
  287. package/dist/types/plugin/index.js.map +1 -1
  288. package/dist/types/provider/errors.d.ts +19 -0
  289. package/dist/types/provider/errors.d.ts.map +1 -1
  290. package/dist/types/provider/errors.js +54 -5
  291. package/dist/types/provider/errors.js.map +1 -1
  292. package/dist/types/provider/interface.d.ts +12 -0
  293. package/dist/types/provider/interface.d.ts.map +1 -1
  294. package/dist/types/provider/stream.d.ts +1 -1
  295. package/dist/types/session/config.d.ts +14 -0
  296. package/dist/types/session/config.d.ts.map +1 -1
  297. package/dist/types/session/events.d.ts +27 -4
  298. package/dist/types/session/events.d.ts.map +1 -1
  299. package/dist/types/session/events.js.map +1 -1
  300. package/dist/types/session/records.d.ts +9 -9
  301. package/dist/types/session/records.d.ts.map +1 -1
  302. package/dist/types/session/records.js +1 -0
  303. package/dist/types/session/records.js.map +1 -1
  304. package/dist/types/session/stop-reason.d.ts +5 -0
  305. package/dist/types/session/stop-reason.d.ts.map +1 -1
  306. package/dist/types/tool/index.d.ts +52 -0
  307. package/dist/types/tool/index.d.ts.map +1 -1
  308. package/dist/types/tool/index.js.map +1 -1
  309. package/package.json +4 -2
  310. package/src/agents/QueryAgent.ts +5 -1
  311. package/src/agents/SupervisorAgent.ts +1 -1
  312. package/src/agents/examples/SupervisorAgent.ts +217 -106
  313. package/src/agents/index.ts +1 -1
  314. package/src/authorization/protected-paths.ts +110 -0
  315. package/src/authorization/readonly-commands.ts +771 -0
  316. package/src/authorization/readonly-flags.ts +196 -0
  317. package/src/authorization/removal.ts +326 -0
  318. package/src/authorization/shell-paths.ts +927 -0
  319. package/src/bridge/acp/update.ts +1 -0
  320. package/src/compaction/context-window.ts +41 -0
  321. package/src/compaction/manual.ts +10 -2
  322. package/src/compaction/plan.ts +11 -2
  323. package/src/compaction/verifier.ts +84 -22
  324. package/src/config/runtime.ts +23 -2
  325. package/src/connector/web/guarded-fetch.ts +26 -0
  326. package/src/connector/web/index.ts +3 -0
  327. package/src/connector/web/page.ts +130 -0
  328. package/src/connector/web/types.ts +47 -0
  329. package/src/constants/continuation.ts +10 -0
  330. package/src/constants/plugin/index.ts +1 -1
  331. package/src/manager/agent/lifecycle.ts +12 -4
  332. package/src/manager/session/turn-recorder.ts +10 -2
  333. package/src/plugin/index.ts +1 -0
  334. package/src/plugin/lifecycle.ts +107 -119
  335. package/src/plugin/shell-hook.ts +477 -43
  336. package/src/provider/capabilities.ts +17 -0
  337. package/src/provider/errors.ts +13 -0
  338. package/src/provider/fallback.ts +10 -0
  339. package/src/provider/idle-timeout.ts +5 -0
  340. package/src/provider/image-input-refusal.ts +54 -0
  341. package/src/provider/index.ts +2 -0
  342. package/src/provider/retry.ts +42 -4
  343. package/src/public-runtime.ts +11 -0
  344. package/src/public-types.ts +4 -0
  345. package/src/runtime/jobs/registry.ts +39 -2
  346. package/src/runtime/query/deferred-sandbox.ts +110 -0
  347. package/src/runtime/query/executor/tool-call-admission.ts +79 -13
  348. package/src/runtime/query/executor.ts +267 -11
  349. package/src/runtime/query/finalize-turn.ts +7 -1
  350. package/src/runtime/query/index.ts +169 -52
  351. package/src/runtime/query/iteration/index.ts +439 -35
  352. package/src/runtime/query/iteration/phases/compaction-breaker.ts +108 -0
  353. package/src/runtime/query/iteration/phases/compaction.ts +241 -38
  354. package/src/runtime/query/iteration/phases/context.ts +11 -0
  355. package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
  356. package/src/runtime/query/iteration/provider-rejected-image.ts +71 -5
  357. package/src/runtime/query/iteration/request-repair.ts +743 -0
  358. package/src/runtime/query/iteration/stream-turn.ts +11 -1
  359. package/src/runtime/query/path-escalation.ts +204 -0
  360. package/src/runtime/query/plugin-hooks.ts +8 -0
  361. package/src/runtime/query/reminders.ts +45 -0
  362. package/src/runtime/query/request-rich-content.ts +77 -27
  363. package/src/runtime/query/resume-pending.ts +27 -0
  364. package/src/runtime/query/review-policy.ts +106 -5
  365. package/src/runtime/query/tool-output-budget.ts +115 -1
  366. package/src/runtime/query/tooling.ts +6 -0
  367. package/src/session/paths.ts +11 -1
  368. package/src/store/memory/disk.ts +2 -1
  369. package/src/store/memory/markdown.ts +2 -1
  370. package/src/store/memory/naming.ts +13 -0
  371. package/src/tools/builtins/bash.ts +203 -34
  372. package/src/tools/builtins/computer-use.ts +13 -4
  373. package/src/tools/builtins/content-fingerprint.ts +5 -0
  374. package/src/tools/builtins/edit.ts +69 -19
  375. package/src/tools/builtins/grep.ts +159 -38
  376. package/src/tools/builtins/read-file.ts +48 -5
  377. package/src/tools/builtins/read-render.ts +193 -0
  378. package/src/tools/builtins/web.ts +258 -33
  379. package/src/tools/coordinator/ask-user-question.ts +309 -103
  380. package/src/tools/coordinator/question-options.ts +8 -1
  381. package/src/tools/defineTool.ts +11 -2
  382. package/src/tools/paths.ts +1 -1
  383. package/src/types/agent/query.ts +3 -0
  384. package/src/types/errors/catalog.ts +1 -1
  385. package/src/types/hitl/index.ts +104 -0
  386. package/src/types/message/index.ts +2 -0
  387. package/src/types/plugin/index.ts +101 -18
  388. package/src/types/provider/errors.ts +66 -5
  389. package/src/types/provider/interface.ts +13 -0
  390. package/src/types/provider/stream.ts +1 -1
  391. package/src/types/session/config.ts +14 -0
  392. package/src/types/session/events.ts +32 -4
  393. package/src/types/session/records.ts +1 -0
  394. package/src/types/session/stop-reason.ts +5 -0
  395. package/src/types/tool/index.ts +53 -2
@@ -260,6 +260,19 @@ const OVERFLOW_BODY_PATTERNS: readonly RegExp[] = [
260
260
  // length" — so a pattern anchored on "exceeded" misses it entirely.
261
261
  /exceeds?\s+(?:the\s+)?(?:model'?s?\s+)?context\s+(?:length|window|size)/i,
262
262
  /context\s+(?:length|window|size)\s+exceed/i,
263
+ // The reference terminal's own list matches the bare phrase "context
264
+ // window". Here it must sit next to a verb that says the request did not
265
+ // fit ("larger than the model context window of N tokens"), so a 400 that
266
+ // only names the window ("does not support this parameter with the 1M
267
+ // context window beta") stays an invalid request. The same pattern lives in
268
+ // `types/provider/errors.ts` (`codeFromMessage`), which also gates it on the
269
+ // status; keep them equal.
270
+ /(?:exceed\w*|too (?:long|large|big)|larger than|longer than|over the|beyond|maximum|overflow\w*)\b[^.]{0,60}context window|context window[^.]{0,60}\b(?:exceed\w*|too (?:long|large|big)|larger than|longer than|over the|beyond|maximum|overflow\w*)/i,
271
+ // "input length and `max_tokens` exceed context limit: A + B > C". The
272
+ // prompt may fit and only the requested output not; the loop tries a smaller
273
+ // `max_tokens` first (readOutputOverflow) and falls back to shedding history
274
+ // when there is no room for a useful reply, which needs this to classify.
275
+ /input length and `?max_tokens`? exceed context limit/i,
263
276
  ]
264
277
 
265
278
  /** Does this response body say the request did not fit the window? */
@@ -512,6 +512,16 @@ export function withProviderFallback(
512
512
  member.provider.capabilities?.supportsHostedWebSearch === true &&
513
513
  (member.provider.supportsHostedWebSearchFor?.(member.model ?? model, mode) ?? true),
514
514
  ),
515
+ // Answered for the member the next request goes to first, the one the
516
+ // cursor rests on (it never rewinds). Unlike the head's other
517
+ // declarations this costs nothing to keep current, and the head's answer
518
+ // would strip images from a vision member serving behind a text-only head.
519
+ // A request that falls over mid-call to a member that cannot read images is
520
+ // refused there, and the runtime sends it again without them.
521
+ supportsImageInputFor: (model: string) => {
522
+ const serving = members[cursor] ?? first
523
+ return serving.provider.supportsImageInputFor?.(serving.model ?? model) ?? true
524
+ },
515
525
  reasoningEffortLevelsFor: (model: string, thinking?: ThinkingConfig) =>
516
526
  commonReasoningEffortLevels(members, model, thinking),
517
527
  // Present on the chain wrapper for the same reason the menu method is:
@@ -218,6 +218,11 @@ export function withStreamIdleTimeout(
218
218
  provider.supportsHostedWebSearchFor?.(model, mode) ?? false,
219
219
  }
220
220
  : {}),
221
+ ...(provider.supportsImageInputFor
222
+ ? {
223
+ supportsImageInputFor: (model: string) => provider.supportsImageInputFor?.(model) ?? true,
224
+ }
225
+ : {}),
221
226
  ...(provider.reasoningEffortLevelsFor
222
227
  ? {
223
228
  reasoningEffortLevelsFor: (
@@ -0,0 +1,54 @@
1
+ import { isProviderRequestError } from './errors.js'
2
+
3
+ /**
4
+ * Wording providers use when they refuse a request because the MODEL reads text
5
+ * only. There is no shared error code for this: each vendor writes its own
6
+ * sentence, so the match is by phrase.
7
+ *
8
+ * Every pattern names the model's input as text-only, or says the model does not
9
+ * take image input at all. A 400 about one image (its size, its format, its URL)
10
+ * or about text somewhere else in the request (a tool message, an output
11
+ * format) is not this, and must not match: the caller drops every image and
12
+ * stops sending any to the model for the rest of the conversation.
13
+ */
14
+ const IMAGE_INPUT_REFUSAL_PATTERNS: readonly RegExp[] = [
15
+ // Zen: "Beam supports text input only. This conversation contains an image."
16
+ /\btext\s+inputs?\s+only\b/i,
17
+ /\bonly\s+(?:supports?|accepts?)\s+text\s+inputs?\b/i,
18
+ /\bonly\s+text\s+inputs?\s+(?:is|are)\s+(?:supported|accepted|allowed)\b/i,
19
+ // "This model is text-only", "a text-only model"
20
+ /\btext[- ]only\s+model\b/i,
21
+ /\bmodel\b[^.;]{0,40}\bis\s+(?:a\s+)?text[- ]only\b/i,
22
+ // Local model servers: "<model> does not support images", "Model does not
23
+ // support images. Please use a model that does." Also "doesn't support vision",
24
+ // "does not support image input", "does not support multimodal inputs".
25
+ /\b(?:does\s?n[o'’]t|do\s+not|cannot|can[’']t)\s+(?:support|accept)\s+(?:images|vision)\b(?![/\w-])/i,
26
+ /\b(?:does\s?n[o'’]t|do\s+not|cannot|can[’']t)\s+(?:support|accept|process|handle)\s+(?:image|vision|multimodal|visual)\s+inputs?\b/i,
27
+ // A local inference server: "image input is not supported - hint: ... mmproj". The
28
+ // sentence must end there or name the model: "image input is not supported
29
+ // on streaming" is a restriction of the endpoint, not of the model.
30
+ /\b(?:image|vision|multimodal)\s+inputs?\s+(?:is|are)\s+(?:not\s+supported|unsupported)(?:\s+(?:by|for)\s+(?:this|the)\s+model)?\s*(?:$|[.;:,!)\-])/i,
31
+ /\bimages\s+(?:is|are)\s+not\s+supported(?:\s+(?:by|for)\s+(?:this|the)\s+model)?\s*(?:$|[.;:,!)\-])/i,
32
+ // A serving engine: "<model> is not a multimodal model"
33
+ /\bis\s+not\s+a\s+(?:multimodal|vision)\s+model\b/i,
34
+ // A chat-completions API: "Invalid content type. image_url is only supported by certain models."
35
+ /\bimage_url\b.{0,40}\bonly\s+supported\s+by\s+certain\s+models\b/i,
36
+ // A chat-completions server that only knows text parts: "unknown variant `image_url`, expected `text`"
37
+ /\bunknown\s+variant\s+\W?image_url\W?,?\s+expected\s+\W?text\b/i,
38
+ ]
39
+
40
+ /** Whether a provider's refusal text says "this model cannot take images". */
41
+ export function isImageInputRefusalMessage(text: string | undefined): boolean {
42
+ if (!text) return false
43
+ return IMAGE_INPUT_REFUSAL_PATTERNS.some((pattern) => pattern.test(text))
44
+ }
45
+
46
+ /**
47
+ * Whether `error` is a provider refusing the request because of an image it
48
+ * holds that the model cannot read. Resending the same request can never help;
49
+ * only removing the image or choosing a model that reads images does.
50
+ */
51
+ export function isImageInputRefusal(error: unknown): boolean {
52
+ if (!isProviderRequestError(error) || error.kind !== 'bad_request') return false
53
+ return isImageInputRefusalMessage(error.detail ?? error.message)
54
+ }
@@ -25,7 +25,9 @@ export { registerMock, MOCK_CAPABILITIES } from './mock-register.js'
25
25
  export {
26
26
  PERMISSIVE_PROVIDER_CAPABILITIES,
27
27
  resolveProviderCapabilities,
28
+ modelReadsImages,
28
29
  } from './capabilities.js'
30
+ export { isImageInputRefusal, isImageInputRefusalMessage } from './image-input-refusal.js'
29
31
  export type { ResolvedProviderCapabilities } from './capabilities.js'
30
32
  export {
31
33
  ProviderError,
@@ -54,7 +54,8 @@ export interface ProviderRetryConfig extends BackoffPolicy {
54
54
  */
55
55
  readonly retryAfter?: 'exact' | 'floor'
56
56
  /**
57
- * Also retry a stream that failed AFTER it produced text or reasoning.
57
+ * Also retry a stream that failed AFTER it produced text.
58
+ * (A stream that produced only reasoning is always retried.)
58
59
  *
59
60
  * Off by default, because the decorator cannot un-emit: the consumer has
60
61
  * already seen those deltas. When on, the retry notice carries
@@ -82,11 +83,34 @@ export interface WithProviderRetryOptions {
82
83
  readonly sleepFn?: (ms: number, signal?: AbortSignal) => Promise<void>
83
84
  }
84
85
 
86
+ /**
87
+ * A chunk that carries a fragment of a reasoning block and nothing a consumer
88
+ * would have to take back: no text, tool call, hosted tool, citation or finish.
89
+ */
90
+ function isReasoningOnly(chunk: StreamChunk): boolean {
91
+ const d = chunk.delta
92
+ return (
93
+ d.reasoning !== undefined &&
94
+ !d.content &&
95
+ !d.toolCalls?.length &&
96
+ d.toolCallEnd === undefined &&
97
+ d.hostedTool === undefined &&
98
+ d.citation === undefined &&
99
+ d.textPart === undefined &&
100
+ chunk.textParts === undefined &&
101
+ chunk.finishReason === undefined
102
+ )
103
+ }
104
+
85
105
  /**
86
106
  * Wrap a provider so transient failures are retried with exponential
87
107
  * backoff and full jitter.
88
108
  *
89
- * **Retry only before the first content chunk.** Once a delta has been
109
+ * **Retry only before the first content chunk.** Reasoning-only chunks do
110
+ * not count as content: a stream that fails after yielding nothing but
111
+ * reasoning is retried, and the notice carries `restart: true` so the consumer
112
+ * drops the thinking the failed attempt showed (the turn replaces reasoning
113
+ * blocks by index, and clears them on a restart). Once a text or tool-call delta has been
90
114
  * yielded the consumer has already emitted `text_delta` events and
91
115
  * appended to its buffer, so restarting the request would duplicate
92
116
  * output. A mid-stream failure is therefore surfaced, not retried — the
@@ -129,6 +153,11 @@ export function withProviderRetry(
129
153
  async function* chatStream(params: ChatCompletionParams): AsyncIterable<StreamChunk> {
130
154
  for (let attempt = 0; ; attempt++) {
131
155
  let produced = false
156
+ // Reasoning has streamed and nothing else has. The consumer holds
157
+ // reasoning by block index and has no text or tool call to repeat, so
158
+ // this is retryable without `retryMidStream`; the notice says
159
+ // `restart` so a host that showed the thinking drops it.
160
+ let reasoned = false
132
161
  // A tool call or hosted tool began: past this point a restart could
133
162
  // repeat work that is already underway, so it is never offered.
134
163
  let acted = false
@@ -140,7 +169,10 @@ export function withProviderRetry(
140
169
  const isErrorOnly =
141
170
  chunk.error !== undefined && !chunk.delta.content && !chunk.delta.toolCalls?.length
142
171
 
143
- if (!isErrorOnly) produced = true
172
+ if (!isErrorOnly) {
173
+ if (isReasoningOnly(chunk)) reasoned = true
174
+ else produced = true
175
+ }
144
176
  if (chunk.delta.toolCalls?.length || chunk.delta.toolCallEnd || chunk.delta.hostedTool) {
145
177
  acted = true
146
178
  }
@@ -170,7 +202,8 @@ export function withProviderRetry(
170
202
  const classified = classifyProviderError(err, provider.id)
171
203
  const exhausted = attempt >= config.maxRetries
172
204
 
173
- const restartable = produced && config.retryMidStream === true && !acted
205
+ const restartable =
206
+ (produced && config.retryMidStream === true && !acted) || (!produced && reasoned)
174
207
  if ((produced && !restartable) || !classified.retryable || exhausted) {
175
208
  log?.warn('Provider call failed', {
176
209
  [GENAI.SYSTEM]: provider.id,
@@ -326,6 +359,11 @@ export function withProviderRetry(
326
359
  provider.supportsHostedWebSearchFor?.(model, mode) ?? false,
327
360
  }
328
361
  : {}),
362
+ ...(provider.supportsImageInputFor
363
+ ? {
364
+ supportsImageInputFor: (model: string) => provider.supportsImageInputFor?.(model) ?? true,
365
+ }
366
+ : {}),
329
367
  ...(provider.reasoningEffortLevelsFor
330
368
  ? {
331
369
  reasoningEffortLevelsFor: (
@@ -318,6 +318,12 @@ export {
318
318
  formatSteeringNote,
319
319
  } from './runtime/query/steering.js'
320
320
  export type { SteeringChannel } from './runtime/query/steering.js'
321
+ // Hidden mid-conversation reminders a host feeds per model request (opt-in).
322
+ export { isReminderMessage } from './runtime/query/reminders.js'
323
+ export type {
324
+ ReminderContext,
325
+ ReminderSource,
326
+ } from './runtime/query/reminders.js'
321
327
  export {
322
328
  BidiSessionCloseTimeoutError,
323
329
  createMockBidiProvider,
@@ -628,6 +634,7 @@ export {
628
634
  runShellHook,
629
635
  SHELL_HOOK_EVENTS,
630
636
  SHELL_HOOKS_PLUGIN_ID,
637
+ shellHookIfMatches,
631
638
  shellHookMatches,
632
639
  shellHookVerdict,
633
640
  } from './plugin/index.js'
@@ -718,8 +725,11 @@ export {
718
725
  isProviderError,
719
726
  LazyProviderLoadError,
720
727
  LazyProviderSyncCreateError,
728
+ isImageInputRefusal,
729
+ isImageInputRefusalMessage,
721
730
  MOCK_CAPABILITIES,
722
731
  MockLLMProvider,
732
+ modelReadsImages,
723
733
  parseRetryAfterMs,
724
734
  PERMISSIVE_PROVIDER_CAPABILITIES,
725
735
  ProviderError,
@@ -1505,6 +1515,7 @@ export {
1505
1515
  // consumer (`SpanProcessorLike`, then `CompactNowInput` and `CompactionResult`).
1506
1516
  // The gate is the reason this is a list rather than a fourth accident.
1507
1517
  export type { ResolvedContextWindow } from './compaction/context-window.js'
1518
+ export { resolveTriggerThreshold } from './compaction/context-window.js'
1508
1519
  export type { CompactRegionInput } from './compaction/manual.js'
1509
1520
  export type {
1510
1521
  CompactionVerificationOptions,
@@ -204,6 +204,7 @@ export type {
204
204
  Disposable,
205
205
  RunAgentOptions,
206
206
  RunAgentResult,
207
+ SupervisorResumeTarget,
207
208
  } from './agents/index.js'
208
209
 
209
210
  export type {
@@ -457,7 +458,10 @@ export type {
457
458
 
458
459
  export type {
459
460
  GuardedFetchConfig,
461
+ WebContentExtractor,
462
+ WebExtractRequest,
460
463
  WebFetchProvider,
464
+ WebFetchRedirectPolicy,
461
465
  WebFetchRefusalReason,
462
466
  WebFetchRequest,
463
467
  WebFetchResult,
@@ -99,6 +99,21 @@ export interface StartJobParams {
99
99
  * expect stdin.
100
100
  */
101
101
  readonly spawn?: () => JobProcess
102
+ /**
103
+ * Output the process already wrote before the registry took it over, in
104
+ * the order it was written. Used when a foreground command is moved to the
105
+ * background, so a later `read` starts with what the model was shown.
106
+ */
107
+ readonly initialOutput?: readonly {
108
+ readonly stream: 'stdout' | 'stderr'
109
+ readonly text: string
110
+ }[]
111
+ /**
112
+ * `spawn` returned a process on THIS host that the host shell started (a
113
+ * foreground command being moved to the background), so its bytes are
114
+ * decoded the way a foreground command's are, not as a Linux guest's.
115
+ */
116
+ readonly hostOutput?: boolean
102
117
  }
103
118
 
104
119
  export interface BackgroundJobRegistryConfig {
@@ -333,8 +348,12 @@ export class BackgroundJobRegistry {
333
348
  // Raw bytes, decoded here: a Windows console writes its OEM code page.
334
349
  // A process a sandbox started runs a Linux guest, so it is UTF-8.
335
350
  const decoders = {
336
- stdout: createHostOutputDecoder(params.spawn ? { platform: 'linux' } : {}),
337
- stderr: createHostOutputDecoder(params.spawn ? { platform: 'linux' } : {}),
351
+ stdout: createHostOutputDecoder(
352
+ params.spawn && !params.hostOutput ? { platform: 'linux' } : {},
353
+ ),
354
+ stderr: createHostOutputDecoder(
355
+ params.spawn && !params.hostOutput ? { platform: 'linux' } : {},
356
+ ),
338
357
  }
339
358
  let startError: string | undefined
340
359
  let settleStarted!: () => void
@@ -473,6 +492,7 @@ export class BackgroundJobRegistry {
473
492
  const text = typeof data === 'string' ? data : decoders[stream].write(data)
474
493
  if (text) append(text, stream)
475
494
  }
495
+ for (const seed of params.initialOutput ?? []) append(seed.text, seed.stream)
476
496
  child.stdout?.on('data', onData('stdout'))
477
497
  child.stderr?.on('data', onData('stderr'))
478
498
 
@@ -855,6 +875,23 @@ export function bindOwner(
855
875
  : {}),
856
876
  })
857
877
  },
878
+ // A foreground command that ran out of time, kept instead of killed.
879
+ // The same owner and cap as `start`; the process is the one already
880
+ // running, with what it has written so far.
881
+ adopt: (params: {
882
+ command: string
883
+ child: ReturnType<typeof spawn>
884
+ workingDirectory?: string
885
+ initialOutput?: StartJobParams['initialOutput']
886
+ }) =>
887
+ registry.start({
888
+ owner,
889
+ command: params.command,
890
+ workingDirectory: params.workingDirectory ?? defaults.workingDirectory ?? process.cwd(),
891
+ spawn: () => ({ child: params.child }),
892
+ hostOutput: true,
893
+ ...(params.initialOutput ? { initialOutput: params.initialOutput } : {}),
894
+ }),
858
895
  get: (id: string) => mine(id),
859
896
  awaitStarted: (id: string) => {
860
897
  mine(id)
@@ -0,0 +1,110 @@
1
+ import type { Sandbox } from '../../types/sandbox/index.js'
2
+ import type { SandboxAcquisitionResult } from './sandbox-lifecycle.js'
3
+
4
+ /**
5
+ * When a turn starts acquiring its sandbox.
6
+ *
7
+ * - `'eager'`: before the input guardrails and the first model request, and the
8
+ * turn waits for it. This is the behaviour every sandboxed turn had before
9
+ * the option existed.
10
+ * - `'concurrent'`: at the same point, but the turn does not wait. The first
11
+ * model request goes out while the provider allocates, and the first tool
12
+ * call waits for the handle.
13
+ * - `'on-first-use'`: not until the first tool call needs one.
14
+ */
15
+ export type SandboxAcquireMode = 'eager' | 'concurrent' | 'on-first-use'
16
+
17
+ export type SandboxAcquisitionStop = Exclude<SandboxAcquisitionResult, { kind: 'created' }>
18
+
19
+ /**
20
+ * A deferred acquisition ended without a sandbox for a reason the turn settles
21
+ * with a stop reason (`cancelled`, `timeout`), exactly as an eager acquisition
22
+ * does. A provider failure is not wrapped: it propagates as the provider's own
23
+ * error, as it does when the turn waits.
24
+ */
25
+ export class SandboxAcquisitionStopped extends Error {
26
+ readonly acquisition: SandboxAcquisitionStop
27
+
28
+ constructor(acquisition: SandboxAcquisitionStop) {
29
+ super(
30
+ acquisition.kind === 'timed_out'
31
+ ? acquisition.error.message
32
+ : 'Sandbox acquisition was cancelled before it finished',
33
+ )
34
+ this.name = 'SandboxAcquisitionStopped'
35
+ this.acquisition = acquisition
36
+ }
37
+ }
38
+
39
+ type Outcome = SandboxAcquisitionResult | { readonly kind: 'failed'; readonly error: unknown }
40
+
41
+ export interface DeferredSandbox {
42
+ /** Start acquiring if nothing has yet. Idempotent. */
43
+ start(): void
44
+ /** Start if needed, then wait for the handle to be published. Throws when none will be. */
45
+ ensure(): Promise<void>
46
+ /**
47
+ * Wait for an acquisition that was started. A no-op when none was, and when
48
+ * the turn has already been cancelled (the loop settles that on its own).
49
+ */
50
+ settle(): Promise<void>
51
+ /** Withdraw authority from a still-pending acquisition and wait until it has settled. Never throws. */
52
+ cancel(): Promise<void>
53
+ }
54
+
55
+ /**
56
+ * A sandbox acquisition that outlives the point that started it.
57
+ *
58
+ * `acquire` is the same lifecycle function an eager turn uses, so a late
59
+ * handle, a timeout and a cancellation are released by the machinery that
60
+ * already owns them. `publish` runs once, when (and only when) the handle
61
+ * arrived in time, so `sandbox_created` marks the moment of resolution.
62
+ */
63
+ export function createDeferredSandbox(options: {
64
+ readonly turnSignal: AbortSignal
65
+ readonly acquire: (signal: AbortSignal) => Promise<SandboxAcquisitionResult>
66
+ readonly publish: (sandbox: Sandbox) => Promise<void>
67
+ }): DeferredSandbox {
68
+ const controller = new AbortController()
69
+ const relay = (): void => controller.abort(options.turnSignal.reason)
70
+ if (options.turnSignal.aborted) relay()
71
+ else options.turnSignal.addEventListener('abort', relay, { once: true })
72
+
73
+ let outcome: Promise<Outcome> | undefined
74
+ const start = (): Promise<Outcome> => {
75
+ outcome ??= (async (): Promise<Outcome> => {
76
+ try {
77
+ const acquired = await options.acquire(controller.signal)
78
+ if (acquired.kind === 'created') await options.publish(acquired.sandbox)
79
+ return acquired
80
+ } catch (error) {
81
+ return { kind: 'failed', error }
82
+ }
83
+ })()
84
+ return outcome
85
+ }
86
+
87
+ const rethrow = (settled: Outcome): void => {
88
+ if (settled.kind === 'created') return
89
+ if (settled.kind === 'failed') throw settled.error
90
+ throw new SandboxAcquisitionStopped(settled)
91
+ }
92
+
93
+ return {
94
+ start: () => {
95
+ void start()
96
+ },
97
+ ensure: async () => rethrow(await start()),
98
+ settle: async () => {
99
+ if (outcome === undefined) return
100
+ const settled = await outcome
101
+ if (options.turnSignal.aborted) return
102
+ rethrow(settled)
103
+ },
104
+ cancel: async () => {
105
+ options.turnSignal.removeEventListener('abort', relay)
106
+ controller.abort()
107
+ if (outcome !== undefined) await outcome
108
+ },
109
+ }
110
+ }
@@ -2,7 +2,7 @@ import { GENAI, NAMZU } from '../../../constants/telemetry/index.js'
2
2
  import { callableToolNames, formatToolNames } from '../../../registry/tool/callable.js'
3
3
  import { renderToolSchema } from '../../../registry/tool/schema.js'
4
4
  import type { ToolCall, ToolInputError } from '../../../types/message/index.js'
5
- import type { PluginHookResult } from '../../../types/plugin/index.js'
5
+ import type { PluginHookPermission, PluginHookResult } from '../../../types/plugin/index.js'
6
6
  import type { ToolDefinition } from '../../../types/tool/index.js'
7
7
  import type { ToolCallRepair, ToolCallRepairReason } from '../../../types/tool/repair.js'
8
8
  import { awaitWithAbort } from '../../../utils/await-with-abort.js'
@@ -10,6 +10,7 @@ import { toErrorMessage } from '../../../utils/error.js'
10
10
  import type { Logger } from '../../../utils/logger.js'
11
11
  import type {
12
12
  EmitEvent,
13
+ PreToolHookNotes,
13
14
  PreToolHookOutcome,
14
15
  PreparedDirectCall,
15
16
  ToolExecutorConfig,
@@ -49,6 +50,11 @@ export interface ToolAdmissionHost {
49
50
  * step, which is the answer that sent a model round in a circle.
50
51
  */
51
52
  readonly allowedTools: readonly string[] | undefined
53
+ /**
54
+ * A hook asked for the turn to stop after the current step. Optional so a
55
+ * host that builds an admission by hand (a test) need not say anything.
56
+ */
57
+ readonly requestHalt?: (reason: string) => void
52
58
  }
53
59
 
54
60
  /**
@@ -89,7 +95,7 @@ export async function runPreToolHook(
89
95
  signal,
90
96
  )
91
97
  signal.throwIfAborted()
92
- return interpretPreToolResults(toolName, input, results)
98
+ return interpretPreToolResults(toolName, input, results, host.requestHalt)
93
99
  }
94
100
 
95
101
  export async function prepareDirectCall(
@@ -169,6 +175,7 @@ export async function prepareDirectCall(
169
175
  ...(providerInput && !truncationRepair && !preOutcome.modified && resolved.inputFailure
170
176
  ? { inputFailure: resolved.inputFailure }
171
177
  : {}),
178
+ ...hookNotesOf(preOutcome),
172
179
  }
173
180
  }
174
181
  return {
@@ -321,6 +328,20 @@ export async function prepareDirectCall(
321
328
  toolName,
322
329
  input: preparation.prepared.input,
323
330
  prepared: preparation.prepared,
331
+ ...hookNotesOf(preOutcome),
332
+ }
333
+ }
334
+
335
+ /** The hook answers a ready or legacy call carries forward, or nothing. */
336
+ function hookNotesOf(outcome: Extract<PreToolHookOutcome, { kind: 'continue' }>): {
337
+ hook?: PreToolHookNotes
338
+ } {
339
+ if (outcome.permission === undefined && outcome.context === undefined) return {}
340
+ return {
341
+ hook: {
342
+ ...(outcome.permission ? { permission: outcome.permission } : {}),
343
+ ...(outcome.context !== undefined ? { context: outcome.context } : {}),
344
+ },
324
345
  }
325
346
  }
326
347
 
@@ -328,29 +349,49 @@ function interpretPreToolResults(
328
349
  toolName: string,
329
350
  initialInput: unknown,
330
351
  results: readonly PluginHookResult[],
352
+ requestHalt?: (reason: string) => void,
331
353
  ): PreToolHookOutcome {
332
354
  let currentInput = initialInput
333
355
  let modified = false
356
+ let permission: PluginHookPermission | undefined
357
+ const contexts: string[] = []
358
+ /** `ask` outranks `allow`: the stricter answer is the one that stands. */
359
+ const takePermission = (next: PluginHookPermission | undefined) => {
360
+ if (next === undefined) return
361
+ if (permission === undefined || (permission.decision === 'allow' && next.decision === 'ask')) {
362
+ permission = next
363
+ }
364
+ }
365
+ const withContext = (text: string): string =>
366
+ contexts.length === 0 ? text : `${text}\n\n${hookNote(contexts)}`
367
+ // The hooks ran side by side on the original input, and their answers are
368
+ // combined here. The first refusal (or failure) in registration order is the
369
+ // answer, whatever the others said; of several rewrites the last wins;
370
+ // `ask` outranks `allow`; every context is kept; every request to stop is
371
+ // passed on.
372
+ let refusal: { kind: 'skip' | 'error'; text: string } | undefined
334
373
  for (const result of results) {
335
374
  switch (result.action) {
336
375
  case 'continue':
376
+ takePermission(result.permission)
377
+ if (result.context) contexts.push(result.context)
378
+ if (result.halt) requestHalt?.(result.halt.reason)
337
379
  continue
338
380
  case 'modify':
339
381
  currentInput = result.input
340
382
  modified = true
383
+ takePermission(result.permission)
384
+ if (result.context) contexts.push(result.context)
385
+ if (result.halt) requestHalt?.(result.halt.reason)
341
386
  continue
342
387
  case 'skip':
343
- return {
344
- kind: 'skip',
345
- input: currentInput,
346
- output: skippedToolResultText(toolName, result.reason),
347
- }
388
+ if (result.context) contexts.push(result.context)
389
+ if (result.halt) requestHalt?.(result.halt.reason)
390
+ refusal ??= { kind: 'skip', text: skippedToolResultText(toolName, result.reason) }
391
+ continue
348
392
  case 'error':
349
- return {
350
- kind: 'error',
351
- input: currentInput,
352
- output: `Error: ${result.message}`,
353
- }
393
+ refusal ??= { kind: 'error', text: `Error: ${result.message}` }
394
+ continue
354
395
  case 'retry':
355
396
  case 'annotate':
356
397
  // There is no result to replace yet. Rejecting loudly beats
@@ -367,9 +408,34 @@ function interpretPreToolResults(
367
408
  }
368
409
  }
369
410
  }
370
- return { kind: 'continue', input: currentInput, modified }
411
+ if (refusal) {
412
+ return {
413
+ kind: refusal.kind,
414
+ input: currentInput,
415
+ output: refusal.kind === 'skip' ? withContext(refusal.text) : refusal.text,
416
+ }
417
+ }
418
+ return {
419
+ kind: 'continue',
420
+ input: currentInput,
421
+ modified,
422
+ ...(permission ? { permission } : {}),
423
+ ...(contexts.length > 0 ? { context: contexts.join('\n') } : {}),
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Hook text, framed so the model reads it as what a configured hook said and
429
+ * not as something the tool returned.
430
+ */
431
+ export function hookNote(texts: readonly string[]): string {
432
+ const joined = texts.join('\n')
433
+ return `[Hook note]\n${joined.length > MAX_HOOK_NOTE_CHARS ? `${joined.slice(0, MAX_HOOK_NOTE_CHARS)}\n[truncated]` : joined}`
371
434
  }
372
435
 
436
+ /** A hook's text is one more thing for the model to read; it gets a bound. */
437
+ const MAX_HOOK_NOTE_CHARS = 8_000
438
+
373
439
  /**
374
440
  * Turn the call the model issued into a name and a parsed input, giving
375
441
  * a configured repairer one chance to fix it first.