pi-agent-flow 1.0.8 → 1.2.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 +159 -45
- package/agents/audit.md +56 -0
- package/agents/build.md +60 -0
- package/agents/craft.md +53 -0
- package/agents/debug.md +33 -15
- package/agents/ideas.md +51 -0
- package/agents/scout.md +51 -0
- package/package.json +12 -15
- package/{agents.ts → src/agents.ts} +15 -12
- package/src/ambient.d.ts +85 -0
- package/src/batch.ts +1221 -0
- package/src/cli-args.ts +283 -0
- package/src/config.ts +305 -0
- package/{flow.ts → src/flow.ts} +120 -33
- package/{hooks.ts → src/hooks.ts} +31 -13
- package/src/index.ts +1002 -0
- package/{render-utils.ts → src/render-utils.ts} +5 -5
- package/{render.ts → src/render.ts} +66 -30
- package/src/runner-events.ts +692 -0
- package/{types.ts → src/types.ts} +5 -2
- package/src/web-tool.ts +663 -0
- package/agents/architect.md +0 -35
- package/agents/brainstorm.md +0 -29
- package/agents/code.md +0 -55
- package/agents/explore.md +0 -29
- package/agents/review.md +0 -35
- package/config.ts +0 -67
- package/index.ts +0 -645
- package/runner-cli.js +0 -254
- package/runner-events.js +0 -405
package/{flow.ts → src/flow.ts}
RENAMED
|
@@ -10,8 +10,8 @@ import * as fs from "node:fs";
|
|
|
10
10
|
import * as os from "node:os";
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
|
13
|
-
import { type FlowConfig
|
|
14
|
-
import { parseFlowCliArgs } from "./
|
|
13
|
+
import { type FlowConfig } from "./agents.js";
|
|
14
|
+
import { parseFlowCliArgs } from "./cli-args.js";
|
|
15
15
|
import { processFlowJsonLine, drainStreamingText, drainStreamingEstimate, drainCtxEstimate, updateSmoothedTps, drainSmoothedTps } from "./runner-events.js";
|
|
16
16
|
import {
|
|
17
17
|
type SingleResult,
|
|
@@ -23,11 +23,14 @@ import {
|
|
|
23
23
|
|
|
24
24
|
const isWindows = process.platform === "win32";
|
|
25
25
|
const SIGKILL_TIMEOUT_MS = 5000;
|
|
26
|
-
const AGENT_END_GRACE_MS =
|
|
26
|
+
const AGENT_END_GRACE_MS = 2000;
|
|
27
|
+
const DEFAULT_FLOW_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
|
|
27
28
|
const FLOW_DEPTH_ENV = "PI_FLOW_DEPTH";
|
|
28
29
|
const FLOW_MAX_DEPTH_ENV = "PI_FLOW_MAX_DEPTH";
|
|
29
30
|
const FLOW_STACK_ENV = "PI_FLOW_STACK";
|
|
30
31
|
const FLOW_PREVENT_CYCLES_ENV = "PI_FLOW_PREVENT_CYCLES";
|
|
32
|
+
const FLOW_TIMEOUT_ENV = "PI_FLOW_TIMEOUT_MS";
|
|
33
|
+
export const FLOW_TOOL_OPTIMIZE_ENV = "PI_FLOW_TOOL_OPTIMIZE";
|
|
31
34
|
const PI_OFFLINE_ENV = "PI_OFFLINE";
|
|
32
35
|
|
|
33
36
|
type FlowUpdateCallback = (partial: AgentToolResult<FlowDetails>) => void;
|
|
@@ -49,7 +52,8 @@ function mergeStreamingUsage(
|
|
|
49
52
|
...actual,
|
|
50
53
|
...(estimatedOutputTokens > 0 ? { output: Math.max(actual.output, estimatedOutputTokens) } : {}),
|
|
51
54
|
...(ctxEstimate > 0 ? { contextTokens: Math.max(actual.contextTokens, ctxEstimate) } : {}),
|
|
52
|
-
|
|
55
|
+
// Show the live EMA value so the dashboard can rise and fall smoothly.
|
|
56
|
+
...(smoothedTps > 0 ? { smoothedTps } : {}),
|
|
53
57
|
};
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -99,13 +103,35 @@ function cleanupFlowTempDir(dir: string | null): void {
|
|
|
99
103
|
|
|
100
104
|
const inheritedCliArgs = parseFlowCliArgs(process.argv);
|
|
101
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Transform a flow's tool list when toolOptimize is enabled.
|
|
108
|
+
* Replaces separate read/write/edit tools with the unified batch tool.
|
|
109
|
+
*/
|
|
110
|
+
export function getOptimizedTools(
|
|
111
|
+
flowTools: string[] | undefined,
|
|
112
|
+
toolOptimize: boolean,
|
|
113
|
+
): string[] | undefined {
|
|
114
|
+
if (!toolOptimize || !flowTools) return flowTools;
|
|
115
|
+
const hasLegacyTools = flowTools.some(
|
|
116
|
+
(t) => t === "read" || t === "write" || t === "edit",
|
|
117
|
+
);
|
|
118
|
+
if (!hasLegacyTools) return flowTools;
|
|
119
|
+
const filtered = flowTools.filter(
|
|
120
|
+
(t) => t !== "read" && t !== "write" && t !== "edit" && t !== "batch" && t !== "batch_read",
|
|
121
|
+
);
|
|
122
|
+
return filtered.includes("batch")
|
|
123
|
+
? filtered
|
|
124
|
+
: [...filtered, "batch"];
|
|
125
|
+
}
|
|
126
|
+
|
|
102
127
|
function buildFlowArgs(
|
|
103
128
|
flow: FlowConfig,
|
|
104
129
|
intent: string,
|
|
105
130
|
forkSessionPath: string | null,
|
|
106
|
-
|
|
131
|
+
model?: string,
|
|
107
132
|
parentDepth: number = 0,
|
|
108
133
|
maxDepth: number = 0,
|
|
134
|
+
toolOptimize: boolean = false,
|
|
109
135
|
): string[] {
|
|
110
136
|
const args: string[] = [
|
|
111
137
|
"--mode",
|
|
@@ -119,23 +145,41 @@ function buildFlowArgs(
|
|
|
119
145
|
args.push("--session", forkSessionPath);
|
|
120
146
|
}
|
|
121
147
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (
|
|
148
|
+
if (inheritedCliArgs.flowModelConfig) {
|
|
149
|
+
args.push("--flow-model-config", inheritedCliArgs.flowModelConfig);
|
|
150
|
+
}
|
|
151
|
+
if (inheritedCliArgs.tieredModels?.lite) {
|
|
152
|
+
args.push("--flow-lite-model", inheritedCliArgs.tieredModels.lite);
|
|
153
|
+
}
|
|
154
|
+
if (inheritedCliArgs.tieredModels?.flash) {
|
|
155
|
+
args.push("--flow-flash-model", inheritedCliArgs.tieredModels.flash);
|
|
156
|
+
}
|
|
157
|
+
if (inheritedCliArgs.tieredModels?.full) {
|
|
158
|
+
args.push("--flow-full-model", inheritedCliArgs.tieredModels.full);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const resolvedModel = model ?? flow.model ?? inheritedCliArgs.fallbackModel;
|
|
162
|
+
if (resolvedModel) args.push("--model", resolvedModel);
|
|
126
163
|
|
|
127
164
|
const thinking = flow.thinking ?? inheritedCliArgs.fallbackThinking;
|
|
128
165
|
if (thinking) args.push("--thinking", thinking);
|
|
129
166
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
167
|
+
// Child flows get their configured tools from flow.tools, optimized by
|
|
168
|
+
// getOptimizedTools, with web explicitly filtered out.
|
|
169
|
+
// When flow.tools is undefined and toolOptimize=true, default to batch+bash+web
|
|
170
|
+
// (flow is unnecessary since the child is already inside a flow).
|
|
171
|
+
// When toolOptimize=false, include batch and web alongside legacy tools.
|
|
172
|
+
const defaultTools = toolOptimize
|
|
173
|
+
? ["batch", "bash", "web"]
|
|
174
|
+
: ["read", "write", "edit", "batch", "bash", "flow", "web"];
|
|
175
|
+
const optimizedTools = getOptimizedTools(flow.tools, toolOptimize) ?? defaultTools;
|
|
176
|
+
let harnessTools = optimizedTools.filter((t) => t !== "web");
|
|
177
|
+
// If the flow explicitly listed only "web" (or nothing after filtering),
|
|
178
|
+
// fall back to defaultTools so the child isn't orphaned with zero tools.
|
|
179
|
+
if (harnessTools.length === 0) {
|
|
180
|
+
harnessTools = defaultTools.filter((t) => t !== "web");
|
|
138
181
|
}
|
|
182
|
+
args.push("--tools", harnessTools.join(","));
|
|
139
183
|
|
|
140
184
|
// No --append-system-prompt: child inherits parent's system prompt for cache hits.
|
|
141
185
|
// Flow instructions go in the intent message instead.
|
|
@@ -143,7 +187,7 @@ function buildFlowArgs(
|
|
|
143
187
|
const currentDepth = Math.max(0, Math.floor(parentDepth)) + 1;
|
|
144
188
|
const effectiveMaxDepth = Math.max(0, Math.floor(maxDepth));
|
|
145
189
|
const canDelegate = currentDepth < effectiveMaxDepth;
|
|
146
|
-
const availableTools =
|
|
190
|
+
const availableTools = harnessTools.join(", ");
|
|
147
191
|
|
|
148
192
|
// Phase 1: Context seal — sharp boundary declaring history sealed
|
|
149
193
|
const contextSeal =
|
|
@@ -196,6 +240,8 @@ export interface RunFlowOptions {
|
|
|
196
240
|
flowName: string;
|
|
197
241
|
/** Intent description. */
|
|
198
242
|
intent: string;
|
|
243
|
+
/** Short headline for display. */
|
|
244
|
+
aim: string;
|
|
199
245
|
/** Optional override working directory. */
|
|
200
246
|
taskCwd?: string;
|
|
201
247
|
/** Serialized parent session snapshot for fork mode. Null when the flow starts with a clean slate. */
|
|
@@ -208,14 +254,18 @@ export interface RunFlowOptions {
|
|
|
208
254
|
maxDepth: number;
|
|
209
255
|
/** Whether cycle prevention should be enforced in child processes. */
|
|
210
256
|
preventCycles: boolean;
|
|
211
|
-
/**
|
|
212
|
-
|
|
257
|
+
/** Whether to transform tool lists to use batch. */
|
|
258
|
+
toolOptimize?: boolean;
|
|
259
|
+
/** Explicit model to use for this flow execution. */
|
|
260
|
+
model?: string;
|
|
213
261
|
/** Abort signal for cancellation. */
|
|
214
262
|
signal?: AbortSignal;
|
|
215
263
|
/** Streaming update callback. */
|
|
216
264
|
onUpdate?: FlowUpdateCallback;
|
|
217
265
|
/** Factory to wrap results into FlowDetails. */
|
|
218
266
|
makeDetails: (results: SingleResult[]) => FlowDetails;
|
|
267
|
+
/** Max execution time in ms before child is terminated. Default: 10 minutes. */
|
|
268
|
+
timeoutMs?: number;
|
|
219
269
|
}
|
|
220
270
|
|
|
221
271
|
/**
|
|
@@ -229,12 +279,15 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
229
279
|
flows,
|
|
230
280
|
flowName,
|
|
231
281
|
intent,
|
|
282
|
+
aim,
|
|
232
283
|
taskCwd,
|
|
233
284
|
forkSessionSnapshotJsonl,
|
|
234
285
|
parentDepth,
|
|
235
286
|
parentFlowStack,
|
|
236
287
|
maxDepth,
|
|
237
288
|
preventCycles,
|
|
289
|
+
toolOptimize = false,
|
|
290
|
+
model,
|
|
238
291
|
signal,
|
|
239
292
|
onUpdate,
|
|
240
293
|
makeDetails,
|
|
@@ -248,6 +301,7 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
248
301
|
type: normalizedFlowName,
|
|
249
302
|
agentSource: "unknown",
|
|
250
303
|
intent,
|
|
304
|
+
aim,
|
|
251
305
|
exitCode: 1,
|
|
252
306
|
messages: [],
|
|
253
307
|
stderr: `Unknown flow: "${flowName}". Available flows: ${available}.`,
|
|
@@ -255,11 +309,12 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
255
309
|
};
|
|
256
310
|
}
|
|
257
311
|
|
|
258
|
-
const resolvedModel = flow.model ?? inheritedCliArgs.fallbackModel;
|
|
312
|
+
const resolvedModel = model ?? flow.model ?? inheritedCliArgs.fallbackModel;
|
|
259
313
|
const result: SingleResult = {
|
|
260
314
|
type: normalizedFlowName,
|
|
261
315
|
agentSource: flow.source,
|
|
262
316
|
intent,
|
|
317
|
+
aim,
|
|
263
318
|
exitCode: -1,
|
|
264
319
|
messages: [],
|
|
265
320
|
stderr: "",
|
|
@@ -267,21 +322,30 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
267
322
|
model: resolvedModel,
|
|
268
323
|
};
|
|
269
324
|
|
|
325
|
+
let liveStreamingText = "";
|
|
326
|
+
let liveEstimatedOutputTokens = 0;
|
|
327
|
+
let lastActualOutputTokens = result.usage.output;
|
|
270
328
|
const emitUpdate = () => {
|
|
271
|
-
const
|
|
329
|
+
const streamingDelta = drainStreamingText(result);
|
|
330
|
+
if (streamingDelta) liveStreamingText += streamingDelta;
|
|
272
331
|
const estimatedTokens = drainStreamingEstimate(result);
|
|
332
|
+
if (result.usage.output !== lastActualOutputTokens) {
|
|
333
|
+
lastActualOutputTokens = result.usage.output;
|
|
334
|
+
liveEstimatedOutputTokens = result.usage.output;
|
|
335
|
+
}
|
|
336
|
+
liveEstimatedOutputTokens += estimatedTokens;
|
|
273
337
|
const ctxEst = drainCtxEstimate(result);
|
|
274
338
|
updateSmoothedTps(result, estimatedTokens);
|
|
275
339
|
const smoothedTps = drainSmoothedTps(result);
|
|
276
|
-
const mergedUsage = mergeStreamingUsage(result.usage,
|
|
340
|
+
const mergedUsage = mergeStreamingUsage(result.usage, liveEstimatedOutputTokens, ctxEst, smoothedTps);
|
|
277
341
|
onUpdate?.({
|
|
278
342
|
content: [
|
|
279
343
|
{
|
|
280
344
|
type: "text",
|
|
281
|
-
text:
|
|
345
|
+
text: liveStreamingText || getFlowOutput(result.messages) || "(running...)",
|
|
282
346
|
},
|
|
283
347
|
],
|
|
284
|
-
details: makeDetails([{ ...result, usage: mergedUsage }]),
|
|
348
|
+
details: makeDetails([{ ...result, usage: mergedUsage, streamingText: liveStreamingText || undefined }]),
|
|
285
349
|
});
|
|
286
350
|
};
|
|
287
351
|
|
|
@@ -299,12 +363,21 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
299
363
|
flow,
|
|
300
364
|
intent,
|
|
301
365
|
forkSessionTmpPath,
|
|
302
|
-
|
|
366
|
+
model,
|
|
303
367
|
parentDepth,
|
|
304
368
|
maxDepth,
|
|
369
|
+
toolOptimize,
|
|
305
370
|
);
|
|
306
371
|
let wasAborted = false;
|
|
307
372
|
|
|
373
|
+
// Resolve timeout: explicit option > env var > default (10 min)
|
|
374
|
+
const envTimeoutRaw = process.env[FLOW_TIMEOUT_ENV];
|
|
375
|
+
const envTimeout = envTimeoutRaw !== undefined ? (() => {
|
|
376
|
+
const n = Number(envTimeoutRaw);
|
|
377
|
+
return Number.isSafeInteger(n) && n >= 0 ? n : null;
|
|
378
|
+
})() : null;
|
|
379
|
+
const effectiveTimeout = opts.timeoutMs ?? envTimeout ?? DEFAULT_FLOW_TIMEOUT_MS;
|
|
380
|
+
|
|
308
381
|
const exitCode = await new Promise<number>((resolve) => {
|
|
309
382
|
const nextDepth = Math.max(0, Math.floor(parentDepth)) + 1;
|
|
310
383
|
const propagatedMaxDepth = Math.max(0, Math.floor(maxDepth));
|
|
@@ -314,12 +387,15 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
314
387
|
cwd: taskCwd ?? cwd,
|
|
315
388
|
shell: false,
|
|
316
389
|
stdio: ["pipe", "pipe", "pipe"],
|
|
390
|
+
// Process group on Unix so we can kill all descendants on timeout/abort.
|
|
391
|
+
detached: !isWindows,
|
|
317
392
|
env: {
|
|
318
393
|
...process.env,
|
|
319
394
|
[FLOW_DEPTH_ENV]: String(nextDepth),
|
|
320
395
|
[FLOW_MAX_DEPTH_ENV]: String(propagatedMaxDepth),
|
|
321
396
|
[FLOW_STACK_ENV]: JSON.stringify(propagatedStack),
|
|
322
397
|
[FLOW_PREVENT_CYCLES_ENV]: preventCycles ? "1" : "0",
|
|
398
|
+
[FLOW_TOOL_OPTIMIZE_ENV]: toolOptimize ? "1" : "0",
|
|
323
399
|
[PI_OFFLINE_ENV]: "1",
|
|
324
400
|
},
|
|
325
401
|
});
|
|
@@ -353,9 +429,12 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
353
429
|
return;
|
|
354
430
|
}
|
|
355
431
|
|
|
356
|
-
|
|
432
|
+
// Kill the entire process group (negative PID).
|
|
433
|
+
if (proc.pid === undefined) { proc.kill("SIGTERM"); } else { try { process.kill(-proc.pid, "SIGTERM"); } catch { proc.kill("SIGTERM"); } }
|
|
357
434
|
const sigkillTimer = setTimeout(() => {
|
|
358
|
-
if (!didClose)
|
|
435
|
+
if (!didClose) {
|
|
436
|
+
if (proc.pid === undefined) { proc.kill("SIGKILL"); } else { try { process.kill(-proc.pid, "SIGKILL"); } catch { proc.kill("SIGKILL"); } }
|
|
437
|
+
}
|
|
359
438
|
}, SIGKILL_TIMEOUT_MS);
|
|
360
439
|
sigkillTimer.unref();
|
|
361
440
|
};
|
|
@@ -381,19 +460,17 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
381
460
|
}
|
|
382
461
|
};
|
|
383
462
|
|
|
463
|
+
let semanticCompletionTimerArmed = false;
|
|
384
464
|
const maybeFinishFromAgentEnd = () => {
|
|
385
|
-
if (!result.sawAgentEnd || didClose || settled) return;
|
|
386
|
-
|
|
465
|
+
if (!result.sawAgentEnd || didClose || settled || semanticCompletionTimerArmed) return;
|
|
466
|
+
semanticCompletionTimerArmed = true;
|
|
387
467
|
semanticCompletionTimer = setTimeout(() => {
|
|
388
468
|
if (didClose || settled || !result.sawAgentEnd) return;
|
|
389
469
|
if (buffer.trim()) {
|
|
390
470
|
flushBufferedLines(buffer);
|
|
391
471
|
buffer = "";
|
|
392
472
|
}
|
|
393
|
-
proc.stdout.removeListener("data", onStdoutData);
|
|
394
|
-
proc.stderr.removeListener("data", onStderrData);
|
|
395
473
|
finish(0);
|
|
396
|
-
terminateChild();
|
|
397
474
|
}, AGENT_END_GRACE_MS);
|
|
398
475
|
semanticCompletionTimer.unref();
|
|
399
476
|
};
|
|
@@ -433,6 +510,16 @@ export async function runFlow(opts: RunFlowOptions): Promise<SingleResult> {
|
|
|
433
510
|
if (signal.aborted) abortHandler();
|
|
434
511
|
else signal.addEventListener("abort", abortHandler, { once: true });
|
|
435
512
|
}
|
|
513
|
+
|
|
514
|
+
// Execution timeout — kill child if it runs too long
|
|
515
|
+
if (effectiveTimeout > 0) {
|
|
516
|
+
const timeoutTimer = setTimeout(() => {
|
|
517
|
+
if (didClose || settled) return;
|
|
518
|
+
result.stderr += `\nFlow timed out after ${Math.round(effectiveTimeout / 1000)}s.`;
|
|
519
|
+
terminateChild();
|
|
520
|
+
}, effectiveTimeout);
|
|
521
|
+
timeoutTimer.unref();
|
|
522
|
+
}
|
|
436
523
|
});
|
|
437
524
|
|
|
438
525
|
result.exitCode = exitCode;
|
|
@@ -68,38 +68,56 @@ export function clearHooks(): void {
|
|
|
68
68
|
// Built-in hooks
|
|
69
69
|
// ---------------------------------------------------------------------------
|
|
70
70
|
|
|
71
|
-
/** Suggest
|
|
71
|
+
/** Suggest audit flow after a successful build flow. */
|
|
72
72
|
registerHook({
|
|
73
|
-
name: "pi-agent-flow/
|
|
74
|
-
trigger: { flowTypes: ["
|
|
73
|
+
name: "pi-agent-flow/build-to-audit",
|
|
74
|
+
trigger: { flowTypes: ["build"], onlyOnSuccess: true },
|
|
75
75
|
action: (ctx) => {
|
|
76
|
-
const
|
|
77
|
-
(p) => p.type.toLowerCase() === "
|
|
76
|
+
const auditWasRequested = ctx.params.some(
|
|
77
|
+
(p) => p.type.toLowerCase() === "audit",
|
|
78
78
|
);
|
|
79
|
-
if (
|
|
79
|
+
if (auditWasRequested) return null;
|
|
80
80
|
|
|
81
81
|
return {
|
|
82
82
|
content:
|
|
83
|
-
"Consider running a [
|
|
83
|
+
"Consider running a [audit] flow to audit the changes for security, correctness, and code quality.",
|
|
84
84
|
priority: 10,
|
|
85
85
|
};
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
|
|
89
|
-
/** Suggest
|
|
89
|
+
/** Suggest build flow after a successful debug flow. */
|
|
90
90
|
registerHook({
|
|
91
|
-
name: "pi-agent-flow/debug-to-
|
|
91
|
+
name: "pi-agent-flow/debug-to-build",
|
|
92
92
|
trigger: { flowTypes: ["debug"], onlyOnSuccess: true },
|
|
93
93
|
action: (ctx) => {
|
|
94
|
-
const
|
|
95
|
-
(p) => p.type.toLowerCase() === "
|
|
94
|
+
const buildWasRequested = ctx.params.some(
|
|
95
|
+
(p) => p.type.toLowerCase() === "build",
|
|
96
96
|
);
|
|
97
|
-
if (
|
|
97
|
+
if (buildWasRequested) return null;
|
|
98
98
|
|
|
99
99
|
return {
|
|
100
100
|
content:
|
|
101
|
-
"The root cause has been identified. Consider running a [
|
|
101
|
+
"The root cause has been identified. Consider running a [build] flow to implement the fix.",
|
|
102
102
|
priority: 10,
|
|
103
103
|
};
|
|
104
104
|
},
|
|
105
105
|
});
|
|
106
|
+
|
|
107
|
+
/** Suggest scout flow after a successful audit flow. */
|
|
108
|
+
registerHook({
|
|
109
|
+
name: "pi-agent-flow/audit-to-scout",
|
|
110
|
+
trigger: { flowTypes: ["audit"], onlyOnSuccess: true },
|
|
111
|
+
action: (ctx) => {
|
|
112
|
+
const scoutWasRequested = ctx.params.some(
|
|
113
|
+
(p) => p.type.toLowerCase() === "scout",
|
|
114
|
+
);
|
|
115
|
+
if (scoutWasRequested) return null;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
content:
|
|
119
|
+
"Audit complete. Consider running a [scout] flow to trace the audit findings across the codebase.",
|
|
120
|
+
priority: 15,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
});
|