Add mode switch event

This commit is contained in:
cte 2025-04-02 14:08:34 -07:00
parent 9164e5e203
commit 238a4137d3
11 changed files with 62 additions and 67 deletions

View file

@ -71,6 +71,11 @@ export const taskEventSchema = z.discriminatedUnion("eventName", [
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskStarted],
taskId: z.number().optional(),
}),
z.object({
eventName: z.literal(RooCodeEventName.TaskModeSwitched),
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskModeSwitched],
taskId: z.number().optional(),
}),
z.object({
eventName: z.literal(RooCodeEventName.TaskPaused),
payload: rooCodeEventsSchema.shape[RooCodeEventName.TaskPaused],

View file

@ -795,6 +795,7 @@ export enum RooCodeEventName {
Message = "message",
TaskCreated = "taskCreated",
TaskStarted = "taskStarted",
TaskModeSwitched = "taskModeSwitched",
TaskPaused = "taskPaused",
TaskUnpaused = "taskUnpaused",
TaskAskResponded = "taskAskResponded",
@ -814,6 +815,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()]),

View file

@ -1,10 +1,10 @@
#!/usr/bin/env bash
menu() {
echo -e "\nWhich eval types would you like to support?\n"
echo -e "\n📋 Which eval types would you like to support?\n"
for i in ${!options[@]}; do
printf "%d %6s [%s]" $((i + 1)) "${options[i]}" "${choices[i]:- }"
printf " %d) %-6s [%s]" $((i + 1)) "${options[i]}" "${choices[i]:- }"
if [[ $i == 0 ]]; then
printf " (required)"
@ -13,11 +13,11 @@ menu() {
printf "\n"
done
echo
echo -e " q) quit\n"
}
build_extension() {
echo "Building the Roo Code extension..."
echo "🔨 Building the Roo Code extension..."
cd ..
mkdir -p bin
npm run install-extension -- --silent --no-audit || exit 1
@ -29,7 +29,7 @@ build_extension() {
}
if [[ "$(uname -s)" != "Darwin" ]]; then
echo "Only macOS is currently supported."
echo "⚠️ Only macOS is currently supported."
exit 1
fi
@ -43,19 +43,11 @@ for i in "${!options[@]}"; do
choices[i]="*"
done
prompt="Type 🔢 to select, 'a' for all, 'q' to quit, ⏎ to continue: "
prompt="Type 1-5 to select, 'q' to quit, ⏎ to continue: "
while menu && read -rp "$prompt" num && [[ "$num" ]]; do
[[ "$num" == "q" ]] && exit 0
[[ "$num" == "a" ]] && {
for i in ${!options[@]}; do
choices[i]="*"
done
continue
}
[[ "$num" != *[![:digit:]]* ]] &&
((num > 1 && num <= ${#options[@]})) ||
{
@ -81,14 +73,14 @@ printf "\n"
if ! command -v brew &>/dev/null; then
if [[ -f "/opt/homebrew/bin/brew" ]]; then
echo "Homebrew is installed but not in your PATH"
echo "⚠️ Homebrew is installed but not in your PATH"
exit 1
fi
read -p "Homebrew (https://brew.sh) is required. Install it? (Y/n): " install_brew
read -p "🍺 Homebrew (https://brew.sh) is required. Install it? (Y/n): " install_brew
if [[ "$install_brew" =~ ^[Yy]|^$ ]]; then
echo " Installing Homebrew..."
echo "🍺 Installing Homebrew..."
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" || exit 1
# Can be undone with:
# /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/uninstall.sh)" && sudo rm -rvf /opt/homebrew
@ -119,14 +111,14 @@ ASDF_PATH="$(brew --prefix asdf)/libexec/asdf.sh"
if ! command -v asdf &>/dev/null; then
if [[ -f "$ASDF_PATH" ]]; then
echo "asdf is installed but not in your PATH"
echo "⚠️ asdf is installed but not in your PATH"
exit 1
fi
read -p "asdf (https://asdf-vm.com) is required. Install it? (Y/n): " install_asdf
read -p "🛠️ asdf (https://asdf-vm.com) is required. Install it? (Y/n): " install_asdf
if [[ "$install_asdf" =~ ^[Yy]|^$ ]]; then
echo "Installing asdf..."
echo "🛠️ Installing asdf..."
brew install asdf || exit 1
# Can be undone with:
# brew uninstall asdf
@ -152,7 +144,7 @@ else
fi
if ! command -v gh &>/dev/null; then
read -p "GitHub cli is needed to submit evals results. Install it? (Y/n): " install_gh
read -p "👨‍💻 GitHub cli is needed to submit evals results. Install it? (Y/n): " install_gh
if [[ "$install_gh" =~ ^[Yy]|^$ ]]; then
brew install gh || exit 1
@ -173,9 +165,9 @@ for i in "${!options[@]}"; do
if [[ "${has_asdf_plugin[$plugin]}" == "true" ]]; then
if ! asdf plugin list | grep -q "^${plugin}$" && ! command -v "${binary}" &>/dev/null; then
echo "Installing ${plugin} asdf plugin..."
echo "📦 Installing ${plugin} asdf plugin..."
asdf plugin add "${plugin}" || exit 1
echo "✅ asdf ${plugin} plugin installed"
echo "✅ asdf ${plugin} plugin installed successfully"
fi
fi
@ -194,7 +186,7 @@ for i in "${!options[@]}"; do
if [[ $(node --version) != "v20.18.1" ]]; then
NODE_VERSION=$(node --version)
echo "🚨 You have the wrong version of node installed ($NODE_VERSION)."
echo "If you are using nvm then run 'nvm install' to install the version specified by the repo's .nvmrc."
echo "💡 If you are using nvm then run 'nvm install' to install the version specified by the repo's .nvmrc."
exit 1
fi
;;
@ -246,7 +238,7 @@ for i in "${!options[@]}"; do
"java")
if ! command -v javac &>/dev/null || ! javac --version &>/dev/null; then
echo "Installing Java..."
echo "Installing Java..."
brew install openjdk@17 || exit 1
export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"
@ -280,7 +272,7 @@ pnpm install --silent || exit 1
if [[ ! -d "../../evals" ]]; then
if gh auth status &>/dev/null; then
read -p "Would you like to be able to share eval results? (Y/n): " fork_evals
read -p "🔗 Would you like to be able to share eval results? (Y/n): " fork_evals
if [[ "$fork_evals" =~ ^[Yy]|^$ ]]; then
gh repo fork cte/evals ../../evals || exit 1
@ -297,14 +289,14 @@ if [[ ! -s .env ]]; then
fi
if ! grep -q "OPENROUTER_API_KEY" .env; then
read -p "Enter your OpenRouter API Key (sk-or-v1-...): " openrouter_api_key
echo "Validating OpenRouter API Key..."
curl --silent --fail https://openrouter.ai/api/v1/key -H "Authorization: Bearer $openrouter_api_key" | jq || exit 1
read -p "🔐 Enter your OpenRouter API key (sk-or-v1-...): " openrouter_api_key
echo "🔑 Validating..."
curl --silent --fail https://openrouter.ai/api/v1/key -H "Authorization: Bearer $openrouter_api_key" || echo "⚠️ Invalid API key" && exit 1
echo "OPENROUTER_API_KEY=$openrouter_api_key" >>.env
fi
if ! command -v code &>/dev/null; then
echo "Visual Studio Code cli is not installed"
echo "⚠️ Visual Studio Code cli is not installed"
exit 1
else
VSCODE_VERSION=$(code --version | head -n 1)
@ -314,7 +306,7 @@ fi
if [[ ! -s "../bin/roo-code-latest.vsix" ]]; then
build_extension
else
read -p "Do you want to build a new version of the Roo Code extension? (y/N): " build_extension
read -p "💻 Do you want to build a new version of the Roo Code extension? (y/N): " build_extension
if [[ "$build_extension" =~ ^[Yy]$ ]]; then
build_extension
@ -323,16 +315,16 @@ else
fi
fi
echo -e "\n🤘 You're ready to rock and roll!\n"
echo -e "\n🚀 You're ready to rock and roll! \n"
if ! nc -z localhost 3000; then
read -p "Would you like to start the evals web app? (y/N): " start_evals
read -p "🚀 Would you like to start the evals web app? (y/N): " start_evals
if [[ "$start_evals" =~ ^[Yy]$ ]]; then
pnpm web
else
echo "You can start it anytime with 'pnpm web'."
echo "💡 You can start it anytime with 'pnpm web'."
fi
else
echo "🤖 The evals web app is running at http://localhost:3000"
echo "🌐 The evals web app is running at http://localhost:3000"
fi

View file

@ -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,13 @@ 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.deepEqual(modeSwitches.sort(), ["architect", "ask", "debug"])
})
})

View file

@ -87,6 +87,7 @@ type UserContent = Array<Anthropic.Messages.ContentBlockParam>
export type ClineEvents = {
message: [{ action: "created" | "updated"; message: ClineMessage }]
taskStarted: []
taskModeSwitched: [taskId: string, mode: string]
taskPaused: []
taskUnpaused: []
taskAskResponded: []

View file

@ -740,10 +740,11 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> 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)

View file

@ -176,6 +176,8 @@ export class API extends EventEmitter<RooCodeEvents> 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),
)

View file

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

View file

@ -479,6 +479,7 @@ type RooCodeEvents = {
]
taskCreated: [string]
taskStarted: [string]
taskModeSwitched: [string, string]
taskPaused: [string]
taskUnpaused: [string]
taskAskResponded: [string]

View file

@ -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()]),

View file

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