@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
@@ -0,0 +1,322 @@
1
+ import { mkdtemp, rm } from 'node:fs/promises'
2
+ import { tmpdir } from 'node:os'
3
+ import { join } from 'node:path'
4
+ import { afterEach, describe, expect, it } from 'vitest'
5
+ import { z } from 'zod'
6
+
7
+ import type { RunPersistence } from '../../../manager/run/persistence.js'
8
+ import { ToolRegistry } from '../../../registry/tool/execute.js'
9
+ import type { CheckpointId, IterationCheckpoint } from '../../../types/hitl/index.js'
10
+ import type { RunId, SessionId, TenantId } from '../../../types/ids/index.js'
11
+ import { createUserMessage } from '../../../types/message/index.js'
12
+ import type { LLMProvider, StreamChunk } from '../../../types/provider/index.js'
13
+ import type { CheckpointRunScope, CheckpointStore } from '../../../types/run/checkpoint-store.js'
14
+ import type { RunEvent } from '../../../types/run/index.js'
15
+ import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
16
+ import { CheckpointManager } from '../checkpoint.js'
17
+ import { drainQuery } from '../index.js'
18
+ import { runIterationCheckpoint } from '../iteration/phases/checkpoint.js'
19
+ import type { IterationContext } from '../iteration/phases/context.js'
20
+
21
+ const ZERO_USAGE = {
22
+ promptTokens: 0,
23
+ completionTokens: 0,
24
+ totalTokens: 0,
25
+ cachedTokens: 0,
26
+ cacheWriteTokens: 0,
27
+ }
28
+
29
+ const ZERO_COST = {
30
+ inputCostPer1M: 0,
31
+ outputCostPer1M: 0,
32
+ totalCost: 0,
33
+ cacheDiscount: 0,
34
+ }
35
+
36
+ const SCOPE: CheckpointRunScope = {
37
+ tenantId: 'tnt_cp_store' as TenantId,
38
+ projectId: 'prj_cp_store' as ProjectId,
39
+ sessionId: 'ses_cp_store' as SessionId,
40
+ runId: 'run_cp_store' as RunId,
41
+ }
42
+
43
+ /**
44
+ * In-memory {@link CheckpointStore} used both as the conformance fixture for
45
+ * `CheckpointManager` and as the injected store in the query()-level test.
46
+ * Keys rows by the full scope so the tests can assert the kernel passes the
47
+ * five-layer attribution through, not just the checkpoint payload.
48
+ */
49
+ class InMemoryCheckpointStore implements CheckpointStore {
50
+ readonly rows = new Map<string, IterationCheckpoint>()
51
+ readonly seenScopes: CheckpointRunScope[] = []
52
+
53
+ private key(scope: CheckpointRunScope, checkpointId: CheckpointId): string {
54
+ return [scope.tenantId, scope.projectId, scope.sessionId, scope.runId, checkpointId].join('/')
55
+ }
56
+
57
+ private runPrefix(scope: CheckpointRunScope): string {
58
+ return `${[scope.tenantId, scope.projectId, scope.sessionId, scope.runId].join('/')}/`
59
+ }
60
+
61
+ async writeCheckpoint(scope: CheckpointRunScope, checkpoint: IterationCheckpoint): Promise<void> {
62
+ this.seenScopes.push(scope)
63
+ this.rows.set(this.key(scope, checkpoint.id), checkpoint)
64
+ }
65
+
66
+ async readCheckpoint(
67
+ scope: CheckpointRunScope,
68
+ checkpointId: CheckpointId,
69
+ ): Promise<IterationCheckpoint | null> {
70
+ return this.rows.get(this.key(scope, checkpointId)) ?? null
71
+ }
72
+
73
+ async listCheckpoints(scope: CheckpointRunScope): Promise<IterationCheckpoint[]> {
74
+ const prefix = this.runPrefix(scope)
75
+ return [...this.rows.entries()]
76
+ .filter(([key]) => key.startsWith(prefix))
77
+ .map(([, cp]) => cp)
78
+ .sort((a, b) => a.createdAt - b.createdAt)
79
+ }
80
+
81
+ async deleteCheckpoint(scope: CheckpointRunScope, checkpointId: CheckpointId): Promise<void> {
82
+ this.rows.delete(this.key(scope, checkpointId))
83
+ }
84
+ }
85
+
86
+ function makeRunMgrStub(): RunPersistence {
87
+ return {
88
+ id: SCOPE.runId,
89
+ messages: [{ role: 'user', content: 'hello' }],
90
+ tokenUsage: { ...ZERO_USAGE },
91
+ costInfo: { ...ZERO_COST },
92
+ currentIteration: 1,
93
+ getSession: () => ({ startedAt: Date.now() }),
94
+ } as unknown as RunPersistence
95
+ }
96
+
97
+ describe('CheckpointManager against an injected CheckpointStore', () => {
98
+ it('round-trips create → restore → list → prune through the interface', async () => {
99
+ const store = new InMemoryCheckpointStore()
100
+ const mgr = new CheckpointManager(store, SCOPE)
101
+ const runMgr = makeRunMgrStub()
102
+
103
+ const first = await mgr.create(runMgr, 1)
104
+ const second = await mgr.create(runMgr, 2)
105
+ // Deterministic ordering for prune (createdAt can tie at ms resolution).
106
+ const storedFirst = store.rows.get(
107
+ [SCOPE.tenantId, SCOPE.projectId, SCOPE.sessionId, SCOPE.runId, first.id].join('/'),
108
+ )
109
+ const storedSecond = store.rows.get(
110
+ [SCOPE.tenantId, SCOPE.projectId, SCOPE.sessionId, SCOPE.runId, second.id].join('/'),
111
+ )
112
+ if (storedFirst) storedFirst.createdAt = 1_000
113
+ if (storedSecond) storedSecond.createdAt = 2_000
114
+
115
+ // restore reads back through the interface
116
+ const restored = await mgr.restore(first.id)
117
+ expect(restored.id).toBe(first.id)
118
+ expect(restored.iteration).toBe(1)
119
+
120
+ // list sees both, oldest first
121
+ const listed = await mgr.list()
122
+ expect(listed.map((cp) => cp.id)).toEqual([first.id, second.id])
123
+
124
+ // prune deletes oldest-first through deleteCheckpoint
125
+ await mgr.prune(1)
126
+ const remaining = await mgr.list()
127
+ expect(remaining.map((cp) => cp.id)).toEqual([second.id])
128
+
129
+ // every write carried the full five-layer scope
130
+ expect(store.seenScopes.every((s) => s.tenantId === SCOPE.tenantId)).toBe(true)
131
+ expect(store.seenScopes.every((s) => s.sessionId === SCOPE.sessionId)).toBe(true)
132
+ })
133
+
134
+ it('restore throws a descriptive error for a missing checkpoint', async () => {
135
+ const mgr = new CheckpointManager(new InMemoryCheckpointStore(), SCOPE)
136
+ await expect(mgr.restore('cp_missing' as CheckpointId)).rejects.toThrow(
137
+ 'Checkpoint not found: cp_missing',
138
+ )
139
+ })
140
+ })
141
+
142
+ // ─── cadence + prune via the iteration-checkpoint phase ──────────────────────
143
+
144
+ function makePhaseContext(
145
+ store: InMemoryCheckpointStore,
146
+ runConfig: { checkpointEvery?: number; pruneKeepLast?: number },
147
+ ): { ctx: IterationContext; events: RunEvent[] } {
148
+ const events: RunEvent[] = []
149
+ const ctx = {
150
+ runConfig: {
151
+ model: 'mock-model',
152
+ timeoutMs: 5_000,
153
+ tokenBudget: 100_000,
154
+ ...runConfig,
155
+ },
156
+ runMgr: makeRunMgrStub(),
157
+ checkpointMgr: new CheckpointManager(store, SCOPE),
158
+ emitEvent: async (event: RunEvent) => {
159
+ events.push(event)
160
+ },
161
+ drainPending: function* (): Generator<RunEvent> {},
162
+ resumeHandler: async () => ({ action: 'continue' as const }),
163
+ } as unknown as IterationContext
164
+ return { ctx, events }
165
+ }
166
+
167
+ async function drivePhase(ctx: IterationContext, iterationNum: number): Promise<void> {
168
+ const gen = runIterationCheckpoint(ctx, iterationNum)
169
+ let result = await gen.next()
170
+ while (!result.done) {
171
+ result = await gen.next()
172
+ }
173
+ }
174
+
175
+ describe('iteration checkpoint cadence (checkpointEvery)', () => {
176
+ it('defaults to a checkpoint on every iteration', async () => {
177
+ const store = new InMemoryCheckpointStore()
178
+ const { ctx, events } = makePhaseContext(store, {})
179
+
180
+ await drivePhase(ctx, 1)
181
+ await drivePhase(ctx, 2)
182
+ await drivePhase(ctx, 3)
183
+
184
+ expect(store.rows.size).toBe(3)
185
+ expect(events.filter((e) => e.type === 'checkpoint_created')).toHaveLength(3)
186
+ })
187
+
188
+ it('checkpointEvery: 2 checkpoints iterations 1, 3, 5 and skips the rest', async () => {
189
+ const store = new InMemoryCheckpointStore()
190
+ const { ctx, events } = makePhaseContext(store, { checkpointEvery: 2 })
191
+
192
+ for (const iteration of [1, 2, 3, 4, 5]) {
193
+ await drivePhase(ctx, iteration)
194
+ }
195
+
196
+ const created = events.filter(
197
+ (e): e is Extract<RunEvent, { type: 'checkpoint_created' }> =>
198
+ e.type === 'checkpoint_created',
199
+ )
200
+ expect(created.map((e) => e.iteration)).toEqual([1, 3, 5])
201
+ expect(store.rows.size).toBe(3)
202
+ })
203
+
204
+ it('pruneKeepLast keeps only the newest N checkpoints after each create', async () => {
205
+ const store = new InMemoryCheckpointStore()
206
+ const { ctx } = makePhaseContext(store, { pruneKeepLast: 2 })
207
+
208
+ // Make createdAt strictly increasing so prune order is deterministic.
209
+ let tick = 0
210
+ const originalWrite = store.writeCheckpoint.bind(store)
211
+ store.writeCheckpoint = async (scope, checkpoint) => {
212
+ tick += 1
213
+ await originalWrite(scope, { ...checkpoint, createdAt: tick })
214
+ }
215
+
216
+ await drivePhase(ctx, 1)
217
+ await drivePhase(ctx, 2)
218
+ await drivePhase(ctx, 3)
219
+ await drivePhase(ctx, 4)
220
+
221
+ const remaining = await store.listCheckpoints(SCOPE)
222
+ expect(remaining).toHaveLength(2)
223
+ expect(remaining.map((cp) => cp.iteration)).toEqual([3, 4])
224
+ })
225
+ })
226
+
227
+ // ─── query()-level injection ─────────────────────────────────────────────────
228
+
229
+ class OneToolCallProvider implements LLMProvider {
230
+ readonly id = 'one-tool-call'
231
+ readonly name = 'One Tool Call Provider'
232
+ calls = 0
233
+
234
+ async *chatStream(): AsyncIterable<StreamChunk> {
235
+ this.calls += 1
236
+
237
+ if (this.calls === 1) {
238
+ yield {
239
+ id: 'msg_1',
240
+ delta: {
241
+ toolCalls: [
242
+ {
243
+ index: 0,
244
+ id: 'toolu_echo_1',
245
+ type: 'function',
246
+ function: { name: 'echo', arguments: '{"text":"hi"}' },
247
+ },
248
+ ],
249
+ },
250
+ }
251
+ yield {
252
+ id: 'msg_1',
253
+ delta: { toolCallEnd: { index: 0, id: 'toolu_echo_1' } },
254
+ }
255
+ yield {
256
+ id: 'msg_1',
257
+ delta: {},
258
+ finishReason: 'tool_calls',
259
+ usage: ZERO_USAGE,
260
+ }
261
+ return
262
+ }
263
+
264
+ yield { id: 'msg_2', delta: { content: 'done' } }
265
+ yield { id: 'msg_2', delta: {}, finishReason: 'stop', usage: ZERO_USAGE }
266
+ }
267
+ }
268
+
269
+ describe('query() with an injected checkpointStore', () => {
270
+ let workdirs: string[] = []
271
+
272
+ afterEach(async () => {
273
+ await Promise.all(workdirs.map((dir) => rm(dir, { recursive: true, force: true })))
274
+ workdirs = []
275
+ })
276
+
277
+ it('persists iteration checkpoints into the injected store, keyed by run scope', async () => {
278
+ const provider = new OneToolCallProvider()
279
+ const store = new InMemoryCheckpointStore()
280
+ const tools = new ToolRegistry()
281
+ tools.register({
282
+ name: 'echo',
283
+ description: 'echo the text back',
284
+ inputSchema: z.object({ text: z.string() }),
285
+ execute: async () => ({ success: true, output: 'hi' }),
286
+ })
287
+
288
+ const workingDirectory = await mkdtemp(join(tmpdir(), 'namzu-checkpoint-store-'))
289
+ workdirs.push(workingDirectory)
290
+
291
+ const run = await drainQuery({
292
+ provider,
293
+ tools,
294
+ checkpointStore: store,
295
+ runConfig: {
296
+ model: 'mock-model',
297
+ timeoutMs: 5_000,
298
+ tokenBudget: 100_000,
299
+ maxIterations: 3,
300
+ maxResponseTokens: 256,
301
+ },
302
+ agentId: 'agent_test',
303
+ agentName: 'Test Agent',
304
+ messages: [createUserMessage('use the echo tool')],
305
+ workingDirectory,
306
+ sessionId: 'ses_cp_inject' as SessionId,
307
+ threadId: 'thd_cp_inject' as ThreadId,
308
+ projectId: 'prj_cp_inject' as ProjectId,
309
+ tenantId: 'tnt_cp_inject' as TenantId,
310
+ })
311
+
312
+ expect(run.status).toBe('completed')
313
+ // The tool-call iteration produced at least one checkpoint, and it
314
+ // landed in the injected store — not on disk.
315
+ expect(store.rows.size).toBeGreaterThan(0)
316
+ const scope = store.seenScopes[0]
317
+ expect(scope?.tenantId).toBe('tnt_cp_inject')
318
+ expect(scope?.projectId).toBe('prj_cp_inject')
319
+ expect(scope?.sessionId).toBe('ses_cp_inject')
320
+ expect(scope?.runId).toBe(run.id)
321
+ })
322
+ })
@@ -1,10 +1,18 @@
1
1
  import { describe, expect, it } from 'vitest'
