@travisliu/open-dynamic-workflow 0.3.0 → 0.3.5
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/CHANGELOG.md +16 -0
- package/README.md +45 -17
- package/dist/agents/codex-exec.js +3 -1
- package/dist/agents/codex-exec.js.map +1 -1
- package/dist/agents/cursor-agent.d.ts +21 -0
- package/dist/agents/cursor-agent.js +171 -0
- package/dist/agents/cursor-agent.js.map +1 -0
- package/dist/agents/execute-agent.js +1 -1
- package/dist/agents/execute-agent.js.map +1 -1
- package/dist/agents/registry.js +2 -0
- package/dist/agents/registry.js.map +1 -1
- package/dist/artifacts/call-cache.d.ts +27 -3
- package/dist/artifacts/call-cache.js +160 -59
- package/dist/artifacts/call-cache.js.map +1 -1
- package/dist/artifacts/run-store.js.map +1 -1
- package/dist/cli/commands/doctor.js +0 -4
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/run.js +2 -1
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/validate.js +2 -1
- package/dist/cli/commands/validate.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +51 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init/prompts.d.ts +1 -1
- package/dist/cli/init/prompts.js +4 -4
- package/dist/cli/init/prompts.js.map +1 -1
- package/dist/cli/init/renderer.js +2 -1
- package/dist/cli/init/renderer.js.map +1 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/load.js +1 -1
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.js +3 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/types.d.ts +1 -0
- package/dist/discovery/collect-files.js +2 -2
- package/dist/discovery/collect-files.js.map +1 -1
- package/dist/discovery/definition-call.js +1 -1
- package/dist/discovery/definition-call.js.map +1 -1
- package/dist/discovery/file-patterns.js +1 -1
- package/dist/discovery/file-patterns.js.map +1 -1
- package/dist/discovery/schema-summary.js.map +1 -1
- package/dist/loop/artifacts.d.ts +39 -0
- package/dist/loop/artifacts.js +58 -0
- package/dist/loop/artifacts.js.map +1 -0
- package/dist/loop/context.d.ts +55 -0
- package/dist/loop/context.js +134 -0
- package/dist/loop/context.js.map +1 -0
- package/dist/loop/events.d.ts +62 -0
- package/dist/loop/events.js +68 -0
- package/dist/loop/events.js.map +1 -0
- package/dist/loop/id.d.ts +24 -0
- package/dist/loop/id.js +65 -0
- package/dist/loop/id.js.map +1 -0
- package/dist/loop/index.d.ts +8 -0
- package/dist/loop/index.js +9 -0
- package/dist/loop/index.js.map +1 -0
- package/dist/loop/replay.d.ts +47 -0
- package/dist/loop/replay.js +104 -0
- package/dist/loop/replay.js.map +1 -0
- package/dist/loop/results.d.ts +74 -0
- package/dist/loop/results.js +101 -0
- package/dist/loop/results.js.map +1 -0
- package/dist/loop/run.d.ts +22 -0
- package/dist/loop/run.js +671 -0
- package/dist/loop/run.js.map +1 -0
- package/dist/loop/summary.d.ts +5 -0
- package/dist/loop/summary.js +16 -0
- package/dist/loop/summary.js.map +1 -0
- package/dist/loop/types.d.ts +120 -0
- package/dist/loop/types.js +2 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/loop/validate.d.ts +9 -0
- package/dist/loop/validate.js +122 -0
- package/dist/loop/validate.js.map +1 -0
- package/dist/orchestration/scheduler.js +1 -1
- package/dist/orchestration/scheduler.js.map +1 -1
- package/dist/output/events.d.ts +49 -1
- package/dist/output/events.js.map +1 -1
- package/dist/output/failed-artifacts.js +5 -0
- package/dist/output/failed-artifacts.js.map +1 -1
- package/dist/output/json-reporter.d.ts +2 -2
- package/dist/output/json-reporter.js +1 -1
- package/dist/output/json-reporter.js.map +1 -1
- package/dist/output/jsonl-reporter.d.ts +3 -4
- package/dist/output/jsonl-reporter.js +2 -2
- package/dist/output/jsonl-reporter.js.map +1 -1
- package/dist/output/pretty-renderer.js +15 -0
- package/dist/output/pretty-renderer.js.map +1 -1
- package/dist/output/pretty-reporter.js +35 -4
- package/dist/output/pretty-reporter.js.map +1 -1
- package/dist/output/pretty-view-builder.js +70 -1
- package/dist/output/pretty-view-builder.js.map +1 -1
- package/dist/output/pretty-view.d.ts +12 -2
- package/dist/output/verbose-formatter.d.ts +4 -1
- package/dist/output/verbose-formatter.js +67 -0
- package/dist/output/verbose-formatter.js.map +1 -1
- package/dist/pipeline/stage-barrier.js.map +1 -1
- package/dist/pipeline/validate.js +1 -1
- package/dist/pipeline/validate.js.map +1 -1
- package/dist/shared-agents/load.js +0 -2
- package/dist/shared-agents/load.js.map +1 -1
- package/dist/tools/load.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/workflow.d.ts +13 -0
- package/dist/workflow/discovery.d.ts +1 -0
- package/dist/workflow/discovery.js +6 -4
- package/dist/workflow/discovery.js.map +1 -1
- package/dist/workflow/dsl.d.ts +1 -0
- package/dist/workflow/dsl.js +281 -137
- package/dist/workflow/dsl.js.map +1 -1
- package/dist/workflow/errors.js +1 -1
- package/dist/workflow/errors.js.map +1 -1
- package/dist/workflow/invocation-manager.js +14 -2
- package/dist/workflow/invocation-manager.js.map +1 -1
- package/dist/workflow/resolve-target.js +1 -1
- package/dist/workflow/resolve-target.js.map +1 -1
- package/dist/workflow/runtime.js +6 -1
- package/dist/workflow/runtime.js.map +1 -1
- package/dist/workflow/sandbox.js +1 -0
- package/dist/workflow/sandbox.js.map +1 -1
- package/dist/workflow/scope.d.ts +1 -1
- package/dist/workflow/scope.js +2 -15
- package/dist/workflow/scope.js.map +1 -1
- package/dist/workflow/types.d.ts +3 -0
- package/dist/workflow/validate.d.ts +2 -0
- package/dist/workflow/validate.js +369 -46
- package/dist/workflow/validate.js.map +1 -1
- package/package.json +16 -2
|
@@ -74,6 +74,7 @@ function getObjectLiteralProperty(node, name) {
|
|
|
74
74
|
}
|
|
75
75
|
export function validateWorkflow(workflow, options) {
|
|
76
76
|
const issues = [];
|
|
77
|
+
const loopLabelsSeen = new Set();
|
|
77
78
|
const sourceFile = ts.createSourceFile(workflow.sourcePath, workflow.sourceText, ts.ScriptTarget.Latest, true);
|
|
78
79
|
// Find the exported default workflow function to get its context parameter name
|
|
79
80
|
const contextParameterNames = new Set(["ctx", "context"]);
|
|
@@ -139,7 +140,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
139
140
|
report(idArg, "Shared agent ID must be a string literal.");
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
|
-
function validateSharedAgentInput(idArg, inputArg
|
|
143
|
+
function validateSharedAgentInput(idArg, inputArg) {
|
|
143
144
|
if (!options.sharedAgentRegistry || !idArg || !ts.isStringLiteral(idArg)) {
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
@@ -149,7 +150,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
149
150
|
return;
|
|
150
151
|
}
|
|
151
152
|
const schema = entry.definition.inputSchema;
|
|
152
|
-
|
|
153
|
+
const parsedInput = parseStaticProperties(inputArg);
|
|
153
154
|
if (parsedInput === undefined) {
|
|
154
155
|
if (!inputArg) {
|
|
155
156
|
try {
|
|
@@ -162,7 +163,9 @@ export function validateWorkflow(workflow, options) {
|
|
|
162
163
|
}
|
|
163
164
|
}
|
|
164
165
|
}
|
|
165
|
-
catch
|
|
166
|
+
catch {
|
|
167
|
+
// ignore validation errors
|
|
168
|
+
}
|
|
166
169
|
}
|
|
167
170
|
return;
|
|
168
171
|
}
|
|
@@ -194,7 +197,9 @@ export function validateWorkflow(workflow, options) {
|
|
|
194
197
|
}
|
|
195
198
|
}
|
|
196
199
|
}
|
|
197
|
-
catch
|
|
200
|
+
catch {
|
|
201
|
+
// ignore validation errors
|
|
202
|
+
}
|
|
198
203
|
}
|
|
199
204
|
function validateInputAgainstSchema(name, schema, argsExpr) {
|
|
200
205
|
if (argsExpr === undefined) {
|
|
@@ -232,7 +237,9 @@ export function validateWorkflow(workflow, options) {
|
|
|
232
237
|
}
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
|
-
catch
|
|
240
|
+
catch {
|
|
241
|
+
// ignore validation errors
|
|
242
|
+
}
|
|
236
243
|
}
|
|
237
244
|
function validateWorkflowCall(node, isContextForm, contextName = "workflow") {
|
|
238
245
|
const firstArg = node.arguments[0];
|
|
@@ -321,7 +328,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
321
328
|
const firstArg = node.arguments[0];
|
|
322
329
|
const callPrefix = isContextForm ? `${contextName}.tool()` : "tool()";
|
|
323
330
|
if (isForbiddenContext) {
|
|
324
|
-
report(node, `${callPrefix} is not allowed in this context (parallel, pipeline stage, or shared agent).`);
|
|
331
|
+
report(node, `${callPrefix} is not allowed in this context (parallel, pipeline stage, loop round, or shared agent).`);
|
|
325
332
|
}
|
|
326
333
|
if (!firstArg) {
|
|
327
334
|
report(node, `${callPrefix} requires an object literal argument.`);
|
|
@@ -418,6 +425,277 @@ export function validateWorkflow(workflow, options) {
|
|
|
418
425
|
report(firstArg, `${callPrefix} is missing required 'args' property.`);
|
|
419
426
|
}
|
|
420
427
|
}
|
|
428
|
+
function validateLoopCall(node, isContextForm, contextName = "ctx", isForbiddenContext = false, functionDepth = 0, isInsideParallel = false, isInsideLoopRun = false, isInsideMainWorkflow = false) {
|
|
429
|
+
const callPrefix = isContextForm ? `${contextName}.loop()` : "loop()";
|
|
430
|
+
if (isInsideParallel) {
|
|
431
|
+
report(node, `loop() inside parallel() is not supported to prevent state overwrites.`);
|
|
432
|
+
}
|
|
433
|
+
if (isInsideLoopRun) {
|
|
434
|
+
report(node, `Nested loops are not supported to prevent state overwrites.`);
|
|
435
|
+
}
|
|
436
|
+
if (functionDepth > 0 && !isInsideMainWorkflow && !isInsideLoopRun) {
|
|
437
|
+
report(node, `loop() is not allowed inside helper functions or recursive scopes to prevent state overwrites.`);
|
|
438
|
+
}
|
|
439
|
+
if (node.arguments.length !== 1) {
|
|
440
|
+
report(node, `${callPrefix} now accepts exactly one object argument.`);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const firstArg = node.arguments[0];
|
|
444
|
+
if (!firstArg) {
|
|
445
|
+
report(node, `${callPrefix} now accepts exactly one object argument.`);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
if (!ts.isObjectLiteralExpression(firstArg)) {
|
|
449
|
+
if (isStaticValue(firstArg)) {
|
|
450
|
+
report(firstArg, `${callPrefix} argument must be an object literal.`);
|
|
451
|
+
}
|
|
452
|
+
visit(firstArg, isForbiddenContext, functionDepth, isInsideLoopRun, new Set(), isInsideParallel, isInsideMainWorkflow);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const propsMap = new Map();
|
|
456
|
+
const seenKeys = new Set();
|
|
457
|
+
for (const prop of firstArg.properties) {
|
|
458
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
459
|
+
report(prop, `${callPrefix} does not support spread properties.`);
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
463
|
+
const key = prop.name.text;
|
|
464
|
+
report(prop, `${callPrefix} does not support shorthand property '${key}'.`);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (ts.isMethodDeclaration(prop)) {
|
|
468
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
469
|
+
report(prop, `${callPrefix} does not support computed method names.`);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const key = prop.name.text;
|
|
473
|
+
if (key !== "run") {
|
|
474
|
+
report(prop, `${callPrefix} does not support method properties for '${key}'.`);
|
|
475
|
+
}
|
|
476
|
+
propsMap.set(key, prop);
|
|
477
|
+
seenKeys.add(key);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
481
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
482
|
+
report(prop, `${callPrefix} does not support computed property names.`);
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const key = prop.name.text;
|
|
486
|
+
propsMap.set(key, prop.initializer);
|
|
487
|
+
seenKeys.add(key);
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
report(prop, `${callPrefix} contains unsupported property type.`);
|
|
491
|
+
}
|
|
492
|
+
const allowedKeys = new Set(["label", "initialState", "options", "run"]);
|
|
493
|
+
for (const key of seenKeys) {
|
|
494
|
+
if (key === "runRound") {
|
|
495
|
+
report(firstArg, `${callPrefix} does not support 'runRound'. Use 'run' instead.`);
|
|
496
|
+
}
|
|
497
|
+
else if (!allowedKeys.has(key)) {
|
|
498
|
+
report(firstArg, `${callPrefix} contains unsupported top-level key '${key}'.`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const requiredKeys = ["label", "initialState", "options", "run"];
|
|
502
|
+
for (const reqKey of requiredKeys) {
|
|
503
|
+
if (!seenKeys.has(reqKey)) {
|
|
504
|
+
report(firstArg, `${callPrefix} is missing required '${reqKey}' property.`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const labelInit = propsMap.get("label");
|
|
508
|
+
if (labelInit && isStaticValue(labelInit)) {
|
|
509
|
+
const labelVal = parseStaticProperties(labelInit);
|
|
510
|
+
if (typeof labelVal !== "string") {
|
|
511
|
+
report(labelInit, "label must be a string literal.");
|
|
512
|
+
}
|
|
513
|
+
else if (labelVal.trim() === "") {
|
|
514
|
+
report(labelInit, "label cannot be empty.");
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
const normalizedLabel = labelVal
|
|
518
|
+
.toLowerCase()
|
|
519
|
+
.trim()
|
|
520
|
+
.replace(/[^a-z0-9_.:-]+/g, "-")
|
|
521
|
+
.replace(/^-+|-+$/g, "");
|
|
522
|
+
if (loopLabelsSeen.has(normalizedLabel)) {
|
|
523
|
+
report(labelInit, `Duplicate loop label detected: '${labelVal}'. All loop labels in a workflow must be unique.`);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
loopLabelsSeen.add(normalizedLabel);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (labelInit) {
|
|
531
|
+
visit(labelInit, isForbiddenContext, functionDepth);
|
|
532
|
+
}
|
|
533
|
+
const initialStateInit = propsMap.get("initialState");
|
|
534
|
+
if (initialStateInit) {
|
|
535
|
+
visit(initialStateInit, isForbiddenContext, functionDepth);
|
|
536
|
+
}
|
|
537
|
+
const optionsInit = propsMap.get("options");
|
|
538
|
+
if (optionsInit) {
|
|
539
|
+
if (ts.isObjectLiteralExpression(optionsInit)) {
|
|
540
|
+
const allowedLoopOptionKeys = new Set(["failureMode", "maxRounds", "timeoutMs"]);
|
|
541
|
+
const deprecatedKeys = new Set(["stopWhen", "nextState", "onFailureState", "resultMode", "metadata"]);
|
|
542
|
+
const optPropsMap = new Map();
|
|
543
|
+
const optSeenKeys = new Set();
|
|
544
|
+
for (const prop of optionsInit.properties) {
|
|
545
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
546
|
+
report(prop, `${callPrefix} does not support spread properties in options.`);
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!ts.isPropertyAssignment(prop)) {
|
|
550
|
+
report(prop, `${callPrefix} does not support shorthand or method properties in options.`);
|
|
551
|
+
continue;
|
|
552
|
+
}
|
|
553
|
+
if (!ts.isIdentifier(prop.name) && !ts.isStringLiteral(prop.name)) {
|
|
554
|
+
report(prop.name, `${callPrefix} does not support computed property names in options.`);
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const key = prop.name.text;
|
|
558
|
+
if (deprecatedKeys.has(key)) {
|
|
559
|
+
report(prop.name, `${callPrefix} option '${key}' is deprecated or unsupported.`);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (!allowedLoopOptionKeys.has(key)) {
|
|
563
|
+
report(prop.name, `${callPrefix} options contain unsupported key '${key}'.`);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
optPropsMap.set(key, prop.initializer);
|
|
567
|
+
optSeenKeys.add(key);
|
|
568
|
+
}
|
|
569
|
+
if (!optSeenKeys.has("maxRounds")) {
|
|
570
|
+
report(optionsInit, `${callPrefix} options is missing required 'maxRounds'.`);
|
|
571
|
+
}
|
|
572
|
+
for (const [key, init] of optPropsMap.entries()) {
|
|
573
|
+
const isStatic = isStaticValue(init);
|
|
574
|
+
const staticVal = isStatic ? parseStaticProperties(init) : undefined;
|
|
575
|
+
if (key === "maxRounds") {
|
|
576
|
+
if (isStatic) {
|
|
577
|
+
const ceiling = options.maxLoopRounds ?? 20;
|
|
578
|
+
if (typeof staticVal !== "number" || isNaN(staticVal) || !Number.isInteger(staticVal) || staticVal < 1) {
|
|
579
|
+
report(init, "maxRounds must be a positive integer.");
|
|
580
|
+
}
|
|
581
|
+
else if (staticVal > ceiling) {
|
|
582
|
+
report(init, `maxRounds (${staticVal}) exceeds the configured ceiling of ${ceiling}.`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
else if (key === "timeoutMs") {
|
|
587
|
+
if (isStatic) {
|
|
588
|
+
if (typeof staticVal !== "number" || isNaN(staticVal) || !Number.isInteger(staticVal) || staticVal <= 0) {
|
|
589
|
+
report(init, "timeoutMs must be a positive integer.");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else if (key === "failureMode") {
|
|
594
|
+
if (isStatic) {
|
|
595
|
+
if (typeof staticVal !== "string") {
|
|
596
|
+
report(init, "failureMode must be a string literal.");
|
|
597
|
+
}
|
|
598
|
+
else if (staticVal !== "throw" && staticVal !== "settled") {
|
|
599
|
+
report(init, "failureMode must be 'throw' or 'settled'.");
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
else if (isStaticValue(optionsInit)) {
|
|
606
|
+
report(optionsInit, `${callPrefix} options must be an object literal.`);
|
|
607
|
+
}
|
|
608
|
+
visit(optionsInit, isForbiddenContext, functionDepth);
|
|
609
|
+
}
|
|
610
|
+
const runInit = propsMap.get("run");
|
|
611
|
+
if (runInit) {
|
|
612
|
+
if (!ts.isArrowFunction(runInit) && !ts.isFunctionExpression(runInit) && !ts.isMethodDeclaration(runInit)) {
|
|
613
|
+
report(runInit, `${callPrefix} 'run' property must be a function expression, arrow function, or method property.`);
|
|
614
|
+
}
|
|
615
|
+
else {
|
|
616
|
+
const params = runInit.parameters;
|
|
617
|
+
const localLoopContextNames = new Set();
|
|
618
|
+
if (params && params.length >= 2) {
|
|
619
|
+
const secondParam = params[1];
|
|
620
|
+
if (secondParam && ts.isIdentifier(secondParam.name)) {
|
|
621
|
+
localLoopContextNames.add(secondParam.name.text);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (localLoopContextNames.size === 0) {
|
|
625
|
+
localLoopContextNames.add("ctx");
|
|
626
|
+
localLoopContextNames.add("context");
|
|
627
|
+
}
|
|
628
|
+
function validateReturnExpression(expr) {
|
|
629
|
+
let unwrapped = expr;
|
|
630
|
+
while (ts.isParenthesizedExpression(unwrapped)) {
|
|
631
|
+
unwrapped = unwrapped.expression;
|
|
632
|
+
}
|
|
633
|
+
if (ts.isObjectLiteralExpression(unwrapped)) {
|
|
634
|
+
const retKeys = new Set();
|
|
635
|
+
let doneInit;
|
|
636
|
+
for (const prop of unwrapped.properties) {
|
|
637
|
+
if (ts.isPropertyAssignment(prop) || ts.isMethodDeclaration(prop)) {
|
|
638
|
+
const propName = ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name) ? prop.name.text : "";
|
|
639
|
+
if (propName) {
|
|
640
|
+
retKeys.add(propName);
|
|
641
|
+
if (propName === "done" && ts.isPropertyAssignment(prop)) {
|
|
642
|
+
doneInit = prop.initializer;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else if (ts.isShorthandPropertyAssignment(prop)) {
|
|
647
|
+
const propName = prop.name.text;
|
|
648
|
+
retKeys.add(propName);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
if (retKeys.has("result")) {
|
|
652
|
+
report(unwrapped, "Loop run return must not contain 'result'.");
|
|
653
|
+
}
|
|
654
|
+
if (!retKeys.has("done")) {
|
|
655
|
+
report(unwrapped, "Loop run return must contain 'done'.");
|
|
656
|
+
}
|
|
657
|
+
if (!retKeys.has("nextState")) {
|
|
658
|
+
report(unwrapped, "Loop run return must contain 'nextState'.");
|
|
659
|
+
}
|
|
660
|
+
if (doneInit && isStaticValue(doneInit)) {
|
|
661
|
+
const doneVal = parseStaticProperties(doneInit);
|
|
662
|
+
if (typeof doneVal !== "boolean") {
|
|
663
|
+
report(doneInit, "done must be a boolean.");
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function findReturnStatements(n, returns) {
|
|
669
|
+
if (ts.isReturnStatement(n)) {
|
|
670
|
+
returns.push(n);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (ts.isFunctionDeclaration(n) || ts.isFunctionExpression(n) || ts.isArrowFunction(n) || ts.isMethodDeclaration(n)) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
ts.forEachChild(n, child => findReturnStatements(child, returns));
|
|
677
|
+
}
|
|
678
|
+
if (runInit.body) {
|
|
679
|
+
if (ts.isBlock(runInit.body)) {
|
|
680
|
+
const returns = [];
|
|
681
|
+
findReturnStatements(runInit.body, returns);
|
|
682
|
+
for (const ret of returns) {
|
|
683
|
+
if (ret.expression) {
|
|
684
|
+
validateReturnExpression(ret.expression);
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
report(ret, "Loop run return must contain 'done' and 'nextState'.");
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
else {
|
|
692
|
+
validateReturnExpression(runInit.body);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
visit(runInit, true, functionDepth, true, localLoopContextNames);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
421
699
|
function validateAgentCall(node, isContextForm, contextName = "ctx") {
|
|
422
700
|
const firstArg = node.arguments[0];
|
|
423
701
|
const callPrefix = isContextForm ? `${contextName}.agent()` : "agent()";
|
|
@@ -511,7 +789,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
511
789
|
if (definitionProp) {
|
|
512
790
|
const definitionArg = ts.isPropertyAssignment(definitionProp) ? definitionProp.initializer : undefined;
|
|
513
791
|
validateSharedAgentId(definitionArg);
|
|
514
|
-
validateSharedAgentInput(definitionArg, firstArg
|
|
792
|
+
validateSharedAgentInput(definitionArg, firstArg);
|
|
515
793
|
}
|
|
516
794
|
else {
|
|
517
795
|
if (!promptProp && !hasSpread) {
|
|
@@ -536,19 +814,21 @@ export function validateWorkflow(workflow, options) {
|
|
|
536
814
|
}
|
|
537
815
|
}
|
|
538
816
|
}
|
|
539
|
-
function isToolOrCtxTool(node) {
|
|
817
|
+
function isToolOrCtxTool(node, currentLoopCtxNames) {
|
|
540
818
|
// direct tool
|
|
541
819
|
if (ts.isIdentifier(node) && node.text === "tool")
|
|
542
820
|
return true;
|
|
543
821
|
// ctx.tool
|
|
544
822
|
if (ts.isPropertyAccessExpression(node) &&
|
|
545
|
-
ts.isIdentifier(node.expression) &&
|
|
823
|
+
ts.isIdentifier(node.expression) &&
|
|
824
|
+
(contextParameterNames.has(node.expression.text) || (currentLoopCtxNames && currentLoopCtxNames.has(node.expression.text))) &&
|
|
546
825
|
node.name.text === "tool") {
|
|
547
826
|
return true;
|
|
548
827
|
}
|
|
549
828
|
// ctx["tool"]
|
|
550
829
|
if (ts.isElementAccessExpression(node) &&
|
|
551
|
-
ts.isIdentifier(node.expression) &&
|
|
830
|
+
ts.isIdentifier(node.expression) &&
|
|
831
|
+
(contextParameterNames.has(node.expression.text) || (currentLoopCtxNames && currentLoopCtxNames.has(node.expression.text))) &&
|
|
552
832
|
ts.isStringLiteral(node.argumentExpression) && node.argumentExpression.text === "tool") {
|
|
553
833
|
return true;
|
|
554
834
|
}
|
|
@@ -557,39 +837,39 @@ export function validateWorkflow(workflow, options) {
|
|
|
557
837
|
if (ts.isPropertyAccessExpression(node)) {
|
|
558
838
|
const name = node.name.text;
|
|
559
839
|
if (name === "bind" || name === "call" || name === "apply") {
|
|
560
|
-
if (isToolOrCtxTool(node.expression))
|
|
840
|
+
if (isToolOrCtxTool(node.expression, currentLoopCtxNames))
|
|
561
841
|
return true;
|
|
562
842
|
}
|
|
563
843
|
}
|
|
564
844
|
if (ts.isCallExpression(node)) {
|
|
565
|
-
if (isToolOrCtxTool(node.expression))
|
|
845
|
+
if (isToolOrCtxTool(node.expression, currentLoopCtxNames))
|
|
566
846
|
return true;
|
|
567
847
|
}
|
|
568
848
|
return false;
|
|
569
849
|
}
|
|
570
|
-
function isLikelyWorkflowContext(node) {
|
|
571
|
-
if (ts.isIdentifier(node) && contextParameterNames.has(node.text))
|
|
850
|
+
function isLikelyWorkflowContext(node, currentLoopCtxNames) {
|
|
851
|
+
if (ts.isIdentifier(node) && (contextParameterNames.has(node.text) || (currentLoopCtxNames && currentLoopCtxNames.has(node.text))))
|
|
572
852
|
return true;
|
|
573
853
|
return false;
|
|
574
854
|
}
|
|
575
|
-
function checkBindingForToolAlias(name, initializer) {
|
|
855
|
+
function checkBindingForToolAlias(name, initializer, currentLoopCtxNames) {
|
|
576
856
|
if (ts.isObjectBindingPattern(name)) {
|
|
577
857
|
for (const element of name.elements) {
|
|
578
858
|
const propName = element.propertyName ? (ts.isIdentifier(element.propertyName) ? element.propertyName.text : undefined) : (ts.isIdentifier(element.name) ? element.name.text : undefined);
|
|
579
859
|
if (propName === "tool") {
|
|
580
860
|
// If we have an initializer, check if it's the context.
|
|
581
861
|
// If no initializer (like in parameter), we assume it's aliasing if the parameter looks like a context.
|
|
582
|
-
if (!initializer || isLikelyWorkflowContext(initializer)) {
|
|
862
|
+
if (!initializer || isLikelyWorkflowContext(initializer, currentLoopCtxNames)) {
|
|
583
863
|
report(element, "Aliasing tool() is not allowed. Use it directly as tool() or ctx.tool().");
|
|
584
864
|
}
|
|
585
865
|
}
|
|
586
866
|
if (element.name && ts.isObjectBindingPattern(element.name)) {
|
|
587
|
-
checkBindingForToolAlias(element.name, initializer);
|
|
867
|
+
checkBindingForToolAlias(element.name, initializer, currentLoopCtxNames);
|
|
588
868
|
}
|
|
589
869
|
}
|
|
590
870
|
}
|
|
591
871
|
}
|
|
592
|
-
function visit(node, isForbiddenContext = false, functionDepth = 0) {
|
|
872
|
+
function visit(node, isForbiddenContext = false, functionDepth = 0, isInsideLoopRun = false, loopContextNames = new Set(), isInsideParallel = false, isInsideMainWorkflow = false) {
|
|
593
873
|
// Skip the metadata declaration statement (export const meta = { ... })
|
|
594
874
|
if (sourceFile.statements.length > 0 && node === sourceFile.statements[0]) {
|
|
595
875
|
if (ts.isVariableStatement(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
@@ -604,24 +884,27 @@ export function validateWorkflow(workflow, options) {
|
|
|
604
884
|
}
|
|
605
885
|
let nextForbiddenContext = isForbiddenContext;
|
|
606
886
|
let nextFunctionDepth = functionDepth;
|
|
887
|
+
const nextInsideLoopRun = isInsideLoopRun;
|
|
888
|
+
let nextInsideMainWorkflow = isInsideMainWorkflow;
|
|
889
|
+
const nextLoopContextNames = new Set(loopContextNames);
|
|
607
890
|
if (ts.isVariableDeclaration(node)) {
|
|
608
891
|
const init = node.initializer;
|
|
609
892
|
if (init) {
|
|
610
|
-
if (isToolOrCtxTool(init)) {
|
|
893
|
+
if (isToolOrCtxTool(init, nextLoopContextNames)) {
|
|
611
894
|
report(node, "Aliasing tool() is not allowed. Use it directly as tool() or ctx.tool().");
|
|
612
895
|
}
|
|
613
|
-
checkBindingForToolAlias(node.name, init);
|
|
896
|
+
checkBindingForToolAlias(node.name, init, nextLoopContextNames);
|
|
614
897
|
}
|
|
615
898
|
else {
|
|
616
|
-
checkBindingForToolAlias(node.name);
|
|
899
|
+
checkBindingForToolAlias(node.name, undefined, nextLoopContextNames);
|
|
617
900
|
}
|
|
618
901
|
}
|
|
619
902
|
if (ts.isParameter(node)) {
|
|
620
|
-
checkBindingForToolAlias(node.name);
|
|
903
|
+
checkBindingForToolAlias(node.name, undefined, nextLoopContextNames);
|
|
621
904
|
}
|
|
622
905
|
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
623
906
|
const rhs = node.right;
|
|
624
|
-
if (isToolOrCtxTool(rhs)) {
|
|
907
|
+
if (isToolOrCtxTool(rhs, nextLoopContextNames)) {
|
|
625
908
|
report(node, "Aliasing tool() is not allowed. Use it directly as tool() or ctx.tool().");
|
|
626
909
|
}
|
|
627
910
|
}
|
|
@@ -629,6 +912,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
629
912
|
nextFunctionDepth++;
|
|
630
913
|
if (nextFunctionDepth > 1) {
|
|
631
914
|
nextForbiddenContext = true;
|
|
915
|
+
nextInsideMainWorkflow = false;
|
|
632
916
|
}
|
|
633
917
|
else if (nextFunctionDepth === 1) {
|
|
634
918
|
// Only the default exported function (the main workflow) is allowed to contain tools.
|
|
@@ -643,6 +927,10 @@ export function validateWorkflow(workflow, options) {
|
|
|
643
927
|
}
|
|
644
928
|
if (!isMainWorkflow) {
|
|
645
929
|
nextForbiddenContext = true;
|
|
930
|
+
nextInsideMainWorkflow = false;
|
|
931
|
+
}
|
|
932
|
+
else {
|
|
933
|
+
nextInsideMainWorkflow = true;
|
|
646
934
|
}
|
|
647
935
|
}
|
|
648
936
|
}
|
|
@@ -650,7 +938,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
650
938
|
const callee = node.expression;
|
|
651
939
|
// Reject tool being passed as an argument
|
|
652
940
|
for (const arg of node.arguments) {
|
|
653
|
-
if (isToolOrCtxTool(arg)) {
|
|
941
|
+
if (isToolOrCtxTool(arg, nextLoopContextNames)) {
|
|
654
942
|
report(arg, "Aliasing tool() is not allowed. Use it directly as tool() or ctx.tool().");
|
|
655
943
|
}
|
|
656
944
|
}
|
|
@@ -742,21 +1030,29 @@ export function validateWorkflow(workflow, options) {
|
|
|
742
1030
|
if (idx === 1) {
|
|
743
1031
|
// stagesArg and its children (the stage objects and their 'run' methods)
|
|
744
1032
|
// need to recursively forbid tools.
|
|
745
|
-
visit(arg, true, nextFunctionDepth);
|
|
1033
|
+
visit(arg, true, nextFunctionDepth, nextInsideLoopRun, nextLoopContextNames, isInsideParallel, false);
|
|
746
1034
|
}
|
|
747
1035
|
else {
|
|
748
|
-
visit(arg, nextForbiddenContext, nextFunctionDepth);
|
|
1036
|
+
visit(arg, nextForbiddenContext, nextFunctionDepth, nextInsideLoopRun, nextLoopContextNames, isInsideParallel, nextInsideMainWorkflow);
|
|
749
1037
|
}
|
|
750
1038
|
});
|
|
751
1039
|
return;
|
|
752
1040
|
}
|
|
753
1041
|
else if (calleeText === "parallel") {
|
|
754
1042
|
nextForbiddenContext = true;
|
|
1043
|
+
node.arguments.forEach((arg) => {
|
|
1044
|
+
visit(arg, true, nextFunctionDepth, nextInsideLoopRun, nextLoopContextNames, true, nextInsideMainWorkflow);
|
|
1045
|
+
});
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
else if (calleeText === "loop") {
|
|
1049
|
+
validateLoopCall(node, false, "ctx", isForbiddenContext, functionDepth, isInsideParallel, isInsideLoopRun, isInsideMainWorkflow);
|
|
1050
|
+
return;
|
|
755
1051
|
}
|
|
756
1052
|
else if (calleeText === "defineAgent") {
|
|
757
1053
|
const firstArg = node.arguments[0];
|
|
758
1054
|
if (firstArg && ts.isObjectLiteralExpression(firstArg)) {
|
|
759
|
-
visit(firstArg, true, nextFunctionDepth);
|
|
1055
|
+
visit(firstArg, true, nextFunctionDepth, nextInsideLoopRun, nextLoopContextNames, isInsideParallel, false);
|
|
760
1056
|
return;
|
|
761
1057
|
}
|
|
762
1058
|
}
|
|
@@ -782,33 +1078,59 @@ export function validateWorkflow(workflow, options) {
|
|
|
782
1078
|
else if (ts.isPropertyAccessExpression(callee)) {
|
|
783
1079
|
const obj = callee.expression;
|
|
784
1080
|
const prop = callee.name;
|
|
785
|
-
if (ts.isIdentifier(obj)
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1081
|
+
if (ts.isIdentifier(obj)) {
|
|
1082
|
+
const isContextParam = contextParameterNames.has(obj.text) || nextLoopContextNames.has(obj.text);
|
|
1083
|
+
if (isContextParam) {
|
|
1084
|
+
if (prop.text === "agent") {
|
|
1085
|
+
validateAgentCall(node, true, obj.text);
|
|
1086
|
+
}
|
|
1087
|
+
else if (prop.text === "workflow") {
|
|
1088
|
+
validateWorkflowCall(node, true, obj.text);
|
|
1089
|
+
}
|
|
1090
|
+
else if (prop.text === "loop") {
|
|
1091
|
+
validateLoopCall(node, true, obj.text, isForbiddenContext, functionDepth, isInsideParallel, isInsideLoopRun, isInsideMainWorkflow);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
else if (prop.text === "tool") {
|
|
1095
|
+
validateToolCall(node, true, nextForbiddenContext, obj.text);
|
|
1096
|
+
}
|
|
791
1097
|
}
|
|
792
|
-
|
|
793
|
-
|
|
1098
|
+
if (nextLoopContextNames.has(obj.text) || (nextInsideLoopRun && (obj.text === "ctx" || obj.text === "context"))) {
|
|
1099
|
+
if (prop.text === "break") {
|
|
1100
|
+
report(node, `${obj.text}.break() is not supported inside loop run callback.`);
|
|
1101
|
+
}
|
|
1102
|
+
else if (prop.text === "parallel") {
|
|
1103
|
+
report(node, `${obj.text}.parallel() is not supported inside loop run callback. Use top-level parallel() around loop task thunks instead.`);
|
|
1104
|
+
}
|
|
794
1105
|
}
|
|
795
1106
|
}
|
|
796
1107
|
}
|
|
797
1108
|
else if (ts.isElementAccessExpression(callee)) {
|
|
798
1109
|
const obj = callee.expression;
|
|
799
1110
|
const arg = callee.argumentExpression;
|
|
800
|
-
if (ts.isIdentifier(obj) &&
|
|
1111
|
+
if (ts.isIdentifier(obj) && ts.isStringLiteral(arg)) {
|
|
801
1112
|
const propName = arg.text;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
if (
|
|
805
|
-
|
|
1113
|
+
const isContextParam = contextParameterNames.has(obj.text) || nextLoopContextNames.has(obj.text);
|
|
1114
|
+
if (isContextParam) {
|
|
1115
|
+
if (["agent", "workflow", "tool"].includes(propName)) {
|
|
1116
|
+
report(node, `Computed access forms like ${obj.text}["${propName}"]() are not allowed. Use direct property access like ${obj.text}.${propName}() instead.`);
|
|
1117
|
+
if (propName === "agent") {
|
|
1118
|
+
validateAgentCall(node, true, obj.text);
|
|
1119
|
+
}
|
|
1120
|
+
else if (propName === "workflow") {
|
|
1121
|
+
validateWorkflowCall(node, true, obj.text);
|
|
1122
|
+
}
|
|
1123
|
+
else if (propName === "tool") {
|
|
1124
|
+
validateToolCall(node, true, nextForbiddenContext, obj.text);
|
|
1125
|
+
}
|
|
806
1126
|
}
|
|
807
|
-
|
|
808
|
-
|
|
1127
|
+
}
|
|
1128
|
+
if (nextLoopContextNames.has(obj.text) || (nextInsideLoopRun && (obj.text === "ctx" || obj.text === "context"))) {
|
|
1129
|
+
if (propName === "break") {
|
|
1130
|
+
report(node, `Computed access forms like ${obj.text}["break"]() are not allowed.`);
|
|
809
1131
|
}
|
|
810
|
-
else if (propName === "
|
|
811
|
-
|
|
1132
|
+
else if (propName === "parallel") {
|
|
1133
|
+
report(node, `${obj.text}.parallel() is not supported inside loop run callback. Use top-level parallel() around loop task thunks instead.`);
|
|
812
1134
|
}
|
|
813
1135
|
}
|
|
814
1136
|
}
|
|
@@ -874,7 +1196,7 @@ export function validateWorkflow(workflow, options) {
|
|
|
874
1196
|
}
|
|
875
1197
|
}
|
|
876
1198
|
}
|
|
877
|
-
ts.forEachChild(node, (child) => visit(child, nextForbiddenContext, nextFunctionDepth));
|
|
1199
|
+
ts.forEachChild(node, (child) => visit(child, nextForbiddenContext, nextFunctionDepth, nextInsideLoopRun, nextLoopContextNames, isInsideParallel, nextInsideMainWorkflow));
|
|
878
1200
|
}
|
|
879
1201
|
visit(sourceFile);
|
|
880
1202
|
return issues;
|
|
@@ -1000,7 +1322,8 @@ export function validateRegistryDependencies(registry, options) {
|
|
|
1000
1322
|
knownWorkflowNames,
|
|
1001
1323
|
workflowInputSchemas,
|
|
1002
1324
|
allowDynamicSharedAgentIds: options.allowDynamicSharedAgentIds,
|
|
1003
|
-
toolRegistry: options.toolRegistry
|
|
1325
|
+
toolRegistry: options.toolRegistry,
|
|
1326
|
+
maxLoopRounds: options.maxLoopRounds
|
|
1004
1327
|
});
|
|
1005
1328
|
const localErrors = issues.filter(issue => issue.severity !== "warning").map(issue => issue.message);
|
|
1006
1329
|
// Log warnings for checked workflows. To avoid duplicate warnings for root workflow (which was
|