From 22a3b964bf484c75864c4eb98151aaa1ca392686 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 10 Feb 2026 21:34:59 -0700 Subject: [PATCH 1/4] refactor: remove browser use functionality entirely Remove all Puppeteer-based browser automation (browser_action tool, BrowserSession, BrowserSessionPanelManager, browser settings, browser panel) from the codebase. - Delete src/services/browser/ (BrowserSession, UrlContentFetcher, browserDiscovery) - Delete BrowserActionTool and browser_action prompt template - Delete BrowserSessionPanelManager and browser panel webview - Delete BrowserSettings, BrowserActionRow, BrowserSessionRow UI components - Remove browser from packages/types (tool groups, message types, state, settings) - Remove browser from CLI agent and tool display - Remove puppeteer-core and puppeteer-chromium-resolver dependencies - Remove browser-related i18n keys across all 18 locales - Update all test files to remove browser fixtures and assertions 165 files changed, 84 insertions(+), 8256 deletions(-) --- .../agent/__tests__/extension-client.test.ts | 8 - apps/cli/src/agent/agent-state.ts | 5 +- apps/cli/src/agent/ask-dispatcher.ts | 31 +- apps/cli/src/agent/extension-host.ts | 2 - apps/cli/src/agent/json-event-emitter.ts | 18 - .../cli/src/ui/components/ChatHistoryItem.tsx | 5 +- .../src/ui/components/tools/BrowserTool.tsx | 87 -- apps/cli/src/ui/components/tools/index.ts | 3 - apps/cli/src/ui/components/tools/types.ts | 12 +- apps/cli/src/ui/components/tools/utils.ts | 8 - apps/cli/src/ui/types.ts | 8 - apps/cli/src/ui/utils/tools.ts | 23 - .../src/db/queries/__tests__/copyRun.spec.ts | 8 +- packages/types/src/__tests__/cloud.test.ts | 4 +- packages/types/src/global-settings.ts | 9 - packages/types/src/message.ts | 15 +- packages/types/src/mode.ts | 8 +- packages/types/src/tool-params.ts | 9 - packages/types/src/tool.ts | 3 +- packages/types/src/vscode-extension-host.ts | 55 - pnpm-lock.yaml | 424 +----- src/__tests__/command-mentions.spec.ts | 15 - .../assistant-message/NativeToolCallParser.ts | 26 - .../__tests__/NativeToolCallParser.spec.ts | 4 +- ...resentAssistantMessage-custom-tool.spec.ts | 3 - .../presentAssistantMessage-images.spec.ts | 3 - ...esentAssistantMessage-unknown-tool.spec.ts | 3 - .../presentAssistantMessage.ts | 83 +- src/core/auto-approval/index.ts | 7 +- .../CustomModesManager.yamlEdgeCases.spec.ts | 35 +- .../__tests__/CustomModesSettings.spec.ts | 2 +- src/core/config/__tests__/ModeConfig.spec.ts | 14 +- .../__tests__/getEnvironmentDetails.spec.ts | 18 - src/core/environment/getEnvironmentDetails.ts | 29 - src/core/mentions/__tests__/index.spec.ts | 139 +- .../processUserContentMentions.spec.ts | 166 ++- src/core/mentions/index.ts | 86 +- .../mentions/processUserContentMentions.ts | 112 +- .../__tests__/add-custom-instructions.spec.ts | 3 - .../prompts/__tests__/system-prompt.spec.ts | 51 - src/core/prompts/system.ts | 3 - .../__tests__/filter-tools-for-mode.spec.ts | 7 +- .../prompts/tools/filter-tools-for-mode.ts | 10 - .../tools/native-tools/browser_action.ts | 76 -- src/core/prompts/tools/native-tools/index.ts | 2 - src/core/prompts/types.ts | 1 - src/core/task-persistence/rooMessage.ts | 21 +- src/core/task/Task.ts | 127 +- src/core/task/__tests__/Task.dispose.test.ts | 2 - src/core/task/__tests__/Task.spec.ts | 1 - src/core/task/__tests__/Task.throttle.test.ts | 2 - .../__tests__/native-tools-filtering.spec.ts | 4 +- src/core/task/build-tools.ts | 3 - src/core/tools/BrowserActionTool.ts | 280 ---- src/core/tools/ToolRepetitionDetector.ts | 22 - ...rowserActionTool.coordinateScaling.spec.ts | 84 -- .../BrowserActionTool.screenshot.spec.ts | 25 - .../__tests__/ToolRepetitionDetector.spec.ts | 160 --- .../tools/__tests__/validateToolUse.spec.ts | 18 +- .../webview/BrowserSessionPanelManager.ts | 310 ----- src/core/webview/ClineProvider.ts | 24 - .../ClineProvider.lockApiConfig.spec.ts | 4 +- .../webview/__tests__/ClineProvider.spec.ts | 182 +-- .../ClineProvider.sticky-mode.spec.ts | 4 +- .../ClineProvider.sticky-profile.spec.ts | 4 +- .../ClineProvider.taskHistory.spec.ts | 12 - ...ateSystemPrompt.browser-capability.spec.ts | 79 -- src/core/webview/generateSystemPrompt.ts | 32 +- src/core/webview/webviewMessageHandler.ts | 102 -- src/package.json | 2 - src/services/browser/BrowserSession.ts | 913 ------------- src/services/browser/UrlContentFetcher.ts | 143 --- .../browser/__tests__/BrowserSession.spec.ts | 628 --------- .../__tests__/UrlContentFetcher.spec.ts | 369 ------ src/services/browser/browserDiscovery.ts | 181 --- src/shared/__tests__/modes.spec.ts | 20 +- src/shared/browserUtils.ts | 95 -- src/shared/tools.ts | 19 +- webview-ui/browser-panel.html | 12 - webview-ui/src/browser-panel.tsx | 12 - .../BrowserPanelStateProvider.tsx | 61 - .../browser-session/BrowserSessionPanel.tsx | 106 -- .../components/chat/AutoApproveDropdown.tsx | 5 - .../src/components/chat/BrowserActionRow.tsx | 195 --- .../src/components/chat/BrowserSessionRow.tsx | 1137 ----------------- .../chat/BrowserSessionStatusRow.tsx | 34 - webview-ui/src/components/chat/ChatRow.tsx | 4 - .../src/components/chat/ChatTextArea.tsx | 11 - webview-ui/src/components/chat/ChatView.tsx | 84 +- webview-ui/src/components/chat/TaskHeader.tsx | 57 +- .../BrowserSessionRow.aspect-ratio.spec.tsx | 55 - ...owserSessionRow.disconnect-button.spec.tsx | 42 - .../chat/__tests__/BrowserSessionRow.spec.tsx | 126 -- .../__tests__/ChatView.keyboard-fix.spec.tsx | 4 - .../ChatView.notification-sound.spec.tsx | 6 - .../ChatView.preserve-images.spec.tsx | 6 - .../chat/__tests__/ChatView.spec.tsx | 6 - .../settings/AutoApproveSettings.tsx | 4 - .../components/settings/AutoApproveToggle.tsx | 8 - .../components/settings/BrowserSettings.tsx | 243 ---- .../src/components/settings/SettingsView.tsx | 29 - .../__tests__/AutoApproveToggle.spec.tsx | 1 - .../SettingsView.change-detection.spec.tsx | 8 - .../settings/__tests__/SettingsView.spec.tsx | 17 - .../SettingsView.unsaved-changes.spec.tsx | 8 - .../src/context/ExtensionStateContext.tsx | 13 - .../__tests__/ExtensionStateContext.spec.tsx | 2 - .../__tests__/useAutoApprovalState.spec.ts | 8 +- webview-ui/src/hooks/useAutoApprovalState.ts | 1 - .../src/hooks/useAutoApprovalToggles.ts | 3 - webview-ui/src/i18n/locales/ca/chat.json | 38 - webview-ui/src/i18n/locales/ca/prompts.json | 1 - webview-ui/src/i18n/locales/ca/settings.json | 33 - webview-ui/src/i18n/locales/de/chat.json | 38 - webview-ui/src/i18n/locales/de/prompts.json | 1 - webview-ui/src/i18n/locales/de/settings.json | 33 - webview-ui/src/i18n/locales/en/chat.json | 38 - webview-ui/src/i18n/locales/en/prompts.json | 1 - webview-ui/src/i18n/locales/en/settings.json | 33 - webview-ui/src/i18n/locales/es/chat.json | 38 - webview-ui/src/i18n/locales/es/prompts.json | 1 - webview-ui/src/i18n/locales/es/settings.json | 33 - webview-ui/src/i18n/locales/fr/chat.json | 38 - webview-ui/src/i18n/locales/fr/prompts.json | 1 - webview-ui/src/i18n/locales/fr/settings.json | 33 - webview-ui/src/i18n/locales/hi/chat.json | 38 - webview-ui/src/i18n/locales/hi/prompts.json | 1 - webview-ui/src/i18n/locales/hi/settings.json | 33 - webview-ui/src/i18n/locales/id/chat.json | 38 - webview-ui/src/i18n/locales/id/prompts.json | 1 - webview-ui/src/i18n/locales/id/settings.json | 33 - webview-ui/src/i18n/locales/it/chat.json | 38 - webview-ui/src/i18n/locales/it/prompts.json | 1 - webview-ui/src/i18n/locales/it/settings.json | 33 - webview-ui/src/i18n/locales/ja/chat.json | 38 - webview-ui/src/i18n/locales/ja/prompts.json | 1 - webview-ui/src/i18n/locales/ja/settings.json | 33 - webview-ui/src/i18n/locales/ko/chat.json | 38 - webview-ui/src/i18n/locales/ko/prompts.json | 1 - webview-ui/src/i18n/locales/ko/settings.json | 33 - webview-ui/src/i18n/locales/nl/chat.json | 38 - webview-ui/src/i18n/locales/nl/prompts.json | 1 - webview-ui/src/i18n/locales/nl/settings.json | 33 - webview-ui/src/i18n/locales/pl/chat.json | 38 - webview-ui/src/i18n/locales/pl/prompts.json | 1 - webview-ui/src/i18n/locales/pl/settings.json | 33 - webview-ui/src/i18n/locales/pt-BR/chat.json | 38 - .../src/i18n/locales/pt-BR/prompts.json | 1 - .../src/i18n/locales/pt-BR/settings.json | 33 - webview-ui/src/i18n/locales/ru/chat.json | 38 - webview-ui/src/i18n/locales/ru/prompts.json | 1 - webview-ui/src/i18n/locales/ru/settings.json | 33 - webview-ui/src/i18n/locales/tr/chat.json | 38 - webview-ui/src/i18n/locales/tr/prompts.json | 1 - webview-ui/src/i18n/locales/tr/settings.json | 33 - webview-ui/src/i18n/locales/vi/chat.json | 38 - webview-ui/src/i18n/locales/vi/prompts.json | 1 - webview-ui/src/i18n/locales/vi/settings.json | 33 - webview-ui/src/i18n/locales/zh-CN/chat.json | 38 - .../src/i18n/locales/zh-CN/prompts.json | 1 - .../src/i18n/locales/zh-CN/settings.json | 33 - webview-ui/src/i18n/locales/zh-TW/chat.json | 38 - .../src/i18n/locales/zh-TW/prompts.json | 1 - .../src/i18n/locales/zh-TW/settings.json | 33 - webview-ui/vite.config.ts | 3 +- 165 files changed, 419 insertions(+), 8703 deletions(-) delete mode 100644 apps/cli/src/ui/components/tools/BrowserTool.tsx delete mode 100644 src/core/prompts/tools/native-tools/browser_action.ts delete mode 100644 src/core/tools/BrowserActionTool.ts delete mode 100644 src/core/tools/__tests__/BrowserActionTool.coordinateScaling.spec.ts delete mode 100644 src/core/tools/__tests__/BrowserActionTool.screenshot.spec.ts delete mode 100644 src/core/webview/BrowserSessionPanelManager.ts delete mode 100644 src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts delete mode 100644 src/services/browser/BrowserSession.ts delete mode 100644 src/services/browser/UrlContentFetcher.ts delete mode 100644 src/services/browser/__tests__/BrowserSession.spec.ts delete mode 100644 src/services/browser/__tests__/UrlContentFetcher.spec.ts delete mode 100644 src/services/browser/browserDiscovery.ts delete mode 100644 src/shared/browserUtils.ts delete mode 100644 webview-ui/browser-panel.html delete mode 100644 webview-ui/src/browser-panel.tsx delete mode 100644 webview-ui/src/components/browser-session/BrowserPanelStateProvider.tsx delete mode 100644 webview-ui/src/components/browser-session/BrowserSessionPanel.tsx delete mode 100644 webview-ui/src/components/chat/BrowserActionRow.tsx delete mode 100644 webview-ui/src/components/chat/BrowserSessionRow.tsx delete mode 100644 webview-ui/src/components/chat/BrowserSessionStatusRow.tsx delete mode 100644 webview-ui/src/components/chat/__tests__/BrowserSessionRow.aspect-ratio.spec.tsx delete mode 100644 webview-ui/src/components/chat/__tests__/BrowserSessionRow.disconnect-button.spec.tsx delete mode 100644 webview-ui/src/components/chat/__tests__/BrowserSessionRow.spec.tsx delete mode 100644 webview-ui/src/components/settings/BrowserSettings.tsx diff --git a/apps/cli/src/agent/__tests__/extension-client.test.ts b/apps/cli/src/agent/__tests__/extension-client.test.ts index 3d87a30200f..7a63fe0174c 100644 --- a/apps/cli/src/agent/__tests__/extension-client.test.ts +++ b/apps/cli/src/agent/__tests__/extension-client.test.ts @@ -93,13 +93,6 @@ describe("detectAgentState", () => { expect(state.requiredAction).toBe("answer") }) - it("should detect waiting for browser_action_launch approval", () => { - const messages = [createMessage({ type: "ask", ask: "browser_action_launch", partial: false })] - const state = detectAgentState(messages) - expect(state.state).toBe(AgentLoopState.WAITING_FOR_INPUT) - expect(state.requiredAction).toBe("approve") - }) - it("should detect waiting for use_mcp_server approval", () => { const messages = [createMessage({ type: "ask", ask: "use_mcp_server", partial: false })] const state = detectAgentState(messages) @@ -202,7 +195,6 @@ describe("Type Guards", () => { expect(isInteractiveAsk("tool")).toBe(true) expect(isInteractiveAsk("command")).toBe(true) expect(isInteractiveAsk("followup")).toBe(true) - expect(isInteractiveAsk("browser_action_launch")).toBe(true) expect(isInteractiveAsk("use_mcp_server")).toBe(true) }) diff --git a/apps/cli/src/agent/agent-state.ts b/apps/cli/src/agent/agent-state.ts index ca4a099ccab..d1451d62fdd 100644 --- a/apps/cli/src/agent/agent-state.ts +++ b/apps/cli/src/agent/agent-state.ts @@ -116,7 +116,7 @@ export enum AgentLoopState { */ export type RequiredAction = | "none" // No action needed (running/streaming) - | "approve" // Can approve/reject (tool, command, browser, mcp) + | "approve" // Can approve/reject (tool, command, mcp) | "answer" // Need to answer a question (followup) | "retry_or_new_task" // Can retry or start new task (api_req_failed) | "proceed_or_new_task" // Can proceed or start new task (mistake_limit) @@ -221,7 +221,6 @@ function getRequiredAction(ask: ClineAsk): RequiredAction { return "answer" case "command": case "tool": - case "browser_action_launch": case "use_mcp_server": return "approve" case "command_output": @@ -264,8 +263,6 @@ function getStateDescription(state: AgentLoopState, ask?: ClineAsk): string { return "Agent wants to execute a command. Approve or reject." case "tool": return "Agent wants to perform a file operation. Approve or reject." - case "browser_action_launch": - return "Agent wants to use the browser. Approve or reject." case "use_mcp_server": return "Agent wants to use an MCP server. Approve or reject." default: diff --git a/apps/cli/src/agent/ask-dispatcher.ts b/apps/cli/src/agent/ask-dispatcher.ts index fe8c557d8d8..44e861ae9b8 100644 --- a/apps/cli/src/agent/ask-dispatcher.ts +++ b/apps/cli/src/agent/ask-dispatcher.ts @@ -244,7 +244,7 @@ export class AskDispatcher { } /** - * Handle interactive asks (followup, command, tool, browser_action_launch, use_mcp_server). + * Handle interactive asks (followup, command, tool, use_mcp_server). * These require user approval or input. */ private async handleInteractiveAsk(ts: number, ask: ClineAsk, text: string): Promise { @@ -258,9 +258,6 @@ export class AskDispatcher { case "tool": return await this.handleToolApproval(ts, text) - case "browser_action_launch": - return await this.handleBrowserApproval(ts, text) - case "use_mcp_server": return await this.handleMcpApproval(ts, text) @@ -444,32 +441,6 @@ export class AskDispatcher { } } - /** - * Handle browser action approval. - */ - private async handleBrowserApproval(ts: number, text: string): Promise { - this.outputManager.output("\n[browser action request]") - if (text) { - this.outputManager.output(` Action: ${text}`) - } - this.outputManager.markDisplayed(ts, text || "", false) - - if (this.nonInteractive) { - // Auto-approved by extension settings - return { handled: true } - } - - try { - const approved = await this.promptManager.promptForYesNo("Allow browser action? (y/n): ") - this.sendApprovalResponse(approved) - return { handled: true, response: approved ? "yesButtonClicked" : "noButtonClicked" } - } catch { - this.outputManager.output("[Defaulting to: no]") - this.sendApprovalResponse(false) - return { handled: true, response: "noButtonClicked" } - } - } - /** * Handle MCP server access approval. */ diff --git a/apps/cli/src/agent/extension-host.ts b/apps/cli/src/agent/extension-host.ts index 42edff12146..4a0e941b4bb 100644 --- a/apps/cli/src/agent/extension-host.ts +++ b/apps/cli/src/agent/extension-host.ts @@ -214,7 +214,6 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac const baseSettings: RooCodeSettings = { mode: this.options.mode, commandExecutionTimeout: 30, - browserToolEnabled: false, enableCheckpoints: false, ...getProviderSettings(this.options.provider, this.options.apiKey, this.options.model), } @@ -227,7 +226,6 @@ export class ExtensionHost extends EventEmitter implements ExtensionHostInterfac alwaysAllowWrite: true, alwaysAllowWriteOutsideWorkspace: true, alwaysAllowWriteProtected: true, - alwaysAllowBrowser: true, alwaysAllowMcp: true, alwaysAllowModeSwitch: true, alwaysAllowSubtasks: true, diff --git a/apps/cli/src/agent/json-event-emitter.ts b/apps/cli/src/agent/json-event-emitter.ts index a1a404e5556..578c52d2b80 100644 --- a/apps/cli/src/agent/json-event-emitter.ts +++ b/apps/cli/src/agent/json-event-emitter.ts @@ -258,15 +258,6 @@ export class JsonEventEmitter { break } - case "browser_action": - case "browser_action_result": - this.emitEvent({ - type: "tool_result", - subtype: "browser", - tool_result: { name: "browser_action", output: msg.text }, - }) - break - case "mcp_server_response": this.emitEvent({ type: "tool_result", @@ -336,15 +327,6 @@ export class JsonEventEmitter { }) break - case "browser_action_launch": - this.emitEvent({ - type: "tool_use", - id: msg.ts, - subtype: "browser", - tool_use: { name: "browser_action", input: { raw: msg.text } }, - }) - break - case "use_mcp_server": this.emitEvent({ type: "tool_use", diff --git a/apps/cli/src/ui/components/ChatHistoryItem.tsx b/apps/cli/src/ui/components/ChatHistoryItem.tsx index c51b0faddbc..e5bbc79366c 100644 --- a/apps/cli/src/ui/components/ChatHistoryItem.tsx +++ b/apps/cli/src/ui/components/ChatHistoryItem.tsx @@ -10,14 +10,13 @@ import { getToolRenderer } from "./tools/index.js" /** * Tool categories for styling */ -type ToolCategory = "file" | "directory" | "search" | "command" | "browser" | "mode" | "completion" | "other" +type ToolCategory = "file" | "directory" | "search" | "command" | "mode" | "completion" | "other" function getToolCategory(toolName: string): ToolCategory { const fileTools = ["readFile", "read_file", "writeToFile", "write_to_file", "applyDiff", "apply_diff"] const dirTools = ["listFiles", "list_files", "listFilesRecursive", "listFilesTopLevel"] const searchTools = ["searchFiles", "search_files"] const commandTools = ["executeCommand", "execute_command"] - const browserTools = ["browserAction", "browser_action"] const modeTools = ["switchMode", "switch_mode", "newTask", "new_task"] const completionTools = ["attemptCompletion", "attempt_completion", "askFollowupQuestion", "ask_followup_question"] @@ -25,7 +24,6 @@ function getToolCategory(toolName: string): ToolCategory { if (dirTools.includes(toolName)) return "directory" if (searchTools.includes(toolName)) return "search" if (commandTools.includes(toolName)) return "command" - if (browserTools.includes(toolName)) return "browser" if (modeTools.includes(toolName)) return "mode" if (completionTools.includes(toolName)) return "completion" return "other" @@ -39,7 +37,6 @@ const CATEGORY_COLORS: Record = { directory: theme.toolHeader, search: theme.warningColor, command: theme.successColor, - browser: theme.focusColor, mode: theme.userHeader, completion: theme.successColor, other: theme.toolHeader, diff --git a/apps/cli/src/ui/components/tools/BrowserTool.tsx b/apps/cli/src/ui/components/tools/BrowserTool.tsx deleted file mode 100644 index 5e6d51857ab..00000000000 --- a/apps/cli/src/ui/components/tools/BrowserTool.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { Box, Text } from "ink" - -import * as theme from "../../theme.js" -import { Icon } from "../Icon.js" - -import type { ToolRendererProps } from "./types.js" -import { getToolDisplayName, getToolIconName } from "./utils.js" - -const ACTION_LABELS: Record = { - launch: "Launch Browser", - click: "Click", - hover: "Hover", - type: "Type Text", - press: "Press Key", - scroll_down: "Scroll Down", - scroll_up: "Scroll Up", - resize: "Resize Window", - close: "Close Browser", - screenshot: "Take Screenshot", -} - -export function BrowserTool({ toolData }: ToolRendererProps) { - const iconName = getToolIconName(toolData.tool) - const displayName = getToolDisplayName(toolData.tool) - const action = toolData.action || "" - const url = toolData.url || "" - const coordinate = toolData.coordinate || "" - const content = toolData.content || "" // May contain text for type action. - - const actionLabel = ACTION_LABELS[action] || action - - return ( - - {/* Header */} - - - - {" "} - {displayName} - - {action && ( - - {" "} - → {actionLabel} - - )} - - - {/* Action details */} - - {/* URL for launch action */} - {url && ( - - url: - - {url} - - - )} - - {/* Coordinates for click/hover actions */} - {coordinate && ( - - at: - {coordinate} - - )} - - {/* Text content for type action */} - {content && action === "type" && ( - - text: - "{content}" - - )} - - {/* Key for press action */} - {content && action === "press" && ( - - key: - {content} - - )} - - - ) -} diff --git a/apps/cli/src/ui/components/tools/index.ts b/apps/cli/src/ui/components/tools/index.ts index c6284320029..e5f5527c2f2 100644 --- a/apps/cli/src/ui/components/tools/index.ts +++ b/apps/cli/src/ui/components/tools/index.ts @@ -15,7 +15,6 @@ import { FileReadTool } from "./FileReadTool.js" import { FileWriteTool } from "./FileWriteTool.js" import { SearchTool } from "./SearchTool.js" import { CommandTool } from "./CommandTool.js" -import { BrowserTool } from "./BrowserTool.js" import { ModeTool } from "./ModeTool.js" import { CompletionTool } from "./CompletionTool.js" import { GenericTool } from "./GenericTool.js" @@ -32,7 +31,6 @@ export { FileReadTool } from "./FileReadTool.js" export { FileWriteTool } from "./FileWriteTool.js" export { SearchTool } from "./SearchTool.js" export { CommandTool } from "./CommandTool.js" -export { BrowserTool } from "./BrowserTool.js" export { ModeTool } from "./ModeTool.js" export { CompletionTool } from "./CompletionTool.js" export { GenericTool } from "./GenericTool.js" @@ -45,7 +43,6 @@ const CATEGORY_RENDERERS: Record> = { "file-write": FileWriteTool, search: SearchTool, command: CommandTool, - browser: BrowserTool, mode: ModeTool, completion: CompletionTool, other: GenericTool, diff --git a/apps/cli/src/ui/components/tools/types.ts b/apps/cli/src/ui/components/tools/types.ts index a16fbd60ea3..29c8444af1d 100644 --- a/apps/cli/src/ui/components/tools/types.ts +++ b/apps/cli/src/ui/components/tools/types.ts @@ -5,15 +5,7 @@ export interface ToolRendererProps { rawContent?: string } -export type ToolCategory = - | "file-read" - | "file-write" - | "search" - | "command" - | "browser" - | "mode" - | "completion" - | "other" +export type ToolCategory = "file-read" | "file-write" | "search" | "command" | "mode" | "completion" | "other" export function getToolCategory(toolName: string): ToolCategory { const fileReadTools = ["readFile", "read_file", "skill", "listFilesTopLevel", "listFilesRecursive", "list_files"] @@ -29,7 +21,6 @@ export function getToolCategory(toolName: string): ToolCategory { const searchTools = ["searchFiles", "search_files", "codebaseSearch", "codebase_search"] const commandTools = ["execute_command", "executeCommand"] - const browserTools = ["browser_action", "browserAction"] const modeTools = ["switchMode", "switch_mode", "newTask", "new_task", "finishTask"] const completionTools = ["attempt_completion", "attemptCompletion", "ask_followup_question", "askFollowupQuestion"] @@ -37,7 +28,6 @@ export function getToolCategory(toolName: string): ToolCategory { if (fileWriteTools.includes(toolName)) return "file-write" if (searchTools.includes(toolName)) return "search" if (commandTools.includes(toolName)) return "command" - if (browserTools.includes(toolName)) return "browser" if (modeTools.includes(toolName)) return "mode" if (completionTools.includes(toolName)) return "completion" return "other" diff --git a/apps/cli/src/ui/components/tools/utils.ts b/apps/cli/src/ui/components/tools/utils.ts index 31acf2cccbc..484125dbb2e 100644 --- a/apps/cli/src/ui/components/tools/utils.ts +++ b/apps/cli/src/ui/components/tools/utils.ts @@ -73,10 +73,6 @@ export function getToolDisplayName(toolName: string): string { execute_command: "Execute Command", executeCommand: "Execute Command", - // Browser operations - browser_action: "Browser Action", - browserAction: "Browser Action", - // Mode operations switchMode: "Switch Mode", switch_mode: "Switch Mode", @@ -129,10 +125,6 @@ export function getToolIconName(toolName: string): IconName { execute_command: "terminal", executeCommand: "terminal", - // Browser operations - browser_action: "browser", - browserAction: "browser", - // Mode operations switchMode: "switch", switch_mode: "switch", diff --git a/apps/cli/src/ui/types.ts b/apps/cli/src/ui/types.ts index c2187fb2b66..3c45377c675 100644 --- a/apps/cli/src/ui/types.ts +++ b/apps/cli/src/ui/types.ts @@ -40,14 +40,6 @@ export interface ToolData { /** Command output */ output?: string - // Browser operation fields - /** Browser action type */ - action?: string - /** Browser URL */ - url?: string - /** Click/hover coordinates */ - coordinate?: string - // Batch operation fields /** Batch file reads */ batchFiles?: Array<{ diff --git a/apps/cli/src/ui/utils/tools.ts b/apps/cli/src/ui/utils/tools.ts index be3ff9484db..b79a506571d 100644 --- a/apps/cli/src/ui/utils/tools.ts +++ b/apps/cli/src/ui/utils/tools.ts @@ -57,17 +57,6 @@ export function extractToolData(toolInfo: Record): ToolData { toolData.output = toolInfo.output as string } - // Extract browser-related fields - if (toolInfo.action !== undefined) { - toolData.action = toolInfo.action as string - } - if (toolInfo.url !== undefined) { - toolData.url = toolInfo.url as string - } - if (toolInfo.coordinate !== undefined) { - toolData.coordinate = toolInfo.coordinate as string - } - // Extract batch file operations if (Array.isArray(toolInfo.files)) { toolData.batchFiles = (toolInfo.files as Array>).map((f) => ({ @@ -165,12 +154,6 @@ export function formatToolOutput(toolInfo: Record): string { return `📁 ${listPath || "."}${recursive ? " (recursive)" : ""}` } - case "browser_action": { - const action = toolInfo.action as string - const url = toolInfo.url as string - return `🌐 ${action || "action"}${url ? `: ${url}` : ""}` - } - case "attempt_completion": { const result = toolInfo.result as string if (result) { @@ -248,12 +231,6 @@ export function formatToolAskMessage(toolInfo: Record): string return `Apply changes to: ${diffPath || "(no path)"}` } - case "browser_action": { - const action = toolInfo.action as string - const url = toolInfo.url as string - return `Browser: ${action || "action"}${url ? ` - ${url}` : ""}` - } - default: { const params = Object.entries(toolInfo) .filter(([key]) => key !== "tool") diff --git a/packages/evals/src/db/queries/__tests__/copyRun.spec.ts b/packages/evals/src/db/queries/__tests__/copyRun.spec.ts index 1537ac1ddbc..606a3d0281d 100644 --- a/packages/evals/src/db/queries/__tests__/copyRun.spec.ts +++ b/packages/evals/src/db/queries/__tests__/copyRun.spec.ts @@ -138,8 +138,8 @@ describe("copyRun", () => { const toolError3 = await createToolError({ runId: sourceRunId, taskId: null, - toolName: "browser_action", - error: "Browser connection timeout", + toolName: "write_to_file", + error: "Write timeout", }) sourceToolErrorIds.push(toolError3.id) @@ -234,8 +234,8 @@ describe("copyRun", () => { expect(taskToolErrors).toHaveLength(2) expect(runToolErrors).toHaveLength(1) - const browserError = runToolErrors.find((te) => te.toolName === "browser_action")! - expect(browserError.error).toBe("Browser connection timeout") + const writeError = runToolErrors.find((te) => te.toolName === "write_to_file")! + expect(writeError.error).toBe("Write timeout") await db.delete(schema.toolErrors).where(eq(schema.toolErrors.runId, newRunId)) await db.delete(schema.tasks).where(eq(schema.tasks.runId, newRunId)) diff --git a/packages/types/src/__tests__/cloud.test.ts b/packages/types/src/__tests__/cloud.test.ts index be8d631ce0a..4e9e792a295 100644 --- a/packages/types/src/__tests__/cloud.test.ts +++ b/packages/types/src/__tests__/cloud.test.ts @@ -487,11 +487,11 @@ describe("userSettingsConfigSchema with llmEnhancedFeaturesEnabled", () => { describe("organizationDefaultSettingsSchema with disabledTools", () => { it("should accept disabledTools as an array of valid tool names", () => { const input: OrganizationDefaultSettings = { - disabledTools: ["execute_command", "browser_action"], + disabledTools: ["execute_command", "write_to_file"], } const result = organizationDefaultSettingsSchema.safeParse(input) expect(result.success).toBe(true) - expect(result.data?.disabledTools).toEqual(["execute_command", "browser_action"]) + expect(result.data?.disabledTools).toEqual(["execute_command", "write_to_file"]) }) it("should accept empty disabledTools array", () => { diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index ddb62206097..5886bd18df4 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -102,7 +102,6 @@ export const globalSettingsSchema = z.object({ alwaysAllowWriteOutsideWorkspace: z.boolean().optional(), alwaysAllowWriteProtected: z.boolean().optional(), writeDelayMs: z.number().min(0).optional(), - alwaysAllowBrowser: z.boolean().optional(), requestDelaySeconds: z.number().optional(), alwaysAllowMcp: z.boolean().optional(), alwaysAllowModeSwitch: z.boolean().optional(), @@ -148,11 +147,7 @@ export const globalSettingsSchema = z.object({ */ maxDiagnosticMessages: z.number().optional(), - browserToolEnabled: z.boolean().optional(), - browserViewportSize: z.string().optional(), screenshotQuality: z.number().optional(), - remoteBrowserEnabled: z.boolean().optional(), - remoteBrowserHost: z.string().optional(), cachedChromeHostUrl: z.string().optional(), enableCheckpoints: z.boolean().optional(), @@ -338,7 +333,6 @@ export const EVALS_SETTINGS: RooCodeSettings = { alwaysAllowWriteOutsideWorkspace: false, alwaysAllowWriteProtected: false, writeDelayMs: 1000, - alwaysAllowBrowser: true, requestDelaySeconds: 10, alwaysAllowMcp: true, alwaysAllowModeSwitch: true, @@ -351,10 +345,7 @@ export const EVALS_SETTINGS: RooCodeSettings = { commandTimeoutAllowlist: [], preventCompletionWithOpenTodos: false, - browserToolEnabled: false, - browserViewportSize: "900x600", screenshotQuality: 75, - remoteBrowserEnabled: false, ttsEnabled: false, ttsSpeed: 1, diff --git a/packages/types/src/message.ts b/packages/types/src/message.ts index a725cb094d0..e518972a1c2 100644 --- a/packages/types/src/message.ts +++ b/packages/types/src/message.ts @@ -21,7 +21,6 @@ import { z } from "zod" * - `resume_task`: Confirmation needed to resume a previously paused task * - `resume_completed_task`: Confirmation needed to resume a task that was already marked as completed * - `mistake_limit_reached`: Too many errors encountered, needs user guidance on how to proceed - * - `browser_action_launch`: Permission to open or interact with a browser * - `use_mcp_server`: Permission to use Model Context Protocol (MCP) server functionality * - `auto_approval_max_req_reached`: Auto-approval limit has been reached, manual approval required */ @@ -35,7 +34,6 @@ export const clineAsks = [ "resume_task", "resume_completed_task", "mistake_limit_reached", - "browser_action_launch", "use_mcp_server", "auto_approval_max_req_reached", ] as const @@ -83,13 +81,7 @@ export function isResumableAsk(ask: ClineAsk): ask is ResumableAsk { * Asks that put the task into an "user interaction required" state. */ -export const interactiveAsks = [ - "followup", - "command", - "tool", - "browser_action_launch", - "use_mcp_server", -] as const satisfies readonly ClineAsk[] +export const interactiveAsks = ["followup", "command", "tool", "use_mcp_server"] as const satisfies readonly ClineAsk[] export type InteractiveAsk = (typeof interactiveAsks)[number] @@ -138,8 +130,6 @@ export function isNonBlockingAsk(ask: ClineAsk): ask is NonBlockingAsk { * - `user_feedback_diff`: Diff-formatted feedback from user showing requested changes * - `command_output`: Output from an executed command * - `shell_integration_warning`: Warning about shell integration issues or limitations - * - `browser_action`: Action performed in the browser - * - `browser_action_result`: Result of a browser action * - `mcp_server_request_started`: MCP server request has been initiated * - `mcp_server_response`: Response received from MCP server * - `subtask_result`: Result of a completed subtask @@ -167,9 +157,6 @@ export const clineSays = [ "user_feedback_diff", "command_output", "shell_integration_warning", - "browser_action", - "browser_action_result", - "browser_session_status", "mcp_server_request_started", "mcp_server_response", "subtask_result", diff --git a/packages/types/src/mode.ts b/packages/types/src/mode.ts index c02c47c1345..d01ffa69902 100644 --- a/packages/types/src/mode.ts +++ b/packages/types/src/mode.ts @@ -142,7 +142,7 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [ whenToUse: "Use this mode when you need to plan, design, or strategize before implementation. Perfect for breaking down complex problems, creating technical specifications, designing system architecture, or brainstorming solutions before coding.", description: "Plan and design before implementation", - groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "browser", "mcp"], + groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "mcp"], customInstructions: "1. Do some information gathering (using provided tools) to get more context about the task.\n\n2. You should also ask the user clarifying questions to get a better understanding of the task.\n\n3. Once you've gained more context about the user's request, break down the task into clear, actionable steps and create a todo list using the `update_todo_list` tool. Each todo item should be:\n - Specific and actionable\n - Listed in logical execution order\n - Focused on a single, well-defined outcome\n - Clear enough that another mode could execute it independently\n\n **Note:** If the `update_todo_list` tool is not available, write the plan to a markdown file (e.g., `plan.md` or `todo.md`) instead.\n\n4. As you gather more information or discover new requirements, update the todo list to reflect the current understanding of what needs to be accomplished.\n\n5. Ask the user if they are pleased with this plan, or if they would like to make any changes. Think of this as a brainstorming session where you can discuss the task and refine the todo list.\n\n6. Include Mermaid diagrams if they help clarify complex workflows or system architecture. Please avoid using double quotes (\"\") and parentheses () inside square brackets ([]) in Mermaid diagrams, as this can cause parsing errors.\n\n7. Use the switch_mode tool to request that the user switch to another mode to implement the solution.\n\n**IMPORTANT: Focus on creating clear, actionable todo lists rather than lengthy markdown documents. Use the todo list as your primary planning tool to track and organize the work that needs to be done.**\n\n**CRITICAL: Never provide level of effort time estimates (e.g., hours, days, weeks) for tasks. Focus solely on breaking down the work into clear, actionable steps without estimating how long they will take.**\n\nUnless told otherwise, if you want to save a plan file, put it in the /plans directory", }, @@ -154,7 +154,7 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [ whenToUse: "Use this mode when you need to write, modify, or refactor code. Ideal for implementing features, fixing bugs, creating new files, or making code improvements across any programming language or framework.", description: "Write, modify, and refactor code", - groups: ["read", "edit", "browser", "command", "mcp"], + groups: ["read", "edit", "command", "mcp"], }, { slug: "ask", @@ -164,7 +164,7 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [ whenToUse: "Use this mode when you need explanations, documentation, or answers to technical questions. Best for understanding concepts, analyzing existing code, getting recommendations, or learning about technologies without making changes.", description: "Get answers and explanations", - groups: ["read", "browser", "mcp"], + groups: ["read", "mcp"], customInstructions: "You can analyze code, explain concepts, and access external resources. Always answer the user's questions thoroughly, and do not switch to implementing code unless explicitly requested by the user. Include Mermaid diagrams when they clarify your response.", }, @@ -176,7 +176,7 @@ export const DEFAULT_MODES: readonly ModeConfig[] = [ whenToUse: "Use this mode when you're troubleshooting issues, investigating errors, or diagnosing problems. Specialized in systematic debugging, adding logging, analyzing stack traces, and identifying root causes before applying fixes.", description: "Diagnose and fix software issues", - groups: ["read", "edit", "browser", "command", "mcp"], + groups: ["read", "edit", "command", "mcp"], customInstructions: "Reflect on 5-7 different possible sources of the problem, distill those down to 1-2 most likely sources, and then add logs to validate your assumptions. Explicitly ask the user to confirm the diagnosis before fixing the problem.", }, diff --git a/packages/types/src/tool-params.ts b/packages/types/src/tool-params.ts index 75be318d8c0..8c3c4d8d8a3 100644 --- a/packages/types/src/tool-params.ts +++ b/packages/types/src/tool-params.ts @@ -102,15 +102,6 @@ export interface Size { height: number } -export interface BrowserActionParams { - action: "launch" | "click" | "hover" | "type" | "scroll_down" | "scroll_up" | "resize" | "close" | "screenshot" - url?: string - coordinate?: Coordinate - size?: Size - text?: string - path?: string -} - export interface GenerateImageParams { prompt: string path: string diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index a8ea826d11d..e43bb647ec6 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -4,7 +4,7 @@ import { z } from "zod" * ToolGroup */ -export const toolGroups = ["read", "edit", "browser", "command", "mcp", "modes"] as const +export const toolGroups = ["read", "edit", "command", "mcp", "modes"] as const export const toolGroupsSchema = z.enum(toolGroups) @@ -27,7 +27,6 @@ export const toolNames = [ "apply_patch", "search_files", "list_files", - "browser_action", "use_mcp_tool", "access_mcp_resource", "ask_followup_question", diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index a727a251035..47a351e1abe 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -59,9 +59,6 @@ export interface ExtensionMessage { | "deleteCustomModeCheck" | "currentCheckpointUpdated" | "checkpointInitWarning" - | "browserToolEnabled" - | "browserConnectionResult" - | "remoteBrowserEnabled" | "ttsStart" | "ttsStop" | "fileSearchResults" @@ -92,8 +89,6 @@ export interface ExtensionMessage { | "dismissedUpsells" | "organizationSwitchResult" | "interactionRequired" - | "browserSessionUpdate" - | "browserSessionNavigate" | "customToolsResult" | "modes" | "taskWithAggregatedCosts" @@ -180,9 +175,6 @@ export interface ExtensionMessage { queuedMessages?: QueuedMessage[] list?: string[] // For dismissedUpsells organizationId?: string | null // For organizationSwitchResult - browserSessionMessages?: ClineMessage[] // For browser session panel updates - isBrowserSessionActive?: boolean // For browser session panel updates - stepIndex?: number // For browserSessionNavigate: the target step index to display tools?: SerializedCustomToolDefinition[] // For customToolsResult modes?: { slug: string; name: string }[] // For modes response skills?: SkillMetadata[] // For skills response @@ -264,7 +256,6 @@ export type ExtensionState = Pick< | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" | "alwaysAllowWriteProtected" - | "alwaysAllowBrowser" | "alwaysAllowMcp" | "alwaysAllowModeSwitch" | "alwaysAllowSubtasks" @@ -275,12 +266,8 @@ export type ExtensionState = Pick< | "deniedCommands" | "allowedMaxRequests" | "allowedMaxCost" - | "browserToolEnabled" - | "browserViewportSize" | "screenshotQuality" - | "remoteBrowserEnabled" | "cachedChromeHostUrl" - | "remoteBrowserHost" | "ttsEnabled" | "ttsSpeed" | "soundEnabled" @@ -367,8 +354,6 @@ export type ExtensionState = Pick< organizationAllowList: OrganizationAllowList organizationSettingsVersion?: number - isBrowserSessionActive: boolean // Actual browser session state - autoCondenseContext: boolean autoCondenseContextPercent: number marketplaceItems?: MarketplaceItem[] @@ -508,8 +493,6 @@ export interface WebviewMessage { | "deleteMcpServer" | "codebaseIndexEnabled" | "telemetrySetting" - | "testBrowserConnection" - | "browserConnectionResult" | "searchFiles" | "toggleApiConfigPin" | "hasOpenedModeSelector" @@ -566,11 +549,6 @@ export interface WebviewMessage { | "allowedCommands" | "getTaskWithAggregatedCosts" | "deniedCommands" - | "killBrowserSession" - | "openBrowserSessionPanel" - | "showBrowserSessionPanelAtStep" - | "refreshBrowserSessionPanel" - | "browserPanelDidLaunch" | "openDebugApiHistory" | "openDebugUiHistory" | "downloadErrorDiagnostics" @@ -852,39 +830,6 @@ export interface ClineSayTool { skill?: string } -// Must keep in sync with system prompt. -export const browserActions = [ - "launch", - "click", - "hover", - "type", - "press", - "scroll_down", - "scroll_up", - "resize", - "close", - "screenshot", -] as const - -export type BrowserAction = (typeof browserActions)[number] - -export interface ClineSayBrowserAction { - action: BrowserAction - coordinate?: string - size?: string - text?: string - executedCoordinate?: string -} - -export type BrowserActionResult = { - screenshot?: string - logs?: string - currentUrl?: string - currentMousePosition?: string - viewportWidth?: number - viewportHeight?: number -} - export interface ClineAskUseMcpServer { serverName: string type: "use_mcp_tool" | "access_mcp_resource" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 78c1b161775..0e7846fa2d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -805,7 +805,7 @@ importers: version: 1.14.0(typescript@5.8.3) '@requesty/ai-sdk': specifier: ^3.0.0 - version: 3.0.0(vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(zod@3.25.76) + version: 3.0.0(vite@6.3.6(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(zod@3.25.76) '@roo-code/cloud': specifier: workspace:^ version: link:../packages/cloud @@ -938,12 +938,6 @@ importers: ps-tree: specifier: ^1.2.0 version: 1.2.0 - puppeteer-chromium-resolver: - specifier: ^24.0.0 - version: 24.0.1 - puppeteer-core: - specifier: ^23.4.0 - version: 23.11.1 reconnecting-eventsource: specifier: ^1.6.4 version: 1.6.4 @@ -3039,16 +3033,6 @@ packages: '@protobufjs/utf8@1.1.0': resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} - '@puppeteer/browsers@2.10.5': - resolution: {integrity: sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==} - engines: {node: '>=18'} - hasBin: true - - '@puppeteer/browsers@2.6.1': - resolution: {integrity: sha512-aBSREisdsGH890S2rQqK82qmQYU3uFpSH8wcZWHgHzl3LfzsxAKbLNiAG9mO8v1Y0UICBeClICxPJvyr0rcuxg==} - engines: {node: '>=18'} - hasBin: true - '@qdrant/js-client-rest@1.14.0': resolution: {integrity: sha512-2sM2g17FSkN2sNCSeAfqxHRr+SPEVnUQLXBjVv/whm4YQ4JjZ53Jiy1iShk95G+xBf3hKBhJdj8itRnor03IYw==} engines: {node: '>=18.0.0', pnpm: '>=8'} @@ -4362,9 +4346,6 @@ packages: peerDependencies: '@testing-library/dom': '>=7.21.4' - '@tootallnate/quickjs-emscripten@0.23.0': - resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} - '@trpc/client@11.8.1': resolution: {integrity: sha512-L/SJFGanr9xGABmuDoeXR4xAdHJmsXsiF9OuH+apecJ+8sUITzVT1EPeqp0ebqA6lBhEl5pPfg3rngVhi/h60Q==} peerDependencies: @@ -4684,9 +4665,6 @@ packages: '@types/yargs@17.0.33': resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.32.1': resolution: {integrity: sha512-6u6Plg9nP/J1GRpe/vcjjabo6Uc5YQPAMxsgQyGC/I0RuukiG1wIe3+Vtg3IrSCVJDmqK3j8adrtzXSENRtFgg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4961,9 +4939,6 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} engines: {node: '>= 6'} @@ -5040,10 +5015,6 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-types@0.13.4: - resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} - engines: {node: '>=4'} - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -5137,10 +5108,6 @@ packages: resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} hasBin: true - basic-ftp@5.0.5: - resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==} - engines: {node: '>=10.0.0'} - better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -5374,16 +5341,6 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} - chromium-bidi@0.11.0: - resolution: {integrity: sha512-6CJWHkNRoyZyjV9Rwv2lYONZf1Xm0IuDyNq97nwSsxxP3wf5Bwy15K5rOvVKMtJ127jJBmxFUanSAOjgFRxgrA==} - peerDependencies: - devtools-protocol: '*' - - chromium-bidi@5.1.0: - resolution: {integrity: sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==} - peerDependencies: - devtools-protocol: '*' - ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -5481,10 +5438,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -5544,9 +5497,6 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - content-disposition@1.0.0: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} engines: {node: '>= 0.6'} @@ -5823,10 +5773,6 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} - data-uri-to-buffer@6.0.2: - resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} - engines: {node: '>= 14'} - data-urls@5.0.0: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} @@ -5948,10 +5894,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - degenerator@5.0.1: - resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} - engines: {node: '>= 14'} - delaunator@5.0.1: resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} @@ -6000,12 +5942,6 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - devtools-protocol@0.0.1367902: - resolution: {integrity: sha512-XxtPuC3PGakY6PD7dG66/o8KwJ/LkH2/EKe19Dcw58w53dv4/vSQEkn/SzuyhHE2q4zPgCkxQBxus3VV4ql+Pg==} - - devtools-protocol@0.0.1452169: - resolution: {integrity: sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==} - didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -6203,9 +6139,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - eight-colors@1.3.1: - resolution: {integrity: sha512-7nXPYDeKh6DgJDR/mpt2G7N/hCNSGwwoPVmoI3+4TEwOb07VFN1WMPG0DFf6nMEjrkgdj8Og7l7IaEEk3VE6Zg==} - electron-to-chromium@1.5.152: resolution: {integrity: sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==} @@ -6371,11 +6304,6 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - eslint-config-prettier@10.1.8: resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} hasBin: true @@ -6570,11 +6498,6 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - fast-csv@4.3.6: resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} engines: {node: '>=10.0.0'} @@ -6811,11 +6734,6 @@ packages: fzf@0.5.2: resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} - gauge@5.0.2: - resolution: {integrity: sha512-pMaFftXPtiGIHCJHdcUUx9Rby/rFT/Kkt3fIIGCs+9PMDIljSyRiqraTlxNtBReJRDfUefpa263RQ3vnp5G/LQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - deprecated: This package is no longer supported. - gaxios@7.1.3: resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} engines: {node: '>=18'} @@ -6858,10 +6776,6 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -6884,10 +6798,6 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - get-uri@6.0.4: - resolution: {integrity: sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==} - engines: {node: '>= 14'} - github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -6995,9 +6905,6 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -7230,10 +7137,6 @@ packages: resolution: {integrity: sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==} engines: {node: '>=12.22.0'} - ip-address@9.0.5: - resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} - engines: {node: '>= 12'} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -7574,9 +7477,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsbn@1.1.0: - resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} - jsdom@26.1.0: resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} engines: {node: '>=18'} @@ -8016,10 +7916,6 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - lucide-react@0.518.0: resolution: {integrity: sha512-kFg34uQqnVl/7HwAiigxPSpj//43VIVHQbMygQPtS1yT4btMXHCWUipHcgcXHD2pm1Z2nUBA/M+Vnh/YmWXQUw==} peerDependencies: @@ -8329,9 +8225,6 @@ packages: resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} engines: {node: '>= 18'} - mitt@3.0.1: - resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -8411,10 +8304,6 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - netmask@2.0.2: - resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} - engines: {node: '>= 0.4.0'} - next-sitemap@4.2.3: resolution: {integrity: sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==} engines: {node: '>=14.18'} @@ -8701,14 +8590,6 @@ packages: resolution: {integrity: sha512-lwx6u1CotQYPVju77R+D0vFomni/AqRfqLmqQ8hekklqZ6gAY9rONh7lBQ0uxWMkC2AuX9b2DVAl8To0NyP1JA==} engines: {node: '>=12'} - pac-proxy-agent@7.2.0: - resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} - engines: {node: '>= 14'} - - pac-resolver@7.0.1: - resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} - engines: {node: '>= 14'} - package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -9012,10 +8893,6 @@ packages: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - promise-limit@2.7.0: resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==} @@ -9046,10 +8923,6 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - proxy-agent@6.5.0: - resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} - engines: {node: '>= 14'} - proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -9069,17 +8942,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - puppeteer-chromium-resolver@24.0.1: - resolution: {integrity: sha512-whu9e5qmnZekCP5hvlYMe7rWe4cU9seCISRlfT0vXGlCsy7psbeXHdGW6QdXrwyadvCTiD1Ft62jPaqia8ZQaA==} - - puppeteer-core@23.11.1: - resolution: {integrity: sha512-3HZ2/7hdDKZvZQ7dhhITOUg4/wOrDRjyK2ZBllRB0ZCOi9u0cwq1ACHDjBB+nX+7+kltHjQvBRdeY7+W0T+7Gg==} - engines: {node: '>=18'} - - puppeteer-core@24.10.2: - resolution: {integrity: sha512-CnzhOgrZj8DvkDqI+Yx+9or33i3Y9uUYbKyYpP4C13jWwXx/keQ38RMTMmxuLCWQlxjZrOH0Foq7P2fGP7adDQ==} - engines: {node: '>=18'} - qrcode@1.5.4: resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} engines: {node: '>=10.13.0'} @@ -9699,10 +9561,6 @@ packages: resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} engines: {node: '>=18'} - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smol-toml@1.3.4: resolution: {integrity: sha512-UOPtVuYkzYGee0Bd2Szz8d2G3RfMfJ2t3qVdZUAozZyAk+a0Sxa+QKix0YCwjL/A1RR0ar44nCxaoN9FxdJGwA==} engines: {node: '>= 18'} @@ -9715,14 +9573,6 @@ packages: resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} engines: {node: '>=10.0.0'} - socks-proxy-agent@8.0.5: - resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} - engines: {node: '>= 14'} - - socks@2.8.4: - resolution: {integrity: sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - sonner@2.0.5: resolution: {integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==} peerDependencies: @@ -10314,9 +10164,6 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typed-query-selector@2.12.0: - resolution: {integrity: sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==} - typed-rest-client@1.8.11: resolution: {integrity: sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==} @@ -10342,9 +10189,6 @@ packages: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} - unbzip2-stream@1.4.3: - resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - underscore@1.13.7: resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} @@ -10805,9 +10649,6 @@ packages: engines: {node: '>=8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - widest-line@5.0.0: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} @@ -12582,7 +12423,7 @@ snapshots: '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -13043,33 +12884,6 @@ snapshots: '@protobufjs/utf8@1.1.0': {} - '@puppeteer/browsers@2.10.5': - dependencies: - debug: 4.4.1 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.3 - tar-fs: 3.1.1 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-buffer - - supports-color - - '@puppeteer/browsers@2.6.1': - dependencies: - debug: 4.4.1 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.5.0 - semver: 7.7.3 - tar-fs: 3.1.1 - unbzip2-stream: 1.4.3 - yargs: 17.7.2 - transitivePeerDependencies: - - bare-buffer - - supports-color - '@qdrant/js-client-rest@1.14.0(typescript@5.8.3)': dependencies: '@qdrant/openapi-typescript-fetch': 1.2.6 @@ -13819,11 +13633,11 @@ snapshots: dependencies: '@redis/client': 5.5.5 - '@requesty/ai-sdk@3.0.0(vite@6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(zod@3.25.76)': + '@requesty/ai-sdk@3.0.0(vite@6.3.6(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0))(zod@3.25.76)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 3.0.20(zod@3.25.76) - vite: 6.3.5(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vite: 6.3.6(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) zod: 3.25.76 '@resvg/resvg-wasm@2.4.0': {} @@ -14449,8 +14263,6 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 - '@tootallnate/quickjs-emscripten@0.23.0': {} - '@trpc/client@11.8.1(@trpc/server@11.8.1(typescript@5.8.3))(typescript@5.8.3)': dependencies: '@trpc/server': 11.8.1(typescript@5.8.3) @@ -14800,11 +14612,6 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 24.2.1 - optional: true - '@typescript-eslint/eslint-plugin@8.32.1(@typescript-eslint/parser@8.32.1(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.27.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -14971,7 +14778,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.17.50)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.2.1)(@vitest/ui@3.2.4)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) '@vitest/utils@3.2.4': dependencies: @@ -15173,8 +14980,6 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - aproba@2.0.0: {} - archiver-utils@2.1.0: dependencies: glob: 11.1.0 @@ -15308,10 +15113,6 @@ snapshots: assertion-error@2.0.1: {} - ast-types@0.13.4: - dependencies: - tslib: 2.8.1 - async-function@1.0.0: {} async-mutex@0.5.0: @@ -15395,8 +15196,6 @@ snapshots: baseline-browser-mapping@2.9.19: {} - basic-ftp@5.0.5: {} - better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -15437,7 +15236,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -15663,18 +15462,6 @@ snapshots: chownr@3.0.0: {} - chromium-bidi@0.11.0(devtools-protocol@0.0.1367902): - dependencies: - devtools-protocol: 0.0.1367902 - mitt: 3.0.1 - zod: 3.25.76 - - chromium-bidi@5.1.0(devtools-protocol@0.0.1452169): - dependencies: - devtools-protocol: 0.0.1452169 - mitt: 3.0.1 - zod: 3.25.76 - ci-info@2.0.0: {} ci-info@3.9.0: {} @@ -15778,8 +15565,6 @@ snapshots: color-name@1.1.4: {} - color-support@1.1.3: {} - colorette@2.0.20: {} combined-stream@1.0.8: @@ -15825,8 +15610,6 @@ snapshots: consola@3.4.2: {} - console-control-strings@1.1.0: {} - content-disposition@1.0.0: dependencies: safe-buffer: 5.2.1 @@ -16130,8 +15913,6 @@ snapshots: data-uri-to-buffer@4.0.1: {} - data-uri-to-buffer@6.0.2: {} - data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 @@ -16169,10 +15950,6 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.1: - dependencies: - ms: 2.1.3 - debug@4.4.1(supports-color@8.1.1): dependencies: ms: 2.1.3 @@ -16232,12 +16009,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - degenerator@5.0.1: - dependencies: - ast-types: 0.13.4 - escodegen: 2.1.0 - esprima: 4.0.1 - delaunator@5.0.1: dependencies: robust-predicates: 3.0.2 @@ -16269,10 +16040,6 @@ snapshots: dependencies: dequal: 2.0.3 - devtools-protocol@0.0.1367902: {} - - devtools-protocol@0.0.1452169: {} - didyoumean@1.2.2: {} diff-match-patch@1.0.5: {} @@ -16386,8 +16153,6 @@ snapshots: ee-first@1.1.1: {} - eight-colors@1.3.1: {} - electron-to-chromium@1.5.152: {} electron-to-chromium@1.5.283: {} @@ -16622,14 +16387,6 @@ snapshots: escape-string-regexp@5.0.0: {} - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - eslint-config-prettier@10.1.8(eslint@9.27.0(jiti@2.4.2)): dependencies: eslint: 9.27.0(jiti@2.4.2) @@ -16924,7 +16681,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -16958,16 +16715,6 @@ snapshots: extendable-error@0.1.7: {} - extract-zip@2.0.1: - dependencies: - debug: 4.4.1 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - fast-csv@4.3.6: dependencies: '@fast-csv/format': 4.3.5 @@ -17061,7 +16808,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -17190,17 +16937,6 @@ snapshots: fzf@0.5.2: {} - gauge@5.0.2: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 4.1.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - gaxios@7.1.3: dependencies: extend: 3.0.2 @@ -17258,10 +16994,6 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-stream@5.2.0: - dependencies: - pump: 3.0.2 - get-stream@6.0.1: {} get-stream@8.0.1: {} @@ -17285,14 +17017,6 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - get-uri@6.0.4: - dependencies: - basic-ftp: 5.0.5 - data-uri-to-buffer: 6.0.2 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - github-from-package@0.0.0: optional: true @@ -17404,8 +17128,6 @@ snapshots: dependencies: has-symbols: 1.1.0 - has-unicode@2.0.1: {} - hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -17584,14 +17306,14 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -17729,11 +17451,6 @@ snapshots: transitivePeerDependencies: - supports-color - ip-address@9.0.5: - dependencies: - jsbn: 1.1.0 - sprintf-js: 1.1.3 - ipaddr.js@1.9.1: {} is-alphabetical@1.0.4: {} @@ -18043,8 +17760,6 @@ snapshots: dependencies: argparse: 2.0.1 - jsbn@1.1.0: {} - jsdom@26.1.0: dependencies: cssstyle: 4.4.0 @@ -18491,8 +18206,6 @@ snapshots: dependencies: yallist: 4.0.0 - lru-cache@7.18.3: {} - lucide-react@0.518.0(react@18.3.1): dependencies: react: 18.3.1 @@ -19046,8 +18759,6 @@ snapshots: dependencies: minipass: 7.1.2 - mitt@3.0.1: {} - mkdirp-classic@0.5.3: optional: true @@ -19139,8 +18850,6 @@ snapshots: negotiator@1.0.0: {} - netmask@2.0.2: {} - next-sitemap@4.2.3(next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): dependencies: '@corex/deepmerge': 4.0.43 @@ -19465,24 +19174,6 @@ snapshots: dependencies: p-timeout: 6.1.4 - pac-proxy-agent@7.2.0: - dependencies: - '@tootallnate/quickjs-emscripten': 0.23.0 - agent-base: 7.1.3 - debug: 4.4.1 - get-uri: 6.0.4 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - pac-resolver: 7.0.1 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - - pac-resolver@7.0.1: - dependencies: - degenerator: 5.0.1 - netmask: 2.0.2 - package-json-from-dist@1.0.1: {} package-manager-detector@0.2.11: @@ -19771,8 +19462,6 @@ snapshots: process@0.11.10: {} - progress@2.0.3: {} - promise-limit@2.7.0: optional: true @@ -19818,19 +19507,6 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-agent@6.5.0: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - lru-cache: 7.18.3 - pac-proxy-agent: 7.2.0 - proxy-from-env: 1.1.0 - socks-proxy-agent: 8.0.5 - transitivePeerDependencies: - - supports-color - proxy-from-env@1.1.0: {} ps-tree@1.2.0: @@ -19841,51 +19517,12 @@ snapshots: dependencies: end-of-stream: 1.4.4 once: 1.4.0 + optional: true punycode.js@2.3.1: {} punycode@2.3.1: {} - puppeteer-chromium-resolver@24.0.1: - dependencies: - '@puppeteer/browsers': 2.10.5 - eight-colors: 1.3.1 - gauge: 5.0.2 - puppeteer-core: 24.10.2 - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - utf-8-validate - - puppeteer-core@23.11.1: - dependencies: - '@puppeteer/browsers': 2.6.1 - chromium-bidi: 0.11.0(devtools-protocol@0.0.1367902) - debug: 4.4.1 - devtools-protocol: 0.0.1367902 - typed-query-selector: 2.12.0 - ws: 8.18.2 - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - utf-8-validate - - puppeteer-core@24.10.2: - dependencies: - '@puppeteer/browsers': 2.10.5 - chromium-bidi: 5.1.0(devtools-protocol@0.0.1452169) - debug: 4.4.1 - devtools-protocol: 0.0.1452169 - typed-query-selector: 2.12.0 - ws: 8.18.2 - transitivePeerDependencies: - - bare-buffer - - bufferutil - - supports-color - - utf-8-validate - qrcode@1.5.4: dependencies: dijkstrajs: 1.0.3 @@ -20397,7 +20034,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -20514,7 +20151,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -20686,7 +20323,7 @@ snapshots: dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -20710,8 +20347,6 @@ snapshots: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.0.0 - smart-buffer@4.2.0: {} - smol-toml@1.3.4: {} socket.io-client@4.8.1: @@ -20732,19 +20367,6 @@ snapshots: transitivePeerDependencies: - supports-color - socks-proxy-agent@8.0.5: - dependencies: - agent-base: 7.1.3 - debug: 4.4.1 - socks: 2.8.4 - transitivePeerDependencies: - - supports-color - - socks@2.8.4: - dependencies: - ip-address: 9.0.5 - smart-buffer: 4.2.0 - sonner@2.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -21092,6 +20714,7 @@ snapshots: bare-path: 3.0.0 transitivePeerDependencies: - bare-buffer + optional: true tar-stream@2.2.0: dependencies: @@ -21245,7 +20868,7 @@ snapshots: cac: 6.7.14 chokidar: 4.0.3 consola: 3.4.2 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) esbuild: 0.25.9 fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 @@ -21359,8 +20982,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typed-query-selector@2.12.0: {} - typed-rest-client@1.8.11: dependencies: qs: 6.14.0 @@ -21390,11 +21011,6 @@ snapshots: has-symbols: 1.1.0 which-boxed-primitive: 1.1.1 - unbzip2-stream@1.4.3: - dependencies: - buffer: 5.7.1 - through: 2.3.8 - underscore@1.13.7: {} undici-types@5.26.5: {} @@ -21646,7 +21262,7 @@ snapshots: vite-node@3.2.4(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.6(@types/node@20.17.50)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.4)(yaml@2.8.0) @@ -21813,7 +21429,7 @@ snapshots: '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.0 - debug: 4.4.1 + debug: 4.4.1(supports-color@8.1.1) expect-type: 1.2.1 magic-string: 0.30.17 pathe: 2.0.3 @@ -22070,10 +21686,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - widest-line@5.0.0: dependencies: string-width: 7.2.0 diff --git a/src/__tests__/command-mentions.spec.ts b/src/__tests__/command-mentions.spec.ts index 7b69d245d81..c421a047a16 100644 --- a/src/__tests__/command-mentions.spec.ts +++ b/src/__tests__/command-mentions.spec.ts @@ -1,28 +1,14 @@ import { parseMentions } from "../core/mentions" -import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { getCommand } from "../services/command/commands" // Mock the dependencies vi.mock("../services/command/commands") -vi.mock("../services/browser/UrlContentFetcher") -const MockedUrlContentFetcher = vi.mocked(UrlContentFetcher) const mockGetCommand = vi.mocked(getCommand) describe("Command Mentions", () => { - let mockUrlContentFetcher: any - beforeEach(() => { vi.clearAllMocks() - - // Create a mock UrlContentFetcher instance - mockUrlContentFetcher = { - launchBrowser: vi.fn(), - urlToMarkdown: vi.fn(), - closeBrowser: vi.fn(), - } - - MockedUrlContentFetcher.mockImplementation(() => mockUrlContentFetcher) }) // Helper function to call parseMentions with required parameters @@ -30,7 +16,6 @@ describe("Command Mentions", () => { return parseMentions( text, "/test/cwd", // cwd - mockUrlContentFetcher, // urlContentFetcher undefined, // fileContextTracker undefined, // rooIgnoreController false, // showRooIgnoredFiles diff --git a/src/core/assistant-message/NativeToolCallParser.ts b/src/core/assistant-message/NativeToolCallParser.ts index c8b96e35e31..e0ea1383f17 100644 --- a/src/core/assistant-message/NativeToolCallParser.ts +++ b/src/core/assistant-message/NativeToolCallParser.ts @@ -490,19 +490,6 @@ export class NativeToolCallParser { } break - case "browser_action": - if (partialArgs.action !== undefined) { - nativeArgs = { - action: partialArgs.action, - url: partialArgs.url, - coordinate: partialArgs.coordinate, - size: partialArgs.size, - text: partialArgs.text, - path: partialArgs.path, - } - } - break - case "codebase_search": if (partialArgs.query !== undefined) { nativeArgs = { @@ -838,19 +825,6 @@ export class NativeToolCallParser { } break - case "browser_action": - if (args.action !== undefined) { - nativeArgs = { - action: args.action, - url: args.url, - coordinate: args.coordinate, - size: args.size, - text: args.text, - path: args.path, - } as NativeArgsFor - } - break - case "codebase_search": if (args.query !== undefined) { nativeArgs = { diff --git a/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts b/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts index db0dc00de41..2c15e12069c 100644 --- a/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts +++ b/src/core/assistant-message/__tests__/NativeToolCallParser.spec.ts @@ -246,7 +246,7 @@ describe("NativeToolCallParser", () => { name: "read_file" as const, arguments: JSON.stringify({ files: JSON.stringify([ - { path: "src/services/browser/browserDiscovery.ts" }, + { path: "src/services/example/service.ts" }, { path: "src/services/mcp/McpServerManager.ts" }, ]), }), @@ -264,7 +264,7 @@ describe("NativeToolCallParser", () => { } expect(nativeArgs._legacyFormat).toBe(true) expect(nativeArgs.files).toHaveLength(2) - expect(nativeArgs.files[0].path).toBe("src/services/browser/browserDiscovery.ts") + expect(nativeArgs.files[0].path).toBe("src/services/example/service.ts") expect(nativeArgs.files[1].path).toBe("src/services/mcp/McpServerManager.ts") } }) diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts index 4440a340fb0..6675f18ce82 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-custom-tool.spec.ts @@ -60,9 +60,6 @@ describe("presentAssistantMessage - Custom Tool Recording", () => { api: { getModel: () => ({ id: "test-model", info: {} }), }, - browserSession: { - closeBrowser: vi.fn().mockResolvedValue(undefined), - }, recordToolUsage: vi.fn(), recordToolError: vi.fn(), toolRepetitionDetector: { diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts index a12ea7a12c3..7e224844c5b 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-images.spec.ts @@ -46,9 +46,6 @@ describe("presentAssistantMessage - Image Handling in Native Tool Calling", () = api: { getModel: () => ({ id: "test-model", info: {} }), }, - browserSession: { - closeBrowser: vi.fn().mockResolvedValue(undefined), - }, recordToolUsage: vi.fn(), toolRepetitionDetector: { check: vi.fn().mockReturnValue({ allowExecution: true }), diff --git a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts index 29b4133f819..dc31c179c2d 100644 --- a/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts +++ b/src/core/assistant-message/__tests__/presentAssistantMessage-unknown-tool.spec.ts @@ -41,9 +41,6 @@ describe("presentAssistantMessage - Unknown Tool Handling", () => { api: { getModel: () => ({ id: "test-model", info: {} }), }, - browserSession: { - closeBrowser: vi.fn().mockResolvedValue(undefined), - }, recordToolUsage: vi.fn(), recordToolError: vi.fn(), toolRepetitionDetector: { diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index 56f6288b238..a1b1673ce75 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -24,7 +24,6 @@ import { searchReplaceTool } from "../tools/SearchReplaceTool" import { editFileTool } from "../tools/EditFileTool" import { applyPatchTool } from "../tools/ApplyPatchTool" import { searchFilesTool } from "../tools/SearchFilesTool" -import { browserActionTool } from "../tools/BrowserActionTool" import { executeCommandTool } from "../tools/ExecuteCommandTool" import { useMcpToolTool } from "../tools/UseMcpToolTool" import { accessMcpResourceTool } from "../tools/accessMcpResourceTool" @@ -358,8 +357,6 @@ export async function presentAssistantMessage(cline: Task) { return `[${block.name}]` case "list_files": return `[${block.name} for '${block.params.path}']` - case "browser_action": - return `[${block.name} for '${block.params.action}']` case "use_mcp_tool": return `[${block.name} for '${block.params.server_name}']` case "access_mcp_resource": @@ -559,34 +556,6 @@ export async function presentAssistantMessage(cline: Task) { pushToolResult(formatResponse.toolError(errorString)) } - // Keep browser open during an active session so other tools can run. - // Session is active if we've seen any browser_action_result and the last browser_action is not "close". - try { - const messages = cline.clineMessages || [] - const hasStarted = messages.some((m: any) => m.say === "browser_action_result") - let isClosed = false - for (let i = messages.length - 1; i >= 0; i--) { - const m = messages[i] - if (m.say === "browser_action") { - try { - const act = JSON.parse(m.text || "{}") - isClosed = act.action === "close" - } catch {} - break - } - } - const sessionActive = hasStarted && !isClosed - // Only auto-close when no active browser session is present, and this isn't a browser_action - if (!sessionActive && block.name !== "browser_action") { - await cline.browserSession.closeBrowser() - } - } catch { - // On any unexpected error, fall back to conservative behavior - if (block.name !== "browser_action") { - await cline.browserSession.closeBrowser() - } - } - if (!block.partial) { // Check if this is a custom tool - if so, record as "custom_tool" (like MCP tools) const isCustomTool = stateExperiments?.customTools && customToolRegistry.has(block.name) @@ -798,15 +767,6 @@ export async function presentAssistantMessage(cline: Task) { pushToolResult, }) break - case "browser_action": - await browserActionTool( - cline, - block as ToolUse<"browser_action">, - askApproval, - handleError, - pushToolResult, - ) - break case "execute_command": await executeCommandTool.handle(cline, block as ToolUse<"execute_command">, { askApproval, @@ -1038,3 +998,46 @@ async function checkpointSaveAndMark(task: Task) { console.error(`[Task#presentAssistantMessage] Error saving checkpoint: ${error.message}`, error) } } + +function containsXmlToolMarkup(text: string): boolean { + // Keep this intentionally narrow: only reject XML-style tool tags matching our tool names. + // Avoid regex so we don't keep legacy XML parsing artifacts around. + // Note: This is a best-effort safeguard; tool_use blocks without an id are rejected elsewhere. + + // First, strip out content inside markdown code fences to avoid false positives + // when users paste documentation or examples containing tool tag references. + // This handles both fenced code blocks (```) and inline code (`). + const textWithoutCodeBlocks = text + .replace(/```[\s\S]*?```/g, "") // Remove fenced code blocks + .replace(/`[^`]+`/g, "") // Remove inline code + + const lower = textWithoutCodeBlocks.toLowerCase() + if (!lower.includes("<") || !lower.includes(">")) { + return false + } + + const toolNames = [ + "access_mcp_resource", + "apply_diff", + "apply_patch", + "ask_followup_question", + "attempt_completion", + "codebase_search", + "edit_file", + "execute_command", + "generate_image", + "list_files", + "new_task", + "read_command_output", + "read_file", + "search_and_replace", + "search_files", + "search_replace", + "switch_mode", + "update_todo_list", + "use_mcp_tool", + "write_to_file", + ] as const + + return toolNames.some((name) => lower.includes(`<${name}`) || lower.includes(` { slug: "test-mode", name: "Test Mode", roleDefinition: "Test role", - groups: [ - "read", - ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], - "browser", - ], + groups: ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }]], }, ], }) @@ -245,20 +241,19 @@ describe("CustomModesManager - YAML Edge Cases", () => { // Should successfully parse the complex fileRegex syntax expect(modes).toHaveLength(1) - expect(modes[0].groups).toHaveLength(3) + expect(modes[0].groups).toHaveLength(2) expect(modes[0].groups[1]).toEqual(["edit", { fileRegex: "\\.md$", description: "Markdown files only" }]) }) it("should handle invalid fileRegex syntax with clear error", async () => { // This YAML has invalid structure that might cause parsing issues const invalidYaml = `customModes: - - slug: "test-mode" - name: "Test Mode" - roleDefinition: "Test role" - groups: - - read - - ["edit", { fileRegex: "\\.md$" }] # This line has invalid YAML syntax - - browser` + - slug: "test-mode" + name: "Test Mode" + roleDefinition: "Test role" + groups: + - read + - ["edit", { fileRegex: "\\.md$" }] # This line has invalid YAML syntax` mockFsReadFile({ [mockRoomodes]: invalidYaml, @@ -433,13 +428,6 @@ describe("CustomModesManager - YAML Edge Cases", () => { description: "Markdown files with \u2018special\u2019 chars", }, ], - [ - "browser", - { - fileRegex: "\\.html?$", - description: "HTML files\u00A0only", - }, - ], ], }, ], @@ -462,13 +450,6 @@ describe("CustomModesManager - YAML Edge Cases", () => { description: "Markdown files with 'special' chars", }, ]) - expect(modes[0].groups[2]).toEqual([ - "browser", - { - fileRegex: "\\.html?$", - description: "HTML files only", - }, - ]) }) }) }) diff --git a/src/core/config/__tests__/CustomModesSettings.spec.ts b/src/core/config/__tests__/CustomModesSettings.spec.ts index 32e7ed9cf4d..b5ab38a9d6b 100644 --- a/src/core/config/__tests__/CustomModesSettings.spec.ts +++ b/src/core/config/__tests__/CustomModesSettings.spec.ts @@ -130,7 +130,7 @@ describe("CustomModesSettings", () => { customModes: [ { ...validMode, - groups: ["read", "edit", "browser"] as const, + groups: ["read", "edit"] as const, }, ], } diff --git a/src/core/config/__tests__/ModeConfig.spec.ts b/src/core/config/__tests__/ModeConfig.spec.ts index dbdd1a0f03b..68d75761e77 100644 --- a/src/core/config/__tests__/ModeConfig.spec.ts +++ b/src/core/config/__tests__/ModeConfig.spec.ts @@ -26,7 +26,7 @@ describe("CustomModeSchema", () => { slug: "test", name: "Test Mode", roleDefinition: "Test role definition", - groups: ["read", "edit", "browser"] as const, + groups: ["read", "edit"] as const, } satisfies ModeConfig expect(() => validateCustomMode(validMode)).not.toThrow() @@ -121,18 +121,14 @@ describe("CustomModeSchema", () => { slug: "markdown-editor", name: "Markdown Editor", roleDefinition: "Markdown editing mode", - groups: ["read", ["edit", { fileRegex: "\\.md$" }], "browser"], + groups: ["read", ["edit", { fileRegex: "\\.md$" }]], } const modeWithDescription = { slug: "docs-editor", name: "Documentation Editor", roleDefinition: "Documentation editing mode", - groups: [ - "read", - ["edit", { fileRegex: "\\.(md|txt)$", description: "Documentation files only" }], - "browser", - ], + groups: ["read", ["edit", { fileRegex: "\\.(md|txt)$", description: "Documentation files only" }]], } expect(() => modeConfigSchema.parse(modeWithJustRegex)).not.toThrow() @@ -195,7 +191,7 @@ describe("CustomModeSchema", () => { test("accepts multiple groups", () => { const mode = { ...validBaseMode, - groups: ["read", "edit", "browser"] as const, + groups: ["read", "edit"] as const, } satisfies ModeConfig expect(() => modeConfigSchema.parse(mode)).not.toThrow() @@ -204,7 +200,7 @@ describe("CustomModeSchema", () => { test("accepts all available groups", () => { const mode = { ...validBaseMode, - groups: ["read", "edit", "browser", "command", "mcp"] as const, + groups: ["read", "edit", "command", "mcp"] as const, } satisfies ModeConfig expect(() => modeConfigSchema.parse(mode)).not.toThrow() diff --git a/src/core/environment/__tests__/getEnvironmentDetails.spec.ts b/src/core/environment/__tests__/getEnvironmentDetails.spec.ts index 74e000d36aa..f05a5066fb3 100644 --- a/src/core/environment/__tests__/getEnvironmentDetails.spec.ts +++ b/src/core/environment/__tests__/getEnvironmentDetails.spec.ts @@ -117,10 +117,6 @@ describe("getEnvironmentDetails", () => { deref: vi.fn().mockReturnValue(mockProvider), [Symbol.toStringTag]: "WeakRef", } as unknown as WeakRef, - browserSession: { - isSessionActive: vi.fn().mockReturnValue(false), - getViewportSize: vi.fn().mockReturnValue({ width: 900, height: 600 }), - } as any, } // Mock other dependencies. @@ -448,18 +444,4 @@ describe("getEnvironmentDetails", () => { expect(getGitStatus).toHaveBeenCalledWith(mockCwd, 5) }) - - it("should NOT include Browser Session Status when inactive", async () => { - const result = await getEnvironmentDetails(mockCline as Task) - expect(result).not.toContain("# Browser Session Status") - }) - - it("should include Browser Session Status with current viewport when active", async () => { - ;(mockCline.browserSession as any).isSessionActive = vi.fn().mockReturnValue(true) - ;(mockCline.browserSession as any).getViewportSize = vi.fn().mockReturnValue({ width: 1280, height: 720 }) - - const result = await getEnvironmentDetails(mockCline as Task) - expect(result).toContain("Active - A browser session is currently open and ready for browser_action commands") - expect(result).toContain("Current viewport size: 1280x720 pixels.") - }) }) diff --git a/src/core/environment/getEnvironmentDetails.ts b/src/core/environment/getEnvironmentDetails.ts index 4de2e20e371..99b3951cd1d 100644 --- a/src/core/environment/getEnvironmentDetails.ts +++ b/src/core/environment/getEnvironmentDetails.ts @@ -226,35 +226,6 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo details += `${modeDetails.name}\n` details += `${modelId}\n` - // Add browser session status - Only show when active to prevent cluttering context - const isBrowserActive = cline.browserSession.isSessionActive() - - if (isBrowserActive) { - // Build viewport info for status (prefer actual viewport if available, else fallback to configured setting) - const configuredViewport = (state?.browserViewportSize as string | undefined) ?? "900x600" - let configuredWidth: number | undefined - let configuredHeight: number | undefined - if (configuredViewport.includes("x")) { - const parts = configuredViewport.split("x").map((v) => Number(v)) - configuredWidth = parts[0] - configuredHeight = parts[1] - } - - let actualWidth: number | undefined - let actualHeight: number | undefined - const vp = cline.browserSession.getViewportSize?.() - if (vp) { - actualWidth = vp.width - actualHeight = vp.height - } - - const width = actualWidth ?? configuredWidth - const height = actualHeight ?? configuredHeight - const viewportInfo = width && height ? `\nCurrent viewport size: ${width}x${height} pixels.` : "" - - details += `\n# Browser Session Status\nActive - A browser session is currently open and ready for browser_action commands${viewportInfo}\n` - } - if (includeFileDetails) { details += `\n\n# Current Workspace Directory (${cline.cwd.toPosix()}) Files\n` const isDesktop = arePathsEqual(cline.cwd, path.join(os.homedir(), "Desktop")) diff --git a/src/core/mentions/__tests__/index.spec.ts b/src/core/mentions/__tests__/index.spec.ts index 8f229c28b87..fa96a396dcc 100644 --- a/src/core/mentions/__tests__/index.spec.ts +++ b/src/core/mentions/__tests__/index.spec.ts @@ -3,7 +3,6 @@ import * as vscode from "vscode" import { parseMentions } from "../index" -import { UrlContentFetcher } from "../../../services/browser/UrlContentFetcher" // Mock vscode vi.mock("vscode", () => ({ @@ -17,143 +16,15 @@ vi.mock("../../../i18n", () => ({ t: vi.fn((key: string) => key), })) -describe("parseMentions - URL error handling", () => { - let mockUrlContentFetcher: UrlContentFetcher - let consoleErrorSpy: any - +describe("parseMentions - URL mention handling", () => { beforeEach(() => { vi.clearAllMocks() - consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}) - - mockUrlContentFetcher = { - launchBrowser: vi.fn(), - urlToMarkdown: vi.fn(), - closeBrowser: vi.fn(), - } as any - }) - - it("should handle timeout errors with appropriate message", async () => { - const timeoutError = new Error("Navigation timeout of 30000 ms exceeded") - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(timeoutError) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(consoleErrorSpy).toHaveBeenCalledWith("Error fetching URL https://example.com:", timeoutError) - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content: Navigation timeout of 30000 ms exceeded") - }) - - it("should handle DNS resolution errors", async () => { - const dnsError = new Error("net::ERR_NAME_NOT_RESOLVED") - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(dnsError) - - const result = await parseMentions("Check @https://nonexistent.example", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content: net::ERR_NAME_NOT_RESOLVED") - }) - - it("should handle network disconnection errors", async () => { - const networkError = new Error("net::ERR_INTERNET_DISCONNECTED") - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(networkError) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content: net::ERR_INTERNET_DISCONNECTED") - }) - - it("should handle 403 Forbidden errors", async () => { - const forbiddenError = new Error("403 Forbidden") - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(forbiddenError) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content: 403 Forbidden") - }) - - it("should handle 404 Not Found errors", async () => { - const notFoundError = new Error("404 Not Found") - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(notFoundError) - - const result = await parseMentions("Check @https://example.com/missing", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content: 404 Not Found") }) - it("should handle generic errors with fallback message", async () => { - const genericError = new Error("Some unexpected error") - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(genericError) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content: Some unexpected error") - }) - - it("should handle non-Error objects thrown", async () => { - const nonErrorObject = { code: "UNKNOWN", details: "Something went wrong" } - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockRejectedValue(nonErrorObject) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.url_fetch_error_with_url") - expect(result.text).toContain("Error fetching content:") - }) - - it("should handle browser launch errors correctly", async () => { - const launchError = new Error("Failed to launch browser") - vi.mocked(mockUrlContentFetcher.launchBrowser).mockRejectedValue(launchError) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( - "Error fetching content for https://example.com: Failed to launch browser", - ) - expect(result.text).toContain("Error fetching content: Failed to launch browser") - // Should not attempt to fetch URL if browser launch failed - expect(mockUrlContentFetcher.urlToMarkdown).not.toHaveBeenCalled() - }) - - it("should handle browser launch errors without message property", async () => { - const launchError = "String error" - vi.mocked(mockUrlContentFetcher.launchBrowser).mockRejectedValue(launchError) - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( - "Error fetching content for https://example.com: String error", - ) - expect(result.text).toContain("Error fetching content: String error") - }) - - it("should successfully fetch URL content when no errors occur", async () => { - vi.mocked(mockUrlContentFetcher.urlToMarkdown).mockResolvedValue("# Example Content\n\nThis is the content.") - - const result = await parseMentions("Check @https://example.com", "/test", mockUrlContentFetcher) - - expect(vscode.window.showErrorMessage).not.toHaveBeenCalled() - expect(result.text).toContain('') - expect(result.text).toContain("# Example Content\n\nThis is the content.") - expect(result.text).toContain("") - }) - - it("should handle multiple URLs with mixed success and failure", async () => { - vi.mocked(mockUrlContentFetcher.urlToMarkdown) - .mockResolvedValueOnce("# First Site") - .mockRejectedValueOnce(new Error("timeout")) - - const result = await parseMentions( - "Check @https://example1.com and @https://example2.com", - "/test", - mockUrlContentFetcher, - ) + it("should replace URL mentions with quoted URL reference", async () => { + const result = await parseMentions("Check @https://example.com", "/test") - expect(result.text).toContain('') - expect(result.text).toContain("# First Site") - expect(result.text).toContain('') - expect(result.text).toContain("Error fetching content: timeout") + // URL mentions are now replaced with a quoted reference (no fetching) + expect(result.text).toContain("'https://example.com'") }) }) diff --git a/src/core/mentions/__tests__/processUserContentMentions.spec.ts b/src/core/mentions/__tests__/processUserContentMentions.spec.ts index 018a4f2a7e8..0541c7d9414 100644 --- a/src/core/mentions/__tests__/processUserContentMentions.spec.ts +++ b/src/core/mentions/__tests__/processUserContentMentions.spec.ts @@ -2,7 +2,6 @@ import { processUserContentMentions } from "../processUserContentMentions" import { parseMentions } from "../index" -import { UrlContentFetcher } from "../../../services/browser/UrlContentFetcher" import { FileContextTracker } from "../../context-tracking/FileContextTracker" // Mock the parseMentions function @@ -11,14 +10,12 @@ vi.mock("../index", () => ({ })) describe("processUserContentMentions", () => { - let mockUrlContentFetcher: UrlContentFetcher let mockFileContextTracker: FileContextTracker let mockRooIgnoreController: any beforeEach(() => { vi.clearAllMocks() - mockUrlContentFetcher = {} as UrlContentFetcher mockFileContextTracker = {} as FileContextTracker mockRooIgnoreController = {} @@ -42,7 +39,6 @@ describe("processUserContentMentions", () => { const result = await processUserContentMentions({ userContent, cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, fileContextTracker: mockFileContextTracker, }) @@ -65,7 +61,6 @@ describe("processUserContentMentions", () => { const result = await processUserContentMentions({ userContent, cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, fileContextTracker: mockFileContextTracker, }) @@ -74,6 +69,78 @@ describe("processUserContentMentions", () => { expect(result.mode).toBeUndefined() }) + it("should process tool_result blocks with string content", async () => { + const userContent = [ + { + type: "tool_result" as const, + tool_use_id: "123", + content: "Tool feedback", + }, + ] + + const result = await processUserContentMentions({ + userContent, + cwd: "/test", + fileContextTracker: mockFileContextTracker, + }) + + expect(parseMentions).toHaveBeenCalled() + // String content is now converted to array format to support content blocks + expect(result.content[0]).toEqual({ + type: "tool_result", + tool_use_id: "123", + content: [ + { + type: "text", + text: "parsed: Tool feedback", + }, + ], + }) + expect(result.mode).toBeUndefined() + }) + + it("should process tool_result blocks with array content", async () => { + const userContent = [ + { + type: "tool_result" as const, + tool_use_id: "123", + content: [ + { + type: "text" as const, + text: "Array task", + }, + { + type: "text" as const, + text: "Regular text", + }, + ], + }, + ] + + const result = await processUserContentMentions({ + userContent, + cwd: "/test", + fileContextTracker: mockFileContextTracker, + }) + + expect(parseMentions).toHaveBeenCalledTimes(1) + expect(result.content[0]).toEqual({ + type: "tool_result", + tool_use_id: "123", + content: [ + { + type: "text", + text: "parsed: Array task", + }, + { + type: "text", + text: "Regular text", + }, + ], + }) + expect(result.mode).toBeUndefined() + }) + it("should handle mixed content types (text + image)", async () => { const userContent = [ { @@ -90,7 +157,6 @@ describe("processUserContentMentions", () => { const result = await processUserContentMentions({ userContent: userContent as any, cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, fileContextTracker: mockFileContextTracker, }) @@ -117,14 +183,12 @@ describe("processUserContentMentions", () => { await processUserContentMentions({ userContent, cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, fileContextTracker: mockFileContextTracker, }) expect(parseMentions).toHaveBeenCalledWith( "Test default", "/test", - mockUrlContentFetcher, mockFileContextTracker, undefined, false, // showRooIgnoredFiles should default to false @@ -144,7 +208,6 @@ describe("processUserContentMentions", () => { await processUserContentMentions({ userContent, cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, fileContextTracker: mockFileContextTracker, showRooIgnoredFiles: false, }) @@ -152,7 +215,6 @@ describe("processUserContentMentions", () => { expect(parseMentions).toHaveBeenCalledWith( "Test explicit false", "/test", - mockUrlContentFetcher, mockFileContextTracker, undefined, false, @@ -181,7 +243,6 @@ describe("processUserContentMentions", () => { const result = await processUserContentMentions({ userContent, cwd: "/test", - urlContentFetcher: mockUrlContentFetcher, fileContextTracker: mockFileContextTracker, }) @@ -195,5 +256,88 @@ describe("processUserContentMentions", () => { text: "command help", }) }) + + it("should include slash command content in tool_result string content", async () => { + vi.mocked(parseMentions).mockResolvedValueOnce({ + text: "parsed tool output", + slashCommandHelp: "command help", + mode: undefined, + contentBlocks: [], + }) + + const userContent = [ + { + type: "tool_result" as const, + tool_use_id: "123", + content: "Tool output", + }, + ] + + const result = await processUserContentMentions({ + userContent, + cwd: "/test", + fileContextTracker: mockFileContextTracker, + }) + + expect(result.content).toHaveLength(1) + expect(result.content[0]).toEqual({ + type: "tool_result", + tool_use_id: "123", + content: [ + { + type: "text", + text: "parsed tool output", + }, + { + type: "text", + text: "command help", + }, + ], + }) + }) + + it("should include slash command content in tool_result array content", async () => { + vi.mocked(parseMentions).mockResolvedValueOnce({ + text: "parsed array item", + slashCommandHelp: "command help", + mode: undefined, + contentBlocks: [], + }) + + const userContent = [ + { + type: "tool_result" as const, + tool_use_id: "123", + content: [ + { + type: "text" as const, + text: "Array item", + }, + ], + }, + ] + + const result = await processUserContentMentions({ + userContent, + cwd: "/test", + fileContextTracker: mockFileContextTracker, + }) + + expect(result.content).toHaveLength(1) + expect(result.content[0]).toEqual({ + type: "tool_result", + tool_use_id: "123", + content: [ + { + type: "text", + text: "parsed array item", + }, + { + type: "text", + text: "command help", + }, + ], + }) + }) }) }) diff --git a/src/core/mentions/index.ts b/src/core/mentions/index.ts index faa7236e67c..d71317d6495 100644 --- a/src/core/mentions/index.ts +++ b/src/core/mentions/index.ts @@ -13,42 +13,11 @@ import { extractTextFromFileWithMetadata, type ExtractTextResult } from "../../i import { diagnosticsToProblemsString } from "../../integrations/diagnostics" import { DEFAULT_LINE_LIMIT } from "../prompts/tools/native-tools/read_file" -import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher" - import { FileContextTracker } from "../context-tracking/FileContextTracker" import { RooIgnoreController } from "../ignore/RooIgnoreController" import { getCommand, type Command } from "../../services/command/commands" -import { t } from "../../i18n" - -function getUrlErrorMessage(error: unknown): string { - const errorMessage = error instanceof Error ? error.message : String(error) - - // Check for common error patterns and return appropriate message - if (errorMessage.includes("timeout")) { - return t("common:errors.url_timeout") - } - if (errorMessage.includes("net::ERR_NAME_NOT_RESOLVED")) { - return t("common:errors.url_not_found") - } - if (errorMessage.includes("net::ERR_INTERNET_DISCONNECTED")) { - return t("common:errors.no_internet") - } - if (errorMessage.includes("net::ERR_ABORTED")) { - return t("common:errors.url_request_aborted") - } - if (errorMessage.includes("403") || errorMessage.includes("Forbidden")) { - return t("common:errors.url_forbidden") - } - if (errorMessage.includes("404") || errorMessage.includes("Not Found")) { - return t("common:errors.url_page_not_found") - } - - // Default error message - return t("common:errors.url_fetch_failed", { error: errorMessage }) -} - export async function openMention(cwd: string, mention?: string): Promise { if (!mention) { return @@ -128,7 +97,6 @@ ${result.content}` export async function parseMentions( text: string, cwd: string, - urlContentFetcher: UrlContentFetcher, fileContextTracker?: FileContextTracker, rooIgnoreController?: RooIgnoreController, showRooIgnoredFiles: boolean = false, @@ -180,8 +148,7 @@ export async function parseMentions( parsedText = parsedText.replace(mentionRegexGlobal, (match, mention) => { mentions.add(mention) if (mention.startsWith("http")) { - // Keep old style for URLs (still XML-based) - return `'${mention}' (see below for site content)` + return `'${mention}'` } else if (mention.startsWith("/")) { // Clean path reference - no "see below" since we format like tool results const mentionPath = mention.slice(1) @@ -198,49 +165,8 @@ export async function parseMentions( return match }) - const urlMention = Array.from(mentions).find((mention) => mention.startsWith("http")) - let launchBrowserError: Error | undefined - if (urlMention) { - try { - await urlContentFetcher.launchBrowser() - } catch (error) { - launchBrowserError = error - const errorMessage = error instanceof Error ? error.message : String(error) - vscode.window.showErrorMessage(`Error fetching content for ${urlMention}: ${errorMessage}`) - } - } - for (const mention of mentions) { - if (mention.startsWith("http")) { - let result: string - if (launchBrowserError) { - const errorMessage = - launchBrowserError instanceof Error ? launchBrowserError.message : String(launchBrowserError) - result = `Error fetching content: ${errorMessage}` - } else { - try { - const markdown = await urlContentFetcher.urlToMarkdown(mention) - result = markdown - } catch (error) { - console.error(`Error fetching URL ${mention}:`, error) - - // Get raw error message for AI - const rawErrorMessage = error instanceof Error ? error.message : String(error) - - // Get localized error message for UI notification - const localizedErrorMessage = getUrlErrorMessage(error) - - vscode.window.showErrorMessage( - t("common:errors.url_fetch_error_with_url", { url: mention, error: localizedErrorMessage }), - ) - - // Send raw error message to AI model - result = `Error fetching content: ${rawErrorMessage}` - } - } - // URLs still use XML format (appended to text for backwards compat) - parsedText += `\n\n\n${result}\n` - } else if (mention.startsWith("/")) { + if (mention.startsWith("/")) { const mentionPath = mention.slice(1) try { const fileResult = await getFileOrFolderContentWithMetadata( @@ -305,14 +231,6 @@ export async function parseMentions( } } - if (urlMention) { - try { - await urlContentFetcher.closeBrowser() - } catch (error) { - console.error(`Error closing browser: ${error.message}`) - } - } - return { text: parsedText, contentBlocks, diff --git a/src/core/mentions/processUserContentMentions.ts b/src/core/mentions/processUserContentMentions.ts index 92c4693e820..860bec53c0c 100644 --- a/src/core/mentions/processUserContentMentions.ts +++ b/src/core/mentions/processUserContentMentions.ts @@ -1,10 +1,9 @@ -import type { TextPart, ImagePart } from "../task-persistence/rooMessage" +import type { TextPart, ImagePart, LegacyToolResultBlock } from "../task-persistence/rooMessage" import { parseMentions, ParseMentionsResult, MentionContentBlock } from "./index" -import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher" import { FileContextTracker } from "../context-tracking/FileContextTracker" export interface ProcessUserContentMentionsResult { - content: Array + content: Array mode?: string // Mode from the first slash command that has one } @@ -30,16 +29,14 @@ function contentBlocksToTextParts(contentBlocks: MentionContentBlock[]): TextPar export async function processUserContentMentions({ userContent, cwd, - urlContentFetcher, fileContextTracker, rooIgnoreController, showRooIgnoredFiles = false, includeDiagnosticMessages = true, maxDiagnosticMessages = 50, }: { - userContent: Array + userContent: Array cwd: string - urlContentFetcher: UrlContentFetcher fileContextTracker: FileContextTracker rooIgnoreController?: any showRooIgnoredFiles?: boolean @@ -61,7 +58,6 @@ export async function processUserContentMentions({ const result = await parseMentions( block.text, cwd, - urlContentFetcher, fileContextTracker, rooIgnoreController, showRooIgnoredFiles, @@ -98,6 +94,106 @@ export async function processUserContentMentions({ return blocks } + return block + } else if (block.type === "tool_result") { + if (typeof block.content === "string") { + if (shouldProcessMentions(block.content)) { + const result = await parseMentions( + block.content, + cwd, + fileContextTracker, + rooIgnoreController, + showRooIgnoredFiles, + includeDiagnosticMessages, + maxDiagnosticMessages, + ) + // Capture the first mode found + if (!commandMode && result.mode) { + commandMode = result.mode + } + + // Build content array with file blocks included + const contentParts: Array<{ type: "text"; text: string }> = [ + { + type: "text" as const, + text: result.text, + }, + ] + + // Add file/folder content blocks + for (const contentBlock of result.contentBlocks) { + contentParts.push({ + type: "text" as const, + text: contentBlock.content, + }) + } + + if (result.slashCommandHelp) { + contentParts.push({ + type: "text" as const, + text: result.slashCommandHelp, + }) + } + + return { + ...block, + content: contentParts, + } + } + + return block + } else if (Array.isArray(block.content)) { + const parsedContent = ( + await Promise.all( + block.content.map(async (contentBlock) => { + if (contentBlock.type === "text" && shouldProcessMentions(contentBlock.text)) { + const result = await parseMentions( + contentBlock.text, + cwd, + fileContextTracker, + rooIgnoreController, + showRooIgnoredFiles, + includeDiagnosticMessages, + maxDiagnosticMessages, + ) + // Capture the first mode found + if (!commandMode && result.mode) { + commandMode = result.mode + } + + // Build blocks array with file content + const blocks: Array<{ type: "text"; text: string }> = [ + { + ...contentBlock, + text: result.text, + }, + ] + + // Add file/folder content blocks + for (const cb of result.contentBlocks) { + blocks.push({ + type: "text" as const, + text: cb.content, + }) + } + + if (result.slashCommandHelp) { + blocks.push({ + type: "text" as const, + text: result.slashCommandHelp, + }) + } + return blocks + } + + return contentBlock + }), + ) + ).flat() + + return { ...block, content: parsedContent } + } + return block } @@ -108,5 +204,5 @@ export async function processUserContentMentions({ ) ).flat() - return { content: content as Array, mode: commandMode } + return { content: content as Array, mode: commandMode } } diff --git a/src/core/prompts/__tests__/add-custom-instructions.spec.ts b/src/core/prompts/__tests__/add-custom-instructions.spec.ts index f10a8bade56..640136de635 100644 --- a/src/core/prompts/__tests__/add-custom-instructions.spec.ts +++ b/src/core/prompts/__tests__/add-custom-instructions.spec.ts @@ -205,7 +205,6 @@ describe("addCustomInstructions", () => { false, // supportsImages undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize "architect", // mode undefined, // customModePrompts undefined, // customModes @@ -226,7 +225,6 @@ describe("addCustomInstructions", () => { false, // supportsImages undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize "ask", // mode undefined, // customModePrompts undefined, // customModes @@ -249,7 +247,6 @@ describe("addCustomInstructions", () => { false, // supportsImages mockMcpHub, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes, diff --git a/src/core/prompts/__tests__/system-prompt.spec.ts b/src/core/prompts/__tests__/system-prompt.spec.ts index 612783b3db3..f555daba060 100644 --- a/src/core/prompts/__tests__/system-prompt.spec.ts +++ b/src/core/prompts/__tests__/system-prompt.spec.ts @@ -220,7 +220,6 @@ describe("SYSTEM_PROMPT", () => { false, // supportsImages undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes @@ -233,26 +232,6 @@ describe("SYSTEM_PROMPT", () => { expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/consistent-system-prompt.snap") }) - it("should include browser actions when supportsImages is true", async () => { - const prompt = await SYSTEM_PROMPT( - mockContext, - "/test/path", - true, // supportsImages - undefined, // mcpHub - undefined, // diffStrategy - "1280x800", // browserViewportSize - defaultModeSlug, // mode - undefined, // customModePrompts - undefined, // customModes, - undefined, // globalCustomInstructions - experiments, - undefined, // language - undefined, // rooIgnoreInstructions - ) - - expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-computer-use-support.snap") - }) - it("should include MCP server info when mcpHub is provided", async () => { mockMcpHub = createMockMcpHub(true) @@ -262,7 +241,6 @@ describe("SYSTEM_PROMPT", () => { false, mockMcpHub, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes, @@ -282,7 +260,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // explicitly undefined mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes, @@ -295,26 +272,6 @@ describe("SYSTEM_PROMPT", () => { expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-undefined-mcp-hub.snap") }) - it("should handle different browser viewport sizes", async () => { - const prompt = await SYSTEM_PROMPT( - mockContext, - "/test/path", - false, - undefined, // mcpHub - undefined, // diffStrategy - "900x600", // different viewport size - defaultModeSlug, // mode - undefined, // customModePrompts - undefined, // customModes, - undefined, // globalCustomInstructions - experiments, - undefined, // language - undefined, // rooIgnoreInstructions - ) - - expect(prompt).toMatchFileSnapshot("./__snapshots__/system-prompt/with-different-viewport-size.snap") - }) - it("should include vscode language in custom instructions", async () => { // Mock vscode.env.language const vscode = vi.mocked(await import("vscode")) as any @@ -349,7 +306,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes @@ -407,7 +363,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize "custom-mode", // mode undefined, // customModePrompts customModes, // customModes @@ -442,7 +397,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug as Mode, // mode customModePrompts, // customModePrompts undefined, // customModes @@ -472,7 +426,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug as Mode, // mode customModePrompts, // customModePrompts undefined, // customModes @@ -499,7 +452,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes @@ -528,7 +480,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes @@ -557,7 +508,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes @@ -586,7 +536,6 @@ describe("SYSTEM_PROMPT", () => { false, undefined, // mcpHub undefined, // diffStrategy - undefined, // browserViewportSize defaultModeSlug, // mode undefined, // customModePrompts undefined, // customModes diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 0a187a9e2e3..0d6071644a9 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -45,7 +45,6 @@ async function generatePrompt( mode: Mode, mcpHub?: McpHub, diffStrategy?: DiffStrategy, - browserViewportSize?: string, promptComponent?: PromptComponent, customModeConfigs?: ModeConfig[], globalCustomInstructions?: string, @@ -116,7 +115,6 @@ export const SYSTEM_PROMPT = async ( supportsComputerUse: boolean, mcpHub?: McpHub, diffStrategy?: DiffStrategy, - browserViewportSize?: string, mode: Mode = defaultModeSlug, customModePrompts?: CustomModePrompts, customModes?: ModeConfig[], @@ -146,7 +144,6 @@ export const SYSTEM_PROMPT = async ( currentMode.slug, mcpHub, diffStrategy, - browserViewportSize, promptComponent, customModes, globalCustomInstructions, diff --git a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts index acef6508f00..0b776a2bad9 100644 --- a/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts +++ b/src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts @@ -20,21 +20,19 @@ describe("filterNativeToolsForMode - disabledTools", () => { makeTool("execute_command"), makeTool("read_file"), makeTool("write_to_file"), - makeTool("browser_action"), makeTool("apply_diff"), makeTool("edit"), ] it("removes tools listed in settings.disabledTools", () => { const settings = { - disabledTools: ["execute_command", "browser_action"], + disabledTools: ["execute_command"], } const result = filterNativeToolsForMode(nativeTools, "code", undefined, undefined, undefined, settings) const resultNames = result.map((t) => (t as any).function.name) expect(resultNames).not.toContain("execute_command") - expect(resultNames).not.toContain("browser_action") expect(resultNames).toContain("read_file") expect(resultNames).toContain("write_to_file") expect(resultNames).toContain("apply_diff") @@ -51,7 +49,6 @@ describe("filterNativeToolsForMode - disabledTools", () => { expect(resultNames).toContain("execute_command") expect(resultNames).toContain("read_file") expect(resultNames).toContain("write_to_file") - expect(resultNames).toContain("browser_action") expect(resultNames).toContain("apply_diff") }) @@ -67,7 +64,6 @@ describe("filterNativeToolsForMode - disabledTools", () => { it("combines disabledTools with other setting-based exclusions", () => { const settings = { - browserToolEnabled: false, disabledTools: ["execute_command"], } @@ -75,7 +71,6 @@ describe("filterNativeToolsForMode - disabledTools", () => { const resultNames = result.map((t) => (t as any).function.name) expect(resultNames).not.toContain("execute_command") - expect(resultNames).not.toContain("browser_action") expect(resultNames).toContain("read_file") }) diff --git a/src/core/prompts/tools/filter-tools-for-mode.ts b/src/core/prompts/tools/filter-tools-for-mode.ts index 085a8af3e2c..fdd41e7e330 100644 --- a/src/core/prompts/tools/filter-tools-for-mode.ts +++ b/src/core/prompts/tools/filter-tools-for-mode.ts @@ -291,11 +291,6 @@ export function filterNativeToolsForMode( allowedToolNames.delete("run_slash_command") } - // Conditionally exclude browser_action if disabled in settings - if (settings?.browserToolEnabled === false) { - allowedToolNames.delete("browser_action") - } - // Remove tools that are explicitly disabled via the disabledTools setting if (settings?.disabledTools?.length) { for (const toolName of settings.disabledTools) { @@ -387,11 +382,6 @@ export function isToolAllowedInMode( return true } - // Check for browser_action being disabled by user settings - if (toolName === "browser_action" && settings?.browserToolEnabled === false) { - return false - } - // Check if the tool is allowed by the mode's groups // Resolve to canonical name and check that single value const canonicalTool = resolveToolAlias(toolName) diff --git a/src/core/prompts/tools/native-tools/browser_action.ts b/src/core/prompts/tools/native-tools/browser_action.ts deleted file mode 100644 index 0068373313f..00000000000 --- a/src/core/prompts/tools/native-tools/browser_action.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type OpenAI from "openai" - -const BROWSER_ACTION_DESCRIPTION = `Request to interact with a Puppeteer-controlled browser. Every action, except close, will be responded to with a screenshot of the browser's current state, along with any new console logs. You may only perform one browser action per message, and wait for the user's response including a screenshot and logs to determine the next action. - -This tool is particularly useful for web development tasks as it allows you to launch a browser, navigate to pages, interact with elements through clicks and keyboard input, and capture the results through screenshots and console logs. Use it at key stages of web development tasks - such as after implementing new features, making substantial changes, when troubleshooting issues, or to verify the result of your work. Analyze the provided screenshots to ensure correct rendering or identify errors, and review console logs for runtime issues. - -The user may ask generic non-development tasks (such as "what's the latest news" or "look up the weather"), in which case you might use this tool to complete the task if it makes sense to do so, rather than trying to create a website or using curl to answer the question. However, if an available MCP server tool or resource can be used instead, you should prefer to use it over browser_action. - -Browser Session Lifecycle: -- Browser sessions start with launch and end with close -- The session remains active across multiple messages and tool uses -- You can use other tools while the browser session is active - it will stay open in the background` - -const ACTION_PARAMETER_DESCRIPTION = `Browser action to perform` - -const URL_PARAMETER_DESCRIPTION = `URL to open when performing the launch action; must include protocol` - -const COORDINATE_PARAMETER_DESCRIPTION = `Screen coordinate for hover or click actions in format 'x,y@WIDTHxHEIGHT' where x,y is the target position on the screenshot image and WIDTHxHEIGHT is the exact pixel dimensions of the screenshot image (not the browser viewport). Example: '450,203@900x600' means click at (450,203) on a 900x600 screenshot. The coordinates will be automatically scaled to match the actual viewport dimensions.` - -const SIZE_PARAMETER_DESCRIPTION = `Viewport dimensions for the resize action in format 'WIDTHxHEIGHT' or 'WIDTH,HEIGHT'. Example: '1280x800' or '1280,800'` - -const TEXT_PARAMETER_DESCRIPTION = `Text to type when performing the type action, or key name to press when performing the press action (e.g., 'Enter', 'Tab', 'Escape')` - -const PATH_PARAMETER_DESCRIPTION = `File path where the screenshot should be saved (relative to workspace). Required for screenshot action. Supports .png, .jpeg, and .webp extensions. Example: 'screenshots/result.png'` - -export default { - type: "function", - function: { - name: "browser_action", - description: BROWSER_ACTION_DESCRIPTION, - strict: false, - parameters: { - type: "object", - properties: { - action: { - type: "string", - description: ACTION_PARAMETER_DESCRIPTION, - enum: [ - "launch", - "click", - "hover", - "type", - "press", - "scroll_down", - "scroll_up", - "resize", - "close", - "screenshot", - ], - }, - url: { - type: ["string", "null"], - description: URL_PARAMETER_DESCRIPTION, - }, - coordinate: { - type: ["string", "null"], - description: COORDINATE_PARAMETER_DESCRIPTION, - }, - size: { - type: ["string", "null"], - description: SIZE_PARAMETER_DESCRIPTION, - }, - text: { - type: ["string", "null"], - description: TEXT_PARAMETER_DESCRIPTION, - }, - path: { - type: ["string", "null"], - description: PATH_PARAMETER_DESCRIPTION, - }, - }, - required: ["action"], - additionalProperties: false, - }, - }, -} satisfies OpenAI.Chat.ChatCompletionTool diff --git a/src/core/prompts/tools/native-tools/index.ts b/src/core/prompts/tools/native-tools/index.ts index 48f1071e1be..758914d2d65 100644 --- a/src/core/prompts/tools/native-tools/index.ts +++ b/src/core/prompts/tools/native-tools/index.ts @@ -4,7 +4,6 @@ import { apply_diff } from "./apply_diff" import applyPatch from "./apply_patch" import askFollowupQuestion from "./ask_followup_question" import attemptCompletion from "./attempt_completion" -import browserAction from "./browser_action" import codebaseSearch from "./codebase_search" import editTool from "./edit" import executeCommand from "./execute_command" @@ -53,7 +52,6 @@ export function getNativeTools(options: NativeToolsOptions = {}): OpenAI.Chat.Ch applyPatch, askFollowupQuestion, attemptCompletion, - browserAction, codebaseSearch, executeCommand, generateImage, diff --git a/src/core/prompts/types.ts b/src/core/prompts/types.ts index ca10dc12772..a4c17c3a6e6 100644 --- a/src/core/prompts/types.ts +++ b/src/core/prompts/types.ts @@ -3,7 +3,6 @@ */ export interface SystemPromptSettings { todoListEnabled: boolean - browserToolEnabled?: boolean useAgentRules: boolean /** When true, recursively discover and load .roo/rules from subdirectories */ enableSubfolderRules?: boolean diff --git a/src/core/task-persistence/rooMessage.ts b/src/core/task-persistence/rooMessage.ts index f71d9d3998e..4328ef7b928 100644 --- a/src/core/task-persistence/rooMessage.ts +++ b/src/core/task-persistence/rooMessage.ts @@ -17,8 +17,10 @@ import type { TextPart, ImagePart, FilePart, ToolCallPart, ToolResultPart } from /** * Union of content parts that can appear in a user message's content array. + * Includes `LegacyToolResultBlock` for backward compatibility with persisted + * data that stores Anthropic-format tool_result blocks inline in user messages. */ -export type UserContentPart = TextPart | ImagePart | FilePart +export type UserContentPart = TextPart | ImagePart | FilePart | LegacyToolResultBlock /** * A minimal content block with a type discriminator and optional text. @@ -73,9 +75,14 @@ export interface RooMessageMetadata { /** * A user-authored message. Content may be a plain string or an array of - * text, image, and file parts. Extends AI SDK `UserModelMessage` with metadata. + * text, image, file, and legacy tool-result parts. + * Overrides the AI SDK `content` field to include `LegacyToolResultBlock` + * for backward compatibility with persisted data. */ -export type RooUserMessage = UserModelMessage & RooMessageMetadata +export type RooUserMessage = Omit & + RooMessageMetadata & { + content: string | UserContentPart[] + } /** * An assistant-authored message. Content may be a plain string or an array of @@ -215,11 +222,17 @@ export interface LegacyToolUseBlock { input: unknown } +/** A text content block within a legacy Anthropic tool result. */ +export interface LegacyToolResultTextBlock { + type: "text" + text: string +} + /** Legacy Anthropic `tool_result` content block shape (persisted data from older versions). */ export interface LegacyToolResultBlock { type: "tool_result" tool_use_id: string - content?: string | ContentBlockParam[] + content?: string | LegacyToolResultTextBlock[] is_error?: boolean } diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index f7188de64fb..267958becae 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -71,13 +71,11 @@ import { combineCommandSequences } from "../../shared/combineCommandSequences" import { t } from "../../i18n" import { getApiMetrics, hasTokenUsageChanged, hasToolUsageChanged } from "../../shared/getApiMetrics" import { ClineAskResponse } from "../../shared/WebviewMessage" -import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" +import { defaultModeSlug, getModeBySlug } from "../../shared/modes" import { DiffStrategy, type ToolUse, type ToolParamName, toolParamNames } from "../../shared/tools" import { getModelMaxOutputTokens } from "../../shared/api" // services -import { UrlContentFetcher } from "../../services/browser/UrlContentFetcher" -import { BrowserSession } from "../../services/browser/BrowserSession" import { McpHub } from "../../services/mcp/McpHub" import { McpServerManager } from "../../services/mcp/McpServerManager" import { RepoPerTaskCheckpointService } from "../../services/checkpoints" @@ -326,12 +324,8 @@ export class Task extends EventEmitter implements TaskLike { rooIgnoreController?: RooIgnoreController rooProtectedController?: RooProtectedController fileContextTracker: FileContextTracker - urlContentFetcher: UrlContentFetcher terminalProcess?: RooTerminalProcess - // Computer User - browserSession: BrowserSession - // Editing diffViewProvider: DiffViewProvider diffStrategy?: DiffStrategy @@ -644,29 +638,6 @@ export class Task extends EventEmitter implements TaskLike { this.api = buildApiHandler(this.apiConfiguration) this.autoApprovalHandler = new AutoApprovalHandler() - this.urlContentFetcher = new UrlContentFetcher(provider.context) - this.browserSession = new BrowserSession(provider.context, (isActive: boolean) => { - // Add a message to indicate browser session status change - this.say("browser_session_status", isActive ? "Browser session opened" : "Browser session closed") - // Broadcast to browser panel - this.broadcastBrowserSessionUpdate() - - // When a browser session becomes active, automatically open/reveal the Browser Session tab - if (isActive) { - try { - // Lazy-load to avoid circular imports at module load time - const { BrowserSessionPanelManager } = require("../webview/BrowserSessionPanelManager") - const providerRef = this.providerRef.deref() - if (providerRef) { - BrowserSessionPanelManager.getInstance(providerRef) - .show() - .catch(() => {}) - } - } catch (err) { - console.error("[Task] Failed to auto-open Browser Session panel:", err) - } - } - }) this.consecutiveMistakeLimit = consecutiveMistakeLimit ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT this.providerRef = new WeakRef(provider) this.globalStoragePath = provider.context.globalStorageUri.fsPath @@ -1601,12 +1572,7 @@ export class Task extends EventEmitter implements TaskLike { if (message) { // Check if this is a tool approval ask that needs to be handled. - if ( - type === "tool" || - type === "command" || - type === "browser_action_launch" || - type === "use_mcp_server" - ) { + if (type === "tool" || type === "command" || type === "use_mcp_server") { // For tool approvals, we need to approve first, then send // the message if there's text/images. this.handleWebviewAskResponse("yesButtonClicked", message.text, message.images) @@ -1633,12 +1599,7 @@ export class Task extends EventEmitter implements TaskLike { if (message) { // If this is a tool approval ask, we need to approve first (yesButtonClicked) // and include any queued text/images. - if ( - type === "tool" || - type === "command" || - type === "browser_action_launch" || - type === "use_mcp_server" - ) { + if (type === "tool" || type === "command" || type === "use_mcp_server") { this.handleWebviewAskResponse("yesButtonClicked", message.text, message.images) } else { this.handleWebviewAskResponse("messageResponse", message.text, message.images) @@ -1836,7 +1797,6 @@ export class Task extends EventEmitter implements TaskLike { customModes: state?.customModes, experiments: state?.experiments, apiConfiguration, - browserToolEnabled: state?.browserToolEnabled ?? true, disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: false, @@ -2030,11 +1990,6 @@ export class Task extends EventEmitter implements TaskLike { contextTruncation, }) } - - // Broadcast browser session updates to panel when browser-related messages are added - if (type === "browser_action" || type === "browser_action_result" || type === "browser_session_status") { - this.broadcastBrowserSessionUpdate() - } } async sayAndCreateMissingParamError(toolName: ToolName, paramName: string, relPath?: string) { @@ -2572,28 +2527,6 @@ export class Task extends EventEmitter implements TaskLike { console.error("Error cleaning up command output artifacts:", error) }) - try { - this.urlContentFetcher.closeBrowser() - } catch (error) { - console.error("Error closing URL content fetcher browser:", error) - } - - try { - this.browserSession.closeBrowser() - } catch (error) { - console.error("Error closing browser session:", error) - } - // Also close the Browser Session panel when the task is disposed - try { - const provider = this.providerRef.deref() - if (provider) { - const { BrowserSessionPanelManager } = require("../webview/BrowserSessionPanelManager") - BrowserSessionPanelManager.getInstance(provider).dispose() - } - } catch (error) { - console.error("Error closing browser session panel:", error) - } - try { if (this.rooIgnoreController) { this.rooIgnoreController.dispose() @@ -2846,7 +2779,6 @@ export class Task extends EventEmitter implements TaskLike { const { content: parsedUserContent, mode: slashCommandMode } = await processUserContentMentions({ userContent: currentUserContent as Array, cwd: this.cwd, - urlContentFetcher: this.urlContentFetcher, fileContextTracker: this.fileContextTracker, rooIgnoreController: this.rooIgnoreController, showRooIgnoredFiles, @@ -3985,13 +3917,11 @@ export class Task extends EventEmitter implements TaskLike { const state = await this.providerRef.deref()?.getState() const { - browserViewportSize, mode, customModes, customModePrompts, customInstructions, experiments, - browserToolEnabled, language, apiConfiguration, enableSubfolderRules, @@ -4004,24 +3934,14 @@ export class Task extends EventEmitter implements TaskLike { throw new Error("Provider not available") } - // Align browser tool enablement with generateSystemPrompt: require model image support, - // mode to include the browser group, and the user setting to be enabled. - const modeConfig = getModeBySlug(mode ?? defaultModeSlug, customModes) - const modeSupportsBrowser = modeConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false - - // Check if model supports browser capability (images) const modelInfo = this.api.getModel().info - const modelSupportsBrowser = (modelInfo as any)?.supportsImages === true - - const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true) return SYSTEM_PROMPT( provider.context, this.cwd, - canUseBrowserTool, + false, mcpHub, this.diffStrategy, - browserViewportSize ?? "900x600", mode ?? defaultModeSlug, customModePrompts, customModes, @@ -4031,7 +3951,6 @@ export class Task extends EventEmitter implements TaskLike { rooIgnoreInstructions, { todoListEnabled: apiConfiguration?.todoListEnabled ?? true, - browserToolEnabled: browserToolEnabled ?? true, useAgentRules: vscode.workspace.getConfiguration(Package.name).get("useAgentRules") ?? true, enableSubfolderRules: enableSubfolderRules ?? false, @@ -4092,7 +4011,6 @@ export class Task extends EventEmitter implements TaskLike { customModes: state?.customModes, experiments: state?.experiments, apiConfiguration, - browserToolEnabled: state?.browserToolEnabled ?? true, disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: false, @@ -4307,7 +4225,6 @@ export class Task extends EventEmitter implements TaskLike { customModes: state?.customModes, experiments: state?.experiments, apiConfiguration, - browserToolEnabled: state?.browserToolEnabled ?? true, disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: false, @@ -4472,7 +4389,6 @@ export class Task extends EventEmitter implements TaskLike { customModes: state?.customModes, experiments: state?.experiments, apiConfiguration, - browserToolEnabled: state?.browserToolEnabled ?? true, disabledTools: state?.disabledTools, modelInfo, includeAllToolsWithRestrictions: supportsAllowedFunctionNames, @@ -4944,41 +4860,6 @@ export class Task extends EventEmitter implements TaskLike { return this._messageManager } - /** - * Broadcast browser session updates to the browser panel (if open) - */ - private broadcastBrowserSessionUpdate(): void { - const provider = this.providerRef.deref() - if (!provider) { - return - } - - try { - const { BrowserSessionPanelManager } = require("../webview/BrowserSessionPanelManager") - const panelManager = BrowserSessionPanelManager.getInstance(provider) - - // Get browser session messages - const browserSessionStartIndex = this.clineMessages.findIndex( - (m) => - m.ask === "browser_action_launch" || - (m.say === "browser_session_status" && m.text?.includes("opened")), - ) - - const browserSessionMessages = - browserSessionStartIndex !== -1 ? this.clineMessages.slice(browserSessionStartIndex) : [] - - const isBrowserSessionActive = this.browserSession?.isSessionActive() ?? false - - // Update the panel asynchronously - panelManager.updateBrowserSession(browserSessionMessages, isBrowserSessionActive).catch((error: Error) => { - console.error("Failed to broadcast browser session update:", error) - }) - } catch (error) { - // Silently fail if panel manager is not available - console.debug("Browser panel not available for update:", error) - } - } - /** * Process any queued messages by dequeuing and submitting them. * This ensures that queued user messages are sent when appropriate, diff --git a/src/core/task/__tests__/Task.dispose.test.ts b/src/core/task/__tests__/Task.dispose.test.ts index 24aee183ac6..16bf3c91c2f 100644 --- a/src/core/task/__tests__/Task.dispose.test.ts +++ b/src/core/task/__tests__/Task.dispose.test.ts @@ -13,8 +13,6 @@ vi.mock("../../../integrations/terminal/TerminalRegistry", () => ({ vi.mock("../../ignore/RooIgnoreController") vi.mock("../../protect/RooProtectedController") vi.mock("../../context-tracking/FileContextTracker") -vi.mock("../../../services/browser/UrlContentFetcher") -vi.mock("../../../services/browser/BrowserSession") vi.mock("../../../integrations/editor/DiffViewProvider") vi.mock("../../tools/ToolRepetitionDetector") vi.mock("../../../api", () => ({ diff --git a/src/core/task/__tests__/Task.spec.ts b/src/core/task/__tests__/Task.spec.ts index 1f034f29042..135d691de93 100644 --- a/src/core/task/__tests__/Task.spec.ts +++ b/src/core/task/__tests__/Task.spec.ts @@ -985,7 +985,6 @@ describe("Cline", () => { const { content: processedContent } = await processUserContentMentions({ userContent, cwd: cline.cwd, - urlContentFetcher: cline.urlContentFetcher, fileContextTracker: cline.fileContextTracker, }) diff --git a/src/core/task/__tests__/Task.throttle.test.ts b/src/core/task/__tests__/Task.throttle.test.ts index 904bc46b55e..c9d78dc291a 100644 --- a/src/core/task/__tests__/Task.throttle.test.ts +++ b/src/core/task/__tests__/Task.throttle.test.ts @@ -14,8 +14,6 @@ vi.mock("../../../integrations/terminal/TerminalRegistry", () => ({ vi.mock("../../ignore/RooIgnoreController") vi.mock("../../protect/RooProtectedController") vi.mock("../../context-tracking/FileContextTracker") -vi.mock("../../../services/browser/UrlContentFetcher") -vi.mock("../../../services/browser/BrowserSession") vi.mock("../../../integrations/editor/DiffViewProvider") vi.mock("../../tools/ToolRepetitionDetector") vi.mock("../../../api", () => ({ diff --git a/src/core/task/__tests__/native-tools-filtering.spec.ts b/src/core/task/__tests__/native-tools-filtering.spec.ts index c9cd6a30604..1c393456ab0 100644 --- a/src/core/task/__tests__/native-tools-filtering.spec.ts +++ b/src/core/task/__tests__/native-tools-filtering.spec.ts @@ -10,14 +10,14 @@ describe("Native Tools Filtering by Mode", () => { slug: "architect", name: "Architect", roleDefinition: "Test architect", - groups: ["read", "browser", "mcp"] as const, + groups: ["read", "mcp"] as const, } const codeMode: ModeConfig = { slug: "code", name: "Code", roleDefinition: "Test code", - groups: ["read", "edit", "browser", "command", "mcp"] as const, + groups: ["read", "edit", "command", "mcp"] as const, } // Import the functions we need to test diff --git a/src/core/task/build-tools.ts b/src/core/task/build-tools.ts index ab74f9443ca..c32d8f6f9b2 100644 --- a/src/core/task/build-tools.ts +++ b/src/core/task/build-tools.ts @@ -22,7 +22,6 @@ interface BuildToolsOptions { customModes: ModeConfig[] | undefined experiments: Record | undefined apiConfiguration: ProviderSettings | undefined - browserToolEnabled: boolean disabledTools?: string[] modelInfo?: ModelInfo /** @@ -88,7 +87,6 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO customModes, experiments, apiConfiguration, - browserToolEnabled, disabledTools, modelInfo, includeAllToolsWithRestrictions, @@ -103,7 +101,6 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO // Build settings object for tool filtering. const filterSettings = { todoListEnabled: apiConfiguration?.todoListEnabled ?? true, - browserToolEnabled: browserToolEnabled ?? true, disabledTools, modelInfo, } diff --git a/src/core/tools/BrowserActionTool.ts b/src/core/tools/BrowserActionTool.ts deleted file mode 100644 index 3bd584e0cb4..00000000000 --- a/src/core/tools/BrowserActionTool.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { Anthropic } from "@anthropic-ai/sdk" - -import { BrowserAction, BrowserActionResult, browserActions, ClineSayBrowserAction } from "@roo-code/types" - -import { Task } from "../task/Task" -import { ToolUse, AskApproval, HandleError, PushToolResult } from "../../shared/tools" -import { formatResponse } from "../prompts/responses" - -import { scaleCoordinate } from "../../shared/browserUtils" - -export async function browserActionTool( - cline: Task, - block: ToolUse, - askApproval: AskApproval, - handleError: HandleError, - pushToolResult: PushToolResult, -) { - const action: BrowserAction | undefined = block.params.action as BrowserAction - const url: string | undefined = block.params.url - const coordinate: string | undefined = block.params.coordinate - const text: string | undefined = block.params.text - const size: string | undefined = block.params.size - const filePath: string | undefined = block.params.path - - if (!action || !browserActions.includes(action)) { - // checking for action to ensure it is complete and valid - if (!block.partial) { - // if the block is complete and we don't have a valid action cline is a mistake - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "action")) - // Do not close the browser on parameter validation errors - } - - return - } - - try { - if (block.partial) { - if (action === "launch") { - await cline.ask("browser_action_launch", url ?? "", block.partial).catch(() => {}) - } else { - await cline.say( - "browser_action", - JSON.stringify({ - action: action as BrowserAction, - coordinate: coordinate ?? "", - text: text ?? "", - size: size ?? "", - } satisfies ClineSayBrowserAction), - undefined, - block.partial, - ) - } - return - } else { - // Initialize with empty object to avoid "used before assigned" errors - let browserActionResult: BrowserActionResult = {} - - if (action === "launch") { - if (!url) { - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "url")) - // Do not close the browser on parameter validation errors - return - } - - cline.consecutiveMistakeCount = 0 - const didApprove = await askApproval("browser_action_launch", url) - - if (!didApprove) { - return - } - - // NOTE: It's okay that we call cline message since the partial inspect_site is finished streaming. - // The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. - // For example the api_req_finished message would interfere with the partial message, so we needed to remove that. - - // Launch browser first (this triggers "Browser session opened" status message) - await cline.browserSession.launchBrowser() - - // Create browser_action say message AFTER launching so status appears first - await cline.say( - "browser_action", - JSON.stringify({ - action: "launch" as BrowserAction, - text: url, - } satisfies ClineSayBrowserAction), - undefined, - false, - ) - - browserActionResult = await cline.browserSession.navigateToUrl(url) - } else { - // Variables to hold validated and processed parameters - let processedCoordinate = coordinate - - if (action === "click" || action === "hover") { - if (!coordinate) { - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "coordinate")) - // Do not close the browser on parameter validation errors - return // can't be within an inner switch - } - - // Get viewport dimensions from the browser session - const viewportSize = cline.browserSession.getViewportSize() - const viewportWidth = viewportSize.width || 900 // default to 900 if not available - const viewportHeight = viewportSize.height || 600 // default to 600 if not available - - // Scale coordinate from image dimensions to viewport dimensions - try { - processedCoordinate = scaleCoordinate(coordinate, viewportWidth, viewportHeight) - } catch (error) { - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult( - await cline.sayAndCreateMissingParamError( - "browser_action", - "coordinate", - error instanceof Error ? error.message : String(error), - ), - ) - return - } - } - - if (action === "type" || action === "press") { - if (!text) { - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "text")) - // Do not close the browser on parameter validation errors - return - } - } - - if (action === "resize") { - if (!size) { - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "size")) - // Do not close the browser on parameter validation errors - return - } - } - - if (action === "screenshot") { - if (!filePath) { - cline.consecutiveMistakeCount++ - cline.recordToolError("browser_action") - cline.didToolFailInCurrentTurn = true - pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "path")) - // Do not close the browser on parameter validation errors - return - } - } - - cline.consecutiveMistakeCount = 0 - - // Prepare say payload; include executedCoordinate for pointer actions - const sayPayload: ClineSayBrowserAction & { executedCoordinate?: string } = { - action: action as BrowserAction, - coordinate, - text, - size, - } - if ((action === "click" || action === "hover") && processedCoordinate) { - sayPayload.executedCoordinate = processedCoordinate - } - await cline.say("browser_action", JSON.stringify(sayPayload), undefined, false) - - switch (action) { - case "click": - browserActionResult = await cline.browserSession.click(processedCoordinate!) - break - case "hover": - browserActionResult = await cline.browserSession.hover(processedCoordinate!) - break - case "type": - browserActionResult = await cline.browserSession.type(text!) - break - case "press": - browserActionResult = await cline.browserSession.press(text!) - break - case "scroll_down": - browserActionResult = await cline.browserSession.scrollDown() - break - case "scroll_up": - browserActionResult = await cline.browserSession.scrollUp() - break - case "resize": - browserActionResult = await cline.browserSession.resize(size!) - break - case "screenshot": - browserActionResult = await cline.browserSession.saveScreenshot(filePath!, cline.cwd) - break - case "close": - browserActionResult = await cline.browserSession.closeBrowser() - break - } - } - - switch (action) { - case "launch": - case "click": - case "hover": - case "type": - case "press": - case "scroll_down": - case "scroll_up": - case "resize": - case "screenshot": { - await cline.say("browser_action_result", JSON.stringify(browserActionResult)) - - const images = browserActionResult?.screenshot ? [browserActionResult.screenshot] : [] - - let messageText = - action === "screenshot" - ? `Screenshot saved to: ${filePath}` - : `The browser action has been executed.` - - messageText += `\n\n**CRITICAL**: When providing click/hover coordinates:` - messageText += `\n1. Screenshot dimensions != Browser viewport dimensions` - messageText += `\n2. Measure x,y on the screenshot image you see below` - messageText += `\n3. Use format: x,y@WIDTHxHEIGHT where WIDTHxHEIGHT is the EXACT pixel size of the screenshot image` - messageText += `\n4. Never use the browser viewport size for WIDTHxHEIGHT - it is only for reference and is often larger than the screenshot` - messageText += `\n5. Screenshots are often downscaled - always use the dimensions you see in the image` - messageText += `\nExample: Viewport 1280x800, screenshot 1000x625, click (500,300) -> 500,300@1000x625` - - // Include browser viewport dimensions (for reference only) - if (browserActionResult?.viewportWidth && browserActionResult?.viewportHeight) { - messageText += `\n\nBrowser viewport: ${browserActionResult.viewportWidth}x${browserActionResult.viewportHeight}` - } - - // Include cursor position if available - if (browserActionResult?.currentMousePosition) { - messageText += `\nCursor position: ${browserActionResult.currentMousePosition}` - } - - messageText += `\n\nConsole logs:\n${browserActionResult?.logs || "(No new logs)"}\n` - - if (images.length > 0) { - const blocks = [ - ...formatResponse.imageBlocks(images), - { type: "text", text: messageText } as Anthropic.TextBlockParam, - ] - pushToolResult(blocks) - } else { - pushToolResult(messageText) - } - - break - } - case "close": - pushToolResult( - formatResponse.toolResult( - `The browser has been closed. You may now proceed to using other tools.`, - ), - ) - - break - } - - return - } - } catch (error) { - // Keep the browser session alive on errors; report the error without terminating the session - await handleError("executing browser action", error) - return - } -} diff --git a/src/core/tools/ToolRepetitionDetector.ts b/src/core/tools/ToolRepetitionDetector.ts index 9e70bb41a00..27592c5210b 100644 --- a/src/core/tools/ToolRepetitionDetector.ts +++ b/src/core/tools/ToolRepetitionDetector.ts @@ -33,13 +33,6 @@ export class ToolRepetitionDetector { messageDetail: string } } { - // Browser scroll actions should not be subject to repetition detection - // as they are frequently needed for navigating through web pages - if (this.isBrowserScrollAction(currentToolCallBlock)) { - // Allow browser scroll actions without counting them as repetitions - return { allowExecution: true } - } - // Serialize the block to a canonical JSON string for comparison const currentToolCallJson = this.serializeToolUse(currentToolCallBlock) @@ -74,21 +67,6 @@ export class ToolRepetitionDetector { return { allowExecution: true } } - /** - * Checks if a tool use is a browser scroll action - * - * @param toolUse The ToolUse object to check - * @returns true if the tool is a browser_action with scroll_down or scroll_up action - */ - private isBrowserScrollAction(toolUse: ToolUse): boolean { - if (toolUse.name !== "browser_action") { - return false - } - - const action = toolUse.params.action as string - return action === "scroll_down" || action === "scroll_up" - } - /** * Serializes a ToolUse object into a canonical JSON string for comparison * diff --git a/src/core/tools/__tests__/BrowserActionTool.coordinateScaling.spec.ts b/src/core/tools/__tests__/BrowserActionTool.coordinateScaling.spec.ts deleted file mode 100644 index 4294fff4d3a..00000000000 --- a/src/core/tools/__tests__/BrowserActionTool.coordinateScaling.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -// Test coordinate scaling functionality in browser actions -import { describe, it, expect } from "vitest" -import { scaleCoordinate } from "../../../shared/browserUtils" - -describe("Browser Action Coordinate Scaling", () => { - describe("Coordinate format validation", () => { - it("should match valid coordinate format with image dimensions", () => { - const validFormats = [ - "450,300@1024x768", - "0,0@1920x1080", - "1920,1080@1920x1080", - "100,200@800x600", - " 273 , 273 @ 1280x800 ", - "267,273@1280,800", // comma separator for dimensions - "450,300@1024,768", // comma separator for dimensions - ] - - validFormats.forEach((coord) => { - // Should not throw - expect(() => scaleCoordinate(coord, 900, 600)).not.toThrow() - }) - }) - - it("should not match invalid coordinate formats", () => { - const invalidFormats = [ - "450,300", // missing image dimensions - "450,300@", // incomplete dimensions - "450,300@1024", // missing height - "450,300@1024x", // missing height value - "@1024x768", // missing coordinates - "450@1024x768", // missing y coordinate - ",300@1024x768", // missing x coordinate - "450,300@1024x768x2", // extra dimension - "a,b@1024x768", // non-numeric coordinates - "450,300@axb", // non-numeric dimensions - ] - - invalidFormats.forEach((coord) => { - expect(() => scaleCoordinate(coord, 900, 600)).toThrow() - }) - }) - }) - - describe("Coordinate scaling logic", () => { - it("should correctly scale coordinates from image to viewport", () => { - // Test case 1: Same dimensions (no scaling) - expect(scaleCoordinate("450,300@900x600", 900, 600)).toBe("450,300") - - // Test case 2: Half dimensions (2x upscale) - expect(scaleCoordinate("225,150@450x300", 900, 600)).toBe("450,300") - - // Test case 3: Double dimensions (0.5x downscale) - expect(scaleCoordinate("900,600@1800x1200", 900, 600)).toBe("450,300") - - // Test case 4: Different aspect ratio - expect(scaleCoordinate("512,384@1024x768", 1920, 1080)).toBe("960,540") - - // Test case 5: Edge cases (0,0) - expect(scaleCoordinate("0,0@1024x768", 1920, 1080)).toBe("0,0") - - // Test case 6: Edge cases (max coordinates) - expect(scaleCoordinate("1024,768@1024x768", 1920, 1080)).toBe("1920,1080") - }) - - it("should throw error for invalid coordinate format", () => { - // Test invalid formats - expect(() => scaleCoordinate("450,300", 900, 600)).toThrow("Invalid coordinate format") - expect(() => scaleCoordinate("450,300@1024", 900, 600)).toThrow("Invalid coordinate format") - expect(() => scaleCoordinate("invalid", 900, 600)).toThrow("Invalid coordinate format") - }) - - it("should handle rounding correctly", () => { - // Test rounding behavior - // 333 / 1000 * 900 = 299.7 -> rounds to 300 - expect(scaleCoordinate("333,333@1000x1000", 900, 900)).toBe("300,300") - - // 666 / 1000 * 900 = 599.4 -> rounds to 599 - expect(scaleCoordinate("666,666@1000x1000", 900, 900)).toBe("599,599") - - // 500 / 1000 * 900 = 450.0 -> rounds to 450 - expect(scaleCoordinate("500,500@1000x1000", 900, 900)).toBe("450,450") - }) - }) -}) diff --git a/src/core/tools/__tests__/BrowserActionTool.screenshot.spec.ts b/src/core/tools/__tests__/BrowserActionTool.screenshot.spec.ts deleted file mode 100644 index 5f3dd271b2d..00000000000 --- a/src/core/tools/__tests__/BrowserActionTool.screenshot.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { browserActions } from "@roo-code/types" - -describe("Browser Action Screenshot", () => { - describe("browserActions array", () => { - it("should include screenshot action", () => { - expect(browserActions).toContain("screenshot") - }) - - it("should have screenshot as a valid browser action type", () => { - const allActions = [ - "launch", - "click", - "hover", - "type", - "press", - "scroll_down", - "scroll_up", - "resize", - "close", - "screenshot", - ] - expect(browserActions).toEqual(allActions) - }) - }) -}) diff --git a/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts b/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts index bda80d711f5..5fe4de8a335 100644 --- a/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts +++ b/src/core/tools/__tests__/ToolRepetitionDetector.spec.ts @@ -403,166 +403,6 @@ describe("ToolRepetitionDetector", () => { }) }) - // ===== Browser Scroll Action Exclusion tests ===== - describe("browser scroll action exclusion", () => { - it("should not count browser scroll_down actions as repetitions", () => { - const detector = new ToolRepetitionDetector(2) - - // Create browser_action tool use with scroll_down - const scrollDownTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: { action: "scroll_down" }, - partial: false, - } - - // Should allow unlimited scroll_down actions - for (let i = 0; i < 10; i++) { - const result = detector.check(scrollDownTool) - expect(result.allowExecution).toBe(true) - expect(result.askUser).toBeUndefined() - } - }) - - it("should not count browser scroll_up actions as repetitions", () => { - const detector = new ToolRepetitionDetector(2) - - // Create browser_action tool use with scroll_up - const scrollUpTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: { action: "scroll_up" }, - partial: false, - } - - // Should allow unlimited scroll_up actions - for (let i = 0; i < 10; i++) { - const result = detector.check(scrollUpTool) - expect(result.allowExecution).toBe(true) - expect(result.askUser).toBeUndefined() - } - }) - - it("should not count alternating scroll_down and scroll_up as repetitions", () => { - const detector = new ToolRepetitionDetector(2) - - const scrollDownTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: { action: "scroll_down" }, - partial: false, - } - - const scrollUpTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: { action: "scroll_up" }, - partial: false, - } - - // Alternate between scroll_down and scroll_up - for (let i = 0; i < 5; i++) { - let result = detector.check(scrollDownTool) - expect(result.allowExecution).toBe(true) - expect(result.askUser).toBeUndefined() - - result = detector.check(scrollUpTool) - expect(result.allowExecution).toBe(true) - expect(result.askUser).toBeUndefined() - } - }) - - it("should still apply repetition detection to other browser_action types", () => { - const detector = new ToolRepetitionDetector(2) - - // Create browser_action tool use with click action - const clickTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: { action: "click", coordinate: "[100, 200]" }, - partial: false, - } - - // First call allowed - expect(detector.check(clickTool).allowExecution).toBe(true) - - // Second call allowed - expect(detector.check(clickTool).allowExecution).toBe(true) - - // Third identical call should be blocked (limit is 2) - const result = detector.check(clickTool) - expect(result.allowExecution).toBe(false) - expect(result.askUser).toBeDefined() - }) - - it("should still apply repetition detection to non-browser tools", () => { - const detector = new ToolRepetitionDetector(2) - - const readFileTool = createToolUse("read_file", "read_file", { path: "test.txt" }) - - // First call allowed - expect(detector.check(readFileTool).allowExecution).toBe(true) - - // Second call allowed - expect(detector.check(readFileTool).allowExecution).toBe(true) - - // Third identical call should be blocked (limit is 2) - const result = detector.check(readFileTool) - expect(result.allowExecution).toBe(false) - expect(result.askUser).toBeDefined() - }) - - it("should not interfere with repetition detection of other tools when scroll actions are interspersed", () => { - const detector = new ToolRepetitionDetector(2) - - const scrollTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: { action: "scroll_down" }, - partial: false, - } - - const otherTool = createToolUse("execute_command", "execute_command", { command: "ls" }) - - // First execute_command - expect(detector.check(otherTool).allowExecution).toBe(true) - - // Scroll actions in between (should not affect counter) - expect(detector.check(scrollTool).allowExecution).toBe(true) - expect(detector.check(scrollTool).allowExecution).toBe(true) - - // Second execute_command - expect(detector.check(otherTool).allowExecution).toBe(true) - - // More scroll actions - expect(detector.check(scrollTool).allowExecution).toBe(true) - - // Third execute_command should be blocked - const result = detector.check(otherTool) - expect(result.allowExecution).toBe(false) - expect(result.askUser).toBeDefined() - }) - - it("should handle browser_action with missing or invalid action parameter gracefully", () => { - const detector = new ToolRepetitionDetector(2) - - // Browser action without action parameter - const noActionTool: ToolUse = { - type: "tool_use", - name: "browser_action" as ToolName, - params: {}, - partial: false, - } - - // Should apply normal repetition detection - expect(detector.check(noActionTool).allowExecution).toBe(true) - expect(detector.check(noActionTool).allowExecution).toBe(true) - const result = detector.check(noActionTool) - expect(result.allowExecution).toBe(false) - expect(result.askUser).toBeDefined() - }) - }) - // ===== Native Protocol (nativeArgs) tests ===== describe("native protocol with nativeArgs", () => { it("should differentiate read_file calls with different files in nativeArgs", () => { diff --git a/src/core/tools/__tests__/validateToolUse.spec.ts b/src/core/tools/__tests__/validateToolUse.spec.ts index b4622096ab8..29455e36883 100644 --- a/src/core/tools/__tests__/validateToolUse.spec.ts +++ b/src/core/tools/__tests__/validateToolUse.spec.ts @@ -30,12 +30,8 @@ describe("mode-validator", () => { describe("architect mode", () => { it("allows configured tools", () => { - // Architect mode has read, browser, and mcp groups - const architectTools = [ - ...TOOL_GROUPS.read.tools, - ...TOOL_GROUPS.browser.tools, - ...TOOL_GROUPS.mcp.tools, - ] + // Architect mode has read and mcp groups + const architectTools = [...TOOL_GROUPS.read.tools, ...TOOL_GROUPS.mcp.tools] architectTools.forEach((tool) => { expect(isToolAllowedForMode(tool, architectMode, [])).toBe(true) }) @@ -44,8 +40,8 @@ describe("mode-validator", () => { describe("ask mode", () => { it("allows configured tools", () => { - // Ask mode has read, browser, and mcp groups - const askTools = [...TOOL_GROUPS.read.tools, ...TOOL_GROUPS.browser.tools, ...TOOL_GROUPS.mcp.tools] + // Ask mode has read and mcp groups + const askTools = [...TOOL_GROUPS.read.tools, ...TOOL_GROUPS.mcp.tools] askTools.forEach((tool) => { expect(isToolAllowedForMode(tool, askMode, [])).toBe(true) }) @@ -211,7 +207,7 @@ describe("mode-validator", () => { }) it("blocks tool when disabledTools is converted to toolRequirements", () => { - const disabledTools = ["execute_command", "browser_action"] + const disabledTools = ["execute_command", "search_files"] const toolRequirements = disabledTools.reduce( (acc: Record, tool: string) => { acc[tool] = false @@ -223,8 +219,8 @@ describe("mode-validator", () => { expect(() => validateToolUse("execute_command", codeMode, [], toolRequirements)).toThrow( 'Tool "execute_command" is not allowed in code mode.', ) - expect(() => validateToolUse("browser_action", codeMode, [], toolRequirements)).toThrow( - 'Tool "browser_action" is not allowed in code mode.', + expect(() => validateToolUse("search_files", codeMode, [], toolRequirements)).toThrow( + 'Tool "search_files" is not allowed in code mode.', ) }) diff --git a/src/core/webview/BrowserSessionPanelManager.ts b/src/core/webview/BrowserSessionPanelManager.ts deleted file mode 100644 index 514c1315f7f..00000000000 --- a/src/core/webview/BrowserSessionPanelManager.ts +++ /dev/null @@ -1,310 +0,0 @@ -import * as vscode from "vscode" -import type { ClineMessage } from "@roo-code/types" -import { getUri } from "./getUri" -import { getNonce } from "./getNonce" -import type { ClineProvider } from "./ClineProvider" -import { webviewMessageHandler } from "./webviewMessageHandler" - -export class BrowserSessionPanelManager { - private static instances: WeakMap = new WeakMap() - private panel: vscode.WebviewPanel | undefined - private disposables: vscode.Disposable[] = [] - private isReady: boolean = false - private pendingUpdate?: { messages: ClineMessage[]; isActive: boolean } - private pendingNavigateIndex?: number - private userManuallyClosedPanel: boolean = false - - private constructor(private readonly provider: ClineProvider) {} - - /** - * Get or create a BrowserSessionPanelManager instance for the given provider - */ - public static getInstance(provider: ClineProvider): BrowserSessionPanelManager { - let instance = BrowserSessionPanelManager.instances.get(provider) - if (!instance) { - instance = new BrowserSessionPanelManager(provider) - BrowserSessionPanelManager.instances.set(provider, instance) - } - return instance - } - - /** - * Show the browser session panel, creating it if necessary - */ - public async show(): Promise { - await this.createOrShowPanel() - - // Send initial browser session data - const task = this.provider.getCurrentTask() - if (task) { - const messages = task.clineMessages || [] - const browserSessionStartIndex = messages.findIndex( - (m) => - m.ask === "browser_action_launch" || - (m.say === "browser_session_status" && m.text?.includes("opened")), - ) - const browserSessionMessages = - browserSessionStartIndex !== -1 ? messages.slice(browserSessionStartIndex) : [] - const isBrowserSessionActive = task.browserSession?.isSessionActive() ?? false - - await this.updateBrowserSession(browserSessionMessages, isBrowserSessionActive) - } - } - - private async createOrShowPanel(): Promise { - // If panel already exists, show it - if (this.panel) { - this.panel.reveal(vscode.ViewColumn.One) - return - } - - const extensionUri = this.provider.context.extensionUri - const extensionMode = this.provider.context.extensionMode - - // Create new panel - this.panel = vscode.window.createWebviewPanel("roo.browserSession", "Browser Session", vscode.ViewColumn.One, { - enableScripts: true, - retainContextWhenHidden: true, - localResourceRoots: [extensionUri], - }) - - // Set up the webview's HTML content - this.panel.webview.html = - extensionMode === vscode.ExtensionMode.Development - ? await this.getHMRHtmlContent(this.panel.webview, extensionUri) - : this.getHtmlContent(this.panel.webview, extensionUri) - - // Wire message channel for this panel (state handshake + actions) - this.panel.webview.onDidReceiveMessage( - async (message: any) => { - try { - // Let the shared handler process commands that work for any webview - if (message?.type) { - await webviewMessageHandler(this.provider as any, message) - } - // Panel-specific readiness and initial state - if (message?.type === "webviewDidLaunch") { - this.isReady = true - // Send full extension state to this panel (the sidebar postState targets the main webview) - const state = await (this.provider as any).getStateToPostToWebview?.() - if (state) { - await this.panel?.webview.postMessage({ type: "state", state }) - } - // Flush any pending browser session update queued before readiness - if (this.pendingUpdate) { - await this.updateBrowserSession(this.pendingUpdate.messages, this.pendingUpdate.isActive) - this.pendingUpdate = undefined - } - // Flush any pending navigation request queued before readiness - if (this.pendingNavigateIndex !== undefined) { - await this.navigateToStep(this.pendingNavigateIndex) - this.pendingNavigateIndex = undefined - } - } - } catch (err) { - console.error("[BrowserSessionPanel] onDidReceiveMessage error:", err) - } - }, - undefined, - this.disposables, - ) - - // Handle panel disposal - track that user closed it manually - this.panel.onDidDispose( - () => { - // Mark that user manually closed the panel (unless we're programmatically disposing) - if (this.panel) { - this.userManuallyClosedPanel = true - } - this.panel = undefined - this.dispose() - }, - null, - this.disposables, - ) - } - - public async updateBrowserSession(messages: ClineMessage[], isBrowserSessionActive: boolean): Promise { - if (!this.panel) { - return - } - // If the panel isn't ready yet, queue the latest snapshot to post after handshake - if (!this.isReady) { - this.pendingUpdate = { messages, isActive: isBrowserSessionActive } - return - } - - await this.panel.webview.postMessage({ - type: "browserSessionUpdate", - browserSessionMessages: messages, - isBrowserSessionActive, - }) - } - - /** - * Navigate the Browser Session panel to a specific step index. - * If the panel isn't ready yet, queue the navigation to run after handshake. - */ - public async navigateToStep(stepIndex: number): Promise { - if (!this.panel) { - return - } - if (!this.isReady) { - this.pendingNavigateIndex = stepIndex - return - } - - await this.panel.webview.postMessage({ - type: "browserSessionNavigate", - stepIndex, - }) - } - - /** - * Reset the manual close flag (call this when a new browser session launches) - */ - public resetManualCloseFlag(): void { - this.userManuallyClosedPanel = false - } - - /** - * Check if auto-opening should be allowed (not manually closed by user) - */ - public shouldAllowAutoOpen(): boolean { - return !this.userManuallyClosedPanel - } - - /** - * Whether the Browser Session panel is currently open. - */ - public isOpen(): boolean { - return !!this.panel - } - - /** - * Toggle the Browser Session panel visibility. - * - If open: closes it - * - If closed: opens it and sends initial session snapshot - */ - public async toggle(): Promise { - if (this.panel) { - this.dispose() - } else { - await this.show() - } - } - - public dispose(): void { - // Clear the panel reference before disposing to prevent marking as manual close - const panelToDispose = this.panel - this.panel = undefined - - while (this.disposables.length) { - const disposable = this.disposables.pop() - if (disposable) { - disposable.dispose() - } - } - try { - panelToDispose?.dispose() - } catch {} - this.isReady = false - this.pendingUpdate = undefined - } - - private async getHMRHtmlContent(webview: vscode.Webview, extensionUri: vscode.Uri): Promise { - const fs = require("fs") - const path = require("path") - let localPort = "5173" - - try { - const portFilePath = path.resolve(__dirname, "../../.vite-port") - if (fs.existsSync(portFilePath)) { - localPort = fs.readFileSync(portFilePath, "utf8").trim() - } - } catch (err) { - console.error("[BrowserSessionPanel:Vite] Failed to read port file:", err) - } - - const localServerUrl = `localhost:${localPort}` - const nonce = getNonce() - - const stylesUri = getUri(webview, extensionUri, ["webview-ui", "build", "assets", "index.css"]) - const codiconsUri = getUri(webview, extensionUri, ["assets", "codicons", "codicon.css"]) - - const scriptUri = `http://${localServerUrl}/src/browser-panel.tsx` - - const reactRefresh = ` - - ` - - const csp = [ - "default-src 'none'", - `font-src ${webview.cspSource} data:`, - `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl}`, - `img-src ${webview.cspSource} data:`, - `script-src 'unsafe-eval' ${webview.cspSource} http://${localServerUrl} 'nonce-${nonce}'`, - `connect-src ${webview.cspSource} ws://${localServerUrl} http://${localServerUrl}`, - ] - - return ` - - - - - - - - - Browser Session - - -
- ${reactRefresh} - - - - ` - } - - private getHtmlContent(webview: vscode.Webview, extensionUri: vscode.Uri): string { - const stylesUri = getUri(webview, extensionUri, ["webview-ui", "build", "assets", "index.css"]) - const scriptUri = getUri(webview, extensionUri, ["webview-ui", "build", "assets", "browser-panel.js"]) - const codiconsUri = getUri(webview, extensionUri, ["assets", "codicons", "codicon.css"]) - - const nonce = getNonce() - - const csp = [ - "default-src 'none'", - `font-src ${webview.cspSource} data:`, - `style-src ${webview.cspSource} 'unsafe-inline'`, - `img-src ${webview.cspSource} data:`, - `script-src ${webview.cspSource} 'wasm-unsafe-eval' 'nonce-${nonce}'`, - `connect-src ${webview.cspSource}`, - ] - - return ` - - - - - - - - - Browser Session - - -
- - - - ` - } -} diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 0f42d6c5fb9..1e33525f0b7 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2091,7 +2091,6 @@ export class ClineProvider alwaysAllowExecute, allowedCommands, deniedCommands, - alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, alwaysAllowSubtasks, @@ -2106,11 +2105,7 @@ export class ClineProvider checkpointTimeout, taskHistory, soundVolume, - browserViewportSize, screenshotQuality, - remoteBrowserHost, - remoteBrowserEnabled, - cachedChromeHostUrl, writeDelayMs, terminalShellIntegrationTimeout, terminalShellIntegrationDisabled, @@ -2133,7 +2128,6 @@ export class ClineProvider experiments, maxOpenTabsContext, maxWorkspaceFiles, - browserToolEnabled, disabledTools, telemetrySetting, showRooIgnoredFiles, @@ -2168,7 +2162,6 @@ export class ClineProvider openRouterImageApiKey, openRouterImageGenerationSelectedModel, featureRoomoteControlEnabled, - isBrowserSessionActive, lockApiConfigAcrossModes, } = await this.getState() @@ -2210,11 +2203,9 @@ export class ClineProvider alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? false, alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? false, alwaysAllowExecute: alwaysAllowExecute ?? false, - alwaysAllowBrowser: alwaysAllowBrowser ?? false, alwaysAllowMcp: alwaysAllowMcp ?? false, alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false, alwaysAllowSubtasks: alwaysAllowSubtasks ?? false, - isBrowserSessionActive, allowedMaxRequests, allowedMaxCost, autoCondenseContext: autoCondenseContext ?? true, @@ -2239,11 +2230,7 @@ export class ClineProvider allowedCommands: mergedAllowedCommands, deniedCommands: mergedDeniedCommands, soundVolume: soundVolume ?? 0.5, - browserViewportSize: browserViewportSize ?? "900x600", screenshotQuality: screenshotQuality ?? 75, - remoteBrowserHost, - remoteBrowserEnabled: remoteBrowserEnabled ?? false, - cachedChromeHostUrl: cachedChromeHostUrl, writeDelayMs: writeDelayMs ?? DEFAULT_WRITE_DELAY_MS, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? Terminal.defaultShellIntegrationTimeout, terminalShellIntegrationDisabled: terminalShellIntegrationDisabled ?? true, @@ -2268,7 +2255,6 @@ export class ClineProvider maxOpenTabsContext: maxOpenTabsContext ?? 20, maxWorkspaceFiles: maxWorkspaceFiles ?? 200, cwd, - browserToolEnabled: browserToolEnabled ?? true, disabledTools, telemetrySetting, telemetryKey, @@ -2442,9 +2428,6 @@ export class ClineProvider ) } - // Get actual browser session state - const isBrowserSessionActive = this.getCurrentTask()?.browserSession?.isSessionActive() ?? false - // Return the same structure as before. return { apiConfiguration: providerSettings, @@ -2457,12 +2440,10 @@ export class ClineProvider alwaysAllowWriteOutsideWorkspace: stateValues.alwaysAllowWriteOutsideWorkspace ?? false, alwaysAllowWriteProtected: stateValues.alwaysAllowWriteProtected ?? false, alwaysAllowExecute: stateValues.alwaysAllowExecute ?? false, - alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false, alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false, alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false, alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false, alwaysAllowFollowupQuestions: stateValues.alwaysAllowFollowupQuestions ?? false, - isBrowserSessionActive, followupAutoApproveTimeoutMs: stateValues.followupAutoApproveTimeoutMs ?? 60000, diagnosticsEnabled: stateValues.diagnosticsEnabled ?? true, allowedMaxRequests: stateValues.allowedMaxRequests, @@ -2478,11 +2459,7 @@ export class ClineProvider enableCheckpoints: stateValues.enableCheckpoints ?? true, checkpointTimeout: stateValues.checkpointTimeout ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, soundVolume: stateValues.soundVolume, - browserViewportSize: stateValues.browserViewportSize ?? "900x600", screenshotQuality: stateValues.screenshotQuality ?? 75, - remoteBrowserHost: stateValues.remoteBrowserHost, - remoteBrowserEnabled: stateValues.remoteBrowserEnabled ?? false, - cachedChromeHostUrl: stateValues.cachedChromeHostUrl as string | undefined, writeDelayMs: stateValues.writeDelayMs ?? DEFAULT_WRITE_DELAY_MS, terminalShellIntegrationTimeout: stateValues.terminalShellIntegrationTimeout ?? Terminal.defaultShellIntegrationTimeout, @@ -2509,7 +2486,6 @@ export class ClineProvider customModes, maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20, maxWorkspaceFiles: stateValues.maxWorkspaceFiles ?? 200, - browserToolEnabled: stateValues.browserToolEnabled ?? true, disabledTools: stateValues.disabledTools, telemetrySetting: stateValues.telemetrySetting || "unset", showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? false, diff --git a/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts b/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts index 9b5e3b16ee6..1a4993b1862 100644 --- a/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts @@ -122,7 +122,7 @@ vi.mock("../../../shared/modes", () => { slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }, { slug: "architect", @@ -171,7 +171,7 @@ vi.mock("../../../shared/modes", () => { slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }), defaultModeSlug: "code", } diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index f8667de5c97..4fa79da038f 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -78,34 +78,6 @@ vi.mock("@modelcontextprotocol/sdk/types.js", () => ({ }, })) -vi.mock("../../../services/browser/BrowserSession", () => ({ - BrowserSession: vi.fn().mockImplementation(() => ({ - testConnection: vi.fn().mockImplementation(async (url) => { - if (url === "http://localhost:9222") { - return { - success: true, - message: "Successfully connected to Chrome", - endpoint: "ws://localhost:9222/devtools/browser/123", - } - } else { - return { - success: false, - message: "Failed to connect to Chrome", - endpoint: undefined, - } - } - }), - })), -})) - -vi.mock("../../../services/browser/browserDiscovery", () => ({ - discoverChromeHostUrl: vi.fn().mockResolvedValue("http://localhost:9222"), - tryChromeHostUrl: vi.fn().mockImplementation(async (url) => { - return url === "http://localhost:9222" - }), - testBrowserConnection: vi.fn(), -})) - // Remove duplicate mock - it's already defined below. const mockAddCustomInstructions = vi.fn().mockResolvedValue("Combined instructions") @@ -248,7 +220,7 @@ vi.mock("../../../shared/modes", () => ({ slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }, { slug: "architect", @@ -267,7 +239,7 @@ vi.mock("../../../shared/modes", () => ({ slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }), getGroupName: vi.fn().mockImplementation((group: string) => { // Return appropriate group names for different tool groups @@ -276,8 +248,6 @@ vi.mock("../../../shared/modes", () => ({ return "Read Tools" case "edit": return "Edit Tools" - case "browser": - return "Browser Tools" case "mcp": return "MCP Tools" default: @@ -537,7 +507,6 @@ describe("ClineProvider", () => { const mockState: ExtensionState = { version: "1.0.0", - isBrowserSessionActive: false, clineMessages: [], taskHistory: [], shouldShowAnnouncement: false, @@ -557,21 +526,18 @@ describe("ClineProvider", () => { }, alwaysAllowWriteOutsideWorkspace: false, alwaysAllowExecute: false, - alwaysAllowBrowser: false, alwaysAllowMcp: false, uriScheme: "vscode", soundEnabled: false, ttsEnabled: false, enableCheckpoints: false, writeDelayMs: 1000, - browserViewportSize: "900x600", mcpEnabled: true, mode: defaultModeSlug, customModes: [], experiments: experimentDefault, maxOpenTabsContext: 20, maxWorkspaceFiles: 200, - browserToolEnabled: true, telemetrySetting: "unset", showRooIgnoredFiles: false, enableSubfolderRules: false, @@ -804,7 +770,6 @@ describe("ClineProvider", () => { expect(state).toHaveProperty("alwaysAllowReadOnly") expect(state).toHaveProperty("alwaysAllowWrite") expect(state).toHaveProperty("alwaysAllowExecute") - expect(state).toHaveProperty("alwaysAllowBrowser") expect(state).toHaveProperty("taskHistory") expect(state).toHaveProperty("soundEnabled") expect(state).toHaveProperty("ttsEnabled") @@ -1006,21 +971,6 @@ describe("ClineProvider", () => { expect(provider.providerSettingsManager.activateProfile).toHaveBeenCalledWith({ id: "config-id-123" }) }) - test("handles browserToolEnabled setting", async () => { - await provider.resolveWebviewView(mockWebviewView) - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - - // Test browserToolEnabled - await messageHandler({ type: "updateSettings", updatedSettings: { browserToolEnabled: true } }) - expect(mockContext.globalState.update).toHaveBeenCalledWith("browserToolEnabled", true) - expect(mockPostMessage).toHaveBeenCalled() - - // Verify state includes browserToolEnabled - const state = await provider.getState() - expect(state).toHaveProperty("browserToolEnabled") - expect(state.browserToolEnabled).toBe(true) // Default value should be true - }) - test("handles showRooIgnoredFiles setting", async () => { await provider.resolveWebviewView(mockWebviewView) const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] @@ -1205,7 +1155,7 @@ describe("ClineProvider", () => { { ts: 1000, type: "say", say: "user_feedback" }, // User message 1 { ts: 2000, type: "say", say: "tool" }, // Tool message { ts: 3000, type: "say", say: "text" }, // Message before delete - { ts: 4000, type: "say", say: "browser_action" }, // Message to delete + { ts: 4000, type: "say", say: "tool" }, // Message to delete { ts: 5000, type: "say", say: "user_feedback" }, // Next user message { ts: 6000, type: "say", say: "user_feedback" }, // Final message ] as ClineMessage[] @@ -1293,7 +1243,7 @@ describe("ClineProvider", () => { { ts: 1000, type: "say", say: "user_feedback" }, // User message 1 { ts: 2000, type: "say", say: "tool" }, // Tool message { ts: 3000, type: "say", say: "text" }, // Message before edit - { ts: 4000, type: "say", say: "browser_action" }, // Message to edit + { ts: 4000, type: "say", say: "tool" }, // Message to edit { ts: 5000, type: "say", say: "user_feedback" }, // Next user message { ts: 6000, type: "say", say: "user_feedback" }, // Final message ] as ClineMessage[] @@ -1486,7 +1436,6 @@ describe("ClineProvider", () => { }, mode: "architect", mcpEnabled: false, - browserViewportSize: "900x600", experiments: experimentDefault, } as any) @@ -1503,54 +1452,6 @@ describe("ClineProvider", () => { }), ) }) - - // Tests for browser tool support - simplified to focus on behavior - test("generates system prompt with different browser tool configurations", async () => { - await provider.resolveWebviewView(mockWebviewView) - const handler = getMessageHandler() - - // Test 1: Browser tools enabled with compatible model and mode - vi.spyOn(provider, "getState").mockResolvedValueOnce({ - apiConfiguration: { - apiProvider: "openrouter", - }, - browserToolEnabled: true, - mode: "code", // code mode includes browser tool group - experiments: experimentDefault, - } as any) - - await handler({ type: "getSystemPrompt", mode: "code" }) - - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "systemPrompt", - text: expect.any(String), - mode: "code", - }), - ) - - mockPostMessage.mockClear() - - // Test 2: Browser tools disabled - vi.spyOn(provider, "getState").mockResolvedValueOnce({ - apiConfiguration: { - apiProvider: "openrouter", - }, - browserToolEnabled: false, - mode: "code", - experiments: experimentDefault, - } as any) - - await handler({ type: "getSystemPrompt", mode: "code" }) - - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "systemPrompt", - text: expect.any(String), - mode: "code", - }), - ) - }) }) describe("handleModeSwitch", () => { @@ -1646,7 +1547,7 @@ describe("ClineProvider", () => { slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }) // Subsequent calls return default mode // Mock provider settings manager @@ -1845,7 +1746,7 @@ describe("ClineProvider", () => { slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }) // Mock provider settings manager to throw error @@ -2096,77 +1997,6 @@ describe("ClineProvider", () => { ]) }) }) - - describe("browser connection features", () => { - beforeEach(async () => { - // Reset mocks - vi.clearAllMocks() - await provider.resolveWebviewView(mockWebviewView) - }) - - // These mocks are already defined at the top of the file - - test("handles testBrowserConnection with provided URL", async () => { - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - - // Test with valid URL - await messageHandler({ - type: "testBrowserConnection", - text: "http://localhost:9222", - }) - - // Verify postMessage was called with success result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: true, - text: expect.stringContaining("Successfully connected to Chrome"), - }), - ) - - // Reset mock - mockPostMessage.mockClear() - - // Test with invalid URL - await messageHandler({ - type: "testBrowserConnection", - text: "http://inlocalhost:9222", - }) - - // Verify postMessage was called with failure result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: false, - text: expect.stringContaining("Failed to connect to Chrome"), - }), - ) - }) - - test("handles testBrowserConnection with auto-discovery", async () => { - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0] - - // Test auto-discovery (no URL provided) - await messageHandler({ - type: "testBrowserConnection", - }) - - // Verify discoverChromeHostUrl was called - const { discoverChromeHostUrl } = await import("../../../services/browser/browserDiscovery") - expect(discoverChromeHostUrl).toHaveBeenCalled() - - // Verify postMessage was called with success result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: true, - text: expect.stringContaining("Auto-discovered and tested connection to Chrome"), - }), - ) - }) - }) }) describe("Project MCP Settings", () => { diff --git a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts index af674d7a5e0..9e4f2fab3ad 100644 --- a/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.sticky-mode.spec.ts @@ -124,7 +124,7 @@ vi.mock("../../../shared/modes", () => ({ slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }, { slug: "architect", @@ -137,7 +137,7 @@ vi.mock("../../../shared/modes", () => ({ slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }), defaultModeSlug: "code", })) diff --git a/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts b/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts index ee63b45b254..2f29d79d0ef 100644 --- a/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.sticky-profile.spec.ts @@ -126,7 +126,7 @@ vi.mock("../../../shared/modes", () => ({ slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }, { slug: "architect", @@ -139,7 +139,7 @@ vi.mock("../../../shared/modes", () => ({ slug: "code", name: "Code Mode", roleDefinition: "You are a code assistant", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }), defaultModeSlug: "code", })) diff --git a/src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts b/src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts index aefed797443..72a6f839608 100644 --- a/src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.taskHistory.spec.ts @@ -67,18 +67,6 @@ vi.mock("@modelcontextprotocol/sdk/types.js", () => ({ }, })) -vi.mock("../../../services/browser/BrowserSession", () => ({ - BrowserSession: vi.fn().mockImplementation(() => ({ - testConnection: vi.fn().mockResolvedValue({ success: false }), - })), -})) - -vi.mock("../../../services/browser/browserDiscovery", () => ({ - discoverChromeHostUrl: vi.fn().mockResolvedValue("http://localhost:9222"), - tryChromeHostUrl: vi.fn().mockResolvedValue(false), - testBrowserConnection: vi.fn(), -})) - vi.mock("@modelcontextprotocol/sdk/client/index.js", () => ({ Client: vi.fn().mockImplementation(() => ({ connect: vi.fn().mockResolvedValue(undefined), diff --git a/src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts b/src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts deleted file mode 100644 index 9ad2709b613..00000000000 --- a/src/core/webview/__tests__/generateSystemPrompt.browser-capability.spec.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { describe, test, expect, vi } from "vitest" - -// Module under test -import { generateSystemPrompt } from "../generateSystemPrompt" - -// Mock SYSTEM_PROMPT to capture its third argument (browser capability flag) -vi.mock("../../prompts/system", () => ({ - SYSTEM_PROMPT: vi.fn(async (_ctx, _cwd, canUseBrowserTool: boolean) => { - // return a simple string to satisfy return type - return `SYSTEM_PROMPT:${canUseBrowserTool}` - }), -})) - -// Mock API handler so we control model.info flags -vi.mock("../../../api", () => ({ - buildApiHandler: vi.fn((_config) => ({ - getModel: () => ({ - id: "mock-model", - info: { - supportsImages: true, - contextWindow: 200_000, - maxTokens: 8192, - supportsPromptCache: false, - }, - }), - })), -})) - -// Minimal mode utilities: provide a custom mode that includes the "browser" group -const mockCustomModes = [ - { - slug: "test-mode", - name: "Test Mode", - roleDefinition: "Test role", - description: "", - groups: ["browser"], // critical: include browser group - }, -] - -// Minimal ClineProvider stub -function makeProviderStub() { - return { - cwd: "/tmp", - context: {} as any, - customModesManager: { - getCustomModes: async () => mockCustomModes, - }, - getCurrentTask: () => ({ - rooIgnoreController: { getInstructions: () => undefined }, - }), - getMcpHub: () => undefined, - getSkillsManager: () => undefined, - // State must enable browser tool and provide apiConfiguration - getState: async () => ({ - apiConfiguration: { - apiProvider: "openrouter", // not used by the test beyond handler creation - }, - customModePrompts: undefined, - customInstructions: undefined, - browserViewportSize: "900x600", - mcpEnabled: false, - experiments: {}, - browserToolEnabled: true, // critical: enabled in settings - language: "en", - }), - } as any -} - -describe("generateSystemPrompt browser capability (supportsImages=true)", () => { - test("passes canUseBrowserTool=true when mode has browser group and setting enabled", async () => { - const provider = makeProviderStub() - const message = { mode: "test-mode" } as any - - const result = await generateSystemPrompt(provider, message) - - // SYSTEM_PROMPT mock encodes the boolean into the returned string - expect(result).toBe("SYSTEM_PROMPT:true") - }) -}) diff --git a/src/core/webview/generateSystemPrompt.ts b/src/core/webview/generateSystemPrompt.ts index abfe36f7ace..f9eeabfa364 100644 --- a/src/core/webview/generateSystemPrompt.ts +++ b/src/core/webview/generateSystemPrompt.ts @@ -1,7 +1,6 @@ import * as vscode from "vscode" import { WebviewMessage } from "../../shared/WebviewMessage" -import { defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" -import { buildApiHandler } from "../../api" +import { defaultModeSlug } from "../../shared/modes" import { SYSTEM_PROMPT } from "../prompts/system" import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace" @@ -14,10 +13,8 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web apiConfiguration, customModePrompts, customInstructions, - browserViewportSize, mcpEnabled, experiments, - browserToolEnabled, language, enableSubfolderRules, } = await provider.getState() @@ -31,36 +28,12 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web const rooIgnoreInstructions = provider.getCurrentTask()?.rooIgnoreController?.getInstructions() - // Determine if browser tools can be used based on model support, mode, and user settings - let modelInfo: any = undefined - - // Create a temporary API handler to check if the model supports browser capability - // This avoids relying on an active Cline instance which might not exist during preview - try { - const tempApiHandler = buildApiHandler(apiConfiguration) - modelInfo = tempApiHandler.getModel().info - } catch (error) { - console.error("Error checking if model supports browser capability:", error) - } - - // Check if the current mode includes the browser tool group - const modeConfig = getModeBySlug(mode, customModes) - const modeSupportsBrowser = modeConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false - - // Check if model supports browser capability (images) - const modelSupportsBrowser = modelInfo && (modelInfo as any)?.supportsImages === true - - // Only enable browser tools if the model supports it, the mode includes browser tools, - // and browser tools are enabled in settings - const canUseBrowserTool = modelSupportsBrowser && modeSupportsBrowser && (browserToolEnabled ?? true) - const systemPrompt = await SYSTEM_PROMPT( provider.context, cwd, - canUseBrowserTool, + false, // supportsComputerUse — browser removed mcpEnabled ? provider.getMcpHub() : undefined, diffStrategy, - browserViewportSize ?? "900x600", mode, customModePrompts, customModes, @@ -75,7 +48,6 @@ export const generateSystemPrompt = async (provider: ClineProvider, message: Web newTaskRequireTodos: vscode.workspace .getConfiguration(Package.name) .get("newTaskRequireTodos", false), - isStealthModel: modelInfo?.isStealthModel, }, undefined, // todoList undefined, // modelId diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 7438056db55..2e8b4add12d 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -29,7 +29,6 @@ import { type ApiMessage } from "../task-persistence/apiMessages" import { saveTaskMessages } from "../task-persistence" import { ClineProvider } from "./ClineProvider" -import { BrowserSessionPanelManager } from "./BrowserSessionPanelManager" import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler" import { generateErrorDiagnostics } from "./diagnosticsHandler" import { @@ -52,7 +51,6 @@ import { openFile } from "../../integrations/misc/open-file" import { openImage, saveImage } from "../../integrations/misc/image-handler" import { selectImages } from "../../integrations/misc/process-images" import { getTheme } from "../../integrations/theme/getTheme" -import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery" import { searchWorkspaceFiles } from "../../services/search/file-search" import { fileExistsAtPath } from "../../utils/fs" import { playTts, setTtsEnabled, setTtsSpeed, stopTts } from "../../utils/tts" @@ -1185,69 +1183,6 @@ export const webviewMessageHandler = async ( // Cancel any pending auto-approval timeout for the current task provider.getCurrentTask()?.cancelAutoApprovalTimeout() break - case "killBrowserSession": - { - const task = provider.getCurrentTask() - if (task?.browserSession) { - await task.browserSession.closeBrowser() - await provider.postStateToWebview() - } - } - break - case "openBrowserSessionPanel": - { - // Toggle the Browser Session panel (open if closed, close if open) - const panelManager = BrowserSessionPanelManager.getInstance(provider) - await panelManager.toggle() - } - break - case "showBrowserSessionPanelAtStep": - { - const panelManager = BrowserSessionPanelManager.getInstance(provider) - - // If this is a launch action, reset the manual close flag - if (message.isLaunchAction) { - panelManager.resetManualCloseFlag() - } - - // Show panel if: - // 1. Manual click (forceShow) - always show - // 2. Launch action - always show and reset flag - // 3. Auto-open for non-launch action - only if user hasn't manually closed - if (message.forceShow || message.isLaunchAction || panelManager.shouldAllowAutoOpen()) { - // Ensure panel is shown and populated - await panelManager.show() - - // Navigate to a specific step if provided - // For launch actions: navigate to step 0 - // For manual clicks: navigate to the clicked step - // For auto-opens of regular actions: don't navigate, let BrowserSessionRow's - // internal auto-advance logic handle it (only advances if user is on most recent step) - if (typeof message.stepIndex === "number" && message.stepIndex >= 0) { - await panelManager.navigateToStep(message.stepIndex) - } - } - } - break - case "refreshBrowserSessionPanel": - { - // Re-send the latest browser session snapshot to the panel - const panelManager = BrowserSessionPanelManager.getInstance(provider) - const task = provider.getCurrentTask() - if (task) { - const messages = task.clineMessages || [] - const browserSessionStartIndex = messages.findIndex( - (m) => - m.ask === "browser_action_launch" || - (m.say === "browser_session_status" && m.text?.includes("opened")), - ) - const browserSessionMessages = - browserSessionStartIndex !== -1 ? messages.slice(browserSessionStartIndex) : [] - const isBrowserSessionActive = task.browserSession?.isSessionActive() ?? false - await panelManager.updateBrowserSession(browserSessionMessages, isBrowserSessionActive) - } - } - break case "allowedCommands": { // Validate and sanitize the commands array const commands = message.commands ?? [] @@ -1476,43 +1411,6 @@ export const webviewMessageHandler = async ( stopTts() break - case "testBrowserConnection": - // If no text is provided, try auto-discovery - if (!message.text) { - // Use testBrowserConnection for auto-discovery - const chromeHostUrl = await discoverChromeHostUrl() - - if (chromeHostUrl) { - // Send the result back to the webview - await provider.postMessageToWebview({ - type: "browserConnectionResult", - success: !!chromeHostUrl, - text: `Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`, - values: { endpoint: chromeHostUrl }, - }) - } else { - await provider.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).", - }) - } - } else { - // Test the provided URL - const customHostUrl = message.text - const hostIsValid = await tryChromeHostUrl(message.text) - - // Send the result back to the webview - await provider.postMessageToWebview({ - type: "browserConnectionResult", - success: hostIsValid, - text: hostIsValid - ? `Successfully connected to Chrome: ${customHostUrl}` - : "Failed to connect to Chrome", - }) - } - break - case "updateVSCodeSetting": { const { setting, value } = message diff --git a/src/package.json b/src/package.json index c16e36ae0c0..9b37e2c6401 100644 --- a/src/package.json +++ b/src/package.json @@ -512,8 +512,6 @@ "pretty-bytes": "^7.0.0", "proper-lockfile": "^4.1.2", "ps-tree": "^1.2.0", - "puppeteer-chromium-resolver": "^24.0.0", - "puppeteer-core": "^23.4.0", "reconnecting-eventsource": "^1.6.4", "safe-stable-stringify": "^2.5.0", "sambanova-ai-provider": "^1.2.2", diff --git a/src/services/browser/BrowserSession.ts b/src/services/browser/BrowserSession.ts deleted file mode 100644 index 7ab7e88cad5..00000000000 --- a/src/services/browser/BrowserSession.ts +++ /dev/null @@ -1,913 +0,0 @@ -import * as vscode from "vscode" -import * as fs from "fs/promises" -import * as path from "path" -import { Browser, Page, ScreenshotOptions, TimeoutError, launch, connect, KeyInput } from "puppeteer-core" -// @ts-ignore -import PCR from "puppeteer-chromium-resolver" -import pWaitFor from "p-wait-for" -import delay from "delay" - -import { type BrowserActionResult } from "@roo-code/types" - -import { fileExistsAtPath } from "../../utils/fs" - -import { discoverChromeHostUrl, tryChromeHostUrl } from "./browserDiscovery" - -// Timeout constants -const BROWSER_NAVIGATION_TIMEOUT = 15_000 // 15 seconds - -interface PCRStats { - puppeteer: { launch: typeof launch } - executablePath: string -} - -export class BrowserSession { - private context: vscode.ExtensionContext - private browser?: Browser - private page?: Page - private currentMousePosition?: string - private lastConnectionAttempt?: number - private isUsingRemoteBrowser: boolean = false - private onStateChange?: (isActive: boolean) => void - - // Track last known viewport to surface in environment details - private lastViewportWidth?: number - private lastViewportHeight?: number - - constructor(context: vscode.ExtensionContext, onStateChange?: (isActive: boolean) => void) { - this.context = context - this.onStateChange = onStateChange - } - - private async ensureChromiumExists(): Promise { - const globalStoragePath = this.context?.globalStorageUri?.fsPath - if (!globalStoragePath) { - throw new Error("Global storage uri is invalid") - } - - const puppeteerDir = path.join(globalStoragePath, "puppeteer") - const dirExists = await fileExistsAtPath(puppeteerDir) - if (!dirExists) { - await fs.mkdir(puppeteerDir, { recursive: true }) - } - - // if chromium doesn't exist, this will download it to path.join(puppeteerDir, ".chromium-browser-snapshots") - // if it does exist it will return the path to existing chromium - const stats: PCRStats = await PCR({ - downloadPath: puppeteerDir, - }) - - return stats - } - - /** - * Gets the viewport size from global state or returns default - */ - private getViewport() { - const size = (this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600" - const [width, height] = size.split("x").map(Number) - return { width, height } - } - - /** - * Launches a local browser instance - */ - private async launchLocalBrowser(): Promise { - console.log("Launching local browser") - const stats = await this.ensureChromiumExists() - this.browser = await stats.puppeteer.launch({ - args: [ - "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - ], - executablePath: stats.executablePath, - defaultViewport: this.getViewport(), - // headless: false, - }) - this.isUsingRemoteBrowser = false - } - - /** - * Connects to a browser using a WebSocket URL - */ - private async connectWithChromeHostUrl(chromeHostUrl: string): Promise { - try { - this.browser = await connect({ - browserURL: chromeHostUrl, - defaultViewport: this.getViewport(), - }) - - // Cache the successful endpoint - console.log(`Connected to remote browser at ${chromeHostUrl}`) - this.context.globalState.update("cachedChromeHostUrl", chromeHostUrl) - this.lastConnectionAttempt = Date.now() - this.isUsingRemoteBrowser = true - - return true - } catch (error) { - console.log(`Failed to connect using WebSocket endpoint: ${error}`) - return false - } - } - - /** - * Attempts to connect to a remote browser using various methods - * Returns true if connection was successful, false otherwise - */ - private async connectToRemoteBrowser(): Promise { - let remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined - let reconnectionAttempted = false - - // Try to connect with cached endpoint first if it exists and is recent (less than 1 hour old) - const cachedChromeHostUrl = this.context.globalState.get("cachedChromeHostUrl") as string | undefined - if (cachedChromeHostUrl && this.lastConnectionAttempt && Date.now() - this.lastConnectionAttempt < 3_600_000) { - console.log(`Attempting to connect using cached Chrome Host Url: ${cachedChromeHostUrl}`) - if (await this.connectWithChromeHostUrl(cachedChromeHostUrl)) { - return true - } - - console.log(`Failed to connect using cached Chrome Host Url: ${cachedChromeHostUrl}`) - // Clear the cached endpoint since it's no longer valid - this.context.globalState.update("cachedChromeHostUrl", undefined) - - // User wants to give up after one reconnection attempt - if (remoteBrowserHost) { - reconnectionAttempted = true - } - } - - // If user provided a remote browser host, try to connect to it - else if (remoteBrowserHost && !reconnectionAttempted) { - console.log(`Attempting to connect to remote browser at ${remoteBrowserHost}`) - try { - const hostIsValid = await tryChromeHostUrl(remoteBrowserHost) - - if (!hostIsValid) { - throw new Error("Could not find chromeHostUrl in the response") - } - - console.log(`Found WebSocket endpoint: ${remoteBrowserHost}`) - - if (await this.connectWithChromeHostUrl(remoteBrowserHost)) { - return true - } - } catch (error) { - console.error(`Failed to connect to remote browser: ${error}`) - // Fall back to auto-discovery if remote connection fails - } - } - - try { - console.log("Attempting browser auto-discovery...") - const chromeHostUrl = await discoverChromeHostUrl() - - if (chromeHostUrl && (await this.connectWithChromeHostUrl(chromeHostUrl))) { - return true - } - } catch (error) { - console.error(`Auto-discovery failed: ${error}`) - // Fall back to local browser if auto-discovery fails - } - - return false - } - - async launchBrowser(): Promise { - console.log("launch browser called") - - // Check if remote browser connection is enabled - const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined - - if (!remoteBrowserEnabled) { - console.log("Launching local browser") - if (this.browser) { - // throw new Error("Browser already launched") - await this.closeBrowser() // this may happen when the model launches a browser again after having used it already before - } else { - // If browser wasn't open, just reset the state - this.resetBrowserState() - } - await this.launchLocalBrowser() - } else { - console.log("Connecting to remote browser") - // Remote browser connection is enabled - const remoteConnected = await this.connectToRemoteBrowser() - - // If all remote connection attempts fail, fall back to local browser - if (!remoteConnected) { - console.log("Falling back to local browser") - await this.launchLocalBrowser() - } - } - - // Notify that browser session is now active - if (this.browser && this.onStateChange) { - this.onStateChange(true) - } - } - - /** - * Closes the browser and resets browser state - */ - async closeBrowser(): Promise { - const wasActive = !!(this.browser || this.page) - - if (wasActive) { - if (this.isUsingRemoteBrowser && this.browser) { - await this.browser.disconnect().catch(() => {}) - } else { - await this.browser?.close().catch(() => {}) - } - this.resetBrowserState() - - // Notify that browser session is now inactive - if (this.onStateChange) { - this.onStateChange(false) - } - } - return {} - } - - /** - * Resets all browser state variables - */ - private resetBrowserState(): void { - this.browser = undefined - this.page = undefined - this.currentMousePosition = undefined - this.isUsingRemoteBrowser = false - this.lastViewportWidth = undefined - this.lastViewportHeight = undefined - } - - async doAction(action: (page: Page) => Promise): Promise { - if (!this.page) { - throw new Error( - "Cannot perform browser action: no active browser session. The browser must be launched first using the 'launch' action before other browser actions can be performed.", - ) - } - - const logs: string[] = [] - let lastLogTs = Date.now() - - const consoleListener = (msg: any) => { - if (msg.type() === "log") { - logs.push(msg.text()) - } else { - logs.push(`[${msg.type()}] ${msg.text()}`) - } - lastLogTs = Date.now() - } - - const errorListener = (err: Error) => { - logs.push(`[Page Error] ${err.toString()}`) - lastLogTs = Date.now() - } - - // Add the listeners - this.page.on("console", consoleListener) - this.page.on("pageerror", errorListener) - - try { - await action(this.page) - } catch (err) { - if (!(err instanceof TimeoutError)) { - logs.push(`[Error] ${err.toString()}`) - } - } - - // Wait for console inactivity, with a timeout - await pWaitFor(() => Date.now() - lastLogTs >= 500, { - timeout: 3_000, - interval: 100, - }).catch(() => {}) - - // Draw cursor indicator if we have a cursor position - if (this.currentMousePosition) { - await this.drawCursorIndicator(this.page, this.currentMousePosition) - } - - let options: ScreenshotOptions = { - encoding: "base64", - - // clip: { - // x: 0, - // y: 0, - // width: 900, - // height: 600, - // }, - } - - let screenshotBase64 = await this.page.screenshot({ - ...options, - type: "webp", - quality: ((await this.context.globalState.get("screenshotQuality")) as number | undefined) ?? 75, - }) - let screenshot = `data:image/webp;base64,${screenshotBase64}` - - if (!screenshotBase64) { - console.log("webp screenshot failed, trying png") - screenshotBase64 = await this.page.screenshot({ - ...options, - type: "png", - }) - screenshot = `data:image/png;base64,${screenshotBase64}` - } - - if (!screenshotBase64) { - throw new Error("Failed to take screenshot.") - } - - // Remove cursor indicator after taking screenshot - if (this.currentMousePosition) { - await this.removeCursorIndicator(this.page) - } - - // this.page.removeAllListeners() <- causes the page to crash! - this.page.off("console", consoleListener) - this.page.off("pageerror", errorListener) - - // Get actual viewport dimensions - const viewport = this.page.viewport() - - // Persist last known viewport dimensions - this.lastViewportWidth = viewport?.width - this.lastViewportHeight = viewport?.height - - return { - screenshot, - logs: logs.join("\n"), - currentUrl: this.page.url(), - currentMousePosition: this.currentMousePosition, - viewportWidth: viewport?.width, - viewportHeight: viewport?.height, - } - } - - /** - * Extract the root domain from a URL - * e.g., http://localhost:3000/path -> localhost:3000 - * e.g., https://example.com/path -> example.com - */ - private getRootDomain(url: string): string { - try { - const urlObj = new URL(url) - // Remove www. prefix if present - return urlObj.host.replace(/^www\./, "") - } catch (error) { - // If URL parsing fails, return the original URL - return url - } - } - - /** - * Navigate to a URL with standard loading options - */ - private async navigatePageToUrl(page: Page, url: string): Promise { - await page.goto(url, { timeout: BROWSER_NAVIGATION_TIMEOUT, waitUntil: ["domcontentloaded", "networkidle2"] }) - await this.waitTillHTMLStable(page) - } - - /** - * Creates a new tab and navigates to the specified URL - */ - private async createNewTab(url: string): Promise { - if (!this.browser) { - throw new Error("Browser is not launched") - } - - // Create a new page - const newPage = await this.browser.newPage() - - // Set the new page as the active page - this.page = newPage - - // Navigate to the URL - const result = await this.doAction(async (page) => { - await this.navigatePageToUrl(page, url) - }) - - return result - } - - async navigateToUrl(url: string): Promise { - if (!this.browser) { - throw new Error("Browser is not launched") - } - // Remove trailing slash for comparison - const normalizedNewUrl = url.replace(/\/$/, "") - - // Extract the root domain from the URL - const rootDomain = this.getRootDomain(normalizedNewUrl) - - // Get all current pages - const pages = await this.browser.pages() - - // Try to find a page with the same root domain - let existingPage: Page | undefined - - for (const page of pages) { - try { - const pageUrl = page.url() - if (pageUrl && this.getRootDomain(pageUrl) === rootDomain) { - existingPage = page - break - } - } catch (error) { - // Skip pages that might have been closed or have errors - console.log(`Error checking page URL: ${error}`) - continue - } - } - - if (existingPage) { - // Tab with the same root domain exists, switch to it - console.log(`Tab with domain ${rootDomain} already exists, switching to it`) - - // Update the active page - this.page = existingPage - existingPage.bringToFront() - - // Navigate to the new URL if it's different] - const currentUrl = existingPage.url().replace(/\/$/, "") // Remove trailing / if present - if (this.getRootDomain(currentUrl) === rootDomain && currentUrl !== normalizedNewUrl) { - console.log(`Navigating to new URL: ${normalizedNewUrl}`) - console.log(`Current URL: ${currentUrl}`) - console.log(`Root domain: ${this.getRootDomain(currentUrl)}`) - console.log(`New URL: ${normalizedNewUrl}`) - // Navigate to the new URL - return this.doAction(async (page) => { - await this.navigatePageToUrl(page, normalizedNewUrl) - }) - } else { - console.log(`Tab with domain ${rootDomain} already exists, and URL is the same: ${normalizedNewUrl}`) - // URL is the same, just reload the page to ensure it's up to date - console.log(`Reloading page: ${normalizedNewUrl}`) - console.log(`Current URL: ${currentUrl}`) - console.log(`Root domain: ${this.getRootDomain(currentUrl)}`) - console.log(`New URL: ${normalizedNewUrl}`) - return this.doAction(async (page) => { - await page.reload({ - timeout: BROWSER_NAVIGATION_TIMEOUT, - waitUntil: ["domcontentloaded", "networkidle2"], - }) - await this.waitTillHTMLStable(page) - }) - } - } else { - // No tab with this root domain exists, create a new one - console.log(`No tab with domain ${rootDomain} exists, creating a new one`) - return this.createNewTab(normalizedNewUrl) - } - } - - // page.goto { waitUntil: "networkidle0" } may not ever resolve, and not waiting could return page content too early before js has loaded - // https://stackoverflow.com/questions/52497252/puppeteer-wait-until-page-is-completely-loaded/61304202#61304202 - private async waitTillHTMLStable(page: Page, timeout = 5_000) { - const checkDurationMsecs = 500 // 1000 - const maxChecks = timeout / checkDurationMsecs - let lastHTMLSize = 0 - let checkCounts = 1 - let countStableSizeIterations = 0 - const minStableSizeIterations = 3 - - while (checkCounts++ <= maxChecks) { - let html = await page.content() - let currentHTMLSize = html.length - - // let bodyHTMLSize = await page.evaluate(() => document.body.innerHTML.length) - console.log("last: ", lastHTMLSize, " <> curr: ", currentHTMLSize) - - if (lastHTMLSize !== 0 && currentHTMLSize === lastHTMLSize) { - countStableSizeIterations++ - } else { - countStableSizeIterations = 0 //reset the counter - } - - if (countStableSizeIterations >= minStableSizeIterations) { - console.log("Page rendered fully...") - break - } - - lastHTMLSize = currentHTMLSize - await delay(checkDurationMsecs) - } - } - - /** - * Force links and window.open to navigate in the same tab. - * This makes clicks on anchors with target="_blank" stay in the current page - * and also intercepts window.open so SPA/open-in-new-tab patterns don't spawn popups. - */ - private async forceLinksToSameTab(page: Page): Promise { - try { - await page.evaluate(() => { - try { - // Ensure we only install once per document - if ((window as any).__ROO_FORCE_SAME_TAB__) return - ;(window as any).__ROO_FORCE_SAME_TAB__ = true - - // Override window.open to navigate current tab instead of creating a new one - const originalOpen = window.open - window.open = function (url: string | URL, target?: string, features?: string) { - try { - const href = typeof url === "string" ? url : String(url) - location.href = href - } catch { - // fall back to original if something unexpected occurs - try { - return originalOpen.apply(window, [url as any, "_self", features]) as any - } catch {} - } - return null as any - } as any - - // Rewrite anchors that explicitly open new tabs - document.querySelectorAll('a[target="_blank"]').forEach((a) => { - a.setAttribute("target", "_self") - }) - - // Defensive capture: if an element still tries to open in a new tab, force same-tab - document.addEventListener( - "click", - (ev) => { - const el = (ev.target as HTMLElement | null)?.closest?.( - 'a[target="_blank"]', - ) as HTMLAnchorElement | null - if (el && el.href) { - ev.preventDefault() - try { - location.href = el.href - } catch {} - } - }, - { capture: true, passive: false }, - ) - } catch { - // no-op; forcing same-tab is best-effort - } - }) - } catch { - // If evaluate fails (e.g., cross-origin/state), continue without breaking the action - } - } - - /** - * Handles mouse interaction with network activity monitoring - */ - private async handleMouseInteraction( - page: Page, - coordinate: string, - action: (x: number, y: number) => Promise, - ): Promise { - const [x, y] = coordinate.split(",").map(Number) - - // Force any new-tab behavior (target="_blank", window.open) to stay in the same tab - await this.forceLinksToSameTab(page) - - // Set up network request monitoring - let hasNetworkActivity = false - const requestListener = () => { - hasNetworkActivity = true - } - page.on("request", requestListener) - - // Perform the mouse action - await action(x, y) - this.currentMousePosition = coordinate - - // Small delay to check if action triggered any network activity - await delay(100) - - if (hasNetworkActivity) { - // If we detected network activity, wait for navigation/loading - await page - .waitForNavigation({ - waitUntil: ["domcontentloaded", "networkidle2"], - timeout: BROWSER_NAVIGATION_TIMEOUT, - }) - .catch(() => {}) - await this.waitTillHTMLStable(page) - } - - // Clean up listener - page.off("request", requestListener) - } - - async click(coordinate: string): Promise { - return this.doAction(async (page) => { - await this.handleMouseInteraction(page, coordinate, async (x, y) => { - await page.mouse.click(x, y) - }) - }) - } - - async type(text: string): Promise { - return this.doAction(async (page) => { - await page.keyboard.type(text) - }) - } - - async press(key: string): Promise { - return this.doAction(async (page) => { - // Parse key combinations (e.g., "Cmd+K", "Shift+Enter") - const parts = key.split("+").map((k) => k.trim()) - const modifiers: string[] = [] - let mainKey = parts[parts.length - 1] - - // Identify modifiers - for (let i = 0; i < parts.length - 1; i++) { - const part = parts[i].toLowerCase() - if (part === "cmd" || part === "command" || part === "meta") { - modifiers.push("Meta") - } else if (part === "ctrl" || part === "control") { - modifiers.push("Control") - } else if (part === "shift") { - modifiers.push("Shift") - } else if (part === "alt" || part === "option") { - modifiers.push("Alt") - } - } - - // Map common key aliases to Puppeteer KeyInput values - const mapping: Record = { - esc: "Escape", - return: "Enter", - escape: "Escape", - enter: "Enter", - tab: "Tab", - space: "Space", - arrowup: "ArrowUp", - arrowdown: "ArrowDown", - arrowleft: "ArrowLeft", - arrowright: "ArrowRight", - } - mainKey = (mapping[mainKey.toLowerCase()] ?? mainKey) as string - - // Avoid new-tab behavior from Enter on links/buttons - await this.forceLinksToSameTab(page) - - // Track inflight requests so we can detect brief network bursts - let inflight = 0 - const onRequest = () => { - inflight++ - } - const onRequestDone = () => { - inflight = Math.max(0, inflight - 1) - } - page.on("request", onRequest) - page.on("requestfinished", onRequestDone) - page.on("requestfailed", onRequestDone) - - // Start a short navigation wait in parallel; if no nav, it times out harmlessly - const HARD_CAP_MS = 3000 - const navPromise = page - .waitForNavigation({ - // domcontentloaded is enough to confirm a submit navigated - waitUntil: ["domcontentloaded"], - timeout: HARD_CAP_MS, - }) - .catch(() => undefined) - - // Press key combination - if (modifiers.length > 0) { - // Hold down modifiers - for (const modifier of modifiers) { - await page.keyboard.down(modifier as KeyInput) - } - - // Press main key - await page.keyboard.press(mainKey as KeyInput) - - // Release modifiers - for (const modifier of modifiers) { - await page.keyboard.up(modifier as KeyInput) - } - } else { - // Single key press - await page.keyboard.press(mainKey as KeyInput) - } - - // Give time for any requests to kick off - await delay(120) - - // Hard-cap the wait to avoid UI hangs - await Promise.race([ - navPromise, - pWaitFor(() => inflight === 0, { timeout: HARD_CAP_MS, interval: 100 }).catch(() => {}), - delay(HARD_CAP_MS), - ]) - - // Stabilize DOM briefly before capturing screenshot (shorter cap) - await this.waitTillHTMLStable(page, 2_000) - - // Cleanup - page.off("request", onRequest) - page.off("requestfinished", onRequestDone) - page.off("requestfailed", onRequestDone) - }) - } - - /** - * Scrolls the page by the specified amount - */ - private async scrollPage(page: Page, direction: "up" | "down"): Promise { - const { height } = this.getViewport() - const scrollAmount = direction === "down" ? height : -height - - await page.evaluate((scrollHeight) => { - window.scrollBy({ - top: scrollHeight, - behavior: "auto", - }) - }, scrollAmount) - - await delay(300) - } - - async scrollDown(): Promise { - return this.doAction(async (page) => { - await this.scrollPage(page, "down") - }) - } - - async scrollUp(): Promise { - return this.doAction(async (page) => { - await this.scrollPage(page, "up") - }) - } - - async hover(coordinate: string): Promise { - return this.doAction(async (page) => { - await this.handleMouseInteraction(page, coordinate, async (x, y) => { - await page.mouse.move(x, y) - // Small delay to allow any hover effects to appear - await delay(300) - }) - }) - } - - async resize(size: string): Promise { - return this.doAction(async (page) => { - const [width, height] = size.split(",").map(Number) - const session = await page.createCDPSession() - await page.setViewport({ width, height }) - const { windowId } = await session.send("Browser.getWindowForTarget") - await session.send("Browser.setWindowBounds", { - bounds: { width, height }, - windowId, - }) - }) - } - - /** - * Determines image type from file extension - */ - private getImageTypeFromPath(filePath: string): "png" | "jpeg" | "webp" { - const ext = path.extname(filePath).toLowerCase() - if (ext === ".jpg" || ext === ".jpeg") return "jpeg" - if (ext === ".webp") return "webp" - return "png" - } - - /** - * Takes a screenshot and saves it to the specified file path. - * @param filePath - The destination file path (relative to workspace) - * @param cwd - Current working directory for resolving relative paths - * @returns BrowserActionResult with screenshot data and saved file path - * @throws Error if the resolved path escapes the workspace directory - */ - async saveScreenshot(filePath: string, cwd: string): Promise { - // Always resolve the path against the workspace root - const normalizedCwd = path.resolve(cwd) - const fullPath = path.resolve(cwd, filePath) - - // Validate that the resolved path stays within the workspace (before calling doAction) - if (!fullPath.startsWith(normalizedCwd + path.sep) && fullPath !== normalizedCwd) { - throw new Error( - `Screenshot path "${filePath}" resolves to "${fullPath}" which is outside the workspace "${normalizedCwd}". ` + - `Paths must be relative to the workspace and cannot escape it.`, - ) - } - - return this.doAction(async (page) => { - // Ensure directory exists - await fs.mkdir(path.dirname(fullPath), { recursive: true }) - - // Determine image type from extension - const imageType = this.getImageTypeFromPath(filePath) - - // Take screenshot directly to file (more efficient than base64 for file saving) - await page.screenshot({ - path: fullPath, - type: imageType, - quality: - imageType === "png" - ? undefined - : ((this.context.globalState.get("screenshotQuality") as number | undefined) ?? 75), - }) - }) - } - - /** - * Draws a cursor indicator on the page at the specified position - */ - private async drawCursorIndicator(page: Page, coordinate: string): Promise { - const [x, y] = coordinate.split(",").map(Number) - - try { - await page.evaluate( - (cursorX: number, cursorY: number) => { - // Create a cursor indicator element - const cursor = document.createElement("div") - cursor.id = "__roo_cursor_indicator__" - cursor.style.cssText = ` - position: fixed; - left: ${cursorX}px; - top: ${cursorY}px; - width: 35px; - height: 35px; - pointer-events: none; - z-index: 2147483647; - ` - - // Create SVG cursor pointer - const svg = ` - - - - - ` - cursor.innerHTML = svg - - document.body.appendChild(cursor) - }, - x, - y, - ) - } catch (error) { - console.error("Failed to draw cursor indicator:", error) - } - } - - /** - * Removes the cursor indicator from the page - */ - private async removeCursorIndicator(page: Page): Promise { - try { - await page.evaluate(() => { - const cursor = document.getElementById("__roo_cursor_indicator__") - if (cursor) { - cursor.remove() - } - }) - } catch (error) { - console.error("Failed to remove cursor indicator:", error) - } - } - - /** - * Returns whether a browser session is currently active - */ - isSessionActive(): boolean { - return !!(this.browser && this.page) - } - - /** - * Returns the last known viewport size (if any) - * - * Prefer the live page viewport when available so we stay accurate after: - * - browser_action resize - * - manual window resizes (especially with remote browsers) - * - * Falls back to the configured default viewport when no prior information exists. - */ - getViewportSize(): { width?: number; height?: number } { - // If we have an active page, ask Puppeteer for the current viewport. - // This keeps us in sync with any resizes that happen outside of our own - // browser_action lifecycle (e.g. user dragging the window). - if (this.page) { - const vp = this.page.viewport() - if (vp?.width) this.lastViewportWidth = vp.width - if (vp?.height) this.lastViewportHeight = vp.height - } - - // If we've ever observed a viewport, use that. - if (this.lastViewportWidth && this.lastViewportHeight) { - return { - width: this.lastViewportWidth, - height: this.lastViewportHeight, - } - } - - // Otherwise fall back to the configured default so the tool can still - // operate before the first screenshot-based action has run. - const { width, height } = this.getViewport() - return { width, height } - } -} diff --git a/src/services/browser/UrlContentFetcher.ts b/src/services/browser/UrlContentFetcher.ts deleted file mode 100644 index 2d8e4a3de84..00000000000 --- a/src/services/browser/UrlContentFetcher.ts +++ /dev/null @@ -1,143 +0,0 @@ -import * as vscode from "vscode" -import * as fs from "fs/promises" -import * as path from "path" -import { Browser, Page, launch } from "puppeteer-core" -import * as cheerio from "cheerio" -import TurndownService from "turndown" -// @ts-ignore -import PCR from "puppeteer-chromium-resolver" -import { fileExistsAtPath } from "../../utils/fs" -import { serializeError } from "serialize-error" - -// Timeout constants -const URL_FETCH_TIMEOUT = 30_000 // 30 seconds -const URL_FETCH_FALLBACK_TIMEOUT = 20_000 // 20 seconds for fallback - -interface PCRStats { - puppeteer: { launch: typeof launch } - executablePath: string -} - -export class UrlContentFetcher { - private context: vscode.ExtensionContext - private browser?: Browser - private page?: Page - - constructor(context: vscode.ExtensionContext) { - this.context = context - } - - private async ensureChromiumExists(): Promise { - const globalStoragePath = this.context?.globalStorageUri?.fsPath - if (!globalStoragePath) { - throw new Error("Global storage uri is invalid") - } - const puppeteerDir = path.join(globalStoragePath, "puppeteer") - const dirExists = await fileExistsAtPath(puppeteerDir) - if (!dirExists) { - await fs.mkdir(puppeteerDir, { recursive: true }) - } - // if chromium doesn't exist, this will download it to path.join(puppeteerDir, ".chromium-browser-snapshots") - // if it does exist it will return the path to existing chromium - const stats: PCRStats = await PCR({ - downloadPath: puppeteerDir, - }) - return stats - } - - async launchBrowser(): Promise { - if (this.browser) { - return - } - const stats = await this.ensureChromiumExists() - const args = [ - "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - "--disable-dev-shm-usage", - "--disable-accelerated-2d-canvas", - "--no-first-run", - "--disable-gpu", - "--disable-features=VizDisplayCompositor", - ] - if (process.platform === "linux") { - // Fixes network errors on Linux hosts (see https://github.com/puppeteer/puppeteer/issues/8246) - args.push("--no-sandbox") - } - this.browser = await stats.puppeteer.launch({ - args, - executablePath: stats.executablePath, - }) - // (latest version of puppeteer does not add headless to user agent) - this.page = await this.browser?.newPage() - - // Set additional page configurations to improve loading success - if (this.page) { - await this.page.setViewport({ width: 1280, height: 720 }) - await this.page.setExtraHTTPHeaders({ - "Accept-Language": "en-US,en;q=0.9", - }) - } - } - - async closeBrowser(): Promise { - await this.browser?.close() - this.browser = undefined - this.page = undefined - } - - // must make sure to call launchBrowser before and closeBrowser after using this - async urlToMarkdown(url: string): Promise { - if (!this.browser || !this.page) { - throw new Error("Browser not initialized") - } - /* - - In Puppeteer, "networkidle2" waits until there are no more than 2 network connections for at least 500 ms (roughly equivalent to Playwright's "networkidle"). - - "domcontentloaded" is when the basic DOM is loaded. - This should be sufficient for most doc sites. - */ - try { - await this.page.goto(url, { - timeout: URL_FETCH_TIMEOUT, - waitUntil: ["domcontentloaded", "networkidle2"], - }) - } catch (error) { - // Use serialize-error to safely extract error information - const serializedError = serializeError(error) - const errorMessage = serializedError.message || String(error) - const errorName = serializedError.name - - // Only retry for timeout or network-related errors - const shouldRetry = - errorMessage.includes("timeout") || - errorMessage.includes("net::") || - errorMessage.includes("NetworkError") || - errorMessage.includes("ERR_") || - errorName === "TimeoutError" - - if (shouldRetry) { - // If networkidle2 fails due to timeout/network issues, try with just domcontentloaded as fallback - console.warn( - `Failed to load ${url} with networkidle2, retrying with domcontentloaded only: ${errorMessage}`, - ) - await this.page.goto(url, { - timeout: URL_FETCH_FALLBACK_TIMEOUT, - waitUntil: ["domcontentloaded"], - }) - } else { - // For other errors, throw them as-is - throw error - } - } - - const content = await this.page.content() - - // use cheerio to parse and clean up the HTML - const $ = cheerio.load(content) - $("script, style, nav, footer, header").remove() - - // convert cleaned HTML to markdown - const turndownService = new TurndownService() - const markdown = turndownService.turndown($.html()) - - return markdown - } -} diff --git a/src/services/browser/__tests__/BrowserSession.spec.ts b/src/services/browser/__tests__/BrowserSession.spec.ts deleted file mode 100644 index 2291fade428..00000000000 --- a/src/services/browser/__tests__/BrowserSession.spec.ts +++ /dev/null @@ -1,628 +0,0 @@ -// npx vitest services/browser/__tests__/BrowserSession.spec.ts - -import * as path from "path" -import { BrowserSession } from "../BrowserSession" -import { discoverChromeHostUrl, tryChromeHostUrl } from "../browserDiscovery" - -// Mock dependencies -vi.mock("vscode", () => ({ - ExtensionContext: vi.fn(), - Uri: { - file: vi.fn((path) => ({ fsPath: path })), - }, -})) - -// Mock puppeteer-core -vi.mock("puppeteer-core", () => { - const mockBrowser = { - newPage: vi.fn().mockResolvedValue({ - goto: vi.fn().mockResolvedValue(undefined), - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - }), - pages: vi.fn().mockResolvedValue([]), - close: vi.fn().mockResolvedValue(undefined), - disconnect: vi.fn().mockResolvedValue(undefined), - } - - return { - Browser: vi.fn(), - Page: vi.fn(), - TimeoutError: class TimeoutError extends Error {}, - launch: vi.fn().mockResolvedValue(mockBrowser), - connect: vi.fn().mockResolvedValue(mockBrowser), - } -}) - -// Mock PCR -vi.mock("puppeteer-chromium-resolver", () => { - return { - default: vi.fn().mockResolvedValue({ - puppeteer: { - launch: vi.fn().mockImplementation(async () => { - const { launch } = await import("puppeteer-core") - return launch() - }), - }, - executablePath: "/mock/path/to/chromium", - }), - } -}) - -// Mock fs -vi.mock("fs/promises", () => ({ - mkdir: vi.fn().mockResolvedValue(undefined), - readFile: vi.fn(), - writeFile: vi.fn(), - access: vi.fn(), -})) - -// Mock fileExistsAtPath -vi.mock("../../../utils/fs", () => ({ - fileExistsAtPath: vi.fn().mockResolvedValue(false), -})) - -// Mock browser discovery functions -vi.mock("../browserDiscovery", () => ({ - discoverChromeHostUrl: vi.fn().mockResolvedValue(null), - tryChromeHostUrl: vi.fn().mockResolvedValue(false), -})) - -// Mock delay -vi.mock("delay", () => ({ - default: vi.fn().mockResolvedValue(undefined), -})) - -// Mock p-wait-for -vi.mock("p-wait-for", () => ({ - default: vi.fn().mockResolvedValue(undefined), -})) - -describe("BrowserSession", () => { - let browserSession: BrowserSession - let mockContext: any - - beforeEach(() => { - vi.clearAllMocks() - - // Set up mock context - mockContext = { - globalState: { - get: vi.fn(), - update: vi.fn(), - }, - globalStorageUri: { - fsPath: "/mock/global/storage/path", - }, - extensionUri: { - fsPath: "/mock/extension/path", - }, - } - - // Create browser session - browserSession = new BrowserSession(mockContext) - }) - - describe("Remote browser disabled", () => { - it("should launch a local browser when remote browser is disabled", async () => { - // Mock context to indicate remote browser is disabled - mockContext.globalState.get.mockImplementation((key: string) => { - if (key === "remoteBrowserEnabled") return false - return undefined - }) - - await browserSession.launchBrowser() - - const puppeteerCore = await import("puppeteer-core") - - // Verify that a local browser was launched - expect(puppeteerCore.launch).toHaveBeenCalled() - - // Verify that remote browser connection was not attempted - expect(discoverChromeHostUrl).not.toHaveBeenCalled() - expect(tryChromeHostUrl).not.toHaveBeenCalled() - - expect((browserSession as any).isUsingRemoteBrowser).toBe(false) - }) - }) - - describe("Remote browser successfully connects", () => { - it("should connect to a remote browser when enabled and connection succeeds", async () => { - // Mock context to indicate remote browser is enabled - mockContext.globalState.get.mockImplementation((key: string) => { - if (key === "remoteBrowserEnabled") return true - if (key === "remoteBrowserHost") return "http://remote-browser:9222" - return undefined - }) - - // Mock successful remote browser connection - vi.mocked(tryChromeHostUrl).mockResolvedValue(true) - - await browserSession.launchBrowser() - - const puppeteerCore = await import("puppeteer-core") - - // Verify that connect was called - expect(puppeteerCore.connect).toHaveBeenCalled() - - // Verify that local browser was not launched - expect(puppeteerCore.launch).not.toHaveBeenCalled() - - expect((browserSession as any).isUsingRemoteBrowser).toBe(true) - }) - }) - - describe("Remote browser enabled but falls back to local", () => { - it("should fall back to local browser when remote connection fails", async () => { - // Mock context to indicate remote browser is enabled - mockContext.globalState.get.mockImplementation((key: string) => { - if (key === "remoteBrowserEnabled") return true - if (key === "remoteBrowserHost") return "http://remote-browser:9222" - return undefined - }) - - // Mock failed remote browser connection - vi.mocked(tryChromeHostUrl).mockResolvedValue(false) - vi.mocked(discoverChromeHostUrl).mockResolvedValue(null) - - await browserSession.launchBrowser() - - // Import puppeteer-core to check if launch was called - const puppeteerCore = await import("puppeteer-core") - - // Verify that local browser was launched as fallback - expect(puppeteerCore.launch).toHaveBeenCalled() - - // Verify that isUsingRemoteBrowser is false - expect((browserSession as any).isUsingRemoteBrowser).toBe(false) - }) - }) - - describe("closeBrowser", () => { - it("should close a local browser properly", async () => { - const puppeteerCore = await import("puppeteer-core") - - // Create a mock browser directly - const mockBrowser = { - newPage: vi.fn().mockResolvedValue({}), - pages: vi.fn().mockResolvedValue([]), - close: vi.fn().mockResolvedValue(undefined), - disconnect: vi.fn().mockResolvedValue(undefined), - } - - // Set browser and page on the session - ;(browserSession as any).browser = mockBrowser - ;(browserSession as any).page = {} - ;(browserSession as any).isUsingRemoteBrowser = false - - await browserSession.closeBrowser() - - // Verify that browser.close was called - expect(mockBrowser.close).toHaveBeenCalled() - expect(mockBrowser.disconnect).not.toHaveBeenCalled() - - // Verify that browser state was reset - expect((browserSession as any).browser).toBeUndefined() - expect((browserSession as any).page).toBeUndefined() - expect((browserSession as any).isUsingRemoteBrowser).toBe(false) - }) - - it("should disconnect from a remote browser properly", async () => { - // Create a mock browser directly - const mockBrowser = { - newPage: vi.fn().mockResolvedValue({}), - pages: vi.fn().mockResolvedValue([]), - close: vi.fn().mockResolvedValue(undefined), - disconnect: vi.fn().mockResolvedValue(undefined), - } - - // Set browser and page on the session - ;(browserSession as any).browser = mockBrowser - ;(browserSession as any).page = {} - ;(browserSession as any).isUsingRemoteBrowser = true - - await browserSession.closeBrowser() - - // Verify that browser.disconnect was called - expect(mockBrowser.disconnect).toHaveBeenCalled() - expect(mockBrowser.close).not.toHaveBeenCalled() - }) - }) - - it("forces same-tab behavior before click", async () => { - // Prepare a minimal mock page with required APIs - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - waitForNavigation: vi.fn().mockResolvedValue(undefined), - evaluate: vi.fn().mockResolvedValue(undefined), - mouse: { - click: vi.fn().mockResolvedValue(undefined), - move: vi.fn().mockResolvedValue(undefined), - }, - } - - ;(browserSession as any).page = page - - // Spy on the forceLinksToSameTab helper to ensure it's invoked - const forceSpy = vi.fn().mockResolvedValue(undefined) - ;(browserSession as any).forceLinksToSameTab = forceSpy - - await browserSession.click("10,20") - - expect(forceSpy).toHaveBeenCalledTimes(1) - expect(forceSpy).toHaveBeenCalledWith(page) - expect(page.mouse.click).toHaveBeenCalledWith(10, 20) - }) -}) - -describe("keyboard press", () => { - it("presses a keyboard key", async () => { - // Prepare a minimal mock page with required APIs - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - waitForNavigation: vi.fn().mockResolvedValue(undefined), - evaluate: vi.fn().mockResolvedValue(undefined), - keyboard: { - press: vi.fn().mockResolvedValue(undefined), - type: vi.fn().mockResolvedValue(undefined), - }, - } - - // Create a fresh BrowserSession with a mock context - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - - ;(session as any).page = page - - await session.press("Enter") - - expect(page.keyboard.press).toHaveBeenCalledTimes(1) - expect(page.keyboard.press).toHaveBeenCalledWith("Enter") - }) -}) - -describe("cursor visualization", () => { - it("should draw cursor indicator when cursor position exists", async () => { - // Prepare a minimal mock page with required APIs - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - mouse: { - click: vi.fn().mockResolvedValue(undefined), - }, - } - - // Create a fresh BrowserSession with a mock context - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - - ;(session as any).page = page - - // Perform a click action which sets cursor position - const result = await session.click("100,200") - - // Verify cursor indicator was drawn and removed - // evaluate is called 3 times: 1 for forceLinksToSameTab, 1 for draw cursor, 1 for remove cursor - expect(page.evaluate).toHaveBeenCalled() - - // Verify the result includes cursor position - expect(result.currentMousePosition).toBe("100,200") - }) - - it("should include cursor position in action result", async () => { - // Prepare a minimal mock page with required APIs - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - mouse: { - move: vi.fn().mockResolvedValue(undefined), - }, - } - - // Create a fresh BrowserSession with a mock context - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - - ;(session as any).page = page - - // Perform a hover action which sets cursor position - const result = await session.hover("150,250") - - // Verify the result includes cursor position - expect(result.currentMousePosition).toBe("150,250") - expect(result.viewportWidth).toBe(900) - expect(result.viewportHeight).toBe(600) - }) - - it("should not draw cursor indicator when no cursor position exists", async () => { - // Prepare a minimal mock page with required APIs - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - // Create a fresh BrowserSession with a mock context - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - - ;(session as any).page = page - - // Perform scroll action which doesn't set cursor position - const result = await session.scrollDown() - - // Verify evaluate was called only for scroll operation (not for cursor drawing/removal) - // scrollDown calls evaluate once for scrolling - expect(page.evaluate).toHaveBeenCalledTimes(1) - - // Verify no cursor position in result - expect(result.currentMousePosition).toBeUndefined() - }) - - describe("saveScreenshot", () => { - // Use a cross-platform workspace path for testing - const testWorkspace = path.resolve("/workspace") - - it("should save screenshot to specified path with png format", async () => { - const mockFs = await import("fs/promises") - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - ;(session as any).page = page - - await session.saveScreenshot("screenshots/test.png", testWorkspace) - - expect(mockFs.mkdir).toHaveBeenCalledWith(path.join(testWorkspace, "screenshots"), { recursive: true }) - expect(page.screenshot).toHaveBeenCalledWith( - expect.objectContaining({ - path: path.join(testWorkspace, "screenshots", "test.png"), - type: "png", - }), - ) - }) - - it("should save screenshot with jpeg format for .jpg extension", async () => { - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - const mockCtx: any = { - globalState: { get: vi.fn().mockReturnValue(80), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - ;(session as any).page = page - - await session.saveScreenshot("screenshots/test.jpg", testWorkspace) - - expect(page.screenshot).toHaveBeenCalledWith( - expect.objectContaining({ - path: path.join(testWorkspace, "screenshots", "test.jpg"), - type: "jpeg", - quality: 80, - }), - ) - }) - - it("should save screenshot with webp format", async () => { - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - const mockCtx: any = { - globalState: { get: vi.fn().mockReturnValue(75), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - ;(session as any).page = page - - await session.saveScreenshot("test.webp", testWorkspace) - - expect(page.screenshot).toHaveBeenCalledWith( - expect.objectContaining({ - path: path.join(testWorkspace, "test.webp"), - type: "webp", - quality: 75, - }), - ) - }) - - it("should reject absolute file paths outside workspace", async () => { - // Create a cross-platform absolute path for testing - const absolutePath = path.resolve("/absolute/path/screenshot.png") - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - ;(session as any).page = page - - await expect(session.saveScreenshot(absolutePath, testWorkspace)).rejects.toThrow(/outside the workspace/) - - expect(page.screenshot).not.toHaveBeenCalled() - }) - - it("should reject paths with .. that escape the workspace", async () => { - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - ;(session as any).page = page - - await expect(session.saveScreenshot("../../etc/passwd", testWorkspace)).rejects.toThrow( - /outside the workspace/, - ) - - expect(page.screenshot).not.toHaveBeenCalled() - }) - - it("should allow paths with .. that stay within workspace", async () => { - const mockFs = await import("fs/promises") - const page: any = { - on: vi.fn(), - off: vi.fn(), - screenshot: vi.fn().mockResolvedValue("mockScreenshotBase64"), - url: vi.fn().mockReturnValue("https://example.com"), - viewport: vi.fn().mockReturnValue({ width: 900, height: 600 }), - evaluate: vi.fn().mockResolvedValue(undefined), - } - - const mockCtx: any = { - globalState: { get: vi.fn(), update: vi.fn() }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(mockCtx) - ;(session as any).page = page - - // Path like "subdir/../screenshot.png" should resolve to "screenshot.png" within workspace - await session.saveScreenshot("subdir/../screenshot.png", testWorkspace) - - expect(page.screenshot).toHaveBeenCalledWith( - expect.objectContaining({ - path: path.join(testWorkspace, "screenshot.png"), - type: "png", - }), - ) - }) - }) - - describe("getViewportSize", () => { - it("falls back to configured viewport when no page or last viewport is available", () => { - const localCtx: any = { - globalState: { - get: vi.fn((key: string) => { - if (key === "browserViewportSize") return "1024x768" - return undefined - }), - update: vi.fn(), - }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - - const session = new BrowserSession(localCtx) - const vp = (session as any).getViewportSize() - expect(vp).toEqual({ width: 1024, height: 768 }) - }) - - it("returns live page viewport when available and updates lastViewport cache", () => { - const localCtx: any = { - globalState: { - get: vi.fn(), - update: vi.fn(), - }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(localCtx) - ;(session as any).page = { - viewport: vi.fn().mockReturnValue({ width: 1111, height: 555 }), - } - - const vp = (session as any).getViewportSize() - expect(vp).toEqual({ width: 1111, height: 555 }) - expect((session as any).lastViewportWidth).toBe(1111) - expect((session as any).lastViewportHeight).toBe(555) - }) - - it("returns cached last viewport when page no longer exists", () => { - const localCtx: any = { - globalState: { - get: vi.fn(), - update: vi.fn(), - }, - globalStorageUri: { fsPath: "/mock/global/storage/path" }, - extensionUri: { fsPath: "/mock/extension/path" }, - } - const session = new BrowserSession(localCtx) - ;(session as any).lastViewportWidth = 800 - ;(session as any).lastViewportHeight = 600 - - const vp = (session as any).getViewportSize() - expect(vp).toEqual({ width: 800, height: 600 }) - }) - }) -}) diff --git a/src/services/browser/__tests__/UrlContentFetcher.spec.ts b/src/services/browser/__tests__/UrlContentFetcher.spec.ts deleted file mode 100644 index b21456e3794..00000000000 --- a/src/services/browser/__tests__/UrlContentFetcher.spec.ts +++ /dev/null @@ -1,369 +0,0 @@ -// npx vitest services/browser/__tests__/UrlContentFetcher.spec.ts - -import * as path from "path" - -import { UrlContentFetcher } from "../UrlContentFetcher" - -// Mock dependencies -vi.mock("vscode", () => ({ - ExtensionContext: vi.fn(), - Uri: { - file: vi.fn((path) => ({ fsPath: path })), - }, -})) - -// Mock fs/promises -vi.mock("fs/promises", () => ({ - default: { - mkdir: vi.fn().mockResolvedValue(undefined), - }, - mkdir: vi.fn().mockResolvedValue(undefined), -})) - -// Mock utils/fs -vi.mock("../../../utils/fs", () => ({ - fileExistsAtPath: vi.fn().mockResolvedValue(true), -})) - -// Mock cheerio -vi.mock("cheerio", () => ({ - load: vi.fn(() => { - const $ = vi.fn((selector) => ({ - remove: vi.fn().mockReturnThis(), - })) as any - $.html = vi.fn().mockReturnValue("Test content") - return $ - }), -})) - -// Mock turndown -vi.mock("turndown", () => { - return { - default: class MockTurndownService { - turndown = vi.fn().mockReturnValue("# Test content") - }, - } -}) - -// Mock puppeteer-chromium-resolver -vi.mock("puppeteer-chromium-resolver", () => ({ - default: vi.fn().mockResolvedValue({ - puppeteer: { - launch: vi.fn().mockResolvedValue({ - newPage: vi.fn().mockResolvedValue({ - goto: vi.fn(), - content: vi.fn().mockResolvedValue("Test content"), - setViewport: vi.fn().mockResolvedValue(undefined), - setExtraHTTPHeaders: vi.fn().mockResolvedValue(undefined), - }), - close: vi.fn().mockResolvedValue(undefined), - }), - }, - executablePath: "/path/to/chromium", - }), -})) - -// Mock serialize-error -vi.mock("serialize-error", () => ({ - serializeError: vi.fn((error) => { - if (error instanceof Error) { - return { message: error.message, name: error.name } - } else if (typeof error === "string") { - return { message: error } - } else if (error && typeof error === "object" && "message" in error) { - return { message: String(error.message), name: "name" in error ? String(error.name) : undefined } - } else { - return { message: String(error) } - } - }), -})) - -describe("UrlContentFetcher", () => { - let urlContentFetcher: UrlContentFetcher - let mockContext: any - let mockPage: any - let mockBrowser: any - let PCR: any - - beforeEach(async () => { - vi.clearAllMocks() - - mockContext = { - globalStorageUri: { - fsPath: "/test/storage", - }, - } - - mockPage = { - goto: vi.fn(), - content: vi.fn().mockResolvedValue("Test content"), - setViewport: vi.fn().mockResolvedValue(undefined), - setExtraHTTPHeaders: vi.fn().mockResolvedValue(undefined), - } - - mockBrowser = { - newPage: vi.fn().mockResolvedValue(mockPage), - close: vi.fn().mockResolvedValue(undefined), - } - - // Reset PCR mock - // @ts-ignore - PCR = (await import("puppeteer-chromium-resolver")).default - vi.mocked(PCR).mockResolvedValue({ - puppeteer: { - launch: vi.fn().mockResolvedValue(mockBrowser), - }, - executablePath: "/path/to/chromium", - }) - - urlContentFetcher = new UrlContentFetcher(mockContext) - }) - - afterEach(() => { - vi.restoreAllMocks() - }) - - describe("launchBrowser", () => { - it("should launch browser with correct arguments on non-Linux platforms", async () => { - // Ensure we're not on Linux for this test - const originalPlatform = process.platform - Object.defineProperty(process, "platform", { - value: "darwin", // macOS - }) - - try { - await urlContentFetcher.launchBrowser() - - expect(vi.mocked(PCR)).toHaveBeenCalledWith({ - downloadPath: path.join("/test/storage", "puppeteer"), - }) - - const stats = await vi.mocked(PCR).mock.results[0].value - expect(stats.puppeteer.launch).toHaveBeenCalledWith({ - args: [ - "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - "--disable-dev-shm-usage", - "--disable-accelerated-2d-canvas", - "--no-first-run", - "--disable-gpu", - "--disable-features=VizDisplayCompositor", - ], - executablePath: "/path/to/chromium", - }) - } finally { - // Restore original platform - Object.defineProperty(process, "platform", { - value: originalPlatform, - }) - } - }) - - it("should launch browser with Linux-specific arguments", async () => { - // Mock process.platform to be linux - const originalPlatform = process.platform - Object.defineProperty(process, "platform", { - value: "linux", - }) - - try { - // Create a new instance to ensure fresh state - const linuxFetcher = new UrlContentFetcher(mockContext) - await linuxFetcher.launchBrowser() - - expect(vi.mocked(PCR)).toHaveBeenCalledWith({ - downloadPath: path.join("/test/storage", "puppeteer"), - }) - - const stats = await vi.mocked(PCR).mock.results[vi.mocked(PCR).mock.results.length - 1].value - expect(stats.puppeteer.launch).toHaveBeenCalledWith({ - args: [ - "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - "--disable-dev-shm-usage", - "--disable-accelerated-2d-canvas", - "--no-first-run", - "--disable-gpu", - "--disable-features=VizDisplayCompositor", - "--no-sandbox", // Linux-specific argument - ], - executablePath: "/path/to/chromium", - }) - } finally { - // Restore original platform - Object.defineProperty(process, "platform", { - value: originalPlatform, - }) - } - }) - - it("should set viewport and headers after launching", async () => { - await urlContentFetcher.launchBrowser() - - expect(mockPage.setViewport).toHaveBeenCalledWith({ width: 1280, height: 720 }) - expect(mockPage.setExtraHTTPHeaders).toHaveBeenCalledWith({ - "Accept-Language": "en-US,en;q=0.9", - }) - }) - - it("should not launch browser if already launched", async () => { - await urlContentFetcher.launchBrowser() - const initialCallCount = vi.mocked(PCR).mock.calls.length - - await urlContentFetcher.launchBrowser() - expect(vi.mocked(PCR)).toHaveBeenCalledTimes(initialCallCount) - }) - }) - - describe("urlToMarkdown", () => { - beforeEach(async () => { - await urlContentFetcher.launchBrowser() - }) - - it("should successfully fetch and convert URL to markdown", async () => { - mockPage.goto.mockResolvedValueOnce(undefined) - - const result = await urlContentFetcher.urlToMarkdown("https://example.com") - - expect(mockPage.goto).toHaveBeenCalledWith("https://example.com", { - timeout: 30000, - waitUntil: ["domcontentloaded", "networkidle2"], - }) - expect(result).toBe("# Test content") - }) - - it("should retry with domcontentloaded only when networkidle2 fails", async () => { - const timeoutError = new Error("Navigation timeout of 30000 ms exceeded") - mockPage.goto.mockRejectedValueOnce(timeoutError).mockResolvedValueOnce(undefined) - - const result = await urlContentFetcher.urlToMarkdown("https://example.com") - - expect(mockPage.goto).toHaveBeenCalledTimes(2) - expect(mockPage.goto).toHaveBeenNthCalledWith(1, "https://example.com", { - timeout: 30000, - waitUntil: ["domcontentloaded", "networkidle2"], - }) - expect(mockPage.goto).toHaveBeenNthCalledWith(2, "https://example.com", { - timeout: 20000, - waitUntil: ["domcontentloaded"], - }) - expect(result).toBe("# Test content") - }) - - it("should retry for network errors", async () => { - const networkError = new Error("net::ERR_CONNECTION_REFUSED") - mockPage.goto.mockRejectedValueOnce(networkError).mockResolvedValueOnce(undefined) - - const result = await urlContentFetcher.urlToMarkdown("https://example.com") - - expect(mockPage.goto).toHaveBeenCalledTimes(2) - expect(result).toBe("# Test content") - }) - - it("should retry for TimeoutError", async () => { - const timeoutError = new Error("TimeoutError: Navigation timeout") - timeoutError.name = "TimeoutError" - mockPage.goto.mockRejectedValueOnce(timeoutError).mockResolvedValueOnce(undefined) - - const result = await urlContentFetcher.urlToMarkdown("https://example.com") - - expect(mockPage.goto).toHaveBeenCalledTimes(2) - expect(result).toBe("# Test content") - }) - - it("should not retry for non-network/timeout errors", async () => { - const otherError = new Error("Some other error") - mockPage.goto.mockRejectedValueOnce(otherError) - - await expect(urlContentFetcher.urlToMarkdown("https://example.com")).rejects.toThrow("Some other error") - expect(mockPage.goto).toHaveBeenCalledTimes(1) - }) - - it("should throw error if browser not initialized", async () => { - const newFetcher = new UrlContentFetcher(mockContext) - - await expect(newFetcher.urlToMarkdown("https://example.com")).rejects.toThrow("Browser not initialized") - }) - - it("should handle errors without message property", async () => { - const errorWithoutMessage = { code: "UNKNOWN_ERROR" } - mockPage.goto.mockRejectedValueOnce(errorWithoutMessage) - - // serialize-error will convert this to a proper error with the object stringified - await expect(urlContentFetcher.urlToMarkdown("https://example.com")).rejects.toThrow() - - // Should not retry for non-network errors - expect(mockPage.goto).toHaveBeenCalledTimes(1) - }) - - it("should handle error objects with message property", async () => { - const errorWithMessage = { message: "Custom error", code: "CUSTOM_ERROR" } - mockPage.goto.mockRejectedValueOnce(errorWithMessage) - - await expect(urlContentFetcher.urlToMarkdown("https://example.com")).rejects.toThrow("Custom error") - - // Should not retry for error objects with message property (they're treated as known errors) - expect(mockPage.goto).toHaveBeenCalledTimes(1) - }) - - it("should retry for error objects with network-related messages", async () => { - const errorWithNetworkMessage = { message: "net::ERR_CONNECTION_REFUSED", code: "NETWORK_ERROR" } - mockPage.goto.mockRejectedValueOnce(errorWithNetworkMessage).mockResolvedValueOnce(undefined) - - const result = await urlContentFetcher.urlToMarkdown("https://example.com") - - // Should retry for network-related errors even in non-Error objects - expect(mockPage.goto).toHaveBeenCalledTimes(2) - expect(result).toBe("# Test content") - }) - - it("should handle string errors", async () => { - const stringError = "Simple string error" - mockPage.goto.mockRejectedValueOnce(stringError) - - await expect(urlContentFetcher.urlToMarkdown("https://example.com")).rejects.toThrow("Simple string error") - expect(mockPage.goto).toHaveBeenCalledTimes(1) - }) - - it("should retry net::ERR_ABORTED like other network errors", async () => { - const abortedError = new Error("net::ERR_ABORTED at https://example.com") - mockPage.goto.mockRejectedValueOnce(abortedError).mockResolvedValueOnce(undefined) - - const result = await urlContentFetcher.urlToMarkdown("https://example.com") - - expect(mockPage.goto).toHaveBeenCalledTimes(2) - expect(mockPage.goto).toHaveBeenNthCalledWith(1, "https://example.com", { - timeout: 30000, - waitUntil: ["domcontentloaded", "networkidle2"], - }) - expect(mockPage.goto).toHaveBeenNthCalledWith(2, "https://example.com", { - timeout: 20000, - waitUntil: ["domcontentloaded"], - }) - expect(result).toBe("# Test content") - }) - - it("should throw error when ERR_ABORTED retry also fails", async () => { - const abortedError = new Error("net::ERR_ABORTED at https://example.com") - const retryError = new Error("net::ERR_CONNECTION_REFUSED") - mockPage.goto.mockRejectedValueOnce(abortedError).mockRejectedValueOnce(retryError) - - await expect(urlContentFetcher.urlToMarkdown("https://example.com")).rejects.toThrow( - "net::ERR_CONNECTION_REFUSED", - ) - - expect(mockPage.goto).toHaveBeenCalledTimes(2) - }) - }) - - describe("closeBrowser", () => { - it("should close browser and reset state", async () => { - await urlContentFetcher.launchBrowser() - await urlContentFetcher.closeBrowser() - - expect(mockBrowser.close).toHaveBeenCalled() - }) - - it("should handle closing when browser not initialized", async () => { - await expect(urlContentFetcher.closeBrowser()).resolves.not.toThrow() - }) - }) -}) diff --git a/src/services/browser/browserDiscovery.ts b/src/services/browser/browserDiscovery.ts deleted file mode 100644 index ecfd1c868a9..00000000000 --- a/src/services/browser/browserDiscovery.ts +++ /dev/null @@ -1,181 +0,0 @@ -import * as net from "net" -import axios from "axios" -import * as dns from "dns" - -/** - * Check if a port is open on a given host - */ -export async function isPortOpen(host: string, port: number, timeout = 1000): Promise { - return new Promise((resolve) => { - const socket = new net.Socket() - let status = false - - // Set timeout - socket.setTimeout(timeout) - - // Handle successful connection - socket.on("connect", () => { - status = true - socket.destroy() - }) - - // Handle any errors - socket.on("error", () => { - socket.destroy() - }) - - // Handle timeout - socket.on("timeout", () => { - socket.destroy() - }) - - // Handle close - socket.on("close", () => { - resolve(status) - }) - - // Attempt to connect - socket.connect(port, host) - }) -} - -/** - * Try to connect to Chrome at a specific IP address - */ -export async function tryChromeHostUrl(chromeHostUrl: string): Promise { - try { - console.log(`Trying to connect to Chrome at: ${chromeHostUrl}/json/version`) - await axios.get(`${chromeHostUrl}/json/version`, { timeout: 1000 }) - return true - } catch (error) { - return false - } -} - -/** - * Get Docker host IP - */ -export async function getDockerHostIP(): Promise { - try { - // Try to resolve host.docker.internal (works on Docker Desktop) - return new Promise((resolve) => { - dns.lookup("host.docker.internal", (err: any, address: string) => { - if (err) { - resolve(null) - } else { - resolve(address) - } - }) - }) - } catch (error) { - console.log("Could not determine Docker host IP:", error) - return null - } -} - -/** - * Scan a network range for Chrome debugging port - */ -export async function scanNetworkForChrome(baseIP: string, port: number): Promise { - if (!baseIP || !baseIP.match(/^\d+\.\d+\.\d+\./)) { - return null - } - - // Extract the network prefix (e.g., "192.168.65.") - const networkPrefix = baseIP.split(".").slice(0, 3).join(".") + "." - - // Common Docker host IPs to try first - const priorityIPs = [ - networkPrefix + "1", // Common gateway - networkPrefix + "2", // Common host - networkPrefix + "254", // Common host in some Docker setups - ] - - console.log(`Scanning priority IPs in network ${networkPrefix}*`) - - // Check priority IPs first - for (const ip of priorityIPs) { - const isOpen = await isPortOpen(ip, port) - if (isOpen) { - console.log(`Found Chrome debugging port open on ${ip}`) - return ip - } - } - - return null -} - -// Function to discover Chrome instances on the network -const discoverChromeHosts = async (port: number): Promise => { - // Get all network interfaces - const ipAddresses = [] - - // Try to get Docker host IP - const hostIP = await getDockerHostIP() - if (hostIP) { - console.log("Found Docker host IP:", hostIP) - ipAddresses.push(hostIP) - } - - // Remove duplicates - const uniqueIPs = [...new Set(ipAddresses)] - console.log("IP Addresses to try:", uniqueIPs) - - // Try connecting to each IP address - for (const ip of uniqueIPs) { - const hostEndpoint = `http://${ip}:${port}` - - const hostIsValid = await tryChromeHostUrl(hostEndpoint) - if (hostIsValid) { - // Store the successful IP for future use - console.log(`✅ Found Chrome at ${hostEndpoint}`) - - // Return the host URL and endpoint - return hostEndpoint - } - } - - return null -} - -/** - * Test connection to a remote browser debugging websocket. - * First tries specific hosts, then attempts auto-discovery if needed. - * @param browserHostUrl Optional specific host URL to check first - * @param port Browser debugging port (default: 9222) - * @returns WebSocket debugger URL if connection is successful, null otherwise - */ -export async function discoverChromeHostUrl(port: number = 9222): Promise { - // First try specific hosts - const hostsToTry = [`http://localhost:${port}`, `http://127.0.0.1:${port}`] - - // Try each host directly first - for (const hostUrl of hostsToTry) { - console.log(`Trying to connect to: ${hostUrl}`) - try { - const hostIsValid = await tryChromeHostUrl(hostUrl) - if (hostIsValid) return hostUrl - } catch (error) { - console.log(`Failed to connect to ${hostUrl}: ${error instanceof Error ? error.message : error}`) - } - } - - // If direct connections failed, attempt auto-discovery - console.log("Direct connections failed. Attempting auto-discovery...") - - const discoveredHostUrl = await discoverChromeHosts(port) - if (discoveredHostUrl) { - console.log(`Trying to connect to discovered host: ${discoveredHostUrl}`) - try { - const hostIsValid = await tryChromeHostUrl(discoveredHostUrl) - if (hostIsValid) return discoveredHostUrl - console.log(`Failed to connect to discovered host ${discoveredHostUrl}`) - } catch (error) { - console.log(`Error connecting to discovered host: ${error instanceof Error ? error.message : error}`) - } - } else { - console.log("No browser instances discovered on network") - } - - return null -} diff --git a/src/shared/__tests__/modes.spec.ts b/src/shared/__tests__/modes.spec.ts index e1d6612a148..ceb3cacb4d9 100644 --- a/src/shared/__tests__/modes.spec.ts +++ b/src/shared/__tests__/modes.spec.ts @@ -19,19 +19,19 @@ describe("isToolAllowedForMode", () => { slug: "markdown-editor", name: "Markdown Editor", roleDefinition: "You are a markdown editor", - groups: ["read", ["edit", { fileRegex: "\\.md$" }], "browser"], + groups: ["read", ["edit", { fileRegex: "\\.md$" }]], }, { slug: "css-editor", name: "CSS Editor", roleDefinition: "You are a CSS editor", - groups: ["read", ["edit", { fileRegex: "\\.css$" }], "browser"], + groups: ["read", ["edit", { fileRegex: "\\.css$" }]], }, { slug: "test-exp-mode", name: "Test Exp Mode", roleDefinition: "You are an experimental tester", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }, ] @@ -42,7 +42,6 @@ describe("isToolAllowedForMode", () => { it("allows unrestricted tools", () => { expect(isToolAllowedForMode("read_file", "markdown-editor", customModes)).toBe(true) - expect(isToolAllowedForMode("browser_action", "markdown-editor", customModes)).toBe(true) }) describe("file restrictions", () => { @@ -151,11 +150,7 @@ describe("isToolAllowedForMode", () => { slug: "docs-editor", name: "Documentation Editor", roleDefinition: "You are a documentation editor", - groups: [ - "read", - ["edit", { fileRegex: "\\.(md|txt)$", description: "Documentation files only" }], - "browser", - ], + groups: ["read", ["edit", { fileRegex: "\\.(md|txt)$", description: "Documentation files only" }]], }, ] @@ -243,7 +238,6 @@ describe("isToolAllowedForMode", () => { // Should maintain read capabilities expect(isToolAllowedForMode("read_file", "architect", [])).toBe(true) - expect(isToolAllowedForMode("browser_action", "architect", [])).toBe(true) expect(isToolAllowedForMode("use_mcp_tool", "architect", [])).toBe(true) }) @@ -535,7 +529,7 @@ describe("isToolAllowedForMode", () => { slug: "test-custom-tools", name: "Test Custom Tools Mode", roleDefinition: "You are a test mode", - groups: ["read", "edit", "browser"], + groups: ["read", "edit"], }, ] @@ -567,7 +561,7 @@ describe("isToolAllowedForMode", () => { slug: "no-edit-mode", name: "No Edit Mode", roleDefinition: "You have no edit powers", - groups: ["read", "browser"], // No edit group + groups: ["read"], // No edit group }, ] @@ -619,7 +613,7 @@ describe("FileRestrictionError", () => { name: "🪲 Debug", roleDefinition: "You are Roo, an expert software debugger specializing in systematic problem diagnosis and resolution.", - groups: ["read", "edit", "browser", "command", "mcp"], + groups: ["read", "edit", "command", "mcp"], }) expect(debugMode?.customInstructions).toContain( "Reflect on 5-7 different possible sources of the problem, distill those down to 1-2 most likely sources, and then add logs to validate your assumptions. Explicitly ask the user to confirm the diagnosis before fixing the problem.", diff --git a/src/shared/browserUtils.ts b/src/shared/browserUtils.ts deleted file mode 100644 index 4e071121c1b..00000000000 --- a/src/shared/browserUtils.ts +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Parses coordinate string and scales from image dimensions to viewport dimensions - * The LLM examines the screenshot it receives (which may be downscaled by the API) - * and reports coordinates in format: "x,y@widthxheight" where widthxheight is what the LLM observed - * - * Format: "x,y@widthxheight" (required) - * Returns: scaled coordinate string "x,y" in viewport coordinates - * Throws: Error if format is invalid or missing image dimensions - */ -export function scaleCoordinate(coordinate: string, viewportWidth: number, viewportHeight: number): string { - // Parse coordinate with required image dimensions (accepts both 'x' and ',' as dimension separators) - const match = coordinate.match(/^\s*(\d+)\s*,\s*(\d+)\s*@\s*(\d+)\s*[x,]\s*(\d+)\s*$/) - - if (!match) { - throw new Error( - `Invalid coordinate format: "${coordinate}". ` + - `Expected format: "x,y@widthxheight" (e.g., "450,300@1024x768")`, - ) - } - - const [, xStr, yStr, imgWidthStr, imgHeightStr] = match - const x = parseInt(xStr, 10) - const y = parseInt(yStr, 10) - const imgWidth = parseInt(imgWidthStr, 10) - const imgHeight = parseInt(imgHeightStr, 10) - - // Scale coordinates from image dimensions to viewport dimensions - const scaledX = Math.round((x / imgWidth) * viewportWidth) - const scaledY = Math.round((y / imgHeight) * viewportHeight) - - return `${scaledX},${scaledY}` -} - -/** - * Formats a key string into a more readable format (e.g., "Control+c" -> "Ctrl + C") - */ -export function prettyKey(k?: string): string { - if (!k) return "" - return k - .split("+") - .map((part) => { - const p = part.trim() - const lower = p.toLowerCase() - const map: Record = { - enter: "Enter", - tab: "Tab", - escape: "Esc", - esc: "Esc", - backspace: "Backspace", - space: "Space", - shift: "Shift", - control: "Ctrl", - ctrl: "Ctrl", - alt: "Alt", - meta: "Meta", - command: "Cmd", - cmd: "Cmd", - arrowup: "Arrow Up", - arrowdown: "Arrow Down", - arrowleft: "Arrow Left", - arrowright: "Arrow Right", - pageup: "Page Up", - pagedown: "Page Down", - home: "Home", - end: "End", - } - if (map[lower]) return map[lower] - const keyMatch = /^Key([A-Z])$/.exec(p) - if (keyMatch) return keyMatch[1].toUpperCase() - const digitMatch = /^Digit([0-9])$/.exec(p) - if (digitMatch) return digitMatch[1] - const spaced = p.replace(/([a-z])([A-Z])/g, "$1 $2") - return spaced.charAt(0).toUpperCase() + spaced.slice(1) - }) - .join(" + ") -} - -/** - * Wrapper around scaleCoordinate that handles failures gracefully by checking for simple coordinates - */ -export function getViewportCoordinate( - coord: string | undefined, - viewportWidth: number, - viewportHeight: number, -): string { - if (!coord) return "" - - try { - return scaleCoordinate(coord, viewportWidth, viewportHeight) - } catch (e) { - // Fallback to simple x,y parsing or return as is - const simpleMatch = /^\s*(\d+)\s*,\s*(\d+)/.exec(coord) - return simpleMatch ? `${simpleMatch[1]},${simpleMatch[2]}` : coord - } -} diff --git a/src/shared/tools.ts b/src/shared/tools.ts index a7298e946a7..06e3d6c48a2 100644 --- a/src/shared/tools.ts +++ b/src/shared/tools.ts @@ -1,13 +1,6 @@ import type { TextPart, ImagePart } from "../core/task-persistence/rooMessage" -import type { - ClineAsk, - ToolProgressStatus, - ToolGroup, - ToolName, - BrowserActionParams, - GenerateImageParams, -} from "@roo-code/types" +import type { ClineAsk, ToolProgressStatus, ToolGroup, ToolName, GenerateImageParams } from "@roo-code/types" export type ToolResponse = string | Array @@ -113,7 +106,6 @@ export type NativeToolArgs = { question: string follow_up: Array<{ text: string; mode?: string }> } - browser_action: BrowserActionParams codebase_search: { query: string; path?: string } generate_image: GenerateImageParams run_slash_command: { command: string; args?: string } @@ -220,11 +212,6 @@ export interface ListFilesToolUse extends ToolUse<"list_files"> { params: Partial, "path" | "recursive">> } -export interface BrowserActionToolUse extends ToolUse<"browser_action"> { - name: "browser_action" - params: Partial, "action" | "url" | "coordinate" | "text" | "size" | "path">> -} - export interface UseMcpToolToolUse extends ToolUse<"use_mcp_tool"> { name: "use_mcp_tool" params: Partial, "server_name" | "tool_name" | "arguments">> @@ -290,7 +277,6 @@ export const TOOL_DISPLAY_NAMES: Record = { apply_patch: "apply patches using codex format", search_files: "search files", list_files: "list files", - browser_action: "use a browser", use_mcp_tool: "use mcp tools", access_mcp_resource: "access mcp resources", ask_followup_question: "ask questions", @@ -314,9 +300,6 @@ export const TOOL_GROUPS: Record = { tools: ["apply_diff", "write_to_file", "generate_image"], customTools: ["edit", "search_replace", "edit_file", "apply_patch"], }, - browser: { - tools: ["browser_action"], - }, command: { tools: ["execute_command", "read_command_output"], }, diff --git a/webview-ui/browser-panel.html b/webview-ui/browser-panel.html deleted file mode 100644 index 92943abfe34..00000000000 --- a/webview-ui/browser-panel.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Browser Session - - -
- - - \ No newline at end of file diff --git a/webview-ui/src/browser-panel.tsx b/webview-ui/src/browser-panel.tsx deleted file mode 100644 index a7f5af891e6..00000000000 --- a/webview-ui/src/browser-panel.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { StrictMode } from "react" -import { createRoot } from "react-dom/client" - -import "./index.css" -import BrowserSessionPanel from "./components/browser-session/BrowserSessionPanel" -import "../node_modules/@vscode/codicons/dist/codicon.css" - -createRoot(document.getElementById("root")!).render( - - - , -) diff --git a/webview-ui/src/components/browser-session/BrowserPanelStateProvider.tsx b/webview-ui/src/components/browser-session/BrowserPanelStateProvider.tsx deleted file mode 100644 index 8430c772aa0..00000000000 --- a/webview-ui/src/components/browser-session/BrowserPanelStateProvider.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { createContext, useContext, useState, useEffect, useCallback } from "react" - -import { type ExtensionMessage } from "@roo-code/types" - -interface BrowserPanelState { - browserViewportSize: string - isBrowserSessionActive: boolean - language: string -} - -const BrowserPanelStateContext = createContext(undefined) - -export const BrowserPanelStateProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const [state, setState] = useState({ - browserViewportSize: "900x600", - isBrowserSessionActive: false, - language: "en", - }) - - const handleMessage = useCallback((event: MessageEvent) => { - const message: ExtensionMessage = event.data - - switch (message.type) { - case "state": - if (message.state) { - setState((prev) => ({ - ...prev, - browserViewportSize: message.state?.browserViewportSize || "900x600", - isBrowserSessionActive: message.state?.isBrowserSessionActive || false, - language: message.state?.language || "en", - })) - } - break - case "browserSessionUpdate": - if (message.isBrowserSessionActive !== undefined) { - setState((prev) => ({ - ...prev, - isBrowserSessionActive: message.isBrowserSessionActive || false, - })) - } - break - } - }, []) - - useEffect(() => { - window.addEventListener("message", handleMessage) - return () => { - window.removeEventListener("message", handleMessage) - } - }, [handleMessage]) - - return {children} -} - -export const useBrowserPanelState = () => { - const context = useContext(BrowserPanelStateContext) - if (context === undefined) { - throw new Error("useBrowserPanelState must be used within a BrowserPanelStateProvider") - } - return context -} diff --git a/webview-ui/src/components/browser-session/BrowserSessionPanel.tsx b/webview-ui/src/components/browser-session/BrowserSessionPanel.tsx deleted file mode 100644 index d9667c56f13..00000000000 --- a/webview-ui/src/components/browser-session/BrowserSessionPanel.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import React, { useEffect, useState } from "react" - -import { type ClineMessage, type ExtensionMessage } from "@roo-code/types" - -import { TooltipProvider } from "@src/components/ui/tooltip" -import TranslationProvider from "@src/i18n/TranslationContext" -import { vscode } from "@src/utils/vscode" - -import { ExtensionStateContextProvider } from "@/context/ExtensionStateContext" - -import BrowserSessionRow from "../chat/BrowserSessionRow" -import ErrorBoundary from "../ErrorBoundary" - -import { BrowserPanelStateProvider, useBrowserPanelState } from "./BrowserPanelStateProvider" - -interface BrowserSessionPanelState { - messages: ClineMessage[] -} - -const BrowserSessionPanelContent: React.FC = () => { - const { browserViewportSize, isBrowserSessionActive } = useBrowserPanelState() - const [state, setState] = useState({ - messages: [], - }) - // Target page index to navigate BrowserSessionRow to - const [navigateToStepIndex, setNavigateToStepIndex] = useState(undefined) - - const [expandedRows, setExpandedRows] = useState>({}) - - useEffect(() => { - const handleMessage = (event: MessageEvent) => { - const message: ExtensionMessage = event.data - - switch (message.type) { - case "browserSessionUpdate": - if (message.browserSessionMessages) { - setState((prev) => ({ - ...prev, - messages: message.browserSessionMessages || [], - })) - } - break - case "browserSessionNavigate": - if (typeof message.stepIndex === "number" && message.stepIndex >= 0) { - setNavigateToStepIndex(message.stepIndex) - } - break - } - } - - window.addEventListener("message", handleMessage) - - return () => { - window.removeEventListener("message", handleMessage) - } - }, []) - - return ( -
- expandedRows[messageTs] ?? false} - onToggleExpand={(messageTs: number) => { - setExpandedRows((prev: Record) => ({ - ...prev, - [messageTs]: !prev[messageTs], - })) - }} - fullScreen={true} - browserViewportSizeProp={browserViewportSize} - isBrowserSessionActiveProp={isBrowserSessionActive} - navigateToPageIndex={navigateToStepIndex} - /> -
- ) -} - -const BrowserSessionPanel: React.FC = () => { - // Ensure the panel receives initial state and becomes "ready" without needing a second click - useEffect(() => { - try { - vscode.postMessage({ type: "webviewDidLaunch" }) - } catch { - // Ignore errors during initial launch - } - }, []) - - return ( - - - - - - - - - - - - ) -} - -export default BrowserSessionPanel diff --git a/webview-ui/src/components/chat/AutoApproveDropdown.tsx b/webview-ui/src/components/chat/AutoApproveDropdown.tsx index 857eb5cfb1f..8a5b8adfd6d 100644 --- a/webview-ui/src/components/chat/AutoApproveDropdown.tsx +++ b/webview-ui/src/components/chat/AutoApproveDropdown.tsx @@ -34,7 +34,6 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }: setAlwaysAllowReadOnly, setAlwaysAllowWrite, setAlwaysAllowExecute, - setAlwaysAllowBrowser, setAlwaysAllowMcp, setAlwaysAllowModeSwitch, setAlwaysAllowSubtasks, @@ -57,9 +56,6 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }: case "alwaysAllowExecute": setAlwaysAllowExecute(value) break - case "alwaysAllowBrowser": - setAlwaysAllowBrowser(value) - break case "alwaysAllowMcp": setAlwaysAllowMcp(value) break @@ -85,7 +81,6 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }: setAlwaysAllowReadOnly, setAlwaysAllowWrite, setAlwaysAllowExecute, - setAlwaysAllowBrowser, setAlwaysAllowMcp, setAlwaysAllowModeSwitch, setAlwaysAllowSubtasks, diff --git a/webview-ui/src/components/chat/BrowserActionRow.tsx b/webview-ui/src/components/chat/BrowserActionRow.tsx deleted file mode 100644 index abc09832804..00000000000 --- a/webview-ui/src/components/chat/BrowserActionRow.tsx +++ /dev/null @@ -1,195 +0,0 @@ -import { memo, useMemo, useEffect, useRef } from "react" -import { useTranslation } from "react-i18next" -import { - MousePointer as MousePointerIcon, - Keyboard, - ArrowDown, - ArrowUp, - Pointer, - Play, - Check, - Maximize2, - Camera, -} from "lucide-react" - -import type { ClineMessage, ClineSayBrowserAction } from "@roo-code/types" - -import { getViewportCoordinate as getViewportCoordinateShared, prettyKey } from "@roo/browserUtils" - -import { vscode } from "@src/utils/vscode" -import { useExtensionState } from "@src/context/ExtensionStateContext" - -interface BrowserActionRowProps { - message: ClineMessage - nextMessage?: ClineMessage - actionIndex?: number - totalActions?: number -} - -// Get icon for each action type -const getActionIcon = (action: string) => { - switch (action) { - case "click": - return - case "type": - case "press": - return - case "scroll_down": - return - case "scroll_up": - return - case "launch": - return - case "close": - return - case "resize": - return - case "screenshot": - return - case "hover": - default: - return - } -} - -const BrowserActionRow = memo(({ message, nextMessage, actionIndex, totalActions }: BrowserActionRowProps) => { - const { t } = useTranslation() - const { isBrowserSessionActive } = useExtensionState() - const hasHandledAutoOpenRef = useRef(false) - - // Parse this specific browser action - const browserAction = useMemo(() => { - try { - return JSON.parse(message.text || "{}") as ClineSayBrowserAction - } catch { - return null - } - }, [message.text]) - - // Get viewport dimensions from the result message if available - const viewportDimensions = useMemo(() => { - if (!nextMessage || nextMessage.say !== "browser_action_result") return null - try { - const result = JSON.parse(nextMessage.text || "{}") - return { - width: result.viewportWidth, - height: result.viewportHeight, - } - } catch { - return null - } - }, [nextMessage]) - - // Format action display text - const actionText = useMemo(() => { - if (!browserAction) return t("chat:browser.actions.title") - - // Helper to scale coordinates from screenshot dimensions to viewport dimensions - // Matches the backend's scaleCoordinate function logic - const getViewportCoordinate = (coord?: string): string => - getViewportCoordinateShared(coord, viewportDimensions?.width ?? 0, viewportDimensions?.height ?? 0) - - switch (browserAction.action) { - case "launch": - return t("chat:browser.actions.launched") - case "click": - return t("chat:browser.actions.clicked", { - coordinate: browserAction.executedCoordinate || getViewportCoordinate(browserAction.coordinate), - }) - case "type": - return t("chat:browser.actions.typed", { text: browserAction.text }) - case "press": - return t("chat:browser.actions.pressed", { key: prettyKey(browserAction.text) }) - case "hover": - return t("chat:browser.actions.hovered", { - coordinate: browserAction.executedCoordinate || getViewportCoordinate(browserAction.coordinate), - }) - case "scroll_down": - return t("chat:browser.actions.scrolledDown") - case "scroll_up": - return t("chat:browser.actions.scrolledUp") - case "resize": - return t("chat:browser.actions.resized", { size: browserAction.size?.split(/[x,]/).join(" x ") }) - case "screenshot": - return t("chat:browser.actions.screenshotSaved") - case "close": - return t("chat:browser.actions.closed") - default: - return browserAction.action - } - }, [browserAction, viewportDimensions, t]) - - // Auto-open Browser Session panel when: - // 1. This is a "launch" action (new browser session) - always opens and navigates to launch - // 2. Regular actions - only open panel if user hasn't manually closed it, let internal auto-advance logic handle step - // Only run this once per action to avoid re-sending messages when scrolling - useEffect(() => { - if (!isBrowserSessionActive || hasHandledAutoOpenRef.current) { - return - } - - const isLaunchAction = browserAction?.action === "launch" - - if (isLaunchAction) { - // Launch action: navigate to step 0 (the launch) - vscode.postMessage({ - type: "showBrowserSessionPanelAtStep", - stepIndex: 0, - isLaunchAction: true, - }) - hasHandledAutoOpenRef.current = true - } else { - // Regular actions: just show panel, don't navigate - // BrowserSessionRow's internal auto-advance logic will handle jumping to new steps - // only if user is currently on the most recent step - vscode.postMessage({ - type: "showBrowserSessionPanelAtStep", - isLaunchAction: false, - }) - hasHandledAutoOpenRef.current = true - } - }, [isBrowserSessionActive, browserAction]) - - const headerStyle: React.CSSProperties = { - display: "flex", - alignItems: "center", - gap: "10px", - marginBottom: "10px", - wordBreak: "break-word", - } - - return ( -
- {/* Header with action description - clicking opens Browser Session panel at this step */} -
{ - const idx = typeof actionIndex === "number" ? Math.max(0, actionIndex - 1) : 0 - vscode.postMessage({ type: "showBrowserSessionPanelAtStep", stepIndex: idx, forceShow: true }) - }}> - - {t("chat:browser.actions.title")} - {actionIndex !== undefined && totalActions !== undefined && ( - - {" "} - - {actionIndex}/{totalActions} -{" "} - - )} - {browserAction && ( - <> - {getActionIcon(browserAction.action)} - {actionText} - - )} -
-
- ) -}) - -BrowserActionRow.displayName = "BrowserActionRow" - -export default BrowserActionRow diff --git a/webview-ui/src/components/chat/BrowserSessionRow.tsx b/webview-ui/src/components/chat/BrowserSessionRow.tsx deleted file mode 100644 index cf67abdc586..00000000000 --- a/webview-ui/src/components/chat/BrowserSessionRow.tsx +++ /dev/null @@ -1,1137 +0,0 @@ -import React, { memo, useEffect, useMemo, useRef, useState } from "react" -import deepEqual from "fast-deep-equal" -import { useTranslation } from "react-i18next" -import type { TFunction } from "i18next" - -import type { ClineMessage, BrowserAction, BrowserActionResult, ClineSayBrowserAction } from "@roo-code/types" - -import { vscode } from "@src/utils/vscode" -import { useExtensionState } from "@src/context/ExtensionStateContext" - -import CodeBlock from "../common/CodeBlock" -import { ProgressIndicator } from "./ProgressIndicator" -import { Button, StandardTooltip } from "@src/components/ui" -import { getViewportCoordinate as getViewportCoordinateShared, prettyKey } from "@roo/browserUtils" -import { - Globe, - Pointer, - SquareTerminal, - MousePointer as MousePointerIcon, - Keyboard, - ArrowDown, - ArrowUp, - Play, - Check, - Maximize2, - OctagonX, - ArrowLeft, - ArrowRight, - ChevronsLeft, - ChevronsRight, - ExternalLink, - Copy, - Camera, -} from "lucide-react" - -const getBrowserActionText = ( - t: TFunction, - action: BrowserAction, - executedCoordinate?: string, - coordinate?: string, - text?: string, - size?: string, - viewportWidth?: number, - viewportHeight?: number, -) => { - // Helper to scale coordinates from screenshot dimensions to viewport dimensions - // Matches the backend's scaleCoordinate function logic - const getViewportCoordinate = (coord?: string): string => - getViewportCoordinateShared(coord, viewportWidth ?? 0, viewportHeight ?? 0) - - switch (action) { - case "launch": - return t("chat:browser.actions.launched") - case "click": - return t("chat:browser.actions.clicked", { - coordinate: executedCoordinate || getViewportCoordinate(coordinate), - }) - case "type": - return t("chat:browser.actions.typed", { text }) - case "press": - return t("chat:browser.actions.pressed", { key: prettyKey(text) }) - case "scroll_down": - return t("chat:browser.actions.scrolledDown") - case "scroll_up": - return t("chat:browser.actions.scrolledUp") - case "hover": - return t("chat:browser.actions.hovered", { - coordinate: executedCoordinate || getViewportCoordinate(coordinate), - }) - case "resize": - return t("chat:browser.actions.resized", { size: size?.split(/[x,]/).join(" x ") }) - case "screenshot": - return t("chat:browser.actions.screenshotSaved") - case "close": - return t("chat:browser.actions.closed") - default: - return action - } -} - -const getActionIcon = (action: BrowserAction) => { - switch (action) { - case "click": - return - case "type": - case "press": - return - case "scroll_down": - return - case "scroll_up": - return - case "launch": - return - case "close": - return - case "resize": - return - case "screenshot": - return - case "hover": - default: - return - } -} - -interface BrowserSessionRowProps { - messages: ClineMessage[] - isExpanded: (messageTs: number) => boolean - onToggleExpand: (messageTs: number) => void - lastModifiedMessage?: ClineMessage - isLast: boolean - onHeightChange?: (isTaller: boolean) => void - isStreaming: boolean - onExpandChange?: (expanded: boolean) => void - fullScreen?: boolean - // Optional props for standalone panel (when not using ExtensionStateContext) - browserViewportSizeProp?: string - isBrowserSessionActiveProp?: boolean - // Optional: navigate to a specific page index (used by Browser Session panel) - navigateToPageIndex?: number -} - -const BrowserSessionRow = memo((props: BrowserSessionRowProps) => { - const { messages, isLast, onHeightChange, lastModifiedMessage, onExpandChange, fullScreen } = props - const { t } = useTranslation() - const prevHeightRef = useRef(0) - const [consoleLogsExpanded, setConsoleLogsExpanded] = useState(false) - const [nextActionsExpanded, setNextActionsExpanded] = useState(false) - const [logFilter, setLogFilter] = useState<"all" | "debug" | "info" | "warn" | "error" | "log">("all") - // Track screenshot container size for precise cursor positioning with object-fit: contain - const screenshotRef = useRef(null) - const [sW, setSW] = useState(0) - const [sH, setSH] = useState(0) - - // Auto-expand drawer when in fullScreen takeover mode so content is visible immediately - useEffect(() => { - if (fullScreen) { - setNextActionsExpanded(true) - } - }, [fullScreen]) - - // Observe screenshot container size to align cursor correctly with letterboxing - useEffect(() => { - const el = screenshotRef.current - if (!el) return - const update = () => { - const r = el.getBoundingClientRect() - setSW(r.width) - setSH(r.height) - } - update() - const ro = - typeof window !== "undefined" && "ResizeObserver" in window ? new ResizeObserver(() => update()) : null - if (ro) ro.observe(el) - return () => { - if (ro) ro.disconnect() - } - }, []) - - // Try to use ExtensionStateContext if available, otherwise use props - let browserViewportSize = props.browserViewportSizeProp || "900x600" - let isBrowserSessionActive = props.isBrowserSessionActiveProp || false - - try { - const extensionState = useExtensionState() - browserViewportSize = extensionState.browserViewportSize || "900x600" - isBrowserSessionActive = extensionState.isBrowserSessionActive || false - } catch (_e) { - // Not in ExtensionStateContext, use props - } - - const [viewportWidth, viewportHeight] = browserViewportSize.split("x").map(Number) - const defaultMousePosition = `${Math.round(viewportWidth / 2)},${Math.round(viewportHeight / 2)}` - - const isLastApiReqInterrupted = useMemo(() => { - // Check if last api_req_started is cancelled - const lastApiReqStarted = [...messages].reverse().find((m) => m.say === "api_req_started") - if (lastApiReqStarted?.text) { - const info = JSON.parse(lastApiReqStarted.text) as { cancelReason: string | null } - if (info && info.cancelReason !== null) { - return true - } - } - const lastApiReqFailed = isLast && lastModifiedMessage?.ask === "api_req_failed" - if (lastApiReqFailed) { - return true - } - return false - }, [messages, lastModifiedMessage, isLast]) - - const isBrowsing = useMemo(() => { - return isLast && messages.some((m) => m.say === "browser_action_result") && !isLastApiReqInterrupted // after user approves, browser_action_result with "" is sent to indicate that the session has started - }, [isLast, messages, isLastApiReqInterrupted]) - - // Organize messages into pages based on ALL browser actions (including those without screenshots) - const pages = useMemo(() => { - const result: { - url?: string - screenshot?: string - mousePosition?: string - consoleLogs?: string - action?: ClineSayBrowserAction - size?: string - viewportWidth?: number - viewportHeight?: number - }[] = [] - - // Build pages from browser_action messages and pair with results - messages.forEach((message) => { - if (message.say === "browser_action") { - try { - const action = JSON.parse(message.text || "{}") as ClineSayBrowserAction - // Find the corresponding result message - const resultMessage = messages.find( - (m) => m.say === "browser_action_result" && m.ts > message.ts && m.text !== "", - ) - - if (resultMessage) { - const resultData = JSON.parse(resultMessage.text || "{}") as BrowserActionResult - result.push({ - url: resultData.currentUrl, - screenshot: resultData.screenshot, - mousePosition: resultData.currentMousePosition, - consoleLogs: resultData.logs, - action, - size: action.size, - viewportWidth: resultData.viewportWidth, - viewportHeight: resultData.viewportHeight, - }) - } else { - // For actions without results (like close), add a page without screenshot - result.push({ action, size: action.size }) - } - } catch { - // ignore parse errors - } - } - }) - - // Add placeholder page if no actions yet - if (result.length === 0) { - result.push({}) - } - - return result - }, [messages]) - - // Page index + user navigation guard (don't auto-jump while exploring history) - const [currentPageIndex, setCurrentPageIndex] = useState(0) - const hasUserNavigatedRef = useRef(false) - const didInitIndexRef = useRef(false) - const prevPagesLengthRef = useRef(0) - - useEffect(() => { - // Initialize to last page on mount - if (!didInitIndexRef.current && pages.length > 0) { - didInitIndexRef.current = true - setCurrentPageIndex(pages.length - 1) - prevPagesLengthRef.current = pages.length - return - } - - // Auto-advance if user is on the most recent step and a new step arrives - if (pages.length > prevPagesLengthRef.current) { - const wasOnLastPage = currentPageIndex === prevPagesLengthRef.current - 1 - if (wasOnLastPage && !hasUserNavigatedRef.current) { - // User was on the most recent step, auto-advance to the new step - setCurrentPageIndex(pages.length - 1) - } - prevPagesLengthRef.current = pages.length - } - }, [pages.length, currentPageIndex]) - - // External navigation request (from panel host) - // Only navigate when navigateToPageIndex actually changes, not when pages.length changes - const prevNavigateToPageIndexRef = useRef() - useEffect(() => { - if ( - typeof props.navigateToPageIndex === "number" && - props.navigateToPageIndex !== prevNavigateToPageIndexRef.current && - pages.length > 0 - ) { - const idx = Math.max(0, Math.min(pages.length - 1, props.navigateToPageIndex)) - setCurrentPageIndex(idx) - // Only reset manual navigation guard if navigating to the last page - // This allows auto-advance to work when clicking to the most recent step - // but prevents unwanted auto-advance when viewing historical steps - if (idx === pages.length - 1) { - hasUserNavigatedRef.current = false - } - prevNavigateToPageIndexRef.current = props.navigateToPageIndex - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [props.navigateToPageIndex]) - - // Get initial URL from launch message - const initialUrl = useMemo(() => { - const launchMessage = messages.find((m) => m.ask === "browser_action_launch") - return launchMessage?.text || "" - }, [messages]) - - const currentPage = pages[currentPageIndex] - - // Use actual viewport dimensions from result if available, otherwise fall back to settings - - // Find the last available screenshot and its associated data to use as placeholders - const lastPageWithScreenshot = useMemo(() => { - for (let i = pages.length - 1; i >= 0; i--) { - if (pages[i].screenshot) { - return pages[i] - } - } - return undefined - }, [pages]) - - // Find last mouse position up to current page (not from future pages) - const lastPageWithMousePositionUpToCurrent = useMemo(() => { - for (let i = currentPageIndex; i >= 0; i--) { - if (pages[i].mousePosition) { - return pages[i] - } - } - return undefined - }, [pages, currentPageIndex]) - - // Display state from current page, with smart fallbacks - const displayState = { - url: currentPage?.url || initialUrl, - mousePosition: - currentPage?.mousePosition || lastPageWithMousePositionUpToCurrent?.mousePosition || defaultMousePosition, - consoleLogs: currentPage?.consoleLogs, - screenshot: currentPage?.screenshot || lastPageWithScreenshot?.screenshot, - } - - // Parse logs for counts and filtering - const parsedLogs = useMemo(() => { - const counts = { debug: 0, info: 0, warn: 0, error: 0, log: 0 } - const byType: Record<"debug" | "info" | "warn" | "error" | "log", string[]> = { - debug: [], - info: [], - warn: [], - error: [], - log: [], - } - const raw = displayState.consoleLogs || "" - raw.split(/\r?\n/).forEach((line) => { - const trimmed = line.trim() - if (!trimmed) return - const m = /^\[([^\]]+)\]\s*/i.exec(trimmed) - let type = (m?.[1] || "").toLowerCase() - if (type === "warning") type = "warn" - if (!["debug", "info", "warn", "error", "log"].includes(type)) type = "log" - counts[type as keyof typeof counts]++ - byType[type as keyof typeof byType].push(line) - }) - return { counts, byType } - }, [displayState.consoleLogs]) - - const logsToShow = useMemo(() => { - if (!displayState.consoleLogs) return t("chat:browser.noNewLogs") as string - if (logFilter === "all") return displayState.consoleLogs - const arr = parsedLogs.byType[logFilter] - return arr.length ? arr.join("\n") : (t("chat:browser.noNewLogs") as string) - }, [displayState.consoleLogs, logFilter, parsedLogs, t]) - - // Meta for log badges (include "All" first) - const logTypeMeta = [ - { key: "all", label: "All" }, - { key: "debug", label: "Debug" }, - { key: "info", label: "Info" }, - { key: "warn", label: "Warn" }, - { key: "error", label: "Error" }, - { key: "log", label: "Log" }, - ] as const - - // Use a fixed standard aspect ratio and dimensions for the drawer to prevent flickering - // Even if viewport changes, the drawer maintains consistent size - const fixedDrawerWidth = 900 - const fixedDrawerHeight = 600 - const drawerAspectRatio = (fixedDrawerHeight / fixedDrawerWidth) * 100 - - // For cursor positioning, use the viewport dimensions from the same page as the data we're displaying - // This ensures cursor position matches the screenshot/mouse position being shown - let cursorViewportWidth: number - let cursorViewportHeight: number - - if (currentPage?.screenshot) { - // Current page has screenshot - use its dimensions - cursorViewportWidth = currentPage.viewportWidth ?? viewportWidth - cursorViewportHeight = currentPage.viewportHeight ?? viewportHeight - } else if (lastPageWithScreenshot) { - // Using placeholder screenshot - use dimensions from that page - cursorViewportWidth = lastPageWithScreenshot.viewportWidth ?? viewportWidth - cursorViewportHeight = lastPageWithScreenshot.viewportHeight ?? viewportHeight - } else { - // No screenshot available - use default settings - cursorViewportWidth = viewportWidth - cursorViewportHeight = viewportHeight - } - - // Get browser action for current page (now stored in pages array) - const currentPageAction = useMemo(() => { - return pages[currentPageIndex]?.action - }, [pages, currentPageIndex]) - - // Latest non-close browser_action for header summary (fallback) - - const lastBrowserActionOverall = useMemo(() => { - const all = messages.filter((m) => m.say === "browser_action") - return all.at(-1) - }, [messages]) - - // Use actual Playwright session state from extension (not message parsing) - const isBrowserSessionOpen = isBrowserSessionActive - - // Check if a browser action is currently in flight (for spinner) - const isActionRunning = useMemo(() => { - if (!lastBrowserActionOverall || isLastApiReqInterrupted) { - return false - } - - // Find the last browser_action_result (including empty text) to detect completion - const lastBrowserActionResult = [...messages].reverse().find((m) => m.say === "browser_action_result") - - if (!lastBrowserActionResult) { - // We have at least one action, but haven't seen any result yet - return true - } - - // If the last action happened after the last result, it's still running - return lastBrowserActionOverall.ts > lastBrowserActionResult.ts - }, [messages, lastBrowserActionOverall, isLastApiReqInterrupted]) - - // Browser session drawer never auto-expands - user must manually toggle it - - // Calculate total API cost for the browser session - const totalApiCost = useMemo(() => { - let total = 0 - messages.forEach((message) => { - if (message.say === "api_req_started" && message.text) { - try { - const data = JSON.parse(message.text) - if (data.cost && typeof data.cost === "number") { - total += data.cost - } - } catch { - // Ignore parsing errors - } - } - }) - return total - }, [messages]) - - // Local size tracking without react-use to avoid timers after unmount in tests - const containerRef = useRef(null) - const [rowHeight, setRowHeight] = useState(0) - useEffect(() => { - const el = containerRef.current - if (!el) return - let mounted = true - const setH = (h: number) => { - if (mounted) setRowHeight(h) - } - const ro = - typeof window !== "undefined" && "ResizeObserver" in window - ? new ResizeObserver((entries) => { - const entry = entries[0] - setH(entry?.contentRect?.height ?? el.getBoundingClientRect().height) - }) - : null - // initial - setH(el.getBoundingClientRect().height) - if (ro) ro.observe(el) - return () => { - mounted = false - if (ro) ro.disconnect() - } - }, []) - - const BrowserSessionHeader: React.FC = () => ( -
- {/* Globe icon - green when browser session is open */} - - setNextActionsExpanded((v) => { - const nv = !v - onExpandChange?.(nv) - return nv - }), - })} - /> - - {/* Simple text: "Browser Session" with step counter */} - - setNextActionsExpanded((v) => { - const nv = !v - onExpandChange?.(nv) - return nv - }), - })} - style={{ - flex: 1, - fontSize: 13, - fontWeight: 500, - lineHeight: "22px", - color: "var(--vscode-editor-foreground)", - cursor: fullScreen ? "default" : "pointer", - display: "flex", - alignItems: "center", - gap: 8, - }}> - {t("chat:browser.session")} - {isActionRunning && ( - - )} - {pages.length > 0 && ( - - {currentPageIndex + 1}/{pages.length} - - )} - {/* Inline action summary to the right, similar to ChatView */} - - {(() => { - const action = currentPageAction - const pageSize = pages[currentPageIndex]?.size - const pageViewportWidth = pages[currentPageIndex]?.viewportWidth - const pageViewportHeight = pages[currentPageIndex]?.viewportHeight - if (action) { - return ( - <> - {getActionIcon(action.action)} - - {getBrowserActionText( - t, - action.action, - action.executedCoordinate, - action.coordinate, - action.text, - pageSize, - pageViewportWidth, - pageViewportHeight, - )} - - - ) - } else if (initialUrl) { - return ( - <> - {getActionIcon("launch" as any)} - {getBrowserActionText(t, "launch", undefined, initialUrl, undefined)} - - ) - } - return null - })()} - - - - {/* Right side: cost badge and chevron */} - {totalApiCost > 0 && ( -
- ${totalApiCost.toFixed(4)} -
- )} - - {/* Chevron toggle hidden in fullScreen */} - {!fullScreen && ( - - setNextActionsExpanded((v) => { - const nv = !v - onExpandChange?.(nv) - return nv - }) - } - className={`codicon ${nextActionsExpanded ? "codicon-chevron-up" : "codicon-chevron-down"}`} - style={{ - fontSize: 13, - fontWeight: 500, - lineHeight: "22px", - color: "var(--vscode-editor-foreground)", - cursor: "pointer", - display: "inline-block", - transition: "transform 150ms ease", - }} - /> - )} - - {/* Kill browser button hidden from header in fullScreen; kept in toolbar */} - {isBrowserSessionOpen && !fullScreen && ( - - - - )} -
- ) - - const BrowserSessionDrawer: React.FC = () => { - if (!nextActionsExpanded) return null - - return ( -
- {/* Browser-like Toolbar */} -
- {/* Go to beginning */} - - - - - {/* Back */} - - - - - {/* Forward */} - - - - - {/* Go to end */} - - - - - {/* Address Bar */} -
- - - {displayState.url || "about:blank"} - - {/* Step counter removed */} -
- - {/* Kill (Disconnect) replaces Reload */} - - - - - {/* Open External */} - - - - - {/* Copy URL */} - - - -
- {/* Screenshot Area */} -
- {displayState.screenshot ? ( - {t("chat:browser.screenshot")} - vscode.postMessage({ - type: "openImage", - text: displayState.screenshot, - }) - } - /> - ) : ( -
- -
- )} - {displayState.mousePosition && - (() => { - // Use measured size if available; otherwise fall back to current client size so cursor remains visible - const containerW = sW || (screenshotRef.current?.clientWidth ?? 0) - const containerH = sH || (screenshotRef.current?.clientHeight ?? 0) - if (containerW <= 0 || containerH <= 0) { - // Minimal fallback to keep cursor visible before first measurement - return ( - - ) - } - - // Compute displayed image box within the container for object-fit: contain; objectPosition: top center - const imgAspect = cursorViewportWidth / cursorViewportHeight - const containerAspect = containerW / containerH - let displayW = containerW - let displayH = containerH - let offsetX = 0 - let offsetY = 0 - if (containerAspect > imgAspect) { - // Full height, letterboxed left/right; top aligned - displayH = containerH - displayW = containerH * imgAspect - offsetX = (containerW - displayW) / 2 - offsetY = 0 - } else { - // Full width, potential space below; top aligned - displayW = containerW - displayH = containerW / imgAspect - offsetX = 0 - offsetY = 0 - } - - // Parse "x,y" or "x,y@widthxheight" for original basis - const m = /^\s*(\d+)\s*,\s*(\d+)(?:\s*@\s*(\d+)\s*[x,]\s*(\d+))?\s*$/.exec( - displayState.mousePosition || "", - ) - const mx = parseInt(m?.[1] || "0", 10) - const my = parseInt(m?.[2] || "0", 10) - const baseW = m?.[3] ? parseInt(m[3], 10) : cursorViewportWidth - const baseH = m?.[4] ? parseInt(m[4], 10) : cursorViewportHeight - - const leftPx = offsetX + (baseW > 0 ? (mx / baseW) * displayW : 0) - const topPx = offsetY + (baseH > 0 ? (my / baseH) * displayH : 0) - - return ( - - ) - })()} -
- - {/* Browser Action summary moved inline to header; row removed */} - - {/* Console Logs Section (collapsible, default collapsed) */} -
-
{ - e.stopPropagation() - setConsoleLogsExpanded((v) => !v) - }} - className="text-vscode-editor-foreground/70 hover:text-vscode-editor-foreground transition-colors" - style={{ - display: "flex", - alignItems: "center", - gap: "8px", - marginBottom: consoleLogsExpanded ? "6px" : 0, - cursor: "pointer", - }}> - - - {t("chat:browser.consoleLogs")} - - - {/* Log type indicators */} -
e.stopPropagation()} - style={{ display: "flex", alignItems: "center", gap: 6, marginLeft: "auto" }}> - {logTypeMeta.map(({ key, label }) => { - const isAll = key === "all" - const count = isAll - ? (Object.values(parsedLogs.counts) as number[]).reduce((a, b) => a + b, 0) - : parsedLogs.counts[key as "debug" | "info" | "warn" | "error" | "log"] - const isActive = logFilter === (key as any) - const disabled = count === 0 - return ( - - ) - })} - setConsoleLogsExpanded((v) => !v)} - className={`codicon codicon-chevron-${consoleLogsExpanded ? "down" : "right"}`} - style={{ marginLeft: 6 }} - /> -
-
- {consoleLogsExpanded && ( -
- -
- )} -
-
- ) - } - - const browserSessionRow = ( -
- - - {/* Expanded drawer content - inline/fullscreen */} - -
- ) - - // Height change effect - useEffect(() => { - const isInitialRender = prevHeightRef.current === 0 - if (isLast && rowHeight !== 0 && rowHeight !== Infinity && rowHeight !== prevHeightRef.current) { - if (!isInitialRender) { - onHeightChange?.(rowHeight > prevHeightRef.current) - } - prevHeightRef.current = rowHeight - } - }, [rowHeight, isLast, onHeightChange]) - - return browserSessionRow -}, deepEqual) - -const BrowserCursor: React.FC<{ style?: React.CSSProperties }> = ({ style }) => { - const { t } = useTranslation() - // (can't use svgs in vsc extensions) - const cursorBase64 = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAAAYCAYAAAAVibZIAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAFaADAAQAAAABAAAAGAAAAADwi9a/AAADGElEQVQ4EZ2VbUiTURTH772be/PxZdsz3cZwC4RVaB8SAjMpxQwSWZbQG/TFkN7oW1Df+h6IRV9C+hCpKUSIZUXOfGM5tAKViijFFEyfZ7Ol29S1Pbdzl8Uw9+aBu91zzv3/nt17zt2DEZjBYOAkKrtFMXIghAWM8U2vMN/FctsxGRMpM7NbEEYNMM2CYUSInlJx3OpawO9i+XSNQYkmk2uFb9njzkcfVSr1p/GJiQKMULVaw2WuBv296UKRxWJR6wxGCmM1EAhSNppv33GBH9qI32cPTAtss9lUm6EM3N7R+RbigT+5/CeosFCZKpjEW+iorS1pb30wDUXzQfHqtD/9L3ieZ2ee1OJCmbL8QHnRs+4uj0wmW4QzrpCwvJ8zGg3JqAmhTLynuLiwv8/5KyND8Q3cEkUEDWu15oJE4KRQJt5hs1rcriGNRqP+DK4dyyWXXm/aFQ+cEpSJ8/LyDGPuEZNOmzsOroUSOqzXG/dtBU4ZysTZYKNut91sNo2Cq6cE9enz86s2g9OCMrFSqVC5hgb32u072W3jKMU90Hb1seC0oUwsB+t92bO/rKx0EFGkgFCnjjc1/gVvC8rE0L+4o63t4InjxwbAJQjTe3qD8QrLkXA4DC24fWtuajp06cLFYSBIFKGmXKPRRmAnME9sPt+yLwIWb9WN69fKoTneQz4Dh2mpPNkvfeV0jjecb9wNAkwIEVQq5VJOds4Kb+DXoAsiVquVwI1Dougpij6UyGYx+5cKroeDEFibm5lWRRMbH1+npmYrq6qhwlQHIbajZEf1fElcqGGFpGg9HMuKzpfBjhytCTMgkJ56RX09zy/ysENTBElmjIgJnmNChJqohDVQqpEfwkILE8v/o0GAnV9F1eEvofVQCbiTBEXOIPQh5PGgefDZeAcjrpGZjULBr/m3tZOnz7oEQWRAQZLjWlEU/XEJWySiILgRc5Cz1DkcAyuBFcnpfF0JiXWKpcolQXizhS5hKAqFpr0MVbgbuxJ6+5xX+P4wNpbqPPrugZfbmIbLmgQR3Aw8QSi66hUXulOFbF73GxqjE5BNXWNeAAAAAElFTkSuQmCC" - - return ( - {t("chat:browser.cursor")} - ) -} - -export default BrowserSessionRow diff --git a/webview-ui/src/components/chat/BrowserSessionStatusRow.tsx b/webview-ui/src/components/chat/BrowserSessionStatusRow.tsx deleted file mode 100644 index 862dc80a62f..00000000000 --- a/webview-ui/src/components/chat/BrowserSessionStatusRow.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { memo } from "react" -import { Globe } from "lucide-react" -import { ClineMessage } from "@roo-code/types" - -interface BrowserSessionStatusRowProps { - message: ClineMessage -} - -const BrowserSessionStatusRow = memo(({ message }: BrowserSessionStatusRowProps) => { - const isOpened = message.text?.includes("opened") - - return ( -
- - - {message.text} - -
- ) -}) - -BrowserSessionStatusRow.displayName = "BrowserSessionStatusRow" - -export default BrowserSessionStatusRow diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 502cd4d82aa..63023f26512 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1558,10 +1558,6 @@ export const ChatRowContent = ({ ) - case "browser_action": - case "browser_action_result": - // Handled by BrowserSessionRow; prevent raw JSON (action/result) from rendering here - return null case "too_many_tools_warning": { const warningData = safeJsonParse<{ toolCount: number diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 4c0b2bbfd08..c5213882068 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -52,9 +52,6 @@ interface ChatTextAreaProps { // Edit mode props isEditMode?: boolean onCancel?: () => void - // Browser session status - isBrowserSessionActive?: boolean - showBrowserDockToggle?: boolean // Stop/Queue functionality isStreaming?: boolean onStop?: () => void @@ -79,8 +76,6 @@ export const ChatTextArea = forwardRef( modeShortcutText, isEditMode = false, onCancel, - isBrowserSessionActive = false, - showBrowserDockToggle = false, isStreaming = false, onStop, onEnqueueMessage, @@ -1354,12 +1349,6 @@ export const ChatTextArea = forwardRef( )} {!isEditMode ? : null} {!isEditMode && cloudUserInfo && } - {/* keep props referenced after moving browser button */} -
diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index d013ffbe16e..6d82071512f 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -38,8 +38,6 @@ import TelemetryBanner from "../common/TelemetryBanner" import VersionIndicator from "../common/VersionIndicator" import HistoryPreview from "../history/HistoryPreview" import Announcement from "./Announcement" -import BrowserActionRow from "./BrowserActionRow" -import BrowserSessionStatusRow from "./BrowserSessionStatusRow" import ChatRow from "./ChatRow" import WarningRow from "./WarningRow" import { ChatTextArea } from "./ChatTextArea" @@ -95,7 +93,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction 0)) { @@ -1179,43 +1164,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - for (let i = 0; i < messages.length; i++) { - if (messages[i].ask === "browser_action_launch") { - return i - } - } - return -1 - }, [messages]) - - const _browserSessionMessages = useMemo(() => { - if (browserSessionStartIndex === -1) return [] - return messages.slice(browserSessionStartIndex) - }, [browserSessionStartIndex, messages]) - - // Show globe toggle only when in a task that has a browser session (active or inactive) - const showBrowserDockToggle = useMemo( - () => Boolean(task && (browserSessionStartIndex !== -1 || isBrowserSessionActive)), - [task, browserSessionStartIndex, isBrowserSessionActive], - ) - - const isBrowserSessionMessage = useCallback((message: ClineMessage): boolean => { - // Only the launch ask should be hidden from chat (it's shown in the drawer header) - if (message.type === "ask" && message.ask === "browser_action_launch") { - return true - } - // browser_action_result messages are paired with browser_action and should not appear independently - if (message.type === "say" && message.say === "browser_action_result") { - return true - } - return false - }, []) - const groupedMessages = useMemo(() => { - // Only filter out the launch ask and result messages - browser actions appear in chat - const filtered: ClineMessage[] = visibleMessages.filter((msg) => !isBrowserSessionMessage(msg)) + const filtered: ClineMessage[] = visibleMessages // Helper to check if a message is a read_file ask that should be batched const isReadFileAsk = (msg: ClineMessage): boolean => { @@ -1361,7 +1311,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction { const hasCheckpoint = modifiedMessages.some((message) => message.say === "checkpoint_saved") - // Check if this is a browser action message - if (messageOrGroup.type === "say" && messageOrGroup.say === "browser_action") { - // Find the corresponding result message by looking for the next browser_action_result after this action's timestamp - const nextMessage = modifiedMessages.find( - (m) => m.ts > messageOrGroup.ts && m.say === "browser_action_result", - ) - - // Calculate action index and total count - const browserActions = modifiedMessages.filter((m) => m.say === "browser_action") - const actionIndex = browserActions.findIndex((m) => m.ts === messageOrGroup.ts) + 1 - const totalActions = browserActions.length - - return ( - - ) - } - - // Check if this is a browser session status message - if (messageOrGroup.type === "say" && messageOrGroup.say === "browser_session_status") { - return - } - // regular message return ( { const { t } = useTranslation() - const { apiConfiguration, currentTaskItem, clineMessages, isBrowserSessionActive } = useExtensionState() + const { apiConfiguration, currentTaskItem, clineMessages } = useExtensionState() const { id: modelId, info: model } = useSelectedModel(apiConfiguration) const [isTaskExpanded, setIsTaskExpanded] = useState(false) const [showLongRunningTaskMessage, setShowLongRunningTaskMessage] = useState(false) @@ -118,18 +110,6 @@ const TaskHeader = ({ ) const reservedForOutput = maxTokens || 0 - // Detect if this task had any browser session activity so we can show a grey globe when inactive - const browserSessionStartIndex = useMemo(() => { - const msgs = clineMessages || [] - for (let i = 0; i < msgs.length; i++) { - const m = msgs[i] as any - if (m?.ask === "browser_action_launch") return i - } - return -1 - }, [clineMessages]) - - const showBrowserGlobe = browserSessionStartIndex !== -1 || !!isBrowserSessionActive - const condenseButton = ( )} - {showBrowserGlobe && ( -
e.stopPropagation()}> - - - - {isBrowserSessionActive && ( - - {t("chat:browser.active")} - - )} -
- )} )} {/* Expanded state: Show task text and images */} diff --git a/webview-ui/src/components/chat/__tests__/BrowserSessionRow.aspect-ratio.spec.tsx b/webview-ui/src/components/chat/__tests__/BrowserSessionRow.aspect-ratio.spec.tsx deleted file mode 100644 index 87465862032..00000000000 --- a/webview-ui/src/components/chat/__tests__/BrowserSessionRow.aspect-ratio.spec.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { render, screen, fireEvent } from "@testing-library/react" -import React from "react" -import BrowserSessionRow from "../BrowserSessionRow" -import { ExtensionStateContext } from "@src/context/ExtensionStateContext" -import { TooltipProvider } from "@src/components/ui/tooltip" - -describe("BrowserSessionRow - screenshot area", () => { - const renderRow = (messages: any[]) => { - const mockExtState: any = { - // Ensure known viewport so expected aspect ratio is deterministic (600/900 = 66.67%) - browserViewportSize: "900x600", - isBrowserSessionActive: false, - } - - return render( - - - true} - onToggleExpand={() => {}} - lastModifiedMessage={undefined as any} - isLast={true} - onHeightChange={() => {}} - isStreaming={false} - /> - - , - ) - } - - it("reserves height while screenshot is loading (no layout collapse)", () => { - // Only a launch action, no corresponding browser_action_result yet (no screenshot) - const messages = [ - { - ts: 1, - say: "browser_action", - text: JSON.stringify({ action: "launch", url: "http://localhost:3000" }), - }, - ] - - renderRow(messages) - - // Open the browser session drawer - const globe = screen.getByLabelText("Browser interaction") - fireEvent.click(globe) - - const container = screen.getByTestId("screenshot-container") as HTMLDivElement - // padding-bottom should reflect aspect ratio (600/900 * 100) even without an image - const pb = parseFloat(container.style.paddingBottom || "0") - expect(pb).toBeGreaterThan(0) - // Be tolerant of rounding - expect(Math.round(pb)).toBe(67) - }) -}) diff --git a/webview-ui/src/components/chat/__tests__/BrowserSessionRow.disconnect-button.spec.tsx b/webview-ui/src/components/chat/__tests__/BrowserSessionRow.disconnect-button.spec.tsx deleted file mode 100644 index 0c2b4762c4e..00000000000 --- a/webview-ui/src/components/chat/__tests__/BrowserSessionRow.disconnect-button.spec.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react" -import { render, screen } from "@testing-library/react" -import BrowserSessionRow from "../BrowserSessionRow" -import { ExtensionStateContext } from "@src/context/ExtensionStateContext" -import { TooltipProvider } from "@radix-ui/react-tooltip" - -describe("BrowserSessionRow - Disconnect session button", () => { - const renderRow = (isActive: boolean) => { - const mockExtState: any = { - browserViewportSize: "900x600", - isBrowserSessionActive: isActive, - } - - return render( - - - false} - onToggleExpand={() => {}} - lastModifiedMessage={undefined as any} - isLast={true} - onHeightChange={() => {}} - isStreaming={false} - /> - - , - ) - } - - it("shows the Disconnect session button when a session is active", () => { - renderRow(true) - const btn = screen.getByLabelText("Disconnect session") - expect(btn).toBeInTheDocument() - }) - - it("does not render the button when no session is active", () => { - renderRow(false) - const btn = screen.queryByLabelText("Disconnect session") - expect(btn).toBeNull() - }) -}) diff --git a/webview-ui/src/components/chat/__tests__/BrowserSessionRow.spec.tsx b/webview-ui/src/components/chat/__tests__/BrowserSessionRow.spec.tsx deleted file mode 100644 index 684145f2556..00000000000 --- a/webview-ui/src/components/chat/__tests__/BrowserSessionRow.spec.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from "react" -import { describe, it, expect, vi } from "vitest" -import { render, screen } from "@testing-library/react" - -import BrowserSessionRow from "../BrowserSessionRow" - -// Mock ExtensionStateContext so BrowserSessionRow falls back to props -vi.mock("@src/context/ExtensionStateContext", () => ({ - useExtensionState: () => { - throw new Error("No ExtensionStateContext in test environment") - }, -})) - -// Simplify i18n usage and provide initReactI18next for i18n setup -vi.mock("react-i18next", () => ({ - useTranslation: () => ({ - t: (key: string) => key, - }), - initReactI18next: { - type: "3rdParty", - init: () => {}, - }, -})) - -// Replace ProgressIndicator with a simple test marker -vi.mock("../ProgressIndicator", () => ({ - ProgressIndicator: () =>
, -})) - -const baseProps = { - isExpanded: () => false, - onToggleExpand: () => {}, - lastModifiedMessage: undefined, - isLast: true, - onHeightChange: () => {}, - isStreaming: false, -} - -describe("BrowserSessionRow - action spinner", () => { - it("does not show spinner when there are no browser actions", () => { - const messages = [ - { - type: "say", - say: "task", - ts: 1, - text: "Task started", - } as any, - ] - - render() - - expect(screen.queryByTestId("browser-session-spinner")).toBeNull() - }) - - it("shows spinner while the latest browser action is still running", () => { - const messages = [ - { - type: "say", - say: "task", - ts: 1, - text: "Task started", - } as any, - { - type: "say", - say: "browser_action", - ts: 2, - text: JSON.stringify({ action: "click" }), - } as any, - { - type: "say", - say: "browser_action_result", - ts: 3, - text: JSON.stringify({ currentUrl: "https://example.com" }), - } as any, - { - type: "say", - say: "browser_action", - ts: 4, - text: JSON.stringify({ action: "scroll_down" }), - } as any, - ] - - render() - - expect(screen.getByTestId("browser-session-spinner")).toBeInTheDocument() - }) - - it("hides spinner once the latest browser action has a result", () => { - const messages = [ - { - type: "say", - say: "task", - ts: 1, - text: "Task started", - } as any, - { - type: "say", - say: "browser_action", - ts: 2, - text: JSON.stringify({ action: "click" }), - } as any, - { - type: "say", - say: "browser_action_result", - ts: 3, - text: JSON.stringify({ currentUrl: "https://example.com" }), - } as any, - { - type: "say", - say: "browser_action", - ts: 4, - text: JSON.stringify({ action: "scroll_down" }), - } as any, - { - type: "say", - say: "browser_action_result", - ts: 5, - text: JSON.stringify({ currentUrl: "https://example.com/page2" }), - } as any, - ] - - render() - - expect(screen.queryByTestId("browser-session-spinner")).toBeNull() - }) -}) diff --git a/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx index 96efb006734..78dcce08ae7 100644 --- a/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatView.keyboard-fix.spec.tsx @@ -24,10 +24,6 @@ vi.mock("use-sound", () => ({ })) // Mock components -vi.mock("../BrowserSessionRow", () => ({ - default: () => null, -})) - vi.mock("../ChatRow", () => ({ default: () => null, })) diff --git a/webview-ui/src/components/chat/__tests__/ChatView.notification-sound.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.notification-sound.spec.tsx index eb3b5df76b0..4c4d70f716e 100644 --- a/webview-ui/src/components/chat/__tests__/ChatView.notification-sound.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatView.notification-sound.spec.tsx @@ -49,12 +49,6 @@ vi.mock("use-sound", () => ({ })) // Mock components that use ESM dependencies -vi.mock("../BrowserSessionRow", () => ({ - default: function MockBrowserSessionRow({ messages }: { messages: ClineMessage[] }) { - return
{JSON.stringify(messages)}
- }, -})) - vi.mock("../ChatRow", () => ({ default: function MockChatRow({ message }: { message: ClineMessage }) { return
{JSON.stringify(message)}
diff --git a/webview-ui/src/components/chat/__tests__/ChatView.preserve-images.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.preserve-images.spec.tsx index 4ed1126ded3..a167c09c052 100644 --- a/webview-ui/src/components/chat/__tests__/ChatView.preserve-images.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatView.preserve-images.spec.tsx @@ -44,12 +44,6 @@ vi.mock("use-sound", () => ({ })) // Mock components that use ESM dependencies -vi.mock("../BrowserSessionRow", () => ({ - default: function MockBrowserSessionRow({ messages }: { messages: ClineMessage[] }) { - return
{JSON.stringify(messages)}
- }, -})) - vi.mock("../ChatRow", () => ({ default: function MockChatRow({ message }: { message: ClineMessage }) { return
{JSON.stringify(message)}
diff --git a/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx b/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx index 1026ac86d09..63e71c9bd1d 100644 --- a/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/ChatView.spec.tsx @@ -45,12 +45,6 @@ vi.mock("use-sound", () => ({ })) // Mock components that use ESM dependencies -vi.mock("../BrowserSessionRow", () => ({ - default: function MockBrowserSessionRow({ messages }: { messages: ClineMessage[] }) { - return
{JSON.stringify(messages)}
- }, -})) - vi.mock("../ChatRow", () => ({ default: function MockChatRow({ message }: { message: ClineMessage }) { return
{JSON.stringify(message)}
diff --git a/webview-ui/src/components/settings/AutoApproveSettings.tsx b/webview-ui/src/components/settings/AutoApproveSettings.tsx index daf3d7d64d4..40e1658f5fd 100644 --- a/webview-ui/src/components/settings/AutoApproveSettings.tsx +++ b/webview-ui/src/components/settings/AutoApproveSettings.tsx @@ -24,7 +24,6 @@ type AutoApproveSettingsProps = HTMLAttributes & { alwaysAllowWrite?: boolean alwaysAllowWriteOutsideWorkspace?: boolean alwaysAllowWriteProtected?: boolean - alwaysAllowBrowser?: boolean alwaysAllowMcp?: boolean alwaysAllowModeSwitch?: boolean alwaysAllowSubtasks?: boolean @@ -41,7 +40,6 @@ type AutoApproveSettingsProps = HTMLAttributes & { | "alwaysAllowWrite" | "alwaysAllowWriteOutsideWorkspace" | "alwaysAllowWriteProtected" - | "alwaysAllowBrowser" | "alwaysAllowMcp" | "alwaysAllowModeSwitch" | "alwaysAllowSubtasks" @@ -61,7 +59,6 @@ export const AutoApproveSettings = ({ alwaysAllowWrite, alwaysAllowWriteOutsideWorkspace, alwaysAllowWriteProtected, - alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, alwaysAllowSubtasks, @@ -155,7 +152,6 @@ export const AutoApproveSettings = ({ & { - browserToolEnabled?: boolean - browserViewportSize?: string - screenshotQuality?: number - remoteBrowserHost?: string - remoteBrowserEnabled?: boolean - setCachedStateField: SetCachedStateField< - | "browserToolEnabled" - | "browserViewportSize" - | "screenshotQuality" - | "remoteBrowserHost" - | "remoteBrowserEnabled" - > -} - -export const BrowserSettings = ({ - browserToolEnabled, - browserViewportSize, - screenshotQuality, - remoteBrowserHost, - remoteBrowserEnabled, - setCachedStateField, - ...props -}: BrowserSettingsProps) => { - const { t } = useAppTranslation() - - const [testingConnection, setTestingConnection] = useState(false) - const [testResult, setTestResult] = useState<{ success: boolean; text: string } | null>(null) - const [discovering, setDiscovering] = useState(false) - - // We don't need a local state for useRemoteBrowser since we're using the - // `enableRemoteBrowser` prop directly. This ensures the checkbox always - // reflects the current global state. - - // Set up message listener for browser connection results. - useEffect(() => { - const handleMessage = (event: MessageEvent) => { - const message = event.data - - if (message.type === "browserConnectionResult") { - setTestResult({ success: message.success, text: message.text }) - setTestingConnection(false) - setDiscovering(false) - } - } - - window.addEventListener("message", handleMessage) - - return () => { - window.removeEventListener("message", handleMessage) - } - }, []) - - const testConnection = async () => { - setTestingConnection(true) - setTestResult(null) - - try { - // Send a message to the extension to test the connection. - vscode.postMessage({ type: "testBrowserConnection", text: remoteBrowserHost }) - } catch (error) { - setTestResult({ - success: false, - text: `Error: ${error instanceof Error ? error.message : String(error)}`, - }) - setTestingConnection(false) - } - } - - const options = useMemo( - () => [ - { - value: "1280x800", - label: t("settings:browser.viewport.options.largeDesktop"), - }, - { - value: "900x600", - label: t("settings:browser.viewport.options.smallDesktop"), - }, - { value: "768x1024", label: t("settings:browser.viewport.options.tablet") }, - { value: "360x640", label: t("settings:browser.viewport.options.mobile") }, - ], - [t], - ) - - return ( -
- {t("settings:sections.browser")} - -
- - setCachedStateField("browserToolEnabled", e.target.checked)}> - {t("settings:browser.enable.label")} - -
- - - {" "} - - -
-
- - {browserToolEnabled && ( -
- - - -
- {t("settings:browser.viewport.description")} -
-
- - - -
- setCachedStateField("screenshotQuality", value)} - /> - {screenshotQuality ?? 75}% -
-
- {t("settings:browser.screenshotQuality.description")} -
-
- - - { - // Update the global state - remoteBrowserEnabled now means "enable remote browser connection". - setCachedStateField("remoteBrowserEnabled", e.target.checked) - - if (!e.target.checked) { - // If disabling remote browser, clear the custom URL. - setCachedStateField("remoteBrowserHost", undefined) - } - }}> - - -
- {t("settings:browser.remote.description")} -
-
- - {remoteBrowserEnabled && ( - <> -
- - setCachedStateField("remoteBrowserHost", e.target.value || undefined) - } - placeholder={t("settings:browser.remote.urlPlaceholder")} - style={{ flexGrow: 1 }} - /> - -
- {testResult && ( -
- {testResult.text} -
- )} -
- {t("settings:browser.remote.instructions")} -
- - )} -
- )} -
-
- ) -} diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index eb57b4e0009..47e087615e3 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -11,7 +11,6 @@ import React, { } from "react" import { CheckCheck, - SquareMousePointer, GitBranch, Bell, Database, @@ -67,7 +66,6 @@ import { SectionHeader } from "./SectionHeader" import ApiConfigManager from "./ApiConfigManager" import ApiOptions from "./ApiOptions" import { AutoApproveSettings } from "./AutoApproveSettings" -import { BrowserSettings } from "./BrowserSettings" import { CheckpointSettings } from "./CheckpointSettings" import { NotificationSettings } from "./NotificationSettings" import { ContextManagementSettings } from "./ContextManagementSettings" @@ -102,7 +100,6 @@ export const sectionNames = [ "autoApprove", "slashCommands", "skills", - "browser", "checkpoints", "notifications", "contextManagement", @@ -157,7 +154,6 @@ const SettingsView = forwardRef(({ onDone, t allowedMaxRequests, allowedMaxCost, language, - alwaysAllowBrowser, alwaysAllowExecute, alwaysAllowMcp, alwaysAllowModeSwitch, @@ -167,16 +163,12 @@ const SettingsView = forwardRef(({ onDone, t alwaysAllowWriteProtected, autoCondenseContext, autoCondenseContextPercent, - browserToolEnabled, - browserViewportSize, enableCheckpoints, checkpointTimeout, experiments, maxOpenTabsContext, maxWorkspaceFiles, mcpEnabled, - remoteBrowserHost, - screenshotQuality, soundEnabled, ttsEnabled, ttsSpeed, @@ -194,7 +186,6 @@ const SettingsView = forwardRef(({ onDone, t writeDelayMs, showRooIgnoredFiles, enableSubfolderRules, - remoteBrowserEnabled, maxImageFileSize, maxTotalImageSize, customSupportPrompts, @@ -379,7 +370,6 @@ const SettingsView = forwardRef(({ onDone, t alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? undefined, alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? undefined, alwaysAllowExecute: alwaysAllowExecute ?? undefined, - alwaysAllowBrowser: alwaysAllowBrowser ?? undefined, alwaysAllowMcp, alwaysAllowModeSwitch, allowedCommands: allowedCommands ?? [], @@ -391,18 +381,13 @@ const SettingsView = forwardRef(({ onDone, t allowedMaxCost: allowedMaxCost ?? null, autoCondenseContext, autoCondenseContextPercent, - browserToolEnabled: browserToolEnabled ?? true, soundEnabled: soundEnabled ?? true, soundVolume: soundVolume ?? 0.5, ttsEnabled, ttsSpeed, enableCheckpoints: enableCheckpoints ?? false, checkpointTimeout: checkpointTimeout ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, - browserViewportSize: browserViewportSize ?? "900x600", - remoteBrowserHost: remoteBrowserEnabled ? remoteBrowserHost : undefined, - remoteBrowserEnabled: remoteBrowserEnabled ?? false, writeDelayMs, - screenshotQuality: screenshotQuality ?? 75, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? 30_000, terminalShellIntegrationDisabled, terminalCommandDelay, @@ -529,7 +514,6 @@ const SettingsView = forwardRef(({ onDone, t { id: "slashCommands", icon: SquareSlash }, { id: "autoApprove", icon: CheckCheck }, { id: "mcp", icon: Server }, - { id: "browser", icon: SquareMousePointer }, { id: "checkpoints", icon: GitCommitVertical }, { id: "notifications", icon: Bell }, { id: "contextManagement", icon: Database }, @@ -801,7 +785,6 @@ const SettingsView = forwardRef(({ onDone, t alwaysAllowWrite={alwaysAllowWrite} alwaysAllowWriteOutsideWorkspace={alwaysAllowWriteOutsideWorkspace} alwaysAllowWriteProtected={alwaysAllowWriteProtected} - alwaysAllowBrowser={alwaysAllowBrowser} alwaysAllowMcp={alwaysAllowMcp} alwaysAllowModeSwitch={alwaysAllowModeSwitch} alwaysAllowSubtasks={alwaysAllowSubtasks} @@ -822,18 +805,6 @@ const SettingsView = forwardRef(({ onDone, t {/* Skills Section */} {renderTab === "skills" && } - {/* Browser Section */} - {renderTab === "browser" && ( - - )} - {/* Checkpoints Section */} {renderTab === "checkpoints" && ( { const initialProps = { alwaysAllowReadOnly: true, alwaysAllowWrite: false, - alwaysAllowBrowser: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: true, alwaysAllowSubtasks: false, diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx index be725ea6a15..65c311ad54f 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx @@ -152,9 +152,6 @@ vi.mock("../Section", () => ({ })) // Mock all settings components -vi.mock("../BrowserSettings", () => ({ - BrowserSettings: () => null, -})) vi.mock("../CheckpointSettings", () => ({ CheckpointSettings: () => null, })) @@ -209,7 +206,6 @@ describe("SettingsView - Change Detection Fix", () => { allowedMaxRequests: undefined, allowedMaxCost: undefined, language: "en", - alwaysAllowBrowser: false, alwaysAllowExecute: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: false, @@ -219,14 +215,11 @@ describe("SettingsView - Change Detection Fix", () => { alwaysAllowWriteProtected: false, autoCondenseContext: false, autoCondenseContextPercent: 50, - browserToolEnabled: false, - browserViewportSize: "1280x720", enableCheckpoints: false, experiments: {}, maxOpenTabsContext: 10, maxWorkspaceFiles: 200, mcpEnabled: false, - remoteBrowserHost: "", screenshotQuality: 75, soundEnabled: false, ttsEnabled: false, @@ -245,7 +238,6 @@ describe("SettingsView - Change Detection Fix", () => { terminalZdotdir: false, writeDelayMs: 0, showRooIgnoredFiles: false, - remoteBrowserEnabled: false, maxReadFileLine: -1, maxImageFileSize: 5, maxTotalImageSize: 20, diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx index f1e13775504..83e2655237a 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.spec.tsx @@ -625,23 +625,6 @@ describe("SettingsView - Allowed Commands", () => { // Check that unsaved changes dialog is shown expect(screen.getByText("settings:unsavedChangesDialog.title")).toBeInTheDocument() }) - - it("renders with targetSection prop", () => { - // Render with a specific target section - render( - - - - - , - ) - - // Hydrate initial state - mockPostMessage({}) - - // Verify browser-related content is visible and API config is not - expect(screen.queryByTestId("api-config-management")).not.toBeInTheDocument() - }) }) }) diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx index 437404e0e7d..d2f55818201 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx @@ -149,9 +149,6 @@ vi.mock("../ApiOptions", () => ({ vi.mock("../AutoApproveSettings", () => ({ AutoApproveSettings: vi.fn(() =>
AutoApproveSettings
), })) -vi.mock("../BrowserSettings", () => ({ - BrowserSettings: vi.fn(() =>
BrowserSettings
), -})) vi.mock("../CheckpointSettings", () => ({ CheckpointSettings: vi.fn(() =>
CheckpointSettings
), })) @@ -214,7 +211,6 @@ describe("SettingsView - Unsaved Changes Detection", () => { allowedMaxRequests: undefined, allowedMaxCost: undefined, language: "en", - alwaysAllowBrowser: false, alwaysAllowExecute: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: false, @@ -224,14 +220,11 @@ describe("SettingsView - Unsaved Changes Detection", () => { alwaysAllowWriteProtected: false, autoCondenseContext: false, autoCondenseContextPercent: 50, - browserToolEnabled: false, - browserViewportSize: "1280x720", enableCheckpoints: false, experiments: {}, maxOpenTabsContext: 10, maxWorkspaceFiles: 200, mcpEnabled: false, - remoteBrowserHost: "", screenshotQuality: 75, soundEnabled: false, ttsEnabled: false, @@ -250,7 +243,6 @@ describe("SettingsView - Unsaved Changes Detection", () => { terminalZdotdir: false, writeDelayMs: 0, showRooIgnoredFiles: false, - remoteBrowserEnabled: false, maxReadFileLine: -1, maxImageFileSize: 5, maxTotalImageSize: 20, diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 85a750065ff..705dba6dd27 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -67,11 +67,9 @@ export interface ExtensionStateContextType extends ExtensionState { setAlwaysAllowWrite: (value: boolean) => void setAlwaysAllowWriteOutsideWorkspace: (value: boolean) => void setAlwaysAllowExecute: (value: boolean) => void - setAlwaysAllowBrowser: (value: boolean) => void setAlwaysAllowMcp: (value: boolean) => void setAlwaysAllowModeSwitch: (value: boolean) => void setAlwaysAllowSubtasks: (value: boolean) => void - setBrowserToolEnabled: (value: boolean) => void setShowRooIgnoredFiles: (value: boolean) => void setEnableSubfolderRules: (value: boolean) => void setShowAnnouncement: (value: boolean) => void @@ -92,7 +90,6 @@ export interface ExtensionStateContextType extends ExtensionState { setEnableCheckpoints: (value: boolean) => void checkpointTimeout: number setCheckpointTimeout: (value: number) => void - setBrowserViewportSize: (value: string) => void setWriteDelayMs: (value: number) => void screenshotQuality?: number setScreenshotQuality: (value: number) => void @@ -122,8 +119,6 @@ export interface ExtensionStateContextType extends ExtensionState { maxWorkspaceFiles: number setMaxWorkspaceFiles: (value: number) => void setTelemetrySetting: (value: TelemetrySetting) => void - remoteBrowserEnabled?: boolean - setRemoteBrowserEnabled: (value: boolean) => void awsUsePromptCache?: boolean setAwsUsePromptCache: (value: boolean) => void maxImageFileSize: number @@ -211,14 +206,12 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode deniedCommands: [], soundEnabled: false, soundVolume: 0.5, - isBrowserSessionActive: false, ttsEnabled: false, ttsSpeed: 1.0, enableCheckpoints: true, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Default to 15 seconds language: "en", // Default language code writeDelayMs: 1000, - browserViewportSize: "900x600", screenshotQuality: 75, terminalShellIntegrationTimeout: 4000, mcpEnabled: true, @@ -238,7 +231,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode maxOpenTabsContext: 20, maxWorkspaceFiles: 200, cwd: "", - browserToolEnabled: true, telemetrySetting: "unset", showRooIgnoredFiles: true, // Default to showing .rooignore'd files with lock symbol (current behavior). enableSubfolderRules: false, // Default to disabled - must be enabled to load rules from subdirectories @@ -538,7 +530,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setAlwaysAllowWriteOutsideWorkspace: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWriteOutsideWorkspace: value })), setAlwaysAllowExecute: (value) => setState((prevState) => ({ ...prevState, alwaysAllowExecute: value })), - setAlwaysAllowBrowser: (value) => setState((prevState) => ({ ...prevState, alwaysAllowBrowser: value })), setAlwaysAllowMcp: (value) => setState((prevState) => ({ ...prevState, alwaysAllowMcp: value })), setAlwaysAllowModeSwitch: (value) => setState((prevState) => ({ ...prevState, alwaysAllowModeSwitch: value })), setAlwaysAllowSubtasks: (value) => setState((prevState) => ({ ...prevState, alwaysAllowSubtasks: value })), @@ -556,8 +547,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setTtsSpeed: (value) => setState((prevState) => ({ ...prevState, ttsSpeed: value })), setEnableCheckpoints: (value) => setState((prevState) => ({ ...prevState, enableCheckpoints: value })), setCheckpointTimeout: (value) => setState((prevState) => ({ ...prevState, checkpointTimeout: value })), - setBrowserViewportSize: (value: string) => - setState((prevState) => ({ ...prevState, browserViewportSize: value })), setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })), setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })), setTerminalOutputPreviewSize: (value) => @@ -583,11 +572,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setCustomModes: (value) => setState((prevState) => ({ ...prevState, customModes: value })), setMaxOpenTabsContext: (value) => setState((prevState) => ({ ...prevState, maxOpenTabsContext: value })), setMaxWorkspaceFiles: (value) => setState((prevState) => ({ ...prevState, maxWorkspaceFiles: value })), - setBrowserToolEnabled: (value) => setState((prevState) => ({ ...prevState, browserToolEnabled: value })), setTelemetrySetting: (value) => setState((prevState) => ({ ...prevState, telemetrySetting: value })), setShowRooIgnoredFiles: (value) => setState((prevState) => ({ ...prevState, showRooIgnoredFiles: value })), setEnableSubfolderRules: (value) => setState((prevState) => ({ ...prevState, enableSubfolderRules: value })), - setRemoteBrowserEnabled: (value) => setState((prevState) => ({ ...prevState, remoteBrowserEnabled: value })), setAwsUsePromptCache: (value) => setState((prevState) => ({ ...prevState, awsUsePromptCache: value })), setMaxImageFileSize: (value) => setState((prevState) => ({ ...prevState, maxImageFileSize: value })), setMaxTotalImageSize: (value) => setState((prevState) => ({ ...prevState, maxTotalImageSize: value })), diff --git a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx index 41dabd7f0c2..89a92134ad8 100644 --- a/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx +++ b/webview-ui/src/context/__tests__/ExtensionStateContext.spec.tsx @@ -217,7 +217,6 @@ describe("mergeExtensionState", () => { remoteControlEnabled: false, taskSyncEnabled: false, featureRoomoteControlEnabled: false, - isBrowserSessionActive: false, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Add the checkpoint timeout property } @@ -288,7 +287,6 @@ describe("mergeExtensionState", () => { remoteControlEnabled: false, taskSyncEnabled: false, featureRoomoteControlEnabled: false, - isBrowserSessionActive: false, checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, } diff --git a/webview-ui/src/hooks/__tests__/useAutoApprovalState.spec.ts b/webview-ui/src/hooks/__tests__/useAutoApprovalState.spec.ts index b7fde38b700..50f02f807f9 100644 --- a/webview-ui/src/hooks/__tests__/useAutoApprovalState.spec.ts +++ b/webview-ui/src/hooks/__tests__/useAutoApprovalState.spec.ts @@ -8,7 +8,6 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: false, alwaysAllowWrite: false, alwaysAllowExecute: false, - alwaysAllowBrowser: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: false, alwaysAllowSubtasks: false, @@ -25,7 +24,6 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: undefined, alwaysAllowWrite: undefined, alwaysAllowExecute: undefined, - alwaysAllowBrowser: undefined, alwaysAllowMcp: undefined, alwaysAllowModeSwitch: undefined, alwaysAllowSubtasks: undefined, @@ -42,7 +40,6 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: true, alwaysAllowWrite: false, alwaysAllowExecute: false, - alwaysAllowBrowser: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: false, alwaysAllowSubtasks: false, @@ -59,7 +56,6 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: true, alwaysAllowWrite: true, alwaysAllowExecute: true, - alwaysAllowBrowser: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: false, alwaysAllowSubtasks: false, @@ -76,7 +72,6 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: true, alwaysAllowWrite: true, alwaysAllowExecute: true, - alwaysAllowBrowser: true, alwaysAllowMcp: true, alwaysAllowModeSwitch: true, alwaysAllowSubtasks: true, @@ -119,7 +114,6 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: false, alwaysAllowWrite: false, alwaysAllowExecute: false, - alwaysAllowBrowser: false, alwaysAllowMcp: false, alwaysAllowModeSwitch: false, alwaysAllowSubtasks: false, @@ -259,7 +253,7 @@ describe("useAutoApprovalState", () => { alwaysAllowReadOnly: 1 as any, // truthy non-boolean alwaysAllowWrite: "" as any, // falsy non-boolean alwaysAllowExecute: null as any, // falsy non-boolean - alwaysAllowBrowser: "yes" as any, // truthy non-boolean + alwaysAllowMcp: "yes" as any, // truthy non-boolean } const { result } = renderHook(() => useAutoApprovalState(toggles, true)) diff --git a/webview-ui/src/hooks/useAutoApprovalState.ts b/webview-ui/src/hooks/useAutoApprovalState.ts index 067a4a45e0e..32d5d9a6cf5 100644 --- a/webview-ui/src/hooks/useAutoApprovalState.ts +++ b/webview-ui/src/hooks/useAutoApprovalState.ts @@ -4,7 +4,6 @@ interface AutoApprovalToggles { alwaysAllowReadOnly?: boolean alwaysAllowWrite?: boolean alwaysAllowExecute?: boolean - alwaysAllowBrowser?: boolean alwaysAllowMcp?: boolean alwaysAllowModeSwitch?: boolean alwaysAllowSubtasks?: boolean diff --git a/webview-ui/src/hooks/useAutoApprovalToggles.ts b/webview-ui/src/hooks/useAutoApprovalToggles.ts index 9c3e1c689ae..0d21b58b7fd 100644 --- a/webview-ui/src/hooks/useAutoApprovalToggles.ts +++ b/webview-ui/src/hooks/useAutoApprovalToggles.ts @@ -10,7 +10,6 @@ export function useAutoApprovalToggles() { alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, - alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, alwaysAllowSubtasks, @@ -22,7 +21,6 @@ export function useAutoApprovalToggles() { alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, - alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, alwaysAllowSubtasks, @@ -32,7 +30,6 @@ export function useAutoApprovalToggles() { alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, - alwaysAllowBrowser, alwaysAllowMcp, alwaysAllowModeSwitch, alwaysAllowSubtasks, diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index cc9a215d898..d16a4466958 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -363,44 +363,6 @@ "total": "Cost total: ${{cost}}", "includesSubtasks": "Inclou els costos de les subtasques" }, - "browser": { - "session": "Sessió del navegador", - "active": "Actiu", - "rooWantsToUse": "Roo vol utilitzar el navegador", - "consoleLogs": "Registres de consola", - "noNewLogs": "(Cap registre nou)", - "screenshot": "Captura de pantalla del navegador", - "cursor": "cursor", - "navigation": { - "step": "Pas {{current}} de {{total}}", - "previous": "Anterior", - "next": "Següent" - }, - "sessionStarted": "Sessió de navegador iniciada", - "actions": { - "title": "Acció del navegador: ", - "launched": "Navegador iniciat", - "launch": "Iniciar navegador a {{url}}", - "clicked": "Fet clic ({{coordinate}})", - "click": "Clic ({{coordinate}})", - "typed": "Escrit: {{text}}", - "type": "Escriure \"{{text}}\"", - "pressed": "Premut: {{key}}", - "press": "Prem {{key}}", - "scrolledDown": "Desplaçat avall", - "scrollDown": "Desplaçar avall", - "scrolledUp": "Desplaçat amunt", - "scrollUp": "Desplaçar amunt", - "hovered": "Planat sobre ({{coordinate}})", - "hover": "Plana sobre ({{coordinate}})", - "resized": "Mida canviada a: {{size}}", - "resize": "Canvia la mida a {{size}}", - "screenshotSaved": "Captura de pantalla guardada", - "screenshot": "Guarda la captura de pantalla a {{path}}", - "closed": "Navegador tancat", - "close": "Tancar navegador" - } - }, "codeblock": { "tooltips": { "expand": "Expandir bloc de codi", diff --git a/webview-ui/src/i18n/locales/ca/prompts.json b/webview-ui/src/i18n/locales/ca/prompts.json index baef6053fe6..f5c9842e904 100644 --- a/webview-ui/src/i18n/locales/ca/prompts.json +++ b/webview-ui/src/i18n/locales/ca/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Llegir fitxers", "edit": "Editar fitxers", - "browser": "Utilitzar navegador", "command": "Executar comandes", "mcp": "Utilitzar MCP" }, diff --git a/webview-ui/src/i18n/locales/ca/settings.json b/webview-ui/src/i18n/locales/ca/settings.json index a04fd202cc4..f1e5ad10e6d 100644 --- a/webview-ui/src/i18n/locales/ca/settings.json +++ b/webview-ui/src/i18n/locales/ca/settings.json @@ -31,7 +31,6 @@ "mcp": "Servidors MCP", "worktrees": "Worktrees", "autoApprove": "Auto-aprovació", - "browser": "Accés a l'ordinador", "checkpoints": "Punts de control", "notifications": "Notificacions", "contextManagement": "Context", @@ -241,10 +240,6 @@ "description": "Permetre a Roo crear i editar fitxers protegits (com .rooignore i fitxers de configuració .roo/) sense requerir aprovació." } }, - "browser": { - "label": "Navegador", - "description": "Realitzar accions del navegador automàticament sense requerir aprovació. Nota: Només s'aplica quan el model admet l'ús de l'ordinador" - }, "mcp": { "label": "MCP", "description": "Habilitar l'aprovació automàtica d'eines MCP individuals a la vista de Servidors MCP (requereix tant aquesta configuració com la casella \"Permetre sempre\" de l'eina)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Nombre màxim de tokens de sortida per a les respostes de Claude Code. El valor per defecte és 8000." } }, - "browser": { - "enable": { - "label": "Habilitar eina de navegador", - "description": "Quan està habilitat, Roo pot utilitzar un navegador per interactuar amb llocs web quan s'utilitzen models que admeten l'ús de l'ordinador. <0>Més informació" - }, - "viewport": { - "label": "Mida del viewport", - "description": "Seleccioneu la mida del viewport per a interaccions del navegador. Això afecta com es mostren i interactuen els llocs web.", - "options": { - "largeDesktop": "Escriptori gran (1280x800)", - "smallDesktop": "Escriptori petit (900x600)", - "tablet": "Tauleta (768x1024)", - "mobile": "Mòbil (360x640)" - } - }, - "screenshotQuality": { - "label": "Qualitat de captures de pantalla", - "description": "Ajusteu la qualitat WebP de les captures de pantalla del navegador. Valors més alts proporcionen captures més clares però augmenten l'ús de token." - }, - "remote": { - "label": "Utilitzar connexió remota del navegador", - "description": "Connectar a un navegador Chrome que s'executa amb depuració remota habilitada (--remote-debugging-port=9222).", - "urlPlaceholder": "URL personalitzada (ex. http://localhost:9222)", - "testButton": "Provar connexió", - "testingButton": "Provant...", - "instructions": "Introduïu l'adreça d'amfitrió del protocol DevTools o deixeu-la buida per descobrir automàticament instàncies locals de Chrome. El botó Provar Connexió provarà la URL personalitzada si es proporciona, o descobrirà automàticament si el camp està buit." - } - }, "checkpoints": { "timeout": { "label": "Temps d'espera per inicialitzar el punt de control (segons)", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 54c462e2a71..fb1dd293004 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -363,44 +363,6 @@ "total": "Gesamtkosten: ${{cost}}", "includesSubtasks": "Enthält Kosten für Unteraufgaben" }, - "browser": { - "session": "Browser-Sitzung", - "active": "Aktiv", - "rooWantsToUse": "Roo möchte den Browser verwenden", - "consoleLogs": "Konsolenprotokolle", - "noNewLogs": "(Keine neuen Protokolle)", - "screenshot": "Browser-Screenshot", - "cursor": "Cursor", - "navigation": { - "step": "Schritt {{current}} von {{total}}", - "previous": "Zurück", - "next": "Weiter" - }, - "sessionStarted": "Browser-Sitzung gestartet", - "actions": { - "title": "Browser-Aktion: ", - "launched": "Browser gestartet", - "launch": "Browser starten auf {{url}}", - "clicked": "Geklickt auf: {{coordinate}}", - "click": "Klicken ({{coordinate}})", - "typed": "Eingegeben: {{text}}", - "type": "Eingeben \"{{text}}\"", - "pressed": "{{key}} gedrückt", - "press": "{{key}} drücken", - "scrolledDown": "Nach unten gescrollt", - "scrollDown": "Nach unten scrollen", - "scrolledUp": "Nach oben gescrollt", - "scrollUp": "Nach oben scrollen", - "hovered": "Gehovered auf: {{coordinate}}", - "hover": "Hover ({{coordinate}})", - "resized": "Größe geändert auf: {{size}}", - "resize": "Größe ändern auf {{size}}", - "screenshotSaved": "Screenshot gespeichert", - "screenshot": "Screenshot speichern unter {{path}}", - "closed": "Browser geschlossen", - "close": "Browser schließen" - } - }, "codeblock": { "tooltips": { "expand": "Code-Block erweitern", diff --git a/webview-ui/src/i18n/locales/de/prompts.json b/webview-ui/src/i18n/locales/de/prompts.json index 0418741c445..6e7c4bdc754 100644 --- a/webview-ui/src/i18n/locales/de/prompts.json +++ b/webview-ui/src/i18n/locales/de/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Dateien lesen", "edit": "Dateien bearbeiten", - "browser": "Browser verwenden", "command": "Befehle ausführen", "mcp": "MCP verwenden" }, diff --git a/webview-ui/src/i18n/locales/de/settings.json b/webview-ui/src/i18n/locales/de/settings.json index 8f565ba2048..0e8d7a3a8e3 100644 --- a/webview-ui/src/i18n/locales/de/settings.json +++ b/webview-ui/src/i18n/locales/de/settings.json @@ -31,7 +31,6 @@ "mcp": "MCP-Server", "worktrees": "Worktrees", "autoApprove": "Auto-Genehmigung", - "browser": "Computerzugriff", "checkpoints": "Kontrollpunkte", "notifications": "Benachrichtigungen", "contextManagement": "Kontext", @@ -241,10 +240,6 @@ "description": "Roo erlauben, geschützte Dateien (wie .rooignore und .roo/ Konfigurationsdateien) ohne Genehmigung zu erstellen und zu bearbeiten." } }, - "browser": { - "label": "Browser", - "description": "Browser-Aktionen automatisch ohne Genehmigung durchführen. Hinweis: Gilt nur, wenn das Modell Computer-Nutzung unterstützt" - }, "mcp": { "label": "MCP", "description": "Automatische Genehmigung einzelner MCP-Tools in der MCP-Server-Ansicht aktivieren (erfordert sowohl diese Einstellung als auch das 'Immer erlauben'-Kontrollkästchen des Tools)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Maximale Anzahl an Ausgabe-Tokens für Claude Code-Antworten. Standard ist 8000." } }, - "browser": { - "enable": { - "label": "Browser-Tool aktivieren", - "description": "Wenn aktiviert, kann Roo einen Browser verwenden, um mit Websites zu interagieren, wenn Modelle verwendet werden, die Computer-Nutzung unterstützen. <0>Mehr erfahren" - }, - "viewport": { - "label": "Viewport-Größe", - "description": "Wählen Sie die Viewport-Größe für Browser-Interaktionen. Dies beeinflusst, wie Websites angezeigt und mit ihnen interagiert wird.", - "options": { - "largeDesktop": "Großer Desktop (1280x800)", - "smallDesktop": "Kleiner Desktop (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Mobil (360x640)" - } - }, - "screenshotQuality": { - "label": "Screenshot-Qualität", - "description": "Passen Sie die WebP-Qualität von Browser-Screenshots an. Höhere Werte bieten klarere Screenshots, erhöhen aber den Token-Verbrauch." - }, - "remote": { - "label": "Remote-Browser-Verbindung verwenden", - "description": "Verbindung zu einem Chrome-Browser herstellen, der mit aktiviertem Remote-Debugging läuft (--remote-debugging-port=9222).", - "urlPlaceholder": "Benutzerdefinierte URL (z.B. http://localhost:9222)", - "testButton": "Verbindung testen", - "testingButton": "Teste...", - "instructions": "Geben Sie die DevTools-Protokoll-Host-Adresse ein oder lassen Sie das Feld leer, um Chrome lokale Instanzen automatisch zu erkennen. Die Schaltfläche 'Verbindung testen' versucht die benutzerdefinierte URL, wenn angegeben, oder erkennt automatisch, wenn das Feld leer ist." - } - }, "checkpoints": { "timeout": { "label": "Timeout für Checkpoint-Initialisierung (Sekunden)", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 0bb3f5bf4b6..b597fdbcc33 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -390,44 +390,6 @@ "total": "Total Cost: ${{cost}}", "includesSubtasks": "Includes subtask costs" }, - "browser": { - "session": "Browser Session", - "active": "Active", - "rooWantsToUse": "Roo wants to use the browser", - "consoleLogs": "Console Logs", - "noNewLogs": "(No new logs)", - "screenshot": "Browser screenshot", - "cursor": "cursor", - "navigation": { - "step": "Step {{current}} of {{total}}", - "previous": "Previous", - "next": "Next" - }, - "sessionStarted": "Browser Session Started", - "actions": { - "title": "Browser Action: ", - "launched": "Launched browser", - "launch": "Launch browser at {{url}}", - "clicked": "Clicked at: {{coordinate}}", - "click": "Click ({{coordinate}})", - "typed": "Typed: {{text}}", - "type": "Type \"{{text}}\"", - "pressed": "Pressed key: {{key}}", - "press": "Press {{key}}", - "scrolledDown": "Scrolled down", - "scrollDown": "Scroll down", - "scrolledUp": "Scrolled up", - "scrollUp": "Scroll up", - "hovered": "Hovered at: {{coordinate}}", - "hover": "Hover ({{coordinate}})", - "resized": "Resized to: {{size}}", - "resize": "Resize to {{size}}", - "screenshotSaved": "Screenshot saved", - "screenshot": "Save screenshot to {{path}}", - "closed": "Closed browser", - "close": "Close browser" - } - }, "codeblock": { "tooltips": { "expand": "Expand code block", diff --git a/webview-ui/src/i18n/locales/en/prompts.json b/webview-ui/src/i18n/locales/en/prompts.json index 9fca2be718f..82db8400c11 100644 --- a/webview-ui/src/i18n/locales/en/prompts.json +++ b/webview-ui/src/i18n/locales/en/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Read Files", "edit": "Edit Files", - "browser": "Use Browser", "command": "Run Commands", "mcp": "Use MCP" }, diff --git a/webview-ui/src/i18n/locales/en/settings.json b/webview-ui/src/i18n/locales/en/settings.json index 9f727255205..471309718bc 100644 --- a/webview-ui/src/i18n/locales/en/settings.json +++ b/webview-ui/src/i18n/locales/en/settings.json @@ -32,7 +32,6 @@ "worktrees": "Worktrees", "skills": "Skills", "autoApprove": "Auto-Approve", - "browser": "Browser", "checkpoints": "Checkpoints", "notifications": "Notifications", "contextManagement": "Context", @@ -302,10 +301,6 @@ "description": "Allow Roo to create and edit protected files (like .rooignore and .roo/ configuration files) without requiring approval." } }, - "browser": { - "label": "Browser", - "description": "Automatically perform browser actions without requiring approval. Note: Only applies when the model supports computer use" - }, "mcp": { "label": "MCP", "description": "Enable auto-approval of individual MCP tools in the MCP Servers view (requires both this setting and the tool's individual \"Always allow\" checkbox)" @@ -629,34 +624,6 @@ "maxTokensDescription": "Maximum number of output tokens for Claude Code responses. Default is 8000." } }, - "browser": { - "enable": { - "label": "Enable browser tool", - "description": "When enabled, Roo can use a browser to interact with websites when using models that support computer use. <0>Learn more" - }, - "viewport": { - "label": "Viewport size", - "description": "Select the viewport size for browser interactions. This affects how websites are displayed and interacted with.", - "options": { - "largeDesktop": "Large Desktop (1280x800)", - "smallDesktop": "Small Desktop (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Mobile (360x640)" - } - }, - "screenshotQuality": { - "label": "Screenshot quality", - "description": "Adjust the WebP quality of browser screenshots. Higher values provide clearer screenshots but increase token usage." - }, - "remote": { - "label": "Use remote browser connection", - "description": "Connect to a Chrome browser running with remote debugging enabled (--remote-debugging-port=9222).", - "urlPlaceholder": "Custom URL (e.g., http://localhost:9222)", - "testButton": "Test Connection", - "testingButton": "Testing...", - "instructions": "Enter the DevTools Protocol host address or leave empty to auto-discover Chrome local instances. The Test Connection button will try the custom URL if provided, or auto-discover if the field is empty." - } - }, "checkpoints": { "timeout": { "label": "Checkpoint initialization timeout (seconds)", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 71dfd95d69b..eb8833b8337 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -363,44 +363,6 @@ "total": "Costo total: ${{cost}}", "includesSubtasks": "Incluye costos de subtareas" }, - "browser": { - "session": "Sesión del navegador", - "active": "Activo", - "rooWantsToUse": "Roo quiere usar el navegador", - "consoleLogs": "Registros de la consola", - "noNewLogs": "(No hay nuevos registros)", - "screenshot": "Captura de pantalla del navegador", - "cursor": "cursor", - "navigation": { - "step": "Paso {{current}} de {{total}}", - "previous": "Anterior", - "next": "Siguiente" - }, - "sessionStarted": "Sesión de navegador iniciada", - "actions": { - "title": "Acción del navegador: ", - "launched": "Navegador iniciado", - "launch": "Iniciar navegador en {{url}}", - "clicked": "Clic en: {{coordinate}}", - "click": "Clic ({{coordinate}})", - "typed": "Escrito: {{text}}", - "type": "Escribir \"{{text}}\"", - "pressed": "Pulsado: {{key}}", - "press": "Pulsar {{key}}", - "scrolledDown": "Desplazado hacia abajo", - "scrollDown": "Desplazar hacia abajo", - "scrolledUp": "Desplazado hacia arriba", - "scrollUp": "Desplazar hacia arriba", - "hovered": "Flotado en: {{coordinate}}", - "hover": "Flotar ({{coordinate}})", - "resized": "Tamaño cambiado a: {{size}}", - "resize": "Cambiar tamaño a {{size}}", - "screenshotSaved": "Captura de pantalla guardada", - "screenshot": "Guardar captura de pantalla en {{path}}", - "closed": "Navegador cerrado", - "close": "Cerrar navegador" - } - }, "codeblock": { "tooltips": { "expand": "Expandir bloque de código", diff --git a/webview-ui/src/i18n/locales/es/prompts.json b/webview-ui/src/i18n/locales/es/prompts.json index 52badf6e5be..6e2ad48051b 100644 --- a/webview-ui/src/i18n/locales/es/prompts.json +++ b/webview-ui/src/i18n/locales/es/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Leer archivos", "edit": "Editar archivos", - "browser": "Usar navegador", "command": "Ejecutar comandos", "mcp": "Usar MCP" }, diff --git a/webview-ui/src/i18n/locales/es/settings.json b/webview-ui/src/i18n/locales/es/settings.json index 55977aae8dc..42688dd5411 100644 --- a/webview-ui/src/i18n/locales/es/settings.json +++ b/webview-ui/src/i18n/locales/es/settings.json @@ -31,7 +31,6 @@ "mcp": "Servidores MCP", "worktrees": "Worktrees", "autoApprove": "Auto-aprobación", - "browser": "Acceso al ordenador", "checkpoints": "Puntos de control", "notifications": "Notificaciones", "contextManagement": "Contexto", @@ -241,10 +240,6 @@ "description": "Permitir a Roo crear y editar archivos protegidos (como .rooignore y archivos de configuración .roo/) sin requerir aprobación." } }, - "browser": { - "label": "Navegador", - "description": "Realizar acciones del navegador automáticamente sin requerir aprobación. Nota: Solo se aplica cuando el modelo admite el uso del ordenador" - }, "mcp": { "label": "MCP", "description": "Habilitar la aprobación automática de herramientas MCP individuales en la vista de Servidores MCP (requiere tanto esta configuración como la casilla \"Permitir siempre\" de la herramienta)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Número máximo de tokens de salida para las respuestas de Claude Code. El valor predeterminado es 8000." } }, - "browser": { - "enable": { - "label": "Habilitar herramienta de navegador", - "description": "Cuando está habilitado, Roo puede usar un navegador para interactuar con sitios web cuando se utilizan modelos que admiten el uso del ordenador. <0>Más información" - }, - "viewport": { - "label": "Tamaño del viewport", - "description": "Seleccione el tamaño del viewport para interacciones del navegador. Esto afecta cómo se muestran e interactúan los sitios web.", - "options": { - "largeDesktop": "Escritorio grande (1280x800)", - "smallDesktop": "Escritorio pequeño (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Móvil (360x640)" - } - }, - "screenshotQuality": { - "label": "Calidad de capturas de pantalla", - "description": "Ajuste la calidad WebP de las capturas de pantalla del navegador. Valores más altos proporcionan capturas más claras pero aumentan el uso de token." - }, - "remote": { - "label": "Usar conexión remota del navegador", - "description": "Conectarse a un navegador Chrome que se ejecuta con depuración remota habilitada (--remote-debugging-port=9222).", - "urlPlaceholder": "URL personalizada (ej. http://localhost:9222)", - "testButton": "Probar conexión", - "testingButton": "Probando...", - "instructions": "Ingrese la dirección del host del protocolo DevTools o déjelo vacío para descubrir automáticamente instancias locales de Chrome. El botón Probar Conexión intentará usar la URL personalizada si se proporciona, o descubrirá automáticamente si el campo está vacío." - } - }, "checkpoints": { "timeout": { "label": "Tiempo de espera para inicializar el punto de control (segundos)", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index c86f01fa1a2..e2858c8e8c1 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -363,44 +363,6 @@ "total": "Coût total : ${{cost}}", "includesSubtasks": "Inclut les coûts des sous-tâches" }, - "browser": { - "session": "Session du navigateur", - "active": "Actif", - "rooWantsToUse": "Roo veut utiliser le navigateur", - "consoleLogs": "Journaux de console", - "noNewLogs": "(Pas de nouveaux journaux)", - "screenshot": "Capture d'écran du navigateur", - "cursor": "curseur", - "navigation": { - "step": "Étape {{current}} sur {{total}}", - "previous": "Précédent", - "next": "Suivant" - }, - "sessionStarted": "Session de navigateur démarrée", - "actions": { - "title": "Action du navigateur : ", - "launched": "Navigateur lancé", - "launch": "Lancer le navigateur sur {{url}}", - "clicked": "Cliqué sur : {{coordinate}}", - "click": "Cliquer ({{coordinate}})", - "typed": "Saisi : {{text}}", - "type": "Saisir \"{{text}}\"", - "pressed": "Appuyé sur : {{key}}", - "press": "Appuyer sur {{key}}", - "scrolledDown": "Défilé vers le bas", - "scrollDown": "Défiler vers le bas", - "scrolledUp": "Défilé vers le haut", - "scrollUp": "Défiler vers le haut", - "hovered": "Survolé : {{coordinate}}", - "hover": "Survoler ({{coordinate}})", - "resized": "Redimensionné à : {{size}}", - "resize": "Redimensionner à {{size}}", - "screenshotSaved": "Capture d'écran enregistrée", - "screenshot": "Enregistrer une capture d'écran dans {{path}}", - "closed": "Navigateur fermé", - "close": "Fermer le navigateur" - } - }, "codeblock": { "tooltips": { "expand": "Développer le bloc de code", diff --git a/webview-ui/src/i18n/locales/fr/prompts.json b/webview-ui/src/i18n/locales/fr/prompts.json index ad8bbc245ce..5adce7b17ad 100644 --- a/webview-ui/src/i18n/locales/fr/prompts.json +++ b/webview-ui/src/i18n/locales/fr/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Lire les fichiers", "edit": "Modifier les fichiers", - "browser": "Utiliser le navigateur", "command": "Exécuter des commandes", "mcp": "Utiliser MCP" }, diff --git a/webview-ui/src/i18n/locales/fr/settings.json b/webview-ui/src/i18n/locales/fr/settings.json index 1423704dd8b..4a213f31801 100644 --- a/webview-ui/src/i18n/locales/fr/settings.json +++ b/webview-ui/src/i18n/locales/fr/settings.json @@ -31,7 +31,6 @@ "mcp": "Serveurs MCP", "worktrees": "Worktrees", "autoApprove": "Auto-approbation", - "browser": "Accès ordinateur", "checkpoints": "Points de contrôle", "notifications": "Notifications", "contextManagement": "Contexte", @@ -242,10 +241,6 @@ "description": "Permettre à Roo de créer et modifier des fichiers protégés (comme .rooignore et les fichiers de configuration .roo/) sans nécessiter d'approbation." } }, - "browser": { - "label": "Navigateur", - "description": "Effectuer automatiquement des actions du navigateur sans nécessiter d'approbation. Remarque : S'applique uniquement lorsque le modèle prend en charge l'utilisation de l'ordinateur" - }, "mcp": { "label": "MCP", "description": "Activer l'approbation automatique des outils MCP individuels dans la vue des serveurs MCP (nécessite à la fois ce paramètre et la case à cocher \"Toujours autoriser\" de l'outil)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Nombre maximum de jetons de sortie pour les réponses de Claude Code. La valeur par défaut est 8000." } }, - "browser": { - "enable": { - "label": "Activer l'outil de navigateur", - "description": "Lorsque cette option est activée, Roo peut utiliser un navigateur pour interagir avec des sites web lors de l'utilisation de modèles qui prennent en charge l'utilisation de l'ordinateur. <0>En savoir plus" - }, - "viewport": { - "label": "Taille de la fenêtre d'affichage", - "description": "Sélectionnez la taille de la fenêtre d'affichage pour les interactions du navigateur. Cela affecte la façon dont les sites web sont affichés et dont on interagit avec eux.", - "options": { - "largeDesktop": "Grand bureau (1280x800)", - "smallDesktop": "Petit bureau (900x600)", - "tablet": "Tablette (768x1024)", - "mobile": "Mobile (360x640)" - } - }, - "screenshotQuality": { - "label": "Qualité des captures d'écran", - "description": "Ajustez la qualité WebP des captures d'écran du navigateur. Des valeurs plus élevées fournissent des captures plus claires mais augmentent l'utilisation de token." - }, - "remote": { - "label": "Utiliser une connexion de navigateur distant", - "description": "Se connecter à un navigateur Chrome exécuté avec le débogage à distance activé (--remote-debugging-port=9222).", - "urlPlaceholder": "URL personnalisée (ex. http://localhost:9222)", - "testButton": "Tester la connexion", - "testingButton": "Test en cours...", - "instructions": "Entrez l'adresse hôte du protocole DevTools ou laissez vide pour découvrir automatiquement les instances Chrome locales. Le bouton Tester la connexion essaiera l'URL personnalisée si fournie, ou découvrira automatiquement si le champ est vide." - } - }, "checkpoints": { "timeout": { "label": "Délai d'initialisation du point de contrôle (secondes)", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index e6447438f26..75ee2c9abf5 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -363,44 +363,6 @@ "total": "कुल लागत: ${{cost}}", "includesSubtasks": "उप-कार्यों की लागत शामिल है" }, - "browser": { - "session": "ब्राउज़र सत्र", - "active": "सक्रिय", - "rooWantsToUse": "Roo ब्राउज़र का उपयोग करना चाहता है", - "consoleLogs": "कंसोल लॉग", - "noNewLogs": "(कोई नया लॉग नहीं)", - "screenshot": "ब्राउज़र स्क्रीनशॉट", - "cursor": "कर्सर", - "navigation": { - "step": "चरण {{current}} / {{total}}", - "previous": "पिछला", - "next": "अगला" - }, - "sessionStarted": "ब्राउज़र सत्र शुरू हुआ", - "actions": { - "title": "ब्राउज़र क्रिया: ", - "launched": "ब्राउज़र लॉन्च हुआ", - "launch": "{{url}} पर ब्राउज़र लॉन्च करें", - "clicked": "क्लिक किया गया ({{coordinate}})", - "click": "क्लिक करें ({{coordinate}})", - "typed": "टाइप किया गया: {{text}}", - "type": "टाइप करें \"{{text}}\"", - "pressed": "दबाया गया: {{key}}", - "press": "{{key}} दबाएँ", - "scrolledDown": "नीचे स्क्रॉल किया गया", - "scrollDown": "नीचे स्क्रॉल करें", - "scrolledUp": "ऊपर स्क्रॉल किया गया", - "scrollUp": "ऊपर स्क्रॉल करें", - "hovered": "होवर किया गया ({{coordinate}})", - "hover": "होवर करें ({{coordinate}})", - "resized": "आकार बदला गया: {{size}}", - "resize": "आकार बदलें {{size}}", - "screenshotSaved": "स्क्रीनशॉट सहेजा गया", - "screenshot": "स्क्रीनशॉट को {{path}} में सहेजें", - "closed": "ब्राउज़र बंद किया गया", - "close": "ब्राउज़र बंद करें" - } - }, "codeblock": { "tooltips": { "expand": "कोड ब्लॉक का विस्तार करें", diff --git a/webview-ui/src/i18n/locales/hi/prompts.json b/webview-ui/src/i18n/locales/hi/prompts.json index d710f28c2d6..8f8fb9caa6d 100644 --- a/webview-ui/src/i18n/locales/hi/prompts.json +++ b/webview-ui/src/i18n/locales/hi/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "फाइलें पढ़ें", "edit": "फाइलें संपादित करें", - "browser": "ब्राउज़र का उपयोग करें", "command": "कमांड्स चलाएँ", "mcp": "MCP का उपयोग करें" }, diff --git a/webview-ui/src/i18n/locales/hi/settings.json b/webview-ui/src/i18n/locales/hi/settings.json index 2932913f37c..3544dbb7681 100644 --- a/webview-ui/src/i18n/locales/hi/settings.json +++ b/webview-ui/src/i18n/locales/hi/settings.json @@ -31,7 +31,6 @@ "mcp": "एमसीपी सर्वर", "worktrees": "Worktrees", "autoApprove": "अनुमोदन", - "browser": "ब्राउज़र", "checkpoints": "चेकपॉइंट", "notifications": "सूचनाएँ", "contextManagement": "संदर्भ", @@ -241,10 +240,6 @@ "description": "Roo को अनुमोदन की आवश्यकता के बिना संरक्षित फाइलें (.rooignore और .roo/ कॉन्फ़िगरेशन फाइलें जैसी) बनाने और संपादित करने की अनुमति दें।" } }, - "browser": { - "label": "ब्राउज़र", - "description": "अनुमोदन की आवश्यकता के बिना स्वचालित रूप से ब्राउज़र क्रियाएँ करें — नोट: केवल तभी लागू होता है जब मॉडल कंप्यूटर उपयोग का समर्थन करता है" - }, "mcp": { "label": "MCP", "description": "MCP सर्वर व्यू में व्यक्तिगत MCP टूल्स के स्वतः अनुमोदन को सक्षम करें (इस सेटिंग और टूल के \"हमेशा अनुमति दें\" चेकबॉक्स दोनों की आवश्यकता है)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Claude Code प्रतिक्रियाओं के लिए आउटपुट टोकन की अधिकतम संख्या। डिफ़ॉल्ट 8000 है।" } }, - "browser": { - "enable": { - "label": "ब्राउज़र टूल सक्षम करें", - "description": "जब सक्षम होता है, तो Roo कंप्यूटर उपयोग का समर्थन करने वाले मॉडल का उपयोग करते समय वेबसाइटों के साथ बातचीत करने के लिए ब्राउज़र का उपयोग कर सकता है। <0>अधिक जानें" - }, - "viewport": { - "label": "व्यूपोर्ट आकार", - "description": "ब्राउज़र इंटरैक्शन के लिए व्यूपोर्ट आकार चुनें। यह वेबसाइटों के प्रदर्शन और उनके साथ बातचीत को प्रभावित करता है।", - "options": { - "largeDesktop": "बड़ा डेस्कटॉप (1280x800)", - "smallDesktop": "छोटा डेस्कटॉप (900x600)", - "tablet": "टैबलेट (768x1024)", - "mobile": "मोबाइल (360x640)" - } - }, - "screenshotQuality": { - "label": "स्क्रीनशॉट गुणवत्ता", - "description": "ब्राउज़र स्क्रीनशॉट की WebP गुणवत्ता समायोजित करें। उच्च मान स्पष्ट स्क्रीनशॉट प्रदान करते हैं लेकिन token उपयोग बढ़ाते हैं।" - }, - "remote": { - "label": "दूरस्थ ब्राउज़र कनेक्शन का उपयोग करें", - "description": "रिमोट डीबगिंग सक्षम के साथ चल रहे Chrome ब्राउज़र से कनेक्ट करें (--remote-debugging-port=9222)।", - "urlPlaceholder": "कस्टम URL (उदा. http://localhost:9222)", - "testButton": "कनेक्शन का परीक्षण करें", - "testingButton": "परीक्षण हो रहा है...", - "instructions": "DevTools प्रोटोकॉल होस्ट पता दर्ज करें या Chrome स्थानीय इंस्टेंस स्वतः खोजने के लिए खाली छोड़ दें। टेस्ट कनेक्शन बटन यदि प्रदान किया गया है तो कस्टम URL का प्रयास करेगा, या यदि फ़ील्ड खाली है तो स्वतः खोज करेगा।" - } - }, "checkpoints": { "timeout": { "label": "चेकपॉइंट इनिशियलाइज़ेशन टाइमआउट (सेकंड)", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index dd7fd0ed55a..ee41b9ceb65 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -400,44 +400,6 @@ "total": "Total Biaya: ${{cost}}", "includesSubtasks": "Termasuk biaya subtugas" }, - "browser": { - "session": "Sesi Browser", - "active": "Aktif", - "rooWantsToUse": "Roo ingin menggunakan browser", - "consoleLogs": "Log Konsol", - "noNewLogs": "(Tidak ada log baru)", - "screenshot": "Screenshot browser", - "cursor": "kursor", - "navigation": { - "step": "Langkah {{current}} dari {{total}}", - "previous": "Sebelumnya", - "next": "Selanjutnya" - }, - "sessionStarted": "Sesi Browser Dimulai", - "actions": { - "title": "Aksi Browser: ", - "launched": "Browser diluncurkan", - "launch": "Luncurkan browser di {{url}}", - "clicked": "Diklik ({{coordinate}})", - "click": "Klik ({{coordinate}})", - "typed": "Diketik: {{text}}", - "type": "Ketik \"{{text}}\"", - "pressed": "Ditekan: {{key}}", - "press": "Tekan {{key}}", - "scrolledDown": "Digulir ke bawah", - "scrollDown": "Gulir ke bawah", - "scrolledUp": "Digulir ke atas", - "scrollUp": "Gulir ke atas", - "hovered": "Diarahkan ({{coordinate}})", - "hover": "Arahkan ({{coordinate}})", - "resized": "Ukuran diubah ke: {{size}}", - "resize": "Ubah ukuran ke {{size}}", - "screenshotSaved": "Screenshot disimpan", - "screenshot": "Simpan screenshot ke {{path}}", - "closed": "Browser ditutup", - "close": "Tutup browser" - } - }, "codeblock": { "tooltips": { "expand": "Perluas blok kode", diff --git a/webview-ui/src/i18n/locales/id/prompts.json b/webview-ui/src/i18n/locales/id/prompts.json index 28454e744fa..938d8de34c5 100644 --- a/webview-ui/src/i18n/locales/id/prompts.json +++ b/webview-ui/src/i18n/locales/id/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Baca File", "edit": "Edit File", - "browser": "Gunakan Browser", "command": "Jalankan Perintah", "mcp": "Gunakan MCP" }, diff --git a/webview-ui/src/i18n/locales/id/settings.json b/webview-ui/src/i18n/locales/id/settings.json index 08e8c7ccec9..bc5db3ae85a 100644 --- a/webview-ui/src/i18n/locales/id/settings.json +++ b/webview-ui/src/i18n/locales/id/settings.json @@ -31,7 +31,6 @@ "mcp": "Server MCP", "worktrees": "Worktrees", "autoApprove": "Auto-Approve", - "browser": "Browser", "checkpoints": "Checkpoint", "notifications": "Notifikasi", "contextManagement": "Konteks", @@ -241,10 +240,6 @@ "description": "Izinkan Roo membuat dan mengedit file yang dilindungi (seperti .rooignore dan file konfigurasi .roo/) tanpa memerlukan persetujuan." } }, - "browser": { - "label": "Browser", - "description": "Secara otomatis melakukan aksi browser tanpa memerlukan persetujuan. Catatan: Hanya berlaku ketika model mendukung computer use" - }, "mcp": { "label": "MCP", "description": "Aktifkan auto-approval tool MCP individual di tampilan Server MCP (memerlukan pengaturan ini dan checkbox \"Selalu izinkan\" tool tersebut)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Jumlah maksimum token output untuk respons Claude Code. Default adalah 8000." } }, - "browser": { - "enable": { - "label": "Aktifkan tool browser", - "description": "Ketika diaktifkan, Roo dapat menggunakan browser untuk berinteraksi dengan website ketika menggunakan model yang mendukung computer use. <0>Pelajari lebih lanjut" - }, - "viewport": { - "label": "Ukuran viewport", - "description": "Pilih ukuran viewport untuk interaksi browser. Ini mempengaruhi bagaimana website ditampilkan dan berinteraksi.", - "options": { - "largeDesktop": "Desktop Besar (1280x800)", - "smallDesktop": "Desktop Kecil (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Mobile (360x640)" - } - }, - "screenshotQuality": { - "label": "Kualitas screenshot", - "description": "Sesuaikan kualitas WebP screenshot browser. Nilai yang lebih tinggi memberikan screenshot yang lebih jelas tetapi meningkatkan penggunaan token." - }, - "remote": { - "label": "Gunakan koneksi browser remote", - "description": "Hubungkan ke browser Chrome yang berjalan dengan remote debugging diaktifkan (--remote-debugging-port=9222).", - "urlPlaceholder": "URL Kustom (misalnya, http://localhost:9222)", - "testButton": "Test Koneksi", - "testingButton": "Testing...", - "instructions": "Masukkan alamat host DevTools Protocol atau biarkan kosong untuk auto-discover instance Chrome lokal. Tombol Test Koneksi akan mencoba URL kustom jika disediakan, atau auto-discover jika field kosong." - } - }, "checkpoints": { "timeout": { "label": "Batas waktu inisialisasi checkpoint (detik)", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index c201425813b..54f5bf9061d 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -363,44 +363,6 @@ "total": "Costo totale: ${{cost}}", "includesSubtasks": "Include i costi delle sottoattività" }, - "browser": { - "session": "Sessione del browser", - "active": "Attivo", - "rooWantsToUse": "Roo vuole utilizzare il browser", - "consoleLogs": "Log della console", - "noNewLogs": "(Nessun nuovo log)", - "screenshot": "Screenshot del browser", - "cursor": "cursore", - "navigation": { - "step": "Passo {{current}} di {{total}}", - "previous": "Precedente", - "next": "Successivo" - }, - "sessionStarted": "Sessione browser avviata", - "actions": { - "title": "Azione browser: ", - "launched": "Browser avviato", - "launch": "Avvia browser su {{url}}", - "clicked": "Cliccato su: {{coordinate}}", - "click": "Clic ({{coordinate}})", - "typed": "Digitato: {{text}}", - "type": "Digita \"{{text}}\"", - "pressed": "Premuto: {{key}}", - "press": "Premi {{key}}", - "scrolledDown": "Scorso verso il basso", - "scrollDown": "Scorri verso il basso", - "scrolledUp": "Scorso verso l'alto", - "scrollUp": "Scorri verso l'alto", - "hovered": "Passato il mouse su: {{coordinate}}", - "hover": "Passa il mouse ({{coordinate}})", - "resized": "Ridimensionato a: {{size}}", - "resize": "Ridimensiona a {{size}}", - "screenshotSaved": "Screenshot salvato", - "screenshot": "Salva screenshot in {{path}}", - "closed": "Browser chiuso", - "close": "Chiudi browser" - } - }, "codeblock": { "tooltips": { "expand": "Espandi blocco di codice", diff --git a/webview-ui/src/i18n/locales/it/prompts.json b/webview-ui/src/i18n/locales/it/prompts.json index 7ba95815a78..538124e5842 100644 --- a/webview-ui/src/i18n/locales/it/prompts.json +++ b/webview-ui/src/i18n/locales/it/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Leggi file", "edit": "Modifica file", - "browser": "Usa browser", "command": "Esegui comandi", "mcp": "Usa MCP" }, diff --git a/webview-ui/src/i18n/locales/it/settings.json b/webview-ui/src/i18n/locales/it/settings.json index 7ee641c3b3a..7eddf6c6127 100644 --- a/webview-ui/src/i18n/locales/it/settings.json +++ b/webview-ui/src/i18n/locales/it/settings.json @@ -31,7 +31,6 @@ "mcp": "Server MCP", "worktrees": "Worktrees", "autoApprove": "Auto-approvazione", - "browser": "Accesso computer", "checkpoints": "Punti di controllo", "notifications": "Notifiche", "contextManagement": "Contesto", @@ -241,10 +240,6 @@ "description": "Permetti a Roo di creare e modificare file protetti (come .rooignore e file di configurazione .roo/) senza richiedere approvazione." } }, - "browser": { - "label": "Browser", - "description": "Esegui automaticamente azioni del browser senza richiedere approvazione. Nota: Si applica solo quando il modello supporta l'uso del computer" - }, "mcp": { "label": "MCP", "description": "Abilita l'approvazione automatica dei singoli strumenti MCP nella vista Server MCP (richiede sia questa impostazione che la casella \"Consenti sempre\" dello strumento)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Numero massimo di token di output per le risposte di Claude Code. Il valore predefinito è 8000." } }, - "browser": { - "enable": { - "label": "Abilita strumento browser", - "description": "Quando abilitato, Roo può utilizzare un browser per interagire con siti web quando si utilizzano modelli che supportano l'uso del computer. <0>Scopri di più" - }, - "viewport": { - "label": "Dimensione viewport", - "description": "Seleziona la dimensione del viewport per le interazioni del browser. Questo influisce su come i siti web vengono visualizzati e su come vi si interagisce.", - "options": { - "largeDesktop": "Desktop grande (1280x800)", - "smallDesktop": "Desktop piccolo (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Mobile (360x640)" - } - }, - "screenshotQuality": { - "label": "Qualità screenshot", - "description": "Regola la qualità WebP degli screenshot del browser. Valori più alti forniscono screenshot più nitidi ma aumentano l'utilizzo di token." - }, - "remote": { - "label": "Usa connessione browser remoto", - "description": "Connettiti a un browser Chrome in esecuzione con debug remoto abilitato (--remote-debugging-port=9222).", - "urlPlaceholder": "URL personalizzato (es. http://localhost:9222)", - "testButton": "Testa connessione", - "testingButton": "Test in corso...", - "instructions": "Inserisci l'indirizzo host del protocollo DevTools o lascia vuoto per scoprire automaticamente le istanze Chrome locali. Il pulsante Test Connessione proverà l'URL personalizzato se fornito, o scoprirà automaticamente se il campo è vuoto." - } - }, "checkpoints": { "timeout": { "label": "Timeout inizializzazione checkpoint (secondi)", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 17fa51aca79..2cf71855032 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -363,44 +363,6 @@ "total": "合計コスト: ${{cost}}", "includesSubtasks": "サブタスクのコストを含む" }, - "browser": { - "session": "ブラウザセッション", - "active": "アクティブ", - "rooWantsToUse": "Rooはブラウザを使用したい", - "consoleLogs": "コンソールログ", - "noNewLogs": "(新しいログはありません)", - "screenshot": "ブラウザのスクリーンショット", - "cursor": "カーソル", - "navigation": { - "step": "ステップ {{current}} / {{total}}", - "previous": "前へ", - "next": "次へ" - }, - "sessionStarted": "ブラウザセッション開始", - "actions": { - "title": "ブラウザ操作: ", - "launched": "ブラウザを起動しました", - "launch": "{{url}} でブラウザを起動", - "clicked": "クリック: {{coordinate}}", - "click": "クリック ({{coordinate}})", - "typed": "入力: {{text}}", - "type": "入力 \"{{text}}\"", - "pressed": "{{key}}を押しました", - "press": "{{key}}を押す", - "scrolledDown": "下にスクロールしました", - "scrollDown": "下にスクロール", - "scrolledUp": "上にスクロールしました", - "scrollUp": "上にスクロール", - "hovered": "ホバー: {{coordinate}}", - "hover": "ホバー ({{coordinate}})", - "resized": "サイズを変更しました: {{size}}", - "resize": "サイズを {{size}} に変更", - "screenshotSaved": "スクリーンショットを保存しました", - "screenshot": "スクリーンショットを {{path}} に保存", - "closed": "ブラウザを閉じました", - "close": "ブラウザを閉じる" - } - }, "codeblock": { "tooltips": { "expand": "コードブロックを展開", diff --git a/webview-ui/src/i18n/locales/ja/prompts.json b/webview-ui/src/i18n/locales/ja/prompts.json index fc774fc318c..4d7c77e688f 100644 --- a/webview-ui/src/i18n/locales/ja/prompts.json +++ b/webview-ui/src/i18n/locales/ja/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "ファイルを読み込む", "edit": "ファイルを編集", - "browser": "ブラウザを使用", "command": "コマンドを実行", "mcp": "MCP を使用" }, diff --git a/webview-ui/src/i18n/locales/ja/settings.json b/webview-ui/src/i18n/locales/ja/settings.json index 787d3db2189..533acf119c9 100644 --- a/webview-ui/src/i18n/locales/ja/settings.json +++ b/webview-ui/src/i18n/locales/ja/settings.json @@ -31,7 +31,6 @@ "mcp": "MCPサーバー", "worktrees": "Worktrees", "autoApprove": "自動承認", - "browser": "コンピューターアクセス", "checkpoints": "チェックポイント", "notifications": "通知", "contextManagement": "コンテキスト", @@ -241,10 +240,6 @@ "description": "Rooが保護されたファイル(.rooignoreや.roo/設定ファイルなど)を承認なしで作成・編集することを許可します。" } }, - "browser": { - "label": "ブラウザ", - "description": "承認なしで自動的にブラウザアクションを実行 — 注意:コンピューター使用をサポートするモデルを使用している場合のみ適用されます" - }, "mcp": { "label": "MCP", "description": "MCPサーバービューで個々のMCPツールの自動承認を有効にします(この設定とツールの「常に許可」チェックボックスの両方が必要)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Claude Codeレスポンスの最大出力トークン数。デフォルトは8000です。" } }, - "browser": { - "enable": { - "label": "ブラウザツールを有効化", - "description": "有効にすると、コンピューター使用をサポートするモデルを使用する際に、Rooはウェブサイトとのやり取りにブラウザを使用できます。 <0>詳細情報" - }, - "viewport": { - "label": "ビューポートサイズ", - "description": "ブラウザインタラクションのビューポートサイズを選択します。これはウェブサイトの表示方法とインタラクション方法に影響します。", - "options": { - "largeDesktop": "大型デスクトップ (1280x800)", - "smallDesktop": "小型デスクトップ (900x600)", - "tablet": "タブレット (768x1024)", - "mobile": "モバイル (360x640)" - } - }, - "screenshotQuality": { - "label": "スクリーンショット品質", - "description": "ブラウザスクリーンショットのWebP品質を調整します。高い値はより鮮明なスクリーンショットを提供しますが、token使用量が増加します。" - }, - "remote": { - "label": "リモートブラウザ接続を使用", - "description": "リモートデバッグを有効にして実行しているChromeブラウザに接続します(--remote-debugging-port=9222)。", - "urlPlaceholder": "カスタムURL(例:http://localhost:9222)", - "testButton": "接続テスト", - "testingButton": "テスト中...", - "instructions": "DevToolsプロトコルホストアドレスを入力するか、Chromeのローカルインスタンスを自動検出するために空のままにします。接続テストボタンは、提供されている場合はカスタムURLを試み、フィールドが空の場合は自動検出します。" - } - }, "checkpoints": { "timeout": { "label": "チェックポイント初期化タイムアウト(秒)", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index a91e59fddce..24debd83edc 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -363,44 +363,6 @@ "total": "총 비용: ${{cost}}", "includesSubtasks": "하위 작업 비용 포함" }, - "browser": { - "session": "브라우저 세션", - "active": "활성", - "rooWantsToUse": "Roo가 브라우저를 사용하고 싶어합니다", - "consoleLogs": "콘솔 로그", - "noNewLogs": "(새 로그 없음)", - "screenshot": "브라우저 스크린샷", - "cursor": "커서", - "navigation": { - "step": "단계 {{current}} / {{total}}", - "previous": "이전", - "next": "다음" - }, - "sessionStarted": "브라우저 세션 시작됨", - "actions": { - "title": "브라우저 작업: ", - "launched": "브라우저 실행됨", - "launch": "{{url}}에서 브라우저 실행", - "clicked": "클릭함: {{coordinate}}", - "click": "클릭 ({{coordinate}})", - "typed": "입력함: {{text}}", - "type": "입력 \"{{text}}\"", - "pressed": "{{key}} 누름", - "press": "{{key}} 누르기", - "scrolledDown": "아래로 스크롤됨", - "scrollDown": "아래로 스크롤", - "scrolledUp": "위로 스크롤됨", - "scrollUp": "위로 스크롤", - "hovered": "가리킴: {{coordinate}}", - "hover": "가리키기 ({{coordinate}})", - "resized": "크기 조정됨: {{size}}", - "resize": "크기를 {{size}}로 조정", - "screenshotSaved": "스크린샷 저장됨", - "screenshot": "스크린샷을 {{path}}에 저장", - "closed": "브라우저 닫음", - "close": "브라우저 닫기" - } - }, "codeblock": { "tooltips": { "expand": "코드 블록 확장", diff --git a/webview-ui/src/i18n/locales/ko/prompts.json b/webview-ui/src/i18n/locales/ko/prompts.json index f3666f1b660..204547e4c39 100644 --- a/webview-ui/src/i18n/locales/ko/prompts.json +++ b/webview-ui/src/i18n/locales/ko/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "파일 읽기", "edit": "파일 편집", - "browser": "브라우저 사용", "command": "명령 실행", "mcp": "MCP 사용" }, diff --git a/webview-ui/src/i18n/locales/ko/settings.json b/webview-ui/src/i18n/locales/ko/settings.json index 6c4a91f9c4a..52544154027 100644 --- a/webview-ui/src/i18n/locales/ko/settings.json +++ b/webview-ui/src/i18n/locales/ko/settings.json @@ -31,7 +31,6 @@ "mcp": "MCP 서버", "worktrees": "Worktrees", "autoApprove": "자동 승인", - "browser": "컴퓨터 접근", "checkpoints": "체크포인트", "notifications": "알림", "contextManagement": "컨텍스트", @@ -241,10 +240,6 @@ "description": "Roo가 보호된 파일(.rooignore 및 .roo/ 구성 파일 등)을 승인 없이 생성하고 편집할 수 있도록 허용합니다." } }, - "browser": { - "label": "브라우저", - "description": "승인 없이 자동으로 브라우저 작업 수행 — 참고: 모델이 컴퓨터 사용을 지원할 때만 적용됩니다" - }, "mcp": { "label": "MCP", "description": "MCP 서버 보기에서 개별 MCP 도구의 자동 승인 활성화(이 설정과 도구의 \"항상 허용\" 체크박스 모두 필요)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Claude Code 응답의 최대 출력 토큰 수. 기본값은 8000입니다." } }, - "browser": { - "enable": { - "label": "브라우저 도구 활성화", - "description": "활성화되면 Roo는 컴퓨터 사용을 지원하는 모델을 사용할 때 웹사이트와 상호 작용하기 위해 브라우저를 사용할 수 있습니다. <0>더 알아보기" - }, - "viewport": { - "label": "뷰포트 크기", - "description": "브라우저 상호 작용을 위한 뷰포트 크기를 선택하세요. 이는 웹사이트가 표시되고 상호 작용하는 방식에 영향을 미칩니다.", - "options": { - "largeDesktop": "대형 데스크톱 (1280x800)", - "smallDesktop": "소형 데스크톱 (900x600)", - "tablet": "태블릿 (768x1024)", - "mobile": "모바일 (360x640)" - } - }, - "screenshotQuality": { - "label": "스크린샷 품질", - "description": "브라우저 스크린샷의 WebP 품질을 조정합니다. 높은 값은 더 선명한 스크린샷을 제공하지만 token 사용량이 증가합니다." - }, - "remote": { - "label": "원격 브라우저 연결 사용", - "description": "원격 디버깅이 활성화된 Chrome 브라우저에 연결합니다(--remote-debugging-port=9222).", - "urlPlaceholder": "사용자 정의 URL(예: http://localhost:9222)", - "testButton": "연결 테스트", - "testingButton": "테스트 중...", - "instructions": "DevTools 프로토콜 호스트 주소를 입력하거나 Chrome 로컬 인스턴스를 자동으로 발견하기 위해 비워두세요. 연결 테스트 버튼은 제공된 경우 사용자 정의 URL을 시도하거나, 필드가 비어 있으면 자동으로 발견합니다." - } - }, "checkpoints": { "timeout": { "label": "체크포인트 초기화 타임아웃(초)", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 613deed507f..0bec6846c04 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -363,44 +363,6 @@ "total": "Totale kosten: ${{cost}}", "includesSubtasks": "Inclusief kosten van subtaken" }, - "browser": { - "session": "Browsersessie", - "active": "Actief", - "rooWantsToUse": "Roo wil de browser gebruiken", - "consoleLogs": "Console-logboeken", - "noNewLogs": "(Geen nieuwe logboeken)", - "screenshot": "Browserschermopname", - "cursor": "cursor", - "navigation": { - "step": "Stap {{current}} van {{total}}", - "previous": "Vorige", - "next": "Volgende" - }, - "sessionStarted": "Browsersessie gestart", - "actions": { - "title": "Browseractie: ", - "launched": "Browser gestart", - "launch": "Browser starten op {{url}}", - "clicked": "Geklikt ({{coordinate}})", - "click": "Klik ({{coordinate}})", - "typed": "Getypt: {{text}}", - "type": "Typ \"{{text}}\"", - "pressed": "Ingedrukt: {{key}}", - "press": "Druk op {{key}}", - "scrolledDown": "Naar beneden gescrolld", - "scrollDown": "Scroll naar beneden", - "scrolledUp": "Naar boven gescrolld", - "scrollUp": "Scroll naar boven", - "hovered": "Zweefde ({{coordinate}})", - "hover": "Zweven ({{coordinate}})", - "resized": "Grootte gewijzigd naar: {{size}}", - "resize": "Grootte wijzigen naar {{size}}", - "screenshotSaved": "Schermopname opgeslagen", - "screenshot": "Schermopname opslaan naar {{path}}", - "closed": "Browser gesloten", - "close": "Browser sluiten" - } - }, "codeblock": { "tooltips": { "expand": "Codeblok uitvouwen", diff --git a/webview-ui/src/i18n/locales/nl/prompts.json b/webview-ui/src/i18n/locales/nl/prompts.json index 097549e8a74..b1be9df5686 100644 --- a/webview-ui/src/i18n/locales/nl/prompts.json +++ b/webview-ui/src/i18n/locales/nl/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Bestanden lezen", "edit": "Bestanden bewerken", - "browser": "Browser gebruiken", "command": "Commando's uitvoeren", "mcp": "MCP gebruiken" }, diff --git a/webview-ui/src/i18n/locales/nl/settings.json b/webview-ui/src/i18n/locales/nl/settings.json index 3cf63857167..db7b0554c3a 100644 --- a/webview-ui/src/i18n/locales/nl/settings.json +++ b/webview-ui/src/i18n/locales/nl/settings.json @@ -31,7 +31,6 @@ "mcp": "MCP-servers", "worktrees": "Worktrees", "autoApprove": "Auto-goedkeuren", - "browser": "Browser", "checkpoints": "Checkpoints", "notifications": "Meldingen", "contextManagement": "Context", @@ -241,10 +240,6 @@ "description": "Sta Roo toe om beschermde bestanden (zoals .rooignore en .roo/ configuratiebestanden) aan te maken en te bewerken zonder goedkeuring." } }, - "browser": { - "label": "Browser", - "description": "Automatisch browseracties uitvoeren zonder goedkeuring. Let op: geldt alleen als het model computergebruik ondersteunt." - }, "mcp": { "label": "MCP", "description": "Automatische goedkeuring van individuele MCP-tools in het MCP-serversoverzicht inschakelen (vereist zowel deze instelling als het selectievakje 'Altijd toestaan' bij de tool)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Maximaal aantal output-tokens voor Claude Code-reacties. Standaard is 8000." } }, - "browser": { - "enable": { - "label": "Browserhulpmiddel inschakelen", - "description": "Indien ingeschakeld, kan Roo een browser gebruiken om te interageren met websites wanneer modellen computergebruik ondersteunen. <0>Meer informatie" - }, - "viewport": { - "label": "Viewport-grootte", - "description": "Selecteer de viewport-grootte voor browserinteracties. Dit beïnvloedt hoe websites worden weergegeven en gebruikt.", - "options": { - "largeDesktop": "Groot bureaublad (1280x800)", - "smallDesktop": "Klein bureaublad (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Mobiel (360x640)" - } - }, - "screenshotQuality": { - "label": "Screenshotkwaliteit", - "description": "Pas de WebP-kwaliteit van browserscreenshots aan. Hogere waarden geven duidelijkere screenshots maar verhogen het tokengebruik." - }, - "remote": { - "label": "Gebruik externe browserverbinding", - "description": "Verbind met een Chrome-browser die draait met remote debugging ingeschakeld (--remote-debugging-port=9222).", - "urlPlaceholder": "Aangepaste URL (bijv. http://localhost:9222)", - "testButton": "Verbinding testen", - "testingButton": "Bezig met testen...", - "instructions": "Voer het DevTools Protocol hostadres in of laat leeg om lokale Chrome-instanties automatisch te detecteren. De knop Verbinding testen probeert de aangepaste URL als opgegeven, of detecteert automatisch als het veld leeg is." - } - }, "checkpoints": { "timeout": { "label": "Timeout voor checkpoint-initialisatie (seconden)", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 026f0ce2888..be012354aa0 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -363,44 +363,6 @@ "total": "Całkowity koszt: ${{cost}}", "includesSubtasks": "Zawiera koszty podzadań" }, - "browser": { - "session": "Sesja przeglądarki", - "active": "Aktywna", - "rooWantsToUse": "Roo chce użyć przeglądarki", - "consoleLogs": "Logi konsoli", - "noNewLogs": "(Brak nowych logów)", - "screenshot": "Zrzut ekranu przeglądarki", - "cursor": "kursor", - "navigation": { - "step": "Krok {{current}} z {{total}}", - "previous": "Poprzedni", - "next": "Następny" - }, - "sessionStarted": "Sesja przeglądarki rozpoczęta", - "actions": { - "title": "Akcja przeglądarki: ", - "launched": "Przeglądarka uruchomiona", - "launch": "Uruchom przeglądarkę na {{url}}", - "clicked": "Kliknięto ({{coordinate}})", - "click": "Kliknij ({{coordinate}})", - "typed": "Wpisano: {{text}}", - "type": "Wpisz \"{{text}}\"", - "pressed": "Naciśnięto: {{key}}", - "press": "Naciśnij {{key}}", - "scrolledDown": "Przewinięto w dół", - "scrollDown": "Przewiń w dół", - "scrolledUp": "Przewinięto w górę", - "scrollUp": "Przewiń w górę", - "hovered": "Najechano ({{coordinate}})", - "hover": "Najedź ({{coordinate}})", - "resized": "Zmieniono rozmiar na: {{size}}", - "resize": "Zmień rozmiar na {{size}}", - "screenshotSaved": "Zrzut ekranu zapisany", - "screenshot": "Zapisz zrzut ekranu do {{path}}", - "closed": "Przeglądarka zamknięta", - "close": "Zamknij przeglądarkę" - } - }, "codeblock": { "tooltips": { "expand": "Rozwiń blok kodu", diff --git a/webview-ui/src/i18n/locales/pl/prompts.json b/webview-ui/src/i18n/locales/pl/prompts.json index b1fb3317c0c..7aa686af04d 100644 --- a/webview-ui/src/i18n/locales/pl/prompts.json +++ b/webview-ui/src/i18n/locales/pl/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Czytaj pliki", "edit": "Edytuj pliki", - "browser": "Używaj przeglądarki", "command": "Uruchamiaj polecenia", "mcp": "Używaj MCP" }, diff --git a/webview-ui/src/i18n/locales/pl/settings.json b/webview-ui/src/i18n/locales/pl/settings.json index 7dc5dfa7661..875f00838da 100644 --- a/webview-ui/src/i18n/locales/pl/settings.json +++ b/webview-ui/src/i18n/locales/pl/settings.json @@ -31,7 +31,6 @@ "mcp": "Serwery MCP", "worktrees": "Worktrees", "autoApprove": "Auto-zatwierdzanie", - "browser": "Dostęp komputera", "checkpoints": "Punkty kontrolne", "notifications": "Powiadomienia", "contextManagement": "Kontekst", @@ -241,10 +240,6 @@ "description": "Pozwól Roo na tworzenie i edycję plików chronionych (takich jak .rooignore i pliki konfiguracyjne .roo/) bez konieczności zatwierdzania." } }, - "browser": { - "label": "Przeglądarka", - "description": "Automatycznie wykonuj akcje przeglądarki bez konieczności zatwierdzania. Uwaga: Dotyczy tylko gdy model obsługuje używanie komputera" - }, "mcp": { "label": "MCP", "description": "Włącz automatyczne zatwierdzanie poszczególnych narzędzi MCP w widoku Serwerów MCP (wymaga zarówno tego ustawienia, jak i pola wyboru \"Zawsze zezwalaj\" narzędzia)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Maksymalna liczba tokenów wyjściowych dla odpowiedzi Claude Code. Domyślnie 8000." } }, - "browser": { - "enable": { - "label": "Włącz narzędzie przeglądarki", - "description": "Gdy włączone, Roo może używać przeglądarki do interakcji ze stronami internetowymi podczas korzystania z modeli obsługujących używanie komputera. <0>Dowiedz się więcej" - }, - "viewport": { - "label": "Rozmiar viewportu", - "description": "Wybierz rozmiar viewportu dla interakcji przeglądarki. Wpływa to na sposób wyświetlania stron internetowych i interakcji z nimi.", - "options": { - "largeDesktop": "Duży pulpit (1280x800)", - "smallDesktop": "Mały pulpit (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Telefon (360x640)" - } - }, - "screenshotQuality": { - "label": "Jakość zrzutów ekranu", - "description": "Dostosuj jakość WebP zrzutów ekranu przeglądarki. Wyższe wartości zapewniają wyraźniejsze zrzuty ekranu, ale zwiększają zużycie token." - }, - "remote": { - "label": "Użyj zdalnego połączenia przeglądarki", - "description": "Połącz się z przeglądarką Chrome uruchomioną z włączonym zdalnym debugowaniem (--remote-debugging-port=9222).", - "urlPlaceholder": "Niestandardowy URL (np. http://localhost:9222)", - "testButton": "Testuj połączenie", - "testingButton": "Testowanie...", - "instructions": "Wprowadź adres hosta protokołu DevTools lub pozostaw puste, aby automatycznie wykryć lokalne instancje Chrome. Przycisk Test Połączenia spróbuje użyć niestandardowego URL, jeśli podany, lub automatycznie wykryje, jeśli pole jest puste." - } - }, "checkpoints": { "timeout": { "label": "Limit czasu inicjalizacji punktu kontrolnego (sekundy)", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index ac208229128..ed4eaf541ea 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -363,44 +363,6 @@ "total": "Custo Total: ${{cost}}", "includesSubtasks": "Inclui custos de subtarefas" }, - "browser": { - "session": "Sessão do Navegador", - "active": "Ativo", - "rooWantsToUse": "Roo quer usar o navegador", - "consoleLogs": "Logs do console", - "noNewLogs": "(Sem novos logs)", - "screenshot": "Captura de tela do navegador", - "cursor": "cursor", - "navigation": { - "step": "Passo {{current}} de {{total}}", - "previous": "Anterior", - "next": "Próximo" - }, - "sessionStarted": "Sessão do navegador iniciada", - "actions": { - "title": "Ação do navegador: ", - "launched": "Navegador iniciado", - "launch": "Iniciar navegador em {{url}}", - "clicked": "Clicado em: {{coordinate}}", - "click": "Clique ({{coordinate}})", - "typed": "Digitado: {{text}}", - "type": "Digitar \"{{text}}\"", - "pressed": "Pressionado: {{key}}", - "press": "Pressione {{key}}", - "scrolledDown": "Rolado para baixo", - "scrollDown": "Rolar para baixo", - "scrolledUp": "Rolado para cima", - "scrollUp": "Rolar para cima", - "hovered": "Pairado em: {{coordinate}}", - "hover": "Pairar ({{coordinate}})", - "resized": "Redimensionado para: {{size}}", - "resize": "Redimensionar para {{size}}", - "screenshotSaved": "Captura de tela salva", - "screenshot": "Salvar captura de tela em {{path}}", - "closed": "Navegador fechado", - "close": "Fechar navegador" - } - }, "codeblock": { "tooltips": { "expand": "Expandir bloco de código", diff --git a/webview-ui/src/i18n/locales/pt-BR/prompts.json b/webview-ui/src/i18n/locales/pt-BR/prompts.json index cbec033b407..1cef958f594 100644 --- a/webview-ui/src/i18n/locales/pt-BR/prompts.json +++ b/webview-ui/src/i18n/locales/pt-BR/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Ler arquivos", "edit": "Editar arquivos", - "browser": "Usar navegador", "command": "Executar comandos", "mcp": "Usar MCP" }, diff --git a/webview-ui/src/i18n/locales/pt-BR/settings.json b/webview-ui/src/i18n/locales/pt-BR/settings.json index af1e68e8240..0d77ea6e5aa 100644 --- a/webview-ui/src/i18n/locales/pt-BR/settings.json +++ b/webview-ui/src/i18n/locales/pt-BR/settings.json @@ -31,7 +31,6 @@ "mcp": "Servidores MCP", "worktrees": "Worktrees", "autoApprove": "Aprovação", - "browser": "Navegador", "checkpoints": "Checkpoints", "notifications": "Notificações", "contextManagement": "Contexto", @@ -241,10 +240,6 @@ "description": "Permitir que o Roo crie e edite arquivos protegidos (como .rooignore e arquivos de configuração .roo/) sem exigir aprovação." } }, - "browser": { - "label": "Navegador", - "description": "Realizar ações do navegador automaticamente sem exigir aprovação. Nota: Aplica-se apenas quando o modelo suporta uso do computador" - }, "mcp": { "label": "MCP", "description": "Ativar aprovação automática de ferramentas MCP individuais na visualização de Servidores MCP (requer tanto esta configuração quanto a caixa de seleção \"Permitir sempre\" da ferramenta)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Número máximo de tokens de saída para respostas do Claude Code. O padrão é 8000." } }, - "browser": { - "enable": { - "label": "Ativar ferramenta de navegador", - "description": "Quando ativado, o Roo pode usar um navegador para interagir com sites ao usar modelos que suportam o uso do computador. <0>Saiba mais" - }, - "viewport": { - "label": "Tamanho da viewport", - "description": "Selecione o tamanho da viewport para interações do navegador. Isso afeta como os sites são exibidos e como se interage com eles.", - "options": { - "largeDesktop": "Desktop grande (1280x800)", - "smallDesktop": "Desktop pequeno (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Móvel (360x640)" - } - }, - "screenshotQuality": { - "label": "Qualidade das capturas de tela", - "description": "Ajuste a qualidade WebP das capturas de tela do navegador. Valores mais altos fornecem capturas mais nítidas, mas aumentam o uso de token." - }, - "remote": { - "label": "Usar conexão remota de navegador", - "description": "Conectar a um navegador Chrome executando com depuração remota ativada (--remote-debugging-port=9222).", - "urlPlaceholder": "URL personalizado (ex. http://localhost:9222)", - "testButton": "Testar conexão", - "testingButton": "Testando...", - "instructions": "Digite o endereço do host do protocolo DevTools ou deixe em branco para descobrir automaticamente instâncias locais do Chrome. O botão Testar Conexão tentará usar o URL personalizado, se fornecido, ou descobrirá automaticamente se o campo estiver vazio." - } - }, "checkpoints": { "timeout": { "label": "Tempo limite para inicialização do checkpoint (segundos)", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 5d6206b84f2..15d8c9ee990 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -364,44 +364,6 @@ "total": "Общая стоимость: ${{cost}}", "includesSubtasks": "Включает стоимость подзадач" }, - "browser": { - "session": "Сеанс браузера", - "active": "Активен", - "rooWantsToUse": "Roo хочет использовать браузер", - "consoleLogs": "Логи консоли", - "noNewLogs": "(Новых логов нет)", - "screenshot": "Скриншот браузера", - "cursor": "курсор", - "navigation": { - "step": "Шаг {{current}} из {{total}}", - "previous": "Предыдущий", - "next": "Следующий" - }, - "sessionStarted": "Сессия браузера запущена", - "actions": { - "title": "Действие браузера: ", - "launched": "Браузер открыт", - "launch": "Открыть браузер по адресу {{url}}", - "clicked": "Клик в: {{coordinate}}", - "click": "Клик ({{coordinate}})", - "typed": "Введено: {{text}}", - "type": "Ввести \"{{text}}\"", - "pressed": "Нажато: {{key}}", - "press": "Нажать {{key}}", - "scrolledDown": "Прокручено вниз", - "scrollDown": "Прокрутить вниз", - "scrolledUp": "Прокручено вверх", - "scrollUp": "Прокрутить вверх", - "hovered": "Наведено в: {{coordinate}}", - "hover": "Навести ({{coordinate}})", - "resized": "Размер изменен на: {{size}}", - "resize": "Изменить размер на {{size}}", - "screenshotSaved": "Снимок экрана сохранён", - "screenshot": "Сохранить снимок экрана в {{path}}", - "closed": "Браузер закрыт", - "close": "Закрыть браузер" - } - }, "codeblock": { "tooltips": { "expand": "Развернуть блок кода", diff --git a/webview-ui/src/i18n/locales/ru/prompts.json b/webview-ui/src/i18n/locales/ru/prompts.json index 3cc222a819f..5af112ceaa9 100644 --- a/webview-ui/src/i18n/locales/ru/prompts.json +++ b/webview-ui/src/i18n/locales/ru/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Чтение файлов", "edit": "Редактирование файлов", - "browser": "Использовать браузер", "command": "Выполнять команды", "mcp": "Использовать MCP" }, diff --git a/webview-ui/src/i18n/locales/ru/settings.json b/webview-ui/src/i18n/locales/ru/settings.json index 94e9a78ff51..87563bdab66 100644 --- a/webview-ui/src/i18n/locales/ru/settings.json +++ b/webview-ui/src/i18n/locales/ru/settings.json @@ -31,7 +31,6 @@ "mcp": "Серверы MCP", "worktrees": "Worktrees", "autoApprove": "Автоодобрение", - "browser": "Доступ к компьютеру", "checkpoints": "Контрольные точки", "notifications": "Уведомления", "contextManagement": "Контекст", @@ -241,10 +240,6 @@ "description": "Разрешить Roo создавать и редактировать защищенные файлы (такие как .rooignore и файлы конфигурации .roo/) без необходимости одобрения." } }, - "browser": { - "label": "Браузер", - "description": "Автоматически выполнять действия в браузере без необходимости одобрения. Применяется только, если модель поддерживает использование компьютера" - }, "mcp": { "label": "MCP", "description": "Включить автоодобрение отдельных инструментов MCP в представлении MCP Servers (требуется включить как этот параметр, так и индивидуальный чекбокс инструмента \"Всегда разрешать\")" @@ -592,34 +587,6 @@ "maxTokensDescription": "Максимальное количество выходных токенов для ответов Claude Code. По умолчанию 8000." } }, - "browser": { - "enable": { - "label": "Включить инструмент браузера", - "description": "Если включено, Roo может использовать браузер для взаимодействия с сайтами при использовании моделей, поддерживающих работу с компьютером. <0>Подробнее" - }, - "viewport": { - "label": "Размер окна просмотра", - "description": "Выберите размер окна для взаимодействия с браузером. Влияет на отображение и взаимодействие с сайтами.", - "options": { - "largeDesktop": "Большой рабочий стол (1280x800)", - "smallDesktop": "Маленький рабочий стол (900x600)", - "tablet": "Планшет (768x1024)", - "mobile": "Мобильный (360x640)" - } - }, - "screenshotQuality": { - "label": "Качество скриншота", - "description": "Настройте качество WebP для скриншотов браузера. Более высокие значения дают более чёткие изображения, но увеличивают расход токенов." - }, - "remote": { - "label": "Использовать удалённое подключение к браузеру", - "description": "Подключиться к Chrome с включённым удалённым дебагом (--remote-debugging-port=9222).", - "urlPlaceholder": "Пользовательский URL (например, http://localhost:9222)", - "testButton": "Проверить соединение", - "testingButton": "Проверка...", - "instructions": "Введите адрес DevTools Protocol или оставьте поле пустым для автоматического поиска локальных экземпляров Chrome. Кнопка проверки попробует пользовательский URL, если он указан, или выполнит автопоиск." - } - }, "checkpoints": { "timeout": { "label": "Таймаут инициализации контрольной точки (секунды)", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index db854b6a3df..e9552d5bfd1 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -364,44 +364,6 @@ "total": "Toplam Maliyet: ${{cost}}", "includesSubtasks": "Alt görev maliyetlerini içerir" }, - "browser": { - "session": "Tarayıcı Oturumu", - "active": "Aktif", - "rooWantsToUse": "Roo tarayıcıyı kullanmak istiyor", - "consoleLogs": "Konsol Kayıtları", - "noNewLogs": "(Yeni kayıt yok)", - "screenshot": "Tarayıcı ekran görüntüsü", - "cursor": "imleç", - "navigation": { - "step": "Adım {{current}} / {{total}}", - "previous": "Önceki", - "next": "Sonraki" - }, - "sessionStarted": "Tarayıcı Oturumu Başlatıldı", - "actions": { - "title": "Tarayıcı Eylemi: ", - "launched": "Tarayıcı başlatıldı", - "launch": "{{url}} adresinde tarayıcı başlat", - "clicked": "Tıklandı ({{coordinate}})", - "click": "Tıkla ({{coordinate}})", - "typed": "Yazıldı: {{text}}", - "type": "Yaz \"{{text}}\"", - "pressed": "Basıldı: {{key}}", - "press": "{{key}} tuşuna bas", - "scrolledDown": "Aşağı kaydırıldı", - "scrollDown": "Aşağı kaydır", - "scrolledUp": "Yukarı kaydırıldı", - "scrollUp": "Yukarı kaydır", - "hovered": "Üzerine gelinildi ({{coordinate}})", - "hover": "Üzerine gel ({{coordinate}})", - "resized": "Boyut değiştirildi: {{size}}", - "resize": "Boyutu değiştir {{size}}", - "screenshotSaved": "Ekran görüntüsü kaydedildi", - "screenshot": "Ekran görüntüsünü {{path}} yoluna kaydet", - "closed": "Tarayıcı kapatıldı", - "close": "Tarayıcıyı kapat" - } - }, "codeblock": { "tooltips": { "expand": "Kod bloğunu genişlet", diff --git a/webview-ui/src/i18n/locales/tr/prompts.json b/webview-ui/src/i18n/locales/tr/prompts.json index b2771d32fef..543fd888980 100644 --- a/webview-ui/src/i18n/locales/tr/prompts.json +++ b/webview-ui/src/i18n/locales/tr/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Dosyaları Oku", "edit": "Dosyaları Düzenle", - "browser": "Tarayıcı Kullan", "command": "Komutları Çalıştır", "mcp": "MCP Kullan" }, diff --git a/webview-ui/src/i18n/locales/tr/settings.json b/webview-ui/src/i18n/locales/tr/settings.json index df219f179d0..ee46b9f01cd 100644 --- a/webview-ui/src/i18n/locales/tr/settings.json +++ b/webview-ui/src/i18n/locales/tr/settings.json @@ -31,7 +31,6 @@ "mcp": "MCP Sunucuları", "worktrees": "Worktrees", "autoApprove": "Oto-Onay", - "browser": "Bilgisayar Erişimi", "checkpoints": "Kontrol Noktaları", "notifications": "Bildirimler", "contextManagement": "Bağlam", @@ -241,10 +240,6 @@ "description": "Roo'nun korumalı dosyaları (.rooignore ve .roo/ yapılandırma dosyaları gibi) onay gerektirmeden oluşturmasına ve düzenlemesine izin ver." } }, - "browser": { - "label": "Tarayıcı", - "description": "Onay gerektirmeden otomatik olarak tarayıcı eylemleri gerçekleştir. Not: Yalnızca model bilgisayar kullanımını desteklediğinde geçerlidir" - }, "mcp": { "label": "MCP", "description": "MCP Sunucuları görünümünde bireysel MCP araçlarının otomatik onayını etkinleştir (hem bu ayar hem de aracın \"Her zaman izin ver\" onay kutusu gerekir)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Claude Code yanıtları için maksimum çıktı token sayısı. Varsayılan 8000'dir." } }, - "browser": { - "enable": { - "label": "Tarayıcı aracını etkinleştir", - "description": "Etkinleştirildiğinde, Roo bilgisayar kullanımını destekleyen modeller kullanırken web siteleriyle etkileşim kurmak için bir tarayıcı kullanabilir. <0>Daha fazla bilgi" - }, - "viewport": { - "label": "Görünüm alanı boyutu", - "description": "Tarayıcı etkileşimleri için görünüm alanı boyutunu seçin. Bu, web sitelerinin nasıl görüntülendiğini ve etkileşime girdiğini etkiler.", - "options": { - "largeDesktop": "Büyük Masaüstü (1280x800)", - "smallDesktop": "Küçük Masaüstü (900x600)", - "tablet": "Tablet (768x1024)", - "mobile": "Mobil (360x640)" - } - }, - "screenshotQuality": { - "label": "Ekran görüntüsü kalitesi", - "description": "Tarayıcı ekran görüntülerinin WebP kalitesini ayarlayın. Daha yüksek değerler daha net ekran görüntüleri sağlar ancak token kullanımını artırır." - }, - "remote": { - "label": "Uzak tarayıcı bağlantısı kullan", - "description": "Uzaktan hata ayıklama etkinleştirilmiş olarak çalışan bir Chrome tarayıcısına bağlanın (--remote-debugging-port=9222).", - "urlPlaceholder": "Özel URL (örn. http://localhost:9222)", - "testButton": "Bağlantıyı Test Et", - "testingButton": "Test Ediliyor...", - "instructions": "DevTools protokolü ana bilgisayar adresini girin veya yerel Chrome örneklerini otomatik olarak keşfetmek için boş bırakın. Bağlantıyı Test Et düğmesi, sağlanmışsa özel URL'yi deneyecek veya alan boşsa otomatik olarak keşfedecektir." - } - }, "checkpoints": { "timeout": { "label": "Kontrol noktası başlatma zaman aşımı (saniye)", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 72391ddced9..dfa5b56c4b2 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -364,44 +364,6 @@ "total": "Tổng chi phí: ${{cost}}", "includesSubtasks": "Bao gồm chi phí của các tác vụ phụ" }, - "browser": { - "session": "Phiên trình duyệt", - "active": "Đang hoạt động", - "rooWantsToUse": "Roo muốn sử dụng trình duyệt", - "consoleLogs": "Nhật ký bảng điều khiển", - "noNewLogs": "(Không có nhật ký mới)", - "screenshot": "Ảnh chụp màn hình trình duyệt", - "cursor": "con trỏ", - "navigation": { - "step": "Bước {{current}} / {{total}}", - "previous": "Trước", - "next": "Tiếp" - }, - "sessionStarted": "Phiên trình duyệt đã bắt đầu", - "actions": { - "title": "Hành động trình duyệt: ", - "launched": "Trình duyệt đã khởi chạy", - "launch": "Khởi chạy trình duyệt tại {{url}}", - "clicked": "Đã nhấp ({{coordinate}})", - "click": "Nhấp ({{coordinate}})", - "typed": "Đã gõ: {{text}}", - "type": "Gõ \"{{text}}\"", - "pressed": "Đã nhấn: {{key}}", - "press": "Nhấn {{key}}", - "scrolledDown": "Đã cuộn xuống", - "scrollDown": "Cuộn xuống", - "scrolledUp": "Đã cuộn lên", - "scrollUp": "Cuộn lên", - "hovered": "Đã di chuột ({{coordinate}})", - "hover": "Di chuột ({{coordinate}})", - "resized": "Đã thay đổi kích thước: {{size}}", - "resize": "Thay đổi kích thước thành {{size}}", - "screenshotSaved": "Ảnh chụp màn hình đã được lưu", - "screenshot": "Lưu ảnh chụp màn hình vào {{path}}", - "closed": "Trình duyệt đã đóng", - "close": "Đóng trình duyệt" - } - }, "codeblock": { "tooltips": { "expand": "Mở rộng khối mã", diff --git a/webview-ui/src/i18n/locales/vi/prompts.json b/webview-ui/src/i18n/locales/vi/prompts.json index 2583d70306a..7ac6fa5d6a9 100644 --- a/webview-ui/src/i18n/locales/vi/prompts.json +++ b/webview-ui/src/i18n/locales/vi/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "Đọc tệp", "edit": "Chỉnh sửa tệp", - "browser": "Sử dụng trình duyệt", "command": "Chạy lệnh", "mcp": "Sử dụng MCP" }, diff --git a/webview-ui/src/i18n/locales/vi/settings.json b/webview-ui/src/i18n/locales/vi/settings.json index 56641811731..1eb0fb09d30 100644 --- a/webview-ui/src/i18n/locales/vi/settings.json +++ b/webview-ui/src/i18n/locales/vi/settings.json @@ -31,7 +31,6 @@ "mcp": "Máy chủ MCP", "worktrees": "Worktrees", "autoApprove": "Phê duyệt", - "browser": "Trình duyệt", "checkpoints": "Điểm kiểm tra", "notifications": "Thông báo", "contextManagement": "Ngữ cảnh", @@ -241,10 +240,6 @@ "description": "Cho phép Roo tạo và chỉnh sửa các tệp được bảo vệ (như .rooignore và các tệp cấu hình .roo/) mà không yêu cầu phê duyệt." } }, - "browser": { - "label": "Trình duyệt", - "description": "Tự động thực hiện các hành động trình duyệt mà không cần phê duyệt. Lưu ý: Chỉ áp dụng khi mô hình hỗ trợ sử dụng máy tính" - }, "mcp": { "label": "MCP", "description": "Bật tự động phê duyệt các công cụ MCP riêng lẻ trong chế độ xem Máy chủ MCP (yêu cầu cả cài đặt này và hộp kiểm \"Luôn cho phép\" của công cụ)" @@ -592,34 +587,6 @@ "maxTokensDescription": "Số lượng token đầu ra tối đa cho các phản hồi của Claude Code. Mặc định là 8000." } }, - "browser": { - "enable": { - "label": "Bật công cụ trình duyệt", - "description": "Khi được bật, Roo có thể sử dụng trình duyệt để tương tác với các trang web khi sử dụng các mô hình hỗ trợ sử dụng máy tính. <0>Tìm hiểu thêm" - }, - "viewport": { - "label": "Kích thước khung nhìn", - "description": "Chọn kích thước khung nhìn cho tương tác trình duyệt. Điều này ảnh hưởng đến cách trang web được hiển thị và tương tác.", - "options": { - "largeDesktop": "Máy tính để bàn lớn (1280x800)", - "smallDesktop": "Máy tính để bàn nhỏ (900x600)", - "tablet": "Máy tính bảng (768x1024)", - "mobile": "Di động (360x640)" - } - }, - "screenshotQuality": { - "label": "Chất lượng ảnh chụp màn hình", - "description": "Điều chỉnh chất lượng WebP của ảnh chụp màn hình trình duyệt. Giá trị cao hơn cung cấp ảnh chụp màn hình rõ ràng hơn nhưng tăng sử dụng token." - }, - "remote": { - "label": "Sử dụng kết nối trình duyệt từ xa", - "description": "Kết nối với trình duyệt Chrome đang chạy với tính năng gỡ lỗi từ xa được bật (--remote-debugging-port=9222).", - "urlPlaceholder": "URL tùy chỉnh (ví dụ: http://localhost:9222)", - "testButton": "Kiểm tra kết nối", - "testingButton": "Đang kiểm tra...", - "instructions": "Nhập địa chỉ DevTools Protocol hoặc để trống để tự động phát hiện các instance Chrome cục bộ. Nút Kiểm tra kết nối sẽ thử URL tùy chỉnh nếu được cung cấp, hoặc tự động phát hiện nếu trường này trống." - } - }, "checkpoints": { "timeout": { "label": "Thời gian chờ khởi tạo điểm kiểm tra (giây)", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 1bd2121506e..35646dc1dfd 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -364,44 +364,6 @@ "total": "总成本: ${{cost}}", "includesSubtasks": "包括子任务成本" }, - "browser": { - "session": "浏览器会话", - "active": "活动中", - "rooWantsToUse": "Roo想使用浏览器", - "consoleLogs": "控制台日志", - "noNewLogs": "(没有新日志)", - "screenshot": "浏览器截图", - "cursor": "光标", - "navigation": { - "step": "步骤 {{current}} / {{total}}", - "previous": "上一步", - "next": "下一步" - }, - "sessionStarted": "浏览器会话已启动", - "actions": { - "title": "浏览器操作: ", - "launched": "浏览器已启动", - "launch": "访问 {{url}}", - "clicked": "点击位置: {{coordinate}}", - "click": "点击 ({{coordinate}})", - "typed": "输入: {{text}}", - "type": "输入 \"{{text}}\"", - "pressed": "已按 {{key}}", - "press": "按 {{key}}", - "scrolledDown": "已向下滚动", - "scrollDown": "向下滚动", - "scrolledUp": "已向上滚动", - "scrollUp": "向上滚动", - "hovered": "悬停位置: {{coordinate}}", - "hover": "悬停 ({{coordinate}})", - "resized": "大小已更改: {{size}}", - "resize": "调整大小为 {{size}}", - "screenshotSaved": "截图已保存", - "screenshot": "将截图保存到 {{path}}", - "closed": "浏览器已关闭", - "close": "关闭浏览器" - } - }, "codeblock": { "tooltips": { "expand": "展开代码块", diff --git a/webview-ui/src/i18n/locales/zh-CN/prompts.json b/webview-ui/src/i18n/locales/zh-CN/prompts.json index 3ecec96c6d9..84255d8868a 100644 --- a/webview-ui/src/i18n/locales/zh-CN/prompts.json +++ b/webview-ui/src/i18n/locales/zh-CN/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "读取文件", "edit": "编辑文件", - "browser": "浏览器", "command": "运行命令", "mcp": "MCP服务" }, diff --git a/webview-ui/src/i18n/locales/zh-CN/settings.json b/webview-ui/src/i18n/locales/zh-CN/settings.json index 4c2eef0efbb..aa0391ac559 100644 --- a/webview-ui/src/i18n/locales/zh-CN/settings.json +++ b/webview-ui/src/i18n/locales/zh-CN/settings.json @@ -31,7 +31,6 @@ "mcp": "MCP 服务", "worktrees": "Worktrees", "autoApprove": "自动批准", - "browser": "计算机交互", "checkpoints": "存档点", "notifications": "通知", "contextManagement": "上下文", @@ -241,10 +240,6 @@ "description": "允许 Roo 创建和编辑受保护的文件(如 .rooignore 和 .roo/ 配置文件),无需批准。" } }, - "browser": { - "label": "浏览器", - "description": "自动执行浏览器操作而无需批准 — 注意:仅当模型支持计算机功能调用时适用" - }, "mcp": { "label": "MCP", "description": "允许自动调用MCP服务而无需批准" @@ -592,34 +587,6 @@ "maxTokensDescription": "Claude Code 响应的最大输出 Token 数量。默认为 8000。" } }, - "browser": { - "enable": { - "label": "启用浏览器工具", - "description": "启用后,若模型支持计算机功能调用,Roo 可以使用浏览器与网站交互。 <0>了解更多" - }, - "viewport": { - "label": "视口大小", - "description": "选择浏览器交互的视口大小。这会影响网站的显示方式和交互方式。", - "options": { - "largeDesktop": "大桌面 (1280x800)", - "smallDesktop": "小桌面 (900x600)", - "tablet": "平板 (768x1024)", - "mobile": "移动设备 (360x640)" - } - }, - "screenshotQuality": { - "label": "截图质量", - "description": "调整浏览器的截图质量。更高的值提供更清晰的截图,但会增加 token 消耗。" - }, - "remote": { - "label": "使用远程浏览器连接", - "description": "连接到启用远程调试的 Chrome 浏览器 (--remote-debugging-port=9222)。", - "urlPlaceholder": "自定义 URL(例如 http://localhost:9222)", - "testButton": "测试连接", - "testingButton": "测试中...", - "instructions": "输入 DevTools 协议主机地址或留空以自动发现本地 Chrome 实例。测试连接按钮将尝试使用自定义 URL(如果提供),或者如果字段为空则自动发现。" - } - }, "checkpoints": { "timeout": { "label": "存档点初始化超时时间(秒)", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index e26bb516bb1..8009d33f21a 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -393,44 +393,6 @@ "total": "總成本:${{cost}}", "includesSubtasks": "包含子任務成本" }, - "browser": { - "session": "瀏覽器工作階段", - "active": "使用中", - "rooWantsToUse": "Roo 想要使用瀏覽器", - "consoleLogs": "主控台記錄", - "noNewLogs": "(沒有新記錄)", - "screenshot": "瀏覽器螢幕擷圖", - "cursor": "游標", - "navigation": { - "step": "步驟 {{current}}/{{total}}", - "previous": "上一步", - "next": "下一步" - }, - "sessionStarted": "瀏覽器工作階段已啟動", - "actions": { - "title": "瀏覽器動作:", - "launched": "瀏覽器已啟動", - "launch": "在 {{url}} 啟動瀏覽器", - "clicked": "點選位置:{{coordinate}}", - "click": "點選 ({{coordinate}})", - "typed": "輸入:{{text}}", - "type": "輸入「{{text}}」", - "pressed": "已按下 {{key}}", - "press": "按下 {{key}}", - "scrolledDown": "已向下捲動", - "scrollDown": "向下捲動", - "scrolledUp": "已向上捲動", - "scrollUp": "向上捲動", - "hovered": "滑鼠停留位置:{{coordinate}}", - "hover": "滑鼠停留 ({{coordinate}})", - "resized": "大小已變更:{{size}}", - "resize": "調整大小為 {{size}}", - "screenshotSaved": "螢幕擷圖已儲存", - "screenshot": "將螢幕擷圖儲存至 {{path}}", - "closed": "瀏覽器已關閉", - "close": "關閉瀏覽器" - } - }, "codeblock": { "tooltips": { "expand": "展開程式碼區塊", diff --git a/webview-ui/src/i18n/locales/zh-TW/prompts.json b/webview-ui/src/i18n/locales/zh-TW/prompts.json index 2620472af9b..89e84824ba8 100644 --- a/webview-ui/src/i18n/locales/zh-TW/prompts.json +++ b/webview-ui/src/i18n/locales/zh-TW/prompts.json @@ -25,7 +25,6 @@ "toolNames": { "read": "讀取檔案", "edit": "編輯檔案", - "browser": "使用瀏覽器", "command": "執行命令", "mcp": "使用 MCP" }, diff --git a/webview-ui/src/i18n/locales/zh-TW/settings.json b/webview-ui/src/i18n/locales/zh-TW/settings.json index cf27a25498c..0e51b95b6af 100644 --- a/webview-ui/src/i18n/locales/zh-TW/settings.json +++ b/webview-ui/src/i18n/locales/zh-TW/settings.json @@ -31,7 +31,6 @@ "mcp": "MCP 伺服器", "worktrees": "Worktree", "autoApprove": "自動核准", - "browser": "瀏覽器", "checkpoints": "檢查點", "notifications": "通知", "contextManagement": "上下文", @@ -249,10 +248,6 @@ "description": "允許 Roo 建立與編輯受保護的檔案(如 .rooignore 和 .roo/ 設定檔)且無需核准。" } }, - "browser": { - "label": "瀏覽器", - "description": "無需核准即可自動執行瀏覽器動作。注意:僅適用於支援電腦操作的模型。" - }, "mcp": { "label": "MCP", "description": "啟用 MCP 伺服器檢視中個別 MCP 工具的自動核准(需同時啟用此設定與該工具的「始終允許」核取方塊)" @@ -602,34 +597,6 @@ "maxTokensDescription": "Claude Code 回應的最大輸出 Token 數量。預設為 8000。" } }, - "browser": { - "enable": { - "label": "啟用瀏覽器工具", - "description": "啟用後,Roo 可在使用支援電腦使用的模型時使用瀏覽器與網站互動。 <0>了解更多" - }, - "viewport": { - "label": "視窗大小", - "description": "選擇瀏覽器互動的視窗大小。這會影響網站的顯示方式和互動方式。", - "options": { - "largeDesktop": "大型桌面 (1280x800)", - "smallDesktop": "小型桌面 (900x600)", - "tablet": "平板 (768x1024)", - "mobile": "行動裝置 (360x640)" - } - }, - "screenshotQuality": { - "label": "截圖品質", - "description": "調整瀏覽器截圖的 WebP 品質。數值越高截圖越清晰,但會增加 Token 用量。" - }, - "remote": { - "label": "使用遠端瀏覽器連線", - "description": "連線到啟用遠端除錯的 Chrome 瀏覽器(--remote-debugging-port=9222)。", - "urlPlaceholder": "自訂 URL(例如 http://localhost:9222)", - "testButton": "測試連線", - "testingButton": "測試中...", - "instructions": "請輸入 DevTools Protocol 主機位址,或留空以自動偵測本機 Chrome 執行個體。「測試連線」按鈕將嘗試連線至您提供的自訂 URL,若未提供則會自動偵測。" - } - }, "checkpoints": { "timeout": { "label": "檢查點初始化逾時(秒)", diff --git a/webview-ui/vite.config.ts b/webview-ui/vite.config.ts index 40c1e449b95..e60a3fada64 100644 --- a/webview-ui/vite.config.ts +++ b/webview-ui/vite.config.ts @@ -110,7 +110,7 @@ export default defineConfig(({ mode }) => { sourcemap: true, // Ensure source maps are properly included in the build minify: mode === "production" ? "esbuild" : false, - // Use a single combined CSS bundle so both webviews share styles + // Use a single combined CSS bundle so all webviews share styles cssCodeSplit: false, rollupOptions: { // Externalize vscode module - it's imported by file-search.ts which is @@ -119,7 +119,6 @@ export default defineConfig(({ mode }) => { external: ["vscode"], input: { index: resolve(__dirname, "index.html"), - "browser-panel": resolve(__dirname, "browser-panel.html"), }, output: { entryFileNames: `assets/[name].js`, From 0f7c8202352f35bb52799e62fe128e6815675f47 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 11 Feb 2026 16:00:53 -0700 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20address=20review=20feedback=20?= =?UTF-8?q?=E2=80=94=20restore=20isStealthModel,=20remove=20browser=20resi?= =?UTF-8?q?duals=20and=20dead=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Restore isStealthModel lookup in generateSystemPrompt.ts that was accidentally removed with the browser modelInfo fetch - Remove residual screenshotQuality and cachedChromeHostUrl from GlobalSettings, ExtensionState, ClineProvider, and ExtensionStateContext - Remove unused containsXmlToolMarkup function from presentAssistantMessage.ts --- packages/types/src/global-settings.ts | 5 --- packages/types/src/vscode-extension-host.ts | 2 - .../presentAssistantMessage.ts | 43 ------------------- src/core/webview/ClineProvider.ts | 3 -- src/core/webview/generateSystemPrompt.ts | 12 ++++++ .../SettingsView.change-detection.spec.tsx | 1 - .../SettingsView.unsaved-changes.spec.tsx | 1 - .../src/context/ExtensionStateContext.tsx | 5 --- 8 files changed, 12 insertions(+), 60 deletions(-) diff --git a/packages/types/src/global-settings.ts b/packages/types/src/global-settings.ts index 5886bd18df4..ff58ba8d301 100644 --- a/packages/types/src/global-settings.ts +++ b/packages/types/src/global-settings.ts @@ -147,9 +147,6 @@ export const globalSettingsSchema = z.object({ */ maxDiagnosticMessages: z.number().optional(), - screenshotQuality: z.number().optional(), - cachedChromeHostUrl: z.string().optional(), - enableCheckpoints: z.boolean().optional(), checkpointTimeout: z .number() @@ -345,8 +342,6 @@ export const EVALS_SETTINGS: RooCodeSettings = { commandTimeoutAllowlist: [], preventCompletionWithOpenTodos: false, - screenshotQuality: 75, - ttsEnabled: false, ttsSpeed: 1, soundEnabled: false, diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index 47a351e1abe..a72f087d24c 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -266,8 +266,6 @@ export type ExtensionState = Pick< | "deniedCommands" | "allowedMaxRequests" | "allowedMaxCost" - | "screenshotQuality" - | "cachedChromeHostUrl" | "ttsEnabled" | "ttsSpeed" | "soundEnabled" diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index a1b1673ce75..6d7d0c366cc 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -998,46 +998,3 @@ async function checkpointSaveAndMark(task: Task) { console.error(`[Task#presentAssistantMessage] Error saving checkpoint: ${error.message}`, error) } } - -function containsXmlToolMarkup(text: string): boolean { - // Keep this intentionally narrow: only reject XML-style tool tags matching our tool names. - // Avoid regex so we don't keep legacy XML parsing artifacts around. - // Note: This is a best-effort safeguard; tool_use blocks without an id are rejected elsewhere. - - // First, strip out content inside markdown code fences to avoid false positives - // when users paste documentation or examples containing tool tag references. - // This handles both fenced code blocks (```) and inline code (`). - const textWithoutCodeBlocks = text - .replace(/```[\s\S]*?```/g, "") // Remove fenced code blocks - .replace(/`[^`]+`/g, "") // Remove inline code - - const lower = textWithoutCodeBlocks.toLowerCase() - if (!lower.includes("<") || !lower.includes(">")) { - return false - } - - const toolNames = [ - "access_mcp_resource", - "apply_diff", - "apply_patch", - "ask_followup_question", - "attempt_completion", - "codebase_search", - "edit_file", - "execute_command", - "generate_image", - "list_files", - "new_task", - "read_command_output", - "read_file", - "search_and_replace", - "search_files", - "search_replace", - "switch_mode", - "update_todo_list", - "use_mcp_tool", - "write_to_file", - ] as const - - return toolNames.some((name) => lower.includes(`<${name}`) || lower.includes(`("newTaskRequireTodos", false), + isStealthModel: modelInfo?.isStealthModel, }, undefined, // todoList undefined, // modelId diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx index 65c311ad54f..ae4c324ef86 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.change-detection.spec.tsx @@ -220,7 +220,6 @@ describe("SettingsView - Change Detection Fix", () => { maxOpenTabsContext: 10, maxWorkspaceFiles: 200, mcpEnabled: false, - screenshotQuality: 75, soundEnabled: false, ttsEnabled: false, ttsSpeed: 1.0, diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx index d2f55818201..99e1f7424b0 100644 --- a/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx +++ b/webview-ui/src/components/settings/__tests__/SettingsView.unsaved-changes.spec.tsx @@ -225,7 +225,6 @@ describe("SettingsView - Unsaved Changes Detection", () => { maxOpenTabsContext: 10, maxWorkspaceFiles: 200, mcpEnabled: false, - screenshotQuality: 75, soundEnabled: false, ttsEnabled: false, ttsSpeed: 1.0, diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index 705dba6dd27..2a48d59d092 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -91,8 +91,6 @@ export interface ExtensionStateContextType extends ExtensionState { checkpointTimeout: number setCheckpointTimeout: (value: number) => void setWriteDelayMs: (value: number) => void - screenshotQuality?: number - setScreenshotQuality: (value: number) => void terminalOutputPreviewSize?: "small" | "medium" | "large" setTerminalOutputPreviewSize: (value: "small" | "medium" | "large") => void mcpEnabled: boolean @@ -212,7 +210,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode checkpointTimeout: DEFAULT_CHECKPOINT_TIMEOUT_SECONDS, // Default to 15 seconds language: "en", // Default language code writeDelayMs: 1000, - screenshotQuality: 75, terminalShellIntegrationTimeout: 4000, mcpEnabled: true, remoteControlEnabled: false, @@ -506,7 +503,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode soundVolume: state.soundVolume, ttsSpeed: state.ttsSpeed, writeDelayMs: state.writeDelayMs, - screenshotQuality: state.screenshotQuality, routerModels: extensionRouterModels, cloudIsAuthenticated: state.cloudIsAuthenticated ?? false, cloudOrganizations: state.cloudOrganizations ?? [], @@ -548,7 +544,6 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode setEnableCheckpoints: (value) => setState((prevState) => ({ ...prevState, enableCheckpoints: value })), setCheckpointTimeout: (value) => setState((prevState) => ({ ...prevState, checkpointTimeout: value })), setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })), - setScreenshotQuality: (value) => setState((prevState) => ({ ...prevState, screenshotQuality: value })), setTerminalOutputPreviewSize: (value) => setState((prevState) => ({ ...prevState, terminalOutputPreviewSize: value })), setTerminalShellIntegrationTimeout: (value) => From a3616b2f2f0c69b623dea564ebc8b5060c9e02a1 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 11 Feb 2026 17:32:51 -0700 Subject: [PATCH 3/4] fix: strip deprecated 'browser' group from custom mode configs before validation Add z.preprocess() to groupEntryArraySchema that filters out deprecated tool groups (currently 'browser') before Zod schema validation runs. This ensures users with older custom_modes.yaml or .roomodes files that include 'browser' in their groups arrays don't get schema validation errors. The fix is at the schema level, so it automatically protects all validation entry points: CustomModesManager (load, watch, update, import), ContextProxy (global settings), and ModesView (UI). Adds 6 new test cases covering string groups, tuple groups, and edge cases. --- packages/types/src/mode.ts | 31 +++++++++++++- packages/types/src/tool.ts | 7 ++++ .../__tests__/CustomModesSettings.spec.ts | 37 ++++++++++++++++ src/core/config/__tests__/ModeConfig.spec.ts | 42 +++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) diff --git a/packages/types/src/mode.ts b/packages/types/src/mode.ts index d01ffa69902..2d539577476 100644 --- a/packages/types/src/mode.ts +++ b/packages/types/src/mode.ts @@ -1,6 +1,6 @@ import { z } from "zod" -import { toolGroupsSchema } from "./tool.js" +import { deprecatedToolGroups, toolGroupsSchema } from "./tool.js" /** * GroupOptions @@ -42,7 +42,24 @@ export type GroupEntry = z.infer * ModeConfig */ -const groupEntryArraySchema = z.array(groupEntrySchema).refine( +/** + * Checks if a group entry references a deprecated tool group. + * Handles both string entries ("browser") and tuple entries (["browser", { ... }]). + */ +function isDeprecatedGroupEntry(entry: unknown): boolean { + if (typeof entry === "string") { + return deprecatedToolGroups.includes(entry) + } + if (Array.isArray(entry) && entry.length >= 1 && typeof entry[0] === "string") { + return deprecatedToolGroups.includes(entry[0]) + } + return false +} + +/** + * Raw schema for validating group entries after deprecated groups are stripped. + */ +const rawGroupEntryArraySchema = z.array(groupEntrySchema).refine( (groups) => { const seen = new Set() @@ -61,6 +78,16 @@ const groupEntryArraySchema = z.array(groupEntrySchema).refine( { message: "Duplicate groups are not allowed" }, ) +/** + * Schema for mode group entries. Preprocesses the input to strip deprecated + * tool groups (e.g., "browser") before validation, ensuring backward compatibility + * with older user configs. + */ +export const groupEntryArraySchema = z.preprocess((val) => { + if (!Array.isArray(val)) return val + return val.filter((entry) => !isDeprecatedGroupEntry(entry)) +}, rawGroupEntryArraySchema) + export const modeConfigSchema = z.object({ slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"), name: z.string().min(1, "Name is required"), diff --git a/packages/types/src/tool.ts b/packages/types/src/tool.ts index e43bb647ec6..4f90b63e9fc 100644 --- a/packages/types/src/tool.ts +++ b/packages/types/src/tool.ts @@ -8,6 +8,13 @@ export const toolGroups = ["read", "edit", "command", "mcp", "modes"] as const export const toolGroupsSchema = z.enum(toolGroups) +/** + * Tool groups that have been removed but may still exist in user config files. + * Used by schema preprocessing to silently strip these before validation, + * preventing errors for users with older configs. + */ +export const deprecatedToolGroups: readonly string[] = ["browser"] + export type ToolGroup = z.infer /** diff --git a/src/core/config/__tests__/CustomModesSettings.spec.ts b/src/core/config/__tests__/CustomModesSettings.spec.ts index b5ab38a9d6b..186ef5aeba7 100644 --- a/src/core/config/__tests__/CustomModesSettings.spec.ts +++ b/src/core/config/__tests__/CustomModesSettings.spec.ts @@ -168,4 +168,41 @@ describe("CustomModesSettings", () => { expect(settings.customModes[0].customInstructions).toBeDefined() }) }) + + describe("deprecated tool group migration", () => { + it("should strip deprecated 'browser' group when validating custom modes settings", () => { + const result = customModesSettingsSchema.parse({ + customModes: [ + { + slug: "test-mode", + name: "Test Mode", + roleDefinition: "Test role", + groups: ["read", "browser", "edit"], + }, + ], + }) + expect(result.customModes[0].groups).toEqual(["read", "edit"]) + }) + + it("should strip deprecated 'browser' from multiple modes in settings", () => { + const result = customModesSettingsSchema.parse({ + customModes: [ + { + slug: "mode-a", + name: "Mode A", + roleDefinition: "Role A", + groups: ["read", "browser"], + }, + { + slug: "mode-b", + name: "Mode B", + roleDefinition: "Role B", + groups: ["browser", "edit", "command"], + }, + ], + }) + expect(result.customModes[0].groups).toEqual(["read"]) + expect(result.customModes[1].groups).toEqual(["edit", "command"]) + }) + }) }) diff --git a/src/core/config/__tests__/ModeConfig.spec.ts b/src/core/config/__tests__/ModeConfig.spec.ts index 68d75761e77..74cbc0c4373 100644 --- a/src/core/config/__tests__/ModeConfig.spec.ts +++ b/src/core/config/__tests__/ModeConfig.spec.ts @@ -248,4 +248,46 @@ describe("CustomModeSchema", () => { expect(() => modeConfigSchema.parse(modeWithUndefined)).toThrow() }) }) + + describe("deprecated tool group migration", () => { + it("should strip deprecated 'browser' string group from mode config", () => { + const result = modeConfigSchema.parse({ + slug: "test-mode", + name: "Test Mode", + roleDefinition: "Test role", + groups: ["read", "browser", "edit"], + }) + expect(result.groups).toEqual(["read", "edit"]) + }) + + it("should strip deprecated 'browser' tuple group from mode config", () => { + const result = modeConfigSchema.parse({ + slug: "test-mode", + name: "Test Mode", + roleDefinition: "Test role", + groups: ["read", ["browser", { fileRegex: ".*", description: "test" }], "edit"], + }) + expect(result.groups).toEqual(["read", "edit"]) + }) + + it("should handle mode config where all groups are deprecated", () => { + const result = modeConfigSchema.parse({ + slug: "test-mode", + name: "Test Mode", + roleDefinition: "Test role", + groups: ["browser"], + }) + expect(result.groups).toEqual([]) + }) + + it("should still reject other invalid group names", () => { + const result = modeConfigSchema.safeParse({ + slug: "test-mode", + name: "Test Mode", + roleDefinition: "Test role", + groups: ["read", "nonexistent"], + }) + expect(result.success).toBe(false) + }) + }) }) From 4a245238b814338a2c7d3185774d21f322a791ab Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Wed, 11 Feb 2026 17:53:49 -0700 Subject: [PATCH 4/4] fix: add type assertion to groupEntryArraySchema to fix web-evals compile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit z.preprocess erases the input type to `unknown`, which propagates through modeConfigSchema → rooCodeSettingsSchema → createRunSchema and breaks zodResolver generic inference in web-evals new-run form. The assertion to `z.ZodType` restores proper input/output type alignment. --- packages/types/src/mode.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/types/src/mode.ts b/packages/types/src/mode.ts index 2d539577476..f981ba7bf9a 100644 --- a/packages/types/src/mode.ts +++ b/packages/types/src/mode.ts @@ -82,11 +82,16 @@ const rawGroupEntryArraySchema = z.array(groupEntrySchema).refine( * Schema for mode group entries. Preprocesses the input to strip deprecated * tool groups (e.g., "browser") before validation, ensuring backward compatibility * with older user configs. + * + * The type assertion to `z.ZodType` is + * required because `z.preprocess` erases the input type to `unknown`, which + * propagates through `modeConfigSchema → rooCodeSettingsSchema → createRunSchema` + * and breaks `zodResolver` generic inference in downstream consumers (e.g., web-evals). */ export const groupEntryArraySchema = z.preprocess((val) => { if (!Array.isArray(val)) return val return val.filter((entry) => !isDeprecatedGroupEntry(entry)) -}, rawGroupEntryArraySchema) +}, rawGroupEntryArraySchema) as z.ZodType export const modeConfigSchema = z.object({ slug: z.string().regex(/^[a-zA-Z0-9-]+$/, "Slug must contain only letters numbers and dashes"),