@agwab/pi-workflow 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/compiler.d.ts +4 -6
- package/dist/compiler.js +70 -39
- package/dist/dynamic-decision.d.ts +0 -1
- package/dist/dynamic-decision.js +0 -7
- package/dist/dynamic-generated-task-runtime.d.ts +2 -0
- package/dist/dynamic-generated-task-runtime.js +21 -8
- package/dist/dynamic-profiles.d.ts +0 -1
- package/dist/dynamic-profiles.js +0 -3
- package/dist/engine-run-graph.d.ts +1 -0
- package/dist/engine-run-graph.js +142 -2
- package/dist/engine.d.ts +10 -6
- package/dist/engine.js +146 -77
- package/dist/extension.d.ts +2 -1
- package/dist/extension.js +38 -15
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -1
- package/dist/store.d.ts +3 -1
- package/dist/store.js +189 -49
- package/dist/subagent-backend.d.ts +4 -0
- package/dist/subagent-backend.js +281 -31
- package/dist/types.d.ts +9 -1
- package/dist/workflow-runtime.d.ts +2 -0
- package/dist/workflow-runtime.js +40 -1
- package/dist/workflow-view.js +3 -1
- package/dist/workflow-web-source-extension.js +167 -48
- package/dist/workflow-web-source.d.ts +2 -1
- package/dist/workflow-web-source.js +84 -19
- package/docs/usage.md +11 -0
- 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 +995 -573
- 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 +1352 -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/src/compiler.ts +127 -66
- package/src/dynamic-decision.ts +0 -11
- package/src/dynamic-generated-task-runtime.ts +47 -12
- package/src/dynamic-profiles.ts +0 -4
- package/src/engine-run-graph.ts +185 -2
- package/src/engine.ts +192 -107
- package/src/extension.ts +50 -17
- package/src/index.ts +3 -1
- package/src/store.ts +253 -55
- package/src/subagent-backend.ts +369 -32
- package/src/types.ts +13 -1
- package/src/workflow-runtime.ts +53 -2
- package/src/workflow-view.ts +2 -1
- package/src/workflow-web-source-extension.ts +621 -228
- package/src/workflow-web-source.ts +118 -28
- package/workflows/deep-research/helpers/claim-evidence-gate.mjs +56 -16
- package/workflows/deep-research/helpers/final-audit-packet.mjs +1 -4
- package/workflows/deep-research/helpers/normalize-input-packet.mjs +1 -1
- package/workflows/deep-research/helpers/render-executive.mjs +8 -21
- package/workflows/deep-research/helpers/sanitize-verification-candidates.mjs +89 -15
- package/workflows/deep-research/schemas/deep-research-executive-render-control.schema.json +0 -1
- package/workflows/deep-research/schemas/deep-research-verify-claims-control.schema.json +4 -1
- 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/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
|
@@ -79,6 +79,13 @@ export interface PiJsonParseResult {
|
|
|
79
79
|
metadata: Partial<ResultMetadata>;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
export interface ContextLengthResolution {
|
|
83
|
+
rawContextLengthExceeded: boolean;
|
|
84
|
+
contextLengthExceeded: boolean;
|
|
85
|
+
contextOverflowRecovered: boolean;
|
|
86
|
+
recoveredStreamErrors: string[];
|
|
87
|
+
}
|
|
88
|
+
|
|
82
89
|
const CONTEXT_LENGTH_ERROR_PATTERN =
|
|
83
90
|
/\bcontext[_ -]?length[_ -]?exceeded\b|\bcontext[_ -]?window[_ -]?(?:exceeded|overflow|exhausted)\b|\b(?:maximum|max)[_ -]?context[_ -]?length\b|\btoo many tokens\b|\b(?:prompt|input|request)[^\n]{0,80}\btoo large\b|\bcontext_length_exceeded\b/i;
|
|
84
91
|
|
|
@@ -94,6 +101,32 @@ export function detectContextLengthExceeded(signals: {
|
|
|
94
101
|
return CONTEXT_LENGTH_ERROR_PATTERN.test(text);
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
export function resolveContextLengthState(
|
|
105
|
+
parsed: PiJsonParseResult,
|
|
106
|
+
rawContextLengthExceeded: boolean,
|
|
107
|
+
): ContextLengthResolution {
|
|
108
|
+
const contextOverflowRecovered =
|
|
109
|
+
rawContextLengthExceeded && finalAssistantSucceeded(parsed);
|
|
110
|
+
return {
|
|
111
|
+
rawContextLengthExceeded,
|
|
112
|
+
contextLengthExceeded:
|
|
113
|
+
rawContextLengthExceeded && !contextOverflowRecovered,
|
|
114
|
+
contextOverflowRecovered,
|
|
115
|
+
recoveredStreamErrors: contextOverflowRecovered
|
|
116
|
+
? parsed.errors.filter((error) =>
|
|
117
|
+
detectContextLengthExceeded({ errors: [error] }),
|
|
118
|
+
)
|
|
119
|
+
: [],
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function finalAssistantSucceeded(parsed: PiJsonParseResult): boolean {
|
|
124
|
+
return (
|
|
125
|
+
parsed.finalAssistantText.length > 0 &&
|
|
126
|
+
parsed.metadata.stopReason !== "error"
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
97
130
|
function normalizeTimeoutMs(timeoutMs: number | undefined): number | undefined {
|
|
98
131
|
if (timeoutMs === undefined) return undefined;
|
|
99
132
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
@@ -390,18 +423,29 @@ export function resolvePiJsonOutcome(
|
|
|
390
423
|
|
|
391
424
|
export function resultMetadataFromParse(
|
|
392
425
|
parsed: PiJsonParseResult,
|
|
393
|
-
|
|
426
|
+
contextLength: ContextLengthResolution,
|
|
394
427
|
outcome: ProcessOutcome,
|
|
395
428
|
): Partial<ResultMetadata> {
|
|
396
429
|
return {
|
|
397
430
|
...parsed.metadata,
|
|
398
|
-
contextLengthExceeded,
|
|
431
|
+
contextLengthExceeded: contextLength.contextLengthExceeded,
|
|
432
|
+
...(contextLength.contextOverflowRecovered
|
|
433
|
+
? { contextOverflowRecovered: true }
|
|
434
|
+
: {}),
|
|
399
435
|
...(parsed.errors.length === 0
|
|
400
436
|
? {}
|
|
401
437
|
: { streamErrors: parsed.errors.slice(0, MAX_METADATA_ERRORS) }),
|
|
402
438
|
...(outcome.status === "completed" && parsed.errors.length > 0
|
|
403
439
|
? { nonFatalStreamErrors: parsed.errors.slice(0, MAX_METADATA_ERRORS) }
|
|
404
440
|
: {}),
|
|
441
|
+
...(contextLength.recoveredStreamErrors.length === 0
|
|
442
|
+
? {}
|
|
443
|
+
: {
|
|
444
|
+
recoveredStreamErrors: contextLength.recoveredStreamErrors.slice(
|
|
445
|
+
0,
|
|
446
|
+
MAX_METADATA_ERRORS,
|
|
447
|
+
),
|
|
448
|
+
}),
|
|
405
449
|
...(parsed.parseErrors.length === 0
|
|
406
450
|
? {}
|
|
407
451
|
: { parseErrors: parsed.parseErrors.slice(0, MAX_METADATA_ERRORS) }),
|
|
@@ -774,14 +818,18 @@ export async function runHeadlessModel(
|
|
|
774
818
|
stderrText,
|
|
775
819
|
stderrContextLengthExceeded,
|
|
776
820
|
} = processResult;
|
|
777
|
-
const
|
|
821
|
+
const rawContextLengthExceeded =
|
|
778
822
|
stderrContextLengthExceeded ||
|
|
779
823
|
detectContextLengthExceeded({ stderrText, errors: parsed.errors });
|
|
824
|
+
const contextLength = resolveContextLengthState(
|
|
825
|
+
parsed,
|
|
826
|
+
rawContextLengthExceeded,
|
|
827
|
+
);
|
|
780
828
|
|
|
781
829
|
const outcome = resolvePiJsonOutcome(
|
|
782
830
|
processOutcome,
|
|
783
831
|
parsed,
|
|
784
|
-
contextLengthExceeded,
|
|
832
|
+
contextLength.contextLengthExceeded,
|
|
785
833
|
);
|
|
786
834
|
|
|
787
835
|
const completedAt = new Date();
|
|
@@ -811,7 +859,7 @@ export async function runHeadlessModel(
|
|
|
811
859
|
artifacts,
|
|
812
860
|
correlationId: options.correlationId,
|
|
813
861
|
metadata: {
|
|
814
|
-
...resultMetadataFromParse(parsed,
|
|
862
|
+
...resultMetadataFromParse(parsed, contextLength, outcome),
|
|
815
863
|
...sessionMetadata,
|
|
816
864
|
...(options.parentSessionId === undefined
|
|
817
865
|
? {}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
detectContextLengthExceeded,
|
|
21
21
|
parsePiJsonFile,
|
|
22
22
|
parsePiJsonLines,
|
|
23
|
+
resolveContextLengthState,
|
|
23
24
|
resolvePiJsonOutcome,
|
|
24
25
|
resultMetadataFromParse,
|
|
25
26
|
resultSessionMetadata,
|
|
@@ -157,9 +158,7 @@ function workerScript(
|
|
|
157
158
|
return `import { spawn } from "node:child_process";\nimport { appendFileSync, closeSync, openSync, writeFileSync } from "node:fs";\nconst argv = ${JSON.stringify(argv)};\nconst cwd = ${JSON.stringify(cwd)};\nconst eventPath = ${JSON.stringify(eventPath)};\nconst stderrPath = ${JSON.stringify(stderrPath)};\nconst metaPath = ${JSON.stringify(metaPath)};\nconst messageUpdatePattern = /"type"\\s*:\\s*"message_update"/;\nconst maxStdoutLogLineChars = 64 * 1024 * 1024;\ncloseSync(openSync(eventPath, "w"));\ncloseSync(openSync(stderrPath, "w"));\nlet settled = false;\nlet stdoutBuffer = "";\nlet discardingOversizedLine = false;\nlet omittedMessageUpdates = 0;\nlet omittedMessageUpdateBytes = 0;\nlet omittedOversizedLines = 0;\nlet omittedOversizedBytes = 0;\nfunction writeStdoutLine(line) {\n if (messageUpdatePattern.test(line)) {\n omittedMessageUpdates += 1;\n omittedMessageUpdateBytes += Buffer.byteLength(line, "utf8");\n return;\n }\n appendFileSync(eventPath, line);\n process.stdout.write(line);\n}\nfunction handleStdoutChunk(chunk) {\n let text = chunk.toString("utf8");\n while (text.length > 0) {\n if (discardingOversizedLine) {\n const newline = text.indexOf("\\n");\n omittedOversizedBytes += Buffer.byteLength(newline < 0 ? text : text.slice(0, newline + 1), "utf8");\n if (newline < 0) return;\n discardingOversizedLine = false;\n text = text.slice(newline + 1);\n continue;\n }\n const newline = text.indexOf("\\n");\n const segment = newline < 0 ? text : text.slice(0, newline + 1);\n stdoutBuffer += segment;\n text = newline < 0 ? "" : text.slice(newline + 1);\n if (stdoutBuffer.length > maxStdoutLogLineChars) {\n omittedOversizedLines += 1;\n omittedOversizedBytes += Buffer.byteLength(stdoutBuffer, "utf8");\n stdoutBuffer = "";\n discardingOversizedLine = newline < 0;\n continue;\n }\n if (newline >= 0) {\n writeStdoutLine(stdoutBuffer);\n stdoutBuffer = "";\n }\n }\n}\nfunction finishStdoutFilter() {\n if (!discardingOversizedLine && stdoutBuffer.length > 0) writeStdoutLine(stdoutBuffer);\n stdoutBuffer = "";\n if (omittedMessageUpdates > 0 || omittedOversizedLines > 0) {\n appendFileSync(eventPath, JSON.stringify({ type: "pi-subagent.stdout_filter", omitted: { messageUpdateEvents: omittedMessageUpdates, messageUpdateBytes: omittedMessageUpdateBytes, oversizedLines: omittedOversizedLines, oversizedBytes: omittedOversizedBytes }, reason: "cumulative message_update snapshots are omitted from durable stdout artifacts; final assistant text is stored in output.log" }) + "\\n");\n }\n}\nfunction writeMeta(meta) {\n if (settled) return;\n settled = true;\n finishStdoutFilter();\n writeFileSync(metaPath, JSON.stringify(meta, null, 2) + "\\n");\n}\nconst env = { ...process.env };\ndelete env.TMUX;\nconst child = spawn(argv[0], argv.slice(1), { cwd, shell: false, stdio: ["ignore", "pipe", "pipe"], env });\nchild.stdout?.on("data", handleStdoutChunk);\nchild.stderr?.on("data", (chunk) => { appendFileSync(stderrPath, chunk); process.stderr.write(chunk); });\nchild.on("error", () => { writeMeta({ status: "failed", failureKind: "spawn", exitCode: null, signal: null }); });\nchild.on("close", (exitCode, signal) => {\n const failureKind = exitCode === 0 ? null : "exit";\n writeMeta({ status: failureKind === null ? "completed" : "failed", failureKind, exitCode, signal });\n});\n`;
|
|
158
159
|
}
|
|
159
160
|
|
|
160
|
-
async function runTmuxProcess(
|
|
161
|
-
options: RunTmuxProcessOptions,
|
|
162
|
-
): Promise<{
|
|
161
|
+
async function runTmuxProcess(options: RunTmuxProcessOptions): Promise<{
|
|
163
162
|
result: TmuxRunResult | null;
|
|
164
163
|
store: Awaited<ReturnType<typeof createAttemptArtifactStore>>;
|
|
165
164
|
cwd: string;
|
|
@@ -439,11 +438,19 @@ export async function runTmuxModel(
|
|
|
439
438
|
parsePiJsonLines(""),
|
|
440
439
|
);
|
|
441
440
|
await unlink(result.eventPath).catch(() => undefined);
|
|
442
|
-
const
|
|
441
|
+
const rawContextLengthExceeded = detectContextLengthExceeded({
|
|
443
442
|
stderrText,
|
|
444
443
|
errors: parsed.errors,
|
|
445
444
|
});
|
|
446
|
-
const
|
|
445
|
+
const contextLength = resolveContextLengthState(
|
|
446
|
+
parsed,
|
|
447
|
+
rawContextLengthExceeded,
|
|
448
|
+
);
|
|
449
|
+
const meta = resolvePiJsonOutcome(
|
|
450
|
+
result.meta,
|
|
451
|
+
parsed,
|
|
452
|
+
contextLength.contextLengthExceeded,
|
|
453
|
+
);
|
|
447
454
|
|
|
448
455
|
const outputRef = await store.writeTextArtifact(
|
|
449
456
|
"output",
|
|
@@ -464,7 +471,7 @@ export async function runTmuxModel(
|
|
|
464
471
|
tmux: result.tmux,
|
|
465
472
|
correlationId: options.correlationId,
|
|
466
473
|
metadata: {
|
|
467
|
-
...resultMetadataFromParse(parsed,
|
|
474
|
+
...resultMetadataFromParse(parsed, contextLength, meta),
|
|
468
475
|
...sessionMetadata,
|
|
469
476
|
...(options.parentSessionId === undefined
|
|
470
477
|
? {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agwab/pi-workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Workflow orchestration for Pi subagents.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
"node": ">=22.19.0"
|
|
77
77
|
},
|
|
78
78
|
"dependencies": {
|
|
79
|
-
"@agwab/pi-subagent": "^0.
|
|
79
|
+
"@agwab/pi-subagent": "^0.4.0",
|
|
80
80
|
"pi-web-access": "^0.10.7",
|
|
81
81
|
"typebox": "^1.1.39"
|
|
82
82
|
},
|
package/src/compiler.ts
CHANGED
|
@@ -3,6 +3,7 @@ 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 { compileRole } from "./roles.js";
|
|
6
7
|
import {
|
|
7
8
|
classifyToolCapability,
|
|
8
9
|
effectiveToolClassification,
|
|
@@ -33,7 +34,10 @@ import {
|
|
|
33
34
|
} from "./types.js";
|
|
34
35
|
import {
|
|
35
36
|
resolveWorkflowRuntime,
|
|
37
|
+
selectWorkflowRuntime,
|
|
36
38
|
type WorkflowModelInfo,
|
|
39
|
+
type WorkflowRuntimeDefaults,
|
|
40
|
+
type WorkflowRuntimeResolutionInput,
|
|
37
41
|
} from "./workflow-runtime.js";
|
|
38
42
|
|
|
39
43
|
const DELEGATION_TOOLS = new Set([
|
|
@@ -549,7 +553,8 @@ export async function compileWorkflow(
|
|
|
549
553
|
spec: ArtifactGraphWorkflowSpec,
|
|
550
554
|
options: CompileOptions & {
|
|
551
555
|
task?: string;
|
|
552
|
-
|
|
556
|
+
runtimeOverrides?: WorkflowRuntimeDefaults;
|
|
557
|
+
runtimeDefaults?: WorkflowRuntimeDefaults;
|
|
553
558
|
},
|
|
554
559
|
): Promise<any> {
|
|
555
560
|
const compilePlan = buildArtifactGraphCompilePlan(spec, options);
|
|
@@ -615,11 +620,25 @@ async function collectForeachPathWarnings(
|
|
|
615
620
|
return warnings;
|
|
616
621
|
}
|
|
617
622
|
|
|
623
|
+
function runtimeSettings(value: unknown): WorkflowRuntimeDefaults | undefined {
|
|
624
|
+
if (!isPlainRecord(value)) return undefined;
|
|
625
|
+
const model =
|
|
626
|
+
typeof value.model === "string" && value.model.trim()
|
|
627
|
+
? value.model.trim()
|
|
628
|
+
: undefined;
|
|
629
|
+
const thinking =
|
|
630
|
+
typeof value.thinking === "string" && value.thinking.trim()
|
|
631
|
+
? (value.thinking.trim() as ThinkingLevel)
|
|
632
|
+
: undefined;
|
|
633
|
+
return model || thinking ? { model, thinking } : undefined;
|
|
634
|
+
}
|
|
635
|
+
|
|
618
636
|
async function compileArtifactGraphPlan(
|
|
619
637
|
spec: any,
|
|
620
638
|
options: CompileOptions & {
|
|
621
639
|
task?: string;
|
|
622
|
-
|
|
640
|
+
runtimeOverrides?: WorkflowRuntimeDefaults;
|
|
641
|
+
runtimeDefaults?: WorkflowRuntimeDefaults;
|
|
623
642
|
},
|
|
624
643
|
): Promise<any> {
|
|
625
644
|
const stages = spec.stages;
|
|
@@ -645,15 +664,19 @@ async function compileArtifactGraphPlan(
|
|
|
645
664
|
return defaultAgent;
|
|
646
665
|
};
|
|
647
666
|
const roleEntries = Object.entries(spec.roles ?? {});
|
|
648
|
-
const roles =
|
|
649
|
-
name,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
667
|
+
const roles = await Promise.all(
|
|
668
|
+
roleEntries.map(async ([name, role]: [string, any]) => {
|
|
669
|
+
const sourceAgent = role.fromAgent
|
|
670
|
+
? await loadWorkflowAgent(
|
|
671
|
+
role.fromAgent,
|
|
672
|
+
options.cwd,
|
|
673
|
+
agentCache,
|
|
674
|
+
`$.roles.${name}.fromAgent`,
|
|
675
|
+
)
|
|
676
|
+
: undefined;
|
|
677
|
+
return compileRole(name, role, sourceAgent);
|
|
678
|
+
}),
|
|
679
|
+
);
|
|
657
680
|
const roleText = roles.length
|
|
658
681
|
? `# Role Context\n\n${roles.map((r) => `## Role: ${r.name}\n${r.content}`).join("\n\n")}`
|
|
659
682
|
: "";
|
|
@@ -665,9 +688,9 @@ async function compileArtifactGraphPlan(
|
|
|
665
688
|
Object.keys(workflowInput).length > 0
|
|
666
689
|
? `# Workflow Input\n\n${JSON.stringify(workflowInput, null, 2)}`
|
|
667
690
|
: "";
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
|
|
691
|
+
const runtimeOverrides = options.runtimeOverrides;
|
|
692
|
+
const runtimeDefaults = options.runtimeDefaults;
|
|
693
|
+
const specRuntimeDefaults = runtimeSettings(spec.defaults);
|
|
671
694
|
const tasks: any[] = [];
|
|
672
695
|
const stageRecords: any[] = [];
|
|
673
696
|
const issues: ValidationIssue[] = [];
|
|
@@ -719,6 +742,22 @@ async function compileArtifactGraphPlan(
|
|
|
719
742
|
dynamicToolPath,
|
|
720
743
|
);
|
|
721
744
|
const dynamicToolSelection = filterToolSelection(rawDynamicToolSelection);
|
|
745
|
+
const requestedRuntime = selectWorkflowRuntime(
|
|
746
|
+
runtimeOverrides,
|
|
747
|
+
runtimeSettings(stage),
|
|
748
|
+
runtimeDefaults,
|
|
749
|
+
specRuntimeDefaults,
|
|
750
|
+
);
|
|
751
|
+
const resolvedDynamicRuntime = await resolveWorkflowRuntime(
|
|
752
|
+
requestedRuntime,
|
|
753
|
+
{
|
|
754
|
+
taskKey: key,
|
|
755
|
+
stageId: stage.id,
|
|
756
|
+
taskId,
|
|
757
|
+
agent: "dynamic",
|
|
758
|
+
},
|
|
759
|
+
{ availableModels: options.availableModels },
|
|
760
|
+
);
|
|
722
761
|
const dynamicTask = buildDynamicTask(
|
|
723
762
|
stage,
|
|
724
763
|
taskId,
|
|
@@ -729,24 +768,22 @@ async function compileArtifactGraphPlan(
|
|
|
729
768
|
specDir,
|
|
730
769
|
workflowInputText,
|
|
731
770
|
options.task,
|
|
732
|
-
|
|
733
|
-
defaultThinking,
|
|
734
|
-
overrides,
|
|
735
|
-
);
|
|
736
|
-
const resolvedDynamicRuntime = await resolveWorkflowRuntime(
|
|
737
|
-
{ model: defaultModel, thinking: defaultThinking },
|
|
771
|
+
resolvedDynamicRuntime,
|
|
738
772
|
{
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
773
|
+
runtimeOverrides,
|
|
774
|
+
runtimeDefaults,
|
|
775
|
+
specRuntimeDefaults,
|
|
776
|
+
stageRuntime: runtimeSettings(stage),
|
|
743
777
|
},
|
|
744
|
-
|
|
778
|
+
overrides,
|
|
745
779
|
);
|
|
746
780
|
dynamicTask.runtime = {
|
|
747
781
|
...dynamicTask.runtime,
|
|
748
782
|
...resolvedDynamicRuntime,
|
|
749
783
|
};
|
|
784
|
+
if (options.availableModels?.length) {
|
|
785
|
+
dynamicTask.dynamic.availableModels = options.availableModels;
|
|
786
|
+
}
|
|
750
787
|
if (dynamicToolSelection.tools || dynamicToolSelection.toolProviders) {
|
|
751
788
|
dynamicTask.runtime = {
|
|
752
789
|
...dynamicTask.runtime,
|
|
@@ -823,15 +860,12 @@ async function compileArtifactGraphPlan(
|
|
|
823
860
|
validateToolSubset(toolSelection.tools, stageAgent, issues, toolPath);
|
|
824
861
|
validateDelegationBoundary(toolSelection.tools, issues, toolPath);
|
|
825
862
|
const filteredToolSelection = filterToolSelection(toolSelection);
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
stage.thinking ??
|
|
833
|
-
spec.defaults?.thinking,
|
|
834
|
-
};
|
|
863
|
+
const requestedRuntime = selectWorkflowRuntime(
|
|
864
|
+
runtimeOverrides,
|
|
865
|
+
runtimeSettings(stage),
|
|
866
|
+
runtimeDefaults,
|
|
867
|
+
specRuntimeDefaults,
|
|
868
|
+
);
|
|
835
869
|
const resolvedRuntime = await resolveWorkflowRuntime(
|
|
836
870
|
requestedRuntime,
|
|
837
871
|
{
|
|
@@ -1208,12 +1242,34 @@ async function compileArtifactGraphPlan(
|
|
|
1208
1242
|
tasks,
|
|
1209
1243
|
warnings,
|
|
1210
1244
|
budget: {
|
|
1211
|
-
models:
|
|
1245
|
+
models: budgetModelRows(tasks),
|
|
1212
1246
|
unratedModels: [],
|
|
1213
1247
|
},
|
|
1214
1248
|
};
|
|
1215
1249
|
}
|
|
1216
1250
|
|
|
1251
|
+
function budgetModelRows(tasks: any[]): Array<{ model: string }> {
|
|
1252
|
+
const models = new Set<string>();
|
|
1253
|
+
for (const task of tasks) {
|
|
1254
|
+
if (typeof task?.runtime?.model === "string" && task.runtime.model.trim()) {
|
|
1255
|
+
models.add(task.runtime.model.trim());
|
|
1256
|
+
}
|
|
1257
|
+
const loop = task?.dynamic?.decisionLoop;
|
|
1258
|
+
if (!loop || typeof loop !== "object") continue;
|
|
1259
|
+
for (const profile of [
|
|
1260
|
+
loop.planner,
|
|
1261
|
+
loop.workerDefaults,
|
|
1262
|
+
loop.verifier,
|
|
1263
|
+
loop.synthesis,
|
|
1264
|
+
]) {
|
|
1265
|
+
if (typeof profile?.model === "string" && profile.model.trim()) {
|
|
1266
|
+
models.add(profile.model.trim());
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return [...models].sort().map((model) => ({ model }));
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1217
1273
|
function isSupportStage(stage: any): boolean {
|
|
1218
1274
|
return stage?.support !== undefined && stage?.type === undefined;
|
|
1219
1275
|
}
|
|
@@ -1301,8 +1357,13 @@ function buildDynamicTask(
|
|
|
1301
1357
|
specDir: string,
|
|
1302
1358
|
workflowInputText: string,
|
|
1303
1359
|
runtimeTask: string | undefined,
|
|
1304
|
-
|
|
1305
|
-
|
|
1360
|
+
controllerRuntime: WorkflowRuntimeResolutionInput,
|
|
1361
|
+
runtimePriority: {
|
|
1362
|
+
runtimeOverrides?: WorkflowRuntimeDefaults;
|
|
1363
|
+
runtimeDefaults?: WorkflowRuntimeDefaults;
|
|
1364
|
+
specRuntimeDefaults?: WorkflowRuntimeDefaults;
|
|
1365
|
+
stageRuntime?: WorkflowRuntimeDefaults;
|
|
1366
|
+
},
|
|
1306
1367
|
overrides: Partial<CompiledTask> & Record<string, unknown>,
|
|
1307
1368
|
): any {
|
|
1308
1369
|
const dynamic = stage.dynamic ?? {};
|
|
@@ -1365,8 +1426,7 @@ function buildDynamicTask(
|
|
|
1365
1426
|
}
|
|
1366
1427
|
const decisionLoop = compileDynamicDecisionLoop(
|
|
1367
1428
|
dynamic.decisionLoop,
|
|
1368
|
-
|
|
1369
|
-
defaultThinking,
|
|
1429
|
+
runtimePriority,
|
|
1370
1430
|
);
|
|
1371
1431
|
|
|
1372
1432
|
return {
|
|
@@ -1386,8 +1446,7 @@ function buildDynamicTask(
|
|
|
1386
1446
|
explicitWorktreePolicy: false,
|
|
1387
1447
|
runtime: {
|
|
1388
1448
|
approvalMode: "non-interactive",
|
|
1389
|
-
|
|
1390
|
-
thinking: defaultThinking,
|
|
1449
|
+
...controllerRuntime,
|
|
1391
1450
|
maxRuntimeMs:
|
|
1392
1451
|
dynamic.budget?.maxRuntimeMs ?? DEFAULT_DYNAMIC_MAX_RUNTIME_MS,
|
|
1393
1452
|
},
|
|
@@ -1431,6 +1490,9 @@ function buildDynamicTask(
|
|
|
1431
1490
|
helpers,
|
|
1432
1491
|
workflows,
|
|
1433
1492
|
...(decisionLoop ? { decisionLoop } : {}),
|
|
1493
|
+
...(runtimePriority.runtimeOverrides
|
|
1494
|
+
? { runtimeOverrides: runtimePriority.runtimeOverrides }
|
|
1495
|
+
: {}),
|
|
1434
1496
|
},
|
|
1435
1497
|
...overrides,
|
|
1436
1498
|
};
|
|
@@ -1438,8 +1500,12 @@ function buildDynamicTask(
|
|
|
1438
1500
|
|
|
1439
1501
|
function compileDynamicDecisionLoop(
|
|
1440
1502
|
value: unknown,
|
|
1441
|
-
|
|
1442
|
-
|
|
1503
|
+
runtimePriority: {
|
|
1504
|
+
runtimeOverrides?: WorkflowRuntimeDefaults;
|
|
1505
|
+
runtimeDefaults?: WorkflowRuntimeDefaults;
|
|
1506
|
+
specRuntimeDefaults?: WorkflowRuntimeDefaults;
|
|
1507
|
+
stageRuntime?: WorkflowRuntimeDefaults;
|
|
1508
|
+
},
|
|
1443
1509
|
): any | undefined {
|
|
1444
1510
|
if (!isPlainRecord(value)) return undefined;
|
|
1445
1511
|
const allowedToolSelection = filterToolSelection(
|
|
@@ -1455,25 +1521,18 @@ function compileDynamicDecisionLoop(
|
|
|
1455
1521
|
recordValue(value.stateIndex, "requiredFindingIds"),
|
|
1456
1522
|
);
|
|
1457
1523
|
return {
|
|
1458
|
-
planner: compileDynamicDecisionLoopProfile(
|
|
1459
|
-
value.planner,
|
|
1460
|
-
defaultModel,
|
|
1461
|
-
defaultThinking,
|
|
1462
|
-
),
|
|
1524
|
+
planner: compileDynamicDecisionLoopProfile(value.planner, runtimePriority),
|
|
1463
1525
|
workerDefaults: compileDynamicDecisionLoopProfile(
|
|
1464
1526
|
value.workerDefaults,
|
|
1465
|
-
|
|
1466
|
-
defaultThinking,
|
|
1527
|
+
runtimePriority,
|
|
1467
1528
|
),
|
|
1468
1529
|
verifier: compileDynamicDecisionLoopProfile(
|
|
1469
1530
|
value.verifier,
|
|
1470
|
-
|
|
1471
|
-
defaultThinking,
|
|
1531
|
+
runtimePriority,
|
|
1472
1532
|
),
|
|
1473
1533
|
synthesis: compileDynamicDecisionLoopProfile(
|
|
1474
1534
|
value.synthesis,
|
|
1475
|
-
|
|
1476
|
-
defaultThinking,
|
|
1535
|
+
runtimePriority,
|
|
1477
1536
|
),
|
|
1478
1537
|
allowedAgents: stringArray(value.allowedAgents),
|
|
1479
1538
|
...(allowedToolSelection.tools
|
|
@@ -1528,8 +1587,12 @@ function compileDynamicDecisionLoop(
|
|
|
1528
1587
|
|
|
1529
1588
|
function compileDynamicDecisionLoopProfile(
|
|
1530
1589
|
value: unknown,
|
|
1531
|
-
|
|
1532
|
-
|
|
1590
|
+
runtimePriority: {
|
|
1591
|
+
runtimeOverrides?: WorkflowRuntimeDefaults;
|
|
1592
|
+
runtimeDefaults?: WorkflowRuntimeDefaults;
|
|
1593
|
+
specRuntimeDefaults?: WorkflowRuntimeDefaults;
|
|
1594
|
+
stageRuntime?: WorkflowRuntimeDefaults;
|
|
1595
|
+
},
|
|
1533
1596
|
): any | undefined {
|
|
1534
1597
|
if (!isPlainRecord(value)) return undefined;
|
|
1535
1598
|
const toolSelection = filterToolSelection(
|
|
@@ -1538,20 +1601,18 @@ function compileDynamicDecisionLoopProfile(
|
|
|
1538
1601
|
undefined,
|
|
1539
1602
|
),
|
|
1540
1603
|
);
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
: defaultThinking;
|
|
1604
|
+
const runtime = selectWorkflowRuntime(
|
|
1605
|
+
runtimePriority.runtimeOverrides,
|
|
1606
|
+
runtimeSettings(value),
|
|
1607
|
+
runtimePriority.stageRuntime,
|
|
1608
|
+
runtimePriority.runtimeDefaults,
|
|
1609
|
+
runtimePriority.specRuntimeDefaults,
|
|
1610
|
+
);
|
|
1549
1611
|
return {
|
|
1550
1612
|
...(typeof value.agent === "string" && value.agent.trim()
|
|
1551
1613
|
? { agent: value.agent.trim() }
|
|
1552
1614
|
: {}),
|
|
1553
|
-
...
|
|
1554
|
-
...(thinking ? { thinking } : {}),
|
|
1615
|
+
...runtime,
|
|
1555
1616
|
...(toolSelection.tools ? { tools: toolSelection.tools } : {}),
|
|
1556
1617
|
...(toolSelection.toolProviders
|
|
1557
1618
|
? { toolProviders: toolSelection.toolProviders }
|
package/src/dynamic-decision.ts
CHANGED
|
@@ -283,17 +283,6 @@ export function validateDynamicDecision(
|
|
|
283
283
|
};
|
|
284
284
|
}
|
|
285
285
|
|
|
286
|
-
export function assertValidDynamicDecision(
|
|
287
|
-
value: unknown,
|
|
288
|
-
context: DynamicDecisionValidationContext = {},
|
|
289
|
-
): NormalizedDynamicDecision {
|
|
290
|
-
const result = validateDynamicDecision(value, context);
|
|
291
|
-
if (!result.ok || !result.decision) {
|
|
292
|
-
throw new Error(`invalid dynamic decision: ${result.errors.join("; ")}`);
|
|
293
|
-
}
|
|
294
|
-
return result.decision;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
286
|
export function hashDynamicDecision(value: unknown): string {
|
|
298
287
|
return createHash("sha256")
|
|
299
288
|
.update(stableStringify(toJsonNormalizedValue(value)))
|
|
@@ -24,6 +24,11 @@ import type {
|
|
|
24
24
|
WorkflowRunRecord,
|
|
25
25
|
WorkflowTaskRunRecord,
|
|
26
26
|
} from "./types.js";
|
|
27
|
+
import {
|
|
28
|
+
resolveWorkflowRuntime,
|
|
29
|
+
selectWorkflowRuntime,
|
|
30
|
+
type WorkflowModelInfo,
|
|
31
|
+
} from "./workflow-runtime.js";
|
|
27
32
|
|
|
28
33
|
const DYNAMIC_OUTPUT_MAX_DIGEST_CHARS = 1000;
|
|
29
34
|
const DYNAMIC_DELEGATION_TOOLS = new Set([
|
|
@@ -92,6 +97,7 @@ export async function buildDynamicGeneratedCompiledTask(input: {
|
|
|
92
97
|
branchId?: string;
|
|
93
98
|
request: DynamicAgentRequest;
|
|
94
99
|
dynamic: CompiledDynamicWorkflowTask;
|
|
100
|
+
availableModels?: WorkflowModelInfo[];
|
|
95
101
|
}): Promise<CompiledTask> {
|
|
96
102
|
if (input.dynamic.budget.maxAgents <= 0) {
|
|
97
103
|
throw new Error("dynamic agent budget is exhausted");
|
|
@@ -172,6 +178,23 @@ export async function buildDynamicGeneratedCompiledTask(input: {
|
|
|
172
178
|
),
|
|
173
179
|
),
|
|
174
180
|
);
|
|
181
|
+
const selectedRuntime = selectWorkflowRuntime(
|
|
182
|
+
input.dynamic.runtimeOverrides,
|
|
183
|
+
runtimeSettings(input.request),
|
|
184
|
+
runtimeSettings(executionProfile),
|
|
185
|
+
runtimeSettings(input.controllerCompiledTask.runtime),
|
|
186
|
+
runtimeSettings(agentDefinition),
|
|
187
|
+
);
|
|
188
|
+
const resolvedRuntime = await resolveWorkflowRuntime(
|
|
189
|
+
selectedRuntime,
|
|
190
|
+
{
|
|
191
|
+
taskKey: input.generatedSpecId,
|
|
192
|
+
stageId: input.controllerStageId,
|
|
193
|
+
taskId: input.request.id,
|
|
194
|
+
agent: requestedAgent,
|
|
195
|
+
},
|
|
196
|
+
{ availableModels: input.availableModels ?? input.dynamic.availableModels },
|
|
197
|
+
);
|
|
175
198
|
const unknownTools = (tools ?? []).filter(
|
|
176
199
|
(tool) => effectiveToolClassification(tool, toolProviders) === undefined,
|
|
177
200
|
);
|
|
@@ -253,16 +276,7 @@ export async function buildDynamicGeneratedCompiledTask(input: {
|
|
|
253
276
|
explicitWorktreePolicy: requiresWorktree,
|
|
254
277
|
runtime: {
|
|
255
278
|
approvalMode: "non-interactive",
|
|
256
|
-
|
|
257
|
-
input.request.model ??
|
|
258
|
-
executionProfile?.model ??
|
|
259
|
-
input.controllerCompiledTask.runtime.model ??
|
|
260
|
-
agentDefinition.model,
|
|
261
|
-
thinking:
|
|
262
|
-
input.request.thinking ??
|
|
263
|
-
executionProfile?.thinking ??
|
|
264
|
-
input.controllerCompiledTask.runtime.thinking ??
|
|
265
|
-
agentDefinition.thinking,
|
|
279
|
+
...resolvedRuntime,
|
|
266
280
|
tools,
|
|
267
281
|
...(toolProviders ? { toolProviders } : {}),
|
|
268
282
|
maxRuntimeMs:
|
|
@@ -419,7 +433,9 @@ function dynamicDecisionLoopProfile(
|
|
|
419
433
|
);
|
|
420
434
|
}
|
|
421
435
|
|
|
422
|
-
export function isDynamicCompiledTaskPayload(
|
|
436
|
+
export function isDynamicCompiledTaskPayload(
|
|
437
|
+
value: unknown,
|
|
438
|
+
): value is CompiledTask {
|
|
423
439
|
return (
|
|
424
440
|
!!value &&
|
|
425
441
|
typeof value === "object" &&
|
|
@@ -679,7 +695,9 @@ export async function readDynamicGeneratedTaskResult(
|
|
|
679
695
|
};
|
|
680
696
|
}
|
|
681
697
|
|
|
682
|
-
export function normalizeDynamicAgentRequest(
|
|
698
|
+
export function normalizeDynamicAgentRequest(
|
|
699
|
+
value: unknown,
|
|
700
|
+
): DynamicAgentRequest {
|
|
683
701
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
684
702
|
throw new Error("ctx.agent() request must be an object");
|
|
685
703
|
}
|
|
@@ -730,6 +748,23 @@ function requiredDynamicString(
|
|
|
730
748
|
return value.trim();
|
|
731
749
|
}
|
|
732
750
|
|
|
751
|
+
function runtimeSettings(
|
|
752
|
+
value: unknown,
|
|
753
|
+
): { model?: string; thinking?: ThinkingLevel } | undefined {
|
|
754
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
755
|
+
return undefined;
|
|
756
|
+
const record = value as Record<string, unknown>;
|
|
757
|
+
const model =
|
|
758
|
+
typeof record.model === "string" && record.model.trim()
|
|
759
|
+
? record.model.trim()
|
|
760
|
+
: undefined;
|
|
761
|
+
const thinking =
|
|
762
|
+
typeof record.thinking === "string" && record.thinking.trim()
|
|
763
|
+
? (record.thinking.trim() as ThinkingLevel)
|
|
764
|
+
: undefined;
|
|
765
|
+
return model || thinking ? { model, thinking } : undefined;
|
|
766
|
+
}
|
|
767
|
+
|
|
733
768
|
function optionalDynamicString(
|
|
734
769
|
value: unknown,
|
|
735
770
|
field: string,
|
package/src/dynamic-profiles.ts
CHANGED
|
@@ -44,7 +44,3 @@ export function isTerminalDynamicOutputProfile(
|
|
|
44
44
|
): value is (typeof DYNAMIC_TERMINAL_OUTPUT_PROFILES)[number] {
|
|
45
45
|
return typeof value === "string" && TERMINAL_OUTPUT_PROFILE_SET.has(value);
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
export function dynamicOutputProfileValues(): string[] {
|
|
49
|
-
return [...DYNAMIC_OUTPUT_PROFILES];
|
|
50
|
-
}
|