Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
// - Task is busy (sendingDisabled)
// - API request in progress (isStreaming)
// - Queue has items (preserve message order during drain)
if (sendingDisabled || isStreaming || messageQueue.length > 0) {
// - Command is running (message should be queued until command completes) (#10675)
if (sendingDisabled || isStreaming || messageQueue.length > 0 || clineAsk === "command_output") {
try {
console.log("queueMessage", text, images)
vscode.postMessage({ type: "queueMessage", text, images })
Expand Down Expand Up @@ -641,7 +642,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
handleChatReset()
}
},
[handleChatReset, markFollowUpAsAnswered, sendingDisabled, isStreaming, messageQueue.length], // messagesRef and clineAskRef are stable
[handleChatReset, markFollowUpAsAnswered, sendingDisabled, isStreaming, messageQueue.length, clineAsk], // messagesRef and clineAskRef are stable
)

const handleSetChatBoxMessage = useCallback(
Expand Down
95 changes: 95 additions & 0 deletions webview-ui/src/components/chat/__tests__/ChatView.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,101 @@ describe("ChatView - Message Queueing Tests", () => {
}),
)
})

it("queues messages when command is running (command_output state) - Issue #10675", async () => {
const { getByTestId, getByText } = renderChatView()

// First hydrate state with initial task
mockPostMessage({
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
],
})

// Wait for component to render
await waitFor(() => {
expect(getByTestId("chat-textarea")).toBeInTheDocument()
})

// Clear any initial calls
vi.mocked(vscode.postMessage).mockClear()

// Add command_output ask (command is running, waiting for user input)
// During command_output: sendingDisabled=false, enableButtons=true, isStreaming=false
// Without the fix, messages would be sent directly (not queued) and could get lost
mockPostMessage({
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "command_output",
ts: Date.now(),
text: "",
partial: false, // Not partial - command is running
},
],
})

// Wait for the command_output state to be fully processed
// The button text "proceedWhileRunning.title" indicates clineAsk is set to "command_output"
// AND the button should NOT be disabled (enableButtons = true)
await waitFor(
() => {
const proceedButton = getByText("chat:proceedWhileRunning.title")
// Button should be enabled (not disabled) for command_output state
expect(proceedButton).not.toBeDisabled()
},
{ timeout: 2000 },
)

// Allow extra time for React to recreate the handleSendMessage callback with updated clineAsk
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 100))
})

// Clear message calls before simulating user input
vi.mocked(vscode.postMessage).mockClear()

// Simulate user sending a message while command is running
const chatTextArea = getByTestId("chat-textarea")
const input = chatTextArea.querySelector("input")! as HTMLInputElement

await act(async () => {
// Use fireEvent to properly trigger React's onChange handler
fireEvent.change(input, { target: { value: "user feedback during command execution" } })

// Simulate pressing Enter to send
fireEvent.keyDown(input, { key: "Enter", code: "Enter" })
})

// Verify that the message was queued, not sent as direct askResponse
// This is the fix for Issue #10675 - messages should be queued during command_output
await waitFor(() => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "queueMessage",
text: "user feedback during command execution",
images: [],
})
})

// Verify it was NOT sent as a direct askResponse (which would cause the message to "disappear")
expect(vscode.postMessage).not.toHaveBeenCalledWith(
expect.objectContaining({
type: "askResponse",
askResponse: "messageResponse",
}),
)
})
})

describe("ChatView - Context Condensing Indicator Tests", () => {
Expand Down
Loading