@workglow/task-graph 0.2.23 → 0.2.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +1957 -1622
- package/dist/browser.js.map +38 -23
- package/dist/bun.js +1958 -1623
- package/dist/bun.js.map +38 -23
- package/dist/common.d.ts +14 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/node.js +1958 -1623
- package/dist/node.js.map +38 -23
- package/dist/task/CacheCoordinator.d.ts +48 -0
- package/dist/task/CacheCoordinator.d.ts.map +1 -0
- package/dist/task/FallbackTask.d.ts +1 -0
- package/dist/task/FallbackTask.d.ts.map +1 -1
- package/dist/task/FallbackTaskRunner.d.ts +3 -2
- package/dist/task/FallbackTaskRunner.d.ts.map +1 -1
- package/dist/task/GraphAsTask.d.ts +1 -0
- package/dist/task/GraphAsTask.d.ts.map +1 -1
- package/dist/task/GraphAsTaskRunner.d.ts +4 -3
- package/dist/task/GraphAsTaskRunner.d.ts.map +1 -1
- package/dist/task/ITask.d.ts +19 -3
- package/dist/task/ITask.d.ts.map +1 -1
- package/dist/task/IteratorTaskRunner.d.ts +3 -2
- package/dist/task/IteratorTaskRunner.d.ts.map +1 -1
- package/dist/task/JobQueueFactory.d.ts +1 -0
- package/dist/task/JobQueueFactory.d.ts.map +1 -1
- package/dist/task/MapTask.d.ts +1 -0
- package/dist/task/MapTask.d.ts.map +1 -1
- package/dist/task/ReduceTask.d.ts +1 -0
- package/dist/task/ReduceTask.d.ts.map +1 -1
- package/dist/task/StreamProcessor.d.ts +38 -0
- package/dist/task/StreamProcessor.d.ts.map +1 -0
- package/dist/task/TaskRunContext.d.ts +33 -0
- package/dist/task/TaskRunContext.d.ts.map +1 -0
- package/dist/task/TaskRunner.d.ts +31 -47
- package/dist/task/TaskRunner.d.ts.map +1 -1
- package/dist/task/WhileTask.d.ts +1 -0
- package/dist/task/WhileTask.d.ts.map +1 -1
- package/dist/task/WhileTaskRunner.d.ts +3 -2
- package/dist/task/WhileTaskRunner.d.ts.map +1 -1
- package/dist/task-graph/Dataflow.d.ts +1 -1
- package/dist/task-graph/EdgeMaterializer.d.ts +88 -0
- package/dist/task-graph/EdgeMaterializer.d.ts.map +1 -0
- package/dist/task-graph/GraphSchemaUtils.d.ts +19 -0
- package/dist/task-graph/GraphSchemaUtils.d.ts.map +1 -1
- package/dist/task-graph/IWorkflow.d.ts +11 -1
- package/dist/task-graph/IWorkflow.d.ts.map +1 -1
- package/dist/task-graph/LoopBuilderContext.d.ts +60 -0
- package/dist/task-graph/LoopBuilderContext.d.ts.map +1 -0
- package/dist/task-graph/RunContext.d.ts +39 -0
- package/dist/task-graph/RunContext.d.ts.map +1 -0
- package/dist/task-graph/RunScheduler.d.ts +81 -0
- package/dist/task-graph/RunScheduler.d.ts.map +1 -0
- package/dist/task-graph/StreamPump.d.ts +122 -0
- package/dist/task-graph/StreamPump.d.ts.map +1 -0
- package/dist/task-graph/TaskGraph.d.ts +10 -2
- package/dist/task-graph/TaskGraph.d.ts.map +1 -1
- package/dist/task-graph/TaskGraphRunner.d.ts +31 -167
- package/dist/task-graph/TaskGraphRunner.d.ts.map +1 -1
- package/dist/task-graph/Workflow.d.ts +18 -99
- package/dist/task-graph/Workflow.d.ts.map +1 -1
- package/dist/task-graph/WorkflowBuilder.d.ts +93 -0
- package/dist/task-graph/WorkflowBuilder.d.ts.map +1 -0
- package/dist/task-graph/WorkflowCacheAdapter.d.ts +19 -0
- package/dist/task-graph/WorkflowCacheAdapter.d.ts.map +1 -0
- package/dist/task-graph/WorkflowEventBridge.d.ts +41 -0
- package/dist/task-graph/WorkflowEventBridge.d.ts.map +1 -0
- package/dist/task-graph/WorkflowFactories.d.ts +52 -0
- package/dist/task-graph/WorkflowFactories.d.ts.map +1 -0
- package/dist/task-graph/WorkflowPipe.d.ts +32 -0
- package/dist/task-graph/WorkflowPipe.d.ts.map +1 -0
- package/dist/task-graph/WorkflowRunContext.d.ts +27 -0
- package/dist/task-graph/WorkflowRunContext.d.ts.map +1 -0
- package/dist/task-graph/WorkflowTask.d.ts +19 -0
- package/dist/task-graph/WorkflowTask.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/EXECUTION_MODEL.md +13 -5
package/dist/node.js
CHANGED
|
@@ -1132,26 +1132,159 @@ function addBoundaryNodesToDependencyJson(items, graph) {
|
|
|
1132
1132
|
}
|
|
1133
1133
|
return [...prependItems, ...items, ...appendItems];
|
|
1134
1134
|
}
|
|
1135
|
+
var TYPED_ARRAY_FORMAT_PREFIX = "TypedArray";
|
|
1136
|
+
function schemaHasTypedArrayFormat(schema) {
|
|
1137
|
+
if (typeof schema === "boolean")
|
|
1138
|
+
return false;
|
|
1139
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema))
|
|
1140
|
+
return false;
|
|
1141
|
+
const s = schema;
|
|
1142
|
+
if (typeof s.format === "string" && s.format.startsWith(TYPED_ARRAY_FORMAT_PREFIX)) {
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
const checkUnion = (schemas) => {
|
|
1146
|
+
if (!Array.isArray(schemas))
|
|
1147
|
+
return false;
|
|
1148
|
+
return schemas.some((sub) => schemaHasTypedArrayFormat(sub));
|
|
1149
|
+
};
|
|
1150
|
+
if (checkUnion(s.oneOf) || checkUnion(s.anyOf))
|
|
1151
|
+
return true;
|
|
1152
|
+
const items = s.items;
|
|
1153
|
+
if (items && typeof items === "object" && !Array.isArray(items)) {
|
|
1154
|
+
if (schemaHasTypedArrayFormat(items))
|
|
1155
|
+
return true;
|
|
1156
|
+
}
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
function hasVectorOutput(task) {
|
|
1160
|
+
const outputSchema = task.outputSchema();
|
|
1161
|
+
if (typeof outputSchema === "boolean" || !outputSchema?.properties)
|
|
1162
|
+
return false;
|
|
1163
|
+
return Object.values(outputSchema.properties).some((prop) => schemaHasTypedArrayFormat(prop));
|
|
1164
|
+
}
|
|
1165
|
+
function hasVectorLikeInput(input) {
|
|
1166
|
+
if (!input || typeof input !== "object")
|
|
1167
|
+
return false;
|
|
1168
|
+
const v = input.vectors;
|
|
1169
|
+
return Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && ArrayBuffer.isView(v[0]);
|
|
1170
|
+
}
|
|
1171
|
+
function updateBoundaryTaskSchemas(graph) {
|
|
1172
|
+
const tasks = graph.getTasks();
|
|
1173
|
+
for (const task of tasks) {
|
|
1174
|
+
if (task.type === "InputTask") {
|
|
1175
|
+
const existingConfig = task.config;
|
|
1176
|
+
const existingSchema = existingConfig?.inputSchema ?? existingConfig?.outputSchema;
|
|
1177
|
+
if (existingSchema && typeof existingSchema === "object" && existingSchema["x-ui-manual"] === true) {
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
const outgoing = graph.getTargetDataflows(task.id);
|
|
1181
|
+
if (outgoing.length === 0)
|
|
1182
|
+
continue;
|
|
1183
|
+
const properties = {};
|
|
1184
|
+
const required = [];
|
|
1185
|
+
for (const df of outgoing) {
|
|
1186
|
+
const targetTask = graph.getTask(df.targetTaskId);
|
|
1187
|
+
if (!targetTask)
|
|
1188
|
+
continue;
|
|
1189
|
+
const targetSchema = targetTask.inputSchema();
|
|
1190
|
+
if (typeof targetSchema === "boolean")
|
|
1191
|
+
continue;
|
|
1192
|
+
const prop = targetSchema.properties?.[df.targetTaskPortId];
|
|
1193
|
+
if (prop && typeof prop !== "boolean") {
|
|
1194
|
+
properties[df.sourceTaskPortId] = prop;
|
|
1195
|
+
if (targetSchema.required?.includes(df.targetTaskPortId)) {
|
|
1196
|
+
if (!required.includes(df.sourceTaskPortId)) {
|
|
1197
|
+
required.push(df.sourceTaskPortId);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
const schema = {
|
|
1203
|
+
type: "object",
|
|
1204
|
+
properties,
|
|
1205
|
+
...required.length > 0 ? { required } : {},
|
|
1206
|
+
additionalProperties: false
|
|
1207
|
+
};
|
|
1208
|
+
task.config = {
|
|
1209
|
+
...task.config,
|
|
1210
|
+
inputSchema: schema,
|
|
1211
|
+
outputSchema: schema
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
if (task.type === "OutputTask") {
|
|
1215
|
+
const incoming = graph.getSourceDataflows(task.id);
|
|
1216
|
+
if (incoming.length === 0)
|
|
1217
|
+
continue;
|
|
1218
|
+
const properties = {};
|
|
1219
|
+
const required = [];
|
|
1220
|
+
for (const df of incoming) {
|
|
1221
|
+
const sourceTask = graph.getTask(df.sourceTaskId);
|
|
1222
|
+
if (!sourceTask)
|
|
1223
|
+
continue;
|
|
1224
|
+
const sourceSchema = sourceTask.outputSchema();
|
|
1225
|
+
if (typeof sourceSchema === "boolean")
|
|
1226
|
+
continue;
|
|
1227
|
+
let prop = sourceSchema.properties?.[df.sourceTaskPortId];
|
|
1228
|
+
let propRequired = sourceSchema.required?.includes(df.sourceTaskPortId) ?? false;
|
|
1229
|
+
if (!prop && sourceSchema.additionalProperties === true && sourceTask.constructor.passthroughInputsToOutputs === true) {
|
|
1230
|
+
const upstreamDfs = graph.getSourceDataflows(sourceTask.id);
|
|
1231
|
+
for (const udf of upstreamDfs) {
|
|
1232
|
+
if (udf.targetTaskPortId !== df.sourceTaskPortId)
|
|
1233
|
+
continue;
|
|
1234
|
+
const upstreamTask = graph.getTask(udf.sourceTaskId);
|
|
1235
|
+
if (!upstreamTask)
|
|
1236
|
+
continue;
|
|
1237
|
+
const upstreamSchema = upstreamTask.outputSchema();
|
|
1238
|
+
if (typeof upstreamSchema === "boolean")
|
|
1239
|
+
continue;
|
|
1240
|
+
prop = upstreamSchema.properties?.[udf.sourceTaskPortId];
|
|
1241
|
+
if (prop) {
|
|
1242
|
+
propRequired = upstreamSchema.required?.includes(udf.sourceTaskPortId) ?? false;
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
if (prop && typeof prop !== "boolean") {
|
|
1248
|
+
properties[df.targetTaskPortId] = prop;
|
|
1249
|
+
if (propRequired && !required.includes(df.targetTaskPortId)) {
|
|
1250
|
+
required.push(df.targetTaskPortId);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const schema = {
|
|
1255
|
+
type: "object",
|
|
1256
|
+
properties,
|
|
1257
|
+
...required.length > 0 ? { required } : {},
|
|
1258
|
+
additionalProperties: false
|
|
1259
|
+
};
|
|
1260
|
+
task.config = {
|
|
1261
|
+
...task.config,
|
|
1262
|
+
inputSchema: schema,
|
|
1263
|
+
outputSchema: schema
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1135
1268
|
// src/task-graph/TaskGraph.ts
|
|
1136
|
-
import { EventEmitter as EventEmitter4, uuid4 as
|
|
1269
|
+
import { EventEmitter as EventEmitter4, uuid4 as uuid45 } from "@workglow/util";
|
|
1137
1270
|
import { DirectedAcyclicGraph } from "@workglow/util/graph";
|
|
1138
1271
|
|
|
1139
1272
|
// src/task/GraphAsTask.ts
|
|
1140
|
-
import { getLogger as
|
|
1273
|
+
import { getLogger as getLogger6 } from "@workglow/util";
|
|
1141
1274
|
import { CycleError } from "@workglow/util/graph";
|
|
1142
1275
|
import { compileSchema as compileSchema2 } from "@workglow/util/schema";
|
|
1143
1276
|
|
|
1144
1277
|
// src/task-graph/TaskGraphRunner.ts
|
|
1145
1278
|
import {
|
|
1146
1279
|
collectPropertyValues,
|
|
1147
|
-
getLogger as
|
|
1280
|
+
getLogger as getLogger5,
|
|
1148
1281
|
getTelemetryProvider as getTelemetryProvider2,
|
|
1149
1282
|
globalServiceRegistry as globalServiceRegistry3,
|
|
1283
|
+
ResourceScope as ResourceScope2,
|
|
1150
1284
|
ServiceRegistry as ServiceRegistry2,
|
|
1151
1285
|
SpanStatusCode as SpanStatusCode2,
|
|
1152
|
-
uuid4 as
|
|
1286
|
+
uuid4 as uuid44
|
|
1153
1287
|
} from "@workglow/util";
|
|
1154
|
-
import { previewSource } from "@workglow/util/media";
|
|
1155
1288
|
|
|
1156
1289
|
// src/storage/TaskOutputRepository.ts
|
|
1157
1290
|
import { createServiceToken as createServiceToken2, EventEmitter as EventEmitter2 } from "@workglow/util";
|
|
@@ -1183,144 +1316,192 @@ class TaskOutputRepository {
|
|
|
1183
1316
|
}
|
|
1184
1317
|
}
|
|
1185
1318
|
|
|
1186
|
-
// src/task/
|
|
1187
|
-
import {
|
|
1319
|
+
// src/task/EntitlementEnforcer.ts
|
|
1320
|
+
import { createServiceToken as createServiceToken4 } from "@workglow/util";
|
|
1188
1321
|
|
|
1189
|
-
// src/task/
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
case "is_false":
|
|
1200
|
-
return true;
|
|
1201
|
-
default:
|
|
1202
|
-
return false;
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
const strValue = String(fieldValue);
|
|
1206
|
-
const numValue = Number(fieldValue);
|
|
1207
|
-
switch (operator) {
|
|
1208
|
-
case "equals":
|
|
1209
|
-
if (!isNaN(numValue) && !isNaN(Number(compareValue))) {
|
|
1210
|
-
return numValue === Number(compareValue);
|
|
1211
|
-
}
|
|
1212
|
-
return strValue === compareValue;
|
|
1213
|
-
case "not_equals":
|
|
1214
|
-
if (!isNaN(numValue) && !isNaN(Number(compareValue))) {
|
|
1215
|
-
return numValue !== Number(compareValue);
|
|
1216
|
-
}
|
|
1217
|
-
return strValue !== compareValue;
|
|
1218
|
-
case "greater_than":
|
|
1219
|
-
return numValue > Number(compareValue);
|
|
1220
|
-
case "greater_or_equal":
|
|
1221
|
-
return numValue >= Number(compareValue);
|
|
1222
|
-
case "less_than":
|
|
1223
|
-
return numValue < Number(compareValue);
|
|
1224
|
-
case "less_or_equal":
|
|
1225
|
-
return numValue <= Number(compareValue);
|
|
1226
|
-
case "contains":
|
|
1227
|
-
return strValue.toLowerCase().includes(compareValue.toLowerCase());
|
|
1228
|
-
case "starts_with":
|
|
1229
|
-
return strValue.toLowerCase().startsWith(compareValue.toLowerCase());
|
|
1230
|
-
case "ends_with":
|
|
1231
|
-
return strValue.toLowerCase().endsWith(compareValue.toLowerCase());
|
|
1232
|
-
case "is_empty":
|
|
1233
|
-
return strValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0;
|
|
1234
|
-
case "is_not_empty":
|
|
1235
|
-
return strValue !== "" && !(Array.isArray(fieldValue) && fieldValue.length === 0);
|
|
1236
|
-
case "is_true":
|
|
1237
|
-
return Boolean(fieldValue) === true;
|
|
1238
|
-
case "is_false":
|
|
1239
|
-
return Boolean(fieldValue) === false;
|
|
1240
|
-
default:
|
|
1241
|
-
return false;
|
|
1242
|
-
}
|
|
1322
|
+
// src/task/EntitlementPolicy.ts
|
|
1323
|
+
var EMPTY_POLICY = Object.freeze({
|
|
1324
|
+
deny: Object.freeze([]),
|
|
1325
|
+
grant: Object.freeze([]),
|
|
1326
|
+
ask: Object.freeze([])
|
|
1327
|
+
});
|
|
1328
|
+
function ruleCovers(rule, required) {
|
|
1329
|
+
if (!entitlementCovers(rule.id, required.id))
|
|
1330
|
+
return false;
|
|
1331
|
+
return grantCoversResources(rule, required);
|
|
1243
1332
|
}
|
|
1244
|
-
function
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1333
|
+
function evaluatePolicy(policy, required) {
|
|
1334
|
+
const results = [];
|
|
1335
|
+
for (const entitlement of required.entitlements) {
|
|
1336
|
+
if (entitlement.optional)
|
|
1337
|
+
continue;
|
|
1338
|
+
const denyMatch = policy.deny.find((rule) => ruleCovers(rule, entitlement));
|
|
1339
|
+
if (denyMatch) {
|
|
1340
|
+
results.push({ verdict: "denied", entitlement, matchedRule: denyMatch });
|
|
1341
|
+
continue;
|
|
1250
1342
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
// src/task/TaskRunner.ts
|
|
1261
|
-
import {
|
|
1262
|
-
getLogger,
|
|
1263
|
-
getTelemetryProvider,
|
|
1264
|
-
globalServiceRegistry as globalServiceRegistry2,
|
|
1265
|
-
SpanStatusCode
|
|
1266
|
-
} from "@workglow/util";
|
|
1267
|
-
import { getPortCodec } from "@workglow/util";
|
|
1268
|
-
|
|
1269
|
-
// src/task/StreamTypes.ts
|
|
1270
|
-
function getPortStreamMode(schema, portId) {
|
|
1271
|
-
if (typeof schema === "boolean")
|
|
1272
|
-
return "none";
|
|
1273
|
-
const prop = schema.properties?.[portId];
|
|
1274
|
-
if (!prop || typeof prop === "boolean")
|
|
1275
|
-
return "none";
|
|
1276
|
-
const xStream = prop["x-stream"];
|
|
1277
|
-
if (xStream === "append" || xStream === "replace" || xStream === "object")
|
|
1278
|
-
return xStream;
|
|
1279
|
-
return "none";
|
|
1280
|
-
}
|
|
1281
|
-
function getStreamingPorts(schema) {
|
|
1282
|
-
if (typeof schema === "boolean")
|
|
1283
|
-
return [];
|
|
1284
|
-
const props = schema.properties;
|
|
1285
|
-
if (!props)
|
|
1286
|
-
return [];
|
|
1287
|
-
const result = [];
|
|
1288
|
-
for (const [name, prop] of Object.entries(props)) {
|
|
1289
|
-
if (!prop || typeof prop === "boolean")
|
|
1343
|
+
const grantMatch = policy.grant.find((rule) => ruleCovers(rule, entitlement));
|
|
1344
|
+
if (grantMatch) {
|
|
1345
|
+
results.push({ verdict: "granted", entitlement, matchedRule: grantMatch });
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
const askMatch = policy.ask.find((rule) => ruleCovers(rule, entitlement));
|
|
1349
|
+
if (askMatch) {
|
|
1350
|
+
results.push({ verdict: "ask", entitlement, matchedRule: askMatch });
|
|
1290
1351
|
continue;
|
|
1291
|
-
const xStream = prop["x-stream"];
|
|
1292
|
-
if (xStream === "append" || xStream === "replace" || xStream === "object") {
|
|
1293
|
-
result.push({ port: name, mode: xStream });
|
|
1294
1352
|
}
|
|
1353
|
+
results.push({ verdict: "denied", entitlement });
|
|
1295
1354
|
}
|
|
1355
|
+
return results;
|
|
1356
|
+
}
|
|
1357
|
+
function can(policy, id, resources) {
|
|
1358
|
+
const required = resources !== undefined ? { id, resources } : { id };
|
|
1359
|
+
const [result] = evaluatePolicy(policy, { entitlements: [required] });
|
|
1296
1360
|
return result;
|
|
1297
1361
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1362
|
+
|
|
1363
|
+
// src/task/EntitlementResolver.ts
|
|
1364
|
+
import { createServiceToken as createServiceToken3 } from "@workglow/util";
|
|
1365
|
+
var PERMISSIVE_RESOLVER = {
|
|
1366
|
+
lookup: () => "grant",
|
|
1367
|
+
prompt: async () => "grant",
|
|
1368
|
+
save: () => {}
|
|
1369
|
+
};
|
|
1370
|
+
var DENY_ALL_RESOLVER = {
|
|
1371
|
+
lookup: () => "deny",
|
|
1372
|
+
prompt: async () => "deny",
|
|
1373
|
+
save: () => {}
|
|
1374
|
+
};
|
|
1375
|
+
var ENTITLEMENT_RESOLVER = createServiceToken3("workglow.entitlementResolver");
|
|
1376
|
+
|
|
1377
|
+
// src/task/EntitlementEnforcer.ts
|
|
1378
|
+
function formatEntitlementDenial(denial) {
|
|
1379
|
+
switch (denial.reason) {
|
|
1380
|
+
case "policy-deny":
|
|
1381
|
+
return `${denial.entitlement.id} (denied by rule ${denial.matchedRule.id})`;
|
|
1382
|
+
case "user-deny":
|
|
1383
|
+
return `${denial.entitlement.id} (denied by user)`;
|
|
1384
|
+
case "default-deny":
|
|
1385
|
+
return `${denial.entitlement.id} (no matching grant)`;
|
|
1307
1386
|
}
|
|
1308
|
-
return mode;
|
|
1309
|
-
}
|
|
1310
|
-
function isTaskStreamable(task) {
|
|
1311
|
-
if (typeof task.executeStream !== "function")
|
|
1312
|
-
return false;
|
|
1313
|
-
return getOutputStreamMode(task.outputSchema()) !== "none";
|
|
1314
1387
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1388
|
+
var PERMISSIVE_ENFORCER = {
|
|
1389
|
+
checkAll: async () => [],
|
|
1390
|
+
checkTask: async () => []
|
|
1391
|
+
};
|
|
1392
|
+
function createPolicyEnforcer(policy, resolver = PERMISSIVE_RESOLVER) {
|
|
1393
|
+
async function resolveAsks(required, taskType, taskId) {
|
|
1394
|
+
const results = evaluatePolicy(policy, required);
|
|
1395
|
+
const denied = [];
|
|
1396
|
+
for (const result of results) {
|
|
1397
|
+
if (result.verdict === "denied") {
|
|
1398
|
+
if (result.matchedRule) {
|
|
1399
|
+
denied.push({
|
|
1400
|
+
entitlement: result.entitlement,
|
|
1401
|
+
reason: "policy-deny",
|
|
1402
|
+
matchedRule: result.matchedRule
|
|
1403
|
+
});
|
|
1404
|
+
} else {
|
|
1405
|
+
denied.push({ entitlement: result.entitlement, reason: "default-deny" });
|
|
1406
|
+
}
|
|
1407
|
+
} else if (result.verdict === "ask") {
|
|
1408
|
+
const request = {
|
|
1409
|
+
entitlement: result.entitlement,
|
|
1410
|
+
taskType: taskType ?? "unknown",
|
|
1411
|
+
taskId: taskId ?? "unknown"
|
|
1412
|
+
};
|
|
1413
|
+
let answer = resolver.lookup(request);
|
|
1414
|
+
if (answer === undefined) {
|
|
1415
|
+
answer = await resolver.prompt(request);
|
|
1416
|
+
resolver.save(request, answer);
|
|
1417
|
+
}
|
|
1418
|
+
if (answer === "deny") {
|
|
1419
|
+
if (!result.matchedRule) {
|
|
1420
|
+
throw new Error(`Invariant violation: ask verdict for "${result.entitlement.id}" is missing matchedRule`);
|
|
1421
|
+
}
|
|
1422
|
+
denied.push({
|
|
1423
|
+
entitlement: result.entitlement,
|
|
1424
|
+
reason: "user-deny",
|
|
1425
|
+
matchedRule: result.matchedRule
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return denied;
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
async checkAll(required) {
|
|
1434
|
+
return resolveAsks(required);
|
|
1435
|
+
},
|
|
1436
|
+
async checkTask(task) {
|
|
1437
|
+
const entitlements = task.entitlements();
|
|
1438
|
+
return resolveAsks(entitlements, task.constructor.type, task.id);
|
|
1439
|
+
}
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
function createScopedEnforcer(grants) {
|
|
1443
|
+
return createPolicyEnforcer({ deny: [], grant: grants, ask: [] });
|
|
1444
|
+
}
|
|
1445
|
+
function createGrantListEnforcer(grants) {
|
|
1446
|
+
return createScopedEnforcer(grants.map((id) => ({ id })));
|
|
1447
|
+
}
|
|
1448
|
+
var ENTITLEMENT_ENFORCER = createServiceToken4("workglow.entitlementEnforcer");
|
|
1449
|
+
|
|
1450
|
+
// src/task/StreamTypes.ts
|
|
1451
|
+
function getPortStreamMode(schema, portId) {
|
|
1452
|
+
if (typeof schema === "boolean")
|
|
1453
|
+
return "none";
|
|
1454
|
+
const prop = schema.properties?.[portId];
|
|
1455
|
+
if (!prop || typeof prop === "boolean")
|
|
1456
|
+
return "none";
|
|
1457
|
+
const xStream = prop["x-stream"];
|
|
1458
|
+
if (xStream === "append" || xStream === "replace" || xStream === "object")
|
|
1459
|
+
return xStream;
|
|
1460
|
+
return "none";
|
|
1461
|
+
}
|
|
1462
|
+
function getStreamingPorts(schema) {
|
|
1463
|
+
if (typeof schema === "boolean")
|
|
1464
|
+
return [];
|
|
1465
|
+
const props = schema.properties;
|
|
1466
|
+
if (!props)
|
|
1467
|
+
return [];
|
|
1468
|
+
const result = [];
|
|
1469
|
+
for (const [name, prop] of Object.entries(props)) {
|
|
1470
|
+
if (!prop || typeof prop === "boolean")
|
|
1471
|
+
continue;
|
|
1472
|
+
const xStream = prop["x-stream"];
|
|
1473
|
+
if (xStream === "append" || xStream === "replace" || xStream === "object") {
|
|
1474
|
+
result.push({ port: name, mode: xStream });
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return result;
|
|
1478
|
+
}
|
|
1479
|
+
function getOutputStreamMode(outputSchema) {
|
|
1480
|
+
const ports = getStreamingPorts(outputSchema);
|
|
1481
|
+
if (ports.length === 0)
|
|
1482
|
+
return "none";
|
|
1483
|
+
const mode = ports[0].mode;
|
|
1484
|
+
for (let i = 1;i < ports.length; i++) {
|
|
1485
|
+
if (ports[i].mode !== mode) {
|
|
1486
|
+
return "mixed";
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return mode;
|
|
1490
|
+
}
|
|
1491
|
+
function isTaskStreamable(task) {
|
|
1492
|
+
if (typeof task.executeStream !== "function")
|
|
1493
|
+
return false;
|
|
1494
|
+
return getOutputStreamMode(task.outputSchema()) !== "none";
|
|
1495
|
+
}
|
|
1496
|
+
function getAppendPortId(schema) {
|
|
1497
|
+
if (typeof schema === "boolean")
|
|
1498
|
+
return;
|
|
1499
|
+
const props = schema.properties;
|
|
1500
|
+
if (!props)
|
|
1501
|
+
return;
|
|
1502
|
+
for (const [name, prop] of Object.entries(props)) {
|
|
1503
|
+
if (!prop || typeof prop === "boolean")
|
|
1504
|
+
continue;
|
|
1324
1505
|
if (prop["x-stream"] === "append")
|
|
1325
1506
|
return name;
|
|
1326
1507
|
}
|
|
@@ -1367,296 +1548,337 @@ function hasStructuredOutput(schema) {
|
|
|
1367
1548
|
return getStructuredOutputSchemas(schema).size > 0;
|
|
1368
1549
|
}
|
|
1369
1550
|
|
|
1370
|
-
// src/task/
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
}
|
|
1551
|
+
// src/task-graph/EdgeMaterializer.ts
|
|
1552
|
+
import { getLogger } from "@workglow/util";
|
|
1553
|
+
import { previewSource } from "@workglow/util/media";
|
|
1554
|
+
class EdgeMaterializer {
|
|
1555
|
+
graph;
|
|
1556
|
+
runner;
|
|
1557
|
+
constructor(graph, runner) {
|
|
1558
|
+
this.graph = graph;
|
|
1559
|
+
this.runner = runner;
|
|
1380
1560
|
}
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
out[key] = await codec.deserialize(out[key]);
|
|
1561
|
+
filterInputForTask(task, input) {
|
|
1562
|
+
const sourceDataflows = this.graph.getSourceDataflows(task.id);
|
|
1563
|
+
const connectedInputs = new Set(sourceDataflows.map((df) => df.targetTaskPortId));
|
|
1564
|
+
const allPortsConnected = connectedInputs.has(DATAFLOW_ALL_PORTS);
|
|
1565
|
+
const filteredInput = {};
|
|
1566
|
+
for (const [key, value] of Object.entries(input)) {
|
|
1567
|
+
if (!connectedInputs.has(key) && !allPortsConnected) {
|
|
1568
|
+
filteredInput[key] = value;
|
|
1569
|
+
}
|
|
1391
1570
|
}
|
|
1571
|
+
return filteredInput;
|
|
1392
1572
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1573
|
+
copyInputFromEdgesToNode(task) {
|
|
1574
|
+
const dataflows = this.graph.getSourceDataflows(task.id);
|
|
1575
|
+
dataflows.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
1576
|
+
for (const dataflow of dataflows) {
|
|
1577
|
+
const live = dataflow.getCurrentValue();
|
|
1578
|
+
const port = dataflow.targetTaskPortId;
|
|
1579
|
+
let portData;
|
|
1580
|
+
if (port === DATAFLOW_ALL_PORTS) {
|
|
1581
|
+
portData = live;
|
|
1582
|
+
} else if (port === DATAFLOW_ERROR_PORT) {
|
|
1583
|
+
portData = { [DATAFLOW_ERROR_PORT]: dataflow.error };
|
|
1584
|
+
} else {
|
|
1585
|
+
portData = { [port]: live };
|
|
1586
|
+
}
|
|
1587
|
+
this.runner.addInputData(task, portData);
|
|
1403
1588
|
}
|
|
1404
1589
|
}
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
running = false;
|
|
1413
|
-
previewRunning = false;
|
|
1414
|
-
task;
|
|
1415
|
-
abortController;
|
|
1416
|
-
outputCache;
|
|
1417
|
-
registry = globalServiceRegistry2;
|
|
1418
|
-
resourceScope;
|
|
1419
|
-
inputStreams;
|
|
1420
|
-
timeoutTimer;
|
|
1421
|
-
pendingTimeoutError;
|
|
1422
|
-
shouldAccumulate = true;
|
|
1423
|
-
telemetrySpan;
|
|
1424
|
-
constructor(task) {
|
|
1425
|
-
this.task = task;
|
|
1426
|
-
this.own = this.own.bind(this);
|
|
1427
|
-
this.handleProgress = this.handleProgress.bind(this);
|
|
1428
|
-
}
|
|
1429
|
-
async run(overrides = {}, config = {}) {
|
|
1430
|
-
await this.handleStart(config);
|
|
1431
|
-
const proto = Object.getPrototypeOf(this.task);
|
|
1432
|
-
if (proto.execute === Task.prototype.execute && typeof proto.executeStream !== "function" && proto.executePreview !== Task.prototype.executePreview) {
|
|
1433
|
-
throw new TaskConfigurationError(`Task "${this.task.type}" implements only executePreview() and cannot be run via run(). ` + `After the run/runPreview split, run() requires execute() (or executeStream()). ` + `See docs/technical/02-dual-mode-execution.md.`);
|
|
1434
|
-
}
|
|
1435
|
-
try {
|
|
1436
|
-
this.task.setInput(overrides);
|
|
1437
|
-
const configSchema = this.task.constructor.configSchema();
|
|
1438
|
-
if (schemaHasFormatAnnotations(configSchema)) {
|
|
1439
|
-
const source = this.task.originalConfig ?? this.task.config;
|
|
1440
|
-
const resolved = await resolveSchemaInputs({ ...source }, configSchema, { registry: this.registry });
|
|
1441
|
-
Object.assign(this.task.config, resolved);
|
|
1442
|
-
}
|
|
1443
|
-
const schema = this.task.constructor.inputSchema();
|
|
1444
|
-
this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
|
|
1445
|
-
const inputs = this.task.runInputData;
|
|
1446
|
-
const isValid = await this.task.validateInput(inputs);
|
|
1447
|
-
if (!isValid) {
|
|
1448
|
-
throw new TaskInvalidInputError("Invalid input data");
|
|
1449
|
-
}
|
|
1450
|
-
if (this.abortController?.signal.aborted) {
|
|
1451
|
-
await this.handleAbort();
|
|
1452
|
-
throw new TaskAbortedError("Promise for task created and aborted before run");
|
|
1453
|
-
}
|
|
1454
|
-
let outputs;
|
|
1455
|
-
const isStreamable = isTaskStreamable(this.task);
|
|
1456
|
-
if (!isStreamable && typeof this.task.executeStream !== "function") {
|
|
1457
|
-
const streamMode = getOutputStreamMode(this.task.outputSchema());
|
|
1458
|
-
if (streamMode !== "none") {
|
|
1459
|
-
getLogger().warn(`Task "${this.task.type}" declares streaming output (x-stream: "${streamMode}") ` + `but does not implement executeStream(). Falling back to non-streaming execute().`);
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
const inputSchema = this.task.constructor.inputSchema();
|
|
1463
|
-
const outputSchema = this.task.constructor.outputSchema();
|
|
1464
|
-
const inputsForKey = this.outputCache ? await normalizeInputsForCacheKey(inputs, inputSchema) : inputs;
|
|
1465
|
-
if (this.task.cacheable) {
|
|
1466
|
-
const cached = await this.outputCache?.getOutput(this.task.type, inputsForKey);
|
|
1467
|
-
if (cached !== undefined) {
|
|
1468
|
-
outputs = await deserializeOutputPorts(cached, outputSchema);
|
|
1469
|
-
this.telemetrySpan?.addEvent("workglow.task.cache_hit");
|
|
1470
|
-
if (isStreamable) {
|
|
1471
|
-
this.task.runOutputData = outputs;
|
|
1472
|
-
this.task.emit("stream_start");
|
|
1473
|
-
this.task.emit("stream_chunk", { type: "finish", data: outputs });
|
|
1474
|
-
this.task.emit("stream_end", outputs);
|
|
1475
|
-
} else {
|
|
1476
|
-
this.task.runOutputData = outputs;
|
|
1477
|
-
}
|
|
1590
|
+
async pushOutputFromNodeToEdges(node, results) {
|
|
1591
|
+
const dataflows = this.graph.getTargetDataflows(node.id);
|
|
1592
|
+
if (this.runner["previewRunning"] && Object.keys(results).length > 0) {
|
|
1593
|
+
for (const port of Object.keys(results)) {
|
|
1594
|
+
const value = results[port];
|
|
1595
|
+
if (EdgeMaterializer.isImageValueShape(value)) {
|
|
1596
|
+
results[port] = await previewSource(value);
|
|
1478
1597
|
}
|
|
1479
1598
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1599
|
+
}
|
|
1600
|
+
for (const dataflow of dataflows) {
|
|
1601
|
+
if (dataflow.stream !== undefined)
|
|
1602
|
+
continue;
|
|
1603
|
+
const registry = this.runner["registry"];
|
|
1604
|
+
const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow, registry);
|
|
1605
|
+
if (compatibility === "static") {
|
|
1606
|
+
dataflow.setPortData(results);
|
|
1607
|
+
await dataflow.applyTransforms(registry);
|
|
1608
|
+
} else if (compatibility === "runtime") {
|
|
1609
|
+
const task = this.graph.getTask(dataflow.targetTaskId);
|
|
1610
|
+
const narrowed = await task.narrowInput({ ...results }, registry);
|
|
1611
|
+
dataflow.setPortData(narrowed);
|
|
1612
|
+
await dataflow.applyTransforms(registry);
|
|
1613
|
+
} else {
|
|
1614
|
+
const resultsKeys = Object.keys(results);
|
|
1615
|
+
if (resultsKeys.length > 0) {
|
|
1616
|
+
getLogger().warn("pushOutputFromNodeToEdge not compatible, not setting port data", {
|
|
1617
|
+
dataflowId: dataflow.id,
|
|
1618
|
+
compatibility,
|
|
1619
|
+
resultsKeys
|
|
1620
|
+
});
|
|
1489
1621
|
}
|
|
1490
|
-
this.task.runOutputData = outputs ?? {};
|
|
1491
1622
|
}
|
|
1492
|
-
await this.handleComplete();
|
|
1493
|
-
return this.task.runOutputData;
|
|
1494
|
-
} catch (err) {
|
|
1495
|
-
await this.handleError(err);
|
|
1496
|
-
throw this.task.error instanceof TaskTimeoutError ? this.task.error : err;
|
|
1497
1623
|
}
|
|
1498
1624
|
}
|
|
1499
|
-
|
|
1500
|
-
if (
|
|
1501
|
-
return
|
|
1625
|
+
pushErrorFromNodeToEdges(node) {
|
|
1626
|
+
if (!node?.config?.id)
|
|
1627
|
+
return;
|
|
1628
|
+
this.graph.getTargetDataflows(node.id).forEach((dataflow) => {
|
|
1629
|
+
dataflow.error = node.error;
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
hasErrorOutputEdges(task) {
|
|
1633
|
+
const dataflows = this.graph.getTargetDataflows(task.id);
|
|
1634
|
+
return dataflows.some((df) => df.sourceTaskPortId === DATAFLOW_ERROR_PORT);
|
|
1635
|
+
}
|
|
1636
|
+
pushErrorOutputToEdges(task) {
|
|
1637
|
+
const taskError = task.error;
|
|
1638
|
+
const errorData = {
|
|
1639
|
+
error: taskError?.message ?? "Unknown error",
|
|
1640
|
+
errorType: taskError?.constructor?.type ?? "TaskError"
|
|
1641
|
+
};
|
|
1642
|
+
const dataflows = this.graph.getTargetDataflows(task.id);
|
|
1643
|
+
for (const df of dataflows) {
|
|
1644
|
+
if (df.sourceTaskPortId === DATAFLOW_ERROR_PORT) {
|
|
1645
|
+
df.value = errorData;
|
|
1646
|
+
df.setStatus(TaskStatus.COMPLETED);
|
|
1647
|
+
} else {
|
|
1648
|
+
df.setStatus(TaskStatus.DISABLED);
|
|
1649
|
+
}
|
|
1502
1650
|
}
|
|
1503
|
-
this.
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
throw new TaskInvalidInputError("Invalid input data");
|
|
1518
|
-
}
|
|
1519
|
-
const resultPreview = await this.executeTaskPreview(inputs);
|
|
1520
|
-
if (resultPreview !== undefined) {
|
|
1521
|
-
this.task.runOutputData = resultPreview;
|
|
1522
|
-
}
|
|
1523
|
-
await this.handleCompletePreview();
|
|
1524
|
-
} catch (err) {
|
|
1525
|
-
getLogger().debug("runPreview failed", { taskId: this.task.config?.id, error: err });
|
|
1526
|
-
await this.handleErrorPreview();
|
|
1527
|
-
} finally {
|
|
1528
|
-
return this.task.runOutputData;
|
|
1651
|
+
this.runner["runScheduler"].propagateDisabledStatus(this.runner["currentCtx"]);
|
|
1652
|
+
}
|
|
1653
|
+
resetTask(graph, task, runId) {
|
|
1654
|
+
task.status = TaskStatus.PENDING;
|
|
1655
|
+
task.resetInputData();
|
|
1656
|
+
task.runOutputData = {};
|
|
1657
|
+
task.error = undefined;
|
|
1658
|
+
task.progress = 0;
|
|
1659
|
+
task.runConfig = { ...task.runConfig, runnerId: runId };
|
|
1660
|
+
this.runner["runScheduler"].pushStatusFromNodeToEdges(task, this.runner["currentCtx"], undefined, graph);
|
|
1661
|
+
if (task?.config?.id) {
|
|
1662
|
+
graph.getTargetDataflows(task.id).forEach((dataflow) => {
|
|
1663
|
+
dataflow.error = task.error;
|
|
1664
|
+
});
|
|
1529
1665
|
}
|
|
1666
|
+
task.emit("reset");
|
|
1667
|
+
task.emit("status", task.status);
|
|
1530
1668
|
}
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1669
|
+
static isImageValueShape(v) {
|
|
1670
|
+
if (v === null || typeof v !== "object")
|
|
1671
|
+
return false;
|
|
1672
|
+
const o = v;
|
|
1673
|
+
return typeof o.width === "number" && typeof o.height === "number" && typeof o.previewScale === "number";
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
// src/task-graph/RunContext.ts
|
|
1678
|
+
import { uuid4 as uuid42 } from "@workglow/util";
|
|
1679
|
+
|
|
1680
|
+
class RunContext {
|
|
1681
|
+
runId;
|
|
1682
|
+
abortController;
|
|
1683
|
+
inProgressTasks = new Map;
|
|
1684
|
+
inProgressFunctions = new Map;
|
|
1685
|
+
failedTaskErrors = new Map;
|
|
1686
|
+
telemetrySpan;
|
|
1687
|
+
graphTimeoutTimer;
|
|
1688
|
+
pendingGraphTimeoutError;
|
|
1689
|
+
activeEnforcer;
|
|
1690
|
+
parentSignalCleanup;
|
|
1691
|
+
constructor(parentSignal) {
|
|
1692
|
+
this.runId = uuid42();
|
|
1693
|
+
this.abortController = new AbortController;
|
|
1694
|
+
if (parentSignal) {
|
|
1695
|
+
const onParentAbort = () => this.abortController.abort();
|
|
1696
|
+
parentSignal.addEventListener("abort", onParentAbort, { once: true });
|
|
1697
|
+
this.parentSignalCleanup = () => parentSignal.removeEventListener("abort", onParentAbort);
|
|
1698
|
+
if (parentSignal.aborted) {
|
|
1699
|
+
this.parentSignalCleanup();
|
|
1700
|
+
this.parentSignalCleanup = undefined;
|
|
1701
|
+
this.abortController.abort();
|
|
1544
1702
|
}
|
|
1545
1703
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
}
|
|
1574
|
-
dirty = true;
|
|
1575
|
-
wake();
|
|
1576
|
-
};
|
|
1577
|
-
const onEnd = () => {
|
|
1578
|
-
pendingUpstreams.delete(upstream);
|
|
1579
|
-
wake();
|
|
1580
|
-
};
|
|
1581
|
-
const onStatus = (status) => {
|
|
1582
|
-
if (status === TaskStatus.COMPLETED || status === TaskStatus.FAILED || status === TaskStatus.DISABLED) {
|
|
1583
|
-
pendingUpstreams.delete(upstream);
|
|
1584
|
-
wake();
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1587
|
-
upstream.on("stream_chunk", onChunk);
|
|
1588
|
-
upstream.on("stream_end", onEnd);
|
|
1589
|
-
upstream.on("status", onStatus);
|
|
1590
|
-
cleanupFns.push(() => {
|
|
1591
|
-
upstream.off("stream_chunk", onChunk);
|
|
1592
|
-
upstream.off("stream_end", onEnd);
|
|
1593
|
-
upstream.off("status", onStatus);
|
|
1594
|
-
});
|
|
1704
|
+
}
|
|
1705
|
+
dispose() {
|
|
1706
|
+
this.parentSignalCleanup?.();
|
|
1707
|
+
this.parentSignalCleanup = undefined;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/task-graph/RunScheduler.ts
|
|
1712
|
+
import { getLogger as getLogger4 } from "@workglow/util";
|
|
1713
|
+
|
|
1714
|
+
// src/task/ConditionalTask.ts
|
|
1715
|
+
import { getLogger as getLogger3 } from "@workglow/util";
|
|
1716
|
+
|
|
1717
|
+
// src/task/ConditionUtils.ts
|
|
1718
|
+
function evaluateCondition(fieldValue, operator, compareValue) {
|
|
1719
|
+
if (fieldValue === null || fieldValue === undefined) {
|
|
1720
|
+
switch (operator) {
|
|
1721
|
+
case "is_empty":
|
|
1722
|
+
return true;
|
|
1723
|
+
case "is_not_empty":
|
|
1724
|
+
return false;
|
|
1725
|
+
case "is_true":
|
|
1726
|
+
return false;
|
|
1727
|
+
case "is_false":
|
|
1728
|
+
return true;
|
|
1729
|
+
default:
|
|
1730
|
+
return false;
|
|
1595
1731
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1732
|
+
}
|
|
1733
|
+
const strValue = String(fieldValue);
|
|
1734
|
+
const numValue = Number(fieldValue);
|
|
1735
|
+
switch (operator) {
|
|
1736
|
+
case "equals":
|
|
1737
|
+
if (!isNaN(numValue) && !isNaN(Number(compareValue))) {
|
|
1738
|
+
return numValue === Number(compareValue);
|
|
1599
1739
|
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
dirty = false;
|
|
1605
|
-
try {
|
|
1606
|
-
const out = await this.runPreview(overrides);
|
|
1607
|
-
yield out;
|
|
1608
|
-
} catch (err) {
|
|
1609
|
-
getLogger().debug("runPreviewStream iteration failed", {
|
|
1610
|
-
taskId: this.task.config?.id,
|
|
1611
|
-
error: err
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
continue;
|
|
1615
|
-
}
|
|
1616
|
-
if (pendingUpstreams.size === 0)
|
|
1617
|
-
return;
|
|
1618
|
-
await wakeNext();
|
|
1740
|
+
return strValue === compareValue;
|
|
1741
|
+
case "not_equals":
|
|
1742
|
+
if (!isNaN(numValue) && !isNaN(Number(compareValue))) {
|
|
1743
|
+
return numValue !== Number(compareValue);
|
|
1619
1744
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1745
|
+
return strValue !== compareValue;
|
|
1746
|
+
case "greater_than":
|
|
1747
|
+
return numValue > Number(compareValue);
|
|
1748
|
+
case "greater_or_equal":
|
|
1749
|
+
return numValue >= Number(compareValue);
|
|
1750
|
+
case "less_than":
|
|
1751
|
+
return numValue < Number(compareValue);
|
|
1752
|
+
case "less_or_equal":
|
|
1753
|
+
return numValue <= Number(compareValue);
|
|
1754
|
+
case "contains":
|
|
1755
|
+
return strValue.toLowerCase().includes(compareValue.toLowerCase());
|
|
1756
|
+
case "starts_with":
|
|
1757
|
+
return strValue.toLowerCase().startsWith(compareValue.toLowerCase());
|
|
1758
|
+
case "ends_with":
|
|
1759
|
+
return strValue.toLowerCase().endsWith(compareValue.toLowerCase());
|
|
1760
|
+
case "is_empty":
|
|
1761
|
+
return strValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0;
|
|
1762
|
+
case "is_not_empty":
|
|
1763
|
+
return strValue !== "" && !(Array.isArray(fieldValue) && fieldValue.length === 0);
|
|
1764
|
+
case "is_true":
|
|
1765
|
+
return Boolean(fieldValue) === true;
|
|
1766
|
+
case "is_false":
|
|
1767
|
+
return Boolean(fieldValue) === false;
|
|
1768
|
+
default:
|
|
1769
|
+
return false;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
function getNestedValue(obj, path) {
|
|
1773
|
+
const parts = path.split(".");
|
|
1774
|
+
let current = obj;
|
|
1775
|
+
for (const part of parts) {
|
|
1776
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
1777
|
+
return;
|
|
1623
1778
|
}
|
|
1779
|
+
current = current[part];
|
|
1624
1780
|
}
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1781
|
+
return current;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
// src/task/Task.ts
|
|
1785
|
+
import { deepEqual, EventEmitter as EventEmitter3, uuid4 as uuid43 } from "@workglow/util";
|
|
1786
|
+
import { compileSchema } from "@workglow/util/schema";
|
|
1787
|
+
|
|
1788
|
+
// src/task/TaskRunner.ts
|
|
1789
|
+
import {
|
|
1790
|
+
getLogger as getLogger2,
|
|
1791
|
+
getTelemetryProvider,
|
|
1792
|
+
globalServiceRegistry as globalServiceRegistry2,
|
|
1793
|
+
ResourceScope,
|
|
1794
|
+
SpanStatusCode
|
|
1795
|
+
} from "@workglow/util";
|
|
1796
|
+
|
|
1797
|
+
// src/task/CacheCoordinator.ts
|
|
1798
|
+
import { getPortCodec } from "@workglow/util";
|
|
1799
|
+
|
|
1800
|
+
class CacheCoordinator {
|
|
1801
|
+
task;
|
|
1802
|
+
constructor(task) {
|
|
1803
|
+
this.task = task;
|
|
1804
|
+
}
|
|
1805
|
+
async buildKey(inputs, outputCache) {
|
|
1806
|
+
if (!outputCache)
|
|
1807
|
+
return inputs;
|
|
1808
|
+
const inputSchema = this.task.constructor.inputSchema();
|
|
1809
|
+
return await CacheCoordinator.normalizeInputsForCacheKey(inputs, inputSchema);
|
|
1810
|
+
}
|
|
1811
|
+
async lookup(keyInputs, outputCache, isStreamable, ctx) {
|
|
1812
|
+
if (!outputCache || !this.task.cacheable)
|
|
1813
|
+
return;
|
|
1814
|
+
const cached = await outputCache.getOutput(this.task.type, keyInputs);
|
|
1815
|
+
if (cached === undefined)
|
|
1816
|
+
return;
|
|
1817
|
+
const outputSchema = this.task.constructor.outputSchema();
|
|
1818
|
+
const outputs = await CacheCoordinator.deserializeOutputPorts(cached, outputSchema);
|
|
1819
|
+
ctx.telemetrySpan?.addEvent("workglow.task.cache_hit");
|
|
1820
|
+
if (isStreamable) {
|
|
1821
|
+
this.task.runOutputData = outputs;
|
|
1822
|
+
this.task.emit("stream_start");
|
|
1823
|
+
this.task.emit("stream_chunk", { type: "finish", data: outputs });
|
|
1824
|
+
this.task.emit("stream_end", outputs);
|
|
1825
|
+
} else {
|
|
1826
|
+
this.task.runOutputData = outputs;
|
|
1628
1827
|
}
|
|
1629
|
-
|
|
1828
|
+
return outputs;
|
|
1630
1829
|
}
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1830
|
+
async save(keyInputs, output, outputCache) {
|
|
1831
|
+
if (!outputCache || !this.task.cacheable || output === undefined)
|
|
1832
|
+
return;
|
|
1833
|
+
const outputSchema = this.task.constructor.outputSchema();
|
|
1834
|
+
const wireOutputs = await CacheCoordinator.serializeOutputPorts(output, outputSchema);
|
|
1835
|
+
await outputCache.saveOutput(this.task.type, keyInputs, wireOutputs);
|
|
1836
|
+
}
|
|
1837
|
+
static async serializeOutputPorts(output, schema) {
|
|
1838
|
+
if (!schema?.properties)
|
|
1839
|
+
return output;
|
|
1840
|
+
const out = { ...output };
|
|
1841
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1842
|
+
const codec = prop.format ? getPortCodec(prop.format) : undefined;
|
|
1843
|
+
if (codec && out[key] !== undefined) {
|
|
1844
|
+
out[key] = await codec.serialize(out[key]);
|
|
1845
|
+
}
|
|
1640
1846
|
}
|
|
1641
|
-
|
|
1642
|
-
|
|
1847
|
+
return out;
|
|
1848
|
+
}
|
|
1849
|
+
static async deserializeOutputPorts(output, schema) {
|
|
1850
|
+
if (!schema?.properties)
|
|
1851
|
+
return output;
|
|
1852
|
+
const out = { ...output };
|
|
1853
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1854
|
+
const codec = prop.format ? getPortCodec(prop.format) : undefined;
|
|
1855
|
+
if (codec && out[key] !== undefined) {
|
|
1856
|
+
out[key] = await codec.deserialize(out[key]);
|
|
1857
|
+
}
|
|
1643
1858
|
}
|
|
1644
|
-
return
|
|
1859
|
+
return out;
|
|
1645
1860
|
}
|
|
1646
|
-
async
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1861
|
+
static async normalizeInputsForCacheKey(inputs, schema) {
|
|
1862
|
+
if (!schema?.properties)
|
|
1863
|
+
return inputs;
|
|
1864
|
+
const out = { ...inputs };
|
|
1865
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
1866
|
+
const codec = prop.format ? getPortCodec(prop.format) : undefined;
|
|
1867
|
+
if (codec && out[key] !== undefined) {
|
|
1868
|
+
out[key] = await codec.serialize(out[key]);
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return out;
|
|
1655
1872
|
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/task/StreamProcessor.ts
|
|
1876
|
+
class StreamProcessor {
|
|
1877
|
+
task;
|
|
1878
|
+
constructor(task) {
|
|
1879
|
+
this.task = task;
|
|
1658
1880
|
}
|
|
1659
|
-
async
|
|
1881
|
+
async run(input, ctx, deps) {
|
|
1660
1882
|
const streamMode = getOutputStreamMode(this.task.outputSchema());
|
|
1661
1883
|
if (streamMode === "append") {
|
|
1662
1884
|
const ports = getStreamingPorts(this.task.outputSchema());
|
|
@@ -1670,18 +1892,18 @@ class TaskRunner {
|
|
|
1670
1892
|
throw new TaskError(`Task ${this.task.type} declares object streaming but no output port has x-stream: "object"`);
|
|
1671
1893
|
}
|
|
1672
1894
|
}
|
|
1673
|
-
const accumulated =
|
|
1674
|
-
const accumulatedObjects =
|
|
1895
|
+
const accumulated = ctx.shouldAccumulate ? new Map : undefined;
|
|
1896
|
+
const accumulatedObjects = ctx.shouldAccumulate ? new Map : undefined;
|
|
1675
1897
|
let streamingStarted = false;
|
|
1676
1898
|
let finalOutput;
|
|
1677
1899
|
this.task.emit("stream_start");
|
|
1678
1900
|
const stream = this.task.executeStream(input, {
|
|
1679
|
-
signal:
|
|
1680
|
-
updateProgress:
|
|
1681
|
-
own:
|
|
1682
|
-
registry:
|
|
1683
|
-
resourceScope:
|
|
1684
|
-
inputStreams:
|
|
1901
|
+
signal: ctx.abortController.signal,
|
|
1902
|
+
updateProgress: deps.onProgress,
|
|
1903
|
+
own: deps.own,
|
|
1904
|
+
registry: deps.registry,
|
|
1905
|
+
resourceScope: deps.resourceScope,
|
|
1906
|
+
inputStreams: deps.inputStreams
|
|
1685
1907
|
});
|
|
1686
1908
|
for await (const event of stream) {
|
|
1687
1909
|
if (event.type === "snapshot") {
|
|
@@ -1690,7 +1912,7 @@ class TaskRunner {
|
|
|
1690
1912
|
switch (event.type) {
|
|
1691
1913
|
case "phase": {
|
|
1692
1914
|
this.task.emit("stream_chunk", event);
|
|
1693
|
-
await
|
|
1915
|
+
await deps.onProgress(event.progress, event.message);
|
|
1694
1916
|
break;
|
|
1695
1917
|
}
|
|
1696
1918
|
case "text-delta": {
|
|
@@ -1798,7 +2020,7 @@ class TaskRunner {
|
|
|
1798
2020
|
}
|
|
1799
2021
|
}
|
|
1800
2022
|
}
|
|
1801
|
-
if (
|
|
2023
|
+
if (ctx.abortController.signal.aborted) {
|
|
1802
2024
|
throw new TaskAbortedError("Task aborted during streaming");
|
|
1803
2025
|
}
|
|
1804
2026
|
if (finalOutput !== undefined) {
|
|
@@ -1807,19 +2029,296 @@ class TaskRunner {
|
|
|
1807
2029
|
this.task.emit("stream_end", this.task.runOutputData);
|
|
1808
2030
|
return this.task.runOutputData;
|
|
1809
2031
|
}
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
if (
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
// src/task/TaskRunContext.ts
|
|
2035
|
+
class TaskRunContext {
|
|
2036
|
+
abortController;
|
|
2037
|
+
shouldAccumulate = true;
|
|
2038
|
+
telemetrySpan;
|
|
2039
|
+
timeoutTimer;
|
|
2040
|
+
pendingTimeoutError;
|
|
2041
|
+
parentSignalCleanup;
|
|
2042
|
+
constructor(parentSignal) {
|
|
2043
|
+
this.abortController = new AbortController;
|
|
2044
|
+
if (parentSignal) {
|
|
2045
|
+
const onParentAbort = () => this.abortController.abort();
|
|
2046
|
+
parentSignal.addEventListener("abort", onParentAbort, { once: true });
|
|
2047
|
+
this.parentSignalCleanup = () => parentSignal.removeEventListener("abort", onParentAbort);
|
|
2048
|
+
if (parentSignal.aborted) {
|
|
2049
|
+
this.parentSignalCleanup();
|
|
2050
|
+
this.parentSignalCleanup = undefined;
|
|
2051
|
+
this.abortController.abort();
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
dispose() {
|
|
2056
|
+
this.parentSignalCleanup?.();
|
|
2057
|
+
this.parentSignalCleanup = undefined;
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
// src/task/TaskRunner.ts
|
|
2062
|
+
function hasRunConfig(i) {
|
|
2063
|
+
return i !== null && typeof i === "object" && "runConfig" in i;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
class TaskRunner {
|
|
2067
|
+
running = false;
|
|
2068
|
+
previewRunning = false;
|
|
2069
|
+
task;
|
|
2070
|
+
currentCtx;
|
|
2071
|
+
outputCache;
|
|
2072
|
+
cacheCoordinator;
|
|
2073
|
+
streamProcessor;
|
|
2074
|
+
registry = globalServiceRegistry2;
|
|
2075
|
+
resourceScope;
|
|
2076
|
+
ownsResourceScope = false;
|
|
2077
|
+
inputStreams;
|
|
2078
|
+
constructor(task) {
|
|
2079
|
+
this.task = task;
|
|
2080
|
+
this.own = this.own.bind(this);
|
|
2081
|
+
this.handleProgress = this.handleProgress.bind(this);
|
|
2082
|
+
this.cacheCoordinator = new CacheCoordinator(task);
|
|
2083
|
+
this.streamProcessor = new StreamProcessor(task);
|
|
2084
|
+
}
|
|
2085
|
+
async run(overrides = {}, config = {}) {
|
|
2086
|
+
if (this.task.status === TaskStatus.PROCESSING) {
|
|
2087
|
+
throw new TaskConfigurationError(`Task "${this.task.type}" is already running. Concurrent run() calls on the same TaskRunner are not supported.`);
|
|
2088
|
+
}
|
|
2089
|
+
const ownsScope = config.resourceScope === undefined;
|
|
2090
|
+
const effectiveConfig = ownsScope ? { ...config, resourceScope: new ResourceScope } : config;
|
|
2091
|
+
this.ownsResourceScope = ownsScope;
|
|
2092
|
+
try {
|
|
2093
|
+
await this.handleStart(effectiveConfig);
|
|
2094
|
+
const ctx = this.currentCtx;
|
|
2095
|
+
const proto = Object.getPrototypeOf(this.task);
|
|
2096
|
+
if (proto.execute === Task.prototype.execute && typeof proto.executeStream !== "function" && proto.executePreview !== Task.prototype.executePreview) {
|
|
2097
|
+
throw new TaskConfigurationError(`Task "${this.task.type}" implements only executePreview() and cannot be run via run(). ` + `After the run/runPreview split, run() requires execute() (or executeStream()). ` + `See docs/technical/02-dual-mode-execution.md.`);
|
|
2098
|
+
}
|
|
2099
|
+
try {
|
|
2100
|
+
this.task.setInput(overrides);
|
|
2101
|
+
await this.resolveSchemas();
|
|
2102
|
+
const inputs = this.task.runInputData;
|
|
2103
|
+
const isValid = await this.task.validateInput(inputs);
|
|
2104
|
+
if (!isValid) {
|
|
2105
|
+
throw new TaskInvalidInputError("Invalid input data");
|
|
2106
|
+
}
|
|
2107
|
+
if (ctx.abortController.signal.aborted) {
|
|
2108
|
+
await this.handleAbort();
|
|
2109
|
+
throw new TaskAbortedError("Promise for task created and aborted before run");
|
|
2110
|
+
}
|
|
2111
|
+
const isStreamable = isTaskStreamable(this.task);
|
|
2112
|
+
if (!isStreamable && typeof this.task.executeStream !== "function") {
|
|
2113
|
+
const streamMode = getOutputStreamMode(this.task.outputSchema());
|
|
2114
|
+
if (streamMode !== "none") {
|
|
2115
|
+
getLogger2().warn(`Task "${this.task.type}" declares streaming output (x-stream: "${streamMode}") ` + `but does not implement executeStream(). Falling back to non-streaming execute().`);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
const keyInputs = await this.cacheCoordinator.buildKey(inputs, this.outputCache);
|
|
2119
|
+
let outputs = await this.cacheCoordinator.lookup(keyInputs, this.outputCache, isStreamable, ctx);
|
|
2120
|
+
if (outputs === undefined) {
|
|
2121
|
+
outputs = isStreamable ? await this.streamProcessor.run(inputs, ctx, {
|
|
2122
|
+
registry: this.registry,
|
|
2123
|
+
resourceScope: this.resourceScope,
|
|
2124
|
+
inputStreams: this.inputStreams,
|
|
2125
|
+
onProgress: this.handleProgress.bind(this),
|
|
2126
|
+
own: this.own
|
|
2127
|
+
}) : await this.executeTask(inputs, ctx);
|
|
2128
|
+
await this.cacheCoordinator.save(keyInputs, outputs, this.outputCache);
|
|
2129
|
+
this.task.runOutputData = outputs ?? {};
|
|
2130
|
+
}
|
|
2131
|
+
await this.handleComplete();
|
|
2132
|
+
return this.task.runOutputData;
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
await this.handleError(err);
|
|
2135
|
+
throw this.task.error instanceof TaskTimeoutError ? this.task.error : err;
|
|
2136
|
+
}
|
|
2137
|
+
} finally {
|
|
2138
|
+
if (ownsScope) {
|
|
2139
|
+
await effectiveConfig.resourceScope.disposeAll();
|
|
2140
|
+
this.resourceScope = undefined;
|
|
2141
|
+
}
|
|
2142
|
+
this.ownsResourceScope = false;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
async runPreview(overrides = {}) {
|
|
2146
|
+
if (this.task.status === TaskStatus.PROCESSING) {
|
|
2147
|
+
return this.task.runOutputData;
|
|
2148
|
+
}
|
|
2149
|
+
this.task.setInput(overrides);
|
|
2150
|
+
await this.resolveSchemas();
|
|
2151
|
+
await this.handleStartPreview();
|
|
2152
|
+
const ctx = new TaskRunContext;
|
|
2153
|
+
try {
|
|
2154
|
+
const inputs = this.task.runInputData;
|
|
2155
|
+
const isValid = await this.task.validateInput(inputs);
|
|
2156
|
+
if (!isValid) {
|
|
2157
|
+
throw new TaskInvalidInputError("Invalid input data");
|
|
2158
|
+
}
|
|
2159
|
+
const resultPreview = await this.executeTaskPreview(inputs, ctx);
|
|
2160
|
+
if (resultPreview !== undefined) {
|
|
2161
|
+
this.task.runOutputData = resultPreview;
|
|
2162
|
+
}
|
|
2163
|
+
await this.handleCompletePreview();
|
|
2164
|
+
} catch (err) {
|
|
2165
|
+
getLogger2().debug("runPreview failed", { taskId: this.task.config?.id, error: err });
|
|
2166
|
+
await this.handleErrorPreview();
|
|
2167
|
+
} finally {
|
|
2168
|
+
ctx.dispose();
|
|
2169
|
+
return this.task.runOutputData;
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
async* runPreviewStream(overrides = {}) {
|
|
2173
|
+
const graph = this.task.parentGraph;
|
|
2174
|
+
const dataflowInfos = [];
|
|
2175
|
+
if (graph) {
|
|
2176
|
+
for (const df of graph.getSourceDataflows(this.task.id)) {
|
|
2177
|
+
const upstream = graph.getTask(df.sourceTaskId);
|
|
2178
|
+
if (upstream) {
|
|
2179
|
+
dataflowInfos.push({
|
|
2180
|
+
upstream,
|
|
2181
|
+
sourcePort: df.sourceTaskPortId,
|
|
2182
|
+
targetPort: df.targetTaskPortId
|
|
2183
|
+
});
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
const upstreamTasks = new Set(dataflowInfos.map((d) => d.upstream));
|
|
2188
|
+
const pendingUpstreams = new Set([...upstreamTasks].filter((u) => u.status === TaskStatus.STREAMING || u.status === TaskStatus.PENDING || u.status === TaskStatus.PROCESSING));
|
|
2189
|
+
let dirty = true;
|
|
2190
|
+
let wakeResolve;
|
|
2191
|
+
const wakeNext = () => new Promise((resolve) => {
|
|
2192
|
+
wakeResolve = resolve;
|
|
2193
|
+
});
|
|
2194
|
+
const wake = () => {
|
|
2195
|
+
const r = wakeResolve;
|
|
2196
|
+
wakeResolve = undefined;
|
|
2197
|
+
if (r)
|
|
2198
|
+
r();
|
|
2199
|
+
};
|
|
2200
|
+
const cleanupFns = [];
|
|
2201
|
+
for (const upstream of pendingUpstreams) {
|
|
2202
|
+
const myDataflows = dataflowInfos.filter((d) => d.upstream === upstream);
|
|
2203
|
+
const onChunk = (event) => {
|
|
2204
|
+
if (event.type !== "snapshot")
|
|
2205
|
+
return;
|
|
2206
|
+
const snapshotData = event.data;
|
|
2207
|
+
if (snapshotData) {
|
|
2208
|
+
for (const { sourcePort, targetPort } of myDataflows) {
|
|
2209
|
+
const value = sourcePort === "*" ? snapshotData : snapshotData[sourcePort];
|
|
2210
|
+
if (value !== undefined) {
|
|
2211
|
+
this.task.runInputData[targetPort] = value;
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
dirty = true;
|
|
2216
|
+
wake();
|
|
2217
|
+
};
|
|
2218
|
+
const onEnd = () => {
|
|
2219
|
+
pendingUpstreams.delete(upstream);
|
|
2220
|
+
wake();
|
|
2221
|
+
};
|
|
2222
|
+
const onStatus = (status) => {
|
|
2223
|
+
if (status === TaskStatus.COMPLETED || status === TaskStatus.FAILED || status === TaskStatus.DISABLED) {
|
|
2224
|
+
pendingUpstreams.delete(upstream);
|
|
2225
|
+
wake();
|
|
2226
|
+
}
|
|
2227
|
+
};
|
|
2228
|
+
upstream.on("stream_chunk", onChunk);
|
|
2229
|
+
upstream.on("stream_end", onEnd);
|
|
2230
|
+
upstream.on("status", onStatus);
|
|
2231
|
+
cleanupFns.push(() => {
|
|
2232
|
+
upstream.off("stream_chunk", onChunk);
|
|
2233
|
+
upstream.off("stream_end", onEnd);
|
|
2234
|
+
upstream.off("status", onStatus);
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
for (const upstream of [...pendingUpstreams]) {
|
|
2238
|
+
if (upstream.status === TaskStatus.COMPLETED || upstream.status === TaskStatus.FAILED || upstream.status === TaskStatus.DISABLED) {
|
|
2239
|
+
pendingUpstreams.delete(upstream);
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
try {
|
|
2243
|
+
while (true) {
|
|
2244
|
+
if (dirty) {
|
|
2245
|
+
dirty = false;
|
|
2246
|
+
try {
|
|
2247
|
+
const out = await this.runPreview(overrides);
|
|
2248
|
+
yield out;
|
|
2249
|
+
} catch (err) {
|
|
2250
|
+
getLogger2().debug("runPreviewStream iteration failed", {
|
|
2251
|
+
taskId: this.task.config?.id,
|
|
2252
|
+
error: err
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
continue;
|
|
2256
|
+
}
|
|
2257
|
+
if (pendingUpstreams.size === 0)
|
|
2258
|
+
return;
|
|
2259
|
+
await wakeNext();
|
|
2260
|
+
}
|
|
2261
|
+
} finally {
|
|
2262
|
+
for (const off of cleanupFns)
|
|
2263
|
+
off();
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
abort() {
|
|
2267
|
+
this.currentCtx?.abortController.abort();
|
|
2268
|
+
}
|
|
2269
|
+
own(i) {
|
|
2270
|
+
const task = ensureTask(i, { isOwned: true });
|
|
2271
|
+
this.task.subGraph.addTask(task);
|
|
2272
|
+
if (hasRunConfig(i)) {
|
|
2273
|
+
const stamp = {
|
|
2274
|
+
registry: this.registry,
|
|
2275
|
+
signal: this.currentCtx?.abortController.signal
|
|
2276
|
+
};
|
|
2277
|
+
if (!this.ownsResourceScope) {
|
|
2278
|
+
stamp.resourceScope = this.resourceScope;
|
|
2279
|
+
}
|
|
2280
|
+
Object.assign(i.runConfig, stamp);
|
|
2281
|
+
}
|
|
2282
|
+
if (this.task.constructor.hasDynamicEntitlements) {
|
|
2283
|
+
this.task.emit("entitlementChange", this.task.entitlements());
|
|
2284
|
+
}
|
|
2285
|
+
return i;
|
|
2286
|
+
}
|
|
2287
|
+
async executeTask(input, ctx) {
|
|
2288
|
+
const result = await this.task.execute(input, {
|
|
2289
|
+
signal: ctx.abortController.signal,
|
|
2290
|
+
updateProgress: this.handleProgress.bind(this),
|
|
2291
|
+
own: this.own,
|
|
2292
|
+
registry: this.registry,
|
|
2293
|
+
resourceScope: this.resourceScope
|
|
2294
|
+
});
|
|
2295
|
+
return result;
|
|
2296
|
+
}
|
|
2297
|
+
async executeTaskPreview(input, _ctx) {
|
|
2298
|
+
return this.task.executePreview?.(input, { own: this.own });
|
|
2299
|
+
}
|
|
2300
|
+
async resolveSchemas() {
|
|
2301
|
+
const configSchema = this.task.constructor.configSchema();
|
|
2302
|
+
if (schemaHasFormatAnnotations(configSchema)) {
|
|
2303
|
+
const source = this.task.originalConfig ?? this.task.config;
|
|
2304
|
+
const resolved = await resolveSchemaInputs({ ...source }, configSchema, { registry: this.registry });
|
|
2305
|
+
Object.assign(this.task.config, resolved);
|
|
2306
|
+
}
|
|
2307
|
+
const schema = this.task.constructor.inputSchema();
|
|
2308
|
+
this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
|
|
2309
|
+
}
|
|
2310
|
+
async handleStart(config = {}) {
|
|
2311
|
+
this.running = true;
|
|
2312
|
+
this.task.startedAt = new Date;
|
|
2313
|
+
this.task.progress = 0;
|
|
2314
|
+
this.task.status = TaskStatus.PROCESSING;
|
|
2315
|
+
const ctx = new TaskRunContext(config.signal);
|
|
2316
|
+
this.currentCtx = ctx;
|
|
2317
|
+
ctx.abortController.signal.addEventListener("abort", () => {
|
|
2318
|
+
this.handleAbort();
|
|
2319
|
+
});
|
|
2320
|
+
const cache = config.outputCache ?? this.task.runConfig?.outputCache;
|
|
2321
|
+
if (cache === true) {
|
|
1823
2322
|
let instance = globalServiceRegistry2.get(TASK_OUTPUT_REPOSITORY);
|
|
1824
2323
|
this.outputCache = instance;
|
|
1825
2324
|
} else if (cache === false) {
|
|
@@ -1827,7 +2326,7 @@ class TaskRunner {
|
|
|
1827
2326
|
} else if (cache instanceof TaskOutputRepository) {
|
|
1828
2327
|
this.outputCache = cache;
|
|
1829
2328
|
}
|
|
1830
|
-
|
|
2329
|
+
ctx.shouldAccumulate = config.shouldAccumulate !== false;
|
|
1831
2330
|
if (config.updateProgress) {
|
|
1832
2331
|
this.updateProgress = config.updateProgress;
|
|
1833
2332
|
}
|
|
@@ -1837,25 +2336,18 @@ class TaskRunner {
|
|
|
1837
2336
|
if (config.resourceScope) {
|
|
1838
2337
|
this.resourceScope = config.resourceScope;
|
|
1839
2338
|
}
|
|
1840
|
-
if (
|
|
1841
|
-
|
|
1842
|
-
config.signal.addEventListener("abort", onAbort, { once: true });
|
|
1843
|
-
if (config.signal.aborted) {
|
|
1844
|
-
config.signal.removeEventListener("abort", onAbort);
|
|
1845
|
-
this.abortController.abort();
|
|
1846
|
-
return;
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
2339
|
+
if (ctx.abortController.signal.aborted)
|
|
2340
|
+
return;
|
|
1849
2341
|
const timeout = this.task.config.timeout;
|
|
1850
2342
|
if (timeout !== undefined && timeout > 0) {
|
|
1851
|
-
|
|
1852
|
-
|
|
2343
|
+
ctx.pendingTimeoutError = new TaskTimeoutError(timeout);
|
|
2344
|
+
ctx.timeoutTimer = setTimeout(() => {
|
|
1853
2345
|
this.abort();
|
|
1854
2346
|
}, timeout);
|
|
1855
2347
|
}
|
|
1856
2348
|
const telemetry = getTelemetryProvider();
|
|
1857
2349
|
if (telemetry.isEnabled) {
|
|
1858
|
-
|
|
2350
|
+
ctx.telemetrySpan = telemetry.startSpan("workglow.task.run", {
|
|
1859
2351
|
attributes: {
|
|
1860
2352
|
"workglow.task.type": this.task.type,
|
|
1861
2353
|
"workglow.task.id": String(this.task.config.id),
|
|
@@ -1872,32 +2364,34 @@ class TaskRunner {
|
|
|
1872
2364
|
this.previewRunning = true;
|
|
1873
2365
|
}
|
|
1874
2366
|
clearTimeoutTimer() {
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2367
|
+
const ctx = this.currentCtx;
|
|
2368
|
+
if (ctx?.timeoutTimer !== undefined) {
|
|
2369
|
+
clearTimeout(ctx.timeoutTimer);
|
|
2370
|
+
ctx.timeoutTimer = undefined;
|
|
1878
2371
|
}
|
|
1879
2372
|
}
|
|
1880
2373
|
async handleAbort() {
|
|
1881
2374
|
if (this.task.status === TaskStatus.ABORTING)
|
|
1882
2375
|
return;
|
|
1883
2376
|
this.clearTimeoutTimer();
|
|
2377
|
+
const ctx = this.currentCtx;
|
|
1884
2378
|
this.task.status = TaskStatus.ABORTING;
|
|
1885
2379
|
await this.handleProgress(100);
|
|
1886
|
-
this.task.error =
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
this.telemetrySpan.addEvent("workglow.task.aborted", {
|
|
2380
|
+
this.task.error = ctx?.pendingTimeoutError ?? new TaskAbortedError;
|
|
2381
|
+
if (ctx?.telemetrySpan) {
|
|
2382
|
+
ctx.telemetrySpan.setStatus(SpanStatusCode.ERROR, "aborted");
|
|
2383
|
+
ctx.telemetrySpan.addEvent("workglow.task.aborted", {
|
|
1891
2384
|
"workglow.task.error": this.task.error.message
|
|
1892
2385
|
});
|
|
1893
|
-
|
|
1894
|
-
this.telemetrySpan = undefined;
|
|
2386
|
+
ctx.telemetrySpan.end();
|
|
1895
2387
|
}
|
|
1896
2388
|
if (typeof this.task.cleanup === "function") {
|
|
1897
2389
|
try {
|
|
1898
2390
|
await this.task.cleanup();
|
|
1899
2391
|
} catch {}
|
|
1900
2392
|
}
|
|
2393
|
+
ctx?.dispose();
|
|
2394
|
+
this.currentCtx = undefined;
|
|
1901
2395
|
this.task.emit("abort", this.task.error);
|
|
1902
2396
|
this.task.emit("status", this.task.status);
|
|
1903
2397
|
}
|
|
@@ -1908,34 +2402,36 @@ class TaskRunner {
|
|
|
1908
2402
|
if (this.task.status === TaskStatus.COMPLETED)
|
|
1909
2403
|
return;
|
|
1910
2404
|
this.clearTimeoutTimer();
|
|
1911
|
-
|
|
2405
|
+
const ctx = this.currentCtx;
|
|
1912
2406
|
this.task.completedAt = new Date;
|
|
1913
2407
|
this.task.status = TaskStatus.COMPLETED;
|
|
1914
|
-
this.abortController = undefined;
|
|
1915
2408
|
await this.handleProgress(100);
|
|
1916
|
-
if (
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
this.telemetrySpan = undefined;
|
|
2409
|
+
if (ctx?.telemetrySpan) {
|
|
2410
|
+
ctx.telemetrySpan.setStatus(SpanStatusCode.OK);
|
|
2411
|
+
ctx.telemetrySpan.end();
|
|
1920
2412
|
}
|
|
2413
|
+
ctx?.dispose();
|
|
2414
|
+
this.currentCtx = undefined;
|
|
1921
2415
|
this.task.emit("complete");
|
|
1922
2416
|
this.task.emit("status", this.task.status);
|
|
1923
2417
|
}
|
|
1924
2418
|
async handleCompletePreview() {
|
|
1925
2419
|
this.previewRunning = false;
|
|
1926
2420
|
}
|
|
1927
|
-
async handleDisable() {
|
|
2421
|
+
async handleDisable(ctx) {
|
|
1928
2422
|
if (this.task.status === TaskStatus.DISABLED)
|
|
1929
2423
|
return;
|
|
1930
2424
|
this.task.status = TaskStatus.DISABLED;
|
|
1931
2425
|
await this.handleProgress(100);
|
|
1932
2426
|
this.task.completedAt = new Date;
|
|
1933
|
-
|
|
2427
|
+
ctx?.dispose();
|
|
2428
|
+
if (this.currentCtx === ctx)
|
|
2429
|
+
this.currentCtx = undefined;
|
|
1934
2430
|
this.task.emit("disabled");
|
|
1935
2431
|
this.task.emit("status", this.task.status);
|
|
1936
2432
|
}
|
|
1937
2433
|
async disable() {
|
|
1938
|
-
await this.handleDisable();
|
|
2434
|
+
await this.handleDisable(this.currentCtx);
|
|
1939
2435
|
}
|
|
1940
2436
|
async handleError(err) {
|
|
1941
2437
|
if (err instanceof TaskAbortedError)
|
|
@@ -1943,7 +2439,7 @@ class TaskRunner {
|
|
|
1943
2439
|
if (this.task.status === TaskStatus.FAILED)
|
|
1944
2440
|
return;
|
|
1945
2441
|
this.clearTimeoutTimer();
|
|
1946
|
-
|
|
2442
|
+
const ctx = this.currentCtx;
|
|
1947
2443
|
if (this.task.hasChildren()) {
|
|
1948
2444
|
this.task.subGraph.abort();
|
|
1949
2445
|
}
|
|
@@ -1958,14 +2454,14 @@ class TaskRunner {
|
|
|
1958
2454
|
this.task.error.taskId ??= this.task.id;
|
|
1959
2455
|
}
|
|
1960
2456
|
this.task.status = TaskStatus.FAILED;
|
|
1961
|
-
this.abortController = undefined;
|
|
1962
2457
|
await this.handleProgress(100);
|
|
1963
|
-
if (
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
this.telemetrySpan = undefined;
|
|
2458
|
+
if (ctx?.telemetrySpan) {
|
|
2459
|
+
ctx.telemetrySpan.setStatus(SpanStatusCode.ERROR, this.task.error.message);
|
|
2460
|
+
ctx.telemetrySpan.setAttributes({ "workglow.task.error": this.task.error.message });
|
|
2461
|
+
ctx.telemetrySpan.end();
|
|
1968
2462
|
}
|
|
2463
|
+
ctx?.dispose();
|
|
2464
|
+
this.currentCtx = undefined;
|
|
1969
2465
|
this.task.emit("error", this.task.error);
|
|
1970
2466
|
this.task.emit("status", this.task.status);
|
|
1971
2467
|
}
|
|
@@ -2105,7 +2601,7 @@ class Task {
|
|
|
2105
2601
|
...title ? { title } : {}
|
|
2106
2602
|
}, restConfig);
|
|
2107
2603
|
if (baseConfig.id === undefined) {
|
|
2108
|
-
baseConfig.id =
|
|
2604
|
+
baseConfig.id = uuid43();
|
|
2109
2605
|
}
|
|
2110
2606
|
this.config = this.validateAndApplyConfigDefaults(baseConfig);
|
|
2111
2607
|
try {
|
|
@@ -2587,7 +3083,7 @@ class ConditionalTask extends Task {
|
|
|
2587
3083
|
}
|
|
2588
3084
|
}
|
|
2589
3085
|
} catch (error) {
|
|
2590
|
-
|
|
3086
|
+
getLogger3().error(`Condition evaluation failed for branch "${branch.id}":`, { error });
|
|
2591
3087
|
}
|
|
2592
3088
|
}
|
|
2593
3089
|
if (this.activeBranches.size === 0 && defaultBranch) {
|
|
@@ -2711,215 +3207,420 @@ class ConditionalTask extends Task {
|
|
|
2711
3207
|
}
|
|
2712
3208
|
}
|
|
2713
3209
|
|
|
2714
|
-
// src/task/
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
function ruleCovers(rule, required) {
|
|
2724
|
-
if (!entitlementCovers(rule.id, required.id))
|
|
2725
|
-
return false;
|
|
2726
|
-
return grantCoversResources(rule, required);
|
|
2727
|
-
}
|
|
2728
|
-
function evaluatePolicy(policy, required) {
|
|
2729
|
-
const results = [];
|
|
2730
|
-
for (const entitlement of required.entitlements) {
|
|
2731
|
-
if (entitlement.optional)
|
|
2732
|
-
continue;
|
|
2733
|
-
const denyMatch = policy.deny.find((rule) => ruleCovers(rule, entitlement));
|
|
2734
|
-
if (denyMatch) {
|
|
2735
|
-
results.push({ verdict: "denied", entitlement, matchedRule: denyMatch });
|
|
2736
|
-
continue;
|
|
2737
|
-
}
|
|
2738
|
-
const grantMatch = policy.grant.find((rule) => ruleCovers(rule, entitlement));
|
|
2739
|
-
if (grantMatch) {
|
|
2740
|
-
results.push({ verdict: "granted", entitlement, matchedRule: grantMatch });
|
|
2741
|
-
continue;
|
|
2742
|
-
}
|
|
2743
|
-
const askMatch = policy.ask.find((rule) => ruleCovers(rule, entitlement));
|
|
2744
|
-
if (askMatch) {
|
|
2745
|
-
results.push({ verdict: "ask", entitlement, matchedRule: askMatch });
|
|
2746
|
-
continue;
|
|
2747
|
-
}
|
|
2748
|
-
results.push({ verdict: "denied", entitlement });
|
|
3210
|
+
// src/task-graph/RunScheduler.ts
|
|
3211
|
+
class RunScheduler {
|
|
3212
|
+
graph;
|
|
3213
|
+
processScheduler;
|
|
3214
|
+
facade;
|
|
3215
|
+
constructor(graph, processScheduler, facade) {
|
|
3216
|
+
this.graph = graph;
|
|
3217
|
+
this.processScheduler = processScheduler;
|
|
3218
|
+
this.facade = facade;
|
|
2749
3219
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
// src/task/EntitlementEnforcer.ts
|
|
2773
|
-
function formatEntitlementDenial(denial) {
|
|
2774
|
-
switch (denial.reason) {
|
|
2775
|
-
case "policy-deny":
|
|
2776
|
-
return `${denial.entitlement.id} (denied by rule ${denial.matchedRule.id})`;
|
|
2777
|
-
case "user-deny":
|
|
2778
|
-
return `${denial.entitlement.id} (denied by user)`;
|
|
2779
|
-
case "default-deny":
|
|
2780
|
-
return `${denial.entitlement.id} (no matching grant)`;
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
2783
|
-
var PERMISSIVE_ENFORCER = {
|
|
2784
|
-
checkAll: async () => [],
|
|
2785
|
-
checkTask: async () => []
|
|
2786
|
-
};
|
|
2787
|
-
function createPolicyEnforcer(policy, resolver = PERMISSIVE_RESOLVER) {
|
|
2788
|
-
async function resolveAsks(required, taskType, taskId) {
|
|
2789
|
-
const results = evaluatePolicy(policy, required);
|
|
2790
|
-
const denied = [];
|
|
2791
|
-
for (const result of results) {
|
|
2792
|
-
if (result.verdict === "denied") {
|
|
2793
|
-
if (result.matchedRule) {
|
|
2794
|
-
denied.push({
|
|
2795
|
-
entitlement: result.entitlement,
|
|
2796
|
-
reason: "policy-deny",
|
|
2797
|
-
matchedRule: result.matchedRule
|
|
2798
|
-
});
|
|
3220
|
+
pushStatusFromNodeToEdges(node, ctx, status, graph = this.graph) {
|
|
3221
|
+
if (!node?.config?.id)
|
|
3222
|
+
return;
|
|
3223
|
+
const dataflows = graph.getTargetDataflows(node.id);
|
|
3224
|
+
const effectiveStatus = status ?? node.status;
|
|
3225
|
+
if (node instanceof ConditionalTask && effectiveStatus === TaskStatus.COMPLETED) {
|
|
3226
|
+
const branches = node.config.branches ?? [];
|
|
3227
|
+
const portToBranch = new Map;
|
|
3228
|
+
for (const branch of branches) {
|
|
3229
|
+
portToBranch.set(branch.outputPort, branch.id);
|
|
3230
|
+
}
|
|
3231
|
+
const activeBranches = node.getActiveBranches();
|
|
3232
|
+
for (const dataflow of dataflows) {
|
|
3233
|
+
if (dataflow.status === TaskStatus.FAILED)
|
|
3234
|
+
continue;
|
|
3235
|
+
const branchId = portToBranch.get(dataflow.sourceTaskPortId);
|
|
3236
|
+
if (branchId !== undefined) {
|
|
3237
|
+
if (activeBranches.has(branchId)) {
|
|
3238
|
+
dataflow.setStatus(TaskStatus.COMPLETED);
|
|
3239
|
+
} else {
|
|
3240
|
+
dataflow.setStatus(TaskStatus.DISABLED);
|
|
3241
|
+
}
|
|
2799
3242
|
} else {
|
|
2800
|
-
|
|
3243
|
+
dataflow.setStatus(effectiveStatus);
|
|
2801
3244
|
}
|
|
2802
|
-
}
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
3245
|
+
}
|
|
3246
|
+
this.propagateDisabledStatus(ctx, graph);
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
dataflows.forEach((dataflow) => {
|
|
3250
|
+
if (dataflow.status === TaskStatus.FAILED)
|
|
3251
|
+
return;
|
|
3252
|
+
dataflow.setStatus(effectiveStatus);
|
|
3253
|
+
});
|
|
3254
|
+
}
|
|
3255
|
+
propagateDisabledStatus(_ctx, graph = this.graph) {
|
|
3256
|
+
let changed = true;
|
|
3257
|
+
while (changed) {
|
|
3258
|
+
changed = false;
|
|
3259
|
+
for (const task of graph.getTasks()) {
|
|
3260
|
+
if (task.status !== TaskStatus.PENDING) {
|
|
3261
|
+
continue;
|
|
2812
3262
|
}
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
3263
|
+
const incomingDataflows = graph.getSourceDataflows(task.id);
|
|
3264
|
+
if (incomingDataflows.length === 0) {
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3267
|
+
const allDisabled = incomingDataflows.every((df) => df.status === TaskStatus.DISABLED);
|
|
3268
|
+
if (allDisabled) {
|
|
3269
|
+
task.status = TaskStatus.DISABLED;
|
|
3270
|
+
task.progress = 100;
|
|
3271
|
+
task.completedAt = new Date;
|
|
3272
|
+
task.emit("disabled");
|
|
3273
|
+
task.emit("status", task.status);
|
|
3274
|
+
graph.getTargetDataflows(task.id).forEach((dataflow) => {
|
|
3275
|
+
dataflow.setStatus(TaskStatus.DISABLED);
|
|
2821
3276
|
});
|
|
3277
|
+
this.processScheduler.onTaskCompleted(task.id);
|
|
3278
|
+
changed = true;
|
|
2822
3279
|
}
|
|
2823
3280
|
}
|
|
2824
3281
|
}
|
|
2825
|
-
return denied;
|
|
2826
3282
|
}
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
3283
|
+
async handleProgress(ctx, task, progress, message, ...args) {
|
|
3284
|
+
const contributors = this.graph.getTasks().filter(taskPrototypeHasOwnExecute);
|
|
3285
|
+
if (contributors.length > 1) {
|
|
3286
|
+
const determinate = contributors.filter((t) => t.progress !== undefined);
|
|
3287
|
+
if (determinate.length === 0) {
|
|
3288
|
+
progress = undefined;
|
|
3289
|
+
} else {
|
|
3290
|
+
const sum = determinate.reduce((acc, t) => acc + t.progress, 0);
|
|
3291
|
+
progress = Math.round(sum / determinate.length);
|
|
3292
|
+
}
|
|
3293
|
+
} else if (contributors.length === 1) {
|
|
3294
|
+
const [only] = contributors;
|
|
3295
|
+
progress = only.progress;
|
|
3296
|
+
}
|
|
3297
|
+
this.pushStatusFromNodeToEdges(task, ctx);
|
|
3298
|
+
this.graph.emit("graph_progress", progress, message, args);
|
|
3299
|
+
const isActive = task.status === TaskStatus.PROCESSING || task.status === TaskStatus.STREAMING;
|
|
3300
|
+
if (isActive && task.runOutputData && Object.keys(task.runOutputData).length > 0) {
|
|
3301
|
+
await this.facade["edgeMaterializer"].pushOutputFromNodeToEdges(task, task.runOutputData);
|
|
2834
3302
|
}
|
|
2835
|
-
};
|
|
2836
|
-
}
|
|
2837
|
-
function createScopedEnforcer(grants) {
|
|
2838
|
-
return createPolicyEnforcer({ deny: [], grant: grants, ask: [] });
|
|
2839
|
-
}
|
|
2840
|
-
function createGrantListEnforcer(grants) {
|
|
2841
|
-
return createScopedEnforcer(grants.map((id) => ({ id })));
|
|
2842
|
-
}
|
|
2843
|
-
var ENTITLEMENT_ENFORCER = createServiceToken4("workglow.entitlementEnforcer");
|
|
2844
|
-
|
|
2845
|
-
// src/task-graph/TaskGraphScheduler.ts
|
|
2846
|
-
class TopologicalScheduler {
|
|
2847
|
-
dag;
|
|
2848
|
-
sortedNodes;
|
|
2849
|
-
currentIndex;
|
|
2850
|
-
constructor(dag) {
|
|
2851
|
-
this.dag = dag;
|
|
2852
|
-
this.sortedNodes = [];
|
|
2853
|
-
this.currentIndex = 0;
|
|
2854
|
-
this.reset();
|
|
2855
3303
|
}
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
3304
|
+
armGraphTimeout(timeoutMs, ctx) {
|
|
3305
|
+
if (timeoutMs <= 0)
|
|
3306
|
+
return;
|
|
3307
|
+
ctx.pendingGraphTimeoutError = undefined;
|
|
3308
|
+
ctx.graphTimeoutTimer = setTimeout(() => {
|
|
3309
|
+
ctx.pendingGraphTimeoutError = new TaskGraphTimeoutError(timeoutMs);
|
|
3310
|
+
ctx.abortController.abort();
|
|
3311
|
+
}, timeoutMs);
|
|
3312
|
+
}
|
|
3313
|
+
clearGraphTimeout(ctx) {
|
|
3314
|
+
if (ctx.graphTimeoutTimer !== undefined) {
|
|
3315
|
+
clearTimeout(ctx.graphTimeoutTimer);
|
|
3316
|
+
ctx.graphTimeoutTimer = undefined;
|
|
2859
3317
|
}
|
|
2860
3318
|
}
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
3319
|
+
async runLoop(input, config, ctx, edgeMat) {
|
|
3320
|
+
const results = [];
|
|
3321
|
+
try {
|
|
3322
|
+
for await (const task of this.processScheduler.tasks()) {
|
|
3323
|
+
if (ctx.abortController.signal.aborted) {
|
|
3324
|
+
break;
|
|
3325
|
+
}
|
|
3326
|
+
if (ctx.failedTaskErrors.size > 0) {
|
|
3327
|
+
break;
|
|
3328
|
+
}
|
|
3329
|
+
const isRootTask = this.graph.getSourceDataflows(task.id).length === 0;
|
|
3330
|
+
const runAsync = async () => {
|
|
3331
|
+
let errorRouted = false;
|
|
3332
|
+
try {
|
|
3333
|
+
const taskInput = isRootTask ? input : config?.matchAllEmptyInputs ? edgeMat.filterInputForTask(task, input) : {};
|
|
3334
|
+
const taskPromise = this.facade["runTask"](task, taskInput);
|
|
3335
|
+
ctx.inProgressTasks.set(task.id, taskPromise);
|
|
3336
|
+
const taskResult = await taskPromise;
|
|
3337
|
+
if (this.graph.getTargetDataflows(task.id).length === 0) {
|
|
3338
|
+
results.push(taskResult);
|
|
3339
|
+
}
|
|
3340
|
+
} catch (error) {
|
|
3341
|
+
if (edgeMat.hasErrorOutputEdges(task)) {
|
|
3342
|
+
errorRouted = true;
|
|
3343
|
+
edgeMat.pushErrorOutputToEdges(task);
|
|
3344
|
+
} else {
|
|
3345
|
+
ctx.failedTaskErrors.set(task.id, error);
|
|
3346
|
+
}
|
|
3347
|
+
} finally {
|
|
3348
|
+
if (!errorRouted) {
|
|
3349
|
+
this.pushStatusFromNodeToEdges(task, ctx);
|
|
3350
|
+
edgeMat.pushErrorFromNodeToEdges(task);
|
|
3351
|
+
}
|
|
3352
|
+
this.processScheduler.onTaskCompleted(task.id);
|
|
3353
|
+
}
|
|
3354
|
+
};
|
|
3355
|
+
ctx.inProgressFunctions.set(Symbol(task.id), runAsync());
|
|
3356
|
+
}
|
|
3357
|
+
} catch (err) {
|
|
3358
|
+
getLogger4().error("Error running graph", { error: err });
|
|
3359
|
+
}
|
|
3360
|
+
await Promise.allSettled(Array.from(ctx.inProgressTasks.values()));
|
|
3361
|
+
await Promise.allSettled(Array.from(ctx.inProgressFunctions.values()));
|
|
3362
|
+
return results;
|
|
2866
3363
|
}
|
|
2867
3364
|
}
|
|
2868
3365
|
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
constructor(
|
|
2876
|
-
this.
|
|
2877
|
-
this.
|
|
2878
|
-
this.
|
|
2879
|
-
this.pendingTasks = new Set;
|
|
2880
|
-
this.reset();
|
|
3366
|
+
// src/task-graph/StreamPump.ts
|
|
3367
|
+
class StreamPump {
|
|
3368
|
+
graph;
|
|
3369
|
+
processScheduler;
|
|
3370
|
+
edgeMaterializer;
|
|
3371
|
+
runScheduler;
|
|
3372
|
+
constructor(graph, processScheduler, edgeMaterializer) {
|
|
3373
|
+
this.graph = graph;
|
|
3374
|
+
this.processScheduler = processScheduler;
|
|
3375
|
+
this.edgeMaterializer = edgeMaterializer;
|
|
2881
3376
|
}
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
return false;
|
|
2885
|
-
}
|
|
2886
|
-
const sourceDataflows = this.dag.getSourceDataflows(task.id);
|
|
2887
|
-
if (sourceDataflows.length > 0) {
|
|
2888
|
-
const allIncomingDisabled = sourceDataflows.every((df) => df.status === TaskStatus.DISABLED);
|
|
2889
|
-
if (allIncomingDisabled) {
|
|
2890
|
-
return false;
|
|
2891
|
-
}
|
|
2892
|
-
}
|
|
2893
|
-
const activeDataflows = sourceDataflows.filter((df) => df.status !== TaskStatus.DISABLED);
|
|
2894
|
-
return activeDataflows.every((df) => {
|
|
2895
|
-
const depId = df.sourceTaskId;
|
|
2896
|
-
if (this.completedTasks.has(depId))
|
|
2897
|
-
return true;
|
|
2898
|
-
if (this.streamingTasks.has(depId)) {
|
|
2899
|
-
const sourceTask = this.dag.getTask(depId);
|
|
2900
|
-
if (sourceTask) {
|
|
2901
|
-
const sourceMode = getPortStreamMode(sourceTask.outputSchema(), df.sourceTaskPortId);
|
|
2902
|
-
const targetMode = getPortStreamMode(task.inputSchema(), df.targetTaskPortId);
|
|
2903
|
-
if (sourceMode !== "none" && sourceMode === targetMode) {
|
|
2904
|
-
return true;
|
|
2905
|
-
}
|
|
2906
|
-
}
|
|
2907
|
-
}
|
|
2908
|
-
return false;
|
|
2909
|
-
});
|
|
3377
|
+
setRunScheduler(rs) {
|
|
3378
|
+
this.runScheduler = rs;
|
|
2910
3379
|
}
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3380
|
+
prepareStreamingInputs(task) {
|
|
3381
|
+
const dataflows = this.graph.getSourceDataflows(task.id);
|
|
3382
|
+
const streamingEdges = dataflows.filter((df) => df.stream !== undefined);
|
|
3383
|
+
if (streamingEdges.length === 0)
|
|
3384
|
+
return;
|
|
3385
|
+
const inputStreams = new Map;
|
|
3386
|
+
for (const df of streamingEdges) {
|
|
3387
|
+
const stream = df.stream;
|
|
3388
|
+
const [forwardCopy, materializeCopy] = stream.tee();
|
|
3389
|
+
inputStreams.set(df.targetTaskPortId, forwardCopy);
|
|
3390
|
+
df.setStream(materializeCopy);
|
|
2918
3391
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3392
|
+
task.runner.inputStreams = inputStreams;
|
|
3393
|
+
}
|
|
3394
|
+
async awaitStreamInputs(task, registry) {
|
|
3395
|
+
const dataflows = this.graph.getSourceDataflows(task.id);
|
|
3396
|
+
const streamingDataflows = dataflows.filter((df) => df.stream !== undefined);
|
|
3397
|
+
if (streamingDataflows.length === 0)
|
|
3398
|
+
return;
|
|
3399
|
+
await Promise.all(streamingDataflows.map(async (df) => {
|
|
3400
|
+
await df.awaitStreamValue();
|
|
3401
|
+
await df.applyTransforms(registry);
|
|
3402
|
+
}));
|
|
3403
|
+
}
|
|
3404
|
+
async runStreamingTask(task, input, ctx, options) {
|
|
3405
|
+
if (!this.runScheduler) {
|
|
3406
|
+
throw new Error("StreamPump.runStreamingTask called before setRunScheduler — facade construction is incomplete.");
|
|
3407
|
+
}
|
|
3408
|
+
const streamMode = getOutputStreamMode(task.outputSchema());
|
|
3409
|
+
const shouldAccumulate = this.taskNeedsAccumulation(task, options.outputCache, options.accumulateLeafOutputs);
|
|
3410
|
+
let streamingNotified = false;
|
|
3411
|
+
const onStatus = (status) => {
|
|
3412
|
+
if (status === TaskStatus.STREAMING && !streamingNotified) {
|
|
3413
|
+
streamingNotified = true;
|
|
3414
|
+
this.runScheduler.pushStatusFromNodeToEdges(task, ctx, TaskStatus.STREAMING);
|
|
3415
|
+
this.pushStreamToEdges(task, streamMode);
|
|
3416
|
+
this.processScheduler.onTaskStreaming(task.id);
|
|
3417
|
+
}
|
|
3418
|
+
};
|
|
3419
|
+
const onStreamStart = () => {
|
|
3420
|
+
this.graph.emit("task_stream_start", task.id);
|
|
3421
|
+
};
|
|
3422
|
+
const onStreamChunk = (event) => {
|
|
3423
|
+
this.graph.emit("task_stream_chunk", task.id, event);
|
|
3424
|
+
};
|
|
3425
|
+
const onStreamEnd = (output) => {
|
|
3426
|
+
this.graph.emit("task_stream_end", task.id, output);
|
|
3427
|
+
};
|
|
3428
|
+
task.on("status", onStatus);
|
|
3429
|
+
task.on("stream_start", onStreamStart);
|
|
3430
|
+
task.on("stream_chunk", onStreamChunk);
|
|
3431
|
+
task.on("stream_end", onStreamEnd);
|
|
3432
|
+
try {
|
|
3433
|
+
const results = await task.runner.run(input, {
|
|
3434
|
+
outputCache: options.outputCache ?? false,
|
|
3435
|
+
shouldAccumulate,
|
|
3436
|
+
updateProgress: options.updateProgress,
|
|
3437
|
+
registry: options.registry,
|
|
3438
|
+
resourceScope: options.resourceScope
|
|
3439
|
+
});
|
|
3440
|
+
await this.edgeMaterializer.pushOutputFromNodeToEdges(task, results);
|
|
3441
|
+
return {
|
|
3442
|
+
id: task.id,
|
|
3443
|
+
type: task.constructor.runtype || task.constructor.type,
|
|
3444
|
+
data: results
|
|
3445
|
+
};
|
|
3446
|
+
} finally {
|
|
3447
|
+
task.off("status", onStatus);
|
|
3448
|
+
task.off("stream_start", onStreamStart);
|
|
3449
|
+
task.off("stream_chunk", onStreamChunk);
|
|
3450
|
+
task.off("stream_end", onStreamEnd);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
taskNeedsAccumulation(task, outputCache, accumulateLeafOutputs) {
|
|
3454
|
+
if (outputCache)
|
|
3455
|
+
return true;
|
|
3456
|
+
const outEdges = this.graph.getTargetDataflows(task.id);
|
|
3457
|
+
if (outEdges.length === 0)
|
|
3458
|
+
return accumulateLeafOutputs;
|
|
3459
|
+
const outSchema = task.outputSchema();
|
|
3460
|
+
for (const df of outEdges) {
|
|
3461
|
+
if (df.sourceTaskPortId === DATAFLOW_ALL_PORTS) {
|
|
3462
|
+
if (getStreamingPorts(outSchema).length > 0)
|
|
3463
|
+
return true;
|
|
3464
|
+
continue;
|
|
3465
|
+
}
|
|
3466
|
+
const targetTask = this.graph.getTask(df.targetTaskId);
|
|
3467
|
+
if (!targetTask)
|
|
3468
|
+
continue;
|
|
3469
|
+
const inSchema = targetTask.inputSchema();
|
|
3470
|
+
if (edgeNeedsAccumulation(outSchema, df.sourceTaskPortId, inSchema, df.targetTaskPortId)) {
|
|
3471
|
+
return true;
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
return false;
|
|
3475
|
+
}
|
|
3476
|
+
static isPortDelta(event) {
|
|
3477
|
+
return event.type === "text-delta" || event.type === "object-delta";
|
|
3478
|
+
}
|
|
3479
|
+
createStreamFromTaskEvents(task, portId, edgesForGroup) {
|
|
3480
|
+
return new ReadableStream({
|
|
3481
|
+
start: (controller) => {
|
|
3482
|
+
const onChunk = (event) => {
|
|
3483
|
+
try {
|
|
3484
|
+
if (portId !== undefined && StreamPump.isPortDelta(event) && event.port !== portId) {
|
|
3485
|
+
return;
|
|
3486
|
+
}
|
|
3487
|
+
if (event.type === "snapshot") {
|
|
3488
|
+
const data = event.data;
|
|
3489
|
+
if (data) {
|
|
3490
|
+
for (const edge of edgesForGroup) {
|
|
3491
|
+
const portValue = edge.sourceTaskPortId === DATAFLOW_ALL_PORTS ? data : data[edge.sourceTaskPortId];
|
|
3492
|
+
edge.latestSnapshot = portValue;
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
controller.enqueue(event);
|
|
3497
|
+
} catch {}
|
|
3498
|
+
};
|
|
3499
|
+
const onEnd = () => {
|
|
3500
|
+
try {
|
|
3501
|
+
controller.close();
|
|
3502
|
+
} catch {}
|
|
3503
|
+
task.off("stream_chunk", onChunk);
|
|
3504
|
+
task.off("stream_end", onEnd);
|
|
3505
|
+
};
|
|
3506
|
+
task.on("stream_chunk", onChunk);
|
|
3507
|
+
task.on("stream_end", onEnd);
|
|
3508
|
+
}
|
|
3509
|
+
});
|
|
3510
|
+
}
|
|
3511
|
+
pushStreamToEdges(task, _streamMode) {
|
|
3512
|
+
const targetDataflows = this.graph.getTargetDataflows(task.id);
|
|
3513
|
+
if (targetDataflows.length === 0)
|
|
3514
|
+
return;
|
|
3515
|
+
const groups = new Map;
|
|
3516
|
+
for (const df of targetDataflows) {
|
|
3517
|
+
const key = df.sourceTaskPortId;
|
|
3518
|
+
let group = groups.get(key);
|
|
3519
|
+
if (!group) {
|
|
3520
|
+
group = [];
|
|
3521
|
+
groups.set(key, group);
|
|
3522
|
+
}
|
|
3523
|
+
group.push(df);
|
|
3524
|
+
}
|
|
3525
|
+
for (const [portKey, edges] of groups) {
|
|
3526
|
+
const filterPort = portKey === DATAFLOW_ALL_PORTS ? undefined : portKey;
|
|
3527
|
+
const stream = this.createStreamFromTaskEvents(task, filterPort, edges);
|
|
3528
|
+
if (edges.length === 1) {
|
|
3529
|
+
edges[0].setStream(stream);
|
|
3530
|
+
} else {
|
|
3531
|
+
let currentStream = stream;
|
|
3532
|
+
for (let i = 0;i < edges.length; i++) {
|
|
3533
|
+
if (i === edges.length - 1) {
|
|
3534
|
+
edges[i].setStream(currentStream);
|
|
3535
|
+
} else {
|
|
3536
|
+
const [s1, s2] = currentStream.tee();
|
|
3537
|
+
edges[i].setStream(s1);
|
|
3538
|
+
currentStream = s2;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// src/task-graph/TaskGraphScheduler.ts
|
|
3547
|
+
class TopologicalScheduler {
|
|
3548
|
+
dag;
|
|
3549
|
+
sortedNodes;
|
|
3550
|
+
currentIndex;
|
|
3551
|
+
constructor(dag) {
|
|
3552
|
+
this.dag = dag;
|
|
3553
|
+
this.sortedNodes = [];
|
|
3554
|
+
this.currentIndex = 0;
|
|
3555
|
+
this.reset();
|
|
3556
|
+
}
|
|
3557
|
+
async* tasks() {
|
|
3558
|
+
while (this.currentIndex < this.sortedNodes.length) {
|
|
3559
|
+
yield this.sortedNodes[this.currentIndex++];
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
onTaskCompleted(_taskId) {}
|
|
3563
|
+
onTaskStreaming(_taskId) {}
|
|
3564
|
+
reset() {
|
|
3565
|
+
this.sortedNodes = this.dag.topologicallySortedNodes();
|
|
3566
|
+
this.currentIndex = 0;
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
class DependencyBasedScheduler {
|
|
3571
|
+
dag;
|
|
3572
|
+
completedTasks;
|
|
3573
|
+
streamingTasks;
|
|
3574
|
+
pendingTasks;
|
|
3575
|
+
nextResolver = null;
|
|
3576
|
+
constructor(dag) {
|
|
3577
|
+
this.dag = dag;
|
|
3578
|
+
this.completedTasks = new Set;
|
|
3579
|
+
this.streamingTasks = new Set;
|
|
3580
|
+
this.pendingTasks = new Set;
|
|
3581
|
+
this.reset();
|
|
3582
|
+
}
|
|
3583
|
+
isTaskReady(task) {
|
|
3584
|
+
if (task.status === TaskStatus.DISABLED) {
|
|
3585
|
+
return false;
|
|
3586
|
+
}
|
|
3587
|
+
const sourceDataflows = this.dag.getSourceDataflows(task.id);
|
|
3588
|
+
if (sourceDataflows.length > 0) {
|
|
3589
|
+
const allIncomingDisabled = sourceDataflows.every((df) => df.status === TaskStatus.DISABLED);
|
|
3590
|
+
if (allIncomingDisabled) {
|
|
3591
|
+
return false;
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
const activeDataflows = sourceDataflows.filter((df) => df.status !== TaskStatus.DISABLED);
|
|
3595
|
+
return activeDataflows.every((df) => {
|
|
3596
|
+
const depId = df.sourceTaskId;
|
|
3597
|
+
if (this.completedTasks.has(depId))
|
|
3598
|
+
return true;
|
|
3599
|
+
if (this.streamingTasks.has(depId)) {
|
|
3600
|
+
const sourceTask = this.dag.getTask(depId);
|
|
3601
|
+
if (sourceTask) {
|
|
3602
|
+
const sourceMode = getPortStreamMode(sourceTask.outputSchema(), df.sourceTaskPortId);
|
|
3603
|
+
const targetMode = getPortStreamMode(task.inputSchema(), df.targetTaskPortId);
|
|
3604
|
+
if (sourceMode !== "none" && sourceMode === targetMode) {
|
|
3605
|
+
return true;
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
return false;
|
|
3610
|
+
});
|
|
3611
|
+
}
|
|
3612
|
+
async waitForNextTask() {
|
|
3613
|
+
if (this.pendingTasks.size === 0)
|
|
3614
|
+
return null;
|
|
3615
|
+
for (const task of Array.from(this.pendingTasks)) {
|
|
3616
|
+
if (task.status === TaskStatus.DISABLED) {
|
|
3617
|
+
this.pendingTasks.delete(task);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
if (this.pendingTasks.size === 0)
|
|
3621
|
+
return null;
|
|
3622
|
+
const readyTask = Array.from(this.pendingTasks).find((task) => this.isTaskReady(task));
|
|
3623
|
+
if (readyTask) {
|
|
2923
3624
|
this.pendingTasks.delete(readyTask);
|
|
2924
3625
|
return readyTask;
|
|
2925
3626
|
}
|
|
@@ -2995,12 +3696,6 @@ function taskPrototypeHasOwnExecute(task) {
|
|
|
2995
3696
|
}
|
|
2996
3697
|
var PROPERTY_ARRAY = "PROPERTY_ARRAY";
|
|
2997
3698
|
var GRAPH_RESULT_ARRAY = "GRAPH_RESULT_ARRAY";
|
|
2998
|
-
function isImageValueShape(v) {
|
|
2999
|
-
if (v === null || typeof v !== "object")
|
|
3000
|
-
return false;
|
|
3001
|
-
const o = v;
|
|
3002
|
-
return typeof o.width === "number" && typeof o.height === "number" && typeof o.previewScale === "number";
|
|
3003
|
-
}
|
|
3004
3699
|
|
|
3005
3700
|
class TaskGraphRunner {
|
|
3006
3701
|
processScheduler;
|
|
@@ -3012,89 +3707,56 @@ class TaskGraphRunner {
|
|
|
3012
3707
|
accumulateLeafOutputs = true;
|
|
3013
3708
|
registry = globalServiceRegistry3;
|
|
3014
3709
|
resourceScope;
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
telemetrySpan;
|
|
3020
|
-
graphTimeoutTimer;
|
|
3021
|
-
pendingGraphTimeoutError;
|
|
3022
|
-
activeEnforcer;
|
|
3710
|
+
currentCtx;
|
|
3711
|
+
edgeMaterializer;
|
|
3712
|
+
streamPump;
|
|
3713
|
+
runScheduler;
|
|
3023
3714
|
constructor(graph, outputCache, processScheduler = new DependencyBasedScheduler(graph), previewScheduler = new TopologicalScheduler(graph)) {
|
|
3024
3715
|
this.processScheduler = processScheduler;
|
|
3025
3716
|
this.previewScheduler = previewScheduler;
|
|
3026
3717
|
this.graph = graph;
|
|
3718
|
+
this.outputCache = outputCache;
|
|
3027
3719
|
graph.outputCache = outputCache;
|
|
3028
|
-
this.
|
|
3720
|
+
this.edgeMaterializer = new EdgeMaterializer(graph, this);
|
|
3721
|
+
this.streamPump = new StreamPump(graph, this.processScheduler, this.edgeMaterializer);
|
|
3722
|
+
this.runScheduler = new RunScheduler(graph, this.processScheduler, this);
|
|
3723
|
+
this.streamPump.setRunScheduler(this.runScheduler);
|
|
3029
3724
|
}
|
|
3030
|
-
runId = "";
|
|
3031
3725
|
async runGraph(input = {}, config) {
|
|
3032
|
-
|
|
3033
|
-
const
|
|
3034
|
-
let error;
|
|
3726
|
+
const ownsScope = config?.resourceScope === undefined;
|
|
3727
|
+
const effectiveConfig = ownsScope ? { ...config, resourceScope: new ResourceScope2 } : config;
|
|
3035
3728
|
try {
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
const isRootTask = this.graph.getSourceDataflows(task.id).length === 0;
|
|
3044
|
-
const runAsync = async () => {
|
|
3045
|
-
let errorRouted = false;
|
|
3046
|
-
try {
|
|
3047
|
-
const taskInput = isRootTask ? input : config?.matchAllEmptyInputs ? this.filterInputForTask(task, input) : {};
|
|
3048
|
-
const taskPromise = this.runTask(task, taskInput);
|
|
3049
|
-
this.inProgressTasks.set(task.id, taskPromise);
|
|
3050
|
-
const taskResult = await taskPromise;
|
|
3051
|
-
if (this.graph.getTargetDataflows(task.id).length === 0) {
|
|
3052
|
-
results.push(taskResult);
|
|
3053
|
-
}
|
|
3054
|
-
} catch (error2) {
|
|
3055
|
-
if (this.hasErrorOutputEdges(task)) {
|
|
3056
|
-
errorRouted = true;
|
|
3057
|
-
this.pushErrorOutputToEdges(task);
|
|
3058
|
-
} else {
|
|
3059
|
-
this.failedTaskErrors.set(task.id, error2);
|
|
3060
|
-
}
|
|
3061
|
-
} finally {
|
|
3062
|
-
if (!errorRouted) {
|
|
3063
|
-
this.pushStatusFromNodeToEdges(this.graph, task);
|
|
3064
|
-
this.pushErrorFromNodeToEdges(this.graph, task);
|
|
3065
|
-
}
|
|
3066
|
-
this.processScheduler.onTaskCompleted(task.id);
|
|
3067
|
-
}
|
|
3068
|
-
};
|
|
3069
|
-
this.inProgressFunctions.set(Symbol(task.id), runAsync());
|
|
3729
|
+
await this.handleStart(effectiveConfig);
|
|
3730
|
+
const ctx = this.currentCtx;
|
|
3731
|
+
const results = await this.runScheduler.runLoop(input, effectiveConfig, ctx, this.edgeMaterializer);
|
|
3732
|
+
const pendingTimeout = ctx.pendingGraphTimeoutError;
|
|
3733
|
+
if (pendingTimeout) {
|
|
3734
|
+
await this.handleAbort();
|
|
3735
|
+
throw pendingTimeout;
|
|
3070
3736
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
throw new TaskAbortedError;
|
|
3089
|
-
}
|
|
3090
|
-
await this.handleComplete();
|
|
3091
|
-
return this.filterLeafResults(results);
|
|
3737
|
+
if (ctx.failedTaskErrors.size > 0) {
|
|
3738
|
+
const latestError = ctx.failedTaskErrors.values().next().value;
|
|
3739
|
+
await this.handleError(latestError);
|
|
3740
|
+
throw latestError;
|
|
3741
|
+
}
|
|
3742
|
+
if (ctx.abortController.signal.aborted) {
|
|
3743
|
+
await this.handleAbort();
|
|
3744
|
+
throw new TaskAbortedError;
|
|
3745
|
+
}
|
|
3746
|
+
await this.handleComplete();
|
|
3747
|
+
return this.filterLeafResults(results);
|
|
3748
|
+
} finally {
|
|
3749
|
+
if (ownsScope) {
|
|
3750
|
+
await effectiveConfig.resourceScope.disposeAll();
|
|
3751
|
+
this.resourceScope = undefined;
|
|
3752
|
+
}
|
|
3753
|
+
}
|
|
3092
3754
|
}
|
|
3093
3755
|
async runGraphPreview(input = {}, config) {
|
|
3094
3756
|
await this.handleStartPreview(config);
|
|
3095
3757
|
const telemetry = getTelemetryProvider2();
|
|
3096
3758
|
const telemetryEnabled = telemetry.isEnabled;
|
|
3097
|
-
const previewRunId = telemetryEnabled ?
|
|
3759
|
+
const previewRunId = telemetryEnabled ? uuid44() : "";
|
|
3098
3760
|
let previewSpan;
|
|
3099
3761
|
if (telemetryEnabled) {
|
|
3100
3762
|
previewSpan = telemetry.startSpan("workglow.graph.runPreview", {
|
|
@@ -3113,7 +3775,7 @@ class TaskGraphRunner {
|
|
|
3113
3775
|
const isRootTask = this.graph.getSourceDataflows(task.id).length === 0;
|
|
3114
3776
|
if (task.status === TaskStatus.PENDING) {
|
|
3115
3777
|
task.resetInputData();
|
|
3116
|
-
this.copyInputFromEdgesToNode(task);
|
|
3778
|
+
this.edgeMaterializer.copyInputFromEdgesToNode(task);
|
|
3117
3779
|
}
|
|
3118
3780
|
const taskInput = isRootTask ? input : {};
|
|
3119
3781
|
if (telemetryEnabled) {
|
|
@@ -3122,7 +3784,7 @@ class TaskGraphRunner {
|
|
|
3122
3784
|
const taskResult = await task.runPreview(taskInput);
|
|
3123
3785
|
const runPreviewMs = performance.now() - tPreview;
|
|
3124
3786
|
const tPush = performance.now();
|
|
3125
|
-
await this.pushOutputFromNodeToEdges(task, taskResult);
|
|
3787
|
+
await this.edgeMaterializer.pushOutputFromNodeToEdges(task, taskResult);
|
|
3126
3788
|
const pushOutputMs = performance.now() - tPush;
|
|
3127
3789
|
taskTimings.push({ id: task.id, type: taskType, runPreviewMs, pushOutputMs });
|
|
3128
3790
|
if (this.graph.getTargetDataflows(task.id).length === 0) {
|
|
@@ -3134,7 +3796,7 @@ class TaskGraphRunner {
|
|
|
3134
3796
|
}
|
|
3135
3797
|
} else {
|
|
3136
3798
|
const taskResult = await task.runPreview(taskInput);
|
|
3137
|
-
await this.pushOutputFromNodeToEdges(task, taskResult);
|
|
3799
|
+
await this.edgeMaterializer.pushOutputFromNodeToEdges(task, taskResult);
|
|
3138
3800
|
if (this.graph.getTargetDataflows(task.id).length === 0) {
|
|
3139
3801
|
results.push({
|
|
3140
3802
|
id: task.id,
|
|
@@ -3153,7 +3815,7 @@ class TaskGraphRunner {
|
|
|
3153
3815
|
});
|
|
3154
3816
|
previewSpan.setStatus(SpanStatusCode2.OK);
|
|
3155
3817
|
previewSpan.end();
|
|
3156
|
-
|
|
3818
|
+
getLogger5().debug("task graph runPreview timings", {
|
|
3157
3819
|
previewRunId,
|
|
3158
3820
|
totalMs: Math.round(totalMs * 1000) / 1000,
|
|
3159
3821
|
taskTimings
|
|
@@ -3171,7 +3833,7 @@ class TaskGraphRunner {
|
|
|
3171
3833
|
});
|
|
3172
3834
|
previewSpan.setStatus(SpanStatusCode2.ERROR, message);
|
|
3173
3835
|
previewSpan.end();
|
|
3174
|
-
|
|
3836
|
+
getLogger5().debug("task graph runPreview failed", {
|
|
3175
3837
|
previewRunId,
|
|
3176
3838
|
totalMs: Math.round(totalMs * 1000) / 1000,
|
|
3177
3839
|
taskTimings,
|
|
@@ -3182,23 +3844,11 @@ class TaskGraphRunner {
|
|
|
3182
3844
|
}
|
|
3183
3845
|
}
|
|
3184
3846
|
abort() {
|
|
3185
|
-
this.abortController
|
|
3847
|
+
this.currentCtx?.abortController.abort();
|
|
3186
3848
|
}
|
|
3187
3849
|
async disable() {
|
|
3188
3850
|
await this.handleDisable();
|
|
3189
3851
|
}
|
|
3190
|
-
filterInputForTask(task, input) {
|
|
3191
|
-
const sourceDataflows = this.graph.getSourceDataflows(task.id);
|
|
3192
|
-
const connectedInputs = new Set(sourceDataflows.map((df) => df.targetTaskPortId));
|
|
3193
|
-
const allPortsConnected = connectedInputs.has(DATAFLOW_ALL_PORTS);
|
|
3194
|
-
const filteredInput = {};
|
|
3195
|
-
for (const [key, value] of Object.entries(input)) {
|
|
3196
|
-
if (!connectedInputs.has(key) && !allPortsConnected) {
|
|
3197
|
-
filteredInput[key] = value;
|
|
3198
|
-
}
|
|
3199
|
-
}
|
|
3200
|
-
return filteredInput;
|
|
3201
|
-
}
|
|
3202
3852
|
addInputData(task, overrides) {
|
|
3203
3853
|
if (!overrides)
|
|
3204
3854
|
return;
|
|
@@ -3234,351 +3884,45 @@ class TaskGraphRunner {
|
|
|
3234
3884
|
return fixedOutput;
|
|
3235
3885
|
}
|
|
3236
3886
|
throw new TaskConfigurationError(`Unknown compound merge strategy: ${compoundMerge}`);
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
|
-
const
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
} else {
|
|
3250
|
-
portData = { [port]: live };
|
|
3251
|
-
}
|
|
3252
|
-
this.addInputData(task, portData);
|
|
3253
|
-
}
|
|
3254
|
-
}
|
|
3255
|
-
async pushOutputFromNodeToEdges(node, results) {
|
|
3256
|
-
const dataflows = this.graph.getTargetDataflows(node.id);
|
|
3257
|
-
if (this.previewRunning && Object.keys(results).length > 0) {
|
|
3258
|
-
for (const port of Object.keys(results)) {
|
|
3259
|
-
const value = results[port];
|
|
3260
|
-
if (isImageValueShape(value)) {
|
|
3261
|
-
results[port] = await previewSource(value);
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
for (const dataflow of dataflows) {
|
|
3266
|
-
if (dataflow.stream !== undefined)
|
|
3267
|
-
continue;
|
|
3268
|
-
const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow, this.registry);
|
|
3269
|
-
if (compatibility === "static") {
|
|
3270
|
-
dataflow.setPortData(results);
|
|
3271
|
-
await dataflow.applyTransforms(this.registry);
|
|
3272
|
-
} else if (compatibility === "runtime") {
|
|
3273
|
-
const task = this.graph.getTask(dataflow.targetTaskId);
|
|
3274
|
-
const narrowed = await task.narrowInput({ ...results }, this.registry);
|
|
3275
|
-
dataflow.setPortData(narrowed);
|
|
3276
|
-
await dataflow.applyTransforms(this.registry);
|
|
3277
|
-
} else {
|
|
3278
|
-
const resultsKeys = Object.keys(results);
|
|
3279
|
-
if (resultsKeys.length > 0) {
|
|
3280
|
-
getLogger3().warn("pushOutputFromNodeToEdge not compatible, not setting port data", {
|
|
3281
|
-
dataflowId: dataflow.id,
|
|
3282
|
-
compatibility,
|
|
3283
|
-
resultsKeys
|
|
3284
|
-
});
|
|
3285
|
-
}
|
|
3286
|
-
}
|
|
3287
|
-
}
|
|
3288
|
-
}
|
|
3289
|
-
pushStatusFromNodeToEdges(graph, node, status) {
|
|
3290
|
-
if (!node?.config?.id)
|
|
3291
|
-
return;
|
|
3292
|
-
const dataflows = graph.getTargetDataflows(node.id);
|
|
3293
|
-
const effectiveStatus = status ?? node.status;
|
|
3294
|
-
if (node instanceof ConditionalTask && effectiveStatus === TaskStatus.COMPLETED) {
|
|
3295
|
-
const branches = node.config.branches ?? [];
|
|
3296
|
-
const portToBranch = new Map;
|
|
3297
|
-
for (const branch of branches) {
|
|
3298
|
-
portToBranch.set(branch.outputPort, branch.id);
|
|
3299
|
-
}
|
|
3300
|
-
const activeBranches = node.getActiveBranches();
|
|
3301
|
-
for (const dataflow of dataflows) {
|
|
3302
|
-
if (dataflow.status === TaskStatus.FAILED)
|
|
3303
|
-
continue;
|
|
3304
|
-
const branchId = portToBranch.get(dataflow.sourceTaskPortId);
|
|
3305
|
-
if (branchId !== undefined) {
|
|
3306
|
-
if (activeBranches.has(branchId)) {
|
|
3307
|
-
dataflow.setStatus(TaskStatus.COMPLETED);
|
|
3308
|
-
} else {
|
|
3309
|
-
dataflow.setStatus(TaskStatus.DISABLED);
|
|
3310
|
-
}
|
|
3311
|
-
} else {
|
|
3312
|
-
dataflow.setStatus(effectiveStatus);
|
|
3313
|
-
}
|
|
3314
|
-
}
|
|
3315
|
-
this.propagateDisabledStatus(graph);
|
|
3316
|
-
return;
|
|
3317
|
-
}
|
|
3318
|
-
dataflows.forEach((dataflow) => {
|
|
3319
|
-
if (dataflow.status === TaskStatus.FAILED)
|
|
3320
|
-
return;
|
|
3321
|
-
dataflow.setStatus(effectiveStatus);
|
|
3322
|
-
});
|
|
3323
|
-
}
|
|
3324
|
-
pushErrorFromNodeToEdges(graph, node) {
|
|
3325
|
-
if (!node?.config?.id)
|
|
3326
|
-
return;
|
|
3327
|
-
graph.getTargetDataflows(node.id).forEach((dataflow) => {
|
|
3328
|
-
dataflow.error = node.error;
|
|
3329
|
-
});
|
|
3330
|
-
}
|
|
3331
|
-
hasErrorOutputEdges(task) {
|
|
3332
|
-
const dataflows = this.graph.getTargetDataflows(task.id);
|
|
3333
|
-
return dataflows.some((df) => df.sourceTaskPortId === DATAFLOW_ERROR_PORT);
|
|
3334
|
-
}
|
|
3335
|
-
pushErrorOutputToEdges(task) {
|
|
3336
|
-
const taskError = task.error;
|
|
3337
|
-
const errorData = {
|
|
3338
|
-
error: taskError?.message ?? "Unknown error",
|
|
3339
|
-
errorType: taskError?.constructor?.type ?? "TaskError"
|
|
3340
|
-
};
|
|
3341
|
-
const dataflows = this.graph.getTargetDataflows(task.id);
|
|
3342
|
-
for (const df of dataflows) {
|
|
3343
|
-
if (df.sourceTaskPortId === DATAFLOW_ERROR_PORT) {
|
|
3344
|
-
df.value = errorData;
|
|
3345
|
-
df.setStatus(TaskStatus.COMPLETED);
|
|
3346
|
-
} else {
|
|
3347
|
-
df.setStatus(TaskStatus.DISABLED);
|
|
3348
|
-
}
|
|
3349
|
-
}
|
|
3350
|
-
this.propagateDisabledStatus(this.graph);
|
|
3351
|
-
}
|
|
3352
|
-
propagateDisabledStatus(graph) {
|
|
3353
|
-
let changed = true;
|
|
3354
|
-
while (changed) {
|
|
3355
|
-
changed = false;
|
|
3356
|
-
for (const task of graph.getTasks()) {
|
|
3357
|
-
if (task.status !== TaskStatus.PENDING) {
|
|
3358
|
-
continue;
|
|
3359
|
-
}
|
|
3360
|
-
const incomingDataflows = graph.getSourceDataflows(task.id);
|
|
3361
|
-
if (incomingDataflows.length === 0) {
|
|
3362
|
-
continue;
|
|
3363
|
-
}
|
|
3364
|
-
const allDisabled = incomingDataflows.every((df) => df.status === TaskStatus.DISABLED);
|
|
3365
|
-
if (allDisabled) {
|
|
3366
|
-
task.status = TaskStatus.DISABLED;
|
|
3367
|
-
task.progress = 100;
|
|
3368
|
-
task.completedAt = new Date;
|
|
3369
|
-
task.emit("disabled");
|
|
3370
|
-
task.emit("status", task.status);
|
|
3371
|
-
graph.getTargetDataflows(task.id).forEach((dataflow) => {
|
|
3372
|
-
dataflow.setStatus(TaskStatus.DISABLED);
|
|
3373
|
-
});
|
|
3374
|
-
this.processScheduler.onTaskCompleted(task.id);
|
|
3375
|
-
changed = true;
|
|
3376
|
-
}
|
|
3377
|
-
}
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
taskNeedsAccumulation(task) {
|
|
3381
|
-
if (this.outputCache)
|
|
3382
|
-
return true;
|
|
3383
|
-
const outEdges = this.graph.getTargetDataflows(task.id);
|
|
3384
|
-
if (outEdges.length === 0)
|
|
3385
|
-
return this.accumulateLeafOutputs;
|
|
3386
|
-
const outSchema = task.outputSchema();
|
|
3387
|
-
for (const df of outEdges) {
|
|
3388
|
-
if (df.sourceTaskPortId === DATAFLOW_ALL_PORTS) {
|
|
3389
|
-
if (getStreamingPorts(outSchema).length > 0)
|
|
3390
|
-
return true;
|
|
3391
|
-
continue;
|
|
3392
|
-
}
|
|
3393
|
-
const targetTask = this.graph.getTask(df.targetTaskId);
|
|
3394
|
-
if (!targetTask)
|
|
3395
|
-
continue;
|
|
3396
|
-
const inSchema = targetTask.inputSchema();
|
|
3397
|
-
if (edgeNeedsAccumulation(outSchema, df.sourceTaskPortId, inSchema, df.targetTaskPortId)) {
|
|
3398
|
-
return true;
|
|
3399
|
-
}
|
|
3400
|
-
}
|
|
3401
|
-
return false;
|
|
3402
|
-
}
|
|
3403
|
-
async runTask(task, input) {
|
|
3404
|
-
const isStreamable = isTaskStreamable(task);
|
|
3405
|
-
if (isStreamable) {
|
|
3406
|
-
const dataflows = this.graph.getSourceDataflows(task.id);
|
|
3407
|
-
const streamingEdges = dataflows.filter((df) => df.stream !== undefined);
|
|
3408
|
-
if (streamingEdges.length > 0) {
|
|
3409
|
-
const inputStreams = new Map;
|
|
3410
|
-
for (const df of streamingEdges) {
|
|
3411
|
-
const stream = df.stream;
|
|
3412
|
-
const [forwardCopy, materializeCopy] = stream.tee();
|
|
3413
|
-
inputStreams.set(df.targetTaskPortId, forwardCopy);
|
|
3414
|
-
df.setStream(materializeCopy);
|
|
3415
|
-
}
|
|
3416
|
-
task.runner.inputStreams = inputStreams;
|
|
3417
|
-
}
|
|
3418
|
-
}
|
|
3419
|
-
await this.awaitStreamInputs(task);
|
|
3420
|
-
this.copyInputFromEdgesToNode(task);
|
|
3421
|
-
if (this.activeEnforcer && task.constructor.hasDynamicEntitlements) {
|
|
3422
|
-
const denied = await this.activeEnforcer.checkTask(task);
|
|
3423
|
-
if (denied.length > 0) {
|
|
3424
|
-
throw new TaskEntitlementError(`Task ${task.constructor.type} denied entitlements: ${denied.map(formatEntitlementDenial).join(", ")}`);
|
|
3425
|
-
}
|
|
3426
|
-
}
|
|
3427
|
-
if (isStreamable) {
|
|
3428
|
-
return this.runStreamingTask(task, input);
|
|
3429
|
-
}
|
|
3430
|
-
const results = await task.runner.run(input, {
|
|
3431
|
-
outputCache: this.outputCache ?? false,
|
|
3432
|
-
updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args),
|
|
3433
|
-
registry: this.registry,
|
|
3434
|
-
resourceScope: this.resourceScope
|
|
3435
|
-
});
|
|
3436
|
-
await this.pushOutputFromNodeToEdges(task, results);
|
|
3437
|
-
return {
|
|
3438
|
-
id: task.id,
|
|
3439
|
-
type: task.constructor.runtype || task.constructor.type,
|
|
3440
|
-
data: results
|
|
3441
|
-
};
|
|
3442
|
-
}
|
|
3443
|
-
async awaitStreamInputs(task) {
|
|
3444
|
-
const dataflows = this.graph.getSourceDataflows(task.id);
|
|
3445
|
-
const streamingDataflows = dataflows.filter((df) => df.stream !== undefined);
|
|
3446
|
-
if (streamingDataflows.length === 0)
|
|
3447
|
-
return;
|
|
3448
|
-
await Promise.all(streamingDataflows.map(async (df) => {
|
|
3449
|
-
await df.awaitStreamValue();
|
|
3450
|
-
await df.applyTransforms(this.registry);
|
|
3451
|
-
}));
|
|
3452
|
-
}
|
|
3453
|
-
async runStreamingTask(task, input) {
|
|
3454
|
-
const streamMode = getOutputStreamMode(task.outputSchema());
|
|
3455
|
-
const shouldAccumulate = this.taskNeedsAccumulation(task);
|
|
3456
|
-
let streamingNotified = false;
|
|
3457
|
-
const onStatus = (status) => {
|
|
3458
|
-
if (status === TaskStatus.STREAMING && !streamingNotified) {
|
|
3459
|
-
streamingNotified = true;
|
|
3460
|
-
this.pushStatusFromNodeToEdges(this.graph, task, TaskStatus.STREAMING);
|
|
3461
|
-
this.pushStreamToEdges(task, streamMode);
|
|
3462
|
-
this.processScheduler.onTaskStreaming(task.id);
|
|
3463
|
-
}
|
|
3464
|
-
};
|
|
3465
|
-
const onStreamStart = () => {
|
|
3466
|
-
this.graph.emit("task_stream_start", task.id);
|
|
3467
|
-
};
|
|
3468
|
-
const onStreamChunk = (event) => {
|
|
3469
|
-
this.graph.emit("task_stream_chunk", task.id, event);
|
|
3470
|
-
};
|
|
3471
|
-
const onStreamEnd = (output) => {
|
|
3472
|
-
this.graph.emit("task_stream_end", task.id, output);
|
|
3473
|
-
};
|
|
3474
|
-
task.on("status", onStatus);
|
|
3475
|
-
task.on("stream_start", onStreamStart);
|
|
3476
|
-
task.on("stream_chunk", onStreamChunk);
|
|
3477
|
-
task.on("stream_end", onStreamEnd);
|
|
3478
|
-
try {
|
|
3479
|
-
const results = await task.runner.run(input, {
|
|
3480
|
-
outputCache: this.outputCache ?? false,
|
|
3481
|
-
shouldAccumulate,
|
|
3482
|
-
updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args),
|
|
3483
|
-
registry: this.registry,
|
|
3484
|
-
resourceScope: this.resourceScope
|
|
3485
|
-
});
|
|
3486
|
-
await this.pushOutputFromNodeToEdges(task, results);
|
|
3487
|
-
return {
|
|
3488
|
-
id: task.id,
|
|
3489
|
-
type: task.constructor.runtype || task.constructor.type,
|
|
3490
|
-
data: results
|
|
3491
|
-
};
|
|
3492
|
-
} finally {
|
|
3493
|
-
task.off("status", onStatus);
|
|
3494
|
-
task.off("stream_start", onStreamStart);
|
|
3495
|
-
task.off("stream_chunk", onStreamChunk);
|
|
3496
|
-
task.off("stream_end", onStreamEnd);
|
|
3497
|
-
}
|
|
3498
|
-
}
|
|
3499
|
-
static isPortDelta(event) {
|
|
3500
|
-
return event.type === "text-delta" || event.type === "object-delta";
|
|
3501
|
-
}
|
|
3502
|
-
createStreamFromTaskEvents(task, portId, edgesForGroup) {
|
|
3503
|
-
return new ReadableStream({
|
|
3504
|
-
start: (controller) => {
|
|
3505
|
-
const onChunk = (event) => {
|
|
3506
|
-
try {
|
|
3507
|
-
if (portId !== undefined && TaskGraphRunner.isPortDelta(event) && event.port !== portId) {
|
|
3508
|
-
return;
|
|
3509
|
-
}
|
|
3510
|
-
if (event.type === "snapshot") {
|
|
3511
|
-
const data = event.data;
|
|
3512
|
-
if (data) {
|
|
3513
|
-
for (const edge of edgesForGroup) {
|
|
3514
|
-
const portValue = edge.sourceTaskPortId === DATAFLOW_ALL_PORTS ? data : data[edge.sourceTaskPortId];
|
|
3515
|
-
edge.latestSnapshot = portValue;
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
controller.enqueue(event);
|
|
3520
|
-
} catch {}
|
|
3521
|
-
};
|
|
3522
|
-
const onEnd = () => {
|
|
3523
|
-
try {
|
|
3524
|
-
controller.close();
|
|
3525
|
-
} catch {}
|
|
3526
|
-
task.off("stream_chunk", onChunk);
|
|
3527
|
-
task.off("stream_end", onEnd);
|
|
3528
|
-
};
|
|
3529
|
-
task.on("stream_chunk", onChunk);
|
|
3530
|
-
task.on("stream_end", onEnd);
|
|
3531
|
-
}
|
|
3532
|
-
});
|
|
3533
|
-
}
|
|
3534
|
-
pushStreamToEdges(task, _streamMode) {
|
|
3535
|
-
const targetDataflows = this.graph.getTargetDataflows(task.id);
|
|
3536
|
-
if (targetDataflows.length === 0)
|
|
3537
|
-
return;
|
|
3538
|
-
const groups = new Map;
|
|
3539
|
-
for (const df of targetDataflows) {
|
|
3540
|
-
const key = df.sourceTaskPortId;
|
|
3541
|
-
let group = groups.get(key);
|
|
3542
|
-
if (!group) {
|
|
3543
|
-
group = [];
|
|
3544
|
-
groups.set(key, group);
|
|
3545
|
-
}
|
|
3546
|
-
group.push(df);
|
|
3547
|
-
}
|
|
3548
|
-
for (const [portKey, edges] of groups) {
|
|
3549
|
-
const filterPort = portKey === DATAFLOW_ALL_PORTS ? undefined : portKey;
|
|
3550
|
-
const stream = this.createStreamFromTaskEvents(task, filterPort, edges);
|
|
3551
|
-
if (edges.length === 1) {
|
|
3552
|
-
edges[0].setStream(stream);
|
|
3553
|
-
} else {
|
|
3554
|
-
let currentStream = stream;
|
|
3555
|
-
for (let i = 0;i < edges.length; i++) {
|
|
3556
|
-
if (i === edges.length - 1) {
|
|
3557
|
-
edges[i].setStream(currentStream);
|
|
3558
|
-
} else {
|
|
3559
|
-
const [s1, s2] = currentStream.tee();
|
|
3560
|
-
edges[i].setStream(s1);
|
|
3561
|
-
currentStream = s2;
|
|
3562
|
-
}
|
|
3563
|
-
}
|
|
3887
|
+
}
|
|
3888
|
+
async runTask(task, input) {
|
|
3889
|
+
const isStreamable = isTaskStreamable(task);
|
|
3890
|
+
if (isStreamable) {
|
|
3891
|
+
this.streamPump.prepareStreamingInputs(task);
|
|
3892
|
+
}
|
|
3893
|
+
await this.streamPump.awaitStreamInputs(task, this.registry);
|
|
3894
|
+
this.edgeMaterializer.copyInputFromEdgesToNode(task);
|
|
3895
|
+
if (this.currentCtx?.activeEnforcer && task.constructor.hasDynamicEntitlements) {
|
|
3896
|
+
const denied = await this.currentCtx.activeEnforcer.checkTask(task);
|
|
3897
|
+
if (denied.length > 0) {
|
|
3898
|
+
throw new TaskEntitlementError(`Task ${task.constructor.type} denied entitlements: ${denied.map(formatEntitlementDenial).join(", ")}`);
|
|
3564
3899
|
}
|
|
3565
3900
|
}
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3901
|
+
if (isStreamable) {
|
|
3902
|
+
return this.streamPump.runStreamingTask(task, input, this.currentCtx, {
|
|
3903
|
+
registry: this.registry,
|
|
3904
|
+
outputCache: this.outputCache,
|
|
3905
|
+
resourceScope: this.resourceScope,
|
|
3906
|
+
accumulateLeafOutputs: this.accumulateLeafOutputs,
|
|
3907
|
+
updateProgress: (t, p, m, ...a) => this.runScheduler.handleProgress(this.currentCtx, t, p, m, ...a)
|
|
3908
|
+
});
|
|
3909
|
+
}
|
|
3910
|
+
const results = await task.runner.run(input, {
|
|
3911
|
+
outputCache: this.outputCache ?? false,
|
|
3912
|
+
updateProgress: async (task2, progress, message, ...args) => await this.runScheduler.handleProgress(this.currentCtx, task2, progress, message, ...args),
|
|
3913
|
+
registry: this.registry,
|
|
3914
|
+
resourceScope: this.resourceScope
|
|
3915
|
+
});
|
|
3916
|
+
await this.edgeMaterializer.pushOutputFromNodeToEdges(task, results);
|
|
3917
|
+
return {
|
|
3918
|
+
id: task.id,
|
|
3919
|
+
type: task.constructor.runtype || task.constructor.type,
|
|
3920
|
+
data: results
|
|
3921
|
+
};
|
|
3578
3922
|
}
|
|
3579
3923
|
resetGraph(graph, runnerId) {
|
|
3580
3924
|
graph.getTasks().forEach((node) => {
|
|
3581
|
-
this.resetTask(graph, node, runnerId);
|
|
3925
|
+
this.edgeMaterializer.resetTask(graph, node, runnerId);
|
|
3582
3926
|
node.regenerateGraph();
|
|
3583
3927
|
if (node.hasChildren()) {
|
|
3584
3928
|
this.resetGraph(node.subGraph, runnerId);
|
|
@@ -3614,34 +3958,18 @@ class TaskGraphRunner {
|
|
|
3614
3958
|
throw new TaskConfigurationError("Graph is already running");
|
|
3615
3959
|
}
|
|
3616
3960
|
this.running = true;
|
|
3617
|
-
|
|
3618
|
-
this.
|
|
3961
|
+
const ctx = new RunContext(config?.parentSignal);
|
|
3962
|
+
this.currentCtx = ctx;
|
|
3963
|
+
ctx.abortController.signal.addEventListener("abort", () => {
|
|
3619
3964
|
this.handleAbort();
|
|
3620
3965
|
});
|
|
3621
|
-
if (config?.timeout !== undefined
|
|
3622
|
-
this.
|
|
3623
|
-
this.graphTimeoutTimer = setTimeout(() => {
|
|
3624
|
-
this.pendingGraphTimeoutError = new TaskGraphTimeoutError(config.timeout);
|
|
3625
|
-
this.abortController?.abort();
|
|
3626
|
-
}, config.timeout);
|
|
3627
|
-
}
|
|
3628
|
-
if (config?.parentSignal) {
|
|
3629
|
-
const onParentAbort = () => {
|
|
3630
|
-
this.abortController?.abort();
|
|
3631
|
-
};
|
|
3632
|
-
config.parentSignal.addEventListener("abort", onParentAbort, { once: true });
|
|
3633
|
-
if (config.parentSignal.aborted) {
|
|
3634
|
-
config.parentSignal.removeEventListener("abort", onParentAbort);
|
|
3635
|
-
this.abortController.abort();
|
|
3636
|
-
return;
|
|
3637
|
-
}
|
|
3966
|
+
if (config?.timeout !== undefined) {
|
|
3967
|
+
this.runScheduler.armGraphTimeout(config.timeout, ctx);
|
|
3638
3968
|
}
|
|
3639
|
-
|
|
3640
|
-
|
|
3969
|
+
if (ctx.abortController.signal.aborted)
|
|
3970
|
+
return;
|
|
3971
|
+
this.resetGraph(this.graph, ctx.runId);
|
|
3641
3972
|
this.processScheduler.reset();
|
|
3642
|
-
this.inProgressTasks.clear();
|
|
3643
|
-
this.inProgressFunctions.clear();
|
|
3644
|
-
this.failedTaskErrors.clear();
|
|
3645
3973
|
try {
|
|
3646
3974
|
if (config?.maxTasks !== undefined && config.maxTasks > 0) {
|
|
3647
3975
|
const taskCount = this.graph.getTasks().length;
|
|
@@ -3658,25 +3986,20 @@ class TaskGraphRunner {
|
|
|
3658
3986
|
if (denied.length > 0) {
|
|
3659
3987
|
throw new TaskEntitlementError(`Denied entitlements: ${denied.map(formatEntitlementDenial).join(", ")}`);
|
|
3660
3988
|
}
|
|
3661
|
-
|
|
3662
|
-
} else {
|
|
3663
|
-
this.activeEnforcer = undefined;
|
|
3989
|
+
ctx.activeEnforcer = enforcer;
|
|
3664
3990
|
}
|
|
3665
3991
|
} catch (err) {
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
}
|
|
3670
|
-
this.abortController = undefined;
|
|
3671
|
-
this.activeEnforcer = undefined;
|
|
3992
|
+
this.runScheduler.clearGraphTimeout(ctx);
|
|
3993
|
+
ctx.dispose();
|
|
3994
|
+
this.currentCtx = undefined;
|
|
3672
3995
|
this.running = false;
|
|
3673
3996
|
throw err;
|
|
3674
3997
|
}
|
|
3675
3998
|
const telemetry = getTelemetryProvider2();
|
|
3676
3999
|
if (telemetry.isEnabled) {
|
|
3677
|
-
|
|
4000
|
+
ctx.telemetrySpan = telemetry.startSpan("workglow.graph.run", {
|
|
3678
4001
|
attributes: {
|
|
3679
|
-
"workglow.graph.run_id":
|
|
4002
|
+
"workglow.graph.run_id": ctx.runId,
|
|
3680
4003
|
"workglow.graph.task_count": this.graph.getTasks().length,
|
|
3681
4004
|
"workglow.graph.dataflow_count": this.graph.getDataflows().length
|
|
3682
4005
|
}
|
|
@@ -3701,20 +4024,20 @@ class TaskGraphRunner {
|
|
|
3701
4024
|
this.previewRunning = true;
|
|
3702
4025
|
}
|
|
3703
4026
|
clearGraphTimeout() {
|
|
3704
|
-
if (this.
|
|
3705
|
-
|
|
3706
|
-
this.graphTimeoutTimer = undefined;
|
|
4027
|
+
if (this.currentCtx) {
|
|
4028
|
+
this.runScheduler.clearGraphTimeout(this.currentCtx);
|
|
3707
4029
|
}
|
|
3708
4030
|
}
|
|
3709
4031
|
async handleComplete() {
|
|
3710
4032
|
this.clearGraphTimeout();
|
|
4033
|
+
const ctx = this.currentCtx;
|
|
3711
4034
|
this.running = false;
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
this.telemetrySpan.end();
|
|
3716
|
-
this.telemetrySpan = undefined;
|
|
4035
|
+
if (ctx?.telemetrySpan) {
|
|
4036
|
+
ctx.telemetrySpan.setStatus(SpanStatusCode2.OK);
|
|
4037
|
+
ctx.telemetrySpan.end();
|
|
3717
4038
|
}
|
|
4039
|
+
ctx?.dispose();
|
|
4040
|
+
this.currentCtx = undefined;
|
|
3718
4041
|
this.graph.emit("complete");
|
|
3719
4042
|
}
|
|
3720
4043
|
async handleCompletePreview() {
|
|
@@ -3727,34 +4050,38 @@ class TaskGraphRunner {
|
|
|
3727
4050
|
return task.abort();
|
|
3728
4051
|
}
|
|
3729
4052
|
}));
|
|
4053
|
+
const ctx = this.currentCtx;
|
|
3730
4054
|
this.running = false;
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
this.telemetrySpan.end();
|
|
3736
|
-
this.telemetrySpan = undefined;
|
|
4055
|
+
if (ctx?.telemetrySpan) {
|
|
4056
|
+
ctx.telemetrySpan.setStatus(SpanStatusCode2.ERROR, error.message);
|
|
4057
|
+
ctx.telemetrySpan.setAttributes({ "workglow.graph.error": error.message });
|
|
4058
|
+
ctx.telemetrySpan.end();
|
|
3737
4059
|
}
|
|
4060
|
+
ctx?.dispose();
|
|
4061
|
+
this.currentCtx = undefined;
|
|
3738
4062
|
this.graph.emit("error", error);
|
|
3739
4063
|
}
|
|
3740
4064
|
async handleErrorPreview() {
|
|
3741
4065
|
this.previewRunning = false;
|
|
3742
4066
|
}
|
|
3743
4067
|
async handleAbort() {
|
|
4068
|
+
if (!this.running)
|
|
4069
|
+
return;
|
|
4070
|
+
this.running = false;
|
|
3744
4071
|
this.clearGraphTimeout();
|
|
3745
4072
|
await Promise.allSettled(this.graph.getTasks().map(async (task) => {
|
|
3746
4073
|
if (task.status === TaskStatus.PROCESSING || task.status === TaskStatus.STREAMING) {
|
|
3747
4074
|
return task.abort();
|
|
3748
4075
|
}
|
|
3749
4076
|
}));
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
4077
|
+
const ctx = this.currentCtx;
|
|
4078
|
+
if (ctx?.telemetrySpan) {
|
|
4079
|
+
ctx.telemetrySpan.setStatus(SpanStatusCode2.ERROR, "aborted");
|
|
4080
|
+
ctx.telemetrySpan.addEvent("workglow.graph.aborted");
|
|
4081
|
+
ctx.telemetrySpan.end();
|
|
4082
|
+
}
|
|
4083
|
+
ctx?.dispose();
|
|
4084
|
+
this.currentCtx = undefined;
|
|
3758
4085
|
this.graph.emit("abort");
|
|
3759
4086
|
}
|
|
3760
4087
|
async handleAbortPreview() {
|
|
@@ -3769,27 +4096,6 @@ class TaskGraphRunner {
|
|
|
3769
4096
|
this.running = false;
|
|
3770
4097
|
this.graph.emit("disabled");
|
|
3771
4098
|
}
|
|
3772
|
-
async handleProgress(task, progress, message, ...args) {
|
|
3773
|
-
const contributors = this.graph.getTasks().filter(taskPrototypeHasOwnExecute);
|
|
3774
|
-
if (contributors.length > 1) {
|
|
3775
|
-
const determinate = contributors.filter((t) => t.progress !== undefined);
|
|
3776
|
-
if (determinate.length === 0) {
|
|
3777
|
-
progress = undefined;
|
|
3778
|
-
} else {
|
|
3779
|
-
const sum = determinate.reduce((acc, t) => acc + t.progress, 0);
|
|
3780
|
-
progress = Math.round(sum / determinate.length);
|
|
3781
|
-
}
|
|
3782
|
-
} else if (contributors.length === 1) {
|
|
3783
|
-
const [only] = contributors;
|
|
3784
|
-
progress = only.progress;
|
|
3785
|
-
}
|
|
3786
|
-
this.pushStatusFromNodeToEdges(this.graph, task);
|
|
3787
|
-
this.graph.emit("graph_progress", progress, message, args);
|
|
3788
|
-
const isActive = task.status === TaskStatus.PROCESSING || task.status === TaskStatus.STREAMING;
|
|
3789
|
-
if (isActive && task.runOutputData && Object.keys(task.runOutputData).length > 0) {
|
|
3790
|
-
await this.pushOutputFromNodeToEdges(task, task.runOutputData);
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
4099
|
}
|
|
3794
4100
|
|
|
3795
4101
|
// src/task/GraphAsTaskRunner.ts
|
|
@@ -3799,7 +4105,7 @@ class GraphAsTaskRunner extends TaskRunner {
|
|
|
3799
4105
|
this.handleProgress(progress, message, ...args);
|
|
3800
4106
|
});
|
|
3801
4107
|
const results = await this.task.subGraph.run(input, {
|
|
3802
|
-
parentSignal: this.abortController
|
|
4108
|
+
parentSignal: this.currentCtx?.abortController.signal,
|
|
3803
4109
|
outputCache: this.outputCache,
|
|
3804
4110
|
registry: this.registry,
|
|
3805
4111
|
resourceScope: this.resourceScope
|
|
@@ -3813,29 +4119,29 @@ class GraphAsTaskRunner extends TaskRunner {
|
|
|
3813
4119
|
resourceScope: this.resourceScope
|
|
3814
4120
|
});
|
|
3815
4121
|
}
|
|
3816
|
-
async handleDisable() {
|
|
4122
|
+
async handleDisable(ctx) {
|
|
3817
4123
|
if (this.task.hasChildren()) {
|
|
3818
4124
|
await this.task.subGraph.disable();
|
|
3819
4125
|
}
|
|
3820
|
-
super.handleDisable();
|
|
4126
|
+
await super.handleDisable(ctx);
|
|
3821
4127
|
}
|
|
3822
|
-
async executeTask(input) {
|
|
4128
|
+
async executeTask(input, ctx) {
|
|
3823
4129
|
if (this.task.hasChildren()) {
|
|
3824
4130
|
const runExecuteOutputData = await this.executeTaskChildren(input);
|
|
3825
4131
|
this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(runExecuteOutputData, this.task.compoundMerge);
|
|
3826
4132
|
} else {
|
|
3827
|
-
const result = await super.executeTask(input);
|
|
4133
|
+
const result = await super.executeTask(input, ctx);
|
|
3828
4134
|
this.task.runOutputData = result ?? {};
|
|
3829
4135
|
}
|
|
3830
4136
|
return this.task.runOutputData;
|
|
3831
4137
|
}
|
|
3832
|
-
async executeTaskPreview(input) {
|
|
4138
|
+
async executeTaskPreview(input, ctx) {
|
|
3833
4139
|
if (this.task.hasChildren()) {
|
|
3834
4140
|
const previewResults = await this.executeTaskChildrenPreview();
|
|
3835
4141
|
this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(previewResults, this.task.compoundMerge);
|
|
3836
4142
|
return this.task.runOutputData;
|
|
3837
4143
|
} else {
|
|
3838
|
-
const previewResult = await super.executeTaskPreview(input);
|
|
4144
|
+
const previewResult = await super.executeTaskPreview(input, ctx);
|
|
3839
4145
|
if (previewResult !== undefined) {
|
|
3840
4146
|
this.task.runOutputData = previewResult;
|
|
3841
4147
|
}
|
|
@@ -3899,7 +4205,7 @@ class GraphAsTask extends Task {
|
|
|
3899
4205
|
const schemaNode = Task.generateInputSchemaNode(dataPortSchema);
|
|
3900
4206
|
this._inputSchemaNode = schemaNode;
|
|
3901
4207
|
} catch (error) {
|
|
3902
|
-
|
|
4208
|
+
getLogger6().warn(`GraphAsTask "${this.type}" (${this.id}): Failed to compile input schema, ` + `falling back to permissive validation. Inputs will NOT be validated.`, { error, taskType: this.type, taskId: this.id });
|
|
3903
4209
|
this._inputSchemaNode = compileSchema2({});
|
|
3904
4210
|
}
|
|
3905
4211
|
}
|
|
@@ -4343,7 +4649,7 @@ class TaskGraph {
|
|
|
4343
4649
|
return this._dag.removeNode(taskId);
|
|
4344
4650
|
}
|
|
4345
4651
|
resetGraph() {
|
|
4346
|
-
this.runner.resetGraph(this,
|
|
4652
|
+
this.runner.resetGraph(this, uuid45());
|
|
4347
4653
|
}
|
|
4348
4654
|
toJSON(options) {
|
|
4349
4655
|
const tasks = this.getTasks().map((node) => node.toJSON(options));
|
|
@@ -4571,7 +4877,7 @@ function serialGraph(tasks, inputHandle, outputHandle) {
|
|
|
4571
4877
|
return graph;
|
|
4572
4878
|
}
|
|
4573
4879
|
// src/task-graph/Workflow.ts
|
|
4574
|
-
import { EventEmitter as EventEmitter5
|
|
4880
|
+
import { EventEmitter as EventEmitter5 } from "@workglow/util";
|
|
4575
4881
|
|
|
4576
4882
|
// src/task-graph/autoConnect.ts
|
|
4577
4883
|
function autoConnect(graph, sourceTask, targetTask, options) {
|
|
@@ -4813,8 +5119,64 @@ function autoConnect(graph, sourceTask, targetTask, options) {
|
|
|
4813
5119
|
};
|
|
4814
5120
|
}
|
|
4815
5121
|
|
|
5122
|
+
// src/task-graph/LoopBuilderContext.ts
|
|
5123
|
+
import { getLogger as getLogger7 } from "@workglow/util";
|
|
5124
|
+
function runLoopAutoConnect(parentGraph, pending) {
|
|
5125
|
+
const { parent, iteratorTask } = pending;
|
|
5126
|
+
if (parentGraph.getTargetDataflows(parent.id).length !== 0)
|
|
5127
|
+
return;
|
|
5128
|
+
const nodes = parentGraph.getTasks();
|
|
5129
|
+
const parentIndex = nodes.findIndex((n) => n.id === parent.id);
|
|
5130
|
+
const earlierTasks = [];
|
|
5131
|
+
for (let i = parentIndex - 1;i >= 0; i--) {
|
|
5132
|
+
earlierTasks.push(nodes[i]);
|
|
5133
|
+
}
|
|
5134
|
+
const result = autoConnect(parentGraph, parent, iteratorTask, { earlierTasks });
|
|
5135
|
+
if (result.error) {
|
|
5136
|
+
const message = result.error + " Task not added.";
|
|
5137
|
+
getLogger7().error(message);
|
|
5138
|
+
parentGraph.removeTask(iteratorTask.id);
|
|
5139
|
+
return message;
|
|
5140
|
+
}
|
|
5141
|
+
return;
|
|
5142
|
+
}
|
|
5143
|
+
|
|
5144
|
+
class LoopBuilderContext {
|
|
5145
|
+
parent;
|
|
5146
|
+
iteratorTask;
|
|
5147
|
+
pendingLoopConnect;
|
|
5148
|
+
constructor(parent, iteratorTask) {
|
|
5149
|
+
this.parent = parent;
|
|
5150
|
+
this.iteratorTask = iteratorTask;
|
|
5151
|
+
}
|
|
5152
|
+
finalizeTemplate(childGraph) {
|
|
5153
|
+
if (childGraph.getTasks().length === 0)
|
|
5154
|
+
return;
|
|
5155
|
+
this.iteratorTask.subGraph = childGraph;
|
|
5156
|
+
this.iteratorTask.validateAcyclic();
|
|
5157
|
+
}
|
|
5158
|
+
consumePendingConnect() {
|
|
5159
|
+
const pending = this.pendingLoopConnect;
|
|
5160
|
+
if (!pending)
|
|
5161
|
+
return;
|
|
5162
|
+
const error = runLoopAutoConnect(this.parent.graph, pending);
|
|
5163
|
+
this.pendingLoopConnect = undefined;
|
|
5164
|
+
return error;
|
|
5165
|
+
}
|
|
5166
|
+
finalizeAndReturn(childGraph) {
|
|
5167
|
+
this.finalizeTemplate(childGraph);
|
|
5168
|
+
const error = this.consumePendingConnect();
|
|
5169
|
+
if (error)
|
|
5170
|
+
this.parent.builder.setError(error);
|
|
5171
|
+
return this.parent;
|
|
5172
|
+
}
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
// src/task-graph/WorkflowBuilder.ts
|
|
5176
|
+
import { getLogger as getLogger8, uuid4 as uuid47 } from "@workglow/util";
|
|
5177
|
+
|
|
4816
5178
|
// src/task-graph/ConditionalBuilder.ts
|
|
4817
|
-
import { uuid4 as
|
|
5179
|
+
import { uuid4 as uuid46 } from "@workglow/util";
|
|
4818
5180
|
class ConditionalBuilder {
|
|
4819
5181
|
workflow;
|
|
4820
5182
|
condition;
|
|
@@ -4853,7 +5215,7 @@ class ConditionalBuilder {
|
|
|
4853
5215
|
});
|
|
4854
5216
|
}
|
|
4855
5217
|
const conditionalTask = new ConditionalTask({
|
|
4856
|
-
id:
|
|
5218
|
+
id: uuid46(),
|
|
4857
5219
|
branches,
|
|
4858
5220
|
exclusive: true,
|
|
4859
5221
|
defaultBranch: this.elseSpec ? elsePort : undefined
|
|
@@ -4867,57 +5229,334 @@ class ConditionalBuilder {
|
|
|
4867
5229
|
this.workflow.graph.addTask(elseTask);
|
|
4868
5230
|
this.workflow.graph.addDataflow(new Dataflow(conditionalTask.id, elsePort, elseTask.id, "*"));
|
|
4869
5231
|
}
|
|
4870
|
-
return this.workflow;
|
|
5232
|
+
return this.workflow;
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
function instantiate(spec) {
|
|
5236
|
+
const config = {
|
|
5237
|
+
id: uuid46(),
|
|
5238
|
+
...spec.config,
|
|
5239
|
+
defaults: spec.input
|
|
5240
|
+
};
|
|
5241
|
+
return new spec.taskClass(config);
|
|
5242
|
+
}
|
|
5243
|
+
|
|
5244
|
+
// src/task-graph/WorkflowPipe.ts
|
|
5245
|
+
function getLastTask(workflow) {
|
|
5246
|
+
const tasks = workflow.graph.getTasks();
|
|
5247
|
+
return tasks.length > 0 ? tasks[tasks.length - 1] : undefined;
|
|
5248
|
+
}
|
|
5249
|
+
function connect(source, target, workflow) {
|
|
5250
|
+
workflow.graph.addDataflow(new Dataflow(source.id, DATAFLOW_ALL_PORTS, target.id, DATAFLOW_ALL_PORTS));
|
|
5251
|
+
}
|
|
5252
|
+
function pipe(args, workflow = new Workflow) {
|
|
5253
|
+
let previousTask = getLastTask(workflow);
|
|
5254
|
+
const tasks = args.map((arg) => ensureTask(arg));
|
|
5255
|
+
tasks.forEach((task) => {
|
|
5256
|
+
workflow.graph.addTask(task);
|
|
5257
|
+
if (previousTask) {
|
|
5258
|
+
connect(previousTask, task, workflow);
|
|
5259
|
+
}
|
|
5260
|
+
previousTask = task;
|
|
5261
|
+
});
|
|
5262
|
+
return workflow;
|
|
5263
|
+
}
|
|
5264
|
+
function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
|
|
5265
|
+
let previousTask = getLastTask(workflow);
|
|
5266
|
+
const tasks = args.map((arg) => ensureTask(arg));
|
|
5267
|
+
const config = {
|
|
5268
|
+
compoundMerge: mergeFn
|
|
5269
|
+
};
|
|
5270
|
+
const name = `‖${args.map((_arg) => "\uD835\uDC53").join("‖")}‖`;
|
|
5271
|
+
|
|
5272
|
+
class ParallelTask extends GraphAsTask {
|
|
5273
|
+
static type = name;
|
|
5274
|
+
}
|
|
5275
|
+
const mergeTask = new ParallelTask(config);
|
|
5276
|
+
mergeTask.subGraph.addTasks(tasks);
|
|
5277
|
+
workflow.graph.addTask(mergeTask);
|
|
5278
|
+
if (previousTask) {
|
|
5279
|
+
connect(previousTask, mergeTask, workflow);
|
|
5280
|
+
}
|
|
5281
|
+
return workflow;
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
// src/task-graph/WorkflowBuilder.ts
|
|
5285
|
+
class WorkflowBuilder {
|
|
5286
|
+
_facade;
|
|
5287
|
+
_dataFlows = [];
|
|
5288
|
+
_error = "";
|
|
5289
|
+
_registry;
|
|
5290
|
+
_loopContext;
|
|
5291
|
+
constructor(facade, registry, loopContext) {
|
|
5292
|
+
this._facade = facade;
|
|
5293
|
+
this._registry = registry;
|
|
5294
|
+
this._loopContext = loopContext;
|
|
5295
|
+
}
|
|
5296
|
+
get error() {
|
|
5297
|
+
return this._error;
|
|
5298
|
+
}
|
|
5299
|
+
get registry() {
|
|
5300
|
+
return this._registry;
|
|
5301
|
+
}
|
|
5302
|
+
get loopContext() {
|
|
5303
|
+
return this._loopContext;
|
|
5304
|
+
}
|
|
5305
|
+
setError(message) {
|
|
5306
|
+
this._error = message;
|
|
5307
|
+
}
|
|
5308
|
+
resetState() {
|
|
5309
|
+
this._dataFlows = [];
|
|
5310
|
+
this._error = "";
|
|
5311
|
+
}
|
|
5312
|
+
addTaskInstance(taskClass, config) {
|
|
5313
|
+
const task = new taskClass(config, this._registry ? { registry: this._registry } : undefined);
|
|
5314
|
+
const id = this._facade.graph.addTask(task);
|
|
5315
|
+
this._facade.events.emit("changed", id);
|
|
5316
|
+
return task;
|
|
5317
|
+
}
|
|
5318
|
+
addTaskWithAutoConnect(taskClass, input = {}, config = {}) {
|
|
5319
|
+
this._error = "";
|
|
5320
|
+
const parent = getLastTask(this._facade);
|
|
5321
|
+
const task = this.addTaskInstance(taskClass, {
|
|
5322
|
+
id: uuid47(),
|
|
5323
|
+
...config,
|
|
5324
|
+
defaults: input
|
|
5325
|
+
});
|
|
5326
|
+
if (this._dataFlows.length > 0) {
|
|
5327
|
+
this._dataFlows.forEach((dataflow) => {
|
|
5328
|
+
const taskSchema = task.inputSchema();
|
|
5329
|
+
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined && taskSchema.additionalProperties !== true || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
5330
|
+
this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.id}`;
|
|
5331
|
+
getLogger8().error(this._error);
|
|
5332
|
+
return;
|
|
5333
|
+
}
|
|
5334
|
+
dataflow.targetTaskId = task.id;
|
|
5335
|
+
this._facade.graph.addDataflow(dataflow);
|
|
5336
|
+
});
|
|
5337
|
+
this._dataFlows = [];
|
|
5338
|
+
}
|
|
5339
|
+
if (parent) {
|
|
5340
|
+
const nodes = this._facade.graph.getTasks();
|
|
5341
|
+
const parentIndex = nodes.findIndex((n) => n.id === parent.id);
|
|
5342
|
+
const earlierTasks = [];
|
|
5343
|
+
for (let i = parentIndex - 1;i >= 0; i--) {
|
|
5344
|
+
earlierTasks.push(nodes[i]);
|
|
5345
|
+
}
|
|
5346
|
+
const providedInputKeys = new Set(Object.keys(input || {}));
|
|
5347
|
+
const connectedInputKeys = new Set(this._facade.graph.getSourceDataflows(task.id).map((df) => df.targetTaskPortId));
|
|
5348
|
+
const result = autoConnect(this._facade.graph, parent, task, {
|
|
5349
|
+
providedInputKeys,
|
|
5350
|
+
connectedInputKeys,
|
|
5351
|
+
earlierTasks
|
|
5352
|
+
});
|
|
5353
|
+
if (result.error) {
|
|
5354
|
+
if (this._loopContext !== undefined) {
|
|
5355
|
+
this._error = result.error;
|
|
5356
|
+
getLogger8().warn(this._error);
|
|
5357
|
+
} else {
|
|
5358
|
+
this._error = result.error + " Task not added.";
|
|
5359
|
+
getLogger8().error(this._error);
|
|
5360
|
+
this._facade.graph.removeTask(task.id);
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5364
|
+
if (!this._error) {
|
|
5365
|
+
updateBoundaryTaskSchemas(this._facade.graph);
|
|
5366
|
+
}
|
|
5367
|
+
return this._facade;
|
|
5368
|
+
}
|
|
5369
|
+
addLoopTask(taskClass, config = {}) {
|
|
5370
|
+
this._error = "";
|
|
5371
|
+
const parent = getLastTask(this._facade);
|
|
5372
|
+
const schema = taskClass.configSchema?.();
|
|
5373
|
+
const required = typeof schema === "object" && schema !== null ? schema.required : undefined;
|
|
5374
|
+
const needsMaxIterations = Array.isArray(required) && required.includes("maxIterations");
|
|
5375
|
+
const resolvedConfig = needsMaxIterations && config.maxIterations === undefined ? { ...config, maxIterations: "unbounded" } : config;
|
|
5376
|
+
const task = this.addTaskInstance(taskClass, { id: uuid47(), ...resolvedConfig });
|
|
5377
|
+
if (this._dataFlows.length > 0) {
|
|
5378
|
+
this._dataFlows.forEach((dataflow) => {
|
|
5379
|
+
const taskSchema = task.inputSchema();
|
|
5380
|
+
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined && taskSchema.additionalProperties !== true || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
5381
|
+
this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.id}`;
|
|
5382
|
+
getLogger8().error(this._error);
|
|
5383
|
+
return;
|
|
5384
|
+
}
|
|
5385
|
+
dataflow.targetTaskId = task.id;
|
|
5386
|
+
this._facade.graph.addDataflow(dataflow);
|
|
5387
|
+
});
|
|
5388
|
+
this._dataFlows = [];
|
|
5389
|
+
}
|
|
5390
|
+
const FacadeCtor = this._facade.constructor;
|
|
5391
|
+
const loopBuilder = new FacadeCtor(this._facade.outputCache(), this._facade, task, this._registry);
|
|
5392
|
+
if (parent) {
|
|
5393
|
+
const childCtx = loopBuilder.builder.loopContext;
|
|
5394
|
+
if (childCtx) {
|
|
5395
|
+
childCtx.pendingLoopConnect = { parent, iteratorTask: task };
|
|
5396
|
+
}
|
|
5397
|
+
}
|
|
5398
|
+
return loopBuilder;
|
|
5399
|
+
}
|
|
5400
|
+
connect(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId) {
|
|
5401
|
+
const sourceTask = this._facade.graph.getTask(sourceTaskId);
|
|
5402
|
+
const targetTask = this._facade.graph.getTask(targetTaskId);
|
|
5403
|
+
if (!sourceTask || !targetTask) {
|
|
5404
|
+
throw new WorkflowError("Source or target task not found");
|
|
5405
|
+
}
|
|
5406
|
+
const sourceSchema = sourceTask.outputSchema();
|
|
5407
|
+
const targetSchema = targetTask.inputSchema();
|
|
5408
|
+
if (typeof sourceSchema === "boolean") {
|
|
5409
|
+
if (sourceSchema === false) {
|
|
5410
|
+
throw new WorkflowError(`Source task has schema 'false' and outputs nothing`);
|
|
5411
|
+
}
|
|
5412
|
+
} else if (!sourceSchema.properties?.[sourceTaskPortId]) {
|
|
5413
|
+
throw new WorkflowError(`Output ${sourceTaskPortId} not found on source task`);
|
|
5414
|
+
}
|
|
5415
|
+
if (typeof targetSchema === "boolean") {
|
|
5416
|
+
if (targetSchema === false) {
|
|
5417
|
+
throw new WorkflowError(`Target task has schema 'false' and accepts no inputs`);
|
|
5418
|
+
}
|
|
5419
|
+
if (targetSchema === true) {}
|
|
5420
|
+
} else if (targetSchema.additionalProperties === true) {} else if (!targetSchema.properties?.[targetTaskPortId]) {
|
|
5421
|
+
throw new WorkflowError(`Input ${targetTaskPortId} not found on target task`);
|
|
5422
|
+
}
|
|
5423
|
+
const dataflow = new Dataflow(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
|
|
5424
|
+
this._facade.graph.addDataflow(dataflow);
|
|
5425
|
+
}
|
|
5426
|
+
rename(source, target, indexOrOptions = -1) {
|
|
5427
|
+
this._error = "";
|
|
5428
|
+
const index = typeof indexOrOptions === "number" ? indexOrOptions : indexOrOptions.index ?? -1;
|
|
5429
|
+
const transforms = typeof indexOrOptions === "number" ? undefined : indexOrOptions.transforms;
|
|
5430
|
+
const nodes = this._facade.graph.getTasks();
|
|
5431
|
+
if (-index > nodes.length) {
|
|
5432
|
+
const errorMsg = `Back index greater than number of tasks`;
|
|
5433
|
+
this._error = errorMsg;
|
|
5434
|
+
getLogger8().error(this._error);
|
|
5435
|
+
throw new WorkflowError(errorMsg);
|
|
5436
|
+
}
|
|
5437
|
+
const lastNode = nodes[nodes.length + index];
|
|
5438
|
+
const outputSchema = lastNode.outputSchema();
|
|
5439
|
+
if (typeof outputSchema === "boolean") {
|
|
5440
|
+
if (outputSchema === false && source !== DATAFLOW_ALL_PORTS) {
|
|
5441
|
+
const errorMsg = `Task ${lastNode.id} has schema 'false' and outputs nothing`;
|
|
5442
|
+
this._error = errorMsg;
|
|
5443
|
+
getLogger8().error(this._error);
|
|
5444
|
+
throw new WorkflowError(errorMsg);
|
|
5445
|
+
}
|
|
5446
|
+
} else if (!outputSchema.properties?.[source] && source !== DATAFLOW_ALL_PORTS) {
|
|
5447
|
+
const errorMsg = `Output ${source} not found on task ${lastNode.id}`;
|
|
5448
|
+
this._error = errorMsg;
|
|
5449
|
+
getLogger8().error(this._error);
|
|
5450
|
+
throw new WorkflowError(errorMsg);
|
|
5451
|
+
}
|
|
5452
|
+
const df = new Dataflow(lastNode.id, source, undefined, target);
|
|
5453
|
+
if (transforms && transforms.length > 0)
|
|
5454
|
+
df.setTransforms(transforms);
|
|
5455
|
+
this._dataFlows.push(df);
|
|
5456
|
+
}
|
|
5457
|
+
onError(handler) {
|
|
5458
|
+
this._error = "";
|
|
5459
|
+
const parent = getLastTask(this._facade);
|
|
5460
|
+
if (!parent) {
|
|
5461
|
+
this._error = "onError() requires a preceding task in the workflow";
|
|
5462
|
+
getLogger8().error(this._error);
|
|
5463
|
+
throw new WorkflowError(this._error);
|
|
5464
|
+
}
|
|
5465
|
+
const handlerTask = ensureTask(handler);
|
|
5466
|
+
this._facade.graph.addTask(handlerTask);
|
|
5467
|
+
const dataflow = new Dataflow(parent.id, DATAFLOW_ERROR_PORT, handlerTask.id, DATAFLOW_ALL_PORTS);
|
|
5468
|
+
this._facade.graph.addDataflow(dataflow);
|
|
5469
|
+
this._facade.events.emit("changed", handlerTask.id);
|
|
5470
|
+
}
|
|
5471
|
+
pop() {
|
|
5472
|
+
this._error = "";
|
|
5473
|
+
const nodes = this._facade.graph.getTasks();
|
|
5474
|
+
if (nodes.length === 0) {
|
|
5475
|
+
this._error = "No tasks to remove";
|
|
5476
|
+
getLogger8().error(this._error);
|
|
5477
|
+
return;
|
|
5478
|
+
}
|
|
5479
|
+
const lastNode = nodes[nodes.length - 1];
|
|
5480
|
+
this._facade.graph.removeTask(lastNode.id);
|
|
5481
|
+
}
|
|
5482
|
+
createConditional(condition) {
|
|
5483
|
+
return new ConditionalBuilder(this._facade, condition);
|
|
4871
5484
|
}
|
|
4872
|
-
}
|
|
4873
|
-
function instantiate(spec) {
|
|
4874
|
-
const config = {
|
|
4875
|
-
id: uuid45(),
|
|
4876
|
-
...spec.config,
|
|
4877
|
-
defaults: spec.input
|
|
4878
|
-
};
|
|
4879
|
-
return new spec.taskClass(config);
|
|
4880
5485
|
}
|
|
4881
5486
|
|
|
4882
|
-
// src/task-graph/
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
5487
|
+
// src/task-graph/WorkflowCacheAdapter.ts
|
|
5488
|
+
class WorkflowCacheAdapter {
|
|
5489
|
+
_outputCache;
|
|
5490
|
+
constructor(cache) {
|
|
5491
|
+
this._outputCache = cache;
|
|
5492
|
+
}
|
|
5493
|
+
outputCache() {
|
|
5494
|
+
return this._outputCache;
|
|
5495
|
+
}
|
|
4889
5496
|
}
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
5497
|
+
|
|
5498
|
+
// src/task-graph/WorkflowEventBridge.ts
|
|
5499
|
+
class WorkflowEventBridge {
|
|
5500
|
+
_events;
|
|
5501
|
+
_attachedGraph;
|
|
5502
|
+
_entitlementUnsub;
|
|
5503
|
+
_onChanged;
|
|
5504
|
+
constructor(events) {
|
|
5505
|
+
this._events = events;
|
|
5506
|
+
this._onChanged = (id) => this._events.emit("changed", id);
|
|
5507
|
+
}
|
|
5508
|
+
attach(graph) {
|
|
5509
|
+
if (this._attachedGraph)
|
|
5510
|
+
this.detach();
|
|
5511
|
+
this._attachedGraph = graph;
|
|
5512
|
+
graph.on("task_added", this._onChanged);
|
|
5513
|
+
graph.on("task_replaced", this._onChanged);
|
|
5514
|
+
graph.on("task_removed", this._onChanged);
|
|
5515
|
+
graph.on("dataflow_added", this._onChanged);
|
|
5516
|
+
graph.on("dataflow_replaced", this._onChanged);
|
|
5517
|
+
graph.on("dataflow_removed", this._onChanged);
|
|
5518
|
+
this._entitlementUnsub = graph.subscribeToTaskEntitlements((entitlements) => this._events.emit("entitlementChange", entitlements));
|
|
5519
|
+
}
|
|
5520
|
+
detach() {
|
|
5521
|
+
const graph = this._attachedGraph;
|
|
5522
|
+
if (!graph)
|
|
5523
|
+
return;
|
|
5524
|
+
graph.off("task_added", this._onChanged);
|
|
5525
|
+
graph.off("task_replaced", this._onChanged);
|
|
5526
|
+
graph.off("task_removed", this._onChanged);
|
|
5527
|
+
graph.off("dataflow_added", this._onChanged);
|
|
5528
|
+
graph.off("dataflow_replaced", this._onChanged);
|
|
5529
|
+
graph.off("dataflow_removed", this._onChanged);
|
|
5530
|
+
this._entitlementUnsub?.();
|
|
5531
|
+
this._entitlementUnsub = undefined;
|
|
5532
|
+
this._attachedGraph = undefined;
|
|
5533
|
+
}
|
|
5534
|
+
beginRun() {
|
|
5535
|
+
const graph = this._attachedGraph;
|
|
5536
|
+
if (!graph)
|
|
5537
|
+
return;
|
|
5538
|
+
return graph.subscribeToTaskStreaming({
|
|
5539
|
+
onStreamStart: (taskId) => this._events.emit("stream_start", taskId),
|
|
5540
|
+
onStreamChunk: (taskId, event) => this._events.emit("stream_chunk", taskId, event),
|
|
5541
|
+
onStreamEnd: (taskId, output) => this._events.emit("stream_end", taskId, output)
|
|
5542
|
+
});
|
|
5543
|
+
}
|
|
4901
5544
|
}
|
|
4902
|
-
function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
|
|
4903
|
-
let previousTask = getLastTask(workflow);
|
|
4904
|
-
const tasks = args.map((arg) => ensureTask(arg));
|
|
4905
|
-
const config = {
|
|
4906
|
-
compoundMerge: mergeFn
|
|
4907
|
-
};
|
|
4908
|
-
const name = `‖${args.map((_arg) => "\uD835\uDC53").join("‖")}‖`;
|
|
4909
5545
|
|
|
4910
|
-
|
|
4911
|
-
|
|
5546
|
+
// src/task-graph/WorkflowRunContext.ts
|
|
5547
|
+
class WorkflowRunContext {
|
|
5548
|
+
abortController;
|
|
5549
|
+
unsubStreaming;
|
|
5550
|
+
constructor() {
|
|
5551
|
+
this.abortController = new AbortController;
|
|
4912
5552
|
}
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
if (previousTask) {
|
|
4917
|
-
connect(previousTask, mergeTask, workflow);
|
|
5553
|
+
dispose() {
|
|
5554
|
+
this.unsubStreaming?.();
|
|
5555
|
+
this.unsubStreaming = undefined;
|
|
4918
5556
|
}
|
|
4919
|
-
return workflow;
|
|
4920
5557
|
}
|
|
5558
|
+
|
|
5559
|
+
// src/task-graph/WorkflowFactories.ts
|
|
4921
5560
|
function CreateWorkflow(taskClass) {
|
|
4922
5561
|
return Workflow.createWorkflow(taskClass);
|
|
4923
5562
|
}
|
|
@@ -4934,42 +5573,6 @@ function CreateEndLoopWorkflow(methodName) {
|
|
|
4934
5573
|
return this.finalizeAndReturn();
|
|
4935
5574
|
};
|
|
4936
5575
|
}
|
|
4937
|
-
var TYPED_ARRAY_FORMAT_PREFIX = "TypedArray";
|
|
4938
|
-
function schemaHasTypedArrayFormat(schema) {
|
|
4939
|
-
if (typeof schema === "boolean")
|
|
4940
|
-
return false;
|
|
4941
|
-
if (!schema || typeof schema !== "object" || Array.isArray(schema))
|
|
4942
|
-
return false;
|
|
4943
|
-
const s = schema;
|
|
4944
|
-
if (typeof s.format === "string" && s.format.startsWith(TYPED_ARRAY_FORMAT_PREFIX)) {
|
|
4945
|
-
return true;
|
|
4946
|
-
}
|
|
4947
|
-
const checkUnion = (schemas) => {
|
|
4948
|
-
if (!Array.isArray(schemas))
|
|
4949
|
-
return false;
|
|
4950
|
-
return schemas.some((sub) => schemaHasTypedArrayFormat(sub));
|
|
4951
|
-
};
|
|
4952
|
-
if (checkUnion(s.oneOf) || checkUnion(s.anyOf))
|
|
4953
|
-
return true;
|
|
4954
|
-
const items = s.items;
|
|
4955
|
-
if (items && typeof items === "object" && !Array.isArray(items)) {
|
|
4956
|
-
if (schemaHasTypedArrayFormat(items))
|
|
4957
|
-
return true;
|
|
4958
|
-
}
|
|
4959
|
-
return false;
|
|
4960
|
-
}
|
|
4961
|
-
function hasVectorOutput(task) {
|
|
4962
|
-
const outputSchema = task.outputSchema();
|
|
4963
|
-
if (typeof outputSchema === "boolean" || !outputSchema?.properties)
|
|
4964
|
-
return false;
|
|
4965
|
-
return Object.values(outputSchema.properties).some((prop) => schemaHasTypedArrayFormat(prop));
|
|
4966
|
-
}
|
|
4967
|
-
function hasVectorLikeInput(input) {
|
|
4968
|
-
if (!input || typeof input !== "object")
|
|
4969
|
-
return false;
|
|
4970
|
-
const v = input.vectors;
|
|
4971
|
-
return Array.isArray(v) && v.length > 0 && typeof v[0] === "object" && v[0] !== null && ArrayBuffer.isView(v[0]);
|
|
4972
|
-
}
|
|
4973
5576
|
function CreateAdaptiveWorkflow(scalarClass, vectorClass) {
|
|
4974
5577
|
const scalarHelper = Workflow.createWorkflow(scalarClass);
|
|
4975
5578
|
const vectorHelper = Workflow.createWorkflow(vectorClass);
|
|
@@ -4983,90 +5586,45 @@ function CreateAdaptiveWorkflow(scalarClass, vectorClass) {
|
|
|
4983
5586
|
};
|
|
4984
5587
|
}
|
|
4985
5588
|
|
|
5589
|
+
// src/task-graph/WorkflowTask.ts
|
|
4986
5590
|
class WorkflowTask extends GraphAsTask {
|
|
4987
5591
|
static type = "Workflow";
|
|
4988
5592
|
static compoundMerge = PROPERTY_ARRAY;
|
|
4989
5593
|
}
|
|
4990
5594
|
|
|
5595
|
+
// src/task-graph/Workflow.ts
|
|
4991
5596
|
class Workflow {
|
|
4992
5597
|
constructor(cache, parent, iteratorTask, registry) {
|
|
4993
|
-
this.
|
|
4994
|
-
this.
|
|
4995
|
-
|
|
4996
|
-
this.
|
|
4997
|
-
this._graph = new TaskGraph({ outputCache: this._outputCache });
|
|
5598
|
+
this._cache = new WorkflowCacheAdapter(cache);
|
|
5599
|
+
this._graph = new TaskGraph({ outputCache: this._cache.outputCache() });
|
|
5600
|
+
const loopContext = parent && iteratorTask ? new LoopBuilderContext(parent, iteratorTask) : undefined;
|
|
5601
|
+
this._builder = new WorkflowBuilder(this, registry ?? parent?.builderRegistry, loopContext);
|
|
4998
5602
|
if (!parent) {
|
|
4999
|
-
this.
|
|
5000
|
-
this.
|
|
5603
|
+
this._bridge = new WorkflowEventBridge(this.events);
|
|
5604
|
+
this._bridge.attach(this._graph);
|
|
5001
5605
|
}
|
|
5002
5606
|
}
|
|
5003
5607
|
_graph;
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5608
|
+
_cache;
|
|
5609
|
+
_bridge;
|
|
5610
|
+
_builder;
|
|
5611
|
+
_currentRun;
|
|
5612
|
+
get builderRegistry() {
|
|
5613
|
+
return this._builder.registry;
|
|
5614
|
+
}
|
|
5615
|
+
get builder() {
|
|
5616
|
+
return this._builder;
|
|
5617
|
+
}
|
|
5013
5618
|
outputCache() {
|
|
5014
|
-
return this.
|
|
5619
|
+
return this._cache.outputCache();
|
|
5015
5620
|
}
|
|
5016
5621
|
get isLoopBuilder() {
|
|
5017
|
-
return this.
|
|
5622
|
+
return this._builder.loopContext !== undefined;
|
|
5018
5623
|
}
|
|
5019
5624
|
events = new EventEmitter5;
|
|
5020
5625
|
static createWorkflow(taskClass) {
|
|
5021
5626
|
const helper = function(input = {}, config = {}) {
|
|
5022
|
-
this.
|
|
5023
|
-
const parent = getLastTask(this);
|
|
5024
|
-
const task = this.addTaskToGraph(taskClass, {
|
|
5025
|
-
id: uuid46(),
|
|
5026
|
-
...config,
|
|
5027
|
-
defaults: input
|
|
5028
|
-
});
|
|
5029
|
-
if (this._dataFlows.length > 0) {
|
|
5030
|
-
this._dataFlows.forEach((dataflow) => {
|
|
5031
|
-
const taskSchema = task.inputSchema();
|
|
5032
|
-
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined && taskSchema.additionalProperties !== true || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
5033
|
-
this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.id}`;
|
|
5034
|
-
getLogger5().error(this._error);
|
|
5035
|
-
return;
|
|
5036
|
-
}
|
|
5037
|
-
dataflow.targetTaskId = task.id;
|
|
5038
|
-
this.graph.addDataflow(dataflow);
|
|
5039
|
-
});
|
|
5040
|
-
this._dataFlows = [];
|
|
5041
|
-
}
|
|
5042
|
-
if (parent) {
|
|
5043
|
-
const nodes = this._graph.getTasks();
|
|
5044
|
-
const parentIndex = nodes.findIndex((n) => n.id === parent.id);
|
|
5045
|
-
const earlierTasks = [];
|
|
5046
|
-
for (let i = parentIndex - 1;i >= 0; i--) {
|
|
5047
|
-
earlierTasks.push(nodes[i]);
|
|
5048
|
-
}
|
|
5049
|
-
const providedInputKeys = new Set(Object.keys(input || {}));
|
|
5050
|
-
const connectedInputKeys = new Set(this.graph.getSourceDataflows(task.id).map((df) => df.targetTaskPortId));
|
|
5051
|
-
const result = Workflow.autoConnect(this.graph, parent, task, {
|
|
5052
|
-
providedInputKeys,
|
|
5053
|
-
connectedInputKeys,
|
|
5054
|
-
earlierTasks
|
|
5055
|
-
});
|
|
5056
|
-
if (result.error) {
|
|
5057
|
-
if (this.isLoopBuilder) {
|
|
5058
|
-
this._error = result.error;
|
|
5059
|
-
getLogger5().warn(this._error);
|
|
5060
|
-
} else {
|
|
5061
|
-
this._error = result.error + " Task not added.";
|
|
5062
|
-
getLogger5().error(this._error);
|
|
5063
|
-
this.graph.removeTask(task.id);
|
|
5064
|
-
}
|
|
5065
|
-
}
|
|
5066
|
-
}
|
|
5067
|
-
if (!this._error) {
|
|
5068
|
-
Workflow.updateBoundaryTaskSchemas(this._graph);
|
|
5069
|
-
}
|
|
5627
|
+
this._builder.addTaskWithAutoConnect(taskClass, input, config);
|
|
5070
5628
|
return this;
|
|
5071
5629
|
};
|
|
5072
5630
|
helper.type = taskClass.runtype ?? taskClass.type;
|
|
@@ -5081,15 +5639,14 @@ class Workflow {
|
|
|
5081
5639
|
return this._graph;
|
|
5082
5640
|
}
|
|
5083
5641
|
set graph(value) {
|
|
5084
|
-
this.
|
|
5085
|
-
this.
|
|
5086
|
-
this.clearEvents();
|
|
5642
|
+
this._builder.resetState();
|
|
5643
|
+
this._bridge?.detach();
|
|
5087
5644
|
this._graph = value;
|
|
5088
|
-
this.
|
|
5645
|
+
this._bridge?.attach(this._graph);
|
|
5089
5646
|
this.events.emit("reset");
|
|
5090
5647
|
}
|
|
5091
5648
|
get error() {
|
|
5092
|
-
return this.
|
|
5649
|
+
return this._builder.error;
|
|
5093
5650
|
}
|
|
5094
5651
|
on(name, fn) {
|
|
5095
5652
|
this.events.on(name, fn);
|
|
@@ -5104,26 +5661,23 @@ class Workflow {
|
|
|
5104
5661
|
return this.events.waitOn(name);
|
|
5105
5662
|
}
|
|
5106
5663
|
async run(input = {}, config) {
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
return
|
|
5664
|
+
const loopContext = this._builder.loopContext;
|
|
5665
|
+
if (loopContext) {
|
|
5666
|
+
loopContext.finalizeTemplate(this._graph);
|
|
5667
|
+
const error = loopContext.consumePendingConnect();
|
|
5668
|
+
if (error)
|
|
5669
|
+
loopContext.parent.builder.setError(error);
|
|
5670
|
+
return loopContext.parent.run(input, config);
|
|
5114
5671
|
}
|
|
5115
5672
|
this.events.emit("start");
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
onStreamChunk: (taskId, event) => this.events.emit("stream_chunk", taskId, event),
|
|
5120
|
-
onStreamEnd: (taskId, output) => this.events.emit("stream_end", taskId, output)
|
|
5121
|
-
});
|
|
5673
|
+
const ctx = new WorkflowRunContext;
|
|
5674
|
+
this._currentRun = ctx;
|
|
5675
|
+
ctx.unsubStreaming = this._bridge?.beginRun();
|
|
5122
5676
|
try {
|
|
5123
5677
|
const output = await this.graph.run(input, {
|
|
5124
|
-
parentSignal:
|
|
5125
|
-
outputCache: this.
|
|
5126
|
-
registry: config?.registry ?? this.
|
|
5678
|
+
parentSignal: ctx.abortController.signal,
|
|
5679
|
+
outputCache: this._cache.outputCache(),
|
|
5680
|
+
registry: config?.registry ?? this._builder.registry,
|
|
5127
5681
|
resourceScope: config?.resourceScope
|
|
5128
5682
|
});
|
|
5129
5683
|
const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
|
|
@@ -5133,26 +5687,20 @@ class Workflow {
|
|
|
5133
5687
|
this.events.emit("error", String(error));
|
|
5134
5688
|
throw error;
|
|
5135
5689
|
} finally {
|
|
5136
|
-
|
|
5137
|
-
this.
|
|
5690
|
+
ctx.dispose();
|
|
5691
|
+
if (this._currentRun === ctx)
|
|
5692
|
+
this._currentRun = undefined;
|
|
5138
5693
|
}
|
|
5139
5694
|
}
|
|
5140
5695
|
async abort() {
|
|
5141
|
-
|
|
5142
|
-
|
|
5696
|
+
const loopContext = this._builder.loopContext;
|
|
5697
|
+
if (loopContext) {
|
|
5698
|
+
return loopContext.parent.abort();
|
|
5143
5699
|
}
|
|
5144
|
-
this.
|
|
5700
|
+
this._currentRun?.abortController.abort();
|
|
5145
5701
|
}
|
|
5146
5702
|
pop() {
|
|
5147
|
-
this.
|
|
5148
|
-
const nodes = this._graph.getTasks();
|
|
5149
|
-
if (nodes.length === 0) {
|
|
5150
|
-
this._error = "No tasks to remove";
|
|
5151
|
-
getLogger5().error(this._error);
|
|
5152
|
-
return this;
|
|
5153
|
-
}
|
|
5154
|
-
const lastNode = nodes[nodes.length - 1];
|
|
5155
|
-
this._graph.removeTask(lastNode.id);
|
|
5703
|
+
this._builder.pop();
|
|
5156
5704
|
return this;
|
|
5157
5705
|
}
|
|
5158
5706
|
toJSON(options = { withBoundaryNodes: true }) {
|
|
@@ -5177,50 +5725,11 @@ class Workflow {
|
|
|
5177
5725
|
return parallel(args, mergeFn ?? PROPERTY_ARRAY, new Workflow);
|
|
5178
5726
|
}
|
|
5179
5727
|
rename(source, target, indexOrOptions = -1) {
|
|
5180
|
-
this.
|
|
5181
|
-
const index = typeof indexOrOptions === "number" ? indexOrOptions : indexOrOptions.index ?? -1;
|
|
5182
|
-
const transforms = typeof indexOrOptions === "number" ? undefined : indexOrOptions.transforms;
|
|
5183
|
-
const nodes = this._graph.getTasks();
|
|
5184
|
-
if (-index > nodes.length) {
|
|
5185
|
-
const errorMsg = `Back index greater than number of tasks`;
|
|
5186
|
-
this._error = errorMsg;
|
|
5187
|
-
getLogger5().error(this._error);
|
|
5188
|
-
throw new WorkflowError(errorMsg);
|
|
5189
|
-
}
|
|
5190
|
-
const lastNode = nodes[nodes.length + index];
|
|
5191
|
-
const outputSchema = lastNode.outputSchema();
|
|
5192
|
-
if (typeof outputSchema === "boolean") {
|
|
5193
|
-
if (outputSchema === false && source !== DATAFLOW_ALL_PORTS) {
|
|
5194
|
-
const errorMsg = `Task ${lastNode.id} has schema 'false' and outputs nothing`;
|
|
5195
|
-
this._error = errorMsg;
|
|
5196
|
-
getLogger5().error(this._error);
|
|
5197
|
-
throw new WorkflowError(errorMsg);
|
|
5198
|
-
}
|
|
5199
|
-
} else if (!outputSchema.properties?.[source] && source !== DATAFLOW_ALL_PORTS) {
|
|
5200
|
-
const errorMsg = `Output ${source} not found on task ${lastNode.id}`;
|
|
5201
|
-
this._error = errorMsg;
|
|
5202
|
-
getLogger5().error(this._error);
|
|
5203
|
-
throw new WorkflowError(errorMsg);
|
|
5204
|
-
}
|
|
5205
|
-
const df = new Dataflow(lastNode.id, source, undefined, target);
|
|
5206
|
-
if (transforms && transforms.length > 0)
|
|
5207
|
-
df.setTransforms(transforms);
|
|
5208
|
-
this._dataFlows.push(df);
|
|
5728
|
+
this._builder.rename(source, target, indexOrOptions);
|
|
5209
5729
|
return this;
|
|
5210
5730
|
}
|
|
5211
5731
|
onError(handler) {
|
|
5212
|
-
this.
|
|
5213
|
-
const parent = getLastTask(this);
|
|
5214
|
-
if (!parent) {
|
|
5215
|
-
this._error = "onError() requires a preceding task in the workflow";
|
|
5216
|
-
getLogger5().error(this._error);
|
|
5217
|
-
throw new WorkflowError(this._error);
|
|
5218
|
-
}
|
|
5219
|
-
const handlerTask = ensureTask(handler);
|
|
5220
|
-
this.graph.addTask(handlerTask);
|
|
5221
|
-
const dataflow = new Dataflow(parent.id, DATAFLOW_ERROR_PORT, handlerTask.id, DATAFLOW_ALL_PORTS);
|
|
5222
|
-
this.graph.addDataflow(dataflow);
|
|
5223
|
-
this.events.emit("changed", handlerTask.id);
|
|
5732
|
+
this._builder.onError(handler);
|
|
5224
5733
|
return this;
|
|
5225
5734
|
}
|
|
5226
5735
|
toTaskGraph() {
|
|
@@ -5232,248 +5741,58 @@ class Workflow {
|
|
|
5232
5741
|
return task;
|
|
5233
5742
|
}
|
|
5234
5743
|
reset() {
|
|
5235
|
-
if (this.
|
|
5744
|
+
if (this._builder.loopContext) {
|
|
5236
5745
|
throw new WorkflowError("Cannot reset a loop workflow. Call reset() on the parent workflow.");
|
|
5237
5746
|
}
|
|
5238
|
-
this.
|
|
5747
|
+
this._bridge?.detach();
|
|
5239
5748
|
this._graph = new TaskGraph({
|
|
5240
|
-
outputCache: this.
|
|
5749
|
+
outputCache: this._cache.outputCache()
|
|
5241
5750
|
});
|
|
5242
|
-
this.
|
|
5243
|
-
this.
|
|
5244
|
-
this.setupEvents();
|
|
5751
|
+
this._builder.resetState();
|
|
5752
|
+
this._bridge?.attach(this._graph);
|
|
5245
5753
|
this.events.emit("changed", undefined);
|
|
5246
5754
|
this.events.emit("reset");
|
|
5247
5755
|
return this;
|
|
5248
5756
|
}
|
|
5249
|
-
setupEvents() {
|
|
5250
|
-
this._graph.on("task_added", this._onChanged);
|
|
5251
|
-
this._graph.on("task_replaced", this._onChanged);
|
|
5252
|
-
this._graph.on("task_removed", this._onChanged);
|
|
5253
|
-
this._graph.on("dataflow_added", this._onChanged);
|
|
5254
|
-
this._graph.on("dataflow_replaced", this._onChanged);
|
|
5255
|
-
this._graph.on("dataflow_removed", this._onChanged);
|
|
5256
|
-
this._entitlementUnsub = this._graph.subscribeToTaskEntitlements((entitlements) => this.events.emit("entitlementChange", entitlements));
|
|
5257
|
-
}
|
|
5258
|
-
clearEvents() {
|
|
5259
|
-
this._graph.off("task_added", this._onChanged);
|
|
5260
|
-
this._graph.off("task_replaced", this._onChanged);
|
|
5261
|
-
this._graph.off("task_removed", this._onChanged);
|
|
5262
|
-
this._graph.off("dataflow_added", this._onChanged);
|
|
5263
|
-
this._graph.off("dataflow_replaced", this._onChanged);
|
|
5264
|
-
this._graph.off("dataflow_removed", this._onChanged);
|
|
5265
|
-
this._entitlementUnsub?.();
|
|
5266
|
-
this._entitlementUnsub = undefined;
|
|
5267
|
-
}
|
|
5268
|
-
_onChanged(id) {
|
|
5269
|
-
this.events.emit("changed", id);
|
|
5270
|
-
}
|
|
5271
5757
|
connect(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId) {
|
|
5272
|
-
|
|
5273
|
-
const targetTask = this.graph.getTask(targetTaskId);
|
|
5274
|
-
if (!sourceTask || !targetTask) {
|
|
5275
|
-
throw new WorkflowError("Source or target task not found");
|
|
5276
|
-
}
|
|
5277
|
-
const sourceSchema = sourceTask.outputSchema();
|
|
5278
|
-
const targetSchema = targetTask.inputSchema();
|
|
5279
|
-
if (typeof sourceSchema === "boolean") {
|
|
5280
|
-
if (sourceSchema === false) {
|
|
5281
|
-
throw new WorkflowError(`Source task has schema 'false' and outputs nothing`);
|
|
5282
|
-
}
|
|
5283
|
-
} else if (!sourceSchema.properties?.[sourceTaskPortId]) {
|
|
5284
|
-
throw new WorkflowError(`Output ${sourceTaskPortId} not found on source task`);
|
|
5285
|
-
}
|
|
5286
|
-
if (typeof targetSchema === "boolean") {
|
|
5287
|
-
if (targetSchema === false) {
|
|
5288
|
-
throw new WorkflowError(`Target task has schema 'false' and accepts no inputs`);
|
|
5289
|
-
}
|
|
5290
|
-
if (targetSchema === true) {}
|
|
5291
|
-
} else if (targetSchema.additionalProperties === true) {} else if (!targetSchema.properties?.[targetTaskPortId]) {
|
|
5292
|
-
throw new WorkflowError(`Input ${targetTaskPortId} not found on target task`);
|
|
5293
|
-
}
|
|
5294
|
-
const dataflow = new Dataflow(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
|
|
5295
|
-
this.graph.addDataflow(dataflow);
|
|
5758
|
+
this._builder.connect(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
|
|
5296
5759
|
return this;
|
|
5297
5760
|
}
|
|
5298
5761
|
addTaskToGraph(taskClass, config) {
|
|
5299
|
-
|
|
5300
|
-
const id = this.graph.addTask(task);
|
|
5301
|
-
this.events.emit("changed", id);
|
|
5302
|
-
return task;
|
|
5762
|
+
return this._builder.addTaskInstance(taskClass, config);
|
|
5303
5763
|
}
|
|
5304
5764
|
addTask(taskClass, input, config) {
|
|
5305
|
-
|
|
5306
|
-
return helper.call(this, input, config);
|
|
5765
|
+
return this._builder.addTaskWithAutoConnect(taskClass, input, config);
|
|
5307
5766
|
}
|
|
5308
5767
|
addLoopTask(taskClass, config = {}) {
|
|
5309
|
-
this.
|
|
5310
|
-
const parent = getLastTask(this);
|
|
5311
|
-
const schema = taskClass.configSchema?.();
|
|
5312
|
-
const required = typeof schema === "object" && schema !== null ? schema.required : undefined;
|
|
5313
|
-
const needsMaxIterations = Array.isArray(required) && required.includes("maxIterations");
|
|
5314
|
-
const resolvedConfig = needsMaxIterations && config.maxIterations === undefined ? { ...config, maxIterations: "unbounded" } : config;
|
|
5315
|
-
const task = this.addTaskToGraph(taskClass, { id: uuid46(), ...resolvedConfig });
|
|
5316
|
-
if (this._dataFlows.length > 0) {
|
|
5317
|
-
this._dataFlows.forEach((dataflow) => {
|
|
5318
|
-
const taskSchema = task.inputSchema();
|
|
5319
|
-
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined && taskSchema.additionalProperties !== true || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
5320
|
-
this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.id}`;
|
|
5321
|
-
getLogger5().error(this._error);
|
|
5322
|
-
return;
|
|
5323
|
-
}
|
|
5324
|
-
dataflow.targetTaskId = task.id;
|
|
5325
|
-
this.graph.addDataflow(dataflow);
|
|
5326
|
-
});
|
|
5327
|
-
this._dataFlows = [];
|
|
5328
|
-
}
|
|
5329
|
-
const loopBuilder = new Workflow(this.outputCache(), this, task, this._registry);
|
|
5330
|
-
if (parent) {
|
|
5331
|
-
loopBuilder._pendingLoopConnect = { parent, iteratorTask: task };
|
|
5332
|
-
}
|
|
5333
|
-
return loopBuilder;
|
|
5768
|
+
return this._builder.addLoopTask(taskClass, config);
|
|
5334
5769
|
}
|
|
5335
5770
|
if(condition) {
|
|
5336
|
-
return
|
|
5771
|
+
return this._builder.createConditional(condition);
|
|
5337
5772
|
}
|
|
5338
5773
|
autoConnectLoopTask(pending) {
|
|
5339
5774
|
if (!pending)
|
|
5340
5775
|
return;
|
|
5341
|
-
const
|
|
5342
|
-
if (
|
|
5343
|
-
|
|
5344
|
-
const parentIndex = nodes.findIndex((n) => n.id === parent.id);
|
|
5345
|
-
const earlierTasks = [];
|
|
5346
|
-
for (let i = parentIndex - 1;i >= 0; i--) {
|
|
5347
|
-
earlierTasks.push(nodes[i]);
|
|
5348
|
-
}
|
|
5349
|
-
const result = Workflow.autoConnect(this.graph, parent, iteratorTask, {
|
|
5350
|
-
earlierTasks
|
|
5351
|
-
});
|
|
5352
|
-
if (result.error) {
|
|
5353
|
-
this._error = result.error + " Task not added.";
|
|
5354
|
-
getLogger5().error(this._error);
|
|
5355
|
-
this.graph.removeTask(iteratorTask.id);
|
|
5356
|
-
}
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
static updateBoundaryTaskSchemas(graph) {
|
|
5360
|
-
const tasks = graph.getTasks();
|
|
5361
|
-
for (const task of tasks) {
|
|
5362
|
-
if (task.type === "InputTask") {
|
|
5363
|
-
const existingConfig = task.config;
|
|
5364
|
-
const existingSchema = existingConfig?.inputSchema ?? existingConfig?.outputSchema;
|
|
5365
|
-
if (existingSchema && typeof existingSchema === "object" && existingSchema["x-ui-manual"] === true) {
|
|
5366
|
-
continue;
|
|
5367
|
-
}
|
|
5368
|
-
const outgoing = graph.getTargetDataflows(task.id);
|
|
5369
|
-
if (outgoing.length === 0)
|
|
5370
|
-
continue;
|
|
5371
|
-
const properties = {};
|
|
5372
|
-
const required = [];
|
|
5373
|
-
for (const df of outgoing) {
|
|
5374
|
-
const targetTask = graph.getTask(df.targetTaskId);
|
|
5375
|
-
if (!targetTask)
|
|
5376
|
-
continue;
|
|
5377
|
-
const targetSchema = targetTask.inputSchema();
|
|
5378
|
-
if (typeof targetSchema === "boolean")
|
|
5379
|
-
continue;
|
|
5380
|
-
const prop = targetSchema.properties?.[df.targetTaskPortId];
|
|
5381
|
-
if (prop && typeof prop !== "boolean") {
|
|
5382
|
-
properties[df.sourceTaskPortId] = prop;
|
|
5383
|
-
if (targetSchema.required?.includes(df.targetTaskPortId)) {
|
|
5384
|
-
if (!required.includes(df.sourceTaskPortId)) {
|
|
5385
|
-
required.push(df.sourceTaskPortId);
|
|
5386
|
-
}
|
|
5387
|
-
}
|
|
5388
|
-
}
|
|
5389
|
-
}
|
|
5390
|
-
const schema = {
|
|
5391
|
-
type: "object",
|
|
5392
|
-
properties,
|
|
5393
|
-
...required.length > 0 ? { required } : {},
|
|
5394
|
-
additionalProperties: false
|
|
5395
|
-
};
|
|
5396
|
-
task.config = {
|
|
5397
|
-
...task.config,
|
|
5398
|
-
inputSchema: schema,
|
|
5399
|
-
outputSchema: schema
|
|
5400
|
-
};
|
|
5401
|
-
}
|
|
5402
|
-
if (task.type === "OutputTask") {
|
|
5403
|
-
const incoming = graph.getSourceDataflows(task.id);
|
|
5404
|
-
if (incoming.length === 0)
|
|
5405
|
-
continue;
|
|
5406
|
-
const properties = {};
|
|
5407
|
-
const required = [];
|
|
5408
|
-
for (const df of incoming) {
|
|
5409
|
-
const sourceTask = graph.getTask(df.sourceTaskId);
|
|
5410
|
-
if (!sourceTask)
|
|
5411
|
-
continue;
|
|
5412
|
-
const sourceSchema = sourceTask.outputSchema();
|
|
5413
|
-
if (typeof sourceSchema === "boolean")
|
|
5414
|
-
continue;
|
|
5415
|
-
let prop = sourceSchema.properties?.[df.sourceTaskPortId];
|
|
5416
|
-
let propRequired = sourceSchema.required?.includes(df.sourceTaskPortId) ?? false;
|
|
5417
|
-
if (!prop && sourceSchema.additionalProperties === true && sourceTask.constructor.passthroughInputsToOutputs === true) {
|
|
5418
|
-
const upstreamDfs = graph.getSourceDataflows(sourceTask.id);
|
|
5419
|
-
for (const udf of upstreamDfs) {
|
|
5420
|
-
if (udf.targetTaskPortId !== df.sourceTaskPortId)
|
|
5421
|
-
continue;
|
|
5422
|
-
const upstreamTask = graph.getTask(udf.sourceTaskId);
|
|
5423
|
-
if (!upstreamTask)
|
|
5424
|
-
continue;
|
|
5425
|
-
const upstreamSchema = upstreamTask.outputSchema();
|
|
5426
|
-
if (typeof upstreamSchema === "boolean")
|
|
5427
|
-
continue;
|
|
5428
|
-
prop = upstreamSchema.properties?.[udf.sourceTaskPortId];
|
|
5429
|
-
if (prop) {
|
|
5430
|
-
propRequired = upstreamSchema.required?.includes(udf.sourceTaskPortId) ?? false;
|
|
5431
|
-
break;
|
|
5432
|
-
}
|
|
5433
|
-
}
|
|
5434
|
-
}
|
|
5435
|
-
if (prop && typeof prop !== "boolean") {
|
|
5436
|
-
properties[df.targetTaskPortId] = prop;
|
|
5437
|
-
if (propRequired && !required.includes(df.targetTaskPortId)) {
|
|
5438
|
-
required.push(df.targetTaskPortId);
|
|
5439
|
-
}
|
|
5440
|
-
}
|
|
5441
|
-
}
|
|
5442
|
-
const schema = {
|
|
5443
|
-
type: "object",
|
|
5444
|
-
properties,
|
|
5445
|
-
...required.length > 0 ? { required } : {},
|
|
5446
|
-
additionalProperties: false
|
|
5447
|
-
};
|
|
5448
|
-
task.config = {
|
|
5449
|
-
...task.config,
|
|
5450
|
-
inputSchema: schema,
|
|
5451
|
-
outputSchema: schema
|
|
5452
|
-
};
|
|
5453
|
-
}
|
|
5454
|
-
}
|
|
5776
|
+
const error = runLoopAutoConnect(this._graph, pending);
|
|
5777
|
+
if (error)
|
|
5778
|
+
this._builder.setError(error);
|
|
5455
5779
|
}
|
|
5456
5780
|
static AutoConnectOptions = Symbol("AutoConnectOptions");
|
|
5457
5781
|
static autoConnect(graph, sourceTask, targetTask, options) {
|
|
5458
5782
|
return autoConnect(graph, sourceTask, targetTask, options);
|
|
5459
5783
|
}
|
|
5460
5784
|
finalizeTemplate() {
|
|
5461
|
-
|
|
5785
|
+
const ctx = this._builder.loopContext;
|
|
5786
|
+
if (!ctx)
|
|
5462
5787
|
return;
|
|
5463
|
-
|
|
5464
|
-
this._iteratorTask.subGraph = this.graph;
|
|
5465
|
-
this._iteratorTask.validateAcyclic();
|
|
5788
|
+
ctx.finalizeTemplate(this._graph);
|
|
5466
5789
|
}
|
|
5467
5790
|
finalizeAndReturn() {
|
|
5468
|
-
|
|
5791
|
+
const ctx = this._builder.loopContext;
|
|
5792
|
+
if (!ctx) {
|
|
5469
5793
|
throw new WorkflowError("finalizeAndReturn() can only be called on loop workflows");
|
|
5470
5794
|
}
|
|
5471
|
-
this.
|
|
5472
|
-
if (this._pendingLoopConnect) {
|
|
5473
|
-
this._parentWorkflow.autoConnectLoopTask(this._pendingLoopConnect);
|
|
5474
|
-
this._pendingLoopConnect = undefined;
|
|
5475
|
-
}
|
|
5476
|
-
return this._parentWorkflow;
|
|
5795
|
+
return ctx.finalizeAndReturn(this._graph);
|
|
5477
5796
|
}
|
|
5478
5797
|
}
|
|
5479
5798
|
Workflow.prototype.group = CreateLoopWorkflow(GraphAsTask);
|
|
@@ -6159,13 +6478,13 @@ function getProfileGrants(profile) {
|
|
|
6159
6478
|
}
|
|
6160
6479
|
// src/task/FallbackTaskRunner.ts
|
|
6161
6480
|
class FallbackTaskRunner extends GraphAsTaskRunner {
|
|
6162
|
-
async executeTask(input) {
|
|
6481
|
+
async executeTask(input, _ctx) {
|
|
6163
6482
|
if (this.task.fallbackMode === "data") {
|
|
6164
6483
|
return this.executeDataFallback(input);
|
|
6165
6484
|
}
|
|
6166
6485
|
return this.executeTaskFallback(input);
|
|
6167
6486
|
}
|
|
6168
|
-
async executeTaskPreview(input) {
|
|
6487
|
+
async executeTaskPreview(input, _ctx) {
|
|
6169
6488
|
return this.task.executePreview?.(input, { own: this.own });
|
|
6170
6489
|
}
|
|
6171
6490
|
async executeTaskFallback(input) {
|
|
@@ -6176,7 +6495,7 @@ class FallbackTaskRunner extends GraphAsTaskRunner {
|
|
|
6176
6495
|
const errors = [];
|
|
6177
6496
|
const totalAttempts = tasks.length;
|
|
6178
6497
|
for (let i = 0;i < tasks.length; i++) {
|
|
6179
|
-
if (this.abortController
|
|
6498
|
+
if (this.currentCtx?.abortController.signal.aborted) {
|
|
6180
6499
|
throw new TaskAbortedError("Fallback aborted");
|
|
6181
6500
|
}
|
|
6182
6501
|
const alternativeTask = tasks[i];
|
|
@@ -6213,7 +6532,7 @@ class FallbackTaskRunner extends GraphAsTaskRunner {
|
|
|
6213
6532
|
const unsubscribeSubgraphProgress = this.task.subGraph.subscribe("graph_progress", onSubgraphProgress);
|
|
6214
6533
|
try {
|
|
6215
6534
|
for (let i = 0;i < alternatives.length; i++) {
|
|
6216
|
-
if (this.abortController
|
|
6535
|
+
if (this.currentCtx?.abortController.signal.aborted) {
|
|
6217
6536
|
throw new TaskAbortedError("Fallback aborted");
|
|
6218
6537
|
}
|
|
6219
6538
|
currentAttemptIndex = i;
|
|
@@ -6224,7 +6543,7 @@ class FallbackTaskRunner extends GraphAsTaskRunner {
|
|
|
6224
6543
|
this.resetSubgraph();
|
|
6225
6544
|
const mergedInput = { ...input, ...alternative };
|
|
6226
6545
|
const results = await this.task.subGraph.run(mergedInput, {
|
|
6227
|
-
parentSignal: this.abortController
|
|
6546
|
+
parentSignal: this.currentCtx?.abortController.signal,
|
|
6228
6547
|
outputCache: this.outputCache,
|
|
6229
6548
|
registry: this.registry
|
|
6230
6549
|
});
|
|
@@ -6446,12 +6765,12 @@ async function compactSchemaInputs(input, schema, config, visited = new Set) {
|
|
|
6446
6765
|
return compacted;
|
|
6447
6766
|
}
|
|
6448
6767
|
// src/task/IteratorTaskRunner.ts
|
|
6449
|
-
import { uuid4 as
|
|
6768
|
+
import { uuid4 as uuid48 } from "@workglow/util";
|
|
6450
6769
|
class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
6451
6770
|
aggregatingParentMapProgress = false;
|
|
6452
6771
|
mapPartialProgress = [];
|
|
6453
6772
|
mapPartialIterationCount = 0;
|
|
6454
|
-
async executeTask(input) {
|
|
6773
|
+
async executeTask(input, _ctx) {
|
|
6455
6774
|
let analysis = this.task.analyzeIterationInput(input);
|
|
6456
6775
|
const maxIterations = resolveIterationBound(this.task.config.maxIterations);
|
|
6457
6776
|
if (analysis.iterationCount > maxIterations) {
|
|
@@ -6463,7 +6782,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6463
6782
|
const result = this.task.isReduceTask() ? await this.executeReduceIterations(analysis) : await this.executeCollectIterations(analysis);
|
|
6464
6783
|
return result;
|
|
6465
6784
|
}
|
|
6466
|
-
async executeTaskPreview(input) {
|
|
6785
|
+
async executeTaskPreview(input, _ctx) {
|
|
6467
6786
|
return this.task.executePreview?.(input, { own: this.own });
|
|
6468
6787
|
}
|
|
6469
6788
|
async executeCollectIterations(analysis) {
|
|
@@ -6479,7 +6798,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6479
6798
|
this.mapPartialProgress = new Array(iterationCount).fill(0);
|
|
6480
6799
|
try {
|
|
6481
6800
|
for (let batchStart = 0;batchStart < iterationCount; batchStart += batchSize) {
|
|
6482
|
-
if (this.abortController
|
|
6801
|
+
if (this.currentCtx?.abortController.signal.aborted) {
|
|
6483
6802
|
break;
|
|
6484
6803
|
}
|
|
6485
6804
|
const batchEnd = Math.min(batchStart + batchSize, iterationCount);
|
|
@@ -6516,7 +6835,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6516
6835
|
const iterationCount = analysis.iterationCount;
|
|
6517
6836
|
let accumulator = this.task.getInitialAccumulator();
|
|
6518
6837
|
for (let index = 0;index < iterationCount; index++) {
|
|
6519
|
-
if (this.abortController
|
|
6838
|
+
if (this.currentCtx?.abortController.signal.aborted) {
|
|
6520
6839
|
break;
|
|
6521
6840
|
}
|
|
6522
6841
|
const iterationInput = this.task.buildIterationRunInput(analysis, index, iterationCount, {
|
|
@@ -6535,7 +6854,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6535
6854
|
const workerCount = Math.max(1, Math.min(concurrency, indices.length));
|
|
6536
6855
|
const workers = Array.from({ length: workerCount }, async () => {
|
|
6537
6856
|
while (true) {
|
|
6538
|
-
if (this.abortController
|
|
6857
|
+
if (this.currentCtx?.abortController.signal.aborted) {
|
|
6539
6858
|
return;
|
|
6540
6859
|
}
|
|
6541
6860
|
const position = cursor;
|
|
@@ -6558,7 +6877,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6558
6877
|
const idMap = new Map;
|
|
6559
6878
|
for (const task of graph.getTasks()) {
|
|
6560
6879
|
const ctor = task.constructor;
|
|
6561
|
-
const newId =
|
|
6880
|
+
const newId = uuid48();
|
|
6562
6881
|
idMap.set(task.config.id, newId);
|
|
6563
6882
|
const clonedConfig = { ...task.config, id: newId };
|
|
6564
6883
|
const newTask = new ctor({ ...clonedConfig, defaults: task.defaults }, task.runConfig);
|
|
@@ -6573,7 +6892,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6573
6892
|
return clone;
|
|
6574
6893
|
}
|
|
6575
6894
|
async executeSubgraphIteration(input, index, iterationCount) {
|
|
6576
|
-
if (this.abortController
|
|
6895
|
+
if (this.currentCtx?.abortController.signal.aborted) {
|
|
6577
6896
|
return;
|
|
6578
6897
|
}
|
|
6579
6898
|
const graphClone = this.cloneGraph(this.task.subGraph);
|
|
@@ -6588,7 +6907,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
6588
6907
|
const unsubscribeGraphProgress = graphClone.subscribe("graph_progress", onGraphProgress);
|
|
6589
6908
|
try {
|
|
6590
6909
|
const results = await graphClone.run(input, {
|
|
6591
|
-
parentSignal: this.abortController
|
|
6910
|
+
parentSignal: this.currentCtx?.abortController.signal,
|
|
6592
6911
|
outputCache: this.outputCache,
|
|
6593
6912
|
registry: this.registry,
|
|
6594
6913
|
resourceScope: this.resourceScope
|
|
@@ -7122,16 +7441,16 @@ class IteratorTask extends GraphAsTask {
|
|
|
7122
7441
|
|
|
7123
7442
|
// src/task/WhileTaskRunner.ts
|
|
7124
7443
|
class WhileTaskRunner extends GraphAsTaskRunner {
|
|
7125
|
-
async executeTask(input) {
|
|
7444
|
+
async executeTask(input, ctx) {
|
|
7126
7445
|
const result = await this.task.execute(input, {
|
|
7127
|
-
signal:
|
|
7446
|
+
signal: ctx.abortController.signal,
|
|
7128
7447
|
updateProgress: this.handleProgress.bind(this),
|
|
7129
7448
|
own: this.own,
|
|
7130
7449
|
registry: this.registry
|
|
7131
7450
|
});
|
|
7132
7451
|
return result;
|
|
7133
7452
|
}
|
|
7134
|
-
async executeTaskPreview(input) {
|
|
7453
|
+
async executeTaskPreview(input, _ctx) {
|
|
7135
7454
|
return this.task.executePreview?.(input, { own: this.own });
|
|
7136
7455
|
}
|
|
7137
7456
|
}
|
|
@@ -7755,7 +8074,8 @@ var defaultJobQueueFactory = async ({
|
|
|
7755
8074
|
deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
|
|
7756
8075
|
deleteAfterFailureMs: options?.deleteAfterFailureMs,
|
|
7757
8076
|
deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
|
|
7758
|
-
cleanupIntervalMs: options?.cleanupIntervalMs
|
|
8077
|
+
cleanupIntervalMs: options?.cleanupIntervalMs,
|
|
8078
|
+
stopTimeoutMs: options?.stopTimeoutMs
|
|
7759
8079
|
});
|
|
7760
8080
|
const client = new JobQueueClient({
|
|
7761
8081
|
storage,
|
|
@@ -7788,7 +8108,8 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
|
|
|
7788
8108
|
deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
|
|
7789
8109
|
deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
|
|
7790
8110
|
deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
|
|
7791
|
-
cleanupIntervalMs: mergedOptions.cleanupIntervalMs
|
|
8111
|
+
cleanupIntervalMs: mergedOptions.cleanupIntervalMs,
|
|
8112
|
+
stopTimeoutMs: mergedOptions.stopTimeoutMs
|
|
7792
8113
|
});
|
|
7793
8114
|
const client = new JobQueueClient({
|
|
7794
8115
|
storage,
|
|
@@ -8002,7 +8323,7 @@ queueMicrotask(() => {
|
|
|
8002
8323
|
// src/task/TaskRegistry.ts
|
|
8003
8324
|
import {
|
|
8004
8325
|
createServiceToken as createServiceToken6,
|
|
8005
|
-
getLogger as
|
|
8326
|
+
getLogger as getLogger9,
|
|
8006
8327
|
globalServiceRegistry as globalServiceRegistry5,
|
|
8007
8328
|
registerInputCompactor,
|
|
8008
8329
|
registerInputResolver
|
|
@@ -8025,7 +8346,7 @@ function registerTask(baseClass) {
|
|
|
8025
8346
|
const result = validateSchema(schema);
|
|
8026
8347
|
if (!result.valid) {
|
|
8027
8348
|
const messages = result.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
8028
|
-
|
|
8349
|
+
getLogger9().warn(`Task "${baseClass.type}" has invalid ${name}: ${messages}`, {
|
|
8029
8350
|
taskType: baseClass.type,
|
|
8030
8351
|
schemaName: name,
|
|
8031
8352
|
errors: result.errors
|
|
@@ -8370,6 +8691,7 @@ export {
|
|
|
8370
8691
|
wrapSchemaInArray,
|
|
8371
8692
|
whileTaskConfigSchema,
|
|
8372
8693
|
uppercaseTransform,
|
|
8694
|
+
updateBoundaryTaskSchemas,
|
|
8373
8695
|
unixToIsoDateTransform,
|
|
8374
8696
|
truncateTransform,
|
|
8375
8697
|
toBooleanTransform,
|
|
@@ -8383,6 +8705,7 @@ export {
|
|
|
8383
8705
|
schemaAcceptsArray,
|
|
8384
8706
|
scanGraphForFormat,
|
|
8385
8707
|
scanGraphForCredentials,
|
|
8708
|
+
runLoopAutoConnect,
|
|
8386
8709
|
resourcePatternMatches,
|
|
8387
8710
|
resolveSchemaInputs,
|
|
8388
8711
|
resolveIterationBound,
|
|
@@ -8475,7 +8798,11 @@ export {
|
|
|
8475
8798
|
addBoundaryNodesToGraphJson,
|
|
8476
8799
|
addBoundaryNodesToDependencyJson,
|
|
8477
8800
|
_resetPortCodecsForTests,
|
|
8801
|
+
WorkflowRunContext,
|
|
8802
|
+
WorkflowEventBridge,
|
|
8478
8803
|
WorkflowError,
|
|
8804
|
+
WorkflowCacheAdapter,
|
|
8805
|
+
WorkflowBuilder,
|
|
8479
8806
|
Workflow,
|
|
8480
8807
|
WhileTaskRunner,
|
|
8481
8808
|
WhileTask,
|
|
@@ -8484,6 +8811,7 @@ export {
|
|
|
8484
8811
|
TaskTimeoutError,
|
|
8485
8812
|
TaskStatus,
|
|
8486
8813
|
TaskSerializationError,
|
|
8814
|
+
TaskRunContext,
|
|
8487
8815
|
TaskRegistry,
|
|
8488
8816
|
TaskQueueRegistry,
|
|
8489
8817
|
TaskOutputTabularRepository,
|
|
@@ -8510,12 +8838,17 @@ export {
|
|
|
8510
8838
|
TASK_OUTPUT_REPOSITORY,
|
|
8511
8839
|
TASK_GRAPH_REPOSITORY,
|
|
8512
8840
|
TASK_CONSTRUCTORS,
|
|
8841
|
+
StreamPump,
|
|
8842
|
+
StreamProcessor,
|
|
8513
8843
|
SERVER_GRANTS,
|
|
8844
|
+
RunScheduler,
|
|
8845
|
+
RunContext,
|
|
8514
8846
|
ReduceTask,
|
|
8515
8847
|
PROPERTY_ARRAY,
|
|
8516
8848
|
PERMISSIVE_RESOLVER,
|
|
8517
8849
|
PERMISSIVE_ENFORCER,
|
|
8518
8850
|
MapTask,
|
|
8851
|
+
LoopBuilderContext,
|
|
8519
8852
|
JobTaskFailedError,
|
|
8520
8853
|
JOB_QUEUE_FACTORY,
|
|
8521
8854
|
IteratorTaskRunner,
|
|
@@ -8530,6 +8863,7 @@ export {
|
|
|
8530
8863
|
EventTaskGraphToDagMapping,
|
|
8531
8864
|
EventDagToTaskGraphMapping,
|
|
8532
8865
|
Entitlements,
|
|
8866
|
+
EdgeMaterializer,
|
|
8533
8867
|
ENTITLEMENT_RESOLVER,
|
|
8534
8868
|
ENTITLEMENT_ENFORCER,
|
|
8535
8869
|
EMPTY_POLICY,
|
|
@@ -8545,7 +8879,8 @@ export {
|
|
|
8545
8879
|
CreateEndLoopWorkflow,
|
|
8546
8880
|
CreateAdaptiveWorkflow,
|
|
8547
8881
|
ConditionalTask,
|
|
8882
|
+
CacheCoordinator,
|
|
8548
8883
|
BROWSER_GRANTS
|
|
8549
8884
|
};
|
|
8550
8885
|
|
|
8551
|
-
//# debugId=
|
|
8886
|
+
//# debugId=0AC520746F7F8CA064756E2164756E21
|