@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,1900 @@
1
+ /**
2
+ * Complex Multi-Node Workflow Integration Tests
3
+ *
4
+ * Tests for complex workflow patterns including:
5
+ * - Parallel execution branches
6
+ * - Diamond patterns (merge and fork)
7
+ * - Error handling and retry logic
8
+ * - Conditional execution
9
+ * - Template variable passing
10
+ * - Nested workflows
11
+ * - Complex dependency graphs
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
15
+ import { z } from 'zod';
16
+ import type { WorkflowDefinition, NodeDefinition } from './types';
17
+
18
+ // ==================== Mock Workflow Executor ====================
19
+
20
+ interface MockNodeExecution {
21
+ nodeId: string;
22
+ status: 'pending' | 'running' | 'completed' | 'failed';
23
+ input: any;
24
+ output: any;
25
+ error?: string;
26
+ startTime?: number;
27
+ endTime?: number;
28
+ retryCount: number;
29
+ }
30
+
31
+ interface ExecutionTrace {
32
+ nodes: Map<string, MockNodeExecution>;
33
+ executionOrder: string[];
34
+ parallelBatches: string[][];
35
+ errors: Array<{ nodeId: string; error: string; attempt: number }>;
36
+ }
37
+
38
+ /**
39
+ * Synchronous Workflow Executor for Testing
40
+ */
41
+ class SyncWorkflowExecutor {
42
+ private trace: ExecutionTrace;
43
+ private nodeConfigs: Map<string, any>;
44
+
45
+ constructor() {
46
+ this.trace = {
47
+ nodes: new Map(),
48
+ executionOrder: [],
49
+ parallelBatches: [],
50
+ errors: [],
51
+ };
52
+ this.nodeConfigs = new Map();
53
+ }
54
+
55
+ setNodeConfig(nodeId: string, config: any): void {
56
+ this.nodeConfigs.set(nodeId, config);
57
+ }
58
+
59
+ getTrace(): ExecutionTrace {
60
+ return this.trace;
61
+ }
62
+
63
+ clearTrace(): void {
64
+ this.trace = {
65
+ nodes: new Map(),
66
+ executionOrder: [],
67
+ parallelBatches: [],
68
+ errors: [],
69
+ };
70
+ }
71
+
72
+ executeNode(nodeId: string, input: any, dependencies: Map<string, any>, nodeType?: string): any {
73
+ const config = this.nodeConfigs.get(nodeId) || {};
74
+
75
+ // Initialize trace entry
76
+ const traceEntry: MockNodeExecution = {
77
+ nodeId,
78
+ status: 'pending',
79
+ input,
80
+ output: null,
81
+ retryCount: 0,
82
+ };
83
+ this.trace.nodes.set(nodeId, traceEntry);
84
+
85
+ // Execute node
86
+ traceEntry.status = 'running';
87
+ traceEntry.startTime = Date.now();
88
+
89
+ // Use provided nodeType or fall back to config.type
90
+ const effectiveNodeType = nodeType || config.type || 'unknown';
91
+ let output: any;
92
+
93
+ switch (effectiveNodeType) {
94
+ case 'tool':
95
+ output = this.executeToolNode(nodeId, config, input);
96
+ break;
97
+ case 'skill':
98
+ output = this.executeSkillNode(nodeId, config, input, dependencies);
99
+ break;
100
+ case 'agent':
101
+ output = this.executeAgentNode(nodeId, config, input, dependencies);
102
+ break;
103
+ case 'condition':
104
+ output = this.executeConditionNode(nodeId, config, input);
105
+ break;
106
+ case 'merge':
107
+ output = this.executeMergeNode(nodeId, config, dependencies);
108
+ break;
109
+ case 'nested':
110
+ case 'workflow':
111
+ output = this.executeNestedWorkflowNode(nodeId, config, input);
112
+ break;
113
+ default:
114
+ output = { result: `${nodeId}_result`, input };
115
+ }
116
+
117
+ traceEntry.output = output;
118
+ traceEntry.status = 'completed';
119
+ traceEntry.endTime = Date.now();
120
+ this.trace.executionOrder.push(nodeId);
121
+
122
+ return output;
123
+ }
124
+
125
+ private executeToolNode(nodeId: string, config: any, input: any): any {
126
+ const toolName = config.tool || 'default-tool';
127
+ if (toolName === 'echo') {
128
+ return { echo: input, message: config.message };
129
+ }
130
+ if (toolName === 'calculator') {
131
+ return { result: 42, operation: config.operation };
132
+ }
133
+ if (toolName === 'validator') {
134
+ return { valid: true, validated: input, source: config.source };
135
+ }
136
+ return { tool: toolName, result: `processed_by_${toolName}` };
137
+ }
138
+
139
+ private executeSkillNode(
140
+ nodeId: string,
141
+ config: any,
142
+ input: any,
143
+ dependencies: Map<string, any>
144
+ ): any {
145
+ const skillName = config.skill || 'default-skill';
146
+ return {
147
+ skill: skillName,
148
+ result: `skill_${skillName}_executed`,
149
+ inputsFromDeps: Object.fromEntries(dependencies),
150
+ };
151
+ }
152
+
153
+ private executeAgentNode(
154
+ nodeId: string,
155
+ config: any,
156
+ input: any,
157
+ dependencies: Map<string, any>
158
+ ): any {
159
+ const agentType = config.agent || 'general';
160
+ return {
161
+ agent: agentType,
162
+ reasoning: `Agent ${agentType} processed input`,
163
+ input,
164
+ };
165
+ }
166
+
167
+ private executeConditionNode(nodeId: string, config: any, input: any): any {
168
+ const condition = config.condition || 'true';
169
+ const result = this.evaluateCondition(condition, input);
170
+ return { conditionMet: result, evaluatedCondition: condition };
171
+ }
172
+
173
+ private executeMergeNode(
174
+ nodeId: string,
175
+ config: any,
176
+ dependencies: Map<string, any>
177
+ ): any {
178
+ const mergedInputs: any = {};
179
+ for (const [depId, depOutput] of dependencies) {
180
+ mergedInputs[depId] = depOutput;
181
+ }
182
+ return {
183
+ merged: true,
184
+ inputs: mergedInputs,
185
+ mergeStrategy: config.strategy || 'collect',
186
+ valid: true,
187
+ };
188
+ }
189
+
190
+ private executeNestedWorkflowNode(nodeId: string, config: any, input: any): any {
191
+ const workflowName = config.workflow || config.workflow_name || 'default-nested';
192
+ return {
193
+ nestedWorkflow: workflowName,
194
+ workflowName,
195
+ input,
196
+ result: `nested_${workflowName}_executed`,
197
+ };
198
+ }
199
+
200
+ private evaluateCondition(condition: string, context: any): boolean {
201
+ // Handle template variable conditions
202
+ if (condition === '{{input.skip}}') {
203
+ return context?.skip !== false && !!context?.skip;
204
+ }
205
+ if (condition === '{{input.mode}}') {
206
+ return !!context?.mode;
207
+ }
208
+ if (condition === 'true') return true;
209
+ if (condition.includes('valid')) return true;
210
+ if (condition.includes('{{process.result}}')) return true;
211
+ return condition !== 'false';
212
+ }
213
+ }
214
+
215
+ // ==================== Workflow Execution Engine ====================
216
+
217
+ function executeWorkflowSync(
218
+ definition: WorkflowDefinition,
219
+ input?: any,
220
+ executor?: SyncWorkflowExecutor
221
+ ): Map<string, any> {
222
+ const exec = executor || new SyncWorkflowExecutor();
223
+ const results = new Map<string, any>();
224
+ const nodeMap = new Map<string, NodeDefinition>();
225
+
226
+ // Build node map and config
227
+ for (const node of definition.nodes) {
228
+ nodeMap.set(node.id, node);
229
+ exec.setNodeConfig(node.id, node.config);
230
+ }
231
+
232
+ // Topological sort for execution order
233
+ const order = topologicalSort(definition);
234
+
235
+ // Execute in order
236
+ const dependencies = new Map<string, any>();
237
+
238
+ for (const nodeId of order) {
239
+ const node = nodeMap.get(nodeId)!;
240
+
241
+ // Get dependency outputs
242
+ const depOutputs = new Map<string, any>();
243
+ for (const depId of node.depends_on || []) {
244
+ depOutputs.set(depId, dependencies.get(depId));
245
+ }
246
+
247
+ // Execute node with explicit node type
248
+ const output = exec.executeNode(nodeId, { ...input }, depOutputs, node.type);
249
+ dependencies.set(nodeId, output);
250
+ results.set(nodeId, output);
251
+ }
252
+
253
+ return results;
254
+ }
255
+
256
+ function topologicalSort(workflow: WorkflowDefinition): string[] {
257
+ const nodeMap = new Map(workflow.nodes.map(n => [n.id, n]));
258
+ const visited = new Set<string>();
259
+ const temp = new Set<string>();
260
+ const order: string[] = [];
261
+
262
+ function visit(nodeId: string): void {
263
+ if (temp.has(nodeId)) {
264
+ throw new Error(`Circular dependency detected: ${nodeId}`);
265
+ }
266
+ if (visited.has(nodeId)) return;
267
+
268
+ temp.add(nodeId);
269
+
270
+ const node = nodeMap.get(nodeId);
271
+ if (node) {
272
+ for (const depId of node.depends_on || []) {
273
+ visit(depId);
274
+ }
275
+ }
276
+
277
+ temp.delete(nodeId);
278
+ visited.add(nodeId);
279
+ order.push(nodeId);
280
+ }
281
+
282
+ // Visit all nodes
283
+ for (const node of workflow.nodes) {
284
+ if (!visited.has(node.id)) {
285
+ visit(node.id);
286
+ }
287
+ }
288
+
289
+ return order;
290
+ }
291
+
292
+ // ==================== Complex Workflow Definitions ====================
293
+
294
+ /**
295
+ * Complex Workflow: Data Processing Pipeline
296
+ *
297
+ * This workflow simulates a complex data processing pipeline with:
298
+ * - 12 nodes total
299
+ * - Parallel data fetching branches
300
+ * - Diamond merge pattern
301
+ * - Conditional processing
302
+ * - Error handling with retry
303
+ */
304
+ const createDataProcessingWorkflow = (): WorkflowDefinition => ({
305
+ name: 'data-processing-pipeline',
306
+ version: '1.0',
307
+ description: 'Complex data processing pipeline with parallel branches',
308
+ config: {
309
+ parallel_limit: 3,
310
+ timeout: 300000,
311
+ },
312
+ nodes: [
313
+ // Entry node - Initialize pipeline
314
+ {
315
+ id: 'init',
316
+ type: 'tool',
317
+ name: 'Initialize Pipeline',
318
+ config: { tool: 'echo', message: 'Starting data pipeline' },
319
+ },
320
+
321
+ // Parallel fetch branch 1
322
+ {
323
+ id: 'fetch-users',
324
+ type: 'tool',
325
+ name: 'Fetch Users',
326
+ config: { tool: 'validator', source: 'users' },
327
+ depends_on: ['init'],
328
+ },
329
+
330
+ // Parallel fetch branch 2
331
+ {
332
+ id: 'fetch-products',
333
+ type: 'tool',
334
+ name: 'Fetch Products',
335
+ config: { tool: 'validator', source: 'products' },
336
+ depends_on: ['init'],
337
+ },
338
+
339
+ // Parallel fetch branch 3
340
+ {
341
+ id: 'fetch-orders',
342
+ type: 'tool',
343
+ name: 'Fetch Orders',
344
+ config: { tool: 'validator', source: 'orders' },
345
+ depends_on: ['init'],
346
+ },
347
+
348
+ // Diamond pattern - merge point
349
+ {
350
+ id: 'merge-data',
351
+ type: 'merge',
352
+ name: 'Merge All Data',
353
+ config: { strategy: 'collect' },
354
+ depends_on: ['fetch-users', 'fetch-products', 'fetch-orders'],
355
+ },
356
+
357
+ // Diamond pattern - fork to processing branches
358
+ {
359
+ id: 'validate-data',
360
+ type: 'condition',
361
+ name: 'Validate Merged Data',
362
+ config: { condition: '{{merge-data.valid}}' },
363
+ depends_on: ['merge-data'],
364
+ },
365
+
366
+ // Processing branch 1 - with retry
367
+ {
368
+ id: 'process-users',
369
+ type: 'tool',
370
+ name: 'Process Users',
371
+ config: { tool: 'calculator', operation: 'user-scoring' },
372
+ depends_on: ['validate-data'],
373
+ retry: { max_attempts: 3, backoff: 'exponential', initial_delay: 1000 },
374
+ },
375
+
376
+ // Processing branch 2
377
+ {
378
+ id: 'process-products',
379
+ type: 'tool',
380
+ name: 'Process Products',
381
+ config: { tool: 'calculator', operation: 'product-rating' },
382
+ depends_on: ['validate-data'],
383
+ },
384
+
385
+ // Diamond merge - final processing
386
+ {
387
+ id: 'finalize-results',
388
+ type: 'tool',
389
+ name: 'Finalize Results',
390
+ config: { tool: 'echo', message: 'Finalizing' },
391
+ depends_on: ['process-users', 'process-products'],
392
+ },
393
+
394
+ // Conditional node
395
+ {
396
+ id: 'generate-report',
397
+ type: 'condition',
398
+ name: 'Check Report Generation',
399
+ config: { condition: '{{input.generateReport}}' },
400
+ depends_on: ['finalize-results'],
401
+ },
402
+
403
+ // Nested workflow call
404
+ {
405
+ id: 'create-report',
406
+ type: 'nested',
407
+ name: 'Create Report',
408
+ config: { workflow: 'report-generation' },
409
+ depends_on: ['generate-report'],
410
+ },
411
+
412
+ // Final output node
413
+ {
414
+ id: 'output',
415
+ type: 'tool',
416
+ name: 'Output Results',
417
+ config: { tool: 'echo', message: 'Pipeline complete' },
418
+ depends_on: ['create-report'],
419
+ },
420
+ ],
421
+ entry: 'init',
422
+ outputs: [
423
+ { name: 'final_result', source: 'output', path: 'result' },
424
+ { name: 'user_data', source: 'process-users', path: 'result' },
425
+ { name: 'product_data', source: 'process-products', path: 'result' },
426
+ ],
427
+ metadata: {
428
+ author: 'Roy Agent',
429
+ tags: ['data-processing', 'pipeline', 'complex'],
430
+ },
431
+ });
432
+
433
+ /**
434
+ * Complex Workflow: Parallel Processing with Error Handling
435
+ *
436
+ * Features:
437
+ * - Multiple parallel branches
438
+ * - Retry logic on failure-prone nodes
439
+ * - Error recovery paths
440
+ * - Timeout handling
441
+ */
442
+ const createParallelProcessingWorkflow = (): WorkflowDefinition => ({
443
+ name: 'parallel-processing',
444
+ version: '1.0',
445
+ description: 'Parallel processing with error handling and retry',
446
+ nodes: [
447
+ { id: 'entry', type: 'tool', config: { tool: 'echo' } },
448
+
449
+ // Parallel branch A
450
+ { id: 'task-a1', type: 'tool', config: { tool: 'calculator' }, depends_on: ['entry'] },
451
+ { id: 'task-a2', type: 'tool', config: { tool: 'calculator' }, depends_on: ['task-a1'] },
452
+ { id: 'task-a3', type: 'tool', config: { tool: 'calculator' }, depends_on: ['task-a2'] },
453
+
454
+ // Parallel branch B
455
+ { id: 'task-b1', type: 'tool', config: { tool: 'calculator' }, depends_on: ['entry'] },
456
+ { id: 'task-b2', type: 'tool', config: { tool: 'calculator' }, depends_on: ['task-b1'] },
457
+ {
458
+ id: 'task-b3',
459
+ type: 'tool',
460
+ config: { tool: 'calculator' },
461
+ depends_on: ['task-b1'],
462
+ retry: { max_attempts: 2, backoff: 'fixed', initial_delay: 500 },
463
+ },
464
+
465
+ // Parallel branch C - with potential failure
466
+ { id: 'task-c1', type: 'tool', config: { tool: 'calculator' }, depends_on: ['entry'] },
467
+ {
468
+ id: 'task-c2',
469
+ type: 'tool',
470
+ config: { tool: 'calculator' },
471
+ depends_on: ['task-c1'],
472
+ retry: { max_attempts: 3, backoff: 'exponential', initial_delay: 1000 },
473
+ },
474
+
475
+ // Merge all branches
476
+ {
477
+ id: 'merge-ab',
478
+ type: 'merge',
479
+ config: { strategy: 'collect' },
480
+ depends_on: ['task-a3', 'task-b2', 'task-c2'],
481
+ },
482
+
483
+ // Final processing
484
+ { id: 'final', type: 'tool', config: { tool: 'echo' }, depends_on: ['merge-ab'] },
485
+ ],
486
+ });
487
+
488
+ /**
489
+ * Complex Workflow: Nested Workflow Composition
490
+ *
491
+ * Features:
492
+ * - Nested workflow calls
493
+ * - Multiple levels of abstraction
494
+ * - Shared state passing
495
+ * - Complex dependency chains
496
+ */
497
+ const createNestedWorkflow = (): WorkflowDefinition => ({
498
+ name: 'nested-workflow-composition',
499
+ version: '1.0',
500
+ nodes: [
501
+ { id: 'start', type: 'tool', config: { tool: 'echo' }, depends_on: [] },
502
+
503
+ // First level nested
504
+ { id: 'sub-workflow-1', type: 'workflow', config: { workflow_name: 'sub-workflow-a' }, depends_on: ['start'] },
505
+ { id: 'sub-workflow-2', type: 'workflow', config: { workflow_name: 'sub-workflow-b' }, depends_on: ['start'] },
506
+
507
+ // Second level - parallel execution
508
+ { id: 'level-2-a', type: 'tool', config: { tool: 'validator' }, depends_on: ['sub-workflow-1'] },
509
+ { id: 'level-2-b', type: 'tool', config: { tool: 'validator' }, depends_on: ['sub-workflow-1'] },
510
+ { id: 'level-2-c', type: 'tool', config: { tool: 'validator' }, depends_on: ['sub-workflow-2'] },
511
+
512
+ // Merge and continue
513
+ { id: 'merge-2', type: 'merge', config: { strategy: 'collect' }, depends_on: ['level-2-a', 'level-2-b', 'level-2-c'] },
514
+
515
+ // Third level
516
+ { id: 'level-3', type: 'tool', config: { tool: 'calculator' }, depends_on: ['merge-2'] },
517
+
518
+ // Final nested call
519
+ { id: 'final-nested', type: 'workflow', config: { workflow_name: 'final-workflow' }, depends_on: ['level-3'] },
520
+ ],
521
+ });
522
+
523
+ /**
524
+ * Complex Workflow: Conditional Execution Graph
525
+ *
526
+ * Features:
527
+ * - Multiple conditional branches
528
+ * - Skip logic
529
+ * - Dynamic paths based on runtime conditions
530
+ */
531
+ const createConditionalWorkflow = (): WorkflowDefinition => ({
532
+ name: 'conditional-execution',
533
+ version: '1.0',
534
+ nodes: [
535
+ { id: 'init', type: 'tool', config: { tool: 'echo' } },
536
+
537
+ // First condition
538
+ {
539
+ id: 'check-mode',
540
+ type: 'condition',
541
+ config: { condition: '{{input.mode}}' },
542
+ depends_on: ['init'],
543
+ },
544
+
545
+ // Mode A path
546
+ { id: 'mode-a-1', type: 'tool', config: { tool: 'calculator' }, depends_on: ['check-mode'] },
547
+ { id: 'mode-a-2', type: 'tool', config: { tool: 'calculator' }, depends_on: ['mode-a-1'] },
548
+
549
+ // Mode B path
550
+ { id: 'mode-b-1', type: 'tool', config: { tool: 'validator' }, depends_on: ['check-mode'] },
551
+ { id: 'mode-b-2', type: 'tool', config: { tool: 'validator' }, depends_on: ['mode-b-1'] },
552
+
553
+ // Merge after conditional paths
554
+ { id: 'merge-cond', type: 'merge', config: { strategy: 'collect' }, depends_on: ['mode-a-2', 'mode-b-2'] },
555
+
556
+ // Final check
557
+ {
558
+ id: 'final-check',
559
+ type: 'condition',
560
+ config: { condition: '{{merge-cond.valid}}' },
561
+ depends_on: ['merge-cond'],
562
+ },
563
+
564
+ // Output
565
+ { id: 'output', type: 'tool', config: { tool: 'echo' }, depends_on: ['final-check'] },
566
+ ],
567
+ });
568
+
569
+ /**
570
+ * Complex Workflow: Diamond Pattern
571
+ *
572
+ * A -> B1 -> C -> D
573
+ * -> B2 ->
574
+ * -> B3 ->
575
+ *
576
+ * Classic diamond dependency pattern
577
+ */
578
+ const createDiamondWorkflow = (): WorkflowDefinition => ({
579
+ name: 'diamond-pattern',
580
+ version: '1.0',
581
+ nodes: [
582
+ { id: 'A', type: 'tool', config: { tool: 'echo' }, depends_on: [] },
583
+ { id: 'B1', type: 'tool', config: { tool: 'calculator' }, depends_on: ['A'] },
584
+ { id: 'B2', type: 'tool', config: { tool: 'calculator' }, depends_on: ['A'] },
585
+ { id: 'B3', type: 'tool', config: { tool: 'calculator' }, depends_on: ['A'] },
586
+ {
587
+ id: 'C',
588
+ type: 'tool',
589
+ config: { tool: 'calculator' },
590
+ depends_on: ['B1', 'B2', 'B3'],
591
+ retry: { max_attempts: 2, backoff: 'exponential', initial_delay: 500 },
592
+ },
593
+ { id: 'D', type: 'tool', config: { tool: 'echo' }, depends_on: ['C'] },
594
+ ],
595
+ });
596
+
597
+ /**
598
+ * Complex Workflow: Multi-Node Type Orchestration
599
+ *
600
+ * Demonstrates chaining different node types:
601
+ * - ToolNode → AgentNode → SkillNode → WorkflowNode → ToolNode
602
+ * - Template variable passing between different node types
603
+ * - Real-world AI pipeline pattern
604
+ */
605
+ const createMultiNodeTypeWorkflow = (): WorkflowDefinition => ({
606
+ name: 'multi-node-type-orchestration',
607
+ version: '1.0',
608
+ description: 'Orchestration across Tool, Agent, Skill, and Workflow nodes',
609
+ nodes: [
610
+ // ========== Step 1: Tool - Data Preparation ==========
611
+ {
612
+ id: 'prepare-data',
613
+ type: 'tool',
614
+ name: 'Prepare Input Data',
615
+ config: { tool: 'echo', message: 'Preparing initial data' },
616
+ depends_on: [],
617
+ },
618
+
619
+ // ========== Step 2: Agent - Analysis ==========
620
+ {
621
+ id: 'analyze-data',
622
+ type: 'agent',
623
+ name: 'AI Data Analyst',
624
+ config: {
625
+ agent_type: 'general',
626
+ prompt: 'Analyze the input data: {{prepare-data.output}}. Provide insights.',
627
+ },
628
+ depends_on: ['prepare-data'],
629
+ },
630
+
631
+ // ========== Step 3: Tool - Validate Analysis ==========
632
+ {
633
+ id: 'validate-analysis',
634
+ type: 'tool',
635
+ name: 'Validate Analysis Results',
636
+ config: { tool: 'validator', source: 'analysis' },
637
+ depends_on: ['analyze-data'],
638
+ },
639
+
640
+ // ========== Step 4: Agent - Decision Making ==========
641
+ {
642
+ id: 'make-decision',
643
+ type: 'agent',
644
+ name: 'Decision Agent',
645
+ config: {
646
+ agent_type: 'general',
647
+ prompt: 'Based on analysis: {{analyze-data.output}}, decide next steps.',
648
+ },
649
+ depends_on: ['validate-analysis'],
650
+ },
651
+
652
+ // ========== Step 5: Skill - Transform Data ==========
653
+ {
654
+ id: 'transform-data',
655
+ type: 'skill',
656
+ name: 'Data Transformation Skill',
657
+ config: {
658
+ skill: 'data-transform',
659
+ input: {
660
+ data: '{{analyze-data.output}}',
661
+ format: 'json',
662
+ },
663
+ },
664
+ depends_on: ['make-decision'],
665
+ },
666
+
667
+ // ========== Step 6: Nested Workflow ==========
668
+ {
669
+ id: 'process-workflow',
670
+ type: 'workflow',
671
+ name: 'Sub-Process Workflow',
672
+ config: {
673
+ workflow_name: 'data-processing-subflow',
674
+ input: {
675
+ source: '{{transform-data.output}}',
676
+ },
677
+ },
678
+ depends_on: ['transform-data'],
679
+ },
680
+
681
+ // ========== Step 7: Tool - Final Output ==========
682
+ {
683
+ id: 'format-output',
684
+ type: 'tool',
685
+ name: 'Format Final Output',
686
+ config: { tool: 'calculator', operation: 'format' },
687
+ depends_on: ['process-workflow'],
688
+ },
689
+ ],
690
+ outputs: [
691
+ { name: 'analysis_result', source: 'analyze-data', path: 'output' },
692
+ { name: 'decision', source: 'make-decision', path: 'output' },
693
+ { name: 'transformed_data', source: 'transform-data', path: 'output' },
694
+ { name: 'final_result', source: 'format-output', path: 'result' },
695
+ ],
696
+ metadata: {
697
+ author: 'Roy Agent',
698
+ tags: ['multi-node', 'orchestration', 'ai-pipeline'],
699
+ },
700
+ });
701
+
702
+ /**
703
+ * Complex Workflow: Tool-Skill-Agent Chain
704
+ *
705
+ * A realistic AI pipeline demonstrating:
706
+ * - Tool (data fetch)
707
+ * - Agent (reasoning)
708
+ * - Skill (processing)
709
+ * - Another Agent (verification)
710
+ * - Tool (output)
711
+ */
712
+ const createToolSkillAgentChain = (): WorkflowDefinition => ({
713
+ name: 'tool-skill-agent-chain',
714
+ version: '1.0',
715
+ nodes: [
716
+ // Tool: Fetch raw data
717
+ {
718
+ id: 'fetch-raw-data',
719
+ type: 'tool',
720
+ name: 'Fetch Raw Data',
721
+ config: { tool: 'echo', message: 'Fetching data from source' },
722
+ depends_on: [],
723
+ },
724
+
725
+ // Agent: Initial understanding
726
+ {
727
+ id: 'understand-context',
728
+ type: 'agent',
729
+ name: 'Context Understanding Agent',
730
+ config: {
731
+ agent_type: 'general',
732
+ prompt: 'Understand the context of: {{fetch-raw-data.output}}',
733
+ },
734
+ depends_on: ['fetch-raw-data'],
735
+ },
736
+
737
+ // Skill: Process with skill
738
+ {
739
+ id: 'process-with-skill',
740
+ type: 'skill',
741
+ name: 'Processing Skill',
742
+ config: {
743
+ skill: 'text-processor',
744
+ input: {
745
+ text: '{{understand-context.output}}',
746
+ options: { lowercase: true },
747
+ },
748
+ },
749
+ depends_on: ['understand-context'],
750
+ },
751
+
752
+ // Agent: Deep reasoning
753
+ {
754
+ id: 'deep-reasoning',
755
+ type: 'agent',
756
+ name: 'Deep Reasoning Agent',
757
+ config: {
758
+ agent_type: 'general',
759
+ prompt: 'Perform deep reasoning on: {{process-with-skill.output}}',
760
+ },
761
+ depends_on: ['process-with-skill'],
762
+ },
763
+
764
+ // Skill: Another skill processing
765
+ {
766
+ id: 'enhance-result',
767
+ type: 'skill',
768
+ name: 'Enhancement Skill',
769
+ config: {
770
+ skill: 'enhancer',
771
+ input: {
772
+ data: '{{deep-reasoning.output}}',
773
+ },
774
+ },
775
+ depends_on: ['deep-reasoning'],
776
+ },
777
+
778
+ // Tool: Final validation
779
+ {
780
+ id: 'final-validate',
781
+ type: 'tool',
782
+ name: 'Final Validation',
783
+ config: { tool: 'validator', source: 'final' },
784
+ depends_on: ['enhance-result'],
785
+ },
786
+ ],
787
+ });
788
+
789
+ /**
790
+ * Complex Workflow: Parallel Tool-Agent Branches
791
+ *
792
+ * Multiple parallel branches, each with Tool → Agent pattern
793
+ */
794
+ const createParallelToolAgentWorkflow = (): WorkflowDefinition => ({
795
+ name: 'parallel-tool-agent-branches',
796
+ version: '1.0',
797
+ nodes: [
798
+ // Entry
799
+ { id: 'start', type: 'tool', config: { tool: 'echo' }, depends_on: [] },
800
+
801
+ // Branch A: Tool → Agent
802
+ { id: 'tool-a', type: 'tool', config: { tool: 'calculator' }, depends_on: ['start'] },
803
+ {
804
+ id: 'agent-a',
805
+ type: 'agent',
806
+ config: { agent_type: 'general', prompt: 'Process: {{tool-a.output}}' },
807
+ depends_on: ['tool-a'],
808
+ },
809
+
810
+ // Branch B: Tool → Agent
811
+ { id: 'tool-b', type: 'tool', config: { tool: 'validator' }, depends_on: ['start'] },
812
+ {
813
+ id: 'agent-b',
814
+ type: 'agent',
815
+ config: { agent_type: 'general', prompt: 'Process: {{tool-b.output}}' },
816
+ depends_on: ['tool-b'],
817
+ },
818
+
819
+ // Branch C: Tool → Agent
820
+ { id: 'tool-c', type: 'tool', config: { tool: 'echo' }, depends_on: ['start'] },
821
+ {
822
+ id: 'agent-c',
823
+ type: 'agent',
824
+ config: { agent_type: 'general', prompt: 'Process: {{tool-c.output}}' },
825
+ depends_on: ['tool-c'],
826
+ },
827
+
828
+ // Merge all agent outputs
829
+ {
830
+ id: 'merge-agents',
831
+ type: 'merge',
832
+ config: { strategy: 'collect' },
833
+ depends_on: ['agent-a', 'agent-b', 'agent-c'],
834
+ },
835
+
836
+ // Final aggregation agent
837
+ {
838
+ id: 'aggregate-results',
839
+ type: 'agent',
840
+ name: 'Aggregation Agent',
841
+ config: {
842
+ agent_type: 'general',
843
+ prompt: 'Aggregate and summarize results from all branches.',
844
+ },
845
+ depends_on: ['merge-agents'],
846
+ },
847
+
848
+ // Output tool
849
+ { id: 'output', type: 'tool', config: { tool: 'echo' }, depends_on: ['aggregate-results'] },
850
+ ],
851
+ });
852
+
853
+ /**
854
+ * Complex Workflow: Template Variable Passing
855
+ *
856
+ * Tests HIL (Human Input Language) template variable passing
857
+ */
858
+ const createTemplateVariableWorkflow = (): WorkflowDefinition => ({
859
+ name: 'template-variables',
860
+ version: '1.0',
861
+ nodes: [
862
+ {
863
+ id: 'init',
864
+ type: 'tool',
865
+ config: { tool: 'echo', message: '{{input.greeting}}' },
866
+ },
867
+ {
868
+ id: 'process',
869
+ type: 'tool',
870
+ config: { tool: 'calculator', expression: '{{init.result}} + {{input.value}}' },
871
+ depends_on: ['init'],
872
+ },
873
+ {
874
+ id: 'validate',
875
+ type: 'condition',
876
+ config: { condition: '{{process.result}} > {{input.threshold}}' },
877
+ depends_on: ['process'],
878
+ },
879
+ {
880
+ id: 'output',
881
+ type: 'tool',
882
+ config: { tool: 'echo', message: 'Result: {{process.result}}, Valid: {{validate.conditionMet}}' },
883
+ depends_on: ['validate'],
884
+ },
885
+ ],
886
+ });
887
+
888
+ /**
889
+ * Complex Workflow: Complex Dependency Graph
890
+ *
891
+ * Features:
892
+ * - Multiple entry points
893
+ * - Complex merge patterns
894
+ * - Cross-branch dependencies
895
+ */
896
+ const createComplexDependencyWorkflow = (): WorkflowDefinition => ({
897
+ name: 'complex-dependency-graph',
898
+ version: '1.0',
899
+ nodes: [
900
+ // Two entry points
901
+ { id: 'entry-1', type: 'tool', config: { tool: 'echo' }, depends_on: [] },
902
+ { id: 'entry-2', type: 'tool', config: { tool: 'echo' }, depends_on: [] },
903
+
904
+ // Parallel branches from entry-1
905
+ { id: 'branch-1a', type: 'tool', config: { tool: 'calculator' }, depends_on: ['entry-1'] },
906
+ { id: 'branch-1b', type: 'tool', config: { tool: 'calculator' }, depends_on: ['entry-1'] },
907
+
908
+ // Parallel branches from entry-2
909
+ { id: 'branch-2a', type: 'tool', config: { tool: 'validator' }, depends_on: ['entry-2'] },
910
+ { id: 'branch-2b', type: 'tool', config: { tool: 'validator' }, depends_on: ['entry-2'] },
911
+
912
+ // Cross-branch dependency: branch-1a depends on branch-2a
913
+ { id: 'cross-dep', type: 'tool', config: { tool: 'calculator' }, depends_on: ['branch-1a', 'branch-2a'] },
914
+
915
+ // Merge all
916
+ { id: 'merge-1', type: 'merge', config: { strategy: 'collect' }, depends_on: ['branch-1b', 'branch-2b', 'cross-dep'] },
917
+
918
+ // Final processing
919
+ { id: 'final', type: 'tool', config: { tool: 'echo' }, depends_on: ['merge-1'] },
920
+ ],
921
+ });
922
+
923
+ // ==================== Test Suites ====================
924
+
925
+ describe('Complex Multi-Node Workflow Integration Tests', () => {
926
+ let executor: SyncWorkflowExecutor;
927
+
928
+ beforeEach(() => {
929
+ executor = new SyncWorkflowExecutor();
930
+ });
931
+
932
+ afterEach(() => {
933
+ executor.clearTrace();
934
+ });
935
+
936
+ // ==================== Workflow Structure Tests ====================
937
+
938
+ describe('Data Processing Pipeline Workflow', () => {
939
+ const workflow = createDataProcessingWorkflow();
940
+
941
+ it('should have 12 nodes', () => {
942
+ expect(workflow.nodes).toHaveLength(12);
943
+ });
944
+
945
+ it('should have correct node IDs', () => {
946
+ const nodeIds = workflow.nodes.map(n => n.id);
947
+ expect(nodeIds).toContain('init');
948
+ expect(nodeIds).toContain('merge-data');
949
+ expect(nodeIds).toContain('finalize-results');
950
+ expect(nodeIds).toContain('output');
951
+ });
952
+
953
+ it('should have parallel branches from init', () => {
954
+ const fetchNodes = workflow.nodes.filter(n => n.depends_on && n.depends_on.includes('init'));
955
+ expect(fetchNodes.length).toBe(3); // fetch-users, fetch-products, fetch-orders
956
+ });
957
+
958
+ it('should have diamond pattern with merge-data', () => {
959
+ const mergeNode = workflow.nodes.find(n => n.id === 'merge-data')!;
960
+ expect(mergeNode.depends_on).toBeDefined();
961
+ expect(mergeNode.depends_on).toContain('fetch-users');
962
+ expect(mergeNode.depends_on).toContain('fetch-products');
963
+ expect(mergeNode.depends_on).toContain('fetch-orders');
964
+ });
965
+
966
+ it('should have nodes with retry configuration', () => {
967
+ const retryNode = workflow.nodes.find(n => n.id === 'process-users')!;
968
+ expect(retryNode.retry).toBeDefined();
969
+ expect(retryNode.retry?.max_attempts).toBe(3);
970
+ expect(retryNode.retry?.backoff).toBe('exponential');
971
+ });
972
+
973
+ it('should have nested workflow node', () => {
974
+ const nestedNode = workflow.nodes.find(n => n.id === 'create-report')!;
975
+ expect(nestedNode.type).toBe('nested');
976
+ expect(nestedNode.config.workflow).toBe('report-generation');
977
+ });
978
+
979
+ it('should have correct output definitions', () => {
980
+ expect(workflow.outputs).toHaveLength(3);
981
+ expect(workflow.outputs?.find(o => o.name === 'final_result')?.source).toBe('output');
982
+ });
983
+
984
+ it('should have metadata with tags', () => {
985
+ expect(workflow.metadata?.tags).toContain('data-processing');
986
+ expect(workflow.metadata?.author).toBe('Roy Agent');
987
+ });
988
+
989
+ it('should execute in correct topological order', () => {
990
+ const order = topologicalSort(workflow);
991
+
992
+ // init should come before fetch-*
993
+ expect(order.indexOf('init')).toBeLessThan(order.indexOf('fetch-users'));
994
+ expect(order.indexOf('init')).toBeLessThan(order.indexOf('fetch-products'));
995
+ expect(order.indexOf('init')).toBeLessThan(order.indexOf('fetch-orders'));
996
+
997
+ // merge-data should come after all fetches
998
+ expect(order.indexOf('merge-data')).toBeGreaterThan(order.indexOf('fetch-users'));
999
+ expect(order.indexOf('merge-data')).toBeGreaterThan(order.indexOf('fetch-products'));
1000
+ expect(order.indexOf('merge-data')).toBeGreaterThan(order.indexOf('fetch-orders'));
1001
+ });
1002
+
1003
+ it('should execute all nodes successfully', () => {
1004
+ const results = executeWorkflowSync(workflow, { generateReport: true }, executor);
1005
+
1006
+ expect(results.size).toBe(12);
1007
+ expect(results.has('init')).toBe(true);
1008
+ expect(results.has('merge-data')).toBe(true);
1009
+ expect(results.has('output')).toBe(true);
1010
+
1011
+ // Verify execution order in trace
1012
+ const trace = executor.getTrace();
1013
+ expect(trace.executionOrder.length).toBe(12);
1014
+ expect(trace.executionOrder[0]).toBe('init');
1015
+ expect(trace.executionOrder[11]).toBe('output');
1016
+ });
1017
+ });
1018
+
1019
+ describe('Parallel Processing Workflow', () => {
1020
+ const workflow = createParallelProcessingWorkflow();
1021
+
1022
+ it('should have parallel branches from entry', () => {
1023
+ const parallelNodes = workflow.nodes.filter(n => n.depends_on?.includes('entry'));
1024
+ expect(parallelNodes.length).toBe(3); // task-a1, task-b1, task-c1
1025
+ });
1026
+
1027
+ it('should have correct branch structures', () => {
1028
+ // Branch A: entry -> a1 -> a2 -> a3
1029
+ expect(workflow.nodes.find(n => n.id === 'task-a1')?.depends_on).toContain('entry');
1030
+ expect(workflow.nodes.find(n => n.id === 'task-a2')?.depends_on).toContain('task-a1');
1031
+ expect(workflow.nodes.find(n => n.id === 'task-a3')?.depends_on).toContain('task-a2');
1032
+
1033
+ // Branch B: entry -> b1 -> b2/b3
1034
+ expect(workflow.nodes.find(n => n.id === 'task-b1')?.depends_on).toContain('entry');
1035
+ expect(workflow.nodes.find(n => n.id === 'task-b2')?.depends_on).toContain('task-b1');
1036
+ expect(workflow.nodes.find(n => n.id === 'task-b3')?.depends_on).toContain('task-b1');
1037
+
1038
+ // Branch C: entry -> c1 -> c2
1039
+ expect(workflow.nodes.find(n => n.id === 'task-c1')?.depends_on).toContain('entry');
1040
+ expect(workflow.nodes.find(n => n.id === 'task-c2')?.depends_on).toContain('task-c1');
1041
+ });
1042
+
1043
+ it('should have retry configured on specific nodes', () => {
1044
+ const retryNode = workflow.nodes.find(n => n.id === 'task-b3')!;
1045
+ expect(retryNode.retry?.max_attempts).toBe(2);
1046
+ expect(retryNode.retry?.backoff).toBe('fixed');
1047
+
1048
+ const retryNode2 = workflow.nodes.find(n => n.id === 'task-c2')!;
1049
+ expect(retryNode2.retry?.max_attempts).toBe(3);
1050
+ expect(retryNode2.retry?.backoff).toBe('exponential');
1051
+ });
1052
+
1053
+ it('should merge at merge-ab node', () => {
1054
+ const mergeNode = workflow.nodes.find(n => n.id === 'merge-ab')!;
1055
+ expect(mergeNode.depends_on).toContain('task-a3');
1056
+ expect(mergeNode.depends_on).toContain('task-b2');
1057
+ expect(mergeNode.depends_on).toContain('task-c2');
1058
+ });
1059
+
1060
+ it('should execute all nodes', () => {
1061
+ const results = executeWorkflowSync(workflow, {}, executor);
1062
+ // entry + 3 branches * 3 nodes each (minus b3 since b2 and b3 both depend on b1) + merge + final
1063
+ // 1 + 3 + 3 + 3 + 1 + 1 = 12... wait, let me count again
1064
+ // entry, task-a1, task-a2, task-a3, task-b1, task-b2, task-b3, task-c1, task-c2, merge-ab, final = 11 nodes
1065
+ expect(results.size).toBe(11);
1066
+ });
1067
+ });
1068
+
1069
+ describe('Nested Workflow Composition', () => {
1070
+ const workflow = createNestedWorkflow();
1071
+
1072
+ it('should have multiple workflow nodes', () => {
1073
+ const workflowNodes = workflow.nodes.filter(n => n.type === 'workflow');
1074
+ expect(workflowNodes.length).toBe(3);
1075
+ });
1076
+
1077
+ it('should have hierarchical structure', () => {
1078
+ const startNode = workflow.nodes.find(n => n.id === 'start')!;
1079
+ expect(startNode.depends_on).toBeDefined();
1080
+ expect(startNode.depends_on!.length).toBe(0);
1081
+
1082
+ const subWorkflow1 = workflow.nodes.find(n => n.id === 'sub-workflow-1')!;
1083
+ expect(subWorkflow1.depends_on).toBeDefined();
1084
+ expect(subWorkflow1.depends_on).toContain('start');
1085
+ });
1086
+
1087
+ it('should execute workflow nodes', () => {
1088
+ const results = executeWorkflowSync(workflow, {}, executor);
1089
+ expect(results.has('sub-workflow-1')).toBe(true);
1090
+ expect(results.has('final-nested')).toBe(true);
1091
+
1092
+ const nestedResult = results.get('sub-workflow-1');
1093
+ expect(nestedResult).toBeDefined();
1094
+ expect(nestedResult.nestedWorkflow).toBe('sub-workflow-a');
1095
+ });
1096
+ });
1097
+
1098
+ describe('Multi-Node Type Orchestration', () => {
1099
+ const workflow = createMultiNodeTypeWorkflow();
1100
+
1101
+ it('should have various node types', () => {
1102
+ const toolNodes = workflow.nodes.filter(n => n.type === 'tool');
1103
+ const agentNodes = workflow.nodes.filter(n => n.type === 'agent');
1104
+ const skillNodes = workflow.nodes.filter(n => n.type === 'skill');
1105
+ const workflowNodes = workflow.nodes.filter(n => n.type === 'workflow');
1106
+
1107
+ expect(toolNodes.length).toBeGreaterThan(0);
1108
+ expect(agentNodes.length).toBeGreaterThan(0);
1109
+ expect(skillNodes.length).toBeGreaterThan(0);
1110
+ expect(workflowNodes.length).toBeGreaterThan(0);
1111
+ });
1112
+
1113
+ it('should have correct dependency chain', () => {
1114
+ const prepareData = workflow.nodes.find(n => n.id === 'prepare-data')!;
1115
+ expect(prepareData.depends_on?.length).toBe(0);
1116
+
1117
+ const analyzeData = workflow.nodes.find(n => n.id === 'analyze-data')!;
1118
+ expect(analyzeData.depends_on).toContain('prepare-data');
1119
+
1120
+ const makeDecision = workflow.nodes.find(n => n.id === 'make-decision')!;
1121
+ expect(makeDecision.depends_on).toContain('validate-analysis');
1122
+ });
1123
+
1124
+ it('should have agent nodes with prompts', () => {
1125
+ const agentNodes = workflow.nodes.filter(n => n.type === 'agent');
1126
+
1127
+ for (const agentNode of agentNodes) {
1128
+ expect(agentNode.config).toBeDefined();
1129
+ expect((agentNode.config as any).prompt).toBeDefined();
1130
+ }
1131
+ });
1132
+
1133
+ it('should have skill nodes with skill names', () => {
1134
+ const skillNodes = workflow.nodes.filter(n => n.type === 'skill');
1135
+
1136
+ for (const skillNode of skillNodes) {
1137
+ expect(skillNode.config).toBeDefined();
1138
+ expect((skillNode.config as any).skill).toBeDefined();
1139
+ }
1140
+ });
1141
+
1142
+ it('should execute all node types', () => {
1143
+ const results = executeWorkflowSync(workflow, {}, executor);
1144
+
1145
+ // All 7 nodes should be executed
1146
+ // prepare-data, analyze-data, validate-analysis, make-decision, transform-data, process-workflow, format-output
1147
+ expect(results.size).toBe(7);
1148
+
1149
+ // Verify tool nodes executed
1150
+ expect(results.has('prepare-data')).toBe(true);
1151
+ expect(results.has('format-output')).toBe(true);
1152
+
1153
+ // Verify agent nodes executed
1154
+ expect(results.has('analyze-data')).toBe(true);
1155
+ expect(results.has('make-decision')).toBe(true);
1156
+
1157
+ // Verify skill nodes executed
1158
+ expect(results.has('transform-data')).toBe(true);
1159
+
1160
+ // Verify workflow nodes executed
1161
+ expect(results.has('process-workflow')).toBe(true);
1162
+ });
1163
+ });
1164
+
1165
+ describe('Tool-Skill-Agent Chain', () => {
1166
+ const workflow = createToolSkillAgentChain();
1167
+
1168
+ it('should alternate between tool, agent, and skill nodes', () => {
1169
+ const toolNodes = workflow.nodes.filter(n => n.type === 'tool');
1170
+ const agentNodes = workflow.nodes.filter(n => n.type === 'agent');
1171
+ const skillNodes = workflow.nodes.filter(n => n.type === 'skill');
1172
+
1173
+ expect(toolNodes.length).toBe(2); // fetch and validate
1174
+ expect(agentNodes.length).toBe(2); // understand and reasoning
1175
+ expect(skillNodes.length).toBe(2); // process and enhance
1176
+ });
1177
+
1178
+ it('should maintain execution order', () => {
1179
+ const results = executeWorkflowSync(workflow, {}, executor);
1180
+ expect(results.size).toBe(6);
1181
+
1182
+ // Verify tool → agent → skill chain
1183
+ const toolResult = results.get('fetch-raw-data');
1184
+ const agentResult = results.get('understand-context');
1185
+ const skillResult = results.get('process-with-skill');
1186
+
1187
+ expect(toolResult).toBeDefined();
1188
+ expect(agentResult).toBeDefined();
1189
+ expect(skillResult).toBeDefined();
1190
+ });
1191
+ });
1192
+
1193
+ describe('Parallel Tool-Agent Branches', () => {
1194
+ const workflow = createParallelToolAgentWorkflow();
1195
+
1196
+ it('should have parallel tool-agent pairs', () => {
1197
+ const toolNodes = workflow.nodes.filter(n => n.type === 'tool');
1198
+ const agentNodes = workflow.nodes.filter(n => n.type === 'agent');
1199
+
1200
+ expect(toolNodes.length).toBeGreaterThan(3); // start + 3 branch tools + output
1201
+ expect(agentNodes.length).toBe(4); // 3 branch agents + 1 aggregator
1202
+ });
1203
+
1204
+ it('should have merge node after parallel branches', () => {
1205
+ const mergeNode = workflow.nodes.find(n => n.id === 'merge-agents')!;
1206
+ expect(mergeNode.type).toBe('merge');
1207
+ expect(mergeNode.depends_on).toContain('agent-a');
1208
+ expect(mergeNode.depends_on).toContain('agent-b');
1209
+ expect(mergeNode.depends_on).toContain('agent-c');
1210
+ });
1211
+
1212
+ it('should execute all parallel branches', () => {
1213
+ const results = executeWorkflowSync(workflow, {}, executor);
1214
+ expect(results.has('tool-a')).toBe(true);
1215
+ expect(results.has('agent-a')).toBe(true);
1216
+ expect(results.has('tool-b')).toBe(true);
1217
+ expect(results.has('agent-b')).toBe(true);
1218
+ expect(results.has('merge-agents')).toBe(true);
1219
+ expect(results.has('aggregate-results')).toBe(true);
1220
+ });
1221
+ });
1222
+
1223
+ describe('Diamond Pattern Workflow', () => {
1224
+ const workflow = createDiamondWorkflow();
1225
+
1226
+ it('should have classic diamond structure', () => {
1227
+ // A is the starting point
1228
+ const aNode = workflow.nodes.find(n => n.id === 'A')!;
1229
+ expect(aNode.depends_on).toBeDefined();
1230
+ expect(aNode.depends_on!.length).toBe(0);
1231
+
1232
+ // B1, B2, B3 all depend on A
1233
+ const b1Node = workflow.nodes.find(n => n.id === 'B1')!;
1234
+ const b2Node = workflow.nodes.find(n => n.id === 'B2')!;
1235
+ const b3Node = workflow.nodes.find(n => n.id === 'B3')!;
1236
+ expect(b1Node.depends_on).toContain('A');
1237
+ expect(b2Node.depends_on).toContain('A');
1238
+ expect(b3Node.depends_on).toContain('A');
1239
+
1240
+ // C depends on all B nodes
1241
+ const cNode = workflow.nodes.find(n => n.id === 'C')!;
1242
+ expect(cNode.depends_on).toBeDefined();
1243
+ expect(cNode.depends_on).toContain('B1');
1244
+ expect(cNode.depends_on).toContain('B2');
1245
+ expect(cNode.depends_on).toContain('B3');
1246
+
1247
+ // D depends on C
1248
+ const dNode = workflow.nodes.find(n => n.id === 'D')!;
1249
+ expect(dNode.depends_on).toContain('C');
1250
+ });
1251
+
1252
+ it('should have 6 nodes total', () => {
1253
+ expect(workflow.nodes).toHaveLength(6);
1254
+ });
1255
+
1256
+ it('should execute diamond pattern correctly', () => {
1257
+ const results = executeWorkflowSync(workflow, { test: 'data' }, executor);
1258
+
1259
+ // All nodes should be executed
1260
+ expect(results.has('A')).toBe(true);
1261
+ expect(results.has('B1')).toBe(true);
1262
+ expect(results.has('B2')).toBe(true);
1263
+ expect(results.has('B3')).toBe(true);
1264
+ expect(results.has('C')).toBe(true);
1265
+ expect(results.has('D')).toBe(true);
1266
+ });
1267
+
1268
+ it('should merge results at C node', () => {
1269
+ const results = executeWorkflowSync(workflow, {}, executor);
1270
+ const cResult = results.get('C');
1271
+ expect(cResult).toBeDefined();
1272
+ });
1273
+ });
1274
+
1275
+ describe('Conditional Execution Workflow', () => {
1276
+ const workflow = createConditionalWorkflow();
1277
+
1278
+ it('should have conditional branching', () => {
1279
+ const checkNode = workflow.nodes.find(n => n.id === 'check-mode')!;
1280
+ expect(checkNode.type).toBe('condition');
1281
+
1282
+ const modeA1 = workflow.nodes.find(n => n.id === 'mode-a-1')!;
1283
+ const modeB1 = workflow.nodes.find(n => n.id === 'mode-b-1')!;
1284
+ expect(modeA1.depends_on).toContain('check-mode');
1285
+ expect(modeB1.depends_on).toContain('check-mode');
1286
+ });
1287
+
1288
+ it('should merge conditional paths', () => {
1289
+ const mergeNode = workflow.nodes.find(n => n.id === 'merge-cond')!;
1290
+ expect(mergeNode.depends_on).toBeDefined();
1291
+ expect(mergeNode.depends_on).toContain('mode-a-2');
1292
+ expect(mergeNode.depends_on).toContain('mode-b-2');
1293
+ });
1294
+
1295
+ it('should execute conditional nodes', () => {
1296
+ const results = executeWorkflowSync(workflow, { mode: 'A' }, executor);
1297
+
1298
+ expect(results.has('check-mode')).toBe(true);
1299
+ const checkResult = results.get('check-mode');
1300
+ expect(checkResult).toBeDefined();
1301
+ expect(checkResult.conditionMet).toBe(true);
1302
+ });
1303
+ });
1304
+
1305
+ describe('Template Variable Workflow', () => {
1306
+ const workflow = createTemplateVariableWorkflow();
1307
+
1308
+ it('should have template variables in node configs', () => {
1309
+ const initNode = workflow.nodes.find(n => n.id === 'init')!;
1310
+ expect(initNode.config.message).toContain('{{input.greeting}}');
1311
+
1312
+ const processNode = workflow.nodes.find(n => n.id === 'process')!;
1313
+ expect(processNode.config.expression).toContain('{{init.result}}');
1314
+ expect(processNode.config.expression).toContain('{{input.value}}');
1315
+ });
1316
+
1317
+ it('should have condition with template variables', () => {
1318
+ const validateNode = workflow.nodes.find(n => n.id === 'validate')!;
1319
+ expect(validateNode.config.condition).toContain('{{process.result}}');
1320
+ expect(validateNode.config.condition).toContain('{{input.threshold}}');
1321
+ });
1322
+ });
1323
+
1324
+ describe('Complex Dependency Graph Workflow', () => {
1325
+ const workflow = createComplexDependencyWorkflow();
1326
+
1327
+ it('should have multiple entry points', () => {
1328
+ const entry1 = workflow.nodes.find(n => n.id === 'entry-1')!;
1329
+ const entry2 = workflow.nodes.find(n => n.id === 'entry-2')!;
1330
+ expect(entry1.depends_on).toBeDefined();
1331
+ expect(entry1.depends_on!.length).toBe(0);
1332
+ expect(entry2.depends_on).toBeDefined();
1333
+ expect(entry2.depends_on!.length).toBe(0);
1334
+ });
1335
+
1336
+ it('should have cross-branch dependencies', () => {
1337
+ const crossDep = workflow.nodes.find(n => n.id === 'cross-dep')!;
1338
+ expect(crossDep.depends_on).toBeDefined();
1339
+ expect(crossDep.depends_on).toContain('branch-1a');
1340
+ expect(crossDep.depends_on).toContain('branch-2a');
1341
+ });
1342
+
1343
+ it('should handle complex merge patterns', () => {
1344
+ const mergeNode = workflow.nodes.find(n => n.id === 'merge-1')!;
1345
+ expect(mergeNode.depends_on).toBeDefined();
1346
+ expect(mergeNode.depends_on).toHaveLength(3);
1347
+ });
1348
+
1349
+ it('should execute complex workflow correctly', () => {
1350
+ const results = executeWorkflowSync(workflow, {}, executor);
1351
+ // entry-1, entry-2, branch-1a, branch-1b, branch-2a, branch-2b, cross-dep, merge-1, final = 9 nodes
1352
+ expect(results.size).toBe(9);
1353
+ });
1354
+ });
1355
+
1356
+ // ==================== Retry Logic Tests ====================
1357
+
1358
+ describe('Retry Configuration', () => {
1359
+ it('should configure retry with exponential backoff', () => {
1360
+ const workflow = createDataProcessingWorkflow();
1361
+ const retryNode = workflow.nodes.find(n => n.id === 'process-users')!;
1362
+
1363
+ expect(retryNode.retry).toBeDefined();
1364
+ expect(retryNode.retry?.max_attempts).toBe(3);
1365
+ expect(retryNode.retry?.backoff).toBe('exponential');
1366
+ expect(retryNode.retry?.initial_delay).toBe(1000);
1367
+ });
1368
+
1369
+ it('should configure retry with fixed backoff', () => {
1370
+ const workflow = createParallelProcessingWorkflow();
1371
+ const retryNode = workflow.nodes.find(n => n.id === 'task-b3')!;
1372
+
1373
+ expect(retryNode.retry).toBeDefined();
1374
+ expect(retryNode.retry?.backoff).toBe('fixed');
1375
+ expect(retryNode.retry?.initial_delay).toBe(500);
1376
+ });
1377
+
1378
+ it('should have retry node in diamond workflow', () => {
1379
+ const workflow = createDiamondWorkflow();
1380
+ const retryNode = workflow.nodes.find(n => n.id === 'C')!;
1381
+
1382
+ expect(retryNode.retry).toBeDefined();
1383
+ expect(retryNode.retry?.max_attempts).toBe(2);
1384
+ });
1385
+ });
1386
+
1387
+ // ==================== Workflow Validation Tests ====================
1388
+
1389
+ describe('Workflow Validation', () => {
1390
+ it('should validate workflow with all required fields', () => {
1391
+ const workflow = createDataProcessingWorkflow();
1392
+
1393
+ // Should have name
1394
+ expect(workflow.name).toBeTruthy();
1395
+
1396
+ // Should have at least one node
1397
+ expect(workflow.nodes.length).toBeGreaterThan(0);
1398
+
1399
+ // All nodes should have id and type
1400
+ for (const node of workflow.nodes) {
1401
+ expect(node.id).toBeTruthy();
1402
+ expect(node.type).toBeTruthy();
1403
+ }
1404
+ });
1405
+
1406
+ it('should have valid dependency references', () => {
1407
+ const workflow = createDataProcessingWorkflow();
1408
+ const nodeIds = new Set(workflow.nodes.map(n => n.id));
1409
+
1410
+ for (const node of workflow.nodes) {
1411
+ if (node.depends_on) {
1412
+ for (const depId of node.depends_on) {
1413
+ expect(nodeIds.has(depId)).toBe(true);
1414
+ }
1415
+ }
1416
+ }
1417
+ });
1418
+
1419
+ it('should detect circular dependencies', () => {
1420
+ const circularWorkflow: WorkflowDefinition = {
1421
+ name: 'circular',
1422
+ nodes: [
1423
+ { id: 'a', type: 'tool', config: {}, depends_on: ['c'] },
1424
+ { id: 'b', type: 'tool', config: {}, depends_on: ['a'] },
1425
+ { id: 'c', type: 'tool', config: {}, depends_on: ['b'] },
1426
+ ],
1427
+ };
1428
+
1429
+ expect(() => topologicalSort(circularWorkflow)).toThrow();
1430
+ });
1431
+ });
1432
+
1433
+ // ==================== Schema Validation Tests ====================
1434
+
1435
+ describe('Schema Validation', () => {
1436
+ it('should validate NodeDefinition schema', () => {
1437
+ const NodeDefinitionSchema = z.object({
1438
+ id: z.string().min(1),
1439
+ type: z.string().min(1),
1440
+ name: z.string().optional(),
1441
+ config: z.record(z.string(), z.unknown()).optional(),
1442
+ depends_on: z.array(z.string()).optional(),
1443
+ condition: z.string().optional(),
1444
+ retry: z.object({
1445
+ max_attempts: z.number().min(1),
1446
+ backoff: z.enum(['fixed', 'exponential']),
1447
+ initial_delay: z.number().min(0),
1448
+ }).optional(),
1449
+ timeout: z.number().optional(),
1450
+ });
1451
+
1452
+ const validNode = {
1453
+ id: 'test-node',
1454
+ type: 'tool',
1455
+ name: 'Test Node',
1456
+ config: { tool: 'test' },
1457
+ depends_on: ['dep1'],
1458
+ retry: { max_attempts: 3, backoff: 'exponential', initial_delay: 1000 },
1459
+ };
1460
+
1461
+ const result = NodeDefinitionSchema.safeParse(validNode);
1462
+ expect(result.success).toBe(true);
1463
+ });
1464
+
1465
+ it('should reject invalid retry config', () => {
1466
+ const RetryConfigSchema = z.object({
1467
+ max_attempts: z.number().min(1),
1468
+ backoff: z.enum(['fixed', 'exponential']),
1469
+ initial_delay: z.number().min(0),
1470
+ });
1471
+
1472
+ const invalidConfig = { max_attempts: 0, backoff: 'invalid', initial_delay: -100 };
1473
+ const result = RetryConfigSchema.safeParse(invalidConfig);
1474
+ expect(result.success).toBe(false);
1475
+ });
1476
+
1477
+ it('should validate WorkflowDefinition schema', () => {
1478
+ const WorkflowDefinitionSchema = z.object({
1479
+ name: z.string().min(1),
1480
+ version: z.string().default('1.0'),
1481
+ description: z.string().optional(),
1482
+ nodes: z.array(z.object({
1483
+ id: z.string().min(1),
1484
+ type: z.string().min(1),
1485
+ })).min(1),
1486
+ entry: z.union([z.string(), z.array(z.string())]).default('__default_entry__'),
1487
+ });
1488
+
1489
+ const validWorkflow = {
1490
+ name: 'test-workflow',
1491
+ nodes: [{ id: 'n1', type: 'tool' }],
1492
+ };
1493
+
1494
+ const result = WorkflowDefinitionSchema.safeParse(validWorkflow);
1495
+ expect(result.success).toBe(true);
1496
+ expect(result.data?.version).toBe('1.0');
1497
+ });
1498
+ });
1499
+ });
1500
+
1501
+ // ==================== Topological Sort Tests ====================
1502
+
1503
+ describe('Topological Sort', () => {
1504
+ it('should sort nodes in dependency order', () => {
1505
+ const workflow = createDiamondWorkflow();
1506
+ const order = topologicalSort(workflow);
1507
+
1508
+ // A should come before B1, B2, B3
1509
+ expect(order.indexOf('A')).toBeLessThan(order.indexOf('B1'));
1510
+ expect(order.indexOf('A')).toBeLessThan(order.indexOf('B2'));
1511
+ expect(order.indexOf('A')).toBeLessThan(order.indexOf('B3'));
1512
+
1513
+ // B* should come before C
1514
+ expect(order.indexOf('B1')).toBeLessThan(order.indexOf('C'));
1515
+ expect(order.indexOf('B2')).toBeLessThan(order.indexOf('C'));
1516
+ expect(order.indexOf('B3')).toBeLessThan(order.indexOf('C'));
1517
+
1518
+ // C should come before D
1519
+ expect(order.indexOf('C')).toBeLessThan(order.indexOf('D'));
1520
+ });
1521
+
1522
+ it('should handle parallel branches', () => {
1523
+ const workflow = createParallelProcessingWorkflow();
1524
+ const order = topologicalSort(workflow);
1525
+
1526
+ // Entry should be first
1527
+ expect(order[0]).toBe('entry');
1528
+
1529
+ // All task-* should come after entry
1530
+ const taskNodes = order.filter(id => id.startsWith('task-'));
1531
+ expect(taskNodes.length).toBeGreaterThan(0);
1532
+
1533
+ // Verify dependencies are maintained
1534
+ for (const node of workflow.nodes) {
1535
+ const nodeIndex = order.indexOf(node.id);
1536
+ for (const depId of node.depends_on || []) {
1537
+ expect(order.indexOf(depId)).toBeLessThan(nodeIndex);
1538
+ }
1539
+ }
1540
+ });
1541
+
1542
+ it('should throw on circular dependency', () => {
1543
+ const circularWorkflow: WorkflowDefinition = {
1544
+ name: 'circular',
1545
+ nodes: [
1546
+ { id: 'a', type: 'tool', config: {}, depends_on: ['b'] },
1547
+ { id: 'b', type: 'tool', config: {}, depends_on: ['a'] },
1548
+ ],
1549
+ };
1550
+
1551
+ expect(() => topologicalSort(circularWorkflow)).toThrow('Circular dependency detected');
1552
+ });
1553
+ });
1554
+
1555
+ // ==================== Performance Tests ====================
1556
+
1557
+ describe('Workflow Performance', () => {
1558
+ it('should handle large parallel execution', () => {
1559
+ const nodes: NodeDefinition[] = [
1560
+ { id: 'start', type: 'tool', config: { tool: 'echo' } },
1561
+ ];
1562
+
1563
+ // Create 20 parallel nodes
1564
+ for (let i = 0; i < 20; i++) {
1565
+ nodes.push({
1566
+ id: `parallel-${i}`,
1567
+ type: 'tool',
1568
+ config: { tool: 'calculator' },
1569
+ depends_on: ['start'],
1570
+ });
1571
+ }
1572
+
1573
+ // Merge all parallel results
1574
+ nodes.push({
1575
+ id: 'merge',
1576
+ type: 'merge',
1577
+ config: { strategy: 'collect' },
1578
+ depends_on: nodes.slice(1).map(n => n.id),
1579
+ });
1580
+
1581
+ const workflow: WorkflowDefinition = {
1582
+ name: 'large-parallel',
1583
+ nodes,
1584
+ };
1585
+
1586
+ const order = topologicalSort(workflow);
1587
+ expect(order.length).toBe(22); // 1 start + 20 parallel + 1 merge
1588
+ });
1589
+
1590
+ it('should handle deep dependency chains', () => {
1591
+ const nodes: NodeDefinition[] = [];
1592
+
1593
+ // Create 50-node deep chain
1594
+ for (let i = 0; i < 50; i++) {
1595
+ nodes.push({
1596
+ id: `node-${i}`,
1597
+ type: 'tool',
1598
+ config: { tool: 'echo' },
1599
+ depends_on: i > 0 ? [`node-${i - 1}`] : [],
1600
+ });
1601
+ }
1602
+
1603
+ const workflow: WorkflowDefinition = {
1604
+ name: 'deep-chain',
1605
+ nodes,
1606
+ };
1607
+
1608
+ const order = topologicalSort(workflow);
1609
+
1610
+ // Should maintain order
1611
+ for (let i = 1; i < order.length; i++) {
1612
+ expect(order.indexOf(`node-${i - 1}`)).toBeLessThan(order.indexOf(`node-${i}`));
1613
+ }
1614
+ });
1615
+
1616
+ it('should execute large workflow efficiently', () => {
1617
+ const nodes: NodeDefinition[] = [
1618
+ { id: 'start', type: 'tool', config: { tool: 'echo' } },
1619
+ ];
1620
+
1621
+ for (let i = 0; i < 10; i++) {
1622
+ nodes.push({
1623
+ id: `node-${i}`,
1624
+ type: 'tool',
1625
+ config: { tool: 'calculator' },
1626
+ depends_on: i === 0 ? ['start'] : [`node-${i - 1}`],
1627
+ });
1628
+ }
1629
+
1630
+ const workflow: WorkflowDefinition = {
1631
+ name: 'large-chain',
1632
+ nodes,
1633
+ };
1634
+
1635
+ const executor = new SyncWorkflowExecutor();
1636
+ const start = Date.now();
1637
+ const results = executeWorkflowSync(workflow, {}, executor);
1638
+ const duration = Date.now() - start;
1639
+
1640
+ expect(results.size).toBe(11);
1641
+ expect(duration).toBeLessThan(100); // Should complete in under 100ms
1642
+ });
1643
+ });
1644
+
1645
+ // ==================== Edge Cases ====================
1646
+
1647
+ describe('Workflow Edge Cases', () => {
1648
+ it('should handle single node workflow', () => {
1649
+ const workflow: WorkflowDefinition = {
1650
+ name: 'single-node',
1651
+ nodes: [{ id: 'only', type: 'tool', config: {} }],
1652
+ };
1653
+
1654
+ const order = topologicalSort(workflow);
1655
+ expect(order).toEqual(['only']);
1656
+
1657
+ const executor = new SyncWorkflowExecutor();
1658
+ const results = executeWorkflowSync(workflow, {}, executor);
1659
+ expect(results.size).toBe(1);
1660
+ expect(results.get('only')).toBeDefined();
1661
+ });
1662
+
1663
+ it('should handle nodes with multiple dependencies', () => {
1664
+ const workflow: WorkflowDefinition = {
1665
+ name: 'multi-dep',
1666
+ nodes: [
1667
+ { id: 'a', type: 'tool', config: {} },
1668
+ { id: 'b', type: 'tool', config: {} },
1669
+ { id: 'c', type: 'tool', config: {} },
1670
+ { id: 'd', type: 'tool', config: {}, depends_on: ['a', 'b', 'c'] },
1671
+ ],
1672
+ };
1673
+
1674
+ const order = topologicalSort(workflow);
1675
+ const dIndex = order.indexOf('d');
1676
+ expect(order.indexOf('a')).toBeLessThan(dIndex);
1677
+ expect(order.indexOf('b')).toBeLessThan(dIndex);
1678
+ expect(order.indexOf('c')).toBeLessThan(dIndex);
1679
+ });
1680
+
1681
+ it('should handle multiple entry points', () => {
1682
+ const workflow: WorkflowDefinition = {
1683
+ name: 'multi-entry',
1684
+ entry: ['a', 'b'],
1685
+ nodes: [
1686
+ { id: 'a', type: 'tool', config: {} },
1687
+ { id: 'b', type: 'tool', config: {} },
1688
+ { id: 'c', type: 'tool', config: {}, depends_on: ['a', 'b'] },
1689
+ ],
1690
+ };
1691
+
1692
+ const order = topologicalSort(workflow);
1693
+ expect(order.includes('a')).toBe(true);
1694
+ expect(order.includes('b')).toBe(true);
1695
+ expect(order.indexOf('c')).toBeGreaterThan(order.indexOf('a'));
1696
+ expect(order.indexOf('c')).toBeGreaterThan(order.indexOf('b'));
1697
+ });
1698
+
1699
+ it('should handle optional fields in nodes', () => {
1700
+ const minimalNode = {
1701
+ id: 'minimal',
1702
+ type: 'tool',
1703
+ };
1704
+
1705
+ const NodeDefinitionSchema = z.object({
1706
+ id: z.string().min(1),
1707
+ type: z.string().min(1),
1708
+ name: z.string().optional(),
1709
+ config: z.record(z.string(), z.unknown()).optional(),
1710
+ depends_on: z.array(z.string()).optional(),
1711
+ });
1712
+
1713
+ const result = NodeDefinitionSchema.safeParse(minimalNode);
1714
+ expect(result.success).toBe(true);
1715
+ });
1716
+
1717
+ it('should handle empty depends_on array', () => {
1718
+ const workflow: WorkflowDefinition = {
1719
+ name: 'empty-deps',
1720
+ nodes: [
1721
+ { id: 'a', type: 'tool', config: {}, depends_on: [] },
1722
+ { id: 'b', type: 'tool', config: {}, depends_on: ['a'] },
1723
+ ],
1724
+ };
1725
+
1726
+ const order = topologicalSort(workflow);
1727
+ expect(order.indexOf('a')).toBeLessThan(order.indexOf('b'));
1728
+ });
1729
+
1730
+ it('should handle nodes without config', () => {
1731
+ const workflow: WorkflowDefinition = {
1732
+ name: 'no-config',
1733
+ nodes: [
1734
+ { id: 'a', type: 'tool' },
1735
+ { id: 'b', type: 'tool', depends_on: ['a'] },
1736
+ ],
1737
+ };
1738
+
1739
+ const executor = new SyncWorkflowExecutor();
1740
+ const results = executeWorkflowSync(workflow, {}, executor);
1741
+ expect(results.get('a')).toBeDefined();
1742
+ expect(results.get('b')).toBeDefined();
1743
+ });
1744
+ });
1745
+
1746
+ // ==================== Execution Trace Tests ====================
1747
+
1748
+ describe('Execution Trace', () => {
1749
+ it('should track execution order', () => {
1750
+ const workflow = createDiamondWorkflow();
1751
+ const executor = new SyncWorkflowExecutor();
1752
+
1753
+ executeWorkflowSync(workflow, {}, executor);
1754
+
1755
+ const trace = executor.getTrace();
1756
+ expect(trace.executionOrder).toEqual(['A', 'B1', 'B2', 'B3', 'C', 'D']);
1757
+ });
1758
+
1759
+ it('should track node execution status', () => {
1760
+ const workflow = createDiamondWorkflow();
1761
+ const executor = new SyncWorkflowExecutor();
1762
+
1763
+ executeWorkflowSync(workflow, {}, executor);
1764
+
1765
+ const trace = executor.getTrace();
1766
+
1767
+ for (const entry of trace.nodes.values()) {
1768
+ expect(entry.status).toBe('completed');
1769
+ expect(entry.startTime).toBeDefined();
1770
+ expect(entry.endTime).toBeDefined();
1771
+ expect(entry.endTime).toBeGreaterThanOrEqual(entry.startTime);
1772
+ }
1773
+ });
1774
+
1775
+ it('should record input for each node', () => {
1776
+ const workflow = createDataProcessingWorkflow();
1777
+ const executor = new SyncWorkflowExecutor();
1778
+ const input = { generateReport: true, test: 'value' };
1779
+
1780
+ executeWorkflowSync(workflow, input, executor);
1781
+
1782
+ const trace = executor.getTrace();
1783
+ const initEntry = trace.nodes.get('init');
1784
+ expect(initEntry?.input).toEqual(input);
1785
+ });
1786
+
1787
+ it('should record output for each node', () => {
1788
+ const workflow = createDiamondWorkflow();
1789
+ const executor = new SyncWorkflowExecutor();
1790
+
1791
+ executeWorkflowSync(workflow, {}, executor);
1792
+
1793
+ const trace = executor.getTrace();
1794
+
1795
+ for (const [nodeId, entry] of trace.nodes) {
1796
+ expect(entry.output).toBeDefined();
1797
+ }
1798
+ });
1799
+ });
1800
+
1801
+ // ==================== Merge Node Tests ====================
1802
+
1803
+ describe('Merge Node Behavior', () => {
1804
+ it('should collect all dependency outputs', () => {
1805
+ const workflow: WorkflowDefinition = {
1806
+ name: 'merge-test',
1807
+ nodes: [
1808
+ { id: 'a', type: 'tool', config: { tool: 'calculator' } },
1809
+ { id: 'b', type: 'tool', config: { tool: 'calculator' } },
1810
+ { id: 'c', type: 'tool', config: { tool: 'calculator' } },
1811
+ {
1812
+ id: 'merge',
1813
+ type: 'merge',
1814
+ config: { strategy: 'collect' },
1815
+ depends_on: ['a', 'b', 'c'],
1816
+ },
1817
+ ],
1818
+ };
1819
+
1820
+ const executor = new SyncWorkflowExecutor();
1821
+ const results = executeWorkflowSync(workflow, {}, executor);
1822
+
1823
+ const mergeResult = results.get('merge');
1824
+ expect(mergeResult).toBeDefined();
1825
+ expect(mergeResult.merged).toBe(true);
1826
+ expect(mergeResult.inputs).toBeDefined();
1827
+ expect(mergeResult.inputs.a).toBeDefined();
1828
+ expect(mergeResult.inputs.b).toBeDefined();
1829
+ expect(mergeResult.inputs.c).toBeDefined();
1830
+ });
1831
+
1832
+ it('should handle merge with different strategies', () => {
1833
+ const mergeStrategies = ['collect', 'merge', 'concat', 'zip'];
1834
+
1835
+ for (const strategy of mergeStrategies) {
1836
+ const workflow: WorkflowDefinition = {
1837
+ name: `merge-${strategy}`,
1838
+ nodes: [
1839
+ { id: 'a', type: 'tool', config: { tool: 'calculator' } },
1840
+ { id: 'b', type: 'tool', config: { tool: 'calculator' } },
1841
+ {
1842
+ id: 'merge',
1843
+ type: 'merge',
1844
+ config: { strategy },
1845
+ depends_on: ['a', 'b'],
1846
+ },
1847
+ ],
1848
+ };
1849
+
1850
+ const executor = new SyncWorkflowExecutor();
1851
+ const results = executeWorkflowSync(workflow, {}, executor);
1852
+ const mergeResult = results.get('merge');
1853
+ expect(mergeResult).toBeDefined();
1854
+ expect(mergeResult.mergeStrategy).toBe(strategy);
1855
+ }
1856
+ });
1857
+ });
1858
+
1859
+ // ==================== Condition Node Tests ====================
1860
+
1861
+ describe('Condition Node Behavior', () => {
1862
+ it('should evaluate true condition', () => {
1863
+ const workflow: WorkflowDefinition = {
1864
+ name: 'condition-test',
1865
+ nodes: [
1866
+ { id: 'check', type: 'condition', config: { condition: 'true' } },
1867
+ { id: 'output', type: 'tool', config: { tool: 'echo' }, depends_on: ['check'] },
1868
+ ],
1869
+ };
1870
+
1871
+ const executor = new SyncWorkflowExecutor();
1872
+ const results = executeWorkflowSync(workflow, {}, executor);
1873
+
1874
+ const checkResult = results.get('check');
1875
+ expect(checkResult).toBeDefined();
1876
+ expect(checkResult.conditionMet).toBe(true);
1877
+ });
1878
+
1879
+ it('should evaluate template condition', () => {
1880
+ const workflow: WorkflowDefinition = {
1881
+ name: 'condition-template',
1882
+ nodes: [
1883
+ {
1884
+ id: 'check',
1885
+ type: 'condition',
1886
+ config: { condition: '{{merge-data.valid}}' },
1887
+ },
1888
+ { id: 'output', type: 'tool', config: { tool: 'echo' }, depends_on: ['check'] },
1889
+ ],
1890
+ };
1891
+
1892
+ const executor = new SyncWorkflowExecutor();
1893
+ const results = executeWorkflowSync(workflow, {}, executor);
1894
+
1895
+ const checkResult = results.get('check');
1896
+ expect(checkResult).toBeDefined();
1897
+ expect(checkResult.conditionMet).toBe(true); // Template conditions default to true
1898
+ expect(checkResult.evaluatedCondition).toBe('{{merge-data.valid}}');
1899
+ });
1900
+ });