pi-goal-x 0.30.1 → 0.30.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/README.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
<img src="pi-goal-x.png" alt="pi-goal-x logo" width="560">
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
|
+
<div align="center">
|
|
6
|
+
<a href="https://pi.dev/packages" target="_blank" rel="noopener noreferrer">
|
|
7
|
+
<picture>
|
|
8
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/badge-dark.svg">
|
|
9
|
+
<img src="assets/badge-light.svg" alt="TOP 1% of Pi coding agent packages: #45 of 5,380 by downloads, as of Aug 2026" width="420">
|
|
10
|
+
</picture>
|
|
11
|
+
</a>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
5
14
|
# pi-goal-x
|
|
6
15
|
|
|
7
16
|
`pi-goal-x` is a goal-management extension for [pi](https://github.com/earendil-works/pi-coding-agent).
|
|
@@ -533,14 +533,20 @@ export function registerGoalEvents(core: GoalCore): void {
|
|
|
533
533
|
return;
|
|
534
534
|
}
|
|
535
535
|
if (!networkErrorGoalId || !core.isActionableContinuationGoal(networkErrorGoalId)) return;
|
|
536
|
-
const
|
|
536
|
+
const recovery = loadGoalSettings(ctx.cwd).networkRecovery;
|
|
537
|
+
const policy = recovery
|
|
538
|
+
? { maxAttempts: recovery.maxAttempts, maxDelayMs: recovery.maxDelayMs }
|
|
539
|
+
: undefined;
|
|
540
|
+
const plan = core.runtime.scheduleNetworkErrorRetry(ctx, core.state.goal!, policy);
|
|
537
541
|
if (plan) {
|
|
542
|
+
const cap = plan.maxAttempts > 0 ? `/${plan.maxAttempts}` : ", unbounded";
|
|
538
543
|
ctx.ui.notify(
|
|
539
|
-
`Provider network error. Retrying the goal in ${Math.round(plan.delayMs / 1000)}s (recovery ${plan.attempt}
|
|
544
|
+
`Provider network error. Retrying the goal in ${Math.round(plan.delayMs / 1000)}s (recovery ${plan.attempt}${cap}).`,
|
|
540
545
|
"warning",
|
|
541
546
|
);
|
|
542
547
|
return;
|
|
543
548
|
}
|
|
549
|
+
// Only reachable under a configured bounded cap (maxAttempts > 0).
|
|
544
550
|
ctx.ui.notify(
|
|
545
551
|
"Provider network errors persisted after all recovery attempts. The goal remains active; resume it when the provider is healthy.",
|
|
546
552
|
"warning",
|
|
@@ -226,14 +226,36 @@ export function isErrorAssistantMessage(message: unknown): boolean {
|
|
|
226
226
|
return raw?.role === "assistant" && raw.stopReason === "error";
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Transient provider failures are safe for goal-level recovery backoff:
|
|
231
|
+
* Pi-declared network errors plus HTTP 5xx-style outages surfaced by
|
|
232
|
+
* providers (503 server_error, "Endpoint is unavailable", overloaded,
|
|
233
|
+
* gateway failures). Deliberately excludes 4xx client errors such as auth
|
|
234
|
+
* and malformed-request failures, which retrying cannot fix.
|
|
235
|
+
*/
|
|
236
|
+
const TRANSIENT_PROVIDER_ERROR_RE = new RegExp(
|
|
237
|
+
[
|
|
238
|
+
"\\bnetwork[_\\s-]?error\\b",
|
|
239
|
+
"\\bserver[_\\s]?error\\b",
|
|
240
|
+
"\\b(?:502|503|504|529)\\b",
|
|
241
|
+
"\\bservice unavailable\\b",
|
|
242
|
+
"\\bbad gateway\\b",
|
|
243
|
+
"\\bgateway timeout\\b",
|
|
244
|
+
"\\bupstream request failed\\b",
|
|
245
|
+
"\\bendpoint is unavailable\\b",
|
|
246
|
+
"\\boverloaded[_\\s]?error\\b",
|
|
247
|
+
].join("|"),
|
|
248
|
+
"i",
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
/** A transient provider failure is safe for the bounded goal backoff. */
|
|
230
252
|
export function isNetworkErrorAssistantMessage(message: unknown): boolean {
|
|
231
253
|
if (!isErrorAssistantMessage(message)) return false;
|
|
232
254
|
const raw = asRecord(message);
|
|
233
255
|
const details = [raw?.rawStopReason, raw?.errorMessage]
|
|
234
256
|
.filter((value): value is string => typeof value === "string")
|
|
235
257
|
.join(" ");
|
|
236
|
-
return
|
|
258
|
+
return TRANSIENT_PROVIDER_ERROR_RE.test(details);
|
|
237
259
|
}
|
|
238
260
|
|
|
239
261
|
export function isToolUseAssistantMessage(message: unknown): boolean {
|
|
@@ -12,7 +12,7 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
|
12
12
|
import type { GoalCheckpointDetailsV2, GoalRecord } from "./goal-record.ts";
|
|
13
13
|
import { checkpointTriggerPrompt } from "./prompts/goal-prompts.ts";
|
|
14
14
|
import { POST_STOP_ALLOWED_TOOLS } from "./goal-tool-names.ts";
|
|
15
|
-
import { networkErrorBackoffPlan, type NetworkErrorBackoffPlan } from "./network-error-backoff.ts";
|
|
15
|
+
import { networkErrorBackoffPlan, type NetworkErrorBackoffPlan, type NetworkErrorRecoveryPolicy } from "./network-error-backoff.ts";
|
|
16
16
|
|
|
17
17
|
export const CONTINUATION_IDLE_RETRY_MS = 50;
|
|
18
18
|
|
|
@@ -111,13 +111,13 @@ export class GoalRuntime {
|
|
|
111
111
|
* have failed. The counter stays in memory and is cleared on a successful
|
|
112
112
|
* turn or any user-owned cancellation path.
|
|
113
113
|
*/
|
|
114
|
-
scheduleNetworkErrorRetry(ctx: ExtensionContext, goal: GoalRecord): NetworkErrorBackoffPlan | null {
|
|
114
|
+
scheduleNetworkErrorRetry(ctx: ExtensionContext, goal: GoalRecord, policy?: NetworkErrorRecoveryPolicy): NetworkErrorBackoffPlan | null {
|
|
115
115
|
if (goal.status !== "active" || !goal.autoContinue || this.networkErrorRetryTimer) return null;
|
|
116
116
|
if (this.networkErrorRetryGoalId !== goal.id) {
|
|
117
117
|
this.networkErrorRetryGoalId = goal.id;
|
|
118
118
|
this.networkErrorRetryAttempt = 0;
|
|
119
119
|
}
|
|
120
|
-
const plan = networkErrorBackoffPlan(this.networkErrorRetryAttempt + 1);
|
|
120
|
+
const plan = networkErrorBackoffPlan(this.networkErrorRetryAttempt + 1, policy);
|
|
121
121
|
if (!plan) return null;
|
|
122
122
|
this.networkErrorRetryAttempt = plan.attempt;
|
|
123
123
|
this.networkErrorRetryTimer = setTimeout(() => {
|
|
@@ -62,6 +62,12 @@ export const DEFAULT_GOAL_KEYBINDINGS: GoalKeybindings = {
|
|
|
62
62
|
},
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Default ceiling for a single recovery delay; the escalation ladder
|
|
67
|
+
* plateaus here. Configurable via networkRecovery.maxDelayMs.
|
|
68
|
+
*/
|
|
69
|
+
export const DEFAULT_NETWORK_RECOVERY_MAX_DELAY_MS = 80_000;
|
|
70
|
+
|
|
65
71
|
export function formatGoalKeybinding(key: string): string {
|
|
66
72
|
return key.split("+").map((part) => ({
|
|
67
73
|
ctrl: "Ctrl",
|
|
@@ -108,6 +114,11 @@ export interface GoalSettingsResolvedShape {
|
|
|
108
114
|
hideUnfocusedBanner?: boolean;
|
|
109
115
|
/** Issue #26: opt-in read-only blocker Oracle configuration (sparse). */
|
|
110
116
|
oracle?: GoalOracleSettingsLayer;
|
|
117
|
+
/**
|
|
118
|
+
* Goal-level provider-error recovery backoff (sparse). maxAttempts 0 or
|
|
119
|
+
* unset = unbounded retry (default); maxDelayMs caps the delay plateau.
|
|
120
|
+
*/
|
|
121
|
+
networkRecovery?: GoalNetworkRecoverySettingsLayer;
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
// Resolved Oracle settings are attached to the resolved runtime shape below
|
|
@@ -126,7 +137,6 @@ export interface GoalOracleSettingsLayer {
|
|
|
126
137
|
maxFailedAttemptsPerBlocker?: number;
|
|
127
138
|
}
|
|
128
139
|
|
|
129
|
-
/** Resolved Oracle settings (concrete defaults). */
|
|
130
140
|
export interface ResolvedGoalOracleSettings {
|
|
131
141
|
enabled: boolean;
|
|
132
142
|
provider?: string;
|
|
@@ -136,6 +146,18 @@ export interface ResolvedGoalOracleSettings {
|
|
|
136
146
|
maxFailedAttemptsPerBlocker: number;
|
|
137
147
|
}
|
|
138
148
|
|
|
149
|
+
/** Sparse per-leaf network-recovery settings (maxAttempts 0 = unbounded). */
|
|
150
|
+
export interface GoalNetworkRecoverySettingsLayer {
|
|
151
|
+
maxAttempts?: number;
|
|
152
|
+
maxDelayMs?: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Resolved network-recovery settings (concrete defaults). */
|
|
156
|
+
export interface ResolvedGoalNetworkRecoverySettings {
|
|
157
|
+
maxAttempts: number;
|
|
158
|
+
maxDelayMs: number;
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
/**
|
|
140
162
|
* Sparse settings layer: exactly what a settings FILE may contain. Every
|
|
141
163
|
* field optional; explicit false/0 preserved so lower layers can override
|
|
@@ -151,6 +173,8 @@ export interface GoalSettingsLayer extends GoalSettingsResolvedShape {}
|
|
|
151
173
|
export interface ResolvedGoalSettingsShape extends GoalSettingsResolvedShape {
|
|
152
174
|
/** Issue #26: resolved opt-in blocker Oracle configuration. */
|
|
153
175
|
oracle?: ResolvedGoalOracleSettings;
|
|
176
|
+
/** Resolved goal-level provider-error recovery configuration. */
|
|
177
|
+
networkRecovery?: ResolvedGoalNetworkRecoverySettings;
|
|
154
178
|
}
|
|
155
179
|
|
|
156
180
|
export type ResolvedGoalSettings = ResolvedGoalSettingsShape;
|
|
@@ -306,8 +330,11 @@ const ALLOWED_SETTINGS_KEYS = new Set([
|
|
|
306
330
|
"keybindings",
|
|
307
331
|
"hideUnfocusedBanner",
|
|
308
332
|
"oracle",
|
|
333
|
+
"networkRecovery",
|
|
309
334
|
]);
|
|
310
335
|
|
|
336
|
+
const ALLOWED_NETWORK_RECOVERY_KEYS = new Set(["maxAttempts", "maxDelayMs"]);
|
|
337
|
+
|
|
311
338
|
const ALLOWED_ORACLE_KEYS = new Set([
|
|
312
339
|
"enabled",
|
|
313
340
|
"provider",
|
|
@@ -475,6 +502,31 @@ export function parseSettingsLayer(
|
|
|
475
502
|
layer.oracle = oracle;
|
|
476
503
|
break;
|
|
477
504
|
}
|
|
505
|
+
case "networkRecovery": {
|
|
506
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
507
|
+
diagnostics.push(diagnostic("invalid_nested_key", "networkRecovery must be an object", key));
|
|
508
|
+
break;
|
|
509
|
+
}
|
|
510
|
+
const nrRecord = value as Record<string, unknown>;
|
|
511
|
+
const nr: GoalNetworkRecoverySettingsLayer = {};
|
|
512
|
+
for (const [nKey, nValue] of Object.entries(nrRecord)) {
|
|
513
|
+
if (!ALLOWED_NETWORK_RECOVERY_KEYS.has(nKey)) {
|
|
514
|
+
diagnostics.push(diagnostic("unknown_key", `unknown networkRecovery key: ${nKey}`, `networkRecovery.${nKey}`));
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (nKey === "maxAttempts") {
|
|
518
|
+
const parsed = asNonNegativeInt(nValue);
|
|
519
|
+
if (parsed === undefined) diagnostics.push(diagnostic("invalid_value", "networkRecovery.maxAttempts must be an integer >= 0 (0 = unbounded)", `networkRecovery.${nKey}`));
|
|
520
|
+
else nr.maxAttempts = parsed;
|
|
521
|
+
} else {
|
|
522
|
+
const parsed = asNonNegativeInt(nValue);
|
|
523
|
+
if (parsed === undefined || parsed < 1_000) diagnostics.push(diagnostic("invalid_value", "networkRecovery.maxDelayMs must be an integer >= 1000", `networkRecovery.${nKey}`));
|
|
524
|
+
else nr.maxDelayMs = parsed;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (Object.keys(nr).length > 0) layer.networkRecovery = nr;
|
|
528
|
+
break;
|
|
529
|
+
}
|
|
478
530
|
default:
|
|
479
531
|
diagnostics.push(diagnostic("unknown_key", `unknown settings key: ${key}`, key));
|
|
480
532
|
break;
|
|
@@ -733,6 +785,20 @@ export function loadSettingsSnapshot(cwd: string, env: NodeJS.ProcessEnv = proce
|
|
|
733
785
|
defaultValue: 0,
|
|
734
786
|
envVar: "PI_GOAL_OBJECTIVE_MAX_CHARS",
|
|
735
787
|
}));
|
|
788
|
+
const networkRecoveryMaxAttempts = track("networkRecovery.maxAttempts", resolveLeaf<number>({
|
|
789
|
+
envValue: envInt("PI_GOAL_NETWORK_RECOVERY_MAX_ATTEMPTS"),
|
|
790
|
+
projectValue: project.layer.networkRecovery?.maxAttempts,
|
|
791
|
+
globalValue: global.layer.networkRecovery?.maxAttempts,
|
|
792
|
+
defaultValue: 0,
|
|
793
|
+
envVar: "PI_GOAL_NETWORK_RECOVERY_MAX_ATTEMPTS",
|
|
794
|
+
}));
|
|
795
|
+
const networkRecoveryMaxDelayMs = track("networkRecovery.maxDelayMs", resolveLeaf<number>({
|
|
796
|
+
envValue: envInt("PI_GOAL_NETWORK_RECOVERY_MAX_DELAY_MS"),
|
|
797
|
+
projectValue: project.layer.networkRecovery?.maxDelayMs,
|
|
798
|
+
globalValue: global.layer.networkRecovery?.maxDelayMs,
|
|
799
|
+
defaultValue: DEFAULT_NETWORK_RECOVERY_MAX_DELAY_MS,
|
|
800
|
+
envVar: "PI_GOAL_NETWORK_RECOVERY_MAX_DELAY_MS",
|
|
801
|
+
}));
|
|
736
802
|
|
|
737
803
|
const keybindings: GoalKeybindings = {
|
|
738
804
|
dashboard: {
|
|
@@ -768,6 +834,10 @@ export function loadSettingsSnapshot(cwd: string, env: NodeJS.ProcessEnv = proce
|
|
|
768
834
|
stallTimeoutMinutes,
|
|
769
835
|
objectiveMaxChars,
|
|
770
836
|
keybindings,
|
|
837
|
+
networkRecovery: {
|
|
838
|
+
maxAttempts: networkRecoveryMaxAttempts,
|
|
839
|
+
maxDelayMs: networkRecoveryMaxDelayMs,
|
|
840
|
+
},
|
|
771
841
|
oracle: {
|
|
772
842
|
enabled: oracleEnabled,
|
|
773
843
|
...(oracleProvider ? { provider: oracleProvider } : {}),
|
|
@@ -1076,6 +1146,13 @@ function buildPersistedLayer(settings: GoalSettings): Record<string, unknown> {
|
|
|
1076
1146
|
if (settings.autoSelectSingleGoal !== undefined) persisted.autoSelectSingleGoal = settings.autoSelectSingleGoal;
|
|
1077
1147
|
if (settings.auditorProjectResources !== undefined) persisted.auditorProjectResources = settings.auditorProjectResources;
|
|
1078
1148
|
if (settings.hideUnfocusedBanner !== undefined) persisted.hideUnfocusedBanner = settings.hideUnfocusedBanner;
|
|
1149
|
+
if ((settings as { networkRecovery?: ResolvedGoalNetworkRecoverySettings }).networkRecovery) {
|
|
1150
|
+
const nr = (settings as { networkRecovery?: ResolvedGoalNetworkRecoverySettings }).networkRecovery!;
|
|
1151
|
+
const o: Record<string, unknown> = {};
|
|
1152
|
+
if (nr.maxAttempts !== undefined) o.maxAttempts = nr.maxAttempts;
|
|
1153
|
+
if (nr.maxDelayMs !== undefined) o.maxDelayMs = nr.maxDelayMs;
|
|
1154
|
+
if (Object.keys(o).length > 0) persisted.networkRecovery = o;
|
|
1155
|
+
}
|
|
1079
1156
|
if ((settings as { oracle?: ResolvedGoalOracleSettings }).oracle) {
|
|
1080
1157
|
const o: Record<string, unknown> = {};
|
|
1081
1158
|
const so = (settings as { oracle?: ResolvedGoalOracleSettings }).oracle!;
|
|
@@ -1104,6 +1181,10 @@ export function envOverrideFor(key: keyof GoalSettings | "settingsFile", env: No
|
|
|
1104
1181
|
if (key === "disableTasks" && env.PI_GOAL_DISABLE_TASKS !== undefined) return "PI_GOAL_DISABLE_TASKS";
|
|
1105
1182
|
if (key === "disableContracts" && env.PI_GOAL_DISABLE_CONTRACTS !== undefined) return "PI_GOAL_DISABLE_CONTRACTS";
|
|
1106
1183
|
if (key === "objectiveMaxChars" && env.PI_GOAL_OBJECTIVE_MAX_CHARS !== undefined) return "PI_GOAL_OBJECTIVE_MAX_CHARS";
|
|
1184
|
+
if (key === "networkRecovery") {
|
|
1185
|
+
if (env.PI_GOAL_NETWORK_RECOVERY_MAX_ATTEMPTS !== undefined) return "PI_GOAL_NETWORK_RECOVERY_MAX_ATTEMPTS";
|
|
1186
|
+
if (env.PI_GOAL_NETWORK_RECOVERY_MAX_DELAY_MS !== undefined) return "PI_GOAL_NETWORK_RECOVERY_MAX_DELAY_MS";
|
|
1187
|
+
}
|
|
1107
1188
|
if (key === "settingsFile" && env[PI_GOAL_SETTINGS_FILE_ENV] !== undefined) return PI_GOAL_SETTINGS_FILE_ENV;
|
|
1108
1189
|
return null;
|
|
1109
1190
|
}
|
|
@@ -1128,6 +1209,8 @@ export function effectiveSettingsReport(cwd: string, env: NodeJS.ProcessEnv = pr
|
|
|
1128
1209
|
{ key: "hideUnfocusedBanner", label: "hide unfocused banner", format: () => String(snapshot.value.hideUnfocusedBanner) },
|
|
1129
1210
|
{ key: "stallTimeoutMinutes", label: "stall timeout (minutes)", format: () => String(snapshot.value.stallTimeoutMinutes) },
|
|
1130
1211
|
{ key: "objectiveMaxChars", label: "max objective length (0 = none)", format: () => String(snapshot.value.objectiveMaxChars) },
|
|
1212
|
+
{ key: "networkRecovery", label: "network recovery attempts (0 = unbounded)", format: () => String(snapshot.value.networkRecovery?.maxAttempts ?? 0) },
|
|
1213
|
+
{ key: "networkRecovery", label: "network recovery max delay (ms)", format: () => String(snapshot.value.networkRecovery?.maxDelayMs ?? DEFAULT_NETWORK_RECOVERY_MAX_DELAY_MS) },
|
|
1131
1214
|
{ key: "keybindings", label: "dashboard keybindings", format: () => `${snapshot.value.keybindings!.dashboard.toggleExpand}, ${snapshot.value.keybindings!.dashboard.scrollUp}, ${snapshot.value.keybindings!.dashboard.scrollDown}` },
|
|
1132
1215
|
];
|
|
1133
1216
|
for (const row of rows) {
|
|
@@ -1,25 +1,55 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Goal-level recovery after Pi has exhausted its provider retries.
|
|
3
3
|
*
|
|
4
4
|
* Pi owns the immediate request retries. These longer delays are deliberately
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* slow and only apply to transient provider failures, so an unavailable
|
|
6
|
+
* provider never becomes a rapid auto-continue loop.
|
|
7
|
+
*
|
|
8
|
+
* Default policy is UNBOUNDED: recovery keeps retrying forever on an
|
|
9
|
+
* escalating delay ladder that plateaus at `maxDelayMs`. A bounded cap can
|
|
10
|
+
* be configured via layered settings (`networkRecovery.maxAttempts`, where
|
|
11
|
+
* 0/unset means unbounded).
|
|
7
12
|
*/
|
|
8
13
|
|
|
9
14
|
export const NETWORK_ERROR_BACKOFF_DELAYS_MS = [5_000, 10_000, 20_000, 40_000, 80_000] as const;
|
|
10
15
|
|
|
16
|
+
/** Recovery policy: maxAttempts 0 = unbounded (default); delays plateau at maxDelayMs. */
|
|
17
|
+
export interface NetworkErrorRecoveryPolicy {
|
|
18
|
+
maxAttempts: number;
|
|
19
|
+
maxDelayMs: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_NETWORK_ERROR_RECOVERY_POLICY: NetworkErrorRecoveryPolicy = {
|
|
23
|
+
maxAttempts: 0,
|
|
24
|
+
maxDelayMs: NETWORK_ERROR_BACKOFF_DELAYS_MS[NETWORK_ERROR_BACKOFF_DELAYS_MS.length - 1]!,
|
|
25
|
+
};
|
|
26
|
+
|
|
11
27
|
export interface NetworkErrorBackoffPlan {
|
|
12
28
|
attempt: number;
|
|
29
|
+
/** 0 = unbounded. */
|
|
13
30
|
maxAttempts: number;
|
|
14
31
|
delayMs: number;
|
|
15
32
|
}
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
34
|
+
function rawLadderDelay(attempt: number): number {
|
|
35
|
+
const last = NETWORK_ERROR_BACKOFF_DELAYS_MS.length - 1;
|
|
36
|
+
return NETWORK_ERROR_BACKOFF_DELAYS_MS[Math.min(attempt - 1, last)]!;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Return the recovery plan for a one-based attempt under the given policy,
|
|
41
|
+
* or undefined once a configured bounded cap is exhausted. With the default
|
|
42
|
+
* unbounded policy this never returns undefined.
|
|
43
|
+
*/
|
|
44
|
+
export function networkErrorBackoffPlan(
|
|
45
|
+
attempt: number,
|
|
46
|
+
policy: NetworkErrorRecoveryPolicy = DEFAULT_NETWORK_ERROR_RECOVERY_POLICY,
|
|
47
|
+
): NetworkErrorBackoffPlan | undefined {
|
|
48
|
+
if (!Number.isInteger(attempt) || attempt < 1) return undefined;
|
|
49
|
+
if (policy.maxAttempts > 0 && attempt > policy.maxAttempts) return undefined;
|
|
20
50
|
return {
|
|
21
51
|
attempt,
|
|
22
|
-
maxAttempts:
|
|
23
|
-
delayMs:
|
|
52
|
+
maxAttempts: policy.maxAttempts,
|
|
53
|
+
delayMs: Math.min(rawLadderDelay(attempt), policy.maxDelayMs),
|
|
24
54
|
};
|
|
25
55
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goal-x",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.3",
|
|
4
4
|
"description": "Goal mode extension for pi: durable long-running objectives, five model tools, structured tasks, independent completion audit, Sisyphus mode, auto-continue, and a status overlay.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "pi-goal-x contributors",
|