pi-agent-flow 2.0.2 → 2.0.5
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 +1 -1
- package/agents/audit.md +24 -17
- package/agents/build.md +1 -1
- package/agents/craft.md +1 -1
- package/agents/debug.md +1 -1
- package/agents/ideas.md +1 -1
- package/agents/scout.md +8 -8
- package/agents/trace.md +23 -0
- package/dist/batch/batch-bash.d.ts +10 -0
- package/dist/batch/batch-bash.d.ts.map +1 -1
- package/dist/batch/batch-bash.js +35 -2
- package/dist/batch/batch-bash.js.map +1 -1
- package/dist/batch/constants.d.ts +6 -3
- package/dist/batch/constants.d.ts.map +1 -1
- package/dist/batch/execute.js +2 -2
- package/dist/batch/execute.js.map +1 -1
- package/dist/batch/index.d.ts +9 -11
- package/dist/batch/index.d.ts.map +1 -1
- package/dist/batch/index.js +88 -58
- package/dist/batch/index.js.map +1 -1
- package/dist/batch/render.d.ts +22 -5
- package/dist/batch/render.d.ts.map +1 -1
- package/dist/batch/render.js +332 -17
- package/dist/batch/render.js.map +1 -1
- package/dist/batch/summary.d.ts.map +1 -1
- package/dist/batch/summary.js +22 -4
- package/dist/batch/summary.js.map +1 -1
- package/dist/config/config.d.ts +5 -4
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +15 -5
- package/dist/config/config.js.map +1 -1
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +7 -1
- package/dist/config/models.js.map +1 -1
- package/dist/config/settings-resolver.d.ts +5 -4
- package/dist/config/settings-resolver.d.ts.map +1 -1
- package/dist/config/settings-resolver.js +50 -21
- package/dist/config/settings-resolver.js.map +1 -1
- package/dist/core2/snapshot.d.ts +21 -0
- package/dist/core2/snapshot.d.ts.map +1 -0
- package/dist/core2/snapshot.js +214 -0
- package/dist/core2/snapshot.js.map +1 -0
- package/dist/{core → flow}/agents.d.ts.map +1 -1
- package/dist/{core → flow}/agents.js +5 -2
- package/dist/{core → flow}/agents.js.map +1 -1
- package/dist/flow/command.d.ts +1 -1
- package/dist/flow/command.d.ts.map +1 -1
- package/dist/flow/complexity.d.ts +20 -0
- package/dist/flow/complexity.d.ts.map +1 -0
- package/dist/flow/complexity.js +34 -0
- package/dist/flow/complexity.js.map +1 -0
- package/dist/flow/continuation.d.ts +1 -1
- package/dist/flow/continuation.d.ts.map +1 -1
- package/dist/flow/continuation.js +2 -1
- package/dist/flow/continuation.js.map +1 -1
- package/dist/{core → flow}/depth.d.ts +1 -1
- package/dist/{core → flow}/depth.d.ts.map +1 -1
- package/dist/{core → flow}/depth.js.map +1 -1
- package/dist/{core → flow}/executor.d.ts +39 -19
- package/dist/flow/executor.d.ts.map +1 -0
- package/dist/flow/executor.js +727 -0
- package/dist/flow/executor.js.map +1 -0
- package/dist/flow/index.d.ts +2 -2
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +1 -1
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/loop-command.d.ts +1 -1
- package/dist/flow/loop-command.d.ts.map +1 -1
- package/dist/{core/flow.d.ts → flow/runner.d.ts} +10 -13
- package/dist/flow/runner.d.ts.map +1 -0
- package/dist/{core/flow.js → flow/runner.js} +35 -34
- package/dist/flow/runner.js.map +1 -0
- package/dist/{core → flow}/session-registry.d.ts.map +1 -1
- package/dist/{core → flow}/session-registry.js.map +1 -1
- package/dist/flow/settings-command.d.ts +3 -3
- package/dist/flow/settings-command.d.ts.map +1 -1
- package/dist/flow/settings-command.js +39 -21
- package/dist/flow/settings-command.js.map +1 -1
- package/dist/{core → flow}/transition.d.ts.map +1 -1
- package/dist/{core → flow}/transition.js.map +1 -1
- package/dist/flow/types.d.ts +4 -0
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/flow/warp.d.ts +1 -1
- package/dist/flow/warp.d.ts.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +189 -188
- package/dist/index.js.map +1 -1
- package/dist/notify/notify.d.ts +1 -1
- package/dist/notify/notify.d.ts.map +1 -1
- package/dist/notify/notify.js +1 -1
- package/dist/snapshot/cli-args.d.ts +2 -2
- package/dist/snapshot/cli-args.d.ts.map +1 -1
- package/dist/snapshot/cli-args.js +21 -5
- package/dist/snapshot/cli-args.js.map +1 -1
- package/dist/snapshot/runner-events.d.ts +2 -2
- package/dist/snapshot/runner-events.d.ts.map +1 -1
- package/dist/snapshot/runner-events.js +48 -4
- package/dist/snapshot/runner-events.js.map +1 -1
- package/dist/snapshot/structured-output.d.ts +1 -1
- package/dist/snapshot/structured-output.d.ts.map +1 -1
- package/dist/snapshot/structured-output.js +20 -2
- package/dist/snapshot/structured-output.js.map +1 -1
- package/dist/steering/flow-prompt.d.ts +2 -2
- package/dist/steering/flow-prompt.d.ts.map +1 -1
- package/dist/steering/flow-prompt.js +14 -33
- package/dist/steering/flow-prompt.js.map +1 -1
- package/dist/steering/sliding-prompt.d.ts.map +1 -1
- package/dist/steering/sliding-prompt.js +3 -2
- package/dist/steering/sliding-prompt.js.map +1 -1
- package/dist/tools/ask-user.d.ts +2 -2
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +1 -1
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/timed-bash.js +1 -1
- package/dist/tools/timed-bash.js.map +1 -1
- package/dist/tools/trace.d.ts +34 -0
- package/dist/tools/trace.d.ts.map +1 -0
- package/dist/tools/trace.js +180 -0
- package/dist/tools/trace.js.map +1 -0
- package/dist/tools/web-ops.d.ts +85 -0
- package/dist/tools/web-ops.d.ts.map +1 -0
- package/dist/tools/{web-tool.js → web-ops.js} +51 -125
- package/dist/tools/web-ops.js.map +1 -0
- package/dist/tui/flow-colors.d.ts +1 -0
- package/dist/tui/flow-colors.d.ts.map +1 -1
- package/dist/tui/flow-colors.js +2 -2
- package/dist/tui/flow-colors.js.map +1 -1
- package/dist/tui/render-utils.js +1 -1
- package/dist/tui/render-utils.js.map +1 -1
- package/dist/tui/render.d.ts +32 -1
- package/dist/tui/render.d.ts.map +1 -1
- package/dist/tui/render.js +666 -170
- package/dist/tui/render.js.map +1 -1
- package/dist/tui/scramble/algorithm.d.ts +4 -2
- package/dist/tui/scramble/algorithm.d.ts.map +1 -1
- package/dist/tui/scramble/algorithm.js +44 -12
- package/dist/tui/scramble/algorithm.js.map +1 -1
- package/dist/tui/scramble/constants.d.ts +3 -0
- package/dist/tui/scramble/constants.d.ts.map +1 -1
- package/dist/tui/scramble/constants.js +4 -1
- package/dist/tui/scramble/constants.js.map +1 -1
- package/dist/tui/scramble/index.d.ts +2 -1
- package/dist/tui/scramble/index.d.ts.map +1 -1
- package/dist/tui/scramble/index.js +1 -1
- package/dist/tui/scramble/index.js.map +1 -1
- package/dist/tui/scramble/manager.d.ts +1 -1
- package/dist/tui/scramble/manager.d.ts.map +1 -1
- package/dist/tui/scramble/manager.js +24 -19
- package/dist/tui/scramble/manager.js.map +1 -1
- package/dist/types/flow.d.ts +17 -1
- package/dist/types/flow.d.ts.map +1 -1
- package/dist/types/flow.js.map +1 -1
- package/dist/types/output.d.ts +11 -36
- package/dist/types/output.d.ts.map +1 -1
- package/dist/types/output.js +1 -1
- package/dist/types/ui.d.ts +1 -1
- package/dist/types/ui.d.ts.map +1 -1
- package/package.json +9 -9
- package/dist/core/executor.d.ts.map +0 -1
- package/dist/core/executor.js +0 -378
- package/dist/core/executor.js.map +0 -1
- package/dist/core/flow.d.ts.map +0 -1
- package/dist/core/flow.js.map +0 -1
- package/dist/core/session-mode.d.ts +0 -11
- package/dist/core/session-mode.d.ts.map +0 -1
- package/dist/core/session-mode.js +0 -26
- package/dist/core/session-mode.js.map +0 -1
- package/dist/core/transitions.d.ts +0 -39
- package/dist/core/transitions.d.ts.map +0 -1
- package/dist/core/transitions.js +0 -59
- package/dist/core/transitions.js.map +0 -1
- package/dist/snapshot/index.d.ts +0 -2
- package/dist/snapshot/index.d.ts.map +0 -1
- package/dist/snapshot/index.js +0 -2
- package/dist/snapshot/index.js.map +0 -1
- package/dist/snapshot/reasoning-strip.d.ts +0 -22
- package/dist/snapshot/reasoning-strip.d.ts.map +0 -1
- package/dist/snapshot/reasoning-strip.js +0 -58
- package/dist/snapshot/reasoning-strip.js.map +0 -1
- package/dist/snapshot/snapshot.d.ts +0 -77
- package/dist/snapshot/snapshot.d.ts.map +0 -1
- package/dist/snapshot/snapshot.js +0 -1824
- package/dist/snapshot/snapshot.js.map +0 -1
- package/dist/tools/web-tool.d.ts +0 -46
- package/dist/tools/web-tool.d.ts.map +0 -1
- package/dist/tools/web-tool.js.map +0 -1
- /package/dist/{core → flow}/agents.d.ts +0 -0
- /package/dist/{core → flow}/depth.js +0 -0
- /package/dist/{core → flow}/session-registry.d.ts +0 -0
- /package/dist/{core → flow}/session-registry.js +0 -0
- /package/dist/{core → flow}/transition.d.ts +0 -0
- /package/dist/{core → flow}/transition.js +0 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlowExecutor — extracted from index.ts for testability.
|
|
3
|
+
*
|
|
4
|
+
* Encapsulates the orchestration logic for running flows: cycle detection,
|
|
5
|
+
* project-flow confirmation, parallel execution with failover, caching,
|
|
6
|
+
* and telemetry.
|
|
7
|
+
*/
|
|
8
|
+
import { isFlowSuccess, isFlowError, isFlowComplete, emptyFlowUsage } from "../types/flow.js";
|
|
9
|
+
// ~6 KB limit to keep grouped audit intent under typical prompt budget while preserving enough context for meaningful audit
|
|
10
|
+
const MAX_AUDIT_OUTPUT_SLICE = 6000;
|
|
11
|
+
import { mapFlowConcurrent, runFlow } from "./runner.js";
|
|
12
|
+
import { getFlowSummaryText } from "../snapshot/runner-events.js";
|
|
13
|
+
import { normalizeFlowModeName, resolveFlowModelCandidates, resolveModelContextWindow, selectFlowModelStrategy } from "../config/config.js";
|
|
14
|
+
import { getComplexityTimeoutMs, resolveComplexity, getImpliedAuditLoop } from "./complexity.js";
|
|
15
|
+
import { setFlowComplete } from "../notify/notify-state.js";
|
|
16
|
+
import { setLiveText, clearLiveText } from '../tui/scramble/index.js';
|
|
17
|
+
import { logWarn } from '../config/log.js';
|
|
18
|
+
import { markFlowCompleted } from '../flow/index.js';
|
|
19
|
+
/**
|
|
20
|
+
* Shallow-merge helper: copies audit-loop metadata fields from `source`
|
|
21
|
+
* onto `target` without mutating the target's identity reference.
|
|
22
|
+
* Used whenever a fresh SingleResult overwrites a slot that may already
|
|
23
|
+
* carry ping-pong or parent-type metadata.
|
|
24
|
+
*/
|
|
25
|
+
export function preserveMetadata(target, source) {
|
|
26
|
+
if (source?.pingPongMeta) {
|
|
27
|
+
target.pingPongMeta = source.pingPongMeta;
|
|
28
|
+
}
|
|
29
|
+
if (source?.auditParentType) {
|
|
30
|
+
target.auditParentType = source.auditParentType;
|
|
31
|
+
}
|
|
32
|
+
if (source?.auditLoopGroupId !== undefined) {
|
|
33
|
+
target.auditLoopGroupId = source.auditLoopGroupId;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Helpers
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
function getFlowCycleViolations(requestedNames, ancestorFlowStack) {
|
|
40
|
+
if (requestedNames.size === 0 || ancestorFlowStack.length === 0)
|
|
41
|
+
return [];
|
|
42
|
+
const stackSet = new Set(ancestorFlowStack);
|
|
43
|
+
return Array.from(requestedNames).filter((name) => stackSet.has(name));
|
|
44
|
+
}
|
|
45
|
+
function getRequestedProjectFlows(flows, requestedNames) {
|
|
46
|
+
return Array.from(requestedNames)
|
|
47
|
+
.map((name) => flows.find((f) => f.name === name.toLowerCase()))
|
|
48
|
+
.filter((f) => f?.source === "project");
|
|
49
|
+
}
|
|
50
|
+
async function confirmProjectFlowsIfNeeded(projectFlows, projectFlowsDir, hasUI, uiConfirm) {
|
|
51
|
+
if (projectFlows.length === 0)
|
|
52
|
+
return { ok: true };
|
|
53
|
+
const names = projectFlows.map((f) => f.name).join(", ");
|
|
54
|
+
const dir = projectFlowsDir ?? "(unknown)";
|
|
55
|
+
if (hasUI) {
|
|
56
|
+
const ok = await uiConfirm("Run project-local flows?", `Flows: ${names}\nSource: ${dir}\n\nProject flows are repo-controlled. Only continue for trusted repositories.`);
|
|
57
|
+
return { ok };
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
blocked: `Blocked: project-local flow confirmation required in non-UI mode.\nFlows: ${names}\nRe-run with confirmProjectFlows: false if trusted.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function shouldFailover(result) {
|
|
65
|
+
if (result.stopReason === "aborted")
|
|
66
|
+
return false;
|
|
67
|
+
const text = `${result.errorMessage ?? ""}\n${result.stderr ?? ""}`.toLowerCase();
|
|
68
|
+
if (!text.trim())
|
|
69
|
+
return false;
|
|
70
|
+
if (text.includes("permission") || text.includes("invalid tool") || text.includes("bad settings")) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
if (result.exitCode > 0)
|
|
74
|
+
return true;
|
|
75
|
+
// Some child runs log HTTP 400 / "Param Incorrect" to stderr while exiting 0
|
|
76
|
+
// without completing a turn — treat as retryable for model failover.
|
|
77
|
+
if (!isFlowComplete(result) && (text.includes("400") && text.includes("param"))) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
export function createGhostResult(type, intent, aim, model, maxContextTokens) {
|
|
83
|
+
return {
|
|
84
|
+
type,
|
|
85
|
+
agentSource: "unknown",
|
|
86
|
+
intent,
|
|
87
|
+
aim,
|
|
88
|
+
exitCode: -1,
|
|
89
|
+
messages: [],
|
|
90
|
+
stderr: "",
|
|
91
|
+
usage: emptyFlowUsage(),
|
|
92
|
+
...(model ? { model } : {}),
|
|
93
|
+
...(maxContextTokens !== undefined ? { maxContextTokens } : {}),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function resolveAuditModel(flows, tierOverrideResolver, strategy, fallbackModel) {
|
|
97
|
+
const auditFlow = flows.find((f) => f.name === "audit");
|
|
98
|
+
const tier = auditFlow?.tier ?? "flash";
|
|
99
|
+
const { candidates } = resolveFlowModelCandidates({
|
|
100
|
+
tier,
|
|
101
|
+
flowModel: auditFlow?.model,
|
|
102
|
+
cliTierOverride: tierOverrideResolver(tier),
|
|
103
|
+
strategy,
|
|
104
|
+
fallbackModel,
|
|
105
|
+
});
|
|
106
|
+
const model = candidates[0];
|
|
107
|
+
const maxContextTokens = resolveModelContextWindow(model);
|
|
108
|
+
return { model, maxContextTokens };
|
|
109
|
+
}
|
|
110
|
+
export function buildReworkIntent(originalIntent, buildAim, acceptance, auditFeedback, cycleHistory) {
|
|
111
|
+
const parts = [
|
|
112
|
+
`## Original Intent`,
|
|
113
|
+
originalIntent,
|
|
114
|
+
``,
|
|
115
|
+
`## Build Aim`,
|
|
116
|
+
buildAim,
|
|
117
|
+
``,
|
|
118
|
+
];
|
|
119
|
+
if (acceptance) {
|
|
120
|
+
parts.push(`## Acceptance Criteria`, acceptance, ``);
|
|
121
|
+
}
|
|
122
|
+
parts.push(`## Audit Feedback`, auditFeedback, ``);
|
|
123
|
+
if (cycleHistory && cycleHistory.length > 0) {
|
|
124
|
+
// Show all prior build outputs (deep-loop: every cycle, not just latest)
|
|
125
|
+
const buildOutputs = formatPriorBuildOutputs(cycleHistory);
|
|
126
|
+
if (buildOutputs) {
|
|
127
|
+
parts.push(buildOutputs, ``);
|
|
128
|
+
}
|
|
129
|
+
// Show all prior audit verdicts/feedbacks
|
|
130
|
+
const auditHistory = formatPriorAuditHistory(cycleHistory);
|
|
131
|
+
if (auditHistory) {
|
|
132
|
+
parts.push(auditHistory, ``);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
parts.push(`Fix the above issues, preserving the Original Intent and incorporating all prior cycle feedback.`);
|
|
136
|
+
return parts.join("\n");
|
|
137
|
+
}
|
|
138
|
+
async function executeSingleFlow(deps, item, allResults, resultIndex, toolCallId, emitProgress, selectedFlowModelConfig) {
|
|
139
|
+
const { flows, currentDepth, maxDepth, ancestorFlowStack, preventCycles, toolOptimize, structuredOutput, cwd, defaultComplexity, signal, makeDetails, tierOverrideResolver, fallbackModel, forkSessionSnapshotJsonl, onFlowMetrics, goalContext, } = deps;
|
|
140
|
+
const normalizedType = item.type.toLowerCase();
|
|
141
|
+
const complexity = resolveComplexity(item.complexity, defaultComplexity);
|
|
142
|
+
const lookupName = normalizedType;
|
|
143
|
+
const targetFlow = flows.find((f) => f.name === lookupName);
|
|
144
|
+
const effectiveMaxDepth = targetFlow?.maxDepth !== undefined ? targetFlow.maxDepth : maxDepth;
|
|
145
|
+
const shouldInheritContext = targetFlow?.inheritContext !== false;
|
|
146
|
+
const tier = targetFlow?.tier ?? "flash";
|
|
147
|
+
const { candidates } = resolveFlowModelCandidates({
|
|
148
|
+
tier,
|
|
149
|
+
flowModel: targetFlow?.model,
|
|
150
|
+
cliTierOverride: tierOverrideResolver(tier),
|
|
151
|
+
strategy: selectedFlowModelConfig.strategy,
|
|
152
|
+
fallbackModel,
|
|
153
|
+
});
|
|
154
|
+
const attemptModels = candidates.length > 0 ? candidates : [undefined];
|
|
155
|
+
const attemptedModels = [];
|
|
156
|
+
let result = allResults[resultIndex];
|
|
157
|
+
const flowStart = Date.now();
|
|
158
|
+
for (let attempt = 0; attempt < attemptModels.length; attempt++) {
|
|
159
|
+
const candidateModel = attemptModels[attempt];
|
|
160
|
+
if (candidateModel)
|
|
161
|
+
attemptedModels.push(candidateModel);
|
|
162
|
+
const attemptStartMs = Date.now();
|
|
163
|
+
const attemptTimeoutMs = getComplexityTimeoutMs(complexity);
|
|
164
|
+
const maxContextTokens = resolveModelContextWindow(candidateModel);
|
|
165
|
+
const previous = allResults[resultIndex];
|
|
166
|
+
allResults[resultIndex] = {
|
|
167
|
+
type: normalizedType,
|
|
168
|
+
agentSource: targetFlow?.source ?? "unknown",
|
|
169
|
+
intent: item.intent,
|
|
170
|
+
aim: item.aim,
|
|
171
|
+
exitCode: -1,
|
|
172
|
+
messages: [],
|
|
173
|
+
stderr: "",
|
|
174
|
+
usage: emptyFlowUsage(),
|
|
175
|
+
model: candidateModel,
|
|
176
|
+
startedAtMs: attemptStartMs,
|
|
177
|
+
deadlineAtMs: attemptStartMs + attemptTimeoutMs,
|
|
178
|
+
...(maxContextTokens !== undefined ? { maxContextTokens } : {}),
|
|
179
|
+
};
|
|
180
|
+
preserveMetadata(allResults[resultIndex], previous);
|
|
181
|
+
emitProgress();
|
|
182
|
+
result = await runFlow({
|
|
183
|
+
cwd,
|
|
184
|
+
flows,
|
|
185
|
+
flowName: lookupName,
|
|
186
|
+
intent: item.intent,
|
|
187
|
+
aim: item.aim,
|
|
188
|
+
acceptance: item.acceptance,
|
|
189
|
+
taskCwd: item.cwd,
|
|
190
|
+
forkSessionSnapshotJsonl: shouldInheritContext ? forkSessionSnapshotJsonl : null,
|
|
191
|
+
parentDepth: currentDepth,
|
|
192
|
+
parentFlowStack: ancestorFlowStack,
|
|
193
|
+
maxDepth: effectiveMaxDepth,
|
|
194
|
+
preventCycles,
|
|
195
|
+
toolOptimize,
|
|
196
|
+
structuredOutput,
|
|
197
|
+
complexity,
|
|
198
|
+
model: candidateModel,
|
|
199
|
+
maxContextTokens,
|
|
200
|
+
goalContext,
|
|
201
|
+
tools: item._childTools,
|
|
202
|
+
preDispatchResults: item.preDispatchResults,
|
|
203
|
+
signal,
|
|
204
|
+
onUpdate: (partial) => {
|
|
205
|
+
if (partial.details?.results[0]) {
|
|
206
|
+
const previous = allResults[resultIndex];
|
|
207
|
+
allResults[resultIndex] = partial.details.results[0];
|
|
208
|
+
preserveMetadata(allResults[resultIndex], previous);
|
|
209
|
+
const flowText = partial.content?.[0]?.text;
|
|
210
|
+
if (flowText !== undefined) {
|
|
211
|
+
setLiveText(`${toolCallId || 'collapsed'}#${resultIndex}`, flowText);
|
|
212
|
+
setLiveText(`collapsed#${resultIndex}`, flowText);
|
|
213
|
+
}
|
|
214
|
+
emitProgress(partial.content?.[0]?.text);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
makeDetails,
|
|
218
|
+
});
|
|
219
|
+
const previous2 = allResults[resultIndex];
|
|
220
|
+
allResults[resultIndex] = result;
|
|
221
|
+
preserveMetadata(allResults[resultIndex], previous2);
|
|
222
|
+
emitProgress();
|
|
223
|
+
if (isFlowSuccess(result) || signal?.aborted)
|
|
224
|
+
break;
|
|
225
|
+
if (attempt < attemptModels.length - 1 && shouldFailover(result)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
if (result && !isFlowSuccess(result) && attemptedModels.length > 1) {
|
|
231
|
+
const summary = `Model failover attempts: ${attemptedModels.join(" -> ")}`;
|
|
232
|
+
const baseStderr = result.stderr.trim();
|
|
233
|
+
result.stderr = baseStderr ? `${baseStderr}\n\n${summary}` : summary;
|
|
234
|
+
const previous3 = allResults[resultIndex];
|
|
235
|
+
allResults[resultIndex] = result;
|
|
236
|
+
preserveMetadata(allResults[resultIndex], previous3);
|
|
237
|
+
emitProgress();
|
|
238
|
+
}
|
|
239
|
+
if (onFlowMetrics) {
|
|
240
|
+
const flowDuration = Date.now() - flowStart;
|
|
241
|
+
onFlowMetrics({
|
|
242
|
+
type: normalizedType,
|
|
243
|
+
durationMs: flowDuration,
|
|
244
|
+
exitCode: result.exitCode,
|
|
245
|
+
success: isFlowSuccess(result),
|
|
246
|
+
model: result.model,
|
|
247
|
+
failoverCount: Math.max(0, attemptedModels.length - 1),
|
|
248
|
+
usage: result.usage,
|
|
249
|
+
source: result.agentSource,
|
|
250
|
+
depth: currentDepth + 1,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
export function formatPriorAuditHistory(entries) {
|
|
256
|
+
if (entries.length === 0)
|
|
257
|
+
return "";
|
|
258
|
+
const lines = entries.map((e) => {
|
|
259
|
+
const parts = [
|
|
260
|
+
`**Cycle ${e.cycle + 1}**`,
|
|
261
|
+
`- Verdict: ${e.verdict}`,
|
|
262
|
+
];
|
|
263
|
+
if (e.feedback) {
|
|
264
|
+
parts.push(`- Feedback: ${e.feedback.slice(0, 2000)}`);
|
|
265
|
+
}
|
|
266
|
+
if (e.buildFeedbacks && e.buildFeedbacks.length > 0) {
|
|
267
|
+
parts.push(`- Per-Build Feedback:`);
|
|
268
|
+
e.buildFeedbacks.forEach((fb, i) => {
|
|
269
|
+
if (fb) {
|
|
270
|
+
parts.push(` - Build ${i + 1}: ${fb.slice(0, 1500)}`);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
parts.push(` - Build ${i + 1}: pass`);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return parts.join("\n");
|
|
278
|
+
});
|
|
279
|
+
return `## Prior Audit History\n\n${lines.join("\n\n")}`;
|
|
280
|
+
}
|
|
281
|
+
export function formatPriorBuildOutputs(entries) {
|
|
282
|
+
if (entries.length === 0)
|
|
283
|
+
return "";
|
|
284
|
+
const lines = entries.map((e) => {
|
|
285
|
+
const parts = [`**Cycle ${e.cycle + 1}**`];
|
|
286
|
+
if (e.buildOutputs.length > 0) {
|
|
287
|
+
parts.push(`- Build Outputs:`);
|
|
288
|
+
e.buildOutputs.forEach((bo, i) => {
|
|
289
|
+
parts.push(` - Build ${i + 1}: ${bo.slice(0, 3000)}`);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
return parts.join("\n");
|
|
293
|
+
});
|
|
294
|
+
return `## Prior Build Outputs\n\n${lines.join("\n\n")}`;
|
|
295
|
+
}
|
|
296
|
+
export function buildGroupAuditIntent(builds, cycleHistory) {
|
|
297
|
+
const sections = builds.map((b, i) => {
|
|
298
|
+
const section = [
|
|
299
|
+
`### Build ${i + 1}`,
|
|
300
|
+
``,
|
|
301
|
+
`## Build Aim`,
|
|
302
|
+
b.aim,
|
|
303
|
+
``,
|
|
304
|
+
];
|
|
305
|
+
if (b.acceptance) {
|
|
306
|
+
section.push(`## Acceptance Criteria`, b.acceptance, ``);
|
|
307
|
+
}
|
|
308
|
+
if (b.intent) {
|
|
309
|
+
section.push(`## Build Intent`, b.intent, ``);
|
|
310
|
+
}
|
|
311
|
+
section.push(`## Build Output`, b.output.slice(0, MAX_AUDIT_OUTPUT_SLICE));
|
|
312
|
+
return section.join("\n");
|
|
313
|
+
});
|
|
314
|
+
const parts = [
|
|
315
|
+
...sections,
|
|
316
|
+
``,
|
|
317
|
+
];
|
|
318
|
+
if (cycleHistory && cycleHistory.length > 0) {
|
|
319
|
+
const auditHistory = formatPriorAuditHistory(cycleHistory);
|
|
320
|
+
if (auditHistory) {
|
|
321
|
+
parts.push(auditHistory, ``);
|
|
322
|
+
}
|
|
323
|
+
const buildOutputs = formatPriorBuildOutputs(cycleHistory);
|
|
324
|
+
if (buildOutputs) {
|
|
325
|
+
parts.push(buildOutputs, ``);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
parts.push(`Check for: security issues, correctness, completeness, edge cases, and any overlooked requirements per build. For each build, indicate whether it passes or needs rework with specific actionable feedback.`);
|
|
329
|
+
return parts.join("\n\n");
|
|
330
|
+
}
|
|
331
|
+
async function executeGroupedPingPong(deps, group, allResults, toolCallId, emitProgress, selectedFlowModelConfig) {
|
|
332
|
+
const { items, auditLoop, buildIndices, auditIndex } = group;
|
|
333
|
+
const maxCycles = (auditLoop ?? 0) + 1;
|
|
334
|
+
let cycle = 0;
|
|
335
|
+
const verdictHistory = [];
|
|
336
|
+
const cycleHistory = [];
|
|
337
|
+
const auditFeedbacks = new Array(items.length).fill(null);
|
|
338
|
+
// Initialize all slots (re-creates ghosts; must stamp auditLoopGroupId
|
|
339
|
+
// because executeFlows pre-allocation gets overwritten here).
|
|
340
|
+
for (let i = 0; i < items.length; i++) {
|
|
341
|
+
allResults[buildIndices[i]] = createGhostResult(items[i].type, items[i].intent, items[i].aim);
|
|
342
|
+
allResults[buildIndices[i]].status = "running";
|
|
343
|
+
allResults[buildIndices[i]].pingPongMeta = { cycles: 0, verdicts: [], finalVerdict: "pending" };
|
|
344
|
+
allResults[buildIndices[i]].auditLoopGroupId = group.groupId;
|
|
345
|
+
}
|
|
346
|
+
const { model: auditModel, maxContextTokens: auditMaxCtx } = resolveAuditModel(deps.flows, deps.tierOverrideResolver, selectedFlowModelConfig.strategy, deps.fallbackModel);
|
|
347
|
+
allResults[auditIndex] = createGhostResult("audit", "", `Audit ${items.length} build outputs`, auditModel, auditMaxCtx);
|
|
348
|
+
allResults[auditIndex].status = "awaiting";
|
|
349
|
+
allResults[auditIndex].auditParentType = "build";
|
|
350
|
+
allResults[auditIndex].auditLoopGroupId = group.groupId;
|
|
351
|
+
const key = toolCallId || 'collapsed';
|
|
352
|
+
const buildResults = new Array(items.length);
|
|
353
|
+
while (cycle < maxCycles) {
|
|
354
|
+
// ─── Phase A: Run all builds in parallel ───
|
|
355
|
+
for (let i = 0; i < items.length; i++) {
|
|
356
|
+
allResults[buildIndices[i]].status = "running";
|
|
357
|
+
allResults[buildIndices[i]].exitCode = -1;
|
|
358
|
+
allResults[buildIndices[i]].streamingText = undefined;
|
|
359
|
+
allResults[buildIndices[i]].startedAtMs = undefined;
|
|
360
|
+
allResults[buildIndices[i]].deadlineAtMs = undefined;
|
|
361
|
+
}
|
|
362
|
+
allResults[auditIndex].status = "awaiting";
|
|
363
|
+
allResults[auditIndex].exitCode = -1;
|
|
364
|
+
clearLiveText(`${key}#${auditIndex}`);
|
|
365
|
+
setLiveText(`${key}#${auditIndex}`, "[awaiting...]");
|
|
366
|
+
setLiveText(`collapsed#${auditIndex}`, "[awaiting...]");
|
|
367
|
+
emitProgress();
|
|
368
|
+
// Run all builds in parallel
|
|
369
|
+
const buildPromises = items.map((item, i) => {
|
|
370
|
+
const buildInput = cycle > 0
|
|
371
|
+
? buildReworkIntent(item.intent, item.aim, item.acceptance, auditFeedbacks[i] ?? "No issues found.", cycleHistory)
|
|
372
|
+
: item.intent;
|
|
373
|
+
const buildItem = { ...item, intent: buildInput };
|
|
374
|
+
return executeSingleFlow(deps, buildItem, allResults, buildIndices[i], toolCallId, emitProgress, selectedFlowModelConfig);
|
|
375
|
+
});
|
|
376
|
+
const newBuildResults = await Promise.all(buildPromises);
|
|
377
|
+
for (let i = 0; i < items.length; i++) {
|
|
378
|
+
buildResults[i] = newBuildResults[i];
|
|
379
|
+
preserveMetadata(buildResults[i], allResults[buildIndices[i]]);
|
|
380
|
+
allResults[buildIndices[i]] = buildResults[i];
|
|
381
|
+
allResults[buildIndices[i]].status = isFlowSuccess(buildResults[i]) ? "done" : "error";
|
|
382
|
+
allResults[buildIndices[i]].streamingText = undefined;
|
|
383
|
+
allResults[buildIndices[i]].startedAtMs = undefined;
|
|
384
|
+
allResults[buildIndices[i]].deadlineAtMs = undefined;
|
|
385
|
+
}
|
|
386
|
+
emitProgress();
|
|
387
|
+
// Check if any build failed
|
|
388
|
+
const anyBuildFailed = buildResults.some(r => !isFlowSuccess(r));
|
|
389
|
+
if (anyBuildFailed) {
|
|
390
|
+
const { model: skipAuditModel, maxContextTokens: skipAuditMaxCtx } = resolveAuditModel(deps.flows, deps.tierOverrideResolver, selectedFlowModelConfig.strategy, deps.fallbackModel);
|
|
391
|
+
allResults[auditIndex] = {
|
|
392
|
+
...createGhostResult("audit", "", `Audit ${items.length} build outputs`, skipAuditModel, skipAuditMaxCtx),
|
|
393
|
+
status: "skipped",
|
|
394
|
+
exitCode: 0,
|
|
395
|
+
stderr: "",
|
|
396
|
+
auditParentType: "build",
|
|
397
|
+
auditLoopGroupId: allResults[auditIndex]?.auditLoopGroupId,
|
|
398
|
+
};
|
|
399
|
+
emitProgress();
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
// ─── Phase B: Run ONE audit that reviews all builds ───
|
|
403
|
+
for (let i = 0; i < items.length; i++) {
|
|
404
|
+
allResults[buildIndices[i]].status = "awaiting";
|
|
405
|
+
clearLiveText(`${key}#${buildIndices[i]}`);
|
|
406
|
+
setLiveText(`${key}#${buildIndices[i]}`, "[awaiting...]");
|
|
407
|
+
setLiveText(`collapsed#${buildIndices[i]}`, "[awaiting...]");
|
|
408
|
+
}
|
|
409
|
+
allResults[auditIndex].status = "running";
|
|
410
|
+
allResults[auditIndex].exitCode = -1;
|
|
411
|
+
allResults[auditIndex].streamingText = undefined;
|
|
412
|
+
allResults[auditIndex].startedAtMs = undefined;
|
|
413
|
+
allResults[auditIndex].deadlineAtMs = undefined;
|
|
414
|
+
emitProgress();
|
|
415
|
+
const auditInput = buildGroupAuditIntent(items.map((item, i) => ({
|
|
416
|
+
aim: item.aim,
|
|
417
|
+
intent: item.intent,
|
|
418
|
+
acceptance: item.acceptance,
|
|
419
|
+
output: getFlowSummaryText(buildResults[i]),
|
|
420
|
+
})), cycleHistory);
|
|
421
|
+
const auditItem = {
|
|
422
|
+
type: "audit",
|
|
423
|
+
intent: `Audit the completed build flows.\n\n${auditInput}`,
|
|
424
|
+
aim: `Audit ${items.length} build outputs`,
|
|
425
|
+
acceptance: "Verify correctness, security, and completeness of all build flows' outputs.",
|
|
426
|
+
complexity: items[0]?.complexity ?? "moderate",
|
|
427
|
+
};
|
|
428
|
+
const auditResult = await executeSingleFlow(deps, auditItem, allResults, auditIndex, toolCallId, emitProgress, selectedFlowModelConfig);
|
|
429
|
+
auditResult.auditParentType = items[0].type;
|
|
430
|
+
allResults[auditIndex] = auditResult;
|
|
431
|
+
// Parse per-build verdicts
|
|
432
|
+
const perBuildVerdicts = auditResult.structuredOutput?.builds;
|
|
433
|
+
const topLevelVerdict = auditResult.structuredOutput?.verdict ?? "pass";
|
|
434
|
+
const topLevelFeedback = auditResult.structuredOutput?.feedback;
|
|
435
|
+
let anyReworkNeeded = false;
|
|
436
|
+
if (Array.isArray(perBuildVerdicts)) {
|
|
437
|
+
for (let i = 0; i < items.length; i++) {
|
|
438
|
+
const bv = perBuildVerdicts.find((b) => Number(b.index) === i);
|
|
439
|
+
if (bv?.verdict === "rework") {
|
|
440
|
+
auditFeedbacks[i] = bv.feedback ?? "Fix issues found in audit.";
|
|
441
|
+
anyReworkNeeded = true;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
auditFeedbacks[i] = null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// Fallback: top-level verdict applies to all builds
|
|
450
|
+
if (topLevelVerdict === "rework") {
|
|
451
|
+
for (let i = 0; i < items.length; i++) {
|
|
452
|
+
auditFeedbacks[i] = topLevelFeedback ?? "Fix issues found in audit.";
|
|
453
|
+
}
|
|
454
|
+
anyReworkNeeded = true;
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
for (let i = 0; i < items.length; i++) {
|
|
458
|
+
auditFeedbacks[i] = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
verdictHistory.push({
|
|
463
|
+
cycle,
|
|
464
|
+
verdict: anyReworkNeeded ? "rework" : "pass",
|
|
465
|
+
...(anyReworkNeeded && topLevelFeedback ? { feedback: topLevelFeedback } : {}),
|
|
466
|
+
});
|
|
467
|
+
cycleHistory.push({
|
|
468
|
+
cycle,
|
|
469
|
+
buildOutputs: buildResults.map((r) => getFlowSummaryText(r)),
|
|
470
|
+
verdict: anyReworkNeeded ? "rework" : "pass",
|
|
471
|
+
feedback: anyReworkNeeded ? topLevelFeedback : undefined,
|
|
472
|
+
buildFeedbacks: [...auditFeedbacks],
|
|
473
|
+
});
|
|
474
|
+
auditResult.status = auditResult.exitCode === 0 ? "done" : "error";
|
|
475
|
+
preserveMetadata(auditResult, allResults[auditIndex]);
|
|
476
|
+
allResults[auditIndex] = auditResult;
|
|
477
|
+
emitProgress();
|
|
478
|
+
if (!anyReworkNeeded) {
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
if (cycle + 1 >= maxCycles) {
|
|
482
|
+
break; // Loop exhausted
|
|
483
|
+
}
|
|
484
|
+
cycle++;
|
|
485
|
+
// Continue loop — builds needing rework will re-run
|
|
486
|
+
}
|
|
487
|
+
// Finalize: set real exit codes
|
|
488
|
+
for (let i = 0; i < items.length; i++) {
|
|
489
|
+
allResults[buildIndices[i]].exitCode = isFlowSuccess(buildResults[i]) ? 0 : (buildResults[i].exitCode > 0 ? buildResults[i].exitCode : 1);
|
|
490
|
+
allResults[buildIndices[i]].status = isFlowSuccess(buildResults[i]) ? "done" : "error";
|
|
491
|
+
}
|
|
492
|
+
if (allResults[auditIndex].status === "skipped") {
|
|
493
|
+
allResults[auditIndex].exitCode = 0;
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
allResults[auditIndex].exitCode = isFlowSuccess(allResults[auditIndex]) ? 0 : (allResults[auditIndex].exitCode > 0 ? allResults[auditIndex].exitCode : 1);
|
|
497
|
+
allResults[auditIndex].status = "done";
|
|
498
|
+
}
|
|
499
|
+
// Populate pingPongMeta on each build result
|
|
500
|
+
for (let i = 0; i < items.length; i++) {
|
|
501
|
+
allResults[buildIndices[i]].pingPongMeta = {
|
|
502
|
+
cycles: cycle + 1,
|
|
503
|
+
verdicts: verdictHistory,
|
|
504
|
+
finalVerdict: allResults[auditIndex].structuredOutput?.verdict ?? (allResults[auditIndex].status === "skipped" ? "fail" : "pass"),
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
emitProgress();
|
|
508
|
+
return buildResults;
|
|
509
|
+
}
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
// FlowExecutor
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
/**
|
|
514
|
+
* Execute a set of flow tasks with full orchestration: cycle detection,
|
|
515
|
+
* project confirmation, parallel execution with model failover, and telemetry.
|
|
516
|
+
*/
|
|
517
|
+
export async function executeFlows(deps, params, toolCallId, auditLoop) {
|
|
518
|
+
const { flows, currentDepth, maxDepth, ancestorFlowStack, preventCycles, toolOptimize, structuredOutput, cwd, loadedFlowModelConfigs, maxConcurrency, defaultComplexity, signal, onUpdate, makeDetails, getFlag, tierOverrideResolver, fallbackModel, forkSessionSnapshotJsonl, projectFlowsDir, hasUI, uiConfirm, onFlowMetrics, confirmProjectFlows, goalContext, } = deps;
|
|
519
|
+
const requested = new Set(params.map((f) => f.type.toLowerCase()));
|
|
520
|
+
// Cycle check
|
|
521
|
+
if (preventCycles) {
|
|
522
|
+
const violations = getFlowCycleViolations(requested, ancestorFlowStack);
|
|
523
|
+
if (violations.length > 0) {
|
|
524
|
+
const stack = ancestorFlowStack.join(" -> ") || "(root)";
|
|
525
|
+
return {
|
|
526
|
+
content: [{
|
|
527
|
+
type: "text",
|
|
528
|
+
text: `Blocked: cycle detected. Flow(s) in stack: ${violations.join(", ")}\nStack: ${stack}`,
|
|
529
|
+
}],
|
|
530
|
+
details: makeDetails([]),
|
|
531
|
+
failed: true,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
// Project flow confirmation
|
|
536
|
+
const projectFlows = getRequestedProjectFlows(flows, requested);
|
|
537
|
+
if (projectFlows.length > 0 && confirmProjectFlows !== false) {
|
|
538
|
+
const { ok, blocked } = await confirmProjectFlowsIfNeeded(projectFlows, projectFlowsDir, hasUI, uiConfirm);
|
|
539
|
+
if (!ok) {
|
|
540
|
+
return {
|
|
541
|
+
content: [{ type: "text", text: blocked ?? "Canceled: project-local flows not approved." }],
|
|
542
|
+
details: makeDetails([]),
|
|
543
|
+
failed: !blocked,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
// Resolve model strategy
|
|
548
|
+
const cliFlowMode = normalizeFlowModeName(getFlag("flow-mode"));
|
|
549
|
+
const cliFlowModelConfig = normalizeFlowModeName(getFlag("flow-model-config"));
|
|
550
|
+
if (cliFlowMode !== undefined && cliFlowModelConfig !== undefined && cliFlowMode !== cliFlowModelConfig) {
|
|
551
|
+
logWarn(`[pi-agent-flow] Both --flow-mode "${cliFlowMode}" and --flow-model-config "${cliFlowModelConfig}" were provided. Using --flow-mode.`);
|
|
552
|
+
}
|
|
553
|
+
const selectedFlowModelConfig = selectFlowModelStrategy(loadedFlowModelConfigs.configs, cliFlowMode ?? cliFlowModelConfig ?? loadedFlowModelConfigs.selectedName);
|
|
554
|
+
// Partition params into regular and grouped ping-pong flows
|
|
555
|
+
const regularParams = [];
|
|
556
|
+
const regularIndices = [];
|
|
557
|
+
const groups = [];
|
|
558
|
+
// Compute effective audit loop per build = max(explicit override, complexity-implied)
|
|
559
|
+
const effectiveAuditLoops = params.map((p) => {
|
|
560
|
+
if (p.type.toLowerCase() === "build") {
|
|
561
|
+
return Math.max(auditLoop ?? 0, getImpliedAuditLoop(p.complexity));
|
|
562
|
+
}
|
|
563
|
+
return 0;
|
|
564
|
+
});
|
|
565
|
+
const hasBuildsWithAudit = effectiveAuditLoops.some((loop, i) => params[i].type.toLowerCase() === "build" && loop > 0);
|
|
566
|
+
let nextIndex = 0;
|
|
567
|
+
let buildGroup;
|
|
568
|
+
for (let i = 0; i < params.length; i++) {
|
|
569
|
+
if (hasBuildsWithAudit && params[i].type.toLowerCase() === "audit") {
|
|
570
|
+
logWarn(`Manual audit flow skipped — audit auto-spawned by complexity or auditLoop`);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (params[i].type.toLowerCase() === "build" && effectiveAuditLoops[i] > 0) {
|
|
574
|
+
if (buildGroup) {
|
|
575
|
+
buildGroup.items.push(params[i]);
|
|
576
|
+
buildGroup.buildIndices.push(nextIndex++);
|
|
577
|
+
buildGroup.auditLoop = Math.max(buildGroup.auditLoop, effectiveAuditLoops[i]);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
const buildIndex = nextIndex++;
|
|
581
|
+
buildGroup = {
|
|
582
|
+
items: [params[i]],
|
|
583
|
+
auditLoop: effectiveAuditLoops[i],
|
|
584
|
+
buildIndices: [buildIndex],
|
|
585
|
+
auditIndex: -1, // placeholder; assigned after all builds
|
|
586
|
+
groupId: -1, // placeholder; set after auditIndex is assigned
|
|
587
|
+
};
|
|
588
|
+
groups.push(buildGroup);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
regularIndices.push(nextIndex);
|
|
593
|
+
regularParams.push(params[i]);
|
|
594
|
+
nextIndex++;
|
|
595
|
+
// Non-build param breaks contiguity — reset so later builds start a new group
|
|
596
|
+
buildGroup = undefined;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
// Assign audit indices after all builds are allocated
|
|
600
|
+
let groupCounter = 0;
|
|
601
|
+
for (const group of groups) {
|
|
602
|
+
group.auditIndex = nextIndex++;
|
|
603
|
+
group.groupId = groupCounter++;
|
|
604
|
+
}
|
|
605
|
+
// Pre-allocate results array
|
|
606
|
+
const allResults = new Array(nextIndex);
|
|
607
|
+
for (let i = 0; i < regularParams.length; i++) {
|
|
608
|
+
const idx = regularIndices[i];
|
|
609
|
+
allResults[idx] = {
|
|
610
|
+
type: regularParams[i].type,
|
|
611
|
+
agentSource: "unknown",
|
|
612
|
+
intent: regularParams[i].intent,
|
|
613
|
+
aim: regularParams[i].aim,
|
|
614
|
+
acceptance: regularParams[i].acceptance,
|
|
615
|
+
exitCode: -1,
|
|
616
|
+
messages: [],
|
|
617
|
+
stderr: "",
|
|
618
|
+
usage: emptyFlowUsage(),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
for (const group of groups) {
|
|
622
|
+
for (let i = 0; i < group.items.length; i++) {
|
|
623
|
+
const buildIndex = group.buildIndices[i];
|
|
624
|
+
allResults[buildIndex] = createGhostResult(group.items[i].type, group.items[i].intent, group.items[i].aim);
|
|
625
|
+
allResults[buildIndex].status = "running";
|
|
626
|
+
allResults[buildIndex].pingPongMeta = { cycles: 0, verdicts: [], finalVerdict: "pending" };
|
|
627
|
+
allResults[buildIndex].auditLoopGroupId = group.groupId;
|
|
628
|
+
}
|
|
629
|
+
const { model: auditModel, maxContextTokens: auditMaxCtx } = resolveAuditModel(flows, tierOverrideResolver, selectedFlowModelConfig.strategy, fallbackModel);
|
|
630
|
+
allResults[group.auditIndex] = createGhostResult("audit", "", `Audit ${group.items.length} build outputs`, auditModel, auditMaxCtx);
|
|
631
|
+
allResults[group.auditIndex].status = "awaiting";
|
|
632
|
+
allResults[group.auditIndex].auditParentType = "build";
|
|
633
|
+
allResults[group.auditIndex].auditLoopGroupId = group.groupId;
|
|
634
|
+
}
|
|
635
|
+
// Streaming progress
|
|
636
|
+
let lastEmittedSignature;
|
|
637
|
+
const emitProgress = (streamingText) => {
|
|
638
|
+
const activeStreamingText = allResults
|
|
639
|
+
.filter((r) => r.exitCode === -1)
|
|
640
|
+
.map((r) => r.streamingText)
|
|
641
|
+
.filter((text) => Boolean(text))
|
|
642
|
+
.at(-1);
|
|
643
|
+
const text = streamingText ?? activeStreamingText ?? "";
|
|
644
|
+
// Update live text store FIRST — always
|
|
645
|
+
const key = toolCallId || 'collapsed';
|
|
646
|
+
setLiveText(key, text);
|
|
647
|
+
setLiveText('collapsed', text);
|
|
648
|
+
for (let i = 0; i < allResults.length; i++) {
|
|
649
|
+
const r = allResults[i];
|
|
650
|
+
if (r.streamingText) {
|
|
651
|
+
setLiveText(`${key}#${i}`, r.streamingText);
|
|
652
|
+
setLiveText(`collapsed#${i}`, r.streamingText);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// Now check onUpdate for host callback
|
|
656
|
+
if (!onUpdate)
|
|
657
|
+
return;
|
|
658
|
+
const signature = text +
|
|
659
|
+
"|" +
|
|
660
|
+
allResults
|
|
661
|
+
.map((r) => {
|
|
662
|
+
const remainingSeconds = r.exitCode === -1 && typeof r.deadlineAtMs === "number"
|
|
663
|
+
? Math.max(0, Math.ceil((r.deadlineAtMs - Date.now()) / 1000))
|
|
664
|
+
: "";
|
|
665
|
+
return `${r.messages.length}:${r.usage.toolCalls}:${r.usage.input}:${r.usage.output}:${r.usage.contextTokens}:${r.usage.smoothedTps ?? 0}:${r.startedAtMs ?? ""}:${r.deadlineAtMs ?? ""}:${remainingSeconds}:${r.errorMessage ?? ""}`;
|
|
666
|
+
})
|
|
667
|
+
.join(";");
|
|
668
|
+
if (signature === lastEmittedSignature)
|
|
669
|
+
return;
|
|
670
|
+
lastEmittedSignature = signature;
|
|
671
|
+
try {
|
|
672
|
+
onUpdate({
|
|
673
|
+
content: [{ type: "text", text }],
|
|
674
|
+
details: makeDetails([...allResults]),
|
|
675
|
+
_toolCallId: toolCallId,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
if (err instanceof Error && err.message.includes("outside active run")) {
|
|
680
|
+
// Agent listener invoked from async callback (child stdout handler)
|
|
681
|
+
// after the active run context ended. Safe to drop this update.
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
throw err;
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
emitProgress();
|
|
688
|
+
// Execute all flows
|
|
689
|
+
const executionStart = Date.now();
|
|
690
|
+
const regularPromise = regularParams.length > 0
|
|
691
|
+
? mapFlowConcurrent(regularParams, maxConcurrency, async (item, localIndex) => {
|
|
692
|
+
const globalIndex = regularIndices[localIndex];
|
|
693
|
+
return executeSingleFlow(deps, item, allResults, globalIndex, toolCallId, emitProgress, selectedFlowModelConfig);
|
|
694
|
+
})
|
|
695
|
+
: Promise.resolve([]);
|
|
696
|
+
const groupPromises = groups.map((group) => executeGroupedPingPong(deps, group, allResults, toolCallId, emitProgress, selectedFlowModelConfig));
|
|
697
|
+
await Promise.all([regularPromise, ...groupPromises]);
|
|
698
|
+
const results = [...allResults];
|
|
699
|
+
// Record last flow completion for dynamic notifications
|
|
700
|
+
const lastResult = results[results.length - 1];
|
|
701
|
+
if (lastResult) {
|
|
702
|
+
setFlowComplete(lastResult.type, lastResult.acceptance, results.length - 1, results.length);
|
|
703
|
+
}
|
|
704
|
+
// Mark flow completion for the continuation hold — gives the user
|
|
705
|
+
// time to read the result before the next flow auto-spawns.
|
|
706
|
+
markFlowCompleted(deps.sessionManager.getSessionId());
|
|
707
|
+
// Goal continuation callback
|
|
708
|
+
if (deps.goalContinuationCallback) {
|
|
709
|
+
await deps.goalContinuationCallback(results);
|
|
710
|
+
}
|
|
711
|
+
// Build tool result
|
|
712
|
+
const successCount = results.filter((r) => isFlowSuccess(r)).length;
|
|
713
|
+
const flowReports = results.map((r) => {
|
|
714
|
+
const output = getFlowSummaryText(r);
|
|
715
|
+
const status = isFlowError(r) ? "failed" : "accomplished";
|
|
716
|
+
return `flow [${r.type}] ${status}\n\n${output}`;
|
|
717
|
+
});
|
|
718
|
+
return {
|
|
719
|
+
content: [{
|
|
720
|
+
type: "text",
|
|
721
|
+
text: `Flow: ${successCount}/${results.length} completed\n\n${flowReports.join("\n\n---\n\n")}`,
|
|
722
|
+
}],
|
|
723
|
+
details: makeDetails(results),
|
|
724
|
+
_toolCallId: toolCallId,
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
//# sourceMappingURL=executor.js.map
|