2
- import type { RunDiskStore } from '../../../store/run/disk.js'
3
2
  import type { CheckpointId, IterationCheckpoint } from '../../../types/hitl/index.js'
4
- import type { EmergencySaveId, RunId } from '../../../types/ids/index.js'
3
+ import type { EmergencySaveId, RunId, SessionId, TenantId } from '../../../types/ids/index.js'
4
+ import type { CheckpointRunScope, CheckpointStore } from '../../../types/run/checkpoint-store.js'
5
5
  import type { EmergencySaveData } from '../../../types/run/emergency.js'
6
+ import type { ProjectId } from '../../../types/session/ids.js'
6
7
  import { CheckpointManager, projectEmergencyToCheckpoint } from '../checkpoint.js'
7
8
 
9
+ const TEST_SCOPE: CheckpointRunScope = {
10
+ tenantId: 'tnt_test' as TenantId,
11
+ projectId: 'prj_test' as ProjectId,
12
+ sessionId: 'ses_test' as SessionId,
13
+ runId: 'run_test' as RunId,
14
+ }
15
+
8
16
  function makeCheckpoint(overrides: Partial<IterationCheckpoint> = {}): IterationCheckpoint {
9
17
  return {
10
18
  id: 'cp_test_a' as CheckpointId,
@@ -30,10 +38,10 @@ function makeCheckpoint(overrides: Partial<IterationCheckpoint> = {}): Iteration
30
38
  }
31
39
  }
