@tranhoangnguyen0310/pi-flow-external 2.1.0-external.1 → 2.1.0-external.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
@@ -4,7 +4,14 @@ All notable changes to pi-flow external are documented here.
4
4
 
5
5
  ## Unreleased
6
6
 
7
- ## [2.1.0-external.1] - 2026-09-16
7
+ ## [2.1.0-external.2] - 2026-09-18
8
+
9
+ ### Fixed
10
+
11
+ - Preserved and exposed available partial assistant output on failure, cancellation, and timeout across all backend adapters (`codex`, `claude`, `agy`, and in-process `pi`). Unsuccessful terminal receipts retain the failure status and error diagnostic while appending a bounded `Interrupted output:` preview.
12
+ - Recovered interrupted assistant output in run evidence inspection (`external_runs(action: "inspect", view: "output")`) from completed non-done summary documents, including Pi SDK child runs.
13
+ - `ChildRunError` preserves `partialOutput` and `assistantOutput` for workflow scripts to inspect, and uncaught child errors surface the interrupted output preview in workflow failure receipts.
14
+ - Hardened CI release workflow polling loop against npm registry replication latency (increased timeout to 150s).
8
15
 
9
16
  ### Fixed
10
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tranhoangnguyen0310/pi-flow-external",
3
- "version": "2.1.0-external.1",
3
+ "version": "2.1.0-external.2",
4
4
  "description": "External Claude Code, Codex CLI, and Antigravity delegation for pi.",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
