@namzu/sdk 51.0.0 → 52.0.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 (352) hide show
  1. package/CHANGELOG.md +110 -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/bridge/sse/mapper.d.ts.map +1 -1
  39. package/dist/bridge/sse/mapper.js +1 -0
  40. package/dist/bridge/sse/mapper.js.map +1 -1
  41. package/dist/compaction/context-window.d.ts +24 -0
  42. package/dist/compaction/context-window.d.ts.map +1 -1
  43. package/dist/compaction/context-window.js +34 -0
  44. package/dist/compaction/context-window.js.map +1 -1
  45. package/dist/compaction/manual.d.ts.map +1 -1
  46. package/dist/compaction/manual.js +10 -2
  47. package/dist/compaction/manual.js.map +1 -1
  48. package/dist/compaction/plan.d.ts +4 -0
  49. package/dist/compaction/plan.d.ts.map +1 -1
  50. package/dist/compaction/plan.js +6 -2
  51. package/dist/compaction/plan.js.map +1 -1
  52. package/dist/compaction/verifier.d.ts +11 -1
  53. package/dist/compaction/verifier.d.ts.map +1 -1
  54. package/dist/compaction/verifier.js +19 -7
  55. package/dist/compaction/verifier.js.map +1 -1
  56. package/dist/config/runtime.d.ts +55 -7
  57. package/dist/config/runtime.d.ts.map +1 -1
  58. package/dist/config/runtime.js +23 -2
  59. package/dist/config/runtime.js.map +1 -1
  60. package/dist/connector/web/guarded-fetch.d.ts.map +1 -1
  61. package/dist/connector/web/guarded-fetch.js +24 -0
  62. package/dist/connector/web/guarded-fetch.js.map +1 -1
  63. package/dist/connector/web/index.d.ts +1 -1
  64. package/dist/connector/web/index.d.ts.map +1 -1
  65. package/dist/connector/web/page.d.ts +59 -0
  66. package/dist/connector/web/page.d.ts.map +1 -0
  67. package/dist/connector/web/page.js +112 -0
  68. package/dist/connector/web/page.js.map +1 -0
  69. package/dist/connector/web/types.d.ts +47 -0
  70. package/dist/connector/web/types.d.ts.map +1 -1
  71. package/dist/connector/web/types.js.map +1 -1
  72. package/dist/constants/continuation.d.ts +8 -0
  73. package/dist/constants/continuation.d.ts.map +1 -1
  74. package/dist/constants/continuation.js +8 -0
  75. package/dist/constants/continuation.js.map +1 -1
  76. package/dist/constants/plugin/index.d.ts +1 -1
  77. package/dist/constants/plugin/index.d.ts.map +1 -1
  78. package/dist/constants/plugin/index.js +1 -1
  79. package/dist/constants/plugin/index.js.map +1 -1
  80. package/dist/manager/agent/lifecycle.d.ts +7 -0
  81. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  82. package/dist/manager/agent/lifecycle.js +11 -2
  83. package/dist/manager/agent/lifecycle.js.map +1 -1
  84. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  85. package/dist/manager/session/turn-recorder.js +4 -2
  86. package/dist/manager/session/turn-recorder.js.map +1 -1
  87. package/dist/plugin/index.d.ts +1 -1
  88. package/dist/plugin/index.d.ts.map +1 -1
  89. package/dist/plugin/index.js +1 -1
  90. package/dist/plugin/index.js.map +1 -1
  91. package/dist/plugin/lifecycle.d.ts +6 -0
  92. package/dist/plugin/lifecycle.d.ts.map +1 -1
  93. package/dist/plugin/lifecycle.js +98 -114
  94. package/dist/plugin/lifecycle.js.map +1 -1
  95. package/dist/plugin/shell-hook.d.ts +90 -22
  96. package/dist/plugin/shell-hook.d.ts.map +1 -1
  97. package/dist/plugin/shell-hook.js +434 -41
  98. package/dist/plugin/shell-hook.js.map +1 -1
  99. package/dist/provider/errors.d.ts +10 -0
  100. package/dist/provider/errors.d.ts.map +1 -1
  101. package/dist/provider/errors.js +16 -0
  102. package/dist/provider/errors.js.map +1 -1
  103. package/dist/provider/retry.d.ts +27 -0
  104. package/dist/provider/retry.d.ts.map +1 -1
  105. package/dist/provider/retry.js +19 -3
  106. package/dist/provider/retry.js.map +1 -1
  107. package/dist/public-runtime.d.ts +4 -1
  108. package/dist/public-runtime.d.ts.map +1 -1
  109. package/dist/public-runtime.js +4 -1
  110. package/dist/public-runtime.js.map +1 -1
  111. package/dist/public-types.d.ts +2 -2
  112. package/dist/public-types.d.ts.map +1 -1
  113. package/dist/runtime/jobs/registry.d.ts +21 -0
  114. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  115. package/dist/runtime/jobs/registry.js +15 -2
  116. package/dist/runtime/jobs/registry.js.map +1 -1
  117. package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
  118. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  119. package/dist/runtime/query/executor/tool-call-admission.js +74 -13
  120. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  121. package/dist/runtime/query/executor.d.ts +41 -2
  122. package/dist/runtime/query/executor.d.ts.map +1 -1
  123. package/dist/runtime/query/executor.js +190 -6
  124. package/dist/runtime/query/executor.js.map +1 -1
  125. package/dist/runtime/query/finalize-turn.d.ts +6 -0
  126. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  127. package/dist/runtime/query/finalize-turn.js +1 -1
  128. package/dist/runtime/query/finalize-turn.js.map +1 -1
  129. package/dist/runtime/query/index.d.ts +29 -0
  130. package/dist/runtime/query/index.d.ts.map +1 -1
  131. package/dist/runtime/query/index.js +15 -3
  132. package/dist/runtime/query/index.js.map +1 -1
  133. package/dist/runtime/query/iteration/index.d.ts +31 -0
  134. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  135. package/dist/runtime/query/iteration/index.js +216 -7
  136. package/dist/runtime/query/iteration/index.js.map +1 -1
  137. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +58 -0
  138. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
  139. package/dist/runtime/query/iteration/phases/compaction-breaker.js +91 -0
  140. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
  141. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  142. package/dist/runtime/query/iteration/phases/compaction.js +89 -7
  143. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  144. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  145. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  146. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  147. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  148. package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
  149. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  150. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  151. package/dist/runtime/query/iteration/stream-turn.js +24 -1
  152. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  153. package/dist/runtime/query/path-escalation.d.ts +36 -0
  154. package/dist/runtime/query/path-escalation.d.ts.map +1 -0
  155. package/dist/runtime/query/path-escalation.js +184 -0
  156. package/dist/runtime/query/path-escalation.js.map +1 -0
  157. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  158. package/dist/runtime/query/plugin-hooks.js +8 -0
  159. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  160. package/dist/runtime/query/reminders.d.ts +35 -0
  161. package/dist/runtime/query/reminders.d.ts.map +1 -0
  162. package/dist/runtime/query/reminders.js +7 -0
  163. package/dist/runtime/query/reminders.js.map +1 -0
  164. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  165. package/dist/runtime/query/resume-pending.js +26 -0
  166. package/dist/runtime/query/resume-pending.js.map +1 -1
  167. package/dist/runtime/query/review-policy.d.ts +26 -0
  168. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  169. package/dist/runtime/query/review-policy.js +79 -5
  170. package/dist/runtime/query/review-policy.js.map +1 -1
  171. package/dist/runtime/query/tool-output-budget.d.ts +46 -0
  172. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  173. package/dist/runtime/query/tool-output-budget.js +85 -1
  174. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  175. package/dist/runtime/query/tooling.d.ts +1 -0
  176. package/dist/runtime/query/tooling.d.ts.map +1 -1
  177. package/dist/runtime/query/tooling.js +5 -0
  178. package/dist/runtime/query/tooling.js.map +1 -1
  179. package/dist/session/paths.d.ts +5 -1
  180. package/dist/session/paths.d.ts.map +1 -1
  181. package/dist/session/paths.js +8 -0
  182. package/dist/session/paths.js.map +1 -1
  183. package/dist/store/memory/disk.d.ts.map +1 -1
  184. package/dist/store/memory/disk.js +2 -2
  185. package/dist/store/memory/disk.js.map +1 -1
  186. package/dist/store/memory/markdown.d.ts.map +1 -1
  187. package/dist/store/memory/markdown.js +2 -2
  188. package/dist/store/memory/markdown.js.map +1 -1
  189. package/dist/store/memory/naming.d.ts +9 -0
  190. package/dist/store/memory/naming.d.ts.map +1 -1
  191. package/dist/store/memory/naming.js +12 -0
  192. package/dist/store/memory/naming.js.map +1 -1
  193. package/dist/tools/builtins/bash.d.ts +30 -1
  194. package/dist/tools/builtins/bash.d.ts.map +1 -1
  195. package/dist/tools/builtins/bash.js +170 -26
  196. package/dist/tools/builtins/bash.js.map +1 -1
  197. package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
  198. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  199. package/dist/tools/builtins/content-fingerprint.js +4 -0
  200. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  201. package/dist/tools/builtins/edit.d.ts.map +1 -1
  202. package/dist/tools/builtins/edit.js +60 -18
  203. package/dist/tools/builtins/edit.js.map +1 -1
  204. package/dist/tools/builtins/grep.d.ts +8 -1
  205. package/dist/tools/builtins/grep.d.ts.map +1 -1
  206. package/dist/tools/builtins/grep.js +152 -33
  207. package/dist/tools/builtins/grep.js.map +1 -1
  208. package/dist/tools/builtins/read-file.d.ts +1 -0
  209. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  210. package/dist/tools/builtins/read-file.js +44 -5
  211. package/dist/tools/builtins/read-file.js.map +1 -1
  212. package/dist/tools/builtins/read-render.d.ts +52 -0
  213. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  214. package/dist/tools/builtins/read-render.js +143 -0
  215. package/dist/tools/builtins/read-render.js.map +1 -1
  216. package/dist/tools/builtins/web.d.ts +13 -2
  217. package/dist/tools/builtins/web.d.ts.map +1 -1
  218. package/dist/tools/builtins/web.js +225 -32
  219. package/dist/tools/builtins/web.js.map +1 -1
  220. package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
  221. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  222. package/dist/tools/coordinator/ask-user-question.js +256 -92
  223. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  224. package/dist/tools/coordinator/question-options.d.ts +1 -0
  225. package/dist/tools/coordinator/question-options.d.ts.map +1 -1
  226. package/dist/tools/coordinator/question-options.js +6 -1
  227. package/dist/tools/coordinator/question-options.js.map +1 -1
  228. package/dist/tools/defineTool.d.ts +4 -1
  229. package/dist/tools/defineTool.d.ts.map +1 -1
  230. package/dist/tools/defineTool.js +6 -1
  231. package/dist/tools/defineTool.js.map +1 -1
  232. package/dist/tools/paths.d.ts +6 -0
  233. package/dist/tools/paths.d.ts.map +1 -1
  234. package/dist/tools/paths.js +1 -1
  235. package/dist/tools/paths.js.map +1 -1
  236. package/dist/types/agent/query.d.ts +3 -0
  237. package/dist/types/agent/query.d.ts.map +1 -1
  238. package/dist/types/errors/catalog.js +1 -1
  239. package/dist/types/errors/catalog.js.map +1 -1
  240. package/dist/types/hitl/index.d.ts +104 -0
  241. package/dist/types/hitl/index.d.ts.map +1 -1
  242. package/dist/types/hitl/index.js.map +1 -1
  243. package/dist/types/message/index.d.ts +1 -1
  244. package/dist/types/message/index.d.ts.map +1 -1
  245. package/dist/types/message/index.js +2 -0
  246. package/dist/types/message/index.js.map +1 -1
  247. package/dist/types/plugin/index.d.ts +93 -19
  248. package/dist/types/plugin/index.d.ts.map +1 -1
  249. package/dist/types/plugin/index.js +3 -0
  250. package/dist/types/plugin/index.js.map +1 -1
  251. package/dist/types/provider/error.d.ts +6 -0
  252. package/dist/types/provider/error.d.ts.map +1 -1
  253. package/dist/types/provider/errors.d.ts +19 -0
  254. package/dist/types/provider/errors.d.ts.map +1 -1
  255. package/dist/types/provider/errors.js +39 -1
  256. package/dist/types/provider/errors.js.map +1 -1
  257. package/dist/types/provider/stream.d.ts +6 -0
  258. package/dist/types/provider/stream.d.ts.map +1 -1
  259. package/dist/types/session/events.d.ts +15 -2
  260. package/dist/types/session/events.d.ts.map +1 -1
  261. package/dist/types/session/events.js.map +1 -1
  262. package/dist/types/session/records.d.ts +9 -9
  263. package/dist/types/session/records.d.ts.map +1 -1
  264. package/dist/types/session/records.js +1 -0
  265. package/dist/types/session/records.js.map +1 -1
  266. package/dist/types/session/stop-reason.d.ts +5 -0
  267. package/dist/types/session/stop-reason.d.ts.map +1 -1
  268. package/dist/types/tool/index.d.ts +52 -0
  269. package/dist/types/tool/index.d.ts.map +1 -1
  270. package/dist/types/tool/index.js.map +1 -1
  271. package/dist/utils/backoff.d.ts +12 -0
  272. package/dist/utils/backoff.d.ts.map +1 -1
  273. package/dist/utils/backoff.js +15 -0
  274. package/dist/utils/backoff.js.map +1 -1
  275. package/package.json +4 -2
  276. package/src/agents/QueryAgent.ts +5 -1
  277. package/src/agents/SupervisorAgent.ts +1 -1
  278. package/src/agents/examples/SupervisorAgent.ts +217 -106
  279. package/src/agents/index.ts +1 -1
  280. package/src/authorization/protected-paths.ts +110 -0
  281. package/src/authorization/readonly-commands.ts +771 -0
  282. package/src/authorization/readonly-flags.ts +196 -0
  283. package/src/authorization/removal.ts +326 -0
  284. package/src/authorization/shell-paths.ts +927 -0
  285. package/src/bridge/acp/update.ts +1 -0
  286. package/src/bridge/sse/mapper.ts +1 -0
  287. package/src/compaction/context-window.ts +41 -0
  288. package/src/compaction/manual.ts +10 -2
  289. package/src/compaction/plan.ts +11 -2
  290. package/src/compaction/verifier.ts +28 -3
  291. package/src/config/runtime.ts +23 -2
  292. package/src/connector/web/guarded-fetch.ts +26 -0
  293. package/src/connector/web/index.ts +3 -0
  294. package/src/connector/web/page.ts +130 -0
  295. package/src/connector/web/types.ts +47 -0
  296. package/src/constants/continuation.ts +10 -0
  297. package/src/constants/plugin/index.ts +1 -1
  298. package/src/manager/agent/lifecycle.ts +12 -4
  299. package/src/manager/session/turn-recorder.ts +10 -2
  300. package/src/plugin/index.ts +1 -0
  301. package/src/plugin/lifecycle.ts +107 -119
  302. package/src/plugin/shell-hook.ts +477 -43
  303. package/src/provider/errors.ts +18 -0
  304. package/src/provider/retry.ts +53 -3
  305. package/src/public-runtime.ts +8 -0
  306. package/src/public-types.ts +4 -0
  307. package/src/runtime/jobs/registry.ts +39 -2
  308. package/src/runtime/query/executor/tool-call-admission.ts +79 -13
  309. package/src/runtime/query/executor.ts +242 -8
  310. package/src/runtime/query/finalize-turn.ts +7 -1
  311. package/src/runtime/query/index.ts +45 -3
  312. package/src/runtime/query/iteration/index.ts +251 -12
  313. package/src/runtime/query/iteration/phases/compaction-breaker.ts +106 -0
  314. package/src/runtime/query/iteration/phases/compaction.ts +99 -12
  315. package/src/runtime/query/iteration/phases/context.ts +11 -0
  316. package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
  317. package/src/runtime/query/iteration/stream-turn.ts +24 -1
  318. package/src/runtime/query/path-escalation.ts +204 -0
  319. package/src/runtime/query/plugin-hooks.ts +8 -0
  320. package/src/runtime/query/reminders.ts +45 -0
  321. package/src/runtime/query/resume-pending.ts +27 -0
  322. package/src/runtime/query/review-policy.ts +106 -5
  323. package/src/runtime/query/tool-output-budget.ts +115 -1
  324. package/src/runtime/query/tooling.ts +6 -0
  325. package/src/session/paths.ts +11 -1
  326. package/src/store/memory/disk.ts +2 -1
  327. package/src/store/memory/markdown.ts +2 -1
  328. package/src/store/memory/naming.ts +13 -0
  329. package/src/tools/builtins/bash.ts +203 -34
  330. package/src/tools/builtins/content-fingerprint.ts +5 -0
  331. package/src/tools/builtins/edit.ts +69 -19
  332. package/src/tools/builtins/grep.ts +159 -38
  333. package/src/tools/builtins/read-file.ts +48 -5
  334. package/src/tools/builtins/read-render.ts +193 -0
  335. package/src/tools/builtins/web.ts +258 -33
  336. package/src/tools/coordinator/ask-user-question.ts +309 -103
  337. package/src/tools/coordinator/question-options.ts +8 -1
  338. package/src/tools/defineTool.ts +11 -2
  339. package/src/tools/paths.ts +1 -1
  340. package/src/types/agent/query.ts +3 -0
  341. package/src/types/errors/catalog.ts +1 -1
  342. package/src/types/hitl/index.ts +104 -0
  343. package/src/types/message/index.ts +2 -0
  344. package/src/types/plugin/index.ts +101 -18
  345. package/src/types/provider/error.ts +6 -0
  346. package/src/types/provider/errors.ts +49 -1
  347. package/src/types/provider/stream.ts +6 -0
  348. package/src/types/session/events.ts +15 -2
  349. package/src/types/session/records.ts +1 -0
  350. package/src/types/session/stop-reason.ts +5 -0
  351. package/src/types/tool/index.ts +53 -2
  352. package/src/utils/backoff.ts +21 -0
