@namzu/sdk 62.6.0 → 62.7.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 (295) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +5 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/agents/examples/SupervisorAgent.d.ts.map +1 -1
  6. package/dist/agents/examples/SupervisorAgent.js +1 -0
  7. package/dist/agents/examples/SupervisorAgent.js.map +1 -1
  8. package/dist/agents/explore.d.ts.map +1 -1
  9. package/dist/agents/explore.js +2 -1
  10. package/dist/agents/explore.js.map +1 -1
  11. package/dist/compaction/index.d.ts +1 -1
  12. package/dist/compaction/index.d.ts.map +1 -1
  13. package/dist/compaction/index.js +1 -1
  14. package/dist/compaction/index.js.map +1 -1
  15. package/dist/compaction/model-summary.js +2 -2
  16. package/dist/compaction/model-summary.js.map +1 -1
  17. package/dist/compaction/summary.d.ts +16 -1
  18. package/dist/compaction/summary.d.ts.map +1 -1
  19. package/dist/compaction/summary.js +25 -2
  20. package/dist/compaction/summary.js.map +1 -1
  21. package/dist/config/runtime.d.ts +8 -0
  22. package/dist/config/runtime.d.ts.map +1 -1
  23. package/dist/config/runtime.js +15 -0
  24. package/dist/config/runtime.js.map +1 -1
  25. package/dist/execution/workflow/runtime.d.ts.map +1 -1
  26. package/dist/execution/workflow/runtime.js +3 -0
  27. package/dist/execution/workflow/runtime.js.map +1 -1
  28. package/dist/execution/workflow/types.d.ts +8 -0
  29. package/dist/execution/workflow/types.d.ts.map +1 -1
  30. package/dist/manager/agent/child-session.d.ts +12 -0
  31. package/dist/manager/agent/child-session.d.ts.map +1 -1
  32. package/dist/manager/agent/child-session.js +29 -0
  33. package/dist/manager/agent/child-session.js.map +1 -1
  34. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  35. package/dist/manager/agent/lifecycle.js +8 -0
  36. package/dist/manager/agent/lifecycle.js.map +1 -1
  37. package/dist/manager/session/turn-recorder.d.ts +13 -2
  38. package/dist/manager/session/turn-recorder.d.ts.map +1 -1
  39. package/dist/manager/session/turn-recorder.js +57 -3
  40. package/dist/manager/session/turn-recorder.js.map +1 -1
  41. package/dist/prompt/coding-agent-doctrine.d.ts +3 -3
  42. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  43. package/dist/prompt/coding-agent-doctrine.js +5 -3
  44. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  45. package/dist/provider/fallback.d.ts.map +1 -1
  46. package/dist/provider/fallback.js +5 -1
  47. package/dist/provider/fallback.js.map +1 -1
  48. package/dist/provider/retry.d.ts +25 -0
  49. package/dist/provider/retry.d.ts.map +1 -1
  50. package/dist/provider/retry.js +28 -0
  51. package/dist/provider/retry.js.map +1 -1
  52. package/dist/public-runtime.d.ts +3 -3
  53. package/dist/public-runtime.d.ts.map +1 -1
  54. package/dist/public-runtime.js +3 -3
  55. package/dist/public-runtime.js.map +1 -1
  56. package/dist/public-tools.d.ts +4 -2
  57. package/dist/public-tools.d.ts.map +1 -1
  58. package/dist/public-tools.js +5 -2
  59. package/dist/public-tools.js.map +1 -1
  60. package/dist/runtime/query/auto-review/classifier.d.ts +7 -1
  61. package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -1
  62. package/dist/runtime/query/auto-review/classifier.js +8 -4
  63. package/dist/runtime/query/auto-review/classifier.js.map +1 -1
  64. package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -1
  65. package/dist/runtime/query/auto-review/prompt.js +2 -0
  66. package/dist/runtime/query/auto-review/prompt.js.map +1 -1
  67. package/dist/runtime/query/auto-review/script-evidence.d.ts +93 -0
  68. package/dist/runtime/query/auto-review/script-evidence.d.ts.map +1 -0
  69. package/dist/runtime/query/auto-review/script-evidence.js +566 -0
  70. package/dist/runtime/query/auto-review/script-evidence.js.map +1 -0
  71. package/dist/runtime/query/checkpoint.d.ts +13 -0
  72. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  73. package/dist/runtime/query/checkpoint.js +37 -0
  74. package/dist/runtime/query/checkpoint.js.map +1 -1
  75. package/dist/runtime/query/executor.d.ts +38 -1
  76. package/dist/runtime/query/executor.d.ts.map +1 -1
  77. package/dist/runtime/query/executor.js +130 -11
  78. package/dist/runtime/query/executor.js.map +1 -1
  79. package/dist/runtime/query/finalize-turn.d.ts +7 -1
  80. package/dist/runtime/query/finalize-turn.d.ts.map +1 -1
  81. package/dist/runtime/query/finalize-turn.js +2 -0
  82. package/dist/runtime/query/finalize-turn.js.map +1 -1
  83. package/dist/runtime/query/index.d.ts +8 -0
  84. package/dist/runtime/query/index.d.ts.map +1 -1
  85. package/dist/runtime/query/index.js +7 -0
  86. package/dist/runtime/query/index.js.map +1 -1
  87. package/dist/runtime/query/iteration/index.d.ts +32 -1
  88. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  89. package/dist/runtime/query/iteration/index.js +158 -10
  90. package/dist/runtime/query/iteration/index.js.map +1 -1
  91. package/dist/runtime/query/iteration/phases/compaction.d.ts +12 -0
  92. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  93. package/dist/runtime/query/iteration/phases/compaction.js +48 -1
  94. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  95. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  96. package/dist/runtime/query/iteration/phases/context.js +5 -0
  97. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  98. package/dist/runtime/query/iteration/stream-turn.js +3 -3
  99. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  100. package/dist/runtime/query/prelude-lease.d.ts.map +1 -1
  101. package/dist/runtime/query/prelude-lease.js +22 -0
  102. package/dist/runtime/query/prelude-lease.js.map +1 -1
  103. package/dist/runtime/query/prepare-turn.d.ts.map +1 -1
  104. package/dist/runtime/query/prepare-turn.js +13 -4
  105. package/dist/runtime/query/prepare-turn.js.map +1 -1
  106. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  107. package/dist/runtime/query/review-policy.js +29 -0
  108. package/dist/runtime/query/review-policy.js.map +1 -1
  109. package/dist/runtime/query/tooling.d.ts +2 -0
  110. package/dist/runtime/query/tooling.d.ts.map +1 -1
  111. package/dist/runtime/query/tooling.js +1 -0
  112. package/dist/runtime/query/tooling.js.map +1 -1
  113. package/dist/runtime/query/turn/completion-audit.d.ts +27 -1
  114. package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -1
  115. package/dist/runtime/query/turn/completion-audit.js +71 -6
  116. package/dist/runtime/query/turn/completion-audit.js.map +1 -1
  117. package/dist/runtime/query/turn/outcome-ledger.d.ts +113 -0
  118. package/dist/runtime/query/turn/outcome-ledger.d.ts.map +1 -0
  119. package/dist/runtime/query/turn/outcome-ledger.js +578 -0
  120. package/dist/runtime/query/turn/outcome-ledger.js.map +1 -0
  121. package/dist/sandbox/file-walk.d.ts.map +1 -1
  122. package/dist/sandbox/file-walk.js +34 -17
  123. package/dist/sandbox/file-walk.js.map +1 -1
  124. package/dist/sandbox/glob-segment.d.ts +35 -0
  125. package/dist/sandbox/glob-segment.d.ts.map +1 -0
  126. package/dist/sandbox/glob-segment.js +300 -0
  127. package/dist/sandbox/glob-segment.js.map +1 -0
  128. package/dist/store/session-log/index.d.ts +1 -0
  129. package/dist/store/session-log/index.d.ts.map +1 -1
  130. package/dist/store/session-log/index.js +1 -0
  131. package/dist/store/session-log/index.js.map +1 -1
  132. package/dist/store/session-log/turn-outcome.d.ts +37 -0
  133. package/dist/store/session-log/turn-outcome.d.ts.map +1 -0
  134. package/dist/store/session-log/turn-outcome.js +77 -0
  135. package/dist/store/session-log/turn-outcome.js.map +1 -0
  136. package/dist/tools/builtins/bash.js +1 -1
  137. package/dist/tools/builtins/bash.js.map +1 -1
  138. package/dist/tools/builtins/edit-apply.d.ts.map +1 -1
  139. package/dist/tools/builtins/edit-apply.js +24 -3
  140. package/dist/tools/builtins/edit-apply.js.map +1 -1
  141. package/dist/tools/builtins/edit.d.ts +31 -17
  142. package/dist/tools/builtins/edit.d.ts.map +1 -1
  143. package/dist/tools/builtins/edit.js +92 -27
  144. package/dist/tools/builtins/edit.js.map +1 -1
  145. package/dist/tools/builtins/glob.js +1 -1
  146. package/dist/tools/builtins/glob.js.map +1 -1
  147. package/dist/tools/builtins/grep.js +1 -1
  148. package/dist/tools/builtins/grep.js.map +1 -1
  149. package/dist/tools/builtins/notebook-guard.d.ts +19 -0
  150. package/dist/tools/builtins/notebook-guard.d.ts.map +1 -0
  151. package/dist/tools/builtins/notebook-guard.js +75 -0
  152. package/dist/tools/builtins/notebook-guard.js.map +1 -0
  153. package/dist/tools/builtins/read-render.d.ts.map +1 -1
  154. package/dist/tools/builtins/read-render.js +8 -2
  155. package/dist/tools/builtins/read-render.js.map +1 -1
  156. package/dist/tools/builtins/write-file.d.ts.map +1 -1
  157. package/dist/tools/builtins/write-file.js +14 -1
  158. package/dist/tools/builtins/write-file.js.map +1 -1
  159. package/dist/tools/coordinator/agent.d.ts.map +1 -1
  160. package/dist/tools/coordinator/agent.js +25 -6
  161. package/dist/tools/coordinator/agent.js.map +1 -1
  162. package/dist/tools/coordinator/index.d.ts.map +1 -1
  163. package/dist/tools/coordinator/index.js +6 -1
  164. package/dist/tools/coordinator/index.js.map +1 -1
  165. package/dist/tools/coordinator/outcome-record.d.ts +22 -0
  166. package/dist/tools/coordinator/outcome-record.d.ts.map +1 -0
  167. package/dist/tools/coordinator/outcome-record.js +91 -0
  168. package/dist/tools/coordinator/outcome-record.js.map +1 -0
  169. package/dist/tools/defineTool.d.ts +2 -0
  170. package/dist/tools/defineTool.d.ts.map +1 -1
  171. package/dist/tools/defineTool.js +1 -0
  172. package/dist/tools/defineTool.js.map +1 -1
  173. package/dist/tools/file-read-tracker.d.ts +23 -2
  174. package/dist/tools/file-read-tracker.d.ts.map +1 -1
  175. package/dist/tools/file-read-tracker.js +37 -2
  176. package/dist/tools/file-read-tracker.js.map +1 -1
  177. package/dist/tools/untrusted-envelope.d.ts +6 -0
  178. package/dist/tools/untrusted-envelope.d.ts.map +1 -1
  179. package/dist/tools/untrusted-envelope.js +22 -1
  180. package/dist/tools/untrusted-envelope.js.map +1 -1
  181. package/dist/toolsets/manager.d.ts.map +1 -1
  182. package/dist/toolsets/manager.js +3 -4
  183. package/dist/toolsets/manager.js.map +1 -1
  184. package/dist/toolsets/validation-issues.d.ts +27 -0
  185. package/dist/toolsets/validation-issues.d.ts.map +1 -0
  186. package/dist/toolsets/validation-issues.js +83 -0
  187. package/dist/toolsets/validation-issues.js.map +1 -0
  188. package/dist/turn/memory-recall.d.ts +13 -0
  189. package/dist/turn/memory-recall.d.ts.map +1 -1
  190. package/dist/turn/memory-recall.js +18 -0
  191. package/dist/turn/memory-recall.js.map +1 -1
  192. package/dist/types/agent/base.d.ts +19 -0
  193. package/dist/types/agent/base.d.ts.map +1 -1
  194. package/dist/types/agent/query.d.ts +2 -0
  195. package/dist/types/agent/query.d.ts.map +1 -1
  196. package/dist/types/common/index.d.ts +17 -0
  197. package/dist/types/common/index.d.ts.map +1 -1
  198. package/dist/types/common/index.js +8 -1
  199. package/dist/types/common/index.js.map +1 -1
  200. package/dist/types/hitl/index.d.ts +22 -0
  201. package/dist/types/hitl/index.d.ts.map +1 -1
  202. package/dist/types/hitl/index.js.map +1 -1
  203. package/dist/types/session/config.d.ts +20 -0
  204. package/dist/types/session/config.d.ts.map +1 -1
  205. package/dist/types/session/events.d.ts +3 -3
  206. package/dist/types/session/events.d.ts.map +1 -1
  207. package/dist/types/session/index.d.ts +1 -0
  208. package/dist/types/session/index.d.ts.map +1 -1
  209. package/dist/types/session/index.js +1 -0
  210. package/dist/types/session/index.js.map +1 -1
  211. package/dist/types/session/outcome.d.ts +95 -0
  212. package/dist/types/session/outcome.d.ts.map +1 -0
  213. package/dist/types/session/outcome.js +45 -0
  214. package/dist/types/session/outcome.js.map +1 -0
  215. package/dist/types/session/prepare-step.d.ts +2 -0
  216. package/dist/types/session/prepare-step.d.ts.map +1 -1
  217. package/dist/types/session/records.d.ts +167 -2
  218. package/dist/types/session/records.d.ts.map +1 -1
  219. package/dist/types/session/records.js +42 -0
  220. package/dist/types/session/records.js.map +1 -1
  221. package/dist/types/session/turn.d.ts +18 -0
  222. package/dist/types/session/turn.d.ts.map +1 -1
  223. package/dist/types/session/turn.js.map +1 -1
  224. package/dist/types/tool/index.d.ts +30 -0
  225. package/dist/types/tool/index.d.ts.map +1 -1
  226. package/dist/types/tool/index.js.map +1 -1
  227. package/package.json +2 -2
  228. package/src/agents/QueryAgent.ts +5 -0
  229. package/src/agents/examples/SupervisorAgent.ts +1 -0
  230. package/src/agents/explore.ts +2 -1
  231. package/src/compaction/index.ts +1 -0
  232. package/src/compaction/model-summary.ts +2 -2
  233. package/src/compaction/summary.ts +30 -2
  234. package/src/config/runtime.ts +15 -0
  235. package/src/execution/workflow/runtime.ts +4 -0
  236. package/src/execution/workflow/types.ts +9 -0
  237. package/src/manager/agent/child-session.ts +36 -0
  238. package/src/manager/agent/lifecycle.ts +9 -0
  239. package/src/manager/session/turn-recorder.ts +62 -3
  240. package/src/pricing/rates.source.json +1 -1
  241. package/src/prompt/coding-agent-doctrine.ts +5 -3
  242. package/src/provider/fallback.ts +6 -1
  243. package/src/provider/retry.ts +51 -0
  244. package/src/public-runtime.ts +10 -1
  245. package/src/public-tools.ts +6 -1
  246. package/src/runtime/query/auto-review/classifier.ts +21 -5
  247. package/src/runtime/query/auto-review/prompt.ts +2 -0
  248. package/src/runtime/query/auto-review/script-evidence.ts +618 -0
  249. package/src/runtime/query/checkpoint.ts +53 -1
  250. package/src/runtime/query/executor.ts +159 -19
  251. package/src/runtime/query/finalize-turn.ts +8 -1
  252. package/src/runtime/query/index.ts +19 -0
  253. package/src/runtime/query/iteration/index.ts +167 -14
  254. package/src/runtime/query/iteration/phases/compaction.ts +53 -3
  255. package/src/runtime/query/iteration/phases/context.ts +6 -0
  256. package/src/runtime/query/iteration/stream-turn.ts +3 -3
  257. package/src/runtime/query/prelude-lease.ts +19 -0
  258. package/src/runtime/query/prepare-turn.ts +13 -4
  259. package/src/runtime/query/review-policy.ts +33 -0
  260. package/src/runtime/query/tooling.ts +3 -0
  261. package/src/runtime/query/turn/completion-audit.ts +76 -5
  262. package/src/runtime/query/turn/outcome-ledger.ts +633 -0
  263. package/src/sandbox/file-walk.ts +38 -17
  264. package/src/sandbox/glob-segment.ts +309 -0
  265. package/src/store/session-log/index.ts +7 -0
  266. package/src/store/session-log/turn-outcome.ts +84 -0
  267. package/src/tools/builtins/bash.ts +1 -1
  268. package/src/tools/builtins/edit-apply.ts +26 -4
  269. package/src/tools/builtins/edit.ts +106 -41
  270. package/src/tools/builtins/glob.ts +1 -1
  271. package/src/tools/builtins/grep.ts +1 -1
  272. package/src/tools/builtins/notebook-guard.ts +76 -0
  273. package/src/tools/builtins/read-render.ts +12 -2
  274. package/src/tools/builtins/write-file.ts +12 -1
  275. package/src/tools/coordinator/agent.ts +31 -7
  276. package/src/tools/coordinator/index.ts +6 -1
  277. package/src/tools/coordinator/outcome-record.ts +93 -0
  278. package/src/tools/defineTool.ts +3 -0
  279. package/src/tools/file-read-tracker.ts +51 -2
  280. package/src/tools/untrusted-envelope.ts +23 -1
  281. package/src/toolsets/manager.ts +6 -10
  282. package/src/toolsets/validation-issues.ts +97 -0
  283. package/src/turn/memory-recall.ts +28 -0
  284. package/src/types/agent/base.ts +20 -0
  285. package/src/types/agent/query.ts +2 -0
  286. package/src/types/common/index.ts +25 -1
  287. package/src/types/hitl/index.ts +27 -0
  288. package/src/types/session/config.ts +20 -0
  289. package/src/types/session/events.ts +3 -3
  290. package/src/types/session/index.ts +1 -0
  291. package/src/types/session/outcome.ts +139 -0
  292. package/src/types/session/prepare-step.ts +2 -0
  293. package/src/types/session/records.ts +67 -1
  294. package/src/types/session/turn.ts +19 -0
  295. package/src/types/tool/index.ts +30 -0
