mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
feat: add DeleteQueuedMessage IPC command (#11464)
* feat: add DeleteQueuedMessage IPC command for queue removal * Delete .changeset/delete-queued-message-ipc.md * fix: add try/catch to DeleteQueuedMessage IPC handler and early return in deleteQueuedMessage --------- Co-authored-by: Roo Code <roomote@roocode.com> Co-authored-by: Hannes Rudolph <hrudolph@gmail.com>
This commit is contained in:
parent
bfbfaf6d46
commit
d575295883
5 changed files with 149 additions and 1 deletions
|
|
@ -108,6 +108,13 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
|
|||
})
|
||||
}
|
||||
|
||||
public deleteQueuedMessage(messageId: string) {
|
||||
this.sendCommand({
|
||||
commandName: TaskCommandName.DeleteQueuedMessage,
|
||||
data: messageId,
|
||||
})
|
||||
}
|
||||
|
||||
public sendMessage(message: IpcMessage) {
|
||||
ipc.of[this._id]?.emit("message", message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,19 @@ describe("IPC Types", () => {
|
|||
expect(TaskCommandName.ResumeTask).toBe("ResumeTask")
|
||||
})
|
||||
|
||||
it("should include DeleteQueuedMessage command", () => {
|
||||
expect(TaskCommandName.DeleteQueuedMessage).toBe("DeleteQueuedMessage")
|
||||
})
|
||||
|
||||
it("should have all expected task commands", () => {
|
||||
const expectedCommands = ["StartNewTask", "CancelTask", "CloseTask", "ResumeTask"]
|
||||
const expectedCommands = [
|
||||
"StartNewTask",
|
||||
"CancelTask",
|
||||
"CloseTask",
|
||||
"ResumeTask",
|
||||
"SendMessage",
|
||||
"DeleteQueuedMessage",
|
||||
]
|
||||
const actualCommands = Object.values(TaskCommandName)
|
||||
|
||||
expectedCommands.forEach((command) => {
|
||||
|
|
@ -70,5 +81,40 @@ describe("IPC Types", () => {
|
|||
const result = taskCommandSchema.safeParse(invalidCommand)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("should validate DeleteQueuedMessage command with messageId", () => {
|
||||
const command = {
|
||||
commandName: TaskCommandName.DeleteQueuedMessage,
|
||||
data: "msg-abc-123",
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(command)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
if (result.success && result.data.commandName === TaskCommandName.DeleteQueuedMessage) {
|
||||
expect(result.data.commandName).toBe("DeleteQueuedMessage")
|
||||
expect(result.data.data).toBe("msg-abc-123")
|
||||
}
|
||||
})
|
||||
|
||||
it("should reject DeleteQueuedMessage command with invalid data", () => {
|
||||
const invalidCommand = {
|
||||
commandName: TaskCommandName.DeleteQueuedMessage,
|
||||
data: 123, // Should be string
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(invalidCommand)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("should reject DeleteQueuedMessage command without data", () => {
|
||||
const invalidCommand = {
|
||||
commandName: TaskCommandName.DeleteQueuedMessage,
|
||||
// Missing data field
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(invalidCommand)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ export enum TaskCommandName {
|
|||
GetCommands = "GetCommands",
|
||||
GetModes = "GetModes",
|
||||
GetModels = "GetModels",
|
||||
DeleteQueuedMessage = "DeleteQueuedMessage",
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -91,6 +92,10 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
|
|||
z.object({
|
||||
commandName: z.literal(TaskCommandName.GetModels),
|
||||
}),
|
||||
z.object({
|
||||
commandName: z.literal(TaskCommandName.DeleteQueuedMessage),
|
||||
data: z.string(), // messageId
|
||||
}),
|
||||
])
|
||||
|
||||
export type TaskCommand = z.infer<typeof taskCommandSchema>
|
||||
|
|
|
|||
70
src/extension/__tests__/api-delete-queued-message.spec.ts
Normal file
70
src/extension/__tests__/api-delete-queued-message.spec.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest"
|
||||
import * as vscode from "vscode"
|
||||
|
||||
import { API } from "../api"
|
||||
import { ClineProvider } from "../../core/webview/ClineProvider"
|
||||
|
||||
vi.mock("vscode")
|
||||
vi.mock("../../core/webview/ClineProvider")
|
||||
|
||||
describe("API - DeleteQueuedMessage Command", () => {
|
||||
let api: API
|
||||
let mockOutputChannel: vscode.OutputChannel
|
||||
let mockProvider: ClineProvider
|
||||
let mockRemoveMessage: ReturnType<typeof vi.fn>
|
||||
let mockLog: ReturnType<typeof vi.fn>
|
||||
|
||||
beforeEach(() => {
|
||||
mockOutputChannel = {
|
||||
appendLine: vi.fn(),
|
||||
} as unknown as vscode.OutputChannel
|
||||
|
||||
mockRemoveMessage = vi.fn().mockReturnValue(true)
|
||||
|
||||
mockProvider = {
|
||||
context: {} as vscode.ExtensionContext,
|
||||
postMessageToWebview: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn(),
|
||||
getCurrentTaskStack: vi.fn().mockReturnValue([]),
|
||||
getCurrentTask: vi.fn().mockReturnValue({
|
||||
messageQueueService: {
|
||||
removeMessage: mockRemoveMessage,
|
||||
},
|
||||
}),
|
||||
viewLaunched: true,
|
||||
} as unknown as ClineProvider
|
||||
|
||||
mockLog = vi.fn()
|
||||
|
||||
api = new API(mockOutputChannel, mockProvider, undefined, true)
|
||||
;(api as any).log = mockLog
|
||||
})
|
||||
|
||||
it("should remove a queued message by id", () => {
|
||||
const messageId = "msg-abc-123"
|
||||
|
||||
api.deleteQueuedMessage(messageId)
|
||||
|
||||
expect(mockRemoveMessage).toHaveBeenCalledWith(messageId)
|
||||
expect(mockRemoveMessage).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should handle missing current task gracefully and log a message", () => {
|
||||
;(mockProvider.getCurrentTask as ReturnType<typeof vi.fn>).mockReturnValue(undefined)
|
||||
|
||||
// Should not throw
|
||||
expect(() => api.deleteQueuedMessage("msg-abc-123")).not.toThrow()
|
||||
expect(mockLog).toHaveBeenCalledWith(
|
||||
"[API#deleteQueuedMessage] no current task; ignoring delete for messageId msg-abc-123",
|
||||
)
|
||||
expect(mockRemoveMessage).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle non-existent message id gracefully", () => {
|
||||
mockRemoveMessage.mockReturnValue(false)
|
||||
|
||||
// Should not throw even when removeMessage returns false
|
||||
expect(() => api.deleteQueuedMessage("non-existent-id")).not.toThrow()
|
||||
expect(mockRemoveMessage).toHaveBeenCalledWith("non-existent-id")
|
||||
})
|
||||
})
|
||||
|
|
@ -150,6 +150,15 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
|
|||
sendResponse(RooCodeEventName.ModelsResponse, [{}])
|
||||
}
|
||||
|
||||
break
|
||||
case TaskCommandName.DeleteQueuedMessage:
|
||||
this.log(`[API] DeleteQueuedMessage -> ${command.data}`)
|
||||
try {
|
||||
this.deleteQueuedMessage(command.data)
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
this.log(`[API] DeleteQueuedMessage failed for messageId ${command.data}: ${errorMessage}`)
|
||||
}
|
||||
break
|
||||
}
|
||||
})
|
||||
|
|
@ -266,6 +275,17 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
|
|||
await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "sendMessage", text, images })
|
||||
}
|
||||
|
||||
public deleteQueuedMessage(messageId: string) {
|
||||
const currentTask = this.sidebarProvider.getCurrentTask()
|
||||
|
||||
if (!currentTask) {
|
||||
this.log(`[API#deleteQueuedMessage] no current task; ignoring delete for messageId ${messageId}`)
|
||||
return
|
||||
}
|
||||
|
||||
currentTask.messageQueueService.removeMessage(messageId)
|
||||
}
|
||||
|
||||
public async pressPrimaryButton() {
|
||||
await this.sidebarProvider.postMessageToWebview({ type: "invoke", invoke: "primaryButtonClick" })
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue