Merge PR #6220: fix(tts): prioritize playback interruption on Esc (@roboomp)

This commit is contained in:
can1357
2026-07-22 21:13:19 +02:00
3 changed files with 27 additions and 7 deletions
+1
View File
@@ -166,6 +166,7 @@
- Fixed Mnemopi consolidation re-storing cumulative session transcripts after incremental auto-retain, including after resuming a session ([#6058](https://github.com/can1357/oh-my-pi/issues/6058)).
- Fixed turn-ending Codex rate-limit errors remaining hidden behind the fullscreen Plan Review overlay and leaving its approval promise pending ([#6086](https://github.com/can1357/oh-my-pi/issues/6086)).
- Fixed prewalked subagents continuing to display their starting model after switching to the target model. ([#6083](https://github.com/can1357/oh-my-pi/issues/6083))
- Fixed Esc aborting an ongoing agent turn instead of overlapping TTS playback, leaving speech uninterruptible ([#6118](https://github.com/can1357/oh-my-pi/issues/6118)).
## [17.0.5] - 2026-07-18
@@ -308,6 +308,14 @@ export class InputController {
if (aborted) return;
}
if (vocalizer.isSpeaking()) {
// Playback from the completed response can overlap the next agent
// turn. Silence it before interrupting any ongoing main-turn work.
vocalizer.clear();
this.ctx.lastEscapeTime = 0;
return;
}
if (this.ctx.loopModeEnabled) {
this.ctx.pauseLoop();
if (this.ctx.session.isStreaming) {
@@ -360,13 +368,6 @@ export class InputController {
} else if (this.ctx.editor.getText().trim()) {
// Esc must not destroy an in-progress draft.
this.ctx.lastEscapeTime = 0;
} else if (vocalizer.isSpeaking()) {
// TTS buffers seconds of PCM past the streaming abort, so an Esc
// arriving after the model stopped would otherwise fall through to
// the double-Esc gesture while Kokoro reads on. Silence first;
// tree/branch stays reachable via a second Esc.
vocalizer.clear();
this.ctx.lastEscapeTime = 0;
} else {
// Double-interrupt with empty editor triggers /tree, /branch, or nothing based on setting
const action = settings.get("doubleEscapeAction");
@@ -669,6 +669,24 @@ describe("InputController escape behavior", () => {
expect(ctx.showUserMessageSelector).not.toHaveBeenCalled();
});
it("silences TTS before aborting an overlapping agent turn (#6118)", () => {
const clear = vi.spyOn(vocalizer, "clear").mockImplementation(() => {});
vi.spyOn(vocalizer, "isSpeaking").mockReturnValue(true);
const { ctx, editor, spies } = createContext();
const pauseLoop = vi.fn();
ctx.loopModeEnabled = true;
ctx.pauseLoop = pauseLoop;
mutableSessionState(ctx).isStreaming = true;
const controller = new InputController(ctx);
controller.setupKeyHandlers();
editor.onEscape?.();
expect(clear).toHaveBeenCalledTimes(1);
expect(pauseLoop).not.toHaveBeenCalled();
expect(spies.abort).not.toHaveBeenCalled();
});
it("silences a still-audible vocalizer on Esc instead of opening the tree selector (#4521)", () => {
const clear = vi.spyOn(vocalizer, "clear").mockImplementation(() => {});
const isSpeaking = vi.spyOn(vocalizer, "isSpeaking").mockReturnValue(true);