@hexabot-ai/agentic 3.1.2-alpha.1 → 3.1.2-alpha.3
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/dist/cjs/step-executors/loop-executor.js +26 -39
- package/dist/cjs/step-executors/parallel-executor.js +9 -15
- package/dist/cjs/step-executors/suspension-continuation.js +27 -0
- package/dist/cjs/suspension-rebuilder.js +61 -74
- package/dist/cjs/workflow-runner.js +2 -10
- package/dist/esm/step-executors/loop-executor.js +26 -39
- package/dist/esm/step-executors/parallel-executor.js +9 -15
- package/dist/esm/step-executors/suspension-continuation.js +24 -0
- package/dist/esm/suspension-rebuilder.js +61 -74
- package/dist/esm/workflow-runner.js +2 -10
- package/dist/types/step-executors/loop-executor.d.ts.map +1 -1
- package/dist/types/step-executors/parallel-executor.d.ts.map +1 -1
- package/dist/types/step-executors/suspension-continuation.d.ts +10 -0
- package/dist/types/step-executors/suspension-continuation.d.ts.map +1 -0
- package/dist/types/suspension-rebuilder.d.ts.map +1 -1
- package/dist/types/workflow-runner.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/suspension-rebuilder.test.ts +43 -9
- package/src/__tests__/workflow-runner.test.ts +119 -1
- package/src/step-executors/loop-executor.ts +49 -64
- package/src/step-executors/parallel-executor.ts +13 -20
- package/src/step-executors/suspension-continuation.ts +31 -0
- package/src/suspension-rebuilder.ts +94 -100
- package/src/workflow-runner.ts +4 -11
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { executeLoop as runLoopExecutor, shouldStopLoop, updateAccumulator, } from './step-executors/loop-executor';
|
|
7
7
|
import { executeParallel as runParallelExecutor } from './step-executors/parallel-executor';
|
|
8
8
|
import { markStepsSkipped } from './step-executors/skip-helpers';
|
|
9
|
+
import { wrapSuspensionContinuation } from './step-executors/suspension-continuation';
|
|
9
10
|
import { StepType, } from './workflow-event-emitter';
|
|
10
11
|
/**
|
|
11
12
|
* Parse a suspended step id into its execution path and iteration stack.
|
|
@@ -149,16 +150,7 @@ export function buildSuspensionForPath(deps, steps, state, targetPath, pathPrefi
|
|
|
149
150
|
}
|
|
150
151
|
const resumed = await executeStep(step, state, currentPath);
|
|
151
152
|
if (resumed) {
|
|
152
|
-
return
|
|
153
|
-
...resumed,
|
|
154
|
-
continue: async (nextResumeData) => {
|
|
155
|
-
const next = await resumed.continue(nextResumeData);
|
|
156
|
-
if (next) {
|
|
157
|
-
return next;
|
|
158
|
-
}
|
|
159
|
-
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
160
|
-
},
|
|
161
|
-
};
|
|
153
|
+
return wrapSuspensionContinuation(resumed, () => deps.executeFlow(steps, state, pathPrefix, current + 1));
|
|
162
154
|
}
|
|
163
155
|
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
164
156
|
}
|
|
@@ -183,22 +175,19 @@ export function buildSuspensionForPath(deps, steps, state, targetPath, pathPrefi
|
|
|
183
175
|
return null;
|
|
184
176
|
}
|
|
185
177
|
const childIndex = typeof childPath[0] === 'number' ? childPath[0] : 0;
|
|
186
|
-
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (next) {
|
|
191
|
-
return next;
|
|
178
|
+
return wrapSuspensionContinuation(childSuspension, async () => {
|
|
179
|
+
if (step.strategy === 'wait_any') {
|
|
180
|
+
if (childIndex + 1 < step.steps.length) {
|
|
181
|
+
markStepsSkipped(env, step.steps.slice(childIndex + 1), state.iterationStack ?? []);
|
|
192
182
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
};
|
|
183
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
184
|
+
}
|
|
185
|
+
const next = await runParallelExecutor(env, step, state, currentPath, childIndex + 1);
|
|
186
|
+
if (next) {
|
|
187
|
+
return wrapSuspensionContinuation(next, () => deps.executeFlow(steps, state, pathPrefix, current + 1));
|
|
188
|
+
}
|
|
189
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
190
|
+
});
|
|
202
191
|
}
|
|
203
192
|
if (step.type === StepType.Conditional) {
|
|
204
193
|
if (rest[0] !== 'branch' || typeof rest[1] !== 'number') {
|
|
@@ -214,16 +203,7 @@ export function buildSuspensionForPath(deps, steps, state, targetPath, pathPrefi
|
|
|
214
203
|
if (!branchSuspension) {
|
|
215
204
|
return null;
|
|
216
205
|
}
|
|
217
|
-
return
|
|
218
|
-
...branchSuspension,
|
|
219
|
-
continue: async (resumeData) => {
|
|
220
|
-
const next = await branchSuspension.continue(resumeData);
|
|
221
|
-
if (next) {
|
|
222
|
-
return next;
|
|
223
|
-
}
|
|
224
|
-
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
225
|
-
},
|
|
226
|
-
};
|
|
206
|
+
return wrapSuspensionContinuation(branchSuspension, () => deps.executeFlow(steps, state, pathPrefix, current + 1));
|
|
227
207
|
}
|
|
228
208
|
if (step.type === StepType.Loop) {
|
|
229
209
|
if (rest.length < 1 || typeof rest[0] !== 'string') {
|
|
@@ -251,46 +231,53 @@ export function buildSuspensionForPath(deps, steps, state, targetPath, pathPrefi
|
|
|
251
231
|
return null;
|
|
252
232
|
}
|
|
253
233
|
const accumulator = iterationState.accumulator ?? state.accumulator ?? undefined;
|
|
254
|
-
return {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const updatedAccumulator = await updateAccumulator(step, scope, accumulator);
|
|
272
|
-
const shouldStop = await shouldStopLoop(step, scope);
|
|
273
|
-
state.output = iterationState.output;
|
|
274
|
-
if (step.accumulate && step.name) {
|
|
275
|
-
state.output[step.name] = {
|
|
276
|
-
[step.accumulate.as]: updatedAccumulator,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
if (step.accumulate) {
|
|
280
|
-
state.accumulator = updatedAccumulator;
|
|
281
|
-
}
|
|
282
|
-
if (shouldStop) {
|
|
283
|
-
return undefined;
|
|
284
|
-
}
|
|
285
|
-
const baseState = {
|
|
286
|
-
...iterationState,
|
|
287
|
-
iterationStack: ancestorIterationStack,
|
|
288
|
-
accumulator: updatedAccumulator,
|
|
289
|
-
output: state.output,
|
|
234
|
+
return wrapSuspensionContinuation(childSuspension, async () => {
|
|
235
|
+
const scope = {
|
|
236
|
+
input: iterationState.input,
|
|
237
|
+
context: env.context.state,
|
|
238
|
+
output: iterationState.output,
|
|
239
|
+
iteration: iterationState.iteration ?? {
|
|
240
|
+
item: undefined,
|
|
241
|
+
index: iterationIndex,
|
|
242
|
+
},
|
|
243
|
+
accumulator,
|
|
244
|
+
};
|
|
245
|
+
const updatedAccumulator = await updateAccumulator(step, scope, accumulator);
|
|
246
|
+
const shouldStop = await shouldStopLoop(step, scope);
|
|
247
|
+
state.output = iterationState.output;
|
|
248
|
+
if (step.accumulate && step.name) {
|
|
249
|
+
state.output[step.name] = {
|
|
250
|
+
[step.accumulate.as]: updatedAccumulator,
|
|
290
251
|
};
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
252
|
+
}
|
|
253
|
+
if (step.accumulate) {
|
|
254
|
+
state.accumulator = updatedAccumulator;
|
|
255
|
+
}
|
|
256
|
+
if (shouldStop) {
|
|
257
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
258
|
+
}
|
|
259
|
+
const baseState = {
|
|
260
|
+
...iterationState,
|
|
261
|
+
iterationStack: ancestorIterationStack,
|
|
262
|
+
accumulator: updatedAccumulator,
|
|
263
|
+
output: state.output,
|
|
264
|
+
};
|
|
265
|
+
const next = await runLoopExecutor(env, step, baseState, currentPath, iterationIndex + 1);
|
|
266
|
+
state.output = baseState.output;
|
|
267
|
+
if (step.accumulate) {
|
|
268
|
+
state.accumulator = baseState.accumulator;
|
|
269
|
+
}
|
|
270
|
+
if (next) {
|
|
271
|
+
return wrapSuspensionContinuation(next, async () => {
|
|
272
|
+
state.output = baseState.output;
|
|
273
|
+
if (step.accumulate) {
|
|
274
|
+
state.accumulator = baseState.accumulator;
|
|
275
|
+
}
|
|
276
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return deps.executeFlow(steps, state, pathPrefix, current + 1);
|
|
280
|
+
});
|
|
294
281
|
}
|
|
295
282
|
return null;
|
|
296
283
|
}
|
|
@@ -8,6 +8,7 @@ import { RunnerRuntimeControl } from './runner-runtime-control';
|
|
|
8
8
|
import { executeConditional as runConditionalExecutor } from './step-executors/conditional-executor';
|
|
9
9
|
import { executeLoop as runLoopExecutor } from './step-executors/loop-executor';
|
|
10
10
|
import { executeParallel as runParallelExecutor } from './step-executors/parallel-executor';
|
|
11
|
+
import { wrapSuspensionContinuation } from './step-executors/suspension-continuation';
|
|
11
12
|
import { executeTaskStep as runTaskExecutor } from './step-executors/task-executor';
|
|
12
13
|
import { rebuildSuspension, } from './suspension-rebuilder';
|
|
13
14
|
import { StepType, } from './workflow-event-emitter';
|
|
@@ -405,16 +406,7 @@ export class WorkflowRunner {
|
|
|
405
406
|
const stepPath = [...path, index];
|
|
406
407
|
const suspension = await this.executeStep(step, state, stepPath);
|
|
407
408
|
if (suspension) {
|
|
408
|
-
return
|
|
409
|
-
...suspension,
|
|
410
|
-
continue: async (resumeData) => {
|
|
411
|
-
const next = await suspension.continue(resumeData);
|
|
412
|
-
if (next) {
|
|
413
|
-
return next;
|
|
414
|
-
}
|
|
415
|
-
return this.executeFlow(steps, state, path, index + 1);
|
|
416
|
-
},
|
|
417
|
-
};
|
|
409
|
+
return wrapSuspensionContinuation(suspension, () => this.executeFlow(steps, state, path, index + 1));
|
|
418
410
|
}
|
|
419
411
|
}
|
|
420
412
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loop-executor.d.ts","sourceRoot":"","sources":["../../../src/step-executors/loop-executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EAEd,QAAQ,EACR,UAAU,EAEX,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"loop-executor.d.ts","sourceRoot":"","sources":["../../../src/step-executors/loop-executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EAEd,QAAQ,EACR,UAAU,EAEX,MAAM,mBAAmB,CAAC;AAI3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,KAAK,SAAS,GAAG,eAAe,CAAC;AAEjC;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAC5B,UAAU,SAAI,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAM5B;AA+KD;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,OAAO,CAAC,CASlB;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,QAAQ,EACd,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,OAAO,CAAC,CAQlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parallel-executor.d.ts","sourceRoot":"","sources":["../../../src/step-executors/parallel-executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACX,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"parallel-executor.d.ts","sourceRoot":"","sources":["../../../src/step-executors/parallel-executor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,UAAU,EACX,MAAM,mBAAmB,CAAC;AAI3B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,YAAY,EAClB,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,EAC5B,UAAU,SAAI,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqC5B"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Suspension } from '../workflow-types';
|
|
2
|
+
/**
|
|
3
|
+
* Compose a suspension with the continuation that should run once it completes.
|
|
4
|
+
*
|
|
5
|
+
* When resuming a suspension yields another suspension from the same logical
|
|
6
|
+
* step, the follow-up suspension still needs to remember how to continue the
|
|
7
|
+
* surrounding flow after that step eventually finishes.
|
|
8
|
+
*/
|
|
9
|
+
export declare function wrapSuspensionContinuation(suspension: Suspension, onComplete: () => Promise<Suspension | void>): Suspension;
|
|
10
|
+
//# sourceMappingURL=suspension-continuation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suspension-continuation.d.ts","sourceRoot":"","sources":["../../../src/step-executors/suspension-continuation.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,UAAU,EAAE,UAAU,EACtB,UAAU,EAAE,MAAM,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAC3C,UAAU,CAYZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suspension-rebuilder.d.ts","sourceRoot":"","sources":["../../src/suspension-rebuilder.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"suspension-rebuilder.d.ts","sourceRoot":"","sources":["../../src/suspension-rebuilder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/workflow-runner.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"workflow-runner.d.ts","sourceRoot":"","sources":["../../src/workflow-runner.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACtB,MAAM,WAAW,CAAC;AAYnB,OAAO,EAEL,KAAK,QAAQ,EAEd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAGV,gBAAgB,EAChB,cAAc,EACd,mBAAmB,EACnB,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,WAAW,EAEX,kBAAkB,EACnB,MAAM,kBAAkB,CAAC;AAG1B;;;GAGG;AACH,qBAAa,cAAc;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAG5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAS;IAGhC,OAAO,CAAC,MAAM,CAA8C;IAG5D,OAAO,CAAC,SAAS,CAAsC;IAGvD,OAAO,CAAC,OAAO,CAA2C;IAG1D,OAAO,CAAC,UAAU,CAAC,CAAa;IAGhC,OAAO,CAAC,cAAc,CAAC,CAAuB;IAG9C,OAAO,CAAC,WAAW,CAAC,CAAW;IAG/B,OAAO,CAAC,cAAc,CAAC,CAAU;IAGjC,OAAO,CAAC,KAAK,CAAC,CAAiB;IAG/B,OAAO,CAAC,OAAO,CAAC,CAAsB;IAEtC;;;;;OAKG;gBACS,QAAQ,EAAE,gBAAgB,EAAE,OAAO,CAAC,EAAE,kBAAkB;IAKpE;;;;OAIG;IACH,QAAQ,IAAI,cAAc,GAAG,SAAS;IAItC;;;;OAIG;IACH,SAAS,IAAI,iBAAiB;IAI9B;;;;OAIG;IACH,WAAW,IAAI,gBAAgB;IAO/B;;;;OAIG;IACH,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAajD;;;;OAIG;IACH,cAAc,IAAI,QAAQ,GAAG,SAAS;IAItC;;;;OAIG;IACH,iBAAiB,IAAI,OAAO;IAI5B;;;;;;OAMG;IACG,KAAK,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,WAAW,CAAC;IA6BxD;;;;;OAKG;IACG,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC;IAiB3D;;;;;OAKG;YACW,YAAY;IA0D1B;;;;;;OAMG;WACU,kBAAkB,CAC7B,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE;QACP,KAAK,EAAE,cAAc,CAAC;QACtB,OAAO,EAAE,mBAAmB,CAAC;QAC7B,QAAQ,EAAE,gBAAgB,CAAC;QAC3B,UAAU,CAAC,EAAE,mBAAmB,CAAC;QACjC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,OAAO,CAAC;KAC1B,GACA,OAAO,CAAC,cAAc,CAAC;IAgD1B;;;;;OAKG;IACH,OAAO,CAAC,IAAI;IAOZ;;;;;;OAMG;YACW,uBAAuB;IAYrC;;;;;;OAMG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IASpB;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IA2B3B;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAqDzB;;;;OAIG;IACH,OAAO,CAAC,6BAA6B;IAkBrC;;;;;;;;OAQG;YACW,WAAW;IAsBzB;;;;;;;OAOG;YACW,WAAW;IAkBzB;;;;;;OAMG;YACW,iBAAiB;CAOhC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hexabot-ai/agentic",
|
|
3
|
-
"version": "3.1.2-alpha.
|
|
3
|
+
"version": "3.1.2-alpha.3",
|
|
4
4
|
"description": "Schemas and utilities for the Hexabot agentic workflow DSL.",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"jsonata": "^2.1.0",
|
|
23
|
-
"yaml": "^2.
|
|
23
|
+
"yaml": "^2.8.3",
|
|
24
24
|
"zod": "^4.3.6"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
@@ -308,10 +308,15 @@ describe('rebuildSuspension', () => {
|
|
|
308
308
|
state,
|
|
309
309
|
stepId: '0.parallel.1:child_b',
|
|
310
310
|
});
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
311
|
+
const nextSuspension = {
|
|
312
|
+
step: {
|
|
313
|
+
id: '0.parallel.2:child_c',
|
|
314
|
+
name: 'child_c',
|
|
315
|
+
type: StepType.Task,
|
|
316
|
+
},
|
|
317
|
+
continue: jest.fn().mockResolvedValue(undefined),
|
|
318
|
+
};
|
|
319
|
+
mockedRunParallelExecutor.mockResolvedValue(nextSuspension);
|
|
315
320
|
|
|
316
321
|
const result = await suspension?.continue({ payload: true });
|
|
317
322
|
|
|
@@ -325,7 +330,17 @@ describe('rebuildSuspension', () => {
|
|
|
325
330
|
[0],
|
|
326
331
|
2,
|
|
327
332
|
);
|
|
328
|
-
expect(result).toBe(
|
|
333
|
+
expect(result?.step.id).toBe(nextSuspension.step.id);
|
|
334
|
+
|
|
335
|
+
await result?.continue({ done: true });
|
|
336
|
+
|
|
337
|
+
expect(nextSuspension.continue).toHaveBeenCalledWith({ done: true });
|
|
338
|
+
expect(deps.executeFlow).toHaveBeenLastCalledWith(
|
|
339
|
+
compiled.flow,
|
|
340
|
+
state,
|
|
341
|
+
[],
|
|
342
|
+
1,
|
|
343
|
+
);
|
|
329
344
|
});
|
|
330
345
|
|
|
331
346
|
it('resumes a loop suspension, updating accumulators and continuing execution', async () => {
|
|
@@ -367,9 +382,15 @@ describe('rebuildSuspension', () => {
|
|
|
367
382
|
|
|
368
383
|
mockedUpdateAccumulator.mockResolvedValue(5);
|
|
369
384
|
mockedShouldStopLoop.mockResolvedValue(false);
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
385
|
+
const nextSuspension = {
|
|
386
|
+
step: {
|
|
387
|
+
id: '0.collector.0:loop_task[2]',
|
|
388
|
+
name: 'loop_task',
|
|
389
|
+
type: StepType.Task,
|
|
390
|
+
},
|
|
391
|
+
continue: jest.fn().mockResolvedValue(undefined),
|
|
392
|
+
};
|
|
393
|
+
mockedRunLoopExecutor.mockResolvedValue(nextSuspension);
|
|
373
394
|
|
|
374
395
|
const suspension = rebuildSuspension(deps, {
|
|
375
396
|
state,
|
|
@@ -404,6 +425,19 @@ describe('rebuildSuspension', () => {
|
|
|
404
425
|
const loopState = mockedRunLoopExecutor.mock.calls[0]?.[2];
|
|
405
426
|
expect(loopState?.accumulator).toBe(5);
|
|
406
427
|
expect(loopState?.iterationStack).toEqual([]);
|
|
407
|
-
expect(result).toBe(
|
|
428
|
+
expect(result?.step.id).toBe(nextSuspension.step.id);
|
|
429
|
+
|
|
430
|
+
await result?.continue({ done: true });
|
|
431
|
+
|
|
432
|
+
expect(nextSuspension.continue).toHaveBeenCalledWith({ done: true });
|
|
433
|
+
expect(deps.executeFlow).toHaveBeenLastCalledWith(
|
|
434
|
+
compiled.flow,
|
|
435
|
+
expect.objectContaining({
|
|
436
|
+
accumulator: 5,
|
|
437
|
+
output: expect.objectContaining({ collector: { sum: 5 } }),
|
|
438
|
+
}),
|
|
439
|
+
[],
|
|
440
|
+
1,
|
|
441
|
+
);
|
|
408
442
|
});
|
|
409
443
|
});
|
|
@@ -588,6 +588,103 @@ describe('WorkflowRunner', () => {
|
|
|
588
588
|
}
|
|
589
589
|
});
|
|
590
590
|
|
|
591
|
+
it('continues following steps after a repeatedly suspended while loop exits', async () => {
|
|
592
|
+
const suspendAction = defineAction<
|
|
593
|
+
{ prompt: string },
|
|
594
|
+
{ reply: string; done: boolean },
|
|
595
|
+
TestContext,
|
|
596
|
+
Settings
|
|
597
|
+
>({
|
|
598
|
+
name: 'loop_suspend_action',
|
|
599
|
+
inputSchema: z.object({ prompt: z.string() }),
|
|
600
|
+
outputSchema: z.object({ reply: z.string(), done: z.boolean() }),
|
|
601
|
+
execute: async ({ input, context }) => {
|
|
602
|
+
const resumeData = (await context.workflow.suspend({
|
|
603
|
+
reason: 'awaiting_reply',
|
|
604
|
+
data: { prompt: input.prompt },
|
|
605
|
+
})) as { reply: string };
|
|
606
|
+
|
|
607
|
+
return {
|
|
608
|
+
reply: resumeData.reply,
|
|
609
|
+
done: resumeData.reply === 'stop',
|
|
610
|
+
};
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
const afterExecute = jest.fn(async ({ input }) => ({
|
|
614
|
+
stored: `stored:${input.reply}`,
|
|
615
|
+
}));
|
|
616
|
+
const afterAction = defineAction<
|
|
617
|
+
{ reply: string },
|
|
618
|
+
{ stored: string },
|
|
619
|
+
TestContext,
|
|
620
|
+
Settings
|
|
621
|
+
>({
|
|
622
|
+
name: 'after_loop_action',
|
|
623
|
+
inputSchema: z.object({ reply: z.string() }),
|
|
624
|
+
outputSchema: z.object({ stored: z.string() }),
|
|
625
|
+
execute: afterExecute,
|
|
626
|
+
});
|
|
627
|
+
const definition: WorkflowDefinition = {
|
|
628
|
+
defs: createTaskDefs({
|
|
629
|
+
wait_step: {
|
|
630
|
+
action: 'loop_suspend_action',
|
|
631
|
+
inputs: { prompt: '="Attempt " & $string($iteration.index)' },
|
|
632
|
+
},
|
|
633
|
+
after_loop_step: {
|
|
634
|
+
action: 'after_loop_action',
|
|
635
|
+
inputs: { reply: '=$output.wait_step.reply' },
|
|
636
|
+
},
|
|
637
|
+
}),
|
|
638
|
+
flow: [
|
|
639
|
+
{
|
|
640
|
+
loop: {
|
|
641
|
+
type: 'while',
|
|
642
|
+
name: 'wait_until_stop',
|
|
643
|
+
while:
|
|
644
|
+
'=$not($exists($output.wait_step.done) and $output.wait_step.done = true)',
|
|
645
|
+
steps: [{ do: 'wait_step' }],
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
{ do: 'after_loop_step' },
|
|
649
|
+
],
|
|
650
|
+
outputs: {
|
|
651
|
+
reply: '=$output.wait_step.reply',
|
|
652
|
+
stored: '=$output.after_loop_step.stored',
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
const compiled = compileWorkflow(definition, {
|
|
656
|
+
actions: {
|
|
657
|
+
loop_suspend_action: suspendAction,
|
|
658
|
+
after_loop_action: afterAction,
|
|
659
|
+
},
|
|
660
|
+
});
|
|
661
|
+
const runner = new WorkflowRunner(compiled, {
|
|
662
|
+
runId: 'run-while-suspend-after',
|
|
663
|
+
});
|
|
664
|
+
const context = new TestContext({});
|
|
665
|
+
const startResult = await runner.start({ inputData: {}, context });
|
|
666
|
+
|
|
667
|
+
expect(startResult.status).toBe('suspended');
|
|
668
|
+
const firstResume = await runner.resume({
|
|
669
|
+
resumeData: { reply: 'continue' },
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
expect(firstResume.status).toBe('suspended');
|
|
673
|
+
const secondResume = await runner.resume({
|
|
674
|
+
resumeData: { reply: 'stop' },
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
expect(secondResume.status).toBe('finished');
|
|
678
|
+
if (secondResume.status === 'finished') {
|
|
679
|
+
expect(secondResume.output.reply).toBe('stop');
|
|
680
|
+
expect(secondResume.output.stored).toBe('stored:stop');
|
|
681
|
+
}
|
|
682
|
+
expect(afterExecute).toHaveBeenCalledTimes(1);
|
|
683
|
+
expect(afterExecute).toHaveBeenCalledWith(
|
|
684
|
+
expect.objectContaining({ input: { reply: 'stop' } }),
|
|
685
|
+
);
|
|
686
|
+
});
|
|
687
|
+
|
|
591
688
|
it('handles suspension and resumes with provided data', async () => {
|
|
592
689
|
const suspendAction = defineAction<
|
|
593
690
|
{ prompt: string },
|
|
@@ -994,12 +1091,27 @@ describe('WorkflowRunner', () => {
|
|
|
994
1091
|
return { reply: `ack:${resumeData.reply}` };
|
|
995
1092
|
},
|
|
996
1093
|
});
|
|
1094
|
+
const afterAction = defineAction<
|
|
1095
|
+
{ reply: string },
|
|
1096
|
+
{ summary: string },
|
|
1097
|
+
TestContext,
|
|
1098
|
+
Settings
|
|
1099
|
+
>({
|
|
1100
|
+
name: 'after_loop_action',
|
|
1101
|
+
inputSchema: z.object({ reply: z.string() }),
|
|
1102
|
+
outputSchema: z.object({ summary: z.string() }),
|
|
1103
|
+
execute: async ({ input }) => ({ summary: `after:${input.reply}` }),
|
|
1104
|
+
});
|
|
997
1105
|
const definition: WorkflowDefinition = {
|
|
998
1106
|
defs: createTaskDefs({
|
|
999
1107
|
wait_step: {
|
|
1000
1108
|
action: 'loop_suspend_action',
|
|
1001
1109
|
inputs: { prompt: '="Ping"' },
|
|
1002
1110
|
},
|
|
1111
|
+
after_step: {
|
|
1112
|
+
action: 'after_loop_action',
|
|
1113
|
+
inputs: { reply: '=$output.wait_step.reply' },
|
|
1114
|
+
},
|
|
1003
1115
|
}),
|
|
1004
1116
|
flow: [
|
|
1005
1117
|
{
|
|
@@ -1010,8 +1122,12 @@ describe('WorkflowRunner', () => {
|
|
|
1010
1122
|
steps: [{ do: 'wait_step' }],
|
|
1011
1123
|
},
|
|
1012
1124
|
},
|
|
1125
|
+
{ do: 'after_step' },
|
|
1013
1126
|
],
|
|
1014
|
-
outputs: {
|
|
1127
|
+
outputs: {
|
|
1128
|
+
reply: '=$output.wait_step.reply',
|
|
1129
|
+
summary: '=$output.after_step.summary',
|
|
1130
|
+
},
|
|
1015
1131
|
inputs: {
|
|
1016
1132
|
schema: {
|
|
1017
1133
|
items: { type: 'array', items: { type: 'string' } },
|
|
@@ -1021,6 +1137,7 @@ describe('WorkflowRunner', () => {
|
|
|
1021
1137
|
const compiled = compileWorkflow(definition, {
|
|
1022
1138
|
actions: {
|
|
1023
1139
|
loop_suspend_action: suspendAction,
|
|
1140
|
+
after_loop_action: afterAction,
|
|
1024
1141
|
},
|
|
1025
1142
|
});
|
|
1026
1143
|
const runner = new WorkflowRunner(compiled);
|
|
@@ -1061,6 +1178,7 @@ describe('WorkflowRunner', () => {
|
|
|
1061
1178
|
expect(resumeResult.status).toBe('finished');
|
|
1062
1179
|
if (resumeResult.status === 'finished') {
|
|
1063
1180
|
expect(resumeResult.output.reply).toBe('ack:Pong');
|
|
1181
|
+
expect(resumeResult.output.summary).toBe('after:ack:Pong');
|
|
1064
1182
|
}
|
|
1065
1183
|
});
|
|
1066
1184
|
|
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
} from '../workflow-types';
|
|
15
15
|
import { evaluateValue } from '../workflow-values';
|
|
16
16
|
|
|
17
|
+
import { wrapSuspensionContinuation } from './suspension-continuation';
|
|
17
18
|
import type { StepExecutorEnv } from './types';
|
|
18
19
|
|
|
19
20
|
type LoopScope = EvaluationScope;
|
|
@@ -74,45 +75,37 @@ async function executeForEachLoop(
|
|
|
74
75
|
]);
|
|
75
76
|
|
|
76
77
|
if (suspension) {
|
|
77
|
-
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
return wrapSuspensionContinuation(suspension, async () => {
|
|
79
|
+
const postScope = buildScope(
|
|
80
|
+
env,
|
|
81
|
+
iterationState,
|
|
82
|
+
{ item, index },
|
|
83
|
+
accumulator,
|
|
84
|
+
);
|
|
85
|
+
accumulator = await updateAccumulator(step, postScope, accumulator);
|
|
86
|
+
|
|
87
|
+
const shouldStop = await shouldStopLoop(step, postScope);
|
|
88
|
+
if (shouldStop) {
|
|
89
|
+
state.accumulator = accumulator;
|
|
90
|
+
if (step.accumulate && step.name) {
|
|
91
|
+
state.output[step.name] = { [step.accumulate.as]: accumulator };
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
iterationState,
|
|
88
|
-
{ item, index },
|
|
89
|
-
accumulator,
|
|
90
|
-
);
|
|
91
|
-
accumulator = await updateAccumulator(step, postScope, accumulator);
|
|
92
|
-
|
|
93
|
-
const shouldStop = await shouldStopLoop(step, postScope);
|
|
94
|
-
if (shouldStop) {
|
|
95
|
-
state.accumulator = accumulator;
|
|
96
|
-
if (step.accumulate && step.name) {
|
|
97
|
-
state.output[step.name] = { [step.accumulate.as]: accumulator };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
97
|
+
return executeForEachLoop(
|
|
98
|
+
env,
|
|
99
|
+
step,
|
|
100
|
+
{
|
|
101
|
+
...iterationState,
|
|
102
|
+
accumulator,
|
|
103
|
+
iterationStack: state.iterationStack,
|
|
104
|
+
},
|
|
105
|
+
path,
|
|
106
|
+
index + 1,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
116
109
|
}
|
|
117
110
|
|
|
118
111
|
const postScope = buildScope(
|
|
@@ -170,35 +163,27 @@ async function executeWhileLoop(
|
|
|
170
163
|
]);
|
|
171
164
|
|
|
172
165
|
if (suspension) {
|
|
173
|
-
return {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
166
|
+
return wrapSuspensionContinuation(suspension, async () => {
|
|
167
|
+
const postScope = buildScope(
|
|
168
|
+
env,
|
|
169
|
+
iterationState,
|
|
170
|
+
{ item: undefined, index },
|
|
171
|
+
accumulator,
|
|
172
|
+
);
|
|
173
|
+
accumulator = await updateAccumulator(step, postScope, accumulator);
|
|
174
|
+
|
|
175
|
+
return executeWhileLoop(
|
|
176
|
+
env,
|
|
177
|
+
step,
|
|
178
|
+
{
|
|
179
|
+
...iterationState,
|
|
185
180
|
accumulator,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
{
|
|
193
|
-
...iterationState,
|
|
194
|
-
accumulator,
|
|
195
|
-
iterationStack: state.iterationStack,
|
|
196
|
-
},
|
|
197
|
-
path,
|
|
198
|
-
index + 1,
|
|
199
|
-
);
|
|
200
|
-
},
|
|
201
|
-
};
|
|
181
|
+
iterationStack: state.iterationStack,
|
|
182
|
+
},
|
|
183
|
+
path,
|
|
184
|
+
index + 1,
|
|
185
|
+
);
|
|
186
|
+
});
|
|
202
187
|
}
|
|
203
188
|
|
|
204
189
|
const postScope = buildScope(
|