@namzu/sdk 61.4.0 → 62.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 (302) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/bridge/acp/permission.d.ts +8 -0
  6. package/dist/bridge/acp/permission.d.ts.map +1 -1
  7. package/dist/bridge/acp/permission.js.map +1 -1
  8. package/dist/bridge/acp/server.d.ts.map +1 -1
  9. package/dist/bridge/acp/server.js +8 -1
  10. package/dist/bridge/acp/server.js.map +1 -1
  11. package/dist/constants/tools/index.d.ts +2 -0
  12. package/dist/constants/tools/index.d.ts.map +1 -1
  13. package/dist/constants/tools/index.js +23 -0
  14. package/dist/constants/tools/index.js.map +1 -1
  15. package/dist/eval/harness-protection.js +1 -1
  16. package/dist/eval/harness-protection.js.map +1 -1
  17. package/dist/learning/cycle.d.ts +5 -3
  18. package/dist/learning/cycle.d.ts.map +1 -1
  19. package/dist/learning/cycle.js +11 -13
  20. package/dist/learning/cycle.js.map +1 -1
  21. package/dist/learning/guidance.d.ts +74 -0
  22. package/dist/learning/guidance.d.ts.map +1 -0
  23. package/dist/learning/guidance.js +259 -0
  24. package/dist/learning/guidance.js.map +1 -0
  25. package/dist/learning/skill.d.ts +2 -2
  26. package/dist/learning/skill.d.ts.map +1 -1
  27. package/dist/learning/skill.js +6 -6
  28. package/dist/learning/skill.js.map +1 -1
  29. package/dist/learning/store.d.ts.map +1 -1
  30. package/dist/learning/store.js +6 -6
  31. package/dist/learning/store.js.map +1 -1
  32. package/dist/pals/communication/ingress-schema.d.ts +8 -8
  33. package/dist/pals/communication/schema.d.ts +2 -2
  34. package/dist/peers/client.d.ts +2 -3
  35. package/dist/peers/client.d.ts.map +1 -1
  36. package/dist/peers/client.js +2 -3
  37. package/dist/peers/client.js.map +1 -1
  38. package/dist/peers/endpoint.d.ts +1 -2
  39. package/dist/peers/endpoint.d.ts.map +1 -1
  40. package/dist/peers/endpoint.js +1 -2
  41. package/dist/peers/endpoint.js.map +1 -1
  42. package/dist/peers/protocol.d.ts.map +1 -1
  43. package/dist/peers/protocol.js +1 -0
  44. package/dist/peers/protocol.js.map +1 -1
  45. package/dist/prompt/coding-agent-doctrine.d.ts +4 -4
  46. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  47. package/dist/prompt/coding-agent-doctrine.js +5 -3
  48. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  49. package/dist/prompt/index.d.ts +0 -4
  50. package/dist/prompt/index.d.ts.map +1 -1
  51. package/dist/prompt/index.js +0 -2
  52. package/dist/prompt/index.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +3 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/provider/idle-timeout.d.ts.map +1 -1
  57. package/dist/provider/idle-timeout.js +5 -0
  58. package/dist/provider/idle-timeout.js.map +1 -1
  59. package/dist/provider/model-window-suffix.d.ts.map +1 -1
  60. package/dist/provider/model-window-suffix.js +1 -0
  61. package/dist/provider/model-window-suffix.js.map +1 -1
  62. package/dist/provider/retry.d.ts.map +1 -1
  63. package/dist/provider/retry.js +5 -0
  64. package/dist/provider/retry.js.map +1 -1
  65. package/dist/provider/step-stopped.d.ts +19 -0
  66. package/dist/provider/step-stopped.d.ts.map +1 -0
  67. package/dist/provider/step-stopped.js +25 -0
  68. package/dist/provider/step-stopped.js.map +1 -0
  69. package/dist/provider/token-budget.d.ts.map +1 -1
  70. package/dist/provider/token-budget.js +6 -1
  71. package/dist/provider/token-budget.js.map +1 -1
  72. package/dist/public-runtime.d.ts +3 -14
  73. package/dist/public-runtime.d.ts.map +1 -1
  74. package/dist/public-runtime.js +3 -14
  75. package/dist/public-runtime.js.map +1 -1
  76. package/dist/public-tools.d.ts +0 -2
  77. package/dist/public-tools.d.ts.map +1 -1
  78. package/dist/public-tools.js +0 -2
  79. package/dist/public-tools.js.map +1 -1
  80. package/dist/public-types.d.ts +3 -21
  81. package/dist/public-types.d.ts.map +1 -1
  82. package/dist/runtime/query/iteration/index.d.ts +17 -0
  83. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  84. package/dist/runtime/query/iteration/index.js +168 -5
  85. package/dist/runtime/query/iteration/index.js.map +1 -1
  86. package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -1
  87. package/dist/runtime/query/iteration/phases/per-call-review.js +4 -5
  88. package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -1
  89. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  90. package/dist/runtime/query/iteration/phases/tool-review.js +6 -3
  91. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  92. package/dist/runtime/query/iteration/stream-turn.d.ts +9 -1
  93. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  94. package/dist/runtime/query/iteration/stream-turn.js +48 -2
  95. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  96. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  97. package/dist/runtime/query/resume-pending.js +7 -4
  98. package/dist/runtime/query/resume-pending.js.map +1 -1
  99. package/dist/runtime/query/review-policy.d.ts +40 -8
  100. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  101. package/dist/runtime/query/review-policy.js +139 -24
  102. package/dist/runtime/query/review-policy.js.map +1 -1
  103. package/dist/runtime/query/turn/completion-audit.d.ts +32 -0
  104. package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -0
  105. package/dist/runtime/query/turn/completion-audit.js +56 -0
  106. package/dist/runtime/query/turn/completion-audit.js.map +1 -0
  107. package/dist/runtime/query/turn/stall-monitor.d.ts +78 -0
  108. package/dist/runtime/query/turn/stall-monitor.d.ts.map +1 -0
  109. package/dist/runtime/query/turn/stall-monitor.js +101 -0
  110. package/dist/runtime/query/turn/stall-monitor.js.map +1 -0
  111. package/dist/session/paths.d.ts +1 -2
  112. package/dist/session/paths.d.ts.map +1 -1
  113. package/dist/session/paths.js +1 -4
  114. package/dist/session/paths.js.map +1 -1
  115. package/dist/tools/builtins/bash.d.ts +2 -1
  116. package/dist/tools/builtins/bash.d.ts.map +1 -1
  117. package/dist/tools/builtins/bash.js +5 -2
  118. package/dist/tools/builtins/bash.js.map +1 -1
  119. package/dist/tools/builtins/content-fingerprint.d.ts +13 -0
  120. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  121. package/dist/tools/builtins/content-fingerprint.js +25 -0
  122. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  123. package/dist/tools/builtins/edit.d.ts.map +1 -1
  124. package/dist/tools/builtins/edit.js +7 -2
  125. package/dist/tools/builtins/edit.js.map +1 -1
  126. package/dist/tools/builtins/shell-read-credit.d.ts +15 -0
  127. package/dist/tools/builtins/shell-read-credit.d.ts.map +1 -0
  128. package/dist/tools/builtins/shell-read-credit.js +161 -0
  129. package/dist/tools/builtins/shell-read-credit.js.map +1 -0
  130. package/dist/tools/paths.d.ts +15 -2
  131. package/dist/tools/paths.d.ts.map +1 -1
  132. package/dist/tools/paths.js +17 -2
  133. package/dist/tools/paths.js.map +1 -1
  134. package/dist/turn/evidence-recall.d.ts +1 -1
  135. package/dist/turn/evidence-recall.d.ts.map +1 -1
  136. package/dist/turn/evidence-recall.js +5 -14
  137. package/dist/turn/evidence-recall.js.map +1 -1
  138. package/dist/types/acp/index.d.ts +6 -0
  139. package/dist/types/acp/index.d.ts.map +1 -1
  140. package/dist/types/agent/base.d.ts +4 -0
  141. package/dist/types/agent/base.d.ts.map +1 -1
  142. package/dist/types/hitl/index.d.ts +8 -0
  143. package/dist/types/hitl/index.d.ts.map +1 -1
  144. package/dist/types/hitl/index.js.map +1 -1
  145. package/dist/types/ids/index.d.ts +1 -1
  146. package/dist/types/message/index.d.ts +1 -1
  147. package/dist/types/message/index.d.ts.map +1 -1
  148. package/dist/types/message/index.js +1 -0
  149. package/dist/types/message/index.js.map +1 -1
  150. package/dist/types/provider/interface.d.ts +12 -0
  151. package/dist/types/provider/interface.d.ts.map +1 -1
  152. package/dist/types/session/config.d.ts +25 -0
  153. package/dist/types/session/config.d.ts.map +1 -1
  154. package/dist/types/session/records.d.ts.map +1 -1
  155. package/dist/types/session/records.js +2 -0
  156. package/dist/types/session/records.js.map +1 -1
  157. package/dist/types/session/turn.d.ts +1 -1
  158. package/dist/types/session/turn.d.ts.map +1 -1
  159. package/dist/types/session/turn.js.map +1 -1
  160. package/package.json +1 -1
  161. package/src/agents/QueryAgent.ts +2 -0
  162. package/src/bridge/acp/permission.ts +8 -0
  163. package/src/bridge/acp/server.ts +7 -1
  164. package/src/constants/tools/index.ts +27 -0
  165. package/src/eval/harness-protection.ts +1 -1
  166. package/src/learning/cycle.ts +15 -16
  167. package/src/learning/guidance.ts +339 -0
  168. package/src/learning/skill.ts +7 -7
  169. package/src/learning/store.ts +5 -6
  170. package/src/peers/client.ts +2 -3
  171. package/src/peers/endpoint.ts +1 -2
  172. package/src/peers/protocol.ts +1 -0
  173. package/src/prompt/coding-agent-doctrine.ts +5 -3
  174. package/src/prompt/index.ts +0 -4
  175. package/src/provider/fallback.ts +7 -0
  176. package/src/provider/idle-timeout.ts +8 -0
  177. package/src/provider/model-window-suffix.ts +1 -0
  178. package/src/provider/retry.ts +8 -0
  179. package/src/provider/step-stopped.ts +27 -0
  180. package/src/provider/token-budget.ts +6 -1
  181. package/src/public-runtime.ts +6 -28
  182. package/src/public-tools.ts +0 -3
  183. package/src/public-types.ts +5 -137
  184. package/src/runtime/query/iteration/index.ts +207 -5
  185. package/src/runtime/query/iteration/phases/per-call-review.ts +7 -5
  186. package/src/runtime/query/iteration/phases/tool-review.ts +5 -3
  187. package/src/runtime/query/iteration/stream-turn.ts +51 -1
  188. package/src/runtime/query/resume-pending.ts +11 -4
  189. package/src/runtime/query/review-policy.ts +157 -24
  190. package/src/runtime/query/turn/completion-audit.ts +71 -0
  191. package/src/runtime/query/turn/stall-monitor.ts +158 -0
  192. package/src/session/paths.ts +2 -6
  193. package/src/tools/builtins/bash.ts +6 -2
  194. package/src/tools/builtins/content-fingerprint.ts +25 -0
  195. package/src/tools/builtins/edit.ts +11 -2
  196. package/src/tools/builtins/shell-read-credit.ts +159 -0
  197. package/src/tools/paths.ts +18 -2
  198. package/src/turn/evidence-recall.ts +7 -15
  199. package/src/types/acp/index.ts +6 -0
  200. package/src/types/agent/base.ts +4 -0
  201. package/src/types/hitl/index.ts +8 -0
  202. package/src/types/ids/index.ts +1 -1
  203. package/src/types/message/index.ts +1 -0
  204. package/src/types/provider/interface.ts +13 -0
  205. package/src/types/session/config.ts +27 -0
  206. package/src/types/session/records.ts +2 -0
  207. package/src/types/session/turn.ts +2 -1
  208. package/dist/learning/deprecated.d.ts +0 -86
  209. package/dist/learning/deprecated.d.ts.map +0 -1
  210. package/dist/learning/deprecated.js +0 -22
  211. package/dist/learning/deprecated.js.map +0 -1
  212. package/dist/manager/resident/activity.d.ts +0 -74
  213. package/dist/manager/resident/activity.d.ts.map +0 -1
  214. package/dist/manager/resident/activity.js +0 -125
  215. package/dist/manager/resident/activity.js.map +0 -1
  216. package/dist/manager/resident/agenda.d.ts +0 -167
  217. package/dist/manager/resident/agenda.d.ts.map +0 -1
  218. package/dist/manager/resident/agenda.js +0 -588
  219. package/dist/manager/resident/agenda.js.map +0 -1
  220. package/dist/manager/resident/consumption.d.ts +0 -98
  221. package/dist/manager/resident/consumption.d.ts.map +0 -1
  222. package/dist/manager/resident/consumption.js +0 -237
  223. package/dist/manager/resident/consumption.js.map +0 -1
  224. package/dist/manager/resident/delivery-window.d.ts +0 -28
  225. package/dist/manager/resident/delivery-window.d.ts.map +0 -1
  226. package/dist/manager/resident/delivery-window.js +0 -80
  227. package/dist/manager/resident/delivery-window.js.map +0 -1
  228. package/dist/manager/resident/evidence-recall.d.ts +0 -30
  229. package/dist/manager/resident/evidence-recall.d.ts.map +0 -1
  230. package/dist/manager/resident/evidence-recall.js +0 -297
  231. package/dist/manager/resident/evidence-recall.js.map +0 -1
  232. package/dist/manager/resident/history-disk.d.ts +0 -10
  233. package/dist/manager/resident/history-disk.d.ts.map +0 -1
  234. package/dist/manager/resident/history-disk.js +0 -50
  235. package/dist/manager/resident/history-disk.js.map +0 -1
  236. package/dist/manager/resident/history.d.ts +0 -115
  237. package/dist/manager/resident/history.d.ts.map +0 -1
  238. package/dist/manager/resident/history.js +0 -203
  239. package/dist/manager/resident/history.js.map +0 -1
  240. package/dist/manager/resident/host.d.ts +0 -112
  241. package/dist/manager/resident/host.d.ts.map +0 -1
  242. package/dist/manager/resident/host.js +0 -254
  243. package/dist/manager/resident/host.js.map +0 -1
  244. package/dist/manager/resident/initiative.d.ts +0 -86
  245. package/dist/manager/resident/initiative.d.ts.map +0 -1
  246. package/dist/manager/resident/initiative.js +0 -131
  247. package/dist/manager/resident/initiative.js.map +0 -1
  248. package/dist/manager/resident/loop.d.ts +0 -49
  249. package/dist/manager/resident/loop.d.ts.map +0 -1
  250. package/dist/manager/resident/loop.js +0 -75
  251. package/dist/manager/resident/loop.js.map +0 -1
  252. package/dist/manager/resident/outbox.d.ts +0 -124
  253. package/dist/manager/resident/outbox.d.ts.map +0 -1
  254. package/dist/manager/resident/outbox.js +0 -216
  255. package/dist/manager/resident/outbox.js.map +0 -1
  256. package/dist/manager/resident/proposal.d.ts +0 -65
  257. package/dist/manager/resident/proposal.d.ts.map +0 -1
  258. package/dist/manager/resident/proposal.js +0 -77
  259. package/dist/manager/resident/proposal.js.map +0 -1
  260. package/dist/manager/resident/store.d.ts +0 -108
  261. package/dist/manager/resident/store.d.ts.map +0 -1
  262. package/dist/manager/resident/store.js +0 -205
  263. package/dist/manager/resident/store.js.map +0 -1
  264. package/dist/manager/resident/tool-evidence.d.ts +0 -112
  265. package/dist/manager/resident/tool-evidence.d.ts.map +0 -1
  266. package/dist/manager/resident/tool-evidence.js +0 -289
  267. package/dist/manager/resident/tool-evidence.js.map +0 -1
  268. package/dist/prompt/resident-learning.d.ts +0 -29
  269. package/dist/prompt/resident-learning.d.ts.map +0 -1
  270. package/dist/prompt/resident-learning.js +0 -126
  271. package/dist/prompt/resident-learning.js.map +0 -1
  272. package/dist/prompt/resident-step.d.ts +0 -42
  273. package/dist/prompt/resident-step.d.ts.map +0 -1
  274. package/dist/prompt/resident-step.js +0 -137
  275. package/dist/prompt/resident-step.js.map +0 -1
  276. package/dist/tools/resident-history.d.ts +0 -12
  277. package/dist/tools/resident-history.d.ts.map +0 -1
  278. package/dist/tools/resident-history.js +0 -82
  279. package/dist/tools/resident-history.js.map +0 -1
  280. package/dist/tools/resident-tool-evidence.d.ts +0 -9
  281. package/dist/tools/resident-tool-evidence.d.ts.map +0 -1
  282. package/dist/tools/resident-tool-evidence.js +0 -61
  283. package/dist/tools/resident-tool-evidence.js.map +0 -1
  284. package/src/learning/deprecated.ts +0 -124
  285. package/src/manager/resident/activity.ts +0 -211
  286. package/src/manager/resident/agenda.ts +0 -937
  287. package/src/manager/resident/consumption.ts +0 -335
  288. package/src/manager/resident/delivery-window.ts +0 -99
  289. package/src/manager/resident/evidence-recall.ts +0 -369
  290. package/src/manager/resident/history-disk.ts +0 -63
  291. package/src/manager/resident/history.ts +0 -348
  292. package/src/manager/resident/host.ts +0 -390
  293. package/src/manager/resident/initiative.ts +0 -231
  294. package/src/manager/resident/loop.ts +0 -119
  295. package/src/manager/resident/outbox.ts +0 -341
  296. package/src/manager/resident/proposal.ts +0 -119
  297. package/src/manager/resident/store.ts +0 -279
  298. package/src/manager/resident/tool-evidence.ts +0 -455
  299. package/src/prompt/resident-learning.ts +0 -152
  300. package/src/prompt/resident-step.ts +0 -182
  301. package/src/tools/resident-history.ts +0 -88
  302. package/src/tools/resident-tool-evidence.ts +0 -72
