Skip to content

Commit ede1d29

Browse files
authored
fix: queue messages during command execution instead of losing them (#11140)
1 parent 8cf82cd commit ede1d29

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,13 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
612612
// - Task is busy (sendingDisabled)
613613
// - API request in progress (isStreaming)
614614
// - Queue has items (preserve message order during drain)
615-
if (sendingDisabled || isStreaming || messageQueue.length > 0) {
615+
// - Command is running (command_output) - user's message should be queued for AI, not sent to terminal
616+
if (
617+
sendingDisabled ||
618+
isStreaming ||
619+
messageQueue.length > 0 ||
620+
clineAskRef.current === "command_output"
621+
) {
616622
try {
617623
console.log("queueMessage", text, images)
618624
vscode.postMessage({ type: "queueMessage", text, images })
@@ -645,7 +651,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
645651
case "tool":
646652
case "browser_action_launch":
647653
case "command": // User can provide feedback to a tool or command use.
648-
case "command_output": // User can send input to command stdin.
649654
case "use_mcp_server":
650655
case "completion_result": // If this happens then the user has feedback for the completion result.
651656
case "resume_task":
@@ -1496,9 +1501,20 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14961501

14971502
useImperativeHandle(ref, () => ({
14981503
acceptInput: () => {
1504+
const hasInput = inputValue.trim() || selectedImages.length > 0
1505+
1506+
// Special case: during command_output, queue the message instead of
1507+
// triggering the primary button action (which would lose the message)
1508+
if (clineAskRef.current === "command_output" && hasInput) {
1509+
vscode.postMessage({ type: "queueMessage", text: inputValue.trim(), images: selectedImages })
1510+
setInputValue("")
1511+
setSelectedImages([])
1512+
return
1513+
}
1514+
14991515
if (enableButtons && primaryButtonText) {
15001516
handlePrimaryButtonClick(inputValue, selectedImages)
1501-
} else if (!sendingDisabled && !isProfileDisabled && (inputValue.trim() || selectedImages.length > 0)) {
1517+
} else if (!sendingDisabled && !isProfileDisabled && hasInput) {
15021518
handleSendMessage(inputValue, selectedImages)
15031519
}
15041520
},

webview-ui/src/components/chat/__tests__/ChatView.spec.tsx

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,68 @@ describe("ChatView - Message Queueing Tests", () => {
10811081
}),
10821082
)
10831083
})
1084+
1085+
it("queues messages during command_output state instead of losing them", async () => {
1086+
const { getByTestId } = renderChatView()
1087+
1088+
// Hydrate state with command_output ask (Proceed While Running state)
1089+
mockPostMessage({
1090+
clineMessages: [
1091+
{
1092+
type: "say",
1093+
say: "task",
1094+
ts: Date.now() - 2000,
1095+
text: "Initial task",
1096+
},
1097+
{
1098+
type: "ask",
1099+
ask: "command_output",
1100+
ts: Date.now(),
1101+
text: "",
1102+
partial: false, // Non-partial so buttons are enabled
1103+
},
1104+
],
1105+
})
1106+
1107+
// Wait for state to be updated - need to allow time for React effects to propagate
1108+
// (clineAsk state update -> clineAskRef.current update)
1109+
await waitFor(() => {
1110+
expect(getByTestId("chat-textarea")).toBeInTheDocument()
1111+
})
1112+
1113+
// Allow React effects to complete (clineAsk -> clineAskRef sync)
1114+
await act(async () => {
1115+
await new Promise((resolve) => setTimeout(resolve, 50))
1116+
})
1117+
1118+
// Clear message calls before simulating user input
1119+
vi.mocked(vscode.postMessage).mockClear()
1120+
1121+
// Simulate user typing and sending a message during command execution
1122+
const chatTextArea = getByTestId("chat-textarea")
1123+
const input = chatTextArea.querySelector("input")! as HTMLInputElement
1124+
1125+
await act(async () => {
1126+
fireEvent.change(input, { target: { value: "message during command execution" } })
1127+
fireEvent.keyDown(input, { key: "Enter", code: "Enter" })
1128+
})
1129+
1130+
// Verify that the message was queued (not lost via terminalOperation)
1131+
await waitFor(() => {
1132+
expect(vscode.postMessage).toHaveBeenCalledWith({
1133+
type: "queueMessage",
1134+
text: "message during command execution",
1135+
images: [],
1136+
})
1137+
})
1138+
1139+
// Verify it was NOT sent as terminalOperation (which would lose the message)
1140+
expect(vscode.postMessage).not.toHaveBeenCalledWith(
1141+
expect.objectContaining({
1142+
type: "terminalOperation",
1143+
}),
1144+
)
1145+
})
10841146
})
10851147

10861148
describe("ChatView - Context Condensing Indicator Tests", () => {

0 commit comments

Comments
 (0)