Skip to content

Commit 97afc41

Browse files
easonliang28easonLiangWorldedtech
authored andcommitted
fix(webview): address view-state review feedback
1 parent 2a01eaa commit 97afc41

9 files changed

Lines changed: 80 additions & 31 deletions

File tree

apps/vscode-e2e/src/suite/view-state.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ suite("Roo Code View State", function () {
4646
}
4747
}
4848

49-
globalThis.api.on(RooCodeEventName.TaskModeSwitched, (taskId, mode) => modeEvents.push({ taskId, mode }))
49+
const modeHandler = (taskId: string, mode: string) => modeEvents.push({ taskId, mode })
50+
51+
globalThis.api.on(RooCodeEventName.TaskModeSwitched, modeHandler)
5052
globalThis.api.on(RooCodeEventName.Message, completionHandler)
5153

5254
try {
@@ -110,6 +112,7 @@ suite("Roo Code View State", function () {
110112
)
111113
}
112114
} finally {
115+
globalThis.api.off(RooCodeEventName.TaskModeSwitched, modeHandler)
113116
globalThis.api.off(RooCodeEventName.Message, completionHandler)
114117
}
115118
})

packages/types/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface RooCodeAPI extends EventEmitter<RooCodeAPIEvents> {
2121
text,
2222
images,
2323
newTab,
24+
preserveOpenTabs,
2425
}: {
2526
configuration?: RooCodeSettings
2627
text?: string

src/core/webview/ClineProvider.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -898,15 +898,6 @@ export class ClineProvider
898898
this.customModesManager?.dispose()
899899
this.taskHistoryStore.dispose()
900900
this.flushGlobalStateWriteThrough()
901-
if (this.renderContext === "editor") {
902-
try {
903-
await this.clearPersistedViewState()
904-
} catch (error) {
905-
this.log(
906-
`[dispose] Failed to clear persisted view state for ${this.viewStateId}: ${error instanceof Error ? error.message : String(error)}`,
907-
)
908-
}
909-
}
910901
this.log("Disposed all disposables")
911902
ClineProvider.activeInstances.delete(this)
912903

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,16 +1206,16 @@ describe("ClineProvider - Parallel Mode Support", () => {
12061206
await provider.dispose()
12071207
})
12081208

1209-
it("should clean up persisted viewStates entry when a tab provider is disposed", async () => {
1209+
it("should preserve persisted viewStates entry when an editor provider is disposed during teardown", async () => {
12101210
const provider = new ClineProvider(mockContext, mockOutputChannel, "editor", new ContextProxy(mockContext))
12111211

1212-
await (provider as any).setViewStateId("tab-to-dispose")
1212+
await (provider as any).setViewStateId("tab-to-preserve")
12131213
await (provider as any).saveViewState("mode", "architect")
1214-
expect(provider.contextProxy.getValue("viewStates" as any)).toHaveProperty("tab-to-dispose")
1214+
expect(provider.contextProxy.getValue("viewStates" as any)).toHaveProperty("tab-to-preserve")
12151215

12161216
await provider.dispose()
12171217

1218-
expect(provider.contextProxy.getValue("viewStates" as any)).not.toHaveProperty("tab-to-dispose")
1218+
expect(provider.contextProxy.getValue("viewStates" as any)).toHaveProperty("tab-to-preserve")
12191219
})
12201220
})
12211221

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,7 @@ describe("ClineProvider", () => {
578578
})
579579

580580
test("does not reload full model details when the LM Studio model is already loaded", async () => {
581-
vi.mocked(hasLoadedFullDetails).mockReturnValue(true)
581+
vi.mocked(hasLoadedFullDetails).mockReturnValueOnce(true)
582582

583583
await provider.performPreparationTasks({
584584
apiConfiguration: {

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { describe, it, expect, vi, beforeEach } from "vitest"
22
import { webviewMessageHandler } from "../webviewMessageHandler"
3+
import type { WebviewMessage } from "@roo-code/types"
4+
35
import type { ClineProvider } from "../ClineProvider"
46

57
// Mock vscode (minimal)
@@ -37,10 +39,16 @@ vi.mock("vscode", () => ({
3739
// Mock modelCache getModels/flushModels used by the handler
3840
const getModelsMock = vi.fn()
3941
const flushModelsMock = vi.fn()
42+
const kimiCodeGetAccessTokenMock = vi.fn()
4043
vi.mock("../../../api/providers/fetchers/modelCache", () => ({
4144
getModels: (...args: any[]) => getModelsMock(...args),
4245
flushModels: (...args: any[]) => flushModelsMock(...args),
4346
}))
47+
vi.mock("../../../integrations/kimi-code/oauth", () => ({
48+
kimiCodeOAuthManager: {
49+
getAccessToken: (...args: unknown[]) => kimiCodeGetAccessTokenMock(...args),
50+
},
51+
}))
4452

4553
describe("webviewMessageHandler - requestRouterModels provider filter", () => {
4654
let mockProvider: ClineProvider & {
@@ -296,6 +304,31 @@ describe("webviewMessageHandler - requestRouterModels provider filter", () => {
296304
})
297305
})
298306

307+
it("continues posting routerModels when Kimi Code OAuth lookup fails", async () => {
308+
mockProvider.getState.mockResolvedValue({
309+
apiConfiguration: {
310+
kimiCodeAuthMethod: "oauth",
311+
},
312+
})
313+
kimiCodeGetAccessTokenMock.mockRejectedValueOnce(new Error("refresh failed"))
314+
315+
await webviewMessageHandler(mockProvider, {
316+
type: "requestRouterModels",
317+
values: { provider: "kimi-code" },
318+
} satisfies WebviewMessage)
319+
320+
expect(kimiCodeGetAccessTokenMock).toHaveBeenCalledOnce()
321+
expect(mockProvider.log).toHaveBeenCalledWith(
322+
"[requestRouterModels] kimi-code credential lookup failed: refresh failed",
323+
)
324+
expect(getModelsMock).not.toHaveBeenCalledWith(expect.objectContaining({ provider: "kimi-code" }))
325+
expect(mockProvider.postMessageToWebview).toHaveBeenCalledWith({
326+
type: "routerModels",
327+
routerModels: {},
328+
values: { provider: "kimi-code" },
329+
})
330+
})
331+
299332
it("fetches Moonshot models when stored Moonshot credentials exist", async () => {
300333
mockProvider.getState.mockResolvedValue({
301334
apiConfiguration: {

src/core/webview/webviewMessageHandler.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ export const webviewMessageHandler = async (
558558
}
559559

560560
switch (message.type) {
561-
case "webviewDidLaunch":
561+
case "webviewDidLaunch": {
562562
await provider.setViewStateId(message.viewStateId)
563563

564564
// Load custom modes first
@@ -645,6 +645,7 @@ export const webviewMessageHandler = async (
645645

646646
provider.isViewLaunched = true
647647
break
648+
}
648649
case "newTask":
649650
// Initializing new instance of Cline will make sure that any
650651
// agentically running promises in old instance don't affect our new
@@ -1205,18 +1206,24 @@ export const webviewMessageHandler = async (
12051206
})
12061207

12071208
if (!providerFilter || providerFilter === "kimi-code") {
1208-
const { kimiCodeOAuthManager } = await import("../../integrations/kimi-code/oauth")
1209-
const kimiCodeAuthMethod =
1210-
message?.values?.kimiCodeAuthMethod ?? apiConfiguration.kimiCodeAuthMethod ?? "oauth"
1211-
const kimiCodeApiKey =
1212-
kimiCodeAuthMethod === "api-key"
1213-
? (message?.values?.kimiCodeApiKey ?? apiConfiguration.kimiCodeApiKey)
1214-
: await kimiCodeOAuthManager.getAccessToken()
1215-
if (kimiCodeApiKey) {
1216-
candidates.push({
1217-
key: "kimi-code",
1218-
options: { provider: "kimi-code", apiKey: kimiCodeApiKey },
1219-
})
1209+
try {
1210+
const { kimiCodeOAuthManager } = await import("../../integrations/kimi-code/oauth")
1211+
const kimiCodeAuthMethod =
1212+
message?.values?.kimiCodeAuthMethod ?? apiConfiguration.kimiCodeAuthMethod ?? "oauth"
1213+
const kimiCodeApiKey =
1214+
kimiCodeAuthMethod === "api-key"
1215+
? (message?.values?.kimiCodeApiKey ?? apiConfiguration.kimiCodeApiKey)
1216+
: await kimiCodeOAuthManager.getAccessToken()
1217+
if (kimiCodeApiKey) {
1218+
candidates.push({
1219+
key: "kimi-code",
1220+
options: { provider: "kimi-code", apiKey: kimiCodeApiKey },
1221+
})
1222+
}
1223+
} catch (error) {
1224+
provider.log(
1225+
`[requestRouterModels] kimi-code credential lookup failed: ${error instanceof Error ? error.message : String(error)}`,
1226+
)
12201227
}
12211228
}
12221229

@@ -1368,11 +1375,12 @@ export const webviewMessageHandler = async (
13681375
}
13691376

13701377
break
1371-
case "requestVsCodeLmModels":
1378+
case "requestVsCodeLmModels": {
13721379
const vsCodeLmModels = await getVsCodeLmModels()
13731380
// TODO: Cache like we do for OpenRouter, etc?
13741381
await provider.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
13751382
break
1383+
}
13761384
case "openImage":
13771385
await openImage(message.text!, { values: message.values })
13781386
break

src/extension/__tests__/api-task-control.spec.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ describe("API task controls", () => {
170170
expect(task.approveAsk).toHaveBeenCalledOnce()
171171
})
172172

173-
it("removes completed and aborted tasks from the registry", async () => {
173+
it("removes completed, aborted, and unfocused tasks from the registry", async () => {
174174
const completedTask = createTask("completed-task")
175175
sidebarProvider.emit(RooCodeEventName.TaskCreated, completedTask)
176176
completedTask.emit(RooCodeEventName.TaskCompleted, completedTask.taskId, {}, {})
@@ -182,6 +182,12 @@ describe("API task controls", () => {
182182
abortedTask.emit(RooCodeEventName.TaskAborted)
183183

184184
await expect(api.approveTaskAsk(abortedTask.taskId)).resolves.toBe(false)
185+
186+
const unfocusedTask = createTask("unfocused-task")
187+
sidebarProvider.emit(RooCodeEventName.TaskCreated, unfocusedTask)
188+
unfocusedTask.emit(RooCodeEventName.TaskUnfocused)
189+
190+
await expect(api.approveTaskAsk(unfocusedTask.taskId)).resolves.toBe(false)
185191
})
186192
})
187193

@@ -218,8 +224,9 @@ describe("API task controls", () => {
218224
expect(task.handleWebviewAskResponse).toHaveBeenCalledWith("messageResponse", "Use architect")
219225
})
220226

221-
it("responds without switching modes when the requested mode is invalid", async () => {
227+
it("responds without switching modes and logs when the requested mode is invalid", async () => {
222228
const task = createTask("task-invalid-mode")
229+
api = new API(outputChannel, asClineProvider(sidebarProvider), undefined, true)
223230
sidebarProvider.emit(RooCodeEventName.TaskCreated, task)
224231

225232
await expect(
@@ -229,6 +236,9 @@ describe("API task controls", () => {
229236
expect(sidebarProvider.getState).toHaveBeenCalledOnce()
230237
expect(sidebarProvider.handleModeSwitch).not.toHaveBeenCalled()
231238
expect(task.handleWebviewAskResponse).toHaveBeenCalledWith("messageResponse", "Use invalid")
239+
expect(outputChannel.appendLine).toHaveBeenCalledWith(
240+
'[API#selectTaskFollowupSuggestion] ignoring unknown mode "not-a-mode" for task task-invalid-mode',
241+
)
232242
})
233243

234244
it("treats custom modes from the task provider state as valid", async () => {

src/extension/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
358358

359359
if (isValidMode) {
360360
await entry.provider.handleModeSwitch(mode)
361+
} else {
362+
this.log(`[API#selectTaskFollowupSuggestion] ignoring unknown mode "${mode}" for task ${taskId}`)
361363
}
362364
}
363365

@@ -416,6 +418,7 @@ export class API extends EventEmitter<RooCodeEvents> implements RooCodeAPI {
416418

417419
task.on(RooCodeEventName.TaskUnfocused, () => {
418420
this.emit(RooCodeEventName.TaskUnfocused, task.taskId)
421+
this.tasksById.delete(task.taskId)
419422
})
420423

421424
task.on(RooCodeEventName.TaskActive, () => {

0 commit comments

Comments
 (0)