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