diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index b9e493a7ac3..66f7a4ef0ee 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -28,7 +28,7 @@ import { supportPrompt } from "../../shared/support-prompt" import { GlobalFileNames } from "../../shared/globalFileNames" import { HistoryItem } from "../../shared/HistoryItem" import { ExtensionMessage } from "../../shared/ExtensionMessage" -import { Mode, PromptComponent, defaultModeSlug } from "../../shared/modes" +import { Mode, PromptComponent, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes" import { experimentDefault } from "../../shared/experiments" import { formatLanguage } from "../../shared/language" import { Terminal, TERMINAL_SHELL_INTEGRATION_TIMEOUT } from "../../integrations/terminal/Terminal" @@ -47,7 +47,8 @@ import { CustomModesManager } from "../config/CustomModesManager" import { buildApiHandler } from "../../api" import { ACTION_NAMES } from "../CodeActionProvider" import { Cline, ClineOptions } from "../Cline" -import { WebviewHTMLManager } from "./WebviewHTMLManager" +import { getNonce } from "./getNonce" +import { getUri } from "./getUri" import { telemetryService } from "../../services/telemetry/TelemetryService" import { getWorkspacePath } from "../../utils/path" import { webviewMessageHandler } from "./webviewMessageHandler" @@ -81,7 +82,6 @@ export class ClineProvider extends EventEmitter implements public readonly contextProxy: ContextProxy public readonly providerSettingsManager: ProviderSettingsManager public readonly customModesManager: CustomModesManager - private readonly webviewHTMLManager: WebviewHTMLManager constructor( readonly context: vscode.ExtensionContext, @@ -92,7 +92,6 @@ export class ClineProvider extends EventEmitter implements this.log("ClineProvider instantiated") this.contextProxy = new ContextProxy(context) - this.webviewHTMLManager = new WebviewHTMLManager(this.contextProxy) ClineProvider.activeInstances.add(this) // Register this provider with the telemetry service to enable it to add @@ -394,8 +393,8 @@ export class ClineProvider extends EventEmitter implements webviewView.webview.html = this.contextProxy.extensionMode === vscode.ExtensionMode.Development - ? await this.webviewHTMLManager.getHMRHtmlContent(webviewView.webview) - : this.webviewHTMLManager.getHtmlContent(webviewView.webview) + ? await this.getHMRHtmlContent(webviewView.webview) + : this.getHtmlContent(webviewView.webview) // Sets up an event listener to listen for messages passed from the webview view context // and executes code based on the message that is recieved @@ -598,6 +597,187 @@ export class ClineProvider extends EventEmitter implements await this.view?.webview.postMessage(message) } + private async getHMRHtmlContent(webview: vscode.Webview): Promise { + // Try to read the port from the file + let localPort = "5173" // Default fallback + try { + const fs = require("fs") + const path = require("path") + const portFilePath = path.resolve(__dirname, "../.vite-port") + + if (fs.existsSync(portFilePath)) { + localPort = fs.readFileSync(portFilePath, "utf8").trim() + console.log(`[ClineProvider:Vite] Using Vite server port from ${portFilePath}: ${localPort}`) + } else { + console.log( + `[ClineProvider:Vite] Port file not found at ${portFilePath}, using default port: ${localPort}`, + ) + } + } catch (err) { + console.error("[ClineProvider:Vite] Failed to read Vite port file:", err) + // Continue with default port if file reading fails + } + + const localServerUrl = `localhost:${localPort}` + + // Check if local dev server is running. + try { + await axios.get(`http://${localServerUrl}`) + } catch (error) { + vscode.window.showErrorMessage(t("common:errors.hmr_not_running")) + + return this.getHtmlContent(webview) + } + + const nonce = getNonce() + + const stylesUri = getUri(webview, this.contextProxy.extensionUri, [ + "webview-ui", + "build", + "assets", + "index.css", + ]) + + const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [ + "node_modules", + "@vscode", + "codicons", + "dist", + "codicon.css", + ]) + + const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) + + const file = "src/index.tsx" + const scriptUri = `http://${localServerUrl}/${file}` + + const reactRefresh = /*html*/ ` + + ` + + const csp = [ + "default-src 'none'", + `font-src ${webview.cspSource}`, + `style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`, + `img-src ${webview.cspSource} data:`, + `script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, + `connect-src https://* https://*.posthog.com ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`, + ] + + return /*html*/ ` + + + + + + + + + + Roo Code + + +
+ ${reactRefresh} + + + + ` + } + + /** + * Defines and returns the HTML that should be rendered within the webview panel. + * + * @remarks This is also the place where references to the React webview build files + * are created and inserted into the webview HTML. + * + * @param webview A reference to the extension webview + * @param extensionUri The URI of the directory containing the extension + * @returns A template string literal containing the HTML that should be + * rendered within the webview panel + */ + private getHtmlContent(webview: vscode.Webview): string { + // Get the local path to main script run in the webview, + // then convert it to a uri we can use in the webview. + + // The CSS file from the React build output + const stylesUri = getUri(webview, this.contextProxy.extensionUri, [ + "webview-ui", + "build", + "assets", + "index.css", + ]) + // The JS file from the React build output + const scriptUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "build", "assets", "index.js"]) + + // The codicon font from the React build output + // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts + // we installed this package in the extension so that we can access it how its intended from the extension (the font file is likely bundled in vscode), and we just import the css fileinto our react app we don't have access to it + // don't forget to add font-src ${webview.cspSource}; + const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [ + "node_modules", + "@vscode", + "codicons", + "dist", + "codicon.css", + ]) + + const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) + + // const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.js")) + + // const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "reset.css")) + // const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "vscode.css")) + + // // Same for stylesheet + // const stylesheetUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, "assets", "main.css")) + + // Use a nonce to only allow a specific script to be run. + /* + content security policy of your webview to only allow scripts that have a specific nonce + create a content security policy meta tag so that only loading scripts with a nonce is allowed + As your extension grows you will likely want to add custom styles, fonts, and/or images to your webview. If you do, you will need to update the content security policy meta tag to explicity allow for these resources. E.g. + + - 'unsafe-inline' is required for styles due to vscode-webview-toolkit's dynamic style injection + - since we pass base64 images to the webview, we need to specify img-src ${webview.cspSource} data:; + + in meta tag we add nonce attribute: A cryptographic nonce (only used once) to allow scripts. The server must generate a unique nonce value each time it transmits a policy. It is critical to provide a nonce that cannot be guessed as bypassing a resource's policy is otherwise trivial. + */ + const nonce = getNonce() + + // Tip: Install the es6-string-html VS Code extension to enable code highlighting below + return /*html*/ ` + + + + + + + + + + + Roo Code + + + +
+ + + + ` + } + /** * Sets up an event listener to listen for messages passed from the webview context and * executes code based on the message that is recieved. diff --git a/src/core/webview/WebviewHTMLManager.ts b/src/core/webview/WebviewHTMLManager.ts deleted file mode 100644 index a0ca1849111..00000000000 --- a/src/core/webview/WebviewHTMLManager.ts +++ /dev/null @@ -1,180 +0,0 @@ -import * as vscode from "vscode" -import axios from "axios" -import { t } from "i18next" -import { ContextProxy } from "../config/ContextProxy" -import { getNonce } from "./getNonce" -import { getUri } from "./getUri" - -/** - * Manages the generation of HTML content for webviews - */ -export class WebviewHTMLManager { - constructor(private readonly contextProxy: ContextProxy) {} - - /** - * Generates HTML content for Hot Module Replacement (development mode) - * - * @param webview A reference to the extension webview - * @returns A promise that resolves to the HTML content - */ - public async getHMRHtmlContent(webview: vscode.Webview): Promise { - // Try to read the port from the file - let localPort = "5173" // Default fallback - try { - const fs = require("fs") - const path = require("path") - const portFilePath = path.resolve(__dirname, "../.vite-port") - - if (fs.existsSync(portFilePath)) { - localPort = fs.readFileSync(portFilePath, "utf8").trim() - console.log(`[WebviewHTMLManager:Vite] Using Vite server port from ${portFilePath}: ${localPort}`) - } else { - console.log( - `[WebviewHTMLManager:Vite] Port file not found at ${portFilePath}, using default port: ${localPort}`, - ) - } - } catch (err) { - console.error("[WebviewHTMLManager:Vite] Failed to read Vite port file:", err) - // Continue with default port if file reading fails - } - - const localServerUrl = `localhost:${localPort}` - - // Check if local dev server is running. - try { - await axios.get(`http://${localServerUrl}`) - } catch (error) { - vscode.window.showErrorMessage(t("common:errors.hmr_not_running")) - - return this.getHtmlContent(webview) - } - - const nonce = getNonce() - - const stylesUri = getUri(webview, this.contextProxy.extensionUri, [ - "webview-ui", - "build", - "assets", - "index.css", - ]) - - const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [ - "node_modules", - "@vscode", - "codicons", - "dist", - "codicon.css", - ]) - - const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) - - const file = "src/index.tsx" - const scriptUri = `http://${localServerUrl}/${file}` - - const reactRefresh = /*html*/ ` - - ` - - // Content Security Policy - const csp = [ - "default-src 'none'", - "font-src 'self' data: https://fonts.gstatic.com", - `style-src ${webview.cspSource} 'unsafe-inline' https://fonts.googleapis.com`, - `img-src ${webview.cspSource} data: https: http:`, - `script-src 'unsafe-eval' ${webview.cspSource} https://* https://*.posthog.com http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`, - "connect-src https://openrouter.ai https://api.requesty.ai https://us.i.posthog.com https://us-assets.i.posthog.com https://api.anthropic.com https://api.openai.com https://api.deepseek.com https://api.unbound.ai https://api.glama.ai https://api.gemini.ai https://api.vertex.ai https://api.aws.amazon.com https://api.ollama.ai https://api.lmstudio.ai ws: wss: http: https:", - ] - - return /*html*/ ` - - - - - - - - - - Roo Code - - -
- ${reactRefresh} - - - - ` - } - - /** - * Defines and returns the HTML that should be rendered within the webview panel. - * - * @remarks This is also the place where references to the React webview build files - * are created and inserted into the webview HTML. - * - * @param webview A reference to the extension webview - * @returns A template string literal containing the HTML that should be - * rendered within the webview panel - */ - public getHtmlContent(webview: vscode.Webview): string { - // Get the local path to main script run in the webview, - // then convert it to a uri we can use in the webview. - const stylesUri = getUri(webview, this.contextProxy.extensionUri, [ - "webview-ui", - "build", - "assets", - "index.css", - ]) - - // The JS file from the React build output - const scriptUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "build", "assets", "index.js"]) - - // The codicon font from the React build output - // https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts - // we installed this package in the extension so that we can access it how its intended from the extension (the font file is likely bundled in vscode), and we just import the css fileinto our react app we don't have access to it - // don't forget to add font-src ${webview.cspSource}; - const codiconsUri = getUri(webview, this.contextProxy.extensionUri, [ - "node_modules", - "@vscode", - "codicons", - "dist", - "codicon.css", - ]) - - const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"]) - - const nonce = getNonce() - - // Tip: Install the es6-string-html VS Code extension to enable code highlighting below - return /*html*/ ` - - - - - - - - - - - Roo Code - - - -
- - - - ` - } -}