(false)
- const [hasStarted, setHasStarted] = useState(false)
// UI layout depends on the last 2 messages
// (since it relies on the content of these messages, we are deep comparing. i.e. the button state after hitting button sets enableButtons to false, and this effect otherwise would have to true again even if messages didn't change
@@ -75,12 +74,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
vscode.postMessage({ type: "playSound", audioType })
}
- function playSoundOnMessage(audioType: AudioType) {
- if (hasStarted && !isStreaming) {
- playSound(audioType)
- }
- }
-
useDeepCompareEffect(() => {
// if last message is an ask, show user ask UI
// if user finished a task, then start a new task with a new conversation history since in this moment that the extension is waiting for user response, the user could close the extension and the conversation history would be lost.
@@ -91,7 +84,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
const isPartial = lastMessage.partial === true
switch (lastMessage.ask) {
case "api_req_failed":
- playSoundOnMessage("progress_loop")
+ playSound("progress_loop")
setTextAreaDisabled(true)
setClineAsk("api_req_failed")
setEnableButtons(true)
@@ -99,7 +92,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setSecondaryButtonText("Start New Task")
break
case "mistake_limit_reached":
- playSoundOnMessage("progress_loop")
+ playSound("progress_loop")
setTextAreaDisabled(false)
setClineAsk("mistake_limit_reached")
setEnableButtons(true)
@@ -107,7 +100,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setSecondaryButtonText("Start New Task")
break
case "followup":
- playSoundOnMessage("notification")
setTextAreaDisabled(isPartial)
setClineAsk("followup")
setEnableButtons(isPartial)
@@ -115,7 +107,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
// setSecondaryButtonText(undefined)
break
case "tool":
- playSoundOnMessage("notification")
+ if (!isAutoApproved(lastMessage)) {
+ playSound("notification")
+ }
setTextAreaDisabled(isPartial)
setClineAsk("tool")
setEnableButtons(!isPartial)
@@ -134,7 +128,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
}
break
case "browser_action_launch":
- playSoundOnMessage("notification")
+ if (!isAutoApproved(lastMessage)) {
+ playSound("notification")
+ }
setTextAreaDisabled(isPartial)
setClineAsk("browser_action_launch")
setEnableButtons(!isPartial)
@@ -142,7 +138,9 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setSecondaryButtonText("Reject")
break
case "command":
- playSoundOnMessage("notification")
+ if (!isAutoApproved(lastMessage)) {
+ playSound("notification")
+ }
setTextAreaDisabled(isPartial)
setClineAsk("command")
setEnableButtons(!isPartial)
@@ -150,7 +148,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setSecondaryButtonText("Reject")
break
case "command_output":
- playSoundOnMessage("notification")
setTextAreaDisabled(false)
setClineAsk("command_output")
setEnableButtons(true)
@@ -166,7 +163,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
break
case "completion_result":
// extension waiting for feedback. but we can just present a new task button
- playSoundOnMessage("celebration")
+ playSound("celebration")
setTextAreaDisabled(isPartial)
setClineAsk("completion_result")
setEnableButtons(!isPartial)
@@ -174,7 +171,6 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setSecondaryButtonText(undefined)
break
case "resume_task":
- playSoundOnMessage("notification")
setTextAreaDisabled(false)
setClineAsk("resume_task")
setEnableButtons(true)
@@ -183,7 +179,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
setDidClickCancel(false) // special case where we reset the cancel button state
break
case "resume_completed_task":
- playSoundOnMessage("celebration")
+ playSound("celebration")
setTextAreaDisabled(false)
setClineAsk("resume_completed_task")
setEnableButtons(true)
@@ -482,36 +478,122 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
return true
})
}, [modifiedMessages])
- useEffect(() => {
- if (isStreaming) {
- // Set to true once any request has started
- setHasStarted(true)
+
+ const isReadOnlyToolAction = useCallback((message: ClineMessage | undefined) => {
+ if (message?.type === "ask") {
+ if (!message.text) {
+ return true
+ }
+ const tool = JSON.parse(message.text)
+ return ["readFile", "listFiles", "listFilesTopLevel", "listFilesRecursive", "listCodeDefinitionNames", "searchFiles"].includes(tool.tool)
}
+ return false
+ }, [])
+
+ const isWriteToolAction = useCallback((message: ClineMessage | undefined) => {
+ if (message?.type === "ask") {
+ if (!message.text) {
+ return true
+ }
+ const tool = JSON.parse(message.text)
+ return ["editedExistingFile", "appliedDiff", "newFileCreated"].includes(tool.tool)
+ }
+ return false
+ }, [])
+
+ const isMcpToolAlwaysAllowed = useCallback((message: ClineMessage | undefined) => {
+ if (message?.type === "ask" && message.ask === "use_mcp_server") {
+ if (!message.text) {
+ return true
+ }
+ const mcpServerUse = JSON.parse(message.text) as { type: string; serverName: string; toolName: string }
+ if (mcpServerUse.type === "use_mcp_tool") {
+ const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName)
+ const tool = server?.tools?.find((t: McpTool) => t.name === mcpServerUse.toolName)
+ return tool?.alwaysAllow || false
+ }
+ }
+ return false
+ }, [mcpServers])
+
+ const isAllowedCommand = useCallback((message: ClineMessage | undefined) => {
+ if (message?.type === "ask") {
+ const command = message.text
+ if (!command) {
+ return true
+ }
+
+ // Split command by chaining operators
+ const commands = command.split(/&&|\|\||;|\||\$\(|`/).map(cmd => cmd.trim())
+
+ // Check if all individual commands are allowed
+ return commands.every((cmd) => {
+ const trimmedCommand = cmd.toLowerCase()
+ return allowedCommands?.some((prefix) => trimmedCommand.startsWith(prefix.toLowerCase()))
+ })
+ }
+ return false
+ }, [allowedCommands])
+
+ const isAutoApproved = useCallback(
+ (message: ClineMessage | undefined) => {
+ if (!message || message.type !== "ask") return false
+
+ return (
+ (alwaysAllowBrowser && message.ask === "browser_action_launch") ||
+ (alwaysAllowReadOnly && message.ask === "tool" && isReadOnlyToolAction(message)) ||
+ (alwaysAllowWrite && message.ask === "tool" && isWriteToolAction(message)) ||
+ (alwaysAllowExecute && message.ask === "command" && isAllowedCommand(message)) ||
+ (alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message))
+ )
+ },
+ [
+ alwaysAllowBrowser,
+ alwaysAllowReadOnly,
+ alwaysAllowWrite,
+ alwaysAllowExecute,
+ alwaysAllowMcp,
+ isReadOnlyToolAction,
+ isWriteToolAction,
+ isAllowedCommand,
+ isMcpToolAlwaysAllowed
+ ]
+ )
+
+ useEffect(() => {
// Only execute when isStreaming changes from true to false
if (wasStreaming && !isStreaming && lastMessage) {
// Play appropriate sound based on lastMessage content
if (lastMessage.type === "ask") {
- switch (lastMessage.ask) {
- case "api_req_failed":
- case "mistake_limit_reached":
- playSound("progress_loop")
- break
- case "tool":
- case "followup":
- case "browser_action_launch":
- case "resume_task":
- playSound("notification")
- break
- case "completion_result":
- case "resume_completed_task":
- playSound("celebration")
- break
+ // Don't play sounds for auto-approved actions
+ if (!isAutoApproved(lastMessage)) {
+ switch (lastMessage.ask) {
+ case "api_req_failed":
+ case "mistake_limit_reached":
+ playSound("progress_loop")
+ break
+ case "followup":
+ if (!lastMessage.partial) {
+ playSound("notification")
+ }
+ break
+ case "tool":
+ case "browser_action_launch":
+ case "resume_task":
+ case "use_mcp_server":
+ playSound("notification")
+ break
+ case "completion_result":
+ case "resume_completed_task":
+ playSound("celebration")
+ break
+ }
}
}
}
// Update previous value
setWasStreaming(isStreaming)
- }, [isStreaming, lastMessage, wasStreaming])
+ }, [isStreaming, lastMessage, wasStreaming, isAutoApproved])
const isBrowserSessionMessage = (message: ClineMessage): boolean => {
// which of visible messages are browser session messages, see above
@@ -750,64 +832,10 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
// Only proceed if we have an ask and buttons are enabled
if (!clineAsk || !enableButtons) return
- const isReadOnlyToolAction = () => {
- const lastMessage = messages.at(-1)
- if (lastMessage?.type === "ask" && lastMessage.text) {
- const tool = JSON.parse(lastMessage.text)
- return ["readFile", "listFiles", "listFilesTopLevel", "listFilesRecursive", "listCodeDefinitionNames", "searchFiles"].includes(tool.tool)
- }
- return false
- }
-
- const isWriteToolAction = () => {
- const lastMessage = messages.at(-1)
- if (lastMessage?.type === "ask" && lastMessage.text) {
- const tool = JSON.parse(lastMessage.text)
- return ["editedExistingFile", "appliedDiff", "newFileCreated"].includes(tool.tool)
- }
- return false
- }
-
- const isMcpToolAlwaysAllowed = () => {
- const lastMessage = messages.at(-1)
- if (lastMessage?.type === "ask" && lastMessage.ask === "use_mcp_server" && lastMessage.text) {
- const mcpServerUse = JSON.parse(lastMessage.text) as { type: string; serverName: string; toolName: string }
- if (mcpServerUse.type === "use_mcp_tool") {
- const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName)
- const tool = server?.tools?.find((t: McpTool) => t.name === mcpServerUse.toolName)
- return tool?.alwaysAllow || false
- }
- }
- return false
- }
-
- const isAllowedCommand = () => {
- const lastMessage = messages.at(-1)
- if (lastMessage?.type === "ask" && lastMessage.text) {
- const command = lastMessage.text
-
- // Split command by chaining operators
- const commands = command.split(/&&|\|\||;|\||\$\(|`/).map(cmd => cmd.trim())
-
- // Check if all individual commands are allowed
- return commands.every((cmd) => {
- const trimmedCommand = cmd.toLowerCase()
- return allowedCommands?.some((prefix) => trimmedCommand.startsWith(prefix.toLowerCase()))
- })
- }
- return false
- }
-
- if (
- (alwaysAllowBrowser && clineAsk === "browser_action_launch") ||
- (alwaysAllowReadOnly && clineAsk === "tool" && isReadOnlyToolAction()) ||
- (alwaysAllowWrite && clineAsk === "tool" && isWriteToolAction()) ||
- (alwaysAllowExecute && clineAsk === "command" && isAllowedCommand()) ||
- (alwaysAllowMcp && clineAsk === "use_mcp_server" && isMcpToolAlwaysAllowed())
- ) {
+ if (isAutoApproved(lastMessage)) {
handlePrimaryButtonClick()
}
- }, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers])
+ }, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers, isAutoApproved, lastMessage])
return (
{
})
})
})
+
+describe('ChatView - Sound Playing Tests', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('does not play sound for auto-approved browser actions', async () => {
+ render(
+
+ {}}
+ showHistoryView={() => {}}
+ />
+
+ )
+
+ // First hydrate state with initial task and streaming
+ mockPostMessage({
+ alwaysAllowBrowser: true,
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'say',
+ say: 'api_req_started',
+ ts: Date.now() - 1000,
+ text: JSON.stringify({}),
+ partial: true
+ }
+ ]
+ })
+
+ // Then send the browser action ask message (streaming finished)
+ mockPostMessage({
+ alwaysAllowBrowser: true,
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'ask',
+ ask: 'browser_action_launch',
+ ts: Date.now(),
+ text: JSON.stringify({ action: 'launch', url: 'http://example.com' }),
+ partial: false
+ }
+ ]
+ })
+
+ // Verify no sound was played
+ expect(vscode.postMessage).not.toHaveBeenCalledWith({
+ type: 'playSound',
+ audioType: expect.any(String)
+ })
+ })
+
+ it('plays notification sound for non-auto-approved browser actions', async () => {
+ render(
+
+ {}}
+ showHistoryView={() => {}}
+ />
+
+ )
+
+ // First hydrate state with initial task and streaming
+ mockPostMessage({
+ alwaysAllowBrowser: false,
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'say',
+ say: 'api_req_started',
+ ts: Date.now() - 1000,
+ text: JSON.stringify({}),
+ partial: true
+ }
+ ]
+ })
+
+ // Then send the browser action ask message (streaming finished)
+ mockPostMessage({
+ alwaysAllowBrowser: false,
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'ask',
+ ask: 'browser_action_launch',
+ ts: Date.now(),
+ text: JSON.stringify({ action: 'launch', url: 'http://example.com' }),
+ partial: false
+ }
+ ]
+ })
+
+ // Verify notification sound was played
+ await waitFor(() => {
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: 'playSound',
+ audioType: 'notification'
+ })
+ })
+ })
+
+ it('plays celebration sound for completion results', async () => {
+ render(
+
+ {}}
+ showHistoryView={() => {}}
+ />
+
+ )
+
+ // First hydrate state with initial task and streaming
+ mockPostMessage({
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'say',
+ say: 'api_req_started',
+ ts: Date.now() - 1000,
+ text: JSON.stringify({}),
+ partial: true
+ }
+ ]
+ })
+
+ // Then send the completion result message (streaming finished)
+ mockPostMessage({
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'ask',
+ ask: 'completion_result',
+ ts: Date.now(),
+ text: 'Task completed successfully',
+ partial: false
+ }
+ ]
+ })
+
+ // Verify celebration sound was played
+ await waitFor(() => {
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: 'playSound',
+ audioType: 'celebration'
+ })
+ })
+ })
+
+ it('plays progress_loop sound for api failures', async () => {
+ render(
+
+ {}}
+ showHistoryView={() => {}}
+ />
+
+ )
+
+ // First hydrate state with initial task and streaming
+ mockPostMessage({
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'say',
+ say: 'api_req_started',
+ ts: Date.now() - 1000,
+ text: JSON.stringify({}),
+ partial: true
+ }
+ ]
+ })
+
+ // Then send the api failure message (streaming finished)
+ mockPostMessage({
+ clineMessages: [
+ {
+ type: 'say',
+ say: 'task',
+ ts: Date.now() - 2000,
+ text: 'Initial task'
+ },
+ {
+ type: 'ask',
+ ask: 'api_req_failed',
+ ts: Date.now(),
+ text: 'API request failed',
+ partial: false
+ }
+ ]
+ })
+
+ // Verify progress_loop sound was played
+ await waitFor(() => {
+ expect(vscode.postMessage).toHaveBeenCalledWith({
+ type: 'playSound',
+ audioType: 'progress_loop'
+ })
+ })
+ })
+})
diff --git a/webview-ui/src/components/mcp/__tests__/McpToolRow.test.tsx b/webview-ui/src/components/mcp/__tests__/McpToolRow.test.tsx
index 9f3cd96..2f4d286 100644
--- a/webview-ui/src/components/mcp/__tests__/McpToolRow.test.tsx
+++ b/webview-ui/src/components/mcp/__tests__/McpToolRow.test.tsx
@@ -23,7 +23,6 @@ jest.mock('@vscode/webview-ui-toolkit/react', () => ({
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx
index ffa45d0..e1c9701 100644
--- a/webview-ui/src/components/settings/SettingsView.tsx
+++ b/webview-ui/src/components/settings/SettingsView.tsx
@@ -29,8 +29,12 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
setAlwaysAllowMcp,
soundEnabled,
setSoundEnabled,
+ soundVolume,
+ setSoundVolume,
diffEnabled,
setDiffEnabled,
+ debugDiffEnabled,
+ setDebugDiffEnabled,
openRouterModels,
setAllowedCommands,
allowedCommands,
@@ -46,7 +50,10 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
setApiErrorMessage(apiValidationResult)
setModelIdErrorMessage(modelIdValidationResult)
if (!apiValidationResult && !modelIdValidationResult) {
- vscode.postMessage({ type: "apiConfiguration", apiConfiguration })
+ vscode.postMessage({
+ type: "apiConfiguration",
+ apiConfiguration
+ })
vscode.postMessage({ type: "customInstructions", text: customInstructions })
vscode.postMessage({ type: "alwaysAllowReadOnly", bool: alwaysAllowReadOnly })
vscode.postMessage({ type: "alwaysAllowWrite", bool: alwaysAllowWrite })
@@ -55,7 +62,9 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
vscode.postMessage({ type: "alwaysAllowMcp", bool: alwaysAllowMcp })
vscode.postMessage({ type: "allowedCommands", commands: allowedCommands ?? [] })
vscode.postMessage({ type: "soundEnabled", bool: soundEnabled })
+ vscode.postMessage({ type: "soundVolume", value: soundVolume })
vscode.postMessage({ type: "diffEnabled", bool: diffEnabled })
+ vscode.postMessage({ type: "debugDiffEnabled", bool: debugDiffEnabled })
onDone()
}
}
@@ -155,7 +164,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
- When enabled, Cline will be able to edit files more quickly and will automatically reject truncated full-file writes.
+ When enabled, Cline will be able to edit files more quickly and will automatically reject truncated full-file writes. Works best with the latest Claude 3.5 Sonnet model.
@@ -312,8 +321,47 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
Experimental Features
-
setSoundEnabled(e.target.checked)}>
- Enable sound effects
+
+
setSoundEnabled(e.target.checked)}>
+ Enable sound effects
+
+
+ When enabled, Cline will play sound effects for notifications and events.
+
+
+ {soundEnabled && (
+
+
+ Volume
+ setSoundVolume(parseFloat(e.target.value))}
+ style={{
+ flexGrow: 1,
+ accentColor: 'var(--vscode-button-background)',
+ height: '2px'
+ }}
+ />
+
+ {Math.round((soundVolume ?? 0.5) * 100)}%
+
+
+
+ )}
+
+
+
+
setDebugDiffEnabled(e.target.checked)}>
+ Debug diff operations
{
marginTop: "5px",
color: "var(--vscode-descriptionForeground)",
}}>
- When enabled, Cline will play sound effects for notifications and events.
+ When enabled, Cline will show detailed debug information when applying diffs fails.
diff --git a/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx b/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx
index 50fd597..c9e5f0a 100644
--- a/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx
+++ b/webview-ui/src/components/settings/__tests__/SettingsView.test.tsx
@@ -40,7 +40,7 @@ jest.mock('@vscode/webview-ui-toolkit/react', () => ({
/>
),
VSCodeTextArea: () => ,
- VSCodeLink: () => ,
+ VSCodeLink: ({ children, href }: any) => {children} ,
VSCodeDropdown: ({ children, value, onChange }: any) => (
{children}
@@ -104,6 +104,9 @@ describe('SettingsView - Sound Settings', () => {
name: /Enable sound effects/i
})
expect(soundCheckbox).not.toBeChecked()
+
+ // Volume slider should not be visible when sound is disabled
+ expect(screen.queryByRole('slider')).not.toBeInTheDocument()
})
it('toggles sound setting and sends message to VSCode', () => {
@@ -128,6 +131,50 @@ describe('SettingsView - Sound Settings', () => {
})
)
})
+
+ it('shows volume slider when sound is enabled', () => {
+ renderSettingsView()
+
+ // Enable sound
+ const soundCheckbox = screen.getByRole('checkbox', {
+ name: /Enable sound effects/i
+ })
+ fireEvent.click(soundCheckbox)
+
+ // Volume slider should be visible
+ const volumeSlider = screen.getByRole('slider')
+ expect(volumeSlider).toBeInTheDocument()
+ expect(volumeSlider).toHaveValue('0.5') // Default value
+ })
+
+ it('updates volume and sends message to VSCode when slider changes', () => {
+ renderSettingsView()
+
+ // Enable sound
+ const soundCheckbox = screen.getByRole('checkbox', {
+ name: /Enable sound effects/i
+ })
+ fireEvent.click(soundCheckbox)
+
+ // Change volume
+ const volumeSlider = screen.getByRole('slider')
+ fireEvent.change(volumeSlider, { target: { value: '0.75' } })
+
+ // Verify volume display updates
+ expect(screen.getByText('75%')).toBeInTheDocument()
+
+ // Click Done to save settings
+ const doneButton = screen.getByText('Done')
+ fireEvent.click(doneButton)
+
+ // Verify message sent to VSCode
+ expect(vscode.postMessage).toHaveBeenCalledWith(
+ expect.objectContaining({
+ type: 'soundVolume',
+ value: 0.75
+ })
+ )
+ })
})
describe('SettingsView - Allowed Commands', () => {
diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx
index f9690b6..f4fdaa3 100644
--- a/webview-ui/src/context/ExtensionStateContext.tsx
+++ b/webview-ui/src/context/ExtensionStateContext.tsx
@@ -29,7 +29,9 @@ export interface ExtensionStateContextType extends ExtensionState {
setShowAnnouncement: (value: boolean) => void
setAllowedCommands: (value: string[]) => void
setSoundEnabled: (value: boolean) => void
+ setSoundVolume: (value: number) => void
setDiffEnabled: (value: boolean) => void
+ setDebugDiffEnabled: (value: boolean) => void
}
const ExtensionStateContext = createContext(undefined)
@@ -42,7 +44,9 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
shouldShowAnnouncement: false,
allowedCommands: [],
soundEnabled: false,
+ soundVolume: 0.5,
diffEnabled: false,
+ debugDiffEnabled: false,
})
const [didHydrateState, setDidHydrateState] = useState(false)
const [showWelcome, setShowWelcome] = useState(false)
@@ -129,7 +133,11 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
openRouterModels,
mcpServers,
filePaths,
- setApiConfiguration: (value) => setState((prevState) => ({ ...prevState, apiConfiguration: value })),
+ soundVolume: state.soundVolume,
+ setApiConfiguration: (value) => setState((prevState) => ({
+ ...prevState,
+ apiConfiguration: value
+ })),
setCustomInstructions: (value) => setState((prevState) => ({ ...prevState, customInstructions: value })),
setAlwaysAllowReadOnly: (value) => setState((prevState) => ({ ...prevState, alwaysAllowReadOnly: value })),
setAlwaysAllowWrite: (value) => setState((prevState) => ({ ...prevState, alwaysAllowWrite: value })),
@@ -139,7 +147,12 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
setShowAnnouncement: (value) => setState((prevState) => ({ ...prevState, shouldShowAnnouncement: value })),
setAllowedCommands: (value) => setState((prevState) => ({ ...prevState, allowedCommands: value })),
setSoundEnabled: (value) => setState((prevState) => ({ ...prevState, soundEnabled: value })),
+ setSoundVolume: (value) => setState((prevState) => ({ ...prevState, soundVolume: value })),
setDiffEnabled: (value) => setState((prevState) => ({ ...prevState, diffEnabled: value })),
+ setDebugDiffEnabled: (value) => setState((prevState) => ({
+ ...prevState,
+ debugDiffEnabled: value
+ })),
}
return {children}