@namzu/sdk 35.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 (360) hide show
  1. package/CHANGELOG.md +218 -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/provider/token-budget.d.ts.map +1 -1
  57. package/dist/provider/token-budget.js +56 -8
  58. package/dist/provider/token-budget.js.map +1 -1
  59. package/dist/public-runtime.d.ts +5 -3
  60. package/dist/public-runtime.d.ts.map +1 -1
  61. package/dist/public-runtime.js +3 -2
  62. package/dist/public-runtime.js.map +1 -1
  63. package/dist/public-tools.d.ts +1 -1
  64. package/dist/public-tools.d.ts.map +1 -1
  65. package/dist/public-tools.js +1 -1
  66. package/dist/public-tools.js.map +1 -1
  67. package/dist/public-types.d.ts +1 -0
  68. package/dist/public-types.d.ts.map +1 -1
  69. package/dist/registry/tool/execute.d.ts +3 -0
  70. package/dist/registry/tool/execute.d.ts.map +1 -1
  71. package/dist/registry/tool/execute.js +11 -80
  72. package/dist/registry/tool/execute.js.map +1 -1
  73. package/dist/run/command-gate.d.ts.map +1 -1
  74. package/dist/run/command-gate.js +65 -12
  75. package/dist/run/command-gate.js.map +1 -1
  76. package/dist/run/index.d.ts +2 -0
  77. package/dist/run/index.d.ts.map +1 -1
  78. package/dist/run/index.js +1 -0
  79. package/dist/run/index.js.map +1 -1
  80. package/dist/run/memory-promoter.d.ts +3 -35
  81. package/dist/run/memory-promoter.d.ts.map +1 -1
  82. package/dist/run/memory-promoter.js +32 -40
  83. package/dist/run/memory-promoter.js.map +1 -1
  84. package/dist/run/memory-recall.d.ts +25 -0
  85. package/dist/run/memory-recall.d.ts.map +1 -0
  86. package/dist/run/memory-recall.js +228 -0
  87. package/dist/run/memory-recall.js.map +1 -0
  88. package/dist/run/reporter.d.ts.map +1 -1
  89. package/dist/run/reporter.js +1 -0
  90. package/dist/run/reporter.js.map +1 -1
  91. package/dist/run/token-budget.d.ts +2 -0
  92. package/dist/run/token-budget.d.ts.map +1 -1
  93. package/dist/run/token-budget.js +4 -0
  94. package/dist/run/token-budget.js.map +1 -1
  95. package/dist/run/workspace-fingerprint.d.ts +10 -3
  96. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  97. package/dist/run/workspace-fingerprint.js +17 -8
  98. package/dist/run/workspace-fingerprint.js.map +1 -1
  99. package/dist/runtime/query/checkpoint.d.ts +14 -0
  100. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  101. package/dist/runtime/query/checkpoint.js +82 -1
  102. package/dist/runtime/query/checkpoint.js.map +1 -1
  103. package/dist/runtime/query/executor.d.ts +6 -0
  104. package/dist/runtime/query/executor.d.ts.map +1 -1
  105. package/dist/runtime/query/executor.js +120 -24
  106. package/dist/runtime/query/executor.js.map +1 -1
  107. package/dist/runtime/query/index.d.ts +12 -8
  108. package/dist/runtime/query/index.d.ts.map +1 -1
  109. package/dist/runtime/query/index.js +55 -7
  110. package/dist/runtime/query/index.js.map +1 -1
  111. package/dist/runtime/query/iteration/index.d.ts +13 -3
  112. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  113. package/dist/runtime/query/iteration/index.js +356 -42
  114. package/dist/runtime/query/iteration/index.js.map +1 -1
  115. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  116. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  117. package/dist/runtime/query/iteration/native-output.js +32 -0
  118. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  119. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  120. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  121. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  122. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  123. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  124. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  125. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/context.d.ts +11 -0
  127. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  128. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  130. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  131. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  132. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  133. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  135. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  136. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  137. package/dist/runtime/query/iteration/stream-turn.js +12 -5
  138. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  139. package/dist/runtime/query/prompt.d.ts.map +1 -1
  140. package/dist/runtime/query/prompt.js +14 -4
  141. package/dist/runtime/query/prompt.js.map +1 -1
  142. package/dist/runtime/query/steering.d.ts +6 -2
  143. package/dist/runtime/query/steering.d.ts.map +1 -1
  144. package/dist/runtime/query/steering.js +17 -2
  145. package/dist/runtime/query/steering.js.map +1 -1
  146. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  147. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  148. package/dist/runtime/query/tool-call-budget.js +110 -0
  149. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  150. package/dist/runtime/query/tooling.d.ts +2 -0
  151. package/dist/runtime/query/tooling.d.ts.map +1 -1
  152. package/dist/runtime/query/tooling.js +4 -0
  153. package/dist/runtime/query/tooling.js.map +1 -1
  154. package/dist/sandbox/file-walk-program.d.ts +3 -0
  155. package/dist/sandbox/file-walk-program.d.ts.map +1 -0
  156. package/dist/sandbox/file-walk-program.js +94 -0
  157. package/dist/sandbox/file-walk-program.js.map +1 -0
  158. package/dist/sandbox/file-walk.d.ts +12 -0
  159. package/dist/sandbox/file-walk.d.ts.map +1 -0
  160. package/dist/sandbox/file-walk.js +429 -0
  161. package/dist/sandbox/file-walk.js.map +1 -0
  162. package/dist/sandbox/index.d.ts +2 -0
  163. package/dist/sandbox/index.d.ts.map +1 -1
  164. package/dist/sandbox/index.js +1 -0
  165. package/dist/sandbox/index.js.map +1 -1
  166. package/dist/sandbox/provider/local.d.ts.map +1 -1
  167. package/dist/sandbox/provider/local.js +9 -0
  168. package/dist/sandbox/provider/local.js.map +1 -1
  169. package/dist/scheduler/completion-inbox.d.ts +3 -2
  170. package/dist/scheduler/completion-inbox.d.ts.map +1 -1
  171. package/dist/scheduler/completion-inbox.js +29 -15
  172. package/dist/scheduler/completion-inbox.js.map +1 -1
  173. package/dist/scheduler/local.d.ts.map +1 -1
  174. package/dist/scheduler/local.js +1 -0
  175. package/dist/scheduler/local.js.map +1 -1
  176. package/dist/session/handoff/capacity.d.ts +2 -2
  177. package/dist/session/handoff/capacity.d.ts.map +1 -1
  178. package/dist/session/handoff/capacity.js +3 -2
  179. package/dist/session/handoff/capacity.js.map +1 -1
  180. package/dist/store/memory/disk.d.ts +6 -2
  181. package/dist/store/memory/disk.d.ts.map +1 -1
  182. package/dist/store/memory/disk.js +42 -25
  183. package/dist/store/memory/disk.js.map +1 -1
  184. package/dist/store/memory/index.d.ts +4 -0
  185. package/dist/store/memory/index.d.ts.map +1 -1
  186. package/dist/store/memory/index.js +55 -19
  187. package/dist/store/memory/index.js.map +1 -1
  188. package/dist/store/memory/memory.d.ts +3 -2
  189. package/dist/store/memory/memory.d.ts.map +1 -1
  190. package/dist/store/memory/memory.js +11 -2
  191. package/dist/store/memory/memory.js.map +1 -1
  192. package/dist/store/memory/operation-lock.d.ts +11 -0
  193. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  194. package/dist/store/memory/operation-lock.js +94 -0
  195. package/dist/store/memory/operation-lock.js.map +1 -0
  196. package/dist/tools/builtins/bash.d.ts.map +1 -1
  197. package/dist/tools/builtins/bash.js +215 -59
  198. package/dist/tools/builtins/bash.js.map +1 -1
  199. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  200. package/dist/tools/builtins/computer-use.js +60 -10
  201. package/dist/tools/builtins/computer-use.js.map +1 -1
  202. package/dist/tools/builtins/glob.d.ts +2 -1
  203. package/dist/tools/builtins/glob.d.ts.map +1 -1
  204. package/dist/tools/builtins/glob.js +95 -103
  205. package/dist/tools/builtins/glob.js.map +1 -1
  206. package/dist/tools/builtins/grep.d.ts.map +1 -1
  207. package/dist/tools/builtins/grep.js +155 -111
  208. package/dist/tools/builtins/grep.js.map +1 -1
  209. package/dist/tools/builtins/ls.js +2 -2
  210. package/dist/tools/builtins/ls.js.map +1 -1
  211. package/dist/tools/builtins/run-code.d.ts +6 -0
  212. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  213. package/dist/tools/builtins/run-code.js +15 -4
  214. package/dist/tools/builtins/run-code.js.map +1 -1
  215. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  216. package/dist/tools/builtins/search-tools.js +14 -2
  217. package/dist/tools/builtins/search-tools.js.map +1 -1
  218. package/dist/tools/defineTool.d.ts +2 -0
  219. package/dist/tools/defineTool.d.ts.map +1 -1
  220. package/dist/tools/defineTool.js +3 -0
  221. package/dist/tools/defineTool.js.map +1 -1
  222. package/dist/tools/memory/delete.d.ts +4 -0
  223. package/dist/tools/memory/delete.d.ts.map +1 -0
  224. package/dist/tools/memory/delete.js +26 -0
  225. package/dist/tools/memory/delete.js.map +1 -0
  226. package/dist/tools/memory/index.d.ts +2 -0
  227. package/dist/tools/memory/index.d.ts.map +1 -1
  228. package/dist/tools/memory/index.js +6 -0
  229. package/dist/tools/memory/index.js.map +1 -1
  230. package/dist/tools/memory/save.d.ts.map +1 -1
  231. package/dist/tools/memory/save.js +3 -2
  232. package/dist/tools/memory/save.js.map +1 -1
  233. package/dist/tools/memory/search.d.ts.map +1 -1
  234. package/dist/tools/memory/search.js +12 -5
  235. package/dist/tools/memory/search.js.map +1 -1
  236. package/dist/tools/memory/update.d.ts +4 -0
  237. package/dist/tools/memory/update.d.ts.map +1 -0
  238. package/dist/tools/memory/update.js +47 -0
  239. package/dist/tools/memory/update.js.map +1 -0
  240. package/dist/types/agent/scheduler.d.ts +5 -0
  241. package/dist/types/agent/scheduler.d.ts.map +1 -1
  242. package/dist/types/agent/task.d.ts +10 -0
  243. package/dist/types/agent/task.d.ts.map +1 -1
  244. package/dist/types/computer-use/index.d.ts +6 -0
  245. package/dist/types/computer-use/index.d.ts.map +1 -1
  246. package/dist/types/computer-use/index.js.map +1 -1
  247. package/dist/types/errors/catalog.js +1 -1
  248. package/dist/types/errors/catalog.js.map +1 -1
  249. package/dist/types/hitl/index.d.ts +11 -1
  250. package/dist/types/hitl/index.d.ts.map +1 -1
  251. package/dist/types/hitl/index.js.map +1 -1
  252. package/dist/types/memory/index.d.ts +14 -1
  253. package/dist/types/memory/index.d.ts.map +1 -1
  254. package/dist/types/project/entity.d.ts +1 -0
  255. package/dist/types/project/entity.d.ts.map +1 -1
  256. package/dist/types/provider/config.d.ts +2 -0
  257. package/dist/types/provider/config.d.ts.map +1 -1
  258. package/dist/types/provider/model.d.ts +4 -0
  259. package/dist/types/provider/model.d.ts.map +1 -1
  260. package/dist/types/run/answer-review.d.ts +2 -0
  261. package/dist/types/run/answer-review.d.ts.map +1 -1
  262. package/dist/types/run/events.d.ts +9 -0
  263. package/dist/types/run/events.d.ts.map +1 -1
  264. package/dist/types/run/events.js.map +1 -1
  265. package/dist/types/run/prepare-step.d.ts +19 -1
  266. package/dist/types/run/prepare-step.d.ts.map +1 -1
  267. package/dist/types/sandbox/index.d.ts +20 -0
  268. package/dist/types/sandbox/index.d.ts.map +1 -1
  269. package/dist/types/sandbox/index.js.map +1 -1
  270. package/dist/types/structured-output/index.d.ts +8 -2
  271. package/dist/types/structured-output/index.d.ts.map +1 -1
  272. package/dist/types/tool/index.d.ts +15 -0
  273. package/dist/types/tool/index.d.ts.map +1 -1
  274. package/dist/types/tool/index.js.map +1 -1
  275. package/dist/utils/json-snapshot.d.ts +3 -0
  276. package/dist/utils/json-snapshot.d.ts.map +1 -0
  277. package/dist/utils/json-snapshot.js +79 -0
  278. package/dist/utils/json-snapshot.js.map +1 -0
  279. package/package.json +8 -1
  280. package/src/bridge/a2a/mapper.ts +2 -0
  281. package/src/bridge/sse/mapper.ts +2 -0
  282. package/src/compaction/extractor.ts +21 -8
  283. package/src/compaction/manager.ts +11 -1
  284. package/src/compaction/verifier.ts +80 -18
  285. package/src/eval/harness-verification.ts +270 -0
  286. package/src/eval/index.ts +10 -0
  287. package/src/execution/code-runtime/json-value.ts +82 -0
  288. package/src/execution/code-runtime/worker-program.ts +239 -0
  289. package/src/execution/code-runtime/worker.ts +164 -111
  290. package/src/manager/agent/lifecycle.ts +340 -45
  291. package/src/manager/run/persistence.ts +7 -0
  292. package/src/provider/capabilities.ts +25 -1
  293. package/src/provider/fallback.ts +2 -0
  294. package/src/provider/token-budget.ts +52 -8
  295. package/src/public-runtime.ts +6 -1
  296. package/src/public-tools.ts +5 -1
  297. package/src/public-types.ts +1 -0
  298. package/src/registry/tool/execute.ts +17 -79
  299. package/src/run/command-gate.ts +61 -13
  300. package/src/run/index.ts +2 -0
  301. package/src/run/memory-promoter.ts +32 -40
  302. package/src/run/memory-recall.ts +254 -0
  303. package/src/run/reporter.ts +1 -0
  304. package/src/run/token-budget.ts +4 -0
  305. package/src/run/workspace-fingerprint.ts +17 -8
  306. package/src/runtime/query/checkpoint.ts +96 -2
  307. package/src/runtime/query/executor.ts +131 -28
  308. package/src/runtime/query/index.ts +81 -16
  309. package/src/runtime/query/iteration/index.ts +406 -53
  310. package/src/runtime/query/iteration/native-output.ts +35 -0
  311. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  312. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  313. package/src/runtime/query/iteration/phases/context.ts +11 -0
  314. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  315. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  316. package/src/runtime/query/iteration/stream-turn.ts +11 -5
  317. package/src/runtime/query/prompt.ts +21 -4
  318. package/src/runtime/query/steering.ts +19 -2
  319. package/src/runtime/query/tool-call-budget.ts +115 -0
  320. package/src/runtime/query/tooling.ts +6 -0
  321. package/src/sandbox/file-walk-program.ts +93 -0
  322. package/src/sandbox/file-walk.ts +463 -0
  323. package/src/sandbox/index.ts +2 -0
  324. package/src/sandbox/provider/local.ts +13 -0
  325. package/src/scheduler/completion-inbox.ts +29 -19
  326. package/src/scheduler/local.ts +1 -0
  327. package/src/session/handoff/capacity.ts +7 -3
  328. package/src/store/memory/disk.ts +52 -30
  329. package/src/store/memory/index.ts +75 -27
  330. package/src/store/memory/memory.ts +18 -6
  331. package/src/store/memory/operation-lock.ts +99 -0
  332. package/src/tools/builtins/bash.ts +230 -58
  333. package/src/tools/builtins/computer-use.ts +61 -7
  334. package/src/tools/builtins/glob.ts +104 -122
  335. package/src/tools/builtins/grep.ts +166 -132
  336. package/src/tools/builtins/ls.ts +2 -2
  337. package/src/tools/builtins/run-code.ts +24 -5
  338. package/src/tools/builtins/search-tools.ts +18 -3
  339. package/src/tools/defineTool.ts +5 -0
  340. package/src/tools/memory/delete.ts +29 -0
  341. package/src/tools/memory/index.ts +6 -0
  342. package/src/tools/memory/save.ts +4 -2
  343. package/src/tools/memory/search.ts +12 -5
  344. package/src/tools/memory/update.ts +50 -0
  345. package/src/types/agent/scheduler.ts +6 -0
  346. package/src/types/agent/task.ts +13 -0
  347. package/src/types/computer-use/index.ts +6 -0
  348. package/src/types/errors/catalog.ts +1 -1
  349. package/src/types/hitl/index.ts +11 -1
  350. package/src/types/memory/index.ts +16 -1
  351. package/src/types/project/entity.ts +1 -0
  352. package/src/types/provider/config.ts +2 -0
  353. package/src/types/provider/model.ts +4 -0
  354. package/src/types/run/answer-review.ts +2 -0
  355. package/src/types/run/events.ts +10 -0
  356. package/src/types/run/prepare-step.ts +19 -1
  357. package/src/types/sandbox/index.ts +21 -0
  358. package/src/types/structured-output/index.ts +15 -2
  359. package/src/types/tool/index.ts +16 -0
  360. package/src/utils/json-snapshot.ts +76 -0
