@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
@@ -3,6 +3,7 @@ import { type TurnRecorder, readFoldedHistory } from '../../manager/session/turn
3
3
  import type { CheckpointScope, SessionCheckpointStore } from '../../store/checkpoint/index.js'
4
4
  import type { SessionLog } from '../../store/session-log/index.js'
5
5
  import { restoreSpilledRequestInputs } from '../../store/session-log/spilled-input.js'
6
+ import { readOutcomeProgress } from '../../store/session-log/turn-outcome.js'
6
7
  import type { SerializedSpanContext } from '../../telemetry/attributes.js'
7
8
  import { NamzuError } from '../../types/errors/index.js'
8
9
  import type {
@@ -84,7 +85,14 @@ type CheckpointState = Pick<
84
85
  | 'review'
85
86
  | 'latestUserMessageId'
86
87
  | 'workingState'
87
- >
88
+ > & {
89
+ /**
90
+ * What the turn's outcome record had gathered. Not a field of the document:
91
+ * it is written as its own `outcome_progress_written` record beside the
92
+ * checkpoint, so a release that validates the document strictly reads it.
93
+ */
94
+ readonly outcome?: Readonly<Record<string, unknown>>
95
+ }
88
96
 
89
97
  /**
90
98
  * A checkpoint read back for a resume: the document, the context it
@@ -276,6 +284,11 @@ export class CheckpointManager {
276
284
  private restoredNativeAttempts = 0
277
285
  private toolStructuredAttemptsSource?: () => number
278
286
  private restoredToolAttempts = 0
287
+ private outcomeSource?: () => Readonly<Record<string, unknown>> | undefined
288
+ private restoredOutcomeState: { resumed: boolean; snapshot?: Readonly<Record<string, unknown>> } =
289
+ {
290
+ resumed: false,
291
+ }
279
292
  private lastCreatedId?: CheckpointId
280
293
  private traceSource?: () => SerializedSpanContext | undefined
281
294
  private parkTtlMs?: number
@@ -329,6 +342,23 @@ export class CheckpointManager {
329
342
  return this.restoredToolAttempts
330
343
  }
331
344
 
345
+ /** Wire the turn's outcome record in, so every checkpoint carries what it had gathered. */
346
+ setOutcomeSource(source: () => Readonly<Record<string, unknown>> | undefined): void {
347
+ this.outcomeSource = source
348
+ }
349
+
350
+ /**
351
+ * What a resumed turn's checkpoint kept of the outcome record. `resumed` is
352
+ * false for a turn that began here; true with no `snapshot` for one resumed
353
+ * from a checkpoint that predates the record.
354
+ */
355
+ get restoredOutcome(): {
356
+ readonly resumed: boolean
357
+ readonly snapshot?: Readonly<Record<string, unknown>>
358
+ } {
359
+ return this.restoredOutcomeState
360
+ }
361
+
332
362
  setStructuredReviewAttemptsSource(source: () => number): void {
333
363
  this.structuredReviewAttemptsSource = source
334
364
  }
@@ -417,6 +447,7 @@ export class CheckpointManager {
417
447
  },
418
448
  ...(latestUserMessageId ? { latestUserMessageId } : {}),
419
449
  ...withDefined('workingState', this.workingStateSource?.()),
450
+ ...withDefined('outcome', this.outcomeSource?.() ?? this.restoredOutcomeState.snapshot),
420
451
  }
421
452
  }
422
453
 
@@ -449,6 +480,19 @@ export class CheckpointManager {
449
480
  turnCreatedAt: this.turnCreatedAt ?? new Date(recorder.getTurn().startedAt).toISOString(),
450
481
  createdAt: new Date().toISOString(),
451
482
  }
