mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-20 04:11:10 -05:00
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
|
- Support for Meta 3, 3.1, and 3.2 models via AWS Bedrock
|
||||||
- Per-tool MCP auto-approval
|
- Per-tool MCP auto-approval
|
||||||
- Enable/disable MCP servers
|
- Enable/disable MCP servers
|
||||||
|
- Configurable delay after auto-writes to allow diagnostics to detect potential problems
|
||||||
- Runs alongside the original Cline
|
- Runs alongside the original Cline
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ type GlobalStateKey =
|
|||||||
| "browserLargeViewport"
|
| "browserLargeViewport"
|
||||||
| "fuzzyMatchThreshold"
|
| "fuzzyMatchThreshold"
|
||||||
| "preferredLanguage" // Language setting for Cline's communication
|
| "preferredLanguage" // Language setting for Cline's communication
|
||||||
|
| "writeDelayMs"
|
||||||
|
|
||||||
export const GlobalFileNames = {
|
export const GlobalFileNames = {
|
||||||
apiConversationHistory: "api_conversation_history.json",
|
apiConversationHistory: "api_conversation_history.json",
|
||||||
@@ -627,6 +628,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
await this.updateGlobalState("preferredLanguage", message.text)
|
await this.updateGlobalState("preferredLanguage", message.text)
|
||||||
await this.postStateToWebview()
|
await this.postStateToWebview()
|
||||||
break
|
break
|
||||||
|
case "writeDelayMs":
|
||||||
|
await this.updateGlobalState("writeDelayMs", message.value)
|
||||||
|
await this.postStateToWebview()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
@@ -957,6 +962,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
soundVolume,
|
soundVolume,
|
||||||
browserLargeViewport,
|
browserLargeViewport,
|
||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
|
writeDelayMs,
|
||||||
} = await this.getState()
|
} = await this.getState()
|
||||||
|
|
||||||
const allowedCommands = vscode.workspace
|
const allowedCommands = vscode.workspace
|
||||||
@@ -984,6 +990,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
soundVolume: soundVolume ?? 0.5,
|
soundVolume: soundVolume ?? 0.5,
|
||||||
browserLargeViewport: browserLargeViewport ?? false,
|
browserLargeViewport: browserLargeViewport ?? false,
|
||||||
preferredLanguage: preferredLanguage ?? 'English',
|
preferredLanguage: preferredLanguage ?? 'English',
|
||||||
|
writeDelayMs: writeDelayMs ?? 1000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1080,6 +1087,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
browserLargeViewport,
|
browserLargeViewport,
|
||||||
fuzzyMatchThreshold,
|
fuzzyMatchThreshold,
|
||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
|
writeDelayMs,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||||
this.getGlobalState("apiModelId") as Promise<string | 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("browserLargeViewport") as Promise<boolean | undefined>,
|
||||||
this.getGlobalState("fuzzyMatchThreshold") as Promise<number | undefined>,
|
this.getGlobalState("fuzzyMatchThreshold") as Promise<number | undefined>,
|
||||||
this.getGlobalState("preferredLanguage") as Promise<string | undefined>,
|
this.getGlobalState("preferredLanguage") as Promise<string | undefined>,
|
||||||
|
this.getGlobalState("writeDelayMs") as Promise<number | undefined>,
|
||||||
])
|
])
|
||||||
|
|
||||||
let apiProvider: ApiProvider
|
let apiProvider: ApiProvider
|
||||||
@@ -1179,6 +1188,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
|||||||
soundVolume,
|
soundVolume,
|
||||||
browserLargeViewport: browserLargeViewport ?? false,
|
browserLargeViewport: browserLargeViewport ?? false,
|
||||||
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
||||||
|
writeDelayMs: writeDelayMs ?? 1000,
|
||||||
preferredLanguage: preferredLanguage ?? (() => {
|
preferredLanguage: preferredLanguage ?? (() => {
|
||||||
// Get VSCode's locale setting
|
// Get VSCode's locale setting
|
||||||
const vscodeLang = vscode.env.language;
|
const vscodeLang = vscode.env.language;
|
||||||
|
|||||||
@@ -248,9 +248,13 @@ describe('ClineProvider', () => {
|
|||||||
alwaysAllowWrite: false,
|
alwaysAllowWrite: false,
|
||||||
alwaysAllowExecute: false,
|
alwaysAllowExecute: false,
|
||||||
alwaysAllowBrowser: false,
|
alwaysAllowBrowser: false,
|
||||||
|
alwaysAllowMcp: false,
|
||||||
uriScheme: 'vscode',
|
uriScheme: 'vscode',
|
||||||
soundEnabled: false,
|
soundEnabled: false,
|
||||||
diffEnabled: false,
|
diffEnabled: false,
|
||||||
|
writeDelayMs: 1000,
|
||||||
|
browserLargeViewport: false,
|
||||||
|
fuzzyMatchThreshold: 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: ExtensionMessage = {
|
const message: ExtensionMessage = {
|
||||||
@@ -300,6 +304,7 @@ describe('ClineProvider', () => {
|
|||||||
expect(state).toHaveProperty('taskHistory')
|
expect(state).toHaveProperty('taskHistory')
|
||||||
expect(state).toHaveProperty('soundEnabled')
|
expect(state).toHaveProperty('soundEnabled')
|
||||||
expect(state).toHaveProperty('diffEnabled')
|
expect(state).toHaveProperty('diffEnabled')
|
||||||
|
expect(state).toHaveProperty('writeDelayMs')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('preferredLanguage defaults to VSCode language when not set', async () => {
|
test('preferredLanguage defaults to VSCode language when not set', async () => {
|
||||||
@@ -308,7 +313,7 @@ describe('ClineProvider', () => {
|
|||||||
|
|
||||||
const state = await provider.getState();
|
const state = await provider.getState();
|
||||||
expect(state.preferredLanguage).toBe('Spanish');
|
expect(state.preferredLanguage).toBe('Spanish');
|
||||||
});
|
})
|
||||||
|
|
||||||
test('preferredLanguage defaults to English for unsupported VSCode language', async () => {
|
test('preferredLanguage defaults to English for unsupported VSCode language', async () => {
|
||||||
// Mock VSCode language as an unsupported language
|
// Mock VSCode language as an unsupported language
|
||||||
@@ -316,7 +321,7 @@ describe('ClineProvider', () => {
|
|||||||
|
|
||||||
const state = await provider.getState();
|
const state = await provider.getState();
|
||||||
expect(state.preferredLanguage).toBe('English');
|
expect(state.preferredLanguage).toBe('English');
|
||||||
});
|
})
|
||||||
|
|
||||||
test('diffEnabled defaults to true when not set', async () => {
|
test('diffEnabled defaults to true when not set', async () => {
|
||||||
// Mock globalState.get to return undefined for diffEnabled
|
// Mock globalState.get to return undefined for diffEnabled
|
||||||
@@ -327,6 +332,29 @@ describe('ClineProvider', () => {
|
|||||||
expect(state.diffEnabled).toBe(true)
|
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 () => {
|
test('updates sound utility when sound setting changes', async () => {
|
||||||
provider.resolveWebviewView(mockWebviewView)
|
provider.resolveWebviewView(mockWebviewView)
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export interface ExtensionState {
|
|||||||
browserLargeViewport?: boolean
|
browserLargeViewport?: boolean
|
||||||
fuzzyMatchThreshold?: number
|
fuzzyMatchThreshold?: number
|
||||||
preferredLanguage: string
|
preferredLanguage: string
|
||||||
|
writeDelayMs: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClineMessage {
|
export interface ClineMessage {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ export interface WebviewMessage {
|
|||||||
| "toggleMcpServer"
|
| "toggleMcpServer"
|
||||||
| "fuzzyMatchThreshold"
|
| "fuzzyMatchThreshold"
|
||||||
| "preferredLanguage"
|
| "preferredLanguage"
|
||||||
|
| "writeDelayMs"
|
||||||
text?: string
|
text?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
askResponse?: ClineAskResponse
|
askResponse?: ClineAskResponse
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ interface ChatViewProps {
|
|||||||
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
export const MAX_IMAGES_PER_MESSAGE = 20 // Anthropic limits to 20 images
|
||||||
|
|
||||||
const ChatView = ({ isHidden, showAnnouncement, hideAnnouncement, showHistoryView }: ChatViewProps) => {
|
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 = 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)
|
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
|
// Only proceed if we have an ask and buttons are enabled
|
||||||
if (!clineAsk || !enableButtons) return
|
if (!clineAsk || !enableButtons) return
|
||||||
|
|
||||||
if (isAutoApproved(lastMessage)) {
|
const autoApprove = async () => {
|
||||||
handlePrimaryButtonClick()
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
setFuzzyMatchThreshold,
|
setFuzzyMatchThreshold,
|
||||||
preferredLanguage,
|
preferredLanguage,
|
||||||
setPreferredLanguage,
|
setPreferredLanguage,
|
||||||
|
writeDelayMs,
|
||||||
|
setWriteDelayMs,
|
||||||
} = useExtensionState()
|
} = useExtensionState()
|
||||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
const [apiErrorMessage, setApiErrorMessage] = useState<string | undefined>(undefined)
|
||||||
const [modelIdErrorMessage, setModelIdErrorMessage] = 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: "browserLargeViewport", bool: browserLargeViewport })
|
||||||
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
|
vscode.postMessage({ type: "fuzzyMatchThreshold", value: fuzzyMatchThreshold ?? 1.0 })
|
||||||
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
|
vscode.postMessage({ type: "preferredLanguage", text: preferredLanguage })
|
||||||
|
vscode.postMessage({ type: "writeDelayMs", value: writeDelayMs })
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,6 +280,31 @@ const SettingsView = ({ onDone }: SettingsViewProps) => {
|
|||||||
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
|
<p style={{ fontSize: "12px", marginTop: "5px", color: "var(--vscode-descriptionForeground)" }}>
|
||||||
Automatically create and edit files without requiring approval
|
Automatically create and edit files without requiring approval
|
||||||
</p>
|
</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>
|
||||||
|
|
||||||
<div style={{ marginBottom: 5 }}>
|
<div style={{ marginBottom: 5 }}>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||||||
setFuzzyMatchThreshold: (value: number) => void
|
setFuzzyMatchThreshold: (value: number) => void
|
||||||
preferredLanguage: string
|
preferredLanguage: string
|
||||||
setPreferredLanguage: (value: string) => void
|
setPreferredLanguage: (value: string) => void
|
||||||
|
setWriteDelayMs: (value: number) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
const ExtensionStateContext = createContext<ExtensionStateContextType | undefined>(undefined)
|
||||||
@@ -51,6 +52,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
diffEnabled: false,
|
diffEnabled: false,
|
||||||
fuzzyMatchThreshold: 1.0,
|
fuzzyMatchThreshold: 1.0,
|
||||||
preferredLanguage: 'English',
|
preferredLanguage: 'English',
|
||||||
|
writeDelayMs: 1000,
|
||||||
})
|
})
|
||||||
const [didHydrateState, setDidHydrateState] = useState(false)
|
const [didHydrateState, setDidHydrateState] = useState(false)
|
||||||
const [showWelcome, setShowWelcome] = useState(false)
|
const [showWelcome, setShowWelcome] = useState(false)
|
||||||
@@ -139,6 +141,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
filePaths,
|
filePaths,
|
||||||
soundVolume: state.soundVolume,
|
soundVolume: state.soundVolume,
|
||||||
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
|
fuzzyMatchThreshold: state.fuzzyMatchThreshold,
|
||||||
|
writeDelayMs: state.writeDelayMs,
|
||||||
setApiConfiguration: (value) => setState((prevState) => ({
|
setApiConfiguration: (value) => setState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
apiConfiguration: value
|
apiConfiguration: value
|
||||||
@@ -157,6 +160,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||||||
setBrowserLargeViewport: (value) => setState((prevState) => ({ ...prevState, browserLargeViewport: value })),
|
setBrowserLargeViewport: (value) => setState((prevState) => ({ ...prevState, browserLargeViewport: value })),
|
||||||
setFuzzyMatchThreshold: (value) => setState((prevState) => ({ ...prevState, fuzzyMatchThreshold: value })),
|
setFuzzyMatchThreshold: (value) => setState((prevState) => ({ ...prevState, fuzzyMatchThreshold: value })),
|
||||||
setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })),
|
setPreferredLanguage: (value) => setState((prevState) => ({ ...prevState, preferredLanguage: value })),
|
||||||
|
setWriteDelayMs: (value) => setState((prevState) => ({ ...prevState, writeDelayMs: value })),
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
return <ExtensionStateContext.Provider value={contextValue}>{children}</ExtensionStateContext.Provider>
|
||||||
|
|||||||
Reference in New Issue
Block a user