feat(jetbrains): improve Agent Manager worktrees and chat - #13315
Conversation
Drive the worktree row icon and text badge from one color token per activity state, add an error state end-to-end (shared DTO, backend activity tracking, frontend aggregate), and show a notification dot on the Agents tab when a session needs attention.
Replace the static running badge in the Agent Manager worktree list with an animated spinner that mirrors the VS Code Spinner: a 4x4 grid of rounded squares pulsing their opacity in the amber running brand color. Built as a frame-based AnimatedIcon so it animates via the list's ANIMATION_IN_RENDERER_ALLOWED flag with no extra wiring.
ActiveList rebuilt its model with CollectionListModel.replaceAll, which fires an intervalRemoved with an empty model. The old sync() then fell back to selecting row 0, and that spurious selection fired onSelect, clobbering the caller's remembered row. Dropping a dragged worktree and switching away from the Agents tab both hit this path, so the selection jumped to the first row. Give ActiveList one capture -> refresh -> restore path: rows expose a stable identity, the view remembers the selected identities as a sticky anchor written only by user intent, and restores them after every rebuild with an explicit absent-row policy. This removes the four hand-rolled restore blocks in AgentManagerPanel, HistoryPanel, and SettingsInlineListPanel, along with the AgentManagerPanel.selected mirror and the settings syncing guard.
A row that opens a section paints the section header band inside its own cell, so capturing the drag placeholder height from getCellBounds folded that band in. The gap then rendered a band taller than the row, and because the equal-height pass measures the gap like any other row, every row stretched to that inflated height. Measure the row body instead, which is also what the floating drag image paints.
rowImage translated the graphics by the body's offset inside the cell before calling wrap.paint(). paint() already maps the graphics origin to the component's own top-left, so the offset was applied twice. On rows that do not open a section the offset is zero and nothing looked wrong, but on a row that carries the section header band the copy was shifted up by the band height, leaving the dragged row sliced off at the top and the rest of the image blank.
…olicy Four call sites still rebuilt their list with ActiveListSelection.Index(selectedIndex()), a hand-rolled Slide that pinned the old slot even when the selected row survived at a new index — McpSettingsUi.edit could therefore land on a different server whenever the reload reordered rows. Replace them with Slide, or Key where the mutated row is known, and delete the helper and the Index policy behind them. Scrolling was a policy case too (PreserveNoScroll), which meant a plain refresh dragged the viewport back to the selection on every status poll. Scroll from sync() only when the refresh actually moved the selection, which collapses Preserve/PreserveNoScroll into one policy and leaves the viewport alone while rows are merely re-found. Also fold the remaining duplicates into the shared path: WorktreeSessionEditorPanel names the shown session with Key instead of update-then-select, HistoryPanel drops its private restore wrapper and its copy of move() (which stepped over unfiltered rows), and move() now treats an empty selection as starting past the near end.
The platform resolves every svg-backed icon to a 1x1 placeholder when GraphicsEnvironment.isHeadless(), which is how CI runs but not how the tests run locally on macOS. The test compared those placeholder widths against the custom-painted activity icons, so it passed locally and failed CI with expected:<1> but was:<16>. Turn real loading on for the assertions with IconLoader.activate(), the platform's TestOnly hook for exactly this, and restore the ambient state afterwards. Tighten the load assertions to > 1 so the placeholder can no longer satisfy them. Verified by forcing -Djava.awt.headless=true on the frontend test JVM: the test fails before this change and the whole frontend suite passes after it.
The workflows page landed on main while selectionIndex() was being removed here, so the merge left it calling a helper that no longer exists. Delete moves the highlight to the row that took the slot, which is what ActiveListSelection.Slide does.
Code Review SummaryStatus: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (2 files)
Fix these issues in Kilo Cloud Previous Review Summaries (6 snapshots, latest commit e9f56d4)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit e9f56d4)Status: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (17 files)
Fix these issues in Kilo Cloud Previous review (commit d4f869d)Status: 4 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (37 files)
Fix these issues in Kilo Cloud Previous review (commit d94f2bf)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (19 files)
Fix these issues in Kilo Cloud Previous review (commit ce5a71e)Status: 8 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (27 files)
Fix these issues in Kilo Cloud Previous review (commit 32573a0)Status: 9 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (7 files)
Fix these issues in Kilo Cloud Previous review (commit 76faa30)Status: 9 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (38 files)
[Snapshot truncated.] Additional previous summary content was truncated to keep this comment within platform limits. Reviewed by grok-4.6 · Input: 100.9K · Output: 11.7K · Cached: 690.9K Review guidance: REVIEW.md from base branch |
Use the Kilo running spinner anywhere the session UI showed the default platform spinner, so progress indicators match the Agent Manager running state. Restore branch and lock glyphs as the resting Agent Manager worktree icons, add a JetBrains-styled local monitor icon for the current checkout, and remove the now-unused idle dot icon.
Keep the PR state badge first, but render the pull request number after the PR title so the header reads like the worktree list and avoids leading with metadata.
ActiveList titles were rendered bold for every row, which made Agent Manager, history, and settings lists read heavier than standard IDE lists and trees. Use the platform plain text style for the primary row title while keeping the explicit foreground so selected and deleting rows retain their color behavior.
Keep ActiveList titles bold, but keep descriptions muted even under selection so the secondary line does not collapse into the title foreground. Recolor the resting worktree glyphs to the New UI secondary icon palette, which matches Label.infoForeground/context help text, and paint the running spinner in neutral icon grey instead of green so it stays bright without becoming a colored status light.
Add a branch/PR dock above the chat prompt in the JetBrains sidebar. It shows the current branch's PR (state badge + title + changes) when one exists, and otherwise a centered row of New Worktree / Move to Worktree actions plus a changes badge, matching the VS Code sidebar dock. New Worktree and Move to Worktree are registered platform actions (Kilo.Chat.NewWorktree, Kilo.Chat.MoveToWorktree) rendered through an ActionToolbar; each is invisible when not enabled, so the dock collapses when there is nothing to show. New Worktree opens the Agent Manager New Worktree dialog and switches to that tab after the dialog closes. Move to Worktree captures uncommitted changes, creates a worktree at the source HEAD, transfers the changes, and forks the session into it, streaming progress into the dock. Branch/PR status uses the session's resolved directory (not the synthetic split-mode project path) so the PR always matches the checked-out branch.
…idle New Worktree and Move to Worktree were offered mid-turn, where neither is a sensible thing to invoke: the move aborts the session and forks it, and both compete with a running agent. The dock's action row now withdraws while the session is active (any busy state, including a pending permission or question), matching the VS Code sidebar dock. A move in progress keeps the row so its spinner and stage stay visible, and the PR row is unaffected because it is informational rather than an action. The dock sits outside the scroll pane, so releasing its row resizes the transcript viewport twice per turn instead of once per session. SessionScroll already re-pins from its viewport listener, but followTail() routed the not-following case through followBottom(false), which bumped the generation counter and aborted in-flight multi-pass chains on the first streamed content growth. It now re-pins only when already following and otherwise just refreshes the jump button, which is what onViewport wanted all along. The connection-panel layout tests compared an absolute connection.y across a state change, encoding an assumption that the bottom container never changes height. They now assert the actual invariant: the banner stays one content gap above that container.
Emit a move error for every failure path and roll back a worktree left by a failed move, so the Agent Manager row cannot stay stuck on its last stage. Fail patch capture loudly through the platform command line instead of treating a git failure as a clean tree. Keep the worktree session editor identity as the path alone and hand the forked session over through a one-shot store, so a move no longer opens a rival tab for the same worktree. Stop clicks and drags in the empty space under a short list from acting on the last row, carry the dragged row identity through the placeholder, let a failed branch status surface instead of posing as healthy git, and require a persisted session before offering Move to Worktree. Also make three worktree RPC tests void-returning: JUnit silently skips @test methods that return a value, so they never ran.
What changed
This improves the JetBrains Agent Manager and chat worktree experience, with a focus on making worktrees easier to manage, sessions easier to follow, and the chat UI more polished.
Features
ChatandAgentsas tool-window tabs.Fixes and polish
Verification
./gradlew typecheck./gradlew test