@ai-setting/roy-agent-core 1.6.12 → 1.6.13
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/env/agent/index.js +3 -3
- package/dist/env/event-source/index.js +5 -5
- package/dist/env/index.js +17 -17
- package/dist/env/prompt/index.js +2 -2
- package/dist/env/task/delegate/index.js +4 -4
- package/dist/env/task/index.js +5 -5
- package/dist/env/task/plugins/index.js +4 -4
- package/dist/env/tool/built-in/index.js +2 -2
- package/dist/env/tool/index.js +3 -3
- package/dist/env/workflow/engine/index.js +3 -3
- package/dist/env/workflow/index.js +7 -7
- package/dist/env/workflow/service/index.js +1 -1
- package/dist/env/workflow/tools/index.js +4 -4
- package/dist/env/workflow/utils/index.js +2 -2
- package/dist/index.js +130 -19
- package/dist/shared/@ai-setting/{roy-agent-core-0n2a8cbn.js → roy-agent-core-0crh2g0m.js} +594 -237
- package/dist/shared/@ai-setting/{roy-agent-core-asp5p3zx.js → roy-agent-core-0pbmweh4.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-jenchn33.js → roy-agent-core-24rmf17n.js} +8 -0
- package/dist/shared/@ai-setting/{roy-agent-core-q2bw3cwd.js → roy-agent-core-2b4078kz.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-z7f8hyv7.js → roy-agent-core-6jqpdjr4.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-cxdnzn17.js → roy-agent-core-6qbyfzsw.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-mb7b62sm.js → roy-agent-core-8cbsjkkx.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-ms5dp89r.js → roy-agent-core-apz6v22z.js} +81 -40
- package/dist/shared/@ai-setting/{roy-agent-core-q7rt3x8d.js → roy-agent-core-deqpzcg7.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-1bvv6s75.js → roy-agent-core-fvkyv5j6.js} +2 -2
- package/dist/shared/@ai-setting/{roy-agent-core-dntkpwqq.js → roy-agent-core-fwxqevpf.js} +4 -1
- package/dist/shared/@ai-setting/{roy-agent-core-pzeexnhf.js → roy-agent-core-nv4yfy3m.js} +4 -3
- package/dist/shared/@ai-setting/{roy-agent-core-3bdpnmtg.js → roy-agent-core-v5nknfh0.js} +1 -8
- package/dist/shared/@ai-setting/{roy-agent-core-06574wqa.js → roy-agent-core-v77pp20w.js} +4 -4
- package/dist/shared/@ai-setting/roy-agent-core-vcjq65nz.js +436 -0
- package/dist/shared/@ai-setting/{roy-agent-core-8wd3qwx5.js → roy-agent-core-wjve1qr5.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-7a3gf3tw.js → roy-agent-core-wpw37qqf.js} +88 -3
- package/dist/shared/@ai-setting/{roy-agent-core-p6x6n3k4.js → roy-agent-core-x7fgze6s.js} +1 -1
- package/dist/shared/@ai-setting/{roy-agent-core-p2vpsd1d.js → roy-agent-core-ywtmc0bh.js} +166 -6
- package/package.json +1 -1
- package/dist/shared/@ai-setting/roy-agent-core-4yq23m5g.js +0 -421
- /package/dist/shared/@ai-setting/{roy-agent-core-zs31w092.js → roy-agent-core-9nhwdq9r.js} +0 -0
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
AgentComponentAdapter,
|
|
9
9
|
init_agent_component_adapter
|
|
10
|
-
} from "./roy-agent-core-
|
|
10
|
+
} from "./roy-agent-core-24rmf17n.js";
|
|
11
11
|
import {
|
|
12
12
|
TemplateResolver,
|
|
13
13
|
init_template_resolver
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
import {
|
|
41
41
|
DAGManager,
|
|
42
42
|
init_dag_manager
|
|
43
|
-
} from "./roy-agent-core-
|
|
43
|
+
} from "./roy-agent-core-vcjq65nz.js";
|
|
44
44
|
import {
|
|
45
45
|
AskUserError,
|
|
46
46
|
createNodeInterruptEvent,
|
|
@@ -154,238 +154,259 @@ var init_event_bus = __esm(() => {
|
|
|
154
154
|
});
|
|
155
155
|
|
|
156
156
|
// src/env/workflow/engine/scheduler.ts
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this.
|
|
157
|
+
var logger2, Scheduler;
|
|
158
|
+
var init_scheduler = __esm(() => {
|
|
159
|
+
init_logger();
|
|
160
|
+
init_decorator();
|
|
161
|
+
logger2 = createLogger("workflow.scheduler");
|
|
162
|
+
Scheduler = class Scheduler {
|
|
163
|
+
dagManager;
|
|
164
|
+
options;
|
|
165
|
+
pending = new Set;
|
|
166
|
+
ready = new Set;
|
|
167
|
+
running = new Set;
|
|
168
|
+
completed = new Set;
|
|
169
|
+
failed = new Set;
|
|
170
|
+
skipped = new Set;
|
|
171
|
+
constructor(dagManager, options) {
|
|
172
|
+
this.dagManager = dagManager;
|
|
173
|
+
const parallelLimit = options?.parallelLimit;
|
|
174
|
+
this.options = {
|
|
175
|
+
parallelLimit: parallelLimit === undefined ? 1 : parallelLimit
|
|
176
|
+
};
|
|
177
|
+
const analysis = dagManager.analyze();
|
|
178
|
+
for (const nodeId of analysis.dependencies.keys()) {
|
|
179
|
+
this.pending.add(nodeId);
|
|
180
|
+
}
|
|
181
|
+
this.updateReadyNodes(new Set);
|
|
182
|
+
}
|
|
183
|
+
getReadyNodes(completedNodes) {
|
|
184
|
+
const allCompleted = new Set(completedNodes);
|
|
185
|
+
for (const nodeId of this.completed) {
|
|
186
|
+
allCompleted.add(nodeId);
|
|
187
|
+
}
|
|
188
|
+
const readyFromDag = this.dagManager.getReadyNodes(allCompleted);
|
|
189
|
+
const ready = [];
|
|
190
|
+
for (const nodeId of readyFromDag) {
|
|
191
|
+
const isProcessed = this.running.has(nodeId) || this.completed.has(nodeId) || this.failed.has(nodeId) || this.skipped.has(nodeId);
|
|
192
|
+
if (!isProcessed) {
|
|
193
|
+
ready.push(nodeId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return ready.sort();
|
|
175
197
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
198
|
+
getRunningCount() {
|
|
199
|
+
return this.running.size;
|
|
200
|
+
}
|
|
201
|
+
canStartMore() {
|
|
202
|
+
if (this.options.parallelLimit === null) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
if (this.options.parallelLimit <= 0) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
return this.running.size < this.options.parallelLimit;
|
|
182
209
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
ready.push(nodeId);
|
|
210
|
+
markStarted(nodeId) {
|
|
211
|
+
if (!this.running.has(nodeId)) {
|
|
212
|
+
this.running.add(nodeId);
|
|
213
|
+
this.ready.delete(nodeId);
|
|
214
|
+
this.pending.delete(nodeId);
|
|
189
215
|
}
|
|
190
216
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
getRunningCount() {
|
|
194
|
-
return this.running.size;
|
|
195
|
-
}
|
|
196
|
-
canStartMore() {
|
|
197
|
-
if (this.options.parallelLimit === null) {
|
|
198
|
-
return true;
|
|
217
|
+
isRunning(nodeId) {
|
|
218
|
+
return this.running.has(nodeId);
|
|
199
219
|
}
|
|
200
|
-
|
|
201
|
-
return
|
|
220
|
+
isCompleted(nodeId) {
|
|
221
|
+
return this.completed.has(nodeId);
|
|
202
222
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this.
|
|
223
|
+
isFailed(nodeId) {
|
|
224
|
+
return this.failed.has(nodeId);
|
|
225
|
+
}
|
|
226
|
+
isPending(nodeId) {
|
|
227
|
+
return this.pending.has(nodeId);
|
|
228
|
+
}
|
|
229
|
+
isReady(nodeId) {
|
|
230
|
+
return this.ready.has(nodeId);
|
|
231
|
+
}
|
|
232
|
+
markCompleted(nodeId) {
|
|
233
|
+
if (nodeId === "__end__" || nodeId === "__END__") {
|
|
234
|
+
logger2.debug(`[Scheduler] Workflow marked as completed via __end__`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.running.delete(nodeId);
|
|
208
238
|
this.ready.delete(nodeId);
|
|
209
239
|
this.pending.delete(nodeId);
|
|
240
|
+
this.failed.delete(nodeId);
|
|
241
|
+
this.skipped.delete(nodeId);
|
|
242
|
+
this.completed.add(nodeId);
|
|
210
243
|
}
|
|
211
|
-
|
|
212
|
-
isRunning(nodeId) {
|
|
213
|
-
return this.running.has(nodeId);
|
|
214
|
-
}
|
|
215
|
-
isCompleted(nodeId) {
|
|
216
|
-
return this.completed.has(nodeId);
|
|
217
|
-
}
|
|
218
|
-
isFailed(nodeId) {
|
|
219
|
-
return this.failed.has(nodeId);
|
|
220
|
-
}
|
|
221
|
-
isPending(nodeId) {
|
|
222
|
-
return this.pending.has(nodeId);
|
|
223
|
-
}
|
|
224
|
-
isReady(nodeId) {
|
|
225
|
-
return this.ready.has(nodeId);
|
|
226
|
-
}
|
|
227
|
-
markCompleted(nodeId) {
|
|
228
|
-
if (nodeId === "__end__" || nodeId === "__END__") {
|
|
229
|
-
logger2.debug(`[Scheduler] Workflow marked as completed via __end__`);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
this.running.delete(nodeId);
|
|
233
|
-
this.ready.delete(nodeId);
|
|
234
|
-
this.pending.delete(nodeId);
|
|
235
|
-
this.failed.delete(nodeId);
|
|
236
|
-
this.skipped.delete(nodeId);
|
|
237
|
-
this.completed.add(nodeId);
|
|
238
|
-
}
|
|
239
|
-
markFailed(nodeId) {
|
|
240
|
-
this.running.delete(nodeId);
|
|
241
|
-
this.ready.delete(nodeId);
|
|
242
|
-
this.pending.delete(nodeId);
|
|
243
|
-
this.completed.delete(nodeId);
|
|
244
|
-
this.skipped.delete(nodeId);
|
|
245
|
-
this.failed.add(nodeId);
|
|
246
|
-
}
|
|
247
|
-
markSkipped(nodeId) {
|
|
248
|
-
if (this.running.has(nodeId)) {
|
|
244
|
+
markFailed(nodeId) {
|
|
249
245
|
this.running.delete(nodeId);
|
|
250
|
-
}
|
|
251
|
-
if (this.ready.has(nodeId)) {
|
|
252
246
|
this.ready.delete(nodeId);
|
|
253
|
-
}
|
|
254
|
-
if (this.pending.has(nodeId)) {
|
|
255
247
|
this.pending.delete(nodeId);
|
|
248
|
+
this.completed.delete(nodeId);
|
|
249
|
+
this.skipped.delete(nodeId);
|
|
250
|
+
this.failed.add(nodeId);
|
|
256
251
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
markSkipped(nodeId) {
|
|
253
|
+
if (this.running.has(nodeId)) {
|
|
254
|
+
this.running.delete(nodeId);
|
|
255
|
+
}
|
|
256
|
+
if (this.ready.has(nodeId)) {
|
|
257
|
+
this.ready.delete(nodeId);
|
|
258
|
+
}
|
|
259
|
+
if (this.pending.has(nodeId)) {
|
|
260
|
+
this.pending.delete(nodeId);
|
|
261
|
+
}
|
|
262
|
+
this.skipped.add(nodeId);
|
|
262
263
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
this.completed.delete(nodeId);
|
|
269
|
-
this.failed.delete(nodeId);
|
|
270
|
-
this.skipped.delete(nodeId);
|
|
271
|
-
this.ready.delete(nodeId);
|
|
272
|
-
this.pending.add(nodeId);
|
|
273
|
-
}
|
|
274
|
-
reset() {
|
|
275
|
-
this.running.clear();
|
|
276
|
-
this.completed.clear();
|
|
277
|
-
this.failed.clear();
|
|
278
|
-
this.skipped.clear();
|
|
279
|
-
const analysis = this.dagManager.analyze();
|
|
280
|
-
this.pending.clear();
|
|
281
|
-
this.ready.clear();
|
|
282
|
-
for (const nodeId of analysis.dependencies.keys()) {
|
|
264
|
+
unmarkSkipped(nodeId) {
|
|
265
|
+
if (!this.skipped.has(nodeId)) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
this.skipped.delete(nodeId);
|
|
283
269
|
this.pending.add(nodeId);
|
|
284
270
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
running: Array.from(this.running).sort(),
|
|
293
|
-
completed: Array.from(this.completed).sort(),
|
|
294
|
-
failed: Array.from(this.failed).sort(),
|
|
295
|
-
skipped: Array.from(this.skipped).sort()
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
markCompletedFromRestore(nodeIds) {
|
|
299
|
-
for (const nodeId of nodeIds) {
|
|
300
|
-
this.markCompleted(nodeId);
|
|
271
|
+
resetNodeForReEntry(nodeId) {
|
|
272
|
+
this.running.delete(nodeId);
|
|
273
|
+
this.completed.delete(nodeId);
|
|
274
|
+
this.failed.delete(nodeId);
|
|
275
|
+
this.skipped.delete(nodeId);
|
|
276
|
+
this.ready.delete(nodeId);
|
|
277
|
+
this.pending.add(nodeId);
|
|
301
278
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
279
|
+
reset() {
|
|
280
|
+
this.running.clear();
|
|
281
|
+
this.completed.clear();
|
|
282
|
+
this.failed.clear();
|
|
283
|
+
this.skipped.clear();
|
|
284
|
+
const analysis = this.dagManager.analyze();
|
|
285
|
+
this.pending.clear();
|
|
286
|
+
this.ready.clear();
|
|
287
|
+
for (const nodeId of analysis.dependencies.keys()) {
|
|
288
|
+
this.pending.add(nodeId);
|
|
289
|
+
}
|
|
290
|
+
this.updateReadyNodes(new Set);
|
|
291
|
+
}
|
|
292
|
+
getState() {
|
|
293
|
+
this.updateReadyNodes(new Set);
|
|
294
|
+
return {
|
|
295
|
+
pending: Array.from(this.pending).sort(),
|
|
296
|
+
ready: Array.from(this.ready).sort(),
|
|
297
|
+
running: Array.from(this.running).sort(),
|
|
298
|
+
completed: Array.from(this.completed).sort(),
|
|
299
|
+
failed: Array.from(this.failed).sort(),
|
|
300
|
+
skipped: Array.from(this.skipped).sort()
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
markCompletedFromRestore(nodeIds) {
|
|
304
|
+
for (const nodeId of nodeIds) {
|
|
305
|
+
this.markCompleted(nodeId);
|
|
317
306
|
}
|
|
318
307
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
skipped: [],
|
|
334
|
-
resetNodes: []
|
|
335
|
-
};
|
|
336
|
-
const completedNode = this.dagManager.getNode(completedNodeId);
|
|
337
|
-
if (completedNode?.condition) {
|
|
338
|
-
const conditionMet = templateResolver.evaluateCondition(completedNode.condition);
|
|
339
|
-
if (!conditionMet) {
|
|
340
|
-
for (const edge of this.getOutgoingEdges(completedNodeId, edges)) {
|
|
341
|
-
this.markSkipped(edge.to);
|
|
342
|
-
result.skipped.push({
|
|
343
|
-
nodeId: edge.to,
|
|
344
|
-
reason: `Inline condition false on node "${completedNodeId}"`
|
|
345
|
-
});
|
|
308
|
+
updateReadyNodes(completedNodes) {
|
|
309
|
+
const allCompleted = new Set(completedNodes);
|
|
310
|
+
for (const nodeId of this.completed) {
|
|
311
|
+
allCompleted.add(nodeId);
|
|
312
|
+
}
|
|
313
|
+
const analysis = this.dagManager.analyze();
|
|
314
|
+
const allNodeIds = Array.from(analysis.dependencies.keys());
|
|
315
|
+
this.ready.clear();
|
|
316
|
+
for (const nodeId of allNodeIds) {
|
|
317
|
+
if (this.running.has(nodeId) || this.completed.has(nodeId) || this.failed.has(nodeId) || this.skipped.has(nodeId)) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (this.dagManager.areDependenciesMet(nodeId, allCompleted)) {
|
|
321
|
+
this.ready.add(nodeId);
|
|
346
322
|
}
|
|
347
|
-
return result;
|
|
348
323
|
}
|
|
349
324
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
325
|
+
getParallelLimit() {
|
|
326
|
+
return this.options.parallelLimit;
|
|
327
|
+
}
|
|
328
|
+
setParallelLimit(limit) {
|
|
329
|
+
this.options.parallelLimit = limit;
|
|
330
|
+
}
|
|
331
|
+
getOutgoingEdges(nodeId, edges) {
|
|
332
|
+
return edges.filter((edge) => edge.from === nodeId);
|
|
333
|
+
}
|
|
334
|
+
processEdgesAfterCompletion(completedNodeId, edges, templateResolver, loopEnabled) {
|
|
335
|
+
const result = {
|
|
336
|
+
toStart: [],
|
|
337
|
+
reEntries: [],
|
|
338
|
+
skipped: [],
|
|
339
|
+
resetNodes: []
|
|
340
|
+
};
|
|
341
|
+
const completedNode = this.dagManager.getNode(completedNodeId);
|
|
342
|
+
if (completedNode?.condition) {
|
|
343
|
+
const conditionMet = templateResolver.evaluateCondition(completedNode.condition);
|
|
344
|
+
if (!conditionMet) {
|
|
345
|
+
for (const edge of this.getOutgoingEdges(completedNodeId, edges)) {
|
|
346
|
+
this.markSkipped(edge.to);
|
|
347
|
+
result.skipped.push({
|
|
348
|
+
nodeId: edge.to,
|
|
349
|
+
reason: `Inline condition false on node "${completedNodeId}"`
|
|
350
|
+
});
|
|
369
351
|
}
|
|
370
|
-
result
|
|
352
|
+
return result;
|
|
371
353
|
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
354
|
+
}
|
|
355
|
+
const outgoingEdges = this.getOutgoingEdges(completedNodeId, edges);
|
|
356
|
+
for (const edge of outgoingEdges) {
|
|
357
|
+
const conditionMet = !edge.when || templateResolver.evaluateCondition(edge.when);
|
|
358
|
+
if (conditionMet) {
|
|
359
|
+
if (this.skipped.has(edge.to)) {
|
|
360
|
+
this.unmarkSkipped(edge.to);
|
|
361
|
+
}
|
|
362
|
+
const isReEntry = loopEnabled && this.completed.has(edge.to);
|
|
363
|
+
const canStart = loopEnabled ? !this.running.has(edge.to) && !this.failed.has(edge.to) : !this.completed.has(edge.to) && !this.running.has(edge.to) && !this.failed.has(edge.to);
|
|
364
|
+
if (canStart) {
|
|
365
|
+
if (isReEntry) {
|
|
366
|
+
const downstream = this.dagManager.getDownstreamUntil(edge.to, completedNodeId);
|
|
367
|
+
for (const nodeId of downstream) {
|
|
368
|
+
this.resetNodeForReEntry(nodeId);
|
|
369
|
+
result.resetNodes.push(nodeId);
|
|
370
|
+
}
|
|
371
|
+
this.resetNodeForReEntry(edge.to);
|
|
372
|
+
result.resetNodes.push(edge.to);
|
|
373
|
+
result.reEntries.push(edge.to);
|
|
374
|
+
result.toStart.push(edge.to);
|
|
375
|
+
} else {
|
|
376
|
+
const completedSoFar = new Set(this.completed);
|
|
377
|
+
completedSoFar.add(completedNodeId);
|
|
378
|
+
if (this.dagManager.areDependenciesMet(edge.to, completedSoFar)) {
|
|
379
|
+
result.toStart.push(edge.to);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
if (!this.skipped.has(edge.to)) {
|
|
385
|
+
result.skipped.push({
|
|
386
|
+
nodeId: edge.to,
|
|
387
|
+
reason: edge.when ? `Edge condition false: ${edge.when}` : "Edge condition false"
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
this.markSkipped(edge.to);
|
|
378
391
|
}
|
|
379
|
-
this.markSkipped(edge.to);
|
|
380
392
|
}
|
|
393
|
+
return result;
|
|
381
394
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
395
|
+
};
|
|
396
|
+
__legacyDecorateClassTS([
|
|
397
|
+
TracedAs("workflow.scheduler.getReadyNodes", {
|
|
398
|
+
recordParams: true,
|
|
399
|
+
recordResult: true,
|
|
400
|
+
log: true
|
|
401
|
+
})
|
|
402
|
+
], Scheduler.prototype, "getReadyNodes", null);
|
|
403
|
+
__legacyDecorateClassTS([
|
|
404
|
+
TracedAs("workflow.scheduler.processEdgesAfterCompletion", {
|
|
405
|
+
recordParams: true,
|
|
406
|
+
recordResult: true,
|
|
407
|
+
log: true
|
|
408
|
+
})
|
|
409
|
+
], Scheduler.prototype, "processEdgesAfterCompletion", null);
|
|
389
410
|
});
|
|
390
411
|
|
|
391
412
|
// src/env/workflow/engine/executor.ts
|
|
@@ -418,7 +439,9 @@ var init_executor = __esm(() => {
|
|
|
418
439
|
await this.writeNodeStart(context.sessionId, nodeId, nodeType, context.input, agentSessionId);
|
|
419
440
|
const abortController = new AbortController;
|
|
420
441
|
this.abortControllers.set(nodeId, abortController);
|
|
421
|
-
context.abortSignal
|
|
442
|
+
const incomingSignal = context.abortSignal;
|
|
443
|
+
const composedSignal = incomingSignal ? AbortSignal.any([incomingSignal, abortController.signal]) : abortController.signal;
|
|
444
|
+
context.abortSignal = composedSignal;
|
|
422
445
|
const ctxCfg = context.config;
|
|
423
446
|
const cfgTimeout = ctxCfg && typeof ctxCfg.timeout === "number" && ctxCfg.timeout > 0 ? ctxCfg.timeout : undefined;
|
|
424
447
|
const timeout = definition.timeout ?? cfgTimeout ?? this.options.globalTimeout ?? undefined;
|
|
@@ -505,6 +528,7 @@ var init_executor = __esm(() => {
|
|
|
505
528
|
const result = await node.execute(context);
|
|
506
529
|
return {
|
|
507
530
|
output: result.output,
|
|
531
|
+
error: result.error,
|
|
508
532
|
durationMs: Date.now() - startTime
|
|
509
533
|
};
|
|
510
534
|
} catch (error) {
|
|
@@ -523,32 +547,25 @@ var init_executor = __esm(() => {
|
|
|
523
547
|
const retryConfig = definition.retry;
|
|
524
548
|
let lastError;
|
|
525
549
|
let delay = retryConfig.initial_delay;
|
|
526
|
-
const signal = abortController.signal;
|
|
550
|
+
const signal = context.abortSignal ?? abortController.signal;
|
|
527
551
|
for (let attempt = 1;attempt <= retryConfig.max_attempts; attempt++) {
|
|
528
552
|
if (signal.aborted) {
|
|
553
|
+
const reason = signal.reason;
|
|
529
554
|
return {
|
|
530
555
|
output: undefined,
|
|
531
|
-
error: new Error("Execution aborted"),
|
|
556
|
+
error: reason instanceof Error ? reason : new Error(typeof reason === "string" && reason ? reason : "Execution aborted"),
|
|
532
557
|
durationMs: 0
|
|
533
558
|
};
|
|
534
559
|
}
|
|
535
560
|
const attemptAbort = new AbortController;
|
|
536
561
|
const combinedSignal = this.combineAbortSignals(signal, attemptAbort.signal);
|
|
537
|
-
const combinedController = new AbortController;
|
|
538
|
-
combinedSignal.addEventListener("abort", () => {
|
|
539
|
-
if (!combinedController.signal.aborted) {
|
|
540
|
-
combinedController.abort(combinedSignal.reason);
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
562
|
const attemptAbortController = {
|
|
544
563
|
get signal() {
|
|
545
564
|
return combinedSignal;
|
|
546
565
|
},
|
|
547
566
|
abort: (reason) => {
|
|
548
|
-
attemptAbort.
|
|
549
|
-
|
|
550
|
-
combinedController.abort(reason);
|
|
551
|
-
}
|
|
567
|
+
if (!attemptAbort.signal.aborted)
|
|
568
|
+
attemptAbort.abort(reason);
|
|
552
569
|
}
|
|
553
570
|
};
|
|
554
571
|
try {
|
|
@@ -561,7 +578,15 @@ var init_executor = __esm(() => {
|
|
|
561
578
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
562
579
|
}
|
|
563
580
|
if (attempt < retryConfig.max_attempts) {
|
|
564
|
-
|
|
581
|
+
try {
|
|
582
|
+
await this.sleep(delay, signal);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
return {
|
|
585
|
+
output: undefined,
|
|
586
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
587
|
+
durationMs: 0
|
|
588
|
+
};
|
|
589
|
+
}
|
|
565
590
|
if (retryConfig.backoff === "exponential") {
|
|
566
591
|
delay *= 2;
|
|
567
592
|
}
|
|
@@ -587,13 +612,29 @@ var init_executor = __esm(() => {
|
|
|
587
612
|
this.abortControllers.clear();
|
|
588
613
|
}
|
|
589
614
|
combineAbortSignals(signal1, signal2) {
|
|
590
|
-
|
|
591
|
-
signal1.addEventListener("abort", () => controller.abort());
|
|
592
|
-
signal2.addEventListener("abort", () => controller.abort());
|
|
593
|
-
return controller.signal;
|
|
615
|
+
return AbortSignal.any([signal1, signal2]);
|
|
594
616
|
}
|
|
595
|
-
sleep(ms) {
|
|
596
|
-
return new Promise((resolve) =>
|
|
617
|
+
sleep(ms, signal) {
|
|
618
|
+
return new Promise((resolve, reject) => {
|
|
619
|
+
if (signal?.aborted) {
|
|
620
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
const handle = setTimeout(() => {
|
|
624
|
+
if (signal) {
|
|
625
|
+
signal.removeEventListener("abort", onAbort);
|
|
626
|
+
}
|
|
627
|
+
resolve();
|
|
628
|
+
}, ms);
|
|
629
|
+
if (typeof handle.unref === "function") {
|
|
630
|
+
handle.unref();
|
|
631
|
+
}
|
|
632
|
+
const onAbort = () => {
|
|
633
|
+
clearTimeout(handle);
|
|
634
|
+
reject(signal.reason ?? new Error("aborted"));
|
|
635
|
+
};
|
|
636
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
637
|
+
});
|
|
597
638
|
}
|
|
598
639
|
async writeNodeStart(sessionId, nodeId, nodeType, input, agentSessionId) {
|
|
599
640
|
if (!this.sessionComponent)
|
|
@@ -1108,6 +1149,10 @@ var init_agent_node = __esm(() => {
|
|
|
1108
1149
|
nodeId: this.definition.id,
|
|
1109
1150
|
agentSessionId
|
|
1110
1151
|
};
|
|
1152
|
+
const ctxScope = context.cancellationScope;
|
|
1153
|
+
if (ctxScope && !agentConfig.cancellationScope) {
|
|
1154
|
+
agentConfig.cancellationScope = ctxScope;
|
|
1155
|
+
}
|
|
1111
1156
|
const resumeOptions = {
|
|
1112
1157
|
userQuery,
|
|
1113
1158
|
agentSessionId
|
|
@@ -1378,9 +1423,131 @@ function mapNonTerminal(agentSessions, terminalStatus) {
|
|
|
1378
1423
|
}
|
|
1379
1424
|
var init_agent_session_metadata = () => {};
|
|
1380
1425
|
|
|
1426
|
+
// src/env/workflow/runtime/runtime-cancellation-scope.ts
|
|
1427
|
+
var RuntimeCancellationScope;
|
|
1428
|
+
var init_runtime_cancellation_scope = __esm(() => {
|
|
1429
|
+
init_decorator();
|
|
1430
|
+
RuntimeCancellationScope = class RuntimeCancellationScope {
|
|
1431
|
+
gracefulController = new AbortController;
|
|
1432
|
+
forceController = new AbortController;
|
|
1433
|
+
signal;
|
|
1434
|
+
forceSignal;
|
|
1435
|
+
_level = "running";
|
|
1436
|
+
trackedWork = new Map;
|
|
1437
|
+
forceHandlers = [];
|
|
1438
|
+
forceHandlerErrors = [];
|
|
1439
|
+
constructor() {
|
|
1440
|
+
this.signal = AbortSignal.any([
|
|
1441
|
+
this.gracefulController.signal,
|
|
1442
|
+
this.forceController.signal
|
|
1443
|
+
]);
|
|
1444
|
+
this.forceSignal = this.forceController.signal;
|
|
1445
|
+
}
|
|
1446
|
+
get level() {
|
|
1447
|
+
return this._level;
|
|
1448
|
+
}
|
|
1449
|
+
track(label, work) {
|
|
1450
|
+
this.trackedWork.set(work, { label });
|
|
1451
|
+
work.finally(() => {
|
|
1452
|
+
this.trackedWork.delete(work);
|
|
1453
|
+
});
|
|
1454
|
+
return work;
|
|
1455
|
+
}
|
|
1456
|
+
onForce(label, handler) {
|
|
1457
|
+
const entry = { label, fn: handler };
|
|
1458
|
+
this.forceHandlers.push(entry);
|
|
1459
|
+
return () => {
|
|
1460
|
+
const idx = this.forceHandlers.indexOf(entry);
|
|
1461
|
+
if (idx >= 0) {
|
|
1462
|
+
this.forceHandlers.splice(idx, 1);
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
async cancel(reason, options = {}) {
|
|
1467
|
+
const force = options.force === true;
|
|
1468
|
+
if (force) {
|
|
1469
|
+
if (!this.gracefulController.signal.aborted) {
|
|
1470
|
+
this.gracefulController.abort(reason);
|
|
1471
|
+
this._level = "graceful";
|
|
1472
|
+
}
|
|
1473
|
+
if (!this.forceController.signal.aborted) {
|
|
1474
|
+
this.forceController.abort(reason);
|
|
1475
|
+
this._level = "force";
|
|
1476
|
+
} else {
|
|
1477
|
+
return {
|
|
1478
|
+
completed: this.trackedWork.size === 0,
|
|
1479
|
+
pendingLabels: Array.from(this.trackedWork.values()).map((v) => v.label),
|
|
1480
|
+
errors: [...this.forceHandlerErrors]
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
} else {
|
|
1484
|
+
if (!this.gracefulController.signal.aborted) {
|
|
1485
|
+
this.gracefulController.abort(reason);
|
|
1486
|
+
this._level = "graceful";
|
|
1487
|
+
}
|
|
1488
|
+
return {
|
|
1489
|
+
completed: this.trackedWork.size === 0,
|
|
1490
|
+
pendingLabels: Array.from(this.trackedWork.values()).map((v) => v.label),
|
|
1491
|
+
errors: [...this.forceHandlerErrors]
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
const handlers = [...this.forceHandlers];
|
|
1495
|
+
for (const { label, fn } of handlers) {
|
|
1496
|
+
try {
|
|
1497
|
+
await fn();
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
const err = e instanceof Error ? e.message : String(e);
|
|
1500
|
+
this.forceHandlerErrors.push({ label, error: err });
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
return {
|
|
1504
|
+
completed: this.trackedWork.size === 0,
|
|
1505
|
+
pendingLabels: Array.from(this.trackedWork.values()).map((v) => v.label),
|
|
1506
|
+
errors: [...this.forceHandlerErrors]
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
async waitForCleanup(timeoutMs) {
|
|
1510
|
+
const pendingPromises = Array.from(this.trackedWork.keys());
|
|
1511
|
+
if (pendingPromises.length === 0) {
|
|
1512
|
+
return {
|
|
1513
|
+
completed: true,
|
|
1514
|
+
pendingLabels: [],
|
|
1515
|
+
errors: [...this.forceHandlerErrors]
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
const settled = Promise.allSettled(pendingPromises).then(() => {
|
|
1519
|
+
return;
|
|
1520
|
+
});
|
|
1521
|
+
let timer;
|
|
1522
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
1523
|
+
timer = setTimeout(resolve, timeoutMs);
|
|
1524
|
+
});
|
|
1525
|
+
try {
|
|
1526
|
+
await Promise.race([settled, timeoutPromise]);
|
|
1527
|
+
} finally {
|
|
1528
|
+
if (timer)
|
|
1529
|
+
clearTimeout(timer);
|
|
1530
|
+
}
|
|
1531
|
+
return {
|
|
1532
|
+
completed: this.trackedWork.size === 0,
|
|
1533
|
+
pendingLabels: Array.from(this.trackedWork.values()).map((v) => v.label),
|
|
1534
|
+
errors: [...this.forceHandlerErrors]
|
|
1535
|
+
};
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1538
|
+
__legacyDecorateClassTS([
|
|
1539
|
+
TracedAs("workflow.runtime.cancel", { recordParams: true, recordResult: true })
|
|
1540
|
+
], RuntimeCancellationScope.prototype, "cancel", null);
|
|
1541
|
+
__legacyDecorateClassTS([
|
|
1542
|
+
TracedAs("workflow.runtime.waitForCleanup", { recordParams: true, recordResult: true })
|
|
1543
|
+
], RuntimeCancellationScope.prototype, "waitForCleanup", null);
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1381
1546
|
// src/env/workflow/engine/engine.ts
|
|
1382
1547
|
import { EventEmitter } from "events";
|
|
1383
|
-
|
|
1548
|
+
import { execFile as execFileCb } from "node:child_process";
|
|
1549
|
+
import { promisify } from "node:util";
|
|
1550
|
+
var execFileAsync, logger3, WorkflowEngine;
|
|
1384
1551
|
var init_engine = __esm(() => {
|
|
1385
1552
|
init_event();
|
|
1386
1553
|
init_workflow_hil();
|
|
@@ -1394,6 +1561,8 @@ var init_engine = __esm(() => {
|
|
|
1394
1561
|
init_decorator();
|
|
1395
1562
|
init_template_resolver();
|
|
1396
1563
|
init_workflow_error();
|
|
1564
|
+
init_runtime_cancellation_scope();
|
|
1565
|
+
execFileAsync = promisify(execFileCb);
|
|
1397
1566
|
logger3 = createLogger("workflow:engine");
|
|
1398
1567
|
WorkflowEngine = class WorkflowEngine extends EventEmitter {
|
|
1399
1568
|
activeSessions = new Map;
|
|
@@ -1616,6 +1785,7 @@ var init_engine = __esm(() => {
|
|
|
1616
1785
|
nodeConfig: options?.nodeConfig ?? {}
|
|
1617
1786
|
};
|
|
1618
1787
|
const abortController = new AbortController;
|
|
1788
|
+
const cancellationScope = new RuntimeCancellationScope;
|
|
1619
1789
|
let resolveCompleted;
|
|
1620
1790
|
let rejectCompleted;
|
|
1621
1791
|
const completedPromise = new Promise((resolve, reject) => {
|
|
@@ -1635,6 +1805,7 @@ var init_engine = __esm(() => {
|
|
|
1635
1805
|
nodeRegistry: this.nodeRegistry,
|
|
1636
1806
|
nodeOutputs: new Map,
|
|
1637
1807
|
config,
|
|
1808
|
+
cancellationScope,
|
|
1638
1809
|
abortController,
|
|
1639
1810
|
completedPromise,
|
|
1640
1811
|
resolveCompleted,
|
|
@@ -1642,7 +1813,8 @@ var init_engine = __esm(() => {
|
|
|
1642
1813
|
workflowHistory: [],
|
|
1643
1814
|
agentSessions: new Map,
|
|
1644
1815
|
edges: dagManager.buildEdges(definition.edges || []),
|
|
1645
|
-
loopIterationCount: 0
|
|
1816
|
+
loopIterationCount: 0,
|
|
1817
|
+
loopGuardCounters: new Map
|
|
1646
1818
|
};
|
|
1647
1819
|
this.setupEventHandlers(sessionState);
|
|
1648
1820
|
return sessionState;
|
|
@@ -1680,6 +1852,17 @@ var init_engine = __esm(() => {
|
|
|
1680
1852
|
const completedNodeIds = Array.from(resumeInfo.nodeOutputs.keys());
|
|
1681
1853
|
sessionState.scheduler.markCompletedFromRestore(completedNodeIds);
|
|
1682
1854
|
}
|
|
1855
|
+
const resolveStoppedOnce = () => {
|
|
1856
|
+
if (sessionState.status === "stopped") {
|
|
1857
|
+
try {
|
|
1858
|
+
sessionState.resolveCompleted({
|
|
1859
|
+
runId: this.getRunIdFromSessionId(sessionState.sessionId),
|
|
1860
|
+
status: "stopped"
|
|
1861
|
+
});
|
|
1862
|
+
} catch {}
|
|
1863
|
+
}
|
|
1864
|
+
};
|
|
1865
|
+
sessionState.cancellationScope?.signal.addEventListener("abort", resolveStoppedOnce, { once: true });
|
|
1683
1866
|
this.scheduleAndExecute(sessionState, {
|
|
1684
1867
|
input: inputForExecution,
|
|
1685
1868
|
pendingNodeId,
|
|
@@ -1721,11 +1904,16 @@ var init_engine = __esm(() => {
|
|
|
1721
1904
|
await sessionState.eventBus.publish(createWorkflowEvent("workflow.paused", this.getRunIdFromSessionId(sessionId), {}));
|
|
1722
1905
|
logger3.info(`[WorkflowEngine] Workflow paused: ${sessionId}`);
|
|
1723
1906
|
}
|
|
1724
|
-
async stop(sessionId, reason) {
|
|
1907
|
+
async stop(sessionId, reason, options = {}) {
|
|
1908
|
+
const force = options.force === true;
|
|
1909
|
+
const stopReason = reason ?? (force ? "Force stop requested" : "User requested stop");
|
|
1725
1910
|
const sessionState = this.activeSessions.get(sessionId);
|
|
1726
1911
|
if (!sessionState) {
|
|
1727
1912
|
if (this.sessionComponent) {
|
|
1728
1913
|
const metadata = await this.getSessionMetadata(sessionId);
|
|
1914
|
+
if (metadata?.status === "completed" || metadata?.status === "failed") {
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1729
1917
|
await this.sessionComponent.update(sessionId, {
|
|
1730
1918
|
metadata: {
|
|
1731
1919
|
...metadata,
|
|
@@ -1733,33 +1921,114 @@ var init_engine = __esm(() => {
|
|
|
1733
1921
|
agentSessions: markAllAgentSessionsStopped(metadata.agentSessions)
|
|
1734
1922
|
}
|
|
1735
1923
|
});
|
|
1924
|
+
await this.propagateStopToChildSessions(sessionId, stopReason);
|
|
1736
1925
|
}
|
|
1737
1926
|
return;
|
|
1738
1927
|
}
|
|
1928
|
+
if (sessionState.status === "completed" || sessionState.status === "failed") {
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1739
1931
|
sessionState.status = "stopped";
|
|
1740
|
-
sessionState.
|
|
1932
|
+
if (sessionState.cancellationScope) {
|
|
1933
|
+
await sessionState.cancellationScope.cancel(stopReason, { force });
|
|
1934
|
+
}
|
|
1935
|
+
if (!sessionState.abortController.signal.aborted) {
|
|
1936
|
+
sessionState.abortController.abort(stopReason);
|
|
1937
|
+
}
|
|
1741
1938
|
sessionState.executor.cancelAll();
|
|
1742
1939
|
if (this.sessionComponent) {
|
|
1743
1940
|
const metadata = await this.getSessionMetadata(sessionId);
|
|
1941
|
+
const updatedAgentSessions = markAllAgentSessionsStopped(metadata.agentSessions);
|
|
1744
1942
|
await this.sessionComponent.update(sessionId, {
|
|
1745
1943
|
metadata: {
|
|
1746
1944
|
...metadata,
|
|
1747
1945
|
status: "stopped",
|
|
1748
|
-
agentSessions:
|
|
1946
|
+
agentSessions: updatedAgentSessions
|
|
1749
1947
|
}
|
|
1750
1948
|
});
|
|
1949
|
+
await this.propagateStopToChildSessions(sessionId, stopReason);
|
|
1751
1950
|
}
|
|
1752
1951
|
await sessionState.eventBus.publish(createWorkflowEvent("workflow.stopped", this.getRunIdFromSessionId(sessionId), {
|
|
1753
|
-
reason:
|
|
1952
|
+
reason: stopReason
|
|
1754
1953
|
}));
|
|
1755
|
-
|
|
1954
|
+
try {
|
|
1955
|
+
await sessionState.cancellationScope?.waitForCleanup(force ? 500 : 1500);
|
|
1956
|
+
} catch {}
|
|
1957
|
+
if (force || !sessionState.cancellationScope) {
|
|
1958
|
+
this.cleanupSession(sessionState);
|
|
1959
|
+
} else {
|
|
1960
|
+
const GRACE_MS = 2000;
|
|
1961
|
+
const timer = setTimeout(() => {
|
|
1962
|
+
const current = this.activeSessions.get(sessionId);
|
|
1963
|
+
if (current === sessionState && current.status === "stopped") {
|
|
1964
|
+
this.cleanupSession(current);
|
|
1965
|
+
}
|
|
1966
|
+
}, GRACE_MS);
|
|
1967
|
+
if (typeof timer.unref === "function") {
|
|
1968
|
+
timer.unref();
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1756
1971
|
}
|
|
1757
1972
|
getSessionStatus(sessionId) {
|
|
1758
1973
|
const sessionState = this.activeSessions.get(sessionId);
|
|
1759
1974
|
return sessionState?.status ?? null;
|
|
1760
1975
|
}
|
|
1976
|
+
async propagateStopToChildSessions(workflowSessionId, reason) {
|
|
1977
|
+
if (!this.sessionComponent?.list || !this.sessionComponent.update)
|
|
1978
|
+
return;
|
|
1979
|
+
let allChildSessions;
|
|
1980
|
+
try {
|
|
1981
|
+
const PAGE = 1000;
|
|
1982
|
+
const HARD_CAP = 50000;
|
|
1983
|
+
const allSessions = [];
|
|
1984
|
+
let offset = 0;
|
|
1985
|
+
while (allSessions.length < HARD_CAP) {
|
|
1986
|
+
const batch = await this.sessionComponent.list({
|
|
1987
|
+
limit: PAGE,
|
|
1988
|
+
offset
|
|
1989
|
+
});
|
|
1990
|
+
allSessions.push(...batch);
|
|
1991
|
+
if (batch.length < PAGE)
|
|
1992
|
+
break;
|
|
1993
|
+
offset += PAGE;
|
|
1994
|
+
}
|
|
1995
|
+
allChildSessions = allSessions.filter((s) => s?.parentSessionId === workflowSessionId).map((s) => ({ id: s.id, metadata: s.metadata }));
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
logger3.warn(`[WorkflowEngine] propagateStopToChildSessions list failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
if (!allChildSessions?.length)
|
|
2001
|
+
return;
|
|
2002
|
+
for (const ref of allChildSessions) {
|
|
2003
|
+
if (!ref?.id)
|
|
2004
|
+
continue;
|
|
2005
|
+
try {
|
|
2006
|
+
const child = await this.sessionComponent.get(ref.id);
|
|
2007
|
+
if (!child)
|
|
2008
|
+
continue;
|
|
2009
|
+
const childMeta = child.metadata ?? {};
|
|
2010
|
+
const currentStatus = childMeta.status;
|
|
2011
|
+
if (currentStatus === "completed" || currentStatus === "failed" || currentStatus === "stopped") {
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
await this.sessionComponent.update(ref.id, {
|
|
2015
|
+
metadata: {
|
|
2016
|
+
...childMeta,
|
|
2017
|
+
status: "stopped",
|
|
2018
|
+
stopReason: reason,
|
|
2019
|
+
stoppedAt: Date.now()
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
} catch (err) {
|
|
2023
|
+
logger3.warn(`[WorkflowEngine] propagateStopToChildSessions failed for ${ref.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1761
2027
|
isSessionActive(sessionId) {
|
|
1762
|
-
|
|
2028
|
+
const s = this.activeSessions.get(sessionId);
|
|
2029
|
+
if (!s)
|
|
2030
|
+
return false;
|
|
2031
|
+
return s.status === "running" || s.status === "paused";
|
|
1763
2032
|
}
|
|
1764
2033
|
setupEventHandlers(sessionState) {
|
|
1765
2034
|
const { sessionId, eventBus, scheduler } = sessionState;
|
|
@@ -1878,7 +2147,7 @@ var init_engine = __esm(() => {
|
|
|
1878
2147
|
} else {
|
|
1879
2148
|
await scheduleAvailableNodes();
|
|
1880
2149
|
}
|
|
1881
|
-
while (sessionState.status === "running" && !abortController.signal.aborted) {
|
|
2150
|
+
while (sessionState.status === "running" && !abortController.signal.aborted && !sessionState.cancellationScope?.signal.aborted) {
|
|
1882
2151
|
const state = scheduler.getState();
|
|
1883
2152
|
if (state.pending.length === 0 && state.running.length === 0) {
|
|
1884
2153
|
break;
|
|
@@ -2053,6 +2322,41 @@ var init_engine = __esm(() => {
|
|
|
2053
2322
|
scheduler.markFailed(nodeId);
|
|
2054
2323
|
}
|
|
2055
2324
|
}
|
|
2325
|
+
async checkLoopGuardWithSnapshot(sessionState, templateResolver, reEntryId, _snapshotPreviousOutput) {
|
|
2326
|
+
const guard = sessionState.dagManager.getLoopGuard(reEntryId);
|
|
2327
|
+
if (!guard) {
|
|
2328
|
+
return { trip: false, counterKey: "", nextCount: 0 };
|
|
2329
|
+
}
|
|
2330
|
+
const counterKey = `_loop_guard_consecutive_${reEntryId}`;
|
|
2331
|
+
const previousCount = sessionState.loopGuardCounters.get(reEntryId) ?? 0;
|
|
2332
|
+
const previousValue = templateResolver.resolveValue(guard.previous_signal);
|
|
2333
|
+
const cwd = guard.cwd ?? sessionState.config.workflowInput?.project_path ?? process.cwd();
|
|
2334
|
+
let currentValue;
|
|
2335
|
+
try {
|
|
2336
|
+
const { stdout } = await execFileAsync("/bin/sh", ["-c", guard.current_signal_command], {
|
|
2337
|
+
cwd,
|
|
2338
|
+
timeout: 1e4
|
|
2339
|
+
});
|
|
2340
|
+
currentValue = stdout.trim();
|
|
2341
|
+
} catch (err) {
|
|
2342
|
+
logger3.warn(`[WorkflowEngine] loop_guard command failed for ${reEntryId}: ${err instanceof Error ? err.message : String(err)}. Allowing re-entry.`);
|
|
2343
|
+
return { trip: false, counterKey, nextCount: 0 };
|
|
2344
|
+
}
|
|
2345
|
+
if (previousValue === undefined) {
|
|
2346
|
+
return { trip: false, counterKey, nextCount: 0 };
|
|
2347
|
+
}
|
|
2348
|
+
const isUnchanged = JSON.stringify(previousValue) === JSON.stringify(currentValue);
|
|
2349
|
+
if (!isUnchanged) {
|
|
2350
|
+
return { trip: false, counterKey, nextCount: 0 };
|
|
2351
|
+
}
|
|
2352
|
+
const nextCount = previousCount + 1;
|
|
2353
|
+
const maxAllowed = guard.max_consecutive_no_progress;
|
|
2354
|
+
if (nextCount >= maxAllowed) {
|
|
2355
|
+
const failMessage = guard.fail_message ?? `no progress: loop_guard detected unchanged signal for ${reEntryId} after ${nextCount} consecutive re-entries`;
|
|
2356
|
+
return { trip: true, failMessage, counterKey, nextCount };
|
|
2357
|
+
}
|
|
2358
|
+
return { trip: false, counterKey, nextCount };
|
|
2359
|
+
}
|
|
2056
2360
|
async processConditionalEdgesAfterCompletion(sessionState, completedNodeId) {
|
|
2057
2361
|
if (!sessionState.edges || sessionState.edges.length === 0) {
|
|
2058
2362
|
return;
|
|
@@ -2066,6 +2370,14 @@ var init_engine = __esm(() => {
|
|
|
2066
2370
|
logger3.debug(`[WorkflowEngine] Processing edges after "${completedNodeId}" completed. edges=${JSON.stringify(sessionState.edges)}`);
|
|
2067
2371
|
const edgeResult = scheduler.processEdgesAfterCompletion(completedNodeId, sessionState.edges, templateResolver, sessionState.config.loopEnabled);
|
|
2068
2372
|
logger3.debug(`[WorkflowEngine] Edge processing result: ${JSON.stringify(edgeResult)}`);
|
|
2373
|
+
const loopGuardSnapshots = new Map;
|
|
2374
|
+
for (const reEntryId of edgeResult.reEntries) {
|
|
2375
|
+
const guard = sessionState.dagManager.getLoopGuard(reEntryId);
|
|
2376
|
+
if (!guard)
|
|
2377
|
+
continue;
|
|
2378
|
+
const previousOutput = sessionState.nodeOutputs.get(reEntryId) ?? {};
|
|
2379
|
+
loopGuardSnapshots.set(reEntryId, previousOutput);
|
|
2380
|
+
}
|
|
2069
2381
|
const resetNodeIds = edgeResult.reEntries.length > 0 ? edgeResult.resetNodes.filter((nodeId) => nodeId !== completedNodeId) : edgeResult.resetNodes;
|
|
2070
2382
|
for (const nodeId of resetNodeIds) {
|
|
2071
2383
|
sessionState.nodeOutputs.delete(nodeId);
|
|
@@ -2090,8 +2402,46 @@ var init_engine = __esm(() => {
|
|
|
2090
2402
|
}
|
|
2091
2403
|
logger3.debug(`[WorkflowEngine] Loop iteration #${sessionState.loopIterationCount}: re-entering ${edgeResult.reEntries.join(", ")}`);
|
|
2092
2404
|
}
|
|
2405
|
+
const loopGuardTripped = new Set;
|
|
2406
|
+
for (const reEntryId of edgeResult.reEntries) {
|
|
2407
|
+
const guard = sessionState.dagManager.getLoopGuard(reEntryId);
|
|
2408
|
+
if (!guard)
|
|
2409
|
+
continue;
|
|
2410
|
+
const snapshotPreviousOutput = loopGuardSnapshots.get(reEntryId) ?? {};
|
|
2411
|
+
const previousOutputsForGuard = new Map(sessionState.nodeOutputs);
|
|
2412
|
+
previousOutputsForGuard.set(reEntryId, snapshotPreviousOutput);
|
|
2413
|
+
const guardResolver = new TemplateResolver({
|
|
2414
|
+
input: sessionState.config.workflowInput || {},
|
|
2415
|
+
previousOutputs: previousOutputsForGuard
|
|
2416
|
+
});
|
|
2417
|
+
const verdict = await this.checkLoopGuardWithSnapshot(sessionState, guardResolver, reEntryId, snapshotPreviousOutput);
|
|
2418
|
+
if (verdict.trip) {
|
|
2419
|
+
const failOutput = {
|
|
2420
|
+
...snapshotPreviousOutput,
|
|
2421
|
+
_loop_guard_triggered: true,
|
|
2422
|
+
fail_reason: verdict.failMessage,
|
|
2423
|
+
[verdict.counterKey]: verdict.nextCount
|
|
2424
|
+
};
|
|
2425
|
+
sessionState.nodeOutputs.set(reEntryId, failOutput);
|
|
2426
|
+
sessionState.loopGuardCounters.set(reEntryId, verdict.nextCount);
|
|
2427
|
+
scheduler.markFailed(reEntryId);
|
|
2428
|
+
loopGuardTripped.add(reEntryId);
|
|
2429
|
+
logger3.warn(`[WorkflowEngine] loop_guard tripped for ${reEntryId} after ${verdict.nextCount} consecutive no-progress re-entries: ${verdict.failMessage}`);
|
|
2430
|
+
await eventBus.publish(createWorkflowEvent("node.failed", runId, {
|
|
2431
|
+
node_id: reEntryId,
|
|
2432
|
+
error: {
|
|
2433
|
+
message: verdict.failMessage
|
|
2434
|
+
}
|
|
2435
|
+
}));
|
|
2436
|
+
} else if (verdict.nextCount > 0) {
|
|
2437
|
+
sessionState.loopGuardCounters.set(reEntryId, verdict.nextCount);
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2093
2440
|
const workflowInput = sessionState.config.workflowInput || {};
|
|
2094
2441
|
for (const targetId of edgeResult.toStart) {
|
|
2442
|
+
if (loopGuardTripped.has(targetId)) {
|
|
2443
|
+
continue;
|
|
2444
|
+
}
|
|
2095
2445
|
if (targetId === "__end__" || targetId === "__END__") {
|
|
2096
2446
|
logger3.info(`[WorkflowEngine] Workflow terminated via __end__ edge from node "${completedNodeId}"`);
|
|
2097
2447
|
scheduler.markCompleted("__end__");
|
|
@@ -2142,7 +2492,9 @@ var init_engine = __esm(() => {
|
|
|
2142
2492
|
},
|
|
2143
2493
|
workflowInput,
|
|
2144
2494
|
templateResolver,
|
|
2145
|
-
resolvedConfig: templateResolver.resolveValue(effectiveConfig)
|
|
2495
|
+
resolvedConfig: templateResolver.resolveValue(effectiveConfig),
|
|
2496
|
+
abortSignal: sessionState.cancellationScope?.signal ?? sessionState.abortController.signal,
|
|
2497
|
+
...sessionState.cancellationScope ? { cancellationScope: sessionState.cancellationScope } : {}
|
|
2146
2498
|
};
|
|
2147
2499
|
return context;
|
|
2148
2500
|
}
|
|
@@ -2224,10 +2576,15 @@ var init_engine = __esm(() => {
|
|
|
2224
2576
|
error: { message: error.message, stack: error.stack },
|
|
2225
2577
|
failed_at: new Date().toISOString()
|
|
2226
2578
|
}), true);
|
|
2579
|
+
const partialOutput = {};
|
|
2580
|
+
for (const [nodeId, nodeOutput] of sessionState.nodeOutputs) {
|
|
2581
|
+
partialOutput[nodeId] = nodeOutput;
|
|
2582
|
+
}
|
|
2227
2583
|
sessionState.resolveCompleted({
|
|
2228
2584
|
runId,
|
|
2229
2585
|
status: "failed",
|
|
2230
2586
|
error: error.message,
|
|
2587
|
+
output: partialOutput,
|
|
2231
2588
|
durationMs
|
|
2232
2589
|
});
|
|
2233
2590
|
this.cleanupSession(sessionState);
|