@namzu/sdk 36.0.0 → 37.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 (308) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +68 -41
  3. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  4. package/dist/bridge/a2a/mapper.js +2 -0
  5. package/dist/bridge/a2a/mapper.js.map +1 -1
  6. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  7. package/dist/bridge/sse/mapper.js +2 -0
  8. package/dist/bridge/sse/mapper.js.map +1 -1
  9. package/dist/compaction/extractor.d.ts +1 -1
  10. package/dist/compaction/extractor.d.ts.map +1 -1
  11. package/dist/compaction/extractor.js +15 -7
  12. package/dist/compaction/extractor.js.map +1 -1
  13. package/dist/compaction/manager.d.ts +2 -0
  14. package/dist/compaction/manager.d.ts.map +1 -1
  15. package/dist/compaction/manager.js +12 -1
  16. package/dist/compaction/manager.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts.map +1 -1
  18. package/dist/compaction/verifier.js +82 -15
  19. package/dist/compaction/verifier.js.map +1 -1
  20. package/dist/contracts/schemas.d.ts +10 -10
  21. package/dist/eval/harness-verification.d.ts +63 -0
  22. package/dist/eval/harness-verification.d.ts.map +1 -0
  23. package/dist/eval/harness-verification.js +165 -0
  24. package/dist/eval/harness-verification.js.map +1 -0
  25. package/dist/eval/index.d.ts +2 -0
  26. package/dist/eval/index.d.ts.map +1 -1
  27. package/dist/eval/index.js +1 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/execution/code-runtime/json-value.d.ts +10 -0
  30. package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
  31. package/dist/execution/code-runtime/json-value.js +89 -0
  32. package/dist/execution/code-runtime/json-value.js.map +1 -0
  33. package/dist/execution/code-runtime/worker-program.d.ts +8 -0
  34. package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
  35. package/dist/execution/code-runtime/worker-program.js +240 -0
  36. package/dist/execution/code-runtime/worker-program.js.map +1 -0
  37. package/dist/execution/code-runtime/worker.d.ts +22 -0
  38. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  39. package/dist/execution/code-runtime/worker.js +148 -111
  40. package/dist/execution/code-runtime/worker.js.map +1 -1
  41. package/dist/manager/agent/lifecycle.d.ts +12 -0
  42. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  43. package/dist/manager/agent/lifecycle.js +306 -37
  44. package/dist/manager/agent/lifecycle.js.map +1 -1
  45. package/dist/manager/run/persistence.d.ts +2 -0
  46. package/dist/manager/run/persistence.d.ts.map +1 -1
  47. package/dist/manager/run/persistence.js +6 -0
  48. package/dist/manager/run/persistence.js.map +1 -1
  49. package/dist/provider/capabilities.d.ts +3 -1
  50. package/dist/provider/capabilities.d.ts.map +1 -1
  51. package/dist/provider/capabilities.js +13 -0
  52. package/dist/provider/capabilities.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +2 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/public-runtime.d.ts +3 -1
  57. package/dist/public-runtime.d.ts.map +1 -1
  58. package/dist/public-runtime.js +2 -1
  59. package/dist/public-runtime.js.map +1 -1
  60. package/dist/public-tools.d.ts +1 -1
  61. package/dist/public-tools.d.ts.map +1 -1
  62. package/dist/public-tools.js +1 -1
  63. package/dist/public-tools.js.map +1 -1
  64. package/dist/public-types.d.ts +1 -0
  65. package/dist/public-types.d.ts.map +1 -1
  66. package/dist/registry/tool/execute.d.ts +3 -0
  67. package/dist/registry/tool/execute.d.ts.map +1 -1
  68. package/dist/registry/tool/execute.js +11 -80
  69. package/dist/registry/tool/execute.js.map +1 -1
  70. package/dist/run/command-gate.d.ts.map +1 -1
  71. package/dist/run/command-gate.js +65 -12
  72. package/dist/run/command-gate.js.map +1 -1
  73. package/dist/run/index.d.ts +2 -0
  74. package/dist/run/index.d.ts.map +1 -1
  75. package/dist/run/index.js +1 -0
  76. package/dist/run/index.js.map +1 -1
  77. package/dist/run/memory-promoter.d.ts +3 -35
  78. package/dist/run/memory-promoter.d.ts.map +1 -1
  79. package/dist/run/memory-promoter.js +32 -40
  80. package/dist/run/memory-promoter.js.map +1 -1
  81. package/dist/run/memory-recall.d.ts +25 -0
  82. package/dist/run/memory-recall.d.ts.map +1 -0
  83. package/dist/run/memory-recall.js +228 -0
  84. package/dist/run/memory-recall.js.map +1 -0
  85. package/dist/run/reporter.d.ts.map +1 -1
  86. package/dist/run/reporter.js +1 -0
  87. package/dist/run/reporter.js.map +1 -1
  88. package/dist/run/token-budget.d.ts +2 -0
  89. package/dist/run/token-budget.d.ts.map +1 -1
  90. package/dist/run/token-budget.js +4 -0
  91. package/dist/run/token-budget.js.map +1 -1
  92. package/dist/run/workspace-fingerprint.d.ts +10 -3
  93. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  94. package/dist/run/workspace-fingerprint.js +17 -8
  95. package/dist/run/workspace-fingerprint.js.map +1 -1
  96. package/dist/runtime/query/checkpoint.d.ts +14 -0
  97. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  98. package/dist/runtime/query/checkpoint.js +82 -1
  99. package/dist/runtime/query/checkpoint.js.map +1 -1
  100. package/dist/runtime/query/executor.d.ts +5 -0
  101. package/dist/runtime/query/executor.d.ts.map +1 -1
  102. package/dist/runtime/query/executor.js +70 -14
  103. package/dist/runtime/query/executor.js.map +1 -1
  104. package/dist/runtime/query/index.d.ts +5 -8
  105. package/dist/runtime/query/index.d.ts.map +1 -1
  106. package/dist/runtime/query/index.js +54 -7
  107. package/dist/runtime/query/index.js.map +1 -1
  108. package/dist/runtime/query/iteration/index.d.ts +10 -0
  109. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  110. package/dist/runtime/query/iteration/index.js +325 -36
  111. package/dist/runtime/query/iteration/index.js.map +1 -1
  112. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  113. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  114. package/dist/runtime/query/iteration/native-output.js +32 -0
  115. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  116. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  117. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  118. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  119. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  120. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  121. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  122. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  124. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  127. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  128. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  130. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  132. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  133. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/stream-turn.js +2 -0
  135. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  136. package/dist/runtime/query/steering.d.ts +6 -2
  137. package/dist/runtime/query/steering.d.ts.map +1 -1
  138. package/dist/runtime/query/steering.js +17 -2
  139. package/dist/runtime/query/steering.js.map +1 -1
  140. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  141. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  142. package/dist/runtime/query/tool-call-budget.js +110 -0
  143. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  144. package/dist/runtime/query/tooling.d.ts +2 -0
  145. package/dist/runtime/query/tooling.d.ts.map +1 -1
  146. package/dist/runtime/query/tooling.js +4 -0
  147. package/dist/runtime/query/tooling.js.map +1 -1
  148. package/dist/scheduler/local.d.ts.map +1 -1
  149. package/dist/scheduler/local.js +1 -0
  150. package/dist/scheduler/local.js.map +1 -1
  151. package/dist/session/handoff/capacity.d.ts +2 -2
  152. package/dist/session/handoff/capacity.d.ts.map +1 -1
  153. package/dist/session/handoff/capacity.js +3 -2
  154. package/dist/session/handoff/capacity.js.map +1 -1
  155. package/dist/store/memory/disk.d.ts +6 -2
  156. package/dist/store/memory/disk.d.ts.map +1 -1
  157. package/dist/store/memory/disk.js +42 -25
  158. package/dist/store/memory/disk.js.map +1 -1
  159. package/dist/store/memory/index.d.ts +4 -0
  160. package/dist/store/memory/index.d.ts.map +1 -1
  161. package/dist/store/memory/index.js +55 -19
  162. package/dist/store/memory/index.js.map +1 -1
  163. package/dist/store/memory/memory.d.ts +3 -2
  164. package/dist/store/memory/memory.d.ts.map +1 -1
  165. package/dist/store/memory/memory.js +11 -2
  166. package/dist/store/memory/memory.js.map +1 -1
  167. package/dist/store/memory/operation-lock.d.ts +11 -0
  168. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  169. package/dist/store/memory/operation-lock.js +94 -0
  170. package/dist/store/memory/operation-lock.js.map +1 -0
  171. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  172. package/dist/tools/builtins/computer-use.js +60 -10
  173. package/dist/tools/builtins/computer-use.js.map +1 -1
  174. package/dist/tools/builtins/run-code.d.ts +6 -0
  175. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  176. package/dist/tools/builtins/run-code.js +15 -4
  177. package/dist/tools/builtins/run-code.js.map +1 -1
  178. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  179. package/dist/tools/builtins/search-tools.js +14 -2
  180. package/dist/tools/builtins/search-tools.js.map +1 -1
  181. package/dist/tools/defineTool.d.ts +2 -0
  182. package/dist/tools/defineTool.d.ts.map +1 -1
  183. package/dist/tools/defineTool.js +3 -0
  184. package/dist/tools/defineTool.js.map +1 -1
  185. package/dist/tools/memory/delete.d.ts +4 -0
  186. package/dist/tools/memory/delete.d.ts.map +1 -0
  187. package/dist/tools/memory/delete.js +26 -0
  188. package/dist/tools/memory/delete.js.map +1 -0
  189. package/dist/tools/memory/index.d.ts +2 -0
  190. package/dist/tools/memory/index.d.ts.map +1 -1
  191. package/dist/tools/memory/index.js +6 -0
  192. package/dist/tools/memory/index.js.map +1 -1
  193. package/dist/tools/memory/save.d.ts.map +1 -1
  194. package/dist/tools/memory/save.js +3 -2
  195. package/dist/tools/memory/save.js.map +1 -1
  196. package/dist/tools/memory/search.d.ts.map +1 -1
  197. package/dist/tools/memory/search.js +12 -5
  198. package/dist/tools/memory/search.js.map +1 -1
  199. package/dist/tools/memory/update.d.ts +4 -0
  200. package/dist/tools/memory/update.d.ts.map +1 -0
  201. package/dist/tools/memory/update.js +47 -0
  202. package/dist/tools/memory/update.js.map +1 -0
  203. package/dist/types/agent/scheduler.d.ts +5 -0
  204. package/dist/types/agent/scheduler.d.ts.map +1 -1
  205. package/dist/types/agent/task.d.ts +10 -0
  206. package/dist/types/agent/task.d.ts.map +1 -1
  207. package/dist/types/computer-use/index.d.ts +6 -0
  208. package/dist/types/computer-use/index.d.ts.map +1 -1
  209. package/dist/types/computer-use/index.js.map +1 -1
  210. package/dist/types/errors/catalog.js +1 -1
  211. package/dist/types/errors/catalog.js.map +1 -1
  212. package/dist/types/hitl/index.d.ts +11 -1
  213. package/dist/types/hitl/index.d.ts.map +1 -1
  214. package/dist/types/hitl/index.js.map +1 -1
  215. package/dist/types/memory/index.d.ts +14 -1
  216. package/dist/types/memory/index.d.ts.map +1 -1
  217. package/dist/types/project/entity.d.ts +1 -0
  218. package/dist/types/project/entity.d.ts.map +1 -1
  219. package/dist/types/provider/config.d.ts +2 -0
  220. package/dist/types/provider/config.d.ts.map +1 -1
  221. package/dist/types/provider/model.d.ts +4 -0
  222. package/dist/types/provider/model.d.ts.map +1 -1
  223. package/dist/types/run/answer-review.d.ts +2 -0
  224. package/dist/types/run/answer-review.d.ts.map +1 -1
  225. package/dist/types/run/events.d.ts +9 -0
  226. package/dist/types/run/events.d.ts.map +1 -1
  227. package/dist/types/run/events.js.map +1 -1
  228. package/dist/types/run/prepare-step.d.ts +19 -1
  229. package/dist/types/run/prepare-step.d.ts.map +1 -1
  230. package/dist/types/structured-output/index.d.ts +8 -2
  231. package/dist/types/structured-output/index.d.ts.map +1 -1
  232. package/dist/types/tool/index.d.ts +15 -0
  233. package/dist/types/tool/index.d.ts.map +1 -1
  234. package/dist/types/tool/index.js.map +1 -1
  235. package/dist/utils/json-snapshot.d.ts +3 -0
  236. package/dist/utils/json-snapshot.d.ts.map +1 -0
  237. package/dist/utils/json-snapshot.js +79 -0
  238. package/dist/utils/json-snapshot.js.map +1 -0
  239. package/package.json +4 -2
  240. package/src/bridge/a2a/mapper.ts +2 -0
  241. package/src/bridge/sse/mapper.ts +2 -0
  242. package/src/compaction/extractor.ts +21 -8
  243. package/src/compaction/manager.ts +11 -1
  244. package/src/compaction/verifier.ts +80 -18
  245. package/src/eval/harness-verification.ts +270 -0
  246. package/src/eval/index.ts +10 -0
  247. package/src/execution/code-runtime/json-value.ts +82 -0
  248. package/src/execution/code-runtime/worker-program.ts +239 -0
  249. package/src/execution/code-runtime/worker.ts +164 -111
  250. package/src/manager/agent/lifecycle.ts +340 -45
  251. package/src/manager/run/persistence.ts +7 -0
  252. package/src/provider/capabilities.ts +25 -1
  253. package/src/provider/fallback.ts +2 -0
  254. package/src/public-runtime.ts +4 -0
  255. package/src/public-tools.ts +5 -1
  256. package/src/public-types.ts +1 -0
  257. package/src/registry/tool/execute.ts +17 -79
  258. package/src/run/command-gate.ts +61 -13
  259. package/src/run/index.ts +2 -0
  260. package/src/run/memory-promoter.ts +32 -40
  261. package/src/run/memory-recall.ts +254 -0
  262. package/src/run/reporter.ts +1 -0
  263. package/src/run/token-budget.ts +4 -0
  264. package/src/run/workspace-fingerprint.ts +17 -8
  265. package/src/runtime/query/checkpoint.ts +96 -2
  266. package/src/runtime/query/executor.ts +77 -14
  267. package/src/runtime/query/index.ts +73 -16
  268. package/src/runtime/query/iteration/index.ts +377 -44
  269. package/src/runtime/query/iteration/native-output.ts +35 -0
  270. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  271. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  272. package/src/runtime/query/iteration/phases/context.ts +9 -0
  273. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  274. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  275. package/src/runtime/query/iteration/stream-turn.ts +2 -0
  276. package/src/runtime/query/steering.ts +19 -2
  277. package/src/runtime/query/tool-call-budget.ts +115 -0
  278. package/src/runtime/query/tooling.ts +6 -0
  279. package/src/scheduler/local.ts +1 -0
  280. package/src/session/handoff/capacity.ts +7 -3
  281. package/src/store/memory/disk.ts +52 -30
  282. package/src/store/memory/index.ts +75 -27
  283. package/src/store/memory/memory.ts +18 -6
  284. package/src/store/memory/operation-lock.ts +99 -0
  285. package/src/tools/builtins/computer-use.ts +61 -7
  286. package/src/tools/builtins/run-code.ts +24 -5
  287. package/src/tools/builtins/search-tools.ts +18 -3
  288. package/src/tools/defineTool.ts +5 -0
  289. package/src/tools/memory/delete.ts +29 -0
  290. package/src/tools/memory/index.ts +6 -0
  291. package/src/tools/memory/save.ts +4 -2
  292. package/src/tools/memory/search.ts +12 -5
  293. package/src/tools/memory/update.ts +50 -0
  294. package/src/types/agent/scheduler.ts +6 -0
  295. package/src/types/agent/task.ts +13 -0
  296. package/src/types/computer-use/index.ts +6 -0
  297. package/src/types/errors/catalog.ts +1 -1
  298. package/src/types/hitl/index.ts +11 -1
  299. package/src/types/memory/index.ts +16 -1
  300. package/src/types/project/entity.ts +1 -0
  301. package/src/types/provider/config.ts +2 -0
  302. package/src/types/provider/model.ts +4 -0
  303. package/src/types/run/answer-review.ts +2 -0
  304. package/src/types/run/events.ts +10 -0
  305. package/src/types/run/prepare-step.ts +19 -1
  306. package/src/types/structured-output/index.ts +15 -2
  307. package/src/types/tool/index.ts +16 -0
  308. package/src/utils/json-snapshot.ts +76 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * A hash of everything a run could have changed in its working tree.
