+
@@ -2039,17 +2052,22 @@ ToolRegistry.register({
const canOpenDiff = () => !!data.openDiff && !!path() && (before() !== "" || after() !== "")
const canOpenFile = () => !!data.openFile && !!path()
+ const openDiff = () => {
+ if (!canOpenDiff()) return
+ data.openDiff!({
+ file: path(),
+ before: before(),
+ after: after(),
+ additions: props.metadata?.filediff?.additions ?? 0,
+ deletions: props.metadata?.filediff?.deletions ?? 0,
+ })
+ }
+
const handleFileClick = (e: MouseEvent) => {
e.stopPropagation()
if (canOpenDiff()) {
- data.openDiff!({
- file: path(),
- before: before(),
- after: after(),
- additions: props.metadata?.filediff?.additions ?? 0,
- deletions: props.metadata?.filediff?.deletions ?? 0,
- })
+ openDiff()
return
}
@@ -2058,6 +2076,11 @@ ToolRegistry.register({
}
}
+ const handleOpenDiffClick = (e: MouseEvent) => {
+ e.stopPropagation()
+ openDiff()
+ }
+
return (
+
+
+
+ e.preventDefault()}
+ onClick={handleOpenDiffClick}
+ aria-label={i18n.t("ui.messagePart.openInDiffViewer")}
+ />
+
+
+
}
>
diff --git a/packages/kilo-ui/src/stories/message-part.stories.tsx b/packages/kilo-ui/src/stories/message-part.stories.tsx
index e67fe32b44..8c256487b1 100644
--- a/packages/kilo-ui/src/stories/message-part.stories.tsx
+++ b/packages/kilo-ui/src/stories/message-part.stories.tsx
@@ -163,6 +163,39 @@ const errorToolPart: ToolPart = {
},
}
+// Completed edit tool with full filediff metadata — exercises canOpenDiff()
+// so the "Open in Diff Viewer" icon button renders in the trigger.
+const editCompletedPart: ToolPart = {
+ id: "part-tool-edit-done",
+ sessionID: SESSION_ID,
+ messageID: ASST_MSG_ID,
+ type: "tool",
+ callID: "call-edit-done",
+ tool: "edit",
+ state: {
+ status: "completed",
+ input: {
+ filePath: "src/counter.tsx",
+ oldString: "const [count, setCount] = createSignal(0)",
+ newString: "const [count, setCount] = createSignal(1)",
+ },
+ output: "File edited successfully",
+ title: "Edit file",
+ metadata: {
+ filediff: {
+ file: "src/counter.tsx",
+ before:
+ "import { createSignal } from 'solid-js'\n\nexport function Counter() {\n const [count, setCount] = createSignal(0)\n return
setCount(count() + 1)}>{count()} \n}\n",
+ after:
+ "import { createSignal } from 'solid-js'\n\nexport function Counter() {\n const [count, setCount] = createSignal(1)\n return
setCount(count() + 1)}>{count()} \n}\n",
+ additions: 1,
+ deletions: 1,
+ },
+ },
+ time: { start: now - 4000, end: now - 3500 },
+ },
+}
+
// --- Reasoning part ---
const reasoningPart: ReasoningPart = {
@@ -208,10 +241,12 @@ const mockDataReasoning = createMockData([reasoningPart, textPart])
const mockDataBash = createMockData([bashCompleted])
// Three context-group tools — exercises ContextToolGroupHeader collapse path
const mockDataContextGroup = createMockData([completedToolPart, grepCompleted, globCompleted, textPart])
+// Completed edit tool with filediff — exercises the "Open in Diff Viewer" button path
+const mockDataEdit = createMockData([editCompletedPart])
-function AllProviders(props: { children: any; data?: MockData }) {
+function AllProviders(props: { children: any; data?: MockData; onOpenDiff?: () => void }) {
return (
-
+
@@ -354,6 +389,25 @@ export const WithContextGroup: Story = {
),
}
+// --- Completed edit tool with filediff → "Open in Diff Viewer" icon visible ---
+//
+// CSS :hover cannot be triggered reliably from Storybook `play` functions,
+// so the action slot is force-revealed via a scoped style override. This
+// captures the layout with the icon present so the visual regression
+// suite catches any regression in:
+// - the `:has()` parent-grow rule on basic-tool-tool-info
+// - the `margin-left: auto` pushing the action to the right
+// - the icon-button ghost variant in the tool-trigger row
+export const WithEditToolOpenDiffAction: Story = {
+ name: "WithEditTool (open-diff action visible)",
+ render: () => (
+ {}}>
+
+
+
+ ),
+}
+
// --- All 5 tool hint error types in a single screenshot ---
const hintErrors: ToolPart[] = [
diff --git a/packages/kilo-vscode/CHANGELOG.md b/packages/kilo-vscode/CHANGELOG.md
index 23d17b8a8f..44cf78391f 100644
--- a/packages/kilo-vscode/CHANGELOG.md
+++ b/packages/kilo-vscode/CHANGELOG.md
@@ -1,5 +1,31 @@
# kilo-code
+## 7.2.33
+
+### Minor Changes
+
+- [#9737](https://github.com/Kilo-Org/kilocode/pull/9737) [`d5fb9eb`](https://github.com/Kilo-Org/kilocode/commit/d5fb9eb2265c03127e776c99020b03bb770255a1) - Support starting Agent Manager local sessions and worktree sessions from an experimental agent tool.
+
+- [#9704](https://github.com/Kilo-Org/kilocode/pull/9704) [`22b5283`](https://github.com/Kilo-Org/kilocode/commit/22b5283f79e4dd8461764904b908001df969cce9) - Keep reasoning blocks expanded by default and add a setting to auto-collapse them after completion.
+
+- [#9708](https://github.com/Kilo-Org/kilocode/pull/9708) [`f2db4d1`](https://github.com/Kilo-Org/kilocode/commit/f2db4d165cacc8b52148e7fe77fd4af7ff71b7bc) - Add a Display setting to collapse terminal command blocks by default instead of keeping them expanded.
+
+### Patch Changes
+
+- [#9317](https://github.com/Kilo-Org/kilocode/pull/9317) [`ce4a595`](https://github.com/Kilo-Org/kilocode/commit/ce4a595cdcba5074b0ebdf74f24213a8322f2cc7) - Dispose autocomplete editor listeners when inline completions are disabled.
+
+- [#9651](https://github.com/Kilo-Org/kilocode/pull/9651) [`bc91af3`](https://github.com/Kilo-Org/kilocode/commit/bc91af398db6e52e4a6bbd1d26efce1b306d4697) - Center the sidebar prompt toolbar on wider sidebars and hide unavailable toolbar actions.
+
+- [#9753](https://github.com/Kilo-Org/kilocode/pull/9753) [`fb11c7b`](https://github.com/Kilo-Org/kilocode/commit/fb11c7b56d49cdd52f8ee96fdfc9ce30d2c883b7) - Center welcome notifications in wide VS Code views.
+
+- [#9460](https://github.com/Kilo-Org/kilocode/pull/9460) [`26e4c11`](https://github.com/Kilo-Org/kilocode/commit/26e4c1148f4e7a734bb8e535e02a1a9ad75be584) - Scope the custom commit message prompt to the current project. Setting it in the VS Code settings now writes to the workspace's `kilo.json` so different repositories can have different conventions, instead of silently applying globally. Also fixes the project-level config update endpoint, which previously wrote to a file that wasn't loaded.
+
+- [#9742](https://github.com/Kilo-Org/kilocode/pull/9742) [`a076e33`](https://github.com/Kilo-Org/kilocode/commit/a076e336b34f897a6b9463b48d2181d90f9dd997) - Preserve Agent Manager local sessions across panel restarts when session refreshes complete out of order.
+
+- [#9733](https://github.com/Kilo-Org/kilocode/pull/9733) [`e9f2760`](https://github.com/Kilo-Org/kilocode/commit/e9f2760d8e9a93d4b71dba6ef5aae522cb4fe263) - Support configuring custom agent tool permissions from the VS Code agent editor.
+
+- [#9669](https://github.com/Kilo-Org/kilocode/pull/9669) [`0bf14eb`](https://github.com/Kilo-Org/kilocode/commit/0bf14eb2ff5ef59f9dc98342218addc670a87481) - Stop emitting `ai.*` and `gen_ai.*` OpenTelemetry spans from AI SDK calls, and remove the PostHog bridge that forwarded them. Tool/session/indexing telemetry is unchanged.
+
## 7.2.31
### Patch Changes
diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts
index bf9d8872c8..3b0ebc44b2 100644
--- a/packages/kilo-vscode/src/KiloProvider.ts
+++ b/packages/kilo-vscode/src/KiloProvider.ts
@@ -827,7 +827,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
)
break
case "updateConfig":
- await this.handleUpdateConfig(message.config)
+ await this.handleUpdateConfig(message.config, message.projectConfig)
break
case "openSettingsTab":
if (message.tab === "indexing") {
@@ -2303,12 +2303,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
return getBusySessionCount(this.sessionStatusMap)
}
- /**
- * Handle config update request from the webview.
- * Applies a partial config update via the global config endpoint, then pushes
- * the full merged config back to the webview.
- */
- private async handleUpdateConfig(partial: Partial): Promise {
+ private async handleUpdateConfig(partial: Partial, project: Partial = {}): Promise {
if (!this.client || this.connectionState !== "connected") {
this.postMessage({ type: "configUpdateFailed", message: "Not connected to CLI backend" })
return
@@ -2318,40 +2313,34 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
partial.provider !== undefined ||
partial.disabled_providers !== undefined ||
partial.enabled_providers !== undefined
+ const hasGlobal = Object.keys(partial).length > 0
+ const hasProject = Object.keys(project).length > 0
- // Guard against fetchAndSendConfig pushing stale data while the write is in flight.
this.pending++
+ const dir = this.getWorkspaceDirectory()
- // Phase 1: write. Errors here = real save failures the user can fix + retry.
try {
await this.connectionService.drainPendingPrompts()
- await this.client.global.config.update({ config: partial }, { throwOnError: true })
+ if (hasGlobal) await this.client.global.config.update({ config: partial }, { throwOnError: true })
+ if (hasProject) await this.client.config.update({ config: project, directory: dir }, { throwOnError: true })
} catch (error) {
- console.error("[Kilo New] KiloProvider: Failed to update config:", error)
- this.postMessage({
- type: "configUpdateFailed",
- message: getErrorMessage(error) || "Failed to update config",
- details: getConfigErrorDetails(error),
- })
+ this.postConfigFailure(error)
this.pending--
return
}
- // Phase 2: refresh. Config is already on disk — post-write errors are
- // transient, so send an optimistic configUpdated to clear the webview's
- // saving/draft state. SSE global.config.updated pushes the real data next.
try {
- const dir = this.getWorkspaceDirectory()
const { data: merged } = await retry(() => this.client!.config.get({ directory: dir }, { throwOnError: true }))
this.cachedConfigMessage = { type: "configLoaded", config: merged, features: configFeatures(merged) }
this.postMessage({ type: "configUpdated", config: merged, features: configFeatures(merged) })
if (refreshProviders) await this.fetchAndSendProviders()
} catch (error) {
console.error("[Kilo New] KiloProvider: Config write succeeded but post-write refresh failed:", error)
+ const patch = { ...partial, ...project }
const cached = (this.cachedConfigMessage as { config?: unknown } | null)?.config
const features = (this.cachedConfigMessage as { features?: unknown } | null)?.features
const optimistic =
- cached && typeof cached === "object" ? { ...(cached as Record), ...partial } : partial
+ cached && typeof cached === "object" ? { ...(cached as Record), ...patch } : patch
this.postMessage({
type: "configUpdated",
config: optimistic,
@@ -2362,6 +2351,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}
+ private postConfigFailure(error: unknown): void {
+ console.error("[Kilo New] KiloProvider: Failed to update config:", error)
+ this.postMessage({
+ type: "configUpdateFailed",
+ message: getErrorMessage(error) || "Failed to update config",
+ details: getConfigErrorDetails(error),
+ })
+ }
+
private async resolveSession(sessionID?: string, draftID?: string, context?: string) {
if (!this.client) return undefined
diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
index 788248d17d..2bce7ae94d 100644
--- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
+++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts
@@ -29,6 +29,7 @@ import { continueInWorktree } from "./continue-in-worktree"
import { WorktreeDiffController } from "./worktree-diff-controller"
import { WorktreeImporter } from "./worktree-importer"
import { diffSummary as localDiffSummary, diffFile as localDiffFile } from "./local-diff"
+import { parseToolRequest, startFromTool, type ToolRequest } from "./tool-start"
import { buildKeybindingMap } from "./format-keybinding"
import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version"
@@ -65,6 +66,7 @@ export class AgentManagerProvider implements Disposable {
private staleWorktreeIds = new Set()
private cachedWorktreeStats: { type: "agentManager.worktreeStats"; stats: WorktreeStats[] } | undefined
private cachedLocalStats: { type: "agentManager.localStats"; stats: LocalStats } | undefined
+ private unsubTool: (() => void) | undefined
/** Session ID most recently loaded via a `loadMessages` message from the webview.
* Updated synchronously — unlike the session provider's currentSession which depends on
@@ -152,6 +154,10 @@ export class AgentManagerProvider implements Disposable {
log: (...a) => this.log(...a),
semaphore,
})
+ this.unsubTool = this.connectionService.onEventFiltered(
+ (event) => (event as { type?: string }).type === "kilocode.agent_manager.start",
+ (event, directory) => this.onToolEvent(event, directory),
+ )
}
private log(...args: unknown[]) {
@@ -159,11 +165,11 @@ export class AgentManagerProvider implements Disposable {
this.outputChannel.appendLine(`${new Date().toISOString()} ${msg}`)
}
- public openPanel(): void {
+ public openPanel(preserveFocus?: boolean): void {
if (this.panel) {
this.log("Panel already open, revealing")
- this.panel.reveal()
- this.postToWebview({ type: "action", action: "focusInput" })
+ this.panel.reveal(preserveFocus)
+ if (!preserveFocus) this.postToWebview({ type: "action", action: "focusInput" })
return
}
this.log("Opening Agent Manager panel")
@@ -792,6 +798,43 @@ export class AgentManagerProvider implements Disposable {
await this.stateReady.catch((err) => this.log(`${context}: stateReady rejected, continuing:`, err))
}
+ private onToolEvent(event: unknown, directory?: string): void {
+ const properties = (event as { properties?: unknown }).properties
+ const req = parseToolRequest(properties)
+ if (!req) return
+ if (directory) req.directory = directory
+ void this.startToolRequest(req)
+ }
+
+ private async startToolRequest(req: ToolRequest): Promise {
+ await startFromTool(
+ {
+ getClient: () => this.connectionService.getClient(),
+ getRoot: () => this.getRoot(),
+ getState: () => this.getStateManager(),
+ getPanel: () => this.panel,
+ openPanel: (preserveFocus) => this.openPanel(preserveFocus),
+ waitReady: (context) => this.waitForStateReady(context),
+ createWorktree: (opts) => this.createWorktreeOnDisk(opts),
+ cleanupWorktree: async (wid, dir) => {
+ this.getStateManager()?.removeWorktree(wid)
+ await this.getWorktreeManager()?.removeWorktree(dir)
+ this.pushState()
+ },
+ setup: (dir, branch, id) => this.runSetupScriptForWorktree(dir, branch, id),
+ createSessionInWorktree: (dir, branch, id) => this.createSessionInWorktree(dir, branch, id),
+ registerWorktreeSession: (sid, dir) => this.registerWorktreeSession(sid, dir),
+ notifyReady: (sid, result, wid) => this.notifyWorktreeReady(sid, result, wid),
+ push: () => this.pushState(),
+ post: (msg) => this.postToWebview(msg as AgentManagerOutMessage),
+ capture: (event, props) => this.host.capture(event, props),
+ log: (...args) => this.log(...args),
+ error: (msg) => this.host.showError(msg),
+ },
+ req,
+ )
+ }
+
// ---------------------------------------------------------------------------
// Worktree actions
// ---------------------------------------------------------------------------
@@ -1565,6 +1608,7 @@ export class AgentManagerProvider implements Disposable {
}
public dispose(): void {
+ this.unsubTool?.()
this.connectionService.unregisterFocused("agent-manager")
this.connectionService.registerOpen("agent-manager", [])
this.diffs.stop()
diff --git a/packages/kilo-vscode/src/agent-manager/setup-script-template.ts b/packages/kilo-vscode/src/agent-manager/setup-script-template.ts
index 64224d1613..01ec67fa7f 100644
--- a/packages/kilo-vscode/src/agent-manager/setup-script-template.ts
+++ b/packages/kilo-vscode/src/agent-manager/setup-script-template.ts
@@ -7,8 +7,12 @@ export const SETUP_SCRIPT_TEMPLATE = `#!/bin/sh
# WORKTREE_PATH - Absolute path to the worktree directory
# REPO_PATH - Absolute path to the main repository
#
+# Kilo already copies root-level .env and .env.* files before this script runs.
+# Use this script for dependencies, nested env files, local config, databases,
+# certificates, or other project-specific setup that is not committed to git.
+#
# Example tasks:
-# - Copy .env files from main repo
+# - Copy nested env files from main repo
# - Install dependencies
# - Run database migrations
# - Set up local configuration
@@ -19,10 +23,10 @@ echo "Setting up worktree: $WORKTREE_PATH"
# Uncomment and modify as needed:
-# Copy environment files
-# if [ -f "$REPO_PATH/.env" ]; then
-# cp "$REPO_PATH/.env" "$WORKTREE_PATH/.env"
-# echo "Copied .env"
+# Copy a nested environment file
+# if [ -f "$REPO_PATH/apps/web/.env.local" ] && [ ! -f "$WORKTREE_PATH/apps/web/.env.local" ]; then
+# cp "$REPO_PATH/apps/web/.env.local" "$WORKTREE_PATH/apps/web/.env.local"
+# echo "Copied apps/web/.env.local"
# fi
# Install dependencies (Node.js)
@@ -48,8 +52,12 @@ export const SETUP_SCRIPT_TEMPLATE_POWERSHELL = `# Kilo Code Worktree Setup Scri
# $env:WORKTREE_PATH - Absolute path to the worktree directory
# $env:REPO_PATH - Absolute path to the main repository
#
+# Kilo already copies root-level .env and .env.* files before this script runs.
+# Use this script for dependencies, nested env files, local config, databases,
+# certificates, or other project-specific setup that is not committed to git.
+#
# Example tasks:
-# - Copy .env files from main repo
+# - Copy nested env files from main repo
# - Install dependencies
# - Run database migrations
# - Set up local configuration
@@ -60,10 +68,10 @@ Write-Host "Setting up worktree: $env:WORKTREE_PATH"
# Uncomment and modify as needed:
-# Copy environment files
-# if (Test-Path "$env:REPO_PATH/.env") {
-# Copy-Item "$env:REPO_PATH/.env" "$env:WORKTREE_PATH/.env" -Force
-# Write-Host "Copied .env"
+# Copy a nested environment file
+# if ((Test-Path "$env:REPO_PATH/apps/web/.env.local") -and !(Test-Path "$env:WORKTREE_PATH/apps/web/.env.local")) {
+# Copy-Item "$env:REPO_PATH/apps/web/.env.local" "$env:WORKTREE_PATH/apps/web/.env.local"
+# Write-Host "Copied apps/web/.env.local"
# }
# Install dependencies (Node.js)
diff --git a/packages/kilo-vscode/src/agent-manager/tool-start.ts b/packages/kilo-vscode/src/agent-manager/tool-start.ts
new file mode 100644
index 0000000000..51b9b0bdcb
--- /dev/null
+++ b/packages/kilo-vscode/src/agent-manager/tool-start.ts
@@ -0,0 +1,257 @@
+import type { KiloClient, Session } from "@kilocode/sdk/v2/client"
+import { sanitizeBranchName, versionedName } from "./branch-name"
+import type { CreateWorktreeResult } from "./WorktreeManager"
+import type { WorktreeStateManager } from "./WorktreeStateManager"
+import type { PanelContext } from "./host"
+import { PLATFORM } from "./constants"
+
+const LABEL_MAX = 28
+const PREFIX = new Set(["feat", "fix", "chore", "bug", "issue", "task", "branch"])
+
+export interface ToolTask {
+ prompt?: string
+ name?: string
+ branchName?: string
+}
+
+export interface ToolRequest {
+ requestID: string
+ sessionID?: string
+ directory?: string
+ mode: "worktree" | "local"
+ versions?: boolean
+ tasks: ToolTask[]
+}
+
+interface WorktreeCreated {
+ worktree: ReturnType
+ result: CreateWorktreeResult
+}
+
+export interface ToolDeps {
+ getClient: () => KiloClient
+ getRoot: () => string | undefined
+ getState: () => WorktreeStateManager | undefined
+ getPanel: () => PanelContext | undefined
+ openPanel: (preserveFocus?: boolean) => void
+ waitReady: (context: string) => Promise
+ createWorktree: (opts: {
+ groupId?: string
+ branchName?: string
+ name?: string
+ label?: string
+ }) => Promise
+ cleanupWorktree: (wid: string, dir: string) => Promise
+ setup: (dir: string, branch?: string, id?: string) => Promise
+ createSessionInWorktree: (dir: string, branch: string, id?: string) => Promise
+ registerWorktreeSession: (sid: string, dir: string) => void
+ notifyReady: (sid: string, result: CreateWorktreeResult, wid?: string) => void
+ push: () => void
+ post: (msg: unknown) => void
+ capture: (event: string, props?: Record) => void
+ log: (...args: unknown[]) => void
+ error: (msg: string) => void
+}
+
+function text(task: ToolTask): string | undefined {
+ return task.prompt?.trim() || undefined
+}
+
+function clean(value: string | undefined): string | undefined {
+ return value?.trim() || undefined
+}
+
+function label(value: string | undefined): string | undefined {
+ const raw = clean(value)
+ if (!raw) return undefined
+ const words = raw
+ .toLowerCase()
+ .replace(/[/_.-]+/g, " ")
+ .replace(/[^a-z0-9 ]+/g, "")
+ .replace(/\s+/g, " ")
+ .trim()
+ .split(" ")
+ .filter(Boolean)
+ const meaningful = words.filter((word) => !PREFIX.has(word))
+ const picked: string[] = []
+ for (const word of meaningful.length > 0 ? meaningful : words) {
+ const next = [...picked, word].join(" ")
+ if (next.length > LABEL_MAX) break
+ picked.push(word)
+ if (picked.length >= 3) break
+ }
+ return picked.join(" ") || words[0]?.slice(0, LABEL_MAX) || undefined
+}
+
+function branch(value: string | undefined): string | undefined {
+ const raw = clean(value)
+ if (!raw) return undefined
+ return sanitizeBranchName(raw) || undefined
+}
+
+function versionedLabel(base: string | undefined, index: number, total: number): string | undefined {
+ if (!base) return undefined
+ if (total > 1 && index > 0) return `${base} v${index + 1}`
+ return base
+}
+
+async function prompt(client: KiloClient, sid: string, dir: string, task: ToolTask) {
+ const body = text(task)
+ if (!body) return
+ await client.session.promptAsync(
+ {
+ sessionID: sid,
+ directory: dir,
+ parts: [{ type: "text", text: body }],
+ },
+ { throwOnError: true },
+ )
+}
+
+async function local(deps: ToolDeps, client: KiloClient, task: ToolTask, directory?: string) {
+ const root = deps.getRoot()
+ const state = deps.getState()
+ if (!root || !state) return false
+
+ const dir = clean(directory) ?? root
+ const wt = dir === root ? undefined : state.findWorktreeByPath(dir)
+ if (dir !== root && !wt) {
+ deps.log("Agent Manager tool local request ignored unknown directory", dir)
+ deps.post({
+ type: "error",
+ message: `Agent Manager tool cannot start a local session for unknown directory: ${dir}`,
+ })
+ return false
+ }
+ const target = wt?.path ?? root
+ const { data } = await client.session.create({ directory: target, platform: PLATFORM }, { throwOnError: true })
+ const session = data
+ state.addSession(session.id, wt?.id ?? null)
+ if (wt) deps.registerWorktreeSession(session.id, wt.path)
+ deps.push()
+ deps.getPanel()?.sessions.registerSession(session)
+ if (wt) deps.post({ type: "agentManager.sessionAdded", sessionId: session.id, worktreeId: wt.id })
+ await prompt(client, session.id, target, task)
+ deps.capture("Agent Manager Session Started", {
+ source: PLATFORM,
+ sessionId: session.id,
+ tool: true,
+ mode: "local",
+ worktreeId: wt?.id,
+ })
+ return true
+}
+
+async function worktree(
+ deps: ToolDeps,
+ client: KiloClient,
+ task: ToolTask,
+ index: number,
+ total: number,
+ groupId?: string,
+ versions?: boolean,
+) {
+ const baseBranch = branch(task.branchName) ?? branch(task.name)
+ const baseLabel = label(task.name) ?? label(task.branchName) ?? label(task.prompt)
+ const version = versionedName(baseBranch, versions ? index : 0, versions ? total : 1)
+ const created = await deps.createWorktree({
+ groupId,
+ branchName: version.branch,
+ name: version.branch,
+ label: versionedLabel(baseLabel, versions ? index : 0, versions ? total : 1),
+ })
+ if (!created) return false
+
+ await deps.setup(created.result.path, created.result.branch, created.worktree.id)
+ const session = await deps.createSessionInWorktree(created.result.path, created.result.branch, created.worktree.id)
+ if (!session) {
+ await deps.cleanupWorktree(created.worktree.id, created.result.path)
+ return false
+ }
+
+ const state = deps.getState()
+ if (!state) {
+ await deps.cleanupWorktree(created.worktree.id, created.result.path)
+ return false
+ }
+ state.addSession(session.id, created.worktree.id)
+ deps.registerWorktreeSession(session.id, created.result.path)
+ deps.notifyReady(session.id, created.result, created.worktree.id)
+ deps.getPanel()?.sessions.registerSession(session)
+ await prompt(client, session.id, created.result.path, task)
+ deps.capture("Agent Manager Session Started", {
+ source: PLATFORM,
+ sessionId: session.id,
+ worktreeId: created.worktree.id,
+ branch: created.result.branch,
+ tool: true,
+ })
+ return true
+}
+
+export async function startFromTool(deps: ToolDeps, req: ToolRequest): Promise {
+ deps.openPanel(true)
+ await deps.getPanel()?.waitForReady()
+ await deps.waitReady("startFromTool")
+ const client = deps.getClient()
+ const total = req.tasks.length
+ const versions = req.mode === "worktree" && req.versions === true && total > 1
+ const groupId = versions ? `grp-${Date.now()}` : undefined
+ const state = { ok: 0 }
+
+ deps.post({ type: "agentManager.multiVersionProgress", status: "creating", total, completed: 0, groupId })
+ for (let i = 0; i < req.tasks.length; i++) {
+ const task = req.tasks[i]!
+ try {
+ const done =
+ req.mode === "local"
+ ? await local(deps, client, task, req.directory)
+ : await worktree(deps, client, task, i, total, groupId, versions)
+ if (done) state.ok++
+ } catch (err) {
+ const msg = err instanceof Error ? err.message : String(err)
+ deps.log("Agent Manager tool task failed", msg)
+ deps.post({ type: "error", message: `Agent Manager tool task failed: ${msg}` })
+ }
+ deps.post({ type: "agentManager.multiVersionProgress", status: "creating", total, completed: state.ok, groupId })
+ }
+
+ deps.post({ type: "agentManager.multiVersionProgress", status: "done", total, completed: state.ok, groupId })
+ if (state.ok === 0) deps.error(`Failed to start any Agent Manager sessions for request ${req.requestID}.`)
+ deps.log(`Agent Manager tool request ${req.requestID} complete: ${state.ok}/${total}`)
+}
+
+function record(value: unknown): value is Record {
+ return !!value && typeof value === "object"
+}
+
+function task(value: unknown): ToolTask | undefined {
+ if (!record(value)) return undefined
+ const out: ToolTask = {}
+ for (const key of ["prompt", "name", "branchName"] as const) {
+ if (typeof value[key] === "string" && value[key].trim()) out[key] = value[key]
+ }
+ if (!out.prompt && !out.name && !out.branchName) return undefined
+ return out
+}
+
+export function parseToolRequest(value: unknown): ToolRequest | undefined {
+ if (!record(value)) return undefined
+ const mode = value.mode
+ const tasks = value.tasks
+ if (mode !== "worktree" && mode !== "local") return undefined
+ if (!Array.isArray(tasks) || tasks.length === 0) return undefined
+ const parsed = tasks
+ .slice(0, 20)
+ .map(task)
+ .filter((item): item is ToolTask => !!item)
+ if (parsed.length === 0) return undefined
+ return {
+ requestID: typeof value.requestID === "string" ? value.requestID : `am-${Date.now()}`,
+ sessionID: typeof value.sessionID === "string" ? value.sessionID : undefined,
+ directory: typeof value.directory === "string" ? value.directory : undefined,
+ mode,
+ versions: typeof value.versions === "boolean" ? value.versions : undefined,
+ tasks: parsed,
+ }
+}
diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts
index 2e2c865dff..01e51ac009 100644
--- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts
@@ -133,6 +133,7 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple
/** Abort controller for the current in-flight FIM request */
private fimAbortController: AbortController | null = null
private acceptedCommand: vscode.Disposable | null = null
+ private contextService: ContextRetrievalService | null = null
private debounceDelayMs: number = INITIAL_DEBOUNCE_DELAY_MS
private latencyHistory: number[] = []
private telemetry: AutocompleteTelemetry | null
@@ -167,10 +168,10 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple
})()
const ide = new VsCodeIde(context)
- const contextService = new ContextRetrievalService(ide)
+ this.contextService = new ContextRetrievalService(ide)
const contextProvider: AutocompleteContextProvider = {
ide,
- contextService,
+ contextService: this.contextService,
model,
ignoreController: this.ignoreController,
}
@@ -304,6 +305,8 @@ export class AutocompleteInlineCompletionProvider implements vscode.InlineComple
this.fimAbortController?.abort()
this.fimAbortController = null
this.telemetry?.dispose()
+ this.contextService?.dispose()
+ this.contextService = null
this.recentlyVisitedRangesService.dispose()
this.recentlyEditedTracker.dispose()
void this.disposeIgnoreController()
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts
index 8b6e8ae5a7..fe4a917f96 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts
@@ -64,6 +64,10 @@ export class ContextRetrievalService {
return this.staticContextService.getContext(helper)
}
+ public dispose(): void {
+ this.importDefinitionsService.dispose()
+ }
+
/**
* Initialize the import definitions cache for a file.
* This is normally done automatically when the active text editor changes,
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts
index 5e24cfe271..71fafe82e7 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts
@@ -25,6 +25,16 @@ describe("ImportDefinitionsService", () => {
service = new ImportDefinitionsService(mockIde)
})
+ it("should dispose active editor listener", () => {
+ const disposable = { dispose: vi.fn() }
+ mockIde.onDidChangeActiveTextEditor = vi.fn().mockReturnValue(disposable)
+ service = new ImportDefinitionsService(mockIde)
+
+ service.dispose()
+
+ expect(disposable.dispose).toHaveBeenCalledTimes(1)
+ })
+
describe("Python Imports - Real Tree-sitter Parsing", () => {
it("should extract named imports from 'from module import' statements", async () => {
const pythonCode = `from os.path import join, exists
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts
index 538d79d218..523af450f6 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts
@@ -1,4 +1,4 @@
-import { IDE, RangeInFileWithContents } from "../.."
+import { Disposable, IDE, RangeInFileWithContents } from "../.."
import { PrecalculatedLruCache } from "../../util/LruCache"
import { getFullLanguageName, getParserForFile, getQueryForFile } from "../../util/treeSitter"
import { findUriInDirs } from "../../util/uri"
@@ -14,15 +14,20 @@ export class ImportDefinitionsService {
this._getFileInfo.bind(this),
ImportDefinitionsService.N,
)
+ private readonly disposable: Disposable | void
constructor(private readonly ide: IDE) {
- ide.onDidChangeActiveTextEditor((filepath) => {
+ this.disposable = ide.onDidChangeActiveTextEditor((filepath) => {
this.cache
.initKey(filepath)
.catch((e) => console.warn(`Failed to initialize ImportDefinitionService: ${e.message}`))
})
}
+ dispose(): void {
+ this.disposable?.dispose()
+ }
+
get(filepath: string): FileInfo | undefined {
return this.cache.get(filepath)
}
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts
index 014efae859..5585fe8a08 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts
@@ -383,6 +383,10 @@ export type FileStatsMap = {
[path: string]: FileStats
}
+export interface Disposable {
+ dispose(): void
+}
+
export interface IDE {
getIdeInfo(): Promise
@@ -423,7 +427,7 @@ export interface IDE {
getDocumentSymbols(textDocumentIdentifier: string): Promise
// Callbacks
- onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void
+ onDidChangeActiveTextEditor(callback: (fileUri: string) => void): Disposable | void
}
export type ContextProviderName =
| "diff"
diff --git a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts
index 894136e8aa..d3cb779748 100644
--- a/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts
+++ b/packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts
@@ -237,8 +237,8 @@ export class VsCodeIde implements IDE {
return result
}
- onDidChangeActiveTextEditor(callback: (fileUri: string) => void): void {
- vscode.window.onDidChangeActiveTextEditor((editor) => {
+ onDidChangeActiveTextEditor(callback: (fileUri: string) => void): vscode.Disposable {
+ return vscode.window.onDidChangeActiveTextEditor((editor) => {
if (editor) {
callback(editor.document.uri.toString())
}
diff --git a/packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts
new file mode 100644
index 0000000000..8742ab2776
--- /dev/null
+++ b/packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts
@@ -0,0 +1,183 @@
+import { describe, expect, it, mock } from "bun:test"
+import { parseToolRequest, startFromTool, type ToolDeps, type ToolRequest } from "../../src/agent-manager/tool-start"
+import type { CreateWorktreeResult } from "../../src/agent-manager/WorktreeManager"
+import type { Session } from "@kilocode/sdk/v2/client"
+
+function session(id: string): Session {
+ return { id, title: id, createdAt: "", updatedAt: "" } as Session
+}
+
+function result(path: string): CreateWorktreeResult {
+ return { path, branch: "kilo/test", parentBranch: "main", startPointSource: "fallback" } as CreateWorktreeResult
+}
+
+function deps(overrides: Partial = {}): ToolDeps {
+ const calls: unknown[] = []
+ const panel = {
+ waitForReady: mock(async () => calls.push("waitForReady")),
+ sessions: { registerSession: mock(() => calls.push("registerSession")) },
+ }
+ return {
+ getClient: () =>
+ ({
+ session: {
+ create: mock(async () => ({ data: session("s-local") })),
+ promptAsync: mock(async () => ({})),
+ },
+ }) as never,
+ getRoot: () => "/repo",
+ getState: () => ({ addSession: mock(() => calls.push("addSession")) }) as never,
+ getPanel: () => panel as never,
+ openPanel: mock(() => calls.push("openPanel")),
+ waitReady: mock(async () => calls.push("waitReady")),
+ createWorktree: mock(async () => ({ worktree: { id: "wt-1" }, result: result("/repo/.kilo/worktrees/wt-1") })),
+ cleanupWorktree: mock(async () => calls.push("cleanupWorktree")),
+ setup: mock(async () => calls.push("setup")),
+ createSessionInWorktree: mock(async () => session("s-wt")),
+ registerWorktreeSession: mock(() => calls.push("registerWorktreeSession")),
+ notifyReady: mock(() => calls.push("notifyReady")),
+ push: mock(() => calls.push("push")),
+ post: mock((msg: unknown) => calls.push(msg)),
+ capture: mock(() => calls.push("capture")),
+ log: mock(() => {}),
+ error: mock(() => {}),
+ ...overrides,
+ }
+}
+
+describe("agent manager tool start", () => {
+ it("parses tool start events defensively", () => {
+ const parsed = parseToolRequest({ mode: "local", tasks: [{ prompt: "one" }] })
+ expect(parsed?.requestID.startsWith("am-")).toBe(true)
+ expect(parsed?.sessionID).toBeUndefined()
+ expect(parsed?.directory).toBeUndefined()
+ expect(parsed?.mode).toBe("local")
+ expect(parsed?.versions).toBeUndefined()
+ expect(parsed?.tasks).toEqual([{ prompt: "one" }])
+ expect(parseToolRequest({ mode: "bad", tasks: [{ prompt: "one" }] })).toBeUndefined()
+ expect(parseToolRequest({ mode: "local", tasks: [] })).toBeUndefined()
+ expect(parseToolRequest({ mode: "local", tasks: [{}] })).toBeUndefined()
+ })
+
+ it("starts local sessions and sends the initial prompt", async () => {
+ const client = {
+ session: {
+ create: mock(async () => ({ data: session("s-local") })),
+ promptAsync: mock(async () => ({})),
+ },
+ }
+ const c = deps({ getClient: () => client as never })
+ const req: ToolRequest = {
+ requestID: "am-1",
+ mode: "local",
+ tasks: [{ prompt: "Do work" }],
+ }
+
+ await startFromTool(c, req)
+
+ expect(c.openPanel).toHaveBeenCalledWith(true)
+ const panel = c.getPanel()
+ expect(panel?.waitForReady).toHaveBeenCalled()
+ expect(client.session.create).toHaveBeenCalledWith(
+ { directory: "/repo", platform: "agent-manager" },
+ { throwOnError: true },
+ )
+ expect(client.session.promptAsync).toHaveBeenCalledWith(
+ expect.objectContaining({
+ sessionID: "s-local",
+ directory: "/repo",
+ parts: [{ type: "text", text: "Do work" }],
+ }),
+ { throwOnError: true },
+ )
+ })
+
+ it("starts worktree sessions through existing hooks", async () => {
+ const c = deps()
+ await startFromTool(c, { requestID: "am-2", mode: "worktree", tasks: [{ prompt: "Fix", branchName: "fix/one" }] })
+
+ expect(c.createWorktree).toHaveBeenCalledWith(
+ expect.objectContaining({ branchName: "fix-one", name: "fix-one", label: "one" }),
+ )
+ expect(c.setup).toHaveBeenCalled()
+ expect(c.createSessionInWorktree).toHaveBeenCalled()
+ expect(c.registerWorktreeSession).toHaveBeenCalledWith("s-wt", "/repo/.kilo/worktrees/wt-1")
+ expect(c.notifyReady).toHaveBeenCalled()
+ })
+
+ it("only applies version suffixes when versions is true", async () => {
+ const normal = deps()
+ await startFromTool(normal, {
+ requestID: "am-normal",
+ mode: "worktree",
+ tasks: [
+ { prompt: "Fix one", branchName: "fix/one" },
+ { prompt: "Fix two", branchName: "fix/two" },
+ ],
+ })
+ expect(normal.createWorktree).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({ branchName: "fix-two", label: "two" }),
+ )
+
+ const grouped = deps()
+ await startFromTool(grouped, {
+ requestID: "am-versions",
+ mode: "worktree",
+ versions: true,
+ tasks: [
+ { prompt: "Try one", branchName: "try/work" },
+ { prompt: "Try two", branchName: "try/work" },
+ ],
+ })
+ expect(grouped.createWorktree).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({ branchName: "try-work_v2", label: "try work v2" }),
+ )
+ })
+
+ it("sanitizes branch names and keeps card labels short", async () => {
+ const c = deps()
+ await startFromTool(c, {
+ requestID: "am-name",
+ mode: "worktree",
+ tasks: [
+ {
+ prompt: "Fix command permissions persistence regression",
+ name: "Fix command permissions persistence regression that is too long",
+ branchName: "fix command permissions @#$ persistence",
+ },
+ ],
+ })
+
+ expect(c.createWorktree).toHaveBeenCalledWith(
+ expect.objectContaining({
+ branchName: "fix-command-permissions-persistence",
+ label: "command permissions",
+ }),
+ )
+ })
+
+ it("rejects local sessions for unknown worktree directories", async () => {
+ const client = {
+ session: {
+ create: mock(async () => ({ data: session("s-local") })),
+ promptAsync: mock(async () => ({})),
+ },
+ }
+ const c = deps({
+ getClient: () => client as never,
+ getState: () => ({ addSession: mock(), findWorktreeByPath: mock(() => undefined) }) as never,
+ })
+
+ await startFromTool(c, {
+ requestID: "am-dir",
+ mode: "local",
+ directory: "/repo/other",
+ tasks: [{ prompt: "Do work" }],
+ })
+
+ expect(client.session.create).not.toHaveBeenCalled()
+ expect(c.error).toHaveBeenCalled()
+ })
+})
diff --git a/packages/kilo-vscode/tests/unit/config-utils.test.ts b/packages/kilo-vscode/tests/unit/config-utils.test.ts
index 15ae52b443..16fba3a660 100644
--- a/packages/kilo-vscode/tests/unit/config-utils.test.ts
+++ b/packages/kilo-vscode/tests/unit/config-utils.test.ts
@@ -204,4 +204,72 @@ describe("ConfigState", () => {
expect(Object.keys(s.draft).length).toBe(0)
})
})
+
+ describe("agent permission patches", () => {
+ it("merges nested per-agent permission patches into existing rules", () => {
+ const s = new ConfigState()
+ s.handleConfigLoaded({
+ agent: {
+ reviewer: {
+ permission: {
+ read: "allow",
+ edit: "deny",
+ },
+ },
+ },
+ })
+
+ s.updateConfig({ agent: { reviewer: { permission: { bash: "ask" } } } })
+
+ expect(s.config.agent?.reviewer?.permission).toEqual({
+ read: "allow",
+ edit: "deny",
+ bash: "ask",
+ })
+ expect(s.draft.agent?.reviewer?.permission).toEqual({ bash: "ask" })
+ })
+
+ it("keeps nested permission delete sentinels in the draft", () => {
+ const s = new ConfigState()
+ s.handleConfigLoaded({
+ agent: {
+ docs: {
+ permission: {
+ edit: { "*": "deny", "**/*.md": "allow" },
+ },
+ },
+ },
+ })
+
+ s.updateConfig({ agent: { docs: { permission: { edit: { "**/*.md": null } } } } })
+
+ expect(s.config.agent?.docs?.permission).toEqual({ edit: { "*": "deny" } })
+ expect(s.draft.agent?.docs?.permission).toEqual({ edit: { "**/*.md": null } })
+ expect(JSON.parse(JSON.stringify(s.draft))).toEqual({
+ agent: { docs: { permission: { edit: { "**/*.md": null } } } },
+ })
+ })
+
+ it("keeps tool-level permission delete sentinels in the draft", () => {
+ const s = new ConfigState()
+ s.handleConfigLoaded({
+ agent: {
+ reviewer: {
+ permission: {
+ read: "allow",
+ bash: "deny",
+ },
+ },
+ },
+ })
+
+ s.updateConfig({ agent: { reviewer: { permission: { bash: null } } } })
+
+ expect(s.config.agent?.reviewer?.permission).toEqual({ read: "allow" })
+ expect(s.draft.agent?.reviewer?.permission).toEqual({ bash: null })
+ expect(JSON.parse(JSON.stringify(s.draft))).toEqual({
+ agent: { reviewer: { permission: { bash: null } } },
+ })
+ })
+ })
})
diff --git a/packages/kilo-vscode/tests/unit/navigate.test.ts b/packages/kilo-vscode/tests/unit/navigate.test.ts
index 61d7110eb6..819a6c15f8 100644
--- a/packages/kilo-vscode/tests/unit/navigate.test.ts
+++ b/packages/kilo-vscode/tests/unit/navigate.test.ts
@@ -4,6 +4,7 @@ import {
validateLocalSession,
adjacentHint,
restoreLocalSessions,
+ reconcileLocalSessions,
LOCAL,
} from "../../webview-ui/agent-manager/navigate"
@@ -294,3 +295,90 @@ describe("restoreLocalSessions", () => {
expect(result).toBeUndefined()
})
})
+
+describe("reconcileLocalSessions", () => {
+ const isPending = (id: string) => id.startsWith("pending-")
+
+ it("keeps restored local sessions through a partial restart refresh", () => {
+ const managed = [
+ { id: "local-1", worktreeId: null },
+ { id: "worktree-1", worktreeId: "wt-1" },
+ ]
+ const restored = restoreLocalSessions(managed, [], undefined, isPending, (items) => items)?.filter(Boolean) ?? []
+
+ const result = reconcileLocalSessions(restored, ["worktree-1"], managed, isPending)
+
+ expect(restored).toEqual(["local-1"])
+ expect(result).toBeUndefined()
+ })
+
+ it("preserves restored local sessions before sessionsLoaded includes them", () => {
+ const result = reconcileLocalSessions(
+ ["s1", "s2"],
+ [],
+ [
+ { id: "s1", worktreeId: null },
+ { id: "s2", worktreeId: null },
+ ],
+ isPending,
+ )
+
+ expect(result).toBeUndefined()
+ })
+
+ it("does not forget persisted local sessions when only worktree sessions loaded", () => {
+ const result = reconcileLocalSessions(
+ ["local-1"],
+ ["worktree-1"],
+ [
+ { id: "local-1", worktreeId: null },
+ { id: "worktree-1", worktreeId: "wt-1" },
+ ],
+ isPending,
+ )
+
+ expect(result).toBeUndefined()
+ })
+
+ it("waits for managed state before removing sessions restored from webview state", () => {
+ const beforeState = reconcileLocalSessions(["local-1"], ["worktree-1"], [], isPending)
+ const afterState = reconcileLocalSessions(
+ ["local-1"],
+ ["worktree-1"],
+ [
+ { id: "local-1", worktreeId: null },
+ { id: "worktree-1", worktreeId: "wt-1" },
+ ],
+ isPending,
+ )
+
+ expect(beforeState).toEqual({ ids: [], forget: ["local-1"] })
+ expect(afterState).toBeUndefined()
+ })
+
+ it("forgets stale local sessions missing from loaded and managed state", () => {
+ const result = reconcileLocalSessions(["s1", "gone"], ["s1"], [{ id: "s1", worktreeId: null }], isPending)
+
+ expect(result).toEqual({ ids: ["s1"], forget: ["gone"] })
+ })
+
+ it("evicts worktree sessions that raced into local state without forgetting them", () => {
+ const result = reconcileLocalSessions(
+ ["local-1", "worktree-1"],
+ ["local-1", "worktree-1"],
+ [
+ { id: "local-1", worktreeId: null },
+ { id: "worktree-1", worktreeId: "wt-1" },
+ ],
+ isPending,
+ )
+
+ expect(result).toEqual({ ids: ["local-1"], forget: [] })
+ })
+
+ it("keeps pending local tabs during reconciliation", () => {
+ const result = reconcileLocalSessions(["pending-1", "gone"], [], [], isPending)
+
+ expect(result).toEqual({ ids: ["pending-1"], forget: ["gone"] })
+ })
+})
diff --git a/packages/kilo-vscode/tests/unit/permission-editor.test.ts b/packages/kilo-vscode/tests/unit/permission-editor.test.ts
new file mode 100644
index 0000000000..f8df357667
--- /dev/null
+++ b/packages/kilo-vscode/tests/unit/permission-editor.test.ts
@@ -0,0 +1,33 @@
+import { describe, expect, it } from "bun:test"
+import { effectiveRuleLevel } from "../../webview-ui/src/components/settings/permission-utils"
+import type { PermissionRuleItem } from "../../webview-ui/src/types/messages"
+
+describe("effectiveRuleLevel", () => {
+ it("uses the last matching wildcard rule from resolved agent rules", () => {
+ const rules: PermissionRuleItem[] = [
+ { permission: "*", pattern: "*", action: "allow" },
+ { permission: "bash", pattern: "*", action: "ask" },
+ { permission: "*", pattern: "*", action: "deny" },
+ ]
+
+ expect(effectiveRuleLevel(rules, "bash")).toBe("deny")
+ expect(effectiveRuleLevel(rules, "external_directory")).toBe("deny")
+ })
+
+ it("uses specific tool rules after wildcard rules", () => {
+ const rules: PermissionRuleItem[] = [
+ { permission: "*", pattern: "*", action: "deny" },
+ { permission: "read", pattern: "*", action: "allow" },
+ { permission: "grep", pattern: "*", action: "allow" },
+ ]
+
+ expect(effectiveRuleLevel(rules, "read")).toBe("allow")
+ expect(effectiveRuleLevel(rules, "grep")).toBe("allow")
+ expect(effectiveRuleLevel(rules, "edit")).toBe("deny")
+ })
+
+ it("falls back to ask when no resolved wildcard rule is available", () => {
+ expect(effectiveRuleLevel(undefined, "bash")).toBe("ask")
+ expect(effectiveRuleLevel([], "bash")).toBe("ask")
+ })
+})
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx
index af24000a88..b5d270f87e 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx
+++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx
@@ -75,6 +75,7 @@ import { VSCodeProvider, useVSCode } from "../src/context/vscode"
import { ServerProvider } from "../src/context/server"
import { ProviderProvider } from "../src/context/provider"
import { ConfigProvider } from "../src/context/config"
+import { DisplayProvider } from "../src/context/display"
import { NotificationsProvider } from "../src/context/notifications"
import { SessionProvider, useSession } from "../src/context/session"
import { WorktreeModeProvider } from "../src/context/worktree-mode"
@@ -84,7 +85,7 @@ import { NewWorktreeDialog } from "./NewWorktreeDialog"
import { LanguageBridge, DataBridge } from "../src/App"
import { useLanguage } from "../src/context/language"
import { formatRelativeDate } from "../src/utils/date"
-import { validateLocalSession, nextSelectionAfterDelete, adjacentHint, restoreLocalSessions, LOCAL } from "./navigate"
+import { nextSelectionAfterDelete, adjacentHint, restoreLocalSessions, reconcileLocalSessions, LOCAL } from "./navigate"
import { reorderTabs, applyTabOrder, firstOrderedTitle } from "./tab-order"
import { createTabOrderSync } from "./tab-order-sync"
import { ConstrainDragYAxis } from "./sortable-tab"
@@ -720,16 +721,18 @@ const AgentManagerContent: Component = () => {
// Invalidate local session IDs if they no longer exist (preserve pending tabs)
createEffect(() => {
+ if (!worktreesLoaded()) return
const all = session.sessions()
if (all.length === 0) return // sessions not loaded yet
- const ids = all.map((s) => s.id)
- const prev = localSessionIDs()
- const valid = prev.filter((lid) => isPending(lid) || validateLocalSession(lid, ids))
- if (valid.length !== prev.length) {
- const removed = prev.filter((lid) => !isPending(lid) && !valid.includes(lid))
- for (const id of removed) vscode.postMessage({ type: "agentManager.forgetSession", sessionId: id })
- setLocalSessionIDs(valid)
- }
+ const next = reconcileLocalSessions(
+ localSessionIDs(),
+ all.map((s) => s.id),
+ managedSessions(),
+ isPending,
+ )
+ if (!next) return
+ for (const id of next.forget) vscode.postMessage({ type: "agentManager.forgetSession", sessionId: id })
+ setLocalSessionIDs(next.ids)
})
// Drop in-memory review state for worktrees that no longer exist.
createEffect(() => {
@@ -3164,17 +3167,19 @@ export const AgentManagerApp: Component = () => {
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts b/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts
index b9098a99c4..d61a8265ef 100644
--- a/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts
+++ b/packages/kilo-vscode/webview-ui/agent-manager/navigate.ts
@@ -129,6 +129,35 @@ export function restoreLocalSessions(
return changed ? merged : undefined
}
+export function reconcileLocalSessions(
+ current: string[],
+ loaded: string[],
+ managed: { id: string; worktreeId: string | null }[],
+ isPending: (id: string) => boolean,
+): { ids: string[]; forget: string[] } | undefined {
+ const seen = new Set(loaded)
+ const local = new Set(managed.filter((s) => !s.worktreeId).map((s) => s.id))
+ const worktree = new Set(managed.filter((s) => s.worktreeId).map((s) => s.id))
+ const ids: string[] = []
+ const forget: string[] = []
+
+ for (const id of current) {
+ if (isPending(id)) {
+ ids.push(id)
+ continue
+ }
+ if (worktree.has(id)) continue
+ if (seen.has(id) || local.has(id)) {
+ ids.push(id)
+ continue
+ }
+ forget.push(id)
+ }
+
+ if (ids.length === current.length && forget.length === 0) return undefined
+ return { ids, forget }
+}
+
/**
* After removing a worktree, pick the nearest remaining sidebar neighbor.
* Order: the worktree just below → the one above → LOCAL.
diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx
index c774defdac..8665a763fa 100644
--- a/packages/kilo-vscode/webview-ui/src/App.tsx
+++ b/packages/kilo-vscode/webview-ui/src/App.tsx
@@ -16,6 +16,7 @@ import { VSCodeProvider, useVSCode } from "./context/vscode"
import { ServerProvider, useServer } from "./context/server"
import { ProviderProvider, useProvider } from "./context/provider"
import { ConfigProvider } from "./context/config"
+import { DisplayProvider } from "./context/display"
import { IndexingProvider } from "./context/indexing"
import { SessionProvider, useSession } from "./context/session"
import { LanguageProvider } from "./context/language"
@@ -349,15 +350,17 @@ const App: Component = () => {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx
index 31e8ec4c37..01d03b0a5b 100644
--- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx
@@ -18,6 +18,7 @@ import type {
} from "@kilocode/sdk/v2"
import { useData } from "@kilocode/kilo-ui/context/data"
import { useSession } from "../../context/session"
+import { useDisplay } from "../../context/display"
import { useConfig } from "../../context/config"
import { QuestionDock } from "./QuestionDock"
import { SuggestBar } from "./SuggestBar"
@@ -112,6 +113,7 @@ function BashToolCard(props: { part: ToolPart; defaultOpen: boolean }) {
export const AssistantMessage: Component = (props) => {
const data = useData()
const session = useSession()
+ const display = useDisplay()
const { config } = useConfig()
const open = createMemo(() => config().terminal_command_display !== "collapsed")
@@ -164,6 +166,7 @@ export const AssistantMessage: Component = (props) => {
part={part}
message={props.message as SDKMessage}
showAssistantCopyPartID={props.showAssistantCopyPartID}
+ reasoningAutoCollapse={display.reasoningAutoCollapse()}
animate={
part.type === "tool" &&
((part as unknown as ToolPart).state?.status === "pending" ||
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx
index 5bc297fd9b..c63cb53350 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx
@@ -1,153 +1,8 @@
-import { Component, For, Show, createEffect, createMemo, createSignal } from "solid-js"
-import { Select } from "@kilocode/kilo-ui/select"
-import { IconButton } from "@kilocode/kilo-ui/icon-button"
+import { Component, createMemo } from "solid-js"
+
import { useConfig } from "../../context/config"
import { useLanguage } from "../../context/language"
-import type { PermissionLevel, PermissionRule } from "../../types/messages"
-
-interface LevelOption {
- value: PermissionLevel
- labelKey: string
-}
-
-const LEVEL_OPTIONS: LevelOption[] = [
- { value: "allow", labelKey: "settings.autoApprove.level.allow" },
- { value: "ask", labelKey: "settings.autoApprove.level.ask" },
- { value: "deny", labelKey: "settings.autoApprove.level.deny" },
-]
-
-interface GranularConfig {
- wildcardKey: string
- addKey: string
- placeholderKey: string
-}
-
-interface ToolDef {
- id: string
- descriptionKey: string
- granular?: GranularConfig
-}
-
-interface GranularToolDef extends ToolDef {
- granular: GranularConfig
-}
-
-/** Grouped tool: maps a single UI row to multiple config keys */
-interface GroupedToolDef {
- ids: string[]
- label: string
- descriptionKey: string
-}
-
-const GRANULAR_TOOLS: GranularToolDef[] = [
- {
- id: "external_directory",
- descriptionKey: "settings.autoApprove.tool.external_directory",
- granular: {
- wildcardKey: "settings.autoApprove.wildcardLabel.paths",
- addKey: "settings.autoApprove.addPath",
- placeholderKey: "settings.autoApprove.placeholder.path",
- },
- },
- {
- id: "bash",
- descriptionKey: "settings.autoApprove.tool.bash",
- granular: {
- wildcardKey: "settings.autoApprove.wildcardLabel.commands",
- addKey: "settings.autoApprove.addCommand",
- placeholderKey: "settings.autoApprove.placeholder.command",
- },
- },
- {
- id: "read",
- descriptionKey: "settings.autoApprove.tool.read",
- granular: {
- wildcardKey: "settings.autoApprove.wildcardLabel.paths",
- addKey: "settings.autoApprove.addPath",
- placeholderKey: "settings.autoApprove.placeholder.path",
- },
- },
- {
- id: "edit",
- descriptionKey: "settings.autoApprove.tool.edit",
- granular: {
- wildcardKey: "settings.autoApprove.wildcardLabel.paths",
- addKey: "settings.autoApprove.addPath",
- placeholderKey: "settings.autoApprove.placeholder.path",
- },
- },
-]
-
-const SIMPLE_TOOLS: ToolDef[] = [
- { id: "glob", descriptionKey: "settings.autoApprove.tool.glob" },
- { id: "grep", descriptionKey: "settings.autoApprove.tool.grep" },
- { id: "list", descriptionKey: "settings.autoApprove.tool.list" },
- { id: "task", descriptionKey: "settings.autoApprove.tool.task" },
- { id: "skill", descriptionKey: "settings.autoApprove.tool.skill" },
- { id: "lsp", descriptionKey: "settings.autoApprove.tool.lsp" },
-]
-
-const GROUPED_TOOLS: GroupedToolDef[] = [
- {
- ids: ["todoread", "todowrite"],
- label: "todoread / todowrite",
- descriptionKey: "settings.autoApprove.tool.todoreadwrite",
- },
- {
- ids: ["websearch", "codesearch"],
- label: "websearch / codesearch",
- descriptionKey: "settings.autoApprove.tool.websearchcodesearch",
- },
-]
-
-const TRAILING_TOOLS: ToolDef[] = [
- { id: "webfetch", descriptionKey: "settings.autoApprove.tool.webfetch" },
- { id: "doom_loop", descriptionKey: "settings.autoApprove.tool.doom_loop" },
-]
-
-/**
- * Backend default permission levels — mirrors the base defaults defined in
- * packages/opencode/src/agent/agent.ts (lines 61-78). The global default
- * is "*": "allow"; these are the per-tool overrides. If the backend defaults
- * change, this map must be updated to match.
- */
-const TOOL_DEFAULTS: Partial> = {
- doom_loop: "ask",
- external_directory: "ask",
-}
-
-const RESTRICTION_ORDER: Record = { allow: 0, ask: 1, deny: 2 }
-
-/** For grouped tools, return the most restrictive level across all IDs. */
-function mostRestrictive(levels: PermissionLevel[]): PermissionLevel {
- return levels.reduce(
- (best, l) => (RESTRICTION_ORDER[l] > RESTRICTION_ORDER[best] ? l : best),
- levels[0] ?? "allow",
- )
-}
-
-function wildcardAction(rule: PermissionRule | undefined, fallback: PermissionLevel): PermissionLevel {
- if (!rule) return fallback
- if (typeof rule === "string") return rule
- return rule["*"] ?? fallback
-}
-
-function exceptions(rule: PermissionRule | undefined): Array<{ pattern: string; action: PermissionLevel }> {
- if (!rule || typeof rule === "string") return []
- return Object.entries(rule)
- .filter(([key, action]) => key !== "*" && action !== null)
- .map(([pattern, action]) => ({ pattern, action: action as PermissionLevel }))
-}
-
-function toolTitle(id: string): string {
- return id
- .split("_")
- .map((word) => word[0].toUpperCase() + word.slice(1))
- .join(" ")
- .split(" / ")
- .map((part) => part[0].toUpperCase() + part.slice(1))
- .join(" / ")
-}
+import PermissionEditor from "./PermissionEditor"
const AutoApproveTab: Component = () => {
const { config, updateConfig } = useConfig()
@@ -155,366 +10,11 @@ const AutoApproveTab: Component = () => {
const permissions = createMemo(() => config().permission ?? {})
- const globalFallback = createMemo((): PermissionLevel => {
- const star = permissions()["*"]
- if (typeof star === "string") return star
- return "allow" // backend default: "*": "allow" (agent.ts)
- })
-
- const defaultFor = (tool: string): PermissionLevel => TOOL_DEFAULTS[tool] ?? globalFallback()
-
- const levelFor = (tool: string): PermissionLevel => wildcardAction(permissions()[tool], defaultFor(tool))
-
- const ruleFor = (tool: string): PermissionRule | undefined => permissions()[tool]
-
- const setSimple = (tool: string, level: PermissionLevel) => {
- updateConfig({ permission: { [tool]: level } })
- }
-
- const setGrouped = (ids: string[], level: PermissionLevel) => {
- const patch: Record = {}
- for (const id of ids) patch[id] = level
- updateConfig({ permission: patch })
- }
-
- const setWildcard = (tool: string, level: PermissionLevel) => {
- const current = ruleFor(tool)
- const excs = exceptions(current)
- if (excs.length === 0) {
- updateConfig({ permission: { [tool]: level } })
- return
- }
- const obj: Record = { "*": level }
- for (const exc of excs) obj[exc.pattern] = exc.action
- updateConfig({ permission: { [tool]: obj } })
- }
-
- const setException = (tool: string, pattern: string, level: PermissionLevel) => {
- const current = ruleFor(tool)
- const base: Record =
- typeof current === "string" ? { "*": current } : { ...(current ?? {}) }
- base[pattern] = level
- updateConfig({ permission: { [tool]: base } })
- }
-
- const addException = (tool: string, pattern: string) => {
- const current = ruleFor(tool)
- const base: Record =
- typeof current === "string" ? { "*": current } : { ...(current ?? {}) }
- base[pattern] = "allow"
- updateConfig({ permission: { [tool]: base } })
- }
-
- const removeException = (tool: string, pattern: string) => {
- const current = ruleFor(tool)
- if (!current || typeof current === "string") return
- // Send a single patch with null for the deleted key.
- // null is a delete sentinel: patchJsonc removes the key from the JSONC file,
- // stripNulls removes it from the optimistic UI.
- updateConfig({ permission: { [tool]: { [pattern]: null } } })
- }
-
- return (
-
-
- {language.t("settings.autoApprove.description")}
-
-
-
- {(tool) => (
- setWildcard(tool.id, level)}
- onExceptionChange={(pattern, level) => setException(tool.id, pattern, level)}
- onExceptionAdd={(pattern) => addException(tool.id, pattern)}
- onExceptionRemove={(pattern) => removeException(tool.id, pattern)}
- />
- )}
-
-
-
- {(tool) => (
- setSimple(tool.id, level)}
- />
- )}
-
-
-
- {(group) => (
- setGrouped(group.ids, level)}
- />
- )}
-
-
-
- {(tool) => (
- setSimple(tool.id, level)}
- />
- )}
-
-
- )
-}
-
-const SimpleToolRow: Component<{
- id: string
- descriptionKey: string
- level: PermissionLevel
- onChange: (level: PermissionLevel) => void
-}> = (props) => {
- const language = useLanguage()
- return (
-
-
-
- {toolTitle(props.id)}
-
-
- {language.t(props.descriptionKey)}
-
-
-
-
- )
-}
-
-const GranularToolRow: Component<{
- tool: GranularToolDef
- rule: PermissionRule | undefined
- fallback: PermissionLevel
- onWildcardChange: (level: PermissionLevel) => void
- onExceptionChange: (pattern: string, level: PermissionLevel) => void
- onExceptionAdd: (pattern: string) => void
- onExceptionRemove: (pattern: string) => void
-}> = (props) => {
- const language = useLanguage()
- const [adding, setAdding] = createSignal(false)
- const [input, setInput] = createSignal("")
- let inputRef: HTMLInputElement | undefined
-
- createEffect(() => {
- if (adding()) inputRef?.focus()
- })
-
- const excs = createMemo(() => exceptions(props.rule))
- const level = createMemo(() => wildcardAction(props.rule, props.fallback))
-
- const submit = () => {
- const val = input().trim()
- if (val) {
- props.onExceptionAdd(val)
- setInput("")
- }
- setAdding(false)
- }
-
- const cancel = () => {
- setInput("")
- setAdding(false)
- }
-
- return (
-
- {/* Tool header with name and description */}
-
-
-
- {toolTitle(props.tool.id)}
-
-
- {language.t(props.tool.descriptionKey)}
-
-
-
-
- {/* Wildcard row */}
-
-
-
- {language.t(props.tool.granular.wildcardKey)}
-
-
-
-
-
- {/* Exceptions */}
-
0}>
-
-
- {language.t("settings.autoApprove.exceptions")}
-
-
- {(exc) => (
-
-
- {exc.pattern}
-
-
-
props.onExceptionChange(exc.pattern, level)} />
- props.onExceptionRemove(exc.pattern)}
- />
-
-
- )}
-
-
-
-
- {/* Add button / inline input */}
-
setAdding(true)}
- >
- +
- {language.t(props.tool.granular.addKey)}
-
- }
- >
-
- (inputRef = el)}
- type="text"
- value={input()}
- onInput={(e) => setInput(e.currentTarget.value)}
- onKeyDown={(e) => {
- if (e.key === "Enter") submit()
- if (e.key === "Escape") cancel()
- }}
- onBlur={() => {
- if (!input().trim()) cancel()
- }}
- placeholder={language.t(props.tool.granular.placeholderKey)}
- style={{
- flex: 1,
- "min-width": 0,
- background: "var(--surface-strong-base, #252526)",
- border: "1px solid var(--border-base, #434443)",
- "border-radius": "2px",
- color: "var(--text-base, #ccc)",
- "font-size": "13px",
- "font-family": "var(--vscode-editor-font-family, monospace)",
- padding: "4px 8px",
- outline: "none",
- }}
- />
-
-
-
-
- )
-}
-
-const ActionSelect: Component<{
- level: PermissionLevel
- onChange: (level: PermissionLevel) => void
-}> = (props) => {
- const language = useLanguage()
return (
- o.value === props.level)}
- value={(o) => o.value}
- label={(o) => language.t(o.labelKey)}
- onSelect={(option) => option && props.onChange(option.value)}
- variant="secondary"
- size="small"
- triggerVariant="settings"
+ updateConfig({ permission: patch })}
/>
)
}
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx
index a300c57cb2..9be295e5b2 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx
@@ -1,8 +1,10 @@
-import { Component } from "solid-js"
+import { type Component } from "solid-js"
import { Select } from "@kilocode/kilo-ui/select"
import { TextField } from "@kilocode/kilo-ui/text-field"
import { Card } from "@kilocode/kilo-ui/card"
+import { Switch } from "@kilocode/kilo-ui/switch"
import { useConfig } from "../../context/config"
+import { useDisplay } from "../../context/display"
import { useLanguage } from "../../context/language"
import type { TerminalCommandDisplay } from "../../types/messages"
import SettingsRow from "./SettingsRow"
@@ -24,6 +26,7 @@ const TERMINAL_OPTIONS: LayoutOption[] = [
const DisplayTab: Component = () => {
const { config, updateConfig } = useConfig()
+ const display = useDisplay()
const language = useLanguage()
return (
@@ -63,6 +66,21 @@ const DisplayTab: Component = () => {
/>
+
+ {
+ display.setReasoningAutoCollapse(checked)
+ }}
+ hideLabel
+ >
+ {language.t("settings.display.reasoningAutoCollapse.title")}
+
+
+
{
+
+ updateExperimental("agent_manager_tool", checked)}
+ hideLabel
+ >
+ {language.t("settings.experimental.agentManagerTool.title")}
+
+
+
= (props) => {
})
}
+ const updatePermission = (patch: PermissionConfig) => {
+ updateConfig({ agent: { [props.name]: { permission: patch } } })
+ }
+
const exportMode = () => {
const data = buildExport(props.name, cfg())
const json = JSON.stringify(data, null, 2)
@@ -231,6 +236,48 @@ const ModeEditView: Component = (props) => {
+
+
+
+
+ Per-Agent Permissions
+
+
+ These settings only apply to this custom agent. Change a dropdown from Default to create an override, or
+ use Add path/Add command for tool-specific exceptions.
+
+
+
+
+
+
{/* Calculated permissions (read-only, collapsible) */}
{(rules) => (
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/PermissionEditor.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/PermissionEditor.tsx
new file mode 100644
index 0000000000..994090a1cb
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/PermissionEditor.tsx
@@ -0,0 +1,571 @@
+import { Component, For, Show, createEffect, createMemo, createSignal } from "solid-js"
+import { Select } from "@kilocode/kilo-ui/select"
+import { IconButton } from "@kilocode/kilo-ui/icon-button"
+
+import { useLanguage } from "../../context/language"
+import type { PermissionConfig, PermissionLevel, PermissionRule, PermissionRuleItem } from "../../types/messages"
+import { effectiveRuleLevel } from "./permission-utils"
+
+type LevelValue = PermissionLevel | "inherit"
+
+interface LevelOption {
+ value: LevelValue
+ labelKey: string
+}
+
+const LEVEL_OPTIONS: LevelOption[] = [
+ { value: "allow", labelKey: "settings.autoApprove.level.allow" },
+ { value: "ask", labelKey: "settings.autoApprove.level.ask" },
+ { value: "deny", labelKey: "settings.autoApprove.level.deny" },
+]
+
+const INHERIT_OPTION: LevelOption = { value: "inherit", labelKey: "common.default" }
+
+interface GranularConfig {
+ wildcardKey: string
+ addKey: string
+ placeholderKey: string
+}
+
+interface ToolDef {
+ id: string
+ descriptionKey: string
+ granular?: GranularConfig
+}
+
+interface GranularToolDef extends ToolDef {
+ granular: GranularConfig
+}
+
+interface GroupedToolDef {
+ ids: string[]
+ label: string
+ descriptionKey: string
+}
+
+const GRANULAR_TOOLS: GranularToolDef[] = [
+ {
+ id: "external_directory",
+ descriptionKey: "settings.autoApprove.tool.external_directory",
+ granular: {
+ wildcardKey: "settings.autoApprove.wildcardLabel.paths",
+ addKey: "settings.autoApprove.addPath",
+ placeholderKey: "settings.autoApprove.placeholder.path",
+ },
+ },
+ {
+ id: "bash",
+ descriptionKey: "settings.autoApprove.tool.bash",
+ granular: {
+ wildcardKey: "settings.autoApprove.wildcardLabel.commands",
+ addKey: "settings.autoApprove.addCommand",
+ placeholderKey: "settings.autoApprove.placeholder.command",
+ },
+ },
+ {
+ id: "read",
+ descriptionKey: "settings.autoApprove.tool.read",
+ granular: {
+ wildcardKey: "settings.autoApprove.wildcardLabel.paths",
+ addKey: "settings.autoApprove.addPath",
+ placeholderKey: "settings.autoApprove.placeholder.path",
+ },
+ },
+ {
+ id: "edit",
+ descriptionKey: "settings.autoApprove.tool.edit",
+ granular: {
+ wildcardKey: "settings.autoApprove.wildcardLabel.paths",
+ addKey: "settings.autoApprove.addPath",
+ placeholderKey: "settings.autoApprove.placeholder.path",
+ },
+ },
+]
+
+const SIMPLE_TOOLS: ToolDef[] = [
+ { id: "glob", descriptionKey: "settings.autoApprove.tool.glob" },
+ { id: "grep", descriptionKey: "settings.autoApprove.tool.grep" },
+ { id: "list", descriptionKey: "settings.autoApprove.tool.list" },
+ { id: "task", descriptionKey: "settings.autoApprove.tool.task" },
+ { id: "skill", descriptionKey: "settings.autoApprove.tool.skill" },
+ { id: "lsp", descriptionKey: "settings.autoApprove.tool.lsp" },
+]
+
+const GROUPED_TOOLS: GroupedToolDef[] = [
+ {
+ ids: ["todoread", "todowrite"],
+ label: "todoread / todowrite",
+ descriptionKey: "settings.autoApprove.tool.todoreadwrite",
+ },
+ {
+ ids: ["websearch", "codesearch"],
+ label: "websearch / codesearch",
+ descriptionKey: "settings.autoApprove.tool.websearchcodesearch",
+ },
+]
+
+const TRAILING_TOOLS: ToolDef[] = [
+ { id: "webfetch", descriptionKey: "settings.autoApprove.tool.webfetch" },
+ { id: "doom_loop", descriptionKey: "settings.autoApprove.tool.doom_loop" },
+]
+
+const RESTRICTION_ORDER: Record = { allow: 0, ask: 1, deny: 2 }
+
+type PermissionPatch = PermissionConfig
+
+function mostRestrictive(levels: PermissionLevel[]): PermissionLevel {
+ return levels.reduce(
+ (best, level) => (RESTRICTION_ORDER[level] > RESTRICTION_ORDER[best] ? level : best),
+ levels[0] ?? "allow",
+ )
+}
+
+function wildcardAction(rule: PermissionRule | undefined, fallback: PermissionLevel): PermissionLevel {
+ if (!rule) return fallback
+ if (typeof rule === "string") return rule
+ if (rule === null) return fallback
+ return rule["*"] ?? fallback
+}
+
+function inheritedWildcard(rule: PermissionRule | undefined): boolean {
+ if (!rule) return true
+ if (typeof rule === "string") return false
+ if (rule === null) return true
+ return rule["*"] === undefined || rule["*"] === null
+}
+
+function exceptions(rule: PermissionRule | undefined): Array<{ pattern: string; action: PermissionLevel }> {
+ if (!rule || typeof rule === "string") return []
+ return Object.entries(rule)
+ .filter(([key, action]) => key !== "*" && action !== null)
+ .map(([pattern, action]) => ({ pattern, action: action as PermissionLevel }))
+}
+
+function toolTitle(id: string): string {
+ return id
+ .split("_")
+ .map((word) => word[0].toUpperCase() + word.slice(1))
+ .join(" ")
+ .split(" / ")
+ .map((part) => part[0].toUpperCase() + part.slice(1))
+ .join(" / ")
+}
+
+const PermissionEditor: Component<{
+ permissions?: PermissionConfig
+ rules?: PermissionRuleItem[]
+ description?: string
+ component?: string
+ inherited?: boolean
+ onChange: (patch: PermissionPatch) => void
+}> = (props) => {
+ const perms = createMemo(() => props.permissions ?? {})
+
+ const levelFor = (tool: string): PermissionLevel =>
+ wildcardAction(perms()[tool], effectiveRuleLevel(props.rules, tool))
+
+ const ruleFor = (tool: string): PermissionRule | undefined => perms()[tool]
+
+ const setSimple = (tool: string, level: PermissionLevel) => {
+ props.onChange({ [tool]: level })
+ }
+
+ const clearSimple = (tool: string) => {
+ props.onChange({ [tool]: null })
+ }
+
+ const setGrouped = (ids: string[], level: PermissionLevel) => {
+ const patch: PermissionPatch = {}
+ for (const id of ids) patch[id] = level
+ props.onChange(patch)
+ }
+
+ const clearGrouped = (ids: string[]) => {
+ const patch: PermissionPatch = {}
+ for (const id of ids) patch[id] = null
+ props.onChange(patch)
+ }
+
+ const setWildcard = (tool: string, level: PermissionLevel) => {
+ const excs = exceptions(ruleFor(tool))
+ if (excs.length === 0) {
+ props.onChange({ [tool]: level })
+ return
+ }
+ const obj: Record = { "*": level }
+ for (const exc of excs) obj[exc.pattern] = exc.action
+ props.onChange({ [tool]: obj })
+ }
+
+ const clearWildcard = (tool: string) => {
+ const excs = exceptions(ruleFor(tool))
+ if (excs.length === 0) {
+ props.onChange({ [tool]: null })
+ return
+ }
+ props.onChange({ [tool]: { "*": null } })
+ }
+
+ const setException = (tool: string, pattern: string, level: PermissionLevel) => {
+ const current = ruleFor(tool)
+ const base: Record =
+ typeof current === "string" || current === null ? { "*": current } : { ...(current ?? {}) }
+ base[pattern] = level
+ props.onChange({ [tool]: base })
+ }
+
+ const addException = (tool: string, pattern: string) => {
+ const current = ruleFor(tool)
+ const base: Record =
+ typeof current === "string" || current === null ? { "*": current } : { ...(current ?? {}) }
+ base[pattern] = "allow"
+ props.onChange({ [tool]: base })
+ }
+
+ const removeException = (tool: string, pattern: string) => {
+ const current = ruleFor(tool)
+ if (!current || typeof current === "string") return
+ props.onChange({ [tool]: { [pattern]: null } })
+ }
+
+ return (
+
+
+
+ {props.description}
+
+
+
+
+ {(tool) => (
+ setWildcard(tool.id, level)}
+ onWildcardInherit={() => clearWildcard(tool.id)}
+ onExceptionChange={(pattern, level) => setException(tool.id, pattern, level)}
+ onExceptionAdd={(pattern) => addException(tool.id, pattern)}
+ onExceptionRemove={(pattern) => removeException(tool.id, pattern)}
+ />
+ )}
+
+
+
+ {(tool) => (
+ setSimple(tool.id, level)}
+ onInherit={() => clearSimple(tool.id)}
+ />
+ )}
+
+
+
+ {(group) => (
+ ruleFor(id) === undefined)}
+ onChange={(level) => setGrouped(group.ids, level)}
+ onInherit={() => clearGrouped(group.ids)}
+ />
+ )}
+
+
+
+ {(tool) => (
+ setSimple(tool.id, level)}
+ onInherit={() => clearSimple(tool.id)}
+ />
+ )}
+
+
+ )
+}
+
+const SimpleToolRow: Component<{
+ id: string
+ descriptionKey: string
+ level: PermissionLevel
+ inherited?: boolean
+ onChange: (level: PermissionLevel) => void
+ onInherit?: () => void
+}> = (props) => {
+ const language = useLanguage()
+ return (
+
+
+
+ {toolTitle(props.id)}
+
+
+ {language.t(props.descriptionKey)}
+
+
+
+
+ )
+}
+
+const GranularToolRow: Component<{
+ tool: GranularToolDef
+ rule: PermissionRule | undefined
+ fallback: PermissionLevel
+ inherited?: boolean
+ allowInherit?: boolean
+ onWildcardChange: (level: PermissionLevel) => void
+ onWildcardInherit: () => void
+ onExceptionChange: (pattern: string, level: PermissionLevel) => void
+ onExceptionAdd: (pattern: string) => void
+ onExceptionRemove: (pattern: string) => void
+}> = (props) => {
+ const language = useLanguage()
+ const [adding, setAdding] = createSignal(false)
+ const [input, setInput] = createSignal("")
+ let ref: HTMLInputElement | undefined
+
+ createEffect(() => {
+ if (adding()) ref?.focus()
+ })
+
+ const excs = createMemo(() => exceptions(props.rule))
+ const level = createMemo(() => wildcardAction(props.rule, props.fallback))
+
+ const submit = () => {
+ const val = input().trim()
+ if (val) {
+ props.onExceptionAdd(val)
+ setInput("")
+ }
+ setAdding(false)
+ }
+
+ const cancel = () => {
+ setInput("")
+ setAdding(false)
+ }
+
+ return (
+
+
+
+
+ {toolTitle(props.tool.id)}
+
+
+ {language.t(props.tool.descriptionKey)}
+
+
+
+
+
+
+
+ {language.t(props.tool.granular.wildcardKey)}
+
+
+
+
+
+
0}>
+
+
+ {language.t("settings.autoApprove.exceptions")}
+
+
+ {(exc) => (
+
+
+ {exc.pattern}
+
+
+
props.onExceptionChange(exc.pattern, level)}
+ />
+ props.onExceptionRemove(exc.pattern)}
+ />
+
+
+ )}
+
+
+
+
+
setAdding(true)}
+ >
+ +
+ {language.t(props.tool.granular.addKey)}
+
+ }
+ >
+
+ (ref = el)}
+ type="text"
+ value={input()}
+ onInput={(e) => setInput(e.currentTarget.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") submit()
+ if (e.key === "Escape") cancel()
+ }}
+ onBlur={() => {
+ if (!input().trim()) cancel()
+ }}
+ placeholder={language.t(props.tool.granular.placeholderKey)}
+ style={{
+ flex: 1,
+ "min-width": 0,
+ background: "var(--surface-strong-base, #252526)",
+ border: "1px solid var(--border-base, #434443)",
+ "border-radius": "2px",
+ color: "var(--text-base, #ccc)",
+ "font-size": "13px",
+ "font-family": "var(--vscode-editor-font-family, monospace)",
+ padding: "4px 8px",
+ outline: "none",
+ }}
+ />
+
+
+
+
+ )
+}
+
+const ActionSelect: Component<{
+ level: PermissionLevel
+ inherited?: boolean
+ onChange: (level: PermissionLevel) => void
+ onInherit?: () => void
+}> = (props) => {
+ const language = useLanguage()
+ const opts = createMemo(() => (props.onInherit ? [INHERIT_OPTION, ...LEVEL_OPTIONS] : LEVEL_OPTIONS))
+ return (
+ option.value === props.level)}
+ value={(option) => option.value}
+ label={(option) => language.t(option.labelKey)}
+ onSelect={(option) => {
+ if (!option) return
+ if (option.value === "inherit") {
+ props.onInherit?.()
+ return
+ }
+ props.onChange(option.value)
+ }}
+ variant="secondary"
+ size="small"
+ triggerVariant="settings"
+ />
+ )
+}
+
+export default PermissionEditor
diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/permission-utils.ts b/packages/kilo-vscode/webview-ui/src/components/settings/permission-utils.ts
new file mode 100644
index 0000000000..ff9989907d
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/permission-utils.ts
@@ -0,0 +1,17 @@
+import type { PermissionLevel, PermissionRuleItem } from "../../types/messages"
+
+function matchTool(tool: string, pattern: string): boolean {
+ if (pattern === tool || pattern === "*") return true
+ if (!pattern.includes("*")) return false
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")
+ return new RegExp(`^${escaped}$`).test(tool)
+}
+
+export function effectiveRuleLevel(rules: PermissionRuleItem[] | undefined, tool: string): PermissionLevel {
+ const list = rules ?? []
+ for (let i = list.length - 1; i >= 0; i--) {
+ const item = list[i]
+ if (item.pattern === "*" && matchTool(tool, item.permission)) return item.action
+ }
+ return "ask"
+}
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
index e03b8b247f..d5b1a3abee 100644
--- a/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts
+++ b/packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts
@@ -32,6 +32,7 @@ export const KNOWN_KEYS: ReadonlyArray = [
"commit_message",
"tools",
"layout",
+ "auto_collapse_reasoning",
"terminal_command_display",
"indexing",
"experimental",
diff --git a/packages/kilo-vscode/webview-ui/src/context/config.tsx b/packages/kilo-vscode/webview-ui/src/context/config.tsx
index 071b467be0..29a61b8c70 100644
--- a/packages/kilo-vscode/webview-ui/src/context/config.tsx
+++ b/packages/kilo-vscode/webview-ui/src/context/config.tsx
@@ -14,6 +14,21 @@ import { useVSCode } from "./vscode"
import type { Config, ExtensionMessage, FeatureFlags } from "../types/messages"
import { deepMerge, stripNulls, resolveConfig } from "../utils/config-utils"
+// Top-level config keys that persist to the project's kilo.json rather than the
+// global one. Settings that are inherently per-repository (e.g. commit message
+// conventions) belong here so they don't leak across workspaces.
+const PROJECT_SCOPED_KEYS: ReadonlySet = new Set(["commit_message"])
+
+function splitByScope(draft: Partial) {
+ const global: Record = {}
+ const project: Record = {}
+ for (const [key, value] of Object.entries(draft)) {
+ if (PROJECT_SCOPED_KEYS.has(key)) project[key] = value
+ else global[key] = value
+ }
+ return { global: global as Partial, project: project as Partial }
+}
+
export interface SaveError {
message: string
details?: string
@@ -137,7 +152,11 @@ export const ConfigProvider: ParentComponent = (props) => {
// If the write fails, the save bar stays visible so the user can retry.
setSaving(true)
setSaveError(null)
- vscode.postMessage({ type: "updateConfig", config: changes })
+ // Split so per-project settings (e.g. commit_message.prompt) land in the
+ // workspace's kilo.json instead of the global one. Send one message so the
+ // extension confirms only after both scopes are saved.
+ const split = splitByScope(changes)
+ vscode.postMessage({ type: "updateConfig", config: split.global, projectConfig: split.project })
}
function discardConfig() {
diff --git a/packages/kilo-vscode/webview-ui/src/context/display.tsx b/packages/kilo-vscode/webview-ui/src/context/display.tsx
new file mode 100644
index 0000000000..eb74fa9f00
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/context/display.tsx
@@ -0,0 +1,33 @@
+import { createContext, createMemo, useContext, type Accessor, type ParentComponent } from "solid-js"
+import { useConfig } from "./config"
+
+interface DisplayContextValue {
+ reasoningAutoCollapse: Accessor
+ setReasoningAutoCollapse: (collapse: boolean) => void
+}
+
+export const DisplayContext = createContext()
+
+export const DisplayProvider: ParentComponent = (props) => {
+ const { config, updateConfig } = useConfig()
+ const reasoningAutoCollapse = createMemo(() => config().auto_collapse_reasoning ?? false)
+
+ return (
+ updateConfig({ auto_collapse_reasoning: collapse }),
+ }}
+ >
+ {props.children}
+
+ )
+}
+
+export function useDisplay(): DisplayContextValue {
+ const context = useContext(DisplayContext)
+ if (!context) {
+ throw new Error("useDisplay must be used within a DisplayProvider")
+ }
+ return context
+}
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts
index 589b61b243..8e8f66afff 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts
@@ -1025,7 +1025,7 @@ export const dict = {
"settings.context.title": "السياق",
"settings.indexing.title": "الفهرسة",
"settings.indexing.enable.title": "تمكين الفهرسة",
- "settings.indexing.enable.description": "تشغيل أو إيقاف فهرسة قاعدة الكود الدلالية لمساحة العمل هذه.",
+ "settings.indexing.enable.description": "تشغيل أو إيقاف فهرسة قاعدة الكود الدلالية.",
"settings.indexing.provider.title": "موفر التضمين",
"settings.indexing.provider.description": "اختر الموفر المستخدم لإنشاء التضمينات للبحث الدلالي.",
"settings.indexing.model.title": "نموذج التضمين",
@@ -1184,6 +1184,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "بحث في قاعدة الكود",
"settings.experimental.codebaseSearch.description": "تمكين البحث بالذكاء الاصطناعي باللغة الطبيعية عبر قاعدة الكود",
+ "settings.experimental.agentManagerTool.title": "أداة Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "السماح للوكلاء ببدء جلسات Agent Manager المحلية وجلسات worktree من استدعاء أداة",
"settings.experimental.continueOnDeny.title": "المتابعة عند الرفض",
"settings.experimental.continueOnDeny.description": "متابعة حلقة الوكيل عند رفض الإذن",
"settings.experimental.mcpTimeout.title": "مهلة MCP (مللي ثانية)",
@@ -1372,6 +1375,9 @@ export const dict = {
"settings.display.layout.description": "وضع التخطيط لواجهة الدردشة",
"settings.display.layout.auto": "تلقائي",
"settings.display.layout.stretch": "تمديد",
+ "settings.display.reasoningAutoCollapse.title": "طي الاستدلال تلقائيًا",
+ "settings.display.reasoningAutoCollapse.description":
+ "يطوي كتل الاستدلال بعد أن ينتهي الوكيل من كتابتها. اتركه معطلاً لإبقاء الاستدلال موسعًا ما لم تطوه يدويًا.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts
index 9624a7d3ff..0287962c2d 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts
@@ -1044,8 +1044,7 @@ export const dict = {
"settings.language.title": "Idioma",
"settings.indexing.title": "Indexação",
"settings.indexing.enable.title": "Ativar indexação",
- "settings.indexing.enable.description":
- "Ativar ou desativar a indexação semântica da base de código para este espaço de trabalho.",
+ "settings.indexing.enable.description": "Ativar ou desativar a indexação semântica da base de código.",
"settings.indexing.provider.title": "Provedor de embedding",
"settings.indexing.provider.description": "Escolha o provedor usado para gerar embeddings para busca semântica.",
"settings.indexing.model.title": "Modelo de embedding",
@@ -1211,6 +1210,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Pesquisa de código",
"settings.experimental.codebaseSearch.description":
"Ativar pesquisa por linguagem natural com IA em toda a base de código",
+ "settings.experimental.agentManagerTool.title": "Ferramenta Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "Permitir que agentes iniciem sessões locais e sessões worktree do Agent Manager a partir de uma chamada de ferramenta",
"settings.experimental.continueOnDeny.title": "Continuar ao negar",
"settings.experimental.continueOnDeny.description": "Continuar o loop do agente quando uma permissão é negada",
"settings.experimental.mcpTimeout.title": "Tempo limite MCP (ms)",
@@ -1411,6 +1413,9 @@ export const dict = {
"settings.display.layout.description": "Modo de layout para a interface de chat",
"settings.display.layout.auto": "Automático",
"settings.display.layout.stretch": "Esticar",
+ "settings.display.reasoningAutoCollapse.title": "Recolher raciocínio automaticamente",
+ "settings.display.reasoningAutoCollapse.description":
+ "Recolhe os blocos de raciocínio depois que o agente termina de escrevê-los. Deixe desativado para manter o raciocínio expandido, a menos que você o recolha manualmente.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts
index f196331e1c..fbbc30ebb0 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts
@@ -757,8 +757,7 @@ export const dict = {
"Ostavite prazno za automatsko prepoznavanje dimenzije embeddinga iz modela.",
"settings.indexing.dimension.placeholder": "Auto",
"settings.indexing.dimension.title": "Dimenzija vektora",
- "settings.indexing.enable.description":
- "Uključite ili isključite semantičko indeksiranje baze koda za ovaj radni prostor.",
+ "settings.indexing.enable.description": "Uključite ili isključite semantičko indeksiranje baze koda.",
"settings.indexing.enable.title": "Omogući indeksiranje",
"settings.indexing.lancedbDirectory.description": "Opcionalni direktorij za lokalno LanceDB skladište.",
"settings.indexing.lancedbDirectory.placeholder": "Ostavite prazno za zadano",
@@ -1211,6 +1210,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "Pretraga koda",
"settings.experimental.codebaseSearch.description": "Omogući AI pretragu prirodnim jezikom kroz bazu koda",
+ "settings.experimental.agentManagerTool.title": "Agent Manager alat",
+ "settings.experimental.agentManagerTool.description":
+ "Dozvoli agentima da pokreću lokalne Agent Manager sesije i worktree sesije iz poziva alata",
"settings.experimental.continueOnDeny.title": "Nastavi pri odbijanju",
"settings.experimental.continueOnDeny.description": "Nastavi petlju agenta kada je dozvola odbijena",
"settings.experimental.mcpTimeout.title": "MCP istek vremena (ms)",
@@ -1408,6 +1410,9 @@ export const dict = {
"settings.display.layout.description": "Način rasporeda za sučelje chata",
"settings.display.layout.auto": "Automatski",
"settings.display.layout.stretch": "Rastegni",
+ "settings.display.reasoningAutoCollapse.title": "Automatski sažmi razmišljanje",
+ "settings.display.reasoningAutoCollapse.description":
+ "Sažima blokove razmišljanja nakon što ih agent završi pisati. Ostavite isključeno da razmišljanje ostane prošireno, osim ako ga ručno sažmete.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts
index 8917aa34df..77072bc4d4 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts
@@ -767,7 +767,7 @@ export const dict = {
"settings.indexing.title": "Indeksering",
"settings.indexing.enable.title": "Aktivér indeksering",
- "settings.indexing.enable.description": "Slå semantisk kodebase-indeksering til eller fra for dette arbejdsområde.",
+ "settings.indexing.enable.description": "Slå semantisk kodebase-indeksering til eller fra.",
"settings.indexing.provider.title": "Embedding-udbyder",
"settings.indexing.provider.description":
"Vælg udbyderen, der bruges til at generere embeddings til semantisk søgning.",
@@ -1203,6 +1203,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "Kodesøgning",
"settings.experimental.codebaseSearch.description": "Aktiver AI-drevet naturlig sprogsøgning på tværs af kodebasen",
+ "settings.experimental.agentManagerTool.title": "Agent Manager-værktøj",
+ "settings.experimental.agentManagerTool.description":
+ "Tillad agenter at starte lokale Agent Manager-sessioner og worktree-sessioner fra et værktøjskald",
"settings.experimental.continueOnDeny.title": "Fortsæt ved afvisning",
"settings.experimental.continueOnDeny.description": "Fortsæt agentløkken, når en tilladelse afvises",
"settings.experimental.mcpTimeout.title": "MCP-timeout (ms)",
@@ -1396,6 +1399,9 @@ export const dict = {
"settings.display.layout.description": "Layouttilstand for chatgrænsefladen",
"settings.display.layout.auto": "Automatisk",
"settings.display.layout.stretch": "Stræk",
+ "settings.display.reasoningAutoCollapse.title": "Skjul ræsonnement automatisk",
+ "settings.display.reasoningAutoCollapse.description":
+ "Skjuler ræsonnementsblokke, når agenten er færdig med at skrive dem. Lad den være slået fra for at holde ræsonnement udvidet, medmindre du skjuler det manuelt.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts
index a5ca1efa8a..c5a2a719ab 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts
@@ -763,8 +763,7 @@ export const dict = {
"Leer lassen, um die Embedding-Dimension automatisch aus dem Modell zu erkennen.",
"settings.indexing.dimension.placeholder": "Auto",
"settings.indexing.dimension.title": "Vektordimension",
- "settings.indexing.enable.description":
- "Semantische Codebasis-Indizierung für diesen Arbeitsbereich ein- oder ausschalten.",
+ "settings.indexing.enable.description": "Semantische Codebasis-Indizierung ein- oder ausschalten.",
"settings.indexing.enable.title": "Indizierung aktivieren",
"settings.indexing.lancedbDirectory.description": "Optionaler Ordner für den lokalen LanceDB-Speicher.",
"settings.indexing.lancedbDirectory.placeholder": "Leer lassen für Standard",
@@ -1226,6 +1225,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Codebase-Suche",
"settings.experimental.codebaseSearch.description":
"KI-gestützte Suche in natürlicher Sprache über die gesamte Codebasis aktivieren",
+ "settings.experimental.agentManagerTool.title": "Agent Manager-Werkzeug",
+ "settings.experimental.agentManagerTool.description":
+ "Agenten erlauben, lokale Agent Manager-Sitzungen und Worktree-Sitzungen per Werkzeugaufruf zu starten",
"settings.experimental.continueOnDeny.title": "Bei Ablehnung fortfahren",
"settings.experimental.continueOnDeny.description":
"Agent-Schleife fortsetzen, wenn eine Berechtigung abgelehnt wird",
@@ -1426,6 +1428,9 @@ export const dict = {
"settings.display.layout.description": "Layout-Modus für die Chat-Oberfläche",
"settings.display.layout.auto": "Automatisch",
"settings.display.layout.stretch": "Gestreckt",
+ "settings.display.reasoningAutoCollapse.title": "Reasoning automatisch einklappen",
+ "settings.display.reasoningAutoCollapse.description":
+ "Klappt Reasoning-Blöcke ein, nachdem der Agent sie fertig geschrieben hat. Deaktiviert lassen, damit Reasoning erweitert bleibt, sofern du es nicht manuell einklappst.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts
index f97d9ed93f..4fa6c6a1f1 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts
@@ -1003,7 +1003,7 @@ export const dict = {
"settings.indexing.title": "Indexing",
"settings.indexing.status.title": "Status",
"settings.indexing.enable.title": "Enable indexing",
- "settings.indexing.enable.description": "Turn semantic codebase indexing on or off for this workspace.",
+ "settings.indexing.enable.description": "Turn semantic codebase indexing on or off.",
"settings.indexing.provider.title": "Embedding provider",
"settings.indexing.provider.description": "Choose the provider used to generate embeddings for semantic search.",
"settings.indexing.model.title": "Embedding model",
@@ -1197,6 +1197,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "Codebase Search",
"settings.experimental.codebaseSearch.description": "Enable AI-powered natural language search across your codebase",
+ "settings.experimental.agentManagerTool.title": "Agent Manager Tool",
+ "settings.experimental.agentManagerTool.description":
+ "Allow agents to start Agent Manager local sessions and worktree sessions from a tool call",
"settings.experimental.continueOnDeny.title": "Continue on Deny",
"settings.experimental.continueOnDeny.description": "Continue the agent loop when a permission is denied",
"settings.experimental.mcpTimeout.title": "MCP Timeout (ms)",
@@ -1390,6 +1393,9 @@ export const dict = {
"settings.display.layout.description": "Layout mode for the chat interface",
"settings.display.layout.auto": "Auto",
"settings.display.layout.stretch": "Stretch",
+ "settings.display.reasoningAutoCollapse.title": "Auto-Collapse Reasoning",
+ "settings.display.reasoningAutoCollapse.description":
+ "Collapse reasoning blocks after the agent finishes writing them. Leave off to keep reasoning expanded unless you collapse it manually.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts
index d6fe1b8ce5..1b7e022492 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts
@@ -754,8 +754,7 @@ export const dict = {
"settings.indexing.title": "Indexación",
"settings.indexing.enable.title": "Habilitar indexación",
- "settings.indexing.enable.description":
- "Activar o desactivar la indexación semántica de la base de código para este espacio de trabajo.",
+ "settings.indexing.enable.description": "Activar o desactivar la indexación semántica de la base de código.",
"settings.indexing.provider.title": "Proveedor de embeddings",
"settings.indexing.provider.description":
"Elige el proveedor utilizado para generar embeddings para búsqueda semántica.",
@@ -1217,6 +1216,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Búsqueda de código",
"settings.experimental.codebaseSearch.description":
"Habilitar búsqueda por lenguaje natural con IA en toda la base de código",
+ "settings.experimental.agentManagerTool.title": "Herramienta Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "Permitir que los agentes inicien sesiones locales y sesiones de worktree de Agent Manager desde una llamada de herramienta",
"settings.experimental.continueOnDeny.title": "Continuar al denegar",
"settings.experimental.continueOnDeny.description": "Continuar el bucle del agente cuando se deniega un permiso",
"settings.experimental.mcpTimeout.title": "Tiempo de espera MCP (ms)",
@@ -1417,6 +1419,9 @@ export const dict = {
"settings.display.layout.description": "Modo de diseño para la interfaz de chat",
"settings.display.layout.auto": "Automático",
"settings.display.layout.stretch": "Estirar",
+ "settings.display.reasoningAutoCollapse.title": "Contraer razonamiento automáticamente",
+ "settings.display.reasoningAutoCollapse.description":
+ "Contrae los bloques de razonamiento después de que el agente termine de escribirlos. Déjalo desactivado para mantener el razonamiento expandido, a menos que lo contraigas manualmente.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts
index 2d87686949..ff841abcd4 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts
@@ -682,8 +682,7 @@ export const dict = {
"Laissez vide pour détecter automatiquement la dimension d'embedding à partir du modèle.",
"settings.indexing.dimension.placeholder": "Auto",
"settings.indexing.dimension.title": "Dimension vectorielle",
- "settings.indexing.enable.description":
- "Activer ou désactiver l'indexation sémantique de la base de code pour cet espace de travail.",
+ "settings.indexing.enable.description": "Activer ou désactiver l'indexation sémantique de la base de code.",
"settings.indexing.enable.title": "Activer l'indexation",
"settings.indexing.lancedbDirectory.description": "Répertoire optionnel pour le stockage local LanceDB.",
"settings.indexing.lancedbDirectory.placeholder": "Laissez vide pour la valeur par défaut",
@@ -1229,6 +1228,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Recherche de code",
"settings.experimental.codebaseSearch.description":
"Activer la recherche en langage naturel par IA dans toute la base de code",
+ "settings.experimental.agentManagerTool.title": "Outil Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "Autoriser les agents à démarrer des sessions locales Agent Manager et des sessions worktree depuis un appel d'outil",
"settings.experimental.continueOnDeny.title": "Continuer en cas de refus",
"settings.experimental.continueOnDeny.description":
"Continuer la boucle de l'agent lorsqu'une autorisation est refusée",
@@ -1432,6 +1434,9 @@ export const dict = {
"settings.display.layout.description": "Mode de disposition pour l'interface de chat",
"settings.display.layout.auto": "Automatique",
"settings.display.layout.stretch": "Étiré",
+ "settings.display.reasoningAutoCollapse.title": "Réduire automatiquement le raisonnement",
+ "settings.display.reasoningAutoCollapse.description":
+ "Réduit les blocs de raisonnement une fois que l'agent a fini de les écrire. Laissez désactivé pour garder le raisonnement développé, sauf si vous le réduisez manuellement.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts
index aab61f3dfa..eaa615e650 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts
@@ -748,8 +748,7 @@ export const dict = {
"settings.indexing.dimension.description": "空のままにすると、モデルから埋め込み次元を自動検出します。",
"settings.indexing.dimension.placeholder": "自動",
"settings.indexing.dimension.title": "ベクトル次元",
- "settings.indexing.enable.description":
- "このワークスペースのセマンティックコードベースインデックスをオンまたはオフにします。",
+ "settings.indexing.enable.description": "セマンティックコードベースインデックスをオンまたはオフにします。",
"settings.indexing.enable.title": "インデックスを有効にする",
"settings.indexing.lancedbDirectory.description": "ローカルLanceDBストアのオプションのディレクトリ。",
"settings.indexing.lancedbDirectory.placeholder": "デフォルトの場合は空のままにする",
@@ -1200,6 +1199,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "コードベース検索",
"settings.experimental.codebaseSearch.description": "コードベース全体でAIによる自然言語検索を有効にする",
+ "settings.experimental.agentManagerTool.title": "Agent Manager ツール",
+ "settings.experimental.agentManagerTool.description":
+ "エージェントがツール呼び出しから Agent Manager のローカルセッションとワークツリーセッションを開始できるようにする",
"settings.experimental.continueOnDeny.title": "拒否時に続行",
"settings.experimental.continueOnDeny.description": "権限が拒否された場合にエージェントループを続行",
"settings.experimental.mcpTimeout.title": "MCPタイムアウト(ミリ秒)",
@@ -1394,6 +1396,9 @@ export const dict = {
"settings.display.layout.description": "チャットインターフェースのレイアウトモード",
"settings.display.layout.auto": "自動",
"settings.display.layout.stretch": "ストレッチ",
+ "settings.display.reasoningAutoCollapse.title": "推論を自動で折りたたむ",
+ "settings.display.reasoningAutoCollapse.description":
+ "エージェントが推論の書き込みを終えた後に推論ブロックを自動で折りたたみます。手動で折りたたむまでは推論を展開したままにするには、オフのままにしてください。",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts
index afb1a148dd..525fa5a4f0 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts
@@ -1037,7 +1037,7 @@ export const dict = {
"settings.indexing.dimension.description": "비워두면 모델에서 임베딩 차원을 자동으로 감지합니다.",
"settings.indexing.dimension.placeholder": "자동",
"settings.indexing.dimension.title": "벡터 차원",
- "settings.indexing.enable.description": "이 작업 공간의 의미적 코드베이스 인덱싱을 켜거나 끕니다.",
+ "settings.indexing.enable.description": "의미적 코드베이스 인덱싱을 켜거나 끕니다.",
"settings.indexing.enable.title": "인덱싱 활성화",
"settings.indexing.lancedbDirectory.description": "로컬 LanceDB 저장소의 선택적 디렉터리입니다.",
"settings.indexing.lancedbDirectory.placeholder": "기본값 사용을 위해 비워두세요",
@@ -1193,6 +1193,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "코드베이스 검색",
"settings.experimental.codebaseSearch.description": "코드베이스 전체에서 AI 기반 자연어 검색 활성화",
+ "settings.experimental.agentManagerTool.title": "Agent Manager 도구",
+ "settings.experimental.agentManagerTool.description":
+ "에이전트가 도구 호출로 Agent Manager 로컬 세션 및 워크트리 세션을 시작하도록 허용",
"settings.experimental.continueOnDeny.title": "거부 시 계속",
"settings.experimental.continueOnDeny.description": "권한이 거부되면 에이전트 루프 계속",
"settings.experimental.mcpTimeout.title": "MCP 타임아웃 (ms)",
@@ -1378,6 +1381,9 @@ export const dict = {
"settings.display.layout.description": "채팅 인터페이스의 레이아웃 모드",
"settings.display.layout.auto": "자동",
"settings.display.layout.stretch": "늘리기",
+ "settings.display.reasoningAutoCollapse.title": "추론 자동 접기",
+ "settings.display.reasoningAutoCollapse.description":
+ "에이전트가 추론 작성을 마친 뒤 추론 블록을 자동으로 접습니다. 수동으로 접기 전까지 추론을 펼친 상태로 두려면 끄세요.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts
index ca3380ea7b..9dec7f8c7c 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts
@@ -1041,7 +1041,7 @@ export const dict = {
"settings.indexing.title": "Indexering",
"settings.indexing.status.title": "Status",
"settings.indexing.enable.title": "Indexering inschakelen",
- "settings.indexing.enable.description": "Schakel semantische codebase-indexering in of uit voor deze werkruimte.",
+ "settings.indexing.enable.description": "Schakel semantische codebase-indexering in of uit.",
"settings.indexing.provider.title": "Embedding-provider",
"settings.indexing.provider.description":
"Kies de provider die wordt gebruikt om embeddings te genereren voor semantisch zoeken.",
@@ -1213,6 +1213,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Codebase Zoeken",
"settings.experimental.codebaseSearch.description":
"Schakel AI-aangedreven zoeken in natuurlijke taal door je codebase in",
+ "settings.experimental.agentManagerTool.title": "Agent Manager-tool",
+ "settings.experimental.agentManagerTool.description":
+ "Sta agents toe om lokale Agent Manager-sessies en worktree-sessies te starten vanuit een tool call",
"settings.experimental.continueOnDeny.title": "Doorgaan bij weigering",
"settings.experimental.continueOnDeny.description":
"Ga door met de agent loop wanneer een toestemming wordt geweigerd",
@@ -1380,6 +1383,9 @@ export const dict = {
"settings.display.layout.description": "Lay-outmodus voor de chatinterface",
"settings.display.layout.auto": "Auto",
"settings.display.layout.stretch": "Uitrekken",
+ "settings.display.reasoningAutoCollapse.title": "Redenering automatisch inklappen",
+ "settings.display.reasoningAutoCollapse.description":
+ "Klapt redeneerblokken in nadat de agent klaar is met schrijven. Laat uitgeschakeld om redenering uitgeklapt te houden, tenzij je die handmatig inklapt.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts
index cc1d2de885..7cdf147224 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts
@@ -1180,6 +1180,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "Kodesøk",
"settings.experimental.codebaseSearch.description": "Aktiver AI-drevet naturlig språksøk på tvers av kodebasen",
+ "settings.experimental.agentManagerTool.title": "Agent Manager-verktøy",
+ "settings.experimental.agentManagerTool.description":
+ "Tillat agenter å starte lokale Agent Manager-økter og worktree-økter fra et verktøykall",
"settings.experimental.continueOnDeny.title": "Fortsett ved avvisning",
"settings.experimental.continueOnDeny.description": "Fortsett agentløkken når en tillatelse avvises",
"settings.experimental.mcpTimeout.title": "MCP-tidsavbrudd (ms)",
@@ -1195,7 +1198,7 @@ export const dict = {
"settings.experimental.toolToggles": "Verktøybrytere",
"settings.indexing.title": "Indeksering",
"settings.indexing.enable.title": "Aktiver indeksering",
- "settings.indexing.enable.description": "Slå semantisk kodebaseindeksering på eller av for dette arbeidsområdet.",
+ "settings.indexing.enable.description": "Slå semantisk kodebaseindeksering på eller av.",
"settings.indexing.provider.title": "Embedding-leverandør",
"settings.indexing.provider.description": "Velg leverandøren som brukes til å generere embeddings for semantisk søk.",
"settings.indexing.model.title": "Embedding-modell",
@@ -1395,6 +1398,9 @@ export const dict = {
"settings.display.layout.description": "Layoutmodus for chatgrensesnittet",
"settings.display.layout.auto": "Automatisk",
"settings.display.layout.stretch": "Strekk",
+ "settings.display.reasoningAutoCollapse.title": "Skjul resonnement automatisk",
+ "settings.display.reasoningAutoCollapse.description":
+ "Skjuler resonnementblokker etter at agenten er ferdig med å skrive dem. La være av for å holde resonnement utvidet med mindre du skjuler det manuelt.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts
index e71f923357..1df0229278 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts
@@ -1180,6 +1180,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "Wyszukiwanie kodu",
"settings.experimental.codebaseSearch.description": "Włącz wyszukiwanie w języku naturalnym z AI w całej bazie kodu",
+ "settings.experimental.agentManagerTool.title": "Narzędzie Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "Zezwól agentom na uruchamianie lokalnych sesji Agent Manager i sesji worktree z wywołania narzędzia",
"settings.experimental.continueOnDeny.title": "Kontynuuj przy odmowie",
"settings.experimental.continueOnDeny.description": "Kontynuuj pętlę agenta po odmowie uprawnienia",
"settings.experimental.mcpTimeout.title": "Limit czasu MCP (ms)",
@@ -1197,8 +1200,7 @@ export const dict = {
"settings.indexing.dimension.description": "Pozostaw puste, aby automatycznie wykryć wymiar osadzania z modelu.",
"settings.indexing.dimension.placeholder": "Automatycznie",
"settings.indexing.dimension.title": "Wymiar wektora",
- "settings.indexing.enable.description":
- "Włącz lub wyłącz semantyczne indeksowanie bazy kodu dla tego obszaru roboczego.",
+ "settings.indexing.enable.description": "Włącz lub wyłącz semantyczne indeksowanie bazy kodu.",
"settings.indexing.enable.title": "Włącz indeksowanie",
"settings.indexing.lancedbDirectory.description": "Opcjonalny katalog dla lokalnego magazynu LanceDB.",
"settings.indexing.lancedbDirectory.placeholder": "Pozostaw puste dla domyślnego",
@@ -1404,6 +1406,9 @@ export const dict = {
"settings.display.layout.description": "Tryb układu interfejsu czatu",
"settings.display.layout.auto": "Automatyczny",
"settings.display.layout.stretch": "Rozciągnij",
+ "settings.display.reasoningAutoCollapse.title": "Automatycznie zwijaj rozumowanie",
+ "settings.display.reasoningAutoCollapse.description":
+ "Zwija bloki rozumowania po zakończeniu ich pisania przez agenta. Pozostaw wyłączone, aby rozumowanie pozostało rozwinięte, chyba że zwiniesz je ręcznie.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts
index 5129da6362..dc5d61053d 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts
@@ -753,8 +753,7 @@ export const dict = {
"settings.indexing.title": "Индексация",
"settings.indexing.enable.title": "Включить индексацию",
- "settings.indexing.enable.description":
- "Включить или отключить семантическую индексацию кодовой базы для этого рабочего пространства.",
+ "settings.indexing.enable.description": "Включить или отключить семантическую индексацию кодовой базы.",
"settings.indexing.status.title": "Статус",
"settings.indexing.provider.title": "Провайдер эмбеддингов",
"settings.indexing.provider.description": "Выберите провайдера для генерации эмбеддингов при семантическом поиске.",
@@ -1208,6 +1207,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "Поиск по коду",
"settings.experimental.codebaseSearch.description": "Включить поиск на естественном языке с ИИ по всей кодовой базе",
+ "settings.experimental.agentManagerTool.title": "Инструмент Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "Разрешить агентам запускать локальные сеансы Agent Manager и сеансы worktree через вызов инструмента",
"settings.experimental.continueOnDeny.title": "Продолжить при отказе",
"settings.experimental.continueOnDeny.description": "Продолжить цикл агента при отказе в разрешении",
"settings.experimental.mcpTimeout.title": "Таймаут MCP (мс)",
@@ -1404,6 +1406,9 @@ export const dict = {
"settings.display.layout.description": "Режим макета для интерфейса чата",
"settings.display.layout.auto": "Авто",
"settings.display.layout.stretch": "Растянуть",
+ "settings.display.reasoningAutoCollapse.title": "Автоматически сворачивать рассуждение",
+ "settings.display.reasoningAutoCollapse.description":
+ "Сворачивает блоки рассуждения после того, как агент закончит их писать. Оставьте выключенным, чтобы рассуждение оставалось раскрытым, пока вы не свернете его вручную.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts
index ce75e9baef..0d60a88a24 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts
@@ -744,7 +744,7 @@ export const dict = {
"settings.indexing.title": "การสร้างดัชนี",
"settings.indexing.enable.title": "เปิดใช้งานการสร้างดัชนี",
- "settings.indexing.enable.description": "เปิดหรือปิดการสร้างดัชนีโค้ดเบสเชิงความหมายสำหรับพื้นที่ทำงานนี้",
+ "settings.indexing.enable.description": "เปิดหรือปิดการสร้างดัชนีโค้ดเบสเชิงความหมาย",
"settings.indexing.provider.title": "ผู้ให้บริการการฝัง",
"settings.indexing.provider.description": "เลือกผู้ให้บริการที่ใช้สร้างการฝังสำหรับการค้นหาเชิงความหมาย",
"settings.indexing.model.title": "โมเดลการฝัง",
@@ -1191,6 +1191,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "ค้นหาโค้ดเบส",
"settings.experimental.codebaseSearch.description": "เปิดใช้งานการค้นหาด้วยภาษาธรรมชาติโดย AI ทั่วทั้งโค้ดเบส",
+ "settings.experimental.agentManagerTool.title": "เครื่องมือ Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "อนุญาตให้เอเจนต์เริ่มเซสชัน Agent Manager ในเครื่องและเซสชัน worktree จากการเรียกเครื่องมือ",
"settings.experimental.continueOnDeny.title": "ดำเนินต่อเมื่อถูกปฏิเสธ",
"settings.experimental.continueOnDeny.description": "ดำเนินลูปเอเจนต์ต่อเมื่อสิทธิ์ถูกปฏิเสธ",
"settings.experimental.mcpTimeout.title": "หมดเวลา MCP (มิลลิวินาที)",
@@ -1377,6 +1380,9 @@ export const dict = {
"settings.display.layout.description": "โหมดเค้าโครงสำหรับอินเทอร์เฟซแชท",
"settings.display.layout.auto": "อัตโนมัติ",
"settings.display.layout.stretch": "ยืด",
+ "settings.display.reasoningAutoCollapse.title": "ยุบเหตุผลอัตโนมัติ",
+ "settings.display.reasoningAutoCollapse.description":
+ "ยุบ block เหตุผลหลังจากเอเจนต์เขียนเสร็จ ปิดไว้เพื่อให้เหตุผลยังคงขยายอยู่ เว้นแต่คุณจะยุบเอง",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts
index 88a6ff675e..dee0b5de84 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts
@@ -1040,7 +1040,7 @@ export const dict = {
"settings.indexing.title": "İndeksleme",
"settings.indexing.status.title": "Durum",
"settings.indexing.enable.title": "İndekslemeyi etkinleştir",
- "settings.indexing.enable.description": "Bu çalışma alanı için anlamsal kod tabanı indekslemeyi açın veya kapatın.",
+ "settings.indexing.enable.description": "Anlamsal kod tabanı indekslemeyi açın veya kapatın.",
"settings.indexing.provider.title": "Yerleştirme sağlayıcısı",
"settings.indexing.provider.description":
"Anlamsal arama için yerleştirmeleri oluşturmak amacıyla kullanılacak sağlayıcıyı seçin.",
@@ -1206,6 +1206,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Kod Tabanı Araması",
"settings.experimental.codebaseSearch.description":
"Kod tabanınız genelinde yapay zeka destekli doğal dil aramasını etkinleştir",
+ "settings.experimental.agentManagerTool.title": "Agent Manager Aracı",
+ "settings.experimental.agentManagerTool.description":
+ "Ajanların bir araç çağrısından Agent Manager yerel oturumları ve worktree oturumları başlatmasına izin ver",
"settings.experimental.continueOnDeny.title": "Reddetme Durumunda Devam Et",
"settings.experimental.continueOnDeny.description": "Bir izin reddedildiğinde ajan döngüsüne devam et",
"settings.experimental.mcpTimeout.title": "MCP Zaman Aşımı (ms)",
@@ -1369,6 +1372,9 @@ export const dict = {
"settings.display.layout.description": "Sohbet arayüzü için düzen modu",
"settings.display.layout.auto": "Otomatik",
"settings.display.layout.stretch": "Genişlet",
+ "settings.display.reasoningAutoCollapse.title": "Akıl yürütmeyi otomatik daralt",
+ "settings.display.reasoningAutoCollapse.description":
+ "Ajan yazmayı bitirdikten sonra akıl yürütme bloklarını daraltır. Manuel olarak daraltmadığınız sürece akıl yürütmenin geniş kalması için kapalı bırakın.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts
index 6db47099df..4957214438 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts
@@ -1040,8 +1040,7 @@ export const dict = {
"settings.indexing.title": "Індексування",
"settings.indexing.status.title": "Статус",
"settings.indexing.enable.title": "Увімкнути індексування",
- "settings.indexing.enable.description":
- "Увімкніть або вимкніть семантичне індексування кодової бази для цього робочого простору.",
+ "settings.indexing.enable.description": "Увімкніть або вимкніть семантичне індексування кодової бази.",
"settings.indexing.provider.title": "Провайдер ембедингів",
"settings.indexing.provider.description": "Виберіть провайдера для генерації ембедингів для семантичного пошуку.",
"settings.indexing.model.title": "Модель ембедингів",
@@ -1209,6 +1208,9 @@ export const dict = {
"settings.experimental.codebaseSearch.title": "Пошук по кодовій базі",
"settings.experimental.codebaseSearch.description":
"Увімкнути пошук природною мовою на основі ШІ по всій кодовій базі",
+ "settings.experimental.agentManagerTool.title": "Інструмент Agent Manager",
+ "settings.experimental.agentManagerTool.description":
+ "Дозволити агентам запускати локальні сесії Agent Manager і сесії worktree через виклик інструмента",
"settings.experimental.continueOnDeny.title": "Продовжувати при відхиленні",
"settings.experimental.continueOnDeny.description": "Продовжувати цикл агента, коли дозвіл відхилено",
"settings.experimental.mcpTimeout.title": "Тайм-аут MCP (мс)",
@@ -1371,6 +1373,9 @@ export const dict = {
"settings.display.layout.description": "Режим макету для інтерфейсу чату",
"settings.display.layout.auto": "Автоматично",
"settings.display.layout.stretch": "Розтягнути",
+ "settings.display.reasoningAutoCollapse.title": "Автоматично згортати міркування",
+ "settings.display.reasoningAutoCollapse.description":
+ "Згортає блоки міркувань після того, як агент закінчить їх писати. Залиште вимкненим, щоб міркування залишалися розгорнутими, доки ви не згорнете їх вручну.",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts
index e3eb3b562f..76cdb98a44 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts
@@ -734,7 +734,7 @@ export const dict = {
"settings.indexing.title": "索引",
"settings.indexing.enable.title": "启用索引",
- "settings.indexing.enable.description": "为此工作区打开或关闭语义代码库索引。",
+ "settings.indexing.enable.description": "打开或关闭语义代码库索引。",
"settings.indexing.provider.title": "嵌入提供商",
"settings.indexing.provider.description": "选择用于生成语义搜索嵌入的提供商。",
"settings.indexing.model.title": "嵌入模型",
@@ -1176,6 +1176,8 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "代码库搜索",
"settings.experimental.codebaseSearch.description": "启用 AI 驱动的自然语言代码库搜索",
+ "settings.experimental.agentManagerTool.title": "Agent Manager 工具",
+ "settings.experimental.agentManagerTool.description": "允许智能体通过工具调用启动 Agent Manager 本地会话和工作树会话",
"settings.experimental.continueOnDeny.title": "拒绝后继续",
"settings.experimental.continueOnDeny.description": "权限被拒绝时继续智能体循环",
"settings.experimental.mcpTimeout.title": "MCP 超时(毫秒)",
@@ -1348,6 +1350,9 @@ export const dict = {
"settings.display.layout.description": "聊天界面的布局模式",
"settings.display.layout.auto": "自动",
"settings.display.layout.stretch": "拉伸",
+ "settings.display.reasoningAutoCollapse.title": "自动折叠推理",
+ "settings.display.reasoningAutoCollapse.description":
+ "在智能体写完推理后折叠推理块。保持关闭可让推理保持展开,除非你手动折叠它。",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts
index ac816ffc9e..3d246ac638 100644
--- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts
+++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts
@@ -1153,6 +1153,9 @@ export const dict = {
"Enable semantic codebase indexing and the semantic_search tool. Requires indexing configuration.",
"settings.experimental.codebaseSearch.title": "程式碼庫搜尋",
"settings.experimental.codebaseSearch.description": "啟用 AI 驅動的自然語言程式碼庫搜尋",
+ "settings.experimental.agentManagerTool.title": "Agent Manager 工具",
+ "settings.experimental.agentManagerTool.description":
+ "允許 Agent 透過工具呼叫啟動 Agent Manager 本機工作階段和工作樹工作階段",
"settings.experimental.continueOnDeny.title": "拒絕後繼續",
"settings.experimental.continueOnDeny.description": "權限被拒絕時繼續 Agent 迴圈",
"settings.experimental.mcpTimeout.title": "MCP 逾時(毫秒)",
@@ -1327,6 +1330,9 @@ export const dict = {
"settings.display.layout.description": "聊天介面的佈局模式",
"settings.display.layout.auto": "自動",
"settings.display.layout.stretch": "延伸",
+ "settings.display.reasoningAutoCollapse.title": "自動收合推理",
+ "settings.display.reasoningAutoCollapse.description":
+ "在代理寫完推理後收合推理區塊。保持關閉可讓推理保持展開,除非你手動收合它。",
"settings.display.terminalCommand.title": "Terminal Command Blocks",
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
"settings.display.terminalCommand.expanded": "Expanded",
@@ -1349,7 +1355,7 @@ export const dict = {
"settings.indexing.dimension.description": "留空以自動從模型偵測嵌入維度。",
"settings.indexing.dimension.placeholder": "自動",
"settings.indexing.dimension.title": "向量維度",
- "settings.indexing.enable.description": "為此工作區開啟或關閉語意程式碼庫索引。",
+ "settings.indexing.enable.description": "開啟或關閉語意程式碼庫索引。",
"settings.indexing.enable.title": "啟用索引",
"settings.indexing.lancedbDirectory.description": "本機 LanceDB 儲存的可選目錄。",
"settings.indexing.lancedbDirectory.placeholder": "留空使用預設值",
diff --git a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
index 211339cac7..ea309a6a66 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
@@ -16,6 +16,7 @@ import { ServerProvider } from "../context/server"
import { ProviderContext } from "../context/provider"
import { flattenModels, findModel as _findModel } from "../context/provider-utils"
import { ConfigProvider, ConfigContext } from "../context/config"
+import { DisplayProvider } from "../context/display"
import { DataProvider } from "@kilocode/kilo-ui/context/data"
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
@@ -317,42 +318,44 @@ export const StoryProviders: ParentComponent = (props) => {
-
-
- "" as any,
- t,
- }}
- >
- "en", t }}>
-
-
-
-
-
-
-
-
- {props.noPadding ? (
- props.children
- ) : (
- {props.children}
- )}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ "" as any,
+ t,
+ }}
+ >
+ "en", t }}>
+
+
+
+
+
+
+
+
+ {props.noPadding ? (
+ props.children
+ ) : (
+ {props.children}
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
index 412f817e28..69f397e5fc 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
@@ -102,6 +102,14 @@ export const AgentBehaviourEditCustomMode: Story = {
prompt: "You are a code reviewer. Focus on code quality, best practices, and potential bugs.",
model: "anthropic/claude-sonnet-4-20250514",
temperature: 0.3,
+ permission: {
+ read: "allow",
+ grep: "allow",
+ glob: "allow",
+ edit: "deny",
+ bash: "deny",
+ task: "ask",
+ },
},
}
return (
@@ -322,6 +330,51 @@ export const ModeEditExport: Story = {
},
}
+export const ModeEditPermissions: Story = {
+ name: "ModeEditView — per-agent permissions",
+ render: () => {
+ const cfg: Record = {
+ reviewer: {
+ description: "Review code without editing it",
+ prompt: "Find bugs, regressions, and missing tests.",
+ permission: {
+ "*": "deny",
+ read: "allow",
+ grep: "allow",
+ glob: "allow",
+ edit: { "*": "deny", "**/*.md": "allow" },
+ bash: "deny",
+ task: "ask",
+ skill: "deny",
+ },
+ },
+ }
+ const session = {
+ ...mockSessionValue({ id: "permissions-story", status: "idle" }),
+ agents: () => MOCK_AGENTS,
+ allAgents: () => MOCK_AGENTS,
+ removeMode: noop,
+ removeMcp: noop,
+ skills: () => [],
+ refreshSkills: noop,
+ removeSkill: noop,
+ }
+ return (
+
+
+
+
+
+
+
+ )
+ },
+}
+
export const IndexingProviderBlurRace: Story = {
name: "IndexingTab",
render: () => {
diff --git a/packages/kilo-vscode/webview-ui/src/styles/notifications.css b/packages/kilo-vscode/webview-ui/src/styles/notifications.css
index 054bd69e5a..ef77957e52 100644
--- a/packages/kilo-vscode/webview-ui/src/styles/notifications.css
+++ b/packages/kilo-vscode/webview-ui/src/styles/notifications.css
@@ -5,6 +5,9 @@
.kilo-notifications {
display: flex;
flex-direction: column;
+ align-self: center;
+ width: 100%;
+ max-width: 720px;
border-radius: 6px;
animation: kilo-notifications-slide-in 0.3s ease-out;
overflow: hidden;
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts
index 3d5851b33c..3454823258 100644
--- a/packages/kilo-vscode/webview-ui/src/types/messages/config.ts
+++ b/packages/kilo-vscode/webview-ui/src/types/messages/config.ts
@@ -41,6 +41,7 @@ export interface ExperimentalConfig {
batch_tool?: boolean
semantic_indexing?: boolean
codebase_search?: boolean
+ agent_manager_tool?: boolean
primary_tools?: string[]
continue_loop_on_deny?: boolean
mcp_timeout?: number
@@ -119,6 +120,7 @@ export interface Config {
commit_message?: CommitMessageConfig
tools?: Record
layout?: "auto" | "stretch"
+ auto_collapse_reasoning?: boolean
experimental?: ExperimentalConfig
indexing?: IndexingConfig
}
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts b/packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts
index fb7104a8f1..ece6dfc036 100644
--- a/packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts
+++ b/packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts
@@ -1,7 +1,7 @@
export type PermissionLevel = "allow" | "ask" | "deny"
/** null in a PermissionRule object is a delete sentinel — removes the key from the config */
-export type PermissionRule = PermissionLevel | Record
+export type PermissionRule = PermissionLevel | null | Record
export type PermissionConfig = Partial>
diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts
index 3b8cc5a604..7c91a2a348 100644
--- a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts
+++ b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts
@@ -378,7 +378,10 @@ export interface OpenSettingsTabRequest {
export interface UpdateConfigMessage {
type: "updateConfig"
+ /** Global config patch written to ~/.config/kilo/kilo.json. */
config: Partial
+ /** Project config patch written to the workspace's .kilo/kilo.json or existing project config. */
+ projectConfig?: Partial
}
export interface RequestNotificationSettingsMessage {
diff --git a/packages/opencode/CHANGELOG.md b/packages/opencode/CHANGELOG.md
index 25bf1737aa..1bc54aec8e 100644
--- a/packages/opencode/CHANGELOG.md
+++ b/packages/opencode/CHANGELOG.md
@@ -1,5 +1,25 @@
# @kilocode/cli
+## 7.2.33
+
+### Minor Changes
+
+- [#9737](https://github.com/Kilo-Org/kilocode/pull/9737) [`d5fb9eb`](https://github.com/Kilo-Org/kilocode/commit/d5fb9eb2265c03127e776c99020b03bb770255a1) - Support starting Agent Manager local sessions and worktree sessions from an experimental agent tool.
+
+### Patch Changes
+
+- [#9746](https://github.com/Kilo-Org/kilocode/pull/9746) [`80535d4`](https://github.com/Kilo-Org/kilocode/commit/80535d4ed6266888988a66ca28706260ee89e533) - Avoid repeated command approval prompts when multiple sessions request the same saved command permission, without widening bash permission matching.
+
+- [#9460](https://github.com/Kilo-Org/kilocode/pull/9460) [`26e4c11`](https://github.com/Kilo-Org/kilocode/commit/26e4c1148f4e7a734bb8e535e02a1a9ad75be584) - Scope the custom commit message prompt to the current project. Setting it in the VS Code settings now writes to the workspace's `kilo.json` so different repositories can have different conventions, instead of silently applying globally. Also fixes the project-level config update endpoint, which previously wrote to a file that wasn't loaded.
+
+- [#9626](https://github.com/Kilo-Org/kilocode/pull/9626) [`5dbf91c`](https://github.com/Kilo-Org/kilocode/commit/5dbf91cc167c16e04bb41e8af68108f8865a18c8) - Honor allowed read-only external-directory access to Kilo config paths without repeated permission prompts.
+
+- [#9745](https://github.com/Kilo-Org/kilocode/pull/9745) [`da3d79a`](https://github.com/Kilo-Org/kilocode/commit/da3d79a6886944b4ad311211e3f67c350958a6ca) - Use a GPT-5.5-specific coding prompt that improves autonomous task handling while keeping older Codex generations on their existing prompt.
+
+- [#9729](https://github.com/Kilo-Org/kilocode/pull/9729) [`1493d65`](https://github.com/Kilo-Org/kilocode/commit/1493d656c9afcafd41a13b45bdf734fb881536df) - Keep Remote status visible in the TUI while remote control is connecting.
+
+- [#9669](https://github.com/Kilo-Org/kilocode/pull/9669) [`0bf14eb`](https://github.com/Kilo-Org/kilocode/commit/0bf14eb2ff5ef59f9dc98342218addc670a87481) - Stop emitting `ai.*` and `gen_ai.*` OpenTelemetry spans from AI SDK calls, and remove the PostHog bridge that forwarded them. Tool/session/indexing telemetry is unchanged.
+
## 7.2.31
### Patch Changes
diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts
index 18c38ddd0d..a71914705a 100644
--- a/packages/opencode/src/config/config.ts
+++ b/packages/opencode/src/config/config.ts
@@ -173,6 +173,9 @@ export const Info = Schema.Struct({
remote_control: Schema.optional(Schema.Boolean).annotate({
description: "Enable remote control of sessions via Kilo Cloud. Equivalent to running /remote on startup.",
}),
+ auto_collapse_reasoning: Schema.optional(Schema.Boolean).annotate({
+ description: "Automatically collapse reasoning blocks after the agent finishes writing them",
+ }),
indexing: Schema.optional(IndexingRef).annotate({ description: "Codebase indexing configuration" }), // kilocode_change
terminal_command_display: Schema.optional(Schema.Literals(["expanded", "collapsed"])).annotate({
description:
@@ -294,6 +297,9 @@ export const Info = Schema.Struct({
semantic_indexing: Schema.optional(Schema.Boolean).annotate({
description: "Enable semantic codebase indexing and the semantic_search tool",
}),
+ agent_manager_tool: Schema.optional(Schema.Boolean).annotate({
+ description: "Enable the VS Code Agent Manager orchestration tool",
+ }),
// kilocode_change end
// kilocode_change start - enable telemetry by default
openTelemetry: Schema.Boolean.pipe(Schema.optional, Schema.withDecodingDefault(Effect.succeed(true))).annotate({
@@ -932,15 +938,19 @@ export const layer = Layer.effect(
// kilocode_change end
const update = Effect.fn("Config.update")(function* (config: Info) {
- const dir = yield* InstanceState.directory
- const file = path.join(dir, "config.json")
- const existing = yield* loadFile(file)
- yield* fs
- .writeFileString(
- file,
- JSON.stringify(KilocodeConfig.mergeConfig(writable(existing), writable(config)), null, 2),
- )
- .pipe(Effect.orDie) // kilocode_change
+ // kilocode_change start - delegate Kilo project config update behavior.
+ const ctx = yield* InstanceState.context
+ yield* KilocodeConfig.updateProjectConfig({
+ fs,
+ directory: ctx.directory,
+ worktree: ctx.worktree,
+ config,
+ read: readConfigFile,
+ parse: (input, file) => ConfigParse.schema(Info.zod, ConfigParse.jsonc(input, file), file),
+ patch: (input, patch) => patchJsonc(input, patch),
+ writable,
+ })
+ // kilocode_change end
yield* Effect.promise(() => Instance.dispose())
})
diff --git a/packages/opencode/src/config/permission.ts b/packages/opencode/src/config/permission.ts
index f2ced3112e..4c49b1c179 100644
--- a/packages/opencode/src/config/permission.ts
+++ b/packages/opencode/src/config/permission.ts
@@ -47,6 +47,7 @@ const InputObject = Schema.StructWithRest(
lsp: Schema.optional(Rule),
doom_loop: Schema.optional(Action),
skill: Schema.optional(Rule),
+ agent_manager: Schema.optional(Rule), // kilocode_change
}),
[Schema.Record(Schema.String, Rule)],
)
diff --git a/packages/opencode/src/config/provider.ts b/packages/opencode/src/config/provider.ts
index 440904d7ce..f49b155c37 100644
--- a/packages/opencode/src/config/provider.ts
+++ b/packages/opencode/src/config/provider.ts
@@ -1,4 +1,5 @@
import { Schema } from "effect"
+import { PROMPTS } from "@kilocode/kilo-gateway" // kilocode_change
import { zod } from "@/util/effect-zod"
import { PositiveInt, withStatics } from "@/util/schema"
@@ -6,6 +7,7 @@ export const Model = Schema.Struct({
id: Schema.optional(Schema.String),
name: Schema.optional(Schema.String),
family: Schema.optional(Schema.String),
+ prompt: Schema.optional(Schema.Literals(PROMPTS)), // kilocode_change
release_date: Schema.optional(Schema.String),
attachment: Schema.optional(Schema.Boolean),
reasoning: Schema.optional(Schema.Boolean),
diff --git a/packages/opencode/src/global/index.ts b/packages/opencode/src/global/index.ts
index c48fb57af2..fb44048c0a 100644
--- a/packages/opencode/src/global/index.ts
+++ b/packages/opencode/src/global/index.ts
@@ -8,15 +8,14 @@ import { Flock } from "@opencode-ai/shared/util/flock"
const app = "kilo" // kilocode_change
// kilocode_change start
-// Defensively strip surrounding whitespace from the resolved XDG paths.
+// Defensively strip newline characters from the resolved XDG paths.
// If `$HOME` (or any `$XDG_*_HOME` override) has a trailing newline in
// the user's shell — e.g. because a shell snippet did `export HOME=$(cmd)`
// against a command with an implicit newline — the unsanitised path
// makes `fs.mkdir` try to create `/Users/\n` and fail with EACCES,
// which breaks every `kilo` invocation at startup (including the SDK
-// regen that runs during `bun run extension`). A trim is cheap and
-// trailing whitespace is never legitimate in a filesystem path.
-const clean = (p: string | undefined) => p?.trim()
+// regen that runs during `bun run extension`).
+const clean = (p: string | undefined) => p?.replace(/[\r\n]+/g, "")
const data = path.join(clean(xdgData)!, app)
const cache = path.join(clean(xdgCache)!, app)
const config = path.join(clean(xdgConfig)!, app)
diff --git a/packages/opencode/src/kilocode/agent-manager/event.ts b/packages/opencode/src/kilocode/agent-manager/event.ts
new file mode 100644
index 0000000000..df432dd218
--- /dev/null
+++ b/packages/opencode/src/kilocode/agent-manager/event.ts
@@ -0,0 +1,26 @@
+// kilocode_change - new file
+import { BusEvent } from "@/bus/bus-event"
+import { SessionID } from "@/session/schema"
+import { Schema } from "effect"
+
+export const AgentManagerTask = Schema.Struct({
+ prompt: Schema.optional(Schema.String).annotate({ description: "Initial prompt to send to the new session" }),
+ name: Schema.optional(Schema.String).annotate({ description: "Short display name for the Agent Manager card" }),
+ branchName: Schema.optional(Schema.String).annotate({ description: "Git branch name seed for worktree mode" }),
+})
+
+export const AgentManagerMode = Schema.Literals(["worktree", "local"])
+
+export const AgentManagerStart = Schema.Struct({
+ requestID: Schema.String,
+ sessionID: SessionID,
+ mode: AgentManagerMode,
+ versions: Schema.optional(Schema.Boolean),
+ tasks: Schema.Array(AgentManagerTask).check(Schema.isMinLength(1), Schema.isMaxLength(20)),
+})
+
+export type AgentManagerStart = Schema.Schema.Type
+
+export const AgentManagerEvent = {
+ Start: BusEvent.define("kilocode.agent_manager.start", AgentManagerStart),
+}
diff --git a/packages/opencode/src/kilocode/agent/index.ts b/packages/opencode/src/kilocode/agent/index.ts
index 8e33dc2eed..f7a3d4da23 100644
--- a/packages/opencode/src/kilocode/agent/index.ts
+++ b/packages/opencode/src/kilocode/agent/index.ts
@@ -6,7 +6,6 @@ import * as Truncate from "../../tool/truncate"
import { Config } from "../../config"
import { Instance } from "../../project/instance"
import { makeRuntime } from "@/effect/run-service"
-import { Telemetry } from "@kilocode/kilo-telemetry"
import z from "zod"
import path from "path"
import { Global } from "@/global"
@@ -246,16 +245,9 @@ export function processConfigItem(item: {
}
// Returns experimental_telemetry config for generate calls.
-export function telemetryOptions(cfg: Config.Info) {
- return {
- isEnabled: cfg.experimental?.openTelemetry !== false,
- recordInputs: false,
- recordOutputs: false,
- tracer: Telemetry.getTracer() ?? undefined,
- metadata: {
- userId: cfg.username ?? "unknown",
- },
- }
+// AI SDK span recording (ai.* / gen_ai.*) is disabled.
+export function telemetryOptions(_cfg: Config.Info) {
+ return { isEnabled: false as const }
}
// Patch the base agents map in-place with all kilo-specific changes:
diff --git a/packages/opencode/src/kilocode/config/config.ts b/packages/opencode/src/kilocode/config/config.ts
index 6a8c141785..583e5fc485 100644
--- a/packages/opencode/src/kilocode/config/config.ts
+++ b/packages/opencode/src/kilocode/config/config.ts
@@ -3,12 +3,13 @@ import path from "path"
import { pathToFileURL } from "url"
import { existsSync } from "fs"
import z from "zod"
-import { Schema } from "effect"
+import { Effect, Schema } from "effect"
import { applyEdits, modify, parse as parseJsonc } from "jsonc-parser"
import { mergeDeep } from "remeda"
import { Log } from "../../util"
import { Global } from "../../global"
import { NamedError } from "@opencode-ai/shared/util/error"
+import type { AppFileSystem } from "@opencode-ai/shared/filesystem"
import { Bus } from "@/bus"
import { isRecord } from "@/util/record"
import { ConfigError } from "../../config/error"
@@ -49,8 +50,15 @@ export namespace KilocodeConfig {
/** Directory suffixes that czcode recognizes in addition to .opencode. */
export const KILO_DIR_SUFFIXES = [".czcode", ".kilo", ".kilocode"] as const
+ // czcode_change start - include .czcode in config dir suffixes
+ /** All config directory suffixes Kilo can update, including upstream .opencode. */
+ export const ALL_CONFIG_DIR_SUFFIXES = [".czcode", ".kilo", ".kilocode", ".opencode"] as const
+ // czcode_change end
+
+ // czcode_change start - include .czcode agent path patterns
/** Path patterns for resolving czcode agent names from file paths. */
export const AGENT_PATTERNS = ["/.czcode/agent/", "/.czcode/agents/", "/.kilo/agent/", "/.kilo/agents/", "/.kilocode/agent/", "/.kilocode/agents/"] as const
+ // czcode_change end
/** Path patterns for resolving czcode command names from file paths. */
export const COMMAND_PATTERNS = [
@@ -63,6 +71,53 @@ export namespace KilocodeConfig {
] as const
// czcode_change end
+ /**
+ * Choose the project config file that Config.update should patch.
+ *
+ * This mirrors the Kilo project-config load chain: prefer existing config files
+ * in ancestor config directories, then existing root config files, and create
+ * `.kilo/kilo.json` when no project config exists yet.
+ */
+ export const projectConfigUpdateTarget = Effect.fn("KilocodeConfig.projectConfigUpdateTarget")(function* (input: {
+ fs: AppFileSystem.Interface
+ directory: string
+ worktree?: string
+ }) {
+ const dirs = yield* input.fs
+ .up({ targets: [...ALL_CONFIG_DIR_SUFFIXES], start: input.directory, stop: input.worktree })
+ .pipe(Effect.orDie)
+ const roots = yield* input.fs
+ .up({ targets: [...ALL_CONFIG_FILES], start: input.directory, stop: input.worktree })
+ .pipe(Effect.orDie)
+ const files = [...dirs.flatMap((dir) => ALL_CONFIG_FILES.map((file) => path.join(dir, file))), ...roots]
+ return files.find((file) => existsSync(file)) ?? path.join(input.directory, ".kilo", "kilo.json")
+ })
+
+ export const updateProjectConfig = Effect.fn("KilocodeConfig.updateProjectConfig")(function* (input: {
+ fs: AppFileSystem.Interface
+ directory: string
+ worktree?: string
+ config: Config.Info
+ read: (file: string) => Effect.Effect
+ parse: (input: string, file: string) => Config.Info
+ patch: (input: string, config: Config.Info) => string
+ writable: (config: Config.Info) => Config.Info
+ }) {
+ const file = yield* projectConfigUpdateTarget(input)
+ const before = (yield* input.read(file)) ?? "{}"
+ const patch = input.writable(input.config)
+
+ if (file.endsWith(".jsonc")) {
+ const updated = input.patch(before, patch)
+ yield* input.fs.writeWithDirs(file, updated).pipe(Effect.orDie)
+ return
+ }
+
+ const existing = input.parse(before, file)
+ const merged = mergeConfig(input.writable(existing), patch)
+ yield* input.fs.writeWithDirs(file, JSON.stringify(merged, null, 2)).pipe(Effect.orDie)
+ })
+
// ── Warning helpers ──────────────────────────────────────────────────
/** Convert known config-loading error types into a Warning. Returns undefined for unknown errors. */
diff --git a/packages/opencode/src/kilocode/tool/agent-manager.ts b/packages/opencode/src/kilocode/tool/agent-manager.ts
new file mode 100644
index 0000000000..270429a1f3
--- /dev/null
+++ b/packages/opencode/src/kilocode/tool/agent-manager.ts
@@ -0,0 +1,75 @@
+// kilocode_change - new file
+import { Bus } from "@/bus"
+import { AgentManagerEvent } from "@/kilocode/agent-manager/event"
+import { Tool } from "@/tool"
+import { Effect, Schema } from "effect"
+import DESCRIPTION from "./agent-manager.txt"
+
+const Task = Schema.Struct({
+ prompt: Schema.optional(Schema.String).annotate({ description: "Initial prompt to send to the new session" }),
+ name: Schema.optional(Schema.String).annotate({ description: "Short display name for the Agent Manager card" }),
+ branchName: Schema.optional(Schema.String).annotate({ description: "Git branch name seed for worktree mode" }),
+}).check(
+ Schema.makeFilter((task: { prompt?: string; name?: string; branchName?: string }) =>
+ task.prompt?.trim() || task.name?.trim() || task.branchName?.trim()
+ ? undefined
+ : "Each task must include prompt, name, or branchName",
+ ),
+)
+
+export const Params = Schema.Struct({
+ mode: Schema.Literals(["worktree", "local"]).annotate({
+ description: "Use worktree for isolated git worktrees, or local for same-directory Agent Manager sessions",
+ }),
+ versions: Schema.optional(Schema.Boolean).annotate({
+ description:
+ "Set true only when tasks are alternative versions of the same work to compare. Omit or false for independent sessions.",
+ }),
+ tasks: Schema.Array(Task)
+ .check(Schema.isMinLength(1), Schema.isMaxLength(20))
+ .annotate({ description: "Agent Manager sessions to start" }),
+})
+
+export const AgentManagerTool = Tool.define<
+ typeof Params,
+ { requestID: string; count: number },
+ Bus.Service,
+ "agent_manager"
+>(
+ "agent_manager",
+ Effect.gen(function* () {
+ const bus = yield* Bus.Service
+ return {
+ description: DESCRIPTION,
+ parameters: Params,
+ execute: (params, ctx) =>
+ Effect.gen(function* () {
+ yield* ctx.ask({
+ permission: "agent_manager",
+ patterns: [params.mode],
+ always: [params.mode],
+ metadata: { mode: params.mode, count: params.tasks.length },
+ })
+
+ const requestID = `am-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
+ yield* bus.publish(AgentManagerEvent.Start, {
+ requestID,
+ sessionID: ctx.sessionID,
+ mode: params.mode,
+ versions: params.versions,
+ tasks: params.tasks,
+ })
+
+ return {
+ title: `Requested ${params.tasks.length} Agent Manager ${params.mode === "worktree" ? "worktree" : "local"} session${params.tasks.length === 1 ? "" : "s"}`,
+ output: [
+ `Requested ${params.tasks.length} Agent Manager ${params.mode === "worktree" ? "worktree" : "local"} session${params.tasks.length === 1 ? "" : "s"}.`,
+ `request_id: ${requestID}`,
+ "The VS Code extension will create the sessions asynchronously and show progress in Agent Manager.",
+ ].join("\n"),
+ metadata: { requestID, count: params.tasks.length },
+ }
+ }),
+ }
+ }),
+)
diff --git a/packages/opencode/src/kilocode/tool/agent-manager.txt b/packages/opencode/src/kilocode/tool/agent-manager.txt
new file mode 100644
index 0000000000..d1a85145af
--- /dev/null
+++ b/packages/opencode/src/kilocode/tool/agent-manager.txt
@@ -0,0 +1,13 @@
+Start Agent Manager sessions in the VS Code extension.
+
+Use this tool when the user explicitly asks you to fan out work into Agent Manager, create Agent Manager worktrees, or start multiple Agent Manager sessions for independent tasks.
+
+Modes:
+- `worktree`: creates a new Agent Manager git worktree for each task, like the New Worktree dialog.
+- `local`: creates Agent Manager sessions in the current workspace directory without git worktree isolation.
+
+Each task may provide a prompt, a short display name, and a branch name. Keep display names short because Agent Manager cards are narrow. Branch names are sanitized before worktree creation. The agent, model, reasoning, and base branch settings always use the normal defaults.
+
+By default, multiple tasks are started as independent Agent Manager sessions. Set `versions` to true only when all tasks are alternate versions of the same work that should be compared together. Versioned worktrees are grouped in Agent Manager and branch names may receive version suffixes.
+
+Do not use this for ordinary subagent research. Use the `task` tool for internal subagents, and use this only when the user wants visible Agent Manager sessions in the extension.
diff --git a/packages/opencode/src/kilocode/tool/registry.ts b/packages/opencode/src/kilocode/tool/registry.ts
index a57dc60a6b..e1a15b0f89 100644
--- a/packages/opencode/src/kilocode/tool/registry.ts
+++ b/packages/opencode/src/kilocode/tool/registry.ts
@@ -1,6 +1,7 @@
// kilocode_change - new file
import { CodebaseSearchTool } from "../../tool/warpgrep"
import { RecallTool } from "../../tool/recall"
+import { AgentManagerTool } from "./agent-manager"
import * as Tool from "../../tool/tool"
import { Flag } from "@/flag/flag"
import { Effect } from "effect"
@@ -18,17 +19,19 @@ export namespace KiloToolRegistry {
return Effect.gen(function* () {
const codebase = yield* CodebaseSearchTool
const recall = yield* RecallTool
- return { codebase, recall }
+ const manager = yield* AgentManagerTool
+ return { codebase, recall, manager }
})
}
/** Finalize Kilo-specific tools into Tool.Defs. Call this inside the InstanceState state Effect —
* it has no Service deps beyond what Tool.init itself needs. */
- export function build(tools: { codebase: Tool.Info; recall: Tool.Info }, deps: Deps) {
+ export function build(tools: { codebase: Tool.Info; recall: Tool.Info; manager: Tool.Info }, deps: Deps) {
return Effect.gen(function* () {
const base = yield* Effect.all({
codebase: Tool.init(tools.codebase),
recall: Tool.init(tools.recall),
+ manager: Tool.init(tools.manager),
})
const semantic = yield* semanticTool(deps)
return { ...base, semantic }
@@ -37,7 +40,9 @@ export namespace KiloToolRegistry {
function semanticTool(deps: Deps) {
return Effect.gen(function* () {
- const ready = yield* Effect.tryPromise(() => import("@/kilocode/indexing").then((mod) => mod.KiloIndexing.ready())).pipe(
+ const ready = yield* Effect.tryPromise(() =>
+ import("@/kilocode/indexing").then((mod) => mod.KiloIndexing.ready()),
+ ).pipe(
Effect.catch((err) =>
Effect.sync(() => {
log.warn("semantic search unavailable", { err })
@@ -83,13 +88,15 @@ export namespace KiloToolRegistry {
/** Kilo-specific tools to append to the builtin list */
export function extra(
- tools: { codebase: Tool.Def; semantic?: Tool.Def; recall: Tool.Def },
- cfg: { experimental?: { codebase_search?: boolean } },
+ tools: { codebase: Tool.Def; semantic?: Tool.Def; recall: Tool.Def; manager: Tool.Def },
+ cfg: { experimental?: { codebase_search?: boolean; agent_manager_tool?: boolean } },
): Tool.Def[] {
return [
...(cfg.experimental?.codebase_search === true ? [tools.codebase] : []),
...(tools.semantic ? [tools.semantic] : []),
tools.recall,
+ // The extension is the only client that can consume the Agent Manager start event.
+ ...(Flag.KILO_CLIENT === "vscode" && cfg.experimental?.agent_manager_tool === true ? [tools.manager] : []),
]
}
diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts
index 6e1516b469..991b0a52f4 100644
--- a/packages/opencode/src/permission/index.ts
+++ b/packages/opencode/src/permission/index.ts
@@ -159,6 +159,7 @@ interface PendingEntry {
info: Request
ruleset: Ruleset // kilocode_change
hardRuleset?: Ruleset // kilocode_change
+ saved?: boolean // kilocode_change
deferred: Deferred.Deferred
}
@@ -309,40 +310,31 @@ export const layer = Layer.effect(
// kilocode_change end
for (const pattern of existing.info.always) {
- approved.push({
- permission: existing.info.permission,
- pattern,
- action: "allow",
- })
- }
-
- for (const [id, item] of pending.entries()) {
- if (item.info.sessionID !== existing.info.sessionID) continue
- if (ConfigProtection.isRequest(item.info)) continue // kilocode_change
- // kilocode_change start
- const ok = item.info.patterns.every((pattern) => {
- if (veto(item.info.permission, pattern, item.hardRuleset)) return false
- return evaluate(item.info.permission, pattern, item.ruleset, approved).action === "allow"
- })
+ // kilocode_change start — saveAlwaysRules may have already persisted selected always-rules
+ if (!existing.saved) {
+ approved.push({
+ permission: existing.info.permission,
+ pattern,
+ action: "allow",
+ })
+ }
// kilocode_change end
- if (!ok) continue
- pending.delete(id)
- yield* bus.publish(Event.Replied, {
- sessionID: item.info.sessionID,
- requestID: item.info.id,
- reply: "always",
- })
- yield* Deferred.succeed(item.deferred, undefined)
}
+ // kilocode_change start — drain covered permissions across sibling Agent Manager sessions
+ yield* drainCovered(pending as unknown as Map, approved, DeniedError)
+ // kilocode_change end
+
// kilocode_change start — persist always-rules to global config
- const alwaysRules: Ruleset = existing.info.always.map((pattern) => ({
- permission: existing.info.permission,
- pattern,
- action: "allow" as const,
- }))
- if (alwaysRules.length > 0) {
- yield* Effect.promise(() => Config.updateGlobal({ permission: toConfig(alwaysRules) }, { dispose: false }))
+ if (!existing.saved) {
+ const alwaysRules: Ruleset = existing.info.always.map((pattern) => ({
+ permission: existing.info.permission,
+ pattern,
+ action: "allow" as const,
+ }))
+ if (alwaysRules.length > 0) {
+ yield* Effect.promise(() => Config.updateGlobal({ permission: toConfig(alwaysRules) }, { dispose: false }))
+ }
}
// kilocode_change end
return true // kilocode_change
@@ -377,6 +369,7 @@ export const layer = Layer.effect(
if (deniedSet.has(pattern)) newRules.push({ permission, pattern, action: "deny" })
}
s.approved.push(...newRules)
+ existing.saved = true // kilocode_change
if (newRules.length > 0) {
yield* Effect.promise(() => Config.updateGlobal({ permission: toConfig(newRules) }, { dispose: false }))
diff --git a/packages/opencode/src/session/llm.ts b/packages/opencode/src/session/llm.ts
index 4e97daefaf..82d5503c0a 100644
--- a/packages/opencode/src/session/llm.ts
+++ b/packages/opencode/src/session/llm.ts
@@ -20,7 +20,6 @@ import { Wildcard } from "@/util"
import { SessionID } from "@/session/schema"
import { Auth } from "@/auth"
// kilocode_change start
-import { Telemetry } from "@kilocode/kilo-telemetry"
import { DEFAULT_HEADERS } from "@/kilocode/const"
import { getKiloProjectId } from "@/kilocode/project-id"
import { HEADER_PROJECTID, HEADER_MACHINEID, HEADER_TASKID } from "@kilocode/kilo-gateway"
@@ -430,14 +429,8 @@ const live: Layer.Layer<
},
],
}),
- // kilocode_change start - enable telemetry by default with custom PostHog tracer
- experimental_telemetry: {
- isEnabled: cfg.experimental?.openTelemetry !== false,
- recordInputs: false,
- recordOutputs: false,
- tracer: Telemetry.getTracer() ?? undefined,
- },
- // kilocode_change end
+ // kilocode_change - disable AI SDK span recording (ai.* / gen_ai.*)
+ experimental_telemetry: { isEnabled: false },
})
})
diff --git a/packages/opencode/src/session/prompt/kilocode-gpt-5.5.txt b/packages/opencode/src/session/prompt/kilocode-gpt-5.5.txt
new file mode 100644
index 0000000000..8f5b8a1c53
--- /dev/null
+++ b/packages/opencode/src/session/prompt/kilocode-gpt-5.5.txt
@@ -0,0 +1,95 @@
+You are Kilo, an AI coding agent operating in the user's current project checkout.
+
+You help users with software engineering tasks by reading the repository, making targeted changes, running relevant checks, and reporting results clearly. Treat the workspace as shared with the user and other agents.
+
+## Engineering judgment
+- Build context from the repository before deciding on an approach; follow existing patterns and keep changes minimal.
+- Prefer the smallest correct fix over broad rewrites, new abstractions, or process ceremony.
+- If two approaches are viable, choose the one that is easier to review and maintain.
+- Ask only when the request is blocked by missing information that cannot be inferred safely from the project.
+
+## Autonomy and persistence
+- Treat short implementation, bugfix, refactor, and maintenance requests as sufficient direction to act.
+- Continue through implementation, verification, and final handoff when feasible instead of stopping at a plan.
+- Do not ask permission to proceed or to run normal local checks; run the most relevant safe check and mention the result.
+- If blocked, complete all non-blocked work first, then ask one targeted question with the recommended default.
+
+## Verification
+- Before saying code changes are ready, run the smallest relevant check that can catch failures in the touched area.
+- Fix failures you introduced before finalizing when practical.
+- If no meaningful automated check applies, state that plainly and explain why.
+- Do not claim a check passed unless you ran it in this workspace.
+
+## Editing constraints
+- Default to ASCII when editing or creating files. Only introduce non-ASCII or other Unicode characters when there is a clear justification and the file already uses them.
+- Only add comments if they are necessary to make a non-obvious block easier to understand.
+- Try to use apply_patch for single file edits, but it is fine to explore other options to make the edit if it does not work well. Do not use apply_patch for changes that are auto-generated (i.e. generating package.json or running a lint or format command like gofmt) or when scripting is more efficient (such as search and replacing a string across a codebase).
+
+## Tool usage
+- If the Task tool is available, use it proactively to delegate focused subtasks to a subagent instance. You can spawn multiple subagents in parallel.
+- Prefer specialized tools over shell for file operations:
+ - Use Read to view files, Edit to modify files, and Write only when needed.
+ - Use Glob to find files by name and Grep to search file contents.
+- Use Bash for terminal operations (git, bun, builds, tests, running scripts).
+- Run tool calls in parallel when neither call needs the other's output; otherwise run sequentially.
+
+## Git and workspace hygiene
+- You may be in a dirty git worktree.
+ * NEVER revert existing changes you did not make unless explicitly requested, since they may be user, agent, or generated changes.
+ * Ignore unrelated changes while you work, including unrelated changes in files you are not touching.
+ * If relevant files already contain changes you did not make, read them carefully and work with them rather than overwriting or reverting them.
+ * Ask only if those changes make the requested task impossible to complete safely.
+- Do not amend commits unless explicitly requested.
+- **NEVER** use destructive commands like `git reset --hard` or `git checkout --` unless specifically requested or approved by the user.
+
+## Frontend tasks
+When doing frontend design tasks, avoid collapsing into bland, generic layouts.
+Aim for interfaces that feel intentional and deliberate.
+- Typography: Use expressive, purposeful fonts and avoid default stacks (Inter, Roboto, Arial, system).
+- Color & Look: Choose a clear visual direction; define CSS variables; avoid purple-on-white defaults. No purple bias or dark mode bias.
+- Motion: Use a few meaningful animations (page-load, staggered reveals) instead of generic micro-motions.
+- Background: Don't rely on flat, single-color backgrounds; use gradients, shapes, or subtle patterns to build atmosphere.
+- Overall: Avoid boilerplate layouts and interchangeable UI patterns. Vary themes, type families, and visual languages across outputs.
+- Ensure the page loads properly on both desktop and mobile.
+
+Exception: If working within an existing website or design system, preserve the established patterns, structure, and visual language.
+
+## Presenting your work and final message
+
+You are producing plain text that will later be styled by the CLI. Follow these rules exactly. Formatting should make results easy to scan, but not feel mechanical. Use judgment to decide how much structure adds value.
+
+- Default: be very concise; friendly coding teammate tone.
+- Questions: only ask when you are truly blocked after checking relevant context AND you cannot safely pick a reasonable default. This usually means one of:
+ * The request is ambiguous in a way that materially changes the result and you cannot disambiguate by reading the repo.
+ * The action is destructive/irreversible, touches production, or changes billing/security posture.
+ * You need a secret/credential/value that cannot be inferred (API key, account id, etc.).
+- For substantial work, summarize clearly; follow final-answer formatting.
+- Skip heavy formatting for simple confirmations.
+- Don't dump large files you've written; reference paths only.
+- No "save/copy this file" - User is on the same machine.
+- Offer logical next steps (tests, commits, build) briefly; add verify steps if you couldn't do something.
+- For code changes:
+ * Lead with a quick explanation of the change, and then give more details on the context covering where and why a change was made. Do not start this explanation with "summary", just jump right in.
+ * If there are natural next steps the user may want to take, suggest them at the end of your response. Do not make suggestions if there are no natural next steps.
+ * When suggesting multiple options, use numeric lists for the suggestions so the user can quickly respond with a single number.
+- The user does not command execution outputs. When asked to show the output of a command (e.g. `git show`), relay the important details in your answer or summarize the key lines so the user understands the result.
+
+## Final answer structure and style guidelines
+
+- Plain text; CLI handles styling. Use structure only when it helps scannability.
+- Headers: optional; short Title Case (1-3 words) wrapped in **...**; no blank line before the first bullet; add only if they truly help.
+- Bullets: use - ; merge related points; keep to one line when possible; 4-6 per list ordered by importance; keep phrasing consistent.
+- Monospace: backticks for commands/paths/env vars/code ids and inline examples; use for literal keyword bullets; never combine with **.
+- Code samples or multi-line snippets should be wrapped in fenced code blocks; include an info string as often as possible.
+- Structure: group related bullets; order sections general -> specific -> supporting; for subsections, start with a bolded keyword bullet, then items; match complexity to the task.
+- Tone: collaborative, concise, factual; present tense, active voice; self-contained; no "above/below"; parallel wording.
+- Don'ts: no nested bullets/hierarchies; no ANSI codes; don't cram unrelated keywords; keep keyword lists short - wrap/reformat if long; avoid naming formatting styles in answers.
+- Adaptation: code explanations -> precise, structured with code refs; simple tasks -> lead with outcome; big changes -> logical walkthrough + rationale + next actions; casual one-offs -> plain sentences, no headers/bullets.
+- File References: When referencing files in your response follow the below rules:
+ * Use inline code to make file paths clickable.
+ * Each reference should have a stand alone path. Even if it's the same file.
+ * Accepted: absolute, workspace-relative, a/ or b/ diff prefixes, or bare filename/suffix.
+ * Optionally include line/column (1-based): :line[:column] or #Lline[Ccolumn] (column defaults to 1).
+ * Do not use URIs like file://, vscode://, or https://.
+ * Do not provide range of lines
+ * Examples: src/app.ts, src/app.ts:42, b/server/index.js#L10, C:\repo\project\main.rs:12:5
diff --git a/packages/opencode/src/session/system.ts b/packages/opencode/src/session/system.ts
index fefbd5bb3b..8b610e4886 100644
--- a/packages/opencode/src/session/system.ts
+++ b/packages/opencode/src/session/system.ts
@@ -8,6 +8,7 @@ import PROMPT_DEFAULT from "./prompt/default.txt"
import PROMPT_BEAST from "./prompt/beast.txt"
import PROMPT_GEMINI from "./prompt/gemini.txt"
import PROMPT_GPT from "./prompt/gpt.txt"
+import PROMPT_GPT55 from "./prompt/kilocode-gpt-5.5.txt" // kilocode_change
import PROMPT_KIMI from "./prompt/kimi.txt"
import PROMPT_LING from "./prompt/ling.txt" // kilocode_change
@@ -47,6 +48,8 @@ export function provider(model: Provider.Model) {
return [PROMPT_CODEX]
case "gemini":
return [PROMPT_GEMINI]
+ case "gpt55":
+ return [PROMPT_GPT55]
case "ling":
return [PROMPT_LING]
case "trinity":
diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts
index ba8345aefe..5101ed574a 100644
--- a/packages/opencode/test/config/config.test.ts
+++ b/packages/opencode/test/config/config.test.ts
@@ -853,20 +853,6 @@ Hello from new command`,
})
})
-test("updates config and writes to file", async () => {
- await using tmp = await tmpdir()
- await Instance.provide({
- directory: tmp.path,
- fn: async () => {
- const newConfig = { model: "updated/model" }
- await save(newConfig as any)
-
- const writtenConfig = await Filesystem.readJson<{ model: string }>(path.join(tmp.path, "config.json"))
- expect(writtenConfig.model).toBe("updated/model")
- },
- })
-})
-
test("gets config directories", async () => {
await using tmp = await tmpdir()
await Instance.provide({
diff --git a/packages/opencode/test/kilocode/agent-manager-tool.test.ts b/packages/opencode/test/kilocode/agent-manager-tool.test.ts
new file mode 100644
index 0000000000..21a3cda013
--- /dev/null
+++ b/packages/opencode/test/kilocode/agent-manager-tool.test.ts
@@ -0,0 +1,71 @@
+import { describe, expect, test } from "bun:test"
+import { Effect, Layer, ManagedRuntime } from "effect"
+import { MessageID, SessionID } from "../../src/session/schema"
+import { provideTmpdirInstance } from "../fixture/fixture"
+import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
+import { AgentManagerTool } from "../../src/kilocode/tool/agent-manager"
+import { Bus } from "../../src/bus"
+import { Tool } from "../../src/tool"
+import { Truncate } from "../../src/tool"
+import { Agent } from "../../src/agent/agent"
+
+const runtime = ManagedRuntime.make(
+ Layer.mergeAll(Truncate.defaultLayer, Agent.defaultLayer, Bus.defaultLayer, CrossSpawnSpawner.defaultLayer),
+)
+
+async function init() {
+ return runtime.runPromise(
+ Effect.gen(function* () {
+ const info = yield* AgentManagerTool
+ return yield* Tool.init(info)
+ }),
+ )
+}
+
+const ctx = {
+ sessionID: SessionID.make("ses_test"),
+ messageID: MessageID.make("msg_test"),
+ callID: "call_agent_manager",
+ agent: "build",
+ abort: AbortSignal.any([]),
+ messages: [],
+ metadata: () => Effect.void,
+ ask: () => Effect.void,
+}
+
+describe("agent_manager tool", () => {
+ test("asks for agent_manager permission", async () => {
+ const tool = await init()
+ const calls: unknown[] = []
+
+ await runtime.runPromise(
+ provideTmpdirInstance(() =>
+ tool.execute(
+ { mode: "local", tasks: [{ prompt: "Fix issue" }] },
+ { ...ctx, ask: (input: unknown) => Effect.sync(() => calls.push(input)) },
+ ),
+ ).pipe(Effect.scoped),
+ )
+
+ expect(calls).toEqual([
+ {
+ permission: "agent_manager",
+ patterns: ["local"],
+ always: ["local"],
+ metadata: { mode: "local", count: 1 },
+ },
+ ])
+ })
+
+ test("rejects empty tasks", async () => {
+ const tool = await init()
+
+ await expect(
+ runtime.runPromise(
+ provideTmpdirInstance(() =>
+ tool.execute({ mode: "local", tasks: [{}] }, { ...ctx, ask: () => Effect.void }),
+ ).pipe(Effect.scoped),
+ ),
+ ).rejects.toThrow("Each task must include prompt, name, or branchName")
+ })
+})
diff --git a/packages/opencode/test/kilocode/permission/next.always-rules.test.ts b/packages/opencode/test/kilocode/permission/next.always-rules.test.ts
index d1fafbbb36..92386c76ef 100644
--- a/packages/opencode/test/kilocode/permission/next.always-rules.test.ts
+++ b/packages/opencode/test/kilocode/permission/next.always-rules.test.ts
@@ -625,6 +625,48 @@ describe("saveAlwaysRules", () => {
),
)
+ it.live("saveAlwaysRules then reply(always) does not duplicate saved rules", () =>
+ withDir({ git: true }, () =>
+ Effect.gen(function* () {
+ const fiber = yield* ask({
+ id: PermissionID.make("permission_saved_always"),
+ sessionID: SessionID.make("session_saved_always"),
+ permission: "bash",
+ patterns: ["kilo-permission-8353 test"],
+ metadata: { rules: ["kilo-permission-8353 *", "kilo-permission-8353 test"] },
+ always: ["kilo-permission-8353 *", "kilo-permission-8353 test"],
+ ruleset: [],
+ }).pipe(Effect.forkScoped)
+
+ yield* waitForPending(1)
+ yield* saveAlwaysRules({
+ requestID: PermissionID.make("permission_saved_always"),
+ approvedAlways: ["kilo-permission-8353 test"],
+ })
+ yield* reply({ requestID: PermissionID.make("permission_saved_always"), reply: "always" })
+ yield* Fiber.join(fiber)
+
+ const cfg = yield* Effect.promise(() => Config.get())
+ expect(cfg.permission?.bash).toMatchObject({ "kilo-permission-8353 test": "allow" })
+ expect(cfg.permission?.bash).not.toMatchObject({ "kilo-permission-8353 *": "allow" })
+
+ const broad = yield* ask({
+ id: PermissionID.make("permission_saved_always_broad"),
+ sessionID: SessionID.make("session_saved_always"),
+ permission: "bash",
+ patterns: ["kilo-permission-8353 install"],
+ metadata: {},
+ always: [],
+ ruleset: [],
+ }).pipe(Effect.forkScoped)
+
+ yield* waitForPending(1)
+ yield* reply({ requestID: PermissionID.make("permission_saved_always_broad"), reply: "reject" })
+ expectFailure(yield* Fiber.await(broad), Permission.RejectedError)
+ }),
+ ),
+ )
+
it.live("auto-rejects pending permission from sibling session when denied", () =>
withDir({ git: true }, () =>
Effect.gen(function* () {
diff --git a/packages/opencode/test/kilocode/project-config-update.test.ts b/packages/opencode/test/kilocode/project-config-update.test.ts
new file mode 100644
index 0000000000..c6b58b7555
--- /dev/null
+++ b/packages/opencode/test/kilocode/project-config-update.test.ts
@@ -0,0 +1,110 @@
+// kilocode_change - new file
+
+import { expect, test } from "bun:test"
+import fs from "fs/promises"
+import path from "path"
+import { Effect, Layer, Option } from "effect"
+import { NodeFileSystem, NodePath } from "@effect/platform-node"
+import { AppFileSystem } from "@opencode-ai/shared/filesystem"
+import { EffectFlock } from "@opencode-ai/shared/util/effect-flock"
+import { Config } from "../../src/config"
+import { Auth } from "../../src/auth"
+import { Account } from "../../src/account/account"
+import { Env } from "../../src/env"
+import { Npm } from "../../src/npm"
+import { Instance } from "../../src/project/instance"
+import { Filesystem } from "../../src/util"
+import * as CrossSpawnSpawner from "../../src/effect/cross-spawn-spawner"
+import { tmpdir } from "../fixture/fixture"
+
+const infra = CrossSpawnSpawner.defaultLayer.pipe(
+ Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)),
+)
+
+const emptyAccount = Layer.mock(Account.Service)({
+ active: () => Effect.succeed(Option.none()),
+ activeOrg: () => Effect.succeed(Option.none()),
+})
+
+const emptyAuth = Layer.mock(Auth.Service)({
+ all: () => Effect.succeed({}),
+})
+
+const noopNpm = Layer.mock(Npm.Service)({
+ install: () => Effect.void,
+ add: () => Effect.die("not implemented"),
+ outdated: () => Effect.succeed(false),
+ which: () => Effect.succeed(Option.none()),
+})
+
+const layer = Config.layer.pipe(
+ Layer.provide(EffectFlock.defaultLayer),
+ Layer.provide(AppFileSystem.defaultLayer),
+ Layer.provide(Env.defaultLayer),
+ Layer.provide(emptyAuth),
+ Layer.provide(emptyAccount),
+ Layer.provideMerge(infra),
+ Layer.provide(noopNpm),
+)
+
+const load = () => Effect.runPromise(Config.Service.use((svc) => svc.get()).pipe(Effect.scoped, Effect.provide(layer)))
+const save = (config: Config.Info) =>
+ Effect.runPromise(Config.Service.use((svc) => svc.update(config)).pipe(Effect.scoped, Effect.provide(layer)))
+
+async function writeConfig(dir: string, config: unknown) {
+ await Filesystem.write(path.join(dir, "kilo.json"), JSON.stringify(config, null, 2))
+}
+
+test("project config update creates .kilo/kilo.json and reloads it", async () => {
+ await using tmp = await tmpdir()
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await save({ model: "updated/model" } as any)
+
+ const written = await Filesystem.readJson<{ model: string }>(path.join(tmp.path, ".kilo", "kilo.json"))
+ expect(written.model).toBe("updated/model")
+
+ const loaded = await load()
+ expect(loaded.model).toBe("updated/model")
+ },
+ })
+})
+
+test("project config update prefers existing root kilo.json", async () => {
+ await using tmp = await tmpdir()
+ await writeConfig(tmp.path, { username: "alice" })
+
+ await Instance.provide({
+ directory: tmp.path,
+ fn: async () => {
+ await save({ model: "updated/model" } as any)
+
+ const merged = await Filesystem.readJson<{ model: string; username: string }>(path.join(tmp.path, "kilo.json"))
+ expect(merged.model).toBe("updated/model")
+ expect(merged.username).toBe("alice")
+ },
+ })
+})
+
+test("project config update patches ancestor .kilo/kilo.json from nested directory", async () => {
+ await using tmp = await tmpdir()
+ const child = path.join(tmp.path, "nested", "workspace")
+ await fs.mkdir(child, { recursive: true })
+ await fs.mkdir(path.join(tmp.path, ".kilo"), { recursive: true })
+ await writeConfig(path.join(tmp.path, ".kilo"), { username: "alice" })
+
+ await Instance.provide({
+ directory: child,
+ fn: async () => {
+ await save({ model: "updated/model" } as any)
+
+ const merged = await Filesystem.readJson<{ model: string; username: string }>(
+ path.join(tmp.path, ".kilo", "kilo.json"),
+ )
+ expect(merged.model).toBe("updated/model")
+ expect(merged.username).toBe("alice")
+ await expect(fs.access(path.join(child, ".kilo", "kilo.json"))).rejects.toThrow()
+ },
+ })
+})
diff --git a/packages/opencode/test/kilocode/system-prompt.test.ts b/packages/opencode/test/kilocode/system-prompt.test.ts
index 69b4b2ac1a..d54b94dc03 100644
--- a/packages/opencode/test/kilocode/system-prompt.test.ts
+++ b/packages/opencode/test/kilocode/system-prompt.test.ts
@@ -7,6 +7,8 @@ import PROMPT_DEFAULT from "../../src/session/prompt/default.txt"
import PROMPT_BEAST from "../../src/session/prompt/beast.txt"
import PROMPT_CODEX from "../../src/session/prompt/codex.txt"
import PROMPT_GEMINI from "../../src/session/prompt/gemini.txt"
+import PROMPT_GPT from "../../src/session/prompt/gpt.txt"
+import PROMPT_GPT55 from "../../src/session/prompt/kilocode-gpt-5.5.txt"
import PROMPT_TRINITY from "../../src/session/prompt/trinity.txt"
describe("SystemPrompt.provider", () => {
@@ -35,6 +37,15 @@ describe("SystemPrompt.provider", () => {
expect(result).toEqual([PROMPT_CODEX])
})
+ test("GPT-5.5 prompt is selected from prompt metadata", () => {
+ const model = ProviderTest.model({
+ prompt: "gpt55",
+ api: { id: "provider-specific-model", url: "https://example.com", npm: "@ai-sdk/openai" },
+ })
+ const result = SystemPrompt.provider(model)
+ expect(result).toEqual([PROMPT_GPT55])
+ })
+
test("gemini prompt is selected when model.prompt is 'gemini'", () => {
const model = ProviderTest.model({ prompt: "gemini" })
const result = SystemPrompt.provider(model)
@@ -66,5 +77,32 @@ describe("SystemPrompt.provider", () => {
const result = SystemPrompt.provider(model)
expect(result).toEqual([PROMPT_ANTHROPIC])
})
+
+ test("GPT-5.5 model ids are not prompt-special without metadata", () => {
+ const model = ProviderTest.model({
+ prompt: undefined,
+ api: { id: "gpt-5.5", url: "https://example.com", npm: "@ai-sdk/openai" },
+ })
+ const result = SystemPrompt.provider(model)
+ expect(result).toEqual([PROMPT_GPT])
+ })
+
+ test("codex prompt metadata still wins for GPT-5.5 model ids", () => {
+ const model = ProviderTest.model({
+ prompt: "codex",
+ api: { id: "gpt-5.5", url: "https://example.com", npm: "@ai-sdk/openai" },
+ })
+ const result = SystemPrompt.provider(model)
+ expect(result).toEqual([PROMPT_CODEX])
+ })
+
+ test("older Codex model ids keep the Codex prompt", () => {
+ const model = ProviderTest.model({
+ prompt: undefined,
+ api: { id: "gpt-5.1-codex", url: "https://example.com", npm: "@ai-sdk/openai" },
+ })
+ const result = SystemPrompt.provider(model)
+ expect(result).toEqual([PROMPT_CODEX])
+ })
})
})
diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts
index 89a703d6da..05e8682d05 100644
--- a/packages/opencode/test/permission/next.test.ts
+++ b/packages/opencode/test/permission/next.test.ts
@@ -949,7 +949,8 @@ it.live("reply - always resolves matching pending requests in same session", ()
),
)
-it.live("reply - always keeps other session pending", () =>
+// kilocode_change start
+it.live("reply - always resolves matching pending requests from other sessions", () =>
withDir({ git: true }, () =>
Effect.gen(function* () {
const a = yield* ask({
@@ -976,13 +977,47 @@ it.live("reply - always keeps other session pending", () =>
yield* reply({ requestID: PermissionID.make("per_test6a"), reply: "always" })
yield* Fiber.join(a)
- expect((yield* list()).map((item) => item.id)).toEqual([PermissionID.make("per_test6b")])
+ yield* Fiber.join(b)
+ expect(yield* list()).toHaveLength(0)
+ }),
+ ),
+)
+
+it.live("reply - always does not resolve dangerous variants from other sessions", () =>
+ withDir({ git: true }, () =>
+ Effect.gen(function* () {
+ const a = yield* ask({
+ id: PermissionID.make("per_test_safe_a"),
+ sessionID: SessionID.make("session_a"),
+ permission: "bash",
+ patterns: ["npm test"],
+ metadata: {},
+ always: ["npm test"],
+ ruleset: [],
+ }).pipe(Effect.forkScoped)
+
+ const b = yield* ask({
+ id: PermissionID.make("per_test_safe_b"),
+ sessionID: SessionID.make("session_b"),
+ permission: "bash",
+ patterns: ["npm test > ~/.ssh/authorized_keys"],
+ metadata: {},
+ always: [],
+ ruleset: [],
+ }).pipe(Effect.forkScoped)
+
+ yield* waitForPending(2)
+ yield* reply({ requestID: PermissionID.make("per_test_safe_a"), reply: "always" })
+
+ yield* Fiber.join(a)
+ expect((yield* list()).map((item) => item.id)).toEqual([PermissionID.make("per_test_safe_b")])
yield* rejectAll()
yield* Fiber.await(b)
}),
),
)
+// kilocode_change end
it.live("reply - publishes replied event", () =>
withDir({ git: true }, () =>
diff --git a/packages/sdk/js/openapi.json b/packages/sdk/js/openapi.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts
index 9922e354a3..a5713b94be 100644
--- a/packages/sdk/js/src/v2/gen/types.gen.ts
+++ b/packages/sdk/js/src/v2/gen/types.gen.ts
@@ -590,6 +590,30 @@ export type EventVcsBranchUpdated = {
}
}
+export type EventKilocodeAgentManagerStart = {
+ type: "kilocode.agent_manager.start"
+ properties: {
+ requestID: string
+ sessionID: string
+ mode: "worktree" | "local"
+ versions?: boolean
+ tasks: Array<{
+ /**
+ * Initial prompt to send to the new session
+ */
+ prompt?: string
+ /**
+ * Short display name for the Agent Manager card
+ */
+ name?: string
+ /**
+ * Git branch name seed for worktree mode
+ */
+ branchName?: string
+ }>
+ }
+}
+
export type EventSessionCompacted = {
type: "session.compacted"
properties: {
@@ -1331,6 +1355,7 @@ export type GlobalEvent = {
| EventSessionIdle
| EventTodoUpdated
| EventVcsBranchUpdated
+ | EventKilocodeAgentManagerStart
| EventSessionCompacted
| EventKiloSessionsRemoteStatusChanged
| EventWorktreeReady
@@ -1539,6 +1564,7 @@ export type PermissionConfig =
lsp?: PermissionRuleConfig
doom_loop?: PermissionActionConfig
skill?: PermissionRuleConfig
+ agent_manager?: PermissionRuleConfig
[key: string]: PermissionRuleConfig | PermissionActionConfig | undefined
}
@@ -1646,6 +1672,7 @@ export type ProviderConfig = {
id?: string
name?: string
family?: string
+ prompt?: "codex" | "gemini" | "beast" | "anthropic" | "trinity" | "anthropic_without_todo" | "ling" | "gpt55"
release_date?: string
attachment?: boolean
reasoning?: boolean
@@ -1855,6 +1882,10 @@ export type Config = {
* Enable remote control of sessions via Kilo Cloud. Equivalent to running /remote on startup.
*/
remote_control?: boolean
+ /**
+ * Automatically collapse reasoning blocks after the agent finishes writing them
+ */
+ auto_collapse_reasoning?: boolean
indexing?: IndexingConfig
/**
* Controls whether terminal command blocks are expanded or collapsed by default in the VS Code chat UI
@@ -2021,6 +2052,10 @@ export type Config = {
* Enable semantic codebase indexing and the semantic_search tool
*/
semantic_indexing?: boolean
+ /**
+ * Enable the VS Code Agent Manager orchestration tool
+ */
+ agent_manager_tool?: boolean
/**
* Enable telemetry. Set to false to opt-out.
*/
@@ -2160,7 +2195,7 @@ export type Model = {
}
}
recommendedIndex?: number
- prompt?: "codex" | "gemini" | "beast" | "anthropic" | "trinity" | "anthropic_without_todo" | "ling"
+ prompt?: "codex" | "gemini" | "beast" | "anthropic" | "trinity" | "anthropic_without_todo" | "ling" | "gpt55"
isFree?: boolean
ai_sdk_provider?: "alibaba" | "anthropic" | "openai" | "openai-compatible" | "openrouter"
}
@@ -2453,6 +2488,7 @@ export type Event =
| EventSessionIdle
| EventTodoUpdated
| EventVcsBranchUpdated
+ | EventKilocodeAgentManagerStart
| EventSessionCompacted
| EventKiloSessionsRemoteStatusChanged
| EventWorktreeReady
diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json
index 75779e397f..0cf4637d35 100644
--- a/packages/sdk/openapi.json
+++ b/packages/sdk/openapi.json
@@ -12225,46 +12225,54 @@
},
"required": ["type", "properties"]
},
- "IndexingStatusState": {
- "type": "string",
- "enum": ["Disabled", "In Progress", "Complete", "Error", "Standby"]
- },
- "IndexingStatus": {
- "type": "object",
- "properties": {
- "state": {
- "$ref": "#/components/schemas/IndexingStatusState"
- },
- "message": {
- "type": "string"
- },
- "processedFiles": {
- "type": "number"
- },
- "totalFiles": {
- "type": "number"
- },
- "percent": {
- "type": "number"
- }
- },
- "required": ["state", "message", "processedFiles", "totalFiles", "percent"]
- },
- "Event.indexing.status": {
+ "Event.kilocode.agent_manager.start": {
"type": "object",
"properties": {
"type": {
"type": "string",
- "const": "indexing.status"
+ "const": "kilocode.agent_manager.start"
},
"properties": {
"type": "object",
"properties": {
- "status": {
- "$ref": "#/components/schemas/IndexingStatus"
+ "requestID": {
+ "type": "string"
+ },
+ "sessionID": {
+ "type": "string",
+ "pattern": "^ses.*"
+ },
+ "mode": {
+ "type": "string",
+ "enum": ["worktree", "local"]
+ },
+ "versions": {
+ "type": "boolean"
+ },
+ "tasks": {
+ "minItems": 1,
+ "maxItems": 20,
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "prompt": {
+ "description": "Initial prompt to send to the new session",
+ "type": "string"
+ },
+ "name": {
+ "description": "Short display name for the Agent Manager card",
+ "type": "string"
+ },
+ "branchName": {
+ "description": "Git branch name seed for worktree mode",
+ "type": "string"
+ }
+ }
+ }
}
},
- "required": ["status"]
+ "required": ["requestID", "sessionID", "mode", "tasks"]
}
},
"required": ["type", "properties"]
@@ -13968,6 +13976,50 @@
},
"required": ["type", "properties"]
},
+ "IndexingStatusState": {
+ "type": "string",
+ "enum": ["Disabled", "In Progress", "Complete", "Error", "Standby"]
+ },
+ "IndexingStatus": {
+ "type": "object",
+ "properties": {
+ "state": {
+ "$ref": "#/components/schemas/IndexingStatusState"
+ },
+ "message": {
+ "type": "string"
+ },
+ "processedFiles": {
+ "type": "number"
+ },
+ "totalFiles": {
+ "type": "number"
+ },
+ "percent": {
+ "type": "number"
+ }
+ },
+ "required": ["state", "message", "processedFiles", "totalFiles", "percent"]
+ },
+ "Event.indexing.status": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "const": "indexing.status"
+ },
+ "properties": {
+ "type": "object",
+ "properties": {
+ "status": {
+ "$ref": "#/components/schemas/IndexingStatus"
+ }
+ },
+ "required": ["status"]
+ }
+ },
+ "required": ["type", "properties"]
+ },
"SyncEvent.message.updated": {
"type": "object",
"properties": {
@@ -14578,7 +14630,7 @@
"$ref": "#/components/schemas/Event.vcs.branch.updated"
},
{
- "$ref": "#/components/schemas/Event.indexing.status"
+ "$ref": "#/components/schemas/Event.kilocode.agent_manager.start"
},
{
"$ref": "#/components/schemas/Event.session.compacted"
@@ -14637,6 +14689,9 @@
{
"$ref": "#/components/schemas/Event.session.deleted"
},
+ {
+ "$ref": "#/components/schemas/Event.indexing.status"
+ },
{
"$ref": "#/components/schemas/SyncEvent.message.updated"
},
@@ -14971,6 +15026,9 @@
},
"skill": {
"$ref": "#/components/schemas/PermissionRuleConfig"
+ },
+ "agent_manager": {
+ "$ref": "#/components/schemas/PermissionRuleConfig"
}
},
"additionalProperties": {
@@ -15160,6 +15218,19 @@
"family": {
"type": "string"
},
+ "prompt": {
+ "type": "string",
+ "enum": [
+ "codex",
+ "gemini",
+ "beast",
+ "anthropic",
+ "trinity",
+ "anthropic_without_todo",
+ "ling",
+ "gpt55"
+ ]
+ },
"release_date": {
"type": "string"
},
@@ -15579,9 +15650,18 @@
"description": "Enable remote control of sessions via Kilo Cloud. Equivalent to running /remote on startup.",
"type": "boolean"
},
+ "auto_collapse_reasoning": {
+ "description": "Automatically collapse reasoning blocks after the agent finishes writing them",
+ "type": "boolean"
+ },
"indexing": {
"$ref": "#/components/schemas/IndexingConfig"
},
+ "terminal_command_display": {
+ "description": "Controls whether terminal command blocks are expanded or collapsed by default in the VS Code chat UI",
+ "type": "string",
+ "enum": ["expanded", "collapsed"]
+ },
"model": {
"description": "Model to use in the format of provider/model, eg anthropic/claude-2",
"anyOf": [
@@ -15921,6 +16001,10 @@
"description": "Enable semantic codebase indexing and the semantic_search tool",
"type": "boolean"
},
+ "agent_manager_tool": {
+ "description": "Enable the VS Code Agent Manager orchestration tool",
+ "type": "boolean"
+ },
"openTelemetry": {
"description": "Enable telemetry. Set to false to opt-out.",
"default": true,
@@ -16322,7 +16406,7 @@
},
"prompt": {
"type": "string",
- "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo", "ling"]
+ "enum": ["codex", "gemini", "beast", "anthropic", "trinity", "anthropic_without_todo", "ling", "gpt55"]
},
"isFree": {
"type": "boolean"
@@ -17202,7 +17286,7 @@
"$ref": "#/components/schemas/Event.vcs.branch.updated"
},
{
- "$ref": "#/components/schemas/Event.indexing.status"
+ "$ref": "#/components/schemas/Event.kilocode.agent_manager.start"
},
{
"$ref": "#/components/schemas/Event.session.compacted"
@@ -17260,6 +17344,9 @@
},
{
"$ref": "#/components/schemas/Event.session.deleted"
+ },
+ {
+ "$ref": "#/components/schemas/Event.indexing.status"
}
]
},
diff --git a/packages/shared/src/global.ts b/packages/shared/src/global.ts
index 4bc0ec93bf..455021baa4 100644
--- a/packages/shared/src/global.ts
+++ b/packages/shared/src/global.ts
@@ -20,11 +20,14 @@ export namespace Global {
Service,
Effect.gen(function* () {
const app = "opencode"
- const home = process.env.KILO_TEST_HOME ?? os.homedir()
- const data = path.join(xdgData!, app)
- const cache = path.join(xdgCache!, app)
- const cfg = path.join(xdgConfig!, app)
- const state = path.join(xdgState!, app)
+ // kilocode_change start - guard against newline-contaminated HOME/XDG paths
+ const clean = (p: string | undefined) => p?.replace(/[\r\n]+/g, "")
+ const home = clean(process.env.KILO_TEST_HOME ?? os.homedir())!
+ const data = path.join(clean(xdgData)!, app)
+ const cache = path.join(clean(xdgCache)!, app)
+ const cfg = path.join(clean(xdgConfig)!, app)
+ const state = path.join(clean(xdgState)!, app)
+ // kilocode_change end
const bin = path.join(cache, "bin")
const log = path.join(data, "log")
diff --git a/script/upstream/find-conflict-markers.sh b/script/upstream/find-conflict-markers.sh
new file mode 100755
index 0000000000..076f5b9e68
--- /dev/null
+++ b/script/upstream/find-conflict-markers.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+# Find git merge conflict markers in a file.
+#
+# Prints the line number and the marker for each of:
+# <<<<<<< (ours start)
+# ||||||| (base / diff3 separator)
+# ======= (separator)
+# >>>>>>> (theirs end)
+#
+# Requires ripgrep (rg).
+#
+# Usage:
+# script/upstream/find-conflict-markers.sh
+set -euo pipefail
+
+if [ "$#" -ne 1 ]; then
+ echo "Usage: $0 " >&2
+ exit 2
+fi
+
+file=$1
+
+if [ ! -f "$file" ]; then
+ echo "error: not a file: $file" >&2
+ exit 2
+fi
+
+if ! command -v rg >/dev/null 2>&1; then
+ echo "error: ripgrep (rg) is required but not installed" >&2
+ exit 2
+fi
+
+# Match the four conflict marker line shapes. `=======` must be the whole line;
+# the others may have trailing content (branch name, commit hash, etc.).
+# rg exits 1 when no matches are found; treat that as success (clean file).
+rg -n '^(<{7}|\|{7}|={7}$|>{7})' "$file" || {
+ status=$?
+ if [ "$status" -eq 1 ]; then
+ exit 0
+ fi
+ exit "$status"
+}
diff --git a/script/upstream/transforms/package-names.ts b/script/upstream/transforms/package-names.ts
index d4f19f8394..1442bdc03d 100644
--- a/script/upstream/transforms/package-names.ts
+++ b/script/upstream/transforms/package-names.ts
@@ -68,12 +68,12 @@ const PACKAGE_PATTERNS = [
// SDK public API renames (Opencode → Kilo)
// Order matters: longer names first to avoid partial matches
- { pattern: /KiloClientConfig/g, replacement: "KiloClientConfig" },
- { pattern: /createKiloClient/g, replacement: "createKiloClient" },
- { pattern: /createKiloServer/g, replacement: "createKiloServer" },
- { pattern: /createKiloTui/g, replacement: "createKiloTui" },
- { pattern: /KiloClient/g, replacement: "KiloClient" },
- // createKilo (without suffix) needs negative lookahead to avoid matching createKiloClient
+ { pattern: /OpencodeClientConfig/g, replacement: "KiloClientConfig" },
+ { pattern: /createOpencodeClient/g, replacement: "createKiloClient" },
+ { pattern: /createOpencodeServer/g, replacement: "createKiloServer" },
+ { pattern: /createOpencodeTui/g, replacement: "createKiloTui" },
+ { pattern: /OpencodeClient/g, replacement: "KiloClient" },
+ // createOpencode (without suffix) needs negative lookahead to avoid matching createOpencodeClient
{ pattern: /\bcreateOpencode\b(?!Client|Server|Tui)/g, replacement: "createKilo" },
// Branding: environment variables (exclude OPENCODE_API_KEY — upstream Zen SaaS key)
diff --git a/script/upstream/transforms/transform-package-json.ts b/script/upstream/transforms/transform-package-json.ts
index 2976f3a009..9e5caec341 100644
--- a/script/upstream/transforms/transform-package-json.ts
+++ b/script/upstream/transforms/transform-package-json.ts
@@ -185,6 +185,7 @@ const KILO_DEPENDENCIES: Record> = {
"packages/opencode/package.json": {
"@kilocode/kilo-gateway": "workspace:*",
"@kilocode/kilo-telemetry": "workspace:*",
+ "@czcode/lakehouse": "workspace:*", // czcode_change
},
// packages/app/package.json needs these
"packages/app/package.json": {
diff --git a/upstream-merge-report-7.2.33.md b/upstream-merge-report-7.2.33.md
new file mode 100644
index 0000000000..1991c670cc
--- /dev/null
+++ b/upstream-merge-report-7.2.33.md
@@ -0,0 +1,399 @@
+# Upstream Merge Conflict Report
+
+Generated: 2026-05-02T09:35:45.278Z
+
+## Summary
+
+- **Upstream Version**: 7.2.33
+- **Upstream Commit**: `5bf9e821`
+- **Base Branch**: main
+- **Merge Branch**: yunqiqiliang/kilo-opencode-v7.2.33
+- **Total Files Changed**: 183
+
+## Files by Recommendation
+
+### Package.json Transform (Auto)
+
+- `package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/app/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/desktop-electron/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/desktop/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/kilo-indexing/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/opencode/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/plugin/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/sdk/js/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/shared/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/storybook/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `packages/ui/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+- `sdks/vscode/package.json` (package)
+ - Package.json: take upstream, transform names, inject Kilo deps, preserve version
+
+### Script Transform (Auto)
+
+- `packages/script/package.json` (script)
+ - Script file: take upstream and transform GitHub references
+
+### Extension Transform (Auto)
+
+- `packages/extensions/zed/extension.toml` (extension)
+ - Extension file: take upstream and apply Kilo branding
+
+### Keep Kilo Version (Ours)
+
+- `.changeset/center-sidebar-toolbar.md` (markdown)
+ - Markdown files are typically Kilo-specific documentation
+- `.changeset/external-directory-read-prompts.md` (markdown)
+ - Markdown files are typically Kilo-specific documentation
+- `.changeset/remote-status-badge.md` (markdown)
+ - Markdown files are typically Kilo-specific documentation
+- `.changeset/terminal-command-display.md` (markdown)
+ - Markdown files are typically Kilo-specific documentation
+- `packages/kilo-docs/lib/nav/ai-providers.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/lib/nav/contributing.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/lib/nav/kiloclaw.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/package.json` (package)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/anthropic.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/bedrock.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/cerebras.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/chutes-ai.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/claude-code.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/deepseek.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/fireworks.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/gemini.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/glama.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/groq.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/human-relay.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/inception.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/kilocode.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/lmstudio.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/minimax.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/mistral.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/moonshot.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/ollama.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/openai-chatgpt-plus-pro.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/openai-compatible.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/openai.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/openrouter.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/ovhcloud.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/requesty.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/sap-ai-core.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/synthetic.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/unbound.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/v0.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/vercel-ai-gateway.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/vertex.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/virtual-quota-fallback.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/vscode-lm.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/xai.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/ai-providers/zenmux.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/automate/agent-manager-workflows.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/automate/agent-manager.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/automate/extending/plugins.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/contributing/architecture/per-message-feedback.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/getting-started/byok.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/getting-started/settings/index.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/getting-started/troubleshooting/troubleshooting-extension.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/index.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/kiloclaw/tools/index.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/pages/kiloclaw/tools/other-tools.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/previous-docs-redirects.js` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/indexing-provider-blur-race-chromium-linux.png` (other)
+ - File is in a Kilo-specific directory
+- `packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/mode-edit-permissions-chromium-linux.png` (other)
+ - File is in a Kilo-specific directory
+- `packages/kilo-gateway/package.json` (package)
+ - File is in a Kilo-specific directory
+- `packages/kilo-gateway/src/api/constants.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/package.json` (package)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/ar.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/br.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/bs.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/da.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/de.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/en.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/es.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/fr.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/ja.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/ko.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/nl.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/no.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/pl.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/ru.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/th.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/tr.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/uk.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/zh.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-i18n/src/zht.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-telemetry/package.json` (package)
+ - File is in a Kilo-specific directory
+- `packages/kilo-telemetry/src/__tests__/telemetry.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-telemetry/src/index.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-telemetry/src/otel-exporter.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-telemetry/src/telemetry.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-telemetry/src/tracer.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-ui/package.json` (package)
+ - File is in a Kilo-specific directory
+- `packages/kilo-ui/src/components/message-part.css` (other)
+ - File is in a Kilo-specific directory
+- `packages/kilo-ui/src/components/message-part.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-ui/src/stories/message-part.stories.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/CHANGELOG.md` (markdown)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/package.json` (package)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/KiloProvider.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/agent-manager/setup-script-template.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/agent-manager/tool-start.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/AutocompleteInlineCompletionProvider.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ContextRetrievalService.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/autocomplete/context/ImportDefinitionsService.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/index.d.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/src/services/autocomplete/continuedev/core/vscode-test-harness/src/VSCodeIde.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/tests/unit/config-utils.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/tests/unit/navigate.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/tests/unit/permission-editor.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/agent-manager/navigate.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/App.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/AutoApproveTab.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/DisplayTab.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/ModeEditView.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/PermissionEditor.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/permission-utils.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/components/settings/settings-io.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/context/config.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/context/display.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/ar.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/br.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/bs.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/da.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/de.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/en.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/es.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/fr.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/ja.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/ko.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/nl.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/no.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/pl.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/ru.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/th.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/tr.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/uk.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/zh.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/i18n/zht.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/styles/notifications.css` (other)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/types/messages/config.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/types/messages/permissions.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/CHANGELOG.md` (markdown)
+ - Markdown files are typically Kilo-specific documentation
+- `packages/opencode/src/kilocode/agent-manager/event.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/src/kilocode/agent/index.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/src/kilocode/config/config.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/src/kilocode/tool/agent-manager.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/src/kilocode/tool/agent-manager.txt` (other)
+ - File is in a Kilo-specific directory
+- `packages/opencode/src/kilocode/tool/registry.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/test/kilocode/agent-manager-tool.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/test/kilocode/permission/next.always-rules.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/test/kilocode/project-config-update.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/opencode/test/kilocode/system-prompt.test.ts` (code)
+ - File is in a Kilo-specific directory
+- `packages/sdk/openapi.json` (config)
+ - File is Kilo-specific and should not be overwritten
+- `script/upstream/find-conflict-markers.sh` (script)
+ - File is in a Kilo-specific directory
+- `script/upstream/package.json` (script)
+ - File is in a Kilo-specific directory
+
+### Manual Review Required
+
+- `bun.lock` (other)
+ - File needs manual review
+- `nix/hashes.json` (config)
+ - Config files may have Kilo-specific settings
+- `packages/opencode/src/config/config.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/src/config/permission.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/src/config/provider.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/src/global/index.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/src/permission/index.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/src/session/llm.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/src/session/prompt/kilocode-gpt-5.5.txt` (other)
+ - File needs manual review
+- `packages/opencode/src/session/system.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/test/config/config.test.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/opencode/test/permission/next.test.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/sdk/js/src/v2/gen/types.gen.ts` (code)
+ - Code files need manual review for kilocode_change markers
+- `packages/shared/src/global.ts` (code)
+ - Code files need manual review for kilocode_change markers
+
+## Recommendations
+
+- 155 files will keep Kilo's version
+- 14 files require manual review