@@ -55,6 +55,14 @@ export type HITLResumeDecision =
55
55
  * listed here for an unmarked call is ignored rather than trusted.
56
56
  */
57
57
  skillGranted?: readonly string[]
58
+ /**
59
+ * Ids of calls approved on the strength of a `pre_tool_use` hook's
60
+ * `allow` ({@link ToolCallSummary.hookGrant}), with nobody asked.
61
+ *
62
+ * Audited like `skillGranted`, and ignored for any id whose call did
63
+ * not carry the mark.
64
+ */
65
+ hookGranted?: readonly string[]
58
66
  }
59
67
  | {
60
68
  action: 'modify_tools'
@@ -77,6 +85,15 @@ export type HITLResumeDecision =
77
85
  action: 'answer_question'
78
86
  selectedOptionIds: string[]
79
87
  freeText?: string
88
+ /**
89
+ * One answer per question of a batch (`UserQuestionData.questions`).
90
+ * When present it is the answer; `selectedOptionIds`/`freeText`
91
+ * then carry nothing the tool reads (send `[]`). Absent on a
92
+ * single-question park, and on a batch from a host that only
93
+ * knows one question — then `selectedOptionIds`/`freeText`
94
+ * answer the first question and the rest are unanswered.
95
+ */
96
+ answers?: readonly UserQuestionAnswer[]
80
97
  /**
81
98
  * Echo of `UserQuestionData.questionId` — the misdirection
82
99
  * guard. The park/resolve registry on hosts is typically
@@ -176,6 +193,28 @@ export interface ToolCallSummary {
176
193
  * and `strict`. A host's own handler may ignore it.
177
194
  */
178
195
  skillGrant?: { readonly skill: string }
196
+ /**
197
+ * What a `pre_tool_use` hook answered about approving this call, as the
198
+ * hooks reported it. `ask` is acted on by the kernel (it makes the call an
199
+ * explicit review); `allow` is acted on only through {@link hookGrant}.
200
+ */
201
+ hookPermission?: {
202
+ readonly decision: 'allow' | 'ask'
203
+ readonly reason?: string
204
+ }
205
+ /**
206
+ * Present when a `pre_tool_use` hook said `allow` and nothing stronger
207
+ * stands in the way.
208
+ *
209
+ * Marked on the same terms as {@link skillGrant}: only a call the
210
+ * operator's policy left to review (no `deny`, no explicit `ask` rule, no
211
+ * hook `ask`), that is not destructive, carries no {@link escalation} and
212
+ * is not one the tool always asks about. A sandbox escape, a path outside
213
+ * the roots, a tool's own `requiresApproval`, a rule's review and the
214
+ * dangerous-pattern floor therefore all still reach a person, or refuse.
215
+ * `plan` and `strict` refuse a marked call exactly as they refuse any other.
216
+ */
217
+ hookGrant?: { readonly reason?: string }
179
218
  }
