@fosterg4/pi-subagent 1.0.1 → 1.0.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/agents/planner.md +3 -0
- package/agents/reviewer.md +2 -1
- package/agents/scout.md +3 -0
- package/agents/worker.md +2 -1
- package/index.ts +65 -422
- package/package.json +1 -1
package/agents/planner.md
CHANGED
|
@@ -47,6 +47,9 @@ outputSchema:
|
|
|
47
47
|
|
|
48
48
|
You are a planning specialist. You receive context (from a scout) and requirements, then produce a clear implementation plan.
|
|
49
49
|
|
|
50
|
+
## STRICT OUTPUT RULE
|
|
51
|
+
Your ENTIRE response must be ONLY a valid JSON object matching the outputSchema below. Do NOT include any conversational text, greetings, explanations, markdown code fences, or any wrapping. No ```json blocks. No "Here is the plan:" prefix. Nothing but the raw JSON object.
|
|
52
|
+
|
|
50
53
|
You must NOT make any changes. Only read, analyze, and plan.
|
|
51
54
|
|
|
52
55
|
Return your plan as a JSON object matching the outputSchema.
|
package/agents/reviewer.md
CHANGED
|
@@ -59,7 +59,8 @@ Strategy:
|
|
|
59
59
|
2. Read the modified files
|
|
60
60
|
3. Check for bugs, security issues, code smells
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
## STRICT OUTPUT RULE
|
|
63
|
+
Your ENTIRE response must be ONLY a valid JSON object matching the outputSchema below. Do NOT include any conversational text, greetings, explanations, markdown code fences, or any wrapping. No ```json blocks. Nothing but the raw JSON object.
|
|
63
64
|
|
|
64
65
|
## Output format (JSON)
|
|
65
66
|
|
package/agents/scout.md
CHANGED
|
@@ -36,6 +36,9 @@ outputSchema:
|
|
|
36
36
|
|
|
37
37
|
You are a scout. Quickly investigate a codebase and return structured findings that another agent can use without re-reading everything.
|
|
38
38
|
|
|
39
|
+
## STRICT OUTPUT RULE
|
|
40
|
+
Your ENTIRE response must be ONLY a valid JSON object matching the outputSchema below. Do NOT include any conversational text, greetings, explanations, markdown code fences, or any wrapping. No ```json blocks. No "Here are my findings:" prefix. Nothing but the raw JSON object.
|
|
41
|
+
|
|
39
42
|
Your output will be passed to an agent who has NOT seen the files you explored.
|
|
40
43
|
|
|
41
44
|
Thoroughness (infer from task, default medium):
|
package/agents/worker.md
CHANGED
|
@@ -32,7 +32,8 @@ You are a worker agent with full capabilities. You operate in an isolated contex
|
|
|
32
32
|
|
|
33
33
|
Work autonomously to complete the assigned task. Use all available tools as needed.
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
## STRICT OUTPUT RULE
|
|
36
|
+
Your ENTIRE response must be ONLY a valid JSON object matching the outputSchema below. Do NOT include any conversational text, greetings, explanations, markdown code fences, or any wrapping. No ```json blocks. Nothing but the raw JSON object.
|
|
36
37
|
|
|
37
38
|
## Output format (JSON)
|
|
38
39
|
|
package/index.ts
CHANGED
|
@@ -38,115 +38,9 @@ import { type ValidationResult, validateSchema } from "./validate.ts";
|
|
|
38
38
|
|
|
39
39
|
const MAX_PARALLEL_TASKS = 8;
|
|
40
40
|
const MAX_CONCURRENCY = 4;
|
|
41
|
-
const COLLAPSED_ITEM_COUNT = 10;
|
|
42
41
|
const PER_TASK_OUTPUT_CAP = 50 * 1024;
|
|
43
42
|
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Formatting utilities
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
function formatTokens(count: number): string {
|
|
49
|
-
if (count < 1000) return count.toString();
|
|
50
|
-
if (count < 10000) return `${(count / 1000).toFixed(1)}k`;
|
|
51
|
-
if (count < 1000000) return `${Math.round(count / 1000)}k`;
|
|
52
|
-
return `${(count / 1000000).toFixed(1)}M`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function formatUsageStats(
|
|
56
|
-
usage: {
|
|
57
|
-
input: number;
|
|
58
|
-
output: number;
|
|
59
|
-
cacheRead: number;
|
|
60
|
-
cacheWrite: number;
|
|
61
|
-
cost: number;
|
|
62
|
-
contextTokens?: number;
|
|
63
|
-
turns?: number;
|
|
64
|
-
},
|
|
65
|
-
model?: string,
|
|
66
|
-
): string {
|
|
67
|
-
const parts: string[] = [];
|
|
68
|
-
if (usage.turns) parts.push(`${usage.turns} turn${usage.turns > 1 ? "s" : ""}`);
|
|
69
|
-
if (usage.input) parts.push(`\u2191${formatTokens(usage.input)}`);
|
|
70
|
-
if (usage.output) parts.push(`\u2193${formatTokens(usage.output)}`);
|
|
71
|
-
if (usage.cacheRead) parts.push(`R${formatTokens(usage.cacheRead)}`);
|
|
72
|
-
if (usage.cacheWrite) parts.push(`W${formatTokens(usage.cacheWrite)}`);
|
|
73
|
-
if (usage.cost) parts.push(`$${usage.cost.toFixed(4)}`);
|
|
74
|
-
if (usage.contextTokens && usage.contextTokens > 0) {
|
|
75
|
-
parts.push(`ctx:${formatTokens(usage.contextTokens)}`);
|
|
76
|
-
}
|
|
77
|
-
if (model) parts.push(model);
|
|
78
|
-
return parts.join(" ");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function formatToolCall(
|
|
82
|
-
toolName: string,
|
|
83
|
-
args: Record<string, unknown>,
|
|
84
|
-
themeFg: (color: string, text: string) => string,
|
|
85
|
-
): string {
|
|
86
|
-
const shortenPath = (p: string) => {
|
|
87
|
-
const home = os.homedir();
|
|
88
|
-
return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
|
|
89
|
-
};
|
|
90
43
|
|
|
91
|
-
switch (toolName) {
|
|
92
|
-
case "bash": {
|
|
93
|
-
const command = (args.command as string) || "...";
|
|
94
|
-
const preview = command.length > 60 ? `${command.slice(0, 60)}...` : command;
|
|
95
|
-
return themeFg("muted", "$ ") + themeFg("toolOutput", preview);
|
|
96
|
-
}
|
|
97
|
-
case "read": {
|
|
98
|
-
const rawPath = (args.file_path || args.path || "...") as string;
|
|
99
|
-
const filePath = shortenPath(rawPath);
|
|
100
|
-
const offset = args.offset as number | undefined;
|
|
101
|
-
const limit = args.limit as number | undefined;
|
|
102
|
-
let text = themeFg("accent", filePath);
|
|
103
|
-
if (offset !== undefined || limit !== undefined) {
|
|
104
|
-
const startLine = offset ?? 1;
|
|
105
|
-
const endLine = limit !== undefined ? startLine + limit - 1 : "";
|
|
106
|
-
text += themeFg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
107
|
-
}
|
|
108
|
-
return themeFg("muted", "read ") + text;
|
|
109
|
-
}
|
|
110
|
-
case "write": {
|
|
111
|
-
const rawPath = (args.file_path || args.path || "...") as string;
|
|
112
|
-
const filePath = shortenPath(rawPath);
|
|
113
|
-
const content = (args.content || "") as string;
|
|
114
|
-
const lines = content.split("\n").length;
|
|
115
|
-
let text = themeFg("muted", "write ") + themeFg("accent", filePath);
|
|
116
|
-
if (lines > 1) text += themeFg("dim", ` (${lines} lines)`);
|
|
117
|
-
return text;
|
|
118
|
-
}
|
|
119
|
-
case "edit": {
|
|
120
|
-
const rawPath = (args.file_path || args.path || "...") as string;
|
|
121
|
-
return themeFg("muted", "edit ") + themeFg("accent", shortenPath(rawPath));
|
|
122
|
-
}
|
|
123
|
-
case "ls":
|
|
124
|
-
return themeFg("muted", "ls ") + themeFg("accent", shortenPath((args.path || ".") as string));
|
|
125
|
-
case "find":
|
|
126
|
-
return (
|
|
127
|
-
themeFg("muted", "find ") +
|
|
128
|
-
themeFg("accent", (args.pattern || "*") as string) +
|
|
129
|
-
themeFg("dim", ` in ${shortenPath((args.path || ".") as string)}`)
|
|
130
|
-
);
|
|
131
|
-
case "grep":
|
|
132
|
-
return (
|
|
133
|
-
themeFg("muted", "grep ") +
|
|
134
|
-
themeFg("accent", `/${(args.pattern || "") as string}/`) +
|
|
135
|
-
themeFg("dim", ` in ${shortenPath((args.path || ".") as string)}`)
|
|
136
|
-
);
|
|
137
|
-
default:
|
|
138
|
-
return (
|
|
139
|
-
themeFg("accent", toolName) +
|
|
140
|
-
themeFg("dim", ` ${JSON.stringify(args).slice(0, 50)}...`)
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
// Types
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
interface UsageStats {
|
|
150
44
|
input: number;
|
|
151
45
|
output: number;
|
|
152
46
|
cacheRead: number;
|
|
@@ -225,25 +119,6 @@ function truncateParallelOutput(output: string): string {
|
|
|
225
119
|
return `${truncated}\n\n[Output truncated: ${byteLength - Buffer.byteLength(truncated, "utf8")} bytes omitted. Full output preserved in tool details.]`;
|
|
226
120
|
}
|
|
227
121
|
|
|
228
|
-
type DisplayItem =
|
|
229
|
-
| { type: "text"; text: string }
|
|
230
|
-
| { type: "toolCall"; name: string; args: Record<string, unknown> };
|
|
231
|
-
|
|
232
|
-
function getDisplayItems(messages: Message[]): DisplayItem[] {
|
|
233
|
-
const items: DisplayItem[] = [];
|
|
234
|
-
for (const msg of messages) {
|
|
235
|
-
if (msg.role === "assistant") {
|
|
236
|
-
for (const part of msg.content) {
|
|
237
|
-
if (part.type === "text")
|
|
238
|
-
items.push({ type: "text", text: part.text });
|
|
239
|
-
else if (part.type === "toolCall")
|
|
240
|
-
items.push({ type: "toolCall", name: part.name, args: part.arguments });
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return items;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
122
|
// ---------------------------------------------------------------------------
|
|
248
123
|
// Concurrency
|
|
249
124
|
// ---------------------------------------------------------------------------
|
|
@@ -1065,363 +940,131 @@ export default function (pi: ExtensionAPI) {
|
|
|
1065
940
|
|
|
1066
941
|
const mdTheme = getMarkdownTheme();
|
|
1067
942
|
|
|
1068
|
-
const
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
} else {
|
|
1081
|
-
text += `${theme.fg("muted", "\u2192 ") + formatToolCall(item.name, item.args, theme.fg.bind(theme))}\n`;
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
return text.trimEnd();
|
|
943
|
+
const getFinalOutputText = (messages: Message[]): string => {
|
|
944
|
+
const text = getFinalOutput(messages);
|
|
945
|
+
// Try to extract clean text from JSON output (remove wrapper text)
|
|
946
|
+
const extracted = extractStructuredOutput(messages);
|
|
947
|
+
if (extracted) return JSON.stringify(extracted, null, 2);
|
|
948
|
+
return text;
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
const getStatusText = (r: SingleResult): string => {
|
|
952
|
+
if (r.exitCode === 0) return theme.fg("success", "\u2713");
|
|
953
|
+
if (r.exitCode === -1) return theme.fg("warning", "\u23F3");
|
|
954
|
+
return theme.fg("error", "\u2717");
|
|
1085
955
|
};
|
|
1086
956
|
|
|
1087
957
|
// --- Single mode ---
|
|
1088
958
|
if (details.mode === "single" && details.results.length === 1) {
|
|
1089
959
|
const r = details.results[0];
|
|
1090
|
-
const
|
|
1091
|
-
const
|
|
1092
|
-
? theme.fg("error", "\u2717")
|
|
1093
|
-
: theme.fg("success", "\u2713");
|
|
1094
|
-
const displayItems = getDisplayItems(r.messages);
|
|
960
|
+
const status = getStatusText(r);
|
|
961
|
+
const finalOutput = getFinalOutputText(r.messages);
|
|
1095
962
|
|
|
1096
963
|
if (expanded) {
|
|
1097
964
|
const container = new Container();
|
|
1098
|
-
|
|
1099
|
-
if (isError && r.stopReason)
|
|
1100
|
-
header += ` ${theme.fg("error", `[${r.stopReason}]`)}`;
|
|
965
|
+
const header = `${status} ${theme.fg("accent", r.agent)}${theme.fg("muted", r.model ? ` \u00B7 ${r.model}` : "")}`;
|
|
1101
966
|
container.addChild(new Text(header, 0, 0));
|
|
1102
|
-
if (
|
|
1103
|
-
container.addChild(
|
|
1104
|
-
new Text(theme.fg("error", `Error: ${r.errorMessage}`), 0, 0),
|
|
1105
|
-
);
|
|
1106
|
-
container.addChild(new Spacer(1));
|
|
1107
|
-
container.addChild(new Text(theme.fg("muted", "\u2500\u2500\u2500 Task \u2500\u2500\u2500"), 0, 0));
|
|
1108
|
-
container.addChild(new Text(theme.fg("dim", r.task), 0, 0));
|
|
1109
|
-
|
|
1110
|
-
// Show tool calls
|
|
1111
|
-
const toolCalls = displayItems.filter(
|
|
1112
|
-
(i) => i.type === "toolCall",
|
|
1113
|
-
);
|
|
1114
|
-
if (toolCalls.length > 0) {
|
|
1115
|
-
container.addChild(new Spacer(1));
|
|
1116
|
-
container.addChild(
|
|
1117
|
-
new Text(
|
|
1118
|
-
theme.fg("muted", "\u2500\u2500\u2500 Tool Calls \u2500\u2500\u2500"),
|
|
1119
|
-
0,
|
|
1120
|
-
0,
|
|
1121
|
-
),
|
|
1122
|
-
);
|
|
1123
|
-
for (const item of toolCalls) {
|
|
1124
|
-
container.addChild(
|
|
1125
|
-
new Text(
|
|
1126
|
-
theme.fg("muted", "\u2192 ") +
|
|
1127
|
-
formatToolCall(
|
|
1128
|
-
item.name,
|
|
1129
|
-
item.args,
|
|
1130
|
-
theme.fg.bind(theme),
|
|
1131
|
-
),
|
|
1132
|
-
0,
|
|
1133
|
-
0,
|
|
1134
|
-
),
|
|
1135
|
-
);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// Show structured output if available
|
|
1140
|
-
if (r.structuredOutput && Object.keys(r.structuredOutput).length > 0) {
|
|
1141
|
-
container.addChild(new Spacer(1));
|
|
1142
|
-
container.addChild(
|
|
1143
|
-
new Text(
|
|
1144
|
-
theme.fg("muted", "\u2500\u2500\u2500 Structured Output \u2500\u2500\u2500"),
|
|
1145
|
-
0,
|
|
1146
|
-
0,
|
|
1147
|
-
),
|
|
1148
|
-
);
|
|
1149
|
-
container.addChild(
|
|
1150
|
-
new Text(
|
|
1151
|
-
theme.fg("toolOutput", JSON.stringify(r.structuredOutput, null, 2)),
|
|
1152
|
-
0,
|
|
1153
|
-
0,
|
|
1154
|
-
),
|
|
1155
|
-
);
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
// Show final output
|
|
1159
|
-
const finalOutput = getFinalOutput(r.messages);
|
|
967
|
+
if (r.stderr) container.addChild(new Text(theme.fg("error", r.stderr), 0, 0));
|
|
1160
968
|
if (finalOutput) {
|
|
1161
969
|
container.addChild(new Spacer(1));
|
|
1162
|
-
container.addChild(
|
|
1163
|
-
new Text(theme.fg("muted", "\u2500\u2500\u2500 Output \u2500\u2500\u2500"), 0, 0),
|
|
1164
|
-
);
|
|
1165
970
|
container.addChild(new Markdown(finalOutput.trim(), 0, 0, mdTheme));
|
|
1166
971
|
}
|
|
1167
|
-
|
|
1168
|
-
const usageStr = formatUsageStats(r.usage, r.model);
|
|
1169
|
-
if (usageStr) {
|
|
1170
|
-
container.addChild(new Spacer(1));
|
|
1171
|
-
container.addChild(new Text(theme.fg("dim", usageStr), 0, 0));
|
|
1172
|
-
}
|
|
1173
972
|
return container;
|
|
1174
973
|
}
|
|
1175
974
|
|
|
1176
|
-
let text = `${
|
|
1177
|
-
if (
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
975
|
+
let text = `${status} ${theme.fg("accent", r.agent)}`;
|
|
976
|
+
if (finalOutput) {
|
|
977
|
+
const preview = finalOutput.split("\n").slice(0, 5).join("\n");
|
|
978
|
+
const truncated = finalOutput.split("\n").length > 5;
|
|
979
|
+
text += `\n${theme.fg("toolOutput", preview)}`;
|
|
980
|
+
if (truncated) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
981
|
+
} else {
|
|
1181
982
|
text += `\n${theme.fg("muted", "(no output)")}`;
|
|
1182
|
-
else {
|
|
1183
|
-
text += `\n${renderDisplayItems(displayItems, COLLAPSED_ITEM_COUNT)}`;
|
|
1184
|
-
if (displayItems.length > COLLAPSED_ITEM_COUNT)
|
|
1185
|
-
text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
1186
983
|
}
|
|
1187
|
-
const usageStr = formatUsageStats(r.usage, r.model);
|
|
1188
|
-
if (usageStr) text += `\n${theme.fg("dim", usageStr)}`;
|
|
1189
984
|
return new Text(text, 0, 0);
|
|
1190
985
|
}
|
|
1191
986
|
|
|
1192
|
-
const aggregateUsage = (results: SingleResult[]) => {
|
|
1193
|
-
const total = {
|
|
1194
|
-
input: 0,
|
|
1195
|
-
output: 0,
|
|
1196
|
-
cacheRead: 0,
|
|
1197
|
-
cacheWrite: 0,
|
|
1198
|
-
cost: 0,
|
|
1199
|
-
turns: 0,
|
|
1200
|
-
};
|
|
1201
|
-
for (const r of results) {
|
|
1202
|
-
total.input += r.usage.input;
|
|
1203
|
-
total.output += r.usage.output;
|
|
1204
|
-
total.cacheRead += r.usage.cacheRead;
|
|
1205
|
-
total.cacheWrite += r.usage.cacheWrite;
|
|
1206
|
-
total.cost += r.usage.cost;
|
|
1207
|
-
total.turns += r.usage.turns;
|
|
1208
|
-
}
|
|
1209
|
-
return total;
|
|
1210
|
-
};
|
|
1211
|
-
|
|
1212
987
|
// --- Chain mode ---
|
|
1213
988
|
if (details.mode === "chain") {
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
).
|
|
1217
|
-
const icon =
|
|
1218
|
-
|
|
1219
|
-
? theme.fg("success", "\u2713")
|
|
1220
|
-
: theme.fg("error", "\u2717");
|
|
989
|
+
const lastResult = details.results[details.results.length - 1];
|
|
990
|
+
const finalOutput = lastResult ? getFinalOutputText(lastResult.messages) : "";
|
|
991
|
+
const allOk = details.results.every((r) => r.exitCode === 0);
|
|
992
|
+
const icon = allOk ? theme.fg("success", "\u2713") : theme.fg("error", "\u2717");
|
|
993
|
+
const steps = details.results.map((r) => r.agent).join(" \u2192 ");
|
|
1221
994
|
|
|
1222
995
|
if (expanded) {
|
|
1223
996
|
const container = new Container();
|
|
1224
997
|
container.addChild(
|
|
1225
|
-
new Text(
|
|
1226
|
-
icon +
|
|
1227
|
-
" " +
|
|
1228
|
-
theme.fg("toolTitle", theme.bold("chain ")) +
|
|
1229
|
-
theme.fg("accent", `${successCount}/${details.results.length} steps`),
|
|
1230
|
-
0,
|
|
1231
|
-
0,
|
|
1232
|
-
),
|
|
998
|
+
new Text(`${icon} ${theme.fg("accent", steps)}`, 0, 0),
|
|
1233
999
|
);
|
|
1234
|
-
|
|
1235
1000
|
for (const r of details.results) {
|
|
1236
|
-
const
|
|
1237
|
-
|
|
1238
|
-
? theme.fg("success", "\u2713")
|
|
1239
|
-
: theme.fg("error", "\u2717");
|
|
1240
|
-
const displayItems = getDisplayItems(r.messages);
|
|
1241
|
-
const finalOutput = getFinalOutput(r.messages);
|
|
1242
|
-
|
|
1243
|
-
container.addChild(new Spacer(1));
|
|
1244
|
-
container.addChild(
|
|
1245
|
-
new Text(
|
|
1246
|
-
`${theme.fg("muted", `\u2500\u2500\u2500 Step ${r.step}: `) + theme.fg("accent", r.agent)} ${rIcon}`,
|
|
1247
|
-
0,
|
|
1248
|
-
0,
|
|
1249
|
-
),
|
|
1250
|
-
);
|
|
1251
|
-
container.addChild(
|
|
1252
|
-
new Text(theme.fg("muted", "Task: ") + theme.fg("dim", r.task), 0, 0),
|
|
1253
|
-
);
|
|
1254
|
-
|
|
1255
|
-
for (const item of displayItems) {
|
|
1256
|
-
if (item.type === "toolCall") {
|
|
1257
|
-
container.addChild(
|
|
1258
|
-
new Text(
|
|
1259
|
-
theme.fg("muted", "\u2192 ") +
|
|
1260
|
-
formatToolCall(
|
|
1261
|
-
item.name,
|
|
1262
|
-
item.args,
|
|
1263
|
-
theme.fg.bind(theme),
|
|
1264
|
-
),
|
|
1265
|
-
0,
|
|
1266
|
-
0,
|
|
1267
|
-
),
|
|
1268
|
-
);
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
if (finalOutput) {
|
|
1001
|
+
const out = getFinalOutputText(r.messages);
|
|
1002
|
+
if (out) {
|
|
1273
1003
|
container.addChild(new Spacer(1));
|
|
1274
1004
|
container.addChild(
|
|
1275
|
-
new
|
|
1005
|
+
new Text(
|
|
1006
|
+
`${getStatusText(r)} ${theme.fg("accent", r.agent)}${r.model ? theme.fg("muted", ` \u00B7 ${r.model}`) : ""}`,
|
|
1007
|
+
0,
|
|
1008
|
+
0,
|
|
1009
|
+
),
|
|
1276
1010
|
);
|
|
1011
|
+
container.addChild(new Markdown(out.trim(), 0, 0, mdTheme));
|
|
1277
1012
|
}
|
|
1278
|
-
|
|
1279
|
-
const stepUsage = formatUsageStats(r.usage, r.model);
|
|
1280
|
-
if (stepUsage)
|
|
1281
|
-
container.addChild(new Text(theme.fg("dim", stepUsage), 0, 0));
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
1285
|
-
if (usageStr) {
|
|
1286
|
-
container.addChild(new Spacer(1));
|
|
1287
|
-
container.addChild(
|
|
1288
|
-
new Text(theme.fg("dim", `Total: ${usageStr}`), 0, 0),
|
|
1289
|
-
);
|
|
1290
1013
|
}
|
|
1291
1014
|
return container;
|
|
1292
1015
|
}
|
|
1293
1016
|
|
|
1294
|
-
let text =
|
|
1295
|
-
|
|
1296
|
-
" "
|
|
1297
|
-
|
|
1298
|
-
theme.fg("
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
? theme.fg("success", "\u2713")
|
|
1303
|
-
: theme.fg("error", "\u2717");
|
|
1304
|
-
const displayItems = getDisplayItems(r.messages);
|
|
1305
|
-
text += `\n\n${theme.fg("muted", `\u2500\u2500\u2500 Step ${r.step}: `)}${theme.fg("accent", r.agent)} ${rIcon}`;
|
|
1306
|
-
if (displayItems.length === 0)
|
|
1307
|
-
text += `\n${theme.fg("muted", "(no output)")}`;
|
|
1308
|
-
else text += `\n${renderDisplayItems(displayItems, 5)}`;
|
|
1017
|
+
let text = `${icon} ${theme.fg("accent", steps)}`;
|
|
1018
|
+
if (finalOutput) {
|
|
1019
|
+
const preview = finalOutput.split("\n").slice(0, 5).join("\n");
|
|
1020
|
+
const truncated = finalOutput.split("\n").length > 5;
|
|
1021
|
+
text += `\n${theme.fg("toolOutput", preview)}`;
|
|
1022
|
+
if (truncated) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
1023
|
+
} else {
|
|
1024
|
+
text += `\n${theme.fg("muted", "(no output)")}`;
|
|
1309
1025
|
}
|
|
1310
|
-
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
1311
|
-
if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`;
|
|
1312
|
-
text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
1313
1026
|
return new Text(text, 0, 0);
|
|
1314
1027
|
}
|
|
1315
1028
|
|
|
1316
1029
|
// --- Parallel mode ---
|
|
1317
1030
|
if (details.mode === "parallel") {
|
|
1318
|
-
const running = details.results.filter(
|
|
1319
|
-
|
|
1320
|
-
).
|
|
1321
|
-
const
|
|
1322
|
-
(r) => r.exitCode !== -1 && !isFailedResult(r),
|
|
1323
|
-
).length;
|
|
1324
|
-
const failCount = details.results.filter(
|
|
1325
|
-
(r) => r.exitCode !== -1 && isFailedResult(r),
|
|
1326
|
-
).length;
|
|
1327
|
-
const isRunning = running > 0;
|
|
1328
|
-
const icon = isRunning
|
|
1031
|
+
const running = details.results.filter((r) => r.exitCode === -1).length;
|
|
1032
|
+
const done = details.results.filter((r) => r.exitCode !== -1).length;
|
|
1033
|
+
const allOk = details.results.every((r) => r.exitCode === 0);
|
|
1034
|
+
const icon = running > 0
|
|
1329
1035
|
? theme.fg("warning", "\u23F3")
|
|
1330
|
-
:
|
|
1331
|
-
? theme.fg("
|
|
1332
|
-
: theme.fg("
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
: `${successCount}/${details.results.length} tasks`;
|
|
1336
|
-
|
|
1337
|
-
if (expanded && !isRunning) {
|
|
1036
|
+
: allOk
|
|
1037
|
+
? theme.fg("success", "\u2713")
|
|
1038
|
+
: theme.fg("error", "\u2717");
|
|
1039
|
+
|
|
1040
|
+
if (expanded && running === 0) {
|
|
1338
1041
|
const container = new Container();
|
|
1339
1042
|
container.addChild(
|
|
1340
1043
|
new Text(
|
|
1341
|
-
`${icon} ${theme.fg("
|
|
1044
|
+
`${icon} ${theme.fg("accent", `${done} tasks`)}`,
|
|
1342
1045
|
0,
|
|
1343
1046
|
0,
|
|
1344
1047
|
),
|
|
1345
1048
|
);
|
|
1346
|
-
|
|
1347
1049
|
for (const r of details.results) {
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
: theme.fg("success", "\u2713");
|
|
1351
|
-
const displayItems = getDisplayItems(r.messages);
|
|
1352
|
-
const finalOutput = getFinalOutput(r.messages);
|
|
1353
|
-
|
|
1354
|
-
container.addChild(new Spacer(1));
|
|
1355
|
-
container.addChild(
|
|
1356
|
-
new Text(
|
|
1357
|
-
`${theme.fg("muted", "\u2500\u2500\u2500 ") + theme.fg("accent", r.agent)} ${rIcon}`,
|
|
1358
|
-
0,
|
|
1359
|
-
0,
|
|
1360
|
-
),
|
|
1361
|
-
);
|
|
1362
|
-
container.addChild(
|
|
1363
|
-
new Text(theme.fg("muted", "Task: ") + theme.fg("dim", r.task), 0, 0),
|
|
1364
|
-
);
|
|
1365
|
-
|
|
1366
|
-
for (const item of displayItems) {
|
|
1367
|
-
if (item.type === "toolCall") {
|
|
1368
|
-
container.addChild(
|
|
1369
|
-
new Text(
|
|
1370
|
-
theme.fg("muted", "\u2192 ") +
|
|
1371
|
-
formatToolCall(
|
|
1372
|
-
item.name,
|
|
1373
|
-
item.args,
|
|
1374
|
-
theme.fg.bind(theme),
|
|
1375
|
-
),
|
|
1376
|
-
0,
|
|
1377
|
-
0,
|
|
1378
|
-
),
|
|
1379
|
-
);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
if (finalOutput) {
|
|
1050
|
+
const out = getFinalOutputText(r.messages);
|
|
1051
|
+
if (out) {
|
|
1384
1052
|
container.addChild(new Spacer(1));
|
|
1385
1053
|
container.addChild(
|
|
1386
|
-
new
|
|
1054
|
+
new Text(
|
|
1055
|
+
`${getStatusText(r)} ${theme.fg("accent", r.agent)}`,
|
|
1056
|
+
0,
|
|
1057
|
+
0,
|
|
1058
|
+
),
|
|
1387
1059
|
);
|
|
1060
|
+
container.addChild(new Markdown(out.trim(), 0, 0, mdTheme));
|
|
1388
1061
|
}
|
|
1389
|
-
|
|
1390
|
-
const taskUsage = formatUsageStats(r.usage, r.model);
|
|
1391
|
-
if (taskUsage)
|
|
1392
|
-
container.addChild(new Text(theme.fg("dim", taskUsage), 0, 0));
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
1396
|
-
if (usageStr) {
|
|
1397
|
-
container.addChild(new Spacer(1));
|
|
1398
|
-
container.addChild(
|
|
1399
|
-
new Text(theme.fg("dim", `Total: ${usageStr}`), 0, 0),
|
|
1400
|
-
);
|
|
1401
1062
|
}
|
|
1402
1063
|
return container;
|
|
1403
1064
|
}
|
|
1404
1065
|
|
|
1405
|
-
let text = `${icon} ${theme.fg("
|
|
1406
|
-
|
|
1407
|
-
const rIcon =
|
|
1408
|
-
r.exitCode === -1
|
|
1409
|
-
? theme.fg("warning", "\u23F3")
|
|
1410
|
-
: isFailedResult(r)
|
|
1411
|
-
? theme.fg("error", "\u2717")
|
|
1412
|
-
: theme.fg("success", "\u2713");
|
|
1413
|
-
const displayItems = getDisplayItems(r.messages);
|
|
1414
|
-
text += `\n\n${theme.fg("muted", "\u2500\u2500\u2500 ")}${theme.fg("accent", r.agent)} ${rIcon}`;
|
|
1415
|
-
if (displayItems.length === 0)
|
|
1416
|
-
text += `\n${theme.fg("muted", r.exitCode === -1 ? "(running...)" : "(no output)")}`;
|
|
1417
|
-
else text += `\n${renderDisplayItems(displayItems, 5)}`;
|
|
1418
|
-
}
|
|
1419
|
-
if (!isRunning) {
|
|
1420
|
-
const usageStr = formatUsageStats(aggregateUsage(details.results));
|
|
1421
|
-
if (usageStr) text += `\n\n${theme.fg("dim", `Total: ${usageStr}`)}`;
|
|
1422
|
-
}
|
|
1423
|
-
if (!expanded)
|
|
1424
|
-
text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
|
|
1066
|
+
let text = `${icon} ${theme.fg("accent", `${done}/${details.results.length} tasks`)}`;
|
|
1067
|
+
if (running === 0 && !expanded) text += theme.fg("muted", " (Ctrl+O to expand)");
|
|
1425
1068
|
return new Text(text, 0, 0);
|
|
1426
1069
|
}
|
|
1427
1070
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fosterg4/pi-subagent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Delegate tasks to specialized subagents with isolated context windows, structured JSON handoff, contract schemas, and live TUI streaming",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|