@namzu/sdk 1.0.0 → 1.2.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 (330) hide show
  1. package/CHANGELOG.md +253 -0
  2. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  3. package/dist/agents/ReactiveAgent.js +5 -0
  4. package/dist/agents/ReactiveAgent.js.map +1 -1
  5. package/dist/agents/SupervisorAgent.d.ts +22 -1
  6. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  7. package/dist/agents/SupervisorAgent.js +47 -14
  8. package/dist/agents/SupervisorAgent.js.map +1 -1
  9. package/dist/agents/__tests__/supervisor-ledger.test.d.ts +2 -0
  10. package/dist/agents/__tests__/supervisor-ledger.test.d.ts.map +1 -0
  11. package/dist/agents/__tests__/supervisor-ledger.test.js +131 -0
  12. package/dist/agents/__tests__/supervisor-ledger.test.js.map +1 -0
  13. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  14. package/dist/bridge/a2a/mapper.js +2 -0
  15. package/dist/bridge/a2a/mapper.js.map +1 -1
  16. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  17. package/dist/bridge/sse/mapper.js +4 -0
  18. package/dist/bridge/sse/mapper.js.map +1 -1
  19. package/dist/config/runtime.d.ts +24 -0
  20. package/dist/config/runtime.d.ts.map +1 -1
  21. package/dist/config/runtime.js +9 -0
  22. package/dist/config/runtime.js.map +1 -1
  23. package/dist/connector/builtins/webhook.test.js +17 -0
  24. package/dist/connector/builtins/webhook.test.js.map +1 -1
  25. package/dist/connector/index.d.ts +1 -0
  26. package/dist/connector/index.d.ts.map +1 -1
  27. package/dist/connector/index.js +1 -0
  28. package/dist/connector/index.js.map +1 -1
  29. package/dist/connector/mcp/client.d.ts.map +1 -1
  30. package/dist/connector/mcp/client.js +4 -0
  31. package/dist/connector/mcp/client.js.map +1 -1
  32. package/dist/connector/mcp/index.d.ts +1 -0
  33. package/dist/connector/mcp/index.d.ts.map +1 -1
  34. package/dist/connector/mcp/index.js +1 -0
  35. package/dist/connector/mcp/index.js.map +1 -1
  36. package/dist/connector/mcp/streamable-http.d.ts +22 -0
  37. package/dist/connector/mcp/streamable-http.d.ts.map +1 -0
  38. package/dist/connector/mcp/streamable-http.js +128 -0
  39. package/dist/connector/mcp/streamable-http.js.map +1 -0
  40. package/dist/connector/mcp/streamable-http.test.d.ts +2 -0
  41. package/dist/connector/mcp/streamable-http.test.d.ts.map +1 -0
  42. package/dist/connector/mcp/streamable-http.test.js +132 -0
  43. package/dist/connector/mcp/streamable-http.test.js.map +1 -0
  44. package/dist/{runtime/query → constants}/continuation.d.ts +5 -0
  45. package/dist/constants/continuation.d.ts.map +1 -0
  46. package/dist/{runtime/query → constants}/continuation.js +5 -0
  47. package/dist/constants/continuation.js.map +1 -0
  48. package/dist/constants/index.d.ts +1 -0
  49. package/dist/constants/index.d.ts.map +1 -1
  50. package/dist/constants/index.js +1 -0
  51. package/dist/constants/index.js.map +1 -1
  52. package/dist/manager/run/persistence.d.ts +11 -0
  53. package/dist/manager/run/persistence.d.ts.map +1 -1
  54. package/dist/manager/run/persistence.js +30 -1
  55. package/dist/manager/run/persistence.js.map +1 -1
  56. package/dist/persona/assembler.d.ts.map +1 -1
  57. package/dist/persona/assembler.js +18 -9
  58. package/dist/persona/assembler.js.map +1 -1
  59. package/dist/provider/capabilities.d.ts +27 -0
  60. package/dist/provider/capabilities.d.ts.map +1 -0
  61. package/dist/provider/capabilities.js +29 -0
  62. package/dist/provider/capabilities.js.map +1 -0
  63. package/dist/provider/collect.d.ts.map +1 -1
  64. package/dist/provider/collect.js +4 -1
  65. package/dist/provider/collect.js.map +1 -1
  66. package/dist/provider/index.d.ts +2 -0
  67. package/dist/provider/index.d.ts.map +1 -1
  68. package/dist/provider/index.js +1 -0
  69. package/dist/provider/index.js.map +1 -1
  70. package/dist/public-runtime.d.ts +4 -3
  71. package/dist/public-runtime.d.ts.map +1 -1
  72. package/dist/public-runtime.js +3 -3
  73. package/dist/public-runtime.js.map +1 -1
  74. package/dist/registry/tool/execute.d.ts +27 -0
  75. package/dist/registry/tool/execute.d.ts.map +1 -1
  76. package/dist/registry/tool/execute.js +124 -21
  77. package/dist/registry/tool/execute.js.map +1 -1
  78. package/dist/registry/tool/execute.test.d.ts +9 -6
  79. package/dist/registry/tool/execute.test.d.ts.map +1 -1
  80. package/dist/registry/tool/execute.test.js +72 -10
  81. package/dist/registry/tool/execute.test.js.map +1 -1
  82. package/dist/registry/toolset/catalog.test.js +62 -0
  83. package/dist/registry/toolset/catalog.test.js.map +1 -1
  84. package/dist/run/reporter.d.ts.map +1 -1
  85. package/dist/run/reporter.js +8 -0
  86. package/dist/run/reporter.js.map +1 -1
  87. package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts +2 -0
  88. package/dist/runtime/query/__tests__/capability-negotiation.test.d.ts.map +1 -0
  89. package/dist/runtime/query/__tests__/capability-negotiation.test.js +184 -0
  90. package/dist/runtime/query/__tests__/capability-negotiation.test.js.map +1 -0
  91. package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts +2 -0
  92. package/dist/runtime/query/__tests__/checkpoint-store.test.d.ts.map +1 -0
  93. package/dist/runtime/query/__tests__/checkpoint-store.test.js +259 -0
  94. package/dist/runtime/query/__tests__/checkpoint-store.test.js.map +1 -0
  95. package/dist/runtime/query/__tests__/checkpoint.test.js +9 -3
  96. package/dist/runtime/query/__tests__/checkpoint.test.js.map +1 -1
  97. package/dist/runtime/query/__tests__/deferred-tools.test.js +43 -5
  98. package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -1
  99. package/dist/runtime/query/__tests__/long-document-flow.test.d.ts +2 -0
  100. package/dist/runtime/query/__tests__/long-document-flow.test.d.ts.map +1 -0
  101. package/dist/runtime/query/__tests__/long-document-flow.test.js +154 -0
  102. package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -0
  103. package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts +2 -0
  104. package/dist/runtime/query/__tests__/orphan-task-turn-end.test.d.ts.map +1 -0
  105. package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js +110 -0
  106. package/dist/runtime/query/__tests__/orphan-task-turn-end.test.js.map +1 -0
  107. package/dist/runtime/query/__tests__/prompt.test.js +15 -11
  108. package/dist/runtime/query/__tests__/prompt.test.js.map +1 -1
  109. package/dist/runtime/query/checkpoint.d.ts +16 -2
  110. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  111. package/dist/runtime/query/checkpoint.js +21 -7
  112. package/dist/runtime/query/checkpoint.js.map +1 -1
  113. package/dist/runtime/query/context.d.ts +7 -0
  114. package/dist/runtime/query/context.d.ts.map +1 -1
  115. package/dist/runtime/query/context.js +1 -0
  116. package/dist/runtime/query/context.js.map +1 -1
  117. package/dist/runtime/query/index.d.ts +29 -0
  118. package/dist/runtime/query/index.d.ts.map +1 -1
  119. package/dist/runtime/query/index.js +98 -8
  120. package/dist/runtime/query/index.js.map +1 -1
  121. package/dist/runtime/query/iteration/index.d.ts +3 -55
  122. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  123. package/dist/runtime/query/iteration/index.js +260 -656
  124. package/dist/runtime/query/iteration/index.js.map +1 -1
  125. package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
  126. package/dist/runtime/query/iteration/phases/checkpoint.js +24 -2
  127. package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
  128. package/dist/runtime/query/iteration/phases/compaction.d.ts +8 -0
  129. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  130. package/dist/runtime/query/iteration/phases/compaction.js +84 -3
  131. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  132. package/dist/runtime/query/iteration/phases/context.d.ts +13 -2
  133. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/phases/context.js +53 -0
  135. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  136. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts +2 -0
  137. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts.map +1 -0
  138. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js +73 -0
  139. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js.map +1 -0
  140. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts +2 -0
  141. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts.map +1 -0
  142. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js +120 -0
  143. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js.map +1 -0
  144. package/dist/runtime/query/iteration/phases/tool-review.d.ts +1 -1
  145. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  146. package/dist/runtime/query/iteration/phases/tool-review.js +7 -2
  147. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  148. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts +19 -0
  149. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts.map +1 -0
  150. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js +177 -0
  151. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js.map +1 -0
  152. package/dist/runtime/query/iteration/phases/working-memory.d.ts +31 -0
  153. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -0
  154. package/dist/runtime/query/iteration/phases/working-memory.js +81 -0
  155. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -0
  156. package/dist/runtime/query/iteration/stream-turn.d.ts +35 -0
  157. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -0
  158. package/dist/runtime/query/iteration/stream-turn.js +341 -0
  159. package/dist/runtime/query/iteration/stream-turn.js.map +1 -0
  160. package/dist/runtime/query/replay/list.d.ts +17 -1
  161. package/dist/runtime/query/replay/list.d.ts.map +1 -1
  162. package/dist/runtime/query/replay/list.js +21 -4
  163. package/dist/runtime/query/replay/list.js.map +1 -1
  164. package/dist/runtime/query/replay/prepare.d.ts +10 -0
  165. package/dist/runtime/query/replay/prepare.d.ts.map +1 -1
  166. package/dist/runtime/query/replay/prepare.js +20 -4
  167. package/dist/runtime/query/replay/prepare.js.map +1 -1
  168. package/dist/session/workspace/__tests__/shared-run.test.js +1 -1
  169. package/dist/session/workspace/__tests__/shared-run.test.js.map +1 -1
  170. package/dist/session/workspace/shared-run.js +1 -0
  171. package/dist/session/workspace/shared-run.js.map +1 -1
  172. package/dist/skills/loader.test.js +8 -8
  173. package/dist/skills/loader.test.js.map +1 -1
  174. package/dist/store/index.d.ts +1 -0
  175. package/dist/store/index.d.ts.map +1 -1
  176. package/dist/store/index.js +1 -0
  177. package/dist/store/index.js.map +1 -1
  178. package/dist/store/run/checkpoint-disk.d.ts +28 -0
  179. package/dist/store/run/checkpoint-disk.d.ts.map +1 -0
  180. package/dist/store/run/checkpoint-disk.js +55 -0
  181. package/dist/store/run/checkpoint-disk.js.map +1 -0
  182. package/dist/streaming/coalesce.test.js.map +1 -1
  183. package/dist/tools/builtins/__tests__/payload-budget.test.js +48 -0
  184. package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -1
  185. package/dist/tools/builtins/__tests__/read-file.test.js +9 -0
  186. package/dist/tools/builtins/__tests__/read-file.test.js.map +1 -1
  187. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  188. package/dist/tools/builtins/read-file.js +50 -2
  189. package/dist/tools/builtins/read-file.js.map +1 -1
  190. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  191. package/dist/tools/builtins/search-tools.js +32 -7
  192. package/dist/tools/builtins/search-tools.js.map +1 -1
  193. package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts +2 -0
  194. package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts.map +1 -0
  195. package/dist/tools/coordinator/__tests__/approve-plan.test.js +114 -0
  196. package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -0
  197. package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts +2 -0
  198. package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts.map +1 -0
  199. package/dist/tools/coordinator/__tests__/ask-user-question.test.js +309 -0
  200. package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -0
  201. package/dist/tools/coordinator/index.d.ts +8 -0
  202. package/dist/tools/coordinator/index.d.ts.map +1 -1
  203. package/dist/tools/coordinator/index.js +195 -15
  204. package/dist/tools/coordinator/index.js.map +1 -1
  205. package/dist/types/agent/index.d.ts +1 -0
  206. package/dist/types/agent/index.d.ts.map +1 -1
  207. package/dist/types/agent/index.js +1 -0
  208. package/dist/types/agent/index.js.map +1 -1
  209. package/dist/types/agent/reactive.d.ts +15 -0
  210. package/dist/types/agent/reactive.d.ts.map +1 -1
  211. package/dist/types/agent/supervisor.d.ts +18 -0
  212. package/dist/types/agent/supervisor.d.ts.map +1 -1
  213. package/dist/types/agent/working-memory.d.ts +29 -0
  214. package/dist/types/agent/working-memory.d.ts.map +1 -0
  215. package/dist/types/agent/working-memory.js +2 -0
  216. package/dist/types/agent/working-memory.js.map +1 -0
  217. package/dist/types/common/index.d.ts +10 -0
  218. package/dist/types/common/index.d.ts.map +1 -1
  219. package/dist/types/common/index.js +18 -0
  220. package/dist/types/common/index.js.map +1 -1
  221. package/dist/types/common/usage-merge.test.d.ts +2 -0
  222. package/dist/types/common/usage-merge.test.d.ts.map +1 -0
  223. package/dist/types/common/usage-merge.test.js +48 -0
  224. package/dist/types/common/usage-merge.test.js.map +1 -0
  225. package/dist/types/connector/mcp.d.ts +9 -2
  226. package/dist/types/connector/mcp.d.ts.map +1 -1
  227. package/dist/types/hitl/index.d.ts +42 -0
  228. package/dist/types/hitl/index.d.ts.map +1 -1
  229. package/dist/types/hitl/index.js +11 -0
  230. package/dist/types/hitl/index.js.map +1 -1
  231. package/dist/types/provider/chat.d.ts +9 -0
  232. package/dist/types/provider/chat.d.ts.map +1 -1
  233. package/dist/types/provider/config.d.ts +18 -0
  234. package/dist/types/provider/config.d.ts.map +1 -1
  235. package/dist/types/provider/interface.d.ts +10 -0
  236. package/dist/types/provider/interface.d.ts.map +1 -1
  237. package/dist/types/run/checkpoint-store.d.ts +64 -0
  238. package/dist/types/run/checkpoint-store.d.ts.map +1 -0
  239. package/dist/types/run/checkpoint-store.js +12 -0
  240. package/dist/types/run/checkpoint-store.js.map +1 -0
  241. package/dist/types/run/config.d.ts +27 -0
  242. package/dist/types/run/config.d.ts.map +1 -1
  243. package/dist/types/run/events.d.ts +6 -0
  244. package/dist/types/run/events.d.ts.map +1 -1
  245. package/dist/types/run/events.js.map +1 -1
  246. package/dist/types/run/index.d.ts +1 -0
  247. package/dist/types/run/index.d.ts.map +1 -1
  248. package/dist/types/run/index.js +1 -0
  249. package/dist/types/run/index.js.map +1 -1
  250. package/dist/version.d.ts.map +1 -1
  251. package/dist/version.js +25 -4
  252. package/dist/version.js.map +1 -1
  253. package/package.json +1 -1
  254. package/src/agents/ReactiveAgent.ts +5 -0
  255. package/src/agents/SupervisorAgent.ts +56 -16
  256. package/src/agents/__tests__/supervisor-ledger.test.ts +185 -0
  257. package/src/bridge/a2a/mapper.ts +3 -0
  258. package/src/bridge/sse/mapper.ts +5 -0
  259. package/src/config/runtime.ts +9 -0
  260. package/src/connector/builtins/webhook.test.ts +21 -0
  261. package/src/connector/index.ts +1 -0
  262. package/src/connector/mcp/client.ts +4 -0
  263. package/src/connector/mcp/index.ts +1 -0
  264. package/src/connector/mcp/streamable-http.test.ts +169 -0
  265. package/src/connector/mcp/streamable-http.ts +152 -0
  266. package/src/{runtime/query → constants}/continuation.ts +5 -0
  267. package/src/constants/index.ts +1 -0
  268. package/src/manager/run/persistence.ts +34 -1
  269. package/src/persona/assembler.ts +18 -6
  270. package/src/provider/capabilities.ts +47 -0
  271. package/src/provider/collect.ts +4 -1
  272. package/src/provider/index.ts +5 -0
  273. package/src/public-runtime.ts +5 -0
  274. package/src/registry/tool/execute.test.ts +85 -10
  275. package/src/registry/tool/execute.ts +124 -21
  276. package/src/registry/toolset/catalog.test.ts +68 -0
  277. package/src/run/reporter.ts +9 -0
  278. package/src/runtime/query/__tests__/capability-negotiation.test.ts +248 -0
  279. package/src/runtime/query/__tests__/checkpoint-store.test.ts +322 -0
  280. package/src/runtime/query/__tests__/checkpoint.test.ts +15 -6
  281. package/src/runtime/query/__tests__/deferred-tools.test.ts +52 -5
  282. package/src/runtime/query/__tests__/long-document-flow.test.ts +180 -0
  283. package/src/runtime/query/__tests__/orphan-task-turn-end.test.ts +127 -0
  284. package/src/runtime/query/__tests__/prompt.test.ts +15 -11
  285. package/src/runtime/query/checkpoint.ts +23 -9
  286. package/src/runtime/query/context.ts +9 -0
  287. package/src/runtime/query/index.ts +148 -29
  288. package/src/runtime/query/iteration/index.ts +316 -831
  289. package/src/runtime/query/iteration/phases/checkpoint.ts +32 -2
  290. package/src/runtime/query/iteration/phases/compaction.ts +89 -3
  291. package/src/runtime/query/iteration/phases/context.ts +59 -3
  292. package/src/runtime/query/iteration/phases/hitl-abort-race.test.ts +86 -0
  293. package/src/runtime/query/iteration/phases/hitl-answer-question.test.ts +139 -0
  294. package/src/runtime/query/iteration/phases/tool-review.ts +7 -3
  295. package/src/runtime/query/iteration/phases/working-memory-compaction.test.ts +215 -0
  296. package/src/runtime/query/iteration/phases/working-memory.ts +86 -0
  297. package/src/runtime/query/iteration/stream-turn.ts +396 -0
  298. package/src/runtime/query/replay/list.ts +38 -4
  299. package/src/runtime/query/replay/prepare.ts +37 -4
  300. package/src/session/workspace/__tests__/shared-run.test.ts +1 -1
  301. package/src/session/workspace/shared-run.ts +1 -0
  302. package/src/skills/loader.test.ts +8 -8
  303. package/src/store/index.ts +1 -0
  304. package/src/store/run/checkpoint-disk.ts +67 -0
  305. package/src/streaming/coalesce.test.ts +3 -3
  306. package/src/tools/builtins/__tests__/payload-budget.test.ts +63 -0
  307. package/src/tools/builtins/__tests__/read-file.test.ts +12 -0
  308. package/src/tools/builtins/read-file.ts +50 -2
  309. package/src/tools/builtins/search-tools.ts +38 -7
  310. package/src/tools/coordinator/__tests__/approve-plan.test.ts +139 -0
  311. package/src/tools/coordinator/__tests__/ask-user-question.test.ts +370 -0
  312. package/src/tools/coordinator/index.ts +227 -17
  313. package/src/types/agent/index.ts +1 -0
  314. package/src/types/agent/reactive.ts +17 -0
  315. package/src/types/agent/supervisor.ts +20 -0
  316. package/src/types/agent/working-memory.ts +29 -0
  317. package/src/types/common/index.ts +19 -0
  318. package/src/types/common/usage-merge.test.ts +53 -0
  319. package/src/types/connector/mcp.ts +14 -2
  320. package/src/types/hitl/index.ts +52 -1
  321. package/src/types/provider/chat.ts +10 -0
  322. package/src/types/provider/config.ts +18 -0
  323. package/src/types/provider/interface.ts +11 -0
  324. package/src/types/run/checkpoint-store.ts +72 -0
  325. package/src/types/run/config.ts +30 -0
  326. package/src/types/run/events.ts +13 -0
  327. package/src/types/run/index.ts +1 -0
  328. package/src/version.ts +26 -4
  329. package/dist/runtime/query/continuation.d.ts.map +0 -1
  330. package/dist/runtime/query/continuation.js.map +0 -1
