Skip to content
Merged
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
22 changes: 19 additions & 3 deletions webview-ui/src/components/chat/ChatView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,13 @@ 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 (command_output) - user's message should be queued for AI, not sent to terminal
if (
sendingDisabled ||
isStreaming ||
messageQueue.length > 0 ||
clineAskRef.current === "command_output"
) {
try {
console.log("queueMessage", text, images)
vscode.postMessage({ type: "queueMessage", text, images })
Expand Down Expand Up @@ -645,7 +651,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
case "tool":
case "browser_action_launch":
case "command": // User can provide feedback to a tool or command use.
case "command_output": // User can send input to command stdin.
case "use_mcp_server":
case "completion_result": // If this happens then the user has feedback for the completion result.
case "resume_task":
Expand Down Expand Up @@ -1496,9 +1501,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro

useImperativeHandle(ref, () => ({
acceptInput: () => {
const hasInput = inputValue.trim() || selectedImages.length > 0

// Special case: during command_output, queue the message instead of
// triggering the primary button action (which would lose the message)
if (clineAskRef.current === "command_output" && hasInput) {
vscode.postMessage({ type: "queueMessage", text: inputValue.trim(), images: selectedImages })
setInputValue("")
setSelectedImages([])
return
}

if (enableButtons && primaryButtonText) {
handlePrimaryButtonClick(inputValue, selectedImages)
} else if (!sendingDisabled && !isProfileDisabled && (inputValue.trim() || selectedImages.length > 0)) {
} else if (!sendingDisabled && !isProfileDisabled && hasInput) {
handleSendMessage(inputValue, selectedImages)
}
},
Expand Down
62 changes: 62 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,68 @@ describe("ChatView - Message Queueing Tests", () => {
}),
)
})

it("queues messages during command_output state instead of losing them", async () => {
const { getByTestId } = renderChatView()

// Hydrate state with command_output ask (Proceed While Running state)
mockPostMessage({
clineMessages: [
{
type: "say",
say: "task",
ts: Date.now() - 2000,
text: "Initial task",
},
{
type: "ask",
ask: "command_output",
ts: Date.now(),
text: "",
partial: false, // Non-partial so buttons are enabled
},
],
})

// Wait for state to be updated - need to allow time for React effects to propagate
// (clineAsk state update -> clineAskRef.current update)
await waitFor(() => {
expect(getByTestId("chat-textarea")).toBeInTheDocument()
})

// Allow React effects to complete (clineAsk -> clineAskRef sync)
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 50))
})

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

// Simulate user typing and sending a message during command execution
const chatTextArea = getByTestId("chat-textarea")
const input = chatTextArea.querySelector("input")! as HTMLInputElement

await act(async () => {
fireEvent.change(input, { target: { value: "message during command execution" } })
fireEvent.keyDown(input, { key: "Enter", code: "Enter" })
})

// Verify that the message was queued (not lost via terminalOperation)
await waitFor(() => {
expect(vscode.postMessage).toHaveBeenCalledWith({
type: "queueMessage",
text: "message during command execution",
images: [],
})
})

// Verify it was NOT sent as terminalOperation (which would lose the message)
expect(vscode.postMessage).not.toHaveBeenCalledWith(
expect.objectContaining({
type: "terminalOperation",
}),
)
})
})

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