diff --git a/src/auto-reply/commands-registry.shared.ts b/src/auto-reply/commands-registry.shared.ts index 7f189e753a45..341caed9a29c 100644 --- a/src/auto-reply/commands-registry.shared.ts +++ b/src/auto-reply/commands-registry.shared.ts @@ -46,14 +46,14 @@ type DefineChatCommandInput = { }; /** - * Keep simple model selections on fast client-side patch paths. Multi-token - * forms can carry runtime selectors or a prompt, so the server directive parser - * must own the full atomic transaction. + * Keep simple model selections on fast client-side patch paths. Semantic reset + * and multi-token forms require the server directive parser to own the full + * atomic transaction. */ export function shouldForwardModelCommandToServer(rawArgs: string): boolean { const args = rawArgs.trim(); const normalized = args.toLowerCase(); - return normalized === "list" || normalized === "status" || /\s/u.test(args); + return ["default", "list", "status"].includes(normalized) || /\s/u.test(args); } /** Defines one command with normalized aliases, scope, and argument parsing defaults. */ diff --git a/src/tui/tui-command-handlers.test.ts b/src/tui/tui-command-handlers.test.ts index 9c0e493ac162..93f268fe8770 100644 --- a/src/tui/tui-command-handlers.test.ts +++ b/src/tui/tui-command-handlers.test.ts @@ -2381,13 +2381,10 @@ describe("tui command handlers", () => { it.each([ { mode: "gateway", local: false, command: "/think default", field: "thinkingLevel" }, { mode: "gateway", local: false, command: "/fast default", field: "fastMode" }, - { mode: "gateway", local: false, command: "/model default", field: "model" }, { mode: "embedded", local: true, command: "/think default", field: "thinkingLevel" }, { mode: "embedded", local: true, command: "/fast default", field: "fastMode" }, - { mode: "embedded", local: true, command: "/model default", field: "model" }, { mode: "gateway", local: false, command: "/think inherit", field: "thinkingLevel" }, { mode: "embedded", local: true, command: "/fast reset", field: "fastMode" }, - { mode: "gateway", local: false, command: "/model DEFAULT", field: "model" }, ])( "clears the $field session override for $command in $mode mode", async ({ local, command, field }) => { @@ -2994,13 +2991,24 @@ describe("tui command handlers", () => { expect(closeOverlay).toHaveBeenCalledTimes(1); }); - it.each(["codex", "openclaw"])( - "forwards model/runtime transactions through the server directive path for %s", - async (runtime) => { + it.each([ + { local: false, command: "/model default" }, + { local: true, command: "/model default" }, + { local: false, command: "/model DEFAULT" }, + { + local: false, + command: "/model openai/gpt-5.6-luna --runtime codex continue with this model", + }, + { + local: false, + command: "/model openai/gpt-5.6-luna --runtime openclaw continue with this model", + }, + ])( + "forwards $command through the server directive path (local: $local)", + async ({ command, local }) => { const sendChat = vi.fn().mockResolvedValue({ status: "ok" }); const patchSession = vi.fn(); - const command = `/model openai/gpt-5.6-luna --runtime ${runtime} continue with this model`; - const { handleCommand } = createHarness({ sendChat, patchSession }); + const { handleCommand } = createHarness({ sendChat, patchSession, opts: { local } }); await handleCommand(command); diff --git a/ui/src/e2e/chat-continue-in-terminal.e2e.test.ts b/ui/src/e2e/chat-continue-in-terminal.e2e.test.ts index 8a8217770820..d04b5269ac70 100644 --- a/ui/src/e2e/chat-continue-in-terminal.e2e.test.ts +++ b/ui/src/e2e/chat-continue-in-terminal.e2e.test.ts @@ -173,7 +173,10 @@ suite.define(() => { }); await page.goto(controlUiSessionUrl(suite.server.baseUrl, sessionKey)); const activePane = page.locator("openclaw-chat-pane.chat-pane-cache__pane--active"); - await activePane.getByText("Mobile session menu proof.", { exact: true }).waitFor(); + await activePane + .getByRole("paragraph") + .filter({ hasText: /^Mobile session menu proof\.$/ }) + .waitFor(); const menuTrigger = activePane.getByRole("button", { name: "Actions for Terminal continuation", diff --git a/ui/src/pages/chat/chat-send.test.ts b/ui/src/pages/chat/chat-send.test.ts index edaa64d8fdf4..2d9879cbe643 100644 --- a/ui/src/pages/chat/chat-send.test.ts +++ b/ui/src/pages/chat/chat-send.test.ts @@ -3428,6 +3428,28 @@ describe("handleSendChat", () => { expect(refreshCurrentSessionTools).toHaveBeenCalledTimes(1); }); + it.each(["/model default", "/model DEFAULT"])( + "forwards semantic model reset commands through chat.send: %s", + async (command) => { + const host = makeChatHost({ + requestHandlers: { + "chat.send": { status: "started" }, + "sessions.patch": createResolvedModelPatch("default", "openai"), + }, + sessionKey: "main", + chatMessage: command, + }); + + await handleSendChat(host); + + expect(host.request).toHaveBeenCalledWith( + "chat.send", + expect.objectContaining({ message: command, sessionKey: "main" }), + ); + expect(host.request).not.toHaveBeenCalledWith("sessions.patch", expect.anything()); + }, + ); + it("queues local slash commands while the gateway client is unavailable", async () => { const host = makeChatHost({ client: null,