From 220cdd20c8af4d4420ee0c4bf9bc7da8a0e82673 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 3 Aug 2026 17:10:43 +0200 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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 454cb934a04c2b62d2cbe38715e4f8f2a2c967d0 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 11:24:51 +0200 Subject: [PATCH 05/37] 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 5ebec9502d106a692dd8bc7adabbd69cbeb8615a Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 7 Aug 2026 12:03:39 +0200 Subject: [PATCH 06/37] fix(vscode): toggle Agent Manager terminal from toolbar button --- .changeset/fix-agent-manager-terminal-toggle.md | 5 +++++ .../unit/agent-manager-terminal-side.test.ts | 17 +++++++++++++++++ .../agent-manager/AgentManagerApp.tsx | 3 --- .../webview-ui/agent-manager/TabBar.tsx | 2 -- .../terminal/TerminalDestinationButton.tsx | 8 +------- .../webview-ui/agent-manager/terminal/side.ts | 17 ++++++++--------- 6 files changed, 31 insertions(+), 21 deletions(-) create mode 100644 .changeset/fix-agent-manager-terminal-toggle.md diff --git a/.changeset/fix-agent-manager-terminal-toggle.md b/.changeset/fix-agent-manager-terminal-toggle.md new file mode 100644 index 0000000000..a93825d595 --- /dev/null +++ b/.changeset/fix-agent-manager-terminal-toggle.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Fix the Agent Manager terminal toolbar button to toggle panel visibility directly. diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts index 4d6a9848d1..4b1c633f58 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts @@ -83,6 +83,23 @@ describe("Agent Manager side terminal controller", () => { expect(hidden.calls.hide).toBe(0) }) + it("toggles panel visibility from toolbar button without requiring focus", () => { + const visibleUnfocused = scene({ destination: "agentManager", visible: true }) + visibleUnfocused.ctl.openPreferred("tab_toolbar") + expect(visibleUnfocused.calls.hide).toBe(1) + expect(visibleUnfocused.calls.requestSide).toBe(0) + + const visibleFocused = scene({ destination: "agentManager", visible: true, focusedId: "terminal:side" }) + visibleFocused.ctl.openPreferred("tab_toolbar") + expect(visibleFocused.calls.hide).toBe(1) + expect(visibleFocused.calls.requestSide).toBe(0) + + const hidden = scene({ destination: "agentManager", visible: false }) + hidden.ctl.openPreferred("tab_toolbar") + expect(hidden.calls.requestSide).toBe(1) + expect(hidden.calls.hide).toBe(0) + }) + it("ensures an open terminal panel has a terminal after switching contexts", async () => { const visible = scene({ visible: true }) visible.ctl.syncContext("wt-2", "wt-1") diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 50aff3bff7..5487c25b06 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -2401,9 +2401,6 @@ const AgentManagerContent: Component = () => { onToggleReview={metrics.click("fullscreen_review", "tab_toolbar", toggleReviewTab)} terminalDestination={sideCtl.destination} terminalDestinationActive={() => sidePanel() === "terminal"} - terminalDestinationFocused={() => - sideCtl.destination() === "agentManager" && terms.sideFocusedId() !== undefined - } terminalKeybind={() => kb().showTerminal ?? ""} onTerminalDestinationOpen={() => { cancelAmbientSetup() diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index 7dacbda4bc..359ad99e4c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -57,7 +57,6 @@ export interface TabBarProps { onToggleReview: () => void terminalDestination: () => TerminalDestination terminalDestinationActive: () => boolean - terminalDestinationFocused: () => boolean terminalKeybind: () => string onTerminalDestinationOpen: () => void onTerminalDestinationChoose: (destination: TerminalDestination) => void @@ -260,7 +259,6 @@ export const TabBar: Component = (props) => ( /** True while the embedded terminal panel is showing. */ active: Accessor - /** True while the embedded terminal owns DOM focus. */ - focused: Accessor keybind: Accessor onOpen: () => void onChoose: (destination: TerminalDestination) => void @@ -29,10 +27,6 @@ interface Props { export const TerminalDestinationButton: Component = (props) => { const { t } = useLanguage() - const title = () => - props.destination() === "agentManager" && props.focused() - ? t("agentManager.shortcuts.toggleTerminal") - : t("agentManager.tab.openTerminal") const item = (destination: TerminalDestination, label: string) => ( props.onChoose(destination)}> @@ -45,7 +39,7 @@ export const TerminalDestinationButton: Component = (props) => { ) return (
- + { + const toggle = (trigger: "keyboard_shortcut" | "tab_toolbar" = "keyboard_shortcut") => { if (deps.visible()) { - if (!deps.focusedId()) { + if (trigger === "keyboard_shortcut" && !deps.focusedId()) { deps.handlers.requestSide() return } + const was = deps.focusedId() !== undefined deps.hide() - handoff(true) + handoff(was) return } deps.handlers.requestSide() @@ -149,7 +148,7 @@ export function createSideTerminal(deps: SideTerminalDeps) { const target = destination() deps.track("terminal", trigger, { destination: target }) if (target === "agentManager") { - toggle() + toggle(trigger) return } deps.openVscode() From a9ef79b792dab35a56000aa7190620e78da60ca4 Mon Sep 17 00:00:00 2001 From: Bruno Agatao Date: Fri, 7 Aug 2026 12:10:25 +0200 Subject: [PATCH 07/37] fix(vscode): restore native VS Code toolbar, scope webview bar to Cursor --- .changeset/fix-secondary-sidebar-nav-bar.md | 2 +- packages/kilo-vscode/package.json | 121 +++++++++++++++++- packages/kilo-vscode/src/KiloProvider.ts | 7 +- packages/kilo-vscode/src/extension.ts | 38 +++++- packages/kilo-vscode/src/utils.ts | 21 +++ packages/kilo-vscode/webview-ui/src/App.tsx | 7 +- .../src/components/chat/SidebarTopBar.tsx | 12 +- .../webview-ui/src/styles/chat-layout.css | 8 +- 8 files changed, 198 insertions(+), 18 deletions(-) diff --git a/.changeset/fix-secondary-sidebar-nav-bar.md b/.changeset/fix-secondary-sidebar-nav-bar.md index 070b89d5f8..7905945a1e 100644 --- a/.changeset/fix-secondary-sidebar-nav-bar.md +++ b/.changeset/fix-secondary-sidebar-nav-bar.md @@ -2,4 +2,4 @@ "kilo-code": patch --- -Fix the sidebar navigation bar (New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, Settings) disappearing when the Kilo Code view is docked in the Secondary Side Bar. The navigation is now rendered inside the webview itself, so it stays visible regardless of where the view is docked. +Fix the sidebar navigation bar (New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, Settings) disappearing in Cursor when the Kilo Code view is docked in the Secondary Side Bar. Cursor now renders the navigation inside the webview itself so it stays visible regardless of dock location. VS Code is unaffected — it continues to use its native title bar toolbar, which already worked correctly everywhere. diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index d1aed14c8d..99d3b5a23f 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -141,6 +141,41 @@ "category": "Kilo Code", "icon": "$(settings-gear)" }, + { + "command": "kilo-code.new.sidebarTitle.plusButtonClicked", + "title": "New Task", + "icon": "$(add)" + }, + { + "command": "kilo-code.new.sidebarTitle.agentManagerOpen", + "title": "Agent Manager", + "icon": "$(organization)" + }, + { + "command": "kilo-code.new.sidebarTitle.kiloClawOpen", + "title": "KiloClaw", + "icon": "$(comment-discussion)" + }, + { + "command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked", + "title": "Marketplace", + "icon": "$(extensions)" + }, + { + "command": "kilo-code.new.sidebarTitle.historyButtonClicked", + "title": "History", + "icon": "$(history)" + }, + { + "command": "kilo-code.new.sidebarTitle.profileButtonClicked", + "title": "Profile", + "icon": "$(account)" + }, + { + "command": "kilo-code.new.sidebarTitle.settingsButtonClicked", + "title": "Settings", + "icon": "$(settings-gear)" + }, { "command": "kilo-code.new.openInTab", "title": "Open in Tab", @@ -426,12 +461,76 @@ ], "menus": { "commandPalette": [ + { + "command": "kilo-code.new.sidebarTitle.plusButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.historyButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.agentManagerOpen", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.kiloClawOpen", + "when": "false" + }, { "command": "kilo-code.new.agentManager.advancedWorktree", "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.profileButtonClicked", + "when": "false" + }, + { + "command": "kilo-code.new.sidebarTitle.settingsButtonClicked", + "when": "false" + } + ], + "view/title": [ + { + "command": "kilo-code.new.sidebarTitle.plusButtonClicked", + "group": "navigation@0", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.historyButtonClicked", + "group": "navigation@1", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.agentManagerOpen", + "group": "navigation@2", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.kiloClawOpen", + "group": "navigation@3", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked", + "group": "navigation@4", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.profileButtonClicked", + "group": "navigation@5", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.sidebarTitle.settingsButtonClicked", + "group": "navigation@6", + "when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor" } ], - "view/title": [], "scm/title": [ { "command": "kilo-code.new.generateCommitMessage", @@ -451,6 +550,26 @@ "command": "kilo-code.new.openInTab", "group": "navigation", "when": "true" + }, + { + "command": "kilo-code.new.plusButtonClicked", + "group": "navigation@0", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.historyButtonClicked", + "group": "navigation@1", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.profileButtonClicked", + "group": "navigation@2", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" + }, + { + "command": "kilo-code.new.settingsButtonClicked", + "group": "navigation@3", + "when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor" } ], "editor/context": [ diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 4de5a31101..4e6f22e97d 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -17,7 +17,7 @@ import type { EditorContext, IndexingStatus } from "./services/cli-backend/types import { FileIgnoreController } from "./services/autocomplete/shims/FileIgnoreController" import { ChatTextAreaAutocomplete } from "./services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete" import { notebookUri } from "./services/autocomplete/continuedev/core/autocomplete/notebook" -import { buildWebviewHtml, getWebviewFontSize } from "./utils" +import { buildWebviewHtml, getWebviewFontSize, isCursorHost } from "./utils" import { saveImage } from "./kilo-provider/save-image" import { handleEditorAction } from "./kilo-provider/editor-actions" import { exportTranscript } from "./kilo-provider/export-transcript" @@ -4905,7 +4905,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper title: "Kilo Code", port: this.connectionService.getServerInfo()?.port, extraStyles: `.container { height: 100vh; }`, - topBar: this.opts.hideTopBar !== true, + // Dedicated single-purpose panels (Settings, Profile, Sub-Agent Viewer) + // never show the bar. Sidebar and "Open in Tab" only need it in Cursor — + // VS Code's native toolbar (restored in package.json) works everywhere. + topBar: this.opts.hideTopBar !== true && isCursorHost(), topBarSurface: this.opts.topBarSurface === "tab" ? "tab_title" : "sidebar_title", }) } diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 6e6e991a3d..e592626b97 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -17,7 +17,7 @@ import { ensureBackendForAutocomplete } from "./services/autocomplete/ensure-bac import { AutocompleteServiceManager } from "./services/autocomplete/AutocompleteServiceManager" import { AttentionService } from "./services/attention" import { BrowserAutomationService } from "./services/browser-automation" -import { TelemetryProxy } from "./services/telemetry" +import { TelemetryEventName, TelemetryProxy } from "./services/telemetry" import { registerCommitMessageService } from "./services/commit-message" import { registerCodeActions, registerTerminalActions, KiloCodeActionProvider } from "./services/code-actions" import { registerToggleAutoApprove } from "./commands/toggle-auto-approve" @@ -26,6 +26,7 @@ import { RemoteStatusService } from "./services/RemoteStatusService" import { markWorkspace } from "./util/spotlight" import { createNotebookBridge } from "./services/notebook" import { createGitExecutable } from "./util/git-executable" +import { isCursorHost } from "./utils" let agentManager: AgentManagerProvider | undefined let shuttingDown = false @@ -48,6 +49,10 @@ export function activate(context: vscode.ExtensionContext) { console.log("Kilo Code extension is now active") shuttingDown = false + // Drives the "!kilo-code.new.isCursor" guards on the native view/title and + // editor/title menu contributions — see isCursorHost() for why. + void vscode.commands.executeCommand("setContext", "kilo-code.new.isCursor", isCursorHost()) + const telemetry = TelemetryProxy.getInstance() // Create shared connection service (one server for all webviews) @@ -334,8 +339,39 @@ export function activate(context: vscode.ExtensionContext) { }), ) + // Sidebar menus use wrapper commands so this event measures real title button presses, + // not programmatic opens, shortcuts, or editor title commands. + const track = (button: string, command: string) => { + TelemetryProxy.capture(TelemetryEventName.TITLE_BUTTON_CLICKED, { + button, + surface: "sidebar_title", + }) + void vscode.commands.executeCommand(command) + } + // Register toolbar button command handlers context.subscriptions.push( + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.plusButtonClicked", () => { + track("new_task", "kilo-code.new.plusButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.historyButtonClicked", () => { + track("history", "kilo-code.new.historyButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.agentManagerOpen", () => { + track("agent_manager", "kilo-code.new.agentManagerOpen") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.kiloClawOpen", () => { + track("kiloclaw", "kilo-code.new.kiloClawOpen") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.marketplaceButtonClicked", () => { + track("marketplace", "kilo-code.new.marketplaceButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.profileButtonClicked", () => { + track("profile", "kilo-code.new.profileButtonClicked") + }), + vscode.commands.registerCommand("kilo-code.new.sidebarTitle.settingsButtonClicked", () => { + track("settings", "kilo-code.new.settingsButtonClicked") + }), vscode.commands.registerCommand("kilo-code.new.plusButtonClicked", () => { const tab = activeTabProvider() if (tab) tab.postMessage({ type: "action", action: "plusButtonClicked" }) diff --git a/packages/kilo-vscode/src/utils.ts b/packages/kilo-vscode/src/utils.ts index 23479043e8..6e3b6b49c0 100644 --- a/packages/kilo-vscode/src/utils.ts +++ b/packages/kilo-vscode/src/utils.ts @@ -18,6 +18,27 @@ export function getWebviewFontSize(): number { return clamp(raw) } +/** + * True when running inside Cursor rather than real VS Code (or another + * fork). Cursor's Secondary Side Bar support is known to be unreliable for + * extension-contributed `view/title` toolbars (see + * https://github.com/anthropics/claude-code/issues/31375 for the same class + * of bug in a different extension), so Cursor falls back to an in-webview + * navigation bar instead of the native toolbar that VS Code renders fine + * everywhere. + * + * This is a per-host choice, not a per-dock-location one: `WebviewView` (and + * the rest of the public API, checked against @types/vscode) exposes no way + * to ask "is my view currently in the primary or secondary side bar", so the + * webview fallback bar is Cursor's only option in both locations. Accepted + * trade-off: Cursor's primary side bar loses the single-line native look it + * had before this existed, in exchange for the Secondary Side Bar actually + * working, with zero guessing about dock position anywhere. + */ +export function isCursorHost(): boolean { + return vscode.env.appName.toLowerCase().includes("cursor") +} + function fontStyle(): string { const base = getWebviewFontSize() const vars = SIZES.map((size) => `--kilo-font-size-${size}: ${(base * size) / 13}px;`).join("\n ") diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index 6dc0e4da1c..e8dafa01c5 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -311,8 +311,11 @@ const AppContent: Component = () => { // Set synchronously in the webview HTML by KiloProvider so it's available // before this component ever mounts (see buildWebviewHtml/_getHtmlForWebview). - // Dedicated single-purpose panels (Settings, Profile, Sub-Agent Viewer) set - // KILO_TOP_BAR = false since navigating away from them makes no sense. + // False for dedicated single-purpose panels (Settings, Profile, Sub-Agent + // Viewer) always, and for the Sidebar/"Open in Tab" outside Cursor — real + // VS Code's native title bar toolbar already covers those. Defaults to + // true only when unset entirely (e.g. Storybook, which doesn't render the + // real page HTML). const host = window as { KILO_TOP_BAR?: boolean; KILO_TOP_BAR_SURFACE?: string } const showTopBar = host.KILO_TOP_BAR !== false const topBarSurface = host.KILO_TOP_BAR_SURFACE ?? "sidebar_title" diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx index c031882e4d..0a0de70c92 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/SidebarTopBar.tsx @@ -1,8 +1,10 @@ /** * Renders New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, and - * Settings inside the webview. VS Code's native `view/title` toolbar renders - * outside the webview DOM and disappears in the Secondary Side Bar with no way - * to detect or work around that — this bar guarantees the actions stay visible. + * Settings inside the webview, as a fallback for Cursor only (see isCursorHost() + * in src/utils.ts). Cursor's Secondary Side Bar support is unreliable for + * extension-contributed `view/title` toolbars, which render outside the webview + * DOM with no API to detect or work around the failure. Real VS Code renders the + * native toolbar fine everywhere, so it keeps using that instead of this bar. */ import { Component, For } from "solid-js" @@ -45,13 +47,12 @@ export const SidebarTopBar: Component = (props) => { type: "openAgentManager" | "openKiloClaw" | "openMarketplacePanel" | "openProfilePanel" | "openSettingsPanel", ) => vscode.postMessage({ type }) - const actions: (Action | "spacer")[] = [ + const actions: Action[] = [ { key: "newTask", codicon: "add", button: "new_task", run: () => props.onNewTask() }, { key: "history", codicon: "history", button: "history", run: () => props.onHistory() }, { key: "agentManager", codicon: "organization", button: "agent_manager", run: () => open("openAgentManager") }, { key: "kiloClaw", codicon: "comment-discussion", button: "kiloclaw", run: () => open("openKiloClaw") }, { key: "marketplace", codicon: "extensions", button: "marketplace", run: () => open("openMarketplacePanel") }, - "spacer", { key: "profile", codicon: "account", button: "profile", run: () => open("openProfilePanel") }, { key: "settings", codicon: "settings-gear", button: "settings", run: () => open("openSettingsPanel") }, ] @@ -60,7 +61,6 @@ export const SidebarTopBar: Component = (props) => {