groundswell 0.0.3 → 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 (292) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -9
  3. package/dist/cache/cache-key.d.ts +20 -0
  4. package/dist/cache/cache-key.d.ts.map +1 -1
  5. package/dist/cache/cache-key.js +9 -0
  6. package/dist/cache/cache-key.js.map +1 -1
  7. package/dist/core/agent.d.ts +120 -29
  8. package/dist/core/agent.d.ts.map +1 -1
  9. package/dist/core/agent.js +584 -177
  10. package/dist/core/agent.js.map +1 -1
  11. package/dist/core/mcp-handler.d.ts +63 -5
  12. package/dist/core/mcp-handler.d.ts.map +1 -1
  13. package/dist/core/mcp-handler.js +184 -4
  14. package/dist/core/mcp-handler.js.map +1 -1
  15. package/dist/core/workflow-context.d.ts +6 -2
  16. package/dist/core/workflow-context.d.ts.map +1 -1
  17. package/dist/core/workflow-context.js +99 -4
  18. package/dist/core/workflow-context.js.map +1 -1
  19. package/dist/core/workflow.d.ts +315 -13
  20. package/dist/core/workflow.d.ts.map +1 -1
  21. package/dist/core/workflow.js +552 -30
  22. package/dist/core/workflow.js.map +1 -1
  23. package/dist/debugger/event-replayer.d.ts +422 -0
  24. package/dist/debugger/event-replayer.d.ts.map +1 -0
  25. package/dist/debugger/event-replayer.js +639 -0
  26. package/dist/debugger/event-replayer.js.map +1 -0
  27. package/dist/debugger/tree-debugger.d.ts +170 -1
  28. package/dist/debugger/tree-debugger.d.ts.map +1 -1
  29. package/dist/debugger/tree-debugger.js +423 -1
  30. package/dist/debugger/tree-debugger.js.map +1 -1
  31. package/dist/decorators/step.d.ts.map +1 -1
  32. package/dist/decorators/step.js +129 -47
  33. package/dist/decorators/step.js.map +1 -1
  34. package/dist/harnesses/claude-code-harness.d.ts +391 -0
  35. package/dist/harnesses/claude-code-harness.d.ts.map +1 -0
  36. package/dist/harnesses/claude-code-harness.js +1076 -0
  37. package/dist/harnesses/claude-code-harness.js.map +1 -0
  38. package/dist/harnesses/harness-registry.d.ts +440 -0
  39. package/dist/harnesses/harness-registry.d.ts.map +1 -0
  40. package/dist/harnesses/harness-registry.js +543 -0
  41. package/dist/harnesses/harness-registry.js.map +1 -0
  42. package/dist/harnesses/index.d.ts +12 -0
  43. package/dist/harnesses/index.d.ts.map +1 -0
  44. package/dist/harnesses/index.js +11 -0
  45. package/dist/harnesses/index.js.map +1 -0
  46. package/dist/harnesses/pi-harness.d.ts +219 -0
  47. package/dist/harnesses/pi-harness.d.ts.map +1 -0
  48. package/dist/harnesses/pi-harness.js +676 -0
  49. package/dist/harnesses/pi-harness.js.map +1 -0
  50. package/dist/harnesses/pi-schema-converter.d.ts +24 -0
  51. package/dist/harnesses/pi-schema-converter.d.ts.map +1 -0
  52. package/dist/harnesses/pi-schema-converter.js +81 -0
  53. package/dist/harnesses/pi-schema-converter.js.map +1 -0
  54. package/dist/harnesses/register-defaults.d.ts +24 -0
  55. package/dist/harnesses/register-defaults.d.ts.map +1 -0
  56. package/dist/harnesses/register-defaults.js +40 -0
  57. package/dist/harnesses/register-defaults.js.map +1 -0
  58. package/dist/harnesses/session-store.d.ts +201 -0
  59. package/dist/harnesses/session-store.d.ts.map +1 -0
  60. package/dist/harnesses/session-store.js +254 -0
  61. package/dist/harnesses/session-store.js.map +1 -0
  62. package/dist/index.d.ts +12 -2
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +17 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/reflection/reflection.d.ts.map +1 -1
  67. package/dist/reflection/reflection.js +19 -4
  68. package/dist/reflection/reflection.js.map +1 -1
  69. package/dist/types/agent.d.ts +1253 -2
  70. package/dist/types/agent.d.ts.map +1 -1
  71. package/dist/types/agent.js +418 -1
  72. package/dist/types/agent.js.map +1 -1
  73. package/dist/types/decorators.d.ts +10 -1
  74. package/dist/types/decorators.d.ts.map +1 -1
  75. package/dist/types/events.d.ts +26 -0
  76. package/dist/types/events.d.ts.map +1 -1
  77. package/dist/types/harnesses.d.ts +474 -0
  78. package/dist/types/harnesses.d.ts.map +1 -0
  79. package/dist/types/harnesses.js +2 -0
  80. package/dist/types/harnesses.js.map +1 -0
  81. package/dist/types/index.d.ts +9 -1
  82. package/dist/types/index.d.ts.map +1 -1
  83. package/dist/types/index.js +6 -0
  84. package/dist/types/index.js.map +1 -1
  85. package/dist/types/providers.d.ts +691 -0
  86. package/dist/types/providers.d.ts.map +1 -0
  87. package/dist/types/providers.js +14 -0
  88. package/dist/types/providers.js.map +1 -0
  89. package/dist/types/restart.d.ts +132 -0
  90. package/dist/types/restart.d.ts.map +1 -0
  91. package/dist/types/restart.js +2 -0
  92. package/dist/types/restart.js.map +1 -0
  93. package/dist/types/streaming.d.ts +194 -0
  94. package/dist/types/streaming.d.ts.map +1 -0
  95. package/dist/types/streaming.js +67 -0
  96. package/dist/types/streaming.js.map +1 -0
  97. package/dist/types/workflow-context.d.ts +137 -1
  98. package/dist/types/workflow-context.d.ts.map +1 -1
  99. package/dist/utils/agent-validation.d.ts +88 -0
  100. package/dist/utils/agent-validation.d.ts.map +1 -0
  101. package/dist/utils/agent-validation.js +87 -0
  102. package/dist/utils/agent-validation.js.map +1 -0
  103. package/dist/utils/delay.d.ts +7 -0
  104. package/dist/utils/delay.d.ts.map +1 -0
  105. package/dist/utils/delay.js +9 -0
  106. package/dist/utils/delay.js.map +1 -0
  107. package/dist/utils/harness-config.d.ts +180 -0
  108. package/dist/utils/harness-config.d.ts.map +1 -0
  109. package/dist/utils/harness-config.js +311 -0
  110. package/dist/utils/harness-config.js.map +1 -0
  111. package/dist/utils/index.d.ts +9 -1
  112. package/dist/utils/index.d.ts.map +1 -1
  113. package/dist/utils/index.js +8 -1
  114. package/dist/utils/index.js.map +1 -1
  115. package/dist/utils/model-spec.d.ts +110 -0
  116. package/dist/utils/model-spec.d.ts.map +1 -0
  117. package/dist/utils/model-spec.js +149 -0
  118. package/dist/utils/model-spec.js.map +1 -0
  119. package/dist/utils/provider-config.d.ts +10 -0
  120. package/dist/utils/provider-config.d.ts.map +1 -0
  121. package/dist/utils/provider-config.js +10 -0
  122. package/dist/utils/provider-config.js.map +1 -0
  123. package/dist/utils/restart-analysis.d.ts +202 -0
  124. package/dist/utils/restart-analysis.d.ts.map +1 -0
  125. package/dist/utils/restart-analysis.js +426 -0
  126. package/dist/utils/restart-analysis.js.map +1 -0
  127. package/dist/utils/session-serialization.d.ts +118 -0
  128. package/dist/utils/session-serialization.d.ts.map +1 -0
  129. package/dist/utils/session-serialization.js +217 -0
  130. package/dist/utils/session-serialization.js.map +1 -0
  131. package/package.json +31 -5
  132. package/CHANGELOG.md +0 -188
  133. package/dist/__tests__/adversarial/attachChild-performance.test.d.ts +0 -16
  134. package/dist/__tests__/adversarial/attachChild-performance.test.d.ts.map +0 -1
  135. package/dist/__tests__/adversarial/attachChild-performance.test.js +0 -187
  136. package/dist/__tests__/adversarial/attachChild-performance.test.js.map +0 -1
  137. package/dist/__tests__/adversarial/circular-reference.test.d.ts +0 -13
  138. package/dist/__tests__/adversarial/circular-reference.test.d.ts.map +0 -1
  139. package/dist/__tests__/adversarial/circular-reference.test.js +0 -92
  140. package/dist/__tests__/adversarial/circular-reference.test.js.map +0 -1
  141. package/dist/__tests__/adversarial/complex-circular-reference.test.d.ts +0 -16
  142. package/dist/__tests__/adversarial/complex-circular-reference.test.d.ts.map +0 -1
  143. package/dist/__tests__/adversarial/complex-circular-reference.test.js +0 -127
  144. package/dist/__tests__/adversarial/complex-circular-reference.test.js.map +0 -1
  145. package/dist/__tests__/adversarial/concurrent-task-failures.test.d.ts +0 -21
  146. package/dist/__tests__/adversarial/concurrent-task-failures.test.d.ts.map +0 -1
  147. package/dist/__tests__/adversarial/concurrent-task-failures.test.js +0 -667
  148. package/dist/__tests__/adversarial/concurrent-task-failures.test.js.map +0 -1
  149. package/dist/__tests__/adversarial/deep-analysis.test.d.ts +0 -6
  150. package/dist/__tests__/adversarial/deep-analysis.test.d.ts.map +0 -1
  151. package/dist/__tests__/adversarial/deep-analysis.test.js +0 -877
  152. package/dist/__tests__/adversarial/deep-analysis.test.js.map +0 -1
  153. package/dist/__tests__/adversarial/deep-hierarchy-stress.test.d.ts +0 -13
  154. package/dist/__tests__/adversarial/deep-hierarchy-stress.test.d.ts.map +0 -1
  155. package/dist/__tests__/adversarial/deep-hierarchy-stress.test.js +0 -186
  156. package/dist/__tests__/adversarial/deep-hierarchy-stress.test.js.map +0 -1
  157. package/dist/__tests__/adversarial/e2e-prd-validation.test.d.ts +0 -6
  158. package/dist/__tests__/adversarial/e2e-prd-validation.test.d.ts.map +0 -1
  159. package/dist/__tests__/adversarial/e2e-prd-validation.test.js +0 -626
  160. package/dist/__tests__/adversarial/e2e-prd-validation.test.js.map +0 -1
  161. package/dist/__tests__/adversarial/edge-case.test.d.ts +0 -6
  162. package/dist/__tests__/adversarial/edge-case.test.d.ts.map +0 -1
  163. package/dist/__tests__/adversarial/edge-case.test.js +0 -857
  164. package/dist/__tests__/adversarial/edge-case.test.js.map +0 -1
  165. package/dist/__tests__/adversarial/error-merge-strategy.test.d.ts +0 -20
  166. package/dist/__tests__/adversarial/error-merge-strategy.test.d.ts.map +0 -1
  167. package/dist/__tests__/adversarial/error-merge-strategy.test.js +0 -907
  168. package/dist/__tests__/adversarial/error-merge-strategy.test.js.map +0 -1
  169. package/dist/__tests__/adversarial/incremental-performance.test.d.ts +0 -2
  170. package/dist/__tests__/adversarial/incremental-performance.test.d.ts.map +0 -1
  171. package/dist/__tests__/adversarial/incremental-performance.test.js +0 -113
  172. package/dist/__tests__/adversarial/incremental-performance.test.js.map +0 -1
  173. package/dist/__tests__/adversarial/node-map-update-benchmarks.test.d.ts +0 -22
  174. package/dist/__tests__/adversarial/node-map-update-benchmarks.test.d.ts.map +0 -1
  175. package/dist/__tests__/adversarial/node-map-update-benchmarks.test.js +0 -383
  176. package/dist/__tests__/adversarial/node-map-update-benchmarks.test.js.map +0 -1
  177. package/dist/__tests__/adversarial/observer-propagation.test.d.ts +0 -21
  178. package/dist/__tests__/adversarial/observer-propagation.test.d.ts.map +0 -1
  179. package/dist/__tests__/adversarial/observer-propagation.test.js +0 -404
  180. package/dist/__tests__/adversarial/observer-propagation.test.js.map +0 -1
  181. package/dist/__tests__/adversarial/parent-validation.test.d.ts +0 -13
  182. package/dist/__tests__/adversarial/parent-validation.test.d.ts.map +0 -1
  183. package/dist/__tests__/adversarial/parent-validation.test.js +0 -128
  184. package/dist/__tests__/adversarial/parent-validation.test.js.map +0 -1
  185. package/dist/__tests__/adversarial/prd-12-2-compliance.test.d.ts +0 -20
  186. package/dist/__tests__/adversarial/prd-12-2-compliance.test.d.ts.map +0 -1
  187. package/dist/__tests__/adversarial/prd-12-2-compliance.test.js +0 -482
  188. package/dist/__tests__/adversarial/prd-12-2-compliance.test.js.map +0 -1
  189. package/dist/__tests__/adversarial/prd-compliance.test.d.ts +0 -6
  190. package/dist/__tests__/adversarial/prd-compliance.test.d.ts.map +0 -1
  191. package/dist/__tests__/adversarial/prd-compliance.test.js +0 -886
  192. package/dist/__tests__/adversarial/prd-compliance.test.js.map +0 -1
  193. package/dist/__tests__/compatibility/backward-compatibility.test.d.ts +0 -22
  194. package/dist/__tests__/compatibility/backward-compatibility.test.d.ts.map +0 -1
  195. package/dist/__tests__/compatibility/backward-compatibility.test.js +0 -1843
  196. package/dist/__tests__/compatibility/backward-compatibility.test.js.map +0 -1
  197. package/dist/__tests__/helpers/index.d.ts +0 -10
  198. package/dist/__tests__/helpers/index.d.ts.map +0 -1
  199. package/dist/__tests__/helpers/index.js +0 -10
  200. package/dist/__tests__/helpers/index.js.map +0 -1
  201. package/dist/__tests__/helpers/tree-verification.d.ts +0 -90
  202. package/dist/__tests__/helpers/tree-verification.d.ts.map +0 -1
  203. package/dist/__tests__/helpers/tree-verification.js +0 -202
  204. package/dist/__tests__/helpers/tree-verification.js.map +0 -1
  205. package/dist/__tests__/integration/agent-workflow.test.d.ts +0 -2
  206. package/dist/__tests__/integration/agent-workflow.test.d.ts.map +0 -1
  207. package/dist/__tests__/integration/agent-workflow.test.js +0 -256
  208. package/dist/__tests__/integration/agent-workflow.test.js.map +0 -1
  209. package/dist/__tests__/integration/bidirectional-consistency.test.d.ts +0 -14
  210. package/dist/__tests__/integration/bidirectional-consistency.test.d.ts.map +0 -1
  211. package/dist/__tests__/integration/bidirectional-consistency.test.js +0 -668
  212. package/dist/__tests__/integration/bidirectional-consistency.test.js.map +0 -1
  213. package/dist/__tests__/integration/observer-logging.test.d.ts +0 -2
  214. package/dist/__tests__/integration/observer-logging.test.d.ts.map +0 -1
  215. package/dist/__tests__/integration/observer-logging.test.js +0 -517
  216. package/dist/__tests__/integration/observer-logging.test.js.map +0 -1
  217. package/dist/__tests__/integration/tree-mirroring.test.d.ts +0 -2
  218. package/dist/__tests__/integration/tree-mirroring.test.d.ts.map +0 -1
  219. package/dist/__tests__/integration/tree-mirroring.test.js +0 -117
  220. package/dist/__tests__/integration/tree-mirroring.test.js.map +0 -1
  221. package/dist/__tests__/integration/workflow-reparenting.test.d.ts +0 -12
  222. package/dist/__tests__/integration/workflow-reparenting.test.d.ts.map +0 -1
  223. package/dist/__tests__/integration/workflow-reparenting.test.js +0 -239
  224. package/dist/__tests__/integration/workflow-reparenting.test.js.map +0 -1
  225. package/dist/__tests__/unit/agent.test.d.ts +0 -2
  226. package/dist/__tests__/unit/agent.test.d.ts.map +0 -1
  227. package/dist/__tests__/unit/agent.test.js +0 -143
  228. package/dist/__tests__/unit/agent.test.js.map +0 -1
  229. package/dist/__tests__/unit/cache-key.test.d.ts +0 -5
  230. package/dist/__tests__/unit/cache-key.test.d.ts.map +0 -1
  231. package/dist/__tests__/unit/cache-key.test.js +0 -145
  232. package/dist/__tests__/unit/cache-key.test.js.map +0 -1
  233. package/dist/__tests__/unit/cache.test.d.ts +0 -5
  234. package/dist/__tests__/unit/cache.test.d.ts.map +0 -1
  235. package/dist/__tests__/unit/cache.test.js +0 -132
  236. package/dist/__tests__/unit/cache.test.js.map +0 -1
  237. package/dist/__tests__/unit/context.test.d.ts +0 -2
  238. package/dist/__tests__/unit/context.test.d.ts.map +0 -1
  239. package/dist/__tests__/unit/context.test.js +0 -220
  240. package/dist/__tests__/unit/context.test.js.map +0 -1
  241. package/dist/__tests__/unit/decorators.test.d.ts +0 -2
  242. package/dist/__tests__/unit/decorators.test.d.ts.map +0 -1
  243. package/dist/__tests__/unit/decorators.test.js +0 -162
  244. package/dist/__tests__/unit/decorators.test.js.map +0 -1
  245. package/dist/__tests__/unit/introspection-tools.test.d.ts +0 -5
  246. package/dist/__tests__/unit/introspection-tools.test.d.ts.map +0 -1
  247. package/dist/__tests__/unit/introspection-tools.test.js +0 -191
  248. package/dist/__tests__/unit/introspection-tools.test.js.map +0 -1
  249. package/dist/__tests__/unit/logger.test.d.ts +0 -2
  250. package/dist/__tests__/unit/logger.test.d.ts.map +0 -1
  251. package/dist/__tests__/unit/logger.test.js +0 -241
  252. package/dist/__tests__/unit/logger.test.js.map +0 -1
  253. package/dist/__tests__/unit/observable.test.d.ts +0 -2
  254. package/dist/__tests__/unit/observable.test.d.ts.map +0 -1
  255. package/dist/__tests__/unit/observable.test.js +0 -251
  256. package/dist/__tests__/unit/observable.test.js.map +0 -1
  257. package/dist/__tests__/unit/prompt.test.d.ts +0 -2
  258. package/dist/__tests__/unit/prompt.test.d.ts.map +0 -1
  259. package/dist/__tests__/unit/prompt.test.js +0 -113
  260. package/dist/__tests__/unit/prompt.test.js.map +0 -1
  261. package/dist/__tests__/unit/reflection.test.d.ts +0 -5
  262. package/dist/__tests__/unit/reflection.test.d.ts.map +0 -1
  263. package/dist/__tests__/unit/reflection.test.js +0 -160
  264. package/dist/__tests__/unit/reflection.test.js.map +0 -1
  265. package/dist/__tests__/unit/tree-debugger-incremental.test.d.ts +0 -2
  266. package/dist/__tests__/unit/tree-debugger-incremental.test.d.ts.map +0 -1
  267. package/dist/__tests__/unit/tree-debugger-incremental.test.js +0 -136
  268. package/dist/__tests__/unit/tree-debugger-incremental.test.js.map +0 -1
  269. package/dist/__tests__/unit/tree-debugger.test.d.ts +0 -2
  270. package/dist/__tests__/unit/tree-debugger.test.d.ts.map +0 -1
  271. package/dist/__tests__/unit/tree-debugger.test.js +0 -69
  272. package/dist/__tests__/unit/tree-debugger.test.js.map +0 -1
  273. package/dist/__tests__/unit/utils/workflow-error-utils.test.d.ts +0 -2
  274. package/dist/__tests__/unit/utils/workflow-error-utils.test.d.ts.map +0 -1
  275. package/dist/__tests__/unit/utils/workflow-error-utils.test.js +0 -154
  276. package/dist/__tests__/unit/utils/workflow-error-utils.test.js.map +0 -1
  277. package/dist/__tests__/unit/workflow-detachChild.test.d.ts +0 -2
  278. package/dist/__tests__/unit/workflow-detachChild.test.d.ts.map +0 -1
  279. package/dist/__tests__/unit/workflow-detachChild.test.js +0 -76
  280. package/dist/__tests__/unit/workflow-detachChild.test.js.map +0 -1
  281. package/dist/__tests__/unit/workflow-emitEvent-childDetached.test.d.ts +0 -2
  282. package/dist/__tests__/unit/workflow-emitEvent-childDetached.test.d.ts.map +0 -1
  283. package/dist/__tests__/unit/workflow-emitEvent-childDetached.test.js +0 -122
  284. package/dist/__tests__/unit/workflow-emitEvent-childDetached.test.js.map +0 -1
  285. package/dist/__tests__/unit/workflow-isDescendantOf.test.d.ts +0 -2
  286. package/dist/__tests__/unit/workflow-isDescendantOf.test.d.ts.map +0 -1
  287. package/dist/__tests__/unit/workflow-isDescendantOf.test.js +0 -140
  288. package/dist/__tests__/unit/workflow-isDescendantOf.test.js.map +0 -1
  289. package/dist/__tests__/unit/workflow.test.d.ts +0 -2
  290. package/dist/__tests__/unit/workflow.test.d.ts.map +0 -1
  291. package/dist/__tests__/unit/workflow.test.js +0 -330
  292. package/dist/__tests__/unit/workflow.test.js.map +0 -1