180
219
 
181
220
  /** See {@link ToolCallSummary.escalation}. */
@@ -218,6 +257,27 @@ export interface ToolCallEscalation {
218
257
  * where a program name is actually decided.
219
258
  */
220
259
  unknownProgram?: string
260
+ /**
261
+ * Absolute paths this call writes that are protected in every mode: git
262
+ * hooks and config, shell rc files, editor and tool configuration, the
263
+ * project's own `.namzu` directory (see `authorization/protected-paths.ts`
264
+ * for the list). A write there plants something that runs later, outside
265
+ * the turn, so it is asked about every time and refused when nobody can be
266
+ * asked; no rule, mode or remembered approval covers it. Set for the file
267
+ * tools' declared path and for the visible write targets of a shell line:
268
+ * output redirections, and the operands of `cp`, `mv`, `tee`, `touch`,
269
+ * `mkdir`, `rm`, `rmdir` and `sed -i`.
270
+ */
271
+ protectedPaths?: readonly string[]
272
+ /**
273
+ * Why a shell line removes something it must not: the file system root, a
274
+ * top-level directory, the home directory, the working directory or one of
275
+ * its parents, a glob that crosses directories, or a target a variable or
276
+ * substitution may empty. The flags do not matter, only the targets. Asked
277
+ * about every time and refused when nobody can be asked. Present only for a
278
+ * tool that declares `commandArgument`.
279
+ */
280
+ dangerousRemoval?: string
221
281
  }
