@oh-my-pi/pi-coding-agent 17.2.10 → 17.2.12
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 +53 -0
- package/dist/{CHANGELOG-rhwzpexa.md → CHANGELOG-k9ghy5sn.md} +53 -0
- package/dist/cli.js +10775 -10762
- package/dist/types/advisor/runtime.d.ts +1 -1
- package/dist/types/capability/mcp.d.ts +11 -0
- package/dist/types/capability/skill.d.ts +6 -0
- package/dist/types/cli/command-help.d.ts +3 -0
- package/dist/types/commands/share.d.ts +25 -0
- package/dist/types/commit/agentic/index.d.ts +3 -1
- package/dist/types/commit/execute.d.ts +32 -0
- package/dist/types/commit/index.d.ts +2 -1
- package/dist/types/commit/pipeline.d.ts +7 -1
- package/dist/types/config/custom-models.d.ts +31 -0
- package/dist/types/config/model-config-values.d.ts +19 -0
- package/dist/types/config/model-patch.d.ts +93 -0
- package/dist/types/config/model-provider-discovery.d.ts +51 -0
- package/dist/types/config/model-registry.d.ts +9 -52
- package/dist/types/config/settings-schema.d.ts +1 -34
- package/dist/types/config/settings.d.ts +5 -3
- package/dist/types/debug/raw-sse-buffer.d.ts +9 -0
- package/dist/types/discovery/agent-plugin-format.d.ts +136 -0
- package/dist/types/discovery/agent-plugins.d.ts +1 -0
- package/dist/types/discovery/contained-path.d.ts +33 -0
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/eval/executor-base.d.ts +8 -2
- package/dist/types/eval/kernel-session-registry.d.ts +60 -0
- package/dist/types/export/share.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/loader.d.ts +7 -0
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +3 -2
- package/dist/types/extensibility/skills.d.ts +5 -0
- package/dist/types/lsp/diagnostics.d.ts +96 -0
- package/dist/types/lsp/index.d.ts +7 -128
- package/dist/types/lsp/servers.d.ts +72 -0
- package/dist/types/lsp/tool.d.ts +42 -0
- package/dist/types/lsp/workspace-diagnostics.d.ts +12 -0
- package/dist/types/lsp/writethrough.d.ts +33 -0
- package/dist/types/mcp/transports/header-policy.d.ts +46 -0
- package/dist/types/mcp/types.d.ts +15 -0
- package/dist/types/modes/components/agent-hub-projection.d.ts +2 -7
- package/dist/types/modes/components/agent-hub-renderer.d.ts +0 -7
- package/dist/types/modes/components/agent-hub.d.ts +2 -0
- package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
- package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -2
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/theme/color.d.ts +19 -0
- package/dist/types/modes/theme/loader.d.ts +19 -0
- package/dist/types/modes/theme/schema.d.ts +175 -0
- package/dist/types/modes/theme/symbols.d.ts +28 -0
- package/dist/types/modes/theme/theme-class.d.ts +244 -0
- package/dist/types/modes/theme/theme.d.ts +9 -280
- package/dist/types/modes/theme/tui-adapters.d.ts +13 -0
- package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
- package/dist/types/registry/agent-registry.d.ts +1 -3
- package/dist/types/secrets/index.d.ts +26 -2
- package/dist/types/secrets/message-transform.d.ts +40 -0
- package/dist/types/secrets/obfuscator.d.ts +0 -108
- package/dist/types/secrets/placeholder-scan.d.ts +95 -0
- package/dist/types/secrets/placeholder.d.ts +94 -0
- package/dist/types/secrets/replacement.d.ts +82 -0
- package/dist/types/session/agent-session-events.d.ts +2 -2
- package/dist/types/session/agent-session-types.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +2 -2
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/prewalk.d.ts +3 -1
- package/dist/types/session/session-handoff.d.ts +3 -3
- package/dist/types/session/session-manager.d.ts +32 -0
- package/dist/types/session/session-provider-boundary.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +8 -0
- package/dist/types/session/turn-recovery.d.ts +9 -4
- package/dist/types/slash-commands/builtin-collaboration.d.ts +2 -0
- package/dist/types/slash-commands/builtin-completions.d.ts +36 -0
- package/dist/types/slash-commands/builtin-control.d.ts +2 -0
- package/dist/types/slash-commands/builtin-lifecycle.d.ts +3 -0
- package/dist/types/slash-commands/builtin-marketplace.d.ts +11 -0
- package/dist/types/slash-commands/builtin-modes.d.ts +5 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -1
- package/dist/types/slash-commands/builtin-session.d.ts +2 -0
- package/dist/types/task/index.d.ts +2 -0
- package/dist/types/tools/gh-common.d.ts +69 -0
- package/dist/types/tools/gh-pr-checkout.d.ts +84 -0
- package/dist/types/tools/gh-pr-diff.d.ts +102 -0
- package/dist/types/tools/gh-run-watch.d.ts +78 -0
- package/dist/types/tools/gh-search.d.ts +61 -0
- package/dist/types/tools/gh-types.d.ts +340 -0
- package/dist/types/tools/gh-view.d.ts +88 -0
- package/dist/types/tools/gh.d.ts +4 -201
- package/dist/types/tools/read-archive.d.ts +16 -0
- package/dist/types/tools/read-format.d.ts +104 -0
- package/dist/types/tools/read-path-resolution.d.ts +19 -0
- package/dist/types/tools/read-pdf-images.d.ts +12 -0
- package/dist/types/tools/read-renderer.d.ts +24 -0
- package/dist/types/tools/read-selector.d.ts +27 -0
- package/dist/types/tools/read-sqlite.d.ts +16 -0
- package/dist/types/tools/read-summary.d.ts +19 -0
- package/dist/types/tools/read.d.ts +1 -23
- package/dist/types/tools/shell-tokenize.d.ts +18 -1
- package/dist/types/utils/changelog.d.ts +0 -5
- package/package.json +13 -13
- package/scripts/legacy-pi-virtual-module.ts +4 -1
- package/src/advisor/runtime.ts +12 -7
- package/src/capability/mcp.ts +11 -0
- package/src/capability/skill.ts +6 -0
- package/src/cli/command-help.ts +4 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli-commands.ts +5 -0
- package/src/commands/commit.ts +16 -3
- package/src/commands/share.ts +71 -0
- package/src/commit/agentic/index.ts +39 -21
- package/src/commit/execute.ts +56 -0
- package/src/commit/index.ts +2 -1
- package/src/commit/pipeline.ts +20 -7
- package/src/config/custom-models.ts +188 -0
- package/src/config/model-config-values.ts +128 -0
- package/src/config/model-discovery.ts +1 -1
- package/src/config/model-patch.ts +252 -0
- package/src/config/model-provider-discovery.ts +132 -0
- package/src/config/model-registry.ts +84 -704
- package/src/config/settings-schema.ts +2 -33
- package/src/config/settings.ts +53 -3
- package/src/debug/raw-sse-buffer.ts +20 -0
- package/src/discovery/agent-plugin-format.ts +551 -0
- package/src/discovery/agent-plugins.ts +341 -0
- package/src/discovery/claude-plugins.ts +21 -5
- package/src/discovery/contained-path.ts +75 -0
- package/src/discovery/helpers.ts +23 -9
- package/src/discovery/index.ts +1 -0
- package/src/discovery/omp-plugins.ts +20 -7
- package/src/edit/hashline/diff.ts +5 -1
- package/src/eval/executor-base.ts +39 -6
- package/src/eval/jl/executor.ts +82 -371
- package/src/eval/kernel-session-registry.ts +398 -0
- package/src/eval/py/executor.ts +53 -261
- package/src/eval/rb/executor.ts +36 -279
- package/src/exec/non-interactive-env.ts +1 -0
- package/src/export/share.ts +2 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +78 -14
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +12 -0
- package/src/extensibility/plugins/marketplace/manager.ts +2 -1
- package/src/extensibility/shared-events.ts +3 -2
- package/src/extensibility/skills.ts +9 -0
- package/src/internal-urls/skill-protocol.ts +14 -0
- package/src/internal-urls/vault-protocol.ts +2 -2
- package/src/launch/client.ts +11 -1
- package/src/launch/protocol.ts +7 -2
- package/src/lsp/diagnostics.ts +516 -0
- package/src/lsp/index.ts +20 -2819
- package/src/lsp/servers.ts +296 -0
- package/src/lsp/tool.ts +1352 -0
- package/src/lsp/workspace-diagnostics.ts +170 -0
- package/src/lsp/writethrough.ts +561 -0
- package/src/main.ts +3 -2
- package/src/mcp/config.ts +3 -0
- package/src/mcp/manager.ts +12 -9
- package/src/mcp/transports/header-policy.ts +95 -0
- package/src/mcp/transports/http.ts +35 -52
- package/src/mcp/transports/sse.ts +20 -27
- package/src/mcp/types.ts +15 -0
- package/src/modes/acp/acp-agent.ts +15 -4
- package/src/modes/components/agent-dashboard.ts +2 -0
- package/src/modes/components/agent-hub-projection.ts +2 -2
- package/src/modes/components/agent-hub-renderer.ts +3 -7
- package/src/modes/components/agent-hub.ts +5 -0
- package/src/modes/components/agent-transcript-viewer.ts +3 -0
- package/src/modes/components/chat-transcript-builder.ts +3 -0
- package/src/modes/components/custom-editor.ts +0 -9
- package/src/modes/components/status-line/component.ts +1 -0
- package/src/modes/components/status-line/segments.ts +6 -4
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/tool-execution.ts +13 -16
- package/src/modes/components/tree-selector.ts +62 -3
- package/src/modes/controllers/command-controller.ts +8 -2
- package/src/modes/controllers/event-controller.ts +15 -15
- package/src/modes/controllers/input-controller.ts +20 -2
- package/src/modes/controllers/selector-controller.ts +1 -0
- package/src/modes/theme/color.ts +133 -0
- package/src/modes/theme/loader.ts +178 -0
- package/src/modes/theme/schema.ts +263 -0
- package/src/modes/theme/symbols.ts +1000 -0
- package/src/modes/theme/theme-class.ts +611 -0
- package/src/modes/theme/theme.ts +27 -2453
- package/src/modes/theme/tui-adapters.ts +279 -0
- package/src/modes/utils/transcript-render-helpers.ts +4 -2
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/registry/agent-registry.ts +2 -2
- package/src/sdk.ts +6 -49
- package/src/secrets/index.ts +51 -6
- package/src/secrets/message-transform.ts +287 -0
- package/src/secrets/obfuscator.ts +39 -1303
- package/src/secrets/placeholder-scan.ts +506 -0
- package/src/secrets/placeholder.ts +309 -0
- package/src/secrets/replacement.ts +216 -0
- package/src/session/agent-session-events.ts +2 -2
- package/src/session/agent-session-types.ts +6 -0
- package/src/session/agent-session.ts +146 -21
- package/src/session/indexed-session-storage.ts +7 -2
- package/src/session/messages.ts +1 -0
- package/src/session/prewalk.ts +57 -17
- package/src/session/session-advisors.ts +32 -34
- package/src/session/session-context.ts +3 -5
- package/src/session/session-handoff.ts +39 -16
- package/src/session/session-manager.ts +67 -3
- package/src/session/session-provider-boundary.ts +3 -6
- package/src/session/session-tools.ts +37 -3
- package/src/session/todo-tracker.ts +11 -1
- package/src/session/turn-recovery.ts +115 -84
- package/src/slash-commands/builtin-collaboration.ts +504 -0
- package/src/slash-commands/builtin-completions.ts +291 -0
- package/src/slash-commands/builtin-control.ts +78 -0
- package/src/slash-commands/builtin-lifecycle.ts +506 -0
- package/src/slash-commands/builtin-marketplace.ts +567 -0
- package/src/slash-commands/builtin-modes.ts +518 -0
- package/src/slash-commands/builtin-registry.ts +21 -2996
- package/src/slash-commands/builtin-session.ts +592 -0
- package/src/task/executor.ts +17 -14
- package/src/task/index.ts +21 -6
- package/src/tools/bash-skill-urls.ts +15 -0
- package/src/tools/bash.ts +10 -11
- package/src/tools/debug.ts +1 -1
- package/src/tools/gh-common.ts +288 -0
- package/src/tools/gh-pr-checkout.ts +679 -0
- package/src/tools/gh-pr-diff.ts +473 -0
- package/src/tools/gh-run-watch.ts +1015 -0
- package/src/tools/gh-search.ts +500 -0
- package/src/tools/gh-types.ts +379 -0
- package/src/tools/gh-view.ts +612 -0
- package/src/tools/gh.ts +109 -3821
- package/src/tools/jtd-to-json-schema.ts +123 -21
- package/src/tools/read-archive.ts +209 -0
- package/src/tools/read-format.ts +593 -0
- package/src/tools/read-path-resolution.ts +36 -0
- package/src/tools/read-pdf-images.ts +250 -0
- package/src/tools/read-renderer.ts +289 -0
- package/src/tools/read-selector.ts +84 -0
- package/src/tools/read-sqlite.ts +215 -0
- package/src/tools/read-summary.ts +199 -0
- package/src/tools/read.ts +82 -1824
- package/src/tools/shell-tokenize.ts +99 -1
- package/src/utils/changelog.ts +1 -1
- package/src/utils/title-generator.ts +3 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/zai.ts +12 -3
|
@@ -28,7 +28,7 @@ import type { ModelRegistry } from "../config/model-registry";
|
|
|
28
28
|
import { formatModelStringWithRouting, resolveModelOverride } from "../config/model-resolver";
|
|
29
29
|
|
|
30
30
|
import type { Settings } from "../config/settings";
|
|
31
|
-
import type {
|
|
31
|
+
import type { RetryErrorUpdate } from "../extensibility/shared-events";
|
|
32
32
|
import emptyStopRetryTemplate from "../prompts/system/empty-stop-retry.md" with { type: "text" };
|
|
33
33
|
import thinkingLoopRedirectTemplate from "../prompts/system/thinking-loop-redirect.md" with { type: "text" };
|
|
34
34
|
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
@@ -161,7 +161,7 @@ export interface TurnRecoveryOptions {
|
|
|
161
161
|
initialRetryFallback?: InitialRetryFallbackState;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
type
|
|
164
|
+
type PendingRetryError = {
|
|
165
165
|
entryId: string;
|
|
166
166
|
persistenceKey: string;
|
|
167
167
|
recovery: AssistantRetryRecoveryKind;
|
|
@@ -184,7 +184,7 @@ export class TurnRecovery {
|
|
|
184
184
|
#retryResolve: (() => void) | undefined;
|
|
185
185
|
#activeRetryFallback: ActiveRetryFallbackState | undefined;
|
|
186
186
|
#usageReserveApprovedSelector: string | undefined;
|
|
187
|
-
#
|
|
187
|
+
#pendingRetryErrors: PendingRetryError[] = [];
|
|
188
188
|
#usageLimitOutcomes = new WeakMap<AssistantMessage, Promise<UsageLimitOutcome>>();
|
|
189
189
|
#emptyStopRetryCount = 0;
|
|
190
190
|
#unexpectedStopRetryCount = 0;
|
|
@@ -250,14 +250,17 @@ export class TurnRecovery {
|
|
|
250
250
|
role: this.#activeRetryFallback.role,
|
|
251
251
|
});
|
|
252
252
|
}
|
|
253
|
-
const
|
|
253
|
+
const retryErrors = await this.#markPendingRetryErrors({
|
|
254
|
+
status: "recovered",
|
|
255
|
+
supersedingMessage: message,
|
|
256
|
+
});
|
|
254
257
|
await this.#host.emitSessionEvent({
|
|
255
258
|
type: "auto_retry_end",
|
|
256
259
|
success: true,
|
|
257
260
|
attempt: this.#retryAttempt,
|
|
258
|
-
|
|
261
|
+
retryErrors,
|
|
259
262
|
});
|
|
260
|
-
this.#
|
|
263
|
+
this.#clearPendingRetryErrors();
|
|
261
264
|
this.#retryAttempt = 0;
|
|
262
265
|
}
|
|
263
266
|
|
|
@@ -272,7 +275,7 @@ export class TurnRecovery {
|
|
|
272
275
|
attempt,
|
|
273
276
|
finalError: message.errorMessage,
|
|
274
277
|
});
|
|
275
|
-
this.#
|
|
278
|
+
this.#clearPendingRetryErrors();
|
|
276
279
|
}
|
|
277
280
|
|
|
278
281
|
/** Persists an otherwise skipped terminal empty error turn. */
|
|
@@ -305,8 +308,13 @@ export class TurnRecovery {
|
|
|
305
308
|
return this.#runRecoveryCompactionWithRollback(reason, message, allowDefer, options);
|
|
306
309
|
}
|
|
307
310
|
|
|
308
|
-
/**
|
|
309
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Restores the configured primary after fallback cooldown expiry.
|
|
313
|
+
* @returns true when the active model was actually switched back to the
|
|
314
|
+
* primary, so callers can re-run the pre-send context-fit check against the
|
|
315
|
+
* reverted (possibly smaller) window before issuing the next request.
|
|
316
|
+
*/
|
|
317
|
+
maybeRestoreRetryFallbackPrimary(): Promise<boolean> {
|
|
310
318
|
return this.#maybeRestoreRetryFallbackPrimary();
|
|
311
319
|
}
|
|
312
320
|
|
|
@@ -381,8 +389,8 @@ export class TurnRecovery {
|
|
|
381
389
|
}
|
|
382
390
|
}
|
|
383
391
|
|
|
384
|
-
#
|
|
385
|
-
this.#
|
|
392
|
+
#clearPendingRetryErrors(): void {
|
|
393
|
+
this.#pendingRetryErrors = [];
|
|
386
394
|
}
|
|
387
395
|
|
|
388
396
|
/**
|
|
@@ -432,7 +440,7 @@ export class TurnRecovery {
|
|
|
432
440
|
return parts.join("; ");
|
|
433
441
|
}
|
|
434
442
|
|
|
435
|
-
async #
|
|
443
|
+
async #recordPendingRetryError(
|
|
436
444
|
message: AssistantMessage,
|
|
437
445
|
id: number,
|
|
438
446
|
options: { switchedCredential: boolean; switchedModel: boolean; delayMs: number },
|
|
@@ -451,11 +459,11 @@ export class TurnRecovery {
|
|
|
451
459
|
break;
|
|
452
460
|
}
|
|
453
461
|
if (!branchEntry) return;
|
|
454
|
-
if (this.#
|
|
462
|
+
if (this.#pendingRetryErrors.some(error => error.entryId === branchEntry.id)) return;
|
|
455
463
|
const rateLimited = AIError.is(id, AIError.Flag.UsageLimit);
|
|
456
464
|
const recovery = this.#retryRecoveryKind(id, options.switchedCredential, options.switchedModel, options.delayMs);
|
|
457
465
|
const note = this.#retryRecoveryNote(recovery, rateLimited);
|
|
458
|
-
this.#
|
|
466
|
+
this.#pendingRetryErrors.push({
|
|
459
467
|
entryId: branchEntry.id,
|
|
460
468
|
persistenceKey,
|
|
461
469
|
recovery,
|
|
@@ -464,24 +472,17 @@ export class TurnRecovery {
|
|
|
464
472
|
});
|
|
465
473
|
}
|
|
466
474
|
|
|
467
|
-
async #
|
|
468
|
-
|
|
475
|
+
async #markPendingRetryErrors(
|
|
476
|
+
completion: { status: "recovered"; supersedingMessage: AssistantMessage } | { status: "superseded" },
|
|
477
|
+
): Promise<RetryErrorUpdate[]> {
|
|
478
|
+
if (this.#pendingRetryErrors.length === 0) return [];
|
|
469
479
|
const branch = this.#host.sessionManager.getBranch();
|
|
470
480
|
const branchById = new Map<string, SessionEntry>();
|
|
471
481
|
for (const entry of branch) {
|
|
472
482
|
branchById.set(entry.id, entry);
|
|
473
483
|
}
|
|
474
|
-
const
|
|
475
|
-
const
|
|
476
|
-
timestamp: supersedingMessage.timestamp,
|
|
477
|
-
provider: supersedingMessage.provider,
|
|
478
|
-
model: supersedingMessage.model,
|
|
479
|
-
};
|
|
480
|
-
if (supersedingMessage.responseId) {
|
|
481
|
-
supersededBy.responseId = supersedingMessage.responseId;
|
|
482
|
-
}
|
|
483
|
-
const recoveredErrors: RecoveredRetryError[] = [];
|
|
484
|
-
for (const pending of this.#pendingRecoveredRetryErrors) {
|
|
484
|
+
const retryErrors: RetryErrorUpdate[] = [];
|
|
485
|
+
for (const pending of this.#pendingRetryErrors) {
|
|
485
486
|
let entry = branchById.get(pending.entryId);
|
|
486
487
|
if (entry?.type !== "message" || entry.message.role !== "assistant") {
|
|
487
488
|
entry = branch
|
|
@@ -495,27 +496,45 @@ export class TurnRecovery {
|
|
|
495
496
|
);
|
|
496
497
|
}
|
|
497
498
|
if (entry?.type !== "message" || entry.message.role !== "assistant") continue;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
499
|
+
let retryRecovery: AssistantRetryRecovery;
|
|
500
|
+
if (completion.status === "recovered") {
|
|
501
|
+
retryRecovery = {
|
|
502
|
+
kind: "auto-retry",
|
|
503
|
+
status: "recovered",
|
|
504
|
+
attempt: pending.attempt,
|
|
505
|
+
recoveredAt: new Date().toISOString(),
|
|
506
|
+
recovery: pending.recovery,
|
|
507
|
+
note: pending.note,
|
|
508
|
+
supersededBy: {
|
|
509
|
+
timestamp: completion.supersedingMessage.timestamp,
|
|
510
|
+
...(completion.supersedingMessage.responseId === undefined
|
|
511
|
+
? {}
|
|
512
|
+
: { responseId: completion.supersedingMessage.responseId }),
|
|
513
|
+
provider: completion.supersedingMessage.provider,
|
|
514
|
+
model: completion.supersedingMessage.model,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
} else {
|
|
518
|
+
retryRecovery = {
|
|
519
|
+
kind: "auto-retry",
|
|
520
|
+
status: "superseded",
|
|
521
|
+
attempt: pending.attempt,
|
|
522
|
+
recovery: pending.recovery,
|
|
523
|
+
note: pending.note,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
507
526
|
entry.message.retryRecovery = retryRecovery;
|
|
508
|
-
|
|
527
|
+
retryErrors.push({
|
|
509
528
|
entryId: entry.id,
|
|
510
529
|
persistenceKey: pending.persistenceKey,
|
|
511
530
|
note: retryRecovery.note,
|
|
512
531
|
retryRecovery,
|
|
513
532
|
});
|
|
514
533
|
}
|
|
515
|
-
if (
|
|
534
|
+
if (retryErrors.length > 0) {
|
|
516
535
|
await this.#host.sessionManager.rewriteEntries();
|
|
517
536
|
}
|
|
518
|
-
return
|
|
537
|
+
return retryErrors;
|
|
519
538
|
}
|
|
520
539
|
|
|
521
540
|
async #handleEmptyAssistantStop(assistantMessage: AssistantMessage): Promise<boolean> {
|
|
@@ -547,7 +566,7 @@ export class TurnRecovery {
|
|
|
547
566
|
attempt: this.#retryAttempt > 0 ? this.#retryAttempt : attempts,
|
|
548
567
|
finalError,
|
|
549
568
|
});
|
|
550
|
-
this.#
|
|
569
|
+
this.#clearPendingRetryErrors();
|
|
551
570
|
this.#retryAttempt = 0;
|
|
552
571
|
this.resolveRetry();
|
|
553
572
|
// A zero-content turn carries no transcript value, while its provider usage
|
|
@@ -905,9 +924,9 @@ export class TurnRecovery {
|
|
|
905
924
|
}
|
|
906
925
|
|
|
907
926
|
/**
|
|
908
|
-
* Check if an error is retryable (transient errors
|
|
927
|
+
* Check if an error is retryable (transient errors, usage limits, or
|
|
928
|
+
* account-scoped policy denials that can rotate credentials).
|
|
909
929
|
* Context overflow is NOT retryable (handled by compaction instead).
|
|
910
|
-
* Usage-limit errors are retryable because the retry handler performs credential switching.
|
|
911
930
|
*/
|
|
912
931
|
isRetryableError(message: AssistantMessage): boolean {
|
|
913
932
|
if (message.stopReason !== "error") return false;
|
|
@@ -918,12 +937,11 @@ export class TurnRecovery {
|
|
|
918
937
|
const contextWindow = this.#host.model()?.contextWindow ?? 0;
|
|
919
938
|
if (AIError.isContextOverflow(message, contextWindow)) return false;
|
|
920
939
|
|
|
921
|
-
//
|
|
922
|
-
//
|
|
923
|
-
//
|
|
924
|
-
// discarded and replayed.
|
|
940
|
+
// Credential rotation and classifier fallbacks are safe only before
|
|
941
|
+
// committed text, images, tool calls, or server tools. Thinking-only
|
|
942
|
+
// output remains replay-safe.
|
|
925
943
|
if (this.#hasReplayUnsafeOutput(message)) return false;
|
|
926
|
-
if (this.isClassifierRefusal(message)) return true;
|
|
944
|
+
if (AIError.is(id, AIError.Flag.AccountPolicy) || this.isClassifierRefusal(message)) return true;
|
|
927
945
|
return AIError.retriable(id);
|
|
928
946
|
}
|
|
929
947
|
|
|
@@ -1433,10 +1451,10 @@ export class TurnRecovery {
|
|
|
1433
1451
|
return true;
|
|
1434
1452
|
}
|
|
1435
1453
|
|
|
1436
|
-
async #maybeRestoreRetryFallbackPrimary(): Promise<
|
|
1437
|
-
if (!this.#activeRetryFallback) return;
|
|
1438
|
-
if (this.#activeRetryFallback.pinned) return;
|
|
1439
|
-
if (this.#getRetryFallbackRevertPolicy() !== "cooldown-expiry") return;
|
|
1454
|
+
async #maybeRestoreRetryFallbackPrimary(): Promise<boolean> {
|
|
1455
|
+
if (!this.#activeRetryFallback) return false;
|
|
1456
|
+
if (this.#activeRetryFallback.pinned) return false;
|
|
1457
|
+
if (this.#getRetryFallbackRevertPolicy() !== "cooldown-expiry") return false;
|
|
1440
1458
|
|
|
1441
1459
|
const {
|
|
1442
1460
|
originalSelector: originalSelectorRaw,
|
|
@@ -1446,19 +1464,19 @@ export class TurnRecovery {
|
|
|
1446
1464
|
const originalSelector = parseRetryFallbackSelector(originalSelectorRaw, this.#host.modelRegistry);
|
|
1447
1465
|
if (!originalSelector) {
|
|
1448
1466
|
this.clearActiveRetryFallback();
|
|
1449
|
-
return;
|
|
1467
|
+
return false;
|
|
1450
1468
|
}
|
|
1451
1469
|
|
|
1452
1470
|
const currentModel = this.#host.model();
|
|
1453
|
-
if (!currentModel) return;
|
|
1471
|
+
if (!currentModel) return false;
|
|
1454
1472
|
const currentSelector = formatRetryFallbackSelector(currentModel, this.#host.thinkingLevel());
|
|
1455
1473
|
if (currentSelector === originalSelector.raw) {
|
|
1456
1474
|
if (!this.isRetryFallbackSelectorSuppressed(originalSelector)) {
|
|
1457
1475
|
this.clearActiveRetryFallback();
|
|
1458
1476
|
}
|
|
1459
|
-
return;
|
|
1477
|
+
return false;
|
|
1460
1478
|
}
|
|
1461
|
-
if (this.isRetryFallbackSelectorSuppressed(originalSelector)) return;
|
|
1479
|
+
if (this.isRetryFallbackSelectorSuppressed(originalSelector)) return false;
|
|
1462
1480
|
|
|
1463
1481
|
const resolvedPrimary = resolveModelOverride(
|
|
1464
1482
|
[originalSelector.raw],
|
|
@@ -1467,9 +1485,9 @@ export class TurnRecovery {
|
|
|
1467
1485
|
);
|
|
1468
1486
|
const primaryModel =
|
|
1469
1487
|
resolvedPrimary.model ?? this.#host.modelRegistry.find(originalSelector.provider, originalSelector.id);
|
|
1470
|
-
if (!primaryModel) return;
|
|
1488
|
+
if (!primaryModel) return false;
|
|
1471
1489
|
const apiKey = await this.#host.modelRegistry.getApiKey(primaryModel, this.#host.sessionId());
|
|
1472
|
-
if (!apiKey) return;
|
|
1490
|
+
if (!apiKey) return false;
|
|
1473
1491
|
|
|
1474
1492
|
const currentThinkingLevel = this.#host.configuredThinkingLevel();
|
|
1475
1493
|
const thinkingToApply =
|
|
@@ -1480,6 +1498,7 @@ export class TurnRecovery {
|
|
|
1480
1498
|
this.#host.settings.getStorage()?.recordModelUsage(primarySelector);
|
|
1481
1499
|
this.#host.setThinkingLevel(thinkingToApply);
|
|
1482
1500
|
this.clearActiveRetryFallback();
|
|
1501
|
+
return true;
|
|
1483
1502
|
}
|
|
1484
1503
|
|
|
1485
1504
|
#parseRetryAfterMsFromError(errorMessage: string): number | undefined {
|
|
@@ -1581,27 +1600,28 @@ export class TurnRecovery {
|
|
|
1581
1600
|
|
|
1582
1601
|
const errorMessage = message.errorMessage || "Unknown error";
|
|
1583
1602
|
const id = this.#classifyRetryMessage(message);
|
|
1603
|
+
const rateLimitReason = parseRateLimitReason(errorMessage);
|
|
1584
1604
|
const staleOpenAIResponsesReplayError = AIError.is(id, AIError.Flag.StaleResponsesItem);
|
|
1605
|
+
const accountPolicyDenial = AIError.is(id, AIError.Flag.AccountPolicy);
|
|
1585
1606
|
const recordedUsageLimitOutcome = await this.#usageLimitOutcomes.get(message);
|
|
1586
1607
|
const parsedRetryAfterMs = this.#parseRetryAfterMsFromError(errorMessage);
|
|
1587
1608
|
let delayMs = staleOpenAIResponsesReplayError
|
|
1588
1609
|
? 0
|
|
1589
1610
|
: calculateRetryBackoffDelayMs(retrySettings.baseDelayMs, this.#retryAttempt);
|
|
1590
|
-
//
|
|
1591
|
-
//
|
|
1592
|
-
//
|
|
1593
|
-
//
|
|
1594
|
-
//
|
|
1595
|
-
//
|
|
1596
|
-
// body merely mentions concurrency is still a usage limit (handled below),
|
|
1597
|
-
// so gate on the flag matching the rotation decision.
|
|
1611
|
+
// Transient rate/concurrency caps stay on the same credential, but must
|
|
1612
|
+
// honor their reason-specific windows. The default exponential base
|
|
1613
|
+
// (≈500ms, capped at 8s) otherwise re-hits the cap and burns the retry
|
|
1614
|
+
// budget before either window can clear. An explicit provider
|
|
1615
|
+
// retry-after is authoritative in both directions, so the heuristic
|
|
1616
|
+
// window only applies when the error carries no parsed timing.
|
|
1598
1617
|
if (
|
|
1599
1618
|
!staleOpenAIResponsesReplayError &&
|
|
1600
1619
|
!AIError.is(id, AIError.Flag.UsageLimit) &&
|
|
1601
|
-
|
|
1620
|
+
parsedRetryAfterMs === undefined &&
|
|
1621
|
+
(rateLimitReason === "CONCURRENT_LIMIT" || rateLimitReason === "RATE_LIMIT_EXCEEDED")
|
|
1602
1622
|
) {
|
|
1603
|
-
const
|
|
1604
|
-
if (
|
|
1623
|
+
const reasonBackoffMs = calculateRateLimitBackoffMs(rateLimitReason);
|
|
1624
|
+
if (reasonBackoffMs > delayMs) delayMs = reasonBackoffMs;
|
|
1605
1625
|
}
|
|
1606
1626
|
let switchedCredential = false;
|
|
1607
1627
|
let switchedModel = false;
|
|
@@ -1652,6 +1672,14 @@ export class TurnRecovery {
|
|
|
1652
1672
|
const currentSelector = currentModel
|
|
1653
1673
|
? formatRetryFallbackSelector(currentModel, this.#host.thinkingLevel())
|
|
1654
1674
|
: undefined;
|
|
1675
|
+
if (accountPolicyDenial && currentModel) {
|
|
1676
|
+
switchedCredential = await this.#host.modelRegistry.authStorage.rotateSessionCredential(
|
|
1677
|
+
currentModel.provider,
|
|
1678
|
+
this.#host.sessionId(),
|
|
1679
|
+
{ error: errorMessage, modelId: currentModel.id },
|
|
1680
|
+
);
|
|
1681
|
+
if (switchedCredential) delayMs = 0;
|
|
1682
|
+
}
|
|
1655
1683
|
if (!staleOpenAIResponsesReplayError && !switchedCredential && currentSelector) {
|
|
1656
1684
|
// A refusal chain stops at the retry budget: the exhausted-attempt
|
|
1657
1685
|
// last resort is for provider failures, not classifier decisions.
|
|
@@ -1676,26 +1704,29 @@ export class TurnRecovery {
|
|
|
1676
1704
|
}
|
|
1677
1705
|
|
|
1678
1706
|
if (retryBudgetExhausted) {
|
|
1679
|
-
if (!switchedModel) {
|
|
1707
|
+
if (!switchedModel && !switchedCredential) {
|
|
1708
|
+
const attempt = this.#retryAttempt - 1;
|
|
1709
|
+
message.errorMessage = `Retry budget exhausted after ${attempt} ${attempt === 1 ? "retry" : "retries"}: ${errorMessage}`;
|
|
1680
1710
|
await this.persistTerminalEmptyErrorTurn(message);
|
|
1681
|
-
|
|
1682
|
-
// final failure and reset.
|
|
1711
|
+
const retryErrors = await this.#markPendingRetryErrors({ status: "superseded" });
|
|
1683
1712
|
await this.#host.emitSessionEvent({
|
|
1684
1713
|
type: "auto_retry_end",
|
|
1685
1714
|
success: false,
|
|
1686
|
-
attempt
|
|
1687
|
-
finalError:
|
|
1715
|
+
attempt,
|
|
1716
|
+
finalError: errorMessage,
|
|
1717
|
+
retryErrors,
|
|
1688
1718
|
});
|
|
1689
|
-
this.#
|
|
1719
|
+
this.#clearPendingRetryErrors();
|
|
1690
1720
|
this.#retryAttempt = 0;
|
|
1691
1721
|
this.resolveRetry(); // Resolve so waitForRetry() completes
|
|
1692
1722
|
return false;
|
|
1693
1723
|
}
|
|
1694
|
-
//
|
|
1695
|
-
//
|
|
1696
|
-
|
|
1724
|
+
// A fallback model gets a fresh retry budget. Credential rotation
|
|
1725
|
+
// instead keeps the cumulative attempt count while bypassing the
|
|
1726
|
+
// same-route budget: every distinct account must be tried first.
|
|
1727
|
+
if (switchedModel) this.#retryAttempt = 1;
|
|
1697
1728
|
}
|
|
1698
|
-
if (classifierRefusal && !switchedModel) {
|
|
1729
|
+
if ((classifierRefusal || accountPolicyDenial) && !switchedCredential && !switchedModel) {
|
|
1699
1730
|
// A prior attempt in this saga already announced `auto_retry_start`
|
|
1700
1731
|
// (retryAttempt was incremented for each call to this method, so > 1
|
|
1701
1732
|
// means at least one earlier attempt started the loop) but this
|
|
@@ -1711,7 +1742,7 @@ export class TurnRecovery {
|
|
|
1711
1742
|
attempt: this.#retryAttempt - 1,
|
|
1712
1743
|
finalError: errorMessage,
|
|
1713
1744
|
});
|
|
1714
|
-
this.#
|
|
1745
|
+
this.#clearPendingRetryErrors();
|
|
1715
1746
|
}
|
|
1716
1747
|
this.#retryAttempt = 0;
|
|
1717
1748
|
this.resolveRetry();
|
|
@@ -1736,7 +1767,7 @@ export class TurnRecovery {
|
|
|
1736
1767
|
attempt: this.#retryAttempt - 1,
|
|
1737
1768
|
finalError: errorMessage,
|
|
1738
1769
|
});
|
|
1739
|
-
this.#
|
|
1770
|
+
this.#clearPendingRetryErrors();
|
|
1740
1771
|
}
|
|
1741
1772
|
this.#retryAttempt = 0;
|
|
1742
1773
|
this.resolveRetry();
|
|
@@ -1761,12 +1792,12 @@ export class TurnRecovery {
|
|
|
1761
1792
|
attempt,
|
|
1762
1793
|
finalError: `Provider requested ${delayMs}ms wait, exceeds retry.maxDelayMs (${maxDelayMs}ms). Original error: ${errorMessage}`,
|
|
1763
1794
|
});
|
|
1764
|
-
this.#
|
|
1795
|
+
this.#clearPendingRetryErrors();
|
|
1765
1796
|
this.resolveRetry();
|
|
1766
1797
|
return false;
|
|
1767
1798
|
}
|
|
1768
1799
|
|
|
1769
|
-
await this.#
|
|
1800
|
+
await this.#recordPendingRetryError(message, id, { switchedCredential, switchedModel, delayMs });
|
|
1770
1801
|
|
|
1771
1802
|
await this.#host.emitSessionEvent({
|
|
1772
1803
|
type: "auto_retry_start",
|
|
@@ -1808,7 +1839,7 @@ export class TurnRecovery {
|
|
|
1808
1839
|
attempt,
|
|
1809
1840
|
finalError: "Retry cancelled",
|
|
1810
1841
|
});
|
|
1811
|
-
this.#
|
|
1842
|
+
this.#clearPendingRetryErrors();
|
|
1812
1843
|
this.resolveRetry();
|
|
1813
1844
|
return false;
|
|
1814
1845
|
}
|
|
@@ -1881,7 +1912,7 @@ export class TurnRecovery {
|
|
|
1881
1912
|
attempt,
|
|
1882
1913
|
finalError: `Retry continuation failed locally: ${localError}. Original error: ${message.errorMessage ?? "Unknown error"}`,
|
|
1883
1914
|
});
|
|
1884
|
-
this.#
|
|
1915
|
+
this.#clearPendingRetryErrors();
|
|
1885
1916
|
this.resolveRetry();
|
|
1886
1917
|
}
|
|
1887
1918
|
|