@namzu/sdk 51.1.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 (323) hide show
  1. package/CHANGELOG.md +100 -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 +19 -7
  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/manager/agent/lifecycle.d.ts +7 -0
  78. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  79. package/dist/manager/agent/lifecycle.js +11 -2
  80. package/dist/manager/agent/lifecycle.js.map +1 -1
  81. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  82. package/dist/manager/session/turn-recorder.js +4 -2
  83. package/dist/manager/session/turn-recorder.js.map +1 -1
  84. package/dist/plugin/index.d.ts +1 -1
  85. package/dist/plugin/index.d.ts.map +1 -1
  86. package/dist/plugin/index.js +1 -1
  87. package/dist/plugin/index.js.map +1 -1
  88. package/dist/plugin/lifecycle.d.ts +6 -0
  89. package/dist/plugin/lifecycle.d.ts.map +1 -1
  90. package/dist/plugin/lifecycle.js +98 -114
  91. package/dist/plugin/lifecycle.js.map +1 -1
  92. package/dist/plugin/shell-hook.d.ts +90 -22
  93. package/dist/plugin/shell-hook.d.ts.map +1 -1
  94. package/dist/plugin/shell-hook.js +434 -41
  95. package/dist/plugin/shell-hook.js.map +1 -1
  96. package/dist/public-runtime.d.ts +4 -1
  97. package/dist/public-runtime.d.ts.map +1 -1
  98. package/dist/public-runtime.js +4 -1
  99. package/dist/public-runtime.js.map +1 -1
  100. package/dist/public-types.d.ts +2 -2
  101. package/dist/public-types.d.ts.map +1 -1
  102. package/dist/runtime/jobs/registry.d.ts +21 -0
  103. package/dist/runtime/jobs/registry.d.ts.map +1 -1
  104. package/dist/runtime/jobs/registry.js +15 -2
  105. package/dist/runtime/jobs/registry.js.map +1 -1
  106. package/dist/runtime/query/executor/tool-call-admission.d.ts +10 -0
  107. package/dist/runtime/query/executor/tool-call-admission.d.ts.map +1 -1
  108. package/dist/runtime/query/executor/tool-call-admission.js +74 -13
  109. package/dist/runtime/query/executor/tool-call-admission.js.map +1 -1
  110. package/dist/runtime/query/executor.d.ts +41 -2
  111. package/dist/runtime/query/executor.d.ts.map +1 -1
  112. package/dist/runtime/query/executor.js +190 -6
  113. package/dist/runtime/query/executor.js.map +1 -1
  114. package/dist/runtime/query/finalize-turn.d.ts +6 -0
  115. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  116. package/dist/runtime/query/finalize-turn.js +1 -1
  117. package/dist/runtime/query/finalize-turn.js.map +1 -1
  118. package/dist/runtime/query/index.d.ts +29 -0
  119. package/dist/runtime/query/index.d.ts.map +1 -1
  120. package/dist/runtime/query/index.js +15 -3
  121. package/dist/runtime/query/index.js.map +1 -1
  122. package/dist/runtime/query/iteration/index.d.ts +31 -0
  123. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/index.js +216 -7
  125. package/dist/runtime/query/iteration/index.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts +58 -0
  127. package/dist/runtime/query/iteration/phases/compaction-breaker.d.ts.map +1 -0
  128. package/dist/runtime/query/iteration/phases/compaction-breaker.js +91 -0
  129. package/dist/runtime/query/iteration/phases/compaction-breaker.js.map +1 -0
  130. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/compaction.js +89 -7
  132. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  133. package/dist/runtime/query/iteration/phases/context.d.ts +10 -0
  134. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  135. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  136. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/phases/tool-review.js +165 -2
  138. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  139. package/dist/runtime/query/path-escalation.d.ts +36 -0
  140. package/dist/runtime/query/path-escalation.d.ts.map +1 -0
  141. package/dist/runtime/query/path-escalation.js +184 -0
  142. package/dist/runtime/query/path-escalation.js.map +1 -0
  143. package/dist/runtime/query/plugin-hooks.d.ts.map +1 -1
  144. package/dist/runtime/query/plugin-hooks.js +8 -0
  145. package/dist/runtime/query/plugin-hooks.js.map +1 -1
  146. package/dist/runtime/query/reminders.d.ts +35 -0
  147. package/dist/runtime/query/reminders.d.ts.map +1 -0
  148. package/dist/runtime/query/reminders.js +7 -0
  149. package/dist/runtime/query/reminders.js.map +1 -0
  150. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  151. package/dist/runtime/query/resume-pending.js +26 -0
  152. package/dist/runtime/query/resume-pending.js.map +1 -1
  153. package/dist/runtime/query/review-policy.d.ts +26 -0
  154. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  155. package/dist/runtime/query/review-policy.js +79 -5
  156. package/dist/runtime/query/review-policy.js.map +1 -1
  157. package/dist/runtime/query/tool-output-budget.d.ts +46 -0
  158. package/dist/runtime/query/tool-output-budget.d.ts.map +1 -1
  159. package/dist/runtime/query/tool-output-budget.js +85 -1
  160. package/dist/runtime/query/tool-output-budget.js.map +1 -1
  161. package/dist/runtime/query/tooling.d.ts +1 -0
  162. package/dist/runtime/query/tooling.d.ts.map +1 -1
  163. package/dist/runtime/query/tooling.js +5 -0
  164. package/dist/runtime/query/tooling.js.map +1 -1
  165. package/dist/session/paths.d.ts +5 -1
  166. package/dist/session/paths.d.ts.map +1 -1
  167. package/dist/session/paths.js +8 -0
  168. package/dist/session/paths.js.map +1 -1
  169. package/dist/store/memory/disk.d.ts.map +1 -1
  170. package/dist/store/memory/disk.js +2 -2
  171. package/dist/store/memory/disk.js.map +1 -1
  172. package/dist/store/memory/markdown.d.ts.map +1 -1
  173. package/dist/store/memory/markdown.js +2 -2
  174. package/dist/store/memory/markdown.js.map +1 -1
  175. package/dist/store/memory/naming.d.ts +9 -0
  176. package/dist/store/memory/naming.d.ts.map +1 -1
  177. package/dist/store/memory/naming.js +12 -0
  178. package/dist/store/memory/naming.js.map +1 -1
  179. package/dist/tools/builtins/bash.d.ts +30 -1
  180. package/dist/tools/builtins/bash.d.ts.map +1 -1
  181. package/dist/tools/builtins/bash.js +170 -26
  182. package/dist/tools/builtins/bash.js.map +1 -1
  183. package/dist/tools/builtins/content-fingerprint.d.ts +2 -0
  184. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  185. package/dist/tools/builtins/content-fingerprint.js +4 -0
  186. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  187. package/dist/tools/builtins/edit.d.ts.map +1 -1
  188. package/dist/tools/builtins/edit.js +60 -18
  189. package/dist/tools/builtins/edit.js.map +1 -1
  190. package/dist/tools/builtins/grep.d.ts +8 -1
  191. package/dist/tools/builtins/grep.d.ts.map +1 -1
  192. package/dist/tools/builtins/grep.js +152 -33
  193. package/dist/tools/builtins/grep.js.map +1 -1
  194. package/dist/tools/builtins/read-file.d.ts +1 -0
  195. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  196. package/dist/tools/builtins/read-file.js +44 -5
  197. package/dist/tools/builtins/read-file.js.map +1 -1
  198. package/dist/tools/builtins/read-render.d.ts +52 -0
  199. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  200. package/dist/tools/builtins/read-render.js +143 -0
  201. package/dist/tools/builtins/read-render.js.map +1 -1
  202. package/dist/tools/builtins/web.d.ts +13 -2
  203. package/dist/tools/builtins/web.d.ts.map +1 -1
  204. package/dist/tools/builtins/web.js +225 -32
  205. package/dist/tools/builtins/web.js.map +1 -1
  206. package/dist/tools/coordinator/ask-user-question.d.ts +2 -0
  207. package/dist/tools/coordinator/ask-user-question.d.ts.map +1 -1
  208. package/dist/tools/coordinator/ask-user-question.js +256 -92
  209. package/dist/tools/coordinator/ask-user-question.js.map +1 -1
  210. package/dist/tools/coordinator/question-options.d.ts +1 -0
  211. package/dist/tools/coordinator/question-options.d.ts.map +1 -1
  212. package/dist/tools/coordinator/question-options.js +6 -1
  213. package/dist/tools/coordinator/question-options.js.map +1 -1
  214. package/dist/tools/defineTool.d.ts +4 -1
  215. package/dist/tools/defineTool.d.ts.map +1 -1
  216. package/dist/tools/defineTool.js +6 -1
  217. package/dist/tools/defineTool.js.map +1 -1
  218. package/dist/tools/paths.d.ts +6 -0
  219. package/dist/tools/paths.d.ts.map +1 -1
  220. package/dist/tools/paths.js +1 -1
  221. package/dist/tools/paths.js.map +1 -1
  222. package/dist/types/agent/query.d.ts +3 -0
  223. package/dist/types/agent/query.d.ts.map +1 -1
  224. package/dist/types/errors/catalog.js +1 -1
  225. package/dist/types/errors/catalog.js.map +1 -1
  226. package/dist/types/hitl/index.d.ts +104 -0
  227. package/dist/types/hitl/index.d.ts.map +1 -1
  228. package/dist/types/hitl/index.js.map +1 -1
  229. package/dist/types/message/index.d.ts +1 -1
  230. package/dist/types/message/index.d.ts.map +1 -1
  231. package/dist/types/message/index.js +2 -0
  232. package/dist/types/message/index.js.map +1 -1
  233. package/dist/types/plugin/index.d.ts +93 -19
  234. package/dist/types/plugin/index.d.ts.map +1 -1
  235. package/dist/types/plugin/index.js +3 -0
  236. package/dist/types/plugin/index.js.map +1 -1
  237. package/dist/types/provider/errors.d.ts +19 -0
  238. package/dist/types/provider/errors.d.ts.map +1 -1
  239. package/dist/types/provider/errors.js +24 -0
  240. package/dist/types/provider/errors.js.map +1 -1
  241. package/dist/types/session/events.d.ts +10 -2
  242. package/dist/types/session/events.d.ts.map +1 -1
  243. package/dist/types/session/events.js.map +1 -1
  244. package/dist/types/session/records.d.ts +9 -9
  245. package/dist/types/session/records.d.ts.map +1 -1
  246. package/dist/types/session/records.js +1 -0
  247. package/dist/types/session/records.js.map +1 -1
  248. package/dist/types/session/stop-reason.d.ts +5 -0
  249. package/dist/types/session/stop-reason.d.ts.map +1 -1
  250. package/dist/types/tool/index.d.ts +52 -0
  251. package/dist/types/tool/index.d.ts.map +1 -1
  252. package/dist/types/tool/index.js.map +1 -1
  253. package/package.json +4 -2
  254. package/src/agents/QueryAgent.ts +5 -1
  255. package/src/agents/SupervisorAgent.ts +1 -1
  256. package/src/agents/examples/SupervisorAgent.ts +217 -106
  257. package/src/agents/index.ts +1 -1
  258. package/src/authorization/protected-paths.ts +110 -0
  259. package/src/authorization/readonly-commands.ts +771 -0
  260. package/src/authorization/readonly-flags.ts +196 -0
  261. package/src/authorization/removal.ts +326 -0
  262. package/src/authorization/shell-paths.ts +927 -0
  263. package/src/bridge/acp/update.ts +1 -0
  264. package/src/compaction/context-window.ts +41 -0
  265. package/src/compaction/manual.ts +10 -2
  266. package/src/compaction/plan.ts +11 -2
  267. package/src/compaction/verifier.ts +28 -3
  268. package/src/config/runtime.ts +23 -2
  269. package/src/connector/web/guarded-fetch.ts +26 -0
  270. package/src/connector/web/index.ts +3 -0
  271. package/src/connector/web/page.ts +130 -0
  272. package/src/connector/web/types.ts +47 -0
  273. package/src/constants/continuation.ts +10 -0
  274. package/src/constants/plugin/index.ts +1 -1
  275. package/src/manager/agent/lifecycle.ts +12 -4
  276. package/src/manager/session/turn-recorder.ts +10 -2
  277. package/src/plugin/index.ts +1 -0
  278. package/src/plugin/lifecycle.ts +107 -119
  279. package/src/plugin/shell-hook.ts +477 -43
  280. package/src/public-runtime.ts +8 -0
  281. package/src/public-types.ts +4 -0
  282. package/src/runtime/jobs/registry.ts +39 -2
  283. package/src/runtime/query/executor/tool-call-admission.ts +79 -13
  284. package/src/runtime/query/executor.ts +242 -8
  285. package/src/runtime/query/finalize-turn.ts +7 -1
  286. package/src/runtime/query/index.ts +45 -3
  287. package/src/runtime/query/iteration/index.ts +251 -12
  288. package/src/runtime/query/iteration/phases/compaction-breaker.ts +106 -0
  289. package/src/runtime/query/iteration/phases/compaction.ts +99 -12
  290. package/src/runtime/query/iteration/phases/context.ts +11 -0
  291. package/src/runtime/query/iteration/phases/tool-review.ts +177 -2
  292. package/src/runtime/query/path-escalation.ts +204 -0
  293. package/src/runtime/query/plugin-hooks.ts +8 -0
  294. package/src/runtime/query/reminders.ts +45 -0
  295. package/src/runtime/query/resume-pending.ts +27 -0
  296. package/src/runtime/query/review-policy.ts +106 -5
  297. package/src/runtime/query/tool-output-budget.ts +115 -1
  298. package/src/runtime/query/tooling.ts +6 -0
  299. package/src/session/paths.ts +11 -1
  300. package/src/store/memory/disk.ts +2 -1
  301. package/src/store/memory/markdown.ts +2 -1
  302. package/src/store/memory/naming.ts +13 -0
  303. package/src/tools/builtins/bash.ts +203 -34
  304. package/src/tools/builtins/content-fingerprint.ts +5 -0
  305. package/src/tools/builtins/edit.ts +69 -19
  306. package/src/tools/builtins/grep.ts +159 -38
  307. package/src/tools/builtins/read-file.ts +48 -5
  308. package/src/tools/builtins/read-render.ts +193 -0
  309. package/src/tools/builtins/web.ts +258 -33
  310. package/src/tools/coordinator/ask-user-question.ts +309 -103
  311. package/src/tools/coordinator/question-options.ts +8 -1
  312. package/src/tools/defineTool.ts +11 -2
  313. package/src/tools/paths.ts +1 -1
  314. package/src/types/agent/query.ts +3 -0
  315. package/src/types/errors/catalog.ts +1 -1
  316. package/src/types/hitl/index.ts +104 -0
  317. package/src/types/message/index.ts +2 -0
  318. package/src/types/plugin/index.ts +101 -18
  319. package/src/types/provider/errors.ts +33 -0
  320. package/src/types/session/events.ts +10 -2
  321. package/src/types/session/records.ts +1 -0
  322. package/src/types/session/stop-reason.ts +5 -0
  323. package/src/types/tool/index.ts +53 -2
