@namzu/sdk 0.6.0 → 1.1.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 (429) hide show
  1. package/CHANGELOG.md +529 -0
  2. package/dist/advisory/executor.d.ts.map +1 -1
  3. package/dist/advisory/executor.js +9 -2
  4. package/dist/advisory/executor.js.map +1 -1
  5. package/dist/advisory/executor.test.d.ts +2 -1
  6. package/dist/advisory/executor.test.d.ts.map +1 -1
  7. package/dist/advisory/executor.test.js +7 -4
  8. package/dist/advisory/executor.test.js.map +1 -1
  9. package/dist/agents/ReactiveAgent.d.ts.map +1 -1
  10. package/dist/agents/ReactiveAgent.js +7 -0
  11. package/dist/agents/ReactiveAgent.js.map +1 -1
  12. package/dist/agents/SupervisorAgent.d.ts +22 -1
  13. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  14. package/dist/agents/SupervisorAgent.js +54 -14
  15. package/dist/agents/SupervisorAgent.js.map +1 -1
  16. package/dist/agents/__tests__/supervisor-ledger.test.d.ts +2 -0
  17. package/dist/agents/__tests__/supervisor-ledger.test.d.ts.map +1 -0
  18. package/dist/agents/__tests__/supervisor-ledger.test.js +131 -0
  19. package/dist/agents/__tests__/supervisor-ledger.test.js.map +1 -0
  20. package/dist/bridge/sse/mapper.test.js +2 -2
  21. package/dist/config/runtime.d.ts +24 -0
  22. package/dist/config/runtime.d.ts.map +1 -1
  23. package/dist/config/runtime.js +9 -0
  24. package/dist/config/runtime.js.map +1 -1
  25. package/dist/connector/builtins/webhook.test.js +17 -0
  26. package/dist/connector/builtins/webhook.test.js.map +1 -1
  27. package/dist/connector/index.d.ts +1 -0
  28. package/dist/connector/index.d.ts.map +1 -1
  29. package/dist/connector/index.js +1 -0
  30. package/dist/connector/index.js.map +1 -1
  31. package/dist/connector/mcp/client.d.ts.map +1 -1
  32. package/dist/connector/mcp/client.js +4 -0
  33. package/dist/connector/mcp/client.js.map +1 -1
  34. package/dist/connector/mcp/index.d.ts +1 -0
  35. package/dist/connector/mcp/index.d.ts.map +1 -1
  36. package/dist/connector/mcp/index.js +1 -0
  37. package/dist/connector/mcp/index.js.map +1 -1
  38. package/dist/connector/mcp/streamable-http.d.ts +22 -0
  39. package/dist/connector/mcp/streamable-http.d.ts.map +1 -0
  40. package/dist/connector/mcp/streamable-http.js +128 -0
  41. package/dist/connector/mcp/streamable-http.js.map +1 -0
  42. package/dist/connector/mcp/streamable-http.test.d.ts +2 -0
  43. package/dist/connector/mcp/streamable-http.test.d.ts.map +1 -0
  44. package/dist/connector/mcp/streamable-http.test.js +132 -0
  45. package/dist/connector/mcp/streamable-http.test.js.map +1 -0
  46. package/dist/constants/compaction/index.d.ts.map +1 -1
  47. package/dist/constants/compaction/index.js +8 -3
  48. package/dist/constants/compaction/index.js.map +1 -1
  49. package/dist/constants/sandbox/index.d.ts +21 -0
  50. package/dist/constants/sandbox/index.d.ts.map +1 -1
  51. package/dist/constants/sandbox/index.js +30 -0
  52. package/dist/constants/sandbox/index.js.map +1 -1
  53. package/dist/constants/tools/index.d.ts.map +1 -1
  54. package/dist/constants/tools/index.js +33 -2
  55. package/dist/constants/tools/index.js.map +1 -1
  56. package/dist/manager/run/persistence.d.ts.map +1 -1
  57. package/dist/manager/run/persistence.js +35 -5
  58. package/dist/manager/run/persistence.js.map +1 -1
  59. package/dist/persona/assembler.d.ts +1 -0
  60. package/dist/persona/assembler.d.ts.map +1 -1
  61. package/dist/persona/assembler.js +37 -6
  62. package/dist/persona/assembler.js.map +1 -1
  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/collect.test.js +2 -2
  67. package/dist/public-runtime.d.ts +6 -5
  68. package/dist/public-runtime.d.ts.map +1 -1
  69. package/dist/public-runtime.js +6 -5
  70. package/dist/public-runtime.js.map +1 -1
  71. package/dist/public-tools.d.ts +2 -0
  72. package/dist/public-tools.d.ts.map +1 -1
  73. package/dist/public-tools.js +2 -0
  74. package/dist/public-tools.js.map +1 -1
  75. package/dist/public-types.d.ts +3 -0
  76. package/dist/public-types.d.ts.map +1 -1
  77. package/dist/registry/index.d.ts +2 -0
  78. package/dist/registry/index.d.ts.map +1 -1
  79. package/dist/registry/index.js +1 -0
  80. package/dist/registry/index.js.map +1 -1
  81. package/dist/registry/tool/execute.d.ts +27 -0
  82. package/dist/registry/tool/execute.d.ts.map +1 -1
  83. package/dist/registry/tool/execute.js +192 -7
  84. package/dist/registry/tool/execute.js.map +1 -1
  85. package/dist/registry/tool/execute.test.d.ts +9 -4
  86. package/dist/registry/tool/execute.test.d.ts.map +1 -1
  87. package/dist/registry/tool/execute.test.js +178 -7
  88. package/dist/registry/tool/execute.test.js.map +1 -1
  89. package/dist/registry/toolset/catalog.d.ts +42 -0
  90. package/dist/registry/toolset/catalog.d.ts.map +1 -0
  91. package/dist/registry/toolset/catalog.js +217 -0
  92. package/dist/registry/toolset/catalog.js.map +1 -0
  93. package/dist/registry/toolset/catalog.test.d.ts +2 -0
  94. package/dist/registry/toolset/catalog.test.d.ts.map +1 -0
  95. package/dist/registry/toolset/catalog.test.js +147 -0
  96. package/dist/registry/toolset/catalog.test.js.map +1 -0
  97. package/dist/runtime/query/__tests__/deferred-tools.test.d.ts +2 -0
  98. package/dist/runtime/query/__tests__/deferred-tools.test.d.ts.map +1 -0
  99. package/dist/runtime/query/__tests__/deferred-tools.test.js +185 -0
  100. package/dist/runtime/query/__tests__/deferred-tools.test.js.map +1 -0
  101. package/dist/runtime/query/__tests__/executor-concurrency.test.d.ts +2 -0
  102. package/dist/runtime/query/__tests__/executor-concurrency.test.d.ts.map +1 -0
  103. package/dist/runtime/query/__tests__/executor-concurrency.test.js +98 -0
  104. package/dist/runtime/query/__tests__/executor-concurrency.test.js.map +1 -0
  105. package/dist/runtime/query/__tests__/executor-plugin-hooks.test.js +38 -3
  106. package/dist/runtime/query/__tests__/executor-plugin-hooks.test.js.map +1 -1
  107. package/dist/runtime/query/__tests__/long-document-flow.test.d.ts +2 -0
  108. package/dist/runtime/query/__tests__/long-document-flow.test.d.ts.map +1 -0
  109. package/dist/runtime/query/__tests__/long-document-flow.test.js +154 -0
  110. package/dist/runtime/query/__tests__/long-document-flow.test.js.map +1 -0
  111. package/dist/runtime/query/__tests__/prompt.test.js +51 -2
  112. package/dist/runtime/query/__tests__/prompt.test.js.map +1 -1
  113. package/dist/runtime/query/__tests__/stream-recovery.test.d.ts +2 -0
  114. package/dist/runtime/query/__tests__/stream-recovery.test.d.ts.map +1 -0
  115. package/dist/runtime/query/__tests__/stream-recovery.test.js +126 -0
  116. package/dist/runtime/query/__tests__/stream-recovery.test.js.map +1 -0
  117. package/dist/runtime/query/continuation.d.ts +16 -0
  118. package/dist/runtime/query/continuation.d.ts.map +1 -0
  119. package/dist/runtime/query/continuation.js +16 -0
  120. package/dist/runtime/query/continuation.js.map +1 -0
  121. package/dist/runtime/query/executor.d.ts +3 -0
  122. package/dist/runtime/query/executor.d.ts.map +1 -1
  123. package/dist/runtime/query/executor.js +71 -3
  124. package/dist/runtime/query/executor.js.map +1 -1
  125. package/dist/runtime/query/index.d.ts +9 -0
  126. package/dist/runtime/query/index.d.ts.map +1 -1
  127. package/dist/runtime/query/index.js +42 -5
  128. package/dist/runtime/query/index.js.map +1 -1
  129. package/dist/runtime/query/iteration/index.d.ts +24 -0
  130. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/index.js +326 -66
  132. package/dist/runtime/query/iteration/index.js.map +1 -1
  133. package/dist/runtime/query/iteration/phases/checkpoint.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/phases/checkpoint.js +2 -2
  135. package/dist/runtime/query/iteration/phases/checkpoint.js.map +1 -1
  136. package/dist/runtime/query/iteration/phases/compaction.d.ts +8 -0
  137. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  138. package/dist/runtime/query/iteration/phases/compaction.js +84 -3
  139. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  140. package/dist/runtime/query/iteration/phases/context.d.ts +22 -0
  141. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  142. package/dist/runtime/query/iteration/phases/context.js +53 -0
  143. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  144. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts +2 -0
  145. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.d.ts.map +1 -0
  146. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js +73 -0
  147. package/dist/runtime/query/iteration/phases/hitl-abort-race.test.js.map +1 -0
  148. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts +2 -0
  149. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.d.ts.map +1 -0
  150. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js +120 -0
  151. package/dist/runtime/query/iteration/phases/hitl-answer-question.test.js.map +1 -0
  152. package/dist/runtime/query/iteration/phases/tool-review.d.ts +1 -1
  153. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  154. package/dist/runtime/query/iteration/phases/tool-review.js +7 -2
  155. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  156. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts +19 -0
  157. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.d.ts.map +1 -0
  158. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js +177 -0
  159. package/dist/runtime/query/iteration/phases/working-memory-compaction.test.js.map +1 -0
  160. package/dist/runtime/query/iteration/phases/working-memory.d.ts +31 -0
  161. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -0
  162. package/dist/runtime/query/iteration/phases/working-memory.js +81 -0
  163. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -0
  164. package/dist/runtime/query/prompt.d.ts.map +1 -1
  165. package/dist/runtime/query/prompt.js +21 -1
  166. package/dist/runtime/query/prompt.js.map +1 -1
  167. package/dist/runtime/query/tooling.d.ts +1 -0
  168. package/dist/runtime/query/tooling.d.ts.map +1 -1
  169. package/dist/runtime/query/tooling.js +1 -0
  170. package/dist/runtime/query/tooling.js.map +1 -1
  171. package/dist/sandbox/provider/local.d.ts.map +1 -1
  172. package/dist/sandbox/provider/local.js +32 -1
  173. package/dist/sandbox/provider/local.js.map +1 -1
  174. package/dist/session/workspace/__tests__/shared-run.test.d.ts +2 -0
  175. package/dist/session/workspace/__tests__/shared-run.test.d.ts.map +1 -0
  176. package/dist/session/workspace/__tests__/shared-run.test.js +147 -0
  177. package/dist/session/workspace/__tests__/shared-run.test.js.map +1 -0
  178. package/dist/session/workspace/index.d.ts +2 -0
  179. package/dist/session/workspace/index.d.ts.map +1 -1
  180. package/dist/session/workspace/index.js +1 -0
  181. package/dist/session/workspace/index.js.map +1 -1
  182. package/dist/session/workspace/shared-run.d.ts +81 -0
  183. package/dist/session/workspace/shared-run.d.ts.map +1 -0
  184. package/dist/session/workspace/shared-run.js +252 -0
  185. package/dist/session/workspace/shared-run.js.map +1 -0
  186. package/dist/skills/loader.d.ts.map +1 -1
  187. package/dist/skills/loader.js +36 -6
  188. package/dist/skills/loader.js.map +1 -1
  189. package/dist/skills/loader.test.d.ts +2 -0
  190. package/dist/skills/loader.test.d.ts.map +1 -0
  191. package/dist/skills/loader.test.js +65 -0
  192. package/dist/skills/loader.test.js.map +1 -0
  193. package/dist/streaming/coalesce.test.js +1 -1
  194. package/dist/streaming/coalesce.test.js.map +1 -1
  195. package/dist/tools/builtins/__tests__/edit.test.d.ts +2 -0
  196. package/dist/tools/builtins/__tests__/edit.test.d.ts.map +1 -0
  197. package/dist/tools/builtins/__tests__/edit.test.js +38 -0
  198. package/dist/tools/builtins/__tests__/edit.test.js.map +1 -0
  199. package/dist/tools/builtins/__tests__/payload-budget.test.d.ts +2 -0
  200. package/dist/tools/builtins/__tests__/payload-budget.test.d.ts.map +1 -0
  201. package/dist/tools/builtins/__tests__/payload-budget.test.js +70 -0
  202. package/dist/tools/builtins/__tests__/payload-budget.test.js.map +1 -0
  203. package/dist/tools/builtins/__tests__/read-file.test.d.ts +2 -0
  204. package/dist/tools/builtins/__tests__/read-file.test.d.ts.map +1 -0
  205. package/dist/tools/builtins/__tests__/read-file.test.js +33 -0
  206. package/dist/tools/builtins/__tests__/read-file.test.js.map +1 -0
  207. package/dist/tools/builtins/__tests__/verify-outputs.test.d.ts +2 -0
  208. package/dist/tools/builtins/__tests__/verify-outputs.test.d.ts.map +1 -0
  209. package/dist/tools/builtins/__tests__/verify-outputs.test.js +52 -0
  210. package/dist/tools/builtins/__tests__/verify-outputs.test.js.map +1 -0
  211. package/dist/tools/builtins/__tests__/write-file.test.d.ts +2 -0
  212. package/dist/tools/builtins/__tests__/write-file.test.d.ts.map +1 -0
  213. package/dist/tools/builtins/__tests__/write-file.test.js +74 -0
  214. package/dist/tools/builtins/__tests__/write-file.test.js.map +1 -0
  215. package/dist/tools/builtins/bash.d.ts.map +1 -1
  216. package/dist/tools/builtins/bash.js +40 -7
  217. package/dist/tools/builtins/bash.js.map +1 -1
  218. package/dist/tools/builtins/edit.d.ts +5 -2
  219. package/dist/tools/builtins/edit.d.ts.map +1 -1
  220. package/dist/tools/builtins/edit.js +114 -18
  221. package/dist/tools/builtins/edit.js.map +1 -1
  222. package/dist/tools/builtins/index.d.ts +1 -0
  223. package/dist/tools/builtins/index.d.ts.map +1 -1
  224. package/dist/tools/builtins/index.js +13 -13
  225. package/dist/tools/builtins/index.js.map +1 -1
  226. package/dist/tools/builtins/read-file.d.ts +1 -0
  227. package/dist/tools/builtins/read-file.d.ts.map +1 -1
  228. package/dist/tools/builtins/read-file.js +73 -10
  229. package/dist/tools/builtins/read-file.js.map +1 -1
  230. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  231. package/dist/tools/builtins/search-tools.js +35 -7
  232. package/dist/tools/builtins/search-tools.js.map +1 -1
  233. package/dist/tools/builtins/verify-outputs.d.ts +5 -0
  234. package/dist/tools/builtins/verify-outputs.d.ts.map +1 -0
  235. package/dist/tools/builtins/verify-outputs.js +103 -0
  236. package/dist/tools/builtins/verify-outputs.js.map +1 -0
  237. package/dist/tools/builtins/write-file.d.ts +3 -2
  238. package/dist/tools/builtins/write-file.d.ts.map +1 -1
  239. package/dist/tools/builtins/write-file.js +72 -12
  240. package/dist/tools/builtins/write-file.js.map +1 -1
  241. package/dist/tools/coordinator/__tests__/agent.test.d.ts +15 -0
  242. package/dist/tools/coordinator/__tests__/agent.test.d.ts.map +1 -0
  243. package/dist/tools/coordinator/__tests__/agent.test.js +142 -0
  244. package/dist/tools/coordinator/__tests__/agent.test.js.map +1 -0
  245. package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts +2 -0
  246. package/dist/tools/coordinator/__tests__/approve-plan.test.d.ts.map +1 -0
  247. package/dist/tools/coordinator/__tests__/approve-plan.test.js +114 -0
  248. package/dist/tools/coordinator/__tests__/approve-plan.test.js.map +1 -0
  249. package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts +2 -0
  250. package/dist/tools/coordinator/__tests__/ask-user-question.test.d.ts.map +1 -0
  251. package/dist/tools/coordinator/__tests__/ask-user-question.test.js +309 -0
  252. package/dist/tools/coordinator/__tests__/ask-user-question.test.js.map +1 -0
  253. package/dist/tools/coordinator/__tests__/task-list.test.d.ts +13 -0
  254. package/dist/tools/coordinator/__tests__/task-list.test.d.ts.map +1 -0
  255. package/dist/tools/coordinator/__tests__/task-list.test.js +162 -0
  256. package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -0
  257. package/dist/tools/coordinator/agent.d.ts +34 -0
  258. package/dist/tools/coordinator/agent.d.ts.map +1 -0
  259. package/dist/tools/coordinator/agent.js +107 -0
  260. package/dist/tools/coordinator/agent.js.map +1 -0
  261. package/dist/tools/coordinator/index.d.ts +15 -0
  262. package/dist/tools/coordinator/index.d.ts.map +1 -1
  263. package/dist/tools/coordinator/index.js +305 -35
  264. package/dist/tools/coordinator/index.js.map +1 -1
  265. package/dist/types/agent/base.d.ts +8 -0
  266. package/dist/types/agent/base.d.ts.map +1 -1
  267. package/dist/types/agent/index.d.ts +1 -0
  268. package/dist/types/agent/index.d.ts.map +1 -1
  269. package/dist/types/agent/index.js +1 -0
  270. package/dist/types/agent/index.js.map +1 -1
  271. package/dist/types/agent/reactive.d.ts +38 -0
  272. package/dist/types/agent/reactive.d.ts.map +1 -1
  273. package/dist/types/agent/supervisor.d.ts +32 -0
  274. package/dist/types/agent/supervisor.d.ts.map +1 -1
  275. package/dist/types/agent/working-memory.d.ts +29 -0
  276. package/dist/types/agent/working-memory.d.ts.map +1 -0
  277. package/dist/types/agent/working-memory.js +2 -0
  278. package/dist/types/agent/working-memory.js.map +1 -0
  279. package/dist/types/common/index.d.ts +10 -0
  280. package/dist/types/common/index.d.ts.map +1 -1
  281. package/dist/types/common/index.js +18 -0
  282. package/dist/types/common/index.js.map +1 -1
  283. package/dist/types/common/usage-merge.test.d.ts +2 -0
  284. package/dist/types/common/usage-merge.test.d.ts.map +1 -0
  285. package/dist/types/common/usage-merge.test.js +48 -0
  286. package/dist/types/common/usage-merge.test.js.map +1 -0
  287. package/dist/types/connector/mcp.d.ts +9 -2
  288. package/dist/types/connector/mcp.d.ts.map +1 -1
  289. package/dist/types/hitl/index.d.ts +42 -0
  290. package/dist/types/hitl/index.d.ts.map +1 -1
  291. package/dist/types/hitl/index.js +11 -0
  292. package/dist/types/hitl/index.js.map +1 -1
  293. package/dist/types/message/index.d.ts +22 -1
  294. package/dist/types/message/index.d.ts.map +1 -1
  295. package/dist/types/message/index.js +7 -2
  296. package/dist/types/message/index.js.map +1 -1
  297. package/dist/types/provider/chat.d.ts +11 -9
  298. package/dist/types/provider/chat.d.ts.map +1 -1
  299. package/dist/types/run/events.d.ts +6 -0
  300. package/dist/types/run/events.d.ts.map +1 -1
  301. package/dist/types/run/events.js.map +1 -1
  302. package/dist/types/sandbox/index.d.ts +193 -0
  303. package/dist/types/sandbox/index.d.ts.map +1 -1
  304. package/dist/types/sandbox/index.js.map +1 -1
  305. package/dist/types/skills/index.d.ts +2 -0
  306. package/dist/types/skills/index.d.ts.map +1 -1
  307. package/dist/types/tool/index.d.ts +22 -0
  308. package/dist/types/tool/index.d.ts.map +1 -1
  309. package/dist/types/toolset/index.d.ts +71 -0
  310. package/dist/types/toolset/index.d.ts.map +1 -0
  311. package/dist/types/toolset/index.js +2 -0
  312. package/dist/types/toolset/index.js.map +1 -0
  313. package/dist/types/workspace/index.d.ts +1 -0
  314. package/dist/types/workspace/index.d.ts.map +1 -1
  315. package/dist/types/workspace/shared-run.d.ts +61 -0
  316. package/dist/types/workspace/shared-run.d.ts.map +1 -0
  317. package/dist/types/workspace/shared-run.js +2 -0
  318. package/dist/types/workspace/shared-run.js.map +1 -0
  319. package/dist/verification/index.d.ts +1 -0
  320. package/dist/verification/index.d.ts.map +1 -1
  321. package/dist/verification/index.js +1 -0
  322. package/dist/verification/index.js.map +1 -1
  323. package/dist/verification/presets.d.ts +53 -0
  324. package/dist/verification/presets.d.ts.map +1 -0
  325. package/dist/verification/presets.js +70 -0
  326. package/dist/verification/presets.js.map +1 -0
  327. package/dist/verification/presets.test.d.ts +16 -0
  328. package/dist/verification/presets.test.d.ts.map +1 -0
  329. package/dist/verification/presets.test.js +79 -0
  330. package/dist/verification/presets.test.js.map +1 -0
  331. package/dist/version.d.ts.map +1 -1
  332. package/dist/version.js +25 -4
  333. package/dist/version.js.map +1 -1
  334. package/package.json +3 -2
  335. package/src/advisory/executor.test.ts +7 -4
  336. package/src/advisory/executor.ts +11 -2
  337. package/src/agents/ReactiveAgent.ts +7 -0
  338. package/src/agents/SupervisorAgent.ts +63 -16
  339. package/src/agents/__tests__/supervisor-ledger.test.ts +185 -0
  340. package/src/bridge/sse/mapper.test.ts +2 -2
  341. package/src/config/runtime.ts +9 -0
  342. package/src/connector/builtins/webhook.test.ts +21 -0
  343. package/src/connector/index.ts +1 -0
  344. package/src/connector/mcp/client.ts +4 -0
  345. package/src/connector/mcp/index.ts +1 -0
  346. package/src/connector/mcp/streamable-http.test.ts +169 -0
  347. package/src/connector/mcp/streamable-http.ts +152 -0
  348. package/src/constants/compaction/index.ts +8 -3
  349. package/src/constants/sandbox/index.ts +37 -0
  350. package/src/constants/tools/index.ts +33 -2
  351. package/src/manager/run/persistence.ts +34 -6
  352. package/src/persona/assembler.ts +43 -8
  353. package/src/provider/collect.test.ts +2 -2
  354. package/src/provider/collect.ts +4 -1
  355. package/src/public-runtime.ts +15 -1
  356. package/src/public-tools.ts +2 -0
  357. package/src/public-types.ts +7 -0
  358. package/src/registry/index.ts +7 -0
  359. package/src/registry/tool/execute.test.ts +211 -7
  360. package/src/registry/tool/execute.ts +199 -11
  361. package/src/registry/toolset/catalog.test.ts +165 -0
  362. package/src/registry/toolset/catalog.ts +283 -0
  363. package/src/runtime/query/__tests__/deferred-tools.test.ts +230 -0
  364. package/src/runtime/query/__tests__/executor-concurrency.test.ts +122 -0
  365. package/src/runtime/query/__tests__/executor-plugin-hooks.test.ts +48 -3
  366. package/src/runtime/query/__tests__/long-document-flow.test.ts +180 -0
  367. package/src/runtime/query/__tests__/prompt.test.ts +55 -2
  368. package/src/runtime/query/__tests__/stream-recovery.test.ts +156 -0
  369. package/src/runtime/query/continuation.ts +16 -0
  370. package/src/runtime/query/executor.ts +82 -13
  371. package/src/runtime/query/index.ts +58 -5
  372. package/src/runtime/query/iteration/index.ts +361 -74
  373. package/src/runtime/query/iteration/phases/checkpoint.ts +7 -2
  374. package/src/runtime/query/iteration/phases/compaction.ts +89 -3
  375. package/src/runtime/query/iteration/phases/context.ts +68 -0
  376. package/src/runtime/query/iteration/phases/hitl-abort-race.test.ts +86 -0
  377. package/src/runtime/query/iteration/phases/hitl-answer-question.test.ts +139 -0
  378. package/src/runtime/query/iteration/phases/tool-review.ts +7 -3
  379. package/src/runtime/query/iteration/phases/working-memory-compaction.test.ts +215 -0
  380. package/src/runtime/query/iteration/phases/working-memory.ts +86 -0
  381. package/src/runtime/query/prompt.ts +17 -1
  382. package/src/runtime/query/tooling.ts +2 -0
  383. package/src/sandbox/provider/local.ts +33 -0
  384. package/src/session/workspace/__tests__/shared-run.test.ts +181 -0
  385. package/src/session/workspace/index.ts +6 -0
  386. package/src/session/workspace/shared-run.ts +317 -0
  387. package/src/skills/loader.test.ts +89 -0
  388. package/src/skills/loader.ts +37 -6
  389. package/src/streaming/coalesce.test.ts +4 -4
  390. package/src/tools/builtins/__tests__/edit.test.ts +57 -0
  391. package/src/tools/builtins/__tests__/payload-budget.test.ts +92 -0
  392. package/src/tools/builtins/__tests__/read-file.test.ts +43 -0
  393. package/src/tools/builtins/__tests__/verify-outputs.test.ts +71 -0
  394. package/src/tools/builtins/__tests__/write-file.test.ts +97 -0
  395. package/src/tools/builtins/bash.ts +48 -7
  396. package/src/tools/builtins/edit.ts +162 -27
  397. package/src/tools/builtins/index.ts +13 -13
  398. package/src/tools/builtins/read-file.ts +81 -10
  399. package/src/tools/builtins/search-tools.ts +42 -7
  400. package/src/tools/builtins/verify-outputs.ts +126 -0
  401. package/src/tools/builtins/write-file.ts +83 -14
  402. package/src/tools/coordinator/__tests__/agent.test.ts +172 -0
  403. package/src/tools/coordinator/__tests__/approve-plan.test.ts +139 -0
  404. package/src/tools/coordinator/__tests__/ask-user-question.test.ts +370 -0
  405. package/src/tools/coordinator/__tests__/task-list.test.ts +182 -0
  406. package/src/tools/coordinator/agent.ts +157 -0
  407. package/src/tools/coordinator/index.ts +355 -39
  408. package/src/types/agent/base.ts +8 -0
  409. package/src/types/agent/index.ts +1 -0
  410. package/src/types/agent/reactive.ts +42 -0
  411. package/src/types/agent/supervisor.ts +36 -0
  412. package/src/types/agent/working-memory.ts +29 -0
  413. package/src/types/common/index.ts +19 -0
  414. package/src/types/common/usage-merge.test.ts +53 -0
  415. package/src/types/connector/mcp.ts +14 -2
  416. package/src/types/hitl/index.ts +52 -1
  417. package/src/types/message/index.ts +32 -2
  418. package/src/types/provider/chat.ts +12 -9
  419. package/src/types/run/events.ts +6 -0
  420. package/src/types/sandbox/index.ts +219 -0
  421. package/src/types/skills/index.ts +4 -0
  422. package/src/types/tool/index.ts +24 -0
  423. package/src/types/toolset/index.ts +86 -0
  424. package/src/types/workspace/index.ts +9 -0
  425. package/src/types/workspace/shared-run.ts +65 -0
  426. package/src/verification/index.ts +1 -0
  427. package/src/verification/presets.test.ts +112 -0
  428. package/src/verification/presets.ts +72 -0
  429. package/src/version.ts +26 -4