@@ -1,6 +1,10 @@
1
1
  import { type Span, SpanStatusCode } from '@opentelemetry/api'
2
2
  import { resolveContextWindow } from '../../../compaction/context-window.js'
3
- import { extractFromAssistantMessage } from '../../../compaction/extractor.js'
3
+ import {
4
+ extractFromAssistantMessage,
5
+ extractFromUserMessage,
6
+ } from '../../../compaction/extractor.js'
7
+ import { estimateMessageTokens } from '../../../compaction/token-estimate.js'
4
8
  import { AUTO_CONTINUATION_USER_MESSAGE } from '../../../constants/continuation.js'
5
9
  import {
6
10
  DEFAULT_STRUCTURED_OUTPUT_RETRIES,
@@ -9,6 +13,7 @@ import {
9
13
  import { renderSkillsSection } from '../../../persona/assembler.js'
10
14
  import { resolveProviderCapabilities } from '../../../provider/capabilities.js'
11
15
  import { collectChatCompletion } from '../../../provider/collect-chat-completion.js'
16
+ import { renderToolSchema } from '../../../registry/tool/schema.js'
12
17
  import { formatCompletionNotification } from '../../../scheduler/completion-inbox.js'
13
18
  import {
14
19
  GENAI,
@@ -24,6 +29,7 @@ import { NamzuError } from '../../../types/errors/index.js'
24
29
  import type { MessageId } from '../../../types/ids/index.js'
25
30
  import {
26
31
  type Message,
32
+ type UserMessage,
27
33
  createAssistantMessage,
28
34
  createRuntimeContextMessage,
29
35
  createSystemMessage,
@@ -33,6 +39,7 @@ import { classifyProviderError } from '../../../types/provider/errors.js'
33
39
  import type { ChatCompletionResponse } from '../../../types/provider/index.js'
34
40
  import type { AnswerReview } from '../../../types/run/answer-review.js'
35
41
  import type {
42
+ PrepareStepContext,
36
43
  PrepareStepResult,
37
44
  RunEvent,
38
45
  StepFailure,
@@ -54,10 +61,16 @@ import {
54
61
  markProviderRejectedImage,
55
62
  projectRequestRichContent,
56
63
  } from '../request-rich-content.js'
57
- import { formatSteeringNote } from '../steering.js'
64
+ import { formatSteeringNote, isOperatorUserMessage } from '../steering.js'
65
+ import { parseNativeCandidate } from './native-output.js'
58
66
  import { runAdvisoryPhase } from './phases/advisory.js'
59
67
  import { runIterationCheckpoint } from './phases/checkpoint.js'
60
- import { measureContext, relieveOverflow, runCompactionCheck } from './phases/compaction.js'
68
+ import {
69
+ activeContextWindow,
70
+ measureContext,
71
+ relieveOverflow,
72
+ runCompactionCheck,
73
+ } from './phases/compaction.js'
61
74
  import type { IterationContext } from './phases/index.js'
62
75
  import { runPlanGate } from './phases/plan.js'
63
76
  import { runToolReview } from './phases/tool-review.js'
@@ -168,9 +181,34 @@ export class IterationOrchestrator {
168
181
  * sets it.
169
182
  */
170
183
  private stopDeferredForOutstandingWork = false
184
+ /** One current input, independent of the compactable history array. */
185
+ private latestUserMessage: UserMessage | undefined
171
186
 
172
187
  constructor(ctx: IterationContext) {
173
- this.ctx = ctx
188
+ this.ctx = {
189
+ ...ctx,
190
+ onSteeringDelivered: (text) => {
191
+ this.rememberUserMessage(createRuntimeContextMessage(text, 'steering'))
192
+ },
193
+ }
194
+ ctx.checkpointMgr.setLatestUserMessageSource(() => this.latestUserMessage)
195
+ ctx.checkpointMgr.setStructuredReviewAttemptsSource?.(() => this.structuredReviewAttempts)
196
+ ctx.checkpointMgr.setNativeStructuredAttemptsSource?.(() => this.nativeStructuredAttempts)
197
+ if (ctx.structuredOutput?.mode === 'native') {
198
+ const limit = ctx.structuredOutput.maxRetries
199
+ if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 0))
200
+ throw new RangeError(
201
+ 'Native structuredOutput.maxRetries must be a nonnegative safe integer',
202
+ )
203
+ }
204
+ if (
205
+ ctx.structuredOutput?.mode !== undefined &&
206
+ !['tool', 'native'].includes(ctx.structuredOutput.mode)
207
+ )
208
+ throw new RangeError('Unknown structuredOutput.mode')
209
+ const maxReviews = ctx.structuredOutput?.maxReviews
210
+ if (maxReviews !== undefined && (!Number.isSafeInteger(maxReviews) || maxReviews < 0))
211
+ throw new RangeError('structuredOutput.maxReviews must be a nonnegative safe integer')
174
212
  }
175
213
 
176
214
  /**
@@ -252,6 +290,17 @@ export class IterationOrchestrator {
252
290
  const { runConfig, runMgr } = this.ctx
253
291
  const { model } = runConfig
254
292
  const tracer = getTracer()
293
+ // Resume hydration happens after construction, before the loop starts.
294
+ this.latestUserMessage = this.ctx.checkpointMgr.restoredLatestUserMessage
295
+ this.structuredReviewAttempts = this.ctx.checkpointMgr.restoredStructuredReviewAttempts ?? 0
296
+ this.nativeStructuredAttempts = this.ctx.checkpointMgr.restoredNativeStructuredAttempts ?? 0
297
+ if (!this.latestUserMessage) {
298
+ for (const message of runMgr.messages) this.rememberUserMessage(message, false)
299
+ }
300
+ // The restored field can outlive the historical message it describes.
301
+ // Only known post-checkpoint arrivals may supersede it, never an old
302
+ // retained user turn encountered while scanning restored history.
303
+ for (const message of this.ctx.resumedInput ?? []) this.rememberUserMessage(message)
255
304
 
256
305
  // One context-overflow relief per *stuck point*, not per run.
257
306
  //
@@ -278,6 +327,26 @@ export class IterationOrchestrator {
278
327
  // no post-loop block reaches.
279
328
  try {
280
329
  while (true) {
330
+ if (this.ctx.abortController.signal.aborted) {
331
+ runMgr.setStopReason('cancelled')
332
+ runMgr.markCancelled()
333
+ break
334
+ }
335
+ if (
336
+ this.ctx.structuredOutput?.mode === 'native' &&
337
+ this.nativeStructuredAttempts > this.structuredOutputRetryLimit()
338
+ ) {
339
+ runMgr.setStopReason('structured_output_failed')
340
+ break
341
+ }
342
+ if (
343
+ this.ctx.structuredOutput?.review &&
344
+ this.structuredReviewAttempts >
345
+ (this.ctx.structuredOutput.maxReviews ?? DEFAULT_ANSWER_REVIEW_LIMIT)
346
+ ) {
347
+ runMgr.setStopReason('answer_rejected')
348
+ break
349
+ }
281
350
  // Read AND clear, in that order, in this one place.
282
351
  //
283
352
  // The flag is set by the previous iteration and read by this
@@ -325,6 +394,14 @@ export class IterationOrchestrator {
325
394
  // run and been paid for; this is the seam a host with a live
326
395
  // rate limit or a revoked tenant actually needs.
327
396
  const veto = await this.beforeStep(runMgr.currentIteration + 1)
397
+ // The hook may settle because its run signal was aborted. Stop
398
+ // before interpreting that settlement as a policy refusal or
399
+ // counting an iteration that will never reach the provider.
400
+ if (this.ctx.abortController.signal.aborted) {
401
+ runMgr.setStopReason('cancelled')
402
+ runMgr.markCancelled()
403
+ break
404
+ }
328
405
  if (veto) {
329
406
  // Namespaced. The un-namespaced keys elsewhere in this file are
330
407
  // the frozen inventory LOG-22 exists to drain; a new call site
@@ -431,10 +508,6 @@ export class IterationOrchestrator {
431
508
  // compaction preserves). No-op when no provider is configured.
432
509
  await refreshWorkingMemory(this.ctx)
433
510
  await runCompactionCheck(this.ctx)
434
- // A context edit is a host-visible state transition, not a prelude
435
- // whose events may wait behind the next network call. In particular,
436
- // a slow or failing provider must not leave the host displaying the
437
- // pre-compaction context after Run.messages already shrank.
438
511
  yield* this.ctx.drainPending()
439
512
 
440
513
  // Cache discipline: keep the tools param byte-stable even on the
@@ -452,10 +525,25 @@ export class IterationOrchestrator {
452
525
  // Shape this step before calling the model. `stopWhen` decides
453
526
  // whether to keep going; this decides HOW. No-op when the host
454
527
  // supplied no hook.
528
+ const contextModelBeforePreparation = this.ctx.contextModel ?? model
455
529
  const step = await this.prepareStep(iterationNum)
530
+ stepModel = step.model ?? model
531
+ await this.selectContextModel(stepModel)
532
+ // Preserve post-compaction preparation/recall semantics. A changed
533
+ // model needs a second check against its own window; never replay
534
+ // host preparation effects merely to rebuild its request guidance.
535
+ if (stepModel !== contextModelBeforePreparation) await runCompactionCheck(this.ctx)
536
+ // Publish context edits before entering a possibly slow provider.
537
+ yield* this.ctx.drainPending()
456
538
 
457
539
  const stepAllowedTools = step.allowedTools ?? this.ctx.allowedTools
458
- const llmTools = this.ctx.tools.toLLMTools(stepAllowedTools)
540
+ const llmTools = this.ctx.tools
541
+ .toLLMTools(stepAllowedTools)
542
+ .filter(
543
+ (tool) =>
544
+ this.ctx.structuredOutput?.mode !== 'native' ||
545
+ tool.function.name !== STRUCTURED_OUTPUT_TOOL_NAME,
546
+ )
459
547
  // The same list the request was built from now also bounds what
460
548
  // may run. Narrowing only the request left the restriction
461
549
  // presentational — the model was shown fewer tools and could
@@ -632,6 +720,18 @@ export class IterationOrchestrator {
632
720
  this.ctx.provider,
633
721
  {
634
722
  model: stepModel,
723
+ ...(this.ctx.structuredOutput?.mode === 'native'
724
+ ? {
725
+ responseFormat: {
726
+ type: 'json_schema' as const,
727
+ json_schema: {
728
+ name: 'structured_output',
729
+ schema: renderToolSchema(this.ctx.structuredOutput.schema),
730
+ strict: true,
731
+ },
732
+ },
733
+ }
734
+ : {}),
635
735
  providerRoute: requestedRoute,
636
736
  messages,
637
737
  tools: llmTools.length > 0 ? llmTools : undefined,
@@ -771,11 +871,7 @@ export class IterationOrchestrator {
771
871
  const contextFigures = this.ctx.compactionConfig
772
872
  ? (() => {
773
873
  const measured = measureContext(this.ctx)
774
- const window = resolveContextWindow(
775
- this.ctx.compactionConfig?.contextWindowTokens,
776
- runConfig.model,
777
- this.ctx.providerContextWindow,
778
- )
874
+ const window = activeContextWindow(this.ctx)
779
875
  return {
780
876
  contextTokens: measured.tokens,
781
877
  contextMeasuredBy: measured.source,
@@ -932,6 +1028,70 @@ export class IterationOrchestrator {
932
1028
  costBefore,
933
1029
  })
934
1030
 
1031
+ if (this.ctx.structuredOutput?.mode === 'native') {
1032
+ const candidate = await parseNativeCandidate(
1033
+ this.ctx.structuredOutput.schema,
1034
+ response,
1035
+ this.ctx.abortController.signal,
1036
+ )
1037
+ let outcome: 'accepted' | 'retry' | 'exhausted' | 'cancelled'
1038
+ if (candidate.success) outcome = await this.reviewStructuredOutput(candidate.value)
1039
+ else {
1040
+ this.nativeStructuredAttempts++
1041
+ runMgr.pushMessage(
1042
+ createRuntimeContextMessage(
1043
+ 'Return a complete JSON value matching the supplied response schema. Do not continue a partial JSON fragment.',
1044
+ 'structured-output',
1045
+ ),
1046
+ )
1047
+ const checkpoint = await this.ctx.checkpointMgr.create(runMgr, iterationNum)
1048
+ await this.ctx.emitEvent({
1049
+ type: 'checkpoint_created',
1050
+ runId: runMgr.id,
1051
+ checkpointId: checkpoint.id,
1052
+ iteration: iterationNum,
1053
+ })
1054
+ outcome =
1055
+ this.nativeStructuredAttempts > this.structuredOutputRetryLimit()
1056
+ ? 'exhausted'
1057
+ : 'retry'
1058
+ }
1059
+ await this.ctx.emitEvent({
1060
+ type: 'iteration_completed',
1061
+ runId: runMgr.id,
1062
+ iteration: iterationNum,
1063
+ hasToolCalls: false,
1064
+ })
1065
+ yield* this.ctx.drainPending()
1066
+ if (this.ctx.abortController.signal.aborted || outcome === 'cancelled') {
1067
+ runMgr.setStopReason('cancelled')
1068
+ runMgr.markCancelled()
1069
+ break
1070
+ }
1071
+ if (outcome === 'accepted') {
1072
+ if (!forceFinalize) {
1073
+ const changed = yield* this.holdForOutstandingWork(iterationNum, false)
1074
+ const inbound = this.deliverInbound()
1075
+ if (changed || inbound > 0) continue
1076
+ }
1077
+ if (this.ctx.abortController.signal.aborted) {
1078
+ runMgr.setStopReason('cancelled')
1079
+ runMgr.markCancelled()
1080
+ break
1081
+ }
1082
+ this.publishStructuredOutput()
1083
+ runMgr.setStopReason('end_turn')
1084
+ break
1085
+ }
1086
+ if (outcome === 'exhausted') {
1087
+ runMgr.setStopReason(
1088
+ candidate.success ? 'answer_rejected' : 'structured_output_failed',
1089
+ )
1090
+ break
1091
+ }
1092
+ continue
1093
+ }
1094
+
935
1095
  const hasContent =
936
1096
  response.message.content !== null && response.message.content.length > 0
937
1097
 
@@ -1022,6 +1182,11 @@ export class IterationOrchestrator {
1022
1182
  // a loud stop rather than a loop.
1023
1183
  if (!forceFinalize && this.ctx.reviewAnswer) {
1024
1184
  const review = await this.reviewAnswer(response.message.content ?? '')
1185
+ if (this.ctx.abortController.signal.aborted) {
1186
+ runMgr.setStopReason('cancelled')
1187
+ runMgr.markCancelled()
1188
+ break
1189
+ }
1025
1190
  if (review && !review.accept) {
1026
1191
  const attempt = ++this.answerReviewAttempts
1027
1192
  const limit = this.ctx.maxAnswerReviews ?? DEFAULT_ANSWER_REVIEW_LIMIT
@@ -1159,12 +1324,39 @@ export class IterationOrchestrator {
1159
1324
  // run ends here rather than paying for another turn whose only
1160
1325
  // job would be to restate it — unless it shared its turn with
1161
1326
  // other calls, which relays instead. See the method.
1162
- if (this.captureStructuredOutput(reviewOutcome.results, response)) {
1327
+ const structuredOutcome = await this.captureStructuredOutput(
1328
+ reviewOutcome.results,
1329
+ response,
1330
+ )
1331
+ if (
1332
+ structuredOutcome === 'retry' ||
1333
+ structuredOutcome === 'exhausted' ||
1334
+ structuredOutcome === 'cancelled'
1335
+ ) {
1336
+ await this.ctx.emitEvent({
1337
+ type: 'iteration_completed',
1338
+ runId: runMgr.id,
1339
+ iteration: iterationNum,
1340
+ hasToolCalls: true,
1341
+ })
1342
+ yield* this.ctx.drainPending()
1343
+ if (this.ctx.abortController.signal.aborted) {
1344
+ runMgr.setStopReason('cancelled')
1345
+ runMgr.markCancelled()
1346
+ break
1347
+ }
1348
+ if (structuredOutcome === 'retry') continue
1349
+ runMgr.setStopReason(
1350
+ structuredOutcome === 'cancelled' ? 'cancelled' : 'answer_rejected',
1351
+ )
1352
+ if (structuredOutcome === 'cancelled') runMgr.markCancelled()
1353
+ break
1354
+ }
1355
+ if (structuredOutcome === 'accepted') {
1163
1356
  this.ctx.log.info('Structured output produced — ending run', {
1164
1357
  [NAMZU.RUN_ID]: runMgr.id,
1165
1358
  [NAMZU.ITERATION]: iterationNum,
1166
1359
  })
1167
- runMgr.setStopReason('end_turn')
1168
1360
  await this.ctx.emitEvent({
1169
1361
  type: 'iteration_completed',
1170
1362
  runId: runMgr.id,
@@ -1172,6 +1364,13 @@ export class IterationOrchestrator {
1172
1364
  hasToolCalls: true,
1173
1365
  })
1174
1366
  yield* this.ctx.drainPending()
1367
+ if (this.ctx.abortController.signal.aborted) {
1368
+ runMgr.setStopReason('cancelled')
1369
+ runMgr.markCancelled()
1370
+ break
1371
+ }
1372
+ this.publishStructuredOutput()
1373
+ runMgr.setStopReason('end_turn')
1175
1374
  break
1176
1375
  }
1177
1376
 
@@ -1450,9 +1649,9 @@ export class IterationOrchestrator {
1450
1649
  /**
1451
1650
  * Hold the run open for a worker that has not finished, and deliver it.
1452
1651
  *
1453
- * Returns whether a completion arrived and was put in the transcript — the
1454
- * caller continues the loop on `true`, so the model gets a turn in which to
1455
- * USE the result. That turn is the entire justification for waiting, which
1652
+ * Returns whether a completion or operator message entered the transcript —
1653
+ * the caller continues on `true`, so the model gets a turn to respond.
1654
+ * That turn is the entire justification for waiting, which
1456
1655
  * is why only the exits that can still take one call this.
1457
1656
  *
1458
1657
  * Bounded by `settleGraceMs` and by `maxIterations`, so a worker that never
@@ -1473,14 +1672,34 @@ export class IterationOrchestrator {
1473
1672
  [NAMZU.ITERATION]: iterationNum,
1474
1673
  'namzu.runtime.grace_ms': graceMs,
1475
1674
  })
1476
- await this.ctx.completionInbox.waitForArrival(graceMs)
1675
+ // User input releases this wait without cancelling any child. Both waits
1676
+ // share a disposable signal so the losing arrival listener cannot leak.
1677
+ const waiting = new AbortController()
1678
+ const runSignal = this.ctx.abortController.signal
1679
+ const cancelWait = () => waiting.abort(runSignal.reason)
1680
+ runSignal.addEventListener('abort', cancelWait, { once: true })
1681
+ if (runSignal.aborted) cancelWait()
1682
+ try {
1683
+ await Promise.race([
1684
+ this.ctx.completionInbox.waitForArrival(graceMs, waiting.signal),
1685
+ ...(this.ctx.waitForInbound ? [this.ctx.waitForInbound(waiting.signal)] : []),
1686
+ ])
1687
+ } catch (error) {
1688
+ if (!runSignal.aborted) throw error
1689
+ } finally {
1690
+ waiting.abort()
1691
+ runSignal.removeEventListener('abort', cancelWait)
1692
+ }
1693
+ runSignal.throwIfAborted()
1477
1694
 
1478
1695
  const arrived = this.ctx.completionInbox.drain()
1479
- if (arrived.length === 0) return false
1480
-
1481
- this.ctx.runMgr.pushMessage(
1482
- createRuntimeContextMessage(formatCompletionNotification(arrived), 'task-completion'),
1483
- )
1696
+ if (arrived.length > 0) {
1697
+ this.ctx.runMgr.pushMessage(
1698
+ createRuntimeContextMessage(formatCompletionNotification(arrived), 'task-completion'),
1699
+ )
1700
+ }
1701
+ const inbound = this.deliverInbound()
1702
+ if (arrived.length === 0 && inbound === 0) return false
1484
1703
  await this.ctx.emitEvent({
1485
1704
  type: 'iteration_completed',
1486
1705
  runId: this.ctx.runMgr.id,
@@ -1583,19 +1802,51 @@ export class IterationOrchestrator {
1583
1802
  * message becomes the reason, so an operator is not left with a run
1584
1803
  * that stopped and no account of it.
1585
1804
  */
1805
+ private stepContext(stepNumber: number, prepared: PrepareStepResult): PrepareStepContext {
1806
+ const model = prepared.model ?? this.ctx.runConfig.model
1807
+ const window = resolveContextWindow(
1808
+ this.ctx.compactionConfig?.contextWindowTokens,
1809
+ model,
1810
+ model === this.ctx.runConfig.model
1811
+ ? this.ctx.providerContextWindow
1812
+ : model === this.ctx.contextModel
1813
+ ? this.ctx.activeProviderContextWindow
1814
+ : undefined,
1815
+ )
1816
+ const skills = prepared.skills ? renderSkillsSection([...prepared.skills]) : null
1817
+ const preamble = [prepared.system, skills].filter(Boolean).join('\n\n')
1818
+ const preparedTokens = preamble ? estimateMessageTokens(createSystemMessage(preamble)) : 0
1819
+ const responseReserve = Math.min(
1820
+ prepared.maxResponseTokens ??
1821
+ this.ctx.runConfig.maxResponseTokens ??
1822
+ Math.floor(window.tokens / 4),
1823
+ Math.floor(window.tokens / 4),
1824
+ )
1825
+ return {
1826
+ runId: this.ctx.runMgr.id,
1827
+ stepNumber,
1828
+ messages: this.ctx.runMgr.messages,
1829
+ ...(this.latestUserMessage ? { latestUserMessage: this.latestUserMessage } : {}),
1830
+ signal: this.ctx.abortController.signal,
1831
+ contextBudget: {
1832
+ windowTokens: window.tokens,
1833
+ remainingTokens: Math.max(
1834
+ 0,
1835
+ Math.floor(
1836
+ window.tokens - measureContext(this.ctx).tokens - preparedTokens - responseReserve,
1837
+ ),
1838
+ ),
1839
+ },
1840
+ steps: this.steps,
1841
+ prepared,
1842
+ }
1843
+ }
1844
+
1586
1845
  private async beforeStep(stepNumber: number): Promise<StepVeto | undefined> {
1587
1846
  const configured = this.ctx.beforeStep
1588
1847
  if (!configured) return undefined
1589
1848
  try {
1590
- return (
1591
- (await configured({
1592
- runId: this.ctx.runMgr.id,
1593
- stepNumber,
1594
- messages: this.ctx.runMgr.messages,
1595
- steps: this.steps,
1596
- prepared: {},
1597
- })) ?? undefined
1598
- )
1849
+ return (await configured(this.stepContext(stepNumber, {}))) ?? undefined
1599
1850
  } catch (err) {
1600
1851
  return { reason: `beforeStep threw: ${toErrorMessage(err)}` }
1601
1852
  }
@@ -1621,14 +1872,9 @@ export class IterationOrchestrator {
1621
1872
  let result: PrepareStepResult = {}
1622
1873
  for (const stage of stages) {
1623
1874
  try {
1624
- const decided = await stage({
1625
- runId: this.ctx.runMgr.id,
1626
- stepNumber,
1627
- messages: this.ctx.runMgr.messages,
1628
- steps: this.steps,
1629
- prepared: result,
1630
- })
1875
+ const decided = await stage(this.stepContext(stepNumber, result))
1631
1876
  if (decided) result = { ...result, ...decided }
1877
+ await this.selectContextModel(result.model ?? this.ctx.runConfig.model)
1632
1878
  } catch (err) {
1633
1879
  // Skipped, and the rest still run: one broken concern must
1634
1880
  // not silently disable the others it was declared beside.
@@ -1690,6 +1936,18 @@ export class IterationOrchestrator {
1690
1936
  return prepared
1691
1937
  }
1692
1938
 
1939
+ private async selectContextModel(model: string | undefined): Promise<void> {
1940
+ if (model !== (this.ctx.contextModel ?? this.ctx.runConfig.model)) {
1941
+ // A measurement from another tokenizer cannot price the new request.
1942
+ this.ctx.runMgr.clearLastPromptTokens()
1943
+ }
1944
+ this.ctx.contextModel = model
1945
+ this.ctx.activeProviderContextWindow =
1946
+ model && model !== this.ctx.runConfig.model && !this.ctx.compactionConfig?.contextWindowTokens
1947
+ ? await this.ctx.resolveModelContextWindow?.(model)
1948
+ : undefined
1949
+ }
1950
+
1693
1951
  /** Steps completed so far, exposed on the returned `Run`. */
1694
1952
  private readonly steps: StepResult[] = []
1695
1953
 
@@ -1718,18 +1976,33 @@ export class IterationOrchestrator {
1718
1976
  * empty drain must change nothing at all: a `continue` on nothing queued
1719
1977
  * spends an iteration and a model call to say the same thing again.
1720
1978
  */
1979
+ private rememberUserMessage(message: Message, arriving = true): void {
1980
+ if (!isOperatorUserMessage(message)) return
1981
+ this.latestUserMessage = message
1982
+ // The hook field alone does not reach the model. Preserve arrivals in
1983
+ // the compaction state too, before their original message or attached
1984
+ // tool result can be shed. Initial history was already extracted at seed.
1985
+ const manager = this.ctx.workingStateManager
1986
+ if (arriving && manager) {
1987
+ extractFromUserMessage(manager, message.content, !manager.getState().task)
1988
+ }
1989
+ }
1990
+
1721
1991
  private deliverInbound(): number {
1722
1992
  const queued = this.ctx.inboundMessages?.() ?? []
1723
- for (const message of queued) this.ctx.runMgr.pushMessage(message)
1993
+ for (const message of queued) {
1994
+ this.ctx.runMgr.pushMessage(message)
1995
+ this.rememberUserMessage(message)
1996
+ }
1724
1997
 
1725
1998
  // The steering channel's remainder. `attachSteering` already took
1726
1999
  // what it could carry on a tool result; anything still pending is
1727
2000
  // guidance from a turn that had no result to attach it to.
1728
2001
  const stranded = this.ctx.steering?.drain()
1729
2002
  if (stranded) {
1730
- this.ctx.runMgr.pushMessage(
1731
- createRuntimeContextMessage(formatSteeringNote(stranded), 'steering'),
1732
- )
2003
+ const message = createRuntimeContextMessage(formatSteeringNote(stranded), 'steering')
2004
+ this.ctx.runMgr.pushMessage(message)
2005
+ this.rememberUserMessage(createRuntimeContextMessage(stranded, 'steering'))
1733
2006
  }
1734
2007
 
1735
2008
  return queued.length + (stranded ? 1 : 0)
@@ -1836,7 +2109,10 @@ export class IterationOrchestrator {
1836
2109
 
1837
2110
  /** Turns spent asking the model again for a valid structured output. */
1838
2111
  private structuredOutputAttempts = 0
2112
+ private nativeStructuredAttempts = 0
1839
2113
  private structuredOutputDone = false
2114
+ private pendingStructuredOutput: unknown
2115
+ private structuredReviewAttempts = 0
1840
2116
 
1841
2117
  private structuredOutputRetryLimit(): number {
1842
2118
  return this.ctx.structuredOutput?.maxRetries ?? DEFAULT_STRUCTURED_OUTPUT_RETRIES
@@ -1863,6 +2139,7 @@ export class IterationOrchestrator {
1863
2139
  results: readonly ToolCallOutcome[],
1864
2140
  response: ChatCompletionResponse,
1865
2141
  ): ToolCallOutcome | undefined {
2142
+ if (this.ctx.structuredOutput?.mode === 'native') return undefined
1866
2143
  const terminal = results.filter((r) => this.ctx.tools.get(r.toolName)?.terminal === true)
1867
2144
  if (terminal.length === 0) return undefined
1868
2145
 
@@ -1931,13 +2208,14 @@ export class IterationOrchestrator {
1931
2208
  * doing work, and it is the bound the neighbour relies on for the
1932
2209
  * identical pathology.
1933
2210
  */
1934
- private captureStructuredOutput(
2211
+ private async captureStructuredOutput(
1935
2212
  results: readonly ToolCallOutcome[],
1936
2213
  response: ChatCompletionResponse,
1937
- ): boolean {
1938
- if (!this.needsStructuredOutput()) return false
2214
+ ): Promise<'absent' | 'accepted' | 'retry' | 'exhausted' | 'cancelled'> {
2215
+ if (!this.needsStructuredOutput() || this.ctx.structuredOutput?.mode === 'native')
2216
+ return 'absent'
1939
2217
  const hit = results.find((r) => r.toolName === STRUCTURED_OUTPUT_TOOL_NAME && !r.isError)
1940
- if (!hit) return false
2218
+ if (!hit) return 'absent'
1941
2219
 
1942
2220
  const callCount = response.message.toolCalls?.length ?? 0
1943
2221
  if (callCount > 1) {
@@ -1945,18 +2223,90 @@ export class IterationOrchestrator {
1945
2223
  [NAMZU.RUN_ID]: this.ctx.runMgr.id,
1946
2224
  'namzu.runtime.calls_in_turn': callCount,
1947
2225
  })
1948
- return false
2226
+ return 'absent'
1949
2227
  }
1950
2228
 
2229
+ let parsed: unknown
1951
2230
  try {
1952
- this.ctx.runMgr.setStructuredOutput(JSON.parse(hit.output))
2231
+ parsed = JSON.parse(hit.output)
1953
2232
  } catch {
1954
2233
  // The tool serializes its own validated input, so this is
1955
2234
  // unreachable in practice; keep the raw text rather than losing it.
1956
- this.ctx.runMgr.setStructuredOutput(hit.output)
2235
+ if (this.ctx.structuredOutput?.review)
2236
+ throw new Error(
2237
+ 'Structured review requires an intact JSON tool result; check tool-output limits and result transformations',
2238
+ )
2239
+ parsed = hit.output
1957
2240
  }
2241
+ return this.reviewStructuredOutput(parsed)
2242
+ }
2243
+
2244
+ private publishStructuredOutput(): void {
2245
+ this.ctx.runMgr.setStructuredOutput(this.pendingStructuredOutput)
1958
2246
  this.structuredOutputDone = true
1959
- return true
2247
+ }
2248
+
2249
+ private async reviewStructuredOutput(
2250
+ parsed: unknown,
2251
+ ): Promise<'accepted' | 'retry' | 'exhausted' | 'cancelled'> {
2252
+ if (this.ctx.abortController.signal.aborted) return 'cancelled'
2253
+ const reviewer = this.ctx.structuredOutput?.review
2254
+ if (reviewer) {
2255
+ const signal = this.ctx.abortController.signal
2256
+ if (signal.aborted) return 'cancelled'
2257
+ let onAbort: () => void = () => {}
2258
+ const aborted = new Promise<never>((_resolve, reject) => {
2259
+ onAbort = () => reject(signal.reason ?? new Error('Structured review cancelled'))
2260
+ signal.addEventListener('abort', onAbort, { once: true })
2261
+ })
2262
+ let verdict: AnswerReview
2263
+ try {
2264
+ verdict = await Promise.race([
2265
+ Promise.resolve().then(() =>
2266
+ reviewer(structuredClone(parsed), {
2267
+ runId: this.ctx.runMgr.id,
2268
+ iteration: this.ctx.runMgr.currentIteration,
2269
+ signal,
2270
+ messages: this.ctx.runMgr.messages,
2271
+ }),
2272
+ ),
2273
+ aborted,
2274
+ ])
2275
+ } catch (error) {
2276
+ if (signal.aborted) return 'cancelled'
2277
+ throw error
2278
+ } finally {
2279
+ signal.removeEventListener('abort', onAbort)
2280
+ }
2281
+ if (signal.aborted) return 'cancelled'
2282
+ if (!verdict || typeof verdict.accept !== 'boolean')
2283
+ throw new Error('Structured reviewer returned an invalid verdict')
2284
+ if (!verdict.accept) {
2285
+ if (typeof verdict.feedback !== 'string' || verdict.feedback.trim().length === 0)
2286
+ throw new Error('Structured reviewer rejection requires feedback')
2287
+ this.structuredReviewAttempts++
2288
+ this.ctx.runMgr.pushMessage(createRuntimeContextMessage(verdict.feedback, 'answer-review'))
2289
+ // Persist both the feedback and its consumed allowance before the
2290
+ // next request. This checkpoint is not an approval/park boundary.
2291
+ const checkpoint = await this.ctx.checkpointMgr.create(
2292
+ this.ctx.runMgr,
2293
+ this.ctx.runMgr.currentIteration,
2294
+ )
2295
+ await this.ctx.emitEvent({
2296
+ type: 'checkpoint_created',
2297
+ runId: this.ctx.runMgr.id,
2298
+ checkpointId: checkpoint.id,
2299
+ iteration: this.ctx.runMgr.currentIteration,
2300
+ })
2301
+ if (signal.aborted) return 'cancelled'
2302
+ return this.structuredReviewAttempts >
2303
+ (this.ctx.structuredOutput?.maxReviews ?? DEFAULT_ANSWER_REVIEW_LIMIT)
2304
+ ? 'exhausted'
2305
+ : 'retry'
2306
+ }
2307
+ }
2308
+ this.pendingStructuredOutput = parsed
2309
+ return 'accepted'
1960
2310
  }
1961
2311
 
1962
2312
  /**
@@ -1977,6 +2327,7 @@ export class IterationOrchestrator {
1977
2327
  return await this.ctx.reviewAnswer(answer, {
1978
2328
  runId: this.ctx.runMgr.id,
1979
2329
  iteration: this.ctx.runMgr.currentIteration,
2330
+ signal: this.ctx.abortController.signal,
1980
2331
  messages: this.ctx.runMgr.messages,
1981
2332
  })
1982
2333
  } catch (err) {
@@ -2039,6 +2390,8 @@ export class IterationOrchestrator {
2039
2390
  model: string,
2040
2391
  reason: StopReason,
2041
2392
  ): Promise<StopReason | undefined> {
2393
+ if (this.ctx.structuredOutput?.mode === 'native') return 'structured_output_failed'
2394
+
2042
2395
  const lastAssistant = [...this.ctx.runMgr.messages]
2043
2396
  .reverse()
2044
2397
  .find((m) => m.role === 'assistant')