2
+ * A change detector for Git-visible workspace state.
3
3
  *
4
4
  * It exists to answer one question, asked between two attempts at the same
5
5
  * verification: **did anything happen since it last failed?** A verify-then-fix
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * ## What is hashed, and why each part
12
12
  *
13
- * Three sources, because no one of them is complete:
13
+ * Four sources, because no one of them is complete:
14
14
  *
15
15
  * 1. **`git status --porcelain`** — which paths differ from the index at all.
16
16
  * Cheap, and it catches additions, deletions and mode changes. On its own
@@ -23,6 +23,13 @@
23
23
  * 3. **Untracked file contents**, which no `git diff` covers. A new file is
24
24
  * named by `status` but its CONTENT is not, so successive edits to a
25
25
  * brand-new file would otherwise look like no change at all.
26
+ * 4. **`git rev-parse HEAD`** — the committed baseline. Two clean checkouts
27
+ * can contain different code while all three sources above are empty.
28
+ * Commit identity also covers verification that inspects Git history.
29
+ *
30
+ * Ignored files, external services and other command inputs are not covered.
31
+ * Reads are not an atomic snapshot. Matching hashes are an optimization hint,
32
+ * not proof that a command must produce the same result.
26
33
  *
27
34
  * ### Symlinks are recorded as their target, not read through
