@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
@@ -32,7 +32,7 @@ import {
32
32
  createToolMessage,
33
33
  } from '../../types/message/index.js'
34
34
  import type { PermissionMode } from '../../types/permission/index.js'
35
- import type { PluginHookResult } from '../../types/plugin/index.js'
35
+ import type { PluginHookPermission, PluginHookResult } from '../../types/plugin/index.js'
36
36
  import type { ChatCompletionResponse } from '../../types/provider/index.js'
37
37
  import type { Sandbox } from '../../types/sandbox/index.js'
38
38
  import type { AuditEventInput } from '../../types/session/audit.js'
@@ -65,6 +65,7 @@ import { type BackgroundJobRegistry, type JobProcess, bindOwner } from '../jobs/
65
65
  import {
66
66
  type ToolAdmissionHost,
67
67
  formatFailedToolOutput,
68
+ hookNote,
68
69
  isUnregistered,
69
70
  prepareDirectCall,
70
71
  runPreToolHook,
@@ -73,14 +74,17 @@ import {
73
74
  import { describeVisibleFileEvidence } from './file-evidence-context.js'
74
75
  import { seedObservationLedger } from './file-evidence-seed.js'
75
76
  import { DEFAULT_TOOL_RESULT_GUARDRAILS } from './guardrail-presets.js'
77
+ import { declaredWriteEscalation, shellEscalations } from './path-escalation.js'
76
78
  import type { ToolResultObservation } from './project-instructions.js'
77
79
  import { savedPresentation } from './saved-presentation.js'
78
80
  import { ToolCallBudget, assertMaxToolCalls } from './tool-call-budget.js'
79
81
  import {
80
82
  DEFAULT_MAX_TOOL_OUTPUT_CHARS,
83
+ DEFAULT_MAX_TOOL_OUTPUT_CHARS_PER_MESSAGE,
81
84
  type ToolOutputBudgetResult,
82
85
  applyToolOutputBudget,
83
86
  describeDroppedContent,
87
+ fitResultsToMessageBudget,
84
88
  measureContentBytes,
85
89
  } from './tool-output-budget.js'
86
90
 
@@ -94,6 +98,7 @@ export type PreparedDirectCall =
94
98
  readonly toolName: string
95
99
  readonly input: unknown
96
100
  readonly prepared: PreparedToolExecution
101
+ readonly hook?: PreToolHookNotes
97
102
  }
98
103
  | {
99
104
  readonly kind: 'legacy'
@@ -101,6 +106,7 @@ export type PreparedDirectCall =
101
106
  readonly toolName: string
102
107
  readonly input: unknown
103
108
  readonly inputFailure?: CompletedToolRecord['inputFailure']
109
+ readonly hook?: PreToolHookNotes
104
110
  }
105
111
  | {
106
112
  readonly kind: 'synthetic'
@@ -129,6 +135,8 @@ export interface PreparedToolBatch {
129
135
  readonly input: unknown
130
136
  /** What the prepared value reaches past the turn's boundary; see `ToolCallSummary.escalation`. */
131
137
  readonly escalation?: ToolCallEscalation
138
+ /** What a `pre_tool_use` hook asked for this call's approval; see `ToolCallSummary.hookPermission`. */
139
+ readonly hookPermission?: PluginHookPermission
132
140
  }[]
133
141
  }
134
142
 
@@ -137,6 +145,16 @@ interface OwnedPreparedToolBatch extends PreparedToolBatch {
137
145
  readonly escalations: ReadonlyMap<string, ToolCallEscalation>
138
146
  }
139
147
 
148
+ /**
149
+ * A nested call cannot open a review from inside the tool that issued it, so
150
+ * a hook that asked for a person to approve it can only be answered with a
151
+ * refusal. Silence would run the call the hook wanted a person to see.
152
+ */
153
+ function askedNestedMessage(toolName: string, outcome: PreToolHookOutcome): string | undefined {
154
+ if (outcome.kind !== 'continue' || outcome.permission?.decision !== 'ask') return undefined
155
+ return `Blocked: a hook asked that a person approve "${toolName}", and an approval cannot be requested from inside another tool. Call it directly.`
156
+ }
157
+
140
158
  function assertUniqueToolCallIds(toolCalls: readonly ToolCall[]): void {
141
159
  const seen = new Set<string>()
142
160
  for (const [index, toolCall] of toolCalls.entries()) {
@@ -420,6 +438,8 @@ export interface ToolExecutorConfig {
420
438
  * {@link DEFAULT_MAX_TOOL_OUTPUT_CHARS}; set `0` to disable.
421
439
  */
422
440
  maxToolOutputChars?: number
441
+ /** See QueryParams.maxToolOutputCharsPerMessage. */
442
+ maxToolOutputCharsPerMessage?: number
423
443
  /** See QueryParams.retainedToolPreviewChars; applies to the recorded host output. */
424
444
  retainedToolPreviewChars?: number
425
445
 
@@ -493,8 +513,16 @@ interface PostToolOverride {
493
513
  readonly content?: ToolResultContent
494
514
  }
495
515
 
516
+ /** What `pre_tool_use` hooks said about a call beyond running it or not. */
517
+ export interface PreToolHookNotes {
518
+ /** The strictest permission answer among the hooks: `ask` outranks `allow`. */
519
+ readonly permission?: PluginHookPermission
520
+ /** Hook text for the model, appended to what it reads of this call. */
521
+ readonly context?: string
522
+ }
523
+
496
524
  export type PreToolHookOutcome =
497
- | { kind: 'continue'; input: unknown; modified: boolean }
525
+ | ({ kind: 'continue'; input: unknown; modified: boolean } & PreToolHookNotes)
498
526
  | { kind: 'skip'; input: unknown; output: string }
499
527
  | { kind: 'error'; input: unknown; output: string }
500
528
 
@@ -625,6 +653,7 @@ export class ToolExecutor {
625
653
  /** Set per turn by the orchestrator; see {@link setStepAllowedTools}. */
626
654
  private stepAllowedTools?: readonly string[]
627
655
  private readonly fileReadTracker: FileReadTracker
656
+ private sandboxGate?: () => Promise<void>
628
657
  /** A ledger is rebuilt from history at most once; a second pass would re-append its chains. */
629
658
  private fileObservationsSeeded = false
630
659
 
@@ -658,6 +687,25 @@ export class ToolExecutor {
658
687
 
659
688
  setSandbox(sandbox: Sandbox): void {
660
689
  this.config = { ...this.config, sandbox }
690
+ this.sandboxGate = undefined
691
+ }
692
+
693
+ /**
694
+ * Hold tool admission until a sandbox that is still being acquired exists.
695
+ * The gate rejects when none will, and the turn settles that rejection. It
696
+ * is cleared by {@link setSandbox}, so a turn pays the check once.
697
+ */
698
+ setSandboxGate(gate: () => Promise<void>): void {
699
+ this.sandboxGate = gate
700
+ }
701
+
702
+ private async awaitSandbox(): Promise<void> {
703
+ await this.sandboxGate?.()
704
+ }
705
+
706
+ /** True once the turn has a sandbox, or is certain to get one before a tool runs. */
707
+ private get sandboxed(): boolean {
708
+ return this.config.sandbox !== undefined || this.sandboxGate !== undefined
661
709
  }
662
710
 
663
711
  /** Classify only the exact successful result executed by this executor. */
@@ -743,7 +791,7 @@ export class ToolExecutor {
743
791
  messages,
744
792
  this.fileReadTracker,
745
793
  this.config.workingDirectory,
746
- this.config.sandbox !== undefined,
794
+ this.sandboxed,
747
795
  )
748
796
  }
749
797
 
@@ -925,7 +973,7 @@ export class ToolExecutor {
925
973
  const tool = this.config.tools.get(toolName)
926
974
  return (
927
975
  tool?.commandDialect?.({
928
- sandboxed: this.config.sandbox !== undefined,
976
+ sandboxed: this.sandboxed,
929
977
  }) ?? 'sh'
930
978
  )
931
979
  }
@@ -951,6 +999,7 @@ export class ToolExecutor {
951
999
  hostModifiedCallIds?: ReadonlySet<string>,
952
1000
  ): Promise<PreparedToolBatch> {
953
1001
  assertUniqueToolCallIds(response.message.toolCalls ?? [])
1002
+ if ((response.message.toolCalls ?? []).length > 0) await this.awaitSandbox()
954
1003
  const calls = new Map<string, PreparedDirectCall>()
955
1004
  const escalations = new Map<string, ToolCallEscalation>()
956
1005
  for (const toolCall of response.message.toolCalls ?? []) {
@@ -984,7 +1033,7 @@ export class ToolExecutor {
984
1033
  const tool = this.config.tools.get(call.toolName)
985
1034
  if (!tool || call.input === null || typeof call.input !== 'object') return undefined
986
1035
  const input = call.input as Record<string, unknown>
987
- const sandboxed = this.config.sandbox !== undefined
1036
+ const sandboxed = this.sandboxed
988
1037
  let outsidePaths: string[] | undefined
989
1038
  if (
990
1039
  this.config.outsideRootAccess === 'review' &&
@@ -1003,11 +1052,80 @@ export class ToolExecutor {
1003
1052
  tool.sandboxEscapeArgument !== undefined &&
1004
1053
  input[tool.sandboxEscapeArgument] === true
1005
1054
  const unknownProgram = this.unknownProgramOf(tool, input, sandboxed)
1006
- if (!outsidePaths && !sandboxEscape && unknownProgram === undefined) return undefined
1055
+ const reaches = await this.pathEscalationsOf(tool, input, sandboxed)
1056
+ if (reaches.outsidePaths.length > 0) {
1057
+ outsidePaths = [...new Set([...(outsidePaths ?? []), ...reaches.outsidePaths])]
1058
+ }
1059
+ const protectedPaths = reaches.protectedPaths
1060
+ const removal = reaches.dangerousRemoval
1061
+ if (
1062
+ !outsidePaths &&
1063
+ !sandboxEscape &&
1064
+ unknownProgram === undefined &&
1065
+ protectedPaths.length === 0 &&
1066
+ removal === undefined
1067
+ ) {
1068
+ return undefined
1069
+ }
1007
1070
  return {
1008
1071
  ...(outsidePaths ? { outsidePaths } : {}),
1009
1072
  ...(sandboxEscape ? { sandboxEscape: true as const } : {}),
1010
1073
  ...(unknownProgram !== undefined ? { unknownProgram } : {}),
1074
+ ...(protectedPaths.length > 0 ? { protectedPaths } : {}),
1075
+ ...(removal !== undefined ? { dangerousRemoval: removal } : {}),
1076
+ }
1077
+ }
1078
+
1079
+ /**
1080
+ * What the call's own arguments write or remove that no mode may approve in
1081
+ * advance, and, for a shell line, the paths outside the roots it names.
1082
+ *
1083
+ * The protected-path and removal floors hold whether or not the turn reviews
1084
+ * outside paths and whether or not it is sandboxed: they are about what the
1085
+ * write plants and what the removal destroys, not about where the turn is
1086
+ * allowed to look. The outside-path half follows `outsideRootAccess`, as the
1087
+ * file tools' own does.
1088
+ */
1089
+ private async pathEscalationsOf(
1090
+ tool: Pick<
1091
+ ToolDefinition,
1092
+ 'pathArgument' | 'commandArgument' | 'commandDialect' | 'isReadOnly'
1093
+ >,
1094
+ input: Record<string, unknown>,
1095
+ sandboxed: boolean,
1096
+ ): Promise<{
1097
+ outsidePaths: readonly string[]
1098
+ protectedPaths: readonly string[]
1099
+ dangerousRemoval?: string
1100
+ }> {
1101
+ const roots = toolRoots(this.config)
1102
+ const protectedPaths: string[] = []
1103
+ let outsidePaths: readonly string[] = []
1104
+ let removal: string | undefined
1105
+ if (tool.pathArgument !== undefined && tool.isReadOnly?.(input) !== true) {
1106
+ const value = input[tool.pathArgument]
1107
+ if (typeof value === 'string') {
1108
+ const hit = await declaredWriteEscalation(roots, value)
1109
+ if (hit !== undefined) protectedPaths.push(hit)
1110
+ }
1111
+ }
1112
+ if (tool.commandArgument !== undefined) {
1113
+ const line = input[tool.commandArgument]
1114
+ if (typeof line === 'string') {
1115
+ const dialect = tool.commandDialect?.({ sandboxed }) ?? 'sh'
1116
+ const found = await shellEscalations(line, dialect, {
1117
+ roots,
1118
+ reviewOutside: this.config.outsideRootAccess === 'review' && !sandboxed,
1119
+ })
1120
+ protectedPaths.push(...found.protectedPaths)
1121
+ outsidePaths = found.outsidePaths
1122
+ removal = found.dangerousRemoval
1123
+ }
1124
+ }
1125
+ return {
1126
+ outsidePaths,
1127
+ protectedPaths: [...new Set(protectedPaths)],
1128
+ ...(removal !== undefined ? { dangerousRemoval: removal } : {}),
1011
1129
  }
1012
1130
  }
1013
1131
 
@@ -1022,7 +1140,10 @@ export class ToolExecutor {
1022
1140
  * outside one.
1023
1141
  */
1024
1142
  private unknownProgramOf(
1025
- tool: { commandArgument?: string; commandDialect?: ToolDefinition['commandDialect'] },
1143
+ tool: {
1144
+ commandArgument?: string
1145
+ commandDialect?: ToolDefinition['commandDialect']
1146
+ },
1026
1147
  input: Record<string, unknown>,
1027
1148
  sandboxed: boolean,
1028
1149
  ): string | undefined {
@@ -1069,11 +1190,13 @@ export class ToolExecutor {
1069
1190
  )
1070
1191
  .map((call) => {
1071
1192
  const escalation = escalations.get(call.toolCall.id)
1193
+ const hookPermission = call.hook?.permission
1072
1194
  return {
1073
1195
  id: call.toolCall.id,
1074
1196
  name: call.toolName,
1075
1197
  input: call.input,
1076
1198
  ...(escalation ? { escalation } : {}),
1199
+ ...(hookPermission ? { hookPermission } : {}),
1077
1200
  }
1078
1201
  })
1079
1202
  const batch: OwnedPreparedToolBatch = Object.freeze({
@@ -1097,6 +1220,7 @@ export class ToolExecutor {
1097
1220
  return { messages: [], results: [], observations: [] }
1098
1221
  }
1099
1222
  assertUniqueToolCallIds(toolCalls)
1223
+ if (toolCalls.length > 0) await this.awaitSandbox()
1100
1224
 
1101
1225
  // Sampled here, once, and held for every call below. See the note on
1102
1226
  // `permissionMode` in the config type.
@@ -1327,6 +1451,8 @@ export class ToolExecutor {
1327
1451
  })
1328
1452
  }
1329
1453
 
1454
+ this.fitBatchToMessageBudget(results)
1455
+
1330
1456
  // isError and rich content were computed and then discarded here: the
1331
1457
  // tuple narrowed to {toolCallId, output} BEFORE the message was built,
1332
1458
  // so the failure signal and any image block were structurally lost at
@@ -1338,6 +1464,51 @@ export class ToolExecutor {
1338
1464
  return { messages, results, observations }
1339
1465
  }
1340
1466
 
1467
+ /**
1468
+ * Hold the batch's combined result text under the per-message cap.
1469
+ *
1470
+ * Runs once, on the settled batch and before the results become history
1471
+ * messages, so what it writes is what every later request replays.
1472
+ */
1473
+ private fitBatchToMessageBudget(results: ToolCallOutcome[]): void {
1474
+ const maxChars =
1475
+ this.config.maxToolOutputCharsPerMessage ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS_PER_MESSAGE
1476
+ const replacements = fitResultsToMessageBudget(
1477
+ results.map((r) => ({
1478
+ toolCallId: r.toolCallId,
1479
+ toolName: r.toolName,
1480
+ output: r.output,
1481
+ // A result carrying rich content or a structured-output copy is
1482
+ // not text the model reads alone, and a self-limiting tool has
1483
+ // already decided its own size.
1484
+ replaceable:
1485
+ r.content === undefined &&
1486
+ r.structuredResultJson === undefined &&
1487
+ this.config.tools.get(r.toolName)?.selfLimitingOutput !== true,
1488
+ })),
1489
+ {
1490
+ maxChars,
1491
+ spillDir: this.outputDirectory(),
1492
+ onError: (message) =>
1493
+ this.log.warn('Failed to retain a tool output replaced for the message budget', {
1494
+ [NAMZU.TURN_ID]: this.config.turnId,
1495
+ 'exception.message': message,
1496
+ }),
1497
+ },
1498
+ )
1499
+ if (replacements.size === 0) return
1500
+ for (const [i, r] of results.entries()) {
1501
+ const replacement = replacements.get(r.toolCallId)
1502
+ if (replacement === undefined) continue
1503
+ results[i] = { ...r, output: replacement.output }
1504
+ }
1505
+ this.log.warn("Tool results exceeded the message's combined budget", {
1506
+ [NAMZU.TURN_ID]: this.config.turnId,
1507
+ 'namzu.runtime.replaced_count': replacements.size,
1508
+ 'namzu.runtime.message_budget_chars': maxChars,
1509
+ })
1510
+ }
1511
+
1341
1512
  /**
1342
1513
  * Run a tool on behalf of another tool, and put it on the record.
1343
1514
  *
@@ -1802,8 +1973,10 @@ export class ToolExecutor {
1802
1973
  let input: unknown
1803
1974
  let prepared: PreparedToolExecution | undefined
1804
1975
  let inputFailure: ToolCallOutcome['inputFailure']
1976
+ let preparedHookContext: string | undefined
1805
1977
 
1806
1978
  if (preparedCall?.kind === 'ready' || preparedCall?.kind === 'legacy') {
1979
+ preparedHookContext = preparedCall.hook?.context
1807
1980
  input = preparedCall.input
1808
1981
  prepared = preparedCall.kind === 'ready' ? preparedCall.prepared : undefined
1809
1982
  if (preparedCall.kind === 'legacy') inputFailure = preparedCall.inputFailure
@@ -1824,6 +1997,7 @@ export class ToolExecutor {
1824
1997
  if (admitted.kind === 'synthetic') return this.recordSyntheticPreparation(admitted)
1825
1998
  toolName = admitted.toolName
1826
1999
  input = admitted.input
2000
+ preparedHookContext = admitted.hook?.context
1827
2001
  prepared = admitted.kind === 'ready' ? admitted.prepared : undefined
1828
2002
  if (admitted.kind === 'legacy') inputFailure = admitted.inputFailure
1829
2003
  }
@@ -2029,7 +2203,11 @@ export class ToolExecutor {
2029
2203
  ]
2030
2204
  }
2031
2205
  }
2032
- const maxToolOutputChars = this.config.maxToolOutputChars ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS
2206
+ // A tool that bounds its own output is exempt from the DEFAULT per-result
2207
+ // cap only; a host that set the cap explicitly still gets it.
2208
+ const maxToolOutputChars =
2209
+ this.config.maxToolOutputChars ??
2210
+ (executedDefinition?.selfLimitingOutput === true ? 0 : DEFAULT_MAX_TOOL_OUTPUT_CHARS)
2033
2211
  const sourceOutput = output
2034
2212
  // This owner-bound channel follows the selected receipt after both result
2035
2213
  // screening and post-tool replacement. ToolResult.data and raw input can
@@ -2085,13 +2263,26 @@ export class ToolExecutor {
2085
2263
  })
2086
2264
  }
2087
2265
  output = budgeted.output
2088
- const modelContent =
2266
+ let modelContent =
2089
2267
  selectedContent === undefined
2090
2268
  ? undefined
2091
2269
  : this.budgetContent(selectedContent, toolName, toolCall.id, {
2092
2270
  sourceOutput,
2093
2271
  budgeted,
2094
2272
  })
2273
+ // What hooks said about this call rides with its result, in the model's
2274
+ // receipt and the host's alike, framed as a hook's words. Added after
2275
+ // the output budget so a bounded result is never trimmed to make room
2276
+ // for it; the note has its own bound (`hookNote`).
2277
+ const hookTexts = [preparedHookContext, post.context].filter(
2278
+ (text): text is string => text !== undefined && text.length > 0,
2279
+ )
2280
+ if (hookTexts.length > 0) {
2281
+ const note = hookNote(hookTexts)
2282
+ output = `${output}\n\n${note}`
2283
+ if (typeof modelContent === 'string') modelContent = `${modelContent}\n\n${note}`
2284
+ else if (modelContent) modelContent = [...modelContent, { type: 'text', text: note }]
2285
+ }
2095
2286
 
2096
2287
  // A failed call, or an override that says the call failed. A `replace`
2097
2288
  // says the opposite, and reading it as a failure is what made redaction
@@ -2438,9 +2629,25 @@ export class ToolExecutor {
2438
2629
  emitEvent: this.emitEvent,
2439
2630
  log: this.log,
2440
2631
  allowedTools: this.effectiveAllowedTools(),
2632
+ requestHalt: (reason) => this.requestHalt(reason),
2441
2633
  }
2442
2634
  }
2443
2635
 
2636
+ /** Why a hook asked for the turn to stop, until the iteration loop reads it. */
2637
+ private haltReason: string | undefined
2638
+
2639
+ /** The first request wins: a later hook does not rewrite why the turn stopped. */
2640
+ requestHalt(reason: string): void {
2641
+ this.haltReason ??= reason
2642
+ }
2643
+
2644
+ /** Read and clear a hook's request to stop the turn. */
2645
+ takeHalt(): string | undefined {
2646
+ const reason = this.haltReason
2647
+ this.haltReason = undefined
2648
+ return reason
2649
+ }
2650
+
2444
2651
  private async prepareNestedCall(
2445
2652
  toolName: string,
2446
2653
  input: unknown,
@@ -2468,6 +2675,14 @@ export class ToolExecutor {
2468
2675
  ...(preOutcome.kind === 'skip' ? { skipped: true as const } : {}),
2469
2676
  }
2470
2677
  }
2678
+ const nestedAsk = askedNestedMessage(toolName, preOutcome)
2679
+ if (nestedAsk)
2680
+ return {
2681
+ kind: 'synthetic',
2682
+ input: preOutcome.input,
2683
+ message: nestedAsk,
2684
+ isError: true,
2685
+ }
2471
2686
  return { kind: 'legacy', input: preOutcome.input }
2472
2687
  }
2473
2688
 
@@ -2513,6 +2728,15 @@ export class ToolExecutor {
2513
2728
  ...(preOutcome.kind === 'skip' ? { skipped: true as const } : {}),
2514
2729
  }
2515
2730
  }
2731
+ const nestedAsk = askedNestedMessage(toolName, preOutcome)
2732
+ if (nestedAsk) {
2733
+ return {
2734
+ kind: 'synthetic',
2735
+ input: preOutcome.input,
2736
+ message: nestedAsk,
2737
+ isError: true,
2738
+ }
2739
+ }
2516
2740
  if (!preOutcome.modified) {
2517
2741
  return {
2518
2742
  kind: 'ready',
@@ -2571,12 +2795,17 @@ export class ToolExecutor {
2571
2795
  /**
2572
2796
  * @returns `override` — text replacing the tool's output, or `null`.
2573
2797
  * `retry` — the hook asked for the tool to run again.
2798
+ * `context` — what the hooks want the model to read beside the result.
2574
2799
  */
2575
2800
  private async runPostToolHook(
2576
2801
  toolName: string,
2577
2802
  input: unknown,
2578
2803
  toolResult: ToolResult,
2579
- ): Promise<{ override: PostToolOverride | null; retry: boolean }> {
2804
+ ): Promise<{
2805
+ override: PostToolOverride | null
2806
+ retry: boolean
2807
+ context?: string
2808
+ }> {
2580
2809
  if (!this.config.pluginManager) return { override: null, retry: false }
2581
2810
  let results: PluginHookResult[]
2582
2811
  try {
@@ -2592,6 +2821,26 @@ export class ToolExecutor {
2592
2821
  },
2593
2822
  this.emitEvent,
2594
2823
  )
2824
+ // A failed call is announced a second time, by name, so a hook that
2825
+ // only cares about failures need not read every result. After
2826
+ // `post_tool_use`, so the existing hooks see exactly what they saw.
2827
+ if (!toolResult.success) {
2828
+ results = [
2829
+ ...results,
2830
+ ...(await this.config.pluginManager.executeHooks(
2831
+ 'post_tool_use_failure',
2832
+ {
2833
+ sessionId: this.config.sessionId,
2834
+ turnId: this.config.turnId,
2835
+ toolName,
2836
+ toolInput: input,
2837
+ toolResult,
2838
+ signal: this.config.abortSignal,
2839
+ },
2840
+ this.emitEvent,
2841
+ )),
2842
+ ]
2843
+ }
2595
2844
  } catch (error) {
2596
2845
  if (!this.config.abortSignal.aborted) throw error
2597
2846
  // The tool has already returned. Cancellation of a later redaction
@@ -2608,9 +2857,12 @@ export class ToolExecutor {
2608
2857
  }
2609
2858
  let override: PostToolOverride | null = null
2610
2859
  let retry = false
2860
+ const contexts: string[] = []
2611
2861
  for (const result of results) {
2612
2862
  switch (result.action) {
2613
2863
  case 'continue':
2864
+ if (result.context) contexts.push(result.context)
2865
+ if (result.halt) this.requestHalt(result.halt.reason)
2614
2866
  continue
2615
2867
  case 'error':
2616
2868
  override = { output: `Error: ${result.message}`, isError: true }
@@ -2646,7 +2898,11 @@ export class ToolExecutor {
2646
2898
  }
2647
2899
  }
2648
2900
  }
2649
- return { override, retry }
2901
+ return {
2902
+ override,
2903
+ retry,
2904
+ ...(contexts.length > 0 ? { context: contexts.join('\n') } : {}),
2905
+ }
2650
2906
  }
2651
2907
 
2652
2908
  /**
@@ -53,6 +53,12 @@ export interface TurnFinalization {
53
53
  * immediately before the record is written.
54
54
  */
55
55
  readonly takeSteps: () => readonly StepResult[]
56
+ /**
57
+ * Whether the loop already fired `turn_end` (and `subagent_stop`) when the
58
+ * model finished, so a hook could keep the turn going. Fired there, it is
59
+ * not fired again here.
60
+ */
61
+ readonly turnEndHooksFired?: () => boolean
56
62
  readonly workingStateManager: WorkingStateManager | undefined
57
63
  readonly resultAssembler: ResultAssembler
58
64
  readonly rootSpan: Span
@@ -71,7 +77,7 @@ export async function* finalizeTurn(
71
77
  rootSpan,
72
78
  } = finalization
73
79
 
74
- if (params.pluginManager) {
80
+ if (params.pluginManager && finalization.turnEndHooksFired?.() !== true) {
75
81
  const hookResults = await params.pluginManager.executeHooks(
76
82
  'turn_end',
77
83
  { sessionId: ctx.sessionId, turnId: ctx.turnId, signal: ctx.abortController.signal },