@tranhoangnguyen0310/pi-flow-external 2.1.0-external.1 → 2.1.0-external.3
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 +18 -0
- package/package.json +1 -1
- package/src/core/agy.ts +5 -3
- package/src/core/claude.ts +7 -2
- package/src/core/codex.ts +4 -2
- package/src/core/progress.ts +14 -1
- package/src/core/run-inspection.ts +11 -2
- package/src/core/spawn.ts +8 -5
- package/src/workflow/script-worker.ts +4 -0
- package/src/workflow/tool.ts +8 -1
- package/src/workflow/types.ts +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,24 @@ All notable changes to pi-flow external are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [2.1.0-external.3] - 2026-09-18
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Prevented Claude `is_error: true` result diagnostic from being mislabeled as assistant output on terminal error.
|
|
12
|
+
- Supported Pi `responseId` in `inspectRun` event-stream projection and added regression coverage for live Pi event inspection.
|
|
13
|
+
- Handled surrogate-pair boundaries when slicing the interrupted output tail and shared the `OUTPUT_PREVIEW_CHARS` constant across spawn and workflow receipts.
|
|
14
|
+
- Pinned spawn-level timeout rewrite behavior and workflow script `assistantOutput` error propagation in automated tests.
|
|
15
|
+
|
|
16
|
+
## [2.1.0-external.2] - 2026-09-18
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- 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.
|
|
21
|
+
- 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.
|
|
22
|
+
- `ChildRunError` preserves `partialOutput` and `assistantOutput` for workflow scripts to inspect, and uncaught child errors surface the interrupted output preview in workflow failure receipts.
|
|
23
|
+
- Hardened CI release workflow polling loop against npm registry replication latency (increased timeout to 150s).
|
|
24
|
+
|
|
7
25
|
## [2.1.0-external.1] - 2026-09-16
|
|
8
26
|
|
|
9
27
|
### Fixed
|
package/package.json
CHANGED
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
|
|
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
|
-
|
|
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,
|
package/src/core/claude.ts
CHANGED
|
@@ -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";
|
|
@@ -242,6 +243,9 @@ function structuredTextFromClaudeValue(value: unknown): string | undefined {
|
|
|
242
243
|
|
|
243
244
|
export function extractClaudeFinalText(event: Record<string, unknown>): string | undefined {
|
|
244
245
|
if (event.type === "result") {
|
|
246
|
+
if (event.is_error === true) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
245
249
|
return (
|
|
246
250
|
structuredTextFromClaudeValue(event.structured_output) ??
|
|
247
251
|
(typeof event.result === "string" ? event.result : undefined)
|
|
@@ -570,7 +574,7 @@ export async function spawnClaudeSubagent(params: {
|
|
|
570
574
|
if (child) abortChildTree(child);
|
|
571
575
|
const message = error instanceof Error ? error.message : String(error);
|
|
572
576
|
const status = params.signal?.aborted ? "aborted" : "error";
|
|
573
|
-
const output = assistantOutput(assistantMessages, "interrupted");
|
|
577
|
+
const output = assistantOutput(assistantMessages, "interrupted", resultText);
|
|
574
578
|
params.onUsage(latestUsage);
|
|
575
579
|
if (progress) {
|
|
576
580
|
progress.status = status;
|
|
@@ -580,7 +584,8 @@ export async function spawnClaudeSubagent(params: {
|
|
|
580
584
|
progress.endedAt = Date.now();
|
|
581
585
|
}
|
|
582
586
|
const verb = status === "aborted" ? "aborted" : "failed";
|
|
583
|
-
|
|
587
|
+
const preview = formatInterruptedOutputPreview(output);
|
|
588
|
+
return textResult(`Subagent "${params.description}" (${subagentType}) ${verb}: ${message}${preview}`, {
|
|
584
589
|
description: params.description,
|
|
585
590
|
subagentType,
|
|
586
591
|
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
|
-
|
|
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,
|
package/src/core/progress.ts
CHANGED
|
@@ -51,6 +51,19 @@ export function assistantOutput(
|
|
|
51
51
|
return output.length ? { status, messages: output } : undefined;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export const OUTPUT_PREVIEW_CHARS = 4_000;
|
|
55
|
+
|
|
56
|
+
export function formatInterruptedOutputPreview(output: SubagentAssistantOutput | undefined): string {
|
|
57
|
+
if (!output?.messages?.length) return "";
|
|
58
|
+
const text = output.messages.map((m) => m.text.trim()).filter(Boolean).join("\n\n");
|
|
59
|
+
if (!text) return "";
|
|
60
|
+
let slice = text.slice(-OUTPUT_PREVIEW_CHARS);
|
|
61
|
+
if (slice.length > 0 && slice.charCodeAt(0) >= 0xdc00 && slice.charCodeAt(0) <= 0xdfff) {
|
|
62
|
+
slice = slice.slice(1);
|
|
63
|
+
}
|
|
64
|
+
return `\n\nInterrupted output:\n${slice}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
54
67
|
export function extractAssistantMessages(messages: readonly unknown[]): Array<{ id?: string; text: string }> {
|
|
55
68
|
return messages.flatMap((value) => {
|
|
56
69
|
const message = value as { id?: unknown; role?: unknown; content?: unknown };
|
|
@@ -88,7 +101,7 @@ function getFirstTextLine(text: string): string {
|
|
|
88
101
|
return text.split("\n").find((line) => line.trim()) ?? text;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
function extractTextContent(content: unknown): string {
|
|
104
|
+
export function extractTextContent(content: unknown): string {
|
|
92
105
|
if (typeof content === "string") {
|
|
93
106
|
return content;
|
|
94
107
|
}
|
|
@@ -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) ?? asString(message.responseId);
|
|
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
|
|
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")
|
|
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
|
-
|
|
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 {
|
package/src/workflow/tool.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { createRunRecord } from "../core/run-record.ts";
|
|
|
17
17
|
import { runRecordsDirectory } from "../core/retention.ts";
|
|
18
18
|
import { captureParentContext } from "../core/parent-context.ts";
|
|
19
19
|
import { RunRegistry } from "../core/run-registry.ts";
|
|
20
|
+
import { OUTPUT_PREVIEW_CHARS } from "../core/progress.ts";
|
|
20
21
|
import { filterExternalAgentProfiles, getSubagentProfiles, mergeSynthesizedPiProfiles, resolveExternalProfile } from "../profiles.ts";
|
|
21
22
|
import { loadHarnessConfigs } from "../harnesses.ts";
|
|
22
23
|
import { WORKFLOW_PROMPT_SNIPPET } from "../prompts.ts";
|
|
@@ -306,12 +307,15 @@ export function createWorkflowTool(
|
|
|
306
307
|
}
|
|
307
308
|
if (resultDetails.status !== "done") {
|
|
308
309
|
const runId = resultDetails.runId ?? call.runRecord?.runId ?? childId;
|
|
310
|
+
const partialOutput = resultDetails.assistantOutput?.messages.map((m) => m.text.trim()).filter(Boolean).join("\n\n");
|
|
309
311
|
throw new ChildRunError({
|
|
310
312
|
runId,
|
|
311
313
|
outcome: childOutcome(resultDetails),
|
|
312
314
|
message: resultDetails.error ?? "subagent failed",
|
|
313
315
|
outputRef: { runId, view: "output" },
|
|
314
316
|
diagnosticsRef: { runId, view: "diagnostics" },
|
|
317
|
+
...(resultDetails.assistantOutput ? { assistantOutput: resultDetails.assistantOutput } : {}),
|
|
318
|
+
...(partialOutput ? { partialOutput } : {}),
|
|
315
319
|
});
|
|
316
320
|
}
|
|
317
321
|
if (externalOutputSchema) {
|
|
@@ -528,8 +532,11 @@ export function createWorkflowTool(
|
|
|
528
532
|
agent.endedAt = Date.now();
|
|
529
533
|
}
|
|
530
534
|
}
|
|
535
|
+
const partial = error instanceof ChildRunError && error.partialOutput
|
|
536
|
+
? `\n\nInterrupted child output (${error.runId}):\n${error.partialOutput.slice(-OUTPUT_PREVIEW_CHARS)}`
|
|
537
|
+
: "";
|
|
531
538
|
return workflowResult(
|
|
532
|
-
`Workflow "${metaName}" ${aborted ? "aborted" : "failed"}: ${message}${formatRecentLogs(snapshot.logs)}`,
|
|
539
|
+
`Workflow "${metaName}" ${aborted ? "aborted" : "failed"}: ${message}${partial}${formatRecentLogs(snapshot.logs)}`,
|
|
533
540
|
cloneSnapshot(snapshot),
|
|
534
541
|
);
|
|
535
542
|
} finally {
|
package/src/workflow/types.ts
CHANGED
|
@@ -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
|
|