Skip to content

Commit f7d9d5d

Browse files
authored
Merge pull request #4535 from Kilo-Org/jl-pass-cli-settings
fix(cli): pass auto-approval settings from CLI config to extension
2 parents 63da1d7 + d27a57a commit f7d9d5d

File tree

3 files changed

+411
-0
lines changed

3 files changed

+411
-0
lines changed
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
import { describe, it, expect } from "vitest"
2+
import { mapConfigToExtensionState } from "../mapper.js"
3+
import type { CLIConfig } from "../types.js"
4+
5+
describe("mapConfigToExtensionState", () => {
6+
const baseConfig: CLIConfig = {
7+
version: "1.0.0",
8+
mode: "code",
9+
telemetry: true,
10+
provider: "test-provider",
11+
providers: [
12+
{
13+
id: "test-provider",
14+
provider: "anthropic",
15+
apiKey: "test-key",
16+
apiModelId: "claude-3-5-sonnet-20241022",
17+
},
18+
],
19+
}
20+
21+
describe("auto-approval settings mapping", () => {
22+
it("should set all auto-approval settings to false when autoApproval.enabled is false", () => {
23+
const config: CLIConfig = {
24+
...baseConfig,
25+
autoApproval: {
26+
enabled: false,
27+
read: { enabled: true, outside: true },
28+
write: { enabled: true, outside: true, protected: true },
29+
},
30+
}
31+
32+
const state = mapConfigToExtensionState(config)
33+
34+
expect(state.autoApprovalEnabled).toBe(false)
35+
expect(state.alwaysAllowReadOnly).toBe(false)
36+
expect(state.alwaysAllowReadOnlyOutsideWorkspace).toBe(false)
37+
expect(state.alwaysAllowWrite).toBe(false)
38+
expect(state.alwaysAllowWriteOutsideWorkspace).toBe(false)
39+
expect(state.alwaysAllowWriteProtected).toBe(false)
40+
})
41+
42+
it("should set read settings correctly when autoApproval is enabled", () => {
43+
const config: CLIConfig = {
44+
...baseConfig,
45+
autoApproval: {
46+
enabled: true,
47+
read: { enabled: true, outside: false },
48+
},
49+
}
50+
51+
const state = mapConfigToExtensionState(config)
52+
53+
expect(state.autoApprovalEnabled).toBe(true)
54+
expect(state.alwaysAllowReadOnly).toBe(true)
55+
expect(state.alwaysAllowReadOnlyOutsideWorkspace).toBe(false)
56+
})
57+
58+
it("should set alwaysAllowReadOnlyOutsideWorkspace to true only when all conditions are met", () => {
59+
const config: CLIConfig = {
60+
...baseConfig,
61+
autoApproval: {
62+
enabled: true,
63+
read: { enabled: true, outside: true },
64+
},
65+
}
66+
67+
const state = mapConfigToExtensionState(config)
68+
69+
expect(state.alwaysAllowReadOnlyOutsideWorkspace).toBe(true)
70+
})
71+
72+
it("should set alwaysAllowReadOnlyOutsideWorkspace to false when read.enabled is false", () => {
73+
const config: CLIConfig = {
74+
...baseConfig,
75+
autoApproval: {
76+
enabled: true,
77+
read: { enabled: false, outside: true },
78+
},
79+
}
80+
81+
const state = mapConfigToExtensionState(config)
82+
83+
expect(state.alwaysAllowReadOnly).toBe(false)
84+
expect(state.alwaysAllowReadOnlyOutsideWorkspace).toBe(false)
85+
})
86+
87+
it("should set write settings correctly", () => {
88+
const config: CLIConfig = {
89+
...baseConfig,
90+
autoApproval: {
91+
enabled: true,
92+
write: { enabled: true, outside: true, protected: false },
93+
},
94+
}
95+
96+
const state = mapConfigToExtensionState(config)
97+
98+
expect(state.alwaysAllowWrite).toBe(true)
99+
expect(state.alwaysAllowWriteOutsideWorkspace).toBe(true)
100+
expect(state.alwaysAllowWriteProtected).toBe(false)
101+
})
102+
103+
it("should set browser settings correctly", () => {
104+
const config: CLIConfig = {
105+
...baseConfig,
106+
autoApproval: {
107+
enabled: true,
108+
browser: { enabled: true },
109+
},
110+
}
111+
112+
const state = mapConfigToExtensionState(config)
113+
114+
expect(state.alwaysAllowBrowser).toBe(true)
115+
})
116+
117+
it("should set execute settings correctly", () => {
118+
const config: CLIConfig = {
119+
...baseConfig,
120+
autoApproval: {
121+
enabled: true,
122+
execute: {
123+
enabled: true,
124+
allowed: ["npm", "git"],
125+
denied: ["rm -rf"],
126+
},
127+
},
128+
}
129+
130+
const state = mapConfigToExtensionState(config)
131+
132+
expect(state.alwaysAllowExecute).toBe(true)
133+
expect(state.allowedCommands).toEqual(["npm", "git"])
134+
expect(state.deniedCommands).toEqual(["rm -rf"])
135+
})
136+
137+
it("should set MCP settings correctly", () => {
138+
const config: CLIConfig = {
139+
...baseConfig,
140+
autoApproval: {
141+
enabled: true,
142+
mcp: { enabled: true },
143+
},
144+
}
145+
146+
const state = mapConfigToExtensionState(config)
147+
148+
expect(state.alwaysAllowMcp).toBe(true)
149+
})
150+
151+
it("should set mode switch settings correctly", () => {
152+
const config: CLIConfig = {
153+
...baseConfig,
154+
autoApproval: {
155+
enabled: true,
156+
mode: { enabled: true },
157+
},
158+
}
159+
160+
const state = mapConfigToExtensionState(config)
161+
162+
expect(state.alwaysAllowModeSwitch).toBe(true)
163+
})
164+
165+
it("should set subtasks settings correctly", () => {
166+
const config: CLIConfig = {
167+
...baseConfig,
168+
autoApproval: {
169+
enabled: true,
170+
subtasks: { enabled: true },
171+
},
172+
}
173+
174+
const state = mapConfigToExtensionState(config)
175+
176+
expect(state.alwaysAllowSubtasks).toBe(true)
177+
})
178+
179+
it("should set retry settings correctly", () => {
180+
const config: CLIConfig = {
181+
...baseConfig,
182+
autoApproval: {
183+
enabled: true,
184+
retry: { enabled: true, delay: 15 },
185+
},
186+
}
187+
188+
const state = mapConfigToExtensionState(config)
189+
190+
expect(state.alwaysApproveResubmit).toBe(true)
191+
expect(state.requestDelaySeconds).toBe(15)
192+
})
193+
194+
it("should set question settings correctly", () => {
195+
const config: CLIConfig = {
196+
...baseConfig,
197+
autoApproval: {
198+
enabled: true,
199+
question: { enabled: true, timeout: 30 },
200+
},
201+
}
202+
203+
const state = mapConfigToExtensionState(config)
204+
205+
expect(state.alwaysAllowFollowupQuestions).toBe(true)
206+
expect(state.followupAutoApproveTimeoutMs).toBe(30000) // 30 seconds in ms
207+
})
208+
209+
it("should set todo settings correctly", () => {
210+
const config: CLIConfig = {
211+
...baseConfig,
212+
autoApproval: {
213+
enabled: true,
214+
todo: { enabled: true },
215+
},
216+
}
217+
218+
const state = mapConfigToExtensionState(config)
219+
220+
expect(state.alwaysAllowUpdateTodoList).toBe(true)
221+
})
222+
223+
it("should use default values when autoApproval is not provided", () => {
224+
const config: CLIConfig = {
225+
...baseConfig,
226+
}
227+
228+
const state = mapConfigToExtensionState(config)
229+
230+
expect(state.autoApprovalEnabled).toBe(false)
231+
expect(state.alwaysAllowReadOnly).toBe(false)
232+
expect(state.alwaysAllowReadOnlyOutsideWorkspace).toBe(false)
233+
expect(state.alwaysAllowWrite).toBe(false)
234+
expect(state.alwaysAllowWriteOutsideWorkspace).toBe(false)
235+
expect(state.alwaysAllowWriteProtected).toBe(false)
236+
expect(state.alwaysAllowBrowser).toBe(false)
237+
expect(state.alwaysAllowMcp).toBe(false)
238+
expect(state.alwaysAllowModeSwitch).toBe(false)
239+
expect(state.alwaysAllowSubtasks).toBe(false)
240+
expect(state.alwaysAllowExecute).toBe(false)
241+
expect(state.allowedCommands).toEqual([])
242+
expect(state.deniedCommands).toEqual([])
243+
expect(state.alwaysAllowFollowupQuestions).toBe(false)
244+
expect(state.alwaysAllowUpdateTodoList).toBe(false)
245+
})
246+
})
247+
248+
describe("provider mapping", () => {
249+
it("should map provider configuration correctly", () => {
250+
const state = mapConfigToExtensionState(baseConfig)
251+
252+
expect(state.apiConfiguration).toBeDefined()
253+
expect(state.apiConfiguration?.apiProvider).toBe("anthropic")
254+
expect(state.currentApiConfigName).toBe("test-provider")
255+
})
256+
257+
it("should create listApiConfigMeta from providers", () => {
258+
const state = mapConfigToExtensionState(baseConfig)
259+
260+
expect(state.listApiConfigMeta).toHaveLength(1)
261+
expect(state.listApiConfigMeta?.[0]).toEqual({
262+
id: "test-provider",
263+
name: "test-provider",
264+
apiProvider: "anthropic",
265+
modelId: "claude-3-5-sonnet-20241022",
266+
})
267+
})
268+
})
269+
270+
describe("telemetry mapping", () => {
271+
it("should map telemetry enabled correctly", () => {
272+
const config: CLIConfig = {
273+
...baseConfig,
274+
telemetry: true,
275+
}
276+
277+
const state = mapConfigToExtensionState(config)
278+
279+
expect(state.telemetrySetting).toBe("enabled")
280+
})
281+
282+
it("should map telemetry disabled correctly", () => {
283+
const config: CLIConfig = {
284+
...baseConfig,
285+
telemetry: false,
286+
}
287+
288+
const state = mapConfigToExtensionState(config)
289+
290+
expect(state.telemetrySetting).toBe("disabled")
291+
})
292+
})
293+
294+
describe("mode mapping", () => {
295+
it("should map mode correctly", () => {
296+
const config: CLIConfig = {
297+
...baseConfig,
298+
mode: "architect",
299+
}
300+
301+
const state = mapConfigToExtensionState(config)
302+
303+
expect(state.mode).toBe("architect")
304+
})
305+
})
306+
})

