@xyne/workflow-sdk 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 (310) hide show
  1. package/README.md +36 -0
  2. package/dist/agents/agent-step.d.ts +152 -0
  3. package/dist/agents/agent-step.d.ts.map +1 -0
  4. package/dist/agents/agent-step.js +403 -0
  5. package/dist/agents/agent-step.js.map +1 -0
  6. package/dist/agents/base-runtime.d.ts +38 -0
  7. package/dist/agents/base-runtime.d.ts.map +1 -0
  8. package/dist/agents/base-runtime.js +21 -0
  9. package/dist/agents/base-runtime.js.map +1 -0
  10. package/dist/agents/index.d.ts +12 -0
  11. package/dist/agents/index.d.ts.map +1 -0
  12. package/dist/agents/index.js +17 -0
  13. package/dist/agents/index.js.map +1 -0
  14. package/dist/agents/interceptor.d.ts +19 -0
  15. package/dist/agents/interceptor.d.ts.map +1 -0
  16. package/dist/agents/interceptor.js +21 -0
  17. package/dist/agents/interceptor.js.map +1 -0
  18. package/dist/agents/interceptors/approval-gate.d.ts +14 -0
  19. package/dist/agents/interceptors/approval-gate.d.ts.map +1 -0
  20. package/dist/agents/interceptors/approval-gate.js +19 -0
  21. package/dist/agents/interceptors/approval-gate.js.map +1 -0
  22. package/dist/agents/interceptors/tool-logger.d.ts +15 -0
  23. package/dist/agents/interceptors/tool-logger.d.ts.map +1 -0
  24. package/dist/agents/interceptors/tool-logger.js +23 -0
  25. package/dist/agents/interceptors/tool-logger.js.map +1 -0
  26. package/dist/agents/pi-mono-runtime.d.ts +88 -0
  27. package/dist/agents/pi-mono-runtime.d.ts.map +1 -0
  28. package/dist/agents/pi-mono-runtime.js +129 -0
  29. package/dist/agents/pi-mono-runtime.js.map +1 -0
  30. package/dist/agents/tool-registry.d.ts +25 -0
  31. package/dist/agents/tool-registry.d.ts.map +1 -0
  32. package/dist/agents/tool-registry.js +47 -0
  33. package/dist/agents/tool-registry.js.map +1 -0
  34. package/dist/agents/tool-types.d.ts +25 -0
  35. package/dist/agents/tool-types.d.ts.map +1 -0
  36. package/dist/agents/tool-types.js +9 -0
  37. package/dist/agents/tool-types.js.map +1 -0
  38. package/dist/agents/types.d.ts +91 -0
  39. package/dist/agents/types.d.ts.map +1 -0
  40. package/dist/agents/types.js +12 -0
  41. package/dist/agents/types.js.map +1 -0
  42. package/dist/builder/index.d.ts +30 -0
  43. package/dist/builder/index.d.ts.map +1 -0
  44. package/dist/builder/index.js +32 -0
  45. package/dist/builder/index.js.map +1 -0
  46. package/dist/client/index.d.ts +19 -0
  47. package/dist/client/index.d.ts.map +1 -0
  48. package/dist/client/index.js +18 -0
  49. package/dist/client/index.js.map +1 -0
  50. package/dist/client/types.d.ts +128 -0
  51. package/dist/client/types.d.ts.map +1 -0
  52. package/dist/client/types.js +10 -0
  53. package/dist/client/types.js.map +1 -0
  54. package/dist/client/workflow-client.d.ts +15 -0
  55. package/dist/client/workflow-client.d.ts.map +1 -0
  56. package/dist/client/workflow-client.js +293 -0
  57. package/dist/client/workflow-client.js.map +1 -0
  58. package/dist/common/attachment.d.ts +31 -0
  59. package/dist/common/attachment.d.ts.map +1 -0
  60. package/dist/common/attachment.js +21 -0
  61. package/dist/common/attachment.js.map +1 -0
  62. package/dist/common/executable-check.d.ts +42 -0
  63. package/dist/common/executable-check.d.ts.map +1 -0
  64. package/dist/common/executable-check.js +115 -0
  65. package/dist/common/executable-check.js.map +1 -0
  66. package/dist/common/index.d.ts +21 -0
  67. package/dist/common/index.d.ts.map +1 -0
  68. package/dist/common/index.js +19 -0
  69. package/dist/common/index.js.map +1 -0
  70. package/dist/common/resume-payload.d.ts +34 -0
  71. package/dist/common/resume-payload.d.ts.map +1 -0
  72. package/dist/common/resume-payload.js +12 -0
  73. package/dist/common/resume-payload.js.map +1 -0
  74. package/dist/engine/available-context.d.ts +29 -0
  75. package/dist/engine/available-context.d.ts.map +1 -0
  76. package/dist/engine/available-context.js +66 -0
  77. package/dist/engine/available-context.js.map +1 -0
  78. package/dist/engine/condition-evaluator.d.ts +13 -0
  79. package/dist/engine/condition-evaluator.d.ts.map +1 -0
  80. package/dist/engine/condition-evaluator.js +92 -0
  81. package/dist/engine/condition-evaluator.js.map +1 -0
  82. package/dist/engine/config-validator.d.ts +25 -0
  83. package/dist/engine/config-validator.d.ts.map +1 -0
  84. package/dist/engine/config-validator.js +316 -0
  85. package/dist/engine/config-validator.js.map +1 -0
  86. package/dist/engine/pause-step.d.ts +20 -0
  87. package/dist/engine/pause-step.d.ts.map +1 -0
  88. package/dist/engine/pause-step.js +24 -0
  89. package/dist/engine/pause-step.js.map +1 -0
  90. package/dist/engine/service-registry.d.ts +49 -0
  91. package/dist/engine/service-registry.d.ts.map +1 -0
  92. package/dist/engine/service-registry.js +76 -0
  93. package/dist/engine/service-registry.js.map +1 -0
  94. package/dist/engine/variable-resolver.d.ts +29 -0
  95. package/dist/engine/variable-resolver.d.ts.map +1 -0
  96. package/dist/engine/variable-resolver.js +130 -0
  97. package/dist/engine/variable-resolver.js.map +1 -0
  98. package/dist/engine/workflow-executor.d.ts +96 -0
  99. package/dist/engine/workflow-executor.d.ts.map +1 -0
  100. package/dist/engine/workflow-executor.js +837 -0
  101. package/dist/engine/workflow-executor.js.map +1 -0
  102. package/dist/index.d.ts +57 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +56 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/persistence/in-memory-adapter.d.ts +138 -0
  107. package/dist/persistence/in-memory-adapter.d.ts.map +1 -0
  108. package/dist/persistence/in-memory-adapter.js +315 -0
  109. package/dist/persistence/in-memory-adapter.js.map +1 -0
  110. package/dist/persistence/types.d.ts +214 -0
  111. package/dist/persistence/types.d.ts.map +1 -0
  112. package/dist/persistence/types.js +8 -0
  113. package/dist/persistence/types.js.map +1 -0
  114. package/dist/router/types.d.ts +57 -0
  115. package/dist/router/types.d.ts.map +1 -0
  116. package/dist/router/types.js +8 -0
  117. package/dist/router/types.js.map +1 -0
  118. package/dist/router/workflow-router.d.ts +26 -0
  119. package/dist/router/workflow-router.d.ts.map +1 -0
  120. package/dist/router/workflow-router.js +611 -0
  121. package/dist/router/workflow-router.js.map +1 -0
  122. package/dist/runtime/execution-event-bus.d.ts +38 -0
  123. package/dist/runtime/execution-event-bus.d.ts.map +1 -0
  124. package/dist/runtime/execution-event-bus.js +87 -0
  125. package/dist/runtime/execution-event-bus.js.map +1 -0
  126. package/dist/runtime/types.d.ts +129 -0
  127. package/dist/runtime/types.d.ts.map +1 -0
  128. package/dist/runtime/types.js +5 -0
  129. package/dist/runtime/types.js.map +1 -0
  130. package/dist/runtime/workflow-runtime.d.ts +256 -0
  131. package/dist/runtime/workflow-runtime.d.ts.map +1 -0
  132. package/dist/runtime/workflow-runtime.js +699 -0
  133. package/dist/runtime/workflow-runtime.js.map +1 -0
  134. package/dist/steps/base-step.d.ts +204 -0
  135. package/dist/steps/base-step.d.ts.map +1 -0
  136. package/dist/steps/base-step.js +69 -0
  137. package/dist/steps/base-step.js.map +1 -0
  138. package/dist/steps/builtin/code.step.d.ts +95 -0
  139. package/dist/steps/builtin/code.step.d.ts.map +1 -0
  140. package/dist/steps/builtin/code.step.js +122 -0
  141. package/dist/steps/builtin/code.step.js.map +1 -0
  142. package/dist/steps/builtin/conditional.step.d.ts +146 -0
  143. package/dist/steps/builtin/conditional.step.d.ts.map +1 -0
  144. package/dist/steps/builtin/conditional.step.js +71 -0
  145. package/dist/steps/builtin/conditional.step.js.map +1 -0
  146. package/dist/steps/builtin/dedup.step.d.ts +65 -0
  147. package/dist/steps/builtin/dedup.step.d.ts.map +1 -0
  148. package/dist/steps/builtin/dedup.step.js +61 -0
  149. package/dist/steps/builtin/dedup.step.js.map +1 -0
  150. package/dist/steps/builtin/http-request.step.d.ts +928 -0
  151. package/dist/steps/builtin/http-request.step.d.ts.map +1 -0
  152. package/dist/steps/builtin/http-request.step.js +570 -0
  153. package/dist/steps/builtin/http-request.step.js.map +1 -0
  154. package/dist/steps/builtin/loop.step.d.ts +100 -0
  155. package/dist/steps/builtin/loop.step.d.ts.map +1 -0
  156. package/dist/steps/builtin/loop.step.js +79 -0
  157. package/dist/steps/builtin/loop.step.js.map +1 -0
  158. package/dist/steps/builtin/parallel.step.d.ts +208 -0
  159. package/dist/steps/builtin/parallel.step.d.ts.map +1 -0
  160. package/dist/steps/builtin/parallel.step.js +249 -0
  161. package/dist/steps/builtin/parallel.step.js.map +1 -0
  162. package/dist/steps/builtin/switch.step.d.ts +200 -0
  163. package/dist/steps/builtin/switch.step.d.ts.map +1 -0
  164. package/dist/steps/builtin/switch.step.js +92 -0
  165. package/dist/steps/builtin/switch.step.js.map +1 -0
  166. package/dist/steps/builtin/transform.step.d.ts +247 -0
  167. package/dist/steps/builtin/transform.step.d.ts.map +1 -0
  168. package/dist/steps/builtin/transform.step.js +135 -0
  169. package/dist/steps/builtin/transform.step.js.map +1 -0
  170. package/dist/steps/builtin/wait.step.d.ts +921 -0
  171. package/dist/steps/builtin/wait.step.d.ts.map +1 -0
  172. package/dist/steps/builtin/wait.step.js +211 -0
  173. package/dist/steps/builtin/wait.step.js.map +1 -0
  174. package/dist/steps/step-registry.d.ts +64 -0
  175. package/dist/steps/step-registry.d.ts.map +1 -0
  176. package/dist/steps/step-registry.js +102 -0
  177. package/dist/steps/step-registry.js.map +1 -0
  178. package/dist/storage/in-memory-adapter.d.ts +25 -0
  179. package/dist/storage/in-memory-adapter.d.ts.map +1 -0
  180. package/dist/storage/in-memory-adapter.js +41 -0
  181. package/dist/storage/in-memory-adapter.js.map +1 -0
  182. package/dist/storage/types.d.ts +53 -0
  183. package/dist/storage/types.d.ts.map +1 -0
  184. package/dist/storage/types.js +13 -0
  185. package/dist/storage/types.js.map +1 -0
  186. package/dist/testing/index.d.ts +10 -0
  187. package/dist/testing/index.d.ts.map +1 -0
  188. package/dist/testing/index.js +10 -0
  189. package/dist/testing/index.js.map +1 -0
  190. package/dist/testing/mock-context.d.ts +13 -0
  191. package/dist/testing/mock-context.d.ts.map +1 -0
  192. package/dist/testing/mock-context.js +21 -0
  193. package/dist/testing/mock-context.js.map +1 -0
  194. package/dist/testing/mock-step-context.d.ts +47 -0
  195. package/dist/testing/mock-step-context.d.ts.map +1 -0
  196. package/dist/testing/mock-step-context.js +59 -0
  197. package/dist/testing/mock-step-context.js.map +1 -0
  198. package/dist/triggers/base-trigger.d.ts +58 -0
  199. package/dist/triggers/base-trigger.d.ts.map +1 -0
  200. package/dist/triggers/base-trigger.js +37 -0
  201. package/dist/triggers/base-trigger.js.map +1 -0
  202. package/dist/triggers/builtin/default-cron-trigger.d.ts +44 -0
  203. package/dist/triggers/builtin/default-cron-trigger.d.ts.map +1 -0
  204. package/dist/triggers/builtin/default-cron-trigger.js +31 -0
  205. package/dist/triggers/builtin/default-cron-trigger.js.map +1 -0
  206. package/dist/triggers/builtin/default-event-trigger.d.ts +32 -0
  207. package/dist/triggers/builtin/default-event-trigger.d.ts.map +1 -0
  208. package/dist/triggers/builtin/default-event-trigger.js +21 -0
  209. package/dist/triggers/builtin/default-event-trigger.js.map +1 -0
  210. package/dist/triggers/builtin/default-manual-trigger.d.ts +119 -0
  211. package/dist/triggers/builtin/default-manual-trigger.d.ts.map +1 -0
  212. package/dist/triggers/builtin/default-manual-trigger.js +64 -0
  213. package/dist/triggers/builtin/default-manual-trigger.js.map +1 -0
  214. package/dist/triggers/builtin/default-webhook-trigger.d.ts +72 -0
  215. package/dist/triggers/builtin/default-webhook-trigger.d.ts.map +1 -0
  216. package/dist/triggers/builtin/default-webhook-trigger.js +91 -0
  217. package/dist/triggers/builtin/default-webhook-trigger.js.map +1 -0
  218. package/dist/triggers/cron-trigger.d.ts +52 -0
  219. package/dist/triggers/cron-trigger.d.ts.map +1 -0
  220. package/dist/triggers/cron-trigger.js +15 -0
  221. package/dist/triggers/cron-trigger.js.map +1 -0
  222. package/dist/triggers/event-trigger.d.ts +14 -0
  223. package/dist/triggers/event-trigger.d.ts.map +1 -0
  224. package/dist/triggers/event-trigger.js +13 -0
  225. package/dist/triggers/event-trigger.js.map +1 -0
  226. package/dist/triggers/manual-trigger.d.ts +11 -0
  227. package/dist/triggers/manual-trigger.d.ts.map +1 -0
  228. package/dist/triggers/manual-trigger.js +10 -0
  229. package/dist/triggers/manual-trigger.js.map +1 -0
  230. package/dist/triggers/trigger-registry.d.ts +48 -0
  231. package/dist/triggers/trigger-registry.d.ts.map +1 -0
  232. package/dist/triggers/trigger-registry.js +81 -0
  233. package/dist/triggers/trigger-registry.js.map +1 -0
  234. package/dist/triggers/webhook-trigger.d.ts +54 -0
  235. package/dist/triggers/webhook-trigger.d.ts.map +1 -0
  236. package/dist/triggers/webhook-trigger.js +13 -0
  237. package/dist/triggers/webhook-trigger.js.map +1 -0
  238. package/dist/types/attachment.d.ts +23 -0
  239. package/dist/types/attachment.d.ts.map +1 -0
  240. package/dist/types/attachment.js +2 -0
  241. package/dist/types/attachment.js.map +1 -0
  242. package/dist/types/categories.d.ts +14 -0
  243. package/dist/types/categories.d.ts.map +1 -0
  244. package/dist/types/categories.js +10 -0
  245. package/dist/types/categories.js.map +1 -0
  246. package/dist/types/context.d.ts +61 -0
  247. package/dist/types/context.d.ts.map +1 -0
  248. package/dist/types/context.js +2 -0
  249. package/dist/types/context.js.map +1 -0
  250. package/dist/types/index.d.ts +18 -0
  251. package/dist/types/index.d.ts.map +1 -0
  252. package/dist/types/index.js +12 -0
  253. package/dist/types/index.js.map +1 -0
  254. package/dist/types/known-types.d.ts +34 -0
  255. package/dist/types/known-types.d.ts.map +1 -0
  256. package/dist/types/known-types.js +49 -0
  257. package/dist/types/known-types.js.map +1 -0
  258. package/dist/types/operators.d.ts +20 -0
  259. package/dist/types/operators.d.ts.map +1 -0
  260. package/dist/types/operators.js +27 -0
  261. package/dist/types/operators.js.map +1 -0
  262. package/dist/types/pause-path.d.ts +23 -0
  263. package/dist/types/pause-path.d.ts.map +1 -0
  264. package/dist/types/pause-path.js +2 -0
  265. package/dist/types/pause-path.js.map +1 -0
  266. package/dist/types/pause-state.d.ts +100 -0
  267. package/dist/types/pause-state.d.ts.map +1 -0
  268. package/dist/types/pause-state.js +15 -0
  269. package/dist/types/pause-state.js.map +1 -0
  270. package/dist/types/resume-payload.d.ts +34 -0
  271. package/dist/types/resume-payload.d.ts.map +1 -0
  272. package/dist/types/resume-payload.js +12 -0
  273. package/dist/types/resume-payload.js.map +1 -0
  274. package/dist/types/status.d.ts +27 -0
  275. package/dist/types/status.d.ts.map +1 -0
  276. package/dist/types/status.js +41 -0
  277. package/dist/types/status.js.map +1 -0
  278. package/dist/types/step-events.d.ts +22 -0
  279. package/dist/types/step-events.d.ts.map +1 -0
  280. package/dist/types/step-events.js +8 -0
  281. package/dist/types/step-events.js.map +1 -0
  282. package/dist/types/step-types.d.ts +12 -0
  283. package/dist/types/step-types.d.ts.map +1 -0
  284. package/dist/types/step-types.js +2 -0
  285. package/dist/types/step-types.js.map +1 -0
  286. package/dist/types/trigger-types.d.ts +12 -0
  287. package/dist/types/trigger-types.d.ts.map +1 -0
  288. package/dist/types/trigger-types.js +2 -0
  289. package/dist/types/trigger-types.js.map +1 -0
  290. package/dist/types/validation.d.ts +19 -0
  291. package/dist/types/validation.d.ts.map +1 -0
  292. package/dist/types/validation.js +11 -0
  293. package/dist/types/validation.js.map +1 -0
  294. package/dist/types/workflow-config.d.ts +122 -0
  295. package/dist/types/workflow-config.d.ts.map +1 -0
  296. package/dist/types/workflow-config.js +71 -0
  297. package/dist/types/workflow-config.js.map +1 -0
  298. package/dist/util/executable-check.d.ts +42 -0
  299. package/dist/util/executable-check.d.ts.map +1 -0
  300. package/dist/util/executable-check.js +115 -0
  301. package/dist/util/executable-check.js.map +1 -0
  302. package/dist/util/schema-convert.d.ts +14 -0
  303. package/dist/util/schema-convert.d.ts.map +1 -0
  304. package/dist/util/schema-convert.js +16 -0
  305. package/dist/util/schema-convert.js.map +1 -0
  306. package/dist/util/variable-ref.d.ts +52 -0
  307. package/dist/util/variable-ref.d.ts.map +1 -0
  308. package/dist/util/variable-ref.js +89 -0
  309. package/dist/util/variable-ref.js.map +1 -0
  310. package/package.json +97 -0