@@ -31,11 +31,12 @@ import { getTracer } from '../../../telemetry/runtime-accessors.js'
31
31
  import { STRUCTURED_OUTPUT_TOOL_NAME } from '../../../tools/builtins/structuredOutput.js'
32
32
  import type { ToolManager } from '../../../toolsets/manager.js'
33
33
  import { RECALLED_MEMORY_ENTRY_KEY, recalledMemoryIds } from '../../../turn/memory-recall.js'
34
- import type { CostInfo, TokenUsage } from '../../../types/common/index.js'
34
+ import { type CostInfo, type TokenUsage, promptSizeOf } from '../../../types/common/index.js'
35
35
  import { NamzuError } from '../../../types/errors/index.js'
36
36
  import type { MessageId, SessionId, TurnId } from '../../../types/ids/index.js'
37
37
  import {
38
38
  type Message,
39
+ type ToolCall,
39
40
  type UserMessage,
40
41
  createAssistantMessage,
41
42
  createRuntimeContextMessage,
@@ -54,6 +55,7 @@ import type {
54
55
  StepProvenance,
55
56
  StepResult,
56
57
  StopReason,
58
+ TurnOutcome,
57
59
  } from '../../../types/session/index.js'
58
60
  import type { LLMToolSchema } from '../../../types/tool/index.js'
59
61
  import { toErrorMessage } from '../../../utils/error.js'
@@ -79,7 +81,8 @@ import {
79
81
  } from '../request-rich-content.js'
80
82
  import { withoutVoidAssistantTurns } from '../request-void-turns.js'
81
83
  import { formatSteeringNote, isOperatorUserMessage } from '../steering.js'
82
- import { COMPLETION_AUDIT_MESSAGE, CompletionAudit } from '../turn/completion-audit.js'
84
+ import { CompletionAudit } from '../turn/completion-audit.js'
85
+ import { type OutcomeCall, OutcomeLedger, type PendingOutcomeCall } from '../turn/outcome-ledger.js'
83
86
  import {
84
87
  FORCED_STEP_MESSAGE,
85
88
  REASONING_CAPPED_MESSAGE,
@@ -107,6 +110,7 @@ import {
107
110
  measureContext,
108
111
  relieveOverflow,
109
112
  runCompactionCheck,
113
+ runStaleResultClear,
110
114
  stopOnRefill,
111
115
  } from './phases/compaction.js'
112
116
  import { runHandoffPause } from './phases/handoff.js'
@@ -333,6 +337,9 @@ export class IterationOrchestrator {
333
337
  private readonly stallMonitor?: StallMonitor
334
338
  /** Holds the turn back once when it changed files and checked nothing afterwards. */
335
339
  private readonly completionAudit?: CompletionAudit
340
+ /** What the turn changed and what checked it; see {@link OutcomeLedger}. Kept whether or not the host asked. */
341
+ private readonly outcomeLedger: OutcomeLedger
342
+ private outcomeHydrated = false
336
343
  /** The next step must call a tool, at low effort: set by the stall monitor or a capped step. */
337
344
  private forceActionNextStep = false
338
345
  /** Steps in a row cut at the per-step reasoning cap; past a few the cap stops applying. */
@@ -359,6 +366,10 @@ export class IterationOrchestrator {
359
366
  private turnEndFired = false
360
367
  /** A hook already kept this turn going once: it is asked again with `stop_hook_active`. */
361
368
  private stopHookActive = false
369
+ /** End-of-turn refusals honoured since the last tool call. */
370
+ private consecutiveHookRefusals = 0
371
+ /** The cap was reached and its notice recorded; refusals are ignored for the rest of the turn. */
372
+ private hookRefusalCapReached = false
362
373
  get turnEndHooksFired(): boolean {
363
374
  return this.turnEndFired
364
375
  }
@@ -371,12 +382,22 @@ export class IterationOrchestrator {
371
382
  * are returned, joined, as feedback for the model. A hook that asks for the
372
383
  * turn to halt wins over any such feedback (`takeHalt`). With nothing asked,
373
384
  * the events have fired for this turn and the finalizer does not repeat them.
385
+ *
386
+ * Refusals are counted in a row (a tool call in between starts the count
387
+ * over). Once `maxHookRefusals` have been honoured the next one is not: the
388
+ * turn ends, with one notice that names the hook.
374
389
  */
375
390
  private async *askStopHooks(): AsyncGenerator<SessionEvent, string | undefined> {
376
391
  const manager = this.ctx.pluginManager
377
392
  if (!manager) return undefined
378
393
  const recorder = this.ctx.recorder
379
394
  const feedback: string[] = []
395
+ const refusers: string[] = []
396
+ const emit = async (event: SessionEvent): Promise<void> => {
397
+ if (event.type === 'plugin_hook_completed' && event.result.action === 'skip')
398
+ refusers.push(String(event.pluginId))
399
+ await this.ctx.emitEvent(event)
400
+ }
380
401
  const ask = async (
381
402
  event: 'turn_end' | 'subagent_stop',
382
403
  extra: {
@@ -394,7 +415,7 @@ export class IterationOrchestrator {
394
415
  ...(this.stopHookActive ? { stopHookActive: true } : {}),
395
416
  signal: this.ctx.abortController.signal,
396
417
  },
397
- this.ctx.emitEvent,
418
+ emit,
398
419
  )
399
420
  applyLifecycleHookResults(event, results)
400
421
  for (const result of results) {
@@ -414,10 +435,35 @@ export class IterationOrchestrator {
414
435
  })
415
436
  yield* this.ctx.drainPending()
416
437
  }
417
- if (feedback.length === 0) {
438
+ // Once the cap has been reached, refusals stay ignored for the rest of the turn,
439
+ // whatever a tool call does to the count; a halt was already passed on above.
440
+ if (feedback.length === 0 || this.hookRefusalCapReached) {
441
+ this.turnEndFired = true
442
+ return undefined
443
+ }
444
+ const limit = this.ctx.turnConfig.maxHookRefusals ?? 0
445
+ if (limit > 0 && this.consecutiveHookRefusals >= limit) {
446
+ // The turn has been kept going `limit` times in a row. A hook that
447
+ // still refuses is not going to stop; say so once, and let the turn end.
418
448
  this.turnEndFired = true
449
+ if (!this.hookRefusalCapReached) {
450
+ this.hookRefusalCapReached = true
451
+ const names = [...new Set(refusers)]
452
+ const who = names.length > 0 ? names.map((n) => `"${n}"`).join(', ') : 'A hook'
453
+ recorder.pushMessage(
454
+ createRuntimeContextMessage(
455
+ `${who} asked for more before this turn could end ${limit} times in a row. Namzu is ending the turn anyway and will not honour further requests from it this turn.`,
456
+ 'hook-feedback',
457
+ ),
458
+ )
459
+ this.ctx.log.warn('Hook refusal limit reached — ending the turn', {
460
+ [NAMZU.TURN_ID]: recorder.turnId,
461
+ 'namzu.runtime.limit': limit,
462
+ })
463
+ }
419
464
  return undefined
420
465
  }
466
+ this.consecutiveHookRefusals++
421
467
  this.stopHookActive = true
422
468
  return feedback.join('\n')
423
469
  }
@@ -436,6 +482,13 @@ export class IterationOrchestrator {
436
482
  },
437
483
  }
438
484
  if (ctx.turnConfig.completionAudit === true) this.completionAudit = new CompletionAudit()
485
+ this.outcomeLedger = new OutcomeLedger(ctx.turnConfig.outcome)
486
+ ctx.checkpointMgr.setOutcomeSource?.(() => {
487
+ this.hydrateOutcome()
488
+ return this.outcomeLedger.recorded
489
+ ? (this.outcomeLedger.snapshot() as unknown as Record<string, unknown>)
490
+ : undefined
491
+ })
439
492
  if (ctx.turnConfig.reasoning?.stall !== false) {
440
493
  this.stallMonitor = new StallMonitor(ctx.turnConfig.reasoning?.stall || undefined)
441
494
  }
@@ -817,6 +870,7 @@ export class IterationOrchestrator {
817
870
  // edge BEFORE compaction runs (so the refreshed slot is what
818
871
  // compaction preserves). No-op when no provider is configured.
819
872
  await refreshWorkingMemory(this.ctx)
873
+ if (iterationNum === 1) await runStaleResultClear(this.ctx)
820
874
  await runCompactionCheck(this.ctx)
821
875
  yield* this.ctx.drainPending()
822
876
  if (this.stopForCompactionBreaker()) break
@@ -1340,7 +1394,7 @@ export class IterationOrchestrator {
1340
1394
  'namzu.runtime.has_content':
1341
1395
  response.message.content !== null && response.message.content.length > 0,
1342
1396
  'namzu.runtime.tool_call_count': response.message.toolCalls?.length ?? 0,
1343
- [GENAI.USAGE_INPUT_TOKENS]: response.usage.promptTokens,
1397
+ [GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(response.usage),
1344
1398
  [GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
1345
1399
  'namzu.usage.total_tokens': recorder.tokenUsage.totalTokens,
1346
1400
  'namzu.runtime.total_cost': recorder.costInfo.totalCost,
@@ -1457,7 +1511,7 @@ export class IterationOrchestrator {
1457
1511
  yield* this.ctx.drainPending()
1458
1512
 
1459
1513
  iterSpan.setAttributes({
1460
- [GENAI.USAGE_INPUT_TOKENS]: response.usage.promptTokens,
1514
+ [GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(response.usage),
1461
1515
  [GENAI.USAGE_OUTPUT_TOKENS]: response.usage.completionTokens,
1462
1516
  })
1463
1517
  iterSpan.setStatus({ code: SpanStatusCode.OK })
@@ -1951,17 +2005,15 @@ export class IterationOrchestrator {
1951
2005
  // back to the model and the turn goes on.
1952
2006
  // Files were changed and nothing checked them afterwards: ask once
1953
2007
  // for a check before the turn is allowed to end.
1954
- if (
1955
- !forceFinalize &&
1956
- closingStopReason === undefined &&
1957
- this.completionAudit?.takeBlock()
1958
- ) {
2008
+ const auditReminder =
2009
+ !forceFinalize && closingStopReason === undefined
2010
+ ? this.completionAudit?.takeReminder(() => this.outcomeLedger.peek())
2011
+ : undefined
2012
+ if (auditReminder !== undefined) {
1959
2013
  this.ctx.log.info('Files changed and nothing checked them — asking for a check', {
1960
2014
  [NAMZU.TURN_ID]: recorder.turnId,
1961
2015
  })
1962
- recorder.pushMessage(
1963
- createRuntimeContextMessage(COMPLETION_AUDIT_MESSAGE, 'progress-nudge'),
1964
- )
2016
+ recorder.pushMessage(createRuntimeContextMessage(auditReminder, 'progress-nudge'))
1965
2017
  continue
1966
2018
  }
1967
2019
  if (!forceFinalize && closingStopReason === undefined) {
@@ -1989,6 +2041,10 @@ export class IterationOrchestrator {
1989
2041
  break
1990
2042
  }
1991
2043
 
2044
+ // A real tool call: whatever a hook refused before it is behind us.
2045
+ this.consecutiveHookRefusals = 0
2046
+
2047
+ await this.outcomeBefore(response)
1992
2048
  const reviewOutcome = yield* runToolReview(this.ctx, response, iterationNum)
1993
2049
 
1994
2050
  // The step record is built even for a rejected batch: a turn that
@@ -2013,6 +2069,7 @@ export class IterationOrchestrator {
2013
2069
  this.reasoningOnlyRetried = false
2014
2070
  this.observeStall(response, reviewOutcome.results)
2015
2071
  this.observeAudit(response, reviewOutcome.results)
2072
+ await this.outcomeObserve(iterationNum, response, reviewOutcome.results)
2016
2073
 
2017
2074
  if (reviewOutcome.decision === 'stop') {
2018
2075
  return
@@ -3334,6 +3391,102 @@ export class IterationOrchestrator {
3334
3391
  }
3335
3392
  }
3336
3393
 
3394
+ /**
3395
+ * Take back the outcome record a checkpoint kept. Done at first use rather
3396
+ * than at construction: the checkpoint is read after this is built. A turn
3397
+ * resumed from a checkpoint with no record cannot say what came before it,
3398
+ * so it keeps none.
3399
+ */
3400
+ private hydrateOutcome(): void {
3401
+ if (this.outcomeHydrated) return
3402
+ this.outcomeHydrated = true
3403
+ const restored = this.ctx.checkpointMgr.restoredOutcome
3404
+ if (restored?.resumed !== true) return
3405
+ if (restored.snapshot === undefined) this.outcomeLedger.markUnrecorded()
3406
+ else this.outcomeLedger.restore(restored.snapshot)
3407
+ }
3408
+
3409
+ /** Whether a call is one the outcome record may treat as only looking. */
3410
+ private lookedOnly(toolName: string, input: unknown): boolean {
3411
+ try {
3412
+ return !callChangedState(toolName, this.ctx.tools.get(toolName)?.isReadOnly?.(input), false)
3413
+ } catch {
3414
+ return false
3415
+ }
3416
+ }
3417
+
3418
+ private parsedInput(call: ToolCall | undefined): unknown {
3419
+ try {
3420
+ return call ? JSON.parse(call.function.arguments) : undefined
3421
+ } catch {
3422
+ return undefined
3423
+ }
3424
+ }
3425
+
3426
+ /**
3427
+ * Before a step's calls run: the first one that could change the workspace
3428
+ * takes the starting fingerprint. A failure here must not fail the turn.
3429
+ */
3430
+ async outcomeBefore(response: ChatCompletionResponse): Promise<void> {
3431
+ try {
3432
+ this.hydrateOutcome()
3433
+ const pending: PendingOutcomeCall[] = (response.message.toolCalls ?? []).map((call) => {
3434
+ const input = this.parsedInput(call)
3435
+ return {
3436
+ toolName: call.function.name,
3437
+ input,
3438
+ readOnly: this.lookedOnly(call.function.name, input),
3439
+ }
3440
+ })
3441
+ await this.outcomeLedger.before(pending)
3442
+ } catch (err) {
3443
+ this.ctx.log.warn('The outcome record could not take its starting point', {
3444
+ 'exception.message': toErrorMessage(err),
3445
+ })
3446
+ }
3447
+ }
3448
+
3449
+ /** Feed the outcome record the calls of one finished step. */
3450
+ async outcomeObserve(
3451
+ step: number,
3452
+ response: ChatCompletionResponse,
3453
+ results: readonly ToolCallOutcome[],
3454
+ ): Promise<void> {
3455
+ try {
3456
+ this.hydrateOutcome()
3457
+ const calls = new Map((response.message.toolCalls ?? []).map((c) => [c.id, c]))
3458
+ const seen: OutcomeCall[] = results.map((result) => {
3459
+ const input = this.parsedInput(calls.get(result.toolCallId))
3460
+ return {
3461
+ toolName: result.toolName,
3462
+ input,
3463
+ isError: result.isError,
3464
+ ...(result.skipped ? { skipped: true } : {}),
3465
+ readOnly: this.lookedOnly(result.toolName, input),
3466
+ ...(result.facts ? { facts: result.facts } : {}),
3467
+ }
3468
+ })
3469
+ await this.outcomeLedger.observe(step, seen)
3470
+ } catch (err) {
3471
+ this.ctx.log.warn('The outcome record could not read a step', {
3472
+ 'exception.message': toErrorMessage(err),
3473
+ })
3474
+ }
3475
+ }
3476
+
3477
+ /** The outcome record as the turn leaves it; `undefined` when none was kept. */
3478
+ async takeOutcome(): Promise<TurnOutcome | undefined> {
3479
+ try {
3480
+ this.hydrateOutcome()
3481
+ return await this.outcomeLedger.finish()
3482
+ } catch (err) {
3483
+ this.ctx.log.warn('The outcome record could not be settled', {
3484
+ 'exception.message': toErrorMessage(err),
3485
+ })
3486
+ return undefined
3487
+ }
3488
+ }
3489
+
3337
3490
  /** Feed the completion audit the calls of one finished step. */
3338
3491
  private observeAudit(
3339
3492
  response: ChatCompletionResponse,
@@ -21,6 +21,7 @@ import {
21
21
  isCompactionMessage,
22
22
  } from '../../../../compaction/summary.js'
23
23
  import { estimateMessagesTokens } from '../../../../compaction/token-estimate.js'
24
+ import { clearStaleToolResults } from '../../../../compaction/tool-result-editing.js'
24
25
  import { buildVerifiedSummaryWithBoundedProvider } from '../../../../compaction/verifier.js'
25
26
  import type { CompactionConfig } from '../../../../config/runtime.js'
26
27
  import { CHARS_PER_TOKEN } from '../../../../constants/limits.js'
@@ -172,7 +173,7 @@ async function commitToolResultClear(
172
173
  plan: ToolResultClearPlan,
173
174
  contextWindowTokens: number,
174
175
  windowSource: 'config' | 'provider' | 'model-table' | 'default',
175
- reason: 'threshold' | 'overflow',
176
+ reason: 'threshold' | 'overflow' | 'manual',
176
177
  ): Promise<void> {
177
178
  // A shorter replacement also removes evidence. Preserve its original
178
179
  // content in the same archive used by whole-message compaction before
@@ -184,7 +185,56 @@ async function commitToolResultClear(
184
185
  ctx.recorder.clearLastPromptTokens?.()
185
186
  await emitToolResultClear(ctx, plan)
186
187
  await emitContextUsageSnapshot(ctx, estimateTokens(ctx), contextWindowTokens, windowSource)
187
- compactionBreaker(ctx)?.recordRelief()
188
+ // A host-requested pass is not the kernel's own relief of a full context,
189
+ // so it must not reset the breaker's failure streak.
190
+ if (reason !== 'manual') compactionBreaker(ctx)?.recordRelief()
191
+ }
192
+
193
+ /**
194
+ * The first request of a turn, for a host that said the prompt cache is gone:
195
+ * shed old, large tool results now, since the prefix is re-read at full price
196
+ * either way. Does nothing unless `clearStaleToolResultsAtStart` is set and the
197
+ * clear would save at least its floor. Reuses the in-turn clear, so the same
198
+ * results are protected, the originals are recorded before they are replaced,
199
+ * and the host gets the same `compaction_tool_results_cleared` event. It is
200
+ * not a threshold pass: the `pre_compact` / `post_compact` hooks, the
201
+ * near-limit and busy guards and the circuit breaker's relief count do not
202
+ * see it.
203
+ */
204
+ export async function runStaleResultClear(ctx: IterationContext): Promise<void> {
205
+ const config = ctx.compactionConfig
206
+ const option = config?.clearStaleToolResultsAtStart
207
+ if (!config || !option) return
208
+ if (config.strategy === 'disabled' || config.clearToolResults === false) return
209
+ // The pass promises the originals stay in the log. With the log switched
210
+ // off, replacing them would lose them, so the pass does not run.
211
+ if (config.recordShedHistory === false) return
212
+
213
+ const live = ctx.recorder.messages
214
+ const edit = clearStaleToolResults(live, {
215
+ keepRecentToolResults: option.keepRecentToolResults ?? config.keepRecentToolResults,
216
+ minCharsToClear: config.minToolResultCharsToClear,
217
+ ...(config.preserveToolResultsFrom ? { preserveTools: config.preserveToolResultsFrom } : {}),
218
+ })
219
+ if (edit.clearedCount === 0) return
220
+ const reclaimedTokens = estimateMessagesTokens(live) - estimateMessagesTokens(edit.messages)
221
+ if (reclaimedTokens < option.minSavedTokens) return
222
+
223
+ const window = activeContextWindow(ctx)
224
+ await commitToolResultClear(
225
+ ctx,
226
+ {
227
+ kind: 'cleared',
228
+ messages: edit.messages,
229
+ clearedCount: edit.clearedCount,
230
+ charsReclaimed: edit.charsReclaimed,
231
+ reclaimedTokens,
232
+ reliefWasEnough: true,
233
+ },
234
+ window.tokens,
235
+ window.source,
236
+ 'manual',
237
+ )
188
238
  }
189
239
 
190
240
  /**
@@ -628,7 +678,7 @@ async function recordShed(
628
678
  ctx: IterationContext,
629
679
  before: readonly Message[],
630
680
  after: readonly Message[],
631
- reason: 'threshold' | 'overflow',
681
+ reason: 'threshold' | 'overflow' | 'manual',
632
682
  ): Promise<void> {
633
683
  if (ctx.compactionConfig?.recordShedHistory === false) return
634
684
 
@@ -471,6 +471,12 @@ function resumeContextOf(ctx: IterationContext, scope?: AskScope): ResumeContext
471
471
  return {
472
472
  transcript: () => ctx.recorder.messages,
473
473
  ...(signal ? { signal } : {}),
474
+ ...(typeof ctx.toolExecutor?.launchedScripts === 'function'
475
+ ? {
476
+ launchedScripts: (callId, line, dialect) =>
477
+ ctx.toolExecutor.launchedScripts(callId, line, dialect),
478
+ }
479
+ : {}),
474
480
  }
475
481
  }
476
482
 
@@ -21,7 +21,7 @@ import {
21
21
  recordTokenUsage,
22
22
  } from '../../../telemetry/metrics.js'
23
23
  import { getTracer } from '../../../telemetry/runtime-accessors.js'
24
- import { mergeTokenUsage } from '../../../types/common/index.js'
24
+ import { mergeTokenUsage, promptSizeOf } from '../../../types/common/index.js'
25
25
  import { NamzuError } from '../../../types/errors/index.js'
26
26
  import type { ToolUseId } from '../../../types/ids/index.js'
27
27
  import type {
@@ -196,7 +196,7 @@ async function settleCancelledTurn(args: {
196
196
  recordTokenUsage(args.model, args.usage)
197
197
  recordModelDuration(args.model, Date.now() - args.startedAt)
198
198
  args.span.setAttributes({
199
- [GENAI.USAGE_INPUT_TOKENS]: args.usage.promptTokens,
199
+ [GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(args.usage),
200
200
  [GENAI.USAGE_OUTPUT_TOKENS]: args.usage.completionTokens,
201
201
  [NAMZU.CACHE_READ_TOKENS]: args.usage.cachedTokens ?? 0,
202
202
  [NAMZU.CACHE_WRITE_TOKENS]: args.usage.cacheWriteTokens ?? 0,
@@ -1128,7 +1128,7 @@ export async function* streamProviderTurn(
1128
1128
  [GENAI.RESPONSE_MODEL]: response.model,
1129
1129
  [GENAI.RESPONSE_ID]: response.id,
1130
1130
  [GENAI.RESPONSE_FINISH_REASONS]: [response.finishReason],
1131
- [GENAI.USAGE_INPUT_TOKENS]: usage.promptTokens,
1131
+ [GENAI.USAGE_INPUT_TOKENS]: promptSizeOf(usage),
1132
1132
  [GENAI.USAGE_OUTPUT_TOKENS]: usage.completionTokens,
1133
1133
  [NAMZU.CACHE_READ_TOKENS]: usage.cachedTokens ?? 0,
1134
1134
  [NAMZU.CACHE_WRITE_TOKENS]: usage.cacheWriteTokens ?? 0,
@@ -99,6 +99,17 @@ export class PreludeSessionLease {
99
99
  }
100
100
  return
101
101
  }
102
+ // A renewal is two durable replaces and four fsyncs; the prelude asks
103
+ // three times in a few milliseconds. While the holding has more than
104
+ // half its time left the heartbeat has nothing to add, and what the
105
+ // question is really about, whether anyone took the session, is
106
+ // answered by reading the fence, as it is for a supplied lease.
107
+ await this.#renewal
108
+ if (this.#renewalError !== undefined) throw this.#renewalError
109
+ if (this.#current.expiresAt - Date.now() >= DEFAULT_TURN_LEASE_TTL_MS / 2) {
110
+ const held = await this.#log.lease()
111
+ if (held?.fence === this.#current.fence && held.holder === this.#current.holder) return
112
+ }
102
113
  this.#queueRenewal()
103
114
  await this.#renewal
104
115
  if (this.#renewalError !== undefined) throw this.#renewalError
@@ -107,6 +118,14 @@ export class PreludeSessionLease {
107
118
  /** Hand the freshest token to TurnRecorder while this heartbeat covers open(). */
108
119
  async transfer(): Promise<SessionLease> {
109
120
  await this.assertCurrent()
121
+ // The recorder's own heartbeat first fires half a lifetime after it
122
+ // opens, so it must be handed a full lifetime, not whatever was left
123
+ // when the last confirmation read the fence.
124
+ if (this.ownsLease) {
125
+ this.#queueRenewal()
126
+ await this.#renewal
127
+ if (this.#renewalError !== undefined) throw this.#renewalError
128
+ }
110
129
  this.#transferred = true
111
130
  return this.#current
112
131
  }
@@ -430,7 +430,11 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
430
430
  ]
431
431
  assertCostIsAttributable(chain, params.pricing)
432
432
  assertBudgetIsMeasurable(params)
433
- const withRecovery = (provider: LLMProvider): LLMProvider => {
433
+ // When the caller asks for it (`retry.overloadedFallOverAfter`), a member
434
+ // with a successor leaves after that many overloaded answers in a row
435
+ // instead of spending the whole budget on a saturated provider; the last
436
+ // member has nowhere to go, so it keeps the full budget whatever was asked.
437
+ const withRecovery = (provider: LLMProvider, hasSuccessor: boolean): LLMProvider => {
434
438
  const withIdleBound = withStreamIdleTimeout(provider, {
435
439
  idleTimeoutMs: streamIdleTimeoutMs,
436
440
  log,
@@ -439,7 +443,12 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
439
443
  return params.retry === false
440
444
  ? metered
441
445
  : withProviderRetry(metered, {
442
- config: params.retry,
446
+ config: {
447
+ ...params.retry,
448
+ overloadedFallOverAfter: hasSuccessor
449
+ ? (params.retry?.overloadedFallOverAfter ?? 0)
450
+ : 0,
451
+ },
443
452
  log,
444
453
  canRetry: () => budget.remaining > 0,
445
454
  })
@@ -455,9 +464,9 @@ export async function prepareTurn(params: QueryParams): Promise<PreparedTurn> {
455
464
  current: { index: 0, providerId: params.provider.id },
456
465
  }
457
466
  const resilientProvider = withProviderFallback(
458
- chain.map((member) => ({
467
+ chain.map((member, position) => ({
459
468
  ...member,
460
- provider: withRecovery(member.provider),
469
+ provider: withRecovery(member.provider, position < chain.length - 1),
461
470
  })),
462
471
  {
463
472
  log,
@@ -37,6 +37,7 @@ import { isTrustedReadOnly } from '../../tools/trusted-read-only.js'
37
37
  import type { ToolManager } from '../../toolsets/manager.js'
38
38
  import type {
39
39
  HITLResumeDecision,
40
+ LaunchedScript,
40
41
  ResumeContext,
41
42
  ResumeHandler,
42
43
  ToolCallDeclined,
@@ -933,6 +934,36 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
933
934
  /** Set once the classifier has said the conversation is too long, until it judges something. */
934
935
  let transcriptTooLong = false
935
936
  const toolOf = (name: string) => registry?.get(name) ?? registry?.get(name.toLowerCase())
937
+ /** The script files a shell call launches that the agent wrote, read now; none for any other call. */
938
+ const launchedScriptsOf = async (
939
+ tc: ToolCallSummary,
940
+ context: ResumeContext | undefined,
941
+ ): Promise<readonly LaunchedScript[]> => {
942
+ if (context?.launchedScripts === undefined) return []
943
+ const tool = toolOf(tc.name)
944
+ const argument = tool?.commandArgument
945
+ if (tool === undefined || argument === undefined) return []
946
+ const line = (tc.input as Record<string, unknown> | null)?.[argument]
947
+ if (typeof line !== 'string') return []
948
+ try {
949
+ return await context.launchedScripts(
950
+ tc.id,
951
+ line,
952
+ tool.commandDialect?.({ sandboxed: false }) ?? 'sh',
953
+ )
954
+ } catch {
955
+ // Reading a script is an addition to the review. A failure to do it is
956
+ // said to the reviewer rather than hidden.
957
+ return [
958
+ {
959
+ path: '(unknown)',
960
+ origin: 'unknown',
961
+ state: 'unreadable',
962
+ note: 'reading the script files this line launches failed, so what they run is not known',
963
+ },
964
+ ]
965
+ }
966
+ }
936
967
  const rootsNow = (): readonly string[] =>
937
968
  typeof options.roots === 'function' ? options.roots() : (options.roots ?? [])
938
969
  const exempt: ReviewExemption =
@@ -1215,9 +1246,11 @@ export function createReviewHandler(options: ReviewPolicyOptions = {}): ResumeHa
1215
1246
  for (const tc of live) {
1216
1247
  // The questions a person must answer are asked of a person, never of a model.
1217
1248
  if (isBypassImmune(tc) || (await neverSent(tc, request.sessionId))) continue
1249
+ const scripts = await launchedScriptsOf(tc, context)
1218
1250
  const decision = await classifier.review({
1219
1251
  call: tc,
1220
1252
  transcript,
1253
+ ...(scripts.length > 0 ? { scripts } : {}),
1221
1254
  ...(registry ? { tools: registry } : {}),
1222
1255
  ...(context?.signal ? { signal: context.signal } : {}),
1223
1256
  })
@@ -32,6 +32,8 @@ export interface ToolingBootstrapConfig {
32
32
  scratchpadDirectory?: string
33
33
  /** See `QueryParams.outsideRootAccess`. */
34
34
  outsideRootAccess?: 'refuse' | 'review'
35
+ /** See `QueryParams.outsideRootRefusal`. */
36
+ outsideRootRefusal?: string
35
37
  /** See `QueryParams.sandboxEscape`. */
36
38
  sandboxEscape?: 'refuse' | 'review'
37
39
  /** A resolver, so an approval inside a turn can change it. See the executor. */
@@ -103,6 +105,7 @@ export class ToolingBootstrap {
103
105
  : {}),
104
106
  ...(config.scratchpadDirectory ? { scratchpadDirectory: config.scratchpadDirectory } : {}),
105
107
  ...(config.outsideRootAccess ? { outsideRootAccess: config.outsideRootAccess } : {}),
108
+ ...(config.outsideRootRefusal ? { outsideRootRefusal: config.outsideRootRefusal } : {}),
106
109
  ...(config.sandboxEscape ? { sandboxEscape: config.sandboxEscape } : {}),
107
110
  permissionMode: config.permissionMode,
108
111
  env: config.env,