@@ -1,10 +1,16 @@
1
1
  import { readFile } from 'node:fs/promises'
2
- import { resolve } from 'node:path'
2
+ import { extname, resolve } from 'node:path'
3
3
  import { z } from 'zod'
4
4
  import { defineTool } from '../defineTool.js'
5
5
 
6
6
  const inputSchema = z.object({
7
7
  path: z.string().describe('Path to the file to read (absolute or relative)'),
8
+ readRange: z
9
+ .tuple([z.coerce.number().int().min(1), z.coerce.number().int().min(1)])
10
+ .optional()
11
+ .describe(
12
+ 'Optional 1-indexed inclusive line range, e.g. [10, 40]. When provided it takes precedence over offset/limit.',
13
+ ),
8
14
  offset: z.coerce
9
15
  .number()
10
16
  .int()
@@ -15,9 +21,9 @@ const inputSchema = z.object({
15
21
  })
16
22
 
17
23
  export const ReadFileTool = defineTool({
18
- name: 'read_file',
24
+ name: 'read',
19
25
  description:
20
- 'Reads a file and returns its contents with line numbers. Supports offset and limit parameters for large files.',
26
+ 'Reads a file and returns its contents with line numbers. Supports readRange ([start,end], 1-indexed inclusive) or offset/limit for large files.',
21
27
  inputSchema,
22
28
  category: 'filesystem',
23
29
  permissions: ['file_read'],
@@ -29,14 +35,27 @@ export const ReadFileTool = defineTool({
29
35
  // Sandbox-aware: route through sandbox.readFile() when available
30
36
  if (context.sandbox) {
31
37
  const buffer = await context.sandbox.readFile(input.path)
38
+ const binaryGuidance = describeStructuredBinaryRead(input.path, buffer)
39
+ if (binaryGuidance) {
40
+ return {
41
+ success: false,
42
+ output: binaryGuidance,
43
+ data: {
44
+ path: input.path,
45
+ sandboxed: true,
46
+ binary: true,
47
+ },
48
+ }
49
+ }
32
50
  const content = buffer.toString('utf-8')
33
51
  const lines = content.split('\n')
34
52
 
35
- const start = Math.max(0, input.offset ?? 0)
36
- const end = input.limit ? start + input.limit : lines.length
53
+ const { start, end } = resolveReadWindow(input, lines.length)
37
54
  const selectedLines = lines.slice(start, end)
38
55
 
39
- const numberedLines = selectedLines.map((line, i) => `${start + i}\t${line}`).join('\n')
56
+ const numberedLines = selectedLines.map((line, i) => `${start + i + 1}\t${line}`).join('\n')
57
+
58
+ context.fileReadTracker?.recordRead(input.path)
40
59
 
41
60
  return {
42
61
  success: true,
@@ -51,14 +70,27 @@ export const ReadFileTool = defineTool({
51
70
  }
52
71
 
53
72
  const filePath = resolve(context.workingDirectory, input.path)
54
- const content = await readFile(filePath, 'utf-8')
73
+ const buffer = await readFile(filePath)
74
+ const binaryGuidance = describeStructuredBinaryRead(filePath, buffer)
75
+ if (binaryGuidance) {
76
+ return {
77
+ success: false,
78
+ output: binaryGuidance,
79
+ data: {
80
+ path: filePath,
81
+ binary: true,
82
+ },
83
+ }
84
+ }
85
+ const content = buffer.toString('utf-8')
55
86
  const lines = content.split('\n')
56
87
 
57
- const start = Math.max(0, input.offset ?? 0)
58
- const end = input.limit ? start + input.limit : lines.length
88
+ const { start, end } = resolveReadWindow(input, lines.length)
59
89
  const selectedLines = lines.slice(start, end)
60
90
 
61
- const numberedLines = selectedLines.map((line, i) => `${start + i}\t${line}`).join('\n')
91
+ const numberedLines = selectedLines.map((line, i) => `${start + i + 1}\t${line}`).join('\n')
92
+
93
+ context.fileReadTracker?.recordRead(filePath)
62
94
 
63
95
  return {
64
96
  success: true,
@@ -71,3 +103,42 @@ export const ReadFileTool = defineTool({
71
103
  }
72
104
  },
73
105
  })
106
+
107
+ function describeStructuredBinaryRead(path: string, buffer: Buffer): string | null {
108
+ const ext = extname(path).toLowerCase()
109
+ if (ext === '.docx') return buildStructuredBinaryGuidance(path, 'DOCX', 'python-docx')
110
+ if (ext === '.pptx') return buildStructuredBinaryGuidance(path, 'PPTX', 'python-pptx')
111
+ if (ext === '.xlsx') return buildStructuredBinaryGuidance(path, 'XLSX', 'openpyxl')
112
+ if (ext === '.pdf' || startsWithPdfHeader(buffer)) {
113
+ return buildStructuredBinaryGuidance(path, 'PDF', 'pdftotext or PyMuPDF')
114
+ }
115
+ return null
116
+ }
117
+
118
+ function startsWithPdfHeader(buffer: Buffer): boolean {
119
+ return buffer.length >= 4 && buffer.subarray(0, 4).toString('utf8') === '%PDF'
120
+ }
121
+
122
+ function buildStructuredBinaryGuidance(path: string, format: string, extractor: string): string {
123
+ return [
124
+ `The file "${path}" is a ${format} document package, not UTF-8 text.`,
125
+ 'Do not use the read/cat tools as evidence for this raw file.',
126
+ `Extract its text with shell/Python tooling already available in the sandbox (${extractor}), write the extracted text or summary under scratch, then read that text file.`,
127
+ 'If extraction fails, report the exact filename and extraction error instead of claiming the attachment is unavailable.',
128
+ ].join('\n')
129
+ }
130
+
131
+ function resolveReadWindow(
132
+ input: z.infer<typeof inputSchema>,
133
+ totalLines: number,
134
+ ): { start: number; end: number } {
135
+ if (input.readRange) {
136
+ const [first, last] = input.readRange
137
+ const start = Math.max(0, first - 1)
138
+ const end = Math.min(totalLines, Math.max(start, last))
139
+ return { start, end }
140
+ }
141
+ const start = Math.max(0, input.offset ?? 0)
142
+ const end = input.limit ? start + input.limit : totalLines
143
+ return { start, end }
144
+ }
@@ -1,10 +1,22 @@
1
1
  import { z } from 'zod'
2
+ import { toolDiscoveryHint } from '../../registry/tool/execute.js'
2
3
  import { defineTool } from '../defineTool.js'
3
4
 
4
5
  const inputSchema = z.object({
5
6
  query: z.string().describe('Tool name or capability keyword to search for'),
6
7
  })
7
8
 
9
+ // Activate only the best-ranked matches. Top-5 is the literature's sweet
10
+ // spot (retrieval at k=5 matches oracle toolsets) and bounds how much
11
+ // schema weight a single search can add to every subsequent iteration —
12
+ // each activation also busts the prompt-cache prefix once.
13
+ const ACTIVATION_TOP_K = 5
14
+
15
+ // Near-misses are reported name+hint WITHOUT activating, so a retrieval
16
+ // miss becomes one cheap re-query instead of a dead end (the fixed-top-k
17
+ // hard-tail failure mode: the right tool ranked 6th-10th).
18
+ const NEAR_MISS_LIMIT = 5
19
+
8
20
  export const SearchToolsTool = defineTool({
9
21
  name: 'search_tools',
10
22
  description:
@@ -26,25 +38,48 @@ export const SearchToolsTool = defineTool({
26
38
  }
27
39
  }
28
40
 
29
- const matches = context.toolRegistry.searchDeferred(input.query)
41
+ const allowed =
42
+ context.allowedTools && context.allowedTools.length > 0 ? new Set(context.allowedTools) : null
43
+ // `searchDeferred` returns a ranked list (score-descending), so slicing
44
+ // the head is a true top-k activation, not an arbitrary subset.
45
+ const ranked = context.toolRegistry
46
+ .searchDeferred(input.query)
47
+ .filter((tool) => !allowed || allowed.has(tool.name))
30
48
 
31
- if (matches.length === 0) {
49
+ if (ranked.length === 0) {
32
50
  return {
33
51
  success: true,
34
52
  output: `No deferred tools matching "${input.query}". All matching tools are already active.`,
35
53
  }
36
54
  }
37
55
 
38
- context.toolRegistry.activate(matches.map((t) => t.name))
56
+ const activated = ranked.slice(0, ACTIVATION_TOP_K)
57
+ const nearMisses = ranked.slice(ACTIVATION_TOP_K, ACTIVATION_TOP_K + NEAR_MISS_LIMIT)
58
+
59
+ context.toolRegistry.activate(activated.map((t) => t.name))
39
60
 
40
- const descriptions = matches.map((t) => `- ${t.name}: ${t.description}`).join('\n')
61
+ const descriptions = activated.map((t) => `- ${t.name}: ${t.description}`).join('\n')
62
+ const sections = [`Activated ${activated.length} tool(s):\n${descriptions}`]
63
+
64
+ if (nearMisses.length > 0) {
65
+ const hints = nearMisses
66
+ .map((t) => {
67
+ const hint = toolDiscoveryHint(t.description)
68
+ return hint.length > 0 ? `- ${t.name}: ${hint}` : `- ${t.name}`
69
+ })
70
+ .join('\n')
71
+ sections.push(
72
+ `Also matched but NOT loaded (search again with a more specific query, e.g. the tool name, to load one of these):\n${hints}`,
73
+ )
74
+ }
41
75
 
42
76
  return {
43
77
  success: true,
44
- output: `Activated ${matches.length} tool(s):\n${descriptions}`,
78
+ output: sections.join('\n\n'),
45
79
  data: {
46
- activated: matches.map((t) => t.name),
47
- count: matches.length,
80
+ activated: activated.map((t) => t.name),
81
+ count: activated.length,
82
+ nearMisses: nearMisses.map((t) => t.name),
48
83
  },
49
84
  }
50
85
  },
@@ -0,0 +1,126 @@
1
+ import { stat } from 'node:fs/promises'
2
+ import { resolve } from 'node:path'
3
+ import { z } from 'zod'
4
+ import type { ToolContext } from '../../types/tool/index.js'
5
+ import { defineTool } from '../defineTool.js'
6
+
7
+ const inputSchema = z.object({
8
+ paths: z
9
+ .array(z.string().min(1))
10
+ .min(1)
11
+ .describe('Expected output file paths to verify. Each path is checked for existence and size.'),
12
+ min_bytes: z
13
+ .number()
14
+ .int()
15
+ .min(0)
16
+ .optional()
17
+ .describe(
18
+ 'Minimum acceptable file size in bytes. A file under this size counts as missing. Default: 1 (any non-empty file passes).',
19
+ ),
20
+ })
21
+
22
+ type Result = {
23
+ path: string
24
+ exists: boolean
25
+ size_bytes?: number
26
+ ok: boolean
27
+ error?: string
28
+ }
29
+
30
+ export const VerifyOutputsTool = defineTool({
31
+ name: 'verify_outputs',
32
+ description:
33
+ "Verify that a set of expected output files actually exist on disk and are non-empty. Use this BEFORE declaring multi-worker work done — pass every expected output path the workers were supposed to produce. Returns a per-path report (exists, size_bytes, ok) plus an overall pass/fail summary. If any path fails, spawn a fresh `create_task` on the responsible specialist with a brief that names the missing path and the prior worker's output location — do NOT paper over a missing file in prose.",
34
+ inputSchema,
35
+ category: 'filesystem',
36
+ permissions: ['file_read'],
37
+ readOnly: true,
38
+ destructive: false,
39
+ concurrencySafe: true,
40
+
41
+ async execute(input, context) {
42
+ const minBytes = input.min_bytes ?? 1
43
+ const results: Result[] = await Promise.all(
44
+ input.paths.map((path) => verifyOne({ path, minBytes, context })),
45
+ )
46
+
47
+ const passed = results.filter((r) => r.ok).length
48
+ const failed = results.filter((r) => !r.ok)
49
+ const summary = {
50
+ total: results.length,
51
+ passed,
52
+ failed: failed.length,
53
+ min_bytes: minBytes,
54
+ }
55
+ const lines = results.map((r) =>
56
+ r.ok
57
+ ? `- OK ${r.path}${typeof r.size_bytes === 'number' ? ` (${r.size_bytes}B)` : ''}`
58
+ : `- FAIL ${r.path} — ${r.error ?? (r.exists ? `size ${r.size_bytes ?? 0}B < min ${minBytes}B` : 'missing')}`,
59
+ )
60
+ const header = `Verify outputs: ${passed}/${results.length} passed (min ${minBytes}B)`
61
+ return {
62
+ success: failed.length === 0,
63
+ output: [header, '', ...lines].join('\n'),
64
+ error:
65
+ failed.length > 0
66
+ ? `${failed.length} of ${results.length} expected outputs failed verification`
67
+ : undefined,
68
+ data: { results, summary },
69
+ }
70
+ },
71
+ })
72
+
73
+ async function verifyOne(input: {
74
+ path: string
75
+ minBytes: number
76
+ context: ToolContext
77
+ }): Promise<Result> {
78
+ const { path, minBytes, context } = input
79
+ if (context.sandbox) {
80
+ try {
81
+ const buffer = await context.sandbox.readFile(path)
82
+ const size = buffer.byteLength
83
+ return {
84
+ path,
85
+ exists: true,
86
+ size_bytes: size,
87
+ ok: size >= minBytes,
88
+ }
89
+ } catch (err) {
90
+ return {
91
+ path,
92
+ exists: false,
93
+ ok: false,
94
+ error: err instanceof Error ? err.message : String(err),
95
+ }
96
+ }
97
+ }
98
+
99
+ const filePath = resolve(context.workingDirectory, path)
100
+ try {
101
+ const info = await stat(filePath)
102
+ if (!info.isFile()) {
103
+ return {
104
+ path,
105
+ exists: true,
106
+ ok: false,
107
+ error: 'not a regular file',
108
+ }
109
+ }
110
+ return {
111
+ path,
112
+ exists: true,
113
+ size_bytes: info.size,
114
+ ok: info.size >= minBytes,
115
+ }
116
+ } catch (err) {
117
+ const code = (err as NodeJS.ErrnoException)?.code
118
+ if (code === 'ENOENT') return { path, exists: false, ok: false }
119
+ return {
120
+ path,
121
+ exists: false,
122
+ ok: false,
123
+ error: err instanceof Error ? err.message : String(err),
124
+ }
125
+ }
126
+ }
@@ -1,17 +1,40 @@
1
- import { mkdir, writeFile } from 'node:fs/promises'
1
+ import { access, mkdir, writeFile } from 'node:fs/promises'
2
2
  import { dirname, resolve } from 'node:path'
3
3
  import { z } from 'zod'
4
+ import type { ToolContext } from '../../types/tool/index.js'
4
5
  import { defineTool } from '../defineTool.js'
5
6
 
6
- const inputSchema = z.object({
7
- path: z.string().describe('Path to the file to write'),
8
- content: z.string().describe('Content to write to the file'),
9
- })
7
+ const inputSchema = z
8
+ .object({
9
+ path: z
10
+ .string()
11
+ .min(1)
12
+ .describe(
13
+ 'Relative path to the file to write (e.g. "outputs/report.md"). Required. Must be a non-empty string.',
14
+ ),
15
+ content: z
16
+ .string()
17
+ .optional()
18
+ .describe(
19
+ 'Full file body to write. Required (use "" only for an intentionally empty file). The file is fully overwritten — pass the COMPLETE intended content for this bounded chunk, not a diff. Self-budget content under 12000 characters before calling; if the intended body is longer, write a smaller opening section here, then use `edit` with insertLine: "end" to extend the file section by section. Do NOT try to chain multiple `write` calls, since each one overwrites the previous.',
20
+ ),
21
+ newStr: z
22
+ .string()
23
+ .optional()
24
+ .describe(
25
+ 'Alias for content. Useful for hosts that expose create/write operations as newStr. Self-budget this payload under 12000 characters before calling.',
26
+ ),
27
+ })
28
+ .refine((value) => typeof value.content === 'string' || typeof value.newStr === 'string', {
29
+ message: 'Either content or newStr is required.',
30
+ })
31
+
32
+ type WriteInput = z.infer<typeof inputSchema>
10
33
 
11
34
  export const WriteFileTool = defineTool({
12
- name: 'write_file',
35
+ name: 'write',
13
36
  description:
14
- 'Writes content to a file. Creates the file if it does not exist, overwrites if it does. Creates intermediate directories as needed.',
37
+ 'Writes a file to the local filesystem. Overwrites the existing file at the path if there is one.\n\n- If the file already exists, you must use the `read` tool on it first in this conversation, or this call will fail.\n- Prefer the `edit` tool for modifying existing files — it only sends the diff and preserves the rest of the file byte-for-byte.\n- Use `write` to create a new file or to perform a deliberate full rewrite of a file you have already read.\n- Self-budget content/newStr under 12000 characters before emitting the tool call. For long content, write a smaller opening section, then use `edit` with insertLine: "end" to extend the file section by section. Do not chain multiple `write` calls — each one overwrites the previous.',
15
38
  inputSchema,
16
39
  category: 'filesystem',
17
40
  permissions: ['file_write'],
@@ -19,26 +42,72 @@ export const WriteFileTool = defineTool({
19
42
  destructive: true,
20
43
  concurrencySafe: false,
21
44
 
22
- async execute(input, context) {
45
+ async execute(input: WriteInput, context) {
46
+ const content = input.content ?? input.newStr ?? ''
23
47
  // Sandbox-aware: route through sandbox.writeFile() when available
24
48
  if (context.sandbox) {
25
- await context.sandbox.writeFile(input.path, input.content)
49
+ const sandboxExists = await sandboxFileExists(context, input.path)
50
+ if (sandboxExists) {
51
+ const guard = enforceReadBeforeOverwrite(context, input.path)
52
+ if (guard) return guard
53
+ }
54
+ await context.sandbox.writeFile(input.path, content)
55
+ context.fileReadTracker?.recordRead(input.path)
26
56
  return {
27
57
  success: true,
28
- output: `File written successfully: ${input.path} (${input.content.length} chars) [sandboxed]`,
29
- data: { path: input.path, size: input.content.length, sandboxed: true },
58
+ output: `File written successfully: ${input.path} (${content.length} chars) [sandboxed]`,
59
+ data: { path: input.path, size: content.length, sandboxed: true },
30
60
  }
31
61
  }
32
62
 
33
63
  const filePath = resolve(context.workingDirectory, input.path)
34
64
 
65
+ const localExists = await pathExists(filePath)
66
+ if (localExists) {
67
+ const guard = enforceReadBeforeOverwrite(context, filePath)
68
+ if (guard) return guard
69
+ }
70
+
35
71
  await mkdir(dirname(filePath), { recursive: true })
36
- await writeFile(filePath, input.content, 'utf-8')
72
+ await writeFile(filePath, content, 'utf-8')
73
+ context.fileReadTracker?.recordRead(filePath)
37
74
 
38
75
  return {
39
76
  success: true,
40
- output: `File written successfully: ${filePath} (${input.content.length} chars)`,
41
- data: { path: filePath, size: input.content.length },
77
+ output: `File written successfully: ${filePath} (${content.length} chars)`,
78
+ data: { path: filePath, size: content.length },
42
79
  }
43
80
  },
44
81
  })
82
+
83
+ function enforceReadBeforeOverwrite(
84
+ context: ToolContext,
85
+ key: string,
86
+ ): { success: false; output: ''; error: string } | null {
87
+ if (!context.fileReadTracker) return null
88
+ if (context.fileReadTracker.hasRead(key)) return null
89
+ return {
90
+ success: false,
91
+ output: '',
92
+ error: `${key} already exists. Use the \`read\` tool on it first in this conversation, then call \`write\` again — or prefer \`edit\` for a targeted change.`,
93
+ }
94
+ }
95
+
96
+ async function pathExists(filePath: string): Promise<boolean> {
97
+ try {
98
+ await access(filePath)
99
+ return true
100
+ } catch {
101
+ return false
102
+ }
103
+ }
104
+
105
+ async function sandboxFileExists(context: ToolContext, path: string): Promise<boolean> {
106
+ if (!context.sandbox) return false
107
+ try {
108
+ await context.sandbox.readFile(path)
109
+ return true
110
+ } catch {
111
+ return false
112
+ }
113
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Behavioural contract for `buildAgentTool`:
3
+ *
4
+ * - Reports `success: true` only when BOTH the gateway task state and
5
+ * the underlying `BaseAgentResult.status` say completed.
6
+ * - Reports `success: false` and surfaces `lastError` when either
7
+ * layer disagrees — the canonical bug Codex caught was that a
8
+ * failed subagent could be reported as successful when the gateway
9
+ * forwarded `state: 'completed'` from a manager that did not
10
+ * propagate the run's `status: 'failed'`.
11
+ * - Returns the subagent's `result` string as the tool output on
12
+ * success.
13
+ */
14
+
15
+ import { describe, expect, it } from 'vitest'
16
+
17
+ import type { TaskGateway, TaskHandle } from '../../../types/agent/gateway.js'
18
+ import type { TaskId } from '../../../types/ids/index.js'
19
+ import type { ToolContext } from '../../../types/tool/index.js'
20
+ import { buildAgentTool } from '../agent.js'
21
+
22
+ function makeContext(): ToolContext {
23
+ return {
24
+ runId: 'run_test' as never,
25
+ workingDirectory: '/tmp/test',
26
+ abortSignal: new AbortController().signal,
27
+ env: {},
28
+ log: () => {},
29
+ }
30
+ }
31
+
32
+ function fakeGateway(handle: TaskHandle, completed: TaskHandle): TaskGateway {
33
+ return {
34
+ async createTask() {
35
+ return handle
36
+ },
37
+ async waitForTask() {
38
+ return completed
39
+ },
40
+ async continueTask() {},
41
+ cancelTask() {},
42
+ getTask() {
43
+ return completed
44
+ },
45
+ listTasks() {
46
+ return [completed]
47
+ },
48
+ onTaskCompleted() {
49
+ return () => {}
50
+ },
51
+ }
52
+ }
53
+
54
+ const taskId = 'task_subagent' as TaskId
55
+
56
+ const launched: TaskHandle = {
57
+ taskId,
58
+ agentId: 'sales-strategy',
59
+ state: 'running',
60
+ createdAt: 0,
61
+ }
62
+
63
+ describe('buildAgentTool', () => {
64
+ it('reports success when both task state and run status say completed', async () => {
65
+ const gateway = fakeGateway(launched, {
66
+ ...launched,
67
+ state: 'completed',
68
+ result: {
69
+ runId: 'run_inner' as never,
70
+ status: 'completed',
71
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 } as never,
72
+ cost: { inputCostUsd: 0, outputCostUsd: 0, totalCostUsd: 0 } as never,
73
+ iterations: 1,
74
+ durationMs: 10,
75
+ messages: [],
76
+ result: 'final report text',
77
+ },
78
+ completedAt: 10,
79
+ })
80
+
81
+ const tool = buildAgentTool({
82
+ gateway,
83
+ workingDirectory: '/tmp/test',
84
+ allowedAgentIds: ['sales-strategy'],
85
+ })
86
+
87
+ const result = await tool.execute(
88
+ { description: 'plan', prompt: 'go', subagent_type: 'sales-strategy' },
89
+ makeContext(),
90
+ )
91
+
92
+ expect(result.success).toBe(true)
93
+ expect(result.output).toBe('final report text')
94
+ })
95
+
96
+ it('reports failure when run status is failed even though task state is completed', async () => {
97
+ const gateway = fakeGateway(launched, {
98
+ ...launched,
99
+ state: 'completed',
100
+ result: {
101
+ runId: 'run_inner' as never,
102
+ status: 'failed',
103
+ usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 } as never,
104
+ cost: { inputCostUsd: 0, outputCostUsd: 0, totalCostUsd: 0 } as never,
105
+ iterations: 1,
106
+ durationMs: 10,
107
+ messages: [],
108
+ lastError: 'tool budget exceeded',
109
+ },
110
+ completedAt: 10,
111
+ })
112
+
113
+ const tool = buildAgentTool({
114
+ gateway,
115
+ workingDirectory: '/tmp/test',
116
+ allowedAgentIds: ['sales-strategy'],
117
+ })
118
+
119
+ const result = await tool.execute(
120
+ { description: 'plan', prompt: 'go', subagent_type: 'sales-strategy' },
121
+ makeContext(),
122
+ )
123
+
124
+ expect(result.success).toBe(false)
125
+ expect(result.error).toContain('tool budget exceeded')
126
+ expect(result.error).toContain('failed')
127
+ })
128
+
129
+ it('reports failure when task state itself is failed', async () => {
130
+ const gateway = fakeGateway(launched, {
131
+ ...launched,
132
+ state: 'failed',
133
+ result: undefined,
134
+ completedAt: 10,
135
+ })
136
+
137
+ const tool = buildAgentTool({
138
+ gateway,
139
+ workingDirectory: '/tmp/test',
140
+ allowedAgentIds: ['sales-strategy'],
141
+ })
142
+
143
+ const result = await tool.execute(
144
+ { description: 'plan', prompt: 'go', subagent_type: 'sales-strategy' },
145
+ makeContext(),
146
+ )
147
+
148
+ expect(result.success).toBe(false)
149
+ expect(result.error).toContain('failed')
150
+ })
151
+
152
+ it("does not accept a taskStore or runId — plan-task lifecycle is the parent's job", () => {
153
+ // Compile-time pin: AgentToolOptions must NOT include `taskStore`
154
+ // or `runId`. The Agent tool used to manage a per-call plan task
155
+ // internally and Codex caught a leak: when the subagent failed,
156
+ // the plan task stayed `'in_progress'` forever because
157
+ // `TaskStatus` has no `'failed'` value to flip to. Drop the
158
+ // integration entirely; if a host wants to track delegations as
159
+ // plan tasks, it does so via `TaskCreate` / `TaskUpdate` on its
160
+ // own side, where it owns the status semantics. This test
161
+ // freezes that decision.
162
+ const allowedOpts: Parameters<typeof buildAgentTool>[0] = {
163
+ gateway: fakeGateway(launched, launched),
164
+ workingDirectory: '/tmp/test',
165
+ allowedAgentIds: ['sales-strategy'],
166
+ runtimeContext: undefined,
167
+ onTaskLaunched: undefined,
168
+ }
169
+ expect('taskStore' in allowedOpts).toBe(false)
170
+ expect('runId' in allowedOpts).toBe(false)
171
+ })
172
+ })