@emmvish/stable-request 1.6.1 → 1.6.2
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 +228 -1974
- package/dist/core/stable-workflow.d.ts.map +1 -1
- package/dist/core/stable-workflow.js +3 -1
- package/dist/core/stable-workflow.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/types/index.d.ts +27 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utilities/execute-branch-workflow.d.ts.map +1 -1
- package/dist/utilities/execute-branch-workflow.js +385 -191
- package/dist/utilities/execute-branch-workflow.js.map +1 -1
- package/package.json +4 -1
- package/dist/utilities/process-phase-result.d.ts +0 -1
- package/dist/utilities/process-phase-result.d.ts.map +0 -1
- package/dist/utilities/process-phase-result.js +0 -2
- package/dist/utilities/process-phase-result.js.map +0 -1
|
@@ -2,110 +2,296 @@ import { executeNonLinearWorkflow } from './execute-non-linear-workflow.js';
|
|
|
2
2
|
import { safelyExecuteUnknownFunction } from './safely-execute-unknown-function.js';
|
|
3
3
|
import { PHASE_DECISION_ACTIONS } from '../enums/index.js';
|
|
4
4
|
export async function executeBranchWorkflow(context) {
|
|
5
|
-
const { branches, workflowId, commonGatewayOptions, requestGroups, logPhaseResults, handlePhaseCompletion, handlePhaseError = () => { }, handleBranchCompletion, maxSerializableChars, workflowHookParams, sharedBuffer, stopOnFirstPhaseError, maxWorkflowIterations } = context;
|
|
5
|
+
const { branches, workflowId, commonGatewayOptions, requestGroups, logPhaseResults, handlePhaseCompletion, handlePhaseError = () => { }, handleBranchCompletion, handleBranchDecision, maxSerializableChars, workflowHookParams, sharedBuffer, stopOnFirstPhaseError, maxWorkflowIterations } = context;
|
|
6
6
|
const branchResults = [];
|
|
7
7
|
const allPhaseResults = [];
|
|
8
8
|
const executionHistory = [];
|
|
9
|
+
const branchExecutionHistory = [];
|
|
10
|
+
const branchExecutionCounts = new Map();
|
|
9
11
|
let totalRequests = 0;
|
|
10
12
|
let successfulRequests = 0;
|
|
11
13
|
let failedRequests = 0;
|
|
12
14
|
let terminatedEarly = false;
|
|
13
15
|
let terminationReason;
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
parallelBranches.push(branch);
|
|
16
|
+
let iterationCount = 0;
|
|
17
|
+
const mergeBranchConfig = (branch) => {
|
|
18
|
+
if (!branch.commonConfig) {
|
|
19
|
+
return commonGatewayOptions;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
return {
|
|
22
|
+
...commonGatewayOptions,
|
|
23
|
+
...branch.commonConfig
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
const executeSingleBranch = async (branch, branchIndex, executionNumber) => {
|
|
27
|
+
const branchStartTime = Date.now();
|
|
28
|
+
try {
|
|
29
|
+
const branchConfig = mergeBranchConfig(branch);
|
|
30
|
+
const result = await executeNonLinearWorkflow({
|
|
31
|
+
phases: branch.phases,
|
|
32
|
+
workflowId: `${workflowId}-branch-${branch.id}`,
|
|
33
|
+
commonGatewayOptions: branchConfig,
|
|
34
|
+
requestGroups,
|
|
35
|
+
logPhaseResults,
|
|
36
|
+
handlePhaseCompletion,
|
|
37
|
+
handlePhaseError,
|
|
38
|
+
handlePhaseDecision: workflowHookParams?.handlePhaseDecision,
|
|
39
|
+
maxSerializableChars,
|
|
40
|
+
workflowHookParams,
|
|
41
|
+
sharedBuffer,
|
|
42
|
+
stopOnFirstPhaseError,
|
|
43
|
+
maxWorkflowIterations
|
|
44
|
+
});
|
|
45
|
+
const branchExecutionTime = Date.now() - branchStartTime;
|
|
46
|
+
const branchResult = {
|
|
47
|
+
branchId: branch.id,
|
|
48
|
+
branchIndex,
|
|
49
|
+
success: result.failedRequests === 0,
|
|
50
|
+
executionTime: branchExecutionTime,
|
|
51
|
+
completedPhases: result.phaseResults.length,
|
|
52
|
+
phaseResults: result.phaseResults,
|
|
53
|
+
executionNumber
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
branchResult,
|
|
57
|
+
phaseResults: result.phaseResults,
|
|
58
|
+
executionHistory: result.executionHistory,
|
|
59
|
+
totalRequests: result.totalRequests,
|
|
60
|
+
successfulRequests: result.successfulRequests,
|
|
61
|
+
failedRequests: result.failedRequests
|
|
62
|
+
};
|
|
22
63
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Branch ${branch.id} failed:`, error);
|
|
66
|
+
return {
|
|
67
|
+
branchResult: {
|
|
68
|
+
branchId: branch.id,
|
|
69
|
+
branchIndex,
|
|
70
|
+
success: false,
|
|
71
|
+
executionTime: Date.now() - branchStartTime,
|
|
72
|
+
completedPhases: 0,
|
|
73
|
+
phaseResults: [],
|
|
74
|
+
executionNumber,
|
|
75
|
+
error: error?.message || 'Branch execution failed',
|
|
76
|
+
decision: undefined
|
|
77
|
+
},
|
|
78
|
+
phaseResults: [],
|
|
79
|
+
executionHistory: [],
|
|
80
|
+
totalRequests: 0,
|
|
81
|
+
successfulRequests: 0,
|
|
82
|
+
failedRequests: 1
|
|
83
|
+
};
|
|
27
84
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
};
|
|
86
|
+
let currentBranchId = branches[0]?.id || null;
|
|
87
|
+
while (currentBranchId !== null && iterationCount < maxWorkflowIterations) {
|
|
88
|
+
iterationCount++;
|
|
89
|
+
const branchIndex = branches.findIndex(b => b.id === currentBranchId);
|
|
90
|
+
if (branchIndex === -1) {
|
|
91
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Branch '${currentBranchId}' not found`);
|
|
92
|
+
terminatedEarly = true;
|
|
93
|
+
terminationReason = `Branch '${currentBranchId}' not found`;
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
const currentBranch = branches[branchIndex];
|
|
97
|
+
const executionNumber = (branchExecutionCounts.get(currentBranchId) || 0) + 1;
|
|
98
|
+
branchExecutionCounts.set(currentBranchId, executionNumber);
|
|
99
|
+
const maxReplayCount = currentBranch.maxReplayCount ?? Infinity;
|
|
100
|
+
if (executionNumber > maxReplayCount + 1) {
|
|
101
|
+
if (logPhaseResults) {
|
|
102
|
+
console.warn(`stable-request: [Workflow: ${workflowId}] Branch '${currentBranchId}' exceeded max replay count (${maxReplayCount}). Skipping.`);
|
|
103
|
+
}
|
|
104
|
+
const skippedResult = {
|
|
105
|
+
branchId: currentBranchId,
|
|
106
|
+
branchIndex,
|
|
107
|
+
success: false,
|
|
108
|
+
executionTime: 0,
|
|
109
|
+
completedPhases: 0,
|
|
110
|
+
phaseResults: [],
|
|
111
|
+
executionNumber,
|
|
112
|
+
skipped: true,
|
|
113
|
+
error: `Exceeded max replay count of ${maxReplayCount}`
|
|
114
|
+
};
|
|
115
|
+
branchResults.push(skippedResult);
|
|
116
|
+
branchExecutionHistory.push({
|
|
117
|
+
branchId: currentBranchId,
|
|
118
|
+
branchIndex,
|
|
119
|
+
executionNumber,
|
|
120
|
+
timestamp: new Date().toISOString(),
|
|
121
|
+
success: false,
|
|
122
|
+
executionTime: 0
|
|
123
|
+
});
|
|
124
|
+
currentBranchId = branches[branchIndex + 1]?.id || null;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const isConcurrent = currentBranch.markConcurrentBranch;
|
|
128
|
+
if (isConcurrent) {
|
|
129
|
+
const concurrentGroup = [
|
|
130
|
+
{ branch: currentBranch, index: branchIndex, executionNumber }
|
|
131
|
+
];
|
|
132
|
+
let j = branchIndex + 1;
|
|
133
|
+
while (j < branches.length && branches[j].markConcurrentBranch) {
|
|
134
|
+
const concurrentBranch = branches[j];
|
|
135
|
+
const concurrentExecNum = (branchExecutionCounts.get(concurrentBranch.id) || 0) + 1;
|
|
136
|
+
branchExecutionCounts.set(concurrentBranch.id, concurrentExecNum);
|
|
137
|
+
concurrentGroup.push({ branch: concurrentBranch, index: j, executionNumber: concurrentExecNum });
|
|
138
|
+
j++;
|
|
139
|
+
}
|
|
140
|
+
if (logPhaseResults) {
|
|
141
|
+
const branchIds = concurrentGroup.map(({ branch }) => branch.id).join(', ');
|
|
142
|
+
console.info(`\nstable-request: [Workflow: ${workflowId}] Executing ${concurrentGroup.length} branches in parallel: [${branchIds}]`);
|
|
143
|
+
}
|
|
144
|
+
const groupPromises = concurrentGroup.map(({ branch, index, executionNumber }) => executeSingleBranch(branch, index, executionNumber));
|
|
145
|
+
const groupResults = await Promise.all(groupPromises);
|
|
146
|
+
const concurrentBranchResults = [];
|
|
147
|
+
let concurrentGroupJumpTarget = null;
|
|
148
|
+
let concurrentGroupSkipTarget = null;
|
|
149
|
+
let shouldTerminate = false;
|
|
150
|
+
for (let k = 0; k < groupResults.length; k++) {
|
|
151
|
+
const result = groupResults[k];
|
|
152
|
+
const { branch, index, executionNumber } = concurrentGroup[k];
|
|
153
|
+
concurrentBranchResults.push(result.branchResult);
|
|
154
|
+
branchResults.push(result.branchResult);
|
|
155
|
+
allPhaseResults.push(...result.phaseResults);
|
|
156
|
+
executionHistory.push(...result.executionHistory);
|
|
157
|
+
totalRequests += result.totalRequests;
|
|
158
|
+
successfulRequests += result.successfulRequests;
|
|
159
|
+
failedRequests += result.failedRequests;
|
|
160
|
+
branchExecutionHistory.push({
|
|
48
161
|
branchId: branch.id,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
162
|
+
branchIndex: index,
|
|
163
|
+
executionNumber,
|
|
164
|
+
timestamp: new Date().toISOString(),
|
|
165
|
+
success: result.branchResult.success,
|
|
166
|
+
executionTime: result.branchResult.executionTime
|
|
167
|
+
});
|
|
168
|
+
if (handleBranchCompletion) {
|
|
169
|
+
try {
|
|
170
|
+
await safelyExecuteUnknownFunction(handleBranchCompletion, {
|
|
171
|
+
branchId: result.branchResult.branchId,
|
|
172
|
+
branchResults: result.branchResult.phaseResults,
|
|
173
|
+
success: result.branchResult.success
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch (hookError) {
|
|
177
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Error in handleBranchCompletion hook:`, hookError);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
54
180
|
if (branch.branchDecisionHook) {
|
|
55
181
|
try {
|
|
56
182
|
const decision = await safelyExecuteUnknownFunction(branch.branchDecisionHook, {
|
|
57
183
|
workflowId,
|
|
58
184
|
branchResults: result.phaseResults,
|
|
59
185
|
branchId: branch.id,
|
|
186
|
+
branchIndex: index,
|
|
187
|
+
executionNumber,
|
|
60
188
|
executionHistory: result.executionHistory,
|
|
189
|
+
branchExecutionHistory,
|
|
61
190
|
sharedBuffer,
|
|
62
|
-
params: workflowHookParams?.handleBranchDecisionParams
|
|
191
|
+
params: workflowHookParams?.handleBranchDecisionParams,
|
|
192
|
+
concurrentBranchResults: concurrentBranchResults
|
|
63
193
|
});
|
|
64
|
-
branchResult.decision = decision;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
194
|
+
result.branchResult.decision = decision;
|
|
195
|
+
const historyRecord = branchExecutionHistory.find(h => h.branchId === branch.id && h.executionNumber === executionNumber);
|
|
196
|
+
if (historyRecord) {
|
|
197
|
+
historyRecord.decision = decision;
|
|
198
|
+
}
|
|
199
|
+
if (handleBranchDecision) {
|
|
200
|
+
try {
|
|
201
|
+
await safelyExecuteUnknownFunction(handleBranchDecision, decision, result.branchResult);
|
|
202
|
+
}
|
|
203
|
+
catch (hookError) {
|
|
204
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Error in handleBranchDecision hook:`, hookError);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (k === groupResults.length - 1) {
|
|
208
|
+
if (decision.action === PHASE_DECISION_ACTIONS.TERMINATE) {
|
|
209
|
+
shouldTerminate = true;
|
|
210
|
+
terminationReason = decision.metadata?.reason || `Branch ${branch.id} terminated workflow`;
|
|
211
|
+
}
|
|
212
|
+
else if (decision.action === PHASE_DECISION_ACTIONS.JUMP) {
|
|
213
|
+
concurrentGroupJumpTarget = decision.targetBranchId || null;
|
|
214
|
+
}
|
|
215
|
+
else if (decision.action === PHASE_DECISION_ACTIONS.SKIP) {
|
|
216
|
+
concurrentGroupSkipTarget = decision.targetBranchId || null;
|
|
217
|
+
}
|
|
218
|
+
if (logPhaseResults && decision.action !== PHASE_DECISION_ACTIONS.CONTINUE) {
|
|
219
|
+
console.info(`stable-request: [Workflow: ${workflowId}] Concurrent group decision: ${decision.action}`, decision.targetBranchId ? `-> ${decision.targetBranchId}` : '');
|
|
220
|
+
}
|
|
68
221
|
}
|
|
69
222
|
}
|
|
70
223
|
catch (decisionError) {
|
|
71
224
|
console.error(`stable-request: [Workflow: ${workflowId}] Error in branch decision hook for ${branch.id}:`, decisionError);
|
|
72
225
|
}
|
|
73
226
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
successfulRequests: result.successfulRequests,
|
|
80
|
-
failedRequests: result.failedRequests
|
|
81
|
-
};
|
|
227
|
+
if (stopOnFirstPhaseError && result.failedRequests > 0) {
|
|
228
|
+
shouldTerminate = true;
|
|
229
|
+
terminationReason = `Branch ${branch.id} in concurrent group failed`;
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
82
232
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
branchResult: {
|
|
87
|
-
branchId: branch.id,
|
|
88
|
-
success: false,
|
|
89
|
-
executionTime: Date.now() - branchStartTime,
|
|
90
|
-
completedPhases: 0,
|
|
91
|
-
phaseResults: []
|
|
92
|
-
},
|
|
93
|
-
phaseResults: [],
|
|
94
|
-
executionHistory: [],
|
|
95
|
-
totalRequests: 0,
|
|
96
|
-
successfulRequests: 0,
|
|
97
|
-
failedRequests: 1
|
|
98
|
-
};
|
|
233
|
+
if (shouldTerminate) {
|
|
234
|
+
terminatedEarly = true;
|
|
235
|
+
break;
|
|
99
236
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
237
|
+
if (concurrentGroupJumpTarget) {
|
|
238
|
+
currentBranchId = concurrentGroupJumpTarget;
|
|
239
|
+
}
|
|
240
|
+
else if (concurrentGroupSkipTarget) {
|
|
241
|
+
const skipTargetIndex = branches.findIndex(b => b.id === concurrentGroupSkipTarget);
|
|
242
|
+
if (skipTargetIndex !== -1) {
|
|
243
|
+
for (let skipIdx = j; skipIdx < skipTargetIndex; skipIdx++) {
|
|
244
|
+
const skippedBranch = branches[skipIdx];
|
|
245
|
+
const skippedResult = {
|
|
246
|
+
branchId: skippedBranch.id,
|
|
247
|
+
branchIndex: skipIdx,
|
|
248
|
+
success: true,
|
|
249
|
+
executionTime: 0,
|
|
250
|
+
completedPhases: 0,
|
|
251
|
+
phaseResults: [],
|
|
252
|
+
executionNumber: 1,
|
|
253
|
+
skipped: true
|
|
254
|
+
};
|
|
255
|
+
branchResults.push(skippedResult);
|
|
256
|
+
branchExecutionHistory.push({
|
|
257
|
+
branchId: skippedBranch.id,
|
|
258
|
+
branchIndex: skipIdx,
|
|
259
|
+
executionNumber: 1,
|
|
260
|
+
timestamp: new Date().toISOString(),
|
|
261
|
+
success: true,
|
|
262
|
+
executionTime: 0,
|
|
263
|
+
decision: { action: PHASE_DECISION_ACTIONS.SKIP }
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
currentBranchId = concurrentGroupSkipTarget;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
currentBranchId = branches[j]?.id || null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
currentBranchId = branches[j]?.id || null;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
if (logPhaseResults) {
|
|
278
|
+
console.info(`\nstable-request: [Workflow: ${workflowId}] Executing branch: ${currentBranch.id} (execution #${executionNumber})`);
|
|
279
|
+
}
|
|
280
|
+
const result = await executeSingleBranch(currentBranch, branchIndex, executionNumber);
|
|
103
281
|
branchResults.push(result.branchResult);
|
|
104
282
|
allPhaseResults.push(...result.phaseResults);
|
|
105
283
|
executionHistory.push(...result.executionHistory);
|
|
106
284
|
totalRequests += result.totalRequests;
|
|
107
285
|
successfulRequests += result.successfulRequests;
|
|
108
286
|
failedRequests += result.failedRequests;
|
|
287
|
+
branchExecutionHistory.push({
|
|
288
|
+
branchId: currentBranchId,
|
|
289
|
+
branchIndex,
|
|
290
|
+
executionNumber,
|
|
291
|
+
timestamp: new Date().toISOString(),
|
|
292
|
+
success: result.branchResult.success,
|
|
293
|
+
executionTime: result.branchResult.executionTime
|
|
294
|
+
});
|
|
109
295
|
if (handleBranchCompletion) {
|
|
110
296
|
try {
|
|
111
297
|
await safelyExecuteUnknownFunction(handleBranchCompletion, {
|
|
@@ -118,154 +304,162 @@ export async function executeBranchWorkflow(context) {
|
|
|
118
304
|
console.error(`stable-request: [Workflow: ${workflowId}] Error in handleBranchCompletion hook:`, hookError);
|
|
119
305
|
}
|
|
120
306
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
branchResults,
|
|
125
|
-
allPhaseResults,
|
|
126
|
-
executionHistory,
|
|
127
|
-
totalRequests,
|
|
128
|
-
successfulRequests,
|
|
129
|
-
failedRequests,
|
|
130
|
-
terminatedEarly,
|
|
131
|
-
terminationReason
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
let branchIndex = 0;
|
|
136
|
-
while (branchIndex < serialBranches.length) {
|
|
137
|
-
const branch = serialBranches[branchIndex];
|
|
138
|
-
if (logPhaseResults) {
|
|
139
|
-
console.info(`\nstable-request: [Workflow: ${workflowId}] Executing branch: ${branch.id}`);
|
|
140
|
-
}
|
|
141
|
-
const branchStartTime = Date.now();
|
|
142
|
-
try {
|
|
143
|
-
const result = await executeNonLinearWorkflow({
|
|
144
|
-
phases: branch.phases,
|
|
145
|
-
workflowId: `${workflowId}-branch-${branch.id}`,
|
|
146
|
-
commonGatewayOptions,
|
|
147
|
-
requestGroups,
|
|
148
|
-
logPhaseResults,
|
|
149
|
-
handlePhaseCompletion,
|
|
150
|
-
handlePhaseError,
|
|
151
|
-
handlePhaseDecision: workflowHookParams?.handlePhaseDecision,
|
|
152
|
-
maxSerializableChars,
|
|
153
|
-
workflowHookParams,
|
|
154
|
-
sharedBuffer,
|
|
155
|
-
stopOnFirstPhaseError,
|
|
156
|
-
maxWorkflowIterations
|
|
157
|
-
});
|
|
158
|
-
const branchExecutionTime = Date.now() - branchStartTime;
|
|
159
|
-
const branchResult = {
|
|
160
|
-
branchId: branch.id,
|
|
161
|
-
success: result.failedRequests === 0,
|
|
162
|
-
executionTime: branchExecutionTime,
|
|
163
|
-
completedPhases: result.phaseResults.length,
|
|
164
|
-
phaseResults: result.phaseResults
|
|
165
|
-
};
|
|
166
|
-
let shouldJump = false;
|
|
167
|
-
let jumpTargetIndex = -1;
|
|
168
|
-
if (branch.branchDecisionHook) {
|
|
307
|
+
let decision = { action: PHASE_DECISION_ACTIONS.CONTINUE };
|
|
308
|
+
if (currentBranch.branchDecisionHook) {
|
|
169
309
|
try {
|
|
170
|
-
|
|
310
|
+
decision = await safelyExecuteUnknownFunction(currentBranch.branchDecisionHook, {
|
|
171
311
|
workflowId,
|
|
172
312
|
branchResults: result.phaseResults,
|
|
173
|
-
branchId:
|
|
313
|
+
branchId: currentBranch.id,
|
|
314
|
+
branchIndex,
|
|
315
|
+
executionNumber,
|
|
174
316
|
executionHistory: result.executionHistory,
|
|
317
|
+
branchExecutionHistory,
|
|
175
318
|
sharedBuffer,
|
|
176
|
-
params: workflowHookParams?.handleBranchDecisionParams
|
|
177
|
-
parallelBranchResults: branchResults
|
|
178
|
-
.filter(br => parallelBranches.some(pb => pb.id === br.branchId))
|
|
179
|
-
.map(br => br.phaseResults)
|
|
319
|
+
params: workflowHookParams?.handleBranchDecisionParams
|
|
180
320
|
});
|
|
181
|
-
branchResult.decision = decision;
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
branchResults.push(branchResult);
|
|
186
|
-
allPhaseResults.push(...result.phaseResults);
|
|
187
|
-
executionHistory.push(...result.executionHistory);
|
|
188
|
-
totalRequests += result.totalRequests;
|
|
189
|
-
successfulRequests += result.successfulRequests;
|
|
190
|
-
failedRequests += result.failedRequests;
|
|
191
|
-
break;
|
|
321
|
+
result.branchResult.decision = decision;
|
|
322
|
+
const historyRecord = branchExecutionHistory.find(h => h.branchId === currentBranchId && h.executionNumber === executionNumber);
|
|
323
|
+
if (historyRecord) {
|
|
324
|
+
historyRecord.decision = decision;
|
|
192
325
|
}
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
shouldJump = true;
|
|
200
|
-
jumpTargetIndex = targetBranchIndex;
|
|
201
|
-
}
|
|
202
|
-
else if (targetBranchIndex === -1) {
|
|
203
|
-
console.warn(`stable-request: [Workflow: ${workflowId}] Target branch ${decision.targetBranchId} not found`);
|
|
326
|
+
if (logPhaseResults) {
|
|
327
|
+
console.info(`stable-request: [Workflow: ${workflowId}] Branch '${currentBranchId}' decision: ${decision.action}`, decision.targetBranchId ? `-> ${decision.targetBranchId}` : '');
|
|
328
|
+
}
|
|
329
|
+
if (handleBranchDecision) {
|
|
330
|
+
try {
|
|
331
|
+
await safelyExecuteUnknownFunction(handleBranchDecision, decision, result.branchResult);
|
|
204
332
|
}
|
|
205
|
-
|
|
206
|
-
console.
|
|
333
|
+
catch (hookError) {
|
|
334
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Error in handleBranchDecision hook:`, hookError);
|
|
207
335
|
}
|
|
208
336
|
}
|
|
209
337
|
}
|
|
210
338
|
catch (decisionError) {
|
|
211
|
-
console.error(`stable-request: [Workflow: ${workflowId}] Error in branch decision hook for ${
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
branchResults.push(branchResult);
|
|
215
|
-
allPhaseResults.push(...result.phaseResults);
|
|
216
|
-
executionHistory.push(...result.executionHistory);
|
|
217
|
-
totalRequests += result.totalRequests;
|
|
218
|
-
successfulRequests += result.successfulRequests;
|
|
219
|
-
failedRequests += result.failedRequests;
|
|
220
|
-
if (handleBranchCompletion) {
|
|
221
|
-
try {
|
|
222
|
-
await safelyExecuteUnknownFunction(handleBranchCompletion, {
|
|
223
|
-
branchId: branchResult.branchId,
|
|
224
|
-
branchResults: branchResult.phaseResults,
|
|
225
|
-
success: branchResult.success
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
catch (hookError) {
|
|
229
|
-
console.error(`stable-request: [Workflow: ${workflowId}] Error in handleBranchCompletion hook:`, hookError);
|
|
339
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Error in branch decision hook for ${currentBranch.id}:`, decisionError);
|
|
340
|
+
decision = { action: PHASE_DECISION_ACTIONS.CONTINUE };
|
|
230
341
|
}
|
|
231
342
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
343
|
+
switch (decision.action) {
|
|
344
|
+
case PHASE_DECISION_ACTIONS.TERMINATE:
|
|
345
|
+
terminatedEarly = true;
|
|
346
|
+
terminationReason = decision.metadata?.reason || `Branch ${currentBranchId} terminated workflow`;
|
|
347
|
+
currentBranchId = null;
|
|
348
|
+
break;
|
|
349
|
+
case PHASE_DECISION_ACTIONS.JUMP:
|
|
350
|
+
if (decision.targetBranchId) {
|
|
351
|
+
const targetIndex = branches.findIndex(b => b.id === decision.targetBranchId);
|
|
352
|
+
if (targetIndex === -1) {
|
|
353
|
+
console.error(`stable-request: [Workflow: ${workflowId}] Jump target branch '${decision.targetBranchId}' not found`);
|
|
354
|
+
terminatedEarly = true;
|
|
355
|
+
terminationReason = `Jump target branch '${decision.targetBranchId}' not found`;
|
|
356
|
+
currentBranchId = null;
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
currentBranchId = decision.targetBranchId;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
currentBranchId = branches[branchIndex + 1]?.id || null;
|
|
364
|
+
}
|
|
365
|
+
break;
|
|
366
|
+
case PHASE_DECISION_ACTIONS.SKIP:
|
|
367
|
+
if (!currentBranch.allowSkip && currentBranch.allowSkip !== undefined) {
|
|
368
|
+
console.warn(`stable-request: [Workflow: ${workflowId}] Branch '${currentBranchId}' attempted to skip but allowSkip is false. Continuing normally.`);
|
|
369
|
+
currentBranchId = branches[branchIndex + 1]?.id || null;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
if (decision.targetBranchId) {
|
|
373
|
+
const skipTargetIndex = branches.findIndex(b => b.id === decision.targetBranchId);
|
|
374
|
+
if (skipTargetIndex !== -1) {
|
|
375
|
+
for (let skipIdx = branchIndex + 1; skipIdx < skipTargetIndex; skipIdx++) {
|
|
376
|
+
const skippedBranch = branches[skipIdx];
|
|
377
|
+
const skippedResult = {
|
|
378
|
+
branchId: skippedBranch.id,
|
|
379
|
+
branchIndex: skipIdx,
|
|
380
|
+
success: true,
|
|
381
|
+
executionTime: 0,
|
|
382
|
+
completedPhases: 0,
|
|
383
|
+
phaseResults: [],
|
|
384
|
+
executionNumber: 1,
|
|
385
|
+
skipped: true
|
|
386
|
+
};
|
|
387
|
+
branchResults.push(skippedResult);
|
|
388
|
+
branchExecutionHistory.push({
|
|
389
|
+
branchId: skippedBranch.id,
|
|
390
|
+
branchIndex: skipIdx,
|
|
391
|
+
executionNumber: 1,
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
success: true,
|
|
394
|
+
executionTime: 0,
|
|
395
|
+
decision: { action: PHASE_DECISION_ACTIONS.SKIP }
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
currentBranchId = decision.targetBranchId;
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
currentBranchId = branches[branchIndex + 1]?.id || null;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const nextBranch = branches[branchIndex + 1];
|
|
406
|
+
if (nextBranch) {
|
|
407
|
+
const skippedResult = {
|
|
408
|
+
branchId: nextBranch.id,
|
|
409
|
+
branchIndex: branchIndex + 1,
|
|
410
|
+
success: true,
|
|
411
|
+
executionTime: 0,
|
|
412
|
+
completedPhases: 0,
|
|
413
|
+
phaseResults: [],
|
|
414
|
+
executionNumber: 1,
|
|
415
|
+
skipped: true
|
|
416
|
+
};
|
|
417
|
+
branchResults.push(skippedResult);
|
|
418
|
+
branchExecutionHistory.push({
|
|
419
|
+
branchId: nextBranch.id,
|
|
420
|
+
branchIndex: branchIndex + 1,
|
|
421
|
+
executionNumber: 1,
|
|
422
|
+
timestamp: new Date().toISOString(),
|
|
423
|
+
success: true,
|
|
424
|
+
executionTime: 0,
|
|
425
|
+
decision: { action: PHASE_DECISION_ACTIONS.SKIP }
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
currentBranchId = branches[branchIndex + 2]?.id || null;
|
|
429
|
+
}
|
|
430
|
+
break;
|
|
431
|
+
case PHASE_DECISION_ACTIONS.REPLAY:
|
|
432
|
+
if (!currentBranch.allowReplay && currentBranch.allowReplay !== undefined) {
|
|
433
|
+
console.warn(`stable-request: [Workflow: ${workflowId}] Branch '${currentBranchId}' attempted to replay but allowReplay is false. Continuing normally.`);
|
|
434
|
+
currentBranchId = branches[branchIndex + 1]?.id || null;
|
|
435
|
+
break;
|
|
436
|
+
}
|
|
437
|
+
currentBranchId = currentBranch.id;
|
|
438
|
+
break;
|
|
439
|
+
case PHASE_DECISION_ACTIONS.CONTINUE:
|
|
440
|
+
default:
|
|
441
|
+
currentBranchId = branches[branchIndex + 1]?.id || null;
|
|
442
|
+
break;
|
|
236
443
|
}
|
|
237
444
|
if (stopOnFirstPhaseError && result.failedRequests > 0) {
|
|
238
|
-
if (logPhaseResults) {
|
|
239
|
-
console.error(`stable-request: [Workflow: ${workflowId}] Branch ${branch.id} has failures. Stopping workflow.`);
|
|
240
|
-
}
|
|
241
445
|
terminatedEarly = true;
|
|
242
|
-
terminationReason = `Branch ${
|
|
446
|
+
terminationReason = `Branch ${currentBranch.id} failed`;
|
|
243
447
|
break;
|
|
244
448
|
}
|
|
245
449
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
completedPhases: 0,
|
|
253
|
-
phaseResults: []
|
|
254
|
-
};
|
|
255
|
-
branchResults.push(branchResult);
|
|
256
|
-
failedRequests += 1;
|
|
257
|
-
if (stopOnFirstPhaseError) {
|
|
258
|
-
terminatedEarly = true;
|
|
259
|
-
terminationReason = `Branch ${branch.id} failed with error`;
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
450
|
+
}
|
|
451
|
+
if (iterationCount >= maxWorkflowIterations) {
|
|
452
|
+
terminatedEarly = true;
|
|
453
|
+
terminationReason = `Exceeded maximum workflow iterations (${maxWorkflowIterations})`;
|
|
454
|
+
if (logPhaseResults) {
|
|
455
|
+
console.warn(`stable-request: [Workflow: ${workflowId}] ${terminationReason}`);
|
|
262
456
|
}
|
|
263
|
-
branchIndex++;
|
|
264
457
|
}
|
|
265
458
|
return {
|
|
266
459
|
branchResults,
|
|
267
460
|
allPhaseResults,
|
|
268
461
|
executionHistory,
|
|
462
|
+
branchExecutionHistory,
|
|
269
463
|
totalRequests,
|
|
270
464
|
successfulRequests,
|
|
271
465
|
failedRequests,
|