| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635 |
- import os from "os"
- import * as path from "path"
- import fs from "fs/promises"
- import EventEmitter from "events"
- import { Anthropic } from "@anthropic-ai/sdk"
- import delay from "delay"
- import axios from "axios"
- import pWaitFor from "p-wait-for"
- import * as vscode from "vscode"
- import {
- type GlobalState,
- type ProviderName,
- type ProviderSettings,
- type RooCodeSettings,
- type ProviderSettingsEntry,
- type TelemetryProperties,
- type TelemetryPropertiesProvider,
- type CodeActionId,
- type CodeActionName,
- type TerminalActionId,
- type TerminalActionPromptType,
- type HistoryItem,
- ORGANIZATION_ALLOW_ALL,
- } from "@roo-code/types"
- import { TelemetryService } from "@roo-code/telemetry"
- import { CloudService } from "@roo-code/cloud"
- import { t } from "../../i18n"
- import { setPanel } from "../../activate/registerCommands"
- import { Package } from "../../shared/package"
- import { requestyDefaultModelId, openRouterDefaultModelId, glamaDefaultModelId } from "../../shared/api"
- import { findLast } from "../../shared/array"
- import { supportPrompt } from "../../shared/support-prompt"
- import { GlobalFileNames } from "../../shared/globalFileNames"
- import { ExtensionMessage } from "../../shared/ExtensionMessage"
- import { Mode, defaultModeSlug } from "../../shared/modes"
- import { experimentDefault } from "../../shared/experiments"
- import { formatLanguage } from "../../shared/language"
- import { Terminal } from "../../integrations/terminal/Terminal"
- import { downloadTask } from "../../integrations/misc/export-markdown"
- import { getTheme } from "../../integrations/theme/getTheme"
- import WorkspaceTracker from "../../integrations/workspace/WorkspaceTracker"
- import { McpHub } from "../../services/mcp/McpHub"
- import { McpServerManager } from "../../services/mcp/McpServerManager"
- import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService"
- import { CodeIndexManager } from "../../services/code-index/manager"
- import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
- import { fileExistsAtPath } from "../../utils/fs"
- import { setTtsEnabled, setTtsSpeed } from "../../utils/tts"
- import { ContextProxy } from "../config/ContextProxy"
- import { ProviderSettingsManager } from "../config/ProviderSettingsManager"
- import { CustomModesManager } from "../config/CustomModesManager"
- import { buildApiHandler } from "../../api"
- import { Task, TaskOptions } from "../task/Task"
- import { getNonce } from "./getNonce"
- import { getUri } from "./getUri"
- import { getSystemPromptFilePath } from "../prompts/sections/custom-system-prompt"
- import { getWorkspacePath } from "../../utils/path"
- import { webviewMessageHandler } from "./webviewMessageHandler"
- import { WebviewMessage } from "../../shared/WebviewMessage"
- import { EMBEDDING_MODEL_PROFILES } from "../../shared/embeddingModels"
- import { ProfileValidator } from "../../shared/ProfileValidator"
- /**
- * https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
- * https://github.com/KumarVariable/vscode-extension-sidebar-html/blob/master/src/customSidebarViewProvider.ts
- */
- export type ClineProviderEvents = {
- clineCreated: [cline: Task]
- }
- class OrganizationAllowListViolationError extends Error {
- constructor(message: string) {
- super(message)
- }
- }
- export class ClineProvider
- extends EventEmitter<ClineProviderEvents>
- implements vscode.WebviewViewProvider, TelemetryPropertiesProvider
- {
- // Used in package.json as the view's id. This value cannot be changed due
- // to how VSCode caches views based on their id, and updating the id would
- // break existing instances of the extension.
- public static readonly sideBarId = `${Package.name}.SidebarProvider`
- public static readonly tabPanelId = `${Package.name}.TabPanelProvider`
- private static activeInstances: Set<ClineProvider> = new Set()
- private disposables: vscode.Disposable[] = []
- private view?: vscode.WebviewView | vscode.WebviewPanel
- private clineStack: Task[] = []
- private codeIndexStatusSubscription?: vscode.Disposable
- private _workspaceTracker?: WorkspaceTracker // workSpaceTracker read-only for access outside this class
- public get workspaceTracker(): WorkspaceTracker | undefined {
- return this._workspaceTracker
- }
- protected mcpHub?: McpHub // Change from private to protected
- public isViewLaunched = false
- public settingsImportedAt?: number
- public readonly latestAnnouncementId = "may-21-2025-3-18" // Update for v3.18.0 announcement
- public readonly providerSettingsManager: ProviderSettingsManager
- public readonly customModesManager: CustomModesManager
- constructor(
- readonly context: vscode.ExtensionContext,
- private readonly outputChannel: vscode.OutputChannel,
- private readonly renderContext: "sidebar" | "editor" = "sidebar",
- public readonly contextProxy: ContextProxy,
- public readonly codeIndexManager?: CodeIndexManager,
- ) {
- super()
- this.log("ClineProvider instantiated")
- ClineProvider.activeInstances.add(this)
- this.codeIndexManager = codeIndexManager
- this.updateGlobalState("codebaseIndexModels", EMBEDDING_MODEL_PROFILES)
- // Start configuration loading (which might trigger indexing) in the background.
- // Don't await, allowing activation to continue immediately.
- // Register this provider with the telemetry service to enable it to add
- // properties like mode and provider.
- TelemetryService.instance.setProvider(this)
- this._workspaceTracker = new WorkspaceTracker(this)
- this.providerSettingsManager = new ProviderSettingsManager(this.context)
- this.customModesManager = new CustomModesManager(this.context, async () => {
- await this.postStateToWebview()
- })
- // Initialize MCP Hub through the singleton manager
- McpServerManager.getInstance(this.context, this)
- .then((hub) => {
- this.mcpHub = hub
- this.mcpHub.registerClient()
- })
- .catch((error) => {
- this.log(`Failed to initialize MCP Hub: ${error}`)
- })
- }
- // Adds a new Cline instance to clineStack, marking the start of a new task.
- // The instance is pushed to the top of the stack (LIFO order).
- // When the task is completed, the top instance is removed, reactivating the previous task.
- async addClineToStack(cline: Task) {
- console.log(`[subtasks] adding task ${cline.taskId}.${cline.instanceId} to stack`)
- // Add this cline instance into the stack that represents the order of all the called tasks.
- this.clineStack.push(cline)
- // Ensure getState() resolves correctly.
- const state = await this.getState()
- if (!state || typeof state.mode !== "string") {
- throw new Error(t("common:errors.retrieve_current_mode"))
- }
- }
- // Removes and destroys the top Cline instance (the current finished task),
- // activating the previous one (resuming the parent task).
- async removeClineFromStack() {
- if (this.clineStack.length === 0) {
- return
- }
- // Pop the top Cline instance from the stack.
- let cline = this.clineStack.pop()
- if (cline) {
- console.log(`[subtasks] removing task ${cline.taskId}.${cline.instanceId} from stack`)
- try {
- // Abort the running task and set isAbandoned to true so
- // all running promises will exit as well.
- await cline.abortTask(true)
- } catch (e) {
- this.log(
- `[subtasks] encountered error while aborting task ${cline.taskId}.${cline.instanceId}: ${e.message}`,
- )
- }
- // Make sure no reference kept, once promises end it will be
- // garbage collected.
- cline = undefined
- }
- }
- // returns the current cline object in the stack (the top one)
- // if the stack is empty, returns undefined
- getCurrentCline(): Task | undefined {
- if (this.clineStack.length === 0) {
- return undefined
- }
- return this.clineStack[this.clineStack.length - 1]
- }
- // returns the current clineStack length (how many cline objects are in the stack)
- getClineStackSize(): number {
- return this.clineStack.length
- }
- public getCurrentTaskStack(): string[] {
- return this.clineStack.map((cline) => cline.taskId)
- }
- // remove the current task/cline instance (at the top of the stack), so this task is finished
- // and resume the previous task/cline instance (if it exists)
- // this is used when a sub task is finished and the parent task needs to be resumed
- async finishSubTask(lastMessage: string) {
- console.log(`[subtasks] finishing subtask ${lastMessage}`)
- // remove the last cline instance from the stack (this is the finished sub task)
- await this.removeClineFromStack()
- // resume the last cline instance in the stack (if it exists - this is the 'parent' calling task)
- await this.getCurrentCline()?.resumePausedTask(lastMessage)
- }
- /*
- VSCode extensions use the disposable pattern to clean up resources when the sidebar/editor tab is closed by the user or system. This applies to event listening, commands, interacting with the UI, etc.
- - https://vscode-docs.readthedocs.io/en/stable/extensions/patterns-and-principles/
- - https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
- */
- async dispose() {
- this.log("Disposing ClineProvider...")
- await this.removeClineFromStack()
- this.log("Cleared task")
- if (this.view && "dispose" in this.view) {
- this.view.dispose()
- this.log("Disposed webview")
- }
- while (this.disposables.length) {
- const x = this.disposables.pop()
- if (x) {
- x.dispose()
- }
- }
- this._workspaceTracker?.dispose()
- this._workspaceTracker = undefined
- await this.mcpHub?.unregisterClient()
- this.mcpHub = undefined
- this.customModesManager?.dispose()
- this.log("Disposed all disposables")
- ClineProvider.activeInstances.delete(this)
- // Unregister from McpServerManager
- McpServerManager.unregisterProvider(this)
- }
- public static getVisibleInstance(): ClineProvider | undefined {
- return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true)
- }
- public static async getInstance(): Promise<ClineProvider | undefined> {
- let visibleProvider = ClineProvider.getVisibleInstance()
- // If no visible provider, try to show the sidebar view
- if (!visibleProvider) {
- await vscode.commands.executeCommand(`${Package.name}.SidebarProvider.focus`)
- // Wait briefly for the view to become visible
- await delay(100)
- visibleProvider = ClineProvider.getVisibleInstance()
- }
- // If still no visible provider, return
- if (!visibleProvider) {
- return
- }
- return visibleProvider
- }
- public static async isActiveTask(): Promise<boolean> {
- const visibleProvider = await ClineProvider.getInstance()
- if (!visibleProvider) {
- return false
- }
- // Check if there is a cline instance in the stack (if this provider has an active task)
- if (visibleProvider.getCurrentCline()) {
- return true
- }
- return false
- }
- public static async handleCodeAction(
- command: CodeActionId,
- promptType: CodeActionName,
- params: Record<string, string | any[]>,
- ): Promise<void> {
- // Capture telemetry for code action usage
- TelemetryService.instance.captureCodeActionUsed(promptType)
- const visibleProvider = await ClineProvider.getInstance()
- if (!visibleProvider) {
- return
- }
- const { customSupportPrompts } = await visibleProvider.getState()
- // TODO: Improve type safety for promptType.
- const prompt = supportPrompt.create(promptType, params, customSupportPrompts)
- if (command === "addToContext") {
- await visibleProvider.postMessageToWebview({ type: "invoke", invoke: "setChatBoxMessage", text: prompt })
- return
- }
- await visibleProvider.initClineWithTask(prompt)
- }
- public static async handleTerminalAction(
- command: TerminalActionId,
- promptType: TerminalActionPromptType,
- params: Record<string, string | any[]>,
- ): Promise<void> {
- TelemetryService.instance.captureCodeActionUsed(promptType)
- const visibleProvider = await ClineProvider.getInstance()
- if (!visibleProvider) {
- return
- }
- const { customSupportPrompts } = await visibleProvider.getState()
- const prompt = supportPrompt.create(promptType, params, customSupportPrompts)
- if (command === "terminalAddToContext") {
- await visibleProvider.postMessageToWebview({ type: "invoke", invoke: "setChatBoxMessage", text: prompt })
- return
- }
- try {
- await visibleProvider.initClineWithTask(prompt)
- } catch (error) {
- if (error instanceof OrganizationAllowListViolationError) {
- // Errors from terminal commands seem to get swallowed / ignored.
- vscode.window.showErrorMessage(error.message)
- }
- throw error
- }
- }
- async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) {
- this.log("Resolving webview view")
- this.view = webviewView
- // Set panel reference according to webview type
- const inTabMode = "onDidChangeViewState" in webviewView
- if (inTabMode) {
- // Tag page type
- setPanel(webviewView, "tab")
- } else if ("onDidChangeVisibility" in webviewView) {
- // Sidebar Type
- setPanel(webviewView, "sidebar")
- }
- // Initialize out-of-scope variables that need to recieve persistent global state values
- this.getState().then(
- ({
- terminalShellIntegrationTimeout = Terminal.defaultShellIntegrationTimeout,
- terminalShellIntegrationDisabled = false,
- terminalCommandDelay = 0,
- terminalZshClearEolMark = true,
- terminalZshOhMy = false,
- terminalZshP10k = false,
- terminalPowershellCounter = false,
- terminalZdotdir = false,
- }) => {
- Terminal.setShellIntegrationTimeout(terminalShellIntegrationTimeout)
- Terminal.setShellIntegrationDisabled(terminalShellIntegrationDisabled)
- Terminal.setCommandDelay(terminalCommandDelay)
- Terminal.setTerminalZshClearEolMark(terminalZshClearEolMark)
- Terminal.setTerminalZshOhMy(terminalZshOhMy)
- Terminal.setTerminalZshP10k(terminalZshP10k)
- Terminal.setPowershellCounter(terminalPowershellCounter)
- Terminal.setTerminalZdotdir(terminalZdotdir)
- },
- )
- // Initialize tts enabled state
- this.getState().then(({ ttsEnabled }) => {
- setTtsEnabled(ttsEnabled ?? false)
- })
- // Initialize tts speed state
- this.getState().then(({ ttsSpeed }) => {
- setTtsSpeed(ttsSpeed ?? 1)
- })
- webviewView.webview.options = {
- // Allow scripts in the webview
- enableScripts: true,
- localResourceRoots: [this.contextProxy.extensionUri],
- }
- webviewView.webview.html =
- this.contextProxy.extensionMode === vscode.ExtensionMode.Development
- ? 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
- this.setWebviewMessageListener(webviewView.webview)
- // Subscribe to code index status updates if the manager exists
- if (this.codeIndexManager) {
- this.codeIndexStatusSubscription = this.codeIndexManager.onProgressUpdate((update: IndexProgressUpdate) => {
- this.postMessageToWebview({
- type: "indexingStatusUpdate",
- values: update,
- })
- })
- // Add the subscription to the main disposables array
- this.disposables.push(this.codeIndexStatusSubscription)
- }
- // Logs show up in bottom panel > Debug Console
- //console.log("registering listener")
- // Listen for when the panel becomes visible
- // https://github.com/microsoft/vscode-discussions/discussions/840
- if ("onDidChangeViewState" in webviewView) {
- // WebviewView and WebviewPanel have all the same properties except for this visibility listener
- // panel
- webviewView.onDidChangeViewState(
- () => {
- if (this.view?.visible) {
- this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
- }
- },
- null,
- this.disposables,
- )
- } else if ("onDidChangeVisibility" in webviewView) {
- // sidebar
- webviewView.onDidChangeVisibility(
- () => {
- if (this.view?.visible) {
- this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
- }
- },
- null,
- this.disposables,
- )
- }
- // Listen for when the view is disposed
- // This happens when the user closes the view or when the view is closed programmatically
- webviewView.onDidDispose(
- async () => {
- if (inTabMode) {
- this.log("Disposing ClineProvider instance for tab view")
- await this.dispose()
- } else {
- this.log("Preserving ClineProvider instance for sidebar view reuse")
- }
- },
- null,
- this.disposables,
- )
- // Listen for when color changes
- vscode.workspace.onDidChangeConfiguration(
- async (e) => {
- if (e && e.affectsConfiguration("workbench.colorTheme")) {
- // Sends latest theme name to webview
- await this.postMessageToWebview({ type: "theme", text: JSON.stringify(await getTheme()) })
- }
- },
- null,
- this.disposables,
- )
- // If the extension is starting a new session, clear previous task state.
- await this.removeClineFromStack()
- this.log("Webview view resolved")
- }
- public async initClineWithSubTask(parent: Task, task?: string, images?: string[]) {
- return this.initClineWithTask(task, images, parent)
- }
- // When initializing a new task, (not from history but from a tool command
- // new_task) there is no need to remove the previouse task since the new
- // task is a subtask of the previous one, and when it finishes it is removed
- // from the stack and the caller is resumed in this way we can have a chain
- // of tasks, each one being a sub task of the previous one until the main
- // task is finished.
- public async initClineWithTask(
- task?: string,
- images?: string[],
- parentTask?: Task,
- options: Partial<
- Pick<
- TaskOptions,
- "enableDiff" | "enableCheckpoints" | "fuzzyMatchThreshold" | "consecutiveMistakeLimit" | "experiments"
- >
- > = {},
- ) {
- const {
- apiConfiguration,
- organizationAllowList,
- diffEnabled: enableDiff,
- enableCheckpoints,
- fuzzyMatchThreshold,
- experiments,
- } = await this.getState()
- if (!ProfileValidator.isProfileAllowed(apiConfiguration, organizationAllowList)) {
- throw new OrganizationAllowListViolationError(t("common:errors.violated_organization_allowlist"))
- }
- const cline = new Task({
- provider: this,
- apiConfiguration,
- enableDiff,
- enableCheckpoints,
- fuzzyMatchThreshold,
- task,
- images,
- experiments,
- rootTask: this.clineStack.length > 0 ? this.clineStack[0] : undefined,
- parentTask,
- taskNumber: this.clineStack.length + 1,
- onCreated: (cline) => this.emit("clineCreated", cline),
- ...options,
- })
- await this.addClineToStack(cline)
- this.log(
- `[subtasks] ${cline.parentTask ? "child" : "parent"} task ${cline.taskId}.${cline.instanceId} instantiated`,
- )
- return cline
- }
- public async initClineWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
- await this.removeClineFromStack()
- const {
- apiConfiguration,
- diffEnabled: enableDiff,
- enableCheckpoints,
- fuzzyMatchThreshold,
- experiments,
- } = await this.getState()
- const cline = new Task({
- provider: this,
- apiConfiguration,
- enableDiff,
- enableCheckpoints,
- fuzzyMatchThreshold,
- historyItem,
- experiments,
- rootTask: historyItem.rootTask,
- parentTask: historyItem.parentTask,
- taskNumber: historyItem.number,
- onCreated: (cline) => this.emit("clineCreated", cline),
- })
- await this.addClineToStack(cline)
- this.log(
- `[subtasks] ${cline.parentTask ? "child" : "parent"} task ${cline.taskId}.${cline.instanceId} instantiated`,
- )
- return cline
- }
- public async postMessageToWebview(message: ExtensionMessage) {
- await this.view?.webview.postMessage(message)
- }
- private async getHMRHtmlContent(webview: vscode.Webview): Promise<string> {
- // 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, ["assets", "codicons", "codicon.css"])
- const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [
- "assets",
- "vscode-material-icons",
- "icons",
- ])
- const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
- const audioUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "audio"])
- const file = "src/index.tsx"
- const scriptUri = `http://${localServerUrl}/${file}`
- const reactRefresh = /*html*/ `
- <script nonce="${nonce}" type="module">
- import RefreshRuntime from "http://localhost:${localPort}/@react-refresh"
- RefreshRuntime.injectIntoGlobalHook(window)
- window.$RefreshReg$ = () => {}
- window.$RefreshSig$ = () => (type) => type
- window.__vite_plugin_react_preamble_installed__ = true
- </script>
- `
- 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} https://storage.googleapis.com https://img.clerk.com data:`,
- `media-src ${webview.cspSource}`,
- `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*/ `
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
- <meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
- <link rel="stylesheet" type="text/css" href="${stylesUri}">
- <link href="${codiconsUri}" rel="stylesheet" />
- <script nonce="${nonce}">
- window.IMAGES_BASE_URI = "${imagesUri}"
- window.AUDIO_BASE_URI = "${audioUri}"
- window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
- </script>
- <title>Roo Code</title>
- </head>
- <body>
- <div id="root"></div>
- ${reactRefresh}
- <script type="module" src="${scriptUri}"></script>
- </body>
- </html>
- `
- }
- /**
- * 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",
- ])
- const scriptUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "build", "assets", "index.js"])
- const codiconsUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "codicons", "codicon.css"])
- const materialIconsUri = getUri(webview, this.contextProxy.extensionUri, [
- "assets",
- "vscode-material-icons",
- "icons",
- ])
- const imagesUri = getUri(webview, this.contextProxy.extensionUri, ["assets", "images"])
- const audioUri = getUri(webview, this.contextProxy.extensionUri, ["webview-ui", "audio"])
- // 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.
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; font-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
- - '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*/ `
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
- <meta name="theme-color" content="#000000">
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https://storage.googleapis.com https://img.clerk.com data:; media-src ${webview.cspSource}; script-src ${webview.cspSource} 'wasm-unsafe-eval' 'nonce-${nonce}' https://us-assets.i.posthog.com 'strict-dynamic'; connect-src https://openrouter.ai https://api.requesty.ai https://us.i.posthog.com https://us-assets.i.posthog.com;">
- <link rel="stylesheet" type="text/css" href="${stylesUri}">
- <link href="${codiconsUri}" rel="stylesheet" />
- <script nonce="${nonce}">
- window.IMAGES_BASE_URI = "${imagesUri}"
- window.AUDIO_BASE_URI = "${audioUri}"
- window.MATERIAL_ICONS_BASE_URI = "${materialIconsUri}"
- </script>
- <title>Roo Code</title>
- </head>
- <body>
- <noscript>You need to enable JavaScript to run this app.</noscript>
- <div id="root"></div>
- <script nonce="${nonce}" type="module" src="${scriptUri}"></script>
- </body>
- </html>
- `
- }
- /**
- * Sets up an event listener to listen for messages passed from the webview context and
- * executes code based on the message that is recieved.
- *
- * @param webview A reference to the extension webview
- */
- private setWebviewMessageListener(webview: vscode.Webview) {
- const onReceiveMessage = async (message: WebviewMessage) => webviewMessageHandler(this, message)
- webview.onDidReceiveMessage(onReceiveMessage, null, this.disposables)
- }
- /**
- * Handle switching to a new mode, including updating the associated API configuration
- * @param newMode The mode to switch to
- */
- public async handleModeSwitch(newMode: Mode) {
- const cline = this.getCurrentCline()
- if (cline) {
- TelemetryService.instance.captureModeSwitch(cline.taskId, newMode)
- cline.emit("taskModeSwitched", cline.taskId, newMode)
- }
- await this.updateGlobalState("mode", newMode)
- // Load the saved API config for the new mode if it exists
- const savedConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
- const listApiConfig = await this.providerSettingsManager.listConfig()
- // Update listApiConfigMeta first to ensure UI has latest data
- await this.updateGlobalState("listApiConfigMeta", listApiConfig)
- // If this mode has a saved config, use it.
- if (savedConfigId) {
- const profile = listApiConfig.find(({ id }) => id === savedConfigId)
- if (profile?.name) {
- await this.activateProviderProfile({ name: profile.name })
- }
- } else {
- // If no saved config for this mode, save current config as default.
- const currentApiConfigName = this.getGlobalState("currentApiConfigName")
- if (currentApiConfigName) {
- const config = listApiConfig.find((c) => c.name === currentApiConfigName)
- if (config?.id) {
- await this.providerSettingsManager.setModeConfig(newMode, config.id)
- }
- }
- }
- await this.postStateToWebview()
- }
- // Provider Profile Management
- getProviderProfileEntries(): ProviderSettingsEntry[] {
- return this.contextProxy.getValues().listApiConfigMeta || []
- }
- getProviderProfileEntry(name: string): ProviderSettingsEntry | undefined {
- return this.getProviderProfileEntries().find((profile) => profile.name === name)
- }
- public hasProviderProfileEntry(name: string): boolean {
- return !!this.getProviderProfileEntry(name)
- }
- async upsertProviderProfile(
- name: string,
- providerSettings: ProviderSettings,
- activate: boolean = true,
- ): Promise<string | undefined> {
- try {
- // TODO: Do we need to be calling `activateProfile`? It's not
- // clear to me what the source of truth should be; in some cases
- // we rely on the `ContextProxy`'s data store and in other cases
- // we rely on the `ProviderSettingsManager`'s data store. It might
- // be simpler to unify these two.
- const id = await this.providerSettingsManager.saveConfig(name, providerSettings)
- if (activate) {
- const { mode } = await this.getState()
- // These promises do the following:
- // 1. Adds or updates the list of provider profiles.
- // 2. Sets the current provider profile.
- // 3. Sets the current mode's provider profile.
- // 4. Copies the provider settings to the context.
- //
- // Note: 1, 2, and 4 can be done in one `ContextProxy` call:
- // this.contextProxy.setValues({ ...providerSettings, listApiConfigMeta: ..., currentApiConfigName: ... })
- // We should probably switch to that and verify that it works.
- // I left the original implementation in just to be safe.
- await Promise.all([
- this.updateGlobalState("listApiConfigMeta", await this.providerSettingsManager.listConfig()),
- this.updateGlobalState("currentApiConfigName", name),
- this.providerSettingsManager.setModeConfig(mode, id),
- this.contextProxy.setProviderSettings(providerSettings),
- ])
- // Notify CodeIndexManager about the settings change
- if (this.codeIndexManager) {
- await this.codeIndexManager.handleExternalSettingsChange()
- }
- // Change the provider for the current task.
- // TODO: We should rename `buildApiHandler` for clarity (e.g. `getProviderClient`).
- const task = this.getCurrentCline()
- if (task) {
- task.api = buildApiHandler(providerSettings)
- }
- } else {
- await this.updateGlobalState("listApiConfigMeta", await this.providerSettingsManager.listConfig())
- }
- await this.postStateToWebview()
- return id
- } catch (error) {
- this.log(
- `Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
- )
- vscode.window.showErrorMessage(t("common:errors.create_api_config"))
- return undefined
- }
- }
- async deleteProviderProfile(profileToDelete: ProviderSettingsEntry) {
- const globalSettings = this.contextProxy.getValues()
- let profileToActivate: string | undefined = globalSettings.currentApiConfigName
- if (profileToDelete.name === profileToActivate) {
- profileToActivate = this.getProviderProfileEntries().find(({ name }) => name !== profileToDelete.name)?.name
- }
- if (!profileToActivate) {
- throw new Error("You cannot delete the last profile")
- }
- const entries = this.getProviderProfileEntries().filter(({ name }) => name !== profileToDelete.name)
- await this.contextProxy.setValues({
- ...globalSettings,
- currentApiConfigName: profileToActivate,
- listApiConfigMeta: entries,
- })
- await this.postStateToWebview()
- }
- async activateProviderProfile(args: { name: string } | { id: string }) {
- const { name, id, ...providerSettings } = await this.providerSettingsManager.activateProfile(args)
- // See `upsertProviderProfile` for a description of what this is doing.
- await Promise.all([
- this.contextProxy.setValue("listApiConfigMeta", await this.providerSettingsManager.listConfig()),
- this.contextProxy.setValue("currentApiConfigName", name),
- this.contextProxy.setProviderSettings(providerSettings),
- ])
- const { mode } = await this.getState()
- if (id) {
- await this.providerSettingsManager.setModeConfig(mode, id)
- }
- // Change the provider for the current task.
- const task = this.getCurrentCline()
- if (task) {
- task.api = buildApiHandler(providerSettings)
- }
- await this.postStateToWebview()
- }
- // Task Management
- async cancelTask() {
- const cline = this.getCurrentCline()
- if (!cline) {
- return
- }
- console.log(`[subtasks] cancelling task ${cline.taskId}.${cline.instanceId}`)
- const { historyItem } = await this.getTaskWithId(cline.taskId)
- // Preserve parent and root task information for history item.
- const rootTask = cline.rootTask
- const parentTask = cline.parentTask
- cline.abortTask()
- await pWaitFor(
- () =>
- this.getCurrentCline()! === undefined ||
- this.getCurrentCline()!.isStreaming === false ||
- this.getCurrentCline()!.didFinishAbortingStream ||
- // If only the first chunk is processed, then there's no
- // need to wait for graceful abort (closes edits, browser,
- // etc).
- this.getCurrentCline()!.isWaitingForFirstChunk,
- {
- timeout: 3_000,
- },
- ).catch(() => {
- console.error("Failed to abort task")
- })
- if (this.getCurrentCline()) {
- // 'abandoned' will prevent this Cline instance from affecting
- // future Cline instances. This may happen if its hanging on a
- // streaming request.
- this.getCurrentCline()!.abandoned = true
- }
- // Clears task again, so we need to abortTask manually above.
- await this.initClineWithHistoryItem({ ...historyItem, rootTask, parentTask })
- }
- async updateCustomInstructions(instructions?: string) {
- // User may be clearing the field.
- await this.updateGlobalState("customInstructions", instructions || undefined)
- await this.postStateToWebview()
- }
- // MCP
- async ensureMcpServersDirectoryExists(): Promise<string> {
- // Get platform-specific application data directory
- let mcpServersDir: string
- if (process.platform === "win32") {
- // Windows: %APPDATA%\Roo-Code\MCP
- mcpServersDir = path.join(os.homedir(), "AppData", "Roaming", "Roo-Code", "MCP")
- } else if (process.platform === "darwin") {
- // macOS: ~/Documents/Cline/MCP
- mcpServersDir = path.join(os.homedir(), "Documents", "Cline", "MCP")
- } else {
- // Linux: ~/.local/share/Cline/MCP
- mcpServersDir = path.join(os.homedir(), ".local", "share", "Roo-Code", "MCP")
- }
- try {
- await fs.mkdir(mcpServersDir, { recursive: true })
- } catch (error) {
- // Fallback to a relative path if directory creation fails
- return path.join(os.homedir(), ".roo-code", "mcp")
- }
- return mcpServersDir
- }
- async ensureSettingsDirectoryExists(): Promise<string> {
- const { getSettingsDirectoryPath } = await import("../../utils/storage")
- const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
- return getSettingsDirectoryPath(globalStoragePath)
- }
- // OpenRouter
- async handleOpenRouterCallback(code: string) {
- let { apiConfiguration, currentApiConfigName } = await this.getState()
- let apiKey: string
- try {
- const baseUrl = apiConfiguration.openRouterBaseUrl || "https://openrouter.ai/api/v1"
- // Extract the base domain for the auth endpoint
- const baseUrlDomain = baseUrl.match(/^(https?:\/\/[^\/]+)/)?.[1] || "https://openrouter.ai"
- const response = await axios.post(`${baseUrlDomain}/api/v1/auth/keys`, { code })
- if (response.data && response.data.key) {
- apiKey = response.data.key
- } else {
- throw new Error("Invalid response from OpenRouter API")
- }
- } catch (error) {
- this.log(
- `Error exchanging code for API key: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
- )
- throw error
- }
- const newConfiguration: ProviderSettings = {
- ...apiConfiguration,
- apiProvider: "openrouter",
- openRouterApiKey: apiKey,
- openRouterModelId: apiConfiguration?.openRouterModelId || openRouterDefaultModelId,
- }
- await this.upsertProviderProfile(currentApiConfigName, newConfiguration)
- }
- // Glama
- async handleGlamaCallback(code: string) {
- let apiKey: string
- try {
- const response = await axios.post("https://glama.ai/api/gateway/v1/auth/exchange-code", { code })
- if (response.data && response.data.apiKey) {
- apiKey = response.data.apiKey
- } else {
- throw new Error("Invalid response from Glama API")
- }
- } catch (error) {
- this.log(
- `Error exchanging code for API key: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
- )
- throw error
- }
- const { apiConfiguration, currentApiConfigName } = await this.getState()
- const newConfiguration: ProviderSettings = {
- ...apiConfiguration,
- apiProvider: "glama",
- glamaApiKey: apiKey,
- glamaModelId: apiConfiguration?.glamaModelId || glamaDefaultModelId,
- }
- await this.upsertProviderProfile(currentApiConfigName, newConfiguration)
- }
- // Requesty
- async handleRequestyCallback(code: string) {
- let { apiConfiguration, currentApiConfigName } = await this.getState()
- const newConfiguration: ProviderSettings = {
- ...apiConfiguration,
- apiProvider: "requesty",
- requestyApiKey: code,
- requestyModelId: apiConfiguration?.requestyModelId || requestyDefaultModelId,
- }
- await this.upsertProviderProfile(currentApiConfigName, newConfiguration)
- }
- // Task history
- async getTaskWithId(id: string): Promise<{
- historyItem: HistoryItem
- taskDirPath: string
- apiConversationHistoryFilePath: string
- uiMessagesFilePath: string
- apiConversationHistory: Anthropic.MessageParam[]
- }> {
- const history = this.getGlobalState("taskHistory") ?? []
- const historyItem = history.find((item) => item.id === id)
- if (historyItem) {
- const { getTaskDirectoryPath } = await import("../../utils/storage")
- const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
- const taskDirPath = await getTaskDirectoryPath(globalStoragePath, id)
- const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory)
- const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages)
- const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath)
- if (fileExists) {
- const apiConversationHistory = JSON.parse(await fs.readFile(apiConversationHistoryFilePath, "utf8"))
- return {
- historyItem,
- taskDirPath,
- apiConversationHistoryFilePath,
- uiMessagesFilePath,
- apiConversationHistory,
- }
- }
- }
- // if we tried to get a task that doesn't exist, remove it from state
- // FIXME: this seems to happen sometimes when the json file doesnt save to disk for some reason
- await this.deleteTaskFromState(id)
- throw new Error("Task not found")
- }
- async showTaskWithId(id: string) {
- if (id !== this.getCurrentCline()?.taskId) {
- // Non-current task.
- const { historyItem } = await this.getTaskWithId(id)
- await this.initClineWithHistoryItem(historyItem) // Clears existing task.
- }
- await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
- }
- async exportTaskWithId(id: string) {
- const { historyItem, apiConversationHistory } = await this.getTaskWithId(id)
- await downloadTask(historyItem.ts, apiConversationHistory)
- }
- /* Condenses a task's message history to use fewer tokens. */
- async condenseTaskContext(taskId: string) {
- let task: Task | undefined
- for (let i = this.clineStack.length - 1; i >= 0; i--) {
- if (this.clineStack[i].taskId === taskId) {
- task = this.clineStack[i]
- break
- }
- }
- if (!task) {
- throw new Error(`Task with id ${taskId} not found in stack`)
- }
- await task.condenseContext()
- await this.postMessageToWebview({ type: "condenseTaskContextResponse", text: taskId })
- }
- // this function deletes a task from task hidtory, and deletes it's checkpoints and delete the task folder
- async deleteTaskWithId(id: string) {
- try {
- // get the task directory full path
- const { taskDirPath } = await this.getTaskWithId(id)
- // remove task from stack if it's the current task
- if (id === this.getCurrentCline()?.taskId) {
- // if we found the taskid to delete - call finish to abort this task and allow a new task to be started,
- // if we are deleting a subtask and parent task is still waiting for subtask to finish - it allows the parent to resume (this case should neve exist)
- await this.finishSubTask(t("common:tasks.deleted"))
- }
- // delete task from the task history state
- await this.deleteTaskFromState(id)
- // Delete associated shadow repository or branch.
- // TODO: Store `workspaceDir` in the `HistoryItem` object.
- const globalStorageDir = this.contextProxy.globalStorageUri.fsPath
- const workspaceDir = this.cwd
- try {
- await ShadowCheckpointService.deleteTask({ taskId: id, globalStorageDir, workspaceDir })
- } catch (error) {
- console.error(
- `[deleteTaskWithId${id}] failed to delete associated shadow repository or branch: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
- // delete the entire task directory including checkpoints and all content
- try {
- await fs.rm(taskDirPath, { recursive: true, force: true })
- console.log(`[deleteTaskWithId${id}] removed task directory`)
- } catch (error) {
- console.error(
- `[deleteTaskWithId${id}] failed to remove task directory: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
- } catch (error) {
- // If task is not found, just remove it from state
- if (error instanceof Error && error.message === "Task not found") {
- await this.deleteTaskFromState(id)
- return
- }
- throw error
- }
- }
- async deleteTaskFromState(id: string) {
- const taskHistory = this.getGlobalState("taskHistory") ?? []
- const updatedTaskHistory = taskHistory.filter((task) => task.id !== id)
- await this.updateGlobalState("taskHistory", updatedTaskHistory)
- await this.postStateToWebview()
- }
- async postStateToWebview() {
- const state = await this.getStateToPostToWebview()
- this.postMessageToWebview({ type: "state", state })
- }
- /**
- * Checks if there is a file-based system prompt override for the given mode
- */
- async hasFileBasedSystemPromptOverride(mode: Mode): Promise<boolean> {
- const promptFilePath = getSystemPromptFilePath(this.cwd, mode)
- return await fileExistsAtPath(promptFilePath)
- }
- async getStateToPostToWebview() {
- const {
- apiConfiguration,
- lastShownAnnouncementId,
- customInstructions,
- alwaysAllowReadOnly,
- alwaysAllowReadOnlyOutsideWorkspace,
- alwaysAllowWrite,
- alwaysAllowWriteOutsideWorkspace,
- alwaysAllowExecute,
- alwaysAllowBrowser,
- alwaysAllowMcp,
- alwaysAllowModeSwitch,
- alwaysAllowSubtasks,
- allowedMaxRequests,
- autoCondenseContext,
- autoCondenseContextPercent,
- soundEnabled,
- ttsEnabled,
- ttsSpeed,
- diffEnabled,
- enableCheckpoints,
- taskHistory,
- soundVolume,
- browserViewportSize,
- screenshotQuality,
- remoteBrowserHost,
- remoteBrowserEnabled,
- cachedChromeHostUrl,
- writeDelayMs,
- terminalOutputLineLimit,
- terminalShellIntegrationTimeout,
- terminalShellIntegrationDisabled,
- terminalCommandDelay,
- terminalPowershellCounter,
- terminalZshClearEolMark,
- terminalZshOhMy,
- terminalZshP10k,
- terminalZdotdir,
- fuzzyMatchThreshold,
- mcpEnabled,
- enableMcpServerCreation,
- alwaysApproveResubmit,
- requestDelaySeconds,
- currentApiConfigName,
- listApiConfigMeta,
- pinnedApiConfigs,
- mode,
- customModePrompts,
- customSupportPrompts,
- enhancementApiConfigId,
- autoApprovalEnabled,
- customModes,
- experiments,
- maxOpenTabsContext,
- maxWorkspaceFiles,
- browserToolEnabled,
- telemetrySetting,
- showRooIgnoredFiles,
- language,
- maxReadFileLine,
- terminalCompressProgressBar,
- historyPreviewCollapsed,
- organizationAllowList,
- condensingApiConfigId,
- customCondensingPrompt,
- codebaseIndexConfig,
- codebaseIndexModels,
- } = await this.getState()
- const telemetryKey = process.env.POSTHOG_API_KEY
- const machineId = vscode.env.machineId
- const allowedCommands = vscode.workspace.getConfiguration(Package.name).get<string[]>("allowedCommands") || []
- const cwd = this.cwd
- // Check if there's a system prompt override for the current mode
- const currentMode = mode ?? defaultModeSlug
- const hasSystemPromptOverride = await this.hasFileBasedSystemPromptOverride(currentMode)
- return {
- version: this.context.extension?.packageJSON?.version ?? "",
- apiConfiguration,
- customInstructions,
- alwaysAllowReadOnly: alwaysAllowReadOnly ?? false,
- alwaysAllowReadOnlyOutsideWorkspace: alwaysAllowReadOnlyOutsideWorkspace ?? false,
- alwaysAllowWrite: alwaysAllowWrite ?? false,
- alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? false,
- alwaysAllowExecute: alwaysAllowExecute ?? false,
- alwaysAllowBrowser: alwaysAllowBrowser ?? false,
- alwaysAllowMcp: alwaysAllowMcp ?? false,
- alwaysAllowModeSwitch: alwaysAllowModeSwitch ?? false,
- alwaysAllowSubtasks: alwaysAllowSubtasks ?? false,
- allowedMaxRequests,
- autoCondenseContext: autoCondenseContext ?? true,
- autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
- uriScheme: vscode.env.uriScheme,
- currentTaskItem: this.getCurrentCline()?.taskId
- ? (taskHistory || []).find((item: HistoryItem) => item.id === this.getCurrentCline()?.taskId)
- : undefined,
- clineMessages: this.getCurrentCline()?.clineMessages || [],
- taskHistory: (taskHistory || [])
- .filter((item: HistoryItem) => item.ts && item.task)
- .sort((a: HistoryItem, b: HistoryItem) => b.ts - a.ts),
- soundEnabled: soundEnabled ?? false,
- ttsEnabled: ttsEnabled ?? false,
- ttsSpeed: ttsSpeed ?? 1.0,
- diffEnabled: diffEnabled ?? true,
- enableCheckpoints: enableCheckpoints ?? true,
- shouldShowAnnouncement:
- telemetrySetting !== "unset" && lastShownAnnouncementId !== this.latestAnnouncementId,
- allowedCommands,
- soundVolume: soundVolume ?? 0.5,
- browserViewportSize: browserViewportSize ?? "900x600",
- screenshotQuality: screenshotQuality ?? 75,
- remoteBrowserHost,
- remoteBrowserEnabled: remoteBrowserEnabled ?? false,
- cachedChromeHostUrl: cachedChromeHostUrl,
- writeDelayMs: writeDelayMs ?? 1000,
- terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
- terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? Terminal.defaultShellIntegrationTimeout,
- terminalShellIntegrationDisabled: terminalShellIntegrationDisabled ?? false,
- terminalCommandDelay: terminalCommandDelay ?? 0,
- terminalPowershellCounter: terminalPowershellCounter ?? false,
- terminalZshClearEolMark: terminalZshClearEolMark ?? true,
- terminalZshOhMy: terminalZshOhMy ?? false,
- terminalZshP10k: terminalZshP10k ?? false,
- terminalZdotdir: terminalZdotdir ?? false,
- fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
- mcpEnabled: mcpEnabled ?? true,
- enableMcpServerCreation: enableMcpServerCreation ?? true,
- alwaysApproveResubmit: alwaysApproveResubmit ?? false,
- requestDelaySeconds: requestDelaySeconds ?? 10,
- currentApiConfigName: currentApiConfigName ?? "default",
- listApiConfigMeta: listApiConfigMeta ?? [],
- pinnedApiConfigs: pinnedApiConfigs ?? {},
- mode: mode ?? defaultModeSlug,
- customModePrompts: customModePrompts ?? {},
- customSupportPrompts: customSupportPrompts ?? {},
- enhancementApiConfigId,
- autoApprovalEnabled: autoApprovalEnabled ?? false,
- customModes,
- experiments: experiments ?? experimentDefault,
- mcpServers: this.mcpHub?.getAllServers() ?? [],
- maxOpenTabsContext: maxOpenTabsContext ?? 20,
- maxWorkspaceFiles: maxWorkspaceFiles ?? 200,
- cwd,
- browserToolEnabled: browserToolEnabled ?? true,
- telemetrySetting,
- telemetryKey,
- machineId,
- showRooIgnoredFiles: showRooIgnoredFiles ?? true,
- language: language ?? formatLanguage(vscode.env.language),
- renderContext: this.renderContext,
- maxReadFileLine: maxReadFileLine ?? -1,
- settingsImportedAt: this.settingsImportedAt,
- terminalCompressProgressBar: terminalCompressProgressBar ?? true,
- hasSystemPromptOverride,
- historyPreviewCollapsed: historyPreviewCollapsed ?? false,
- organizationAllowList,
- condensingApiConfigId,
- customCondensingPrompt,
- codebaseIndexModels: codebaseIndexModels ?? EMBEDDING_MODEL_PROFILES,
- codebaseIndexConfig: codebaseIndexConfig ?? {
- codebaseIndexEnabled: false,
- codebaseIndexQdrantUrl: "http://localhost:6333",
- codebaseIndexEmbedderProvider: "openai",
- codebaseIndexEmbedderBaseUrl: "",
- codebaseIndexEmbedderModelId: "",
- },
- }
- }
- /**
- * Storage
- * https://dev.to/kompotkot/how-to-use-secretstorage-in-your-vscode-extensions-2hco
- * https://www.eliostruyf.com/devhack-code-extension-storage-options/
- */
- async getState() {
- const stateValues = this.contextProxy.getValues()
- const customModes = await this.customModesManager.getCustomModes()
- // Determine apiProvider with the same logic as before.
- const apiProvider: ProviderName = stateValues.apiProvider ? stateValues.apiProvider : "anthropic"
- // Build the apiConfiguration object combining state values and secrets.
- const providerSettings = this.contextProxy.getProviderSettings()
- // Ensure apiProvider is set properly if not already in state
- if (!providerSettings.apiProvider) {
- providerSettings.apiProvider = apiProvider
- }
- let organizationAllowList = ORGANIZATION_ALLOW_ALL
- try {
- organizationAllowList = await CloudService.instance.getAllowList()
- } catch (error) {
- console.error(
- `[getState] failed to get organization allow list: ${error instanceof Error ? error.message : String(error)}`,
- )
- }
- // Return the same structure as before
- return {
- apiConfiguration: providerSettings,
- lastShownAnnouncementId: stateValues.lastShownAnnouncementId,
- customInstructions: stateValues.customInstructions,
- apiModelId: stateValues.apiModelId,
- alwaysAllowReadOnly: stateValues.alwaysAllowReadOnly ?? false,
- alwaysAllowReadOnlyOutsideWorkspace: stateValues.alwaysAllowReadOnlyOutsideWorkspace ?? false,
- alwaysAllowWrite: stateValues.alwaysAllowWrite ?? false,
- alwaysAllowWriteOutsideWorkspace: stateValues.alwaysAllowWriteOutsideWorkspace ?? false,
- alwaysAllowExecute: stateValues.alwaysAllowExecute ?? false,
- alwaysAllowBrowser: stateValues.alwaysAllowBrowser ?? false,
- alwaysAllowMcp: stateValues.alwaysAllowMcp ?? false,
- alwaysAllowModeSwitch: stateValues.alwaysAllowModeSwitch ?? false,
- alwaysAllowSubtasks: stateValues.alwaysAllowSubtasks ?? false,
- allowedMaxRequests: stateValues.allowedMaxRequests,
- autoCondenseContext: stateValues.autoCondenseContext ?? true,
- autoCondenseContextPercent: stateValues.autoCondenseContextPercent ?? 100,
- taskHistory: stateValues.taskHistory,
- allowedCommands: stateValues.allowedCommands,
- soundEnabled: stateValues.soundEnabled ?? false,
- ttsEnabled: stateValues.ttsEnabled ?? false,
- ttsSpeed: stateValues.ttsSpeed ?? 1.0,
- diffEnabled: stateValues.diffEnabled ?? true,
- enableCheckpoints: stateValues.enableCheckpoints ?? true,
- soundVolume: stateValues.soundVolume,
- browserViewportSize: stateValues.browserViewportSize ?? "900x600",
- screenshotQuality: stateValues.screenshotQuality ?? 75,
- remoteBrowserHost: stateValues.remoteBrowserHost,
- remoteBrowserEnabled: stateValues.remoteBrowserEnabled ?? false,
- cachedChromeHostUrl: stateValues.cachedChromeHostUrl as string | undefined,
- fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0,
- writeDelayMs: stateValues.writeDelayMs ?? 1000,
- terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500,
- terminalShellIntegrationTimeout:
- stateValues.terminalShellIntegrationTimeout ?? Terminal.defaultShellIntegrationTimeout,
- terminalShellIntegrationDisabled: stateValues.terminalShellIntegrationDisabled ?? false,
- terminalCommandDelay: stateValues.terminalCommandDelay ?? 0,
- terminalPowershellCounter: stateValues.terminalPowershellCounter ?? false,
- terminalZshClearEolMark: stateValues.terminalZshClearEolMark ?? true,
- terminalZshOhMy: stateValues.terminalZshOhMy ?? false,
- terminalZshP10k: stateValues.terminalZshP10k ?? false,
- terminalZdotdir: stateValues.terminalZdotdir ?? false,
- terminalCompressProgressBar: stateValues.terminalCompressProgressBar ?? true,
- mode: stateValues.mode ?? defaultModeSlug,
- language: stateValues.language ?? formatLanguage(vscode.env.language),
- mcpEnabled: stateValues.mcpEnabled ?? true,
- enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true,
- alwaysApproveResubmit: stateValues.alwaysApproveResubmit ?? false,
- requestDelaySeconds: Math.max(5, stateValues.requestDelaySeconds ?? 10),
- currentApiConfigName: stateValues.currentApiConfigName ?? "default",
- listApiConfigMeta: stateValues.listApiConfigMeta ?? [],
- pinnedApiConfigs: stateValues.pinnedApiConfigs ?? {},
- modeApiConfigs: stateValues.modeApiConfigs ?? ({} as Record<Mode, string>),
- customModePrompts: stateValues.customModePrompts ?? {},
- customSupportPrompts: stateValues.customSupportPrompts ?? {},
- enhancementApiConfigId: stateValues.enhancementApiConfigId,
- experiments: stateValues.experiments ?? experimentDefault,
- autoApprovalEnabled: stateValues.autoApprovalEnabled ?? false,
- customModes,
- maxOpenTabsContext: stateValues.maxOpenTabsContext ?? 20,
- maxWorkspaceFiles: stateValues.maxWorkspaceFiles ?? 200,
- openRouterUseMiddleOutTransform: stateValues.openRouterUseMiddleOutTransform ?? true,
- browserToolEnabled: stateValues.browserToolEnabled ?? true,
- telemetrySetting: stateValues.telemetrySetting || "unset",
- showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
- maxReadFileLine: stateValues.maxReadFileLine ?? -1,
- historyPreviewCollapsed: stateValues.historyPreviewCollapsed ?? false,
- organizationAllowList,
- // Explicitly add condensing settings
- condensingApiConfigId: stateValues.condensingApiConfigId,
- customCondensingPrompt: stateValues.customCondensingPrompt,
- codebaseIndexModels: stateValues.codebaseIndexModels ?? EMBEDDING_MODEL_PROFILES,
- codebaseIndexConfig: stateValues.codebaseIndexConfig ?? {
- codebaseIndexEnabled: false,
- codebaseIndexQdrantUrl: "http://localhost:6333",
- codebaseIndexEmbedderProvider: "openai",
- codebaseIndexEmbedderBaseUrl: "",
- codebaseIndexEmbedderModelId: "",
- },
- }
- }
- async updateTaskHistory(item: HistoryItem): Promise<HistoryItem[]> {
- const history = (this.getGlobalState("taskHistory") as HistoryItem[] | undefined) || []
- const existingItemIndex = history.findIndex((h) => h.id === item.id)
- if (existingItemIndex !== -1) {
- history[existingItemIndex] = item
- } else {
- history.push(item)
- }
- await this.updateGlobalState("taskHistory", history)
- return history
- }
- // ContextProxy
- // @deprecated - Use `ContextProxy#setValue` instead.
- private async updateGlobalState<K extends keyof GlobalState>(key: K, value: GlobalState[K]) {
- await this.contextProxy.setValue(key, value)
- }
- // @deprecated - Use `ContextProxy#getValue` instead.
- private getGlobalState<K extends keyof GlobalState>(key: K) {
- return this.contextProxy.getValue(key)
- }
- public async setValue<K extends keyof RooCodeSettings>(key: K, value: RooCodeSettings[K]) {
- await this.contextProxy.setValue(key, value)
- }
- public getValue<K extends keyof RooCodeSettings>(key: K) {
- return this.contextProxy.getValue(key)
- }
- public getValues() {
- return this.contextProxy.getValues()
- }
- public async setValues(values: RooCodeSettings) {
- await this.contextProxy.setValues(values)
- }
- // cwd
- get cwd() {
- return getWorkspacePath()
- }
- // dev
- async resetState() {
- const answer = await vscode.window.showInformationMessage(
- t("common:confirmation.reset_state"),
- { modal: true },
- t("common:answers.yes"),
- )
- if (answer !== t("common:answers.yes")) {
- return
- }
- await this.contextProxy.resetAllState()
- await this.providerSettingsManager.resetAllConfigs()
- await this.customModesManager.resetCustomModes()
- await this.removeClineFromStack()
- await this.postStateToWebview()
- await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" })
- }
- // logging
- public log(message: string) {
- this.outputChannel.appendLine(message)
- console.log(message)
- }
- // integration tests
- get viewLaunched() {
- return this.isViewLaunched
- }
- get messages() {
- return this.getCurrentCline()?.clineMessages || []
- }
- // Add public getter
- public getMcpHub(): McpHub | undefined {
- return this.mcpHub
- }
- /**
- * Returns properties to be included in every telemetry event
- * This method is called by the telemetry service to get context information
- * like the current mode, API provider, etc.
- */
- public async getTelemetryProperties(): Promise<TelemetryProperties> {
- const { mode, apiConfiguration, language } = await this.getState()
- const task = this.getCurrentCline()
- return {
- appVersion: this.context.extension?.packageJSON?.version,
- vscodeVersion: vscode.version,
- platform: process.platform,
- editorName: vscode.env.appName,
- language,
- mode,
- apiProvider: apiConfiguration?.apiProvider,
- modelId: task?.api?.getModel().id,
- diffStrategy: task?.diffStrategy?.getName(),
- isSubtask: task ? !!task.parentTask : undefined,
- }
- }
- }
|