@agwab/pi-workflow 0.2.0 → 0.3.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.
Files changed (79) hide show
  1. package/README.md +2 -0
  2. package/dist/compiler.d.ts +4 -6
  3. package/dist/compiler.js +70 -39
  4. package/dist/dynamic-decision.d.ts +0 -1
  5. package/dist/dynamic-decision.js +0 -7
  6. package/dist/dynamic-generated-task-runtime.d.ts +2 -0
  7. package/dist/dynamic-generated-task-runtime.js +21 -8
  8. package/dist/dynamic-profiles.d.ts +0 -1
  9. package/dist/dynamic-profiles.js +0 -3
  10. package/dist/engine-run-graph.d.ts +1 -0
  11. package/dist/engine-run-graph.js +142 -2
  12. package/dist/engine.d.ts +10 -6
  13. package/dist/engine.js +146 -77
  14. package/dist/extension.d.ts +2 -1
  15. package/dist/extension.js +38 -15
  16. package/dist/index.d.ts +3 -3
  17. package/dist/index.js +2 -1
  18. package/dist/store.d.ts +3 -1
  19. package/dist/store.js +189 -49
  20. package/dist/subagent-backend.d.ts +4 -0
  21. package/dist/subagent-backend.js +281 -31
  22. package/dist/types.d.ts +9 -1
  23. package/dist/workflow-runtime.d.ts +2 -0
  24. package/dist/workflow-runtime.js +40 -1
  25. package/dist/workflow-view.js +3 -1
  26. package/dist/workflow-web-source-extension.js +167 -48
  27. package/dist/workflow-web-source.d.ts +2 -1
  28. package/dist/workflow-web-source.js +84 -19
  29. package/docs/usage.md +11 -0
  30. package/node_modules/@agwab/pi-subagent/README.md +3 -3
  31. package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
  32. package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
  33. package/node_modules/@agwab/pi-subagent/package.json +2 -2
  34. package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
  35. package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
  36. package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
  37. package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
  38. package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
  39. package/node_modules/@agwab/pi-subagent/src/index.ts +995 -573
  40. package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
  41. package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
  42. package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
  43. package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
  44. package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
  45. package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
  46. package/node_modules/@agwab/pi-subagent/src/panel.ts +1352 -560
  47. package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
  48. package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
  49. package/package.json +2 -2
  50. package/src/compiler.ts +127 -66
  51. package/src/dynamic-decision.ts +0 -11
  52. package/src/dynamic-generated-task-runtime.ts +47 -12
  53. package/src/dynamic-profiles.ts +0 -4
  54. package/src/engine-run-graph.ts +185 -2
  55. package/src/engine.ts +192 -107
  56. package/src/extension.ts +50 -17
  57. package/src/index.ts +3 -1
  58. package/src/store.ts +253 -55
  59. package/src/subagent-backend.ts +369 -32
  60. package/src/types.ts +13 -1
  61. package/src/workflow-runtime.ts +53 -2
  62. package/src/workflow-view.ts +2 -1
  63. package/src/workflow-web-source-extension.ts +621 -228
  64. package/src/workflow-web-source.ts +118 -28
  65. package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
  66. package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
  67. package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
  68. package/workflows/deep-research/helpers/render-executive.mjs +8 -21
  69. package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
  70. package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
  71. package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
  72. package/workflows/impact-review/spec.json +3 -3
  73. package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
  74. package/dist/dynamic-loader.d.ts +0 -25
  75. package/dist/dynamic-loader.js +0 -13
  76. package/src/dynamic-loader.ts +0 -49
  77. package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
  78. package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
  79. package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
package/README.md CHANGED
@@ -64,6 +64,8 @@ If you want deterministic manual control, use the slash command form:
64
64
  /workflow run deep-research "Research this repository and summarize the architecture tradeoffs."
65
65
  ```
66
66
 
67
+ For opt-in lower-latency runs, add `--thinking low`; defaults remain conservative pending holdout evidence. See [`docs/usage.md`](./docs/usage.md).
68
+
67
69
  For a one-off adaptive workflow that should plan, fan out, and synthesize without choosing a saved workflow, use:
68
70
 
69
71
  ```text
