@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 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(): PiResolution {
175
- const packageEntryPoint = fileURLToPath(import.meta.resolve('@earendil-works/pi-coding-agent'));
176
- const packageRoot = path.dirname(path.dirname(packageEntryPoint));
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
- return {
179
- command: process.execPath,
180
- entryPoint: path.join(packageRoot, 'dist', 'cli.js'),
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') return text;
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 lastAssistantModel(messages: unknown): string | undefined {
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
- const message = messages[index];
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 = [pi.entryPoint, '--mode', 'json', '-p', '--no-skills', '--no-prompt-templates'];
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
- const text = textFromMessage(event.message);
558
- if (text !== undefined) output = text;
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 isError = exit.exitCode !== 0;
596
- if (isError && !output) output = stderr || `Subagent exited with code ${exit.exitCode}`;
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.0",
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",