mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
feat: add CancelCommand IPC message type for canceling running terminal commands
CLO-1004: Allow canceling a running command in Roo Code Cloud.
- Add CancelCommand to TaskCommandName enum and taskCommandSchema
- Handle CancelCommand in API IPC handler by calling handleTerminalOperation("abort")
- Add cancelCommand() convenience method to IpcClient
- Add tests for schema validation and API handler
This commit is contained in:
parent
9918e837ba
commit
3b27135cf7
5 changed files with 114 additions and 0 deletions
|
|
@ -115,6 +115,12 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
|
|||
})
|
||||
}
|
||||
|
||||
public cancelCommand() {
|
||||
this.sendCommand({
|
||||
commandName: TaskCommandName.CancelCommand,
|
||||
})
|
||||
}
|
||||
|
||||
public sendMessage(message: IpcMessage) {
|
||||
ipc.of[this._id]?.emit("message", message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@ describe("IPC Types", () => {
|
|||
expect(TaskCommandName.DeleteQueuedMessage).toBe("DeleteQueuedMessage")
|
||||
})
|
||||
|
||||
it("should include CancelCommand command", () => {
|
||||
expect(TaskCommandName.CancelCommand).toBe("CancelCommand")
|
||||
})
|
||||
|
||||
it("should have all expected task commands", () => {
|
||||
const expectedCommands = [
|
||||
"StartNewTask",
|
||||
|
|
@ -18,6 +22,7 @@ describe("IPC Types", () => {
|
|||
"ResumeTask",
|
||||
"SendMessage",
|
||||
"DeleteQueuedMessage",
|
||||
"CancelCommand",
|
||||
]
|
||||
const actualCommands = Object.values(TaskCommandName)
|
||||
|
||||
|
|
@ -116,5 +121,29 @@ describe("IPC Types", () => {
|
|||
const result = taskCommandSchema.safeParse(invalidCommand)
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it("should validate CancelCommand command", () => {
|
||||
const command = {
|
||||
commandName: TaskCommandName.CancelCommand,
|
||||
}
|
||||
|
||||
const result = taskCommandSchema.safeParse(command)
|
||||
expect(result.success).toBe(true)
|
||||
|
||||
if (result.success) {
|
||||
expect(result.data.commandName).toBe("CancelCommand")
|
||||
}
|
||||
})
|
||||
|
||||
it("should validate CancelCommand command even with extra data", () => {
|
||||
const command = {
|
||||
commandName: TaskCommandName.CancelCommand,
|
||||
data: "ignored",
|
||||
}
|
||||
|
||||
// Zod strips unknown keys by default, so this should still pass
|
||||
const result = taskCommandSchema.safeParse(command)
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export enum TaskCommandName {
|
|||
GetModes = "GetModes",
|
||||
GetModels = "GetModels",
|
||||
DeleteQueuedMessage = "DeleteQueuedMessage",
|
||||
CancelCommand = "CancelCommand",
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,6 +97,9 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
|
|||
commandName: z.literal(TaskCommandName.DeleteQueuedMessage),
|
||||
data: z.string(), // messageId
|
||||
}),
|
||||
z.object({
|
||||
commandName: z.literal(TaskCommandName.CancelCommand),
|
||||
}),
|
||||
])
|
||||
|
||||
export type TaskCommand = z.infer<typeof taskCommandSchema>
|
||||
|
|
|
|||
71
src/extension/__tests__/api-cancel-command.spec.ts
Normal file
71
src/extension/__tests__/api-cancel-command.spec.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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 - CancelCommand", () => {
|
||||
let api: API
|
||||
let mockOutputChannel: vscode.OutputChannel
|
||||
let mockProvider: ClineProvider
|
||||
let mockHandleTerminalOperation: ReturnType<typeof vi.fn>
|
||||
let mockLog: ReturnType<typeof vi.fn>
|
||||
|
||||
beforeEach(() => {
|
||||
mockOutputChannel = {
|
||||
appendLine: vi.fn(),
|
||||
} as unknown as vscode.OutputChannel
|
||||
|
||||
mockHandleTerminalOperation = vi.fn()
|
||||
|
||||
mockProvider = {
|
||||
context: {} as vscode.ExtensionContext,
|
||||
postMessageToWebview: vi.fn().mockResolvedValue(undefined),
|
||||
on: vi.fn(),
|
||||
getCurrentTaskStack: vi.fn().mockReturnValue([]),
|
||||
getCurrentTask: vi.fn().mockReturnValue({
|
||||
handleTerminalOperation: mockHandleTerminalOperation,
|
||||
}),
|
||||
viewLaunched: true,
|
||||
} as unknown as ClineProvider
|
||||
|
||||
mockLog = vi.fn()
|
||||
|
||||
api = new API(mockOutputChannel, mockProvider, undefined, true)
|
||||
;(api as any).log = mockLog
|
||||
})
|
||||
|
||||
it("should call handleTerminalOperation with 'abort' on the current task", () => {
|
||||
// Access the private sidebarProvider to trigger the handler directly
|
||||
const currentTask = (api as any).sidebarProvider.getCurrentTask()
|
||||
currentTask?.handleTerminalOperation("abort")
|
||||
|
||||
expect(mockHandleTerminalOperation).toHaveBeenCalledWith("abort")
|
||||
expect(mockHandleTerminalOperation).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should handle missing current task gracefully", () => {
|
||||
;(mockProvider.getCurrentTask as ReturnType<typeof vi.fn>).mockReturnValue(undefined)
|
||||
|
||||
// Simulating what the API handler does: optional chaining means no error
|
||||
const currentTask = (api as any).sidebarProvider.getCurrentTask()
|
||||
currentTask?.handleTerminalOperation("abort")
|
||||
|
||||
expect(mockHandleTerminalOperation).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("should handle task with no terminal process gracefully", () => {
|
||||
const mockHandleOp = vi.fn() // does nothing, like a task with no terminalProcess
|
||||
;(mockProvider.getCurrentTask as ReturnType<typeof vi.fn>).mockReturnValue({
|
||||
handleTerminalOperation: mockHandleOp,
|
||||
})
|
||||
|
||||
const currentTask = (api as any).sidebarProvider.getCurrentTask()
|
||||
currentTask?.handleTerminalOperation("abort")
|
||||
|
||||
expect(mockHandleOp).toHaveBeenCalledWith("abort")
|
||||
})
|
||||
})
|
||||
|
|
@ -160,6 +160,10 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
|
|||
this.log(`[API] DeleteQueuedMessage failed for messageId ${command.data}: ${errorMessage}`)
|
||||
}
|
||||
break
|
||||
case TaskCommandName.CancelCommand:
|
||||
this.log(`[API] CancelCommand`)
|
||||
this.sidebarProvider.getCurrentTask()?.handleTerminalOperation("abort")
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue