diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index b024f41d..aaa34e0d 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed interactive auto-retry status updates to show a live countdown during backoff instead of a static retry delay message ([#3187](https://github.com/badlogic/pi-mono/issues/3187)) + ## [0.67.2] - 2026-04-14 ### New Features diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index a1aaadaa..d71b9dd6 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -77,6 +77,7 @@ import { BashExecutionComponent } from "./components/bash-execution.js"; import { BorderedLoader } from "./components/bordered-loader.js"; import { BranchSummaryMessageComponent } from "./components/branch-summary-message.js"; import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message.js"; +import { CountdownTimer } from "./components/countdown-timer.js"; import { CustomEditor } from "./components/custom-editor.js"; import { CustomMessageComponent } from "./components/custom-message.js"; import { DaxnutsComponent } from "./components/daxnuts.js"; @@ -227,6 +228,7 @@ export class InteractiveMode { // Auto-retry state private retryLoader: Loader | undefined = undefined; + private retryCountdown: CountdownTimer | undefined = undefined; private retryEscapeHandler?: () => void; // Messages queued while compaction is running @@ -2337,6 +2339,10 @@ export class InteractiveMode { this.defaultEditor.onEscape = this.retryEscapeHandler; this.retryEscapeHandler = undefined; } + if (this.retryCountdown) { + this.retryCountdown.dispose(); + this.retryCountdown = undefined; + } if (this.retryLoader) { this.retryLoader.stop(); this.retryLoader = undefined; @@ -2593,12 +2599,24 @@ export class InteractiveMode { }; // Show retry indicator this.statusContainer.clear(); - const delaySeconds = Math.round(event.delayMs / 1000); + this.retryCountdown?.dispose(); + const retryMessage = (seconds: number) => + `Retrying (${event.attempt}/${event.maxAttempts}) in ${seconds}s... (${keyText("app.interrupt")} to cancel)`; this.retryLoader = new Loader( this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), - `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${keyText("app.interrupt")} to cancel)`, + retryMessage(Math.ceil(event.delayMs / 1000)), + ); + this.retryCountdown = new CountdownTimer( + event.delayMs, + this.ui, + (seconds) => { + this.retryLoader?.setMessage(retryMessage(seconds)); + }, + () => { + this.retryCountdown = undefined; + }, ); this.statusContainer.addChild(this.retryLoader); this.ui.requestRender(); @@ -2611,6 +2629,10 @@ export class InteractiveMode { this.defaultEditor.onEscape = this.retryEscapeHandler; this.retryEscapeHandler = undefined; } + if (this.retryCountdown) { + this.retryCountdown.dispose(); + this.retryCountdown = undefined; + } // Stop loader if (this.retryLoader) { this.retryLoader.stop();