@travisliu/open-dynamic-workflow 0.3.0 → 0.3.5

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 (131) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +45 -17
  3. package/dist/agents/codex-exec.js +3 -1
  4. package/dist/agents/codex-exec.js.map +1 -1
  5. package/dist/agents/cursor-agent.d.ts +21 -0
  6. package/dist/agents/cursor-agent.js +171 -0
  7. package/dist/agents/cursor-agent.js.map +1 -0
  8. package/dist/agents/execute-agent.js +1 -1
  9. package/dist/agents/execute-agent.js.map +1 -1
  10. package/dist/agents/registry.js +2 -0
  11. package/dist/agents/registry.js.map +1 -1
  12. package/dist/artifacts/call-cache.d.ts +27 -3
  13. package/dist/artifacts/call-cache.js +160 -59
  14. package/dist/artifacts/call-cache.js.map +1 -1
  15. package/dist/artifacts/run-store.js.map +1 -1
  16. package/dist/cli/commands/doctor.js +0 -4
  17. package/dist/cli/commands/doctor.js.map +1 -1
  18. package/dist/cli/commands/init.js.map +1 -1
  19. package/dist/cli/commands/run.js +2 -1
  20. package/dist/cli/commands/run.js.map +1 -1
  21. package/dist/cli/commands/validate.js +2 -1
  22. package/dist/cli/commands/validate.js.map +1 -1
  23. package/dist/cli/index.d.ts +1 -0
  24. package/dist/cli/index.js +51 -0
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/init/prompts.d.ts +1 -1
  27. package/dist/cli/init/prompts.js +4 -4
  28. package/dist/cli/init/prompts.js.map +1 -1
  29. package/dist/cli/init/renderer.js +2 -1
  30. package/dist/cli/init/renderer.js.map +1 -1
  31. package/dist/config/defaults.js +2 -1
  32. package/dist/config/defaults.js.map +1 -1
  33. package/dist/config/load.js +1 -1
  34. package/dist/config/load.js.map +1 -1
  35. package/dist/config/schema.js +3 -0
  36. package/dist/config/schema.js.map +1 -1
  37. package/dist/config/types.d.ts +1 -0
  38. package/dist/discovery/collect-files.js +2 -2
  39. package/dist/discovery/collect-files.js.map +1 -1
  40. package/dist/discovery/definition-call.js +1 -1
  41. package/dist/discovery/definition-call.js.map +1 -1
  42. package/dist/discovery/file-patterns.js +1 -1
  43. package/dist/discovery/file-patterns.js.map +1 -1
  44. package/dist/discovery/schema-summary.js.map +1 -1
  45. package/dist/loop/artifacts.d.ts +39 -0
  46. package/dist/loop/artifacts.js +58 -0
  47. package/dist/loop/artifacts.js.map +1 -0
  48. package/dist/loop/context.d.ts +55 -0
  49. package/dist/loop/context.js +134 -0
  50. package/dist/loop/context.js.map +1 -0
  51. package/dist/loop/events.d.ts +62 -0
  52. package/dist/loop/events.js +68 -0
  53. package/dist/loop/events.js.map +1 -0
  54. package/dist/loop/id.d.ts +24 -0
  55. package/dist/loop/id.js +65 -0
  56. package/dist/loop/id.js.map +1 -0
  57. package/dist/loop/index.d.ts +8 -0
  58. package/dist/loop/index.js +9 -0
  59. package/dist/loop/index.js.map +1 -0
  60. package/dist/loop/replay.d.ts +47 -0
  61. package/dist/loop/replay.js +104 -0
  62. package/dist/loop/replay.js.map +1 -0
  63. package/dist/loop/results.d.ts +74 -0
  64. package/dist/loop/results.js +101 -0
  65. package/dist/loop/results.js.map +1 -0
  66. package/dist/loop/run.d.ts +22 -0
  67. package/dist/loop/run.js +671 -0
  68. package/dist/loop/run.js.map +1 -0
  69. package/dist/loop/summary.d.ts +5 -0
  70. package/dist/loop/summary.js +16 -0
  71. package/dist/loop/summary.js.map +1 -0
  72. package/dist/loop/types.d.ts +120 -0
  73. package/dist/loop/types.js +2 -0
  74. package/dist/loop/types.js.map +1 -0
  75. package/dist/loop/validate.d.ts +9 -0
  76. package/dist/loop/validate.js +122 -0
  77. package/dist/loop/validate.js.map +1 -0
  78. package/dist/orchestration/scheduler.js +1 -1
  79. package/dist/orchestration/scheduler.js.map +1 -1
  80. package/dist/output/events.d.ts +49 -1
  81. package/dist/output/events.js.map +1 -1
  82. package/dist/output/failed-artifacts.js +5 -0
  83. package/dist/output/failed-artifacts.js.map +1 -1
  84. package/dist/output/json-reporter.d.ts +2 -2
  85. package/dist/output/json-reporter.js +1 -1
  86. package/dist/output/json-reporter.js.map +1 -1
  87. package/dist/output/jsonl-reporter.d.ts +3 -4
  88. package/dist/output/jsonl-reporter.js +2 -2
  89. package/dist/output/jsonl-reporter.js.map +1 -1
  90. package/dist/output/pretty-renderer.js +15 -0
  91. package/dist/output/pretty-renderer.js.map +1 -1
  92. package/dist/output/pretty-reporter.js +35 -4
  93. package/dist/output/pretty-reporter.js.map +1 -1
  94. package/dist/output/pretty-view-builder.js +70 -1
  95. package/dist/output/pretty-view-builder.js.map +1 -1
  96. package/dist/output/pretty-view.d.ts +12 -2
  97. package/dist/output/verbose-formatter.d.ts +4 -1
  98. package/dist/output/verbose-formatter.js +67 -0
  99. package/dist/output/verbose-formatter.js.map +1 -1
  100. package/dist/pipeline/stage-barrier.js.map +1 -1
  101. package/dist/pipeline/validate.js +1 -1
  102. package/dist/pipeline/validate.js.map +1 -1
  103. package/dist/shared-agents/load.js +0 -2
  104. package/dist/shared-agents/load.js.map +1 -1
  105. package/dist/tools/load.js.map +1 -1
  106. package/dist/types/config.d.ts +1 -0
  107. package/dist/types/workflow.d.ts +13 -0
  108. package/dist/workflow/discovery.d.ts +1 -0
  109. package/dist/workflow/discovery.js +6 -4
  110. package/dist/workflow/discovery.js.map +1 -1
  111. package/dist/workflow/dsl.d.ts +1 -0
  112. package/dist/workflow/dsl.js +281 -137
  113. package/dist/workflow/dsl.js.map +1 -1
  114. package/dist/workflow/errors.js +1 -1
  115. package/dist/workflow/errors.js.map +1 -1
  116. package/dist/workflow/invocation-manager.js +14 -2
  117. package/dist/workflow/invocation-manager.js.map +1 -1
  118. package/dist/workflow/resolve-target.js +1 -1
  119. package/dist/workflow/resolve-target.js.map +1 -1
  120. package/dist/workflow/runtime.js +6 -1
  121. package/dist/workflow/runtime.js.map +1 -1
  122. package/dist/workflow/sandbox.js +1 -0
  123. package/dist/workflow/sandbox.js.map +1 -1
  124. package/dist/workflow/scope.d.ts +1 -1
  125. package/dist/workflow/scope.js +2 -15
  126. package/dist/workflow/scope.js.map +1 -1
  127. package/dist/workflow/types.d.ts +3 -0
  128. package/dist/workflow/validate.d.ts +2 -0
  129. package/dist/workflow/validate.js +369 -46
  130. package/dist/workflow/validate.js.map +1 -1
  131. package/package.json +16 -2
