@narumitw/pi-subagents 0.15.1 → 0.17.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/package.json +1 -1
- package/src/execution.ts +54 -27
- package/src/render.ts +461 -337
- package/src/runner.ts +262 -54
package/src/runner.ts
CHANGED
|
@@ -5,12 +5,7 @@ import * as path from "node:path";
|
|
|
5
5
|
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
6
6
|
import type { Message } from "@earendil-works/pi-ai";
|
|
7
7
|
import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import type {
|
|
9
|
-
AgentConfig,
|
|
10
|
-
AgentScope,
|
|
11
|
-
AgentSource,
|
|
12
|
-
SubagentThinkingLevel,
|
|
13
|
-
} from "./agents.js";
|
|
8
|
+
import type { AgentConfig, AgentScope, AgentSource, SubagentThinkingLevel } from "./agents.js";
|
|
14
9
|
import {
|
|
15
10
|
appendBounded,
|
|
16
11
|
DEFAULT_MAX_CONTEXT_BYTES,
|
|
@@ -32,6 +27,13 @@ export interface UsageStats {
|
|
|
32
27
|
contextTokens: number;
|
|
33
28
|
turns: number;
|
|
34
29
|
}
|
|
30
|
+
export type RecentActivityItem =
|
|
31
|
+
| { type: "text"; text: string }
|
|
32
|
+
| { type: "toolCall"; name: string; args: Record<string, unknown> };
|
|
33
|
+
|
|
34
|
+
const MAX_RECENT_ACTIVITY_ITEMS = 10;
|
|
35
|
+
const MAX_RECENT_ACTIVITY_BYTES = 8 * 1024;
|
|
36
|
+
const MAX_RECENT_ACTIVITY_ARGUMENT_BYTES = 1024;
|
|
35
37
|
|
|
36
38
|
export interface SingleResult {
|
|
37
39
|
agent: string;
|
|
@@ -42,6 +44,10 @@ export interface SingleResult {
|
|
|
42
44
|
stderr: string;
|
|
43
45
|
usage: UsageStats;
|
|
44
46
|
model?: string;
|
|
47
|
+
actualProvider?: string;
|
|
48
|
+
actualModel?: string;
|
|
49
|
+
recentActivity?: RecentActivityItem[];
|
|
50
|
+
recentActivityTotal?: number;
|
|
45
51
|
thinkingLevel?: SubagentThinkingLevel;
|
|
46
52
|
stopReason?: string;
|
|
47
53
|
errorMessage?: string;
|
|
@@ -72,9 +78,11 @@ function getFinalOutput(messages: Message[]): string {
|
|
|
72
78
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
73
79
|
const msg = messages[i];
|
|
74
80
|
if (msg.role === "assistant") {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
const text = msg.content
|
|
82
|
+
.filter((part) => part.type === "text")
|
|
83
|
+
.map((part) => part.text)
|
|
84
|
+
.join("\n");
|
|
85
|
+
if (text) return text;
|
|
78
86
|
}
|
|
79
87
|
}
|
|
80
88
|
return "";
|
|
@@ -84,36 +92,157 @@ export function getResultFinalOutput(result: SingleResult): string {
|
|
|
84
92
|
return result.finalOutput ?? getFinalOutput(result.messages);
|
|
85
93
|
}
|
|
86
94
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
export function isResultError(result: SingleResult): boolean {
|
|
96
|
+
return (
|
|
97
|
+
(result.exitCode !== 0 && result.exitCode !== -1) ||
|
|
98
|
+
result.timedOut === true ||
|
|
99
|
+
result.stopReason === "timeout" ||
|
|
100
|
+
result.stopReason === "error" ||
|
|
101
|
+
result.stopReason === "aborted"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function formatResultFailure(result: SingleResult): string {
|
|
106
|
+
const error = result.errorMessage || result.stderr.trim();
|
|
107
|
+
const output = getResultFinalOutput(result);
|
|
108
|
+
const combined =
|
|
109
|
+
error && output ? `${error}\n\nPartial output:\n${output}` : error || output || "(no output)";
|
|
110
|
+
return truncateUtf8(combined, DEFAULT_MAX_CONTEXT_BYTES).text;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function boundMessageText(
|
|
114
|
+
message: Message,
|
|
115
|
+
maxBytes: number,
|
|
116
|
+
): { message?: Message; bytes: number; truncated: boolean } {
|
|
117
|
+
const originalBytes = Buffer.byteLength(JSON.stringify(message), "utf8");
|
|
118
|
+
if (Number.isSafeInteger(maxBytes) && maxBytes >= 0 && originalBytes <= maxBytes) {
|
|
119
|
+
return { message, bytes: originalBytes, truncated: false };
|
|
120
|
+
}
|
|
121
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) return { bytes: 0, truncated: true };
|
|
122
|
+
|
|
123
|
+
const content: Array<
|
|
124
|
+
| { type: "text"; text: string }
|
|
125
|
+
| { type: "toolCall"; id: string; name: string; arguments: Record<string, unknown> }
|
|
126
|
+
> = [];
|
|
127
|
+
const bounded = () => ({ ...message, content }) as Message;
|
|
128
|
+
const fits = () => Buffer.byteLength(JSON.stringify(bounded()), "utf8") <= maxBytes;
|
|
129
|
+
const addText = (text: string, prepend = false) => {
|
|
130
|
+
if (!text.trim()) return;
|
|
131
|
+
const part = { type: "text" as const, text: "" };
|
|
132
|
+
if (prepend) content.unshift(part);
|
|
133
|
+
else content.push(part);
|
|
134
|
+
if (!fits()) {
|
|
135
|
+
content.splice(content.indexOf(part), 1);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
let low = 0;
|
|
139
|
+
let high = Buffer.byteLength(text, "utf8");
|
|
140
|
+
while (low < high) {
|
|
141
|
+
const middle = Math.ceil((low + high) / 2);
|
|
142
|
+
part.text = truncateUtf8(text, middle).text;
|
|
143
|
+
if (fits()) low = middle;
|
|
144
|
+
else high = middle - 1;
|
|
145
|
+
}
|
|
146
|
+
part.text = truncateUtf8(text, low).text;
|
|
147
|
+
if (!part.text.trim()) content.splice(content.indexOf(part), 1);
|
|
148
|
+
};
|
|
149
|
+
const addToolCall = (part: Extract<Message["content"][number], { type: "toolCall" }>) => {
|
|
150
|
+
const toolCall = {
|
|
151
|
+
type: "toolCall" as const,
|
|
152
|
+
id: part.id,
|
|
153
|
+
name: part.name,
|
|
154
|
+
arguments: part.arguments,
|
|
155
|
+
};
|
|
156
|
+
content.unshift(toolCall);
|
|
157
|
+
if (fits()) return;
|
|
158
|
+
const arguments_: Record<string, unknown> = {};
|
|
159
|
+
for (const key of ["command", "path", "file_path", "pattern", "url"]) {
|
|
160
|
+
const value = part.arguments[key];
|
|
161
|
+
if (typeof value === "string") arguments_[key] = truncateUtf8(value, 256).text;
|
|
162
|
+
}
|
|
163
|
+
toolCall.arguments = arguments_;
|
|
164
|
+
if (fits()) return;
|
|
165
|
+
toolCall.arguments = {};
|
|
166
|
+
if (!fits()) content.shift();
|
|
104
167
|
};
|
|
168
|
+
|
|
169
|
+
if (message.role === "assistant") {
|
|
170
|
+
for (let index = message.content.length - 1; index >= 0; index--) {
|
|
171
|
+
const part = message.content[index];
|
|
172
|
+
if (part.type === "text") addText(part.text, true);
|
|
173
|
+
else if (part.type === "toolCall") addToolCall(part);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
for (const part of message.content) {
|
|
177
|
+
if (typeof part === "object" && part && part.type === "text") addText(part.text);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (content.length === 0) return { bytes: 0, truncated: true };
|
|
182
|
+
const result = bounded();
|
|
183
|
+
const bytes = Buffer.byteLength(JSON.stringify(result), "utf8");
|
|
184
|
+
return { message: result, bytes, truncated: true };
|
|
185
|
+
}
|
|
186
|
+
function compactRecentActivityArguments(args: Record<string, unknown>): Record<string, unknown> {
|
|
187
|
+
if (Buffer.byteLength(JSON.stringify(args), "utf8") <= MAX_RECENT_ACTIVITY_ARGUMENT_BYTES)
|
|
188
|
+
return args;
|
|
189
|
+
const compact: Record<string, unknown> = {};
|
|
190
|
+
for (const key of ["command", "path", "file_path", "pattern", "url", "selector"]) {
|
|
191
|
+
const value = args[key];
|
|
192
|
+
if (typeof value === "string") compact[key] = truncateUtf8(value, 256).text;
|
|
193
|
+
else if (typeof value === "number" || typeof value === "boolean") compact[key] = value;
|
|
194
|
+
}
|
|
195
|
+
return compact;
|
|
105
196
|
}
|
|
106
197
|
|
|
107
|
-
|
|
198
|
+
function appendRecentActivity(result: SingleResult, message: Message): void {
|
|
199
|
+
if (message.role !== "assistant") return;
|
|
200
|
+
const append = (item: RecentActivityItem) => {
|
|
201
|
+
result.recentActivity ??= [];
|
|
202
|
+
result.recentActivityTotal = (result.recentActivityTotal ?? 0) + 1;
|
|
203
|
+
result.recentActivity.push(item);
|
|
204
|
+
if (result.recentActivity.length > MAX_RECENT_ACTIVITY_ITEMS) {
|
|
205
|
+
result.recentActivity.splice(0, result.recentActivity.length - MAX_RECENT_ACTIVITY_ITEMS);
|
|
206
|
+
}
|
|
207
|
+
while (
|
|
208
|
+
Buffer.byteLength(JSON.stringify(result.recentActivity), "utf8") > MAX_RECENT_ACTIVITY_BYTES
|
|
209
|
+
) {
|
|
210
|
+
result.recentActivity.shift();
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
for (const part of message.content) {
|
|
214
|
+
if (part.type === "text") {
|
|
215
|
+
const text = part.text.trim();
|
|
216
|
+
if (text) append({ type: "text", text: truncateUtf8(text, 1024).text });
|
|
217
|
+
} else if (part.type === "toolCall") {
|
|
218
|
+
append({
|
|
219
|
+
type: "toolCall",
|
|
220
|
+
name: part.name,
|
|
221
|
+
args: compactRecentActivityArguments(part.arguments),
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function buildFanInContext(
|
|
228
|
+
results: SingleResult[],
|
|
229
|
+
maxBytes = DEFAULT_MAX_CONTEXT_BYTES,
|
|
230
|
+
): string {
|
|
108
231
|
const text = results
|
|
109
232
|
.map((result, index) => {
|
|
110
|
-
const
|
|
233
|
+
const failed = isResultError(result);
|
|
234
|
+
const status = result.exitCode === -1 ? "running" : failed ? "failed" : "completed";
|
|
111
235
|
const output = getResultFinalOutput(result);
|
|
112
236
|
const error = result.errorMessage || result.stderr.trim();
|
|
237
|
+
const resultText = failed
|
|
238
|
+
? `${error ? "Error" : output ? "Partial output" : "Error"}:\n${formatResultFailure(result)}`
|
|
239
|
+
: output
|
|
240
|
+
? `Output:\n${output}`
|
|
241
|
+
: "Output: (no output)";
|
|
113
242
|
return [
|
|
114
243
|
`## Result ${index + 1}: ${result.agent} (${status})`,
|
|
115
244
|
`Task: ${result.task}`,
|
|
116
|
-
|
|
245
|
+
resultText,
|
|
117
246
|
].join("\n\n");
|
|
118
247
|
})
|
|
119
248
|
.join("\n\n---\n\n");
|
|
@@ -146,7 +275,10 @@ export async function mapWithConcurrencyLimit<TIn, TOut>(
|
|
|
146
275
|
return results;
|
|
147
276
|
}
|
|
148
277
|
|
|
149
|
-
async function writePromptToTempFile(
|
|
278
|
+
async function writePromptToTempFile(
|
|
279
|
+
agentName: string,
|
|
280
|
+
prompt: string,
|
|
281
|
+
): Promise<{ dir: string; filePath: string }> {
|
|
150
282
|
const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "pi-subagent-"));
|
|
151
283
|
const safeName = agentName.replace(/[^\w.-]+/g, "_");
|
|
152
284
|
const filePath = path.join(tmpDir, `prompt-${safeName}.md`);
|
|
@@ -207,8 +339,15 @@ function signalProcess(proc: ReturnType<typeof spawn>, signal: NodeJS.Signals):
|
|
|
207
339
|
}
|
|
208
340
|
}
|
|
209
341
|
|
|
210
|
-
export function terminateProcess(
|
|
211
|
-
|
|
342
|
+
export function terminateProcess(
|
|
343
|
+
proc: ReturnType<typeof spawn>,
|
|
344
|
+
graceMs = KILL_GRACE_MS,
|
|
345
|
+
): () => void {
|
|
346
|
+
const leaderExited = proc.exitCode !== null || proc.signalCode !== null;
|
|
347
|
+
const capturedOutputClosed = [proc.stdout, proc.stderr].every(
|
|
348
|
+
(stream) => !stream || stream.readableEnded || stream.destroyed,
|
|
349
|
+
);
|
|
350
|
+
let closed = leaderExited && capturedOutputClosed;
|
|
212
351
|
const onClose = () => {
|
|
213
352
|
closed = true;
|
|
214
353
|
};
|
|
@@ -251,7 +390,15 @@ export async function runSingleAgent(
|
|
|
251
390
|
exitCode: 1,
|
|
252
391
|
messages: [],
|
|
253
392
|
stderr: `Unknown agent: "${agentName}". Available agents: ${available}.`,
|
|
254
|
-
usage: {
|
|
393
|
+
usage: {
|
|
394
|
+
input: 0,
|
|
395
|
+
output: 0,
|
|
396
|
+
cacheRead: 0,
|
|
397
|
+
cacheWrite: 0,
|
|
398
|
+
cost: 0,
|
|
399
|
+
contextTokens: 0,
|
|
400
|
+
turns: 0,
|
|
401
|
+
},
|
|
255
402
|
thinkingLevel,
|
|
256
403
|
step,
|
|
257
404
|
finalOutput: "",
|
|
@@ -261,6 +408,9 @@ export async function runSingleAgent(
|
|
|
261
408
|
let tmpPromptDir: string | null = null;
|
|
262
409
|
let tmpPromptPath: string | null = null;
|
|
263
410
|
|
|
411
|
+
let latestAssistantOutput = "";
|
|
412
|
+
let terminalAssistantOutput: string | undefined;
|
|
413
|
+
|
|
264
414
|
const currentResult: SingleResult = {
|
|
265
415
|
agent: agentName,
|
|
266
416
|
agentSource: agent.source,
|
|
@@ -268,15 +418,35 @@ export async function runSingleAgent(
|
|
|
268
418
|
exitCode: 0,
|
|
269
419
|
messages: [],
|
|
270
420
|
stderr: "",
|
|
271
|
-
usage: {
|
|
421
|
+
usage: {
|
|
422
|
+
input: 0,
|
|
423
|
+
output: 0,
|
|
424
|
+
cacheRead: 0,
|
|
425
|
+
cacheWrite: 0,
|
|
426
|
+
cost: 0,
|
|
427
|
+
contextTokens: 0,
|
|
428
|
+
turns: 0,
|
|
429
|
+
},
|
|
272
430
|
model: agent.model ?? undefined,
|
|
273
431
|
thinkingLevel,
|
|
274
432
|
step,
|
|
275
433
|
timeoutMs,
|
|
276
434
|
};
|
|
435
|
+
const selectedAssistantOutput = () =>
|
|
436
|
+
terminalAssistantOutput !== undefined
|
|
437
|
+
? terminalAssistantOutput
|
|
438
|
+
: latestAssistantOutput || getFinalOutput(currentResult.messages);
|
|
439
|
+
const setErrorMessage = (message: string) => {
|
|
440
|
+
const bounded = truncateUtf8(message, DEFAULT_MAX_STDERR_BYTES);
|
|
441
|
+
currentResult.errorMessage = bounded.text;
|
|
442
|
+
currentResult.truncated ||= bounded.truncated;
|
|
443
|
+
return bounded.text;
|
|
444
|
+
};
|
|
277
445
|
|
|
278
446
|
const emitUpdate = () => {
|
|
279
|
-
|
|
447
|
+
const latest = truncateUtf8(selectedAssistantOutput(), DEFAULT_MAX_OUTPUT_BYTES);
|
|
448
|
+
currentResult.finalOutput = latest.text;
|
|
449
|
+
currentResult.truncated ||= latest.truncated;
|
|
280
450
|
if (onUpdate) {
|
|
281
451
|
onUpdate({
|
|
282
452
|
content: [{ type: "text", text: currentResult.finalOutput || "(running...)" }],
|
|
@@ -293,8 +463,7 @@ export async function runSingleAgent(
|
|
|
293
463
|
currentResult.exitCode = 1;
|
|
294
464
|
currentResult.stopReason = "error";
|
|
295
465
|
const reason = error instanceof Error ? error.message : String(error);
|
|
296
|
-
currentResult.
|
|
297
|
-
currentResult.stderr = currentResult.errorMessage;
|
|
466
|
+
currentResult.stderr = setErrorMessage(`Invalid subagent cwd: ${effectiveCwd} (${reason})`);
|
|
298
467
|
return currentResult;
|
|
299
468
|
}
|
|
300
469
|
|
|
@@ -302,7 +471,7 @@ export async function runSingleAgent(
|
|
|
302
471
|
currentResult.exitCode = 130;
|
|
303
472
|
currentResult.aborted = true;
|
|
304
473
|
currentResult.stopReason = "aborted";
|
|
305
|
-
|
|
474
|
+
setErrorMessage("Subagent was aborted before start");
|
|
306
475
|
return currentResult;
|
|
307
476
|
}
|
|
308
477
|
|
|
@@ -356,22 +525,37 @@ export async function runSingleAgent(
|
|
|
356
525
|
},
|
|
357
526
|
});
|
|
358
527
|
} catch (error) {
|
|
359
|
-
currentResult.
|
|
360
|
-
|
|
528
|
+
currentResult.stderr = setErrorMessage(
|
|
529
|
+
error instanceof Error ? error.message : String(error),
|
|
530
|
+
);
|
|
361
531
|
finish(1);
|
|
362
532
|
return;
|
|
363
533
|
}
|
|
364
534
|
|
|
365
|
-
let capturedMessageBytes = 0;
|
|
366
535
|
const addMessage = (msg: Message) => {
|
|
367
|
-
|
|
536
|
+
const boundedMessage = boundMessageText(msg, DEFAULT_MAX_OUTPUT_BYTES - 2);
|
|
537
|
+
currentResult.truncated ||= boundedMessage.truncated;
|
|
538
|
+
if (!boundedMessage.message) return;
|
|
539
|
+
while (
|
|
540
|
+
currentResult.messages.length >= DEFAULT_MAX_MESSAGES ||
|
|
541
|
+
Buffer.byteLength(
|
|
542
|
+
JSON.stringify([...currentResult.messages, boundedMessage.message]),
|
|
543
|
+
"utf8",
|
|
544
|
+
) > DEFAULT_MAX_OUTPUT_BYTES
|
|
545
|
+
) {
|
|
546
|
+
const removed = currentResult.messages.shift();
|
|
547
|
+
if (!removed) break;
|
|
548
|
+
currentResult.truncated = true;
|
|
549
|
+
}
|
|
550
|
+
if (
|
|
551
|
+
Buffer.byteLength(
|
|
552
|
+
JSON.stringify([...currentResult.messages, boundedMessage.message]),
|
|
553
|
+
"utf8",
|
|
554
|
+
) > DEFAULT_MAX_OUTPUT_BYTES
|
|
555
|
+
) {
|
|
368
556
|
currentResult.truncated = true;
|
|
369
557
|
return;
|
|
370
558
|
}
|
|
371
|
-
const remaining = Math.max(0, DEFAULT_MAX_OUTPUT_BYTES - capturedMessageBytes);
|
|
372
|
-
const boundedMessage = boundMessageText(msg, remaining);
|
|
373
|
-
capturedMessageBytes += boundedMessage.bytes;
|
|
374
|
-
currentResult.truncated ||= boundedMessage.truncated;
|
|
375
559
|
currentResult.messages.push(boundedMessage.message);
|
|
376
560
|
};
|
|
377
561
|
const processEvent = (raw: unknown) => {
|
|
@@ -379,6 +563,15 @@ export async function runSingleAgent(
|
|
|
379
563
|
const event = raw as { type?: string; message?: Message };
|
|
380
564
|
if (event.type === "message_end" && event.message) {
|
|
381
565
|
const msg = event.message;
|
|
566
|
+
if (msg.role === "assistant") {
|
|
567
|
+
const output = truncateUtf8(getFinalOutput([msg]), DEFAULT_MAX_OUTPUT_BYTES);
|
|
568
|
+
currentResult.truncated ||= output.truncated;
|
|
569
|
+
if (output.text) latestAssistantOutput = output.text;
|
|
570
|
+
if (msg.stopReason === "stop" || msg.stopReason === "length") {
|
|
571
|
+
terminalAssistantOutput = output.text;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
appendRecentActivity(currentResult, msg);
|
|
382
575
|
addMessage(msg);
|
|
383
576
|
if (msg.role === "assistant") {
|
|
384
577
|
currentResult.usage.turns++;
|
|
@@ -391,9 +584,11 @@ export async function runSingleAgent(
|
|
|
391
584
|
currentResult.usage.cost += usage.cost?.total || 0;
|
|
392
585
|
currentResult.usage.contextTokens = usage.totalTokens || 0;
|
|
393
586
|
}
|
|
394
|
-
if (
|
|
587
|
+
if (msg.provider) currentResult.actualProvider = msg.provider;
|
|
588
|
+
if (msg.responseModel ?? msg.model)
|
|
589
|
+
currentResult.actualModel = msg.responseModel ?? msg.model;
|
|
395
590
|
if (msg.stopReason) currentResult.stopReason = msg.stopReason;
|
|
396
|
-
if (msg.errorMessage)
|
|
591
|
+
if (msg.errorMessage) setErrorMessage(msg.errorMessage);
|
|
397
592
|
}
|
|
398
593
|
emitUpdate();
|
|
399
594
|
} else if (event.type === "tool_result_end" && event.message) {
|
|
@@ -415,7 +610,7 @@ export async function runSingleAgent(
|
|
|
415
610
|
timedOut = true;
|
|
416
611
|
currentResult.timedOut = true;
|
|
417
612
|
currentResult.stopReason = "timeout";
|
|
418
|
-
|
|
613
|
+
setErrorMessage(`Subagent timed out after ${timeoutMs}ms`);
|
|
419
614
|
const bounded = appendBounded(
|
|
420
615
|
currentResult.stderr,
|
|
421
616
|
`\nSubagent timed out after ${timeoutMs}ms.`,
|
|
@@ -430,7 +625,11 @@ export async function runSingleAgent(
|
|
|
430
625
|
|
|
431
626
|
proc.stdout?.on("data", (data) => decoder.push(data));
|
|
432
627
|
proc.stderr?.on("data", (data) => {
|
|
433
|
-
const bounded = appendBounded(
|
|
628
|
+
const bounded = appendBounded(
|
|
629
|
+
currentResult.stderr,
|
|
630
|
+
data.toString(),
|
|
631
|
+
DEFAULT_MAX_STDERR_BYTES,
|
|
632
|
+
);
|
|
434
633
|
currentResult.stderr = bounded.text;
|
|
435
634
|
currentResult.truncated ||= bounded.truncated;
|
|
436
635
|
});
|
|
@@ -439,10 +638,10 @@ export async function runSingleAgent(
|
|
|
439
638
|
finish(timedOut ? 124 : wasAborted ? 130 : (code ?? 0));
|
|
440
639
|
});
|
|
441
640
|
proc.on("error", (error) => {
|
|
442
|
-
|
|
641
|
+
const message = setErrorMessage(error.message);
|
|
443
642
|
const bounded = appendBounded(
|
|
444
643
|
currentResult.stderr,
|
|
445
|
-
`${currentResult.stderr ? "\n" : ""}${
|
|
644
|
+
`${currentResult.stderr ? "\n" : ""}${message}`,
|
|
446
645
|
DEFAULT_MAX_STDERR_BYTES,
|
|
447
646
|
);
|
|
448
647
|
currentResult.stderr = bounded.text;
|
|
@@ -456,7 +655,7 @@ export async function runSingleAgent(
|
|
|
456
655
|
wasAborted = true;
|
|
457
656
|
currentResult.aborted = true;
|
|
458
657
|
currentResult.stopReason = "aborted";
|
|
459
|
-
|
|
658
|
+
setErrorMessage("Subagent was aborted");
|
|
460
659
|
cleanupTermination = terminateProcess(proc);
|
|
461
660
|
};
|
|
462
661
|
if (signal.aborted) abortHandler();
|
|
@@ -465,9 +664,18 @@ export async function runSingleAgent(
|
|
|
465
664
|
});
|
|
466
665
|
|
|
467
666
|
currentResult.exitCode = exitCode;
|
|
468
|
-
const final = truncateUtf8(
|
|
667
|
+
const final = truncateUtf8(selectedAssistantOutput(), DEFAULT_MAX_OUTPUT_BYTES);
|
|
469
668
|
currentResult.finalOutput = final.text;
|
|
470
669
|
currentResult.truncated ||= final.truncated;
|
|
670
|
+
if (
|
|
671
|
+
currentResult.exitCode === 0 &&
|
|
672
|
+
currentResult.stopReason !== "error" &&
|
|
673
|
+
(currentResult.stopReason === "toolUse" || !currentResult.finalOutput.trim())
|
|
674
|
+
) {
|
|
675
|
+
currentResult.exitCode = 1;
|
|
676
|
+
currentResult.stopReason = "error";
|
|
677
|
+
setErrorMessage("Subagent completed without final text");
|
|
678
|
+
}
|
|
471
679
|
currentResult.policy = {
|
|
472
680
|
inherited: ["environment"],
|
|
473
681
|
overridden: [
|