@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 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.1.1-external.0",
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 --experimental-transform-types scripts/e2e/external.mjs",
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": "~0.99.2",
50
- "@earendil-works/pi-ai": "~0.99.2",
51
- "@earendil-works/pi-coding-agent": "~0.99.2",
52
- "@earendil-works/pi-tui": "~0.99.2",
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": "0.99.2",
57
- "@earendil-works/pi-ai": "0.99.2",
58
- "@earendil-works/pi-coding-agent": "0.99.2",
59
- "@earendil-works/pi-tui": "0.99.2",
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",
@@ -23,7 +23,13 @@ export class ConfigModal implements Component {
23
23
  private remember = false;
24
24
  private cancelBusy?: () => void;
25
25
  focused = true;
26
- constructor(private tui: TUI, private theme: Theme, private cancelled: (data:string)=>boolean = data=>matchesKey(data,Key.escape), private externalEditorKey:(data:string)=>boolean = ()=>false) {}
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
  }
@@ -42,10 +42,13 @@ export class ConcurrencyLimiter {
42
42
  private active = 0;
43
43
  private readonly waiters: Waiter[] = [];
44
44
 
45
- constructor(readonly max: number) {
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 {
@@ -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
- constructor(readonly code: FlowErrorCode, message: string) {
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
  }
@@ -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
- constructor(private readonly completedLimit = DEFAULT_COMPLETED_LIMIT, private readonly shutdownGraceMs = 4_000) {
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 {
@@ -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
- constructor(readonly code: string, message: string) {
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
 
@@ -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;
@@ -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
- constructor(message: string, readonly project: string | undefined, readonly integrity: "damaged" | "unknown") {
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
  }