cli/src/config/mapper.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,46 @@ export function mapConfigToExtensionState(
2929
modelId: getModelIdForProvider(p),
3030
}))
3131

32+
// Map auto-approval settings from CLI config to extension state
33+
// These settings control whether the extension auto-approves operations
34+
// or asks the CLI for approval (which then prompts the user)
35+
const autoApproval = config.autoApproval
36+
const autoApprovalEnabled = autoApproval?.enabled ?? false
37+
3238
return {
3339
...currentState,
3440
apiConfiguration,
3541
currentApiConfigName: provider.id,
3642
listApiConfigMeta,
3743
telemetrySetting: config.telemetry ? "enabled" : "disabled",
3844
mode: config.mode,
45+
// Auto-approval settings - these control whether the extension auto-approves
46+
// or defers to the CLI's approval flow
47+
autoApprovalEnabled,
48+
alwaysAllowReadOnly: autoApprovalEnabled && (autoApproval?.read?.enabled ?? false),
49+
alwaysAllowReadOnlyOutsideWorkspace:
50+
autoApprovalEnabled && (autoApproval?.read?.enabled ?? false) && (autoApproval?.read?.outside ?? false),
51+
alwaysAllowWrite: autoApprovalEnabled && (autoApproval?.write?.enabled ?? false),
52+
alwaysAllowWriteOutsideWorkspace:
53+
autoApprovalEnabled &&
54+
(autoApproval?.write?.enabled ?? false) &&
55+
(autoApproval?.write?.outside ?? false),
56+
alwaysAllowWriteProtected:
57+
autoApprovalEnabled &&
58+
(autoApproval?.write?.enabled ?? false) &&
59+
(autoApproval?.write?.protected ?? false),
60+
alwaysAllowBrowser: autoApprovalEnabled && (autoApproval?.browser?.enabled ?? false),
61+
alwaysApproveResubmit: autoApprovalEnabled && (autoApproval?.retry?.enabled ?? false),
62+
requestDelaySeconds: autoApproval?.retry?.delay ?? 10,
63+
alwaysAllowMcp: autoApprovalEnabled && (autoApproval?.mcp?.enabled ?? false),
64+
alwaysAllowModeSwitch: autoApprovalEnabled && (autoApproval?.mode?.enabled ?? false),
65+
alwaysAllowSubtasks: autoApprovalEnabled && (autoApproval?.subtasks?.enabled ?? false),
66+
alwaysAllowExecute: autoApprovalEnabled && (autoApproval?.execute?.enabled ?? false),
67+
allowedCommands: autoApproval?.execute?.allowed ?? [],
68+
deniedCommands: autoApproval?.execute?.denied ?? [],
69+
alwaysAllowFollowupQuestions: autoApprovalEnabled && (autoApproval?.question?.enabled ?? false),
70+
followupAutoApproveTimeoutMs: (autoApproval?.question?.timeout ?? 60) * 1000,
71+
alwaysAllowUpdateTodoList: autoApprovalEnabled && (autoApproval?.todo?.enabled ?? false),
3972
}
4073
} catch (error) {
4174
logs.error("Failed to map config to extension state", "ConfigMapper", { error })

0 commit comments

Comments
 (0)