diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 0c0bd37050..652c0ebf33 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -1044,6 +1044,12 @@ export class Cline { const modeName = getModeBySlug(mode, customModes)?.name ?? mode return `[${block.name} in ${modeName} mode: '${message}']` } + case "open_cursor": { + const prompt = block.params.prompt ?? "(no prompt)" + const mode = block.params.mode ?? "(no mode)" + const monitor = block.params.monitor ?? "(no monitor)" + return `[${block.name} with prompt: '${prompt}' in ${mode} mode, monitor: ${monitor}]` + } } } @@ -2472,6 +2478,52 @@ export class Cline { } } + case "open_cursor": { + const mode: string | undefined = block.params.mode + const prompt: string | undefined = block.params.prompt + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + tool: "openCursor", + mode: removeClosingTag("mode", mode), + prompt: removeClosingTag("prompt", prompt), + }) + await this.ask("tool", partialMessage, block.partial).catch(() => {}) + break + } else { + if (!mode) { + this.consecutiveMistakeCount++ + pushToolResult(await this.sayAndCreateMissingParamError("open_cursor", "mode")) + break + } + if (!prompt) { + this.consecutiveMistakeCount++ + pushToolResult(await this.sayAndCreateMissingParamError("open_cursor", "prompt")) + break + } + this.consecutiveMistakeCount = 0 + + const provider = this.providerRef.deref() + if (provider) { + await provider.openCursorInstance(prompt, mode) + pushToolResult( + `Successfully opened new Cursor instance in ${mode} mode with prompt: ${prompt}`, + ) + } else { + pushToolResult( + formatResponse.toolError( + "Failed to open Cursor instance: provider not available", + ), + ) + } + break + } + } catch (error) { + await handleError("opening cursor", error) + break + } + } + case "attempt_completion": { /* this.consecutiveMistakeCount = 0 diff --git a/src/core/assistant-message/index.ts b/src/core/assistant-message/index.ts index f1c49f85ab..7b7e8bfb23 100644 --- a/src/core/assistant-message/index.ts +++ b/src/core/assistant-message/index.ts @@ -25,6 +25,7 @@ export const toolUseNames = [ "attempt_completion", "switch_mode", "new_task", + "open_cursor", ] as const // Converts array of tool call names into a union type ("execute_command" | "read_file" | ...) @@ -56,6 +57,8 @@ export const toolParamNames = [ "operations", "mode", "message", + "prompt", + "monitor", ] as const export type ToolParamName = (typeof toolParamNames)[number] @@ -138,3 +141,8 @@ export interface NewTaskToolUse extends ToolUse { name: "new_task" params: Partial, "mode" | "message">> } + +export interface OpenCursorToolUse extends ToolUse { + name: "open_cursor" + params: Partial, "prompt" | "mode" | "monitor">> +} diff --git a/src/core/prompts/tools/index.ts b/src/core/prompts/tools/index.ts index c4bdfc98c3..f36bf94d3b 100644 --- a/src/core/prompts/tools/index.ts +++ b/src/core/prompts/tools/index.ts @@ -13,6 +13,7 @@ import { getUseMcpToolDescription } from "./use-mcp-tool" import { getAccessMcpResourceDescription } from "./access-mcp-resource" import { getSwitchModeDescription } from "./switch-mode" import { getNewTaskDescription } from "./new-task" +import { getOpenCursorDescription } from "./open_cursor" import { DiffStrategy } from "../../diff/DiffStrategy" import { McpHub } from "../../../services/mcp/McpHub" import { Mode, ModeConfig, getModeConfig, isToolAllowedForMode, getGroupName } from "../../../shared/modes" @@ -36,6 +37,7 @@ const toolDescriptionMap: Record string | undefined> new_task: (args) => getNewTaskDescription(args), insert_content: (args) => getInsertContentDescription(args), search_and_replace: (args) => getSearchAndReplaceDescription(args), + open_cursor: (args) => getOpenCursorDescription(args), apply_diff: (args) => args.diffStrategy ? args.diffStrategy.getToolDescription({ cwd: args.cwd, toolOptions: args.toolOptions }) : "", } diff --git a/src/core/prompts/tools/open_cursor.ts b/src/core/prompts/tools/open_cursor.ts new file mode 100644 index 0000000000..6c78c20cdf --- /dev/null +++ b/src/core/prompts/tools/open_cursor.ts @@ -0,0 +1,25 @@ +import { ToolArgs } from "./types" + +export function getOpenCursorDescription(args: ToolArgs): string { + return `## open_cursor + +Description: Request to open a new instance of Cursor with a specified prompt and mode. This tool allows Roo Code to start and monitor a new, separate task within a new Cursor instance. + +Parameters: +- prompt: (required) The initial prompt or instructions to send to the new Cursor instance. +- mode: (optional) The mode to start the new instance in (e.g., "code", "ask", "architect"). If not provided, will use the current mode. +- monitor: (optional) Whether to monitor the task's progress. Defaults to true. + +Usage: + +Your initial prompt here +Mode to start in (optional) + + +Example: + +Create a new React component +code + +` +} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index ffb984813b..7a61795b8c 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1413,6 +1413,28 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.postStateToWebview() } + public async openCursorInstance(prompt: string, mode: string) { + // Construct command to open new Cursor instance + const cursorCommand = { + command: "workbench.action.newWindow", + args: [ + { + mode, + prompt, + }, + ], + } + + // Execute command to open new window + const result = await vscode.commands.executeCommand(cursorCommand.command, ...cursorCommand.args) + console.log("result", result) + const windows = vscode.window.state.active + console.log("windows", windows) + await vscode.commands.executeCommand("workbench.view.extension.roo-cline-ActivityBar") + await this.postMessageToWebview({ type: "action", action: "openCursorInstance" }) + await this.postStateToWebview() + } + private async updateApiConfiguration(apiConfiguration: ApiConfiguration) { // Update mode's default config const { mode } = await this.getState() diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index a226beb802..f5c18ac82b 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -50,6 +50,7 @@ export interface ExtensionMessage { | "historyButtonClicked" | "promptsButtonClicked" | "didBecomeVisible" + | "openCursorInstance" invoke?: "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage" state?: ExtensionState images?: string[] @@ -161,7 +162,7 @@ export type ClineSay = | "mcp_server_response" | "new_task_started" | "new_task" - + | "openCursorInstance" export interface ClineSayTool { tool: | "editedExistingFile" @@ -174,6 +175,7 @@ export interface ClineSayTool { | "searchFiles" | "switchMode" | "newTask" + | "openCursor" path?: string diff?: string content?: string diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 357a237caf..16757d953d 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -81,6 +81,7 @@ export interface WebviewMessage { | "deleteCustomMode" | "setopenAiCustomModelInfo" | "openCustomModesSettings" + | "openCursorInstance" text?: string disabled?: boolean askResponse?: ClineAskResponse diff --git a/src/shared/tool-groups.ts b/src/shared/tool-groups.ts index 055fb11660..9ef1837a13 100644 --- a/src/shared/tool-groups.ts +++ b/src/shared/tool-groups.ts @@ -17,6 +17,7 @@ export const TOOL_DISPLAY_NAMES = { attempt_completion: "complete tasks", switch_mode: "switch modes", new_task: "create new task", + open_cursor: "open cursor instance", } as const // Define available tool groups @@ -27,6 +28,7 @@ export const TOOL_GROUPS: Record = { command: ["execute_command"], mcp: ["use_mcp_tool", "access_mcp_resource"], modes: ["switch_mode", "new_task"], + open_cursor: ["open_cursor"], } export type ToolGroup = keyof typeof TOOL_GROUPS @@ -37,6 +39,7 @@ export const ALWAYS_AVAILABLE_TOOLS = [ "attempt_completion", "switch_mode", "new_task", + "open_cursor", ] as const // Tool name types for type safety diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index cb4649a915..e6fac06fd1 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -489,6 +489,15 @@ export const ChatRowContent = ({ ) + case "openCursor": + return ( + <> +
+ {toolIcon("open-cursor")} + Roo wants to open a new Cursor instance: +
+ + ) default: return null } diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 3f58c7aefd..e67d098dc3 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -348,6 +348,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie vscode.postMessage({ type: "clearTask" }) }, []) + const openCursorInstance = useCallback(() => { + vscode.postMessage({ type: "openCursorInstance" }) + }, []) + /* This logic depends on the useEffect[messages] above to set clineAsk, after which buttons are shown and we then send an askResponse to the extension. */