From a0e1fd82826b7d3c104fb3df82c30b08e5fbebd0 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 27 Mar 2026 17:45:15 +0100 Subject: [PATCH] feat(vscode): add settings export/import to About tab (#7794) Add Export and Import buttons in the About Kilo Code settings tab so users can transfer settings between VS Code instances (closes #7755). - Export fetches global-only config (not merged/project-level) to avoid promoting project-level agent definitions to global config - All fields exported as-is including secrets for complete transfers - Import populates the draft for review before saving via existing save bar flow - KNOWN_KEYS drift guard test catches newly added Config fields - 40 unit tests covering export, import, merge, round-trip, and constants - i18n keys translated for all 17 locales --- packages/kilo-vscode/src/KiloProvider.ts | 14 + .../src/agent-manager/AgentManagerProvider.ts | 14 +- .../tests/unit/agent-manager-arch.test.ts | 9 +- .../tests/unit/settings-io.test.ts | 397 ++++++++++++++++++ .../agent-manager/AgentManagerApp.tsx | 127 +++--- .../src/components/chat/PromptInput.tsx | 2 +- .../components/settings/AboutKiloCodeTab.tsx | 119 +++++- .../src/components/settings/settings-io.ts | 130 ++++++ .../webview-ui/src/context/worktree-mode.tsx | 10 +- .../kilo-vscode/webview-ui/src/i18n/ar.ts | 10 + .../kilo-vscode/webview-ui/src/i18n/br.ts | 14 + .../kilo-vscode/webview-ui/src/i18n/bs.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/da.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/de.ts | 14 + .../kilo-vscode/webview-ui/src/i18n/en.ts | 11 + .../kilo-vscode/webview-ui/src/i18n/es.ts | 14 + .../kilo-vscode/webview-ui/src/i18n/fr.ts | 14 + .../kilo-vscode/webview-ui/src/i18n/ja.ts | 13 + .../kilo-vscode/webview-ui/src/i18n/ko.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/nl.ts | 14 + .../kilo-vscode/webview-ui/src/i18n/no.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/pl.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/ru.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/th.ts | 11 + .../kilo-vscode/webview-ui/src/i18n/tr.ts | 12 + .../kilo-vscode/webview-ui/src/i18n/zh.ts | 9 + .../kilo-vscode/webview-ui/src/i18n/zht.ts | 9 + .../webview-ui/src/types/messages.ts | 11 + 28 files changed, 949 insertions(+), 101 deletions(-) create mode 100644 packages/kilo-vscode/tests/unit/settings-io.test.ts create mode 100644 packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index e917b82ebc5..5f30cc1f65f 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -652,6 +652,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "requestConfig": this.fetchAndSendConfig().catch((e) => console.error("[Kilo New] fetchAndSendConfig failed:", e)) break + case "requestGlobalConfig": + this.fetchAndSendGlobalConfig().catch((e) => console.error("[Kilo New] fetchAndSendGlobalConfig failed:", e)) + break case "updateConfig": await this.handleUpdateConfig(message.config) break @@ -1739,6 +1742,17 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } + /** Fetch global-only config (no project/managed layers) for settings export. */ + private async fetchAndSendGlobalConfig(): Promise { + if (!this.client || this.connectionState !== "connected") return + try { + const { data: config } = await this.client.global.config.get({ throwOnError: true }) + this.postMessage({ type: "globalConfigLoaded", config }) + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to fetch global config:", error) + } + } + /** * Seed sessionStatusMap with current session statuses on connect. * Without this, the Settings panel (which has no tracked sessions) would see diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 43dd185ccc7..b463e7682da 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -276,12 +276,14 @@ export class AgentManagerProvider implements Disposable { // already emitted before the webview was ready to receive messages. if (this.cachedWorktreeStats) this.postToWebview(this.cachedWorktreeStats) if (this.cachedLocalStats) this.postToWebview(this.cachedLocalStats) - // Always refresh sessions after pushState so the webview receives - // a sessionsLoaded message. Without this unconditional call, closing - // and reopening the panel (or recovering from a backend crash) can - // leave sessionsLoaded stuck on false because the initial - // refreshSessions() in initializeState() raced ahead of mount. - this.panel?.sessions.refreshSessions() + // Refresh sessions after pushState so the webview's sessionsLoaded + // handler is guaranteed to be registered (requestState fires from + // onMount). Without this, the initial refreshSessions() in + // initializeState() can race ahead of webview mount, causing + // sessionsLoaded to never flip to true. + if (this.state.getSessions().length > 0) { + this.panel?.sessions.refreshSessions() + } }) .catch((err) => { this.log("initializeState failed, pushing partial state:", err) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index 045352476c4..bf2ffcb5706 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -321,12 +321,13 @@ describe("Agent Manager Webview — non-git sessionsLoaded fix", () => { /** * Regression: when isGitRepo is false, the Kilo server never sends a * "sessionsLoaded" message, so the skeleton was stuck forever. - * The fix must set sessionsLoaded(true) from the state-application path - * when isGitRepo === false. + * The fix must set sessionsLoaded(true) when receiving a state message + * with isGitRepo === false. */ it("sets sessionsLoaded when agentManager.state arrives with isGitRepo false", () => { - const start = tsx.indexOf("const applyState =") - expect(start, "applyState helper must exist").toBeGreaterThan(-1) + // Find the agentManager.state handler block + const start = tsx.indexOf('"agentManager.state"') + expect(start, "agentManager.state handler must exist").toBeGreaterThan(-1) const snippet = tsx.slice(start, start + 800) expect(snippet, "must call setSessionsLoaded in the non-git branch").toContain("setSessionsLoaded") expect(snippet, "must check isGitRepo === false before setting sessionsLoaded").toMatch( diff --git a/packages/kilo-vscode/tests/unit/settings-io.test.ts b/packages/kilo-vscode/tests/unit/settings-io.test.ts new file mode 100644 index 00000000000..142f1e82ad1 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/settings-io.test.ts @@ -0,0 +1,397 @@ +import { describe, it, expect } from "bun:test" +import { + buildExport, + parseImport, + mergeConfig, + MAX_IMPORT_SIZE, + KNOWN_KEYS, + META_VERSION, +} from "../../webview-ui/src/components/settings/settings-io" +import type { Config } from "../../webview-ui/src/types/messages" + +// --------------------------------------------------------------------------- +// buildExport +// --------------------------------------------------------------------------- +describe("buildExport", () => { + it("wraps config in _meta envelope", () => { + const cfg: Config = { model: "anthropic/claude-sonnet-4-20250514" } + const result = buildExport(cfg) + expect(result._meta).toBeDefined() + expect(result._meta.version).toBe(META_VERSION) + expect(typeof result._meta.exportedAt).toBe("string") + expect(result.model).toBe("anthropic/claude-sonnet-4-20250514") + }) + + it("preserves provider fields including secrets", () => { + const cfg: Config = { + provider: { + openai: { name: "OpenAI", api_key: "sk-secret-123" }, + custom: { name: "Custom", options: { apiKey: "secret", baseURL: "https://example.com" } }, + }, + } + const result = buildExport(cfg) + expect(result.provider.openai.api_key).toBe("sk-secret-123") + expect(result.provider.openai.name).toBe("OpenAI") + expect(result.provider.custom.options.apiKey).toBe("secret") + expect(result.provider.custom.options.baseURL).toBe("https://example.com") + }) + + it("preserves mcp fields including env and headers", () => { + const cfg: Config = { + mcp: { + github: { + type: "local" as const, + command: "npx", + env: { GITHUB_TOKEN: "ghp_secret123" }, + enabled: true, + }, + server: { + type: "local" as const, + command: "node", + environment: { SECRET: "val" }, + }, + remote: { + type: "remote" as const, + url: "https://mcp.example.com", + headers: { Authorization: "Bearer secret-token" }, + enabled: true, + }, + }, + } + const result = buildExport(cfg) + expect(result.mcp.github.env.GITHUB_TOKEN).toBe("ghp_secret123") + expect(result.mcp.github.command).toBe("npx") + expect(result.mcp.server.environment.SECRET).toBe("val") + expect(result.mcp.remote.headers.Authorization).toBe("Bearer secret-token") + expect(result.mcp.remote.url).toBe("https://mcp.example.com") + }) + + it("preserves all config fields", () => { + const cfg: Config = { + model: "test-model", + small_model: "test-small", + default_agent: "coder", + agent: { coder: { mode: "primary", prompt: "Code stuff" } }, + permission: { read: "allow" }, + instructions: ["rule1.md"], + snapshot: true, + share: "manual", + } + const result = buildExport(cfg) + expect(result.model).toBe("test-model") + expect(result.small_model).toBe("test-small") + expect(result.default_agent).toBe("coder") + expect(result.agent).toEqual({ coder: { mode: "primary", prompt: "Code stuff" } }) + expect(result.permission).toEqual({ read: "allow" }) + expect(result.instructions).toEqual(["rule1.md"]) + expect(result.snapshot).toBe(true) + expect(result.share).toBe("manual") + }) + + it("handles empty config", () => { + const result = buildExport({}) + expect(result._meta).toBeDefined() + expect(Object.keys(result).length).toBe(1) // only _meta + }) + + it("handles config with no providers or mcp", () => { + const cfg: Config = { model: "test" } + const result = buildExport(cfg) + expect(result.model).toBe("test") + expect(result.provider).toBeUndefined() + expect(result.mcp).toBeUndefined() + }) +}) + +// --------------------------------------------------------------------------- +// parseImport +// --------------------------------------------------------------------------- +describe("parseImport", () => { + it("rejects non-JSON", () => { + const result = parseImport("not json at all") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON null", () => { + const result = parseImport("null") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON array", () => { + const result = parseImport("[]") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON string", () => { + const result = parseImport('"hello"') + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects JSON number", () => { + const result = parseImport("42") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidJson") + }) + + it("rejects empty object (no known keys)", () => { + const result = parseImport("{}") + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidConfig") + }) + + it("rejects object with only unknown keys", () => { + const result = parseImport(JSON.stringify({ foo: "bar", baz: 42 })) + expect(result.ok).toBe(false) + if (!result.ok) expect(result.error).toBe("invalidConfig") + }) + + it("accepts valid partial config (just agent)", () => { + const json = JSON.stringify({ + agent: { coder: { mode: "primary", prompt: "Code things" } }, + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.agent).toEqual({ coder: { mode: "primary", prompt: "Code things" } }) + } + }) + + it("accepts valid partial config (just model)", () => { + const json = JSON.stringify({ model: "test-model" }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) expect(result.config.model).toBe("test-model") + }) + + it("strips _meta before returning config", () => { + const json = JSON.stringify({ + _meta: { version: 1, exportedAt: "2026-01-01", secretsStripped: true }, + model: "test", + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect((result.config as Record)._meta).toBeUndefined() + expect(result.config.model).toBe("test") + } + }) + + it("strips unknown top-level keys", () => { + const json = JSON.stringify({ + model: "test", + unknownField: "should be stripped", + anotherUnknown: 42, + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.model).toBe("test") + expect((result.config as Record).unknownField).toBeUndefined() + expect((result.config as Record).anotherUnknown).toBeUndefined() + } + }) + + it("returns warning when _meta.version > current", () => { + const json = JSON.stringify({ + _meta: { version: 999 }, + model: "test", + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.warning).toBe("newerVersion") + } + }) + + it("returns no warning when _meta.version <= current", () => { + const json = JSON.stringify({ + _meta: { version: META_VERSION }, + model: "test", + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.warning).toBeUndefined() + } + }) + + it("returns no warning when _meta is absent", () => { + const json = JSON.stringify({ model: "test" }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.warning).toBeUndefined() + } + }) + + it("accepts config with all known keys", () => { + const cfg: Record = {} + for (const key of KNOWN_KEYS) { + cfg[key] = key === "instructions" ? ["rule.md"] : key === "snapshot" ? true : "value" + } + const json = JSON.stringify(cfg) + const result = parseImport(json) + expect(result.ok).toBe(true) + }) + + it("preserves provider and mcp fields as-is (including secrets on import)", () => { + const json = JSON.stringify({ + provider: { openai: { name: "OpenAI", api_key: "sk-123" } }, + mcp: { gh: { command: "npx", env: { TOKEN: "secret" } } }, + }) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.provider?.openai?.api_key).toBe("sk-123") + expect(result.config.mcp?.gh?.env?.TOKEN).toBe("secret") + } + }) +}) + +// --------------------------------------------------------------------------- +// mergeConfig +// --------------------------------------------------------------------------- +describe("mergeConfig", () => { + it("merges imported agents with existing agents", () => { + const existing: Config = { + agent: { + coder: { mode: "primary", prompt: "Code" }, + reviewer: { mode: "primary", prompt: "Review" }, + }, + } + const imported: Config = { + agent: { + reviewer: { mode: "primary", prompt: "Updated review" }, + planner: { mode: "primary", prompt: "Plan" }, + }, + } + const result = mergeConfig(existing, imported) + expect(result.agent?.coder?.prompt).toBe("Code") + expect(result.agent?.reviewer?.prompt).toBe("Updated review") + expect(result.agent?.planner?.prompt).toBe("Plan") + }) + + it("imported values override existing for same keys", () => { + const existing: Config = { model: "old-model", default_agent: "coder" } + const imported: Config = { model: "new-model" } + const result = mergeConfig(existing, imported) + expect(result.model).toBe("new-model") + expect(result.default_agent).toBe("coder") + }) + + it("existing values not in import are preserved", () => { + const existing: Config = { + model: "test", + permission: { read: "allow" }, + instructions: ["old.md"], + } + const imported: Config = { model: "updated" } + const result = mergeConfig(existing, imported) + expect(result.model).toBe("updated") + expect(result.permission).toEqual({ read: "allow" }) + expect(result.instructions).toEqual(["old.md"]) + }) + + it("merges providers without losing existing ones", () => { + const existing: Config = { + provider: { + openai: { name: "OpenAI", api_key: "sk-existing" }, + anthropic: { name: "Anthropic", api_key: "sk-ant" }, + }, + } + const imported: Config = { + provider: { + openai: { name: "OpenAI Updated", base_url: "https://new.api" }, + }, + } + const result = mergeConfig(existing, imported) + expect(result.provider?.openai?.name).toBe("OpenAI Updated") + expect(result.provider?.openai?.base_url).toBe("https://new.api") + expect(result.provider?.anthropic?.name).toBe("Anthropic") + expect(result.provider?.anthropic?.api_key).toBe("sk-ant") + }) + + it("handles empty existing config", () => { + const imported: Config = { model: "test", agent: { coder: { mode: "primary" } } } + const result = mergeConfig({}, imported) + expect(result.model).toBe("test") + expect(result.agent?.coder?.mode).toBe("primary") + }) + + it("handles empty imported config", () => { + const existing: Config = { model: "test" } + const result = mergeConfig(existing, {}) + expect(result.model).toBe("test") + }) +}) + +// --------------------------------------------------------------------------- +// Round-trip +// --------------------------------------------------------------------------- +describe("round-trip", () => { + it("export then import preserves all fields including secrets", () => { + const original: Config = { + model: "test-model", + agent: { coder: { mode: "primary", prompt: "Code" } }, + provider: { openai: { name: "OpenAI", api_key: "sk-secret" } }, + mcp: { gh: { command: "npx", env: { TOKEN: "secret" } } }, + permission: { read: "allow" }, + instructions: ["rules.md"], + } + const exported = buildExport(original) + const json = JSON.stringify(exported) + const result = parseImport(json) + expect(result.ok).toBe(true) + if (result.ok) { + expect(result.config.model).toBe("test-model") + expect(result.config.agent).toEqual({ coder: { mode: "primary", prompt: "Code" } }) + expect(result.config.provider?.openai?.name).toBe("OpenAI") + expect(result.config.provider?.openai?.api_key).toBe("sk-secret") + expect(result.config.mcp?.gh?.command).toBe("npx") + expect(result.config.mcp?.gh?.env?.TOKEN).toBe("secret") + expect(result.config.permission).toEqual({ read: "allow" }) + expect(result.config.instructions).toEqual(["rules.md"]) + } + }) +}) + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- +describe("constants", () => { + it("MAX_IMPORT_SIZE is 1 MB", () => { + expect(MAX_IMPORT_SIZE).toBe(1_048_576) + }) + + it("META_VERSION is 1", () => { + expect(META_VERSION).toBe(1) + }) + + it("KNOWN_KEYS includes core config keys", () => { + expect(KNOWN_KEYS).toContain("agent") + expect(KNOWN_KEYS).toContain("provider") + expect(KNOWN_KEYS).toContain("mcp") + expect(KNOWN_KEYS).toContain("permission") + expect(KNOWN_KEYS).toContain("model") + expect(KNOWN_KEYS).toContain("instructions") + }) + + it("KNOWN_KEYS matches all keys in the Config interface (drift guard)", async () => { + // Read the Config interface from messages.ts and extract its keys. + // If someone adds a new field to Config, this test fails as a reminder + // to also add it to KNOWN_KEYS in settings-io.ts. + const src = await Bun.file(require("path").join(__dirname, "../../webview-ui/src/types/messages.ts")).text() + const match = src.match(/export interface Config \{([^}]+)\}/) + expect(match).not.toBeNull() + const body = match![1] + const keys = [...body.matchAll(/^\s+(\w+)\??:/gm)].map((m) => m[1]) + expect(keys.length).toBeGreaterThan(0) + + const sorted = (arr: readonly string[]) => [...arr].sort() + expect(sorted(KNOWN_KEYS)).toEqual(sorted(keys)) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index e66f04eb737..08917a8ecc4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -74,7 +74,7 @@ import { ProviderProvider } from "../src/context/provider" import { ConfigProvider } from "../src/context/config" import { NotificationsProvider } from "../src/context/notifications" import { SessionProvider, useSession } from "../src/context/session" -import { WorktreeModeProvider, useWorktreeMode } from "../src/context/worktree-mode" +import { WorktreeModeProvider } from "../src/context/worktree-mode" import { ChatView } from "../src/components/chat" import { NewWorktreeDialog } from "./NewWorktreeDialog" import { LanguageBridge, DataBridge } from "../src/App" @@ -312,44 +312,6 @@ const AgentManagerContent: Component = () => { const repoDefaultBranch = () => defaultBaseBranch() ?? repoDetectedBranch() ?? "main" const hasConfiguredBranch = () => !!defaultBaseBranch() - const applyState = (state: AgentManagerStateMessage) => { - setWorktrees(state.worktrees) - setManagedSessions(state.sessions) - setStaleWorktreeIds(new Set(state.staleWorktreeIds ?? [])) - if (state.isGitRepo !== undefined) setIsGitRepo(state.isGitRepo) - if (!worktreesLoaded()) setWorktreesLoaded(true) - if (state.isGitRepo === false && !sessionsLoaded()) setSessionsLoaded(true) - if (state.tabOrder) setWorktreeTabOrder(state.tabOrder) - if (state.worktreeOrder) setSidebarWorktreeOrder(state.worktreeOrder) - if (state.reviewDiffStyle === "split" || state.reviewDiffStyle === "unified") { - setReviewDiffStyle(state.reviewDiffStyle) - } - if ("defaultBaseBranch" in state) setDefaultBaseBranch(state.defaultBaseBranch || undefined) - const current = session.currentSessionID() - if (current) { - const ms = state.sessions.find((s) => s.id === current) - if (ms?.worktreeId) setSelection(ms.worktreeId) - } - const localOrder = state.tabOrder?.[LOCAL] - if (localOrder && localSessionIDs().length > 0) { - const reordered = applyTabOrder( - localSessionIDs().map((id) => ({ id })), - localOrder, - ).map((item) => item.id) - setLocalSessionIDs(reordered) - } - if (state.sessionsCollapsed !== undefined) setSessionsCollapsed(state.sessionsCollapsed) - const ids = new Set(state.worktrees.map((wt) => wt.id)) - setBusyWorktrees((prev) => { - const next = new Map([...prev].filter(([id]) => ids.has(id))) - return next.size === prev.size ? prev : next - }) - } - - const requestState = () => { - vscode.postMessage({ type: "agentManager.requestState" }) - } - const DEFAULT_SIDEBAR_WIDTH = 260 const MIN_SIDEBAR_WIDTH = 200 const MAX_SIDEBAR_WIDTH_RATIO = 0.4 @@ -397,10 +359,6 @@ const AgentManagerContent: Component = () => { const PENDING_PREFIX = "pending:" const [activePendingId, setActivePendingId] = createSignal() - // Sync activePendingId into WorktreeMode context so PromptInput can key drafts per pending tab - const worktreeCtx = useWorktreeMode() - createEffect(() => worktreeCtx?.setPendingId(activePendingId())) - // Inline delete confirmation: tracks which worktree is awaiting a second click/press const [pendingDelete, setPendingDelete] = createSignal(null) let pendingDeleteTimer: ReturnType | undefined @@ -1039,41 +997,6 @@ const AgentManagerContent: Component = () => { if (agent) session.selectAgent(agent.name) } - // Subscribe outside onMount so early sessionsLoaded messages (posted before - // the component mounts) are not missed. This matches the codebase pattern for - // loaded-message listeners and prevents the loading skeleton from persisting - // after a backend crash/restart + panel reopen. - const unsubState = vscode.onMessage((msg) => { - if (msg.type !== "agentManager.state") return - applyState(msg as AgentManagerStateMessage) - }) - onCleanup(unsubState) - - const unsubSessions = vscode.onMessage((msg) => { - if (msg.type !== "sessionsLoaded") return - if (!sessionsLoaded()) setSessionsLoaded(true) - }) - onCleanup(unsubSessions) - - // Retry state requests while the panel is bootstrapping so reopening after a - // backend restart cannot get stuck behind a missed requestState/refresh race. - const retryMs = 500 - const maxRetries = 10 - let retries = 0 - const retry = setInterval(() => { - if (worktreesLoaded() && sessionsLoaded()) { - clearInterval(retry) - return - } - retries++ - if (retries >= maxRetries) { - clearInterval(retry) - return - } - requestState() - }, retryMs) - onCleanup(() => clearInterval(retry)) - onMount(() => { const handler = (event: MessageEvent) => { const msg = event.data as ExtensionMessage @@ -1177,6 +1100,11 @@ const AgentManagerContent: Component = () => { } }) + // Mark sessions loaded as soon as the session context receives data (even if empty) + const unsubSessions = vscode.onMessage((msg) => { + if (msg.type === "sessionsLoaded" && !sessionsLoaded()) setSessionsLoaded(true) + }) + const unsub = vscode.onMessage((msg) => { if (msg.type === "agentManager.repoInfo") { const info = msg as AgentManagerRepoInfoMessage @@ -1250,6 +1178,46 @@ const AgentManagerContent: Component = () => { setKb(ev.bindings) } + if (msg.type === "agentManager.state") { + const state = msg as AgentManagerStateMessage + setWorktrees(state.worktrees) + setManagedSessions(state.sessions) + setStaleWorktreeIds(new Set(state.staleWorktreeIds ?? [])) + if (state.isGitRepo !== undefined) setIsGitRepo(state.isGitRepo) + if (!worktreesLoaded()) setWorktreesLoaded(true) + // When not a git repo, also mark sessions as loaded since the Kilo + // server won't connect to send the sessionsLoaded message. + if (state.isGitRepo === false && !sessionsLoaded()) setSessionsLoaded(true) + if (state.tabOrder) setWorktreeTabOrder(state.tabOrder) + if (state.worktreeOrder) setSidebarWorktreeOrder(state.worktreeOrder) + if (state.reviewDiffStyle === "split" || state.reviewDiffStyle === "unified") { + setReviewDiffStyle(state.reviewDiffStyle) + } + if ("defaultBaseBranch" in state) setDefaultBaseBranch(state.defaultBaseBranch || undefined) + const current = session.currentSessionID() + if (current) { + const ms = state.sessions.find((s) => s.id === current) + if (ms?.worktreeId) setSelection(ms.worktreeId) + } + // Recover local tab order from persisted state + const localOrder = state.tabOrder?.[LOCAL] + if (localOrder && localSessionIDs().length > 0) { + const reordered = applyTabOrder( + localSessionIDs().map((id) => ({ id })), + localOrder, + ).map((item) => item.id) + setLocalSessionIDs(reordered) + } + // Recover sessions collapsed state from extension-persisted state + if (state.sessionsCollapsed !== undefined) setSessionsCollapsed(state.sessionsCollapsed) + // Clear busy state for worktrees that have been removed + const ids = new Set(state.worktrees.map((wt) => wt.id)) + setBusyWorktrees((prev) => { + const next = new Map([...prev].filter(([id]) => ids.has(id))) + return next.size === prev.size ? prev : next + }) + } + // When a multi-version progress update arrives, mark newly created worktrees as loading if ((msg as { type: string }).type === "agentManager.multiVersionProgress") { const ev = msg as unknown as AgentManagerMultiVersionProgressMessage @@ -1417,6 +1385,7 @@ const AgentManagerContent: Component = () => { window.removeEventListener("keydown", deleteKeyHandler) window.removeEventListener("focus", onWindowFocus) unsubCreate() + unsubSessions() unsub() }) }) @@ -1426,7 +1395,7 @@ const AgentManagerContent: Component = () => { selectLocal() // Request worktree/session state from extension — handles race where // initializeState() pushState fires before the webview is mounted - requestState() + vscode.postMessage({ type: "agentManager.requestState" }) // Open a pending "New Session" tab if there are no persisted local sessions if (localSessionIDs().length === 0) { addPendingTab() diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 508ce41dde1..cf8804cff15 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -78,7 +78,7 @@ export const PromptInput: Component = (props) => { }) const history = usePromptHistory() - const sessionKey = () => session.currentSessionID() ?? worktree?.pendingId() ?? "__new__" + const sessionKey = () => session.currentSessionID() ?? "__new__" const [text, setText] = createSignal("") const [reviewComments, setReviewComments] = createSignal([]) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx index cbf098b2a35..c531abe2de3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AboutKiloCodeTab.tsx @@ -1,7 +1,12 @@ -import { Component } from "solid-js" +import { Component, createSignal, onCleanup } from "solid-js" +import { Button } from "@kilocode/kilo-ui/button" +import { Icon } from "@kilocode/kilo-ui/icon" +import { showToast } from "@kilocode/kilo-ui/toast" import { useLanguage } from "../../context/language" import { useVSCode } from "../../context/vscode" -import type { ConnectionState } from "../../types/messages" +import { useConfig } from "../../context/config" +import type { ConnectionState, ExtensionMessage } from "../../types/messages" +import { buildExport, parseImport, MAX_IMPORT_SIZE } from "./settings-io" export interface AboutKiloCodeTabProps { port: number | null @@ -13,11 +18,96 @@ export interface AboutKiloCodeTabProps { const AboutKiloCodeTab: Component = (props) => { const language = useLanguage() const vscode = useVSCode() + const { updateConfig } = useConfig() + const [importing, setImporting] = createSignal(false) + const [exporting, setExporting] = createSignal(false) + let epoch = 0 const open = (url: string) => { vscode.postMessage({ type: "openExternal", url }) } + // Listen for globalConfigLoaded response + const handler = (event: MessageEvent) => { + const msg = event.data as ExtensionMessage + if (msg.type !== "globalConfigLoaded" || !exporting()) return + setExporting(false) + epoch++ + const payload = buildExport(msg.config) + const json = JSON.stringify(payload, null, 2) + const blob = new Blob([json], { type: "application/json" }) + const url = URL.createObjectURL(blob) + const a = document.createElement("a") + a.href = url + a.download = "kilo-settings.json" + a.click() + URL.revokeObjectURL(url) + } + window.addEventListener("message", handler) + onCleanup(() => window.removeEventListener("message", handler)) + + // ----- Export ----- + const handleExport = () => { + if (exporting()) return + setExporting(true) + const token = ++epoch + vscode.postMessage({ type: "requestGlobalConfig" }) + setTimeout(() => { + if (epoch === token) setExporting(false) + }, 5000) + } + + // ----- Import ----- + const handleImport = () => { + if (importing()) return + const input = document.createElement("input") + input.type = "file" + input.accept = ".json" + input.style.display = "none" + input.addEventListener("change", () => { + const file = input.files?.[0] + if (!file) return + if (file.size > MAX_IMPORT_SIZE) { + showToast({ variant: "error", title: language.t("settings.aboutKiloCode.importSettings.tooLarge") }) + return + } + setImporting(true) + const reader = new FileReader() + reader.onload = () => { + setImporting(false) + const text = reader.result as string + const result = parseImport(text) + if (!result.ok) { + const key = + result.error === "invalidJson" + ? "settings.aboutKiloCode.importSettings.invalidJson" + : "settings.aboutKiloCode.importSettings.invalidConfig" + showToast({ variant: "error", title: language.t(key) }) + return + } + if (result.warning === "newerVersion") { + showToast({ + variant: "default", + title: language.t("settings.aboutKiloCode.importSettings.newerVersion"), + }) + } + updateConfig(result.config) + showToast({ + variant: "success", + title: language.t("settings.aboutKiloCode.importSettings.success"), + }) + } + reader.onerror = () => { + setImporting(false) + showToast({ variant: "error", title: language.t("settings.aboutKiloCode.importSettings.invalidJson") }) + } + reader.readAsText(file) + }) + document.body.appendChild(input) + input.click() + document.body.removeChild(input) + } + const getStatusColor = () => { switch (props.connectionState) { case "connected": @@ -158,6 +248,31 @@ const AboutKiloCodeTab: Component = (props) => { + {/* Settings Transfer */} +
+

{language.t("settings.aboutKiloCode.settingsTransfer.title")}

+

+ {language.t("settings.aboutKiloCode.settingsTransfer.description")} +

+
+ + +
+
+ {/* legacy-migration start */}

{language.t("settings.aboutKiloCode.legacyMigration.title")}

diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts b/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts new file mode 100644 index 00000000000..ea46f235d27 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts @@ -0,0 +1,130 @@ +import type { Config } from "../../types/messages" +import { deepMerge } from "../../utils/config-utils" + +/** Maximum import file size in bytes (1 MB). */ +export const MAX_IMPORT_SIZE = 1_048_576 + +/** Current export format version. */ +export const META_VERSION = 1 + +/** Top-level keys recognised as valid Config fields. */ +export const KNOWN_KEYS: ReadonlyArray = [ + "permission", + "model", + "small_model", + "default_agent", + "agent", + "provider", + "disabled_providers", + "enabled_providers", + "mcp", + "command", + "instructions", + "skills", + "snapshot", + "share", + "username", + "watcher", + "formatter", + "lsp", + "compaction", + "tools", + "layout", + "experimental", +] + +export type ImportError = "invalidJson" | "invalidConfig" | "tooLarge" +export type ImportWarning = "newerVersion" + +export type ImportResult = { ok: true; config: Config; warning?: ImportWarning } | { ok: false; error: ImportError } + +interface ExportMeta { + version: number + exportedAt: string +} + +// --------------------------------------------------------------------------- +// Export +// --------------------------------------------------------------------------- + +/** + * Build a JSON-serialisable export payload from the current config. + * All fields are included as-is so the export is a complete snapshot + * that can be imported on another instance without re-entering secrets. + */ +export function buildExport(cfg: Config): Record { + const meta: ExportMeta = { + version: META_VERSION, + exportedAt: new Date().toISOString(), + } + + const out: Record = { _meta: meta } + + for (const [key, value] of Object.entries(cfg)) { + if (value === undefined || value === null) continue + out[key] = value + } + + return out +} + +// --------------------------------------------------------------------------- +// Import +// --------------------------------------------------------------------------- + +/** + * Parse a raw JSON string into a validated Config. + * Unknown keys and `_meta` are stripped. Returns an error tag on failure + * or a warning when the file was exported from a newer version. + */ +export function parseImport(json: string): ImportResult { + let data: unknown + try { + data = JSON.parse(json) + } catch { + return { ok: false, error: "invalidJson" } + } + + if (typeof data !== "object" || data === null || Array.isArray(data)) { + return { ok: false, error: "invalidJson" } + } + + const obj = data as Record + + // Check for newer version warning + let warning: ImportWarning | undefined + const meta = obj._meta + if (typeof meta === "object" && meta !== null && !Array.isArray(meta)) { + const version = (meta as Record).version + if (typeof version === "number" && version > META_VERSION) { + warning = "newerVersion" + } + } + + // Keep only known config keys + const config: Record = {} + for (const key of KNOWN_KEYS) { + if (key in obj && obj[key] !== undefined) { + config[key] = obj[key] + } + } + + // Must have at least one known key + if (Object.keys(config).length === 0) { + return { ok: false, error: "invalidConfig" } + } + + return warning ? { ok: true, config: config as Config, warning } : { ok: true, config: config as Config } +} + +// --------------------------------------------------------------------------- +// Merge +// --------------------------------------------------------------------------- + +/** + * Deep-merge imported config on top of existing config. + * Imported values take precedence; existing values not in import are preserved. + */ +export function mergeConfig(existing: Config, imported: Config): Config { + return deepMerge(existing, imported) +} diff --git a/packages/kilo-vscode/webview-ui/src/context/worktree-mode.tsx b/packages/kilo-vscode/webview-ui/src/context/worktree-mode.tsx index 662c5805a46..d1c9ee23b2c 100644 --- a/packages/kilo-vscode/webview-ui/src/context/worktree-mode.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/worktree-mode.tsx @@ -14,22 +14,14 @@ export type SessionMode = "local" | "worktree" interface WorktreeModeContextValue { mode: Accessor setMode: (mode: SessionMode) => void - /** Active pending tab ID (e.g. "pending:1") — set by Agent Manager so PromptInput can key drafts per tab. */ - pendingId: Accessor - setPendingId: (id: string | undefined) => void } const WorktreeModeContext = createContext() export const WorktreeModeProvider: ParentComponent = (props) => { const [mode, setMode] = createSignal("local") - const [pendingId, setPendingId] = createSignal() - return ( - - {props.children} - - ) + return {props.children} } /** diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index b727ed6ae26..ac8b73e92e1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -968,6 +968,16 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "إعادة تعيين جميع إعدادات إضافة Kilo Code إلى قيمها الافتراضية. لا يؤثر هذا على تكوين CLI أو الواجهة الخلفية.", "settings.aboutKiloCode.resetSettings.button": "إعادة تعيين جميع الإعدادات", + "settings.aboutKiloCode.settingsTransfer.title": "نقل الإعدادات", + "settings.aboutKiloCode.settingsTransfer.description": "تصدير أو استيراد إعداداتك لنقلها بين نُسخ VS Code.", + "settings.aboutKiloCode.exportSettings": "تصدير", + "settings.aboutKiloCode.importSettings": "استيراد", + "settings.aboutKiloCode.importSettings.invalidJson": "ملف JSON غير صالح. يرجى اختيار ملف إعدادات صالح.", + "settings.aboutKiloCode.importSettings.invalidConfig": "الملف لا يحتوي على إعدادات Kilo صالحة.", + "settings.aboutKiloCode.importSettings.tooLarge": "الملف كبير جدًا. يجب أن تكون ملفات الإعدادات أقل من 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "تم تصدير هذا الملف من إصدار أحدث من Kilo. قد يتم تجاهل بعض الإعدادات.", + "settings.aboutKiloCode.importSettings.success": "تم استيراد الإعدادات. راجع التغييرات أعلاه، ثم انقر على حفظ.", "settings.agentBehaviour.subtab.modes": "الأوضاع", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 719a3314cba..b2edcbca372 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -977,6 +977,20 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Redefinir todas as configurações da extensão Kilo Code para os valores padrão. Isso não afeta a configuração do CLI ou do backend.", "settings.aboutKiloCode.resetSettings.button": "Redefinir Todas as Configurações", + "settings.aboutKiloCode.settingsTransfer.title": "Transferência de configurações", + "settings.aboutKiloCode.settingsTransfer.description": + "Exporte ou importe suas configurações para transferi-las entre instâncias do VS Code.", + "settings.aboutKiloCode.exportSettings": "Exportar", + "settings.aboutKiloCode.importSettings": "Importar", + "settings.aboutKiloCode.importSettings.invalidJson": + "Arquivo JSON inválido. Selecione um arquivo de configurações válido.", + "settings.aboutKiloCode.importSettings.invalidConfig": "O arquivo não contém configurações válidas do Kilo.", + "settings.aboutKiloCode.importSettings.tooLarge": + "O arquivo é muito grande. Arquivos de configurações devem ter menos de 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Este arquivo foi exportado de uma versão mais recente do Kilo. Algumas configurações podem ser ignoradas.", + "settings.aboutKiloCode.importSettings.success": + "Configurações importadas. Revise as alterações acima e clique em Salvar.", "settings.agentBehaviour.subtab.modes": "Modos", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 451dd808444..2f989657a50 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -981,6 +981,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Resetujte sve postavke Kilo Code ekstenzije na zadane vrijednosti. Ovo ne utiče na CLI ili backend konfiguraciju.", "settings.aboutKiloCode.resetSettings.button": "Resetuj sve postavke", + "settings.aboutKiloCode.settingsTransfer.title": "Prijenos postavki", + "settings.aboutKiloCode.settingsTransfer.description": + "Izvezite ili uvezite postavke za prijenos između VS Code instanci.", + "settings.aboutKiloCode.exportSettings": "Izvezi", + "settings.aboutKiloCode.importSettings": "Uvezi", + "settings.aboutKiloCode.importSettings.invalidJson": "Nevažeći JSON fajl. Odaberite važeći fajl s postavkama.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Fajl ne sadrži važeće Kilo postavke.", + "settings.aboutKiloCode.importSettings.tooLarge": "Fajl je prevelik. Fajlovi s postavkama moraju biti manji od 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Ovaj fajl je izvezen iz novije verzije Kilo-a. Neke postavke mogu biti zanemarene.", + "settings.aboutKiloCode.importSettings.success": + "Postavke su uvezene. Pregledajte promjene iznad, a zatim kliknite Sačuvaj.", "settings.agentBehaviour.subtab.modes": "Modovi", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 3f148b55c83..d0cc455b133 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -975,6 +975,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Nulstil alle Kilo Code-udvidelsesindstillinger til standardværdierne. Dette påvirker ikke CLI- eller backend-konfiguration.", "settings.aboutKiloCode.resetSettings.button": "Nulstil alle indstillinger", + "settings.aboutKiloCode.settingsTransfer.title": "Overførsel af indstillinger", + "settings.aboutKiloCode.settingsTransfer.description": + "Eksportér eller importér dine indstillinger for at overføre dem mellem VS Code-instanser.", + "settings.aboutKiloCode.exportSettings": "Eksportér", + "settings.aboutKiloCode.importSettings": "Importér", + "settings.aboutKiloCode.importSettings.invalidJson": "Ugyldig JSON-fil. Vælg venligst en gyldig indstillingsfil.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Filen indeholder ikke gyldige Kilo-indstillinger.", + "settings.aboutKiloCode.importSettings.tooLarge": "Filen er for stor. Indstillingsfiler skal være under 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Denne fil blev eksporteret fra en nyere version af Kilo. Nogle indstillinger kan blive ignoreret.", + "settings.aboutKiloCode.importSettings.success": + "Indstillinger importeret. Gennemgå ændringerne ovenfor, og klik derefter på Gem.", "settings.agentBehaviour.subtab.modes": "Tilstande", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index e5d5a9fd9c7..8ff2588e346 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -988,6 +988,20 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Alle Kilo Code-Erweiterungseinstellungen auf Standardwerte zurücksetzen. Dies hat keinen Einfluss auf die CLI- oder Backend-Konfiguration.", "settings.aboutKiloCode.resetSettings.button": "Alle Einstellungen zurücksetzen", + "settings.aboutKiloCode.settingsTransfer.title": "Einstellungen übertragen", + "settings.aboutKiloCode.settingsTransfer.description": + "Exportieren oder importieren Sie Ihre Einstellungen, um sie zwischen VS Code-Instanzen zu übertragen.", + "settings.aboutKiloCode.exportSettings": "Exportieren", + "settings.aboutKiloCode.importSettings": "Importieren", + "settings.aboutKiloCode.importSettings.invalidJson": + "Ungültige JSON-Datei. Bitte wählen Sie eine gültige Einstellungsdatei aus.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Die Datei enthält keine gültigen Kilo-Einstellungen.", + "settings.aboutKiloCode.importSettings.tooLarge": + "Die Datei ist zu groß. Einstellungsdateien müssen kleiner als 1 MB sein.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Diese Datei wurde mit einer neueren Version von Kilo exportiert. Einige Einstellungen werden möglicherweise ignoriert.", + "settings.aboutKiloCode.importSettings.success": + "Einstellungen importiert. Überprüfen Sie die obigen Änderungen und klicken Sie dann auf Speichern.", "settings.agentBehaviour.subtab.modes": "Modi", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 1082423fa44..213bded0966 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -974,6 +974,17 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Reset all Kilo Code extension settings to their default values. This does not affect CLI or backend configuration.", "settings.aboutKiloCode.resetSettings.button": "Reset All Settings", + "settings.aboutKiloCode.settingsTransfer.title": "Settings Transfer", + "settings.aboutKiloCode.settingsTransfer.description": + "Export or import your settings to transfer them between VS Code instances.", + "settings.aboutKiloCode.exportSettings": "Export", + "settings.aboutKiloCode.importSettings": "Import", + "settings.aboutKiloCode.importSettings.invalidJson": "Invalid JSON file. Please select a valid settings file.", + "settings.aboutKiloCode.importSettings.invalidConfig": "File does not contain valid Kilo settings.", + "settings.aboutKiloCode.importSettings.tooLarge": "File is too large. Settings files must be under 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "This file was exported from a newer version of Kilo. Some settings may be ignored.", + "settings.aboutKiloCode.importSettings.success": "Settings imported. Review the changes above, then click Save.", "settings.agentBehaviour.subtab.modes": "Modes", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 3c9f4c51751..4302c5187b9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -982,6 +982,20 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Restablecer todas las configuraciones de la extensión Kilo Code a sus valores predeterminados. Esto no afecta la configuración del CLI o del backend.", "settings.aboutKiloCode.resetSettings.button": "Restablecer toda la configuración", + "settings.aboutKiloCode.settingsTransfer.title": "Transferencia de ajustes", + "settings.aboutKiloCode.settingsTransfer.description": + "Exporta o importa tus ajustes para transferirlos entre instancias de VS Code.", + "settings.aboutKiloCode.exportSettings": "Exportar", + "settings.aboutKiloCode.importSettings": "Importar", + "settings.aboutKiloCode.importSettings.invalidJson": + "Archivo JSON no válido. Seleccione un archivo de ajustes válido.", + "settings.aboutKiloCode.importSettings.invalidConfig": "El archivo no contiene ajustes válidos de Kilo.", + "settings.aboutKiloCode.importSettings.tooLarge": + "El archivo es demasiado grande. Los archivos de ajustes deben ser menores de 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Este archivo fue exportado desde una versión más reciente de Kilo. Algunos ajustes podrían ignorarse.", + "settings.aboutKiloCode.importSettings.success": + "Ajustes importados. Revise los cambios anteriores y luego haga clic en Guardar.", "settings.agentBehaviour.subtab.modes": "Modos", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index d0d6974ca07..fb74537fbdb 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -990,6 +990,20 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Réinitialiser tous les paramètres de l'extension Kilo Code à leurs valeurs par défaut. Cela n'affecte pas la configuration CLI ou backend.", "settings.aboutKiloCode.resetSettings.button": "Réinitialiser tous les paramètres", + "settings.aboutKiloCode.settingsTransfer.title": "Transfert des paramètres", + "settings.aboutKiloCode.settingsTransfer.description": + "Exportez ou importez vos paramètres pour les transférer entre instances VS Code.", + "settings.aboutKiloCode.exportSettings": "Exporter", + "settings.aboutKiloCode.importSettings": "Importer", + "settings.aboutKiloCode.importSettings.invalidJson": + "Fichier JSON invalide. Veuillez sélectionner un fichier de paramètres valide.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Le fichier ne contient pas de paramètres Kilo valides.", + "settings.aboutKiloCode.importSettings.tooLarge": + "Le fichier est trop volumineux. Les fichiers de paramètres doivent faire moins de 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Ce fichier a été exporté depuis une version plus récente de Kilo. Certains paramètres pourraient être ignorés.", + "settings.aboutKiloCode.importSettings.success": + "Paramètres importés. Vérifiez les modifications ci-dessus, puis cliquez sur Enregistrer.", "settings.agentBehaviour.subtab.modes": "Modes", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 26d49373271..a8e298ef97e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -974,6 +974,19 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Kilo Code拡張機能のすべての設定をデフォルト値にリセットします。CLIやバックエンドの設定には影響しません。", "settings.aboutKiloCode.resetSettings.button": "すべての設定をリセット", + "settings.aboutKiloCode.settingsTransfer.title": "設定の移行", + "settings.aboutKiloCode.settingsTransfer.description": + "VS Code インスタンス間で設定を転送するには、エクスポートまたはインポートしてください。", + "settings.aboutKiloCode.exportSettings": "エクスポート", + "settings.aboutKiloCode.importSettings": "インポート", + "settings.aboutKiloCode.importSettings.invalidJson": + "無効な JSON ファイルです。有効な設定ファイルを選択してください。", + "settings.aboutKiloCode.importSettings.invalidConfig": "ファイルに有効な Kilo の設定が含まれていません。", + "settings.aboutKiloCode.importSettings.tooLarge": "ファイルが大きすぎます。設定ファイルは 1 MB 以下にしてください。", + "settings.aboutKiloCode.importSettings.newerVersion": + "このファイルはより新しいバージョンの Kilo からエクスポートされたものです。一部の設定が無視される場合があります。", + "settings.aboutKiloCode.importSettings.success": + "設定をインポートしました。上記の変更内容を確認してから、保存をクリックしてください。", "settings.agentBehaviour.subtab.modes": "モード", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 088d8746038..31b562e318b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -973,6 +973,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Kilo Code 확장 프로그램의 모든 설정을 기본값으로 초기화합니다. CLI 또는 백엔드 구성에는 영향을 미치지 않습니다.", "settings.aboutKiloCode.resetSettings.button": "모든 설정 초기화", + "settings.aboutKiloCode.settingsTransfer.title": "설정 이전", + "settings.aboutKiloCode.settingsTransfer.description": + "VS Code 인스턴스 간에 설정을 전송하려면 내보내기 또는 가져오기하세요.", + "settings.aboutKiloCode.exportSettings": "내보내기", + "settings.aboutKiloCode.importSettings": "가져오기", + "settings.aboutKiloCode.importSettings.invalidJson": + "유효하지 않은 JSON 파일입니다. 올바른 설정 파일을 선택해 주세요.", + "settings.aboutKiloCode.importSettings.invalidConfig": "파일에 유효한 Kilo 설정이 포함되어 있지 않습니다.", + "settings.aboutKiloCode.importSettings.tooLarge": "파일이 너무 큽니다. 설정 파일은 1 MB 이하여야 합니다.", + "settings.aboutKiloCode.importSettings.newerVersion": + "이 파일은 더 최신 버전의 Kilo에서 내보낸 것입니다. 일부 설정이 무시될 수 있습니다.", + "settings.aboutKiloCode.importSettings.success": "설정을 가져왔습니다. 위의 변경 사항을 확인한 후 저장을 클릭하세요.", "settings.agentBehaviour.subtab.modes": "모드", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index e1d5e9f4302..f7818d373f2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -975,6 +975,20 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Reset alle instellingen van de Kilo Code extensie naar hun standaardwaarden. Dit heeft geen invloed op CLI of backend configuratie.", "settings.aboutKiloCode.resetSettings.button": "Alle instellingen resetten", + "settings.aboutKiloCode.settingsTransfer.title": "Instellingen overdragen", + "settings.aboutKiloCode.settingsTransfer.description": + "Exporteer of importeer uw instellingen om ze tussen VS Code-instanties over te dragen.", + "settings.aboutKiloCode.exportSettings": "Exporteren", + "settings.aboutKiloCode.importSettings": "Importeren", + "settings.aboutKiloCode.importSettings.invalidJson": + "Ongeldig JSON-bestand. Selecteer een geldig instellingenbestand.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Het bestand bevat geen geldige Kilo-instellingen.", + "settings.aboutKiloCode.importSettings.tooLarge": + "Het bestand is te groot. Instellingenbestanden moeten kleiner zijn dan 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Dit bestand is geëxporteerd vanuit een nieuwere versie van Kilo. Sommige instellingen worden mogelijk genegeerd.", + "settings.aboutKiloCode.importSettings.success": + "Instellingen geïmporteerd. Controleer de bovenstaande wijzigingen en klik vervolgens op Opslaan.", "settings.agentBehaviour.subtab.modes": "Modi", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index ebde27238fa..485a1d0b668 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -978,6 +978,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Tilbakestill alle Kilo Code-utvidelsesinnstillinger til standardverdier. Dette påvirker ikke CLI- eller backend-konfigurasjon.", "settings.aboutKiloCode.resetSettings.button": "Tilbakestill alle innstillinger", + "settings.aboutKiloCode.settingsTransfer.title": "Overføring av innstillinger", + "settings.aboutKiloCode.settingsTransfer.description": + "Eksporter eller importer innstillingene dine for å overføre dem mellom VS Code-instanser.", + "settings.aboutKiloCode.exportSettings": "Eksporter", + "settings.aboutKiloCode.importSettings": "Importer", + "settings.aboutKiloCode.importSettings.invalidJson": "Ugyldig JSON-fil. Vennligst velg en gyldig innstillingsfil.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Filen inneholder ikke gyldige Kilo-innstillinger.", + "settings.aboutKiloCode.importSettings.tooLarge": "Filen er for stor. Innstillingsfiler må være under 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Denne filen ble eksportert fra en nyere versjon av Kilo. Noen innstillinger kan bli ignorert.", + "settings.aboutKiloCode.importSettings.success": + "Innstillinger importert. Gjennomgå endringene ovenfor, og klikk deretter på Lagre.", "settings.agentBehaviour.subtab.modes": "Moduser", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index 049bec1e0d7..5db2913b2cf 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -979,6 +979,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Zresetuj wszystkie ustawienia rozszerzenia Kilo Code do wartości domyślnych. Nie wpływa to na konfigurację CLI ani backendu.", "settings.aboutKiloCode.resetSettings.button": "Resetuj wszystkie ustawienia", + "settings.aboutKiloCode.settingsTransfer.title": "Przenoszenie ustawień", + "settings.aboutKiloCode.settingsTransfer.description": + "Eksportuj lub importuj ustawienia, aby przenosić je między instancjami VS Code.", + "settings.aboutKiloCode.exportSettings": "Eksportuj", + "settings.aboutKiloCode.importSettings": "Importuj", + "settings.aboutKiloCode.importSettings.invalidJson": "Nieprawidłowy plik JSON. Wybierz prawidłowy plik ustawień.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Plik nie zawiera prawidłowych ustawień Kilo.", + "settings.aboutKiloCode.importSettings.tooLarge": "Plik jest za duży. Pliki ustawień muszą mieć mniej niż 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Ten plik został wyeksportowany z nowszej wersji Kilo. Niektóre ustawienia mogą zostać zignorowane.", + "settings.aboutKiloCode.importSettings.success": + "Ustawienia zaimportowane. Przejrzyj powyższe zmiany, a następnie kliknij Zapisz.", "settings.agentBehaviour.subtab.modes": "Tryby", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 128e711f201..c9b42fbe0a4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -981,6 +981,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Сбросить все настройки расширения Kilo Code до значений по умолчанию. Это не влияет на конфигурацию CLI или бэкенда.", "settings.aboutKiloCode.resetSettings.button": "Сбросить все настройки", + "settings.aboutKiloCode.settingsTransfer.title": "Перенос настроек", + "settings.aboutKiloCode.settingsTransfer.description": + "Экспортируйте или импортируйте настройки для переноса между экземплярами VS Code.", + "settings.aboutKiloCode.exportSettings": "Экспорт", + "settings.aboutKiloCode.importSettings": "Импорт", + "settings.aboutKiloCode.importSettings.invalidJson": "Недопустимый файл JSON. Выберите корректный файл настроек.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Файл не содержит допустимых настроек Kilo.", + "settings.aboutKiloCode.importSettings.tooLarge": "Файл слишком большой. Файлы настроек должны быть менее 1 MB.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Этот файл был экспортирован из более новой версии Kilo. Некоторые настройки могут быть проигнорированы.", + "settings.aboutKiloCode.importSettings.success": + "Настройки импортированы. Просмотрите изменения выше и нажмите «Сохранить».", "settings.agentBehaviour.subtab.modes": "Режимы", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 3e1a51b665f..7d812f4fb1a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -969,6 +969,17 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "รีเซ็ตการตั้งค่าส่วนขยาย Kilo Code ทั้งหมดเป็นค่าเริ่มต้น ไม่ส่งผลกระทบต่อการกำหนดค่า CLI หรือแบ็กเอนด์", "settings.aboutKiloCode.resetSettings.button": "รีเซ็ตการตั้งค่าทั้งหมด", + "settings.aboutKiloCode.settingsTransfer.title": "ถ่ายโอนการตั้งค่า", + "settings.aboutKiloCode.settingsTransfer.description": + "ส่งออกหรือนำเข้าการตั้งค่าเพื่อถ่ายโอนระหว่างอินสแตนซ์ VS Code", + "settings.aboutKiloCode.exportSettings": "ส่งออก", + "settings.aboutKiloCode.importSettings": "นำเข้า", + "settings.aboutKiloCode.importSettings.invalidJson": "ไฟล์ JSON ไม่ถูกต้อง กรุณาเลือกไฟล์การตั้งค่าที่ถูกต้อง", + "settings.aboutKiloCode.importSettings.invalidConfig": "ไฟล์ไม่มีการตั้งค่า Kilo ที่ถูกต้อง", + "settings.aboutKiloCode.importSettings.tooLarge": "ไฟล์มีขนาดใหญ่เกินไป ไฟล์การตั้งค่าต้องมีขนาดไม่เกิน 1 MB", + "settings.aboutKiloCode.importSettings.newerVersion": + "ไฟล์นี้ถูกส่งออกจาก Kilo เวอร์ชันใหม่กว่า การตั้งค่าบางรายการอาจถูกข้ามไป", + "settings.aboutKiloCode.importSettings.success": "นำเข้าการตั้งค่าแล้ว ตรวจสอบการเปลี่ยนแปลงด้านบน จากนั้นคลิกบันทึก", "settings.agentBehaviour.subtab.modes": "โหมด", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 85cbbca10fe..07ffb9711bd 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -978,6 +978,18 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "Tüm Kilo Code uzantı ayarlarını varsayılan değerlerine sıfırla. Bu, CLI veya arka uç yapılandırmasını etkilemez.", "settings.aboutKiloCode.resetSettings.button": "Tüm Ayarları Sıfırla", + "settings.aboutKiloCode.settingsTransfer.title": "Ayar Aktarımı", + "settings.aboutKiloCode.settingsTransfer.description": + "Ayarlarınızı VS Code örnekleri arasında aktarmak için dışa veya içe aktarın.", + "settings.aboutKiloCode.exportSettings": "Dışa Aktar", + "settings.aboutKiloCode.importSettings": "İçe Aktar", + "settings.aboutKiloCode.importSettings.invalidJson": "Geçersiz JSON dosyası. Lütfen geçerli bir ayar dosyası seçin.", + "settings.aboutKiloCode.importSettings.invalidConfig": "Dosya geçerli Kilo ayarları içermiyor.", + "settings.aboutKiloCode.importSettings.tooLarge": "Dosya çok büyük. Ayar dosyaları 1 MB altında olmalıdır.", + "settings.aboutKiloCode.importSettings.newerVersion": + "Bu dosya Kilo'nun daha yeni bir sürümünden dışa aktarılmış. Bazı ayarlar göz ardı edilebilir.", + "settings.aboutKiloCode.importSettings.success": + "Ayarlar içe aktarıldı. Yukarıdaki değişiklikleri gözden geçirin, ardından Kaydet'e tıklayın.", "settings.agentBehaviour.subtab.modes": "Modlar", "settings.agentBehaviour.subtab.agents": "Ajanlar", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 43dd9ff31ac..ca2b27cb8ff 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -961,6 +961,15 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "将所有 Kilo Code 扩展设置重置为默认值。这不会影响 CLI 或后端配置。", "settings.aboutKiloCode.resetSettings.button": "重置所有设置", + "settings.aboutKiloCode.settingsTransfer.title": "设置迁移", + "settings.aboutKiloCode.settingsTransfer.description": "导出或导入设置,以便在 VS Code 实例之间传输。", + "settings.aboutKiloCode.exportSettings": "导出", + "settings.aboutKiloCode.importSettings": "导入", + "settings.aboutKiloCode.importSettings.invalidJson": "无效的 JSON 文件。请选择有效的设置文件。", + "settings.aboutKiloCode.importSettings.invalidConfig": "文件不包含有效的 Kilo 设置。", + "settings.aboutKiloCode.importSettings.tooLarge": "文件过大。设置文件必须小于 1 MB。", + "settings.aboutKiloCode.importSettings.newerVersion": "此文件由较新版本的 Kilo 导出。部分设置可能会被忽略。", + "settings.aboutKiloCode.importSettings.success": "设置已导入。请查看上方的更改,然后点击保存。", "settings.agentBehaviour.subtab.modes": "模式", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index b29daf739e6..635328ba589 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -963,6 +963,15 @@ export const dict = { "settings.aboutKiloCode.resetSettings.description": "將所有 Kilo Code 擴充功能設定重置為預設值。這不會影響 CLI 或後端設定。", "settings.aboutKiloCode.resetSettings.button": "重置所有設定", + "settings.aboutKiloCode.settingsTransfer.title": "設定轉移", + "settings.aboutKiloCode.settingsTransfer.description": "匯出或匯入設定,以便在 VS Code 實例之間轉移。", + "settings.aboutKiloCode.exportSettings": "匯出", + "settings.aboutKiloCode.importSettings": "匯入", + "settings.aboutKiloCode.importSettings.invalidJson": "無效的 JSON 檔案。請選擇有效的設定檔。", + "settings.aboutKiloCode.importSettings.invalidConfig": "檔案不包含有效的 Kilo 設定。", + "settings.aboutKiloCode.importSettings.tooLarge": "檔案過大。設定檔必須小於 1 MB。", + "settings.aboutKiloCode.importSettings.newerVersion": "此檔案由較新版本的 Kilo 匯出。部分設定可能會被忽略。", + "settings.aboutKiloCode.importSettings.success": "設定已匯入。請檢視上方的變更,然後點擊儲存。", "settings.agentBehaviour.subtab.modes": "模式", "settings.agentBehaviour.subtab.agents": "Agents", diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index c99670c4ea1..ccec9dd04de 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -722,6 +722,11 @@ export interface ConfigUpdatedMessage { config: Config } +export interface GlobalConfigLoadedMessage { + type: "globalConfigLoaded" + config: Config +} + export interface NotificationSettingsLoadedMessage { type: "notificationSettingsLoaded" settings: { @@ -1260,6 +1265,7 @@ export type ExtensionMessage = | BrowserSettingsLoadedMessage | ConfigLoadedMessage | ConfigUpdatedMessage + | GlobalConfigLoadedMessage | NotificationSettingsLoadedMessage | NotificationsLoadedMessage | AgentManagerSessionMetaMessage @@ -1568,6 +1574,10 @@ export interface RequestConfigMessage { type: "requestConfig" } +export interface RequestGlobalConfigMessage { + type: "requestGlobalConfig" +} + export interface UpdateConfigMessage { type: "updateConfig" config: Partial @@ -1998,6 +2008,7 @@ export type WebviewMessage = | UpdateSettingRequest | RequestBrowserSettingsMessage | RequestConfigMessage + | RequestGlobalConfigMessage | UpdateConfigMessage | RequestNotificationSettingsMessage | ResetAllSettingsRequest