@acpus/runtime 0.6.1 → 0.6.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/evaluator.d.ts.map +1 -1
- package/dist/evaluator.js +29 -3
- package/dist/evaluator.js.map +1 -1
- package/dist/fork.d.ts +0 -18
- package/dist/fork.d.ts.map +1 -1
- package/dist/fork.js +16 -7
- package/dist/fork.js.map +1 -1
- package/dist/hooks/loader.d.ts.map +1 -1
- package/dist/hooks/loader.js +5 -63
- package/dist/hooks/loader.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/interpreter.d.ts +2 -4
- package/dist/interpreter.d.ts.map +1 -1
- package/dist/interpreter.js +179 -129
- package/dist/interpreter.js.map +1 -1
- package/dist/keys.d.ts +3 -4
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +62 -39
- package/dist/keys.js.map +1 -1
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/interpreter.js
CHANGED
|
@@ -247,9 +247,10 @@ export class WorkflowInterpreter {
|
|
|
247
247
|
}
|
|
248
248
|
break;
|
|
249
249
|
case "parallel":
|
|
250
|
-
(node.
|
|
251
|
-
const branchDynamic = nestedParallelBranchDynamic(dynamic,
|
|
252
|
-
|
|
250
|
+
(node.branches ?? []).forEach((branch) => {
|
|
251
|
+
const branchDynamic = nestedParallelBranchDynamic(dynamic, branch.id);
|
|
252
|
+
const branchCtx = { ...ctx, steps: { ...ctx.steps } };
|
|
253
|
+
this.replayNode(branch.child, branchCtx, runId, branchDynamic, keyPrefix, recorded, reached, evaluator);
|
|
253
254
|
});
|
|
254
255
|
break;
|
|
255
256
|
case "fanout": {
|
|
@@ -258,7 +259,7 @@ export class WorkflowInterpreter {
|
|
|
258
259
|
items.forEach((item, index) => {
|
|
259
260
|
const keyCtx = { ...ctx, item, item_index: index };
|
|
260
261
|
const itemId = this.extractItemId(item, node.metadata.key, index, keyCtx, evaluator);
|
|
261
|
-
const itemDynamic =
|
|
262
|
+
const itemDynamic = appendDynamicFrame(dynamic, { fanoutItemId: itemId, laneId: String(index) });
|
|
262
263
|
const itemCtx = { ...keyCtx, steps: { ...ctx.steps }, item_id: itemId };
|
|
263
264
|
for (const child of node.children ?? []) {
|
|
264
265
|
this.replayNode(child, itemCtx, runId, itemDynamic, keyPrefix, recorded, reached, evaluator);
|
|
@@ -270,9 +271,7 @@ export class WorkflowInterpreter {
|
|
|
270
271
|
for (const branch of node.branches ?? []) {
|
|
271
272
|
const taken = !branch.when || Boolean(evaluator.evaluateExpression(branch.when, ctx));
|
|
272
273
|
if (taken) {
|
|
273
|
-
|
|
274
|
-
this.replayNode(child, ctx, runId, dynamic, keyPrefix, recorded, reached, evaluator);
|
|
275
|
-
}
|
|
274
|
+
this.replayNode(branch.child, ctx, runId, dynamic, keyPrefix, recorded, reached, evaluator);
|
|
276
275
|
break;
|
|
277
276
|
}
|
|
278
277
|
}
|
|
@@ -283,7 +282,7 @@ export class WorkflowInterpreter {
|
|
|
283
282
|
let lastOutput;
|
|
284
283
|
for (let iter = 0; iter < maxIterations; iter++) {
|
|
285
284
|
const loopCtx = { ...ctx, loop: { iter, last: lastOutput } };
|
|
286
|
-
const loopDynamic =
|
|
285
|
+
const loopDynamic = appendDynamicFrame(dynamic, { loopRound: iter });
|
|
287
286
|
if (untilExpr && iter > 0 && evaluator.evaluateExpression(untilExpr, loopCtx))
|
|
288
287
|
break;
|
|
289
288
|
// Only descend while this round's children were actually recorded.
|
|
@@ -294,7 +293,8 @@ export class WorkflowInterpreter {
|
|
|
294
293
|
if (reached.size > before)
|
|
295
294
|
anyChildReached = true;
|
|
296
295
|
const childKey = withNodeKeyPrefix(keyPrefix, resolveNodeKey(child.keyTemplate, loopDynamic));
|
|
297
|
-
|
|
296
|
+
const recordedOutput = recorded.get(childKey)?.output;
|
|
297
|
+
lastOutput = recordedOutput !== undefined ? primaryOutput(recordedOutput) : lastOutput;
|
|
298
298
|
}
|
|
299
299
|
if (!anyChildReached)
|
|
300
300
|
break;
|
|
@@ -335,13 +335,14 @@ export class WorkflowInterpreter {
|
|
|
335
335
|
// transition. `attempt` is incremented by executeNode.
|
|
336
336
|
this.runControl.resetNodeForRetry(runId, state);
|
|
337
337
|
const ctx = this.buildContext(ir, input, runId);
|
|
338
|
-
this.
|
|
338
|
+
const retryTarget = this.hydrateContextForNodeRetry(ir.root, ctx, runId, nodeKey, {});
|
|
339
339
|
// Restore the parent dynamic value-context captured at first execution.
|
|
340
|
-
|
|
340
|
+
const retryCtx = retryTarget?.ctx ?? ctx;
|
|
341
|
+
this.restoreDynamicContext(retryCtx, state.dynamicContext);
|
|
341
342
|
// Retry re-runs the executable as a continuation: agents keep the same acpx
|
|
342
343
|
// session name and receive the fixed continuation prompt. The original full
|
|
343
344
|
// node key is preserved for stable identity.
|
|
344
|
-
await this.executeNode(node,
|
|
345
|
+
await this.executeNode(node, retryCtx, runId, retryTarget?.dynamic ?? {}, undefined, true, nodeKey);
|
|
345
346
|
}
|
|
346
347
|
// ─── Run-level controls ─────────────────────────────────────────
|
|
347
348
|
/**
|
|
@@ -565,18 +566,35 @@ export class WorkflowInterpreter {
|
|
|
565
566
|
// ─── Kind-specific execution ───────────────────────────────────
|
|
566
567
|
async executePipeline(node, ctx, runId, dynamic, keyPrefix) {
|
|
567
568
|
const children = node.children ?? [];
|
|
569
|
+
if (node.metadata.implicit === true) {
|
|
570
|
+
for (const child of children) {
|
|
571
|
+
try {
|
|
572
|
+
await this.executeNode(child, ctx, runId, dynamic, keyPrefix);
|
|
573
|
+
}
|
|
574
|
+
catch (error) {
|
|
575
|
+
if (error instanceof ScopeCompleted)
|
|
576
|
+
return { output: { ...ctx.steps } };
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return { output: { ...ctx.steps } };
|
|
581
|
+
}
|
|
582
|
+
const frame = { ...ctx, steps: { ...ctx.steps } };
|
|
583
|
+
let lastOutput;
|
|
568
584
|
for (const child of children) {
|
|
569
585
|
try {
|
|
570
|
-
await this.executeNode(child,
|
|
586
|
+
lastOutput = await this.executeNode(child, frame, runId, dynamic, keyPrefix);
|
|
571
587
|
}
|
|
572
588
|
catch (error) {
|
|
573
589
|
if (error instanceof ScopeCompleted)
|
|
574
|
-
return { output: error.output };
|
|
590
|
+
return { output: primaryOutput(error.output) };
|
|
575
591
|
throw error;
|
|
576
592
|
}
|
|
577
593
|
}
|
|
578
|
-
|
|
579
|
-
|
|
594
|
+
if (isRecord(node.metadata.outputs)) {
|
|
595
|
+
return { output: evaluateWorkflowOutputs({ outputs: node.metadata.outputs }, frame, this.evaluator) };
|
|
596
|
+
}
|
|
597
|
+
return { output: primaryOutput(lastOutput) };
|
|
580
598
|
}
|
|
581
599
|
async executeAgent(node, ctx, runId, signal, nodeKey, continuation) {
|
|
582
600
|
const retry = node.metadata.retry;
|
|
@@ -809,21 +827,20 @@ export class WorkflowInterpreter {
|
|
|
809
827
|
state.renderedPrompt = persisted?.renderedPrompt ?? state.renderedPrompt;
|
|
810
828
|
}
|
|
811
829
|
async executeParallel(node, ctx, runId, dynamic, nodeKey, keyPrefix) {
|
|
812
|
-
const
|
|
830
|
+
const branches = node.branches ?? [];
|
|
813
831
|
const maxConcurrency = node.metadata.max_concurrency ?? this.maxConcurrency;
|
|
814
832
|
const join = node.metadata.join ?? "all";
|
|
815
833
|
if (join === "all") {
|
|
816
|
-
|
|
817
|
-
this.materializePendingNode(runId, child, ctx, nestedParallelBranchDynamic(dynamic,
|
|
834
|
+
branches.forEach((branch) => {
|
|
835
|
+
this.materializePendingNode(runId, branch.child, ctx, nestedParallelBranchDynamic(dynamic, branch.id), keyPrefix);
|
|
818
836
|
});
|
|
819
837
|
}
|
|
820
838
|
const limit = pLimit(maxConcurrency);
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
const branchDynamic = nestedParallelBranchDynamic(dynamic, index);
|
|
839
|
+
const branchPromises = branches.map((branch) => limit(async () => {
|
|
840
|
+
const branchDynamic = nestedParallelBranchDynamic(dynamic, branch.id);
|
|
824
841
|
let output;
|
|
825
842
|
try {
|
|
826
|
-
output = await this.executeNode(child, { ...ctx, steps: { ...ctx.steps } }, runId, branchDynamic, keyPrefix);
|
|
843
|
+
output = await this.executeNode(branch.child, { ...ctx, steps: { ...ctx.steps } }, runId, branchDynamic, keyPrefix);
|
|
827
844
|
}
|
|
828
845
|
catch (error) {
|
|
829
846
|
if (error instanceof ScopeCompleted) {
|
|
@@ -833,7 +850,7 @@ export class WorkflowInterpreter {
|
|
|
833
850
|
throw error;
|
|
834
851
|
}
|
|
835
852
|
}
|
|
836
|
-
return {
|
|
853
|
+
return { branch, output };
|
|
837
854
|
}));
|
|
838
855
|
if (join === "race") {
|
|
839
856
|
try {
|
|
@@ -841,8 +858,7 @@ export class WorkflowInterpreter {
|
|
|
841
858
|
// consumed so their later rejection doesn't surface as unhandled.
|
|
842
859
|
const winner = await Promise.race(branchPromises);
|
|
843
860
|
branchPromises.forEach((p) => void p.catch(() => undefined));
|
|
844
|
-
const mapOutput = { [winner.
|
|
845
|
-
ctx.steps[winner.child.id] = winner.output;
|
|
861
|
+
const mapOutput = { [winner.branch.id]: primaryOutput(winner.output) };
|
|
846
862
|
return { output: mapOutput };
|
|
847
863
|
}
|
|
848
864
|
catch (error) {
|
|
@@ -871,9 +887,8 @@ export class WorkflowInterpreter {
|
|
|
871
887
|
throw error;
|
|
872
888
|
}
|
|
873
889
|
const mapOutput = {};
|
|
874
|
-
for (const {
|
|
875
|
-
mapOutput[
|
|
876
|
-
ctx.steps[child.id] = output;
|
|
890
|
+
for (const { branch, output } of results) {
|
|
891
|
+
mapOutput[branch.id] = primaryOutput(output);
|
|
877
892
|
}
|
|
878
893
|
return { output: mapOutput };
|
|
879
894
|
}
|
|
@@ -887,7 +902,10 @@ export class WorkflowInterpreter {
|
|
|
887
902
|
const join = node.metadata.join ?? "all";
|
|
888
903
|
const quorum = node.metadata.quorum;
|
|
889
904
|
const successCriteria = node.metadata.success_criteria;
|
|
890
|
-
const
|
|
905
|
+
const body = node.children?.[0];
|
|
906
|
+
if (!body) {
|
|
907
|
+
throw new Error(`fanout node ${node.id} missing body pipeline`);
|
|
908
|
+
}
|
|
891
909
|
const limit = pLimit(maxConcurrency);
|
|
892
910
|
// Success target (how many lanes must succeed). Default follows join.
|
|
893
911
|
const defaultMinSuccess = join === "race" ? 1 : join === "quorum" ? (quorum ?? 1) : items.length;
|
|
@@ -895,7 +913,7 @@ export class WorkflowInterpreter {
|
|
|
895
913
|
const lanePlan = items.map((item, index) => {
|
|
896
914
|
const keyCtx = { ...ctx, item, item_index: index };
|
|
897
915
|
const itemId = this.extractItemId(item, node.metadata.key, index, keyCtx, this.evaluator);
|
|
898
|
-
const laneDynamic = { fanoutItemId: itemId, laneId: String(index) };
|
|
916
|
+
const laneDynamic = appendDynamicFrame({}, { fanoutItemId: itemId, laneId: String(index) });
|
|
899
917
|
return {
|
|
900
918
|
itemId,
|
|
901
919
|
keyCtx,
|
|
@@ -904,10 +922,8 @@ export class WorkflowInterpreter {
|
|
|
904
922
|
});
|
|
905
923
|
if (join === "all") {
|
|
906
924
|
for (const lane of lanePlan) {
|
|
907
|
-
const itemDynamic =
|
|
908
|
-
|
|
909
|
-
this.materializePendingNode(runId, child, lane.keyCtx, itemDynamic, keyPrefix);
|
|
910
|
-
}
|
|
925
|
+
const itemDynamic = appendDynamicFrames(dynamic, lane.itemDynamic);
|
|
926
|
+
this.materializePendingNode(runId, body, lane.keyCtx, itemDynamic, keyPrefix);
|
|
911
927
|
}
|
|
912
928
|
}
|
|
913
929
|
// Fail-fast: once enough lanes have failed that the success target is
|
|
@@ -922,27 +938,19 @@ export class WorkflowInterpreter {
|
|
|
922
938
|
// not reject) so the join/min_success logic can run. A NodeAbortedError
|
|
923
939
|
// (operator pause/cancel) re-throws so it propagates to the parent.
|
|
924
940
|
const lanePromises = lanePlan.map((lane) => limit(async () => {
|
|
925
|
-
const itemDynamic =
|
|
941
|
+
const itemDynamic = appendDynamicFrames(dynamic, lane.itemDynamic);
|
|
926
942
|
const itemCtx = {
|
|
927
943
|
...lane.keyCtx,
|
|
928
944
|
steps: { ...ctx.steps },
|
|
929
945
|
item_id: lane.itemId
|
|
930
946
|
};
|
|
931
947
|
try {
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
try {
|
|
935
|
-
laneOutput = await this.executeNode(child, itemCtx, runId, itemDynamic, keyPrefix);
|
|
936
|
-
}
|
|
937
|
-
catch (error) {
|
|
938
|
-
if (error instanceof ScopeCompleted)
|
|
939
|
-
return { ok: true, output: error.output };
|
|
940
|
-
throw error;
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
return { ok: true, output: laneOutput };
|
|
948
|
+
const laneOutput = await this.executeNode(body, itemCtx, runId, itemDynamic, keyPrefix);
|
|
949
|
+
return { ok: true, output: primaryOutput(laneOutput) };
|
|
944
950
|
}
|
|
945
951
|
catch (error) {
|
|
952
|
+
if (error instanceof ScopeCompleted)
|
|
953
|
+
return { ok: true, output: primaryOutput(error.output) };
|
|
946
954
|
if (error instanceof NodeAbortedError) {
|
|
947
955
|
throw error;
|
|
948
956
|
}
|
|
@@ -1008,37 +1016,16 @@ export class WorkflowInterpreter {
|
|
|
1008
1016
|
async executeSwitch(node, ctx, runId, dynamic, keyPrefix) {
|
|
1009
1017
|
const branches = node.branches ?? [];
|
|
1010
1018
|
for (const branch of branches) {
|
|
1011
|
-
if (branch.when)
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
try {
|
|
1017
|
-
lastOutput = await this.executeNode(child, ctx, runId, dynamic, keyPrefix);
|
|
1018
|
-
}
|
|
1019
|
-
catch (error) {
|
|
1020
|
-
if (error instanceof ScopeCompleted)
|
|
1021
|
-
return { output: error.output };
|
|
1022
|
-
throw error;
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
return { output: lastOutput };
|
|
1026
|
-
}
|
|
1019
|
+
if (branch.when && !this.evaluator.evaluateExpression(branch.when, ctx))
|
|
1020
|
+
continue;
|
|
1021
|
+
// Default branch has no condition and is reached only after earlier cases fail.
|
|
1022
|
+
try {
|
|
1023
|
+
return { output: primaryOutput(await this.executeNode(branch.child, ctx, runId, dynamic, keyPrefix)) };
|
|
1027
1024
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
try {
|
|
1033
|
-
lastOutput = await this.executeNode(child, ctx, runId, dynamic, keyPrefix);
|
|
1034
|
-
}
|
|
1035
|
-
catch (error) {
|
|
1036
|
-
if (error instanceof ScopeCompleted)
|
|
1037
|
-
return { output: error.output };
|
|
1038
|
-
throw error;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
return { output: lastOutput };
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
if (error instanceof ScopeCompleted)
|
|
1027
|
+
return { output: primaryOutput(error.output) };
|
|
1028
|
+
throw error;
|
|
1042
1029
|
}
|
|
1043
1030
|
}
|
|
1044
1031
|
throw new Error(`Switch node ${node.id}: no branch matched and no default`);
|
|
@@ -1046,35 +1033,33 @@ export class WorkflowInterpreter {
|
|
|
1046
1033
|
async executeLoop(node, ctx, runId, dynamic, keyPrefix) {
|
|
1047
1034
|
const untilExpr = node.metadata.until;
|
|
1048
1035
|
const maxIterations = node.metadata.max_iterations ?? 100;
|
|
1049
|
-
const
|
|
1036
|
+
const body = node.children?.[0];
|
|
1037
|
+
if (!body) {
|
|
1038
|
+
throw new Error(`loop node ${node.id} missing body pipeline`);
|
|
1039
|
+
}
|
|
1050
1040
|
let lastOutput;
|
|
1051
1041
|
for (let iter = 0; iter < maxIterations; iter++) {
|
|
1052
1042
|
const loopCtx = {
|
|
1053
1043
|
...ctx,
|
|
1054
1044
|
loop: { iter, last: lastOutput }
|
|
1055
1045
|
};
|
|
1056
|
-
const loopDynamic =
|
|
1046
|
+
const loopDynamic = appendDynamicFrame(dynamic, { loopRound: iter });
|
|
1057
1047
|
// Check until condition (skip on first iteration)
|
|
1058
1048
|
if (untilExpr && iter > 0) {
|
|
1059
1049
|
const done = this.evaluator.evaluateExpression(untilExpr, loopCtx);
|
|
1060
1050
|
if (done)
|
|
1061
1051
|
break;
|
|
1062
1052
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
return { output: error.output };
|
|
1071
|
-
throw error;
|
|
1072
|
-
}
|
|
1053
|
+
try {
|
|
1054
|
+
lastOutput = primaryOutput(await this.executeNode(body, loopCtx, runId, loopDynamic, keyPrefix));
|
|
1055
|
+
}
|
|
1056
|
+
catch (error) {
|
|
1057
|
+
if (error instanceof ScopeCompleted)
|
|
1058
|
+
return { output: primaryOutput(error.output) };
|
|
1059
|
+
throw error;
|
|
1073
1060
|
}
|
|
1074
1061
|
}
|
|
1075
|
-
|
|
1076
|
-
// from executeNode, so return it directly (no extra wrapping).
|
|
1077
|
-
return lastOutput ?? { output: {} };
|
|
1062
|
+
return { output: lastOutput ?? {} };
|
|
1078
1063
|
}
|
|
1079
1064
|
executeGuard(node, ctx) {
|
|
1080
1065
|
const when = node.metadata.when;
|
|
@@ -1245,12 +1230,45 @@ export class WorkflowInterpreter {
|
|
|
1245
1230
|
buildContext(ir, input, runId) {
|
|
1246
1231
|
return { input, steps: {}, workflow: buildWorkflowExpressionContext(ir), run_id: runId };
|
|
1247
1232
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1233
|
+
hydrateContextForNodeRetry(node, ctx, runId, targetNodeKey, dynamic, keyPrefix) {
|
|
1234
|
+
const nodeKey = withNodeKeyPrefix(keyPrefix, resolveNodeKey(node.keyTemplate, dynamic));
|
|
1235
|
+
if (nodeKey === targetNodeKey)
|
|
1236
|
+
return { ctx, dynamic };
|
|
1237
|
+
const state = this.store.readNodeState(runId, nodeKey);
|
|
1238
|
+
if (state?.state === "completed" && state.output !== undefined && node.metadata.implicit !== true) {
|
|
1239
|
+
ctx.steps[node.id] = state.output;
|
|
1240
|
+
return undefined;
|
|
1241
|
+
}
|
|
1242
|
+
if (node.kind === "pipeline") {
|
|
1243
|
+
const frame = node.metadata.implicit === true ? ctx : { ...ctx, steps: { ...ctx.steps } };
|
|
1244
|
+
for (const child of node.children ?? []) {
|
|
1245
|
+
const found = this.hydrateContextForNodeRetry(child, frame, runId, targetNodeKey, dynamic, keyPrefix);
|
|
1246
|
+
if (found)
|
|
1247
|
+
return found;
|
|
1252
1248
|
}
|
|
1249
|
+
return undefined;
|
|
1253
1250
|
}
|
|
1251
|
+
if (node.kind === "parallel") {
|
|
1252
|
+
for (const branch of node.branches ?? []) {
|
|
1253
|
+
const branchCtx = { ...ctx, steps: { ...ctx.steps } };
|
|
1254
|
+
const branchDynamic = nestedParallelBranchDynamic(dynamic, branch.id);
|
|
1255
|
+
const found = this.hydrateContextForNodeRetry(branch.child, branchCtx, runId, targetNodeKey, branchDynamic, keyPrefix);
|
|
1256
|
+
if (found)
|
|
1257
|
+
return found;
|
|
1258
|
+
}
|
|
1259
|
+
return undefined;
|
|
1260
|
+
}
|
|
1261
|
+
for (const child of node.children ?? []) {
|
|
1262
|
+
const found = this.hydrateContextForNodeRetry(child, ctx, runId, targetNodeKey, dynamic, keyPrefix);
|
|
1263
|
+
if (found)
|
|
1264
|
+
return found;
|
|
1265
|
+
}
|
|
1266
|
+
for (const branch of node.branches ?? []) {
|
|
1267
|
+
const found = this.hydrateContextForNodeRetry(branch.child, ctx, runId, targetNodeKey, dynamic, keyPrefix);
|
|
1268
|
+
if (found)
|
|
1269
|
+
return found;
|
|
1270
|
+
}
|
|
1271
|
+
return undefined;
|
|
1254
1272
|
}
|
|
1255
1273
|
/** Extract the parent dynamic value-context (fanout item / loop round) from a context, if any. */
|
|
1256
1274
|
captureDynamicContext(ctx) {
|
|
@@ -1288,33 +1306,21 @@ export class WorkflowInterpreter {
|
|
|
1288
1306
|
ctx.loop = snapshot.loop;
|
|
1289
1307
|
}
|
|
1290
1308
|
findNodeByKey(root, nodeKey) {
|
|
1291
|
-
// Node keys contain dynamic dimensions (e.g. "workflow/mapped/item:0/lane:0")
|
|
1292
|
-
// but IR nodes have template keys without dynamics. Match by extracting
|
|
1293
|
-
// the nodePath from the resolved key and comparing to the template.
|
|
1294
|
-
// The nodePath is always the prefix before any dynamic segments.
|
|
1295
|
-
// We match by the IR node's id since that's unique and stable.
|
|
1296
|
-
const nodeId = this.extractNodeIdFromKey(nodeKey);
|
|
1297
|
-
return this.findNodeById(root, nodeId);
|
|
1298
|
-
}
|
|
1299
|
-
/** Extract the step ID from a resolved node key. The ID is the last path segment before dynamic dims. */
|
|
1300
|
-
extractNodeIdFromKey(nodeKey) {
|
|
1301
1309
|
const staticPath = staticNodePathFromKey(nodeKey);
|
|
1302
|
-
return
|
|
1310
|
+
return this.findNodeByPath(root, staticPath);
|
|
1303
1311
|
}
|
|
1304
|
-
|
|
1305
|
-
if (root.
|
|
1312
|
+
findNodeByPath(root, staticPath) {
|
|
1313
|
+
if (root.nodePath.join("/") === staticPath)
|
|
1306
1314
|
return root;
|
|
1307
1315
|
for (const child of root.children ?? []) {
|
|
1308
|
-
const found = this.
|
|
1316
|
+
const found = this.findNodeByPath(child, staticPath);
|
|
1309
1317
|
if (found)
|
|
1310
1318
|
return found;
|
|
1311
1319
|
}
|
|
1312
1320
|
for (const branch of root.branches ?? []) {
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
return found;
|
|
1317
|
-
}
|
|
1321
|
+
const found = this.findNodeByPath(branch.child, staticPath);
|
|
1322
|
+
if (found)
|
|
1323
|
+
return found;
|
|
1318
1324
|
}
|
|
1319
1325
|
return undefined;
|
|
1320
1326
|
}
|
|
@@ -1551,6 +1557,12 @@ function hookAgentTelemetry(state) {
|
|
|
1551
1557
|
}
|
|
1552
1558
|
return result;
|
|
1553
1559
|
}
|
|
1560
|
+
function primaryOutput(value) {
|
|
1561
|
+
return isRecord(value) && "output" in value ? value.output : value;
|
|
1562
|
+
}
|
|
1563
|
+
function isRecord(value) {
|
|
1564
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1565
|
+
}
|
|
1554
1566
|
/** Generate a locally sortable run ID: yyyyMMddHHmmss + 20 uppercase hex chars. */
|
|
1555
1567
|
export function generateRunId(now = new Date()) {
|
|
1556
1568
|
const pad = (n) => String(n).padStart(2, "0");
|
|
@@ -1564,12 +1576,52 @@ export function generateRunId(now = new Date()) {
|
|
|
1564
1576
|
].join("");
|
|
1565
1577
|
return `${timestamp}${randomBytes(10).toString("hex").toUpperCase()}`;
|
|
1566
1578
|
}
|
|
1567
|
-
function nestedParallelBranchDynamic(dynamic,
|
|
1568
|
-
const
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1579
|
+
function nestedParallelBranchDynamic(dynamic, branchId) {
|
|
1580
|
+
const current = currentDynamicFrame(dynamic);
|
|
1581
|
+
const nextBranchId = current.parallelBranchId === undefined ? branchId : `${current.parallelBranchId}.${branchId}`;
|
|
1582
|
+
return replaceCurrentDynamicFrame(dynamic, { ...current, parallelBranchId: nextBranchId });
|
|
1583
|
+
}
|
|
1584
|
+
function appendDynamicFrame(dynamic, frame) {
|
|
1585
|
+
const frames = [...dynamicFrames(dynamic), frame];
|
|
1586
|
+
return withCollapsedDynamic(frames);
|
|
1587
|
+
}
|
|
1588
|
+
function appendDynamicFrames(dynamic, child) {
|
|
1589
|
+
return withCollapsedDynamic([...dynamicFrames(dynamic), ...dynamicFrames(child)]);
|
|
1590
|
+
}
|
|
1591
|
+
function replaceCurrentDynamicFrame(dynamic, frame) {
|
|
1592
|
+
const frames = dynamicFrames(dynamic);
|
|
1593
|
+
if (frames.length === 0)
|
|
1594
|
+
return withCollapsedDynamic([frame]);
|
|
1595
|
+
return withCollapsedDynamic([...frames.slice(0, -1), frame]);
|
|
1596
|
+
}
|
|
1597
|
+
function currentDynamicFrame(dynamic) {
|
|
1598
|
+
return dynamicFrames(dynamic).at(-1) ?? {};
|
|
1599
|
+
}
|
|
1600
|
+
function dynamicFrames(dynamic) {
|
|
1601
|
+
if (dynamic.frames && dynamic.frames.length > 0)
|
|
1602
|
+
return dynamic.frames;
|
|
1603
|
+
const { frames: _frames, ...frame } = dynamic;
|
|
1604
|
+
return isEmptyDynamicFrame(frame) ? [] : [frame];
|
|
1605
|
+
}
|
|
1606
|
+
function withCollapsedDynamic(frames) {
|
|
1607
|
+
const collapsed = { frames };
|
|
1608
|
+
for (const frame of frames) {
|
|
1609
|
+
if (frame.loopRound !== undefined)
|
|
1610
|
+
collapsed.loopRound = frame.loopRound;
|
|
1611
|
+
if (frame.fanoutItemId !== undefined)
|
|
1612
|
+
collapsed.fanoutItemId = frame.fanoutItemId;
|
|
1613
|
+
if (frame.laneId !== undefined)
|
|
1614
|
+
collapsed.laneId = frame.laneId;
|
|
1615
|
+
if (frame.parallelBranchId !== undefined)
|
|
1616
|
+
collapsed.parallelBranchId = frame.parallelBranchId;
|
|
1617
|
+
}
|
|
1618
|
+
return collapsed;
|
|
1619
|
+
}
|
|
1620
|
+
function isEmptyDynamicFrame(frame) {
|
|
1621
|
+
return frame.loopRound === undefined &&
|
|
1622
|
+
frame.fanoutItemId === undefined &&
|
|
1623
|
+
frame.laneId === undefined &&
|
|
1624
|
+
frame.parallelBranchId === undefined;
|
|
1573
1625
|
}
|
|
1574
1626
|
/** Find the immediate parent IR node of `childId`, or undefined for the root. */
|
|
1575
1627
|
function findParentNode(node, childId) {
|
|
@@ -1581,13 +1633,11 @@ function findParentNode(node, childId) {
|
|
|
1581
1633
|
return found;
|
|
1582
1634
|
}
|
|
1583
1635
|
for (const branch of node.branches ?? []) {
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
return found;
|
|
1590
|
-
}
|
|
1636
|
+
if (branch.child.id === childId)
|
|
1637
|
+
return node;
|
|
1638
|
+
const found = findParentNode(branch.child, childId);
|
|
1639
|
+
if (found)
|
|
1640
|
+
return found;
|
|
1591
1641
|
}
|
|
1592
1642
|
return undefined;
|
|
1593
1643
|
}
|