@narumitw/pi-subagents 0.15.0 → 0.16.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 +25 -26
- package/src/render.ts +21 -12
- package/src/runner.ts +71 -17
package/package.json
CHANGED
package/src/execution.ts
CHANGED
|
@@ -6,17 +6,19 @@ import {
|
|
|
6
6
|
type AgentScope,
|
|
7
7
|
type SubagentThinkingLevel,
|
|
8
8
|
} from "./agents.js";
|
|
9
|
+
import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
|
|
10
|
+
import type { SubagentParams } from "./params.js";
|
|
9
11
|
import {
|
|
10
12
|
buildFanInContext,
|
|
13
|
+
formatResultFailure,
|
|
11
14
|
getResultFinalOutput,
|
|
15
|
+
isResultError,
|
|
12
16
|
mapWithConcurrencyLimit,
|
|
13
|
-
runSingleAgent,
|
|
14
17
|
type OnUpdateCallback,
|
|
18
|
+
runSingleAgent,
|
|
15
19
|
type SingleResult,
|
|
16
20
|
type SubagentDetails,
|
|
17
21
|
} from "./runner.js";
|
|
18
|
-
import { DEFAULT_MAX_CONTEXT_BYTES, truncateUtf8 } from "./limits.js";
|
|
19
|
-
import type { SubagentParams } from "./params.js";
|
|
20
22
|
import { readSubagentSettings, resolveSubagentThinkingLevel } from "./settings.js";
|
|
21
23
|
|
|
22
24
|
const MAX_PARALLEL_TASKS = 8;
|
|
@@ -225,10 +227,9 @@ export async function executeSubagent(
|
|
|
225
227
|
);
|
|
226
228
|
results.push(result);
|
|
227
229
|
|
|
228
|
-
const isError =
|
|
229
|
-
result.exitCode !== 0 || result.stopReason === "error" || result.stopReason === "aborted";
|
|
230
|
+
const isError = isResultError(result);
|
|
230
231
|
if (isError) {
|
|
231
|
-
const errorMsg =
|
|
232
|
+
const errorMsg = formatResultFailure(result);
|
|
232
233
|
return {
|
|
233
234
|
content: [{ type: "text", text: `Chain stopped at step ${i + 1} (${step.agent}): ${errorMsg}` }],
|
|
234
235
|
details: { ...makeDetails("chain")(results), isError: true },
|
|
@@ -372,14 +373,16 @@ export async function executeSubagent(
|
|
|
372
373
|
);
|
|
373
374
|
}
|
|
374
375
|
|
|
375
|
-
const successCount = results.filter((
|
|
376
|
-
const summaries = results.map((
|
|
377
|
-
const
|
|
378
|
-
const
|
|
379
|
-
const
|
|
380
|
-
const
|
|
381
|
-
|
|
376
|
+
const successCount = results.filter((result) => !isResultError(result)).length;
|
|
377
|
+
const summaries = results.map((result) => {
|
|
378
|
+
const failed = isResultError(result);
|
|
379
|
+
const output = getResultFinalOutput(result);
|
|
380
|
+
const error = result.errorMessage || result.stderr.trim();
|
|
381
|
+
const summaryText = failed ? formatResultFailure(result) : output || error;
|
|
382
|
+
const preview = truncateUtf8(summaryText, 160).text;
|
|
383
|
+
return `[${result.agent}] ${failed ? "failed" : "completed"}: ${preview || "(no output)"}`;
|
|
382
384
|
});
|
|
385
|
+
const aggregatorFailed = aggregatorResult ? isResultError(aggregatorResult) : false;
|
|
383
386
|
const aggregatorOutput = aggregatorResult ? getResultFinalOutput(aggregatorResult) : "";
|
|
384
387
|
const aggregatorError = aggregatorResult?.errorMessage || aggregatorResult?.stderr.trim() || "";
|
|
385
388
|
return {
|
|
@@ -387,23 +390,19 @@ export async function executeSubagent(
|
|
|
387
390
|
{
|
|
388
391
|
type: "text",
|
|
389
392
|
text: aggregatorResult
|
|
390
|
-
?
|
|
393
|
+
? aggregatorFailed
|
|
394
|
+
? formatResultFailure(aggregatorResult)
|
|
395
|
+
: aggregatorOutput ||
|
|
396
|
+
aggregatorError ||
|
|
397
|
+
`(aggregator ${aggregatorResult.agent} produced no output)`
|
|
391
398
|
: `Parallel: ${successCount}/${results.length} succeeded\n\n${summaries.join("\n\n")}`,
|
|
392
399
|
},
|
|
393
400
|
],
|
|
394
401
|
details: {
|
|
395
402
|
...makeDetails("parallel")(results, aggregatorResult),
|
|
396
|
-
isError:
|
|
397
|
-
? aggregatorResult.exitCode !== 0 ||
|
|
398
|
-
aggregatorResult.stopReason === "error" ||
|
|
399
|
-
aggregatorResult.stopReason === "aborted"
|
|
400
|
-
: false,
|
|
403
|
+
isError: aggregatorFailed,
|
|
401
404
|
},
|
|
402
|
-
isError: aggregatorResult
|
|
403
|
-
? aggregatorResult.exitCode !== 0 ||
|
|
404
|
-
aggregatorResult.stopReason === "error" ||
|
|
405
|
-
aggregatorResult.stopReason === "aborted"
|
|
406
|
-
: undefined,
|
|
405
|
+
isError: aggregatorResult ? aggregatorFailed : undefined,
|
|
407
406
|
};
|
|
408
407
|
} finally {
|
|
409
408
|
status.clear();
|
|
@@ -427,9 +426,9 @@ export async function executeSubagent(
|
|
|
427
426
|
onUpdate,
|
|
428
427
|
makeDetails("single"),
|
|
429
428
|
);
|
|
430
|
-
const isError = result
|
|
429
|
+
const isError = isResultError(result);
|
|
431
430
|
if (isError) {
|
|
432
|
-
const errorMsg =
|
|
431
|
+
const errorMsg = formatResultFailure(result);
|
|
433
432
|
return {
|
|
434
433
|
content: [{ type: "text", text: `Agent ${result.stopReason || "failed"}: ${errorMsg}` }],
|
|
435
434
|
details: { ...makeDetails("single")([result]), isError: true },
|
package/src/render.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { AgentScope, SubagentThinkingLevel } from "./agents.js";
|
|
|
12
12
|
import type { SubagentParams } from "./params.js";
|
|
13
13
|
import {
|
|
14
14
|
getResultFinalOutput,
|
|
15
|
+
isResultError,
|
|
15
16
|
type SingleResult,
|
|
16
17
|
type SubagentDetails,
|
|
17
18
|
} from "./runner.js";
|
|
@@ -221,7 +222,7 @@ export function renderSubagentResult(
|
|
|
221
222
|
|
|
222
223
|
if (details.mode === "single" && details.results.length === 1) {
|
|
223
224
|
const r = details.results[0];
|
|
224
|
-
const isError = r
|
|
225
|
+
const isError = isResultError(r);
|
|
225
226
|
const icon = isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
226
227
|
const displayItems = getDisplayItems(r.messages);
|
|
227
228
|
const finalOutput = getResultFinalOutput(r);
|
|
@@ -291,7 +292,7 @@ export function renderSubagentResult(
|
|
|
291
292
|
};
|
|
292
293
|
|
|
293
294
|
if (details.mode === "chain") {
|
|
294
|
-
const successCount = details.results.filter((
|
|
295
|
+
const successCount = details.results.filter((result) => !isResultError(result)).length;
|
|
295
296
|
const icon = successCount === details.results.length ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
296
297
|
|
|
297
298
|
if (expanded) {
|
|
@@ -308,7 +309,7 @@ export function renderSubagentResult(
|
|
|
308
309
|
);
|
|
309
310
|
|
|
310
311
|
for (const r of details.results) {
|
|
311
|
-
const rIcon = r
|
|
312
|
+
const rIcon = !isResultError(r) ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
312
313
|
const displayItems = getDisplayItems(r.messages);
|
|
313
314
|
const finalOutput = getResultFinalOutput(r);
|
|
314
315
|
|
|
@@ -360,7 +361,7 @@ export function renderSubagentResult(
|
|
|
360
361
|
theme.fg("toolTitle", theme.bold("chain ")) +
|
|
361
362
|
theme.fg("accent", `${successCount}/${details.results.length} steps`);
|
|
362
363
|
for (const r of details.results) {
|
|
363
|
-
const rIcon = r
|
|
364
|
+
const rIcon = !isResultError(r) ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
364
365
|
const displayItems = getDisplayItems(r.messages);
|
|
365
366
|
text += `\n\n${theme.fg("muted", `─── Step ${r.step}: `)}${theme.fg("accent", r.agent)} ${rIcon}`;
|
|
366
367
|
if (displayItems.length === 0) text += `\n${theme.fg("muted", "(no output)")}`;
|
|
@@ -373,12 +374,18 @@ export function renderSubagentResult(
|
|
|
373
374
|
}
|
|
374
375
|
|
|
375
376
|
if (details.mode === "parallel") {
|
|
376
|
-
const running = details.results.filter((
|
|
377
|
-
const successCount = details.results.filter(
|
|
378
|
-
|
|
377
|
+
const running = details.results.filter((result) => result.exitCode === -1).length;
|
|
378
|
+
const successCount = details.results.filter(
|
|
379
|
+
(result) => result.exitCode !== -1 && !isResultError(result),
|
|
380
|
+
).length;
|
|
381
|
+
const failCount = details.results.filter(
|
|
382
|
+
(result) => result.exitCode !== -1 && isResultError(result),
|
|
383
|
+
).length;
|
|
379
384
|
const aggregator = details.aggregator;
|
|
380
385
|
const aggregatorRunning = aggregator?.exitCode === -1;
|
|
381
|
-
const aggregatorFailed = aggregator
|
|
386
|
+
const aggregatorFailed = aggregator
|
|
387
|
+
? aggregator.exitCode !== -1 && isResultError(aggregator)
|
|
388
|
+
: false;
|
|
382
389
|
const isRunning = running > 0 || aggregatorRunning;
|
|
383
390
|
const icon = isRunning
|
|
384
391
|
? theme.fg("warning", "⏳")
|
|
@@ -404,7 +411,7 @@ export function renderSubagentResult(
|
|
|
404
411
|
);
|
|
405
412
|
|
|
406
413
|
for (const r of details.results) {
|
|
407
|
-
const rIcon = r
|
|
414
|
+
const rIcon = !isResultError(r) ? theme.fg("success", "✓") : theme.fg("error", "✗");
|
|
408
415
|
const displayItems = getDisplayItems(r.messages);
|
|
409
416
|
const finalOutput = getResultFinalOutput(r);
|
|
410
417
|
|
|
@@ -438,7 +445,9 @@ export function renderSubagentResult(
|
|
|
438
445
|
}
|
|
439
446
|
|
|
440
447
|
if (aggregator) {
|
|
441
|
-
const rIcon = aggregator
|
|
448
|
+
const rIcon = !isResultError(aggregator)
|
|
449
|
+
? theme.fg("success", "✓")
|
|
450
|
+
: theme.fg("error", "✗");
|
|
442
451
|
const displayItems = getDisplayItems(aggregator.messages);
|
|
443
452
|
const finalOutput = getResultFinalOutput(aggregator);
|
|
444
453
|
|
|
@@ -485,7 +494,7 @@ export function renderSubagentResult(
|
|
|
485
494
|
const rIcon =
|
|
486
495
|
r.exitCode === -1
|
|
487
496
|
? theme.fg("warning", "⏳")
|
|
488
|
-
: r
|
|
497
|
+
: !isResultError(r)
|
|
489
498
|
? theme.fg("success", "✓")
|
|
490
499
|
: theme.fg("error", "✗");
|
|
491
500
|
const displayItems = getDisplayItems(r.messages);
|
|
@@ -498,7 +507,7 @@ export function renderSubagentResult(
|
|
|
498
507
|
const rIcon =
|
|
499
508
|
aggregator.exitCode === -1
|
|
500
509
|
? theme.fg("warning", "⏳")
|
|
501
|
-
: aggregator
|
|
510
|
+
: !isResultError(aggregator)
|
|
502
511
|
? theme.fg("success", "✓")
|
|
503
512
|
: theme.fg("error", "✗");
|
|
504
513
|
const displayItems = getDisplayItems(aggregator.messages);
|
package/src/runner.ts
CHANGED
|
@@ -72,9 +72,11 @@ function getFinalOutput(messages: Message[]): string {
|
|
|
72
72
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
73
73
|
const msg = messages[i];
|
|
74
74
|
if (msg.role === "assistant") {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const text = msg.content
|
|
76
|
+
.filter((part) => part.type === "text")
|
|
77
|
+
.map((part) => part.text)
|
|
78
|
+
.join("\n");
|
|
79
|
+
if (text) return text;
|
|
78
80
|
}
|
|
79
81
|
}
|
|
80
82
|
return "";
|
|
@@ -84,6 +86,22 @@ export function getResultFinalOutput(result: SingleResult): string {
|
|
|
84
86
|
return result.finalOutput ?? getFinalOutput(result.messages);
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
export function isResultError(result: SingleResult): boolean {
|
|
90
|
+
return (
|
|
91
|
+
(result.exitCode !== 0 && result.exitCode !== -1) ||
|
|
92
|
+
result.stopReason === "error" ||
|
|
93
|
+
result.stopReason === "aborted"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function formatResultFailure(result: SingleResult): string {
|
|
98
|
+
const error = result.errorMessage || result.stderr.trim();
|
|
99
|
+
const output = getResultFinalOutput(result);
|
|
100
|
+
const combined =
|
|
101
|
+
error && output ? `${error}\n\nPartial output:\n${output}` : error || output || "(no output)";
|
|
102
|
+
return truncateUtf8(combined, DEFAULT_MAX_CONTEXT_BYTES).text;
|
|
103
|
+
}
|
|
104
|
+
|
|
87
105
|
function boundMessageText(message: Message, maxBytes: number): { message: Message; bytes: number; truncated: boolean } {
|
|
88
106
|
const serializedBytes = Buffer.byteLength(JSON.stringify(message), "utf8");
|
|
89
107
|
if (serializedBytes <= maxBytes) return { message, bytes: serializedBytes, truncated: false };
|
|
@@ -107,13 +125,19 @@ function boundMessageText(message: Message, maxBytes: number): { message: Messag
|
|
|
107
125
|
export function buildFanInContext(results: SingleResult[], maxBytes = DEFAULT_MAX_CONTEXT_BYTES): string {
|
|
108
126
|
const text = results
|
|
109
127
|
.map((result, index) => {
|
|
110
|
-
const
|
|
128
|
+
const failed = isResultError(result);
|
|
129
|
+
const status = result.exitCode === -1 ? "running" : failed ? "failed" : "completed";
|
|
111
130
|
const output = getResultFinalOutput(result);
|
|
112
131
|
const error = result.errorMessage || result.stderr.trim();
|
|
132
|
+
const resultText = failed
|
|
133
|
+
? `${error ? "Error" : output ? "Partial output" : "Error"}:\n${formatResultFailure(result)}`
|
|
134
|
+
: output
|
|
135
|
+
? `Output:\n${output}`
|
|
136
|
+
: "Output: (no output)";
|
|
113
137
|
return [
|
|
114
138
|
`## Result ${index + 1}: ${result.agent} (${status})`,
|
|
115
139
|
`Task: ${result.task}`,
|
|
116
|
-
|
|
140
|
+
resultText,
|
|
117
141
|
].join("\n\n");
|
|
118
142
|
})
|
|
119
143
|
.join("\n\n---\n\n");
|
|
@@ -261,6 +285,9 @@ export async function runSingleAgent(
|
|
|
261
285
|
let tmpPromptDir: string | null = null;
|
|
262
286
|
let tmpPromptPath: string | null = null;
|
|
263
287
|
|
|
288
|
+
let latestAssistantOutput = "";
|
|
289
|
+
let terminalAssistantOutput: string | undefined;
|
|
290
|
+
|
|
264
291
|
const currentResult: SingleResult = {
|
|
265
292
|
agent: agentName,
|
|
266
293
|
agentSource: agent.source,
|
|
@@ -274,9 +301,21 @@ export async function runSingleAgent(
|
|
|
274
301
|
step,
|
|
275
302
|
timeoutMs,
|
|
276
303
|
};
|
|
304
|
+
const selectedAssistantOutput = () =>
|
|
305
|
+
terminalAssistantOutput !== undefined
|
|
306
|
+
? terminalAssistantOutput
|
|
307
|
+
: latestAssistantOutput || getFinalOutput(currentResult.messages);
|
|
308
|
+
const setErrorMessage = (message: string) => {
|
|
309
|
+
const bounded = truncateUtf8(message, DEFAULT_MAX_STDERR_BYTES);
|
|
310
|
+
currentResult.errorMessage = bounded.text;
|
|
311
|
+
currentResult.truncated ||= bounded.truncated;
|
|
312
|
+
return bounded.text;
|
|
313
|
+
};
|
|
277
314
|
|
|
278
315
|
const emitUpdate = () => {
|
|
279
|
-
|
|
316
|
+
const latest = truncateUtf8(selectedAssistantOutput(), DEFAULT_MAX_OUTPUT_BYTES);
|
|
317
|
+
currentResult.finalOutput = latest.text;
|
|
318
|
+
currentResult.truncated ||= latest.truncated;
|
|
280
319
|
if (onUpdate) {
|
|
281
320
|
onUpdate({
|
|
282
321
|
content: [{ type: "text", text: currentResult.finalOutput || "(running...)" }],
|
|
@@ -293,8 +332,7 @@ export async function runSingleAgent(
|
|
|
293
332
|
currentResult.exitCode = 1;
|
|
294
333
|
currentResult.stopReason = "error";
|
|
295
334
|
const reason = error instanceof Error ? error.message : String(error);
|
|
296
|
-
currentResult.
|
|
297
|
-
currentResult.stderr = currentResult.errorMessage;
|
|
335
|
+
currentResult.stderr = setErrorMessage(`Invalid subagent cwd: ${effectiveCwd} (${reason})`);
|
|
298
336
|
return currentResult;
|
|
299
337
|
}
|
|
300
338
|
|
|
@@ -302,7 +340,7 @@ export async function runSingleAgent(
|
|
|
302
340
|
currentResult.exitCode = 130;
|
|
303
341
|
currentResult.aborted = true;
|
|
304
342
|
currentResult.stopReason = "aborted";
|
|
305
|
-
|
|
343
|
+
setErrorMessage("Subagent was aborted before start");
|
|
306
344
|
return currentResult;
|
|
307
345
|
}
|
|
308
346
|
|
|
@@ -356,8 +394,7 @@ export async function runSingleAgent(
|
|
|
356
394
|
},
|
|
357
395
|
});
|
|
358
396
|
} catch (error) {
|
|
359
|
-
currentResult.
|
|
360
|
-
currentResult.stderr = currentResult.errorMessage;
|
|
397
|
+
currentResult.stderr = setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
361
398
|
finish(1);
|
|
362
399
|
return;
|
|
363
400
|
}
|
|
@@ -379,6 +416,14 @@ export async function runSingleAgent(
|
|
|
379
416
|
const event = raw as { type?: string; message?: Message };
|
|
380
417
|
if (event.type === "message_end" && event.message) {
|
|
381
418
|
const msg = event.message;
|
|
419
|
+
if (msg.role === "assistant") {
|
|
420
|
+
const output = truncateUtf8(getFinalOutput([msg]), DEFAULT_MAX_OUTPUT_BYTES);
|
|
421
|
+
currentResult.truncated ||= output.truncated;
|
|
422
|
+
if (output.text) latestAssistantOutput = output.text;
|
|
423
|
+
if (msg.stopReason === "stop" || msg.stopReason === "length") {
|
|
424
|
+
terminalAssistantOutput = output.text;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
382
427
|
addMessage(msg);
|
|
383
428
|
if (msg.role === "assistant") {
|
|
384
429
|
currentResult.usage.turns++;
|
|
@@ -393,7 +438,7 @@ export async function runSingleAgent(
|
|
|
393
438
|
}
|
|
394
439
|
if (!currentResult.model && msg.model) currentResult.model = msg.model;
|
|
395
440
|
if (msg.stopReason) currentResult.stopReason = msg.stopReason;
|
|
396
|
-
if (msg.errorMessage)
|
|
441
|
+
if (msg.errorMessage) setErrorMessage(msg.errorMessage);
|
|
397
442
|
}
|
|
398
443
|
emitUpdate();
|
|
399
444
|
} else if (event.type === "tool_result_end" && event.message) {
|
|
@@ -415,7 +460,7 @@ export async function runSingleAgent(
|
|
|
415
460
|
timedOut = true;
|
|
416
461
|
currentResult.timedOut = true;
|
|
417
462
|
currentResult.stopReason = "timeout";
|
|
418
|
-
|
|
463
|
+
setErrorMessage(`Subagent timed out after ${timeoutMs}ms`);
|
|
419
464
|
const bounded = appendBounded(
|
|
420
465
|
currentResult.stderr,
|
|
421
466
|
`\nSubagent timed out after ${timeoutMs}ms.`,
|
|
@@ -439,10 +484,10 @@ export async function runSingleAgent(
|
|
|
439
484
|
finish(timedOut ? 124 : wasAborted ? 130 : (code ?? 0));
|
|
440
485
|
});
|
|
441
486
|
proc.on("error", (error) => {
|
|
442
|
-
|
|
487
|
+
const message = setErrorMessage(error.message);
|
|
443
488
|
const bounded = appendBounded(
|
|
444
489
|
currentResult.stderr,
|
|
445
|
-
`${currentResult.stderr ? "\n" : ""}${
|
|
490
|
+
`${currentResult.stderr ? "\n" : ""}${message}`,
|
|
446
491
|
DEFAULT_MAX_STDERR_BYTES,
|
|
447
492
|
);
|
|
448
493
|
currentResult.stderr = bounded.text;
|
|
@@ -456,7 +501,7 @@ export async function runSingleAgent(
|
|
|
456
501
|
wasAborted = true;
|
|
457
502
|
currentResult.aborted = true;
|
|
458
503
|
currentResult.stopReason = "aborted";
|
|
459
|
-
|
|
504
|
+
setErrorMessage("Subagent was aborted");
|
|
460
505
|
cleanupTermination = terminateProcess(proc);
|
|
461
506
|
};
|
|
462
507
|
if (signal.aborted) abortHandler();
|
|
@@ -465,9 +510,18 @@ export async function runSingleAgent(
|
|
|
465
510
|
});
|
|
466
511
|
|
|
467
512
|
currentResult.exitCode = exitCode;
|
|
468
|
-
const final = truncateUtf8(
|
|
513
|
+
const final = truncateUtf8(selectedAssistantOutput(), DEFAULT_MAX_OUTPUT_BYTES);
|
|
469
514
|
currentResult.finalOutput = final.text;
|
|
470
515
|
currentResult.truncated ||= final.truncated;
|
|
516
|
+
if (
|
|
517
|
+
currentResult.exitCode === 0 &&
|
|
518
|
+
currentResult.stopReason !== "error" &&
|
|
519
|
+
(currentResult.stopReason === "toolUse" || !currentResult.finalOutput.trim())
|
|
520
|
+
) {
|
|
521
|
+
currentResult.exitCode = 1;
|
|
522
|
+
currentResult.stopReason = "error";
|
|
523
|
+
setErrorMessage("Subagent completed without final text");
|
|
524
|
+
}
|
|
471
525
|
currentResult.policy = {
|
|
472
526
|
inherited: ["environment"],
|
|
473
527
|
overridden: [
|