mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 12:21:13 -05:00
Merge pull request #220 from RooVetGit/diagnostics_delay
Add configurable delay after auto-writes to allow diagnostics to catch up
This commit is contained in:
5
.changeset/selfish-eyes-speak.md
Normal file
5
.changeset/selfish-eyes-speak.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"roo-cline": patch
|
||||
---
|
||||
|
||||
Add configurable delay after auto-writes to allow diagnostics to catch up
|
||||
@@ -15,6 +15,7 @@ A fork of Cline, an autonomous coding agent, with some additional experimental f
|
||||
- Support for Meta 3, 3.1, and 3.2 models via AWS Bedrock
|
||||
- Per-tool MCP auto-approval
|
||||
- Enable/disable MCP servers
|
||||
- Configurable delay after auto-writes to allow diagnostics to detect potential problems
|
||||
- Runs alongside the original Cline
|
||||
|
||||
## Disclaimer
|
||||
|
||||
@@ -72,6 +72,7 @@ type GlobalStateKey =
|
||||
| "browserLargeViewport"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "preferredLanguage" // Language setting for Cline's communication
|
||||
| "writeDelayMs"
|
||||
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
@@ -627,6 +628,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
await this.updateGlobalState("preferredLanguage", message.text)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "writeDelayMs":
|
||||
await this.updateGlobalState("writeDelayMs", message.value)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
}
|
||||
},
|
||||
null,
|
||||
@@ -957,6 +962,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
soundVolume,
|
||||
browserLargeViewport,
|
||||
preferredLanguage,
|
||||
writeDelayMs,
|
||||
} = await this.getState()
|
||||
|
||||
const allowedCommands = vscode.workspace
|
||||
@@ -984,6 +990,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
soundVolume: soundVolume ?? 0.5,
|
||||
browserLargeViewport: browserLargeViewport ?? false,
|
||||
preferredLanguage: preferredLanguage ?? 'English',
|
||||
writeDelayMs: writeDelayMs ?? 1000,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1080,6 +1087,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
browserLargeViewport,
|
||||
fuzzyMatchThreshold,
|
||||
preferredLanguage,
|
||||
writeDelayMs,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
@@ -1121,6 +1129,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.getGlobalState("browserLargeViewport") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("fuzzyMatchThreshold") as Promise<number | undefined>,
|
||||
this.getGlobalState("preferredLanguage") as Promise<string | undefined>,
|
||||
this.getGlobalState("writeDelayMs") as Promise<number | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -1179,6 +1188,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
soundVolume,
|
||||
browserLargeViewport: browserLargeViewport ?? false,
|
||||
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
||||
writeDelayMs: writeDelayMs ?? 1000,
|
||||
preferredLanguage: preferredLanguage ?? (() => {
|
||||
// Get VSCode's locale setting
|
||||
const vscodeLang = vscode.env.language;
|
||||
|
||||
@@ -248,9 +248,13 @@ describe('ClineProvider', () => {
|
||||
alwaysAllowWrite: false,
|
||||
alwaysAllowExecute: false,
|
||||
alwaysAllowBrowser: false,
|
||||
alwaysAllowMcp: false,
|
||||
uriScheme: 'vscode',
|
||||
soundEnabled: false,
|
||||
diffEnabled: false,
|
||||
writeDelayMs: 1000,
|
||||
browserLargeViewport: false,
|
||||
fuzzyMatchThreshold: 1.0,
|
||||
}
|
||||
|
||||
const message: ExtensionMessage = {
|
||||
@@ -300,6 +304,7 @@ describe('ClineProvider', () => {
|
||||
expect(state).toHaveProperty('taskHistory')
|
||||
expect(state).toHaveProperty('soundEnabled')
|
||||
expect(state).toHaveProperty('diffEnabled')
|
||||
expect(state).toHaveProperty('writeDelayMs')
|
||||
})
|
||||
|
||||
test('preferredLanguage defaults to VSCode language when not set', async () => {
|
||||
@@ -308,7 +313,7 @@ describe('ClineProvider', () => {
|
||||
|
||||
const state = await provider.getState();
|
||||
expect(state.preferredLanguage).toBe('Spanish');
|
||||
});
|
||||
})
|
||||
|
||||
test('preferredLanguage defaults to English for unsupported VSCode language', async () => {
|
||||
// Mock VSCode language as an unsupported language
|
||||
@@ -316,7 +321,7 @@ describe('ClineProvider', () => {
|
||||
|
||||
const state = await provider.getState();
|
||||
expect(state.preferredLanguage).toBe('English');
|
||||
});
|
||||
})
|
||||
|
||||
test('diffEnabled defaults to true when not set', async () => {
|
||||
// Mock globalState.get to return undefined for diffEnabled
|
||||
@@ -327,6 +332,29 @@ describe('ClineProvider', () => {
|
||||
expect(state.diffEnabled).toBe(true)
|
||||
})
|
||||
|
||||
test('writeDelayMs defaults to 1000ms', async () => {
|
||||
// Mock globalState.get to return undefined for writeDelayMs
|
||||
(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
|
||||
if (key === 'writeDelayMs') {
|
||||
return undefined
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const state = await provider.getState()
|
||||
expect(state.writeDelayMs).toBe(1000)
|
||||
})
|
||||
|
||||
test('handles writeDelayMs message', async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
|
||||
|
||||
await messageHandler({ type: 'writeDelayMs', value: 2000 })
|
||||
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith('writeDelayMs', 2000)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('updates sound utility when sound setting changes', async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface ExtensionState {
|
||||
browserLargeViewport?: boolean
|
||||
fuzzyMatchThreshold?: number
|
||||
preferredLanguage: string
|
||||
writeDelayMs: number
|
||||
}
|
||||
|
||||
export interface ClineMessage {
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface WebviewMessage {
|
||||
| "toggleMcpServer"
|
||||
| "fuzzyMatchThreshold"
|
||||
| "preferredLanguage"
|
||||
| "writeDelayMs"
|
||||
text?: string
|
||||
disabled?: boolean
|
||||
askResponse?: ClineAskResponse
|
||||
|
||||
@@ -37,7 +37,7 @@ interface ChatViewProps {
|
||||
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
||||
|
||||
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
|
||||
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands } = useExtensionState()
|
||||
const { version, clineMessages: messages, taskHistory, apiConfiguration, mcpServers, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, allowedCommands, writeDelayMs } = useExtensionState()
|
||||
|
||||
//const task = messages.length > 0 ? (messages[0].say === "task" ? messages[0] : undefined) : undefined) : undefined
|
||||
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)
|
||||
@@ -831,10 +831,17 @@ const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryVie
|
||||
// Only proceed if we have an ask and buttons are enabled
|
||||
if (!clineAsk || !enableButtons) return
|
||||
|
||||
const autoApprove = async () => {
|
||||
if (isAutoApproved(lastMessage)) {
|
||||
// Add delay for write operations
|
||||
if (alwaysAllowWrite && isWriteToolAction(lastMessage)) {
|
||||
await new Promise(resolve => setTimeout(resolve, writeDelayMs))
|
||||
}
|
||||
handlePrimaryButtonClick()
|
||||
}
|
||||
}, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers, isAutoApproved, lastMessage])
|
||||
}
|
||||
autoApprove()
|
||||
}, [clineAsk, enableButtons, handlePrimaryButtonClick, alwaysAllowBrowser, alwaysAllowReadOnly, alwaysAllowWrite, alwaysAllowExecute, alwaysAllowMcp, messages, allowedCommands, mcpServers, isAutoApproved, lastMessage, writeDelayMs, isWriteToolAction])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -42,6 +42,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
setFuzzyMatchThreshold,
|
||||
preferredLanguage,
|
||||
setPreferredLanguage,
|
||||
writeDelayMs,
|
||||
setWriteDelayMs,
|
||||
} = useExtensionState()
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [modelIdErrorMessage, setModelIdErrorMessage] = useState<string | undefined>(undefined)
|
||||
@@ -70,6 +72,7 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
vscode.postMessage({ type: "browserLargeViewport", bool: browserLargeViewport })
|
||||
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
|
||||
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
|
||||
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
@@ -277,6 +280,31 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
||||
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
Automatically create and edit files without requiring approval
|
||||
</p>
|
||||
{alwaysAllowWrite && (
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="5000"
|
||||
step="100"
|
||||
value={writeDelayMs}
|
||||
onChange={(e) => setWriteDelayMs(parseInt(e.target.value))}
|
||||
style={{
|
||||
flex: 1,
|
||||
accentColor: 'var(--vscode-button-background)',
|
||||
height: '2px'
|
||||
}}
|
||||
/>
|
||||
<span style={{ minWidth: '45px', textAlign: 'left' }}>
|
||||
{writeDelayMs}ms
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
|
||||
Delay after writes to allow diagnostics to detect potential problems
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 5 }}>
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
||||
setFuzzyMatchThreshold: (value: number) => void
|
||||
preferredLanguage: string
|
||||
setPreferredLanguage: (value: string) => void
|
||||
setWriteDelayMs: (value: number) => void
|
||||
}
|
||||
|
||||
const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||
@@ -51,6 +52,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
diffEnabled: false,
|
||||
fuzzyMatchThreshold: 1.0,
|
||||
preferredLanguage: 'English',
|
||||
writeDelayMs: 1000,
|
||||
})
|
||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||
const [showWelcome, setShowWelcome] = useState(false)
|
||||
@@ -139,6 +141,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
filePaths,
|
||||
soundVolume: state.soundVolume,
|
||||
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
|
||||
writeDelayMs: state.writeDelayMs,
|
||||
setApiConfiguration: (value) => setState((prevState) => ({
|
||||
...prevState,
|
||||
apiConfiguration: value
|
||||
@@ -157,6 +160,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
||||
setBrowserLargeViewport: (value) => setState((prevState) => ({ ...prevState, browserLargeViewport: value })),
|
||||
setFuzzyMatchThreshold: (value) => setState((prevState) => ({ ...prevState, fuzzyMatchThreshold: value })),
|
||||
setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })),
|
||||
setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })),
|
||||
}
|
||||
|
||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||
|
||||
Reference in New Issue
Block a user