mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-10-09 03:17:58 +00:00
Batch settings updates from the webview to the extension host (#9165)
Co-authored-by: Roo Code <roomote@roocode.com>
This commit is contained in:
parent
e70c1754bd
commit
62d8cc0d66
20 changed files with 438 additions and 694 deletions
|
|
@ -704,7 +704,13 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
|
|||
signal: AbortSignal.timeout(10000),
|
||||
})
|
||||
|
||||
return clerkOrganizationMembershipsSchema.parse(await response.json()).response
|
||||
if (response.ok) {
|
||||
return clerkOrganizationMembershipsSchema.parse(await response.json()).response
|
||||
}
|
||||
|
||||
const errorMessage = `Failed to get organization memberships: ${response.status} ${response.statusText}`
|
||||
this.log(`[auth] ${errorMessage}`)
|
||||
throw new Error(errorMessage)
|
||||
}
|
||||
|
||||
private async getOrganizationMetadata(
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ export class ClineProvider
|
|||
private pendingOperations: Map<string, PendingEditOperation> = new Map()
|
||||
private static readonly PENDING_OPERATION_TIMEOUT_MS = 30000 // 30 seconds
|
||||
|
||||
private cloudOrganizationsCache: CloudOrganizationMembership[] | null = null
|
||||
private cloudOrganizationsCacheTimestamp: number | null = null
|
||||
private static readonly CLOUD_ORGANIZATIONS_CACHE_DURATION_MS = 5 * 1000 // 5 seconds
|
||||
|
||||
public isViewLaunched = false
|
||||
public settingsImportedAt?: number
|
||||
public readonly latestAnnouncementId = "nov-2025-v3.30.0-pr-fixer" // v3.30.0 PR Fixer announcement
|
||||
|
|
@ -1919,7 +1923,19 @@ export class ClineProvider
|
|||
|
||||
try {
|
||||
if (!CloudService.instance.isCloudAgent) {
|
||||
cloudOrganizations = await CloudService.instance.getOrganizationMemberships()
|
||||
const now = Date.now()
|
||||
|
||||
if (
|
||||
this.cloudOrganizationsCache !== null &&
|
||||
this.cloudOrganizationsCacheTimestamp !== null &&
|
||||
now - this.cloudOrganizationsCacheTimestamp < ClineProvider.CLOUD_ORGANIZATIONS_CACHE_DURATION_MS
|
||||
) {
|
||||
cloudOrganizations = this.cloudOrganizationsCache!
|
||||
} else {
|
||||
cloudOrganizations = await CloudService.instance.getOrganizationMemberships()
|
||||
this.cloudOrganizationsCache = cloudOrganizations
|
||||
this.cloudOrganizationsCacheTimestamp = now
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore this error.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// npx vitest core/webview/__tests__/ClineProvider.spec.ts
|
||||
// pnpm --filter roo-cline test core/webview/__tests__/ClineProvider.spec.ts
|
||||
|
||||
import Anthropic from "@anthropic-ai/sdk"
|
||||
import * as vscode from "vscode"
|
||||
|
|
@ -786,7 +786,7 @@ describe("ClineProvider", () => {
|
|||
await provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
|
||||
await messageHandler({ type: "writeDelayMs", value: 2000 })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { writeDelayMs: 2000 } })
|
||||
|
||||
expect(updateGlobalStateSpy).toHaveBeenCalledWith("writeDelayMs", 2000)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("writeDelayMs", 2000)
|
||||
|
|
@ -800,24 +800,24 @@ describe("ClineProvider", () => {
|
|||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
|
||||
// Simulate setting sound to enabled
|
||||
await messageHandler({ type: "soundEnabled", bool: true })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { soundEnabled: true } })
|
||||
expect(updateGlobalStateSpy).toHaveBeenCalledWith("soundEnabled", true)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", true)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
||||
// Simulate setting sound to disabled
|
||||
await messageHandler({ type: "soundEnabled", bool: false })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { soundEnabled: false } })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", false)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
||||
// Simulate setting tts to enabled
|
||||
await messageHandler({ type: "ttsEnabled", bool: true })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { ttsEnabled: true } })
|
||||
expect(setTtsEnabled).toHaveBeenCalledWith(true)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("ttsEnabled", true)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
||||
// Simulate setting tts to disabled
|
||||
await messageHandler({ type: "ttsEnabled", bool: false })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { ttsEnabled: false } })
|
||||
expect(setTtsEnabled).toHaveBeenCalledWith(false)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("ttsEnabled", false)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
|
@ -856,7 +856,7 @@ describe("ClineProvider", () => {
|
|||
test("handles autoCondenseContext message", async () => {
|
||||
await provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
await messageHandler({ type: "autoCondenseContext", bool: false })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { autoCondenseContext: false } })
|
||||
expect(updateGlobalStateSpy).toHaveBeenCalledWith("autoCondenseContext", false)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("autoCondenseContext", false)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
|
@ -876,7 +876,7 @@ describe("ClineProvider", () => {
|
|||
await provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
|
||||
await messageHandler({ type: "autoCondenseContextPercent", value: 75 })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { autoCondenseContextPercent: 75 } })
|
||||
|
||||
expect(updateGlobalStateSpy).toHaveBeenCalledWith("autoCondenseContextPercent", 75)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("autoCondenseContextPercent", 75)
|
||||
|
|
@ -984,7 +984,7 @@ describe("ClineProvider", () => {
|
|||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
|
||||
// Test browserToolEnabled
|
||||
await messageHandler({ type: "browserToolEnabled", bool: true })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { browserToolEnabled: true } })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("browserToolEnabled", true)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
||||
|
|
@ -1002,13 +1002,13 @@ describe("ClineProvider", () => {
|
|||
expect((await provider.getState()).showRooIgnoredFiles).toBe(false)
|
||||
|
||||
// Test showRooIgnoredFiles with true
|
||||
await messageHandler({ type: "showRooIgnoredFiles", bool: true })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { showRooIgnoredFiles: true } })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", true)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
expect((await provider.getState()).showRooIgnoredFiles).toBe(true)
|
||||
|
||||
// Test showRooIgnoredFiles with false
|
||||
await messageHandler({ type: "showRooIgnoredFiles", bool: false })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { showRooIgnoredFiles: false } })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", false)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
expect((await provider.getState()).showRooIgnoredFiles).toBe(false)
|
||||
|
|
@ -1019,13 +1019,13 @@ describe("ClineProvider", () => {
|
|||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
|
||||
// Test alwaysApproveResubmit
|
||||
await messageHandler({ type: "alwaysApproveResubmit", bool: true })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { alwaysApproveResubmit: true } })
|
||||
expect(updateGlobalStateSpy).toHaveBeenCalledWith("alwaysApproveResubmit", true)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("alwaysApproveResubmit", true)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
||||
// Test requestDelaySeconds
|
||||
await messageHandler({ type: "requestDelaySeconds", value: 10 })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { requestDelaySeconds: 10 } })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("requestDelaySeconds", 10)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
})
|
||||
|
|
@ -1092,7 +1092,7 @@ describe("ClineProvider", () => {
|
|||
await provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
|
||||
|
||||
await messageHandler({ type: "maxWorkspaceFiles", value: 300 })
|
||||
await messageHandler({ type: "updateSettings", updatedSettings: { maxWorkspaceFiles: 300 } })
|
||||
|
||||
expect(updateGlobalStateSpy).toHaveBeenCalledWith("maxWorkspaceFiles", 300)
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith("maxWorkspaceFiles", 300)
|
||||
|
|
|
|||
|
|
@ -721,8 +721,8 @@ describe("webviewMessageHandler - mcpEnabled", () => {
|
|||
|
||||
it("delegates enable=true to McpHub and posts updated state", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "mcpEnabled",
|
||||
bool: true,
|
||||
type: "updateSettings",
|
||||
updatedSettings: { mcpEnabled: true },
|
||||
})
|
||||
|
||||
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
|
||||
|
|
@ -733,8 +733,8 @@ describe("webviewMessageHandler - mcpEnabled", () => {
|
|||
|
||||
it("delegates enable=false to McpHub and posts updated state", async () => {
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "mcpEnabled",
|
||||
bool: false,
|
||||
type: "updateSettings",
|
||||
updatedSettings: { mcpEnabled: false },
|
||||
})
|
||||
|
||||
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
|
||||
|
|
@ -747,8 +747,8 @@ describe("webviewMessageHandler - mcpEnabled", () => {
|
|||
;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(undefined)
|
||||
|
||||
await webviewMessageHandler(mockClineProvider, {
|
||||
type: "mcpEnabled",
|
||||
bool: true,
|
||||
type: "updateSettings",
|
||||
updatedSettings: { mcpEnabled: true },
|
||||
})
|
||||
|
||||
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
|
||||
|
|
|
|||
|
|
@ -10,9 +10,11 @@ import {
|
|||
type GlobalState,
|
||||
type ClineMessage,
|
||||
type TelemetrySetting,
|
||||
type UserSettingsConfig,
|
||||
TelemetryEventName,
|
||||
UserSettingsConfig,
|
||||
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
|
||||
RooCodeSettings,
|
||||
Experiments,
|
||||
ExperimentId,
|
||||
} from "@roo-code/types"
|
||||
import { CloudService } from "@roo-code/cloud"
|
||||
import { TelemetryService } from "@roo-code/telemetry"
|
||||
|
|
@ -507,16 +509,10 @@ export const webviewMessageHandler = async (
|
|||
try {
|
||||
await provider.createTask(message.text, message.images)
|
||||
// Task created successfully - notify the UI to reset
|
||||
await provider.postMessageToWebview({
|
||||
type: "invoke",
|
||||
invoke: "newChat",
|
||||
})
|
||||
await provider.postMessageToWebview({ type: "invoke", invoke: "newChat" })
|
||||
} catch (error) {
|
||||
// For all errors, reset the UI and show error
|
||||
await provider.postMessageToWebview({
|
||||
type: "invoke",
|
||||
invoke: "newChat",
|
||||
})
|
||||
await provider.postMessageToWebview({ type: "invoke", invoke: "newChat" })
|
||||
// Show error to user
|
||||
vscode.window.showErrorMessage(
|
||||
`Failed to create task: ${error instanceof Error ? error.message : String(error)}`,
|
||||
|
|
@ -526,69 +522,111 @@ export const webviewMessageHandler = async (
|
|||
case "customInstructions":
|
||||
await provider.updateCustomInstructions(message.text)
|
||||
break
|
||||
case "alwaysAllowReadOnly":
|
||||
await updateGlobalState("alwaysAllowReadOnly", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowReadOnlyOutsideWorkspace":
|
||||
await updateGlobalState("alwaysAllowReadOnlyOutsideWorkspace", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowWrite":
|
||||
await updateGlobalState("alwaysAllowWrite", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowWriteOutsideWorkspace":
|
||||
await updateGlobalState("alwaysAllowWriteOutsideWorkspace", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowWriteProtected":
|
||||
await updateGlobalState("alwaysAllowWriteProtected", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowExecute":
|
||||
await updateGlobalState("alwaysAllowExecute", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowBrowser":
|
||||
await updateGlobalState("alwaysAllowBrowser", message.bool ?? undefined)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowMcp":
|
||||
await updateGlobalState("alwaysAllowMcp", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowModeSwitch":
|
||||
await updateGlobalState("alwaysAllowModeSwitch", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "allowedMaxRequests":
|
||||
await updateGlobalState("allowedMaxRequests", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "allowedMaxCost":
|
||||
await updateGlobalState("allowedMaxCost", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowSubtasks":
|
||||
await updateGlobalState("alwaysAllowSubtasks", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowUpdateTodoList":
|
||||
await updateGlobalState("alwaysAllowUpdateTodoList", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
|
||||
case "askResponse":
|
||||
provider.getCurrentTask()?.handleWebviewAskResponse(message.askResponse!, message.text, message.images)
|
||||
break
|
||||
case "autoCondenseContext":
|
||||
await updateGlobalState("autoCondenseContext", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "autoCondenseContextPercent":
|
||||
await updateGlobalState("autoCondenseContextPercent", message.value)
|
||||
await provider.postStateToWebview()
|
||||
|
||||
case "updateSettings":
|
||||
if (message.updatedSettings) {
|
||||
for (const [key, value] of Object.entries(message.updatedSettings)) {
|
||||
let newValue = value
|
||||
|
||||
if (key === "language") {
|
||||
newValue = value ?? "en"
|
||||
changeLanguage(newValue as Language)
|
||||
} else if (key === "allowedCommands") {
|
||||
const commands = value ?? []
|
||||
|
||||
newValue = Array.isArray(commands)
|
||||
? commands.filter((cmd) => typeof cmd === "string" && cmd.trim().length > 0)
|
||||
: []
|
||||
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update("allowedCommands", newValue, vscode.ConfigurationTarget.Global)
|
||||
} else if (key === "deniedCommands") {
|
||||
const commands = value ?? []
|
||||
|
||||
newValue = Array.isArray(commands)
|
||||
? commands.filter((cmd) => typeof cmd === "string" && cmd.trim().length > 0)
|
||||
: []
|
||||
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update("deniedCommands", newValue, vscode.ConfigurationTarget.Global)
|
||||
} else if (key === "ttsEnabled") {
|
||||
newValue = value ?? true
|
||||
setTtsEnabled(newValue as boolean)
|
||||
} else if (key === "ttsSpeed") {
|
||||
newValue = value ?? 1.0
|
||||
setTtsSpeed(newValue as number)
|
||||
} else if (key === "terminalShellIntegrationTimeout") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setShellIntegrationTimeout(value as number)
|
||||
}
|
||||
} else if (key === "terminalShellIntegrationDisabled") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setShellIntegrationDisabled(value as boolean)
|
||||
}
|
||||
} else if (key === "terminalCommandDelay") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setCommandDelay(value as number)
|
||||
}
|
||||
} else if (key === "terminalPowershellCounter") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setPowershellCounter(value as boolean)
|
||||
}
|
||||
} else if (key === "terminalZshClearEolMark") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setTerminalZshClearEolMark(value as boolean)
|
||||
}
|
||||
} else if (key === "terminalZshOhMy") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setTerminalZshOhMy(value as boolean)
|
||||
}
|
||||
} else if (key === "terminalZshP10k") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setTerminalZshP10k(value as boolean)
|
||||
}
|
||||
} else if (key === "terminalZdotdir") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setTerminalZdotdir(value as boolean)
|
||||
}
|
||||
} else if (key === "terminalCompressProgressBar") {
|
||||
if (value !== undefined) {
|
||||
Terminal.setCompressProgressBar(value as boolean)
|
||||
}
|
||||
} else if (key === "mcpEnabled") {
|
||||
newValue = value ?? true
|
||||
const mcpHub = provider.getMcpHub()
|
||||
|
||||
if (mcpHub) {
|
||||
await mcpHub.handleMcpEnabledChange(newValue as boolean)
|
||||
}
|
||||
} else if (key === "experiments") {
|
||||
if (!value) {
|
||||
continue
|
||||
}
|
||||
|
||||
newValue = {
|
||||
...(getGlobalState("experiments") ?? experimentDefault),
|
||||
...(value as Record<ExperimentId, boolean>),
|
||||
}
|
||||
} else if (key === "customSupportPrompts") {
|
||||
if (!value) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
await provider.contextProxy.setValue(key as keyof RooCodeSettings, newValue)
|
||||
}
|
||||
|
||||
await provider.postStateToWebview()
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case "terminalOperation":
|
||||
if (message.terminalOperation) {
|
||||
provider.getCurrentTask()?.handleTerminalOperation(message.terminalOperation)
|
||||
|
|
@ -1053,38 +1091,6 @@ export const webviewMessageHandler = async (
|
|||
case "cancelTask":
|
||||
await provider.cancelTask()
|
||||
break
|
||||
case "allowedCommands": {
|
||||
// Validate and sanitize the commands array
|
||||
const commands = message.commands ?? []
|
||||
const validCommands = Array.isArray(commands)
|
||||
? commands.filter((cmd) => typeof cmd === "string" && cmd.trim().length > 0)
|
||||
: []
|
||||
|
||||
await updateGlobalState("allowedCommands", validCommands)
|
||||
|
||||
// Also update workspace settings.
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update("allowedCommands", validCommands, vscode.ConfigurationTarget.Global)
|
||||
|
||||
break
|
||||
}
|
||||
case "deniedCommands": {
|
||||
// Validate and sanitize the commands array
|
||||
const commands = message.commands ?? []
|
||||
const validCommands = Array.isArray(commands)
|
||||
? commands.filter((cmd) => typeof cmd === "string" && cmd.trim().length > 0)
|
||||
: []
|
||||
|
||||
await updateGlobalState("deniedCommands", validCommands)
|
||||
|
||||
// Also update workspace settings.
|
||||
await vscode.workspace
|
||||
.getConfiguration(Package.name)
|
||||
.update("deniedCommands", validCommands, vscode.ConfigurationTarget.Global)
|
||||
|
||||
break
|
||||
}
|
||||
case "openCustomModesSettings": {
|
||||
const customModesFilePath = await provider.customModesManager.getCustomModesFilePath()
|
||||
|
||||
|
|
@ -1219,18 +1225,6 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
break
|
||||
}
|
||||
case "mcpEnabled":
|
||||
const mcpEnabled = message.bool ?? true
|
||||
await updateGlobalState("mcpEnabled", mcpEnabled)
|
||||
|
||||
const mcpHubInstance = provider.getMcpHub()
|
||||
|
||||
if (mcpHubInstance) {
|
||||
await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
|
||||
}
|
||||
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "enableMcpServerCreation":
|
||||
await updateGlobalState("enableMcpServerCreation", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
|
|
@ -1244,21 +1238,24 @@ export const webviewMessageHandler = async (
|
|||
)
|
||||
}
|
||||
break
|
||||
|
||||
case "taskSyncEnabled":
|
||||
const enabled = message.bool ?? false
|
||||
const updatedSettings: Partial<UserSettingsConfig> = {
|
||||
taskSyncEnabled: enabled,
|
||||
}
|
||||
// If disabling task sync, also disable remote control
|
||||
const updatedSettings: Partial<UserSettingsConfig> = { taskSyncEnabled: enabled }
|
||||
|
||||
// If disabling task sync, also disable remote control.
|
||||
if (!enabled) {
|
||||
updatedSettings.extensionBridgeEnabled = false
|
||||
}
|
||||
|
||||
try {
|
||||
await CloudService.instance.updateUserSettings(updatedSettings)
|
||||
} catch (error) {
|
||||
provider.log(`Failed to update cloud settings for task sync: ${error}`)
|
||||
}
|
||||
|
||||
break
|
||||
|
||||
case "refreshAllMcpServers": {
|
||||
const mcpHub = provider.getMcpHub()
|
||||
|
||||
|
|
@ -1268,16 +1265,7 @@ export const webviewMessageHandler = async (
|
|||
|
||||
break
|
||||
}
|
||||
case "soundEnabled":
|
||||
const soundEnabled = message.bool ?? true
|
||||
await updateGlobalState("soundEnabled", soundEnabled)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "soundVolume":
|
||||
const soundVolume = message.value ?? 0.5
|
||||
await updateGlobalState("soundVolume", soundVolume)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
|
||||
case "ttsEnabled":
|
||||
const ttsEnabled = message.bool ?? true
|
||||
await updateGlobalState("ttsEnabled", ttsEnabled)
|
||||
|
|
@ -1302,40 +1290,7 @@ export const webviewMessageHandler = async (
|
|||
case "stopTts":
|
||||
stopTts()
|
||||
break
|
||||
case "diffEnabled":
|
||||
const diffEnabled = message.bool ?? true
|
||||
await updateGlobalState("diffEnabled", diffEnabled)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "enableCheckpoints":
|
||||
const enableCheckpoints = message.bool ?? true
|
||||
await updateGlobalState("enableCheckpoints", enableCheckpoints)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "checkpointTimeout":
|
||||
const checkpointTimeout = message.value ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS
|
||||
await updateGlobalState("checkpointTimeout", checkpointTimeout)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "browserViewportSize":
|
||||
const browserViewportSize = message.text ?? "900x600"
|
||||
await updateGlobalState("browserViewportSize", browserViewportSize)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "remoteBrowserHost":
|
||||
await updateGlobalState("remoteBrowserHost", message.text)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "remoteBrowserEnabled":
|
||||
// Store the preference in global state
|
||||
// remoteBrowserEnabled now means "enable remote browser connection"
|
||||
await updateGlobalState("remoteBrowserEnabled", message.bool ?? false)
|
||||
// If disabling remote browser connection, clear the remoteBrowserHost
|
||||
if (!message.bool) {
|
||||
await updateGlobalState("remoteBrowserHost", undefined)
|
||||
}
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
|
||||
case "testBrowserConnection":
|
||||
// If no text is provided, try auto-discovery
|
||||
if (!message.text) {
|
||||
|
|
@ -1372,10 +1327,7 @@ export const webviewMessageHandler = async (
|
|||
})
|
||||
}
|
||||
break
|
||||
case "fuzzyMatchThreshold":
|
||||
await updateGlobalState("fuzzyMatchThreshold", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
|
||||
case "updateVSCodeSetting": {
|
||||
const { setting, value } = message
|
||||
|
||||
|
|
@ -1412,129 +1364,10 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
|
||||
break
|
||||
case "alwaysApproveResubmit":
|
||||
await updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "requestDelaySeconds":
|
||||
await updateGlobalState("requestDelaySeconds", message.value ?? 5)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "writeDelayMs":
|
||||
await updateGlobalState("writeDelayMs", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "diagnosticsEnabled":
|
||||
await updateGlobalState("diagnosticsEnabled", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "terminalOutputLineLimit":
|
||||
// Validate that the line limit is a positive number
|
||||
const lineLimit = message.value
|
||||
if (typeof lineLimit === "number" && lineLimit > 0) {
|
||||
await updateGlobalState("terminalOutputLineLimit", lineLimit)
|
||||
await provider.postStateToWebview()
|
||||
} else {
|
||||
vscode.window.showErrorMessage(
|
||||
t("common:errors.invalid_line_limit") || "Terminal output line limit must be a positive number",
|
||||
)
|
||||
}
|
||||
break
|
||||
case "terminalOutputCharacterLimit":
|
||||
// Validate that the character limit is a positive number
|
||||
const charLimit = message.value
|
||||
if (typeof charLimit === "number" && charLimit > 0) {
|
||||
await updateGlobalState("terminalOutputCharacterLimit", charLimit)
|
||||
await provider.postStateToWebview()
|
||||
} else {
|
||||
vscode.window.showErrorMessage(
|
||||
t("common:errors.invalid_character_limit") ||
|
||||
"Terminal output character limit must be a positive number",
|
||||
)
|
||||
}
|
||||
break
|
||||
case "terminalShellIntegrationTimeout":
|
||||
await updateGlobalState("terminalShellIntegrationTimeout", message.value)
|
||||
await provider.postStateToWebview()
|
||||
if (message.value !== undefined) {
|
||||
Terminal.setShellIntegrationTimeout(message.value)
|
||||
}
|
||||
break
|
||||
case "terminalShellIntegrationDisabled":
|
||||
await updateGlobalState("terminalShellIntegrationDisabled", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setShellIntegrationDisabled(message.bool)
|
||||
}
|
||||
break
|
||||
case "terminalCommandDelay":
|
||||
await updateGlobalState("terminalCommandDelay", message.value)
|
||||
await provider.postStateToWebview()
|
||||
if (message.value !== undefined) {
|
||||
Terminal.setCommandDelay(message.value)
|
||||
}
|
||||
break
|
||||
case "terminalPowershellCounter":
|
||||
await updateGlobalState("terminalPowershellCounter", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setPowershellCounter(message.bool)
|
||||
}
|
||||
break
|
||||
case "terminalZshClearEolMark":
|
||||
await updateGlobalState("terminalZshClearEolMark", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setTerminalZshClearEolMark(message.bool)
|
||||
}
|
||||
break
|
||||
case "terminalZshOhMy":
|
||||
await updateGlobalState("terminalZshOhMy", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setTerminalZshOhMy(message.bool)
|
||||
}
|
||||
break
|
||||
case "terminalZshP10k":
|
||||
await updateGlobalState("terminalZshP10k", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setTerminalZshP10k(message.bool)
|
||||
}
|
||||
break
|
||||
case "terminalZdotdir":
|
||||
await updateGlobalState("terminalZdotdir", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setTerminalZdotdir(message.bool)
|
||||
}
|
||||
break
|
||||
case "terminalCompressProgressBar":
|
||||
await updateGlobalState("terminalCompressProgressBar", message.bool)
|
||||
await provider.postStateToWebview()
|
||||
if (message.bool !== undefined) {
|
||||
Terminal.setCompressProgressBar(message.bool)
|
||||
}
|
||||
break
|
||||
|
||||
case "mode":
|
||||
await provider.handleModeSwitch(message.text as Mode)
|
||||
break
|
||||
case "updateSupportPrompt":
|
||||
try {
|
||||
if (!message?.values) {
|
||||
return
|
||||
}
|
||||
|
||||
// Replace all prompts with the new values from the cached state
|
||||
await updateGlobalState("customSupportPrompts", message.values)
|
||||
await provider.postStateToWebview()
|
||||
} catch (error) {
|
||||
provider.log(
|
||||
`Error update support prompt: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
|
||||
)
|
||||
vscode.window.showErrorMessage(t("common:errors.update_support_prompt"))
|
||||
}
|
||||
break
|
||||
case "updatePrompt":
|
||||
if (message.promptMode && message.customPrompt !== undefined) {
|
||||
const existingPrompts = getGlobalState("customModePrompts") ?? {}
|
||||
|
|
@ -1594,96 +1427,12 @@ export const webviewMessageHandler = async (
|
|||
}
|
||||
break
|
||||
}
|
||||
case "screenshotQuality":
|
||||
await updateGlobalState("screenshotQuality", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxOpenTabsContext":
|
||||
const tabCount = Math.min(Math.max(0, message.value ?? 20), 500)
|
||||
await updateGlobalState("maxOpenTabsContext", tabCount)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxWorkspaceFiles":
|
||||
const fileCount = Math.min(Math.max(0, message.value ?? 200), 500)
|
||||
await updateGlobalState("maxWorkspaceFiles", fileCount)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "alwaysAllowFollowupQuestions":
|
||||
await updateGlobalState("alwaysAllowFollowupQuestions", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "followupAutoApproveTimeoutMs":
|
||||
await updateGlobalState("followupAutoApproveTimeoutMs", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "browserToolEnabled":
|
||||
await updateGlobalState("browserToolEnabled", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "language":
|
||||
changeLanguage(message.text ?? "en")
|
||||
await updateGlobalState("language", message.text as Language)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "openRouterImageApiKey":
|
||||
await provider.contextProxy.setValue("openRouterImageApiKey", message.text)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "openRouterImageGenerationSelectedModel":
|
||||
await provider.contextProxy.setValue("openRouterImageGenerationSelectedModel", message.text)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "showRooIgnoredFiles":
|
||||
await updateGlobalState("showRooIgnoredFiles", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
|
||||
case "hasOpenedModeSelector":
|
||||
await updateGlobalState("hasOpenedModeSelector", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxReadFileLine":
|
||||
await updateGlobalState("maxReadFileLine", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxImageFileSize":
|
||||
await updateGlobalState("maxImageFileSize", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxTotalImageSize":
|
||||
await updateGlobalState("maxTotalImageSize", message.value)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxConcurrentFileReads":
|
||||
const valueToSave = message.value // Capture the value intended for saving
|
||||
await updateGlobalState("maxConcurrentFileReads", valueToSave)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "includeDiagnosticMessages":
|
||||
// Only apply default if the value is truly undefined (not false)
|
||||
const includeValue = message.bool !== undefined ? message.bool : true
|
||||
await updateGlobalState("includeDiagnosticMessages", includeValue)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "includeCurrentTime":
|
||||
await updateGlobalState("includeCurrentTime", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "includeCurrentCost":
|
||||
await updateGlobalState("includeCurrentCost", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "maxDiagnosticMessages":
|
||||
await updateGlobalState("maxDiagnosticMessages", message.value ?? 50)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "setHistoryPreviewCollapsed": // Add the new case handler
|
||||
await updateGlobalState("historyPreviewCollapsed", message.bool ?? false)
|
||||
// No need to call postStateToWebview here as the UI already updated optimistically
|
||||
break
|
||||
case "setReasoningBlockCollapsed":
|
||||
await updateGlobalState("reasoningBlockCollapsed", message.bool ?? true)
|
||||
// No need to call postStateToWebview here as the UI already updated optimistically
|
||||
break
|
||||
|
||||
case "toggleApiConfigPin":
|
||||
if (message.text) {
|
||||
const currentPinned = getGlobalState("pinnedApiConfigs") ?? {}
|
||||
|
|
@ -1703,27 +1452,17 @@ export const webviewMessageHandler = async (
|
|||
await updateGlobalState("enhancementApiConfigId", message.text)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "includeTaskHistoryInEnhance":
|
||||
await updateGlobalState("includeTaskHistoryInEnhance", message.bool ?? true)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "condensingApiConfigId":
|
||||
await updateGlobalState("condensingApiConfigId", message.text)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
|
||||
case "updateCondensingPrompt":
|
||||
// Store the condensing prompt in customSupportPrompts["CONDENSE"] instead of customCondensingPrompt
|
||||
// Store the condensing prompt in customSupportPrompts["CONDENSE"]
|
||||
// instead of customCondensingPrompt.
|
||||
const currentSupportPrompts = getGlobalState("customSupportPrompts") ?? {}
|
||||
const updatedSupportPrompts = { ...currentSupportPrompts, CONDENSE: message.text }
|
||||
await updateGlobalState("customSupportPrompts", updatedSupportPrompts)
|
||||
// Also update the old field for backward compatibility during migration
|
||||
// Also update the old field for backward compatibility during migration.
|
||||
await updateGlobalState("customCondensingPrompt", message.text)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "profileThresholds":
|
||||
await updateGlobalState("profileThresholds", message.values)
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
case "autoApprovalEnabled":
|
||||
await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
|
||||
await provider.postStateToWebview()
|
||||
|
|
@ -1755,7 +1494,6 @@ export const webviewMessageHandler = async (
|
|||
})
|
||||
|
||||
if (result.success && result.enhancedText) {
|
||||
// Capture telemetry for prompt enhancement
|
||||
MessageEnhancer.captureTelemetry(currentCline?.taskId, includeTaskHistoryInEnhance)
|
||||
await provider.postMessageToWebview({ type: "enhancedPrompt", text: result.enhancedText })
|
||||
} else {
|
||||
|
|
@ -2009,21 +1747,7 @@ export const webviewMessageHandler = async (
|
|||
vscode.window.showErrorMessage(t("common:errors.list_api_config"))
|
||||
}
|
||||
break
|
||||
case "updateExperimental": {
|
||||
if (!message.values) {
|
||||
break
|
||||
}
|
||||
|
||||
const updatedExperiments = {
|
||||
...(getGlobalState("experiments") ?? experimentDefault),
|
||||
...message.values,
|
||||
}
|
||||
|
||||
await updateGlobalState("experiments", updatedExperiments)
|
||||
|
||||
await provider.postStateToWebview()
|
||||
break
|
||||
}
|
||||
case "updateMcpTimeout":
|
||||
if (message.serverName && typeof message.timeout === "number") {
|
||||
try {
|
||||
|
|
@ -2373,8 +2097,10 @@ export const webviewMessageHandler = async (
|
|||
if (wasPreviouslyOptedIn && !isOptedIn && TelemetryService.hasInstance()) {
|
||||
TelemetryService.instance.captureTelemetrySettingsChanged(previousSetting, telemetrySetting)
|
||||
}
|
||||
|
||||
// Update the telemetry state
|
||||
await updateGlobalState("telemetrySetting", telemetrySetting)
|
||||
|
||||
if (TelemetryService.hasInstance()) {
|
||||
TelemetryService.instance.updateTelemetryState(isOptedIn)
|
||||
}
|
||||
|
|
@ -3161,6 +2887,7 @@ export const webviewMessageHandler = async (
|
|||
|
||||
break
|
||||
}
|
||||
|
||||
case "dismissUpsell": {
|
||||
if (message.upsellId) {
|
||||
try {
|
||||
|
|
@ -3195,5 +2922,32 @@ export const webviewMessageHandler = async (
|
|||
})
|
||||
break
|
||||
}
|
||||
default: {
|
||||
// console.log(`Unhandled message type: ${message.type}`)
|
||||
//
|
||||
// Currently unhandled:
|
||||
//
|
||||
// "currentApiConfigName" |
|
||||
// "codebaseIndexEnabled" |
|
||||
// "enhancedPrompt" |
|
||||
// "systemPrompt" |
|
||||
// "exportModeResult" |
|
||||
// "importModeResult" |
|
||||
// "checkRulesDirectoryResult" |
|
||||
// "browserConnectionResult" |
|
||||
// "vsCodeSetting" |
|
||||
// "indexingStatusUpdate" |
|
||||
// "indexCleared" |
|
||||
// "marketplaceInstallResult" |
|
||||
// "shareTaskSuccess" |
|
||||
// "playSound" |
|
||||
// "draggedImages" |
|
||||
// "setApiConfigPassword" |
|
||||
// "setopenAiCustomModelInfo" |
|
||||
// "marketplaceButtonClicked" |
|
||||
// "cancelMarketplaceInstall" |
|
||||
// "imageGenerationSettings"
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,9 +219,7 @@ export type ExtensionState = Pick<
|
|||
| "currentApiConfigName"
|
||||
| "listApiConfigMeta"
|
||||
| "pinnedApiConfigs"
|
||||
// | "lastShownAnnouncementId"
|
||||
| "customInstructions"
|
||||
// | "taskHistory" // Optional in GlobalSettings, required here.
|
||||
| "dismissedUpsells"
|
||||
| "autoApprovalEnabled"
|
||||
| "alwaysAllowReadOnly"
|
||||
|
|
@ -229,10 +227,8 @@ export type ExtensionState = Pick<
|
|||
| "alwaysAllowWrite"
|
||||
| "alwaysAllowWriteOutsideWorkspace"
|
||||
| "alwaysAllowWriteProtected"
|
||||
// | "writeDelayMs" // Optional in GlobalSettings, required here.
|
||||
| "alwaysAllowBrowser"
|
||||
| "alwaysApproveResubmit"
|
||||
// | "requestDelaySeconds" // Optional in GlobalSettings, required here.
|
||||
| "alwaysAllowMcp"
|
||||
| "alwaysAllowModeSwitch"
|
||||
| "alwaysAllowSubtasks"
|
||||
|
|
@ -250,16 +246,11 @@ export type ExtensionState = Pick<
|
|||
| "remoteBrowserEnabled"
|
||||
| "cachedChromeHostUrl"
|
||||
| "remoteBrowserHost"
|
||||
// | "enableCheckpoints" // Optional in GlobalSettings, required here.
|
||||
| "ttsEnabled"
|
||||
| "ttsSpeed"
|
||||
| "soundEnabled"
|
||||
| "soundVolume"
|
||||
// | "maxOpenTabsContext" // Optional in GlobalSettings, required here.
|
||||
// | "maxWorkspaceFiles" // Optional in GlobalSettings, required here.
|
||||
// | "showRooIgnoredFiles" // Optional in GlobalSettings, required here.
|
||||
// | "maxReadFileLine" // Optional in GlobalSettings, required here.
|
||||
| "maxConcurrentFileReads" // Optional in GlobalSettings, required here.
|
||||
| "maxConcurrentFileReads"
|
||||
| "terminalOutputLineLimit"
|
||||
| "terminalOutputCharacterLimit"
|
||||
| "terminalShellIntegrationTimeout"
|
||||
|
|
@ -274,14 +265,8 @@ export type ExtensionState = Pick<
|
|||
| "diagnosticsEnabled"
|
||||
| "diffEnabled"
|
||||
| "fuzzyMatchThreshold"
|
||||
// | "experiments" // Optional in GlobalSettings, required here.
|
||||
| "language"
|
||||
// | "telemetrySetting" // Optional in GlobalSettings, required here.
|
||||
// | "mcpEnabled" // Optional in GlobalSettings, required here.
|
||||
// | "enableMcpServerCreation" // Optional in GlobalSettings, required here.
|
||||
// | "mode" // Optional in GlobalSettings, required here.
|
||||
| "modeApiConfigs"
|
||||
// | "customModes" // Optional in GlobalSettings, required here.
|
||||
| "customModePrompts"
|
||||
| "customSupportPrompts"
|
||||
| "enhancementApiConfigId"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { z } from "zod"
|
||||
|
||||
import {
|
||||
type RooCodeSettings,
|
||||
type ProviderSettings,
|
||||
type PromptComponent,
|
||||
type ModeConfig,
|
||||
|
|
@ -38,17 +39,6 @@ export interface WebviewMessage {
|
|||
| "renameApiConfiguration"
|
||||
| "getListApiConfiguration"
|
||||
| "customInstructions"
|
||||
| "allowedCommands"
|
||||
| "deniedCommands"
|
||||
| "alwaysAllowReadOnly"
|
||||
| "alwaysAllowReadOnlyOutsideWorkspace"
|
||||
| "alwaysAllowWrite"
|
||||
| "alwaysAllowWriteOutsideWorkspace"
|
||||
| "alwaysAllowWriteProtected"
|
||||
| "alwaysAllowExecute"
|
||||
| "alwaysAllowFollowupQuestions"
|
||||
| "alwaysAllowUpdateTodoList"
|
||||
| "followupAutoApproveTimeoutMs"
|
||||
| "webviewDidLaunch"
|
||||
| "newTask"
|
||||
| "askResponse"
|
||||
|
|
@ -80,30 +70,12 @@ export interface WebviewMessage {
|
|||
| "updateVSCodeSetting"
|
||||
| "getVSCodeSetting"
|
||||
| "vsCodeSetting"
|
||||
| "alwaysAllowBrowser"
|
||||
| "alwaysAllowMcp"
|
||||
| "alwaysAllowModeSwitch"
|
||||
| "allowedMaxRequests"
|
||||
| "allowedMaxCost"
|
||||
| "alwaysAllowSubtasks"
|
||||
| "alwaysAllowUpdateTodoList"
|
||||
| "autoCondenseContext"
|
||||
| "autoCondenseContextPercent"
|
||||
| "condensingApiConfigId"
|
||||
| "updateCondensingPrompt"
|
||||
| "playSound"
|
||||
| "playTts"
|
||||
| "stopTts"
|
||||
| "soundEnabled"
|
||||
| "ttsEnabled"
|
||||
| "ttsSpeed"
|
||||
| "soundVolume"
|
||||
| "diffEnabled"
|
||||
| "enableCheckpoints"
|
||||
| "checkpointTimeout"
|
||||
| "browserViewportSize"
|
||||
| "screenshotQuality"
|
||||
| "remoteBrowserHost"
|
||||
| "openKeyboardShortcuts"
|
||||
| "openMcpSettings"
|
||||
| "openProjectMcpSettings"
|
||||
|
|
@ -113,9 +85,6 @@ export interface WebviewMessage {
|
|||
| "toggleToolEnabledForPrompt"
|
||||
| "toggleMcpServer"
|
||||
| "updateMcpTimeout"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "writeDelayMs"
|
||||
| "diagnosticsEnabled"
|
||||
| "enhancePrompt"
|
||||
| "enhancedPrompt"
|
||||
| "draggedImages"
|
||||
|
|
@ -123,34 +92,17 @@ export interface WebviewMessage {
|
|||
| "deleteMessageConfirm"
|
||||
| "submitEditedMessage"
|
||||
| "editMessageConfirm"
|
||||
| "terminalOutputLineLimit"
|
||||
| "terminalOutputCharacterLimit"
|
||||
| "terminalShellIntegrationTimeout"
|
||||
| "terminalShellIntegrationDisabled"
|
||||
| "terminalCommandDelay"
|
||||
| "terminalPowershellCounter"
|
||||
| "terminalZshClearEolMark"
|
||||
| "terminalZshOhMy"
|
||||
| "terminalZshP10k"
|
||||
| "terminalZdotdir"
|
||||
| "terminalCompressProgressBar"
|
||||
| "mcpEnabled"
|
||||
| "enableMcpServerCreation"
|
||||
| "remoteControlEnabled"
|
||||
| "taskSyncEnabled"
|
||||
| "searchCommits"
|
||||
| "alwaysApproveResubmit"
|
||||
| "requestDelaySeconds"
|
||||
| "setApiConfigPassword"
|
||||
| "mode"
|
||||
| "updatePrompt"
|
||||
| "updateSupportPrompt"
|
||||
| "getSystemPrompt"
|
||||
| "copySystemPrompt"
|
||||
| "systemPrompt"
|
||||
| "enhancementApiConfigId"
|
||||
| "includeTaskHistoryInEnhance"
|
||||
| "updateExperimental"
|
||||
| "autoApprovalEnabled"
|
||||
| "updateCustomMode"
|
||||
| "deleteCustomMode"
|
||||
|
|
@ -159,29 +111,14 @@ export interface WebviewMessage {
|
|||
| "checkpointDiff"
|
||||
| "checkpointRestore"
|
||||
| "deleteMcpServer"
|
||||
| "maxOpenTabsContext"
|
||||
| "maxWorkspaceFiles"
|
||||
| "humanRelayResponse"
|
||||
| "humanRelayCancel"
|
||||
| "browserToolEnabled"
|
||||
| "codebaseIndexEnabled"
|
||||
| "telemetrySetting"
|
||||
| "showRooIgnoredFiles"
|
||||
| "testBrowserConnection"
|
||||
| "browserConnectionResult"
|
||||
| "remoteBrowserEnabled"
|
||||
| "language"
|
||||
| "maxReadFileLine"
|
||||
| "maxImageFileSize"
|
||||
| "maxTotalImageSize"
|
||||
| "maxConcurrentFileReads"
|
||||
| "includeDiagnosticMessages"
|
||||
| "maxDiagnosticMessages"
|
||||
| "includeCurrentTime"
|
||||
| "includeCurrentCost"
|
||||
| "searchFiles"
|
||||
| "toggleApiConfigPin"
|
||||
| "setHistoryPreviewCollapsed"
|
||||
| "hasOpenedModeSelector"
|
||||
| "cloudButtonClicked"
|
||||
| "rooCloudSignIn"
|
||||
|
|
@ -196,9 +133,6 @@ export interface WebviewMessage {
|
|||
| "indexingStatusUpdate"
|
||||
| "indexCleared"
|
||||
| "focusPanelRequest"
|
||||
| "profileThresholds"
|
||||
| "setHistoryPreviewCollapsed"
|
||||
| "setReasoningBlockCollapsed"
|
||||
| "openExternal"
|
||||
| "filterMarketplaceItems"
|
||||
| "marketplaceButtonClicked"
|
||||
|
|
@ -209,7 +143,6 @@ export interface WebviewMessage {
|
|||
| "marketplaceInstallResult"
|
||||
| "fetchMarketplaceData"
|
||||
| "switchTab"
|
||||
| "profileThresholds"
|
||||
| "shareTaskSuccess"
|
||||
| "exportMode"
|
||||
| "exportModeResult"
|
||||
|
|
@ -226,13 +159,12 @@ export interface WebviewMessage {
|
|||
| "insertTextIntoTextarea"
|
||||
| "showMdmAuthRequiredNotification"
|
||||
| "imageGenerationSettings"
|
||||
| "openRouterImageApiKey"
|
||||
| "openRouterImageGenerationSelectedModel"
|
||||
| "queueMessage"
|
||||
| "removeQueuedMessage"
|
||||
| "editQueuedMessage"
|
||||
| "dismissUpsell"
|
||||
| "getDismissedUpsells"
|
||||
| "updateSettings"
|
||||
text?: string
|
||||
editedMessageContent?: string
|
||||
tab?: "settings" | "history" | "mcp" | "modes" | "chat" | "marketplace" | "cloud"
|
||||
|
|
@ -309,6 +241,7 @@ export interface WebviewMessage {
|
|||
codebaseIndexVercelAiGatewayApiKey?: string
|
||||
codebaseIndexOpenRouterApiKey?: string
|
||||
}
|
||||
updatedSettings?: RooCodeSettings
|
||||
}
|
||||
|
||||
export const checkoutDiffPayloadSchema = z.object({
|
||||
|
|
|
|||
|
|
@ -2,15 +2,22 @@ import React from "react"
|
|||
import { ListChecks, LayoutList, Settings, CheckCheck, X } from "lucide-react"
|
||||
|
||||
import { vscode } from "@/utils/vscode"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
import { useExtensionState } from "@/context/ExtensionStateContext"
|
||||
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
|
||||
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, ToggleSwitch, Button } from "@/components/ui"
|
||||
import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
|
||||
|
||||
import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles"
|
||||
import { useAutoApprovalState } from "@/hooks/useAutoApprovalState"
|
||||
|
||||
import { useRooPortal } from "@/components/ui/hooks/useRooPortal"
|
||||
|
||||
import { Popover, PopoverContent, PopoverTrigger, StandardTooltip, ToggleSwitch, Button } from "@/components/ui"
|
||||
|
||||
import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
|
||||
|
||||
interface AutoApproveDropdownProps {
|
||||
disabled?: boolean
|
||||
triggerClassName?: string
|
||||
|
|
@ -39,7 +46,7 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
|
|||
|
||||
const baseToggles = useAutoApprovalToggles()
|
||||
|
||||
// Include alwaysApproveResubmit in addition to the base toggles
|
||||
// Include alwaysApproveResubmit in addition to the base toggles.
|
||||
const toggles = React.useMemo(
|
||||
() => ({
|
||||
...baseToggles,
|
||||
|
|
@ -50,9 +57,8 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
|
|||
|
||||
const onAutoApproveToggle = React.useCallback(
|
||||
(key: AutoApproveSetting, value: boolean) => {
|
||||
vscode.postMessage({ type: key, bool: value })
|
||||
vscode.postMessage({ type: "updateSettings", updatedSettings: { [key]: value } })
|
||||
|
||||
// Update the specific toggle state
|
||||
switch (key) {
|
||||
case "alwaysAllowReadOnly":
|
||||
setAlwaysAllowReadOnly(value)
|
||||
|
|
@ -86,7 +92,7 @@ export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }:
|
|||
break
|
||||
}
|
||||
|
||||
// If enabling any option, ensure autoApprovalEnabled is true
|
||||
// If enabling any option, ensure autoApprovalEnabled is true.
|
||||
if (value && !autoApprovalEnabled) {
|
||||
setAutoApprovalEnabled(true)
|
||||
vscode.postMessage({ type: "autoApprovalEnabled", bool: true })
|
||||
|
|
|
|||
|
|
@ -89,8 +89,11 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
|
|||
|
||||
setAllowedCommands(newAllowed)
|
||||
setDeniedCommands(newDenied)
|
||||
vscode.postMessage({ type: "allowedCommands", commands: newAllowed })
|
||||
vscode.postMessage({ type: "deniedCommands", commands: newDenied })
|
||||
|
||||
vscode.postMessage({
|
||||
type: "updateSettings",
|
||||
updatedSettings: { allowedCommands: newAllowed, deniedCommands: newDenied },
|
||||
})
|
||||
}
|
||||
|
||||
const handleDenyPatternChange = (pattern: string) => {
|
||||
|
|
@ -100,8 +103,11 @@ export const CommandExecution = ({ executionId, text, icon, title }: CommandExec
|
|||
|
||||
setAllowedCommands(newAllowed)
|
||||
setDeniedCommands(newDenied)
|
||||
vscode.postMessage({ type: "allowedCommands", commands: newAllowed })
|
||||
vscode.postMessage({ type: "deniedCommands", commands: newDenied })
|
||||
|
||||
vscode.postMessage({
|
||||
type: "updateSettings",
|
||||
updatedSettings: { allowedCommands: newAllowed, deniedCommands: newDenied },
|
||||
})
|
||||
}
|
||||
|
||||
const onMessage = useCallback(
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// pnpm --filter @roo-code/vscode-webview test src/components/chat/__tests__/CommandExecution.spec.tsx
|
||||
|
||||
import React from "react"
|
||||
import { render, screen, fireEvent } from "@testing-library/react"
|
||||
|
||||
|
|
@ -111,8 +113,13 @@ describe("CommandExecution", () => {
|
|||
|
||||
expect(mockExtensionState.setAllowedCommands).toHaveBeenCalledWith(["npm", "git push"])
|
||||
expect(mockExtensionState.setDeniedCommands).toHaveBeenCalledWith(["rm"])
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "allowedCommands", commands: ["npm", "git push"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "deniedCommands", commands: ["rm"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: ["npm", "git push"],
|
||||
deniedCommands: ["rm"],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle deny command change", () => {
|
||||
|
|
@ -127,8 +134,13 @@ describe("CommandExecution", () => {
|
|||
|
||||
expect(mockExtensionState.setAllowedCommands).toHaveBeenCalledWith(["npm"])
|
||||
expect(mockExtensionState.setDeniedCommands).toHaveBeenCalledWith(["rm", "docker run"])
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "allowedCommands", commands: ["npm"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "deniedCommands", commands: ["rm", "docker run"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: ["npm"],
|
||||
deniedCommands: ["rm", "docker run"],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should toggle allowed command", () => {
|
||||
|
|
@ -151,8 +163,13 @@ describe("CommandExecution", () => {
|
|||
// "npm test" is already in allowedCommands, so it should be removed
|
||||
expect(stateWithNpmTest.setAllowedCommands).toHaveBeenCalledWith([])
|
||||
expect(stateWithNpmTest.setDeniedCommands).toHaveBeenCalledWith(["rm"])
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "allowedCommands", commands: [] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "deniedCommands", commands: ["rm"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: [],
|
||||
deniedCommands: ["rm"],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should toggle denied command", () => {
|
||||
|
|
@ -175,8 +192,13 @@ describe("CommandExecution", () => {
|
|||
// "rm -rf" is already in deniedCommands, so it should be removed
|
||||
expect(stateWithRmRf.setAllowedCommands).toHaveBeenCalledWith(["npm"])
|
||||
expect(stateWithRmRf.setDeniedCommands).toHaveBeenCalledWith([])
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "allowedCommands", commands: ["npm"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "deniedCommands", commands: [] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: ["npm"],
|
||||
deniedCommands: [],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("should parse command with Output: separator", () => {
|
||||
|
|
@ -311,8 +333,13 @@ Output here`
|
|||
// "rm file.txt" should be removed from denied and added to allowed
|
||||
expect(stateWithRmInDenied.setAllowedCommands).toHaveBeenCalledWith(["npm", "rm file.txt"])
|
||||
expect(stateWithRmInDenied.setDeniedCommands).toHaveBeenCalledWith([])
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "allowedCommands", commands: ["npm", "rm file.txt"] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({ type: "deniedCommands", commands: [] })
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: ["npm", "rm file.txt"],
|
||||
deniedCommands: [],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe("integration with CommandPatternSelector", () => {
|
||||
|
|
|
|||
|
|
@ -10,9 +10,13 @@ const McpEnabledToggle = () => {
|
|||
|
||||
const handleChange = (e: Event | FormEvent<HTMLElement>) => {
|
||||
const target = ("target" in e ? e.target : null) as HTMLInputElement | null
|
||||
if (!target) return
|
||||
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
setMcpEnabled(target.checked)
|
||||
vscode.postMessage({ type: "mcpEnabled", bool: target.checked })
|
||||
vscode.postMessage({ type: "updateSettings", updatedSettings: { mcpEnabled: target.checked } })
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export const AutoApproveSettings = ({
|
|||
const newCommands = [...currentCommands, commandInput]
|
||||
setCachedStateField("allowedCommands", newCommands)
|
||||
setCommandInput("")
|
||||
vscode.postMessage({ type: "allowedCommands", commands: newCommands })
|
||||
vscode.postMessage({ type: "updateSettings", updatedSettings: { allowedCommands: newCommands } })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ export const AutoApproveSettings = ({
|
|||
const newCommands = [...currentCommands, deniedCommandInput]
|
||||
setCachedStateField("deniedCommands", newCommands)
|
||||
setDeniedCommandInput("")
|
||||
vscode.postMessage({ type: "deniedCommands", commands: newCommands })
|
||||
vscode.postMessage({ type: "updateSettings", updatedSettings: { deniedCommands: newCommands } })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -341,7 +341,11 @@ export const AutoApproveSettings = ({
|
|||
onClick={() => {
|
||||
const newCommands = (allowedCommands ?? []).filter((_, i) => i !== index)
|
||||
setCachedStateField("allowedCommands", newCommands)
|
||||
vscode.postMessage({ type: "allowedCommands", commands: newCommands })
|
||||
|
||||
vscode.postMessage({
|
||||
type: "updateSettings",
|
||||
updatedSettings: { allowedCommands: newCommands },
|
||||
})
|
||||
}}>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<div>{cmd}</div>
|
||||
|
|
@ -392,7 +396,11 @@ export const AutoApproveSettings = ({
|
|||
onClick={() => {
|
||||
const newCommands = (deniedCommands ?? []).filter((_, i) => i !== index)
|
||||
setCachedStateField("deniedCommands", newCommands)
|
||||
vscode.postMessage({ type: "deniedCommands", commands: newCommands })
|
||||
|
||||
vscode.postMessage({
|
||||
type: "updateSettings",
|
||||
updatedSettings: { deniedCommands: newCommands },
|
||||
})
|
||||
}}>
|
||||
<div className="flex flex-row items-center gap-1">
|
||||
<div>{cmd}</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,17 @@
|
|||
import React, { useCallback } from "react"
|
||||
import { Slider } from "@/components/ui"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
import { DEFAULT_CONSECUTIVE_MISTAKE_LIMIT } from "@roo-code/types"
|
||||
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
|
||||
import { Slider } from "@/components/ui"
|
||||
|
||||
interface ConsecutiveMistakeLimitControlProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
export const ConsecutiveMistakeLimitControl: React.FC<ConsecutiveMistakeLimitControlProps> = ({ value, onChange }) => {
|
||||
export const ConsecutiveMistakeLimitControl = ({ value, onChange }: ConsecutiveMistakeLimitControlProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(newValue: number) => {
|
||||
// Ensure value is not negative
|
||||
const validValue = Math.max(0, newValue)
|
||||
onChange(validValue)
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.consecutiveMistakeLimit.label")}</label>
|
||||
|
|
@ -29,7 +21,7 @@ export const ConsecutiveMistakeLimitControl: React.FC<ConsecutiveMistakeLimitCon
|
|||
min={0}
|
||||
max={10}
|
||||
step={1}
|
||||
onValueChange={(newValue) => handleValueChange(newValue[0])}
|
||||
onValueChange={(newValue) => onChange(Math.max(0, newValue[0]))}
|
||||
/>
|
||||
<span className="w-10">{Math.max(0, value ?? DEFAULT_CONSECUTIVE_MISTAKE_LIMIT)}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -93,11 +93,9 @@ export const ContextManagementSettings = ({
|
|||
...profileThresholds,
|
||||
[selectedThresholdProfile]: value,
|
||||
}
|
||||
|
||||
setCachedStateField("profileThresholds", newThresholds)
|
||||
vscode.postMessage({
|
||||
type: "profileThresholds",
|
||||
values: newThresholds,
|
||||
})
|
||||
vscode.postMessage({ type: "updateSettings", updatedSettings: { profileThresholds: newThresholds } })
|
||||
}
|
||||
}
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useTranslation } from "react-i18next"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { useCallback } from "react"
|
||||
|
||||
import { FormattedTextField, unlimitedDecimalFormatter } from "../common/FormattedTextField"
|
||||
|
||||
interface MaxCostInputProps {
|
||||
|
|
@ -11,14 +10,6 @@ interface MaxCostInputProps {
|
|||
export function MaxCostInput({ allowedMaxCost, onValueChange }: MaxCostInputProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(value: number | undefined) => {
|
||||
onValueChange(value)
|
||||
vscode.postMessage({ type: "allowedMaxCost", value })
|
||||
},
|
||||
[onValueChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="flex items-center gap-2 text-sm font-medium whitespace-nowrap">
|
||||
|
|
@ -27,7 +18,7 @@ export function MaxCostInput({ allowedMaxCost, onValueChange }: MaxCostInputProp
|
|||
</label>
|
||||
<FormattedTextField
|
||||
value={allowedMaxCost}
|
||||
onValueChange={handleValueChange}
|
||||
onValueChange={onValueChange}
|
||||
formatter={unlimitedDecimalFormatter}
|
||||
placeholder={t("settings:autoApprove.apiCostLimit.unlimited")}
|
||||
style={{ maxWidth: "200px" }}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useTranslation } from "react-i18next"
|
||||
import { vscode } from "@/utils/vscode"
|
||||
import { useCallback } from "react"
|
||||
|
||||
import { FormattedTextField, unlimitedIntegerFormatter } from "../common/FormattedTextField"
|
||||
|
||||
interface MaxRequestsInputProps {
|
||||
|
|
@ -11,14 +10,6 @@ interface MaxRequestsInputProps {
|
|||
export function MaxRequestsInput({ allowedMaxRequests, onValueChange }: MaxRequestsInputProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(value: number | undefined) => {
|
||||
onValueChange(value)
|
||||
vscode.postMessage({ type: "allowedMaxRequests", value })
|
||||
},
|
||||
[onValueChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<label className="flex items-center gap-2 text-sm font-medium whitespace-nowrap">
|
||||
|
|
@ -27,7 +18,7 @@ export function MaxRequestsInput({ allowedMaxRequests, onValueChange }: MaxReque
|
|||
</label>
|
||||
<FormattedTextField
|
||||
value={allowedMaxRequests}
|
||||
onValueChange={handleValueChange}
|
||||
onValueChange={onValueChange}
|
||||
formatter={unlimitedIntegerFormatter}
|
||||
placeholder={t("settings:autoApprove.apiRequestLimit.unlimited")}
|
||||
style={{ maxWidth: "200px" }}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useState, useEffect } from "react"
|
||||
import { useState, useEffect, FormEvent } from "react"
|
||||
import { VSCodeTextArea, VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
|
||||
import { MessageSquare } from "lucide-react"
|
||||
|
||||
import { supportPrompt, SupportPromptType } from "@roo/support-prompt"
|
||||
|
||||
|
|
@ -15,9 +16,9 @@ import {
|
|||
SelectValue,
|
||||
StandardTooltip,
|
||||
} from "@src/components/ui"
|
||||
|
||||
import { SectionHeader } from "./SectionHeader"
|
||||
import { Section } from "./Section"
|
||||
import { MessageSquare } from "lucide-react"
|
||||
|
||||
interface PromptsSettingsProps {
|
||||
customSupportPrompts: Record<string, string | undefined>
|
||||
|
|
@ -215,8 +216,8 @@ const PromptsSettings = ({
|
|||
} else {
|
||||
setCondensingApiConfigId(newConfigId)
|
||||
vscode.postMessage({
|
||||
type: "condensingApiConfigId",
|
||||
text: newConfigId,
|
||||
type: "updateSettings",
|
||||
updatedSettings: { condensingApiConfigId: newConfigId },
|
||||
})
|
||||
}
|
||||
}}>
|
||||
|
|
@ -257,12 +258,20 @@ const PromptsSettings = ({
|
|||
<div>
|
||||
<VSCodeCheckbox
|
||||
checked={includeTaskHistoryInEnhance}
|
||||
onChange={(e: any) => {
|
||||
const value = e.target.checked
|
||||
setIncludeTaskHistoryInEnhance(value)
|
||||
onChange={(e: Event | FormEvent<HTMLElement>) => {
|
||||
const target = (
|
||||
"target" in e ? e.target : null
|
||||
) as HTMLInputElement | null
|
||||
|
||||
if (!target) {
|
||||
return
|
||||
}
|
||||
|
||||
setIncludeTaskHistoryInEnhance(target.checked)
|
||||
|
||||
vscode.postMessage({
|
||||
type: "includeTaskHistoryInEnhance",
|
||||
bool: value,
|
||||
type: "updateSettings",
|
||||
updatedSettings: { includeTaskHistoryInEnhance: target.checked },
|
||||
})
|
||||
}}>
|
||||
<span className="font-medium">
|
||||
|
|
|
|||
|
|
@ -1,33 +1,20 @@
|
|||
import React, { useCallback } from "react"
|
||||
import { Slider } from "@/components/ui"
|
||||
import { useAppTranslation } from "@/i18n/TranslationContext"
|
||||
|
||||
import { Slider } from "@/components/ui"
|
||||
|
||||
interface RateLimitSecondsControlProps {
|
||||
value: number
|
||||
onChange: (value: number) => void
|
||||
}
|
||||
|
||||
export const RateLimitSecondsControl: React.FC<RateLimitSecondsControlProps> = ({ value, onChange }) => {
|
||||
export const RateLimitSecondsControl = ({ value, onChange }: RateLimitSecondsControlProps) => {
|
||||
const { t } = useAppTranslation()
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(newValue: number) => {
|
||||
onChange(newValue)
|
||||
},
|
||||
[onChange],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="block font-medium mb-1">{t("settings:providers.rateLimitSeconds.label")}</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Slider
|
||||
value={[value]}
|
||||
min={0}
|
||||
max={60}
|
||||
step={1}
|
||||
onValueChange={(newValue) => handleValueChange(newValue[0])}
|
||||
/>
|
||||
<Slider value={[value]} min={0} max={60} step={1} onValueChange={(newValue) => onChange(newValue[0])} />
|
||||
<span className="w-10">{value}s</span>
|
||||
</div>
|
||||
<div className="text-sm text-vscode-descriptionForeground">
|
||||
|
|
|
|||
|
|
@ -27,7 +27,12 @@ import {
|
|||
Glasses,
|
||||
} from "lucide-react"
|
||||
|
||||
import type { ProviderSettings, ExperimentId, TelemetrySetting } from "@roo-code/types"
|
||||
import {
|
||||
type ProviderSettings,
|
||||
type ExperimentId,
|
||||
type TelemetrySetting,
|
||||
DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
|
||||
} from "@roo-code/types"
|
||||
|
||||
import { vscode } from "@src/utils/vscode"
|
||||
import { cn } from "@src/lib/utils"
|
||||
|
|
@ -285,20 +290,20 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
|
||||
const setOpenRouterImageApiKey = useCallback((apiKey: string) => {
|
||||
setCachedState((prevState) => {
|
||||
// Only set change detected if value actually changed
|
||||
if (prevState.openRouterImageApiKey !== apiKey) {
|
||||
setChangeDetected(true)
|
||||
}
|
||||
|
||||
return { ...prevState, openRouterImageApiKey: apiKey }
|
||||
})
|
||||
}, [])
|
||||
|
||||
const setImageGenerationSelectedModel = useCallback((model: string) => {
|
||||
setCachedState((prevState) => {
|
||||
// Only set change detected if value actually changed
|
||||
if (prevState.openRouterImageGenerationSelectedModel !== model) {
|
||||
setChangeDetected(true)
|
||||
}
|
||||
|
||||
return { ...prevState, openRouterImageGenerationSelectedModel: model }
|
||||
})
|
||||
}, [])
|
||||
|
|
@ -321,83 +326,89 @@ const SettingsView = forwardRef<SettingsViewRef, SettingsViewProps>(({ onDone, t
|
|||
|
||||
const handleSubmit = () => {
|
||||
if (isSettingValid) {
|
||||
vscode.postMessage({ type: "language", text: language })
|
||||
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly })
|
||||
vscode.postMessage({
|
||||
type: "alwaysAllowReadOnlyOutsideWorkspace",
|
||||
bool: alwaysAllowReadOnlyOutsideWorkspace,
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
language,
|
||||
alwaysAllowReadOnly: alwaysAllowReadOnly ?? undefined,
|
||||
alwaysAllowReadOnlyOutsideWorkspace: alwaysAllowReadOnlyOutsideWorkspace ?? undefined,
|
||||
alwaysAllowWrite: alwaysAllowWrite ?? undefined,
|
||||
alwaysAllowWriteOutsideWorkspace: alwaysAllowWriteOutsideWorkspace ?? undefined,
|
||||
alwaysAllowWriteProtected: alwaysAllowWriteProtected ?? undefined,
|
||||
alwaysAllowExecute: alwaysAllowExecute ?? undefined,
|
||||
alwaysAllowBrowser: alwaysAllowBrowser ?? undefined,
|
||||
alwaysAllowMcp,
|
||||
alwaysAllowModeSwitch,
|
||||
allowedCommands: allowedCommands ?? [],
|
||||
deniedCommands: deniedCommands ?? [],
|
||||
// Note that we use `null` instead of `undefined` since `JSON.stringify`
|
||||
// will omit `undefined` when serializing the object and passing it to the
|
||||
// extension host. We may need to do the same for other nullable fields.
|
||||
allowedMaxRequests: allowedMaxRequests ?? null,
|
||||
allowedMaxCost: allowedMaxCost ?? null,
|
||||
autoCondenseContext,
|
||||
autoCondenseContextPercent,
|
||||
browserToolEnabled: browserToolEnabled ?? true,
|
||||
soundEnabled: soundEnabled ?? true,
|
||||
soundVolume: soundVolume ?? 0.5,
|
||||
ttsEnabled,
|
||||
ttsSpeed,
|
||||
diffEnabled: diffEnabled ?? true,
|
||||
enableCheckpoints: enableCheckpoints ?? false,
|
||||
checkpointTimeout: checkpointTimeout ?? DEFAULT_CHECKPOINT_TIMEOUT_SECONDS,
|
||||
browserViewportSize: browserViewportSize ?? "900x600",
|
||||
remoteBrowserHost: remoteBrowserEnabled ? remoteBrowserHost : undefined,
|
||||
remoteBrowserEnabled: remoteBrowserEnabled ?? false,
|
||||
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
||||
writeDelayMs,
|
||||
screenshotQuality: screenshotQuality ?? 75,
|
||||
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
|
||||
terminalOutputCharacterLimit: terminalOutputCharacterLimit ?? 50_000,
|
||||
terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? 30_000,
|
||||
terminalShellIntegrationDisabled,
|
||||
terminalCommandDelay,
|
||||
terminalPowershellCounter,
|
||||
terminalZshClearEolMark,
|
||||
terminalZshOhMy,
|
||||
terminalZshP10k,
|
||||
terminalZdotdir,
|
||||
terminalCompressProgressBar,
|
||||
mcpEnabled,
|
||||
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
||||
requestDelaySeconds: requestDelaySeconds ?? 5,
|
||||
maxOpenTabsContext: Math.min(Math.max(0, maxOpenTabsContext ?? 20), 500),
|
||||
maxWorkspaceFiles: Math.min(Math.max(0, maxWorkspaceFiles ?? 200), 500),
|
||||
showRooIgnoredFiles: showRooIgnoredFiles ?? true,
|
||||
maxReadFileLine: maxReadFileLine ?? -1,
|
||||
maxImageFileSize: maxImageFileSize ?? 5,
|
||||
maxTotalImageSize: maxTotalImageSize ?? 20,
|
||||
maxConcurrentFileReads: cachedState.maxConcurrentFileReads ?? 5,
|
||||
includeDiagnosticMessages:
|
||||
includeDiagnosticMessages !== undefined ? includeDiagnosticMessages : true,
|
||||
maxDiagnosticMessages: maxDiagnosticMessages ?? 50,
|
||||
alwaysAllowSubtasks,
|
||||
alwaysAllowUpdateTodoList,
|
||||
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
|
||||
followupAutoApproveTimeoutMs,
|
||||
condensingApiConfigId: condensingApiConfigId || "",
|
||||
includeTaskHistoryInEnhance: includeTaskHistoryInEnhance ?? true,
|
||||
reasoningBlockCollapsed: reasoningBlockCollapsed ?? true,
|
||||
includeCurrentTime: includeCurrentTime ?? true,
|
||||
includeCurrentCost: includeCurrentCost ?? true,
|
||||
profileThresholds,
|
||||
openRouterImageApiKey,
|
||||
openRouterImageGenerationSelectedModel,
|
||||
experiments,
|
||||
customSupportPrompts,
|
||||
},
|
||||
})
|
||||
vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite })
|
||||
vscode.postMessage({ type: "alwaysAllowWriteOutsideWorkspace", bool: alwaysAllowWriteOutsideWorkspace })
|
||||
vscode.postMessage({ type: "alwaysAllowWriteProtected", bool: alwaysAllowWriteProtected })
|
||||
vscode.postMessage({ type: "alwaysAllowExecute", bool: alwaysAllowExecute })
|
||||
vscode.postMessage({ type: "alwaysAllowBrowser", bool: alwaysAllowBrowser })
|
||||
vscode.postMessage({ type: "alwaysAllowMcp", bool: alwaysAllowMcp })
|
||||
vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] })
|
||||
vscode.postMessage({ type: "deniedCommands", commands: deniedCommands ?? [] })
|
||||
vscode.postMessage({ type: "allowedMaxRequests", value: allowedMaxRequests ?? undefined })
|
||||
vscode.postMessage({ type: "allowedMaxCost", value: allowedMaxCost ?? undefined })
|
||||
vscode.postMessage({ type: "autoCondenseContext", bool: autoCondenseContext })
|
||||
vscode.postMessage({ type: "autoCondenseContextPercent", value: autoCondenseContextPercent })
|
||||
vscode.postMessage({ type: "browserToolEnabled", bool: browserToolEnabled })
|
||||
vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
|
||||
vscode.postMessage({ type: "ttsEnabled", bool: ttsEnabled })
|
||||
vscode.postMessage({ type: "ttsSpeed", value: ttsSpeed })
|
||||
vscode.postMessage({ type: "soundVolume", value: soundVolume })
|
||||
vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
|
||||
vscode.postMessage({ type: "enableCheckpoints", bool: enableCheckpoints })
|
||||
vscode.postMessage({ type: "checkpointTimeout", value: checkpointTimeout })
|
||||
vscode.postMessage({ type: "browserViewportSize", text: browserViewportSize })
|
||||
vscode.postMessage({ type: "remoteBrowserHost", text: remoteBrowserHost })
|
||||
vscode.postMessage({ type: "remoteBrowserEnabled", bool: remoteBrowserEnabled })
|
||||
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
|
||||
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
|
||||
vscode.postMessage({ type: "screenshotQuality", value: screenshotQuality ?? 75 })
|
||||
vscode.postMessage({ type: "terminalOutputLineLimit", value: terminalOutputLineLimit ?? 500 })
|
||||
vscode.postMessage({ type: "terminalOutputCharacterLimit", value: terminalOutputCharacterLimit ?? 50000 })
|
||||
vscode.postMessage({ type: "terminalShellIntegrationTimeout", value: terminalShellIntegrationTimeout })
|
||||
vscode.postMessage({ type: "terminalShellIntegrationDisabled", bool: terminalShellIntegrationDisabled })
|
||||
vscode.postMessage({ type: "terminalCommandDelay", value: terminalCommandDelay })
|
||||
vscode.postMessage({ type: "terminalPowershellCounter", bool: terminalPowershellCounter })
|
||||
vscode.postMessage({ type: "terminalZshClearEolMark", bool: terminalZshClearEolMark })
|
||||
vscode.postMessage({ type: "terminalZshOhMy", bool: terminalZshOhMy })
|
||||
vscode.postMessage({ type: "terminalZshP10k", bool: terminalZshP10k })
|
||||
vscode.postMessage({ type: "terminalZdotdir", bool: terminalZdotdir })
|
||||
vscode.postMessage({ type: "terminalCompressProgressBar", bool: terminalCompressProgressBar })
|
||||
vscode.postMessage({ type: "mcpEnabled", bool: mcpEnabled })
|
||||
vscode.postMessage({ type: "alwaysApproveResubmit", bool: alwaysApproveResubmit })
|
||||
vscode.postMessage({ type: "requestDelaySeconds", value: requestDelaySeconds })
|
||||
vscode.postMessage({ type: "maxOpenTabsContext", value: maxOpenTabsContext })
|
||||
vscode.postMessage({ type: "maxWorkspaceFiles", value: maxWorkspaceFiles ?? 200 })
|
||||
vscode.postMessage({ type: "showRooIgnoredFiles", bool: showRooIgnoredFiles })
|
||||
vscode.postMessage({ type: "maxReadFileLine", value: maxReadFileLine ?? -1 })
|
||||
vscode.postMessage({ type: "maxImageFileSize", value: maxImageFileSize ?? 5 })
|
||||
vscode.postMessage({ type: "maxTotalImageSize", value: maxTotalImageSize ?? 20 })
|
||||
vscode.postMessage({ type: "maxConcurrentFileReads", value: cachedState.maxConcurrentFileReads ?? 5 })
|
||||
vscode.postMessage({ type: "includeDiagnosticMessages", bool: includeDiagnosticMessages })
|
||||
vscode.postMessage({ type: "maxDiagnosticMessages", value: maxDiagnosticMessages ?? 50 })
|
||||
vscode.postMessage({ type: "currentApiConfigName", text: currentApiConfigName })
|
||||
vscode.postMessage({ type: "updateExperimental", values: experiments })
|
||||
vscode.postMessage({ type: "alwaysAllowModeSwitch", bool: alwaysAllowModeSwitch })
|
||||
vscode.postMessage({ type: "alwaysAllowSubtasks", bool: alwaysAllowSubtasks })
|
||||
vscode.postMessage({ type: "alwaysAllowFollowupQuestions", bool: alwaysAllowFollowupQuestions })
|
||||
vscode.postMessage({ type: "alwaysAllowUpdateTodoList", bool: alwaysAllowUpdateTodoList })
|
||||
vscode.postMessage({ type: "followupAutoApproveTimeoutMs", value: followupAutoApproveTimeoutMs })
|
||||
vscode.postMessage({ type: "condensingApiConfigId", text: condensingApiConfigId || "" })
|
||||
|
||||
// These have more complex logic so they aren't (yet) handled
|
||||
// by the `updateSettings` message.
|
||||
vscode.postMessage({ type: "updateCondensingPrompt", text: customCondensingPrompt || "" })
|
||||
vscode.postMessage({ type: "updateSupportPrompt", values: customSupportPrompts || {} })
|
||||
vscode.postMessage({ type: "includeTaskHistoryInEnhance", bool: includeTaskHistoryInEnhance ?? true })
|
||||
vscode.postMessage({ type: "setReasoningBlockCollapsed", bool: reasoningBlockCollapsed ?? true })
|
||||
vscode.postMessage({ type: "includeCurrentTime", bool: includeCurrentTime ?? true })
|
||||
vscode.postMessage({ type: "includeCurrentCost", bool: includeCurrentCost ?? true })
|
||||
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
|
||||
vscode.postMessage({ type: "telemetrySetting", text: telemetrySetting })
|
||||
vscode.postMessage({ type: "profileThresholds", values: profileThresholds })
|
||||
vscode.postMessage({ type: "openRouterImageApiKey", text: openRouterImageApiKey })
|
||||
vscode.postMessage({
|
||||
type: "openRouterImageGenerationSelectedModel",
|
||||
text: openRouterImageGenerationSelectedModel,
|
||||
})
|
||||
|
||||
setChangeDetected(false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
// pnpm --filter @roo-code/vscode-webview test src/components/settings/__tests__/SettingsView.spec.tsx
|
||||
|
||||
import { render, screen, fireEvent } from "@/utils/test-utils"
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
|
||||
|
||||
|
|
@ -311,8 +313,10 @@ describe("SettingsView - Sound Settings", () => {
|
|||
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "ttsEnabled",
|
||||
bool: true,
|
||||
type: "updateSettings",
|
||||
updatedSettings: expect.objectContaining({
|
||||
ttsEnabled: true,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -336,8 +340,10 @@ describe("SettingsView - Sound Settings", () => {
|
|||
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "soundEnabled",
|
||||
bool: true,
|
||||
type: "updateSettings",
|
||||
updatedSettings: expect.objectContaining({
|
||||
soundEnabled: true,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
@ -396,10 +402,14 @@ describe("SettingsView - Sound Settings", () => {
|
|||
fireEvent.click(saveButton)
|
||||
|
||||
// Verify message sent to VSCode
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "ttsSpeed",
|
||||
value: 0.75,
|
||||
})
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "updateSettings",
|
||||
updatedSettings: expect.objectContaining({
|
||||
ttsSpeed: 0.75,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("updates volume and sends message to VSCode when slider changes", () => {
|
||||
|
|
@ -422,10 +432,14 @@ describe("SettingsView - Sound Settings", () => {
|
|||
fireEvent.click(saveButtons[0])
|
||||
|
||||
// Verify message sent to VSCode
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "soundVolume",
|
||||
value: 0.75,
|
||||
})
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "updateSettings",
|
||||
updatedSettings: expect.objectContaining({
|
||||
soundVolume: 0.75,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -484,8 +498,10 @@ describe("SettingsView - Allowed Commands", () => {
|
|||
|
||||
// Verify VSCode message was sent
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith({
|
||||
type: "allowedCommands",
|
||||
commands: ["npm test"],
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: ["npm test"],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -515,8 +531,10 @@ describe("SettingsView - Allowed Commands", () => {
|
|||
|
||||
// Verify VSCode message was sent
|
||||
expect(vscode.postMessage).toHaveBeenLastCalledWith({
|
||||
type: "allowedCommands",
|
||||
commands: [],
|
||||
type: "updateSettings",
|
||||
updatedSettings: {
|
||||
allowedCommands: [],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -631,8 +649,10 @@ describe("SettingsView - Duplicate Commands", () => {
|
|||
// Verify VSCode messages were sent
|
||||
expect(vscode.postMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: "allowedCommands",
|
||||
commands: ["npm test"],
|
||||
type: "updateSettings",
|
||||
updatedSettings: expect.objectContaining({
|
||||
allowedCommands: ["npm test"],
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue