@librechat/agents 3.4.7 → 3.5.1

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 (924) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +0 -356
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/agents/projection.cjs +0 -9
  4. package/dist/cjs/agents/projection.cjs.map +1 -1
  5. package/dist/cjs/common/constants.cjs +0 -26
  6. package/dist/cjs/common/constants.cjs.map +1 -1
  7. package/dist/cjs/common/enum.cjs +0 -85
  8. package/dist/cjs/common/enum.cjs.map +1 -1
  9. package/dist/cjs/events.cjs +0 -14
  10. package/dist/cjs/events.cjs.map +1 -1
  11. package/dist/cjs/graphs/Graph.cjs +1 -909
  12. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  13. package/dist/cjs/graphs/MultiAgentGraph.cjs +7 -272
  14. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  15. package/dist/cjs/graphs/applyGraphRuntimeConfig.cjs.map +1 -1
  16. package/dist/cjs/graphs/createGraph.cjs.map +1 -1
  17. package/dist/cjs/hitl/askUserQuestion.cjs +0 -53
  18. package/dist/cjs/hitl/askUserQuestion.cjs.map +1 -1
  19. package/dist/cjs/hitl/askUserQuestions.cjs +0 -20
  20. package/dist/cjs/hitl/askUserQuestions.cjs.map +1 -1
  21. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs +0 -6
  22. package/dist/cjs/hitl/askUserQuestionsInterrupt.cjs.map +1 -1
  23. package/dist/cjs/hooks/HookRegistry.cjs +0 -127
  24. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -1
  25. package/dist/cjs/hooks/createToolPolicyHook.cjs +0 -38
  26. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -1
  27. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +0 -77
  28. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
  29. package/dist/cjs/hooks/executeHooks.cjs +0 -79
  30. package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
  31. package/dist/cjs/hooks/index.cjs +0 -16
  32. package/dist/cjs/hooks/index.cjs.map +1 -1
  33. package/dist/cjs/hooks/matchers.cjs +0 -104
  34. package/dist/cjs/hooks/matchers.cjs.map +1 -1
  35. package/dist/cjs/hooks/types.cjs +0 -8
  36. package/dist/cjs/hooks/types.cjs.map +1 -1
  37. package/dist/cjs/instrumentation.cjs +0 -14
  38. package/dist/cjs/instrumentation.cjs.map +1 -1
  39. package/dist/cjs/langfuse.cjs +0 -75
  40. package/dist/cjs/langfuse.cjs.map +1 -1
  41. package/dist/cjs/langfuseConfig.cjs.map +1 -1
  42. package/dist/cjs/langfuseRuntimeContext.cjs +0 -13
  43. package/dist/cjs/langfuseRuntimeContext.cjs.map +1 -1
  44. package/dist/cjs/langfuseRuntimeScope.cjs +0 -4
  45. package/dist/cjs/langfuseRuntimeScope.cjs.map +1 -1
  46. package/dist/cjs/langfuseSpanRegistry.cjs +0 -28
  47. package/dist/cjs/langfuseSpanRegistry.cjs.map +1 -1
  48. package/dist/cjs/langfuseToolOutputTracing.cjs +0 -3
  49. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  50. package/dist/cjs/langfuseTraceShaping.cjs +0 -74
  51. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  52. package/dist/cjs/llm/anthropic/index.cjs +0 -3
  53. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  54. package/dist/cjs/llm/anthropic/types.cjs.map +1 -1
  55. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +1 -111
  56. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  57. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs +0 -1
  58. package/dist/cjs/llm/anthropic/utils/message_outputs.cjs.map +1 -1
  59. package/dist/cjs/llm/anthropic/utils/stream_events.cjs +0 -8
  60. package/dist/cjs/llm/anthropic/utils/stream_events.cjs.map +1 -1
  61. package/dist/cjs/llm/anthropic/utils/tools.cjs.map +1 -1
  62. package/dist/cjs/llm/bedrock/cachePoints.cjs.map +1 -1
  63. package/dist/cjs/llm/bedrock/index.cjs +1 -95
  64. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  65. package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
  66. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +0 -78
  67. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  68. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs +0 -24
  69. package/dist/cjs/llm/bedrock/utils/message_outputs.cjs.map +1 -1
  70. package/dist/cjs/llm/contextOverflowRecovery.cjs +0 -62
  71. package/dist/cjs/llm/contextOverflowRecovery.cjs.map +1 -1
  72. package/dist/cjs/llm/fake.cjs.map +1 -1
  73. package/dist/cjs/llm/google/index.cjs +2 -21
  74. package/dist/cjs/llm/google/index.cjs.map +1 -1
  75. package/dist/cjs/llm/google/utils/common.cjs +0 -35
  76. package/dist/cjs/llm/google/utils/common.cjs.map +1 -1
  77. package/dist/cjs/llm/init.cjs +0 -9
  78. package/dist/cjs/llm/init.cjs.map +1 -1
  79. package/dist/cjs/llm/invoke.cjs +1 -270
  80. package/dist/cjs/llm/invoke.cjs.map +1 -1
  81. package/dist/cjs/llm/mistral/index.cjs.map +1 -1
  82. package/dist/cjs/llm/openai/index.cjs +0 -99
  83. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  84. package/dist/cjs/llm/openai/streamMetadata.cjs +0 -21
  85. package/dist/cjs/llm/openai/streamMetadata.cjs.map +1 -1
  86. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  87. package/dist/cjs/llm/openrouter/index.cjs +0 -1
  88. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  89. package/dist/cjs/llm/openrouter/toolCache.cjs +0 -6
  90. package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
  91. package/dist/cjs/llm/preempt.cjs +0 -75
  92. package/dist/cjs/llm/preempt.cjs.map +1 -1
  93. package/dist/cjs/llm/providers.cjs.map +1 -1
  94. package/dist/cjs/llm/request.cjs +0 -10
  95. package/dist/cjs/llm/request.cjs.map +1 -1
  96. package/dist/cjs/llm/stream/chunkAdapters.cjs +0 -62
  97. package/dist/cjs/llm/stream/chunkAdapters.cjs.map +1 -1
  98. package/dist/cjs/llm/stream/smoother.cjs +0 -58
  99. package/dist/cjs/llm/stream/smoother.cjs.map +1 -1
  100. package/dist/cjs/llm/streamLimits.cjs +4 -293
  101. package/dist/cjs/llm/streamLimits.cjs.map +1 -1
  102. package/dist/cjs/llm/truncation.cjs +0 -36
  103. package/dist/cjs/llm/truncation.cjs.map +1 -1
  104. package/dist/cjs/llm/vertexai/index.cjs +0 -330
  105. package/dist/cjs/llm/vertexai/index.cjs.map +1 -1
  106. package/dist/cjs/messages/alternation.cjs +0 -43
  107. package/dist/cjs/messages/alternation.cjs.map +1 -1
  108. package/dist/cjs/messages/anthropicToolCache.cjs +0 -45
  109. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
  110. package/dist/cjs/messages/assistantPhase.cjs +0 -12
  111. package/dist/cjs/messages/assistantPhase.cjs.map +1 -1
  112. package/dist/cjs/messages/budget.cjs +0 -9
  113. package/dist/cjs/messages/budget.cjs.map +1 -1
  114. package/dist/cjs/messages/cache.cjs +0 -172
  115. package/dist/cjs/messages/cache.cjs.map +1 -1
  116. package/dist/cjs/messages/content.cjs +0 -9
  117. package/dist/cjs/messages/content.cjs.map +1 -1
  118. package/dist/cjs/messages/contextPruning.cjs +0 -15
  119. package/dist/cjs/messages/contextPruning.cjs.map +1 -1
  120. package/dist/cjs/messages/contextPruningSettings.cjs +0 -3
  121. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -1
  122. package/dist/cjs/messages/core.cjs +0 -48
  123. package/dist/cjs/messages/core.cjs.map +1 -1
  124. package/dist/cjs/messages/format.cjs +1 -345
  125. package/dist/cjs/messages/format.cjs.map +1 -1
  126. package/dist/cjs/messages/handoffCue.cjs +0 -36
  127. package/dist/cjs/messages/handoffCue.cjs.map +1 -1
  128. package/dist/cjs/messages/ids.cjs.map +1 -1
  129. package/dist/cjs/messages/injected.cjs +0 -33
  130. package/dist/cjs/messages/injected.cjs.map +1 -1
  131. package/dist/cjs/messages/langchain.cjs +0 -14
  132. package/dist/cjs/messages/langchain.cjs.map +1 -1
  133. package/dist/cjs/messages/prune.cjs +1 -184
  134. package/dist/cjs/messages/prune.cjs.map +1 -1
  135. package/dist/cjs/messages/recency.cjs +0 -32
  136. package/dist/cjs/messages/recency.cjs.map +1 -1
  137. package/dist/cjs/messages/reducer.cjs +0 -30
  138. package/dist/cjs/messages/reducer.cjs.map +1 -1
  139. package/dist/cjs/messages/tools.cjs +0 -14
  140. package/dist/cjs/messages/tools.cjs.map +1 -1
  141. package/dist/cjs/openai/index.cjs.map +1 -1
  142. package/dist/cjs/prompts/activityLabel.cjs +9 -79
  143. package/dist/cjs/prompts/activityLabel.cjs.map +1 -1
  144. package/dist/cjs/responses/index.cjs.map +1 -1
  145. package/dist/cjs/run.cjs +2 -538
  146. package/dist/cjs/run.cjs.map +1 -1
  147. package/dist/cjs/session/AgentSession.cjs +1 -17
  148. package/dist/cjs/session/AgentSession.cjs.map +1 -1
  149. package/dist/cjs/session/JsonlSessionStore.cjs.map +1 -1
  150. package/dist/cjs/session/handlers.cjs +0 -7
  151. package/dist/cjs/session/handlers.cjs.map +1 -1
  152. package/dist/cjs/session/ids.cjs.map +1 -1
  153. package/dist/cjs/session/messageSerialization.cjs +0 -4
  154. package/dist/cjs/session/messageSerialization.cjs.map +1 -1
  155. package/dist/cjs/stream.cjs +1 -93
  156. package/dist/cjs/stream.cjs.map +1 -1
  157. package/dist/cjs/summarization/index.cjs +0 -27
  158. package/dist/cjs/summarization/index.cjs.map +1 -1
  159. package/dist/cjs/summarization/node.cjs +0 -273
  160. package/dist/cjs/summarization/node.cjs.map +1 -1
  161. package/dist/cjs/tools/BashExecutor.cjs +0 -35
  162. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  163. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +0 -24
  164. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  165. package/dist/cjs/tools/Calculator.cjs.map +1 -1
  166. package/dist/cjs/tools/CodeExecutor.cjs +0 -13
  167. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  168. package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -1
  169. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +0 -101
  170. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  171. package/dist/cjs/tools/ReadFile.cjs.map +1 -1
  172. package/dist/cjs/tools/SkillTool.cjs +0 -5
  173. package/dist/cjs/tools/SkillTool.cjs.map +1 -1
  174. package/dist/cjs/tools/SubagentTool.cjs +0 -10
  175. package/dist/cjs/tools/SubagentTool.cjs.map +1 -1
  176. package/dist/cjs/tools/ToolNode.cjs +6 -1038
  177. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  178. package/dist/cjs/tools/ToolSearch.cjs +0 -179
  179. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  180. package/dist/cjs/tools/cloudflare/CloudflareBridgeRuntime.cjs.map +1 -1
  181. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  182. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +0 -39
  183. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  184. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
  185. package/dist/cjs/tools/eagerEventExecution.cjs +0 -6
  186. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
  187. package/dist/cjs/tools/handlers.cjs +1 -41
  188. package/dist/cjs/tools/handlers.cjs.map +1 -1
  189. package/dist/cjs/tools/intentArg.cjs +1 -132
  190. package/dist/cjs/tools/intentArg.cjs.map +1 -1
  191. package/dist/cjs/tools/local/CompileCheckTool.cjs +0 -25
  192. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  193. package/dist/cjs/tools/local/FileCheckpointer.cjs +0 -23
  194. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
  195. package/dist/cjs/tools/local/LocalCodingTools.cjs +0 -80
  196. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  197. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +0 -126
  198. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  199. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -1
  200. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +0 -12
  201. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -1
  202. package/dist/cjs/tools/local/attachments.cjs +0 -44
  203. package/dist/cjs/tools/local/attachments.cjs.map +1 -1
  204. package/dist/cjs/tools/local/bashAst.cjs +0 -16
  205. package/dist/cjs/tools/local/bashAst.cjs.map +1 -1
  206. package/dist/cjs/tools/local/editStrategies.cjs +0 -14
  207. package/dist/cjs/tools/local/editStrategies.cjs.map +1 -1
  208. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -1
  209. package/dist/cjs/tools/local/syntaxCheck.cjs +0 -31
  210. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
  211. package/dist/cjs/tools/local/textEncoding.cjs +0 -11
  212. package/dist/cjs/tools/local/textEncoding.cjs.map +1 -1
  213. package/dist/cjs/tools/local/workspaceFS.cjs +0 -33
  214. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
  215. package/dist/cjs/tools/ptcTimeout.cjs.map +1 -1
  216. package/dist/cjs/tools/schema.cjs +0 -8
  217. package/dist/cjs/tools/schema.cjs.map +1 -1
  218. package/dist/cjs/tools/search/anthropic.cjs +0 -9
  219. package/dist/cjs/tools/search/anthropic.cjs.map +1 -1
  220. package/dist/cjs/tools/search/content.cjs.map +1 -1
  221. package/dist/cjs/tools/search/crw-scraper.cjs +0 -33
  222. package/dist/cjs/tools/search/crw-scraper.cjs.map +1 -1
  223. package/dist/cjs/tools/search/crw-search.cjs +0 -2
  224. package/dist/cjs/tools/search/crw-search.cjs.map +1 -1
  225. package/dist/cjs/tools/search/firecrawl.cjs +0 -25
  226. package/dist/cjs/tools/search/firecrawl.cjs.map +1 -1
  227. package/dist/cjs/tools/search/format.cjs +0 -30
  228. package/dist/cjs/tools/search/format.cjs.map +1 -1
  229. package/dist/cjs/tools/search/highlights.cjs +0 -16
  230. package/dist/cjs/tools/search/highlights.cjs.map +1 -1
  231. package/dist/cjs/tools/search/keenable-scraper.cjs +0 -8
  232. package/dist/cjs/tools/search/keenable-scraper.cjs.map +1 -1
  233. package/dist/cjs/tools/search/keenable-search.cjs +0 -7
  234. package/dist/cjs/tools/search/keenable-search.cjs.map +1 -1
  235. package/dist/cjs/tools/search/rerankers.cjs +0 -35
  236. package/dist/cjs/tools/search/rerankers.cjs.map +1 -1
  237. package/dist/cjs/tools/search/schema.cjs +0 -1
  238. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  239. package/dist/cjs/tools/search/search.cjs +1 -14
  240. package/dist/cjs/tools/search/search.cjs.map +1 -1
  241. package/dist/cjs/tools/search/serper-scraper.cjs +0 -46
  242. package/dist/cjs/tools/search/serper-scraper.cjs.map +1 -1
  243. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -1
  244. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -1
  245. package/dist/cjs/tools/search/tool.cjs +0 -25
  246. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  247. package/dist/cjs/tools/search/utils.cjs +0 -8
  248. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  249. package/dist/cjs/tools/skillCatalog.cjs +0 -6
  250. package/dist/cjs/tools/skillCatalog.cjs.map +1 -1
  251. package/dist/cjs/tools/streamedToolCallSeals.cjs +0 -6
  252. package/dist/cjs/tools/streamedToolCallSeals.cjs.map +1 -1
  253. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs +0 -2
  254. package/dist/cjs/tools/subagent/SubagentExecutionRegistry.cjs.map +1 -1
  255. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +3 -213
  256. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  257. package/dist/cjs/tools/subagent/SubagentReplay.cjs.map +1 -1
  258. package/dist/cjs/tools/subagent/childGraphConfig.cjs +0 -4
  259. package/dist/cjs/tools/subagent/childGraphConfig.cjs.map +1 -1
  260. package/dist/cjs/tools/subagent/runtimeLimits.cjs.map +1 -1
  261. package/dist/cjs/tools/toolOutputReferences.cjs +1 -322
  262. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
  263. package/dist/cjs/types/hitl.cjs +0 -5
  264. package/dist/cjs/types/hitl.cjs.map +1 -1
  265. package/dist/cjs/utils/callbacks.cjs.map +1 -1
  266. package/dist/cjs/utils/errors.cjs +14 -165
  267. package/dist/cjs/utils/errors.cjs.map +1 -1
  268. package/dist/cjs/utils/events.cjs +0 -10
  269. package/dist/cjs/utils/events.cjs.map +1 -1
  270. package/dist/cjs/utils/graph.cjs.map +1 -1
  271. package/dist/cjs/utils/handlers.cjs +0 -3
  272. package/dist/cjs/utils/handlers.cjs.map +1 -1
  273. package/dist/cjs/utils/llm.cjs +0 -1
  274. package/dist/cjs/utils/llm.cjs.map +1 -1
  275. package/dist/cjs/utils/misc.cjs +0 -16
  276. package/dist/cjs/utils/misc.cjs.map +1 -1
  277. package/dist/cjs/utils/proxy.cjs +0 -26
  278. package/dist/cjs/utils/proxy.cjs.map +1 -1
  279. package/dist/cjs/utils/run.cjs +0 -6
  280. package/dist/cjs/utils/run.cjs.map +1 -1
  281. package/dist/cjs/utils/schema.cjs +0 -5
  282. package/dist/cjs/utils/schema.cjs.map +1 -1
  283. package/dist/cjs/utils/title.cjs +0 -6
  284. package/dist/cjs/utils/title.cjs.map +1 -1
  285. package/dist/cjs/utils/tokens.cjs +0 -113
  286. package/dist/cjs/utils/tokens.cjs.map +1 -1
  287. package/dist/cjs/utils/toolContent.cjs +0 -41
  288. package/dist/cjs/utils/toolContent.cjs.map +1 -1
  289. package/dist/cjs/utils/truncation.cjs +0 -61
  290. package/dist/cjs/utils/truncation.cjs.map +1 -1
  291. package/dist/esm/agents/AgentContext.mjs +0 -356
  292. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  293. package/dist/esm/agents/projection.mjs +0 -9
  294. package/dist/esm/agents/projection.mjs.map +1 -1
  295. package/dist/esm/common/constants.mjs +0 -26
  296. package/dist/esm/common/constants.mjs.map +1 -1
  297. package/dist/esm/common/enum.mjs +0 -85
  298. package/dist/esm/common/enum.mjs.map +1 -1
  299. package/dist/esm/events.mjs +0 -14
  300. package/dist/esm/events.mjs.map +1 -1
  301. package/dist/esm/graphs/Graph.mjs +1 -909
  302. package/dist/esm/graphs/Graph.mjs.map +1 -1
  303. package/dist/esm/graphs/MultiAgentGraph.mjs +7 -272
  304. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  305. package/dist/esm/graphs/applyGraphRuntimeConfig.mjs.map +1 -1
  306. package/dist/esm/graphs/createGraph.mjs.map +1 -1
  307. package/dist/esm/hitl/askUserQuestion.mjs +0 -53
  308. package/dist/esm/hitl/askUserQuestion.mjs.map +1 -1
  309. package/dist/esm/hitl/askUserQuestions.mjs +0 -20
  310. package/dist/esm/hitl/askUserQuestions.mjs.map +1 -1
  311. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs +0 -6
  312. package/dist/esm/hitl/askUserQuestionsInterrupt.mjs.map +1 -1
  313. package/dist/esm/hooks/HookRegistry.mjs +0 -127
  314. package/dist/esm/hooks/HookRegistry.mjs.map +1 -1
  315. package/dist/esm/hooks/createToolPolicyHook.mjs +0 -38
  316. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -1
  317. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +0 -77
  318. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
  319. package/dist/esm/hooks/executeHooks.mjs +0 -79
  320. package/dist/esm/hooks/executeHooks.mjs.map +1 -1
  321. package/dist/esm/hooks/index.mjs +0 -16
  322. package/dist/esm/hooks/index.mjs.map +1 -1
  323. package/dist/esm/hooks/matchers.mjs +0 -104
  324. package/dist/esm/hooks/matchers.mjs.map +1 -1
  325. package/dist/esm/hooks/types.mjs +0 -8
  326. package/dist/esm/hooks/types.mjs.map +1 -1
  327. package/dist/esm/instrumentation.mjs +0 -14
  328. package/dist/esm/instrumentation.mjs.map +1 -1
  329. package/dist/esm/langfuse.mjs +0 -75
  330. package/dist/esm/langfuse.mjs.map +1 -1
  331. package/dist/esm/langfuseConfig.mjs.map +1 -1
  332. package/dist/esm/langfuseRuntimeContext.mjs +0 -13
  333. package/dist/esm/langfuseRuntimeContext.mjs.map +1 -1
  334. package/dist/esm/langfuseRuntimeScope.mjs +0 -4
  335. package/dist/esm/langfuseRuntimeScope.mjs.map +1 -1
  336. package/dist/esm/langfuseSpanRegistry.mjs +0 -28
  337. package/dist/esm/langfuseSpanRegistry.mjs.map +1 -1
  338. package/dist/esm/langfuseToolOutputTracing.mjs +0 -3
  339. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  340. package/dist/esm/langfuseTraceShaping.mjs +0 -74
  341. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  342. package/dist/esm/llm/anthropic/index.mjs +0 -3
  343. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  344. package/dist/esm/llm/anthropic/types.mjs.map +1 -1
  345. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +1 -111
  346. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  347. package/dist/esm/llm/anthropic/utils/message_outputs.mjs +0 -1
  348. package/dist/esm/llm/anthropic/utils/message_outputs.mjs.map +1 -1
  349. package/dist/esm/llm/anthropic/utils/stream_events.mjs +0 -8
  350. package/dist/esm/llm/anthropic/utils/stream_events.mjs.map +1 -1
  351. package/dist/esm/llm/anthropic/utils/tools.mjs.map +1 -1
  352. package/dist/esm/llm/bedrock/cachePoints.mjs.map +1 -1
  353. package/dist/esm/llm/bedrock/index.mjs +1 -95
  354. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  355. package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
  356. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +0 -78
  357. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  358. package/dist/esm/llm/bedrock/utils/message_outputs.mjs +0 -24
  359. package/dist/esm/llm/bedrock/utils/message_outputs.mjs.map +1 -1
  360. package/dist/esm/llm/contextOverflowRecovery.mjs +0 -62
  361. package/dist/esm/llm/contextOverflowRecovery.mjs.map +1 -1
  362. package/dist/esm/llm/fake.mjs.map +1 -1
  363. package/dist/esm/llm/google/index.mjs +2 -21
  364. package/dist/esm/llm/google/index.mjs.map +1 -1
  365. package/dist/esm/llm/google/utils/common.mjs +0 -35
  366. package/dist/esm/llm/google/utils/common.mjs.map +1 -1
  367. package/dist/esm/llm/init.mjs +0 -9
  368. package/dist/esm/llm/init.mjs.map +1 -1
  369. package/dist/esm/llm/invoke.mjs +1 -270
  370. package/dist/esm/llm/invoke.mjs.map +1 -1
  371. package/dist/esm/llm/mistral/index.mjs.map +1 -1
  372. package/dist/esm/llm/openai/index.mjs +0 -99
  373. package/dist/esm/llm/openai/index.mjs.map +1 -1
  374. package/dist/esm/llm/openai/streamMetadata.mjs +0 -21
  375. package/dist/esm/llm/openai/streamMetadata.mjs.map +1 -1
  376. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  377. package/dist/esm/llm/openrouter/index.mjs +0 -1
  378. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  379. package/dist/esm/llm/openrouter/toolCache.mjs +0 -6
  380. package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
  381. package/dist/esm/llm/preempt.mjs +0 -75
  382. package/dist/esm/llm/preempt.mjs.map +1 -1
  383. package/dist/esm/llm/providers.mjs.map +1 -1
  384. package/dist/esm/llm/request.mjs +0 -10
  385. package/dist/esm/llm/request.mjs.map +1 -1
  386. package/dist/esm/llm/stream/chunkAdapters.mjs +0 -62
  387. package/dist/esm/llm/stream/chunkAdapters.mjs.map +1 -1
  388. package/dist/esm/llm/stream/smoother.mjs +0 -58
  389. package/dist/esm/llm/stream/smoother.mjs.map +1 -1
  390. package/dist/esm/llm/streamLimits.mjs +4 -293
  391. package/dist/esm/llm/streamLimits.mjs.map +1 -1
  392. package/dist/esm/llm/truncation.mjs +0 -36
  393. package/dist/esm/llm/truncation.mjs.map +1 -1
  394. package/dist/esm/llm/vertexai/index.mjs +0 -330
  395. package/dist/esm/llm/vertexai/index.mjs.map +1 -1
  396. package/dist/esm/messages/alternation.mjs +0 -43
  397. package/dist/esm/messages/alternation.mjs.map +1 -1
  398. package/dist/esm/messages/anthropicToolCache.mjs +0 -45
  399. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
  400. package/dist/esm/messages/assistantPhase.mjs +0 -12
  401. package/dist/esm/messages/assistantPhase.mjs.map +1 -1
  402. package/dist/esm/messages/budget.mjs +0 -9
  403. package/dist/esm/messages/budget.mjs.map +1 -1
  404. package/dist/esm/messages/cache.mjs +0 -172
  405. package/dist/esm/messages/cache.mjs.map +1 -1
  406. package/dist/esm/messages/content.mjs +0 -9
  407. package/dist/esm/messages/content.mjs.map +1 -1
  408. package/dist/esm/messages/contextPruning.mjs +0 -15
  409. package/dist/esm/messages/contextPruning.mjs.map +1 -1
  410. package/dist/esm/messages/contextPruningSettings.mjs +0 -3
  411. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -1
  412. package/dist/esm/messages/core.mjs +0 -48
  413. package/dist/esm/messages/core.mjs.map +1 -1
  414. package/dist/esm/messages/format.mjs +1 -345
  415. package/dist/esm/messages/format.mjs.map +1 -1
  416. package/dist/esm/messages/handoffCue.mjs +0 -36
  417. package/dist/esm/messages/handoffCue.mjs.map +1 -1
  418. package/dist/esm/messages/ids.mjs.map +1 -1
  419. package/dist/esm/messages/injected.mjs +0 -33
  420. package/dist/esm/messages/injected.mjs.map +1 -1
  421. package/dist/esm/messages/langchain.mjs +0 -14
  422. package/dist/esm/messages/langchain.mjs.map +1 -1
  423. package/dist/esm/messages/prune.mjs +1 -184
  424. package/dist/esm/messages/prune.mjs.map +1 -1
  425. package/dist/esm/messages/recency.mjs +0 -32
  426. package/dist/esm/messages/recency.mjs.map +1 -1
  427. package/dist/esm/messages/reducer.mjs +0 -30
  428. package/dist/esm/messages/reducer.mjs.map +1 -1
  429. package/dist/esm/messages/tools.mjs +0 -14
  430. package/dist/esm/messages/tools.mjs.map +1 -1
  431. package/dist/esm/openai/index.mjs.map +1 -1
  432. package/dist/esm/prompts/activityLabel.mjs +9 -79
  433. package/dist/esm/prompts/activityLabel.mjs.map +1 -1
  434. package/dist/esm/responses/index.mjs.map +1 -1
  435. package/dist/esm/run.mjs +2 -538
  436. package/dist/esm/run.mjs.map +1 -1
  437. package/dist/esm/session/AgentSession.mjs +1 -17
  438. package/dist/esm/session/AgentSession.mjs.map +1 -1
  439. package/dist/esm/session/JsonlSessionStore.mjs.map +1 -1
  440. package/dist/esm/session/handlers.mjs +0 -7
  441. package/dist/esm/session/handlers.mjs.map +1 -1
  442. package/dist/esm/session/ids.mjs.map +1 -1
  443. package/dist/esm/session/messageSerialization.mjs +0 -4
  444. package/dist/esm/session/messageSerialization.mjs.map +1 -1
  445. package/dist/esm/stream.mjs +1 -93
  446. package/dist/esm/stream.mjs.map +1 -1
  447. package/dist/esm/summarization/index.mjs +0 -27
  448. package/dist/esm/summarization/index.mjs.map +1 -1
  449. package/dist/esm/summarization/node.mjs +0 -273
  450. package/dist/esm/summarization/node.mjs.map +1 -1
  451. package/dist/esm/tools/BashExecutor.mjs +0 -35
  452. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  453. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +0 -24
  454. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  455. package/dist/esm/tools/Calculator.mjs.map +1 -1
  456. package/dist/esm/tools/CodeExecutor.mjs +0 -13
  457. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  458. package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -1
  459. package/dist/esm/tools/ProgrammaticToolCalling.mjs +0 -101
  460. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  461. package/dist/esm/tools/ReadFile.mjs.map +1 -1
  462. package/dist/esm/tools/SkillTool.mjs +0 -5
  463. package/dist/esm/tools/SkillTool.mjs.map +1 -1
  464. package/dist/esm/tools/SubagentTool.mjs +0 -10
  465. package/dist/esm/tools/SubagentTool.mjs.map +1 -1
  466. package/dist/esm/tools/ToolNode.mjs +6 -1038
  467. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  468. package/dist/esm/tools/ToolSearch.mjs +0 -179
  469. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  470. package/dist/esm/tools/cloudflare/CloudflareBridgeRuntime.mjs.map +1 -1
  471. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  472. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +0 -39
  473. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  474. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
  475. package/dist/esm/tools/eagerEventExecution.mjs +0 -6
  476. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
  477. package/dist/esm/tools/handlers.mjs +1 -41
  478. package/dist/esm/tools/handlers.mjs.map +1 -1
  479. package/dist/esm/tools/intentArg.mjs +1 -132
  480. package/dist/esm/tools/intentArg.mjs.map +1 -1
  481. package/dist/esm/tools/local/CompileCheckTool.mjs +0 -25
  482. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  483. package/dist/esm/tools/local/FileCheckpointer.mjs +0 -23
  484. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
  485. package/dist/esm/tools/local/LocalCodingTools.mjs +0 -80
  486. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  487. package/dist/esm/tools/local/LocalExecutionEngine.mjs +0 -126
  488. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  489. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -1
  490. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +0 -12
  491. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -1
  492. package/dist/esm/tools/local/attachments.mjs +0 -44
  493. package/dist/esm/tools/local/attachments.mjs.map +1 -1
  494. package/dist/esm/tools/local/bashAst.mjs +0 -16
  495. package/dist/esm/tools/local/bashAst.mjs.map +1 -1
  496. package/dist/esm/tools/local/editStrategies.mjs +0 -14
  497. package/dist/esm/tools/local/editStrategies.mjs.map +1 -1
  498. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -1
  499. package/dist/esm/tools/local/syntaxCheck.mjs +0 -31
  500. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
  501. package/dist/esm/tools/local/textEncoding.mjs +0 -11
  502. package/dist/esm/tools/local/textEncoding.mjs.map +1 -1
  503. package/dist/esm/tools/local/workspaceFS.mjs +0 -33
  504. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
  505. package/dist/esm/tools/ptcTimeout.mjs.map +1 -1
  506. package/dist/esm/tools/schema.mjs +0 -8
  507. package/dist/esm/tools/schema.mjs.map +1 -1
  508. package/dist/esm/tools/search/anthropic.mjs +0 -9
  509. package/dist/esm/tools/search/anthropic.mjs.map +1 -1
  510. package/dist/esm/tools/search/content.mjs.map +1 -1
  511. package/dist/esm/tools/search/crw-scraper.mjs +0 -33
  512. package/dist/esm/tools/search/crw-scraper.mjs.map +1 -1
  513. package/dist/esm/tools/search/crw-search.mjs +0 -2
  514. package/dist/esm/tools/search/crw-search.mjs.map +1 -1
  515. package/dist/esm/tools/search/firecrawl.mjs +0 -25
  516. package/dist/esm/tools/search/firecrawl.mjs.map +1 -1
  517. package/dist/esm/tools/search/format.mjs +0 -30
  518. package/dist/esm/tools/search/format.mjs.map +1 -1
  519. package/dist/esm/tools/search/highlights.mjs +0 -16
  520. package/dist/esm/tools/search/highlights.mjs.map +1 -1
  521. package/dist/esm/tools/search/keenable-scraper.mjs +0 -8
  522. package/dist/esm/tools/search/keenable-scraper.mjs.map +1 -1
  523. package/dist/esm/tools/search/keenable-search.mjs +0 -7
  524. package/dist/esm/tools/search/keenable-search.mjs.map +1 -1
  525. package/dist/esm/tools/search/rerankers.mjs +0 -35
  526. package/dist/esm/tools/search/rerankers.mjs.map +1 -1
  527. package/dist/esm/tools/search/schema.mjs +0 -1
  528. package/dist/esm/tools/search/schema.mjs.map +1 -1
  529. package/dist/esm/tools/search/search.mjs +1 -14
  530. package/dist/esm/tools/search/search.mjs.map +1 -1
  531. package/dist/esm/tools/search/serper-scraper.mjs +0 -46
  532. package/dist/esm/tools/search/serper-scraper.mjs.map +1 -1
  533. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -1
  534. package/dist/esm/tools/search/tavily-search.mjs.map +1 -1
  535. package/dist/esm/tools/search/tool.mjs +0 -25
  536. package/dist/esm/tools/search/tool.mjs.map +1 -1
  537. package/dist/esm/tools/search/utils.mjs +0 -8
  538. package/dist/esm/tools/search/utils.mjs.map +1 -1
  539. package/dist/esm/tools/skillCatalog.mjs +0 -6
  540. package/dist/esm/tools/skillCatalog.mjs.map +1 -1
  541. package/dist/esm/tools/streamedToolCallSeals.mjs +0 -6
  542. package/dist/esm/tools/streamedToolCallSeals.mjs.map +1 -1
  543. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs +0 -2
  544. package/dist/esm/tools/subagent/SubagentExecutionRegistry.mjs.map +1 -1
  545. package/dist/esm/tools/subagent/SubagentExecutor.mjs +3 -213
  546. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  547. package/dist/esm/tools/subagent/SubagentReplay.mjs.map +1 -1
  548. package/dist/esm/tools/subagent/childGraphConfig.mjs +0 -4
  549. package/dist/esm/tools/subagent/childGraphConfig.mjs.map +1 -1
  550. package/dist/esm/tools/subagent/runtimeLimits.mjs.map +1 -1
  551. package/dist/esm/tools/toolOutputReferences.mjs +1 -322
  552. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
  553. package/dist/esm/types/hitl.mjs +0 -5
  554. package/dist/esm/types/hitl.mjs.map +1 -1
  555. package/dist/esm/utils/callbacks.mjs.map +1 -1
  556. package/dist/esm/utils/errors.mjs +14 -165
  557. package/dist/esm/utils/errors.mjs.map +1 -1
  558. package/dist/esm/utils/events.mjs +0 -10
  559. package/dist/esm/utils/events.mjs.map +1 -1
  560. package/dist/esm/utils/graph.mjs.map +1 -1
  561. package/dist/esm/utils/handlers.mjs +0 -3
  562. package/dist/esm/utils/handlers.mjs.map +1 -1
  563. package/dist/esm/utils/llm.mjs +0 -1
  564. package/dist/esm/utils/llm.mjs.map +1 -1
  565. package/dist/esm/utils/misc.mjs +0 -16
  566. package/dist/esm/utils/misc.mjs.map +1 -1
  567. package/dist/esm/utils/proxy.mjs +0 -26
  568. package/dist/esm/utils/proxy.mjs.map +1 -1
  569. package/dist/esm/utils/run.mjs +0 -6
  570. package/dist/esm/utils/run.mjs.map +1 -1
  571. package/dist/esm/utils/schema.mjs +0 -5
  572. package/dist/esm/utils/schema.mjs.map +1 -1
  573. package/dist/esm/utils/title.mjs +0 -6
  574. package/dist/esm/utils/title.mjs.map +1 -1
  575. package/dist/esm/utils/tokens.mjs +0 -113
  576. package/dist/esm/utils/tokens.mjs.map +1 -1
  577. package/dist/esm/utils/toolContent.mjs +0 -41
  578. package/dist/esm/utils/toolContent.mjs.map +1 -1
  579. package/dist/esm/utils/truncation.mjs +0 -61
  580. package/dist/esm/utils/truncation.mjs.map +1 -1
  581. package/package.json +35 -32
  582. package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +0 -52
  583. package/dist/types/test/mockTools.d.ts +0 -28
  584. package/dist/types/utils/__tests__/fixtures/contextOverflowSignatures.d.ts +0 -40
  585. package/src/__tests__/stream.eagerArgsDivergence.test.ts +0 -911
  586. package/src/__tests__/stream.eagerEventExecution.test.ts +0 -5046
  587. package/src/__tests__/stream.streamLimits.test.ts +0 -1982
  588. package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +0 -448
  589. package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +0 -655
  590. package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +0 -128
  591. package/src/agents/__tests__/AgentContext.overflow.test.ts +0 -205
  592. package/src/agents/__tests__/AgentContext.test.ts +0 -2425
  593. package/src/agents/__tests__/projection.test.ts +0 -73
  594. package/src/agents/__tests__/promptCacheLiveHelpers.ts +0 -175
  595. package/src/aggregator.test.ts +0 -1214
  596. package/src/graphs/__tests__/Graph.breakerLifecycle.test.ts +0 -273
  597. package/src/graphs/__tests__/Graph.closeRunStep.test.ts +0 -385
  598. package/src/graphs/__tests__/Graph.contextOverflow.test.ts +0 -1766
  599. package/src/graphs/__tests__/Graph.preemptSignal.test.ts +0 -126
  600. package/src/graphs/__tests__/Graph.reasoning.test.ts +0 -1717
  601. package/src/graphs/__tests__/Graph.subagentResumeState.test.ts +0 -80
  602. package/src/graphs/__tests__/MultiAgentGraph.test.ts +0 -177
  603. package/src/graphs/__tests__/composition.smoke.test.ts +0 -509
  604. package/src/graphs/__tests__/createGraph.test.ts +0 -206
  605. package/src/hooks/__tests__/HookRegistry.test.ts +0 -334
  606. package/src/hooks/__tests__/compactHooks.test.ts +0 -250
  607. package/src/hooks/__tests__/createToolPolicyHook.test.ts +0 -268
  608. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +0 -405
  609. package/src/hooks/__tests__/executeHooks.test.ts +0 -1294
  610. package/src/hooks/__tests__/integration.test.ts +0 -337
  611. package/src/hooks/__tests__/matchers.test.ts +0 -238
  612. package/src/hooks/__tests__/preemptBoundary.test.ts +0 -152
  613. package/src/hooks/__tests__/toolHooks.test.ts +0 -669
  614. package/src/llm/__tests__/contextOverflowRecovery.test.ts +0 -401
  615. package/src/llm/__tests__/fallbackOverflow.test.ts +0 -287
  616. package/src/llm/anthropic/Jacob_Lee_Resume_2023.pdf +0 -0
  617. package/src/llm/anthropic/inherited-content-utils.spec.ts +0 -249
  618. package/src/llm/anthropic/inherited-stream-events.spec.ts +0 -1259
  619. package/src/llm/anthropic/inherited-strict.spec.ts +0 -302
  620. package/src/llm/anthropic/llm.spec.ts +0 -3394
  621. package/src/llm/anthropic/utils/cross-provider-reasoning.test.ts +0 -383
  622. package/src/llm/anthropic/utils/cross-provider-server-tools.test.ts +0 -110
  623. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +0 -508
  624. package/src/llm/anthropic/utils/streaming-tool-input.test.ts +0 -361
  625. package/src/llm/anthropic/utils/stripPrefillCache.test.ts +0 -111
  626. package/src/llm/anthropic/utils/tool-id-normalization.test.ts +0 -287
  627. package/src/llm/bedrock/inherited-cache.spec.ts +0 -144
  628. package/src/llm/bedrock/inherited.spec.ts +0 -724
  629. package/src/llm/bedrock/llm.spec.ts +0 -1533
  630. package/src/llm/bedrock/streamSealDispatch.test.ts +0 -331
  631. package/src/llm/bedrock/toolCache.test.ts +0 -194
  632. package/src/llm/bedrock/utils/cross-provider-reasoning.test.ts +0 -131
  633. package/src/llm/bedrock/utils/cross-provider-server-tools.test.ts +0 -122
  634. package/src/llm/bedrock/utils/message_inputs.test.ts +0 -650
  635. package/src/llm/bedrock/utils/message_outputs.test.ts +0 -85
  636. package/src/llm/bedrock/utils/toolResultCachePoint.test.ts +0 -118
  637. package/src/llm/custom-chat-models.smoke.test.ts +0 -1966
  638. package/src/llm/google/data/gettysburg10.wav +0 -0
  639. package/src/llm/google/data/hotdog.jpg +0 -0
  640. package/src/llm/google/inherited-stream-events.spec.ts +0 -350
  641. package/src/llm/google/llm.spec.ts +0 -1388
  642. package/src/llm/google/streamSmoothing.test.ts +0 -121
  643. package/src/llm/google/utils/common.test.ts +0 -196
  644. package/src/llm/invoke.alternation.test.ts +0 -87
  645. package/src/llm/invoke.handoffCue.test.ts +0 -112
  646. package/src/llm/invoke.streamLimits.test.ts +0 -142
  647. package/src/llm/invoke.test.ts +0 -1198
  648. package/src/llm/mistral/streamSmoothing.test.ts +0 -97
  649. package/src/llm/openai/cacheWriteTokens.test.ts +0 -112
  650. package/src/llm/openai/contentBlocks.test.ts +0 -381
  651. package/src/llm/openai/deepseek.test.ts +0 -747
  652. package/src/llm/openai/inherited-deepseek.spec.ts +0 -347
  653. package/src/llm/openai/inherited-xai.spec.ts +0 -416
  654. package/src/llm/openai/llm.spec.ts +0 -1774
  655. package/src/llm/openai/managedRequests.test.ts +0 -182
  656. package/src/llm/openai/sequentialToolCallSeals.test.ts +0 -199
  657. package/src/llm/openai/streamMetadata.spec.ts +0 -86
  658. package/src/llm/openai/streamMetadataDedup.spec.ts +0 -166
  659. package/src/llm/openai/utils/isReasoningModel.test.ts +0 -90
  660. package/src/llm/openai/utils/messages.test.ts +0 -488
  661. package/src/llm/openrouter/reasoning.test.ts +0 -561
  662. package/src/llm/openrouter/toolCache.test.ts +0 -134
  663. package/src/llm/preempt.test.ts +0 -323
  664. package/src/llm/stream/chunkAdapters.test.ts +0 -202
  665. package/src/llm/stream/reassembly.test.ts +0 -241
  666. package/src/llm/stream/smoother.bench.test.ts +0 -155
  667. package/src/llm/stream/smoother.test.ts +0 -519
  668. package/src/llm/streamLimits.test.ts +0 -450
  669. package/src/llm/truncation.test.ts +0 -242
  670. package/src/llm/vertexai/fixThoughtSignatures.test.ts +0 -154
  671. package/src/llm/vertexai/inherited-stream-events.spec.ts +0 -271
  672. package/src/llm/vertexai/llm.spec.ts +0 -159
  673. package/src/llm/vertexai/repairUsageMetadata.test.ts +0 -54
  674. package/src/llm/vertexai/sealStreamedToolCalls.test.ts +0 -88
  675. package/src/llm/vertexai/streamSealDispatch.test.ts +0 -148
  676. package/src/llm/vertexai/streamSmoothing.test.ts +0 -109
  677. package/src/messages/__tests__/anthropicToolCache.test.ts +0 -339
  678. package/src/messages/__tests__/observationMasking.test.ts +0 -312
  679. package/src/messages/__tests__/recency.test.ts +0 -267
  680. package/src/messages/__tests__/tools.test.ts +0 -473
  681. package/src/messages/alternation.test.ts +0 -212
  682. package/src/messages/assistantPhase.test.ts +0 -75
  683. package/src/messages/cache.tail.test.ts +0 -533
  684. package/src/messages/cache.test.ts +0 -2166
  685. package/src/messages/content.test.ts +0 -362
  686. package/src/messages/contextPruning.test.ts +0 -184
  687. package/src/messages/ensureThinkingBlock.test.ts +0 -1427
  688. package/src/messages/foldToollessToolBlocks.test.ts +0 -596
  689. package/src/messages/formatAgentMessages.reducer.test.ts +0 -162
  690. package/src/messages/formatAgentMessages.skills.test.ts +0 -513
  691. package/src/messages/formatAgentMessages.steer.test.ts +0 -598
  692. package/src/messages/formatAgentMessages.test.ts +0 -8111
  693. package/src/messages/formatAgentMessages.tools.test.ts +0 -522
  694. package/src/messages/formatMessage.test.ts +0 -693
  695. package/src/messages/handoffCue.test.ts +0 -96
  696. package/src/messages/injected.test.ts +0 -107
  697. package/src/messages/labelContentByAgent.test.ts +0 -962
  698. package/src/messages/reducer.spec.ts +0 -32
  699. package/src/messages/shiftIndexTokenCountMap.test.ts +0 -81
  700. package/src/messages/tailCacheConversion.test.ts +0 -161
  701. package/src/openai/__tests__/openai.test.ts +0 -337
  702. package/src/responses/__tests__/responses.test.ts +0 -652
  703. package/src/scripts/abort.ts +0 -157
  704. package/src/scripts/activity-labels/captured.json +0 -56
  705. package/src/scripts/activity-labels/checks.cjs +0 -205
  706. package/src/scripts/activity-labels/corpus.cjs +0 -473
  707. package/src/scripts/activity-labels/report.cjs +0 -203
  708. package/src/scripts/activity-labels/rescore.cjs +0 -102
  709. package/src/scripts/activity-labels/run.ts +0 -705
  710. package/src/scripts/activity-labels/variants.ts +0 -71
  711. package/src/scripts/ant_web_search.ts +0 -159
  712. package/src/scripts/ant_web_search_edge_case.ts +0 -163
  713. package/src/scripts/ant_web_search_error_edge_case.ts +0 -149
  714. package/src/scripts/args.ts +0 -48
  715. package/src/scripts/bedrock-cache-debug.ts +0 -250
  716. package/src/scripts/bedrock-content-aggregation-test.ts +0 -266
  717. package/src/scripts/bedrock-merge-test.ts +0 -107
  718. package/src/scripts/bedrock-parallel-tools-test.ts +0 -204
  719. package/src/scripts/bench-prompt-cache.ts +0 -479
  720. package/src/scripts/caching.ts +0 -132
  721. package/src/scripts/cli.ts +0 -172
  722. package/src/scripts/cli2.ts +0 -133
  723. package/src/scripts/cli3.ts +0 -184
  724. package/src/scripts/cli4.ts +0 -191
  725. package/src/scripts/cli5.ts +0 -191
  726. package/src/scripts/code_exec.ts +0 -214
  727. package/src/scripts/code_exec_files.ts +0 -237
  728. package/src/scripts/code_exec_multi_session.ts +0 -237
  729. package/src/scripts/code_exec_ptc.ts +0 -335
  730. package/src/scripts/code_exec_session.ts +0 -283
  731. package/src/scripts/code_exec_simple.ts +0 -148
  732. package/src/scripts/compare_pi_vs_ours.ts +0 -1259
  733. package/src/scripts/content.ts +0 -139
  734. package/src/scripts/context-overflow-probe.ts +0 -997
  735. package/src/scripts/empty_input.ts +0 -137
  736. package/src/scripts/handoff-test.ts +0 -134
  737. package/src/scripts/local_engine.ts +0 -166
  738. package/src/scripts/local_engine_checkpointer.ts +0 -205
  739. package/src/scripts/local_engine_compile.ts +0 -263
  740. package/src/scripts/local_engine_hooks.ts +0 -226
  741. package/src/scripts/local_engine_image.ts +0 -201
  742. package/src/scripts/local_engine_ptc.ts +0 -151
  743. package/src/scripts/local_engine_workspace.ts +0 -258
  744. package/src/scripts/memory.ts +0 -107
  745. package/src/scripts/multi-agent-chain.ts +0 -332
  746. package/src/scripts/multi-agent-conditional.ts +0 -222
  747. package/src/scripts/multi-agent-document-review-chain.ts +0 -198
  748. package/src/scripts/multi-agent-hybrid-flow.ts +0 -311
  749. package/src/scripts/multi-agent-parallel-start.ts +0 -265
  750. package/src/scripts/multi-agent-parallel.ts +0 -395
  751. package/src/scripts/multi-agent-sequence.ts +0 -217
  752. package/src/scripts/multi-agent-subagent.ts +0 -246
  753. package/src/scripts/multi-agent-supervisor.ts +0 -366
  754. package/src/scripts/multi-agent-test.ts +0 -187
  755. package/src/scripts/openrouter_prompt_cache_live.ts +0 -310
  756. package/src/scripts/parallel-asymmetric-tools-test.ts +0 -275
  757. package/src/scripts/parallel-full-metadata-test.ts +0 -241
  758. package/src/scripts/parallel-tools-test.ts +0 -341
  759. package/src/scripts/preempt-probe.ts +0 -330
  760. package/src/scripts/preempt-scenarios.ts +0 -388
  761. package/src/scripts/programmatic_exec.ts +0 -387
  762. package/src/scripts/programmatic_exec_agent.ts +0 -232
  763. package/src/scripts/search.ts +0 -147
  764. package/src/scripts/sequential-full-metadata-test.ts +0 -198
  765. package/src/scripts/session_live.ts +0 -548
  766. package/src/scripts/simple.ts +0 -226
  767. package/src/scripts/single-agent-metadata-test.ts +0 -196
  768. package/src/scripts/stream.ts +0 -141
  769. package/src/scripts/subagent-configurable-inheritance.ts +0 -252
  770. package/src/scripts/subagent-event-driven-debug.ts +0 -190
  771. package/src/scripts/subagent-tools-debug.ts +0 -160
  772. package/src/scripts/subagent-usage-sink.ts +0 -176
  773. package/src/scripts/summarization-recency.ts +0 -462
  774. package/src/scripts/test-custom-prompt-key.ts +0 -145
  775. package/src/scripts/test-handoff-input.ts +0 -170
  776. package/src/scripts/test-handoff-preamble.ts +0 -278
  777. package/src/scripts/test-handoff-steering.ts +0 -430
  778. package/src/scripts/test-multi-agent-list-handoff.ts +0 -418
  779. package/src/scripts/test-parallel-agent-labeling.ts +0 -327
  780. package/src/scripts/test-parallel-handoffs.ts +0 -292
  781. package/src/scripts/test-thinking-handoff-bedrock.ts +0 -154
  782. package/src/scripts/test-thinking-handoff.ts +0 -156
  783. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +0 -166
  784. package/src/scripts/test-tool-before-handoff-role-order.ts +0 -276
  785. package/src/scripts/test-tools-before-handoff.ts +0 -222
  786. package/src/scripts/test_code_api.ts +0 -354
  787. package/src/scripts/thinking-bedrock.ts +0 -160
  788. package/src/scripts/thinking-vertexai.ts +0 -168
  789. package/src/scripts/thinking.ts +0 -172
  790. package/src/scripts/tool_search.ts +0 -153
  791. package/src/scripts/tools.ts +0 -163
  792. package/src/session/__tests__/JsonlSessionStore.test.ts +0 -1463
  793. package/src/session/__tests__/handlers.test.ts +0 -161
  794. package/src/specs/activity-label-observability.live.test.ts +0 -262
  795. package/src/specs/activity-label-observability.test.ts +0 -167
  796. package/src/specs/activity-label-prompt.test.ts +0 -359
  797. package/src/specs/activity-label-trace-seed.test.ts +0 -47
  798. package/src/specs/activity-phase-label.test.ts +0 -306
  799. package/src/specs/agent-handoffs.live.test.ts +0 -140
  800. package/src/specs/agent-handoffs.test.ts +0 -2465
  801. package/src/specs/anthropic.simple.test.ts +0 -453
  802. package/src/specs/ask-user-question-batch.test.ts +0 -289
  803. package/src/specs/ask-user-questions.live.test.ts +0 -185
  804. package/src/specs/ask-user-questions.test.ts +0 -293
  805. package/src/specs/azure.simple.test.ts +0 -502
  806. package/src/specs/bedrock-toolless.live.test.ts +0 -123
  807. package/src/specs/bedrock-truncation.live.test.ts +0 -191
  808. package/src/specs/cache.simple.test.ts +0 -416
  809. package/src/specs/context-accuracy.live.test.ts +0 -409
  810. package/src/specs/context-overflow-recovery.live.test.ts +0 -213
  811. package/src/specs/context-usage-event.test.ts +0 -117
  812. package/src/specs/context-usage.live.test.ts +0 -297
  813. package/src/specs/custom-event-await.test.ts +0 -304
  814. package/src/specs/deepseek.simple.test.ts +0 -290
  815. package/src/specs/deterministic-trace-id.test.ts +0 -43
  816. package/src/specs/discovered-tools.test.ts +0 -217
  817. package/src/specs/durability-checkpoint.integration.test.ts +0 -243
  818. package/src/specs/graph-subagent.live.test.ts +0 -242
  819. package/src/specs/graph-subagent.test.ts +0 -322
  820. package/src/specs/handoffCue.test.ts +0 -165
  821. package/src/specs/langfuse-callbacks.test.ts +0 -1578
  822. package/src/specs/langfuse-config.test.ts +0 -247
  823. package/src/specs/langfuse-instrumentation.test.ts +0 -393
  824. package/src/specs/langfuse-metadata.test.ts +0 -245
  825. package/src/specs/langfuse-routing.integration.test.ts +0 -844
  826. package/src/specs/langfuse-runtime-context.test.ts +0 -92
  827. package/src/specs/langfuse-span-registry.test.ts +0 -87
  828. package/src/specs/langfuse-tool-output-tracing.test.ts +0 -1858
  829. package/src/specs/langfuse-trace-shaping.test.ts +0 -591
  830. package/src/specs/moonshot.simple.test.ts +0 -367
  831. package/src/specs/multi-agent-summarization.test.ts +0 -396
  832. package/src/specs/openai.simple.test.ts +0 -315
  833. package/src/specs/openrouter.simple.test.ts +0 -275
  834. package/src/specs/preemptSeal.test.ts +0 -678
  835. package/src/specs/prune.test.ts +0 -3741
  836. package/src/specs/reasoning.test.ts +0 -201
  837. package/src/specs/run-step-timestamps.test.ts +0 -414
  838. package/src/specs/spec.utils.ts +0 -15
  839. package/src/specs/subagent.test.ts +0 -864
  840. package/src/specs/summarization-unit.test.ts +0 -973
  841. package/src/specs/summarization.test.ts +0 -3823
  842. package/src/specs/summarize-prune.test.ts +0 -376
  843. package/src/specs/thinking-handoff.test.ts +0 -621
  844. package/src/specs/thinking-prune.test.ts +0 -827
  845. package/src/specs/token-accounting-e2e.test.ts +0 -1034
  846. package/src/specs/token-accounting-pipeline.test.ts +0 -1008
  847. package/src/specs/token-distribution-edge-case.test.ts +0 -315
  848. package/src/specs/token-memoization.test.ts +0 -40
  849. package/src/specs/tokens.test.ts +0 -1000
  850. package/src/specs/tool-error-resume.test.ts +0 -194
  851. package/src/specs/tool-error.test.ts +0 -198
  852. package/src/specs/vllm-reasoning-toolcalls.test.ts +0 -340
  853. package/src/stream.dispatch.test.ts +0 -63
  854. package/src/stream.test.ts +0 -144
  855. package/src/summarization/__tests__/aggregator.test.ts +0 -236
  856. package/src/summarization/__tests__/node.test.ts +0 -1996
  857. package/src/summarization/__tests__/trigger.test.ts +0 -149
  858. package/src/summarization/chunkHandler.test.ts +0 -196
  859. package/src/test/mockTools.ts +0 -386
  860. package/src/tools/Calculator.test.ts +0 -278
  861. package/src/tools/__tests__/BashExecutor.test.ts +0 -108
  862. package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +0 -939
  863. package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +0 -858
  864. package/src/tools/__tests__/CodeExecutor.stateful.test.ts +0 -124
  865. package/src/tools/__tests__/LocalExecutionRoots.test.ts +0 -8
  866. package/src/tools/__tests__/LocalExecutionTools.test.ts +0 -2733
  867. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +0 -318
  868. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -1557
  869. package/src/tools/__tests__/ReadFile.test.ts +0 -44
  870. package/src/tools/__tests__/SkillTool.test.ts +0 -442
  871. package/src/tools/__tests__/SubagentExecutor.test.ts +0 -3728
  872. package/src/tools/__tests__/SubagentReplay.test.ts +0 -329
  873. package/src/tools/__tests__/SubagentTool.test.ts +0 -149
  874. package/src/tools/__tests__/ToolNode.breakerSignal.test.ts +0 -595
  875. package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +0 -1191
  876. package/src/tools/__tests__/ToolNode.invalidToolCalls.test.ts +0 -757
  877. package/src/tools/__tests__/ToolNode.langfuse.test.ts +0 -57
  878. package/src/tools/__tests__/ToolNode.onResultCompletion.test.ts +0 -736
  879. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +0 -1710
  880. package/src/tools/__tests__/ToolNode.runtimeState.test.ts +0 -120
  881. package/src/tools/__tests__/ToolNode.session.test.ts +0 -1352
  882. package/src/tools/__tests__/ToolNode.streamLimits.test.ts +0 -69
  883. package/src/tools/__tests__/ToolSearch.integration.test.ts +0 -161
  884. package/src/tools/__tests__/ToolSearch.test.ts +0 -1227
  885. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +0 -561
  886. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +0 -980
  887. package/src/tools/__tests__/directToolHooks.test.ts +0 -700
  888. package/src/tools/__tests__/eagerEventExecution.session.test.ts +0 -92
  889. package/src/tools/__tests__/handlers.test.ts +0 -997
  890. package/src/tools/__tests__/hitl.test.ts +0 -4663
  891. package/src/tools/__tests__/intentArg.test.ts +0 -357
  892. package/src/tools/__tests__/intentCoverage.test.ts +0 -139
  893. package/src/tools/__tests__/localToolNames.test.ts +0 -75
  894. package/src/tools/__tests__/skillCatalog.test.ts +0 -161
  895. package/src/tools/__tests__/subagentHooks.test.ts +0 -1651
  896. package/src/tools/__tests__/toolOutputReferences.test.ts +0 -515
  897. package/src/tools/__tests__/workspaceSeam.test.ts +0 -147
  898. package/src/tools/local/__tests__/FileCheckpointer.test.ts +0 -162
  899. package/src/tools/local/__tests__/editStrategies.test.ts +0 -134
  900. package/src/tools/search/content.test.ts +0 -173
  901. package/src/tools/search/crw.test.ts +0 -836
  902. package/src/tools/search/format.test.ts +0 -242
  903. package/src/tools/search/http-agent.test.ts +0 -133
  904. package/src/tools/search/jina-reranker.test.ts +0 -306
  905. package/src/tools/search/keenable-scraper.test.ts +0 -153
  906. package/src/tools/search/keenable.test.ts +0 -183
  907. package/src/tools/search/outcome.test.ts +0 -90
  908. package/src/tools/search/output.md +0 -2775
  909. package/src/tools/search/rag-api-reranker.test.ts +0 -891
  910. package/src/tools/search/source-processing.test.ts +0 -516
  911. package/src/tools/search/tavily.test.ts +0 -965
  912. package/src/tools/search/test.html +0 -884
  913. package/src/tools/search/test.md +0 -643
  914. package/src/tools/subagent/__tests__/SubagentExecutionRegistry.test.ts +0 -637
  915. package/src/tools/subagent/__tests__/SubagentExecutor.lazy.test.ts +0 -3054
  916. package/src/tools/subagent/__tests__/childGraphConfig.test.ts +0 -521
  917. package/src/utils/__tests__/apportion.test.ts +0 -32
  918. package/src/utils/__tests__/errors.test.ts +0 -270
  919. package/src/utils/__tests__/fixtures/contextOverflowSignatures.ts +0 -336
  920. package/src/utils/__tests__/redactSecrets.test.ts +0 -56
  921. package/src/utils/__tests__/truncation.test.ts +0 -66
  922. package/src/utils/llm.test.ts +0 -18
  923. package/src/utils/proxy.test.ts +0 -176
  924. package/src/utils/toolContent.test.ts +0 -1432