28
35
  *
@@ -131,7 +138,7 @@ async function untrackedEntry(cwd: string, rel: string, fs: FingerprintFs): Prom
131
138
  }
132
139
 
133
140
  /**
134
- * A hash of the working tree's uncommitted state, or `null` when it cannot be
141
+ * A hash of the commit and Git-visible working state, or `null` when it cannot be
135
142
  * established.
136
143
  *
137
144
  * **`null` is never "unchanged".** It means "I cannot tell", and the caller
@@ -152,15 +159,17 @@ export async function fingerprintWorkspace(
152
159
  } catch {
153
160
  return null
154
161
  }
155
- // A timeout surfaces here as a non-zero exit, and so does "not a
156
- // repository" and "no commits yet". All three mean the same thing to
157
- // this function: it has no basis for a comparison.
158
- if (result.exitCode !== 0) return null
162
+ // A terminated process may handle its signal and exit zero. Its
163
+ // retained output still cannot establish a complete fingerprint.
164
+ if (result.exitCode !== 0 || result.termination !== undefined) return null
159
165
  if (result.stdoutTruncated === true || result.stderrTruncated === true) return null
160
166
  if (Buffer.byteLength(result.stdout, 'utf8') > maxBytes) return null
161
167
  return result.stdout
162
168
  }
163
169
 
170
+ const head = await git(['rev-parse', '--verify', 'HEAD'])
171
+ if (head === null) return null
172
+
164
173
  const status = await git(['status', '--porcelain'])
165
174
  if (status === null) return null
166
175
 
@@ -170,7 +179,7 @@ export async function fingerprintWorkspace(
170
179
  const untracked = await git(['ls-files', '--others', '--exclude-standard', '-z'])
171
180
  if (untracked === null) return null
172
181
 
173
- const parts = [`status ${status}`, `diff ${diff}`]
182
+ const parts = [`head ${head}`, `status ${status}`, `diff ${diff}`]
174
183
  // Split on NUL, which is what `-z` is for: a path may contain a newline,
175
184
  // and splitting on one would turn a single strange filename into two
176
185
  // ordinary-looking ones.
@@ -10,7 +10,7 @@ import type {
10
10
  IterationCheckpoint,
11
11
  PendingDecision,
12
12
  } from '../../types/hitl/index.js'
13
- import type { AssistantMessage } from '../../types/message/index.js'
13
+ import type { AssistantMessage, UserMessage } from '../../types/message/index.js'
14
14
  import type {
15
15
  CheckpointRunScope,
16
16
  CheckpointStore,
@@ -20,7 +20,53 @@ import type { EmergencySaveData } from '../../types/run/emergency.js'
20
20
  import type { CheckpointListEntry } from '../../types/run/replay.js'
21
21
  import { ZERO_COST } from '../../utils/cost.js'
22
22
  import { buildToolResultHashes } from '../../utils/hash.js'
23
- import { asCheckpointId, asEmergencySaveId, generateCheckpointId } from '../../utils/id.js'
23
+ import {
24
+ asCheckpointId,
25
+ asEmergencySaveId,
26
+ asGoalId,
27
+ generateCheckpointId,
28
+ } from '../../utils/id.js'
29
+
30
+ /** Keep intent text/provenance without copying attachment payloads into a second slot. */
31
+ function snapshotUserIntent(value: unknown): UserMessage {
32
+ const invalid = (): never => {
33
+ throw new Error('Checkpoint latestUserMessage has invalid operator intent or provenance')
34
+ }
35
+ if (!value || typeof value !== 'object') return invalid()
36
+ const message = value as Record<string, unknown>
37
+ if (message.role !== 'user' || typeof message.content !== 'string') return invalid()
38
+ const result: UserMessage = { role: 'user', content: message.content }
39
+ if (message.timestamp !== undefined) {
40
+ if (typeof message.timestamp !== 'number' || !Number.isFinite(message.timestamp))
41
+ return invalid()
42
+ result.timestamp = message.timestamp
43
+ }
44
+ if (message.source === undefined) return result
45
+ if (!message.source || typeof message.source !== 'object') return invalid()
46
+ const source = message.source as Record<string, unknown>
47
+ if (source.type === 'runtime-context' && source.kind === 'steering') {
48
+ result.source = { type: 'runtime-context', kind: 'steering' }
49
+ return result
50
+ }
51
+ if (
52
+ source.type !== 'goal-round' ||
53
+ typeof source.goalId !== 'string' ||
54
+ typeof source.objective !== 'string' ||
55
+ ![source.goalRevision, source.round, source.maxGoalRounds].every(
56
+ (value) => typeof value === 'number' && Number.isInteger(value) && value > 0,
57
+ )
58
+ )
59
+ return invalid()
60
+ result.source = {
61
+ type: 'goal-round',
62
+ goalId: asGoalId(source.goalId),
63
+ objective: source.objective,
64
+ goalRevision: source.goalRevision as number,
65
+ round: source.round as number,
66
+ maxGoalRounds: source.maxGoalRounds as number,
67
+ }
68
+ return result
69
+ }
24
70
 