@@ -1,4 +1,7 @@
1
- import { resolveContextWindow } from '../../../../compaction/context-window.js'
1
+ import {
2
+ resolveContextWindow,
3
+ resolveTriggerThreshold,
4
+ } from '../../../../compaction/context-window.js'
2
5
  import { findDanglingMessages } from '../../../../compaction/dangling.js'
3
6
  import {
4
7
  type CompactionPlan,
@@ -19,6 +22,11 @@ import { invariants } from '../../../../invariants/index.js'
19
22
  import { resolveTaskModel } from '../../../../model-router/task-router.js'
20
23
  import type { Message } from '../../../../types/message/index.js'
21
24
  import { applyLifecycleHookResults } from '../../plugin-hooks.js'
25
+ import {
26
+ COMPACTION_FAILURE_MESSAGE,
27
+ COMPACTION_REFILL_MESSAGE,
28
+ compactionBreaker,
29
+ } from './compaction-breaker.js'
22
30
  import type { IterationContext } from './context.js'
23
31
  import { isWorkingMemoryMessage } from './working-memory.js'
24
32
 
@@ -169,6 +177,7 @@ async function commitToolResultClear(
169
177
  ctx.recorder.clearLastPromptTokens?.()
170
178
  await emitToolResultClear(ctx, plan)
171
179
  await emitContextUsageSnapshot(ctx, estimateTokens(ctx), contextWindowTokens, windowSource)
180
+ compactionBreaker(ctx)?.recordRelief()
172
181
  }
173
182
 
174
183
  /**
@@ -312,6 +321,7 @@ async function declined(
312
321
  messages: number,
313
322
  error?: string,
314
323
  ): Promise<void> {
324
+ compactionBreaker(ctx)?.recordFailure()
315
325
  await ctx.emitEvent?.({
316
326
  type: 'compaction_failed',
317
327
  turnId: ctx.recorder.turnId,
@@ -322,6 +332,32 @@ async function declined(
322
332
  })
323
333
  }
324
334
 
335
+ /**
336
+ * The third refill in a row: stop the turn instead of compacting again.
337
+ * Returns true when this pass must not run. Only with `circuitBreaker`.
338
+ */
339
+ async function stopOnRefill(ctx: IterationContext): Promise<boolean> {
340
+ const breaker = compactionBreaker(ctx)
341
+ if (!breaker) return false
342
+ // Already announced: the turn is on its way out; do not say it twice.
343
+ if (breaker.stop !== undefined) return true
344
+ if (!breaker.wouldTrip(ctx.recorder.currentIteration)) return false
345
+ ctx.log.warn('Compaction keeps refilling — stopping the turn', {
346
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
347
+ [NAMZU.ITERATION]: ctx.recorder.currentIteration,
348
+ })
349
+ breaker.stopTurn(COMPACTION_REFILL_MESSAGE)
350
+ await ctx.emitEvent?.({
351
+ type: 'compaction_failed',
352
+ turnId: ctx.recorder.turnId,
353
+ iteration: ctx.recorder.currentIteration,
354
+ cause: 'refilling',
355
+ messages: ctx.recorder.messages.length,
356
+ error: COMPACTION_REFILL_MESSAGE,
357
+ })
358
+ return true
359
+ }
360
+
325
361
  /**
326
362
  * Ctx for `compaction:no-split-tool-pair`, registered just below: the
327
363
  * candidate history a reducer just returned, not yet installed. Passed on
@@ -456,6 +492,7 @@ async function applyReducer(
456
492
  reduction.contextWindowTokens,
457
493
  measurement.windowSource,
458
494
  )
495
+ compactionBreaker(ctx)?.recordCompaction(ctx.recorder.currentIteration)
459
496
  }
460
497
 
461
498
  /**
@@ -498,9 +535,10 @@ async function recordShed(
498
535
 
499
536
  /**
500
537
  * A pass, once the check decided to run one, is bracketed by the
501
- * `pre_compact` and `post_compact` hooks. Observational: a hook sees the
502
- * numbers and cannot stop the pass, because a context the provider will
503
- * reject is not a thing a hook may insist on.
538
+ * `pre_compact` and `post_compact` hooks. A hook sees the numbers and may
539
+ * cancel a pass the threshold asked for (`skip`), and cannot stop one the
540
+ * provider forced, because a context the provider will reject is not a thing
541
+ * a hook may insist on.
504
542
  */
505
543
  interface CompactionPass {
506
544
  attempted: boolean
@@ -573,11 +611,31 @@ async function runCompactionCheckInner(
573
611
  // The salience strategy starts holding the context at `softTarget`,
574
612
  // well before the summary trigger; every other strategy waits for it.
575
613
  const salience = config.strategy === 'salience'
576
- const startAt = salience
577
- ? Math.min(config.softTarget ?? DEFAULT_SOFT_TARGET, config.triggerThreshold)
578
- : config.triggerThreshold
614
+ const maxOutputTokens = ctx.providerCapabilities?.maxOutputTokens
615
+ const trigger = resolveTriggerThreshold(config.triggerThreshold, budget, maxOutputTokens)
616
+ const startAt = salience ? Math.min(config.softTarget ?? DEFAULT_SOFT_TARGET, trigger) : trigger
579
617
  if (!options?.force && usage < startAt) return
580
618
 
619
+ // Three failed passes in a row: stop trying for the rest of the turn, and
620
+ // say so once. A pass the provider forced is never gated.
621
+ const breaker = compactionBreaker(ctx)
622
+ if (breaker?.failuresOpen && !options?.force) {
623
+ if (breaker.takeFailureNotice()) {
624
+ ctx.log.warn('Compaction failed three times in a row — switched off for this turn', {
625
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
626
+ })
627
+ await ctx.emitEvent?.({
628
+ type: 'compaction_failed',
629
+ turnId: ctx.recorder.turnId,
630
+ iteration: ctx.recorder.currentIteration,
631
+ cause: 'breaker_open',
632
+ messages: ctx.recorder.messages.length,
633
+ error: COMPACTION_FAILURE_MESSAGE,
634
+ })
635
+ }
636
+ return
637
+ }
638
+
581
639
  pass.attempted = true
582
640
  pass.tokensBefore = estimatedTokens
583
641
  pass.contextWindowTokens = budget
@@ -598,6 +656,25 @@ async function runCompactionCheckInner(
598
656
  ctx.emitEvent,
599
657
  )
600
658
  applyLifecycleHookResults('pre_compact', results)
659
+ const refusal = results.find((result) => result.action === 'skip')
660
+ if (refusal?.action === 'skip') {
661
+ // A threshold pass is the context's own housekeeping and a hook may
662
+ // postpone it. A forced pass runs because the provider rejected the
663
+ // prompt: a context it will reject is not something a hook may insist
664
+ // on, so the refusal is reported and the pass runs.
665
+ if (!options?.force) {
666
+ pass.attempted = false
667
+ ctx.log.info('A pre_compact hook cancelled the compaction pass', {
668
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
669
+ 'namzu.hook.reason': refusal.reason,
670
+ })
671
+ return
672
+ }
673
+ ctx.log.warn('A pre_compact hook asked to cancel a compaction the provider forced; it ran', {
674
+ [NAMZU.TURN_ID]: ctx.recorder.turnId,
675
+ 'namzu.hook.reason': refusal.reason,
676
+ })
677
+ }
601
678
  }
602
679
 
603
680
  // A reducer, when the turn has one, OWNS reduction — the structured pass
@@ -609,6 +686,7 @@ async function runCompactionCheckInner(
609
686
  ctx.contextReducer ??
610
687
  (config.strategy === 'sliding-window' ? createSlidingWindowReducer() : undefined)
611
688
  if (reducer) {
689
+ if (!options?.force && (await stopOnRefill(ctx))) return
612
690
  await applyReducer(
613
691
  ctx,
614
692
  reducer,
@@ -635,7 +713,7 @@ async function runCompactionCheckInner(
635
713
  'namzu.runtime.window': budget,
636
714
  'namzu.runtime.window_source': window.source,
637
715
  'namzu.runtime.usage': Math.round(usage * 100),
638
- 'namzu.runtime.trigger_threshold': config.triggerThreshold,
716
+ 'namzu.runtime.trigger_threshold': trigger,
639
717
  'namzu.runtime.slot_count': manager.slotCount(),
640
718
  })
641
719
 
@@ -668,6 +746,7 @@ async function runCompactionCheckInner(
668
746
  messages: ctx.recorder.messages,
669
747
  config,
670
748
  contextWindowTokens: budget,
749
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
671
750
  estimatedTokens,
672
751
  ...(openTasks ? { openTasks } : {}),
673
752
  })
@@ -676,10 +755,7 @@ async function runCompactionCheckInner(
676
755
  }
677
756
  // Under the trigger, the working set was the whole pass: the summary
678
757
  // path is for the trigger, not for the soft target.
679
- if (
680
- !options?.force &&
681
- (estimatedTokens - working.reclaimedTokens) / budget < config.triggerThreshold
682
- ) {
758
+ if (!options?.force && (estimatedTokens - working.reclaimedTokens) / budget < trigger) {
683
759
  return
684
760
  }
685
761
  }
@@ -687,6 +763,7 @@ async function runCompactionCheckInner(
687
763
  messages: ctx.recorder.messages,
688
764
  config,
689
765
  contextWindowTokens: budget,
766
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
690
767
  estimatedTokens,
691
768
  ...(options?.force ? { force: true } : {}),
692
769
  })
@@ -712,6 +789,7 @@ async function runCompactionCheckInner(
712
789
  messages,
713
790
  config,
714
791
  contextWindowTokens: budget,
792
+ ...(maxOutputTokens !== undefined ? { maxOutputTokens } : {}),
715
793
  estimatedTokens,
716
794
  skipToolResultClear: true,
717
795
  ...(options?.force ? { force: true } : {}),
@@ -754,6 +832,14 @@ async function runCompactionCheckInner(
754
832
  return
755
833
  }
756
834
 
835
+ if (!options?.force && (await stopOnRefill(ctx))) {
836
+ // A clear staged for this pass is still a useful edit.
837
+ if (stagedClear) {
838
+ await commitToolResultClear(ctx, stagedClear, budget, window.source, pass.reason)
839
+ }
840
+ return
841
+ }
842
+
757
843
  const { systemMessages, olderMessages, recentMessages, keepStart } = plan
758
844
 
759
845
  let compactedContent: string
@@ -910,4 +996,5 @@ async function runCompactionCheckInner(
910
996
  reachedResetThreshold: reachedReset,
911
997
  })
912
998
  await emitContextUsageSnapshot(ctx, newEstimate, budget, window.source)
999
+ compactionBreaker(ctx)?.recordCompaction(ctx.recorder.currentIteration)
913
1000
  }
@@ -199,6 +199,8 @@ export interface IterationContext {
199
199
  readonly taskRouter?: TaskRouterConfig
200
200
 
201
201
  readonly compactionConfig?: CompactionConfig
202
+ /** See `QueryParams.maxOutputContinuations`. */
203
+ readonly maxOutputContinuations?: number
202
204
 
203
205
  /**
204
206
  * What the driver said this model's context window is, resolved once.
@@ -225,6 +227,11 @@ export interface IterationContext {
225
227
  * after tool results and before the next turn.
226
228
  */
227
229
  readonly inboundMessages?: () => readonly import('../../../../types/message/index.js').Message[]
230
+ /**
231
+ * Hidden reminders for each model request; see `ReminderSource`. Absent:
232
+ * the turn gets none, as before.
233
+ */
234
+ readonly reminders?: import('../../reminders.js').ReminderSource
228
235
  /** Known topic-queue arrivals appended after the restored checkpoint history. */
229
236
  readonly resumedInput?: readonly import('../../../../types/message/index.js').Message[]
230
237
  /** Observes pending input without draining it; abort releases the waiter. */
@@ -252,6 +259,10 @@ export interface IterationContext {
252
259
 
253
260
  readonly pluginManager?: import('../../../../plugin/lifecycle.js').PluginLifecycleManager
254
261
 
262
+ /** The delegating session, when this turn is a child's: `subagent_stop` names it. */
263
+ readonly parentSessionId?: import('../../../../types/ids/index.js').SessionId
264
+ readonly parentTurnId?: import('../../../../types/ids/index.js').TurnId
265
+
255
266
  /**
256
267
  * Override for {@link PARK_RECORD_DELAY_MS}. Internal; tests set `0` to
257
268
  * observe a recorded park without waiting out the real threshold.
@@ -12,6 +12,7 @@ import {
12
12
  replaceProjectInstructionSnapshot,
13
13
  } from '../../project-instructions.js'
14
14
  import { attachRepeatNotice } from '../../repeat-call.js'
15
+ import { isReviewExempt } from '../../review-policy.js'
15
16
  import { attachNotice, attachSteering, formatJobNote } from '../../steering.js'
16
17
  import { type IterationContext, awaitDecisionDurably } from './context.js'
17
18
 
@@ -25,11 +26,23 @@ function escalationReason(escalation: ToolCallSummary['escalation']): string {
25
26
  return `this call's program cannot be verified ahead of time (${escalation.unknownProgram})`
26
27
  }
27
28
  if (escalation?.sandboxEscape) return "this call asks to run outside the turn's sandbox"
29
+ if (escalation?.dangerousRemoval !== undefined) {
30
+ return `this call removes something catastrophic (${escalation.dangerousRemoval})`
31
+ }
32
+ if (escalation?.protectedPaths?.length) {
33
+ return `this call writes a protected ${escalation.protectedPaths.length === 1 ? 'path' : 'paths'} (${escalation.protectedPaths.join(', ')})`
34
+ }
28
35
  if (escalation?.outsidePaths?.length)
29
36
  return `this call reaches ${escalation.outsidePaths.length === 1 ? 'a path' : 'paths'} outside the turn's boundary`
30
37
  return "this call reaches past the turn's boundary, which a rule cannot approve on its own"
31
38
  }
32
39
 
40
+ function hookAskedReason(reason: string | undefined): string {
41
+ return reason
42
+ ? `a hook asked that a person approve this call: ${reason}`
43
+ : 'a hook asked that a person approve this call'
44
+ }
45
+
33
46
  export type ToolReviewDecision = 'executed' | 'rejected' | 'stop'
34
47
 
35
48
  /**
@@ -124,15 +137,27 @@ export async function* runToolReview(
124
137
  const isDestructive = tool?.isDestructive ? tool.isDestructive(tc.input) : false
125
138
  const requiresApproval = tool?.requiresApproval ? tool.requiresApproval(tc.input) : false
126
139
  const escalation = 'escalation' in tc ? tc.escalation : undefined
140
+ const hookPermission = 'hookPermission' in tc ? tc.hookPermission : undefined
127
141
 
128
142
  return {
129
143
  id: tc.id,
130
144
  name: tc.name,
131
145
  input: tc.input,
132
146
  isDestructive,
133
- authorization: { decision: 'review' },
147
+ // A hook that asked for a person is an explicit review: a call the
148
+ // tool declares read-only, or one a rule would allow, is still put
149
+ // to one.
150
+ authorization:
151
+ hookPermission?.decision === 'ask'
152
+ ? {
153
+ decision: 'review' as const,
154
+ reason: hookAskedReason(hookPermission.reason),
155
+ explicitReview: true as const,
156
+ }
157
+ : { decision: 'review' as const },
134
158
  ...(requiresApproval ? { requiresApproval: true as const } : {}),
135
159
  ...(escalation ? { escalation } : {}),
160
+ ...(hookPermission ? { hookPermission } : {}),
136
161
  }
137
162
  })
138
163
  }
@@ -355,6 +380,24 @@ export async function* runToolReview(
355
380
  reason,
356
381
  })
357
382
  }
383
+ for (const path of tc.escalation?.protectedPaths ?? []) {
384
+ await ctx.recorder.recordAudit({
385
+ what: { action: 'protected_path_write', tool: tc.name, resource: path },
386
+ outcome: 'refused',
387
+ reason,
388
+ })
389
+ }
390
+ if (tc.escalation?.dangerousRemoval !== undefined) {
391
+ await ctx.recorder.recordAudit({
392
+ what: {
393
+ action: 'dangerous_removal',
394
+ tool: tc.name,
395
+ resource: tc.escalation.dangerousRemoval,
396
+ },
397
+ outcome: 'refused',
398
+ reason,
399
+ })
400
+ }
358
401
  }
359
402
 
360
403
  // Gate-denied ids survive the whole function: a later human approval
@@ -419,11 +462,21 @@ export async function* runToolReview(
419
462
  reason: `${gr.gateResult.reason}; but the tool itself declares this call always needs a person's approval, which a rule cannot waive`,
420
463
  }
421
464
  }
465
+ // A hook that asked for a person can only ADD a review, like the two
466
+ // overrides above: a `deny` stays a denial.
467
+ const hookAsked = gr.toolCall.hookPermission?.decision === 'ask'
468
+ if (hookAsked && gr.gateResult.decision === 'allow') {
469
+ gr.gateResult = {
470
+ decision: 'review',
471
+ matchedRule: gr.gateResult.matchedRule,
472
+ reason: `${gr.gateResult.reason}; but ${hookAskedReason(gr.toolCall.hookPermission?.reason)}`,
473
+ }
474
+ }
422
475
  const { toolCall, gateResult } = gr
423
476
  toolCall.authorization = {
424
477
  decision: gateResult.decision,
425
478
  ...(gateResult.reason ? { reason: gateResult.reason } : {}),
426
- ...(gateResult.decision === 'review' && gateResult.matchedRule
479
+ ...(gateResult.decision === 'review' && (gateResult.matchedRule || hookAsked)
427
480
  ? { explicitReview: true as const }
428
481
  : {}),
429
482
  }
@@ -498,6 +551,27 @@ export async function* runToolReview(
498
551
  }
499
552
  }
500
553
 
554
+ // A `pre_tool_use` hook's `allow`, marked on the calls it covers on the
555
+ // same terms as a skill's grant above and honoured by the review policy,
556
+ // never decided here. A deny, an explicit review (a rule's, or a hook's own
557
+ // `ask`), a destructive call, a path outside the roots, a sandbox escape and
558
+ // a tool's own `requiresApproval` all leave the call unmarked, so it is
559
+ // reviewed as though no hook had spoken.
560
+ /** Whether a hook's `allow` may stand in for a person on this call. */
561
+ const hookMayAllow = (tc: ToolCallSummary): boolean =>
562
+ !gateDenied.has(tc.id) &&
563
+ tc.authorization?.decision !== 'deny' &&
564
+ !tc.authorization?.explicitReview &&
565
+ !tc.isDestructive &&
566
+ tc.escalation === undefined &&
567
+ !tc.requiresApproval
568
+ for (const tc of toolCallSummaries) {
569
+ if (tc.hookPermission?.decision !== 'allow' || !hookMayAllow(tc)) continue
570
+ tc.hookGrant = {
571
+ ...(tc.hookPermission.reason ? { reason: tc.hookPermission.reason } : {}),
572
+ }
573
+ }
574
+
501
575
  // Already approved, at a scope the approver chose — and nothing the
502
576
  // operator's policy denied, because `gateDenied` is checked first.
503
577
  // Re-asking about a call somebody has already said yes to is how an
@@ -525,6 +599,76 @@ export async function* runToolReview(
525
599
  return finish('executed')
526
600
  }
527
601
 
602
+ // A person is about to be asked. `permission_request` hooks may answer
603
+ // first, per call: `skip` refuses it (and always wins), `allow` approves it
604
+ // on the same terms as a `pre_tool_use` allow. A read the review policy
605
+ // would not have asked about is not a question and is not offered.
606
+ if (ctx.pluginManager) {
607
+ const questions = toolCallSummaries.filter(
608
+ (tc) =>
609
+ !gateDenied.has(tc.id) &&
610
+ tc.authorization?.decision !== 'deny' &&
611
+ tc.authorization?.decision !== 'allow' &&
612
+ (tc.authorization?.explicitReview === true ||
613
+ tc.escalation !== undefined ||
614
+ tc.requiresApproval === true ||
615
+ tc.isDestructive ||
616
+ !isReviewExempt(ctx.tools, tc.name, tc.input)),
617
+ )
618
+ for (const tc of questions) {
619
+ const answers = await ctx.pluginManager.executeHooks(
620
+ 'permission_request',
621
+ {
622
+ sessionId: ctx.recorder.sessionId,
623
+ turnId: ctx.recorder.turnId,
624
+ toolName: tc.name,
625
+ toolInput: tc.input,
626
+ signal: ctx.abortController.signal,
627
+ },
628
+ ctx.emitEvent,
629
+ )
630
+ // The hooks answered side by side: a refusal from any of them stands,
631
+ // whatever the others allowed.
632
+ const refusal = answers.find((answer) => answer.action === 'skip')
633
+ for (const answer of answers) {
634
+ if ((answer.action === 'continue' || answer.action === 'skip') && answer.halt) {
635
+ ctx.toolExecutor.requestHalt?.(answer.halt.reason)
636
+ }
637
+ }
638
+ if (refusal?.action === 'skip') {
639
+ const reason = `Blocked by a permission_request hook: ${refusal.reason}`
640
+ gateDenied.set(tc.id, reason)
641
+ tc.authorization = { decision: 'deny', reason }
642
+ await ctx.recorder.recordAudit({
643
+ what: { action: 'tool_call', tool: tc.name },
644
+ outcome: 'refused',
645
+ reason,
646
+ })
647
+ continue
648
+ }
649
+ for (const answer of answers) {
650
+ if (
651
+ answer.action === 'continue' &&
652
+ answer.permission?.decision === 'allow' &&
653
+ hookMayAllow(tc)
654
+ ) {
655
+ tc.hookGrant = {
656
+ ...(answer.permission.reason ? { reason: answer.permission.reason } : {}),
657
+ }
658
+ }
659
+ }
660
+ }
661
+ if (toolCallSummaries.every((tc) => gateDenied.has(tc.id))) {
662
+ for (const tc of escalated()) {
663
+ const reason = gateDenied.get(tc.id)
664
+ if (reason !== undefined) await recordRefusedEscalation(tc, reason)
665
+ }
666
+ yield* settle(gateDenied)
667
+ yield* ctx.drainPending()
668
+ return finish('rejected')
669
+ }
670
+ }
671
+
528
672
  const reviewCheckpoint = await ctx.checkpointMgr.create(ctx.recorder, iterationNum)
529
673
 
530
674
  await ctx.emitEvent({
@@ -607,6 +751,24 @@ export async function* runToolReview(
607
751
  reason: "the turn's review approved this call",
608
752
  })
609
753
  }
754
+ for (const path of tc.escalation?.protectedPaths ?? []) {
755
+ await ctx.recorder.recordAudit({
756
+ what: { action: 'protected_path_write', tool: tc.name, resource: path },
757
+ outcome: 'approved',
758
+ reason: "the turn's review approved this call",
759
+ })
760
+ }
761
+ if (tc.escalation?.dangerousRemoval !== undefined) {
762
+ await ctx.recorder.recordAudit({
763
+ what: {
764
+ action: 'dangerous_removal',
765
+ tool: tc.name,
766
+ resource: tc.escalation.dangerousRemoval,
767
+ },
768
+ outcome: 'approved',
769
+ reason: "the turn's review approved this call",
770
+ })
771
+ }
610
772
  }
611
773
  }
612
774
 
@@ -762,6 +924,19 @@ export async function* runToolReview(
762
924
  }
763
925
  }
764
926
 
927
+ // The same for a call a hook's `allow` approved.
928
+ if (reviewDecision.action === 'approve_tools' && reviewDecision.hookGranted) {
929
+ const listed = new Set(reviewDecision.hookGranted)
930
+ for (const tc of toolCallSummaries) {
931
+ if (!listed.has(tc.id) || !tc.hookGrant || gateDenied.has(tc.id)) continue
932
+ await ctx.recorder.recordAudit({
933
+ what: { action: 'tool_call', tool: tc.name },
934
+ outcome: 'approved',
935
+ reason: `pre-approved by a pre_tool_use hook${tc.hookGrant.reason ? ` (${tc.hookGrant.reason})` : ''}; nobody was asked`,
936
+ })
937
+ }
938
+ }
939
+
765
940
  await ctx.emitEvent({
766
941
  type: 'tool_review_completed',
767
942
  turnId: ctx.recorder.turnId,