@sprinterai/runtime 0.4.1 → 0.5.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 (384) hide show
  1. package/dist/adapters/a2a-adapter.d.ts +37 -9
  2. package/dist/adapters/a2a-adapter.d.ts.map +1 -1
  3. package/dist/adapters/a2a-adapter.js +132 -9
  4. package/dist/adapters/a2a-adapter.js.map +1 -1
  5. package/dist/adapters/a2a-adapter.test.d.ts +2 -0
  6. package/dist/adapters/a2a-adapter.test.d.ts.map +1 -0
  7. package/dist/adapters/a2a-adapter.test.js +295 -0
  8. package/dist/adapters/a2a-adapter.test.js.map +1 -0
  9. package/dist/adapters/http-agent-adapter.d.ts +19 -9
  10. package/dist/adapters/http-agent-adapter.d.ts.map +1 -1
  11. package/dist/adapters/http-agent-adapter.js +55 -9
  12. package/dist/adapters/http-agent-adapter.js.map +1 -1
  13. package/dist/adapters/http-agent-adapter.test.d.ts +2 -0
  14. package/dist/adapters/http-agent-adapter.test.d.ts.map +1 -0
  15. package/dist/adapters/http-agent-adapter.test.js +164 -0
  16. package/dist/adapters/http-agent-adapter.test.js.map +1 -0
  17. package/dist/adapters/index.d.ts +8 -8
  18. package/dist/adapters/index.d.ts.map +1 -1
  19. package/dist/adapters/index.js +4 -4
  20. package/dist/adapters/index.js.map +1 -1
  21. package/dist/adapters/mcp-adapter.d.ts +29 -9
  22. package/dist/adapters/mcp-adapter.d.ts.map +1 -1
  23. package/dist/adapters/mcp-adapter.js +43 -8
  24. package/dist/adapters/mcp-adapter.js.map +1 -1
  25. package/dist/adapters/mcp-adapter.test.d.ts +2 -0
  26. package/dist/adapters/mcp-adapter.test.d.ts.map +1 -0
  27. package/dist/adapters/mcp-adapter.test.js +118 -0
  28. package/dist/adapters/mcp-adapter.test.js.map +1 -0
  29. package/dist/adapters/openclaw-adapter.d.ts +34 -8
  30. package/dist/adapters/openclaw-adapter.d.ts.map +1 -1
  31. package/dist/adapters/openclaw-adapter.js +38 -8
  32. package/dist/adapters/openclaw-adapter.js.map +1 -1
  33. package/dist/adapters/openclaw-adapter.test.d.ts +2 -0
  34. package/dist/adapters/openclaw-adapter.test.d.ts.map +1 -0
  35. package/dist/adapters/openclaw-adapter.test.js +77 -0
  36. package/dist/adapters/openclaw-adapter.test.js.map +1 -0
  37. package/dist/agent/agent-registry.test.js +1 -1
  38. package/dist/agent/agent-resolver.d.ts +8 -2
  39. package/dist/agent/agent-resolver.d.ts.map +1 -1
  40. package/dist/agent/agent-resolver.js +7 -1
  41. package/dist/agent/agent-resolver.js.map +1 -1
  42. package/dist/agent/agent-resolver.test.js +21 -3
  43. package/dist/agent/agent-resolver.test.js.map +1 -1
  44. package/dist/agent/delegate.test.js +1 -1
  45. package/dist/agent/execute-agent.d.ts +32 -8
  46. package/dist/agent/execute-agent.d.ts.map +1 -1
  47. package/dist/agent/execute-agent.js +40 -3
  48. package/dist/agent/execute-agent.js.map +1 -1
  49. package/dist/agent/index.d.ts +9 -9
  50. package/dist/agent/index.d.ts.map +1 -1
  51. package/dist/agent/index.js +5 -5
  52. package/dist/agent/index.js.map +1 -1
  53. package/dist/agent/prompt-builder.test.js +1 -1
  54. package/dist/approval/approval-manager.d.ts +19 -0
  55. package/dist/approval/approval-manager.d.ts.map +1 -0
  56. package/dist/approval/approval-manager.js +36 -0
  57. package/dist/approval/approval-manager.js.map +1 -0
  58. package/dist/approval/approval-manager.test.d.ts +2 -0
  59. package/dist/approval/approval-manager.test.d.ts.map +1 -0
  60. package/dist/approval/approval-manager.test.js +239 -0
  61. package/dist/approval/approval-manager.test.js.map +1 -0
  62. package/dist/approval/index.d.ts +3 -0
  63. package/dist/approval/index.d.ts.map +1 -0
  64. package/dist/approval/index.js +2 -0
  65. package/dist/approval/index.js.map +1 -0
  66. package/dist/chat/chat-handler.d.ts +14 -7
  67. package/dist/chat/chat-handler.d.ts.map +1 -1
  68. package/dist/chat/chat-handler.js +56 -11
  69. package/dist/chat/chat-handler.js.map +1 -1
  70. package/dist/chat/chat-handler.test.js +100 -11
  71. package/dist/chat/chat-handler.test.js.map +1 -1
  72. package/dist/chat/index.d.ts +3 -3
  73. package/dist/chat/index.js +2 -2
  74. package/dist/chat/message-utils.d.ts +15 -7
  75. package/dist/chat/message-utils.d.ts.map +1 -1
  76. package/dist/chat/message-utils.js +94 -22
  77. package/dist/chat/message-utils.js.map +1 -1
  78. package/dist/chat/message-utils.test.js +71 -1
  79. package/dist/chat/message-utils.test.js.map +1 -1
  80. package/dist/document/chunk-generator.d.ts +6 -0
  81. package/dist/document/chunk-generator.d.ts.map +1 -0
  82. package/dist/document/chunk-generator.js +107 -0
  83. package/dist/document/chunk-generator.js.map +1 -0
  84. package/dist/document/chunk-generator.test.d.ts +2 -0
  85. package/dist/document/chunk-generator.test.d.ts.map +1 -0
  86. package/dist/document/chunk-generator.test.js +166 -0
  87. package/dist/document/chunk-generator.test.js.map +1 -0
  88. package/dist/document/document-processor.d.ts +27 -0
  89. package/dist/document/document-processor.d.ts.map +1 -0
  90. package/dist/document/document-processor.js +44 -0
  91. package/dist/document/document-processor.js.map +1 -0
  92. package/dist/document/document-processor.test.d.ts +2 -0
  93. package/dist/document/document-processor.test.d.ts.map +1 -0
  94. package/dist/document/document-processor.test.js +197 -0
  95. package/dist/document/document-processor.test.js.map +1 -0
  96. package/dist/document/index.d.ts +5 -0
  97. package/dist/document/index.d.ts.map +1 -0
  98. package/dist/document/index.js +4 -0
  99. package/dist/document/index.js.map +1 -0
  100. package/dist/document/parsers/index.d.ts +2 -0
  101. package/dist/document/parsers/index.d.ts.map +1 -0
  102. package/dist/document/parsers/index.js +2 -0
  103. package/dist/document/parsers/index.js.map +1 -0
  104. package/dist/document/parsers/text-parser.d.ts +4 -0
  105. package/dist/document/parsers/text-parser.d.ts.map +1 -0
  106. package/dist/document/parsers/text-parser.js +23 -0
  107. package/dist/document/parsers/text-parser.js.map +1 -0
  108. package/dist/document/parsers/text-parser.test.d.ts +2 -0
  109. package/dist/document/parsers/text-parser.test.d.ts.map +1 -0
  110. package/dist/document/parsers/text-parser.test.js +64 -0
  111. package/dist/document/parsers/text-parser.test.js.map +1 -0
  112. package/dist/eval/eval-runner.test.js +2 -2
  113. package/dist/eval/index.d.ts +3 -3
  114. package/dist/eval/index.js +2 -2
  115. package/dist/eval/scorers.test.js +1 -1
  116. package/dist/events/event-bus.d.ts +12 -0
  117. package/dist/events/event-bus.d.ts.map +1 -0
  118. package/dist/events/event-bus.js +77 -0
  119. package/dist/events/event-bus.js.map +1 -0
  120. package/dist/events/event-bus.test.d.ts +2 -0
  121. package/dist/events/event-bus.test.d.ts.map +1 -0
  122. package/dist/events/event-bus.test.js +155 -0
  123. package/dist/events/event-bus.test.js.map +1 -0
  124. package/dist/events/index.d.ts +2 -0
  125. package/dist/events/index.d.ts.map +1 -0
  126. package/dist/events/index.js +2 -0
  127. package/dist/events/index.js.map +1 -0
  128. package/dist/guardrail/guardrail-pipeline.test.js +1 -1
  129. package/dist/guardrail/index.d.ts +2 -2
  130. package/dist/guardrail/index.js +1 -1
  131. package/dist/index.d.ts +81 -45
  132. package/dist/index.d.ts.map +1 -1
  133. package/dist/index.js +55 -27
  134. package/dist/index.js.map +1 -1
  135. package/dist/jobs/in-memory-job-queue.d.ts +20 -0
  136. package/dist/jobs/in-memory-job-queue.d.ts.map +1 -0
  137. package/dist/jobs/in-memory-job-queue.js +120 -0
  138. package/dist/jobs/in-memory-job-queue.js.map +1 -0
  139. package/dist/jobs/in-memory-job-queue.test.d.ts +2 -0
  140. package/dist/jobs/in-memory-job-queue.test.d.ts.map +1 -0
  141. package/dist/jobs/in-memory-job-queue.test.js +146 -0
  142. package/dist/jobs/in-memory-job-queue.test.js.map +1 -0
  143. package/dist/jobs/index.d.ts +4 -0
  144. package/dist/jobs/index.d.ts.map +1 -0
  145. package/dist/jobs/index.js +3 -0
  146. package/dist/jobs/index.js.map +1 -0
  147. package/dist/jobs/job-runner.d.ts +42 -0
  148. package/dist/jobs/job-runner.d.ts.map +1 -0
  149. package/dist/jobs/job-runner.js +119 -0
  150. package/dist/jobs/job-runner.js.map +1 -0
  151. package/dist/jobs/job-runner.test.d.ts +2 -0
  152. package/dist/jobs/job-runner.test.d.ts.map +1 -0
  153. package/dist/jobs/job-runner.test.js +190 -0
  154. package/dist/jobs/job-runner.test.js.map +1 -0
  155. package/dist/memory/index.d.ts +3 -3
  156. package/dist/memory/index.js +2 -2
  157. package/dist/memory/memory-prompt.d.ts +1 -1
  158. package/dist/module/create-runtime.d.ts +21 -6
  159. package/dist/module/create-runtime.d.ts.map +1 -1
  160. package/dist/module/create-runtime.js +7 -7
  161. package/dist/module/create-runtime.js.map +1 -1
  162. package/dist/module/create-runtime.test.js +8 -8
  163. package/dist/module/index.d.ts +5 -4
  164. package/dist/module/index.d.ts.map +1 -1
  165. package/dist/module/index.js +3 -2
  166. package/dist/module/index.js.map +1 -1
  167. package/dist/module/map-supabase-stores.d.ts +33 -0
  168. package/dist/module/map-supabase-stores.d.ts.map +1 -0
  169. package/dist/module/map-supabase-stores.js +28 -0
  170. package/dist/module/map-supabase-stores.js.map +1 -0
  171. package/dist/module/module-loader.d.ts +9 -1
  172. package/dist/module/module-loader.d.ts.map +1 -1
  173. package/dist/module/module-loader.js +40 -1
  174. package/dist/module/module-loader.js.map +1 -1
  175. package/dist/module/module-loader.test.js +38 -1
  176. package/dist/module/module-loader.test.js.map +1 -1
  177. package/dist/notification/digest-scheduler.d.ts +18 -0
  178. package/dist/notification/digest-scheduler.d.ts.map +1 -0
  179. package/dist/notification/digest-scheduler.js +44 -0
  180. package/dist/notification/digest-scheduler.js.map +1 -0
  181. package/dist/notification/digest-scheduler.test.d.ts +2 -0
  182. package/dist/notification/digest-scheduler.test.d.ts.map +1 -0
  183. package/dist/notification/digest-scheduler.test.js +306 -0
  184. package/dist/notification/digest-scheduler.test.js.map +1 -0
  185. package/dist/notification/index.d.ts +5 -0
  186. package/dist/notification/index.d.ts.map +1 -0
  187. package/dist/notification/index.js +3 -0
  188. package/dist/notification/index.js.map +1 -0
  189. package/dist/notification/notification-engine.d.ts +20 -0
  190. package/dist/notification/notification-engine.d.ts.map +1 -0
  191. package/dist/notification/notification-engine.js +78 -0
  192. package/dist/notification/notification-engine.js.map +1 -0
  193. package/dist/notification/notification-engine.test.d.ts +2 -0
  194. package/dist/notification/notification-engine.test.d.ts.map +1 -0
  195. package/dist/notification/notification-engine.test.js +364 -0
  196. package/dist/notification/notification-engine.test.js.map +1 -0
  197. package/dist/providers/index.d.ts +5 -0
  198. package/dist/providers/index.d.ts.map +1 -0
  199. package/dist/providers/index.js +3 -0
  200. package/dist/providers/index.js.map +1 -0
  201. package/dist/providers/model-resolver.d.ts +93 -0
  202. package/dist/providers/model-resolver.d.ts.map +1 -0
  203. package/dist/providers/model-resolver.js +152 -0
  204. package/dist/providers/model-resolver.js.map +1 -0
  205. package/dist/providers/model-resolver.test.d.ts +2 -0
  206. package/dist/providers/model-resolver.test.d.ts.map +1 -0
  207. package/dist/providers/model-resolver.test.js +199 -0
  208. package/dist/providers/model-resolver.test.js.map +1 -0
  209. package/dist/providers/noop-providers.d.ts +6 -0
  210. package/dist/providers/noop-providers.d.ts.map +1 -0
  211. package/dist/providers/noop-providers.js +15 -0
  212. package/dist/providers/noop-providers.js.map +1 -0
  213. package/dist/providers/noop-providers.test.d.ts +2 -0
  214. package/dist/providers/noop-providers.test.d.ts.map +1 -0
  215. package/dist/providers/noop-providers.test.js +63 -0
  216. package/dist/providers/noop-providers.test.js.map +1 -0
  217. package/dist/providers/provider-interfaces.d.ts +65 -0
  218. package/dist/providers/provider-interfaces.d.ts.map +1 -0
  219. package/dist/providers/provider-interfaces.js +2 -0
  220. package/dist/providers/provider-interfaces.js.map +1 -0
  221. package/dist/realtime/index.d.ts +3 -0
  222. package/dist/realtime/index.d.ts.map +1 -0
  223. package/dist/realtime/index.js +2 -0
  224. package/dist/realtime/index.js.map +1 -0
  225. package/dist/realtime/realtime-manager.d.ts +20 -0
  226. package/dist/realtime/realtime-manager.d.ts.map +1 -0
  227. package/dist/realtime/realtime-manager.js +110 -0
  228. package/dist/realtime/realtime-manager.js.map +1 -0
  229. package/dist/realtime/realtime-manager.test.d.ts +2 -0
  230. package/dist/realtime/realtime-manager.test.d.ts.map +1 -0
  231. package/dist/realtime/realtime-manager.test.js +273 -0
  232. package/dist/realtime/realtime-manager.test.js.map +1 -0
  233. package/dist/scoring/compute-score.test.js +1 -1
  234. package/dist/scoring/index.d.ts +2 -2
  235. package/dist/scoring/index.js +1 -1
  236. package/dist/search/cosine-similarity.d.ts +8 -0
  237. package/dist/search/cosine-similarity.d.ts.map +1 -0
  238. package/dist/search/cosine-similarity.js +28 -0
  239. package/dist/search/cosine-similarity.js.map +1 -0
  240. package/dist/search/cosine-similarity.test.d.ts +2 -0
  241. package/dist/search/cosine-similarity.test.d.ts.map +1 -0
  242. package/dist/search/cosine-similarity.test.js +49 -0
  243. package/dist/search/cosine-similarity.test.js.map +1 -0
  244. package/dist/search/hybrid-search.d.ts +47 -0
  245. package/dist/search/hybrid-search.d.ts.map +1 -0
  246. package/dist/search/hybrid-search.js +111 -0
  247. package/dist/search/hybrid-search.js.map +1 -0
  248. package/dist/search/hybrid-search.test.d.ts +2 -0
  249. package/dist/search/hybrid-search.test.d.ts.map +1 -0
  250. package/dist/search/hybrid-search.test.js +238 -0
  251. package/dist/search/hybrid-search.test.js.map +1 -0
  252. package/dist/search/in-memory-search.d.ts +17 -0
  253. package/dist/search/in-memory-search.d.ts.map +1 -0
  254. package/dist/search/in-memory-search.js +59 -0
  255. package/dist/search/in-memory-search.js.map +1 -0
  256. package/dist/search/in-memory-search.test.d.ts +2 -0
  257. package/dist/search/in-memory-search.test.d.ts.map +1 -0
  258. package/dist/search/in-memory-search.test.js +169 -0
  259. package/dist/search/in-memory-search.test.js.map +1 -0
  260. package/dist/search/index.d.ts +6 -0
  261. package/dist/search/index.d.ts.map +1 -0
  262. package/dist/search/index.js +4 -0
  263. package/dist/search/index.js.map +1 -0
  264. package/dist/testing/in-memory-agent-store.d.ts +5 -1
  265. package/dist/testing/in-memory-agent-store.d.ts.map +1 -1
  266. package/dist/testing/in-memory-agent-store.js +19 -0
  267. package/dist/testing/in-memory-agent-store.js.map +1 -1
  268. package/dist/testing/in-memory-agent-store.test.js +1 -1
  269. package/dist/testing/in-memory-chat-store.d.ts.map +1 -1
  270. package/dist/testing/in-memory-chat-store.js +2 -1
  271. package/dist/testing/in-memory-chat-store.js.map +1 -1
  272. package/dist/testing/in-memory-entity-store.d.ts +5 -1
  273. package/dist/testing/in-memory-entity-store.d.ts.map +1 -1
  274. package/dist/testing/in-memory-entity-store.js +69 -4
  275. package/dist/testing/in-memory-entity-store.js.map +1 -1
  276. package/dist/testing/in-memory-entity-store.test.js +1 -1
  277. package/dist/testing/in-memory-memory-store.d.ts +3 -1
  278. package/dist/testing/in-memory-memory-store.d.ts.map +1 -1
  279. package/dist/testing/in-memory-memory-store.js +20 -0
  280. package/dist/testing/in-memory-memory-store.js.map +1 -1
  281. package/dist/testing/in-memory-tool-store.d.ts +13 -2
  282. package/dist/testing/in-memory-tool-store.d.ts.map +1 -1
  283. package/dist/testing/in-memory-tool-store.js +51 -0
  284. package/dist/testing/in-memory-tool-store.js.map +1 -1
  285. package/dist/testing/index.d.ts +7 -7
  286. package/dist/testing/index.js +7 -7
  287. package/dist/tool/ai-bridge.d.ts +1 -0
  288. package/dist/tool/ai-bridge.d.ts.map +1 -1
  289. package/dist/tool/ai-bridge.js +1 -0
  290. package/dist/tool/ai-bridge.js.map +1 -1
  291. package/dist/tool/ai-bridge.test.js +1 -1
  292. package/dist/tool/catalog.d.ts +19 -0
  293. package/dist/tool/catalog.d.ts.map +1 -0
  294. package/dist/tool/catalog.js +88 -0
  295. package/dist/tool/catalog.js.map +1 -0
  296. package/dist/tool/catalog.test.d.ts +2 -0
  297. package/dist/tool/catalog.test.d.ts.map +1 -0
  298. package/dist/tool/catalog.test.js +129 -0
  299. package/dist/tool/catalog.test.js.map +1 -0
  300. package/dist/tool/entity-tools-factory.test.js +2 -2
  301. package/dist/tool/execute-registered-tool.d.ts +17 -0
  302. package/dist/tool/execute-registered-tool.d.ts.map +1 -0
  303. package/dist/tool/execute-registered-tool.js +24 -0
  304. package/dist/tool/execute-registered-tool.js.map +1 -0
  305. package/dist/tool/execute-registered-tool.test.d.ts +2 -0
  306. package/dist/tool/execute-registered-tool.test.d.ts.map +1 -0
  307. package/dist/tool/execute-registered-tool.test.js +73 -0
  308. package/dist/tool/execute-registered-tool.test.js.map +1 -0
  309. package/dist/tool/index.d.ts +11 -7
  310. package/dist/tool/index.d.ts.map +1 -1
  311. package/dist/tool/index.js +7 -5
  312. package/dist/tool/index.js.map +1 -1
  313. package/dist/tool/resolve-tools.d.ts +3 -1
  314. package/dist/tool/resolve-tools.d.ts.map +1 -1
  315. package/dist/tool/resolve-tools.js +1 -1
  316. package/dist/tool/resolve-tools.js.map +1 -1
  317. package/dist/tool/resolve-tools.test.js +1 -1
  318. package/dist/tool/tool-executor.d.ts +3 -2
  319. package/dist/tool/tool-executor.d.ts.map +1 -1
  320. package/dist/tool/tool-executor.js +4 -2
  321. package/dist/tool/tool-executor.js.map +1 -1
  322. package/dist/tool/tool-executor.test.js +2 -2
  323. package/dist/tool/tool-registry.test.js +1 -1
  324. package/dist/tool/zod-to-json-schema.d.ts +7 -0
  325. package/dist/tool/zod-to-json-schema.d.ts.map +1 -0
  326. package/dist/tool/zod-to-json-schema.js +12 -0
  327. package/dist/tool/zod-to-json-schema.js.map +1 -0
  328. package/dist/tool/zod-to-json-schema.test.d.ts +2 -0
  329. package/dist/tool/zod-to-json-schema.test.d.ts.map +1 -0
  330. package/dist/tool/zod-to-json-schema.test.js +127 -0
  331. package/dist/tool/zod-to-json-schema.test.js.map +1 -0
  332. package/dist/webhook/index.d.ts +4 -0
  333. package/dist/webhook/index.d.ts.map +1 -0
  334. package/dist/webhook/index.js +3 -0
  335. package/dist/webhook/index.js.map +1 -0
  336. package/dist/webhook/webhook-delivery.d.ts +12 -0
  337. package/dist/webhook/webhook-delivery.d.ts.map +1 -0
  338. package/dist/webhook/webhook-delivery.js +102 -0
  339. package/dist/webhook/webhook-delivery.js.map +1 -0
  340. package/dist/webhook/webhook-delivery.test.d.ts +2 -0
  341. package/dist/webhook/webhook-delivery.test.d.ts.map +1 -0
  342. package/dist/webhook/webhook-delivery.test.js +284 -0
  343. package/dist/webhook/webhook-delivery.test.js.map +1 -0
  344. package/dist/webhook/webhook-signer.d.ts +14 -0
  345. package/dist/webhook/webhook-signer.d.ts.map +1 -0
  346. package/dist/webhook/webhook-signer.js +31 -0
  347. package/dist/webhook/webhook-signer.js.map +1 -0
  348. package/dist/webhook/webhook-signer.test.d.ts +2 -0
  349. package/dist/webhook/webhook-signer.test.d.ts.map +1 -0
  350. package/dist/webhook/webhook-signer.test.js +74 -0
  351. package/dist/webhook/webhook-signer.test.js.map +1 -0
  352. package/dist/workflow/compile.js +4 -1
  353. package/dist/workflow/compile.js.map +1 -1
  354. package/dist/workflow/compile.test.js +1 -1
  355. package/dist/workflow/evaluate-nodes.d.ts +24 -0
  356. package/dist/workflow/evaluate-nodes.d.ts.map +1 -0
  357. package/dist/workflow/evaluate-nodes.js +126 -0
  358. package/dist/workflow/evaluate-nodes.js.map +1 -0
  359. package/dist/workflow/evaluate-nodes.test.d.ts +2 -0
  360. package/dist/workflow/evaluate-nodes.test.d.ts.map +1 -0
  361. package/dist/workflow/evaluate-nodes.test.js +363 -0
  362. package/dist/workflow/evaluate-nodes.test.js.map +1 -0
  363. package/dist/workflow/index.d.ts +8 -3
  364. package/dist/workflow/index.d.ts.map +1 -1
  365. package/dist/workflow/index.js +4 -2
  366. package/dist/workflow/index.js.map +1 -1
  367. package/dist/workflow/node-executor.d.ts +40 -0
  368. package/dist/workflow/node-executor.d.ts.map +1 -0
  369. package/dist/workflow/node-executor.js +2 -0
  370. package/dist/workflow/node-executor.js.map +1 -0
  371. package/dist/workflow/node-executor.test.d.ts +2 -0
  372. package/dist/workflow/node-executor.test.d.ts.map +1 -0
  373. package/dist/workflow/node-executor.test.js +91 -0
  374. package/dist/workflow/node-executor.test.js.map +1 -0
  375. package/dist/workflow/status.test.js +1 -1
  376. package/dist/workflow/workflow-runner.d.ts +57 -0
  377. package/dist/workflow/workflow-runner.d.ts.map +1 -0
  378. package/dist/workflow/workflow-runner.js +263 -0
  379. package/dist/workflow/workflow-runner.js.map +1 -0
  380. package/dist/workflow/workflow-runner.test.d.ts +2 -0
  381. package/dist/workflow/workflow-runner.test.d.ts.map +1 -0
  382. package/dist/workflow/workflow-runner.test.js +657 -0
  383. package/dist/workflow/workflow-runner.test.js.map +1 -0
  384. package/package.json +9 -4
