@hexabot-ai/agentic 3.1.2-alpha.9 → 3.1.2-beta.1
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.
- package/README.md +13 -5
- package/dist/cjs/action/abstract-action.js +8 -3
- package/dist/cjs/dsl.types.js +2 -1
- package/dist/cjs/errors.js +41 -0
- package/dist/cjs/index.js +7 -1
- package/dist/cjs/step-executors/parallel-executor.js +238 -24
- package/dist/cjs/step-executors/skip-helpers.js +25 -1
- package/dist/cjs/step-executors/task-executor.js +77 -10
- package/dist/cjs/suspension-rebuilder.js +1 -24
- package/dist/cjs/utils/naming.js +4 -4
- package/dist/cjs/utils/timeout.js +63 -8
- package/dist/cjs/utils/workflow-definition-resources.js +73 -0
- package/dist/cjs/workflow-compiler.js +0 -2
- package/dist/cjs/workflow-runner.js +40 -17
- package/dist/esm/action/abstract-action.js +8 -3
- package/dist/esm/dsl.types.js +2 -1
- package/dist/esm/errors.js +33 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/step-executors/parallel-executor.js +239 -25
- package/dist/esm/step-executors/skip-helpers.js +23 -0
- package/dist/esm/step-executors/task-executor.js +77 -10
- package/dist/esm/suspension-rebuilder.js +1 -24
- package/dist/esm/utils/naming.js +1 -1
- package/dist/esm/utils/timeout.js +63 -8
- package/dist/esm/utils/workflow-definition-resources.js +68 -0
- package/dist/esm/workflow-compiler.js +0 -2
- package/dist/esm/workflow-runner.js +40 -17
- package/dist/types/action/abstract-action.d.ts +1 -1
- package/dist/types/action/abstract-action.d.ts.map +1 -1
- package/dist/types/action/action.types.d.ts +2 -1
- package/dist/types/action/action.types.d.ts.map +1 -1
- package/dist/types/context.d.ts +2 -1
- package/dist/types/context.d.ts.map +1 -1
- package/dist/types/dsl.types.d.ts.map +1 -1
- package/dist/types/errors.d.ts +10 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/step-executors/parallel-executor.d.ts +3 -4
- package/dist/types/step-executors/parallel-executor.d.ts.map +1 -1
- package/dist/types/step-executors/skip-helpers.d.ts +1 -0
- package/dist/types/step-executors/skip-helpers.d.ts.map +1 -1
- package/dist/types/step-executors/task-executor.d.ts.map +1 -1
- package/dist/types/step-executors/types.d.ts +9 -1
- package/dist/types/step-executors/types.d.ts.map +1 -1
- package/dist/types/suspension-rebuilder.d.ts.map +1 -1
- package/dist/types/utils/naming.d.ts +1 -0
- package/dist/types/utils/naming.d.ts.map +1 -1
- package/dist/types/utils/timeout.d.ts +4 -2
- package/dist/types/utils/timeout.d.ts.map +1 -1
- package/dist/types/utils/workflow-definition-resources.d.ts +16 -0
- package/dist/types/utils/workflow-definition-resources.d.ts.map +1 -0
- package/dist/types/workflow-compiler.d.ts.map +1 -1
- package/dist/types/workflow-event-emitter.d.ts +12 -13
- package/dist/types/workflow-event-emitter.d.ts.map +1 -1
- package/dist/types/workflow-runner.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/suspension-rebuilder.test.ts +3 -42
- package/src/__tests__/workflow-definition-resources.test.ts +97 -0
- package/src/__tests__/workflow-runner.test.ts +58 -5
- package/src/action/__tests__/action.test.ts +2 -1
- package/src/action/abstract-action.ts +9 -1
- package/src/action/action.types.ts +2 -0
- package/src/context.ts +2 -0
- package/src/dsl.types.ts +5 -1
- package/src/errors.ts +43 -0
- package/src/index.ts +10 -0
- package/src/step-executors/conditional-executor.test.ts +8 -3
- package/src/step-executors/loop-executor.test.ts +8 -3
- package/src/step-executors/parallel-executor.test.ts +191 -82
- package/src/step-executors/parallel-executor.ts +402 -34
- package/src/step-executors/skip-helpers.ts +41 -0
- package/src/step-executors/task-executor.test.ts +85 -21
- package/src/step-executors/task-executor.ts +94 -10
- package/src/step-executors/types.ts +14 -1
- package/src/suspension-rebuilder.ts +1 -53
- package/src/utils/naming.ts +1 -1
- package/src/utils/timeout.ts +78 -8
- package/src/utils/workflow-definition-resources.ts +111 -0
- package/src/workflow-compiler.ts +0 -3
- package/src/workflow-event-emitter.ts +13 -6
- package/src/workflow-runner.ts +59 -19
|
@@ -9,8 +9,6 @@ exports.parseSuspendedStepId = parseSuspendedStepId;
|
|
|
9
9
|
exports.rebuildSuspension = rebuildSuspension;
|
|
10
10
|
exports.buildSuspensionForPath = buildSuspensionForPath;
|
|
11
11
|
const loop_executor_1 = require("./step-executors/loop-executor");
|
|
12
|
-
const parallel_executor_1 = require("./step-executors/parallel-executor");
|
|
13
|
-
const skip_helpers_1 = require("./step-executors/skip-helpers");
|
|
14
12
|
const suspension_continuation_1 = require("./step-executors/suspension-continuation");
|
|
15
13
|
const workflow_event_emitter_1 = require("./workflow-event-emitter");
|
|
16
14
|
/**
|
|
@@ -171,28 +169,7 @@ function buildSuspensionForPath(deps, steps, state, targetPath, pathPrefix, iter
|
|
|
171
169
|
};
|
|
172
170
|
}
|
|
173
171
|
if (step.type === workflow_event_emitter_1.StepType.Parallel) {
|
|
174
|
-
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
const childPath = rest.slice(1);
|
|
178
|
-
const childSuspension = buildSuspensionForPath(deps, step.steps, state, childPath, [...currentPath, 'parallel'], iterationDepth, suspensionMetadata);
|
|
179
|
-
if (!childSuspension) {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
const childIndex = typeof childPath[0] === 'number' ? childPath[0] : 0;
|
|
183
|
-
return (0, suspension_continuation_1.wrapSuspensionContinuation)(childSuspension, async () => {
|
|
184
|
-
if (step.strategy === 'wait_any') {
|
|
185
|
-
if (childIndex + 1 < step.steps.length) {
|
|
186
|
-
(0, skip_helpers_1.markStepsSkipped)(env, step.steps.slice(childIndex + 1), state.iterationStack ?? []);
|
|
187
|
-
}
|
|
188
|
-
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
189
|
-
}
|
|
190
|
-
const next = await (0, parallel_executor_1.executeParallel)(env, step, state, currentPath, childIndex + 1);
|
|
191
|
-
if (next) {
|
|
192
|
-
return (0, suspension_continuation_1.wrapSuspensionContinuation)(next, () => deps.executeFlow(steps, state, pathPrefix, current + 1));
|
|
193
|
-
}
|
|
194
|
-
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
195
|
-
});
|
|
172
|
+
return null;
|
|
196
173
|
}
|
|
197
174
|
if (step.type === workflow_event_emitter_1.StepType.Conditional) {
|
|
198
175
|
if (rest[0] !== 'branch' || typeof rest[1] !== 'number') {
|
package/dist/cjs/utils/naming.js
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* Full terms: see LICENSE.md.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.isSnakeCaseName = void 0;
|
|
8
|
+
exports.isSnakeCaseName = exports.SNAKE_CASE_REGEX = void 0;
|
|
9
9
|
exports.assertSnakeCaseName = assertSnakeCaseName;
|
|
10
10
|
exports.toSnakeCase = toSnakeCase;
|
|
11
|
-
|
|
11
|
+
exports.SNAKE_CASE_REGEX = /^[a-z0-9]+(?:_[a-z0-9]+)*$/;
|
|
12
12
|
/**
|
|
13
13
|
* Verifies that names follow the snake_case convention required by the system.
|
|
14
14
|
*
|
|
@@ -17,7 +17,7 @@ const SNAKE_CASE_REGEX = /^[a-z0-9]+(?:_[a-z0-9]+)+$/;
|
|
|
17
17
|
* @throws Error when the name is not snake_case.
|
|
18
18
|
*/
|
|
19
19
|
function assertSnakeCaseName(name, entity) {
|
|
20
|
-
if (!SNAKE_CASE_REGEX.test(name)) {
|
|
20
|
+
if (!exports.SNAKE_CASE_REGEX.test(name)) {
|
|
21
21
|
throw new Error(`${entity} name must be snake_case with at least one underscore. Received: "${name}"`);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -27,7 +27,7 @@ function assertSnakeCaseName(name, entity) {
|
|
|
27
27
|
* @param value - Text to evaluate.
|
|
28
28
|
* @returns `true` when the value is snake_case; otherwise `false`.
|
|
29
29
|
*/
|
|
30
|
-
const isSnakeCaseName = (value) => SNAKE_CASE_REGEX.test(value);
|
|
30
|
+
const isSnakeCaseName = (value) => exports.SNAKE_CASE_REGEX.test(value);
|
|
31
31
|
exports.isSnakeCaseName = isSnakeCaseName;
|
|
32
32
|
/**
|
|
33
33
|
* Converts arbitrary text into snake_case for use in workflow entities.
|
|
@@ -7,35 +7,90 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.sleep = void 0;
|
|
9
9
|
exports.withTimeout = withTimeout;
|
|
10
|
+
const errors_1 = require("../errors");
|
|
10
11
|
/**
|
|
11
12
|
* Resolves after the specified duration; useful for retry delays.
|
|
12
13
|
*
|
|
13
14
|
* @param durationMs - Number of milliseconds to wait before resolving.
|
|
15
|
+
* @param signal - Optional signal used to cancel the wait.
|
|
14
16
|
* @returns A promise that resolves once the duration elapses.
|
|
15
17
|
*/
|
|
16
|
-
const sleep = (durationMs
|
|
18
|
+
const sleep = (durationMs, signal) => {
|
|
19
|
+
if (!signal) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
21
|
+
}
|
|
22
|
+
(0, errors_1.throwIfAborted)(signal);
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const timer = setTimeout(() => {
|
|
25
|
+
signal.removeEventListener('abort', onAbort);
|
|
26
|
+
resolve();
|
|
27
|
+
}, durationMs);
|
|
28
|
+
const onAbort = () => {
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
reject((0, errors_1.getAbortReason)(signal));
|
|
31
|
+
};
|
|
32
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
33
|
+
});
|
|
34
|
+
};
|
|
17
35
|
exports.sleep = sleep;
|
|
18
36
|
/**
|
|
19
37
|
* Wraps a promise and rejects if it does not settle within the timeout.
|
|
20
38
|
*
|
|
21
39
|
* @param promise - Operation that may take longer than the allowed timeout.
|
|
22
40
|
* @param timeoutMs - Maximum time in milliseconds to wait before rejecting.
|
|
41
|
+
* @param signal - Optional signal used to cancel the operation.
|
|
23
42
|
* @returns The result of the original promise when it resolves in time.
|
|
24
43
|
* @throws Error when the timeout is exceeded.
|
|
25
44
|
*/
|
|
26
|
-
async function withTimeout(promise, timeoutMs) {
|
|
27
|
-
if (!timeoutMs) {
|
|
45
|
+
async function withTimeout(promise, timeoutMs, signal) {
|
|
46
|
+
if (!timeoutMs && !signal) {
|
|
28
47
|
return promise;
|
|
29
48
|
}
|
|
30
49
|
return new Promise((resolve, reject) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
let settled = false;
|
|
51
|
+
const timer = timeoutMs
|
|
52
|
+
? setTimeout(() => {
|
|
53
|
+
settled = true;
|
|
54
|
+
signal?.removeEventListener('abort', onAbort);
|
|
55
|
+
reject(new Error(`Step execution exceeded timeout of ${timeoutMs}ms`));
|
|
56
|
+
}, timeoutMs)
|
|
57
|
+
: undefined;
|
|
58
|
+
const onAbort = () => {
|
|
59
|
+
if (settled) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
settled = true;
|
|
63
|
+
if (timer) {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
}
|
|
66
|
+
reject((0, errors_1.getAbortReason)(signal));
|
|
67
|
+
};
|
|
68
|
+
if (signal) {
|
|
69
|
+
if (signal.aborted) {
|
|
70
|
+
onAbort();
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
74
|
+
}
|
|
34
75
|
promise.then((value) => {
|
|
35
|
-
|
|
76
|
+
if (settled) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
settled = true;
|
|
80
|
+
if (timer) {
|
|
81
|
+
clearTimeout(timer);
|
|
82
|
+
}
|
|
83
|
+
signal?.removeEventListener('abort', onAbort);
|
|
36
84
|
resolve(value);
|
|
37
85
|
}, (error) => {
|
|
38
|
-
|
|
86
|
+
if (settled) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
settled = true;
|
|
90
|
+
if (timer) {
|
|
91
|
+
clearTimeout(timer);
|
|
92
|
+
}
|
|
93
|
+
signal?.removeEventListener('abort', onAbort);
|
|
39
94
|
reject(error);
|
|
40
95
|
});
|
|
41
96
|
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
4
|
+
* Copyright (c) 2026 Hexastack.
|
|
5
|
+
* Full terms: see LICENSE.md.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.remapWorkflowDefinitionResourceRefs = exports.collectWorkflowDefinitionResourceRefs = void 0;
|
|
9
|
+
const getSettingsRef = (definition, settingsKey) => {
|
|
10
|
+
const settings = definition.settings;
|
|
11
|
+
if (!settings || typeof settings !== 'object') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const value = settings[settingsKey];
|
|
15
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Collect external resource IDs referenced by workflow definition defs.
|
|
19
|
+
*/
|
|
20
|
+
const collectWorkflowDefinitionResourceRefs = (definition, descriptors) => {
|
|
21
|
+
const result = Object.fromEntries(descriptors.map((descriptor) => [descriptor.kind, new Set()]));
|
|
22
|
+
for (const def of Object.values(definition.defs ?? {})) {
|
|
23
|
+
for (const descriptor of descriptors) {
|
|
24
|
+
if (def.kind !== descriptor.kind) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const ref = getSettingsRef(def, descriptor.settingsKey);
|
|
28
|
+
if (ref) {
|
|
29
|
+
result[descriptor.kind].add(ref);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.fromEntries(Object.entries(result).map(([kind, refs]) => [kind, Array.from(refs)]));
|
|
34
|
+
};
|
|
35
|
+
exports.collectWorkflowDefinitionResourceRefs = collectWorkflowDefinitionResourceRefs;
|
|
36
|
+
/**
|
|
37
|
+
* Rewrite external resource IDs referenced by workflow definition defs.
|
|
38
|
+
*/
|
|
39
|
+
const remapWorkflowDefinitionResourceRefs = (definition, descriptors, idMaps) => {
|
|
40
|
+
let didChange = false;
|
|
41
|
+
const nextDefs = Object.fromEntries(Object.entries(definition.defs ?? {}).map(([defName, def]) => {
|
|
42
|
+
const descriptor = descriptors.find((item) => item.kind === def.kind);
|
|
43
|
+
if (!descriptor) {
|
|
44
|
+
return [defName, def];
|
|
45
|
+
}
|
|
46
|
+
const currentRef = getSettingsRef(def, descriptor.settingsKey);
|
|
47
|
+
const nextRef = currentRef
|
|
48
|
+
? idMaps[descriptor.kind]?.[currentRef]
|
|
49
|
+
: undefined;
|
|
50
|
+
if (!currentRef || !nextRef || nextRef === currentRef) {
|
|
51
|
+
return [defName, def];
|
|
52
|
+
}
|
|
53
|
+
didChange = true;
|
|
54
|
+
return [
|
|
55
|
+
defName,
|
|
56
|
+
{
|
|
57
|
+
...def,
|
|
58
|
+
settings: {
|
|
59
|
+
...(def.settings ?? {}),
|
|
60
|
+
[descriptor.settingsKey]: nextRef,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
}));
|
|
65
|
+
if (!didChange) {
|
|
66
|
+
return definition;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...definition,
|
|
70
|
+
defs: nextDefs,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
exports.remapWorkflowDefinitionResourceRefs = remapWorkflowDefinitionResourceRefs;
|
|
@@ -9,7 +9,6 @@ exports.compileWorkflow = void 0;
|
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
10
|
const base_binding_1 = require("./bindings/base-binding");
|
|
11
11
|
const dsl_types_1 = require("./dsl.types");
|
|
12
|
-
const naming_1 = require("./utils/naming");
|
|
13
12
|
const workflow_event_emitter_1 = require("./workflow-event-emitter");
|
|
14
13
|
const workflow_values_1 = require("./workflow-values");
|
|
15
14
|
/** Build a stable identifier for a step using its path and label. */
|
|
@@ -103,7 +102,6 @@ const compileTasks = (definition, options) => {
|
|
|
103
102
|
}
|
|
104
103
|
assertActionsBound(taskDefinitions, options.actions);
|
|
105
104
|
for (const [taskName, task] of Object.entries(taskDefinitions)) {
|
|
106
|
-
(0, naming_1.assertSnakeCaseName)(taskName, 'action');
|
|
107
105
|
const action = options.actions[task.action];
|
|
108
106
|
const settingsPayload = (0, workflow_values_1.mergeSettings)(defaultSettings, task.settings);
|
|
109
107
|
const parsedSettings = action.parseSettings(settingsPayload);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
8
|
exports.WorkflowRunner = void 0;
|
|
9
9
|
const context_1 = require("./context");
|
|
10
|
+
const errors_1 = require("./errors");
|
|
10
11
|
const runner_runtime_control_1 = require("./runner-runtime-control");
|
|
11
12
|
const conditional_executor_1 = require("./step-executors/conditional-executor");
|
|
12
13
|
const loop_executor_1 = require("./step-executors/loop-executor");
|
|
@@ -122,7 +123,8 @@ class WorkflowRunner {
|
|
|
122
123
|
if (!state) {
|
|
123
124
|
throw new Error('Workflow state not initialized.');
|
|
124
125
|
}
|
|
125
|
-
|
|
126
|
+
const env = this.createExecutorEnv();
|
|
127
|
+
return this.runExecution(() => this.executeFlow(this.compiled.flow, state, [], 0, env));
|
|
126
128
|
}
|
|
127
129
|
/**
|
|
128
130
|
* Resume a previously suspended workflow using the supplied resume data.
|
|
@@ -316,7 +318,7 @@ class WorkflowRunner {
|
|
|
316
318
|
const mergedContext = existing?.context || update.context
|
|
317
319
|
? { ...existing?.context, ...update.context }
|
|
318
320
|
: undefined;
|
|
319
|
-
|
|
321
|
+
const nextRecord = {
|
|
320
322
|
...base,
|
|
321
323
|
...update,
|
|
322
324
|
id: step.id,
|
|
@@ -324,6 +326,12 @@ class WorkflowRunner {
|
|
|
324
326
|
status: update.status ?? base.status,
|
|
325
327
|
context: mergedContext,
|
|
326
328
|
};
|
|
329
|
+
this.stepLog[step.id] = nextRecord;
|
|
330
|
+
return {
|
|
331
|
+
...nextRecord,
|
|
332
|
+
context: nextRecord.context ? { ...nextRecord.context } : undefined,
|
|
333
|
+
error: nextRecord.error ? { ...nextRecord.error } : undefined,
|
|
334
|
+
};
|
|
327
335
|
}
|
|
328
336
|
/**
|
|
329
337
|
* Construct the environment object passed to step executors.
|
|
@@ -331,21 +339,28 @@ class WorkflowRunner {
|
|
|
331
339
|
* @returns A step executor environment bound to this runner.
|
|
332
340
|
* @throws When the workflow context is missing.
|
|
333
341
|
*/
|
|
334
|
-
createExecutorEnv() {
|
|
335
|
-
|
|
342
|
+
createExecutorEnv(overrides = {}) {
|
|
343
|
+
const context = overrides.context ?? this.context;
|
|
344
|
+
if (!context) {
|
|
336
345
|
throw new Error('Workflow context is not attached.');
|
|
337
346
|
}
|
|
338
|
-
|
|
347
|
+
const signal = overrides.signal ?? new AbortController().signal;
|
|
348
|
+
const setCurrentStep = overrides.setCurrentStep ??
|
|
349
|
+
((step) => {
|
|
350
|
+
this.currentStep = step;
|
|
351
|
+
});
|
|
352
|
+
const captureTaskOutput = overrides.captureTaskOutput ??
|
|
353
|
+
((task, state, result) => this.captureTaskOutput(task, state, result));
|
|
354
|
+
const executorEnv = {
|
|
339
355
|
compiled: this.compiled,
|
|
340
|
-
context
|
|
356
|
+
context,
|
|
357
|
+
signal,
|
|
341
358
|
runId: this.runId,
|
|
342
359
|
buildInstanceStepInfo: (step, iterationStack) => this.buildInstanceStepInfo(step, iterationStack),
|
|
343
360
|
markSnapshot: (step, status, reason) => this.markSnapshot(step, status, reason),
|
|
344
361
|
recordStepExecution: (step, update) => this.recordStepExecution(step, update),
|
|
345
362
|
emit: (event, payload) => this.emit(event, payload),
|
|
346
|
-
setCurrentStep
|
|
347
|
-
this.currentStep = step;
|
|
348
|
-
},
|
|
363
|
+
setCurrentStep,
|
|
349
364
|
beginStepExecution: (stepId) => {
|
|
350
365
|
if (!this.runtimeControl) {
|
|
351
366
|
return `${stepId}#1`;
|
|
@@ -370,10 +385,17 @@ class WorkflowRunner {
|
|
|
370
385
|
recordStepSuspendResult: (params) => {
|
|
371
386
|
this.runtimeControl?.recordStepSuspendResult(params);
|
|
372
387
|
},
|
|
373
|
-
captureTaskOutput
|
|
374
|
-
executeFlow: (steps, state, path, startIndex) => this.executeFlow(steps, state, path, startIndex),
|
|
375
|
-
executeStep: (step, state, path) => this.executeStep(step, state, path),
|
|
388
|
+
captureTaskOutput,
|
|
389
|
+
executeFlow: (steps, state, path, startIndex) => this.executeFlow(steps, state, path, startIndex, executorEnv),
|
|
390
|
+
executeStep: (step, state, path) => this.executeStep(step, state, path, executorEnv),
|
|
391
|
+
fork: (forkOverrides) => this.createExecutorEnv({
|
|
392
|
+
context: forkOverrides.context ?? context,
|
|
393
|
+
signal: forkOverrides.signal ?? signal,
|
|
394
|
+
setCurrentStep: forkOverrides.setCurrentStep ?? setCurrentStep,
|
|
395
|
+
captureTaskOutput: forkOverrides.captureTaskOutput ?? captureTaskOutput,
|
|
396
|
+
}),
|
|
376
397
|
};
|
|
398
|
+
return executorEnv;
|
|
377
399
|
}
|
|
378
400
|
/**
|
|
379
401
|
* Build dependencies used to reconstruct a suspension from persisted state.
|
|
@@ -402,14 +424,15 @@ class WorkflowRunner {
|
|
|
402
424
|
* @param startIndex Index to resume from within the flow.
|
|
403
425
|
* @returns A suspension if execution pauses, otherwise void.
|
|
404
426
|
*/
|
|
405
|
-
async executeFlow(steps, state, path, startIndex = 0) {
|
|
427
|
+
async executeFlow(steps, state, path, startIndex = 0, env = this.createExecutorEnv()) {
|
|
406
428
|
// Walk the flow sequentially; if a step suspends, wrap its continuation so we resume at the same index.
|
|
407
429
|
for (let index = startIndex; index < steps.length; index += 1) {
|
|
430
|
+
(0, errors_1.throwIfAborted)(env.signal);
|
|
408
431
|
const step = steps[index];
|
|
409
432
|
const stepPath = [...path, index];
|
|
410
|
-
const suspension = await this.executeStep(step, state, stepPath);
|
|
433
|
+
const suspension = await this.executeStep(step, state, stepPath, env);
|
|
411
434
|
if (suspension) {
|
|
412
|
-
return (0, suspension_continuation_1.wrapSuspensionContinuation)(suspension, () => this.executeFlow(steps, state, path, index + 1));
|
|
435
|
+
return (0, suspension_continuation_1.wrapSuspensionContinuation)(suspension, () => this.executeFlow(steps, state, path, index + 1, env));
|
|
413
436
|
}
|
|
414
437
|
}
|
|
415
438
|
return undefined;
|
|
@@ -422,8 +445,8 @@ class WorkflowRunner {
|
|
|
422
445
|
* @param path Tokens describing the location of the step in the workflow.
|
|
423
446
|
* @returns A suspension if the step pauses execution, otherwise void.
|
|
424
447
|
*/
|
|
425
|
-
async executeStep(step, state, path) {
|
|
426
|
-
|
|
448
|
+
async executeStep(step, state, path, env = this.createExecutorEnv()) {
|
|
449
|
+
(0, errors_1.throwIfAborted)(env.signal);
|
|
427
450
|
switch (step.type) {
|
|
428
451
|
case workflow_event_emitter_1.StepType.Task:
|
|
429
452
|
return (0, task_executor_1.executeTaskStep)(env, step, state, path);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { BaseSettingsSchema } from '../dsl.types';
|
|
8
|
+
import { throwIfAborted } from '../errors';
|
|
8
9
|
import { assertSnakeCaseName } from '../utils/naming';
|
|
9
10
|
import { sleep, withTimeout } from '../utils/timeout';
|
|
10
11
|
const BASE_SETTINGS_KEYS = Object.keys(BaseSettingsSchema.shape);
|
|
@@ -64,7 +65,7 @@ export class AbstractAction {
|
|
|
64
65
|
.parse(payload ?? {});
|
|
65
66
|
return (settings ?? {});
|
|
66
67
|
}
|
|
67
|
-
async run(payload, context, settings, bindings) {
|
|
68
|
+
async run(payload, context, settings, bindings, signal) {
|
|
68
69
|
const input = this.parseInput(payload);
|
|
69
70
|
const parsedSettings = this.parseSettings(settings);
|
|
70
71
|
const parsedBindings = (bindings ?? {});
|
|
@@ -86,13 +87,17 @@ export class AbstractAction {
|
|
|
86
87
|
const jitter = retrySettings.jitter;
|
|
87
88
|
const multiplier = retrySettings.multiplier;
|
|
88
89
|
while (attempt < maxAttempts) {
|
|
90
|
+
if (signal) {
|
|
91
|
+
throwIfAborted(signal);
|
|
92
|
+
}
|
|
89
93
|
try {
|
|
90
94
|
const result = await withTimeout(this.execute({
|
|
91
95
|
input,
|
|
92
96
|
context,
|
|
93
97
|
settings: parsedSettings,
|
|
94
98
|
bindings: parsedBindings,
|
|
95
|
-
|
|
99
|
+
signal: signal ?? new AbortController().signal,
|
|
100
|
+
}), timeoutMs, signal);
|
|
96
101
|
return this.parseOutput(result);
|
|
97
102
|
}
|
|
98
103
|
catch (error) {
|
|
@@ -108,7 +113,7 @@ export class AbstractAction {
|
|
|
108
113
|
const jitterFactor = jitter > 0 ? 1 + (Math.random() * 2 - 1) * jitter : 1;
|
|
109
114
|
const jitteredDelay = Math.max(0, Math.round(delay * jitterFactor));
|
|
110
115
|
if (jitteredDelay > 0) {
|
|
111
|
-
await sleep(jitteredDelay);
|
|
116
|
+
await sleep(jitteredDelay, signal);
|
|
112
117
|
}
|
|
113
118
|
}
|
|
114
119
|
const nextDelay = currentDelay * multiplier;
|
package/dist/esm/dsl.types.js
CHANGED
|
@@ -7,6 +7,7 @@ import jsonata from 'jsonata';
|
|
|
7
7
|
import { parse as parseYaml } from 'yaml';
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import { validateAndResolveBindings } from './bindings/base-binding';
|
|
10
|
+
import { SNAKE_CASE_REGEX } from './utils/naming';
|
|
10
11
|
export const ExpressionStringSchema = z
|
|
11
12
|
.string()
|
|
12
13
|
.regex(/^=/, 'Expression strings must start with "="')
|
|
@@ -227,7 +228,7 @@ export const WorkflowDefinitionSchema = z.strictObject({
|
|
|
227
228
|
inputs: InputsSchema.optional(),
|
|
228
229
|
context: z.record(z.string(), JsonValueSchema).optional(),
|
|
229
230
|
defaults: DefaultsSchema.optional(),
|
|
230
|
-
defs: z.record(z.string(), DefDefinitionSchema),
|
|
231
|
+
defs: z.record(z.string().regex(SNAKE_CASE_REGEX, 'Name must be snake_case'), DefDefinitionSchema),
|
|
231
232
|
flow: z.array(FlowStepSchema),
|
|
232
233
|
outputs: z.record(z.string(), ExpressionStringSchema),
|
|
233
234
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
export class WorkflowCancellationError extends Error {
|
|
7
|
+
constructor(message = 'Workflow execution was cancelled.') {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'WorkflowCancellationError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class ParallelSuspensionError extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super('workflow.suspend() is not supported inside a parallel block.');
|
|
15
|
+
this.name = 'ParallelSuspensionError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export const isWorkflowCancellationError = (error) => error instanceof WorkflowCancellationError ||
|
|
19
|
+
(error instanceof Error && error.name === 'WorkflowCancellationError');
|
|
20
|
+
export const getAbortReason = (signal) => {
|
|
21
|
+
if (signal.reason instanceof Error) {
|
|
22
|
+
return signal.reason;
|
|
23
|
+
}
|
|
24
|
+
if (signal.reason !== undefined) {
|
|
25
|
+
return new WorkflowCancellationError(String(signal.reason));
|
|
26
|
+
}
|
|
27
|
+
return new WorkflowCancellationError();
|
|
28
|
+
};
|
|
29
|
+
export const throwIfAborted = (signal) => {
|
|
30
|
+
if (signal.aborted) {
|
|
31
|
+
throw getAbortReason(signal);
|
|
32
|
+
}
|
|
33
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -10,7 +10,9 @@ export * from './dsl.types';
|
|
|
10
10
|
export { compileWorkflow, Workflow, WorkflowEventEmitter, WorkflowRunner, } from './workflow';
|
|
11
11
|
export { StepType } from './workflow-event-emitter';
|
|
12
12
|
export { NonDeterministicWorkflowError } from './runner-runtime-control';
|
|
13
|
+
export { ParallelSuspensionError, WorkflowCancellationError } from './errors';
|
|
13
14
|
export { compileValue, evaluateMapping, evaluateValue, mergeSettings, } from './workflow-values';
|
|
14
15
|
export { createDeferred } from './utils/deferred';
|
|
15
16
|
export { assertSnakeCaseName, isSnakeCaseName, toSnakeCase, } from './utils/naming';
|
|
16
17
|
export { sleep, withTimeout } from './utils/timeout';
|
|
18
|
+
export { collectWorkflowDefinitionResourceRefs, remapWorkflowDefinitionResourceRefs, } from './utils/workflow-definition-resources';
|