Add submitUserMessage to Task (#6895)

This commit is contained in:
Matt Rubens 2025-08-10 03:13:15 -04:00 committed by GitHub
parent 3ee607202e
commit f53fd39014
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 148 additions and 1 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@roo-code/types",
"version": "1.44.0",
"version": "1.45.0",
"description": "TypeScript type definitions for Roo Code.",
"publishConfig": {
"access": "public",

View file

@ -70,6 +70,7 @@ export interface TaskLike {
off<K extends keyof TaskEvents>(event: K, listener: (...args: TaskEvents[K]) => void | Promise<void>): this
setMessageResponse(text: string, images?: string[]): void
submitUserMessage(text: string, images?: string[]): void
}
export type TaskEvents = {

View file

@ -740,6 +740,32 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.askResponseImages = images
}
public submitUserMessage(text: string, images?: string[]): void {
try {
const trimmed = (text ?? "").trim()
const imgs = images ?? []
if (!trimmed && imgs.length === 0) {
return
}
const provider = this.providerRef.deref()
if (!provider) {
console.error("[Task#submitUserMessage] Provider reference lost")
return
}
void provider.postMessageToWebview({
type: "invoke",
invoke: "sendMessage",
text: trimmed,
images: imgs,
})
} catch (error) {
console.error("[Task#submitUserMessage] Failed to submit user message:", error)
}
}
async handleTerminalOperation(terminalOperation: "continue" | "abort") {
if (terminalOperation === "continue") {
this.terminalProcess?.continue()

View file

@ -1493,5 +1493,125 @@ describe("Cline", () => {
expect(noModelTask.apiConfiguration.apiProvider).toBe("openai")
})
})
describe("submitUserMessage", () => {
it("should always route through webview sendMessage invoke", async () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "initial task",
startTask: false,
})
// Set up some existing messages to simulate an ongoing conversation
task.clineMessages = [
{
ts: Date.now(),
type: "say",
say: "text",
text: "Initial message",
},
]
// Call submitUserMessage
task.submitUserMessage("test message", ["image1.png"])
// Verify postMessageToWebview was called with sendMessage invoke
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: "test message",
images: ["image1.png"],
})
})
it("should handle empty messages gracefully", async () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "initial task",
startTask: false,
})
// Call with empty text and no images
task.submitUserMessage("", [])
// Should not call postMessageToWebview for empty messages
expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
// Call with whitespace only
task.submitUserMessage(" ", [])
expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
})
it("should route through webview for both new and existing tasks", async () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "initial task",
startTask: false,
})
// Test with no messages (new task scenario)
task.clineMessages = []
task.submitUserMessage("new task", ["image1.png"])
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: "new task",
images: ["image1.png"],
})
// Clear mock
mockProvider.postMessageToWebview.mockClear()
// Test with existing messages (ongoing task scenario)
task.clineMessages = [
{
ts: Date.now(),
type: "say",
say: "text",
text: "Initial message",
},
]
task.submitUserMessage("follow-up message", ["image2.png"])
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
type: "invoke",
invoke: "sendMessage",
text: "follow-up message",
images: ["image2.png"],
})
})
it("should handle undefined provider gracefully", async () => {
const task = new Task({
provider: mockProvider,
apiConfiguration: mockApiConfig,
task: "initial task",
startTask: false,
})
// Simulate weakref returning undefined
Object.defineProperty(task, "providerRef", {
value: { deref: () => undefined },
writable: false,
configurable: true,
})
// Spy on console.error to verify error is logged
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {})
// Should log error but not throw
task.submitUserMessage("test message")
expect(consoleErrorSpy).toHaveBeenCalledWith("[Task#submitUserMessage] Provider reference lost")
expect(mockProvider.postMessageToWebview).not.toHaveBeenCalled()
// Restore console.error
consoleErrorSpy.mockRestore()
})
})
})
})