@@ -29,6 +29,8 @@ import { diffRequestContext, snapshotRequestContext, } from '../request-context.
29
29
  import { DEFAULT_MAX_REQUEST_RICH_CONTENT_BYTES, markProviderRejectedImage, projectRequestRichContent, } from '../request-rich-content.js';
30
30
  import { withoutVoidAssistantTurns } from '../request-void-turns.js';
31
31
  import { formatSteeringNote, isOperatorUserMessage } from '../steering.js';
32
+ import { COMPLETION_AUDIT_MESSAGE, CompletionAudit } from '../turn/completion-audit.js';
33
+ import { FORCED_STEP_MESSAGE, REASONING_CAPPED_MESSAGE, REASONING_ONLY_MESSAGE, StallMonitor, callChangedState, reasoningCapChars, } from '../turn/stall-monitor.js';
32
34
  import { effortForModel } from './effort-clamp.js';
33
35
  import { parseNativeCandidate } from './native-output.js';
34
36
  import { drainArrivedTaskCompletions, holdForOutstandingWork, settleOutstandingWork, } from './outstanding-work.js';
@@ -46,6 +48,8 @@ import { RequestRepair } from './request-repair.js';
46
48
  import { placeRequestContext } from './step-context.js';
47
49
  import { beforeStep, prepareStep, selectContextModel, stepContextMessage, workContext, } from './step-shaping.js';
