mirror of
https://github.com/pacnpal/Roo-Code.git
synced 2025-12-22 21:31:08 -05:00
feat: add retry request control with delay settings
- Add requestDelaySeconds setting for configuring delay between retry attempts - Add alwaysApproveResubmit option for automatic retry approval - Add api_req_retry_delayed message type for delayed retries - Update UI components to support new retry control settings
This commit is contained in:
@@ -83,6 +83,8 @@ type GlobalStateKey =
|
||||
| "writeDelayMs"
|
||||
| "terminalOutputLineLimit"
|
||||
| "mcpEnabled"
|
||||
| "alwaysApproveResubmit"
|
||||
| "requestDelaySeconds"
|
||||
export const GlobalFileNames = {
|
||||
apiConversationHistory: "api_conversation_history.json",
|
||||
uiMessages: "ui_messages.json",
|
||||
@@ -233,7 +235,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
diffEnabled,
|
||||
fuzzyMatchThreshold
|
||||
} = await this.getState()
|
||||
|
||||
|
||||
this.cline = new Cline(
|
||||
this,
|
||||
apiConfiguration,
|
||||
@@ -253,7 +255,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
diffEnabled,
|
||||
fuzzyMatchThreshold
|
||||
} = await this.getState()
|
||||
|
||||
|
||||
this.cline = new Cline(
|
||||
this,
|
||||
apiConfiguration,
|
||||
@@ -319,15 +321,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
|
||||
// Use a nonce to only allow a specific script to be run.
|
||||
/*
|
||||
content security policy of your webview to only allow scripts that have a specific nonce
|
||||
create a content security policy meta tag so that only loading scripts with a nonce is allowed
|
||||
As your extension grows you will likely want to add custom styles, fonts, and/or images to your webview. If you do, you will need to update the content security policy meta tag to explicity allow for these resources. E.g.
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; font-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
|
||||
content security policy of your webview to only allow scripts that have a specific nonce
|
||||
create a content security policy meta tag so that only loading scripts with a nonce is allowed
|
||||
As your extension grows you will likely want to add custom styles, fonts, and/or images to your webview. If you do, you will need to update the content security policy meta tag to explicity allow for these resources. E.g.
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src ${webview.cspSource}; font-src ${webview.cspSource}; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}';">
|
||||
- 'unsafe-inline' is required for styles due to vscode-webview-toolkit's dynamic style injection
|
||||
- since we pass base64 images to the webview, we need to specify img-src ${webview.cspSource} data:;
|
||||
|
||||
in meta tag we add nonce attribute: A cryptographic nonce (only used once) to allow scripts. The server must generate a unique nonce value each time it transmits a policy. It is critical to provide a nonce that cannot be guessed as bypassing a resource's policy is otherwise trivial.
|
||||
*/
|
||||
in meta tag we add nonce attribute: A cryptographic nonce (only used once) to allow scripts. The server must generate a unique nonce value each time it transmits a policy. It is critical to provide a nonce that cannot be guessed as bypassing a resource's policy is otherwise trivial.
|
||||
*/
|
||||
const nonce = getNonce()
|
||||
|
||||
// Tip: Install the es6-string-html VS Code extension to enable code highlighting below
|
||||
@@ -555,7 +557,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.postMessageToWebview({ type: "lmStudioModels", lmStudioModels })
|
||||
break
|
||||
case "refreshGlamaModels":
|
||||
await this.refreshGlamaModels()
|
||||
await this.refreshGlamaModels()
|
||||
break
|
||||
case "refreshOpenRouterModels":
|
||||
await this.refreshOpenRouterModels()
|
||||
@@ -564,7 +566,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
if (message?.values?.baseUrl && message?.values?.apiKey) {
|
||||
const openAiModels = await this.getOpenAiModels(message?.values?.baseUrl, message?.values?.apiKey)
|
||||
this.postMessageToWebview({ type: "openAiModels", openAiModels })
|
||||
}
|
||||
}
|
||||
break
|
||||
case "openImage":
|
||||
openImage(message.text!)
|
||||
@@ -675,6 +677,14 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
await this.updateGlobalState("fuzzyMatchThreshold", message.value)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "alwaysApproveResubmit":
|
||||
await this.updateGlobalState("alwaysApproveResubmit", message.bool ?? false)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "requestDelaySeconds":
|
||||
await this.updateGlobalState("requestDelaySeconds", message.value ?? 5)
|
||||
await this.postStateToWebview()
|
||||
break
|
||||
case "preferredLanguage":
|
||||
await this.updateGlobalState("preferredLanguage", message.text)
|
||||
await this.postStateToWebview()
|
||||
@@ -1224,9 +1234,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
|
||||
async getStateToPostToWebview() {
|
||||
const {
|
||||
apiConfiguration,
|
||||
lastShownAnnouncementId,
|
||||
const {
|
||||
apiConfiguration,
|
||||
lastShownAnnouncementId,
|
||||
customInstructions,
|
||||
alwaysAllowReadOnly,
|
||||
alwaysAllowWrite,
|
||||
@@ -1244,8 +1254,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
terminalOutputLineLimit,
|
||||
fuzzyMatchThreshold,
|
||||
mcpEnabled,
|
||||
alwaysApproveResubmit,
|
||||
requestDelaySeconds,
|
||||
} = await this.getState()
|
||||
|
||||
|
||||
const allowedCommands = vscode.workspace
|
||||
.getConfiguration('roo-cline')
|
||||
.get<string[]>('allowedCommands') || []
|
||||
@@ -1276,6 +1288,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
terminalOutputLineLimit: terminalOutputLineLimit ?? 500,
|
||||
fuzzyMatchThreshold: fuzzyMatchThreshold ?? 1.0,
|
||||
mcpEnabled: mcpEnabled ?? true,
|
||||
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
||||
requestDelaySeconds: requestDelaySeconds ?? 5,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1381,6 +1395,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
screenshotQuality,
|
||||
terminalOutputLineLimit,
|
||||
mcpEnabled,
|
||||
alwaysApproveResubmit,
|
||||
requestDelaySeconds,
|
||||
] = await Promise.all([
|
||||
this.getGlobalState("apiProvider") as Promise<ApiProvider | undefined>,
|
||||
this.getGlobalState("apiModelId") as Promise<string | undefined>,
|
||||
@@ -1431,6 +1447,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
this.getGlobalState("screenshotQuality") as Promise<number | undefined>,
|
||||
this.getGlobalState("terminalOutputLineLimit") as Promise<number | undefined>,
|
||||
this.getGlobalState("mcpEnabled") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("alwaysApproveResubmit") as Promise<boolean | undefined>,
|
||||
this.getGlobalState("requestDelaySeconds") as Promise<number | undefined>,
|
||||
])
|
||||
|
||||
let apiProvider: ApiProvider
|
||||
@@ -1525,6 +1543,8 @@ export class ClineProvider implements vscode.WebviewViewProvider {
|
||||
return langMap[vscodeLang.split('-')[0]] ?? 'English';
|
||||
})(),
|
||||
mcpEnabled: mcpEnabled ?? true,
|
||||
alwaysApproveResubmit: alwaysApproveResubmit ?? false,
|
||||
requestDelaySeconds: requestDelaySeconds ?? 5,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,8 +146,8 @@ jest.mock('../../../integrations/misc/extract-text', () => ({
|
||||
|
||||
// Spy on console.error and console.log to suppress expected messages
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {})
|
||||
jest.spyOn(console, 'log').mockImplementation(() => {})
|
||||
jest.spyOn(console, 'error').mockImplementation(() => { })
|
||||
jest.spyOn(console, 'log').mockImplementation(() => { })
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
@@ -230,7 +230,7 @@ describe('ClineProvider', () => {
|
||||
|
||||
test('resolveWebviewView sets up webview correctly', () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
|
||||
|
||||
expect(mockWebviewView.webview.options).toEqual({
|
||||
enableScripts: true,
|
||||
localResourceRoots: [mockContext.extensionUri]
|
||||
@@ -240,7 +240,7 @@ describe('ClineProvider', () => {
|
||||
|
||||
test('postMessageToWebview sends message to webview', async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
|
||||
|
||||
const mockState: ExtensionState = {
|
||||
version: '1.0.0',
|
||||
preferredLanguage: 'English',
|
||||
@@ -263,14 +263,16 @@ describe('ClineProvider', () => {
|
||||
browserViewportSize: "900x600",
|
||||
fuzzyMatchThreshold: 1.0,
|
||||
mcpEnabled: true,
|
||||
alwaysApproveResubmit: false,
|
||||
requestDelaySeconds: 5,
|
||||
}
|
||||
|
||||
const message: ExtensionMessage = {
|
||||
type: 'state',
|
||||
|
||||
const message: ExtensionMessage = {
|
||||
type: 'state',
|
||||
state: mockState
|
||||
}
|
||||
await provider.postMessageToWebview(message)
|
||||
|
||||
|
||||
expect(mockPostMessage).toHaveBeenCalledWith(message)
|
||||
})
|
||||
|
||||
@@ -301,7 +303,7 @@ describe('ClineProvider', () => {
|
||||
|
||||
test('getState returns correct initial state', async () => {
|
||||
const state = await provider.getState()
|
||||
|
||||
|
||||
expect(state).toHaveProperty('apiConfiguration')
|
||||
expect(state.apiConfiguration).toHaveProperty('apiProvider')
|
||||
expect(state).toHaveProperty('customInstructions')
|
||||
@@ -318,7 +320,7 @@ describe('ClineProvider', () => {
|
||||
test('preferredLanguage defaults to VSCode language when not set', async () => {
|
||||
// Mock VSCode language as Spanish
|
||||
(vscode.env as any).language = 'es-ES';
|
||||
|
||||
|
||||
const state = await provider.getState();
|
||||
expect(state.preferredLanguage).toBe('Spanish');
|
||||
})
|
||||
@@ -326,7 +328,7 @@ describe('ClineProvider', () => {
|
||||
test('preferredLanguage defaults to English for unsupported VSCode language', async () => {
|
||||
// Mock VSCode language as an unsupported language
|
||||
(vscode.env as any).language = 'unsupported-LANG';
|
||||
|
||||
|
||||
const state = await provider.getState();
|
||||
expect(state.preferredLanguage).toBe('English');
|
||||
})
|
||||
@@ -334,9 +336,9 @@ describe('ClineProvider', () => {
|
||||
test('diffEnabled defaults to true when not set', async () => {
|
||||
// Mock globalState.get to return undefined for diffEnabled
|
||||
(mockContext.globalState.get as jest.Mock).mockReturnValue(undefined)
|
||||
|
||||
|
||||
const state = await provider.getState()
|
||||
|
||||
|
||||
expect(state.diffEnabled).toBe(true)
|
||||
})
|
||||
|
||||
@@ -348,7 +350,7 @@ describe('ClineProvider', () => {
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
|
||||
const state = await provider.getState()
|
||||
expect(state.writeDelayMs).toBe(1000)
|
||||
})
|
||||
@@ -356,9 +358,9 @@ describe('ClineProvider', () => {
|
||||
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()
|
||||
})
|
||||
@@ -382,6 +384,42 @@ describe('ClineProvider', () => {
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('requestDelaySeconds defaults to 5 seconds', async () => {
|
||||
// Mock globalState.get to return undefined for requestDelaySeconds
|
||||
(mockContext.globalState.get as jest.Mock).mockImplementation((key: string) => {
|
||||
if (key === 'requestDelaySeconds') {
|
||||
return undefined
|
||||
}
|
||||
return null
|
||||
})
|
||||
|
||||
const state = await provider.getState()
|
||||
expect(state.requestDelaySeconds).toBe(5)
|
||||
})
|
||||
|
||||
test('alwaysApproveResubmit defaults to false', async () => {
|
||||
// Mock globalState.get to return undefined for alwaysApproveResubmit
|
||||
(mockContext.globalState.get as jest.Mock).mockReturnValue(undefined)
|
||||
|
||||
const state = await provider.getState()
|
||||
expect(state.alwaysApproveResubmit).toBe(false)
|
||||
})
|
||||
|
||||
test('handles request delay settings messages', async () => {
|
||||
provider.resolveWebviewView(mockWebviewView)
|
||||
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0]
|
||||
|
||||
// Test alwaysApproveResubmit
|
||||
await messageHandler({ type: 'alwaysApproveResubmit', bool: true })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith('alwaysApproveResubmit', true)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
|
||||
// Test requestDelaySeconds
|
||||
await messageHandler({ type: 'requestDelaySeconds', value: 10 })
|
||||
expect(mockContext.globalState.update).toHaveBeenCalledWith('requestDelaySeconds', 10)
|
||||
expect(mockPostMessage).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('file content includes line numbers', async () => {
|
||||
const { extractTextFromFile } = require('../../../integrations/misc/extract-text')
|
||||
const result = await extractTextFromFile('test.js')
|
||||
|
||||
Reference in New Issue
Block a user