Skip to content

Commit 62d8cc0

Browse files
cteroomote
andauthored
Batch settings updates from the webview to the extension host (#9165)
Co-authored-by: Roo Code <[email protected]>
1 parent e70c175 commit 62d8cc0

20 files changed

+437
-693
lines changed

packages/cloud/src/WebAuthService.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,13 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> implements A
704704
signal: AbortSignal.timeout(10000),
705705
})
706706

707-
return clerkOrganizationMembershipsSchema.parse(await response.json()).response
707+
if (response.ok) {
708+
return clerkOrganizationMembershipsSchema.parse(await response.json()).response
709+
}
710+
711+
const errorMessage = `Failed to get organization memberships: ${response.status} ${response.statusText}`
712+
this.log(`[auth] ${errorMessage}`)
713+
throw new Error(errorMessage)
708714
}
709715

710716
private async getOrganizationMetadata(

src/core/webview/ClineProvider.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,10 @@ export class ClineProvider
145145
private pendingOperations: Map<string, PendingEditOperation> = new Map()
146146
private static readonly PENDING_OPERATION_TIMEOUT_MS = 30000 // 30 seconds
147147

148+
private cloudOrganizationsCache: CloudOrganizationMembership[] | null = null
149+
private cloudOrganizationsCacheTimestamp: number | null = null
150+
private static readonly CLOUD_ORGANIZATIONS_CACHE_DURATION_MS = 5 * 1000 // 5 seconds
151+
148152
public isViewLaunched = false
149153
public settingsImportedAt?: number
150154
public readonly latestAnnouncementId = "nov-2025-v3.30.0-pr-fixer" // v3.30.0 PR Fixer announcement
@@ -1919,7 +1923,19 @@ export class ClineProvider
19191923

19201924
try {
19211925
if (!CloudService.instance.isCloudAgent) {
1922-
cloudOrganizations = await CloudService.instance.getOrganizationMemberships()
1926+
const now = Date.now()
1927+
1928+
if (
1929+
this.cloudOrganizationsCache !== null &&
1930+
this.cloudOrganizationsCacheTimestamp !== null &&
1931+
now - this.cloudOrganizationsCacheTimestamp < ClineProvider.CLOUD_ORGANIZATIONS_CACHE_DURATION_MS
1932+
) {
1933+
cloudOrganizations = this.cloudOrganizationsCache!
1934+
} else {
1935+
cloudOrganizations = await CloudService.instance.getOrganizationMemberships()
1936+
this.cloudOrganizationsCache = cloudOrganizations
1937+
this.cloudOrganizationsCacheTimestamp = now
1938+
}
19231939
}
19241940
} catch (error) {
19251941
// Ignore this error.

src/core/webview/__tests__/ClineProvider.spec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// npx vitest core/webview/__tests__/ClineProvider.spec.ts
1+
// pnpm --filter roo-cline test core/webview/__tests__/ClineProvider.spec.ts
22

33
import Anthropic from "@anthropic-ai/sdk"
44
import * as vscode from "vscode"
@@ -786,7 +786,7 @@ describe("ClineProvider", () => {
786786
await provider.resolveWebviewView(mockWebviewView)
787787
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
788788

789-
await messageHandler({ type: "writeDelayMs", value: 2000 })
789+
await messageHandler({ type: "updateSettings", updatedSettings: { writeDelayMs: 2000 } })
790790

791791
expect(updateGlobalStateSpy).toHaveBeenCalledWith("writeDelayMs", 2000)
792792
expect(mockContext.globalState.update).toHaveBeenCalledWith("writeDelayMs", 2000)
@@ -800,24 +800,24 @@ describe("ClineProvider", () => {
800800
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
801801

802802
// Simulate setting sound to enabled
803-
await messageHandler({ type: "soundEnabled", bool: true })
803+
await messageHandler({ type: "updateSettings", updatedSettings: { soundEnabled: true } })
804804
expect(updateGlobalStateSpy).toHaveBeenCalledWith("soundEnabled", true)
805805
expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", true)
806806
expect(mockPostMessage).toHaveBeenCalled()
807807

808808
// Simulate setting sound to disabled
809-
await messageHandler({ type: "soundEnabled", bool: false })
809+
await messageHandler({ type: "updateSettings", updatedSettings: { soundEnabled: false } })
810810
expect(mockContext.globalState.update).toHaveBeenCalledWith("soundEnabled", false)
811811
expect(mockPostMessage).toHaveBeenCalled()
812812

813813
// Simulate setting tts to enabled
814-
await messageHandler({ type: "ttsEnabled", bool: true })
814+
await messageHandler({ type: "updateSettings", updatedSettings: { ttsEnabled: true } })
815815
expect(setTtsEnabled).toHaveBeenCalledWith(true)
816816
expect(mockContext.globalState.update).toHaveBeenCalledWith("ttsEnabled", true)
817817
expect(mockPostMessage).toHaveBeenCalled()
818818

819819
// Simulate setting tts to disabled
820-
await messageHandler({ type: "ttsEnabled", bool: false })
820+
await messageHandler({ type: "updateSettings", updatedSettings: { ttsEnabled: false } })
821821
expect(setTtsEnabled).toHaveBeenCalledWith(false)
822822
expect(mockContext.globalState.update).toHaveBeenCalledWith("ttsEnabled", false)
823823
expect(mockPostMessage).toHaveBeenCalled()
@@ -856,7 +856,7 @@ describe("ClineProvider", () => {
856856
test("handles autoCondenseContext message", async () => {
857857
await provider.resolveWebviewView(mockWebviewView)
858858
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
859-
await messageHandler({ type: "autoCondenseContext", bool: false })
859+
await messageHandler({ type: "updateSettings", updatedSettings: { autoCondenseContext: false } })
860860
expect(updateGlobalStateSpy).toHaveBeenCalledWith("autoCondenseContext", false)
861861
expect(mockContext.globalState.update).toHaveBeenCalledWith("autoCondenseContext", false)
862862
expect(mockPostMessage).toHaveBeenCalled()
@@ -876,7 +876,7 @@ describe("ClineProvider", () => {
876876
await provider.resolveWebviewView(mockWebviewView)
877877
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
878878

879-
await messageHandler({ type: "autoCondenseContextPercent", value: 75 })
879+
await messageHandler({ type: "updateSettings", updatedSettings: { autoCondenseContextPercent: 75 } })
880880

881881
expect(updateGlobalStateSpy).toHaveBeenCalledWith("autoCondenseContextPercent", 75)
882882
expect(mockContext.globalState.update).toHaveBeenCalledWith("autoCondenseContextPercent", 75)
@@ -984,7 +984,7 @@ describe("ClineProvider", () => {
984984
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
985985

986986
// Test browserToolEnabled
987-
await messageHandler({ type: "browserToolEnabled", bool: true })
987+
await messageHandler({ type: "updateSettings", updatedSettings: { browserToolEnabled: true } })
988988
expect(mockContext.globalState.update).toHaveBeenCalledWith("browserToolEnabled", true)
989989
expect(mockPostMessage).toHaveBeenCalled()
990990

@@ -1002,13 +1002,13 @@ describe("ClineProvider", () => {
10021002
expect((await provider.getState()).showRooIgnoredFiles).toBe(false)
10031003

10041004
// Test showRooIgnoredFiles with true
1005-
await messageHandler({ type: "showRooIgnoredFiles", bool: true })
1005+
await messageHandler({ type: "updateSettings", updatedSettings: { showRooIgnoredFiles: true } })
10061006
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", true)
10071007
expect(mockPostMessage).toHaveBeenCalled()
10081008
expect((await provider.getState()).showRooIgnoredFiles).toBe(true)
10091009

10101010
// Test showRooIgnoredFiles with false
1011-
await messageHandler({ type: "showRooIgnoredFiles", bool: false })
1011+
await messageHandler({ type: "updateSettings", updatedSettings: { showRooIgnoredFiles: false } })
10121012
expect(mockContext.globalState.update).toHaveBeenCalledWith("showRooIgnoredFiles", false)
10131013
expect(mockPostMessage).toHaveBeenCalled()
10141014
expect((await provider.getState()).showRooIgnoredFiles).toBe(false)
@@ -1019,13 +1019,13 @@ describe("ClineProvider", () => {
10191019
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
10201020

10211021
// Test alwaysApproveResubmit
1022-
await messageHandler({ type: "alwaysApproveResubmit", bool: true })
1022+
await messageHandler({ type: "updateSettings", updatedSettings: { alwaysApproveResubmit: true } })
10231023
expect(updateGlobalStateSpy).toHaveBeenCalledWith("alwaysApproveResubmit", true)
10241024
expect(mockContext.globalState.update).toHaveBeenCalledWith("alwaysApproveResubmit", true)
10251025
expect(mockPostMessage).toHaveBeenCalled()
10261026

10271027
// Test requestDelaySeconds
1028-
await messageHandler({ type: "requestDelaySeconds", value: 10 })
1028+
await messageHandler({ type: "updateSettings", updatedSettings: { requestDelaySeconds: 10 } })
10291029
expect(mockContext.globalState.update).toHaveBeenCalledWith("requestDelaySeconds", 10)
10301030
expect(mockPostMessage).toHaveBeenCalled()
10311031
})
@@ -1092,7 +1092,7 @@ describe("ClineProvider", () => {
10921092
await provider.resolveWebviewView(mockWebviewView)
10931093
const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as any).mock.calls[0][0]
10941094

1095-
await messageHandler({ type: "maxWorkspaceFiles", value: 300 })
1095+
await messageHandler({ type: "updateSettings", updatedSettings: { maxWorkspaceFiles: 300 } })
10961096

10971097
expect(updateGlobalStateSpy).toHaveBeenCalledWith("maxWorkspaceFiles", 300)
10981098
expect(mockContext.globalState.update).toHaveBeenCalledWith("maxWorkspaceFiles", 300)

src/core/webview/__tests__/webviewMessageHandler.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -721,8 +721,8 @@ describe("webviewMessageHandler - mcpEnabled", () => {
721721

722722
it("delegates enable=true to McpHub and posts updated state", async () => {
723723
await webviewMessageHandler(mockClineProvider, {
724-
type: "mcpEnabled",
725-
bool: true,
724+
type: "updateSettings",
725+
updatedSettings: { mcpEnabled: true },
726726
})
727727

728728
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
@@ -733,8 +733,8 @@ describe("webviewMessageHandler - mcpEnabled", () => {
733733

734734
it("delegates enable=false to McpHub and posts updated state", async () => {
735735
await webviewMessageHandler(mockClineProvider, {
736-
type: "mcpEnabled",
737-
bool: false,
736+
type: "updateSettings",
737+
updatedSettings: { mcpEnabled: false },
738738
})
739739

740740
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
@@ -747,8 +747,8 @@ describe("webviewMessageHandler - mcpEnabled", () => {
747747
;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(undefined)
748748

749749
await webviewMessageHandler(mockClineProvider, {
750-
type: "mcpEnabled",
751-
bool: true,
750+
type: "updateSettings",
751+
updatedSettings: { mcpEnabled: true },
752752
})
753753

754754
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)

0 commit comments

Comments
 (0)