@tranhoangnguyen0310/pi-flow-external 1.2.0-external.0 → 1.3.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/AGENTS.md CHANGED
@@ -29,7 +29,7 @@ This fork changes the original pi-flow contract: `Agent` is not a generic Pi sub
29
29
  - A backend failure with a complete local record is different from an incomplete or damaged record; preserve that distinction in reports.
30
30
  - Normal runs write private best-effort evidence under `~/.pi/agent/pi-flow-external/runs/` or `PI_FLOW_EXTERNAL_RUNS_DIR`. Records may still contain sensitive prompts, excerpts, and tool output despite redaction.
31
31
  - Structured nested-agent activity may extend the wall-clock deadline once, by one fresh base timeout, capped at twice the original deadline.
32
- - Do not automatically retry failed or aborted external runs. Preserve the receipt and retry only when the user asks.
32
+ - Do not automatically retry failed or aborted external runs. Preserve the receipt and retry only when the user asks. Exception: the agy backend retries once on infrastructure-classified failures (auth, eligibility, network); the retry is disclosed in the receipt details (`retries`, `retryOf`) and never applies to agent-level failures, aborts, or timeouts.
33
33
 
34
34
  ## Workflow contract
35
35
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to pi-flow external are documented here.
4
4
 
5
+ ## [1.3.0-external.0] - 2026-09-06
6
+
7
+ Adds a bounded retry for the Antigravity backend and documents the standardized external profile role matrix.
8
+
9
+ ### Added
10
+
11
+ - Single automatic retry for agy infrastructure failures (authentication, eligibility, network timeouts), disclosed in receipt details as `retries` and `retryOf`. Agent-level failures, aborts, and timeouts are never retried.
12
+
13
+ ### Changed
14
+
15
+ - AGENTS.md records the agy retry exception to the no-auto-retry policy; CONTEXT.md documents the profile role-matrix design stance (guardrails define lanes, not methods) and the known one-file-per-backend format inelegance.
16
+
5
17
  ## [1.2.0-external.0] - 2026-08-29
6
18
 
7
19
  Implements the orchestrator-decided tiers design ([#11](https://github.com/tranhoangnguyen03/pi-flow-external/issues/11)), live-verified against Claude Code 2.1.239, codex-cli 0.150.1, and agy 1.1.22.
package/CONTEXT.md CHANGED
@@ -28,6 +28,15 @@ The split is global and intentional to avoid tool ambiguity across projects. The
28
28
 
29
29
  User operations are namespaced under `/external`: status, Doctor, settings, profiles, profile creation, workflows, runs, and help. Extension-owned concurrency and timeout defaults live in `$PI_CODING_AGENT_DIR/pi-flow-external/settings.json`; profiles remain authoritative for downstream backend/model/thinking metadata.
30
30
 
31
+ ## Design stance
32
+
33
+ Guardrails exist to keep lanes from bleeding into each other (a reviewer that edits, a debugger that fixes), not to constrain how a model works. Role bodies stay short: define the job, state the boundary, then get out of the way and trust the model's judgment on approach, depth, and method. The generic `worker` role covers non-coding tasks with no method constraints at all.
34
+
35
+ ## Known inelegance
36
+
37
+ <!-- ponytail: one-backend-per-file profile format forces role x backend file duplication; extend src/profiles.ts with multi-backend profiles (e.g. backends: [claude, codex, agy] plus per-backend model map) if maintaining N copies of identical role bodies ever hurts -->
38
+ A standardized role roster needs one file per role per backend (e.g. `claude-qa`, `codex-qa`, `agy-qa` with identical bodies) because the profile format binds `backend:` and `model:` to a single file. The duplication is accepted for now; a future format extension could let one role file cover all backends. The same applies to `permission:` tiers: one tier per profile, but backends interpret tiers differently (Claude denies Bash at anything below `danger`), so command-running roles must declare `danger` even where a finer tier exists.
39
+
31
40
  ## Evidence boundary
32
41
 
33
- Normal external runs write best-effort local records under `~/.pi/agent/pi-flow-external/runs/` unless `PI_FLOW_EXTERNAL_RUNS_DIR` overrides it. The TUI shows short evidence IDs by default and exposes record/journal paths only through expanded output. Redaction is best-effort, records are not rotated automatically, and failed/aborted runs are not retried automatically.
42
+ Normal external runs write best-effort local records under `~/.pi/agent/pi-flow-external/runs/` unless `PI_FLOW_EXTERNAL_RUNS_DIR` overrides it. The TUI shows short evidence IDs by default and exposes record/journal paths only through expanded output. Redaction is best-effort, records are not rotated automatically, and failed/aborted runs are not retried automatically, except one agy retry for infrastructure-classified failures (disclosed via receipt `retries`/`retryOf`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tranhoangnguyen0310/pi-flow-external",
3
- "version": "1.2.0-external.0",
3
+ "version": "1.3.0-external.0",
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
@@ -237,6 +237,20 @@ function abortChild(child: ChildProcess): void {
237
237
  }, FORCE_KILL_DELAY_MS).unref();
238
238
  }
239
239
 
240
+ /** Infra-classified agy failures worth one bounded retry (auth/eligibility/network).
241
+ * Agent-level failures (task errors, protocol errors, aborts, timeouts) must not match. */
242
+ const TRANSIENT_AGY_FAILURE_PATTERNS: RegExp[] = [
243
+ /authentication failed/i,
244
+ /eligibility check failed/i,
245
+ /operation timed out/i,
246
+ /\b(ETIMEDOUT|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN)\b/,
247
+ ];
248
+
249
+ export function isTransientAgyFailure(error: unknown): boolean {
250
+ const message = error instanceof Error ? error.message : String(error);
251
+ return TRANSIENT_AGY_FAILURE_PATTERNS.some((pattern) => pattern.test(message));
252
+ }
253
+
240
254
  export async function spawnAgySubagent(params: {
241
255
  toolCallId: string;
242
256
  description: string;
package/src/core/spawn.ts CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  } from "./progress.ts";
20
20
  import { spawnClaudeSubagent } from "./claude.ts";
21
21
  import { spawnCodexSubagent } from "./codex.ts";
22
- import { spawnAgySubagent } from "./agy.ts";
22
+ import { spawnAgySubagent, isTransientAgyFailure } from "./agy.ts";
23
23
  import type {
24
24
  PermissionTier,
25
25
  SubagentBackend,
@@ -281,6 +281,8 @@ export async function spawnSubagent(params: SpawnSubagentParams): Promise<AgentT
281
281
  ? { budgetEnforceable: false }
282
282
  : {}),
283
283
  ...(details.sessionId ? { sessionId: details.sessionId } : {}),
284
+ ...(details.retries !== undefined ? { retries: details.retries } : {}),
285
+ ...(details.retryOf ? { retryOf: details.retryOf } : {}),
284
286
  ...(resumeSession ? { resumedFrom: resumeSession.runId } : {}),
285
287
  });
