@agwab/pi-workflow 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +9 -13
  2. package/dist/compiler.d.ts +5 -5
  3. package/dist/compiler.js +82 -24
  4. package/dist/dynamic-generated-task-runtime.d.ts +2 -0
  5. package/dist/dynamic-generated-task-runtime.js +21 -8
  6. package/dist/engine.d.ts +6 -5
  7. package/dist/engine.js +39 -54
  8. package/dist/extension.js +211 -24
  9. package/dist/store.d.ts +3 -1
  10. package/dist/store.js +135 -38
  11. package/dist/subagent-backend.d.ts +4 -0
  12. package/dist/subagent-backend.js +128 -4
  13. package/dist/types.d.ts +5 -0
  14. package/dist/workflow-progress-health.d.ts +37 -0
  15. package/dist/workflow-progress-health.js +296 -0
  16. package/dist/workflow-runtime.d.ts +8 -0
  17. package/dist/workflow-runtime.js +63 -10
  18. package/dist/workflow-view.d.ts +2 -0
  19. package/dist/workflow-view.js +97 -18
  20. package/dist/workflow-web-source.js +32 -14
  21. package/docs/usage.md +12 -1
  22. package/package.json +6 -6
  23. package/src/compiler.ts +136 -41
  24. package/src/dynamic-generated-task-runtime.ts +47 -12
  25. package/src/engine.ts +55 -100
  26. package/src/extension.ts +270 -34
  27. package/src/store.ts +180 -44
  28. package/src/subagent-backend.ts +170 -6
  29. package/src/types.ts +10 -0
  30. package/src/workflow-progress-health.ts +461 -0
  31. package/src/workflow-runtime.ts +85 -13
  32. package/src/workflow-view.ts +186 -41
  33. package/src/workflow-web-source.ts +192 -69
  34. package/workflows/deep-research/helpers/claim-evidence-gate.mjs +111 -37
  35. package/workflows/deep-research/helpers/final-audit-packet.mjs +191 -14
  36. package/workflows/deep-research/helpers/normalize-input-packet.mjs +159 -50
  37. package/workflows/deep-research/helpers/render-executive.mjs +671 -37
  38. package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +624 -0
  39. package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +2 -0
  40. package/workflows/deep-research/schemas/deep-research-final-synthesis-control.schema.json +110 -0
  41. package/workflows/deep-research/spec.json +41 -11
