webviewMessageHandler.ts 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326
  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 } 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, getModeBySlug, getGroupName } from "../../shared/modes"
  34. import { SYSTEM_PROMPT } from "../prompts/system"
  35. import { buildApiHandler } from "../../api"
  36. import { GlobalState } from "../../schemas"
  37. import { MultiSearchReplaceDiffStrategy } from "../diff/strategies/multi-search-replace"
  38. import { getModels } from "../../api/providers/fetchers/cache"
  39. export const webviewMessageHandler = async (provider: ClineProvider, message: WebviewMessage) => {
  40. // Utility functions provided for concise get/update of global state via contextProxy API.
  41. const getGlobalState = <K extends keyof GlobalState>(key: K) => provider.contextProxy.getValue(key)
  42. const updateGlobalState = async <K extends keyof GlobalState>(key: K, value: GlobalState[K]) =>
  43. await provider.contextProxy.setValue(key, value)
  44. switch (message.type) {
  45. case "webviewDidLaunch":
  46. // Load custom modes first
  47. const customModes = await provider.customModesManager.getCustomModes()
  48. await updateGlobalState("customModes", customModes)
  49. provider.postStateToWebview()
  50. provider.workspaceTracker?.initializeFilePaths() // Don't await.
  51. getTheme().then((theme) => provider.postMessageToWebview({ type: "theme", text: JSON.stringify(theme) }))
  52. // If MCP Hub is already initialized, update the webview with
  53. // current server list.
  54. const mcpHub = provider.getMcpHub()
  55. if (mcpHub) {
  56. provider.postMessageToWebview({ type: "mcpServers", mcpServers: mcpHub.getAllServers() })
  57. }
  58. provider.providerSettingsManager
  59. .listConfig()
  60. .then(async (listApiConfig) => {
  61. if (!listApiConfig) {
  62. return
  63. }
  64. if (listApiConfig.length === 1) {
  65. // Check if first time init then sync with exist config.
  66. if (!checkExistKey(listApiConfig[0])) {
  67. const { apiConfiguration } = await provider.getState()
  68. await provider.providerSettingsManager.saveConfig(
  69. listApiConfig[0].name ?? "default",
  70. apiConfiguration,
  71. )
  72. listApiConfig[0].apiProvider = apiConfiguration.apiProvider
  73. }
  74. }
  75. const currentConfigName = getGlobalState("currentApiConfigName")
  76. if (currentConfigName) {
  77. if (!(await provider.providerSettingsManager.hasConfig(currentConfigName))) {
  78. // current config name not valid, get first config in list
  79. await updateGlobalState("currentApiConfigName", listApiConfig?.[0]?.name)
  80. if (listApiConfig?.[0]?.name) {
  81. const apiConfig = await provider.providerSettingsManager.loadConfig(
  82. listApiConfig?.[0]?.name,
  83. )
  84. await Promise.all([
  85. updateGlobalState("listApiConfigMeta", listApiConfig),
  86. provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
  87. provider.updateApiConfiguration(apiConfig),
  88. ])
  89. await provider.postStateToWebview()
  90. return
  91. }
  92. }
  93. }
  94. await Promise.all([
  95. await updateGlobalState("listApiConfigMeta", listApiConfig),
  96. await provider.postMessageToWebview({ type: "listApiConfig", listApiConfig }),
  97. ])
  98. })
  99. .catch((error) =>
  100. provider.log(
  101. `Error list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  102. ),
  103. )
  104. // If user already opted in to telemetry, enable telemetry service
  105. provider.getStateToPostToWebview().then((state) => {
  106. const { telemetrySetting } = state
  107. const isOptedIn = telemetrySetting === "enabled"
  108. telemetryService.updateTelemetryState(isOptedIn)
  109. })
  110. provider.isViewLaunched = true
  111. break
  112. case "newTask":
  113. // Code that should run in response to the hello message command
  114. //vscode.window.showInformationMessage(message.text!)
  115. // Send a message to our webview.
  116. // You can send any JSON serializable data.
  117. // Could also do this in extension .ts
  118. //provider.postMessageToWebview({ type: "text", text: `Extension: ${Date.now()}` })
  119. // initializing new instance of Cline will make sure that any agentically running promises in old instance don't affect our new task. this essentially creates a fresh slate for the new task
  120. await provider.initClineWithTask(message.text, message.images)
  121. break
  122. case "apiConfiguration":
  123. if (message.apiConfiguration) {
  124. await provider.updateApiConfiguration(message.apiConfiguration)
  125. }
  126. await provider.postStateToWebview()
  127. break
  128. case "customInstructions":
  129. await provider.updateCustomInstructions(message.text)
  130. break
  131. case "alwaysAllowReadOnly":
  132. await updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined)
  133. await provider.postStateToWebview()
  134. break
  135. case "alwaysAllowReadOnlyOutsideWorkspace":
  136. await updateGlobalState("alwaysAllowReadOnlyOutsideWorkspace", message.bool ?? undefined)
  137. await provider.postStateToWebview()
  138. break
  139. case "alwaysAllowWrite":
  140. await updateGlobalState("alwaysAllowWrite", message.bool ?? undefined)
  141. await provider.postStateToWebview()
  142. break
  143. case "alwaysAllowWriteOutsideWorkspace":
  144. await updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined)
  145. await provider.postStateToWebview()
  146. break
  147. case "alwaysAllowExecute":
  148. await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined)
  149. await provider.postStateToWebview()
  150. break
  151. case "alwaysAllowBrowser":
  152. await updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
  153. await provider.postStateToWebview()
  154. break
  155. case "alwaysAllowMcp":
  156. await updateGlobalState("alwaysAllowMcp", message.bool)
  157. await provider.postStateToWebview()
  158. break
  159. case "alwaysAllowModeSwitch":
  160. await updateGlobalState("alwaysAllowModeSwitch", message.bool)
  161. await provider.postStateToWebview()
  162. break
  163. case "alwaysAllowSubtasks":
  164. await updateGlobalState("alwaysAllowSubtasks", message.bool)
  165. await provider.postStateToWebview()
  166. break
  167. case "askResponse":
  168. provider.getCurrentCline()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
  169. break
  170. case "terminalOperation":
  171. if (message.terminalOperation) {
  172. provider.getCurrentCline()?.handleTerminalOperation(message.terminalOperation)
  173. }
  174. break
  175. case "clearTask":
  176. // 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
  177. await provider.finishSubTask(t("common:tasks.canceled"))
  178. await provider.postStateToWebview()
  179. break
  180. case "didShowAnnouncement":
  181. await updateGlobalState("lastShownAnnouncementId", provider.latestAnnouncementId)
  182. await provider.postStateToWebview()
  183. break
  184. case "selectImages":
  185. const images = await selectImages()
  186. await provider.postMessageToWebview({ type: "selectedImages", images })
  187. break
  188. case "exportCurrentTask":
  189. const currentTaskId = provider.getCurrentCline()?.taskId
  190. if (currentTaskId) {
  191. provider.exportTaskWithId(currentTaskId)
  192. }
  193. break
  194. case "showTaskWithId":
  195. provider.showTaskWithId(message.text!)
  196. break
  197. case "deleteTaskWithId":
  198. provider.deleteTaskWithId(message.text!)
  199. break
  200. case "deleteMultipleTasksWithIds": {
  201. const ids = message.ids
  202. if (Array.isArray(ids)) {
  203. // Process in batches of 20 (or another reasonable number)
  204. const batchSize = 20
  205. const results = []
  206. // Only log start and end of the operation
  207. console.log(`Batch deletion started: ${ids.length} tasks total`)
  208. for (let i = 0; i < ids.length; i += batchSize) {
  209. const batch = ids.slice(i, i + batchSize)
  210. const batchPromises = batch.map(async (id) => {
  211. try {
  212. await provider.deleteTaskWithId(id)
  213. return { id, success: true }
  214. } catch (error) {
  215. // Keep error logging for debugging purposes
  216. console.log(
  217. `Failed to delete task ${id}: ${error instanceof Error ? error.message : String(error)}`,
  218. )
  219. return { id, success: false }
  220. }
  221. })
  222. // Process each batch in parallel but wait for completion before starting the next batch
  223. const batchResults = await Promise.all(batchPromises)
  224. results.push(...batchResults)
  225. // Update the UI after each batch to show progress
  226. await provider.postStateToWebview()
  227. }
  228. // Log final results
  229. const successCount = results.filter((r) => r.success).length
  230. const failCount = results.length - successCount
  231. console.log(
  232. `Batch deletion completed: ${successCount}/${ids.length} tasks successful, ${failCount} tasks failed`,
  233. )
  234. }
  235. break
  236. }
  237. case "exportTaskWithId":
  238. provider.exportTaskWithId(message.text!)
  239. break
  240. case "importSettings":
  241. const { success } = await importSettings({
  242. providerSettingsManager: provider.providerSettingsManager,
  243. contextProxy: provider.contextProxy,
  244. customModesManager: provider.customModesManager,
  245. })
  246. if (success) {
  247. provider.settingsImportedAt = Date.now()
  248. await provider.postStateToWebview()
  249. await vscode.window.showInformationMessage(t("common:info.settings_imported"))
  250. }
  251. break
  252. case "exportSettings":
  253. await exportSettings({
  254. providerSettingsManager: provider.providerSettingsManager,
  255. contextProxy: provider.contextProxy,
  256. })
  257. break
  258. case "resetState":
  259. await provider.resetState()
  260. break
  261. case "requestRouterModels":
  262. const [openRouterModels, requestyModels, glamaModels, unboundModels] = await Promise.all([
  263. getModels("openrouter"),
  264. getModels("requesty"),
  265. getModels("glama"),
  266. getModels("unbound"),
  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?.hostHeader,
  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 })
  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 "openMcpSettings": {
  346. const mcpSettingsFilePath = await provider.getMcpHub()?.getMcpSettingsFilePath()
  347. if (mcpSettingsFilePath) {
  348. openFile(mcpSettingsFilePath)
  349. }
  350. break
  351. }
  352. case "openProjectMcpSettings": {
  353. if (!vscode.workspace.workspaceFolders?.length) {
  354. vscode.window.showErrorMessage(t("common:errors.no_workspace"))
  355. return
  356. }
  357. const workspaceFolder = vscode.workspace.workspaceFolders[0]
  358. const rooDir = path.join(workspaceFolder.uri.fsPath, ".roo")
  359. const mcpPath = path.join(rooDir, "mcp.json")
  360. try {
  361. await fs.mkdir(rooDir, { recursive: true })
  362. const exists = await fileExistsAtPath(mcpPath)
  363. if (!exists) {
  364. await fs.writeFile(mcpPath, JSON.stringify({ mcpServers: {} }, null, 2))
  365. }
  366. await openFile(mcpPath)
  367. } catch (error) {
  368. vscode.window.showErrorMessage(t("common:errors.create_mcp_json", { error: `${error}` }))
  369. }
  370. break
  371. }
  372. case "openCustomModesSettings": {
  373. const customModesFilePath = await provider.customModesManager.getCustomModesFilePath()
  374. if (customModesFilePath) {
  375. openFile(customModesFilePath)
  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 "alwaysApproveResubmit":
  555. await updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
  556. await provider.postStateToWebview()
  557. break
  558. case "requestDelaySeconds":
  559. await updateGlobalState("requestDelaySeconds", message.value ?? 5)
  560. await provider.postStateToWebview()
  561. break
  562. case "writeDelayMs":
  563. await updateGlobalState("writeDelayMs", message.value)
  564. await provider.postStateToWebview()
  565. break
  566. case "terminalOutputLineLimit":
  567. await updateGlobalState("terminalOutputLineLimit", message.value)
  568. await provider.postStateToWebview()
  569. break
  570. case "terminalShellIntegrationTimeout":
  571. await updateGlobalState("terminalShellIntegrationTimeout", message.value)
  572. await provider.postStateToWebview()
  573. if (message.value !== undefined) {
  574. Terminal.setShellIntegrationTimeout(message.value)
  575. }
  576. break
  577. case "terminalShellIntegrationDisabled":
  578. await updateGlobalState("terminalShellIntegrationDisabled", message.bool)
  579. await provider.postStateToWebview()
  580. if (message.bool !== undefined) {
  581. Terminal.setShellIntegrationDisabled(message.bool)
  582. }
  583. break
  584. case "terminalCommandDelay":
  585. await updateGlobalState("terminalCommandDelay", message.value)
  586. await provider.postStateToWebview()
  587. if (message.value !== undefined) {
  588. Terminal.setCommandDelay(message.value)
  589. }
  590. break
  591. case "terminalPowershellCounter":
  592. await updateGlobalState("terminalPowershellCounter", message.bool)
  593. await provider.postStateToWebview()
  594. if (message.bool !== undefined) {
  595. Terminal.setPowershellCounter(message.bool)
  596. }
  597. break
  598. case "terminalZshClearEolMark":
  599. await updateGlobalState("terminalZshClearEolMark", message.bool)
  600. await provider.postStateToWebview()
  601. if (message.bool !== undefined) {
  602. Terminal.setTerminalZshClearEolMark(message.bool)
  603. }
  604. break
  605. case "terminalZshOhMy":
  606. await updateGlobalState("terminalZshOhMy", message.bool)
  607. await provider.postStateToWebview()
  608. if (message.bool !== undefined) {
  609. Terminal.setTerminalZshOhMy(message.bool)
  610. }
  611. break
  612. case "terminalZshP10k":
  613. await updateGlobalState("terminalZshP10k", message.bool)
  614. await provider.postStateToWebview()
  615. if (message.bool !== undefined) {
  616. Terminal.setTerminalZshP10k(message.bool)
  617. }
  618. break
  619. case "terminalZdotdir":
  620. await updateGlobalState("terminalZdotdir", message.bool)
  621. await provider.postStateToWebview()
  622. if (message.bool !== undefined) {
  623. Terminal.setTerminalZdotdir(message.bool)
  624. }
  625. break
  626. case "terminalCompressProgressBar":
  627. await updateGlobalState("terminalCompressProgressBar", message.bool)
  628. await provider.postStateToWebview()
  629. if (message.bool !== undefined) {
  630. Terminal.setCompressProgressBar(message.bool)
  631. }
  632. break
  633. case "mode":
  634. await provider.handleModeSwitch(message.text as Mode)
  635. break
  636. case "updateSupportPrompt":
  637. try {
  638. if (Object.keys(message?.values ?? {}).length === 0) {
  639. return
  640. }
  641. const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
  642. const updatedPrompts = { ...existingPrompts, ...message.values }
  643. await updateGlobalState("customSupportPrompts", updatedPrompts)
  644. await provider.postStateToWebview()
  645. } catch (error) {
  646. provider.log(
  647. `Error update support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  648. )
  649. vscode.window.showErrorMessage(t("common:errors.update_support_prompt"))
  650. }
  651. break
  652. case "resetSupportPrompt":
  653. try {
  654. if (!message?.text) {
  655. return
  656. }
  657. const existingPrompts = getGlobalState("customSupportPrompts") ?? {}
  658. const updatedPrompts = { ...existingPrompts }
  659. updatedPrompts[message.text] = undefined
  660. await updateGlobalState("customSupportPrompts", updatedPrompts)
  661. await provider.postStateToWebview()
  662. } catch (error) {
  663. provider.log(
  664. `Error reset support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  665. )
  666. vscode.window.showErrorMessage(t("common:errors.reset_support_prompt"))
  667. }
  668. break
  669. case "updatePrompt":
  670. if (message.promptMode && message.customPrompt !== undefined) {
  671. const existingPrompts = getGlobalState("customModePrompts") ?? {}
  672. const updatedPrompts = { ...existingPrompts, [message.promptMode]: message.customPrompt }
  673. await updateGlobalState("customModePrompts", updatedPrompts)
  674. const currentState = await provider.getStateToPostToWebview()
  675. const stateWithPrompts = { ...currentState, customModePrompts: updatedPrompts }
  676. provider.postMessageToWebview({ type: "state", state: stateWithPrompts })
  677. }
  678. break
  679. case "deleteMessage": {
  680. const answer = await vscode.window.showInformationMessage(
  681. t("common:confirmation.delete_message"),
  682. { modal: true },
  683. t("common:confirmation.just_this_message"),
  684. t("common:confirmation.this_and_subsequent"),
  685. )
  686. if (
  687. (answer === t("common:confirmation.just_this_message") ||
  688. answer === t("common:confirmation.this_and_subsequent")) &&
  689. provider.getCurrentCline() &&
  690. typeof message.value === "number" &&
  691. message.value
  692. ) {
  693. const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete
  694. const messageIndex = provider
  695. .getCurrentCline()!
  696. .clineMessages.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
  697. const apiConversationHistoryIndex = provider
  698. .getCurrentCline()
  699. ?.apiConversationHistory.findIndex((msg) => msg.ts && msg.ts >= timeCutoff)
  700. if (messageIndex !== -1) {
  701. const { historyItem } = await provider.getTaskWithId(provider.getCurrentCline()!.taskId)
  702. if (answer === t("common:confirmation.just_this_message")) {
  703. // Find the next user message first
  704. const nextUserMessage = provider
  705. .getCurrentCline()!
  706. .clineMessages.slice(messageIndex + 1)
  707. .find((msg) => msg.type === "say" && msg.say === "user_feedback")
  708. // Handle UI messages
  709. if (nextUserMessage) {
  710. // Find absolute index of next user message
  711. const nextUserMessageIndex = provider
  712. .getCurrentCline()!
  713. .clineMessages.findIndex((msg) => msg === nextUserMessage)
  714. // Keep messages before current message and after next user message
  715. await provider
  716. .getCurrentCline()!
  717. .overwriteClineMessages([
  718. ...provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
  719. ...provider.getCurrentCline()!.clineMessages.slice(nextUserMessageIndex),
  720. ])
  721. } else {
  722. // If no next user message, keep only messages before current message
  723. await provider
  724. .getCurrentCline()!
  725. .overwriteClineMessages(
  726. provider.getCurrentCline()!.clineMessages.slice(0, messageIndex),
  727. )
  728. }
  729. // Handle API messages
  730. if (apiConversationHistoryIndex !== -1) {
  731. if (nextUserMessage && nextUserMessage.ts) {
  732. // Keep messages before current API message and after next user message
  733. await provider
  734. .getCurrentCline()!
  735. .overwriteApiConversationHistory([
  736. ...provider
  737. .getCurrentCline()!
  738. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  739. ...provider
  740. .getCurrentCline()!
  741. .apiConversationHistory.filter(
  742. (msg) => msg.ts && msg.ts >= nextUserMessage.ts,
  743. ),
  744. ])
  745. } else {
  746. // If no next user message, keep only messages before current API message
  747. await provider
  748. .getCurrentCline()!
  749. .overwriteApiConversationHistory(
  750. provider
  751. .getCurrentCline()!
  752. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  753. )
  754. }
  755. }
  756. } else if (answer === t("common:confirmation.this_and_subsequent")) {
  757. // Delete this message and all that follow
  758. await provider
  759. .getCurrentCline()!
  760. .overwriteClineMessages(provider.getCurrentCline()!.clineMessages.slice(0, messageIndex))
  761. if (apiConversationHistoryIndex !== -1) {
  762. await provider
  763. .getCurrentCline()!
  764. .overwriteApiConversationHistory(
  765. provider
  766. .getCurrentCline()!
  767. .apiConversationHistory.slice(0, apiConversationHistoryIndex),
  768. )
  769. }
  770. }
  771. await provider.initClineWithHistoryItem(historyItem)
  772. }
  773. }
  774. break
  775. }
  776. case "screenshotQuality":
  777. await updateGlobalState("screenshotQuality", message.value)
  778. await provider.postStateToWebview()
  779. break
  780. case "maxOpenTabsContext":
  781. const tabCount = Math.min(Math.max(0, message.value ?? 20), 500)
  782. await updateGlobalState("maxOpenTabsContext", tabCount)
  783. await provider.postStateToWebview()
  784. break
  785. case "maxWorkspaceFiles":
  786. const fileCount = Math.min(Math.max(0, message.value ?? 200), 500)
  787. await updateGlobalState("maxWorkspaceFiles", fileCount)
  788. await provider.postStateToWebview()
  789. break
  790. case "browserToolEnabled":
  791. await updateGlobalState("browserToolEnabled", message.bool ?? true)
  792. await provider.postStateToWebview()
  793. break
  794. case "language":
  795. changeLanguage(message.text ?? "en")
  796. await updateGlobalState("language", message.text as Language)
  797. await provider.postStateToWebview()
  798. break
  799. case "showRooIgnoredFiles":
  800. await updateGlobalState("showRooIgnoredFiles", message.bool ?? true)
  801. await provider.postStateToWebview()
  802. break
  803. case "maxReadFileLine":
  804. await updateGlobalState("maxReadFileLine", message.value)
  805. await provider.postStateToWebview()
  806. break
  807. case "setHistoryPreviewCollapsed": // Add the new case handler
  808. await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
  809. // No need to call postStateToWebview here as the UI already updated optimistically
  810. break
  811. case "toggleApiConfigPin":
  812. if (message.text) {
  813. const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
  814. const updatedPinned: Record<string, boolean> = { ...currentPinned }
  815. if (currentPinned[message.text]) {
  816. delete updatedPinned[message.text]
  817. } else {
  818. updatedPinned[message.text] = true
  819. }
  820. await updateGlobalState("pinnedApiConfigs", updatedPinned)
  821. await provider.postStateToWebview()
  822. }
  823. break
  824. case "enhancementApiConfigId":
  825. await updateGlobalState("enhancementApiConfigId", message.text)
  826. await provider.postStateToWebview()
  827. break
  828. case "autoApprovalEnabled":
  829. await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
  830. await provider.postStateToWebview()
  831. break
  832. case "enhancePrompt":
  833. if (message.text) {
  834. try {
  835. const { apiConfiguration, customSupportPrompts, listApiConfigMeta, enhancementApiConfigId } =
  836. await provider.getState()
  837. // Try to get enhancement config first, fall back to current config
  838. let configToUse: ApiConfiguration = apiConfiguration
  839. if (enhancementApiConfigId) {
  840. const config = listApiConfigMeta?.find((c: ApiConfigMeta) => c.id === enhancementApiConfigId)
  841. if (config?.name) {
  842. const loadedConfig = await provider.providerSettingsManager.loadConfig(config.name)
  843. if (loadedConfig.apiProvider) {
  844. configToUse = loadedConfig
  845. }
  846. }
  847. }
  848. const enhancedPrompt = await singleCompletionHandler(
  849. configToUse,
  850. supportPrompt.create(
  851. "ENHANCE",
  852. {
  853. userInput: message.text,
  854. },
  855. customSupportPrompts,
  856. ),
  857. )
  858. // Capture telemetry for prompt enhancement
  859. const currentCline = provider.getCurrentCline()
  860. telemetryService.capturePromptEnhanced(currentCline?.taskId)
  861. await provider.postMessageToWebview({
  862. type: "enhancedPrompt",
  863. text: enhancedPrompt,
  864. })
  865. } catch (error) {
  866. provider.log(
  867. `Error enhancing prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  868. )
  869. vscode.window.showErrorMessage(t("common:errors.enhance_prompt"))
  870. await provider.postMessageToWebview({
  871. type: "enhancedPrompt",
  872. })
  873. }
  874. }
  875. break
  876. case "getSystemPrompt":
  877. try {
  878. const systemPrompt = await generateSystemPrompt(provider, message)
  879. await provider.postMessageToWebview({
  880. type: "systemPrompt",
  881. text: systemPrompt,
  882. mode: message.mode,
  883. })
  884. } catch (error) {
  885. provider.log(
  886. `Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  887. )
  888. vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
  889. }
  890. break
  891. case "copySystemPrompt":
  892. try {
  893. const systemPrompt = await generateSystemPrompt(provider, message)
  894. await vscode.env.clipboard.writeText(systemPrompt)
  895. await vscode.window.showInformationMessage(t("common:info.clipboard_copy"))
  896. } catch (error) {
  897. provider.log(
  898. `Error getting system prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  899. )
  900. vscode.window.showErrorMessage(t("common:errors.get_system_prompt"))
  901. }
  902. break
  903. case "searchCommits": {
  904. const cwd = provider.cwd
  905. if (cwd) {
  906. try {
  907. const commits = await searchCommits(message.query || "", cwd)
  908. await provider.postMessageToWebview({
  909. type: "commitSearchResults",
  910. commits,
  911. })
  912. } catch (error) {
  913. provider.log(
  914. `Error searching commits: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  915. )
  916. vscode.window.showErrorMessage(t("common:errors.search_commits"))
  917. }
  918. }
  919. break
  920. }
  921. case "searchFiles": {
  922. const workspacePath = getWorkspacePath()
  923. if (!workspacePath) {
  924. // Handle case where workspace path is not available
  925. await provider.postMessageToWebview({
  926. type: "fileSearchResults",
  927. results: [],
  928. requestId: message.requestId,
  929. error: "No workspace path available",
  930. })
  931. break
  932. }
  933. try {
  934. // Call file search service with query from message
  935. const results = await searchWorkspaceFiles(
  936. message.query || "",
  937. workspacePath,
  938. 20, // Use default limit, as filtering is now done in the backend
  939. )
  940. // Send results back to webview
  941. await provider.postMessageToWebview({
  942. type: "fileSearchResults",
  943. results,
  944. requestId: message.requestId,
  945. })
  946. } catch (error) {
  947. const errorMessage = error instanceof Error ? error.message : String(error)
  948. // Send error response to webview
  949. await provider.postMessageToWebview({
  950. type: "fileSearchResults",
  951. results: [],
  952. error: errorMessage,
  953. requestId: message.requestId,
  954. })
  955. }
  956. break
  957. }
  958. case "saveApiConfiguration":
  959. if (message.text && message.apiConfiguration) {
  960. try {
  961. await provider.providerSettingsManager.saveConfig(message.text, message.apiConfiguration)
  962. const listApiConfig = await provider.providerSettingsManager.listConfig()
  963. await updateGlobalState("listApiConfigMeta", listApiConfig)
  964. } catch (error) {
  965. provider.log(
  966. `Error save api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  967. )
  968. vscode.window.showErrorMessage(t("common:errors.save_api_config"))
  969. }
  970. }
  971. break
  972. case "upsertApiConfiguration":
  973. if (message.text && message.apiConfiguration) {
  974. await provider.upsertApiConfiguration(message.text, message.apiConfiguration)
  975. }
  976. break
  977. case "renameApiConfiguration":
  978. if (message.values && message.apiConfiguration) {
  979. try {
  980. const { oldName, newName } = message.values
  981. if (oldName === newName) {
  982. break
  983. }
  984. // Load the old configuration to get its ID
  985. const oldConfig = await provider.providerSettingsManager.loadConfig(oldName)
  986. // Create a new configuration with the same ID
  987. const newConfig = {
  988. ...message.apiConfiguration,
  989. id: oldConfig.id, // Preserve the ID
  990. }
  991. // Save with the new name but same ID
  992. await provider.providerSettingsManager.saveConfig(newName, newConfig)
  993. await provider.providerSettingsManager.deleteConfig(oldName)
  994. const listApiConfig = await provider.providerSettingsManager.listConfig()
  995. // Update listApiConfigMeta first to ensure UI has latest data
  996. await updateGlobalState("listApiConfigMeta", listApiConfig)
  997. await updateGlobalState("currentApiConfigName", newName)
  998. await provider.postStateToWebview()
  999. } catch (error) {
  1000. provider.log(
  1001. `Error rename api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1002. )
  1003. vscode.window.showErrorMessage(t("common:errors.rename_api_config"))
  1004. }
  1005. }
  1006. break
  1007. case "loadApiConfiguration":
  1008. if (message.text) {
  1009. try {
  1010. const apiConfig = await provider.providerSettingsManager.loadConfig(message.text)
  1011. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1012. await Promise.all([
  1013. updateGlobalState("listApiConfigMeta", listApiConfig),
  1014. updateGlobalState("currentApiConfigName", message.text),
  1015. provider.updateApiConfiguration(apiConfig),
  1016. ])
  1017. await provider.postStateToWebview()
  1018. } catch (error) {
  1019. provider.log(
  1020. `Error load api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1021. )
  1022. vscode.window.showErrorMessage(t("common:errors.load_api_config"))
  1023. }
  1024. }
  1025. break
  1026. case "loadApiConfigurationById":
  1027. if (message.text) {
  1028. try {
  1029. const { config: apiConfig, name } = await provider.providerSettingsManager.loadConfigById(
  1030. message.text,
  1031. )
  1032. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1033. await Promise.all([
  1034. updateGlobalState("listApiConfigMeta", listApiConfig),
  1035. updateGlobalState("currentApiConfigName", name),
  1036. provider.updateApiConfiguration(apiConfig),
  1037. ])
  1038. await provider.postStateToWebview()
  1039. } catch (error) {
  1040. provider.log(
  1041. `Error load api configuration by ID: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1042. )
  1043. vscode.window.showErrorMessage(t("common:errors.load_api_config"))
  1044. }
  1045. }
  1046. break
  1047. case "deleteApiConfiguration":
  1048. if (message.text) {
  1049. const answer = await vscode.window.showInformationMessage(
  1050. t("common:confirmation.delete_config_profile"),
  1051. { modal: true },
  1052. t("common:answers.yes"),
  1053. )
  1054. if (answer !== t("common:answers.yes")) {
  1055. break
  1056. }
  1057. try {
  1058. await provider.providerSettingsManager.deleteConfig(message.text)
  1059. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1060. // Update listApiConfigMeta first to ensure UI has latest data
  1061. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1062. // If this was the current config, switch to first available
  1063. const currentApiConfigName = getGlobalState("currentApiConfigName")
  1064. if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) {
  1065. const apiConfig = await provider.providerSettingsManager.loadConfig(listApiConfig[0].name)
  1066. await Promise.all([
  1067. updateGlobalState("currentApiConfigName", listApiConfig[0].name),
  1068. provider.updateApiConfiguration(apiConfig),
  1069. ])
  1070. }
  1071. await provider.postStateToWebview()
  1072. } catch (error) {
  1073. provider.log(
  1074. `Error delete api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1075. )
  1076. vscode.window.showErrorMessage(t("common:errors.delete_api_config"))
  1077. }
  1078. }
  1079. break
  1080. case "getListApiConfiguration":
  1081. try {
  1082. const listApiConfig = await provider.providerSettingsManager.listConfig()
  1083. await updateGlobalState("listApiConfigMeta", listApiConfig)
  1084. provider.postMessageToWebview({ type: "listApiConfig", listApiConfig })
  1085. } catch (error) {
  1086. provider.log(
  1087. `Error get list api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1088. )
  1089. vscode.window.showErrorMessage(t("common:errors.list_api_config"))
  1090. }
  1091. break
  1092. case "updateExperimental": {
  1093. if (!message.values) {
  1094. break
  1095. }
  1096. const updatedExperiments = {
  1097. ...(getGlobalState("experiments") ?? experimentDefault),
  1098. ...message.values,
  1099. }
  1100. await updateGlobalState("experiments", updatedExperiments)
  1101. await provider.postStateToWebview()
  1102. break
  1103. }
  1104. case "updateMcpTimeout":
  1105. if (message.serverName && typeof message.timeout === "number") {
  1106. try {
  1107. await provider
  1108. .getMcpHub()
  1109. ?.updateServerTimeout(
  1110. message.serverName,
  1111. message.timeout,
  1112. message.source as "global" | "project",
  1113. )
  1114. } catch (error) {
  1115. provider.log(
  1116. `Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
  1117. )
  1118. vscode.window.showErrorMessage(t("common:errors.update_server_timeout"))
  1119. }
  1120. }
  1121. break
  1122. case "updateCustomMode":
  1123. if (message.modeConfig) {
  1124. await provider.customModesManager.updateCustomMode(message.modeConfig.slug, message.modeConfig)
  1125. // Update state after saving the mode
  1126. const customModes = await provider.customModesManager.getCustomModes()
  1127. await updateGlobalState("customModes", customModes)
  1128. await updateGlobalState("mode", message.modeConfig.slug)
  1129. await provider.postStateToWebview()
  1130. }
  1131. break
  1132. case "deleteCustomMode":
  1133. if (message.slug) {
  1134. const answer = await vscode.window.showInformationMessage(
  1135. t("common:confirmation.delete_custom_mode"),
  1136. { modal: true },
  1137. t("common:answers.yes"),
  1138. )
  1139. if (answer !== t("common:answers.yes")) {
  1140. break
  1141. }
  1142. await provider.customModesManager.deleteCustomMode(message.slug)
  1143. // Switch back to default mode after deletion
  1144. await updateGlobalState("mode", defaultModeSlug)
  1145. await provider.postStateToWebview()
  1146. }
  1147. break
  1148. case "humanRelayResponse":
  1149. if (message.requestId && message.text) {
  1150. vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
  1151. requestId: message.requestId,
  1152. text: message.text,
  1153. cancelled: false,
  1154. })
  1155. }
  1156. break
  1157. case "humanRelayCancel":
  1158. if (message.requestId) {
  1159. vscode.commands.executeCommand("roo-cline.handleHumanRelayResponse", {
  1160. requestId: message.requestId,
  1161. cancelled: true,
  1162. })
  1163. }
  1164. break
  1165. case "telemetrySetting": {
  1166. const telemetrySetting = message.text as TelemetrySetting
  1167. await updateGlobalState("telemetrySetting", telemetrySetting)
  1168. const isOptedIn = telemetrySetting === "enabled"
  1169. telemetryService.updateTelemetryState(isOptedIn)
  1170. await provider.postStateToWebview()
  1171. break
  1172. }
  1173. }
  1174. }
  1175. const generateSystemPrompt = async (provider: ClineProvider, message: WebviewMessage) => {
  1176. const {
  1177. apiConfiguration,
  1178. customModePrompts,
  1179. customInstructions,
  1180. browserViewportSize,
  1181. diffEnabled,
  1182. mcpEnabled,
  1183. fuzzyMatchThreshold,
  1184. experiments,
  1185. enableMcpServerCreation,
  1186. browserToolEnabled,
  1187. language,
  1188. } = await provider.getState()
  1189. const diffStrategy = new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold)
  1190. const cwd = provider.cwd
  1191. const mode = message.mode ?? defaultModeSlug
  1192. const customModes = await provider.customModesManager.getCustomModes()
  1193. const rooIgnoreInstructions = provider.getCurrentCline()?.rooIgnoreController?.getInstructions()
  1194. // Determine if browser tools can be used based on model support, mode, and user settings
  1195. let modelSupportsComputerUse = false
  1196. // Create a temporary API handler to check if the model supports computer use
  1197. // This avoids relying on an active Cline instance which might not exist during preview
  1198. try {
  1199. const tempApiHandler = buildApiHandler(apiConfiguration)
  1200. modelSupportsComputerUse = tempApiHandler.getModel().info.supportsComputerUse ?? false
  1201. } catch (error) {
  1202. console.error("Error checking if model supports computer use:", error)
  1203. }
  1204. // Check if the current mode includes the browser tool group
  1205. const modeConfig = getModeBySlug(mode, customModes)
  1206. const modeSupportsBrowser = modeConfig?.groups.some((group) => getGroupName(group) === "browser") ?? false
  1207. // Only enable browser tools if the model supports it, the mode includes browser tools,
  1208. // and browser tools are enabled in settings
  1209. const canUseBrowserTool = modelSupportsComputerUse && modeSupportsBrowser && (browserToolEnabled ?? true)
  1210. const systemPrompt = await SYSTEM_PROMPT(
  1211. provider.context,
  1212. cwd,
  1213. canUseBrowserTool,
  1214. mcpEnabled ? provider.getMcpHub() : undefined,
  1215. diffStrategy,
  1216. browserViewportSize ?? "900x600",
  1217. mode,
  1218. customModePrompts,
  1219. customModes,
  1220. customInstructions,
  1221. diffEnabled,
  1222. experiments,
  1223. enableMcpServerCreation,
  1224. language,
  1225. rooIgnoreInstructions,
  1226. )
  1227. return systemPrompt
  1228. }