48
50
  import { streamProviderTurn } from './stream-turn.js';
51
+ /** Capped steps in a row after which the per-step cap stops applying for the turn's next step. */
52
+ const MAX_CAPPED_STREAK = 3;
49
53
  /** A host reviewer is not the model transport, even when its cause is an HTTP failure. */
50
54
  class AnswerReviewFailure extends NamzuError {
51
55
  constructor(cause) {
@@ -193,6 +197,16 @@ export class IterationOrchestrator {
193
197
  stopDeferredForOutstandingWork = false;
194
198
  /** Nudges sent since the model last finished a call without hitting its output cap. */
195
199
  outputContinuations = 0;
200
+ /** Watches the turn's steps for reasoning that piles up while nothing changes. */
201
+ stallMonitor;
202
+ /** Holds the turn back once when it changed files and checked nothing afterwards. */
203
+ completionAudit;
204
+ /** The next step must call a tool, at low effort: set by the stall monitor or a capped step. */
205
+ forceActionNextStep = false;
206
+ /** Steps in a row cut at the per-step reasoning cap; past a few the cap stops applying. */
207
+ cappedStreak = 0;
208
+ /** A reply that held only reasoning was already answered with one more request. */
209
+ reasoningOnlyRetried = false;
196
210
  /**
197
211
  * The model a refused request moved to. Once set it replaces the model the
198
212
  * step asked for, for the rest of the turn: the request that was refused
@@ -275,6 +289,11 @@ export class IterationOrchestrator {
275
289
  this.rememberUserMessage(createRuntimeContextMessage(text, 'steering'));
276
290
  },
277
291
  };
292
+ if (ctx.turnConfig.completionAudit === true)
293
+ this.completionAudit = new CompletionAudit();
294
+ if (ctx.turnConfig.reasoning?.stall !== false) {
295
+ this.stallMonitor = new StallMonitor(ctx.turnConfig.reasoning?.stall || undefined);
296
+ }
278
297
  ctx.checkpointMgr.setLatestUserMessageSource(() => this.latestUserMessage);
279
298
  ctx.checkpointMgr.setAnswerReviewAttemptsSource?.(() => this.answerReviewAttempts);
280
299
  ctx.checkpointMgr.setStructuredReviewAttemptsSource?.(() => this.structuredReviewAttempts);
@@ -871,6 +890,11 @@ export class IterationOrchestrator {
871
890
  // died part-way has already emitted both `message_started` and
872
891
  // `message_completed` under this id, which is the trail a
873
892
  // reader wants most on exactly that turn.
893
+ // A step the loop forces to act runs at low effort with a tool call
894
+ // required, for this step only. Low is used only where the model
895
+ // is known to offer it; effort is refused, not clamped.
896
+ const forcedAction = this.forceActionNextStep && !forceFinalize && llmTools.length > 0;
897
+ this.forceActionNextStep = false;
874
898
  stepMessageId = generateMessageId();
875
899
  const requestedMember = this.ctx.servingMember?.() ?? {
876
900
  index: 0,
@@ -895,11 +919,22 @@ export class IterationOrchestrator {
895
919
  });
896
920
  }
897
921
  }
922
+ // A forced action step runs at low effort, where the model is known
923
+ // to offer it; effort is refused, not clamped.
924
+ const offeredEfforts = this.ctx.provider.reasoningEffortLevelsFor?.(stepModel);
925
+ const stepEffort = forcedAction && requestEffort && (offeredEfforts?.includes('low') ?? false)
926
+ ? 'low'
927
+ : requestEffort;
928
+ // A provider that refuses a forced tool choice (some models do, and
929
+ // so does manual extended thinking) is not sent one: the step keeps
930
+ // its low effort and asks in words, on this request only.
931
+ const forcedChoiceRefused = forcedAction &&
932
+ this.ctx.provider.acceptsForcedToolChoiceFor?.(stepModel, turnConfig.thinking) === false;
898
933
  const operatorInputAtDispatch = this.latestUserMessage;
899
934
  const latestReviewUserMessage = (this.ctx.reviewAnswer || this.ctx.structuredOutput?.review) && operatorInputAtDispatch
900
935
  ? structuredClone(operatorInputAtDispatch)
901
936
  : undefined;
902
- const { response, messageId, requestMessages } = yield* streamProviderTurn(this.ctx.provider, {
937
+ const { response, messageId, requestMessages, reasoningCapped = false, } = yield* streamProviderTurn(this.ctx.provider, {
903
938
  model: stepModel,
904
939
  ...(this.ctx.structuredOutput?.mode === 'native'
905
940
  ? {
@@ -914,7 +949,9 @@ export class IterationOrchestrator {
914
949
  }
915
950
  : {}),
916
951
  providerRoute: requestedRoute,
917
- messages,
952
+ messages: forcedChoiceRefused
953
+ ? [...messages, createRuntimeContextMessage(FORCED_STEP_MESSAGE, 'progress-nudge')]
954
+ : messages,
918
955
  tools: llmTools.length > 0 ? llmTools : undefined,
919
956
  ...(enforceToolInputSchema ? { enforceToolInputSchema } : {}),
920
957
  // The forced-final turn wins: a step that asked to force a
@@ -925,13 +962,15 @@ export class IterationOrchestrator {
925
962
  toolChoice: forceFinalize && llmTools.length > 0
926
963
  ? 'none'
927
964
  : llmTools.length > 0
928
- ? step.toolChoice
965
+ ? forcedAction && !forcedChoiceRefused
966
+ ? (step.toolChoice ?? 'required')
967
+ : step.toolChoice
929
968
  : undefined,
930
969
  temperature: step.temperature ?? turnConfig.temperature,
931
970
  maxTokens: outputCapOverride ?? step.maxResponseTokens ?? turnConfig.maxResponseTokens,
932
971
  cacheControl: { type: 'auto' },
933
972
  ...(turnConfig.thinking ? { thinking: turnConfig.thinking } : {}),
934
- ...(requestEffort ? { effort: requestEffort } : {}),
973
+ ...(stepEffort ? { effort: stepEffort } : {}),
935
974
  ...(!forceFinalize && turnConfig.webSearch
936
975
  ? { webSearch: turnConfig.webSearch }
937
976
  : {}),
@@ -943,8 +982,15 @@ export class IterationOrchestrator {
943
982
  onAccepted: (identity) => this.acceptProviderRejectedImage(identity),
944
983
  textOnly: this.textOnlyRecovery(stepModel),
945
984
  repair: this.requestRepair,
946
- }, Boolean(this.ctx.reviewAnswer || this.ctx.structuredOutput?.review || this.ctx.advisoryCtx), recorder.sessionId);
985
+ }, Boolean(this.ctx.reviewAnswer || this.ctx.structuredOutput?.review || this.ctx.advisoryCtx), recorder.sessionId,
986
+ // Applies only to a step that can act. Stops after a few capped
987
+ // steps in a row, so a model that cannot act within the allowance
988
+ // still gets to finish.
989
+ this.cappedStreak >= MAX_CAPPED_STREAK || forceFinalize || llmTools.length === 0
990
+ ? undefined
991
+ : reasoningCapChars(turnConfig.reasoning, stepEffort));
947
992
  stepResponse = response;
993
+ this.cappedStreak = reasoningCapped ? this.cappedStreak + 1 : 0;
948
994
  const reviewRequest = {
949
995
  ...(requestMessages ? { requestMessages } : {}),
950
996
  ...(latestReviewUserMessage ? { latestUserMessage: latestReviewUserMessage } : {}),
@@ -1206,6 +1252,25 @@ export class IterationOrchestrator {
1206
1252
  usageBefore,
1207
1253
  costBefore,
1208
1254
  });
1255
+ // The step spent its whole reasoning allowance before acting. Tell
1256
+ // the model so and make the next step an action step.
1257
+ if (reasoningCapped && !forceFinalize) {
1258
+ this.ctx.log.info('Reasoning reached the per-step cap — asking the model to act', {
1259
+ [NAMZU.TURN_ID]: recorder.turnId,
1260
+ [NAMZU.ITERATION]: iterationNum,
1261
+ });
1262
+ recorder.pushMessage(createRuntimeContextMessage(REASONING_CAPPED_MESSAGE, 'progress-nudge'));
1263
+ this.forceActionNextStep = true;
1264
+ this.observeStall(response, []);
1265
+ await this.ctx.emitEvent({
1266
+ type: 'iteration_completed',
1267
+ turnId: recorder.turnId,
1268
+ iteration: iterationNum,
1269
+ hasToolCalls: false,
1270
+ });
1271
+ yield* this.ctx.drainPending();
1272
+ continue;
1273
+ }
1209
1274
  if (this.ctx.structuredOutput?.mode === 'native') {
1210
1275
  const candidate = await parseNativeCandidate(this.ctx.structuredOutput.schema, response, this.ctx.abortController.signal);
1211
1276
  let outcome;
@@ -1445,6 +1510,27 @@ export class IterationOrchestrator {
1445
1510
  let closingStopReason = forceFinalize
1446
1511
  ? guardResult.stopReason
1447
1512
  : undefined;
1513
+ const reasoningOnly = !hasContent && (response.message.reasoning?.length ?? 0) > 0;
1514
+ if (hasContent)
1515
+ this.reasoningOnlyRetried = false;
1516
+ // A reply of reasoning alone is not an end: ask once more, plainly,
1517
+ // before the closing request below.
1518
+ if (reasoningOnly && !forceFinalize && !this.reasoningOnlyRetried) {
1519
+ this.reasoningOnlyRetried = true;
1520
+ this.ctx.log.warn('Reply held reasoning only — asking once more', {
1521
+ [NAMZU.TURN_ID]: recorder.turnId,
1522
+ [NAMZU.ITERATION]: iterationNum,
1523
+ });
1524
+ recorder.pushMessage(createRuntimeContextMessage(REASONING_ONLY_MESSAGE, 'progress-nudge'));
1525
+ await this.ctx.emitEvent({
1526
+ type: 'iteration_completed',
1527
+ turnId: recorder.turnId,
1528
+ iteration: iterationNum,
1529
+ hasToolCalls: false,
1530
+ });
1531
+ yield* this.ctx.drainPending();
1532
+ continue;
1533
+ }
1448
1534
  if (!hasContent && !forceFinalize) {
1449
1535
  this.ctx.log.warn('Empty completion detected — requesting final summary', {
1450
1536
  [NAMZU.ITERATION]: iterationNum,
@@ -1452,6 +1538,13 @@ export class IterationOrchestrator {
1452
1538
  });
1453
1539
  closingStopReason = await this.requestFinalResponse(this.refusalPinnedModel ?? model, 'end_turn');
1454
1540
  yield* this.ctx.drainPending();
1541
+ // Never end silently: if even the closing request produced nothing,
1542
+ // say so in plain words and how to go on.
1543
+ if (reasoningOnly &&
1544
+ closingStopReason === undefined &&
1545
+ !([...recorder.messages].reverse().find((m) => m.role === 'assistant')?.content ?? '').length) {
1546
+ recorder.setLastError('The model stopped after thinking without giving an answer or taking an action, even when asked again. Say "continue" and it will pick the work up from here.');
1547
+ }
1455
1548
  }
1456
1549
  await this.ctx.emitEvent({
1457
1550
  type: 'iteration_completed',
@@ -1483,6 +1576,17 @@ export class IterationOrchestrator {
1483
1576
  // The model is done and has no tool calls. A `turn_end` (or
1484
1577
  // `subagent_stop`) hook may refuse to let it be: its reason goes
1485
1578
  // back to the model and the turn goes on.
1579
+ // Files were changed and nothing checked them afterwards: ask once
1580
+ // for a check before the turn is allowed to end.
1581
+ if (!forceFinalize &&
1582
+ closingStopReason === undefined &&
1583
+ this.completionAudit?.takeBlock()) {
1584
+ this.ctx.log.info('Files changed and nothing checked them — asking for a check', {
1585
+ [NAMZU.TURN_ID]: recorder.turnId,
1586
+ });
1587
+ recorder.pushMessage(createRuntimeContextMessage(COMPLETION_AUDIT_MESSAGE, 'progress-nudge'));
1588
+ continue;
1589
+ }
1486
1590
  if (!forceFinalize && closingStopReason === undefined) {
1487
1591
  const feedback = yield* this.askStopHooks();
1488
1592
  const stoppedByHook = this.ctx.toolExecutor.takeHalt?.();
@@ -1520,6 +1624,9 @@ export class IterationOrchestrator {
1520
1624
  usageBefore,
1521
1625
  costBefore,
1522
1626
  });
1627
+ this.reasoningOnlyRetried = false;
1628
+ this.observeStall(response, reviewOutcome.results);
1629
+ this.observeAudit(response, reviewOutcome.results);
1523
1630
  if (reviewOutcome.decision === 'stop') {
1524
1631
  return;
1525
1632
  }
@@ -2645,6 +2752,62 @@ export class IterationOrchestrator {
2645
2752
  return undefined;
2646
2753
  }
2647
2754
  }
2755
+ /** Feed the completion audit the calls of one finished step. */
2756
+ observeAudit(response, results) {
2757
+ const audit = this.completionAudit;
2758
+ if (!audit)
2759
+ return;
2760
+ const calls = new Map((response.message.toolCalls ?? []).map((c) => [c.id, c]));
2761
+ audit.observe(results.map((result) => {
2762
+ let input;
2763
+ try {
2764
+ const call = calls.get(result.toolCallId);
2765
+ input = call ? JSON.parse(call.function.arguments) : undefined;
2766
+ }
2767
+ catch {
2768
+ input = undefined;
2769
+ }
2770
+ return { toolName: result.toolName, input, isError: result.isError };
2771
+ }));
2772
+ }
2773
+ /**
2774
+ * Feed the stall monitor one finished step and act on its verdict: a nudge
2775
+ * is recorded as a message, a force makes the next step an action step.
2776
+ */
2777
+ observeStall(response, results) {
2778
+ const monitor = this.stallMonitor;
2779
+ if (!monitor)
2780
+ return;
2781
+ const calls = new Map((response.message.toolCalls ?? []).map((c) => [c.id, c]));
2782
+ let changedState = false;
2783
+ for (const result of results) {
2784
+ const call = calls.get(result.toolCallId);
2785
+ let readOnly;
2786
+ try {
2787
+ const input = call ? JSON.parse(call.function.arguments) : undefined;
2788
+ readOnly = this.ctx.tools.get(result.toolName)?.isReadOnly?.(input);
2789
+ }
2790
+ catch {
2791
+ readOnly = undefined;
2792
+ }
2793
+ if (callChangedState(result.toolName, readOnly, result.isError))
2794
+ changedState = true;
2795
+ }
2796
+ const reasoningChars = (response.message.reasoning ?? []).reduce((n, block) => n + (block.text?.length ?? 0), 0);
2797
+ const directive = monitor.observe({ reasoningChars, changedState });
2798
+ if (directive.kind === 'nudge') {
2799
+ this.ctx.log.info('Many steps without a change — nudging the model to act', {
2800
+ [NAMZU.TURN_ID]: this.ctx.recorder.turnId,
2801
+ });
2802
+ this.ctx.recorder.pushMessage(createRuntimeContextMessage(directive.text, 'progress-nudge'));
2803
+ }
2804
+ else if (directive.kind === 'force') {
2805
+ this.ctx.log.info('Still no change after the nudge — forcing an action step', {
2806
+ [NAMZU.TURN_ID]: this.ctx.recorder.turnId,
2807
+ });
2808
+ this.forceActionNextStep = true;
2809
+ }
2810
+ }
2648
2811
  async requestFinalResponse(model, reason) {
2649
2812
  if (this.ctx.structuredOutput?.mode === 'native')
2650
2813
  return 'structured_output_failed';