@@ -1,3054 +0,0 @@
1
- import { describe, expect, it, jest } from '@jest/globals';
2
- import { AIMessage, ToolMessage } from '@langchain/core/messages';
3
- import { GraphInterrupt, MemorySaver } from '@langchain/langgraph';
4
- import type { Checkpoint, CheckpointMetadata } from '@langchain/langgraph';
5
- import type {
6
- AgentInputs,
7
- ExecutableSubagentConfigEntry,
8
- GraphSubagentConfig,
9
- LazySingleAgentSubagentConfig,
10
- MultiAgentGraphState,
11
- ResolvedSubagentConfig,
12
- SubagentResolveConfigurable,
13
- StandardGraphInput,
14
- SubagentResolveContext,
15
- } from '@/types';
16
- import type {
17
- SubagentResumeExecution,
18
- SubagentResumeManifest,
19
- SettledSubagentToolOutput,
20
- } from '@/tools/subagent/SubagentReplay';
21
- import type {
22
- PreparedSubagentExecutionIdentity,
23
- SubagentExecutionIdentity,
24
- } from '@/tools/subagent/SubagentExecutionRegistry';
25
- import type { GraphFactoryRequest } from '@/graphs/graphFactory';
26
- import type { StandardGraph } from '@/graphs/Graph';
27
- import {
28
- SUBAGENT_PARENT_BATCH_CONFIG_KEY,
29
- SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY,
30
- SUBAGENT_RESUME_MANIFEST_CONFIG_KEY,
31
- } from '@/tools/subagent/SubagentReplay';
32
- import {
33
- getSubagentApprovalExecutionScope,
34
- SubagentExecutionRegistry,
35
- } from '@/tools/subagent/SubagentExecutionRegistry';
36
- import {
37
- RUN_BREAKER_SCOPE_CONFIG_KEY,
38
- StreamLimitExceededError,
39
- } from '@/llm/streamLimits';
40
- import { normalizeSubagentConfigEntries } from '@/tools/subagent/childGraphConfig';
41
- import {
42
- HookRegistry,
43
- TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY,
44
- } from '@/hooks';
45
- import { SubagentExecutor } from '@/tools/subagent/SubagentExecutor';
46
- import { AgentContext } from '@/agents/AgentContext';
47
- import { Providers } from '@/common';
48
-
49
- const makeAgent = (agentId = 'child-agent'): AgentInputs => ({
50
- agentId,
51
- provider: Providers.OPENAI,
52
- clientOptions: { modelName: 'gpt-4o-mini', apiKey: 'test-key' },
53
- instructions: `You are ${agentId}.`,
54
- maxContextTokens: 8000,
55
- });
56
-
57
- const makeLazyConfig = (
58
- type: string,
59
- resolveAgentInputs: LazySingleAgentSubagentConfig['resolveAgentInputs'],
60
- overrides: Partial<LazySingleAgentSubagentConfig> = {}
61
- ): LazySingleAgentSubagentConfig => ({
62
- type,
63
- name: `${type} worker`,
64
- description: `Handles ${type} tasks.`,
65
- configId: `${type}@v1`,
66
- resolveAgentInputs,
67
- ...overrides,
68
- });
69
-
70
- const makeConfigMap = (
71
- ...configs: ExecutableSubagentConfigEntry[]
72
- ): Map<string, ExecutableSubagentConfigEntry> =>
73
- new Map(configs.map((config) => [config.type, config]));
74
-
75
- const makeGraph = (
76
- result: MultiAgentGraphState = {
77
- messages: [new AIMessage('Task completed')],
78
- }
79
- ): StandardGraph =>
80
- ({
81
- createWorkflow: () => ({
82
- invoke: jest.fn(async (): Promise<MultiAgentGraphState> => result),
83
- }),
84
- clearHeavyState: jest.fn(),
85
- }) as unknown as StandardGraph;
86
-
87
- const makeRecoveredGraph = (): StandardGraph =>
88
- ({
89
- createWorkflow: () => ({
90
- getState: jest.fn(async () => ({
91
- values: { messages: [new AIMessage('persisted result')] },
92
- next: [],
93
- tasks: [],
94
- })),
95
- invoke: jest.fn(),
96
- updateState: jest.fn(async () => ({})),
97
- }),
98
- restoreSubagentResumeState: jest.fn(),
99
- createSubagentResumeState: jest.fn(() => ({
100
- toolCallSteps: [],
101
- toolSessions: [],
102
- toolNodes: [],
103
- eagerToolUsage: [],
104
- eagerToolSuppressions: [],
105
- })),
106
- clearHeavyState: jest.fn(),
107
- }) as unknown as StandardGraph;
108
-
109
- const makeResumeExecution = (
110
- parentToolCallId: string,
111
- configId: string,
112
- checkpointThreadId = 'resume-source-thread',
113
- subagentType = 'researcher'
114
- ): SubagentResumeExecution => ({
115
- parentToolCallId,
116
- childRunId: 'original-child-run',
117
- subagentType,
118
- configId,
119
- approvalExecutionScope: 'original-approval-scope',
120
- checkpoints: [
121
- {
122
- threadId: checkpointThreadId,
123
- checkpointId: 'resume-checkpoint',
124
- checkpointNs: '',
125
- },
126
- ],
127
- graphState: {
128
- toolCallSteps: [],
129
- toolSessions: [],
130
- toolNodes: [],
131
- eagerToolUsage: [],
132
- eagerToolSuppressions: [],
133
- },
134
- approvalReplays: [],
135
- });
136
-
137
- const putCheckpoint = async (
138
- checkpointer: MemorySaver,
139
- threadId: string,
140
- checkpointId: string
141
- ): Promise<void> => {
142
- const checkpoint: Checkpoint = {
143
- v: 4,
144
- id: checkpointId,
145
- ts: new Date().toISOString(),
146
- channel_values: {},
147
- channel_versions: {},
148
- versions_seen: {},
149
- };
150
- const metadata: CheckpointMetadata = {
151
- source: 'loop',
152
- step: 0,
153
- parents: {},
154
- };
155
- await checkpointer.put(
156
- { configurable: { thread_id: threadId, checkpoint_ns: '' } },
157
- checkpoint,
158
- metadata
159
- );
160
- };
161
-
162
- const createExecutor = (
163
- configs: ExecutableSubagentConfigEntry[],
164
- overrides: Partial<ConstructorParameters<typeof SubagentExecutor>[0]> = {}
165
- ): SubagentExecutor =>
166
- new SubagentExecutor({
167
- configs: makeConfigMap(...configs),
168
- parentRunId: 'parent-run',
169
- parentAgentId: 'parent-agent',
170
- createChildGraph: () => makeGraph(),
171
- ...overrides,
172
- });
173
-
174
- describe('SubagentExecutor lazy selected-subagent resolution', () => {
175
- it('resolves only the selected descriptor', async () => {
176
- const researcherResolver = jest.fn(async () =>
177
- makeAgent('lazy-researcher')
178
- );
179
- const coderResolver = jest.fn(async () => makeAgent('lazy-coder'));
180
- const researcher = makeLazyConfig('researcher', researcherResolver);
181
- const coder = makeLazyConfig('coder', coderResolver);
182
- let observedInput: StandardGraphInput | undefined;
183
- const executor = createExecutor([researcher, coder], {
184
- createChildGraph: (input): StandardGraph => {
185
- observedInput = input;
186
- return makeGraph();
187
- },
188
- });
189
-
190
- await executor.execute({
191
- description: 'Research this.',
192
- subagentType: researcher.type,
193
- parentToolCallId: 'call_research',
194
- });
195
-
196
- expect(researcherResolver).toHaveBeenCalledTimes(1);
197
- expect(coderResolver).not.toHaveBeenCalled();
198
- expect(observedInput?.agents[0].agentId).toBe('lazy-researcher');
199
- });
200
-
201
- it('redacts a selected resolver failure without affecting a healthy sibling', async () => {
202
- const failingConfig = makeLazyConfig('failing', async () => {
203
- throw new Error('oauth-token=private-selected-resolver-secret');
204
- });
205
- const healthyResolver = jest.fn(async () => makeAgent('healthy-child'));
206
- const healthyConfig = makeLazyConfig('healthy', healthyResolver);
207
- const executor = createExecutor([failingConfig, healthyConfig]);
208
-
209
- const failed = await executor.execute({
210
- description: 'Fail this selected child.',
211
- subagentType: failingConfig.type,
212
- parentToolCallId: 'call_failing',
213
- });
214
- const succeeded = await executor.execute({
215
- description: 'Run the healthy child.',
216
- subagentType: healthyConfig.type,
217
- parentToolCallId: 'call_healthy',
218
- });
219
-
220
- expect(failed.content).toBe(
221
- 'Subagent error: Unable to initialize the selected subagent.'
222
- );
223
- expect(failed.content).not.toContain('oauth-token');
224
- expect(failed.content).not.toContain('private-selected-resolver-secret');
225
- expect(succeeded.content).toBe('Task completed');
226
- expect(healthyResolver).toHaveBeenCalledTimes(1);
227
- });
228
-
229
- it('releases lazy resolution when SubagentStart denies', async () => {
230
- const resolver = jest.fn(async () => makeAgent());
231
- const config = makeLazyConfig('researcher', resolver);
232
- const hookRegistry = new HookRegistry();
233
- hookRegistry.register('SubagentStart', {
234
- hooks: [
235
- async (): Promise<{ decision: 'deny'; reason: string }> => ({
236
- decision: 'deny',
237
- reason: 'blocked by policy',
238
- }),
239
- ],
240
- });
241
- const executor = createExecutor([config], { hookRegistry });
242
- const params = {
243
- description: 'Block this selected child.',
244
- subagentType: config.type,
245
- parentToolCallId: 'call_blocked',
246
- };
247
-
248
- const first = await executor.execute(params);
249
- expect(first.content).toBe('Blocked: blocked by policy');
250
-
251
- const retry = await executor.execute(params);
252
-
253
- expect(retry.content).toBe('Blocked: blocked by policy');
254
- expect(resolver).toHaveBeenCalledTimes(2);
255
- });
256
-
257
- it('coalesces concurrent execution and releases resolved inputs after completion', async () => {
258
- let finishResolution = (_inputs: AgentInputs): void => undefined;
259
- const resolverResult = new Promise<AgentInputs>((resolve) => {
260
- finishResolution = resolve;
261
- });
262
- let markResolutionStarted = (): void => undefined;
263
- const resolutionStarted = new Promise<void>((resolve) => {
264
- markResolutionStarted = resolve;
265
- });
266
- const resolver = jest.fn(async () => {
267
- markResolutionStarted();
268
- return resolverResult;
269
- });
270
- const config = makeLazyConfig('researcher', resolver);
271
- const invoke = jest.fn(
272
- async (): Promise<MultiAgentGraphState> => ({
273
- messages: [new AIMessage('Task completed')],
274
- })
275
- );
276
- const executor = createExecutor([config], {
277
- parentRunId: 'coalesced-run',
278
- createChildGraph: () =>
279
- ({
280
- createWorkflow: () => ({ invoke }),
281
- clearHeavyState: jest.fn(),
282
- }) as unknown as StandardGraph,
283
- });
284
- const params = {
285
- description: 'Run the same selected execution.',
286
- subagentType: config.type,
287
- parentToolCallId: 'call_same',
288
- };
289
-
290
- const first = executor.execute(params);
291
- const duplicate = executor.execute(params);
292
- await resolutionStarted;
293
- expect(resolver).toHaveBeenCalledTimes(1);
294
-
295
- finishResolution(makeAgent('coalesced-child'));
296
- await Promise.all([first, duplicate]);
297
- expect(invoke).toHaveBeenCalledTimes(1);
298
- await executor.execute({
299
- ...params,
300
- parentToolCallId: 'call_fresh',
301
- });
302
-
303
- expect(resolver).toHaveBeenCalledTimes(2);
304
- expect(invoke).toHaveBeenCalledTimes(2);
305
- });
306
-
307
- it('rejects a different invocation before sharing an in-flight result', async () => {
308
- let finish = (_result: MultiAgentGraphState): void => undefined;
309
- const invocation = new Promise<MultiAgentGraphState>((resolve) => {
310
- finish = resolve;
311
- });
312
- const researcherResolver = jest.fn(async () => makeAgent('researcher'));
313
- const coderResolver = jest.fn(async () => makeAgent('coder'));
314
- const researcher = makeLazyConfig('researcher', researcherResolver);
315
- const coder = makeLazyConfig('coder', coderResolver);
316
- const invoke = jest.fn(() => invocation);
317
- const executor = createExecutor([researcher, coder], {
318
- createChildGraph: () =>
319
- ({
320
- createWorkflow: () => ({ invoke }),
321
- clearHeavyState: jest.fn(),
322
- }) as unknown as StandardGraph,
323
- });
324
- const common = { parentToolCallId: 'call_immutable_invocation' };
325
-
326
- const first = executor.execute({
327
- ...common,
328
- description: 'Run the original request.',
329
- subagentType: researcher.type,
330
- });
331
- const changedType = executor.execute({
332
- ...common,
333
- description: 'Run the original request.',
334
- subagentType: coder.type,
335
- });
336
- const changedDescription = executor.execute({
337
- ...common,
338
- description: 'Run a changed request.',
339
- subagentType: researcher.type,
340
- });
341
-
342
- await expect(changedType).resolves.toMatchObject({
343
- content:
344
- 'Subagent error: Subagent configuration changed since this execution was paused.',
345
- });
346
- await expect(changedDescription).resolves.toMatchObject({
347
- content:
348
- 'Subagent error: Subagent invocation changed for this execution.',
349
- });
350
- expect(coderResolver).not.toHaveBeenCalled();
351
- finish({ messages: [new AIMessage('original result')] });
352
- await expect(first).resolves.toMatchObject({ content: 'original result' });
353
- expect(researcherResolver).toHaveBeenCalledTimes(1);
354
- expect(invoke).toHaveBeenCalledTimes(1);
355
- });
356
-
357
- it('does not execute a child after its lazy resolution was invalidated', async () => {
358
- let finishFirst = (_inputs: AgentInputs): void => undefined;
359
- let finishSecond = (_inputs: AgentInputs): void => undefined;
360
- const firstResult = new Promise<AgentInputs>((resolve) => {
361
- finishFirst = resolve;
362
- });
363
- const secondResult = new Promise<AgentInputs>((resolve) => {
364
- finishSecond = resolve;
365
- });
366
- let markFirstStarted = (): void => undefined;
367
- let markSecondStarted = (): void => undefined;
368
- const firstStarted = new Promise<void>((resolve) => {
369
- markFirstStarted = resolve;
370
- });
371
- const secondStarted = new Promise<void>((resolve) => {
372
- markSecondStarted = resolve;
373
- });
374
- let resolverInvocation = 0;
375
- const config = makeLazyConfig('researcher', () => {
376
- resolverInvocation += 1;
377
- if (resolverInvocation === 1) {
378
- markFirstStarted();
379
- return firstResult;
380
- }
381
- markSecondStarted();
382
- return secondResult;
383
- });
384
- const observedAgentIds: string[] = [];
385
- const createChildGraph = jest.fn((input: StandardGraphInput) => {
386
- observedAgentIds.push(input.agents[0].agentId);
387
- return makeGraph();
388
- });
389
- const executor = createExecutor([config], { createChildGraph });
390
- const params = {
391
- description: 'Run the selected child.',
392
- subagentType: config.type,
393
- parentToolCallId: 'call_cleanup_retry',
394
- };
395
-
396
- const staleExecution = executor.execute(params);
397
- await firstStarted;
398
- executor.clearHeavyState();
399
- const currentExecution = executor.execute(params);
400
- await secondStarted;
401
-
402
- finishFirst(makeAgent('stale-child'));
403
- await expect(staleExecution).resolves.toMatchObject({
404
- content: 'Subagent error: Unable to initialize the selected subagent.',
405
- });
406
- expect(createChildGraph).not.toHaveBeenCalled();
407
-
408
- finishSecond(makeAgent('current-child'));
409
- await expect(currentExecution).resolves.toMatchObject({
410
- content: 'Task completed',
411
- });
412
- expect(createChildGraph).toHaveBeenCalledTimes(1);
413
- expect(observedAgentIds).toEqual(['current-child']);
414
- });
415
-
416
- it('rolls back a stale checkpoint fork before committing its identity', async () => {
417
- const resolver = jest.fn(async () => makeAgent());
418
- const config = makeLazyConfig('researcher', resolver);
419
- const checkpointer = new MemorySaver();
420
- const hookRegistry = new HookRegistry();
421
- const parentToolCallId = 'call_stale_fork';
422
- const resumeAttemptId = 'attempt-stale';
423
- const approvalExecutionScope = getSubagentApprovalExecutionScope(
424
- 'original-child-run',
425
- resumeAttemptId
426
- );
427
- const approvalKey = {
428
- executionScope: approvalExecutionScope,
429
- agentId: 'child-agent',
430
- toolUseId: 'call_child_tool',
431
- };
432
- const resumeExecution = {
433
- ...makeResumeExecution(parentToolCallId, config.configId),
434
- approvalReplays: [
435
- {
436
- key: {
437
- ...approvalKey,
438
- executionScope: 'original-approval-scope',
439
- },
440
- result: {
441
- decision: 'ask' as const,
442
- reason: 'review child tool',
443
- additionalContexts: [],
444
- injectedMessages: [],
445
- errors: [],
446
- },
447
- },
448
- ],
449
- };
450
- const parentConfigurable = {
451
- thread_id: 'durable-thread',
452
- checkpoint_id: 'parent-checkpoint',
453
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
454
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId,
455
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
456
- version: 1 as const,
457
- executions: [resumeExecution],
458
- },
459
- };
460
- const address = new SubagentExecutionRegistry({
461
- parentRunId: 'parent-run',
462
- parentAgentId: 'parent-agent',
463
- durable: true,
464
- }).open({
465
- threadId: parentConfigurable.thread_id,
466
- parentToolCallId,
467
- parentConfigurable,
468
- }).address;
469
- let releaseFork = (): void => undefined;
470
- let markForkStarted = (): void => undefined;
471
- const forkGate = new Promise<void>((resolve) => {
472
- releaseFork = resolve;
473
- });
474
- const forkStarted = new Promise<void>((resolve) => {
475
- markForkStarted = resolve;
476
- });
477
- const executor = createExecutor([config], {
478
- checkpointer,
479
- hookRegistry,
480
- humanInTheLoop: { enabled: true },
481
- });
482
- const checkpointTarget = executor as unknown as {
483
- forkCheckpointSnapshot: (
484
- sources: SubagentResumeExecution['checkpoints'],
485
- targetThreadId: string
486
- ) => Promise<void>;
487
- };
488
- jest
489
- .spyOn(checkpointTarget, 'forkCheckpointSnapshot')
490
- .mockImplementation(async (_sources, targetThreadId) => {
491
- markForkStarted();
492
- await forkGate;
493
- await putCheckpoint(
494
- checkpointer,
495
- targetThreadId,
496
- 'stale-child-checkpoint'
497
- );
498
- });
499
- const restoreApprovals = jest.spyOn(
500
- hookRegistry,
501
- 'restorePendingToolApprovals'
502
- );
503
- const deleteThread = jest.spyOn(checkpointer, 'deleteThread');
504
-
505
- const staleExecution = executor.execute({
506
- description: 'Resume the selected child.',
507
- subagentType: config.type,
508
- threadId: parentConfigurable.thread_id,
509
- parentToolCallId,
510
- parentConfigurable,
511
- });
512
- await forkStarted;
513
- executor.clearHeavyState();
514
- releaseFork();
515
-
516
- await expect(staleExecution).resolves.toMatchObject({
517
- content: 'Subagent error: Unable to initialize the selected subagent.',
518
- });
519
- expect(deleteThread).toHaveBeenCalledWith(address.branchChildThreadId);
520
- await expect(
521
- checkpointer.getTuple({
522
- configurable: { thread_id: address.branchChildThreadId },
523
- })
524
- ).resolves.toBeUndefined();
525
- expect(restoreApprovals).not.toHaveBeenCalled();
526
- expect(
527
- hookRegistry.getPendingToolApproval(approvalExecutionScope, approvalKey)
528
- ).toBeUndefined();
529
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
530
- await expect(
531
- executor.getResumeManifest(new Set([parentToolCallId]), {
532
- configurable: parentConfigurable,
533
- })
534
- ).resolves.toBeUndefined();
535
- expect(resolver).not.toHaveBeenCalled();
536
- });
537
-
538
- it('retries a same-address execution only after an old fork rolls back', async () => {
539
- const resolver = jest.fn(async () => {
540
- throw new Error('stop after identity commit');
541
- });
542
- const config = makeLazyConfig('researcher', resolver);
543
- const checkpointer = new MemorySaver();
544
- const hookRegistry = new HookRegistry();
545
- const parentToolCallId = 'call_fork_owner_race';
546
- const resumeAttemptId = 'attempt-shared';
547
- const approvalExecutionScope = getSubagentApprovalExecutionScope(
548
- 'original-child-run',
549
- resumeAttemptId
550
- );
551
- const approvalKey = {
552
- executionScope: approvalExecutionScope,
553
- agentId: 'child-agent',
554
- toolUseId: 'call_child_tool',
555
- };
556
- const approval = {
557
- decision: 'ask' as const,
558
- reason: 'review child tool',
559
- additionalContexts: [],
560
- injectedMessages: [],
561
- errors: [],
562
- };
563
- const resumeExecution = {
564
- ...makeResumeExecution(parentToolCallId, config.configId),
565
- approvalReplays: [
566
- {
567
- key: {
568
- ...approvalKey,
569
- executionScope: 'original-approval-scope',
570
- },
571
- result: approval,
572
- },
573
- ],
574
- };
575
- const parentConfigurable = {
576
- thread_id: 'durable-thread',
577
- checkpoint_id: 'parent-checkpoint',
578
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
579
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId,
580
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
581
- version: 1 as const,
582
- executions: [resumeExecution],
583
- },
584
- };
585
- const address = new SubagentExecutionRegistry({
586
- parentRunId: 'parent-run',
587
- parentAgentId: 'parent-agent',
588
- durable: true,
589
- }).open({
590
- threadId: parentConfigurable.thread_id,
591
- parentToolCallId,
592
- parentConfigurable,
593
- }).address;
594
- let releaseOldFork = (): void => undefined;
595
- let markOldForkStarted = (): void => undefined;
596
- const oldForkGate = new Promise<void>((resolve) => {
597
- releaseOldFork = resolve;
598
- });
599
- const oldForkStarted = new Promise<void>((resolve) => {
600
- markOldForkStarted = resolve;
601
- });
602
- const executor = createExecutor([config], {
603
- checkpointer,
604
- hookRegistry,
605
- humanInTheLoop: { enabled: true },
606
- });
607
- const checkpointTarget = executor as unknown as {
608
- forkCheckpointSnapshot: (
609
- sources: SubagentResumeExecution['checkpoints'],
610
- targetThreadId: string
611
- ) => Promise<void>;
612
- };
613
- let forkInvocation = 0;
614
- jest
615
- .spyOn(checkpointTarget, 'forkCheckpointSnapshot')
616
- .mockImplementation(async (_sources, targetThreadId) => {
617
- forkInvocation += 1;
618
- if (forkInvocation === 1) {
619
- markOldForkStarted();
620
- await oldForkGate;
621
- await putCheckpoint(
622
- checkpointer,
623
- targetThreadId,
624
- '00000000-0000-0000-0000-000000000001'
625
- );
626
- return;
627
- }
628
- await putCheckpoint(
629
- checkpointer,
630
- targetThreadId,
631
- '00000000-0000-0000-0000-000000000002'
632
- );
633
- });
634
- const deleteThread = jest.spyOn(checkpointer, 'deleteThread');
635
- const params = {
636
- description: 'Resume the selected child.',
637
- subagentType: config.type,
638
- threadId: parentConfigurable.thread_id,
639
- parentToolCallId,
640
- parentConfigurable,
641
- };
642
-
643
- const staleExecution = executor.execute(params);
644
- await oldForkStarted;
645
- executor.clearHeavyState();
646
- await expect(executor.execute(params)).resolves.toMatchObject({
647
- content: 'Subagent error: Unable to initialize the selected subagent.',
648
- });
649
- expect(
650
- hookRegistry.getPendingToolApproval(approvalExecutionScope, approvalKey)
651
- ).toBeUndefined();
652
- releaseOldFork();
653
-
654
- await expect(staleExecution).resolves.toMatchObject({
655
- content: 'Subagent error: Unable to initialize the selected subagent.',
656
- });
657
- expect(deleteThread).toHaveBeenCalledTimes(1);
658
- expect(deleteThread).toHaveBeenCalledWith(address.branchChildThreadId);
659
- await expect(executor.execute(params)).resolves.toMatchObject({
660
- content: 'Subagent error: Unable to initialize the selected subagent.',
661
- });
662
- await expect(
663
- checkpointer.getTuple({
664
- configurable: { thread_id: address.branchChildThreadId },
665
- })
666
- ).resolves.toBeDefined();
667
- expect(
668
- hookRegistry.getPendingToolApproval(approvalExecutionScope, approvalKey)
669
- ).toEqual(approval);
670
- expect(executor.getChildCheckpointThreadIds()).toContain(
671
- address.branchChildThreadId
672
- );
673
- await expect(
674
- executor.getResumeManifest(new Set([parentToolCallId]), {
675
- configurable: parentConfigurable,
676
- })
677
- ).resolves.toBeDefined();
678
- expect(forkInvocation).toBe(2);
679
- });
680
-
681
- it('does not adopt a partial branch while its producer is pending', async () => {
682
- const resolver = jest.fn(async () => {
683
- throw new Error('stop after adopted identity commit');
684
- });
685
- const config = makeLazyConfig('researcher', resolver);
686
- const checkpointer = new MemorySaver();
687
- const parentToolCallId = 'call_partial_fork_adoption';
688
- const resumeAttemptId = 'attempt-adopted';
689
- const resumeExecution = makeResumeExecution(
690
- parentToolCallId,
691
- config.configId
692
- );
693
- const parentConfigurable = {
694
- thread_id: 'durable-thread',
695
- checkpoint_id: 'parent-checkpoint',
696
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
697
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId,
698
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
699
- version: 1 as const,
700
- executions: [resumeExecution],
701
- },
702
- };
703
- const {
704
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: _resumeManifest,
705
- ...retryConfigurable
706
- } = parentConfigurable;
707
- const address = new SubagentExecutionRegistry({
708
- parentRunId: 'parent-run',
709
- parentAgentId: 'parent-agent',
710
- durable: true,
711
- }).open({
712
- threadId: parentConfigurable.thread_id,
713
- parentToolCallId,
714
- parentConfigurable,
715
- }).address;
716
- let releaseOldFork = (): void => undefined;
717
- let markPartialForkWritten = (): void => undefined;
718
- const oldForkGate = new Promise<void>((resolve) => {
719
- releaseOldFork = resolve;
720
- });
721
- const partialForkWritten = new Promise<void>((resolve) => {
722
- markPartialForkWritten = resolve;
723
- });
724
- const executor = createExecutor([config], {
725
- checkpointer,
726
- humanInTheLoop: { enabled: true },
727
- });
728
- const checkpointTarget = executor as unknown as {
729
- forkCheckpointSnapshot: (
730
- sources: SubagentResumeExecution['checkpoints'],
731
- targetThreadId: string
732
- ) => Promise<void>;
733
- };
734
- const forkCheckpointSnapshot = jest
735
- .spyOn(checkpointTarget, 'forkCheckpointSnapshot')
736
- .mockImplementation(async (_sources, targetThreadId) => {
737
- await putCheckpoint(
738
- checkpointer,
739
- targetThreadId,
740
- '00000000-0000-0000-0000-000000000001'
741
- );
742
- markPartialForkWritten();
743
- await oldForkGate;
744
- });
745
- const deleteThread = jest.spyOn(checkpointer, 'deleteThread');
746
- const staleExecution = executor.execute({
747
- description: 'Resume the selected child.',
748
- subagentType: config.type,
749
- threadId: parentConfigurable.thread_id,
750
- parentToolCallId,
751
- parentConfigurable,
752
- });
753
- await partialForkWritten;
754
- executor.clearHeavyState();
755
-
756
- await expect(
757
- executor.execute({
758
- description: 'Adopt the partially forked child.',
759
- subagentType: config.type,
760
- threadId: retryConfigurable.thread_id,
761
- parentToolCallId,
762
- parentConfigurable: retryConfigurable,
763
- })
764
- ).resolves.toMatchObject({
765
- content: 'Subagent error: Unable to initialize the selected subagent.',
766
- });
767
- expect(forkCheckpointSnapshot).toHaveBeenCalledTimes(1);
768
- releaseOldFork();
769
-
770
- await expect(staleExecution).resolves.toMatchObject({
771
- content: 'Subagent error: Unable to initialize the selected subagent.',
772
- });
773
- expect(deleteThread).toHaveBeenCalledTimes(1);
774
- expect(deleteThread).toHaveBeenCalledWith(address.branchChildThreadId);
775
- await expect(
776
- checkpointer.getTuple({
777
- configurable: { thread_id: address.branchChildThreadId },
778
- })
779
- ).resolves.toBeUndefined();
780
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
781
- await expect(
782
- executor.getResumeManifest(new Set([parentToolCallId]), {
783
- configurable: retryConfigurable,
784
- })
785
- ).resolves.toBeUndefined();
786
- expect(resolver).not.toHaveBeenCalled();
787
- });
788
-
789
- it('fences retries until a late checkpoint writer and cleanup settle', async () => {
790
- const resolver = jest.fn(async () => makeAgent());
791
- const config = makeLazyConfig('researcher', resolver);
792
- const checkpointer = new MemorySaver();
793
- const parentToolCallId = 'call_partial_fork_failed_adoption';
794
- const resumeAttemptId = 'attempt-failed-adoption';
795
- const parentConfigurable = {
796
- thread_id: 'durable-thread',
797
- checkpoint_id: 'parent-checkpoint',
798
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
799
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId,
800
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
801
- version: 1 as const,
802
- executions: [makeResumeExecution(parentToolCallId, config.configId)],
803
- },
804
- };
805
- const {
806
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: _resumeManifest,
807
- ...retryConfigurable
808
- } = parentConfigurable;
809
- const address = new SubagentExecutionRegistry({
810
- parentRunId: 'parent-run',
811
- parentAgentId: 'parent-agent',
812
- durable: true,
813
- }).open({
814
- threadId: parentConfigurable.thread_id,
815
- parentToolCallId,
816
- parentConfigurable,
817
- }).address;
818
- let releaseLateWrite = (): void => undefined;
819
- let markLateWriteFinished = (): void => undefined;
820
- let releaseOldFork = (): void => undefined;
821
- let markPartialForkWritten = (): void => undefined;
822
- const lateWriteGate = new Promise<void>((resolve) => {
823
- releaseLateWrite = resolve;
824
- });
825
- const lateWriteFinished = new Promise<void>((resolve) => {
826
- markLateWriteFinished = resolve;
827
- });
828
- const oldForkGate = new Promise<void>((resolve) => {
829
- releaseOldFork = resolve;
830
- });
831
- const partialForkWritten = new Promise<void>((resolve) => {
832
- markPartialForkWritten = resolve;
833
- });
834
- const executor = createExecutor([config], {
835
- checkpointer,
836
- humanInTheLoop: { enabled: true },
837
- createChildGraph: makeRecoveredGraph,
838
- });
839
- const checkpointTarget = executor as unknown as {
840
- forkCheckpointSnapshot: (
841
- sources: SubagentResumeExecution['checkpoints'],
842
- targetThreadId: string
843
- ) => Promise<void>;
844
- };
845
- let forkInvocation = 0;
846
- const forkCheckpointSnapshot = jest
847
- .spyOn(checkpointTarget, 'forkCheckpointSnapshot')
848
- .mockImplementation(async (_sources, targetThreadId) => {
849
- forkInvocation += 1;
850
- if (forkInvocation > 1) {
851
- await putCheckpoint(
852
- checkpointer,
853
- targetThreadId,
854
- '00000000-0000-0000-0000-000000000002'
855
- );
856
- return;
857
- }
858
- await putCheckpoint(
859
- checkpointer,
860
- targetThreadId,
861
- '00000000-0000-0000-0000-000000000001'
862
- );
863
- markPartialForkWritten();
864
- await lateWriteGate;
865
- await putCheckpoint(
866
- checkpointer,
867
- targetThreadId,
868
- '00000000-0000-0000-0000-000000000003'
869
- );
870
- markLateWriteFinished();
871
- await oldForkGate;
872
- });
873
- let releaseDelete = (): void => undefined;
874
- let markDeleteStarted = (): void => undefined;
875
- const deleteGate = new Promise<void>((resolve) => {
876
- releaseDelete = resolve;
877
- });
878
- const deleteStarted = new Promise<void>((resolve) => {
879
- markDeleteStarted = resolve;
880
- });
881
- const deleteCheckpointThread = checkpointer.deleteThread.bind(checkpointer);
882
- const deleteThread = jest
883
- .spyOn(checkpointer, 'deleteThread')
884
- .mockImplementation(async (threadId) => {
885
- markDeleteStarted();
886
- await deleteGate;
887
- await deleteCheckpointThread(threadId);
888
- });
889
- const staleExecution = executor.execute({
890
- description: 'Resume the selected child.',
891
- subagentType: config.type,
892
- threadId: parentConfigurable.thread_id,
893
- parentToolCallId,
894
- parentConfigurable,
895
- });
896
- await partialForkWritten;
897
- executor.clearHeavyState();
898
-
899
- const retryParams = {
900
- description: 'Adopt the partially forked child.',
901
- subagentType: config.type,
902
- threadId: retryConfigurable.thread_id,
903
- parentToolCallId,
904
- parentConfigurable: retryConfigurable,
905
- };
906
- await expect(executor.execute(retryParams)).resolves.toMatchObject({
907
- content: 'Subagent error: Unable to initialize the selected subagent.',
908
- });
909
- expect(deleteThread).not.toHaveBeenCalled();
910
- releaseLateWrite();
911
- await lateWriteFinished;
912
- await expect(executor.execute(retryParams)).resolves.toMatchObject({
913
- content: 'Subagent error: Unable to initialize the selected subagent.',
914
- });
915
- expect(deleteThread).not.toHaveBeenCalled();
916
- releaseOldFork();
917
- await deleteStarted;
918
- await expect(executor.execute(retryParams)).resolves.toMatchObject({
919
- content: 'Subagent error: Unable to initialize the selected subagent.',
920
- });
921
- releaseDelete();
922
- await expect(staleExecution).resolves.toMatchObject({
923
- content: 'Subagent error: Unable to initialize the selected subagent.',
924
- });
925
- await expect(
926
- checkpointer.getTuple({
927
- configurable: { thread_id: address.branchChildThreadId },
928
- })
929
- ).resolves.toBeUndefined();
930
-
931
- await expect(
932
- executor.execute({
933
- ...retryParams,
934
- parentConfigurable,
935
- })
936
- ).resolves.toMatchObject({ content: 'persisted result' });
937
-
938
- expect(deleteThread).toHaveBeenCalledTimes(1);
939
- expect(deleteThread).toHaveBeenCalledWith(address.branchChildThreadId);
940
- expect(forkCheckpointSnapshot).toHaveBeenCalledTimes(2);
941
- await expect(
942
- checkpointer.getTuple({
943
- configurable: { thread_id: address.branchChildThreadId },
944
- })
945
- ).resolves.toBeDefined();
946
- expect(executor.getChildCheckpointThreadIds()).toContain(
947
- address.branchChildThreadId
948
- );
949
- await expect(
950
- executor.getResumeManifest(new Set([parentToolCallId]), {
951
- configurable: parentConfigurable,
952
- })
953
- ).resolves.toBeDefined();
954
- expect(resolver).toHaveBeenCalledTimes(1);
955
- });
956
-
957
- it('preserves a preexisting branch when its adopter is invalidated', async () => {
958
- const resolver = jest.fn(async () => makeAgent());
959
- const config = makeLazyConfig('researcher', resolver);
960
- const checkpointer = new MemorySaver();
961
- const parentToolCallId = 'call_preexisting_failed_adoption';
962
- const parentConfigurable = {
963
- thread_id: 'durable-thread',
964
- checkpoint_id: 'parent-checkpoint',
965
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
966
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-preexisting-adoption',
967
- };
968
- const address = new SubagentExecutionRegistry({
969
- parentRunId: 'parent-run',
970
- parentAgentId: 'parent-agent',
971
- durable: true,
972
- }).open({
973
- threadId: parentConfigurable.thread_id,
974
- parentToolCallId,
975
- parentConfigurable,
976
- }).address;
977
- await putCheckpoint(
978
- checkpointer,
979
- address.branchChildThreadId,
980
- '00000000-0000-0000-0000-000000000001'
981
- );
982
- const executor = createExecutor([config], {
983
- checkpointer,
984
- humanInTheLoop: { enabled: true },
985
- });
986
- const identityTarget = executor as unknown as {
987
- prepareIdentity(
988
- execution: object,
989
- identity: SubagentExecutionIdentity
990
- ): PreparedSubagentExecutionIdentity;
991
- };
992
- const prepareIdentity = identityTarget.prepareIdentity.bind(identityTarget);
993
- jest
994
- .spyOn(identityTarget, 'prepareIdentity')
995
- .mockImplementation((execution, identity) => {
996
- const preparation = prepareIdentity(execution, identity);
997
- queueMicrotask(() => executor.clearHeavyState());
998
- return preparation;
999
- });
1000
- const deleteThread = jest.spyOn(checkpointer, 'deleteThread');
1001
-
1002
- await expect(
1003
- executor.execute({
1004
- description: 'Adopt the existing child branch.',
1005
- subagentType: config.type,
1006
- threadId: parentConfigurable.thread_id,
1007
- parentToolCallId,
1008
- parentConfigurable,
1009
- })
1010
- ).resolves.toMatchObject({
1011
- content: 'Subagent error: Unable to initialize the selected subagent.',
1012
- });
1013
-
1014
- expect(deleteThread).not.toHaveBeenCalled();
1015
- await expect(
1016
- checkpointer.getTuple({
1017
- configurable: { thread_id: address.branchChildThreadId },
1018
- })
1019
- ).resolves.toBeDefined();
1020
- await expect(
1021
- executor.getResumeManifest(new Set([parentToolCallId]), {
1022
- configurable: parentConfigurable,
1023
- })
1024
- ).resolves.toBeUndefined();
1025
- expect(resolver).not.toHaveBeenCalled();
1026
- });
1027
-
1028
- it.each([
1029
- 'resume manifest fork',
1030
- 'branch adoption',
1031
- 'missing base',
1032
- 'missing base with explicit attempt',
1033
- 'base reuse',
1034
- 'base fork',
1035
- ] as const)('leases the exact HITL identity for %s', async (path) => {
1036
- const config = makeLazyConfig('researcher', async () => {
1037
- throw new Error('stop after identity commit');
1038
- });
1039
- const checkpointer = new MemorySaver();
1040
- const parentToolCallId = `call_identity_${path.replaceAll(' ', '_')}`;
1041
- const explicitResumeAttempt =
1042
- path === 'resume manifest fork' ||
1043
- path === 'branch adoption' ||
1044
- path === 'missing base with explicit attempt' ||
1045
- path === 'base fork';
1046
- const parentConfigurable: Record<string, unknown> = {
1047
- thread_id: 'durable-thread',
1048
- checkpoint_id: 'parent-checkpoint',
1049
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
1050
- ...(explicitResumeAttempt
1051
- ? { [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-exact' }
1052
- : {}),
1053
- };
1054
- if (path === 'resume manifest fork') {
1055
- parentConfigurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = {
1056
- version: 1,
1057
- executions: [makeResumeExecution(parentToolCallId, config.configId)],
1058
- };
1059
- await putCheckpoint(
1060
- checkpointer,
1061
- 'resume-source-thread',
1062
- 'resume-checkpoint'
1063
- );
1064
- }
1065
- const address = new SubagentExecutionRegistry({
1066
- parentRunId: 'parent-run',
1067
- parentAgentId: 'parent-agent',
1068
- durable: true,
1069
- }).open({
1070
- threadId: 'durable-thread',
1071
- parentToolCallId,
1072
- parentConfigurable,
1073
- }).address;
1074
- if (path === 'branch adoption') {
1075
- await putCheckpoint(
1076
- checkpointer,
1077
- address.branchChildThreadId,
1078
- '00000000-0000-0000-0000-000000000001'
1079
- );
1080
- }
1081
- if (path === 'base reuse' || path === 'base fork') {
1082
- await putCheckpoint(
1083
- checkpointer,
1084
- address.baseChildThreadId,
1085
- '00000000-0000-0000-0000-000000000001'
1086
- );
1087
- }
1088
- const beginPreparation = jest.spyOn(
1089
- SubagentExecutionRegistry.prototype,
1090
- 'beginIdentityPreparation'
1091
- );
1092
- const executor = createExecutor([config], {
1093
- checkpointer,
1094
- humanInTheLoop: { enabled: true },
1095
- });
1096
- const expectedChildRunId =
1097
- path === 'resume manifest fork'
1098
- ? 'original-child-run'
1099
- : address.currentChildRunId;
1100
- const expectedChildThreadId =
1101
- path === 'missing base' || path === 'base reuse'
1102
- ? address.baseChildThreadId
1103
- : address.branchChildThreadId;
1104
-
1105
- try {
1106
- await expect(
1107
- executor.execute({
1108
- description: 'Resolve the selected child identity.',
1109
- subagentType: config.type,
1110
- threadId: 'durable-thread',
1111
- parentToolCallId,
1112
- parentConfigurable,
1113
- })
1114
- ).resolves.toMatchObject({
1115
- content: 'Subagent error: Unable to initialize the selected subagent.',
1116
- });
1117
- expect(beginPreparation).toHaveBeenCalledTimes(1);
1118
- expect(beginPreparation.mock.calls[0][0].address.key).toBe(address.key);
1119
- expect(beginPreparation.mock.calls[0][1]).toEqual({
1120
- childRunId: expectedChildRunId,
1121
- childThreadId: expectedChildThreadId,
1122
- approvalExecutionScope: getSubagentApprovalExecutionScope(
1123
- expectedChildRunId,
1124
- address.resumeAttemptId
1125
- ),
1126
- });
1127
- } finally {
1128
- beginPreparation.mockRestore();
1129
- }
1130
- });
1131
-
1132
- it('preserves a preexisting branch when a later fork fails', async () => {
1133
- const config = makeLazyConfig('researcher', async () => makeAgent());
1134
- const checkpointer = new MemorySaver();
1135
- const parentToolCallId = 'call_preexisting_fork_failure';
1136
- const resumeAttemptId = 'attempt-existing';
1137
- const parentConfigurable = {
1138
- thread_id: 'durable-thread',
1139
- checkpoint_id: 'parent-checkpoint',
1140
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'parent-batch',
1141
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: resumeAttemptId,
1142
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
1143
- version: 1 as const,
1144
- executions: [makeResumeExecution(parentToolCallId, config.configId)],
1145
- },
1146
- };
1147
- const address = new SubagentExecutionRegistry({
1148
- parentRunId: 'parent-run',
1149
- parentAgentId: 'parent-agent',
1150
- durable: true,
1151
- }).open({
1152
- threadId: parentConfigurable.thread_id,
1153
- parentToolCallId,
1154
- parentConfigurable,
1155
- }).address;
1156
- await putCheckpoint(
1157
- checkpointer,
1158
- address.branchChildThreadId,
1159
- '00000000-0000-0000-0000-000000000001'
1160
- );
1161
- const executor = createExecutor([config], {
1162
- checkpointer,
1163
- humanInTheLoop: { enabled: true },
1164
- });
1165
- const checkpointTarget = executor as unknown as {
1166
- forkCheckpointSnapshot: () => Promise<void>;
1167
- };
1168
- const forkError = new Error('fork failed after finding target');
1169
- jest
1170
- .spyOn(checkpointTarget, 'forkCheckpointSnapshot')
1171
- .mockRejectedValue(forkError);
1172
- const deleteThread = jest.spyOn(checkpointer, 'deleteThread');
1173
-
1174
- await expect(
1175
- executor.execute({
1176
- description: 'Resume the selected child.',
1177
- subagentType: config.type,
1178
- threadId: parentConfigurable.thread_id,
1179
- parentToolCallId,
1180
- parentConfigurable,
1181
- })
1182
- ).resolves.toMatchObject({
1183
- content: 'Subagent error: Unable to initialize the selected subagent.',
1184
- });
1185
-
1186
- expect(deleteThread).not.toHaveBeenCalled();
1187
- await expect(
1188
- checkpointer.getTuple({
1189
- configurable: { thread_id: address.branchChildThreadId },
1190
- })
1191
- ).resolves.toBeDefined();
1192
- });
1193
-
1194
- it('rejects a missing durable tool call ID before opening an execution record', async () => {
1195
- const config = makeLazyConfig('researcher', async () => makeAgent());
1196
- const openExecution = jest.spyOn(
1197
- SubagentExecutionRegistry.prototype,
1198
- 'open'
1199
- );
1200
- const executor = createExecutor([config], {
1201
- checkpointer: new MemorySaver(),
1202
- humanInTheLoop: { enabled: true },
1203
- });
1204
-
1205
- const result = await executor.execute({
1206
- description: 'Do not register this child.',
1207
- subagentType: config.type,
1208
- });
1209
-
1210
- expect(result.content).toBe(
1211
- 'Error: Resumable subagent execution requires a parent tool call ID.'
1212
- );
1213
- expect(openExecution).not.toHaveBeenCalled();
1214
- openExecution.mockRestore();
1215
- });
1216
-
1217
- it('preserves a workflow construction error and allows a retry', async () => {
1218
- const config = makeLazyConfig('researcher', async () => makeAgent());
1219
- const clearFailedGraph = jest.fn();
1220
- let graphCount = 0;
1221
- const executor = createExecutor([config], {
1222
- createChildGraph: (): StandardGraph => {
1223
- graphCount += 1;
1224
- if (graphCount > 1) {
1225
- return makeGraph();
1226
- }
1227
- return {
1228
- createWorkflow: () => {
1229
- throw new Error('workflow setup failed');
1230
- },
1231
- clearHeavyState: clearFailedGraph,
1232
- } as unknown as StandardGraph;
1233
- },
1234
- });
1235
- const params = {
1236
- description: 'Retry workflow construction.',
1237
- subagentType: config.type,
1238
- parentToolCallId: 'call_workflow_retry',
1239
- };
1240
-
1241
- const failed = await executor.execute(params);
1242
- const retried = await executor.execute(params);
1243
-
1244
- expect(failed.content).toBe('Subagent error: workflow setup failed');
1245
- expect(retried.content).toBe('Task completed');
1246
- expect(clearFailedGraph).toHaveBeenCalledTimes(1);
1247
- });
1248
-
1249
- it('keeps concurrent checkpoint forks as distinct child executions', async () => {
1250
- const resolverContexts: SubagentResolveContext[] = [];
1251
- let releaseInvocations = (): void => undefined;
1252
- const invocationGate = new Promise<void>((resolve) => {
1253
- releaseInvocations = resolve;
1254
- });
1255
- let invocationCount = 0;
1256
- let markInvocationsStarted = (): void => undefined;
1257
- const invocationsStarted = new Promise<void>((resolve) => {
1258
- markInvocationsStarted = resolve;
1259
- });
1260
- const invoke = jest.fn(async (): Promise<MultiAgentGraphState> => {
1261
- invocationCount += 1;
1262
- const current = invocationCount;
1263
- if (invocationCount === 2) {
1264
- markInvocationsStarted();
1265
- }
1266
- await invocationGate;
1267
- return { messages: [new AIMessage(`result-${current}`)] };
1268
- });
1269
- const config = makeLazyConfig('researcher', async (context) => {
1270
- resolverContexts.push(context);
1271
- return makeAgent();
1272
- });
1273
- const executor = createExecutor([config], {
1274
- createChildGraph: () =>
1275
- ({
1276
- createWorkflow: () => ({ invoke }),
1277
- clearHeavyState: jest.fn(),
1278
- }) as unknown as StandardGraph,
1279
- });
1280
- const common = {
1281
- description: 'Run this selected execution.',
1282
- subagentType: config.type,
1283
- threadId: 'durable-thread',
1284
- parentToolCallId: 'call_reused',
1285
- };
1286
-
1287
- const first = executor.execute({
1288
- ...common,
1289
- parentConfigurable: { checkpoint_id: 'fork-a' },
1290
- });
1291
- const second = executor.execute({
1292
- ...common,
1293
- parentConfigurable: { checkpoint_id: 'fork-b' },
1294
- });
1295
- await invocationsStarted;
1296
- const callsBeforeRelease = invoke.mock.calls.length;
1297
- releaseInvocations();
1298
- const [firstResult, secondResult] = await Promise.all([first, second]);
1299
-
1300
- expect(callsBeforeRelease).toBe(2);
1301
- expect(resolverContexts).toHaveLength(2);
1302
- expect(
1303
- new Set(resolverContexts.map((context) => context.executionId)).size
1304
- ).toBe(2);
1305
- expect(new Set([firstResult.content, secondResult.content])).toEqual(
1306
- new Set(['result-1', 'result-2'])
1307
- );
1308
- });
1309
-
1310
- it('scopes resume manifests to concurrent forks sharing a tool call ID', async () => {
1311
- const checkpointer = new MemorySaver();
1312
- const config = makeLazyConfig('researcher', async () => makeAgent());
1313
- const executor = createExecutor([config], {
1314
- checkpointer,
1315
- humanInTheLoop: { enabled: true },
1316
- });
1317
- const parentToolCallId = 'call_shared_forks';
1318
- const call = {
1319
- id: parentToolCallId,
1320
- name: 'spawn_subagent',
1321
- args: {
1322
- description: 'Run the forked child.',
1323
- subagent_type: config.type,
1324
- },
1325
- type: 'tool_call' as const,
1326
- };
1327
- const forkAConfig = {
1328
- configurable: {
1329
- thread_id: 'durable-thread',
1330
- checkpoint_id: 'fork-a',
1331
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'shared-batch',
1332
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-a',
1333
- },
1334
- };
1335
- const forkBConfig = {
1336
- configurable: {
1337
- thread_id: 'durable-thread',
1338
- checkpoint_id: 'fork-b',
1339
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'shared-batch',
1340
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-b',
1341
- },
1342
- };
1343
- const forkCConfig = {
1344
- configurable: {
1345
- thread_id: 'durable-thread',
1346
- checkpoint_id: 'fork-a',
1347
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'shared-batch',
1348
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-c',
1349
- },
1350
- };
1351
- const makeSettledOutput = (content: string): SettledSubagentToolOutput => ({
1352
- output: new ToolMessage({
1353
- content,
1354
- name: call.name,
1355
- tool_call_id: parentToolCallId,
1356
- }),
1357
- additionalContexts: [],
1358
- resolvedArgs: call.args,
1359
- });
1360
-
1361
- await Promise.all([
1362
- executor.persistSettledToolOutput(
1363
- call,
1364
- forkAConfig,
1365
- makeSettledOutput('fork-a result')
1366
- ),
1367
- executor.persistSettledToolOutput(
1368
- call,
1369
- forkBConfig,
1370
- makeSettledOutput('fork-b result')
1371
- ),
1372
- ]);
1373
-
1374
- const parentToolCallIds = new Set([parentToolCallId]);
1375
- const [manifestA, manifestB] = await Promise.all([
1376
- executor.getResumeManifest(parentToolCallIds, forkAConfig),
1377
- executor.getResumeManifest(parentToolCallIds, forkBConfig),
1378
- ]);
1379
- if (manifestA == null) {
1380
- throw new Error('Expected fork A resume state.');
1381
- }
1382
- const forkCResumeConfig = {
1383
- configurable: {
1384
- ...forkCConfig.configurable,
1385
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: manifestA,
1386
- },
1387
- };
1388
- await executor.persistSettledToolOutput(
1389
- call,
1390
- forkCResumeConfig,
1391
- makeSettledOutput('fork-c result')
1392
- );
1393
- const manifestC = await executor.getResumeManifest(
1394
- parentToolCallIds,
1395
- forkCResumeConfig
1396
- );
1397
- if (manifestB == null || manifestC == null) {
1398
- throw new Error('Expected fork B and C resume state.');
1399
- }
1400
-
1401
- await expect(
1402
- executor.getResumeManifest(parentToolCallIds)
1403
- ).resolves.toBeUndefined();
1404
- expect(manifestA.executions).toHaveLength(1);
1405
- expect(manifestB.executions).toHaveLength(1);
1406
- expect(manifestC.executions).toHaveLength(1);
1407
- expect(manifestA.executions[0].childRunId).toBe(
1408
- manifestC.executions[0].childRunId
1409
- );
1410
- expect(manifestA.executions[0].childRunId).not.toBe(
1411
- manifestB.executions[0].childRunId
1412
- );
1413
- expect(
1414
- new Set(
1415
- [manifestA, manifestB, manifestC].map(
1416
- (manifest) => manifest.executions[0].checkpoints[0].threadId
1417
- )
1418
- ).size
1419
- ).toBe(3);
1420
-
1421
- const replay = async (
1422
- manifest: SubagentResumeManifest | undefined,
1423
- parentConfigurable: Record<string, unknown>
1424
- ): Promise<SettledSubagentToolOutput | undefined> =>
1425
- createExecutor([config], {
1426
- checkpointer,
1427
- humanInTheLoop: { enabled: true },
1428
- }).getSettledToolOutput(call, {
1429
- configurable: {
1430
- ...parentConfigurable,
1431
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: manifest,
1432
- },
1433
- });
1434
- const [replayedA, replayedB, replayedC] = await Promise.all([
1435
- replay(manifestA, forkAConfig.configurable),
1436
- replay(manifestB, forkBConfig.configurable),
1437
- replay(manifestC, forkCConfig.configurable),
1438
- ]);
1439
- expect(replayedA?.output.content).toBe('fork-a result');
1440
- expect(replayedB?.output.content).toBe('fork-b result');
1441
- expect(replayedC?.output.content).toBe('fork-c result');
1442
- });
1443
-
1444
- it('isolates concurrent resume attempts without a manifest', async () => {
1445
- const checkpointer = new MemorySaver();
1446
- const config = makeLazyConfig('researcher', async () => makeAgent());
1447
- const executor = createExecutor([config], {
1448
- checkpointer,
1449
- humanInTheLoop: { enabled: true },
1450
- });
1451
- const parentToolCallId = 'call_shared_attempts';
1452
- const call = {
1453
- id: parentToolCallId,
1454
- name: 'spawn_subagent',
1455
- args: {
1456
- description: 'Run the attempted child.',
1457
- subagent_type: config.type,
1458
- },
1459
- type: 'tool_call' as const,
1460
- };
1461
- const baseConfigurable = {
1462
- thread_id: 'durable-thread',
1463
- checkpoint_id: 'shared-fork',
1464
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'shared-batch',
1465
- };
1466
- const attemptA = {
1467
- configurable: {
1468
- ...baseConfigurable,
1469
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-a',
1470
- },
1471
- };
1472
- const attemptB = {
1473
- configurable: {
1474
- ...baseConfigurable,
1475
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-b',
1476
- },
1477
- };
1478
- const makeSettled = (content: string): SettledSubagentToolOutput => ({
1479
- output: new ToolMessage({
1480
- content,
1481
- name: call.name,
1482
- tool_call_id: parentToolCallId,
1483
- }),
1484
- additionalContexts: [],
1485
- resolvedArgs: call.args,
1486
- });
1487
-
1488
- await Promise.all([
1489
- executor.persistSettledToolOutput(
1490
- call,
1491
- attemptA,
1492
- makeSettled('attempt-a result')
1493
- ),
1494
- executor.persistSettledToolOutput(
1495
- call,
1496
- attemptB,
1497
- makeSettled('attempt-b result')
1498
- ),
1499
- ]);
1500
- const [manifestA, manifestB] = await Promise.all([
1501
- executor.getResumeManifest(new Set([parentToolCallId]), attemptA),
1502
- executor.getResumeManifest(new Set([parentToolCallId]), attemptB),
1503
- ]);
1504
-
1505
- expect(manifestA?.executions).toHaveLength(1);
1506
- expect(manifestB?.executions).toHaveLength(1);
1507
- expect(manifestA?.executions[0].childRunId).toBe(
1508
- manifestB?.executions[0].childRunId
1509
- );
1510
- expect(manifestA?.executions[0].checkpoints[0].threadId).not.toBe(
1511
- manifestB?.executions[0].checkpoints[0].threadId
1512
- );
1513
- });
1514
-
1515
- it('forks a legacy base checkpoint into each concurrent resume attempt', async () => {
1516
- const checkpointer = new MemorySaver();
1517
- const config = makeLazyConfig('researcher', async () => makeAgent());
1518
- const parentToolCallId = 'call_legacy_attempts';
1519
- const baseConfigurable = {
1520
- checkpoint_id: 'shared-fork',
1521
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'shared-batch',
1522
- };
1523
- const addressRegistry = new SubagentExecutionRegistry({
1524
- parentRunId: 'parent-run',
1525
- parentAgentId: 'parent-agent',
1526
- durable: true,
1527
- });
1528
- const legacyAddress = addressRegistry.open({
1529
- threadId: 'durable-thread',
1530
- parentToolCallId,
1531
- parentConfigurable: {
1532
- ...baseConfigurable,
1533
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-a',
1534
- },
1535
- }).address;
1536
- await putCheckpoint(
1537
- checkpointer,
1538
- legacyAddress.baseChildThreadId,
1539
- '00000000-0000-0000-0000-000000000001'
1540
- );
1541
- const executor = createExecutor([config], {
1542
- checkpointer,
1543
- humanInTheLoop: { enabled: true },
1544
- });
1545
- const call = {
1546
- id: parentToolCallId,
1547
- name: 'spawn_subagent',
1548
- args: {
1549
- description: 'Resume the legacy child.',
1550
- subagent_type: config.type,
1551
- },
1552
- type: 'tool_call' as const,
1553
- };
1554
- const attemptConfig = (attempt: string) => ({
1555
- configurable: {
1556
- thread_id: 'durable-thread',
1557
- ...baseConfigurable,
1558
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: attempt,
1559
- },
1560
- });
1561
- const attemptA = attemptConfig('attempt-a');
1562
- const attemptB = attemptConfig('attempt-b');
1563
- const makeSettled = (content: string): SettledSubagentToolOutput => ({
1564
- output: new ToolMessage({
1565
- content,
1566
- name: call.name,
1567
- tool_call_id: parentToolCallId,
1568
- }),
1569
- additionalContexts: [],
1570
- resolvedArgs: call.args,
1571
- });
1572
-
1573
- await Promise.all([
1574
- executor.persistSettledToolOutput(
1575
- call,
1576
- attemptA,
1577
- makeSettled('attempt-a result')
1578
- ),
1579
- executor.persistSettledToolOutput(
1580
- call,
1581
- attemptB,
1582
- makeSettled('attempt-b result')
1583
- ),
1584
- ]);
1585
- const [manifestA, manifestB] = await Promise.all([
1586
- executor.getResumeManifest(new Set([parentToolCallId]), attemptA),
1587
- executor.getResumeManifest(new Set([parentToolCallId]), attemptB),
1588
- ]);
1589
- const threadA = manifestA?.executions[0].checkpoints[0].threadId;
1590
- const threadB = manifestB?.executions[0].checkpoints[0].threadId;
1591
-
1592
- expect(threadA).toBe(
1593
- addressRegistry.open({
1594
- threadId: 'durable-thread',
1595
- parentToolCallId,
1596
- parentConfigurable: attemptA.configurable,
1597
- }).address.branchChildThreadId
1598
- );
1599
- expect(threadB).toBe(
1600
- addressRegistry.open({
1601
- threadId: 'durable-thread',
1602
- parentToolCallId,
1603
- parentConfigurable: attemptB.configurable,
1604
- }).address.branchChildThreadId
1605
- );
1606
- expect(threadA).not.toBe(threadB);
1607
- expect(threadA).not.toBe(legacyAddress.baseChildThreadId);
1608
- expect(threadB).not.toBe(legacyAddress.baseChildThreadId);
1609
- });
1610
-
1611
- it('retires superseded graphs across sequential resume attempts', async () => {
1612
- const checkpointer = new MemorySaver();
1613
- const config = makeLazyConfig('researcher', async () => makeAgent());
1614
- const parentToolCallId = 'call_sequential_attempts';
1615
- const addressRegistry = new SubagentExecutionRegistry({
1616
- parentRunId: 'parent-run',
1617
- parentAgentId: 'parent-agent',
1618
- durable: true,
1619
- });
1620
- const baseConfigurable = {
1621
- thread_id: 'durable-thread',
1622
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'shared-batch',
1623
- };
1624
- const attemptConfig = (
1625
- attempt: string,
1626
- checkpointId: string,
1627
- sourceThreadId?: string
1628
- ): Record<string, unknown> => {
1629
- const configurable: Record<string, unknown> = {
1630
- ...baseConfigurable,
1631
- checkpoint_id: checkpointId,
1632
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: attempt,
1633
- };
1634
- if (sourceThreadId != null) {
1635
- configurable[SUBAGENT_RESUME_MANIFEST_CONFIG_KEY] = {
1636
- version: 1,
1637
- executions: [
1638
- makeResumeExecution(
1639
- parentToolCallId,
1640
- config.configId,
1641
- sourceThreadId
1642
- ),
1643
- ],
1644
- };
1645
- }
1646
- return configurable;
1647
- };
1648
- const configA = attemptConfig('attempt-a', 'fork-a');
1649
- const addressA = addressRegistry.open({
1650
- threadId: baseConfigurable.thread_id,
1651
- parentToolCallId,
1652
- parentConfigurable: configA,
1653
- }).address;
1654
- const configB = attemptConfig(
1655
- 'attempt-b',
1656
- 'fork-b',
1657
- addressA.branchChildThreadId
1658
- );
1659
- const addressB = addressRegistry.open({
1660
- threadId: baseConfigurable.thread_id,
1661
- parentToolCallId,
1662
- parentConfigurable: configB,
1663
- }).address;
1664
- const configC = attemptConfig(
1665
- 'attempt-c',
1666
- 'fork-c',
1667
- addressB.branchChildThreadId
1668
- );
1669
- const hookRegistry = new HookRegistry();
1670
- const scopeA = getSubagentApprovalExecutionScope(
1671
- addressA.currentChildRunId,
1672
- 'attempt-a'
1673
- );
1674
- const scopeB = getSubagentApprovalExecutionScope(
1675
- 'original-child-run',
1676
- 'attempt-b'
1677
- );
1678
- for (const scope of [scopeA, scopeB]) {
1679
- hookRegistry.registerSession(scope, 'PreToolUse', {
1680
- hooks: [async (): Promise<Record<string, never>> => ({})],
1681
- });
1682
- }
1683
- const clearHeavyState: Array<jest.MockedFunction<() => void>> = [];
1684
- let graphIndex = 0;
1685
- const executor = createExecutor([config], {
1686
- checkpointer,
1687
- hookRegistry,
1688
- humanInTheLoop: { enabled: true },
1689
- createChildGraph: (): StandardGraph => {
1690
- graphIndex += 1;
1691
- const currentGraphIndex = graphIndex;
1692
- const clear = jest.fn();
1693
- clearHeavyState.push(clear);
1694
- return {
1695
- createWorkflow: () => ({
1696
- getState: jest.fn(async () => ({
1697
- values: {},
1698
- next: [],
1699
- tasks: [],
1700
- })),
1701
- invoke: jest.fn(async (): Promise<MultiAgentGraphState> => {
1702
- throw new GraphInterrupt([
1703
- {
1704
- id: `interrupt-${currentGraphIndex}`,
1705
- value: 'pause',
1706
- },
1707
- ]);
1708
- }),
1709
- updateState: jest.fn(async () => ({})),
1710
- }),
1711
- createSubagentResumeState: jest.fn(() => ({
1712
- toolCallSteps: [],
1713
- toolSessions: [],
1714
- toolNodes: [],
1715
- eagerToolUsage: [],
1716
- eagerToolSuppressions: [],
1717
- })),
1718
- restoreSubagentResumeState: jest.fn(),
1719
- clearHeavyState: clear,
1720
- } as unknown as StandardGraph;
1721
- },
1722
- });
1723
- const checkpointTarget = executor as unknown as {
1724
- forkCheckpointSnapshot: () => Promise<void>;
1725
- getLatestCheckpointSnapshot: (
1726
- threadId: string
1727
- ) => Promise<SubagentResumeExecution['checkpoints']>;
1728
- };
1729
- jest
1730
- .spyOn(checkpointTarget, 'forkCheckpointSnapshot')
1731
- .mockResolvedValue(undefined);
1732
- jest
1733
- .spyOn(checkpointTarget, 'getLatestCheckpointSnapshot')
1734
- .mockImplementation(async (threadId) => [
1735
- {
1736
- threadId,
1737
- checkpointId: '00000000-0000-0000-0000-000000000001',
1738
- checkpointNs: '',
1739
- },
1740
- ]);
1741
- const runAttempt = (parentConfigurable: Record<string, unknown>) =>
1742
- executor.execute({
1743
- description: 'Resume the interrupted child.',
1744
- subagentType: config.type,
1745
- threadId: baseConfigurable.thread_id,
1746
- parentToolCallId,
1747
- parentConfigurable,
1748
- });
1749
-
1750
- await expect(runAttempt(configA)).rejects.toBeInstanceOf(GraphInterrupt);
1751
- expect(clearHeavyState.map((clear) => clear.mock.calls.length)).toEqual([
1752
- 0,
1753
- ]);
1754
- await expect(runAttempt(configB)).rejects.toBeInstanceOf(GraphInterrupt);
1755
- expect(clearHeavyState.map((clear) => clear.mock.calls.length)).toEqual([
1756
- 1, 0,
1757
- ]);
1758
- expect(hookRegistry.hasHookFor('PreToolUse', scopeA)).toBe(false);
1759
- expect(hookRegistry.hasHookFor('PreToolUse', scopeB)).toBe(true);
1760
- await expect(runAttempt(configC)).rejects.toBeInstanceOf(GraphInterrupt);
1761
- expect(clearHeavyState.map((clear) => clear.mock.calls.length)).toEqual([
1762
- 1, 1, 0,
1763
- ]);
1764
- expect(hookRegistry.hasHookFor('PreToolUse', scopeB)).toBe(false);
1765
- });
1766
-
1767
- it('coalesces duplicate settlement and replays its retained output in-process', async () => {
1768
- const checkpointer = new MemorySaver();
1769
- const config = makeLazyConfig('researcher', async () => makeAgent());
1770
- const executor = createExecutor([config], {
1771
- checkpointer,
1772
- humanInTheLoop: { enabled: true },
1773
- });
1774
- const parentToolCallId = 'call_duplicate_settlement';
1775
- const call = {
1776
- id: parentToolCallId,
1777
- name: 'spawn_subagent',
1778
- args: {
1779
- description: 'Persist this result once.',
1780
- subagent_type: config.type,
1781
- },
1782
- type: 'tool_call' as const,
1783
- };
1784
- const runnableConfig = {
1785
- configurable: {
1786
- thread_id: 'durable-thread',
1787
- checkpoint_id: 'shared-fork',
1788
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-a',
1789
- },
1790
- };
1791
- const settled: SettledSubagentToolOutput = {
1792
- output: new ToolMessage({
1793
- content: 'settled once',
1794
- name: call.name,
1795
- tool_call_id: parentToolCallId,
1796
- }),
1797
- additionalContexts: [],
1798
- resolvedArgs: call.args,
1799
- };
1800
- const persistCheckpoint = jest.spyOn(checkpointer, 'put');
1801
-
1802
- await Promise.all([
1803
- executor.persistSettledToolOutput(call, runnableConfig, settled),
1804
- executor.persistSettledToolOutput(call, runnableConfig, settled),
1805
- ]);
1806
-
1807
- expect(persistCheckpoint).toHaveBeenCalledTimes(1);
1808
- const readCheckpoint = jest.spyOn(checkpointer, 'getTuple');
1809
- readCheckpoint.mockClear();
1810
- await expect(
1811
- executor.getSettledToolOutput(call, runnableConfig)
1812
- ).resolves.toMatchObject({
1813
- output: { content: 'settled once' },
1814
- resolvedArgs: call.args,
1815
- });
1816
- expect(readCheckpoint).not.toHaveBeenCalled();
1817
- });
1818
-
1819
- it('rejects mismatched settled args before they can win an execution race', async () => {
1820
- let releaseResolver = (): void => undefined;
1821
- const resolverGate = new Promise<void>((resolve) => {
1822
- releaseResolver = resolve;
1823
- });
1824
- const config = makeLazyConfig('researcher', async () => {
1825
- await resolverGate;
1826
- return makeAgent();
1827
- });
1828
- const checkpointer = new MemorySaver();
1829
- const updateState = jest.fn(async () => ({}));
1830
- const executor = createExecutor([config], {
1831
- checkpointer,
1832
- humanInTheLoop: { enabled: true },
1833
- createChildGraph: () =>
1834
- ({
1835
- createWorkflow: () => ({
1836
- getState: jest.fn(async () => ({
1837
- values: { messages: [new AIMessage('original result')] },
1838
- next: [],
1839
- tasks: [],
1840
- })),
1841
- invoke: jest.fn(),
1842
- updateState,
1843
- }),
1844
- clearHeavyState: jest.fn(),
1845
- }) as unknown as StandardGraph,
1846
- });
1847
- const parentToolCallId = 'call_settlement_race';
1848
- const call = {
1849
- id: parentToolCallId,
1850
- name: 'spawn_subagent',
1851
- args: {
1852
- description: 'Run the original request.',
1853
- subagent_type: config.type,
1854
- },
1855
- type: 'tool_call' as const,
1856
- };
1857
- const parentConfigurable = {
1858
- thread_id: 'durable-thread',
1859
- checkpoint_id: 'shared-fork',
1860
- [SUBAGENT_RESUME_ATTEMPT_CONFIG_KEY]: 'attempt-a',
1861
- };
1862
- const runnableConfig = { configurable: parentConfigurable };
1863
- const execution = executor.execute({
1864
- description: call.args.description,
1865
- subagentType: config.type,
1866
- threadId: parentConfigurable.thread_id,
1867
- parentToolCallId,
1868
- parentConfigurable,
1869
- });
1870
- const mismatched: SettledSubagentToolOutput = {
1871
- output: new ToolMessage({
1872
- content: 'mismatched result',
1873
- name: call.name,
1874
- tool_call_id: parentToolCallId,
1875
- }),
1876
- additionalContexts: [],
1877
- resolvedArgs: {
1878
- ...call.args,
1879
- description: 'Run a changed request.',
1880
- },
1881
- };
1882
-
1883
- await executor.persistSettledToolOutput(call, runnableConfig, mismatched);
1884
- expect(updateState).not.toHaveBeenCalled();
1885
- releaseResolver();
1886
- await expect(execution).resolves.toMatchObject({
1887
- content: 'original result',
1888
- });
1889
- const accepted: SettledSubagentToolOutput = {
1890
- output: new ToolMessage({
1891
- content: 'original result',
1892
- name: call.name,
1893
- tool_call_id: parentToolCallId,
1894
- }),
1895
- additionalContexts: [],
1896
- resolvedArgs: call.args,
1897
- };
1898
- await executor.persistSettledToolOutput(call, runnableConfig, accepted);
1899
-
1900
- await expect(
1901
- executor.getSettledToolOutput(call, runnableConfig)
1902
- ).resolves.toMatchObject({ output: { content: 'original result' } });
1903
- });
1904
-
1905
- it('sanitizes private runtime state before calling the resolver', async () => {
1906
- let configurable: Readonly<SubagentResolveConfigurable> | undefined;
1907
- const config = makeLazyConfig('researcher', async (context) => {
1908
- configurable = context.configurable;
1909
- return makeAgent();
1910
- });
1911
- const executor = createExecutor([config]);
1912
-
1913
- await executor.execute({
1914
- description: 'Resolve safely.',
1915
- subagentType: config.type,
1916
- parentToolCallId: 'call_sanitized',
1917
- parentConfigurable: {
1918
- requestBody: { messageId: 'allowed-message' },
1919
- user: {
1920
- id: 'allowed-user',
1921
- role: 'member',
1922
- tenantId: 'tenant-1',
1923
- privateToken: 'principal-secret',
1924
- },
1925
- user_id: 'allowed-user',
1926
- userMCPAuthMap: { private: { token: 'mcp-secret' } },
1927
- __pregel_scratchpad: { secret: 'pregel-secret' },
1928
- checkpoint_id: 'checkpoint-secret',
1929
- [SUBAGENT_PARENT_BATCH_CONFIG_KEY]: 'batch-secret',
1930
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: { secret: 'resume-secret' },
1931
- [TOOL_APPROVAL_EXECUTION_SCOPE_CONFIG_KEY]: 'approval-secret',
1932
- [RUN_BREAKER_SCOPE_CONFIG_KEY]: { secret: 'breaker-secret' },
1933
- },
1934
- });
1935
-
1936
- expect(configurable).toEqual({
1937
- requestBody: { messageId: 'allowed-message' },
1938
- user: {
1939
- id: 'allowed-user',
1940
- role: 'member',
1941
- tenantId: 'tenant-1',
1942
- },
1943
- user_id: 'allowed-user',
1944
- });
1945
- expect(JSON.stringify(configurable)).not.toContain('secret');
1946
- });
1947
-
1948
- it('uses a per-call abort signal while awaiting a non-cooperative resolver', async () => {
1949
- const callAbort = new AbortController();
1950
- let observedContext: SubagentResolveContext | undefined;
1951
- let markResolutionStarted = (): void => undefined;
1952
- const resolutionStarted = new Promise<void>((resolve) => {
1953
- markResolutionStarted = resolve;
1954
- });
1955
- const config = makeLazyConfig('researcher', async (context) => {
1956
- observedContext = context;
1957
- markResolutionStarted();
1958
- return new Promise<AgentInputs>(() => undefined);
1959
- });
1960
- const executor = createExecutor([config]);
1961
-
1962
- const execution = executor.execute({
1963
- description: 'Cancel this selection.',
1964
- subagentType: config.type,
1965
- parentToolCallId: 'call_cancel',
1966
- signal: callAbort.signal,
1967
- });
1968
- await resolutionStarted;
1969
- callAbort.abort(new Error('private cancellation reason'));
1970
- const result = await execution;
1971
-
1972
- expect(observedContext?.signal.aborted).toBe(true);
1973
- expect(result.content).toBe(
1974
- 'Subagent error: Unable to initialize the selected subagent.'
1975
- );
1976
- expect(result.content).not.toContain('private cancellation reason');
1977
- });
1978
-
1979
- it('rethrows a stream-limit trip while awaiting lazy resolution', async () => {
1980
- const breaker = new AbortController();
1981
- let markResolutionStarted = (): void => undefined;
1982
- const resolutionStarted = new Promise<void>((resolve) => {
1983
- markResolutionStarted = resolve;
1984
- });
1985
- const config = makeLazyConfig('researcher', async () => {
1986
- markResolutionStarted();
1987
- return new Promise<AgentInputs>(() => undefined);
1988
- });
1989
- const executor = createExecutor([config]);
1990
- const trip = new StreamLimitExceededError({
1991
- kind: 'tool_call_args',
1992
- limit: 10,
1993
- observed: 11,
1994
- toolName: 'db_query',
1995
- });
1996
-
1997
- const execution = executor.execute({
1998
- description: 'Stop this selection.',
1999
- subagentType: config.type,
2000
- parentToolCallId: 'call_limit',
2001
- breaker,
2002
- });
2003
- await resolutionStarted;
2004
- breaker.abort(trip);
2005
-
2006
- await expect(execution).rejects.toBe(trip);
2007
- });
2008
-
2009
- it('prefers eager agent inputs without calling an attached resolver', async () => {
2010
- const resolveAgentInputs = jest.fn(async () => makeAgent('unexpected'));
2011
- const eagerInputs = makeAgent('eager-child');
2012
- const eagerConfig: ResolvedSubagentConfig = {
2013
- type: 'eager',
2014
- name: 'Eager Worker',
2015
- description: 'Already has complete inputs.',
2016
- configId: 'eager@v1',
2017
- agentInputs: eagerInputs,
2018
- resolveAgentInputs,
2019
- };
2020
- let observedInput: StandardGraphInput | undefined;
2021
- const executor = createExecutor([eagerConfig], {
2022
- createChildGraph: (input): StandardGraph => {
2023
- observedInput = input;
2024
- return makeGraph();
2025
- },
2026
- });
2027
-
2028
- await executor.execute({
2029
- description: 'Use eager inputs.',
2030
- subagentType: eagerConfig.type,
2031
- parentToolCallId: 'call_eager',
2032
- });
2033
-
2034
- expect(resolveAgentInputs).not.toHaveBeenCalled();
2035
- expect(observedInput?.agents[0].agentId).toBe(eagerInputs.agentId);
2036
- });
2037
-
2038
- it('accepts a legacy type-less resume for a versioned eager config', async () => {
2039
- const configId = 'eager@v1';
2040
- const eagerConfig: ResolvedSubagentConfig = {
2041
- type: 'eager',
2042
- name: 'Eager Worker',
2043
- description: 'Already has complete inputs.',
2044
- configId,
2045
- agentInputs: makeAgent('eager-child'),
2046
- };
2047
- const parentToolCallId = 'call_legacy_eager';
2048
- const resumeExecution = makeResumeExecution(parentToolCallId, configId);
2049
- delete resumeExecution.subagentType;
2050
- const executor = createExecutor([eagerConfig], {
2051
- checkpointer: new MemorySaver(),
2052
- humanInTheLoop: { enabled: true },
2053
- createChildGraph: makeRecoveredGraph,
2054
- });
2055
- const forkTarget = executor as unknown as {
2056
- forkCheckpointSnapshot: () => Promise<void>;
2057
- };
2058
- jest
2059
- .spyOn(forkTarget, 'forkCheckpointSnapshot')
2060
- .mockResolvedValue(undefined);
2061
-
2062
- const result = await executor.execute({
2063
- description: 'Resume eager inputs.',
2064
- subagentType: eagerConfig.type,
2065
- threadId: 'durable-thread',
2066
- parentToolCallId,
2067
- parentConfigurable: {
2068
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2069
- version: 1,
2070
- executions: [resumeExecution],
2071
- },
2072
- },
2073
- });
2074
-
2075
- expect(result.content).toBe('persisted result');
2076
- });
2077
-
2078
- it('preserves nested lazy descriptors and decrements the child depth', async () => {
2079
- const nestedDescriptor = makeLazyConfig('grandchild', async () =>
2080
- makeAgent('grandchild')
2081
- );
2082
- const config = makeLazyConfig(
2083
- 'nested',
2084
- async () => ({
2085
- ...makeAgent('nested-child'),
2086
- subagentConfigs: [nestedDescriptor],
2087
- maxSubagentDepth: 3,
2088
- }),
2089
- { allowNested: true }
2090
- );
2091
- let observedInput: StandardGraphInput | undefined;
2092
- const executor = createExecutor([config], {
2093
- maxDepth: 3,
2094
- createChildGraph: (input): StandardGraph => {
2095
- observedInput = input;
2096
- return makeGraph();
2097
- },
2098
- });
2099
-
2100
- await executor.execute({
2101
- description: 'Delegate recursively.',
2102
- subagentType: config.type,
2103
- parentToolCallId: 'call_nested',
2104
- });
2105
-
2106
- expect(observedInput?.agents[0].maxSubagentDepth).toBe(2);
2107
- expect(observedInput?.agents[0].subagentConfigs).toEqual([
2108
- nestedDescriptor,
2109
- ]);
2110
- });
2111
-
2112
- it('keeps resolver identity stable after executor reconstruction', async () => {
2113
- const contexts: SubagentResolveContext[] = [];
2114
- const config = makeLazyConfig('researcher', async (context) => {
2115
- contexts.push(context);
2116
- return makeAgent();
2117
- });
2118
- const makeRebuiltExecutor = (): SubagentExecutor =>
2119
- createExecutor([config], {
2120
- parentRunId: 'rebuilt-parent-run',
2121
- humanInTheLoop: { enabled: true },
2122
- createChildGraph: makeRecoveredGraph,
2123
- });
2124
- const params = {
2125
- description: 'Recover the deterministic child.',
2126
- subagentType: config.type,
2127
- threadId: 'durable-thread',
2128
- parentToolCallId: 'call_rebuild',
2129
- };
2130
-
2131
- await makeRebuiltExecutor().execute(params);
2132
- await makeRebuiltExecutor().execute(params);
2133
-
2134
- expect(contexts).toHaveLength(2);
2135
- expect(contexts[0].executionId).toBe(contexts[1].executionId);
2136
- expect(contexts[0].descriptor).toEqual(contexts[1].descriptor);
2137
- expect(contexts[0].descriptor.configId).toBe('researcher@v1');
2138
- expect(contexts[0].threadId).toBe('durable-thread');
2139
- expect(contexts[0].parentToolCallId).toBe('call_rebuild');
2140
- });
2141
-
2142
- it('keeps the persisted executionId when resuming a lazy child', async () => {
2143
- let resolverContext: SubagentResolveContext | undefined;
2144
- const config = makeLazyConfig('researcher', async (context) => {
2145
- resolverContext = context;
2146
- return makeAgent();
2147
- });
2148
- const parentToolCallId = 'call_persisted_identity';
2149
- const resumeExecution = makeResumeExecution(
2150
- parentToolCallId,
2151
- config.configId
2152
- );
2153
- resumeExecution.childRunId = 'persisted-execution-id';
2154
- const executor = createExecutor([config], {
2155
- checkpointer: new MemorySaver(),
2156
- humanInTheLoop: { enabled: true },
2157
- createChildGraph: makeRecoveredGraph,
2158
- });
2159
- const forkTarget = executor as unknown as {
2160
- forkCheckpointSnapshot: () => Promise<void>;
2161
- };
2162
- jest
2163
- .spyOn(forkTarget, 'forkCheckpointSnapshot')
2164
- .mockResolvedValue(undefined);
2165
-
2166
- await executor.execute({
2167
- description: 'Resume the selected child.',
2168
- subagentType: config.type,
2169
- threadId: 'durable-thread',
2170
- parentToolCallId,
2171
- parentConfigurable: {
2172
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2173
- version: 1,
2174
- executions: [resumeExecution],
2175
- },
2176
- },
2177
- });
2178
-
2179
- expect(resolverContext?.executionId).toBe('persisted-execution-id');
2180
- });
2181
-
2182
- it('rejects a changed configId before restoring or recording resume state', async () => {
2183
- const resolver = jest.fn(async () => makeAgent());
2184
- const config = makeLazyConfig('researcher', resolver, {
2185
- configId: 'researcher@v2',
2186
- });
2187
- const parentRunId = 'rebuilt-parent-run';
2188
- const parentAgentId = 'parent-agent';
2189
- const threadId = 'durable-thread';
2190
- const parentToolCallId = 'call_rebuild';
2191
- const branchThreadId = `subagent:${Buffer.from(
2192
- JSON.stringify([
2193
- threadId,
2194
- 'root',
2195
- parentAgentId,
2196
- parentToolCallId,
2197
- 'batch',
2198
- parentRunId,
2199
- ])
2200
- ).toString('base64url')}`;
2201
- const executor = createExecutor([config], {
2202
- parentRunId,
2203
- parentAgentId,
2204
- checkpointer: new MemorySaver(),
2205
- humanInTheLoop: { enabled: true },
2206
- });
2207
-
2208
- const result = await executor.execute({
2209
- description: 'Resume the original child.',
2210
- subagentType: config.type,
2211
- threadId,
2212
- parentToolCallId,
2213
- parentConfigurable: {
2214
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2215
- version: 1,
2216
- executions: [
2217
- {
2218
- parentToolCallId,
2219
- childRunId: 'original-child-run',
2220
- configId: 'researcher@v1',
2221
- approvalExecutionScope: 'original-approval-scope',
2222
- checkpoints: [
2223
- {
2224
- threadId: branchThreadId,
2225
- checkpointId: 'original-checkpoint',
2226
- checkpointNs: '',
2227
- },
2228
- ],
2229
- graphState: {
2230
- toolCallSteps: [],
2231
- toolSessions: [],
2232
- toolNodes: [],
2233
- eagerToolUsage: [],
2234
- eagerToolSuppressions: [],
2235
- },
2236
- approvalReplays: [],
2237
- },
2238
- ],
2239
- },
2240
- },
2241
- });
2242
-
2243
- expect(result.content).toBe(
2244
- 'Subagent error: Subagent configuration changed since this execution was paused.'
2245
- );
2246
- expect(result.content).not.toContain('researcher@v1');
2247
- expect(result.content).not.toContain('researcher@v2');
2248
- expect(resolver).not.toHaveBeenCalled();
2249
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
2250
- await expect(
2251
- executor.getResumeManifest(new Set([parentToolCallId]))
2252
- ).resolves.toBeUndefined();
2253
- });
2254
-
2255
- it('rejects a versionless resume manifest for a lazy config', async () => {
2256
- const resolver = jest.fn(async () => makeAgent());
2257
- const config = makeLazyConfig('researcher', resolver);
2258
- const parentToolCallId = 'call_versionless_resume';
2259
- const resumeExecution = makeResumeExecution(
2260
- parentToolCallId,
2261
- config.configId
2262
- );
2263
- delete resumeExecution.configId;
2264
- const executor = createExecutor([config], {
2265
- checkpointer: new MemorySaver(),
2266
- humanInTheLoop: { enabled: true },
2267
- });
2268
-
2269
- const result = await executor.execute({
2270
- description: 'Resume the selected child.',
2271
- subagentType: config.type,
2272
- threadId: 'durable-thread',
2273
- parentToolCallId,
2274
- parentConfigurable: {
2275
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2276
- version: 1,
2277
- executions: [resumeExecution],
2278
- },
2279
- },
2280
- });
2281
-
2282
- expect(result.content).toBe(
2283
- 'Subagent error: Subagent configuration changed since this execution was paused.'
2284
- );
2285
- expect(resolver).not.toHaveBeenCalled();
2286
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
2287
- await expect(
2288
- executor.getResumeManifest(new Set([parentToolCallId]))
2289
- ).resolves.toBeUndefined();
2290
- });
2291
-
2292
- it('rejects a lazy resume without manifest or checkpoint type evidence', async () => {
2293
- const resolver = jest.fn(async () => makeAgent());
2294
- const config = makeLazyConfig('researcher', resolver);
2295
- const parentToolCallId = 'call_unbound_lazy_resume';
2296
- const resumeExecution = makeResumeExecution(
2297
- parentToolCallId,
2298
- config.configId
2299
- );
2300
- delete resumeExecution.subagentType;
2301
- const executor = createExecutor([config], {
2302
- checkpointer: new MemorySaver(),
2303
- humanInTheLoop: { enabled: true },
2304
- });
2305
-
2306
- const result = await executor.execute({
2307
- description: 'Resume the selected child.',
2308
- subagentType: config.type,
2309
- threadId: 'durable-thread',
2310
- parentToolCallId,
2311
- parentConfigurable: {
2312
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2313
- version: 1,
2314
- executions: [resumeExecution],
2315
- },
2316
- },
2317
- });
2318
-
2319
- expect(result.content).toBe(
2320
- 'Subagent error: Subagent configuration changed since this execution was paused.'
2321
- );
2322
- expect(resolver).not.toHaveBeenCalled();
2323
- });
2324
-
2325
- it('rejects a resume manifest bound to another type with the same configId', async () => {
2326
- const resolver = jest.fn(async () => makeAgent('coder'));
2327
- const config = makeLazyConfig('coder', resolver, {
2328
- configId: 'shared@v1',
2329
- });
2330
- const parentToolCallId = 'call_cross_type_resume';
2331
- const resumeExecution = makeResumeExecution(
2332
- parentToolCallId,
2333
- 'shared@v1',
2334
- 'resume-source-thread',
2335
- 'researcher'
2336
- );
2337
- const executor = createExecutor([config], {
2338
- checkpointer: new MemorySaver(),
2339
- humanInTheLoop: { enabled: true },
2340
- });
2341
-
2342
- const result = await executor.execute({
2343
- description: 'Resume the selected child.',
2344
- subagentType: config.type,
2345
- threadId: 'durable-thread',
2346
- parentToolCallId,
2347
- parentConfigurable: {
2348
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2349
- version: 1,
2350
- executions: [resumeExecution],
2351
- },
2352
- },
2353
- });
2354
-
2355
- expect(result.content).toBe(
2356
- 'Subagent error: Subagent configuration changed since this execution was paused.'
2357
- );
2358
- expect(resolver).not.toHaveBeenCalled();
2359
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
2360
- await expect(
2361
- executor.getResumeManifest(new Set([parentToolCallId]))
2362
- ).resolves.toBeUndefined();
2363
- });
2364
-
2365
- it('rejects replay lifecycle revision mismatches before restoring state', async () => {
2366
- const config = makeLazyConfig('researcher', async () => makeAgent(), {
2367
- configId: 'researcher@v2',
2368
- });
2369
- const executor = createExecutor([config], {
2370
- checkpointer: new MemorySaver(),
2371
- humanInTheLoop: { enabled: true },
2372
- });
2373
- const parentToolCallId = 'call_lifecycle_mismatch';
2374
- const call = {
2375
- id: parentToolCallId,
2376
- name: 'spawn_subagent',
2377
- args: {
2378
- description: 'Resume the selected child.',
2379
- subagent_type: config.type,
2380
- },
2381
- type: 'tool_call' as const,
2382
- };
2383
- const runnableConfig = {
2384
- configurable: {
2385
- thread_id: 'durable-thread',
2386
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2387
- version: 1 as const,
2388
- executions: [makeResumeExecution(parentToolCallId, 'researcher@v1')],
2389
- },
2390
- },
2391
- };
2392
-
2393
- await expect(
2394
- executor.getSettledToolOutput(call, runnableConfig)
2395
- ).resolves.toBeUndefined();
2396
- await executor.persistSettledToolOutput(call, runnableConfig, {
2397
- output: new ToolMessage({
2398
- content: 'Do not persist this mismatch.',
2399
- tool_call_id: parentToolCallId,
2400
- }),
2401
- additionalContexts: [],
2402
- });
2403
-
2404
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
2405
- await expect(
2406
- executor.getResumeManifest(new Set([parentToolCallId]))
2407
- ).resolves.toBeUndefined();
2408
- });
2409
-
2410
- it('rejects replay lifecycle type mismatches with the same configId', async () => {
2411
- const config = makeLazyConfig('coder', async () => makeAgent('coder'), {
2412
- configId: 'shared@v1',
2413
- });
2414
- const executor = createExecutor([config], {
2415
- checkpointer: new MemorySaver(),
2416
- humanInTheLoop: { enabled: true },
2417
- });
2418
- const parentToolCallId = 'call_lifecycle_type_mismatch';
2419
- const call = {
2420
- id: parentToolCallId,
2421
- name: 'spawn_subagent',
2422
- args: {
2423
- description: 'Resume the selected child.',
2424
- subagent_type: config.type,
2425
- },
2426
- type: 'tool_call' as const,
2427
- };
2428
- const runnableConfig = {
2429
- configurable: {
2430
- thread_id: 'durable-thread',
2431
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2432
- version: 1 as const,
2433
- executions: [
2434
- makeResumeExecution(
2435
- parentToolCallId,
2436
- 'shared@v1',
2437
- 'resume-source-thread',
2438
- 'researcher'
2439
- ),
2440
- ],
2441
- },
2442
- },
2443
- };
2444
-
2445
- await expect(
2446
- executor.getSettledToolOutput(call, runnableConfig)
2447
- ).resolves.toBeUndefined();
2448
- await executor.persistSettledToolOutput(call, runnableConfig, {
2449
- output: new ToolMessage({
2450
- content: 'Do not persist this mismatch.',
2451
- tool_call_id: parentToolCallId,
2452
- }),
2453
- additionalContexts: [],
2454
- resolvedArgs: {
2455
- description: 'Run the coder.',
2456
- subagent_type: config.type,
2457
- },
2458
- });
2459
-
2460
- expect(executor.getChildCheckpointThreadIds()).toEqual([]);
2461
- await expect(
2462
- executor.getResumeManifest(new Set([parentToolCallId]))
2463
- ).resolves.toBeUndefined();
2464
- });
2465
-
2466
- it('retains configId in regenerated manifests after resolver failure', async () => {
2467
- const config = makeLazyConfig('researcher', async () => {
2468
- throw new Error('transient resolver failure');
2469
- });
2470
- const executor = createExecutor([config], {
2471
- checkpointer: new MemorySaver(),
2472
- humanInTheLoop: { enabled: true },
2473
- });
2474
- const parentToolCallId = 'call_resolver_retry';
2475
- const branchThreadId = `subagent:${Buffer.from(
2476
- JSON.stringify([
2477
- 'durable-thread',
2478
- 'root',
2479
- 'parent-agent',
2480
- parentToolCallId,
2481
- 'batch',
2482
- 'parent-run',
2483
- ])
2484
- ).toString('base64url')}`;
2485
- const resumeExecution = makeResumeExecution(
2486
- parentToolCallId,
2487
- config.configId,
2488
- branchThreadId
2489
- );
2490
- const result = await executor.execute({
2491
- description: 'Resume the selected child.',
2492
- subagentType: config.type,
2493
- threadId: 'durable-thread',
2494
- parentToolCallId,
2495
- parentConfigurable: {
2496
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: {
2497
- version: 1,
2498
- executions: [resumeExecution],
2499
- },
2500
- },
2501
- });
2502
- const checkpointTarget = executor as unknown as {
2503
- getLatestCheckpointSnapshot: (
2504
- threadId: string
2505
- ) => Promise<SubagentResumeExecution['checkpoints']>;
2506
- };
2507
- jest
2508
- .spyOn(checkpointTarget, 'getLatestCheckpointSnapshot')
2509
- .mockResolvedValue([
2510
- {
2511
- threadId: 'restored-child-thread',
2512
- checkpointId: 'restored-checkpoint',
2513
- checkpointNs: '',
2514
- },
2515
- ]);
2516
- const manifest = await executor.getResumeManifest(
2517
- new Set([parentToolCallId])
2518
- );
2519
-
2520
- expect(result.content).toBe(
2521
- 'Subagent error: Unable to initialize the selected subagent.'
2522
- );
2523
- expect(manifest?.executions).toHaveLength(1);
2524
- expect(manifest?.executions[0].configId).toBe(config.configId);
2525
- });
2526
-
2527
- it('retains configId through fresh lifecycle persistence after resolver failure', async () => {
2528
- const config = makeLazyConfig('researcher', async () => {
2529
- throw new Error('transient resolver failure');
2530
- });
2531
- const executor = createExecutor([config], {
2532
- checkpointer: new MemorySaver(),
2533
- humanInTheLoop: { enabled: true },
2534
- });
2535
- const parentToolCallId = 'call_fresh_lifecycle_retry';
2536
- const call = {
2537
- id: parentToolCallId,
2538
- name: 'spawn_subagent',
2539
- args: {
2540
- description: 'Start the selected child.',
2541
- subagent_type: config.type,
2542
- },
2543
- type: 'tool_call' as const,
2544
- };
2545
- const parentConfigurable = {
2546
- thread_id: 'durable-thread',
2547
- checkpoint_id: 'parent-checkpoint',
2548
- run_id: 'parent-run',
2549
- };
2550
- const runnableConfig = { configurable: parentConfigurable };
2551
-
2552
- await expect(
2553
- executor.getSettledToolOutput(call, runnableConfig)
2554
- ).resolves.toBeUndefined();
2555
- const result = await executor.execute({
2556
- description: 'Start the selected child.',
2557
- subagentType: config.type,
2558
- threadId: parentConfigurable.thread_id,
2559
- parentToolCallId,
2560
- parentConfigurable,
2561
- });
2562
- await executor.persistSettledToolOutput(call, runnableConfig, {
2563
- output: new ToolMessage({
2564
- content: result.content,
2565
- name: call.name,
2566
- tool_call_id: parentToolCallId,
2567
- }),
2568
- additionalContexts: [],
2569
- });
2570
- const manifest = await executor.getResumeManifest(
2571
- new Set([parentToolCallId])
2572
- );
2573
-
2574
- expect(result.content).toBe(
2575
- 'Subagent error: Unable to initialize the selected subagent.'
2576
- );
2577
- expect(manifest?.executions).toHaveLength(1);
2578
- expect(manifest?.executions[0].configId).toBe(config.configId);
2579
- });
2580
-
2581
- it('persists a bound default description when lifecycle args omit it', async () => {
2582
- const checkpointer = new MemorySaver();
2583
- const resolver = jest.fn(async () => {
2584
- throw new Error('transient resolver failure');
2585
- });
2586
- const config = makeLazyConfig('researcher', resolver);
2587
- const source = createExecutor([config], {
2588
- checkpointer,
2589
- humanInTheLoop: { enabled: true },
2590
- });
2591
- const parentToolCallId = 'call_defaulted_description';
2592
- const call = {
2593
- id: parentToolCallId,
2594
- name: 'spawn_subagent',
2595
- args: {
2596
- description: 'Original description before PreToolUse.',
2597
- subagent_type: config.type,
2598
- },
2599
- type: 'tool_call' as const,
2600
- };
2601
- const parentConfigurable = {
2602
- thread_id: 'durable-thread',
2603
- checkpoint_id: 'parent-checkpoint',
2604
- run_id: 'parent-run',
2605
- };
2606
- const runnableConfig = { configurable: parentConfigurable };
2607
- const effectiveDescription = 'No task description provided';
2608
- const result = await source.execute({
2609
- description: effectiveDescription,
2610
- subagentType: config.type,
2611
- threadId: parentConfigurable.thread_id,
2612
- parentToolCallId,
2613
- parentConfigurable,
2614
- });
2615
- const settled: SettledSubagentToolOutput = {
2616
- output: new ToolMessage({
2617
- content: result.content,
2618
- name: call.name,
2619
- tool_call_id: parentToolCallId,
2620
- }),
2621
- additionalContexts: [],
2622
- resolvedArgs: {
2623
- description: ' ',
2624
- subagent_type: config.type,
2625
- },
2626
- };
2627
- const persistCheckpoint = jest.spyOn(checkpointer, 'put');
2628
-
2629
- await Promise.all([
2630
- source.persistSettledToolOutput(call, runnableConfig, settled),
2631
- source.persistSettledToolOutput(call, runnableConfig, settled),
2632
- ]);
2633
- const manifest = await source.getResumeManifest(
2634
- new Set([parentToolCallId])
2635
- );
2636
- if (manifest == null) {
2637
- throw new Error('Expected the defaulted invocation to persist.');
2638
- }
2639
- expect(persistCheckpoint).toHaveBeenCalledTimes(1);
2640
- const rebuilt = createExecutor([config], {
2641
- checkpointer,
2642
- humanInTheLoop: { enabled: true },
2643
- });
2644
- const replayed = await rebuilt.getSettledToolOutput(call, {
2645
- configurable: {
2646
- ...parentConfigurable,
2647
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: manifest,
2648
- },
2649
- });
2650
-
2651
- expect(replayed?.output.content).toBe(result.content);
2652
- expect(resolver).toHaveBeenCalledTimes(1);
2653
- });
2654
-
2655
- it('replays a terminal hook result whose description was normalized away', async () => {
2656
- const checkpointer = new MemorySaver();
2657
- const resolver = jest.fn(async () => makeAgent());
2658
- const config = makeLazyConfig('researcher', resolver);
2659
- const source = createExecutor([config], {
2660
- checkpointer,
2661
- humanInTheLoop: { enabled: true },
2662
- });
2663
- const parentToolCallId = 'call_terminal_default_description';
2664
- const call = {
2665
- id: parentToolCallId,
2666
- name: 'spawn_subagent',
2667
- args: {
2668
- description: ' ',
2669
- subagent_type: config.type,
2670
- },
2671
- type: 'tool_call' as const,
2672
- };
2673
- const parentConfigurable = {
2674
- thread_id: 'durable-thread',
2675
- checkpoint_id: 'parent-checkpoint',
2676
- run_id: 'parent-run',
2677
- };
2678
- const runnableConfig = { configurable: parentConfigurable };
2679
- const terminalHook = jest.fn(
2680
- async (): Promise<SettledSubagentToolOutput> => ({
2681
- output: new ToolMessage({
2682
- content: 'Blocked before execution.',
2683
- name: call.name,
2684
- status: 'error',
2685
- tool_call_id: parentToolCallId,
2686
- }),
2687
- additionalContexts: [],
2688
- resolvedArgs: { subagent_type: config.type },
2689
- })
2690
- );
2691
- const runLifecycle = async (
2692
- executor: SubagentExecutor,
2693
- currentConfig: typeof runnableConfig
2694
- ): Promise<SettledSubagentToolOutput> => {
2695
- const replayed = await executor.getSettledToolOutput(call, currentConfig);
2696
- if (replayed != null) {
2697
- return replayed;
2698
- }
2699
- const settled = await terminalHook();
2700
- await executor.persistSettledToolOutput(call, currentConfig, settled);
2701
- return settled;
2702
- };
2703
-
2704
- await expect(runLifecycle(source, runnableConfig)).resolves.toMatchObject({
2705
- output: { content: 'Blocked before execution.' },
2706
- });
2707
- const manifest = await source.getResumeManifest(
2708
- new Set([parentToolCallId])
2709
- );
2710
- if (manifest == null) {
2711
- throw new Error('Expected the terminal hook result to persist.');
2712
- }
2713
- const rebuilt = createExecutor([config], {
2714
- checkpointer,
2715
- humanInTheLoop: { enabled: true },
2716
- });
2717
- const replayConfig = {
2718
- configurable: {
2719
- ...parentConfigurable,
2720
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: manifest,
2721
- },
2722
- };
2723
- const replayed = await runLifecycle(rebuilt, replayConfig);
2724
-
2725
- expect(replayed.output.content).toBe('Blocked before execution.');
2726
- expect(replayed.resolvedArgs).toEqual({
2727
- subagent_type: config.type,
2728
- });
2729
- expect(terminalHook).toHaveBeenCalledTimes(1);
2730
- expect(resolver).not.toHaveBeenCalled();
2731
- });
2732
-
2733
- it('persists the configId for the effective rewritten subagent type', async () => {
2734
- const researcher = makeLazyConfig('researcher', async () =>
2735
- makeAgent('researcher')
2736
- );
2737
- const coder = makeLazyConfig('coder', async () => {
2738
- throw new Error('transient coder resolver failure');
2739
- });
2740
- const executor = createExecutor([researcher, coder], {
2741
- checkpointer: new MemorySaver(),
2742
- humanInTheLoop: { enabled: true },
2743
- });
2744
- const parentToolCallId = 'call_rewritten_type';
2745
- const call = {
2746
- id: parentToolCallId,
2747
- name: 'spawn_subagent',
2748
- args: {
2749
- description: 'Start the original child.',
2750
- subagent_type: researcher.type,
2751
- },
2752
- type: 'tool_call' as const,
2753
- };
2754
- const parentConfigurable = {
2755
- thread_id: 'durable-thread',
2756
- checkpoint_id: 'parent-checkpoint',
2757
- run_id: 'parent-run',
2758
- };
2759
- const runnableConfig = { configurable: parentConfigurable };
2760
-
2761
- await expect(
2762
- executor.getSettledToolOutput(call, runnableConfig)
2763
- ).resolves.toBeUndefined();
2764
- const result = await executor.execute({
2765
- description: 'Run the rewritten child.',
2766
- subagentType: coder.type,
2767
- threadId: parentConfigurable.thread_id,
2768
- parentToolCallId,
2769
- parentConfigurable,
2770
- });
2771
- await executor.persistSettledToolOutput(call, runnableConfig, {
2772
- output: new ToolMessage({
2773
- content: result.content,
2774
- name: call.name,
2775
- tool_call_id: parentToolCallId,
2776
- }),
2777
- additionalContexts: [],
2778
- resolvedArgs: {
2779
- description: 'Run the rewritten child.',
2780
- subagent_type: coder.type,
2781
- },
2782
- });
2783
- const manifest = await executor.getResumeManifest(
2784
- new Set([parentToolCallId])
2785
- );
2786
-
2787
- expect(result.content).toBe(
2788
- 'Subagent error: Unable to initialize the selected subagent.'
2789
- );
2790
- expect(manifest?.executions).toHaveLength(1);
2791
- expect(manifest?.executions[0].subagentType).toBe(coder.type);
2792
- expect(manifest?.executions[0].configId).toBe(coder.configId);
2793
- });
2794
-
2795
- it('replays a persisted effective type before rerunning lifecycle hooks', async () => {
2796
- const checkpointer = new MemorySaver();
2797
- const researcher = makeLazyConfig('researcher', async () =>
2798
- makeAgent('researcher')
2799
- );
2800
- const coder = makeLazyConfig('coder', async () => makeAgent('coder'));
2801
- const source = createExecutor([researcher, coder], {
2802
- checkpointer,
2803
- humanInTheLoop: { enabled: true },
2804
- });
2805
- const parentToolCallId = 'call_effective_replay';
2806
- const call = {
2807
- id: parentToolCallId,
2808
- name: 'spawn_subagent',
2809
- args: {
2810
- description: 'Start the original child.',
2811
- subagent_type: researcher.type,
2812
- },
2813
- type: 'tool_call' as const,
2814
- };
2815
- const parentConfigurable = {
2816
- thread_id: 'durable-thread',
2817
- checkpoint_id: 'parent-checkpoint',
2818
- run_id: 'parent-run',
2819
- };
2820
- const runnableConfig = { configurable: parentConfigurable };
2821
-
2822
- await expect(
2823
- source.getSettledToolOutput(call, runnableConfig)
2824
- ).resolves.toBeUndefined();
2825
- await source.persistSettledToolOutput(call, runnableConfig, {
2826
- output: new ToolMessage({
2827
- content: 'Coder completed.',
2828
- name: call.name,
2829
- tool_call_id: parentToolCallId,
2830
- }),
2831
- additionalContexts: [],
2832
- resolvedArgs: {
2833
- description: 'Run the rewritten child.',
2834
- subagent_type: coder.type,
2835
- },
2836
- });
2837
- const manifest = await source.getResumeManifest(
2838
- new Set([parentToolCallId])
2839
- );
2840
- expect(manifest?.executions[0].subagentType).toBe(coder.type);
2841
- if (manifest == null) {
2842
- throw new Error('Expected a persisted replay manifest.');
2843
- }
2844
- const mismatchedManifest = {
2845
- ...manifest,
2846
- executions: [
2847
- {
2848
- ...manifest.executions[0],
2849
- subagentType: researcher.type,
2850
- },
2851
- ],
2852
- };
2853
- const rejected = createExecutor([researcher, coder], {
2854
- checkpointer,
2855
- humanInTheLoop: { enabled: true },
2856
- });
2857
- const rejectedForkTarget = rejected as unknown as {
2858
- forkCheckpointSnapshot: () => Promise<void>;
2859
- };
2860
- const forkCheckpointSnapshot = jest
2861
- .spyOn(rejectedForkTarget, 'forkCheckpointSnapshot')
2862
- .mockResolvedValue(undefined);
2863
- await expect(
2864
- rejected.getSettledToolOutput(call, {
2865
- configurable: {
2866
- ...parentConfigurable,
2867
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: mismatchedManifest,
2868
- },
2869
- })
2870
- ).resolves.toBeUndefined();
2871
- expect(forkCheckpointSnapshot).not.toHaveBeenCalled();
2872
- expect(rejected.getChildCheckpointThreadIds()).toEqual([]);
2873
-
2874
- delete manifest.executions[0].subagentType;
2875
-
2876
- const rebuilt = createExecutor([researcher, coder], {
2877
- checkpointer,
2878
- humanInTheLoop: { enabled: true },
2879
- });
2880
- const replayed = await rebuilt.getSettledToolOutput(call, {
2881
- configurable: {
2882
- ...parentConfigurable,
2883
- [SUBAGENT_RESUME_MANIFEST_CONFIG_KEY]: manifest,
2884
- },
2885
- });
2886
-
2887
- expect(replayed?.output.content).toBe('Coder completed.');
2888
- expect(replayed?.resolvedArgs).toMatchObject({
2889
- subagent_type: coder.type,
2890
- });
2891
- });
2892
-
2893
- it('does not retain the original configId after rewriting to an eager config', async () => {
2894
- const original = makeLazyConfig('researcher', async () =>
2895
- makeAgent('researcher')
2896
- );
2897
- const eager: ResolvedSubagentConfig = {
2898
- type: 'eager',
2899
- name: 'Eager worker',
2900
- description: 'Runs from eager inputs.',
2901
- agentInputs: makeAgent('eager-child'),
2902
- };
2903
- const executor = createExecutor([original, eager], {
2904
- checkpointer: new MemorySaver(),
2905
- humanInTheLoop: { enabled: true },
2906
- });
2907
- const parentToolCallId = 'call_rewritten_eager';
2908
- const call = {
2909
- id: parentToolCallId,
2910
- name: 'spawn_subagent',
2911
- args: {
2912
- description: 'Start the original child.',
2913
- subagent_type: original.type,
2914
- },
2915
- type: 'tool_call' as const,
2916
- };
2917
- const runnableConfig = {
2918
- configurable: {
2919
- thread_id: 'durable-thread',
2920
- checkpoint_id: 'parent-checkpoint',
2921
- run_id: 'parent-run',
2922
- },
2923
- };
2924
-
2925
- await expect(
2926
- executor.getSettledToolOutput(call, runnableConfig)
2927
- ).resolves.toBeUndefined();
2928
- await executor.persistSettledToolOutput(call, runnableConfig, {
2929
- output: new ToolMessage({
2930
- content: 'Eager child completed.',
2931
- name: call.name,
2932
- tool_call_id: parentToolCallId,
2933
- }),
2934
- additionalContexts: [],
2935
- resolvedArgs: {
2936
- description: 'Run the eager child.',
2937
- subagent_type: eager.type,
2938
- },
2939
- });
2940
- const manifest = await executor.getResumeManifest(
2941
- new Set([parentToolCallId])
2942
- );
2943
-
2944
- expect(manifest?.executions).toHaveLength(1);
2945
- expect(manifest?.executions[0].subagentType).toBe(eager.type);
2946
- expect(manifest?.executions[0].configId).toBeUndefined();
2947
- });
2948
-
2949
- it('uses the standard factory for lazy agents and the polymorphic factory for graphs', async () => {
2950
- const lazyConfig = makeLazyConfig('researcher', async () =>
2951
- makeAgent('lazy-researcher')
2952
- );
2953
- const graphConfig: GraphSubagentConfig = {
2954
- kind: 'graph',
2955
- type: 'research-team',
2956
- name: 'Research Team',
2957
- description: 'Runs a graph child.',
2958
- agents: [makeAgent('result')],
2959
- edges: [],
2960
- entryAgentId: 'result',
2961
- resultAgentId: 'result',
2962
- };
2963
- const standardInputs: StandardGraphInput[] = [];
2964
- const graphRequests: GraphFactoryRequest[] = [];
2965
- const createChildGraph = jest.fn((input: StandardGraphInput) => {
2966
- standardInputs.push(input);
2967
- return makeGraph({ messages: [new AIMessage('lazy result')] });
2968
- });
2969
- const createChildGraphByKind = jest.fn((request: GraphFactoryRequest) => {
2970
- graphRequests.push(request);
2971
- return makeGraph({
2972
- messages: [new AIMessage('graph result')],
2973
- subagentResult: {
2974
- agentId: 'result',
2975
- message: new AIMessage('graph result'),
2976
- },
2977
- });
2978
- });
2979
- const executor = createExecutor([lazyConfig, graphConfig], {
2980
- createChildGraph,
2981
- createChildGraphByKind,
2982
- });
2983
-
2984
- const lazyResult = await executor.execute({
2985
- description: 'Run one agent.',
2986
- subagentType: lazyConfig.type,
2987
- parentToolCallId: 'call_lazy',
2988
- });
2989
- const graphResult = await executor.execute({
2990
- description: 'Run the team.',
2991
- subagentType: graphConfig.type,
2992
- parentToolCallId: 'call_graph',
2993
- });
2994
-
2995
- expect(lazyResult.content).toBe('lazy result');
2996
- expect(graphResult.content).toBe('graph result');
2997
- expect(createChildGraph).toHaveBeenCalledTimes(1);
2998
- expect(standardInputs[0].agents[0].agentId).toBe('lazy-researcher');
2999
- expect(createChildGraphByKind).toHaveBeenCalledTimes(1);
3000
- expect(graphRequests[0]).toMatchObject({
3001
- kind: 'multi-agent',
3002
- input: {
3003
- agents: [{ agentId: 'result' }],
3004
- resultAgentId: 'result',
3005
- },
3006
- });
3007
- });
3008
-
3009
- it('rejects graph lazy fields during normalization before resolver or factory invocation', async () => {
3010
- const resolveAgentInputs = jest.fn(async () => makeAgent('unexpected'));
3011
- const graphConfig: GraphSubagentConfig = {
3012
- kind: 'graph',
3013
- type: 'invalid-graph',
3014
- name: 'Invalid Graph',
3015
- description: 'Illegally carries lazy single-agent fields.',
3016
- agents: [makeAgent('result')],
3017
- edges: [],
3018
- entryAgentId: 'result',
3019
- resultAgentId: 'result',
3020
- };
3021
- Reflect.set(graphConfig, 'configId', 'invalid-graph@v1');
3022
- Reflect.set(graphConfig, 'resolveAgentInputs', resolveAgentInputs);
3023
- const createChildGraph = jest.fn((_input: StandardGraphInput) =>
3024
- makeGraph()
3025
- );
3026
- const createChildGraphByKind = jest.fn((_request: GraphFactoryRequest) =>
3027
- makeGraph()
3028
- );
3029
- const parentContext = AgentContext.fromConfig(makeAgent('parent'));
3030
-
3031
- const initializeAndExecute = async (): Promise<void> => {
3032
- const configs = normalizeSubagentConfigEntries(
3033
- [graphConfig],
3034
- parentContext
3035
- );
3036
- const executor = createExecutor(configs, {
3037
- createChildGraph,
3038
- createChildGraphByKind,
3039
- });
3040
- await executor.execute({
3041
- description: 'This must never execute.',
3042
- subagentType: graphConfig.type,
3043
- parentToolCallId: 'call_invalid_graph',
3044
- });
3045
- };
3046
-
3047
- await expect(initializeAndExecute()).rejects.toThrow(
3048
- /lazy fields configId\/resolveAgentInputs/
3049
- );
3050
- expect(resolveAgentInputs).not.toHaveBeenCalled();
3051
- expect(createChildGraph).not.toHaveBeenCalled();
3052
- expect(createChildGraphByKind).not.toHaveBeenCalled();
3053
- });
3054
- });