diff --git a/CHANGELOG.md b/CHANGELOG.md index 4629a4415ab..2487de0f09f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ Docs: https://docs.openclaw.ai - Cron/Gateway: keep `cron.list` and `cron.status` responsive during startup catch-up by avoiding a long-held cron lock while missed jobs execute. (#23106) Thanks @jayleekr. - Gateway/Config reload: compare array-valued config paths structurally during diffing so unchanged `memory.qmd.paths` and `memory.qmd.scope.rules` no longer trigger false restart-required reloads. (#23185) Thanks @rex05ai. - TUI/Input: enable multiline-paste burst coalescing on macOS Terminal.app and iTerm so pasted blocks no longer submit line-by-line as separate messages. (#18809) Thanks @fwends. +- TUI/Status: request immediate renders after setting `sending`/`waiting` activity states so in-flight runs always show visible progress indicators instead of appearing idle until completion. (#21549) Thanks @13Guinness. - Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane. - Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize. - Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07. diff --git a/src/tui/tui-command-handlers.test.ts b/src/tui/tui-command-handlers.test.ts index 8e9f45d6cff..28c38f40ec3 100644 --- a/src/tui/tui-command-handlers.test.ts +++ b/src/tui/tui-command-handlers.test.ts @@ -2,6 +2,55 @@ import { describe, expect, it, vi } from "vitest"; import { createCommandHandlers } from "./tui-command-handlers.js"; describe("tui command handlers", () => { + it("renders the sending indicator before chat.send resolves", async () => { + let resolveSend: ((value: { runId: string }) => void) | null = null; + const sendChat = vi.fn( + () => + new Promise<{ runId: string }>((resolve) => { + resolveSend = resolve; + }), + ); + const addUser = vi.fn(); + const requestRender = vi.fn(); + const setActivityStatus = vi.fn(); + + const { handleCommand } = createCommandHandlers({ + client: { sendChat } as never, + chatLog: { addUser, addSystem: vi.fn() } as never, + tui: { requestRender } as never, + opts: {}, + state: { + currentSessionKey: "agent:main:main", + activeChatRunId: null, + sessionInfo: {}, + } as never, + deliverDefault: false, + openOverlay: vi.fn(), + closeOverlay: vi.fn(), + refreshSessionInfo: vi.fn(), + loadHistory: vi.fn(), + setSession: vi.fn(), + refreshAgents: vi.fn(), + abortActive: vi.fn(), + setActivityStatus, + formatSessionKey: vi.fn(), + applySessionInfoFromPatch: vi.fn(), + noteLocalRunId: vi.fn(), + }); + + const pending = handleCommand("/context"); + await Promise.resolve(); + + expect(setActivityStatus).toHaveBeenCalledWith("sending"); + const sendingOrder = setActivityStatus.mock.invocationCallOrder[0] ?? 0; + const renderOrders = requestRender.mock.invocationCallOrder; + expect(renderOrders.some((order) => order > sendingOrder)).toBe(true); + + resolveSend?.({ runId: "r1" }); + await pending; + expect(setActivityStatus).toHaveBeenCalledWith("waiting"); + }); + it("forwards unknown slash commands to the gateway", async () => { const sendChat = vi.fn().mockResolvedValue({ runId: "r1" }); const addUser = vi.fn(); diff --git a/src/tui/tui-command-handlers.ts b/src/tui/tui-command-handlers.ts index bc39a1ed244..1695169bcdd 100644 --- a/src/tui/tui-command-handlers.ts +++ b/src/tui/tui-command-handlers.ts @@ -470,6 +470,7 @@ export function createCommandHandlers(context: CommandHandlerContext) { noteLocalRunId(runId); state.activeChatRunId = runId; setActivityStatus("sending"); + tui.requestRender(); await client.sendChat({ sessionKey: state.currentSessionKey, message: text, @@ -479,6 +480,7 @@ export function createCommandHandlers(context: CommandHandlerContext) { runId, }); setActivityStatus("waiting"); + tui.requestRender(); } catch (err) { if (state.activeChatRunId) { forgetLocalRunId?.(state.activeChatRunId); @@ -486,8 +488,8 @@ export function createCommandHandlers(context: CommandHandlerContext) { state.activeChatRunId = null; chatLog.addSystem(`send failed: ${String(err)}`); setActivityStatus("error"); + tui.requestRender(); } - tui.requestRender(); }; return {