Skip to content

feat(jetbrains): improve session changes and diff review - #12612

Merged
kirillk merged 30 commits into
mainfrom
lovely-wallflower
Jul 31, 2026
Merged

feat(jetbrains): improve session changes and diff review#12612
kirillk merged 30 commits into
mainfrom
lovely-wallflower

Conversation

@kirillk

@kirillk kirillk commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Issue

No tracked issue — batch of JetBrains session and diff improvements from dogfooding.

Features

  • Queue multiple pending permission requests and advance through them one by one.
  • Show modified files under the assistant turn that produced them.
  • Polish the modified-files card header to match the session UI.
  • Keep transcript auto-scroll behavior stable while messages expand and collapse.
  • Add a branch diff editor with a file tree and per-file navigation.
  • Show stale diff views with a refresh action when files change on disk.
  • Improve diff tree badges and changed-file grouping.
  • Add Open in Diff Viewer actions to inline edit and modified-files cards.
  • Hide zero-value diff stats so badges only show meaningful changes.
  • Show aligned old/new line numbers in inline and popup diffs.

Screenshots

Screen Shot 2026-07-30 at 11 43 43 AM Screen Shot 2026-07-30 at 11 46 07 AM

Verification

  • ./gradlew typecheck
  • ./gradlew test
  • Targeted frontend tests for permissions, modified files, diff editor, and inline diff cards

Checklist

  • Tests/verification described
  • Screenshots/video included for visual changes
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

kirillk added 13 commits July 27, 2026 17:04
The session UI tracked only one permission at a time, so when the CLI
asked for several permissions in one turn the last PermissionAsked
overwrote the previous one and replying it dropped the session to Busy
while the earlier permission stayed pending forever.

Maintain a controller-owned FIFO queue keyed by permission id. The model
still holds one active permission; advancing is driven by the
authoritative PermissionReplied event so it behaves the same whether the
reply came from this client, another client, or auto-approve. Recovery
and child-session permissions seed the same queue in FIFO order, and the
queue is cleared on abort, auto-approve enable, and subscription reset.
# Conflicts:
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/views/TurnView.kt
Show JetBrains session changes against the base branch after restart by wiring the diff editor entry point and matching VS Code diff semantics for uncommitted and untracked files.
- Color file names with IntelliJ VCS status colors (added/modified/deleted/untracked)
- Add tree context menu with Open File (F4) and Refresh actions
- Show changed-file count in the toolbar before the diff stat badge
- Fix keyboard selection snap-back while preserving debounced buffering
@kirillk kirillk changed the title feat(jetbrains): per-turn modified files, pending permission queue, and scroll fixes feat(jetbrains): improve session changes and diff review Jul 30, 2026
@kirillk
kirillk marked this pull request as ready for review July 30, 2026 15:46
Comment thread packages/ui/src/pierre/virtualizer.ts
Comment thread packages/opencode/src/mcp/catalog.ts Outdated
Comment thread .changeset/jetbrains-modified-files-view.md Outdated
@kilo-code-bot

kilo-code-bot Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
.../session/controller/SessionController.kt 788 the drain's Busy transition is exempted only for skip ids, so a skill-shell card that arrives while pendingPermissions() is in flight still gets hidden with nothing to re-promote it
Notes and assumptions
  • Incremental review of 36fbfbc..e20f5ec (2 files, 55 lines). Files outside that range were not re-reviewed.
  • Resolved this revision: the previous finding on the skill-shell branch. setAutoApprove now runs pending.clear() before approve(), so the synchronous show() is the last writer and the preserved card stays tracked in pending; the new id !in skip guard stops the drain from flipping that card to Busy. Both paths are covered by the two new PermissionQueueTest cases, which exercise the real controller/EDT and assert the card survives abort() purge.
  • Read-only run: ./gradlew typecheck / test were not executed; CI-owned checks were intentionally skipped.
Files Reviewed this revision (2 files)
  • .../session/controller/SessionController.kt - 1 issue (drain Busy transition vs. concurrently arriving skill-shell card)
  • .../session/controller/PermissionQueueTest.kt - no issues

Fix these issues in Kilo Cloud

