Skip to content

Commit b65c8d0

Browse files
committed
Customize for Roo
1 parent 4b4905e commit b65c8d0

File tree

9 files changed

+842
-133
lines changed

9 files changed

+842
-133
lines changed

.changeset/large-humans-exist.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Add auto-approve menu (thanks Cline!)

src/shared/ExtensionMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export interface ExtensionState {
9393
mode: Mode
9494
modeApiConfigs?: Record<Mode, string>
9595
enhancementApiConfigId?: string
96+
autoApprovalEnabled?: boolean
9697
}
9798

9899
export interface ClineMessage {
Lines changed: 96 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,110 @@
1-
import { VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react"
1+
import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react"
22
import { useCallback, useState } from "react"
3-
import styled from "styled-components"
3+
import { useExtensionState } from "../../context/ExtensionStateContext"
44

55
interface AutoApproveAction {
66
id: string
77
label: string
88
enabled: boolean
9+
shortName: string
910
description: string
1011
}
1112

1213
interface AutoApproveMenuProps {
1314
style?: React.CSSProperties
1415
}
1516

16-
const DEFAULT_MAX_REQUESTS = 50
17-
1817
const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
1918
const [isExpanded, setIsExpanded] = useState(false)
20-
const [actions, setActions] = useState<AutoApproveAction[]>([
19+
const {
20+
alwaysAllowReadOnly,
21+
setAlwaysAllowReadOnly,
22+
alwaysAllowWrite,
23+
setAlwaysAllowWrite,
24+
alwaysAllowExecute,
25+
setAlwaysAllowExecute,
26+
alwaysAllowBrowser,
27+
setAlwaysAllowBrowser,
28+
alwaysAllowMcp,
29+
setAlwaysAllowMcp,
30+
alwaysApproveResubmit,
31+
setAlwaysApproveResubmit,
32+
autoApprovalEnabled,
33+
setAutoApprovalEnabled,
34+
} = useExtensionState()
35+
36+
const actions: AutoApproveAction[] = [
2137
{
2238
id: "readFiles",
2339
label: "Read files and directories",
24-
enabled: false,
40+
shortName: "Read",
41+
enabled: alwaysAllowReadOnly ?? false,
2542
description: "Allows access to read any file on your computer.",
2643
},
2744
{
2845
id: "editFiles",
2946
label: "Edit files",
30-
enabled: false,
47+
shortName: "Edit",
48+
enabled: alwaysAllowWrite ?? false,
3149
description: "Allows modification of any files on your computer.",
3250
},
3351
{
3452
id: "executeCommands",
3553
label: "Execute safe commands",
36-
enabled: false,
54+
shortName: "Commands",
55+
enabled: alwaysAllowExecute ?? false,
3756
description:
38-
"Allows automatic execution of safe terminal commands. The model will determine if a command is potentially destructive and ask for explicit approval.",
57+
"Allows execution of approved terminal commands. You can configure this in the settings panel.",
3958
},
4059
{
4160
id: "useBrowser",
4261
label: "Use the browser",
43-
enabled: false,
62+
shortName: "Browser",
63+
enabled: alwaysAllowBrowser ?? false,
4464
description: "Allows ability to launch and interact with any website in a headless browser.",
4565
},
4666
{
4767
id: "useMcp",
4868
label: "Use MCP servers",
49-
enabled: false,
69+
shortName: "MCP",
70+
enabled: alwaysAllowMcp ?? false,
5071
description: "Allows use of configured MCP servers which may modify filesystem or interact with APIs.",
5172
},
52-
])
53-
const [maxRequests, setMaxRequests] = useState(DEFAULT_MAX_REQUESTS)
54-
const [enableNotifications, setEnableNotifications] = useState(false)
73+
{
74+
id: "retryRequests",
75+
label: "Retry failed requests",
76+
shortName: "Retries",
77+
enabled: alwaysApproveResubmit ?? false,
78+
description: "Automatically retry failed API requests when the provider returns an error response.",
79+
},
80+
]
5581

5682
const toggleExpanded = useCallback(() => {
5783
setIsExpanded((prev) => !prev)
5884
}, [])
5985

60-
const toggleAction = useCallback((actionId: string) => {
61-
setActions((prev) =>
62-
prev.map((action) => (action.id === actionId ? { ...action, enabled: !action.enabled } : action)),
63-
)
64-
}, [])
86+
const enabledActionsList = actions
87+
.filter((action) => action.enabled)
88+
.map((action) => action.shortName)
89+
.join(", ")
6590

66-
const enabledActions = actions.filter((action) => action.enabled)
67-
const enabledActionsList = enabledActions.map((action) => action.label).join(", ")
91+
// Individual checkbox handlers - each one only updates its own state
92+
const handleReadOnlyChange = useCallback(() => setAlwaysAllowReadOnly(!(alwaysAllowReadOnly ?? false)), [alwaysAllowReadOnly, setAlwaysAllowReadOnly])
93+
const handleWriteChange = useCallback(() => setAlwaysAllowWrite(!(alwaysAllowWrite ?? false)), [alwaysAllowWrite, setAlwaysAllowWrite])
94+
const handleExecuteChange = useCallback(() => setAlwaysAllowExecute(!(alwaysAllowExecute ?? false)), [alwaysAllowExecute, setAlwaysAllowExecute])
95+
const handleBrowserChange = useCallback(() => setAlwaysAllowBrowser(!(alwaysAllowBrowser ?? false)), [alwaysAllowBrowser, setAlwaysAllowBrowser])
96+
const handleMcpChange = useCallback(() => setAlwaysAllowMcp(!(alwaysAllowMcp ?? false)), [alwaysAllowMcp, setAlwaysAllowMcp])
97+
const handleRetryChange = useCallback(() => setAlwaysApproveResubmit(!(alwaysApproveResubmit ?? false)), [alwaysApproveResubmit, setAlwaysApproveResubmit])
98+
99+
// Map action IDs to their specific handlers
100+
const actionHandlers: Record<AutoApproveAction['id'], () => void> = {
101+
readFiles: handleReadOnlyChange,
102+
editFiles: handleWriteChange,
103+
executeCommands: handleExecuteChange,
104+
useBrowser: handleBrowserChange,
105+
useMcp: handleMcpChange,
106+
retryRequests: handleRetryChange,
107+
}
68108

69109
return (
70110
<div
@@ -86,39 +126,41 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
86126
cursor: "pointer",
87127
}}
88128
onClick={toggleExpanded}>
89-
<VSCodeCheckbox
90-
checked={enabledActions.length > 0}
91-
onChange={(e) => {
92-
const checked = (e.target as HTMLInputElement).checked
93-
setActions((prev) =>
94-
prev.map((action) => ({
95-
...action,
96-
enabled: checked,
97-
})),
98-
)
99-
e.stopPropagation()
100-
}}
101-
onClick={(e) => e.stopPropagation()}
102-
/>
103-
<CollapsibleSection>
104-
<span style={{ color: "var(--vscode-foreground)" }}>Auto-approve:</span>
105-
<span
106-
style={{
107-
whiteSpace: "nowrap",
108-
overflow: "hidden",
109-
textOverflow: "ellipsis",
110-
}}>
111-
{enabledActions.length === 0 ? "None" : enabledActionsList}
129+
<div onClick={(e) => e.stopPropagation()}>
130+
<VSCodeCheckbox
131+
checked={autoApprovalEnabled ?? false}
132+
onChange={() => setAutoApprovalEnabled(!(autoApprovalEnabled ?? false))}
133+
/>
134+
</div>
135+
<div style={{
136+
display: 'flex',
137+
alignItems: 'center',
138+
gap: '4px',
139+
flex: 1,
140+
minWidth: 0
141+
}}>
142+
<span style={{
143+
color: "var(--vscode-foreground)",
144+
flexShrink: 0
145+
}}>Auto-approve:</span>
146+
<span style={{
147+
color: "var(--vscode-descriptionForeground)",
148+
overflow: "hidden",
149+
textOverflow: "ellipsis",
150+
whiteSpace: "nowrap",
151+
flex: 1,
152+
minWidth: 0
153+
}}>
154+
{enabledActionsList || "None"}
112155
</span>
113156
<span
114157
className={`codicon codicon-chevron-${isExpanded ? "down" : "right"}`}
115158
style={{
116-
// fontSize: "14px",
117159
flexShrink: 0,
118160
marginLeft: isExpanded ? "2px" : "-2px",
119161
}}
120162
/>
121-
</CollapsibleSection>
163+
</div>
122164
</div>
123165
{isExpanded && (
124166
<div style={{ padding: "0" }}>
@@ -129,13 +171,17 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
129171
fontSize: "12px",
130172
}}>
131173
Auto-approve allows Cline to perform actions without asking for permission. Only enable for
132-
actions you fully trust, and consider setting a low request limit as a safeguard.
174+
actions you fully trust.
133175
</div>
134176
{actions.map((action) => (
135177
<div key={action.id} style={{ margin: "6px 0" }}>
136-
<VSCodeCheckbox checked={action.enabled} onChange={() => toggleAction(action.id)}>
137-
{action.label}
138-
</VSCodeCheckbox>
178+
<div onClick={(e) => e.stopPropagation()}>
179+
<VSCodeCheckbox
180+
checked={action.enabled}
181+
onChange={actionHandlers[action.id]}>
182+
{action.label}
183+
</VSCodeCheckbox>
184+
</div>
139185
<div
140186
style={{
141187
marginLeft: "28px",
@@ -146,76 +192,10 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
146192
</div>
147193
</div>
148194
))}
149-
<div
150-
style={{
151-
height: "0.5px",
152-
background: "var(--vscode-titleBar-inactiveForeground)",
153-
margin: "15px 0",
154-
opacity: 0.2,
155-
}}
156-
/>
157-
<div
158-
style={{
159-
display: "flex",
160-
alignItems: "center",
161-
gap: "8px",
162-
marginTop: "10px",
163-
marginBottom: "8px",
164-
color: "var(--vscode-foreground)",
165-
}}>
166-
<span style={{ flexShrink: 1, minWidth: 0 }}>Max Requests:</span>
167-
<VSCodeTextField
168-
value={maxRequests.toString()}
169-
onChange={(e) => {
170-
const value = parseInt((e.target as HTMLInputElement).value)
171-
if (!isNaN(value) && value > 0) {
172-
setMaxRequests(value)
173-
}
174-
}}
175-
style={{ flex: 1 }}
176-
/>
177-
</div>
178-
<div
179-
style={{
180-
color: "var(--vscode-descriptionForeground)",
181-
fontSize: "12px",
182-
marginBottom: "10px",
183-
}}>
184-
Cline will make this many API requests before asking for approval to proceed with the task.
185-
</div>
186-
<div style={{ margin: "6px 0" }}>
187-
<VSCodeCheckbox
188-
checked={enableNotifications}
189-
onChange={() => setEnableNotifications((prev) => !prev)}>
190-
Enable Notifications
191-
</VSCodeCheckbox>
192-
<div
193-
style={{
194-
marginLeft: "28px",
195-
color: "var(--vscode-descriptionForeground)",
196-
fontSize: "12px",
197-
}}>
198-
Receive system notifications when Cline requires approval to proceed or when a task is
199-
completed.
200-
</div>
201-
</div>
202195
</div>
203196
)}
204197
</div>
205198
)
206199
}
207200

