Skip to content

Commit 308f32a

Browse files
author
Your Name
committed
feat: add VS Code Settings Sync integration
- Add enableSettingsSync configuration option (default: true) - Implement SettingsSyncService to manage sync key registration - Register user preferences, modes, and non-sensitive settings for sync - API keys remain in secure VS Code secrets storage (already synced) - Add comprehensive tests for sync functionality - Support opt-in/opt-out via settings with automatic updates Resolves: #2057
1 parent 6ca4137 commit 308f32a

File tree

5 files changed

+197
-0
lines changed

5 files changed

+197
-0
lines changed

src/extension.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { initializeI18n } from "./i18n"
4545
import { registerGhostProvider } from "./services/ghost" // kilocode_change
4646
import { TerminalWelcomeService } from "./services/terminal-welcome/TerminalWelcomeService" // kilocode_change
4747
import { getKiloCodeWrapperProperties } from "./core/kilocode/wrapper" // kilocode_change
48+
import { SettingsSyncService } from "./services/settings-sync/SettingsSyncService"
4849

4950
/**
5051
* Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -313,6 +314,31 @@ export async function activate(context: vscode.ExtensionContext) {
313314
)
314315
}
315316

317+
// Initialize VS Code Settings Sync integration
318+
try {
319+
await SettingsSyncService.initialize(context)
320+
outputChannel.appendLine("[SettingsSync] VS Code Settings Sync integration initialized")
321+
322+
// Listen for configuration changes to update sync registration
323+
const configChangeListener = vscode.workspace.onDidChangeConfiguration(async (event) => {
324+
if (event.affectsConfiguration(`${Package.name}.enableSettingsSync`)) {
325+
try {
326+
await SettingsSyncService.updateSyncRegistration(context)
327+
outputChannel.appendLine("[SettingsSync] Sync registration updated due to configuration change")
328+
} catch (error) {
329+
outputChannel.appendLine(
330+
`[SettingsSync] Error updating sync registration: ${error instanceof Error ? error.message : String(error)}`,
331+
)
332+
}
333+
}
334+
})
335+
context.subscriptions.push(configChangeListener)
336+
} catch (error) {
337+
outputChannel.appendLine(
338+
`[SettingsSync] Error during settings sync initialization: ${error instanceof Error ? error.message : String(error)}`,
339+
)
340+
}
341+
316342
registerCommands({ context, outputChannel, provider })
317343

318344
/**

src/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,11 @@
585585
"type": "boolean",
586586
"default": false,
587587
"description": "%settings.newTaskRequireTodos.description%"
588+
},
589+
"kilo-code.enableSettingsSync": {
590+
"type": "boolean",
591+
"default": true,
592+
"description": "%settings.enableSettingsSync.description%"
588593
}
589594
}
590595
},

src/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"settings.useAgentRules.description": "Enable loading of AGENTS.md files for agent-specific rules (see https://agent-rules.org/)",
4545
"settings.apiRequestTimeout.description": "Maximum time in seconds to wait for API responses (0 = no timeout, 1-3600s, default: 600s). Higher values are recommended for local providers like LM Studio and Ollama that may need more processing time.",
4646
"settings.newTaskRequireTodos.description": "Require todos parameter when creating new tasks with the new_task tool",
47+
"settings.enableSettingsSync.description": "Enable synchronization of Kilo Code settings across devices via VS Code Settings Sync. When enabled, your API configurations, custom modes, and preferences will automatically sync when you sign in to VS Code Settings Sync.",
4748
"ghost.input.title": "Press 'Enter' to confirm or 'Escape' to cancel",
4849
"ghost.input.placeholder": "Describe what you want to do...",
4950
"ghost.commands.generateSuggestions": "Generate Suggested Edits",
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import * as vscode from "vscode"
2+
import { Package } from "../../shared/package"
3+
4+
/**
5+
* Service for managing Kilo Code settings synchronization with VS Code Settings Sync
6+
*/
7+
export class SettingsSyncService {
8+
// Keys from global state that should be synchronized
9+
// Note: API keys are stored in VS Code secrets storage and are already sync'd by VS Code
10+
private static readonly SYNC_KEYS = [
11+
"allowedCommands",
12+
"deniedCommands",
13+
"autoApprovalEnabled",
14+
"fuzzyMatchThreshold",
15+
"diffEnabled",
16+
"directoryContextAddedContext",
17+
"language",
18+
"customModes",
19+
"firstInstallCompleted",
20+
"telemetrySetting",
21+
] as const
22+
23+
/**
24+
* Initialize settings synchronization
25+
* @param context VS Code extension context
26+
*/
27+
static async initialize(context: vscode.ExtensionContext): Promise<void> {
28+
const enableSync = vscode.workspace.getConfiguration(Package.name).get<boolean>("enableSettingsSync", true)
29+
30+
if (enableSync) {
31+
// Register keys for synchronization with VS Code Settings Sync
32+
const syncKeys = this.SYNC_KEYS.map((key) => `${Package.name}.${key}`)
33+
context.globalState.setKeysForSync(syncKeys)
34+
35+
console.log(`[SettingsSyncService] Registered ${syncKeys.length} keys for synchronization:`, syncKeys)
36+
} else {
37+
// Clear sync keys if sync is disabled
38+
context.globalState.setKeysForSync([])
39+
console.log(`[SettingsSyncService] Settings sync disabled - cleared sync keys`)
40+
}
41+
}
42+
43+
/**
44+
* Update sync registration when the setting changes
45+
* @param context VS Code extension context
46+
*/
47+
static async updateSyncRegistration(context: vscode.ExtensionContext): Promise<void> {
48+
await this.initialize(context)
49+
}
50+
51+
/**
52+
* Get the list of keys that are registered for sync
53+
*/
54+
static getSyncKeys(): readonly string[] {
55+
return this.SYNC_KEYS.map((key) => `${Package.name}.${key}`)
56+
}
57+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { describe, it, expect, vi, beforeEach } from "vitest"
2+
import * as vscode from "vscode"
3+
import { SettingsSyncService } from "../SettingsSyncService"
4+
5+
// Mock VS Code API
6+
vi.mock("vscode", () => ({
7+
workspace: {
8+
getConfiguration: vi.fn(),
9+
},
10+
}))
11+
12+
describe("SettingsSyncService", () => {
13+
let mockContext: vscode.ExtensionContext
14+
let mockGlobalState: any
15+
16+
beforeEach(() => {
17+
mockGlobalState = {
18+
setKeysForSync: vi.fn(),
19+
}
20+
21+
mockContext = {
22+
globalState: mockGlobalState,
23+
} as any
24+
25+
vi.clearAllMocks()
26+
})
27+
28+
describe("initialize", () => {
29+
it("should register sync keys when settings sync is enabled", async () => {
30+
const mockConfiguration = {
31+
get: vi.fn().mockReturnValue(true),
32+
}
33+
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfiguration as any)
34+
35+
await SettingsSyncService.initialize(mockContext)
36+
37+
expect(mockGlobalState.setKeysForSync).toHaveBeenCalledWith([
38+
"kilo-code.allowedCommands",
39+
"kilo-code.deniedCommands",
40+
"kilo-code.autoApprovalEnabled",
41+
"kilo-code.fuzzyMatchThreshold",
42+
"kilo-code.diffEnabled",
43+
"kilo-code.directoryContextAddedContext",
44+
"kilo-code.language",
45+
"kilo-code.customModes",
46+
"kilo-code.firstInstallCompleted",
47+
"kilo-code.telemetrySetting",
48+
])
49+
})
50+
51+
it("should clear sync keys when settings sync is disabled", async () => {
52+
const mockConfiguration = {
53+
get: vi.fn().mockReturnValue(false),
54+
}
55+
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfiguration as any)
56+
57+
await SettingsSyncService.initialize(mockContext)
58+
59+
expect(mockGlobalState.setKeysForSync).toHaveBeenCalledWith([])
60+
})
61+
62+
it("should use default value true when setting is not configured", async () => {
63+
const mockConfiguration = {
64+
get: vi.fn().mockReturnValue(undefined),
65+
}
66+
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfiguration as any)
67+
68+
await SettingsSyncService.initialize(mockContext)
69+
70+
expect(mockConfiguration.get).toHaveBeenCalledWith("enableSettingsSync", true)
71+
expect(mockGlobalState.setKeysForSync).toHaveBeenCalledWith(
72+
expect.arrayContaining(["kilo-code.allowedCommands", "kilo-code.deniedCommands"]),
73+
)
74+
})
75+
})
76+
77+
describe("updateSyncRegistration", () => {
78+
it("should call initialize to update sync registration", async () => {
79+
const mockConfiguration = {
80+
get: vi.fn().mockReturnValue(false),
81+
}
82+
vi.mocked(vscode.workspace.getConfiguration).mockReturnValue(mockConfiguration as any)
83+
84+
await SettingsSyncService.updateSyncRegistration(mockContext)
85+
86+
expect(mockGlobalState.setKeysForSync).toHaveBeenCalledWith([])
87+
})
88+
})
89+
90+
describe("getSyncKeys", () => {
91+
it("should return the list of sync keys", () => {
92+
const syncKeys = SettingsSyncService.getSyncKeys()
93+
94+
expect(syncKeys).toEqual([
95+
"kilo-code.allowedCommands",
96+
"kilo-code.deniedCommands",
97+
"kilo-code.autoApprovalEnabled",
98+
"kilo-code.fuzzyMatchThreshold",
99+
"kilo-code.diffEnabled",
100+
"kilo-code.directoryContextAddedContext",
101+
"kilo-code.language",
102+
"kilo-code.customModes",
103+
"kilo-code.firstInstallCompleted",
104+
"kilo-code.telemetrySetting",
105+
])
106+
})
107+
})
108+
})

0 commit comments

Comments
 (0)