@@ -1,13 +1,43 @@
1
1
  import type { RunEvent } from '../../../../types/run/index.js'
2
2
  import { CheckpointManager } from '../../checkpoint.js'
3
- import { type IterationContext, type PhaseSignal, handleHITLDecision } from './context.js'
3
+ import {
4
+ type IterationContext,
5
+ type PhaseSignal,
6
+ awaitDecisionOrAbort,
7
+ handleHITLDecision,
8
+ } from './context.js'
9
+
10
+ /**
11
+ * Cadence gate for the per-iteration checkpoint (`runConfig.checkpointEvery`,
12
+ * default 1 = every iteration). Off-cadence iterations skip the whole phase —
13
+ * no checkpoint, no `checkpoint_created` event, and no HITL
14
+ * `iteration_checkpoint` park (there is no checkpoint id to park on).
15
+ * Iterations 1, 1+N, 1+2N, … checkpoint, so the first tool iteration is
16
+ * always covered (a crash before the first cadence hit would otherwise leave
17
+ * nothing to resume from).
18
+ */
19
+ function isOnCheckpointCadence(iterationNum: number, checkpointEvery: number | undefined): boolean {
20
+ const every = Math.max(1, Math.floor(checkpointEvery ?? 1))
21
+ return (iterationNum - 1) % every === 0
22
+ }
4
23
 
