Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 0fc4684

Browse files
committed
feat: add DeleteQueuedMessage IPC command for queue removal
1 parent bfbfaf6 commit 0fc4684

6 files changed

Lines changed: 140 additions & 1 deletion

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"roo-cline": minor
3+
"@roo-code/types": minor
4+
"@roo-code/ipc": minor
5+
---
6+
7+
Add DeleteQueuedMessage IPC command to allow removing queued messages from the extension's message queue via the IPC protocol

packages/ipc/src/ipc-client.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
108108
})
109109
}
110110

111+
public deleteQueuedMessage(messageId: string) {
112+
this.sendCommand({
113+
commandName: TaskCommandName.DeleteQueuedMessage,
114+
data: messageId,
115+
})
116+
}
117+
111118
public sendMessage(message: IpcMessage) {
112119
ipc.of[this._id]?.emit("message", message)
113120
}

packages/types/src/__tests__/ipc.test.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,19 @@ describe("IPC Types", () => {
66
expect(TaskCommandName.ResumeTask).toBe("ResumeTask")
77
})
88

9+
it("should include DeleteQueuedMessage command", () => {
10+
expect(TaskCommandName.DeleteQueuedMessage).toBe("DeleteQueuedMessage")
11+
})
12+
913
it("should have all expected task commands", () => {
10-
const expectedCommands = ["StartNewTask", "CancelTask", "CloseTask", "ResumeTask"]
14+
const expectedCommands = [
15+
"StartNewTask",
16+
"CancelTask",
17+
"CloseTask",
18+
"ResumeTask",
19+
"SendMessage",
20+
"DeleteQueuedMessage",
21+
]
1122
const actualCommands = Object.values(TaskCommandName)
1223

1324
expectedCommands.forEach((command) => {
@@ -70,5 +81,40 @@ describe("IPC Types", () => {
7081
const result = taskCommandSchema.safeParse(invalidCommand)
7182
expect(result.success).toBe(false)
7283
})
84+
85+
it("should validate DeleteQueuedMessage command with messageId", () => {
86+
const command = {
87+
commandName: TaskCommandName.DeleteQueuedMessage,
88+
data: "msg-abc-123",
89+
}
90+
91+
const result = taskCommandSchema.safeParse(command)
92+
expect(result.success).toBe(true)
93+
94+
if (result.success && result.data.commandName === TaskCommandName.DeleteQueuedMessage) {
95+
expect(result.data.commandName).toBe("DeleteQueuedMessage")
96+
expect(result.data.data).toBe("msg-abc-123")
97+
}
98+
})
99+
100+
it("should reject DeleteQueuedMessage command with invalid data", () => {
101+
const invalidCommand = {
102+
commandName: TaskCommandName.DeleteQueuedMessage,
103+
data: 123, // Should be string
104+
}
105+
106+
const result = taskCommandSchema.safeParse(invalidCommand)
107+
expect(result.success).toBe(false)
108+
})
109+
110+
it("should reject DeleteQueuedMessage command without data", () => {
111+
const invalidCommand = {
112+
commandName: TaskCommandName.DeleteQueuedMessage,
113+
// Missing data field
114+
}
115+
116+
const result = taskCommandSchema.safeParse(invalidCommand)
117+
expect(result.success).toBe(false)
118+
})
73119
})
74120
})

packages/types/src/ipc.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export enum TaskCommandName {
4949
GetCommands = "GetCommands",
5050
GetModes = "GetModes",
5151
GetModels = "GetModels",
52+
DeleteQueuedMessage = "DeleteQueuedMessage",
5253
}
5354

5455
/**
@@ -91,6 +92,10 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
9192
z.object({
9293
commandName: z.literal(TaskCommandName.GetModels),
9394
}),
95+
z.object({
96+
commandName: z.literal(TaskCommandName.DeleteQueuedMessage),
97+
data: z.string(), // messageId
98+
}),
9499
])
95100

96101
export type TaskCommand = z.infer<typeof taskCommandSchema>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import * as vscode from "vscode"
3+
4+
import { API } from "../api"
5+
import { ClineProvider } from "../../core/webview/ClineProvider"
6+
7+
vi.mock("vscode")
8+
vi.mock("../../core/webview/ClineProvider")
9+
10+
describe("API - DeleteQueuedMessage Command", () => {
11+
let api: API
12+
let mockOutputChannel: vscode.OutputChannel
13+
let mockProvider: ClineProvider
14+
let mockRemoveMessage: ReturnType<typeof vi.fn>
15+
let mockLog: ReturnType<typeof vi.fn>
16+
17+
beforeEach(() => {
18+
mockOutputChannel = {
19+
appendLine: vi.fn(),
20+
} as unknown as vscode.OutputChannel
21+
22+
mockRemoveMessage = vi.fn().mockReturnValue(true)
23+
24+
mockProvider = {
25+
context: {} as vscode.ExtensionContext,
26+
postMessageToWebview: vi.fn().mockResolvedValue(undefined),
27+
on: vi.fn(),
28+
getCurrentTaskStack: vi.fn().mockReturnValue([]),
29+
getCurrentTask: vi.fn().mockReturnValue({
30+
messageQueueService: {
31+
removeMessage: mockRemoveMessage,
32+
},
33+
}),
34+
viewLaunched: true,
35+
} as unknown as ClineProvider
36+
37+
mockLog = vi.fn()
38+
39+
api = new API(mockOutputChannel, mockProvider, undefined, true)
40+
;(api as any).log = mockLog
41+
})
42+
43+
it("should remove a queued message by id", () => {
44+
const messageId = "msg-abc-123"
45+
46+
api.deleteQueuedMessage(messageId)
47+
48+
expect(mockRemoveMessage).toHaveBeenCalledWith(messageId)
49+
expect(mockRemoveMessage).toHaveBeenCalledTimes(1)
50+
})
51+
52+
it("should handle missing current task gracefully", () => {
53+
;(mockProvider.getCurrentTask as ReturnType<typeof vi.fn>).mockReturnValue(undefined)
54+
55+
// Should not throw
56+
expect(() => api.deleteQueuedMessage("msg-abc-123")).not.toThrow()
57+
})
58+
59+
it("should handle non-existent message id gracefully", () => {
60+
mockRemoveMessage.mockReturnValue(false)
61+
62+
// Should not throw even when removeMessage returns false
63+
expect(() => api.deleteQueuedMessage("non-existent-id")).not.toThrow()
64+
expect(mockRemoveMessage).toHaveBeenCalledWith("non-existent-id")
65+
})
66+
})

src/extension/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
150150
sendResponse(RooCodeEventName.ModelsResponse, [{}])
151151
}
152152

153+
break
154+
case TaskCommandName.DeleteQueuedMessage:
155+
this.log(`[API] DeleteQueuedMessage -> ${data}`)
156+
this.deleteQueuedMessage(data)
153157
break
154158
}
155159
})
@@ -266,6 +270,10 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
266270
await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images })
267271
}
268272

273+
public deleteQueuedMessage(messageId: string) {
274+
this.sidebarProvider.getCurrentTask()?.messageQueueService.removeMessage(messageId)
275+
}
276+
269277
public async pressPrimaryButton() {
270278
await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "primaryButtonClick" })
271279
}

0 commit comments

Comments
 (0)