From d46c58b5c8511d3784201da936731e0772d35a97 Mon Sep 17 00:00:00 2001 From: can1357 Date: Mon, 3 Aug 2026 14:43:11 +0200 Subject: [PATCH] fix(tui): clear extension command image drafts --- .../src/modes/controllers/input-controller.ts | 48 +++++++++---------- .../input-controller-slash-history.test.ts | 9 +++- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index 3a841b835..610898c5e 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -752,24 +752,6 @@ export class InputController { } } - // Extension commands are local actions. Execute them before the normal - // submission path creates an optimistic user message; otherwise a - // consumed command remains rendered like a prompt sent to the model. - const extensionCommandSpace = text.indexOf(" "); - const isLocalExtensionCommand = - text.startsWith("/") && - runner?.getCommand(extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace)) !== - undefined; - if (isLocalExtensionCommand) { - this.ctx.editor.addToHistory(text); - try { - await this.ctx.session.prompt(text, { images: inputImages }); - } catch (error) { - this.ctx.editor.setText(text); - this.ctx.showError(error instanceof Error ? error.message : String(error)); - } - return; - } // Handle bash command (! for normal, !! for excluded from context) if (text.startsWith("!")) { @@ -820,6 +802,20 @@ export class InputController { this.ctx.queueCompactionMessage(text, "steer", images); return; } + // Extension commands are local actions. Execute them before the normal + // submission path creates an optimistic user message; otherwise a + // consumed command remains rendered like a prompt sent to the model. + if (this.#isLocalExtensionCommand(text)) { + this.ctx.editor.clearDraft(text); + try { + await this.ctx.session.prompt(text, { images: inputImages }); + } catch (error) { + this.ctx.editor.setText(text); + this.ctx.showError(error instanceof Error ? error.message : String(error)); + } + return; + } + // If streaming, use prompt() with steer behavior // This handles extension commands (execute immediately), prompt template expansion, and queueing @@ -933,14 +929,18 @@ export class InputController { * Local extension commands are consumed before reaching the shared session * title gate and must not name the conversation. */ - #maybeStartTitleGeneration(text: string): void { - const runner = this.ctx.session.extensionRunner; + #isLocalExtensionCommand(text: string): boolean { const extensionCommandSpace = text.indexOf(" "); - const isLocalExtensionCommand = + return ( text.startsWith("/") && - runner?.getCommand(extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace)) !== - undefined; - if (isLocalExtensionCommand) { + this.ctx.session.extensionRunner?.getCommand( + extensionCommandSpace === -1 ? text.slice(1) : text.slice(1, extensionCommandSpace), + ) !== undefined + ); + } + + #maybeStartTitleGeneration(text: string): void { + if (this.#isLocalExtensionCommand(text)) { return; } this.ctx.session.maybeStartTitleGeneration(text, () => { diff --git a/packages/coding-agent/test/input-controller-slash-history.test.ts b/packages/coding-agent/test/input-controller-slash-history.test.ts index 22d9ea5de..9fd672567 100644 --- a/packages/coding-agent/test/input-controller-slash-history.test.ts +++ b/packages/coding-agent/test/input-controller-slash-history.test.ts @@ -120,7 +120,7 @@ describe("input controller — slash command history (#3148)", () => { expect(addToHistory).not.toHaveBeenCalled(); }); - it("executes extension commands without rendering them as user prompts", async () => { + it("executes extension commands without rendering them as user prompts or retaining image drafts", async () => { const { ctx, editor, addToHistory, onInputCallback, prompt } = makeCtx(); Object.defineProperty(ctx.session, "extensionRunner", { value: { @@ -128,13 +128,18 @@ describe("input controller — slash command history (#3148)", () => { hasHandlers: () => false, }, }); + const image: ImageContent = { type: "image", data: "image-data", mimeType: "image/png" }; + editor.pendingImages = [image]; + editor.pendingImageLinks = ["file:///draft.png"]; controllerFor(ctx); await editor.onSubmit?.("/id"); - expect(prompt).toHaveBeenCalledWith("/id", { images: undefined }); + expect(prompt).toHaveBeenCalledWith("/id", { images: [image] }); expect(addToHistory).toHaveBeenCalledWith("/id"); expect(onInputCallback).not.toHaveBeenCalled(); + expect(editor.pendingImages).toEqual([]); + expect(editor.pendingImageLinks).toEqual([]); }); it("routes /queue through the yield-only follow-up queue while streaming", async () => {