@johnnywu/pi-subagents 2.2.0 → 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 +14 -0
- package/extensions/subagent-executor.ts +85 -22
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
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
|
+
|
|
8
|
+
## [2.2.1](https://github.com/jwu/pi-subagents/compare/v2.2.0...v2.2.1) (2026-08-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* support bundled pi CLI invocation ([0651956](https://github.com/jwu/pi-subagents/commit/065195604d1d3b54ea2810f10d20049c4023daf9))
|
|
14
|
+
|
|
1
15
|
# [2.2.0](https://github.com/jwu/pi-subagents/compare/v2.1.1...v2.2.0) (2026-08-06)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import * as fs from 'node:fs/promises';
|
|
8
8
|
import * as os from 'node:os';
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
9
10
|
import * as path from 'node:path';
|
|
10
|
-
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import type { AgentConfig } from './agent-loader.ts';
|
|
12
12
|
import { AUTO_RUNTIME_TOOLS_MARKER } from './subagent-prompt.ts';
|
|
13
13
|
import { resolveSkills } from './skill-resolver.ts';
|
|
@@ -59,7 +59,13 @@ export interface AgentResult extends AgentProgress {
|
|
|
59
59
|
|
|
60
60
|
export interface PiResolution {
|
|
61
61
|
command: string;
|
|
62
|
-
entryPoint: string;
|
|
62
|
+
entryPoint: string | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface PiRuntime {
|
|
66
|
+
currentScript?: string;
|
|
67
|
+
execPath: string;
|
|
68
|
+
fileExists(filePath: string): boolean;
|
|
63
69
|
}
|
|
64
70
|
|
|
65
71
|
export interface ProcessInvocation {
|
|
@@ -171,14 +177,25 @@ export function subagentSessionDir(
|
|
|
171
177
|
return path.join(agentDir, 'sessions', safeProject, 'subagents');
|
|
172
178
|
}
|
|
173
179
|
|
|
174
|
-
export function resolvePiEntryPoint(
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
export function resolvePiEntryPoint(
|
|
181
|
+
runtime: PiRuntime = {
|
|
182
|
+
currentScript: process.argv[1],
|
|
183
|
+
execPath: process.execPath,
|
|
184
|
+
fileExists: existsSync,
|
|
185
|
+
},
|
|
186
|
+
): PiResolution {
|
|
187
|
+
const currentScript = runtime.currentScript;
|
|
188
|
+
const isBunVirtualScript = currentScript?.startsWith('/$bunfs/root/');
|
|
177
189
|
|
|
178
|
-
|
|
179
|
-
command:
|
|
180
|
-
|
|
181
|
-
|
|
190
|
+
if (currentScript && !isBunVirtualScript && runtime.fileExists(currentScript)) {
|
|
191
|
+
return { command: runtime.execPath, entryPoint: currentScript };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const execName = path.basename(runtime.execPath).toLowerCase();
|
|
195
|
+
const isGenericRuntime = /^(node|bun)(\.exe)?$/.test(execName);
|
|
196
|
+
if (!isGenericRuntime) return { command: runtime.execPath, entryPoint: null };
|
|
197
|
+
|
|
198
|
+
return { command: 'pi', entryPoint: null };
|
|
182
199
|
}
|
|
183
200
|
|
|
184
201
|
export const defaultRunner: ProcessRunner = (invocation, handlers, signal) =>
|
|
@@ -206,14 +223,27 @@ function textFromMessage(message: unknown): string | undefined {
|
|
|
206
223
|
const content = (message as { content?: unknown }).content;
|
|
207
224
|
if (!Array.isArray(content)) return undefined;
|
|
208
225
|
|
|
226
|
+
const texts: string[] = [];
|
|
209
227
|
for (const part of content) {
|
|
210
228
|
if (part && typeof part === 'object' && (part as { type?: unknown }).type === 'text') {
|
|
211
229
|
const text = (part as { text?: unknown }).text;
|
|
212
|
-
if (typeof text === 'string')
|
|
230
|
+
if (typeof text === 'string') texts.push(text);
|
|
213
231
|
}
|
|
214
232
|
}
|
|
215
233
|
|
|
216
|
-
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;
|
|
217
247
|
}
|
|
218
248
|
|
|
219
249
|
type ContextWindowLookup = {
|
|
@@ -305,18 +335,18 @@ function usageFromMessages(
|
|
|
305
335
|
return sawUsage ? aggregate : undefined;
|
|
306
336
|
}
|
|
307
337
|
|
|
308
|
-
function
|
|
338
|
+
function lastAssistantMessage(messages: unknown): unknown | undefined {
|
|
309
339
|
if (!Array.isArray(messages)) return undefined;
|
|
310
340
|
for (let index = messages.length - 1; index >= 0; index--) {
|
|
311
|
-
|
|
312
|
-
if (!message || typeof message !== 'object') continue;
|
|
313
|
-
if ((message as { role?: unknown }).role !== 'assistant') continue;
|
|
314
|
-
const model = modelFromMessage(message);
|
|
315
|
-
if (model) return model;
|
|
341
|
+
if (isAssistantMessage(messages[index])) return messages[index];
|
|
316
342
|
}
|
|
317
343
|
return undefined;
|
|
318
344
|
}
|
|
319
345
|
|
|
346
|
+
function lastAssistantModel(messages: unknown): string | undefined {
|
|
347
|
+
return modelFromMessage(lastAssistantMessage(messages));
|
|
348
|
+
}
|
|
349
|
+
|
|
320
350
|
function buildTaskArgument(task: string, taskFilePath: string | undefined): string {
|
|
321
351
|
return taskFilePath ? `Task: @${taskFilePath}` : `Task: ${task}`;
|
|
322
352
|
}
|
|
@@ -436,6 +466,8 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
436
466
|
let stderr = '';
|
|
437
467
|
let model = options.agent.model;
|
|
438
468
|
let stdoutBuffer = '';
|
|
469
|
+
let finalAssistantStopReason: string | undefined;
|
|
470
|
+
let finalAssistantErrorMessage: string | undefined;
|
|
439
471
|
|
|
440
472
|
const progress = (status: AgentProgress['status']): AgentProgress => ({
|
|
441
473
|
agent: options.agent.name,
|
|
@@ -452,6 +484,13 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
452
484
|
const emit = (status: AgentProgress['status'] = 'running') =>
|
|
453
485
|
options.onProgress?.(progress(status));
|
|
454
486
|
|
|
487
|
+
const setFinalAssistantOutput = (message: unknown) => {
|
|
488
|
+
// 最后一条 assistant 消息才是权威的最终输出,不能让工具结果占位符或旧文本伪装为结果。
|
|
489
|
+
output = textFromMessage(message) ?? '';
|
|
490
|
+
finalAssistantStopReason = stringMessageProperty(message, 'stopReason');
|
|
491
|
+
finalAssistantErrorMessage = stringMessageProperty(message, 'errorMessage');
|
|
492
|
+
};
|
|
493
|
+
|
|
455
494
|
try {
|
|
456
495
|
const promptFilePath = path.join(tempDir, 'system-prompt.md');
|
|
457
496
|
|
|
@@ -483,7 +522,14 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
483
522
|
modelsPath: options.agentDir ? path.join(options.agentDir, 'models.json') : undefined,
|
|
484
523
|
});
|
|
485
524
|
const modelRegistry = new ModelRegistry(runtime);
|
|
486
|
-
const args = [
|
|
525
|
+
const args = [
|
|
526
|
+
...(pi.entryPoint ? [pi.entryPoint] : []),
|
|
527
|
+
'--mode',
|
|
528
|
+
'json',
|
|
529
|
+
'-p',
|
|
530
|
+
'--no-skills',
|
|
531
|
+
'--no-prompt-templates',
|
|
532
|
+
];
|
|
487
533
|
|
|
488
534
|
if (options.agent.systemPromptMode === 'replace-all') args.push('--no-context-files');
|
|
489
535
|
if (options.agent.model) args.push('--model', options.agent.model);
|
|
@@ -554,8 +600,9 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
554
600
|
}
|
|
555
601
|
|
|
556
602
|
if (event.type === 'message_end' && event.message) {
|
|
557
|
-
|
|
558
|
-
|
|
603
|
+
if (!isAssistantMessage(event.message)) return;
|
|
604
|
+
|
|
605
|
+
setFinalAssistantOutput(event.message);
|
|
559
606
|
updateUsage(usage, usageFromMessage(event.message, modelRegistry));
|
|
560
607
|
model = modelFromMessage(event.message) ?? model;
|
|
561
608
|
emit();
|
|
@@ -565,6 +612,8 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
565
612
|
if (event.type === 'agent_end') {
|
|
566
613
|
const aggregate = usageFromMessages(event.messages, modelRegistry);
|
|
567
614
|
if (aggregate) replaceUsage(usage, aggregate);
|
|
615
|
+
const finalAssistantMessage = lastAssistantMessage(event.messages);
|
|
616
|
+
if (finalAssistantMessage) setFinalAssistantOutput(finalAssistantMessage);
|
|
568
617
|
model = lastAssistantModel(event.messages) ?? model;
|
|
569
618
|
emit();
|
|
570
619
|
}
|
|
@@ -592,8 +641,22 @@ export async function runSubagent(options: RunSubagentOptions): Promise<AgentRes
|
|
|
592
641
|
);
|
|
593
642
|
|
|
594
643
|
if (stdoutBuffer.trim()) processLine(stdoutBuffer);
|
|
595
|
-
const
|
|
596
|
-
|
|
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
|
+
}
|
|
597
660
|
|
|
598
661
|
const truncated = truncateHeadContent(output, OUTPUT_MAX_BYTES, OUTPUT_MAX_LINES);
|
|
599
662
|
if (truncated !== undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnnywu/pi-subagents",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.2",
|
|
4
4
|
"description": "Sub-agents extension for pi coding agent.",
|
|
5
5
|
"homepage": "https://github.com/jwu/pi-subagents#readme",
|
|
6
6
|
"repository": {
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@commitlint/cli": "^20.5.3",
|
|
91
|
-
"@earendil-works/pi-coding-agent": "^0.83.0",
|
|
92
91
|
"@commitlint/config-conventional": "^20.5.3",
|
|
92
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
93
93
|
"@semantic-release/changelog": "^6.0.3",
|
|
94
94
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
95
95
|
"@semantic-release/git": "^10.0.1",
|