286
288
  attachRunRecord(
@@ -341,7 +343,8 @@ async function spawnSubagentRuntime(params: SpawnSubagentRuntimeParams): Promise
341
343
  });
342
344
  }
343
345
  if (params.profile.backend === "agy") {
344
- return spawnAgySubagent({
346
+ // ponytail: one immediate retry, agy-only, infra-classified failures only; per-attempt backoff if flakes persist
347
+ const agyParams = {
345
348
  toolCallId: params.toolCallId,
346
349
  description: params.description,
347
350
  prompt: params.prompt,
@@ -357,7 +360,25 @@ async function spawnSubagentRuntime(params: SpawnSubagentRuntimeParams): Promise
357
360
  outputSchema: params.outputSchema,
358
361
  permission: params.permission,
359
362
  resumeConversationId: params.resumeSessionId,
360
- });
363
+ };
364
+ let retryOf: string | undefined;
365
+ for (let attempt = 1; ; attempt++) {
366
+ const result = await spawnAgySubagent(agyParams);
367
+ const details = result.details as SubagentToolDetails;
368
+ const transientFailure =
369
+ details.status === "error" && !params.signal?.aborted && isTransientAgyFailure(details.error);
370
+ if (transientFailure && attempt === 1) {
371
+ retryOf = details.error ?? "agy transient failure";
372
+ // Resume the failed conversation so a mid-turn network failure does not
373
+ // replay already-executed tool calls in a fresh conversation.
374
+ agyParams.resumeConversationId = details.sessionId ?? agyParams.resumeConversationId;
375
+ continue;
376
+ }
377
+ if (retryOf) {
378
+ return { ...result, details: { ...details, retries: attempt - 1, retryOf } };
379
+ }
380
+ return result;
381
+ }
361
382
  }
362
383
  if (params.profile.backend === "claude") {
363
384
  return spawnClaudeSubagent({
package/src/types.ts CHANGED
@@ -191,6 +191,10 @@ export interface SubagentToolDetails {
191
191
  recordingError?: string;
192
192
  /** Number of currently running subagents, used to choose rich vs compact live rendering. */
193
193
  activeCount?: number;
194
+ /** Attempts retried for this run (agy infra failures retry once). */
195
+ retries?: number;
196
+ /** Error message of the retried-away first attempt, for transparency. */
197
+ retryOf?: string;
194
198
  frame?: number;
195
199
  /** Resolved permission tier for this run. */
196
200
  permission?: PermissionTier;