Skip to content

Commit d87e2b8

Browse files
committed
fix(webview): address view-state review feedback
1 parent bf11501 commit d87e2b8

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
@@ -955,15 +955,6 @@ export class ClineProvider
955955
this.customModesManager?.dispose()
956956
this.taskHistoryStore.dispose()
957957
this.flushGlobalStateWriteThrough()
958-
if (this.renderContext === "editor") {
959-
try {
960-
await this.clearPersistedViewState()
961-
} catch (error) {
962-
this.log(
963-
`[dispose] Failed to clear persisted view state for ${this.viewStateId}: ${error instanceof Error ? error.message : String(error)}`,
964-
)
965-
}
966-
}
967958
this.log("Disposed all disposables")
968959
ClineProvider.activeInstances.delete(this)
969960

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
@@ -1223,18 +1224,24 @@ export const webviewMessageHandler = async (
12231224
})
12241225

12251226
if (!providerFilter || providerFilter === "kimi-code") {
1226-
const { kimiCodeOAuthManager } = await import("../../integrations/kimi-code/oauth")
1227-
const kimiCodeAuthMethod =
1228-
message?.values?.kimiCodeAuthMethod ?? apiConfiguration.kimiCodeAuthMethod ?? "oauth"
1229-
const kimiCodeApiKey =
1230-
kimiCodeAuthMethod === "api-key"
1231-
? (message?.values?.kimiCodeApiKey ?? apiConfiguration.kimiCodeApiKey)
1232-
: await kimiCodeOAuthManager.getAccessToken()
1233-
if (kimiCodeApiKey) {
1234-
candidates.push({
1235-
key: "kimi-code",
1236-
options: { provider: "kimi-code", apiKey: kimiCodeApiKey },
1237-
})
1227+
try {
1228+
const { kimiCodeOAuthManager } = await import("../../integrations/kimi-code/oauth")
1229+
const kimiCodeAuthMethod =
1230+
message?.values?.kimiCodeAuthMethod ?? apiConfiguration.kimiCodeAuthMethod ?? "oauth"
1231+
const kimiCodeApiKey =
1232+
kimiCodeAuthMethod === "api-key"
1233+
? (message?.values?.kimiCodeApiKey ?? apiConfiguration.kimiCodeApiKey)
1234+
: await kimiCodeOAuthManager.getAccessToken()
1235+
if (kimiCodeApiKey) {
1236+
candidates.push({
1237+
key: "kimi-code",
1238+
options: { provider: "kimi-code", apiKey: kimiCodeApiKey },
1239+
})
1240+
}
1241+
} catch (error) {
1242+
provider.log(
1243+
`[requestRouterModels] kimi-code credential lookup failed: ${error instanceof Error ? error.message : String(error)}`,
1244+
)
12381245
}
12391246
}
12401247

@@ -1386,11 +1393,12 @@ export const webviewMessageHandler = async (
13861393
}
13871394

13881395
break
1389-
case "requestVsCodeLmModels":
1396+
case "requestVsCodeLmModels": {
13901397
const vsCodeLmModels = await getVsCodeLmModels()
13911398
// TODO: Cache like we do for OpenRouter, etc?
13921399
await provider.postMessageToWebview({ type: "vsCodeLmModels", vsCodeLmModels })
13931400
break
1401+
}
13941402
case "openImage":
13951403
await openImage(message.text!, { values: message.values })
13961404
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)