@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,504 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ActionSnapshot, BaseWorkflowContext } from './context';
|
|
8
|
+
import {
|
|
9
|
+
executeLoop as runLoopExecutor,
|
|
10
|
+
shouldStopLoop,
|
|
11
|
+
updateAccumulator,
|
|
12
|
+
} from './step-executors/loop-executor';
|
|
13
|
+
import { executeParallel as runParallelExecutor } from './step-executors/parallel-executor';
|
|
14
|
+
import { markStepsSkipped } from './step-executors/skip-helpers';
|
|
15
|
+
import type { StepExecutorEnv } from './step-executors/types';
|
|
16
|
+
import {
|
|
17
|
+
StepType,
|
|
18
|
+
type StepInfo,
|
|
19
|
+
type WorkflowEventMap,
|
|
20
|
+
} from './workflow-event-emitter';
|
|
21
|
+
import type {
|
|
22
|
+
CompiledStep,
|
|
23
|
+
CompiledTask,
|
|
24
|
+
CompiledWorkflow,
|
|
25
|
+
EvaluationScope,
|
|
26
|
+
ExecutionState,
|
|
27
|
+
Suspension,
|
|
28
|
+
} from './workflow-types';
|
|
29
|
+
|
|
30
|
+
export type SuspensionRebuilderDeps = {
|
|
31
|
+
compiled: CompiledWorkflow;
|
|
32
|
+
context?: BaseWorkflowContext;
|
|
33
|
+
runId?: string;
|
|
34
|
+
createExecutorEnv: () => StepExecutorEnv;
|
|
35
|
+
buildInstanceStepInfo: (
|
|
36
|
+
step: CompiledStep,
|
|
37
|
+
iterationStack: number[],
|
|
38
|
+
) => StepInfo;
|
|
39
|
+
captureTaskOutput: (
|
|
40
|
+
task: CompiledTask,
|
|
41
|
+
state: ExecutionState,
|
|
42
|
+
result: unknown,
|
|
43
|
+
) => Promise<void>;
|
|
44
|
+
markSnapshot: (
|
|
45
|
+
step: StepInfo,
|
|
46
|
+
status: ActionSnapshot['status'],
|
|
47
|
+
reason?: string,
|
|
48
|
+
) => void;
|
|
49
|
+
emit: <K extends keyof WorkflowEventMap>(
|
|
50
|
+
event: K,
|
|
51
|
+
payload: WorkflowEventMap[K],
|
|
52
|
+
) => void;
|
|
53
|
+
executeFlow: (
|
|
54
|
+
steps: CompiledStep[],
|
|
55
|
+
state: ExecutionState,
|
|
56
|
+
path: Array<number | string>,
|
|
57
|
+
startIndex?: number,
|
|
58
|
+
) => Promise<Suspension | void>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type RebuildSuspensionMetadata = {
|
|
62
|
+
stepExecId?: string;
|
|
63
|
+
suspendIndex?: number;
|
|
64
|
+
suspendKey?: string;
|
|
65
|
+
reason?: string;
|
|
66
|
+
awaitResults?: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parse a suspended step id into its execution path and iteration stack.
|
|
71
|
+
*
|
|
72
|
+
* @param stepId The serialized step identifier stored in snapshots.
|
|
73
|
+
* @returns The decoded path tokens and iteration stack.
|
|
74
|
+
*/
|
|
75
|
+
export function parseSuspendedStepId(stepId: string): {
|
|
76
|
+
path: Array<number | string>;
|
|
77
|
+
iterationStack: number[];
|
|
78
|
+
} {
|
|
79
|
+
const iterationMatch = stepId.match(/^(.*)\[(.+)\]$/);
|
|
80
|
+
const baseId = iterationMatch ? iterationMatch[1] : stepId;
|
|
81
|
+
const iterationStack = iterationMatch
|
|
82
|
+
? iterationMatch[2]
|
|
83
|
+
.split('.')
|
|
84
|
+
.map((token) => Number.parseInt(token, 10))
|
|
85
|
+
.filter((value) => !Number.isNaN(value))
|
|
86
|
+
: [];
|
|
87
|
+
const [pathPart] = baseId.split(':');
|
|
88
|
+
const path =
|
|
89
|
+
pathPart.length === 0 || pathPart === 'root'
|
|
90
|
+
? []
|
|
91
|
+
: pathPart.split('.').map((token) => {
|
|
92
|
+
const numeric = Number.parseInt(token, 10);
|
|
93
|
+
|
|
94
|
+
return Number.isNaN(numeric) ? token : numeric;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { path, iterationStack };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Rebuild a suspension object based on persisted state and step identifiers.
|
|
102
|
+
*
|
|
103
|
+
* @param deps Dependencies needed to reconstruct executors and events.
|
|
104
|
+
* @param param0 Persisted state and metadata about the suspended step.
|
|
105
|
+
* @param param0.state Execution state captured at suspension time.
|
|
106
|
+
* @param param0.stepId Serialized step id used to locate the suspension point.
|
|
107
|
+
* @param param0.reason Optional reason supplied when the workflow suspended.
|
|
108
|
+
* @param param0.data Arbitrary payload provided with the suspension.
|
|
109
|
+
* @returns A suspension ready to be resumed, or null if reconstruction fails.
|
|
110
|
+
*/
|
|
111
|
+
export function rebuildSuspension(
|
|
112
|
+
deps: SuspensionRebuilderDeps,
|
|
113
|
+
{
|
|
114
|
+
state,
|
|
115
|
+
stepId,
|
|
116
|
+
reason,
|
|
117
|
+
data,
|
|
118
|
+
stepExecId,
|
|
119
|
+
suspendIndex,
|
|
120
|
+
suspendKey,
|
|
121
|
+
awaitResults,
|
|
122
|
+
}: {
|
|
123
|
+
state?: ExecutionState;
|
|
124
|
+
stepId: string;
|
|
125
|
+
reason?: string;
|
|
126
|
+
data?: unknown;
|
|
127
|
+
stepExecId?: string;
|
|
128
|
+
suspendIndex?: number;
|
|
129
|
+
suspendKey?: string;
|
|
130
|
+
awaitResults?: Record<string, unknown>;
|
|
131
|
+
},
|
|
132
|
+
): Suspension | null {
|
|
133
|
+
if (!state) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const { path, iterationStack } = parseSuspendedStepId(stepId);
|
|
138
|
+
const executionState: ExecutionState = {
|
|
139
|
+
...state,
|
|
140
|
+
iterationStack:
|
|
141
|
+
iterationStack.length > 0 ? iterationStack : (state.iterationStack ?? []),
|
|
142
|
+
};
|
|
143
|
+
const suspensionMetadata: RebuildSuspensionMetadata = {
|
|
144
|
+
stepExecId,
|
|
145
|
+
suspendIndex,
|
|
146
|
+
suspendKey,
|
|
147
|
+
reason,
|
|
148
|
+
awaitResults,
|
|
149
|
+
};
|
|
150
|
+
const suspension = buildSuspensionForPath(
|
|
151
|
+
deps,
|
|
152
|
+
deps.compiled.flow,
|
|
153
|
+
executionState,
|
|
154
|
+
path,
|
|
155
|
+
[],
|
|
156
|
+
0,
|
|
157
|
+
suspensionMetadata,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (!suspension) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
...suspension,
|
|
166
|
+
reason,
|
|
167
|
+
data,
|
|
168
|
+
stepExecId,
|
|
169
|
+
suspendIndex,
|
|
170
|
+
suspendKey,
|
|
171
|
+
awaitResults,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Walk the compiled flow to rebuild the continuation matching a target path.
|
|
177
|
+
*
|
|
178
|
+
* @param deps Dependencies for executor creation and event emission.
|
|
179
|
+
* @param steps The steps to search for the suspended step.
|
|
180
|
+
* @param state Mutable execution state to thread through the traversal.
|
|
181
|
+
* @param targetPath Path tokens identifying the suspended step location.
|
|
182
|
+
* @param pathPrefix Prefix accumulated while descending the tree.
|
|
183
|
+
* @param iterationDepth Current loop depth to match iteration stacks.
|
|
184
|
+
* @returns A rebuilt suspension or null if the path cannot be matched.
|
|
185
|
+
*/
|
|
186
|
+
export function buildSuspensionForPath(
|
|
187
|
+
deps: SuspensionRebuilderDeps,
|
|
188
|
+
steps: CompiledStep[],
|
|
189
|
+
state: ExecutionState,
|
|
190
|
+
targetPath: Array<number | string>,
|
|
191
|
+
pathPrefix: Array<number | string>,
|
|
192
|
+
iterationDepth = 0,
|
|
193
|
+
suspensionMetadata: RebuildSuspensionMetadata = {},
|
|
194
|
+
): Suspension | null {
|
|
195
|
+
if (!deps.context) {
|
|
196
|
+
throw new Error('Workflow context is not attached.');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const env = deps.createExecutorEnv();
|
|
200
|
+
if (targetPath.length === 0) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const [current, ...rest] = targetPath;
|
|
205
|
+
if (typeof current !== 'number') {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const step = steps[current];
|
|
210
|
+
const currentPath = [...pathPrefix, current];
|
|
211
|
+
|
|
212
|
+
if (!step) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (rest.length === 0) {
|
|
217
|
+
if (step.type !== StepType.Task) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const stepInfo = deps.buildInstanceStepInfo(step, state.iterationStack);
|
|
222
|
+
const replayStepExecId =
|
|
223
|
+
suspensionMetadata.stepExecId ?? `${stepInfo.id}#1`;
|
|
224
|
+
const replaySuspension =
|
|
225
|
+
suspensionMetadata.suspendIndex !== undefined ||
|
|
226
|
+
suspensionMetadata.suspendKey !== undefined
|
|
227
|
+
? {
|
|
228
|
+
suspendIndex: suspensionMetadata.suspendIndex,
|
|
229
|
+
suspendKey: suspensionMetadata.suspendKey,
|
|
230
|
+
reason: suspensionMetadata.reason,
|
|
231
|
+
}
|
|
232
|
+
: undefined;
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
step: stepInfo,
|
|
236
|
+
continue: async (resumeData: unknown) => {
|
|
237
|
+
const executorEnv = deps.createExecutorEnv();
|
|
238
|
+
const prepareStepReplay = (executorEnv as Partial<StepExecutorEnv>)
|
|
239
|
+
.prepareStepReplay;
|
|
240
|
+
const recordStepSuspendResult = (
|
|
241
|
+
executorEnv as Partial<StepExecutorEnv>
|
|
242
|
+
).recordStepSuspendResult;
|
|
243
|
+
const primeStepResumeData = (executorEnv as Partial<StepExecutorEnv>)
|
|
244
|
+
.primeStepResumeData;
|
|
245
|
+
const executeStep = (executorEnv as Partial<StepExecutorEnv>)
|
|
246
|
+
.executeStep;
|
|
247
|
+
|
|
248
|
+
if (typeof executeStep === 'function') {
|
|
249
|
+
if (typeof prepareStepReplay === 'function') {
|
|
250
|
+
prepareStepReplay({
|
|
251
|
+
stepId: stepInfo.id,
|
|
252
|
+
stepExecId: replayStepExecId,
|
|
253
|
+
awaitResults: suspensionMetadata.awaitResults,
|
|
254
|
+
activeSuspension: replaySuspension,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (
|
|
259
|
+
typeof recordStepSuspendResult === 'function' &&
|
|
260
|
+
replaySuspension
|
|
261
|
+
) {
|
|
262
|
+
recordStepSuspendResult({
|
|
263
|
+
stepId: stepInfo.id,
|
|
264
|
+
stepExecId: replayStepExecId,
|
|
265
|
+
suspendIndex: suspensionMetadata.suspendIndex,
|
|
266
|
+
suspendKey: suspensionMetadata.suspendKey,
|
|
267
|
+
resumeData,
|
|
268
|
+
});
|
|
269
|
+
} else if (typeof primeStepResumeData === 'function') {
|
|
270
|
+
primeStepResumeData(stepInfo.id, resumeData);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const resumed = await executeStep(step, state, currentPath);
|
|
274
|
+
if (resumed) {
|
|
275
|
+
return {
|
|
276
|
+
...resumed,
|
|
277
|
+
continue: async (nextResumeData: unknown) => {
|
|
278
|
+
const next = await resumed.continue(nextResumeData);
|
|
279
|
+
if (next) {
|
|
280
|
+
return next;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
284
|
+
},
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const task = deps.compiled.tasks[step.taskName];
|
|
292
|
+
if (!task) {
|
|
293
|
+
throw new Error(`Task "${step.taskName}" is not defined.`);
|
|
294
|
+
}
|
|
295
|
+
await deps.captureTaskOutput(task, state, resumeData);
|
|
296
|
+
deps.markSnapshot(stepInfo, 'completed');
|
|
297
|
+
deps.emit('hook:step:success', { runId: deps.runId, step: stepInfo });
|
|
298
|
+
|
|
299
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (step.type === StepType.Parallel) {
|
|
305
|
+
if (rest[0] !== 'parallel') {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const childPath = rest.slice(1);
|
|
310
|
+
const childSuspension = buildSuspensionForPath(
|
|
311
|
+
deps,
|
|
312
|
+
step.steps,
|
|
313
|
+
state,
|
|
314
|
+
childPath,
|
|
315
|
+
[...currentPath, 'parallel'],
|
|
316
|
+
iterationDepth,
|
|
317
|
+
suspensionMetadata,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
if (!childSuspension) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const childIndex =
|
|
325
|
+
typeof childPath[0] === 'number' ? (childPath[0] as number) : 0;
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
...childSuspension,
|
|
329
|
+
continue: async (resumeData: unknown) => {
|
|
330
|
+
const next = await childSuspension.continue(resumeData);
|
|
331
|
+
if (next) {
|
|
332
|
+
return next;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (step.strategy === 'wait_any') {
|
|
336
|
+
if (childIndex + 1 < step.steps.length) {
|
|
337
|
+
markStepsSkipped(
|
|
338
|
+
env,
|
|
339
|
+
step.steps.slice(childIndex + 1),
|
|
340
|
+
state.iterationStack ?? [],
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return runParallelExecutor(
|
|
348
|
+
env,
|
|
349
|
+
step,
|
|
350
|
+
state,
|
|
351
|
+
currentPath,
|
|
352
|
+
childIndex + 1,
|
|
353
|
+
);
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (step.type === StepType.Conditional) {
|
|
359
|
+
if (rest[0] !== 'branch' || typeof rest[1] !== 'number') {
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const branchIndex = rest[1] as number;
|
|
364
|
+
const childPath = rest.slice(2);
|
|
365
|
+
const branch = step.branches[branchIndex];
|
|
366
|
+
|
|
367
|
+
if (!branch) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const branchSuspension = buildSuspensionForPath(
|
|
372
|
+
deps,
|
|
373
|
+
branch.steps,
|
|
374
|
+
state,
|
|
375
|
+
childPath,
|
|
376
|
+
[...currentPath, 'branch', branchIndex],
|
|
377
|
+
iterationDepth,
|
|
378
|
+
suspensionMetadata,
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
if (!branchSuspension) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
...branchSuspension,
|
|
387
|
+
continue: async (resumeData: unknown) => {
|
|
388
|
+
const next = await branchSuspension.continue(resumeData);
|
|
389
|
+
if (next) {
|
|
390
|
+
return next;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return undefined;
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (step.type === StepType.Loop) {
|
|
399
|
+
if (rest.length < 1 || typeof rest[0] !== 'string') {
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const loopToken = rest[0] as string;
|
|
404
|
+
const childPath = rest.slice(1);
|
|
405
|
+
const targetIterationStack = state.iterationStack ?? [];
|
|
406
|
+
const iterationIndex =
|
|
407
|
+
targetIterationStack[iterationDepth] ?? state.iteration?.index;
|
|
408
|
+
|
|
409
|
+
if (iterationIndex === undefined) {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const ancestorIterationStack = targetIterationStack.slice(
|
|
414
|
+
0,
|
|
415
|
+
iterationDepth,
|
|
416
|
+
);
|
|
417
|
+
const iterationState: ExecutionState = {
|
|
418
|
+
...state,
|
|
419
|
+
iterationStack: targetIterationStack,
|
|
420
|
+
iteration:
|
|
421
|
+
state.iteration ??
|
|
422
|
+
({
|
|
423
|
+
item: undefined,
|
|
424
|
+
index: iterationIndex,
|
|
425
|
+
} satisfies ExecutionState['iteration']),
|
|
426
|
+
};
|
|
427
|
+
const childSuspension = buildSuspensionForPath(
|
|
428
|
+
deps,
|
|
429
|
+
step.steps,
|
|
430
|
+
iterationState,
|
|
431
|
+
childPath,
|
|
432
|
+
[...currentPath, loopToken],
|
|
433
|
+
iterationDepth + 1,
|
|
434
|
+
suspensionMetadata,
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
if (!childSuspension) {
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const accumulator =
|
|
442
|
+
iterationState.accumulator ?? state.accumulator ?? undefined;
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
...childSuspension,
|
|
446
|
+
continue: async (resumeData: unknown) => {
|
|
447
|
+
const next = await childSuspension.continue(resumeData);
|
|
448
|
+
if (next) {
|
|
449
|
+
return next;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const scope: EvaluationScope = {
|
|
453
|
+
input: iterationState.input,
|
|
454
|
+
context: env.context.state,
|
|
455
|
+
output: iterationState.output,
|
|
456
|
+
iteration: iterationState.iteration ?? {
|
|
457
|
+
item: undefined,
|
|
458
|
+
index: iterationIndex,
|
|
459
|
+
},
|
|
460
|
+
accumulator,
|
|
461
|
+
};
|
|
462
|
+
const updatedAccumulator = await updateAccumulator(
|
|
463
|
+
step,
|
|
464
|
+
scope,
|
|
465
|
+
accumulator,
|
|
466
|
+
);
|
|
467
|
+
const shouldStop = await shouldStopLoop(step, scope);
|
|
468
|
+
|
|
469
|
+
state.output = iterationState.output;
|
|
470
|
+
|
|
471
|
+
if (step.accumulate && step.name) {
|
|
472
|
+
state.output[step.name] = {
|
|
473
|
+
[step.accumulate.as]: updatedAccumulator,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (step.accumulate) {
|
|
478
|
+
state.accumulator = updatedAccumulator;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (shouldStop) {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const baseState: ExecutionState = {
|
|
486
|
+
...iterationState,
|
|
487
|
+
iterationStack: ancestorIterationStack,
|
|
488
|
+
accumulator: updatedAccumulator,
|
|
489
|
+
output: state.output,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
return runLoopExecutor(
|
|
493
|
+
env,
|
|
494
|
+
step,
|
|
495
|
+
baseState,
|
|
496
|
+
currentPath,
|
|
497
|
+
iterationIndex + 1,
|
|
498
|
+
);
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type Deferred<T> = {
|
|
8
|
+
promise: Promise<T>;
|
|
9
|
+
resolve: (value: T) => void;
|
|
10
|
+
reject: (reason?: unknown) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createDeferred<T>(): Deferred<T> {
|
|
14
|
+
let resolve!: (value: T) => void;
|
|
15
|
+
let reject!: (reason?: unknown) => void;
|
|
16
|
+
|
|
17
|
+
const promise = new Promise<T>((res, rej) => {
|
|
18
|
+
resolve = res;
|
|
19
|
+
reject = rej;
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return { promise, resolve, reject };
|
|
23
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const SNAKE_CASE_REGEX = /^[a-z0-9]+(?:_[a-z0-9]+)+$/;
|
|
8
|
+
|
|
9
|
+
export type WorkflowEntity = 'action' | 'workflow' | 'predicate';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Verifies that names follow the snake_case convention required by the system.
|
|
13
|
+
*
|
|
14
|
+
* @param name - Candidate name that should be validated.
|
|
15
|
+
* @param entity - Entity type used to customize the error message.
|
|
16
|
+
* @throws Error when the name is not snake_case.
|
|
17
|
+
*/
|
|
18
|
+
export function assertSnakeCaseName(
|
|
19
|
+
name: string,
|
|
20
|
+
entity: WorkflowEntity,
|
|
21
|
+
): void {
|
|
22
|
+
if (!SNAKE_CASE_REGEX.test(name)) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`${entity} name must be snake_case with at least one underscore. Received: "${name}"`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Checks whether the provided string is snake_case compliant.
|
|
31
|
+
*
|
|
32
|
+
* @param value - Text to evaluate.
|
|
33
|
+
* @returns `true` when the value is snake_case; otherwise `false`.
|
|
34
|
+
*/
|
|
35
|
+
export const isSnakeCaseName = (value: string): boolean =>
|
|
36
|
+
SNAKE_CASE_REGEX.test(value);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Converts arbitrary text into snake_case for use in workflow entities.
|
|
40
|
+
*
|
|
41
|
+
* @param value - Input text that should be converted.
|
|
42
|
+
* @returns Snake cased version of the input.
|
|
43
|
+
*/
|
|
44
|
+
export function toSnakeCase(value: string): string {
|
|
45
|
+
if (!value) {
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return value
|
|
50
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
51
|
+
.replace(/[^a-zA-Z0-9]+/g, '_')
|
|
52
|
+
.replace(/_{2,}/g, '_')
|
|
53
|
+
.replace(/^_+|_+$/g, '')
|
|
54
|
+
.toLowerCase();
|
|
55
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2025 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves after the specified duration; useful for retry delays.
|
|
9
|
+
*
|
|
10
|
+
* @param durationMs - Number of milliseconds to wait before resolving.
|
|
11
|
+
* @returns A promise that resolves once the duration elapses.
|
|
12
|
+
*/
|
|
13
|
+
export const sleep = (durationMs: number): Promise<void> =>
|
|
14
|
+
new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Wraps a promise and rejects if it does not settle within the timeout.
|
|
18
|
+
*
|
|
19
|
+
* @param promise - Operation that may take longer than the allowed timeout.
|
|
20
|
+
* @param timeoutMs - Maximum time in milliseconds to wait before rejecting.
|
|
21
|
+
* @returns The result of the original promise when it resolves in time.
|
|
22
|
+
* @throws Error when the timeout is exceeded.
|
|
23
|
+
*/
|
|
24
|
+
export async function withTimeout<T>(
|
|
25
|
+
promise: Promise<T>,
|
|
26
|
+
timeoutMs?: number,
|
|
27
|
+
): Promise<T> {
|
|
28
|
+
if (!timeoutMs) {
|
|
29
|
+
return promise;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return new Promise<T>((resolve, reject) => {
|
|
33
|
+
const timer = setTimeout(() => {
|
|
34
|
+
reject(new Error(`Step execution exceeded timeout of ${timeoutMs}ms`));
|
|
35
|
+
}, timeoutMs);
|
|
36
|
+
|
|
37
|
+
promise.then(
|
|
38
|
+
(value) => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
resolve(value);
|
|
41
|
+
},
|
|
42
|
+
(error) => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
reject(error);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|