@ai-setting/roy-agent-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/dist/index.js +99145 -0
  2. package/package.json +114 -0
  3. package/src/config/config-component.test.ts +627 -0
  4. package/src/config/config-component.ts +906 -0
  5. package/src/config/config-parser.test.ts +319 -0
  6. package/src/config/config-parser.ts +203 -0
  7. package/src/config/decentralized-config.test.ts +740 -0
  8. package/src/config/env-key.ts +210 -0
  9. package/src/config/env-source.test.ts +252 -0
  10. package/src/config/env-source.ts +301 -0
  11. package/src/config/file-source.test.ts +357 -0
  12. package/src/config/file-source.ts +421 -0
  13. package/src/config/index.ts +24 -0
  14. package/src/config/protocol-resolver.test.ts +217 -0
  15. package/src/config/protocol-resolver.ts +228 -0
  16. package/src/env/agent/agent-component.abort.test.ts +511 -0
  17. package/src/env/agent/agent-component.record-session.test.ts +349 -0
  18. package/src/env/agent/agent-component.test.ts +1389 -0
  19. package/src/env/agent/agent-component.tool-error.test.ts +327 -0
  20. package/src/env/agent/agent-component.ts +1711 -0
  21. package/src/env/agent/agent-config-registration.test.ts +226 -0
  22. package/src/env/agent/agent-config-registration.ts +46 -0
  23. package/src/env/agent/agent-reminder-plugin.integration.test.ts +243 -0
  24. package/src/env/agent/index.ts +10 -0
  25. package/src/env/agent/summary-agent.parse-hint.test.ts +360 -0
  26. package/src/env/agent/summary-agent.ts +508 -0
  27. package/src/env/agent/types.ts +536 -0
  28. package/src/env/commands/commands-component.test.ts +364 -0
  29. package/src/env/commands/commands-component.ts +604 -0
  30. package/src/env/commands/commands-config-registration.test.ts +198 -0
  31. package/src/env/commands/commands-config-registration.ts +38 -0
  32. package/src/env/commands/index.ts +21 -0
  33. package/src/env/commands/parser.test.ts +203 -0
  34. package/src/env/commands/parser.ts +115 -0
  35. package/src/env/commands/types.ts +184 -0
  36. package/src/env/commands-prompt-integration.test.ts +243 -0
  37. package/src/env/component-env.test.ts +119 -0
  38. package/src/env/component.ts +335 -0
  39. package/src/env/constants.test.ts +72 -0
  40. package/src/env/constants.ts +123 -0
  41. package/src/env/debug/debug-component.test.ts +114 -0
  42. package/src/env/debug/debug-component.ts +547 -0
  43. package/src/env/debug/formatters/index.ts +9 -0
  44. package/src/env/debug/formatters/repl-formatter.test.ts +139 -0
  45. package/src/env/debug/formatters/repl-formatter.ts +358 -0
  46. package/src/env/debug/formatters/trace-formatter.test.ts +119 -0
  47. package/src/env/debug/formatters/trace-formatter.ts +191 -0
  48. package/src/env/debug/formatters/tree-formatter.test.ts +107 -0
  49. package/src/env/debug/formatters/tree-formatter.ts +325 -0
  50. package/src/env/debug/index.ts +38 -0
  51. package/src/env/debug/parser/regex-parser.test.ts +201 -0
  52. package/src/env/debug/parser/regex-parser.ts +196 -0
  53. package/src/env/debug/parser/span-builder.test.ts +241 -0
  54. package/src/env/debug/parser/span-builder.ts +386 -0
  55. package/src/env/debug/reader/log-reader.test.ts +170 -0
  56. package/src/env/debug/reader/log-reader.ts +186 -0
  57. package/src/env/debug/reader/span-db-reader.test.ts +118 -0
  58. package/src/env/debug/reader/span-db-reader.ts +201 -0
  59. package/src/env/debug/types.test.ts +187 -0
  60. package/src/env/debug/types.ts +171 -0
  61. package/src/env/environment-init.test.ts +183 -0
  62. package/src/env/environment-lifecycle.test.ts +516 -0
  63. package/src/env/environment-service.test.ts +332 -0
  64. package/src/env/environment.handle-query.test.ts +96 -0
  65. package/src/env/environment.test.ts +232 -0
  66. package/src/env/environment.ts +708 -0
  67. package/src/env/errors.test.ts +165 -0
  68. package/src/env/errors.ts +157 -0
  69. package/src/env/event-source/event-source-agent-handler.test.ts +193 -0
  70. package/src/env/event-source/event-source-agent-handler.ts +111 -0
  71. package/src/env/event-source/event-source-component.process-cleanup.test.ts +236 -0
  72. package/src/env/event-source/event-source-component.stop.test.ts +346 -0
  73. package/src/env/event-source/event-source-component.test.ts +1207 -0
  74. package/src/env/event-source/event-source-component.ts +1379 -0
  75. package/src/env/event-source/event-source-config-registration.test.ts +242 -0
  76. package/src/env/event-source/event-source-config-registration.ts +37 -0
  77. package/src/env/event-source/event-source-integration.test.ts +320 -0
  78. package/src/env/event-source/event-source-platform.test.ts +630 -0
  79. package/src/env/event-source/types.ts +298 -0
  80. package/src/env/hook/global-hook-manager.ts +162 -0
  81. package/src/env/hook/hook-manager.test.ts +374 -0
  82. package/src/env/hook/hook-manager.ts +309 -0
  83. package/src/env/hook/index.ts +38 -0
  84. package/src/env/hook/types.ts +138 -0
  85. package/src/env/index.ts +144 -0
  86. package/src/env/interface.ts +203 -0
  87. package/src/env/llm/hooks.test.ts +293 -0
  88. package/src/env/llm/hooks.ts +316 -0
  89. package/src/env/llm/index.ts +61 -0
  90. package/src/env/llm/invoke-threshold-check.test.ts +88 -0
  91. package/src/env/llm/invoke-timeout.test.ts +54 -0
  92. package/src/env/llm/invoke.test.ts +71 -0
  93. package/src/env/llm/invoke.ts +1039 -0
  94. package/src/env/llm/llm-config.test.ts +523 -0
  95. package/src/env/llm/llm.test.ts +233 -0
  96. package/src/env/llm/llm.ts +568 -0
  97. package/src/env/llm/provider.test.ts +182 -0
  98. package/src/env/llm/provider.ts +108 -0
  99. package/src/env/llm/transform.test.ts +251 -0
  100. package/src/env/llm/transform.ts +286 -0
  101. package/src/env/llm/types.test.ts +580 -0
  102. package/src/env/llm/types.ts +424 -0
  103. package/src/env/log-trace/decorator-otel.test.ts +182 -0
  104. package/src/env/log-trace/decorator.ts +230 -0
  105. package/src/env/log-trace/index.ts +79 -0
  106. package/src/env/log-trace/log-trace-component.test.ts +242 -0
  107. package/src/env/log-trace/log-trace-component.ts +497 -0
  108. package/src/env/log-trace/log-trace-config-registration.test.ts +348 -0
  109. package/src/env/log-trace/log-trace-config-registration.ts +45 -0
  110. package/src/env/log-trace/logger.test.ts +149 -0
  111. package/src/env/log-trace/logger.ts +522 -0
  112. package/src/env/log-trace/opentelemetry/cli-propagation.test.ts +147 -0
  113. package/src/env/log-trace/opentelemetry/cli-propagation.ts +194 -0
  114. package/src/env/log-trace/opentelemetry/integration.test.ts +668 -0
  115. package/src/env/log-trace/opentelemetry/mod.ts +25 -0
  116. package/src/env/log-trace/opentelemetry/propagation-env.test.ts +181 -0
  117. package/src/env/log-trace/opentelemetry/propagation-env.ts +136 -0
  118. package/src/env/log-trace/opentelemetry/propagation.test.ts +259 -0
  119. package/src/env/log-trace/opentelemetry/propagation.ts +215 -0
  120. package/src/env/log-trace/opentelemetry/tracer-provider-context.test.ts +166 -0
  121. package/src/env/log-trace/opentelemetry/tracer-provider.test.ts +379 -0
  122. package/src/env/log-trace/opentelemetry/tracer-provider.ts +612 -0
  123. package/src/env/log-trace/span-storage.test.ts +145 -0
  124. package/src/env/log-trace/span-storage.ts +230 -0
  125. package/src/env/log-trace/trace-context.test.ts +187 -0
  126. package/src/env/log-trace/trace-context.ts +162 -0
  127. package/src/env/log-trace/types.test.ts +63 -0
  128. package/src/env/log-trace/types.ts +172 -0
  129. package/src/env/mcp/README.md +244 -0
  130. package/src/env/mcp/__integration__/mcp-component.integration.test.ts +373 -0
  131. package/src/env/mcp/config.test.ts +74 -0
  132. package/src/env/mcp/config.ts +116 -0
  133. package/src/env/mcp/index.ts +41 -0
  134. package/src/env/mcp/loader.test.ts +161 -0
  135. package/src/env/mcp/loader.ts +209 -0
  136. package/src/env/mcp/mcp-component.test.ts +111 -0
  137. package/src/env/mcp/mcp-component.ts +358 -0
  138. package/src/env/mcp/mcp-config-registration.test.ts +304 -0
  139. package/src/env/mcp/mcp-config-registration.ts +50 -0
  140. package/src/env/mcp/scanner.test.ts +170 -0
  141. package/src/env/mcp/scanner.ts +246 -0
  142. package/src/env/mcp/tool/adapter.test.ts +520 -0
  143. package/src/env/mcp/tool/adapter.ts +521 -0
  144. package/src/env/mcp/tool/index.ts +5 -0
  145. package/src/env/mcp/types.test.ts +171 -0
  146. package/src/env/mcp/types.ts +79 -0
  147. package/src/env/memory/README.md +177 -0
  148. package/src/env/memory/built-in/index.ts +59 -0
  149. package/src/env/memory/built-in/recall-memory.ts +103 -0
  150. package/src/env/memory/built-in/record-memory.ts +148 -0
  151. package/src/env/memory/index.ts +20 -0
  152. package/src/env/memory/memory-component.test.ts +239 -0
  153. package/src/env/memory/memory-component.ts +503 -0
  154. package/src/env/memory/memory-config-registration.test.ts +67 -0
  155. package/src/env/memory/memory-config-registration.ts +48 -0
  156. package/src/env/memory/memory-config.ts +45 -0
  157. package/src/env/memory/memory-file.test.ts +268 -0
  158. package/src/env/memory/plugin/index.ts +48 -0
  159. package/src/env/memory/plugin/memory-agent.test.ts +249 -0
  160. package/src/env/memory/plugin/memory-agent.ts +365 -0
  161. package/src/env/memory/plugin/memory-manager.ts +198 -0
  162. package/src/env/memory/plugin/memory-plugin-agent.test.ts +145 -0
  163. package/src/env/memory/plugin/memory-plugin.ts +210 -0
  164. package/src/env/memory/plugin/plugin-simplified.test.ts +51 -0
  165. package/src/env/memory/plugin/recall-memory.test.ts +106 -0
  166. package/src/env/memory/plugin/recall-memory.ts +53 -0
  167. package/src/env/memory/plugin/types.ts +101 -0
  168. package/src/env/memory/tools/memory-agent-tools.ts +228 -0
  169. package/src/env/memory/types.ts +85 -0
  170. package/src/env/paths.ts +118 -0
  171. package/src/env/prompt/index.ts +18 -0
  172. package/src/env/prompt/memory-prompts.test.ts +91 -0
  173. package/src/env/prompt/prompt-component.test.ts +491 -0
  174. package/src/env/prompt/prompt-component.ts +619 -0
  175. package/src/env/prompt/prompt-config-registration.test.ts +213 -0
  176. package/src/env/prompt/prompt-config-registration.ts +39 -0
  177. package/src/env/prompt/prompts-index.ts +504 -0
  178. package/src/env/prompt/renderer.ts +67 -0
  179. package/src/env/prompt/types.ts +136 -0
  180. package/src/env/session/hooks.ts +18 -0
  181. package/src/env/session/index.ts +37 -0
  182. package/src/env/session/search-query-parser.test.ts +425 -0
  183. package/src/env/session/search-query-parser.ts +171 -0
  184. package/src/env/session/session-checkpoint.test.ts +523 -0
  185. package/src/env/session/session-component.extract-recent-messages.test.ts +209 -0
  186. package/src/env/session/session-component.test.ts +132 -0
  187. package/src/env/session/session-component.ts +1249 -0
  188. package/src/env/session/session-config-registration.test.ts +138 -0
  189. package/src/env/session/session-config-registration.ts +52 -0
  190. package/src/env/session/session-message-converter.test.ts +763 -0
  191. package/src/env/session/session-message-converter.ts +415 -0
  192. package/src/env/session/session-message-e2e.test.ts +448 -0
  193. package/src/env/session/session-search.test.ts +391 -0
  194. package/src/env/session/session-store.test.ts +362 -0
  195. package/src/env/session/session-store.ts +141 -0
  196. package/src/env/session/storage/index.ts +6 -0
  197. package/src/env/session/storage/memory.ts +502 -0
  198. package/src/env/session/storage/sqlite.ts +794 -0
  199. package/src/env/session/types.ts +742 -0
  200. package/src/env/skill/config.ts +39 -0
  201. package/src/env/skill/index.ts +6 -0
  202. package/src/env/skill/parser.test.ts +116 -0
  203. package/src/env/skill/parser.ts +77 -0
  204. package/src/env/skill/scanner.test.ts +211 -0
  205. package/src/env/skill/scanner.ts +119 -0
  206. package/src/env/skill/skill-component.test.ts +234 -0
  207. package/src/env/skill/skill-component.ts +352 -0
  208. package/src/env/skill/skill-config-registration.test.ts +60 -0
  209. package/src/env/skill/skill-config-registration.ts +43 -0
  210. package/src/env/skill/tool/index.ts +1 -0
  211. package/src/env/skill/tool/skill-tool.test.ts +100 -0
  212. package/src/env/skill/tool/skill-tool.ts +72 -0
  213. package/src/env/skill/types.ts +64 -0
  214. package/src/env/task/delegate/delegate-tool.test.ts +498 -0
  215. package/src/env/task/delegate/delegate-tool.ts +1014 -0
  216. package/src/env/task/delegate/index.ts +18 -0
  217. package/src/env/task/delegate/stop-tool.test.ts +140 -0
  218. package/src/env/task/delegate/stop-tool.ts +119 -0
  219. package/src/env/task/delegate/task-events.test.ts +178 -0
  220. package/src/env/task/delegate/task-events.ts +143 -0
  221. package/src/env/task/hooks/contexts.test.ts +92 -0
  222. package/src/env/task/hooks/contexts.ts +192 -0
  223. package/src/env/task/hooks/index.ts +23 -0
  224. package/src/env/task/hooks/task-hook-points.test.ts +32 -0
  225. package/src/env/task/hooks/task-hook-points.ts +54 -0
  226. package/src/env/task/index.ts +7 -0
  227. package/src/env/task/plugins/index.ts +13 -0
  228. package/src/env/task/plugins/task-plugin.test.ts +74 -0
  229. package/src/env/task/plugins/task-plugin.ts +89 -0
  230. package/src/env/task/plugins/task-tag-plugin.test.ts +377 -0
  231. package/src/env/task/plugins/task-tag-plugin.ts +319 -0
  232. package/src/env/task/plugins/task-workflow-extractor.integration.test.ts +226 -0
  233. package/src/env/task/plugins/workflow-extractor-agent.test.ts +107 -0
  234. package/src/env/task/plugins/workflow-extractor-agent.ts +225 -0
  235. package/src/env/task/storage/index.ts +6 -0
  236. package/src/env/task/storage/sqlite-task-store.test.ts +283 -0
  237. package/src/env/task/storage/sqlite-task-store.ts +903 -0
  238. package/src/env/task/storage/task-search.test.ts +291 -0
  239. package/src/env/task/tag-service.test.ts +198 -0
  240. package/src/env/task/tag-service.ts +264 -0
  241. package/src/env/task/task-component.test.ts +193 -0
  242. package/src/env/task/task-component.ts +658 -0
  243. package/src/env/task/task-config-registration.test.ts +57 -0
  244. package/src/env/task/task-config-registration.ts +37 -0
  245. package/src/env/task/task-types.test.ts +137 -0
  246. package/src/env/task/tools/complete-tool.ts +44 -0
  247. package/src/env/task/tools/create-tool.ts +49 -0
  248. package/src/env/task/tools/delete-tool.ts +43 -0
  249. package/src/env/task/tools/get-tool.ts +59 -0
  250. package/src/env/task/tools/index.ts +10 -0
  251. package/src/env/task/tools/list-tool.ts +40 -0
  252. package/src/env/task/tools/operation/create-tool.ts +48 -0
  253. package/src/env/task/tools/operation/delete-tool.ts +43 -0
  254. package/src/env/task/tools/operation/get-tool.ts +43 -0
  255. package/src/env/task/tools/operation/index.ts +9 -0
  256. package/src/env/task/tools/operation/list-tool.ts +40 -0
  257. package/src/env/task/tools/operation/operation-tools.test.ts +274 -0
  258. package/src/env/task/tools/operation/operation-types.ts +75 -0
  259. package/src/env/task/tools/operation/update-tool.ts +47 -0
  260. package/src/env/task/tools/task-tools.test.ts +203 -0
  261. package/src/env/task/tools/task-types.test.ts +75 -0
  262. package/src/env/task/tools/task-types.ts +68 -0
  263. package/src/env/task/tools/update-tool.ts +70 -0
  264. package/src/env/task/types.ts +160 -0
  265. package/src/env/tool/built-in/bash.ts +201 -0
  266. package/src/env/tool/built-in/echo.ts +29 -0
  267. package/src/env/tool/built-in/edit-file.test.ts +136 -0
  268. package/src/env/tool/built-in/edit-file.ts +92 -0
  269. package/src/env/tool/built-in/glob.test.ts +94 -0
  270. package/src/env/tool/built-in/glob.ts +65 -0
  271. package/src/env/tool/built-in/grep.test.ts +122 -0
  272. package/src/env/tool/built-in/grep.ts +108 -0
  273. package/src/env/tool/built-in/index.ts +44 -0
  274. package/src/env/tool/built-in/read-file.test.ts +84 -0
  275. package/src/env/tool/built-in/read-file.ts +75 -0
  276. package/src/env/tool/built-in/write-file.test.ts +119 -0
  277. package/src/env/tool/built-in/write-file.ts +68 -0
  278. package/src/env/tool/index.ts +24 -0
  279. package/src/env/tool/registry.test.ts +257 -0
  280. package/src/env/tool/registry.ts +167 -0
  281. package/src/env/tool/tool-component.test.ts +559 -0
  282. package/src/env/tool/tool-component.ts +563 -0
  283. package/src/env/tool/tool-config-registration.test.ts +249 -0
  284. package/src/env/tool/tool-config-registration.ts +46 -0
  285. package/src/env/tool/types.ts +267 -0
  286. package/src/env/tool/validator.test.ts +143 -0
  287. package/src/env/tool/validator.ts +44 -0
  288. package/src/env/types.ts +180 -0
  289. package/src/env/workflow/ask-user-tool-registration.test.ts +216 -0
  290. package/src/env/workflow/complex-workflow.integration.test.ts +1900 -0
  291. package/src/env/workflow/decorators/decorator-node.ts +229 -0
  292. package/src/env/workflow/decorators/decorator.test.ts +196 -0
  293. package/src/env/workflow/decorators/edge.ts +82 -0
  294. package/src/env/workflow/decorators/index.ts +31 -0
  295. package/src/env/workflow/decorators/node-as.ts +98 -0
  296. package/src/env/workflow/decorators/workflow.ts +54 -0
  297. package/src/env/workflow/engine/dag-manager.test.ts +570 -0
  298. package/src/env/workflow/engine/dag-manager.ts +594 -0
  299. package/src/env/workflow/engine/engine.ts +1422 -0
  300. package/src/env/workflow/engine/event-bus.test.ts +359 -0
  301. package/src/env/workflow/engine/event-bus.ts +156 -0
  302. package/src/env/workflow/engine/executor-agent-session.test.ts +84 -0
  303. package/src/env/workflow/engine/executor.test.ts +619 -0
  304. package/src/env/workflow/engine/executor.ts +593 -0
  305. package/src/env/workflow/engine/index.ts +24 -0
  306. package/src/env/workflow/engine/node-registry.test.ts +560 -0
  307. package/src/env/workflow/engine/node-registry.ts +289 -0
  308. package/src/env/workflow/engine/resume-removed.test.ts +22 -0
  309. package/src/env/workflow/engine/scheduler.test.ts +715 -0
  310. package/src/env/workflow/engine/scheduler.ts +318 -0
  311. package/src/env/workflow/engine/workflow-engine.test.ts +815 -0
  312. package/src/env/workflow/extractor/workflow-converter.ts +306 -0
  313. package/src/env/workflow/fixtures.ts +380 -0
  314. package/src/env/workflow/index.ts +38 -0
  315. package/src/env/workflow/integration/run-resume-unified.test.ts +186 -0
  316. package/src/env/workflow/integration/service-integration.test.ts +267 -0
  317. package/src/env/workflow/metadata/keys.ts +12 -0
  318. package/src/env/workflow/nodes/agent-component-adapter.test.ts +318 -0
  319. package/src/env/workflow/nodes/agent-component-adapter.ts +448 -0
  320. package/src/env/workflow/nodes/agent-node.test.ts +371 -0
  321. package/src/env/workflow/nodes/agent-node.ts +598 -0
  322. package/src/env/workflow/nodes/ask-user-node.ts +113 -0
  323. package/src/env/workflow/nodes/condition-node.ts +200 -0
  324. package/src/env/workflow/nodes/index.ts +9 -0
  325. package/src/env/workflow/nodes/merge-node.ts +141 -0
  326. package/src/env/workflow/nodes/skill-node.test.ts +253 -0
  327. package/src/env/workflow/nodes/skill-node.ts +393 -0
  328. package/src/env/workflow/nodes/tool-node.test.ts +251 -0
  329. package/src/env/workflow/nodes/tool-node.ts +493 -0
  330. package/src/env/workflow/nodes/workflow-llm-history.test.ts +455 -0
  331. package/src/env/workflow/nodes/workflow-node.test.ts +315 -0
  332. package/src/env/workflow/nodes/workflow-node.ts +311 -0
  333. package/src/env/workflow/service/index.ts +27 -0
  334. package/src/env/workflow/service/registry.test.ts +133 -0
  335. package/src/env/workflow/service/registry.ts +71 -0
  336. package/src/env/workflow/service/workflow-service.test.ts +310 -0
  337. package/src/env/workflow/service/workflow-service.ts +393 -0
  338. package/src/env/workflow/storage/index.ts +28 -0
  339. package/src/env/workflow/storage/mock-repositories.ts +385 -0
  340. package/src/env/workflow/storage/sqlite.test.ts +179 -0
  341. package/src/env/workflow/storage/sqlite.ts +163 -0
  342. package/src/env/workflow/storage/workflow-repo.test.ts +780 -0
  343. package/src/env/workflow/storage/workflow-repo.ts +342 -0
  344. package/src/env/workflow/tools/ask-user-tool.ts +82 -0
  345. package/src/env/workflow/tools/index.ts +26 -0
  346. package/src/env/workflow/tools/run-workflow.test.ts +352 -0
  347. package/src/env/workflow/tools/run-workflow.ts +214 -0
  348. package/src/env/workflow/types/context.ts +18 -0
  349. package/src/env/workflow/types/decorators-types.ts +198 -0
  350. package/src/env/workflow/types/event.test.ts +515 -0
  351. package/src/env/workflow/types/event.ts +193 -0
  352. package/src/env/workflow/types/index.ts +49 -0
  353. package/src/env/workflow/types/run.test.ts +437 -0
  354. package/src/env/workflow/types/run.ts +173 -0
  355. package/src/env/workflow/types/workflow-hil.ts +114 -0
  356. package/src/env/workflow/types/workflow-message.test.ts +138 -0
  357. package/src/env/workflow/types/workflow-message.ts +196 -0
  358. package/src/env/workflow/types/workflow-session.test.ts +95 -0
  359. package/src/env/workflow/types/workflow-session.ts +59 -0
  360. package/src/env/workflow/types/workflow.test.ts +495 -0
  361. package/src/env/workflow/types/workflow.ts +195 -0
  362. package/src/env/workflow/types_compat.ts +51 -0
  363. package/src/env/workflow/utils/create-workflow.ts +47 -0
  364. package/src/env/workflow/utils/execution-state.ts +245 -0
  365. package/src/env/workflow/utils/index.ts +18 -0
  366. package/src/env/workflow/utils/node-registry-helper.ts +58 -0
  367. package/src/env/workflow/utils/recovery-validator.test.ts +460 -0
  368. package/src/env/workflow/utils/recovery-validator.ts +377 -0
  369. package/src/env/workflow/utils/session-parser.test.ts +111 -0
  370. package/src/env/workflow/utils/session-parser.ts +94 -0
  371. package/src/env/workflow/utils/session-recovery.test.ts +334 -0
  372. package/src/env/workflow/utils/session-recovery.ts +188 -0
  373. package/src/env/workflow/utils/template-resolver.test.ts +258 -0
  374. package/src/env/workflow/utils/template-resolver.ts +436 -0
  375. package/src/env/workflow/utils/validation-rules.ts +149 -0
  376. package/src/env/workflow/workflow-component.ts +544 -0
  377. package/src/index.ts +422 -0
  378. package/src/utils/id.ts +21 -0