Previous Review Summaries (7 snapshots, latest commit 36fbfbc)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 36fbfbc)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
.../session/controller/SessionController.kt 731 making the skill-shell branch synchronous lets setAutoApprove's pending.clear() (line 397) drop the card show() just queued, recreating the untracked-card ghost (and losing it entirely when the drain takes the setState(Busy) branch)
Notes and assumptions
  • Incremental review of 56e0785..36fbfbc (permission queue determinism). Files outside that commit were not re-reviewed.
  • Read-only run: ./gradlew typecheck / test were not executed; CI-owned checks were intentionally skipped.
  • Resolved this revision: both previous findings. The skill-shell / non-auto-approve decision now happens synchronously on the EDT, so back-to-back asks keep arrival order, and the auto-approve failure card goes through show() so purgePending on Stop / TurnClose / idle can clear it (covered by the new auto approve failure card is queued and purged by stop test).
  • gh pr diff returned a stale patch for SessionController.kt (pre-show() content); diff targets were computed from the local base...HEAD diff at 36fbfbc, which matches how GitHub repositioned the existing comments.
Files Reviewed this revision (3 files)
  • .../session/controller/SessionController.kt - 1 issue (auto-approve toggle clears the just-queued card)
  • .../session/controller/PermissionQueueTest.kt - no issues (exercises the real abort/purge path)
  • .../client/testing/FakeSessionRpcApi.kt - no issues

Fix these issues in Kilo Cloud

Previous review (commit 56e0785)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
.../session/controller/SessionController.kt 734 the skill-shell enqueue runs inside cs.launch even though the branch never suspends, so two rapid permissions can land in pending out of order (and the new FIFO test can flake); these calls also mutate state outside updateModel, unlike the drain (778) and child-recovery (1212) paths

SUGGESTION

File Line Issue
.../session/controller/SessionController.kt 367 purgePending returns early when nothing was queued, so the auto-approve error card set at line 752 (never enqueued) survives Stop / TurnClose / SessionIdle and can only fail with NotFoundError
Notes and assumptions
  • Incremental review of 79ac066..56e0785 (single commit: compacted-summary parsing + queued permission ghosts). Files outside that commit were not re-reviewed.
  • Read-only run: ./gradlew typecheck / test were not executed; CI-owned checks were intentionally skipped.
  • Resolved this revision: all four previous findings. parseMessage now uses the safe obj() accessor so summary: true on assistant messages no longer throws (covered by the updated history test), abort() purges outstanding permission state instead of only clearing the queue, both auto-approve entry points queue every skill-shell request through enqueue/promote instead of overwriting the visible card, and capDiff skips an oversized patch while still fetching later small ones with a bounded miss count.
  • capDiff's used >= cap in the oversized branch is unreachable (the fitting branch already latches full at that point) — harmless, noted only for clarity.
Files Reviewed this revision (6 files)
  • .../session/controller/SessionController.kt - 2 issues (abort purge, auto-approve queueing)
  • .../backend/cli/KiloCliDataParser.kt - no issues (safe obj() accessor verified against the CLI schema)
  • .../backend/rpc/KiloWorkspaceRpcApiImpl.kt - no issues (capDiff miss bound verified; per-file blanks are handled independently by the client)
  • .../session/controller/PermissionQueueTest.kt - no issues (see the FIFO-ordering note above)
  • .../backend/rpc/BranchDiffTest.kt - no issues
  • .../backend/cli/KiloCliDataParserTest.kt - no issues

Fix these issues in Kilo Cloud

Previous review (commit 79ac066)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

CRITICAL

File Line Issue
.../backend/cli/KiloCliDataParser.kt 1042 obj["summary"]?.jsonObject throws for assistant messages, where summary is a boolean (compaction.ts sets summary: true), so history parsing and message.updated break for any compacted session

WARNING

File Line Issue
.../session/controller/SessionController.kt 367 abort() clears pending but not the visible card, so the later purge sites no-op and Stop leaves a dead permission card that can only fail with NotFoundError
.../session/controller/SessionController.kt 1595 the purge assumes every visible card is queued, but auto-approve skill-shell cards (approve(), drainAutoApprove) set AwaitingPermission without enqueue, so they are never purged and overwrite each other

SUGGESTION

