pi-agent-flow 2.0.2 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/README.md +1 -1
  2. package/agents/audit.md +24 -17
  3. package/agents/build.md +1 -1
  4. package/agents/craft.md +1 -1
  5. package/agents/debug.md +1 -1
  6. package/agents/ideas.md +1 -1
  7. package/agents/scout.md +8 -8
  8. package/agents/trace.md +23 -0
  9. package/dist/batch/batch-bash.d.ts +10 -0
  10. package/dist/batch/batch-bash.d.ts.map +1 -1
  11. package/dist/batch/batch-bash.js +35 -2
  12. package/dist/batch/batch-bash.js.map +1 -1
  13. package/dist/batch/constants.d.ts +6 -3
  14. package/dist/batch/constants.d.ts.map +1 -1
  15. package/dist/batch/execute.js +2 -2
  16. package/dist/batch/execute.js.map +1 -1
  17. package/dist/batch/index.d.ts +9 -11
  18. package/dist/batch/index.d.ts.map +1 -1
  19. package/dist/batch/index.js +88 -58
  20. package/dist/batch/index.js.map +1 -1
  21. package/dist/batch/render.d.ts +22 -5
  22. package/dist/batch/render.d.ts.map +1 -1
  23. package/dist/batch/render.js +332 -17
  24. package/dist/batch/render.js.map +1 -1
  25. package/dist/batch/summary.d.ts.map +1 -1
  26. package/dist/batch/summary.js +22 -4
  27. package/dist/batch/summary.js.map +1 -1
  28. package/dist/config/config.d.ts +5 -4
  29. package/dist/config/config.d.ts.map +1 -1
  30. package/dist/config/config.js +15 -5
  31. package/dist/config/config.js.map +1 -1
  32. package/dist/config/models.d.ts.map +1 -1
  33. package/dist/config/models.js +7 -1
  34. package/dist/config/models.js.map +1 -1
  35. package/dist/config/settings-resolver.d.ts +5 -4
  36. package/dist/config/settings-resolver.d.ts.map +1 -1
  37. package/dist/config/settings-resolver.js +50 -21
  38. package/dist/config/settings-resolver.js.map +1 -1
  39. package/dist/core2/snapshot.d.ts +21 -0
  40. package/dist/core2/snapshot.d.ts.map +1 -0
  41. package/dist/core2/snapshot.js +214 -0
  42. package/dist/core2/snapshot.js.map +1 -0
  43. package/dist/{core → flow}/agents.d.ts.map +1 -1
  44. package/dist/{core → flow}/agents.js +5 -2
  45. package/dist/{core → flow}/agents.js.map +1 -1
  46. package/dist/flow/command.d.ts +1 -1
  47. package/dist/flow/command.d.ts.map +1 -1
  48. package/dist/flow/complexity.d.ts +20 -0
  49. package/dist/flow/complexity.d.ts.map +1 -0
  50. package/dist/flow/complexity.js +34 -0
  51. package/dist/flow/complexity.js.map +1 -0
  52. package/dist/flow/continuation.d.ts +1 -1
  53. package/dist/flow/continuation.d.ts.map +1 -1
  54. package/dist/flow/continuation.js +2 -1
  55. package/dist/flow/continuation.js.map +1 -1
  56. package/dist/{core → flow}/depth.d.ts +1 -1
  57. package/dist/{core → flow}/depth.d.ts.map +1 -1
  58. package/dist/{core → flow}/depth.js.map +1 -1
  59. package/dist/{core → flow}/executor.d.ts +39 -19
  60. package/dist/flow/executor.d.ts.map +1 -0
  61. package/dist/flow/executor.js +727 -0
  62. package/dist/flow/executor.js.map +1 -0
  63. package/dist/flow/index.d.ts +2 -2
  64. package/dist/flow/index.d.ts.map +1 -1
  65. package/dist/flow/index.js +1 -1
  66. package/dist/flow/index.js.map +1 -1
  67. package/dist/flow/loop-command.d.ts +1 -1
  68. package/dist/flow/loop-command.d.ts.map +1 -1
  69. package/dist/{core/flow.d.ts → flow/runner.d.ts} +10 -13
  70. package/dist/flow/runner.d.ts.map +1 -0
  71. package/dist/{core/flow.js → flow/runner.js} +35 -34
  72. package/dist/flow/runner.js.map +1 -0
  73. package/dist/{core → flow}/session-registry.d.ts.map +1 -1
  74. package/dist/{core → flow}/session-registry.js.map +1 -1
  75. package/dist/flow/settings-command.d.ts +3 -3
  76. package/dist/flow/settings-command.d.ts.map +1 -1
  77. package/dist/flow/settings-command.js +39 -21
  78. package/dist/flow/settings-command.js.map +1 -1
  79. package/dist/{core → flow}/transition.d.ts.map +1 -1
  80. package/dist/{core → flow}/transition.js.map +1 -1
  81. package/dist/flow/types.d.ts +4 -0
  82. package/dist/flow/types.d.ts.map +1 -1
  83. package/dist/flow/warp.d.ts +1 -1
  84. package/dist/flow/warp.d.ts.map +1 -1
  85. package/dist/index.d.ts +1 -2
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +190 -189
  88. package/dist/index.js.map +1 -1
  89. package/dist/notify/notify.d.ts +1 -1
  90. package/dist/notify/notify.d.ts.map +1 -1
  91. package/dist/notify/notify.js +1 -1
  92. package/dist/snapshot/cli-args.d.ts +2 -2
  93. package/dist/snapshot/cli-args.d.ts.map +1 -1
  94. package/dist/snapshot/cli-args.js +21 -5
  95. package/dist/snapshot/cli-args.js.map +1 -1
  96. package/dist/snapshot/runner-events.d.ts +2 -2
  97. package/dist/snapshot/runner-events.d.ts.map +1 -1
  98. package/dist/snapshot/runner-events.js +48 -4
  99. package/dist/snapshot/runner-events.js.map +1 -1
  100. package/dist/snapshot/structured-output.d.ts +1 -1
  101. package/dist/snapshot/structured-output.d.ts.map +1 -1
  102. package/dist/snapshot/structured-output.js +20 -2
  103. package/dist/snapshot/structured-output.js.map +1 -1
  104. package/dist/steering/flow-prompt.d.ts +1 -3
  105. package/dist/steering/flow-prompt.d.ts.map +1 -1
  106. package/dist/steering/flow-prompt.js +7 -63
  107. package/dist/steering/flow-prompt.js.map +1 -1
  108. package/dist/steering/sliding-prompt.js +10 -10
  109. package/dist/steering/sliding-prompt.js.map +1 -1
  110. package/dist/tools/ask-user.d.ts +2 -2
  111. package/dist/tools/ask-user.d.ts.map +1 -1
  112. package/dist/tools/ask-user.js +1 -1
  113. package/dist/tools/ask-user.js.map +1 -1
  114. package/dist/tools/timed-bash.js +1 -1
  115. package/dist/tools/timed-bash.js.map +1 -1
  116. package/dist/tools/trace.d.ts +34 -0
  117. package/dist/tools/trace.d.ts.map +1 -0
  118. package/dist/tools/trace.js +180 -0
  119. package/dist/tools/trace.js.map +1 -0
  120. package/dist/tools/web-ops.d.ts +85 -0
  121. package/dist/tools/web-ops.d.ts.map +1 -0
  122. package/dist/tools/{web-tool.js → web-ops.js} +51 -125
  123. package/dist/tools/web-ops.js.map +1 -0
  124. package/dist/tui/flow-colors.d.ts +1 -0
  125. package/dist/tui/flow-colors.d.ts.map +1 -1
  126. package/dist/tui/flow-colors.js +2 -2
  127. package/dist/tui/flow-colors.js.map +1 -1
  128. package/dist/tui/render-utils.js +1 -1
  129. package/dist/tui/render-utils.js.map +1 -1
  130. package/dist/tui/render.d.ts +32 -1
  131. package/dist/tui/render.d.ts.map +1 -1
  132. package/dist/tui/render.js +666 -170
  133. package/dist/tui/render.js.map +1 -1
  134. package/dist/tui/scramble/algorithm.d.ts +4 -2
  135. package/dist/tui/scramble/algorithm.d.ts.map +1 -1
  136. package/dist/tui/scramble/algorithm.js +44 -12
  137. package/dist/tui/scramble/algorithm.js.map +1 -1
  138. package/dist/tui/scramble/constants.d.ts +3 -0
  139. package/dist/tui/scramble/constants.d.ts.map +1 -1
  140. package/dist/tui/scramble/constants.js +4 -1
  141. package/dist/tui/scramble/constants.js.map +1 -1
  142. package/dist/tui/scramble/index.d.ts +2 -1
  143. package/dist/tui/scramble/index.d.ts.map +1 -1
  144. package/dist/tui/scramble/index.js +1 -1
  145. package/dist/tui/scramble/index.js.map +1 -1
  146. package/dist/tui/scramble/manager.d.ts +1 -1
  147. package/dist/tui/scramble/manager.d.ts.map +1 -1
  148. package/dist/tui/scramble/manager.js +24 -19
  149. package/dist/tui/scramble/manager.js.map +1 -1
  150. package/dist/types/flow.d.ts +17 -1
  151. package/dist/types/flow.d.ts.map +1 -1
  152. package/dist/types/flow.js.map +1 -1
  153. package/dist/types/output.d.ts +11 -36
  154. package/dist/types/output.d.ts.map +1 -1
  155. package/dist/types/output.js +1 -1
  156. package/dist/types/ui.d.ts +1 -1
  157. package/dist/types/ui.d.ts.map +1 -1
  158. package/package.json +9 -9
  159. package/dist/core/executor.d.ts.map +0 -1
  160. package/dist/core/executor.js +0 -378
  161. package/dist/core/executor.js.map +0 -1
  162. package/dist/core/flow.d.ts.map +0 -1
  163. package/dist/core/flow.js.map +0 -1
  164. package/dist/core/session-mode.d.ts +0 -11
  165. package/dist/core/session-mode.d.ts.map +0 -1
  166. package/dist/core/session-mode.js +0 -26
  167. package/dist/core/session-mode.js.map +0 -1
  168. package/dist/core/transitions.d.ts +0 -39
  169. package/dist/core/transitions.d.ts.map +0 -1
  170. package/dist/core/transitions.js +0 -59
  171. package/dist/core/transitions.js.map +0 -1
  172. package/dist/snapshot/index.d.ts +0 -2
  173. package/dist/snapshot/index.d.ts.map +0 -1
  174. package/dist/snapshot/index.js +0 -2
  175. package/dist/snapshot/index.js.map +0 -1
  176. package/dist/snapshot/reasoning-strip.d.ts +0 -22
  177. package/dist/snapshot/reasoning-strip.d.ts.map +0 -1
  178. package/dist/snapshot/reasoning-strip.js +0 -58
  179. package/dist/snapshot/reasoning-strip.js.map +0 -1
  180. package/dist/snapshot/snapshot.d.ts +0 -77
  181. package/dist/snapshot/snapshot.d.ts.map +0 -1
  182. package/dist/snapshot/snapshot.js +0 -1824
  183. package/dist/snapshot/snapshot.js.map +0 -1
  184. package/dist/tools/web-tool.d.ts +0 -46
  185. package/dist/tools/web-tool.d.ts.map +0 -1
  186. package/dist/tools/web-tool.js.map +0 -1
  187. /package/dist/{core → flow}/agents.d.ts +0 -0
  188. /package/dist/{core → flow}/depth.js +0 -0
  189. /package/dist/{core → flow}/session-registry.d.ts +0 -0
  190. /package/dist/{core → flow}/session-registry.js +0 -0
  191. /package/dist/{core → flow}/transition.d.ts +0 -0
  192. /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