@workglow/task-graph 0.2.23 → 0.2.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/browser.js +1957 -1622
  2. package/dist/browser.js.map +38 -23
  3. package/dist/bun.js +1958 -1623
  4. package/dist/bun.js.map +38 -23
  5. package/dist/common.d.ts +14 -0
  6. package/dist/common.d.ts.map +1 -1
  7. package/dist/node.js +1958 -1623
  8. package/dist/node.js.map +38 -23
  9. package/dist/task/CacheCoordinator.d.ts +48 -0
  10. package/dist/task/CacheCoordinator.d.ts.map +1 -0
  11. package/dist/task/FallbackTask.d.ts +1 -0
  12. package/dist/task/FallbackTask.d.ts.map +1 -1
  13. package/dist/task/FallbackTaskRunner.d.ts +3 -2
  14. package/dist/task/FallbackTaskRunner.d.ts.map +1 -1
  15. package/dist/task/GraphAsTask.d.ts +1 -0
  16. package/dist/task/GraphAsTask.d.ts.map +1 -1
  17. package/dist/task/GraphAsTaskRunner.d.ts +4 -3
  18. package/dist/task/GraphAsTaskRunner.d.ts.map +1 -1
  19. package/dist/task/ITask.d.ts +19 -3
  20. package/dist/task/ITask.d.ts.map +1 -1
  21. package/dist/task/IteratorTaskRunner.d.ts +3 -2
  22. package/dist/task/IteratorTaskRunner.d.ts.map +1 -1
  23. package/dist/task/JobQueueFactory.d.ts +1 -0
  24. package/dist/task/JobQueueFactory.d.ts.map +1 -1
  25. package/dist/task/MapTask.d.ts +1 -0
  26. package/dist/task/MapTask.d.ts.map +1 -1
  27. package/dist/task/ReduceTask.d.ts +1 -0
  28. package/dist/task/ReduceTask.d.ts.map +1 -1
  29. package/dist/task/StreamProcessor.d.ts +38 -0
  30. package/dist/task/StreamProcessor.d.ts.map +1 -0
  31. package/dist/task/TaskRunContext.d.ts +33 -0
  32. package/dist/task/TaskRunContext.d.ts.map +1 -0
  33. package/dist/task/TaskRunner.d.ts +31 -47
  34. package/dist/task/TaskRunner.d.ts.map +1 -1
  35. package/dist/task/WhileTask.d.ts +1 -0
  36. package/dist/task/WhileTask.d.ts.map +1 -1
  37. package/dist/task/WhileTaskRunner.d.ts +3 -2
  38. package/dist/task/WhileTaskRunner.d.ts.map +1 -1
  39. package/dist/task-graph/Dataflow.d.ts +1 -1
  40. package/dist/task-graph/EdgeMaterializer.d.ts +88 -0
  41. package/dist/task-graph/EdgeMaterializer.d.ts.map +1 -0
  42. package/dist/task-graph/GraphSchemaUtils.d.ts +19 -0
  43. package/dist/task-graph/GraphSchemaUtils.d.ts.map +1 -1
  44. package/dist/task-graph/IWorkflow.d.ts +11 -1
  45. package/dist/task-graph/IWorkflow.d.ts.map +1 -1
  46. package/dist/task-graph/LoopBuilderContext.d.ts +60 -0
  47. package/dist/task-graph/LoopBuilderContext.d.ts.map +1 -0
  48. package/dist/task-graph/RunContext.d.ts +39 -0
  49. package/dist/task-graph/RunContext.d.ts.map +1 -0
  50. package/dist/task-graph/RunScheduler.d.ts +81 -0
  51. package/dist/task-graph/RunScheduler.d.ts.map +1 -0
  52. package/dist/task-graph/StreamPump.d.ts +122 -0
  53. package/dist/task-graph/StreamPump.d.ts.map +1 -0
  54. package/dist/task-graph/TaskGraph.d.ts +10 -2
  55. package/dist/task-graph/TaskGraph.d.ts.map +1 -1
  56. package/dist/task-graph/TaskGraphRunner.d.ts +31 -167
  57. package/dist/task-graph/TaskGraphRunner.d.ts.map +1 -1
  58. package/dist/task-graph/Workflow.d.ts +18 -99
  59. package/dist/task-graph/Workflow.d.ts.map +1 -1
  60. package/dist/task-graph/WorkflowBuilder.d.ts +93 -0
  61. package/dist/task-graph/WorkflowBuilder.d.ts.map +1 -0
  62. package/dist/task-graph/WorkflowCacheAdapter.d.ts +19 -0
  63. package/dist/task-graph/WorkflowCacheAdapter.d.ts.map +1 -0
  64. package/dist/task-graph/WorkflowEventBridge.d.ts +41 -0
  65. package/dist/task-graph/WorkflowEventBridge.d.ts.map +1 -0
  66. package/dist/task-graph/WorkflowFactories.d.ts +52 -0
  67. package/dist/task-graph/WorkflowFactories.d.ts.map +1 -0
  68. package/dist/task-graph/WorkflowPipe.d.ts +32 -0
  69. package/dist/task-graph/WorkflowPipe.d.ts.map +1 -0
  70. package/dist/task-graph/WorkflowRunContext.d.ts +27 -0
  71. package/dist/task-graph/WorkflowRunContext.d.ts.map +1 -0
  72. package/dist/task-graph/WorkflowTask.d.ts +19 -0
  73. package/dist/task-graph/WorkflowTask.d.ts.map +1 -0
  74. package/package.json +7 -7
  75. 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 uuid44 } from "@workglow/util";
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 getLogger4 } from "@workglow/util";
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 getLogger3,
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 uuid43
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/ConditionalTask.ts
1188
- import { getLogger as getLogger2 } from "@workglow/util";
1320
+ // src/task/EntitlementEnforcer.ts
1321
+ import { createServiceToken as createServiceToken4 } from "@workglow/util";
1189
1322
 
