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