From 220cdd20c8af4d4420ee0c4bf9bc7da8a0e82673 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 3 Aug 2026 17:10:43 +0200 Subject: [PATCH 01/57] fix(vscode): inset recent sessions in narrow sidebars --- .changeset/narrow-sidebar-recent.md | 5 +++++ packages/kilo-vscode/webview-ui/src/styles/chat-layout.css | 3 +++ 2 files changed, 8 insertions(+) create mode 100644 .changeset/narrow-sidebar-recent.md diff --git a/.changeset/narrow-sidebar-recent.md b/.changeset/narrow-sidebar-recent.md new file mode 100644 index 0000000000..fcd56b0f73 --- /dev/null +++ b/.changeset/narrow-sidebar-recent.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Keep recent sessions and the Show History action inset and usable in narrow VS Code sidebars. diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css b/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css index 3e475d3ff3..d071072d90 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css @@ -115,7 +115,10 @@ flex-direction: column; align-items: center; justify-content: center; + box-sizing: border-box; height: 100%; + width: 100%; + min-width: 0; color: var(--vscode-descriptionForeground); text-align: center; padding: 20px; From 4057d7eb387455b58c4d46d83b81ff3bc61a79a5 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 3 Aug 2026 17:44:22 +0200 Subject: [PATCH 02/57] fix(vscode): reduce narrow recent view side margins --- packages/kilo-vscode/webview-ui/src/styles/chat-layout.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css b/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css index d071072d90..decab4d0f1 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat-layout.css @@ -122,6 +122,7 @@ color: var(--vscode-descriptionForeground); text-align: center; padding: 20px; + padding-inline: 12px; gap: 20px; } From e26acef176992b922515f9cbec3ab48eaf237d54 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 4 Aug 2026 10:41:05 +0200 Subject: [PATCH 03/57] fix(vscode): keep feedback label on one line --- packages/kilo-vscode/webview-ui/src/styles/welcome.css | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/webview-ui/src/styles/welcome.css b/packages/kilo-vscode/webview-ui/src/styles/welcome.css index e281493143..a1e1eb0820 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/welcome.css +++ b/packages/kilo-vscode/webview-ui/src/styles/welcome.css @@ -210,6 +210,7 @@ font-family: inherit; font-size: var(--kilo-font-size-12); font-weight: 500; + white-space: nowrap; cursor: pointer; transition: background-color 0.15s, From bdc66f447f3eb31545fc1dfd9ad78c15ea7c0bb3 Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" Date: Tue, 4 Aug 2026 08:45:00 +0000 Subject: [PATCH 04/57] chore: update kilo-vscode visual regression baselines --- .../settings/work-style-onboarding-default-chromium-linux.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png index 2597a60c84..13b431ad97 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/work-style-onboarding-default-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e3f86dabc39700b3a86508c1e6499a546eeabf720832b62c9ce12f2d97378ebd -size 34649 +oid sha256:00092414080e2f70fb9b3166e8d06a1692af93e33717d0ab8fea5fe96e6a6982 +size 34468 From 29dec8580f4c37ee5e743d7dbaabe94334bf963a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 6 Aug 2026 16:22:13 +0200 Subject: [PATCH 05/57] refactor(agent-manager): extract keybinding defaults into keybind-defaults.ts Move the platform-aware defaultBindings map (and its MAX_JUMP_INDEX helper) out of AgentManagerApp.tsx into a vscode-free helper module, following the documented extraction pattern for capped files. This reduces AgentManagerApp.tsx from 2808 to 2781 lines and lowers the ESLint max-lines cap from 2800 to 2785 to lock in the improvement. --- packages/kilo-vscode/eslint.config.mjs | 8 ++--- .../agent-manager/AgentManagerApp.tsx | 29 +----------------- .../agent-manager/keybind-defaults.ts | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 32 deletions(-) create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/keybind-defaults.ts diff --git a/packages/kilo-vscode/eslint.config.mjs b/packages/kilo-vscode/eslint.config.mjs index 8aedaac465..5db05cfbe9 100644 --- a/packages/kilo-vscode/eslint.config.mjs +++ b/packages/kilo-vscode/eslint.config.mjs @@ -43,10 +43,10 @@ export default [ }, { files: ["webview-ui/agent-manager/AgentManagerApp.tsx"], - // Lowered 3210 → 2800 after extracting the sidebar body (SidebarBody.tsx) - // and the tab bar (TabBar.tsx) into components. Keep shrinking as more - // logic moves out; do not raise. - rules: { complexity: ["error", 74], "max-lines": ["error", 2800] }, + // Lowered 3210 → 2800 → 2785 after extracting the sidebar body (SidebarBody.tsx), + // the tab bar (TabBar.tsx) into components, and the keybinding defaults + // (keybind-defaults.ts). Keep shrinking as more logic moves out; do not raise. + rules: { complexity: ["error", 74], "max-lines": ["error", 2785] }, }, { files: ["src/agent-manager/AgentManagerProvider.ts"], diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 615ebb350d..e95775758d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -199,37 +199,10 @@ interface SetupState { type SidebarSelection = typeof LOCAL | string | null type SidePanel = "diff" | "pr" | "terminal" | null const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) -// Fallback keybindings before extension sends resolved ones -const MAX_JUMP_INDEX = 9 const SIDE_RESIZE_INTERVAL_MS = 32 -const defaultBindings: Record = { - previousSession: isMac ? "⌘⌥↑" : "Ctrl+Alt+↑", - nextSession: isMac ? "⌘⌥↓" : "Ctrl+Alt+↓", - previousTab: isMac ? "⌘⌥←" : "Ctrl+Alt+←", - nextTab: isMac ? "⌘⌥→" : "Ctrl+Alt+→", - search: isMac ? "⌘F" : "Ctrl+F", - showTerminal: isMac ? "⌘/" : "Ctrl+/", - newTerminal: isMac ? "⌘⇧T" : "Ctrl+Shift+T", - runScript: isMac ? "⌘E" : "Ctrl+E", - toggleDiff: isMac ? "⌘D" : "Ctrl+D", - showShortcuts: isMac ? "⌘⇧/" : "Ctrl+Shift+/", - newTab: isMac ? "⌘T" : "Ctrl+T", - closeTab: isMac ? "⌘W" : "Ctrl+W", - newWorktree: isMac ? "⌘N" : "Ctrl+N", - quickWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N", - closeWorktree: isMac ? "⌘⇧W" : "Ctrl+Shift+W", - openWorktree: isMac ? "⌘⇧O" : "Ctrl+Shift+O", - openPR: isMac ? "⌘⇧R" : "Ctrl+Shift+R", - agentManagerOpen: isMac ? "⌘⇧M" : "Ctrl+Shift+M", - cycleAgentMode: isMac ? "⌘." : "Ctrl+.", - cyclePreviousAgentMode: isMac ? "⌘⇧." : "Ctrl+Shift+.", - ...Object.fromEntries( - Array.from({ length: MAX_JUMP_INDEX }, (_, i) => [`jumpTo${i + 1}`, isMac ? `⌘${i + 1}` : `Ctrl+${i + 1}`]), - ), -} - import { parseBindingTokens } from "./keybind-tokens" +import { defaultBindings } from "./keybind-defaults" const AgentManagerContent: Component = () => { const { t } = useLanguage() diff --git a/packages/kilo-vscode/webview-ui/agent-manager/keybind-defaults.ts b/packages/kilo-vscode/webview-ui/agent-manager/keybind-defaults.ts new file mode 100644 index 0000000000..9ec6bd7955 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/keybind-defaults.ts @@ -0,0 +1,30 @@ +const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) + +const MAX_JUMP_INDEX = 9 + +/** Fallback keybindings before the extension sends resolved ones. */ +export const defaultBindings: Record = { + previousSession: isMac ? "⌘⌥↑" : "Ctrl+Alt+↑", + nextSession: isMac ? "⌘⌥↓" : "Ctrl+Alt+↓", + previousTab: isMac ? "⌘⌥←" : "Ctrl+Alt+←", + nextTab: isMac ? "⌘⌥→" : "Ctrl+Alt+→", + search: isMac ? "⌘F" : "Ctrl+F", + showTerminal: isMac ? "⌘/" : "Ctrl+/", + newTerminal: isMac ? "⌘⇧T" : "Ctrl+Shift+T", + runScript: isMac ? "⌘E" : "Ctrl+E", + toggleDiff: isMac ? "⌘D" : "Ctrl+D", + showShortcuts: isMac ? "⌘⇧/" : "Ctrl+Shift+/", + newTab: isMac ? "⌘T" : "Ctrl+T", + closeTab: isMac ? "⌘W" : "Ctrl+W", + newWorktree: isMac ? "⌘N" : "Ctrl+N", + quickWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N", + closeWorktree: isMac ? "⌘⇧W" : "Ctrl+Shift+W", + openWorktree: isMac ? "⌘⇧O" : "Ctrl+Shift+O", + openPR: isMac ? "⌘⇧R" : "Ctrl+Shift+R", + agentManagerOpen: isMac ? "⌘⇧M" : "Ctrl+Shift+M", + cycleAgentMode: isMac ? "⌘." : "Ctrl+.", + cyclePreviousAgentMode: isMac ? "⌘⇧." : "Ctrl+Shift+.", + ...Object.fromEntries( + Array.from({ length: MAX_JUMP_INDEX }, (_, i) => [`jumpTo${i + 1}`, isMac ? `⌘${i + 1}` : `Ctrl+${i + 1}`]), + ), +} From 07b45a348b7038ef705e6e5b6fc1458b637a057d Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 6 Aug 2026 16:56:26 +0200 Subject: [PATCH 06/57] test(agent-manager): read keybinding literals from keybind-defaults.ts The defaultBindings map moved out of AgentManagerApp.tsx into keybind-defaults.ts, so the source-content assertion that checked for the newWorktree/quickWorktree literals against AgentManagerApp.tsx no longer holds. Point it at the new file instead. --- packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index 3d41e4e7dc..6b7e8e2de7 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -64,6 +64,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/diff-viewer/BaseBranchPicker.tsx"), ] const TSX_FILE = TSX_FILES[0]! +const KEYBIND_DEFAULTS_FILE = path.join(ROOT, "webview-ui/agent-manager/keybind-defaults.ts") const PROVIDER_FILE = path.join(ROOT, "src/agent-manager/AgentManagerProvider.ts") const DIFF_CONTROLLER_FILE = path.join(ROOT, "src/agent-manager/worktree-diff-controller.ts") const IMPORTER_FILE = path.join(ROOT, "src/agent-manager/worktree-importer.ts") @@ -365,7 +366,6 @@ describe("Agent Manager Worktree Actions", () => { const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf-8")) as { contributes: { keybindings: { command: string; key?: string; mac?: string }[] } } - const source = fs.readFileSync(TSX_FILE, "utf-8") const dialog = manifest.contributes.keybindings.find( (item) => item.command === "kilo-code.new.agentManager.newWorktree", ) @@ -375,8 +375,9 @@ describe("Agent Manager Worktree Actions", () => { expect(dialog).toMatchObject({ key: "ctrl+n", mac: "cmd+n" }) expect(quick).toMatchObject({ key: "ctrl+shift+n", mac: "cmd+shift+n" }) - expect(source).toContain('newWorktree: isMac ? "⌘N" : "Ctrl+N"') - expect(source).toContain('quickWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N"') + const bindings = fs.readFileSync(KEYBIND_DEFAULTS_FILE, "utf-8") + expect(bindings).toContain('newWorktree: isMac ? "⌘N" : "Ctrl+N"') + expect(bindings).toContain('quickWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N"') }) it("forwards the quick-worktree command to immediate creation", () => { From 6efe6475991e54cb987ad408c87028dd9f08d583 Mon Sep 17 00:00:00 2001 From: Rakshith N Date: Thu, 6 Aug 2026 23:28:48 +0530 Subject: [PATCH 07/57] feat: avoid long skill paths/URLs tooltip to overflow --- .../src/components/settings/AgentBehaviourTab.tsx | 4 ++-- packages/kilo-vscode/webview-ui/src/styles/settings.css | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index 3a39b7da5d..e97b1cff6d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -903,7 +903,7 @@ const AgentBehaviourTab: Component = () => { "border-bottom": index() < skillPaths().length - 1 ? "1px solid var(--border-weak-base)" : "none", }} > - + { "border-bottom": index() < skillUrls().length - 1 ? "1px solid var(--border-weak-base)" : "none", }} > - + Date: Fri, 7 Aug 2026 00:31:01 +0530 Subject: [PATCH 08/57] test: add viewport bound checks --- .../tests/skills-settings-responsive.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts b/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts index 5665ea93a3..6403880ced 100644 --- a/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts +++ b/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts @@ -73,6 +73,12 @@ test.describe("skills settings responsive layout", () => { await trigger.hover() const content = page.locator('[data-component="tooltip"]').filter({ hasText: seeded }) await expect(content, `Kilo Tooltip exposes full path on hover: ${seeded}`).toBeVisible() + const tipBox = await content.boundingBox() + const viewport = page.viewportSize()! + expect(tipBox, "path tooltip bounding box").not.toBeNull() + expect(tipBox!.x + tipBox!.width, `path tooltip stays inside viewport: ${seeded}`).toBeLessThanOrEqual( + viewport.width + 1, + ) } for (const seeded of [SEEDED_URL, SEEDED_URL_2]) { @@ -99,6 +105,12 @@ test.describe("skills settings responsive layout", () => { await trigger.hover() const content = page.locator('[data-component="tooltip"]').filter({ hasText: seeded }) await expect(content, `Kilo Tooltip exposes full URL on hover: ${seeded}`).toBeVisible() + const tipBox = await content.boundingBox() + const viewport = page.viewportSize()! + expect(tipBox, "URL tooltip bounding box").not.toBeNull() + expect(tipBox!.x + tipBox!.width, `URL tooltip stays inside viewport: ${seeded}`).toBeLessThanOrEqual( + viewport.width + 1, + ) } for (const [label, card] of [ From a47a3fb2f6512ee7dd596d6e618c881a337e2177 Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Thu, 6 Aug 2026 15:32:30 -0400 Subject: [PATCH 09/57] fix(agent-manager): keep provider below its line cap --- packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 5533ad7323..33b988a922 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -1698,7 +1698,6 @@ export class AgentManagerProvider implements Disposable { panel.reveal(false) focusPanelPrompt(panel, this.waitForPanelReady(panel), this.waitForPanelActive(panel)) } - public isActive(): boolean { return this.panel?.active === true } From a78e85f2348f1bfd4906cdfe3e70af2e5468820d Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" Date: Fri, 7 Aug 2026 01:56:23 +0000 Subject: [PATCH 10/57] release(jetbrains): v7.0.14 --- packages/kilo-jetbrains/CHANGELOG.md | 36 +++++++++++++++++++++++ packages/kilo-jetbrains/gradle.properties | 2 +- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/kilo-jetbrains/CHANGELOG.md b/packages/kilo-jetbrains/CHANGELOG.md index 8f50db751a..4984804be0 100644 --- a/packages/kilo-jetbrains/CHANGELOG.md +++ b/packages/kilo-jetbrains/CHANGELOG.md @@ -128,6 +128,42 @@ ## [Unreleased] +## [7.0.14] - 2026-08-07 + +### Added +- feat(vscode): improve model search ranking by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12943 +- feat(agent-manager): target new worktrees by project by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12931 +- feat(vscode): improve Agent Manager terminal navigation by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12945 +- feat(cli): add command file management parity with skills by @kirillk in https://github.com/Kilo-Org/kilocode/pull/12825 + +### Fixed +- fix(jetbrains): improve slash command matching by @kirillk in https://github.com/Kilo-Org/kilocode/pull/12909 +- fix(vscode): avoid macOS Git launcher overhead in Agent Manager by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12888 +- fix(vscode): speed up snapshot installs and isolate project state by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12871 +- fix(vscode): avoid Agent Manager terminal logo artifacts by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12925 +- fix(cli): safely restore grep signal controls by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12882 +- fix(vscode): reduce Agent Manager Git polling load by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12924 +- fix(cli): keep built-in skill examples inert by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12926 +- fix(vscode): persist Agent Manager dialog selections by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12927 +- fix(cli): prevent TUI config logs from corrupting terminal by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12929 +- fix(vscode): prevent Windows gh console flashes by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12936 +- fix(agent-manager): tighten multi-project sidebar layout by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12940 +- fix(cli): restore interactive terminal input for workspaces by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12935 +- fix(cli): resume subagents after session fork by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12937 +- fix(agent-manager): improve inspector resize performance by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12948 +- fix(cli): avoid startup database lock crashes by @johnnyeric in https://github.com/Kilo-Org/kilocode/pull/12947 +- fix(agent-manager): support llama.cpp tool schema by @chrarnoldus in https://github.com/Kilo-Org/kilocode/pull/12946 +- fix(agent-manager): extract pending-create below the app size cap by @lambertjosh in https://github.com/Kilo-Org/kilocode/pull/12952 +- fix(tui): suppress expected disposal aborts by @lambertjosh in https://github.com/Kilo-Org/kilocode/pull/12950 +- fix(vscode/cursor): keep sidebar navigation visible in the secondary side bar by @bagatao-anaconda in https://github.com/Kilo-Org/kilocode/pull/12894 +- fix(vscode): preserve model variants on switch by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12944 +- fix(vscode): translate Agent Manager terminal shortcut by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12956 + +### Changed +- release(jetbrains): v7.0.13 by @kilo-maintainer[bot] in https://github.com/Kilo-Org/kilocode/pull/12921 +- docs: add bundled JetBrains install instructions by @kirillk in https://github.com/Kilo-Org/kilocode/pull/12953 + + ## [7.0.13] - 2026-08-05 ### Added diff --git a/packages/kilo-jetbrains/gradle.properties b/packages/kilo-jetbrains/gradle.properties index 7db9a13fbb..21e1e5e45b 100644 --- a/packages/kilo-jetbrains/gradle.properties +++ b/packages/kilo-jetbrains/gradle.properties @@ -1,5 +1,5 @@ kotlin.stdlib.default.dependency=false -kilo.jetbrains.version=7.0.13 +kilo.jetbrains.version=7.0.14 # When true (default) the JetBrains plugin uses the pinned CLI release from package.json. # Set to false ONLY for local dev: generate the client from local source + bundle the local binary. # false is NOT releasable -- production builds fail unless this is true. From e8c6d0cfb80009e7f1b881d54c1492de97a8ef05 Mon Sep 17 00:00:00 2001 From: Kirill Kalishev Date: Thu, 6 Aug 2026 21:57:53 -0400 Subject: [PATCH 11/57] docs(jetbrains): edit changelog for v7.0.14 --- packages/kilo-jetbrains/CHANGELOG.md | 36 +++------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) diff --git a/packages/kilo-jetbrains/CHANGELOG.md b/packages/kilo-jetbrains/CHANGELOG.md index 4984804be0..168b62f5ea 100644 --- a/packages/kilo-jetbrains/CHANGELOG.md +++ b/packages/kilo-jetbrains/CHANGELOG.md @@ -128,41 +128,11 @@ ## [Unreleased] -## [7.0.14] - 2026-08-07 - -### Added -- feat(vscode): improve model search ranking by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12943 -- feat(agent-manager): target new worktrees by project by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12931 -- feat(vscode): improve Agent Manager terminal navigation by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12945 -- feat(cli): add command file management parity with skills by @kirillk in https://github.com/Kilo-Org/kilocode/pull/12825 +## [7.0.14] - 2026-08-06 ### Fixed -- fix(jetbrains): improve slash command matching by @kirillk in https://github.com/Kilo-Org/kilocode/pull/12909 -- fix(vscode): avoid macOS Git launcher overhead in Agent Manager by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12888 -- fix(vscode): speed up snapshot installs and isolate project state by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12871 -- fix(vscode): avoid Agent Manager terminal logo artifacts by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12925 -- fix(cli): safely restore grep signal controls by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12882 -- fix(vscode): reduce Agent Manager Git polling load by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12924 -- fix(cli): keep built-in skill examples inert by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12926 -- fix(vscode): persist Agent Manager dialog selections by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12927 -- fix(cli): prevent TUI config logs from corrupting terminal by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12929 -- fix(vscode): prevent Windows gh console flashes by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12936 -- fix(agent-manager): tighten multi-project sidebar layout by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12940 -- fix(cli): restore interactive terminal input for workspaces by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12935 -- fix(cli): resume subagents after session fork by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12937 -- fix(agent-manager): improve inspector resize performance by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12948 -- fix(cli): avoid startup database lock crashes by @johnnyeric in https://github.com/Kilo-Org/kilocode/pull/12947 -- fix(agent-manager): support llama.cpp tool schema by @chrarnoldus in https://github.com/Kilo-Org/kilocode/pull/12946 -- fix(agent-manager): extract pending-create below the app size cap by @lambertjosh in https://github.com/Kilo-Org/kilocode/pull/12952 -- fix(tui): suppress expected disposal aborts by @lambertjosh in https://github.com/Kilo-Org/kilocode/pull/12950 -- fix(vscode/cursor): keep sidebar navigation visible in the secondary side bar by @bagatao-anaconda in https://github.com/Kilo-Org/kilocode/pull/12894 -- fix(vscode): preserve model variants on switch by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12944 -- fix(vscode): translate Agent Manager terminal shortcut by @marius-kilocode in https://github.com/Kilo-Org/kilocode/pull/12956 - -### Changed -- release(jetbrains): v7.0.13 by @kilo-maintainer[bot] in https://github.com/Kilo-Org/kilocode/pull/12921 -- docs: add bundled JetBrains install instructions by @kirillk in https://github.com/Kilo-Org/kilocode/pull/12953 - +- Improve slash command matching in the JetBrains plugin so typed commands resolve more reliably. +- Avoid startup crashes when the Kilo CLI database is temporarily locked by another process. ## [7.0.13] - 2026-08-05 From 0a2e5c278b0cc7e532b6d43126b9f96e861c28ea Mon Sep 17 00:00:00 2001 From: Josh Lambert Date: Fri, 7 Aug 2026 00:53:00 -0400 Subject: [PATCH 12/57] refactor(vscode): keep webview files below line caps --- .../tests/unit/agent-manager-arch.test.ts | 39 +++---- .../tests/unit/session-variants.test.ts | 66 +++++++++++ .../agent-manager/AgentManagerApp.tsx | 107 ++--------------- .../agent-manager/DefaultBaseBranchDialog.tsx | 110 ++++++++++++++++++ .../src/context/session-variants.ts | 73 ++++++++++++ .../webview-ui/src/context/session.tsx | 74 +++--------- 6 files changed, 287 insertions(+), 182 deletions(-) create mode 100644 packages/kilo-vscode/tests/unit/session-variants.test.ts create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/DefaultBaseBranchDialog.tsx create mode 100644 packages/kilo-vscode/webview-ui/src/context/session-variants.ts diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index 954114ac57..c64c332596 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -50,6 +50,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/SidebarBody.tsx"), path.join(ROOT, "webview-ui/agent-manager/TabBar.tsx"), path.join(ROOT, "webview-ui/agent-manager/ProjectBranchDialog.tsx"), + path.join(ROOT, "webview-ui/agent-manager/DefaultBaseBranchDialog.tsx"), path.join(ROOT, "webview-ui/agent-manager/tab-rendering.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalTab.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/SideTerminalPanel.tsx"), @@ -865,7 +866,7 @@ describe("KiloProvider — pending session refresh on reconnect", () => { // --------------------------------------------------------------------------- describe("Agent Manager — dialog listener cleanup", () => { - const tsx = fs.readFileSync(TSX_FILE, "utf-8") + const tsx = fs.readFileSync(path.join(ROOT, "webview-ui/agent-manager/DefaultBaseBranchDialog.tsx"), "utf-8") /** * Regression: handleChangeDefaultBaseBranch subscribes to vscode.onMessage @@ -873,34 +874,20 @@ describe("Agent Manager — dialog listener cleanup", () => { * and the Escape keydown handler. If the dialog closed via backdrop click or * external dialog.close(), the listener leaked and stacked on every reopen. * - * The fix ties unsub() to Solid's onCleanup inside the dialog.show() render - * function so it always disposes regardless of how the dialog closes. + * The fix ties unsub() to the dialog component's Solid cleanup so it always + * disposes regardless of how the dialog closes. */ - it("handleChangeDefaultBaseBranch uses onCleanup(unsub) inside dialog.show", () => { - const fnStart = tsx.indexOf("const handleChangeDefaultBaseBranch") - expect(fnStart, "handleChangeDefaultBaseBranch must exist").toBeGreaterThan(-1) - - // Grab the function body (enough to cover the dialog.show callback) - const snippet = tsx.slice(fnStart, fnStart + 2000) - - // The dialog.show callback must register onCleanup(unsub) - const showIdx = snippet.indexOf("dialog.show(") - expect(showIdx, "dialog.show() call must exist").toBeGreaterThan(-1) - const afterShow = snippet.slice(showIdx) - expect(afterShow, "onCleanup(unsub) must be inside dialog.show callback").toContain("onCleanup(unsub)") + it("DefaultBaseBranchDialog disposes its message listener on cleanup", () => { + expect(tsx).toContain("const unsub = vscode.onMessage") + expect(tsx).toContain("onCleanup(unsub)") }) - it("selectBranch does not manually call unsub (handled by onCleanup)", () => { - const fnStart = tsx.indexOf("const handleChangeDefaultBaseBranch") - const snippet = tsx.slice(fnStart, fnStart + 2000) - - // Find the selectBranch function body - const selStart = snippet.indexOf("const selectBranch") - expect(selStart, "selectBranch must exist").toBeGreaterThan(-1) - const selEnd = snippet.indexOf("}", selStart + 50) - const selBody = snippet.slice(selStart, selEnd + 1) - - expect(selBody, "selectBranch should not call unsub() directly").not.toContain("unsub()") + it("select does not manually call unsub (handled by onCleanup)", () => { + const selStart = tsx.indexOf("const select =") + expect(selStart, "select must exist").toBeGreaterThan(-1) + const selEnd = tsx.indexOf("}", selStart + 40) + const selBody = tsx.slice(selStart, selEnd + 1) + expect(selBody, "select should not call unsub() directly").not.toContain("unsub()") }) }) diff --git a/packages/kilo-vscode/tests/unit/session-variants.test.ts b/packages/kilo-vscode/tests/unit/session-variants.test.ts new file mode 100644 index 0000000000..c266da6310 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/session-variants.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from "bun:test" +import { createSessionVariants } from "../../webview-ui/src/context/session-variants" +import type { ExtensionMessage, ModelSelection } from "../../webview-ui/src/types/messages" + +const model: ModelSelection = { providerID: "anthropic", modelID: "claude-sonnet-4" } + +function setup(session?: string) { + const selections: Record = {} + const messages: Array<{ type: string; key?: string; value?: string }> = [] + const order: string[] = [] + let handler: ((message: ExtensionMessage) => void) | undefined + const variants = createSessionVariants({ + selections: () => selections, + set: (key, value) => { + selections[key] = value + }, + selected: () => model, + session: () => session, + agent: () => "code", + find: () => ({ variants: { low: {}, high: {} } }), + post: (message) => { + order.push("post") + messages.push(message) + }, + listen: (next) => { + order.push("listen") + handler = next + return () => order.push("unsub") + }, + }) + return { variants, selections, messages, order, dispatch: (message: ExtensionMessage) => handler?.(message) } +} + +describe("session variants", () => { + it("subscribes before requesting persisted variants and returns cleanup", () => { + const state = setup() + const unsub = state.variants.load() + expect(state.order).toEqual(["listen", "post"]) + expect(state.messages).toEqual([{ type: "requestVariants" }]) + unsub() + expect(state.order).toEqual(["listen", "post", "unsub"]) + }) + + it("loads global variants without restoring stale session variants", () => { + const state = setup() + state.variants.load() + state.dispatch({ + type: "variantsLoaded", + variants: { "agent/code/anthropic/claude-sonnet-4": "high", "session/old/model": "low" }, + }) + expect(state.selections).toEqual({ "agent/code/anthropic/claude-sonnet-4": "high" }) + }) + + it("persists global selections but keeps session selections local", () => { + const global = setup() + global.variants.select("high") + expect(global.messages).toEqual([ + { type: "persistVariant", key: "agent/code/anthropic/claude-sonnet-4", value: "high" }, + ]) + + const scoped = setup("session-a") + scoped.variants.select("low") + expect(scoped.selections).toEqual({ "session/session-a/anthropic/claude-sonnet-4": "low" }) + expect(scoped.messages).toEqual([]) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 8b814fe417..90775511cf 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -21,7 +21,6 @@ import type { AgentManagerKeybindingsMessage, AgentManagerMultiVersionProgressMessage, AgentManagerSendInitialMessage, - AgentManagerBranchesMessage, AgentManagerWorktreeDiffMessage, AgentManagerWorktreeDiffFileMessage, AgentManagerWorktreeDiffLoadingMessage, @@ -43,7 +42,6 @@ import type { SectionState, SessionInfo, SessionCreatedMessage, - BranchInfo, TerminalDestination, TerminalFont, } from "../src/types/messages" @@ -78,6 +76,7 @@ import { ProviderShell } from "../src/context/provider-shell" import { ChatView } from "../src/components/chat" import HistoryView from "../src/components/history/HistoryView" import { NewWorktreeDialog } from "./NewWorktreeDialog" +import { DefaultBaseBranchDialog } from "./DefaultBaseBranchDialog" import { createModeRouter } from "./mode-router" import { ProjectList } from "./ProjectList" import { SidebarBody } from "./SidebarBody" @@ -152,7 +151,6 @@ import type { ReviewComment } from "../diff-viewer/review-comments" import { clearReviewComposer, createReviewComposer } from "../diff-viewer/review-annotations" import type { SidebarSearchMenuRef } from "./SidebarSearchMenu" import { createSidebarSearch, type SidebarSearchItem } from "./sidebar-search" -import { BranchSelect } from "../src/components/shared/BranchSelect" import { randomColor } from "./section-colors" import { createNewTaskDrafts } from "./new-task-drafts" import { @@ -280,7 +278,6 @@ const AgentManagerContent: Component = () => { projectList().length === 0 || pid === undefined || pid === activeProjectId() const repoDefaultBranch = () => defaultBaseBranch() ?? repoDetectedBranch() ?? "main" - const hasConfiguredBranch = () => !!defaultBaseBranch() const DEFAULT_SIDEBAR_WIDTH = 260 const MIN_SIDEBAR_WIDTH = 200 @@ -1773,99 +1770,15 @@ const AgentManagerContent: Component = () => { const setupScript = metrics.click("configure_setup_script", "worktree_settings", handleConfigureSetupScript) const handleChangeDefaultBaseBranch = () => { - const [search, setSearch] = createSignal("") - const [branches, setBranches] = createSignal([]) - const [loading, setLoading] = createSignal(true) - const [highlighted, setHighlighted] = createSignal(-1) - - const unsub = vscode.onMessage((msg) => { - if (msg.type === "agentManager.branches") { - const ev = msg as AgentManagerBranchesMessage - setBranches(ev.branches) - if (ev.defaultBranch) setRepoDetectedBranch(ev.defaultBranch) - setLoading(false) - } - }) - - vscode.postMessage({ type: "agentManager.requestBranches" }) - - const filtered = createMemo(() => { - const s = search().toLowerCase() - if (!s) return branches() - return branches().filter((b) => b.name.toLowerCase().includes(s)) - }) - - const selectBranch = (name: string | undefined) => { - vscode.postMessage({ type: "agentManager.setDefaultBaseBranch", branch: name }) - setDefaultBaseBranch(name) - dialog.close() - } - - const handleKeyDown = (e: KeyboardEvent) => { - const items = filtered() - // offset by 1 for auto-detect option (-1 = auto-detect) - const total = items.length + 1 - if (e.key === "ArrowDown") { - e.preventDefault() - e.stopPropagation() - setHighlighted((prev) => Math.min(prev + 1, total - 2)) - } else if (e.key === "ArrowUp") { - e.preventDefault() - e.stopPropagation() - setHighlighted((prev) => Math.max(prev - 1, -1)) - } else if (e.key === "Enter") { - e.preventDefault() - e.stopPropagation() - const idx = highlighted() - if (idx === -1) { - selectBranch(undefined) - } else { - const branch = items[idx] - if (branch) selectBranch(branch.name) - } - } else if (e.key === "Escape") { - e.preventDefault() - e.stopPropagation() - dialog.close() - } - } - - dialog.show(() => { - onCleanup(unsub) - return ( - -
- { - setSearch(v) - setHighlighted(-1) - }} - onSelect={(b) => selectBranch(b.name)} - onSearchKeyDown={handleKeyDown} - selected={defaultBaseBranch()} - highlighted={highlighted()} - onHighlight={setHighlighted} - searchPlaceholder={t("agentManager.dialog.searchBranches")} - emptyLabel={t("agentManager.import.noMatchingBranches")} - loadingLabel={t("agentManager.import.loadingBranches")} - defaultLabel={t("agentManager.dialog.branchBadge.default")} - remoteLabel={t("agentManager.dialog.branchBadge.remote")} - defaultName={defaultBaseBranch()} - autoOption={{ - label: t("agentManager.worktree.defaultBaseBranchAuto"), - hint: repoDetectedBranch(), - active: !hasConfiguredBranch(), - highlighted: highlighted() === -1, - onSelect: () => selectBranch(undefined), - }} - /> -
-
- ) - }) + dialog.show(() => ( + dialog.close()} + /> + )) } const handleShowKeyboardShortcuts = () => { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/DefaultBaseBranchDialog.tsx b/packages/kilo-vscode/webview-ui/agent-manager/DefaultBaseBranchDialog.tsx new file mode 100644 index 0000000000..4b4abbba1c --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/DefaultBaseBranchDialog.tsx @@ -0,0 +1,110 @@ +/** @jsxImportSource solid-js */ + +import { createMemo, createSignal, onCleanup, type Component } from "solid-js" +import { Dialog } from "@kilocode/kilo-ui/dialog" +import { BranchSelect } from "../src/components/shared/BranchSelect" +import { useLanguage } from "../src/context/language" +import { useVSCode } from "../src/context/vscode" +import type { AgentManagerBranchesMessage, BranchInfo } from "../src/types/messages" + +interface Props { + selected?: string + detected?: string + onSelect: (branch?: string) => void + onDetected: (branch: string) => void + onClose: () => void +} + +export const DefaultBaseBranchDialog: Component = (props) => { + const { t } = useLanguage() + const vscode = useVSCode() + const [search, setSearch] = createSignal("") + const [branches, setBranches] = createSignal([]) + const [loading, setLoading] = createSignal(true) + const [highlighted, setHighlighted] = createSignal(-1) + const filtered = createMemo(() => { + const value = search().toLowerCase() + return value ? branches().filter((branch) => branch.name.toLowerCase().includes(value)) : branches() + }) + const select = (branch?: string) => { + vscode.postMessage({ type: "agentManager.setDefaultBaseBranch", branch }) + props.onSelect(branch) + props.onClose() + } + const unsub = vscode.onMessage((message) => { + if (message.type !== "agentManager.branches") return + const event = message as AgentManagerBranchesMessage + setBranches(event.branches) + if (event.defaultBranch) props.onDetected(event.defaultBranch) + setLoading(false) + }) + onCleanup(unsub) + vscode.postMessage({ type: "agentManager.requestBranches" }) + + const keydown = (event: KeyboardEvent) => { + const items = filtered() + const total = items.length + 1 + if (event.key === "ArrowDown") { + event.preventDefault() + event.stopPropagation() + setHighlighted((value) => Math.min(value + 1, total - 2)) + return + } + if (event.key === "ArrowUp") { + event.preventDefault() + event.stopPropagation() + setHighlighted((value) => Math.max(value - 1, -1)) + return + } + if (event.key === "Enter") { + event.preventDefault() + event.stopPropagation() + const index = highlighted() + if (index === -1) { + select() + return + } + const branch = items[index] + if (branch) select(branch.name) + return + } + if (event.key !== "Escape") return + event.preventDefault() + event.stopPropagation() + props.onClose() + } + + return ( + +
+ { + setSearch(value) + setHighlighted(-1) + }} + onSelect={(branch) => select(branch.name)} + onSearchKeyDown={keydown} + selected={props.selected} + highlighted={highlighted()} + onHighlight={setHighlighted} + searchPlaceholder={t("agentManager.dialog.searchBranches")} + emptyLabel={t("agentManager.import.noMatchingBranches")} + loadingLabel={t("agentManager.import.loadingBranches")} + defaultLabel={t("agentManager.dialog.branchBadge.default")} + remoteLabel={t("agentManager.dialog.branchBadge.remote")} + defaultName={props.selected} + autoOption={{ + label: t("agentManager.worktree.defaultBaseBranchAuto"), + hint: props.detected, + active: !props.selected, + highlighted: highlighted() === -1, + onSelect: () => select(), + }} + /> +
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/src/context/session-variants.ts b/packages/kilo-vscode/webview-ui/src/context/session-variants.ts new file mode 100644 index 0000000000..5902605944 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/context/session-variants.ts @@ -0,0 +1,73 @@ +import type { Accessor } from "solid-js" +import type { ExtensionMessage, ModelSelection } from "../types/messages" +import { getAgentVariant, getVariant, preserveVariant, variantKey } from "./session-variant-store" + +interface Model { + variants?: Record +} + +type Message = { type: "requestVariants" } | { type: "persistVariant"; key: string; value: string } + +interface Options { + selections: Accessor> + set: (key: string, value: string) => void + selected: (sessionID?: string) => ModelSelection | null + session: Accessor + agent: (sessionID?: string) => string + find: (selection: ModelSelection) => Model | undefined + post: (message: Message) => void + listen: (handler: (message: ExtensionMessage) => void) => () => void +} + +export function createSessionVariants(options: Options) { + const list = (sessionID?: string) => { + const selection = options.selected(sessionID) + if (!selection) return [] + return Object.keys(options.find(selection)?.variants ?? {}) + } + + const agent = (name: string, selection: ModelSelection | null) => { + if (!selection) return undefined + return getAgentVariant(options.selections(), selection, options.find(selection), name) + } + + const current = (sessionID?: string) => { + const sid = sessionID ?? options.session() + const selection = options.selected(sid) + if (!selection) return undefined + const variants = list(sid) + if (variants.length === 0) return undefined + return getVariant(options.selections(), selection, variants, options.agent(sid), sid) + } + + const select = (value: string, sessionID?: string) => { + const sid = sessionID ?? options.session() + const selection = options.selected(sid) + if (!selection) return + const key = variantKey(selection, options.agent(sid), sid) + options.set(key, value) + if (!sid) options.post({ type: "persistVariant", key, value }) + } + + const carry = (selection: ModelSelection, value: string | undefined, name: string, sessionID?: string) => { + const next = preserveVariant(value, Object.keys(options.find(selection)?.variants ?? {})) + if (!next) return + const key = variantKey(selection, name, sessionID) + options.set(key, next) + if (!sessionID) options.post({ type: "persistVariant", key, value: next }) + } + + const load = () => { + const unsub = options.listen((message) => { + if (message.type !== "variantsLoaded") return + for (const [key, value] of Object.entries(message.variants)) { + if (key.startsWith("session/")) continue + options.set(key, value) + } + }) + options.post({ type: "requestVariants" }) + return unsub + } + + return { carry, list, agent, current, select, load } +} diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 839b0dc3f5..1e09519fa5 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -74,14 +74,8 @@ import { errorIDs } from "./session-errors" import { PartStash } from "./part-stash" import { mergeParts, sameParts } from "./session-parts" import { state as todoState } from "./todo-revert" -import { - getAgentVariant, - getVariant, - preserveVariant, - sessionVariantKeys, - transferVariants, - variantKey, -} from "./session-variant-store" +import { sessionVariantKeys, transferVariants, variantKey } from "./session-variant-store" +import { createSessionVariants } from "./session-variants" import { KILO_AUTO, KILO_PROVIDER_ID, parseModelString } from "../../../src/shared/provider-model" import { reviewMetadata, type ReviewMessageData } from "../../../src/shared/review-comments" import { visibleMessages as filterVisibleMessages } from "./session-queue" @@ -670,13 +664,18 @@ export const SessionProvider: ParentComponent = (props) => { }) } - function carryVariant(selection: ModelSelection, current: string | undefined, agent: string, sessionID?: string) { - const value = preserveVariant(current, Object.keys(provider.findModel(selection)?.variants ?? {})) - if (!value) return - const key = variantKey(selection, agent, sessionID) - setStore("variantSelections", key, value) - if (!sessionID) vscode.postMessage({ type: "persistVariant", key, value }) - } + const variants = createSessionVariants({ + selections: () => store.variantSelections, + set: (key, value) => setStore("variantSelections", key, value), + selected, + session: currentSessionID, + agent: agentForScope, + find: provider.findModel, + post: vscode.postMessage, + listen: vscode.onMessage, + }) + const { carry: carryVariant, list: variantList, agent: variantForAgent, current: currentVariant } = variants + const selectVariant = variants.select function selectModel(providerID: string, modelID: string, sessionID?: string) { const sid = sessionID ?? currentSessionID() @@ -930,50 +929,7 @@ export const SessionProvider: ParentComponent = (props) => { clearTimeout(fallback) }) - const variantList = (sessionID?: string) => { - const sel = selected(sessionID) - if (!sel) return [] - const model = provider.findModel(sel) - if (!model?.variants) return [] - return Object.keys(model.variants) - } - - function variantForAgent(agentName: string, sel: ModelSelection | null) { - if (!sel) return undefined - const model = provider.findModel(sel) - return getAgentVariant(store.variantSelections, sel, model, agentName) - } - - const currentVariant = (sessionID?: string) => { - const sid = sessionID ?? currentSessionID() - const sel = selected(sid) - if (!sel) return undefined - const list = variantList(sid) - if (list.length === 0) return undefined - return getVariant(store.variantSelections, sel, list, agentForScope(sid), sid) - } - - const selectVariant = (value: string, sessionID?: string) => { - const sid = sessionID ?? currentSessionID() - const sel = selected(sid) - if (!sel) return - const key = variantKey(sel, agentForScope(sid), sid) - setStore("variantSelections", key, value) - if (!sid) vscode.postMessage({ type: "persistVariant", key, value }) - } - - // Load persisted variants from extension globalState - const unsubVariants = vscode.onMessage((message: ExtensionMessage) => { - if (message.type !== "variantsLoaded") return - for (const [k, v] of Object.entries(message.variants)) { - if (k.startsWith("session/")) continue - setStore("variantSelections", k, v) - } - }) - - vscode.postMessage({ type: "requestVariants" }) - - onCleanup(unsubVariants) + onCleanup(variants.load()) // Load persisted per-mode model selections from model.json via extension host. // Uses replace semantics so a reset (empty payload) clears old entries. From e2db06453915fe25a42122a64348285aee2c8f60 Mon Sep 17 00:00:00 2001 From: Rakshith N Date: Fri, 7 Aug 2026 11:24:43 +0530 Subject: [PATCH 13/57] (chore): add user readable changeset --- .changeset/vscode-skills-tooltip-wrap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/vscode-skills-tooltip-wrap.md diff --git a/.changeset/vscode-skills-tooltip-wrap.md b/.changeset/vscode-skills-tooltip-wrap.md new file mode 100644 index 0000000000..a4a5ecec03 --- /dev/null +++ b/.changeset/vscode-skills-tooltip-wrap.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Long skill folder paths and URLs shown in the tooltip on the Skills settings page now wrap inside the viewport instead of overflowing on a single line. From 56e981b589c3c772fc388e6a0d349da6d5543b17 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 09:13:57 +0200 Subject: [PATCH 14/57] fix(agent-manager): align multi-project header actions --- .changeset/align-agent-manager-project-actions.md | 5 +++++ .../kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx | 2 +- .../kilo-vscode/webview-ui/agent-manager/agent-manager.css | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 .changeset/align-agent-manager-project-actions.md diff --git a/.changeset/align-agent-manager-project-actions.md b/.changeset/align-agent-manager-project-actions.md new file mode 100644 index 0000000000..b2d36e56ce --- /dev/null +++ b/.changeset/align-agent-manager-project-actions.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Align multi-project Agent Manager header actions with the worktree controls. diff --git a/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx b/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx index 1e2dcb8fb8..36359ab5f2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx @@ -36,7 +36,6 @@ export const ProjectsSection: Component = (props) => ( label={} actions={
- {props.tools} = (props) => ( label={props.t("agentManager.project.add")} onClick={props.onAdd} /> + {props.tools}
} /> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index fc1b050530..fabf666ad8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -287,6 +287,9 @@ html[data-theme="kilo-vscode"] land on the same glyph line as the rows inside the list. */ .am-projects > .am-section-header { padding-left: 2px; + /* The project toolbar has one more action than the worktree toolbar. Extend + its row so both toolbars start on the same leading action column. */ + margin-right: -14px; } .am-section-label { From 707c7bb9efd68e765ab82c8433a557413afb2ee4 Mon Sep 17 00:00:00 2001 From: Marius Date: Fri, 7 Aug 2026 09:31:08 +0200 Subject: [PATCH 15/57] docs(vscode): update multi-project Agent Manager guidance (#12973) --- ...er-multi-project-implementation-handoff.md | 459 ------------------ ...ent-manager-multi-project-shipping-gaps.md | 210 -------- packages/kilo-vscode/AGENTS.md | 6 + 3 files changed, 6 insertions(+), 669 deletions(-) delete mode 100644 .kilo/plans/agent-manager-multi-project-implementation-handoff.md delete mode 100644 .kilo/plans/agent-manager-multi-project-shipping-gaps.md diff --git a/.kilo/plans/agent-manager-multi-project-implementation-handoff.md b/.kilo/plans/agent-manager-multi-project-implementation-handoff.md deleted file mode 100644 index 510a5e1c38..0000000000 --- a/.kilo/plans/agent-manager-multi-project-implementation-handoff.md +++ /dev/null @@ -1,459 +0,0 @@ -# Agent Manager multi-project implementation handoff - -**Status:** Implementation-ready handoff from the current branch - -**Date:** 2026-07-22 - -This document tells the next implementation model exactly how to continue from branch `abalone-bactrosaurus` at commit `8f48da2278` plus the uncommitted plan files. It is the execution checklist. Detailed decisions live in: - -- [`agent-manager-multi-project-configuration.md`](./agent-manager-multi-project-configuration.md) for configuration ownership, bindings, revisions, indexing consent, and settings tests; -- [`agent-manager-multi-project-uniform-ui.md`](./agent-manager-multi-project-uniform-ui.md) for runtime, routing, lifecycle, uniform sidebar UI, and acceptance criteria. - -If this handoff conflicts with either architecture document, stop and resolve the contradiction before coding. - -## Goal - -Finish the current prototype into a safe, complete multi-project Agent Manager behind a default-off VS Code experimental flag. - -The feature is done only when: - -- multiple expanded projects show the same full Agent Manager sidebar behavior; -- every action uses explicit project/worktree/session ownership; -- Settings cannot write the wrong project's config; -- indexing requires machine-local consent per canonical project; -- project identity is canonical, Git-root-aware, common-dir-safe, and remote-authority-aware; -- single-project behavior remains unchanged when the flag is off. - -## Current branch baseline - -### Implemented and worth keeping - -- persistent additional-project registry with serialized/re-read mutations; -- separate `ProjectContext` objects and per-project Agent Manager state files; -- project-stamped state, stats, PR, and session payloads; -- canonical worktree presence comparison for symlink/`/tmp` aliases; -- per-project session listing from project root and worktree directories; -- partial production `ProjectRouteService` integration and raw-session ambiguity rejection; -- project-qualified sidebar DOM IDs and cross-project previous/next navigation; -- atomic selection validation and same-project activation fast path; -- flag-off return to pinned project; -- Experimental Settings toggle and translations; -- focused unit tests for registry, contexts, paths, pollers, routing, sessions, selection, and navigation. - -### Not implemented or incomplete - -- immutable/revisioned Settings bindings; -- machine-local indexing consent; -- canonical pinned Git toplevel, `commonGitDir`, URI scheme/authority identity; -- strict project envelopes for all Agent Manager operations; -- complete route-service use for every session/terminal/diff/share/permission operation; -- one full sidebar body shared by single- and multi-project modes; -- uniform per-context poller and stale-state ownership; -- all repository mutations through `ProjectContext.run()`; -- complete real two-project E2E verification. - -### Known diff cleanup - -- remove unrelated `packages/opencode/package.json` ordering/dependency drift; -- restore unrelated `bun.lock` ordering/version drift unless required by a retained change; -- remove `experimental.multi_project` from the CLI config schema and generated SDK once the VS Code flag is the sole source of truth; -- rewrite the changeset after final behavior is complete. - -Do not reset or discard unrelated user changes. Inspect every cleanup diff before applying it. - -## Non-negotiable invariants - -1. Agent Manager activation is not a Settings write target. -2. No identified operation falls back to the active project or workspace root in multi-project mode. -3. A project config write targets the explicitly selected registered project root, never an active worktree. -4. Runtime config remains directory-correct: project Local uses root, worktree sessions use exact worktree directory. -5. Repository config cannot grant indexing consent. -6. New projects default to indexing disabled on this machine. -7. Provider/API credentials and indexing infrastructure never enter project config through Settings. -8. Raw session/worktree IDs are never sufficient UI/runtime identity when multiple projects are exposed. -9. The full existing sidebar behavior is reused; do not maintain a simplified multi-project copy. -10. Do not raise `AgentManagerProvider.ts` or `AgentManagerApp.tsx` line caps. - -## Work order - -Complete slices in order. Do not start the next slice while the current slice's tests or checks fail. - -## Slice 0A: consolidate the feature flag - -### Required result - -Use one source of truth: VS Code application setting `kilo-code.new.experimental.multiProject`. - -### Required changes - -1. Remove `experimental.multi_project` from: - - `packages/core/src/v1/config/config.ts`; - - `packages/sdk/js/src/v2/gen/types.gen.ts` by regenerating the SDK after schema removal; - - `packages/kilo-vscode/webview-ui/src/types/messages/config.ts`. -2. Do not hand-edit generated SDK files. Run `./script/generate.ts` from repository root after endpoint/schema changes. -3. Change `ExperimentalTab.tsx` so the toggle reads the VS Code setting delivered in `configLoaded.settings`, not `config().experimental`. -4. Add `multiProject` to the extension settings payload sent by `KiloProvider.fetchAndSendConfig()`. -5. Toggling it sends only `updateSetting("experimental.multiProject", checked)`. -6. Keep `VscodeHost.multiProject()` and its change listener reading the same VS Code setting. - -### Tests - -- toggle initial state reflects VS Code setting when CLI config disagrees; -- toggling updates the VS Code setting and Agent Manager reacts; -- no CLI/project config file gains `experimental.multi_project`. - -### Stop gate - -Run from `packages/kilo-vscode/`: - -```sh -bun run format -bun run format:check -bun run typecheck -bun run lint -bun run test:unit -``` - -Do not continue if any command fails. - -## Slice 0B: machine-local indexing consent - -### Required result - -Indexing enablement is explicit machine-local consent keyed by canonical `ProjectId`, default false. Project config controls indexing rules but cannot enable indexing. - -### Required changes - -1. Add a versioned machine-local consent store in the extension, not repository config and not synced across machines. -2. Use canonical project identity as the key. The pinned project and registered projects use the same identity resolver. -3. Remove hidden routing of `indexing.enabled` to project config from `webview-ui/src/utils/config-scope.ts`. -4. Remove project/global inheritance logic for `indexing.enabled` from `indexing-tab-state.ts`. -5. The Indexing UI requires an explicit project selector for enablement and reads/writes consent through dedicated messages. -6. Keep these in User config only: - - provider; - - model and dimension; - - credentials/API keys/base URLs; - - vector-store type and connection/storage settings; - - machine/infrastructure tuning defaults. -7. Keep these available in explicit Project scope: - - file extensions; - - repository include/ignore rules; - - deliberate repository chunking/tuning overrides. -8. Indexing startup/status must require both valid User indexing configuration and consent for the routed project. -9. A repository containing `indexing.enabled: true` must not enable indexing. - -### Suggested files - -- new VS Code-free consent store under `src/indexing/` or `src/agent-manager/`; -- `src/KiloProvider.ts` only as a thin protocol adapter; -- `webview-ui/src/components/settings/IndexingTab.tsx`; -- `webview-ui/src/components/settings/indexing-tab-state.ts`; -- `webview-ui/src/context/config.tsx` only if necessary; -- focused tests beside existing indexing tests. - -### Tests - -- unknown project defaults off; -- A enabled does not enable B; -- symlink/path alias resolves to the same consent; -- repository config cannot grant consent; -- removing/re-adding behavior follows the documented store policy; -- no credentials/storage settings are emitted in a Project-scope patch. - -## Slice 0C: revisioned config overlay backend - -### Required result - -Config reads return authoritative target descriptors and revisions. Writes use compare-and-swap and cannot overwrite external changes or a changed target. - -### Required changes - -1. Extend Kilo-owned config overlay code: - - `packages/opencode/src/kilocode/config/overlay.ts`; - - a new Kilo-owned writer under `packages/opencode/src/kilocode/config/`; - - `packages/opencode/src/kilocode/server/httpapi/groups/config-console.ts`; - - `packages/opencode/src/kilocode/server/httpapi/handlers/config-console.ts`. -2. Read response includes for global and project targets: - - canonical path; - - scope; - - exists/writable; - - SHA-256 revision of canonical path, existence marker, and exact bytes; - - raw parsed target layer; - - effective config and source metadata. -3. Write request accepts exactly one scope, `set`, `unset`, and expected path/revision. -4. Server re-resolves the authoritative target. Never trust client path as a destination. -5. Lock by canonical target path, re-read inside lock, compare revision, patch raw JSONC, validate, and atomically replace. -6. Return a fresh authoritative snapshot. -7. Return typed 409 conflicts for target/revision changes; preserve drafts client-side. -8. Keep Kilo logic in `packages/opencode/src/kilocode/`. Shared upstream files get minimal marked delegation only when unavoidable. -9. Regenerate the SDK after endpoint changes. - -### Tests - -Add/extend: - -- `packages/opencode/test/kilocode/server/config-overlay.test.ts`; -- `packages/opencode/test/kilocode/project-config-update.test.ts`. - -Cover: - -- exact raw target layer; -- comment-only external edit conflict; -- missing-file target revision; -- newly created higher-priority target conflict; -- concurrent writers: one success, one 409; -- symlink escape rejection; -- global and project writes remain separate; -- atomic write failure never exposes partial content. - -### Stop gate - -From `packages/opencode/`: - -```sh -bun run typecheck -bun test test/kilocode/server/config-overlay.test.ts -bun test test/kilocode/project-config-update.test.ts -``` - -From repository root: - -```sh -./script/generate.ts -bun run script/check-opencode-annotations.ts -bun run script/check-opencode-promise-facades.ts -``` - -## Slice 0D: immutable Settings bindings - -### Required result - -Settings has explicit `User | Project` scope. Project scope requires an explicit trusted project selector. Drafts and saves never follow Agent Manager activation. - -### Required changes - -1. Split runtime-effective config from Settings editor config. -2. Add a binding controller under `packages/kilo-vscode/src/kilo-provider/`; keep `KiloProvider.ts` as a thin adapter. -3. Replace unqualified config protocol with: - - read request carrying request ID, scope, and optional project ID; - - snapshot carrying opaque binding ID and source/target metadata; - - write request carrying request ID, binding ID, set, and unset. -4. The extension stores authoritative bindings. The webview never supplies a writable path. -5. Project binding resolution: - - resolve registered `ProjectContext.root`; - - require project existence, matching generation, and trust; - - never use active worktree/session or `getWorkspaceDirectory()` after binding creation. -6. Bindings expire on successful save, reconnect, project removal, generation change, or trust revocation. -7. Separate drafts by scope/project binding. -8. Dirty selector changes prompt Save, Discard, or Stay. -9. Out-of-order reads/writes update only matching request/binding. -10. External config events refresh clean drafts and mark dirty drafts stale without overwriting them. -11. Remove `splitConfigByScope()` after all controls declare scope explicitly. -12. Audit direct config mutators outside the save bar, including provider disconnect, imports/resets, custom providers, work styles, Permission Dock, MCP actions, and indexing. -13. Open Project Config takes explicit `ProjectRef`, checks trust, and resolves the registered root. - -### UI scope policy - -Use the table in `agent-manager-multi-project-configuration.md`. Do not invent another policy. - -### Blocking tests - -- load A, activate B, save A: only A target changes; -- same while selecting worktrees/sessions; -- User save changes only user config; -- Project save changes only selected trusted project's root config; -- dirty draft cannot migrate to another project; -- project removal/trust revocation expires binding; -- external edit returns conflict and preserves draft; -- no save-time active-directory lookup occurs. - -## Slice 1: canonical project identity - -### Required result - -Every project is a canonical Git repository root with URI authority and common Git directory identity. - -### Required changes - -1. Replace path-only project descriptors with: - -```ts -{ - id, - uri, - scheme, - authority, - root, - commonGitDir, - label, - order, - trusted, - addedAt -} -``` - -2. Pinned and added projects use the same resolver: - - validate URI host scope; - - `git rev-parse --show-toplevel`; - - `git rev-parse --path-format=absolute --git-common-dir`; - - realpath/normalize both; - - derive ID from scheme, authority, and canonical root. -3. Opening VS Code in a repository subdirectory still uses the Git toplevel and root state file. -4. Reject another exposed project sharing canonical `commonGitDir`. -5. Key Git mutation locks by common Git directory. -6. Preserve remote URI scheme/authority in picker, storage, and open-folder operations. -7. Migrate or safely read the current registry version without dropping valid entries. - -### Tests - -- workspace subdirectory resolves to repository root; -- symlink aliases dedupe; -- linked worktree/common-dir duplicate rejected; -- other remote authority hidden but preserved; -- missing/non-Git entry remains removable and never falls back to pinned root. - -## Slice 2: strict routing and lifecycle - -### Required result - -Every repository-bound operation is explicitly project-qualified once multiple projects are exposed. - -### Required changes - -1. Introduce one project envelope at the Agent Manager boundary with project ID and generation. -2. Compatibility adapter injects pinned identity only in single-project mode. -3. Multi-project mode rejects missing/mismatched identity with typed errors. -4. Wire `ProjectRouteService` into every existing-session operation: - - transcript/messages; - - prompt/abort; - - share/unshare; - - fork/continuation; - - permission/question responses; - - terminal; - - file/context operations; - - diff/apply/revert. -5. Route global SSE events once by exact directory/session ownership. -6. Preserve non-Agent-Manager KiloProvider behavior. -7. Execute repository mutations through `ctx.run()` and generation-check commits. -8. Removal waits for mutation queue, stops pollers/watchers, flushes state, detaches routes, then removes registry entry. -9. Flag-off activates pinned Local and suspends secondaries without aborting sessions. - -### Tests - -- same raw session ID across projects is ambiguous without qualifier; -- B Local/share/unshare/permission never routes through A; -- unknown IDs produce no terminal/file/diff operation; -- project switch during mutation cannot change operation ownership; -- late completion after removal cannot mutate replacement context. - -## Slice 3: uniform context-owned state and polling - -### Required result - -Every initialized expanded project owns the same live-state services. Active selection changes cadence/detail only, not ownership. - -### Required changes - -1. Move stats and PR pollers into `ProjectContext`; remove active singleton/background split. -2. Each context owns stale/presence state, cached stats, local stats, PR state, run/setup state, and generation. -3. Background presence updates project stale state rather than emitting empty arrays. -4. All project outputs include project ID and generation; webview discards stale generations. -5. Session-created/deleted/updated events refresh the owning project store without active-project filtering. -6. Panel visibility and expansion uniformly control polling. - -### Tests - -- two expanded projects both receive stats/presence/PR updates; -- collapse stops only that project's pollers; -- stale late poll result is dropped; -- background session deletion updates only its project. - -## Slice 4: one full sidebar body - -### Required result - -Single- and multi-project modes render the same full `ProjectSidebarBody` implementation. - -### Required changes - -1. Extract the current full `renderBody()` from `AgentManagerApp.tsx` intact. -2. Parameterize it with one project store and project-qualified actions. -3. Preserve: - - busy state; - - drag/drop and grouping; - - section actions; - - search and keyboard hints; - - run/setup state; - - New Worktree dialog; - - worktree menus and PR actions; - - complete managed/unassigned session behavior. -4. Delete the simplified duplicate body path. -5. Render project rows keyed by stable project ID and keep bodies mounted across active changes. -6. New Worktree receives explicit target project, defaulting to Settings/detail selection policy as specified. - -### Tests - -- single-project behavior unchanged with flag off; -- two expanded projects render two full bodies; -- active selection changes no body mount identity; -- drag/drop, sections, worktree actions, sessions, and keyboard navigation work across projects. - -## Slice 5: cleanup and release validation - -1. Remove unrelated manifest/lockfile drift. -2. Update changeset to final user-visible behavior. -3. Run all extension and affected CLI checks. -4. Build/package the extension. -5. Use the VS Code self-test fixture with two repositories and multiple worktrees/sessions. -6. Keep feature default off until every blocking test passes. - -### Required extension checks - -From `packages/kilo-vscode/`: - -```sh -bun run format -bun run format:check -bun run typecheck -bun run lint -bun run test:unit -bun run knip -bun run check-kilocode-change -bun run compile -``` - -From repository root: - -```sh -bun run script/check-opencode-annotations.ts -bun run script/check-opencode-promise-facades.ts -bun run script/check-md-table-padding.ts -``` - -### Manual scenarios - -1. Flag off: current single-project Agent Manager is unchanged. -2. Enable flag in Experimental Settings; restart; value persists without CLI/project config changes. -3. Add/trust/expand two repositories; restart; pinned remains first. -4. Both projects show Local, worktrees, sessions, stats, PR state, and full actions. -5. Navigate previous/next across projects. -6. Create/delete/rename worktrees in both projects. -7. Send prompts and answer permission/question requests in both projects. -8. Share/unshare B while A is active; exact B directory is used. -9. Load Project Settings A, activate B, save A; B is unchanged. -10. Modify A config externally while draft is dirty; conflict preserves draft. -11. Indexing defaults off for a new project; enabling A does not enable B; repo config cannot enable either. -12. Disable flag while B is active; pinned Local becomes active and B remains registered/suspended. -13. Repeat key routing and identity checks in a remote VS Code window. - -## Rules for the implementation model - -- Work on one slice at a time. -- Read the referenced architecture section before editing. -- Add focused tests before or with behavior changes. -- Use `apply_patch` for manual edits. -- Do not hand-edit generated SDK files. -- Do not raise file-size caps. -- Do not add fallback compatibility code unless the plan explicitly requires the single-project adapter. -- Do not claim a slice complete until its stop gate passes. -- If a required API or ownership decision is missing, stop and update this plan instead of guessing. diff --git a/.kilo/plans/agent-manager-multi-project-shipping-gaps.md b/.kilo/plans/agent-manager-multi-project-shipping-gaps.md deleted file mode 100644 index 8301929bbd..0000000000 --- a/.kilo/plans/agent-manager-multi-project-shipping-gaps.md +++ /dev/null @@ -1,210 +0,0 @@ -# Agent Manager Multi-Project — Shipping Gaps - -Status as of 2026-07-27. The multi-project sidebar renders and works end to end in the -self-test harness (both projects list worktrees/sessions/sections, section creation -persists, worktree delete works, project switching restores selection, live session -upsert verified). Full unit suite green (3444 pass / 0 fail), typecheck, lint, knip, -arch caps all pass. Everything is uncommitted in the worktree. - -This document lists what is still missing, ranked by whether it should block shipping. - -Two audiences matter for the blocking decision: - -- **All users**: the branch changes shared surfaces (legacy sidebar refactor, config - write path, indexing consent, pollers). Regressions here ship to everyone, flag off - or not. -- **Experimental users**: multi-project mode is gated behind - `kilo-code.new.experimental.multiProject`, default off. Rough edges here are - acceptable if they cannot corrupt data. - ---- - -## 1. Blocks shipping (all-user surfaces) - -### 1.0 Empty-state skeleton regression (fixed, must stay fixed) - -`initializeState` and `onRequestState` only called `refreshSessions()` when the -managed state contained at least one session. With zero managed sessions the -backend listing never ran, `sessionsLoaded` never reached the webview, and both -the WORKTREES and SESSIONS sections stayed on skeletons forever (the worktree -gate requires `worktreesLoaded() && sessionsLoaded()`). Any user whose state file -lost its sessions — exactly what the earlier session-persist bugs caused — hit a -permanently empty-looking sidebar. Reproduced in the harness on a worktree-root -workspace and fixed by making both refreshes unconditional. Root-caused via -stage-by-stage init logging on 2026-07-27; do not reintroduce a content-based -guard here. - -### 1.1 Config write path can fail or wipe drafts for existing callers — P0-6 / P1-7 - -The config binding rework requires every `updateConfig` sender to carry a binding id, -and `configUpdateFailed` currently wipes the draft for the failed scope. Any existing -sender without a binding (permission dock, model picker, auto-approve, onboarding) -now fails or loses the user's unsaved edits. - -- Backend half is done: `expected` is optional in the overlay schema, writer, and - handler, so a client without a binding writes unconditionally again instead of - getting a 400. The webview half (draft retention on `configUpdateFailed`, and the - audit of every `updateConfig` sender) is still open. -- Work: the code change is small; the real work is auditing every webview - `updateConfig` sender and classifying it. -- Also: this change is orthogonal to multi-project. Split it into its own commit - (`feat(vscode): config write revision bindings`) so it can be reverted alone. - -### 1.2 Indexing status read silently revokes consent — P0-3 / P0-4 - -Partially addressed: untrusted projects are now filtered out of the consent list -(P0-4), and config scope switching plus project-scoped `indexing.enabled` writes -were restored after the rework had hardcoded the tab to global scope (the earlier -P1-8 gap). The remaining blocker is the read path below. - - -`fetchAndSendIndexingStatus` issues `PUT /indexing/consent` (a write) on a plain -status refresh, defaulting to `enabled: false` for any project not in local -`globalState`. On a fresh machine/profile, the first status read turns indexing off -for users who had it on. A refresh can also target the wrong project -(`requestIndexingStatus` resolves from the current session's directory). Separately, -the Indexing tab lists untrusted projects and would let a user enable indexing for a -repo the trust system has not approved. - -- Fix: read status with a GET; PUT only from the explicit setter; seed consent from - the effective config on first read instead of defaulting false; require an explicit - project on refresh; filter the project list to trusted projects. -- Dependency: needs a read-only status endpoint. If none exists, this escapes into - `packages/opencode` (shared upstream code, needs `kilocode_change` markers) or the - cloud repo. -- Also orthogonal to multi-project; split into its own commit - (`feat(vscode): per-project indexing consent`). - -### 1.3 Land the current work as separate, revertable commits - -Everything currently sits uncommitted in one worktree. The review's recommendation -stands: `git reset --soft origin/main` and stage by path into three commits — -multi-project, config bindings, indexing consent. The multi-project commit message -must not promise per-project config/indexing behavior that lands in the other two. - ---- - -## 2. Should fix soon after (experimental surface, data-integrity relevant) - -### 2.1 Same repo can register twice — P0-5 - -`projectIdFor` hashes the canonical path verbatim while `samePath` folds case, so two -casings of one repo produce two project ids, two contexts, and two state managers -racing to write the same `.kilo/agent-manager.json` (last write wins, worktrees and -sessions vanish). - -- Fix: fold case inside `projectIdFor` on darwin/win32; reject `addProject` when - `samePath` matches any registered root. -- Migration: existing registry keys were built from the old hash, so re-key them on - load or dual-lookup on read. Note `canonicalizePath` already realpaths existing - paths; the fallback branch must fold case too. -- Why not blocking: requires an unusual casing mismatch at registration time, and the - feature is flag-gated. - -### 2.2 Route service is shared across panels but versioned per context — P0-7 - -Two VS Code windows each have their own `ProjectContexts` with independent generation -counters feeding one shared `ProjectRouteService`. Panel B registering a project at -generation 0 while panel A is at 2 unregisters panel A's routes; closing one panel -drops routes the other still needs. - -- Fix: panel-qualified keys (`panelId + projectId + sessionId`), generations issued - by the service, ambiguity computed across all panels. -- Why not blocking: needs two windows running Agent Manager against the same repos. - The fallback already refuses ambiguous raw ids instead of resolving them wrong. -- Note: no two-panel test harness exists today; this fix should create one. - -### 2.3 `gh pr` noise in repos without remotes — Bug 5 - -`gh pr view` fails every 15s per worktree forever, logs before the dedupe, and -`pollOnce` rejections are unhandled. - -- Fix: log after the `lastHash` dedupe; `void this.pollOnce().catch(log)` in - `schedule`; per-root remote probe with a single error emission; skip PR pollers for - remote-less projects in `ProjectPollers.sync`. - ---- - -## 3. UX papercuts in experimental mode (fix opportunistically) - -- **Legacy tabs orphaned on upgrade** (P1-1): `createLocalTabs` migrates persisted - `localSessionIDs` into a `single` bucket that `tabKey()` never reads once the - catalog arrives. Migrate `single` to the pinned project id on first state apply. -- **`restoreProjectTarget` skips tab bookkeeping** (P1-3): a restored session has no - tab. Call `selectLocal()` first, add the tab, then select. -- **`ensurePendingTab` runs before restore** (P1-4): switch adds a "New Session" - draft that restore may contradict. Move it after restore. -- **Per-keystroke state saves**: `setActiveTarget` writes - `.kilo/agent-manager.json` on every selection change. Debounce or persist on - deactivation/dispose only. -- **Untranslated Indexing tab strings** (P1-9), **unused-ish composite id schemes** - (P1-12), **stats messages tagged at emit time** (P1-14), **no presence sync for - background projects** (P1-15), **registry read cache** (P1-16/17), **realpath - syscall churn** (P1-18), **`resolveProjectRoot` process spawning** (P1-19). - ---- - -## 4. Structural debt (schedule, don't block) - -### 4.1 Two sidebar implementations — Arch 1.1 - -Tracked as #12685 (section parity) and #12686 (sidebar drag-and-drop). - -`AgentManagerApp` keeps the legacy `renderBody` (now `SidebarBody.tsx`) and -`ProjectSidebarBody.tsx` as a reduced reimplementation. The reimplementation already -caused one full outage of multi-project mode (missing `DragDropProvider` crashed the -webview render). Missing versus legacy: worktree ordering, drag-and-drop reorder and -move-to-section, grouping, busy/navHint/shortcut badges, section auto-rename, stats -skeletons. - -- Direction: single-project becomes the degenerate case of multi-project (one - implicit pinned project, header row hidden), `SidebarBody.tsx` is deleted. -- Do this last: it churns the same message-stamping code as 2.2, needs per-project - DnD state, and must keep legacy pixel-identical for the default-off population. - Use the visual-regression skill to cover both modes before merging it. - -### 4.2 Ambient project scope via AsyncLocalStorage — Arch 1.3 - -`ProjectScope` plus the `this.state`/`this.context` getters make the target project -invisible at call sites; any continuation escaping ALS silently falls back to the -active project. `provider-lifecycle.ts` shows the intended end state (explicit deps). -Keep threading `ctx` explicitly into the remaining handler groups; add a dev-mode log -in the `context` getter when a project-stamped message resolves without a scope. - -### 4.3 Per-project webview store — Arch 1.2 - -`worktrees()`, `managedSessions()`, `selection()` etc. are single-valued with -`memKey()`/`tabKey()` and two "current project" accessors that disagree during the -switch window. Long-term: one `createProjectStore(projectId)` per project. For now -the gating added to the remember effect contains the known race. - ---- - -## 5. Verification gaps to close before the PR - -- **SSE session upsert** is unit-tested (`upsertSession`, `byDirectory`, fresh-skip - re-post) but not E2E-verified: the harness backend's basic-auth credentials could - not be re-extracted after a window reload, so external session creation was not - exercised live. Verify manually: `kilo` a new session in a registered repo from a - terminal and watch it appear in that project's sidebar. -- **Tab isolation across projects** (per-project buckets) is unit-tested but not - E2E-verified with real sessions open in two projects. -- **Legacy mode parity**: legacy sidebar and tab bar were smoke-tested (render, - select, section create + inline rename), but not the full matrix (delete, DnD - reorder, move-to-section, review tab, terminals). The extraction moved ~700 lines - of JSX; a visual-regression pass over `SidebarBody`/`TabBar` stories is the - cheapest safety net. -- **Harness instability**: the isolated VS Code window crashed repeatedly during this - work. If it keeps failing on the next pass, say so in the PR rather than claiming - coverage that does not exist. - ---- - -## Proposed landing sequence - -1. Quick independent fixes: 2.3 (gh noise), P0-4 (trust filter), 1.1's audit + 1.2's - fallback (config), 1.2's indexing read/write split if the GET endpoint exists. -2. 2.1 (case-fold + registry migration). -3. 2.2 (route service, with a two-panel test harness). -4. 1.1's config/indexing commits split out and merged separately. -5. Arch 1.1 (sidebar collapse) with visual-regression coverage; then the P1 batch. diff --git a/packages/kilo-vscode/AGENTS.md b/packages/kilo-vscode/AGENTS.md index 64fe5fc221..71986c85be 100644 --- a/packages/kilo-vscode/AGENTS.md +++ b/packages/kilo-vscode/AGENTS.md @@ -168,6 +168,12 @@ Agent Manager local worktree sessions use the current shared `kilo serve` proces Extension-side code lives in `src/agent-manager/`, webview code in `webview-ui/agent-manager/`. The webview reuses the sidebar's provider chain and `ChatView` component, adding a `WorktreeModeProvider` and a split layout. +### Multi-project migration + +Multi-project Agent Manager is an incremental migration behind the application-scoped `kilo-code.new.experimental.multiProject` flag (default `false`); flag-off behavior must remain unchanged. The project registry/contexts, per-project state and session routing, project sidebar, sections and drag-and-drop, progress/persistence, and project-targeted worktree creation are implemented. + +It is not yet a complete convergence: audit every operation for explicit project/worktree/session routing, finish immutable project-bound Settings and machine-local indexing consent, harden canonical Git identity and multi-window route ownership, and replace the duplicate `SidebarBody`/`ProjectSidebarBody` implementations with one shared body. Full two-project E2E and legacy-parity coverage is still incomplete. + ## Webview UI (kilo-ui) New webview features must use **`@kilocode/kilo-ui`** components instead of raw HTML elements with inline styles. This is a Solid.js component library built on `@kobalte/core`. From dec5bef7ccd72728fd4810235293fe11f5c0805b Mon Sep 17 00:00:00 2001 From: Rakshith N Date: Fri, 7 Aug 2026 13:37:37 +0530 Subject: [PATCH 16/57] (chore): address bot review comments --- .../tests/skills-settings-responsive.spec.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts b/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts index 6403880ced..5d0330e207 100644 --- a/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts +++ b/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts @@ -32,6 +32,16 @@ async function assertRowContained(row: Locator, card: Locator, label: string) { ) } +async function assertTooltipFitsViewport(content: Locator, label: string, page: Page) { + const tipBox = await content.boundingBox() + const viewport = page.viewportSize()! + expect(tipBox, `${label}: tooltip bounding box`).not.toBeNull() + expect(tipBox!.x, `${label}: tooltip left edge inside viewport`).toBeGreaterThanOrEqual(-1) + expect(tipBox!.x + tipBox!.width, `${label}: tooltip right edge inside viewport`).toBeLessThanOrEqual( + viewport.width + 1, + ) +} + test.describe("skills settings responsive layout", () => { test("folder-path and URL rows stay contained and the × button remains visible at a narrow viewport", async ({ page, @@ -73,12 +83,7 @@ test.describe("skills settings responsive layout", () => { await trigger.hover() const content = page.locator('[data-component="tooltip"]').filter({ hasText: seeded }) await expect(content, `Kilo Tooltip exposes full path on hover: ${seeded}`).toBeVisible() - const tipBox = await content.boundingBox() - const viewport = page.viewportSize()! - expect(tipBox, "path tooltip bounding box").not.toBeNull() - expect(tipBox!.x + tipBox!.width, `path tooltip stays inside viewport: ${seeded}`).toBeLessThanOrEqual( - viewport.width + 1, - ) + await assertTooltipFitsViewport(content, `path tooltip "${seeded}"`, page) } for (const seeded of [SEEDED_URL, SEEDED_URL_2]) { @@ -105,12 +110,7 @@ test.describe("skills settings responsive layout", () => { await trigger.hover() const content = page.locator('[data-component="tooltip"]').filter({ hasText: seeded }) await expect(content, `Kilo Tooltip exposes full URL on hover: ${seeded}`).toBeVisible() - const tipBox = await content.boundingBox() - const viewport = page.viewportSize()! - expect(tipBox, "URL tooltip bounding box").not.toBeNull() - expect(tipBox!.x + tipBox!.width, `URL tooltip stays inside viewport: ${seeded}`).toBeLessThanOrEqual( - viewport.width + 1, - ) + await assertTooltipFitsViewport(content, `URL tooltip "${seeded}"`, page) } for (const [label, card] of [ From 1406d719e36afd6d98d60b9fe54fa79b51e6b294 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Fri, 7 Aug 2026 10:24:46 +0200 Subject: [PATCH 17/57] fix(cli): remove unsupported kilo web command --- .changeset/remove-web-command.md | 5 ++ .../markdoc/partials/cli-commands-table.md | 1 - .../code-with-ai/platforms/cli-reference.md | 15 ----- packages/opencode/src/cli/cmd/web.ts | 61 ------------------- packages/opencode/src/index.ts | 4 +- packages/opencode/src/kilocode/commands.ts | 2 - .../__snapshots__/help-snapshots.test.ts.snap | 21 ------- .../test/cli/help/help-snapshots.test.ts | 1 - packages/opencode/test/kilocode/help.test.ts | 2 - 9 files changed, 7 insertions(+), 105 deletions(-) create mode 100644 .changeset/remove-web-command.md delete mode 100644 packages/opencode/src/cli/cmd/web.ts diff --git a/.changeset/remove-web-command.md b/.changeset/remove-web-command.md new file mode 100644 index 0000000000..8dd97b02fe --- /dev/null +++ b/.changeset/remove-web-command.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Remove unsupported `kilo web` CLI command. diff --git a/packages/kilo-docs/markdoc/partials/cli-commands-table.md b/packages/kilo-docs/markdoc/partials/cli-commands-table.md index 0ac95fd554..d8653a898a 100644 --- a/packages/kilo-docs/markdoc/partials/cli-commands-table.md +++ b/packages/kilo-docs/markdoc/partials/cli-commands-table.md @@ -13,7 +13,6 @@ | `kilo upgrade [target]` | upgrade kilo to the latest or a specific version | | `kilo uninstall` | uninstall kilo and remove all related files | | `kilo serve` | starts a headless kilo server | -| `kilo web` | start kilo server and open web interface | | `kilo models [provider]` | list all available models | | `kilo roll-call ` | batch-test text models matching a filter for connectivity and latency | | `kilo profile` | show Kilo account profile | diff --git a/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md index 0047db658f..9a4ec19735 100644 --- a/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md +++ b/packages/kilo-docs/pages/code-with-ai/platforms/cli-reference.md @@ -659,21 +659,6 @@ Options: --cors additional domains to allow for CORS [array] [default: []] ``` -## kilo web - -``` -start kilo server and open web interface - -Options: - --help Show help [boolean] - --version Show version number [boolean] - --port port to listen on [number] [default: 0] - --hostname hostname to listen on [string] [default: "127.0.0.1"] - --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) [boolean] [default: false] - --mdns-domain custom domain name for mDNS service (default: kilo.local) [string] [default: "kilo.local"] - --cors additional domains to allow for CORS [array] [default: []] -``` - ## kilo models ``` diff --git a/packages/opencode/src/cli/cmd/web.ts b/packages/opencode/src/cli/cmd/web.ts deleted file mode 100644 index 36df40c8c9..0000000000 --- a/packages/opencode/src/cli/cmd/web.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Effect } from "effect" -import { UI } from "../ui" -import { effectCmd } from "../effect-cmd" -import { withNetworkOptions, resolveNetworkOptions } from "../network" -import { Flag } from "@opencode-ai/core/flag/flag" -import open from "open" - -export const WebCommand = effectCmd({ - command: "web", - builder: (yargs) => withNetworkOptions(yargs), - describe: "start kilo server and open web interface", - // Server loads instances per-request via x-kilo-directory header — no - // ambient project InstanceContext needed at startup. - instance: false, // kilocode_change - handler: Effect.fn("Cli.web")(function* (args) { - const { Server } = yield* Effect.promise(() => import("../../server/server")) - if (!Flag.KILO_SERVER_PASSWORD) { - UI.println(UI.Style.TEXT_WARNING_BOLD + "! KILO_SERVER_PASSWORD is not set; server is unsecured.") - } - const opts = yield* resolveNetworkOptions(args) - const server = yield* Effect.promise(() => Server.listen(opts)) - UI.empty() - UI.println(UI.logo(" ")) - UI.empty() - - // kilocode_change start - const urls = server.urls - - UI.println(UI.Style.TEXT_INFO_BOLD + " Local: ", UI.Style.TEXT_NORMAL, urls.local) - if (urls.network) { - UI.println(UI.Style.TEXT_INFO_BOLD + " Network: ", UI.Style.TEXT_NORMAL, urls.network) - } - - if (opts.mdns) { - UI.println(UI.Style.TEXT_INFO_BOLD + " mDNS: ", UI.Style.TEXT_NORMAL, `${opts.mdnsDomain}:${server.port}`) - } - - open(urls.local).catch(() => {}) - // kilocode_change end - - // kilocode_change start - graceful signal shutdown - const { InstanceRuntime } = yield* Effect.promise(() => import("../../project/instance-runtime")) - yield* Effect.promise( - () => - new Promise((resolve) => { - const shutdown = async () => { - try { - await InstanceRuntime.disposeAllInstances() - await server.stop(true) - } finally { - resolve() - } - } - process.once("SIGTERM", shutdown) - process.once("SIGINT", shutdown) - process.once("SIGHUP", shutdown) - }), - ) - // kilocode_change end - }), -}) diff --git a/packages/opencode/src/index.ts b/packages/opencode/src/index.ts index 779f51423a..f23744f03f 100644 --- a/packages/opencode/src/index.ts +++ b/packages/opencode/src/index.ts @@ -22,7 +22,7 @@ import { AttachCommand } from "./cli/cmd/attach" import { TuiThreadCommand } from "./cli/cmd/tui" import { AcpCommand } from "./cli/cmd/acp" import { EOL } from "os" -import { WebCommand } from "./cli/cmd/web" +// kilocode_change - upstream web command intentionally omitted; Kilo does not ship an embedded web UI import { PrCommand } from "./cli/cmd/pr" import { SessionCommand } from "./cli/cmd/session" import { DbCommand } from "./cli/cmd/db" @@ -106,7 +106,7 @@ let cli = yargs(args) // kilocode_change .command(UpgradeCommand) .command(UninstallCommand) .command(ServeCommand) - .command(WebCommand) + // kilocode_change - upstream web command intentionally omitted .command(ModelsCommand) .command(StatsCommand) .command(ExportCommand) diff --git a/packages/opencode/src/kilocode/commands.ts b/packages/opencode/src/kilocode/commands.ts index 27f6316c5e..fff41dc464 100644 --- a/packages/opencode/src/kilocode/commands.ts +++ b/packages/opencode/src/kilocode/commands.ts @@ -13,7 +13,6 @@ import { AgentCommand } from "../cli/cmd/agent" import { UpgradeCommand } from "../cli/cmd/upgrade" import { UninstallCommand } from "../cli/cmd/uninstall" import { ServeCommand } from "../cli/cmd/serve" -import { WebCommand } from "../cli/cmd/web" import { ModelsCommand } from "../cli/cmd/models" import { StatsCommand } from "../cli/cmd/stats" import { ExportCommand } from "../cli/cmd/export" @@ -61,7 +60,6 @@ export const commands = [ UpgradeCommand, UninstallCommand, ServeCommand, - WebCommand, ModelsCommand, RollCallCommand, ProfileCommand, diff --git a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap index 7a5c5b19cd..241f8bd376 100644 --- a/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap +++ b/packages/opencode/test/cli/help/__snapshots__/help-snapshots.test.ts.snap @@ -235,27 +235,6 @@ Options: " `; -exports[`Kilo CLI help-text snapshots every documented command emits stable help text: kilo web --help 1`] = ` -"kilo web - -start kilo server and open web interface - -Options: - -h, --help show help [boolean] - -v, --version show version number [boolean] - --print-logs print logs to stderr [boolean] - --log-level log level [string] [choices: "DEBUG", "INFO", "WARN", "ERROR"] - --pure run without external plugins [boolean] - --port port to listen on [number] [default: 0] - --hostname hostname to listen on [string] [default: "127.0.0.1"] - --mdns enable mDNS service discovery (defaults hostname to 0.0.0.0) - [boolean] [default: false] - --mdns-domain custom domain name for mDNS service (default: kilo.local) - [string] [default: "kilo.local"] - --cors additional domains to allow for CORS [array] [default: []] -" -`; - exports[`Kilo CLI help-text snapshots every documented command emits stable help text: kilo models --help 1`] = ` "kilo models [provider] diff --git a/packages/opencode/test/cli/help/help-snapshots.test.ts b/packages/opencode/test/cli/help/help-snapshots.test.ts index 2f28861d4e..f870c1abea 100644 --- a/packages/opencode/test/cli/help/help-snapshots.test.ts +++ b/packages/opencode/test/cli/help/help-snapshots.test.ts @@ -66,7 +66,6 @@ const TOP_LEVEL = [ "upgrade", "uninstall", "serve", - "web", "models", "stats", "export", diff --git a/packages/opencode/test/kilocode/help.test.ts b/packages/opencode/test/kilocode/help.test.ts index bb1179b38a..264fbb8984 100644 --- a/packages/opencode/test/kilocode/help.test.ts +++ b/packages/opencode/test/kilocode/help.test.ts @@ -12,7 +12,6 @@ import { AgentCommand } from "../../src/cli/cmd/agent" import { UpgradeCommand } from "../../src/cli/cmd/upgrade" import { UninstallCommand } from "../../src/cli/cmd/uninstall" import { ServeCommand } from "../../src/cli/cmd/serve" -import { WebCommand } from "../../src/cli/cmd/web" import { ModelsCommand } from "../../src/cli/cmd/models" import { StatsCommand } from "../../src/cli/cmd/stats" import { ExportCommand } from "../../src/cli/cmd/export" @@ -64,7 +63,6 @@ const commands = [ UpgradeCommand, UninstallCommand, ServeCommand, - WebCommand, ModelsCommand, StatsCommand, ExportCommand, From 0485e361c6fcde687923daf2d42a1761bd315366 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 10:27:53 +0200 Subject: [PATCH 18/57] perf(agent-manager): speed up worktree session startup --- .changeset/faster-agent-manager-worktrees.md | 5 + packages/kilo-vscode/package.json | 6 + .../src/agent-manager/WorktreeManager.ts | 34 +++-- .../agent-manager/provider-multi-version.ts | 132 +++++++++++------- .../src/agent-manager/worktree-importer.ts | 3 + .../tests/unit/agent-manager-arch.test.ts | 2 +- .../tests/unit/provider-multi-version.test.ts | 67 +++++++++ .../tests/unit/sandbox-bootstrap.test.ts | 8 +- 8 files changed, 190 insertions(+), 67 deletions(-) create mode 100644 .changeset/faster-agent-manager-worktrees.md create mode 100644 packages/kilo-vscode/tests/unit/provider-multi-version.test.ts diff --git a/.changeset/faster-agent-manager-worktrees.md b/.changeset/faster-agent-manager-worktrees.md new file mode 100644 index 0000000000..259a5413f7 --- /dev/null +++ b/.changeset/faster-agent-manager-worktrees.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Start Agent Manager worktree sessions faster by prefetching base branches, reducing workspace file-watcher load, and overlapping independent multi-session setup. diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 1ce8210b66..acca2e4b35 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -59,6 +59,12 @@ ], "main": "./dist/extension.js", "contributes": { + "configurationDefaults": { + "files.watcherExclude": { + "**/.kilo/worktrees/**": true, + "**/.kilocode/worktrees/**": true + } + }, "taskDefinitions": [ { "type": "kilo-worktree-setup", diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts index 4879b32af3..763484584e 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts @@ -150,6 +150,13 @@ export class WorktreeManager { return this.withGitLock(() => this.createWorktreeImpl(params)) } + /** Start the remote base refresh before creation reaches the git mutex. */ + async prefetchBase(branch?: string): Promise { + await this.ensureMigrated() + const base = branch || (await this.defaultBranch()) + await this.withGitLock(() => this.refreshBase(base)) + } + async renameBranch(worktreePath: string, current: string, requested: string): Promise { await this.ensureMigrated() return this.withGitLock(() => this.renameBranchImpl(worktreePath, current, requested)) @@ -762,20 +769,14 @@ export class WorktreeManager { source: "remote", } } + WorktreeManager.fetchCache.delete(cacheKey) } // Either not cached or cache is stale - do the fetch. // Use non-interactive env to prevent SSH passphrase popups. onProgress?.("fetching", `Fetching ${remote}/${branch}...`) try { - // Only opt into simple-git's allowUnsafeSshCommand when the SSH command - // is the fixed value Kilo injects — never for an inherited one, which - // could be attacker-controlled. - const env = nonInteractiveEnv() - await simpleGit(this.root, { unsafe: { allowUnsafeSshCommand: isKiloOwnedSshCommand(env) } }) - .env(env) - .fetch(remote, branch, { "--quiet": null, "--no-tags": null }) - WorktreeManager.fetchCache.set(cacheKey, Date.now()) + await this.refreshBase(branch, remote) if (await this.refExistsLocally(`${remote}/${branch}`)) { return { ref: `${remote}/${branch}`, @@ -830,6 +831,23 @@ export class WorktreeManager { throw new Error(`Could not resolve start point for branch "${branch}"`) } + private async refreshBase(branch: string, requested?: string): Promise { + const remote = requested ?? (await this.resolveRemote()) + if (!remote) return + const key = `${this.root}:${remote}:${branch}` + const cached = WorktreeManager.fetchCache.get(key) + if (cached && Date.now() - cached < WorktreeManager.FETCH_CACHE_TTL) return + + // Only opt into simple-git's allowUnsafeSshCommand when the SSH command + // is the fixed value Kilo injects — never for an inherited one, which + // could be attacker-controlled. + const env = nonInteractiveEnv() + await simpleGit(this.root, { unsafe: { allowUnsafeSshCommand: isKiloOwnedSshCommand(env) } }) + .env(env) + .fetch(remote, branch, { "--quiet": null, "--no-tags": null }) + WorktreeManager.fetchCache.set(key, Date.now()) + } + /** * Resolve the primary remote name for this repo. * Uses `GitOps.resolveRemote` when available, otherwise checks for "origin". diff --git a/packages/kilo-vscode/src/agent-manager/provider-multi-version.ts b/packages/kilo-vscode/src/agent-manager/provider-multi-version.ts index 41cc02049e..59b6aba1af 100644 --- a/packages/kilo-vscode/src/agent-manager/provider-multi-version.ts +++ b/packages/kilo-vscode/src/agent-manager/provider-multi-version.ts @@ -6,6 +6,9 @@ import { versionedName } from "./branch-name" import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version" import { ensureSandbox } from "./sandbox-bootstrap" import type { LifecycleHost } from "./provider-lifecycle" +import { Semaphore } from "./semaphore" + +const PROVISION_CONCURRENCY = 2 /** * Multi-version creation needs the lifecycle capabilities plus three provider @@ -19,8 +22,8 @@ export interface MultiVersionHost extends LifecycleHost { } /** - * Create N worktrees with one session each (optionally one model per version), - * then fan the initial prompt out to every created session. State is reached + * Create N worktrees, provision their sessions with bounded concurrency, then + * send each initial prompt as soon as that session is ready. State is reached * through the project context; everything else goes through the host. */ export async function createMultiVersion( @@ -57,26 +60,49 @@ export async function createMultiVersion( groupId, }) - // Phase 1: Create all worktrees + sessions first + // Phase 1: finish every shared-repository Git mutation before setup scripts + // or agents can run their own Git commands in the new worktrees. const created: CreatedVersion[] = [] - for (let i = 0; i < versions; i++) { - const version = await createVersion(ctx, host, { - index: i, - versions, - groupId, - baseBranch, - branchName, - worktreeName, - models, - providerID, - modelID, - sandbox: msg.sandbox, - }) - if (!version) continue - created.push(version) + const specs = Array.from({ length: versions }, (_, index) => ({ + index, + versions, + groupId, + baseBranch, + branchName, + worktreeName, + models, + providerID, + modelID, + sandbox: msg.sandbox, + })) + const prepared: PreparedVersion[] = [] + for (const spec of specs) { + const version = await prepareVersion(host, spec) + if (version) prepared.push(version) + } + + // Phase 2: Git creation is complete, so independent setup/session pipelines + // can overlap without racing the shared worktree metadata mutation. + const provision = async (version: PreparedVersion) => { + const ready = await provisionVersion(ctx, host, version) + if (!ready) return + created.push(ready) + + sendInitialPrompt( + host, + ctx.id, + ready, + models, + { providerID, modelID }, + { + text, + agent, + variant: msg.variant, + files, + }, + ) - // Update progress host.post({ type: "agentManager.multiVersionProgress", projectId: ctx.id, @@ -87,15 +113,8 @@ export async function createMultiVersion( }) } - // Phase 2: Send the initial prompt to all sessions, or clear busy state if no text. - await sendInitialPrompts( - host, - ctx.id, - created, - models, - { providerID, modelID }, - { text, agent, variant: msg.variant, files }, - ) + const gate = new Semaphore(PROVISION_CONCURRENCY) + await Promise.all(prepared.map((version) => gate.run(() => provision(version)))) // Notify completion host.post({ @@ -128,12 +147,13 @@ interface VersionSpec { sandbox: boolean | undefined } -/** Create one version's worktree + session and wire it into state and the webview. */ -async function createVersion( - ctx: ProjectContext, - host: MultiVersionHost, - spec: VersionSpec, -): Promise { +interface PreparedVersion { + spec: VersionSpec + wt: NonNullable>> +} + +/** Create one version's worktree while the shared-repository Git barrier is active. */ +async function prepareVersion(host: MultiVersionHost, spec: VersionSpec): Promise { host.log(`Creating worktree ${spec.index + 1}/${spec.versions}`) const version = versionedName(spec.branchName || spec.worktreeName, spec.index, spec.versions) @@ -148,6 +168,16 @@ async function createVersion( host.log(`Failed to create worktree for version ${spec.index + 1}`) return null } + return { spec, wt } +} + +/** Set up one prepared worktree, create its session, and expose it to the UI. */ +async function provisionVersion( + ctx: ProjectContext, + host: MultiVersionHost, + prepared: PreparedVersion, +): Promise { + const { spec, wt } = prepared await host.runSetup(wt.result.path, wt.result.branch, wt.worktree.id) @@ -240,11 +270,11 @@ async function reconcileSandbox( } } -/** Fan the initial prompt out to every created session, throttled between sends. */ -async function sendInitialPrompts( +/** Send one version's initial prompt as soon as its session is ready. */ +function sendInitialPrompt( host: MultiVersionHost, projectId: string, - created: CreatedVersion[], + created: CreatedVersion, models: VersionSpec["models"], resolved: { providerID: string | undefined; modelID: string | undefined }, input: { @@ -253,22 +283,16 @@ async function sendInitialPrompts( variant: string | undefined files: Extract["files"] }, -): Promise { - const messages = buildInitialMessages(created, models, resolved, input.text, input.agent, input.variant, input.files) - for (let i = 0; i < messages.length; i++) { - const msg = messages[i]! - if (input.text) { - host.log(`Sending initial message to version ${i + 1} (session=${msg.sessionId})`) - host.promptName({ - sessionID: msg.sessionId, - text: input.text, - providerID: msg.providerID, - modelID: msg.modelID, - }) - } - host.post({ type: "agentManager.sendInitialMessage", projectId, ...msg }) - if (input.text && i < messages.length - 1) { - await new Promise((resolve) => setTimeout(resolve, 300)) - } +): void { + const msg = buildInitialMessages([created], models, resolved, input.text, input.agent, input.variant, input.files)[0]! + if (input.text) { + host.log(`Sending initial message to version ${created.versionIndex + 1} (session=${msg.sessionId})`) + host.promptName({ + sessionID: msg.sessionId, + text: input.text, + providerID: msg.providerID, + modelID: msg.modelID, + }) } + host.post({ type: "agentManager.sendInitialMessage", projectId, ...msg }) } diff --git a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts index c44d7d9cf9..c1a55a8ba8 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts @@ -30,6 +30,9 @@ export class WorktreeImporter { try { const result = await manager.listBranches() + void manager + .prefetchBase(result.defaultBranch) + .catch((err) => this.host.log("Failed to prefetch base branch:", err)) const checked = await manager.checkedOutBranches() const branches = result.branches.map((branch) => ({ ...branch, diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index a9c2f4b355..7d0b8b1c13 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -461,7 +461,7 @@ describe("Agent Manager Provider — onMessage routing", () => { const lifecycle = source.getProject().addSourceFileAtPath(path.join(ROOT, "src/agent-manager", module)) const fn = lifecycle.getFunction(delegated[1]!) expect(fn, `delegated function ${delegated[1]} not found in ${module}`).toBeTruthy() - // The multi-version flow spans phase helpers (createVersion, sendInitialPrompts), + // The multi-version flow spans prepare, provision, and initial-prompt helpers, // so ordering assertions need the whole module, not just the orchestrator. if (delegated[1] === "createMultiVersion") return lifecycle.getText() return fn!.getText() diff --git a/packages/kilo-vscode/tests/unit/provider-multi-version.test.ts b/packages/kilo-vscode/tests/unit/provider-multi-version.test.ts new file mode 100644 index 0000000000..df0ec2d53b --- /dev/null +++ b/packages/kilo-vscode/tests/unit/provider-multi-version.test.ts @@ -0,0 +1,67 @@ +import { describe, expect, it, mock } from "bun:test" +import type { Session } from "@kilocode/sdk/v2/client" +import { createMultiVersion, type MultiVersionHost } from "../../src/agent-manager/provider-multi-version" +import type { ProjectContext } from "../../src/agent-manager/project/context" +import type { CreateWorktreeOnDiskResult } from "../../src/agent-manager/worktree-create" + +describe("multi-version provisioning", () => { + it("finishes git creation before provisioning at bounded concurrency", async () => { + const flow: string[] = [] + const gates = [Promise.withResolvers(), Promise.withResolvers()] + const entered = [Promise.withResolvers(), Promise.withResolvers()] + const state = { addSession: mock(() => {}), armAutoName: mock(() => {}) } + const ctx = { + id: "project-1", + stateManager: () => state, + peekState: () => state, + worktreeManager: () => ({ removeWorktree: mock(async () => {}) }), + } as unknown as ProjectContext + const host = { + log: mock(() => {}), + post: mock((msg: { type: string; sessionId?: string }) => { + if (msg.type === "agentManager.sendInitialMessage") flow.push(`prompt:${msg.sessionId}`) + }), + createOnDisk: mock(async (opts: { branchName?: string }) => { + const index = opts.branchName?.endsWith("_v2") ? 1 : opts.branchName?.endsWith("_v3") ? 2 : 0 + flow.push(`git:${index}`) + return { + worktree: { id: `wt-${index}` }, + result: { path: `/repo/wt-${index}`, branch: `branch-${index}`, parentBranch: "main" }, + } as CreateWorktreeOnDiskResult + }), + runSetup: mock(async (dir: string) => { + const index = Number(dir.at(-1)!) + flow.push(`setup:${index}`) + if (index < 2) { + entered[index]?.resolve() + await gates[index]?.promise + } + }), + createSession: mock(async (dir: string) => ({ id: `session-${dir.at(-1)!}` }) as Session), + autoName: () => ({ enabled: false }), + register: mock(() => {}), + notifyReady: mock(() => {}), + sessions: { register: mock(() => {}) }, + promptName: mock(() => {}), + capture: mock(() => {}), + error: mock(() => {}), + } as unknown as MultiVersionHost + + const pending = createMultiVersion(ctx, host, { + type: "agentManager.createMultiVersion", + text: "Fix it", + branchName: "fix-it", + versions: 3, + }) + await Promise.all(entered.map((entry) => entry.promise)) + + expect(flow.slice(0, 5)).toEqual(["git:0", "git:1", "git:2", "setup:0", "setup:1"]) + expect(flow).not.toContain("setup:2") + + gates.forEach((gate) => gate.resolve()) + await pending + + expect(flow).toContain("setup:2") + expect(flow.filter((event) => event.startsWith("prompt:"))).toHaveLength(3) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/sandbox-bootstrap.test.ts b/packages/kilo-vscode/tests/unit/sandbox-bootstrap.test.ts index 445192eeff..7d7a02172d 100644 --- a/packages/kilo-vscode/tests/unit/sandbox-bootstrap.test.ts +++ b/packages/kilo-vscode/tests/unit/sandbox-bootstrap.test.ts @@ -98,8 +98,8 @@ describe("Agent Manager sandbox startup", () => { ) test("reconciles before exposing or prompting the session", () => { - // In createVersion the sandbox gate runs before the session is exposed. - const start = flow.indexOf("async function createVersion") + // In provisionVersion the sandbox gate runs before the session is exposed. + const start = flow.indexOf("async function provisionVersion") const version = flow.slice(start, flow.indexOf("\n/**", start + 1)) const gate = version.indexOf("await reconcileSandbox") const register = version.indexOf("host.register", gate) @@ -122,8 +122,8 @@ describe("Agent Manager sandbox startup", () => { expect(abort).toBeGreaterThan(discard) // The created sessions feed the initial prompt phase. - const prompts = flow.slice(flow.indexOf("async function sendInitialPrompts")) - expect(prompts).toContain("buildInitialMessages(created") + const prompts = flow.slice(flow.indexOf("function sendInitialPrompt")) + expect(prompts).toContain("buildInitialMessages([created]") expect(prompts).toContain('type: "agentManager.sendInitialMessage"') }) From 620bb927be08cb1e6930fe6f4227bc26dc9b4d75 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 10:30:33 +0200 Subject: [PATCH 19/57] fix(agent-manager): keep header actions resize safe --- .../webview-ui/agent-manager/agent-manager.css | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index fabf666ad8..88adf06270 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -281,15 +281,14 @@ html[data-theme="kilo-vscode"] .am-project-body .am-section-header { padding-left: 6px; + padding-right: 6px; } /* This heading sits outside the projects list, so it needs its own inset to land on the same glyph line as the rows inside the list. */ .am-projects > .am-section-header { padding-left: 2px; - /* The project toolbar has one more action than the worktree toolbar. Extend - its row so both toolbars start on the same leading action column. */ - margin-right: -14px; + padding-right: 2px; } .am-section-label { @@ -331,7 +330,11 @@ html[data-theme="kilo-vscode"] .am-project-actions { display: flex; align-items: center; - gap: 2px; + justify-content: space-between; + /* The project row needs 20px + 24px + 20px; the worktree row uses the + remaining space for its split-button and settings gap. */ + width: 64px; + gap: 0; } .am-project { From db64768d5bcc5e25fdc0d968c35febe2289492e1 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 10:42:52 +0200 Subject: [PATCH 20/57] fix(agent-manager): prefetch configured base branch --- .../kilo-vscode/src/agent-manager/worktree-importer.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts index c1a55a8ba8..59cad84055 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts @@ -30,8 +30,11 @@ export class WorktreeImporter { try { const result = await manager.listBranches() + const state = this.host.state() + const configured = state?.getDefaultBaseBranch() + const base = configured && result.branches.some((branch) => branch.name === configured) ? configured : result.defaultBranch void manager - .prefetchBase(result.defaultBranch) + .prefetchBase(base) .catch((err) => this.host.log("Failed to prefetch base branch:", err)) const checked = await manager.checkedOutBranches() const branches = result.branches.map((branch) => ({ @@ -39,8 +42,6 @@ export class WorktreeImporter { isCheckedOut: checked.has(branch.name), })) - const state = this.host.state() - const configured = state?.getDefaultBaseBranch() if (state && configured && !branches.some((branch) => branch.name === configured)) { this.host.log(`Default base branch "${configured}" no longer exists, clearing`) state.setDefaultBaseBranch(undefined) From cfaa224f3a63d6270692cbd8facb1b269137fdf8 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 10:51:10 +0200 Subject: [PATCH 21/57] style(agent-manager): format base branch prefetch --- packages/kilo-vscode/src/agent-manager/worktree-importer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts index 59cad84055..439bc8c3e6 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts @@ -32,7 +32,8 @@ export class WorktreeImporter { const result = await manager.listBranches() const state = this.host.state() const configured = state?.getDefaultBaseBranch() - const base = configured && result.branches.some((branch) => branch.name === configured) ? configured : result.defaultBranch + const base = + configured && result.branches.some((branch) => branch.name === configured) ? configured : result.defaultBranch void manager .prefetchBase(base) .catch((err) => this.host.log("Failed to prefetch base branch:", err)) From cccc9a52c647d904b37acff4ec5adbacb8269a7f Mon Sep 17 00:00:00 2001 From: Rakshith N Date: Fri, 7 Aug 2026 14:28:35 +0530 Subject: [PATCH 22/57] fix(vscode): allow 8px viewport slack for Kobalte tooltip overflowPadding --- .../kilo-vscode/tests/skills-settings-responsive.spec.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts b/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts index 5d0330e207..4c66eb9fd6 100644 --- a/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts +++ b/packages/kilo-vscode/tests/skills-settings-responsive.spec.ts @@ -36,9 +36,12 @@ async function assertTooltipFitsViewport(content: Locator, label: string, page: const tipBox = await content.boundingBox() const viewport = page.viewportSize()! expect(tipBox, `${label}: tooltip bounding box`).not.toBeNull() - expect(tipBox!.x, `${label}: tooltip left edge inside viewport`).toBeGreaterThanOrEqual(-1) + // Kobalte's PopperRoot defaults to overflowPadding: 8, so the floating + // tooltip is allowed to extend up to 8px past each viewport edge before + // the shift middleware stops nudging it. + expect(tipBox!.x, `${label}: tooltip left edge inside viewport`).toBeGreaterThanOrEqual(-9) expect(tipBox!.x + tipBox!.width, `${label}: tooltip right edge inside viewport`).toBeLessThanOrEqual( - viewport.width + 1, + viewport.width + 9, ) } From 83d8e039d76046b5e60c0d276e561af0221dc04e Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 11:07:40 +0200 Subject: [PATCH 23/57] perf(agent-manager): cache git preflight checks and format base prefetch --- packages/kilo-vscode/src/agent-manager/WorktreeManager.ts | 8 ++++++++ .../kilo-vscode/src/agent-manager/worktree-importer.ts | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts index 763484584e..a28e157a4e 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts @@ -121,6 +121,8 @@ export class WorktreeManager { // Key: `${root}:${remote}:${branch}`, Value: timestamp when fetch was done private static fetchCache = new Map() private static readonly FETCH_CACHE_TTL = 60_000 // 1 minute + private static gitAvailable = false + private static lfsAvailable: boolean | undefined private withGitLock(fn: () => Promise): Promise { const key = this.root @@ -183,9 +185,12 @@ export class WorktreeManager { } private async ensureGitAvailable(): Promise { + if (WorktreeManager.gitAvailable) return try { await execWithShellEnv("git", ["--version"]) + WorktreeManager.gitAvailable = true } catch (error) { + WorktreeManager.gitAvailable = false if (error instanceof Error && "code" in error && (error as NodeJS.ErrnoException).code === "ENOENT") { throw new Error( "Git is not installed or not found in PATH. Please install Git (https://git-scm.com) and restart VS Code.", @@ -912,10 +917,13 @@ export class WorktreeManager { } async checkLfsAvailable(): Promise { + if (WorktreeManager.lfsAvailable) return true try { await execWithShellEnv("git", ["lfs", "version"], { cwd: this.root, timeout: 5000 }) + WorktreeManager.lfsAvailable = true return true } catch { + WorktreeManager.lfsAvailable = false // git-lfs not installed return false } diff --git a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts index 439bc8c3e6..0324ce6e1b 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts @@ -34,9 +34,7 @@ export class WorktreeImporter { const configured = state?.getDefaultBaseBranch() const base = configured && result.branches.some((branch) => branch.name === configured) ? configured : result.defaultBranch - void manager - .prefetchBase(base) - .catch((err) => this.host.log("Failed to prefetch base branch:", err)) + void manager.prefetchBase(base).catch((err) => this.host.log("Failed to prefetch base branch:", err)) const checked = await manager.checkedOutBranches() const branches = result.branches.map((branch) => ({ ...branch, From 454cb934a04c2b62d2cbe38715e4f8f2a2c967d0 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 11:24:51 +0200 Subject: [PATCH 24/57] feat(vscode): sync model selector with slash command overrides --- .changeset/command-model-selector-sync.md | 5 +++ .../kilo-vscode/src/kilo-provider/commands.ts | 18 ++++++--- .../tests/unit/prompt-send-contract.test.ts | 9 +++++ .../tests/unit/use-slash-command.test.ts | 27 +++++++++++++ .../src/components/chat/PromptInput.tsx | 5 +++ .../webview-ui/src/context/session.tsx | 40 ++++++++++++++----- .../webview-ui/src/types/messages/agents.ts | 3 ++ 7 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 .changeset/command-model-selector-sync.md diff --git a/.changeset/command-model-selector-sync.md b/.changeset/command-model-selector-sync.md new file mode 100644 index 0000000000..cd64ab6f90 --- /dev/null +++ b/.changeset/command-model-selector-sync.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Update the active model, mode, and thinking selectors when executing a slash command with configured overrides. diff --git a/packages/kilo-vscode/src/kilo-provider/commands.ts b/packages/kilo-vscode/src/kilo-provider/commands.ts index d641fff1f3..822ef45ba2 100644 --- a/packages/kilo-vscode/src/kilo-provider/commands.ts +++ b/packages/kilo-vscode/src/kilo-provider/commands.ts @@ -13,12 +13,18 @@ export async function loadCommands(client: KiloClient, dir: string): Promise client.command.list({ directory: dir }, { throwOnError: true })).then(({ data }) => ({ type: "commandsLoaded", - commands: data.map((cmd) => ({ - name: cmd.name, - description: cmd.description, - source: cmd.source, - hints: cmd.hints, - })), + commands: data.map((cmd) => { + const item = cmd as typeof cmd & { variant?: string } + return { + name: item.name, + description: item.description, + agent: item.agent, + model: item.model, + variant: item.variant, + source: item.source, + hints: item.hints, + } + }), })) promises.set(dir, promise) diff --git a/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts b/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts index 8cce494525..f94736a960 100644 --- a/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts +++ b/packages/kilo-vscode/tests/unit/prompt-send-contract.test.ts @@ -78,6 +78,15 @@ describe("sendCommand dismisses pending tool requests", () => { it("rejects questions before sending", () => { expect(body).toContain("dismissQuestion") }) + + it("applies model, agent, and variant overrides when provided by a command", () => { + expect(body).toContain("if (overrides?.agent)") + expect(body).toContain("selectAgent(overrides.agent, scope)") + expect(body).toContain("if (overrides?.model)") + expect(body).toContain("selectModel(parsed.providerID, parsed.modelID, scope)") + expect(body).toContain("if (overrides?.variant)") + expect(body).toContain("selectVariant(overrides.variant, scope)") + }) }) describe("static command completion contract", () => { diff --git a/packages/kilo-vscode/tests/unit/use-slash-command.test.ts b/packages/kilo-vscode/tests/unit/use-slash-command.test.ts index 710ca744be..03862380fe 100644 --- a/packages/kilo-vscode/tests/unit/use-slash-command.test.ts +++ b/packages/kilo-vscode/tests/unit/use-slash-command.test.ts @@ -188,4 +188,31 @@ describe("useSlashCommand sandbox action", () => { expect(ctx.slash.results()[0]?.description).toBe("Toggle sandbox") ctx.dispose() }) + + it("preserves model, agent, and variant metadata on loaded server commands", () => { + const ctx = setup(() => {}) + + ctx.fire({ + type: "commandsLoaded", + commands: [ + { + name: "ship", + description: "Ship PR", + agent: "code", + model: "openai/gpt-5.6-luna-fast", + variant: "xhigh", + hints: ["deploy"], + }, + ], + }) + + ctx.slash.onInput("/ship", 5) + const matches = ctx.slash.results() + expect(matches).toHaveLength(1) + expect(matches[0]?.name).toBe("ship") + expect(matches[0]?.agent).toBe("code") + expect(matches[0]?.model).toBe("openai/gpt-5.6-luna-fast") + expect(matches[0]?.variant).toBe("xhigh") + ctx.dispose() + }) }) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 075b925b73..da9e7789a8 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -1239,6 +1239,11 @@ export const PromptInput: Component = (props) => { pendingId, context, origin ?? null, + { + agent: matched.agent, + model: matched.model, + variant: matched.variant, + }, ) } else { session.sendMessage(message, sel?.providerID, sel?.modelID, attachments, pendingId, context, data, origin ?? null) diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index 1e09519fa5..8831d7edfb 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -283,6 +283,7 @@ interface SessionContextValue { draftID?: string, context?: string, origin?: string | null, + overrides?: { agent?: string; model?: string; variant?: string }, ) => void abort: () => void compact: () => void @@ -2352,31 +2353,51 @@ export const SessionProvider: ParentComponent = (props) => { draftID?: string, context?: string, origin?: string | null, + overrides?: { agent?: string; model?: string; variant?: string }, ) { if (!server.isConnected()) { console.warn("[Kilo New] Cannot send command: not connected") return } - // Cloud previews need import-then-command; post importAndSend with command metadata const sid = origin === undefined ? currentSessionID() : (origin ?? undefined) - const selection = providerID && modelID ? { providerID, modelID } : selected(sid) - recordModelUsage(selection?.providerID, selection?.modelID) + const effectiveDraftID = !sid && !draftID ? crypto.randomUUID() : draftID + const scope = effectiveDraftID ?? sid + if (!sid && !draftID && effectiveDraftID) agentDrafts.seed(effectiveDraftID) + + if (overrides?.agent) { + selectAgent(overrides.agent, scope) + } + if (overrides?.model) { + const parsed = parseModelString(overrides.model) + if (parsed) { + selectModel(parsed.providerID, parsed.modelID, scope) + } + } + if (overrides?.variant) { + selectVariant(overrides.variant, scope) + } + + const effectiveSelection = selected(scope) + const effectiveProvider = effectiveSelection?.providerID ?? providerID + const effectiveModel = effectiveSelection?.modelID ?? modelID + recordModelUsage(effectiveProvider, effectiveModel) + + // Cloud previews need import-then-command; post importAndSend with command metadata const preview = sid?.startsWith("cloud:") ? sid.slice("cloud:".length) : origin === undefined ? cloudPreviewId() : null if (preview) { - const scope = draftID ?? sid const agent = promptAgent(scope) vscode.postMessage({ type: "importAndSend", cloudSessionId: preview, text: `/${command} ${args}`.trim(), messageID: Identifier.ascending("message"), - providerID, - modelID, + providerID: effectiveProvider, + modelID: effectiveModel, agent, variant: currentVariant(scope), files, @@ -2393,9 +2414,6 @@ export const SessionProvider: ParentComponent = (props) => { dismissQuestion(q.id) } - const effectiveDraftID = !sid && !draftID ? crypto.randomUUID() : draftID - const scope = effectiveDraftID ?? sid - if (!sid && !draftID && effectiveDraftID) agentDrafts.seed(effectiveDraftID) if (scope) { clearClose(scope) addOptimistic(scope, messageID, `/${command} ${args}`.trim(), files) @@ -2414,8 +2432,8 @@ export const SessionProvider: ParentComponent = (props) => { messageID, sessionID: sid, draftID: effectiveDraftID, - providerID, - modelID, + providerID: effectiveProvider, + modelID: effectiveModel, agent, variant: currentVariant(scope), files, diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/agents.ts b/packages/kilo-vscode/webview-ui/src/types/messages/agents.ts index 4079413853..90a6940818 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/agents.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/agents.ts @@ -11,6 +11,9 @@ export interface SkillInfo { export interface SlashCommandInfo { name: string description?: string + agent?: string + model?: string + variant?: string source?: "command" | "mcp" | "skill" hints: string[] } From 40121d832591d743cd360482e4f19e21a353310a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 11:51:34 +0200 Subject: [PATCH 25/57] fix(vscode): compress speech-to-text audio to AAC to prevent payload errors --- .changeset/compressed-speech-to-text-audio.md | 5 ++ .../kilo-vscode/src/speech-to-text/capture.ts | 72 ++++++++++--------- .../tests/unit/speech-to-text-capture.test.ts | 71 ++++++++++++++++-- 3 files changed, 109 insertions(+), 39 deletions(-) create mode 100644 .changeset/compressed-speech-to-text-audio.md diff --git a/.changeset/compressed-speech-to-text-audio.md b/.changeset/compressed-speech-to-text-audio.md new file mode 100644 index 0000000000..fee2a9166d --- /dev/null +++ b/.changeset/compressed-speech-to-text-audio.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Compress speech-to-text audio input to AAC format across macOS, Linux, and Windows to prevent payload size errors on long recordings. diff --git a/packages/kilo-vscode/src/speech-to-text/capture.ts b/packages/kilo-vscode/src/speech-to-text/capture.ts index cbf5c54848..e8b6125107 100644 --- a/packages/kilo-vscode/src/speech-to-text/capture.ts +++ b/packages/kilo-vscode/src/speech-to-text/capture.ts @@ -24,7 +24,7 @@ type Recording = Input & { type Audio = { data: string - format: "wav" + format: "m4a" model: string language?: string } @@ -39,12 +39,10 @@ ObjC.import("AVFoundation") ObjC.import("Foundation") function run(args) { const settings = $.NSMutableDictionary.alloc.init - settings.setObjectForKey($.NSNumber.numberWithUnsignedInt(1819304813), $.AVFormatIDKey) + settings.setObjectForKey($.NSNumber.numberWithUnsignedInt(1633772320), $.AVFormatIDKey) settings.setObjectForKey($.NSNumber.numberWithDouble(16000), $.AVSampleRateKey) settings.setObjectForKey($.NSNumber.numberWithInt(1), $.AVNumberOfChannelsKey) - settings.setObjectForKey($.NSNumber.numberWithInt(16), $.AVLinearPCMBitDepthKey) - settings.setObjectForKey($.NSNumber.numberWithBool(false), $.AVLinearPCMIsFloatKey) - settings.setObjectForKey($.NSNumber.numberWithBool(false), $.AVLinearPCMIsBigEndianKey) + settings.setObjectForKey($.NSNumber.numberWithInt(24000), $.AVEncoderBitRateKey) const error = Ref() const url = $.NSURL.fileURLWithPath(args[0]) const recorder = $.AVAudioRecorder.alloc.initWithURLSettingsError(url, settings, error) @@ -68,7 +66,7 @@ export async function startSpeechCapture(input: Input): Promise { starting = input.requestId try { - const file = path.join(os.tmpdir(), `kilo-stt-${process.pid}-${Date.now()}.wav`) + const file = path.join(os.tmpdir(), `kilo-stt-${process.pid}-${Date.now()}.m4a`) if (useMacCapture(process.platform, process.env)) { const result = await startMac(file, input).catch((err: unknown) => { console.warn("[Kilo New] Native macOS speech capture failed, falling back to FFmpeg", err) @@ -105,7 +103,7 @@ export async function stopSpeechCapture(requestId: string): Promise