32
40
 
33
- function makeStoreStub(checkpoints: IterationCheckpoint[]): RunDiskStore {
41
+ function makeStoreStub(checkpoints: IterationCheckpoint[]): CheckpointStore {
34
42
  return {
35
43
  listCheckpoints: async () => checkpoints,
36
- } as unknown as RunDiskStore
44
+ } as unknown as CheckpointStore
37
45
  }
38
46
 
39
47
  describe('CheckpointManager.listEntries', () => {
@@ -60,7 +68,7 @@ describe('CheckpointManager.listEntries', () => {
60
68
  }),
61
69
  ])
62
70
 
63
- const mgr = new CheckpointManager(store)
71
+ const mgr = new CheckpointManager(store, TEST_SCOPE)
64
72
  const entries = await mgr.listEntries()
65
73
 
66
74
  expect(entries).toHaveLength(2)
@@ -81,7 +89,7 @@ describe('CheckpointManager.listEntries', () => {
81
89
  })
82
90
 
83
91
  it('returns empty array when no checkpoints exist', async () => {
84
- const mgr = new CheckpointManager(makeStoreStub([]))
92
+ const mgr = new CheckpointManager(makeStoreStub([]), TEST_SCOPE)
85
93
  expect(await mgr.listEntries()).toEqual([])
86
94
  })