5
24
  export async function* runIterationCheckpoint(
6
25
  ctx: IterationContext,
7
26
  iterationNum: number,
8
27
  ): AsyncGenerator<RunEvent, PhaseSignal> {
28
+ if (!isOnCheckpointCadence(iterationNum, ctx.runConfig.checkpointEvery)) {
29
+ return 'continue'
30
+ }
31
+
9
32
  const iterCheckpoint = await ctx.checkpointMgr.create(ctx.runMgr, iterationNum)
10
33
 
34
+ // Growth control: keep only the newest N checkpoints when the host asked
35
+ // for pruning. Default undefined ⇒ never prune (today's behavior).
36
+ const pruneKeepLast = ctx.runConfig.pruneKeepLast
37
+ if (pruneKeepLast !== undefined && pruneKeepLast >= 1) {
38
+ await ctx.checkpointMgr.prune(Math.floor(pruneKeepLast))
39
+ }
40
+
11
41
  await ctx.emitEvent({
12
42
  type: 'checkpoint_created',
13
43
  runId: ctx.runMgr.id,
@@ -17,7 +47,7 @@ export async function* runIterationCheckpoint(
17
47
  yield* ctx.drainPending()
18
48
 
19
49
  const summary = CheckpointManager.buildSummary(ctx.runMgr, iterationNum)
20
- const iterDecision = await ctx.resumeHandler({
50
+ const iterDecision = await awaitDecisionOrAbort(ctx, {
21
51
  type: 'iteration_checkpoint',
22
52
  runId: ctx.runMgr.id,
23
53
  checkpointId: iterCheckpoint.id,
@@ -1,12 +1,37 @@
1
+ import { findSafeTrimIndex } from '../../../../compaction/dangling.js'
1
2
  import { serializeState } from '../../../../compaction/serializer.js'
2
3
  import { buildVerifiedSummary } from '../../../../compaction/verifier.js'
3
4
  import { CHARS_PER_TOKEN } from '../../../../constants/limits.js'
4
5
  import { createSystemMessage } from '../../../../types/message/index.js'
5
6
  import type { IterationContext } from './context.js'
7
+ import { isWorkingMemoryMessage } from './working-memory.js'
6
8
 
7
9
  const COMPACTION_HEADER =
8
10
  '[COMPACTED CONTEXT] The following is a structured summary of the conversation so far.'
9
11
 
12
+ /**
13
+ * Identity check for a prior compaction summary in the leading floor. Used to
14
+ * REPLACE one in place on the per-iteration (contextWindowTokens) path so at
15
+ * most one `[COMPACTED CONTEXT]` block ever lives in the never-trimmed floor,
16
+ * and by the checkpoint-restore path to PRESERVE the summary across a resume
17
+ * (it is the only surviving record of the older history the run compacted away).
18
+ */
19
+ export function isCompactionMessage(content: string | null | undefined): boolean {
20
+ return typeof content === 'string' && content.startsWith(COMPACTION_HEADER)
21
+ }
22
+
23
+ /**
24
+ * Minimum number of compactable (older) messages required before a compaction
25
+ * pass is worth running. When there is NOTHING between the never-trimmed
26
+ * leading floor and the recent window, a pass would only replace nothing with a
27
+ * `[COMPACTED CONTEXT]` summary that joins the floor — pure overhead that fires
28
+ * again next iteration (the permanent-floor thrash, ses_055 D7). Set to the
29
+ * literal EMPTY guard so any existing compaction consumer with ≥1 older message
30
+ * stays byte-identical; the per-iteration Vandal path additionally defangs the
31
+ * cost with `llmVerification:false`.
32
+ */
33
+ const MIN_OLDER_MESSAGES_TO_COMPACT = 1
34
+
10
35
  function estimateTokens(ctx: IterationContext): number {
11
36
  let chars = 0
12
37
  for (const msg of ctx.runMgr.messages) {
@@ -31,7 +56,13 @@ export async function runCompactionCheck(ctx: IterationContext): Promise<void> {
31
56
  if (!manager) return
32
57
 
33
58
  const estimatedTokens = estimateTokens(ctx)
34
- const budget = ctx.runConfig.tokenBudget
59
+ // F1: measure the CURRENT window against the model context window when the
60
+ // host supplies one, falling back to the run-level cumulative `tokenBudget`
61
+ // otherwise. Repointing the SINGLE `budget` assignment moves BOTH the
62
+ // `<= 0` guard AND the divisor at once — so "compaction on + tokenBudget=0"
63
+ // is no longer a silent no-op. Existing consumers (no contextWindowTokens)
64
+ // stay byte-identical.
65
+ const budget = config.contextWindowTokens ?? ctx.runConfig.tokenBudget
35
66
  if (budget <= 0) return
36
67
 
37
68
  const usage = estimatedTokens / budget
@@ -63,10 +94,34 @@ export async function runCompactionCheck(ctx: IterationContext): Promise<void> {
63
94
  }
64
95
  if (systemMessages.length === 0) return
65
96
 
66
- const keepStart = messages.length - config.keepRecentMessages
97
+ // Tool-pair atomicity guard. A naive cut at `length - keepRecentMessages`
98
+ // can land BETWEEN an assistant-with-toolCalls (which would be dropped into
99
+ // `olderMessages`) and its `tool` results (kept in `recentMessages`),
100
+ // leaving orphaned `tool_result` blocks at the head of the recent window.
101
+ // The Anthropic provider then emits a `tool_result` with no matching
102
+ // `tool_use` and the API rejects the next turn with a 400 — so compaction,
103
+ // whose whole job is to keep a long run alive, instead kills it. Snap the
104
+ // boundary FORWARD to a safe point (existing `findSafeTrimIndex`, previously
105
+ // only wired to the unused ConversationManager strategy classes) so no pair
106
+ // is split. Any message this moves out of the recent window is already
107
+ // represented in the extracted WorkingState the summary is built from.
108
+ const keepStart = findSafeTrimIndex(messages, messages.length - config.keepRecentMessages)
67
109
  const recentMessages = messages.slice(keepStart)
68
110
  const olderMessages = messages.slice(systemMessages.length, keepStart)
69
111
 
112
+ // D7: nothing meaningful to compact — skip instead of thrashing the
113
+ // permanent leading floor every iteration (and avoid an LLM verification
114
+ // call when there is no older history to summarize). Scoped to the new
115
+ // contextWindowTokens path so any existing consumer (tokenBudget-only) keeps
116
+ // its exact prior behavior — byte-identical (ses_055 verify).
117
+ if (config.contextWindowTokens != null && olderMessages.length < MIN_OLDER_MESSAGES_TO_COMPACT) {
118
+ ctx.log.debug('Skipping compaction — too few older messages', {
119
+ runId: ctx.runMgr.id,
120
+ olderMessages: olderMessages.length,
121
+ })
122
+ return
123
+ }
124
+
70
125
  let compactedContent: string
71
126
 
72
127
  if (config.llmVerification && manager.slotCount() < config.richStateThreshold) {
@@ -77,7 +132,38 @@ export async function runCompactionCheck(ctx: IterationContext): Promise<void> {
77
132
 
78
133
  const compactionMessage = createSystemMessage(`${COMPACTION_HEADER}\n\n${compactedContent}`)
79
134
 
80
- const newMessages = [...systemMessages, compactionMessage, ...recentMessages]
135
+ // On the new per-iteration (contextWindowTokens) path, drop any PRIOR
136
+ // `[COMPACTED CONTEXT]` summary from the leading floor — `serializeState` is
137
+ // cumulative, so the new summary supersedes it. Without this the never-trimmed
138
+ // floor accumulates one redundant summary per pass and the frequent
139
+ // per-iteration trigger makes that bloat unbounded (ses_055 verify, MEDIUM).
140
+ // Legacy consumers (no contextWindowTokens) keep their exact prior
141
+ // accumulation behavior — byte-identical.
142
+ const preservedSystem =
143
+ config.contextWindowTokens != null
144
+ ? systemMessages.filter((m) => !isCompactionMessage(m.content))
145
+ : systemMessages
146
+ const newMessages = [...preservedSystem, compactionMessage, ...recentMessages]
147
+
148
+ // OPAQUE survival guard (ses_055 D1): the pinned working-memory slot is a
149
+ // leading system message, so it is kept in `preservedSystem` (the compaction
150
+ // filter only drops prior `[COMPACTED CONTEXT]` summaries, never the WM slot)
151
+ // and survives for free — this branch is DEFENSIVE-ONLY, exercised only if a
152
+ // future change drops the slot from the rebuilt set. It re-pins the block
153
+ // already present in `messages` (the one `refreshWorkingMemory` placed).
154
+ // Identity is the sentinel HEADER only — no path parsing, no second provider
155
+ // call, no host format knowledge in the SDK.
156
+ const survives = newMessages.some((m) => m.role === 'system' && isWorkingMemoryMessage(m.content))
157
+ if (!survives) {
158
+ const priorSlot = messages.find((m) => m.role === 'system' && isWorkingMemoryMessage(m.content))
159
+ if (priorSlot) {
160
+ // Re-pin as the last leading system message, before the summary.
161
+ newMessages.splice(preservedSystem.length, 0, priorSlot)
162
+ ctx.log.warn('Re-pinned working-memory slot dropped by compaction', {
163
+ runId: ctx.runMgr.id,
164
+ })
165
+ }
166
+ }
81
167
 
82
168
  const oldCount = messages.length
83
169
  messages.length = 0
@@ -5,7 +5,8 @@ import type { CompactionConfig } from '../../../../config/runtime.js'
5
5
  import type { PlanManager } from '../../../../manager/plan/lifecycle.js'
6
6
  import type { RunPersistence } from '../../../../manager/run/persistence.js'
7
7
  import type { ActivityStore } from '../../../../store/activity/memory.js'
8
- import type { TaskGateway, TaskHandle } from '../../../../types/agent/gateway.js'
8
+ import type { TaskGateway } from '../../../../types/agent/gateway.js'
9
+ import type { WorkingMemoryProvider } from '../../../../types/agent/working-memory.js'
9
10
  import type { HITLResumeDecision, ResumeHandler } from '../../../../types/hitl/index.js'
10
11
  import type { TaskId } from '../../../../types/ids/index.js'
11
12
  import type { LLMProvider } from '../../../../types/provider/index.js'
@@ -55,14 +56,14 @@ export interface IterationContext {
55
56
 
56
57
  readonly taskStore?: TaskStore
57
58
 
58
- readonly pendingNotifications: TaskHandle[]
59
-
60
59
  readonly launchedTasks: Map<TaskId, LaunchedTaskMeta>
61
60
 
62
61
  readonly compactionConfig?: CompactionConfig
63
62
 
64
63
  readonly workingStateManager?: WorkingStateManager
65
64
 
65
+ readonly workingMemoryProvider?: WorkingMemoryProvider
66
+
66
67
  readonly advisoryCtx?: AdvisoryContext
67
68
 
68
69
  readonly agentBus?: AgentBus
@@ -74,6 +75,57 @@ export interface IterationContext {
74
75
 
75
76
  export type PhaseSignal = 'continue' | 'stop'
76
77
 
78
+ /**
79
+ * Await a HITL `resumeHandler` decision, but RACE it against the run's abort
80
+ * signal. A Stop that arrives while the run is parked on a tool-review or
81
+ * iteration checkpoint used to do nothing until the host eventually answered
82
+ * (the park await was not cancellable). Racing the signal lets a Stop resolve
83
+ * the park immediately as an `abort` decision, which `handleHITLDecision`
84
+ * turns into `setStopReason('cancelled') + markCancelled + stop`. Fails closed:
85
+ * a resume-handler rejection also resolves to `abort` rather than hanging.
86
+ */
87
+ export async function awaitDecisionOrAbort(
88
+ ctx: IterationContext,
89
+ request: Parameters<ResumeHandler>[0],
90
+ ): Promise<HITLResumeDecision> {
91
+ const signal = ctx.abortController?.signal
92
+ // No abort signal wired (e.g. a minimal test harness) → behave exactly as a
93
+ // direct resumeHandler await, no race. In production RunContextFactory always
94
+ // provides the controller, so the race below is live.
95
+ if (!signal) return ctx.resumeHandler(request)
96
+ const abortDecision: HITLResumeDecision = {
97
+ action: 'abort',
98
+ reason: 'run aborted while parked for HITL',
99
+ }
100
+ if (signal.aborted) return abortDecision
101
+ return new Promise<HITLResumeDecision>((resolve) => {
102
+ let settled = false
103
+ const onAbort = (): void => {
104
+ if (settled) return
105
+ settled = true
106
+ resolve(abortDecision)
107
+ }
108
+ signal.addEventListener('abort', onAbort, { once: true })
109
+ Promise.resolve(ctx.resumeHandler(request)).then(
110
+ (decision) => {
111
+ if (settled) return
112
+ settled = true
113
+ signal.removeEventListener('abort', onAbort)
114
+ resolve(decision)
115
+ },
116
+ (err) => {
117
+ if (settled) return
118
+ settled = true
119
+ signal.removeEventListener('abort', onAbort)
120
+ resolve({
121
+ action: 'abort',
122
+ reason: err instanceof Error ? err.message : 'resume handler failed',
123
+ })
124
+ },
125
+ )
126
+ })
127
+ }
128
+
77
129
  export async function* handleHITLDecision(
78
130
  ctx: IterationContext,
79
131
  decision: HITLResumeDecision,
@@ -125,6 +177,10 @@ export async function* handleHITLDecision(
125
177
  case 'approve_tools':
126
178
  case 'modify_tools':
127
179
  case 'reject_tools':
180
+ // 'answer_question' can only arrive misdirected at an iteration
181
+ // checkpoint (answers are consumed inside the ask_user_question
182
+ // tool's own park); treat it as a plain continue.
183
+ case 'answer_question':
128
184
  return 'continue'
129
185
  default: {
130
186
  const _exhaustive: never = decision
@@ -0,0 +1,86 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import type { HITLResumeDecision } from '../../../../types/hitl/index.js'
4
+ import type { RunId } from '../../../../types/ids/index.js'
5
+ import { type IterationContext, awaitDecisionOrAbort } from './context.js'
6
+
7
+ /**
8
+ * C12: a Stop that arrives while the run is parked on a HITL decision must
9
+ * interrupt the park (resolve as `abort`) instead of hanging until the host
10
+ * eventually answers. `awaitDecisionOrAbort` races `resumeHandler` against
11
+ * `ctx.abortController.signal`.
12
+ */
13
+
14
+ function ctxWith(opts: {
15
+ controller?: AbortController
16
+ resumeHandler: IterationContext['resumeHandler']
17
+ }): IterationContext {
18
+ return {
19
+ abortController: opts.controller,
20
+ resumeHandler: opts.resumeHandler,
21
+ runMgr: { id: 'run_abort_test' as RunId },
22
+ } as unknown as IterationContext
23
+ }
24
+
25
+ const REVIEW_REQUEST = {
26
+ type: 'tool_review' as const,
27
+ runId: 'run_abort_test' as RunId,
28
+ checkpointId: 'cp_1' as `cp_${string}`,
29
+ toolCalls: [],
30
+ }
31
+
32
+ describe('awaitDecisionOrAbort (C12 — cancellable HITL park)', () => {
33
+ it('returns abort immediately when the signal is already aborted', async () => {
34
+ const controller = new AbortController()
35
+ controller.abort()
36
+ const ctx = ctxWith({
37
+ controller,
38
+ // A handler that never resolves — proving we did not wait on it.
39
+ resumeHandler: () => new Promise<HITLResumeDecision>(() => {}),
40
+ })
41
+ const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
42
+ expect(decision.action).toBe('abort')
43
+ })
44
+
45
+ it('resolves as abort when the signal fires DURING the park', async () => {
46
+ const controller = new AbortController()
47
+ const ctx = ctxWith({
48
+ controller,
49
+ resumeHandler: () => new Promise<HITLResumeDecision>(() => {}), // never answers
50
+ })
51
+ const pending = awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
52
+ controller.abort()
53
+ const decision = await pending
54
+ expect(decision.action).toBe('abort')
55
+ })
56
+
57
+ it('returns the real decision when the handler answers before any abort', async () => {
58
+ const controller = new AbortController()
59
+ const ctx = ctxWith({
60
+ controller,
61
+ resumeHandler: async () => ({ action: 'approve_tools' }) as HITLResumeDecision,
62
+ })
63
+ const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
64
+ expect(decision.action).toBe('approve_tools')
65
+ })
66
+
67
+ it('degrades to a direct resumeHandler await when no controller is wired', async () => {
68
+ const ctx = ctxWith({
69
+ resumeHandler: async () => ({ action: 'reject_tools' }) as HITLResumeDecision,
70
+ })
71
+ const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
72
+ expect(decision.action).toBe('reject_tools')
73
+ })
74
+
75
+ it('fails closed to abort when the resume handler rejects', async () => {
76
+ const controller = new AbortController()
77
+ const ctx = ctxWith({
78
+ controller,
79
+ resumeHandler: async () => {
80
+ throw new Error('handler boom')
81
+ },
82
+ })
83
+ const decision = await awaitDecisionOrAbort(ctx, REVIEW_REQUEST)
84
+ expect(decision.action).toBe('abort')
85
+ })
86
+ })
@@ -0,0 +1,139 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { type HITLResumeDecision, autoApproveHandler } from '../../../../types/hitl/index.js'
4
+ import type { CheckpointId, RunId } from '../../../../types/ids/index.js'
5
+ import type { ChatCompletionResponse } from '../../../../types/provider/index.js'
6
+ import type { RunEvent } from '../../../../types/run/index.js'
7
+ import { type IterationContext, handleHITLDecision } from './context.js'
8
+ import { runToolReview } from './tool-review.js'
9
+
10
+ async function drainGenerator<TReturn>(
11
+ gen: AsyncGenerator<RunEvent, TReturn>,
12
+ ): Promise<{ events: RunEvent[]; value: TReturn }> {
13
+ const events: RunEvent[] = []
14
+ let next = await gen.next()
15
+ while (!next.done) {
16
+ events.push(next.value)
17
+ next = await gen.next()
18
+ }
19
+ return { events, value: next.value }
20
+ }
21
+
22
+ describe('autoApproveHandler user_question case', () => {
23
+ it('returns the non-fabricating no-answer sentinel with the questionId echoed', async () => {
24
+ const decision = await autoApproveHandler({
25
+ type: 'user_question',
26
+ runId: 'run_headless_test' as RunId,
27
+ checkpointId: 'cp_question_toolu_1' as CheckpointId,
28
+ question: {
29
+ questionId: 'toolu_1',
30
+ question: 'Pick one?',
31
+ options: [
32
+ { id: 'opt_1', label: 'A' },
33
+ { id: 'opt_2', label: 'B' },
34
+ ],
35
+ multiSelect: false,
36
+ allowFreeText: true,
37
+ },
38
+ })
39
+
40
+ expect(decision).toEqual({
41
+ action: 'answer_question',
42
+ selectedOptionIds: [],
43
+ freeText: 'No user is available to answer. Proceed using your best judgment.',
44
+ questionId: 'toolu_1',
45
+ })
46
+ })
47
+ })
48
+
49
+ describe('handleHITLDecision answer_question case', () => {
50
+ it("treats a misdirected 'answer_question' at an iteration checkpoint as continue", async () => {
51
+ // The branch returns without touching the context — answers are
52
+ // consumed inside the ask_user_question tool's own park, so an
53
+ // answer arriving here can only be misdirected.
54
+ const ctx = {} as IterationContext
55
+ const { events, value } = await drainGenerator(
56
+ handleHITLDecision(
57
+ ctx,
58
+ { action: 'answer_question', selectedOptionIds: ['opt_1'] },
59
+ 'cp_checkpoint_1',
60
+ 'iteration checkpoint',
61
+ ),
62
+ )
63
+ expect(value).toBe('continue')
64
+ expect(events).toEqual([])
65
+ })
66
+ })
67
+
68
+ describe('runToolReview answer_question case', () => {
69
+ function reviewContext(decision: HITLResumeDecision) {
70
+ const warns: string[] = []
71
+ const pushed: unknown[] = []
72
+ let batches = 0
73
+ const ctx = {
74
+ tools: { get: () => undefined },
75
+ checkpointMgr: {
76
+ create: async () => ({ id: 'cp_review_1' as CheckpointId }),
77
+ },
78
+ emitEvent: async () => {},
79
+ drainPending: (): Generator<RunEvent> => [][Symbol.iterator]() as Generator<RunEvent>,
80
+ resumeHandler: async () => decision,
81
+ log: {
82
+ debug: () => {},
83
+ info: () => {},
84
+ warn: (message: string) => {
85
+ warns.push(message)
86
+ },
87
+ error: () => {},
88
+ },
89
+ runMgr: {
90
+ id: 'run_tool_review_test' as RunId,
91
+ pushMessage: (message: unknown) => {
92
+ pushed.push(message)
93
+ },
94
+ },
95
+ toolExecutor: {
96
+ executeBatch: async () => {
97
+ batches += 1
98
+ return { messages: [], results: [] }
99
+ },
100
+ },
101
+ } as unknown as IterationContext
102
+ return { ctx, warns, pushed, batchCount: () => batches }
103
+ }
104
+
105
+ const response: ChatCompletionResponse = {
106
+ id: 'resp_1',
107
+ model: 'test-model',
108
+ message: {
109
+ role: 'assistant',
110
+ content: null,
111
+ toolCalls: [
112
+ {
113
+ id: 'toolu_review_1',
114
+ type: 'function',
115
+ function: { name: 'ask_user_question', arguments: '{}' },
116
+ },
117
+ ],
118
+ },
119
+ finishReason: 'tool_calls',
120
+ usage: {
121
+ promptTokens: 0,
122
+ completionTokens: 0,
123
+ totalTokens: 0,
124
+ cachedTokens: 0,
125
+ cacheWriteTokens: 0,
126
+ },
127
+ }
128
+
129
+ it("warns and executes the batch on a misdirected 'answer_question' instead of stalling", async () => {
130
+ const { ctx, warns, batchCount } = reviewContext({
131
+ action: 'answer_question',
132
+ selectedOptionIds: [],
133
+ })
134
+ const { value } = await drainGenerator(runToolReview(ctx, response, 1))
135
+ expect(value).toBe('executed')
136
+ expect(batchCount()).toBe(1)
137
+ expect(warns).toHaveLength(1)
138
+ })
139
+ })
@@ -2,7 +2,7 @@ import { createUserMessage } from '../../../../types/message/index.js'
2
2
  import type { ChatCompletionResponse } from '../../../../types/provider/index.js'
3
3
  import type { RunEvent } from '../../../../types/run/index.js'
4
4
  import type { VerificationGate } from '../../../../verification/index.js'
5
- import type { IterationContext } from './context.js'
5
+ import { type IterationContext, awaitDecisionOrAbort } from './context.js'
6
6
 
7
7
  interface VerificationAwareContext extends IterationContext {
8
8
  readonly verificationGate?: VerificationGate
@@ -94,7 +94,7 @@ export async function* runToolReview(
94
94
  })
95
95
  yield* ctx.drainPending()
96
96
 
97
- const reviewDecision = await ctx.resumeHandler({
97
+ const reviewDecision = await awaitDecisionOrAbort(ctx, {
98
98
  type: 'tool_review',
99
99
  runId: ctx.runMgr.id,
100
100
  checkpointId: reviewCheckpoint.id,
@@ -198,7 +198,11 @@ export async function* runToolReview(
198
198
  }
199
199
 
200
200
  case 'approve_plan':
201
- case 'reject_plan': {
201
+ case 'reject_plan':
202
+ // 'answer_question' belongs to an ask_user_question park, not a
203
+ // tool review — like the misdirected plan decisions above, warn
204
+ // and proceed with execution rather than stalling the run.
205
+ case 'answer_question': {
202
206
  ctx.log.warn('Unexpected plan decision during tool review', {
203
207
  action: reviewDecision.action,
204
208
  })