222
282
 
223
283
  export interface ToolModification {
@@ -250,6 +310,12 @@ export interface UserQuestionOption {
250
310
  * Absent, not `false`, on every other option.
251
311
  */
252
312
  recommended?: boolean
313
+ /**
314
+ * Plain text or markdown a host may show beside the option list while this
315
+ * option is focused (a snippet, a mock-up, a side-by-side). Optional and
316
+ * never required to answer; a host that has nowhere to show it ignores it.
317
+ */
318
+ preview?: string
253
319
  }
254
320
 
255
321
  /**
@@ -267,6 +333,44 @@ export interface UserQuestionData {
267
333
  options: UserQuestionOption[]
268
334
  multiSelect: boolean
269
335
  allowFreeText: boolean
336
+ /**
337
+ * Present when one `ask_user_question` call carries several related
338
+ * questions (two to four; a single question leaves this out). The
339
+ * top-level `question`, `header`, `options`, `multiSelect` and
340
+ * `allowFreeText` then mirror the FIRST entry, so a host that does not
341
+ * know this field still shows a coherent question and answers it; the
342
+ * rest read as unanswered.
343
+ *
344
+ * A host shows all of them together, collects every answer, and returns
345
+ * one {@link UserQuestionAnswer} per question it has an answer for through
346
+ * `answer_question.answers`.
347
+ */
348
+ questions?: readonly UserQuestionItem[]
349
+ }
350
+
351
+ /**
352
+ * One question of a batch. `id` is stable: the asking `tool_use_id`, `#`, and
353
+ * the question's zero-based position, so an answer names the question it
354
+ * belongs to even when a host shows them in another order.
355
+ */
356
+ export interface UserQuestionItem {
357
+ id: string
358
+ question: string
359
+ header?: string
360
+ options: UserQuestionOption[]
361
+ multiSelect: boolean
362
+ allowFreeText: boolean
363
+ }
364
+
365
+ /**
366
+ * The answer to one question of a batch. A question with no entry was not
367
+ * answered; an entry with no selection and no text counts the same.
368
+ */
369
+ export interface UserQuestionAnswer {
370
+ /** {@link UserQuestionItem.id}. */
371
+ questionId: string
372
+ selectedOptionIds: string[]
373
+ freeText?: string
270
374
  }
