@ai-setting/roy-agent-core 1.6.12 → 1.6.14

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.
Files changed (40) hide show
  1. package/dist/env/agent/index.js +3 -3
  2. package/dist/env/event-source/index.js +7 -5
  3. package/dist/env/index.js +19 -19
  4. package/dist/env/prompt/index.js +2 -2
  5. package/dist/env/task/delegate/index.js +4 -4
  6. package/dist/env/task/index.js +5 -5
  7. package/dist/env/task/plugins/index.js +6 -4
  8. package/dist/env/tool/built-in/index.js +2 -2
  9. package/dist/env/tool/index.js +3 -3
  10. package/dist/env/workflow/engine/index.js +5 -5
  11. package/dist/env/workflow/index.js +12 -10
  12. package/dist/env/workflow/service/index.js +1 -1
  13. package/dist/env/workflow/tools/index.js +5 -4
  14. package/dist/env/workflow/types/index.js +3 -1
  15. package/dist/env/workflow/utils/index.js +4 -4
  16. package/dist/index.js +132 -21
  17. package/dist/shared/@ai-setting/{roy-agent-core-jenchn33.js → roy-agent-core-24rmf17n.js} +8 -0
  18. package/dist/shared/@ai-setting/{roy-agent-core-z7f8hyv7.js → roy-agent-core-6jqpdjr4.js} +1 -1
  19. package/dist/shared/@ai-setting/{roy-agent-core-cxdnzn17.js → roy-agent-core-6qbyfzsw.js} +1 -1
  20. package/dist/shared/@ai-setting/{roy-agent-core-8wd3qwx5.js → roy-agent-core-80e4a3g5.js} +1 -1
  21. package/dist/shared/@ai-setting/{roy-agent-core-ms5dp89r.js → roy-agent-core-apz6v22z.js} +81 -40
  22. package/dist/shared/@ai-setting/{roy-agent-core-1bvv6s75.js → roy-agent-core-fvkyv5j6.js} +2 -2
  23. package/dist/shared/@ai-setting/{roy-agent-core-dntkpwqq.js → roy-agent-core-fwxqevpf.js} +4 -1
  24. package/dist/shared/@ai-setting/{roy-agent-core-qnrf2aw6.js → roy-agent-core-jrj30vq3.js} +33 -2
  25. package/dist/shared/@ai-setting/{roy-agent-core-q7rt3x8d.js → roy-agent-core-m0cnmg7b.js} +2 -2
  26. package/dist/shared/@ai-setting/roy-agent-core-mmb9484f.js +458 -0
  27. package/dist/shared/@ai-setting/{roy-agent-core-0n2a8cbn.js → roy-agent-core-nhncpbzz.js} +602 -245
  28. package/dist/shared/@ai-setting/{roy-agent-core-pzeexnhf.js → roy-agent-core-nv4yfy3m.js} +4 -3
  29. package/dist/shared/@ai-setting/{roy-agent-core-w22tzjj5.js → roy-agent-core-psfkwe4g.js} +93 -14
  30. package/dist/shared/@ai-setting/{roy-agent-core-q2bw3cwd.js → roy-agent-core-px8ghk4g.js} +1 -1
  31. package/dist/shared/@ai-setting/{roy-agent-core-mb7b62sm.js → roy-agent-core-r53regz9.js} +2 -2
  32. package/dist/shared/@ai-setting/{roy-agent-core-06574wqa.js → roy-agent-core-ra4y7e4p.js} +4 -4
  33. package/dist/shared/@ai-setting/{roy-agent-core-asp5p3zx.js → roy-agent-core-st9y9z07.js} +1 -1
  34. package/dist/shared/@ai-setting/{roy-agent-core-3bdpnmtg.js → roy-agent-core-v5nknfh0.js} +1 -8
  35. package/dist/shared/@ai-setting/{roy-agent-core-7a3gf3tw.js → roy-agent-core-wpw37qqf.js} +88 -3
  36. package/dist/shared/@ai-setting/{roy-agent-core-p6x6n3k4.js → roy-agent-core-x7fgze6s.js} +1 -1
  37. package/dist/shared/@ai-setting/{roy-agent-core-p2vpsd1d.js → roy-agent-core-ywtmc0bh.js} +166 -6
  38. package/package.json +1 -1
  39. package/dist/shared/@ai-setting/roy-agent-core-4yq23m5g.js +0 -421
  40. /package/dist/shared/@ai-setting/{roy-agent-core-zs31w092.js → roy-agent-core-9nhwdq9r.js} +0 -0