package/src/core/agy.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn, type ChildProcess } from "node:child_process";
2
2
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import { assistantOutput, createProgressEmitter, textResult, type AgentToolResult } from "./progress.ts";
3
+ import { assistantOutput, createProgressEmitter, formatInterruptedOutputPreview, textResult, type AgentToolResult } from "./progress.ts";
4
4
  import {
5
5
  createBoundedBuffer,
6
6
  MAX_STDERR_CHARS,
@@ -486,7 +486,8 @@ export async function spawnAgySubagent(params: {
486
486
  if (child) abortChildTree(child);
487
487
  const message = error instanceof Error ? error.message : String(error);
488
488
  const status = params.signal?.aborted ? "aborted" : "error";
489
- const output = assistantOutput(assistantMessages, "interrupted");
489
+ const candidate = terminalResult ? textFromAgyResult(terminalResult)?.trim() : undefined;
490
+ const output = assistantOutput(assistantMessages, "interrupted", candidate);
490
491
  params.onUsage(latestUsage);
491
492
  if (progress) {
492
493
  progress.status = status;
@@ -495,7 +496,8 @@ export async function spawnAgySubagent(params: {
495
496
  progress.assistantOutput = output;
496
497
  progress.endedAt = Date.now();
497
498
  }
498
- return textResult(`Subagent "${params.description}" (${subagentType}) ${status === "aborted" ? "aborted" : "failed"}: ${message}`, {
499
+ const preview = formatInterruptedOutputPreview(output);
500
+ return textResult(`Subagent "${params.description}" (${subagentType}) ${status === "aborted" ? "aborted" : "failed"}: ${message}${preview}`, {
499
501
  description: params.description,
500
502
  subagentType,
501
503
  backend: params.profile.backend,
@@ -3,6 +3,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import {
4
4
  assistantOutput,
5
5
  createProgressEmitter,
6
+ formatInterruptedOutputPreview,
6
7
  textResult,
7
8
  type AgentToolResult,
8
9
  } from "./progress.ts";
@@ -570,7 +571,7 @@ export async function spawnClaudeSubagent(params: {
570
571
  if (child) abortChildTree(child);
571
572
  const message = error instanceof Error ? error.message : String(error);
572
573
  const status = params.signal?.aborted ? "aborted" : "error";
573
- const output = assistantOutput(assistantMessages, "interrupted");
574
+ const output = assistantOutput(assistantMessages, "interrupted", resultText);
574
575
  params.onUsage(latestUsage);
575
576
  if (progress) {
576
577
  progress.status = status;
@@ -580,7 +581,8 @@ export async function spawnClaudeSubagent(params: {
580
581
  progress.endedAt = Date.now();
581
582
  }
582
583
  const verb = status === "aborted" ? "aborted" : "failed";
583
- return textResult(`Subagent "${params.description}" (${subagentType}) ${verb}: ${message}`, {
584
+ const preview = formatInterruptedOutputPreview(output);
585
+ return textResult(`Subagent "${params.description}" (${subagentType}) ${verb}: ${message}${preview}`, {
584
586
  description: params.description,
585
587
  subagentType,
586
588
  backend: params.profile.backend,
package/src/core/codex.ts CHANGED
@@ -6,6 +6,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
6
  import {
7
7
  assistantOutput,
8
8
  createProgressEmitter,
9
+ formatInterruptedOutputPreview,
9
10
  textResult,
10
11
  type AgentToolResult,
11
12
  } from "./progress.ts";
@@ -533,7 +534,7 @@ export async function spawnCodexSubagent(params: {
533
534
  if (child) abortChildTree(child);
534
535
  const message = error instanceof Error ? error.message : String(error);
535
536
  const status = params.signal?.aborted ? "aborted" : "error";
536
- const output = assistantOutput(assistantMessages, "interrupted");
537
+ const output = assistantOutput(assistantMessages, "interrupted", resultText);
537
538
  params.onUsage(latestUsage);
538
539
  if (progress) {
539
540
  progress.status = status;
@@ -543,7 +544,8 @@ export async function spawnCodexSubagent(params: {
543
544
  progress.endedAt = Date.now();
544
545
  }
545
546
  const verb = status === "aborted" ? "aborted" : "failed";
546
- return textResult(`Subagent "${params.description}" (${subagentType}) ${verb}: ${message}`, {
547
+ const preview = formatInterruptedOutputPreview(output);
548
+ return textResult(`Subagent "${params.description}" (${subagentType}) ${verb}: ${message}${preview}`, {
547
549
  description: params.description,
548
550
  subagentType,
549
551
  backend: params.profile.backend,
@@ -51,6 +51,12 @@ export function assistantOutput(
51
51
  return output.length ? { status, messages: output } : undefined;
52
52
  }
53
53
 
54
+ export function formatInterruptedOutputPreview(output: SubagentAssistantOutput | undefined): string {
55
+ if (!output?.messages?.length) return "";
56
+ const text = output.messages.map((m) => m.text.trim()).filter(Boolean).join("\n\n");
57
+ return text ? `\n\nInterrupted output:\n${text.slice(-4_000)}` : "";
58
+ }
59
+
54
60
  export function extractAssistantMessages(messages: readonly unknown[]): Array<{ id?: string; text: string }> {
55
61
  return messages.flatMap((value) => {
56
62
  const message = value as { id?: unknown; role?: unknown; content?: unknown };
@@ -88,7 +94,7 @@ function getFirstTextLine(text: string): string {
88
94
  return text.split("\n").find((line) => line.trim()) ?? text;
89
95
  }
90
96
 
91
- function extractTextContent(content: unknown): string {
97
+ export function extractTextContent(content: unknown): string {
92
98
  if (typeof content === "string") {
93
99
  return content;
94
100
  }
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { agyActivityFromEvent } from "./agy.ts";
6
6
  import { claudeActivityFromEvent, extractClaudeFinalText } from "./claude.ts";
7
7
  import { codexActivityFromEvent, extractCodexFinalText } from "./codex.ts";
8
+ import { extractTextContent } from "./progress.ts";
8
9
 
9
10
  const CURSOR_VERSION = 1;
10
11
  const DEFAULT_PAGE_BYTES = 32 * 1024;
@@ -117,7 +118,7 @@ export async function inspectRun({
117
118
  }
118
119
 
119
120
  if (decoded?.kind === "inspect" && decoded.source === "summary") throw new Error("Cursor source does not match inspection view");
120
- if (view === "output" && summary.document && terminalStatus === "done") {
121
+ if (view === "output" && summary.document && (terminalStatus === "done" || assistantItems(terminal).length > 0)) {
121
122
  if (decoded?.kind === "inspect" && decoded.source === "events") throw staleCursorError();
122
123
  const observation = await readObservation(eventsPath, runId);
123
124
  const items = terminalOutputItems(terminal);
@@ -347,7 +348,7 @@ function summaryProjection(runId: string, summary: SummaryState, observation: Ru
347
348
  };
348
349
  }
349
350
 
350
- function outputFromEvent(event: EvidenceEvent): ({ kind: "claude" | "codex" | "agy"; id?: string; text: string }) | undefined {
351
+ function outputFromEvent(event: EvidenceEvent): ({ kind: "claude" | "codex" | "agy" | "pi"; id?: string; text: string }) | undefined {
351
352
  if (event.type !== "backend_event") return undefined;
352
353
  const envelope = asRecord(event.data);
353
354
  const backendEvent = asRecord(envelope?.event);
@@ -368,6 +369,14 @@ function outputFromEvent(event: EvidenceEvent): ({ kind: "claude" | "codex" | "a
368
369
  const id = asString(update?.step_id);
369
370
  return text ? { kind: "agy", ...(id ? { id } : {}), text } : undefined;
370
371
  }
372
+ if (envelope?.backend === "pi" && backendEvent.type === "message_end") {
373
+ const message = asRecord(backendEvent.message);
374
+ if (message?.role === "assistant") {
375
+ const text = extractTextContent(message.content);
376
+ const id = asString(message.id);
377
+ return text ? { kind: "pi", ...(id ? { id } : {}), text } : undefined;
378
+ }
379
+ }
371
380
  return undefined;
372
381
  }
373
382
 
package/src/core/spawn.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  extractFinalAssistantText,
14
14
  extractAssistantMessages,
15
15
  assistantOutput,
16
+ formatInterruptedOutputPreview,
16
17
  getFinalAssistantFailure,
17
18
  getSubagentUsage,
18
19
  textResult,
@@ -223,8 +224,7 @@ function rewriteTimeoutResult(
223
224
  ): AgentToolResult {
224
225
  const details = markSubagentTimedOut(result.details as SubagentToolDetails, params.timeoutMs);
225
226
  const message = details.error;
226
- const partial = details.assistantOutput?.messages.at(-1)?.text;
227
- const preview = partial ? `\n\nInterrupted output:\n${partial.slice(-4_000)}` : "";
227
+ const preview = formatInterruptedOutputPreview(details.assistantOutput);
228
228
  return textResult(`Subagent "${params.description}" (${params.profile.name}) aborted: ${message}${preview}`, {
229
229
  ...details,
230
230
  description: params.description,
@@ -345,11 +345,13 @@ export async function spawnSubagent(params: SpawnSubagentParams): Promise<AgentT
345
345
  const details = result.details as SubagentToolDetails;
346
346
  if (details.status === "aborted") {
347
347
  const reason = params.signal.reason instanceof Error ? params.signal.reason.message : String(params.signal.reason);
348
- const previous = details.error;
349
348
  details.error = reason;
350
349
  if (details.progress) details.progress.error = reason;
351
350
  const first = result.content[0];
352
- if (first?.type === "text") first.text = previous ? first.text.replace(previous, reason) : `${first.text}: ${reason}`;
351
+ if (first?.type === "text") {
352
+ const preview = formatInterruptedOutputPreview(details.assistantOutput);
353
+ first.text = `Subagent "${params.description}" (${params.profile.name}) aborted: ${reason}${preview}`;
354
+ }
353
355
  }
354
356
  }
355
357
  if (params.context) {
@@ -829,7 +831,8 @@ async function spawnSubagentRuntime(params: SpawnSubagentRuntimeParams): Promise
829
831
  if (thinkingClamped) progress.thinkingClamped = thinkingClamped;
830
832
  }
831
833
  const verb = status === "aborted" ? "aborted" : "failed";
832
- return textResult(`Subagent "${description}" (${subagentType}) ${verb}: ${message}`, {
834
+ const preview = formatInterruptedOutputPreview(output);
835
+ return textResult(`Subagent "${description}" (${subagentType}) ${verb}: ${message}${preview}`, {
833
836
  description,
834
837
  subagentType,
835
838
  backend: profile.backend,
@@ -63,6 +63,8 @@ class ChildRunError extends Error {
63
63
  this.outcome = error.outcome;
64
64
  this.outputRef = error.outputRef;
65
65
  this.diagnosticsRef = error.diagnosticsRef;
66
+ this.assistantOutput = error.assistantOutput;
67
+ this.partialOutput = error.partialOutput;
66
68
  }
67
69
  }
68
70
 
@@ -116,6 +118,8 @@ function postError(error) {
116
118
  message: error.message,
117
119
  ...(error.outputRef ? { outputRef: error.outputRef } : {}),
118
120
  ...(error.diagnosticsRef ? { diagnosticsRef: error.diagnosticsRef } : {}),
121
+ ...(error.assistantOutput ? { assistantOutput: error.assistantOutput } : {}),
122
+ ...(error.partialOutput ? { partialOutput: error.partialOutput } : {}),
119
123
  } } : {}),
120
124
  });
121
125
  } finally {
@@ -306,12 +306,15 @@ export function createWorkflowTool(
306
306
  }
307
307
  if (resultDetails.status !== "done") {
308
308
  const runId = resultDetails.runId ?? call.runRecord?.runId ?? childId;
309
+ const partialOutput = resultDetails.assistantOutput?.messages.map((m) => m.text.trim()).filter(Boolean).join("\n\n");
309
310
  throw new ChildRunError({
310
311
  runId,
311
312
  outcome: childOutcome(resultDetails),
312
313
  message: resultDetails.error ?? "subagent failed",
313
314
  outputRef: { runId, view: "output" },
314
315
  diagnosticsRef: { runId, view: "diagnostics" },
316
+ ...(resultDetails.assistantOutput ? { assistantOutput: resultDetails.assistantOutput } : {}),
317
+ ...(partialOutput ? { partialOutput } : {}),
315
318
  });
316
319
  }
317
320
  if (externalOutputSchema) {
@@ -528,8 +531,11 @@ export function createWorkflowTool(
528
531
  agent.endedAt = Date.now();
529
532
  }
530
533
  }
534
+ const partial = error instanceof ChildRunError && error.partialOutput
535
+ ? `\n\nInterrupted child output (${error.runId}):\n${error.partialOutput.slice(-4_000)}`
536
+ : "";
531
537
  return workflowResult(
532
- `Workflow "${metaName}" ${aborted ? "aborted" : "failed"}: ${message}${formatRecentLogs(snapshot.logs)}`,
538
+ `Workflow "${metaName}" ${aborted ? "aborted" : "failed"}: ${message}${partial}${formatRecentLogs(snapshot.logs)}`,
533
539
  cloneSnapshot(snapshot),
534
540
  );
535
541
  } finally {
@@ -1,6 +1,7 @@
1
1
  import type { ParentContextMessages, ParentContextReceipt } from "../core/parent-context.ts";
2
2
  import type { ConcurrencyLimiter } from "../core/concurrency.ts";
3
3
  import type { RunRecord } from "../core/run-record.ts";
4
+ import type { SubagentAssistantOutput } from "../types.ts";
4
5
 
5
6
  export type ChildRunOutcome = "failed" | "cancelled" | "timed_out";
6
7
 
@@ -15,6 +16,8 @@ export interface SerializedChildRunError {
15
16
  message: string;
16
17
  outputRef?: ChildRunReference;
17
18
  diagnosticsRef?: ChildRunReference;
19
+ assistantOutput?: SubagentAssistantOutput;
20
+ partialOutput?: string;
18
21
  }
19
22
 
20
23
  export class ChildRunError extends Error {
@@ -22,6 +25,8 @@ export class ChildRunError extends Error {
22
25
  readonly outcome: ChildRunOutcome;
23
26
  readonly outputRef?: ChildRunReference;
24
27
  readonly diagnosticsRef?: ChildRunReference;
28
+ readonly assistantOutput?: SubagentAssistantOutput;
29
+ readonly partialOutput?: string;
25
30
 
26
31
  constructor(error: SerializedChildRunError) {
27
32
  super(error.message);
@@ -30,6 +35,8 @@ export class ChildRunError extends Error {
30
35
  this.outcome = error.outcome;
31
36
  this.outputRef = error.outputRef;
32
37
  this.diagnosticsRef = error.diagnosticsRef;
38
+ this.assistantOutput = error.assistantOutput;
39
+ this.partialOutput = error.partialOutput;
33
40
  }
34
41
  }
35
42
 
@@ -40,6 +47,8 @@ export function childRunErrorData(error: ChildRunError): SerializedChildRunError
40
47
  message: error.message,
41
48
  ...(error.outputRef ? { outputRef: error.outputRef } : {}),
42
49
  ...(error.diagnosticsRef ? { diagnosticsRef: error.diagnosticsRef } : {}),
50
+ ...(error.assistantOutput ? { assistantOutput: error.assistantOutput } : {}),
51
+ ...(error.partialOutput ? { partialOutput: error.partialOutput } : {}),
43
52
  };
44
53
  }
45
54