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
This commit is contained in:
Marius
2026-03-27 17:45:15 +01:00
committed by GitHub
parent 57aa3396f9
commit a0e1fd8282
28 changed files with 949 additions and 101 deletions
+14
View File
@@ -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<void> {
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
@@ -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)
@@ -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(
@@ -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<string, unknown>)._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<string, unknown>).unknownField).toBeUndefined()
expect((result.config as Record<string, unknown>).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<string, unknown> = {}
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))
})
})
@@ -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<string | undefined>()
// 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<string | null>(null)
let pendingDeleteTimer: ReturnType<typeof setTimeout> | 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()
@@ -78,7 +78,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
})
const history = usePromptHistory()
const sessionKey = () => session.currentSessionID() ?? worktree?.pendingId() ?? "__new__"
const sessionKey = () => session.currentSessionID() ?? "__new__"
const [text, setText] = createSignal("")
const [reviewComments, setReviewComments] = createSignal<ReviewComment[]>([])
@@ -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<AboutKiloCodeTabProps> = (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<AboutKiloCodeTabProps> = (props) => {
</div>
</div>
{/* Settings Transfer */}
<div style={sectionStyle}>
<h4 style={headingStyle}>{language.t("settings.aboutKiloCode.settingsTransfer.title")}</h4>
<p
style={{
"font-size": "12px",
color: "var(--vscode-descriptionForeground)",
margin: "0 0 12px 0",
"line-height": "1.5",
}}
>
{language.t("settings.aboutKiloCode.settingsTransfer.description")}
</p>
<div style={{ display: "flex", gap: "8px" }}>
<Button variant="secondary" size="small" onClick={handleExport}>
<Icon name="cloud-upload" />
{language.t("settings.aboutKiloCode.exportSettings")}
</Button>
<Button variant="secondary" size="small" onClick={handleImport} disabled={importing()}>
<Icon name="download" />
{language.t("settings.aboutKiloCode.importSettings")}
</Button>
</div>
</div>
{/* legacy-migration start */}
<div style={{ ...sectionStyle, "margin-bottom": "0" }}>
<h4 style={headingStyle}>{language.t("settings.aboutKiloCode.legacyMigration.title")}</h4>
@@ -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<string> = [
"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<string, unknown> {
const meta: ExportMeta = {
version: META_VERSION,
exportedAt: new Date().toISOString(),
}
const out: Record<string, unknown> = { _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<string, unknown>
// 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<string, unknown>).version
if (typeof version === "number" && version > META_VERSION) {
warning = "newerVersion"
}
}
// Keep only known config keys
const config: Record<string, unknown> = {}
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)
}
@@ -14,22 +14,14 @@ export type SessionMode = "local" | "worktree"
interface WorktreeModeContextValue {
mode: Accessor<SessionMode>
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<string | undefined>
setPendingId: (id: string | undefined) => void
}
const WorktreeModeContext = createContext<WorktreeModeContextValue>()
export const WorktreeModeProvider: ParentComponent = (props) => {
const [mode, setMode] = createSignal<SessionMode>("local")
const [pendingId, setPendingId] = createSignal<string | undefined>()
return (
<WorktreeModeContext.Provider value={{ mode, setMode, pendingId, setPendingId }}>
{props.children}
</WorktreeModeContext.Provider>
)
return <WorktreeModeContext.Provider value={{ mode, setMode }}>{props.children}</WorktreeModeContext.Provider>
}
/**
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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",
@@ -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<Config>
@@ -1998,6 +2008,7 @@ export type WebviewMessage =
| UpdateSettingRequest
| RequestBrowserSettingsMessage
| RequestConfigMessage
| RequestGlobalConfigMessage
| UpdateConfigMessage
| RequestNotificationSettingsMessage
| ResetAllSettingsRequest