@exellix/graph-engine 7.2.9 → 7.2.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +1 -0
- package/dist/src/runtime/ExellixGraphRuntime.js +581 -564
- package/dist/src/runtime/buildRunLog.js +12 -0
- package/dist/src/runtime/graphEngineLogxer.d.ts +42 -0
- package/dist/src/runtime/graphEngineLogxer.js +165 -0
- package/dist/src/runtime/mergeExellixGraphRuntimeInvocation.js +1 -0
- package/dist/src/runtime/runtimeObjects.d.ts +5 -18
- package/dist/src/runtime/runtimeObjects.js +0 -4
- package/dist/src/types/options.d.ts +6 -0
- package/dist/src/types/runLog.d.ts +9 -0
- package/dist/src/types/runLog.js +0 -4
- package/package.json +8 -8
|
@@ -24,6 +24,7 @@ import { buildAiTasksObservabilityRecord } from "./aiTasksObservability.js";
|
|
|
24
24
|
import { buildRunTaskIdentityEnvelope, mergeDefinedLlmCallParts, shouldForwardRunTaskTraceMode, } from "./runTaskAugments.js";
|
|
25
25
|
import { buildRunLog, extractLogxerCorrelationFromMetadata, extractTaskRunLogFromMetadata, resolveRunLogLimits, } from "./buildRunLog.js";
|
|
26
26
|
import { setRuntimeObjectsLastJobId, summarizeRuntimeObjectsForPlayground } from "./runtimeObjects.js";
|
|
27
|
+
import { DebugLogAbstract, bindGraphEngineRunLogxer, clearGraphEngineRunLogxer, createGraphEngineLogxer, ensureGraphEngineLogxerOnRuntimeObjects, logGraphEngineErrorCode, patchGraphNodeLogContext, runGraphWithLogContext, traceExecutionMemory, } from "./graphEngineLogxer.js";
|
|
27
28
|
import { assertHostJobId, newGraphRunTaskId } from "./graphRunIdentity.js";
|
|
28
29
|
import { resolveTaskKey } from "./resolveTaskKey.js";
|
|
29
30
|
import { buildPredicateEvalContextForNode, mirrorTaskVariablesOnExecution, readExecutionVariableBuckets, seedGraphVariableBucketsFromRuntime, } from "./variables.js";
|
|
@@ -611,9 +612,11 @@ export function createExellixGraphRuntime(opts) {
|
|
|
611
612
|
? toRunTaskModelConfigForPhase(effectiveModelConfig, 'main')
|
|
612
613
|
: undefined;
|
|
613
614
|
// DEBUG: Verify execution object before sending
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
615
|
+
traceExecutionMemory('executeNode', 'Node execution object before runTask', {
|
|
616
|
+
nodeId: input.node?.id,
|
|
617
|
+
execution: input.execution,
|
|
618
|
+
});
|
|
619
|
+
patchGraphNodeLogContext(String(input.node?.id ?? ''));
|
|
617
620
|
let execution = input.execution;
|
|
618
621
|
if (execution == null || typeof execution !== "object")
|
|
619
622
|
execution = {};
|
|
@@ -855,18 +858,18 @@ export function createExellixGraphRuntime(opts) {
|
|
|
855
858
|
smartInput: input.node.smartInput,
|
|
856
859
|
});
|
|
857
860
|
// TRACE: Validate request object before sending
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
}
|
|
861
|
+
traceExecutionMemory('executeNode', 'runTask request built', {
|
|
862
|
+
nodeId: input.node?.id,
|
|
863
|
+
executionMemory: req.executionMemory,
|
|
864
|
+
executionMemoryType: typeof req.executionMemory,
|
|
865
|
+
executionMemoryIsUndefined: req.executionMemory === undefined,
|
|
866
|
+
executionMemoryIsNull: req.executionMemory === null,
|
|
867
|
+
hasExecutionMemoryProperty: 'executionMemory' in req,
|
|
868
|
+
requestKeys: Object.keys(req),
|
|
869
|
+
executionMemoryKeys: req.executionMemory && typeof req.executionMemory === 'object'
|
|
870
|
+
? Object.keys(req.executionMemory)
|
|
871
|
+
: undefined,
|
|
872
|
+
});
|
|
870
873
|
let res;
|
|
871
874
|
const nodeTimeoutMs = input.nodeTimeoutMs;
|
|
872
875
|
const stepRetryPolicyResolved = resolveStepRetryPolicy(input.stepRetryPolicy ?? opts.stepRetryPolicy, input.node);
|
|
@@ -1047,28 +1050,25 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1047
1050
|
updatedExecution = postExec;
|
|
1048
1051
|
}
|
|
1049
1052
|
// TRACE: Log execution object from response
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
}
|
|
1053
|
+
traceExecutionMemory('executeNode', 'Processing runTask response', {
|
|
1054
|
+
nodeId: input.node?.id,
|
|
1055
|
+
responseExecution: res.execution,
|
|
1056
|
+
inputExecution: input.execution,
|
|
1057
|
+
updatedExecutionBeforeMapping: updatedExecution,
|
|
1058
|
+
});
|
|
1056
1059
|
// Normalize task output for executionMapping.
|
|
1057
1060
|
// Normalize: create output object that includes both response output and parsed
|
|
1058
1061
|
// This allows path resolution to access both "output.parsed.shortAnswer" and "output.shortAnswer"
|
|
1059
1062
|
// If response.parsed exists, wrap it in { parsed: ... } structure for path resolution
|
|
1060
1063
|
const responseParsed = res.parsed;
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeNode: res.parsed.fullAnswer =`, res.parsed.fullAnswer);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1064
|
+
traceExecutionMemory('executeNode', 'runTask response structure', {
|
|
1065
|
+
nodeId: input.node?.id,
|
|
1066
|
+
parsed: res.parsed,
|
|
1067
|
+
parsedType: typeof res.parsed,
|
|
1068
|
+
parsedKeys: res.parsed && typeof res.parsed === 'object' ? Object.keys(res.parsed) : undefined,
|
|
1069
|
+
parsedShortAnswer: res.parsed && typeof res.parsed === 'object' ? res.parsed.shortAnswer : undefined,
|
|
1070
|
+
parsedFullAnswer: res.parsed && typeof res.parsed === 'object' ? res.parsed.fullAnswer : undefined,
|
|
1071
|
+
});
|
|
1072
1072
|
// Create output object similar to executeNode.ts - use parsed as the base, or create structure
|
|
1073
1073
|
const outputForMapping = res.parsed !== undefined
|
|
1074
1074
|
? (typeof res.parsed === 'object' && res.parsed !== null && !Array.isArray(res.parsed))
|
|
@@ -1076,15 +1076,13 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1076
1076
|
: { parsed: responseParsed } // Wrap parsed in object structure for path resolution
|
|
1077
1077
|
: undefined;
|
|
1078
1078
|
// TRACE: Log outputForMapping structure
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1079
|
+
traceExecutionMemory('executeNode', 'outputForMapping structure', {
|
|
1080
|
+
nodeId: input.node?.id,
|
|
1081
|
+
outputForMapping,
|
|
1082
|
+
outputForMappingParsed: outputForMapping?.parsed,
|
|
1083
|
+
outputForMappingParsedShortAnswer: outputForMapping?.parsed?.shortAnswer,
|
|
1084
|
+
outputForMappingParsedFullAnswer: outputForMapping?.parsed?.fullAnswer,
|
|
1085
|
+
});
|
|
1088
1086
|
updatedExecution = applyTaskResultMapping({
|
|
1089
1087
|
targetMemory: updatedExecution,
|
|
1090
1088
|
mapping: input.node?.executionMapping,
|
|
@@ -1094,12 +1092,12 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1094
1092
|
});
|
|
1095
1093
|
const updatedOutputsMemory = isPlainRecord(input.outputsMemory) ? input.outputsMemory : {};
|
|
1096
1094
|
// TRACE: Log final execution state before returning
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
}
|
|
1095
|
+
traceExecutionMemory('executeNode', 'Final execution state before returning', {
|
|
1096
|
+
nodeId: input.node?.id,
|
|
1097
|
+
updatedExecution,
|
|
1098
|
+
updatedExecutionType: typeof updatedExecution,
|
|
1099
|
+
updatedExecutionIsUndefined: updatedExecution === undefined,
|
|
1100
|
+
});
|
|
1103
1101
|
const resMetaOk = res.metadata ?? res.meta;
|
|
1104
1102
|
const taskRunLogOk = [...retryOutcome.syntheticRunLog, ...extractTaskRunLogFromMetadata(resMetaOk)];
|
|
1105
1103
|
const logxerCorrelationIdOk = extractLogxerCorrelationFromMetadata(resMetaOk);
|
|
@@ -1158,553 +1156,572 @@ export function createExellixGraphRuntime(opts) {
|
|
|
1158
1156
|
throw new Error('GRAPH_ID_REQUIRED: execution request model must include a non-empty id');
|
|
1159
1157
|
}
|
|
1160
1158
|
const resolvedGraphId = String(resolvedGraphIdRaw);
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1159
|
+
const runLogxer = createGraphEngineLogxer({ logging: merged.logging });
|
|
1160
|
+
return runGraphWithLogContext({ jobId, taskId: graphTaskId, graphId: resolvedGraphId, runId: graphTaskId }, async () => {
|
|
1161
|
+
bindGraphEngineRunLogxer(runLogxer);
|
|
1162
|
+
try {
|
|
1163
|
+
assertCanonicalGraphDocument(graph, { jobId, graphId: resolvedGraphId });
|
|
1164
|
+
let runxClient = opts.runx;
|
|
1165
|
+
if (graphNeedsRunxClient(graph, merged.modelConfig)) {
|
|
1166
|
+
if (!runxClient) {
|
|
1167
|
+
if (!opts.runxCreateOptions) {
|
|
1168
|
+
const err = new Error("RUNX_REQUIRED: graph uses jsConditionFunction, aiCondition, or conditional modelConfig cases that require runx. Pass runx or runxCreateOptions on createExellixGraphRuntime.");
|
|
1169
|
+
err.code = "RUNX_REQUIRED";
|
|
1170
|
+
throw err;
|
|
1171
|
+
}
|
|
1172
|
+
runxClient = await createRunx(opts.runxCreateOptions);
|
|
1173
|
+
await runxClient.bootstrap();
|
|
1174
|
+
await runxClient.reload();
|
|
1175
|
+
}
|
|
1169
1176
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
const graphAudit = {
|
|
1176
|
-
source: 'model',
|
|
1177
|
-
contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
|
|
1178
|
-
};
|
|
1179
|
-
const graphDocumentModel = mergeGraphDocumentModel(graph);
|
|
1180
|
-
const graphExecution = graphDocumentModel.graphExecution;
|
|
1181
|
-
const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
|
|
1182
|
-
const coreObjectiveConfig = resolveCoreObjectiveConfig(graphExecution);
|
|
1183
|
-
setRuntimeObjectsLastJobId(merged.runtimeObjects, jobId);
|
|
1184
|
-
const { finalizer } = validateGraphFinalizer(graph);
|
|
1185
|
-
const engine = opts.engineFactory.create({
|
|
1186
|
-
graph,
|
|
1187
|
-
mode: runtime.mode,
|
|
1188
|
-
goalNodeId: runtime.goalNodeId,
|
|
1189
|
-
dimension: runtime.dimension,
|
|
1190
|
-
initialState: runtime.initialState,
|
|
1191
|
-
initialVariables: runtime.initialVariables,
|
|
1192
|
-
});
|
|
1193
|
-
const outputsByNodeId = {};
|
|
1194
|
-
const stepsResponses = [];
|
|
1195
|
-
const errors = [];
|
|
1196
|
-
const finalizerNodeId = String(finalizer.id);
|
|
1197
|
-
const jobCorrelation = job?.jobType != null ? { jobType: job.jobType } : undefined;
|
|
1198
|
-
// Pre-compute incoming-edge map for conditional-edge filtering of plan.nextNodes.
|
|
1199
|
-
const rawEdges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
1200
|
-
const hasConditionalEdges = rawEdges.some((e) => e && typeof e === "object" && e.when != null);
|
|
1201
|
-
const incomingByTo = new Map();
|
|
1202
|
-
if (hasConditionalEdges) {
|
|
1203
|
-
for (const e of rawEdges) {
|
|
1204
|
-
if (!e || typeof e !== "object")
|
|
1205
|
-
continue;
|
|
1206
|
-
const to = e.to;
|
|
1207
|
-
if (typeof to !== "string" || to.length === 0)
|
|
1208
|
-
continue;
|
|
1209
|
-
const list = incomingByTo.get(to) ?? [];
|
|
1210
|
-
list.push(e);
|
|
1211
|
-
incomingByTo.set(to, list);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
const graphRunStartedAt = Date.now();
|
|
1215
|
-
const taskRunLogBuffer = [];
|
|
1216
|
-
let logxerCorrelationIdLast;
|
|
1217
|
-
function appendTaskRunLogFromError(e) {
|
|
1218
|
-
if (Array.isArray(e?.taskRunLog))
|
|
1219
|
-
taskRunLogBuffer.push(...e.taskRunLog);
|
|
1220
|
-
if (typeof e?.logxerCorrelationId === "string" && e.logxerCorrelationId.length > 0) {
|
|
1221
|
-
logxerCorrelationIdLast = e.logxerCorrelationId;
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
function finalizeGraphPayload(graphStatus) {
|
|
1225
|
-
const lim = resolveRunLogLimits({
|
|
1226
|
-
runLogMode: merged.runLogMode,
|
|
1227
|
-
maxRunLogEntries: merged.maxRunLogEntries,
|
|
1228
|
-
maxRunLogDataJsonChars: merged.maxRunLogDataJsonChars,
|
|
1229
|
-
defaults: {},
|
|
1230
|
-
});
|
|
1231
|
-
const built = buildRunLog({
|
|
1232
|
-
...lim,
|
|
1233
|
-
jobId,
|
|
1234
|
-
taskId: graphTaskId,
|
|
1235
|
-
graphId: resolvedGraphId,
|
|
1236
|
-
graphRunStartedAt,
|
|
1237
|
-
graphStatus,
|
|
1238
|
-
execution: currentExecution,
|
|
1239
|
-
taskAppendedEntries: taskRunLogBuffer,
|
|
1240
|
-
logxerCorrelationId: logxerCorrelationIdLast,
|
|
1241
|
-
});
|
|
1242
|
-
return {
|
|
1243
|
-
execution: currentExecution,
|
|
1244
|
-
outputsMemory: currentOutputsMemory,
|
|
1245
|
-
...built,
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
// Track current memory and execution state (will be updated as nodes execute)
|
|
1249
|
-
let currentJobMemory = runtime.jobMemory || {};
|
|
1250
|
-
let currentTaskMemory = runtime.taskMemory;
|
|
1251
|
-
let currentExecution = normalizeRuntimeExecutionMemory(runtime);
|
|
1252
|
-
let currentOutputsMemory = normalizeRuntimeOutputsMemory(runtime);
|
|
1253
|
-
if (!currentExecution._trace)
|
|
1254
|
-
currentExecution._trace = { nodes: {} };
|
|
1255
|
-
if (!currentExecution._trace.nodes)
|
|
1256
|
-
currentExecution._trace.nodes = {};
|
|
1257
|
-
const jobKnowledgePatch = await resolveKnowledgePatch({
|
|
1258
|
-
refs: graph.jobKnowledge,
|
|
1259
|
-
resolver: opts.resolveJobKnowledge,
|
|
1260
|
-
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId },
|
|
1261
|
-
});
|
|
1262
|
-
seedGraphRunExecutionState({
|
|
1263
|
-
execution: currentExecution,
|
|
1264
|
-
jobMemory: currentJobMemory,
|
|
1265
|
-
job: runtime.job,
|
|
1266
|
-
});
|
|
1267
|
-
seedGraphVariableBucketsFromRuntime({
|
|
1268
|
-
execution: currentExecution,
|
|
1269
|
-
graph,
|
|
1270
|
-
runtime,
|
|
1271
|
-
job,
|
|
1272
|
-
});
|
|
1273
|
-
assertFinalizerRequiredReadsResolvable({
|
|
1274
|
-
graph,
|
|
1275
|
-
finalizer,
|
|
1276
|
-
executionMemory: currentExecution,
|
|
1277
|
-
outputsMemory: currentOutputsMemory,
|
|
1278
|
-
});
|
|
1279
|
-
const coreObjectiveValue = resolveCoreObjectiveValue({
|
|
1280
|
-
sourcePath: coreObjectiveConfig.sourcePath,
|
|
1281
|
-
execution: currentExecution,
|
|
1282
|
-
jobMemory: currentJobMemory,
|
|
1283
|
-
taskMemory: currentTaskMemory,
|
|
1284
|
-
job,
|
|
1285
|
-
});
|
|
1286
|
-
if (eventEmitter) {
|
|
1287
|
-
eventEmitter.emit(createGraphStartEvent(jobId, resolvedGraphId, graphTaskId, {
|
|
1288
|
-
agentId: job?.agentId,
|
|
1289
|
-
input: {
|
|
1290
|
-
variables: runtime.variables,
|
|
1291
|
-
jobMemory: currentJobMemory,
|
|
1292
|
-
taskMemory: currentTaskMemory,
|
|
1293
|
-
},
|
|
1294
|
-
...(jobCorrelation ?? {}),
|
|
1295
|
-
}));
|
|
1296
|
-
}
|
|
1297
|
-
function buildDebugTrace() {
|
|
1298
|
-
if (!debugMode)
|
|
1299
|
-
return undefined;
|
|
1300
|
-
const traceNodes = (currentExecution?._trace?.nodes ?? {});
|
|
1301
|
-
const out = [];
|
|
1302
|
-
for (const [nodeId, entry] of Object.entries(traceNodes)) {
|
|
1303
|
-
if (entry == null || typeof entry !== "object")
|
|
1304
|
-
continue;
|
|
1305
|
-
out.push({ nodeId, ...entry });
|
|
1306
|
-
}
|
|
1307
|
-
// Stable order by startedAt, then by nodeId.
|
|
1308
|
-
out.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0) || a.nodeId.localeCompare(b.nodeId));
|
|
1309
|
-
return { nodes: out };
|
|
1310
|
-
}
|
|
1311
|
-
function appendStepResponse(node, output) {
|
|
1312
|
-
if (node?.type === "finalizer")
|
|
1313
|
-
return;
|
|
1314
|
-
const nodeResponse = unwrapNodeResponse(output);
|
|
1315
|
-
stepsResponses.push({
|
|
1316
|
-
[coreObjectiveConfig.propertyName]: coreObjectiveValue,
|
|
1317
|
-
[nodeResponseKeys.singular]: nodeResponse,
|
|
1318
|
-
});
|
|
1319
|
-
}
|
|
1320
|
-
function buildFinalOutputFromGraphResponse() {
|
|
1321
|
-
const executionMemoryForResponse = isPlainRecord(currentExecution)
|
|
1322
|
-
? structuredClone(currentExecution)
|
|
1323
|
-
: currentExecution;
|
|
1324
|
-
return applyGraphResponseDefinition({
|
|
1325
|
-
response: graph.response,
|
|
1326
|
-
context: {
|
|
1327
|
-
graph,
|
|
1328
|
-
executionMemory: executionMemoryForResponse,
|
|
1329
|
-
outputsMemory: isPlainRecord(currentOutputsMemory)
|
|
1330
|
-
? structuredClone(currentOutputsMemory)
|
|
1331
|
-
: currentOutputsMemory,
|
|
1332
|
-
outputsByNodeId,
|
|
1333
|
-
stepsResponses,
|
|
1334
|
-
},
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
const playgroundReporter = runtime.playgroundReporter ?? opts.playgroundReporter;
|
|
1338
|
-
if (playgroundReporter) {
|
|
1339
|
-
playgroundReporter.step("graph:start", {
|
|
1340
|
-
jobId,
|
|
1341
|
-
taskId: graphTaskId,
|
|
1342
|
-
graphId: resolvedGraphId,
|
|
1343
|
-
executionKeys: Object.keys(currentExecution),
|
|
1344
|
-
variablesKeys: runtime.variables != null ? Object.keys(runtime.variables) : [],
|
|
1345
|
-
executionExcerpt: typeof currentExecution === "object" && currentExecution !== null
|
|
1346
|
-
? JSON.stringify(currentExecution, null, 2).slice(0, 500)
|
|
1347
|
-
: undefined,
|
|
1348
|
-
...(merged.playgroundMeta != null && typeof merged.playgroundMeta === "object"
|
|
1349
|
-
? merged.playgroundMeta
|
|
1350
|
-
: {}),
|
|
1351
|
-
});
|
|
1352
|
-
if (merged.runtimeObjects) {
|
|
1353
|
-
playgroundReporter.step("graph:observability", summarizeRuntimeObjectsForPlayground(merged.runtimeObjects));
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
// TRACE: Log initial execution state
|
|
1357
|
-
if (process.env.DEBUG_EXECUTION_MEMORY === 'true') {
|
|
1358
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: Initial execution state`);
|
|
1359
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: runtime.executionMemory =`, JSON.stringify(runtime.executionMemory, null, 2));
|
|
1360
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: currentExecution =`, JSON.stringify(currentExecution, null, 2));
|
|
1361
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: currentExecution type =`, typeof currentExecution);
|
|
1362
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: currentExecution is undefined?`, currentExecution === undefined);
|
|
1363
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: currentExecution is null?`, currentExecution === null);
|
|
1364
|
-
}
|
|
1365
|
-
function recordForStructuredDataFilters() {
|
|
1366
|
-
return isPlainRecord(currentExecution?.input)
|
|
1367
|
-
? currentExecution.input
|
|
1368
|
-
: {};
|
|
1369
|
-
}
|
|
1370
|
-
const graphEntryForGate = graphDocumentModel.graphEntry;
|
|
1371
|
-
if (graphEntryForGate?.dataFilters !== undefined) {
|
|
1372
|
-
const entryEv = evaluateStructuredDataFilters(graphEntryForGate.dataFilters, recordForStructuredDataFilters());
|
|
1373
|
-
if (entryEv.status === "unsupported_shape" ||
|
|
1374
|
-
(entryEv.status === "evaluated" && !entryEv.ok)) {
|
|
1375
|
-
const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: metadata.graphEntry.dataFilters (structured) did not accept runtime.executionMemory.input");
|
|
1376
|
-
err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
|
|
1377
|
-
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1378
|
-
const result = {
|
|
1379
|
-
jobId,
|
|
1380
|
-
taskId: graphTaskId,
|
|
1381
|
-
graphId: resolvedGraphId,
|
|
1382
|
-
status: "failed",
|
|
1383
|
-
outputsByNodeId,
|
|
1384
|
-
stepsResponses,
|
|
1385
|
-
engineSnapshot: engine.snapshot(),
|
|
1386
|
-
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1387
|
-
errors: [...errors, { error: err }],
|
|
1388
|
-
graphAudit,
|
|
1389
|
-
...finalizeGraphPayload("failed"),
|
|
1177
|
+
const graphAudit = {
|
|
1178
|
+
source: 'model',
|
|
1179
|
+
contentSha256: computeGraphDocumentContentSha256(suppliedGraph),
|
|
1390
1180
|
};
|
|
1391
|
-
const
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
}));
|
|
1399
|
-
}
|
|
1400
|
-
return result;
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
while (true) {
|
|
1404
|
-
const plan = engine.plan();
|
|
1405
|
-
if (plan.status === "completed") {
|
|
1406
|
-
const graphStatus = errors.length ? "failed" : "completed";
|
|
1407
|
-
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1408
|
-
const result = {
|
|
1409
|
-
jobId,
|
|
1410
|
-
taskId: graphTaskId,
|
|
1181
|
+
const graphDocumentModel = mergeGraphDocumentModel(graph);
|
|
1182
|
+
const graphExecution = graphDocumentModel.graphExecution;
|
|
1183
|
+
const nodeResponseKeys = resolveNodeResponseKeys(graphExecution);
|
|
1184
|
+
const coreObjectiveConfig = resolveCoreObjectiveConfig(graphExecution);
|
|
1185
|
+
setRuntimeObjectsLastJobId(merged.runtimeObjects, jobId);
|
|
1186
|
+
ensureGraphEngineLogxerOnRuntimeObjects(merged.runtimeObjects, runLogxer);
|
|
1187
|
+
runLogxer.info("Graph run started", {
|
|
1411
1188
|
graphId: resolvedGraphId,
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1189
|
+
debugKind: DebugLogAbstract.EVENT,
|
|
1190
|
+
});
|
|
1191
|
+
const { finalizer } = validateGraphFinalizer(graph);
|
|
1192
|
+
const engine = opts.engineFactory.create({
|
|
1193
|
+
graph,
|
|
1194
|
+
mode: runtime.mode,
|
|
1195
|
+
goalNodeId: runtime.goalNodeId,
|
|
1196
|
+
dimension: runtime.dimension,
|
|
1197
|
+
initialState: runtime.initialState,
|
|
1198
|
+
initialVariables: runtime.initialVariables,
|
|
1199
|
+
});
|
|
1200
|
+
const outputsByNodeId = {};
|
|
1201
|
+
const stepsResponses = [];
|
|
1202
|
+
const errors = [];
|
|
1203
|
+
const finalizerNodeId = String(finalizer.id);
|
|
1204
|
+
const jobCorrelation = job?.jobType != null ? { jobType: job.jobType } : undefined;
|
|
1205
|
+
// Pre-compute incoming-edge map for conditional-edge filtering of plan.nextNodes.
|
|
1206
|
+
const rawEdges = Array.isArray(graph.edges) ? graph.edges : [];
|
|
1207
|
+
const hasConditionalEdges = rawEdges.some((e) => e && typeof e === "object" && e.when != null);
|
|
1208
|
+
const incomingByTo = new Map();
|
|
1209
|
+
if (hasConditionalEdges) {
|
|
1210
|
+
for (const e of rawEdges) {
|
|
1211
|
+
if (!e || typeof e !== "object")
|
|
1212
|
+
continue;
|
|
1213
|
+
const to = e.to;
|
|
1214
|
+
if (typeof to !== "string" || to.length === 0)
|
|
1215
|
+
continue;
|
|
1216
|
+
const list = incomingByTo.get(to) ?? [];
|
|
1217
|
+
list.push(e);
|
|
1218
|
+
incomingByTo.set(to, list);
|
|
1433
1219
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1220
|
+
}
|
|
1221
|
+
const graphRunStartedAt = Date.now();
|
|
1222
|
+
const taskRunLogBuffer = [];
|
|
1223
|
+
let logxerCorrelationIdLast;
|
|
1224
|
+
function appendTaskRunLogFromError(e) {
|
|
1225
|
+
if (Array.isArray(e?.taskRunLog))
|
|
1226
|
+
taskRunLogBuffer.push(...e.taskRunLog);
|
|
1227
|
+
if (typeof e?.logxerCorrelationId === "string" && e.logxerCorrelationId.length > 0) {
|
|
1228
|
+
logxerCorrelationIdLast = e.logxerCorrelationId;
|
|
1439
1229
|
}
|
|
1440
1230
|
}
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
|
|
1465
|
-
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1466
|
-
...(jobCorrelation ?? {}),
|
|
1467
|
-
}));
|
|
1231
|
+
function finalizeGraphPayload(graphStatus) {
|
|
1232
|
+
const lim = resolveRunLogLimits({
|
|
1233
|
+
runLogMode: merged.runLogMode,
|
|
1234
|
+
maxRunLogEntries: merged.maxRunLogEntries,
|
|
1235
|
+
maxRunLogDataJsonChars: merged.maxRunLogDataJsonChars,
|
|
1236
|
+
defaults: {},
|
|
1237
|
+
});
|
|
1238
|
+
const built = buildRunLog({
|
|
1239
|
+
...lim,
|
|
1240
|
+
jobId,
|
|
1241
|
+
taskId: graphTaskId,
|
|
1242
|
+
graphId: resolvedGraphId,
|
|
1243
|
+
graphRunStartedAt,
|
|
1244
|
+
graphStatus,
|
|
1245
|
+
execution: currentExecution,
|
|
1246
|
+
taskAppendedEntries: taskRunLogBuffer,
|
|
1247
|
+
logxerCorrelationId: logxerCorrelationIdLast,
|
|
1248
|
+
});
|
|
1249
|
+
return {
|
|
1250
|
+
execution: currentExecution,
|
|
1251
|
+
outputsMemory: currentOutputsMemory,
|
|
1252
|
+
...built,
|
|
1253
|
+
};
|
|
1468
1254
|
}
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1255
|
+
// Track current memory and execution state (will be updated as nodes execute)
|
|
1256
|
+
let currentJobMemory = runtime.jobMemory || {};
|
|
1257
|
+
let currentTaskMemory = runtime.taskMemory;
|
|
1258
|
+
let currentExecution = normalizeRuntimeExecutionMemory(runtime);
|
|
1259
|
+
let currentOutputsMemory = normalizeRuntimeOutputsMemory(runtime);
|
|
1260
|
+
if (!currentExecution._trace)
|
|
1261
|
+
currentExecution._trace = { nodes: {} };
|
|
1262
|
+
if (!currentExecution._trace.nodes)
|
|
1263
|
+
currentExecution._trace.nodes = {};
|
|
1264
|
+
const jobKnowledgePatch = await resolveKnowledgePatch({
|
|
1265
|
+
refs: graph.jobKnowledge,
|
|
1266
|
+
resolver: opts.resolveJobKnowledge,
|
|
1267
|
+
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId },
|
|
1268
|
+
});
|
|
1269
|
+
seedGraphRunExecutionState({
|
|
1270
|
+
execution: currentExecution,
|
|
1478
1271
|
jobMemory: currentJobMemory,
|
|
1479
|
-
|
|
1480
|
-
};
|
|
1481
|
-
runnableNodes = runnableNodes.filter((n) => {
|
|
1482
|
-
const id = n?.id;
|
|
1483
|
-
if (typeof id !== "string" || id.length === 0)
|
|
1484
|
-
return true;
|
|
1485
|
-
const incoming = incomingByTo.get(id) ?? [];
|
|
1486
|
-
if (incoming.length === 0)
|
|
1487
|
-
return true;
|
|
1488
|
-
const hasUnconditional = incoming.some((e) => e?.when == null);
|
|
1489
|
-
if (hasUnconditional)
|
|
1490
|
-
return true;
|
|
1491
|
-
const planningContext = buildPredicateEvalContextForNode({
|
|
1492
|
-
executionMemory: currentExecution,
|
|
1493
|
-
jobMemory: currentJobMemory,
|
|
1494
|
-
taskMemory: currentTaskMemory,
|
|
1495
|
-
node: n,
|
|
1496
|
-
runtimeTaskVariables: runtime.taskVariables,
|
|
1497
|
-
});
|
|
1498
|
-
return incoming.some((e) => evaluateGraphPredicate(e.when, planningContext));
|
|
1272
|
+
job: runtime.job,
|
|
1499
1273
|
});
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
const gatedRunnable = [];
|
|
1510
|
-
for (const n of runnableNodes) {
|
|
1511
|
-
if (n?.type === "finalizer") {
|
|
1512
|
-
gatedRunnable.push(n);
|
|
1513
|
-
continue;
|
|
1514
|
-
}
|
|
1515
|
-
const nodePredicateCtx = buildPredicateEvalContextForNode({
|
|
1274
|
+
seedGraphVariableBucketsFromRuntime({
|
|
1275
|
+
execution: currentExecution,
|
|
1276
|
+
graph,
|
|
1277
|
+
runtime,
|
|
1278
|
+
job,
|
|
1279
|
+
});
|
|
1280
|
+
assertFinalizerRequiredReadsResolvable({
|
|
1281
|
+
graph,
|
|
1282
|
+
finalizer,
|
|
1516
1283
|
executionMemory: currentExecution,
|
|
1284
|
+
outputsMemory: currentOutputsMemory,
|
|
1285
|
+
});
|
|
1286
|
+
const coreObjectiveValue = resolveCoreObjectiveValue({
|
|
1287
|
+
sourcePath: coreObjectiveConfig.sourcePath,
|
|
1288
|
+
execution: currentExecution,
|
|
1517
1289
|
jobMemory: currentJobMemory,
|
|
1518
1290
|
taskMemory: currentTaskMemory,
|
|
1519
|
-
|
|
1520
|
-
runtimeTaskVariables: runtime.taskVariables,
|
|
1291
|
+
job,
|
|
1521
1292
|
});
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1293
|
+
if (eventEmitter) {
|
|
1294
|
+
eventEmitter.emit(createGraphStartEvent(jobId, resolvedGraphId, graphTaskId, {
|
|
1295
|
+
agentId: job?.agentId,
|
|
1296
|
+
input: {
|
|
1297
|
+
variables: runtime.variables,
|
|
1298
|
+
jobMemory: currentJobMemory,
|
|
1299
|
+
taskMemory: currentTaskMemory,
|
|
1300
|
+
},
|
|
1301
|
+
...(jobCorrelation ?? {}),
|
|
1302
|
+
}));
|
|
1529
1303
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
const concurrency = resolveConcurrencyLimit({ graph, runnableNodes });
|
|
1540
|
-
// Execute the current runnable batch in parallel (bounded)
|
|
1541
|
-
// Important: we only call plan() again AFTER all commits from this batch land.
|
|
1542
|
-
let batchHadFailFastError = false;
|
|
1543
|
-
await runPool(runnableNodes, concurrency, async (node) => {
|
|
1544
|
-
// If failFast was triggered, don't schedule more work (runPool also stops scheduling)
|
|
1545
|
-
if (batchHadFailFastError)
|
|
1546
|
-
return;
|
|
1547
|
-
const executionBaseForNode = cloneJsonLike(currentExecution);
|
|
1548
|
-
const executionForNode = cloneJsonLike(executionBaseForNode);
|
|
1549
|
-
const outputsBaseForNode = cloneJsonLike(currentOutputsMemory);
|
|
1550
|
-
const outputsForNode = cloneJsonLike(outputsBaseForNode);
|
|
1551
|
-
// TRACE: Log execution before passing to node
|
|
1552
|
-
if (process.env.DEBUG_EXECUTION_MEMORY === 'true') {
|
|
1553
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: About to execute node ${node.id}`);
|
|
1554
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: executionForNode =`, JSON.stringify(executionForNode, null, 2));
|
|
1555
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: executionForNode type =`, typeof executionForNode);
|
|
1556
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: executionForNode is undefined?`, executionForNode === undefined);
|
|
1557
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: executionForNode is null?`, executionForNode === null);
|
|
1558
|
-
if (executionForNode && typeof executionForNode === 'object') {
|
|
1559
|
-
console.log(`[TRACE] ExellixGraphRuntime.executeGraph: executionForNode keys =`, Object.keys(executionForNode));
|
|
1304
|
+
function buildDebugTrace() {
|
|
1305
|
+
if (!debugMode)
|
|
1306
|
+
return undefined;
|
|
1307
|
+
const traceNodes = (currentExecution?._trace?.nodes ?? {});
|
|
1308
|
+
const out = [];
|
|
1309
|
+
for (const [nodeId, entry] of Object.entries(traceNodes)) {
|
|
1310
|
+
if (entry == null || typeof entry !== "object")
|
|
1311
|
+
continue;
|
|
1312
|
+
out.push({ nodeId, ...entry });
|
|
1560
1313
|
}
|
|
1314
|
+
// Stable order by startedAt, then by nodeId.
|
|
1315
|
+
out.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0) || a.nodeId.localeCompare(b.nodeId));
|
|
1316
|
+
return { nodes: out };
|
|
1561
1317
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
|
|
1318
|
+
function appendStepResponse(node, output) {
|
|
1319
|
+
if (node?.type === "finalizer")
|
|
1320
|
+
return;
|
|
1321
|
+
const nodeResponse = unwrapNodeResponse(output);
|
|
1322
|
+
stepsResponses.push({
|
|
1323
|
+
[coreObjectiveConfig.propertyName]: coreObjectiveValue,
|
|
1324
|
+
[nodeResponseKeys.singular]: nodeResponse,
|
|
1570
1325
|
});
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
const
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
graphId: resolvedGraphId,
|
|
1588
|
-
nodeId: typeof node.id === "string" ? node.id : String(node.id),
|
|
1589
|
-
jobId,
|
|
1326
|
+
}
|
|
1327
|
+
function buildFinalOutputFromGraphResponse() {
|
|
1328
|
+
const executionMemoryForResponse = isPlainRecord(currentExecution)
|
|
1329
|
+
? structuredClone(currentExecution)
|
|
1330
|
+
: currentExecution;
|
|
1331
|
+
return applyGraphResponseDefinition({
|
|
1332
|
+
response: graph.response,
|
|
1333
|
+
context: {
|
|
1334
|
+
graph,
|
|
1335
|
+
executionMemory: executionMemoryForResponse,
|
|
1336
|
+
outputsMemory: isPlainRecord(currentOutputsMemory)
|
|
1337
|
+
? structuredClone(currentOutputsMemory)
|
|
1338
|
+
: currentOutputsMemory,
|
|
1339
|
+
outputsByNodeId,
|
|
1340
|
+
stepsResponses,
|
|
1341
|
+
},
|
|
1590
1342
|
});
|
|
1591
|
-
|
|
1343
|
+
}
|
|
1344
|
+
const playgroundReporter = runtime.playgroundReporter ?? opts.playgroundReporter;
|
|
1345
|
+
if (playgroundReporter) {
|
|
1346
|
+
playgroundReporter.step("graph:start", {
|
|
1347
|
+
jobId,
|
|
1348
|
+
taskId: graphTaskId,
|
|
1592
1349
|
graphId: resolvedGraphId,
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
execution: executionForNode,
|
|
1602
|
-
outputsMemory: outputsForNode,
|
|
1603
|
-
variables: runtime.variables,
|
|
1604
|
-
taskVariables: runtime.taskVariables,
|
|
1605
|
-
modelConfig: effectiveModelConfig,
|
|
1606
|
-
llmCall: merged.llmCall,
|
|
1607
|
-
runTaskIdentity: merged.runTaskIdentity,
|
|
1608
|
-
runTaskExecutionMode: merged.runTaskExecutionMode,
|
|
1609
|
-
runTaskDiagnostics: merged.runTaskDiagnostics,
|
|
1610
|
-
graphExecutionPipeline: merged.executionPipeline,
|
|
1611
|
-
skillKeyResolution: merged.skillKeyResolution,
|
|
1612
|
-
nodeTimeoutMs: merged.nodeTimeoutMs,
|
|
1613
|
-
clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
|
|
1614
|
-
stepRetryPolicy: merged.stepRetryPolicy,
|
|
1615
|
-
mainReadinessPolicy: merged.mainReadinessPolicy,
|
|
1616
|
-
eventEmitter,
|
|
1617
|
-
jobCorrelation,
|
|
1350
|
+
executionKeys: Object.keys(currentExecution),
|
|
1351
|
+
variablesKeys: runtime.variables != null ? Object.keys(runtime.variables) : [],
|
|
1352
|
+
executionExcerpt: typeof currentExecution === "object" && currentExecution !== null
|
|
1353
|
+
? JSON.stringify(currentExecution, null, 2).slice(0, 500)
|
|
1354
|
+
: undefined,
|
|
1355
|
+
...(merged.playgroundMeta != null && typeof merged.playgroundMeta === "object"
|
|
1356
|
+
? merged.playgroundMeta
|
|
1357
|
+
: {}),
|
|
1618
1358
|
});
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1359
|
+
if (merged.runtimeObjects) {
|
|
1360
|
+
playgroundReporter.step("graph:observability", summarizeRuntimeObjectsForPlayground(merged.runtimeObjects));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
// TRACE: Log initial execution state
|
|
1364
|
+
traceExecutionMemory('executeGraph', 'Initial execution state', {
|
|
1365
|
+
runtimeExecutionMemory: runtime.executionMemory,
|
|
1366
|
+
currentExecution,
|
|
1367
|
+
currentExecutionType: typeof currentExecution,
|
|
1368
|
+
currentExecutionIsUndefined: currentExecution === undefined,
|
|
1369
|
+
currentExecutionIsNull: currentExecution === null,
|
|
1370
|
+
});
|
|
1371
|
+
function recordForStructuredDataFilters() {
|
|
1372
|
+
return isPlainRecord(currentExecution?.input)
|
|
1373
|
+
? currentExecution.input
|
|
1374
|
+
: {};
|
|
1375
|
+
}
|
|
1376
|
+
const graphEntryForGate = graphDocumentModel.graphEntry;
|
|
1377
|
+
if (graphEntryForGate?.dataFilters !== undefined) {
|
|
1378
|
+
const entryEv = evaluateStructuredDataFilters(graphEntryForGate.dataFilters, recordForStructuredDataFilters());
|
|
1379
|
+
if (entryEv.status === "unsupported_shape" ||
|
|
1380
|
+
(entryEv.status === "evaluated" && !entryEv.ok)) {
|
|
1381
|
+
const err = new Error("GRAPH_ENTRY_DATA_FILTERS_REJECTED: metadata.graphEntry.dataFilters (structured) did not accept runtime.executionMemory.input");
|
|
1382
|
+
err.code = ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED;
|
|
1383
|
+
logGraphEngineErrorCode(ExellixGraphErrorCode.GRAPH_ENTRY_DATA_FILTERS_REJECTED, err.message, {
|
|
1384
|
+
graphId: resolvedGraphId,
|
|
1385
|
+
jobId,
|
|
1386
|
+
taskId: graphTaskId,
|
|
1387
|
+
error: err,
|
|
1388
|
+
});
|
|
1389
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1390
|
+
const result = {
|
|
1391
|
+
jobId,
|
|
1392
|
+
taskId: graphTaskId,
|
|
1393
|
+
graphId: resolvedGraphId,
|
|
1394
|
+
status: "failed",
|
|
1395
|
+
outputsByNodeId,
|
|
1396
|
+
stepsResponses,
|
|
1397
|
+
engineSnapshot: engine.snapshot(),
|
|
1398
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1399
|
+
errors: [...errors, { error: err }],
|
|
1400
|
+
graphAudit,
|
|
1401
|
+
...finalizeGraphPayload("failed"),
|
|
1402
|
+
};
|
|
1403
|
+
const debug = buildDebugTrace();
|
|
1404
|
+
if (debug)
|
|
1405
|
+
result.debug = debug;
|
|
1406
|
+
if (eventEmitter) {
|
|
1407
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
|
|
1408
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1409
|
+
...(jobCorrelation ?? {}),
|
|
1410
|
+
}));
|
|
1637
1411
|
}
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1412
|
+
return result;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
while (true) {
|
|
1416
|
+
const plan = engine.plan();
|
|
1417
|
+
if (plan.status === "completed") {
|
|
1418
|
+
const graphStatus = errors.length ? "failed" : "completed";
|
|
1419
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1420
|
+
const result = {
|
|
1421
|
+
jobId,
|
|
1422
|
+
taskId: graphTaskId,
|
|
1423
|
+
graphId: resolvedGraphId,
|
|
1424
|
+
status: graphStatus,
|
|
1425
|
+
outputsByNodeId,
|
|
1426
|
+
stepsResponses,
|
|
1427
|
+
engineSnapshot: engine.snapshot(),
|
|
1428
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1429
|
+
...(errors.length === 0 ? { finalizerNodeId, finalizerType: finalizer.finalizerType } : {}),
|
|
1430
|
+
errors: errors.length ? errors : undefined,
|
|
1431
|
+
graphAudit,
|
|
1432
|
+
...finalizeGraphPayload(graphStatus),
|
|
1433
|
+
};
|
|
1434
|
+
const debug = buildDebugTrace();
|
|
1435
|
+
if (debug)
|
|
1436
|
+
result.debug = debug;
|
|
1437
|
+
if (eventEmitter) {
|
|
1438
|
+
if (graphStatus === "completed") {
|
|
1439
|
+
eventEmitter.emit(createGraphCompleteEvent(jobId, resolvedGraphId, graphTaskId, {
|
|
1440
|
+
output: finalOutput,
|
|
1441
|
+
nodesExecuted: Object.keys(outputsByNodeId).length,
|
|
1442
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1443
|
+
...(jobCorrelation ?? {}),
|
|
1444
|
+
}));
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, errors[0]?.error, {
|
|
1448
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1449
|
+
...(jobCorrelation ?? {}),
|
|
1450
|
+
}));
|
|
1451
|
+
}
|
|
1642
1452
|
}
|
|
1453
|
+
return result;
|
|
1643
1454
|
}
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1455
|
+
if (plan.status !== "continue") {
|
|
1456
|
+
const err = new Error(`GRAPH_BLOCKED: status=${plan.status}`);
|
|
1457
|
+
err.code = "GRAPH_BLOCKED";
|
|
1458
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1459
|
+
const result = {
|
|
1460
|
+
jobId,
|
|
1461
|
+
taskId: graphTaskId,
|
|
1462
|
+
graphId: resolvedGraphId,
|
|
1463
|
+
status: "failed",
|
|
1464
|
+
outputsByNodeId,
|
|
1465
|
+
stepsResponses,
|
|
1466
|
+
engineSnapshot: engine.snapshot(),
|
|
1467
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1468
|
+
errors: [...errors, { error: err }],
|
|
1469
|
+
graphAudit,
|
|
1470
|
+
...finalizeGraphPayload("failed"),
|
|
1471
|
+
};
|
|
1472
|
+
const debug = buildDebugTrace();
|
|
1473
|
+
if (debug)
|
|
1474
|
+
result.debug = debug;
|
|
1475
|
+
if (eventEmitter) {
|
|
1476
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, err, {
|
|
1477
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1478
|
+
...(jobCorrelation ?? {}),
|
|
1479
|
+
}));
|
|
1650
1480
|
}
|
|
1481
|
+
return result;
|
|
1651
1482
|
}
|
|
1652
|
-
|
|
1653
|
-
|
|
1483
|
+
let runnableNodes = plan.nextNodes ?? [];
|
|
1484
|
+
// Conditional edge filtering: a node with incoming edges that are *all* conditional must
|
|
1485
|
+
// have at least one incoming edge whose `when` evaluates true to be runnable this round.
|
|
1486
|
+
// Roots and nodes with at least one unconditional incoming edge always pass through.
|
|
1487
|
+
if (hasConditionalEdges && runnableNodes.length > 0) {
|
|
1488
|
+
const planningContextBase = {
|
|
1489
|
+
executionMemory: currentExecution,
|
|
1490
|
+
jobMemory: currentJobMemory,
|
|
1491
|
+
taskMemory: currentTaskMemory,
|
|
1492
|
+
};
|
|
1493
|
+
runnableNodes = runnableNodes.filter((n) => {
|
|
1494
|
+
const id = n?.id;
|
|
1495
|
+
if (typeof id !== "string" || id.length === 0)
|
|
1496
|
+
return true;
|
|
1497
|
+
const incoming = incomingByTo.get(id) ?? [];
|
|
1498
|
+
if (incoming.length === 0)
|
|
1499
|
+
return true;
|
|
1500
|
+
const hasUnconditional = incoming.some((e) => e?.when == null);
|
|
1501
|
+
if (hasUnconditional)
|
|
1502
|
+
return true;
|
|
1503
|
+
const planningContext = buildPredicateEvalContextForNode({
|
|
1504
|
+
executionMemory: currentExecution,
|
|
1505
|
+
jobMemory: currentJobMemory,
|
|
1506
|
+
taskMemory: currentTaskMemory,
|
|
1507
|
+
node: n,
|
|
1508
|
+
runtimeTaskVariables: runtime.taskVariables,
|
|
1509
|
+
});
|
|
1510
|
+
return incoming.some((e) => evaluateGraphPredicate(e.when, planningContext));
|
|
1511
|
+
});
|
|
1654
1512
|
}
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1513
|
+
const dataFiltersRecord = recordForStructuredDataFilters();
|
|
1514
|
+
const conditionCtxBase = {
|
|
1515
|
+
executionMemory: currentExecution,
|
|
1516
|
+
jobMemory: currentJobMemory,
|
|
1517
|
+
taskMemory: currentTaskMemory,
|
|
1518
|
+
job,
|
|
1519
|
+
};
|
|
1520
|
+
const skippedByConditions = [];
|
|
1521
|
+
const gatedRunnable = [];
|
|
1522
|
+
for (const n of runnableNodes) {
|
|
1523
|
+
if (n?.type === "finalizer") {
|
|
1524
|
+
gatedRunnable.push(n);
|
|
1525
|
+
continue;
|
|
1526
|
+
}
|
|
1527
|
+
const nodePredicateCtx = buildPredicateEvalContextForNode({
|
|
1528
|
+
executionMemory: currentExecution,
|
|
1529
|
+
jobMemory: currentJobMemory,
|
|
1530
|
+
taskMemory: currentTaskMemory,
|
|
1531
|
+
node: n,
|
|
1532
|
+
runtimeTaskVariables: runtime.taskVariables,
|
|
1533
|
+
});
|
|
1534
|
+
const condEv = await evaluateTaskNodeConditions(n.conditions, { ...conditionCtxBase, ...nodePredicateCtx }, dataFiltersRecord, { runx: runxClient });
|
|
1535
|
+
if (!condEv.ok) {
|
|
1536
|
+
skippedByConditions.push({
|
|
1537
|
+
node: n,
|
|
1538
|
+
skipReason: condEv.skipReason ?? "condition_eval_error",
|
|
1539
|
+
});
|
|
1540
|
+
continue;
|
|
1541
|
+
}
|
|
1542
|
+
gatedRunnable.push(n);
|
|
1668
1543
|
}
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1544
|
+
for (const { node, skipReason } of skippedByConditions) {
|
|
1545
|
+
engine.commit({
|
|
1546
|
+
nodeId: String(node.id),
|
|
1547
|
+
output: { ...skippedByConditionsOutput(skipReason) },
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
runnableNodes = gatedRunnable;
|
|
1551
|
+
const concurrency = resolveConcurrencyLimit({ graph, runnableNodes });
|
|
1552
|
+
// Execute the current runnable batch in parallel (bounded)
|
|
1553
|
+
// Important: we only call plan() again AFTER all commits from this batch land.
|
|
1554
|
+
let batchHadFailFastError = false;
|
|
1555
|
+
await runPool(runnableNodes, concurrency, async (node) => {
|
|
1556
|
+
// If failFast was triggered, don't schedule more work (runPool also stops scheduling)
|
|
1557
|
+
if (batchHadFailFastError)
|
|
1558
|
+
return;
|
|
1559
|
+
const executionBaseForNode = cloneJsonLike(currentExecution);
|
|
1560
|
+
const executionForNode = cloneJsonLike(executionBaseForNode);
|
|
1561
|
+
const outputsBaseForNode = cloneJsonLike(currentOutputsMemory);
|
|
1562
|
+
const outputsForNode = cloneJsonLike(outputsBaseForNode);
|
|
1563
|
+
// TRACE: Log execution before passing to node
|
|
1564
|
+
traceExecutionMemory('executeGraph', 'About to execute node', {
|
|
1565
|
+
nodeId: node.id,
|
|
1566
|
+
executionForNode,
|
|
1567
|
+
executionForNodeType: typeof executionForNode,
|
|
1568
|
+
executionForNodeIsUndefined: executionForNode === undefined,
|
|
1569
|
+
executionForNodeIsNull: executionForNode === null,
|
|
1570
|
+
executionForNodeKeys: executionForNode && typeof executionForNode === 'object'
|
|
1571
|
+
? Object.keys(executionForNode)
|
|
1572
|
+
: undefined,
|
|
1573
|
+
});
|
|
1574
|
+
try {
|
|
1575
|
+
const isTaskNodeForKnowledge = node?.type !== "finalizer";
|
|
1576
|
+
const taskKnowledgePatch = await resolveKnowledgePatch({
|
|
1577
|
+
refs: isTaskNodeForKnowledge
|
|
1578
|
+
? (Array.isArray(node.taskKnowledge) ? node.taskKnowledge : [])
|
|
1579
|
+
: undefined,
|
|
1580
|
+
resolver: opts.resolveTaskKnowledge,
|
|
1581
|
+
context: { model: graph, runtime, graphId: resolvedGraphId, jobId, taskId: graphTaskId, node: node },
|
|
1582
|
+
});
|
|
1583
|
+
const nodeTaskMemory = isPlainRecord(currentTaskMemory) ? { ...currentTaskMemory } : currentTaskMemory;
|
|
1584
|
+
const runtimeNodeConfig = readRuntimeNodeConfig(merged.nodes, typeof node.id === "string" ? node.id : undefined);
|
|
1585
|
+
const effectiveModelConfig = await resolveModelConfigForNode({
|
|
1586
|
+
runtimeNodeConfig,
|
|
1587
|
+
nodeModelConfig: node.taskConfiguration?.modelConfig,
|
|
1588
|
+
runtimeModelConfig: merged.modelConfig,
|
|
1589
|
+
graphModelConfig: graph.modelConfig,
|
|
1590
|
+
conditionCtx: buildPredicateEvalContextForNode({
|
|
1591
|
+
executionMemory: currentExecution,
|
|
1592
|
+
jobMemory: currentJobMemory,
|
|
1593
|
+
taskMemory: currentTaskMemory,
|
|
1594
|
+
node: node,
|
|
1595
|
+
runtimeTaskVariables: runtime.taskVariables,
|
|
1596
|
+
}),
|
|
1597
|
+
executionInput: dataFiltersRecord,
|
|
1598
|
+
runx: runxClient,
|
|
1599
|
+
graphId: resolvedGraphId,
|
|
1600
|
+
nodeId: typeof node.id === "string" ? node.id : String(node.id),
|
|
1601
|
+
jobId,
|
|
1602
|
+
});
|
|
1603
|
+
const r = await executeNode({
|
|
1604
|
+
graphId: resolvedGraphId,
|
|
1605
|
+
graph,
|
|
1606
|
+
graphRunTaskId: graphTaskId,
|
|
1607
|
+
node,
|
|
1608
|
+
job,
|
|
1609
|
+
jobMemory: currentJobMemory,
|
|
1610
|
+
taskMemory: nodeTaskMemory,
|
|
1611
|
+
jobKnowledgePatch,
|
|
1612
|
+
taskKnowledgePatch,
|
|
1613
|
+
execution: executionForNode,
|
|
1614
|
+
outputsMemory: outputsForNode,
|
|
1615
|
+
variables: runtime.variables,
|
|
1616
|
+
taskVariables: runtime.taskVariables,
|
|
1617
|
+
modelConfig: effectiveModelConfig,
|
|
1618
|
+
llmCall: merged.llmCall,
|
|
1619
|
+
runTaskIdentity: merged.runTaskIdentity,
|
|
1620
|
+
runTaskExecutionMode: merged.runTaskExecutionMode,
|
|
1621
|
+
runTaskDiagnostics: merged.runTaskDiagnostics,
|
|
1622
|
+
graphExecutionPipeline: merged.executionPipeline,
|
|
1623
|
+
skillKeyResolution: merged.skillKeyResolution,
|
|
1624
|
+
nodeTimeoutMs: merged.nodeTimeoutMs,
|
|
1625
|
+
clearSynthesizedContextPerNode: merged.clearSynthesizedContextPerNode,
|
|
1626
|
+
stepRetryPolicy: merged.stepRetryPolicy,
|
|
1627
|
+
mainReadinessPolicy: merged.mainReadinessPolicy,
|
|
1628
|
+
eventEmitter,
|
|
1629
|
+
jobCorrelation,
|
|
1630
|
+
});
|
|
1631
|
+
outputsByNodeId[r.nodeId] = r.output;
|
|
1632
|
+
appendStepResponse(node, r.output);
|
|
1633
|
+
engine.commit({ nodeId: r.nodeId, output: r.output });
|
|
1634
|
+
const trl = r.taskRunLog;
|
|
1635
|
+
if (trl?.length)
|
|
1636
|
+
taskRunLogBuffer.push(...trl);
|
|
1637
|
+
const lcid = r.logxerCorrelationId;
|
|
1638
|
+
if (typeof lcid === "string" && lcid.length > 0)
|
|
1639
|
+
logxerCorrelationIdLast = lcid;
|
|
1640
|
+
// Update execution object from node result if available.
|
|
1641
|
+
// Task nodes write execution state through executionMapping.
|
|
1642
|
+
if (r.execution !== undefined) {
|
|
1643
|
+
traceExecutionMemory('executeGraph', 'Node returned execution object', {
|
|
1644
|
+
nodeId: node.id,
|
|
1645
|
+
execution: r.execution,
|
|
1646
|
+
executionType: typeof r.execution,
|
|
1647
|
+
executionKeys: r.execution && typeof r.execution === 'object'
|
|
1648
|
+
? Object.keys(r.execution)
|
|
1649
|
+
: undefined,
|
|
1650
|
+
});
|
|
1651
|
+
currentExecution = mergeExecutionUpdate(currentExecution, r.execution, executionBaseForNode);
|
|
1652
|
+
traceExecutionMemory('executeGraph', 'Updated currentExecution after node', {
|
|
1653
|
+
nodeId: node.id,
|
|
1654
|
+
currentExecution,
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
else {
|
|
1658
|
+
traceExecutionMemory('executeGraph', 'Node did not return execution object', {
|
|
1659
|
+
nodeId: node.id,
|
|
1660
|
+
execution: r.execution,
|
|
1661
|
+
currentExecution,
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
if (r.outputsMemory !== undefined) {
|
|
1665
|
+
currentOutputsMemory = mergeExecutionUpdate(currentOutputsMemory, r.outputsMemory, outputsBaseForNode);
|
|
1666
|
+
}
|
|
1667
|
+
// Note: We don't modify jobMemory or taskMemory here
|
|
1668
|
+
// They are set before the job starts and we just pass them through
|
|
1669
|
+
}
|
|
1670
|
+
catch (e) {
|
|
1671
|
+
appendTaskRunLogFromError(e);
|
|
1672
|
+
errors.push({ nodeId: node?.id, error: e });
|
|
1673
|
+
engine.commit({ nodeId: node?.id, error: e });
|
|
1674
|
+
if (eventEmitter && !wasNodeLifecycleEmitted(e)) {
|
|
1675
|
+
eventEmitter.emit(createNodeFailEvent(jobId, resolvedGraphId, graphTaskId, node, String(e?.skillKey ?? ""), {
|
|
1676
|
+
error: { code: e?.code, message: e?.message, stack: e?.stack },
|
|
1677
|
+
memoryAfter: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1678
|
+
response: undefined,
|
|
1679
|
+
}, jobCorrelation));
|
|
1680
|
+
}
|
|
1681
|
+
if (failFast) {
|
|
1682
|
+
batchHadFailFastError = true;
|
|
1683
|
+
// Throw to stop scheduling new nodes in runPool
|
|
1684
|
+
throw e;
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}, { failFast }).catch((e) => {
|
|
1688
|
+
// failFast path: we intentionally end early after stopping scheduling
|
|
1689
|
+
// (in-flight nodes may still finish; JS won't cancel them).
|
|
1690
|
+
if (!failFast)
|
|
1691
|
+
throw e;
|
|
1692
|
+
});
|
|
1693
|
+
if (failFast && errors.length) {
|
|
1694
|
+
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1695
|
+
const result = {
|
|
1696
|
+
jobId,
|
|
1697
|
+
taskId: graphTaskId,
|
|
1698
|
+
graphId: resolvedGraphId,
|
|
1699
|
+
status: "failed",
|
|
1700
|
+
outputsByNodeId,
|
|
1701
|
+
stepsResponses,
|
|
1702
|
+
engineSnapshot: engine.snapshot(),
|
|
1703
|
+
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1704
|
+
errors,
|
|
1705
|
+
graphAudit,
|
|
1706
|
+
...finalizeGraphPayload("failed"),
|
|
1707
|
+
};
|
|
1708
|
+
const debug = buildDebugTrace();
|
|
1709
|
+
if (debug)
|
|
1710
|
+
result.debug = debug;
|
|
1711
|
+
if (eventEmitter) {
|
|
1712
|
+
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, errors[0]?.error, {
|
|
1713
|
+
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1714
|
+
...(jobCorrelation ?? {}),
|
|
1715
|
+
}));
|
|
1716
|
+
}
|
|
1717
|
+
return result;
|
|
1673
1718
|
}
|
|
1674
1719
|
}
|
|
1675
|
-
}, { failFast }).catch((e) => {
|
|
1676
|
-
// failFast path: we intentionally end early after stopping scheduling
|
|
1677
|
-
// (in-flight nodes may still finish; JS won't cancel them).
|
|
1678
|
-
if (!failFast)
|
|
1679
|
-
throw e;
|
|
1680
|
-
});
|
|
1681
|
-
if (failFast && errors.length) {
|
|
1682
|
-
const finalOutput = buildFinalOutputFromGraphResponse();
|
|
1683
|
-
const result = {
|
|
1684
|
-
jobId,
|
|
1685
|
-
taskId: graphTaskId,
|
|
1686
|
-
graphId: resolvedGraphId,
|
|
1687
|
-
status: "failed",
|
|
1688
|
-
outputsByNodeId,
|
|
1689
|
-
stepsResponses,
|
|
1690
|
-
engineSnapshot: engine.snapshot(),
|
|
1691
|
-
...(finalOutput !== undefined ? { finalOutput } : {}),
|
|
1692
|
-
errors,
|
|
1693
|
-
graphAudit,
|
|
1694
|
-
...finalizeGraphPayload("failed"),
|
|
1695
|
-
};
|
|
1696
|
-
const debug = buildDebugTrace();
|
|
1697
|
-
if (debug)
|
|
1698
|
-
result.debug = debug;
|
|
1699
|
-
if (eventEmitter) {
|
|
1700
|
-
eventEmitter.emit(createGraphFailEvent(jobId, resolvedGraphId, graphTaskId, errors[0]?.error, {
|
|
1701
|
-
finalMemory: { jobMemory: currentJobMemory, taskMemory: currentTaskMemory, execution: currentExecution, outputsMemory: currentOutputsMemory },
|
|
1702
|
-
...(jobCorrelation ?? {}),
|
|
1703
|
-
}));
|
|
1704
|
-
}
|
|
1705
|
-
return result;
|
|
1706
1720
|
}
|
|
1707
|
-
|
|
1721
|
+
finally {
|
|
1722
|
+
clearGraphEngineRunLogxer();
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1708
1725
|
}
|
|
1709
1726
|
return {
|
|
1710
1727
|
executeGraph,
|