@agwab/pi-workflow 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
@@ -4,7 +4,7 @@ import { loadAgentByName } from "./agents.js";
4
4
  import { DYNAMIC_OUTPUT_PROFILES } from "./dynamic-profiles.js";
5
5
  import { classifyToolCapability, effectiveToolClassification, providersForSelectedTools, resolveToolSelection, TOOL_NAME_PATTERN, toolAllowedByAuthorityCeiling, toolNameForSpec, } from "./tool-metadata.js";
6
6
  import { WorkflowValidationError, WORKFLOW_RUN_TYPE, } from "./types.js";
7
- import { resolveWorkflowRuntime, } from "./workflow-runtime.js";
7
+ import { resolveWorkflowRuntime, selectWorkflowRuntime, } from "./workflow-runtime.js";
8
8
  const DELEGATION_TOOLS = new Set([
9
9
  "skill_test_subagent",
10
10
  "workflow",
@@ -413,6 +413,17 @@ async function collectForeachPathWarnings(stages, specDir) {
413
413
  }
414
414
  return warnings;
415
415
  }
416
+ function runtimeSettings(value) {
417
+ if (!isPlainRecord(value))
418
+ return undefined;
419
+ const model = typeof value.model === "string" && value.model.trim()
420
+ ? value.model.trim()
421
+ : undefined;
422
+ const thinking = typeof value.thinking === "string" && value.thinking.trim()
423
+ ? value.thinking.trim()
424
+ : undefined;
425
+ return model || thinking ? { model, thinking } : undefined;
426
+ }
416
427
  async function compileArtifactGraphPlan(spec, options) {
417
428
  const stages = spec.stages;
418
429
  if (!Array.isArray(stages)) {
@@ -450,8 +461,9 @@ async function compileArtifactGraphPlan(spec, options) {
450
461
  Object.keys(workflowInput).length > 0
451
462
  ? `# Workflow Input\n\n${JSON.stringify(workflowInput, null, 2)}`
452
463
  : "";
453
- const defaultModel = options.runtimeDefaults?.model ?? spec.defaults?.model;
454
- const defaultThinking = options.runtimeDefaults?.thinking ?? spec.defaults?.thinking;
464
+ const runtimeOverrides = options.runtimeOverrides;
465
+ const runtimeDefaults = options.runtimeDefaults;
466
+ const specRuntimeDefaults = runtimeSettings(spec.defaults);
455
467
  const tasks = [];
456
468
  const stageRecords = [];
457
469
  const issues = [];
@@ -475,17 +487,26 @@ async function compileArtifactGraphPlan(spec, options) {
475
487
  : `$.artifactGraph.stages.${jsonKey(stage.id)}.dynamic`;
476
488
  validateDelegationBoundary(rawDynamicToolSelection.tools, issues, dynamicToolPath);
477
489
  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 }, {
490
+ const requestedRuntime = selectWorkflowRuntime(runtimeOverrides, runtimeSettings(stage), runtimeDefaults, specRuntimeDefaults);
491
+ const resolvedDynamicRuntime = await resolveWorkflowRuntime(requestedRuntime, {
480
492
  taskKey: key,
481
493
  stageId: stage.id,
482
494
  taskId,
483
495
  agent: "dynamic",
484
496
  }, { availableModels: options.availableModels });
497
+ const dynamicTask = buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, options.cwd, specDir, workflowInputText, options.task, resolvedDynamicRuntime, {
498
+ runtimeOverrides,
499
+ runtimeDefaults,
500
+ specRuntimeDefaults,
501
+ stageRuntime: runtimeSettings(stage),
502
+ }, overrides);
485
503
  dynamicTask.runtime = {
486
504
  ...dynamicTask.runtime,
487
505
  ...resolvedDynamicRuntime,
488
506
  };
507
+ if (options.availableModels?.length) {
508
+ dynamicTask.dynamic.availableModels = options.availableModels;
509
+ }
489
510
  if (dynamicToolSelection.tools || dynamicToolSelection.toolProviders) {
490
511
  dynamicTask.runtime = {
491
512
  ...dynamicTask.runtime,
@@ -539,13 +560,7 @@ async function compileArtifactGraphPlan(spec, options) {
539
560
  validateToolSubset(toolSelection.tools, stageAgent, issues, toolPath);
540
561
  validateDelegationBoundary(toolSelection.tools, issues, toolPath);
541
562
  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
- };
563
+ const requestedRuntime = selectWorkflowRuntime(runtimeOverrides, runtimeSettings(stage), runtimeDefaults, specRuntimeDefaults);
549
564
  const resolvedRuntime = await resolveWorkflowRuntime(requestedRuntime, {
550
565
  taskKey: key,
551
566
  stageId: stage.id,
@@ -825,11 +840,33 @@ async function compileArtifactGraphPlan(spec, options) {
825
840
  tasks,
826
841
  warnings,
827
842
  budget: {
828
- models: defaultModel ? [{ model: defaultModel }] : [],
843
+ models: budgetModelRows(tasks),
829
844
  unratedModels: [],
830
845
  },
831
846
  };
832
847
  }
848
+ function budgetModelRows(tasks) {
849
+ const models = new Set();
850
+ for (const task of tasks) {
851
+ if (typeof task?.runtime?.model === "string" && task.runtime.model.trim()) {
852
+ models.add(task.runtime.model.trim());
853
+ }
854
+ const loop = task?.dynamic?.decisionLoop;
855
+ if (!loop || typeof loop !== "object")
856
+ continue;
857
+ for (const profile of [
858
+ loop.planner,
859
+ loop.workerDefaults,
860
+ loop.verifier,
861
+ loop.synthesis,
862
+ ]) {
863
+ if (typeof profile?.model === "string" && profile.model.trim()) {
864
+ models.add(profile.model.trim());
865
+ }
866
+ }
867
+ }
868
+ return [...models].sort().map((model) => ({ model }));
869
+ }
833
870
  function isSupportStage(stage) {
834
871
  return stage?.support !== undefined && stage?.type === undefined;
835
872
  }
@@ -889,7 +926,7 @@ function buildSupportTask(stage, taskId, key, prompt, dependencyKeys, cwd, workf
889
926
  ...overrides,
890
927
  };
891
928
  }
892
- function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specDir, workflowInputText, runtimeTask, defaultModel, defaultThinking, overrides) {
929
+ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specDir, workflowInputText, runtimeTask, controllerRuntime, runtimePriority, overrides) {
893
930
  const dynamic = stage.dynamic ?? {};
894
931
  const uses = String(dynamic.uses);
895
932
  const normalizedPrompt = String(prompt ?? "").replace(/\$\{item\}/g, "the relevant item from the dependency context");
@@ -942,7 +979,7 @@ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specD
942
979
  usesPath: resolve(specDir, String(workflow.uses)),
943
980
  };
944
981
  }
945
- const decisionLoop = compileDynamicDecisionLoop(dynamic.decisionLoop, defaultModel, defaultThinking);
982
+ const decisionLoop = compileDynamicDecisionLoop(dynamic.decisionLoop, runtimePriority);
946
983
  return {
947
984
  key,
948
985
  id: key,
@@ -960,8 +997,7 @@ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specD
960
997
  explicitWorktreePolicy: false,
961
998
  runtime: {
962
999
  approvalMode: "non-interactive",
963
- model: defaultModel,
964
- thinking: defaultThinking,
1000
+ ...controllerRuntime,
965
1001
  maxRuntimeMs: dynamic.budget?.maxRuntimeMs ?? DEFAULT_DYNAMIC_MAX_RUNTIME_MS,
966
1002
  },
967
1003
  safety: {
@@ -999,21 +1035,24 @@ function buildDynamicTask(stage, taskId, key, prompt, dependencyKeys, cwd, specD
999
1035
  helpers,
1000
1036
  workflows,
1001
1037
  ...(decisionLoop ? { decisionLoop } : {}),
1038
+ ...(runtimePriority.runtimeOverrides
1039
+ ? { runtimeOverrides: runtimePriority.runtimeOverrides }
1040
+ : {}),
1002
1041
  },
1003
1042
  ...overrides,
1004
1043
  };
1005
1044
  }
1006
- function compileDynamicDecisionLoop(value, defaultModel, defaultThinking) {
1045
+ function compileDynamicDecisionLoop(value, runtimePriority) {
1007
1046
  if (!isPlainRecord(value))
1008
1047
  return undefined;
1009
1048
  const allowedToolSelection = filterToolSelection(resolveToolSelection([Array.isArray(value.allowedTools) ? value.allowedTools : undefined], undefined));
1010
1049
  const maxFindings = positiveInteger(recordValue(value.stateIndex, "maxFindings"));
1011
1050
  const deprecatedRequiredFindingIds = stringArray(recordValue(value.stateIndex, "requiredFindingIds"));
1012
1051
  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),
1052
+ planner: compileDynamicDecisionLoopProfile(value.planner, runtimePriority),
1053
+ workerDefaults: compileDynamicDecisionLoopProfile(value.workerDefaults, runtimePriority),
1054
+ verifier: compileDynamicDecisionLoopProfile(value.verifier, runtimePriority),
1055
+ synthesis: compileDynamicDecisionLoopProfile(value.synthesis, runtimePriority),
1017
1056
  allowedAgents: stringArray(value.allowedAgents),
1018
1057
  ...(allowedToolSelection.tools
1019
1058
  ? { allowedTools: allowedToolSelection.tools }
@@ -1054,22 +1093,16 @@ function compileDynamicDecisionLoop(value, defaultModel, defaultThinking) {
1054
1093
  },
1055
1094
  };
1056
1095
  }
1057
- function compileDynamicDecisionLoopProfile(value, defaultModel, defaultThinking) {
1096
+ function compileDynamicDecisionLoopProfile(value, runtimePriority) {
1058
1097
  if (!isPlainRecord(value))
1059
1098
  return undefined;
1060
1099
  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;
1100
+ const runtime = selectWorkflowRuntime(runtimePriority.runtimeOverrides, runtimeSettings(value), runtimePriority.stageRuntime, runtimePriority.runtimeDefaults, runtimePriority.specRuntimeDefaults);
1067
1101
  return {
1068
1102
  ...(typeof value.agent === "string" && value.agent.trim()
1069
1103
  ? { agent: value.agent.trim() }
1070
1104
  : {}),
1071
- ...(model ? { model } : {}),
1072
- ...(thinking ? { thinking } : {}),
1105
+ ...runtime,
1073
1106
  ...(toolSelection.tools ? { tools: toolSelection.tools } : {}),
1074
1107
  ...(toolSelection.toolProviders
1075
1108
  ? { toolProviders: toolSelection.toolProviders }
@@ -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;
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>;
package/dist/engine.js CHANGED
@@ -1,10 +1,10 @@
1
- import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
1
+ import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, extname, join, resolve } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
  import { Worker } from "node:worker_threads";
5
5
  import { compileWorkflow } from "./compiler.js";
6
6
  import { loadWorkflowSpec } from "./schema.js";
7
- import { createRunRecord, createTaskRunRecord, compiledWorkflowPath, fromProjectPath, indexSupervisorErrorPath, isTerminalWorkflowStatus, isTerminalTaskStatus, listRunRecords, readIndex, readJson, readRunRecord, resetTaskForResume, setTaskTerminal, supervisorPath, toProjectPath, updateIndex, withRunLease, writeJsonAtomic, writeRunRecord, writeCompiledRunArtifact, writeStaticRunArtifacts, } from "./store.js";
7
+ import { createRunRecord, createTaskRunRecord, compiledWorkflowPath, fromProjectPath, indexSupervisorErrorPath, isTerminalWorkflowStatus, isTerminalTaskStatus, listRunRecords, readIndex, readJson, readRunRecord, resetTaskForResume, setTaskTerminal, supervisorPath, toProjectPath, updateIndex, withRunLease, workflowRunPath, writeJsonAtomic, writeRunRecord, writeCompiledRunArtifact, writeStaticRunArtifacts, } from "./store.js";
8
8
  import { resolveWorkflowBackend } from "./backend.js";
9
9
  import { ensureManagedWorktree } from "./worktree.js";
10
10
  import { resolveWorkflowHelperRef } from "./workflow-helpers.js";
@@ -21,7 +21,6 @@ import { normalizeDynamicFanoutPlanRequest, runDynamicDecisionLoopStatusPersistC
21
21
  import { assertRunTaskPositionalAlignment, buildForeachGeneratedTasks, dependenciesReady, markDagDependentsSkipped, nextTaskRecordIndex, reconcileDynamicGeneratedRunRecords, recoverStaleRunningDynamicControllers, replaceDependencyList, sourceStageIdsForFrom, stageSourcePolicy, updateDownstreamDependencies, } from "./engine-run-graph.js";
22
22
  import { reconcileLoopTaskMaterialization, scheduleLoop, } from "./loop-runtime.js";
23
23
  import { executeSupportTask, normalizeDynamicControllerOutput, prepareArtifactGraphRetryTask, prepareDagTask, readArtifactGraphControl, readArtifactGraphSupportSources, readSupportSources, writeArtifactGraphDynamicResult, } from "./artifact-graph-runtime.js";
24
- import { isDynamicOutputProfile, } from "./dynamic-profiles.js";
25
24
  import { DIRECT_DYNAMIC_RUNTIME_VERSION, ensureDirectDynamicRuntimeBundle, } from "./dynamic-runtime-bundle.js";
26
25
  import { WORKFLOW_RUN_TYPE, } from "./types.js";
27
26
  export { buildRunSourceContext } from "./workflow-source-context-runtime.js";
@@ -37,6 +36,7 @@ const DYNAMIC_CONTROLLER_ENGINE_CAPABILITIES = Object.freeze({
37
36
  });
38
37
  const DYNAMIC_CONTROLLER_ENGINE_INTEGRITY_ERROR_MESSAGE = "incompatible or stale pi-workflow engine: dynamic controller context is missing runDecisionLoop (rebuild dist / reload workflow engine)";
39
38
  const supervisorTimers = new Map();
39
+ const supervisorRunMtimes = new Map();
40
40
  export async function runWorkflowSpec(specPath, cwd, options = {}) {
41
41
  const loaded = await loadWorkflowSpec(specPath, cwd);
42
42
  return runLoadedWorkflowSpec(cwd, loaded.specPath, loaded.spec, options);
@@ -62,6 +62,7 @@ async function runLoadedWorkflowSpec(cwd, specPath, spec, options, provenance) {
62
62
  cwd,
63
63
  specPath,
64
64
  task: options.task,
65
+ runtimeOverrides: options.runtimeOverrides,
65
66
  runtimeDefaults: options.runtimeDefaults,
66
67
  availableModels: options.availableModels,
67
68
  });
@@ -76,11 +77,14 @@ async function runLoadedWorkflowSpec(cwd, specPath, spec, options, provenance) {
76
77
  await writeStaticRunArtifacts(cwd, run, compiled, spec);
77
78
  await writeRunRecord(cwd, run);
78
79
  });
79
- const scheduled = (await scheduleRun(cwd, run.runId, compiled, {
80
+ const scheduleOptions = {
80
81
  dynamicUi: options.dynamicUi,
81
- })) ?? (await readRunRecord(cwd, run.runId));
82
+ availableModels: options.availableModels,
83
+ };
84
+ const scheduled = (await scheduleRun(cwd, run.runId, compiled, scheduleOptions)) ??
85
+ (await readRunRecord(cwd, run.runId));
82
86
  if (scheduled.status === "running")
83
- watchRun(cwd, scheduled.runId, { dynamicUi: options.dynamicUi });
87
+ watchRun(cwd, scheduled.runId, scheduleOptions);
84
88
  return scheduled;
85
89
  }
86
90
  export async function refreshRun(cwd, runIdOrPrefix) {
@@ -168,15 +172,26 @@ export function watchRun(cwd, runId, options = {}) {
168
172
  return;
169
173
  const timer = setInterval(() => {
170
174
  void (async () => {
175
+ const previousMtime = supervisorRunMtimes.get(key);
176
+ const beforeMtime = await readRunMtimeMs(cwd, runId);
171
177
  const refreshed = await refreshRun(cwd, runId);
178
+ const afterMtime = await readRunMtimeMs(cwd, runId);
179
+ const currentMtime = afterMtime ?? beforeMtime;
180
+ if (currentMtime !== undefined)
181
+ supervisorRunMtimes.set(key, currentMtime);
172
182
  if (refreshed.status === "running") {
173
- await scheduleRun(cwd, runId, undefined, options);
183
+ const unchanged = previousMtime !== undefined &&
184
+ currentMtime !== undefined &&
185
+ currentMtime <= previousMtime;
186
+ if (!unchanged)
187
+ await scheduleRun(cwd, runId, undefined, options);
174
188
  return;
175
189
  }
176
190
  const existing = supervisorTimers.get(key);
177
191
  if (existing)
178
192
  clearInterval(existing);
179
193
  supervisorTimers.delete(key);
194
+ supervisorRunMtimes.delete(key);
180
195
  })().catch((error) => {
181
196
  void recordSupervisorError(cwd, runId, error);
182
197
  });
@@ -184,6 +199,16 @@ export function watchRun(cwd, runId, options = {}) {
184
199
  timer.unref?.();
185
200
  supervisorTimers.set(key, timer);
186
201
  }
202
+ async function readRunMtimeMs(cwd, runId) {
203
+ try {
204
+ return (await stat(workflowRunPath(cwd, runId))).mtimeMs;
205
+ }
206
+ catch (error) {
207
+ if (error.code === "ENOENT")
208
+ return undefined;
209
+ throw error;
210
+ }
211
+ }
187
212
  export async function scheduleRun(cwd, runId, compiled, options = {}) {
188
213
  return withRunLease(cwd, runId, async () => {
189
214
  let run = await readRunRecord(cwd, runId);
@@ -630,6 +655,7 @@ async function executeDynamicControllerTask(cwd, run, compiledFlow, controllerIn
630
655
  sources,
631
656
  dynamic: compiledTask.dynamic,
632
657
  dynamicUi: options.dynamicUi,
658
+ availableModels: options.availableModels,
633
659
  });
634
660
  await assertDynamicGeneratedTasksSettled({
635
661
  cwd,
@@ -639,6 +665,7 @@ async function executeDynamicControllerTask(cwd, run, compiledFlow, controllerIn
639
665
  controllerTask: task,
640
666
  controllerCompiledTask: compiledTask,
641
667
  dynamic: compiledTask.dynamic,
668
+ availableModels: options.availableModels,
642
669
  });
643
670
  await recordActiveRuntime();
644
671
  const unrunBranchBlockers = await dynamicUnrunBranchBlockers(cwd, run.runId, task.specId);
@@ -1171,57 +1198,12 @@ function requiredDynamicString(value, field, api = "ctx.agent()") {
1171
1198
  }
1172
1199
  return value.trim();
1173
1200
  }
1174
- function optionalDynamicString(value, field) {
1175
- if (value === undefined)
1176
- return undefined;
1177
- return requiredDynamicString(value, field);
1178
- }
1179
- function optionalDynamicStringArray(value, field) {
1180
- if (value === undefined)
1181
- return undefined;
1182
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1183
- throw new Error(`ctx.agent() ${field} must be an array of strings`);
1184
- }
1185
- return value.map((item) => item.trim()).filter(Boolean);
1186
- }
1187
- function isPlainDynamicRecord(value) {
1188
- return typeof value === "object" && value !== null && !Array.isArray(value);
1189
- }
1190
- function optionalDynamicPositiveInteger(value, field) {
1191
- if (value === undefined)
1192
- return undefined;
1193
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
1194
- throw new Error(`ctx.agent() ${field} must be a positive integer`);
1195
- }
1196
- return value;
1197
- }
1198
- function requiredDynamicOutputProfile(value, field, api) {
1199
- const profile = requiredDynamicString(value, field, api);
1200
- if (!isDynamicOutputProfile(profile)) {
1201
- throw new Error(`${api} ${field} has an unsupported output profile`);
1202
- }
1203
- return profile;
1204
- }
1205
- function requiredDynamicNonNegativeInteger(value, field, api) {
1206
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
1207
- throw new Error(`${api} ${field} must be a non-negative integer`);
1208
- }
1209
- return value;
1210
- }
1211
1201
  function requiredDynamicPositiveInteger(value, field, api) {
1212
1202
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
1213
1203
  throw new Error(`${api} ${field} must be a positive integer`);
1214
1204
  }
1215
1205
  return value;
1216
1206
  }
1217
- function optionalDynamicStringField(value) {
1218
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
1219
- }
1220
- function optionalDynamicOutputProfile(value) {
1221
- if (value === undefined)
1222
- return undefined;
1223
- return requiredDynamicOutputProfile(value, "outputProfile", "ctx.agent()");
1224
- }
1225
1207
  async function currentDynamicBudgetRemaining(input) {
1226
1208
  const state = await readOrRebuildDynamicState(input.cwd, input.run.runId);
1227
1209
  const run = await readRunRecord(input.cwd, input.run.runId).catch(() => input.run);
@@ -1540,6 +1522,7 @@ async function repairMissingDynamicGeneratedTask(input, specId) {
1540
1522
  branchId: optionalEventString(event.payload.branchId),
1541
1523
  request,
1542
1524
  dynamic: input.dynamic,
1525
+ availableModels: input.availableModels,
1543
1526
  });
1544
1527
  assertDynamicGeneratedMetadataMatches(compiledTask, {
1545
1528
  controllerSpecId: input.controllerTask.specId,
@@ -1635,6 +1618,7 @@ async function runDynamicAgentRequest(input) {
1635
1618
  branchId: generationBranchId,
1636
1619
  request: generationRequest,
1637
1620
  dynamic: input.dynamic,
1621
+ availableModels: input.availableModels,
1638
1622
  });
1639
1623
  assertDynamicGeneratedMetadataMatches(compiledTask, {
1640
1624
  controllerSpecId: input.controllerTask.specId,
package/dist/extension.js CHANGED
@@ -13,7 +13,7 @@ import { fromProjectPath, readIndex, readRunRecord } from "./store.js";
13
13
  import { loadWorkflowSpec } from "./schema.js";
14
14
  import { listWorkflows, resolveWorkflowRef } from "./workflow-specs.js";
15
15
  import { WorkflowValidationError, } from "./types.js";
16
- import { toWorkflowModelInfo } from "./workflow-runtime.js";
16
+ import { toWorkflowModelInfo, } from "./workflow-runtime.js";
17
17
  const UNFINISHED_RUN_NOTICE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
18
18
  const UNFINISHED_RUN_NOTICE_MAX_RUNS = 5;
19
19
  const UNFINISHED_RUN_NOTICE_DEDUPE_MS = 6 * 60 * 60 * 1000;
@@ -442,8 +442,8 @@ function parseWorkflowDynamicToolParams(params) {
442
442
  const model = optionalStringParam(params, "model", "workflow_dynamic")?.trim();
443
443
  const rawThinking = optionalStringParam(params, "thinking", "workflow_dynamic")?.trim();
444
444
  const thinking = rawThinking ? parseThinkingLevel(rawThinking) : undefined;
445
- const runtimeDefaults = model || thinking ? { model: model || undefined, thinking } : undefined;
446
- return { task, detach: detachValue === true, runtimeDefaults };
445
+ const runtimeOverrides = model || thinking ? { model: model || undefined, thinking } : undefined;
446
+ return { task, detach: detachValue === true, runtimeOverrides };
447
447
  }
448
448
  function stringParam(params, key, toolName) {
449
449
  const value = params[key];
@@ -521,7 +521,8 @@ async function startWorkflowRunFromRequest(request, ctx, api) {
521
521
  throw new Error('This workflow needs a task. Usage: /workflow run <workflow-name-or-path> "<task>"');
522
522
  const run = await runWorkflowSpec(workflow, ctx.cwd, {
523
523
  task,
524
- runtimeDefaults: request.runtimeDefaults ?? currentRuntimeDefaults(ctx, api),
524
+ runtimeOverrides: request.runtimeOverrides,
525
+ runtimeDefaults: currentRuntimeDefaults(ctx, api),
525
526
  availableModels: availableWorkflowModels(ctx),
526
527
  dynamicUi: dynamicUiFromContext(ctx),
527
528
  });
@@ -544,7 +545,8 @@ async function startDynamicRunFromRequest(request, ctx, api) {
544
545
  throw new Error('This dynamic workflow needs a task. Usage: /workflow dynamic "<task>"');
545
546
  const run = await runDynamicTask(ctx.cwd, {
546
547
  task,
547
- runtimeDefaults: request.runtimeDefaults ?? currentRuntimeDefaults(ctx, api),
548
+ runtimeOverrides: request.runtimeOverrides,
549
+ runtimeDefaults: currentRuntimeDefaults(ctx, api),
548
550
  availableModels: availableWorkflowModels(ctx),
549
551
  dynamicUi: dynamicUiFromContext(ctx),
550
552
  });
@@ -763,27 +765,27 @@ async function handleWorkflowCommand(args, ctx, api) {
763
765
  const parsed = parseWorkflowRunArgs(args);
764
766
  const specPath = parsed.specPath ||
765
767
  requireArg(tokens, 1, '/workflow run <workflow-name-or-path> "<task>"');
766
- const runtimeDefaults = parsed.model || parsed.thinking
768
+ const runtimeOverrides = parsed.model || parsed.thinking
767
769
  ? { model: parsed.model, thinking: parsed.thinking }
768
770
  : undefined;
769
771
  const result = await startWorkflowRunFromRequest({
770
772
  workflow: specPath,
771
773
  task: parsed.task,
772
774
  detach: parsed.detach,
773
- runtimeDefaults,
775
+ runtimeOverrides,
774
776
  }, ctx, api);
775
777
  emitRunStartResult(ctx, result.run.status, result.text);
776
778
  return;
777
779
  }
778
780
  if (action === "dynamic") {
779
781
  const parsed = parseWorkflowDynamicArgs(args);
780
- const runtimeDefaults = parsed.model || parsed.thinking
782
+ const runtimeOverrides = parsed.model || parsed.thinking
781
783
  ? { model: parsed.model, thinking: parsed.thinking }
782
784
  : undefined;
783
785
  const result = await startDynamicRunFromRequest({
784
786
  task: parsed.task,
785
787
  detach: parsed.detach,
786
- runtimeDefaults,
788
+ runtimeOverrides,
787
789
  }, ctx, api);
788
790
  emitRunStartResult(ctx, result.run.status, result.text);
789
791
  return;
package/dist/store.d.ts CHANGED
@@ -25,13 +25,15 @@ export declare function createRunRecord(cwd: string, compiled: CompiledWorkflow,
25
25
  runDir: string;
26
26
  }>;
27
27
  export declare function writeRunRecord(cwd: string, run: WorkflowRunRecord): Promise<void>;
28
+ export declare function flushPendingIndexUpdatesForTests(): Promise<void>;
29
+ export declare function setIndexUpdateDebounceMsForTests(value?: number): void;
28
30
  export declare function writeCompiledRunArtifact(cwd: string, runId: string, compiled: CompiledWorkflow): Promise<void>;
29
31
  export declare function writeStaticRunArtifacts(cwd: string, run: WorkflowRunRecord, compiled: CompiledWorkflow, originalSpec: unknown): Promise<void>;
30
32
  export declare function findRunRecordPath(cwd: string, runIdOrPrefix: string): Promise<string | undefined>;
31
33
  export declare function readRunRecord(cwd: string, runIdOrPrefix: string): Promise<WorkflowRunRecord>;
32
34
  export declare function readIndex(cwd: string): Promise<WorkflowIndexRecord | undefined>;
33
35
  export declare function listRunRecords(cwd: string): Promise<WorkflowRunRecord[]>;
34
- export declare function updateIndex(cwd: string): Promise<WorkflowIndexRecord>;
36
+ export declare function updateIndex(cwd: string, changedRunId?: string): Promise<WorkflowIndexRecord>;
35
37
  export declare function deriveRunStatus(run: WorkflowRunRecord): WorkflowRunRecord;
36
38
  export declare function summarizeTasks(tasks: WorkflowTaskRunRecord[]): TaskSummary;
37
39
  export declare function deriveWorkflowStatus(summary: TaskSummary): WorkflowRunStatus;