Skip to content

Commit 9824f7b

Browse files
authored
Merge pull request #103 from grimmerk/feat/vscode-session-support
feat: VS Code session support + real-time preview refresh
2 parents 8dcadf7 + 3b5153d commit 9824f7b

10 files changed

Lines changed: 953 additions & 99 deletions

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,22 @@
11
# Changelog
22

3+
## 1.0.67
4+
5+
- Feat: VS Code Claude Code session support — detect, display, switch, resume
6+
- Active sessions: `[VSCODE]` badge, instant switch via URI handler
7+
- Closed sessions: JSONL scan + hooks index, resume via `code <path>` + URI handler
8+
- `ai-title` as display name fallback (custom-title > ai-title > first prompt)
9+
- VS Code added to Launch Terminal dropdown in Settings
10+
- Requires Claude Code VS Code extension v2.1.72+
11+
- Feat: real-time session preview updates on idle
12+
- Last assistant message, last user message, and session order auto-update
13+
- Single `tail -n 100` read for both user + assistant messages
14+
- fs.watch debounced (50ms) to reduce duplicate triggers on macOS
15+
- Feat: search by terminal type (`vscode`/`ghostty`/`iterm2`)
16+
- Style: PR badge before terminal badge, search highlighting on both
17+
- Fix: ISO string timestamps normalized to unix ms (correct sort order)
18+
- Fix: skip `<ide_opened_file>` context blocks in VS Code session preview
19+
320
## 1.0.66
421