271
375
 
272
376
  export interface PlanApprovalData {
@@ -352,11 +352,13 @@ export const RUNTIME_CONTEXT_MESSAGE_KINDS = [
352
352
  'answer-review',
353
353
  'auto-continuation',
354
354
  'channel-message',
355
+ 'hook-feedback',
355
356
  'host-observation',
356
357
  'job-exit',
357
358
  'limit-finalization',
358
359
  'peer-message',
359
360
  'peer-notice',
361
+ 'reminder',
360
362
  'repeat-call',
361
363
  'steering',
362
364
  'step-context',
@@ -107,6 +107,24 @@ export type PluginHookEvent =
107
107
  | 'turn_interrupt'
108
108
  | 'pre_tool_use'
109
109
  | 'post_tool_use'
110
+ /**
111
+ * A tool call ended in failure. Fired after `post_tool_use` for the same
112
+ * call, only when the result is a failure. Carries `toolName`, `toolInput`
113
+ * and `toolResult`.
114
+ */
115
+ | 'post_tool_use_failure'
116
+ /**
117
+ * A person is about to be asked whether a tool call may run. Carries
118
+ * `toolName` and `toolInput`. A hook may answer before the question is
119
+ * shown: `continue` with `permission: { decision: 'allow' }` approves the
120
+ * call (never one that is always asked about), `skip` refuses it.
121
+ */
122
+ | 'permission_request'
123
+ /**
124
+ * The host wants the operator's attention: a review is waiting, or a turn
125
+ * settled. Carries `notification` (`type` and `message`). Observational.
126
+ */
127
+ | 'notification'
110
128
  | 'pre_llm_call'
111
129
  | 'post_llm_call'
112
130
  | 'iteration_start'
@@ -139,6 +157,9 @@ export function assertPluginHookEvent(event: PluginHookEvent): void {
139
157
  case 'turn_interrupt':
140
158
  case 'pre_tool_use':
141
159
  case 'post_tool_use':
160
+ case 'post_tool_use_failure':
161
+ case 'permission_request':
162
+ case 'notification':
142
163
  case 'pre_llm_call':
143
164
  case 'post_llm_call':
144
165
  case 'iteration_start':
@@ -196,6 +217,16 @@ export interface PluginCompactionInfo {
196
217
  readonly contextWindowTokens: number
197
218
  }
198
219
 
220
+ /** What a `notification` hook is shown. */
221
+ export interface PluginNotificationInfo {
222
+ /**
223
+ * `approval_required` (a review is waiting), `turn_settled` (a turn ended) or
224
+ * `idle_prompt` (the host has waited for the operator for a while).
225
+ */
226
+ readonly type: 'approval_required' | 'turn_settled' | 'idle_prompt'
227
+ readonly message: string
228
+ }
229
+
199
230
  export interface PluginHookContext {
200
231
  /** The turn the hook fired in. Absent on `session_start` and `session_end`. */
201
232
  readonly turnId?: TurnId
@@ -214,6 +245,15 @@ export interface PluginHookContext {
214
245
  readonly toolName?: string
215
246
  readonly toolInput?: unknown
216
247
  readonly toolResult?: ToolResult
248
+ /** What the host is telling the operator, on `notification`. */
249
+ readonly notification?: PluginNotificationInfo
250
+ /**
251
+ * On `turn_end` and `subagent_stop` when an earlier answer to the same
252
+ * event kept the turn going: the hook is being asked again about a turn it
253
+ * already refused to let end. A hook that always refuses would otherwise
254
+ * keep the model working until the turn's own limits stop it.
255
+ */
256
+ readonly stopHookActive?: boolean
217
257
  readonly iteration?: number
218
258
  /**
219
259
  * Why the turn was stopped, on `turn_interrupt`.
@@ -267,8 +307,41 @@ export interface PluginHookContext {
267
307
  readonly signal?: AbortSignal
268
308
  }
269
309
 
310
+ /**
311
+ * What a hook may say about a tool call beyond letting it run or not. Only
312
+ * `allow` and `ask` exist here: a refusal is `skip`, which always wins.
313
+ *
314
+ * `allow` stands in for the person's yes ONLY on a call that would have been
315
+ * asked about for no reason beyond "it changes something". It never covers a
316
+ * sandbox escape, a path outside the roots, a call the tool declares it always
317
+ * needs approval for, a call a rule sent to review explicitly, or one the tool
318
+ * declares destructive; and it never reopens what a rule denied.
319
+ * `ask` makes the call go to a person even where a rule or the tool's own
320
+ * read-only declaration would have let it through.
321
+ */
322
+ export interface PluginHookPermission {
323
+ readonly decision: 'allow' | 'ask'
324
+ readonly reason?: string
325
+ }
326
+
327
+ /** Extra facts a hook may attach to the verdicts that let the work go on. */
328
+ export interface PluginHookNotes {
329
+ /**
330
+ * Text for the model. On a tool event it is appended to what the model
331
+ * reads of that call; on `user_prompt_submit` it joins the turn's context.
332
+ */
333
+ readonly context?: string
334
+ /** Stop the turn once the current step is done. `reason` is shown to the operator. */
335
+ readonly halt?: { readonly reason: string }
336
+ /** A warning for the operator, reported through the logger. */
337
+ readonly notice?: string
338
+ }
339
+
270
340
  export type PluginHookResult =
271
- | { action: 'continue' }
341
+ | ({
342
+ action: 'continue'
343
+ permission?: PluginHookPermission
344
+ } & PluginHookNotes)
272
345
  /**
273
346
  * Add to what the model is told, without changing anything else.
274
347
  *
@@ -278,8 +351,12 @@ export type PluginHookResult =
278
351
  * it is rejected, because a tool result is not a place for context.
279
352
  */
280
353
  | { action: 'annotate'; text: string }
281
- | { action: 'skip'; reason: string }
282
- | { action: 'modify'; input: unknown }
354
+ | ({ action: 'skip'; reason: string } & PluginHookNotes)
355
+ | ({
356
+ action: 'modify'
357
+ input: unknown
358
+ permission?: PluginHookPermission
359
+ } & PluginHookNotes)
283
360
  | { action: 'error'; message: string }
284
361
  | { action: 'retry' }
285
362
  /**
@@ -333,9 +410,15 @@ export type PluginHookResultFor<E extends PluginHookEvent> = E extends 'user_pro
333
410
  ? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'modify' | 'error' }>
334
411
  : E extends 'post_tool_use'
335
412
  ? Extract<PluginHookResult, { action: 'continue' | 'replace' | 'retry' | 'error' }>
336
- : E extends 'turn_interrupt' | 'session_start' | 'session_end'
337
- ? Extract<PluginHookResult, { action: 'continue' }>
338
- : Extract<PluginHookResult, { action: 'continue' | 'error' }>
413
+ : E extends 'post_tool_use_failure'
414
+ ? Extract<PluginHookResult, { action: 'continue' | 'error' }>
415
+ : E extends 'permission_request'
416
+ ? Extract<PluginHookResult, { action: 'continue' | 'skip' }>
417
+ : E extends 'turn_end' | 'subagent_stop' | 'pre_compact'
418
+ ? Extract<PluginHookResult, { action: 'continue' | 'skip' | 'error' }>
419
+ : E extends 'turn_interrupt' | 'session_start' | 'session_end' | 'notification'
420
+ ? Extract<PluginHookResult, { action: 'continue' }>
421
+ : Extract<PluginHookResult, { action: 'continue' | 'error' }>
339
422
 
340
423
  /** The event and its handler verdict stay correlated, including in hook arrays. */
341
424
  export type PluginHookDefinition<E extends PluginHookEvent = PluginHookEvent> =
@@ -344,21 +427,21 @@ export type PluginHookDefinition<E extends PluginHookEvent = PluginHookEvent> =
344
427
  readonly event: E
345
428
  readonly handler: (context: PluginHookContext) => Promise<PluginHookResultFor<E>>
346
429
  /**
347
- * Lower runs first. Default 100.
430
+ * Lower comes first in the answers. Default 100.
348
431
  *
349
- * Order was install order, which is neither declared nor stable — it
350
- * depends on when each plugin happened to be installed. That is fine
351
- * for hooks that only observe, and wrong for the ones that decide:
352
- * `executeHooks` SHORT-CIRCUITS on `skip` and `error`, so a hook that
353
- * denies a dangerous command only gets to deny it if it runs before
354
- * whatever else stops the chain. A guard that fires depending on
355
- * installation history is not a guard.
356
- *
357
- * Ties keep registration order, so plugins that never set a priority
358
- * behave exactly as before. Convention: guards below 100, observers
359
- * above.
432
+ * The hooks of one event start together and none waits for another, so
433
+ * priority no longer says who runs first; it says whose answer is read
434
+ * first. The first refusal in that order is the one the model is told,
435
+ * and of several rewrites the last in that order wins. Ties keep
436
+ * registration order. Convention: guards below 100, observers above.
360
437
  */
361
438
  readonly priority?: number
439
+ /**
440
+ * Text for the operator's status line while this hook runs. Carried on
441
+ * the `plugin_hook_executing` event; a host that draws no status line
442
+ * ignores it.
443
+ */
444
+ readonly statusMessage?: string
362
445
  }
363
446
  : never
364
447
 
@@ -45,4 +45,10 @@ export interface ProviderRequestErrorInit extends ProviderErrorInfo {
45
45
  * still welcome; what is not welcome is a raw body passed straight in.
46
46
  */
47
47
  readonly detail?: string
48
+ /**
49
+ * The provider's own `x-should-retry` verdict, when it sent one. Only `false`
50
+ * is acted on: a provider that says the same request must not be sent again
51
+ * is believed over the status code, so the retry loop leaves it alone.
52
+ */
53
+ readonly shouldRetry?: boolean
48
54
  }
@@ -54,6 +54,16 @@ export interface ProviderErrorInit {
54
54
  readonly cause?: unknown
55
55
  }
56
56
 
57
+ function shouldRetryHeader(link: Record<string, unknown>): false | undefined {
58
+ const headers = link.headers
59
+ if (!headers || typeof headers !== 'object') return undefined
60
+ const raw =
61
+ typeof (headers as Headers).get === 'function'
62
+ ? (headers as Headers).get('x-should-retry')
63
+ : (headers as Record<string, unknown>)['x-should-retry']
64
+ return typeof raw === 'string' && raw.trim().toLowerCase() === 'false' ? false : undefined
65
+ }
66
+
57
67
  /**
58
68
  * A `retryable` flag declared anywhere on the cause chain.
59
69
  *
@@ -72,6 +82,9 @@ export function declaredRetryable(err: unknown): boolean | undefined {
72
82
  for (const link of causeChain(err)) {
73
83
  const declared = link.retryable
74
84
  if (typeof declared === 'boolean') return declared
85
+ // A raw vendor error that still carries its response headers: the
86
+ // provider's own `x-should-retry: false` is a first-hand "do not resend".
87
+ if (shouldRetryHeader(link) === false) return false
75
88
  }
76
89
  return undefined
77
90
  }
@@ -224,6 +237,39 @@ function chainMessages(err: unknown): string[] {
224
237
  return out
225
238
  }
226
239
 
240
+ /**
241
+ * What a provider said when prompt plus requested output together passed the
242
+ * window: the prompt size, the output cap that was asked for, and the window.
243
+ */
244
+ export interface OutputOverflowFigures {
245
+ readonly inputTokens: number
246
+ readonly maxTokens: number
247
+ readonly contextLimit: number
248
+ }
249
+
250
+ /**
251
+ * Read the "input length and `max_tokens` exceed context limit: A + B > C"
252
+ * wording from a failure, anywhere on its cause chain.
253
+ *
254
+ * Different from a prompt that is too long on its own: here the prompt fits
255
+ * and only the requested output does not, so the remedy is a smaller
256
+ * `max_tokens`, not a smaller history. `undefined` means the failure does not
257
+ * carry those figures.
258
+ */
259
+ export function readOutputOverflow(err: unknown): OutputOverflowFigures | undefined {
260
+ const pattern = /input length and `?max_tokens`? exceed context limit: (\d+) \+ (\d+) > (\d+)/i
261
+ for (const message of chainMessages(err)) {
262
+ const match = pattern.exec(message)
263
+ if (!match) continue
264
+ const inputTokens = Number.parseInt(match[1] ?? '', 10)
265
+ const maxTokens = Number.parseInt(match[2] ?? '', 10)
266
+ const contextLimit = Number.parseInt(match[3] ?? '', 10)
267
+ if (![inputTokens, maxTokens, contextLimit].every(Number.isFinite)) continue
268
+ return { inputTokens, maxTokens, contextLimit }
269
+ }
270
+ return undefined
271
+ }
272
+
227
273
  /** Node/undici transport failures that mean "never reached the model". */
228
274
  const NETWORK_ERRNOS: ReadonlySet<string> = new Set([
229
275
  'ECONNRESET',
@@ -395,6 +441,7 @@ function classifyFromProviderRequestError(
395
441
  providerId?: unknown
396
442
  status?: unknown
397
443
  retryAfterMs?: unknown
444
+ shouldRetry?: unknown
398
445
  }
399
446
  if (typeof candidate.kind !== 'string') return undefined
400
447
  const mapped = KIND_TO_CODE[candidate.kind]
@@ -406,7 +453,8 @@ function classifyFromProviderRequestError(
406
453
  return new ProviderError({
407
454
  code: mapped.code,
408
455
  message: err.message,
409
- retryable: mapped.retryable,
456
+ // The provider saying "do not resend" beats the kind's own verdict.
457
+ retryable: candidate.shouldRetry === false ? false : mapped.retryable,
410
458
  cause: err,
411
459
  ...(typeof candidate.providerId === 'string'
412
460
  ? { providerId: candidate.providerId }
@@ -173,6 +173,12 @@ export interface ProviderRetryNotice {
173
173
  readonly status?: number
174
174
  /** The delay came from the server's own `Retry-After`, not backoff. */
175
175
  readonly serverDirected: boolean
176
+ /**
177
+ * The failed attempt had already streamed text or reasoning, and the request
178
+ * is being sent again from the start. Whatever that attempt produced is void
179
+ * and the consumer must drop it before the next chunk.
180
+ */
181
+ readonly restart?: boolean
176
182
  }
177
183
 
178
184
  /**
@@ -279,11 +279,17 @@ type CoreSessionEvent =
279
279
  * `tool_result`, so it was refused wholesale rather than sent to
280
280
  * a provider that rejects the pairing. A reducer bug, and one
281
281
  * `findSafeTrimIndex` exists to prevent.
282
+ * - `breaker_open` — only with `compactionConfig.circuitBreaker`:
283
+ * three passes in a row failed, so automatic compaction is off for
284
+ * the rest of the turn. Sent once. `messages` is the live count.
285
+ * - `refilling` — only with `compactionConfig.circuitBreaker`: the
286
+ * context filled back up within three iterations of a compaction,
287
+ * three times in a row, and the turn is stopping. Sent once.
282
288
  */
283
- cause: 'reducer_threw' | 'shed_nothing' | 'split_tool_pair'
289
+ cause: 'reducer_threw' | 'shed_nothing' | 'split_tool_pair' | 'breaker_open' | 'refilling'
284
290
  /** Unchanged, and stated so a reader need not infer it. */
285
291
  messages: number
286
- /** Present only for `reducer_threw`. */
292
+ /** For `reducer_threw`, the reducer's message; for `breaker_open` and `refilling`, the one line to show. */
287
293
  error?: string
288
294
  }
289
295
  | {
@@ -381,6 +387,11 @@ type CoreSessionEvent =
381
387
  status?: number
382
388
  /** The delay came from the server's own `Retry-After`. */
383
389
  serverDirected: boolean
390
+ /**
391
+ * The failed attempt had already streamed text or reasoning and the
392
+ * request starts over: drop what this message showed so far.
393
+ */
394
+ restart?: boolean
384
395
  }
385
396
  /**
386
397
  * A member of the provider chain could not serve, and a later member has
@@ -987,6 +998,8 @@ type CoreSessionEvent =
987
998
  turnId?: TurnId
988
999
  pluginId: PluginId
989
1000
  hookEvent: PluginHookEvent
1001
+ /** The hook's own text for a status line while it runs, when it declared one. */
1002
+ statusMessage?: string
990
1003
  }
991
1004
  | {
992
1005
  type: 'plugin_hook_completed'
@@ -235,6 +235,7 @@ const stopReason = z.enum([
235
235
  'stop_condition',
236
236
  'step_refused',
237
237
  'structured_output_failed',
238
+ 'output_limit',
238
239
  'answer_rejected',
239
240
  'input_guardrail',
240
241
  'output_guardrail',
@@ -38,6 +38,11 @@ export type StopReason =
38
38
  | 'step_refused'
39
39
  /** The model never produced a valid structured output within its retries. */
40
40
  | 'structured_output_failed'
41
+ /**
42
+ * The model's reply was cut off at its output limit more times in a row
43
+ * than `maxOutputContinuations` allows. Only with that option set.
44
+ */
45
+ | 'output_limit'
41
46
  /**
42
47
  * A host reviewer kept rejecting the answer and the turn ran out of
43
48
  * attempts.
@@ -1,3 +1,4 @@
1
+ import type { ChildProcess } from 'node:child_process'
1
2
  import type { z } from 'zod'
2
3
  import type { CodeNavigationProvider } from '../code-navigation/index.js'
3
4
  // Type-only, and circular by design: a tool-result guardrail is described in
@@ -118,7 +119,24 @@ export interface BackgroundJobRegistryRef {
118
119
  id: string
119
120
  status: string
120
121
  }
121
- get(id: string): { id: string; status: string; exitCode?: number; error?: string }
122
+ get(id: string): {
123
+ id: string
124
+ status: string
125
+ exitCode?: number
126
+ error?: string
127
+ }
128
+ /**
129
+ * Take over a process the caller already started and is waiting on, so a
130
+ * foreground command that outran its timeout can keep running as a job
131
+ * instead of being killed. Optional: a host without it (or a sandboxed
132
+ * turn) keeps the kill. Throws when the owner is at its job cap.
133
+ */
134
+ adopt?(params: {
135
+ command: string
136
+ child: ChildProcess
137
+ workingDirectory?: string
138
+ initialOutput?: readonly { readonly stream: 'stdout' | 'stderr'; readonly text: string }[]
139
+ }): { id: string; status: string }
122
140
  /**
123
141
  * Resolves once the process exists or failed to start (`error` says why).
124
142
  * Optional: a host that cannot tell may omit it, and `bash` then replies
@@ -483,15 +501,39 @@ export interface ToolContext {
483
501
  */
484
502
  web?: {
485
503
  readonly fetch?: {
486
- fetch(request: { url: string; signal?: AbortSignal }): Promise<{
504
+ fetch(request: {
505
+ url: string
506
+ signal?: AbortSignal
507
+ /** `same-host` asks for a redirect to another site to be handed back, not followed. */
508
+ redirects?: 'follow' | 'same-host'
509
+ headers?: Readonly<Record<string, string>>
510
+ }): Promise<{
487
511
  url: string
488
512
  status: number
489
513
  contentType?: string
490
514
  body: string
491
515
  truncated: boolean
492
516
  redirects: readonly string[]
517
+ /** A redirect that was not followed; `body` is then empty. */
518
+ redirect?: { url: string; status: number }
493
519
  }>
494
520
  }
521
+ /**
522
+ * Answers a prompt from a page with a small model. Optional: without
523
+ * it `web_fetch` returns the page as markdown instead of an answer.
524
+ */
525
+ readonly extract?: {
526
+ extract(request: {
527
+ url: string
528
+ prompt: string
529
+ content: string
530
+ instruction: string
531
+ body: string
532
+ signal?: AbortSignal
533
+ sessionId: string
534
+ turnId: string
535
+ }): Promise<string | undefined>
536
+ }
495
537
  readonly search?: {
496
538
  search(request: {
497
539
  query: string
@@ -1103,6 +1145,15 @@ export interface ToolDefinition<TInput = unknown> extends ToolPresentation<TInpu
1103
1145
  * their external effects have stopped, and does not imply success.
1104
1146
  */
1105
1147
  executionBarrier?: boolean
1148
+ /**
1149
+ * The tool bounds its own output (a paged read, say), so the turn's default
1150
+ * per-result cap does not apply to it and its result is never replaced to
1151
+ * fit the per-message budget.
1152
+ *
1153
+ * Only the DEFAULT is lifted. A host that sets `maxToolOutputChars`
1154
+ * explicitly still bounds this tool's result by it.
1155
+ */
1156
+ selfLimitingOutput?: boolean
1106
1157
  }
1107
1158
 
1108
1159
  /**
@@ -43,6 +43,27 @@ export function backoffWithJitter(
43
43
  return Math.round(random() * exponential)
44
44
  }
45
45
 
46
+ /**
47
+ * Exponential backoff that only ever adds jitter: the delay is the exponential
48
+ * step plus up to `ratio` of itself (a quarter by default), never less.
49
+ *
50
+ * Full jitter spreads a herd better but can answer a failure after a few
51
+ * milliseconds, which is a poor way to treat a server that just said it is
52
+ * struggling. This curve keeps the floor of every step, so a person watching a
53
+ * countdown sees a wait that grows the way the numbers on screen say it does.
54
+ *
55
+ * `attempt` is 0-based: attempt 0 draws from `[initialDelayMs, initialDelayMs * (1 + ratio)]`.
56
+ */
57
+ export function backoffProportional(
58
+ attempt: number,
59
+ policy: BackoffPolicy,
60
+ random: () => number = Math.random,
61
+ ratio = 0.25,
62
+ ): number {
63
+ const exponential = Math.min(policy.initialDelayMs * 2 ** attempt, policy.maxDelayMs)
64
+ return Math.round(exponential + random() * ratio * exponential)
65
+ }
66
+
46
67
  /**
47
68
  * Sleep, unless the signal says not to.
48
69
  *