@agwab/pi-workflow 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/artifact-graph-runtime.d.ts +1 -1
- package/dist/artifact-graph-runtime.js +10 -5
- package/dist/artifact-graph-schema.js +127 -5
- package/dist/compiler.js +52 -19
- package/dist/dynamic-generated-task-runtime.js +3 -1
- package/dist/dynamic-profiles.d.ts +1 -1
- package/dist/engine-run-graph.d.ts +3 -0
- package/dist/engine-run-graph.js +194 -4
- package/dist/engine.d.ts +5 -0
- package/dist/engine.js +389 -41
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +30 -8
- package/dist/index.d.ts +11 -3
- package/dist/index.js +6 -1
- package/dist/prompt-json.d.ts +7 -0
- package/dist/prompt-json.js +13 -0
- package/dist/roles.d.ts +1 -1
- package/dist/roles.js +5 -8
- package/dist/store.d.ts +20 -1
- package/dist/store.js +139 -35
- package/dist/strings.d.ts +11 -0
- package/dist/strings.js +24 -0
- package/dist/subagent-backend.js +710 -40
- package/dist/types.d.ts +107 -1
- package/dist/verification-ontology.d.ts +31 -0
- package/dist/verification-ontology.js +66 -0
- package/dist/workflow-artifact-tool.js +5 -6
- package/dist/workflow-artifacts.d.ts +7 -0
- package/dist/workflow-artifacts.js +55 -4
- package/dist/workflow-fetch-cache-extension.d.ts +1 -0
- package/dist/workflow-fetch-cache-extension.js +57 -9
- package/dist/workflow-metrics.d.ts +113 -0
- package/dist/workflow-metrics.js +272 -0
- package/dist/workflow-output-artifacts.js +5 -3
- package/dist/workflow-partial-output.d.ts +45 -0
- package/dist/workflow-partial-output.js +205 -0
- package/dist/workflow-progress-health.js +42 -10
- package/dist/workflow-runtime.js +10 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +194 -52
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +109 -30
- package/docs/usage.md +76 -29
- package/node_modules/@agwab/pi-subagent/README.md +3 -3
- package/node_modules/@agwab/pi-subagent/api.mjs +1 -0
- package/node_modules/@agwab/pi-subagent/docs/usage.md +63 -12
- package/node_modules/@agwab/pi-subagent/package.json +2 -2
- package/node_modules/@agwab/pi-subagent/src/api.ts +54 -1
- package/node_modules/@agwab/pi-subagent/src/artifacts/registry.ts +9 -4
- package/node_modules/@agwab/pi-subagent/src/artifacts/result.ts +8 -0
- package/node_modules/@agwab/pi-subagent/src/core/constants.ts +9 -0
- package/node_modules/@agwab/pi-subagent/src/core/validation.ts +21 -0
- package/node_modules/@agwab/pi-subagent/src/index.ts +1046 -576
- package/node_modules/@agwab/pi-subagent/src/orchestrate/async.ts +279 -156
- package/node_modules/@agwab/pi-subagent/src/orchestrate/interrupt.ts +165 -89
- package/node_modules/@agwab/pi-subagent/src/orchestrate/reconcile.ts +111 -65
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run-ref.ts +219 -0
- package/node_modules/@agwab/pi-subagent/src/orchestrate/run.ts +88 -8
- package/node_modules/@agwab/pi-subagent/src/orchestrate/status.ts +614 -298
- package/node_modules/@agwab/pi-subagent/src/panel.ts +1356 -560
- package/node_modules/@agwab/pi-subagent/src/runners/headless-model.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/runners/tmux.ts +13 -6
- package/package.json +2 -2
- package/skills/workflow-guide/SKILL.md +1 -0
- package/src/artifact-graph-runtime.ts +19 -13
- package/src/artifact-graph-schema.ts +143 -3
- package/src/cli.mjs +52 -0
- package/src/compiler.ts +63 -18
- package/src/dynamic-generated-task-runtime.ts +3 -1
- package/src/dynamic-profiles.ts +1 -1
- package/src/engine-run-graph.ts +246 -4
- package/src/engine.ts +545 -38
- package/src/extension.ts +36 -6
- package/src/index.ts +52 -1
- package/src/prompt-json.ts +13 -0
- package/src/roles.ts +6 -9
- package/src/store.ts +194 -42
- package/src/strings.ts +38 -0
- package/src/subagent-backend.ts +921 -62
- package/src/types.ts +116 -2
- package/src/verification-ontology.ts +88 -0
- package/src/workflow-artifact-tool.ts +5 -7
- package/src/workflow-artifacts.ts +83 -3
- package/src/workflow-fetch-cache-extension.ts +78 -13
- package/src/workflow-metrics.ts +478 -0
- package/src/workflow-output-artifacts.ts +5 -3
- package/src/workflow-partial-output.ts +299 -0
- package/src/workflow-progress-health.ts +47 -15
- package/src/workflow-runtime.ts +18 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +654 -232
- package/src/workflow-web-source.ts +153 -39
- package/workflows/README.md +7 -25
- package/workflows/deep-research/batched-verification.spec.json +253 -0
- package/workflows/deep-research/helpers/batch-verification-candidates.mjs +136 -0
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +229 -36
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +81 -2
- package/workflows/deep-research/helpers/render-executive.mjs +40 -26
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/helpers/shadow-select-verification.mjs +229 -0
- package/workflows/deep-research/helpers/verification-ontology.mjs +77 -0
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +3 -3
- package/workflows/deep-research/schemas/deep-research-research-questions-control.schema.json +38 -0
- package/workflows/deep-research/schemas/deep-research-sanitize-claims-control.schema.json +63 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-batch-control.schema.json +47 -0
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +13 -3
- package/workflows/deep-research/spec.json +32 -12
- package/workflows/impact-review/spec.json +3 -3
- package/workflows/spec-review/helpers/spec-review-pipeline.mjs +1 -8
- package/dist/dynamic-loader.d.ts +0 -25
- package/dist/dynamic-loader.js +0 -13
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stderr +0 -0
- package/skills/workflow-guide/scaffolds/dag-required-reads/spec.json.validate.stdout +0 -13
- package/src/dynamic-loader.ts +0 -49
- package/workflows/impact-review/schemas/docs-release-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/security-performance-impact-control.schema.json +0 -42
- package/workflows/impact-review/schemas/state-data-impact-control.schema.json +0 -42
package/src/compiler.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { dirname, resolve } from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { loadAgentByName } from "./agents.js";
|
|
5
5
|
import { DYNAMIC_OUTPUT_PROFILES } from "./dynamic-profiles.js";
|
|
6
|
+
import { stringifyPromptJson } from "./prompt-json.js";
|
|
7
|
+
import { compileRole } from "./roles.js";
|
|
6
8
|
import {
|
|
7
9
|
classifyToolCapability,
|
|
8
10
|
effectiveToolClassification,
|
|
@@ -178,7 +180,13 @@ function lowerArtifactGraphFrom(from: ArtifactGraphStageSpec["from"]): unknown {
|
|
|
178
180
|
!Array.isArray(from) &&
|
|
179
181
|
typeof from.source === "string"
|
|
180
182
|
) {
|
|
181
|
-
return {
|
|
183
|
+
return {
|
|
184
|
+
stage: from.source,
|
|
185
|
+
path: from.path,
|
|
186
|
+
...((from as { streaming?: unknown }).streaming !== undefined
|
|
187
|
+
? { streaming: (from as { streaming?: unknown }).streaming }
|
|
188
|
+
: {}),
|
|
189
|
+
};
|
|
182
190
|
}
|
|
183
191
|
return from;
|
|
184
192
|
}
|
|
@@ -209,11 +217,26 @@ function appendWorkflowOutputInstructions(
|
|
|
209
217
|
: "Use schema `stage-control-v1` unless the workflow asks for a more specific control schema.",
|
|
210
218
|
"Put detailed prose, reasoning, and evidence discussion in <analysis> only.",
|
|
211
219
|
"Put structured evidence pointers in <refs> as a JSON array; use [] if none.",
|
|
220
|
+
...partialOutputInstructions(stage.output?.partial?.paths),
|
|
212
221
|
]
|
|
213
222
|
.filter(Boolean)
|
|
214
223
|
.join("\n\n");
|
|
215
224
|
}
|
|
216
225
|
|
|
226
|
+
function partialOutputInstructions(
|
|
227
|
+
paths: readonly string[] | undefined,
|
|
228
|
+
): string[] {
|
|
229
|
+
if (!paths || paths.length === 0) return [];
|
|
230
|
+
return [
|
|
231
|
+
"# Workflow Partial Output Protocol (optional)",
|
|
232
|
+
`If a complete stable array item is ready before your final answer for one of these control paths (${paths.join(", ")}), you may emit a partial-control section before the final output:`,
|
|
233
|
+
'<partial-control>{"schema":"workflow-partial-output-v1","path":"$.items","items":[{"id":"stable-id","...":"..."}]}</partial-control>',
|
|
234
|
+
"Use the actual declared path, not the example path, and include only items that are final/stable enough to appear unchanged in your final <control> at that path.",
|
|
235
|
+
"Every partial item must be the exact JSON object that will appear in the final array and must include a stable non-empty string `id`; never revise or withdraw a published partial item.",
|
|
236
|
+
"If an item might change, do not publish it partially; wait for the final workflow output. The final answer must still include the normal <control>, <analysis>, and <refs> sections exactly once.",
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
|
|
217
240
|
function artifactGraphTaskMetadata(
|
|
218
241
|
stage: ArtifactGraphStageSpec,
|
|
219
242
|
specDir: string,
|
|
@@ -230,8 +253,12 @@ function artifactGraphTaskMetadata(
|
|
|
230
253
|
? resolve(specDir, controlSchema)
|
|
231
254
|
: undefined,
|
|
232
255
|
maxDigestChars: stage.output?.maxDigestChars,
|
|
256
|
+
partial: stage.output?.partial
|
|
257
|
+
? { paths: [...stage.output.partial.paths] }
|
|
258
|
+
: undefined,
|
|
233
259
|
},
|
|
234
260
|
requiredReads: stage.inputPolicy?.requiredReads ?? [],
|
|
261
|
+
artifactAccess: stage.inputPolicy?.artifactAccess ?? "enabled",
|
|
235
262
|
sourceProjection: stage.sourceProjection,
|
|
236
263
|
};
|
|
237
264
|
}
|
|
@@ -663,15 +690,19 @@ async function compileArtifactGraphPlan(
|
|
|
663
690
|
return defaultAgent;
|
|
664
691
|
};
|
|
665
692
|
const roleEntries = Object.entries(spec.roles ?? {});
|
|
666
|
-
const roles =
|
|
667
|
-
name,
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
693
|
+
const roles = await Promise.all(
|
|
694
|
+
roleEntries.map(async ([name, role]: [string, any]) => {
|
|
695
|
+
const sourceAgent = role.fromAgent
|
|
696
|
+
? await loadWorkflowAgent(
|
|
697
|
+
role.fromAgent,
|
|
698
|
+
options.cwd,
|
|
699
|
+
agentCache,
|
|
700
|
+
`$.roles.${name}.fromAgent`,
|
|
701
|
+
)
|
|
702
|
+
: undefined;
|
|
703
|
+
return compileRole(name, role, sourceAgent);
|
|
704
|
+
}),
|
|
705
|
+
);
|
|
675
706
|
const roleText = roles.length
|
|
676
707
|
? `# Role Context\n\n${roles.map((r) => `## Role: ${r.name}\n${r.content}`).join("\n\n")}`
|
|
677
708
|
: "";
|
|
@@ -681,7 +712,7 @@ async function compileArtifactGraphPlan(
|
|
|
681
712
|
typeof workflowInput === "object" &&
|
|
682
713
|
!Array.isArray(workflowInput) &&
|
|
683
714
|
Object.keys(workflowInput).length > 0
|
|
684
|
-
? `# Workflow Input\n\n${
|
|
715
|
+
? `# Workflow Input\n\n${stringifyPromptJson(workflowInput)}`
|
|
685
716
|
: "";
|
|
686
717
|
const runtimeOverrides = options.runtimeOverrides;
|
|
687
718
|
const runtimeDefaults = options.runtimeDefaults;
|
|
@@ -831,15 +862,29 @@ async function compileArtifactGraphPlan(
|
|
|
831
862
|
/\$\{item\}/g,
|
|
832
863
|
"the relevant item from the dependency context",
|
|
833
864
|
);
|
|
834
|
-
const
|
|
865
|
+
const instructionText = `# Instructions\n\n${normalizedPrompt}`;
|
|
866
|
+
const stageText = `# Workflow Stage\n\nstage=${stage.id}\ntype=${runtimeStageKind}`;
|
|
867
|
+
const taskText =
|
|
835
868
|
injectRuntimeTaskInPrompt && options.task
|
|
836
869
|
? `# Task\n\n${options.task}`
|
|
837
|
-
: undefined
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
870
|
+
: undefined;
|
|
871
|
+
const compiledPrompt = (
|
|
872
|
+
runtimeStageKind === "foreach"
|
|
873
|
+
? [
|
|
874
|
+
taskText,
|
|
875
|
+
workflowInputText || undefined,
|
|
876
|
+
stageText,
|
|
877
|
+
roleText || undefined,
|
|
878
|
+
instructionText,
|
|
879
|
+
]
|
|
880
|
+
: [
|
|
881
|
+
taskText,
|
|
882
|
+
workflowInputText || undefined,
|
|
883
|
+
stageText,
|
|
884
|
+
instructionText,
|
|
885
|
+
roleText || undefined,
|
|
886
|
+
]
|
|
887
|
+
)
|
|
843
888
|
.filter(Boolean)
|
|
844
889
|
.join("\n\n");
|
|
845
890
|
const toolSelection = resolveToolSelection(
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "./dynamic-profiles.js";
|
|
8
8
|
import { readOrRebuildDynamicState } from "./dynamic-state.js";
|
|
9
9
|
import { sanitizeTaskId } from "./engine-run-graph.js";
|
|
10
|
+
import { compactStrings } from "./strings.js";
|
|
10
11
|
import { fromProjectPath, isTerminalTaskStatus, readJson } from "./store.js";
|
|
11
12
|
import {
|
|
12
13
|
classifyToolCapability,
|
|
@@ -307,6 +308,7 @@ export async function buildDynamicGeneratedCompiledTask(input: {
|
|
|
307
308
|
maxDigestChars: DYNAMIC_OUTPUT_MAX_DIGEST_CHARS,
|
|
308
309
|
},
|
|
309
310
|
requiredReads: input.request.requiredReads,
|
|
311
|
+
artifactAccess: "enabled",
|
|
310
312
|
sourceProjection: dynamicInputSourceProjection(input.request.inputs),
|
|
311
313
|
},
|
|
312
314
|
dynamicGenerated: {
|
|
@@ -966,5 +968,5 @@ function dynamicOutputProfileInstructions(
|
|
|
966
968
|
}
|
|
967
969
|
|
|
968
970
|
function uniqueStrings(values: readonly string[]): string[] {
|
|
969
|
-
return
|
|
971
|
+
return compactStrings(values, { trim: false, dropWhitespaceOnly: true });
|
|
970
972
|
}
|
package/src/dynamic-profiles.ts
CHANGED
|
@@ -45,6 +45,6 @@ export function isTerminalDynamicOutputProfile(
|
|
|
45
45
|
return typeof value === "string" && TERMINAL_OUTPUT_PROFILE_SET.has(value);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
export function dynamicOutputProfileValues():
|
|
48
|
+
export function dynamicOutputProfileValues(): DynamicOutputProfile[] {
|
|
49
49
|
return [...DYNAMIC_OUTPUT_PROFILES];
|
|
50
50
|
}
|
package/src/engine-run-graph.ts
CHANGED
|
@@ -127,6 +127,151 @@ export function reconcileDynamicGeneratedRunRecords(
|
|
|
127
127
|
return changed;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
export function reconcileForeachGeneratedRunRecords(
|
|
131
|
+
cwd: string,
|
|
132
|
+
run: WorkflowRunRecord,
|
|
133
|
+
compiledFlow: CompiledWorkflow,
|
|
134
|
+
): boolean {
|
|
135
|
+
let changed = false;
|
|
136
|
+
const compiledSpecIds = new Set(
|
|
137
|
+
compiledFlow.tasks.map((task) => compiledTaskSpecId(task)),
|
|
138
|
+
);
|
|
139
|
+
const compiledTaskBySpecId = new Map(
|
|
140
|
+
compiledFlow.tasks.map((task) => [compiledTaskSpecId(task), task]),
|
|
141
|
+
);
|
|
142
|
+
const placeholderToGeneratedSpecIds = new Map<string, string[]>();
|
|
143
|
+
const streamingPlaceholderSpecIds = new Set<string>();
|
|
144
|
+
|
|
145
|
+
for (const compiledTask of compiledFlow.tasks) {
|
|
146
|
+
const specId = compiledTaskSpecId(compiledTask);
|
|
147
|
+
if (compiledTask.foreach && foreachStreamingEnabled(compiledTask)) {
|
|
148
|
+
streamingPlaceholderSpecIds.add(specId);
|
|
149
|
+
}
|
|
150
|
+
const placeholderSpecId = foreachGeneratedPlaceholderSpecId(
|
|
151
|
+
compiledTask,
|
|
152
|
+
compiledFlow,
|
|
153
|
+
specId,
|
|
154
|
+
);
|
|
155
|
+
if (!placeholderSpecId) continue;
|
|
156
|
+
if (
|
|
157
|
+
compiledTask.foreachGenerated?.placeholderSpecId !== placeholderSpecId
|
|
158
|
+
) {
|
|
159
|
+
compiledTask.foreachGenerated = { placeholderSpecId };
|
|
160
|
+
changed = true;
|
|
161
|
+
}
|
|
162
|
+
const generated =
|
|
163
|
+
placeholderToGeneratedSpecIds.get(placeholderSpecId) ?? [];
|
|
164
|
+
generated.push(specId);
|
|
165
|
+
placeholderToGeneratedSpecIds.set(placeholderSpecId, generated);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (placeholderToGeneratedSpecIds.size === 0) return changed;
|
|
169
|
+
|
|
170
|
+
const filteredRunTasks: WorkflowTaskRunRecord[] = [];
|
|
171
|
+
const seenGeneratedSpecIds = new Set<string>();
|
|
172
|
+
for (const task of run.tasks) {
|
|
173
|
+
const generatedSpecIds = placeholderToGeneratedSpecIds.get(task.specId);
|
|
174
|
+
let placeholderSpecId = foreachGeneratedPlaceholderSpecId(
|
|
175
|
+
task,
|
|
176
|
+
compiledFlow,
|
|
177
|
+
task.specId,
|
|
178
|
+
);
|
|
179
|
+
if (generatedSpecIds && !placeholderSpecId) {
|
|
180
|
+
const compiledTask = compiledTaskBySpecId.get(task.specId);
|
|
181
|
+
if (
|
|
182
|
+
compiledTask?.foreach &&
|
|
183
|
+
streamingPlaceholderSpecIds.has(task.specId)
|
|
184
|
+
) {
|
|
185
|
+
filteredRunTasks.push(task);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (generatedSpecIds.includes(task.specId)) {
|
|
189
|
+
placeholderSpecId = task.specId;
|
|
190
|
+
task.foreachGenerated = { placeholderSpecId };
|
|
191
|
+
changed = true;
|
|
192
|
+
} else {
|
|
193
|
+
changed = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (placeholderSpecId && !compiledSpecIds.has(task.specId)) {
|
|
198
|
+
changed = true;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (placeholderSpecId && seenGeneratedSpecIds.has(task.specId)) {
|
|
202
|
+
changed = true;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (placeholderSpecId) {
|
|
206
|
+
seenGeneratedSpecIds.add(task.specId);
|
|
207
|
+
if (task.foreachGenerated?.placeholderSpecId !== placeholderSpecId) {
|
|
208
|
+
task.foreachGenerated = { placeholderSpecId };
|
|
209
|
+
changed = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
filteredRunTasks.push(task);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const runTaskBySpecId = new Map<string, WorkflowTaskRunRecord>();
|
|
216
|
+
for (const task of filteredRunTasks) {
|
|
217
|
+
if (!runTaskBySpecId.has(task.specId))
|
|
218
|
+
runTaskBySpecId.set(task.specId, task);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const reordered: WorkflowTaskRunRecord[] = [];
|
|
222
|
+
const usedSpecIds = new Set<string>();
|
|
223
|
+
let nextIndex = nextTaskRecordIndex({ ...run, tasks: filteredRunTasks });
|
|
224
|
+
for (const compiledTask of compiledFlow.tasks) {
|
|
225
|
+
const specId = compiledTaskSpecId(compiledTask);
|
|
226
|
+
const existing = runTaskBySpecId.get(specId);
|
|
227
|
+
if (existing) {
|
|
228
|
+
const placeholderSpecId =
|
|
229
|
+
compiledTask.foreachGenerated?.placeholderSpecId;
|
|
230
|
+
if (
|
|
231
|
+
placeholderSpecId &&
|
|
232
|
+
existing.foreachGenerated?.placeholderSpecId !== placeholderSpecId
|
|
233
|
+
) {
|
|
234
|
+
existing.foreachGenerated = { placeholderSpecId };
|
|
235
|
+
changed = true;
|
|
236
|
+
}
|
|
237
|
+
reordered.push(existing);
|
|
238
|
+
usedSpecIds.add(specId);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (!compiledTask.foreachGenerated) continue;
|
|
242
|
+
const created = createTaskRunRecord(
|
|
243
|
+
cwd,
|
|
244
|
+
run.runId,
|
|
245
|
+
compiledTask,
|
|
246
|
+
nextIndex,
|
|
247
|
+
);
|
|
248
|
+
nextIndex += 1;
|
|
249
|
+
reordered.push(created);
|
|
250
|
+
usedSpecIds.add(specId);
|
|
251
|
+
changed = true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const task of filteredRunTasks) {
|
|
255
|
+
if (!usedSpecIds.has(task.specId)) reordered.push(task);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!sameTaskRecordOrder(run.tasks, reordered)) changed = true;
|
|
259
|
+
for (const task of reordered) {
|
|
260
|
+
if (!task.dependsOn) continue;
|
|
261
|
+
const replaced = replaceForeachGeneratedDependencies(
|
|
262
|
+
task.dependsOn,
|
|
263
|
+
placeholderToGeneratedSpecIds,
|
|
264
|
+
streamingPlaceholderSpecIds,
|
|
265
|
+
);
|
|
266
|
+
if (!sameStringList(task.dependsOn, replaced)) {
|
|
267
|
+
task.dependsOn = replaced;
|
|
268
|
+
changed = true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (changed) run.tasks = reordered;
|
|
272
|
+
return changed;
|
|
273
|
+
}
|
|
274
|
+
|
|
130
275
|
export function assertRunTaskPositionalAlignment(
|
|
131
276
|
run: WorkflowRunRecord,
|
|
132
277
|
compiledFlow: CompiledWorkflow,
|
|
@@ -209,6 +354,57 @@ export function compiledTaskSpecId(task: CompiledTask): string {
|
|
|
209
354
|
return typeof specId === "string" && specId.trim() !== "" ? specId : task.id;
|
|
210
355
|
}
|
|
211
356
|
|
|
357
|
+
function foreachGeneratedPlaceholderSpecId(
|
|
358
|
+
task: CompiledTask | WorkflowTaskRunRecord,
|
|
359
|
+
compiledFlow: CompiledWorkflow,
|
|
360
|
+
specId: string,
|
|
361
|
+
): string | undefined {
|
|
362
|
+
const explicit = task.foreachGenerated?.placeholderSpecId;
|
|
363
|
+
if (typeof explicit === "string" && explicit.trim() !== "") return explicit;
|
|
364
|
+
if ((task as CompiledTask).foreach) return undefined;
|
|
365
|
+
if (task.kind !== "foreach" || !task.stageId) return undefined;
|
|
366
|
+
const placeholderSpecId = foreachPlaceholderSpecId(
|
|
367
|
+
compiledFlow,
|
|
368
|
+
task.stageId,
|
|
369
|
+
);
|
|
370
|
+
if (!placeholderSpecId || specId === placeholderSpecId) return undefined;
|
|
371
|
+
return placeholderSpecId;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function foreachPlaceholderSpecId(
|
|
375
|
+
compiledFlow: CompiledWorkflow,
|
|
376
|
+
stageId: string,
|
|
377
|
+
): string | undefined {
|
|
378
|
+
const stage = ((compiledFlow as any).stages ?? []).find(
|
|
379
|
+
(candidate: any) => candidate?.id === stageId,
|
|
380
|
+
);
|
|
381
|
+
if (stage?.type !== "foreach") return undefined;
|
|
382
|
+
return `${stageId}.item`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function replaceForeachGeneratedDependencies(
|
|
386
|
+
dependsOn: string[],
|
|
387
|
+
placeholderToGeneratedSpecIds: Map<string, string[]>,
|
|
388
|
+
keepPlaceholderSpecIds = new Set<string>(),
|
|
389
|
+
): string[] {
|
|
390
|
+
const replaced: string[] = [];
|
|
391
|
+
for (const dep of dependsOn) {
|
|
392
|
+
const generatedSpecIds = placeholderToGeneratedSpecIds.get(dep);
|
|
393
|
+
if (generatedSpecIds) {
|
|
394
|
+
if (keepPlaceholderSpecIds.has(dep)) replaced.push(dep);
|
|
395
|
+
replaced.push(...generatedSpecIds);
|
|
396
|
+
} else replaced.push(dep);
|
|
397
|
+
}
|
|
398
|
+
return [...new Set(replaced)];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function sameStringList(left: string[], right: string[]): boolean {
|
|
402
|
+
return (
|
|
403
|
+
left.length === right.length &&
|
|
404
|
+
left.every((value, index) => value === right[index])
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
212
408
|
function isLoopGeneratedCompiledTask(
|
|
213
409
|
task: CompiledTask,
|
|
214
410
|
loopIds: Set<string>,
|
|
@@ -303,6 +499,29 @@ export function dependenciesReady(
|
|
|
303
499
|
if (deps.length === 0) return true;
|
|
304
500
|
const partial =
|
|
305
501
|
stageSourcePolicy(compiledFlow, compiledTask.stageId ?? "") === "partial";
|
|
502
|
+
if (foreachStreamingEnabled(compiledTask)) {
|
|
503
|
+
let completedDependencyReady = false;
|
|
504
|
+
let runningDependencyMayHavePartialItems = false;
|
|
505
|
+
let allKnownDependenciesTerminal = true;
|
|
506
|
+
for (const dep of deps) {
|
|
507
|
+
const status = bySpecId.get(dep)?.status;
|
|
508
|
+
if (status === "completed") {
|
|
509
|
+
completedDependencyReady = true;
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
if (status && isTerminalTaskStatus(status)) {
|
|
513
|
+
if (!partial) return false;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (status === "running") runningDependencyMayHavePartialItems = true;
|
|
517
|
+
allKnownDependenciesTerminal = false;
|
|
518
|
+
}
|
|
519
|
+
return (
|
|
520
|
+
completedDependencyReady ||
|
|
521
|
+
runningDependencyMayHavePartialItems ||
|
|
522
|
+
allKnownDependenciesTerminal
|
|
523
|
+
);
|
|
524
|
+
}
|
|
306
525
|
return deps.every((dep) => {
|
|
307
526
|
const status = bySpecId.get(dep)?.status;
|
|
308
527
|
if (status === "completed") return true;
|
|
@@ -311,6 +530,22 @@ export function dependenciesReady(
|
|
|
311
530
|
});
|
|
312
531
|
}
|
|
313
532
|
|
|
533
|
+
export function foreachStreamingEnabled(compiledTask: CompiledTask): boolean {
|
|
534
|
+
const streaming = (compiledTask.foreach?.from as any)?.streaming;
|
|
535
|
+
return Boolean(
|
|
536
|
+
streaming &&
|
|
537
|
+
typeof streaming === "object" &&
|
|
538
|
+
(streaming as { enabled?: unknown }).enabled === true,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function foreachStreamingMinChunk(compiledTask: CompiledTask): number {
|
|
543
|
+
const value = (compiledTask.foreach?.from as any)?.streaming?.minChunk;
|
|
544
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
545
|
+
? Math.floor(value)
|
|
546
|
+
: 1;
|
|
547
|
+
}
|
|
548
|
+
|
|
314
549
|
export function buildForeachGeneratedTasks(
|
|
315
550
|
template: CompiledTask,
|
|
316
551
|
runtimeTask: string | undefined,
|
|
@@ -330,15 +565,16 @@ export function buildForeachGeneratedTasks(
|
|
|
330
565
|
const itemText = formatForeachItem(item);
|
|
331
566
|
const instructions = template.foreach!.prompt.replace(
|
|
332
567
|
/\$\{item\}/g,
|
|
333
|
-
itemText,
|
|
568
|
+
escapeReplacementText(itemText),
|
|
334
569
|
);
|
|
335
570
|
const compiledPrompt = [
|
|
336
571
|
template.foreach!.injectRuntimeTask && runtimeTask
|
|
337
572
|
? `# Task\n\n${runtimeTask}`
|
|
338
573
|
: undefined,
|
|
339
|
-
`# Workflow Stage\n\nstage=${template.stageId}\ntype=foreach
|
|
340
|
-
`# Instructions\n\n${instructions}`,
|
|
574
|
+
`# Workflow Stage\n\nstage=${template.stageId}\ntype=foreach`,
|
|
341
575
|
template.foreach!.roleText || undefined,
|
|
576
|
+
`# Workflow Item\n\nitem=${taskId}`,
|
|
577
|
+
`# Instructions\n\n${instructions}`,
|
|
342
578
|
]
|
|
343
579
|
.filter(Boolean)
|
|
344
580
|
.join("\n\n");
|
|
@@ -352,6 +588,7 @@ export function buildForeachGeneratedTasks(
|
|
|
352
588
|
compiledPrompt,
|
|
353
589
|
dependsOn: [...(template.dependsOn ?? [])],
|
|
354
590
|
foreach: undefined,
|
|
591
|
+
foreachGenerated: { placeholderSpecId: template.id },
|
|
355
592
|
} as CompiledTask);
|
|
356
593
|
}
|
|
357
594
|
return { tasks };
|
|
@@ -382,6 +619,10 @@ function formatForeachItem(item: unknown): string {
|
|
|
382
619
|
return typeof item === "string" ? item : JSON.stringify(item);
|
|
383
620
|
}
|
|
384
621
|
|
|
622
|
+
function escapeReplacementText(value: string): string {
|
|
623
|
+
return value.replace(/\$/g, "$$$$");
|
|
624
|
+
}
|
|
625
|
+
|
|
385
626
|
export function sourceStageIdsForFrom(from: unknown): string[] {
|
|
386
627
|
if (Array.isArray(from))
|
|
387
628
|
return from.filter((item): item is string => typeof item === "string");
|
|
@@ -453,7 +694,8 @@ export function markDagDependentsSkipped(
|
|
|
453
694
|
return (
|
|
454
695
|
status === "failed" ||
|
|
455
696
|
status === "interrupted" ||
|
|
456
|
-
status === "skipped"
|
|
697
|
+
status === "skipped" ||
|
|
698
|
+
status === "blocked"
|
|
457
699
|
);
|
|
458
700
|
});
|
|
459
701
|
if (!failedDep) continue;
|