483
+ // What the outcome record had gathered goes in its own record, ahead of the
484
+ // one that commits the checkpoint, and is read back by the checkpoint's id.
485
+ // Failing to write it is not failing to checkpoint: a resume without it
486
+ // reports no outcome, never a false `unchanged`.
487
+ if (state.outcome !== undefined) {
488
+ await recorder.appendIgnorableRecord({
489
+ type: 'outcome_progress_written',
490
+ turnId: this.scope.turnId,
491
+ ignorable: true,
492
+ checkpointId: document.checkpointId,
493
+ progress: state.outcome,
494
+ })
495
+ }
452
496
  const receipt = await this.store.write(this.scope, document)
453
497
  await recorder.appendRecord({
454
498
  type: 'checkpoint_written',
@@ -654,6 +698,14 @@ export class CheckpointManager {
654
698
  this.restoredNativeAttempts = document.review.nativeStructuredAttempts
655
699
  this.restoredToolAttempts = document.review.toolStructuredAttempts ?? 0
656
700
  this.restoredReviewAttempts = document.review.structuredAttempts
701
+ await this.recorder.flush()
702
+ const progress = await readOutcomeProgress(this.recorder.log, checkpointId).catch(
703
+ () => undefined,
704
+ )
705
+ this.restoredOutcomeState = {
706
+ resumed: true,
707
+ ...(progress !== undefined ? { snapshot: progress } : {}),
708
+ }
657
709
  this.restoredUserMessage = restored.latestUserMessage
658
710
  this.restoredUserMessageId = restored.latestUserMessage
659
711
  ? document.latestUserMessageId
@@ -29,7 +29,11 @@ import { resolveRetainedOutput } from '../../tools/retained-output.js'
29
29
  import type { ToolManager } from '../../toolsets/manager.js'
30
30
  import type { ToolSourceRef } from '../../toolsets/types.js'
31
31
  import type { ToolResultGuardrailSpec } from '../../types/guardrail/index.js'
32
- import type { ToolCallDeclined, ToolCallEscalation } from '../../types/hitl/index.js'
32
+ import type {
33
+ LaunchedScript,
34
+ ToolCallDeclined,
35
+ ToolCallEscalation,
36
+ } from '../../types/hitl/index.js'
33
37
  import type { SessionId, ToolUseId, TurnId } from '../../types/ids/index.js'
34
38
  import type { InvocationState } from '../../types/invocation/index.js'
35
39
  import {
@@ -72,6 +76,12 @@ import { compressShellOutput } from '../../utils/shell-compress.js'
72
76
  import { parseStructuredResultJson } from '../../utils/structured-result-json.js'
73
77
  import { ensureScratchpadDirectory } from '../../utils/temp.js'
74
78
  import { type BackgroundJobRegistry, type JobProcess, bindOwner } from '../jobs/registry.js'
79
+ import {
80
+ type ScriptEvidence,
81
+ analyzeLaunches,
82
+ changedSinceRead,
83
+ readLaunchedScripts,
84
+ } from './auto-review/script-evidence.js'
75
85
  import { type BatchDetach, stillRunningResult } from './detached-calls.js'
76
86
  import {
77
87
  type ToolAdmissionHost,
@@ -378,6 +388,8 @@ export interface ToolExecutorConfig {
378
388
  outsideRootAccess?: 'refuse' | 'review'
379
389
  /** See `QueryParams.sandboxEscape`. Default `'refuse'`. */
380
390
  sandboxEscape?: 'refuse' | 'review'
391
+ /** See `QueryParams.outsideRootRefusal`. */
392
+ outsideRootRefusal?: string
381
393
  /**
382
394
  * Read LIVE, not frozen at turn start.
383
395
  *
@@ -577,6 +589,12 @@ export interface ToolCallOutcome {
577
589
  /** Rich form for the model, when the tool supplied one. */
578
590
  content?: ToolResultContent
579
591
  isError?: boolean
592
+ /**
593
+ * What the call was about and what came of it, as typed fields; the same value
594
+ * `tool_completed` carries. Absent when a post-tool hook replaced the output,
595
+ * so a reader never takes an exit status from a result the hook rewrote.
596
+ */
597
+ facts?: ToolFacts
580
598
  /** Provider argument admission failure, never inferred from receipt text. */
581
599
  inputFailure?: CompletedToolRecord['inputFailure']
582
600
  /** A pre-tool hook deliberately skipped execution; its receipt is not an output candidate. */
@@ -732,6 +750,9 @@ export class ToolExecutor {
732
750
  private readonly toolCallBudget?: ToolCallBudget
733
751
  private readonly preparedBatches = new WeakSet<PreparedToolBatch>()
734
752
  private readonly preparedBashDirectories = new WeakMap<PreparedSafetyCall, string>()
753
+ /** Script files a classifier read for a call, by call id, to be checked again when it runs. */
754
+ private readonly reviewedScripts = new Map<string, ScriptEvidence['watch']>()
755
+ private readonly startedAt = Date.now()
735
756
  /** Ephemeral proof of actual execution; restored messages do not acquire it. */
736
757
  private readonly recordedObservations = new Map<
737
758
  string,
@@ -982,6 +1003,47 @@ export class ToolExecutor {
982
1003
  )
983
1004
  }
984
1005
 
1006
+ /**
1007
+ * The script files a shell line launches that the agent wrote or that
1008
+ * appeared during the session, read now for the auto-mode classifier.
1009
+ * Remembers what was read for `callId`, so the file can be checked again
1010
+ * just before the call runs.
1011
+ */
1012
+ async launchedScripts(
1013
+ callId: string,
1014
+ line: string,
1015
+ dialect: ShellDialect,
1016
+ ): Promise<readonly LaunchedScript[]> {
1017
+ if (this.sandboxed) {
1018
+ // A sandbox's files are not on this host's disk. Say so rather than
1019
+ // letting a launch inside one look like a launch of nothing.
1020
+ const analysis = analyzeLaunches(line, dialect)
1021
+ const named = analysis.launches.map((l) => l.path)
1022
+ return named.length === 0 && analysis.unresolved.length === 0
1023
+ ? []
1024
+ : [
1025
+ {
1026
+ path: named[0] ?? '(launch form not resolved)',
1027
+ origin: 'unknown',
1028
+ state: 'unreadable',
1029
+ note: 'the command runs in a sandbox whose files are not read for this review, so what the script does is not known',
1030
+ },
1031
+ ]
1032
+ }
1033
+ const evidence = await readLaunchedScripts(line, dialect, {
1034
+ cwd: effectiveBashWorkingDirectory(this.config.sessionId, this.config.workingDirectory, {
1035
+ sandboxed: false,
1036
+ }),
1037
+ startedAt: this.fileReadTracker.startedAt ?? this.startedAt,
1038
+ ...(this.fileReadTracker.authoredFingerprint
1039
+ ? { authoredFingerprint: (key: string) => this.fileReadTracker.authoredFingerprint?.(key) }
1040
+ : {}),
1041
+ })
1042
+ if (evidence.watch.length > 0) this.reviewedScripts.set(callId, evidence.watch)
1043
+ else this.reviewedScripts.delete(callId)
1044
+ return evidence.scripts
1045
+ }
1046
+
985
1047
  /** The whole of the evidence as one text, from the same ledger. No filesystem I/O. */
986
1048
  describeFileEvidence(messages: readonly Message[]): string | undefined {
987
1049
  return describeVisibleFileEvidence(
@@ -1207,11 +1269,12 @@ export class ToolExecutor {
1207
1269
  toolCall,
1208
1270
  !hostModifiedCallIds?.has(toolCall.id),
1209
1271
  )
1210
- const call = await this.refusedBeforeReview(prepared, earlierPaths)
1272
+ const beforeReview = await this.refusedBeforeReview(prepared, earlierPaths)
1211
1273
  const path = inputPathOf(prepared)
1212
1274
  if (path !== undefined) earlierPaths.push(path)
1275
+ this.admitScratchpad(beforeReview)
1276
+ const call = await this.refusedOutsideRoot(beforeReview)
1213
1277
  calls.set(toolCall.id, call)
1214
- this.admitScratchpad(call)
1215
1278
  const escalation = await this.escalationOrUnfinished(call)
1216
1279
  if (escalation) escalations.set(toolCall.id, escalation)
1217
1280
  if (await this.writesScratchpad(call, escalation)) scratchpadWrites.add(toolCall.id)
@@ -1220,6 +1283,49 @@ export class ToolExecutor {
1220
1283
  return this.publishPreparedBatch(calls, escalations, scratchpadWrites)
1221
1284
  }
1222
1285
 
1286
+ /**
1287
+ * A file tool's path outside the roots, refused with the host's own words
1288
+ * (`QueryParams.outsideRootRefusal`) instead of the tool's generic one.
1289
+ *
1290
+ * It takes the place of the call like any refusal found while preparing it,
1291
+ * so it holds in every permission mode and reaches no reviewer. Only a turn
1292
+ * that refuses (`outsideRootAccess: 'refuse'`) and has no sandbox is looked
1293
+ * at, and only a tool that declares `pathArgument`. The exemptions are the
1294
+ * review's: the scratch folder is a root, and so is this session's retained
1295
+ * output for `read`.
1296
+ */
1297
+ private async refusedOutsideRoot(call: PreparedDirectCall): Promise<PreparedDirectCall> {
1298
+ const note = this.config.outsideRootRefusal
1299
+ if (call.kind !== 'ready' || note === undefined) return call
1300
+ if (this.config.outsideRootAccess !== 'refuse' || this.sandboxed) return call
1301
+ const tool = this.config.tools.get(call.toolName)
1302
+ if (tool?.pathArgument === undefined) return call
1303
+ if (call.input === null || typeof call.input !== 'object') return call
1304
+ const value = (call.input as Record<string, unknown>)[tool.pathArgument]
1305
+ if (typeof value !== 'string') return call
1306
+ try {
1307
+ const retained =
1308
+ tool.name === 'read' && (await resolveRetainedOutput(this.outputDirectory(), value))
1309
+ if (retained) return call
1310
+ const outside = await pathOutsideRoots(this.rootsNow(), value)
1311
+ if (outside === undefined) return call
1312
+ return {
1313
+ kind: 'synthetic',
1314
+ toolCall: call.toolCall,
1315
+ toolName: call.toolName,
1316
+ input: call.input,
1317
+ message: formatFailedToolOutput(
1318
+ undefined,
1319
+ `${outside} is outside the working directory. ${note}`,
1320
+ ),
1321
+ isError: true,
1322
+ }
1323
+ } catch {
1324
+ this.config.abortSignal.throwIfAborted()
1325
+ return call
1326
+ }
1327
+ }
1328
+
1223
1329
  /**
1224
1330
  * A ready call the tool already knows cannot run, answered now instead of
1225
1331
  * after a reviewer has been asked about it (`ToolDefinition.refuseBeforeReview`).
@@ -1461,12 +1567,13 @@ export class ToolExecutor {
1461
1567
  const earlierPaths: string[] = []
1462
1568
  for (const toolCall of response.message.toolCalls ?? []) {
1463
1569
  if (changedCallIds.has(toolCall.id)) {
1464
- const call = await this.refusedBeforeReview(
1570
+ const beforeReview = await this.refusedBeforeReview(
1465
1571
  await prepareDirectCall(this.admissionHost(), toolCall, false),
1466
1572
  earlierPaths,
1467
1573
  )
1574
+ this.admitScratchpad(beforeReview)
1575
+ const call = await this.refusedOutsideRoot(beforeReview)
1468
1576
  calls.set(toolCall.id, call)
1469
- this.admitScratchpad(call)
1470
1577
  const escalation = await this.escalationOrUnfinished(call)
1471
1578
  if (escalation) escalations.set(toolCall.id, escalation)
1472
1579
  else escalations.delete(toolCall.id)
@@ -1730,13 +1837,25 @@ export class ToolExecutor {
1730
1837
  let outcome: ToolCallOutcome
1731
1838
  started.add(toolCall.id)
1732
1839
  try {
1733
- outcome = await this.executeSingle(
1734
- toolCall,
1735
- ctx,
1736
- recordObservation,
1737
- () => progress.close(),
1738
- preparedCall,
1739
- )
1840
+ const reviewedScripts = this.reviewedScripts.get(toolCall.id)
1841
+ this.reviewedScripts.delete(toolCall.id)
1842
+ const moved =
1843
+ reviewedScripts === undefined ? undefined : await changedSinceRead(reviewedScripts)
1844
+ outcome =
1845
+ moved !== undefined
1846
+ ? {
1847
+ toolCallId: toolCall.id,
1848
+ toolName: toolCall.function.name,
1849
+ output: `Error: ${moved} changed after the safety check read it, so the command was not run. Run it again and it will be checked against the new text.`,
1850
+ isError: true,
1851
+ }
1852
+ : await this.executeSingle(
1853
+ toolCall,
1854
+ ctx,
1855
+ recordObservation,
1856
+ () => progress.close(),
1857
+ preparedCall,
1858
+ )
1740
1859
  } catch (error) {
1741
1860
  // A call already answered with the placeholder has no batch
1742
1861
  // left to fail; its failure is its outcome.
@@ -1932,6 +2051,7 @@ export class ToolExecutor {
1932
2051
  } catch (error) {
1933
2052
  failure ??= { error }
1934
2053
  } finally {
2054
+ this.reviewedScripts.delete(unit.toolCall.id)
1935
2055
  settledUnit()
1936
2056
  inFlight.delete(unit)
1937
2057
  pump()
@@ -2269,7 +2389,7 @@ export class ToolExecutor {
2269
2389
  toolName: name,
2270
2390
  toolUseId: nestedId,
2271
2391
  output: rawOutput,
2272
- maxChars: this.config.maxToolOutputChars ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS,
2392
+ maxChars: this.toolOutputCap(this.config.tools.get(name), false),
2273
2393
  retainedPreviewChars: this.config.retainedToolPreviewChars,
2274
2394
  spillDir: this.outputDirectory(),
2275
2395
  recoveryRoutes: this.recoveryRoutes(),
@@ -2734,11 +2854,7 @@ export class ToolExecutor {
2734
2854
  ]
2735
2855
  }
2736
2856
  }
2737
- // A tool that bounds its own output is exempt from the DEFAULT per-result
2738
- // cap only; a host that set the cap explicitly still gets it.
2739
- const maxToolOutputChars =
2740
- this.config.maxToolOutputChars ??
2741
- (executedDefinition?.selfLimitingOutput === true ? 0 : DEFAULT_MAX_TOOL_OUTPUT_CHARS)
2857
+ const maxToolOutputChars = this.toolOutputCap(executedDefinition, true)
2742
2858
  const sourceOutput = output
2743
2859
  // This owner-bound channel follows the selected receipt after both result
2744
2860
  // screening and post-tool replacement. ToolResult.data and raw input can
@@ -2967,6 +3083,7 @@ export class ToolExecutor {
2967
3083
  ? { handoff: result.handoff }
2968
3084
  : {}),
2969
3085
  ...(revealedTools !== undefined ? { revealedTools } : {}),
3086
+ ...(result.facts !== undefined && !postOverride ? { facts: result.facts } : {}),
2970
3087
  }
2971
3088
  }
2972
3089
 
@@ -3465,6 +3582,8 @@ export class ToolExecutor {
3465
3582
  preparedCall?: PreparedDirectCall,
3466
3583
  declined?: ToolCallDeclined,
3467
3584
  ): Promise<ToolCallOutcome> {
3585
+ // A call that does not run has no use for what a review read of its scripts.
3586
+ this.reviewedScripts.delete(toolCall.id)
3468
3587
  const toolName = preparedCall?.toolName ?? toolCall.function.name
3469
3588
  let input: unknown = preparedCall?.input ?? {}
3470
3589
  if (!preparedCall) {
@@ -3623,6 +3742,7 @@ export class ToolExecutor {
3623
3742
  toolName,
3624
3743
  output: outcome.output,
3625
3744
  isError: outcome.kind === 'error',
3745
+ ...(outcome.facts ? { facts: outcome.facts } : {}),
3626
3746
  ...(outcome.inputFailure ? { inputFailure: outcome.inputFailure } : {}),
3627
3747
  ...(outcome.skipped ? { skipped: true as const } : {}),
3628
3748
  }
@@ -3638,6 +3758,26 @@ export class ToolExecutor {
3638
3758
  })
3639
3759
  }
3640
3760
 
3761
+ /**
3762
+ * The per-result character cap for one tool: the turn's cap, tightened by
3763
+ * the tool's own `maxOutputChars`. A tool that bounds its own output is
3764
+ * exempt from the DEFAULT cap only (`exemptSelfLimiting`); a host that set
3765
+ * the cap explicitly still gets it. 0 means no cap.
3766
+ */
3767
+ private toolOutputCap(
3768
+ definition: ToolDefinition | undefined,
3769
+ exemptSelfLimiting: boolean,
3770
+ ): number {
3771
+ const turn =
3772
+ this.config.maxToolOutputChars ??
3773
+ (exemptSelfLimiting && definition?.selfLimitingOutput === true
3774
+ ? 0
3775
+ : DEFAULT_MAX_TOOL_OUTPUT_CHARS)
3776
+ const own = definition?.maxOutputChars
3777
+ if (own === undefined || !Number.isFinite(own) || own <= 0) return turn
3778
+ return turn > 0 ? Math.min(turn, Math.floor(own)) : Math.floor(own)
3779
+ }
3780
+
3641
3781
  /**
3642
3782
  * The host preview and model text may differ; each gets its own text
3643
3783
  * budget. Rich bytes are measured separately and withheld whole when
@@ -3674,7 +3814,7 @@ export class ToolExecutor {
3674
3814
  toolName,
3675
3815
  toolUseId,
3676
3816
  output: text,
3677
- maxChars: this.config.maxToolOutputChars ?? DEFAULT_MAX_TOOL_OUTPUT_CHARS,
3817
+ maxChars: this.toolOutputCap(this.config.tools.get(toolName), false),
3678
3818
  ...(notice ? { notice } : {}),
3679
3819
  // A tool may return different host and model text. They must
3680
3820
  // never compete for the same exclusive spill filename.
@@ -2,7 +2,7 @@ import type { Span } from '@opentelemetry/api'
2
2
  import { consolidationEntry, isConsolidated } from '../../compaction/consolidation.js'
3
3
  import type { WorkingStateManager } from '../../compaction/manager.js'
4
4
  import { NAMZU } from '../../constants/telemetry/index.js'
5
- import type { SessionEvent, StepResult } from '../../types/session/index.js'
5
+ import type { SessionEvent, StepResult, TurnOutcome } from '../../types/session/index.js'
6
6
  import { toErrorMessage } from '../../utils/error.js'
7
7
  import type { TurnContext } from './context.js'
8
8
  import type { EventTranslator } from './events.js'
@@ -53,6 +53,12 @@ export interface TurnFinalization {
53
53
  * immediately before the record is written.
54
54
  */
55
55
  readonly takeSteps: () => readonly StepResult[]
56
+ /**
57
+ * The turn's outcome record, read here for the same reason as the steps: the
58
+ * workspace is compared once, as late as it can be and before the record is
59
+ * written. Never throws; `undefined` means no record was kept.
60
+ */
61
+ readonly takeOutcome?: () => Promise<TurnOutcome | undefined>
56
62
  /**
57
63
  * Whether the loop already fired `turn_end` (and `subagent_stop`) when the
58
64
  * model finished, so a hook could keep the turn going. Fired there, it is
@@ -108,6 +114,7 @@ export async function* finalizeTurn(
108
114
  // Hand the step record to the turn before it settles, so the
109
115
  // returned `Turn` carries it.
110
116
  ctx.recorder.setSteps(takeSteps())
117
+ if (finalization.takeOutcome) ctx.recorder.setOutcome(await finalization.takeOutcome())
111
118
 
112
119
  // Gates the FINAL result, not the stream — `text_delta` already
113
120
  // reached the host as the model produced it. A rewrite is
@@ -619,6 +619,14 @@ export interface QueryParams {
619
619
  * is never, because the path is not mounted there to be reached.
620
620
  */
621
621
  outsideRootAccess?: 'refuse' | 'review'
622
+ /**
623
+ * The sentence a host wants the model to read when `outsideRootAccess` is
624
+ * `'refuse'` and a file tool names a path outside the roots, for example
625
+ * which setting made the wall and how to move it. Without it the tool's own
626
+ * generic refusal stands. The refusal is made before any review, so no
627
+ * permission mode (bypass included) lifts it.
628
+ */
629
+ outsideRootRefusal?: string
622
630
  /**
623
631
  * Whether a call may ask to run outside the turn's sandbox (the shipped
624
632
  * `bash` tool's `dangerously_disable_sandbox`).
@@ -1599,6 +1607,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
1599
1607
  : {}),
1600
1608
  ...(scratchpadDirectory ? { scratchpadDirectory } : {}),
1601
1609
  ...(params.outsideRootAccess ? { outsideRootAccess: params.outsideRootAccess } : {}),
1610
+ ...(params.outsideRootRefusal ? { outsideRootRefusal: params.outsideRootRefusal } : {}),
1602
1611
  ...(params.sandboxEscape ? { sandboxEscape: params.sandboxEscape } : {}),
1603
1612
  permissionMode: () => ctx.permissionMode.current,
1604
1613
  env: turnConfig.env ?? {},
@@ -2823,6 +2832,9 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
2823
2832
  }
2824
2833
  }
2825
2834
 
2835
+ // The parked batch runs here, outside the loop's own steps, so
2836
+ // the outcome record is told about it here.
2837
+ await iterationOrchestrator.outcomeBefore(pendingResume.response)
2826
2838
  const resumedBatch = await applyPendingResume(
2827
2839
  pendingResume,
2828
2840
  ctx.recorder,
@@ -2831,6 +2843,11 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
2831
2843
  pendingResumeAssistantId,
2832
2844
  )
2833
2845
  iterationOrchestrator.setResumedToolResults(resumedBatch.results)
2846
+ await iterationOrchestrator.outcomeObserve(
2847
+ ctx.recorder.currentIteration,
2848
+ pendingResume.response,
2849
+ resumedBatch.results,
2850
+ )
2834
2851
  yield* eventTranslator.drainPending()
2835
2852
  }
2836
2853
 
@@ -2911,6 +2928,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
2911
2928
  params,
2912
2929
  eventTranslator,
2913
2930
  takeSteps: () => iterationOrchestrator.getSteps(),
2931
+ takeOutcome: () => iterationOrchestrator.takeOutcome(),
2914
2932
  turnEndHooksFired: () => iterationOrchestrator.turnEndHooksFired,
2915
2933
  workingStateManager,
2916
2934
  resultAssembler,
@@ -2933,6 +2951,7 @@ export async function* query(params: QueryParams): AsyncGenerator<SessionEvent,
2933
2951
  if (isTurnInProgressError(err) && !ctx.recorder.isActive) throw err
2934
2952
  // A failed turn still spent its steps; report them.
2935
2953
  ctx.recorder.setSteps(iterationOrchestrator.getSteps())
2954
+ ctx.recorder.setOutcome(await iterationOrchestrator.takeOutcome())
2936
2955
  await executeUserInterruptHooks(err)
2937
2956
  yield* eventTranslator.drainPending()
2938
2957
  await executeTurnFailureHooks(err)