diff --git a/src/tui/tui-busy-notice.test.ts b/src/tui/tui-busy-notice.test.ts new file mode 100644 index 000000000000..e646a5ce7720 --- /dev/null +++ b/src/tui/tui-busy-notice.test.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from "vitest"; +import { normalizeTestText } from "../../test/helpers/normalize-text.js"; +import { ChatLog } from "./components/chat-log.js"; +import { addBlockedChatSubmitNotice, TUI_AGENT_BUSY_MESSAGE } from "./tui-busy-notice.js"; + +describe("addBlockedChatSubmitNotice", () => { + it("coalesces repeated busy submit notices", () => { + const chatLog = new ChatLog(20); + + addBlockedChatSubmitNotice(chatLog); + addBlockedChatSubmitNotice(chatLog); + addBlockedChatSubmitNotice(chatLog); + + const rendered = normalizeTestText(chatLog.render(120).join("\n")); + expect(chatLog.children.length).toBe(1); + expect(rendered).toContain(`${TUI_AGENT_BUSY_MESSAGE} x3`); + }); +}); diff --git a/src/tui/tui-busy-notice.ts b/src/tui/tui-busy-notice.ts new file mode 100644 index 000000000000..8286fd070a09 --- /dev/null +++ b/src/tui/tui-busy-notice.ts @@ -0,0 +1,8 @@ +import type { ChatLog } from "./components/chat-log.js"; + +export const TUI_AGENT_BUSY_MESSAGE = + "agent is busy — press Esc to abort before sending a new message"; + +export function addBlockedChatSubmitNotice(chatLog: Pick) { + chatLog.addSystem(TUI_AGENT_BUSY_MESSAGE, { coalesceConsecutive: true }); +} diff --git a/src/tui/tui-command-handlers.test.ts b/src/tui/tui-command-handlers.test.ts index 5d346a5ae025..7b51245105dc 100644 --- a/src/tui/tui-command-handlers.test.ts +++ b/src/tui/tui-command-handlers.test.ts @@ -1122,6 +1122,7 @@ describe("tui command handlers", () => { expect(addPendingUser).not.toHaveBeenCalled(); expect(addSystem).toHaveBeenCalledWith( "agent is busy — press Esc to abort before sending a new message", + { coalesceConsecutive: true }, ); }); @@ -1179,6 +1180,7 @@ describe("tui command handlers", () => { expect(addUser).not.toHaveBeenCalled(); expect(addSystem).toHaveBeenCalledWith( "agent is busy — press Esc to abort before sending a new message", + { coalesceConsecutive: true }, ); }); @@ -1210,6 +1212,7 @@ describe("tui command handlers", () => { expect(addUser).not.toHaveBeenCalled(); expect(addSystem).toHaveBeenCalledWith( "agent is busy — press Esc to abort before sending a new message", + { coalesceConsecutive: true }, ); }); @@ -1465,9 +1468,7 @@ describe("tui command handlers", () => { await handleCommand("/usage reset"); - expect(patchSession).toHaveBeenCalledWith( - expect.objectContaining({ responseUsage: null }), - ); + expect(patchSession).toHaveBeenCalledWith(expect.objectContaining({ responseUsage: null })); expect(addSystem).toHaveBeenCalledWith("usage footer: reset to default"); // Both stale local values must be cleared so the toggle/display is not stale // until refreshSessionInfo() repopulates the inherited default. @@ -1492,9 +1493,7 @@ describe("tui command handlers", () => { await handleCommand("/usage"); - expect(patchSession).toHaveBeenCalledWith( - expect.objectContaining({ responseUsage: "full" }), - ); + expect(patchSession).toHaveBeenCalledWith(expect.objectContaining({ responseUsage: "full" })); expect(addSystem).toHaveBeenCalledWith("usage footer: full"); }); }); diff --git a/src/tui/tui-command-handlers.ts b/src/tui/tui-command-handlers.ts index 9f5e16fabceb..6c56b0e9be53 100644 --- a/src/tui/tui-command-handlers.ts +++ b/src/tui/tui-command-handlers.ts @@ -26,6 +26,7 @@ import { createSettingsList, } from "./components/selectors.js"; import type { TuiBackend, TuiSessionMutationResult } from "./tui-backend.js"; +import { addBlockedChatSubmitNotice } from "./tui-busy-notice.js"; import { sanitizeRenderableText } from "./tui-formatters.js"; import { TUI_RECENT_SESSIONS_ACTIVE_MINUTES, @@ -782,7 +783,7 @@ export function createCommandHandlers(context: CommandHandlerContext) { state.pendingOptimisticUserMessage || (opts.local !== true && state.activeChatRunId)) ) { - chatLog.addSystem("agent is busy — press Esc to abort before sending a new message"); + addBlockedChatSubmitNotice(chatLog); tui.requestRender(); return; } diff --git a/src/tui/tui.ts b/src/tui/tui.ts index 369636c5db17..ff7dadee137f 100644 --- a/src/tui/tui.ts +++ b/src/tui/tui.ts @@ -39,6 +39,7 @@ import { CustomEditor } from "./components/custom-editor.js"; import { resolveLocalRunShutdownGraceMs } from "./local-run-shutdown.js"; import { editorTheme, theme } from "./theme/theme.js"; import type { TuiBackend } from "./tui-backend.js"; +import { addBlockedChatSubmitNotice } from "./tui-busy-notice.js"; import { createCommandHandlers } from "./tui-command-handlers.js"; import { createEventHandlers } from "./tui-event-handlers.js"; import { @@ -1434,7 +1435,7 @@ export async function runTui(opts: RunTuiOptions): Promise { message, }); const notifyBlockedChatSubmit = () => { - chatLog.addSystem("agent is busy — press Esc to abort before sending a new message"); + addBlockedChatSubmitNotice(chatLog); tui.requestRender(); }; const submitHandler = createEditorSubmitHandler({