From 99c04c7163efb8cafa6e35f052b743c6dcf96f12 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 24 Jul 2026 13:15:08 +0200 Subject: [PATCH 1/2] feat(agent-manager): filter sessions by worktree --- .changeset/worktree-session-history.md | 5 ++ .../tests/history-accessibility.spec.ts | 25 ++++++ .../agent-manager/AgentManagerApp.tsx | 11 +++ .../src/components/history/HistoryView.tsx | 78 +++++++++++++++---- .../src/components/history/SessionList.tsx | 13 +++- .../kilo-vscode/webview-ui/src/i18n/ar.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/br.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/bs.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/da.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/de.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/en.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/es.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/fr.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/it.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/ja.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/ko.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/nl.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/no.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/pl.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/ru.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/th.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/tr.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/uk.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/zh.ts | 1 + .../kilo-vscode/webview-ui/src/i18n/zht.ts | 1 + .../src/stories/history.stories.tsx | 21 +++++ .../webview-ui/src/styles/history.css | 2 +- 27 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 .changeset/worktree-session-history.md diff --git a/.changeset/worktree-session-history.md b/.changeset/worktree-session-history.md new file mode 100644 index 000000000000..19ef392739ae --- /dev/null +++ b/.changeset/worktree-session-history.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Filter `/sessions` history to sessions in the current Agent Manager worktree. diff --git a/packages/kilo-vscode/tests/history-accessibility.spec.ts b/packages/kilo-vscode/tests/history-accessibility.spec.ts index 554dd3ee00d2..230bc7e47f9c 100644 --- a/packages/kilo-vscode/tests/history-accessibility.spec.ts +++ b/packages/kilo-vscode/tests/history-accessibility.spec.ts @@ -99,4 +99,29 @@ test.describe("history session accessibility", () => { await expect(local).toHaveAttribute("aria-selected", "true") await expect(page.getByRole("tabpanel", { name: "Local" })).toBeVisible() }) + + test("filters sessions to the current worktree and includes it in keyboard navigation", async ({ page }) => { + await story(page, "history-sessionlist--worktree-sources") + + const local = page.getByRole("tab", { name: "Local" }) + const worktree = page.getByRole("tab", { name: "Worktree" }) + await local.focus() + await page.keyboard.press("End") + await expect(worktree).toBeFocused() + await page.keyboard.press("Enter") + + await expect(worktree).toHaveAttribute("aria-selected", "true") + await expect(page.getByRole("tabpanel", { name: "Worktree" })).toBeVisible() + const rows = page.locator('[data-slot="list-item"]') + await expect(rows.filter({ hasText: "Refactor authentication module" })).toBeVisible() + await expect(rows.filter({ hasText: "Fix TypeScript errors in webview" })).toBeVisible() + await expect(rows.filter({ hasText: "Add screenshot test coverage" })).toHaveCount(0) + + await rows.filter({ hasText: "Refactor authentication module" }).click() + await expect(page.locator('[data-slot="selected-session"]')).toHaveText("s1") + + await worktree.focus() + await page.keyboard.press("ArrowRight") + await expect(local).toBeFocused() + }) }) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index a9f607b1565e..60252b3977b9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -761,6 +761,16 @@ const AgentManagerContent: Component = () => { return sessionsForWorktree(sel) }) + const activeWorktreeSessionIds = createMemo | undefined>(() => { + const sel = selection() + if (!sel || sel === LOCAL) return undefined + return new Set( + managedSessions() + .filter((item) => item.worktreeId === sel) + .map((item) => item.id), + ) + }) + const activeTabs = createMemo((): SessionInfo[] => { const sel = selection() if (sel === LOCAL) return localSessions() @@ -2898,6 +2908,7 @@ const AgentManagerContent: Component = () => { openLocally(id) }} onBack={() => setHistory(false)} + worktreeSessionIds={activeWorktreeSessionIds} /> diff --git a/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx b/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx index 0db4bba2fe7c..9f31a31115a3 100644 --- a/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/history/HistoryView.tsx @@ -1,10 +1,10 @@ /** * HistoryView component - * Unified panel for local and cloud session history. - * Contains a tab bar ("Local" | "Cloud") and an always-visible "Import session" button. + * Unified panel for local, cloud, and optional worktree session history. + * Contains a source tab bar and an always-visible "Import session" button. */ -import { Component, createEffect, createSignal, onCleanup } from "solid-js" +import { Component, Show, createEffect, createSignal, onCleanup, type Accessor } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { useLanguage } from "../../context/language" @@ -17,21 +17,34 @@ import CloudSessionList from "./CloudSessionList" interface HistoryViewProps { onSelectSession: (id: string) => void onBack?: () => void + worktreeSessionIds?: Accessor | undefined> } +type Source = "local" | "cloud" | "worktree" + +const EMPTY_SESSION_IDS = new Set() + const HistoryView: Component = (props) => { const language = useLanguage() const dialog = useDialog() const session = useSession() const tabs = useLocalTabs() - const [tab, setTab] = createSignal<"local" | "cloud">("local") + const [tab, setTab] = createSignal("local") let local: HTMLButtonElement | undefined let cloud: HTMLButtonElement | undefined + let worktree: HTMLButtonElement | undefined let localPanel: HTMLDivElement | undefined let cloudPanel: HTMLDivElement | undefined + let worktreePanel: HTMLDivElement | undefined + + const worktreeIds = () => props.worktreeSessionIds?.() createEffect(() => { - const panel = tab() === "local" ? localPanel : cloudPanel + if (tab() === "worktree" && !worktreeIds()) setTab("local") + }) + + createEffect(() => { + const panel = tab() === "local" ? localPanel : tab() === "cloud" ? cloudPanel : worktreePanel const frame = requestAnimationFrame(() => { panel @@ -60,17 +73,20 @@ const HistoryView: Component = (props) => { props.onBack?.() } - function move(event: KeyboardEvent, current: "local" | "cloud") { - const next = + function move(event: KeyboardEvent, current: Source) { + const sources: Source[] = worktreeIds() ? ["local", "cloud", "worktree"] : ["local", "cloud"] + const index = sources.indexOf(current) + const source = event.key === "Home" - ? local + ? sources[0] : event.key === "End" - ? cloud - : event.key === "ArrowLeft" || event.key === "ArrowRight" - ? current === "local" - ? cloud - : local - : undefined + ? sources.at(-1) + : event.key === "ArrowLeft" + ? sources[(index - 1 + sources.length) % sources.length] + : event.key === "ArrowRight" + ? sources[(index + 1) % sources.length] + : undefined + const next = source === "local" ? local : source === "cloud" ? cloud : source === "worktree" ? worktree : undefined if (!next) return event.preventDefault() next.focus() @@ -113,6 +129,23 @@ const HistoryView: Component = (props) => { > {language.t("session.tab.cloud")} + + +