@workglow/task-graph 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +810 -189
- package/dist/browser.js.map +40 -33
- package/dist/bun.js +810 -189
- package/dist/bun.js.map +40 -33
- package/dist/common.d.ts +2 -0
- package/dist/common.d.ts.map +1 -1
- package/dist/node.js +810 -189
- package/dist/node.js.map +40 -33
- package/dist/storage/TaskOutputTabularRepository.d.ts +1 -1
- package/dist/storage/TaskOutputTabularRepository.d.ts.map +1 -1
- package/dist/task/ConditionalTask.d.ts +7 -2
- package/dist/task/ConditionalTask.d.ts.map +1 -1
- package/dist/task/EntitlementEnforcer.d.ts +55 -0
- package/dist/task/EntitlementEnforcer.d.ts.map +1 -0
- package/dist/task/EntitlementPolicy.d.ts +60 -0
- package/dist/task/EntitlementPolicy.d.ts.map +1 -0
- package/dist/task/EntitlementProfiles.d.ts +49 -0
- package/dist/task/EntitlementProfiles.d.ts.map +1 -0
- package/dist/task/EntitlementResolver.d.ts +50 -0
- package/dist/task/EntitlementResolver.d.ts.map +1 -0
- package/dist/task/FallbackTask.d.ts +11 -4
- package/dist/task/FallbackTask.d.ts.map +1 -1
- package/dist/task/FallbackTaskRunner.d.ts +2 -2
- package/dist/task/FallbackTaskRunner.d.ts.map +1 -1
- package/dist/task/GraphAsTask.d.ts +18 -6
- package/dist/task/GraphAsTask.d.ts.map +1 -1
- package/dist/task/GraphAsTaskRunner.d.ts +3 -2
- package/dist/task/GraphAsTaskRunner.d.ts.map +1 -1
- package/dist/task/ITask.d.ts +12 -1
- package/dist/task/ITask.d.ts.map +1 -1
- package/dist/task/InputCompactor.d.ts +1 -1
- package/dist/task/InputCompactor.d.ts.map +1 -1
- package/dist/task/InputResolver.d.ts +3 -3
- package/dist/task/InputResolver.d.ts.map +1 -1
- package/dist/task/IteratorTask.d.ts +9 -4
- package/dist/task/IteratorTask.d.ts.map +1 -1
- package/dist/task/IteratorTaskRunner.d.ts +1 -1
- package/dist/task/IteratorTaskRunner.d.ts.map +1 -1
- package/dist/task/MapTask.d.ts +8 -3
- package/dist/task/MapTask.d.ts.map +1 -1
- package/dist/task/ReduceTask.d.ts +9 -4
- package/dist/task/ReduceTask.d.ts.map +1 -1
- package/dist/task/StreamTypes.d.ts +6 -2
- package/dist/task/StreamTypes.d.ts.map +1 -1
- package/dist/task/Task.d.ts +37 -7
- package/dist/task/Task.d.ts.map +1 -1
- package/dist/task/TaskEntitlements.d.ts +134 -0
- package/dist/task/TaskEntitlements.d.ts.map +1 -0
- package/dist/task/TaskError.d.ts +7 -0
- package/dist/task/TaskError.d.ts.map +1 -1
- package/dist/task/TaskEvents.d.ts +3 -0
- package/dist/task/TaskEvents.d.ts.map +1 -1
- package/dist/task/TaskJSON.d.ts +14 -1
- package/dist/task/TaskJSON.d.ts.map +1 -1
- package/dist/task/TaskRegistry.d.ts +16 -3
- package/dist/task/TaskRegistry.d.ts.map +1 -1
- package/dist/task/TaskRunner.d.ts +5 -4
- package/dist/task/TaskRunner.d.ts.map +1 -1
- package/dist/task/TaskTypes.d.ts +10 -2
- package/dist/task/TaskTypes.d.ts.map +1 -1
- package/dist/task/WhileTask.d.ts +8 -4
- package/dist/task/WhileTask.d.ts.map +1 -1
- package/dist/task/WhileTaskRunner.d.ts +1 -1
- package/dist/task/WhileTaskRunner.d.ts.map +1 -1
- package/dist/task/index.d.ts +5 -0
- package/dist/task/index.d.ts.map +1 -1
- package/dist/task/iterationSchema.d.ts +2 -1
- package/dist/task/iterationSchema.d.ts.map +1 -1
- package/dist/task-graph/Dataflow.d.ts +1 -1
- package/dist/task-graph/Dataflow.d.ts.map +1 -1
- package/dist/task-graph/GraphEntitlementUtils.d.ts +30 -0
- package/dist/task-graph/GraphEntitlementUtils.d.ts.map +1 -0
- package/dist/task-graph/GraphFormatScanner.d.ts +41 -0
- package/dist/task-graph/GraphFormatScanner.d.ts.map +1 -0
- package/dist/task-graph/GraphToWorkflowCode.d.ts.map +1 -1
- package/dist/task-graph/TaskGraph.d.ts +23 -5
- package/dist/task-graph/TaskGraph.d.ts.map +1 -1
- package/dist/task-graph/TaskGraphEvents.d.ts +3 -0
- package/dist/task-graph/TaskGraphEvents.d.ts.map +1 -1
- package/dist/task-graph/TaskGraphRunner.d.ts +13 -1
- package/dist/task-graph/TaskGraphRunner.d.ts.map +1 -1
- package/dist/task-graph/Workflow.d.ts +29 -17
- package/dist/task-graph/Workflow.d.ts.map +1 -1
- package/package.json +12 -7
package/dist/bun.js
CHANGED
|
@@ -39,6 +39,11 @@ var TaskConfigSchema = {
|
|
|
39
39
|
type: "object",
|
|
40
40
|
additionalProperties: true,
|
|
41
41
|
"x-ui-hidden": true
|
|
42
|
+
},
|
|
43
|
+
defaults: {
|
|
44
|
+
type: "object",
|
|
45
|
+
additionalProperties: true,
|
|
46
|
+
"x-ui-hidden": true
|
|
42
47
|
}
|
|
43
48
|
},
|
|
44
49
|
additionalProperties: false
|
|
@@ -257,6 +262,316 @@ class DataflowArrow extends Dataflow {
|
|
|
257
262
|
super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
|
|
258
263
|
}
|
|
259
264
|
}
|
|
265
|
+
// src/task/TaskEntitlements.ts
|
|
266
|
+
var Entitlements = {
|
|
267
|
+
NETWORK: "network",
|
|
268
|
+
NETWORK_HTTP: "network:http",
|
|
269
|
+
NETWORK_WEBSOCKET: "network:websocket",
|
|
270
|
+
NETWORK_PRIVATE: "network:private",
|
|
271
|
+
FILESYSTEM: "filesystem",
|
|
272
|
+
FILESYSTEM_READ: "filesystem:read",
|
|
273
|
+
FILESYSTEM_WRITE: "filesystem:write",
|
|
274
|
+
CODE_EXECUTION: "code-execution",
|
|
275
|
+
CODE_EXECUTION_JS: "code-execution:javascript",
|
|
276
|
+
CREDENTIAL: "credential",
|
|
277
|
+
AI: "ai",
|
|
278
|
+
AI_MODEL: "ai:model",
|
|
279
|
+
AI_INFERENCE: "ai:inference",
|
|
280
|
+
MCP: "mcp",
|
|
281
|
+
MCP_TOOL_CALL: "mcp:tool-call",
|
|
282
|
+
MCP_RESOURCE_READ: "mcp:resource-read",
|
|
283
|
+
MCP_PROMPT_GET: "mcp:prompt-get",
|
|
284
|
+
MCP_STDIO: "mcp:stdio",
|
|
285
|
+
STORAGE: "storage",
|
|
286
|
+
STORAGE_READ: "storage:read",
|
|
287
|
+
STORAGE_WRITE: "storage:write"
|
|
288
|
+
};
|
|
289
|
+
var EMPTY_ENTITLEMENTS = Object.freeze({
|
|
290
|
+
entitlements: Object.freeze([])
|
|
291
|
+
});
|
|
292
|
+
function entitlementCovers(granted, required) {
|
|
293
|
+
return required === granted || required.startsWith(granted + ":");
|
|
294
|
+
}
|
|
295
|
+
function resourcePatternMatches(grantPattern, requiredResource) {
|
|
296
|
+
if (grantPattern === requiredResource)
|
|
297
|
+
return true;
|
|
298
|
+
const starIdx = grantPattern.indexOf("*");
|
|
299
|
+
if (starIdx === -1)
|
|
300
|
+
return false;
|
|
301
|
+
const prefix = grantPattern.slice(0, starIdx);
|
|
302
|
+
const suffix = grantPattern.slice(starIdx + 1);
|
|
303
|
+
if (!requiredResource.startsWith(prefix))
|
|
304
|
+
return false;
|
|
305
|
+
if (!requiredResource.endsWith(suffix))
|
|
306
|
+
return false;
|
|
307
|
+
return requiredResource.length >= prefix.length + suffix.length;
|
|
308
|
+
}
|
|
309
|
+
function grantCoversResources(grant, required) {
|
|
310
|
+
if (grant.resources === undefined)
|
|
311
|
+
return true;
|
|
312
|
+
if (required.resources === undefined)
|
|
313
|
+
return false;
|
|
314
|
+
return required.resources.every((req) => grant.resources.some((pat) => resourcePatternMatches(pat, req)));
|
|
315
|
+
}
|
|
316
|
+
function mergeEntitlements(a, b) {
|
|
317
|
+
if (a.entitlements.length === 0)
|
|
318
|
+
return b;
|
|
319
|
+
if (b.entitlements.length === 0)
|
|
320
|
+
return a;
|
|
321
|
+
const merged = new Map;
|
|
322
|
+
for (const entitlement of a.entitlements) {
|
|
323
|
+
merged.set(entitlement.id, entitlement);
|
|
324
|
+
}
|
|
325
|
+
for (const entitlement of b.entitlements) {
|
|
326
|
+
const existing = merged.get(entitlement.id);
|
|
327
|
+
if (existing) {
|
|
328
|
+
merged.set(entitlement.id, mergeEntitlementPair(existing, entitlement));
|
|
329
|
+
} else {
|
|
330
|
+
merged.set(entitlement.id, entitlement);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return { entitlements: Array.from(merged.values()) };
|
|
334
|
+
}
|
|
335
|
+
function mergeEntitlementPair(a, b) {
|
|
336
|
+
const optional = (a.optional ?? false) && (b.optional ?? false) ? true : undefined;
|
|
337
|
+
const reason = a.reason ?? b.reason;
|
|
338
|
+
const resources = mergeResources(a.resources, b.resources);
|
|
339
|
+
const result = {
|
|
340
|
+
id: a.id,
|
|
341
|
+
...reason !== undefined && { reason },
|
|
342
|
+
...optional === true && { optional: true },
|
|
343
|
+
...resources !== undefined && { resources }
|
|
344
|
+
};
|
|
345
|
+
return result;
|
|
346
|
+
}
|
|
347
|
+
function mergeResources(a, b) {
|
|
348
|
+
if (a === undefined || b === undefined)
|
|
349
|
+
return;
|
|
350
|
+
const set = new Set([...a, ...b]);
|
|
351
|
+
return Array.from(set);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/task-graph/GraphEntitlementUtils.ts
|
|
355
|
+
function computeGraphEntitlements(graph, options) {
|
|
356
|
+
const tasks = graph.getTasks();
|
|
357
|
+
if (tasks.length === 0)
|
|
358
|
+
return EMPTY_ENTITLEMENTS;
|
|
359
|
+
const trackOrigins = options?.trackOrigins ?? false;
|
|
360
|
+
const conditionalBranches = options?.conditionalBranches ?? "all";
|
|
361
|
+
const merged = new Map;
|
|
362
|
+
for (const task of tasks) {
|
|
363
|
+
if (conditionalBranches === "active" && task.status !== undefined) {
|
|
364
|
+
if (task.status === TaskStatus.DISABLED)
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const taskEntitlements = task.entitlements();
|
|
368
|
+
for (const entitlement of taskEntitlements.entitlements) {
|
|
369
|
+
const existing = merged.get(entitlement.id);
|
|
370
|
+
if (existing) {
|
|
371
|
+
existing.entitlement = mergeEntitlementPair(existing.entitlement, entitlement);
|
|
372
|
+
if (trackOrigins) {
|
|
373
|
+
existing.sourceTaskIds.push(task.id);
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
merged.set(entitlement.id, {
|
|
377
|
+
entitlement,
|
|
378
|
+
sourceTaskIds: trackOrigins ? [task.id] : []
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (merged.size === 0)
|
|
384
|
+
return EMPTY_ENTITLEMENTS;
|
|
385
|
+
if (trackOrigins) {
|
|
386
|
+
const entitlements = [];
|
|
387
|
+
for (const { entitlement, sourceTaskIds } of merged.values()) {
|
|
388
|
+
entitlements.push({ ...entitlement, sourceTaskIds });
|
|
389
|
+
}
|
|
390
|
+
return { entitlements };
|
|
391
|
+
}
|
|
392
|
+
return { entitlements: Array.from(merged.values()).map((e) => e.entitlement) };
|
|
393
|
+
}
|
|
394
|
+
// src/task/InputResolver.ts
|
|
395
|
+
import { getInputResolvers } from "@workglow/util";
|
|
396
|
+
function getSchemaFormat(schema, visited = new WeakSet) {
|
|
397
|
+
if (typeof schema !== "object" || schema === null)
|
|
398
|
+
return;
|
|
399
|
+
if (visited.has(schema))
|
|
400
|
+
return;
|
|
401
|
+
visited.add(schema);
|
|
402
|
+
const s = schema;
|
|
403
|
+
if (typeof s.format === "string")
|
|
404
|
+
return s.format;
|
|
405
|
+
const variants = s.oneOf ?? s.anyOf;
|
|
406
|
+
if (Array.isArray(variants)) {
|
|
407
|
+
for (const variant of variants) {
|
|
408
|
+
if (typeof variant === "object" && variant !== null) {
|
|
409
|
+
const v = variant;
|
|
410
|
+
if (typeof v.format === "string")
|
|
411
|
+
return v.format;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
const allOf = s.allOf;
|
|
416
|
+
if (Array.isArray(allOf)) {
|
|
417
|
+
for (const sub of allOf) {
|
|
418
|
+
const fmt = getSchemaFormat(sub, visited);
|
|
419
|
+
if (fmt !== undefined)
|
|
420
|
+
return fmt;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
function getObjectSchema(schema, visited = new WeakSet) {
|
|
426
|
+
if (typeof schema !== "object" || schema === null)
|
|
427
|
+
return;
|
|
428
|
+
if (visited.has(schema))
|
|
429
|
+
return;
|
|
430
|
+
visited.add(schema);
|
|
431
|
+
const s = schema;
|
|
432
|
+
if (s.type === "object" && s.properties && typeof s.properties === "object") {
|
|
433
|
+
return s;
|
|
434
|
+
}
|
|
435
|
+
const variants = s.oneOf ?? s.anyOf;
|
|
436
|
+
if (Array.isArray(variants)) {
|
|
437
|
+
for (const variant of variants) {
|
|
438
|
+
if (typeof variant === "object" && variant !== null) {
|
|
439
|
+
const v = variant;
|
|
440
|
+
if (v.type === "object" && v.properties && typeof v.properties === "object") {
|
|
441
|
+
return v;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const allOf = s.allOf;
|
|
447
|
+
if (Array.isArray(allOf)) {
|
|
448
|
+
for (const sub of allOf) {
|
|
449
|
+
const result = getObjectSchema(sub, visited);
|
|
450
|
+
if (result !== undefined)
|
|
451
|
+
return result;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
function getFormatPrefix(format) {
|
|
457
|
+
const colonIndex = format.indexOf(":");
|
|
458
|
+
return colonIndex >= 0 ? format.substring(0, colonIndex) : format;
|
|
459
|
+
}
|
|
460
|
+
function schemaHasFormatAnnotations(schema) {
|
|
461
|
+
if (typeof schema === "boolean")
|
|
462
|
+
return false;
|
|
463
|
+
const properties = schema.properties;
|
|
464
|
+
if (!properties || typeof properties !== "object")
|
|
465
|
+
return false;
|
|
466
|
+
for (const propSchema of Object.values(properties)) {
|
|
467
|
+
if (getSchemaFormat(propSchema) !== undefined)
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
async function resolveSchemaInputs(input, schema, config, visited = new Set) {
|
|
473
|
+
if (typeof schema === "boolean")
|
|
474
|
+
return input;
|
|
475
|
+
const properties = schema.properties;
|
|
476
|
+
if (!properties || typeof properties !== "object")
|
|
477
|
+
return input;
|
|
478
|
+
const resolvers = getInputResolvers();
|
|
479
|
+
const resolved = { ...input };
|
|
480
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
481
|
+
let value = resolved[key];
|
|
482
|
+
const format = getSchemaFormat(propSchema);
|
|
483
|
+
if (format) {
|
|
484
|
+
let resolver = resolvers.get(format);
|
|
485
|
+
if (!resolver) {
|
|
486
|
+
const prefix = getFormatPrefix(format);
|
|
487
|
+
resolver = resolvers.get(prefix);
|
|
488
|
+
}
|
|
489
|
+
if (resolver) {
|
|
490
|
+
if (typeof value === "string") {
|
|
491
|
+
value = await resolver(value, format, config.registry);
|
|
492
|
+
resolved[key] = value;
|
|
493
|
+
} else if (Array.isArray(value) && value.some((item) => typeof item === "string")) {
|
|
494
|
+
const results = await Promise.all(value.map((item) => typeof item === "string" ? resolver(item, format, config.registry) : item));
|
|
495
|
+
value = results.filter((result) => result !== undefined);
|
|
496
|
+
resolved[key] = value;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (value !== null && value !== undefined && typeof value === "object" && !Array.isArray(value)) {
|
|
501
|
+
const objectSchema = getObjectSchema(propSchema);
|
|
502
|
+
if (objectSchema && !visited.has(objectSchema)) {
|
|
503
|
+
visited.add(objectSchema);
|
|
504
|
+
try {
|
|
505
|
+
resolved[key] = await resolveSchemaInputs(value, objectSchema, config, visited);
|
|
506
|
+
} finally {
|
|
507
|
+
visited.delete(objectSchema);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return resolved;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/task-graph/GraphFormatScanner.ts
|
|
516
|
+
function schemaHasFormat(schema, targetFormat) {
|
|
517
|
+
if (typeof schema !== "object" || schema === null)
|
|
518
|
+
return false;
|
|
519
|
+
const s = schema;
|
|
520
|
+
const properties = s.properties;
|
|
521
|
+
if (properties && typeof properties === "object") {
|
|
522
|
+
for (const propSchema of Object.values(properties)) {
|
|
523
|
+
const format = getSchemaFormat(propSchema);
|
|
524
|
+
if (format === targetFormat)
|
|
525
|
+
return true;
|
|
526
|
+
const objectSchema = getObjectSchema(propSchema);
|
|
527
|
+
if (objectSchema && schemaHasFormat(objectSchema, targetFormat))
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
function scanGraphForFormat(graph, targetFormat) {
|
|
534
|
+
for (const task of graph.getTasks()) {
|
|
535
|
+
const inputSchema = task.inputSchema();
|
|
536
|
+
if (typeof inputSchema !== "boolean" && schemaHasFormat(inputSchema, targetFormat)) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
const configSchema = task.configSchema();
|
|
540
|
+
if (typeof configSchema !== "boolean" && schemaHasFormat(configSchema, targetFormat)) {
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
function scanGraphForCredentials(graph) {
|
|
547
|
+
const credentialFormats = new Set;
|
|
548
|
+
for (const task of graph.getTasks()) {
|
|
549
|
+
collectCredentialFormats(task.inputSchema(), credentialFormats);
|
|
550
|
+
collectCredentialFormats(task.configSchema(), credentialFormats);
|
|
551
|
+
}
|
|
552
|
+
return {
|
|
553
|
+
needsCredentials: credentialFormats.size > 0,
|
|
554
|
+
credentialFormats
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
function collectCredentialFormats(schema, formats) {
|
|
558
|
+
if (typeof schema === "boolean" || typeof schema !== "object" || schema === null)
|
|
559
|
+
return;
|
|
560
|
+
const s = schema;
|
|
561
|
+
const properties = s.properties;
|
|
562
|
+
if (!properties || typeof properties !== "object")
|
|
563
|
+
return;
|
|
564
|
+
for (const propSchema of Object.values(properties)) {
|
|
565
|
+
const format = getSchemaFormat(propSchema);
|
|
566
|
+
if (format === "credential") {
|
|
567
|
+
formats.add(format);
|
|
568
|
+
}
|
|
569
|
+
const objectSchema = getObjectSchema(propSchema);
|
|
570
|
+
if (objectSchema) {
|
|
571
|
+
collectCredentialFormats(objectSchema, formats);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
260
575
|
// src/task-graph/GraphSchemaUtils.ts
|
|
261
576
|
import { uuid4 } from "@workglow/util";
|
|
262
577
|
function calculateNodeDepths(graph) {
|
|
@@ -606,8 +921,8 @@ function addBoundaryNodesToDependencyJson(items, graph) {
|
|
|
606
921
|
return [...prependItems, ...items, ...appendItems];
|
|
607
922
|
}
|
|
608
923
|
// src/task-graph/TaskGraph.ts
|
|
609
|
-
import { DirectedAcyclicGraph } from "@workglow/util/graph";
|
|
610
924
|
import { EventEmitter as EventEmitter4, uuid4 as uuid44 } from "@workglow/util";
|
|
925
|
+
import { DirectedAcyclicGraph } from "@workglow/util/graph";
|
|
611
926
|
|
|
612
927
|
// src/task/GraphAsTask.ts
|
|
613
928
|
import { getLogger as getLogger4 } from "@workglow/util";
|
|
@@ -725,8 +1040,8 @@ function getNestedValue(obj, path) {
|
|
|
725
1040
|
}
|
|
726
1041
|
|
|
727
1042
|
// src/task/Task.ts
|
|
728
|
-
import { compileSchema } from "@workglow/util/schema";
|
|
729
1043
|
import { deepEqual, EventEmitter as EventEmitter3, uuid4 as uuid42 } from "@workglow/util";
|
|
1044
|
+
import { compileSchema } from "@workglow/util/schema";
|
|
730
1045
|
|
|
731
1046
|
// src/task/TaskError.ts
|
|
732
1047
|
import { BaseError } from "@workglow/util";
|
|
@@ -806,6 +1121,13 @@ class TaskInvalidInputError extends TaskError {
|
|
|
806
1121
|
}
|
|
807
1122
|
}
|
|
808
1123
|
|
|
1124
|
+
class TaskEntitlementError extends TaskError {
|
|
1125
|
+
static type = "TaskEntitlementError";
|
|
1126
|
+
constructor(message = "Required entitlements denied") {
|
|
1127
|
+
super(message);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
809
1131
|
class TaskSerializationError extends TaskError {
|
|
810
1132
|
static type = "TaskSerializationError";
|
|
811
1133
|
constructor(taskType) {
|
|
@@ -821,100 +1143,6 @@ import {
|
|
|
821
1143
|
SpanStatusCode
|
|
822
1144
|
} from "@workglow/util";
|
|
823
1145
|
|
|
824
|
-
// src/task/InputResolver.ts
|
|
825
|
-
import { getInputResolvers } from "@workglow/util";
|
|
826
|
-
function getSchemaFormat(schema) {
|
|
827
|
-
if (typeof schema !== "object" || schema === null)
|
|
828
|
-
return;
|
|
829
|
-
const s = schema;
|
|
830
|
-
if (typeof s.format === "string")
|
|
831
|
-
return s.format;
|
|
832
|
-
const variants = s.oneOf ?? s.anyOf;
|
|
833
|
-
if (Array.isArray(variants)) {
|
|
834
|
-
for (const variant of variants) {
|
|
835
|
-
if (typeof variant === "object" && variant !== null) {
|
|
836
|
-
const v = variant;
|
|
837
|
-
if (typeof v.format === "string")
|
|
838
|
-
return v.format;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
function getObjectSchema(schema) {
|
|
845
|
-
if (typeof schema !== "object" || schema === null)
|
|
846
|
-
return;
|
|
847
|
-
const s = schema;
|
|
848
|
-
if (s.type === "object" && s.properties && typeof s.properties === "object") {
|
|
849
|
-
return s;
|
|
850
|
-
}
|
|
851
|
-
const variants = s.oneOf ?? s.anyOf;
|
|
852
|
-
if (Array.isArray(variants)) {
|
|
853
|
-
for (const variant of variants) {
|
|
854
|
-
if (typeof variant === "object" && variant !== null) {
|
|
855
|
-
const v = variant;
|
|
856
|
-
if (v.type === "object" && v.properties && typeof v.properties === "object") {
|
|
857
|
-
return v;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
function getFormatPrefix(format) {
|
|
865
|
-
const colonIndex = format.indexOf(":");
|
|
866
|
-
return colonIndex >= 0 ? format.substring(0, colonIndex) : format;
|
|
867
|
-
}
|
|
868
|
-
function schemaHasFormatAnnotations(schema) {
|
|
869
|
-
if (typeof schema === "boolean")
|
|
870
|
-
return false;
|
|
871
|
-
const properties = schema.properties;
|
|
872
|
-
if (!properties || typeof properties !== "object")
|
|
873
|
-
return false;
|
|
874
|
-
for (const propSchema of Object.values(properties)) {
|
|
875
|
-
if (getSchemaFormat(propSchema) !== undefined)
|
|
876
|
-
return true;
|
|
877
|
-
}
|
|
878
|
-
return false;
|
|
879
|
-
}
|
|
880
|
-
async function resolveSchemaInputs(input, schema, config) {
|
|
881
|
-
if (typeof schema === "boolean")
|
|
882
|
-
return input;
|
|
883
|
-
const properties = schema.properties;
|
|
884
|
-
if (!properties || typeof properties !== "object")
|
|
885
|
-
return input;
|
|
886
|
-
const resolvers = getInputResolvers();
|
|
887
|
-
const resolved = { ...input };
|
|
888
|
-
for (const [key, propSchema] of Object.entries(properties)) {
|
|
889
|
-
let value = resolved[key];
|
|
890
|
-
const format = getSchemaFormat(propSchema);
|
|
891
|
-
if (format) {
|
|
892
|
-
let resolver = resolvers.get(format);
|
|
893
|
-
if (!resolver) {
|
|
894
|
-
const prefix = getFormatPrefix(format);
|
|
895
|
-
resolver = resolvers.get(prefix);
|
|
896
|
-
}
|
|
897
|
-
if (resolver) {
|
|
898
|
-
if (typeof value === "string") {
|
|
899
|
-
value = await resolver(value, format, config.registry);
|
|
900
|
-
resolved[key] = value;
|
|
901
|
-
} else if (Array.isArray(value) && value.some((item) => typeof item === "string")) {
|
|
902
|
-
const results = await Promise.all(value.map((item) => typeof item === "string" ? resolver(item, format, config.registry) : item));
|
|
903
|
-
value = results.filter((result) => result !== undefined);
|
|
904
|
-
resolved[key] = value;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
if (value !== null && value !== undefined && typeof value === "object" && !Array.isArray(value)) {
|
|
909
|
-
const objectSchema = getObjectSchema(propSchema);
|
|
910
|
-
if (objectSchema) {
|
|
911
|
-
resolved[key] = await resolveSchemaInputs(value, objectSchema, config);
|
|
912
|
-
}
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
return resolved;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
1146
|
// src/task/StreamTypes.ts
|
|
919
1147
|
function getPortStreamMode(schema, portId) {
|
|
920
1148
|
if (typeof schema === "boolean")
|
|
@@ -1206,11 +1434,29 @@ class TaskRunner {
|
|
|
1206
1434
|
}
|
|
1207
1435
|
case "object-delta": {
|
|
1208
1436
|
if (accumulatedObjects) {
|
|
1209
|
-
accumulatedObjects.
|
|
1437
|
+
const existing = accumulatedObjects.get(event.port);
|
|
1438
|
+
if (Array.isArray(event.objectDelta)) {
|
|
1439
|
+
const arr = Array.isArray(existing) ? [...existing] : [];
|
|
1440
|
+
for (const item of event.objectDelta) {
|
|
1441
|
+
const itemObj = item;
|
|
1442
|
+
if (itemObj && typeof itemObj === "object" && "id" in itemObj) {
|
|
1443
|
+
const idx = arr.findIndex((e) => e.id === itemObj.id);
|
|
1444
|
+
if (idx >= 0)
|
|
1445
|
+
arr[idx] = item;
|
|
1446
|
+
else
|
|
1447
|
+
arr.push(item);
|
|
1448
|
+
} else {
|
|
1449
|
+
arr.push(item);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
accumulatedObjects.set(event.port, arr);
|
|
1453
|
+
} else {
|
|
1454
|
+
accumulatedObjects.set(event.port, event.objectDelta);
|
|
1455
|
+
}
|
|
1210
1456
|
}
|
|
1211
1457
|
this.task.runOutputData = {
|
|
1212
1458
|
...this.task.runOutputData,
|
|
1213
|
-
[event.port]: event.objectDelta
|
|
1459
|
+
[event.port]: accumulatedObjects?.get(event.port) ?? event.objectDelta
|
|
1214
1460
|
};
|
|
1215
1461
|
this.task.emit("stream_chunk", event);
|
|
1216
1462
|
const progress = Math.min(99, Math.round(100 * (1 - Math.exp(-0.05 * chunkCount))));
|
|
@@ -1271,11 +1517,6 @@ class TaskRunner {
|
|
|
1271
1517
|
this.abortController.signal.addEventListener("abort", () => {
|
|
1272
1518
|
this.handleAbort();
|
|
1273
1519
|
});
|
|
1274
|
-
if (config.signal?.aborted) {
|
|
1275
|
-
this.abortController.abort();
|
|
1276
|
-
} else if (config.signal) {
|
|
1277
|
-
config.signal.addEventListener("abort", () => this.abortController.abort(), { once: true });
|
|
1278
|
-
}
|
|
1279
1520
|
const cache = config.outputCache ?? this.task.runConfig?.outputCache;
|
|
1280
1521
|
if (cache === true) {
|
|
1281
1522
|
let instance = globalServiceRegistry.get(TASK_OUTPUT_REPOSITORY);
|
|
@@ -1286,6 +1527,21 @@ class TaskRunner {
|
|
|
1286
1527
|
this.outputCache = cache;
|
|
1287
1528
|
}
|
|
1288
1529
|
this.shouldAccumulate = config.shouldAccumulate !== false;
|
|
1530
|
+
if (config.updateProgress) {
|
|
1531
|
+
this.updateProgress = config.updateProgress;
|
|
1532
|
+
}
|
|
1533
|
+
if (config.registry) {
|
|
1534
|
+
this.registry = config.registry;
|
|
1535
|
+
}
|
|
1536
|
+
if (config.signal) {
|
|
1537
|
+
const onAbort = () => this.abortController.abort();
|
|
1538
|
+
config.signal.addEventListener("abort", onAbort, { once: true });
|
|
1539
|
+
if (config.signal.aborted) {
|
|
1540
|
+
config.signal.removeEventListener("abort", onAbort);
|
|
1541
|
+
this.abortController.abort();
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1289
1545
|
const timeout = this.task.config.timeout;
|
|
1290
1546
|
if (timeout !== undefined && timeout > 0) {
|
|
1291
1547
|
this.pendingTimeoutError = new TaskTimeoutError(timeout);
|
|
@@ -1293,12 +1549,6 @@ class TaskRunner {
|
|
|
1293
1549
|
this.abort();
|
|
1294
1550
|
}, timeout);
|
|
1295
1551
|
}
|
|
1296
|
-
if (config.updateProgress) {
|
|
1297
|
-
this.updateProgress = config.updateProgress;
|
|
1298
|
-
}
|
|
1299
|
-
if (config.registry) {
|
|
1300
|
-
this.registry = config.registry;
|
|
1301
|
-
}
|
|
1302
1552
|
const telemetry = getTelemetryProvider();
|
|
1303
1553
|
if (telemetry.isEnabled) {
|
|
1304
1554
|
this.telemetrySpan = telemetry.startSpan("workglow.task.run", {
|
|
@@ -1435,6 +1685,11 @@ class Task {
|
|
|
1435
1685
|
static hasDynamicSchemas = false;
|
|
1436
1686
|
static passthroughInputsToOutputs = false;
|
|
1437
1687
|
static customizable = false;
|
|
1688
|
+
static isGraphOutput = false;
|
|
1689
|
+
static hasDynamicEntitlements = false;
|
|
1690
|
+
static entitlements() {
|
|
1691
|
+
return EMPTY_ENTITLEMENTS;
|
|
1692
|
+
}
|
|
1438
1693
|
static inputSchema() {
|
|
1439
1694
|
return {
|
|
1440
1695
|
type: "object",
|
|
@@ -1489,6 +1744,13 @@ class Task {
|
|
|
1489
1744
|
configSchema() {
|
|
1490
1745
|
return this.constructor.configSchema();
|
|
1491
1746
|
}
|
|
1747
|
+
entitlements() {
|
|
1748
|
+
return this.constructor.entitlements();
|
|
1749
|
+
}
|
|
1750
|
+
emitEntitlementChange(entitlements) {
|
|
1751
|
+
const final = entitlements ?? this.entitlements();
|
|
1752
|
+
this.emit("entitlementChange", final);
|
|
1753
|
+
}
|
|
1492
1754
|
get type() {
|
|
1493
1755
|
return this.constructor.type;
|
|
1494
1756
|
}
|
|
@@ -1526,15 +1788,16 @@ class Task {
|
|
|
1526
1788
|
return this._events;
|
|
1527
1789
|
}
|
|
1528
1790
|
_events;
|
|
1529
|
-
constructor(
|
|
1791
|
+
constructor(config = {}, runConfig = {}) {
|
|
1792
|
+
const { defaults: callerDefaultInputs, ...restConfig } = config;
|
|
1530
1793
|
const inputDefaults = this.getDefaultInputsFromStaticInputDefinitions();
|
|
1531
|
-
const mergedDefaults = Object.assign(inputDefaults, callerDefaultInputs);
|
|
1794
|
+
const mergedDefaults = Object.assign(inputDefaults, callerDefaultInputs ?? {});
|
|
1532
1795
|
this.defaults = this.stripSymbols(mergedDefaults);
|
|
1533
1796
|
this.resetInputData();
|
|
1534
1797
|
const title = this.constructor.title || undefined;
|
|
1535
1798
|
const baseConfig = Object.assign({
|
|
1536
1799
|
...title ? { title } : {}
|
|
1537
|
-
},
|
|
1800
|
+
}, restConfig);
|
|
1538
1801
|
if (baseConfig.id === undefined) {
|
|
1539
1802
|
baseConfig.id = uuid42();
|
|
1540
1803
|
}
|
|
@@ -1881,6 +2144,10 @@ class Task {
|
|
|
1881
2144
|
if (Object.keys(config).length > 0) {
|
|
1882
2145
|
base.config = config;
|
|
1883
2146
|
}
|
|
2147
|
+
const taskEntitlements = this.entitlements();
|
|
2148
|
+
if (taskEntitlements.entitlements.length > 0) {
|
|
2149
|
+
base.entitlements = taskEntitlements;
|
|
2150
|
+
}
|
|
1884
2151
|
return this.stripSymbols(base);
|
|
1885
2152
|
}
|
|
1886
2153
|
toDependencyJSON(options) {
|
|
@@ -2010,7 +2277,7 @@ class ConditionalTask extends Task {
|
|
|
2010
2277
|
}
|
|
2011
2278
|
}
|
|
2012
2279
|
} catch (error) {
|
|
2013
|
-
getLogger2().
|
|
2280
|
+
getLogger2().error(`Condition evaluation failed for branch "${branch.id}":`, { error });
|
|
2014
2281
|
}
|
|
2015
2282
|
}
|
|
2016
2283
|
if (this.activeBranches.size === 0 && defaultBranch) {
|
|
@@ -2134,6 +2401,111 @@ class ConditionalTask extends Task {
|
|
|
2134
2401
|
}
|
|
2135
2402
|
}
|
|
2136
2403
|
|
|
2404
|
+
// src/task/EntitlementEnforcer.ts
|
|
2405
|
+
import { createServiceToken as createServiceToken3 } from "@workglow/util";
|
|
2406
|
+
|
|
2407
|
+
// src/task/EntitlementPolicy.ts
|
|
2408
|
+
var EMPTY_POLICY = Object.freeze({
|
|
2409
|
+
deny: Object.freeze([]),
|
|
2410
|
+
grant: Object.freeze([]),
|
|
2411
|
+
ask: Object.freeze([])
|
|
2412
|
+
});
|
|
2413
|
+
function ruleCovers(rule, required) {
|
|
2414
|
+
if (!entitlementCovers(rule.id, required.id))
|
|
2415
|
+
return false;
|
|
2416
|
+
return grantCoversResources(rule, required);
|
|
2417
|
+
}
|
|
2418
|
+
function evaluatePolicy(policy, required) {
|
|
2419
|
+
const results = [];
|
|
2420
|
+
for (const entitlement of required.entitlements) {
|
|
2421
|
+
if (entitlement.optional)
|
|
2422
|
+
continue;
|
|
2423
|
+
const denyMatch = policy.deny.find((rule) => ruleCovers(rule, entitlement));
|
|
2424
|
+
if (denyMatch) {
|
|
2425
|
+
results.push({ verdict: "denied", entitlement, matchedRule: denyMatch });
|
|
2426
|
+
continue;
|
|
2427
|
+
}
|
|
2428
|
+
const grantMatch = policy.grant.find((rule) => ruleCovers(rule, entitlement));
|
|
2429
|
+
if (grantMatch) {
|
|
2430
|
+
results.push({ verdict: "granted", entitlement, matchedRule: grantMatch });
|
|
2431
|
+
continue;
|
|
2432
|
+
}
|
|
2433
|
+
const askMatch = policy.ask.find((rule) => ruleCovers(rule, entitlement));
|
|
2434
|
+
if (askMatch) {
|
|
2435
|
+
results.push({ verdict: "ask", entitlement, matchedRule: askMatch });
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
results.push({ verdict: "denied", entitlement });
|
|
2439
|
+
}
|
|
2440
|
+
return results;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
// src/task/EntitlementResolver.ts
|
|
2444
|
+
import { createServiceToken as createServiceToken2 } from "@workglow/util";
|
|
2445
|
+
var PERMISSIVE_RESOLVER = {
|
|
2446
|
+
lookup: () => "grant",
|
|
2447
|
+
prompt: async () => "grant",
|
|
2448
|
+
save: () => {}
|
|
2449
|
+
};
|
|
2450
|
+
var DENY_ALL_RESOLVER = {
|
|
2451
|
+
lookup: () => "deny",
|
|
2452
|
+
prompt: async () => "deny",
|
|
2453
|
+
save: () => {}
|
|
2454
|
+
};
|
|
2455
|
+
var ENTITLEMENT_RESOLVER = createServiceToken2("workglow.entitlementResolver");
|
|
2456
|
+
|
|
2457
|
+
// src/task/EntitlementEnforcer.ts
|
|
2458
|
+
var PERMISSIVE_ENFORCER = {
|
|
2459
|
+
checkAll: async () => [],
|
|
2460
|
+
checkTask: async () => []
|
|
2461
|
+
};
|
|
2462
|
+
function createPolicyEnforcer(policy, resolver = PERMISSIVE_RESOLVER) {
|
|
2463
|
+
async function resolveAsks(required, taskType, taskId) {
|
|
2464
|
+
const results = evaluatePolicy(policy, required);
|
|
2465
|
+
const denied = [];
|
|
2466
|
+
for (const result of results) {
|
|
2467
|
+
if (result.verdict === "denied") {
|
|
2468
|
+
denied.push(result.entitlement);
|
|
2469
|
+
} else if (result.verdict === "ask") {
|
|
2470
|
+
const request = {
|
|
2471
|
+
entitlement: result.entitlement,
|
|
2472
|
+
taskType: taskType ?? "unknown",
|
|
2473
|
+
taskId: taskId ?? "unknown"
|
|
2474
|
+
};
|
|
2475
|
+
const saved = resolver.lookup(request);
|
|
2476
|
+
if (saved !== undefined) {
|
|
2477
|
+
if (saved === "deny") {
|
|
2478
|
+
denied.push(result.entitlement);
|
|
2479
|
+
}
|
|
2480
|
+
continue;
|
|
2481
|
+
}
|
|
2482
|
+
const answer = await resolver.prompt(request);
|
|
2483
|
+
resolver.save(request, answer);
|
|
2484
|
+
if (answer === "deny") {
|
|
2485
|
+
denied.push(result.entitlement);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
return denied;
|
|
2490
|
+
}
|
|
2491
|
+
return {
|
|
2492
|
+
async checkAll(required) {
|
|
2493
|
+
return resolveAsks(required);
|
|
2494
|
+
},
|
|
2495
|
+
async checkTask(task) {
|
|
2496
|
+
const entitlements = task.entitlements();
|
|
2497
|
+
return resolveAsks(entitlements, task.constructor.type, task.id);
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
function createScopedEnforcer(grants) {
|
|
2502
|
+
return createPolicyEnforcer({ deny: [], grant: grants, ask: [] });
|
|
2503
|
+
}
|
|
2504
|
+
function createGrantListEnforcer(grants) {
|
|
2505
|
+
return createScopedEnforcer(grants.map((id) => ({ id })));
|
|
2506
|
+
}
|
|
2507
|
+
var ENTITLEMENT_ENFORCER = createServiceToken3("workglow.entitlementEnforcer");
|
|
2508
|
+
|
|
2137
2509
|
// src/task-graph/TaskGraphScheduler.ts
|
|
2138
2510
|
class TopologicalScheduler {
|
|
2139
2511
|
dag;
|
|
@@ -2302,6 +2674,7 @@ class TaskGraphRunner {
|
|
|
2302
2674
|
telemetrySpan;
|
|
2303
2675
|
graphTimeoutTimer;
|
|
2304
2676
|
pendingGraphTimeoutError;
|
|
2677
|
+
activeEnforcer;
|
|
2305
2678
|
constructor(graph, outputCache, processScheduler = new DependencyBasedScheduler(graph), reactiveScheduler = new TopologicalScheduler(graph)) {
|
|
2306
2679
|
this.processScheduler = processScheduler;
|
|
2307
2680
|
this.reactiveScheduler = reactiveScheduler;
|
|
@@ -2370,7 +2743,7 @@ class TaskGraphRunner {
|
|
|
2370
2743
|
throw new TaskAbortedError;
|
|
2371
2744
|
}
|
|
2372
2745
|
await this.handleComplete();
|
|
2373
|
-
return results;
|
|
2746
|
+
return this.filterLeafResults(results);
|
|
2374
2747
|
}
|
|
2375
2748
|
async runGraphReactive(input = {}, config) {
|
|
2376
2749
|
await this.handleStartReactive(config);
|
|
@@ -2394,7 +2767,7 @@ class TaskGraphRunner {
|
|
|
2394
2767
|
}
|
|
2395
2768
|
}
|
|
2396
2769
|
await this.handleCompleteReactive();
|
|
2397
|
-
return results;
|
|
2770
|
+
return this.filterLeafResults(results);
|
|
2398
2771
|
} catch (error) {
|
|
2399
2772
|
await this.handleErrorReactive();
|
|
2400
2773
|
throw error;
|
|
@@ -2426,6 +2799,15 @@ class TaskGraphRunner {
|
|
|
2426
2799
|
task.regenerateGraph();
|
|
2427
2800
|
}
|
|
2428
2801
|
}
|
|
2802
|
+
filterLeafResults(results) {
|
|
2803
|
+
if (results.length <= 1)
|
|
2804
|
+
return results;
|
|
2805
|
+
const graphOutputResults = results.filter((r) => {
|
|
2806
|
+
const task = this.graph.getTask(r.id);
|
|
2807
|
+
return task && task.constructor.isGraphOutput;
|
|
2808
|
+
});
|
|
2809
|
+
return graphOutputResults.length > 0 ? graphOutputResults : results;
|
|
2810
|
+
}
|
|
2429
2811
|
mergeExecuteOutputsToRunOutput(results, compoundMerge) {
|
|
2430
2812
|
if (compoundMerge === GRAPH_RESULT_ARRAY) {
|
|
2431
2813
|
return results;
|
|
@@ -2607,6 +2989,12 @@ class TaskGraphRunner {
|
|
|
2607
2989
|
}
|
|
2608
2990
|
await this.awaitStreamInputs(task);
|
|
2609
2991
|
this.copyInputFromEdgesToNode(task);
|
|
2992
|
+
if (this.activeEnforcer && task.constructor.hasDynamicEntitlements) {
|
|
2993
|
+
const denied = await this.activeEnforcer.checkTask(task);
|
|
2994
|
+
if (denied.length > 0) {
|
|
2995
|
+
throw new TaskEntitlementError(`Task ${task.constructor.type} denied entitlements: ${denied.map((e) => e.id).join(", ")}`);
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2610
2998
|
if (isStreamable) {
|
|
2611
2999
|
return this.runStreamingTask(task, input);
|
|
2612
3000
|
}
|
|
@@ -2776,12 +3164,6 @@ class TaskGraphRunner {
|
|
|
2776
3164
|
}
|
|
2777
3165
|
this.graph.outputCache = this.outputCache;
|
|
2778
3166
|
}
|
|
2779
|
-
if (config?.maxTasks !== undefined && config.maxTasks > 0) {
|
|
2780
|
-
const taskCount = this.graph.getTasks().length;
|
|
2781
|
-
if (taskCount > config.maxTasks) {
|
|
2782
|
-
throw new TaskConfigurationError(`Graph has ${taskCount} tasks, exceeding the limit of ${config.maxTasks}`);
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
3167
|
if (this.running || this.reactiveRunning) {
|
|
2786
3168
|
throw new TaskConfigurationError("Graph is already running");
|
|
2787
3169
|
}
|
|
@@ -2797,13 +3179,16 @@ class TaskGraphRunner {
|
|
|
2797
3179
|
this.abortController?.abort();
|
|
2798
3180
|
}, config.timeout);
|
|
2799
3181
|
}
|
|
2800
|
-
if (config?.parentSignal
|
|
2801
|
-
|
|
2802
|
-
return;
|
|
2803
|
-
} else {
|
|
2804
|
-
config?.parentSignal?.addEventListener("abort", () => {
|
|
3182
|
+
if (config?.parentSignal) {
|
|
3183
|
+
const onParentAbort = () => {
|
|
2805
3184
|
this.abortController?.abort();
|
|
2806
|
-
}
|
|
3185
|
+
};
|
|
3186
|
+
config.parentSignal.addEventListener("abort", onParentAbort, { once: true });
|
|
3187
|
+
if (config.parentSignal.aborted) {
|
|
3188
|
+
config.parentSignal.removeEventListener("abort", onParentAbort);
|
|
3189
|
+
this.abortController.abort();
|
|
3190
|
+
return;
|
|
3191
|
+
}
|
|
2807
3192
|
}
|
|
2808
3193
|
this.runId = uuid43();
|
|
2809
3194
|
this.resetGraph(this.graph, this.runId);
|
|
@@ -2811,6 +3196,36 @@ class TaskGraphRunner {
|
|
|
2811
3196
|
this.inProgressTasks.clear();
|
|
2812
3197
|
this.inProgressFunctions.clear();
|
|
2813
3198
|
this.failedTaskErrors.clear();
|
|
3199
|
+
try {
|
|
3200
|
+
if (config?.maxTasks !== undefined && config.maxTasks > 0) {
|
|
3201
|
+
const taskCount = this.graph.getTasks().length;
|
|
3202
|
+
if (taskCount > config.maxTasks) {
|
|
3203
|
+
throw new TaskConfigurationError(`Graph has ${taskCount} tasks, exceeding the limit of ${config.maxTasks}`);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
if (config?.enforceEntitlements) {
|
|
3207
|
+
if (!this.registry.has(ENTITLEMENT_ENFORCER)) {
|
|
3208
|
+
throw new TaskConfigurationError("enforceEntitlements is enabled but no IEntitlementEnforcer is registered. " + "Register an enforcer via ENTITLEMENT_ENFORCER before running the graph.");
|
|
3209
|
+
}
|
|
3210
|
+
const enforcer = this.registry.get(ENTITLEMENT_ENFORCER);
|
|
3211
|
+
const denied = await enforcer.checkAll(computeGraphEntitlements(this.graph));
|
|
3212
|
+
if (denied.length > 0) {
|
|
3213
|
+
throw new TaskEntitlementError(`Denied entitlements: ${denied.map((e) => e.id).join(", ")}`);
|
|
3214
|
+
}
|
|
3215
|
+
this.activeEnforcer = enforcer;
|
|
3216
|
+
} else {
|
|
3217
|
+
this.activeEnforcer = undefined;
|
|
3218
|
+
}
|
|
3219
|
+
} catch (err) {
|
|
3220
|
+
if (this.graphTimeoutTimer !== undefined) {
|
|
3221
|
+
clearTimeout(this.graphTimeoutTimer);
|
|
3222
|
+
this.graphTimeoutTimer = undefined;
|
|
3223
|
+
}
|
|
3224
|
+
this.abortController = undefined;
|
|
3225
|
+
this.activeEnforcer = undefined;
|
|
3226
|
+
this.running = false;
|
|
3227
|
+
throw err;
|
|
3228
|
+
}
|
|
2814
3229
|
const telemetry = getTelemetryProvider2();
|
|
2815
3230
|
if (telemetry.isEnabled) {
|
|
2816
3231
|
this.telemetrySpan = telemetry.startSpan("workglow.graph.run", {
|
|
@@ -2848,6 +3263,7 @@ class TaskGraphRunner {
|
|
|
2848
3263
|
async handleComplete() {
|
|
2849
3264
|
this.clearGraphTimeout();
|
|
2850
3265
|
this.running = false;
|
|
3266
|
+
this.activeEnforcer = undefined;
|
|
2851
3267
|
if (this.telemetrySpan) {
|
|
2852
3268
|
this.telemetrySpan.setStatus(SpanStatusCode2.OK);
|
|
2853
3269
|
this.telemetrySpan.end();
|
|
@@ -2866,6 +3282,7 @@ class TaskGraphRunner {
|
|
|
2866
3282
|
}
|
|
2867
3283
|
}));
|
|
2868
3284
|
this.running = false;
|
|
3285
|
+
this.activeEnforcer = undefined;
|
|
2869
3286
|
if (this.telemetrySpan) {
|
|
2870
3287
|
this.telemetrySpan.setStatus(SpanStatusCode2.ERROR, error.message);
|
|
2871
3288
|
this.telemetrySpan.setAttributes({ "workglow.graph.error": error.message });
|
|
@@ -2885,6 +3302,7 @@ class TaskGraphRunner {
|
|
|
2885
3302
|
}
|
|
2886
3303
|
}));
|
|
2887
3304
|
this.running = false;
|
|
3305
|
+
this.activeEnforcer = undefined;
|
|
2888
3306
|
if (this.telemetrySpan) {
|
|
2889
3307
|
this.telemetrySpan.setStatus(SpanStatusCode2.ERROR, "aborted");
|
|
2890
3308
|
this.telemetrySpan.addEvent("workglow.graph.aborted");
|
|
@@ -2986,9 +3404,10 @@ class GraphAsTask extends Task {
|
|
|
2986
3404
|
static category = "Flow Control";
|
|
2987
3405
|
static compoundMerge = PROPERTY_ARRAY;
|
|
2988
3406
|
static hasDynamicSchemas = true;
|
|
2989
|
-
|
|
3407
|
+
static hasDynamicEntitlements = true;
|
|
3408
|
+
constructor(config = {}) {
|
|
2990
3409
|
const { subGraph, ...rest } = config;
|
|
2991
|
-
super(
|
|
3410
|
+
super(rest);
|
|
2992
3411
|
if (subGraph) {
|
|
2993
3412
|
this.subGraph = subGraph;
|
|
2994
3413
|
}
|
|
@@ -3035,6 +3454,12 @@ class GraphAsTask extends Task {
|
|
|
3035
3454
|
}
|
|
3036
3455
|
return computeGraphOutputSchema(this.subGraph);
|
|
3037
3456
|
}
|
|
3457
|
+
entitlements() {
|
|
3458
|
+
if (!this.hasChildren()) {
|
|
3459
|
+
return this.constructor.entitlements();
|
|
3460
|
+
}
|
|
3461
|
+
return computeGraphEntitlements(this.subGraph);
|
|
3462
|
+
}
|
|
3038
3463
|
resetInputData() {
|
|
3039
3464
|
super.resetInputData();
|
|
3040
3465
|
if (this.hasChildren()) {
|
|
@@ -3073,26 +3498,44 @@ class GraphAsTask extends Task {
|
|
|
3073
3498
|
}
|
|
3074
3499
|
}
|
|
3075
3500
|
const eventQueue = [];
|
|
3076
|
-
let resolveWaiting;
|
|
3077
3501
|
let subgraphDone = false;
|
|
3502
|
+
let { promise: notifyPromise, resolve: notifyResolve } = Promise.withResolvers();
|
|
3503
|
+
let isWaiting = false;
|
|
3504
|
+
let hasPending = false;
|
|
3505
|
+
const notify = () => {
|
|
3506
|
+
if (isWaiting) {
|
|
3507
|
+
notifyResolve();
|
|
3508
|
+
({ promise: notifyPromise, resolve: notifyResolve } = Promise.withResolvers());
|
|
3509
|
+
isWaiting = false;
|
|
3510
|
+
} else {
|
|
3511
|
+
hasPending = true;
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3078
3514
|
const unsub = this.subGraph.subscribeToTaskStreaming({
|
|
3079
3515
|
onStreamChunk: (taskId, event) => {
|
|
3080
3516
|
if (endingNodeIds.has(taskId) && event.type !== "finish") {
|
|
3081
3517
|
eventQueue.push(event);
|
|
3082
|
-
|
|
3518
|
+
notify();
|
|
3083
3519
|
}
|
|
3084
3520
|
}
|
|
3085
3521
|
});
|
|
3086
3522
|
const runPromise = this.subGraph.run(input, { parentSignal: context.signal, accumulateLeafOutputs: false }).then((results2) => {
|
|
3087
3523
|
subgraphDone = true;
|
|
3088
|
-
|
|
3524
|
+
notify();
|
|
3089
3525
|
return results2;
|
|
3526
|
+
}, (err) => {
|
|
3527
|
+
subgraphDone = true;
|
|
3528
|
+
notify();
|
|
3529
|
+
throw err;
|
|
3090
3530
|
});
|
|
3091
3531
|
while (!subgraphDone) {
|
|
3092
3532
|
if (eventQueue.length === 0) {
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
}
|
|
3533
|
+
if (hasPending) {
|
|
3534
|
+
hasPending = false;
|
|
3535
|
+
} else {
|
|
3536
|
+
isWaiting = true;
|
|
3537
|
+
await notifyPromise;
|
|
3538
|
+
}
|
|
3096
3539
|
}
|
|
3097
3540
|
while (eventQueue.length > 0) {
|
|
3098
3541
|
yield eventQueue.shift();
|
|
@@ -3112,6 +3555,7 @@ class GraphAsTask extends Task {
|
|
|
3112
3555
|
regenerateGraph() {
|
|
3113
3556
|
this._inputSchemaNode = undefined;
|
|
3114
3557
|
this.events.emit("regenerate");
|
|
3558
|
+
this.emitEntitlementChange();
|
|
3115
3559
|
}
|
|
3116
3560
|
toJSON(options) {
|
|
3117
3561
|
let json = super.toJSON(options);
|
|
@@ -3146,8 +3590,8 @@ function getWrapperClasses() {
|
|
|
3146
3590
|
if (!_OwnGraphTask) {
|
|
3147
3591
|
|
|
3148
3592
|
class ListeningGraphAsTask extends GraphAsTask {
|
|
3149
|
-
constructor(
|
|
3150
|
-
super(
|
|
3593
|
+
constructor(config) {
|
|
3594
|
+
super(config);
|
|
3151
3595
|
this.subGraph.on("start", () => {
|
|
3152
3596
|
this.emit("start");
|
|
3153
3597
|
});
|
|
@@ -3208,7 +3652,7 @@ function convertPipeFunctionToTask(fn, config) {
|
|
|
3208
3652
|
return fn(input, context);
|
|
3209
3653
|
}
|
|
3210
3654
|
}
|
|
3211
|
-
return new QuickTask(
|
|
3655
|
+
return new QuickTask(config);
|
|
3212
3656
|
}
|
|
3213
3657
|
function isWorkflowLike(arg) {
|
|
3214
3658
|
return arg != null && typeof arg === "object" && "graph" in arg && arg.graph instanceof TaskGraph && "run" in arg && typeof arg.run === "function";
|
|
@@ -3221,18 +3665,18 @@ function ensureTask(arg, config = {}) {
|
|
|
3221
3665
|
getWrapperClasses();
|
|
3222
3666
|
const { isOwned, ...cleanConfig } = config;
|
|
3223
3667
|
if (isOwned) {
|
|
3224
|
-
return new _OwnGraphTask({
|
|
3668
|
+
return new _OwnGraphTask({ ...cleanConfig, subGraph: arg });
|
|
3225
3669
|
} else {
|
|
3226
|
-
return new _GraphTask({
|
|
3670
|
+
return new _GraphTask({ ...cleanConfig, subGraph: arg });
|
|
3227
3671
|
}
|
|
3228
3672
|
}
|
|
3229
3673
|
if (isWorkflowLike(arg)) {
|
|
3230
3674
|
getWrapperClasses();
|
|
3231
3675
|
const { isOwned, ...cleanConfig } = config;
|
|
3232
3676
|
if (isOwned) {
|
|
3233
|
-
return new _OwnWorkflowTask({
|
|
3677
|
+
return new _OwnWorkflowTask({ ...cleanConfig, subGraph: arg.graph });
|
|
3234
3678
|
} else {
|
|
3235
|
-
return new _ConvWorkflowTask({
|
|
3679
|
+
return new _ConvWorkflowTask({ ...cleanConfig, subGraph: arg.graph });
|
|
3236
3680
|
}
|
|
3237
3681
|
}
|
|
3238
3682
|
return convertPipeFunctionToTask(arg, config);
|
|
@@ -3500,6 +3944,43 @@ class TaskGraph {
|
|
|
3500
3944
|
unsubscribes.forEach((unsub) => unsub());
|
|
3501
3945
|
};
|
|
3502
3946
|
}
|
|
3947
|
+
subscribeToTaskEntitlements(callback) {
|
|
3948
|
+
const globalUnsubs = [];
|
|
3949
|
+
const taskUnsubs = new Map;
|
|
3950
|
+
const emitChange = () => {
|
|
3951
|
+
const entitlements = computeGraphEntitlements(this);
|
|
3952
|
+
this.emit("entitlementChange", entitlements);
|
|
3953
|
+
callback(entitlements);
|
|
3954
|
+
};
|
|
3955
|
+
const subscribeTask = (taskId) => {
|
|
3956
|
+
const task = this.getTask(taskId);
|
|
3957
|
+
if (!task || typeof task.subscribe !== "function")
|
|
3958
|
+
return;
|
|
3959
|
+
const unsub = task.subscribe("entitlementChange", () => emitChange());
|
|
3960
|
+
taskUnsubs.set(taskId, unsub);
|
|
3961
|
+
};
|
|
3962
|
+
for (const task of this.getTasks()) {
|
|
3963
|
+
subscribeTask(task.id);
|
|
3964
|
+
}
|
|
3965
|
+
emitChange();
|
|
3966
|
+
globalUnsubs.push(this.subscribe("task_added", (taskId) => {
|
|
3967
|
+
subscribeTask(taskId);
|
|
3968
|
+
emitChange();
|
|
3969
|
+
}));
|
|
3970
|
+
globalUnsubs.push(this.subscribe("task_removed", (taskId) => {
|
|
3971
|
+
const unsub = taskUnsubs.get(taskId);
|
|
3972
|
+
if (unsub) {
|
|
3973
|
+
unsub();
|
|
3974
|
+
taskUnsubs.delete(taskId);
|
|
3975
|
+
}
|
|
3976
|
+
emitChange();
|
|
3977
|
+
}));
|
|
3978
|
+
return () => {
|
|
3979
|
+
globalUnsubs.forEach((unsub) => unsub());
|
|
3980
|
+
taskUnsubs.forEach((unsub) => unsub());
|
|
3981
|
+
taskUnsubs.clear();
|
|
3982
|
+
};
|
|
3983
|
+
}
|
|
3503
3984
|
on(name, fn) {
|
|
3504
3985
|
const dagEvent = EventTaskGraphToDagMapping[name];
|
|
3505
3986
|
if (dagEvent) {
|
|
@@ -3544,11 +4025,7 @@ function serialGraph(tasks, inputHandle, outputHandle) {
|
|
|
3544
4025
|
return graph;
|
|
3545
4026
|
}
|
|
3546
4027
|
// src/task-graph/Workflow.ts
|
|
3547
|
-
import {
|
|
3548
|
-
EventEmitter as EventEmitter5,
|
|
3549
|
-
getLogger as getLogger5,
|
|
3550
|
-
uuid4 as uuid45
|
|
3551
|
-
} from "@workglow/util";
|
|
4028
|
+
import { EventEmitter as EventEmitter5, getLogger as getLogger5, uuid4 as uuid45 } from "@workglow/util";
|
|
3552
4029
|
function getLastTask(workflow) {
|
|
3553
4030
|
const tasks = workflow.graph.getTasks();
|
|
3554
4031
|
return tasks.length > 0 ? tasks[tasks.length - 1] : undefined;
|
|
@@ -3571,7 +4048,6 @@ function pipe(args, workflow = new Workflow) {
|
|
|
3571
4048
|
function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
|
|
3572
4049
|
let previousTask = getLastTask(workflow);
|
|
3573
4050
|
const tasks = args.map((arg) => ensureTask(arg));
|
|
3574
|
-
const input = {};
|
|
3575
4051
|
const config = {
|
|
3576
4052
|
compoundMerge: mergeFn
|
|
3577
4053
|
};
|
|
@@ -3580,7 +4056,7 @@ function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
|
|
|
3580
4056
|
class ParallelTask extends GraphAsTask {
|
|
3581
4057
|
static type = name;
|
|
3582
4058
|
}
|
|
3583
|
-
const mergeTask = new ParallelTask(
|
|
4059
|
+
const mergeTask = new ParallelTask(config);
|
|
3584
4060
|
mergeTask.subGraph.addTasks(tasks);
|
|
3585
4061
|
workflow.graph.addTask(mergeTask);
|
|
3586
4062
|
if (previousTask) {
|
|
@@ -3675,6 +4151,7 @@ class Workflow {
|
|
|
3675
4151
|
_error = "";
|
|
3676
4152
|
_outputCache;
|
|
3677
4153
|
_registry;
|
|
4154
|
+
_entitlementUnsub;
|
|
3678
4155
|
_abortController;
|
|
3679
4156
|
_parentWorkflow;
|
|
3680
4157
|
_iteratorTask;
|
|
@@ -3690,7 +4167,11 @@ class Workflow {
|
|
|
3690
4167
|
const helper = function(input = {}, config = {}) {
|
|
3691
4168
|
this._error = "";
|
|
3692
4169
|
const parent = getLastTask(this);
|
|
3693
|
-
const task = this.addTaskToGraph(taskClass,
|
|
4170
|
+
const task = this.addTaskToGraph(taskClass, {
|
|
4171
|
+
id: uuid45(),
|
|
4172
|
+
...config,
|
|
4173
|
+
defaults: input
|
|
4174
|
+
});
|
|
3694
4175
|
if (this._dataFlows.length > 0) {
|
|
3695
4176
|
this._dataFlows.forEach((dataflow) => {
|
|
3696
4177
|
const taskSchema = task.inputSchema();
|
|
@@ -3825,6 +4306,9 @@ class Workflow {
|
|
|
3825
4306
|
toDependencyJSON(options = { withBoundaryNodes: true }) {
|
|
3826
4307
|
return this._graph.toDependencyJSON(options);
|
|
3827
4308
|
}
|
|
4309
|
+
entitlements(options) {
|
|
4310
|
+
return computeGraphEntitlements(this._graph, options);
|
|
4311
|
+
}
|
|
3828
4312
|
pipe(...args) {
|
|
3829
4313
|
return pipe(args, this);
|
|
3830
4314
|
}
|
|
@@ -3909,6 +4393,7 @@ class Workflow {
|
|
|
3909
4393
|
this._graph.on("dataflow_added", this._onChanged);
|
|
3910
4394
|
this._graph.on("dataflow_replaced", this._onChanged);
|
|
3911
4395
|
this._graph.on("dataflow_removed", this._onChanged);
|
|
4396
|
+
this._entitlementUnsub = this._graph.subscribeToTaskEntitlements((entitlements) => this.events.emit("entitlementChange", entitlements));
|
|
3912
4397
|
}
|
|
3913
4398
|
clearEvents() {
|
|
3914
4399
|
this._graph.off("task_added", this._onChanged);
|
|
@@ -3917,6 +4402,8 @@ class Workflow {
|
|
|
3917
4402
|
this._graph.off("dataflow_added", this._onChanged);
|
|
3918
4403
|
this._graph.off("dataflow_replaced", this._onChanged);
|
|
3919
4404
|
this._graph.off("dataflow_removed", this._onChanged);
|
|
4405
|
+
this._entitlementUnsub?.();
|
|
4406
|
+
this._entitlementUnsub = undefined;
|
|
3920
4407
|
}
|
|
3921
4408
|
_onChanged(id) {
|
|
3922
4409
|
this.events.emit("changed", id);
|
|
@@ -3948,8 +4435,8 @@ class Workflow {
|
|
|
3948
4435
|
this.graph.addDataflow(dataflow);
|
|
3949
4436
|
return this;
|
|
3950
4437
|
}
|
|
3951
|
-
addTaskToGraph(taskClass,
|
|
3952
|
-
const task = new taskClass(
|
|
4438
|
+
addTaskToGraph(taskClass, config) {
|
|
4439
|
+
const task = new taskClass(config, this._registry ? { registry: this._registry } : undefined);
|
|
3953
4440
|
const id = this.graph.addTask(task);
|
|
3954
4441
|
this.events.emit("changed", id);
|
|
3955
4442
|
return task;
|
|
@@ -3961,7 +4448,7 @@ class Workflow {
|
|
|
3961
4448
|
addLoopTask(taskClass, config = {}) {
|
|
3962
4449
|
this._error = "";
|
|
3963
4450
|
const parent = getLastTask(this);
|
|
3964
|
-
const task = this.addTaskToGraph(taskClass, {
|
|
4451
|
+
const task = this.addTaskToGraph(taskClass, { id: uuid45(), ...config });
|
|
3965
4452
|
if (this._dataFlows.length > 0) {
|
|
3966
4453
|
this._dataFlows.forEach((dataflow) => {
|
|
3967
4454
|
const taskSchema = task.inputSchema();
|
|
@@ -4058,10 +4545,29 @@ class Workflow {
|
|
|
4058
4545
|
const sourceSchema = sourceTask.outputSchema();
|
|
4059
4546
|
if (typeof sourceSchema === "boolean")
|
|
4060
4547
|
continue;
|
|
4061
|
-
|
|
4548
|
+
let prop = sourceSchema.properties?.[df.sourceTaskPortId];
|
|
4549
|
+
let propRequired = sourceSchema.required?.includes(df.sourceTaskPortId) ?? false;
|
|
4550
|
+
if (!prop && sourceSchema.additionalProperties === true && sourceTask.constructor.passthroughInputsToOutputs === true) {
|
|
4551
|
+
const upstreamDfs = graph.getSourceDataflows(sourceTask.id);
|
|
4552
|
+
for (const udf of upstreamDfs) {
|
|
4553
|
+
if (udf.targetTaskPortId !== df.sourceTaskPortId)
|
|
4554
|
+
continue;
|
|
4555
|
+
const upstreamTask = graph.getTask(udf.sourceTaskId);
|
|
4556
|
+
if (!upstreamTask)
|
|
4557
|
+
continue;
|
|
4558
|
+
const upstreamSchema = upstreamTask.outputSchema();
|
|
4559
|
+
if (typeof upstreamSchema === "boolean")
|
|
4560
|
+
continue;
|
|
4561
|
+
prop = upstreamSchema.properties?.[udf.sourceTaskPortId];
|
|
4562
|
+
if (prop) {
|
|
4563
|
+
propRequired = upstreamSchema.required?.includes(udf.sourceTaskPortId) ?? false;
|
|
4564
|
+
break;
|
|
4565
|
+
}
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4062
4568
|
if (prop && typeof prop !== "boolean") {
|
|
4063
4569
|
properties[df.targetTaskPortId] = prop;
|
|
4064
|
-
if (
|
|
4570
|
+
if (propRequired && !required.includes(df.targetTaskPortId)) {
|
|
4065
4571
|
required.push(df.targetTaskPortId);
|
|
4066
4572
|
}
|
|
4067
4573
|
}
|
|
@@ -4161,11 +4667,30 @@ class Workflow {
|
|
|
4161
4667
|
const makeMatch = (fromSchema, toSchema, fromTaskId, toTaskId, comparator) => {
|
|
4162
4668
|
if (typeof fromSchema === "object") {
|
|
4163
4669
|
if (toSchema === true || typeof toSchema === "object" && toSchema.additionalProperties === true) {
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4670
|
+
const outputKeys = Object.keys(fromSchema.properties || {});
|
|
4671
|
+
if (outputKeys.length > 0) {
|
|
4672
|
+
for (const fromOutputPortId of outputKeys) {
|
|
4673
|
+
if (matches.has(fromOutputPortId))
|
|
4674
|
+
continue;
|
|
4675
|
+
matches.set(fromOutputPortId, fromOutputPortId);
|
|
4676
|
+
graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, fromOutputPortId));
|
|
4677
|
+
}
|
|
4678
|
+
} else if (fromSchema.additionalProperties === true) {
|
|
4679
|
+
const sourceGraphTask = graph.getTask(fromTaskId);
|
|
4680
|
+
if (sourceGraphTask && sourceGraphTask.constructor.passthroughInputsToOutputs === true) {
|
|
4681
|
+
const incomingDfs = graph.getSourceDataflows(fromTaskId);
|
|
4682
|
+
for (const df of incomingDfs) {
|
|
4683
|
+
const portId = df.targetTaskPortId;
|
|
4684
|
+
if (portId === DATAFLOW_ALL_PORTS)
|
|
4685
|
+
continue;
|
|
4686
|
+
if (matches.has(portId))
|
|
4687
|
+
continue;
|
|
4688
|
+
if (connectedInputKeys.has(portId))
|
|
4689
|
+
continue;
|
|
4690
|
+
matches.set(portId, portId);
|
|
4691
|
+
graph.addDataflow(new Dataflow(fromTaskId, portId, toTaskId, portId));
|
|
4692
|
+
}
|
|
4693
|
+
}
|
|
4169
4694
|
}
|
|
4170
4695
|
return;
|
|
4171
4696
|
}
|
|
@@ -4648,6 +5173,37 @@ ${baseIndent}}`;
|
|
|
4648
5173
|
function resetMethodNameCache() {
|
|
4649
5174
|
methodNameCache = undefined;
|
|
4650
5175
|
}
|
|
5176
|
+
// src/task/EntitlementProfiles.ts
|
|
5177
|
+
var BROWSER_GRANTS = [
|
|
5178
|
+
{ id: Entitlements.NETWORK },
|
|
5179
|
+
{ id: Entitlements.AI },
|
|
5180
|
+
{ id: Entitlements.MCP_TOOL_CALL },
|
|
5181
|
+
{ id: Entitlements.MCP_RESOURCE_READ },
|
|
5182
|
+
{ id: Entitlements.MCP_PROMPT_GET },
|
|
5183
|
+
{ id: Entitlements.STORAGE },
|
|
5184
|
+
{ id: Entitlements.CREDENTIAL }
|
|
5185
|
+
];
|
|
5186
|
+
var DESKTOP_GRANTS = [
|
|
5187
|
+
...BROWSER_GRANTS,
|
|
5188
|
+
{ id: Entitlements.FILESYSTEM },
|
|
5189
|
+
{ id: Entitlements.CODE_EXECUTION },
|
|
5190
|
+
{ id: Entitlements.MCP_STDIO }
|
|
5191
|
+
];
|
|
5192
|
+
var SERVER_GRANTS = [...DESKTOP_GRANTS];
|
|
5193
|
+
var PROFILE_GRANTS = {
|
|
5194
|
+
browser: BROWSER_GRANTS,
|
|
5195
|
+
desktop: DESKTOP_GRANTS,
|
|
5196
|
+
server: SERVER_GRANTS
|
|
5197
|
+
};
|
|
5198
|
+
function createProfilePolicy(profile) {
|
|
5199
|
+
return { deny: [], grant: PROFILE_GRANTS[profile], ask: [] };
|
|
5200
|
+
}
|
|
5201
|
+
function createProfileEnforcer(profile, resolver) {
|
|
5202
|
+
return createPolicyEnforcer(createProfilePolicy(profile), resolver);
|
|
5203
|
+
}
|
|
5204
|
+
function getProfileGrants(profile) {
|
|
5205
|
+
return PROFILE_GRANTS[profile];
|
|
5206
|
+
}
|
|
4651
5207
|
// src/task/FallbackTaskRunner.ts
|
|
4652
5208
|
class FallbackTaskRunner extends GraphAsTaskRunner {
|
|
4653
5209
|
async executeTask(input) {
|
|
@@ -4849,22 +5405,32 @@ queueMicrotask(() => {
|
|
|
4849
5405
|
});
|
|
4850
5406
|
// src/task/InputCompactor.ts
|
|
4851
5407
|
import { getInputCompactors } from "@workglow/util";
|
|
4852
|
-
function schemaAllowsString(schema) {
|
|
5408
|
+
function schemaAllowsString(schema, visited = new WeakSet) {
|
|
4853
5409
|
if (typeof schema !== "object" || schema === null)
|
|
4854
5410
|
return false;
|
|
5411
|
+
if (visited.has(schema))
|
|
5412
|
+
return false;
|
|
5413
|
+
visited.add(schema);
|
|
4855
5414
|
const s = schema;
|
|
4856
5415
|
if (s.type === "string")
|
|
4857
5416
|
return true;
|
|
4858
5417
|
const variants = s.oneOf ?? s.anyOf;
|
|
4859
5418
|
if (Array.isArray(variants)) {
|
|
4860
5419
|
for (const variant of variants) {
|
|
4861
|
-
if (schemaAllowsString(variant))
|
|
5420
|
+
if (schemaAllowsString(variant, visited))
|
|
5421
|
+
return true;
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const allOf = s.allOf;
|
|
5425
|
+
if (Array.isArray(allOf)) {
|
|
5426
|
+
for (const sub of allOf) {
|
|
5427
|
+
if (schemaAllowsString(sub, visited))
|
|
4862
5428
|
return true;
|
|
4863
5429
|
}
|
|
4864
5430
|
}
|
|
4865
5431
|
return false;
|
|
4866
5432
|
}
|
|
4867
|
-
async function compactSchemaInputs(input, schema, config) {
|
|
5433
|
+
async function compactSchemaInputs(input, schema, config, visited = new Set) {
|
|
4868
5434
|
if (typeof schema === "boolean")
|
|
4869
5435
|
return input;
|
|
4870
5436
|
const properties = schema.properties;
|
|
@@ -4902,8 +5468,13 @@ async function compactSchemaInputs(input, schema, config) {
|
|
|
4902
5468
|
}
|
|
4903
5469
|
if (value !== null && value !== undefined && typeof value === "object" && !Array.isArray(value)) {
|
|
4904
5470
|
const objectSchema = getObjectSchema(propSchema);
|
|
4905
|
-
if (objectSchema) {
|
|
4906
|
-
|
|
5471
|
+
if (objectSchema && !visited.has(objectSchema)) {
|
|
5472
|
+
visited.add(objectSchema);
|
|
5473
|
+
try {
|
|
5474
|
+
compacted[key] = await compactSchemaInputs(value, objectSchema, config, visited);
|
|
5475
|
+
} finally {
|
|
5476
|
+
visited.delete(objectSchema);
|
|
5477
|
+
}
|
|
4907
5478
|
}
|
|
4908
5479
|
}
|
|
4909
5480
|
}
|
|
@@ -5027,7 +5598,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
|
5027
5598
|
const newId = uuid46();
|
|
5028
5599
|
idMap.set(task.config.id, newId);
|
|
5029
5600
|
const clonedConfig = { ...task.config, id: newId };
|
|
5030
|
-
const newTask = new ctor(task.defaults
|
|
5601
|
+
const newTask = new ctor({ ...clonedConfig, defaults: task.defaults }, task.runConfig);
|
|
5031
5602
|
if (task.hasChildren()) {
|
|
5032
5603
|
newTask.subGraph = this.cloneGraph(task.subGraph);
|
|
5033
5604
|
}
|
|
@@ -5227,9 +5798,6 @@ class IteratorTask extends GraphAsTask {
|
|
|
5227
5798
|
}
|
|
5228
5799
|
_iteratorPortInfo;
|
|
5229
5800
|
_iterationInputSchema;
|
|
5230
|
-
constructor(input = {}, config = {}) {
|
|
5231
|
-
super(input, config);
|
|
5232
|
-
}
|
|
5233
5801
|
get runner() {
|
|
5234
5802
|
if (!this._runner) {
|
|
5235
5803
|
this._runner = new IteratorTaskRunner(this);
|
|
@@ -5640,9 +6208,6 @@ class WhileTask extends GraphAsTask {
|
|
|
5640
6208
|
canSerializeConfig() {
|
|
5641
6209
|
return typeof this.config.condition !== "function";
|
|
5642
6210
|
}
|
|
5643
|
-
constructor(input = {}, config = {}) {
|
|
5644
|
-
super(input, config);
|
|
5645
|
-
}
|
|
5646
6211
|
get runner() {
|
|
5647
6212
|
if (!this._runner) {
|
|
5648
6213
|
this._runner = new WhileTaskRunner(this);
|
|
@@ -6164,8 +6729,8 @@ import {
|
|
|
6164
6729
|
JobQueueServer
|
|
6165
6730
|
} from "@workglow/job-queue";
|
|
6166
6731
|
import { InMemoryQueueStorage } from "@workglow/storage";
|
|
6167
|
-
import { createServiceToken as
|
|
6168
|
-
var JOB_QUEUE_FACTORY =
|
|
6732
|
+
import { createServiceToken as createServiceToken4, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
|
|
6733
|
+
var JOB_QUEUE_FACTORY = createServiceToken4("taskgraph.jobQueueFactory");
|
|
6169
6734
|
var defaultJobQueueFactory = async ({
|
|
6170
6735
|
queueName,
|
|
6171
6736
|
jobClass,
|
|
@@ -6330,13 +6895,13 @@ class ReduceTask extends IteratorTask {
|
|
|
6330
6895
|
static configSchema() {
|
|
6331
6896
|
return reduceTaskConfigSchema;
|
|
6332
6897
|
}
|
|
6333
|
-
constructor(
|
|
6898
|
+
constructor(config = {}) {
|
|
6334
6899
|
const reduceConfig = {
|
|
6335
6900
|
...config,
|
|
6336
6901
|
concurrencyLimit: 1,
|
|
6337
6902
|
batchSize: 1
|
|
6338
6903
|
};
|
|
6339
|
-
super(
|
|
6904
|
+
super(reduceConfig);
|
|
6340
6905
|
}
|
|
6341
6906
|
get initialValue() {
|
|
6342
6907
|
return this.config.initialValue ?? {};
|
|
@@ -6408,24 +6973,48 @@ queueMicrotask(() => {
|
|
|
6408
6973
|
});
|
|
6409
6974
|
// src/task/TaskRegistry.ts
|
|
6410
6975
|
import {
|
|
6411
|
-
createServiceToken as
|
|
6976
|
+
createServiceToken as createServiceToken5,
|
|
6977
|
+
getLogger as getLogger6,
|
|
6412
6978
|
globalServiceRegistry as globalServiceRegistry4,
|
|
6413
6979
|
registerInputCompactor,
|
|
6414
6980
|
registerInputResolver
|
|
6415
6981
|
} from "@workglow/util";
|
|
6982
|
+
import { validateSchema } from "@workglow/util/schema";
|
|
6416
6983
|
var taskConstructors = new Map;
|
|
6417
6984
|
function registerTask(baseClass) {
|
|
6418
|
-
|
|
6985
|
+
const existing = taskConstructors.get(baseClass.type);
|
|
6986
|
+
if (existing) {
|
|
6987
|
+
if (existing === baseClass)
|
|
6988
|
+
return;
|
|
6989
|
+
throw new Error(`Task type "${baseClass.type}" is already registered. Unregister it first to replace.`);
|
|
6990
|
+
}
|
|
6419
6991
|
taskConstructors.set(baseClass.type, baseClass);
|
|
6992
|
+
const schemas = [
|
|
6993
|
+
{ name: "inputSchema", schema: baseClass.inputSchema() },
|
|
6994
|
+
{ name: "outputSchema", schema: baseClass.outputSchema() }
|
|
6995
|
+
];
|
|
6996
|
+
for (const { name, schema } of schemas) {
|
|
6997
|
+
const result = validateSchema(schema);
|
|
6998
|
+
if (!result.valid) {
|
|
6999
|
+
const messages = result.errors.map((e) => `${e.path}: ${e.message}`).join("; ");
|
|
7000
|
+
getLogger6().warn(`Task "${baseClass.type}" has invalid ${name}: ${messages}`, {
|
|
7001
|
+
taskType: baseClass.type,
|
|
7002
|
+
schemaName: name,
|
|
7003
|
+
errors: result.errors
|
|
7004
|
+
});
|
|
7005
|
+
}
|
|
7006
|
+
}
|
|
7007
|
+
}
|
|
7008
|
+
function unregisterTask(type) {
|
|
7009
|
+
return taskConstructors.delete(type);
|
|
6420
7010
|
}
|
|
6421
7011
|
var TaskRegistry = {
|
|
6422
7012
|
all: taskConstructors,
|
|
6423
|
-
registerTask
|
|
7013
|
+
registerTask,
|
|
7014
|
+
unregisterTask
|
|
6424
7015
|
};
|
|
6425
|
-
var TASK_CONSTRUCTORS =
|
|
6426
|
-
|
|
6427
|
-
globalServiceRegistry4.register(TASK_CONSTRUCTORS, () => TaskRegistry.all, true);
|
|
6428
|
-
}
|
|
7016
|
+
var TASK_CONSTRUCTORS = createServiceToken5("task.constructors");
|
|
7017
|
+
globalServiceRegistry4.registerIfAbsent(TASK_CONSTRUCTORS, () => TaskRegistry.all, true);
|
|
6429
7018
|
function getGlobalTaskConstructors() {
|
|
6430
7019
|
return globalServiceRegistry4.get(TASK_CONSTRUCTORS);
|
|
6431
7020
|
}
|
|
@@ -6483,11 +7072,15 @@ var createSingleTaskFromJSON = (item, registry, options) => {
|
|
|
6483
7072
|
const taskClass = constructors.get(item.type);
|
|
6484
7073
|
if (!taskClass)
|
|
6485
7074
|
throw new TaskJSONError(`Task type ${item.type} not found, perhaps not registered?`);
|
|
7075
|
+
if (typeof taskClass !== "function" || typeof taskClass.type !== "string") {
|
|
7076
|
+
throw new TaskJSONError(`Task type ${item.type} resolved to an invalid constructor`);
|
|
7077
|
+
}
|
|
6486
7078
|
const taskConfig = {
|
|
6487
7079
|
...item.config,
|
|
6488
|
-
id: item.id
|
|
7080
|
+
id: item.id,
|
|
7081
|
+
defaults: item.defaults ?? {}
|
|
6489
7082
|
};
|
|
6490
|
-
const task = new taskClass(
|
|
7083
|
+
const task = new taskClass(taskConfig, registry ? { registry } : {});
|
|
6491
7084
|
return task;
|
|
6492
7085
|
};
|
|
6493
7086
|
var createTaskFromDependencyJSON = (item, registry, options) => {
|
|
@@ -6581,8 +7174,8 @@ var registerBaseTasks = () => {
|
|
|
6581
7174
|
return tasks;
|
|
6582
7175
|
};
|
|
6583
7176
|
// src/storage/TaskGraphRepository.ts
|
|
6584
|
-
import { createServiceToken as
|
|
6585
|
-
var TASK_GRAPH_REPOSITORY =
|
|
7177
|
+
import { createServiceToken as createServiceToken6, EventEmitter as EventEmitter7 } from "@workglow/util";
|
|
7178
|
+
var TASK_GRAPH_REPOSITORY = createServiceToken6("taskgraph.taskGraphRepository");
|
|
6586
7179
|
|
|
6587
7180
|
class TaskGraphRepository {
|
|
6588
7181
|
type = "TaskGraphRepository";
|
|
@@ -6748,6 +7341,9 @@ export {
|
|
|
6748
7341
|
serialGraph,
|
|
6749
7342
|
schemaHasFormatAnnotations,
|
|
6750
7343
|
schemaAcceptsArray,
|
|
7344
|
+
scanGraphForFormat,
|
|
7345
|
+
scanGraphForCredentials,
|
|
7346
|
+
resourcePatternMatches,
|
|
6751
7347
|
resolveSchemaInputs,
|
|
6752
7348
|
resetMethodNameCache,
|
|
6753
7349
|
removeIterationProperties,
|
|
@@ -6756,6 +7352,9 @@ export {
|
|
|
6756
7352
|
reduceTaskConfigSchema,
|
|
6757
7353
|
pipe,
|
|
6758
7354
|
parallel,
|
|
7355
|
+
mergeResources,
|
|
7356
|
+
mergeEntitlements,
|
|
7357
|
+
mergeEntitlementPair,
|
|
6759
7358
|
mergeChainedOutputToInput,
|
|
6760
7359
|
mapTaskConfigSchema,
|
|
6761
7360
|
iteratorTaskConfigSchema,
|
|
@@ -6768,11 +7367,13 @@ export {
|
|
|
6768
7367
|
hasStructuredOutput,
|
|
6769
7368
|
graphToWorkflowCode,
|
|
6770
7369
|
graphAsTaskConfigSchema,
|
|
7370
|
+
grantCoversResources,
|
|
6771
7371
|
getTaskQueueRegistry,
|
|
6772
7372
|
getTaskConstructors,
|
|
6773
7373
|
getStructuredOutputSchemas,
|
|
6774
7374
|
getStreamingPorts,
|
|
6775
7375
|
getSchemaFormat,
|
|
7376
|
+
getProfileGrants,
|
|
6776
7377
|
getPortStreamMode,
|
|
6777
7378
|
getOutputStreamMode,
|
|
6778
7379
|
getObjectSchema,
|
|
@@ -6791,20 +7392,28 @@ export {
|
|
|
6791
7392
|
fallbackTaskConfigSchema,
|
|
6792
7393
|
extractIterationProperties,
|
|
6793
7394
|
extractBaseSchema,
|
|
7395
|
+
evaluatePolicy,
|
|
6794
7396
|
evaluateCondition,
|
|
7397
|
+
entitlementCovers,
|
|
6795
7398
|
ensureTask,
|
|
6796
7399
|
edgeNeedsAccumulation,
|
|
6797
7400
|
createTaskFromGraphJSON,
|
|
6798
7401
|
createTaskFromDependencyJSON,
|
|
7402
|
+
createScopedEnforcer,
|
|
7403
|
+
createProfilePolicy,
|
|
7404
|
+
createProfileEnforcer,
|
|
7405
|
+
createPolicyEnforcer,
|
|
6799
7406
|
createJobQueueFactoryWithOptions,
|
|
6800
7407
|
createGraphFromGraphJSON,
|
|
6801
7408
|
createGraphFromDependencyJSON,
|
|
7409
|
+
createGrantListEnforcer,
|
|
6802
7410
|
createFlexibleSchema,
|
|
6803
7411
|
createArraySchema,
|
|
6804
7412
|
connect,
|
|
6805
7413
|
conditionalTaskConfigSchema,
|
|
6806
7414
|
computeGraphOutputSchema,
|
|
6807
7415
|
computeGraphInputSchema,
|
|
7416
|
+
computeGraphEntitlements,
|
|
6808
7417
|
compactSchemaInputs,
|
|
6809
7418
|
calculateNodeDepths,
|
|
6810
7419
|
buildIterationInputSchema,
|
|
@@ -6836,6 +7445,7 @@ export {
|
|
|
6836
7445
|
TaskGraph,
|
|
6837
7446
|
TaskFailedError,
|
|
6838
7447
|
TaskError,
|
|
7448
|
+
TaskEntitlementError,
|
|
6839
7449
|
TaskConfigurationError,
|
|
6840
7450
|
TaskConfigSchema,
|
|
6841
7451
|
TaskAbortedError,
|
|
@@ -6843,8 +7453,11 @@ export {
|
|
|
6843
7453
|
TASK_OUTPUT_REPOSITORY,
|
|
6844
7454
|
TASK_GRAPH_REPOSITORY,
|
|
6845
7455
|
TASK_CONSTRUCTORS,
|
|
7456
|
+
SERVER_GRANTS,
|
|
6846
7457
|
ReduceTask,
|
|
6847
7458
|
PROPERTY_ARRAY,
|
|
7459
|
+
PERMISSIVE_RESOLVER,
|
|
7460
|
+
PERMISSIVE_ENFORCER,
|
|
6848
7461
|
MapTask,
|
|
6849
7462
|
JobTaskFailedError,
|
|
6850
7463
|
JOB_QUEUE_FACTORY,
|
|
@@ -6858,15 +7471,23 @@ export {
|
|
|
6858
7471
|
FallbackTask,
|
|
6859
7472
|
EventTaskGraphToDagMapping,
|
|
6860
7473
|
EventDagToTaskGraphMapping,
|
|
7474
|
+
Entitlements,
|
|
7475
|
+
ENTITLEMENT_RESOLVER,
|
|
7476
|
+
ENTITLEMENT_ENFORCER,
|
|
7477
|
+
EMPTY_POLICY,
|
|
7478
|
+
EMPTY_ENTITLEMENTS,
|
|
6861
7479
|
DataflowArrow,
|
|
6862
7480
|
Dataflow,
|
|
7481
|
+
DESKTOP_GRANTS,
|
|
7482
|
+
DENY_ALL_RESOLVER,
|
|
6863
7483
|
DATAFLOW_ERROR_PORT,
|
|
6864
7484
|
DATAFLOW_ALL_PORTS,
|
|
6865
7485
|
CreateWorkflow,
|
|
6866
7486
|
CreateLoopWorkflow,
|
|
6867
7487
|
CreateEndLoopWorkflow,
|
|
6868
7488
|
CreateAdaptiveWorkflow,
|
|
6869
|
-
ConditionalTask
|
|
7489
|
+
ConditionalTask,
|
|
7490
|
+
BROWSER_GRANTS
|
|
6870
7491
|
};
|
|
6871
7492
|
|
|
6872
|
-
//# debugId=
|
|
7493
|
+
//# debugId=022B1E4C55D2D5D864756E2164756E21
|