@@ -1,4 +1,8 @@
1
+ import { z } from 'zod';
1
2
  import { generateId } from '../utils/id.js';
3
+ import { validateAgentResponse } from '../utils/agent-validation.js';
4
+ import { analyzeErrorForRestart } from '../utils/restart-analysis.js';
5
+ import { mergeWorkflowErrors } from '../utils/workflow-error-utils.js';
2
6
  import { WorkflowLogger } from './logger.js';
3
7
  import { getObservedState } from '../decorators/observed-state.js';
4
8
  import { createWorkflowContext } from './workflow-context.js';
@@ -46,16 +50,25 @@ export class Workflow {
46
50
  executor;
47
51
  /** Workflow configuration */
48
52
  config;
53
+ /** Error collection state for workflow-level error merge */
54
+ collectedErrors = [];
55
+ /** Event history entries with insertion timestamps for replay functionality (ES2022 private field) */
56
+ #eventHistory = [];
57
+ /** Total operations count for error merge context */
58
+ totalOperations = 0;
59
+ /** Operation counter for error merge context */
60
+ operationCounter = 0;
49
61
  /**
50
62
  * Create a new workflow instance
51
63
  *
52
- * @overload Class-based pattern
53
- * @param name Human-readable name (defaults to class name)
54
- * @param parent Optional parent workflow
64
+ * @overload Class-based pattern: constructor(name?: string, parent?: Workflow)
65
+ * @overload Functional pattern: constructor(config: WorkflowConfig, executor?: WorkflowExecutor)
66
+ * @param name For class-based pattern, human-readable name. Allowed characters: alphanumeric (a-z, A-Z, 0-9), spaces, hyphens (-), underscores (_). (default: class name).
67
+ * For functional pattern, config object with workflow settings.
68
+ * @param parentOrExecutor For class-based pattern, optional parent workflow.
69
+ * For functional pattern, executor function.
55
70
  *
56
- * @overload Functional pattern
57
- * @param config Workflow configuration
58
- * @param executor Executor function
71
+ * @remarks Security validation rejects names containing control characters, HTML tags, JavaScript patterns, path traversal sequences (..), and file system special characters (/ \ : * ? " < > |). This prevents XSS attacks, injection attacks, and path traversal vulnerabilities.
59
72
  */
60
73
  constructor(name, parentOrExecutor) {
61
74
  this.id = generateId();
@@ -80,6 +93,32 @@ export class Workflow {
80
93
  if (this.config.name.length > 100) {
81
94
  throw new Error('Workflow name cannot exceed 100 characters');
82
95
  }
96
+ // Shared message for all security validations below.
97
+ const invalidNameMessage = 'Invalid workflow name. Names may contain letters, numbers, spaces, hyphens, underscores, and emoji.';
98
+ // Security validation: control characters (ASCII 0x00-0x1F, 0x7F)
99
+ if (/[\x00-\x1F\x7F]/.test(trimmedName)) {
100
+ throw new Error(invalidNameMessage);
101
+ }
102
+ // Security validation: HTML/JavaScript injection patterns
103
+ if (/<[^>]*>/.test(trimmedName) || /javascript:/i.test(trimmedName)) {
104
+ throw new Error(invalidNameMessage);
105
+ }
106
+ // Security validation: path traversal patterns
107
+ if (/\.\./.test(trimmedName)) {
108
+ throw new Error(invalidNameMessage);
109
+ }
110
+ // Security validation: file system special characters
111
+ if (/[\/\\:*?"<>|]/.test(trimmedName)) {
112
+ throw new Error(invalidNameMessage);
113
+ }
114
+ // Security validation: allowed characters (allowlist - defense in depth)
115
+ // Unicode-aware: permits letters (\p{L}), numbers (\p{N}), and emoji
116
+ // (including pictographic bases, variation selectors U+FE0F, and ZWJ
117
+ // sequences U+200D) from any script, while still blocking exotic or
118
+ // symbolic characters not covered by the targeted checks above.
119
+ if (!/^[\p{L}\p{N}\p{Emoji_Presentation}\p{Extended_Pictographic}\u200D\uFE0F _-]+$/u.test(trimmedName)) {
120
+ throw new Error(invalidNameMessage);
121
+ }
83
122
  }
84
123
  // Create the node representation
85
124
  this.node = {
@@ -118,6 +157,64 @@ export class Workflow {
118
157
  }
119
158
  return root.observers;
120
159
  }
160
+ /**
161
+ * Check if event history is enabled for this workflow
162
+ *
163
+ * @returns true if event history is enabled, false otherwise
164
+ */
165
+ isEventHistoryEnabled() {
166
+ return this.config.eventHistory?.enabled === true;
167
+ }
168
+ /**
169
+ * Get event history configuration with defaults applied
170
+ *
171
+ * @returns Configuration object with all required fields populated
172
+ */
173
+ getEventHistoryConfig() {
174
+ return {
175
+ enabled: this.config.eventHistory?.enabled ?? false,
176
+ maxEvents: this.config.eventHistory?.maxEvents ?? 1000,
177
+ maxAgeMs: this.config.eventHistory?.maxAgeMs ?? 3600000,
178
+ };
179
+ }
180
+ /**
181
+ * Trim event history based on configuration
182
+ *
183
+ * Uses lazy trimming for performance:
184
+ * - Only trims when at least 1.5x over the maxEvents limit
185
+ * - Applies both count and age constraints
186
+ * - Uses slice() for efficiency (not shift())
187
+ *
188
+ * @remarks
189
+ * Lazy trimming reduces the number of trim operations by only trimming
190
+ * when the history is significantly over the limit (1.5x). This provides
191
+ * better performance for high-frequency event emission.
192
+ */
193
+ trimEventHistory() {
194
+ const config = this.getEventHistoryConfig();
195
+ // Lazy trimming: only trim when significantly over limit
196
+ const trimThreshold = Math.floor(config.maxEvents * 1.5);
197
+ if (this.#eventHistory.length < trimThreshold) {
198
+ return;
199
+ }
200
+ const now = Date.now();
201
+ const ageCutoff = now - config.maxAgeMs;
202
+ // Age-based trimming: find first entry within age limit
203
+ let keepFromIndex = 0;
204
+ for (let i = 0; i < this.#eventHistory.length; i++) {
205
+ if (this.#eventHistory[i].insertedAt > ageCutoff) {
206
+ keepFromIndex = i;
207
+ break;
208
+ }
209
+ }
210
+ // Count-based trimming: remove excess events
211
+ const countBasedIndex = Math.max(0, this.#eventHistory.length - config.maxEvents);
212
+ // Use the more aggressive constraint
213
+ const finalIndex = Math.max(keepFromIndex, countBasedIndex);
214
+ if (finalIndex > 0) {
215
+ this.#eventHistory = this.#eventHistory.slice(finalIndex);
216
+ }
217
+ }
121
218
  /**
122
219
  * Check if this workflow is a descendant of the given ancestor workflow.
123
220
  *
@@ -126,12 +223,11 @@ export class Workflow {
126
223
  * a convenient way to check workflow hierarchy relationships without manually
127
224
  * traversing the parent chain.
128
225
  *
129
- * @warning This method reveals workflow hierarchy information. If your
130
- * application exposes workflows via an API, ensure you implement proper
131
- * access control to prevent unauthorized topology discovery. Note that
132
- * the `parent` and `children` properties are already public, so this
133
- * method does not expose any new information beyond what is currently
134
- * accessible.
226
+ * @remarks SECURITY WARNING: This method reveals workflow hierarchy information.
227
+ * If your application exposes workflows via an API, ensure you implement proper
228
+ * access control to prevent unauthorized topology discovery. Note that the parent
229
+ * and children properties are already public, so this method does not expose any
230
+ * new information beyond what is currently accessible.
135
231
  *
136
232
  * **Time Complexity**: O(d) where d is the depth of the hierarchy
137
233
  * **Space Complexity**: O(d) for the visited Set in worst case (cycle detection)
@@ -203,6 +299,8 @@ export class Workflow {
203
299
  /**
204
300
  * Add an observer to this workflow (must be root)
205
301
  * @throws Error if called on non-root workflow
302
+ * @side effects Adds observer to internal observers array for root workflows.
303
+ * Observers will receive notifications for workflow events.
206
304
  */
207
305
  addObserver(observer) {
208
306
  if (this.parent) {
@@ -233,6 +331,7 @@ export class Workflow {
233
331
  * - Sets child.parent = this (workflow tree)
234
332
  * - Sets child.node.parent = this.node (node tree)
235
333
  * - Emits childAttached event to notify observers
334
+ * - Emits treeUpdated event to trigger tree debugger rebuild
236
335
  *
237
336
  * **Invariants Maintained:**
238
337
  * - Single-parent rule: A workflow can only have one parent
@@ -247,6 +346,8 @@ export class Workflow {
247
346
  * @throws {Error} If the child is already attached to this workflow
248
347
  * @throws {Error} If the child already has a different parent (use detachChild() first for reparenting)
249
348
  * @throws {Error} If the child is an ancestor of this parent (would create circular reference)
349
+ * @side effects Modifies workflow tree structure, emits childAttached event,
350
+ * and triggers treeUpdated event for debugger.
250
351
  *
251
352
  * @example
252
353
  * ```ts
@@ -298,7 +399,7 @@ export class Workflow {
298
399
  }
299
400
  this.children.push(child);
300
401
  this.node.children.push(child.node);
301
- // Emit child attached event
402
+ // Emit child attached event (triggers onTreeChanged via emitEvent)
302
403
  this.emitEvent({
303
404
  type: 'childAttached',
304
405
  parentId: this.id,
@@ -312,10 +413,14 @@ export class Workflow {
312
413
  * the node tree (this.node.children), clears the child's parent reference,
313
414
  * and emits a childDetached event to notify observers.
314
415
  *
416
+ * Also emits treeUpdated event to trigger tree debugger rebuild.
417
+ *
315
418
  * This enables reparenting workflows: oldParent.detachChild(child); newParent.attachChild(child);
316
419
  *
317
420
  * @param child - The child workflow to detach
318
421
  * @throws {Error} If the child is not attached to this parent workflow
422
+ * @side effects Modifies workflow tree structure, emits childDetached event,
423
+ * and triggers treeUpdated event for debugger.
319
424
  *
320
425
  * @example
321
426
  * ```ts
@@ -343,7 +448,7 @@ export class Workflow {
343
448
  // Clear child's parent reference (both workflow tree and node tree)
344
449
  child.parent = null;
345
450
  child.node.parent = null; // Maintain 1:1 mirror between workflow tree and node tree
346
- // Emit childDetached event
451
+ // Emit childDetached event (triggers onTreeChanged via emitEvent)
347
452
  this.emitEvent({
348
453
  type: 'childDetached',
349
454
  parentId: this.id,
@@ -352,8 +457,15 @@ export class Workflow {
352
457
  }
353
458
  /**
354
459
  * Emit an event to all root observers
460
+ * @side effects Pushes event to node.events array and notifies all registered observers.
461
+ * May trigger treeUpdated notifications for specific event types.
355
462
  */
356
463
  emitEvent(event) {
464
+ // Store event in history FIRST (for replay functionality) - only if enabled
465
+ if (this.isEventHistoryEnabled()) {
466
+ this.#eventHistory.push({ event, insertedAt: Date.now() });
467
+ this.trimEventHistory();
468
+ }
357
469
  this.node.events.push(event);
358
470
  const observers = this.getRootObservers();
359
471
  for (const obs of observers) {
@@ -369,8 +481,117 @@ export class Workflow {
369
481
  }
370
482
  }
371
483
  }
484
+ /**
485
+ * Replay historical events to an observer
486
+ *
487
+ * **Strategy:**
488
+ * 1. Start with event history array
489
+ * 2. Filter by timestamp if `since` is provided
490
+ * 3. Limit events if `limit` is provided
491
+ * 4. Call observer.onEvent() for each event
492
+ * 5. Handle observer errors gracefully
493
+ *
494
+ * **Performance:** O(n) where n = number of events in history
495
+ *
496
+ * **Timestamp Handling:**
497
+ * - Events with timestamps: stepRetry, stepRestarted, invalidResponse
498
+ * - Events without timestamps: Always included (considered timeless)
499
+ * - Filter applies only to events with timestamp field
500
+ *
501
+ * **Order of Operations:** Filter first, then limit (more efficient)
502
+ *
503
+ * **Use Case:**
504
+ * - Catch up new observers to current state
505
+ * - Debug by replaying events to diagnostic observers
506
+ * - Test scenarios by replaying historical events
507
+ *
508
+ * @param observer - The observer to replay events to
509
+ * @param options - Optional replay configuration
510
+ * @param options.since - Only replay events after this timestamp (ms since epoch)
511
+ * @param options.limit - Maximum number of events to replay
512
+ *
513
+ * @example Replay all events to new observer
514
+ * ```ts
515
+ * const observer = {
516
+ * onLog: () => {},
517
+ * onEvent: (e) => console.log(e.type),
518
+ * onStateUpdated: () => {},
519
+ * onTreeChanged: () => {},
520
+ * };
521
+ * workflow.replayEvents(observer);
522
+ * ```
523
+ *
524
+ * @example Replay last 10 events
525
+ * ```ts
526
+ * workflow.replayEvents(observer, { limit: 10 });
527
+ * ```
528
+ *
529
+ * @example Replay events from last 5 minutes
530
+ * ```ts
531
+ * const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
532
+ * workflow.replayEvents(observer, { since: fiveMinutesAgo });
533
+ * ```
534
+ */
535
+ replayEvents(observer, options) {
536
+ // Extract events from entries
537
+ let events = this.#eventHistory.map(entry => entry.event);
538
+ // Filter by timestamp if provided
539
+ if (options?.since !== undefined) {
540
+ events = events.filter(event => {
541
+ // Extract timestamp from events that have it
542
+ const timestamp = event.type === 'stepRetry' ? event.timestamp :
543
+ event.type === 'stepRestarted' ? event.timestamp :
544
+ event.type === 'invalidResponse' ? event.timestamp :
545
+ undefined;
546
+ // Include events without timestamp or events after since
547
+ return timestamp === undefined || timestamp >= options.since;
548
+ });
549
+ }
550
+ // Apply limit if provided
551
+ if (options?.limit !== undefined) {
552
+ events = events.slice(0, options.limit);
553
+ }
554
+ // Replay events to observer
555
+ for (const event of events) {
556
+ try {
557
+ observer.onEvent(event);
558
+ }
559
+ catch (err) {
560
+ this.logger.error('Observer replay error', { error: err, eventType: event.type });
561
+ }
562
+ }
563
+ }
564
+ /**
565
+ * Clear the event history array
566
+ *
567
+ * **Strategy:**
568
+ * - Reassign #eventHistory to empty array
569
+ * - Frees memory by discarding all stored events
570
+ * - Events in node.events are preserved
571
+ *
572
+ * **Use Case:**
573
+ * - Free memory after workflow completes
574
+ * - Reset history between test runs
575
+ * - Prevent memory leaks in long-running workflows
576
+ *
577
+ * **Side Effects:**
578
+ * - Frees memory for discarded events
579
+ * - Future replayEvents() calls will return empty
580
+ * - Does NOT affect node.events array
581
+ *
582
+ * @example Clear history after workflow completes
583
+ * ```ts
584
+ * await workflow.run();
585
+ * workflow.clearEventHistory(); // Free memory
586
+ * ```
587
+ */
588
+ clearEventHistory() {
589
+ this.#eventHistory = [];
590
+ }
372
591
  /**
373
592
  * Capture and emit a state snapshot
593
+ * @side effects Updates node.stateSnapshot, notifies observers via onStateUpdated callback,
594
+ * emits snapshot event, and triggers treeUpdated event for debugger.
374
595
  */
375
596
  snapshotState() {
376
597
  const snapshot = getObservedState(this);
@@ -393,6 +614,278 @@ export class Workflow {
393
614
  // Emit treeUpdated event to trigger tree debugger rebuild
394
615
  this.emitEvent({ type: 'treeUpdated', root: this.getRoot().node });
395
616
  }
617
+ /**
618
+ * Restart a specific step with state restoration and retry tracking
619
+ *
620
+ * This method enables manual step restart from parent workflows. It validates
621
+ * the step exists, checks retry limits, optionally restores state, re-executes
622
+ * the step method, and emits a stepRestarted event for observability.
623
+ *
624
+ * @param stepName - The name of the step method to restart
625
+ * @param options - Optional configuration for the restart attempt
626
+ * @returns The result of the step execution
627
+ * @throws {WorkflowError} When step is not found or max retries exceeded
628
+ *
629
+ * @example Restart a step with default retry tracking
630
+ * ```ts
631
+ * class MyWorkflow extends Workflow {
632
+ * @Step({ restartable: true })
633
+ * async myStep() { return 'result'; }
634
+ *
635
+ * async run() {
636
+ * const result = await this.restartStep('myStep');
637
+ * }
638
+ * }
639
+ * ```
640
+ *
641
+ * @example Restart with explicit retry count and state override
642
+ * ```ts
643
+ * await this.restartStep('failingStep', {
644
+ * retryCount: 1,
645
+ * maxRetries: 3,
646
+ * stateOverride: { counter: 5 }
647
+ * });
648
+ * ```
649
+ */
650
+ async restartStep(stepName, options) {
651
+ // Calculate the retry count for this attempt
652
+ const retryCount = (options?.retryCount ?? 0) + 1;
653
+ const maxRetries = options?.maxRetries ?? 3;
654
+ // Check retry limit
655
+ if (retryCount > maxRetries) {
656
+ const error = {
657
+ message: `Max retries (${maxRetries}) exceeded for step '${stepName}'`,
658
+ original: new Error('Max retries exceeded'),
659
+ workflowId: this.id,
660
+ state: getObservedState(this),
661
+ logs: [...this.node.logs],
662
+ };
663
+ throw error;
664
+ }
665
+ // Verify the step method exists and is callable
666
+ const method = this[stepName];
667
+ if (typeof method !== 'function') {
668
+ const error = {
669
+ message: `Step '${stepName}' not found`,
670
+ original: new Error('Step not found'),
671
+ workflowId: this.id,
672
+ state: getObservedState(this),
673
+ logs: [...this.node.logs],
674
+ };
675
+ throw error;
676
+ }
677
+ // Restore state - use override if provided, otherwise capture current state
678
+ let restoredState;
679
+ if (options?.stateOverride !== undefined) {
680
+ restoredState = options.stateOverride;
681
+ // For state override, we'd ideally restore the state here
682
+ // Since no restoreState() method exists, stateOverride is primarily for the event payload
683
+ // and any future state restoration implementation
684
+ }
685
+ else {
686
+ // Capture current state as the restored state
687
+ this.snapshotState();
688
+ restoredState = this.node.stateSnapshot ?? {};
689
+ }
690
+ // Execute the step method
691
+ const result = await method.call(this);
692
+ // Emit stepRestarted event
693
+ this.emitEvent({
694
+ type: 'stepRestarted',
695
+ node: this.node,
696
+ stepName,
697
+ retryCount,
698
+ restoredState,
699
+ timestamp: Date.now(),
700
+ });
701
+ return result;
702
+ }
703
+ /**
704
+ * Analyze a WorkflowError from a child workflow and determine restart action
705
+ *
706
+ * This method enables parent workflows to make intelligent decisions about child
707
+ * workflow failures by analyzing the error and step metadata to determine whether
708
+ * to retry the child, abort the parent, or rebuild the execution plan.
709
+ *
710
+ * **Analysis Flow:**
711
+ * 1. Check `error.original?.recoverable` - if false, return 'abort'
712
+ * 2. Extract stepName from error metadata (if available)
713
+ * 3. Retrieve step metadata from stepMetadata map (if exists)
714
+ * 4. Check if step is marked as restartable - if not, return 'abort'
715
+ * 5. Use `analyzeErrorForRestart` utility to check retry criteria
716
+ * 6. Return 'retry' if any criteria match, otherwise 'abort'
717
+ *
718
+ * **Integration with restartStep:**
719
+ * This method is designed to be used alongside `restartStep()`:
720
+ * - Call `analyzeError()` to get the decision
721
+ * - If 'retry', call `restartStep(stepName)` to execute
722
+ * - If 'abort', throw the error or return early
723
+ * - If 'rebuild', trigger plan rebuild logic
724
+ *
725
+ * @param error - The WorkflowError to analyze (typically from child workflow)
726
+ * @returns The recommended action: 'retry', 'abort', or 'rebuild'
727
+ *
728
+ * @example Parent workflow error handling
729
+ * ```ts
730
+ * class ParentWorkflow extends Workflow {
731
+ * @Step({ restartable: true, retryOn: [{ code: 'TIMEOUT' }] })
732
+ * async childWorkflow(): Promise<void> {
733
+ * // Child logic that may fail
734
+ * }
735
+ *
736
+ * async run(): Promise<void> {
737
+ * try {
738
+ * await this.childWorkflow();
739
+ * } catch (err) {
740
+ * const error = err as WorkflowError;
741
+ * const action = this.analyzeError(error);
742
+ *
743
+ * if (action === 'retry') {
744
+ * await this.restartStep('childWorkflow');
745
+ * } else if (action === 'abort') {
746
+ * throw error;
747
+ * } else if (action === 'rebuild') {
748
+ * // Trigger plan rebuild logic
749
+ * }
750
+ * }
751
+ * }
752
+ * }
753
+ * ```
754
+ *
755
+ * @example Analyze error from child workflow event
756
+ * ```ts
757
+ * class ParentWorkflow extends Workflow {
758
+ * private lastError: WorkflowError | null = null;
759
+ *
760
+ * async run(): Promise<void> {
761
+ * // Subscribe to error events
762
+ * this.on('error', (event) => {
763
+ * this.lastError = event.error;
764
+ * });
765
+ *
766
+ * // Later, analyze the error
767
+ * if (this.lastError) {
768
+ * const action = this.analyzeError(this.lastError);
769
+ * // Take action based on decision
770
+ * }
771
+ * }
772
+ * }
773
+ * ```
774
+ *
775
+ * @remarks
776
+ * **Known Limitation:**
777
+ * The `stepMetadata` map is not yet populated by the `@Step` decorator.
778
+ * This method will return 'abort' if stepMetadata is not available or the step
779
+ * is not found. This will be improved in a future update to the decorator.
780
+ *
781
+ * **Error Metadata:**
782
+ * The stepName is extracted from `error.state?.stepName`. Ensure child
783
+ * workflows populate this field when creating WorkflowError instances.
784
+ *
785
+ * @see {@link restartStep} - For executing a retry after analysis
786
+ * @see {@link analyzeErrorForRestart} - For the underlying utility function
787
+ */
788
+ analyzeError(error) {
789
+ // STEP 1: Check recoverable flag
790
+ const original = error.original;
791
+ if (original && 'recoverable' in original && !original.recoverable) {
792
+ return 'abort';
793
+ }
794
+ // STEP 2: Extract stepName from error metadata
795
+ // GOTCHA: WorkflowError doesn't have stepName property
796
+ // Check if error.state or error.original has step information
797
+ const stepName = error.state?.stepName;
798
+ if (!stepName) {
799
+ return 'abort'; // Can't determine which step failed
800
+ }
801
+ // STEP 3: Get step metadata with graceful handling
802
+ // CRITICAL: stepMetadata may not exist yet (decorator doesn't store it)
803
+ if (!('stepMetadata' in this)) {
804
+ return 'abort'; // No metadata available
805
+ }
806
+ const stepMeta = this.stepMetadata.get(stepName);
807
+ if (!stepMeta) {
808
+ return 'abort'; // Step not found in metadata
809
+ }
810
+ // STEP 4: Check if step is restartable
811
+ if (!stepMeta.options?.restartable) {
812
+ return 'abort'; // Step not marked as restartable
813
+ }
814
+ // STEP 5: Use analyzeErrorForRestart for criterion matching
815
+ const analysis = analyzeErrorForRestart(error, stepMeta.options);
816
+ if (analysis.shouldRestart) {
817
+ return 'retry';
818
+ }
819
+ // STEP 6: Default to abort
820
+ return 'abort';
821
+ }
822
+ /**
823
+ * Validate an agent response at the workflow level
824
+ *
825
+ * This method enables parent workflows to validate agent responses
826
+ * before processing them. It follows the same validation pattern as
827
+ * Agent.validateResponse() but emits events and creates WorkflowError
828
+ * for workflow-level error handling.
829
+ *
830
+ * @template T - The type of response data
831
+ * @param response - The AgentResponse to validate
832
+ * @param agentId - Identifier of the agent that produced the response
833
+ * @param dataSchema - Optional Zod schema for response data (defaults to z.unknown())
834
+ * @returns true if validation passes, false if validation fails
835
+ *
836
+ * @example Validate response from child workflow
837
+ * ```ts
838
+ * class ParentWorkflow extends Workflow {
839
+ * @Step()
840
+ * async processChildResult() {
841
+ * const response = await this.childWorkflow.run();
842
+ *
843
+ * if (!this.validateAgentResponse(response, this.childWorkflow.agent.id)) {
844
+ * // Handle validation failure
845
+ * const action = this.analyzeError(this.lastError);
846
+ * if (action === 'retry') {
847
+ * return await this.restartStep('processChildResult');
848
+ * }
849
+ * }
850
+ *
851
+ * // Process valid response
852
+ * return response.data;
853
+ * }
854
+ * }
855
+ * ```
856
+ */
857
+ validateAgentResponse(response, agentId, dataSchema = z.unknown()) {
858
+ // Call shared utility for validation
859
+ const result = validateAgentResponse(response, dataSchema);
860
+ if (result.valid) {
861
+ // Response is valid
862
+ return true;
863
+ }
864
+ // Validation failed - emit event and create error
865
+ const zodError = result.errors; // Safe: errors exists when valid is false
866
+ // Emit invalidResponse event
867
+ this.emitEvent({
868
+ type: 'invalidResponse',
869
+ node: this.node,
870
+ response,
871
+ agentId,
872
+ errors: zodError,
873
+ timestamp: Date.now(),
874
+ });
875
+ // Create WorkflowError with INVALID_RESPONSE_FORMAT context
876
+ const validationError = {
877
+ message: `Agent response validation failed for agent '${agentId}'`,
878
+ original: zodError,
879
+ workflowId: this.id,
880
+ stack: zodError.stack,
881
+ state: getObservedState(this),
882
+ logs: [...this.node.logs],
883
+ };
884
+ // Store error for potential use by analyzeError/restartStep
885
+ // Note: Consider adding lastError property to Workflow class if not exists
886
+ // For now, emit event and return false
887
+ return false;
888
+ }
396
889
  /**
397
890
  * Update workflow status and sync with node
398
891
  */
@@ -431,10 +924,34 @@ export class Workflow {
431
924
  }
432
925
  const startTime = Date.now();
433
926
  this.setStatus('running');
434
- // Create workflow context
435
- const ctx = createWorkflowContext(this, this.parent?.id, this.config.enableReflection ? { enabled: true } : undefined);
927
+ // Reset error collection state
928
+ this.collectedErrors = [];
929
+ this.operationCounter = 0;
930
+ // Create workflow context with error merge strategy
931
+ const ctx = createWorkflowContext(this, this.parent?.id, this.config.enableReflection ? { enabled: true } : undefined, this.config.autoValidateResponses ?? true, this.config.errorMergeStrategy);
436
932
  try {
437
933
  const result = await this.executor(ctx);
934
+ // Check if we should merge collected errors
935
+ if (this.collectedErrors.length > 0) {
936
+ if (this.config.errorMergeStrategy?.enabled) {
937
+ // Merge errors
938
+ const mergedError = this.config.errorMergeStrategy?.combine
939
+ ? this.config.errorMergeStrategy.combine(this.collectedErrors)
940
+ : mergeWorkflowErrors(this.collectedErrors, this.config.name || this.id, this.id, this.operationCounter);
941
+ // Emit error event with merged error
942
+ this.emitEvent({
943
+ type: 'error',
944
+ node: this.node,
945
+ error: mergedError,
946
+ });
947
+ // Throw merged error
948
+ throw mergedError;
949
+ }
950
+ else {
951
+ // Throw first error (backward compatibility)
952
+ throw this.collectedErrors[0];
953
+ }
954
+ }
438
955
  this.setStatus('completed');
439
956
  return {
440
957
  data: result,
@@ -443,20 +960,25 @@ export class Workflow {
443
960
  };
444
961
  }
445
962
  catch (error) {
446
- this.setStatus('failed');
447
- // Emit error event
448
- this.emitEvent({
449
- type: 'error',
450
- node: this.node,
451
- error: {
452
- message: error instanceof Error ? error.message : 'Unknown error',
453
- original: error,
454
- workflowId: this.id,
455
- stack: error instanceof Error ? error.stack : undefined,
456
- state: getObservedState(this),
457
- logs: [...this.node.logs],
458
- },
459
- });
963
+ // Handle errors thrown directly (not collected)
964
+ if (!this.config.errorMergeStrategy?.enabled) {
965
+ this.setStatus('failed');
966
+ this.emitEvent({
967
+ type: 'error',
968
+ node: this.node,
969
+ error: {
970
+ message: error instanceof Error ? error.message : 'Unknown error',
971
+ original: error,
972
+ workflowId: this.id,
973
+ stack: error instanceof Error ? error.stack : undefined,
974
+ state: getObservedState(this),
975
+ logs: [...this.node.logs],
976
+ },
977
+ });
978
+ throw error;
979
+ }
980
+ // If in collection mode, error should have been collected already
981
+ // Re-throw if it somehow escaped collection
460
982
  throw error;
461
983
  }
462
984
  }