浏览代码

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

Matt Rubens 1 月之前
父节点
当前提交
b09d0a7640
共有 4 个文件被更改,包括 175 次插入0 次删除
  1. 8 0
      packages/ipc/src/ipc-client.ts
  2. 8 0
      packages/types/src/ipc.ts
  3. 155 0
      src/extension/__tests__/api-send-message.spec.ts
  4. 4 0
      src/extension/api.ts

+ 8 - 0
packages/ipc/src/ipc-client.ts

@@ -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)
 	}

+ 8 - 0
packages/types/src/ipc.ts

@@ -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>

+ 155 - 0
src/extension/__tests__/api-send-message.spec.ts

@@ -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)
+	})
+})

+ 4 - 0
src/extension/api.ts

@@ -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
 				}
 			})
 		}