File Line Issue
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 534 full latches on the first oversized full-context patch, so one large file early in the order blanks the patch for every later file even though DIFF_CAP was never spent
Notes and assumptions
  • Incremental review of a0e6273..79ac066: the main merge (7d65efa, conflict resolutions in KiloCliDataParser.kt, SessionController.kt, ChatDto.kt) plus the review-feedback commit. Unrelated files pulled in by the merge were not reviewed.
  • Read-only run: ./gradlew typecheck / test were not executed, and CI-owned checks were intentionally skipped.
  • Resolved this revision: all four previous findings. capDiff now stops fetching once a patch overflows the cap (previous WARNING), the badge path streams a newline count instead of reading each untracked file (countLines matches lines() including the empty-file case), buildBranchDiff/splitGitPatch are deleted with the suite re-targeted onto the live capDiff/parseNumstat/parseNameStatus path, and status("idle") no longer clobbers a permission promoted by purgePending.
  • The CRITICAL and both WARNINGs come from the merge of the skill-shell permission work in main with the new permission queue: the two features each assume they own the visible AwaitingPermission state.
Files Reviewed this revision (6 files)
  • .../backend/cli/KiloCliDataParser.kt - 1 issue (merge resolution)
  • .../session/controller/SessionController.kt - 2 issues (merge resolution + idle fix)
  • .../backend/rpc/KiloWorkspaceRpcApiImpl.kt - 1 issue (capDiff, countLines)
  • .../backend/rpc/BranchDiffTest.kt - new suite verified against the live capDiff path
  • .../session/controller/PermissionQueueTest.kt - new idle/promote regression case verified
  • .../shared/.../rpc/dto/ChatDto.kt - no issues

Fix these issues in Kilo Cloud

Previous review (commit a0e6273)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 276 used only advances when a patch fits, so one oversized file leaves the cap untripped and every remaining file still spawns a full-context git diff that is then discarded

SUGGESTION

File Line Issue
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 270 the patches = false badge path still readBytes() every untracked file just to count additions, on every turn end and revert
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 285 buildBranchDiff / splitGitPatch remain production-dead (only BranchDiffBuildTest calls them), so the suite pins behavior branchDiff no longer has
.../session/controller/SessionController.kt 1589 in the "idle" status branch, purgePending's re-promote is overwritten by SessionState.Idle one statement later (dead promote + extra StateChanged)
Notes and assumptions
  • Incremental review of 568b21c..a0e6273 (2 files). Read-only: ./gradlew typecheck / test were not run, and CI-owned checks were intentionally skipped.
  • Resolved this revision: the CRITICAL DiffPatchReconstruct finding. sides() now iterates patch.split('\n').dropLastWhile { it.isEmpty() }, so the trailing element of a newline-terminated patch no longer inflates oldSeen/newSeen, and newline terminated full context patch stays renderable covers the real-git input shape the earlier trimIndent() fixtures missed.
  • No new issues were found in the incremental diff. The remaining four findings are carried forward after re-verifying them against current HEAD (KiloWorkspaceRpcApiImpl.kt:270/276/285, SessionController.kt:1589); they already have active inline comments and were not re-posted.
Files Reviewed this revision (2 files)
  • .../client/diff/DiffPatchReconstruct.kt - previous CRITICAL fixed
  • .../client/diff/DiffPatchReconstructTest.kt - new regression test verified

Fix these issues in Kilo Cloud

Previous review (commit 568b21c)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

CRITICAL

File Line Issue
.../client/diff/DiffPatchReconstruct.kt 56 the trailing newline in every git diff / CLI patch adds a phantom line to oldSeen/newSeen, so the new length guard reports renderable = false and the diff editor falls back to "empty vs raw patch text" for essentially every file

WARNING

File Line Issue
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 276 used only advances when a patch fits, so one oversized file leaves the cap untripped and every remaining file still spawns a full-context git diff that is then discarded

SUGGESTION

