@hexabot-ai/agentic 3.1.2-alpha.9 → 3.1.2-beta.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/README.md +13 -5
- package/dist/cjs/action/abstract-action.js +8 -3
- 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/timeout.js +63 -8
- package/dist/cjs/utils/workflow-definition-resources.js +73 -0
- package/dist/cjs/workflow-runner.js +40 -17
- package/dist/esm/action/abstract-action.js +8 -3
- 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/timeout.js +63 -8
- package/dist/esm/utils/workflow-definition-resources.js +68 -0
- 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/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/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-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/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/timeout.ts +78 -8
- package/src/utils/workflow-definition-resources.ts +111 -0
- package/src/workflow-event-emitter.ts +13 -6
- package/src/workflow-runner.ts +59 -19
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Full terms: see LICENSE.md.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { getAbortReason, throwIfAborted } from '../errors';
|
|
7
8
|
import type { RuntimeSuspensionRequest } from '../runner-runtime-control';
|
|
8
9
|
import type {
|
|
9
10
|
CompiledTask,
|
|
@@ -19,6 +20,7 @@ import type { StepExecutorEnv } from './types';
|
|
|
19
20
|
type TaskProgressOutcome =
|
|
20
21
|
| { type: 'completed'; value: unknown }
|
|
21
22
|
| { type: 'failed'; error: unknown }
|
|
23
|
+
| { type: 'cancelled'; error: Error }
|
|
22
24
|
| { type: 'suspended'; request: RuntimeSuspensionRequest };
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -43,6 +45,12 @@ export async function executeTaskStep(
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const stepInfo = env.buildInstanceStepInfo(step, state.iterationStack);
|
|
48
|
+
if (env.signal.aborted) {
|
|
49
|
+
const error = getAbortReason(env.signal);
|
|
50
|
+
recordTaskCancellation(env, stepInfo, error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
const scope: EvaluationScope = {
|
|
47
55
|
input: state.input,
|
|
48
56
|
context: env.context.state,
|
|
@@ -51,7 +59,13 @@ export async function executeTaskStep(
|
|
|
51
59
|
accumulator: state.accumulator,
|
|
52
60
|
};
|
|
53
61
|
const inputs = await evaluateMapping(task.inputs, scope);
|
|
54
|
-
env.
|
|
62
|
+
if (env.signal.aborted) {
|
|
63
|
+
const error = getAbortReason(env.signal);
|
|
64
|
+
recordTaskCancellation(env, stepInfo, error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const stepExecution = env.recordStepExecution(stepInfo, {
|
|
55
69
|
action: task.actionName,
|
|
56
70
|
status: 'running',
|
|
57
71
|
startedAt: Date.now(),
|
|
@@ -61,11 +75,23 @@ export async function executeTaskStep(
|
|
|
61
75
|
env.beginStepExecution?.(stepInfo.id);
|
|
62
76
|
env.setCurrentStep(stepInfo);
|
|
63
77
|
env.markSnapshot(stepInfo, 'running');
|
|
64
|
-
env.emit('hook:step:start', {
|
|
78
|
+
env.emit('hook:step:start', {
|
|
79
|
+
runId: env.runId,
|
|
80
|
+
step: stepInfo,
|
|
81
|
+
stepExecution,
|
|
82
|
+
});
|
|
65
83
|
|
|
66
84
|
try {
|
|
85
|
+
throwIfAborted(env.signal);
|
|
86
|
+
|
|
67
87
|
const actionPromise = Promise.resolve().then(() =>
|
|
68
|
-
task.action.run(
|
|
88
|
+
task.action.run(
|
|
89
|
+
inputs,
|
|
90
|
+
env.context,
|
|
91
|
+
task.settings,
|
|
92
|
+
task.bindings,
|
|
93
|
+
env.signal,
|
|
94
|
+
),
|
|
69
95
|
);
|
|
70
96
|
const outcome = await waitForTaskProgress(env, stepInfo.id, actionPromise);
|
|
71
97
|
|
|
@@ -88,6 +114,12 @@ export async function executeTaskStep(
|
|
|
88
114
|
throw outcome.error;
|
|
89
115
|
}
|
|
90
116
|
|
|
117
|
+
if (outcome.type === 'cancelled') {
|
|
118
|
+
env.clearStepSuspensions(stepInfo.id, outcome.error);
|
|
119
|
+
recordTaskCancellation(env, stepInfo, outcome.error);
|
|
120
|
+
throw outcome.error;
|
|
121
|
+
}
|
|
122
|
+
|
|
91
123
|
return buildSuspensionContinuation(
|
|
92
124
|
env,
|
|
93
125
|
stepInfo.id,
|
|
@@ -114,8 +146,29 @@ const waitForTaskProgress = async (
|
|
|
114
146
|
const suspension = env
|
|
115
147
|
.waitForStepSuspension(stepId)
|
|
116
148
|
.then<TaskProgressOutcome>((request) => ({ type: 'suspended', request }));
|
|
149
|
+
let cleanupCancellation = () => undefined;
|
|
150
|
+
const cancellation = new Promise<TaskProgressOutcome>((resolve) => {
|
|
151
|
+
if (env.signal.aborted) {
|
|
152
|
+
resolve({ type: 'cancelled', error: getAbortReason(env.signal) });
|
|
153
|
+
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const onAbort = () => {
|
|
158
|
+
resolve({ type: 'cancelled', error: getAbortReason(env.signal) });
|
|
159
|
+
};
|
|
117
160
|
|
|
118
|
-
|
|
161
|
+
env.signal.addEventListener('abort', onAbort, { once: true });
|
|
162
|
+
cleanupCancellation = () => {
|
|
163
|
+
env.signal.removeEventListener('abort', onAbort);
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
return await Promise.race([completion, suspension, cancellation]);
|
|
169
|
+
} finally {
|
|
170
|
+
cleanupCancellation();
|
|
171
|
+
}
|
|
119
172
|
};
|
|
120
173
|
const completeTask = async (
|
|
121
174
|
env: StepExecutorEnv,
|
|
@@ -126,14 +179,18 @@ const completeTask = async (
|
|
|
126
179
|
result: unknown,
|
|
127
180
|
) => {
|
|
128
181
|
await env.captureTaskOutput(task, state, result);
|
|
129
|
-
env.recordStepExecution(stepInfo, {
|
|
182
|
+
const stepExecution = env.recordStepExecution(stepInfo, {
|
|
130
183
|
status: 'completed',
|
|
131
184
|
endedAt: Date.now(),
|
|
132
185
|
output: result,
|
|
133
186
|
context: { after: env.context.snapshot() },
|
|
134
187
|
});
|
|
135
188
|
env.markSnapshot(stepInfo, 'completed');
|
|
136
|
-
env.emit('hook:step:success', {
|
|
189
|
+
env.emit('hook:step:success', {
|
|
190
|
+
runId: env.runId,
|
|
191
|
+
step: stepInfo,
|
|
192
|
+
stepExecution,
|
|
193
|
+
});
|
|
137
194
|
env.clearStepSuspensions(stepId);
|
|
138
195
|
};
|
|
139
196
|
const recordSuspension = (
|
|
@@ -142,7 +199,7 @@ const recordSuspension = (
|
|
|
142
199
|
reason?: string,
|
|
143
200
|
data?: unknown,
|
|
144
201
|
) => {
|
|
145
|
-
env.recordStepExecution(stepInfo, {
|
|
202
|
+
const stepExecution = env.recordStepExecution(stepInfo, {
|
|
146
203
|
status: 'suspended',
|
|
147
204
|
endedAt: Date.now(),
|
|
148
205
|
reason,
|
|
@@ -152,6 +209,7 @@ const recordSuspension = (
|
|
|
152
209
|
env.emit('hook:step:suspended', {
|
|
153
210
|
runId: env.runId,
|
|
154
211
|
step: stepInfo,
|
|
212
|
+
stepExecution,
|
|
155
213
|
reason,
|
|
156
214
|
data,
|
|
157
215
|
});
|
|
@@ -231,14 +289,40 @@ const recordTaskFailure = (
|
|
|
231
289
|
stepInfo: Suspension['step'],
|
|
232
290
|
error: unknown,
|
|
233
291
|
) => {
|
|
234
|
-
|
|
292
|
+
const normalizedError = normalizeError(error);
|
|
293
|
+
const stepExecution = env.recordStepExecution(stepInfo, {
|
|
235
294
|
status: 'failed',
|
|
236
295
|
endedAt: Date.now(),
|
|
237
|
-
error:
|
|
296
|
+
error: normalizedError,
|
|
238
297
|
context: { after: env.context.snapshot() },
|
|
239
298
|
});
|
|
240
299
|
env.markSnapshot(stepInfo, 'failed', normalizeErrorMessage(error));
|
|
241
|
-
env.emit('hook:step:error', {
|
|
300
|
+
env.emit('hook:step:error', {
|
|
301
|
+
runId: env.runId,
|
|
302
|
+
step: stepInfo,
|
|
303
|
+
stepExecution,
|
|
304
|
+
error,
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
const recordTaskCancellation = (
|
|
308
|
+
env: StepExecutorEnv,
|
|
309
|
+
stepInfo: Suspension['step'],
|
|
310
|
+
error: unknown,
|
|
311
|
+
) => {
|
|
312
|
+
const normalizedError = normalizeError(error);
|
|
313
|
+
const stepExecution = env.recordStepExecution(stepInfo, {
|
|
314
|
+
status: 'cancelled',
|
|
315
|
+
endedAt: Date.now(),
|
|
316
|
+
error: normalizedError,
|
|
317
|
+
context: { after: env.context.snapshot() },
|
|
318
|
+
});
|
|
319
|
+
env.markSnapshot(stepInfo, 'cancelled', normalizeErrorMessage(error));
|
|
320
|
+
env.emit('hook:step:cancelled', {
|
|
321
|
+
runId: env.runId,
|
|
322
|
+
step: stepInfo,
|
|
323
|
+
stepExecution,
|
|
324
|
+
error,
|
|
325
|
+
});
|
|
242
326
|
};
|
|
243
327
|
const normalizeError = (error: unknown): { message: string; stack?: string } =>
|
|
244
328
|
error instanceof Error
|
|
@@ -23,9 +23,21 @@ import type {
|
|
|
23
23
|
Suspension,
|
|
24
24
|
} from '../workflow-types';
|
|
25
25
|
|
|
26
|
+
export type StepExecutorEnvForkOverrides = {
|
|
27
|
+
context?: BaseWorkflowContext;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
setCurrentStep?: (step?: StepInfo) => void;
|
|
30
|
+
captureTaskOutput?: (
|
|
31
|
+
task: CompiledTask,
|
|
32
|
+
state: ExecutionState,
|
|
33
|
+
result: unknown,
|
|
34
|
+
) => Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
|
|
26
37
|
export type StepExecutorEnv = {
|
|
27
38
|
compiled: CompiledWorkflow;
|
|
28
39
|
context: BaseWorkflowContext;
|
|
40
|
+
signal: AbortSignal;
|
|
29
41
|
runId?: string;
|
|
30
42
|
buildInstanceStepInfo: (
|
|
31
43
|
step: CompiledStep,
|
|
@@ -39,7 +51,7 @@ export type StepExecutorEnv = {
|
|
|
39
51
|
recordStepExecution: (
|
|
40
52
|
step: StepInfo,
|
|
41
53
|
update: Partial<StepExecutionRecord>,
|
|
42
|
-
) =>
|
|
54
|
+
) => StepExecutionRecord;
|
|
43
55
|
emit: <K extends keyof WorkflowEventMap>(
|
|
44
56
|
event: K,
|
|
45
57
|
payload: WorkflowEventMap[K],
|
|
@@ -67,4 +79,5 @@ export type StepExecutorEnv = {
|
|
|
67
79
|
state: ExecutionState,
|
|
68
80
|
path: Array<number | string>,
|
|
69
81
|
) => Promise<Suspension | void>;
|
|
82
|
+
fork: (overrides: StepExecutorEnvForkOverrides) => StepExecutorEnv;
|
|
70
83
|
};
|
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
shouldStopLoop,
|
|
11
11
|
updateAccumulator,
|
|
12
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
13
|
import { wrapSuspensionContinuation } from './step-executors/suspension-continuation';
|
|
16
14
|
import type { StepExecutorEnv } from './step-executors/types';
|
|
17
15
|
import {
|
|
@@ -295,57 +293,7 @@ export function buildSuspensionForPath(
|
|
|
295
293
|
}
|
|
296
294
|
|
|
297
295
|
if (step.type === StepType.Parallel) {
|
|
298
|
-
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const childPath = rest.slice(1);
|
|
303
|
-
const childSuspension = buildSuspensionForPath(
|
|
304
|
-
deps,
|
|
305
|
-
step.steps,
|
|
306
|
-
state,
|
|
307
|
-
childPath,
|
|
308
|
-
[...currentPath, 'parallel'],
|
|
309
|
-
iterationDepth,
|
|
310
|
-
suspensionMetadata,
|
|
311
|
-
);
|
|
312
|
-
|
|
313
|
-
if (!childSuspension) {
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const childIndex =
|
|
318
|
-
typeof childPath[0] === 'number' ? (childPath[0] as number) : 0;
|
|
319
|
-
|
|
320
|
-
return wrapSuspensionContinuation(childSuspension, async () => {
|
|
321
|
-
if (step.strategy === 'wait_any') {
|
|
322
|
-
if (childIndex + 1 < step.steps.length) {
|
|
323
|
-
markStepsSkipped(
|
|
324
|
-
env,
|
|
325
|
-
step.steps.slice(childIndex + 1),
|
|
326
|
-
state.iterationStack ?? [],
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const next = await runParallelExecutor(
|
|
334
|
-
env,
|
|
335
|
-
step,
|
|
336
|
-
state,
|
|
337
|
-
currentPath,
|
|
338
|
-
childIndex + 1,
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
if (next) {
|
|
342
|
-
return wrapSuspensionContinuation(next, () =>
|
|
343
|
-
deps.executeFlow(steps, state, pathPrefix, current + 1),
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
348
|
-
});
|
|
296
|
+
return null;
|
|
349
297
|
}
|
|
350
298
|
|
|
351
299
|
if (step.type === StepType.Conditional) {
|
package/src/utils/timeout.ts
CHANGED
|
@@ -4,43 +4,113 @@
|
|
|
4
4
|
* Full terms: see LICENSE.md.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { getAbortReason, throwIfAborted } from '../errors';
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Resolves after the specified duration; useful for retry delays.
|
|
9
11
|
*
|
|
10
12
|
* @param durationMs - Number of milliseconds to wait before resolving.
|
|
13
|
+
* @param signal - Optional signal used to cancel the wait.
|
|
11
14
|
* @returns A promise that resolves once the duration elapses.
|
|
12
15
|
*/
|
|
13
|
-
export const sleep = (
|
|
14
|
-
|
|
16
|
+
export const sleep = (
|
|
17
|
+
durationMs: number,
|
|
18
|
+
signal?: AbortSignal,
|
|
19
|
+
): Promise<void> => {
|
|
20
|
+
if (!signal) {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, durationMs));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throwIfAborted(signal);
|
|
25
|
+
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const timer = setTimeout(() => {
|
|
28
|
+
signal.removeEventListener('abort', onAbort);
|
|
29
|
+
resolve();
|
|
30
|
+
}, durationMs);
|
|
31
|
+
const onAbort = () => {
|
|
32
|
+
clearTimeout(timer);
|
|
33
|
+
reject(getAbortReason(signal));
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
37
|
+
});
|
|
38
|
+
};
|
|
15
39
|
|
|
16
40
|
/**
|
|
17
41
|
* Wraps a promise and rejects if it does not settle within the timeout.
|
|
18
42
|
*
|
|
19
43
|
* @param promise - Operation that may take longer than the allowed timeout.
|
|
20
44
|
* @param timeoutMs - Maximum time in milliseconds to wait before rejecting.
|
|
45
|
+
* @param signal - Optional signal used to cancel the operation.
|
|
21
46
|
* @returns The result of the original promise when it resolves in time.
|
|
22
47
|
* @throws Error when the timeout is exceeded.
|
|
23
48
|
*/
|
|
24
49
|
export async function withTimeout<T>(
|
|
25
50
|
promise: Promise<T>,
|
|
26
51
|
timeoutMs?: number,
|
|
52
|
+
signal?: AbortSignal,
|
|
27
53
|
): Promise<T> {
|
|
28
|
-
if (!timeoutMs) {
|
|
54
|
+
if (!timeoutMs && !signal) {
|
|
29
55
|
return promise;
|
|
30
56
|
}
|
|
31
57
|
|
|
32
58
|
return new Promise<T>((resolve, reject) => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
59
|
+
let settled = false;
|
|
60
|
+
const timer = timeoutMs
|
|
61
|
+
? setTimeout(() => {
|
|
62
|
+
settled = true;
|
|
63
|
+
signal?.removeEventListener('abort', onAbort);
|
|
64
|
+
reject(
|
|
65
|
+
new Error(`Step execution exceeded timeout of ${timeoutMs}ms`),
|
|
66
|
+
);
|
|
67
|
+
}, timeoutMs)
|
|
68
|
+
: undefined;
|
|
69
|
+
const onAbort = () => {
|
|
70
|
+
if (settled) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
settled = true;
|
|
75
|
+
if (timer) {
|
|
76
|
+
clearTimeout(timer);
|
|
77
|
+
}
|
|
78
|
+
reject(getAbortReason(signal as AbortSignal));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (signal) {
|
|
82
|
+
if (signal.aborted) {
|
|
83
|
+
onAbort();
|
|
84
|
+
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
89
|
+
}
|
|
36
90
|
|
|
37
91
|
promise.then(
|
|
38
92
|
(value) => {
|
|
39
|
-
|
|
93
|
+
if (settled) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
settled = true;
|
|
98
|
+
if (timer) {
|
|
99
|
+
clearTimeout(timer);
|
|
100
|
+
}
|
|
101
|
+
signal?.removeEventListener('abort', onAbort);
|
|
40
102
|
resolve(value);
|
|
41
103
|
},
|
|
42
104
|
(error) => {
|
|
43
|
-
|
|
105
|
+
if (settled) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
settled = true;
|
|
110
|
+
if (timer) {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
}
|
|
113
|
+
signal?.removeEventListener('abort', onAbort);
|
|
44
114
|
reject(error);
|
|
45
115
|
},
|
|
46
116
|
);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Hexabot — Fair Core License (FCL-1.0-ALv2)
|
|
3
|
+
* Copyright (c) 2026 Hexastack.
|
|
4
|
+
* Full terms: see LICENSE.md.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { DefDefinition, WorkflowDefinition } from '../dsl.types';
|
|
8
|
+
|
|
9
|
+
export type WorkflowDefinitionResourceDescriptor = {
|
|
10
|
+
kind: string;
|
|
11
|
+
settingsKey: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type WorkflowDefinitionResourceRefs = Record<string, string[]>;
|
|
15
|
+
|
|
16
|
+
export type WorkflowDefinitionResourceIdMaps = Record<
|
|
17
|
+
string,
|
|
18
|
+
Record<string, string>
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
const getSettingsRef = (
|
|
22
|
+
definition: DefDefinition,
|
|
23
|
+
settingsKey: string,
|
|
24
|
+
): string | null => {
|
|
25
|
+
const settings = definition.settings;
|
|
26
|
+
if (!settings || typeof settings !== 'object') {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const value = (settings as Record<string, unknown>)[settingsKey];
|
|
31
|
+
|
|
32
|
+
return typeof value === 'string' && value.trim() ? value : null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Collect external resource IDs referenced by workflow definition defs.
|
|
37
|
+
*/
|
|
38
|
+
export const collectWorkflowDefinitionResourceRefs = (
|
|
39
|
+
definition: WorkflowDefinition,
|
|
40
|
+
descriptors: WorkflowDefinitionResourceDescriptor[],
|
|
41
|
+
): WorkflowDefinitionResourceRefs => {
|
|
42
|
+
const result = Object.fromEntries(
|
|
43
|
+
descriptors.map((descriptor) => [descriptor.kind, new Set<string>()]),
|
|
44
|
+
) as Record<string, Set<string>>;
|
|
45
|
+
|
|
46
|
+
for (const def of Object.values(definition.defs ?? {})) {
|
|
47
|
+
for (const descriptor of descriptors) {
|
|
48
|
+
if (def.kind !== descriptor.kind) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const ref = getSettingsRef(def, descriptor.settingsKey);
|
|
53
|
+
if (ref) {
|
|
54
|
+
result[descriptor.kind].add(ref);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Object.fromEntries(
|
|
60
|
+
Object.entries(result).map(([kind, refs]) => [kind, Array.from(refs)]),
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Rewrite external resource IDs referenced by workflow definition defs.
|
|
66
|
+
*/
|
|
67
|
+
export const remapWorkflowDefinitionResourceRefs = (
|
|
68
|
+
definition: WorkflowDefinition,
|
|
69
|
+
descriptors: WorkflowDefinitionResourceDescriptor[],
|
|
70
|
+
idMaps: WorkflowDefinitionResourceIdMaps,
|
|
71
|
+
): WorkflowDefinition => {
|
|
72
|
+
let didChange = false;
|
|
73
|
+
const nextDefs = Object.fromEntries(
|
|
74
|
+
Object.entries(definition.defs ?? {}).map(([defName, def]) => {
|
|
75
|
+
const descriptor = descriptors.find((item) => item.kind === def.kind);
|
|
76
|
+
if (!descriptor) {
|
|
77
|
+
return [defName, def];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const currentRef = getSettingsRef(def, descriptor.settingsKey);
|
|
81
|
+
const nextRef = currentRef
|
|
82
|
+
? idMaps[descriptor.kind]?.[currentRef]
|
|
83
|
+
: undefined;
|
|
84
|
+
if (!currentRef || !nextRef || nextRef === currentRef) {
|
|
85
|
+
return [defName, def];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
didChange = true;
|
|
89
|
+
|
|
90
|
+
return [
|
|
91
|
+
defName,
|
|
92
|
+
{
|
|
93
|
+
...def,
|
|
94
|
+
settings: {
|
|
95
|
+
...(def.settings ?? {}),
|
|
96
|
+
[descriptor.settingsKey]: nextRef,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
];
|
|
100
|
+
}),
|
|
101
|
+
) as WorkflowDefinition['defs'];
|
|
102
|
+
|
|
103
|
+
if (!didChange) {
|
|
104
|
+
return definition;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
...definition,
|
|
109
|
+
defs: nextDefs,
|
|
110
|
+
};
|
|
111
|
+
};
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Full terms: see LICENSE.md.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import type { StepExecutionRecord } from './context';
|
|
8
|
+
|
|
7
9
|
export enum StepType {
|
|
8
10
|
Task = 'task',
|
|
9
11
|
Parallel = 'parallel',
|
|
@@ -17,6 +19,12 @@ export type StepInfo = {
|
|
|
17
19
|
type: StepType;
|
|
18
20
|
};
|
|
19
21
|
|
|
22
|
+
export type StepWorkflowEventPayload = {
|
|
23
|
+
runId?: string;
|
|
24
|
+
step: StepInfo;
|
|
25
|
+
stepExecution?: StepExecutionRecord;
|
|
26
|
+
};
|
|
27
|
+
|
|
20
28
|
export type WorkflowEventMap = {
|
|
21
29
|
'hook:workflow:start': { runId?: string };
|
|
22
30
|
'hook:workflow:finish': { runId?: string; output: Record<string, unknown> };
|
|
@@ -27,12 +35,11 @@ export type WorkflowEventMap = {
|
|
|
27
35
|
reason?: string;
|
|
28
36
|
data?: unknown;
|
|
29
37
|
};
|
|
30
|
-
'hook:step:start':
|
|
31
|
-
'hook:step:success':
|
|
32
|
-
'hook:step:error':
|
|
33
|
-
'hook:step:
|
|
34
|
-
|
|
35
|
-
step: StepInfo;
|
|
38
|
+
'hook:step:start': StepWorkflowEventPayload;
|
|
39
|
+
'hook:step:success': StepWorkflowEventPayload;
|
|
40
|
+
'hook:step:error': StepWorkflowEventPayload & { error: unknown };
|
|
41
|
+
'hook:step:cancelled': StepWorkflowEventPayload & { error: unknown };
|
|
42
|
+
'hook:step:suspended': StepWorkflowEventPayload & {
|
|
36
43
|
reason?: string;
|
|
37
44
|
data?: unknown;
|
|
38
45
|
};
|