@travisliu/open-dynamic-workflow 0.3.1 → 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.
- package/CHANGELOG.md +16 -0
- package/README.md +43 -17
- package/dist/agents/codex-exec.js +3 -1
- package/dist/agents/codex-exec.js.map +1 -1
- package/dist/agents/cursor-agent.d.ts +21 -0
- package/dist/agents/cursor-agent.js +171 -0
- package/dist/agents/cursor-agent.js.map +1 -0
- package/dist/agents/execute-agent.js +1 -1
- package/dist/agents/execute-agent.js.map +1 -1
- package/dist/agents/registry.js +2 -0
- package/dist/agents/registry.js.map +1 -1
- package/dist/artifacts/call-cache.d.ts +27 -3
- package/dist/artifacts/call-cache.js +160 -59
- package/dist/artifacts/call-cache.js.map +1 -1
- package/dist/artifacts/run-store.js.map +1 -1
- package/dist/cli/commands/doctor.js +0 -4
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.js +2 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/validate.js +2 -1
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/init/prompts.d.ts +1 -1
- package/dist/cli/init/prompts.js +4 -4
- package/dist/cli/init/prompts.js.map +1 -1
- package/dist/cli/init/renderer.js +2 -1
- package/dist/cli/init/renderer.js.map +1 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/load.js +1 -1
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.js +3 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/discovery/collect-files.js +2 -2
- package/dist/discovery/collect-files.js.map +1 -1
- package/dist/discovery/definition-call.js +1 -1
- package/dist/discovery/definition-call.js.map +1 -1
- package/dist/discovery/file-patterns.js +1 -1
- package/dist/discovery/file-patterns.js.map +1 -1
- package/dist/discovery/schema-summary.js.map +1 -1
- package/dist/loop/artifacts.d.ts +39 -0
- package/dist/loop/artifacts.js +58 -0
- package/dist/loop/artifacts.js.map +1 -0
- package/dist/loop/context.d.ts +55 -0
- package/dist/loop/context.js +134 -0
- package/dist/loop/context.js.map +1 -0
- package/dist/loop/events.d.ts +62 -0
- package/dist/loop/events.js +68 -0
- package/dist/loop/events.js.map +1 -0
- package/dist/loop/id.d.ts +24 -0
- package/dist/loop/id.js +65 -0
- package/dist/loop/id.js.map +1 -0
- package/dist/loop/index.d.ts +8 -0
- package/dist/loop/index.js +9 -0
- package/dist/loop/index.js.map +1 -0
- package/dist/loop/replay.d.ts +47 -0
- package/dist/loop/replay.js +104 -0
- package/dist/loop/replay.js.map +1 -0
- package/dist/loop/results.d.ts +74 -0
- package/dist/loop/results.js +101 -0
- package/dist/loop/results.js.map +1 -0
- package/dist/loop/run.d.ts +22 -0
- package/dist/loop/run.js +671 -0
- package/dist/loop/run.js.map +1 -0
- package/dist/loop/summary.d.ts +5 -0
- package/dist/loop/summary.js +16 -0
- package/dist/loop/summary.js.map +1 -0
- package/dist/loop/types.d.ts +120 -0
- package/dist/loop/types.js +2 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/loop/validate.d.ts +9 -0
- package/dist/loop/validate.js +122 -0
- package/dist/loop/validate.js.map +1 -0
- package/dist/orchestration/scheduler.js +1 -1
- package/dist/orchestration/scheduler.js.map +1 -1
- package/dist/output/events.d.ts +49 -1
- package/dist/output/events.js.map +1 -1
- package/dist/output/failed-artifacts.js +5 -0
- package/dist/output/failed-artifacts.js.map +1 -1
- package/dist/output/json-reporter.d.ts +2 -2
- package/dist/output/json-reporter.js +1 -1
- package/dist/output/json-reporter.js.map +1 -1
- package/dist/output/jsonl-reporter.d.ts +3 -4
- package/dist/output/jsonl-reporter.js +2 -2
- package/dist/output/jsonl-reporter.js.map +1 -1
- package/dist/output/pretty-renderer.js +15 -0
- package/dist/output/pretty-renderer.js.map +1 -1
- package/dist/output/pretty-reporter.js +35 -4
- package/dist/output/pretty-reporter.js.map +1 -1
- package/dist/output/pretty-view-builder.js +70 -1
- package/dist/output/pretty-view-builder.js.map +1 -1
- package/dist/output/pretty-view.d.ts +12 -2
- package/dist/output/verbose-formatter.d.ts +4 -1
- package/dist/output/verbose-formatter.js +67 -0
- package/dist/output/verbose-formatter.js.map +1 -1
- package/dist/pipeline/stage-barrier.js.map +1 -1
- package/dist/pipeline/validate.js +1 -1
- package/dist/pipeline/validate.js.map +1 -1
- package/dist/shared-agents/load.js +0 -2
- package/dist/shared-agents/load.js.map +1 -1
- package/dist/tools/load.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/workflow.d.ts +13 -0
- package/dist/workflow/discovery.d.ts +1 -0
- package/dist/workflow/discovery.js +6 -4
- package/dist/workflow/discovery.js.map +1 -1
- package/dist/workflow/dsl.d.ts +1 -0
- package/dist/workflow/dsl.js +281 -137
- package/dist/workflow/dsl.js.map +1 -1
- package/dist/workflow/errors.js +1 -1
- package/dist/workflow/errors.js.map +1 -1
- package/dist/workflow/invocation-manager.js +14 -2
- package/dist/workflow/invocation-manager.js.map +1 -1
- package/dist/workflow/resolve-target.js +1 -1
- package/dist/workflow/resolve-target.js.map +1 -1
- package/dist/workflow/runtime.js +6 -1
- package/dist/workflow/runtime.js.map +1 -1
- package/dist/workflow/sandbox.js +1 -0
- package/dist/workflow/sandbox.js.map +1 -1
- package/dist/workflow/scope.d.ts +1 -1
- package/dist/workflow/scope.js +2 -15
- package/dist/workflow/scope.js.map +1 -1
- package/dist/workflow/types.d.ts +3 -0
- package/dist/workflow/validate.d.ts +2 -0
- package/dist/workflow/validate.js +369 -46
- package/dist/workflow/validate.js.map +1 -1
- package/package.json +5 -2
package/dist/loop/run.js
ADDED
|
@@ -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
|