@hexabot-ai/agentic 3.0.1-alpha.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 (176) hide show
  1. package/LICENSE.md +120 -0
  2. package/README.md +231 -0
  3. package/dist/cjs/action/abstract-action.js +141 -0
  4. package/dist/cjs/action/action.js +47 -0
  5. package/dist/cjs/action/action.types.js +7 -0
  6. package/dist/cjs/bindings/base-binding.js +278 -0
  7. package/dist/cjs/context.js +79 -0
  8. package/dist/cjs/dsl.types.js +318 -0
  9. package/dist/cjs/index.js +52 -0
  10. package/dist/cjs/runner-runtime-control.js +343 -0
  11. package/dist/cjs/step-executors/conditional-executor.js +63 -0
  12. package/dist/cjs/step-executors/loop-executor.js +187 -0
  13. package/dist/cjs/step-executors/parallel-executor.js +51 -0
  14. package/dist/cjs/step-executors/skip-helpers.js +29 -0
  15. package/dist/cjs/step-executors/task-executor.js +155 -0
  16. package/dist/cjs/step-executors/types.js +7 -0
  17. package/dist/cjs/suspension-rebuilder.js +301 -0
  18. package/dist/cjs/utils/deferred.js +17 -0
  19. package/dist/cjs/utils/naming.js +48 -0
  20. package/dist/cjs/utils/timeout.js +42 -0
  21. package/dist/cjs/utils/workflow-definition.js +102 -0
  22. package/dist/cjs/workflow-compiler.js +212 -0
  23. package/dist/cjs/workflow-event-emitter.js +40 -0
  24. package/dist/cjs/workflow-runner.js +448 -0
  25. package/dist/cjs/workflow-types.js +7 -0
  26. package/dist/cjs/workflow-values.js +99 -0
  27. package/dist/cjs/workflow.js +243 -0
  28. package/dist/esm/action/abstract-action.js +137 -0
  29. package/dist/esm/action/action.js +44 -0
  30. package/dist/esm/action/action.types.js +6 -0
  31. package/dist/esm/bindings/base-binding.js +273 -0
  32. package/dist/esm/context.js +75 -0
  33. package/dist/esm/dsl.types.js +309 -0
  34. package/dist/esm/index.js +16 -0
  35. package/dist/esm/runner-runtime-control.js +338 -0
  36. package/dist/esm/step-executors/conditional-executor.js +60 -0
  37. package/dist/esm/step-executors/loop-executor.js +182 -0
  38. package/dist/esm/step-executors/parallel-executor.js +48 -0
  39. package/dist/esm/step-executors/skip-helpers.js +25 -0
  40. package/dist/esm/step-executors/task-executor.js +152 -0
  41. package/dist/esm/step-executors/types.js +6 -0
  42. package/dist/esm/suspension-rebuilder.js +296 -0
  43. package/dist/esm/utils/deferred.js +14 -0
  44. package/dist/esm/utils/naming.js +42 -0
  45. package/dist/esm/utils/timeout.js +37 -0
  46. package/dist/esm/utils/workflow-definition.js +98 -0
  47. package/dist/esm/workflow-compiler.js +208 -0
  48. package/dist/esm/workflow-event-emitter.js +36 -0
  49. package/dist/esm/workflow-runner.js +444 -0
  50. package/dist/esm/workflow-types.js +6 -0
  51. package/dist/esm/workflow-values.js +89 -0
  52. package/dist/esm/workflow.js +236 -0
  53. package/dist/types/action/abstract-action.d.ts +54 -0
  54. package/dist/types/action/abstract-action.d.ts.map +1 -0
  55. package/dist/types/action/action.d.ts +23 -0
  56. package/dist/types/action/action.d.ts.map +1 -0
  57. package/dist/types/action/action.types.d.ts +55 -0
  58. package/dist/types/action/action.types.d.ts.map +1 -0
  59. package/dist/types/bindings/base-binding.d.ts +52 -0
  60. package/dist/types/bindings/base-binding.d.ts.map +1 -0
  61. package/dist/types/context.d.ts +105 -0
  62. package/dist/types/context.d.ts.map +1 -0
  63. package/dist/types/dsl.types.d.ts +220 -0
  64. package/dist/types/dsl.types.d.ts.map +1 -0
  65. package/dist/types/index.d.ts +18 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/runner-runtime-control.d.ts +64 -0
  68. package/dist/types/runner-runtime-control.d.ts.map +1 -0
  69. package/dist/types/step-executors/conditional-executor.d.ts +13 -0
  70. package/dist/types/step-executors/conditional-executor.d.ts.map +1 -0
  71. package/dist/types/step-executors/loop-executor.d.ts +33 -0
  72. package/dist/types/step-executors/loop-executor.d.ts.map +1 -0
  73. package/dist/types/step-executors/parallel-executor.d.ts +14 -0
  74. package/dist/types/step-executors/parallel-executor.d.ts.map +1 -0
  75. package/dist/types/step-executors/skip-helpers.d.ts +12 -0
  76. package/dist/types/step-executors/skip-helpers.d.ts.map +1 -0
  77. package/dist/types/step-executors/task-executor.d.ts +13 -0
  78. package/dist/types/step-executors/task-executor.d.ts.map +1 -0
  79. package/dist/types/step-executors/types.d.ts +24 -0
  80. package/dist/types/step-executors/types.d.ts.map +1 -0
  81. package/dist/types/suspension-rebuilder.d.ts +67 -0
  82. package/dist/types/suspension-rebuilder.d.ts.map +1 -0
  83. package/dist/types/utils/deferred.d.ts +7 -0
  84. package/dist/types/utils/deferred.d.ts.map +1 -0
  85. package/dist/types/utils/naming.d.ts +24 -0
  86. package/dist/types/utils/naming.d.ts.map +1 -0
  87. package/dist/types/utils/timeout.d.ts +17 -0
  88. package/dist/types/utils/timeout.d.ts.map +1 -0
  89. package/dist/types/utils/workflow-definition.d.ts +3 -0
  90. package/dist/types/utils/workflow-definition.d.ts.map +1 -0
  91. package/dist/types/workflow-compiler.d.ts +12 -0
  92. package/dist/types/workflow-compiler.d.ts.map +1 -0
  93. package/dist/types/workflow-event-emitter.d.ts +70 -0
  94. package/dist/types/workflow-event-emitter.d.ts.map +1 -0
  95. package/dist/types/workflow-runner.d.ts +179 -0
  96. package/dist/types/workflow-runner.d.ts.map +1 -0
  97. package/dist/types/workflow-types.d.ts +195 -0
  98. package/dist/types/workflow-types.d.ts.map +1 -0
  99. package/dist/types/workflow-values.d.ts +34 -0
  100. package/dist/types/workflow-values.d.ts.map +1 -0
  101. package/dist/types/workflow.d.ts +75 -0
  102. package/dist/types/workflow.d.ts.map +1 -0
  103. package/examples/defs-bindings-agent/actions/ai-agent.ts +110 -0
  104. package/examples/defs-bindings-agent/actions/caculate-score.ts +49 -0
  105. package/examples/defs-bindings-agent/actions/index.ts +15 -0
  106. package/examples/defs-bindings-agent/bindings.ts +20 -0
  107. package/examples/defs-bindings-agent/context.ts +32 -0
  108. package/examples/defs-bindings-agent/workflow.ts +67 -0
  109. package/examples/defs-bindings-agent/workflow.yml +28 -0
  110. package/examples/full/actions/await-user-input.ts +46 -0
  111. package/examples/full/actions/call-llm.ts +133 -0
  112. package/examples/full/actions/create-ticket.ts +46 -0
  113. package/examples/full/actions/decision-router.ts +83 -0
  114. package/examples/full/actions/get-calendar-events.ts +55 -0
  115. package/examples/full/actions/get-user-profile.ts +72 -0
  116. package/examples/full/actions/index.ts +33 -0
  117. package/examples/full/actions/query-memory.ts +45 -0
  118. package/examples/full/actions/search-web.ts +53 -0
  119. package/examples/full/actions/send-email.ts +50 -0
  120. package/examples/full/context.ts +41 -0
  121. package/examples/full/workflow.ts +75 -0
  122. package/examples/full/workflow.yml +359 -0
  123. package/examples/loop/actions/await-reply.ts +47 -0
  124. package/examples/loop/actions/index.ts +19 -0
  125. package/examples/loop/actions/send-text-message.ts +40 -0
  126. package/examples/loop/context.ts +32 -0
  127. package/examples/loop/workflow.ts +66 -0
  128. package/examples/loop/workflow.yml +66 -0
  129. package/examples/suspend-resume/actions/format-reply.ts +43 -0
  130. package/examples/suspend-resume/actions/index.ts +13 -0
  131. package/examples/suspend-resume/actions/wait-for-user.ts +51 -0
  132. package/examples/suspend-resume/context.ts +32 -0
  133. package/examples/suspend-resume/workflow.ts +79 -0
  134. package/examples/suspend-resume/workflow.yml +29 -0
  135. package/package.json +60 -0
  136. package/src/__tests__/parser.test.ts +95 -0
  137. package/src/__tests__/suspension-rebuilder.test.ts +361 -0
  138. package/src/__tests__/test-helpers.ts +36 -0
  139. package/src/__tests__/validation.test.ts +526 -0
  140. package/src/__tests__/workflow-compiler.test.ts +715 -0
  141. package/src/__tests__/workflow-definition-path.test.ts +381 -0
  142. package/src/__tests__/workflow-event-emitter.test.ts +50 -0
  143. package/src/__tests__/workflow-runner.test.ts +1397 -0
  144. package/src/__tests__/workflow-values.test.ts +132 -0
  145. package/src/__tests__/workflow.test.ts +320 -0
  146. package/src/action/__tests__/abstract-action-timing.test.ts +183 -0
  147. package/src/action/__tests__/action.test.ts +473 -0
  148. package/src/action/abstract-action.ts +215 -0
  149. package/src/action/action.ts +83 -0
  150. package/src/action/action.types.ts +93 -0
  151. package/src/bindings/base-binding.ts +508 -0
  152. package/src/context.ts +179 -0
  153. package/src/dsl.types.ts +473 -0
  154. package/src/index.ts +114 -0
  155. package/src/runner-runtime-control.ts +544 -0
  156. package/src/step-executors/conditional-executor.test.ts +192 -0
  157. package/src/step-executors/conditional-executor.ts +83 -0
  158. package/src/step-executors/loop-executor.test.ts +303 -0
  159. package/src/step-executors/loop-executor.ts +299 -0
  160. package/src/step-executors/parallel-executor.test.ts +182 -0
  161. package/src/step-executors/parallel-executor.ts +77 -0
  162. package/src/step-executors/skip-helpers.ts +68 -0
  163. package/src/step-executors/task-executor.test.ts +257 -0
  164. package/src/step-executors/task-executor.ts +248 -0
  165. package/src/step-executors/types.ts +70 -0
  166. package/src/suspension-rebuilder.ts +504 -0
  167. package/src/utils/deferred.ts +23 -0
  168. package/src/utils/naming.ts +55 -0
  169. package/src/utils/timeout.ts +48 -0
  170. package/src/utils/workflow-definition.ts +173 -0
  171. package/src/workflow-compiler.ts +317 -0
  172. package/src/workflow-event-emitter.ts +86 -0
  173. package/src/workflow-runner.ts +603 -0
  174. package/src/workflow-types.ts +207 -0
  175. package/src/workflow-values.ts +159 -0
  176. package/src/workflow.ts +387 -0
