From 42f74656663a594c0b63b9a878de056ff6f4f620 Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Wed, 2 Apr 2025 14:25:11 -0700 Subject: [PATCH] Add a mode switched event, better mode switch e2e test (#2237) --- e2e/src/suite/modes.test.ts | 43 ++++++++++--------------------- src/core/Cline.ts | 1 + src/core/webview/ClineProvider.ts | 7 ++--- src/exports/api.ts | 2 ++ src/exports/roo-code.d.ts | 2 ++ src/exports/types.ts | 1 + src/schemas/index.ts | 2 ++ src/schemas/ipc.ts | 4 +++ 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/e2e/src/suite/modes.test.ts b/e2e/src/suite/modes.test.ts index c4ae7f1af6..f5cd2141f3 100644 --- a/e2e/src/suite/modes.test.ts +++ b/e2e/src/suite/modes.test.ts @@ -13,11 +13,17 @@ suite("Roo Code Modes", () => { */ const switchModesPrompt = - "For each mode (Code, Architect, Ask) respond with the mode name and what it specializes in after switching to that mode. " + - "Do not start with the current mode." + "For each mode (Architect, Ask, Debug) respond with the mode name and what it specializes in after switching to that mode." let messages: ClineMessage[] = [] + const modeSwitches: string[] = [] + + api.on("taskModeSwitched", (_taskId, mode) => { + console.log("taskModeSwitched", mode) + modeSwitches.push(mode) + }) + api.on("message", ({ message }) => { if (message.type === "say" && message.partial === false) { messages.push(message) @@ -25,36 +31,15 @@ suite("Roo Code Modes", () => { }) const switchModesTaskId = await api.startNewTask({ - configuration: { mode: "Code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, + configuration: { mode: "code", alwaysAllowModeSwitch: true, autoApprovalEnabled: true }, text: switchModesPrompt, }) - await waitUntilCompleted({ api, taskId: switchModesTaskId, timeout: 60_000 }) - - /** - * Grade the response. - */ - - const response = messages - .filter(({ type, say, partial }) => say === "text") - .map(({ text }) => text ?? "") - .join("\n") - - const gradePrompt = `Given this prompt: ${switchModesPrompt} grade the response from 1 to 10 in the format of "Grade: (1-10)". For example: Grade 7\n\nResponse: ${response}` - - messages = [] - - const gradeTaskId = await api.startNewTask({ configuration: { mode: "Ask" }, text: gradePrompt }) - await waitUntilCompleted({ api, taskId: gradeTaskId }) - - const completion = messages.find(({ type, say, partial }) => say === "completion_result") - const match = completion?.text?.match(/Grade: (\d+)/) - const score = parseInt(match?.[1] ?? "0") - assert.ok( - score >= 7 && score <= 10, - `Grade must be between 7 and 10. DEBUG: score = ${score}, completion = ${completion?.text}`, - ) - + await waitUntilCompleted({ api, taskId: switchModesTaskId }) await api.cancelCurrentTask() + + assert.ok(modeSwitches.includes("architect")) + assert.ok(modeSwitches.includes("ask")) + assert.ok(modeSwitches.includes("debug")) }) }) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 005b00fc0d..c7e9fe66a5 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -87,6 +87,7 @@ type UserContent = Array export type ClineEvents = { message: [{ action: "created" | "updated"; message: ClineMessage }] taskStarted: [] + taskModeSwitched: [taskId: string, mode: string] taskPaused: [] taskUnpaused: [] taskAskResponded: [] diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8206375d79..c31b77a5e1 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -740,10 +740,11 @@ export class ClineProvider extends EventEmitter implements */ public async handleModeSwitch(newMode: Mode) { // Capture mode switch telemetry event - const currentTaskId = this.getCurrentCline()?.taskId + const cline = this.getCurrentCline() - if (currentTaskId) { - telemetryService.captureModeSwitch(currentTaskId, newMode) + if (cline) { + telemetryService.captureModeSwitch(cline.taskId, newMode) + cline.emit("taskModeSwitched", cline.taskId, newMode) } await this.updateGlobalState("mode", newMode) diff --git a/src/exports/api.ts b/src/exports/api.ts index 6ddac43056..609eda3b6d 100644 --- a/src/exports/api.ts +++ b/src/exports/api.ts @@ -176,6 +176,8 @@ export class API extends EventEmitter implements RooCodeAPI { cline.on("message", (message) => this.emit(RooCodeEventName.Message, { taskId: cline.taskId, ...message })) + cline.on("taskModeSwitched", (taskId, mode) => this.emit(RooCodeEventName.TaskModeSwitched, taskId, mode)) + cline.on("taskTokenUsageUpdated", (_, usage) => this.emit(RooCodeEventName.TaskTokenUsageUpdated, cline.taskId, usage), ) diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 350d0ae5a3..aa9607a012 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -470,6 +470,7 @@ type RooCodeEvents = { ] taskCreated: [string] taskStarted: [string] + taskModeSwitched: [string, string] taskPaused: [string] taskUnpaused: [string] taskAskResponded: [string] @@ -506,6 +507,7 @@ declare enum RooCodeEventName { Message = "message", TaskCreated = "taskCreated", TaskStarted = "taskStarted", + TaskModeSwitched = "taskModeSwitched", TaskPaused = "taskPaused", TaskUnpaused = "taskUnpaused", TaskAskResponded = "taskAskResponded", diff --git a/src/exports/types.ts b/src/exports/types.ts index 20f583fb60..794c120904 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -479,6 +479,7 @@ type RooCodeEvents = { ] taskCreated: [string] taskStarted: [string] + taskModeSwitched: [string, string] taskPaused: [string] taskUnpaused: [string] taskAskResponded: [string] diff --git a/src/schemas/index.ts b/src/schemas/index.ts index e397635d41..a339e9bb54 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -797,6 +797,7 @@ export enum RooCodeEventName { Message = "message", TaskCreated = "taskCreated", TaskStarted = "taskStarted", + TaskModeSwitched = "taskModeSwitched", TaskPaused = "taskPaused", TaskUnpaused = "taskUnpaused", TaskAskResponded = "taskAskResponded", @@ -816,6 +817,7 @@ export const rooCodeEventsSchema = z.object({ ]), [RooCodeEventName.TaskCreated]: z.tuple([z.string()]), [RooCodeEventName.TaskStarted]: z.tuple([z.string()]), + [RooCodeEventName.TaskModeSwitched]: z.tuple([z.string(), z.string()]), [RooCodeEventName.TaskPaused]: z.tuple([z.string()]), [RooCodeEventName.TaskUnpaused]: z.tuple([z.string()]), [RooCodeEventName.TaskAskResponded]: z.tuple([z.string()]), diff --git a/src/schemas/ipc.ts b/src/schemas/ipc.ts index 9a351bdf8a..08ab0c974d 100644 --- a/src/schemas/ipc.ts +++ b/src/schemas/ipc.ts @@ -63,6 +63,10 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [ eventName: z.literal(RooCodeEventName.TaskStarted), payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskStarted], }), + z.object({ + eventName: z.literal(RooCodeEventName.TaskModeSwitched), + payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskModeSwitched], + }), z.object({ eventName: z.literal(RooCodeEventName.TaskPaused), payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskPaused],