@johnnywu/pi-subagents 2.2.1 → 2.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/CHANGELOG.md +7 -0
- package/extensions/subagent-executor.ts +51 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [2.2.2](https://github.com/jwu/pi-subagents/compare/v2.2.1...v2.2.2) (2026-09-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* surface subagent assistant-turn failures ([b9a3013](https://github.com/jwu/pi-subagents/commit/b9a3013ef9577fea1f634d9fda7b82c4ad09074d))
|
|
7
|
+
|
|
1
8
|
## [2.2.1](https://github.com/jwu/pi-subagents/compare/v2.2.0...v2.2.1) (2026-08-24)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -223,14 +223,27 @@ function textFromMessage(message: unknown): string | undefined {
|
|
|
223
223
|
const content = (message as { content?: unknown }).content;
|
|
224
224
|
if (!Array.isArray(content)) return undefined;
|
|
225
225
|
|
|
226
|
+
const texts: string[] = [];
|
|
226
227
|
for (const part of content) {
|
|
227
228
|
if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'text') {
|
|
228
229
|
const text = (part as { text?: unknown }).text;
|
|
229
|
-
if (typeof text === 'string')
|
|
230
|
+
if (typeof text === 'string') texts.push(text);
|
|
230
231
|
}
|
|
231
232
|
}
|
|
232
233
|
|
|
233
|
-
return undefined;
|
|
234
|
+
return texts.length > 0 ? texts.join('\n') : undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function isAssistantMessage(message: unknown): boolean {
|
|
238
|
+
return (
|
|
239
|
+
!!message && typeof message === 'object' && (message as { role?: unknown }).role === 'assistant'
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function stringMessageProperty(message: unknown, property: 'stopReason' | 'errorMessage') {
|
|
244
|
+
if (!message || typeof message !== 'object') return undefined;
|
|
245
|
+
const value = (message as Record<string, unknown>)[property];
|
|
246
|
+
return typeof value === 'string' ? value : undefined;
|
|
234
247
|
}
|
|
235
248
|
|
|
236
249
|
type ContextWindowLookup = {
|
|
@@ -322,18 +335,18 @@ function usageFromMessages(
|
|
|
322
335
|
return sawUsage ? aggregate : undefined;
|
|
323
336
|
}
|
|
324
337
|
|
|
325
|
-
function
|
|
338
|
+
function lastAssistantMessage(messages: unknown): unknown | undefined {
|
|
326
339
|
if (!Array.isArray(messages)) return undefined;
|
|
327
340
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
328
|
-
|
|
329
|
-
if (!message || typeof message !== 'object') continue;
|
|
330
|
-
if ((message as { role?: unknown }).role !== 'assistant') continue;
|
|
331
|
-
const model = modelFromMessage(message);
|
|
332
|
-
if (model) return model;
|
|
341
|
+
if (isAssistantMessage(messages[index])) return messages[index];
|
|
333
342
|
}
|
|
334
343
|
return undefined;
|
|
335
344
|
}
|
|
336
345
|
|
|
346
|
+
function lastAssistantModel(messages: unknown): string | undefined {
|
|
347
|
+
return modelFromMessage(lastAssistantMessage(messages));
|
|
348
|
+
}
|
|
349
|
+
|
|
337
350
|
function buildTaskArgument(task: string, taskFilePath: string | undefined): string {
|
|
338
351
|
return taskFilePath ? `Task: @${taskFilePath}` : `Task: ${task}`;
|
|
339
352
|
}
|
|
@@ -453,6 +466,8 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
453
466
|
let stderr = '';
|
|
454
467
|
let model = options.agent.model;
|
|
455
468
|
let stdoutBuffer = '';
|
|
469
|
+
let finalAssistantStopReason: string | undefined;
|
|
470
|
+
let finalAssistantErrorMessage: string | undefined;
|
|
456
471
|
|
|
457
472
|
const progress = (status: AgentProgress['status']): AgentProgress => ({
|
|
458
473
|
agent: options.agent.name,
|
|
@@ -469,6 +484,13 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
469
484
|
const emit = (status: AgentProgress['status'] = 'running') =>
|
|
470
485
|
options.onProgress?.(progress(status));
|
|
471
486
|
|
|
487
|
+
const setFinalAssistantOutput = (message: unknown) => {
|
|
488
|
+
// 最后一条 assistant 消息才是权威的最终输出,不能让工具结果占位符或旧文本伪装为结果。
|
|
489
|
+
output = textFromMessage(message) ?? '';
|
|
490
|
+
finalAssistantStopReason = stringMessageProperty(message, 'stopReason');
|
|
491
|
+
finalAssistantErrorMessage = stringMessageProperty(message, 'errorMessage');
|
|
492
|
+
};
|
|
493
|
+
|
|
472
494
|
try {
|
|
473
495
|
const promptFilePath = path.join(tempDir, 'system-prompt.md');
|
|
474
496
|
|
|
@@ -578,8 +600,9 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
578
600
|
}
|
|
579
601
|
|
|
580
602
|
if (event.type === 'message_end' && event.message) {
|
|
581
|
-
|
|
582
|
-
|
|
603
|
+
if (!isAssistantMessage(event.message)) return;
|
|
604
|
+
|
|
605
|
+
setFinalAssistantOutput(event.message);
|
|
583
606
|
updateUsage(usage, usageFromMessage(event.message, modelRegistry));
|
|
584
607
|
model = modelFromMessage(event.message) ?? model;
|
|
585
608
|
emit();
|
|
@@ -589,6 +612,8 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
589
612
|
if (event.type === 'agent_end') {
|
|
590
613
|
const aggregate = usageFromMessages(event.messages, modelRegistry);
|
|
591
614
|
if (aggregate) replaceUsage(usage, aggregate);
|
|
615
|
+
const finalAssistantMessage = lastAssistantMessage(event.messages);
|
|
616
|
+
if (finalAssistantMessage) setFinalAssistantOutput(finalAssistantMessage);
|
|
592
617
|
model = lastAssistantModel(event.messages) ?? model;
|
|
593
618
|
emit();
|
|
594
619
|
}
|
|
@@ -616,8 +641,22 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
616
641
|
);
|
|
617
642
|
|
|
618
643
|
if (stdoutBuffer.trim()) processLine(stdoutBuffer);
|
|
619
|
-
const
|
|
620
|
-
|
|
644
|
+
const failedStopReason =
|
|
645
|
+
finalAssistantStopReason === 'error' || finalAssistantStopReason === 'aborted';
|
|
646
|
+
const hasFinalOutput = output.trim().length > 0;
|
|
647
|
+
const isError = exit.exitCode !== 0 || failedStopReason || !hasFinalOutput;
|
|
648
|
+
|
|
649
|
+
if (failedStopReason && finalAssistantErrorMessage) {
|
|
650
|
+
output = finalAssistantErrorMessage;
|
|
651
|
+
} else if (isError && !hasFinalOutput) {
|
|
652
|
+
output =
|
|
653
|
+
stderr ||
|
|
654
|
+
(failedStopReason
|
|
655
|
+
? `Subagent stopped with reason: ${finalAssistantStopReason}`
|
|
656
|
+
: exit.exitCode !== 0
|
|
657
|
+
? `Subagent exited with code ${exit.exitCode}`
|
|
658
|
+
: 'Subagent produced no final text output.');
|
|
659
|
+
}
|
|
621
660
|
|
|
622
661
|
const truncated = truncateHeadContent(output, OUTPUT_MAX_BYTES, OUTPUT_MAX_LINES);
|
|
623
662
|
if (truncated !== undefined) {
|