@@ -1,5 +1,5 @@
1
- import { type ArtifactGraphWorkflowSpec, type ThinkingLevel } from "./types.js";
2
- import { type WorkflowModelInfo } from "./workflow-runtime.js";
1
+ import { type ArtifactGraphWorkflowSpec } from "./types.js";
2
+ import { type WorkflowModelInfo, type WorkflowRuntimeDefaults } from "./workflow-runtime.js";
3
3
  interface CompileOptions {
4
4
  cwd: string;
5
5
  specPath?: string;
@@ -7,9 +7,7 @@ interface CompileOptions {
7
7
  }
8
8
  export declare function compileWorkflow(spec: ArtifactGraphWorkflowSpec, options: CompileOptions & {
9
9
  task?: string;
10
- runtimeDefaults?: {
11
- model?: string;
12
- thinking?: ThinkingLevel;
13
- };
10
+ runtimeOverrides?: WorkflowRuntimeDefaults;
11
+ runtimeDefaults?: WorkflowRuntimeDefaults;
14
12
  }): Promise<any>;
15
13
  export {};
package/dist/compiler.js CHANGED
@@ -2,9 +2,10 @@ import { readFile } from "node:fs/promises";
2
2
  import { dirname, resolve } from "node:path";
3
3
  import { loadAgentByName } from "./agents.js";
4
4
  import { DYNAMIC_OUTPUT_PROFILES } from "./dynamic-profiles.js";
5
+ import { compileRole } from "./roles.js";
5
6
  import { classifyToolCapability, effectiveToolClassification, providersForSelectedTools, resolveToolSelection, TOOL_NAME_PATTERN, toolAllowedByAuthorityCeiling, toolNameForSpec, } from "./tool-metadata.js";
6
7
  import { WorkflowValidationError, WORKFLOW_RUN_TYPE, } from "./types.js";
7
- import { resolveWorkflowRuntime, } from "./workflow-runtime.js";
8
+ import { resolveWorkflowRuntime, selectWorkflowRuntime, } from "./workflow-runtime.js";
8
9
  const DELEGATION_TOOLS = new Set([
9
10
  "skill_test_subagent",
10
11
  "workflow",
@@ -413,6 +414,17 @@ async function collectForeachPathWarnings(stages, specDir) {
413
414
  }
414
415
  return warnings;
415
416
  }
417
+ function runtimeSettings(value) {
418
+ if (!isPlainRecord(value))
419
+ return undefined;
420
+ const model = typeof value.model === "string" && value.model.trim()
421
+ ? value.model.trim()
422
+ : undefined;
423
+ const thinking = typeof value.thinking === "string" && value.thinking.trim()
424
+ ? value.thinking.trim()
425
+ : undefined;
426
+ return model || thinking ? { model, thinking } : undefined;
427
+ }
416
428
  async function compileArtifactGraphPlan(spec, options) {
417
429
  const stages = spec.stages;
418
430
  if (!Array.isArray(stages)) {
@@ -431,14 +443,11 @@ async function compileArtifactGraphPlan(spec, options) {
431
443
  return defaultAgent;
432
444
  };
433
445
  const roleEntries = Object.entries(spec.roles ?? {});
434
- const roles = roleEntries.map(([name, role]) => ({
435
- name,
436
- fromAgent: role.fromAgent,
437
- content: role.prompt ?? "",
438
- maxChars: role.maxChars ?? 8000,
439
- truncated: false,
440
- includedSections: [],
441
- excludedSections: [],
446
+ const roles = await Promise.all(roleEntries.map(async ([name, role]) => {
447
+ const sourceAgent = role.fromAgent
448
+ ? await loadWorkflowAgent(role.fromAgent, options.cwd, agentCache, `$.roles.${name}.fromAgent`)
449
+ : undefined;
450
+ return compileRole(name, role, sourceAgent);
442
451
  }));
443
452
  const roleText = roles.length
444
453
  ? `# Role Context\n\n${roles.map((r) => `## Role: ${r.name}\n${r.content}`).join("\n\n")}`
@@ -450,8 +459,9 @@ async function compileArtifactGraphPlan(spec, options) {
450
459
  Object.keys(workflowInput).length > 0
451
460
  ? `# Workflow Input\n\n${JSON.stringify(workflowInput, null, 2)}`
452
461
  : "";
453
- const defaultModel = options.runtimeDefaults?.model ?? spec.defaults?.model;
454
- const defaultThinking = options.runtimeDefaults?.thinking ?? spec.defaults?.thinking;
462
+ const runtimeOverrides = options.runtimeOverrides;
463
+ const runtimeDefaults = options.runtimeDefaults;
464
+ const specRuntimeDefaults = runtimeSettings(spec.defaults);
455
465
  const tasks = [];
456
466
  const stageRecords = [];
457
467
  const issues = [];
@@ -475,17 +485,26 @@ async function compileArtifactGraphPlan(spec, options) {
475
485
  : `$.artifactGraph.stages.${jsonKey(stage.id)}.dynamic`;
476
486
  validateDelegationBoundary(rawDynamicToolSelection.tools, issues, dynamicToolPath);
477
487
  const dynamicToolSelection = filterToolSelection(rawDynamicToolSelection);
478
- const dynamicTask = buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, options.cwd, specDir, workflowInputText, options.task, defaultModel, defaultThinking, overrides);
479
- const resolvedDynamicRuntime = await resolveWorkflowRuntime({ model: defaultModel, thinking: defaultThinking }, {
488
+ const requestedRuntime = selectWorkflowRuntime(runtimeOverrides, runtimeSettings(stage), runtimeDefaults, specRuntimeDefaults);
489
+ const resolvedDynamicRuntime = await resolveWorkflowRuntime(requestedRuntime, {
480
490
  taskKey: key,
481
491
  stageId: stage.id,
482
492
  taskId,
483
493
  agent: "dynamic",
484
494
  }, { availableModels: options.availableModels });
495
+ const dynamicTask = buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, options.cwd, specDir, workflowInputText, options.task, resolvedDynamicRuntime, {
496
+ runtimeOverrides,
497
+ runtimeDefaults,
498
+ specRuntimeDefaults,
499
+ stageRuntime: runtimeSettings(stage),
500
+ }, overrides);
485
501
  dynamicTask.runtime = {
486
502
  ...dynamicTask.runtime,
487
503
  ...resolvedDynamicRuntime,
488
504
  };
505
+ if (options.availableModels?.length) {
506
+ dynamicTask.dynamic.availableModels = options.availableModels;
507
+ }
489
508
  if (dynamicToolSelection.tools || dynamicToolSelection.toolProviders) {
490
509
  dynamicTask.runtime = {
491
510
  ...dynamicTask.runtime,
@@ -539,13 +558,7 @@ async function compileArtifactGraphPlan(spec, options) {
539
558
  validateToolSubset(toolSelection.tools, stageAgent, issues, toolPath);
540
559
  validateDelegationBoundary(toolSelection.tools, issues, toolPath);
541
560
  const filteredToolSelection = filterToolSelection(toolSelection);
542
- // Explicit runtime overrides outrank stage pins; spec defaults fill last.
543
- const requestedRuntime = {
544
- model: options.runtimeDefaults?.model ?? stage.model ?? spec.defaults?.model,
545
- thinking: options.runtimeDefaults?.thinking ??
546
- stage.thinking ??
547
- spec.defaults?.thinking,
548
- };
561
+ const requestedRuntime = selectWorkflowRuntime(runtimeOverrides, runtimeSettings(stage), runtimeDefaults, specRuntimeDefaults);
549
562
  const resolvedRuntime = await resolveWorkflowRuntime(requestedRuntime, {
550
563
  taskKey: key,
551
564
  stageId: stage.id,
@@ -825,11 +838,33 @@ async function compileArtifactGraphPlan(spec, options) {
825
838
  tasks,
826
839
  warnings,
827
840
  budget: {
828
- models: defaultModel ? [{ model: defaultModel }] : [],
841
+ models: budgetModelRows(tasks),
829
842
  unratedModels: [],
830
843
  },
831
844
  };
832
845
  }
846
+ function budgetModelRows(tasks) {
847
+ const models = new Set();
848
+ for (const task of tasks) {
849
+ if (typeof task?.runtime?.model === "string" && task.runtime.model.trim()) {
850
+ models.add(task.runtime.model.trim());
851
+ }
852
+ const loop = task?.dynamic?.decisionLoop;
853
+ if (!loop || typeof loop !== "object")
854
+ continue;
855
+ for (const profile of [
856
+ loop.planner,
857
+ loop.workerDefaults,
858
+ loop.verifier,
859
+ loop.synthesis,
860
+ ]) {
861
+ if (typeof profile?.model === "string" && profile.model.trim()) {
862
+ models.add(profile.model.trim());
863
+ }
864
+ }
865
+ }
866
+ return [...models].sort().map((model) => ({ model }));
867
+ }
833
868
  function isSupportStage(stage) {
834
869
  return stage?.support !== undefined && stage?.type === undefined;
835
870
  }
@@ -889,7 +924,7 @@ function buildSupportTask(stage, taskId, key, prompt, dependencyKeys, cwd, workf
889
924
  ...overrides,
890
925
  };
891
926
  }
892
- function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specDir, workflowInputText, runtimeTask, defaultModel, defaultThinking, overrides) {
927
+ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specDir, workflowInputText, runtimeTask, controllerRuntime, runtimePriority, overrides) {
893
928
  const dynamic = stage.dynamic ?? {};
894
929
  const uses = String(dynamic.uses);
895
930
  const normalizedPrompt = String(prompt ?? "").replace(/\$\{item\}/g, "the relevant item from the dependency context");
@@ -942,7 +977,7 @@ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specD
942
977
  usesPath: resolve(specDir, String(workflow.uses)),
943
978
  };
944
979
  }
945
- const decisionLoop = compileDynamicDecisionLoop(dynamic.decisionLoop, defaultModel, defaultThinking);
980
+ const decisionLoop = compileDynamicDecisionLoop(dynamic.decisionLoop, runtimePriority);
946
981
  return {
947
982
  key,
948
983
  id: key,
@@ -960,8 +995,7 @@ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specD
960
995
  explicitWorktreePolicy: false,
961
996
  runtime: {
962
997
  approvalMode: "non-interactive",
963
- model: defaultModel,
964
- thinking: defaultThinking,
998
+ ...controllerRuntime,
965
999
  maxRuntimeMs: dynamic.budget?.maxRuntimeMs ?? DEFAULT_DYNAMIC_MAX_RUNTIME_MS,
966
1000
  },
967
1001
  safety: {
@@ -999,21 +1033,24 @@ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specD
999
1033
  helpers,
1000
1034
  workflows,
1001
1035
  ...(decisionLoop ? { decisionLoop } : {}),
1036
+ ...(runtimePriority.runtimeOverrides
1037
+ ? { runtimeOverrides: runtimePriority.runtimeOverrides }
1038
+ : {}),
1002
1039
  },
1003
1040
  ...overrides,
1004
1041
  };
1005
1042
  }
1006
- function compileDynamicDecisionLoop(value, defaultModel, defaultThinking) {
1043
+ function compileDynamicDecisionLoop(value, runtimePriority) {
1007
1044
  if (!isPlainRecord(value))
1008
1045
  return undefined;
1009
1046
  const allowedToolSelection = filterToolSelection(resolveToolSelection([Array.isArray(value.allowedTools) ? value.allowedTools : undefined], undefined));
1010
1047
  const maxFindings = positiveInteger(recordValue(value.stateIndex, "maxFindings"));
1011
1048
  const deprecatedRequiredFindingIds = stringArray(recordValue(value.stateIndex, "requiredFindingIds"));
1012
1049
  return {
1013
- planner: compileDynamicDecisionLoopProfile(value.planner, defaultModel, defaultThinking),
1014
- workerDefaults: compileDynamicDecisionLoopProfile(value.workerDefaults, defaultModel, defaultThinking),
1015
- verifier: compileDynamicDecisionLoopProfile(value.verifier, defaultModel, defaultThinking),
1016
- synthesis: compileDynamicDecisionLoopProfile(value.synthesis, defaultModel, defaultThinking),
1050
+ planner: compileDynamicDecisionLoopProfile(value.planner, runtimePriority),
1051
+ workerDefaults: compileDynamicDecisionLoopProfile(value.workerDefaults, runtimePriority),
1052
+ verifier: compileDynamicDecisionLoopProfile(value.verifier, runtimePriority),
1053
+ synthesis: compileDynamicDecisionLoopProfile(value.synthesis, runtimePriority),
1017
1054
  allowedAgents: stringArray(value.allowedAgents),
1018
1055
  ...(allowedToolSelection.tools
1019
1056
  ? { allowedTools: allowedToolSelection.tools }
@@ -1054,22 +1091,16 @@ function compileDynamicDecisionLoop(value, defaultModel, defaultThinking) {
1054
1091
  },
1055
1092
  };
1056
1093
  }
1057
- function compileDynamicDecisionLoopProfile(value, defaultModel, defaultThinking) {
1094
+ function compileDynamicDecisionLoopProfile(value, runtimePriority) {
1058
1095
  if (!isPlainRecord(value))
1059
1096
  return undefined;
1060
1097
  const toolSelection = filterToolSelection(resolveToolSelection([Array.isArray(value.tools) ? value.tools : undefined], undefined));
1061
- const model = typeof value.model === "string" && value.model.trim()
1062
- ? value.model.trim()
1063
- : defaultModel;
1064
- const thinking = typeof value.thinking === "string" && value.thinking.trim()
1065
- ? value.thinking.trim()
1066
- : defaultThinking;
1098
+ const runtime = selectWorkflowRuntime(runtimePriority.runtimeOverrides, runtimeSettings(value), runtimePriority.stageRuntime, runtimePriority.runtimeDefaults, runtimePriority.specRuntimeDefaults);
1067
1099
  return {
1068
1100
  ...(typeof value.agent === "string" && value.agent.trim()
1069
1101
  ? { agent: value.agent.trim() }
1070
1102
  : {}),
1071
- ...(model ? { model } : {}),
1072
- ...(thinking ? { thinking } : {}),
1103
+ ...runtime,
1073
1104
  ...(toolSelection.tools ? { tools: toolSelection.tools } : {}),
1074
1105
  ...(toolSelection.toolProviders
1075
1106
  ? { toolProviders: toolSelection.toolProviders }
@@ -97,7 +97,6 @@ export interface DynamicDecisionArtifactWriteResult {
97
97
  hash?: string;
98
98
  }
99
99
  export declare function validateDynamicDecision(value: unknown, context?: DynamicDecisionValidationContext): DynamicDecisionValidationResult;
100
- export declare function assertValidDynamicDecision(value: unknown, context?: DynamicDecisionValidationContext): NormalizedDynamicDecision;
101
100
  export declare function hashDynamicDecision(value: unknown): string;
102
101
  export declare function dynamicLoopSignature(decision: NormalizedDynamicDecision): string;
103
102
  export declare function writeDynamicDecisionArtifacts(input: DynamicDecisionArtifactWriteInput): Promise<DynamicDecisionArtifactWriteResult>;
@@ -121,13 +121,6 @@ export function validateDynamicDecision(value, context = {}) {
121
121
  hash: hashDynamicDecision(decision),
122
122
  };
123
123
  }
124
- export function assertValidDynamicDecision(value, context = {}) {
125
- const result = validateDynamicDecision(value, context);
126
- if (!result.ok || !result.decision) {
127
- throw new Error(`invalid dynamic decision: ${result.errors.join("; ")}`);
128
- }
129
- return result.decision;
130
- }
131
124
  export function hashDynamicDecision(value) {
132
125
  return createHash("sha256")
133
126
  .update(stableStringify(toJsonNormalizedValue(value)))
@@ -1,4 +1,5 @@
1
1
  import type { CompiledDynamicWorkflowTask, CompiledTask, CompiledWorkflow, ThinkingLevel, WorkflowRunRecord, WorkflowTaskRunRecord } from "./types.js";
2
+ import { type WorkflowModelInfo } from "./workflow-runtime.js";
2
3
  export interface DynamicArtifactInput {
3
4
  kind: "workflow-artifact-ref";
4
5
  name: string;
@@ -41,6 +42,7 @@ export declare function buildDynamicGeneratedCompiledTask(input: {
41
42
  branchId?: string;
42
43
  request: DynamicAgentRequest;
43
44
  dynamic: CompiledDynamicWorkflowTask;
45
+ availableModels?: WorkflowModelInfo[];
44
46
  }): Promise<CompiledTask>;
45
47
  export declare function isDynamicCompiledTaskPayload(value: unknown): value is CompiledTask;
46
48
  export declare function assertDynamicGeneratedMetadataMatches(compiledTask: CompiledTask, expected: {
@@ -6,6 +6,7 @@ import { readOrRebuildDynamicState } from "./dynamic-state.js";
6
6
  import { sanitizeTaskId } from "./engine-run-graph.js";
7
7
  import { fromProjectPath, isTerminalTaskStatus, readJson } from "./store.js";
8
8
  import { classifyToolCapability, effectiveToolClassification, providersForSelectedTools, toolAllowedByAuthorityCeiling, } from "./tool-metadata.js";
9
+ import { resolveWorkflowRuntime, selectWorkflowRuntime, } from "./workflow-runtime.js";
9
10
  const DYNAMIC_OUTPUT_MAX_DIGEST_CHARS = 1000;
10
11
  const DYNAMIC_DELEGATION_TOOLS = new Set([
11
12
  "skill_test_subagent",
@@ -69,6 +70,13 @@ export async function buildDynamicGeneratedCompiledTask(input) {
69
70
  }
70
71
  const toolProviders = executionProfile?.toolProviders ??
71
72
  providersForSelectedTools(tools, new Map(Object.entries(input.controllerCompiledTask.runtime.toolProviders ?? {})));
73
+ const selectedRuntime = selectWorkflowRuntime(input.dynamic.runtimeOverrides, runtimeSettings(input.request), runtimeSettings(executionProfile), runtimeSettings(input.controllerCompiledTask.runtime), runtimeSettings(agentDefinition));
74
+ const resolvedRuntime = await resolveWorkflowRuntime(selectedRuntime, {
75
+ taskKey: input.generatedSpecId,
76
+ stageId: input.controllerStageId,
77
+ taskId: input.request.id,
78
+ agent: requestedAgent,
79
+ }, { availableModels: input.availableModels ?? input.dynamic.availableModels });
72
80
  const unknownTools = (tools ?? []).filter((tool) => effectiveToolClassification(tool, toolProviders) === undefined);
73
81
  if (unknownTools.length > 0) {
74
82
  throw new Error(`dynamic agent requested tools without trusted classification metadata: ${unknownTools.join(", ")}`);
@@ -135,14 +143,7 @@ export async function buildDynamicGeneratedCompiledTask(input) {
135
143
  explicitWorktreePolicy: requiresWorktree,
136
144
  runtime: {
137
145
  approvalMode: "non-interactive",
138
- model: input.request.model ??
139
- executionProfile?.model ??
140
- input.controllerCompiledTask.runtime.model ??
141
- agentDefinition.model,
142
- thinking: input.request.thinking ??
143
- executionProfile?.thinking ??
144
- input.controllerCompiledTask.runtime.thinking ??
145
- agentDefinition.thinking,
146
+ ...resolvedRuntime,
146
147
  tools,
147
148
  ...(toolProviders ? { toolProviders } : {}),
148
149
  maxRuntimeMs: input.request.maxRuntimeMs ??
@@ -461,6 +462,18 @@ function requiredDynamicString(value, field, api = "ctx.agent()") {
461
462
  }
462
463
  return value.trim();
463
464
  }
465
+ function runtimeSettings(value) {
466
+ if (!value || typeof value !== "object" || Array.isArray(value))
467
+ return undefined;
468
+ const record = value;
469
+ const model = typeof record.model === "string" && record.model.trim()
470
+ ? record.model.trim()
471
+ : undefined;
472
+ const thinking = typeof record.thinking === "string" && record.thinking.trim()
473
+ ? record.thinking.trim()
474
+ : undefined;
475
+ return model || thinking ? { model, thinking } : undefined;
476
+ }
464
477
  function optionalDynamicString(value, field) {
465
478
  if (value === undefined)
466
479
  return undefined;
@@ -5,4 +5,3 @@ export declare const DYNAMIC_TERMINAL_OUTPUT_PROFILES: readonly ["synthesis_v1"]
5
5
  export declare function isDynamicOutputProfile(value: unknown): value is DynamicOutputProfile;
6
6
  export declare function isExtractableDynamicOutputProfile(value: unknown): value is (typeof DYNAMIC_EXTRACTABLE_OUTPUT_PROFILES)[number];
7
7
  export declare function isTerminalDynamicOutputProfile(value: unknown): value is (typeof DYNAMIC_TERMINAL_OUTPUT_PROFILES)[number];
8
- export declare function dynamicOutputProfileValues(): string[];
@@ -26,6 +26,3 @@ export function isExtractableDynamicOutputProfile(value) {
26
26
  export function isTerminalDynamicOutputProfile(value) {
27
27
  return typeof value === "string" && TERMINAL_OUTPUT_PROFILE_SET.has(value);
28
28
  }
29
- export function dynamicOutputProfileValues() {
30
- return [...DYNAMIC_OUTPUT_PROFILES];
31
- }
@@ -2,6 +2,7 @@ import type { CompiledTask, CompiledWorkflow, WorkflowRunRecord, WorkflowTaskRun
2
2
  export declare function reconcileLoopTaskRecordsInMemory(cwd: string, run: WorkflowRunRecord, compiledFlow: CompiledWorkflow, loopIds: Set<string>): boolean;
3
3
  export declare function recoverStaleRunningDynamicControllers(run: WorkflowRunRecord, compiledFlow: CompiledWorkflow): boolean;
4
4
  export declare function reconcileDynamicGeneratedRunRecords(cwd: string, run: WorkflowRunRecord, compiledFlow: CompiledWorkflow): boolean;
5
+ export declare function reconcileForeachGeneratedRunRecords(cwd: string, run: WorkflowRunRecord, compiledFlow: CompiledWorkflow): boolean;
5
6
  export declare function assertRunTaskPositionalAlignment(run: WorkflowRunRecord, compiledFlow: CompiledWorkflow): void;
6
7
  export declare function assertLoopTaskPositionalAlignment(run: WorkflowRunRecord, compiledFlow: CompiledWorkflow, loopIds?: Set<string>): void;
7
8
  export declare function upsertCompiledLoopTasksAtInsertion(compiledFlow: CompiledWorkflow, loopId: string, placeholderIndex: number, tasks: CompiledTask[]): void;
@@ -92,6 +92,107 @@ export function reconcileDynamicGeneratedRunRecords(cwd, run, compiledFlow) {
92
92
  }
93
93
  return changed;
94
94
  }
95
+ export function reconcileForeachGeneratedRunRecords(cwd, run, compiledFlow) {
96
+ let changed = false;
97
+ const compiledSpecIds = new Set(compiledFlow.tasks.map((task) => compiledTaskSpecId(task)));
98
+ const placeholderToGeneratedSpecIds = new Map();
99
+ for (const compiledTask of compiledFlow.tasks) {
100
+ const specId = compiledTaskSpecId(compiledTask);
101
+ const placeholderSpecId = foreachGeneratedPlaceholderSpecId(compiledTask, compiledFlow, specId);
102
+ if (!placeholderSpecId)
103
+ continue;
104
+ if (compiledTask.foreachGenerated?.placeholderSpecId !== placeholderSpecId) {
105
+ compiledTask.foreachGenerated = { placeholderSpecId };
106
+ changed = true;
107
+ }
108
+ const generated = placeholderToGeneratedSpecIds.get(placeholderSpecId) ?? [];
109
+ generated.push(specId);
110
+ placeholderToGeneratedSpecIds.set(placeholderSpecId, generated);
111
+ }
112
+ if (placeholderToGeneratedSpecIds.size === 0)
113
+ return changed;
114
+ const filteredRunTasks = [];
115
+ const seenGeneratedSpecIds = new Set();
116
+ for (const task of run.tasks) {
117
+ const generatedSpecIds = placeholderToGeneratedSpecIds.get(task.specId);
118
+ let placeholderSpecId = foreachGeneratedPlaceholderSpecId(task, compiledFlow, task.specId);
119
+ if (generatedSpecIds && !placeholderSpecId) {
120
+ if (generatedSpecIds.includes(task.specId)) {
121
+ placeholderSpecId = task.specId;
122
+ task.foreachGenerated = { placeholderSpecId };
123
+ changed = true;
124
+ }
125
+ else {
126
+ changed = true;
127
+ continue;
128
+ }
129
+ }
130
+ if (placeholderSpecId && !compiledSpecIds.has(task.specId)) {
131
+ changed = true;
132
+ continue;
133
+ }
134
+ if (placeholderSpecId && seenGeneratedSpecIds.has(task.specId)) {
135
+ changed = true;
136
+ continue;
137
+ }
138
+ if (placeholderSpecId) {
139
+ seenGeneratedSpecIds.add(task.specId);
140
+ if (task.foreachGenerated?.placeholderSpecId !== placeholderSpecId) {
141
+ task.foreachGenerated = { placeholderSpecId };
142
+ changed = true;
143
+ }
144
+ }
145
+ filteredRunTasks.push(task);
146
+ }
147
+ const runTaskBySpecId = new Map();
148
+ for (const task of filteredRunTasks) {
149
+ if (!runTaskBySpecId.has(task.specId))
150
+ runTaskBySpecId.set(task.specId, task);
151
+ }
152
+ const reordered = [];
153
+ const usedSpecIds = new Set();
154
+ let nextIndex = nextTaskRecordIndex({ ...run, tasks: filteredRunTasks });
155
+ for (const compiledTask of compiledFlow.tasks) {
156
+ const specId = compiledTaskSpecId(compiledTask);
157
+ const existing = runTaskBySpecId.get(specId);
158
+ if (existing) {
159
+ const placeholderSpecId = compiledTask.foreachGenerated?.placeholderSpecId;
160
+ if (placeholderSpecId &&
161
+ existing.foreachGenerated?.placeholderSpecId !== placeholderSpecId) {
162
+ existing.foreachGenerated = { placeholderSpecId };
163
+ changed = true;
164
+ }
165
+ reordered.push(existing);
166
+ usedSpecIds.add(specId);
167
+ continue;
168
+ }
169
+ if (!compiledTask.foreachGenerated)
170
+ continue;
171
+ const created = createTaskRunRecord(cwd, run.runId, compiledTask, nextIndex);
172
+ nextIndex += 1;
173
+ reordered.push(created);
174
+ usedSpecIds.add(specId);
175
+ changed = true;
176
+ }
177
+ for (const task of filteredRunTasks) {
178
+ if (!usedSpecIds.has(task.specId))
179
+ reordered.push(task);
180
+ }
181
+ if (!sameTaskRecordOrder(run.tasks, reordered))
182
+ changed = true;
183
+ for (const task of reordered) {
184
+ if (!task.dependsOn)
185
+ continue;
186
+ const replaced = replaceForeachGeneratedDependencies(task.dependsOn, placeholderToGeneratedSpecIds);
187
+ if (!sameStringList(task.dependsOn, replaced)) {
188
+ task.dependsOn = replaced;
189
+ changed = true;
190
+ }
191
+ }
192
+ if (changed)
193
+ run.tasks = reordered;
194
+ return changed;
195
+ }
95
196
  export function assertRunTaskPositionalAlignment(run, compiledFlow) {
96
197
  const maxLength = Math.max(run.tasks.length, compiledFlow.tasks.length);
97
198
  for (let index = 0; index < maxLength; index += 1) {
@@ -141,6 +242,40 @@ export function compiledTaskSpecId(task) {
141
242
  const specId = task.specId;
142
243
  return typeof specId === "string" && specId.trim() !== "" ? specId : task.id;
143
244
  }
245
+ function foreachGeneratedPlaceholderSpecId(task, compiledFlow, specId) {
246
+ const explicit = task.foreachGenerated?.placeholderSpecId;
247
+ if (typeof explicit === "string" && explicit.trim() !== "")
248
+ return explicit;
249
+ if (task.foreach)
250
+ return undefined;
251
+ if (task.kind !== "foreach" || !task.stageId)
252
+ return undefined;
253
+ const placeholderSpecId = foreachPlaceholderSpecId(compiledFlow, task.stageId);
254
+ if (!placeholderSpecId || specId === placeholderSpecId)
255
+ return undefined;
256
+ return placeholderSpecId;
257
+ }
258
+ function foreachPlaceholderSpecId(compiledFlow, stageId) {
259
+ const stage = (compiledFlow.stages ?? []).find((candidate) => candidate?.id === stageId);
260
+ if (stage?.type !== "foreach")
261
+ return undefined;
262
+ return `${stageId}.item`;
263
+ }
264
+ function replaceForeachGeneratedDependencies(dependsOn, placeholderToGeneratedSpecIds) {
265
+ const replaced = [];
266
+ for (const dep of dependsOn) {
267
+ const generatedSpecIds = placeholderToGeneratedSpecIds.get(dep);
268
+ if (generatedSpecIds)
269
+ replaced.push(...generatedSpecIds);
270
+ else
271
+ replaced.push(dep);
272
+ }
273
+ return [...new Set(replaced)];
274
+ }
275
+ function sameStringList(left, right) {
276
+ return (left.length === right.length &&
277
+ left.every((value, index) => value === right[index]));
278
+ }
144
279
  function isLoopGeneratedCompiledTask(task, loopIds) {
145
280
  return Boolean((task.loopChild?.loopId && loopIds.has(task.loopChild.loopId)) ||
146
281
  (task.loopExhausted?.loopId && loopIds.has(task.loopExhausted.loopId)));
@@ -228,7 +363,7 @@ export function buildForeachGeneratedTasks(template, runtimeTask, items) {
228
363
  seen.add(taskId);
229
364
  const specId = `${template.stageId}.${taskId}`;
230
365
  const itemText = formatForeachItem(item);
231
- const instructions = template.foreach.prompt.replace(/\$\{item\}/g, itemText);
366
+ const instructions = template.foreach.prompt.replace(/\$\{item\}/g, escapeReplacementText(itemText));
232
367
  const compiledPrompt = [
233
368
  template.foreach.injectRuntimeTask && runtimeTask
234
369
  ? `# Task\n\n${runtimeTask}`
@@ -249,6 +384,7 @@ export function buildForeachGeneratedTasks(template, runtimeTask, items) {
249
384
  compiledPrompt,
250
385
  dependsOn: [...(template.dependsOn ?? [])],
251
386
  foreach: undefined,
387
+ foreachGenerated: { placeholderSpecId: template.id },
252
388
  });
253
389
  }
254
390
  return { tasks };
@@ -274,6 +410,9 @@ export function sanitizeTaskId(value) {
274
410
  function formatForeachItem(item) {
275
411
  return typeof item === "string" ? item : JSON.stringify(item);
276
412
  }
413
+ function escapeReplacementText(value) {
414
+ return value.replace(/\$/g, "$$$$");
415
+ }
277
416
  export function sourceStageIdsForFrom(from) {
278
417
  if (Array.isArray(from))
279
418
  return from.filter((item) => typeof item === "string");
@@ -321,7 +460,8 @@ export function markDagDependentsSkipped(run, compiledFlow) {
321
460
  const status = bySpecId.get(dep)?.status;
322
461
  return (status === "failed" ||
323
462
  status === "interrupted" ||
324
- status === "skipped");
463
+ status === "skipped" ||
464
+ status === "blocked");
325
465
  });
326
466
  if (!failedDep)
327
467
  continue;
package/dist/engine.d.ts CHANGED
@@ -1,15 +1,13 @@
1
- import { type WorkflowModelInfo } from "./workflow-runtime.js";
1
+ import { type WorkflowModelInfo, type WorkflowRuntimeDefaults } from "./workflow-runtime.js";
2
2
  import { type DynamicWorkflowUi } from "./dynamic-controller-policy.js";
3
- import { type CompiledWorkflow, type ThinkingLevel, type WorkflowRunRecord } from "./types.js";
3
+ import { type CompiledWorkflow, type WorkflowRunRecord } from "./types.js";
4
4
  export { buildRunSourceContext } from "./workflow-source-context-runtime.js";
5
5
  export { evaluateLoopUntilCondition } from "./loop-runtime.js";
6
6
  export type { DynamicWorkflowUi } from "./dynamic-controller-policy.js";
7
7
  export interface WorkflowRunOptions {
8
8
  task?: string;
9
- runtimeDefaults?: {
10
- model?: string;
11
- thinking?: ThinkingLevel;
12
- };
9
+ runtimeOverrides?: WorkflowRuntimeDefaults;
10
+ runtimeDefaults?: WorkflowRuntimeDefaults;
13
11
  availableModels?: WorkflowModelInfo[];
14
12
  dynamicUi?: DynamicWorkflowUi;
15
13
  runId?: string;
@@ -17,6 +15,7 @@ export interface WorkflowRunOptions {
17
15
  }
18
16
  interface WorkflowScheduleOptions {
19
17
  dynamicUi?: DynamicWorkflowUi;
18
+ availableModels?: WorkflowModelInfo[];
20
19
  }
21
20
  export declare function runWorkflowSpec(specPath: string, cwd: string, options?: WorkflowRunOptions): Promise<WorkflowRunRecord>;
22
21
  export declare function runDynamicTask(cwd: string, options?: WorkflowRunOptions): Promise<WorkflowRunRecord>;
@@ -26,6 +25,11 @@ export interface ResumeRunSummary {
26
25
  run: WorkflowRunRecord;
27
26
  resetTaskIds: string[];
28
27
  }
28
+ export interface StopRunSummary {
29
+ run: WorkflowRunRecord;
30
+ interruptedTaskIds: string[];
31
+ }
32
+ export declare function stopRun(cwd: string, runIdOrPrefix: string): Promise<StopRunSummary>;
29
33
  export declare function resumeRun(cwd: string, runIdOrPrefix: string, options?: WorkflowScheduleOptions): Promise<ResumeRunSummary>;
30
34
  export declare function resumeSupervisors(cwd: string, options?: WorkflowScheduleOptions): Promise<void>;
31
35
  export declare function watchRun(cwd: string, runId: string, options?: WorkflowScheduleOptions): void;