@workglow/task-graph 0.2.24 → 0.2.25

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