1190
- // src/task/ConditionUtils.ts
1191
- function evaluateCondition(fieldValue, operator, compareValue) {
1192
- if (fieldValue === null || fieldValue === undefined) {
1193
- switch (operator) {
1194
- case "is_empty":
1195
- return true;
1196
- case "is_not_empty":
1197
- return false;
1198
- case "is_true":
1199
- return false;
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 getNestedValue(obj, path) {
1246
- const parts = path.split(".");
1247
- let current = obj;
1248
- for (const part of parts) {
1249
- if (current === null || current === undefined || typeof current !== "object") {
1250
- return;
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
- current = current[part];
1253
- }
1254
- return current;
1255
- }
1256
-
1257
- // src/task/Task.ts
1258
- import { deepEqual, EventEmitter as EventEmitter3, uuid4 as uuid42 } from "@workglow/util";
1259
- import { compileSchema } from "@workglow/util/schema";
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
- function getOutputStreamMode(outputSchema) {
1300
- const ports = getStreamingPorts(outputSchema);
1301
- if (ports.length === 0)
1302
- return "none";
1303
- const mode = ports[0].mode;
1304
- for (let i = 1;i < ports.length; i++) {
1305
- if (ports[i].mode !== mode) {
1306
- return "mixed";
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
- function getAppendPortId(schema) {
1317
- if (typeof schema === "boolean")
1318
- return;
1319
- const props = schema.properties;
1320
- if (!props)
1321
- return;
1322
- for (const [name, prop] of Object.entries(props)) {
1323
- if (!prop || typeof prop === "boolean")
1324
- continue;
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/TaskRunner.ts
1372
- async function serializeOutputPorts(output, schema) {
1373
- if (!schema?.properties)
1374
- return output;
1375
- const out = { ...output };
1376
- for (const [key, prop] of Object.entries(schema.properties)) {
1377
- const codec = prop.format ? getPortCodec(prop.format) : undefined;
1378
- if (codec && out[key] !== undefined) {
1379
- out[key] = await codec.serialize(out[key]);
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
- return out;
1383
- }
1384
- async function deserializeOutputPorts(output, schema) {
1385
- if (!schema?.properties)
1386
- return output;
1387
- const out = { ...output };
1388
- for (const [key, prop] of Object.entries(schema.properties)) {
1389
- const codec = prop.format ? getPortCodec(prop.format) : undefined;
1390
- if (codec && out[key] !== undefined) {
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
- return out;
1395
- }
1396
- async function normalizeInputsForCacheKey(inputs, schema) {
1397
- if (!schema?.properties)
1398
- return inputs;
1399
- const out = { ...inputs };
1400
- for (const [key, prop] of Object.entries(schema.properties)) {
1401
- const codec = prop.format ? getPortCodec(prop.format) : undefined;
1402
- if (codec && out[key] !== undefined) {
1403
- out[key] = await codec.serialize(out[key]);
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
- return out;
1407
- }
1408
- function hasRunConfig(i) {
1409
- return i !== null && typeof i === "object" && "runConfig" in i;
1410
- }
1411
-
1412
- class TaskRunner {
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
- if (!outputs) {
1482
- if (isStreamable) {
1483
- outputs = await this.executeStreamingTask(inputs);
1484
- } else {
1485
- outputs = await this.executeTask(inputs);
1486
- }
1487
- if (this.task.cacheable && outputs !== undefined) {
1488
- const wireOutputs = await serializeOutputPorts(outputs, outputSchema);
1489
- await this.outputCache?.saveOutput(this.task.type, inputsForKey, wireOutputs);
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
- async runPreview(overrides = {}) {
1501
- if (this.task.status === TaskStatus.PROCESSING) {
1502
- return this.task.runOutputData;
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.task.setInput(overrides);
1505
- const configSchema = this.task.constructor.configSchema();
1506
- if (schemaHasFormatAnnotations(configSchema)) {
1507
- const source = this.task.originalConfig ?? this.task.config;
1508
- const resolved = await resolveSchemaInputs({ ...source }, configSchema, { registry: this.registry });
1509
- Object.assign(this.task.config, resolved);
1510
- }
1511
- const schema = this.task.constructor.inputSchema();
1512
- this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
1513
- await this.handleStartPreview();
1514
- try {
1515
- const inputs = this.task.runInputData;
1516
- const isValid = await this.task.validateInput(inputs);
1517
- if (!isValid) {
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
- async* runPreviewStream(overrides = {}) {
1533
- const graph = this.task.parentGraph;
1534
- const dataflowInfos = [];
1535
- if (graph) {
1536
- for (const df of graph.getSourceDataflows(this.task.id)) {
1537
- const upstream = graph.getTask(df.sourceTaskId);
1538
- if (upstream) {
1539
- dataflowInfos.push({
1540
- upstream,
1541
- sourcePort: df.sourceTaskPortId,
1542
- targetPort: df.targetTaskPortId
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
- const upstreamTasks = new Set(dataflowInfos.map((d) => d.upstream));
1548
- const pendingUpstreams = new Set([...upstreamTasks].filter((u) => u.status === TaskStatus.STREAMING || u.status === TaskStatus.PENDING || u.status === TaskStatus.PROCESSING));
1549
- let dirty = true;
1550
- let wakeResolve;
1551
- const wakeNext = () => new Promise((resolve) => {
1552
- wakeResolve = resolve;
1553
- });
1554
- const wake = () => {
1555
- const r = wakeResolve;
1556
- wakeResolve = undefined;
1557
- if (r)
1558
- r();
1559
- };
1560
- const cleanupFns = [];
1561
- for (const upstream of pendingUpstreams) {
1562
- const myDataflows = dataflowInfos.filter((d) => d.upstream === upstream);
1563
- const onChunk = (event) => {
1564
- if (event.type !== "snapshot")
1565
- return;
1566
- const snapshotData = event.data;
1567
- if (snapshotData) {
1568
- for (const { sourcePort, targetPort } of myDataflows) {
1569
- const value = sourcePort === "*" ? snapshotData : snapshotData[sourcePort];
1570
- if (value !== undefined) {
1571
- this.task.runInputData[targetPort] = value;
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
- for (const upstream of [...pendingUpstreams]) {
1598
- if (upstream.status === TaskStatus.COMPLETED || upstream.status === TaskStatus.FAILED || upstream.status === TaskStatus.DISABLED) {
1599
- pendingUpstreams.delete(upstream);
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
- try {
1603
- while (true) {
1604
- if (dirty) {
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
- } finally {
1622
- for (const off of cleanupFns)
1623
- off();
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
- abort() {
1627
- if (this.task.hasChildren()) {
1628
- this.task.subGraph.abort();
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
- this.abortController?.abort();
1829
+ return outputs;
1631
1830
  }
1632
- own(i) {
1633
- const task = ensureTask(i, { isOwned: true });
1634
- this.task.subGraph.addTask(task);
1635
- if (hasRunConfig(i)) {
1636
- Object.assign(i.runConfig, {
1637
- registry: this.registry,
1638
- signal: this.abortController?.signal,
1639
- resourceScope: this.resourceScope
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
- if (this.task.constructor.hasDynamicEntitlements) {
1643
- this.task.emit("entitlementChange", this.task.entitlements());
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 i;
1860
+ return out;
1646
1861
  }
1647
- async executeTask(input) {
1648
- const result = await this.task.execute(input, {
1649
- signal: this.abortController.signal,
1650
- updateProgress: this.handleProgress.bind(this),
1651
- own: this.own,
1652
- registry: this.registry,
1653
- resourceScope: this.resourceScope
1654
- });
1655
- return result;
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
- async executeTaskPreview(input) {
1658
- return this.task.executePreview?.(input, { own: this.own });
1874
+ }
1875
+
1876
+ // src/task/StreamProcessor.ts
1877
+ class StreamProcessor {
1878
+ task;
1879
+ constructor(task) {
1880
+ this.task = task;
1659
1881
  }
1660
- async executeStreamingTask(input) {
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 = this.shouldAccumulate ? new Map : undefined;
1675
- const accumulatedObjects = this.shouldAccumulate ? new Map : undefined;
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: this.abortController.signal,
1681
- updateProgress: this.handleProgress.bind(this),
1682
- own: this.own,
1683
- registry: this.registry,
1684
- resourceScope: this.resourceScope,
1685
- inputStreams: this.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 this.handleProgress(event.progress, event.message);
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 (this.abortController?.signal.aborted) {
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
- async handleStart(config = {}) {
1812
- if (this.task.status === TaskStatus.PROCESSING)
1813
- return;
1814
- this.running = true;
1815
- this.task.startedAt = new Date;
1816
- this.task.progress = 0;
1817
- this.task.status = TaskStatus.PROCESSING;
1818
- this.abortController = new AbortController;
1819
- this.abortController.signal.addEventListener("abort", () => {
1820
- this.handleAbort();
1821
- });
1822
- const cache = config.outputCache ?? this.task.runConfig?.outputCache;
1823
- if (cache === true) {
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
- this.shouldAccumulate = config.shouldAccumulate !== false;
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 (config.signal) {
1842
- const onAbort = () => this.abortController.abort();
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
- this.pendingTimeoutError = new TaskTimeoutError(timeout);
1853
- this.timeoutTimer = setTimeout(() => {
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
- this.telemetrySpan = telemetry.startSpan("workglow.task.run", {
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
- if (this.timeoutTimer !== undefined) {
1877
- clearTimeout(this.timeoutTimer);
1878
- this.timeoutTimer = undefined;
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 = this.pendingTimeoutError ?? new TaskAbortedError;
1888
- this.pendingTimeoutError = undefined;
1889
- if (this.telemetrySpan) {
1890
- this.telemetrySpan.setStatus(SpanStatusCode.ERROR, "aborted");
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
- this.telemetrySpan.end();
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
- this.pendingTimeoutError = undefined;
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 (this.telemetrySpan) {
1918
- this.telemetrySpan.setStatus(SpanStatusCode.OK);
1919
- this.telemetrySpan.end();
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
- this.abortController = undefined;
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
- this.pendingTimeoutError = undefined;
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 (this.telemetrySpan) {
1965
- this.telemetrySpan.setStatus(SpanStatusCode.ERROR, this.task.error.message);
1966
- this.telemetrySpan.setAttributes({ "workglow.task.error": this.task.error.message });
1967
- this.telemetrySpan.end();
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 = uuid42();
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
- getLogger2().error(`Condition evaluation failed for branch "${branch.id}":`, { error });
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/EntitlementEnforcer.ts
2716
- import { createServiceToken as createServiceToken4 } from "@workglow/util";
2717
-
2718
- // src/task/EntitlementPolicy.ts
2719
- var EMPTY_POLICY = Object.freeze({
2720
- deny: Object.freeze([]),
2721
- grant: Object.freeze([]),
2722
- ask: Object.freeze([])
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
- return results;
2752
- }
2753
- function can(policy, id, resources) {
2754
- const required = resources !== undefined ? { id, resources } : { id };
2755
- const [result] = evaluatePolicy(policy, { entitlements: [required] });
2756
- return result;
2757
- }
2758
-
2759
- // src/task/EntitlementResolver.ts
2760
- import { createServiceToken as createServiceToken3 } from "@workglow/util";
2761
- var PERMISSIVE_RESOLVER = {
2762
- lookup: () => "grant",
2763
- prompt: async () => "grant",
2764
- save: () => {}
2765
- };
2766
- var DENY_ALL_RESOLVER = {
2767
- lookup: () => "deny",
2768
- prompt: async () => "deny",
2769
- save: () => {}
2770
- };
2771
- var ENTITLEMENT_RESOLVER = createServiceToken3("workglow.entitlementResolver");
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
- denied.push({ entitlement: result.entitlement, reason: "default-deny" });
3244
+ dataflow.setStatus(effectiveStatus);
2802
3245
  }
2803
- } else if (result.verdict === "ask") {
2804
- const request = {
2805
- entitlement: result.entitlement,
2806
- taskType: taskType ?? "unknown",
2807
- taskId: taskId ?? "unknown"
2808
- };
2809
- let answer = resolver.lookup(request);
2810
- if (answer === undefined) {
2811
- answer = await resolver.prompt(request);
2812
- resolver.save(request, answer);
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
- if (answer === "deny") {
2815
- if (!result.matchedRule) {
2816
- throw new Error(`Invariant violation: ask verdict for "${result.entitlement.id}" is missing matchedRule`);
2817
- }
2818
- denied.push({
2819
- entitlement: result.entitlement,
2820
- reason: "user-deny",
2821
- matchedRule: result.matchedRule
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
- return {
2829
- async checkAll(required) {
2830
- return resolveAsks(required);
2831
- },
2832
- async checkTask(task) {
2833
- const entitlements = task.entitlements();
2834
- return resolveAsks(entitlements, task.constructor.type, task.id);
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
- async* tasks() {
2858
- while (this.currentIndex < this.sortedNodes.length) {
2859
- yield this.sortedNodes[this.currentIndex++];
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
- onTaskCompleted(_taskId) {}
2863
- onTaskStreaming(_taskId) {}
2864
- reset() {
2865
- this.sortedNodes = this.dag.topologicallySortedNodes();
2866
- this.currentIndex = 0;
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
- class DependencyBasedScheduler {
2871
- dag;
2872
- completedTasks;
2873
- streamingTasks;
2874
- pendingTasks;
2875
- nextResolver = null;
2876
- constructor(dag) {
2877
- this.dag = dag;
2878
- this.completedTasks = new Set;
2879
- this.streamingTasks = new Set;
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
- isTaskReady(task) {
2884
- if (task.status === TaskStatus.DISABLED) {
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
- async waitForNextTask() {
2913
- if (this.pendingTasks.size === 0)
2914
- return null;
2915
- for (const task of Array.from(this.pendingTasks)) {
2916
- if (task.status === TaskStatus.DISABLED) {
2917
- this.pendingTasks.delete(task);
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
- if (this.pendingTasks.size === 0)
2921
- return null;
2922
- const readyTask = Array.from(this.pendingTasks).find((task) => this.isTaskReady(task));
2923
- if (readyTask) {
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
- abortController;
3017
- inProgressTasks = new Map;
3018
- inProgressFunctions = new Map;
3019
- failedTaskErrors = new Map;
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.handleProgress = this.handleProgress.bind(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
- await this.handleStart(config);
3034
- const results = [];
3035
- let error;
3727
+ const ownsScope = config?.resourceScope === undefined;
3728
+ const effectiveConfig = ownsScope ? { ...config, resourceScope: new ResourceScope2 } : config;
3036
3729
  try {
3037
- for await (const task of this.processScheduler.tasks()) {
3038
- if (this.abortController?.signal.aborted) {
3039
- break;
3040
- }
3041
- if (this.failedTaskErrors.size > 0) {
3042
- break;
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
- } catch (err) {
3073
- error = err;
3074
- getLogger3().error("Error running graph", { error });
3075
- }
3076
- await Promise.allSettled(Array.from(this.inProgressTasks.values()));
3077
- await Promise.allSettled(Array.from(this.inProgressFunctions.values()));
3078
- if (this.pendingGraphTimeoutError) {
3079
- await this.handleAbort();
3080
- throw this.pendingGraphTimeoutError;
3081
- }
3082
- if (this.failedTaskErrors.size > 0) {
3083
- const latestError = this.failedTaskErrors.values().next().value;
3084
- this.handleError(latestError);
3085
- throw latestError;
3086
- }
3087
- if (this.abortController?.signal.aborted) {
3088
- await this.handleAbort();
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 ? uuid43() : "";
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
- getLogger3().debug("task graph runPreview timings", {
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
- getLogger3().debug("task graph runPreview failed", {
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?.abort();
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
- copyInputFromEdgesToNode(task) {
3240
- const dataflows = this.graph.getSourceDataflows(task.id);
3241
- dataflows.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
3242
- for (const dataflow of dataflows) {
3243
- const live = dataflow.getCurrentValue();
3244
- const port = dataflow.targetTaskPortId;
3245
- let portData;
3246
- if (port === DATAFLOW_ALL_PORTS) {
3247
- portData = live;
3248
- } else if (port === DATAFLOW_ERROR_PORT) {
3249
- portData = { [DATAFLOW_ERROR_PORT]: dataflow.error };
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
- resetTask(graph, task, runId) {
3569
- task.status = TaskStatus.PENDING;
3570
- task.resetInputData();
3571
- task.runOutputData = {};
3572
- task.error = undefined;
3573
- task.progress = 0;
3574
- task.runConfig = { ...task.runConfig, runnerId: runId };
3575
- this.pushStatusFromNodeToEdges(graph, task);
3576
- this.pushErrorFromNodeToEdges(graph, task);
3577
- task.emit("reset");
3578
- task.emit("status", task.status);
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
- this.abortController = new AbortController;
3619
- this.abortController.signal.addEventListener("abort", () => {
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 && config.timeout > 0) {
3623
- this.pendingGraphTimeoutError = undefined;
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
- this.runId = uuid43();
3641
- this.resetGraph(this.graph, this.runId);
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
- this.activeEnforcer = enforcer;
3663
- } else {
3664
- this.activeEnforcer = undefined;
3990
+ ctx.activeEnforcer = enforcer;
3665
3991
  }
3666
3992
  } catch (err) {
3667
- if (this.graphTimeoutTimer !== undefined) {
3668
- clearTimeout(this.graphTimeoutTimer);
3669
- this.graphTimeoutTimer = undefined;
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
- this.telemetrySpan = telemetry.startSpan("workglow.graph.run", {
4001
+ ctx.telemetrySpan = telemetry.startSpan("workglow.graph.run", {
3679
4002
  attributes: {
3680
- "workglow.graph.run_id": this.runId,
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.graphTimeoutTimer !== undefined) {
3706
- clearTimeout(this.graphTimeoutTimer);
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
- this.activeEnforcer = undefined;
3714
- if (this.telemetrySpan) {
3715
- this.telemetrySpan.setStatus(SpanStatusCode2.OK);
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
- this.activeEnforcer = undefined;
3733
- if (this.telemetrySpan) {
3734
- this.telemetrySpan.setStatus(SpanStatusCode2.ERROR, error.message);
3735
- this.telemetrySpan.setAttributes({ "workglow.graph.error": error.message });
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
- this.running = false;
3752
- this.activeEnforcer = undefined;
3753
- if (this.telemetrySpan) {
3754
- this.telemetrySpan.setStatus(SpanStatusCode2.ERROR, "aborted");
3755
- this.telemetrySpan.addEvent("workglow.graph.aborted");
3756
- this.telemetrySpan.end();
3757
- this.telemetrySpan = undefined;
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?.signal,
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
- getLogger4().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 });
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, uuid44());
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, getLogger as getLogger5, uuid4 as uuid46 } from "@workglow/util";
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 uuid45 } from "@workglow/util";
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: uuid45(),
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/Workflow.ts
4884
- function getLastTask(workflow) {
4885
- const tasks = workflow.graph.getTasks();
4886
- return tasks.length > 0 ? tasks[tasks.length - 1] : undefined;
4887
- }
4888
- function connect(source, target, workflow) {
4889
- workflow.graph.addDataflow(new Dataflow(source.id, "*", target.id, "*"));
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
- function pipe(args, workflow = new Workflow) {
4892
- let previousTask = getLastTask(workflow);
4893
- const tasks = args.map((arg) => ensureTask(arg));
4894
- tasks.forEach((task) => {
4895
- workflow.graph.addTask(task);
4896
- if (previousTask) {
4897
- connect(previousTask, task, workflow);
4898
- }
4899
- previousTask = task;
4900
- });
4901
- return workflow;
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
- class ParallelTask extends GraphAsTask {
4912
- static type = name;
5547
+ // src/task-graph/WorkflowRunContext.ts
5548
+ class WorkflowRunContext {
5549
+ abortController;
5550
+ unsubStreaming;
5551
+ constructor() {
5552
+ this.abortController = new AbortController;
4913
5553
  }
4914
- const mergeTask = new ParallelTask(config);
4915
- mergeTask.subGraph.addTasks(tasks);
4916
- workflow.graph.addTask(mergeTask);
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._outputCache = cache;
4995
- this._parentWorkflow = parent;
4996
- this._iteratorTask = iteratorTask;
4997
- this._registry = registry ?? parent?._registry;
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._onChanged = this._onChanged.bind(this);
5001
- this.setupEvents();
5604
+ this._bridge = new WorkflowEventBridge(this.events);
5605
+ this._bridge.attach(this._graph);
5002
5606
  }
5003
5607
  }
5004
5608
  _graph;
5005
- _dataFlows = [];
5006
- _error = "";
5007
- _outputCache;
5008
- _registry;
5009
- _entitlementUnsub;
5010
- _abortController;
5011
- _parentWorkflow;
5012
- _iteratorTask;
5013
- _pendingLoopConnect;
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._outputCache;
5620
+ return this._cache.outputCache();
5016
5621
  }
5017
5622
  get isLoopBuilder() {
5018
- return this._parentWorkflow !== undefined;
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._error = "";
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._dataFlows = [];
5086
- this._error = "";
5087
- this.clearEvents();
5643
+ this._builder.resetState();
5644
+ this._bridge?.detach();
5088
5645
  this._graph = value;
5089
- this.setupEvents();
5646
+ this._bridge?.attach(this._graph);
5090
5647
  this.events.emit("reset");
5091
5648
  }
5092
5649
  get error() {
5093
- return this._error;
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
- if (this.isLoopBuilder) {
5109
- this.finalizeTemplate();
5110
- if (this._pendingLoopConnect) {
5111
- this._parentWorkflow.autoConnectLoopTask(this._pendingLoopConnect);
5112
- this._pendingLoopConnect = undefined;
5113
- }
5114
- return this._parentWorkflow.run(input, config);
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
- this._abortController = new AbortController;
5118
- const unsubStreaming = this.graph.subscribeToTaskStreaming({
5119
- onStreamStart: (taskId) => this.events.emit("stream_start", taskId),
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: this._abortController.signal,
5126
- outputCache: this._outputCache,
5127
- registry: config?.registry ?? this._registry,
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
- unsubStreaming();
5138
- this._abortController = undefined;
5691
+ ctx.dispose();
5692
+ if (this._currentRun === ctx)
5693
+ this._currentRun = undefined;
5139
5694
  }
5140
5695
  }
5141
5696
  async abort() {
5142
- if (this._parentWorkflow) {
5143
- return this._parentWorkflow.abort();
5697
+ const loopContext = this._builder.loopContext;
5698
+ if (loopContext) {
5699
+ return loopContext.parent.abort();
5144
5700
  }
5145
- this._abortController?.abort();
5701
+ this._currentRun?.abortController.abort();
5146
5702
  }
5147
5703
  pop() {
5148
- this._error = "";
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._error = "";
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._error = "";
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._parentWorkflow) {
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.clearEvents();
5748
+ this._bridge?.detach();
5240
5749
  this._graph = new TaskGraph({
5241
- outputCache: this._outputCache
5750
+ outputCache: this._cache.outputCache()
5242
5751
  });
5243
- this._dataFlows = [];
5244
- this._error = "";
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
- const sourceTask = this.graph.getTask(sourceTaskId);
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
- const task = new taskClass(config, this._registry ? { registry: this._registry } : undefined);
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
- const helper = Workflow.createWorkflow(taskClass);
5307
- return helper.call(this, input, config);
5766
+ return this._builder.addTaskWithAutoConnect(taskClass, input, config);
5308
5767
  }
5309
5768
  addLoopTask(taskClass, config = {}) {
5310
- this._error = "";
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 new ConditionalBuilder(this, condition);
5772
+ return this._builder.createConditional(condition);
5338
5773
  }
5339
5774
  autoConnectLoopTask(pending) {
5340
5775
  if (!pending)
5341
5776
  return;
5342
- const { parent, iteratorTask } = pending;
5343
- if (this.graph.getTargetDataflows(parent.id).length === 0) {
5344
- const nodes = this._graph.getTasks();
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
- if (!this._iteratorTask || this.graph.getTasks().length === 0) {
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
- if (!this._parentWorkflow) {
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.finalizeTemplate();
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?.signal.aborted) {
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?.signal.aborted) {
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?.signal,
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 uuid47 } from "@workglow/util";
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?.signal.aborted) {
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?.signal.aborted) {
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?.signal.aborted) {
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 = uuid47();
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?.signal.aborted) {
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?.signal,
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: this.abortController.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
  }
@@ -7756,7 +8075,8 @@ var defaultJobQueueFactory = async ({
7756
8075
  deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
7757
8076
  deleteAfterFailureMs: options?.deleteAfterFailureMs,
7758
8077
  deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
7759
- cleanupIntervalMs: options?.cleanupIntervalMs
8078
+ cleanupIntervalMs: options?.cleanupIntervalMs,
8079
+ stopTimeoutMs: options?.stopTimeoutMs
7760
8080
  });
7761
8081
  const client = new JobQueueClient({
7762
8082
  storage,
@@ -7789,7 +8109,8 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
7789
8109
  deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
7790
8110
  deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
7791
8111
  deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
7792
- cleanupIntervalMs: mergedOptions.cleanupIntervalMs
8112
+ cleanupIntervalMs: mergedOptions.cleanupIntervalMs,
8113
+ stopTimeoutMs: mergedOptions.stopTimeoutMs
7793
8114
  });
7794
8115
  const client = new JobQueueClient({
7795
8116
  storage,
@@ -8003,7 +8324,7 @@ queueMicrotask(() => {
8003
8324
  // src/task/TaskRegistry.ts
8004
8325
  import {
8005
8326
  createServiceToken as createServiceToken6,
8006
- getLogger as getLogger6,
8327
+ getLogger as getLogger9,
8007
8328
  globalServiceRegistry as globalServiceRegistry5,
8008
8329
  registerInputCompactor,
8009
8330
  registerInputResolver
@@ -8026,7 +8347,7 @@ function registerTask(baseClass) {
8026
8347
  const result = validateSchema(schema);
8027
8348
  if (!result.valid) {
8028
8349
  const messages = result.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
8029
- getLogger6().warn(`Task "${baseClass.type}" has invalid ${name}: ${messages}`, {
8350
+ getLogger9().warn(`Task "${baseClass.type}" has invalid ${name}: ${messages}`, {
8030
8351
  taskType: baseClass.type,
8031
8352
  schemaName: name,
8032
8353
  errors: result.errors
@@ -8371,6 +8692,7 @@ export {
8371
8692
  wrapSchemaInArray,
8372
8693
  whileTaskConfigSchema,
8373
8694
  uppercaseTransform,
8695
+ updateBoundaryTaskSchemas,
8374
8696
  unixToIsoDateTransform,
8375
8697
  truncateTransform,
8376
8698
  toBooleanTransform,
@@ -8384,6 +8706,7 @@ export {
8384
8706
  schemaAcceptsArray,
8385
8707
  scanGraphForFormat,
8386
8708
  scanGraphForCredentials,
8709
+ runLoopAutoConnect,
8387
8710
  resourcePatternMatches,
8388
8711
  resolveSchemaInputs,
8389
8712
  resolveIterationBound,
@@ -8476,7 +8799,11 @@ export {
8476
8799
  addBoundaryNodesToGraphJson,
8477
8800
  addBoundaryNodesToDependencyJson,
8478
8801
  _resetPortCodecsForTests,
8802
+ WorkflowRunContext,
8803
+ WorkflowEventBridge,
8479
8804
  WorkflowError,
8805
+ WorkflowCacheAdapter,
8806
+ WorkflowBuilder,
8480
8807
  Workflow,
8481
8808
  WhileTaskRunner,
8482
8809
  WhileTask,
@@ -8485,6 +8812,7 @@ export {
8485
8812
  TaskTimeoutError,
8486
8813
  TaskStatus,
8487
8814
  TaskSerializationError,
8815
+ TaskRunContext,
8488
8816
  TaskRegistry,
8489
8817
  TaskQueueRegistry,
8490
8818
  TaskOutputTabularRepository,
@@ -8511,12 +8839,17 @@ export {
8511
8839
  TASK_OUTPUT_REPOSITORY,
8512
8840
  TASK_GRAPH_REPOSITORY,
8513
8841
  TASK_CONSTRUCTORS,
8842
+ StreamPump,
8843
+ StreamProcessor,
8514
8844
  SERVER_GRANTS,
8845
+ RunScheduler,
8846
+ RunContext,
8515
8847
  ReduceTask,
8516
8848
  PROPERTY_ARRAY,
8517
8849
  PERMISSIVE_RESOLVER,
8518
8850
  PERMISSIVE_ENFORCER,
8519
8851
  MapTask,
8852
+ LoopBuilderContext,
8520
8853
  JobTaskFailedError,
8521
8854
  JOB_QUEUE_FACTORY,
8522
8855
  IteratorTaskRunner,
@@ -8531,6 +8864,7 @@ export {
8531
8864
  EventTaskGraphToDagMapping,
8532
8865
  EventDagToTaskGraphMapping,
8533
8866
  Entitlements,
8867
+ EdgeMaterializer,
8534
8868
  ENTITLEMENT_RESOLVER,
8535
8869
  ENTITLEMENT_ENFORCER,
8536
8870
  EMPTY_POLICY,
@@ -8546,7 +8880,8 @@ export {
8546
8880
  CreateEndLoopWorkflow,
8547
8881
  CreateAdaptiveWorkflow,
8548
8882
  ConditionalTask,
8883
+ CacheCoordinator,
8549
8884
  BROWSER_GRANTS
8550
8885
  };
8551
8886
 
8552
- //# debugId=84C93480A08A674564756E2164756E21
8887
+ //# debugId=732100600E6F7E9764756E2164756E21