@@ -0,0 +1,837 @@
1
+ import { CONTROL_FLOW_STEP_TYPES } from '../types/known-types.js';
2
+ import { BaseActionStep, BaseControlFlowStep, StepKind, createPauseFunction, } from '../steps/base-step.js';
3
+ import { VariableResolver, stripNullForOptionalKeys } from './variable-resolver.js';
4
+ import { PauseStep } from './pause-step.js';
5
+ const noopLogger = {
6
+ info: () => { },
7
+ warn: () => { },
8
+ error: () => { },
9
+ };
10
+ // ─── WorkflowExecutor ───
11
+ /**
12
+ * Core runtime engine for executing workflows.
13
+ *
14
+ * Orchestrates: step execution, variable resolution, pause/resume
15
+ * (including inside branches), error handling (onError: continue),
16
+ * retry, timeout, credential resolution, and state persistence.
17
+ *
18
+ * Host provides: PersistenceAdapter, StepRegistry, TriggerRegistry,
19
+ * ServiceRegistry, and an optional logger.
20
+ */
21
+ export class WorkflowExecutor {
22
+ persistence;
23
+ stepRegistry;
24
+ triggerRegistry;
25
+ services;
26
+ resolver;
27
+ log;
28
+ eventBus;
29
+ baseUrl;
30
+ constructor(persistence, stepRegistry, triggerRegistry, services, options) {
31
+ this.persistence = persistence;
32
+ this.stepRegistry = stepRegistry;
33
+ this.triggerRegistry = triggerRegistry;
34
+ this.services = services;
35
+ this.resolver = options?.variableResolver ?? new VariableResolver();
36
+ this.log = options?.logger ?? noopLogger;
37
+ this.eventBus = options?.eventBus;
38
+ this.baseUrl = options?.baseUrl ?? '';
39
+ }
40
+ /** Emit an execution stream event (no-op if no eventBus). */
41
+ emitEvent(executionId, event) {
42
+ this.eventBus?.emit(executionId, event);
43
+ }
44
+ // ─── Public API ───
45
+ /**
46
+ * Run or resume an execution by ID.
47
+ *
48
+ * Loads the execution from persistence, determines if it's a fresh start
49
+ * or a resume, then walks through the steps.
50
+ */
51
+ async runExecution(executionId) {
52
+ const exec = await this.persistence.getExecution(executionId);
53
+ if (!exec) {
54
+ this.log.warn(`runExecution: execution ${executionId} not found`);
55
+ return;
56
+ }
57
+ const isResume = exec.status === 'EXTERNAL_WAIT';
58
+ const isFresh = exec.status === 'PENDING' || exec.status === 'SCHEDULED';
59
+ if (!isFresh && !isResume) {
60
+ this.log.warn(`runExecution: execution ${executionId} status=${exec.status} — skipping`);
61
+ return;
62
+ }
63
+ const workflow = await this.persistence.getWorkflow(exec.workflowId);
64
+ if (!workflow) {
65
+ await this.persistence.updateExecutionStatus(executionId, 'CANCELLED');
66
+ this.log.warn(`runExecution: workflow ${exec.workflowId} not found — CANCELLED`);
67
+ return;
68
+ }
69
+ const config = JSON.parse(workflow.config ?? '{}');
70
+ const state = await this.persistence.getExecutionState(executionId);
71
+ if (!state?.context) {
72
+ if (isResume) {
73
+ throw new Error(`runExecution: pause state missing for resume of ${executionId}`);
74
+ }
75
+ await this.persistence.updateExecutionStatus(executionId, 'FAILED');
76
+ this.log.error(`runExecution: state.context missing for ${executionId} — FAILED`);
77
+ return;
78
+ }
79
+ let context;
80
+ try {
81
+ context = JSON.parse(state.context);
82
+ }
83
+ catch {
84
+ if (isResume) {
85
+ throw new Error(`runExecution: state.context unparseable for resume of ${executionId}`);
86
+ }
87
+ await this.persistence.updateExecutionStatus(executionId, 'FAILED');
88
+ this.log.error(`runExecution: state.context unparseable for ${executionId} — FAILED`);
89
+ return;
90
+ }
91
+ // Hydrate trigger data if fresh run
92
+ if (!isResume && this.triggerRegistry.has(config.trigger.type)) {
93
+ const triggerImpl = this.triggerRegistry.get(config.trigger.type);
94
+ if (typeof triggerImpl.hydratePayload === 'function') {
95
+ try {
96
+ const triggerData = context.trigger;
97
+ const hydrated = await triggerImpl.hydratePayload(triggerData);
98
+ context.trigger = {
99
+ ...context.trigger,
100
+ ...hydrated,
101
+ };
102
+ }
103
+ catch (err) {
104
+ this.log.warn(`runExecution: hydratePayload failed for ${executionId}: ${err instanceof Error ? err.message : String(err)}`);
105
+ }
106
+ }
107
+ // Filter check
108
+ const filterConfig = (config.trigger.config ?? {});
109
+ if (!triggerImpl.matchFilters(filterConfig, context.trigger)) {
110
+ await this.persistence.updateExecutionStatus(executionId, 'SKIPPED');
111
+ await this.persistence.persistState(executionId, {
112
+ context: JSON.stringify(context),
113
+ });
114
+ this.log.info(`runExecution: filter mismatch for ${executionId} — SKIPPED`);
115
+ return;
116
+ }
117
+ }
118
+ // Resume: hydrate step context from persisted step rows
119
+ let startIndex = 0;
120
+ let resumeAtIndex;
121
+ let branchResumePath;
122
+ if (isResume) {
123
+ const stepRows = await this.persistence.getStepRows(executionId);
124
+ for (const row of stepRows) {
125
+ const idx = parseStepIndex(row.stepName);
126
+ if (idx === null)
127
+ continue;
128
+ const stepConfig = config.steps[idx];
129
+ if (!stepConfig || !row.data)
130
+ continue;
131
+ const parsed = safeParseJson(row.data);
132
+ if (parsed) {
133
+ context.steps[stepConfig.id] = {
134
+ type: parsed.type ?? stepConfig.type,
135
+ ...(parsed.input !== undefined ? { input: parsed.input } : {}),
136
+ output: parsed.output ?? {},
137
+ };
138
+ }
139
+ }
140
+ if (state.pauseType === 'review') {
141
+ // ─── Review-gate resume ───
142
+ const reviewedIdx = state.currentStepIndex;
143
+ const reviewedStep = config.steps[reviewedIdx];
144
+ const resumePayload = await this.persistence.getResumePayload(executionId);
145
+ const action = resumePayload?.action ?? 'approve';
146
+ if (action === 'abort') {
147
+ await this.persistence.updateExecutionStatus(executionId, 'CANCELLED');
148
+ this.emitEvent(executionId, { event: 'execution_cancelled', data: {} });
149
+ this.log.info(`run ${executionId} review-gate ABORTED`);
150
+ return;
151
+ }
152
+ if (action === 'retry' && reviewedStep) {
153
+ // Re-execute the reviewed step fresh, with priorState + feedback overrides.
154
+ startIndex = reviewedIdx;
155
+ const priorRow = await this.persistence.getStep(executionId, `step_${String(reviewedIdx)}`);
156
+ const priorState = priorRow?.data
157
+ ? safeParseJson(priorRow.data) ?? undefined
158
+ : undefined;
159
+ context.__meta = {
160
+ ...context.__meta,
161
+ ...(priorState ? { __priorStepState: priorState } : {}),
162
+ __configOverrides: extractConfigOverrides(resumePayload),
163
+ };
164
+ // Clear the entry so it re-executes (and downstream resolvers don't see stale output)
165
+ delete context.steps[reviewedStep.id];
166
+ }
167
+ else {
168
+ // approve: advance past the reviewed step
169
+ startIndex = reviewedIdx + 1;
170
+ }
171
+ }
172
+ else {
173
+ // ─── Step-level pause resume (existing PausePath logic) ───
174
+ const savedPausePath = state.pausePath
175
+ ? safeParseJson(state.pausePath)
176
+ : null;
177
+ if (savedPausePath && savedPausePath.length > 0) {
178
+ const first = savedPausePath[0];
179
+ startIndex = first.stepIndex;
180
+ resumeAtIndex = first.stepIndex;
181
+ branchResumePath =
182
+ savedPausePath.length > 1 ? savedPausePath.slice(1) : undefined;
183
+ }
184
+ else {
185
+ startIndex = state.currentStepIndex;
186
+ resumeAtIndex = state.currentStepIndex;
187
+ }
188
+ }
189
+ }
190
+ else {
191
+ // ─── Fresh-run path: check for rerunFromStep seed ───
192
+ const rerun = context.__meta?.rerunSource;
193
+ if (rerun) {
194
+ startIndex = rerun.fromStepIndex;
195
+ }
196
+ }
197
+ // Run — preserve transient seed fields (priorState, configOverrides, rerunSource)
198
+ // that the resume/rerun paths populated above.
199
+ const prevMeta = context.__meta ?? {};
200
+ context.__meta = {
201
+ ...prevMeta,
202
+ error: null,
203
+ chain: prevMeta.chain ?? [],
204
+ };
205
+ await this.persistence.updateExecutionStatus(executionId, 'RUNNING');
206
+ await this.persistence.persistState(executionId, {
207
+ context: JSON.stringify(context),
208
+ });
209
+ this.log.info(`run ${isResume ? 'RESUMED' : 'STARTED'} execId=${executionId} workflow=${workflow.id} startIndex=${startIndex}`);
210
+ await this.runSteps(executionId, context, config.steps, startIndex, resumeAtIndex, branchResumePath, config.settings);
211
+ }
212
+ /**
213
+ * Start a fresh execution for a workflow.
214
+ *
215
+ * Creates the execution record + initial state, then runs it.
216
+ * Returns the execution ID.
217
+ */
218
+ async startExecution(workflowId, context) {
219
+ const executionId = await this.persistence.createExecution({
220
+ workflowId,
221
+ status: 'PENDING',
222
+ context: JSON.stringify(context),
223
+ });
224
+ await this.runExecution(executionId);
225
+ return executionId;
226
+ }
227
+ /**
228
+ * Create a new execution that reruns from `fromStepId`, using the source
229
+ * execution's cached outputs for all prior steps as the resolved context.
230
+ *
231
+ * The new execution starts at `fromStepIndex` (not 0). Steps before that
232
+ * are pre-populated in `context.steps` so variable resolution finds them.
233
+ * The step at `fromStepIndex` receives the source row's data as
234
+ * `ctx.priorState` (when `inheritStepState` is true, the default), enabling
235
+ * agents and other stateful steps to continue from where they left off.
236
+ *
237
+ * Returns the new execution ID. Caller is responsible for enqueueing it.
238
+ */
239
+ async rerunFromStep(sourceExecutionId, fromStepId, options) {
240
+ const inheritStepState = options?.inheritStepState ?? true;
241
+ const source = await this.persistence.getExecution(sourceExecutionId);
242
+ if (!source) {
243
+ throw new Error(`Source execution ${sourceExecutionId} not found`);
244
+ }
245
+ const terminal = ['COMPLETED', 'FAILED', 'CANCELLED'];
246
+ if (!terminal.includes(source.status)) {
247
+ throw new Error(`Cannot rerun from non-terminal execution ${sourceExecutionId} (status: ${source.status})`);
248
+ }
249
+ const workflow = await this.persistence.getWorkflow(source.workflowId);
250
+ if (!workflow) {
251
+ throw new Error(`Workflow ${source.workflowId} not found`);
252
+ }
253
+ const config = JSON.parse(workflow.config ?? '{}');
254
+ const fromStepIndex = config.steps.findIndex((s) => s.id === fromStepId);
255
+ if (fromStepIndex < 0) {
256
+ throw new Error(`Step "${fromStepId}" not found in workflow ${source.workflowId}`);
257
+ }
258
+ const sourceState = await this.persistence.getExecutionState(sourceExecutionId);
259
+ if (!sourceState?.context) {
260
+ throw new Error(`Source execution ${sourceExecutionId} has no persisted context`);
261
+ }
262
+ const sourceContext = JSON.parse(sourceState.context);
263
+ // Seed the new context with the source's trigger + steps 0..N-1 outputs.
264
+ const seededSteps = {};
265
+ const stepRows = await this.persistence.getStepRows(sourceExecutionId);
266
+ const rowByName = new Map();
267
+ for (const row of stepRows) {
268
+ rowByName.set(row.stepName, row);
269
+ }
270
+ for (let i = 0; i < fromStepIndex; i++) {
271
+ const stepCfg = config.steps[i];
272
+ const row = rowByName.get(`step_${String(i)}`);
273
+ if (!row?.data)
274
+ continue;
275
+ const parsed = safeParseJson(row.data);
276
+ if (parsed) {
277
+ seededSteps[stepCfg.id] = {
278
+ type: parsed.type ?? stepCfg.type,
279
+ ...(parsed.input !== undefined ? { input: parsed.input } : {}),
280
+ output: parsed.output ?? {},
281
+ };
282
+ }
283
+ }
284
+ // Priorstate for the entry step (the one being re-executed)
285
+ let priorStepState;
286
+ if (inheritStepState) {
287
+ const entryRow = rowByName.get(`step_${String(fromStepIndex)}`);
288
+ if (entryRow?.data) {
289
+ priorStepState =
290
+ safeParseJson(entryRow.data) ?? undefined;
291
+ }
292
+ }
293
+ const newContext = {
294
+ workflow: sourceContext.workflow,
295
+ trigger: sourceContext.trigger,
296
+ steps: seededSteps,
297
+ __meta: {
298
+ error: null,
299
+ chain: sourceContext.__meta?.chain ?? [],
300
+ rerunSource: {
301
+ executionId: sourceExecutionId,
302
+ fromStepIndex,
303
+ fromStepId,
304
+ },
305
+ ...(priorStepState ? { __priorStepState: priorStepState } : {}),
306
+ ...(options?.configOverrides
307
+ ? { __configOverrides: options.configOverrides }
308
+ : {}),
309
+ },
310
+ };
311
+ const newExecutionId = await this.persistence.createExecution({
312
+ workflowId: source.workflowId,
313
+ status: 'PENDING',
314
+ context: JSON.stringify(newContext),
315
+ sourceExecutionId,
316
+ });
317
+ this.log.info(`rerun created exec=${newExecutionId} from=${sourceExecutionId} step=${fromStepId}`);
318
+ return newExecutionId;
319
+ }
320
+ // ─── Internal: run loop ───
321
+ async runSteps(executionId, context, steps, startIndex, resumeAtIndex, branchResumePath, settings) {
322
+ try {
323
+ const walkResult = await this.walkSteps(steps, context, executionId, startIndex, resumeAtIndex, branchResumePath, settings);
324
+ if (walkResult.kind === 'paused') {
325
+ this.log.info(`run ${executionId} PAUSED at path=${JSON.stringify(walkResult.pausePath)}`);
326
+ return;
327
+ }
328
+ await this.persistence.updateExecutionStatus(executionId, 'COMPLETED');
329
+ context.__meta = { ...context.__meta, error: null };
330
+ await this.persistence.persistState(executionId, {
331
+ context: JSON.stringify(context),
332
+ currentStepIndex: steps.length,
333
+ });
334
+ this.emitEvent(executionId, { event: 'execution_completed', data: { status: 'COMPLETED' } });
335
+ this.log.info(`run ${executionId} COMPLETED`);
336
+ }
337
+ catch (err) {
338
+ const errMsg = err instanceof Error ? err.message : String(err);
339
+ await this.persistence.updateExecutionStatus(executionId, 'FAILED');
340
+ context.__meta = { ...context.__meta, error: errMsg };
341
+ await this.persistence.persistState(executionId, {
342
+ context: JSON.stringify(context),
343
+ });
344
+ this.emitEvent(executionId, { event: 'execution_failed', data: { error: errMsg } });
345
+ this.log.error(`run ${executionId} FAILED: ${errMsg}`);
346
+ throw err;
347
+ }
348
+ }
349
+ async walkSteps(steps, context, executionId, startIndex = 0, resumeAtIndex, branchResumePath, settings) {
350
+ const onError = settings?.onError ?? 'stop';
351
+ const retry = settings?.retry;
352
+ for (let i = startIndex; i < steps.length; i++) {
353
+ const step = steps[i];
354
+ const stepName = `step_${String(i)}`;
355
+ const isResuming = resumeAtIndex === i;
356
+ // Only pass branchResumePath for the step being resumed
357
+ const stepBranchResumePath = isResuming ? branchResumePath : undefined;
358
+ if (!isResuming) {
359
+ const executorType = CONTROL_FLOW_STEP_TYPES.has(step.type)
360
+ ? 'conditional'
361
+ : 'deterministic';
362
+ await this.persistence.upsertStep(executionId, stepName, {
363
+ status: 'RUNNING',
364
+ executorType,
365
+ });
366
+ this.emitEvent(executionId, {
367
+ event: 'step_started',
368
+ data: { stepName: step.id, stepType: step.type },
369
+ });
370
+ }
371
+ // Consume entry-step extras (priorState + configOverrides) on the first
372
+ // iteration only. These come from review-gate retry or rerunFromStep.
373
+ const isEntryStep = i === startIndex && !isResuming;
374
+ const entryPriorState = isEntryStep
375
+ ? context.__meta?.__priorStepState
376
+ : undefined;
377
+ const entryConfigOverrides = isEntryStep
378
+ ? context.__meta?.__configOverrides
379
+ : undefined;
380
+ if (isEntryStep && context.__meta) {
381
+ // Single-use — clear so subsequent steps don't see them.
382
+ delete context.__meta.__priorStepState;
383
+ delete context.__meta.__configOverrides;
384
+ }
385
+ try {
386
+ await this.executeStepWithRetry(step, context, { executionId, stepName, isResuming, priorState: entryPriorState, configOverrides: entryConfigOverrides }, stepBranchResumePath, retry);
387
+ const ctxEntry = context.steps[step.id];
388
+ await this.persistence.upsertStep(executionId, stepName, {
389
+ status: 'COMPLETED',
390
+ executorType: CONTROL_FLOW_STEP_TYPES.has(step.type)
391
+ ? 'conditional'
392
+ : 'deterministic',
393
+ data: JSON.stringify(ctxEntry ?? { output: null }),
394
+ });
395
+ this.emitEvent(executionId, {
396
+ event: 'step_completed',
397
+ data: {
398
+ stepName: step.id,
399
+ output: (ctxEntry?.output ?? {}),
400
+ },
401
+ });
402
+ // ─── Review gate: pause after step completes if onComplete === 'review' ───
403
+ if (step.onComplete === 'review') {
404
+ const reviewPauseState = {
405
+ reason: `Review output of "${step.id}"`,
406
+ category: 'review',
407
+ display: ctxEntry?.output
408
+ ? [{ label: 'Output', value: ctxEntry.output }]
409
+ : undefined,
410
+ actions: [
411
+ { key: 'approve', label: 'Approve', style: 'primary' },
412
+ { key: 'retry', label: 'Retry with Feedback', style: 'default', submitsForm: true },
413
+ { key: 'abort', label: 'Reject', style: 'danger', abort: true },
414
+ ],
415
+ form: {
416
+ fields: [
417
+ {
418
+ type: 'textarea',
419
+ name: 'feedback',
420
+ label: 'Feedback',
421
+ placeholder: 'Optional instructions for retry...',
422
+ },
423
+ ],
424
+ },
425
+ };
426
+ await this.persistence.persistState(executionId, {
427
+ context: JSON.stringify(context),
428
+ currentStepIndex: i,
429
+ pauseType: 'review',
430
+ });
431
+ await this.persistence.updateExecutionStatus(executionId, 'EXTERNAL_WAIT');
432
+ this.emitEvent(executionId, {
433
+ event: 'execution_paused',
434
+ data: { stepName: step.id, pauseState: reviewPauseState },
435
+ });
436
+ this.log.info(`run ${executionId} REVIEW-GATE paused at step=${step.id}`);
437
+ return { kind: 'paused', pausePath: [], externalRef: undefined };
438
+ }
439
+ }
440
+ catch (err) {
441
+ if (PauseStep.is(err)) {
442
+ const pe = err;
443
+ const fullPath = [
444
+ { stepIndex: i },
445
+ ...(pe._pausePath ?? []),
446
+ ];
447
+ const ctxEntry = context.steps[step.id];
448
+ const base = ctxEntry ?? { type: step.type, output: {} };
449
+ const merged = pe.statePatch
450
+ ? { ...base, ...pe.statePatch }
451
+ : base;
452
+ await this.persistence.upsertStep(executionId, stepName, {
453
+ status: 'EXTERNAL_WAIT',
454
+ executorType: CONTROL_FLOW_STEP_TYPES.has(step.type)
455
+ ? 'conditional'
456
+ : 'deterministic',
457
+ data: JSON.stringify(merged),
458
+ });
459
+ await this.persistence.persistState(executionId, {
460
+ context: JSON.stringify(context),
461
+ currentStepIndex: i,
462
+ pausePath: JSON.stringify(fullPath),
463
+ });
464
+ await this.persistence.updateExecutionStatus(executionId, 'EXTERNAL_WAIT');
465
+ this.emitEvent(executionId, {
466
+ event: 'execution_paused',
467
+ data: {
468
+ stepName: step.id,
469
+ pauseState: merged['pauseState'] ?? { reason: pe.message },
470
+ },
471
+ });
472
+ return {
473
+ kind: 'paused',
474
+ pausePath: fullPath,
475
+ externalRef: pe.externalRef,
476
+ };
477
+ }
478
+ const errMsg = err instanceof Error ? err.message : String(err);
479
+ const ctxEntry = context.steps[step.id];
480
+ if (onError === 'continue') {
481
+ context.steps[step.id] = {
482
+ type: step.type,
483
+ ...(ctxEntry?.input !== undefined
484
+ ? { input: ctxEntry.input }
485
+ : {}),
486
+ output: ctxEntry?.output ?? {},
487
+ error: errMsg,
488
+ };
489
+ await this.persistence.upsertStep(executionId, stepName, {
490
+ status: 'FAILED',
491
+ executorType: CONTROL_FLOW_STEP_TYPES.has(step.type)
492
+ ? 'conditional'
493
+ : 'deterministic',
494
+ data: JSON.stringify({ ...(ctxEntry ?? {}), error: errMsg }),
495
+ });
496
+ this.emitEvent(executionId, {
497
+ event: 'step_failed',
498
+ data: { stepName: step.id, error: errMsg },
499
+ });
500
+ this.log.warn(`step ${step.id} failed but continuing: ${errMsg}`);
501
+ continue;
502
+ }
503
+ await this.persistence.upsertStep(executionId, stepName, {
504
+ status: 'FAILED',
505
+ executorType: CONTROL_FLOW_STEP_TYPES.has(step.type)
506
+ ? 'conditional'
507
+ : 'deterministic',
508
+ data: JSON.stringify({ ...(ctxEntry ?? {}), error: errMsg }),
509
+ });
510
+ this.emitEvent(executionId, {
511
+ event: 'step_failed',
512
+ data: { stepName: step.id, error: errMsg },
513
+ });
514
+ throw err;
515
+ }
516
+ }
517
+ return { kind: 'completed' };
518
+ }
519
+ async executeStepWithRetry(step, context, callCtx, branchResumePath, retry) {
520
+ if (!retry || callCtx.isResuming) {
521
+ await this.executeStep(step, context, callCtx, branchResumePath);
522
+ return;
523
+ }
524
+ let lastError;
525
+ for (let attempt = 1; attempt <= retry.maxAttempts; attempt++) {
526
+ try {
527
+ await this.executeStep(step, context, callCtx, branchResumePath);
528
+ return;
529
+ }
530
+ catch (err) {
531
+ if (PauseStep.is(err))
532
+ throw err;
533
+ lastError = err;
534
+ this.log.warn(`step ${step.id} attempt ${String(attempt)}/${String(retry.maxAttempts)} failed: ${err instanceof Error ? err.message : String(err)}`);
535
+ if (attempt < retry.maxAttempts) {
536
+ await delay(retry.intervalMs);
537
+ }
538
+ }
539
+ }
540
+ throw lastError;
541
+ }
542
+ async executeStep(step, context, callCtx, branchResumePath) {
543
+ const stepImpl = this.stepRegistry.get(step.type);
544
+ if (stepImpl.kind === StepKind.CONTROL) {
545
+ const controlImpl = stepImpl;
546
+ const parsed = controlImpl.configSchema.safeParse(step.config);
547
+ if (!parsed.success) {
548
+ throw new Error(`Step "${step.id}" (${step.type}) config validation failed:\n${formatZodErrors(parsed.error)}`);
549
+ }
550
+ this.log.info(`step ${callCtx.isResuming ? 'RESUME' : 'START'} id=${step.id} type=${step.type}`);
551
+ const t0 = Date.now();
552
+ const stepCtx = await this.buildStepContext(context, { ...callCtx, stepId: step.id }, stepImpl);
553
+ const output = await controlImpl.execute(parsed.data, {
554
+ ...stepCtx,
555
+ walkBranch: (branchSteps, ctx, branchKey) => this.walkBranchSteps(branchSteps, ctx, callCtx.executionId, step.id, branchKey, branchResumePath),
556
+ });
557
+ context.steps[step.id] = { type: step.type, output };
558
+ this.log.info(`step OK id=${step.id} type=${step.type} elapsed=${String(Date.now() - t0)}ms`);
559
+ return;
560
+ }
561
+ // Action step: merge configOverrides → resolve variables → validate → execute
562
+ const baseConfig = callCtx.configOverrides
563
+ ? { ...step.config, ...callCtx.configOverrides }
564
+ : step.config;
565
+ const resolvedConfig = stripNullForOptionalKeys(this.resolver.resolve(baseConfig, context), stepImpl.configSchema);
566
+ const parsed = stepImpl.configSchema.safeParse(resolvedConfig);
567
+ if (!parsed.success) {
568
+ throw new Error(`Step "${step.id}" (${step.type}) config validation failed after variable resolution:\n${formatZodErrors(parsed.error)}`);
569
+ }
570
+ const resolvedInput = parsed.data;
571
+ const persistedInput = stepImpl.redactInput
572
+ ? stepImpl.redactInput(resolvedInput)
573
+ : resolvedInput;
574
+ context.steps[step.id] = {
575
+ type: step.type,
576
+ input: persistedInput,
577
+ output: {},
578
+ };
579
+ const actionImpl = stepImpl;
580
+ const stepCtx = await this.buildStepContext(context, { ...callCtx, stepId: step.id }, stepImpl);
581
+ this.log.info(`step ${callCtx.isResuming ? 'RESUME' : 'START'} id=${step.id} type=${step.type}`);
582
+ const t0 = Date.now();
583
+ try {
584
+ let output;
585
+ if (callCtx.isResuming) {
586
+ output = await this.invokeResume(actionImpl, callCtx.executionId, callCtx.stepName, resolvedInput, stepCtx);
587
+ }
588
+ else {
589
+ output = await actionImpl.execute(resolvedInput, stepCtx);
590
+ }
591
+ context.steps[step.id] = {
592
+ type: step.type,
593
+ input: persistedInput,
594
+ output,
595
+ };
596
+ this.log.info(`step OK id=${step.id} type=${step.type} elapsed=${String(Date.now() - t0)}ms`);
597
+ }
598
+ catch (err) {
599
+ if (!PauseStep.is(err)) {
600
+ this.log.error(`step FAIL id=${step.id} type=${step.type} elapsed=${String(Date.now() - t0)}ms err=${err instanceof Error ? err.message : String(err)}`);
601
+ }
602
+ throw err;
603
+ }
604
+ }
605
+ /**
606
+ * Walk steps inside a control-flow branch. Supports nested pause.
607
+ *
608
+ * Each sub-step is persisted as its own row (keyed by config `id`) and
609
+ * emits SSE events, mirroring the top-level `walkSteps` behavior.
610
+ *
611
+ * When a step pauses inside a branch, the PauseStep error is enriched
612
+ * with branch path information and re-thrown so the top-level walkSteps
613
+ * can persist the full PausePath.
614
+ */
615
+ async walkBranchSteps(steps, context, executionId, controlStepId, branchKey, branchResumePath) {
616
+ const resolvedBranchKey = branchKey ?? 'default';
617
+ // Determine start position for resume within this branch
618
+ let startIndex = 0;
619
+ let innerResuming = false;
620
+ let deeperResumePath;
621
+ if (branchResumePath && branchResumePath.length > 0) {
622
+ const segment = branchResumePath[0];
623
+ if (segment.branch?.stepId === controlStepId &&
624
+ segment.branch.branchKey === resolvedBranchKey) {
625
+ startIndex = segment.stepIndex;
626
+ innerResuming = true;
627
+ deeperResumePath =
628
+ branchResumePath.length > 1
629
+ ? branchResumePath.slice(1)
630
+ : undefined;
631
+ }
632
+ }
633
+ for (let j = startIndex; j < steps.length; j++) {
634
+ const bStep = steps[j];
635
+ const isResuming = innerResuming && j === startIndex;
636
+ const executorType = CONTROL_FLOW_STEP_TYPES.has(bStep.type)
637
+ ? 'conditional'
638
+ : 'deterministic';
639
+ // Persist RUNNING + emit event (skip for resuming steps)
640
+ if (!isResuming && executionId) {
641
+ await this.persistence.upsertStep(executionId, bStep.id, {
642
+ status: 'RUNNING',
643
+ executorType,
644
+ });
645
+ this.emitEvent(executionId, {
646
+ event: 'step_started',
647
+ data: { stepName: bStep.id, stepType: bStep.type },
648
+ });
649
+ }
650
+ try {
651
+ await this.executeStep(bStep, context, {
652
+ executionId,
653
+ stepName: bStep.id,
654
+ isResuming,
655
+ }, isResuming ? deeperResumePath : undefined);
656
+ // Persist COMPLETED + emit event
657
+ if (executionId) {
658
+ const ctxEntry = context.steps[bStep.id];
659
+ await this.persistence.upsertStep(executionId, bStep.id, {
660
+ status: 'COMPLETED',
661
+ executorType,
662
+ data: JSON.stringify(ctxEntry ?? { output: null }),
663
+ });
664
+ this.emitEvent(executionId, {
665
+ event: 'step_completed',
666
+ data: {
667
+ stepName: bStep.id,
668
+ output: (ctxEntry?.output ?? {}),
669
+ },
670
+ });
671
+ }
672
+ }
673
+ catch (err) {
674
+ if (PauseStep.is(err)) {
675
+ const pe = err;
676
+ // Persist EXTERNAL_WAIT for the sub-step
677
+ if (executionId) {
678
+ const ctxEntry = context.steps[bStep.id];
679
+ const base = ctxEntry ?? { type: bStep.type, output: {} };
680
+ const merged = pe.statePatch
681
+ ? { ...base, ...pe.statePatch }
682
+ : base;
683
+ await this.persistence.upsertStep(executionId, bStep.id, {
684
+ status: 'EXTERNAL_WAIT',
685
+ executorType,
686
+ data: JSON.stringify(merged),
687
+ });
688
+ }
689
+ pe._pausePath = [
690
+ {
691
+ stepIndex: j,
692
+ branch: {
693
+ stepId: controlStepId,
694
+ branchKey: resolvedBranchKey,
695
+ },
696
+ },
697
+ ...(pe._pausePath ?? []),
698
+ ];
699
+ throw pe;
700
+ }
701
+ // Persist FAILED + emit event
702
+ if (executionId) {
703
+ const errMsg = err instanceof Error ? err.message : String(err);
704
+ await this.persistence.upsertStep(executionId, bStep.id, {
705
+ status: 'FAILED',
706
+ executorType,
707
+ data: JSON.stringify({ error: errMsg }),
708
+ });
709
+ this.emitEvent(executionId, {
710
+ event: 'step_failed',
711
+ data: { stepName: bStep.id, error: errMsg },
712
+ });
713
+ }
714
+ throw err;
715
+ }
716
+ }
717
+ }
718
+ async invokeResume(actionImpl, executionId, stepName, resolvedInput, ctx) {
719
+ const row = await this.persistence.getStep(executionId, stepName);
720
+ let rowData = row?.data
721
+ ? (safeParseJson(row.data) ?? {})
722
+ : {};
723
+ // Merge user's resume payload (approve/reject/form data) into rowData
724
+ const resumePayload = await this.persistence.getResumePayload(executionId);
725
+ if (resumePayload) {
726
+ rowData = { ...rowData, resumePayload };
727
+ }
728
+ if (typeof actionImpl.onResume === 'function') {
729
+ return actionImpl.onResume(rowData, resolvedInput, ctx);
730
+ }
731
+ const fallback = rowData['output'];
732
+ return fallback &&
733
+ typeof fallback === 'object' &&
734
+ !Array.isArray(fallback)
735
+ ? fallback
736
+ : {};
737
+ }
738
+ async buildStepContext(context, callCtx, stepImpl) {
739
+ // Resolve credentials if step declares credentialSlots
740
+ let credentials = {};
741
+ if (stepImpl.credentialSlots && stepImpl.credentialSlots.length > 0) {
742
+ for (const slot of stepImpl.credentialSlots) {
743
+ const resolved = await this.persistence.resolveCredential(context.workflow.metadata, slot.name);
744
+ if (resolved) {
745
+ credentials[slot.name] = resolved;
746
+ }
747
+ else if (slot.required) {
748
+ throw new Error(`Required credential "${slot.name}" not found`);
749
+ }
750
+ }
751
+ }
752
+ return {
753
+ workflow: context,
754
+ runtime: {
755
+ executionId: callCtx.executionId,
756
+ stepId: callCtx.stepId,
757
+ stepName: callCtx.stepName,
758
+ isResuming: callCtx.isResuming,
759
+ metadata: context.workflow.metadata,
760
+ baseUrl: this.baseUrl,
761
+ },
762
+ ...(callCtx.priorState ? { priorState: callCtx.priorState } : {}),
763
+ services: this.services,
764
+ credentials,
765
+ staticData: {
766
+ get: (key) => this.persistence.getStaticData(context.workflow.id, key),
767
+ set: (key, value) => this.persistence.setStaticData(context.workflow.id, key, value),
768
+ delete: (key) => this.persistence.deleteStaticData(context.workflow.id, key),
769
+ },
770
+ pause: createPauseFunction(),
771
+ emit: (event) => {
772
+ const normalized = {
773
+ type: event.type,
774
+ data: JSON.stringify(event.data),
775
+ };
776
+ // Fire-and-forget — never blocks step execution
777
+ this.persistence
778
+ .appendStepEvent(callCtx.executionId, callCtx.stepName, normalized)
779
+ .catch((err) => {
780
+ this.log.warn(`Failed to persist step event: ${err instanceof Error ? err.message : String(err)}`);
781
+ });
782
+ // Also forward to the SSE event bus
783
+ this.emitEvent(callCtx.executionId, {
784
+ event: 'step_progress',
785
+ data: { stepName: callCtx.stepId, ...event.data },
786
+ });
787
+ },
788
+ log: this.log,
789
+ };
790
+ }
791
+ }
792
+ // ─── Helpers ───
793
+ function safeParseJson(raw) {
794
+ try {
795
+ return JSON.parse(raw);
796
+ }
797
+ catch {
798
+ return null;
799
+ }
800
+ }
801
+ function parseStepIndex(name) {
802
+ const m = /^step_(\d+)$/.exec(name);
803
+ if (!m?.[1])
804
+ return null;
805
+ const n = Number.parseInt(m[1], 10);
806
+ return Number.isInteger(n) ? n : null;
807
+ }
808
+ function formatZodErrors(error) {
809
+ return error.issues
810
+ .map((i) => ` ${i.path.join('.')}: ${i.message}`)
811
+ .join('\n');
812
+ }
813
+ function delay(ms) {
814
+ return new Promise((resolve) => {
815
+ setTimeout(resolve, ms);
816
+ });
817
+ }
818
+ /**
819
+ * Convert a review-gate retry's resume payload into config overrides
820
+ * the entry step will see. Surfaces `feedback` + `attachments` as top-level
821
+ * keys (so steps can read them without unwrapping), then merges the opaque
822
+ * `data` blob underneath.
823
+ */
824
+ function extractConfigOverrides(payload) {
825
+ if (!payload)
826
+ return {};
827
+ const overrides = {};
828
+ if (payload.data) {
829
+ Object.assign(overrides, payload.data);
830
+ }
831
+ if (payload.feedback !== undefined)
832
+ overrides['feedback'] = payload.feedback;
833
+ if (payload.attachments !== undefined)
834
+ overrides['attachments'] = payload.attachments;
835
+ return overrides;
836
+ }
837
+ //# sourceMappingURL=workflow-executor.js.map