fix(tts): gated playback clearing to user messages

Moved vocalizer interruption from agent_start to user message_start so agentLoopContinue preserves queued speech.

Expanded regression coverage for continuation starts and user prompt boundaries.

Fixes #6375
This commit is contained in:
roboomp
2026-07-23 20:05:49 +00:00
parent bdceb660d3
commit 741725612b
3 changed files with 18 additions and 4 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
### Fixed
- Fixed Assistant-mode TTS playback aborting when an agent continued after a tool call in the same run; internal turn boundaries now preserve queued speech, while a new agent run still interrupts prior playback. ([#6375](https://github.com/can1357/oh-my-pi/issues/6375))
- Fixed Assistant-mode TTS playback aborting when an agent continued after a tool call or automatic follow-up in the same run; internal continuation boundaries now preserve queued speech, while a new user message still interrupts prior playback. ([#6375](https://github.com/can1357/oh-my-pi/issues/6375))
- Fixed credential-shaped tokens (GitHub/GitLab/OpenAI/Anthropic key patterns) being redacted from outbound provider requests even with `secrets.enabled` off; the pattern redaction now follows the `secrets.enabled` ("Hide Secrets") setting like the secret obfuscator.
- Fixed Ctrl-clicking a wrapped OAuth authorization URL opening only the clicked row's truncated fragment by preserving the complete hyperlink target on every rendered row.
- Fixed used-only absolute usage amounts across output surfaces: CLI now renders `$123.45 used`; the TUI shows a neutral, width-bounded amount instead of a pending/dotted/account-count placeholder; and ACP preserves `123.45 usd used` while suppressing duplicate window suffixes such as `— extra`. ([#5575](https://github.com/can1357/oh-my-pi/issues/5575))
@@ -405,7 +405,6 @@ export class EventController {
}
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
vocalizer.clear();
this.#toolTimelineComponents.clear();
this.#postToolAssistantComponents.clear();
this.#lastIntent = undefined;
@@ -450,6 +449,7 @@ export class EventController {
}
this.ctx.ui.requestRender();
} else if (event.message.role === "user") {
vocalizer.clear();
const textContent = this.ctx.getUserMessageText(event.message);
const imageBlocks =
typeof event.message.content === "string"
@@ -22,6 +22,11 @@ function createContext() {
transcriptMessageComponents: new WeakMap(),
pendingTools,
hideThinkingBlock: false,
getUserMessageText: () => "new prompt",
locallySubmittedUserSignatures: new Set<string>(),
addMessageToChat: vi.fn(),
editor: { setText: vi.fn() },
updatePendingMessagesDisplay: vi.fn(),
setWorkingMessage,
clearPinnedError: vi.fn(),
ensureLoadingAnimation,
@@ -62,15 +67,24 @@ describe("EventController aborted-turn working messages", () => {
resetSettingsForTest();
});
it("preserves playback across internal turns and clears it for a new agent run", async () => {
it("preserves playback across internal continuations and clears it for a user message", async () => {
const clear = vi.spyOn(vocalizer, "clear").mockImplementation(() => {});
const { ctx } = createContext();
const controller = new EventController(ctx);
await controller.handleEvent(AGENT_START);
await controller.handleEvent({ type: "turn_start" });
expect(clear).not.toHaveBeenCalled();
await controller.handleEvent(AGENT_START);
await controller.handleEvent({
type: "message_start",
message: {
role: "user",
content: [{ type: "text", text: "new prompt" }],
attribution: "user",
timestamp: Date.now(),
},
});
expect(clear).toHaveBeenCalledTimes(1);
});