File Line Issue
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 270 the patches = false badge path still readBytes() every untracked file just to count additions, on every turn end and revert
.../backend/rpc/KiloWorkspaceRpcApiImpl.kt 285 buildBranchDiff / splitGitPatch are now unreachable from production; BranchDiffBuildTest pins behavior branchDiff no longer has
.../session/controller/SessionController.kt 1589 in the "idle" status branch, purgePending's re-promote is overwritten by SessionState.Idle one statement later (dead promote + extra StateChanged)
Notes and assumptions
  • Incremental review of a103f4a..568b21c (32 files). Read-only: ./gradlew typecheck / test were not run, and CI-owned checks (lint, typecheck, unused imports, annotation/marker guards) were intentionally skipped.
  • All findings from earlier revisions are resolved in this revision and are not carried forward: --relative path scoping, per-file patch fetching, defaultBranch remote-HEAD resolution, GET /session/:id/diff for session diffs, purgePending for permission ghosts, the KiloInlineDiffStore LRU, processor-scoped diff listeners, path() containment, CancellationException rethrow, pureDiff/DiffLineNumbers alignment, gutter re-install on re-expand (EditToolView.expand calls body.applyStyle), parts.link unbinding, conditional transcript refresh(), split refreshJob/openJob, badge keyboard activation, per-tool diff titles, the localized "Diff unavailable" string, and the dead bundle keys. packages/ui/ and packages/opencode/ are now byte-identical to main, so the shared-file findings there no longer apply.
  • The CRITICAL finding is not covered by the new tests because trimIndent() fixtures never carry the trailing newline that runWorkspaceGit stdout and CLI patches do.
Files Reviewed this revision (32 files)
  • .../client/diff/DiffPatchReconstruct.kt - 1 issue
  • .../backend/rpc/KiloWorkspaceRpcApiImpl.kt - 3 issues
  • .../session/controller/SessionController.kt - 1 issue
  • .../backend/rpc/KiloSessionRpcApiImpl.kt, shared/.../KiloWorkspaceRpcApi.kt
  • .../client/app/KiloSessionService.kt, .../client/app/KiloWorkspaceService.kt
  • .../client/diff/DiffBlocks.kt, DiffLineNumbers.kt, KiloDiffEditorContent.kt, KiloDiffEditorKind.kt, KiloInlineDiffStore.kt
  • .../client/session/SessionUi.kt, ui/ModifiedFilesView.kt, ui/SessionMessageListPanel.kt, ui/header/BranchChangesBadge.kt, views/TurnView.kt
  • .../session/views/tool/EditToolView.kt, ToolMarkdownBody.kt, ToolSupport.kt
  • messages/KiloBundle.properties, .changeset/jetbrains-session-diff-improvements.md
  • Tests: DiffLineNumbersTest.kt, DiffPatchReconstructTest.kt, KiloDiffEditorContentTest.kt, KiloInlineDiffStoreTest.kt, PermissionQueueTest.kt, EditToolViewTest.kt, FakeWorkspaceRpcApi.kt
  • Reverted to main: packages/opencode/src/mcp/catalog.ts, packages/ui/src/components/file.tsx, packages/ui/src/pierre/virtualizer.ts

Fix these issues in Kilo Cloud

Previous review (commit a103f4a)

Status: 29 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 13
SUGGESTION 16
Issue Details (click to expand)

New in this revision

Severity File Line Issue
SUGGESTION .../client/diff/KiloDiffEditorContent.kt 21 IdeActions import is dead after the prev/next diff actions were dropped from the tree toolbar; no CI check flags it
SUGGESTION .../client/diff/KiloDiffEditorContentTest.kt 182 the new toolbar-order test asserts on a freshly constructed treeToolbarGroup, not on the toolbar buildTreePanel installs, so it can't catch the regression it guards

Still open from earlier revisions