87
95
 
@@ -93,6 +101,7 @@ describe('CheckpointManager.listEntries', () => {
93
101
  branchStack: [{ agentId: 'a', decision: 'd', confidence: 1, timestamp: 0 }],
94
102
  }),
95
103
  ]),
104
+ TEST_SCOPE,
96
105
  )
97
106
  const [entry] = await mgr.listEntries()
98
107
  expect(entry).not.toHaveProperty('tokenUsage')
@@ -159,10 +159,12 @@ describe('query deferred tool discovery', () => {
159
159
  it('does not let search_tools reveal or activate deferred tools outside allowedTools', async () => {
160
160
  const tools = new ToolRegistry()
161
161
  registerDeferredDocumentTool(tools)
162
- registerDeferredDocumentTool(tools, 'dangerous_delete_document')
162
+ registerDeferredDocumentTool(tools, 'dangerous_purge_document')
163
163
 
164
+ // 'dangerous' matches only the out-of-allowlist tool ('delete'-style
165
+ // CRUD verbs are stop tokens and never match anything by themselves).
164
166
  const result = await SearchToolsTool.execute(
165
- { query: 'delete' },
167
+ { query: 'dangerous' },
166
168
  {
167
169
  runId: 'run_deferred_allowed_tools' as RunId,
168
170
  workingDirectory: '/tmp',
@@ -175,9 +177,54 @@ describe('query deferred tool discovery', () => {
175
177
  )
176
178
 
177
179
  expect(result.success).toBe(true)
178
- expect(result.output).toContain('No deferred tools matching "delete"')
179
- expect(result.output).not.toContain('dangerous_delete_document')
180
+ expect(result.output).toContain('No deferred tools matching "dangerous"')
181
+ expect(result.output).not.toContain('dangerous_purge_document')
180
182
  expect(tools.getAvailability('generate_document')).toBe('deferred')
181
- expect(tools.getAvailability('dangerous_delete_document')).toBe('deferred')
183
+ expect(tools.getAvailability('dangerous_purge_document')).toBe('deferred')
184
+ })
185
+
186
+ it('activates only the top-5 ranked matches and reports near-misses without activating', async () => {
187
+ const tools = new ToolRegistry()
188
+ // Eight deferred tools that all match "invoice" equally by name; the
189
+ // alphabetical tie-break makes the top-5 cut deterministic.
190
+ const names = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].map((s) => `invoice_${s}`)
191
+ for (const name of names) {
192
+ tools.register(
193
+ {
194
+ name,
195
+ description: `Billing helper ${name.slice(-1)}.`,
196
+ inputSchema: z.object({ id: z.string() }),
197
+ execute: async () => ({ success: true, output: 'ok' }),
198
+ },
199
+ 'deferred',
200
+ )
201
+ }
202
+
203
+ const result = await SearchToolsTool.execute(
204
+ { query: 'invoice' },
205
+ {
206
+ runId: 'run_deferred_top_k' as RunId,
207
+ workingDirectory: '/tmp',
208
+ abortSignal: new AbortController().signal,
209
+ env: {},
210
+ log: () => undefined,
211
+ toolRegistry: tools,
212
+ },
213
+ )
214
+
215
+ expect(result.success).toBe(true)
216
+ expect(result.output).toContain('Activated 5 tool(s)')
217
+ expect(result.output).toContain('NOT loaded')
218
+ expect(result.data).toMatchObject({
219
+ activated: ['invoice_a', 'invoice_b', 'invoice_c', 'invoice_d', 'invoice_e'],
220
+ count: 5,
221
+ nearMisses: ['invoice_f', 'invoice_g', 'invoice_h'],
222
+ })
223
+ for (const name of ['invoice_a', 'invoice_b', 'invoice_c', 'invoice_d', 'invoice_e']) {
224
+ expect(tools.getAvailability(name)).toBe('active')
225
+ }
226
+ for (const name of ['invoice_f', 'invoice_g', 'invoice_h']) {
227
+ expect(tools.getAvailability(name)).toBe('deferred')
228
+ }
182
229
  })
183
230
  })
@@ -0,0 +1,180 @@
1
+ import { readFile, rm } from 'node:fs/promises'
2
+ import { mkdtemp } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { afterEach, describe, expect, it } from 'vitest'
6
+
7
+ import { ToolRegistry } from '../../../registry/tool/execute.js'
8
+ import { EditTool, WriteFileTool } from '../../../tools/builtins/index.js'
9
+ import type { SessionId, TenantId } from '../../../types/ids/index.js'
10
+ import { createUserMessage } from '../../../types/message/index.js'
11
+ import type { LLMProvider, StreamChunk } from '../../../types/provider/index.js'
12
+ import type { RunEvent } from '../../../types/run/index.js'
13
+ import type { ProjectId, ThreadId } from '../../../types/session/ids.js'
14
+ import { drainQuery } from '../index.js'
15
+
16
+ const ZERO_USAGE = {
17
+ promptTokens: 0,
18
+ completionTokens: 0,
19
+ totalTokens: 0,
20
+ cachedTokens: 0,
21
+ cacheWriteTokens: 0,
22
+ }
23
+
24
+ interface ToolStep {
25
+ readonly name: string
26
+ readonly input: Record<string, unknown>
27
+ }
28
+
29
+ class ScriptedLongDocumentProvider implements LLMProvider {
30
+ readonly id = 'scripted-long-document'
31
+ readonly name = 'Scripted Long Document Provider'
32
+ calls = 0
33
+
34
+ constructor(private readonly steps: readonly ToolStep[]) {}
35
+
36
+ async *chatStream(): AsyncIterable<StreamChunk> {
37
+ const step = this.steps[this.calls]
38
+ this.calls += 1
39
+
40
+ if (!step) {
41
+ yield {
42
+ id: 'msg_done',
43
+ delta: { content: 'Long document created and verified.' },
44
+ finishReason: 'stop',
45
+ usage: ZERO_USAGE,
46
+ }
47
+ return
48
+ }
49
+
50
+ yield {
51
+ id: `msg_${this.calls}`,
52
+ delta: {
53
+ toolCalls: [
54
+ {
55
+ index: 0,
56
+ id: `toolu_${this.calls}`,
57
+ type: 'function',
58
+ function: { name: step.name },
59
+ },
60
+ ],
61
+ },
62
+ }
63
+ yield {
64
+ id: `msg_${this.calls}`,
65
+ delta: {
66
+ toolCalls: [
67
+ {
68
+ index: 0,
69
+ id: `toolu_${this.calls}`,
70
+ function: { arguments: JSON.stringify(step.input) },
71
+ },
72
+ ],
73
+ },
74
+ }
75
+ yield {
76
+ id: `msg_${this.calls}`,
77
+ delta: {},
78
+ finishReason: 'tool_calls',
79
+ usage: ZERO_USAGE,
80
+ }
81
+ }
82
+ }
83
+
84
+ describe('query long-document tool flow', () => {
85
+ const workdirs: string[] = []
86
+
87
+ afterEach(async () => {
88
+ await Promise.all(workdirs.map((dir) => rm(dir, { recursive: true, force: true })))
89
+ workdirs.length = 0
90
+ })
91
+
92
+ it('executes the canonical write skeleton plus bounded edit chunks flow end to end', async () => {
93
+ const workingDirectory = await mkdtemp(join(tmpdir(), 'namzu-query-long-doc-'))
94
+ workdirs.push(workingDirectory)
95
+
96
+ const chunks = Array.from({ length: 4 }, (_, sectionIndex) => {
97
+ const lines = Array.from({ length: 40 }, (_, lineIndex) => {
98
+ return `Section ${sectionIndex + 1}.${lineIndex + 1}: this content is intentionally long enough to require repeated bounded edit calls.`
99
+ })
100
+ const chunk = [`## Section ${sectionIndex + 1}`, ...lines, ''].join('\n')
101
+ expect(chunk.length).toBeLessThan(12_000)
102
+ return chunk
103
+ })
104
+
105
+ const provider = new ScriptedLongDocumentProvider([
106
+ {
107
+ name: 'write',
108
+ input: {
109
+ path: 'outputs/long-document-flow.md',
110
+ content: '# Long document flow\n\n{{BODY}}\n',
111
+ },
112
+ },
113
+ {
114
+ name: 'edit',
115
+ input: {
116
+ path: 'outputs/long-document-flow.md',
117
+ oldStr: '{{BODY}}',
118
+ newStr: chunks[0],
119
+ replace_all: false,
120
+ },
121
+ },
122
+ ...chunks.slice(1).map((chunk) => ({
123
+ name: 'edit',
124
+ input: {
125
+ path: 'outputs/long-document-flow.md',
126
+ insertLine: 'end',
127
+ newStr: chunk,
128
+ replace_all: false,
129
+ },
130
+ })),
131
+ ])
132
+ const tools = new ToolRegistry()
133
+ tools.register(WriteFileTool)
134
+ tools.register(EditTool)
135
+ const events: RunEvent[] = []
136
+
137
+ const run = await drainQuery(
138
+ {
139
+ provider,
140
+ tools,
141
+ runConfig: {
142
+ model: 'mock-model',
143
+ timeoutMs: 5_000,
144
+ tokenBudget: 100_000,
145
+ maxIterations: 8,
146
+ maxResponseTokens: 256,
147
+ },
148
+ agentId: 'agent_long_doc',
149
+ agentName: 'Long Document Agent',
150
+ messages: [createUserMessage('create a long document')],
151
+ workingDirectory,
152
+ sessionId: 'ses_long_document_flow' as SessionId,
153
+ threadId: 'thd_long_document_flow' as ThreadId,
154
+ projectId: 'prj_long_document_flow' as ProjectId,
155
+ tenantId: 'tnt_long_document_flow' as TenantId,
156
+ },
157
+ (event) => {
158
+ events.push(event)
159
+ },
160
+ )
161
+
162
+ const final = await readFile(join(workingDirectory, 'outputs/long-document-flow.md'), 'utf-8')
163
+ const executingTools = events
164
+ .filter(
165
+ (event): event is Extract<RunEvent, { type: 'tool_executing' }> =>
166
+ event.type === 'tool_executing',
167
+ )
168
+ .map((event) => event.toolName)
169
+
170
+ expect(run.status).toBe('completed')
171
+ expect(run.result).toBe('Long document created and verified.')
172
+ expect(provider.calls).toBe(6)
173
+ expect(executingTools).toEqual(['write', 'edit', 'edit', 'edit', 'edit'])
174
+ expect(final).not.toContain('{{BODY}}')
175
+ expect(final.split('\n').length).toBeGreaterThan(160)
176
+ expect(final).toContain('## Section 1')
177
+ expect(final).toContain('## Section 4')
178
+ expect(final).toContain('Section 4.40')
179
+ })
180
+ })