@@ -0,0 +1,393 @@
1
+ import { Node, NodeDefinition, NodeExecutionContext, NodeExecutionResult } from '../types';
2
+
3
+ /**
4
+ * Skill Registry interface for looking up skills
5
+ */
6
+ export interface SkillRegistry {
7
+ getSkill(name: string): Skill | undefined;
8
+ hasSkill(name: string): boolean;
9
+ }
10
+
11
+ /**
12
+ * Skill interface for invocation
13
+ */
14
+ export interface Skill {
15
+ name: string;
16
+ invoke(input: any, context: any): Promise<any>;
17
+ }
18
+
19
+ /**
20
+ * SkillNode invokes a registered Skill with the provided input.
21
+ *
22
+ * Input can contain template strings like {{nodeId.output.path}} that will be
23
+ * resolved from previousOutputs.
24
+ */
25
+ export class SkillNode implements Node {
26
+ readonly type = 'skill';
27
+ readonly id: string;
28
+
29
+ constructor(
30
+ private definition: NodeDefinition,
31
+ private skillRegistry: SkillRegistry
32
+ ) {
33
+ this.id = definition.id;
34
+ }
35
+
36
+ async execute(context: NodeExecutionContext): Promise<NodeExecutionResult> {
37
+ const startTime = Date.now();
38
+
39
+ try {
40
+ // 1. Get skill name from definition.config.skill
41
+ const skillName = this.definition.config?.skill as string | undefined;
42
+
43
+ if (!skillName) {
44
+ throw new Error('Skill name is required. Please specify config.skill in the node definition.');
45
+ }
46
+
47
+ // 2. Look up skill from registry
48
+ const skill = this.skillRegistry.getSkill(skillName);
49
+
50
+ if (!skill) {
51
+ throw new Error(`Skill not found: ${skillName}`);
52
+ }
53
+
54
+ // 3. Prepare input (may reference previous outputs and workflow input)
55
+ const input = this.definition.config?.input ?? {};
56
+ const resolvedInput = this.resolveTemplateReferences(input, context.previousOutputs, context.input as any);
57
+
58
+ // 4. Prepare context for skill invocation
59
+ const skillContext = {
60
+ runId: context.runId,
61
+ workflowName: context.workflowName,
62
+ nodeId: context.nodeId,
63
+ debug: context.debug,
64
+ };
65
+
66
+ // 5. Invoke skill
67
+ const output = await skill.invoke(resolvedInput, skillContext);
68
+
69
+ // 6. Return result
70
+ return {
71
+ output,
72
+ error: undefined,
73
+ durationMs: Date.now() - startTime,
74
+ };
75
+ } catch (error) {
76
+ return {
77
+ output: undefined,
78
+ error: error instanceof Error ? error : new Error(String(error)),
79
+ durationMs: Date.now() - startTime,
80
+ };
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Resolve template references in input values.
86
+ * Templates can be in the format:
87
+ * - {{input.key}} - from workflow input
88
+ * - {{nodeId}} - entire previous node output
89
+ * - {{nodeId.path}} - nested path within previous node output
90
+ * - {{nodes.nodeId}} - entire previous node output (alias)
91
+ * - {{nodes.nodeId.path}} - nested path within previous node output
92
+ *
93
+ * Key behavior for template strings:
94
+ * - If the string is a pure template reference (e.g., "{{nodes.fetch}}"),
95
+ * the resolved value is returned as-is (preserving objects/arrays)
96
+ * - If the string contains text + templates (e.g., "Hello {{name}}"),
97
+ * returns a string with interpolated values
98
+ *
99
+ * @param input - The input object that may contain template strings
100
+ * @param previousOutputs - Map of previous node outputs
101
+ * @param workflowInput - The workflow input parameters
102
+ * @returns The input with template references resolved
103
+ */
104
+ private resolveTemplateReferences(
105
+ input: any,
106
+ previousOutputs: Map<string, any>,
107
+ workflowInput?: Record<string, unknown>
108
+ ): any {
109
+ if (typeof input === 'string') {
110
+ // Check if this is a pure template reference (no surrounding text)
111
+ const pureTemplate = this.extractPureTemplate(input);
112
+ if (pureTemplate) {
113
+ // Pure template reference - resolve and return value as-is (preserve objects/arrays)
114
+ return this.resolvePureTemplate(pureTemplate, previousOutputs, workflowInput);
115
+ }
116
+ // Mixed text + templates - return resolved string
117
+ return this.resolveStringTemplate(input, previousOutputs, workflowInput);
118
+ }
119
+
120
+ if (Array.isArray(input)) {
121
+ return input.map(item => this.resolveTemplateReferences(item, previousOutputs, workflowInput));
122
+ }
123
+
124
+ if (input !== null && typeof input === 'object') {
125
+ const resolved: Record<string, any> = {};
126
+ for (const [key, value] of Object.entries(input)) {
127
+ resolved[key] = this.resolveTemplateReferences(value, previousOutputs, workflowInput);
128
+ }
129
+ return resolved;
130
+ }
131
+
132
+ // Return primitive values as-is
133
+ return input;
134
+ }
135
+
136
+ /**
137
+ * Check if a string is a pure template reference (starts with {{ and ends with }})
138
+ * @param str - String to check
139
+ * @returns The template content if pure, null otherwise
140
+ */
141
+ private extractPureTemplate(str: string): string | null {
142
+ const trimmed = str.trim();
143
+ if (trimmed.startsWith('{{') && trimmed.endsWith('}}') && trimmed.length > 4) {
144
+ // Extract content between {{ and }}
145
+ const content = trimmed.slice(2, -2).trim();
146
+ // Check there's no other text (except the template patterns themselves)
147
+ if (!content.includes('{{') && !content.includes('}}')) {
148
+ return content;
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+
154
+ /**
155
+ * Resolve a pure template reference and return the value as-is.
156
+ * @param templateContent - Content between {{ and }}
157
+ * @param previousOutputs - Map of previous node outputs
158
+ * @param workflowInput - The workflow input parameters
159
+ * @returns The resolved value (may be object, array, or primitive),
160
+ * or the original template string if unresolved
161
+ */
162
+ private resolvePureTemplate(
163
+ templateContent: string,
164
+ previousOutputs: Map<string, any>,
165
+ workflowInput?: Record<string, unknown>
166
+ ): any {
167
+ const trimmed = templateContent.trim();
168
+ const originalTemplate = `{{${templateContent}}}`;
169
+
170
+ // Pattern 1: {{input.key}} or {{input.key.nested}}
171
+ if (trimmed.startsWith('input.')) {
172
+ const value = this.getNestedValue(workflowInput, trimmed.slice(6));
173
+ return value !== undefined ? value : originalTemplate;
174
+ }
175
+
176
+ // Pattern 2: {{nodes.nodeId}} or {{nodes.nodeId.output}}
177
+ if (trimmed.startsWith('nodes.')) {
178
+ const path = trimmed.slice(6);
179
+ const segments = path.split('.');
180
+ const nodeId = segments[0];
181
+ const restPath = segments.slice(1).join('.') || undefined;
182
+ const value = this.resolvePath(nodeId, restPath, previousOutputs);
183
+ return value !== undefined ? value : originalTemplate;
184
+ }
185
+
186
+ // Pattern 3: Bare nodeId (backward compatibility)
187
+ if (trimmed !== 'input' && trimmed !== 'nodes') {
188
+ const segments = trimmed.split('.');
189
+ const nodeId = segments[0];
190
+ const restPath = segments.slice(1).join('.') || undefined;
191
+ const value = this.resolvePath(nodeId, restPath, previousOutputs);
192
+ return value !== undefined ? value : originalTemplate;
193
+ }
194
+
195
+ return originalTemplate;
196
+ }
197
+
198
+ /**
199
+ * Resolve template references in a single string (for mixed text + templates).
200
+ * Supports formats:
201
+ * - {{input.key}} - from workflow input
202
+ * - {{nodeId}} - entire previous node output
203
+ * - {{nodeId.path}} - nested path within previous node output
204
+ * - {{nodes.nodeId}} - entire previous node output (alias)
205
+ * - {{nodes.nodeId.path}} - nested path within previous node output
206
+ *
207
+ * Note: Returns string because the input contains text around the template.
208
+ * For pure template references, use resolvePureTemplate instead.
209
+ */
210
+ private resolveStringTemplate(
211
+ template: string,
212
+ previousOutputs: Map<string, any>,
213
+ workflowInput?: Record<string, unknown>
214
+ ): string {
215
+ let resolved = template;
216
+
217
+ // Pattern 1: {{input.key}} or {{input.key.path}}
218
+ resolved = resolved.replace(/\{\{input\.([^}]+)\}\}/g, (match, path) => {
219
+ const value = this.getNestedValue(workflowInput, path.trim());
220
+ return this.stringifyValue(value, match);
221
+ });
222
+
223
+ // Pattern 2: {{nodeId}} or {{nodeId.path}} (bare nodeId without prefix)
224
+ resolved = resolved.replace(/\{\{([^:.{}][^}]*?)\}\}/g, (match, path) => {
225
+ // Skip if it starts with 'nodes.' or 'input.'
226
+ if (path.trim().startsWith('nodes.') || path.trim().startsWith('input.')) {
227
+ return match;
228
+ }
229
+ // Split into nodeId and optional path
230
+ const segments = path.trim().split('.');
231
+ const nodeId = segments[0];
232
+ const restPath = segments.slice(1).join('.') || undefined;
233
+ const value = this.resolvePath(nodeId, restPath, previousOutputs);
234
+ return this.stringifyValue(value, match);
235
+ });
236
+
237
+ // Pattern 3: {{nodes.nodeId}} or {{nodes.nodeId.path}}
238
+ resolved = resolved.replace(/\{\{nodes\.([^}]+)\}\}/g, (match, path) => {
239
+ const segments = path.trim().split('.');
240
+ const nodeId = segments[0];
241
+ const restPath = segments.slice(1).join('.') || undefined;
242
+ const value = this.resolvePath(nodeId, restPath, previousOutputs);
243
+ return this.stringifyValue(value, match);
244
+ });
245
+
246
+ return resolved;
247
+ }
248
+
249
+ /**
250
+ * Resolve a value from previous outputs.
251
+ * @param nodeId - The node ID to look up
252
+ * @param path - Optional dot-notation path within the node output
253
+ * @param previousOutputs - Map of previous node outputs
254
+ */
255
+ private resolvePath(nodeId: string, path: string | undefined, previousOutputs: Map<string, any>): any {
256
+ // Try exact match first, then try normalized versions (hyphen <-> underscore)
257
+ let nodeOutput = previousOutputs.get(nodeId);
258
+
259
+ if (nodeOutput === undefined) {
260
+ // Try normalized versions for flexibility
261
+ const normalizedUnderscore = nodeId.replace(/-/g, '_');
262
+ const normalizedHyphen = nodeId.replace(/_/g, '-');
263
+
264
+ if (normalizedUnderscore !== nodeId) {
265
+ nodeOutput = previousOutputs.get(normalizedUnderscore);
266
+ }
267
+ if (nodeOutput === undefined && normalizedHyphen !== nodeId) {
268
+ nodeOutput = previousOutputs.get(normalizedHyphen);
269
+ }
270
+ }
271
+
272
+ if (nodeOutput === undefined) {
273
+ return undefined;
274
+ }
275
+
276
+ // Extract from wrapper formats (DecoratorNode: {success, output} and AgentNode: {result, metadata})
277
+ let value = this.extractFromWrapper(nodeOutput);
278
+
279
+ // If no path specified, return the extracted value
280
+ if (!path) {
281
+ return value;
282
+ }
283
+
284
+ // Navigate the path within the node's output
285
+ // Skip first segment if it's a wrapper property since extractFromWrapper already extracted it
286
+ // (e.g., template {{node-0.output.value}} -> path="output.value" -> skip "output")
287
+ let segments = path.split('.');
288
+ if (segments.length > 1 &&
289
+ (segments[0] === 'output' || segments[0] === 'result' || segments[0] === 'metadata')) {
290
+ segments = segments.slice(1);
291
+ }
292
+
293
+ let current: any = value;
294
+
295
+ for (const segment of segments) {
296
+ if (current === null || current === undefined) {
297
+ return undefined;
298
+ }
299
+ current = current[segment];
300
+ }
301
+
302
+ return current;
303
+ }
304
+
305
+ /**
306
+ * Extract the actual output value from wrapper formats.
307
+ *
308
+ * Handles:
309
+ * - AgentNode format: { result, metadata } -> returns result
310
+ * - DecoratorNode format: { success, output, ... } -> returns output
311
+ * - Other formats: returns value as-is
312
+ *
313
+ * @param value - The value to extract from
314
+ * @returns The extracted value
315
+ */
316
+ private extractFromWrapper(value: unknown): unknown {
317
+ if (value === null || value === undefined) {
318
+ return value;
319
+ }
320
+
321
+ if (typeof value !== 'object') {
322
+ return value;
323
+ }
324
+
325
+ // Check for AgentNode wrapper: { result, metadata }
326
+ // This is the signature of AgentNode output
327
+ if ('result' in value && 'metadata' in value) {
328
+ return (value as { result: unknown }).result;
329
+ }
330
+
331
+ // Check for DecoratorNode wrapper: { success, output, ... }
332
+ // This has 'output' but NOT 'result' (that's AgentNode's signature)
333
+ if ('output' in value) {
334
+ return (value as { output: unknown }).output;
335
+ }
336
+
337
+ return value;
338
+ }
339
+
340
+ /**
341
+ * Get nested value from an object using dot-notation path.
342
+ *
343
+ * Special handling: If the object has an 'input' key and we're looking for a path that doesn't
344
+ * exist at the top level, check inside the 'input' key.
345
+ * This allows {{input.city}} to work when workflowInput is wrapped as {input: {city: "..."}}
346
+ */
347
+ private getNestedValue(obj: Record<string, unknown> | undefined, path: string): unknown {
348
+ if (!obj) {
349
+ return undefined;
350
+ }
351
+
352
+ // Special case: If obj has an 'input' key, we might be accessing a key inside it
353
+ // This happens when createExecutionContext wraps globalInput as {input: globalInput}
354
+ if ('input' in obj && path in (obj['input'] as Record<string, unknown>)) {
355
+ // Path exists inside 'input', so look there
356
+ return (obj['input'] as Record<string, unknown>)[path];
357
+ }
358
+
359
+ // Normal case: path.split('.').reduce(...) with obj as initial value
360
+ // This allows {{input.city}} to work when path = "city" and obj = {input: {city: "..."}}
361
+ return path.split('.').reduce((current: unknown, key: string) => {
362
+ if (current && typeof current === 'object') {
363
+ return (current as Record<string, unknown>)[key];
364
+ }
365
+ return undefined;
366
+ }, obj as unknown);
367
+ }
368
+
369
+ /**
370
+ * Convert a value to string for template replacement.
371
+ * - If value is undefined, return the original template
372
+ * - If value is a string, return it directly
373
+ * - If value is null, return 'null'
374
+ * - If value is an object or array, serialize to JSON
375
+ *
376
+ * @param value - The value to stringify
377
+ * @param originalTemplate - The original template string (returned if value is undefined)
378
+ * @returns String representation of the value
379
+ */
380
+ private stringifyValue(value: unknown, originalTemplate: string): string {
381
+ if (value === undefined) {
382
+ return originalTemplate;
383
+ }
384
+ if (value === null) {
385
+ return 'null';
386
+ }
387
+ if (typeof value === 'string') {
388
+ return value;
389
+ }
390
+ // For objects and arrays, serialize to JSON
391
+ return JSON.stringify(value);
392
+ }
393
+ }
@@ -0,0 +1,251 @@
1
+ import { describe, it, expect, beforeEach, jest } from 'vitest';
2
+ import { ToolNode, ToolRegistry, Tool } from './tool-node';
3
+ import type { NodeDefinition, NodeExecutionContext } from '../types';
4
+ import type { EventBus } from '../engine/event-bus';
5
+
6
+ // Mock EventBus
7
+ const mockEventBus = {
8
+ emit: jest.fn(),
9
+ on: jest.fn(),
10
+ off: jest.fn(),
11
+ } as Partial<EventBus> as EventBus;
12
+
13
+ // Mock ToolRegistry
14
+ const mockRegistry = {
15
+ getTool: jest.fn<(name: string) => Tool | undefined>(),
16
+ hasTool: jest.fn<(name: string) => boolean>(),
17
+ } as {
18
+ getTool: ReturnType<typeof jest.fn>;
19
+ hasTool: ReturnType<typeof jest.fn>;
20
+ };
21
+
22
+ // Mock Tool
23
+ const mockTool = {
24
+ name: 'test-tool',
25
+ execute: jest.fn<(input: any) => Promise<any>>(),
26
+ };
27
+
28
+ describe('ToolNode', () => {
29
+ let toolNode: ToolNode;
30
+ let nodeDefinition: NodeDefinition;
31
+ let executionContext: NodeExecutionContext;
32
+
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+
36
+ nodeDefinition = {
37
+ id: 'node-1',
38
+ type: 'tool',
39
+ name: 'Test Tool Node',
40
+ config: {
41
+ tool: 'test-tool',
42
+ input: { message: 'hello' },
43
+ },
44
+ };
45
+
46
+ executionContext = {
47
+ runId: 'run-1',
48
+ workflowName: 'test-workflow',
49
+ nodeId: 'node-1',
50
+ input: {},
51
+ previousOutputs: new Map(),
52
+ config: nodeDefinition.config,
53
+ debug: false,
54
+ eventBus: mockEventBus,
55
+ };
56
+
57
+ mockRegistry.getTool = jest.fn().mockReturnValue(mockTool);
58
+ mockRegistry.hasTool = jest.fn().mockReturnValue(true);
59
+ mockTool.execute = jest.fn().mockResolvedValue({ result: 'success' });
60
+ });
61
+
62
+ describe('constructor', () => {
63
+ it('should create a ToolNode with correct type', () => {
64
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
65
+ expect(toolNode.type).toBe('tool');
66
+ expect(toolNode.id).toBe('node-1');
67
+ });
68
+ });
69
+
70
+ describe('execute', () => {
71
+ it('should execute tool successfully', async () => {
72
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
73
+
74
+ const result = await toolNode.execute(executionContext);
75
+
76
+ expect(result.output).toEqual({ result: 'success' });
77
+ expect(result.error).toBeUndefined();
78
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
79
+ expect(mockRegistry.getTool).toHaveBeenCalledWith('test-tool');
80
+ // Tool is called with (input, context)
81
+ expect(mockTool.execute).toHaveBeenCalledWith(
82
+ { message: 'hello' },
83
+ expect.objectContaining({
84
+ session_id: expect.any(String),
85
+ })
86
+ );
87
+ });
88
+
89
+ it('should return error when tool not found', async () => {
90
+ mockRegistry.getTool = jest.fn().mockReturnValue(undefined);
91
+ mockRegistry.hasTool = jest.fn().mockReturnValue(false);
92
+
93
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
94
+
95
+ const result = await toolNode.execute(executionContext);
96
+
97
+ expect(result.output).toBeUndefined();
98
+ expect(result.error).toBeDefined();
99
+ expect(result.error.message).toContain('Tool not found: test-tool');
100
+ });
101
+
102
+ it('should pass input to tool', async () => {
103
+ const customInput = { param1: 'value1', param2: 123 };
104
+ nodeDefinition.config.input = customInput;
105
+ executionContext.config = nodeDefinition.config;
106
+
107
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
108
+
109
+ await toolNode.execute(executionContext);
110
+
111
+ expect(mockTool.execute).toHaveBeenCalledWith(
112
+ customInput,
113
+ expect.objectContaining({
114
+ session_id: expect.any(String),
115
+ })
116
+ );
117
+ });
118
+
119
+ it('should handle tool execution errors', async () => {
120
+ const error = new Error('Tool execution failed');
121
+ mockTool.execute = jest.fn().mockRejectedValue(error);
122
+
123
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
124
+
125
+ const result = await toolNode.execute(executionContext);
126
+
127
+ expect(result.output).toBeUndefined();
128
+ expect(result.error).toBeDefined();
129
+ expect(result.error.message).toBe('Tool execution failed');
130
+ });
131
+
132
+ it('should resolve template references from previous outputs', async () => {
133
+ // Set up previous outputs
134
+ executionContext.previousOutputs.set('node-0', {
135
+ value: 'from-node-0'
136
+ });
137
+
138
+ // Input contains template reference with nodes. prefix
139
+ nodeDefinition.config.input = {
140
+ message: '{{nodes.node-0.value}}',
141
+ };
142
+ executionContext.config = nodeDefinition.config;
143
+
144
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
145
+
146
+ const result = await toolNode.execute(executionContext);
147
+
148
+ expect(result.output).toEqual({ result: 'success' });
149
+ expect(mockTool.execute).toHaveBeenCalledWith({
150
+ message: 'from-node-0',
151
+ }, expect.any(Object));
152
+ });
153
+
154
+ it('should resolve nested template references', async () => {
155
+ executionContext.previousOutputs.set('data-node', {
156
+ nested: {
157
+ deep: {
158
+ value: 'nested-value',
159
+ }
160
+ }
161
+ });
162
+
163
+ nodeDefinition.config.input = {
164
+ message: '{{nodes.data-node.nested.deep.value}}',
165
+ };
166
+ executionContext.config = nodeDefinition.config;
167
+
168
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
169
+
170
+ await toolNode.execute(executionContext);
171
+
172
+ expect(mockTool.execute).toHaveBeenCalledWith(
173
+ { message: 'nested-value' },
174
+ expect.objectContaining({
175
+ session_id: expect.any(String),
176
+ })
177
+ );
178
+ });
179
+
180
+ it('should handle unresolved template references gracefully', async () => {
181
+ // Template references non-existent node
182
+ nodeDefinition.config.input = {
183
+ message: '{{non-existent.output.value}}',
184
+ };
185
+ executionContext.config = nodeDefinition.config;
186
+
187
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
188
+
189
+ const result = await toolNode.execute(executionContext);
190
+
191
+ // Should still execute with unresolved template
192
+ expect(mockTool.execute).toHaveBeenCalledWith(
193
+ { message: '{{non-existent.output.value}}' },
194
+ expect.any(Object)
195
+ );
196
+ });
197
+
198
+ it('should record execution duration', async () => {
199
+ mockTool.execute = jest.fn().mockImplementation(async () => {
200
+ await new Promise(resolve => setTimeout(resolve, 10));
201
+ return { result: 'success' };
202
+ });
203
+
204
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
205
+
206
+ const result = await toolNode.execute(executionContext);
207
+
208
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
209
+ });
210
+
211
+ it('should use tool name from config.tool', async () => {
212
+ nodeDefinition.config.tool = 'custom-tool';
213
+ executionContext.config = nodeDefinition.config;
214
+
215
+ mockRegistry.getTool = jest.fn().mockReturnValue({
216
+ name: 'custom-tool',
217
+ execute: jest.fn().mockResolvedValue({ custom: true }),
218
+ });
219
+
220
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
221
+
222
+ await toolNode.execute(executionContext);
223
+
224
+ expect(mockRegistry.getTool).toHaveBeenCalledWith('custom-tool');
225
+ });
226
+
227
+ it('should throw error when tool config is missing', async () => {
228
+ nodeDefinition.config = {}; // No tool specified
229
+ executionContext.config = nodeDefinition.config;
230
+
231
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
232
+
233
+ const result = await toolNode.execute(executionContext);
234
+
235
+ expect(result.error).toBeDefined();
236
+ expect(result.error.message).toContain('Tool name is required');
237
+ });
238
+ });
239
+
240
+ describe('ToolRegistry interface', () => {
241
+ it('should check tool existence with hasTool', async () => {
242
+ mockRegistry.hasTool = jest.fn().mockReturnValue(true);
243
+
244
+ toolNode = new ToolNode(nodeDefinition, mockRegistry);
245
+
246
+ // This is tested implicitly - if hasTool returns false,
247
+ // getTool should also return undefined
248
+ expect(mockRegistry.hasTool('test-tool')).toBe(true);
249
+ });
250
+ });
251
+ });