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