Tidy up following ClineProvider refactor (#2182)

* Remove unused imports

* Restore protected status for mcpHub property

* More mcpHub tidy-up

* Remove need for access to private "view" property

Increase type flexibility so we can directly use postMessageToWebview()

* Make ensureCacheDirectoryExists private again

expose new public writeDataToCache() method to commonize code.
also decided to leave updateApiConfiguration as public, since widely used in diverse ways.

* Match naming of readModelsFromCache.

Symmetric naming of functions that complement each other.

* Decision: Leave readModelsFromCache as public

* Make updateGlobalState and setGlobalState private again

Stop using deprecated (private) method.  Use contextProxy set/getValue methods instead.
Utility functions provided for brevity.

* Add clarifying comment to explain presence of utility functions

* Prefer this/provider.log over outputChannel.appendLine

log is already public, outputChannel should be private.
But also prefer log internally for the additional value of console logging, plus code brevity.

* Lock down access to properties as far as reasonably possible

* PR feedback - don't relax type checks

---------

Co-authored-by: Matt Rubens <mrubens@users.noreply.github.com>
This commit is contained in:
Diarmid Mackenzie 2025-04-05 23:00:23 +01:00 committed by GitHub
parent 70528034bd
commit 7661ef97cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 169 additions and 187 deletions

View file

@ -29,7 +29,7 @@ import { GlobalFileNames } from "../../shared/globalFileNames"
import { HistoryItem } from "../../shared/HistoryItem"
import { ExtensionMessage } from "../../shared/ExtensionMessage"
import { Mode, PromptComponent, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
import { experimentDefault } from "../../shared/experiments"
import { formatLanguage } from "../../shared/language"
import { Terminal, TERMINAL_SHELL_INTEGRATION_TIMEOUT } from "../../integrations/terminal/Terminal"
import { downloadTask } from "../../integrations/misc/export-markdown"
@ -68,34 +68,29 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
public static readonly tabPanelId = "roo-cline.TabPanelProvider"
private static activeInstances: Set<ClineProvider> = new Set()
private disposables: vscode.Disposable[] = []
// not private, so it can be accessed from webviewMessageHandler
view?: vscode.WebviewView | vscode.WebviewPanel
// not private, so it can be accessed from webviewMessageHandler
// callers could update to get viewLaunched() getter function
isViewLaunched = false
private view?: vscode.WebviewView | vscode.WebviewPanel
private clineStack: Cline[] = []
// not private, so it can be accessed from webviewMessageHandler
workspaceTracker?: WorkspaceTracker
// not protected, so it can be accessed from webviewMessageHandler.
// Could modify code to use getMcpHub() instead.
mcpHub?: McpHub // Change from private to protected
// not private, so it can be accessed from webviewMessageHandler
latestAnnouncementId = "apr-04-2025-boomerang" // update for Boomerang Tasks announcement
// not private, so it can be accessed from webviewMessageHandler
settingsImportedAt?: number
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 = "apr-04-2025-boomerang" // update for Boomerang Tasks announcement
public readonly contextProxy: ContextProxy
public readonly providerSettingsManager: ProviderSettingsManager
public readonly customModesManager: CustomModesManager
constructor(
readonly context: vscode.ExtensionContext,
// not private, so it can be accessed from webviewMessageHandler
readonly outputChannel: vscode.OutputChannel,
private readonly outputChannel: vscode.OutputChannel,
private readonly renderContext: "sidebar" | "editor" = "sidebar",
) {
super()
this.outputChannel.appendLine("ClineProvider instantiated")
this.log("ClineProvider instantiated")
this.contextProxy = new ContextProxy(context)
ClineProvider.activeInstances.add(this)
@ -103,7 +98,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
// properties like mode and provider.
telemetryService.setProvider(this)
this.workspaceTracker = new WorkspaceTracker(this)
this._workspaceTracker = new WorkspaceTracker(this)
this.providerSettingsManager = new ProviderSettingsManager(this.context)
@ -118,7 +113,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
this.mcpHub.registerClient()
})
.catch((error) => {
this.outputChannel.appendLine(`Failed to initialize MCP Hub: ${error}`)
this.log(`Failed to initialize MCP Hub: ${error}`)
})
}
@ -203,13 +198,13 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
- https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
*/
async dispose() {
this.outputChannel.appendLine("Disposing ClineProvider...")
this.log("Disposing ClineProvider...")
await this.removeClineFromStack()
this.outputChannel.appendLine("Cleared task")
this.log("Cleared task")
if (this.view && "dispose" in this.view) {
this.view.dispose()
this.outputChannel.appendLine("Disposed webview")
this.log("Disposed webview")
}
while (this.disposables.length) {
@ -220,12 +215,12 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
}
this.workspaceTracker?.dispose()
this.workspaceTracker = undefined
this._workspaceTracker?.dispose()
this._workspaceTracker = undefined
await this.mcpHub?.unregisterClient()
this.mcpHub = undefined
this.customModesManager?.dispose()
this.outputChannel.appendLine("Disposed all disposables")
this.log("Disposed all disposables")
ClineProvider.activeInstances.delete(this)
// Unregister from McpServerManager
@ -338,7 +333,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
}
async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) {
this.outputChannel.appendLine("Resolving webview view")
this.log("Resolving webview view")
if (!this.contextProxy.isInitialized) {
await this.contextProxy.initialize()
@ -441,7 +436,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
// If the extension is starting a new session, clear previous task state.
await this.removeClineFromStack()
this.outputChannel.appendLine("Webview view resolved")
this.log("Webview view resolved")
}
public async initClineWithSubTask(parent: Cline, task?: string, images?: string[]) {
@ -807,7 +802,6 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
await this.postStateToWebview()
}
// not private, so it can be accessed from webviewMessageHandler
async updateApiConfiguration(providerSettings: ProviderSettings) {
// Update mode's default config.
const { mode } = await this.getState()
@ -914,14 +908,17 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
return getSettingsDirectoryPath(globalStoragePath)
}
// not private, so it can be accessed from webviewMessageHandler
async ensureCacheDirectoryExists() {
private async ensureCacheDirectoryExists() {
const { getCacheDirectoryPath } = await import("../../shared/storagePathManager")
const globalStoragePath = this.contextProxy.globalStorageUri.fsPath
return getCacheDirectoryPath(globalStoragePath)
}
// not private, so it can be accessed from webviewMessageHandler
async writeModelsToCache<T>(filename: string, data: T) {
const cacheDir = await this.ensureCacheDirectoryExists()
await fs.writeFile(path.join(cacheDir, filename), JSON.stringify(data))
}
async readModelsFromCache(filename: string): Promise<Record<string, ModelInfo> | undefined> {
const filePath = path.join(await this.ensureCacheDirectoryExists(), filename)
const fileExists = await fileExistsAtPath(filePath)
@ -951,7 +948,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
throw new Error("Invalid response from OpenRouter API")
}
} catch (error) {
this.outputChannel.appendLine(
this.log(
`Error exchanging code for API key: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
throw error
@ -980,7 +977,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
throw new Error("Invalid response from Glama API")
}
} catch (error) {
this.outputChannel.appendLine(
this.log(
`Error exchanging code for API key: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
throw error
@ -1030,7 +1027,7 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
await this.postStateToWebview()
} catch (error) {
this.outputChannel.appendLine(
this.log(
`Error create new api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.create_api_config"))
@ -1380,14 +1377,12 @@ export class ClineProvider extends EventEmitter<ClineProviderEvents> implements
// ContextProxy
// @deprecated - Use `ContextProxy#setValue` instead.
// not private, so it can be accessed from webviewMessageHandler
async updateGlobalState<K extends keyof GlobalState>(key: K, value: GlobalState[K]) {
private async updateGlobalState<K extends keyof GlobalState>(key: K, value: GlobalState[K]) {
await this.contextProxy.setValue(key, value)
}
// @deprecated - Use `ContextProxy#getValue` instead.
// not private, so it can be accessed from webviewMessageHandler
getGlobalState<K extends keyof GlobalState>(key: K) {
private getGlobalState<K extends keyof GlobalState>(key: K) {
return this.contextProxy.getValue(key)
}

View file

@ -12,7 +12,7 @@ import { GlobalFileNames } from "../../shared/globalFileNames"
import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { EXPERIMENT_IDS, experiments as Experiments, experimentDefault, ExperimentId } from "../../shared/experiments"
import { EXPERIMENT_IDS, experimentDefault, ExperimentId } from "../../shared/experiments"
import { Terminal } from "../../integrations/terminal/Terminal"
import { openFile, openImage } from "../../integrations/misc/open-file"
import { selectImages } from "../../integrations/misc/process-images"
@ -37,17 +37,23 @@ import { openMention } from "../mentions"
import { telemetryService } from "../../services/telemetry/TelemetryService"
import { TelemetrySetting } from "../../shared/TelemetrySetting"
import { getWorkspacePath } from "../../utils/path"
import { Mode, PromptComponent, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
import { Mode, defaultModeSlug, getModeBySlug, getGroupName } from "../../shared/modes"
import { getDiffStrategy } from "../diff/DiffStrategy"
import { SYSTEM_PROMPT } from "../prompts/system"
import { buildApiHandler } from "../../api"
import { GlobalState } from "../../schemas"
export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
// Utility functions provided for concise get/update of global state via contextProxy API.
const getGlobalState = <K extends keyof GlobalState>(key: K) => provider.contextProxy.getValue(key)
const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
await provider.contextProxy.setValue(key, value)
switch (message.type) {
case "webviewDidLaunch":
// Load custom modes first
const customModes = await provider.customModesManager.getCustomModes()
await provider.updateGlobalState("customModes", customModes)
await updateGlobalState("customModes", customModes)
provider.postStateToWebview()
provider.workspaceTracker?.initializeFilePaths() // don't await
@ -55,15 +61,14 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
getTheme().then((theme) => provider.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }))
// If MCP Hub is already initialized, update the webview with current server list
if (provider.mcpHub) {
const mcpHub = provider.getMcpHub()
if (mcpHub) {
provider.postMessageToWebview({
type: "mcpServers",
mcpServers: provider.mcpHub.getAllServers(),
mcpServers: mcpHub.getAllServers(),
})
}
const cacheDir = await provider.ensureCacheDirectoryExists()
// Post last cached models in case the call to endpoint fails.
provider.readModelsFromCache(GlobalFileNames.openRouterModels).then((openRouterModels) => {
if (openRouterModels) {
@ -82,10 +87,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const { apiConfiguration: currentApiConfig } = await provider.getState()
getOpenRouterModels(currentApiConfig).then(async (openRouterModels) => {
if (Object.keys(openRouterModels).length > 0) {
await fs.writeFile(
path.join(cacheDir, GlobalFileNames.openRouterModels),
JSON.stringify(openRouterModels),
)
await provider.writeModelsToCache(GlobalFileNames.openRouterModels, openRouterModels)
await provider.postMessageToWebview({ type: "openRouterModels", openRouterModels })
// Update model info in state (this needs to be
@ -95,7 +97,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const { apiConfiguration } = await provider.getState()
if (apiConfiguration.openRouterModelId) {
await provider.updateGlobalState(
await updateGlobalState(
"openRouterModelInfo",
openRouterModels[apiConfiguration.openRouterModelId],
)
@ -112,13 +114,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
getGlamaModels().then(async (glamaModels) => {
if (Object.keys(glamaModels).length > 0) {
await fs.writeFile(path.join(cacheDir, GlobalFileNames.glamaModels), JSON.stringify(glamaModels))
await provider.writeModelsToCache(GlobalFileNames.glamaModels, glamaModels)
await provider.postMessageToWebview({ type: "glamaModels", glamaModels })
const { apiConfiguration } = await provider.getState()
if (apiConfiguration.glamaModelId) {
await provider.updateGlobalState("glamaModelInfo", glamaModels[apiConfiguration.glamaModelId])
await updateGlobalState("glamaModelInfo", glamaModels[apiConfiguration.glamaModelId])
await provider.postStateToWebview()
}
}
@ -132,19 +134,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
getUnboundModels().then(async (unboundModels) => {
if (Object.keys(unboundModels).length > 0) {
await fs.writeFile(
path.join(cacheDir, GlobalFileNames.unboundModels),
JSON.stringify(unboundModels),
)
await provider.writeModelsToCache(GlobalFileNames.unboundModels, unboundModels)
await provider.postMessageToWebview({ type: "unboundModels", unboundModels })
const { apiConfiguration } = await provider.getState()
if (apiConfiguration?.unboundModelId) {
await provider.updateGlobalState(
"unboundModelInfo",
unboundModels[apiConfiguration.unboundModelId],
)
await updateGlobalState("unboundModelInfo", unboundModels[apiConfiguration.unboundModelId])
await provider.postStateToWebview()
}
}
@ -158,19 +154,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
getRequestyModels().then(async (requestyModels) => {
if (Object.keys(requestyModels).length > 0) {
await fs.writeFile(
path.join(cacheDir, GlobalFileNames.requestyModels),
JSON.stringify(requestyModels),
)
await provider.writeModelsToCache(GlobalFileNames.requestyModels, requestyModels)
await provider.postMessageToWebview({ type: "requestyModels", requestyModels })
const { apiConfiguration } = await provider.getState()
if (apiConfiguration.requestyModelId) {
await provider.updateGlobalState(
"requestyModelInfo",
requestyModels[apiConfiguration.requestyModelId],
)
await updateGlobalState("requestyModelInfo", requestyModels[apiConfiguration.requestyModelId])
await provider.postStateToWebview()
}
}
@ -197,19 +187,19 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
}
const currentConfigName = provider.getGlobalState("currentApiConfigName")
const currentConfigName = getGlobalState("currentApiConfigName")
if (currentConfigName) {
if (!(await provider.providerSettingsManager.hasConfig(currentConfigName))) {
// current config name not valid, get first config in list
await provider.updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name)
await updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name)
if (listApiConfig?.[0]?.name) {
const apiConfig = await provider.providerSettingsManager.loadConfig(
listApiConfig?.[0]?.name,
)
await Promise.all([
provider.updateGlobalState("listApiConfigMeta", listApiConfig),
updateGlobalState("listApiConfigMeta", listApiConfig),
provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
provider.updateApiConfiguration(apiConfig),
])
@ -220,12 +210,12 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
await Promise.all([
await provider.updateGlobalState("listApiConfigMeta", listApiConfig),
await updateGlobalState("listApiConfigMeta", listApiConfig),
await provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
])
})
.catch((error) =>
provider.outputChannel.appendLine(
provider.log(
`Error list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
),
)
@ -260,39 +250,39 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
await provider.updateCustomInstructions(message.text)
break
case "alwaysAllowReadOnly":
await provider.updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined)
await updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined)
await provider.postStateToWebview()
break
case "alwaysAllowReadOnlyOutsideWorkspace":
await provider.updateGlobalState("alwaysAllowReadOnlyOutsideWorkspace", message.bool ?? undefined)
await updateGlobalState("alwaysAllowReadOnlyOutsideWorkspace", message.bool ?? undefined)
await provider.postStateToWebview()
break
case "alwaysAllowWrite":
await provider.updateGlobalState("alwaysAllowWrite", message.bool ?? undefined)
await updateGlobalState("alwaysAllowWrite", message.bool ?? undefined)
await provider.postStateToWebview()
break
case "alwaysAllowWriteOutsideWorkspace":
await provider.updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined)
await updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined)
await provider.postStateToWebview()
break
case "alwaysAllowExecute":
await provider.updateGlobalState("alwaysAllowExecute", message.bool ?? undefined)
await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined)
await provider.postStateToWebview()
break
case "alwaysAllowBrowser":
await provider.updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
await updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
await provider.postStateToWebview()
break
case "alwaysAllowMcp":
await provider.updateGlobalState("alwaysAllowMcp", message.bool)
await updateGlobalState("alwaysAllowMcp", message.bool)
await provider.postStateToWebview()
break
case "alwaysAllowModeSwitch":
await provider.updateGlobalState("alwaysAllowModeSwitch", message.bool)
await updateGlobalState("alwaysAllowModeSwitch", message.bool)
await provider.postStateToWebview()
break
case "alwaysAllowSubtasks":
await provider.updateGlobalState("alwaysAllowSubtasks", message.bool)
await updateGlobalState("alwaysAllowSubtasks", message.bool)
await provider.postStateToWebview()
break
case "askResponse":
@ -304,7 +294,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
await provider.postStateToWebview()
break
case "didShowAnnouncement":
await provider.updateGlobalState("lastShownAnnouncementId", provider.latestAnnouncementId)
await updateGlobalState("lastShownAnnouncementId", provider.latestAnnouncementId)
await provider.postStateToWebview()
break
case "selectImages":
@ -398,11 +388,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const openRouterModels = await getOpenRouterModels(configForRefresh)
if (Object.keys(openRouterModels).length > 0) {
const cacheDir = await provider.ensureCacheDirectoryExists()
await fs.writeFile(
path.join(cacheDir, GlobalFileNames.openRouterModels),
JSON.stringify(openRouterModels),
)
await provider.writeModelsToCache(GlobalFileNames.openRouterModels, openRouterModels)
await provider.postMessageToWebview({ type: "openRouterModels", openRouterModels })
}
@ -412,8 +398,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const glamaModels = await getGlamaModels()
if (Object.keys(glamaModels).length > 0) {
const cacheDir = await provider.ensureCacheDirectoryExists()
await fs.writeFile(path.join(cacheDir, GlobalFileNames.glamaModels), JSON.stringify(glamaModels))
await provider.writeModelsToCache(GlobalFileNames.glamaModels, glamaModels)
await provider.postMessageToWebview({ type: "glamaModels", glamaModels })
}
@ -422,8 +407,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const unboundModels = await getUnboundModels()
if (Object.keys(unboundModels).length > 0) {
const cacheDir = await provider.ensureCacheDirectoryExists()
await fs.writeFile(path.join(cacheDir, GlobalFileNames.unboundModels), JSON.stringify(unboundModels))
await provider.writeModelsToCache(GlobalFileNames.unboundModels, unboundModels)
await provider.postMessageToWebview({ type: "unboundModels", unboundModels })
}
@ -432,8 +416,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const requestyModels = await getRequestyModels()
if (Object.keys(requestyModels).length > 0) {
const cacheDir = await provider.ensureCacheDirectoryExists()
await fs.writeFile(path.join(cacheDir, GlobalFileNames.requestyModels), JSON.stringify(requestyModels))
await provider.writeModelsToCache(GlobalFileNames.requestyModels, requestyModels)
await provider.postMessageToWebview({ type: "requestyModels", requestyModels })
}
@ -509,7 +492,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
.update("allowedCommands", message.commands, vscode.ConfigurationTarget.Global)
break
case "openMcpSettings": {
const mcpSettingsFilePath = await provider.mcpHub?.getMcpSettingsFilePath()
const mcpSettingsFilePath = await provider.getMcpHub()?.getMcpSettingsFilePath()
if (mcpSettingsFilePath) {
openFile(mcpSettingsFilePath)
}
@ -550,21 +533,21 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
try {
provider.outputChannel.appendLine(`Attempting to delete MCP server: ${message.serverName}`)
await provider.mcpHub?.deleteServer(message.serverName, message.source as "global" | "project")
provider.outputChannel.appendLine(`Successfully deleted MCP server: ${message.serverName}`)
provider.log(`Attempting to delete MCP server: ${message.serverName}`)
await provider.getMcpHub()?.deleteServer(message.serverName, message.source as "global" | "project")
provider.log(`Successfully deleted MCP server: ${message.serverName}`)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error)
provider.outputChannel.appendLine(`Failed to delete MCP server: ${errorMessage}`)
provider.log(`Failed to delete MCP server: ${errorMessage}`)
// Error messages are already handled by McpHub.deleteServer
}
break
}
case "restartMcpServer": {
try {
await provider.mcpHub?.restartConnection(message.text!, message.source as "global" | "project")
await provider.getMcpHub()?.restartConnection(message.text!, message.source as "global" | "project")
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Failed to retry connection for ${message.text}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
}
@ -572,16 +555,16 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
case "toggleToolAlwaysAllow": {
try {
if (provider.mcpHub) {
await provider.mcpHub.toggleToolAlwaysAllow(
await provider
.getMcpHub()
?.toggleToolAlwaysAllow(
message.serverName!,
message.source as "global" | "project",
message.toolName!,
Boolean(message.alwaysAllow),
)
}
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Failed to toggle auto-approve for tool ${message.toolName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
}
@ -589,13 +572,15 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
case "toggleMcpServer": {
try {
await provider.mcpHub?.toggleServerDisabled(
message.serverName!,
message.disabled!,
message.source as "global" | "project",
)
await provider
.getMcpHub()
?.toggleServerDisabled(
message.serverName!,
message.disabled!,
message.source as "global" | "project",
)
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Failed to toggle MCP server ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
}
@ -603,11 +588,11 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
case "mcpEnabled":
const mcpEnabled = message.bool ?? true
await provider.updateGlobalState("mcpEnabled", mcpEnabled)
await updateGlobalState("mcpEnabled", mcpEnabled)
await provider.postStateToWebview()
break
case "enableMcpServerCreation":
await provider.updateGlobalState("enableMcpServerCreation", message.bool ?? true)
await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
await provider.postStateToWebview()
break
case "playSound":
@ -618,25 +603,25 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
break
case "soundEnabled":
const soundEnabled = message.bool ?? true
await provider.updateGlobalState("soundEnabled", soundEnabled)
await updateGlobalState("soundEnabled", soundEnabled)
setSoundEnabled(soundEnabled) // Add this line to update the sound utility
await provider.postStateToWebview()
break
case "soundVolume":
const soundVolume = message.value ?? 0.5
await provider.updateGlobalState("soundVolume", soundVolume)
await updateGlobalState("soundVolume", soundVolume)
setSoundVolume(soundVolume)
await provider.postStateToWebview()
break
case "ttsEnabled":
const ttsEnabled = message.bool ?? true
await provider.updateGlobalState("ttsEnabled", ttsEnabled)
await updateGlobalState("ttsEnabled", ttsEnabled)
setTtsEnabled(ttsEnabled) // Add this line to update the tts utility
await provider.postStateToWebview()
break
case "ttsSpeed":
const ttsSpeed = message.value ?? 1.0
await provider.updateGlobalState("ttsSpeed", ttsSpeed)
await updateGlobalState("ttsSpeed", ttsSpeed)
setTtsSpeed(ttsSpeed)
await provider.postStateToWebview()
break
@ -653,36 +638,36 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
break
case "diffEnabled":
const diffEnabled = message.bool ?? true
await provider.updateGlobalState("diffEnabled", diffEnabled)
await updateGlobalState("diffEnabled", diffEnabled)
await provider.postStateToWebview()
break
case "enableCheckpoints":
const enableCheckpoints = message.bool ?? true
await provider.updateGlobalState("enableCheckpoints", enableCheckpoints)
await updateGlobalState("enableCheckpoints", enableCheckpoints)
await provider.postStateToWebview()
break
case "checkpointStorage":
console.log(`[ClineProvider] checkpointStorage: ${message.text}`)
const checkpointStorage = message.text ?? "task"
await provider.updateGlobalState("checkpointStorage", checkpointStorage as CheckpointStorage)
await updateGlobalState("checkpointStorage", checkpointStorage as CheckpointStorage)
await provider.postStateToWebview()
break
case "browserViewportSize":
const browserViewportSize = message.text ?? "900x600"
await provider.updateGlobalState("browserViewportSize", browserViewportSize)
await updateGlobalState("browserViewportSize", browserViewportSize)
await provider.postStateToWebview()
break
case "remoteBrowserHost":
await provider.updateGlobalState("remoteBrowserHost", message.text)
await updateGlobalState("remoteBrowserHost", message.text)
await provider.postStateToWebview()
break
case "remoteBrowserEnabled":
// Store the preference in global state
// remoteBrowserEnabled now means "enable remote browser connection"
await provider.updateGlobalState("remoteBrowserEnabled", message.bool ?? false)
await updateGlobalState("remoteBrowserEnabled", message.bool ?? false)
// If disabling remote browser connection, clear the remoteBrowserHost
if (!message.bool) {
await provider.updateGlobalState("remoteBrowserHost", undefined)
await updateGlobalState("remoteBrowserHost", undefined)
}
await provider.postStateToWebview()
break
@ -721,31 +706,31 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
break
case "fuzzyMatchThreshold":
await provider.updateGlobalState("fuzzyMatchThreshold", message.value)
await updateGlobalState("fuzzyMatchThreshold", message.value)
await provider.postStateToWebview()
break
case "alwaysApproveResubmit":
await provider.updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
await updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
await provider.postStateToWebview()
break
case "requestDelaySeconds":
await provider.updateGlobalState("requestDelaySeconds", message.value ?? 5)
await updateGlobalState("requestDelaySeconds", message.value ?? 5)
await provider.postStateToWebview()
break
case "rateLimitSeconds":
await provider.updateGlobalState("rateLimitSeconds", message.value ?? 0)
await updateGlobalState("rateLimitSeconds", message.value ?? 0)
await provider.postStateToWebview()
break
case "writeDelayMs":
await provider.updateGlobalState("writeDelayMs", message.value)
await updateGlobalState("writeDelayMs", message.value)
await provider.postStateToWebview()
break
case "terminalOutputLineLimit":
await provider.updateGlobalState("terminalOutputLineLimit", message.value)
await updateGlobalState("terminalOutputLineLimit", message.value)
await provider.postStateToWebview()
break
case "terminalShellIntegrationTimeout":
await provider.updateGlobalState("terminalShellIntegrationTimeout", message.value)
await updateGlobalState("terminalShellIntegrationTimeout", message.value)
await provider.postStateToWebview()
if (message.value !== undefined) {
Terminal.setShellIntegrationTimeout(message.value)
@ -760,12 +745,12 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
return
}
const existingPrompts = provider.getGlobalState("customSupportPrompts") ?? {}
const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
const updatedPrompts = { ...existingPrompts, ...message.values }
await provider.updateGlobalState("customSupportPrompts", updatedPrompts)
await updateGlobalState("customSupportPrompts", updatedPrompts)
await provider.postStateToWebview()
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error update support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.update_support_prompt"))
@ -777,13 +762,13 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
return
}
const existingPrompts = provider.getGlobalState("customSupportPrompts") ?? {}
const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
const updatedPrompts = { ...existingPrompts }
updatedPrompts[message.text] = undefined
await provider.updateGlobalState("customSupportPrompts", updatedPrompts)
await updateGlobalState("customSupportPrompts", updatedPrompts)
await provider.postStateToWebview()
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error reset support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.reset_support_prompt"))
@ -791,12 +776,12 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
break
case "updatePrompt":
if (message.promptMode && message.customPrompt !== undefined) {
const existingPrompts = provider.getGlobalState("customModePrompts") ?? {}
const existingPrompts = getGlobalState("customModePrompts") ?? {}
const updatedPrompts = { ...existingPrompts, [message.promptMode]: message.customPrompt }
await provider.updateGlobalState("customModePrompts", updatedPrompts)
const currentState = await provider.getState()
await updateGlobalState("customModePrompts", updatedPrompts)
const currentState = await provider.getStateToPostToWebview()
const stateWithPrompts = { ...currentState, customModePrompts: updatedPrompts }
provider.view?.webview.postMessage({ type: "state", state: stateWithPrompts })
provider.postMessageToWebview({ type: "state", state: stateWithPrompts })
}
break
case "deleteMessage": {
@ -906,39 +891,39 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
break
}
case "screenshotQuality":
await provider.updateGlobalState("screenshotQuality", message.value)
await updateGlobalState("screenshotQuality", message.value)
await provider.postStateToWebview()
break
case "maxOpenTabsContext":
const tabCount = Math.min(Math.max(0, message.value ?? 20), 500)
await provider.updateGlobalState("maxOpenTabsContext", tabCount)
await updateGlobalState("maxOpenTabsContext", tabCount)
await provider.postStateToWebview()
break
case "maxWorkspaceFiles":
const fileCount = Math.min(Math.max(0, message.value ?? 200), 500)
await provider.updateGlobalState("maxWorkspaceFiles", fileCount)
await updateGlobalState("maxWorkspaceFiles", fileCount)
await provider.postStateToWebview()
break
case "browserToolEnabled":
await provider.updateGlobalState("browserToolEnabled", message.bool ?? true)
await updateGlobalState("browserToolEnabled", message.bool ?? true)
await provider.postStateToWebview()
break
case "language":
changeLanguage(message.text ?? "en")
await provider.updateGlobalState("language", message.text as Language)
await updateGlobalState("language", message.text as Language)
await provider.postStateToWebview()
break
case "showRooIgnoredFiles":
await provider.updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
await provider.postStateToWebview()
break
case "maxReadFileLine":
await provider.updateGlobalState("maxReadFileLine", message.value)
await updateGlobalState("maxReadFileLine", message.value)
await provider.postStateToWebview()
break
case "toggleApiConfigPin":
if (message.text) {
const currentPinned = provider.getGlobalState("pinnedApiConfigs") ?? {}
const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
const updatedPinned: Record<string, boolean> = { ...currentPinned }
if (currentPinned[message.text]) {
@ -947,16 +932,16 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
updatedPinned[message.text] = true
}
await provider.updateGlobalState("pinnedApiConfigs", updatedPinned)
await updateGlobalState("pinnedApiConfigs", updatedPinned)
await provider.postStateToWebview()
}
break
case "enhancementApiConfigId":
await provider.updateGlobalState("enhancementApiConfigId", message.text)
await updateGlobalState("enhancementApiConfigId", message.text)
await provider.postStateToWebview()
break
case "autoApprovalEnabled":
await provider.updateGlobalState("autoApprovalEnabled", message.bool ?? false)
await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
await provider.postStateToWebview()
break
case "enhancePrompt":
@ -993,7 +978,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
text: enhancedPrompt,
})
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error enhancing prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.enhance_prompt"))
@ -1013,7 +998,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
mode: message.mode,
})
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
@ -1026,7 +1011,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
await vscode.env.clipboard.writeText(systemPrompt)
await vscode.window.showInformationMessage(t("common:info.clipboard_copy"))
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
@ -1042,7 +1027,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
commits,
})
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error searching commits: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.search_commits"))
@ -1095,9 +1080,9 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
try {
await provider.providerSettingsManager.saveConfig(message.text, message.apiConfiguration)
const listApiConfig = await provider.providerSettingsManager.listConfig()
await provider.updateGlobalState("listApiConfigMeta", listApiConfig)
await updateGlobalState("listApiConfigMeta", listApiConfig)
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error save api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.save_api_config"))
@ -1134,12 +1119,12 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const listApiConfig = await provider.providerSettingsManager.listConfig()
// Update listApiConfigMeta first to ensure UI has latest data
await provider.updateGlobalState("listApiConfigMeta", listApiConfig)
await provider.updateGlobalState("currentApiConfigName", newName)
await updateGlobalState("listApiConfigMeta", listApiConfig)
await updateGlobalState("currentApiConfigName", newName)
await provider.postStateToWebview()
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error rename api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.rename_api_config"))
@ -1153,14 +1138,14 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const listApiConfig = await provider.providerSettingsManager.listConfig()
await Promise.all([
provider.updateGlobalState("listApiConfigMeta", listApiConfig),
provider.updateGlobalState("currentApiConfigName", message.text),
updateGlobalState("listApiConfigMeta", listApiConfig),
updateGlobalState("currentApiConfigName", message.text),
provider.updateApiConfiguration(apiConfig),
])
await provider.postStateToWebview()
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error load api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.load_api_config"))
@ -1176,14 +1161,14 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const listApiConfig = await provider.providerSettingsManager.listConfig()
await Promise.all([
provider.updateGlobalState("listApiConfigMeta", listApiConfig),
provider.updateGlobalState("currentApiConfigName", name),
updateGlobalState("listApiConfigMeta", listApiConfig),
updateGlobalState("currentApiConfigName", name),
provider.updateApiConfiguration(apiConfig),
])
await provider.postStateToWebview()
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error load api configuration by ID: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.load_api_config"))
@ -1207,22 +1192,22 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
const listApiConfig = await provider.providerSettingsManager.listConfig()
// Update listApiConfigMeta first to ensure UI has latest data
await provider.updateGlobalState("listApiConfigMeta", listApiConfig)
await updateGlobalState("listApiConfigMeta", listApiConfig)
// If this was the current config, switch to first available
const currentApiConfigName = provider.getGlobalState("currentApiConfigName")
const currentApiConfigName = getGlobalState("currentApiConfigName")
if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) {
const apiConfig = await provider.providerSettingsManager.loadConfig(listApiConfig[0].name)
await Promise.all([
provider.updateGlobalState("currentApiConfigName", listApiConfig[0].name),
updateGlobalState("currentApiConfigName", listApiConfig[0].name),
provider.updateApiConfiguration(apiConfig),
])
}
await provider.postStateToWebview()
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error delete api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.delete_api_config"))
@ -1232,10 +1217,10 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
case "getListApiConfiguration":
try {
const listApiConfig = await provider.providerSettingsManager.listConfig()
await provider.updateGlobalState("listApiConfigMeta", listApiConfig)
await updateGlobalState("listApiConfigMeta", listApiConfig)
provider.postMessageToWebview({ type: "listApiConfig", listApiConfig })
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Error get list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.list_api_config"))
@ -1247,11 +1232,11 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
}
const updatedExperiments = {
...(provider.getGlobalState("experiments") ?? experimentDefault),
...(getGlobalState("experiments") ?? experimentDefault),
...message.values,
}
await provider.updateGlobalState("experiments", updatedExperiments)
await updateGlobalState("experiments", updatedExperiments)
await provider.postStateToWebview()
break
@ -1259,13 +1244,15 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
case "updateMcpTimeout":
if (message.serverName && typeof message.timeout === "number") {
try {
await provider.mcpHub?.updateServerTimeout(
message.serverName,
message.timeout,
message.source as "global" | "project",
)
await provider
.getMcpHub()
?.updateServerTimeout(
message.serverName,
message.timeout,
message.source as "global" | "project",
)
} catch (error) {
provider.outputChannel.appendLine(
provider.log(
`Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
)
vscode.window.showErrorMessage(t("common:errors.update_server_timeout"))
@ -1277,8 +1264,8 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
await provider.customModesManager.updateCustomMode(message.modeConfig.slug, message.modeConfig)
// Update state after saving the mode
const customModes = await provider.customModesManager.getCustomModes()
await provider.updateGlobalState("customModes", customModes)
await provider.updateGlobalState("mode", message.modeConfig.slug)
await updateGlobalState("customModes", customModes)
await updateGlobalState("mode", message.modeConfig.slug)
await provider.postStateToWebview()
}
break
@ -1296,7 +1283,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
await provider.customModesManager.deleteCustomMode(message.slug)
// Switch back to default mode after deletion
await provider.updateGlobalState("mode", defaultModeSlug)
await updateGlobalState("mode", defaultModeSlug)
await provider.postStateToWebview()
}
break
@ -1321,7 +1308,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We
case "telemetrySetting": {
const telemetrySetting = message.text as TelemetrySetting
await provider.updateGlobalState("telemetrySetting", telemetrySetting)
await updateGlobalState("telemetrySetting", telemetrySetting)
const isOptedIn = telemetrySetting === "enabled"
telemetryService.updateTelemetryState(isOptedIn)
await provider.postStateToWebview()
@ -1383,7 +1370,7 @@ const generateSystemPrompt = async (provider: ClineProvider, message: WebviewMes
provider.context,
cwd,
canUseBrowserTool,
mcpEnabled ? provider.mcpHub : undefined,
mcpEnabled ? provider.getMcpHub() : undefined,
diffStrategy,
browserViewportSize ?? "900x600",
mode,