IPC command for sending messages to the current task (#9149)

This commit is contained in:
Matt Rubens 2025-11-10 11:44:04 -05:00 committed by GitHub
parent 62636add14
commit b09d0a7640
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 175 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import {
type IpcMessage,
IpcOrigin,
IpcMessageType,
TaskCommandName,
ipcMessageSchema,
} from "@roo-code/types"
@ -98,6 +99,13 @@ export class IpcClient extends EventEmitter<IpcClientEvents> {
this.sendMessage(message)
}
public sendTaskMessage(text?: string, images?: string[]) {
this.sendCommand({
commandName: TaskCommandName.SendMessage,
data: { text, images },
})
}
public sendMessage(message: IpcMessage) {
ipc.of[this._id]?.emit("message", message)
}

View file

@ -45,6 +45,7 @@ export enum TaskCommandName {
CancelTask = "CancelTask",
CloseTask = "CloseTask",
ResumeTask = "ResumeTask",
SendMessage = "SendMessage",
}
/**
@ -73,6 +74,13 @@ export const taskCommandSchema = z.discriminatedUnion("commandName", [
commandName: z.literal(TaskCommandName.ResumeTask),
data: z.string(),
}),
z.object({
commandName: z.literal(TaskCommandName.SendMessage),
data: z.object({
text: z.string().optional(),
images: z.array(z.string()).optional(),
}),
}),
])
export type TaskCommand = z.infer<typeof taskCommandSchema>

View file

@ -0,0 +1,155 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import * as vscode from "vscode"
import { API } from "../api"
import { ClineProvider } from "../../core/webview/ClineProvider"
import { TaskCommandName } from "@roo-code/types"
vi.mock("vscode")
vi.mock("../../core/webview/ClineProvider")
describe("API - SendMessage Command", () => {
let api: API
let mockOutputChannel: vscode.OutputChannel
let mockProvider: ClineProvider
let mockPostMessageToWebview: ReturnType<typeof vi.fn>
let mockLog: ReturnType<typeof vi.fn>
beforeEach(() => {
// Setup mocks
mockOutputChannel = {
appendLine: vi.fn(),
} as unknown as vscode.OutputChannel
mockPostMessageToWebview = vi.fn().mockResolvedValue(undefined)
mockProvider = {
context: {} as vscode.ExtensionContext,
postMessageToWebview: mockPostMessageToWebview,
on: vi.fn(),
getCurrentTaskStack: vi.fn().mockReturnValue([]),
viewLaunched: true,
} as unknown as ClineProvider
mockLog = vi.fn()
// Create API instance with logging enabled for testing
api = new API(mockOutputChannel, mockProvider, undefined, true)
// Override the log method to use our mock
;(api as any).log = mockLog
})
it("should handle SendMessage command with text only", async () => {
// Arrange
const messageText = "Hello, this is a test message"
// Act
await api.sendMessage(messageText)
// Assert
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: messageText,
images: undefined,
})
})
it("should handle SendMessage command with text and images", async () => {
// Arrange
const messageText = "Analyze this image"
const images = [
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
]
// Act
await api.sendMessage(messageText, images)
// Assert
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: messageText,
images,
})
})
it("should handle SendMessage command with images only", async () => {
// Arrange
const images = [
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
]
// Act
await api.sendMessage(undefined, images)
// Assert
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: undefined,
images,
})
})
it("should handle SendMessage command with empty parameters", async () => {
// Act
await api.sendMessage()
// Assert
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: undefined,
images: undefined,
})
})
it("should log SendMessage command when processed via IPC", async () => {
// This test verifies the logging behavior when the command comes through IPC
// We need to simulate the IPC handler directly since we can't easily test the full IPC flow
const messageText = "Test message from IPC"
const commandData = {
text: messageText,
images: undefined,
}
// Simulate the IPC command handler calling sendMessage
mockLog(`[API] SendMessage -> ${commandData.text}`)
await api.sendMessage(commandData.text, commandData.images)
// Assert that logging occurred
expect(mockLog).toHaveBeenCalledWith(`[API] SendMessage -> ${messageText}`)
// Assert that the message was sent
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: messageText,
images: undefined,
})
})
it("should handle SendMessage with multiple images", async () => {
// Arrange
const messageText = "Compare these images"
const images = [
"data:image/png;base64,image1data",
"data:image/png;base64,image2data",
"data:image/png;base64,image3data",
]
// Act
await api.sendMessage(messageText, images)
// Assert
expect(mockPostMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: messageText,
images,
})
expect(mockPostMessageToWebview).toHaveBeenCalledTimes(1)
})
})

View file

@ -91,6 +91,10 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
// The error is logged for debugging purposes
}
break
case TaskCommandName.SendMessage:
this.log(`[API] SendMessage -> ${data.text}`)
await this.sendMessage(data.text, data.images)
break
}
})
}