@@ -0,0 +1,657 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { runWorkflow } from './workflow-runner.js';
3
+ // ─── In-Memory Mock Store ──────────────────────────────────────
4
+ function createMockStore() {
5
+ const runs = [];
6
+ const nodeRuns = [];
7
+ let runCounter = 0;
8
+ let nodeCounter = 0;
9
+ return {
10
+ runs,
11
+ nodeRuns,
12
+ async listWorkflowRuns() {
13
+ return runs;
14
+ },
15
+ async getWorkflowRun(id) {
16
+ return runs.find((r) => r.id === id) ?? null;
17
+ },
18
+ async getNodeRuns(workflowRunId) {
19
+ return nodeRuns.filter((n) => n.workflow_run_id === workflowRunId);
20
+ },
21
+ async createWorkflowRun(input) {
22
+ runCounter++;
23
+ const run = {
24
+ id: `run-${runCounter}`,
25
+ entity_id: input.entityId,
26
+ tenant_id: input.tenantId,
27
+ status: 'pending',
28
+ trigger_type: input.triggerType,
29
+ started_at: new Date().toISOString(),
30
+ completed_at: null,
31
+ duration_ms: null,
32
+ metadata: input.metadata ?? null,
33
+ created_at: new Date().toISOString(),
34
+ };
35
+ runs.push(run);
36
+ return run;
37
+ },
38
+ async createNodeRuns(inputs) {
39
+ const created = [];
40
+ for (const input of inputs) {
41
+ nodeCounter++;
42
+ const nodeRun = {
43
+ id: `nr-${nodeCounter}`,
44
+ workflow_run_id: input.workflowRunId,
45
+ entity_id: input.entityId,
46
+ tenant_id: input.tenantId,
47
+ node_key: input.nodeKey,
48
+ node_type: input.nodeType,
49
+ label: input.label,
50
+ field_name: input.fieldName ?? null,
51
+ block_id: input.blockId ?? null,
52
+ output_type: input.outputType,
53
+ status: input.status,
54
+ assignee_type: input.assigneeType ?? null,
55
+ assignee_agent_slug: input.assigneeAgentSlug ?? null,
56
+ depends_on: input.dependsOn ?? null,
57
+ instructions: input.instructions ?? null,
58
+ metadata: null,
59
+ error_message: null,
60
+ claimed_by: null,
61
+ claimed_at: null,
62
+ attempt_count: 0,
63
+ started_at: null,
64
+ completed_at: null,
65
+ duration_ms: null,
66
+ created_at: new Date().toISOString(),
67
+ };
68
+ nodeRuns.push(nodeRun);
69
+ created.push(nodeRun);
70
+ }
71
+ return created;
72
+ },
73
+ async updateNodeRun(nodeRunId, updates) {
74
+ const node = nodeRuns.find((n) => n.id === nodeRunId);
75
+ if (!node)
76
+ return;
77
+ if (updates.status !== undefined)
78
+ node.status = updates.status;
79
+ if (updates.errorMessage !== undefined)
80
+ node.error_message = updates.errorMessage;
81
+ if (updates.startedAt !== undefined)
82
+ node.started_at = updates.startedAt;
83
+ if (updates.completedAt !== undefined)
84
+ node.completed_at = updates.completedAt;
85
+ if (updates.durationMs !== undefined)
86
+ node.duration_ms = updates.durationMs;
87
+ if (updates.attemptCount !== undefined)
88
+ node.attempt_count = updates.attemptCount;
89
+ },
90
+ async claimNodeRun(nodeRunId, claimedBy) {
91
+ const node = nodeRuns.find((n) => n.id === nodeRunId);
92
+ if (!node || node.claimed_by)
93
+ return null;
94
+ node.claimed_by = claimedBy;
95
+ node.claimed_at = new Date().toISOString();
96
+ return node;
97
+ },
98
+ async completeWorkflowRun(runId, status, durationMs) {
99
+ const run = runs.find((r) => r.id === runId);
100
+ if (!run)
101
+ return;
102
+ run.status = status;
103
+ run.completed_at = new Date().toISOString();
104
+ if (durationMs !== undefined)
105
+ run.duration_ms = durationMs;
106
+ },
107
+ async getClaimableNodes(tenantId, agentSlug) {
108
+ return nodeRuns.filter((n) => n.tenant_id === tenantId &&
109
+ n.status === 'pending' &&
110
+ (!agentSlug || n.assignee_agent_slug === agentSlug));
111
+ },
112
+ async expireStaleNodeClaims() {
113
+ return [];
114
+ },
115
+ };
116
+ }
117
+ // ─── Test Helpers ──────────────────────────────────────────────
118
+ function successExecutor(extractedFields, content) {
119
+ return async (ctx) => ({
120
+ success: true,
121
+ fieldsExtracted: extractedFields ?? (ctx.node.field_name ? [ctx.node.field_name] : []),
122
+ updatedContent: content ??
123
+ (ctx.node.field_name ? { [ctx.node.field_name]: `extracted-${ctx.node.field_name}` } : {}),
124
+ });
125
+ }
126
+ function failingExecutor(error) {
127
+ return async () => ({
128
+ success: false,
129
+ fieldsExtracted: [],
130
+ error,
131
+ });
132
+ }
133
+ function throwingExecutor(error) {
134
+ return async () => {
135
+ throw new Error(error);
136
+ };
137
+ }
138
+ function makeDefinition(nodes) {
139
+ return { nodes };
140
+ }
141
+ function makeBaseOptions(store, definition, executeNode, overrides = {}) {
142
+ return {
143
+ entityId: 'ent-1',
144
+ tenantId: 'ten-1',
145
+ definition,
146
+ entityContent: {},
147
+ store,
148
+ executeNode,
149
+ ...overrides,
150
+ };
151
+ }
152
+ // ─── Tests ──────────────────────────────────────────────────────
153
+ describe('runWorkflow', () => {
154
+ let store;
155
+ beforeEach(() => {
156
+ store = createMockStore();
157
+ });
158
+ describe('empty workflow', () => {
159
+ it('completes immediately with no nodes', async () => {
160
+ const definition = makeDefinition([]);
161
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
162
+ expect(result.status).toBe('completed');
163
+ expect(result.fieldsExtracted).toEqual([]);
164
+ expect(result.claimedNodeCount).toBe(0);
165
+ expect(Object.keys(result.errors)).toHaveLength(0);
166
+ });
167
+ });
168
+ describe('simple linear workflow (A -> B -> C)', () => {
169
+ const definition = makeDefinition([
170
+ {
171
+ key: 'agent_task:title',
172
+ type: 'agent_task',
173
+ label: 'Extract title',
174
+ fieldName: 'title',
175
+ outputType: 'field',
176
+ dependsOn: [],
177
+ assigneeType: 'agent',
178
+ },
179
+ {
180
+ key: 'agent_task:summary',
181
+ type: 'agent_task',
182
+ label: 'Extract summary',
183
+ fieldName: 'summary',
184
+ outputType: 'field',
185
+ dependsOn: ['agent_task:title'],
186
+ assigneeType: 'agent',
187
+ },
188
+ {
189
+ key: 'agent_task:tags',
190
+ type: 'agent_task',
191
+ label: 'Extract tags',
192
+ fieldName: 'tags',
193
+ outputType: 'field',
194
+ dependsOn: ['agent_task:summary'],
195
+ assigneeType: 'agent',
196
+ },
197
+ ]);
198
+ it('executes all nodes in order', async () => {
199
+ const executionOrder = [];
200
+ const executor = async (ctx) => {
201
+ executionOrder.push(ctx.node.node_key);
202
+ return {
203
+ success: true,
204
+ fieldsExtracted: ctx.node.field_name ? [ctx.node.field_name] : [],
205
+ updatedContent: ctx.node.field_name
206
+ ? { [ctx.node.field_name]: `extracted-${ctx.node.field_name}` }
207
+ : {},
208
+ };
209
+ };
210
+ const result = await runWorkflow(makeBaseOptions(store, definition, executor));
211
+ expect(result.status).toBe('completed');
212
+ expect(result.fieldsExtracted).toEqual(['title', 'summary', 'tags']);
213
+ expect(result.claimedNodeCount).toBe(3);
214
+ expect(executionOrder).toEqual(['agent_task:title', 'agent_task:summary', 'agent_task:tags']);
215
+ });
216
+ it('creates workflow run and node runs in store', async () => {
217
+ await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
218
+ expect(store.runs).toHaveLength(1);
219
+ expect(store.runs[0].status).toBe('completed');
220
+ expect(store.nodeRuns).toHaveLength(3);
221
+ expect(store.nodeRuns.every((n) => n.status === 'completed')).toBe(true);
222
+ });
223
+ });
224
+ describe('parallel nodes', () => {
225
+ const definition = makeDefinition([
226
+ {
227
+ key: 'agent_task:title',
228
+ type: 'agent_task',
229
+ label: 'Extract title',
230
+ fieldName: 'title',
231
+ outputType: 'field',
232
+ dependsOn: [],
233
+ assigneeType: 'agent',
234
+ },
235
+ {
236
+ key: 'agent_task:author',
237
+ type: 'agent_task',
238
+ label: 'Extract author',
239
+ fieldName: 'author',
240
+ outputType: 'field',
241
+ dependsOn: [],
242
+ assigneeType: 'agent',
243
+ },
244
+ {
245
+ key: 'agent_task:summary',
246
+ type: 'agent_task',
247
+ label: 'Extract summary',
248
+ fieldName: 'summary',
249
+ outputType: 'field',
250
+ dependsOn: ['agent_task:title', 'agent_task:author'],
251
+ assigneeType: 'agent',
252
+ },
253
+ ]);
254
+ it('executes parallel nodes in the same iteration', async () => {
255
+ const executionOrder = [];
256
+ const executor = async (ctx) => {
257
+ executionOrder.push(ctx.node.node_key);
258
+ return {
259
+ success: true,
260
+ fieldsExtracted: ctx.node.field_name ? [ctx.node.field_name] : [],
261
+ updatedContent: ctx.node.field_name
262
+ ? { [ctx.node.field_name]: `val-${ctx.node.field_name}` }
263
+ : {},
264
+ };
265
+ };
266
+ const result = await runWorkflow(makeBaseOptions(store, definition, executor));
267
+ expect(result.status).toBe('completed');
268
+ expect(result.fieldsExtracted).toContain('title');
269
+ expect(result.fieldsExtracted).toContain('author');
270
+ expect(result.fieldsExtracted).toContain('summary');
271
+ expect(result.claimedNodeCount).toBe(3);
272
+ // title and author should be in the first batch (before summary)
273
+ const titleIdx = executionOrder.indexOf('agent_task:title');
274
+ const authorIdx = executionOrder.indexOf('agent_task:author');
275
+ const summaryIdx = executionOrder.indexOf('agent_task:summary');
276
+ expect(summaryIdx).toBeGreaterThan(titleIdx);
277
+ expect(summaryIdx).toBeGreaterThan(authorIdx);
278
+ });
279
+ });
280
+ describe('wait_human nodes', () => {
281
+ const definition = makeDefinition([
282
+ {
283
+ key: 'wait_human:notes',
284
+ type: 'wait_human',
285
+ label: 'Human notes',
286
+ fieldName: 'notes',
287
+ outputType: 'field',
288
+ dependsOn: [],
289
+ assigneeType: 'human',
290
+ },
291
+ {
292
+ key: 'agent_task:summary',
293
+ type: 'agent_task',
294
+ label: 'Extract summary',
295
+ fieldName: 'summary',
296
+ outputType: 'field',
297
+ dependsOn: ['wait_human:notes'],
298
+ assigneeType: 'agent',
299
+ },
300
+ ]);
301
+ it('blocks dependent nodes when wait_human is not populated', async () => {
302
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
303
+ // The workflow should end as waiting_human since the human node is unsatisfied
304
+ expect(result.status).toBe('waiting_human');
305
+ expect(result.fieldsExtracted).toEqual([]);
306
+ expect(result.claimedNodeCount).toBe(0);
307
+ // notes should be waiting_human, summary should be blocked
308
+ const notesNode = store.nodeRuns.find((n) => n.node_key === 'wait_human:notes');
309
+ const summaryNode = store.nodeRuns.find((n) => n.node_key === 'agent_task:summary');
310
+ expect(notesNode?.status).toBe('waiting_human');
311
+ expect(summaryNode?.status).toBe('blocked');
312
+ });
313
+ it('proceeds when wait_human field is already populated', async () => {
314
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor(), {
315
+ entityContent: { notes: 'User provided notes' },
316
+ }));
317
+ expect(result.status).toBe('completed');
318
+ expect(result.fieldsExtracted).toContain('summary');
319
+ expect(result.claimedNodeCount).toBe(1); // only summary was executed
320
+ });
321
+ });
322
+ describe('already-populated fields', () => {
323
+ const definition = makeDefinition([
324
+ {
325
+ key: 'agent_task:title',
326
+ type: 'agent_task',
327
+ label: 'Extract title',
328
+ fieldName: 'title',
329
+ outputType: 'field',
330
+ dependsOn: [],
331
+ assigneeType: 'agent',
332
+ },
333
+ {
334
+ key: 'agent_task:summary',
335
+ type: 'agent_task',
336
+ label: 'Extract summary',
337
+ fieldName: 'summary',
338
+ outputType: 'field',
339
+ dependsOn: ['agent_task:title'],
340
+ assigneeType: 'agent',
341
+ },
342
+ ]);
343
+ it('skips nodes whose fields are already populated', async () => {
344
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor(), {
345
+ entityContent: { title: 'Already have a title' },
346
+ }));
347
+ expect(result.status).toBe('completed');
348
+ // title was skipped, summary was extracted
349
+ expect(result.fieldsExtracted).toContain('summary');
350
+ expect(result.claimedNodeCount).toBe(1);
351
+ });
352
+ it('skips all nodes when all fields populated', async () => {
353
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor(), {
354
+ entityContent: { title: 'Title', summary: 'Summary' },
355
+ }));
356
+ expect(result.status).toBe('completed');
357
+ expect(result.fieldsExtracted).toEqual([]);
358
+ expect(result.claimedNodeCount).toBe(0);
359
+ });
360
+ });
361
+ describe('force re-extraction', () => {
362
+ const definition = makeDefinition([
363
+ {
364
+ key: 'agent_task:title',
365
+ type: 'agent_task',
366
+ label: 'Extract title',
367
+ fieldName: 'title',
368
+ outputType: 'field',
369
+ dependsOn: [],
370
+ assigneeType: 'agent',
371
+ },
372
+ ]);
373
+ it('re-extracts populated fields when force=true', async () => {
374
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor(), {
375
+ entityContent: { title: 'Old title' },
376
+ force: true,
377
+ }));
378
+ expect(result.status).toBe('completed');
379
+ expect(result.fieldsExtracted).toContain('title');
380
+ expect(result.claimedNodeCount).toBe(1);
381
+ });
382
+ });
383
+ describe('field filtering', () => {
384
+ const definition = makeDefinition([
385
+ {
386
+ key: 'agent_task:title',
387
+ type: 'agent_task',
388
+ label: 'Extract title',
389
+ fieldName: 'title',
390
+ outputType: 'field',
391
+ dependsOn: [],
392
+ assigneeType: 'agent',
393
+ },
394
+ {
395
+ key: 'agent_task:summary',
396
+ type: 'agent_task',
397
+ label: 'Extract summary',
398
+ fieldName: 'summary',
399
+ outputType: 'field',
400
+ dependsOn: ['agent_task:title'],
401
+ assigneeType: 'agent',
402
+ },
403
+ {
404
+ key: 'agent_task:tags',
405
+ type: 'agent_task',
406
+ label: 'Extract tags',
407
+ fieldName: 'tags',
408
+ outputType: 'field',
409
+ dependsOn: [],
410
+ assigneeType: 'agent',
411
+ },
412
+ ]);
413
+ it('only processes requested fields and their dependencies', async () => {
414
+ const executionOrder = [];
415
+ const executor = async (ctx) => {
416
+ executionOrder.push(ctx.node.node_key);
417
+ return {
418
+ success: true,
419
+ fieldsExtracted: ctx.node.field_name ? [ctx.node.field_name] : [],
420
+ updatedContent: ctx.node.field_name
421
+ ? { [ctx.node.field_name]: `val-${ctx.node.field_name}` }
422
+ : {},
423
+ };
424
+ };
425
+ const result = await runWorkflow(makeBaseOptions(store, definition, executor, {
426
+ fields: ['summary'],
427
+ }));
428
+ expect(result.status).toBe('completed');
429
+ // Should include title (dependency) and summary, but not tags
430
+ expect(executionOrder).toContain('agent_task:title');
431
+ expect(executionOrder).toContain('agent_task:summary');
432
+ expect(executionOrder).not.toContain('agent_task:tags');
433
+ // Only 2 node runs should have been created
434
+ expect(store.nodeRuns).toHaveLength(2);
435
+ });
436
+ });
437
+ describe('error handling', () => {
438
+ const definition = makeDefinition([
439
+ {
440
+ key: 'agent_task:title',
441
+ type: 'agent_task',
442
+ label: 'Extract title',
443
+ fieldName: 'title',
444
+ outputType: 'field',
445
+ dependsOn: [],
446
+ assigneeType: 'agent',
447
+ },
448
+ {
449
+ key: 'agent_task:summary',
450
+ type: 'agent_task',
451
+ label: 'Extract summary',
452
+ fieldName: 'summary',
453
+ outputType: 'field',
454
+ dependsOn: [],
455
+ assigneeType: 'agent',
456
+ },
457
+ ]);
458
+ it('handles executor returning failure', async () => {
459
+ const result = await runWorkflow(makeBaseOptions(store, definition, failingExecutor('API timeout')));
460
+ expect(result.status).toBe('failed');
461
+ expect(result.errors['agent_task:title']).toBe('API timeout');
462
+ expect(result.errors['agent_task:summary']).toBe('API timeout');
463
+ expect(result.claimedNodeCount).toBe(2);
464
+ });
465
+ it('handles executor throwing an exception', async () => {
466
+ const result = await runWorkflow(makeBaseOptions(store, definition, throwingExecutor('Network error')));
467
+ expect(result.status).toBe('failed');
468
+ expect(result.errors['agent_task:title']).toBe('Network error');
469
+ });
470
+ it('partial completion when some nodes fail', async () => {
471
+ const mixedExecutor = async (ctx) => {
472
+ if (ctx.node.node_key === 'agent_task:title') {
473
+ return {
474
+ success: true,
475
+ fieldsExtracted: ['title'],
476
+ updatedContent: { title: 'Extracted' },
477
+ };
478
+ }
479
+ return {
480
+ success: false,
481
+ fieldsExtracted: [],
482
+ error: 'Failed to extract',
483
+ };
484
+ };
485
+ const result = await runWorkflow(makeBaseOptions(store, definition, mixedExecutor));
486
+ expect(result.status).toBe('partial');
487
+ expect(result.fieldsExtracted).toContain('title');
488
+ expect(result.errors['agent_task:summary']).toBe('Failed to extract');
489
+ });
490
+ });
491
+ describe('node claim racing', () => {
492
+ it('handles claim returning null (already claimed)', async () => {
493
+ const definition = makeDefinition([
494
+ {
495
+ key: 'agent_task:title',
496
+ type: 'agent_task',
497
+ label: 'Extract title',
498
+ fieldName: 'title',
499
+ outputType: 'field',
500
+ dependsOn: [],
501
+ assigneeType: 'agent',
502
+ },
503
+ ]);
504
+ // Override claimNodeRun to always return null (simulating race condition)
505
+ store.claimNodeRun = async () => null;
506
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
507
+ // Should complete but with 0 claimed nodes since claim always failed
508
+ expect(result.claimedNodeCount).toBe(0);
509
+ });
510
+ });
511
+ describe('event bus integration', () => {
512
+ it('emits workflow.started and workflow.completed events', async () => {
513
+ const definition = makeDefinition([
514
+ {
515
+ key: 'agent_task:title',
516
+ type: 'agent_task',
517
+ label: 'Extract title',
518
+ fieldName: 'title',
519
+ outputType: 'field',
520
+ dependsOn: [],
521
+ assigneeType: 'agent',
522
+ },
523
+ ]);
524
+ const events = [];
525
+ const mockEventBus = {
526
+ emit: async (type, data) => {
527
+ events.push({ type, data });
528
+ },
529
+ on: () => () => { },
530
+ onAny: () => () => { },
531
+ removeAllListeners: () => { },
532
+ };
533
+ await runWorkflow(makeBaseOptions(store, definition, successExecutor(), {
534
+ eventBus: mockEventBus,
535
+ }));
536
+ const eventTypes = events.map((e) => e.type);
537
+ expect(eventTypes).toContain('workflow.started');
538
+ expect(eventTypes).toContain('workflow.node_completed');
539
+ expect(eventTypes).toContain('workflow.completed');
540
+ });
541
+ it('works without event bus', async () => {
542
+ const definition = makeDefinition([
543
+ {
544
+ key: 'agent_task:title',
545
+ type: 'agent_task',
546
+ label: 'Extract title',
547
+ fieldName: 'title',
548
+ outputType: 'field',
549
+ dependsOn: [],
550
+ assigneeType: 'agent',
551
+ },
552
+ ]);
553
+ const result = await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
554
+ expect(result.status).toBe('completed');
555
+ });
556
+ });
557
+ describe('trigger metadata', () => {
558
+ it('stores triggeredBy in workflow run metadata', async () => {
559
+ const definition = makeDefinition([]);
560
+ await runWorkflow(makeBaseOptions(store, definition, successExecutor(), {
561
+ triggeredBy: 'user-123',
562
+ triggerType: 'manual',
563
+ }));
564
+ expect(store.runs[0].metadata).toEqual({ triggeredBy: 'user-123' });
565
+ expect(store.runs[0].trigger_type).toBe('manual');
566
+ });
567
+ });
568
+ describe('duration tracking', () => {
569
+ it('records workflow run duration', async () => {
570
+ const definition = makeDefinition([
571
+ {
572
+ key: 'agent_task:title',
573
+ type: 'agent_task',
574
+ label: 'Extract title',
575
+ fieldName: 'title',
576
+ outputType: 'field',
577
+ dependsOn: [],
578
+ assigneeType: 'agent',
579
+ },
580
+ ]);
581
+ await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
582
+ expect(store.runs[0].duration_ms).toBeGreaterThanOrEqual(0);
583
+ expect(store.runs[0].completed_at).not.toBeNull();
584
+ });
585
+ it('records node run duration on success', async () => {
586
+ const definition = makeDefinition([
587
+ {
588
+ key: 'agent_task:title',
589
+ type: 'agent_task',
590
+ label: 'Extract title',
591
+ fieldName: 'title',
592
+ outputType: 'field',
593
+ dependsOn: [],
594
+ assigneeType: 'agent',
595
+ },
596
+ ]);
597
+ await runWorkflow(makeBaseOptions(store, definition, successExecutor()));
598
+ const nodeRun = store.nodeRuns[0];
599
+ expect(nodeRun.duration_ms).toBeGreaterThanOrEqual(0);
600
+ expect(nodeRun.completed_at).not.toBeNull();
601
+ expect(nodeRun.started_at).not.toBeNull();
602
+ });
603
+ it('records node run duration on failure', async () => {
604
+ const definition = makeDefinition([
605
+ {
606
+ key: 'agent_task:title',
607
+ type: 'agent_task',
608
+ label: 'Extract title',
609
+ fieldName: 'title',
610
+ outputType: 'field',
611
+ dependsOn: [],
612
+ assigneeType: 'agent',
613
+ },
614
+ ]);
615
+ await runWorkflow(makeBaseOptions(store, definition, failingExecutor('error')));
616
+ const nodeRun = store.nodeRuns[0];
617
+ expect(nodeRun.duration_ms).toBeGreaterThanOrEqual(0);
618
+ expect(nodeRun.status).toBe('failed');
619
+ });
620
+ });
621
+ describe('safety: infinite loop protection', () => {
622
+ it('stops after max iterations', async () => {
623
+ const definition = makeDefinition([
624
+ {
625
+ key: 'agent_task:title',
626
+ type: 'agent_task',
627
+ label: 'Extract title',
628
+ fieldName: 'title',
629
+ outputType: 'field',
630
+ dependsOn: [],
631
+ assigneeType: 'agent',
632
+ },
633
+ ]);
634
+ // An executor that always succeeds but doesn't update content,
635
+ // AND we make the store always re-create the node as pending
636
+ // by resetting status after each update
637
+ let executionCount = 0;
638
+ const nonTerminatingExecutor = async () => {
639
+ executionCount++;
640
+ return {
641
+ success: true,
642
+ fieldsExtracted: [],
643
+ // Deliberately don't return updatedContent so field stays empty
644
+ };
645
+ };
646
+ const result = await runWorkflow(makeBaseOptions(store, definition, nonTerminatingExecutor));
647
+ // Should terminate due to max iterations, not infinite loop
648
+ // After first execution, node becomes completed but field stays empty.
649
+ // On next evaluation, the node gets set back to pending (no field value).
650
+ // However, since we claimed it already, claimNodeRun returns null (already claimed).
651
+ // So the loop breaks because no nodes could be executed.
652
+ expect(executionCount).toBeGreaterThanOrEqual(1);
653
+ expect(result.runId).toBeDefined();
654
+ });
655
+ });
656
+ });
657
+ //# sourceMappingURL=workflow-runner.test.js.map