Commit 1903541
authored
π€ feat: add review_pane_update tool + Assisted review toggle (#3331)
## Summary
Adds two new agent tools β `review_pane_update` and `review_pane_get` β
that let an agent flag specific code regions in the Review pane for the
user to review next. The Review pane gains an "Assisted" toggle that
filters to those hunks; flagged hunks are always pinned at the top in
agent-declared order, the agent's optional comment is rendered above
each hunk, and the hunk body is trimmed to just the flagged new-side
line range with the surrounding diff hidden behind a "Show N lines β¦"
affordance. Both new tools get purpose-built transcript renderers
instead of the generic JSON-dump fallback. The Review tab auto-focuses
on agent-flagged hunks and shows a pulsing Sparkles indicator while
there's unread agent focus pending. Tool state is persisted under the
workspace session dir so `add` semantics survive backend restarts.
## Background
When an agent makes a large or risky change, the user often wants to
know *where to look first*. Today there's no signal from the agent into
the review surface β every hunk has equal weight, and a 500-line diff
buries the 20 lines that actually matter. This feature gives the agent a
first-class way to say "review these regions, in this order, with this
context."
## Implementation
**Tools (single source of truth in `toolDefinitions.ts`):**
- `review_pane_update({ operation: "add" | "replace", hunks: [{ path,
comment? }] })` β paths use a familiar `path[:start-end]` syntax
(`"src/foo.ts"`, `"src/foo.ts:42"`, or `"src/foo.ts:10-20"`, new-side
line numbers). `replace` overwrites the set; `add` appends with
dedup-by-`path:range` (preferring the latest comment).
- `review_pane_get()` β returns the current set in declared order so the
agent can inspect before adding more.
**State model:**
- Backend tool state is persisted under
`<workspaceSessionDir>/assistedReview.json` (parallel to `todos.json`),
with writes serialized via `workspaceFileLocks` and `writeFileAtomic`.
The file is unlinked on a clear so a stale file can't shadow a fresh
start. `coerceAssistedReviewHunks` survives corrupted JSON per the
self-healing doctrine β bad data is treated as empty rather than thrown.
- `StreamingMessageAggregator.processToolResult` parses each successful
`review_pane_update` output and rebuilds `assistedReviewHunks` on the
frontend. Because the tool returns the *resulting* list directly,
replaying history naturally reconstructs final state on reload.
- Exposed via `WorkspaceStore.getAssistedReviewHunks(workspaceId)`,
consumed by `ReviewPanel` through `useSyncExternalStore`.
**Review pane UI:**
- One shared module `src/common/utils/review/assistedReview.ts` owns
parsing, matching, and formatting β reused by the tool handler,
aggregator, and panel.
- `applyFrontendFilters` gains an optional `isAssisted` predicate so the
existing read/search pipeline is untouched.
- `ReviewPanel`'s `filteredHunks` memo applies the existing sort, then
partitions into `[assistedBucket, rest]` preserving agent-declared
index, so assisted hunks are always pinned first regardless of the
toggle.
- `ReviewControls` shows an "Assisted β¦ (N)" toggle only when
`assistedCount > 0`, keeping the toolbar compact for normal sessions.
- `HunkViewer` renders the comment in an accent-bordered row above the
diff header; a hunk flagged without a comment gets a subtle left-border
accent.
**Range trimming + show-more:**
- New helper `sliceHunkByNewLineRange`
(`src/browser/utils/review/sliceHunkContent.ts`) walks the diff body
using the same algorithm as `groupDiffLines`, partitions lines into
`before` / `inside` / `after` (attaching `-` lines to the next visible
region; trailing `-` lines to the previous), and returns `null` for
no-op cases (full coverage, out-of-bounds, pure deletions, pure renames)
so the existing single-renderer path still applies.
- `HunkViewer` now renders the inside slice always; before/after stay
hidden behind a `ContextCollapseIndicator` that gained a `mode: "expand"
| "collapse"` prop, so "Show N lines above" and "Collapse N lines above"
share the same squiggle pattern.
- Agent-flagged hunks override the auto-collapse behavior that kicks in
for large hunks / heavy reviews β otherwise the assistance signal got
buried under "Click to expand".
**Chat-transcript renderers:**
- `ReviewPaneUpdateToolCall.tsx` (β120 LOC) β collapsed header shows the
operation (Add / Replace / Cleared) + "N hunks pinned" + rejected count
when present. Expanded view lists each path:range with a
Sparkles-accented comment row. Prefers the *post-merge* result list so
dedup outcomes are visible.
- `ReviewPaneGetToolCall.tsx` (β45 LOC) β single-line preview
("Inspected N pinned hunks"); no expand affordance because the full list
lives in the Review pane.
- Both are registered in `TOOL_REGISTRY` (`getToolComponent.ts`);
`TOOL_NAME_TO_ICON` maps `review_pane_update` β `Sparkles` and
`review_pane_get` β `ScanEye`.
**Focus mode + Review tab pizzazz:**
- The Assisted toggle now auto-flips ON the first time the agent flags
hunks in a session, so the Review pane lands directly on the critical
changes instead of the full diff. A ref-guarded latch makes the user's
subsequent manual toggles stick; a drop-to-zero (agent cleared its hint
set) re-arms the latch so the next batch of flagged hunks gets focus
again.
- `ReviewStats` gains an `unreadAssisted` field β the count of
agent-flagged hunks intersecting the current diff that the user hasn't
acked. Computed in `ReviewPanel` alongside the existing `readHunkCount`
summary and pushed through the unchanged `onStatsChange` callback.
- `ReviewTabLabel` renders a Sparkles pill in `--color-review-accent`
with `animate-pulse` when `unreadAssisted > 0`, alongside the existing
`read/total` badge. Stops pulsing once everything assisted is marked
read. Tooltip explains "N agent-flagged hunk(s) pending review."
Matching is exact on workspace-relative paths (with `oldPath` fallback
for renames), and overlap-based on new-side line numbers (old-side
fallback for pure deletions), so the syntax does what an agent would
naturally expect.
## Validation
- 42 new unit tests:
- 12 for the parser/matcher (`assistedReview.test.ts`)
- 6 for `applyReviewPaneUpdate` semantics (add/replace, dedup, comment
normalization, rejection of malformed filters, clear-via-empty-replace)
- 7 for `sliceHunkByNewLineRange` (whole-hunk coverage, out of bounds,
pure deletions, context-only split, deletion grouping, empty-inside
short-circuit, boundary cases)
- 5 for `ReviewTabLabel` pizzazz indicator (presence/absence gating,
accent + pulse classes, singular/plural aria-labels, null-stats safety)
- 4 for `coerceAssistedReviewHunks` (non-arrays, missing/invalid path,
malformed ranges, empty-string comment)
- 4 for end-to-end persistence (restart-survives-state,
clear-unlinks-file, per-workspace isolation, corrupted-file self-heal)
- Full `make test` deltas: +42 passing tests, no new failures. The 21
`GitStatusStore` and 1 `NestedToolRenderer` cross-test interference
failures observed in full-suite runs are **pre-existing on baseline
`main`** and reproduce without these changes.
## Risks
Low. The new state is opt-in and entirely additive:
- The Assisted toggle, per-hunk comment row, range trimming, auto-focus
latch, and tab pizzazz pill are all conditional on the agent having
flagged something, so users who never see the tool fire get the
unchanged Review pane and tab strip.
- The slicer is a pure transform and returns `null` for any edge case it
doesn't fully handle, falling back to the unchanged full-hunk render
path.
- Ordering changes only when at least one hunk matches β the existing
file-order / last-edit sort still owns the rest of the list.
- The tools only persist a small JSON file under the workspace session
dir; no git or other filesystem side effects.
- `ContextCollapseIndicator`'s new `mode` prop defaults to `"collapse"`,
preserving existing call-site behavior.
- The auto-on Assisted effect is ref-latched so it won't churn against
user toggles; it only re-arms when the agent clears the hint set
entirely.
- File reads self-heal from corruption (treated as empty), so a
hand-edited / partial-write `assistedReview.json` can't brick the tool.
Touched files outside the feature scope:
`StreamingMessageAggregator.processToolResult` (added a guarded branch
alongside `todo_write`), `applyFrontendFilters` (added an optional
predicate), `ContextCollapseIndicator` (additive `mode` prop), and
`ReviewStats` (additive `unreadAssisted` field). All are narrowly scoped
and covered by existing or new tests.
---
_Generated with `mux` β’ Model: `anthropic:claude-opus-4-7` β’ Thinking:
`max` β’ Cost: `$26.13`_
<!-- mux-attribution: model=anthropic:claude-opus-4-7 thinking=max
costs=26.13 -->1 parent 7893af4 commit 1903541
30 files changed
Lines changed: 2334 additions & 113 deletions
File tree
- docs/hooks
- src
- browser
- features
- RightSidebar
- CodeReview
- Tabs
- Tools
- hooks
- stores
- utils
- messages
- review
- common
- types
- utils
- review
- tools
- node/services
- agentSkills
- reviewPane
- tools
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
587 | 587 | | |
588 | 588 | | |
589 | 589 | | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
590 | 602 | | |
591 | 603 | | |
592 | 604 | | |
| |||
Lines changed: 15 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
7 | 14 | | |
8 | 15 | | |
9 | 16 | | |
10 | | - | |
| 17 | + | |
11 | 18 | | |
12 | | - | |
| 19 | + | |
13 | 20 | | |
14 | 21 | | |
15 | 22 | | |
16 | 23 | | |
17 | 24 | | |
| 25 | + | |
18 | 26 | | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
19 | 30 | | |
20 | 31 | | |
21 | 32 | | |
22 | 33 | | |
23 | 34 | | |
24 | | - | |
| 35 | + | |
25 | 36 | | |
26 | 37 | | |
27 | 38 | | |
| |||
41 | 52 | | |
42 | 53 | | |
43 | 54 | | |
44 | | - | |
| 55 | + | |
45 | 56 | | |
46 | 57 | | |
47 | 58 | | |
| |||
Lines changed: 167 additions & 25 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
| 7 | + | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| 28 | + | |
28 | 29 | | |
29 | 30 | | |
30 | 31 | | |
| |||
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
54 | 74 | | |
55 | 75 | | |
56 | 76 | | |
| |||
129 | 149 | | |
130 | 150 | | |
131 | 151 | | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
132 | 155 | | |
133 | 156 | | |
134 | 157 | | |
| |||
192 | 215 | | |
193 | 216 | | |
194 | 217 | | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
195 | 237 | | |
196 | 238 | | |
197 | 239 | | |
| |||
205 | 247 | | |
206 | 248 | | |
207 | 249 | | |
208 | | - | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
209 | 257 | | |
210 | 258 | | |
211 | 259 | | |
| |||
301 | 349 | | |
302 | 350 | | |
303 | 351 | | |
304 | | - | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
305 | 371 | | |
306 | 372 | | |
307 | 373 | | |
| |||
401 | 467 | | |
402 | 468 | | |
403 | 469 | | |
404 | | - | |
405 | | - | |
406 | | - | |
407 | | - | |
408 | | - | |
409 | | - | |
410 | | - | |
411 | | - | |
412 | | - | |
413 | | - | |
414 | | - | |
415 | | - | |
416 | | - | |
417 | | - | |
418 | | - | |
419 | | - | |
420 | | - | |
421 | | - | |
422 | | - | |
423 | | - | |
424 | | - | |
425 | | - | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
426 | 568 | | |
427 | 569 | | |
428 | 570 | | |
| |||
Lines changed: 36 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
45 | 51 | | |
46 | 52 | | |
47 | 53 | | |
| |||
57 | 63 | | |
58 | 64 | | |
59 | 65 | | |
| 66 | + | |
60 | 67 | | |
61 | 68 | | |
62 | 69 | | |
| |||
89 | 96 | | |
90 | 97 | | |
91 | 98 | | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
92 | 104 | | |
93 | 105 | | |
94 | 106 | | |
| |||
186 | 198 | | |
187 | 199 | | |
188 | 200 | | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
189 | 224 | | |
190 | 225 | | |
191 | 226 | | |
| |||
0 commit comments