@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,715 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Scheduler, SchedulerOptions, SchedulerState } from './scheduler';
3
+ import { DAGManager } from './dag-manager';
4
+ import type { WorkflowDefinition, NodeDefinition } from '../types';
5
+
6
+ // Helper to create a basic workflow definition
7
+ function createWorkflow(nodes: NodeDefinition[], config?: { parallel_limit?: number | null }): WorkflowDefinition {
8
+ return {
9
+ name: 'test-workflow',
10
+ version: '1.0',
11
+ nodes,
12
+ entry: '__default_entry__',
13
+ config: config ? { parallel_limit: config.parallel_limit } : undefined,
14
+ };
15
+ }
16
+
17
+ // Helper to create a node definition
18
+ function createNode(id: string, dependsOn: string[] = []): NodeDefinition {
19
+ return {
20
+ id,
21
+ type: 'test',
22
+ depends_on: dependsOn,
23
+ };
24
+ }
25
+
26
+ describe('Scheduler', () => {
27
+ describe('constructor', () => {
28
+ it('should create a Scheduler instance with DAGManager', () => {
29
+ const workflow = createWorkflow([createNode('A')]);
30
+ const dagManager = new DAGManager(workflow);
31
+ const scheduler = new Scheduler(dagManager);
32
+
33
+ expect(scheduler).toBeInstanceOf(Scheduler);
34
+ });
35
+
36
+ it('should create scheduler with default options', () => {
37
+ const workflow = createWorkflow([createNode('A')]);
38
+ const dagManager = new DAGManager(workflow);
39
+ const scheduler = new Scheduler(dagManager);
40
+
41
+ expect(scheduler).toBeDefined();
42
+ });
43
+
44
+ it('should create scheduler with custom parallel limit', () => {
45
+ const workflow = createWorkflow([createNode('A')]);
46
+ const dagManager = new DAGManager(workflow);
47
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
48
+
49
+ expect(scheduler).toBeInstanceOf(Scheduler);
50
+ });
51
+
52
+ it('should support unlimited parallel (null limit)', () => {
53
+ const workflow = createWorkflow([createNode('A')]);
54
+ const dagManager = new DAGManager(workflow);
55
+ const scheduler = new Scheduler(dagManager, { parallelLimit: null });
56
+
57
+ expect(scheduler).toBeInstanceOf(Scheduler);
58
+ });
59
+ });
60
+
61
+ describe('getReadyNodes()', () => {
62
+ it('should return entry nodes when nothing is completed', () => {
63
+ const workflow = createWorkflow([
64
+ createNode('A'),
65
+ createNode('B', ['A']),
66
+ createNode('C', ['B']),
67
+ ]);
68
+ const dagManager = new DAGManager(workflow);
69
+ const scheduler = new Scheduler(dagManager);
70
+
71
+ const ready = scheduler.getReadyNodes(new Set());
72
+ expect(ready).toEqual(['A']);
73
+ });
74
+
75
+ it('should return next nodes when dependencies are met', () => {
76
+ const workflow = createWorkflow([
77
+ createNode('A'),
78
+ createNode('B', ['A']),
79
+ createNode('C', ['B']),
80
+ ]);
81
+ const dagManager = new DAGManager(workflow);
82
+ const scheduler = new Scheduler(dagManager);
83
+
84
+ const ready = scheduler.getReadyNodes(new Set(['A']));
85
+ expect(ready).toEqual(['B']);
86
+ });
87
+
88
+ it('should return multiple nodes for parallel workflow', () => {
89
+ const workflow = createWorkflow([
90
+ createNode('A'),
91
+ createNode('B'),
92
+ createNode('C'),
93
+ ]);
94
+ const dagManager = new DAGManager(workflow);
95
+ const scheduler = new Scheduler(dagManager);
96
+
97
+ const ready = scheduler.getReadyNodes(new Set());
98
+ expect(ready).toHaveLength(3);
99
+ expect(ready).toContain('A');
100
+ expect(ready).toContain('B');
101
+ expect(ready).toContain('C');
102
+ });
103
+
104
+ it('should return multiple nodes for diamond workflow after entry completes', () => {
105
+ // Diamond: A -> B -> D, A -> C -> D
106
+ const workflow = createWorkflow([
107
+ createNode('A'),
108
+ createNode('B', ['A']),
109
+ createNode('C', ['A']),
110
+ createNode('D', ['B', 'C']),
111
+ ]);
112
+ const dagManager = new DAGManager(workflow);
113
+ const scheduler = new Scheduler(dagManager);
114
+
115
+ // After A is done, B and C should be ready
116
+ const readyAfterA = scheduler.getReadyNodes(new Set(['A']));
117
+ expect(readyAfterA).toContain('B');
118
+ expect(readyAfterA).toContain('C');
119
+ });
120
+
121
+ it('should return empty array when all nodes are completed', () => {
122
+ const workflow = createWorkflow([
123
+ createNode('A'),
124
+ createNode('B', ['A']),
125
+ ]);
126
+ const dagManager = new DAGManager(workflow);
127
+ const scheduler = new Scheduler(dagManager);
128
+
129
+ const ready = scheduler.getReadyNodes(new Set(['A', 'B']));
130
+ expect(ready).toEqual([]);
131
+ });
132
+
133
+ it('should not return already running nodes', () => {
134
+ const workflow = createWorkflow([
135
+ createNode('A'),
136
+ createNode('B'),
137
+ createNode('C'),
138
+ ]);
139
+ const dagManager = new DAGManager(workflow);
140
+ const scheduler = new Scheduler(dagManager);
141
+
142
+ // Mark A as running
143
+ scheduler.markStarted('A');
144
+
145
+ const ready = scheduler.getReadyNodes(new Set());
146
+ expect(ready).not.toContain('A');
147
+ expect(ready).toContain('B');
148
+ expect(ready).toContain('C');
149
+ });
150
+
151
+ it('should not return completed nodes', () => {
152
+ const workflow = createWorkflow([
153
+ createNode('A'),
154
+ createNode('B'),
155
+ createNode('C'),
156
+ ]);
157
+ const dagManager = new DAGManager(workflow);
158
+ const scheduler = new Scheduler(dagManager);
159
+
160
+ // Mark A as completed
161
+ scheduler.markCompleted('A');
162
+
163
+ const ready = scheduler.getReadyNodes(new Set());
164
+ expect(ready).not.toContain('A');
165
+ expect(ready).toContain('B');
166
+ expect(ready).toContain('C');
167
+ });
168
+ });
169
+
170
+ describe('getRunningCount()', () => {
171
+ it('should return 0 initially', () => {
172
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
173
+ const dagManager = new DAGManager(workflow);
174
+ const scheduler = new Scheduler(dagManager);
175
+
176
+ expect(scheduler.getRunningCount()).toBe(0);
177
+ });
178
+
179
+ it('should return correct count after marking nodes as started', () => {
180
+ const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
181
+ const dagManager = new DAGManager(workflow);
182
+ const scheduler = new Scheduler(dagManager);
183
+
184
+ scheduler.markStarted('A');
185
+ expect(scheduler.getRunningCount()).toBe(1);
186
+
187
+ scheduler.markStarted('B');
188
+ expect(scheduler.getRunningCount()).toBe(2);
189
+ });
190
+
191
+ it('should return correct count after completing nodes', () => {
192
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
193
+ const dagManager = new DAGManager(workflow);
194
+ const scheduler = new Scheduler(dagManager);
195
+
196
+ scheduler.markStarted('A');
197
+ scheduler.markStarted('B');
198
+ expect(scheduler.getRunningCount()).toBe(2);
199
+
200
+ scheduler.markCompleted('A');
201
+ expect(scheduler.getRunningCount()).toBe(1);
202
+ });
203
+ });
204
+
205
+ describe('canStartMore()', () => {
206
+ it('should return true when under parallel limit', () => {
207
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
208
+ const dagManager = new DAGManager(workflow);
209
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 3 });
210
+
211
+ scheduler.markStarted('A');
212
+ scheduler.markStarted('B');
213
+
214
+ expect(scheduler.canStartMore()).toBe(true);
215
+ });
216
+
217
+ it('should return false when at parallel limit', () => {
218
+ const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
219
+ const dagManager = new DAGManager(workflow);
220
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
221
+
222
+ scheduler.markStarted('A');
223
+ scheduler.markStarted('B');
224
+
225
+ expect(scheduler.canStartMore()).toBe(false);
226
+ });
227
+
228
+ it('should return true when parallel limit is null (unlimited)', () => {
229
+ const workflow = createWorkflow([
230
+ createNode('A'),
231
+ createNode('B'),
232
+ createNode('C'),
233
+ createNode('D'),
234
+ createNode('E'),
235
+ ]);
236
+ const dagManager = new DAGManager(workflow);
237
+ const scheduler = new Scheduler(dagManager, { parallelLimit: null });
238
+
239
+ // Start many nodes
240
+ scheduler.markStarted('A');
241
+ scheduler.markStarted('B');
242
+ scheduler.markStarted('C');
243
+ scheduler.markStarted('D');
244
+ scheduler.markStarted('E');
245
+
246
+ expect(scheduler.canStartMore()).toBe(true);
247
+ });
248
+
249
+ it('should return true when single-threaded and no nodes running', () => {
250
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
251
+ const dagManager = new DAGManager(workflow);
252
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
253
+
254
+ expect(scheduler.canStartMore()).toBe(true);
255
+ });
256
+
257
+ it('should return false when single-threaded and one node running', () => {
258
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
259
+ const dagManager = new DAGManager(workflow);
260
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
261
+
262
+ scheduler.markStarted('A');
263
+
264
+ expect(scheduler.canStartMore()).toBe(false);
265
+ });
266
+ });
267
+
268
+ describe('markStarted()', () => {
269
+ it('should track running node', () => {
270
+ const workflow = createWorkflow([createNode('A')]);
271
+ const dagManager = new DAGManager(workflow);
272
+ const scheduler = new Scheduler(dagManager);
273
+
274
+ scheduler.markStarted('A');
275
+
276
+ expect(scheduler.getRunningCount()).toBe(1);
277
+ expect(scheduler.canStartMore()).toBe(false);
278
+ });
279
+
280
+ it('should allow starting more when under limit', () => {
281
+ const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
282
+ const dagManager = new DAGManager(workflow);
283
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 3 });
284
+
285
+ scheduler.markStarted('A');
286
+ scheduler.markStarted('B');
287
+
288
+ expect(scheduler.canStartMore()).toBe(true);
289
+ });
290
+ });
291
+
292
+ describe('markCompleted()', () => {
293
+ it('should remove node from running set', () => {
294
+ const workflow = createWorkflow([createNode('A')]);
295
+ const dagManager = new DAGManager(workflow);
296
+ const scheduler = new Scheduler(dagManager);
297
+
298
+ scheduler.markStarted('A');
299
+ expect(scheduler.getRunningCount()).toBe(1);
300
+
301
+ scheduler.markCompleted('A');
302
+ expect(scheduler.getRunningCount()).toBe(0);
303
+ });
304
+
305
+ it('should allow starting more after completion', () => {
306
+ const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
307
+ const dagManager = new DAGManager(workflow);
308
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
309
+
310
+ scheduler.markStarted('A');
311
+ scheduler.markStarted('B');
312
+ expect(scheduler.canStartMore()).toBe(false);
313
+
314
+ scheduler.markCompleted('A');
315
+ expect(scheduler.canStartMore()).toBe(true);
316
+ });
317
+
318
+ it('should handle completing a node that was not started', () => {
319
+ const workflow = createWorkflow([createNode('A')]);
320
+ const dagManager = new DAGManager(workflow);
321
+ const scheduler = new Scheduler(dagManager);
322
+
323
+ // Should not throw
324
+ scheduler.markCompleted('A');
325
+ expect(scheduler.getRunningCount()).toBe(0);
326
+ });
327
+ });
328
+
329
+ describe('markFailed()', () => {
330
+ it('should remove node from running set on failure', () => {
331
+ const workflow = createWorkflow([createNode('A')]);
332
+ const dagManager = new DAGManager(workflow);
333
+ const scheduler = new Scheduler(dagManager);
334
+
335
+ scheduler.markStarted('A');
336
+ expect(scheduler.getRunningCount()).toBe(1);
337
+
338
+ scheduler.markFailed('A');
339
+ expect(scheduler.getRunningCount()).toBe(0);
340
+ });
341
+
342
+ it('should track failed nodes in state', () => {
343
+ const workflow = createWorkflow([createNode('A')]);
344
+ const dagManager = new DAGManager(workflow);
345
+ const scheduler = new Scheduler(dagManager);
346
+
347
+ scheduler.markFailed('A');
348
+
349
+ const state = scheduler.getState();
350
+ expect(state.failed).toContain('A');
351
+ });
352
+
353
+ it('should not return failed node in ready nodes', () => {
354
+ const workflow = createWorkflow([createNode('A'), createNode('B', ['A'])]);
355
+ const dagManager = new DAGManager(workflow);
356
+ const scheduler = new Scheduler(dagManager);
357
+
358
+ scheduler.markFailed('A');
359
+
360
+ // A should not be ready since it failed
361
+ const ready = scheduler.getReadyNodes(new Set());
362
+ expect(ready).not.toContain('A');
363
+ });
364
+ });
365
+
366
+ describe('reset()', () => {
367
+ it('should clear all running, completed, and failed states', () => {
368
+ const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
369
+ const dagManager = new DAGManager(workflow);
370
+ const scheduler = new Scheduler(dagManager);
371
+
372
+ // Set up some state
373
+ scheduler.markStarted('A');
374
+ scheduler.markStarted('B');
375
+ scheduler.markCompleted('A');
376
+ scheduler.markFailed('C');
377
+
378
+ // Reset
379
+ scheduler.reset();
380
+
381
+ expect(scheduler.getRunningCount()).toBe(0);
382
+
383
+ const state = scheduler.getState();
384
+ expect(state.running).toHaveLength(0);
385
+ expect(state.completed).toHaveLength(0);
386
+ expect(state.failed).toHaveLength(0);
387
+ });
388
+
389
+ it('should allow starting fresh after reset', () => {
390
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
391
+ const dagManager = new DAGManager(workflow);
392
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
393
+
394
+ scheduler.markStarted('A');
395
+ scheduler.markCompleted('A');
396
+ scheduler.reset();
397
+
398
+ // Should be able to start again
399
+ scheduler.markStarted('A');
400
+ expect(scheduler.getRunningCount()).toBe(1);
401
+ });
402
+ });
403
+
404
+ describe('getState()', () => {
405
+ it('should return current scheduler state', () => {
406
+ const workflow = createWorkflow([
407
+ createNode('A'),
408
+ createNode('B', ['A']),
409
+ createNode('C', ['A']),
410
+ createNode('D', ['B', 'C']),
411
+ ]);
412
+ const dagManager = new DAGManager(workflow);
413
+ const scheduler = new Scheduler(dagManager);
414
+
415
+ const state = scheduler.getState();
416
+
417
+ expect(state).toHaveProperty('pending');
418
+ expect(state).toHaveProperty('ready');
419
+ expect(state).toHaveProperty('running');
420
+ expect(state).toHaveProperty('completed');
421
+ expect(state).toHaveProperty('failed');
422
+ expect(state).toHaveProperty('skipped');
423
+ });
424
+
425
+ it('should track pending nodes correctly', () => {
426
+ const workflow = createWorkflow([createNode('A'), createNode('B'), createNode('C')]);
427
+ const dagManager = new DAGManager(workflow);
428
+ const scheduler = new Scheduler(dagManager);
429
+
430
+ const state = scheduler.getState();
431
+
432
+ // All nodes should be pending initially
433
+ expect(state.pending).toHaveLength(3);
434
+ expect(state.pending).toContain('A');
435
+ expect(state.pending).toContain('B');
436
+ expect(state.pending).toContain('C');
437
+ });
438
+
439
+ it('should track ready nodes correctly', () => {
440
+ const workflow = createWorkflow([createNode('A'), createNode('B', ['A'])]);
441
+ const dagManager = new DAGManager(workflow);
442
+ const scheduler = new Scheduler(dagManager);
443
+
444
+ const state = scheduler.getState();
445
+
446
+ // A should be ready (entry node)
447
+ expect(state.ready).toContain('A');
448
+ // B should not be ready yet (depends on A)
449
+ expect(state.ready).not.toContain('B');
450
+ });
451
+
452
+ it('should reflect state changes after operations', () => {
453
+ const workflow = createWorkflow([createNode('A'), createNode('B')]);
454
+ const dagManager = new DAGManager(workflow);
455
+ const scheduler = new Scheduler(dagManager);
456
+
457
+ scheduler.markStarted('A');
458
+ scheduler.markCompleted('A');
459
+ scheduler.markStarted('B');
460
+
461
+ const state = scheduler.getState();
462
+
463
+ expect(state.running).toContain('B');
464
+ expect(state.completed).toContain('A');
465
+ expect(state.pending).not.toContain('A');
466
+ expect(state.pending).not.toContain('B');
467
+ });
468
+ });
469
+
470
+ describe('parallel execution scenarios', () => {
471
+ it('should respect parallel limit of 2', () => {
472
+ const workflow = createWorkflow([
473
+ createNode('A'),
474
+ createNode('B'),
475
+ createNode('C'),
476
+ createNode('D'),
477
+ ]);
478
+ const dagManager = new DAGManager(workflow);
479
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 2 });
480
+
481
+ // Start first two nodes
482
+ scheduler.markStarted('A');
483
+ scheduler.markStarted('B');
484
+
485
+ expect(scheduler.canStartMore()).toBe(false);
486
+
487
+ // Complete one
488
+ scheduler.markCompleted('A');
489
+
490
+ expect(scheduler.canStartMore()).toBe(true);
491
+
492
+ // Start third
493
+ scheduler.markStarted('C');
494
+ expect(scheduler.canStartMore()).toBe(false);
495
+ });
496
+
497
+ it('should handle single-threaded execution', () => {
498
+ const workflow = createWorkflow([
499
+ createNode('A'),
500
+ createNode('B', ['A']),
501
+ createNode('C', ['B']),
502
+ ]);
503
+ const dagManager = new DAGManager(workflow);
504
+ const scheduler = new Scheduler(dagManager, { parallelLimit: 1 });
505
+
506
+ // Initially only A is ready
507
+ let ready = scheduler.getReadyNodes(new Set());
508
+ expect(ready).toEqual(['A']);
509
+
510
+ // Start A
511
+ scheduler.markStarted('A');
512
+
513
+ // B should not be in ready (A not completed)
514
+ ready = scheduler.getReadyNodes(new Set());
515
+ expect(ready).not.toContain('B');
516
+
517
+ // Complete A
518
+ scheduler.markCompleted('A');
519
+
520
+ // Now B should be ready
521
+ ready = scheduler.getReadyNodes(new Set(['A']));
522
+ expect(ready).toEqual(['B']);
523
+
524
+ scheduler.markStarted('B');
525
+ scheduler.markCompleted('B');
526
+
527
+ // Now C should be ready
528
+ ready = scheduler.getReadyNodes(new Set(['A', 'B']));
529
+ expect(ready).toEqual(['C']);
530
+ });
531
+
532
+ it('should allow unlimited parallel execution', () => {
533
+ const workflow = createWorkflow([
534
+ createNode('A'),
535
+ createNode('B'),
536
+ createNode('C'),
537
+ createNode('D'),
538
+ createNode('E'),
539
+ ]);
540
+ const dagManager = new DAGManager(workflow);
541
+ const scheduler = new Scheduler(dagManager, { parallelLimit: null });
542
+
543
+ // Start all nodes
544
+ scheduler.markStarted('A');
545
+ scheduler.markStarted('B');
546
+ scheduler.markStarted('C');
547
+ scheduler.markStarted('D');
548
+ scheduler.markStarted('E');
549
+
550
+ expect(scheduler.canStartMore()).toBe(true);
551
+ expect(scheduler.getRunningCount()).toBe(5);
552
+ });
553
+ });
554
+
555
+ describe('complex DAG scenarios', () => {
556
+ it('should handle diamond DAG correctly', () => {
557
+ // Diamond: A -> B -> D, A -> C -> D
558
+ const workflow = createWorkflow([
559
+ createNode('A'),
560
+ createNode('B', ['A']),
561
+ createNode('C', ['A']),
562
+ createNode('D', ['B', 'C']),
563
+ ]);
564
+ const dagManager = new DAGManager(workflow);
565
+ const scheduler = new Scheduler(dagManager);
566
+
567
+ // Initially only A is ready
568
+ let ready = scheduler.getReadyNodes(new Set());
569
+ expect(ready).toEqual(['A']);
570
+
571
+ // Complete A, B and C should be ready
572
+ scheduler.markStarted('A');
573
+ scheduler.markCompleted('A');
574
+
575
+ ready = scheduler.getReadyNodes(new Set(['A']));
576
+ expect(ready).toContain('B');
577
+ expect(ready).toContain('C');
578
+
579
+ // Complete B and C, D should be ready
580
+ scheduler.markStarted('B');
581
+ scheduler.markCompleted('B');
582
+ scheduler.markStarted('C');
583
+ scheduler.markCompleted('C');
584
+
585
+ ready = scheduler.getReadyNodes(new Set(['A', 'B', 'C']));
586
+ expect(ready).toEqual(['D']);
587
+ });
588
+
589
+ it('should handle complex DAG with multiple paths', () => {
590
+ // Complex workflow:
591
+ // A -> B -> D
592
+ // A -> C -> E
593
+ // D -> F
594
+ // E -> F
595
+ const workflow = createWorkflow([
596
+ createNode('A'),
597
+ createNode('B', ['A']),
598
+ createNode('C', ['A']),
599
+ createNode('D', ['B']),
600
+ createNode('E', ['C']),
601
+ createNode('F', ['D', 'E']),
602
+ ]);
603
+ const dagManager = new DAGManager(workflow);
604
+ const scheduler = new Scheduler(dagManager);
605
+
606
+ // A is ready first
607
+ let ready = scheduler.getReadyNodes(new Set());
608
+ expect(ready).toEqual(['A']);
609
+
610
+ // Complete A, B and C should be ready
611
+ scheduler.markStarted('A');
612
+ scheduler.markCompleted('A');
613
+
614
+ ready = scheduler.getReadyNodes(new Set(['A']));
615
+ expect(ready).toContain('B');
616
+ expect(ready).toContain('C');
617
+
618
+ // Complete B and C
619
+ scheduler.markStarted('B');
620
+ scheduler.markCompleted('B');
621
+ scheduler.markStarted('C');
622
+ scheduler.markCompleted('C');
623
+
624
+ // D and E should be ready
625
+ ready = scheduler.getReadyNodes(new Set(['A', 'B', 'C']));
626
+ expect(ready).toContain('D');
627
+ expect(ready).toContain('E');
628
+
629
+ // Complete D and E
630
+ scheduler.markStarted('D');
631
+ scheduler.markCompleted('D');
632
+ scheduler.markStarted('E');
633
+ scheduler.markCompleted('E');
634
+
635
+ // F should be ready
636
+ ready = scheduler.getReadyNodes(new Set(['A', 'B', 'C', 'D', 'E']));
637
+ expect(ready).toEqual(['F']);
638
+ });
639
+
640
+ it('should handle multiple entry nodes', () => {
641
+ const workflow = createWorkflow([
642
+ createNode('A'),
643
+ createNode('B'),
644
+ createNode('C', ['A', 'B']),
645
+ ]);
646
+ const dagManager = new DAGManager(workflow);
647
+ const scheduler = new Scheduler(dagManager);
648
+
649
+ // A and B should be ready
650
+ let ready = scheduler.getReadyNodes(new Set());
651
+ expect(ready).toContain('A');
652
+ expect(ready).toContain('B');
653
+
654
+ // Complete A only, C should not be ready
655
+ scheduler.markStarted('A');
656
+ scheduler.markCompleted('A');
657
+
658
+ ready = scheduler.getReadyNodes(new Set(['A']));
659
+ expect(ready).not.toContain('C');
660
+
661
+ // Complete B, C should now be ready
662
+ scheduler.markStarted('B');
663
+ scheduler.markCompleted('B');
664
+
665
+ ready = scheduler.getReadyNodes(new Set(['A', 'B']));
666
+ expect(ready).toEqual(['C']);
667
+ });
668
+ });
669
+
670
+ describe('edge cases', () => {
671
+ it('should handle single node workflow', () => {
672
+ const workflow = createWorkflow([createNode('A')]);
673
+ const dagManager = new DAGManager(workflow);
674
+ const scheduler = new Scheduler(dagManager);
675
+
676
+ const ready = scheduler.getReadyNodes(new Set());
677
+ expect(ready).toEqual(['A']);
678
+
679
+ scheduler.markStarted('A');
680
+ expect(scheduler.getRunningCount()).toBe(1);
681
+
682
+ scheduler.markCompleted('A');
683
+ expect(scheduler.getRunningCount()).toBe(0);
684
+
685
+ const finalReady = scheduler.getReadyNodes(new Set(['A']));
686
+ expect(finalReady).toEqual([]);
687
+ });
688
+
689
+ it('should handle node marked as started multiple times', () => {
690
+ const workflow = createWorkflow([createNode('A')]);
691
+ const dagManager = new DAGManager(workflow);
692
+ const scheduler = new Scheduler(dagManager);
693
+
694
+ // Mark as started multiple times
695
+ scheduler.markStarted('A');
696
+ scheduler.markStarted('A');
697
+ scheduler.markStarted('A');
698
+
699
+ // Should only count as one
700
+ expect(scheduler.getRunningCount()).toBe(1);
701
+ });
702
+
703
+ it('should handle node completion after failure', () => {
704
+ const workflow = createWorkflow([createNode('A')]);
705
+ const dagManager = new DAGManager(workflow);
706
+ const scheduler = new Scheduler(dagManager);
707
+
708
+ scheduler.markFailed('A');
709
+ scheduler.markCompleted('A');
710
+
711
+ // Should not throw, running count should be 0
712
+ expect(scheduler.getRunningCount()).toBe(0);
713
+ });
714
+ });
715
+ });