Severity File Line Issue
WARNING .../client/session/SessionUi.kt 839 single branchJob means an open = false refresh (idle transition / revert) cancels an in-flight badge click, so the diff editor silently never opens
WARNING .../client/session/SessionUi.kt 837 badge refresh refetches the full branch patch payload (up to DIFF_CAP) on every turn end and revert, and the badge retains it just to show counts
WARNING .../client/session/SessionUi.kt 866 reopening an already-open branch tab only focuses it, so the seeded snapshot is never popped: stale content, a permanent store entry, and a later Refresh that replays the stale seed
WARNING .../backend/rpc/KiloWorkspaceRpcApiImpl.kt 261 git diff paths are repo-root-relative while ls-files --others paths are cwd-relative; missing --relative breaks subdirectory projects
WARNING .../backend/rpc/KiloWorkspaceRpcApiImpl.kt 263 --unified=2147483647 plus unbounded untracked reads materialize everything before DIFF_CAP applies
WARNING .../session/controller/SessionController.kt 1352 aborted turns leave a stuck permission card and ghost queue entries
WARNING .../session/controller/SessionController.kt 1177 untrackChild never purges that child's pending entries
WARNING .../client/diff/KiloInlineDiffStore.kt 12 store never evicts inline (turn:/tool:) tokens; pop only helps the branch source
WARNING .../client/diff/KiloDiffEditorContent.kt 180 listener removal hooks accumulate under the per-render parent on every applyFiles
WARNING .../client/diff/DiffLineNumbers.kt 43 isNotBlank() doesn't mirror pureDiff's trimming, shifting the gutter
WARNING .../session/views/tool/ToolMarkdownBody.kt 93 gutter is not reinstalled in applyStyle, so line numbers are lost after collapse/re-expand
WARNING .../session/views/tool/EditToolView.kt - binding parts.link makes a file-path click both open the file and toggle the card
WARNING .../session/ui/SessionMessageListPanel.kt 171 unconditional refresh() on every MessageUpdated relayouts the transcript during streaming
WARNING packages/ui/src/pierre/virtualizer.ts 18 unrelated spacing: 0 removal changes shared web-UI diff geometry
SUGGESTION .../client/session/SessionUi.kt 565 computeInitialBranchChanges() on model events is unreachable — init already set branchStarted
SUGGESTION .../session/ui/header/BranchChangesBadge.kt 42 badge is mouse-only (replaces an AbstractButton) and the click handler ignores isEnabled
SUGGESTION .../session/views/tool/EditToolView.kt 239 toDiffFiles(item) rebuilds every DiffFileDto (copying patch text) per streaming delta just for an emptiness check
SUGGESTION .../session/views/tool/EditToolView.kt 295 per-tool diff tabs all get the generic Edit/Patch title, so concurrently open tabs are indistinguishable
SUGGESTION messages/KiloBundle.properties 130 diff.editor.session.title.named and diff.editor.inline.title have no readers
SUGGESTION .../backend/rpc/KiloWorkspaceRpcApiImpl.kt 400 defaultBranch probes only local main/master, silently degrading to HEAD
SUGGESTION .../backend/rpc/KiloSessionRpcApiImpl.kt 145 flat-mapped per-turn summaries duplicate the cumulative GET /session/:id/diff set
SUGGESTION .../client/diff/KiloDiffEditorContent.kt 317 resolved path isn't containment-checked before openFile
SUGGESTION .../client/diff/KiloDiffEditorKind.kt 118 runCatching swallows CancellationException; refresh job isn't disposable-bound
SUGGESTION .../client/diff/DiffPatchReconstruct.kt 19 hunk offsets ignored, so partial-context/multi-hunk patches reconstruct wrong while reporting renderable = true
SUGGESTION .../client/app/KiloWorkspaceService.kt 169 swallowed failures render a git error as "no changes"
SUGGESTION packages/opencode/src/mcp/catalog.ts 69 shared upstream file diverges for defensive code the pinned SDK types make unreachable
SUGGESTION .../client/diff/DiffBlocks.kt 29 hardcoded user-visible "diff unavailable" string

Resolved in this revision

