Move the per-project SESSIONS list out of the Agent Manager sidebar and behind a per-project history button that opens the sessions view scoped to that project. Each session row offers direct actions to resume it in a new worktree or in the project's local tabs, and sessions are no longer part of the sidebar keyboard navigation order.
18 KiB
Plan: Replace the Agent Manager SESSIONS list with a per-project sessions button
Worktree: /Users/marius/Documents/git/kilocode/.kilo/worktrees/hill-animal
All paths below are relative to packages/kilo-vscode/.
Related: https://github.com/Kilo-Org/kilocode/issues/12928
Problem
The Agent Manager sidebar renders an inline SESSIONS section in both modes:
- Single project:
SidebarBody.tsxrendersUnassignedSessionsSection(bottom of the sidebar). - Multi project:
ProjectSidebarBody.tsxrenders the same section per expanded project (bottom of each project body).
Limitations:
- The section only lists root sessions (
worktreeId === null). Sessions that live in a worktree are not reachable from it. - In multi-project mode every expanded project renders its own unbounded list, pushing other projects out of the viewport.
- The inline list grows without a scroll container (
UnassignedSessionsSectionsetsam-section-growbut the scroll container is the projects list, so the list can only push).
Goal: remove the inline SESSIONS element from both modes and behind a per-project button. Clicking the button does the same as /sessions in the Agent Manager (open the history view), but scoped to that project only, so sessions of other projects never appear.
Current /sessions flow in Agent Manager (reference behavior)
/sessions is an action in useSlashCommand.ts that posts { type: "navigate", view: "history" }. In the Agent Manager this hits AgentManagerApp.tsx handler setHistory(true), which shows HistoryView.
HistoryView (src/components/history/HistoryView.tsx) has three tabs:
- Local:
SessionListwithout asessionIdsfilter, so it renderssession.sessions()(the session context store). - Cloud: unchanged.
- Worktree: only when the sidebar has a worktree selected;
SessionListfiltered to that worktree's session ids (activeWorktreeSessionIds).
Picking a session (HistoryView onSelectSession, AgentManagerApp.tsx):
addSessionToCurrentWorktree(sid): when a worktree is selected and the session is not owned by any worktree, the session is attached to the selected worktree (agentManager.addSessionToWorktree).- When the session is already owned by a worktree: navigate to its owning worktree (
worktreeSessionIds().has(id)). - When it is in the project's local tabs: select it in the local context.
- Otherwise:
openLocally(id)(adds it to the project's local tabs, replacing a pending New Session draft).
session.sessions() is populated by loadSessions, which the extension scopes to a single project (the active project's projectQualifier). It structurally cannot contain another project's sessions. Per-project session data arrives over a separate pipeline: agentManager.projectSessions -> createProjectSessionsLive() in the webview, which contains every session of a project (root and worktree sessions, tagged with worktreeId: string | null).
Design
1. Button per project, same behavior as /sessions
- Single project: add a history
IconButtonto the WORKTREES header actions (WorktreeSectionActions.tsx, next to search / new worktree). Click ->setHistory(true)(identical to/sessions). - Multi project: add a history
IconButtonto each project row header inProjectsSection.tsx(next to the remove X), so every project has exactly one button, also when the project is collapsed. Click -> activate that project viaagentManager.activateSelectionwith target{ projectId, kind: "local" }(no restore), thensetHistory(true)with the project-scoped filter.
Reason for activating the project first: HistoryView/SessionList render session.sessions(), which is only populated for the active project. Activating the target project first is the only way to reuse the existing /sessions machinery without writing a bespoke list. When the user clicks back, they are in that project, which matches clicking the project row today.
Icon: Icon name="history" (kilo-ui falls back to the upstream icon set; already used by WelcomeEmptyState.tsx). Label: reuse the existing session.showHistory key, already translated in every locale.
2. Project-scoped filtering
Track historyProject (signal, string | undefined) in AgentManagerContent:
undefined= legacy/sessionsflow (unchanged behavior, Worktree tab stays).- project id = project-scoped history:
- Local tab:
HistoryViewgets a new optionalsessionIdsprop;SessionListalready acceptssessionIds, so the Local tab renders only the filter set. - Filter set = ids of
projectSessionsLive()[pid](all root + worktree sessions of that project, including live-store overlays once the project is active), filtered byisKnownRootSession. - Worktree tab: hidden while
historyProjectis set. All project sessions are already in the Local tab; a Worktree tab could show a different project's selected worktree and pollute the scope. - Cloud tab: unchanged.
- Local tab:
historyProjectresets when history closes (Back button, session picked,onBack).
Session list membership uses projectSessionsLive()[pid], which the extension pushes per project at project init. If it is missing, the filter set is empty (no other project's sessions can bleed in).
3. Where a picked session starts, and the start-location choice
When historyProject is set, picking a session uses the same routing as /sessions in that project (the project is active after step 1, so the existing onSelectSession handler in AgentManagerApp.tsx operates on the right project):
- Row click (default, same action as the old SESSIONS row click): session owned by a worktree stays bound to that worktree and jumps to it; root sessions open in the project's local tabs (
openLocally), replacing a pending New Session draft. - Row menu (three-dot, avatar/hover): each row also offers a start-location choice, shown in the sessions view:
- Start in a new worktree: the existing promote pipeline (
agentManager.promoteSession), which creates a fresh worktree in the project, runs its setup script, moves the session into it, and opens it there. - Open in project local tabs (worktree-bound rows only; root rows just open locally): new message
agentManager.openSessionLocallymoves the session back to the project root (statemoveSession(sessionId, null)+ root route registration + state push) and activates it in the project's local tabs.
- Start in a new worktree: the existing promote pipeline (
- Attach-to-selected-worktree (
addSessionToCurrentWorktree) only applies in the legacy/sessionsflow, where the user opened history while a worktree was selected. Because the button activates the project with alocaltarget first, the project-scoped flow never rebinds a root session to a worktree.
This is the deliberate behavior difference: the button's list is project-scoped and origin-driven; the in-chat /sessions keeps the continue-in-worktree semantics.
Removed worktree edge case
When a worktree is deleted, the flow in provider-lifecycle.ts already de-syncs its sessions: state.removeWorktree(worktreeId) deletes the worktree and every session bound to it from the managed state (WorktreeStateManager.removeWorktree removes sessions[worktreeId] entries and returns them as orphaned), and the caller runs host.sessions.clearDirectory(sessionId) for each orphaned session. collectProjectSessions lists only the project root plus directories of worktrees that still exist in state, so a deleted worktree's sessions are no longer listed in any project list. The backend session records stay on disk (keyed to the removed directory) but are unreachable from the Agent Manager lists.
Consequences for the new flow:
- The project-scoped history cannot show a dangling "session in removed worktree" row:
projectSessionsLive()only contains sessions found under root or live worktree directories, and the persisted managed list prunes them on removal. End-to-end: delete worktree -> button -> history -> the session is not listed, so "selecting" it is impossible. It reappears only when the worktree is re-created andrestoreWorktreesre-attaches it. - Defensive guard in the picker: if a picked session claims a
worktreeIdthat no longer resolves in the current project state (race between webview state push and removal), fall back toopenLocallyinstead ofselectWorktree. This mirrors the extension-side fallback inproject/messages.tsactivateSelection("worktree is gone, falling back to local"). - Open-tab nuance (not reachable through the history, no change planned): a still-open chat tab for the pruned session keeps working after removal. Message load falls back to the session record's stored directory (the deleted path); prompt-send falls through
resolveNewSessionDirectoryto the project root. So such a tab continues against the project root, not the deleted worktree.
4. Sidebar cleanup
- Remove
UnassignedSessionsSectionfromSidebarBody.tsxandProjectSidebarBody.tsx; deleteUnassignedSessionsSection.tsx. - Remove
sessionsCollapsed/toggleSessionswebview plumbing: props inSidebarBody,ProjectSidebarBody,AgentManagerAppsignals, the registry store field (project/store.ts), and theagentManager.setSessionsCollapsedpost (keep the extension-side state field and message handler inert, so existing.kilo/agent-manager.jsonfiles andworktree-state-manager.test.tsstay valid). - WORKTREES section always grows (
am-section-grow) and scrolls; the old grow-toggle tied tosessionsCollapsed(SidebarBody.tsxline 187) is replaced by always-grow. In single mode this removes themax-height: 50vhcap because.am-section-grow .am-worktree-listalready setsmax-height: none; flex: 1. - Remove unassigned session rows from keyboard navigation and jump shortcuts:
buildSidebarOrder(drop thesessionsparam),projectSidebarOrder(project-local-navigation.ts, drop the localSessions param),buildProjectNavEntries(project-nav.ts, drop theunassigned/sessionsCollapsedmapping). Invisible rows must not receive Cmd+Alt+arrow or Cmd+1-9 jumps (this also resolves the known issue documented inagent-manager-multi-project-sidebar-density.md). - Remove now-dead helpers and props:
filterUnassignedSessions(and its tests),unassignedSessionsmemo,selectUnassigned,SidebarBodypropsunassignedSessions/selectUnassigned/sessionsCollapsed/toggleSessions,ProjectSidebarBodyproponSelectSession,SidebarBodyproponShowHistorywiring stays forChatView. - Session search palette (Cmd+F) keeps its "sessions" group; it is not the tree list and stays.
- Agent manager
.am-list/.am-itemCSS used only byUnassignedSessionsSectioncan be deleted after the section is gone; other.am-item-style classes used by edit preview etc. stay. agent-manager-arch.test.tsreferencesUnassignedSessionsSection.tsxin the file list and in a test; remove those references.- Stories (
webview-ui/src/stories/agent-manager.stories.tsx) remove the sessions fixtures in multi-project/sidebar stories and thesessionsCollapsed: falsestate fixture.
Implementation tasks
Order matters: the section removal first, then the button, then the nav/CSS cleanup, then tests.
Task 1: Remove the inline SESSIONS section
SidebarBody.tsx: delete theUnassignedSessionsSectionusage at the bottom; remove the import and the propssessionsCollapsed,toggleSessions,unassignedSessions,selectUnassignedfromSidebarBodyProps; make the WORKTREES div alwaysam-section-grow.ProjectSidebarBody.tsx: delete theUnassignedSessionsSectionusage; remove the import and theonSelectSessionprop; remove thelocalSessionsmemo if unused (still used byprojectSidebarOrder, removed in task 3).- Delete
UnassignedSessionsSection.tsx. AgentManagerApp.tsx: removesessionsCollapsed,setSessionsCollapsed,toggleSessionsand theunassignedSessionsmemo; remove the corresponding props passed toSidebarBody; removefilterUnassignedSessionsimport.project/store.ts: remove thesessionsCollapsedfield;ProjectList.tsxstops passingonSelectSessioninto the body.
Task 2: Add the per-project history button
WorktreeSectionActions.tsx: addonHistory: () => voidprop and aTooltip-wrapped historyIconButton(session.showHistory).SidebarBody.tsx: pass-throughonHistoryprop.ProjectsSection.tsx: addonHistory: (projectId: string) => voidprop; in the project row header actions add the historyIconButtonbefore the remove X (withstopPropagation).ProjectList.tsx: wireonHistory.AgentManagerApp.tsx:- Add
historyProjectsignal andopenHistory(pid?: string):pidundefined ->setHistory(true);pidset -> postagentManager.activateSelection{ target: { projectId: pid, kind: "local" } }, thensetHistoryProject(pid),setHistory(true). - Pass
onHistorytoSidebarBodyandProjectList. - Reset
historyProjectin the close-history paths (Back, session picked,setHistory(false)calls for history).
- Add
Task 3: Project-scoped history filter + start-location choice
src/components/history/HistoryView.tsx: add optionalsessionIds?: Accessor<ReadonlySet<string>>for the Local tabSessionList, and optionalrowActions?: (session: SessionInfo) => JSX.Elementrendered per row.src/components/history/SessionList.tsx: forwardrowActionsintowrapItem(after rename/delete buttons).webview-ui/agent-manager/SessionRowActions.tsx(new): per-row three-dot menu withagentManager.session.openInWorktree("Start in a new worktree") andagentManager.session.openLocally("Open in project local tabs", worktree-bound rows only).AgentManagerApp.tsx:- Memo
historySessionIdsfromprojectSessionsLive()[historyProject()]filtered byisKnownRootSession. - Pass
sessionIds={historySessionIds}androwActionstoHistoryViewonly whenhistoryProjectis set. - Pass
worktreeSessionIdstoHistoryViewonly whenhistoryProjectis unset (legacy flow unchanged). - Row actions: "Start in a new worktree" posts
agentManager.promoteSession(project already active); "Open in local tabs" postsagentManager.openSessionLocally(withprojectIdin multi mode), then closes history.
- Memo
- New extension message
agentManager.openSessionLocally { projectId?, sessionId }:- Multi project (
projectIdset):project/messages.tshandler moves the session to local (statemoveSession(id, null),routeSessionroot registration,push()), thenfinishwith a session target so the webview'sapplyProjectSelectionopens it viafocusLocalSession. - Legacy (no
projectId): provider dispatch moves the session withmoveSession(id, null)+registerWorktreeSession(id, root)+pushState(); the webview then opens it locally. ProjectMessageDepsgains optionalrouteSessionwired inproject/wiring.tsfromhost.sessions(setSessionDirectory+registerSessionRoute); add the type toagent-manager/types.tsand to thestate-gate.tslist.
- Multi project (
Task 4: Navigation and CSS cleanup
section-helpers.ts: remove thesessionsparameter frombuildSidebarOrder; update call sites andtests/unit/section-helpers.test.ts.project-local-navigation.ts: remove thelocalSessionsparameter fromprojectSidebarOrder; drop session nav entries.project-nav.ts: removeunassigned/sessionsCollapsedmapping frombuildProjectNavEntries; drop session nav entries; removesessionNavIdif unused.navigate.ts: removefilterUnassignedSessions; remove theselectUnassignedglue inAgentManagerApp(focusSidebarItemelse-branch,SidebarBodyprop).agent-manager.css: remove dead session-list rules if no longer used after task 1 (verifyam-list,am-itemusages first).agent-manager-arch.test.ts: dropUnassignedSessionsSection.tsxreferences.tests/unit/navigate.test.ts: drop thefilterUnassignedSessionsdescribe block.webview-ui/src/stories/agent-manager.stories.tsx: update stories to the button-driven UI.
Verification
From packages/kilo-vscode:
bun run format
bun run typecheck
bun run lint
bun run test:unit
bun run check-kilocode-change
bun run compile
Changeset
.changeset/agent-manager-sessions-button.md:
---
"kilo-code": patch
---
Replace the Agent Manager sidebar sessions list with a per-project history button that opens all sessions of that project, including sessions running in its worktrees.
Manual test
Open Agent Manager (Cmd+Shift+M). Single project:
- The SESSIONS section at the bottom is gone; the WORKTREES list fills the sidebar and scrolls.
- Click the history icon in the WORKTREES header: the history view opens (Local/Cloud tabs); the Local list matches the current project's sessions.
- Type
/sessionsin the chat: behavior unchanged (history opens).
Multi project (kilo-code.new.experimental.multiProject on, add a second project):
- Each project row has exactly one history icon; collapsed projects too.
- Click project B's button: history opens, even when project A was active; the Local list shows only B's sessions (root and worktree sessions).
- Pick a worktree-bound session of B: jumps to B's worktree, session selected.
- Pick a root session of B: opens in B's local tabs; switch back to project A: A's sessions are untouched.
- While project B is active and a worktree of B is selected,
Cmd+click/sessionsin chat: continue-in-worktree behavior preserved. - Cmd+Alt+Up/Down and Cmd+1-9 no longer land on invisible session rows.
Out of scope / rejected options
- A bespoke project-session list rendering
ProjectSessionInfodirectly (per issue #12928's out-of-scope note): rename/export/delete and session selection all require the active-project session context. Activating the project and reusingHistoryViewis the lower-risk path. - Bounding the inline list to N rows with a "Show all" row (issue #12928 proposal): superseded by this design, which removes the list entirely.
- Keeping
sessionsCollapsedpersistence on the extension side: kept inert to avoid touchingWorktreeStateManagerand its tests; can be removed in a follow-up once no webview writes to it.