208-
const CollapsibleSection = styled.div`
209-
display: flex;
210-
align-items: center;
211-
gap: 4px;
212-
color: var(--vscode-descriptionForeground);
213-
flex: 1;
214-
min-width: 0;
215-
216-
&:hover {
217-
color: var(--vscode-foreground);
218-
}
219-
`
220-
221201
export default AutoApproveMenu

webview-ui/src/components/chat/ChatTextArea.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,6 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
527527
flexDirection: "column",
528528
gap: "8px",
529529
backgroundColor: "var(--vscode-input-background)",
530-
minHeight: "100px",
531530
margin: "10px 15px",
532531
padding: "8px"
533532
}}
@@ -652,7 +651,7 @@ const ChatTextArea = forwardRef<HTMLTextAreaElement, ChatTextAreaProps>(
652651
onHeightChange?.(height)
653652
}}
654653
placeholder={placeholderText}
655-
minRows={4}
654+
minRows={2}
656655
maxRows={20}
657656
autoFocus={true}
658657
style={{

webview-ui/src/components/chat/ChatView.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ interface ChatViewProps {
3939
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
4040

4141
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
42-
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs, mode, setMode } = useExtensionState()
42+
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs, mode, setMode, autoApprovalEnabled } = useExtensionState()
4343

4444
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
4545
const task = useMemo(() => messages.at(0), [messages]) // leaving this less safe version here since if the first message is not a task, then the extension is in a bad state and needs to be debugged (see Cline.abort)
@@ -529,7 +529,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
529529

530530
const isAutoApproved = useCallback(
531531
(message: ClineMessage | undefined) => {
532-
if (!message || message.type !== "ask") return false
532+
if (!autoApprovalEnabled || !message || message.type !== "ask") return false
533533

534534
return (
535535
(alwaysAllowBrowser && message.ask === "browser_action_launch") ||
@@ -539,17 +539,7 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
539539
(alwaysAllowMcp && message.ask === "use_mcp_server" && isMcpToolAlwaysAllowed(message))
540540
)
541541
},
542-
[
543-
alwaysAllowBrowser,
544-
alwaysAllowReadOnly,
545-
alwaysAllowWrite,
546-
alwaysAllowExecute,
547-
alwaysAllowMcp,
548-
isReadOnlyToolAction,
549-
isWriteToolAction,
550-
isAllowedCommand,
551-
isMcpToolAlwaysAllowed
552-
]
542+
[autoApprovalEnabled, alwaysAllowBrowser, alwaysAllowReadOnly, isReadOnlyToolAction, alwaysAllowWrite, isWriteToolAction, alwaysAllowExecute, isAllowedCommand, alwaysAllowMcp, isMcpToolAlwaysAllowed]
553543
)
554544

555545
useEffect(() => {

0 commit comments

Comments
 (0)