522
- Style: use `*` separator for custom titles (matches Claude Code display)
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# VS Code Claude Code Session Support
2+
3+
## Goal
4+
5+
Add VS Code Claude Code sessions to CodeV's session list, enabling detection, display, switching, resuming, and launching — on par with terminal-based sessions.
6+
7+
## Background
8+
9+
Claude Code runs inside VS Code as an extension (`entrypoint: "claude-vscode"`). These sessions share the same `~/.claude/` data layer as CLI sessions, but with key differences:
10+
11+
- **Not in `history.jsonl`**: VS Code sessions are excluded from the global history log (upstream bugs [#24579](https://github.com/anthropics/claude-code/issues/24579), [#18619](https://github.com/anthropics/claude-code/issues/18619))
12+
- **No `/rename` support**: Cannot set custom titles ([#33165](https://github.com/anthropics/claude-code/issues/33165))
13+
- **Has `ai-title`**: Auto-generated titles stored in session JSONL (e.g., `"Casual greeting and session start"`)
14+
- **URI handler available**: `vscode://anthropic.claude-code/open?session=<UUID>` (requires extension v2.1.72+)
15+
- **Hooks work**: Status hooks fire for VS Code sessions via `$CLAUDE_CODE_ENTRYPOINT` env var (verified experimentally)
16+
17+
## Data Availability
18+
19+
| Data | Available | Source |
20+
|------|-----------|--------|
21+
| Active session metadata | Yes | `~/.claude/sessions/<PID>.json` with `entrypoint: "claude-vscode"` |
22+
| Session status (working/idle/needs-attention) | Yes | Hooks write to `~/.claude/codev-status/{sessionId}.json` |
23+
| Conversation history | Yes | `~/.claude/projects/{path}/{sessionId}.jsonl` (same format as CLI) |
24+
| AI-generated title | Yes | `ai-title` entry in session JSONL (written once per session) |
25+
| Custom title (`/rename`) | No | Not supported in VS Code extension |
26+
| First/last user prompt | Yes | Readable from session JSONL head/tail |
27+
| Last assistant response | Yes | Readable from session JSONL tail (shared algorithm) |
28+
| Git branch | Yes | In JSONL entries |
29+
| PR links | Yes | If created during session |
30+
| Closed session record | **No** | Not in `history.jsonl` — solved via JSONL scan + hooks index |
31+
| IDE workspace info | Yes | `~/.claude/ide/<PID>.lock` (workspace folders, IDE name) |
32+
33+
## Architecture
34+
35+
### Layer 1: Detection
36+
37+
**Active sessions** — Removed the `entrypoint !== 'cli'` filter in `detectActiveSessions()`. VS Code sessions appear alongside CLI sessions. Uses async `readVSCodeSessionFromJSONL()` with head/tail for metadata.
38+
39+
**Closed sessions** — Two-pronged approach:
40+
1. **Hooks index**: `codev-status-hook.sh` detects `$CLAUDE_CODE_ENTRYPOINT === "claude-vscode"` and appends to `~/.claude/codev-status/vscode-sessions.jsonl`. Uses marker files (`.vs-{sessionId}`) to avoid duplicate writes. Works even when CodeV is not running.
41+
2. **JSONL scan**: `scanClosedVSCodeSessions()` reads first 4KB of each JSONL file to check entrypoint. Hooks-indexed sessions are skipped (no 4KB read needed). Results cached for 30s.
42+
43+
### Layer 2: Display
44+
45+
- **Badge**: `[VSCODE]` badge shown for **active** sessions only (consistent with CLI terminal badges). Closed sessions do not show terminal badges to avoid visual noise and stale badge issues.
46+
- **Title**: `ai-title` from JSONL as fallback. Priority: `custom-title > ai-title > first prompt`
47+
- **Status dot**: Same as CLI sessions (hooks fire for VS Code via `$CLAUDE_CODE_ENTRYPOINT`)
48+
- **First/last prompt**: Read from JSONL via shared `parseUserMessageFromLines()`, skips `<ide_>` context blocks via `extractUserText()`
49+
- **Search**: Includes `ai-title`, terminal type (`vscode`/`ghostty`/`iterm2`), and PR URLs
50+
- **Badge highlights**: Search matches highlight terminal badge and PR badge text
51+
52+
### Layer 3: Switch (active sessions)
53+
54+
Uses the VS Code URI handler:
55+
```
56+
open "vscode://anthropic.claude-code/open?session=<UUID>"
57+
```
58+
59+
Verified behavior:
60+
- Existing session UUID: switches to that session tab in VS Code
61+
- First call requires user to allow the URI handler (one-time dialog, check "Do not ask me again")
62+
- No Accessibility API or AppleScript needed
63+
- MAS-compatible (uses standard URI scheme)
64+
65+
### Layer 4: Resume (closed sessions)
66+
67+
Two-step process for VS Code resume:
68+
1. Open project folder: `code "<projectPath>"`
69+
2. After 2s delay (for VS Code to load workspace): URI handler `vscode://anthropic.claude-code/open?session=<UUID>`
70+
71+
If the user's Launch Terminal is not set to VS Code, closed sessions resume in the default terminal (standard behavior).
72+
73+
Measured latency:
74+
| Scenario | Time |
75+
|----------|------|
76+
| Active session switch | Instant |
77+
| Resume in already-open VS Code project | ~1-2s |
78+
| Resume in new VS Code project | ~3-5s |
79+
80+
### Layer 5: Settings
81+
82+
- **Launch Terminal dropdown**: Added "VS Code" option alongside iTerm2, Terminal, Ghostty, cmux
83+
- When set to VS Code, closed session clicks use the URI handler resume flow
84+
- Launch Mode (tab/window) does not apply to VS Code
85+
86+
### Layer 6: Shared Algorithm (head/tail reads)
87+
88+
`readVSCodeSessionFromJSONL()` performs a single set of parallel reads:
89+
- `head -n 20` → first user prompt (via `parseUserMessageFromLines()`)
90+
- `tail -n 100` → last user prompt + last assistant message (via `parseUserMessageFromLines(lines, true)` + `parseAssistantMessageFromLines()`)
91+
- `grep -c '"type":"user"'` → message count
92+
- Also extracts `cwd` from JSONL content (directory name decode is lossy)
93+
94+
This avoids duplicate tail reads — the assistant response is extracted from the same tail output, so `loadLastAssistantResponses()` is not called again for VS Code sessions.
95+
96+
Shared helper functions:
97+
| Function | Purpose | Used by |
98+
|----------|---------|---------|
99+
| `extractUserText(content)` | Extract text from message content, skip `<ide_>` blocks | `parseUserMessageFromLines()` |
100+
| `parseUserMessageFromLines(lines, fromEnd?)` | Find user message in JSONL lines | `readVSCodeSessionFromJSONL()` |
101+
| `parseAssistantMessageFromLines(lines)` | Find last assistant message in JSONL lines | `readVSCodeSessionFromJSONL()` |
102+
103+
### Layer 7: Title Enrichment (`ai-title`)
104+
105+
Added `ai-title` grep to `loadSessionEnrichment()` alongside existing `custom-title` grep. Priority: `custom-title > ai-title`.
106+
107+
Format in JSONL: `{"type":"ai-title","sessionId":"...","aiTitle":"..."}`
108+
109+
Characteristics:
110+
- Written once per session (does not change)
111+
- Present in both VS Code and newer CLI sessions
112+
- AI-generated, descriptive (e.g., "Casual greeting and session start")
113+
- See #104 for applying ai-title fallback to all sessions
114+
115+
### Layer 8: Real-time Preview Refresh
116+
117+
When a session becomes idle (Stop hook → fs.watch → status update):
118+
1. fs.watch fires (debounced 50ms to avoid 3-6x duplicate triggers on macOS)
119+
2. Status update sent to renderer with `{status, timestamp}` per session
120+
3. Renderer compares status timestamp vs last fetch timestamp (avoids unreliable working→idle transition detection)
121+
4. After 300ms delay (ensures JSONL fully flushed): `refreshSessionPreview()` reads `tail -n 100`
122+
5. Single tail read extracts both last user message + last assistant message
123+
6. Updates `assistantResponses`, `lastUserMessage`, `lastTimestamp`, and re-sorts session list
124+
125+
### Layer 9: Launch New Session
126+
127+
```bash
128+
# Open new Claude Code tab in VS Code
129+
open "vscode://anthropic.claude-code/open"
130+
131+
# Open with pre-filled prompt
132+
open "vscode://anthropic.claude-code/open?prompt=help%20me%20review%20this%20PR"
133+
```
134+
135+
## Performance
136+
137+
| Operation | Cost | Notes |
138+
|-----------|------|-------|
139+
| Detection (active) | +0ms | Same `sessions/*.json` read, removed filter |
140+
| JSONL scan (closed) | ~50ms | 218 files, 4KB read per file, cached 30s |
141+
| Hooks index skip | saves ~0.2ms/file | Known VS Code sessions skip 4KB entrypoint check |
142+
| head/tail read per session | ~5-10ms | Parallel head-20 + tail-100 + grep-c |
143+
| ai-title grep | +~1ms/session | Parallel with existing greps |
144+
| URI handler switch | instant | Single `open` command |
145+
| URI handler resume | ~1-5s | `code <path>` + 2s delay + URI handler |
146+
| Hooks index write | ~5ms/event | Per hook event, marker file prevents duplicates |
147+
| Session count | capped at 100 | Sort by timestamp, then slice after merge |
148+
| Timestamp normalization | +0ms | ISO string → unix ms conversion in reader |
149+
| Real-time refresh | ~2ms/session | Single tail-100, 300ms delay, debounced fs.watch |
150+
151+
## VS Code URI Handler Reference
152+
153+
| URL | Effect |
154+
|-----|--------|
155+
| `vscode://anthropic.claude-code/open` | Open new Claude Code tab |
156+
| `vscode://anthropic.claude-code/open?session=<UUID>` | Switch to / resume session |
157+
| `vscode://anthropic.claude-code/open?prompt=<text>` | Open with pre-filled prompt |
158+
159+
Requires Claude Code VS Code extension **v2.1.72+** (released 2026-03-10).
160+
161+
First call shows a permission dialog in VS Code. User can check "Do not ask me again" to suppress future dialogs.
162+
163+
## Known Limitations
164+
165+
1. **Closed sessions not in `history.jsonl`**: Workaround via JSONL scan + hooks index. Upstream fix may come via [#24579](https://github.com/anthropics/claude-code/issues/24579).
166+
2. **No `/rename` in VS Code**: Use `ai-title` as fallback ([#33165](https://github.com/anthropics/claude-code/issues/33165)).
167+
3. **Resume delay**: 2s fixed delay for workspace loading. Could be optimized by detecting if project is already open.
168+
4. **URI handler one-time dialog**: First use requires user to click "Allow" in VS Code.
169+
5. **JSONL timestamp format**: VS Code uses ISO strings, CLI uses unix ms. Normalized at read time.
170+
171+
## Alternatives Considered
172+
173+
### Accessibility API (AXUIElement)
174+
- Can find Claude Code tab in VS Code's AX tree (`AXRadioButton title="Claude Code"`)
175+
- More complex, requires Accessibility permission, not MAS-compatible
176+
- **Decision**: Not needed — URI handler provides better precision (session-level vs tab-level)
177+
- Reference: [mediar-ai/mcp-server-macos-use](https://github.com/mediar-ai/mcp-server-macos-use)
178+
179+
### `code -r <path>` (c9watch / claude-control approach)
180+
- Only switches to VS Code window, cannot target specific session
181+
- **Decision**: URI handler is strictly better
182+
183+
### Wait for upstream `history.jsonl` fix
184+
- Upstream bugs #24579, #18619 may eventually add VS Code sessions to history
185+
- **Decision**: Don't block on this — hooks index + JSONL scan covers the gap

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "CodeV",
33
"productName": "CodeV",
4-
"version": "1.0.66",
4+
"version": "1.0.67",
55
"description": "Quick switcher for VS Code, Cursor, and Claude Code sessions",
66
"repository": {
77
"type": "git",

0 commit comments

Comments
 (0)