pi-agent-flow 1.2.0 → 1.2.2
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 +2 -27
- package/agents/build.md +2 -27
- package/agents/craft.md +2 -26
- package/agents/debug.md +0 -26
- package/agents/ideas.md +2 -25
- package/agents/scout.md +3 -28
- package/package.json +1 -1
- package/src/ambient.d.ts +1 -1
- package/src/batch.ts +445 -17
- package/src/config.ts +5 -0
- package/src/flow.ts +54 -3
- package/src/index.ts +162 -2
- package/src/render.ts +39 -0
- package/src/structured-output.ts +97 -0
- package/src/types.ts +98 -0
package/src/index.ts
CHANGED
|
@@ -23,10 +23,15 @@ import { mapFlowConcurrent, runFlow } from "./flow.js";
|
|
|
23
23
|
import {
|
|
24
24
|
type SingleResult,
|
|
25
25
|
type FlowDetails,
|
|
26
|
+
type CompressedFlowResult,
|
|
27
|
+
type FileEntry,
|
|
28
|
+
type CommandEntry,
|
|
26
29
|
emptyFlowUsage,
|
|
27
30
|
isFlowError,
|
|
28
31
|
isFlowSuccess,
|
|
32
|
+
getFlowOutput,
|
|
29
33
|
} from "./types.js";
|
|
34
|
+
import { extractStructuredOutput } from "./structured-output.js";
|
|
30
35
|
import { createBatchTool, createBatchReadTool } from "./batch.js";
|
|
31
36
|
import {
|
|
32
37
|
createWebTool,
|
|
@@ -471,6 +476,152 @@ function stripReasoningFromAssistantMessage(message: any): {
|
|
|
471
476
|
function isJsonEqual(a: any, b: any): boolean {
|
|
472
477
|
return JSON.stringify(a) === JSON.stringify(b);
|
|
473
478
|
}
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// Flow result compression
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Module-level cache populated after each flow tool execution.
|
|
485
|
+
* Maps toolCallId → compressed flow result.
|
|
486
|
+
* Consumed by compressFlowToolResults at fork time.
|
|
487
|
+
*/
|
|
488
|
+
const flowResultCache = new Map<string, CompressedFlowResult[]>();
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Build compressed representations of flow results and cache them by toolCallId.
|
|
492
|
+
*/
|
|
493
|
+
function cacheFlowResults(toolCallId: string, results: SingleResult[]): void {
|
|
494
|
+
for (const result of results) {
|
|
495
|
+
const so = result.structuredOutput;
|
|
496
|
+
if (!so) continue;
|
|
497
|
+
const compressed: CompressedFlowResult = {
|
|
498
|
+
type: result.type,
|
|
499
|
+
status: isFlowError(result) ? "failed" : "accomplished",
|
|
500
|
+
};
|
|
501
|
+
if (so.files.length > 0) compressed.files = so.files;
|
|
502
|
+
if (so.commands.length > 0) compressed.commands = so.commands;
|
|
503
|
+
if (result.errorMessage) compressed.error = result.errorMessage;
|
|
504
|
+
const existing = flowResultCache.get(toolCallId) ?? [];
|
|
505
|
+
existing.push(compressed);
|
|
506
|
+
flowResultCache.set(toolCallId, existing);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Render a compressed flow result as compact text for child context.
|
|
512
|
+
*/
|
|
513
|
+
function renderCompressedFlowResult(r: CompressedFlowResult): string {
|
|
514
|
+
const parts: string[] = [`[Flow: ${r.type} ${r.status}]`];
|
|
515
|
+
if (r.files?.length) {
|
|
516
|
+
const fileLines = r.files.map((f) => {
|
|
517
|
+
const role = f.role ? ` (${f.role})` : "";
|
|
518
|
+
const desc = f.description ? ` — ${f.description}` : "";
|
|
519
|
+
return ` ${f.path}${role}${desc}`;
|
|
520
|
+
});
|
|
521
|
+
parts.push(`Files:\n${fileLines.join("\n")}`);
|
|
522
|
+
}
|
|
523
|
+
if (r.commands?.length) {
|
|
524
|
+
const cmdLines = r.commands.map((c) => {
|
|
525
|
+
const result = c.result ? ` (${c.result})` : "";
|
|
526
|
+
const purpose = c.purpose ? ` — ${c.purpose}` : "";
|
|
527
|
+
return ` ${c.tool ?? "cmd"}: ${c.command}${result}${purpose}`;
|
|
528
|
+
});
|
|
529
|
+
parts.push(`Commands:\n${cmdLines.join("\n")}`);
|
|
530
|
+
}
|
|
531
|
+
if (r.error) parts.push(`Error: ${r.error}`);
|
|
532
|
+
return parts.join("\n");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Compress flow tool results in a sanitized session snapshot.
|
|
537
|
+
*
|
|
538
|
+
* Scans for tool result messages that correspond to flow invocations
|
|
539
|
+
* and replaces their content with compact compressed output.
|
|
540
|
+
*/
|
|
541
|
+
export function compressFlowToolResults(snapshot: string, cache: Map<string, CompressedFlowResult[]>): string {
|
|
542
|
+
if (cache.size === 0) return snapshot;
|
|
543
|
+
|
|
544
|
+
const lines = snapshot.trimEnd().split("\n");
|
|
545
|
+
const result: string[] = [];
|
|
546
|
+
|
|
547
|
+
// First pass: map toolCallId → tool name from assistant messages
|
|
548
|
+
const toolCallIdToName = new Map<string, string>();
|
|
549
|
+
for (const line of lines) {
|
|
550
|
+
let entry: any;
|
|
551
|
+
try { entry = JSON.parse(line); } catch { continue; }
|
|
552
|
+
if (entry?.type !== "message" || entry.message?.role !== "assistant") continue;
|
|
553
|
+
const content = entry.message.content;
|
|
554
|
+
if (!Array.isArray(content)) continue;
|
|
555
|
+
for (const part of content) {
|
|
556
|
+
if (part.type === "toolCall" && part.toolCallId && part.name) {
|
|
557
|
+
toolCallIdToName.set(part.toolCallId, part.name);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Second pass: compress flow tool results
|
|
563
|
+
for (const line of lines) {
|
|
564
|
+
let entry: any;
|
|
565
|
+
try { entry = JSON.parse(line); } catch { result.push(line); continue; }
|
|
566
|
+
|
|
567
|
+
if (entry?.type !== "message" || entry.message?.role !== "tool") {
|
|
568
|
+
result.push(line);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Extract toolCallId — either from message-level or content-level toolResult
|
|
573
|
+
let toolCallId: string | undefined;
|
|
574
|
+
if (typeof entry.message.toolCallId === "string") {
|
|
575
|
+
toolCallId = entry.message.toolCallId;
|
|
576
|
+
} else if (Array.isArray(entry.message.content)) {
|
|
577
|
+
for (const part of entry.message.content) {
|
|
578
|
+
if (part.type === "toolResult" && part.toolCallId) {
|
|
579
|
+
toolCallId = part.toolCallId;
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
if (!toolCallId) { result.push(line); continue; }
|
|
586
|
+
|
|
587
|
+
const toolName = toolCallIdToName.get(toolCallId);
|
|
588
|
+
if (toolName !== "flow") { result.push(line); continue; }
|
|
589
|
+
|
|
590
|
+
const compressed = cache.get(toolCallId);
|
|
591
|
+
if (!compressed || compressed.length === 0) { result.push(line); continue; }
|
|
592
|
+
|
|
593
|
+
const rendered = compressed.map(renderCompressedFlowResult).join("\n\n");
|
|
594
|
+
|
|
595
|
+
// Replace content in the tool result message
|
|
596
|
+
if (typeof entry.message.toolCallId === "string") {
|
|
597
|
+
// Format 1: toolCallId at message level, content is text array
|
|
598
|
+
entry = {
|
|
599
|
+
...entry,
|
|
600
|
+
message: {
|
|
601
|
+
...entry.message,
|
|
602
|
+
content: [{ type: "text", text: rendered }],
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
} else {
|
|
606
|
+
// Format 2: toolCallId inside content array
|
|
607
|
+
entry = {
|
|
608
|
+
...entry,
|
|
609
|
+
message: {
|
|
610
|
+
...entry.message,
|
|
611
|
+
content: entry.message.content.map((part: any) =>
|
|
612
|
+
part.type === "toolResult" && part.toolCallId === toolCallId
|
|
613
|
+
? { ...part, content: rendered }
|
|
614
|
+
: part,
|
|
615
|
+
),
|
|
616
|
+
},
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
result.push(JSON.stringify(entry));
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return `${result.join("\n")}\n`;
|
|
624
|
+
}
|
|
474
625
|
|
|
475
626
|
/**
|
|
476
627
|
* Sanitize a fork session snapshot JSONL to remove only non-inheritable
|
|
@@ -533,7 +684,8 @@ function sanitizeForkSnapshot(snapshot: string | null): string | null {
|
|
|
533
684
|
sanitizedLines.push(changed ? JSON.stringify(entry) : line);
|
|
534
685
|
}
|
|
535
686
|
|
|
536
|
-
|
|
687
|
+
const sanitized = `${sanitizedLines.join("\n")}\n`;
|
|
688
|
+
return compressFlowToolResults(sanitized, flowResultCache);
|
|
537
689
|
}
|
|
538
690
|
|
|
539
691
|
function computeActiveTools(optimize: boolean): string[] {
|
|
@@ -582,6 +734,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
582
734
|
|
|
583
735
|
// toolOptimize: CLI flag > env var > settings.json > default (true)
|
|
584
736
|
let toolOptimize = true;
|
|
737
|
+
// structuredOutput: settings.json > default (true)
|
|
738
|
+
let structuredOutput = true;
|
|
585
739
|
const envToolOptimize = process.env[FLOW_TOOL_OPTIMIZE_ENV];
|
|
586
740
|
if (envToolOptimize !== undefined) {
|
|
587
741
|
const parsed = parseBoolean(envToolOptimize);
|
|
@@ -613,6 +767,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
613
767
|
if (typeof flowSettings.toolOptimize === "boolean") {
|
|
614
768
|
toolOptimize = flowSettings.toolOptimize;
|
|
615
769
|
}
|
|
770
|
+
if (typeof flowSettings.structuredOutput === "boolean") {
|
|
771
|
+
structuredOutput = flowSettings.structuredOutput;
|
|
772
|
+
}
|
|
616
773
|
}
|
|
617
774
|
|
|
618
775
|
// Only restrict tools for the main orchestrator (depth 0).
|
|
@@ -776,8 +933,9 @@ flow [type] accomplished
|
|
|
776
933
|
].join("\n"),
|
|
777
934
|
parameters: FlowParams,
|
|
778
935
|
|
|
779
|
-
async execute(
|
|
936
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
780
937
|
const discovery = discoverFlows(ctx.cwd, "all");
|
|
938
|
+
flowResultCache.clear();
|
|
781
939
|
const { flows } = discovery;
|
|
782
940
|
const makeDetails = makeFlowDetailsFactory(discovery.projectFlowsDir);
|
|
783
941
|
|
|
@@ -940,6 +1098,7 @@ flow [type] accomplished
|
|
|
940
1098
|
maxDepth: effectiveMaxDepth,
|
|
941
1099
|
preventCycles,
|
|
942
1100
|
toolOptimize,
|
|
1101
|
+
structuredOutput,
|
|
943
1102
|
model: candidateModel,
|
|
944
1103
|
signal,
|
|
945
1104
|
onUpdate: (partial) => {
|
|
@@ -970,6 +1129,7 @@ flow [type] accomplished
|
|
|
970
1129
|
return result;
|
|
971
1130
|
});
|
|
972
1131
|
|
|
1132
|
+
cacheFlowResults(toolCallId, results);
|
|
973
1133
|
// Build tool result with FULL flow output — no truncation
|
|
974
1134
|
const successCount = results.filter((r) => isFlowSuccess(r)).length;
|
|
975
1135
|
const flowReports = results.map((r) => {
|
package/src/render.ts
CHANGED
|
@@ -241,6 +241,42 @@ function renderFlowExpanded(
|
|
|
241
241
|
// Flow report (structured output)
|
|
242
242
|
container.addChild(new Spacer(1));
|
|
243
243
|
container.addChild(new Text(theme.fg("muted", sectionHeader("report")), 0, 0));
|
|
244
|
+
|
|
245
|
+
// Structured output summary (compact badge when available)
|
|
246
|
+
if (r.structuredOutput) {
|
|
247
|
+
const so = r.structuredOutput;
|
|
248
|
+
const statusColor = so.status === "complete" ? "success" : so.status === "partial" ? "warning" : "error";
|
|
249
|
+
container.addChild(new Text(
|
|
250
|
+
`${theme.fg(statusColor, `[${so.status}]`)} ${theme.fg("dim", so.summary)}`,
|
|
251
|
+
0, 0,
|
|
252
|
+
));
|
|
253
|
+
if (so.files.length > 0) {
|
|
254
|
+
container.addChild(new Text(theme.fg("dim", `Files: ${so.files.map((f) => f.path).join(", ")}`), 0, 0));
|
|
255
|
+
}
|
|
256
|
+
if (so.commands?.length > 0) {
|
|
257
|
+
const cmdLabels = so.commands.map((c) => {
|
|
258
|
+
const short = c.command.length > 30 ? c.command.slice(0, 30) + "..." : c.command;
|
|
259
|
+
return `${c.tool ?? "cmd"}: ${short}`;
|
|
260
|
+
});
|
|
261
|
+
container.addChild(new Text(theme.fg("dim", `Commands: ${cmdLabels.join(", ")}`), 0, 0));
|
|
262
|
+
}
|
|
263
|
+
if (so.notDone.length > 0) {
|
|
264
|
+
const notDoneText = so.notDone.map((item) => {
|
|
265
|
+
const details = [
|
|
266
|
+
item.reason ? `reason: ${item.reason}` : undefined,
|
|
267
|
+
item.blocker ? `blocker: ${item.blocker}` : undefined,
|
|
268
|
+
item.nextStep ? `next: ${item.nextStep}` : undefined,
|
|
269
|
+
].filter(Boolean).join("; ");
|
|
270
|
+
return details ? `${item.item} (${details})` : item.item;
|
|
271
|
+
}).join("; ");
|
|
272
|
+
container.addChild(new Text(theme.fg("dim", `Not Done: ${notDoneText}`), 0, 0));
|
|
273
|
+
}
|
|
274
|
+
if (so.nextSteps.length > 0) {
|
|
275
|
+
container.addChild(new Text(theme.fg("dim", `Next: ${so.nextSteps.join("; ")}`), 0, 0));
|
|
276
|
+
}
|
|
277
|
+
container.addChild(new Spacer(1));
|
|
278
|
+
}
|
|
279
|
+
|
|
244
280
|
if (flowOutput) {
|
|
245
281
|
container.addChild(new Markdown(flowOutput.trim(), 0, 0, mdTheme));
|
|
246
282
|
} else {
|
|
@@ -294,6 +330,9 @@ function renderFlowCollapsed(
|
|
|
294
330
|
if (r.exitCode === -1 && streamingText) {
|
|
295
331
|
const logContent = tailText(streamingText, contentBudget(10));
|
|
296
332
|
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", logContent)}`, 0, 0));
|
|
333
|
+
} else if (r.structuredOutput?.summary) {
|
|
334
|
+
const logContent = truncateChars(r.structuredOutput.summary, contentBudget(10));
|
|
335
|
+
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", logContent)}`, 0, 0));
|
|
297
336
|
} else if (flowOutput) {
|
|
298
337
|
const logContent = tailText(flowOutput, contentBudget(10));
|
|
299
338
|
container.addChild(new TruncatedText(`${theme.fg("dim", "└─ msg:")} ${theme.fg("dim", logContent)}`, 0, 0));
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured JSON output extraction from flow responses.
|
|
3
|
+
*
|
|
4
|
+
* Parses a JSON code block from the end of the flow's final assistant text
|
|
5
|
+
* and validates it against the FlowStructuredOutput schema.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Action, CommandEntry, FileEntry, FlowStructuredOutput, NotDoneItem } from "./types.js";
|
|
9
|
+
|
|
10
|
+
type FlowStatus = FlowStructuredOutput["status"];
|
|
11
|
+
|
|
12
|
+
type StructuredOutputRecord = {
|
|
13
|
+
version: string;
|
|
14
|
+
status: FlowStatus;
|
|
15
|
+
summary: string;
|
|
16
|
+
files?: FileEntry[];
|
|
17
|
+
actions?: Action[];
|
|
18
|
+
commands?: CommandEntry[];
|
|
19
|
+
notDone?: NotDoneItem[];
|
|
20
|
+
nextSteps?: string[];
|
|
21
|
+
reasoning?: string[];
|
|
22
|
+
notes?: string[];
|
|
23
|
+
extensions?: Record<string, unknown>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const VALID_STATUSES: FlowStatus[] = ["complete", "partial", "blocked", "failed"];
|
|
27
|
+
|
|
28
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
29
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isOptionalArray(record: Record<string, unknown>, key: string): boolean {
|
|
33
|
+
return record[key] === undefined || Array.isArray(record[key]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Minimum required fields to consider parsed JSON a valid structured output. */
|
|
37
|
+
function isValidStructuredOutput(obj: unknown): obj is StructuredOutputRecord {
|
|
38
|
+
if (!isRecord(obj)) return false;
|
|
39
|
+
return (
|
|
40
|
+
typeof obj.version === "string" &&
|
|
41
|
+
typeof obj.status === "string" &&
|
|
42
|
+
VALID_STATUSES.includes(obj.status as FlowStatus) &&
|
|
43
|
+
typeof obj.summary === "string" &&
|
|
44
|
+
isOptionalArray(obj, "files") &&
|
|
45
|
+
isOptionalArray(obj, "actions") &&
|
|
46
|
+
isOptionalArray(obj, "commands") &&
|
|
47
|
+
isOptionalArray(obj, "notDone") &&
|
|
48
|
+
isOptionalArray(obj, "nextSteps") &&
|
|
49
|
+
isOptionalArray(obj, "reasoning") &&
|
|
50
|
+
isOptionalArray(obj, "notes")
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Extract a structured JSON output block from the end of an assistant's text.
|
|
56
|
+
*
|
|
57
|
+
* Looks for a final ```json ... ``` code block, parses it, and validates
|
|
58
|
+
* against the FlowStructuredOutput schema. Returns undefined when the block
|
|
59
|
+
* is missing, malformed, or fails validation.
|
|
60
|
+
*/
|
|
61
|
+
export function extractStructuredOutput(text: string): FlowStructuredOutput | undefined {
|
|
62
|
+
if (!text) return undefined;
|
|
63
|
+
|
|
64
|
+
// Find the last ```json ... ``` block in the text.
|
|
65
|
+
// Scan backward from the end to handle multiple blocks.
|
|
66
|
+
const allMatches = [...text.matchAll(/```json\s*([\s\S]*?)\s*```/g)];
|
|
67
|
+
const match = allMatches.length > 0 ? allMatches[allMatches.length - 1] : null;
|
|
68
|
+
if (!match || !match[1]) return undefined;
|
|
69
|
+
|
|
70
|
+
const jsonStr = match[1].trim();
|
|
71
|
+
if (!jsonStr) return undefined;
|
|
72
|
+
|
|
73
|
+
let parsed: unknown;
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(jsonStr);
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isValidStructuredOutput(parsed)) return undefined;
|
|
81
|
+
|
|
82
|
+
// Sanitize: trim string fields and normalize omitted arrays to [] for
|
|
83
|
+
// backward compatibility with earlier structured-output prompts.
|
|
84
|
+
return {
|
|
85
|
+
version: parsed.version.trim(),
|
|
86
|
+
status: parsed.status,
|
|
87
|
+
summary: parsed.summary.trim(),
|
|
88
|
+
files: parsed.files ?? [],
|
|
89
|
+
actions: parsed.actions ?? [],
|
|
90
|
+
commands: parsed.commands ?? [],
|
|
91
|
+
notDone: parsed.notDone ?? [],
|
|
92
|
+
nextSteps: parsed.nextSteps ?? [],
|
|
93
|
+
reasoning: parsed.reasoning ?? [],
|
|
94
|
+
notes: parsed.notes ?? [],
|
|
95
|
+
...(parsed.extensions !== undefined ? { extensions: parsed.extensions } : {}),
|
|
96
|
+
};
|
|
97
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -18,6 +18,102 @@ export interface UsageStats {
|
|
|
18
18
|
smoothedTps?: number;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Structured file entry in a flow's output. */
|
|
22
|
+
export interface FileEntry {
|
|
23
|
+
/** Path to the file, relative or absolute. */
|
|
24
|
+
path: string;
|
|
25
|
+
/** Semantic role of this file in the flow's work. */
|
|
26
|
+
role?: "reference" | "read" | "modified" | "created" | "deleted" | "test";
|
|
27
|
+
/** Why this file matters (1 sentence). */
|
|
28
|
+
description?: string;
|
|
29
|
+
/** Short excerpt or snippet (not full content). */
|
|
30
|
+
snippet?: string;
|
|
31
|
+
/** Specific line ranges of interest. */
|
|
32
|
+
ranges?: Array<{
|
|
33
|
+
start: number;
|
|
34
|
+
end: number;
|
|
35
|
+
/** Free-form label like "bug", "fix", "ref", "added". */
|
|
36
|
+
label?: string;
|
|
37
|
+
}>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Structured command/tool invocation entry in a flow's output. */
|
|
41
|
+
export interface CommandEntry {
|
|
42
|
+
/** The command string or tool call representation. */
|
|
43
|
+
command: string;
|
|
44
|
+
/** Tool used: bash, grep, find, ls, batch, read, write, edit, flow, web. */
|
|
45
|
+
tool?: string;
|
|
46
|
+
/** Working directory or target scope (file path, URL, etc.). */
|
|
47
|
+
target?: string;
|
|
48
|
+
/** Whether it succeeded, failed, or was aborted. */
|
|
49
|
+
result?: "success" | "failure" | "partial" | "aborted";
|
|
50
|
+
/** High-level output or error excerpt (not full stdout). */
|
|
51
|
+
output?: string;
|
|
52
|
+
/** Why this command was run (1 sentence). */
|
|
53
|
+
purpose?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Compressed representation of a flow result for child context inheritance. */
|
|
57
|
+
export interface CompressedFlowResult {
|
|
58
|
+
/** Flow type (scout, build, debug, etc.). */
|
|
59
|
+
type: string;
|
|
60
|
+
/** Execution outcome. */
|
|
61
|
+
status: "accomplished" | "failed" | "aborted";
|
|
62
|
+
/** Files touched, read, or referenced. */
|
|
63
|
+
files?: FileEntry[];
|
|
64
|
+
/** Commands or tool calls executed. */
|
|
65
|
+
commands?: CommandEntry[];
|
|
66
|
+
/** Error message for failed/aborted flows. */
|
|
67
|
+
error?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Action performed or attempted by a flow. */
|
|
71
|
+
export interface Action {
|
|
72
|
+
type: string;
|
|
73
|
+
description: string;
|
|
74
|
+
target?: string;
|
|
75
|
+
result?: "success" | "failure" | "partial" | "skipped";
|
|
76
|
+
evidence?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Incomplete, skipped, blocked, or deferred work reported by a flow. */
|
|
80
|
+
export interface NotDoneItem {
|
|
81
|
+
/** The unfinished item. */
|
|
82
|
+
item: string;
|
|
83
|
+
/** Why the item was not completed. */
|
|
84
|
+
reason?: string;
|
|
85
|
+
/** Concrete blocker preventing completion, when applicable. */
|
|
86
|
+
blocker?: string;
|
|
87
|
+
/** Suggested follow-up for this item. */
|
|
88
|
+
nextStep?: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Structured JSON output from a flow run. */
|
|
92
|
+
export interface FlowStructuredOutput {
|
|
93
|
+
/** Schema version for forward compatibility. */
|
|
94
|
+
version: string;
|
|
95
|
+
/** Overall completion status. */
|
|
96
|
+
status: "complete" | "partial" | "blocked" | "failed";
|
|
97
|
+
/** 1–3 sentence summary of what was accomplished. */
|
|
98
|
+
summary: string;
|
|
99
|
+
/** Files touched, read, or referenced. */
|
|
100
|
+
files: FileEntry[];
|
|
101
|
+
/** Actions performed or attempted. */
|
|
102
|
+
actions: Action[];
|
|
103
|
+
/** Commands or tool calls executed during the flow. */
|
|
104
|
+
commands: CommandEntry[];
|
|
105
|
+
/** Incomplete, skipped, blocked, or deferred work. */
|
|
106
|
+
notDone: NotDoneItem[];
|
|
107
|
+
/** Recommended next steps or follow-up flows. */
|
|
108
|
+
nextSteps: string[];
|
|
109
|
+
/** Reasoning chains, hypotheses, inferences made during the flow. */
|
|
110
|
+
reasoning: string[];
|
|
111
|
+
/** Observations, warnings, caveats, side notes. */
|
|
112
|
+
notes: string[];
|
|
113
|
+
/** Escape hatch for flow-specific data (audit findings, debug root cause, etc.). */
|
|
114
|
+
extensions?: Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
21
117
|
/** Result of a single flow invocation. */
|
|
22
118
|
export interface SingleResult {
|
|
23
119
|
type: string;
|
|
@@ -34,6 +130,8 @@ export interface SingleResult {
|
|
|
34
130
|
sawAgentEnd?: boolean;
|
|
35
131
|
/** Live in-progress text for status rendering; not part of the final flow report. */
|
|
36
132
|
streamingText?: string;
|
|
133
|
+
/** Structured JSON output parsed from the flow's final response. */
|
|
134
|
+
structuredOutput?: FlowStructuredOutput;
|
|
37
135
|
}
|
|
38
136
|
|
|
39
137
|
/** Metadata attached to every tool result for rendering. */
|