@@ -0,0 +1,152 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ import { evaluateMapping } from '../workflow-values';
7
+ /**
8
+ * Execute a task step by running its task and handling suspension or output mapping.
9
+ *
10
+ * @param env Executor environment with compiled workflow and helpers.
11
+ * @param step The compiled task step to execute.
12
+ * @param state Mutable workflow execution state.
13
+ * @param _path Location of the step within the workflow (unused in this executor).
14
+ * @returns A suspension if the task pauses execution, otherwise void.
15
+ */
16
+ export async function executeTaskStep(env, step, state, _path) {
17
+ void _path;
18
+ const task = env.compiled.tasks[step.taskName];
19
+ if (!task) {
20
+ throw new Error(`Task "${step.taskName}" is not defined.`);
21
+ }
22
+ const stepInfo = env.buildInstanceStepInfo(step, state.iterationStack);
23
+ const scope = {
24
+ input: state.input,
25
+ context: env.context.state,
26
+ output: state.output,
27
+ iteration: state.iteration,
28
+ accumulator: state.accumulator,
29
+ };
30
+ const inputs = await evaluateMapping(task.inputs, scope);
31
+ env.recordStepExecution(stepInfo, {
32
+ action: task.actionName,
33
+ status: 'running',
34
+ startedAt: Date.now(),
35
+ input: inputs,
36
+ context: { before: env.context.snapshot() },
37
+ });
38
+ env.beginStepExecution?.(stepInfo.id);
39
+ env.setCurrentStep(stepInfo);
40
+ env.markSnapshot(stepInfo, 'running');
41
+ env.emit('hook:step:start', { runId: env.runId, step: stepInfo });
42
+ try {
43
+ const actionPromise = Promise.resolve().then(() => task.action.run(inputs, env.context, task.settings, task.bindings));
44
+ const outcome = await waitForTaskProgress(env, stepInfo.id, actionPromise);
45
+ if (outcome.type === 'completed') {
46
+ await completeTask(env, stepInfo.id, stepInfo, task, state, outcome.value);
47
+ return undefined;
48
+ }
49
+ if (outcome.type === 'failed') {
50
+ env.clearStepSuspensions(stepInfo.id, outcome.error);
51
+ recordTaskFailure(env, stepInfo, outcome.error);
52
+ throw outcome.error;
53
+ }
54
+ return buildSuspensionContinuation(env, stepInfo.id, stepInfo, task, state, actionPromise, outcome.request);
55
+ }
56
+ finally {
57
+ env.setCurrentStep(undefined);
58
+ }
59
+ }
60
+ const waitForTaskProgress = async (env, stepId, actionPromise) => {
61
+ const completion = actionPromise.then((value) => ({ type: 'completed', value }), (error) => ({ type: 'failed', error }));
62
+ const suspension = env
63
+ .waitForStepSuspension(stepId)
64
+ .then((request) => ({ type: 'suspended', request }));
65
+ return Promise.race([completion, suspension]);
66
+ };
67
+ const completeTask = async (env, stepId, stepInfo, task, state, result) => {
68
+ await env.captureTaskOutput(task, state, result);
69
+ env.recordStepExecution(stepInfo, {
70
+ status: 'completed',
71
+ endedAt: Date.now(),
72
+ output: result,
73
+ context: { after: env.context.snapshot() },
74
+ });
75
+ env.markSnapshot(stepInfo, 'completed');
76
+ env.emit('hook:step:success', { runId: env.runId, step: stepInfo });
77
+ env.clearStepSuspensions(stepId);
78
+ };
79
+ const recordSuspension = (env, stepInfo, reason, data) => {
80
+ env.recordStepExecution(stepInfo, {
81
+ status: 'suspended',
82
+ endedAt: Date.now(),
83
+ reason,
84
+ context: { after: env.context.snapshot() },
85
+ });
86
+ env.markSnapshot(stepInfo, 'suspended', reason);
87
+ env.emit('hook:step:suspended', {
88
+ runId: env.runId,
89
+ step: stepInfo,
90
+ reason,
91
+ data,
92
+ });
93
+ };
94
+ const buildSuspensionContinuation = (env, stepId, stepInfo, task, state, actionPromise, request) => {
95
+ recordSuspension(env, stepInfo, request.reason, request.data);
96
+ let resumed = false;
97
+ return {
98
+ step: stepInfo,
99
+ reason: request.reason,
100
+ data: request.data,
101
+ stepExecId: request.stepExecId,
102
+ suspendIndex: request.suspendIndex,
103
+ suspendKey: request.suspendKey,
104
+ awaitResults: request.awaitResults,
105
+ continue: async (resumeData) => {
106
+ if (resumed) {
107
+ throw new Error(`Suspension for step "${stepInfo.id}" has already been resumed.`);
108
+ }
109
+ resumed = true;
110
+ env.setCurrentStep(stepInfo);
111
+ env.recordStepSuspendResult?.({
112
+ stepId,
113
+ stepExecId: request.stepExecId,
114
+ suspendIndex: request.suspendIndex,
115
+ suspendKey: request.suspendKey,
116
+ resumeData,
117
+ });
118
+ request.resume.resolve(resumeData);
119
+ let outcome;
120
+ try {
121
+ outcome = await waitForTaskProgress(env, stepId, actionPromise);
122
+ }
123
+ finally {
124
+ env.setCurrentStep(undefined);
125
+ }
126
+ if (outcome.type === 'suspended') {
127
+ return buildSuspensionContinuation(env, stepId, stepInfo, task, state, actionPromise, outcome.request);
128
+ }
129
+ if (outcome.type === 'completed') {
130
+ await completeTask(env, stepId, stepInfo, task, state, outcome.value);
131
+ return undefined;
132
+ }
133
+ env.clearStepSuspensions(stepId, outcome.error);
134
+ recordTaskFailure(env, stepInfo, outcome.error);
135
+ throw outcome.error;
136
+ },
137
+ };
138
+ };
139
+ const recordTaskFailure = (env, stepInfo, error) => {
140
+ env.recordStepExecution(stepInfo, {
141
+ status: 'failed',
142
+ endedAt: Date.now(),
143
+ error: normalizeError(error),
144
+ context: { after: env.context.snapshot() },
145
+ });
146
+ env.markSnapshot(stepInfo, 'failed', normalizeErrorMessage(error));
147
+ env.emit('hook:step:error', { runId: env.runId, step: stepInfo, error });
148
+ };
149
+ const normalizeError = (error) => error instanceof Error
150
+ ? { message: error.message, stack: error.stack }
151
+ : { message: String(error) };
152
+ const normalizeErrorMessage = (error) => error instanceof Error ? error.message : String(error);
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ export {};
@@ -0,0 +1,296 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ import { executeLoop as runLoopExecutor, shouldStopLoop, updateAccumulator, } from './step-executors/loop-executor';
7
+ import { executeParallel as runParallelExecutor } from './step-executors/parallel-executor';
8
+ import { markStepsSkipped } from './step-executors/skip-helpers';
9
+ import { StepType, } from './workflow-event-emitter';
10
+ /**
11
+ * Parse a suspended step id into its execution path and iteration stack.
12
+ *
13
+ * @param stepId The serialized step identifier stored in snapshots.
14
+ * @returns The decoded path tokens and iteration stack.
15
+ */
16
+ export function parseSuspendedStepId(stepId) {
17
+ const iterationMatch = stepId.match(/^(.*)\[(.+)\]$/);
18
+ const baseId = iterationMatch ? iterationMatch[1] : stepId;
19
+ const iterationStack = iterationMatch
20
+ ? iterationMatch[2]
21
+ .split('.')
22
+ .map((token) => Number.parseInt(token, 10))
23
+ .filter((value) => !Number.isNaN(value))
24
+ : [];
25
+ const [pathPart] = baseId.split(':');
26
+ const path = pathPart.length === 0 || pathPart === 'root'
27
+ ? []
28
+ : pathPart.split('.').map((token) => {
29
+ const numeric = Number.parseInt(token, 10);
30
+ return Number.isNaN(numeric) ? token : numeric;
31
+ });
32
+ return { path, iterationStack };
33
+ }
34
+ /**
35
+ * Rebuild a suspension object based on persisted state and step identifiers.
36
+ *
37
+ * @param deps Dependencies needed to reconstruct executors and events.
38
+ * @param param0 Persisted state and metadata about the suspended step.
39
+ * @param param0.state Execution state captured at suspension time.
40
+ * @param param0.stepId Serialized step id used to locate the suspension point.
41
+ * @param param0.reason Optional reason supplied when the workflow suspended.
42
+ * @param param0.data Arbitrary payload provided with the suspension.
43
+ * @returns A suspension ready to be resumed, or null if reconstruction fails.
44
+ */
45
+ export function rebuildSuspension(deps, { state, stepId, reason, data, stepExecId, suspendIndex, suspendKey, awaitResults, }) {
46
+ if (!state) {
47
+ return null;
48
+ }
49
+ const { path, iterationStack } = parseSuspendedStepId(stepId);
50
+ const executionState = {
51
+ ...state,
52
+ iterationStack: iterationStack.length > 0 ? iterationStack : (state.iterationStack ?? []),
53
+ };
54
+ const suspensionMetadata = {
55
+ stepExecId,
56
+ suspendIndex,
57
+ suspendKey,
58
+ reason,
59
+ awaitResults,
60
+ };
61
+ const suspension = buildSuspensionForPath(deps, deps.compiled.flow, executionState, path, [], 0, suspensionMetadata);
62
+ if (!suspension) {
63
+ return null;
64
+ }
65
+ return {
66
+ ...suspension,
67
+ reason,
68
+ data,
69
+ stepExecId,
70
+ suspendIndex,
71
+ suspendKey,
72
+ awaitResults,
73
+ };
74
+ }
75
+ /**
76
+ * Walk the compiled flow to rebuild the continuation matching a target path.
77
+ *
78
+ * @param deps Dependencies for executor creation and event emission.
79
+ * @param steps The steps to search for the suspended step.
80
+ * @param state Mutable execution state to thread through the traversal.
81
+ * @param targetPath Path tokens identifying the suspended step location.
82
+ * @param pathPrefix Prefix accumulated while descending the tree.
83
+ * @param iterationDepth Current loop depth to match iteration stacks.
84
+ * @returns A rebuilt suspension or null if the path cannot be matched.
85
+ */
86
+ export function buildSuspensionForPath(deps, steps, state, targetPath, pathPrefix, iterationDepth = 0, suspensionMetadata = {}) {
87
+ if (!deps.context) {
88
+ throw new Error('Workflow context is not attached.');
89
+ }
90
+ const env = deps.createExecutorEnv();
91
+ if (targetPath.length === 0) {
92
+ return null;
93
+ }
94
+ const [current, ...rest] = targetPath;
95
+ if (typeof current !== 'number') {
96
+ return null;
97
+ }
98
+ const step = steps[current];
99
+ const currentPath = [...pathPrefix, current];
100
+ if (!step) {
101
+ return null;
102
+ }
103
+ if (rest.length === 0) {
104
+ if (step.type !== StepType.Task) {
105
+ return null;
106
+ }
107
+ const stepInfo = deps.buildInstanceStepInfo(step, state.iterationStack);
108
+ const replayStepExecId = suspensionMetadata.stepExecId ?? `${stepInfo.id}#1`;
109
+ const replaySuspension = suspensionMetadata.suspendIndex !== undefined ||
110
+ suspensionMetadata.suspendKey !== undefined
111
+ ? {
112
+ suspendIndex: suspensionMetadata.suspendIndex,
113
+ suspendKey: suspensionMetadata.suspendKey,
114
+ reason: suspensionMetadata.reason,
115
+ }
116
+ : undefined;
117
+ return {
118
+ step: stepInfo,
119
+ continue: async (resumeData) => {
120
+ const executorEnv = deps.createExecutorEnv();
121
+ const prepareStepReplay = executorEnv
122
+ .prepareStepReplay;
123
+ const recordStepSuspendResult = executorEnv.recordStepSuspendResult;
124
+ const primeStepResumeData = executorEnv
125
+ .primeStepResumeData;
126
+ const executeStep = executorEnv
127
+ .executeStep;
128
+ if (typeof executeStep === 'function') {
129
+ if (typeof prepareStepReplay === 'function') {
130
+ prepareStepReplay({
131
+ stepId: stepInfo.id,
132
+ stepExecId: replayStepExecId,
133
+ awaitResults: suspensionMetadata.awaitResults,
134
+ activeSuspension: replaySuspension,
135
+ });
136
+ }
137
+ if (typeof recordStepSuspendResult === 'function' &&
138
+ replaySuspension) {
139
+ recordStepSuspendResult({
140
+ stepId: stepInfo.id,
141
+ stepExecId: replayStepExecId,
142
+ suspendIndex: suspensionMetadata.suspendIndex,
143
+ suspendKey: suspensionMetadata.suspendKey,
144
+ resumeData,
145
+ });
146
+ }
147
+ else if (typeof primeStepResumeData === 'function') {
148
+ primeStepResumeData(stepInfo.id, resumeData);
149
+ }
150
+ const resumed = await executeStep(step, state, currentPath);
151
+ if (resumed) {
152
+ return {
153
+ ...resumed,
154
+ continue: async (nextResumeData) => {
155
+ const next = await resumed.continue(nextResumeData);
156
+ if (next) {
157
+ return next;
158
+ }
159
+ return deps.executeFlow(steps, state, pathPrefix, current + 1);
160
+ },
161
+ };
162
+ }
163
+ return deps.executeFlow(steps, state, pathPrefix, current + 1);
164
+ }
165
+ const task = deps.compiled.tasks[step.taskName];
166
+ if (!task) {
167
+ throw new Error(`Task "${step.taskName}" is not defined.`);
168
+ }
169
+ await deps.captureTaskOutput(task, state, resumeData);
170
+ deps.markSnapshot(stepInfo, 'completed');
171
+ deps.emit('hook:step:success', { runId: deps.runId, step: stepInfo });
172
+ return deps.executeFlow(steps, state, pathPrefix, current + 1);
173
+ },
174
+ };
175
+ }
176
+ if (step.type === StepType.Parallel) {
177
+ if (rest[0] !== 'parallel') {
178
+ return null;
179
+ }
180
+ const childPath = rest.slice(1);
181
+ const childSuspension = buildSuspensionForPath(deps, step.steps, state, childPath, [...currentPath, 'parallel'], iterationDepth, suspensionMetadata);
182
+ if (!childSuspension) {
183
+ return null;
184
+ }
185
+ const childIndex = typeof childPath[0] === 'number' ? childPath[0] : 0;
186
+ return {
187
+ ...childSuspension,
188
+ continue: async (resumeData) => {
189
+ const next = await childSuspension.continue(resumeData);
190
+ if (next) {
191
+ return next;
192
+ }
193
+ if (step.strategy === 'wait_any') {
194
+ if (childIndex + 1 < step.steps.length) {
195
+ markStepsSkipped(env, step.steps.slice(childIndex + 1), state.iterationStack ?? []);
196
+ }
197
+ return undefined;
198
+ }
199
+ return runParallelExecutor(env, step, state, currentPath, childIndex + 1);
200
+ },
201
+ };
202
+ }
203
+ if (step.type === StepType.Conditional) {
204
+ if (rest[0] !== 'branch' || typeof rest[1] !== 'number') {
205
+ return null;
206
+ }
207
+ const branchIndex = rest[1];
208
+ const childPath = rest.slice(2);
209
+ const branch = step.branches[branchIndex];
210
+ if (!branch) {
211
+ return null;
212
+ }
213
+ const branchSuspension = buildSuspensionForPath(deps, branch.steps, state, childPath, [...currentPath, 'branch', branchIndex], iterationDepth, suspensionMetadata);
214
+ if (!branchSuspension) {
215
+ return null;
216
+ }
217
+ return {
218
+ ...branchSuspension,
219
+ continue: async (resumeData) => {
220
+ const next = await branchSuspension.continue(resumeData);
221
+ if (next) {
222
+ return next;
223
+ }
224
+ return undefined;
225
+ },
226
+ };
227
+ }
228
+ if (step.type === StepType.Loop) {
229
+ if (rest.length < 1 || typeof rest[0] !== 'string') {
230
+ return null;
231
+ }
232
+ const loopToken = rest[0];
233
+ const childPath = rest.slice(1);
234
+ const targetIterationStack = state.iterationStack ?? [];
235
+ const iterationIndex = targetIterationStack[iterationDepth] ?? state.iteration?.index;
236
+ if (iterationIndex === undefined) {
237
+ return null;
238
+ }
239
+ const ancestorIterationStack = targetIterationStack.slice(0, iterationDepth);
240
+ const iterationState = {
241
+ ...state,
242
+ iterationStack: targetIterationStack,
243
+ iteration: state.iteration ??
244
+ {
245
+ item: undefined,
246
+ index: iterationIndex,
247
+ },
248
+ };
249
+ const childSuspension = buildSuspensionForPath(deps, step.steps, iterationState, childPath, [...currentPath, loopToken], iterationDepth + 1, suspensionMetadata);
250
+ if (!childSuspension) {
251
+ return null;
252
+ }
253
+ const accumulator = iterationState.accumulator ?? state.accumulator ?? undefined;
254
+ return {
255
+ ...childSuspension,
256
+ continue: async (resumeData) => {
257
+ const next = await childSuspension.continue(resumeData);
258
+ if (next) {
259
+ return next;
260
+ }
261
+ const scope = {
262
+ input: iterationState.input,
263
+ context: env.context.state,
264
+ output: iterationState.output,
265
+ iteration: iterationState.iteration ?? {
266
+ item: undefined,
267
+ index: iterationIndex,
268
+ },
269
+ accumulator,
270
+ };
271
+ const updatedAccumulator = await updateAccumulator(step, scope, accumulator);
272
+ const shouldStop = await shouldStopLoop(step, scope);
273
+ state.output = iterationState.output;
274
+ if (step.accumulate && step.name) {
275
+ state.output[step.name] = {
276
+ [step.accumulate.as]: updatedAccumulator,
277
+ };
278
+ }
279
+ if (step.accumulate) {
280
+ state.accumulator = updatedAccumulator;
281
+ }
282
+ if (shouldStop) {
283
+ return undefined;
284
+ }
285
+ const baseState = {
286
+ ...iterationState,
287
+ iterationStack: ancestorIterationStack,
288
+ accumulator: updatedAccumulator,
289
+ output: state.output,
290
+ };
291
+ return runLoopExecutor(env, step, baseState, currentPath, iterationIndex + 1);
292
+ },
293
+ };
294
+ }
295
+ return null;
296
+ }
@@ -0,0 +1,14 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ export function createDeferred() {
7
+ let resolve;
8
+ let reject;
9
+ const promise = new Promise((res, rej) => {
10
+ resolve = res;
11
+ reject = rej;
12
+ });
13
+ return { promise, resolve, reject };
14
+ }
@@ -0,0 +1,42 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ const SNAKE_CASE_REGEX = /^[a-z0-9]+(?:_[a-z0-9]+)+$/;
7
+ /**
8
+ * Verifies that names follow the snake_case convention required by the system.
9
+ *
10
+ * @param name - Candidate name that should be validated.
11
+ * @param entity - Entity type used to customize the error message.
12
+ * @throws Error when the name is not snake_case.
13
+ */
14
+ export function assertSnakeCaseName(name, entity) {
15
+ if (!SNAKE_CASE_REGEX.test(name)) {
16
+ throw new Error(`${entity} name must be snake_case with at least one underscore. Received: "${name}"`);
17
+ }
18
+ }
19
+ /**
20
+ * Checks whether the provided string is snake_case compliant.
21
+ *
22
+ * @param value - Text to evaluate.
23
+ * @returns `true` when the value is snake_case; otherwise `false`.
24
+ */
25
+ export const isSnakeCaseName = (value) => SNAKE_CASE_REGEX.test(value);
26
+ /**
27
+ * Converts arbitrary text into snake_case for use in workflow entities.
28
+ *
29
+ * @param value - Input text that should be converted.
30
+ * @returns Snake cased version of the input.
31
+ */
32
+ export function toSnakeCase(value) {
33
+ if (!value) {
34
+ return '';
35
+ }
36
+ return value
37
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
38
+ .replace(/[^a-zA-Z0-9]+/g, '_')
39
+ .replace(/_{2,}/g, '_')
40
+ .replace(/^_+|_+$/g, '')
41
+ .toLowerCase();
42
+ }
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ /**
7
+ * Resolves after the specified duration; useful for retry delays.
8
+ *
9
+ * @param durationMs - Number of milliseconds to wait before resolving.
10
+ * @returns A promise that resolves once the duration elapses.
11
+ */
12
+ export const sleep = (durationMs) => new Promise((resolve) => setTimeout(resolve, durationMs));
13
+ /**
14
+ * Wraps a promise and rejects if it does not settle within the timeout.
15
+ *
16
+ * @param promise - Operation that may take longer than the allowed timeout.
17
+ * @param timeoutMs - Maximum time in milliseconds to wait before rejecting.
18
+ * @returns The result of the original promise when it resolves in time.
19
+ * @throws Error when the timeout is exceeded.
20
+ */
21
+ export async function withTimeout(promise, timeoutMs) {
22
+ if (!timeoutMs) {
23
+ return promise;
24
+ }
25
+ return new Promise((resolve, reject) => {
26
+ const timer = setTimeout(() => {
27
+ reject(new Error(`Step execution exceeded timeout of ${timeoutMs}ms`));
28
+ }, timeoutMs);
29
+ promise.then((value) => {
30
+ clearTimeout(timer);
31
+ resolve(value);
32
+ }, (error) => {
33
+ clearTimeout(timer);
34
+ reject(error);
35
+ });
36
+ });
37
+ }
@@ -0,0 +1,98 @@
1
+ /*
2
+ * Hexabot — Fair Core License (FCL-1.0-ALv2)
3
+ * Copyright (c) 2025 Hexastack.
4
+ * Full terms: see LICENSE.md.
5
+ */
6
+ import { TASK_KIND, } from '../dsl.types';
7
+ const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
8
+ const replaceOutputTaskReferences = (source, currentTaskName, nextTaskName) => {
9
+ if (!source.includes('$output')) {
10
+ return source;
11
+ }
12
+ const escapedTaskName = escapeRegExp(currentTaskName);
13
+ const dotNotationPattern = new RegExp(`(\\$output\\.)${escapedTaskName}(?=[^a-zA-Z0-9_]|$)`, 'g');
14
+ const singleQuotePattern = new RegExp(`(\\$output\\s*\\[\\s*')${escapedTaskName}('\\s*\\])`, 'g');
15
+ const doubleQuotePattern = new RegExp(`(\\$output\\s*\\[\\s*")${escapedTaskName}("\\s*\\])`, 'g');
16
+ return source
17
+ .replace(dotNotationPattern, `$1${nextTaskName}`)
18
+ .replace(singleQuotePattern, `$1${nextTaskName}$2`)
19
+ .replace(doubleQuotePattern, `$1${nextTaskName}$2`);
20
+ };
21
+ const renameTaskInFlow = (steps, currentTaskName, nextTaskName) => {
22
+ return steps.map((step) => {
23
+ if ('do' in step) {
24
+ return {
25
+ ...step,
26
+ do: step.do === currentTaskName ? nextTaskName : step.do,
27
+ };
28
+ }
29
+ if ('parallel' in step) {
30
+ return {
31
+ ...step,
32
+ parallel: {
33
+ ...step.parallel,
34
+ steps: renameTaskInFlow(step.parallel.steps, currentTaskName, nextTaskName),
35
+ },
36
+ };
37
+ }
38
+ if ('conditional' in step) {
39
+ return {
40
+ ...step,
41
+ conditional: {
42
+ ...step.conditional,
43
+ when: step.conditional.when.map((branch) => ({
44
+ ...branch,
45
+ steps: renameTaskInFlow(branch.steps, currentTaskName, nextTaskName),
46
+ })),
47
+ },
48
+ };
49
+ }
50
+ if ('loop' in step) {
51
+ return {
52
+ ...step,
53
+ loop: {
54
+ ...step.loop,
55
+ steps: renameTaskInFlow(step.loop.steps, currentTaskName, nextTaskName),
56
+ },
57
+ };
58
+ }
59
+ return step;
60
+ });
61
+ };
62
+ const renameOutputTaskReferencesInValue = (value, currentTaskName, nextTaskName) => {
63
+ if (typeof value === 'string') {
64
+ return replaceOutputTaskReferences(value, currentTaskName, nextTaskName);
65
+ }
66
+ if (Array.isArray(value)) {
67
+ return value.map((item) => renameOutputTaskReferencesInValue(item, currentTaskName, nextTaskName));
68
+ }
69
+ if (!value || typeof value !== 'object') {
70
+ return value;
71
+ }
72
+ return Object.entries(value).reduce((acc, [key, nestedValue]) => {
73
+ acc[key] = renameOutputTaskReferencesInValue(nestedValue, currentTaskName, nextTaskName);
74
+ return acc;
75
+ }, {});
76
+ };
77
+ export const safeRenameTaskInDefinition = (definition, currentTaskName, nextTaskName) => {
78
+ if (currentTaskName === nextTaskName) {
79
+ return definition;
80
+ }
81
+ if (!Object.prototype.hasOwnProperty.call(definition.defs, currentTaskName)) {
82
+ return definition;
83
+ }
84
+ const currentDefinition = definition.defs[currentTaskName];
85
+ if (!currentDefinition || currentDefinition.kind !== TASK_KIND) {
86
+ return definition;
87
+ }
88
+ const renamedDefs = Object.entries(definition.defs).reduce((acc, [defName, def]) => {
89
+ acc[defName === currentTaskName ? nextTaskName : defName] = def;
90
+ return acc;
91
+ }, {});
92
+ const definitionWithRenamedFlow = {
93
+ ...definition,
94
+ defs: renamedDefs,
95
+ flow: renameTaskInFlow(definition.flow, currentTaskName, nextTaskName),
96
+ };
97
+ return renameOutputTaskReferencesInValue(definitionWithRenamedFlow, currentTaskName, nextTaskName);
98
+ };