@tranhoangnguyen0310/pi-flow-external 5.1.1-external.0 → 5.2.0-external.0
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 +9 -0
- package/package.json +10 -10
- package/src/config-modal.ts +7 -1
- package/src/core/concurrency.ts +4 -1
- package/src/core/errors.ts +4 -1
- package/src/core/run-registry.ts +6 -1
- package/src/external-command.ts +4 -1
- package/src/pi-subagent.ts +43 -1
- package/src/workflow/journal.ts +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to pi-flow external are documented here.
|
|
4
4
|
|
|
5
|
+
## [5.2.0-external.0] - 2026-10-20
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- Requires Pi 1.3: peer dependencies on `@earendil-works/pi-agent-core`, `pi-ai`, `pi-coding-agent` and `pi-tui` move from `~0.99.2` to `~1.3.0`.
|
|
9
|
+
- Node 26 is supported and tested in CI. `npm run e2e` no longer passes `--experimental-transform-types`, which Node 26 removed; the sources now use erasable TypeScript syntax only and load through Node's built-in type stripping.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `external_delegate` and `external_workflow` no longer disappear from a session saved before the #85 rename. Pi restores a resumed or tree-navigated session's tool loadout from its transcript, and the legacy names `Agent` and `workflow` no longer register. Each legacy name the restored loadout declares now activates its replacement. A launcher stays off when the loadout also declares its current name, or when `--tools -<name>` or `--no-tools` excludes it.
|
|
13
|
+
|
|
5
14
|
## [5.1.1-external.0] - 2026-10-20
|
|
6
15
|
|
|
7
16
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tranhoangnguyen0310/pi-flow-external",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0-external.0",
|
|
4
4
|
"description": "External Claude Code, Codex CLI, Antigravity, Grok Build CLI, Muse Code, and OpenCode delegation for pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.ts",
|
|
@@ -41,22 +41,22 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc",
|
|
43
43
|
"check": "tsc --noEmit && vitest run",
|
|
44
|
-
"e2e": "node
|
|
44
|
+
"e2e": "node scripts/e2e/external.mjs",
|
|
45
45
|
"field-report": "node scripts/field-report.mjs",
|
|
46
46
|
"test": "vitest run"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@earendil-works/pi-agent-core": "~
|
|
50
|
-
"@earendil-works/pi-ai": "~
|
|
51
|
-
"@earendil-works/pi-coding-agent": "~
|
|
52
|
-
"@earendil-works/pi-tui": "~
|
|
49
|
+
"@earendil-works/pi-agent-core": "~1.3.0",
|
|
50
|
+
"@earendil-works/pi-ai": "~1.3.0",
|
|
51
|
+
"@earendil-works/pi-coding-agent": "~1.3.0",
|
|
52
|
+
"@earendil-works/pi-tui": "~1.3.0",
|
|
53
53
|
"typebox": "*"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
-
"@earendil-works/pi-agent-core": "
|
|
57
|
-
"@earendil-works/pi-ai": "
|
|
58
|
-
"@earendil-works/pi-coding-agent": "
|
|
59
|
-
"@earendil-works/pi-tui": "
|
|
56
|
+
"@earendil-works/pi-agent-core": "1.3.0",
|
|
57
|
+
"@earendil-works/pi-ai": "1.3.0",
|
|
58
|
+
"@earendil-works/pi-coding-agent": "1.3.0",
|
|
59
|
+
"@earendil-works/pi-tui": "1.3.0",
|
|
60
60
|
"@types/node": "^24.12.4",
|
|
61
61
|
"typebox": "1.1.38",
|
|
62
62
|
"typescript": "^5.9.3",
|
package/src/config-modal.ts
CHANGED
|
@@ -23,7 +23,13 @@ export class ConfigModal implements Component {
|
|
|
23
23
|
private remember = false;
|
|
24
24
|
private cancelBusy?: () => void;
|
|
25
25
|
focused = true;
|
|
26
|
-
|
|
26
|
+
private tui: TUI;
|
|
27
|
+
private theme: Theme;
|
|
28
|
+
private cancelled: (data:string)=>boolean;
|
|
29
|
+
private externalEditorKey: (data:string)=>boolean;
|
|
30
|
+
constructor(tui: TUI, theme: Theme, cancelled: (data:string)=>boolean = data=>matchesKey(data,Key.escape), externalEditorKey:(data:string)=>boolean = ()=>false) {
|
|
31
|
+
this.tui = tui; this.theme = theme; this.cancelled = cancelled; this.externalEditorKey = externalEditorKey;
|
|
32
|
+
}
|
|
27
33
|
private listTheme() {
|
|
28
34
|
return { selectedPrefix: (s:string)=>this.theme.fg('accent',s), selectedText: (s:string)=>this.theme.fg('accent',s), description:(s:string)=>this.theme.fg('muted',s),scrollInfo:(s:string)=>this.theme.fg('dim',s),noMatch:(s:string)=>this.theme.fg('warning',s) };
|
|
29
35
|
}
|
package/src/core/concurrency.ts
CHANGED
|
@@ -42,10 +42,13 @@ export class ConcurrencyLimiter {
|
|
|
42
42
|
private active = 0;
|
|
43
43
|
private readonly waiters: Waiter[] = [];
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
readonly max: number;
|
|
46
|
+
|
|
47
|
+
constructor(max: number) {
|
|
46
48
|
if (!Number.isInteger(max) || max < 1) {
|
|
47
49
|
throw new Error("ConcurrencyLimiter max must be a positive integer");
|
|
48
50
|
}
|
|
51
|
+
this.max = max;
|
|
49
52
|
}
|
|
50
53
|
|
|
51
54
|
get activeCount(): number {
|
package/src/core/errors.ts
CHANGED
|
@@ -8,8 +8,11 @@ export type FlowErrorCode = (typeof FLOW_ERROR_CODES)[number];
|
|
|
8
8
|
|
|
9
9
|
/** Expected caller/configuration failure, distinct from a programming error. */
|
|
10
10
|
export class ExpectedFlowError extends Error {
|
|
11
|
-
|
|
11
|
+
readonly code: FlowErrorCode;
|
|
12
|
+
|
|
13
|
+
constructor(code: FlowErrorCode, message: string) {
|
|
12
14
|
super(message);
|
|
15
|
+
this.code = code;
|
|
13
16
|
this.name = "ExpectedFlowError";
|
|
14
17
|
}
|
|
15
18
|
}
|
package/src/core/run-registry.ts
CHANGED
|
@@ -80,11 +80,16 @@ export class RunRegistry {
|
|
|
80
80
|
private readonly closedSessions = new Set<string>();
|
|
81
81
|
private readonly backgroundListeners = new Set<(event: BackgroundRunEvent) => void>();
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
private readonly completedLimit: number;
|
|
84
|
+
private readonly shutdownGraceMs: number;
|
|
85
|
+
|
|
86
|
+
constructor(completedLimit = DEFAULT_COMPLETED_LIMIT, shutdownGraceMs = 4_000) {
|
|
84
87
|
if (!Number.isInteger(completedLimit) || completedLimit < 0) {
|
|
85
88
|
throw new Error("completed run limit must be a non-negative integer");
|
|
86
89
|
}
|
|
87
90
|
if (!Number.isInteger(shutdownGraceMs) || shutdownGraceMs < 0) throw new Error("shutdown grace must be a non-negative integer");
|
|
91
|
+
this.completedLimit = completedLimit;
|
|
92
|
+
this.shutdownGraceMs = shutdownGraceMs;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
sessionVersion(sessionId: string): number {
|
package/src/external-command.ts
CHANGED
|
@@ -1090,8 +1090,11 @@ type ExternalRunsResult = { content: Array<{ type: string; text?: string }>; det
|
|
|
1090
1090
|
|
|
1091
1091
|
/** A returned external_runs failure, raised with its code so callers recover by code, never by message. */
|
|
1092
1092
|
class RunsActionError extends Error {
|
|
1093
|
-
|
|
1093
|
+
readonly code: string;
|
|
1094
|
+
|
|
1095
|
+
constructor(code: string, message: string) {
|
|
1094
1096
|
super(message);
|
|
1097
|
+
this.code = code;
|
|
1095
1098
|
}
|
|
1096
1099
|
}
|
|
1097
1100
|
|
package/src/pi-subagent.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { resolve, join } from "node:path";
|
|
|
17
17
|
import { resolveExecutionProfile } from './execution-config.ts';
|
|
18
18
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
19
19
|
import { tmpdir } from 'node:os';
|
|
20
|
+
import { getCurrentSystemMessage } from "@earendil-works/pi-ai";
|
|
20
21
|
import { Text } from "@earendil-works/pi-tui";
|
|
21
22
|
import { Type, type Static } from "typebox";
|
|
22
23
|
import {
|
|
@@ -51,7 +52,7 @@ import { registerProfileCreator, startRoleInterview, startHarnessInterview } fro
|
|
|
51
52
|
import { registerExternalCommand } from "./external-command.ts";
|
|
52
53
|
import { DEFAULT_EXTERNAL_SETTINGS, loadExternalSettings, resolveCtxDefaultHarness, resolveExternalSettings, renderDefaultHarness } from "./settings.ts";
|
|
53
54
|
import { EXTERNAL_HARNESSES } from "./types.ts";
|
|
54
|
-
import { EXTERNAL_DELEGATE_TOOL } from "./tool-names.ts";
|
|
55
|
+
import { EXTERNAL_DELEGATE_TOOL, EXTERNAL_WORKFLOW_TOOL } from "./tool-names.ts";
|
|
55
56
|
import type {
|
|
56
57
|
PermissionTier,
|
|
57
58
|
SubagentBackend,
|
|
@@ -257,6 +258,45 @@ function isSubagentProgressNode(value: unknown): value is SubagentProgressNode {
|
|
|
257
258
|
);
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
// Launcher names a transcript saved before the #85 rename declares, and their replacements.
|
|
262
|
+
const RENAMED_LAUNCHERS: ReadonlyArray<readonly [legacy: string, current: string]> = [
|
|
263
|
+
["Agent", EXTERNAL_DELEGATE_TOOL],
|
|
264
|
+
["workflow", EXTERNAL_WORKFLOW_TOOL],
|
|
265
|
+
];
|
|
266
|
+
|
|
267
|
+
// Launchers the user kept off on the command line. Pi applies `--tools` modifiers
|
|
268
|
+
// to the restored loadout but does not expose them to extensions, and a `-name`
|
|
269
|
+
// modifier cannot match the legacy name it replaces. `--no-tools` turns
|
|
270
|
+
// extension tools off by default. SDK `tools` options are not visible here.
|
|
271
|
+
function cliExcludedLaunchers(argv: readonly string[]): Set<string> {
|
|
272
|
+
if (argv.includes("--no-tools") || argv.includes("-nt")) return new Set(RENAMED_LAUNCHERS.map(([, current]) => current));
|
|
273
|
+
let tools: string[] = [];
|
|
274
|
+
argv.forEach((arg, index) => {
|
|
275
|
+
if ((arg === "--tools" || arg === "-t") && index + 1 < argv.length) {
|
|
276
|
+
tools = argv[index + 1].split(",").map((name) => name.trim());
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
return new Set(tools.filter((name) => name.startsWith("-")).map((name) => name.slice(1)));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Pi restores a resumed or tree-navigated session's tool loadout from its
|
|
283
|
+
// transcript. A legacy name no longer registers, so its replacement stays off.
|
|
284
|
+
// Activate the replacement for each legacy name the restored loadout declares,
|
|
285
|
+
// unless the loadout also declares the replacement (it was then turned off on
|
|
286
|
+
// purpose) or the command line excludes it. setActiveTools ignores a tool that
|
|
287
|
+
// is not registered or not allowed.
|
|
288
|
+
function restoreRenamedLaunchers(pi: ExtensionAPI, ctx: ExtensionContext, argv: readonly string[] = process.argv): void {
|
|
289
|
+
const declared = new Set(
|
|
290
|
+
(getCurrentSystemMessage(ctx.sessionManager.buildSessionProjection().messages)?.toolsAdded ?? []).map((tool) => tool.name),
|
|
291
|
+
);
|
|
292
|
+
const active = pi.getActiveTools();
|
|
293
|
+
const excluded = cliExcludedLaunchers(argv);
|
|
294
|
+
const restored = RENAMED_LAUNCHERS
|
|
295
|
+
.filter(([legacy, current]) => declared.has(legacy) && !declared.has(current) && !active.includes(current) && !excluded.has(current))
|
|
296
|
+
.map(([, current]) => current);
|
|
297
|
+
if (restored.length > 0) pi.setActiveTools([...active, ...restored]);
|
|
298
|
+
}
|
|
299
|
+
|
|
260
300
|
function createUsageStatusState(): SubagentUsageStatusState {
|
|
261
301
|
return {
|
|
262
302
|
calls: new Map(),
|
|
@@ -1019,7 +1059,9 @@ export function createSubagentExtension(options: SubagentExtensionOptions = {}):
|
|
|
1019
1059
|
if (activeSessionId) scheduleWakeFlush(activeSessionId);
|
|
1020
1060
|
});
|
|
1021
1061
|
|
|
1062
|
+
pi.on("session_tree", (_event, ctx) => restoreRenamedLaunchers(pi, ctx));
|
|
1022
1063
|
pi.on("session_start", (_event, ctx) => {
|
|
1064
|
+
restoreRenamedLaunchers(pi, ctx);
|
|
1023
1065
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
1024
1066
|
rememberSessionContext(ctx, sessionId);
|
|
1025
1067
|
inAgentTurn = false;
|
package/src/workflow/journal.ts
CHANGED
|
@@ -48,8 +48,13 @@ export interface LoadedWorkflowJournal {
|
|
|
48
48
|
|
|
49
49
|
/** A journal that exists but cannot be loaded; `project` is set when its own run_start line names one. */
|
|
50
50
|
export class WorkflowJournalReadError extends ExpectedFlowError {
|
|
51
|
-
|
|
51
|
+
readonly project: string | undefined;
|
|
52
|
+
readonly integrity: "damaged" | "unknown";
|
|
53
|
+
|
|
54
|
+
constructor(message: string, project: string | undefined, integrity: "damaged" | "unknown") {
|
|
52
55
|
super("run_unavailable", message);
|
|
56
|
+
this.project = project;
|
|
57
|
+
this.integrity = integrity;
|
|
53
58
|
this.name = "WorkflowJournalReadError";
|
|
54
59
|
}
|
|
55
60
|
}
|