@illuma-ai/agents 1.4.0-alpha.6 → 1.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 (653) hide show
  1. package/README.md +62 -0
  2. package/dist/cjs/agents/AgentContext.cjs +274 -67
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/common/enum.cjs +44 -13
  5. package/dist/cjs/common/enum.cjs.map +1 -1
  6. package/dist/cjs/graphs/Graph.cjs +182 -5
  7. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs +152 -1167
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  10. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +1 -1
  11. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -1
  12. package/dist/cjs/hooks/HookRegistry.cjs +162 -0
  13. package/dist/cjs/hooks/HookRegistry.cjs.map +1 -0
  14. package/dist/cjs/hooks/executeHooks.cjs +276 -0
  15. package/dist/cjs/hooks/executeHooks.cjs.map +1 -0
  16. package/dist/cjs/hooks/matchers.cjs +256 -0
  17. package/dist/cjs/hooks/matchers.cjs.map +1 -0
  18. package/dist/cjs/hooks/types.cjs +27 -0
  19. package/dist/cjs/hooks/types.cjs.map +1 -0
  20. package/dist/cjs/langchain/google-common.cjs +3 -0
  21. package/dist/cjs/langchain/google-common.cjs.map +1 -0
  22. package/dist/cjs/langchain/index.cjs +86 -0
  23. package/dist/cjs/langchain/index.cjs.map +1 -0
  24. package/dist/cjs/langchain/language_models/chat_models.cjs +3 -0
  25. package/dist/cjs/langchain/language_models/chat_models.cjs.map +1 -0
  26. package/dist/cjs/langchain/messages/tool.cjs +3 -0
  27. package/dist/cjs/langchain/messages/tool.cjs.map +1 -0
  28. package/dist/cjs/langchain/messages.cjs +51 -0
  29. package/dist/cjs/langchain/messages.cjs.map +1 -0
  30. package/dist/cjs/langchain/openai.cjs +3 -0
  31. package/dist/cjs/langchain/openai.cjs.map +1 -0
  32. package/dist/cjs/langchain/prompts.cjs +11 -0
  33. package/dist/cjs/langchain/prompts.cjs.map +1 -0
  34. package/dist/cjs/langchain/runnables.cjs +19 -0
  35. package/dist/cjs/langchain/runnables.cjs.map +1 -0
  36. package/dist/cjs/langchain/tools.cjs +23 -0
  37. package/dist/cjs/langchain/tools.cjs.map +1 -0
  38. package/dist/cjs/langchain/utils/env.cjs +11 -0
  39. package/dist/cjs/langchain/utils/env.cjs.map +1 -0
  40. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +5 -1
  41. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  42. package/dist/cjs/llm/bedrock/cacheSupport.cjs +55 -0
  43. package/dist/cjs/llm/bedrock/cacheSupport.cjs.map +1 -0
  44. package/dist/cjs/llm/bedrock/index.cjs +61 -33
  45. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  46. package/dist/cjs/llm/openai/index.cjs +1 -4
  47. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  48. package/dist/cjs/llm/openai/utils/index.cjs +27 -10
  49. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  50. package/dist/cjs/main.cjs +178 -127
  51. package/dist/cjs/main.cjs.map +1 -1
  52. package/dist/cjs/memory/citations.cjs +4 -4
  53. package/dist/cjs/memory/citations.cjs.map +1 -1
  54. package/dist/cjs/memory/constants.cjs +17 -17
  55. package/dist/cjs/memory/constants.cjs.map +1 -1
  56. package/dist/cjs/memory/mmr.cjs +1 -1
  57. package/dist/cjs/memory/mmr.cjs.map +1 -1
  58. package/dist/cjs/memory/paths.cjs +1 -1
  59. package/dist/cjs/memory/paths.cjs.map +1 -1
  60. package/dist/cjs/memory/recallTracking.cjs +3 -3
  61. package/dist/cjs/memory/recallTracking.cjs.map +1 -1
  62. package/dist/cjs/memory/temporalDecay.cjs +2 -2
  63. package/dist/cjs/memory/temporalDecay.cjs.map +1 -1
  64. package/dist/cjs/messages/cache.cjs +89 -0
  65. package/dist/cjs/messages/cache.cjs.map +1 -1
  66. package/dist/cjs/messages/contextPruning.cjs +156 -0
  67. package/dist/cjs/messages/contextPruning.cjs.map +1 -0
  68. package/dist/cjs/messages/contextPruningSettings.cjs +53 -0
  69. package/dist/cjs/messages/contextPruningSettings.cjs.map +1 -0
  70. package/dist/cjs/messages/format.cjs +144 -20
  71. package/dist/cjs/messages/format.cjs.map +1 -1
  72. package/dist/cjs/messages/prune.cjs +505 -4
  73. package/dist/cjs/messages/prune.cjs.map +1 -1
  74. package/dist/cjs/run.cjs +141 -1
  75. package/dist/cjs/run.cjs.map +1 -1
  76. package/dist/cjs/tools/BashExecutor.cjs +235 -0
  77. package/dist/cjs/tools/BashExecutor.cjs.map +1 -0
  78. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +297 -0
  79. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -0
  80. package/dist/cjs/tools/CodeExecutor.cjs +45 -47
  81. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  82. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -11
  83. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  84. package/dist/cjs/tools/ReadFile.cjs +44 -0
  85. package/dist/cjs/tools/ReadFile.cjs.map +1 -0
  86. package/dist/cjs/tools/SkillTool.cjs +51 -0
  87. package/dist/cjs/tools/SkillTool.cjs.map +1 -0
  88. package/dist/cjs/tools/SubagentTool.cjs +93 -0
  89. package/dist/cjs/tools/SubagentTool.cjs.map +1 -0
  90. package/dist/cjs/tools/ToolNode.cjs +450 -24
  91. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  92. package/dist/cjs/tools/memory/memoryAppendTool.cjs +1 -1
  93. package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -1
  94. package/dist/cjs/tools/memory/memoryGetTool.cjs +2 -2
  95. package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -1
  96. package/dist/cjs/tools/memory/memorySearchTool.cjs +3 -3
  97. package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -1
  98. package/dist/cjs/tools/memory/shared.cjs +1 -1
  99. package/dist/cjs/tools/memory/shared.cjs.map +1 -1
  100. package/dist/cjs/tools/search/search.cjs +11 -3
  101. package/dist/cjs/tools/search/search.cjs.map +1 -1
  102. package/dist/cjs/tools/search/tavily-scraper.cjs +189 -0
  103. package/dist/cjs/tools/search/tavily-scraper.cjs.map +1 -0
  104. package/dist/cjs/tools/search/tavily-search.cjs +372 -0
  105. package/dist/cjs/tools/search/tavily-search.cjs.map +1 -0
  106. package/dist/cjs/tools/search/tool.cjs +28 -4
  107. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  108. package/dist/cjs/tools/search/utils.cjs +10 -3
  109. package/dist/cjs/tools/search/utils.cjs.map +1 -1
  110. package/dist/cjs/tools/skillCatalog.cjs +84 -0
  111. package/dist/cjs/tools/skillCatalog.cjs.map +1 -0
  112. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +512 -0
  113. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -0
  114. package/dist/cjs/tools/toolOutputReferences.cjs +670 -0
  115. package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -0
  116. package/dist/cjs/types/agent-cache.cjs +54 -0
  117. package/dist/cjs/types/agent-cache.cjs.map +1 -0
  118. package/dist/cjs/types/graph.cjs.map +1 -1
  119. package/dist/cjs/utils/truncation.cjs +135 -0
  120. package/dist/cjs/utils/truncation.cjs.map +1 -0
  121. package/dist/esm/agents/AgentContext.mjs +274 -67
  122. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  123. package/dist/esm/common/enum.mjs +44 -12
  124. package/dist/esm/common/enum.mjs.map +1 -1
  125. package/dist/esm/graphs/Graph.mjs +182 -5
  126. package/dist/esm/graphs/Graph.mjs.map +1 -1
  127. package/dist/esm/graphs/MultiAgentGraph.mjs +155 -1170
  128. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  129. package/dist/esm/graphs/phases/memoryFlushPhase.mjs +1 -1
  130. package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -1
  131. package/dist/esm/hooks/HookRegistry.mjs +160 -0
  132. package/dist/esm/hooks/HookRegistry.mjs.map +1 -0
  133. package/dist/esm/hooks/executeHooks.mjs +273 -0
  134. package/dist/esm/hooks/executeHooks.mjs.map +1 -0
  135. package/dist/esm/hooks/matchers.mjs +251 -0
  136. package/dist/esm/hooks/matchers.mjs.map +1 -0
  137. package/dist/esm/hooks/types.mjs +25 -0
  138. package/dist/esm/hooks/types.mjs.map +1 -0
  139. package/dist/esm/langchain/google-common.mjs +2 -0
  140. package/dist/esm/langchain/google-common.mjs.map +1 -0
  141. package/dist/esm/langchain/index.mjs +5 -0
  142. package/dist/esm/langchain/language_models/chat_models.mjs +2 -0
  143. package/dist/esm/langchain/language_models/chat_models.mjs.map +1 -0
  144. package/dist/esm/langchain/messages/tool.mjs +2 -0
  145. package/dist/esm/langchain/messages/tool.mjs.map +1 -0
  146. package/dist/esm/langchain/messages.mjs +2 -0
  147. package/dist/esm/langchain/messages.mjs.map +1 -0
  148. package/dist/esm/langchain/openai.mjs +2 -0
  149. package/dist/esm/langchain/openai.mjs.map +1 -0
  150. package/dist/esm/langchain/prompts.mjs +2 -0
  151. package/dist/esm/langchain/prompts.mjs.map +1 -0
  152. package/dist/esm/langchain/runnables.mjs +2 -0
  153. package/dist/esm/langchain/runnables.mjs.map +1 -0
  154. package/dist/esm/langchain/tools.mjs +2 -0
  155. package/dist/esm/langchain/tools.mjs.map +1 -0
  156. package/dist/esm/langchain/utils/env.mjs +2 -0
  157. package/dist/esm/langchain/utils/env.mjs.map +1 -0
  158. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +5 -1
  159. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  160. package/dist/esm/llm/bedrock/cacheSupport.mjs +52 -0
  161. package/dist/esm/llm/bedrock/cacheSupport.mjs.map +1 -0
  162. package/dist/esm/llm/bedrock/index.mjs +61 -34
  163. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  164. package/dist/esm/llm/openai/index.mjs +1 -4
  165. package/dist/esm/llm/openai/index.mjs.map +1 -1
  166. package/dist/esm/llm/openai/utils/index.mjs +27 -10
  167. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  168. package/dist/esm/main.mjs +21 -27
  169. package/dist/esm/main.mjs.map +1 -1
  170. package/dist/esm/memory/citations.mjs +4 -4
  171. package/dist/esm/memory/citations.mjs.map +1 -1
  172. package/dist/esm/memory/constants.mjs +17 -17
  173. package/dist/esm/memory/constants.mjs.map +1 -1
  174. package/dist/esm/memory/mmr.mjs +1 -1
  175. package/dist/esm/memory/mmr.mjs.map +1 -1
  176. package/dist/esm/memory/paths.mjs +1 -1
  177. package/dist/esm/memory/paths.mjs.map +1 -1
  178. package/dist/esm/memory/recallTracking.mjs +3 -3
  179. package/dist/esm/memory/recallTracking.mjs.map +1 -1
  180. package/dist/esm/memory/temporalDecay.mjs +2 -2
  181. package/dist/esm/memory/temporalDecay.mjs.map +1 -1
  182. package/dist/esm/messages/cache.mjs +89 -0
  183. package/dist/esm/messages/cache.mjs.map +1 -1
  184. package/dist/esm/messages/contextPruning.mjs +154 -0
  185. package/dist/esm/messages/contextPruning.mjs.map +1 -0
  186. package/dist/esm/messages/contextPruningSettings.mjs +50 -0
  187. package/dist/esm/messages/contextPruningSettings.mjs.map +1 -0
  188. package/dist/esm/messages/format.mjs +136 -12
  189. package/dist/esm/messages/format.mjs.map +1 -1
  190. package/dist/esm/messages/prune.mjs +504 -7
  191. package/dist/esm/messages/prune.mjs.map +1 -1
  192. package/dist/esm/run.mjs +141 -1
  193. package/dist/esm/run.mjs.map +1 -1
  194. package/dist/esm/tools/BashExecutor.mjs +227 -0
  195. package/dist/esm/tools/BashExecutor.mjs.map +1 -0
  196. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +288 -0
  197. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -0
  198. package/dist/esm/tools/CodeExecutor.mjs +45 -48
  199. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  200. package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -12
  201. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  202. package/dist/esm/tools/ReadFile.mjs +39 -0
  203. package/dist/esm/tools/ReadFile.mjs.map +1 -0
  204. package/dist/esm/tools/SkillTool.mjs +46 -0
  205. package/dist/esm/tools/SkillTool.mjs.map +1 -0
  206. package/dist/esm/tools/SubagentTool.mjs +86 -0
  207. package/dist/esm/tools/SubagentTool.mjs.map +1 -0
  208. package/dist/esm/tools/ToolNode.mjs +452 -26
  209. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  210. package/dist/esm/tools/memory/memoryAppendTool.mjs +1 -1
  211. package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -1
  212. package/dist/esm/tools/memory/memoryGetTool.mjs +2 -2
  213. package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -1
  214. package/dist/esm/tools/memory/memorySearchTool.mjs +3 -3
  215. package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -1
  216. package/dist/esm/tools/memory/shared.mjs +1 -1
  217. package/dist/esm/tools/memory/shared.mjs.map +1 -1
  218. package/dist/esm/tools/search/search.mjs +11 -3
  219. package/dist/esm/tools/search/search.mjs.map +1 -1
  220. package/dist/esm/tools/search/tavily-scraper.mjs +186 -0
  221. package/dist/esm/tools/search/tavily-scraper.mjs.map +1 -0
  222. package/dist/esm/tools/search/tavily-search.mjs +370 -0
  223. package/dist/esm/tools/search/tavily-search.mjs.map +1 -0
  224. package/dist/esm/tools/search/tool.mjs +28 -4
  225. package/dist/esm/tools/search/tool.mjs.map +1 -1
  226. package/dist/esm/tools/search/utils.mjs +10 -3
  227. package/dist/esm/tools/search/utils.mjs.map +1 -1
  228. package/dist/esm/tools/skillCatalog.mjs +82 -0
  229. package/dist/esm/tools/skillCatalog.mjs.map +1 -0
  230. package/dist/esm/tools/subagent/SubagentExecutor.mjs +506 -0
  231. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -0
  232. package/dist/esm/tools/toolOutputReferences.mjs +662 -0
  233. package/dist/esm/tools/toolOutputReferences.mjs.map +1 -0
  234. package/dist/esm/types/agent-cache.mjs +52 -0
  235. package/dist/esm/types/agent-cache.mjs.map +1 -0
  236. package/dist/esm/types/graph.mjs.map +1 -1
  237. package/dist/esm/utils/truncation.mjs +128 -0
  238. package/dist/esm/utils/truncation.mjs.map +1 -0
  239. package/dist/types/agents/AgentContext.d.ts +101 -8
  240. package/dist/types/common/enum.d.ts +39 -12
  241. package/dist/types/common/index.d.ts +0 -1
  242. package/dist/types/graphs/Graph.d.ts +43 -0
  243. package/dist/types/graphs/MultiAgentGraph.d.ts +26 -150
  244. package/dist/types/graphs/index.d.ts +0 -1
  245. package/dist/types/graphs/phases/memoryFlushPhase.d.ts +2 -2
  246. package/dist/types/hooks/HookRegistry.d.ts +56 -0
  247. package/dist/types/hooks/executeHooks.d.ts +79 -0
  248. package/dist/types/hooks/index.d.ts +6 -0
  249. package/dist/types/hooks/matchers.d.ts +95 -0
  250. package/dist/types/hooks/types.d.ts +320 -0
  251. package/dist/types/index.d.ts +9 -9
  252. package/dist/types/langchain/google-common.d.ts +1 -0
  253. package/dist/types/langchain/index.d.ts +8 -0
  254. package/dist/types/langchain/language_models/chat_models.d.ts +1 -0
  255. package/dist/types/langchain/messages/tool.d.ts +1 -0
  256. package/dist/types/langchain/messages.d.ts +2 -0
  257. package/dist/types/langchain/openai.d.ts +1 -0
  258. package/dist/types/langchain/prompts.d.ts +1 -0
  259. package/dist/types/langchain/runnables.d.ts +2 -0
  260. package/dist/types/langchain/tools.d.ts +2 -0
  261. package/dist/types/langchain/utils/env.d.ts +1 -0
  262. package/dist/types/llm/bedrock/cacheSupport.d.ts +35 -0
  263. package/dist/types/llm/bedrock/index.d.ts +54 -1
  264. package/dist/types/llm/openai/index.d.ts +1 -1
  265. package/dist/types/memory/citations.d.ts +4 -4
  266. package/dist/types/memory/constants.d.ts +17 -17
  267. package/dist/types/memory/mmr.d.ts +3 -3
  268. package/dist/types/memory/paths.d.ts +1 -1
  269. package/dist/types/memory/temporalDecay.d.ts +2 -2
  270. package/dist/types/memory/types.d.ts +3 -3
  271. package/dist/types/messages/contextPruning.d.ts +42 -0
  272. package/dist/types/messages/contextPruningSettings.d.ts +44 -0
  273. package/dist/types/messages/format.d.ts +9 -1
  274. package/dist/types/messages/index.d.ts +2 -0
  275. package/dist/types/messages/prune.d.ts +91 -1
  276. package/dist/types/run.d.ts +2 -0
  277. package/dist/types/tools/BashExecutor.d.ts +76 -0
  278. package/dist/types/tools/BashProgrammaticToolCalling.d.ts +72 -0
  279. package/dist/types/tools/CodeExecutor.d.ts +8 -26
  280. package/dist/types/tools/ReadFile.d.ts +28 -0
  281. package/dist/types/tools/SkillTool.d.ts +40 -0
  282. package/dist/types/tools/SubagentTool.d.ts +36 -0
  283. package/dist/types/tools/ToolNode.d.ts +77 -5
  284. package/dist/types/tools/memory/shared.d.ts +1 -1
  285. package/dist/types/tools/search/tavily-scraper.d.ts +19 -0
  286. package/dist/types/tools/search/tavily-search.d.ts +4 -0
  287. package/dist/types/tools/search/types.d.ts +99 -5
  288. package/dist/types/tools/search/utils.d.ts +2 -2
  289. package/dist/types/tools/skillCatalog.d.ts +19 -0
  290. package/dist/types/tools/subagent/SubagentExecutor.d.ts +137 -0
  291. package/dist/types/tools/subagent/index.d.ts +2 -0
  292. package/dist/types/tools/subagent/types.d.ts +84 -0
  293. package/dist/types/tools/toolOutputReferences.d.ts +236 -0
  294. package/dist/types/types/agent-cache.d.ts +71 -0
  295. package/dist/types/types/graph.d.ts +163 -22
  296. package/dist/types/types/index.d.ts +3 -0
  297. package/dist/types/types/messages.d.ts +26 -0
  298. package/dist/types/types/run.d.ts +22 -0
  299. package/dist/types/types/skill.d.ts +9 -0
  300. package/dist/types/types/tools.d.ts +111 -0
  301. package/dist/types/utils/index.d.ts +1 -3
  302. package/dist/types/utils/truncation.d.ts +70 -0
  303. package/package.json +57 -17
  304. package/src/agents/AgentContext.ts +321 -78
  305. package/src/agents/__tests__/AgentContext.cacheTtl.live.test.ts +259 -0
  306. package/src/agents/__tests__/AgentContext.crossAgentTier1.live.test.ts +266 -0
  307. package/src/agents/__tests__/AgentContext.crossUserCache.live.test.ts +342 -0
  308. package/src/agents/__tests__/AgentContext.test.ts +632 -0
  309. package/src/common/__tests__/enum.test.ts +7 -17
  310. package/src/common/enum.ts +43 -12
  311. package/src/common/index.ts +0 -1
  312. package/src/graphs/Graph.ts +222 -2
  313. package/src/graphs/MultiAgentGraph.ts +154 -1466
  314. package/src/graphs/__tests__/MultiAgentGraph.test.ts +91 -0
  315. package/src/graphs/gapFeatures.test.ts +1 -1
  316. package/src/graphs/index.ts +0 -1
  317. package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +1 -1
  318. package/src/graphs/phases/memoryFlushPhase.ts +2 -2
  319. package/src/hooks/HookRegistry.ts +208 -0
  320. package/src/hooks/__tests__/HookRegistry.test.ts +190 -0
  321. package/src/hooks/__tests__/compactHooks.test.ts +214 -0
  322. package/src/hooks/__tests__/executeHooks.test.ts +1013 -0
  323. package/src/hooks/__tests__/integration.test.ts +337 -0
  324. package/src/hooks/__tests__/matchers.test.ts +238 -0
  325. package/src/hooks/__tests__/toolHooks.test.ts +665 -0
  326. package/src/hooks/executeHooks.ts +375 -0
  327. package/src/hooks/index.ts +57 -0
  328. package/src/hooks/matchers.ts +280 -0
  329. package/src/hooks/types.ts +404 -0
  330. package/src/index.ts +15 -24
  331. package/src/langchain/google-common.ts +1 -0
  332. package/src/langchain/index.ts +8 -0
  333. package/src/langchain/language_models/chat_models.ts +1 -0
  334. package/src/langchain/messages/tool.ts +5 -0
  335. package/src/langchain/messages.ts +21 -0
  336. package/src/langchain/openai.ts +1 -0
  337. package/src/langchain/prompts.ts +1 -0
  338. package/src/langchain/runnables.ts +7 -0
  339. package/src/langchain/tools.ts +8 -0
  340. package/src/langchain/utils/env.ts +1 -0
  341. package/src/llm/anthropic/utils/message_inputs.ts +10 -1
  342. package/src/llm/anthropic/utils/server-tool-inputs.test.ts +436 -0
  343. package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +166 -18
  344. package/src/llm/bedrock/cacheSupport.test.ts +99 -0
  345. package/src/llm/bedrock/cacheSupport.ts +53 -0
  346. package/src/llm/bedrock/index.ts +116 -41
  347. package/src/llm/openai/index.ts +2 -2
  348. package/src/llm/openai/utils/index.ts +31 -14
  349. package/src/memory/citations.ts +4 -4
  350. package/src/memory/constants.ts +17 -17
  351. package/src/memory/mmr.ts +3 -3
  352. package/src/memory/paths.ts +1 -1
  353. package/src/memory/recallTracking.ts +3 -3
  354. package/src/memory/temporalDecay.ts +2 -2
  355. package/src/memory/types.ts +3 -3
  356. package/src/messages/__tests__/contextPruning.test.ts +228 -0
  357. package/src/messages/cache.test.ts +62 -24
  358. package/src/messages/cache.ts +112 -0
  359. package/src/messages/contextPruning.ts +191 -0
  360. package/src/messages/contextPruningSettings.ts +90 -0
  361. package/src/messages/ensureThinkingBlock.test.ts +1 -1
  362. package/src/messages/format.ts +164 -12
  363. package/src/messages/formatAgentMessages.skills.test.ts +413 -0
  364. package/src/messages/formatAgentMessages.test.ts +1 -1
  365. package/src/messages/index.ts +2 -0
  366. package/src/messages/prune.ts +661 -4
  367. package/src/run.ts +155 -1
  368. package/src/scripts/multi-agent-chain.ts +2 -2
  369. package/src/scripts/multi-agent-document-review-chain.ts +2 -2
  370. package/src/scripts/multi-agent-hybrid-flow.ts +4 -4
  371. package/src/scripts/multi-agent-parallel.ts +3 -3
  372. package/src/scripts/multi-agent-sequence.ts +3 -3
  373. package/src/scripts/multi-agent-subagent.ts +246 -0
  374. package/src/scripts/multi-agent-supervisor.ts +5 -5
  375. package/src/scripts/poc-multi-agent-comprehensive.ts +8 -8
  376. package/src/scripts/sequential-full-metadata-test.ts +2 -2
  377. package/src/scripts/subagent-event-driven-debug.ts +190 -0
  378. package/src/scripts/subagent-tools-debug.ts +160 -0
  379. package/src/scripts/test-custom-prompt-key.ts +3 -3
  380. package/src/scripts/test-handoff-input.ts +1 -1
  381. package/src/scripts/test-handoff-steering.ts +3 -3
  382. package/src/scripts/test-multi-agent-list-handoff.ts +1 -1
  383. package/src/scripts/test-parallel-agent-labeling.ts +3 -3
  384. package/src/scripts/test-parallel-handoffs.ts +2 -2
  385. package/src/scripts/test-thinking-handoff-bedrock.ts +1 -1
  386. package/src/scripts/test-thinking-handoff.ts +1 -1
  387. package/src/scripts/test-thinking-to-thinking-handoff-bedrock.ts +1 -1
  388. package/src/scripts/test-tool-before-handoff-role-order.ts +1 -1
  389. package/src/scripts/test-tools-before-handoff.ts +1 -1
  390. package/src/specs/agent-handoffs.test.ts +26 -483
  391. package/src/specs/anthropic.simple.test.ts +61 -0
  392. package/src/specs/multi-agent-summarization.test.ts +396 -0
  393. package/src/specs/prune.orphans.test.ts +248 -0
  394. package/src/specs/prune.test.ts +104 -16
  395. package/src/specs/thinking-handoff.test.ts +19 -19
  396. package/src/tools/BashExecutor.ts +281 -0
  397. package/src/tools/BashProgrammaticToolCalling.ts +397 -0
  398. package/src/tools/CodeExecutor.ts +63 -54
  399. package/src/tools/ProgrammaticToolCalling.ts +29 -14
  400. package/src/tools/ReadFile.ts +39 -0
  401. package/src/tools/SkillTool.ts +46 -0
  402. package/src/tools/SubagentTool.ts +100 -0
  403. package/src/tools/ToolNode.ts +548 -26
  404. package/src/tools/__tests__/BashExecutor.test.ts +49 -0
  405. package/src/tools/__tests__/CodeExecutor.test.ts +37 -36
  406. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +60 -0
  407. package/src/tools/__tests__/ReadFile.test.ts +44 -0
  408. package/src/tools/__tests__/SkillTool.test.ts +442 -0
  409. package/src/tools/__tests__/SubagentExecutor.test.ts +1148 -0
  410. package/src/tools/__tests__/SubagentTool.test.ts +149 -0
  411. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +1438 -0
  412. package/src/tools/__tests__/annotateMessagesForLLM.test.ts +479 -0
  413. package/src/tools/__tests__/skillCatalog.test.ts +161 -0
  414. package/src/tools/__tests__/subagentHooks.test.ts +210 -0
  415. package/src/tools/__tests__/toolOutputReferences.test.ts +415 -0
  416. package/src/tools/memory/memoryAppendTool.ts +1 -1
  417. package/src/tools/memory/memoryGetTool.ts +2 -2
  418. package/src/tools/memory/memorySearchTool.ts +3 -3
  419. package/src/tools/memory/shared.ts +1 -1
  420. package/src/tools/search/search.ts +12 -2
  421. package/src/tools/search/tavily-scraper.ts +235 -0
  422. package/src/tools/search/tavily-search.ts +424 -0
  423. package/src/tools/search/tavily.test.ts +965 -0
  424. package/src/tools/search/tool.ts +36 -2
  425. package/src/tools/search/types.ts +133 -8
  426. package/src/tools/search/utils.ts +13 -5
  427. package/src/tools/skillCatalog.ts +126 -0
  428. package/src/tools/subagent/SubagentExecutor.ts +676 -0
  429. package/src/tools/subagent/index.ts +13 -0
  430. package/src/tools/subagent/types.test.ts +70 -0
  431. package/src/tools/subagent/types.ts +115 -0
  432. package/src/tools/toolOutputReferences.ts +825 -0
  433. package/src/types/agent-cache.ts +74 -0
  434. package/src/types/graph.ts +172 -20
  435. package/src/types/index.ts +3 -0
  436. package/src/types/messages.ts +27 -0
  437. package/src/types/run.ts +22 -0
  438. package/src/types/skill.ts +11 -0
  439. package/src/types/tools.ts +118 -0
  440. package/src/utils/__tests__/truncation.test.ts +66 -0
  441. package/src/utils/index.ts +1 -3
  442. package/src/utils/truncation.ts +154 -0
  443. package/dist/cjs/common/spawnPath.cjs +0 -104
  444. package/dist/cjs/common/spawnPath.cjs.map +0 -1
  445. package/dist/cjs/content/ArtifactStore.cjs +0 -579
  446. package/dist/cjs/content/ArtifactStore.cjs.map +0 -1
  447. package/dist/cjs/content/ContentStore.cjs +0 -638
  448. package/dist/cjs/content/ContentStore.cjs.map +0 -1
  449. package/dist/cjs/content/contentAnalyzer.cjs +0 -91
  450. package/dist/cjs/content/contentAnalyzer.cjs.map +0 -1
  451. package/dist/cjs/content/index.cjs +0 -20
  452. package/dist/cjs/content/index.cjs.map +0 -1
  453. package/dist/cjs/content/mcpAutoCache.cjs +0 -115
  454. package/dist/cjs/content/mcpAutoCache.cjs.map +0 -1
  455. package/dist/cjs/graphs/HandoffRegistry.cjs +0 -143
  456. package/dist/cjs/graphs/HandoffRegistry.cjs.map +0 -1
  457. package/dist/cjs/providers/a2a/A2ACapabilityProvider.cjs +0 -288
  458. package/dist/cjs/providers/a2a/A2ACapabilityProvider.cjs.map +0 -1
  459. package/dist/cjs/providers/a2a/client.cjs +0 -92
  460. package/dist/cjs/providers/a2a/client.cjs.map +0 -1
  461. package/dist/cjs/providers/a2a/config.cjs +0 -38
  462. package/dist/cjs/providers/a2a/config.cjs.map +0 -1
  463. package/dist/cjs/providers/capabilityNaming.cjs +0 -43
  464. package/dist/cjs/providers/capabilityNaming.cjs.map +0 -1
  465. package/dist/cjs/providers/mcp/MCPCapabilityProvider.cjs +0 -244
  466. package/dist/cjs/providers/mcp/MCPCapabilityProvider.cjs.map +0 -1
  467. package/dist/cjs/providers/mcp/config.cjs +0 -42
  468. package/dist/cjs/providers/mcp/config.cjs.map +0 -1
  469. package/dist/cjs/providers/mcp/transport.cjs +0 -65
  470. package/dist/cjs/providers/mcp/transport.cjs.map +0 -1
  471. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs +0 -128
  472. package/dist/cjs/providers/tools-server/ToolsServerCapabilityProvider.cjs.map +0 -1
  473. package/dist/cjs/providers/types.cjs +0 -51
  474. package/dist/cjs/providers/types.cjs.map +0 -1
  475. package/dist/cjs/tools/artifacts/schema.cjs +0 -86
  476. package/dist/cjs/tools/artifacts/schema.cjs.map +0 -1
  477. package/dist/cjs/tools/artifacts/tool.cjs +0 -219
  478. package/dist/cjs/tools/artifacts/tool.cjs.map +0 -1
  479. package/dist/cjs/tools/fileSearch/formatter.cjs +0 -93
  480. package/dist/cjs/tools/fileSearch/formatter.cjs.map +0 -1
  481. package/dist/cjs/tools/fileSearch/ragClient.cjs +0 -102
  482. package/dist/cjs/tools/fileSearch/ragClient.cjs.map +0 -1
  483. package/dist/cjs/tools/fileSearch/schema.cjs +0 -18
  484. package/dist/cjs/tools/fileSearch/schema.cjs.map +0 -1
  485. package/dist/cjs/tools/fileSearch/tool.cjs +0 -155
  486. package/dist/cjs/tools/fileSearch/tool.cjs.map +0 -1
  487. package/dist/cjs/tools/proxyTool.cjs +0 -102
  488. package/dist/cjs/tools/proxyTool.cjs.map +0 -1
  489. package/dist/cjs/utils/childAgentContext.cjs +0 -242
  490. package/dist/cjs/utils/childAgentContext.cjs.map +0 -1
  491. package/dist/cjs/utils/credentials.cjs +0 -142
  492. package/dist/cjs/utils/credentials.cjs.map +0 -1
  493. package/dist/cjs/utils/httpClient.cjs +0 -74
  494. package/dist/cjs/utils/httpClient.cjs.map +0 -1
  495. package/dist/cjs/utils/toolManifest.cjs +0 -100
  496. package/dist/cjs/utils/toolManifest.cjs.map +0 -1
  497. package/dist/esm/common/spawnPath.mjs +0 -95
  498. package/dist/esm/common/spawnPath.mjs.map +0 -1
  499. package/dist/esm/content/ArtifactStore.mjs +0 -576
  500. package/dist/esm/content/ArtifactStore.mjs.map +0 -1
  501. package/dist/esm/content/ContentStore.mjs +0 -635
  502. package/dist/esm/content/ContentStore.mjs.map +0 -1
  503. package/dist/esm/content/contentAnalyzer.mjs +0 -87
  504. package/dist/esm/content/contentAnalyzer.mjs.map +0 -1
  505. package/dist/esm/content/index.mjs +0 -5
  506. package/dist/esm/content/mcpAutoCache.mjs +0 -111
  507. package/dist/esm/content/mcpAutoCache.mjs.map +0 -1
  508. package/dist/esm/graphs/HandoffRegistry.mjs +0 -141
  509. package/dist/esm/graphs/HandoffRegistry.mjs.map +0 -1
  510. package/dist/esm/providers/a2a/A2ACapabilityProvider.mjs +0 -281
  511. package/dist/esm/providers/a2a/A2ACapabilityProvider.mjs.map +0 -1
  512. package/dist/esm/providers/a2a/client.mjs +0 -88
  513. package/dist/esm/providers/a2a/client.mjs.map +0 -1
  514. package/dist/esm/providers/a2a/config.mjs +0 -35
  515. package/dist/esm/providers/a2a/config.mjs.map +0 -1
  516. package/dist/esm/providers/capabilityNaming.mjs +0 -39
  517. package/dist/esm/providers/capabilityNaming.mjs.map +0 -1
  518. package/dist/esm/providers/mcp/MCPCapabilityProvider.mjs +0 -240
  519. package/dist/esm/providers/mcp/MCPCapabilityProvider.mjs.map +0 -1
  520. package/dist/esm/providers/mcp/config.mjs +0 -39
  521. package/dist/esm/providers/mcp/config.mjs.map +0 -1
  522. package/dist/esm/providers/mcp/transport.mjs +0 -63
  523. package/dist/esm/providers/mcp/transport.mjs.map +0 -1
  524. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs +0 -126
  525. package/dist/esm/providers/tools-server/ToolsServerCapabilityProvider.mjs.map +0 -1
  526. package/dist/esm/providers/types.mjs +0 -51
  527. package/dist/esm/providers/types.mjs.map +0 -1
  528. package/dist/esm/tools/artifacts/schema.mjs +0 -79
  529. package/dist/esm/tools/artifacts/schema.mjs.map +0 -1
  530. package/dist/esm/tools/artifacts/tool.mjs +0 -213
  531. package/dist/esm/tools/artifacts/tool.mjs.map +0 -1
  532. package/dist/esm/tools/fileSearch/formatter.mjs +0 -90
  533. package/dist/esm/tools/fileSearch/formatter.mjs.map +0 -1
  534. package/dist/esm/tools/fileSearch/ragClient.mjs +0 -98
  535. package/dist/esm/tools/fileSearch/ragClient.mjs.map +0 -1
  536. package/dist/esm/tools/fileSearch/schema.mjs +0 -15
  537. package/dist/esm/tools/fileSearch/schema.mjs.map +0 -1
  538. package/dist/esm/tools/fileSearch/tool.mjs +0 -152
  539. package/dist/esm/tools/fileSearch/tool.mjs.map +0 -1
  540. package/dist/esm/tools/proxyTool.mjs +0 -100
  541. package/dist/esm/tools/proxyTool.mjs.map +0 -1
  542. package/dist/esm/utils/childAgentContext.mjs +0 -237
  543. package/dist/esm/utils/childAgentContext.mjs.map +0 -1
  544. package/dist/esm/utils/credentials.mjs +0 -135
  545. package/dist/esm/utils/credentials.mjs.map +0 -1
  546. package/dist/esm/utils/httpClient.mjs +0 -70
  547. package/dist/esm/utils/httpClient.mjs.map +0 -1
  548. package/dist/esm/utils/toolManifest.mjs +0 -96
  549. package/dist/esm/utils/toolManifest.mjs.map +0 -1
  550. package/dist/types/common/spawnPath.d.ts +0 -59
  551. package/dist/types/content/ArtifactStore.d.ts +0 -223
  552. package/dist/types/content/ContentStore.d.ts +0 -140
  553. package/dist/types/content/contentAnalyzer.d.ts +0 -38
  554. package/dist/types/content/index.d.ts +0 -24
  555. package/dist/types/content/mcpAutoCache.d.ts +0 -89
  556. package/dist/types/content/types.d.ts +0 -75
  557. package/dist/types/graphs/HandoffRegistry.d.ts +0 -97
  558. package/dist/types/providers/a2a/A2ACapabilityProvider.d.ts +0 -89
  559. package/dist/types/providers/a2a/client.d.ts +0 -47
  560. package/dist/types/providers/a2a/config.d.ts +0 -18
  561. package/dist/types/providers/a2a/index.d.ts +0 -6
  562. package/dist/types/providers/a2a/types.d.ts +0 -173
  563. package/dist/types/providers/capabilityNaming.d.ts +0 -25
  564. package/dist/types/providers/index.d.ts +0 -12
  565. package/dist/types/providers/mcp/MCPCapabilityProvider.d.ts +0 -54
  566. package/dist/types/providers/mcp/config.d.ts +0 -20
  567. package/dist/types/providers/mcp/index.d.ts +0 -5
  568. package/dist/types/providers/mcp/transport.d.ts +0 -18
  569. package/dist/types/providers/mcp/types.d.ts +0 -112
  570. package/dist/types/providers/tools-server/ToolsServerCapabilityProvider.d.ts +0 -59
  571. package/dist/types/providers/tools-server/index.d.ts +0 -1
  572. package/dist/types/providers/types.d.ts +0 -184
  573. package/dist/types/tools/artifacts/index.d.ts +0 -3
  574. package/dist/types/tools/artifacts/schema.d.ts +0 -63
  575. package/dist/types/tools/artifacts/tool.d.ts +0 -16
  576. package/dist/types/tools/artifacts/types.d.ts +0 -127
  577. package/dist/types/tools/fileSearch/formatter.d.ts +0 -25
  578. package/dist/types/tools/fileSearch/index.d.ts +0 -5
  579. package/dist/types/tools/fileSearch/ragClient.d.ts +0 -32
  580. package/dist/types/tools/fileSearch/schema.d.ts +0 -13
  581. package/dist/types/tools/fileSearch/tool.d.ts +0 -18
  582. package/dist/types/tools/fileSearch/types.d.ts +0 -139
  583. package/dist/types/tools/proxyTool.d.ts +0 -62
  584. package/dist/types/tools/search/test.d.ts +0 -1
  585. package/dist/types/utils/childAgentContext.d.ts +0 -99
  586. package/dist/types/utils/credentials.d.ts +0 -77
  587. package/dist/types/utils/httpClient.d.ts +0 -46
  588. package/dist/types/utils/toolManifest.d.ts +0 -49
  589. package/src/common/__tests__/spawnPath.test.ts +0 -110
  590. package/src/common/spawnPath.ts +0 -101
  591. package/src/content/ArtifactStore.ts +0 -782
  592. package/src/content/ContentStore.ts +0 -753
  593. package/src/content/contentAnalyzer.ts +0 -105
  594. package/src/content/index.ts +0 -51
  595. package/src/content/mcpAutoCache.ts +0 -185
  596. package/src/content/types.ts +0 -82
  597. package/src/graphs/HandoffRegistry.ts +0 -199
  598. package/src/graphs/__tests__/HandoffRegistry.test.ts +0 -410
  599. package/src/graphs/__tests__/multi-agent-delegate.test.ts +0 -458
  600. package/src/graphs/__tests__/multi-agent-edges.test.ts +0 -276
  601. package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +0 -221
  602. package/src/graphs/handoffValidation.test.ts +0 -353
  603. package/src/providers/__tests__/ToolsServerCapabilityProvider.integration.spec.ts +0 -79
  604. package/src/providers/__tests__/ToolsServerCapabilityProvider.test.ts +0 -271
  605. package/src/providers/__tests__/types.test.ts +0 -64
  606. package/src/providers/a2a/A2ACapabilityProvider.ts +0 -384
  607. package/src/providers/a2a/__tests__/A2ACapabilityProvider.integration.spec.ts +0 -345
  608. package/src/providers/a2a/__tests__/A2ACapabilityProvider.test.ts +0 -460
  609. package/src/providers/a2a/client.ts +0 -115
  610. package/src/providers/a2a/config.ts +0 -40
  611. package/src/providers/a2a/index.ts +0 -29
  612. package/src/providers/a2a/types.ts +0 -191
  613. package/src/providers/capabilityNaming.ts +0 -42
  614. package/src/providers/index.ts +0 -68
  615. package/src/providers/mcp/MCPCapabilityProvider.ts +0 -345
  616. package/src/providers/mcp/__tests__/MCPCapabilityProvider.integration.spec.ts +0 -386
  617. package/src/providers/mcp/__tests__/MCPCapabilityProvider.test.ts +0 -371
  618. package/src/providers/mcp/config.ts +0 -45
  619. package/src/providers/mcp/index.ts +0 -21
  620. package/src/providers/mcp/transport.ts +0 -76
  621. package/src/providers/mcp/types.ts +0 -139
  622. package/src/providers/tools-server/ToolsServerCapabilityProvider.ts +0 -249
  623. package/src/providers/tools-server/index.ts +0 -1
  624. package/src/providers/types.ts +0 -204
  625. package/src/scripts/test-bedrock-handoff-autonomous.ts +0 -267
  626. package/src/scripts/test-handoff-preamble.ts +0 -278
  627. package/src/specs/agent-handoffs-bedrock.integration.test.ts +0 -415
  628. package/src/tools/artifacts/__tests__/tool.test.ts +0 -259
  629. package/src/tools/artifacts/index.ts +0 -33
  630. package/src/tools/artifacts/schema.ts +0 -99
  631. package/src/tools/artifacts/tool.ts +0 -289
  632. package/src/tools/artifacts/types.ts +0 -162
  633. package/src/tools/fileSearch/__tests__/tool.test.ts +0 -261
  634. package/src/tools/fileSearch/formatter.ts +0 -129
  635. package/src/tools/fileSearch/index.ts +0 -23
  636. package/src/tools/fileSearch/ragClient.ts +0 -137
  637. package/src/tools/fileSearch/schema.ts +0 -19
  638. package/src/tools/fileSearch/tool.ts +0 -207
  639. package/src/tools/fileSearch/types.ts +0 -149
  640. package/src/tools/proxyTool.ts +0 -166
  641. package/src/tools/search/output.md +0 -2775
  642. package/src/tools/search/test.html +0 -884
  643. package/src/tools/search/test.md +0 -643
  644. package/src/tools/search/test.ts +0 -159
  645. package/src/utils/__tests__/childAgentContext.test.ts +0 -217
  646. package/src/utils/__tests__/credentials.test.ts +0 -130
  647. package/src/utils/__tests__/httpClient.test.ts +0 -75
  648. package/src/utils/__tests__/toolManifest.test.ts +0 -116
  649. package/src/utils/childAgentContext.ts +0 -259
  650. package/src/utils/credentials.ts +0 -157
  651. package/src/utils/httpClient.ts +0 -92
  652. package/src/utils/toolManifest.ts +0 -109
  653. /package/dist/esm/{content → langchain}/index.mjs.map +0 -0
