Skip to content

Commit 711c1a4

Browse files
committed
fix(extension): preserve headless tool approval checks
1 parent f9aa31a commit 711c1a4

3 files changed

Lines changed: 60 additions & 19 deletions

File tree

‎apps/vscode-e2e/src/suite/tools/use-mcp-tool.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,9 @@ suite("Roo Code use_mcp_tool Tool", function () {
6464
{
6565
mcpServers: {
6666
[FILESYSTEM_SERVER_NAME]: {
67-
command: process.execPath,
67+
command: process.env.npm_node_execpath ?? "node",
6868
args: [path.join(__dirname, "fixtures", "filesystem-mcp-server.js"), workspaceDir],
6969
env: {
70-
ELECTRON_RUN_AS_NODE: "1",
7170
MCP_TEST_READY_FILE: mcpServerReadyPath,
7271
},
7372
alwaysAllow: [

‎src/extension/__tests__/api.spec.ts‎

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type * as vscode from "vscode"
2-
import { IpcMessageType, TaskCommandName, type IpcMessage } from "@roo-code/types"
2+
import { IpcMessageType, TaskCommandName, type ClineMessage, type IpcMessage } from "@roo-code/types"
33

44
import { API } from "../api"
55
import type { ClineProvider } from "../../core/webview/ClineProvider"
6+
import { MessageQueueService } from "../../core/message-queue/MessageQueueService"
7+
import { Task } from "../../core/task/Task"
68

79
vi.mock("vscode")
810
vi.mock("../../core/webview/ClineProvider")
@@ -14,6 +16,33 @@ type TaskCommandHandler = (
1416

1517
let taskCommandHandler: TaskCommandHandler | undefined
1618

19+
type TaskTestAccess = {
20+
addToClineMessages: (message: ClineMessage) => Promise<void>
21+
}
22+
23+
const createStreamingTask = (provider: object) => {
24+
const task = Object.create(Task.prototype) as Task
25+
Object.assign(task, {
26+
abort: false,
27+
clineMessages: [],
28+
taskId: "task-1",
29+
instanceId: "instance-1",
30+
isStreaming: true,
31+
messageQueueService: new MessageQueueService(),
32+
providerRef: { deref: () => provider },
33+
addToClineMessages: vi.fn(async () => {}),
34+
saveClineMessages: vi.fn(async () => true),
35+
updateClineMessage: vi.fn(async () => {}),
36+
cancelAutoApprovalTimeout: vi.fn(),
37+
checkpointSave: vi.fn(async () => {}),
38+
emit: vi.fn(),
39+
})
40+
vi.spyOn(task as unknown as TaskTestAccess, "addToClineMessages").mockImplementation(async (message) => {
41+
task.clineMessages.push(message)
42+
})
43+
return task
44+
}
45+
1746
vi.mock("@roo-code/ipc", () => ({
1847
IpcServer: class {
1948
listen() {}
@@ -26,10 +55,11 @@ vi.mock("@roo-code/ipc", () => ({
2655
}))
2756

2857
describe("API.sendMessage", () => {
29-
it("enqueues directly when the current task is streaming", async () => {
58+
it("enqueues directly when the current webview task is streaming", async () => {
3059
const addMessage = vi.fn()
3160
const postMessageToWebview = vi.fn()
3261
const provider = {
62+
viewLaunched: true,
3363
getCurrentTask: vi.fn().mockReturnValue({
3464
isStreaming: true,
3565
messageQueueService: { addMessage },
@@ -51,29 +81,41 @@ describe("API.sendMessage", () => {
5181
expect(addMessage).toHaveBeenCalledWith("", images)
5282
})
5383

54-
it("delivers SendMessage commands through the IPC handler", async () => {
55-
const addMessage = vi.fn()
84+
it.each([
85+
["command", "npm publish"],
86+
["use_mcp_server", '{"server_name":"filesystem","tool_name":"write_file"}'],
87+
] as const)("does not approve a protected headless %s ask from queued IPC input", async (askType, askText) => {
5688
const appendLine = vi.fn()
5789
const provider = {
5890
context: {},
5991
cwd: "/test/cwd",
60-
getCurrentTask: vi.fn().mockReturnValue({
61-
isStreaming: true,
62-
messageQueueService: { addMessage },
63-
}),
92+
viewLaunched: false,
93+
getState: vi.fn().mockResolvedValue({ autoApprovalEnabled: false }),
94+
getCurrentTask: vi.fn(),
6495
getCurrentTaskStack: vi.fn().mockReturnValue([]),
6596
on: vi.fn(),
6697
} as unknown as ClineProvider
98+
const task = createStreamingTask(provider)
99+
vi.mocked(provider.getCurrentTask).mockReturnValue(task)
67100
new API({ appendLine } as unknown as vscode.OutputChannel, provider, "/tmp/roo-test.sock", true)
68101
const images = ["data:image/png;base64,image1data"]
102+
const executeProtectedTool = vi.fn()
103+
const ask = task.ask(askType, askText, false)
104+
await vi.waitFor(() => expect(task.clineMessages).toHaveLength(1))
69105

70106
await taskCommandHandler?.("client-1", {
71107
commandName: TaskCommandName.SendMessage,
72108
data: { text: "Use this before completing", images },
73109
})
110+
const result = await ask
111+
if (result.response === "yesButtonClicked") {
112+
executeProtectedTool()
113+
}
74114

75115
expect(appendLine).toHaveBeenCalledWith("[API] SendMessage -> Use this before completing")
76-
expect(addMessage).toHaveBeenCalledWith("Use this before completing", images)
116+
expect(result).toMatchObject({ response: "messageResponse", text: "Use this before completing", images })
117+
expect(task.messageQueueService.isEmpty()).toBe(true)
118+
expect(executeProtectedTool).not.toHaveBeenCalled()
77119
})
78120

79121
it("logs rejected SendMessage commands without rejecting the IPC handler", async () => {

‎src/extension/api.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -328,15 +328,9 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
328328
public async sendMessage(text?: string, images?: string[]) {
329329
const currentTask = this.sidebarProvider.getCurrentTask()
330330

331-
// Ensure steering input reaches the active task before it can finish.
332-
if (currentTask?.isStreaming) {
333-
currentTask.messageQueueService.addMessage(text ?? "", images)
334-
return
335-
}
336-
337331
// In headless/sandbox flows the webview may not be launched, so routing
338-
// through invoke=sendMessage drops the message. Deliver directly to the
339-
// task ask-response channel instead.
332+
// through invoke=sendMessage drops the message. Keep this path on the task
333+
// ask-response channel so queued input cannot approve protected tool asks.
340334
if (!this.sidebarProvider.viewLaunched) {
341335
if (!currentTask) {
342336
this.log("[API#sendMessage] no current task in headless mode; message dropped")
@@ -347,6 +341,12 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
347341
return
348342
}
349343

344+
// Ensure steering input reaches the active task before it can finish.
345+
if (currentTask?.isStreaming) {
346+
currentTask.messageQueueService.addMessage(text ?? "", images)
347+
return
348+
}
349+
350350
await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images })
351351
}
352352

0 commit comments

Comments
 (0)