@agwab/pi-workflow 0.3.0 → 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 +46 -11
- package/dist/dynamic-decision.d.ts +1 -0
- package/dist/dynamic-decision.js +7 -0
- package/dist/dynamic-generated-task-runtime.js +3 -1
- package/dist/dynamic-profiles.d.ts +1 -0
- package/dist/dynamic-profiles.js +3 -0
- package/dist/engine-run-graph.d.ts +2 -0
- package/dist/engine-run-graph.js +55 -5
- package/dist/engine.js +278 -15
- package/dist/extension.js +3 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +4 -0
- 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 +89 -29
- package/dist/strings.d.ts +11 -0
- package/dist/strings.js +24 -0
- package/dist/subagent-backend.js +557 -13
- package/dist/types.d.ts +101 -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-web-source-extension.js +27 -4
- package/dist/workflow-web-source.js +26 -12
- package/docs/usage.md +76 -29
- package/node_modules/@agwab/pi-subagent/package.json +1 -1
- package/node_modules/@agwab/pi-subagent/src/index.ts +53 -5
- package/node_modules/@agwab/pi-subagent/src/panel.ts +7 -3
- 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 +49 -9
- package/src/dynamic-decision.ts +11 -0
- package/src/dynamic-generated-task-runtime.ts +3 -1
- package/src/dynamic-profiles.ts +4 -0
- package/src/engine-run-graph.ts +63 -4
- package/src/engine.ts +400 -14
- package/src/extension.ts +3 -2
- package/src/index.ts +49 -0
- package/src/prompt-json.ts +13 -0
- package/src/roles.ts +6 -9
- package/src/store.ts +123 -34
- package/src/strings.ts +38 -0
- package/src/subagent-backend.ts +727 -41
- package/src/types.ts +110 -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-web-source-extension.ts +33 -4
- package/src/workflow-web-source.ts +36 -12
- 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 +173 -20
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +80 -1
- package/workflows/deep-research/helpers/render-executive.mjs +32 -5
- 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 -2
- 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 +10 -3
- package/workflows/deep-research/spec.json +32 -12
- 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/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
`pi-workflow` lets Pi run named, repeatable multi-step workflows: research, code review, spec conformance checks, impact review, and project-specific team routines.
|
|
14
14
|
|
|
15
|
-
Built on [`@agwab/pi-subagent`](https://github.com/AgwaB/pi-subagent), it coordinates Pi subagent workers across workflow steps, passes results between them, and records the run so it can be inspected or resumed.
|
|
15
|
+
Built on [`@agwab/pi-subagent`](https://github.com/AgwaB/pi-subagent), it coordinates Pi subagent workers across workflow steps, passes results between them, and records the run so it can be inspected, stopped, or resumed.
|
|
16
16
|
|
|
17
17
|
You choose a workflow and describe the task in natural language.
|
|
18
18
|
|
|
@@ -74,6 +74,8 @@ For a one-off adaptive workflow that should plan, fan out, and synthesize withou
|
|
|
74
74
|
|
|
75
75
|
`/workflow dynamic` uses pi-workflow's built-in trusted dynamic controller and records a normal workflow run under `.pi/workflows/`. Use it when you explicitly want adaptive orchestration rather than a named reusable workflow.
|
|
76
76
|
|
|
77
|
+
To interrupt a non-terminal run and stop its local supervisor watch, use `/workflow stop <run-id>`. Resume later with `/workflow resume <run-id>` if unfinished tasks should be retried.
|
|
78
|
+
|
|
77
79
|
## Usage: choose an execution mode
|
|
78
80
|
|
|
79
81
|
Use the bundled `execution-router` skill when you are not sure whether a task should be handled directly, by a targeted verifier/subagent, by an existing workflow, or by a new workflow:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type WorkflowSourceManifestSource } from "./workflow-artifact-tool.js";
|
|
2
|
-
import {
|
|
2
|
+
import type { CompiledTask, CompiledWorkflow, WorkflowRunRecord, WorkflowTaskRunRecord } from "./types.js";
|
|
3
3
|
export declare function executeSupportTask(cwd: string, run: WorkflowRunRecord, task: WorkflowTaskRunRecord, compiledTask: CompiledWorkflow["tasks"][number]): Promise<boolean>;
|
|
4
4
|
export declare function readSupportSources(cwd: string, run: WorkflowRunRecord, dependsOn: string[]): Promise<Record<string, unknown>>;
|
|
5
5
|
export declare function readArtifactGraphSupportSources(cwd: string, run: WorkflowRunRecord, dependsOn: string[]): Promise<Record<string, unknown>>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, extname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { stringifyPromptJson } from "./prompt-json.js";
|
|
5
|
+
import { compactStrings } from "./strings.js";
|
|
4
6
|
import { loadWorkflowHelper } from "./workflow-helpers.js";
|
|
5
7
|
import { WORKFLOW_ARTIFACT_TOOL_NAME, writeWorkflowArtifactExtensionWrapper, } from "./workflow-artifact-extension.js";
|
|
6
8
|
import { WORKFLOW_SOURCE_MANIFEST_SCHEMA, } from "./workflow-artifact-tool.js";
|
|
@@ -337,11 +339,14 @@ export async function prepareDagTask(cwd, run, compiledFlow, index) {
|
|
|
337
339
|
compiledTask.compiledPrompt,
|
|
338
340
|
"# Source Stage Context",
|
|
339
341
|
"Use this deterministic source context packet. Prefer structuredOutput over outputPreview. Do not assume dependencies beyond this explicit packet.",
|
|
340
|
-
|
|
342
|
+
stringifyPromptJson({ ...context, missingDependencies: missing }),
|
|
341
343
|
].join("\n\n"),
|
|
342
344
|
};
|
|
343
345
|
}
|
|
344
346
|
async function prepareArtifactGraphTask(cwd, run, compiledTask, task, contextDependsOn) {
|
|
347
|
+
if (compiledTask.artifactGraph?.artifactAccess === "none") {
|
|
348
|
+
return { ...compiledTask, cwd: task.cwd };
|
|
349
|
+
}
|
|
345
350
|
const taskDir = dirname(fromProjectPath(cwd, task.files.result));
|
|
346
351
|
const manifestPath = join(taskDir, "source-manifest.json");
|
|
347
352
|
const ledgerPath = join(taskDir, "read-ledger.jsonl");
|
|
@@ -665,7 +670,7 @@ export function formatArtifactGraphSourceContext(sources, requiredReads) {
|
|
|
665
670
|
return [
|
|
666
671
|
"# Workflow Artifact Inputs",
|
|
667
672
|
"Use workflow_artifact to list/read upstream workflow artifacts. Inline controlProjection fields are authoritative for the projected data they contain; use artifact reads for declared requiredReads, missing fields, or debug detail.",
|
|
668
|
-
|
|
673
|
+
'Projected reads must include a JSON path when using maxItems or maxChars, for example {"action":"read","source":"plan","artifact":"control","path":"$.factSlots","maxItems":8,"maxChars":2000}. For a whole artifact read, omit maxItems/maxChars.',
|
|
669
674
|
requiredReads.length > 0
|
|
670
675
|
? [
|
|
671
676
|
"Required reads before final output:",
|
|
@@ -673,7 +678,7 @@ export function formatArtifactGraphSourceContext(sources, requiredReads) {
|
|
|
673
678
|
].join("\n")
|
|
674
679
|
: "No hard requiredReads are declared for this stage.",
|
|
675
680
|
"Available sources:",
|
|
676
|
-
|
|
681
|
+
stringifyPromptJson(sources.map((source) => ({
|
|
677
682
|
source: source.source,
|
|
678
683
|
taskId: source.taskId,
|
|
679
684
|
specId: source.specId,
|
|
@@ -687,11 +692,11 @@ export function formatArtifactGraphSourceContext(sources, requiredReads) {
|
|
|
687
692
|
projectionMissingPaths: source.projectionMissingPaths,
|
|
688
693
|
projectionTruncated: source.projectionTruncated,
|
|
689
694
|
availableArtifacts: Object.keys(source.artifacts),
|
|
690
|
-
}))
|
|
695
|
+
}))),
|
|
691
696
|
].join("\n\n");
|
|
692
697
|
}
|
|
693
698
|
function uniqueStrings(values) {
|
|
694
|
-
return
|
|
699
|
+
return compactStrings(values, { trim: false, dropWhitespaceOnly: true });
|
|
695
700
|
}
|
|
696
701
|
export async function readArtifactGraphControl(cwd, task) {
|
|
697
702
|
const taskDir = dirname(fromProjectPath(cwd, task.files.result));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isAbsolute } from "node:path";
|
|
2
2
|
import { DYNAMIC_OUTPUT_PROFILES } from "./dynamic-profiles.js";
|
|
3
|
+
import { compactStrings } from "./strings.js";
|
|
3
4
|
import { APPROVAL_MODES, FAST_MODES, THINKING_LEVELS, TOOL_CLASSIFICATIONS, WORKTREE_POLICIES, WorkflowValidationError, } from "./types.js";
|
|
4
5
|
const TOP_LEVEL_KEYS = new Set([
|
|
5
6
|
"schemaVersion",
|
|
@@ -58,10 +59,16 @@ const OUTPUT_KEYS = new Set([
|
|
|
58
59
|
"analysis",
|
|
59
60
|
"refs",
|
|
60
61
|
"maxDigestChars",
|
|
62
|
+
"partial",
|
|
61
63
|
]);
|
|
64
|
+
const OUTPUT_PARTIAL_KEYS = new Set(["paths"]);
|
|
62
65
|
const REQUIRED_FLAG_KEYS = new Set(["required"]);
|
|
63
66
|
const REFS_OUTPUT_KEYS = new Set(["required", "minItems"]);
|
|
64
|
-
const INPUT_POLICY_KEYS = new Set([
|
|
67
|
+
const INPUT_POLICY_KEYS = new Set([
|
|
68
|
+
"requiredReads",
|
|
69
|
+
"enforcement",
|
|
70
|
+
"artifactAccess",
|
|
71
|
+
]);
|
|
65
72
|
const SOURCE_PROJECTION_KEYS = new Set(["include", "maxChars"]);
|
|
66
73
|
const SUPPORT_KEYS = new Set(["uses", "options"]);
|
|
67
74
|
const DYNAMIC_STAGE_FORBIDDEN_KEYS = new Set([
|
|
@@ -172,7 +179,8 @@ const UNTIL_KEYS = new Set([
|
|
|
172
179
|
"all",
|
|
173
180
|
"any",
|
|
174
181
|
]);
|
|
175
|
-
const FOREACH_FROM_KEYS = new Set(["source", "path"]);
|
|
182
|
+
const FOREACH_FROM_KEYS = new Set(["source", "path", "streaming"]);
|
|
183
|
+
const FOREACH_STREAMING_KEYS = new Set(["enabled", "minChunk"]);
|
|
176
184
|
const NORMAL_ARTIFACT_KINDS = new Set([
|
|
177
185
|
"control",
|
|
178
186
|
"analysis",
|
|
@@ -262,8 +270,48 @@ function validateStageArray(value, path, issues) {
|
|
|
262
270
|
for (const [index, item] of value.entries()) {
|
|
263
271
|
validateStage(item, `${path}[${index}]`, ids, sourceIds, issues);
|
|
264
272
|
}
|
|
273
|
+
validateStreamingProducerDeclarations(value, path, issues);
|
|
265
274
|
validateStageDependencyGraph(value, path, issues);
|
|
266
275
|
}
|
|
276
|
+
function validateStreamingProducerDeclarations(stages, path, issues) {
|
|
277
|
+
const byId = new Map();
|
|
278
|
+
for (const stage of stages) {
|
|
279
|
+
if (isRecord(stage) && typeof stage.id === "string") {
|
|
280
|
+
byId.set(stage.id, stage);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
for (const [index, stage] of stages.entries()) {
|
|
284
|
+
if (!isRecord(stage) || !isRecord(stage.from))
|
|
285
|
+
continue;
|
|
286
|
+
const from = stage.from;
|
|
287
|
+
if (!isRecord(from.streaming) || from.streaming.enabled !== true)
|
|
288
|
+
continue;
|
|
289
|
+
const source = typeof from.source === "string" ? from.source : undefined;
|
|
290
|
+
const controlPath = typeof from.path === "string" ? from.path : undefined;
|
|
291
|
+
if (!source || !controlPath)
|
|
292
|
+
continue;
|
|
293
|
+
const sourceStage = byId.get(source);
|
|
294
|
+
const partialPaths = outputPartialPaths(sourceStage);
|
|
295
|
+
if (!partialPaths.includes(controlPath)) {
|
|
296
|
+
issues.push({
|
|
297
|
+
path: `${path}[${index}].from.streaming`,
|
|
298
|
+
message: `source stage "${source}" must declare output.partial.paths including "${controlPath}" to use streaming`,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function outputPartialPaths(stage) {
|
|
304
|
+
const output = isRecord(stage?.output) ? stage.output : undefined;
|
|
305
|
+
const partial = isRecord(output?.partial) ? output.partial : undefined;
|
|
306
|
+
return Array.isArray(partial?.paths)
|
|
307
|
+
? compactStrings(partial.paths, {
|
|
308
|
+
trim: false,
|
|
309
|
+
unique: false,
|
|
310
|
+
dropEmpty: false,
|
|
311
|
+
dropWhitespaceOnly: false,
|
|
312
|
+
})
|
|
313
|
+
: [];
|
|
314
|
+
}
|
|
267
315
|
function validateStageDependencyGraph(stages, path, issues) {
|
|
268
316
|
const ids = new Set(stages
|
|
269
317
|
.filter(isRecord)
|
|
@@ -334,7 +382,12 @@ function extractDependencyRefs(value) {
|
|
|
334
382
|
if (typeof value === "string")
|
|
335
383
|
return [value];
|
|
336
384
|
if (Array.isArray(value))
|
|
337
|
-
return value
|
|
385
|
+
return compactStrings(value, {
|
|
386
|
+
trim: false,
|
|
387
|
+
unique: false,
|
|
388
|
+
dropEmpty: false,
|
|
389
|
+
dropWhitespaceOnly: false,
|
|
390
|
+
});
|
|
338
391
|
if (isRecord(value) && typeof value.source === "string")
|
|
339
392
|
return [value.source];
|
|
340
393
|
return [];
|
|
@@ -445,7 +498,7 @@ function validateStage(value, path, siblingIds, sourceIds, issues) {
|
|
|
445
498
|
allowControlPath: false,
|
|
446
499
|
});
|
|
447
500
|
validateSourceProjection(stage.sourceProjection, `${path}.sourceProjection`, issues);
|
|
448
|
-
validateInputPolicy(stage.inputPolicy, `${path}.inputPolicy`, sourceIds, issues);
|
|
501
|
+
validateInputPolicy(stage.inputPolicy, `${path}.inputPolicy`, sourceIds, issues, stage.sourceProjection);
|
|
449
502
|
validateOutput(stage.output, `${path}.output`, issues);
|
|
450
503
|
validateSupportStage(stage, type, path, issues);
|
|
451
504
|
validateDynamicStage(stage, type, path, issues);
|
|
@@ -518,6 +571,19 @@ function validateControlPathRef(value, path, siblingIds, issues) {
|
|
|
518
571
|
message: "must be a control JSONPath starting with $.",
|
|
519
572
|
});
|
|
520
573
|
}
|
|
574
|
+
if (value.streaming !== undefined) {
|
|
575
|
+
validateForeachStreaming(value.streaming, `${path}.streaming`, issues);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function validateForeachStreaming(value, path, issues) {
|
|
579
|
+
const streaming = recordAt(value, path, issues);
|
|
580
|
+
if (!streaming)
|
|
581
|
+
return;
|
|
582
|
+
rejectUnknownKeys(streaming, FOREACH_STREAMING_KEYS, path, issues);
|
|
583
|
+
if (streaming.enabled !== true) {
|
|
584
|
+
issues.push({ path: `${path}.enabled`, message: "must be true" });
|
|
585
|
+
}
|
|
586
|
+
optionalPositiveInteger(streaming.minChunk, `${path}.minChunk`, issues);
|
|
521
587
|
}
|
|
522
588
|
function validateKnownStageRef(stageId, path, siblingIds, issues) {
|
|
523
589
|
if (stageId.trim() === "") {
|
|
@@ -539,6 +605,39 @@ function validateOutput(value, path, issues) {
|
|
|
539
605
|
optionalPositiveInteger(output.maxDigestChars, `${path}.maxDigestChars`, issues);
|
|
540
606
|
validateRequiredFlagObject(output.analysis, `${path}.analysis`, issues);
|
|
541
607
|
validateRefsOutputObject(output.refs, `${path}.refs`, issues);
|
|
608
|
+
validatePartialOutput(output.partial, `${path}.partial`, issues);
|
|
609
|
+
}
|
|
610
|
+
function validatePartialOutput(value, path, issues) {
|
|
611
|
+
if (value === undefined)
|
|
612
|
+
return;
|
|
613
|
+
const partial = recordAt(value, path, issues);
|
|
614
|
+
if (!partial)
|
|
615
|
+
return;
|
|
616
|
+
rejectUnknownKeys(partial, OUTPUT_PARTIAL_KEYS, path, issues);
|
|
617
|
+
if (!Array.isArray(partial.paths)) {
|
|
618
|
+
issues.push({ path: `${path}.paths`, message: "must be an array" });
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (partial.paths.length === 0) {
|
|
622
|
+
issues.push({ path: `${path}.paths`, message: "must not be empty" });
|
|
623
|
+
}
|
|
624
|
+
const seen = new Set();
|
|
625
|
+
for (const [index, item] of partial.paths.entries()) {
|
|
626
|
+
const itemPath = `${path}.paths[${index}]`;
|
|
627
|
+
if (typeof item !== "string" || item.trim() === "") {
|
|
628
|
+
issues.push({ path: itemPath, message: "must be a non-empty string" });
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (!item.startsWith("$.")) {
|
|
632
|
+
issues.push({
|
|
633
|
+
path: itemPath,
|
|
634
|
+
message: "must be a control JSONPath starting with $.",
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
if (seen.has(item))
|
|
638
|
+
issues.push({ path: itemPath, message: `duplicate value "${item}"` });
|
|
639
|
+
seen.add(item);
|
|
640
|
+
}
|
|
542
641
|
}
|
|
543
642
|
function validateControlSchemaRef(value, path, issues) {
|
|
544
643
|
if (value === undefined)
|
|
@@ -568,7 +667,7 @@ function validateRefsOutputObject(value, path, issues) {
|
|
|
568
667
|
optionalBoolean(object.required, `${path}.required`, issues);
|
|
569
668
|
optionalPositiveInteger(object.minItems, `${path}.minItems`, issues);
|
|
570
669
|
}
|
|
571
|
-
function validateInputPolicy(value, path, siblingIds, issues) {
|
|
670
|
+
function validateInputPolicy(value, path, siblingIds, issues, sourceProjection) {
|
|
572
671
|
if (value === undefined)
|
|
573
672
|
return;
|
|
574
673
|
const policy = recordAt(value, path, issues);
|
|
@@ -579,6 +678,29 @@ function validateInputPolicy(value, path, siblingIds, issues) {
|
|
|
579
678
|
if (policy.enforcement !== undefined && policy.enforcement !== "fail") {
|
|
580
679
|
issues.push({ path: `${path}.enforcement`, message: 'must be "fail"' });
|
|
581
680
|
}
|
|
681
|
+
if (policy.artifactAccess !== undefined &&
|
|
682
|
+
policy.artifactAccess !== "enabled" &&
|
|
683
|
+
policy.artifactAccess !== "none") {
|
|
684
|
+
issues.push({
|
|
685
|
+
path: `${path}.artifactAccess`,
|
|
686
|
+
message: 'must be "enabled" or "none"',
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
if (policy.artifactAccess === "none") {
|
|
690
|
+
if (Array.isArray(policy.requiredReads) &&
|
|
691
|
+
policy.requiredReads.length > 0) {
|
|
692
|
+
issues.push({
|
|
693
|
+
path: `${path}.requiredReads`,
|
|
694
|
+
message: 'must be empty when artifactAccess is "none"',
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
if (sourceProjection !== undefined) {
|
|
698
|
+
issues.push({
|
|
699
|
+
path: `${path}.artifactAccess`,
|
|
700
|
+
message: 'cannot be "none" when sourceProjection is declared',
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
582
704
|
}
|
|
583
705
|
function validateRequiredReads(value, path, sourceIds, issues) {
|
|
584
706
|
if (value === undefined)
|
package/dist/compiler.js
CHANGED
|
@@ -2,6 +2,7 @@ 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 { stringifyPromptJson } from "./prompt-json.js";
|
|
5
6
|
import { compileRole } from "./roles.js";
|
|
6
7
|
import { classifyToolCapability, effectiveToolClassification, providersForSelectedTools, resolveToolSelection, TOOL_NAME_PATTERN, toolAllowedByAuthorityCeiling, toolNameForSpec, } from "./tool-metadata.js";
|
|
7
8
|
import { WorkflowValidationError, WORKFLOW_RUN_TYPE, } from "./types.js";
|
|
@@ -102,7 +103,13 @@ function lowerArtifactGraphFrom(from) {
|
|
|
102
103
|
typeof from === "object" &&
|
|
103
104
|
!Array.isArray(from) &&
|
|
104
105
|
typeof from.source === "string") {
|
|
105
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
stage: from.source,
|
|
108
|
+
path: from.path,
|
|
109
|
+
...(from.streaming !== undefined
|
|
110
|
+
? { streaming: from.streaming }
|
|
111
|
+
: {}),
|
|
112
|
+
};
|
|
106
113
|
}
|
|
107
114
|
return from;
|
|
108
115
|
}
|
|
@@ -127,10 +134,23 @@ function appendWorkflowOutputInstructions(prompt, stage) {
|
|
|
127
134
|
: "Use schema `stage-control-v1` unless the workflow asks for a more specific control schema.",
|
|
128
135
|
"Put detailed prose, reasoning, and evidence discussion in <analysis> only.",
|
|
129
136
|
"Put structured evidence pointers in <refs> as a JSON array; use [] if none.",
|
|
137
|
+
...partialOutputInstructions(stage.output?.partial?.paths),
|
|
130
138
|
]
|
|
131
139
|
.filter(Boolean)
|
|
132
140
|
.join("\n\n");
|
|
133
141
|
}
|
|
142
|
+
function partialOutputInstructions(paths) {
|
|
143
|
+
if (!paths || paths.length === 0)
|
|
144
|
+
return [];
|
|
145
|
+
return [
|
|
146
|
+
"# Workflow Partial Output Protocol (optional)",
|
|
147
|
+
`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:`,
|
|
148
|
+
'<partial-control>{"schema":"workflow-partial-output-v1","path":"$.items","items":[{"id":"stable-id","...":"..."}]}</partial-control>',
|
|
149
|
+
"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.",
|
|
150
|
+
"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.",
|
|
151
|
+
"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.",
|
|
152
|
+
];
|
|
153
|
+
}
|
|
134
154
|
function artifactGraphTaskMetadata(stage, specDir) {
|
|
135
155
|
const controlSchema = stage.output?.controlSchema;
|
|
136
156
|
return {
|
|
@@ -144,8 +164,12 @@ function artifactGraphTaskMetadata(stage, specDir) {
|
|
|
144
164
|
? resolve(specDir, controlSchema)
|
|
145
165
|
: undefined,
|
|
146
166
|
maxDigestChars: stage.output?.maxDigestChars,
|
|
167
|
+
partial: stage.output?.partial
|
|
168
|
+
? { paths: [...stage.output.partial.paths] }
|
|
169
|
+
: undefined,
|
|
147
170
|
},
|
|
148
171
|
requiredReads: stage.inputPolicy?.requiredReads ?? [],
|
|
172
|
+
artifactAccess: stage.inputPolicy?.artifactAccess ?? "enabled",
|
|
149
173
|
sourceProjection: stage.sourceProjection,
|
|
150
174
|
};
|
|
151
175
|
}
|
|
@@ -457,7 +481,7 @@ async function compileArtifactGraphPlan(spec, options) {
|
|
|
457
481
|
typeof workflowInput === "object" &&
|
|
458
482
|
!Array.isArray(workflowInput) &&
|
|
459
483
|
Object.keys(workflowInput).length > 0
|
|
460
|
-
? `# Workflow Input\n\n${
|
|
484
|
+
? `# Workflow Input\n\n${stringifyPromptJson(workflowInput)}`
|
|
461
485
|
: "";
|
|
462
486
|
const runtimeOverrides = options.runtimeOverrides;
|
|
463
487
|
const runtimeDefaults = options.runtimeDefaults;
|
|
@@ -538,15 +562,26 @@ async function compileArtifactGraphPlan(spec, options) {
|
|
|
538
562
|
const injectRuntimeTaskInPrompt = (runtimeStageKind !== "foreach" && injectTask) ||
|
|
539
563
|
(runtimeStageKind === "foreach" && optInInjectRuntimeTask);
|
|
540
564
|
const normalizedPrompt = String(prompt ?? "").replace(/\$\{item\}/g, "the relevant item from the dependency context");
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
565
|
+
const instructionText = `# Instructions\n\n${normalizedPrompt}`;
|
|
566
|
+
const stageText = `# Workflow Stage\n\nstage=${stage.id}\ntype=${runtimeStageKind}`;
|
|
567
|
+
const taskText = injectRuntimeTaskInPrompt && options.task
|
|
568
|
+
? `# Task\n\n${options.task}`
|
|
569
|
+
: undefined;
|
|
570
|
+
const compiledPrompt = (runtimeStageKind === "foreach"
|
|
571
|
+
? [
|
|
572
|
+
taskText,
|
|
573
|
+
workflowInputText || undefined,
|
|
574
|
+
stageText,
|
|
575
|
+
roleText || undefined,
|
|
576
|
+
instructionText,
|
|
577
|
+
]
|
|
578
|
+
: [
|
|
579
|
+
taskText,
|
|
580
|
+
workflowInputText || undefined,
|
|
581
|
+
stageText,
|
|
582
|
+
instructionText,
|
|
583
|
+
roleText || undefined,
|
|
584
|
+
])
|
|
550
585
|
.filter(Boolean)
|
|
551
586
|
.join("\n\n");
|
|
552
587
|
const toolSelection = resolveToolSelection([spec.defaults?.tools, stage.tools], stageAgent.tools);
|
|
@@ -97,6 +97,7 @@ 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;
|
|
100
101
|
export declare function hashDynamicDecision(value: unknown): string;
|
|
101
102
|
export declare function dynamicLoopSignature(decision: NormalizedDynamicDecision): string;
|
|
102
103
|
export declare function writeDynamicDecisionArtifacts(input: DynamicDecisionArtifactWriteInput): Promise<DynamicDecisionArtifactWriteResult>;
|
package/dist/dynamic-decision.js
CHANGED
|
@@ -121,6 +121,13 @@ 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
|
+
}
|
|
124
131
|
export function hashDynamicDecision(value) {
|
|
125
132
|
return createHash("sha256")
|
|
126
133
|
.update(stableStringify(toJsonNormalizedValue(value)))
|
|
@@ -4,6 +4,7 @@ import { DynamicControllerBudgetBlocked } from "./dynamic-controller-errors.js";
|
|
|
4
4
|
import { isDynamicOutputProfile, } from "./dynamic-profiles.js";
|
|
5
5
|
import { readOrRebuildDynamicState } from "./dynamic-state.js";
|
|
6
6
|
import { sanitizeTaskId } from "./engine-run-graph.js";
|
|
7
|
+
import { compactStrings } from "./strings.js";
|
|
7
8
|
import { fromProjectPath, isTerminalTaskStatus, readJson } from "./store.js";
|
|
8
9
|
import { classifyToolCapability, effectiveToolClassification, providersForSelectedTools, toolAllowedByAuthorityCeiling, } from "./tool-metadata.js";
|
|
9
10
|
import { resolveWorkflowRuntime, selectWorkflowRuntime, } from "./workflow-runtime.js";
|
|
@@ -173,6 +174,7 @@ export async function buildDynamicGeneratedCompiledTask(input) {
|
|
|
173
174
|
maxDigestChars: DYNAMIC_OUTPUT_MAX_DIGEST_CHARS,
|
|
174
175
|
},
|
|
175
176
|
requiredReads: input.request.requiredReads,
|
|
177
|
+
artifactAccess: "enabled",
|
|
176
178
|
sourceProjection: dynamicInputSourceProjection(input.request.inputs),
|
|
177
179
|
},
|
|
178
180
|
dynamicGenerated: {
|
|
@@ -630,5 +632,5 @@ function dynamicOutputProfileInstructions(outputProfile) {
|
|
|
630
632
|
return `# Dynamic Output Profile: ${outputProfile}\nEmit control JSON suitable for this output profile and surface gaps/blockers explicitly.`;
|
|
631
633
|
}
|
|
632
634
|
function uniqueStrings(values) {
|
|
633
|
-
return
|
|
635
|
+
return compactStrings(values, { trim: false, dropWhitespaceOnly: true });
|
|
634
636
|
}
|
|
@@ -5,3 +5,4 @@ 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(): DynamicOutputProfile[];
|
package/dist/dynamic-profiles.js
CHANGED
|
@@ -26,3 +26,6 @@ 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
|
+
}
|
|
@@ -10,6 +10,8 @@ export declare function compiledTaskSpecId(task: CompiledTask): string;
|
|
|
10
10
|
export declare function loopStageIdSet(compiledFlow: CompiledWorkflow): Set<string>;
|
|
11
11
|
export declare function nextTaskRecordIndex(run: WorkflowRunRecord): number;
|
|
12
12
|
export declare function dependenciesReady(compiledTask: CompiledTask, bySpecId: Map<string, WorkflowTaskRunRecord>, compiledFlow: CompiledWorkflow): boolean;
|
|
13
|
+
export declare function foreachStreamingEnabled(compiledTask: CompiledTask): boolean;
|
|
14
|
+
export declare function foreachStreamingMinChunk(compiledTask: CompiledTask): number;
|
|
13
15
|
export declare function buildForeachGeneratedTasks(template: CompiledTask, runtimeTask: string | undefined, items: unknown[]): {
|
|
14
16
|
tasks: CompiledTask[];
|
|
15
17
|
error?: string;
|
package/dist/engine-run-graph.js
CHANGED
|
@@ -95,9 +95,14 @@ export function reconcileDynamicGeneratedRunRecords(cwd, run, compiledFlow) {
|
|
|
95
95
|
export function reconcileForeachGeneratedRunRecords(cwd, run, compiledFlow) {
|
|
96
96
|
let changed = false;
|
|
97
97
|
const compiledSpecIds = new Set(compiledFlow.tasks.map((task) => compiledTaskSpecId(task)));
|
|
98
|
+
const compiledTaskBySpecId = new Map(compiledFlow.tasks.map((task) => [compiledTaskSpecId(task), task]));
|
|
98
99
|
const placeholderToGeneratedSpecIds = new Map();
|
|
100
|
+
const streamingPlaceholderSpecIds = new Set();
|
|
99
101
|
for (const compiledTask of compiledFlow.tasks) {
|
|
100
102
|
const specId = compiledTaskSpecId(compiledTask);
|
|
103
|
+
if (compiledTask.foreach && foreachStreamingEnabled(compiledTask)) {
|
|
104
|
+
streamingPlaceholderSpecIds.add(specId);
|
|
105
|
+
}
|
|
101
106
|
const placeholderSpecId = foreachGeneratedPlaceholderSpecId(compiledTask, compiledFlow, specId);
|
|
102
107
|
if (!placeholderSpecId)
|
|
103
108
|
continue;
|
|
@@ -117,6 +122,12 @@ export function reconcileForeachGeneratedRunRecords(cwd, run, compiledFlow) {
|
|
|
117
122
|
const generatedSpecIds = placeholderToGeneratedSpecIds.get(task.specId);
|
|
118
123
|
let placeholderSpecId = foreachGeneratedPlaceholderSpecId(task, compiledFlow, task.specId);
|
|
119
124
|
if (generatedSpecIds && !placeholderSpecId) {
|
|
125
|
+
const compiledTask = compiledTaskBySpecId.get(task.specId);
|
|
126
|
+
if (compiledTask?.foreach &&
|
|
127
|
+
streamingPlaceholderSpecIds.has(task.specId)) {
|
|
128
|
+
filteredRunTasks.push(task);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
120
131
|
if (generatedSpecIds.includes(task.specId)) {
|
|
121
132
|
placeholderSpecId = task.specId;
|
|
122
133
|
task.foreachGenerated = { placeholderSpecId };
|
|
@@ -183,7 +194,7 @@ export function reconcileForeachGeneratedRunRecords(cwd, run, compiledFlow) {
|
|
|
183
194
|
for (const task of reordered) {
|
|
184
195
|
if (!task.dependsOn)
|
|
185
196
|
continue;
|
|
186
|
-
const replaced = replaceForeachGeneratedDependencies(task.dependsOn, placeholderToGeneratedSpecIds);
|
|
197
|
+
const replaced = replaceForeachGeneratedDependencies(task.dependsOn, placeholderToGeneratedSpecIds, streamingPlaceholderSpecIds);
|
|
187
198
|
if (!sameStringList(task.dependsOn, replaced)) {
|
|
188
199
|
task.dependsOn = replaced;
|
|
189
200
|
changed = true;
|
|
@@ -261,12 +272,15 @@ function foreachPlaceholderSpecId(compiledFlow, stageId) {
|
|
|
261
272
|
return undefined;
|
|
262
273
|
return `${stageId}.item`;
|
|
263
274
|
}
|
|
264
|
-
function replaceForeachGeneratedDependencies(dependsOn, placeholderToGeneratedSpecIds) {
|
|
275
|
+
function replaceForeachGeneratedDependencies(dependsOn, placeholderToGeneratedSpecIds, keepPlaceholderSpecIds = new Set()) {
|
|
265
276
|
const replaced = [];
|
|
266
277
|
for (const dep of dependsOn) {
|
|
267
278
|
const generatedSpecIds = placeholderToGeneratedSpecIds.get(dep);
|
|
268
|
-
if (generatedSpecIds)
|
|
279
|
+
if (generatedSpecIds) {
|
|
280
|
+
if (keepPlaceholderSpecIds.has(dep))
|
|
281
|
+
replaced.push(dep);
|
|
269
282
|
replaced.push(...generatedSpecIds);
|
|
283
|
+
}
|
|
270
284
|
else
|
|
271
285
|
replaced.push(dep);
|
|
272
286
|
}
|
|
@@ -341,6 +355,29 @@ export function dependenciesReady(compiledTask, bySpecId, compiledFlow) {
|
|
|
341
355
|
if (deps.length === 0)
|
|
342
356
|
return true;
|
|
343
357
|
const partial = stageSourcePolicy(compiledFlow, compiledTask.stageId ?? "") === "partial";
|
|
358
|
+
if (foreachStreamingEnabled(compiledTask)) {
|
|
359
|
+
let completedDependencyReady = false;
|
|
360
|
+
let runningDependencyMayHavePartialItems = false;
|
|
361
|
+
let allKnownDependenciesTerminal = true;
|
|
362
|
+
for (const dep of deps) {
|
|
363
|
+
const status = bySpecId.get(dep)?.status;
|
|
364
|
+
if (status === "completed") {
|
|
365
|
+
completedDependencyReady = true;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
if (status && isTerminalTaskStatus(status)) {
|
|
369
|
+
if (!partial)
|
|
370
|
+
return false;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (status === "running")
|
|
374
|
+
runningDependencyMayHavePartialItems = true;
|
|
375
|
+
allKnownDependenciesTerminal = false;
|
|
376
|
+
}
|
|
377
|
+
return (completedDependencyReady ||
|
|
378
|
+
runningDependencyMayHavePartialItems ||
|
|
379
|
+
allKnownDependenciesTerminal);
|
|
380
|
+
}
|
|
344
381
|
return deps.every((dep) => {
|
|
345
382
|
const status = bySpecId.get(dep)?.status;
|
|
346
383
|
if (status === "completed")
|
|
@@ -350,6 +387,18 @@ export function dependenciesReady(compiledTask, bySpecId, compiledFlow) {
|
|
|
350
387
|
return false;
|
|
351
388
|
});
|
|
352
389
|
}
|
|
390
|
+
export function foreachStreamingEnabled(compiledTask) {
|
|
391
|
+
const streaming = compiledTask.foreach?.from?.streaming;
|
|
392
|
+
return Boolean(streaming &&
|
|
393
|
+
typeof streaming === "object" &&
|
|
394
|
+
streaming.enabled === true);
|
|
395
|
+
}
|
|
396
|
+
export function foreachStreamingMinChunk(compiledTask) {
|
|
397
|
+
const value = compiledTask.foreach?.from?.streaming?.minChunk;
|
|
398
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
399
|
+
? Math.floor(value)
|
|
400
|
+
: 1;
|
|
401
|
+
}
|
|
353
402
|
export function buildForeachGeneratedTasks(template, runtimeTask, items) {
|
|
354
403
|
const seen = new Set();
|
|
355
404
|
const tasks = [];
|
|
@@ -368,9 +417,10 @@ export function buildForeachGeneratedTasks(template, runtimeTask, items) {
|
|
|
368
417
|
template.foreach.injectRuntimeTask && runtimeTask
|
|
369
418
|
? `# Task\n\n${runtimeTask}`
|
|
370
419
|
: undefined,
|
|
371
|
-
`# Workflow Stage\n\nstage=${template.stageId}\ntype=foreach
|
|
372
|
-
`# Instructions\n\n${instructions}`,
|
|
420
|
+
`# Workflow Stage\n\nstage=${template.stageId}\ntype=foreach`,
|
|
373
421
|
template.foreach.roleText || undefined,
|
|
422
|
+
`# Workflow Item\n\nitem=${taskId}`,
|
|
423
|
+
`# Instructions\n\n${instructions}`,
|
|
374
424
|
]
|
|
375
425
|
.filter(Boolean)
|
|
376
426
|
.join("\n\n");
|