feat(cli): /evict + /evicted commands and context viewer section jumps (by Wren) - #404
Conversation
…s (by Wren) User-facing layer on top of persistent eviction (#403): - /evict — user-driven context eviction with the same selector grammar as the SB's evict_context tool: entry ids, source:<name>, role:<role>. No selector lists evictable entries (never mutates); --dry-run previews a selection. Persists via context_evict with actor: 'user'. - /evicted — show everything evicted this session, ✕-labeled with actor · reason attribution (out of the prompt window, still in the transcript). - recordEviction: single writer for all three eviction actors (SB tool, user /evict, system trim) so the transcript event shape and the live evicted-display list can't drift. Fixes two inconsistencies: live trims now appear in Ctrl+O's Evicted section without a reattach, and SB evictions now carry their reason like hydrated records do. - Context Inspector section jumps: e/t/m/b keys jump to Evicted, Tool Calls, Memories, Bootstrap. Footer shows available jumps only. Tests: evict-selection parse/match suite, context-viewer jump mapping, hydration regression suite still green. Co-Authored-By: Wren <noreply@anthropic.com>
conoremclaughlin
left a comment
There was a problem hiding this comment.
Changes requested (posted as COMMENT because GitHub rejects formal REQUEST_CHANGES from the PR owner identity).
Thanks Wren — the recordEviction consolidation looks structurally right to me: the persisted context_evict refs stay display-free (eid + hash only), live trim/SB/user paths now share the same writer, and the compaction-history eviction case replays safely as a normal hash-backed entry.
I found one blocker in the new selector parser: malformed numeric selectors are accepted as valid ids because parseInt allows partial parses. Since /evict mutates the live prompt window and persists a context_evict, a typo like /evict 1abc or /evict 1.5 should fail rather than silently evicting entry #1.
Verification run:
git diff --check origin/main...HEAD- NUL scan across the six touched files
npx vitest run packages/cli/src/repl/evict-selection.test.ts packages/cli/src/repl/ink/context-viewer.test.ts packages/cli/src/commands/chat-hydration.test.ts packages/cli/src/repl/context-ledger.test.ts packages/cli/src/repl/context-tools.test.ts— 76/76 passednpx vitest run packages/cli/src/repl/slash.test.ts packages/cli/src/repl/ink/MessageLine.test.ts packages/cli/src/repl/ink/dock-resize.test.ts packages/cli/src/repl/ink/dock-snapshot.test.ts— 45/45 passedyarn workspace @inklabs/cli type-check
conoremclaughlin
left a comment
There was a problem hiding this comment.
LGTM — final re-review at 9dafe3a5.
The prior /evict selector blocker is fixed: each comma-separated id part now has to match a full positive-integer token before parsing, so the parseInt truncation cases (1abc, 1.5, 1e3, 0x2, etc.) error instead of silently targeting #1. The added regression coverage exercises the cases I called out. I also resolved the prior inline thread.
I re-checked the surrounding PR shape as well: recordEviction still persists display-free context_evict refs (eid + hash only), live evicted display attribution stays separate, and compaction-history eviction remains safe as a normal hash-backed entry.
Verification run:
- Direct parser smoke check for malformed selectors and valid
3,5 7 git diff --check origin/main...HEAD- NUL scan across the six touched files
npx vitest run packages/cli/src/repl/evict-selection.test.ts packages/cli/src/repl/ink/context-viewer.test.ts packages/cli/src/commands/chat-hydration.test.ts packages/cli/src/repl/context-ledger.test.ts packages/cli/src/repl/context-tools.test.ts packages/cli/src/repl/slash.test.ts packages/cli/src/repl/ink/MessageLine.test.ts packages/cli/src/repl/ink/dock-resize.test.ts packages/cli/src/repl/ink/dock-snapshot.test.ts— 122/122 passedyarn workspace @inklabs/cli type-check
…ren) (#405) ## What Kills the 4 "Multimodal Image Handling" failures that have turned **every CI run red for the past week** (#399–#404 all merged over this noise). ## Root cause — stale tests, not a regression `8153008b` (June 3) deliberately made image attachments **claude-code-only**: > Image attachments are only read for claude-code backend (ClaudeRunner supports --image flags). InkRunner spawns ink chat which manages its own backend. The tests still asserted the pre-InkRunner inverse: ink runner receives `imageContents`, CLI backends don't. Hence the mirrored failures — ink expected images and got none; claude-code expected none and got images. ## Changes (test-only, one file) - Positive paths (single image, gallery payload, MAX_IMAGES limit) → target `claude-code` / `ClaudeRunner` - New inverse test: `does not pass imageContents to ink runner (ink chat manages its own backend)` - Filter tests (non-image media, unsupported types, oversized images) switched from ink to claude-code — on ink they passed **vacuously**, since ink never receives images regardless of whether the filter logic works ## Verification - `session-service.test.ts`: **70/70 green** — first time this file fully passes in weeks - NUL-byte scan clean - No production code touched 🤖 Generated with [Claude Code](https://claude.com/claude-code)
What
The user-facing layer on top of persistent context eviction (#403). The SB has had
evict_contextsince #403 — this gives the human at the terminal the same agency, plus better visibility into what's been evicted./evict— user-driven evictionSame selector grammar as the SB's
evict_contexttool, so both actors speak the same language:Persists through the same
context_evicttranscript event path withactor: 'user'— evictions survive reattach exactly like SB evictions./evicted— review what's out of the windowLists everything evicted this session, ✕-labeled with
actor · reasonattribution and the standing reminder: out of the prompt window, still in the transcript.recordEviction— single writer for all eviction actorsSB tool, user
/evict, and system trim all flow through one helper now, so the transcript event shape and the live evicted-display list can't drift. This fixes two real inconsistencies:sessionEvictedEntrieswasn't updated).reasonin the live display, matching what hydrated records already showed.Context Inspector section jumps
Inside Ctrl+O:
e→ Evicted,t→ Tool Calls,m→ Memories,b→ Bootstrap. Footer advertises only the jumps available for the current content.Tests
evict-selection.test.ts— parse/match suite: ids, comma lists, dedupe,source:/role:filters, dry-run flag, mixed-selector rejection, garbage rejectioncontext-viewer.test.ts— section jump mapping against realformatContextLinesoutput, attribution renderingcontext_evictevent shape is unchangedNext layers (spec'd in
ink://specs/sb-context-eviction, not this PR)Interactive eviction from the Ctrl+O viewer, budget nudges, age-based filters, retention policies.
🤖 Generated with Claude Code