@@ -7,11 +7,11 @@ import {
7
7
  import {
8
8
  AgentComponentAdapter,
9
9
  init_agent_component_adapter
10
- } from "./roy-agent-core-jenchn33.js";
10
+ } from "./roy-agent-core-24rmf17n.js";
11
11
  import {
12
12
  TemplateResolver,
13
13
  init_template_resolver
14
- } from "./roy-agent-core-w22tzjj5.js";
14
+ } from "./roy-agent-core-psfkwe4g.js";
15
15
  import {
16
16
  PythonCallNode,
17
17
  SkillNode,
@@ -22,13 +22,6 @@ import {
22
22
  init_tool_node,
23
23
  init_workflow_node
24
24
  } from "./roy-agent-core-resnbd2j.js";
25
- import {
26
- WorkflowError,
27
- createWorkflowEvent,
28
- init_event,
29
- init_types,
30
- init_workflow_error
31
- } from "./roy-agent-core-qnrf2aw6.js";
32
25
  import {
33
26
  WORKFLOW_SUBMIT_OUTPUT_TOOL_NAME,
34
27
  init_json_schema_to_zod,
@@ -40,7 +33,14 @@ import {
40
33
  import {
41
34
  DAGManager,
42
35
  init_dag_manager
43
- } from "./roy-agent-core-4yq23m5g.js";
36
+ } from "./roy-agent-core-mmb9484f.js";
37
+ import {
38
+ WorkflowError,
39
+ createWorkflowEvent,
40
+ init_event,
41
+ init_types,
42
+ init_workflow_error
43
+ } from "./roy-agent-core-jrj30vq3.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
- class Scheduler {
158
- dagManager;
159
- options;
160
- pending = new Set;
161
- ready = new Set;
162
- running = new Set;
163
- completed = new Set;
164
- failed = new Set;
165
- skipped = new Set;
166
- constructor(dagManager, options) {
167
- this.dagManager = dagManager;
168
- const parallelLimit = options?.parallelLimit;
169
- this.options = {
170
- parallelLimit: parallelLimit === undefined ? 1 : parallelLimit
171
- };
172
- const analysis = dagManager.analyze();
173
- for (const nodeId of analysis.dependencies.keys()) {
174
- this.pending.add(nodeId);
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
- this.updateReadyNodes(new Set);
177
- }
178
- getReadyNodes(completedNodes) {
179
- const allCompleted = new Set(completedNodes);
180
- for (const nodeId of this.completed) {
181
- allCompleted.add(nodeId);
198
+ getRunningCount() {
199
+ return this.running.size;
182
200
  }
183
- const readyFromDag = this.dagManager.getReadyNodes(allCompleted);
184
- const ready = [];
185
- for (const nodeId of readyFromDag) {
186
- const isProcessed = this.running.has(nodeId) || this.completed.has(nodeId) || this.failed.has(nodeId) || this.skipped.has(nodeId);
187
- if (!isProcessed) {
188
- ready.push(nodeId);
201
+ canStartMore() {
202
+ if (this.options.parallelLimit === null) {
203
+ return true;
204
+ }
205
+ if (this.options.parallelLimit <= 0) {
206
+ return false;
189
207
  }
208
+ return this.running.size < this.options.parallelLimit;
190
209
  }
191
- return ready.sort();
192
- }
193
- getRunningCount() {
194
- return this.running.size;
195
- }
196
- canStartMore() {
197
- if (this.options.parallelLimit === null) {
198
- return true;
210
+ markStarted(nodeId) {
211
+ if (!this.running.has(nodeId)) {
212
+ this.running.add(nodeId);
213
+ this.ready.delete(nodeId);
214
+ this.pending.delete(nodeId);
215
+ }
199
216
  }
200
- if (this.options.parallelLimit <= 0) {
201
- return false;
217
+ isRunning(nodeId) {
218
+ return this.running.has(nodeId);
202
219
  }
203
- return this.running.size < this.options.parallelLimit;
204
- }
205
- markStarted(nodeId) {
206
- if (!this.running.has(nodeId)) {
207
- this.running.add(nodeId);
220
+ isCompleted(nodeId) {
221
+ return this.completed.has(nodeId);
222
+ }
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
- this.skipped.add(nodeId);
258
- }
259
- unmarkSkipped(nodeId) {
260
- if (!this.skipped.has(nodeId)) {
261
- return;
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
- this.skipped.delete(nodeId);
264
- this.pending.add(nodeId);
265
- }
266
- resetNodeForReEntry(nodeId) {
267
- this.running.delete(nodeId);
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
- this.updateReadyNodes(new Set);
286
- }
287
- getState() {
288
- this.updateReadyNodes(new Set);
289
- return {
290
- pending: Array.from(this.pending).sort(),
291
- ready: Array.from(this.ready).sort(),
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
- updateReadyNodes(completedNodes) {
304
- const allCompleted = new Set(completedNodes);
305
- for (const nodeId of this.completed) {
306
- allCompleted.add(nodeId);
307
- }
308
- const analysis = this.dagManager.analyze();
309
- const allNodeIds = Array.from(analysis.dependencies.keys());
310
- this.ready.clear();
311
- for (const nodeId of allNodeIds) {
312
- if (this.running.has(nodeId) || this.completed.has(nodeId) || this.failed.has(nodeId) || this.skipped.has(nodeId)) {
313
- continue;
314
- }
315
- if (this.dagManager.areDependenciesMet(nodeId, allCompleted)) {
316
- this.ready.add(nodeId);
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
- getParallelLimit() {
321
- return this.options.parallelLimit;
322
- }
323
- setParallelLimit(limit) {
324
- this.options.parallelLimit = limit;
325
- }
326
- getOutgoingEdges(nodeId, edges) {
327
- return edges.filter((edge) => edge.from === nodeId);
328
- }
329
- processEdgesAfterCompletion(completedNodeId, edges, templateResolver, loopEnabled) {
330
- const result = {
331
- toStart: [],
332
- reEntries: [],
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
- const outgoingEdges = this.getOutgoingEdges(completedNodeId, edges);
351
- for (const edge of outgoingEdges) {
352
- const conditionMet = !edge.when || templateResolver.evaluateCondition(edge.when);
353
- if (conditionMet) {
354
- if (this.skipped.has(edge.to)) {
355
- this.unmarkSkipped(edge.to);
356
- }
357
- const isReEntry = loopEnabled && this.completed.has(edge.to);
358
- 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);
359
- if (canStart) {
360
- if (isReEntry) {
361
- const downstream = this.dagManager.getDownstreamUntil(edge.to, completedNodeId);
362
- for (const nodeId of downstream) {
363
- this.resetNodeForReEntry(nodeId);
364
- result.resetNodes.push(nodeId);
365
- }
366
- this.resetNodeForReEntry(edge.to);
367
- result.resetNodes.push(edge.to);
368
- result.reEntries.push(edge.to);
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.toStart.push(edge.to);
352
+ return result;
371
353
  }
372
- } else {
373
- if (!this.skipped.has(edge.to)) {
374
- result.skipped.push({
375
- nodeId: edge.to,
376
- reason: edge.when ? `Edge condition false: ${edge.when}` : "Edge condition false"
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
- return result;
383
- }
384
- }
385
- var logger2;
386
- var init_scheduler = __esm(() => {
387
- init_logger();
388
- logger2 = createLogger("workflow.scheduler");
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 ??= abortController.signal;
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.abort(reason);
549
- if (!combinedController.signal.aborted) {
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
- await this.sleep(delay);
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
- const controller = new AbortController;
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) => setTimeout(resolve, ms));
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
- var logger3, WorkflowEngine;
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.abortController.abort();
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: markAllAgentSessionsStopped(metadata.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: reason ?? "User requested stop"
1952
+ reason: stopReason
1754
1953
  }));
1755
- this.cleanupSession(sessionState);
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
- return this.activeSessions.has(sessionId);
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);