webviewMessageHandler.ts 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309
  1. import * as path from "path"
  2. import fs from "fs/promises"
  3. import pWaitFor from "p-wait-for"
  4. import * as vscode from "vscode"
  5. import { ClineProvider } from "./ClineProvider"
  6. import { Language, ApiConfigMeta } from "../../schemas"
  7. import { changeLanguage, t } from "../../i18n"
  8. import { ApiConfiguration, RouterName, toRouterName } from "../../shared/api"
  9. import { supportPrompt } from "../../shared/support-prompt"
  10. import { checkoutDiffPayloadSchema, checkoutRestorePayloadSchema, WebviewMessage } from "../../shared/WebviewMessage"
  11. import { checkExistKey } from "../../shared/checkExistApiConfig"
  12. import { experimentDefault } from "../../shared/experiments"
  13. import { Terminal } from "../../integrations/terminal/Terminal"
  14. import { openFile, openImage } from "../../integrations/misc/open-file"
  15. import { selectImages } from "../../integrations/misc/process-images"
  16. import { getTheme } from "../../integrations/theme/getTheme"
  17. import { discoverChromeHostUrl, tryChromeHostUrl } from "../../services/browser/browserDiscovery"
  18. import { searchWorkspaceFiles } from "../../services/search/file-search"
  19. import { fileExistsAtPath } from "../../utils/fs"
  20. import { playSound, setSoundEnabled, setSoundVolume } from "../../utils/sound"
  21. import { playTts, setTtsEnabled, setTtsSpeed, stopTts } from "../../utils/tts"
  22. import { singleCompletionHandler } from "../../utils/single-completion-handler"
  23. import { searchCommits } from "../../utils/git"
  24. import { exportSettings, importSettings } from "../config/importExport"
  25. import { getOpenAiModels } from "../../api/providers/openai"
  26. import { getOllamaModels } from "../../api/providers/ollama"
  27. import { getVsCodeLmModels } from "../../api/providers/vscode-lm"
  28. import { getLmStudioModels } from "../../api/providers/lmstudio"
  29. import { openMention } from "../mentions"
  30. import { telemetryService } from "../../services/telemetry/TelemetryService"
  31. import { TelemetrySetting } from "../../shared/TelemetrySetting"
  32. import { getWorkspacePath } from "../../utils/path"
  33. import { Mode, defaultModeSlug } from "../../shared/modes"
  34. import { GlobalState } from "../../schemas"
  35. import { getModels, flushModels } from "../../api/providers/fetchers/cache"
  36. import { generateSystemPrompt } from "./generateSystemPrompt"
  37. const ALLOWED_VSCODE_SETTINGS = new Set(["terminal.integrated.inheritEnv"])
  38. export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
  39. // Utility functions provided for concise get/update of global state via contextProxy API.
  40. const getGlobalState = <K extends keyof GlobalState>(key: K) => provider.contextProxy.getValue(key)
  41. const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
  42. await provider.contextProxy.setValue(key, value)
  43. switch (message.type) {
  44. case "webviewDidLaunch":
  45. // Load custom modes first
  46. const customModes = await provider.customModesManager.getCustomModes()
  47. await updateGlobalState("customModes", customModes)
  48. provider.postStateToWebview()
  49. provider.workspaceTracker?.initializeFilePaths() // Don't await.
  50. getTheme().then((theme) => provider.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }))
  51. // If MCP Hub is already initialized, update the webview with
  52. // current server list.
  53. const mcpHub = provider.getMcpHub()
  54. if (mcpHub) {
  55. provider.postMessageToWebview({ type: "mcpServers", mcpServers: mcpHub.getAllServers() })
  56. }
  57. provider.providerSettingsManager
  58. .listConfig()
  59. .then(async (listApiConfig) => {
  60. if (!listApiConfig) {
  61. return
  62. }
  63. if (listApiConfig.length === 1) {
  64. // Check if first time init then sync with exist config.
  65. if (!checkExistKey(listApiConfig[0])) {
  66. const { apiConfiguration } = await provider.getState()
  67. await provider.providerSettingsManager.saveConfig(
  68. listApiConfig[0].name ?? "default",
  69. apiConfiguration,
  70. )
  71. listApiConfig[0].apiProvider = apiConfiguration.apiProvider
  72. }
  73. }
  74. const currentConfigName = getGlobalState("currentApiConfigName")
  75. if (currentConfigName) {
  76. if (!(await provider.providerSettingsManager.hasConfig(currentConfigName))) {
  77. // current config name not valid, get first config in list
  78. await updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name)
  79. if (listApiConfig?.[0]?.name) {
  80. const apiConfig = await provider.providerSettingsManager.loadConfig(
  81. listApiConfig?.[0]?.name,
  82. )
  83. await Promise.all([
  84. updateGlobalState("listApiConfigMeta", listApiConfig),
  85. provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
  86. provider.updateApiConfiguration(apiConfig),
  87. ])
  88. await provider.postStateToWebview()
  89. return
  90. }
  91. }
  92. }
  93. await Promise.all([
  94. await updateGlobalState("listApiConfigMeta", listApiConfig),
  95. await provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
  96. ])
  97. })
  98. .catch((error) =>
  99. provider.log(
  100. `Error list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  101. ),
  102. )
  103. // If user already opted in to telemetry, enable telemetry service
  104. provider.getStateToPostToWebview().then((state) => {
  105. const { telemetrySetting } = state
  106. const isOptedIn = telemetrySetting === "enabled"
  107. telemetryService.updateTelemetryState(isOptedIn)
  108. })
  109. provider.isViewLaunched = true
  110. break
  111. case "newTask":
  112. // Initializing new instance of Cline will make sure that any
  113. // agentically running promises in old instance don't affect our new
  114. // task. This essentially creates a fresh slate for the new task.
  115. await provider.initClineWithTask(message.text, message.images)
  116. break
  117. case "apiConfiguration":
  118. if (message.apiConfiguration) {
  119. await provider.updateApiConfiguration(message.apiConfiguration)
  120. }
  121. await provider.postStateToWebview()
  122. break
  123. case "customInstructions":
  124. await provider.updateCustomInstructions(message.text)
  125. break
  126. case "alwaysAllowReadOnly":
  127. await updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined)
  128. await provider.postStateToWebview()
  129. break
  130. case "alwaysAllowReadOnlyOutsideWorkspace":
  131. await updateGlobalState("alwaysAllowReadOnlyOutsideWorkspace", message.bool ?? undefined)
  132. await provider.postStateToWebview()
  133. break
  134. case "alwaysAllowWrite":
  135. await updateGlobalState("alwaysAllowWrite", message.bool ?? undefined)
  136. await provider.postStateToWebview()
  137. break
  138. case "alwaysAllowWriteOutsideWorkspace":
  139. await updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined)
  140. await provider.postStateToWebview()
  141. break
  142. case "alwaysAllowExecute":
  143. await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined)
  144. await provider.postStateToWebview()
  145. break
  146. case "alwaysAllowBrowser":
  147. await updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
  148. await provider.postStateToWebview()
  149. break
  150. case "alwaysAllowMcp":
  151. await updateGlobalState("alwaysAllowMcp", message.bool)
  152. await provider.postStateToWebview()
  153. break
  154. case "alwaysAllowModeSwitch":
  155. await updateGlobalState("alwaysAllowModeSwitch", message.bool)
  156. await provider.postStateToWebview()
  157. break
  158. case "alwaysAllowSubtasks":
  159. await updateGlobalState("alwaysAllowSubtasks", message.bool)
  160. await provider.postStateToWebview()
  161. break
  162. case "askResponse":
  163. provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
  164. break
  165. case "terminalOperation":
  166. if (message.terminalOperation) {
  167. provider.getCurrentCline()?.handleTerminalOperation(message.terminalOperation)
  168. }
  169. break
  170. case "clearTask":
  171. // clear task resets the current session and allows for a new task to be started, if this session is a subtask - it allows the parent task to be resumed
  172. await provider.finishSubTask(t("common:tasks.canceled"))
  173. await provider.postStateToWebview()
  174. break
  175. case "didShowAnnouncement":
  176. await updateGlobalState("lastShownAnnouncementId", provider.latestAnnouncementId)
  177. await provider.postStateToWebview()
  178. break
  179. case "selectImages":
  180. const images = await selectImages()
  181. await provider.postMessageToWebview({ type: "selectedImages", images })
  182. break
  183. case "exportCurrentTask":
  184. const currentTaskId = provider.getCurrentCline()?.taskId
  185. if (currentTaskId) {
  186. provider.exportTaskWithId(currentTaskId)
  187. }
  188. break
  189. case "showTaskWithId":
  190. provider.showTaskWithId(message.text!)
  191. break
  192. case "deleteTaskWithId":
  193. provider.deleteTaskWithId(message.text!)
  194. break
  195. case "deleteMultipleTasksWithIds": {
  196. const ids = message.ids
  197. if (Array.isArray(ids)) {
  198. // Process in batches of 20 (or another reasonable number)
  199. const batchSize = 20
  200. const results = []
  201. // Only log start and end of the operation
  202. console.log(`Batch deletion started: ${ids.length} tasks total`)
  203. for (let i = 0; i < ids.length; i += batchSize) {
  204. const batch = ids.slice(i, i + batchSize)
  205. const batchPromises = batch.map(async (id) => {
  206. try {
  207. await provider.deleteTaskWithId(id)
  208. return { id, success: true }
  209. } catch (error) {
  210. // Keep error logging for debugging purposes
  211. console.log(
  212. `Failed to delete task ${id}: ${error instanceof Error ? error.message : String(error)}`,
  213. )
  214. return { id, success: false }
  215. }
  216. })
  217. // Process each batch in parallel but wait for completion before starting the next batch
  218. const batchResults = await Promise.all(batchPromises)
  219. results.push(...batchResults)
  220. // Update the UI after each batch to show progress
  221. await provider.postStateToWebview()
  222. }
  223. // Log final results
  224. const successCount = results.filter((r) => r.success).length
  225. const failCount = results.length - successCount
  226. console.log(
  227. `Batch deletion completed: ${successCount}/${ids.length} tasks successful, ${failCount} tasks failed`,
  228. )
  229. }
  230. break
  231. }
  232. case "exportTaskWithId":
  233. provider.exportTaskWithId(message.text!)
  234. break
  235. case "importSettings":
  236. const { success } = await importSettings({
  237. providerSettingsManager: provider.providerSettingsManager,
  238. contextProxy: provider.contextProxy,
  239. customModesManager: provider.customModesManager,
  240. })
  241. if (success) {
  242. provider.settingsImportedAt = Date.now()
  243. await provider.postStateToWebview()
  244. await vscode.window.showInformationMessage(t("common:info.settings_imported"))
  245. }
  246. break
  247. case "exportSettings":
  248. await exportSettings({
  249. providerSettingsManager: provider.providerSettingsManager,
  250. contextProxy: provider.contextProxy,
  251. })
  252. break
  253. case "resetState":
  254. await provider.resetState()
  255. break
  256. case "flushRouterModels":
  257. const routerName: RouterName = toRouterName(message.text)
  258. await flushModels(routerName)
  259. break
  260. case "requestRouterModels":
  261. const { apiConfiguration } = await provider.getState()
  262. const [openRouterModels, requestyModels, glamaModels, unboundModels] = await Promise.all([
  263. getModels("openrouter", apiConfiguration.openRouterApiKey),
  264. getModels("requesty", apiConfiguration.requestyApiKey),
  265. getModels("glama", apiConfiguration.glamaApiKey),
  266. getModels("unbound", apiConfiguration.unboundApiKey),
  267. ])
  268. provider.postMessageToWebview({
  269. type: "routerModels",
  270. routerModels: {
  271. openrouter: openRouterModels,
  272. requesty: requestyModels,
  273. glama: glamaModels,
  274. unbound: unboundModels,
  275. },
  276. })
  277. break
  278. case "requestOpenAiModels":
  279. if (message?.values?.baseUrl && message?.values?.apiKey) {
  280. const openAiModels = await getOpenAiModels(
  281. message?.values?.baseUrl,
  282. message?.values?.apiKey,
  283. message?.values?.openAiHeaders,
  284. )
  285. provider.postMessageToWebview({ type: "openAiModels", openAiModels })
  286. }
  287. break
  288. case "requestOllamaModels":
  289. const ollamaModels = await getOllamaModels(message.text)
  290. // TODO: Cache like we do for OpenRouter, etc?
  291. provider.postMessageToWebview({ type: "ollamaModels", ollamaModels })
  292. break
  293. case "requestLmStudioModels":
  294. const lmStudioModels = await getLmStudioModels(message.text)
  295. // TODO: Cache like we do for OpenRouter, etc?
  296. provider.postMessageToWebview({ type: "lmStudioModels", lmStudioModels })
  297. break
  298. case "requestVsCodeLmModels":
  299. const vsCodeLmModels = await getVsCodeLmModels()
  300. // TODO: Cache like we do for OpenRouter, etc?
  301. provider.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
  302. break
  303. case "openImage":
  304. openImage(message.text!)
  305. break
  306. case "openFile":
  307. openFile(message.text!, message.values as { create?: boolean; content?: string; line?: number })
  308. break
  309. case "openMention":
  310. openMention(message.text)
  311. break
  312. case "checkpointDiff":
  313. const result = checkoutDiffPayloadSchema.safeParse(message.payload)
  314. if (result.success) {
  315. await provider.getCurrentCline()?.checkpointDiff(result.data)
  316. }
  317. break
  318. case "checkpointRestore": {
  319. const result = checkoutRestorePayloadSchema.safeParse(message.payload)
  320. if (result.success) {
  321. await provider.cancelTask()
  322. try {
  323. await pWaitFor(() => provider.getCurrentCline()?.isInitialized === true, { timeout: 3_000 })
  324. } catch (error) {
  325. vscode.window.showErrorMessage(t("common:errors.checkpoint_timeout"))
  326. }
  327. try {
  328. await provider.getCurrentCline()?.checkpointRestore(result.data)
  329. } catch (error) {
  330. vscode.window.showErrorMessage(t("common:errors.checkpoint_failed"))
  331. }
  332. }
  333. break
  334. }
  335. case "cancelTask":
  336. await provider.cancelTask()
  337. break
  338. case "allowedCommands":
  339. await provider.context.globalState.update("allowedCommands", message.commands)
  340. // Also update workspace settings.
  341. await vscode.workspace
  342. .getConfiguration("roo-cline")
  343. .update("allowedCommands", message.commands, vscode.ConfigurationTarget.Global)
  344. break
  345. case "openCustomModesSettings": {
  346. const customModesFilePath = await provider.customModesManager.getCustomModesFilePath()
  347. if (customModesFilePath) {
  348. openFile(customModesFilePath)
  349. }
  350. break
  351. }
  352. case "openMcpSettings": {
  353. const mcpSettingsFilePath = await provider.getMcpHub()?.getMcpSettingsFilePath()
  354. if (mcpSettingsFilePath) {
  355. openFile(mcpSettingsFilePath)
  356. }
  357. break
  358. }
  359. case "openProjectMcpSettings": {
  360. if (!vscode.workspace.workspaceFolders?.length) {
  361. vscode.window.showErrorMessage(t("common:errors.no_workspace"))
  362. return
  363. }
  364. const workspaceFolder = vscode.workspace.workspaceFolders[0]
  365. const rooDir = path.join(workspaceFolder.uri.fsPath, ".roo")
  366. const mcpPath = path.join(rooDir, "mcp.json")
  367. try {
  368. await fs.mkdir(rooDir, { recursive: true })
  369. const exists = await fileExistsAtPath(mcpPath)
  370. if (!exists) {
  371. await fs.writeFile(mcpPath, JSON.stringify({ mcpServers: {} }, null, 2))
  372. }
  373. await openFile(mcpPath)
  374. } catch (error) {
  375. vscode.window.showErrorMessage(t("common:errors.create_mcp_json", { error: `${error}` }))
  376. }
  377. break
  378. }
  379. case "deleteMcpServer": {
  380. if (!message.serverName) {
  381. break
  382. }
  383. try {
  384. provider.log(`Attempting to delete MCP server: ${message.serverName}`)
  385. await provider.getMcpHub()?.deleteServer(message.serverName, message.source as "global" | "project")
  386. provider.log(`Successfully deleted MCP server: ${message.serverName}`)
  387. } catch (error) {
  388. const errorMessage = error instanceof Error ? error.message : String(error)
  389. provider.log(`Failed to delete MCP server: ${errorMessage}`)
  390. // Error messages are already handled by McpHub.deleteServer
  391. }
  392. break
  393. }
  394. case "restartMcpServer": {
  395. try {
  396. await provider.getMcpHub()?.restartConnection(message.text!, message.source as "global" | "project")
  397. } catch (error) {
  398. provider.log(
  399. `Failed to retry connection for ${message.text}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  400. )
  401. }
  402. break
  403. }
  404. case "toggleToolAlwaysAllow": {
  405. try {
  406. await provider
  407. .getMcpHub()
  408. ?.toggleToolAlwaysAllow(
  409. message.serverName!,
  410. message.source as "global" | "project",
  411. message.toolName!,
  412. Boolean(message.alwaysAllow),
  413. )
  414. } catch (error) {
  415. provider.log(
  416. `Failed to toggle auto-approve for tool ${message.toolName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  417. )
  418. }
  419. break
  420. }
  421. case "toggleMcpServer": {
  422. try {
  423. await provider
  424. .getMcpHub()
  425. ?.toggleServerDisabled(
  426. message.serverName!,
  427. message.disabled!,
  428. message.source as "global" | "project",
  429. )
  430. } catch (error) {
  431. provider.log(
  432. `Failed to toggle MCP server ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  433. )
  434. }
  435. break
  436. }
  437. case "mcpEnabled":
  438. const mcpEnabled = message.bool ?? true
  439. await updateGlobalState("mcpEnabled", mcpEnabled)
  440. await provider.postStateToWebview()
  441. break
  442. case "enableMcpServerCreation":
  443. await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
  444. await provider.postStateToWebview()
  445. break
  446. case "playSound":
  447. if (message.audioType) {
  448. const soundPath = path.join(provider.context.extensionPath, "audio", `${message.audioType}.wav`)
  449. playSound(soundPath)
  450. }
  451. break
  452. case "soundEnabled":
  453. const soundEnabled = message.bool ?? true
  454. await updateGlobalState("soundEnabled", soundEnabled)
  455. setSoundEnabled(soundEnabled) // Add this line to update the sound utility
  456. await provider.postStateToWebview()
  457. break
  458. case "soundVolume":
  459. const soundVolume = message.value ?? 0.5
  460. await updateGlobalState("soundVolume", soundVolume)
  461. setSoundVolume(soundVolume)
  462. await provider.postStateToWebview()
  463. break
  464. case "ttsEnabled":
  465. const ttsEnabled = message.bool ?? true
  466. await updateGlobalState("ttsEnabled", ttsEnabled)
  467. setTtsEnabled(ttsEnabled) // Add this line to update the tts utility
  468. await provider.postStateToWebview()
  469. break
  470. case "ttsSpeed":
  471. const ttsSpeed = message.value ?? 1.0
  472. await updateGlobalState("ttsSpeed", ttsSpeed)
  473. setTtsSpeed(ttsSpeed)
  474. await provider.postStateToWebview()
  475. break
  476. case "playTts":
  477. if (message.text) {
  478. playTts(message.text, {
  479. onStart: () => provider.postMessageToWebview({ type: "ttsStart", text: message.text }),
  480. onStop: () => provider.postMessageToWebview({ type: "ttsStop", text: message.text }),
  481. })
  482. }
  483. break
  484. case "stopTts":
  485. stopTts()
  486. break
  487. case "diffEnabled":
  488. const diffEnabled = message.bool ?? true
  489. await updateGlobalState("diffEnabled", diffEnabled)
  490. await provider.postStateToWebview()
  491. break
  492. case "enableCheckpoints":
  493. const enableCheckpoints = message.bool ?? true
  494. await updateGlobalState("enableCheckpoints", enableCheckpoints)
  495. await provider.postStateToWebview()
  496. break
  497. case "browserViewportSize":
  498. const browserViewportSize = message.text ?? "900x600"
  499. await updateGlobalState("browserViewportSize", browserViewportSize)
  500. await provider.postStateToWebview()
  501. break
  502. case "remoteBrowserHost":
  503. await updateGlobalState("remoteBrowserHost", message.text)
  504. await provider.postStateToWebview()
  505. break
  506. case "remoteBrowserEnabled":
  507. // Store the preference in global state
  508. // remoteBrowserEnabled now means "enable remote browser connection"
  509. await updateGlobalState("remoteBrowserEnabled", message.bool ?? false)
  510. // If disabling remote browser connection, clear the remoteBrowserHost
  511. if (!message.bool) {
  512. await updateGlobalState("remoteBrowserHost", undefined)
  513. }
  514. await provider.postStateToWebview()
  515. break
  516. case "testBrowserConnection":
  517. // If no text is provided, try auto-discovery
  518. if (!message.text) {
  519. // Use testBrowserConnection for auto-discovery
  520. const chromeHostUrl = await discoverChromeHostUrl()
  521. if (chromeHostUrl) {
  522. // Send the result back to the webview
  523. await provider.postMessageToWebview({
  524. type: "browserConnectionResult",
  525. success: !!chromeHostUrl,
  526. text: `Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`,
  527. values: { endpoint: chromeHostUrl },
  528. })
  529. } else {
  530. await provider.postMessageToWebview({
  531. type: "browserConnectionResult",
  532. success: false,
  533. text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
  534. })
  535. }
  536. } else {
  537. // Test the provided URL
  538. const customHostUrl = message.text
  539. const hostIsValid = await tryChromeHostUrl(message.text)
  540. // Send the result back to the webview
  541. await provider.postMessageToWebview({
  542. type: "browserConnectionResult",
  543. success: hostIsValid,
  544. text: hostIsValid
  545. ? `Successfully connected to Chrome: ${customHostUrl}`
  546. : "Failed to connect to Chrome",
  547. })
  548. }
  549. break
  550. case "fuzzyMatchThreshold":
  551. await updateGlobalState("fuzzyMatchThreshold", message.value)
  552. await provider.postStateToWebview()
  553. break
  554. case "updateVSCodeSetting": {
  555. const { setting, value } = message
  556. if (setting !== undefined && value !== undefined) {
  557. if (ALLOWED_VSCODE_SETTINGS.has(setting)) {
  558. await vscode.workspace.getConfiguration().update(setting, value, true)
  559. } else {
  560. vscode.window.showErrorMessage(`Cannot update restricted VSCode setting: ${setting}`)
  561. }
  562. }
  563. break
  564. }
  565. case "getVSCodeSetting":
  566. const { setting } = message
  567. if (setting) {
  568. try {
  569. await provider.postMessageToWebview({
  570. type: "vsCodeSetting",
  571. setting,
  572. value: vscode.workspace.getConfiguration().get(setting),
  573. })
  574. } catch (error) {
  575. console.error(`Failed to get VSCode setting ${message.setting}:`, error)
  576. await provider.postMessageToWebview({
  577. type: "vsCodeSetting",
  578. setting,
  579. error: `Failed to get setting: ${error.message}`,
  580. value: undefined,
  581. })
  582. }
  583. }
  584. break
  585. case "alwaysApproveResubmit":
  586. await updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
  587. await provider.postStateToWebview()
  588. break
  589. case "requestDelaySeconds":
  590. await updateGlobalState("requestDelaySeconds", message.value ?? 5)
  591. await provider.postStateToWebview()
  592. break
  593. case "writeDelayMs":
  594. await updateGlobalState("writeDelayMs", message.value)
  595. await provider.postStateToWebview()
  596. break
  597. case "terminalOutputLineLimit":
  598. await updateGlobalState("terminalOutputLineLimit", message.value)
  599. await provider.postStateToWebview()
  600. break
  601. case "terminalShellIntegrationTimeout":
  602. await updateGlobalState("terminalShellIntegrationTimeout", message.value)
  603. await provider.postStateToWebview()
  604. if (message.value !== undefined) {
  605. Terminal.setShellIntegrationTimeout(message.value)
  606. }
  607. break
  608. case "terminalShellIntegrationDisabled":
  609. await updateGlobalState("terminalShellIntegrationDisabled", message.bool)
  610. await provider.postStateToWebview()
  611. if (message.bool !== undefined) {
  612. Terminal.setShellIntegrationDisabled(message.bool)
  613. }
  614. break
  615. case "terminalCommandDelay":
  616. await updateGlobalState("terminalCommandDelay", message.value)
  617. await provider.postStateToWebview()
  618. if (message.value !== undefined) {
  619. Terminal.setCommandDelay(message.value)
  620. }
  621. break
  622. case "terminalPowershellCounter":
  623. await updateGlobalState("terminalPowershellCounter", message.bool)
  624. await provider.postStateToWebview()
  625. if (message.bool !== undefined) {
  626. Terminal.setPowershellCounter(message.bool)
  627. }
  628. break
  629. case "terminalZshClearEolMark":
  630. await updateGlobalState("terminalZshClearEolMark", message.bool)
  631. await provider.postStateToWebview()
  632. if (message.bool !== undefined) {
  633. Terminal.setTerminalZshClearEolMark(message.bool)
  634. }
  635. break
  636. case "terminalZshOhMy":
  637. await updateGlobalState("terminalZshOhMy", message.bool)
  638. await provider.postStateToWebview()
  639. if (message.bool !== undefined) {
  640. Terminal.setTerminalZshOhMy(message.bool)
  641. }
  642. break
  643. case "terminalZshP10k":
  644. await updateGlobalState("terminalZshP10k", message.bool)
  645. await provider.postStateToWebview()
  646. if (message.bool !== undefined) {
  647. Terminal.setTerminalZshP10k(message.bool)
  648. }
  649. break
  650. case "terminalZdotdir":
  651. await updateGlobalState("terminalZdotdir", message.bool)
  652. await provider.postStateToWebview()
  653. if (message.bool !== undefined) {
  654. Terminal.setTerminalZdotdir(message.bool)
  655. }
  656. break
  657. case "terminalCompressProgressBar":
  658. await updateGlobalState("terminalCompressProgressBar", message.bool)
  659. await provider.postStateToWebview()
  660. if (message.bool !== undefined) {
  661. Terminal.setCompressProgressBar(message.bool)
  662. }
  663. break
  664. case "mode":
  665. await provider.handleModeSwitch(message.text as Mode)
  666. break
  667. case "updateSupportPrompt":
  668. try {
  669. if (Object.keys(message?.values ?? {}).length === 0) {
  670. return
  671. }
  672. const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
  673. const updatedPrompts = { ...existingPrompts, ...message.values }
  674. await updateGlobalState("customSupportPrompts", updatedPrompts)
  675. await provider.postStateToWebview()
  676. } catch (error) {
  677. provider.log(
  678. `Error update support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  679. )
  680. vscode.window.showErrorMessage(t("common:errors.update_support_prompt"))
  681. }
  682. break
  683. case "resetSupportPrompt":
  684. try {
  685. if (!message?.text) {
  686. return
  687. }
  688. const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
  689. const updatedPrompts = { ...existingPrompts }
  690. updatedPrompts[message.text] = undefined
  691. await updateGlobalState("customSupportPrompts", updatedPrompts)
  692. await provider.postStateToWebview()
  693. } catch (error) {
  694. provider.log(
  695. `Error reset support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  696. )
  697. vscode.window.showErrorMessage(t("common:errors.reset_support_prompt"))
  698. }
  699. break
  700. case "updatePrompt":
  701. if (message.promptMode && message.customPrompt !== undefined) {
  702. const existingPrompts = getGlobalState("customModePrompts") ?? {}
  703. const updatedPrompts = { ...existingPrompts, [message.promptMode]: message.customPrompt }
  704. await updateGlobalState("customModePrompts", updatedPrompts)
  705. const currentState = await provider.getStateToPostToWebview()
  706. const stateWithPrompts = { ...currentState, customModePrompts: updatedPrompts }
  707. provider.postMessageToWebview({ type: "state", state: stateWithPrompts })
  708. }
  709. break
  710. case "deleteMessage": {
  711. const answer = await vscode.window.showInformationMessage(
  712. t("common:confirmation.delete_message"),
  713. { modal: true },
  714. t("common:confirmation.just_this_message"),
  715. t("common:confirmation.this_and_subsequent"),
  716. )
  717. if (
  718. (answer === t("common:confirmation.just_this_message") ||
  719. answer === t("common:confirmation.this_and_subsequent")) &&
  720. provider.getCurrentCline() &&
  721. typeof message.value === "number" &&
  722. message.value
  723. ) {
  724. const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete
  725. const messageIndex = provider
  726. .getCurrentCline()!
  727. .clineMessages.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
  728. const apiConversationHistoryIndex = provider
  729. .getCurrentCline()
  730. ?.apiConversationHistory.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
  731. if (messageIndex !== -1) {
  732. const { historyItem } = await provider.getTaskWithId(provider.getCurrentCline()!.taskId)
  733. if (answer === t("common:confirmation.just_this_message")) {
  734. // Find the next user message first
  735. const nextUserMessage = provider
  736. .getCurrentCline()!
  737. .clineMessages.slice(messageIndex + 1)
  738. .find((msg) => msg.type === "say" && msg.say === "user_feedback")
  739. // Handle UI messages
  740. if (nextUserMessage) {
  741. // Find absolute index of next user message
  742. const nextUserMessageIndex = provider
  743. .getCurrentCline()!
  744. .clineMessages.findIndex((msg) => msg === nextUserMessage)
  745. // Keep messages before current message and after next user message
  746. await provider
  747. .getCurrentCline()!
  748. .overwriteClineMessages([
  749. ...provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
  750. ...provider.getCurrentCline()!.clineMessages.slice(nextUserMessageIndex),
  751. ])
  752. } else {
  753. // If no next user message, keep only messages before current message
  754. await provider
  755. .getCurrentCline()!
  756. .overwriteClineMessages(
  757. provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
  758. )
  759. }
  760. // Handle API messages
  761. if (apiConversationHistoryIndex !== -1) {
  762. if (nextUserMessage && nextUserMessage.ts) {
  763. // Keep messages before current API message and after next user message
  764. await provider
  765. .getCurrentCline()!
  766. .overwriteApiConversationHistory([
  767. ...provider
  768. .getCurrentCline()!
  769. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  770. ...provider
  771. .getCurrentCline()!
  772. .apiConversationHistory.filter(
  773. (msg) => msg.ts && msg.ts >= nextUserMessage.ts,
  774. ),
  775. ])
  776. } else {
  777. // If no next user message, keep only messages before current API message
  778. await provider
  779. .getCurrentCline()!
  780. .overwriteApiConversationHistory(
  781. provider
  782. .getCurrentCline()!
  783. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  784. )
  785. }
  786. }
  787. } else if (answer === t("common:confirmation.this_and_subsequent")) {
  788. // Delete this message and all that follow
  789. await provider
  790. .getCurrentCline()!
  791. .overwriteClineMessages(provider.getCurrentCline()!.clineMessages.slice(0, messageIndex))
  792. if (apiConversationHistoryIndex !== -1) {
  793. await provider
  794. .getCurrentCline()!
  795. .overwriteApiConversationHistory(
  796. provider
  797. .getCurrentCline()!
  798. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  799. )
  800. }
  801. }
  802. await provider.initClineWithHistoryItem(historyItem)
  803. }
  804. }
  805. break
  806. }
  807. case "screenshotQuality":
  808. await updateGlobalState("screenshotQuality", message.value)
  809. await provider.postStateToWebview()
  810. break
  811. case "maxOpenTabsContext":
  812. const tabCount = Math.min(Math.max(0, message.value ?? 20), 500)
  813. await updateGlobalState("maxOpenTabsContext", tabCount)
  814. await provider.postStateToWebview()
  815. break
  816. case "maxWorkspaceFiles":
  817. const fileCount = Math.min(Math.max(0, message.value ?? 200), 500)
  818. await updateGlobalState("maxWorkspaceFiles", fileCount)
  819. await provider.postStateToWebview()
  820. break
  821. case "browserToolEnabled":
  822. await updateGlobalState("browserToolEnabled", message.bool ?? true)
  823. await provider.postStateToWebview()
  824. break
  825. case "language":
  826. changeLanguage(message.text ?? "en")
  827. await updateGlobalState("language", message.text as Language)
  828. await provider.postStateToWebview()
  829. break
  830. case "showRooIgnoredFiles":
  831. await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
  832. await provider.postStateToWebview()
  833. break
  834. case "maxReadFileLine":
  835. await updateGlobalState("maxReadFileLine", message.value)
  836. await provider.postStateToWebview()
  837. break
  838. case "setHistoryPreviewCollapsed": // Add the new case handler
  839. await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
  840. // No need to call postStateToWebview here as the UI already updated optimistically
  841. break
  842. case "toggleApiConfigPin":
  843. if (message.text) {
  844. const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
  845. const updatedPinned: Record<string, boolean> = { ...currentPinned }
  846. if (currentPinned[message.text]) {
  847. delete updatedPinned[message.text]
  848. } else {
  849. updatedPinned[message.text] = true
  850. }
  851. await updateGlobalState("pinnedApiConfigs", updatedPinned)
  852. await provider.postStateToWebview()
  853. }
  854. break
  855. case "enhancementApiConfigId":
  856. await updateGlobalState("enhancementApiConfigId", message.text)
  857. await provider.postStateToWebview()
  858. break
  859. case "autoApprovalEnabled":
  860. await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
  861. await provider.postStateToWebview()
  862. break
  863. case "enhancePrompt":
  864. if (message.text) {
  865. try {
  866. const { apiConfiguration, customSupportPrompts, listApiConfigMeta, enhancementApiConfigId } =
  867. await provider.getState()
  868. // Try to get enhancement config first, fall back to current config
  869. let configToUse: ApiConfiguration = apiConfiguration
  870. if (enhancementApiConfigId) {
  871. const config = listApiConfigMeta?.find((c: ApiConfigMeta) => c.id === enhancementApiConfigId)
  872. if (config?.name) {
  873. const loadedConfig = await provider.providerSettingsManager.loadConfig(config.name)
  874. if (loadedConfig.apiProvider) {
  875. configToUse = loadedConfig
  876. }
  877. }
  878. }
  879. const enhancedPrompt = await singleCompletionHandler(
  880. configToUse,
  881. supportPrompt.create(
  882. "ENHANCE",
  883. {
  884. userInput: message.text,
  885. },
  886. customSupportPrompts,
  887. ),
  888. )
  889. // Capture telemetry for prompt enhancement
  890. const currentCline = provider.getCurrentCline()
  891. telemetryService.capturePromptEnhanced(currentCline?.taskId)
  892. await provider.postMessageToWebview({
  893. type: "enhancedPrompt",
  894. text: enhancedPrompt,
  895. })
  896. } catch (error) {
  897. provider.log(
  898. `Error enhancing prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  899. )
  900. vscode.window.showErrorMessage(t("common:errors.enhance_prompt"))
  901. await provider.postMessageToWebview({
  902. type: "enhancedPrompt",
  903. })
  904. }
  905. }
  906. break
  907. case "getSystemPrompt":
  908. try {
  909. const systemPrompt = await generateSystemPrompt(provider, message)
  910. await provider.postMessageToWebview({
  911. type: "systemPrompt",
  912. text: systemPrompt,
  913. mode: message.mode,
  914. })
  915. } catch (error) {
  916. provider.log(
  917. `Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  918. )
  919. vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
  920. }
  921. break
  922. case "copySystemPrompt":
  923. try {
  924. const systemPrompt = await generateSystemPrompt(provider, message)
  925. await vscode.env.clipboard.writeText(systemPrompt)
  926. await vscode.window.showInformationMessage(t("common:info.clipboard_copy"))
  927. } catch (error) {
  928. provider.log(
  929. `Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  930. )
  931. vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
  932. }
  933. break
  934. case "searchCommits": {
  935. const cwd = provider.cwd
  936. if (cwd) {
  937. try {
  938. const commits = await searchCommits(message.query || "", cwd)
  939. await provider.postMessageToWebview({
  940. type: "commitSearchResults",
  941. commits,
  942. })
  943. } catch (error) {
  944. provider.log(
  945. `Error searching commits: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  946. )
  947. vscode.window.showErrorMessage(t("common:errors.search_commits"))
  948. }
  949. }
  950. break
  951. }
  952. case "searchFiles": {
  953. const workspacePath = getWorkspacePath()
  954. if (!workspacePath) {
  955. // Handle case where workspace path is not available
  956. await provider.postMessageToWebview({
  957. type: "fileSearchResults",
  958. results: [],
  959. requestId: message.requestId,
  960. error: "No workspace path available",
  961. })
  962. break
  963. }
  964. try {
  965. // Call file search service with query from message
  966. const results = await searchWorkspaceFiles(
  967. message.query || "",
  968. workspacePath,
  969. 20, // Use default limit, as filtering is now done in the backend
  970. )
  971. // Send results back to webview
  972. await provider.postMessageToWebview({
  973. type: "fileSearchResults",
  974. results,
  975. requestId: message.requestId,
  976. })
  977. } catch (error) {
  978. const errorMessage = error instanceof Error ? error.message : String(error)
  979. // Send error response to webview
  980. await provider.postMessageToWebview({
  981. type: "fileSearchResults",
  982. results: [],
  983. error: errorMessage,
  984. requestId: message.requestId,
  985. })
  986. }
  987. break
  988. }
  989. case "saveApiConfiguration":
  990. if (message.text && message.apiConfiguration) {
  991. try {
  992. await provider.providerSettingsManager.saveConfig(message.text, message.apiConfiguration)
  993. const listApiConfig = await provider.providerSettingsManager.listConfig()
  994. await updateGlobalState("listApiConfigMeta", listApiConfig)
  995. } catch (error) {
  996. provider.log(
  997. `Error save api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  998. )
  999. vscode.window.showErrorMessage(t("common:errors.save_api_config"))
  1000. }
  1001. }
  1002. break
  1003. case "upsertApiConfiguration":
  1004. if (message.text && message.apiConfiguration) {
  1005. await provider.upsertApiConfiguration(message.text, message.apiConfiguration)
  1006. }
  1007. break
  1008. case "renameApiConfiguration":
  1009. if (message.values && message.apiConfiguration) {
  1010. try {
  1011. const { oldName, newName } = message.values
  1012. if (oldName === newName) {
  1013. break
  1014. }
  1015. // Load the old configuration to get its ID
  1016. const oldConfig = await provider.providerSettingsManager.loadConfig(oldName)
  1017. // Create a new configuration with the same ID
  1018. const newConfig = {
  1019. ...message.apiConfiguration,
  1020. id: oldConfig.id, // Preserve the ID
  1021. }
  1022. // Save with the new name but same ID
  1023. await provider.providerSettingsManager.saveConfig(newName, newConfig)
  1024. await provider.providerSettingsManager.deleteConfig(oldName)
  1025. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1026. // Update listApiConfigMeta first to ensure UI has latest data
  1027. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1028. await updateGlobalState("currentApiConfigName", newName)
  1029. await provider.postStateToWebview()
  1030. } catch (error) {
  1031. provider.log(
  1032. `Error rename api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1033. )
  1034. vscode.window.showErrorMessage(t("common:errors.rename_api_config"))
  1035. }
  1036. }
  1037. break
  1038. case "loadApiConfiguration":
  1039. if (message.text) {
  1040. try {
  1041. const apiConfig = await provider.providerSettingsManager.loadConfig(message.text)
  1042. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1043. await Promise.all([
  1044. updateGlobalState("listApiConfigMeta", listApiConfig),
  1045. updateGlobalState("currentApiConfigName", message.text),
  1046. provider.updateApiConfiguration(apiConfig),
  1047. ])
  1048. await provider.postStateToWebview()
  1049. } catch (error) {
  1050. provider.log(
  1051. `Error load api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1052. )
  1053. vscode.window.showErrorMessage(t("common:errors.load_api_config"))
  1054. }
  1055. }
  1056. break
  1057. case "loadApiConfigurationById":
  1058. if (message.text) {
  1059. try {
  1060. const { config: apiConfig, name } = await provider.providerSettingsManager.loadConfigById(
  1061. message.text,
  1062. )
  1063. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1064. await Promise.all([
  1065. updateGlobalState("listApiConfigMeta", listApiConfig),
  1066. updateGlobalState("currentApiConfigName", name),
  1067. provider.updateApiConfiguration(apiConfig),
  1068. ])
  1069. await provider.postStateToWebview()
  1070. } catch (error) {
  1071. provider.log(
  1072. `Error load api configuration by ID: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1073. )
  1074. vscode.window.showErrorMessage(t("common:errors.load_api_config"))
  1075. }
  1076. }
  1077. break
  1078. case "deleteApiConfiguration":
  1079. if (message.text) {
  1080. const answer = await vscode.window.showInformationMessage(
  1081. t("common:confirmation.delete_config_profile"),
  1082. { modal: true },
  1083. t("common:answers.yes"),
  1084. )
  1085. if (answer !== t("common:answers.yes")) {
  1086. break
  1087. }
  1088. try {
  1089. await provider.providerSettingsManager.deleteConfig(message.text)
  1090. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1091. // Update listApiConfigMeta first to ensure UI has latest data
  1092. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1093. // If this was the current config, switch to first available
  1094. const currentApiConfigName = getGlobalState("currentApiConfigName")
  1095. if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) {
  1096. const apiConfig = await provider.providerSettingsManager.loadConfig(listApiConfig[0].name)
  1097. await Promise.all([
  1098. updateGlobalState("currentApiConfigName", listApiConfig[0].name),
  1099. provider.updateApiConfiguration(apiConfig),
  1100. ])
  1101. }
  1102. await provider.postStateToWebview()
  1103. } catch (error) {
  1104. provider.log(
  1105. `Error delete api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1106. )
  1107. vscode.window.showErrorMessage(t("common:errors.delete_api_config"))
  1108. }
  1109. }
  1110. break
  1111. case "getListApiConfiguration":
  1112. try {
  1113. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1114. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1115. provider.postMessageToWebview({ type: "listApiConfig", listApiConfig })
  1116. } catch (error) {
  1117. provider.log(
  1118. `Error get list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1119. )
  1120. vscode.window.showErrorMessage(t("common:errors.list_api_config"))
  1121. }
  1122. break
  1123. case "updateExperimental": {
  1124. if (!message.values) {
  1125. break
  1126. }
  1127. const updatedExperiments = {
  1128. ...(getGlobalState("experiments") ?? experimentDefault),
  1129. ...message.values,
  1130. }
  1131. await updateGlobalState("experiments", updatedExperiments)
  1132. await provider.postStateToWebview()
  1133. break
  1134. }
  1135. case "updateMcpTimeout":
  1136. if (message.serverName && typeof message.timeout === "number") {
  1137. try {
  1138. await provider
  1139. .getMcpHub()
  1140. ?.updateServerTimeout(
  1141. message.serverName,
  1142. message.timeout,
  1143. message.source as "global" | "project",
  1144. )
  1145. } catch (error) {
  1146. provider.log(
  1147. `Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1148. )
  1149. vscode.window.showErrorMessage(t("common:errors.update_server_timeout"))
  1150. }
  1151. }
  1152. break
  1153. case "updateCustomMode":
  1154. if (message.modeConfig) {
  1155. await provider.customModesManager.updateCustomMode(message.modeConfig.slug, message.modeConfig)
  1156. // Update state after saving the mode
  1157. const customModes = await provider.customModesManager.getCustomModes()
  1158. await updateGlobalState("customModes", customModes)
  1159. await updateGlobalState("mode", message.modeConfig.slug)
  1160. await provider.postStateToWebview()
  1161. }
  1162. break
  1163. case "deleteCustomMode":
  1164. if (message.slug) {
  1165. const answer = await vscode.window.showInformationMessage(
  1166. t("common:confirmation.delete_custom_mode"),
  1167. { modal: true },
  1168. t("common:answers.yes"),
  1169. )
  1170. if (answer !== t("common:answers.yes")) {
  1171. break
  1172. }
  1173. await provider.customModesManager.deleteCustomMode(message.slug)
  1174. // Switch back to default mode after deletion
  1175. await updateGlobalState("mode", defaultModeSlug)
  1176. await provider.postStateToWebview()
  1177. }
  1178. break
  1179. case "humanRelayResponse":
  1180. if (message.requestId && message.text) {
  1181. vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
  1182. requestId: message.requestId,
  1183. text: message.text,
  1184. cancelled: false,
  1185. })
  1186. }
  1187. break
  1188. case "humanRelayCancel":
  1189. if (message.requestId) {
  1190. vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
  1191. requestId: message.requestId,
  1192. cancelled: true,
  1193. })
  1194. }
  1195. break
  1196. case "telemetrySetting": {
  1197. const telemetrySetting = message.text as TelemetrySetting
  1198. await updateGlobalState("telemetrySetting", telemetrySetting)
  1199. const isOptedIn = telemetrySetting === "enabled"
  1200. telemetryService.updateTelemetryState(isOptedIn)
  1201. await provider.postStateToWebview()
  1202. break
  1203. }
  1204. }
  1205. }