@@ -0,0 +1,1013 @@
1
+ // src/hooks/__tests__/executeHooks.test.ts
2
+ import { HookRegistry } from '../HookRegistry';
3
+ import { executeHooks } from '../executeHooks';
4
+ import { clearMatcherCache } from '../matchers';
5
+ import type {
6
+ HookCallback,
7
+ HookMatcher,
8
+ RunStartHookInput,
9
+ RunStartHookOutput,
10
+ StopHookInput,
11
+ StopHookOutput,
12
+ PreToolUseHookInput,
13
+ PreToolUseHookOutput,
14
+ PostToolUseHookInput,
15
+ PostToolUseHookOutput,
16
+ } from '../types';
17
+
18
+ function preToolUseInput(
19
+ toolName: string,
20
+ overrides: Partial<PreToolUseHookInput> = {}
21
+ ): PreToolUseHookInput {
22
+ return {
23
+ hook_event_name: 'PreToolUse',
24
+ runId: 'run-1',
25
+ threadId: 'thread-1',
26
+ toolName,
27
+ toolInput: { cmd: 'ls' },
28
+ toolUseId: 'tool-call-1',
29
+ ...overrides,
30
+ };
31
+ }
32
+
33
+ function postToolUseInput(
34
+ toolName: string,
35
+ overrides: Partial<PostToolUseHookInput> = {}
36
+ ): PostToolUseHookInput {
37
+ return {
38
+ hook_event_name: 'PostToolUse',
39
+ runId: 'run-1',
40
+ toolName,
41
+ toolInput: {},
42
+ toolOutput: null,
43
+ toolUseId: 'tc-1',
44
+ ...overrides,
45
+ };
46
+ }
47
+
48
+ function stopInput(overrides: Partial<StopHookInput> = {}): StopHookInput {
49
+ return {
50
+ hook_event_name: 'Stop',
51
+ runId: 'run-1',
52
+ messages: [],
53
+ stopHookActive: false,
54
+ ...overrides,
55
+ };
56
+ }
57
+
58
+ function runStartInput(): RunStartHookInput {
59
+ return {
60
+ hook_event_name: 'RunStart',
61
+ runId: 'run-1',
62
+ messages: [],
63
+ };
64
+ }
65
+
66
+ function preToolHook(
67
+ fn: HookCallback<'PreToolUse'>
68
+ ): HookCallback<'PreToolUse'> {
69
+ return fn;
70
+ }
71
+
72
+ function postToolHook(
73
+ fn: HookCallback<'PostToolUse'>
74
+ ): HookCallback<'PostToolUse'> {
75
+ return fn;
76
+ }
77
+
78
+ function runStartHook(fn: HookCallback<'RunStart'>): HookCallback<'RunStart'> {
79
+ return fn;
80
+ }
81
+
82
+ function stopHook(fn: HookCallback<'Stop'>): HookCallback<'Stop'> {
83
+ return fn;
84
+ }
85
+
86
+ const emptyPreOutput: PreToolUseHookOutput = {};
87
+ const emptyRunStartOutput: RunStartHookOutput = {};
88
+
89
+ const noopPreHook = preToolHook(
90
+ async (): Promise<PreToolUseHookOutput> => emptyPreOutput
91
+ );
92
+ const noopRunStartHook = runStartHook(
93
+ async (): Promise<RunStartHookOutput> => emptyRunStartOutput
94
+ );
95
+
96
+ describe('executeHooks', () => {
97
+ let consoleWarnSpy: jest.SpyInstance;
98
+
99
+ beforeEach(() => {
100
+ clearMatcherCache();
101
+ consoleWarnSpy = jest
102
+ .spyOn(console, 'warn')
103
+ .mockImplementation((): void => {
104
+ /* silence expected warnings */
105
+ });
106
+ });
107
+
108
+ afterEach(() => {
109
+ consoleWarnSpy.mockRestore();
110
+ });
111
+
112
+ describe('empty matcher set', () => {
113
+ it('returns an empty aggregated result when no matchers are registered', async () => {
114
+ const registry = new HookRegistry();
115
+ const result = await executeHooks({
116
+ registry,
117
+ input: preToolUseInput('Bash'),
118
+ matchQuery: 'Bash',
119
+ });
120
+ expect(result).toEqual({ additionalContexts: [], errors: [] });
121
+ });
122
+
123
+ it('returns an empty result when no matcher pattern matches the query', async () => {
124
+ const registry = new HookRegistry();
125
+ let called = false;
126
+ registry.register('PreToolUse', {
127
+ pattern: '^Edit$',
128
+ hooks: [
129
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
130
+ called = true;
131
+ return emptyPreOutput;
132
+ }),
133
+ ],
134
+ });
135
+ const result = await executeHooks({
136
+ registry,
137
+ input: preToolUseInput('Bash'),
138
+ matchQuery: 'Bash',
139
+ });
140
+ expect(called).toBe(false);
141
+ expect(result).toEqual({ additionalContexts: [], errors: [] });
142
+ });
143
+ });
144
+
145
+ describe('matcher regex filtering', () => {
146
+ it('fires hooks whose matcher regex matches the query', async () => {
147
+ const registry = new HookRegistry();
148
+ const calls: string[] = [];
149
+ registry.register('PreToolUse', {
150
+ pattern: '^Bash$',
151
+ hooks: [
152
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
153
+ calls.push('bash-only');
154
+ return emptyPreOutput;
155
+ }),
156
+ ],
157
+ });
158
+ registry.register('PreToolUse', {
159
+ pattern: 'Bash|Edit',
160
+ hooks: [
161
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
162
+ calls.push('bash-or-edit');
163
+ return emptyPreOutput;
164
+ }),
165
+ ],
166
+ });
167
+ registry.register('PreToolUse', {
168
+ pattern: '^Edit$',
169
+ hooks: [
170
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
171
+ calls.push('edit-only');
172
+ return emptyPreOutput;
173
+ }),
174
+ ],
175
+ });
176
+
177
+ await executeHooks({
178
+ registry,
179
+ input: preToolUseInput('Bash'),
180
+ matchQuery: 'Bash',
181
+ });
182
+ expect(calls.sort()).toEqual(['bash-only', 'bash-or-edit']);
183
+ });
184
+
185
+ it('fires matchers with no pattern regardless of query', async () => {
186
+ const registry = new HookRegistry();
187
+ let fired = false;
188
+ registry.register('PreToolUse', {
189
+ hooks: [
190
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
191
+ fired = true;
192
+ return emptyPreOutput;
193
+ }),
194
+ ],
195
+ });
196
+ await executeHooks({
197
+ registry,
198
+ input: preToolUseInput('Bash'),
199
+ matchQuery: 'Bash',
200
+ });
201
+ expect(fired).toBe(true);
202
+ });
203
+ });
204
+
205
+ describe('decision precedence (deny > ask > allow)', () => {
206
+ it('deny beats allow', async () => {
207
+ const registry = new HookRegistry();
208
+ registry.register('PreToolUse', {
209
+ hooks: [
210
+ preToolHook(
211
+ async (): Promise<PreToolUseHookOutput> => ({
212
+ decision: 'allow',
213
+ reason: 'all good',
214
+ })
215
+ ),
216
+ ],
217
+ });
218
+ registry.register('PreToolUse', {
219
+ hooks: [
220
+ preToolHook(
221
+ async (): Promise<PreToolUseHookOutput> => ({
222
+ decision: 'deny',
223
+ reason: 'forbidden',
224
+ })
225
+ ),
226
+ ],
227
+ });
228
+ const result = await executeHooks({
229
+ registry,
230
+ input: preToolUseInput('Bash'),
231
+ matchQuery: 'Bash',
232
+ });
233
+ expect(result.decision).toBe('deny');
234
+ expect(result.reason).toBe('forbidden');
235
+ });
236
+
237
+ it('deny beats ask', async () => {
238
+ const registry = new HookRegistry();
239
+ registry.register('PreToolUse', {
240
+ hooks: [
241
+ preToolHook(
242
+ async (): Promise<PreToolUseHookOutput> => ({
243
+ decision: 'ask',
244
+ reason: 'needs prompt',
245
+ })
246
+ ),
247
+ ],
248
+ });
249
+ registry.register('PreToolUse', {
250
+ hooks: [
251
+ preToolHook(
252
+ async (): Promise<PreToolUseHookOutput> => ({
253
+ decision: 'deny',
254
+ reason: 'forbidden',
255
+ })
256
+ ),
257
+ ],
258
+ });
259
+ const result = await executeHooks({
260
+ registry,
261
+ input: preToolUseInput('Bash'),
262
+ matchQuery: 'Bash',
263
+ });
264
+ expect(result.decision).toBe('deny');
265
+ expect(result.reason).toBe('forbidden');
266
+ });
267
+
268
+ it('ask beats allow but not deny', async () => {
269
+ const registry = new HookRegistry();
270
+ registry.register('PreToolUse', {
271
+ hooks: [
272
+ preToolHook(
273
+ async (): Promise<PreToolUseHookOutput> => ({ decision: 'allow' })
274
+ ),
275
+ ],
276
+ });
277
+ registry.register('PreToolUse', {
278
+ hooks: [
279
+ preToolHook(
280
+ async (): Promise<PreToolUseHookOutput> => ({
281
+ decision: 'ask',
282
+ reason: 'please confirm',
283
+ })
284
+ ),
285
+ ],
286
+ });
287
+ const result = await executeHooks({
288
+ registry,
289
+ input: preToolUseInput('Bash'),
290
+ matchQuery: 'Bash',
291
+ });
292
+ expect(result.decision).toBe('ask');
293
+ expect(result.reason).toBe('please confirm');
294
+ });
295
+
296
+ it('allow is the default when any hook returned allow and none denied or asked', async () => {
297
+ const registry = new HookRegistry();
298
+ registry.register('PreToolUse', {
299
+ hooks: [
300
+ preToolHook(
301
+ async (): Promise<PreToolUseHookOutput> => ({
302
+ decision: 'allow',
303
+ reason: 'ok',
304
+ })
305
+ ),
306
+ ],
307
+ });
308
+ const result = await executeHooks({
309
+ registry,
310
+ input: preToolUseInput('Bash'),
311
+ matchQuery: 'Bash',
312
+ });
313
+ expect(result.decision).toBe('allow');
314
+ expect(result.reason).toBe('ok');
315
+ });
316
+
317
+ it('no decision is set when no hook returns one', async () => {
318
+ const registry = new HookRegistry();
319
+ registry.register('PreToolUse', {
320
+ hooks: [noopPreHook],
321
+ });
322
+ const result = await executeHooks({
323
+ registry,
324
+ input: preToolUseInput('Bash'),
325
+ matchQuery: 'Bash',
326
+ });
327
+ expect(result.decision).toBeUndefined();
328
+ });
329
+ });
330
+
331
+ describe('stop decision folding', () => {
332
+ it('any block wins over continue', async () => {
333
+ const registry = new HookRegistry();
334
+ registry.register('Stop', {
335
+ hooks: [
336
+ stopHook(
337
+ async (): Promise<StopHookOutput> => ({ decision: 'continue' })
338
+ ),
339
+ ],
340
+ });
341
+ registry.register('Stop', {
342
+ hooks: [
343
+ stopHook(
344
+ async (): Promise<StopHookOutput> => ({
345
+ decision: 'block',
346
+ reason: 'more work to do',
347
+ })
348
+ ),
349
+ ],
350
+ });
351
+ const result = await executeHooks({ registry, input: stopInput() });
352
+ expect(result.stopDecision).toBe('block');
353
+ expect(result.reason).toBe('more work to do');
354
+ });
355
+
356
+ it('continue is the aggregated result when no hook blocks', async () => {
357
+ const registry = new HookRegistry();
358
+ registry.register('Stop', {
359
+ hooks: [
360
+ stopHook(
361
+ async (): Promise<StopHookOutput> => ({ decision: 'continue' })
362
+ ),
363
+ ],
364
+ });
365
+ const result = await executeHooks({ registry, input: stopInput() });
366
+ expect(result.stopDecision).toBe('continue');
367
+ });
368
+ });
369
+
370
+ describe('additionalContext accumulation', () => {
371
+ it('accumulates non-empty additionalContext from every hook', async () => {
372
+ const registry = new HookRegistry();
373
+ registry.register('PreToolUse', {
374
+ hooks: [
375
+ preToolHook(
376
+ async (): Promise<PreToolUseHookOutput> => ({
377
+ additionalContext: 'context one',
378
+ })
379
+ ),
380
+ preToolHook(
381
+ async (): Promise<PreToolUseHookOutput> => ({
382
+ additionalContext: '',
383
+ })
384
+ ),
385
+ preToolHook(
386
+ async (): Promise<PreToolUseHookOutput> => ({
387
+ additionalContext: 'context two',
388
+ })
389
+ ),
390
+ ],
391
+ });
392
+ const result = await executeHooks({
393
+ registry,
394
+ input: preToolUseInput('Bash'),
395
+ matchQuery: 'Bash',
396
+ });
397
+ expect(result.additionalContexts.sort()).toEqual([
398
+ 'context one',
399
+ 'context two',
400
+ ]);
401
+ });
402
+ });
403
+
404
+ describe('updatedInput handling', () => {
405
+ it('last-writer-wins on updatedInput follows registration order', async () => {
406
+ const registry = new HookRegistry();
407
+ registry.register('PreToolUse', {
408
+ hooks: [
409
+ preToolHook(
410
+ async (): Promise<PreToolUseHookOutput> => ({
411
+ updatedInput: { cmd: 'first' },
412
+ })
413
+ ),
414
+ ],
415
+ });
416
+ registry.register('PreToolUse', {
417
+ hooks: [
418
+ preToolHook(
419
+ async (): Promise<PreToolUseHookOutput> => ({
420
+ updatedInput: { cmd: 'second' },
421
+ })
422
+ ),
423
+ ],
424
+ });
425
+ registry.register('PreToolUse', {
426
+ hooks: [
427
+ preToolHook(
428
+ async (): Promise<PreToolUseHookOutput> => ({
429
+ updatedInput: { cmd: 'third' },
430
+ })
431
+ ),
432
+ ],
433
+ });
434
+ const result = await executeHooks({
435
+ registry,
436
+ input: preToolUseInput('Bash'),
437
+ matchQuery: 'Bash',
438
+ });
439
+ expect(result.updatedInput).toEqual({ cmd: 'third' });
440
+ });
441
+
442
+ it('last-writer-wins within a single matcher follows hook array order', async () => {
443
+ const registry = new HookRegistry();
444
+ registry.register('PreToolUse', {
445
+ hooks: [
446
+ preToolHook(
447
+ async (): Promise<PreToolUseHookOutput> => ({
448
+ updatedInput: { cmd: 'inner-first' },
449
+ })
450
+ ),
451
+ preToolHook(
452
+ async (): Promise<PreToolUseHookOutput> => ({
453
+ updatedInput: { cmd: 'inner-second' },
454
+ })
455
+ ),
456
+ ],
457
+ });
458
+ const result = await executeHooks({
459
+ registry,
460
+ input: preToolUseInput('Bash'),
461
+ matchQuery: 'Bash',
462
+ });
463
+ expect(result.updatedInput).toEqual({ cmd: 'inner-second' });
464
+ });
465
+ });
466
+
467
+ describe('updatedOutput handling', () => {
468
+ it('flows updatedOutput through the aggregated result', async () => {
469
+ const registry = new HookRegistry();
470
+ registry.register('PostToolUse', {
471
+ hooks: [
472
+ postToolHook(
473
+ async (): Promise<PostToolUseHookOutput> => ({
474
+ updatedOutput: 'redacted',
475
+ })
476
+ ),
477
+ ],
478
+ });
479
+ const result = await executeHooks({
480
+ registry,
481
+ input: postToolUseInput('Bash'),
482
+ matchQuery: 'Bash',
483
+ });
484
+ expect(result.updatedOutput).toBe('redacted');
485
+ });
486
+
487
+ it('last-writer-wins on updatedOutput follows registration order', async () => {
488
+ const registry = new HookRegistry();
489
+ registry.register('PostToolUse', {
490
+ hooks: [
491
+ postToolHook(
492
+ async (): Promise<PostToolUseHookOutput> => ({
493
+ updatedOutput: { tag: 'first' },
494
+ })
495
+ ),
496
+ ],
497
+ });
498
+ registry.register('PostToolUse', {
499
+ hooks: [
500
+ postToolHook(
501
+ async (): Promise<PostToolUseHookOutput> => ({
502
+ updatedOutput: { tag: 'second' },
503
+ })
504
+ ),
505
+ ],
506
+ });
507
+ const result = await executeHooks({
508
+ registry,
509
+ input: postToolUseInput('Bash'),
510
+ matchQuery: 'Bash',
511
+ });
512
+ expect(result.updatedOutput).toEqual({ tag: 'second' });
513
+ });
514
+
515
+ it('leaves updatedOutput undefined when no hook sets it', async () => {
516
+ const registry = new HookRegistry();
517
+ registry.register('PostToolUse', {
518
+ hooks: [postToolHook(async (): Promise<PostToolUseHookOutput> => ({}))],
519
+ });
520
+ const result = await executeHooks({
521
+ registry,
522
+ input: postToolUseInput('Bash'),
523
+ matchQuery: 'Bash',
524
+ });
525
+ expect(result.updatedOutput).toBeUndefined();
526
+ });
527
+ });
528
+
529
+ describe('preventContinuation', () => {
530
+ it('propagates preventContinuation and stopReason', async () => {
531
+ const registry = new HookRegistry();
532
+ registry.register('PostToolUse', {
533
+ hooks: [
534
+ postToolHook(
535
+ async (): Promise<PostToolUseHookOutput> => ({
536
+ preventContinuation: true,
537
+ stopReason: 'budget exhausted',
538
+ })
539
+ ),
540
+ ],
541
+ });
542
+ const result = await executeHooks({
543
+ registry,
544
+ input: postToolUseInput('Bash'),
545
+ matchQuery: 'Bash',
546
+ });
547
+ expect(result.preventContinuation).toBe(true);
548
+ expect(result.stopReason).toBe('budget exhausted');
549
+ });
550
+
551
+ it('keeps the first stopReason when multiple hooks set preventContinuation', async () => {
552
+ const registry = new HookRegistry();
553
+ registry.register('PostToolUse', {
554
+ hooks: [
555
+ postToolHook(
556
+ async (): Promise<PostToolUseHookOutput> => ({
557
+ preventContinuation: true,
558
+ stopReason: 'first writer',
559
+ })
560
+ ),
561
+ ],
562
+ });
563
+ registry.register('PostToolUse', {
564
+ hooks: [
565
+ postToolHook(
566
+ async (): Promise<PostToolUseHookOutput> => ({
567
+ preventContinuation: true,
568
+ stopReason: 'second writer',
569
+ })
570
+ ),
571
+ ],
572
+ });
573
+ const result = await executeHooks({
574
+ registry,
575
+ input: postToolUseInput('Bash'),
576
+ matchQuery: 'Bash',
577
+ });
578
+ expect(result.preventContinuation).toBe(true);
579
+ expect(result.stopReason).toBe('first writer');
580
+ });
581
+
582
+ it('sets preventContinuation even if only the flag, no reason, is present', async () => {
583
+ const registry = new HookRegistry();
584
+ registry.register('PostToolUse', {
585
+ hooks: [
586
+ postToolHook(
587
+ async (): Promise<PostToolUseHookOutput> => ({
588
+ preventContinuation: true,
589
+ })
590
+ ),
591
+ ],
592
+ });
593
+ const result = await executeHooks({
594
+ registry,
595
+ input: postToolUseInput('Bash'),
596
+ matchQuery: 'Bash',
597
+ });
598
+ expect(result.preventContinuation).toBe(true);
599
+ expect(result.stopReason).toBeUndefined();
600
+ });
601
+ });
602
+
603
+ describe('session scoping', () => {
604
+ it('runs session matchers only when sessionId is supplied', async () => {
605
+ const registry = new HookRegistry();
606
+ let globalFired = false;
607
+ let sessionFired = false;
608
+ registry.register('PreToolUse', {
609
+ hooks: [
610
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
611
+ globalFired = true;
612
+ return emptyPreOutput;
613
+ }),
614
+ ],
615
+ });
616
+ registry.registerSession('run-1', 'PreToolUse', {
617
+ hooks: [
618
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
619
+ sessionFired = true;
620
+ return emptyPreOutput;
621
+ }),
622
+ ],
623
+ });
624
+
625
+ await executeHooks({
626
+ registry,
627
+ input: preToolUseInput('Bash'),
628
+ matchQuery: 'Bash',
629
+ });
630
+ expect(globalFired).toBe(true);
631
+ expect(sessionFired).toBe(false);
632
+
633
+ globalFired = false;
634
+ await executeHooks({
635
+ registry,
636
+ input: preToolUseInput('Bash'),
637
+ matchQuery: 'Bash',
638
+ sessionId: 'run-1',
639
+ });
640
+ expect(globalFired).toBe(true);
641
+ expect(sessionFired).toBe(true);
642
+ });
643
+ });
644
+
645
+ describe('once: true self-removal', () => {
646
+ it('removes the matcher after a successful fire', async () => {
647
+ const registry = new HookRegistry();
648
+ let calls = 0;
649
+ const matcher: HookMatcher<'RunStart'> = {
650
+ once: true,
651
+ hooks: [
652
+ runStartHook(async (): Promise<RunStartHookOutput> => {
653
+ calls++;
654
+ return emptyRunStartOutput;
655
+ }),
656
+ ],
657
+ };
658
+ registry.register('RunStart', matcher);
659
+
660
+ await executeHooks({ registry, input: runStartInput() });
661
+ expect(calls).toBe(1);
662
+ expect(registry.getMatchers('RunStart')).toHaveLength(0);
663
+
664
+ await executeHooks({ registry, input: runStartInput() });
665
+ expect(calls).toBe(1);
666
+ });
667
+
668
+ it('removes the matcher even when every hook in it throws (at-most-once dispatch)', async () => {
669
+ const registry = new HookRegistry();
670
+ const matcher: HookMatcher<'RunStart'> = {
671
+ once: true,
672
+ hooks: [
673
+ runStartHook(async (): Promise<RunStartHookOutput> => {
674
+ throw new Error('hook failed');
675
+ }),
676
+ runStartHook(async (): Promise<RunStartHookOutput> => {
677
+ throw new Error('hook also failed');
678
+ }),
679
+ ],
680
+ };
681
+ registry.register('RunStart', matcher);
682
+
683
+ const result = await executeHooks({ registry, input: runStartInput() });
684
+ expect(result.errors).toHaveLength(2);
685
+ expect(registry.getMatchers('RunStart')).toHaveLength(0);
686
+ });
687
+
688
+ it('removes the matcher when at least one hook succeeds', async () => {
689
+ const registry = new HookRegistry();
690
+ const matcher: HookMatcher<'RunStart'> = {
691
+ once: true,
692
+ hooks: [
693
+ runStartHook(async (): Promise<RunStartHookOutput> => {
694
+ throw new Error('boom');
695
+ }),
696
+ noopRunStartHook,
697
+ ],
698
+ };
699
+ registry.register('RunStart', matcher);
700
+
701
+ const result = await executeHooks({ registry, input: runStartInput() });
702
+ expect(result.errors).toHaveLength(1);
703
+ expect(registry.getMatchers('RunStart')).toHaveLength(0);
704
+ });
705
+
706
+ it('removes once-matchers registered for a session from the session scope', async () => {
707
+ const registry = new HookRegistry();
708
+ const matcher: HookMatcher<'PreToolUse'> = {
709
+ once: true,
710
+ hooks: [noopPreHook],
711
+ };
712
+ registry.registerSession('run-1', 'PreToolUse', matcher);
713
+ await executeHooks({
714
+ registry,
715
+ input: preToolUseInput('Bash'),
716
+ matchQuery: 'Bash',
717
+ sessionId: 'run-1',
718
+ });
719
+ expect(registry.getMatchers('PreToolUse', 'run-1')).toHaveLength(0);
720
+ });
721
+
722
+ it('fires exactly once across concurrent executeHooks calls (atomic claim)', async () => {
723
+ const registry = new HookRegistry();
724
+ let calls = 0;
725
+ const matcher: HookMatcher<'RunStart'> = {
726
+ once: true,
727
+ hooks: [
728
+ runStartHook(async (): Promise<RunStartHookOutput> => {
729
+ calls++;
730
+ return emptyRunStartOutput;
731
+ }),
732
+ ],
733
+ };
734
+ registry.register('RunStart', matcher);
735
+
736
+ await Promise.all([
737
+ executeHooks({ registry, input: runStartInput() }),
738
+ executeHooks({ registry, input: runStartInput() }),
739
+ executeHooks({ registry, input: runStartInput() }),
740
+ ]);
741
+
742
+ expect(calls).toBe(1);
743
+ expect(registry.getMatchers('RunStart')).toHaveLength(0);
744
+ });
745
+
746
+ it('fires exactly once across concurrent dispatch even when hooks are slow', async () => {
747
+ const registry = new HookRegistry();
748
+ let calls = 0;
749
+ const matcher: HookMatcher<'RunStart'> = {
750
+ once: true,
751
+ hooks: [
752
+ runStartHook(async (): Promise<RunStartHookOutput> => {
753
+ calls++;
754
+ await new Promise<void>((resolve): void => {
755
+ setTimeout(resolve, 10);
756
+ });
757
+ return emptyRunStartOutput;
758
+ }),
759
+ ],
760
+ };
761
+ registry.register('RunStart', matcher);
762
+
763
+ await Promise.all(
764
+ Array.from({ length: 8 }, () =>
765
+ executeHooks({ registry, input: runStartInput() })
766
+ )
767
+ );
768
+
769
+ expect(calls).toBe(1);
770
+ expect(registry.getMatchers('RunStart')).toHaveLength(0);
771
+ });
772
+ });
773
+
774
+ describe('timeout enforcement', () => {
775
+ it('aborts hooks that exceed the matcher timeout', async () => {
776
+ const registry = new HookRegistry();
777
+ registry.register('RunStart', {
778
+ timeout: 20,
779
+ hooks: [
780
+ runStartHook(
781
+ (_input, signal): Promise<RunStartHookOutput> =>
782
+ new Promise<RunStartHookOutput>((_resolve, reject) => {
783
+ const id = setTimeout((): void => {
784
+ reject(new Error('hook should have been aborted'));
785
+ }, 500);
786
+ signal.addEventListener('abort', (): void => {
787
+ clearTimeout(id);
788
+ reject(new Error('aborted'));
789
+ });
790
+ })
791
+ ),
792
+ ],
793
+ });
794
+
795
+ const start = Date.now();
796
+ const result = await executeHooks({ registry, input: runStartInput() });
797
+ const elapsed = Date.now() - start;
798
+
799
+ expect(result.errors).toHaveLength(1);
800
+ expect(elapsed).toBeLessThan(400);
801
+ });
802
+
803
+ it('times out hooks that ignore the signal and surfaces an abort-shaped error', async () => {
804
+ const registry = new HookRegistry();
805
+ const pendingTimers: NodeJS.Timeout[] = [];
806
+ registry.register('RunStart', {
807
+ timeout: 15,
808
+ hooks: [
809
+ runStartHook(
810
+ (): Promise<RunStartHookOutput> =>
811
+ new Promise<RunStartHookOutput>((resolve): void => {
812
+ const id = setTimeout(
813
+ (): void => resolve(emptyRunStartOutput),
814
+ 500
815
+ );
816
+ pendingTimers.push(id);
817
+ })
818
+ ),
819
+ ],
820
+ });
821
+
822
+ const start = Date.now();
823
+ const result = await executeHooks({ registry, input: runStartInput() });
824
+ const elapsed = Date.now() - start;
825
+
826
+ for (const id of pendingTimers) {
827
+ clearTimeout(id);
828
+ }
829
+ expect(result.errors).toHaveLength(1);
830
+ expect(result.errors[0]?.toLowerCase()).toMatch(
831
+ /timeout|timed out|abort/
832
+ );
833
+ expect(elapsed).toBeLessThan(400);
834
+ });
835
+
836
+ it('honours the batch timeoutMs default when the matcher does not set its own', async () => {
837
+ const registry = new HookRegistry();
838
+ registry.register('RunStart', {
839
+ hooks: [
840
+ runStartHook(
841
+ (_input, signal): Promise<RunStartHookOutput> =>
842
+ new Promise<RunStartHookOutput>((_resolve, reject) => {
843
+ signal.addEventListener('abort', (): void =>
844
+ reject(new Error('aborted'))
845
+ );
846
+ })
847
+ ),
848
+ ],
849
+ });
850
+
851
+ const start = Date.now();
852
+ const result = await executeHooks({
853
+ registry,
854
+ input: runStartInput(),
855
+ timeoutMs: 25,
856
+ });
857
+ const elapsed = Date.now() - start;
858
+
859
+ expect(result.errors).toHaveLength(1);
860
+ expect(elapsed).toBeLessThan(400);
861
+ });
862
+ });
863
+
864
+ describe('error non-fatality', () => {
865
+ it('swallows synchronous throws into the errors array and keeps going', async () => {
866
+ const registry = new HookRegistry();
867
+ let otherRan = false;
868
+ registry.register('PreToolUse', {
869
+ hooks: [
870
+ preToolHook((): Promise<PreToolUseHookOutput> => {
871
+ throw new Error('sync boom');
872
+ }),
873
+ ],
874
+ });
875
+ registry.register('PreToolUse', {
876
+ hooks: [
877
+ preToolHook(async (): Promise<PreToolUseHookOutput> => {
878
+ otherRan = true;
879
+ return { additionalContext: 'still ran' };
880
+ }),
881
+ ],
882
+ });
883
+
884
+ const result = await executeHooks({
885
+ registry,
886
+ input: preToolUseInput('Bash'),
887
+ matchQuery: 'Bash',
888
+ });
889
+ expect(otherRan).toBe(true);
890
+ expect(result.additionalContexts).toEqual(['still ran']);
891
+ expect(result.errors).toHaveLength(1);
892
+ expect(result.errors[0]).toContain('sync boom');
893
+ });
894
+
895
+ it('swallows async rejections', async () => {
896
+ const registry = new HookRegistry();
897
+ registry.register('PreToolUse', {
898
+ hooks: [
899
+ preToolHook(
900
+ async (): Promise<PreToolUseHookOutput> =>
901
+ Promise.reject(new Error('async boom'))
902
+ ),
903
+ ],
904
+ });
905
+ const result = await executeHooks({
906
+ registry,
907
+ input: preToolUseInput('Bash'),
908
+ matchQuery: 'Bash',
909
+ });
910
+ expect(result.errors).toHaveLength(1);
911
+ expect(result.errors[0]).toContain('async boom');
912
+ });
913
+
914
+ it('excludes internal matcher errors from the errors array', async () => {
915
+ const registry = new HookRegistry();
916
+ registry.register('PreToolUse', {
917
+ internal: true,
918
+ hooks: [
919
+ preToolHook(
920
+ async (): Promise<PreToolUseHookOutput> =>
921
+ Promise.reject(new Error('internal failure'))
922
+ ),
923
+ ],
924
+ });
925
+ const result = await executeHooks({
926
+ registry,
927
+ input: preToolUseInput('Bash'),
928
+ matchQuery: 'Bash',
929
+ });
930
+ expect(result.errors).toHaveLength(0);
931
+ });
932
+
933
+ it('routes non-internal errors through an optional logger instead of console', async () => {
934
+ const registry = new HookRegistry();
935
+ registry.register('PreToolUse', {
936
+ hooks: [
937
+ preToolHook(
938
+ async (): Promise<PreToolUseHookOutput> =>
939
+ Promise.reject(new Error('oops'))
940
+ ),
941
+ ],
942
+ });
943
+ const warnings: string[] = [];
944
+ const fakeLogger = {
945
+ warn: (msg: string): void => {
946
+ warnings.push(msg);
947
+ },
948
+ } as unknown as import('winston').Logger;
949
+ await executeHooks({
950
+ registry,
951
+ input: preToolUseInput('Bash'),
952
+ matchQuery: 'Bash',
953
+ logger: fakeLogger,
954
+ });
955
+ expect(warnings).toHaveLength(1);
956
+ expect(warnings[0]).toContain('oops');
957
+ expect(consoleWarnSpy).not.toHaveBeenCalled();
958
+ });
959
+
960
+ it('falls back to console.warn when no logger is supplied', async () => {
961
+ const registry = new HookRegistry();
962
+ registry.register('PreToolUse', {
963
+ hooks: [
964
+ preToolHook(
965
+ async (): Promise<PreToolUseHookOutput> =>
966
+ Promise.reject(new Error('fallback'))
967
+ ),
968
+ ],
969
+ });
970
+ await executeHooks({
971
+ registry,
972
+ input: preToolUseInput('Bash'),
973
+ matchQuery: 'Bash',
974
+ });
975
+ expect(consoleWarnSpy).toHaveBeenCalledTimes(1);
976
+ const firstCall = consoleWarnSpy.mock.calls[0] as unknown[];
977
+ expect(String(firstCall[0])).toContain('fallback');
978
+ });
979
+ });
980
+
981
+ describe('parent AbortSignal combination', () => {
982
+ it('aborts hooks when the caller signal fires', async () => {
983
+ const registry = new HookRegistry();
984
+ registry.register('RunStart', {
985
+ hooks: [
986
+ runStartHook(
987
+ (_input, signal): Promise<RunStartHookOutput> =>
988
+ new Promise<RunStartHookOutput>((_resolve, reject) => {
989
+ signal.addEventListener('abort', (): void =>
990
+ reject(new Error('aborted'))
991
+ );
992
+ })
993
+ ),
994
+ ],
995
+ });
996
+
997
+ const controller = new AbortController();
998
+ setTimeout((): void => controller.abort(), 20);
999
+
1000
+ const start = Date.now();
1001
+ const result = await executeHooks({
1002
+ registry,
1003
+ input: runStartInput(),
1004
+ signal: controller.signal,
1005
+ timeoutMs: 5_000,
1006
+ });
1007
+ const elapsed = Date.now() - start;
1008
+
1009
+ expect(result.errors).toHaveLength(1);
1010
+ expect(elapsed).toBeLessThan(400);
1011
+ });
1012
+ });
1013
+ });