@@ -0,0 +1,671 @@
1
+ import { validateAndNormalizeLoopArgs, validateLoopRunResult } from "./validate.js";
2
+ import { createLoopId, createRoundId } from "./id.js";
3
+ import { buildLoopStartedPayload, buildLoopRoundStartedPayload, buildLoopRoundTerminalPayload, buildLoopTerminalPayload, } from "./events.js";
4
+ import { writeLoopDefinition, writeLoopInitialState, writeLoopFinalState, writeLoopExecutionRecord, writeLoopError, writeRoundArtifacts, } from "./artifacts.js";
5
+ import { getIsoTimestamp, getDurationMs, createLoopRoundRecord, createLoopExecutionRecord, createSettledSuccessEnvelope, createSettledFailureEnvelope, createLoopExhaustionError, } from "./results.js";
6
+ import { createLoopRoundContext, withActiveLoopContext, getActiveLoopContext } from "./context.js";
7
+ import { buildLoopStartReplayMarker, buildLoopRoundReplayMarker, recordLoopCacheMarker, stableHashJson, } from "./replay.js";
8
+ import { withToolForbidden } from "../workflow/scope.js";
9
+ import { OpenDynamicWorkflowError } from "../errors/types.js";
10
+ import { ErrorCode } from "../errors/codes.js";
11
+ import { getActiveWorkflowInvocation } from "../workflow/invocation-types.js";
12
+ import { cloneJsonValue } from "../workflow/json.js";
13
+ import { serializeError } from "../errors/serialize.js";
14
+ import { buildLoopSummary } from "./summary.js";
15
+ import { createPreview } from "../tools/serialization.js";
16
+ import { collectSecretValues, redactJsonValue, redactSerializedError } from "../security/env.js";
17
+ function terminalLoopEventType(status) {
18
+ if (status === "failed")
19
+ return "loop.failed";
20
+ if (status === "cancelled")
21
+ return "loop.cancelled";
22
+ if (status === "timed_out")
23
+ return "loop.timed_out";
24
+ if (status === "max_rounds")
25
+ return "loop.max_rounds";
26
+ return "loop.completed";
27
+ }
28
+ function terminalRoundEventType(status) {
29
+ if (status === "failed")
30
+ return "loop.round.failed";
31
+ if (status === "cancelled")
32
+ return "loop.round.cancelled";
33
+ if (status === "timed_out")
34
+ return "loop.round.timed_out";
35
+ return "loop.round.completed";
36
+ }
37
+ /**
38
+ * Main loop execution runtime.
39
+ */
40
+ export async function runLoop(input) {
41
+ const { runtime, signal, dsl } = input;
42
+ const secretValues = collectSecretValues(process.env, runtime.config?.security?.redactEnv);
43
+ // 1. Normalize loop input
44
+ const maxRoundsCeiling = runtime.config?.workflow?.maxLoopRounds ?? 20;
45
+ const normalized = validateAndNormalizeLoopArgs(input.loopInput, maxRoundsCeiling);
46
+ // 2. Allocate loop ID
47
+ const loopId = createLoopId(normalized.label);
48
+ // 3. Link parent cancellation and options.timeoutMs
49
+ let loopSignal = signal;
50
+ let timeoutHandle;
51
+ let timeoutController;
52
+ let parentAbortListener;
53
+ if (normalized.options.timeoutMs) {
54
+ timeoutController = new AbortController();
55
+ timeoutHandle = setTimeout(() => {
56
+ timeoutController?.abort(new OpenDynamicWorkflowError(ErrorCode.WORKFLOW_TIMEOUT, `Loop ${loopId} timed out after ${normalized.options.timeoutMs}ms.`));
57
+ }, normalized.options.timeoutMs);
58
+ parentAbortListener = () => {
59
+ if (timeoutHandle)
60
+ clearTimeout(timeoutHandle);
61
+ timeoutController?.abort(signal.reason);
62
+ };
63
+ if (signal.aborted) {
64
+ timeoutController.abort(signal.reason);
65
+ }
66
+ else {
67
+ signal.addEventListener("abort", parentAbortListener);
68
+ }
69
+ loopSignal = timeoutController.signal;
70
+ }
71
+ const parentLoop = getActiveLoopContext();
72
+ const parentLoopId = parentLoop?.loopId;
73
+ const loopArtifactDir = `loops/${loopId}`;
74
+ const workflowInvocationId = getActiveWorkflowInvocation()?.workflowInvocationId ?? runtime.runId;
75
+ // 4. Clone and validate initialState, capturing JSON-safety runtime failures
76
+ let initialStateCloned;
77
+ try {
78
+ initialStateCloned = cloneJsonValue(normalized.initialState, "initial state");
79
+ }
80
+ catch (err) {
81
+ const startedAt = getIsoTimestamp();
82
+ // Emit loop.started
83
+ const startedPayloadInput = {
84
+ loopId,
85
+ workflowInvocationId,
86
+ label: normalized.label,
87
+ maxRounds: normalized.options.maxRounds,
88
+ artifactPath: loopArtifactDir,
89
+ };
90
+ if (parentLoopId !== undefined) {
91
+ startedPayloadInput.parentLoopId = parentLoopId;
92
+ }
93
+ if (normalized.options.timeoutMs !== undefined) {
94
+ startedPayloadInput.timeoutMs = normalized.options.timeoutMs;
95
+ }
96
+ runtime.eventSink.emit("loop.started", buildLoopStartedPayload(startedPayloadInput));
97
+ // Write loop definition loops/<loopId>/loop.json
98
+ await writeLoopDefinition(runtime.artifactStore, loopId, {
99
+ options: {
100
+ failureMode: normalized.options.failureMode,
101
+ maxRounds: normalized.options.maxRounds,
102
+ timeoutMs: normalized.options.timeoutMs,
103
+ },
104
+ });
105
+ const valError = new OpenDynamicWorkflowError(ErrorCode.WORKFLOW_INVALID_CALL, `Loop '${normalized.label}' initialState is not JSON-safe: ${err.message}`);
106
+ const serializedError = serializeError(valError);
107
+ await writeLoopError(runtime.artifactStore, loopId, serializedError);
108
+ const finishedAt = getIsoTimestamp();
109
+ const durationMs = getDurationMs(startedAt, finishedAt);
110
+ const record = createLoopExecutionRecord({
111
+ loopId,
112
+ label: normalized.label,
113
+ status: "failed",
114
+ roundsCompleted: 0,
115
+ maxRounds: normalized.options.maxRounds,
116
+ initialState: undefined,
117
+ rounds: [],
118
+ startedAt,
119
+ finishedAt,
120
+ durationMs,
121
+ artifactPath: loopArtifactDir,
122
+ error: serializedError,
123
+ });
124
+ await writeLoopExecutionRecord(runtime.artifactStore, loopId, record);
125
+ const summary = buildLoopSummary(record);
126
+ if (summary.error) {
127
+ summary.error = redactSerializedError(summary.error, secretValues);
128
+ }
129
+ if (!runtime.loopSummaries) {
130
+ runtime.loopSummaries = [];
131
+ }
132
+ runtime.loopSummaries.push(summary);
133
+ const loopTerminalPayloadInput = {
134
+ loopId,
135
+ workflowInvocationId,
136
+ label: normalized.label,
137
+ status: "failed",
138
+ roundsCompleted: 0,
139
+ roundCount: 0,
140
+ maxRounds: normalized.options.maxRounds,
141
+ durationMs,
142
+ artifactPath: loopArtifactDir,
143
+ error: redactSerializedError(serializedError, secretValues),
144
+ };
145
+ runtime.eventSink.emit("loop.failed", buildLoopTerminalPayload(loopTerminalPayloadInput));
146
+ if (timeoutHandle) {
147
+ clearTimeout(timeoutHandle);
148
+ }
149
+ if (parentAbortListener) {
150
+ signal.removeEventListener("abort", parentAbortListener);
151
+ }
152
+ throw valError;
153
+ }
154
+ try {
155
+ const startedAt = getIsoTimestamp();
156
+ // 5. Emit loop.started
157
+ const startedPayloadInput = {
158
+ loopId,
159
+ workflowInvocationId,
160
+ label: normalized.label,
161
+ maxRounds: normalized.options.maxRounds,
162
+ artifactPath: loopArtifactDir,
163
+ };
164
+ if (parentLoopId !== undefined) {
165
+ startedPayloadInput.parentLoopId = parentLoopId;
166
+ }
167
+ if (normalized.options.timeoutMs !== undefined) {
168
+ startedPayloadInput.timeoutMs = normalized.options.timeoutMs;
169
+ }
170
+ runtime.eventSink.emit("loop.started", buildLoopStartedPayload(startedPayloadInput));
171
+ // 6. Write loop definition artifacts and initial-state.json
172
+ await writeLoopDefinition(runtime.artifactStore, loopId, {
173
+ options: {
174
+ failureMode: normalized.options.failureMode,
175
+ maxRounds: normalized.options.maxRounds,
176
+ timeoutMs: normalized.options.timeoutMs,
177
+ },
178
+ });
179
+ await writeLoopInitialState(runtime.artifactStore, loopId, initialStateCloned);
180
+ // 7. Record loop-start replay marker and cache marker
181
+ const initialStateHash = stableHashJson(initialStateCloned);
182
+ const optionsFingerprint = stableHashJson({
183
+ failureMode: normalized.options.failureMode,
184
+ maxRounds: normalized.options.maxRounds,
185
+ timeoutMs: normalized.options.timeoutMs,
186
+ });
187
+ const startMarkerInput = {
188
+ loopId,
189
+ label: normalized.label,
190
+ maxRounds: normalized.options.maxRounds,
191
+ optionsFingerprint,
192
+ initialStateHash,
193
+ maxRoundsCeiling,
194
+ };
195
+ if (parentLoopId !== undefined) {
196
+ startMarkerInput.parentLoopId = parentLoopId;
197
+ }
198
+ const loopStartMarker = buildLoopStartReplayMarker(startMarkerInput);
199
+ const callSequence = (runtime.callSequence ?? 0) + 1;
200
+ runtime.callSequence = callSequence;
201
+ await recordLoopCacheMarker({
202
+ store: runtime.artifactStore,
203
+ ...(runtime.callCache !== undefined ? { cache: runtime.callCache } : {}),
204
+ kind: "loop",
205
+ sequence: callSequence,
206
+ loopId,
207
+ fingerprint: loopStartMarker,
208
+ resultPath: `${loopArtifactDir}/loop.json`,
209
+ });
210
+ let currentState = initialStateCloned;
211
+ const rounds = [];
212
+ let loopStatus;
213
+ let loopError;
214
+ let roundErrorThrown;
215
+ // 8. Loop round progression
216
+ for (let roundIndex = 0; roundIndex < normalized.options.maxRounds; roundIndex++) {
217
+ const roundNumber = roundIndex + 1;
218
+ const roundId = createRoundId(loopId, roundNumber);
219
+ const roundArtifactDir = `loops/${loopId}/rounds/${roundNumber.toString().padStart(4, "0")}`;
220
+ const inputStateSnapshot = cloneJsonValue(currentState, "round input state");
221
+ // Stop before scheduling if aborted
222
+ if (loopSignal.aborted) {
223
+ if (signal.aborted) {
224
+ loopStatus = "cancelled";
225
+ }
226
+ else {
227
+ loopStatus = "timed_out";
228
+ }
229
+ loopError = serializeError(loopSignal.reason || new Error("Aborted"));
230
+ break;
231
+ }
232
+ // Create active loop context metadata
233
+ const activeRoundContext = {
234
+ loopId,
235
+ label: normalized.label,
236
+ roundIndex,
237
+ roundNumber,
238
+ roundId,
239
+ childAgentIds: [],
240
+ childWorkflowInvocationIds: [],
241
+ signal: loopSignal,
242
+ workflowInvocationId,
243
+ };
244
+ if (parentLoopId !== undefined) {
245
+ activeRoundContext.parentLoopId = parentLoopId;
246
+ }
247
+ // Create public LoopContext
248
+ const ctx = createLoopRoundContext({
249
+ loopId,
250
+ label: normalized.label,
251
+ runId: runtime.runId,
252
+ artifactsDir: runtime.artifactsDir,
253
+ roundIndex,
254
+ roundNumber,
255
+ signal: loopSignal,
256
+ dsl,
257
+ });
258
+ // Write round input-state.json
259
+ await runtime.artifactStore.writeJson(`${roundArtifactDir}/input-state.json`, inputStateSnapshot);
260
+ // Emit loop.round.started
261
+ const roundStartedAt = getIsoTimestamp();
262
+ runtime.eventSink.emit("loop.round.started", buildLoopRoundStartedPayload({
263
+ loopId,
264
+ workflowInvocationId,
265
+ label: normalized.label,
266
+ roundIndex,
267
+ roundNumber,
268
+ roundId,
269
+ startedAt: roundStartedAt,
270
+ artifactPath: roundArtifactDir,
271
+ }));
272
+ let roundResult;
273
+ let roundError;
274
+ let roundStatus = "completed";
275
+ let abortListener;
276
+ try {
277
+ const abortPromise = new Promise((_, reject) => {
278
+ if (loopSignal.aborted) {
279
+ reject(loopSignal.reason || new Error("Aborted"));
280
+ return;
281
+ }
282
+ abortListener = () => {
283
+ reject(loopSignal.reason || new Error("Aborted"));
284
+ };
285
+ loopSignal.addEventListener("abort", abortListener);
286
+ });
287
+ const runStateArg = cloneJsonValue(inputStateSnapshot, "run state argument");
288
+ const roundPromise = withActiveLoopContext(activeRoundContext, async () => {
289
+ return withToolForbidden("loop-round", () => {
290
+ return normalized.run(runStateArg, ctx);
291
+ });
292
+ });
293
+ roundResult = await Promise.race([roundPromise, abortPromise]);
294
+ }
295
+ catch (err) {
296
+ roundError = err;
297
+ if (signal.aborted) {
298
+ roundStatus = "cancelled";
299
+ }
300
+ else if (loopSignal.aborted) {
301
+ roundStatus = "timed_out";
302
+ }
303
+ else {
304
+ roundStatus = err.name === "AbortError" || err.code === "WORKFLOW_CANCELLED" ? "cancelled" :
305
+ err.code === "WORKFLOW_TIMEOUT" ? "timed_out" : "failed";
306
+ }
307
+ }
308
+ finally {
309
+ if (abortListener) {
310
+ loopSignal.removeEventListener("abort", abortListener);
311
+ }
312
+ }
313
+ const roundFinishedAt = getIsoTimestamp();
314
+ const roundDurationMs = getDurationMs(roundStartedAt, roundFinishedAt);
315
+ if (roundStatus === "completed") {
316
+ try {
317
+ validateLoopRunResult(roundResult, normalized.label);
318
+ }
319
+ catch (err) {
320
+ roundStatus = "failed";
321
+ roundError = err;
322
+ }
323
+ }
324
+ if (roundStatus !== "completed") {
325
+ roundErrorThrown = roundError;
326
+ const serializedError = serializeError(roundError);
327
+ const roundRecord = createLoopRoundRecord({
328
+ index: roundIndex,
329
+ roundNumber,
330
+ status: roundStatus,
331
+ inputState: inputStateSnapshot,
332
+ durationMs: roundDurationMs,
333
+ error: serializedError,
334
+ nestedCalls: {
335
+ agents: activeRoundContext.childAgentIds,
336
+ workflows: activeRoundContext.childWorkflowInvocationIds,
337
+ },
338
+ });
339
+ rounds.push(roundRecord);
340
+ await writeRoundArtifacts(runtime.artifactStore, loopId, roundNumber, {
341
+ inputState: inputStateSnapshot,
342
+ error: serializedError,
343
+ nestedCalls: {
344
+ agents: activeRoundContext.childAgentIds,
345
+ workflows: activeRoundContext.childWorkflowInvocationIds,
346
+ },
347
+ });
348
+ // Record round cache/replay marker
349
+ const roundMarker = buildLoopRoundReplayMarker({
350
+ loopId,
351
+ label: normalized.label,
352
+ roundIndex,
353
+ roundNumber,
354
+ nestedCallSequence: [
355
+ ...activeRoundContext.childAgentIds,
356
+ ...activeRoundContext.childWorkflowInvocationIds,
357
+ ],
358
+ stateBeforeHash: stableHashJson(inputStateSnapshot),
359
+ status: roundStatus,
360
+ });
361
+ const roundCallSequence = (runtime.callSequence ?? 0) + 1;
362
+ runtime.callSequence = roundCallSequence;
363
+ await recordLoopCacheMarker({
364
+ store: runtime.artifactStore,
365
+ ...(runtime.callCache !== undefined ? { cache: runtime.callCache } : {}),
366
+ kind: "loop",
367
+ sequence: roundCallSequence,
368
+ loopId,
369
+ roundIndex,
370
+ roundId,
371
+ fingerprint: roundMarker,
372
+ resultPath: `${roundArtifactDir}/error.json`,
373
+ status: roundStatus,
374
+ });
375
+ const roundTerminalPayloadInput = {
376
+ loopId,
377
+ workflowInvocationId,
378
+ label: normalized.label,
379
+ roundIndex,
380
+ roundNumber,
381
+ roundId,
382
+ status: roundStatus,
383
+ durationMs: roundDurationMs,
384
+ };
385
+ if (serializedError) {
386
+ roundTerminalPayloadInput.error = redactSerializedError(serializedError, secretValues);
387
+ }
388
+ runtime.eventSink.emit(terminalRoundEventType(roundStatus), buildLoopRoundTerminalPayload(roundTerminalPayloadInput));
389
+ loopStatus = roundStatus;
390
+ loopError = serializedError;
391
+ break;
392
+ }
393
+ // If execution was successful
394
+ const { done, nextState } = roundResult;
395
+ let nextStateCloned;
396
+ try {
397
+ nextStateCloned = cloneJsonValue(nextState, "next state");
398
+ }
399
+ catch (err) {
400
+ const valError = new OpenDynamicWorkflowError(ErrorCode.WORKFLOW_INVALID_CALL, `Loop '${normalized.label}' nextState is not JSON-safe: ${err.message}`);
401
+ roundErrorThrown = valError;
402
+ const serializedError = serializeError(valError);
403
+ const roundRecord = createLoopRoundRecord({
404
+ index: roundIndex,
405
+ roundNumber,
406
+ status: "failed",
407
+ inputState: inputStateSnapshot,
408
+ durationMs: roundDurationMs,
409
+ error: serializedError,
410
+ nestedCalls: {
411
+ agents: activeRoundContext.childAgentIds,
412
+ workflows: activeRoundContext.childWorkflowInvocationIds,
413
+ },
414
+ });
415
+ rounds.push(roundRecord);
416
+ await writeRoundArtifacts(runtime.artifactStore, loopId, roundNumber, {
417
+ inputState: inputStateSnapshot,
418
+ error: serializedError,
419
+ nestedCalls: {
420
+ agents: activeRoundContext.childAgentIds,
421
+ workflows: activeRoundContext.childWorkflowInvocationIds,
422
+ },
423
+ });
424
+ const roundMarker = buildLoopRoundReplayMarker({
425
+ loopId,
426
+ label: normalized.label,
427
+ roundIndex,
428
+ roundNumber,
429
+ nestedCallSequence: [
430
+ ...activeRoundContext.childAgentIds,
431
+ ...activeRoundContext.childWorkflowInvocationIds,
432
+ ],
433
+ stateBeforeHash: stableHashJson(inputStateSnapshot),
434
+ status: "failed",
435
+ });
436
+ const roundCallSequence = (runtime.callSequence ?? 0) + 1;
437
+ runtime.callSequence = roundCallSequence;
438
+ await recordLoopCacheMarker({
439
+ store: runtime.artifactStore,
440
+ ...(runtime.callCache !== undefined ? { cache: runtime.callCache } : {}),
441
+ kind: "loop",
442
+ sequence: roundCallSequence,
443
+ loopId,
444
+ roundIndex,
445
+ roundId,
446
+ fingerprint: roundMarker,
447
+ resultPath: `${roundArtifactDir}/error.json`,
448
+ status: "failed",
449
+ });
450
+ const failedRoundTerminalPayloadInput = {
451
+ loopId,
452
+ workflowInvocationId,
453
+ label: normalized.label,
454
+ roundIndex,
455
+ roundNumber,
456
+ roundId,
457
+ status: "failed",
458
+ durationMs: roundDurationMs,
459
+ };
460
+ if (serializedError) {
461
+ failedRoundTerminalPayloadInput.error = redactSerializedError(serializedError, secretValues);
462
+ }
463
+ runtime.eventSink.emit("loop.round.failed", buildLoopRoundTerminalPayload(failedRoundTerminalPayloadInput));
464
+ loopStatus = "failed";
465
+ loopError = serializedError;
466
+ break;
467
+ }
468
+ const roundRecord = createLoopRoundRecord({
469
+ index: roundIndex,
470
+ roundNumber,
471
+ status: "completed",
472
+ inputState: inputStateSnapshot,
473
+ nextState: nextStateCloned,
474
+ durationMs: roundDurationMs,
475
+ nestedCalls: {
476
+ agents: activeRoundContext.childAgentIds,
477
+ workflows: activeRoundContext.childWorkflowInvocationIds,
478
+ },
479
+ });
480
+ rounds.push(roundRecord);
481
+ await writeRoundArtifacts(runtime.artifactStore, loopId, roundNumber, {
482
+ inputState: inputStateSnapshot,
483
+ runResult: roundResult,
484
+ nextState: nextStateCloned,
485
+ nestedCalls: {
486
+ agents: activeRoundContext.childAgentIds,
487
+ workflows: activeRoundContext.childWorkflowInvocationIds,
488
+ },
489
+ });
490
+ const roundMarker = buildLoopRoundReplayMarker({
491
+ loopId,
492
+ label: normalized.label,
493
+ roundIndex,
494
+ roundNumber,
495
+ nestedCallSequence: [
496
+ ...activeRoundContext.childAgentIds,
497
+ ...activeRoundContext.childWorkflowInvocationIds,
498
+ ],
499
+ stateBeforeHash: stableHashJson(inputStateSnapshot),
500
+ stateAfterHash: stableHashJson(nextStateCloned),
501
+ status: "completed",
502
+ });
503
+ const roundCallSequence = (runtime.callSequence ?? 0) + 1;
504
+ runtime.callSequence = roundCallSequence;
505
+ await recordLoopCacheMarker({
506
+ store: runtime.artifactStore,
507
+ ...(runtime.callCache !== undefined ? { cache: runtime.callCache } : {}),
508
+ kind: "loop",
509
+ sequence: roundCallSequence,
510
+ loopId,
511
+ roundIndex,
512
+ roundId,
513
+ fingerprint: roundMarker,
514
+ resultPath: `${roundArtifactDir}/run-result.json`,
515
+ status: "succeeded",
516
+ });
517
+ const statePreview = redactJsonValue(createPreview(nextStateCloned), secretValues);
518
+ const roundTerminalPayloadInput = {
519
+ loopId,
520
+ workflowInvocationId,
521
+ label: normalized.label,
522
+ roundIndex,
523
+ roundNumber,
524
+ roundId,
525
+ status: "completed",
526
+ durationMs: roundDurationMs,
527
+ statePreview,
528
+ };
529
+ runtime.eventSink.emit("loop.round.completed", buildLoopRoundTerminalPayload(roundTerminalPayloadInput));
530
+ currentState = nextStateCloned;
531
+ if (done === true) {
532
+ loopStatus = "succeeded";
533
+ break;
534
+ }
535
+ if (roundNumber === normalized.options.maxRounds) {
536
+ loopStatus = "max_rounds";
537
+ const exhaustionError = createLoopExhaustionError(normalized.label, normalized.options.maxRounds);
538
+ roundErrorThrown = exhaustionError;
539
+ loopError = serializeError(exhaustionError);
540
+ break;
541
+ }
542
+ }
543
+ if (!loopStatus) {
544
+ if (loopSignal.aborted) {
545
+ if (signal.aborted) {
546
+ loopStatus = "cancelled";
547
+ }
548
+ else {
549
+ loopStatus = "timed_out";
550
+ }
551
+ loopError = serializeError(loopSignal.reason || new Error("Aborted"));
552
+ }
553
+ else {
554
+ loopStatus = "failed";
555
+ }
556
+ }
557
+ // Finalize record
558
+ const finishedAt = getIsoTimestamp();
559
+ const durationMs = getDurationMs(startedAt, finishedAt);
560
+ const recordInput = {
561
+ loopId,
562
+ label: normalized.label,
563
+ status: loopStatus,
564
+ roundsCompleted: rounds.length,
565
+ maxRounds: normalized.options.maxRounds,
566
+ initialState: initialStateCloned,
567
+ rounds,
568
+ startedAt,
569
+ finishedAt,
570
+ durationMs,
571
+ artifactPath: loopArtifactDir,
572
+ };
573
+ if (rounds.length > 0) {
574
+ recordInput.finalState = cloneJsonValue(currentState, "loop final state");
575
+ }
576
+ if (loopError !== undefined) {
577
+ recordInput.error = loopError;
578
+ }
579
+ const record = createLoopExecutionRecord(recordInput);
580
+ // Write final loop artifacts
581
+ if (rounds.length > 0) {
582
+ await writeLoopFinalState(runtime.artifactStore, loopId, currentState);
583
+ }
584
+ await writeLoopExecutionRecord(runtime.artifactStore, loopId, record);
585
+ if (loopStatus !== "succeeded" && loopError !== undefined) {
586
+ await writeLoopError(runtime.artifactStore, loopId, loopError);
587
+ }
588
+ // Push LoopSummary
589
+ const summary = buildLoopSummary(record);
590
+ if (summary.error) {
591
+ summary.error = redactSerializedError(summary.error, secretValues);
592
+ }
593
+ if (!runtime.loopSummaries) {
594
+ runtime.loopSummaries = [];
595
+ }
596
+ runtime.loopSummaries.push(summary);
597
+ // Emit terminal loop event
598
+ const loopTerminalPayloadInput = {
599
+ loopId,
600
+ workflowInvocationId,
601
+ label: normalized.label,
602
+ status: loopStatus,
603
+ roundsCompleted: rounds.length,
604
+ roundCount: rounds.length,
605
+ maxRounds: normalized.options.maxRounds,
606
+ durationMs,
607
+ artifactPath: loopArtifactDir,
608
+ };
609
+ if (loopError !== undefined) {
610
+ loopTerminalPayloadInput.error = redactSerializedError(loopError, secretValues);
611
+ }
612
+ if (rounds.length > 0) {
613
+ loopTerminalPayloadInput.statePreview = redactJsonValue(createPreview(currentState), secretValues);
614
+ }
615
+ runtime.eventSink.emit(terminalLoopEventType(loopStatus), buildLoopTerminalPayload(loopTerminalPayloadInput));
616
+ // Return or throw
617
+ if (normalized.options.failureMode === "throw") {
618
+ if (loopStatus === "succeeded") {
619
+ return currentState;
620
+ }
621
+ if (loopStatus === "max_rounds") {
622
+ throw createLoopExhaustionError(normalized.label, normalized.options.maxRounds);
623
+ }
624
+ if (loopStatus === "timed_out") {
625
+ throw new OpenDynamicWorkflowError(ErrorCode.WORKFLOW_TIMEOUT, `Loop '${normalized.label}' timed out.`);
626
+ }
627
+ if (loopStatus === "cancelled") {
628
+ throw new OpenDynamicWorkflowError(ErrorCode.WORKFLOW_CANCELLED, `Loop '${normalized.label}' was cancelled.`);
629
+ }
630
+ throw roundErrorThrown || new OpenDynamicWorkflowError(ErrorCode.WORKFLOW_FAILED, loopError?.message || `Loop '${normalized.label}' failed.`);
631
+ }
632
+ else {
633
+ // Settled mode
634
+ if (loopStatus === "succeeded") {
635
+ return createSettledSuccessEnvelope({
636
+ label: normalized.label,
637
+ loopId,
638
+ roundsCompleted: rounds.length,
639
+ finalState: currentState,
640
+ artifactsDir: loopArtifactDir,
641
+ });
642
+ }
643
+ else {
644
+ const failureStatus = loopStatus;
645
+ const settledFailureInput = {
646
+ status: failureStatus,
647
+ label: normalized.label,
648
+ loopId,
649
+ roundsCompleted: rounds.length,
650
+ artifactsDir: loopArtifactDir,
651
+ };
652
+ if (rounds.length > 0) {
653
+ settledFailureInput.finalState = currentState;
654
+ }
655
+ if (loopError !== undefined) {
656
+ settledFailureInput.error = loopError;
657
+ }
658
+ return createSettledFailureEnvelope(settledFailureInput);
659
+ }
660
+ }
661
+ }
662
+ finally {
663
+ if (timeoutHandle) {
664
+ clearTimeout(timeoutHandle);
665
+ }
666
+ if (parentAbortListener) {
667
+ signal.removeEventListener("abort", parentAbortListener);
668
+ }
669
+ }
670
+ }
671
+ //# sourceMappingURL=run.js.map