25
71
  /**
26
72
  * Projection from a full checkpoint payload to the public listing entry.
@@ -166,6 +212,12 @@ export class CheckpointManager {
166
212
  * activity, and the record of everything before the resume is gone.
167
213
  */
168
214
  private workingStateSource?: () => WorkingStateSnapshot | undefined
215
+ private latestUserMessageSource?: () => UserMessage | undefined
216
+ private restoredUserMessage?: UserMessage
217
+ private structuredReviewAttemptsSource?: () => number
218
+ private restoredReviewAttempts = 0
219
+ private nativeStructuredAttemptsSource?: () => number
220
+ private restoredNativeAttempts = 0
169
221
 
170
222
  /**
171
223
  * The most recent checkpoint this manager wrote, if any.
@@ -237,6 +289,30 @@ export class CheckpointManager {
237
289
  this.workingStateSource = source
238
290
  }
239
291
 
292
+ /** One current intent snapshot, including checkpoints created by tool/HITL paths. */
293
+ setLatestUserMessageSource(source: () => UserMessage | undefined): void {
294
+ this.latestUserMessageSource = source
295
+ }
296
+
297
+ get restoredLatestUserMessage(): UserMessage | undefined {
298
+ return this.restoredUserMessage
299
+ }
300
+
301
+ setNativeStructuredAttemptsSource(source: () => number): void {
302
+ this.nativeStructuredAttemptsSource = source
303
+ }
304
+ get restoredNativeStructuredAttempts(): number {
305
+ return this.restoredNativeAttempts
306
+ }
307
+
308
+ setStructuredReviewAttemptsSource(source: () => number): void {
309
+ this.structuredReviewAttemptsSource = source
310
+ }
311
+
312
+ get restoredStructuredReviewAttempts(): number {
313
+ return this.restoredReviewAttempts
314
+ }
315
+
240
316
  /**
241
317
  * The run's root span, so every checkpoint records the trace it was
242
318
  * taken inside and a resume can join it rather than starting a second,
@@ -260,6 +336,7 @@ export class CheckpointManager {
260
336
  // fact, and naming it after the earlier one would make it wrong in
261
337
  // exactly the way that is hard to notice.
262
338
  this.runCreatedAt ??= runMgr.getSession().startedAt ?? Date.now()
339
+ const latestUserMessage = this.latestUserMessageSource?.() ?? this.restoredUserMessage
263
340
 
264
341
  const checkpoint: IterationCheckpoint = {
265
342
  id: generateCheckpointId(),
@@ -267,6 +344,11 @@ export class CheckpointManager {
267
344
  runCreatedAt: this.runCreatedAt,
268
345
  iteration,
269
346
  messages: [...runMgr.messages],
347
+ nativeStructuredAttempts:
348
+ this.nativeStructuredAttemptsSource?.() ?? this.restoredNativeAttempts,
349
+ structuredReviewAttempts:
350
+ this.structuredReviewAttemptsSource?.() ?? this.restoredReviewAttempts,
351
+ ...(latestUserMessage ? { latestUserMessage: snapshotUserIntent(latestUserMessage) } : {}),
270
352
  tokenUsage: { ...runMgr.tokenUsage },
271
353
  budgetBinding: runMgr.budget?.binding,
272
354
  budgetAccountId: runMgr.budget?.accountId,
@@ -457,6 +539,18 @@ export class CheckpointManager {
457
539
  // its origin's age. A guard that no input can trip would have read as
458
540
  // protection and been none.
459
541
  this.runCreatedAt ??= checkpoint.runCreatedAt
542
+ const nativeAttempts = checkpoint.nativeStructuredAttempts ?? 0
543
+ if (!Number.isSafeInteger(nativeAttempts) || nativeAttempts < 0)
544
+ throw new Error('Invalid nativeStructuredAttempts in checkpoint')
545
+ this.restoredNativeAttempts = nativeAttempts
546
+ const reviewAttempts = checkpoint.structuredReviewAttempts ?? 0
547
+ if (!Number.isSafeInteger(reviewAttempts) || reviewAttempts < 0)
548
+ throw new Error('Checkpoint structuredReviewAttempts must be a nonnegative safe integer')
549
+ this.restoredReviewAttempts = reviewAttempts
550
+ this.restoredUserMessage =
551
+ checkpoint.latestUserMessage === undefined
552
+ ? undefined
553
+ : snapshotUserIntent(checkpoint.latestUserMessage)
460
554
 
461
555
  return checkpoint
462
556
  }
@@ -50,6 +50,7 @@ import type { Logger } from '../../utils/logger.js'
50
50
  import { compressShellOutput } from '../../utils/shell-compress.js'
51
51
  import { type BackgroundJobRegistry, type JobProcess, bindOwner } from '../jobs/registry.js'
52
52
  import type { ToolResultObservation } from './project-instructions.js'
53
+ import { ToolCallBudget, assertMaxToolCalls } from './tool-call-budget.js'
53
54
  import {
54
55
  DEFAULT_MAX_TOOL_OUTPUT_CHARS,
55
56
  type ToolOutputBudgetResult,
@@ -356,6 +357,10 @@ export interface ToolExecutorConfig {
356
357
  toolRetryBackoff?: Partial<BackoffPolicy>
357
358
  /** Max concurrently-executing concurrency-safe tools. */