package/src/engine.ts CHANGED
@@ -1,11 +1,5 @@
1
- import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
2
- import {
3
- dirname,
4
- extname,
5
- join,
6
- relative,
7
- resolve,
8
- } from "node:path";
1
+ import { appendFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
2
+ import { dirname, extname, join, resolve } from "node:path";
9
3
  import { fileURLToPath, pathToFileURL } from "node:url";
10
4
  import { Worker } from "node:worker_threads";
11
5
 
@@ -29,7 +23,7 @@ import {
29
23
  toProjectPath,
30
24
  updateIndex,
31
25
  withRunLease,
32
- workflowRunDir,
26
+ workflowRunPath,
33
27
  writeJsonAtomic,
34
28
  writeRunRecord,
35
29
  writeCompiledRunArtifact,
@@ -43,7 +37,11 @@ import {
43
37
  workflowBundleFingerprint,
44
38
  workflowBundleSpecPath,
45
39
  } from "./workflow-source-context-runtime.js";
46
- import { readSimpleJsonPath } from "./workflow-runtime.js";
40
+ import {
41
+ readSimpleJsonPath,
42
+ type WorkflowModelInfo,
43
+ type WorkflowRuntimeDefaults,
44
+ } from "./workflow-runtime.js";
47
45
  import {
48
46
  dynamicRunDir,
49
47
  hashDynamicRequest,
@@ -77,7 +75,6 @@ import {
77
75
  isDynamicCompiledTaskPayload,
78
76
  normalizeDynamicAgentRequest,
79
77
  readDynamicGeneratedTaskResult,
80
- type DynamicAgentRequest,
81
78
  } from "./dynamic-generated-task-runtime.js";
82
79
  import {
83
80
  optionalEventString,
@@ -119,10 +116,6 @@ import {
119
116
  readSupportSources,
120
117
  writeArtifactGraphDynamicResult,
121
118
  } from "./artifact-graph-runtime.js";
122
- import {
123
- isDynamicOutputProfile,
124
- type DynamicOutputProfile,
125
- } from "./dynamic-profiles.js";
126
119
  import {
127
120
  DIRECT_DYNAMIC_RUNTIME_VERSION,
128
121
  ensureDirectDynamicRuntimeBundle,
@@ -131,7 +124,6 @@ import {
131
124
  type CompiledDynamicWorkflowTask,
132
125
  type CompiledTask,
133
126
  type CompiledWorkflow,
134
- type ThinkingLevel,
135
127
  WORKFLOW_RUN_TYPE,
136
128
  type WorkflowIndexRecord,
137
129
  type WorkflowRunRecord,
@@ -154,10 +146,13 @@ const DYNAMIC_CONTROLLER_ENGINE_CAPABILITIES = Object.freeze({
154
146
  const DYNAMIC_CONTROLLER_ENGINE_INTEGRITY_ERROR_MESSAGE =
155
147
  "incompatible or stale pi-workflow engine: dynamic controller context is missing runDecisionLoop (rebuild dist / reload workflow engine)";
156
148
  const supervisorTimers = new Map<string, ReturnType<typeof setInterval>>();
149
+ const supervisorRunMtimes = new Map<string, number>();
157
150
 
158
151
  export interface WorkflowRunOptions {
159
152
  task?: string;
160
- runtimeDefaults?: { model?: string; thinking?: ThinkingLevel };
153
+ runtimeOverrides?: WorkflowRuntimeDefaults;
154
+ runtimeDefaults?: WorkflowRuntimeDefaults;
155
+ availableModels?: WorkflowModelInfo[];
161
156
  dynamicUi?: DynamicWorkflowUi;
162
157
  runId?: string;
163
158
  parentRunId?: string;
@@ -165,6 +160,7 @@ export interface WorkflowRunOptions {
165
160
 
166
161
  interface WorkflowScheduleOptions {
167
162
  dynamicUi?: DynamicWorkflowUi;
163
+ availableModels?: WorkflowModelInfo[];
168
164
  }
169
165
 
170
166
  export async function runWorkflowSpec(
@@ -209,7 +205,9 @@ async function runLoadedWorkflowSpec(
209
205
  cwd,
210
206
  specPath,
211
207
  task: options.task,
208
+ runtimeOverrides: options.runtimeOverrides,
212
209
  runtimeDefaults: options.runtimeDefaults,
210
+ availableModels: options.availableModels,
213
211
  });
214
212
 
215
213
  const { run } = await createRunRecord(cwd, compiled, specPath, {
@@ -223,12 +221,15 @@ async function runLoadedWorkflowSpec(
223
221
  await writeRunRecord(cwd, run);
224
222
  });
225
223
 
224
+ const scheduleOptions = {
225
+ dynamicUi: options.dynamicUi,
226
+ availableModels: options.availableModels,
227
+ };
226
228
  const scheduled =
227
- (await scheduleRun(cwd, run.runId, compiled, {
228
- dynamicUi: options.dynamicUi,
229
- })) ?? (await readRunRecord(cwd, run.runId));
229
+ (await scheduleRun(cwd, run.runId, compiled, scheduleOptions)) ??
230
+ (await readRunRecord(cwd, run.runId));
230
231
  if (scheduled.status === "running")
231
- watchRun(cwd, scheduled.runId, { dynamicUi: options.dynamicUi });
232
+ watchRun(cwd, scheduled.runId, scheduleOptions);
232
233
  return scheduled;
233
234
  }
234
235
 
@@ -367,15 +368,27 @@ export function watchRun(
367
368
 
368
369
  const timer = setInterval(() => {
369
370
  void (async () => {
371
+ const previousMtime = supervisorRunMtimes.get(key);
372
+ const beforeMtime = await readRunMtimeMs(cwd, runId);
370
373
  const refreshed = await refreshRun(cwd, runId);
374
+ const afterMtime = await readRunMtimeMs(cwd, runId);
375
+ const currentMtime = afterMtime ?? beforeMtime;
376
+ if (currentMtime !== undefined)
377
+ supervisorRunMtimes.set(key, currentMtime);
378
+
371
379
  if (refreshed.status === "running") {
372
- await scheduleRun(cwd, runId, undefined, options);
380
+ const unchanged =
381
+ previousMtime !== undefined &&
382
+ currentMtime !== undefined &&
383
+ currentMtime <= previousMtime;
384
+ if (!unchanged) await scheduleRun(cwd, runId, undefined, options);
373
385
  return;
374
386
  }
375
387
 
376
388
  const existing = supervisorTimers.get(key);
377
389
  if (existing) clearInterval(existing);
378
390
  supervisorTimers.delete(key);
391
+ supervisorRunMtimes.delete(key);
379
392
  })().catch((error) => {
380
393
  void recordSupervisorError(cwd, runId, error);
381
394
  });
@@ -385,6 +398,18 @@ export function watchRun(
385
398
  supervisorTimers.set(key, timer);
386
399
  }
387
400
 
401
+ async function readRunMtimeMs(
402
+ cwd: string,
403
+ runId: string,
404
+ ): Promise<number | undefined> {
405
+ try {
406
+ return (await stat(workflowRunPath(cwd, runId))).mtimeMs;
407
+ } catch (error) {
408
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
409
+ throw error;
410
+ }
411
+ }
412
+
388
413
  export async function scheduleRun(
389
414
  cwd: string,
390
415
  runId: string,
@@ -861,7 +886,6 @@ async function extractArtifactGraphForeachItems(
861
886
  return { items };
862
887
  }
863
888
 
864
-
865
889
  async function launchPendingTaskAt(
866
890
  cwd: string,
867
891
  run: WorkflowRunRecord,
@@ -1049,6 +1073,7 @@ async function executeDynamicControllerTask(
1049
1073
  sources,
1050
1074
  dynamic: compiledTask.dynamic,
1051
1075
  dynamicUi: options.dynamicUi,
1076
+ availableModels: options.availableModels,
1052
1077
  });
1053
1078
  await assertDynamicGeneratedTasksSettled({
1054
1079
  cwd,
@@ -1058,6 +1083,7 @@ async function executeDynamicControllerTask(
1058
1083
  controllerTask: task,
1059
1084
  controllerCompiledTask: compiledTask,
1060
1085
  dynamic: compiledTask.dynamic,
1086
+ availableModels: options.availableModels,
1061
1087
  });
1062
1088
  await recordActiveRuntime();
1063
1089
  const unrunBranchBlockers = await dynamicUnrunBranchBlockers(
@@ -1190,6 +1216,7 @@ async function runDynamicControllerWorker(input: {
1190
1216
  sources: Record<string, unknown>;
1191
1217
  dynamic: CompiledDynamicWorkflowTask;
1192
1218
  dynamicUi?: DynamicWorkflowUi;
1219
+ availableModels?: WorkflowModelInfo[];
1193
1220
  }): Promise<unknown> {
1194
1221
  const resolved = await resolveWorkflowHelperRef(
1195
1222
  input.dynamic.uses,
@@ -1801,7 +1828,6 @@ function isDynamicReplayInvariantError(error: unknown): boolean {
1801
1828
  );
1802
1829
  }
1803
1830
 
1804
-
1805
1831
  function requiredDynamicString(
1806
1832
  value: unknown,
1807
1833
  field: string,
@@ -1813,65 +1839,6 @@ function requiredDynamicString(
1813
1839
  return value.trim();
1814
1840
  }
1815
1841
 
1816
- function optionalDynamicString(
1817
- value: unknown,
1818
- field: string,
1819
- ): string | undefined {
1820
- if (value === undefined) return undefined;
1821
- return requiredDynamicString(value, field);
1822
- }
1823
-
1824
- function optionalDynamicStringArray(
1825
- value: unknown,
1826
- field: string,
1827
- ): string[] | undefined {
1828
- if (value === undefined) return undefined;
1829
- if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
1830
- throw new Error(`ctx.agent() ${field} must be an array of strings`);
1831
- }
1832
- return value.map((item) => item.trim()).filter(Boolean);
1833
- }
1834
-
1835
- function isPlainDynamicRecord(
1836
- value: unknown,
1837
- ): value is Record<string, unknown> {
1838
- return typeof value === "object" && value !== null && !Array.isArray(value);
1839
- }
1840
-
1841
- function optionalDynamicPositiveInteger(
1842
- value: unknown,
1843
- field: string,
1844
- ): number | undefined {
1845
- if (value === undefined) return undefined;
1846
- if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
1847
- throw new Error(`ctx.agent() ${field} must be a positive integer`);
1848
- }
1849
- return value;
1850
- }
1851
-
1852
- function requiredDynamicOutputProfile(
1853
- value: unknown,
1854
- field: string,
1855
- api: string,
1856
- ): DynamicOutputProfile {
1857
- const profile = requiredDynamicString(value, field, api);
1858
- if (!isDynamicOutputProfile(profile)) {
1859
- throw new Error(`${api} ${field} has an unsupported output profile`);
1860
- }
1861
- return profile;
1862
- }
1863
-
1864
- function requiredDynamicNonNegativeInteger(
1865
- value: unknown,
1866
- field: string,
1867
- api: string,
1868
- ): number {
1869
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0) {
1870
- throw new Error(`${api} ${field} must be a non-negative integer`);
1871
- }
1872
- return value;
1873
- }
1874
-
1875
1842
  function requiredDynamicPositiveInteger(
1876
1843
  value: unknown,
1877
1844
  field: string,
@@ -1883,17 +1850,6 @@ function requiredDynamicPositiveInteger(
1883
1850
  return value;
1884
1851
  }
1885
1852
 
1886
- function optionalDynamicStringField(value: unknown): string | undefined {
1887
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
1888
- }
1889
-
1890
- function optionalDynamicOutputProfile(
1891
- value: unknown,
1892
- ): DynamicOutputProfile | undefined {
1893
- if (value === undefined) return undefined;
1894
- return requiredDynamicOutputProfile(value, "outputProfile", "ctx.agent()");
1895
- }
1896
-
1897
1853
  async function currentDynamicBudgetRemaining(input: {
1898
1854
  cwd: string;
1899
1855
  run: WorkflowRunRecord;
@@ -2266,6 +2222,7 @@ async function assertDynamicGeneratedTasksSettled(input: {
2266
2222
  controllerTask: WorkflowTaskRunRecord;
2267
2223
  controllerCompiledTask: CompiledTask;
2268
2224
  dynamic: CompiledDynamicWorkflowTask;
2225
+ availableModels?: WorkflowModelInfo[];
2269
2226
  }): Promise<void> {
2270
2227
  const state = await readOrRebuildDynamicState(input.cwd, input.run.runId);
2271
2228
  const generatedTaskIds =
@@ -2292,6 +2249,7 @@ async function repairMissingDynamicGeneratedTask(
2292
2249
  controllerTask: WorkflowTaskRunRecord;
2293
2250
  controllerCompiledTask: CompiledTask;
2294
2251
  dynamic: CompiledDynamicWorkflowTask;
2252
+ availableModels?: WorkflowModelInfo[];
2295
2253
  },
2296
2254
  specId: string,
2297
2255
  ): Promise<WorkflowTaskRunRecord> {
@@ -2328,6 +2286,7 @@ async function repairMissingDynamicGeneratedTask(
2328
2286
  branchId: optionalEventString(event.payload.branchId),
2329
2287
  request,
2330
2288
  dynamic: input.dynamic,
2289
+ availableModels: input.availableModels,
2331
2290
  });
2332
2291
  assertDynamicGeneratedMetadataMatches(compiledTask, {
2333
2292
  controllerSpecId: input.controllerTask.specId,
@@ -2377,6 +2336,7 @@ async function runDynamicAgentRequest(input: {
2377
2336
  request: unknown;
2378
2337
  generatedTaskIds: string[];
2379
2338
  isSettled?: () => boolean;
2339
+ availableModels?: WorkflowModelInfo[];
2380
2340
  }): Promise<unknown> {
2381
2341
  await assertDynamicRuntimeBudgetAvailable({
2382
2342
  cwd: input.cwd,
@@ -2462,6 +2422,7 @@ async function runDynamicAgentRequest(input: {
2462
2422
  branchId: generationBranchId,
2463
2423
  request: generationRequest,
2464
2424
  dynamic: input.dynamic,
2425
+ availableModels: input.availableModels,
2465
2426
  });
2466
2427
  assertDynamicGeneratedMetadataMatches(compiledTask, {
2467
2428
  controllerSpecId: input.controllerTask.specId,
@@ -2534,9 +2495,6 @@ async function runDynamicAgentRequest(input: {
2534
2495
  );
2535
2496
  }
2536
2497
 
2537
-
2538
-
2539
-
2540
2498
  interface DynamicControllerOutcome {
2541
2499
  taskStatus: "completed" | "blocked" | "failed";
2542
2500
  statusDetail: string;
@@ -2676,7 +2634,6 @@ function dynamicControllerIssueMessage(
2676
2634
  return `${prefix}: ${first}${suffix}`;
2677
2635
  }
2678
2636
 
2679
-
2680
2637
  function applyExistingLoopWorktree(
2681
2638
  run: WorkflowRunRecord,
2682
2639
  task: WorkflowTaskRunRecord,
@@ -2724,12 +2681,10 @@ function recordCreatedLoopWorktree(
2724
2681
  else run.loopWorktrees[index] = record;
2725
2682
  }
2726
2683
 
2727
-
2728
2684
  function uniqueStrings(values: readonly string[]): string[] {
2729
2685
  return [...new Set(values.filter((value) => value.trim().length > 0))];
2730
2686
  }
2731
2687
 
2732
-
2733
2688
  async function readCompiledWorkflow(
2734
2689
  cwd: string,
2735
2690
  runId: string,