diff --git a/packages/coding-agent/src/tools/bash-interactive.ts b/packages/coding-agent/src/tools/bash-interactive.ts index b65e90851..761cd7cdc 100644 --- a/packages/coding-agent/src/tools/bash-interactive.ts +++ b/packages/coding-agent/src/tools/bash-interactive.ts @@ -151,9 +151,23 @@ class BashInteractiveOverlayComponent implements Component { } #trimWriteQueue(): void { - const firstPending = this.#writing ? this.#writeOffset + 1 : this.#writeOffset; - while (this.#writeQueue.length - firstPending > MAX_LIVE_WRITE_QUEUE_CHUNKS) { - this.#writeQueue.splice(firstPending, 1); + // Compact the consumed prefix first: the queue only self-resets on a + // full drain, which never happens while a fast producer keeps a + // backlog alive, so already-written chunks must be released here to + // keep the retained array itself bounded. + if (this.#writeOffset > 0) { + this.#writeQueue.splice(0, this.#writeOffset); + this.#writeOffset = 0; + } + const firstPending = this.#writing ? 1 : 0; + const overflow = this.#writeQueue.length - firstPending - MAX_LIVE_WRITE_QUEUE_CHUNKS; + if (overflow > 0) { + this.#writeQueue.splice(firstPending, overflow); + // Dropped chunks can split an in-flight DCS/OSC/APC string (e.g. a + // sixel payload) across the gap; a stray string terminator is a + // no-op in the ground state but resynchronizes the parser if the + // terminator was dropped. + this.#writeQueue[firstPending] = `\u001b\\${this.#writeQueue[firstPending]}`; } }