358
359
  maxToolConcurrency?: number
360
+ /** Per-run cumulative attempt admission limit; unset is unlimited. */
361
+ maxToolCalls?: number
362
+ /** Complete strict event replay for restoring a configured call budget. */
363
+ readToolCallBudgetEvents?: () => Promise<readonly RunEvent[]>
359
364
 
360
365
  /**
361
366
  * Builds the durable-pause seam handed to one tool call.
@@ -492,6 +497,7 @@ export class ToolExecutor {
492
497
  private workingStateManager?: WorkingStateManager
493
498
  private probes: ProbeEnforcement
494
499
  private parentSpan?: Span
500
+ private readonly toolCallBudget?: ToolCallBudget
495
501
  private readonly preparedBatches = new WeakSet<PreparedToolBatch>()
496
502
  /** Set per turn by the orchestrator; see {@link setStepAllowedTools}. */
497
503
  private stepAllowedTools?: readonly string[]
@@ -517,6 +523,15 @@ export class ToolExecutor {
517
523
  log: Logger,
518
524
  probes: ProbeEnforcement = defaultProbeRegistry,
519
525
  ) {
526
+ assertMaxToolCalls(config.maxToolCalls)
527
+ if (config.maxToolCalls !== undefined) {
528
+ this.toolCallBudget = new ToolCallBudget(
529
+ config.maxToolCalls,
530
+ config.runId,
531
+ emitEvent,
532
+ config.readToolCallBudgetEvents,
533
+ )
534
+ }
520
535
  this.config = config
521
536
  this.activityStore = activityStore
522
537
  this.emitEvent = emitEvent
@@ -730,6 +745,18 @@ export class ToolExecutor {
730
745
  if (owned && !this.preparedBatches.has(owned)) {
731
746
  throw new Error('Prepared tool batch is not owned by this executor.')
732
747
  }
748
+ const refusal = this.toolCallBudget
749
+ ? await this.toolCallBudget.admit(
750
+ toolCalls.filter((call) => prior?.get(call.id) === undefined).length,
751
+ 'batch',
752
+ this.config.abortSignal,
753
+ )
754
+ : undefined
755
+ if (refusal) {
756
+ const refused = new Map(denials)
757
+ for (const call of toolCalls) if (!refused.has(call.id)) refused.set(call.id, refusal)
758
+ return await this.runBatch(toolCalls, refused, prior, owned)
759
+ }
733
760
  return await this.runBatch(toolCalls, denials, prior, owned)
734
761
  } finally {
735
762
  // Cleared so a later single execution outside a batch resolves
@@ -779,6 +806,24 @@ export class ToolExecutor {
779
806
  const results: ToolCallOutcome[] = new Array(toolCalls.length)
780
807
  const parallel: Promise<void>[] = []
781
808
  let serial: Promise<void> = Promise.resolve()
809
+ let barrier: Promise<void> | undefined
810
+ const schedule = (run: () => Promise<void>, safe: boolean, isBarrier: boolean): void => {
811
+ if (isBarrier) {
812
+ // Settle the entire preceding segment, even when host plumbing
813
+ // rejects. A rejected host operation prevents dependent execution.
814
+ barrier = Promise.allSettled([...parallel, serial]).then(async (settled) => {
815
+ const failed = settled.find((entry) => entry.status === 'rejected')
816
+ if (failed?.status === 'rejected') throw failed.reason
817
+ await run()
818
+ })
819
+ parallel.length = 0
820
+ serial = barrier
821
+ } else if (safe) {
822
+ parallel.push(barrier ? barrier.then(run) : run())
823
+ } else {
824
+ serial = serial.then(run)
825
+ }
826
+ }
782
827
  // Bounded fan-out. A model emitting fifty parallel reads used to open
783
828
  // fifty file handles and fifty activity records simultaneously; the
784
829
  // gate keeps that at a working-set size while preserving completion
@@ -786,6 +831,8 @@ export class ToolExecutor {
786
831
  const gate = new Semaphore(this.config.maxToolConcurrency ?? DEFAULT_TOOL_CONCURRENCY)
787
832
  toolCalls.forEach((toolCall, i) => {
788
833
  const preparedCall = preparedBatch?.calls.get(toolCall.id)
834
+ const tool = this.config.tools.get(preparedCall?.toolName ?? toolCall.function.name)
835
+ const isBarrier = tool?.executionBarrier === true
789
836
  const recovered = prior?.get(toolCall.id)
790
837
  if (recovered !== undefined) {
791
838
  // This call already ran, in a process that died before the
@@ -798,19 +845,21 @@ export class ToolExecutor {
798
845
  output: recovered.result,
799
846
  isError: recovered.isError,
800
847
  }
848
+ if (isBarrier) schedule(async () => {}, true, true)
801
849
  return
802
850
  }
803
851
 
804
852
  const denialReason = denials?.get(toolCall.id)
805
853
  if (denialReason !== undefined) {
806
854
  // Denied calls never touch the tool; they still get a result
807
- // message so the assistant turn stays fully answered. Run them
808
- // on the parallel branch — they perform no side effects, so
809
- // serialization would only add latency.
810
- parallel.push(
811
- this.recordDenial(toolCall, denialReason, preparedCall).then((r) => {
812
- results[i] = r
813
- }),
855
+ // message so the assistant turn stays fully answered. They can
856
+ // overlap other calls within a segment, respecting its barriers.
857
+ schedule(
858
+ async () => {
859
+ results[i] = await this.recordDenial(toolCall, denialReason, preparedCall)
860
+ },
861
+ true,
862
+ isBarrier,
814
863
  )
815
864
  return
816
865
  }
@@ -868,13 +917,8 @@ export class ToolExecutor {
868
917
  } catch {
869
918
  // non-JSON args → treat as unsafe (serialize), the conservative path
870
919
  }
871
- const preparedToolName = preparedCall?.toolName
872
- const safe =
873
- this.config.tools
874
- .get(preparedToolName ?? toolCall.function.name)
875
- ?.isConcurrencySafe?.(input) === true
876
- if (safe) parallel.push(gated())
877
- else serial = serial.then(run)
920
+ const safe = tool?.isConcurrencySafe?.(input) === true
921
+ schedule(safe ? gated : run, safe, isBarrier)
878
922
  })
879
923
  await Promise.all([...parallel, serial])
880
924
 
@@ -942,8 +986,14 @@ export class ToolExecutor {
942
986
  // retained closure must be observationally inert after its invocation
943
987
  // ends, not merely unable to finish the registry call it already started.
944
988
  signal.throwIfAborted()
989
+ // Preparation detaches input synchronously before its first await.
990
+ // Admission must not add an earlier yield where the caller can mutate it.
945
991
  const preparedCall = await this.prepareNestedCall(name, input, signal)
946
992
  signal.throwIfAborted()
993
+ if (this.toolCallBudget) {
994
+ const refusal = await this.toolCallBudget.admit(1, 'nested', signal)
995
+ if (refusal) return { success: false, output: '', error: refusal }
996
+ }
947
997
  const preparedInput = preparedCall.input
948
998
 
949
999
  const parent = context.toolUseId
@@ -1550,6 +1600,19 @@ export class ToolExecutor {
1550
1600
  break
1551
1601
  }
1552
1602
 
1603
+ let refusal: string | undefined
1604
+ try {
1605
+ if (this.toolCallBudget)
1606
+ refusal = await this.toolCallBudget.admit(1, 'retry', this.config.abortSignal)
1607
+ } catch (error) {
1608
+ if (this.config.abortSignal.aborted) break
1609
+ throw error
1610
+ }
1611
+ if (refusal) {
1612
+ result = { success: false, output: '', error: refusal }
1613
+ post = { override: null, retry: false }
1614
+ break
1615
+ }
1553
1616
  result = await this.runOnce(toolName, input, toolContext, prepared)
1554
1617
  post = await this.runPostToolHook(toolName, input, result)
1555
1618
  }
@@ -149,8 +149,9 @@ import {
149
149
  resolveSandboxTeardownTimeoutMs,
150
150
  teardownSandbox,
151
151
  } from './sandbox-lifecycle.js'
152
- import { SteeringBinding, type SteeringChannel } from './steering.js'
152
+ import { SteeringBinding, type SteeringChannel, isOperatorUserMessage } from './steering.js'
153
153
  import { resolveQueryBudget } from './token-budget.js'
154
+ import { assertMaxToolCalls } from './tool-call-budget.js'
154
155
  import { ToolGrantSet } from './tool-grants.js'
155
156
  import { createToolPause } from './tool-pause.js'
156
157
  import { ToolingBootstrap } from './tooling.js'
@@ -349,6 +350,8 @@ export interface QueryParams {
349
350
 
350
351
  /** Max concurrently-executing concurrency-safe tools in one batch. */
351
352
  maxToolConcurrency?: number
353
+ /** Per-run cumulative tool attempt limit, including nested calls and retries. Unset is unlimited. Re-supply on resume; durable reservations are never refunded. */
354
+ maxToolCalls?: number
352
355
 
353
356
  /**
354
357
  * Model-visible size cap for a single tool result. Over-budget output is
@@ -450,14 +453,9 @@ export interface QueryParams {
450
453
  beforeStep?: BeforeStep
451
454
 
452
455
  /**
453
- * Force the run to finish by calling a schema-validated tool, and land
454
- * the parsed value on `Run.structuredOutput`.
455
- *
456
- * Both leaf pieces already shipped and neither was reachable:
457
- * `createStructuredOutputTool` is excluded from the default builtin set,
458
- * and `StructuredOutputConfig` had no field on QueryParams at all. A host
459
- * needing a typed result had to register the tool by hand and hope —
460
- * nothing forced the call, and nothing stopped the loop when it came.
456
+ * Produce a locally validated structured result. Defaults to an output tool;
457
+ * mode:'native' requests JSON Schema from an explicitly capable driver.
458
+ * Host review and bounded corrections apply before publication.
461
459
  */
462
460
  structuredOutput?: StructuredOutputConfig
463
461
 
@@ -1027,6 +1025,19 @@ function withoutOwnedResumeTurn(
1027
1025
  }
1028
1026
 
1029
1027
  export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run> {
1028
+ assertMaxToolCalls(params.maxToolCalls)
1029
+ // Required types do not protect JavaScript callers. Reject missing scope
1030
+ // before opening a budget or persisting a run without its owning identity.
1031
+ const missingFields = (['sessionId', 'topicId', 'projectId', 'tenantId'] as const).filter(
1032
+ (field) => !params[field],
1033
+ )
1034
+ if (missingFields.length > 0) {
1035
+ throw new NamzuError({
1036
+ code: 'invalid_config',
1037
+ message: `query requires sessionId, topicId, projectId, and tenantId; missing: ${missingFields.join(', ')}.`,
1038
+ details: { missingFields },
1039
+ })
1040
+ }
1030
1041
  const selectedResumeState = selectedResumeStates.get(params)
1031
1042
  selectedResumeStates.delete(params)
1032
1043
  // Resolved at the DOOR, before a run id exists or a logger is built.
@@ -1154,6 +1165,8 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
1154
1165
  runConfig.timeoutMs,
1155
1166
  log,
1156
1167
  )
1168
+ const modelContextWindows = new Map<string, number | undefined>()
1169
+ if (runConfig.model) modelContextWindows.set(runConfig.model, providerContextWindow)
1157
1170
 
1158
1171
  // The mode this conversation was left in, when the run config names none.
1159
1172
  // Read once, before the loop exists, for the same reason the context
@@ -1556,7 +1569,11 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
1556
1569
  // invalidate the whole prompt cache for the rest of the run — the same
1557
1570
  // reason the forced-final turn keeps its tools array and uses
1558
1571
  // `toolChoice: 'none'` instead of dropping it.
1559
- if (params.structuredOutput && !params.tools.has(STRUCTURED_OUTPUT_TOOL_NAME)) {
1572
+ if (
1573
+ params.structuredOutput &&
1574
+ params.structuredOutput.mode !== 'native' &&
1575
+ !params.tools.has(STRUCTURED_OUTPUT_TOOL_NAME)
1576
+ ) {
1560
1577
  params.tools.register(createStructuredOutputTool(params.structuredOutput.schema))
1561
1578
  }
1562
1579
 
@@ -1718,6 +1735,13 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
1718
1735
  ...(params.toolRetryBackoff !== undefined
1719
1736
  ? { toolRetryBackoff: params.toolRetryBackoff }
1720
1737
  : {}),
1738
+ ...(params.maxToolCalls !== undefined
1739
+ ? {
1740
+ maxToolCalls: params.maxToolCalls,
1741
+ readToolCallBudgetEvents: () =>
1742
+ ctx.runMgr.getRunStore().readEvents({ integrity: 'strict' }),
1743
+ }
1744
+ : {}),
1721
1745
  ...(params.maxToolConcurrency !== undefined
1722
1746
  ? { maxToolConcurrency: params.maxToolConcurrency }
1723
1747
  : {}),
@@ -1970,11 +1994,27 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
1970
1994
  ...(params.repeatCallAdvisory === false ? {} : { repeatCalls: new RepeatCallTracker() }),
1971
1995
  compactionConfig,
1972
1996
  ...(params.inboundMessages ? { inboundMessages: params.inboundMessages } : {}),
1997
+ ...(params.resumeFromCheckpoint ? { resumedInput: queuedForThisRun } : {}),
1973
1998
  ...(params.waitForInbound ? { waitForInbound: params.waitForInbound } : {}),
1974
1999
  ...(params.projectInstructionContext
1975
2000
  ? { projectInstructionContext: params.projectInstructionContext }
1976
2001
  : {}),
1977
2002
  ...(providerContextWindow !== undefined ? { providerContextWindow } : {}),
2003
+ resolveModelContextWindow: async (model) => {
2004
+ if (!modelContextWindows.has(model)) {
2005
+ modelContextWindows.set(
2006
+ model,
2007
+ await resolveProviderContextWindow(
2008
+ resilientProvider,
2009
+ model,
2010
+ ctx.abortController.signal,
2011
+ runConfig.timeoutMs,
2012
+ log,
2013
+ ),
2014
+ )
2015
+ }
2016
+ return modelContextWindows.get(model)
2017
+ },
1978
2018
  workingStateManager,
1979
2019
  taskRouter: params.taskRouter,
1980
2020
  contextReducer: params.contextReducer,
@@ -2333,7 +2373,6 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
2333
2373
  }
2334
2374
  } else {
2335
2375
  pushSystemMessages()
2336
- let isFirstUserMessage = true
2337
2376
  for (const msg of initialMessages) {
2338
2377
  if (msg.role === 'system') {
2339
2378
  // A fresh run rebuilds its current static/dynamic prompt above,
@@ -2351,11 +2390,18 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
2351
2390
  continue
2352
2391
  }
2353
2392
  ctx.runMgr.pushMessage(msg)
2354
-
2355
- if (workingStateManager && msg.role === 'user' && msg.content) {
2356
- extractFromUserMessage(workingStateManager, msg.content, isFirstUserMessage)
2357
- isFirstUserMessage = false
2358
- }
2393
+ }
2394
+ }
2395
+ // Fresh and continuation histories seed the same operator state.
2396
+ // A worker report or project-policy message may have role `user`,
2397
+ // but it must not become the task or an operator requirement.
2398
+ // Resume restores this state and adopts only its newer queue arrivals.
2399
+ if (!params.resumeFromCheckpoint && workingStateManager) {
2400
+ let isFirstUserMessage = true
2401
+ for (const message of initialMessages) {
2402
+ if (!isOperatorUserMessage(message) || !message.content.trim()) continue
2403
+ extractFromUserMessage(workingStateManager, message.content, isFirstUserMessage)
2404
+ isFirstUserMessage = false
2359
2405
  }
2360
2406
  }
2361
2407
 
@@ -2713,6 +2759,16 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
2713
2759
  ctx.log,
2714
2760
  )
2715
2761
 
2762
+ if (outputVerdict.blocked || outputVerdict.rewritten !== undefined) {
2763
+ ctx.runMgr.clearStructuredOutput()
2764
+ if (
2765
+ params.structuredOutput &&
2766
+ outputVerdict.rewritten !== undefined &&
2767
+ ctx.runMgr.stopReason === 'end_turn'
2768
+ )
2769
+ ctx.runMgr.setStopReason('output_guardrail')
2770
+ }
2771
+
2716
2772
  if (outputVerdict.blocked) {
2717
2773
  await eventTranslator.emitEvent({
2718
2774
  type: 'guardrail_triggered',
@@ -2773,6 +2829,7 @@ export async function* query(params: QueryParams): AsyncGenerator<RunEvent, Run>
2773
2829
  }
2774
2830
  }
2775
2831
  }
2832
+ if (ctx.abortController.signal.aborted) ctx.runMgr.markCancelled()
2776
2833
  yield* resultAssembler.completeRun(rootSpan)
2777
2834
  } catch (err) {
2778
2835
  // A failed run still spent its steps; report them.