-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix(vscode): list past chats across the worktree family #12692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "kilo-code": patch | ||
| --- | ||
|
|
||
| Fix the `@` "Past chats" picker in Agent Manager showing only the current session's directory. It now lists previous sessions across the whole worktree family — the local workspace and every Agent Manager worktree — each labeled with its worktree name, matching the Agent Manager session search. Any listed session can be attached as context, including chats from other worktrees of the same repository. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| import { describe, expect, it } from "bun:test" | ||
| import { handleSessionSearch } from "../../src/kilo-provider/session-search" | ||
|
|
||
| type Query = Record<string, unknown> | ||
|
|
||
| function stub(data: Array<Record<string, unknown>> | Error) { | ||
| const calls: Query[] = [] | ||
| const client = { | ||
| experimental: { | ||
| session: { | ||
| list: async (query: Query) => { | ||
| calls.push(query) | ||
| if (data instanceof Error) throw data | ||
| return { data } | ||
| }, | ||
| }, | ||
| }, | ||
| } | ||
| return { calls, client } | ||
| } | ||
|
|
||
| function session(id: string, title: string, updated: number, worktreeName?: string) { | ||
| return { id, title, time: { updated }, worktreeName } | ||
| } | ||
|
|
||
| describe("handleSessionSearch", () => { | ||
| it("lists root sessions across the worktree family for the resolved directory", async () => { | ||
| const { calls, client } = stub([session("ses_a", "Alpha", 2, "neon-author")]) | ||
| const posted: unknown[] = [] | ||
|
|
||
| await handleSessionSearch({ | ||
| client: client as never, | ||
| message: { requestId: "r1", sessionID: "ses_current" }, | ||
| dir: (id) => (id === "ses_current" ? "/repo/.kilo/worktrees/wt-1" : "/repo"), | ||
| post: (msg) => posted.push(msg), | ||
| }) | ||
|
|
||
| expect(calls).toEqual([{ worktrees: true, roots: true, directory: "/repo/.kilo/worktrees/wt-1", limit: 50 }]) | ||
| expect(posted).toEqual([ | ||
| { | ||
| type: "sessionSearchResult", | ||
| sessions: [{ id: "ses_a", title: "Alpha", updated: 2, worktreeName: "neon-author" }], | ||
| requestId: "r1", | ||
| }, | ||
| ]) | ||
| }) | ||
|
|
||
| it("falls back to the current and context sessions for directory resolution", async () => { | ||
| const { calls, client } = stub([]) | ||
|
|
||
| await handleSessionSearch({ | ||
| client: client as never, | ||
| message: { requestId: "r2" }, | ||
| current: "ses_current", | ||
| context: "ses_context", | ||
| dir: (id) => `/dir/${id}`, | ||
| post: () => {}, | ||
| }) | ||
|
|
||
| expect(calls[0]?.directory).toBe("/dir/ses_current") | ||
|
|
||
| await handleSessionSearch({ | ||
| client: client as never, | ||
| message: { requestId: "r3" }, | ||
| context: "ses_context", | ||
| dir: (id) => `/dir/${id}`, | ||
| post: () => {}, | ||
| }) | ||
|
|
||
| expect(calls[1]?.directory).toBe("/dir/ses_context") | ||
| }) | ||
|
|
||
| it("excludes the given session and sessions without titles", async () => { | ||
| const { client } = stub([ | ||
| session("ses_keep", "Keep", 3), | ||
| session("ses_exclude", "Excluded", 2), | ||
| session("ses_untitled", "", 1), | ||
| ]) | ||
| const posted: Array<{ sessions: Array<{ id: string }> }> = [] | ||
|
|
||
| await handleSessionSearch({ | ||
| client: client as never, | ||
| message: { requestId: "r4" }, | ||
| dir: () => "/repo", | ||
| exclude: "ses_exclude", | ||
| post: (msg) => posted.push(msg as never), | ||
| }) | ||
|
|
||
| expect(posted[0]?.sessions.map((s) => s.id)).toEqual(["ses_keep"]) | ||
| }) | ||
|
|
||
| it("posts an empty result when the client is missing or the list fails", async () => { | ||
| const posted: unknown[] = [] | ||
|
|
||
| await handleSessionSearch({ | ||
| client: null, | ||
| message: { requestId: "r5" }, | ||
| dir: () => "/repo", | ||
| post: (msg) => posted.push(msg), | ||
| }) | ||
|
|
||
| const failing = stub(new Error("boom")) | ||
| await handleSessionSearch({ | ||
| client: failing.client as never, | ||
| message: { requestId: "r6" }, | ||
| dir: () => "/repo", | ||
| post: (msg) => posted.push(msg), | ||
| }) | ||
|
|
||
| expect(posted).toEqual([ | ||
| { type: "sessionSearchResult", sessions: [], requestId: "r5" }, | ||
| { type: "sessionSearchResult", sessions: [], requestId: "r6" }, | ||
| ]) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,7 +42,7 @@ export function SessionMentionPicker(props: Props) { | |
| <List<SessionSearchItem> | ||
| items={props.sessions} | ||
| key={(item) => item.id} | ||
| filterKeys={["title"]} | ||
| filterKeys={["title", "worktreeName"]} | ||
| search={{ placeholder: "Search sessions", autofocus: true }} | ||
| onSelect={(item) => { | ||
| if (item) props.onSelect(item) | ||
|
|
@@ -52,6 +52,7 @@ export function SessionMentionPicker(props: Props) { | |
| <span class="session-mention-item"> | ||
| <Icon name="history" class="file-mention-icon" /> | ||
| <span class="session-mention-title">{item.title}</span> | ||
| {item.worktreeName && <span class="session-mention-worktree">{item.worktreeName}</span>} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. SUGGESTION: the worktree label is rendered even when it carries no information The server sets Consider only rendering the badge when the candidates actually span more than one worktree, e.g. compute Reply with |
||
| <span class="session-mention-time">{formatRelativeDate(new Date(item.updated).toISOString())}</span> | ||
| </span> | ||
| )} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SUGGESTION:
worktrees: truemoves this onto a noticeably more expensive server path than the old directory-scopedsession.listWith
worktreesset, the handler runsWorktreeFamily.list()(agit worktree list --porcelainsubprocess) and thenKiloSession.listGlobalskips the SQLLIMITentirely becausedirectoriesis non-empty (packages/opencode/src/kilocode/session/index.ts:400), loading every non-archived root session in the project family and filtering in JS — with aexistsSync(.git)parent walk per row vianested(). Since the webview refetches candidates on every picker open (useFileMention.tsopenSessionPicker), that cost is paid each time@past-chats is opened, and it scales with the project's total session count rather than withlimit: 50.Probably fine in practice, but if it shows up on large histories, consider caching the candidate list for the lifetime of the composer instead of refetching per open.
Reply with
@kilocode-bot fix itto have Kilo Code address this issue.