File Issue
.changeset/jetbrains-modified-files-view.md SUGGESTION: the changeset claiming a "session-level" card is gone; the seven per-feature changesets are consolidated into one accurate, user-facing .changeset/jetbrains-session-diff-improvements.md
Notes and assumptions
  • Incremental review of 8a15b00..a103f4a (13 files: 8 changeset files, 2 deleted .kilo/plans/*.md, and 3 JetBrains source/test files). Reviewed read-only; ./gradlew typecheck/test were not run, and CI-owned checks were intentionally skipped.
  • Dropping ACTION_PREVIOUS_DIFF / ACTION_NEXT_DIFF from the tree toolbar is not flagged as a regression: CacheDiffRequestChainProcessor's own toolbar already exposes previous/next difference and previous/next file navigation, so the tree toolbar entries were duplicates.
  • Removing the centerGroup left border in SessionHeaderPanel is a deliberate spacing tightening; the custom doLayout/getPreferredSize already derive everything from insets, so the layout stays consistent.
  • Deleting the two .kilo/plans/*.md planning docs from the branch is good hygiene, not a finding.
  • Findings on files untouched by this revision were re-checked where the changed files overlapped (KiloDiffEditorContent.kt lines 180 and 317 both still hold) and are otherwise carried forward unchanged.
Files Reviewed this revision (13 files)
  • .../client/diff/KiloDiffEditorContent.kt - 1 new issue (+2 carried)
  • .../client/diff/KiloDiffEditorContentTest.kt - 1 new issue
  • .../session/ui/header/SessionHeaderPanel.kt
  • .changeset/jetbrains-session-diff-improvements.md (new, consolidates 7 deleted changesets)
  • .changeset/jetbrains-branch-diff-editor.md, jetbrains-diff-navigation-reload.md, jetbrains-inline-diff-improvements.md, jetbrains-modified-files-view.md, jetbrains-permission-queue.md, jetbrains-session-header-changes-badge.md, quiet-diff-tree.md (deleted)
  • .kilo/plans/1785179166798-jetbrains-modified-files-view.md, .kilo/plans/1785185060000-jetbrains-per-turn-modified-files.md (deleted)

Fix these issues in Kilo Cloud

Previous review (commit 8a15b00)

Status: 28 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 13
SUGGESTION 15
Issue Details (click to expand)

New in this revision

Severity File Line Issue
WARNING .../client/session/SessionUi.kt 839 single branchJob means an open = false refresh (idle transition / revert) cancels an in-flight badge click, so the diff editor silently never opens
WARNING .../client/session/SessionUi.kt 837 badge refresh refetches the full branch patch payload (up to DIFF_CAP) on every turn end and revert, and the badge retains it just to show counts
WARNING .../client/session/SessionUi.kt 866 reopening an already-open branch tab only focuses it, so the seeded snapshot is never popped: stale content, a permanent s

[Snapshot truncated.]

Additional previous summary content was truncated to keep this comment within platform limits.


Reviewed by claude-opus-5 · Input: 38 · Output: 12.4K · Cached: 1.3M

Review guidance: REVIEW.md from base branch main

Roll back unrelated shared changes and address the PR review on the
JetBrains session-diff work.

Shared reverts (keep the PR scoped to JetBrains):
- Restore packages/opencode/src/mcp/catalog.ts to upstream (the defensive
  MCP error-shape guard was unnecessary and widened the fork diff).
- Restore the ui `spacing: 0` diff metrics in file.tsx / virtualizer.ts.

Backend:
- branchDiff: add --relative so tracked paths are project-relative and
  scoped to the opened directory (matches the untracked list in monorepos).
- branchDiff: fetch patches per file and stop at DIFF_CAP instead of
  materializing the whole repo's full-context diff up front; add a
  patches=false stat-only path for the header badge.
- defaultBranch: resolve via origin/HEAD then origin/local main|master
  using fully-qualified refs (no tag confusion, non-main/master repos).
- session diff() now uses GET /session/:id/diff (cumulative, deduped,
  unquoted) instead of concatenating per-turn summaries.

Frontend:
- Permission queue: purge ghost permissions the CLI abandons on turn
  interruption (TurnClose / idle) and on child untrack.
- Diff editor: bind processor listeners to the processor, constrain
  "open file" to the diff directory, bind the refresh coroutine to the
  view, stop swallowing CancellationException, and make the branch source
  fetch authoritative (drop the store seeding side channel).
- KiloInlineDiffStore: bound with a small LRU.
- DiffLineNumbers / pureDiff: keep in-hunk header-shaped lines and mirror
  trim('\n') so the gutter stays aligned; DiffPatchReconstruct treats
  multi-hunk / length-mismatched patches as non-renderable.
- ToolMarkdownBody re-installs the diff gutter on applyStyle so it
  survives collapse/re-expand.
- EditToolView: don't bind the file link for toggling, avoid re-parsing
  diff metadata per delta, keep the file name in single-file diff titles.
- Only relayout the transcript when a turn's modified-files card changed.
- Propagate branch/session diff errors so the editor shows a retry.
- Badge: separate open vs refresh jobs, keyboard-accessible, gate on
  isEnabled; remove dead history-load refresh branches.
- DiffBlocks / KiloBundle: bundle key for the diff-unavailable string,
  drop unused bundle keys.
Address the follow-up review on the diff tree toolbar:
- Remove the now-unused IdeActions import from KiloDiffEditorContent
  (its only users were the removed prev/next-diff lookups).
- KiloDiffEditorContentTest now asserts on the ActionToolbar the view
  actually installs (via its actionGroup) instead of a freshly built
  detached group, so it guards the real "toolbar lost its actions"
  regression.
kirillk added 3 commits July 30, 2026 17:57
DiffPatchReconstruct counted the trailing empty element that
split('\n') produces for a newline-terminated patch (every real git
patch) as a hunk body line. That inflated oldSeen/newSeen past the
header lengths, so the renderable check failed and every modified-file
diff fell back to the empty-vs-patch view — rendering the whole file
(and its line-number gutter) as all-added green.

Drop the trailing empty split artifact before counting, mirroring the
edge trim in DiffLineNumbers. Add a newline-terminated regression test;
the existing trimIndent fixtures masked the bug by omitting the final
newline.
# Conflicts:
#	packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt
…d permissions

Address review feedback on the session/branch diff work:

- branchDiff no longer keeps calling git per file after the DIFF_CAP
  budget is exhausted. A single oversized full-context patch previously
  left the budget open, so every remaining file still spawned a
  `git diff --unified=inf` that was fetched and discarded — hundreds of
  subprocesses per open/refresh on large branches. The new pure
  `capDiff` accumulator stops fetching once a patch overflows the cap.
- The stats-only (badge) untracked path now streams a newline count
  instead of reading each file into a String on every turn end/revert.
- Delete the now-dead `buildBranchDiff`/`splitGitPatch` helpers and
  re-target the suite onto the live `capDiff` path plus `parseNumstat`/
  `parseNameStatus` (renamed BranchDiffBuildTest -> BranchDiffTest).
- SessionController.status("idle") no longer clobbers a permission that
  purgePending just promoted from an unpurged child session; it mirrors
  idle() and leaves the promoted card in place.
Address follow-up review on the JetBrains diff/session work:

- Parse message `summary` with the safe JSON-object accessor so assistant
  compaction summaries (`summary: true`) do not crash history/event parsing.
- Route auto-approve skill-shell permission cards through the same queue as
  ordinary permissions, preserving FIFO order and letting purge/resolve logic
  handle them consistently.
- Make Stop purge current root/child permission ghosts instead of clearing
  only the backing queue and leaving a dead card on screen.
- Let capDiff skip an oversized patch and still fetch later small patches,
  while bounding repeated oversized misses to keep subprocess count finite.

Add regression coverage for compacted assistant messages, oversized diff cap
behavior, Stop ghost purge, and multi skill-shell queueing.
Follow-up review on the permission queue:

- Decide the skill-shell "needs a human" case synchronously on the EDT in
  approve() and enqueue there, so back-to-back auto-approve asks keep
  arrival (FIFO) order instead of racing two independent coroutines. Only
  the replyPermission RPC stays in a coroutine.
- Route permission enqueue/promote through a show() helper wrapped in
  updateModel, so cards added from approve()/abort() preserve the
  transcript's bottom-follow like the drain and child-recovery paths.
- Queue the auto-approve error card too, so pending stays the single
  source of truth and Stop / TurnClose / idle purge can clear it rather
  than stranding a card that can only fail with NotFoundError.

Add coverage for the purged auto-approve error card.
// machine reply. Decide and enqueue synchronously on the EDT so back-to-back asks keep
// arrival (FIFO) order, matching asked()'s non-auto path; only the RPC needs a coroutine.
if (!autoApprove || restore().meta.raw["skillShell"] == "true") {
show(restore())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: making this branch synchronous lets setAutoApprove wipe the card it just queued

setAutoApprove(true) writes the setting first, then (line 392) calls approve(current.permission) for the visible card, and then does pending.clear() on line 397 before draining. With the old deferred cs.launch { edt { enqueue(...) } }, the enqueue landed after that clear(), so a visible skill-shell card stayed tracked. Now show(restore()) runs inline: it enqueues, skips promote() (state is already AwaitingPermission), and line 397 immediately drops the entry again.

The result is the exact ghost this commit is fixing elsewhere — a visible skill-shell card that is not in pending, so purgePending on Stop / TurnClose / idle can't clear it, and answering it later fails with NotFoundError. It's worse if the drain finds other permissions: drainAutoApprove excludes skip, so cards is empty, count > 0 takes the setState(Busy) branch (line 781) and the card disappears with nothing left in pending to re-promote, leaving the CLI waiting on a request the user can no longer answer.

Suggestion: move pending.clear() above the approve(current.permission) call in setAutoApprove (or have it drop only the ids not being shown), so the synchronous show() is the last writer. A regression test that toggles auto-approve on while a skill-shell card is visible and then aborts would cover it.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e20f5ec. setAutoApprove now runs pending.clear() before re-surfacing the visible card, so the synchronous show() inside approve() is the last writer and the preserved skill-shell card stays tracked in pending (a later Stop / TurnClose / idle purge can clear it instead of stranding a card that only NotFoundErrors). I also closed the "worse if the drain finds other permissions" case: drainAutoApprove now guards its setState(Busy) branch with current.permission.id !in skip, so it never flips a card handled synchronously by the caller to Busy and vanishes a preserved skill-shell card. Covered by test toggling auto approve on keeps a visible skill shell card queued for purge (toggle on with the card up, then abort → Idle) and test toggling auto approve on keeps skill shell card while draining other permissions (the other permission is replied while the skill-shell card stays visible); reverting either fix independently makes the corresponding test fail.

@marius-kilocode

Copy link
Copy Markdown
Collaborator

I like the tooltip, maybe we need that in vs code as well!

Toggling auto-approve on while a skill-shell permission card is visible
stranded the card: approve() synchronously re-enqueues it via show(), but
the trailing pending.clear() dropped that entry, leaving a ghost card not
in pending that Stop/idle purge could not clear (answering it later failed
with NotFoundError). Clear the queue before re-surfacing the card so show()
is the last writer. Also guard drainAutoApprove so it never flips a card
that was handled synchronously (in skip) to Busy, which otherwise hid a
preserved skill-shell card with no reply path left.
// replied to it — already Busy — or re-showed a skill-shell card we must keep).
// Never transition it to Busy here or the preserved skill-shell card vanishes
// with no reply path left.
if (current is SessionState.AwaitingPermission &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: the skip exemption only covers cards that existed before the drain started

The new id !in skip guard fixes the card setAutoApprove preserved, but this branch still flips any visible AwaitingPermission for ids to Busy, including one that has nothing to do with what the drain replied to. Concrete window:

  1. setAutoApprove(true) clears pending, approve() replies to the visible non-skill-shell card (state Busy), drainAutoApprove launches and suspends in sessions.pendingPermissions(...).
  2. While that RPC is in flight, a new skill-shell PermissionAsked arrives on the EDT → asked()approve()show()AwaitingPermission(permX).
  3. The drain resumes with a server snapshot taken before permX existed, so cards is empty while count > 0, and this branch sets Busy — hiding permX.

permX stays in pending, so it isn't leaked, but nothing re-promotes it unless another permission.replied or a purge fires. If it's the only outstanding request the turn just sits on "considering" until the ask is abandoned. Consider narrowing the condition to the permissions the drain actually answered (e.g. have replyAll return the replied ids and require current.permission.id to be one of them) instead of exempting only skip.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — this is a real but low-severity race, tracking it as a follow-up rather than blocking this PR. It does not leak: permX stays in pending, and the drain replied to count > 0 permissions, so the resulting permission.replied events hit replied() while state is Busy and call promote(), re-surfacing permX. Worst case is a transient "considering" spinner during the pendingPermissions RPC round-trip, not a stuck state or a NotFoundError. The suggested hardening (have replyAll return the replied ids and gate the Busy transition on current.permission.id being one of them, instead of exempting only skip) is the right narrowing and we'll do it in a follow-up.

@kirillk
kirillk merged commit 20c6064 into main Jul 31, 2026
24 checks passed
@kirillk
kirillk deleted the lovely-wallflower branch July 31, 2026 15:04
unixcrh pushed a commit to unixcrh/kilocode that referenced this pull request Aug 1, 2026
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
t7tran pushed a commit to t7tran/kilocode that referenced this pull request Aug 14, 2026
feat(jetbrains): improve session changes and diff review
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants