Skip to content

Commit 51cc23a

Browse files
committed
fix(api): wire task controls and sidebar-targeted configuration
Ports the vps2 CS API wiring onto the F4 head 80c147f: - startNewTask(newTab, preserveOpenTabs): skips editor revert/close-all when preserveOpenTabs is set - task ask registry: approveTaskAsk + selectTaskFollowupSuggestion (per-provider mode validation; a failed mode switch does not swallow the follow-up answer; a stale instance's teardown cannot evict its replacement) - setConfiguration routes through ClineProvider.setValues so the view-local subset stays in sync with the sidebar view's state - getConfiguration flattens the nested view-local apiConfiguration and strips secrets before returning - getGlobalState read surface (test-only) - docs: setConfiguration JSDoc now states writes target the extension-host (sidebar) view (parked A4 major, documented limitation); @PARAM note added for preserveOpenTabs - specs: api-task-control (12 tests), api-set-configuration (1), api-configuration getConfiguration flatten/strip-secrets (1) Upstream: #982 (vps2 F5)
1 parent b9e8fb7 commit 51cc23a

5 files changed

Lines changed: 575 additions & 8 deletions

File tree

packages/types/src/api.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { EventEmitter } from "events"
22
import type { Socket } from "net"
33

44
import type { RooCodeEvents } from "./events.js"
5-
import type { RooCodeSettings } from "./global-settings.js"
5+
import type { GlobalState, RooCodeSettings } from "./global-settings.js"
66
import type { HistoryItem } from "./history.js"
77
import type { ProviderSettingsEntry, ProviderSettings } from "./provider-settings.js"
88
import type { IpcMessage, IpcServerEvents } from "./ipc.js"
@@ -21,18 +21,21 @@ export interface RooCodeAPI extends EventEmitter<RooCodeAPIEvents> {
2121
* Starts a new task with an optional initial message and images.
2222
* @param task Optional initial task message.
2323
* @param images Optional array of image data URIs (e.g., "data:image/webp;base64,...").
24+
* @param preserveOpenTabs When true, skips the new-tab editor cleanup (revert + close-all), preserving open tabs and dirty/unsaved editors.
2425
* @returns The ID of the new task.
2526
*/
2627
startNewTask({
2728
configuration,
2829
text,
2930
images,
3031
newTab,
32+
preserveOpenTabs,
3133
}: {
3234
configuration?: RooCodeSettings
3335
text?: string
3436
images?: string[]
3537
newTab?: boolean
38+
preserveOpenTabs?: boolean
3639
}): Promise<string>
3740
/**
3841
* Resumes a task with the given ID.
@@ -109,6 +112,15 @@ export interface RooCodeAPI extends EventEmitter<RooCodeAPIEvents> {
109112
* confirming a completion result. No-ops if no task is active.
110113
*/
111114
approveCurrentAsk(): Promise<void>
115+
/**
116+
* Programmatically approves the pending ask for a task by ID. Intended for use in tests only.
117+
*/
118+
approveTaskAsk(taskId: string): Promise<boolean>
119+
/**
120+
* Simulates selecting a follow-up suggestion for a task by ID, including its optional mode switch.
121+
* Intended for use in tests only.
122+
*/
123+
selectTaskFollowupSuggestion(options: { taskId: string; answer: string; mode?: string }): Promise<boolean>
112124
/**
113125
* Returns true if the API is ready to use.
114126
*/
@@ -119,10 +131,16 @@ export interface RooCodeAPI extends EventEmitter<RooCodeAPIEvents> {
119131
*/
120132
getConfiguration(): RooCodeSettings
121133
/**
122-
* Sets the configuration for the current task.
134+
* Sets the configuration for the extension. Writes target the extension-host (sidebar) view:
135+
* view-local values are pinned to the sidebar's per-view state, while global values are
136+
* written to the shared ContextProxy.
123137
* @param values An object containing key-value pairs to set.
124138
*/
125139
setConfiguration(values: RooCodeSettings): Promise<void>
140+
/**
141+
* Returns a value from VS Code globalState. Intended for use in tests only.
142+
*/
143+
getGlobalState<K extends keyof GlobalState>(key: K): GlobalState[K]
126144
/**
127145
* Returns a list of all configured profile names
128146
* @returns Array of profile names

src/extension/__tests__/api-configuration.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { describe, expect, it, vi } from "vitest"
22
import type * as vscode from "vscode"
33

4+
import { providerIdentifiers } from "@roo-code/types"
5+
46
import { API } from "../api"
57
import type { ClineProvider } from "../../core/webview/ClineProvider"
68

@@ -17,6 +19,7 @@ describe("API - configuration", () => {
1719
const provider = {
1820
context: {},
1921
on: vi.fn(),
22+
setValues,
2023
contextProxy: { setValues },
2124
providerSettingsManager: { saveConfig, setModeConfig },
2225
postStateToWebview,
@@ -50,6 +53,7 @@ describe("API - configuration", () => {
5053
const provider = {
5154
context: {},
5255
on: vi.fn(),
56+
setValues,
5357
contextProxy: { setValues },
5458
providerSettingsManager: { saveConfig, setModeConfig },
5559
postStateToWebview,
@@ -62,4 +66,38 @@ describe("API - configuration", () => {
6266
expect(setModeConfig).not.toHaveBeenCalled()
6367
expect(postStateToWebview).toHaveBeenCalledOnce()
6468
})
69+
70+
it("flattens the nested view-local apiConfiguration and strips its secrets", () => {
71+
const getValues = vi.fn().mockReturnValue({
72+
mode: "architect",
73+
currentApiConfigName: "view-profile",
74+
apiConfiguration: {
75+
apiProvider: providerIdentifiers.openrouter,
76+
openRouterModelId: "openai/gpt-4o",
77+
apiKey: "nested-secret-key",
78+
openRouterApiKey: "nested-openrouter-secret",
79+
},
80+
})
81+
// Structural double: API.getConfiguration() only reads sidebarProvider.getValues()
82+
// from the provider; the double assertion adapts this minimal shape to the
83+
// constructor's ClineProvider parameter (same pattern as the tests above).
84+
const provider = {
85+
context: {},
86+
on: vi.fn(),
87+
getValues,
88+
} as unknown as ClineProvider
89+
const outputChannel = { appendLine: vi.fn() } as unknown as vscode.OutputChannel
90+
const api = new API(outputChannel, provider)
91+
92+
const configuration = api.getConfiguration()
93+
94+
expect(getValues).toHaveBeenCalledOnce()
95+
expect(configuration.mode).toBe("architect")
96+
expect(configuration.currentApiConfigName).toBe("view-profile")
97+
expect(configuration.apiProvider).toBe(providerIdentifiers.openrouter)
98+
expect(configuration.openRouterModelId).toBe("openai/gpt-4o")
99+
expect(configuration).not.toHaveProperty("apiConfiguration")
100+
expect(configuration).not.toHaveProperty("apiKey")
101+
expect(configuration).not.toHaveProperty("openRouterApiKey")
102+
})
65103
})
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, it, vi } from "vitest"
2+
3+
import { providerIdentifiers } from "@roo-code/types"
4+
5+
import { API } from "../api"
6+
import type { ClineProvider } from "../../core/webview/ClineProvider"
7+
import type { OutputChannel } from "vscode"
8+
9+
vi.mock("@roo-code/ipc", () => ({
10+
IpcServer: class {},
11+
}))
12+
13+
vi.mock("../../integrations/terminal/Terminal", () => ({
14+
Terminal: {
15+
getTerminalProfile: vi.fn(),
16+
setTerminalProfile: vi.fn(),
17+
},
18+
}))
19+
20+
vi.mock("../../integrations/terminal/TerminalRegistry", () => ({
21+
TerminalRegistry: {
22+
closeIdleTerminals: vi.fn(),
23+
},
24+
}))
25+
26+
describe("API.setConfiguration", () => {
27+
it("routes configuration through ClineProvider.setValues so view-local state stays in sync", async () => {
28+
const provider = {
29+
context: {},
30+
on: vi.fn(),
31+
setValues: vi.fn().mockResolvedValue(undefined),
32+
contextProxy: {
33+
setValues: vi.fn().mockResolvedValue(undefined),
34+
},
35+
providerSettingsManager: {
36+
saveConfig: vi.fn().mockResolvedValue("default-id"),
37+
},
38+
postStateToWebview: vi.fn().mockResolvedValue(undefined),
39+
} as unknown as ClineProvider
40+
const api = new API({ appendLine: vi.fn() } as unknown as OutputChannel, provider)
41+
const configuration = {
42+
apiProvider: providerIdentifiers.bedrock,
43+
currentApiConfigName: "default",
44+
awsRegion: "us-east-1",
45+
apiModelId: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
46+
}
47+
48+
await api.setConfiguration(configuration)
49+
50+
expect(provider.setValues).toHaveBeenCalledWith(configuration)
51+
expect(provider.contextProxy.setValues).not.toHaveBeenCalled()
52+
expect(provider.providerSettingsManager.saveConfig).toHaveBeenCalledWith("default", configuration)
53+
expect(provider.postStateToWebview).toHaveBeenCalled()
54+
})
55+
})

0 commit comments

Comments
 (0)