From f31b060e150ee7d4cbfcd13e55e4e48c8b4a349e Mon Sep 17 00:00:00 2001 From: roboomp Date: Sun, 7 Jun 2026 09:24:20 +0000 Subject: [PATCH] fix(tui): applied follow-up slash commands Route follow-up shortcut submissions through the builtin slash-command dispatcher before queueing them as deferred prompts. Added regression coverage for /goal set submitted through InputController.handleFollowUp while a stream is active. Fixes #2038 --- packages/coding-agent/CHANGELOG.md | 4 ++++ .../src/modes/controllers/input-controller.ts | 12 +++++++++- .../test/input-controller-skill-queue.test.ts | 22 ++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 0243ed841..7a3932802 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed follow-up shortcut submission of builtin slash commands so `/goal set ...` applies goal mode instead of queueing as plain text. + ## [15.10.1] - 2026-06-07 ### Added diff --git a/packages/coding-agent/src/modes/controllers/input-controller.ts b/packages/coding-agent/src/modes/controllers/input-controller.ts index da83ea41b..d1e0ef6ad 100644 --- a/packages/coding-agent/src/modes/controllers/input-controller.ts +++ b/packages/coding-agent/src/modes/controllers/input-controller.ts @@ -589,7 +589,7 @@ export class InputController { /** Send editor text as a follow-up message (queued behind current stream). */ async handleFollowUp(): Promise { - const text = this.ctx.editor.getText().trim(); + let text = this.ctx.editor.getText().trim(); if (!text) return; // Compaction first: while compacting, free text gets queued via @@ -603,6 +603,16 @@ export class InputController { return; } + const slashResult = await executeBuiltinSlashCommand(text, { + ctx: this.ctx, + }); + if (slashResult === true) { + return; + } + if (typeof slashResult === "string") { + text = slashResult; + } + // Skill commands invoke through the custom-message path regardless of // which keybinding submitted them. Enter routes them as `steer`; // Ctrl+Enter (this handler) routes them as `followUp`. diff --git a/packages/coding-agent/test/input-controller-skill-queue.test.ts b/packages/coding-agent/test/input-controller-skill-queue.test.ts index 93dbba9ef..36154cc31 100644 --- a/packages/coding-agent/test/input-controller-skill-queue.test.ts +++ b/packages/coding-agent/test/input-controller-skill-queue.test.ts @@ -71,6 +71,8 @@ function createStubInputControllerContext(opts: { skillCommands: Map {}); + const prompt = vi.fn(async (_text: string, _options?: unknown) => {}); + const handleGoalModeCommand = vi.fn(async (_rest?: string) => {}); const updatePendingMessagesDisplay = vi.fn(); const requestRender = vi.fn(); const showError = vi.fn(); @@ -86,9 +88,12 @@ function createStubInputControllerContext(opts: { skillCommands: Map unknown) => fn(), } as unknown as InteractiveModeContext; - return { ctx, editor, enqueueCustomMessageDisplay, promptCustomMessage }; + return { ctx, editor, enqueueCustomMessageDisplay, prompt, promptCustomMessage, handleGoalModeCommand }; } describe("InputController #invokeSkillCommand (E1-E3)", () => { @@ -166,6 +171,21 @@ describe("InputController #invokeSkillCommand (E1-E3)", () => { expect(messageArg.details.__pendingDisplayTag).toBe("sk-test-0"); }); + it("E2b: streaming follow-up applies builtin slash commands instead of queueing them", async () => { + const { ctx, editor, prompt, handleGoalModeCommand } = createStubInputControllerContext({ + skillCommands, + isStreaming: true, + }); + + const controller = new InputController(ctx); + editor.setText("/goal set Ship the release"); + await controller.handleFollowUp(); + + expect(handleGoalModeCommand).toHaveBeenCalledWith("set Ship the release"); + expect(prompt).not.toHaveBeenCalled(); + expect(editor.getText()).toBe(""); + }); + it("E3: not streaming -> enqueueCustomMessageDisplay NOT called and tag absent", async () => { const { ctx, editor, enqueueCustomMessageDisplay, promptCustomMessage } = createStubInputControllerContext({ skillCommands,