Skip to content

Add Cursor agent support with filter UI - #64

Merged
wesm merged 14 commits into
mainfrom
cursor-agent
Feb 27, 2026
Merged

Add Cursor agent support with filter UI#64
wesm merged 14 commits into
mainfrom
cursor-agent

Conversation

@wesm

@wesm wesm commented Feb 27, 2026

Copy link
Copy Markdown
Member

Summary

Supersedes #22 (squashed, rebased on main, with review fixes).

  • Add Cursor as a supported agent type, syncing transcripts from ~/.cursor/projects/<project>/agent-transcripts/ with a dedicated parser, file discovery, and sync engine wiring
  • Support both .txt (legacy plain-text) and .jsonl (Anthropic API message format) transcript files with automatic format detection
  • Add /api/v1/agents endpoint and agent filter dropdown in the dashboard header to filter sessions by source
  • Thread agent filter through analytics so all dashboard panels respect the selection
  • Map Cursor tool names (Shell, StrReplace, LS) to normalized categories

Parser and sync details

  • Format detection: first non-empty line is checked for valid JSON to dispatch between JSONL and text parsers
  • JSONL parser reuses ExtractTextContent for assistant messages (handles text, thinking, tool_use, tool_result blocks) and extractUserQuery for user messages (strips <user_query> tags)
  • Discovery dedupes by basename when both .txt and .jsonl exist, preferring .jsonl
  • FindCursorSourceFile picks the newest file by mtime when both extensions exist
  • DecodeCursorProjectDir anchors marker matching to the home-directory position to avoid truncating project names containing marker words (e.g. my-dev-tool)
  • CursorSessionID derives extension-agnostic session IDs

Content parser fix

  • Frontend content parser no longer false-matches [Thinking] / [Bash] markers inside inline code spans (backtick-quoted text in markdown)
  • Replaced regex-based inline code detection with a CommonMark-compliant scanner supporting arbitrary backtick delimiter lengths
  • Fenced code blocks at line start are correctly distinguished from inline triple-backtick spans

Earlier review fixes

  • Fix extractAssistantContent dropping visible prose after marker blocks (added isBlockBodyEnd heuristic)
  • Fix isContainedIn rejecting ..-prefixed child names and accepting rel == "."
  • Exclude empty agent names from GetAgents query
  • Return [] instead of null JSON when no agents match
  • Use black accent color for Cursor agent

Co-authored-by: jfan-nux jfan3@wellesley.edu

Closes #22

jfan3 and others added 3 commits February 26, 2026 21:10
Add parser for Cursor's SQLite-based chat history (workspace storage),
thread agent type filter through analytics dashboard, and update UI
to support filtering by agent.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- extractAssistantContent now uses isBlockBodyEnd to stop consuming
  lines when non-indented prose follows a marker block, preserving
  visible assistant text after thinking/tool sections.
- isContainedIn rejects rel=="." so a path equal to root is not
  treated as contained.
- GetAgents excludes empty agent names to avoid a blank UI option.
- Add tests for all three fixes plus isBlockBodyEnd and
  DecodeCursorProjectDir.

Addresses review #7566 findings 1, 2, 3, and 4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- GetAgents returns [] instead of null when no agents match
- isContainedIn no longer false-negatives on ..prefixed names
- Add black accent color for cursor agent

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reject rel == ".." (parent directory) in isContainedIn
- Add "parent of root" test case for isContainedIn
- Add TestGetAgentsEmptyResultSerializesAsArray verifying [] JSON

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Feb 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (9ba3e5b)

Summary Verdict: The PR successfully implements Cursor transcript ingestion and UI filtering, but introduces a high-severity UI crash and a medium-severity path validation flaw that must be addressed.

High Severity

  • Missing setAgentFilter method causes UI
    crash
    • Location: frontend/src/lib/stores/sessions.svelte.ts
    • Problem: The Svelte store is missing the setAgentFilter method. AppHeader.svelte calls sessions.setAgentFilter(select.value), which will cause
      a TypeError and crash the UI when attempting to change the agent filter.
    • Suggested Fix: Implement setAgentFilter(agent: string) on the SessionsStore class.

Medium Severity

  • Flawed containment checks reject valid paths
    • Locations:
      *
      internal/sync/discovery.go:552 (in FindCursorSourceFile)
      • internal/sync/engine.go:1119 (in validateCursorContainment)
    • Problem: Both functions incorrectly use strings.HasPrefix(rel, ".. ") for containment checks. This falsely rejects valid project directories or files that legitimately start with .. (e.g., ..hidden/...). This edge case was fixed for isContainedIn previously, but these locations still use the flawed logic, which can break Cursor file lookup/sync.

Suggested Fix:** Replace the manual filepath.Rel and prefix checks with calls to the fixed !isContainedIn(resolved, resolvedRoot) function to ensure consistency and avoid drift.


Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

validateCursorContainment and FindCursorSourceFile used
strings.HasPrefix(rel, "..") which rejects valid paths like
"..config/". Use separator-aware check matching isContainedIn.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Feb 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (5a47de1)

Summary Verdict: The
code introduces essential functionality for Cursor transcripts and agent filtering, but requires fixes for a missing frontend method causing runtime errors and a bug in project name decoding.

High

  • Missing setAgentFilter method in SessionsStore causes runtime error
    • Location: frontend/src/lib/components /layout/AppHeader.svelte line 14 & frontend/src/lib/stores/sessions.svelte.ts
    • Description: handleAgentChange calls sessions.setAgentFilter(...), but the setAgentFilter method was never added to the SessionsStore class. This will cause a runtime error when a user tries to filter by agent.
    • Suggested Fix: Add a setAgentFilter(agent: string) method to SessionsStore in sessions.svelte.ts that updates this.filters.agent, resets pagination, clears active SessionId, and calls this.load().

Medium

  • Project decoding can truncate valid Cursor project names
    • Location: /home/roborev/.roborev/clones/wesm/agentsview/internal/parser/cursor.go:292

    • Description: DecodeCursorProjectDir() picks the last marker from a broad marker set (dev, work, src, etc.). If those tokens appear inside the actual project name, decoding drops earlier parts (for example, ...-Documents-my-dev-tool can collapse
      to tool).

    • Suggested Fix: Prefer a stricter/earlier boundary strategy (for example first high-confidence marker), or remove ambiguous markers like dev/work/src, and add guard logic before trimming.


Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

wesm and others added 5 commits February 26, 2026 21:46
Cursor changed its transcript format from .txt (plain text with
role markers) to .jsonl (Anthropic API message format). Add format
detection (first non-empty line is valid JSON) and a JSONL parser
that reuses ExtractTextContent for assistant messages. Update all
5 extension-check sites in discovery and engine to accept both
.txt and .jsonl files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The content parser's regex matched `[Thinking]` and `[Tool call]`
markers even when they appeared inside backtick code spans in
markdown. This caused false-positive thinking/tool block detection
when message text discussed these markers (e.g. code reviews).

Pre-compute inline code span positions and skip any marker match
whose start falls inside a span.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace isCursorJSONL SplitN(20) with byte-capped scan (4 KB)
  so leading blank lines beyond 20 don't cause misdetection
- FindCursorSourceFile now picks the newest file by mtime when
  both .jsonl and .txt exist for the same session ID
- Replace INLINE_CODE_RE regex with a CommonMark-compliant
  scanner that handles arbitrary backtick delimiter lengths
- Add tests for all three fixes: many-blank-line detection,
  discovery/find coexistence, and long-delimiter inline code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
DecodeCursorProjectDir now only matches marker words (dev, work,
code, etc.) at the expected home-directory position rather than
scanning the entire hyphenated name. This prevents project names
like "my-dev-tool" from being truncated to "tool".

DiscoverCursorSessions now dedupes by basename stem when both
.txt and .jsonl exist for the same session, preferring .jsonl.
This prevents both files from mapping to the same session ID and
the last-processed one silently overwriting the other.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
scanInlineCodeSpans now only skips triple-backtick runs at line
start as fenced blocks when there is no closing run of the same
length on the same line. This correctly treats ``` [Bash] ``` at
line start as an inline code span.

isCursorJSONL now uses the scan window (4 KB) only to locate the
first non-empty line start, then validates the full line from
the original data. Lines exceeding 4 KB are no longer truncated
before validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Feb 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (a87c312)

Verdict: Security checks passed with no issues found, but the
PR contains a High-severity compilation failure and Medium-severity logic bugs requiring attention.

High

  • Missing setAgentFilter Method
    • File refs: frontend/src/lib/stores/sessions.svelte.ts and frontend/src/lib/components/layout/ AppHeader.svelte
    • Problem: AppHeader.svelte calls sessions.setAgentFilter(select.value), but the setAgentFilter method is missing from SessionsStore. This will result in a TypeScript compilation failure and runtime crash.
    • Suggested fix: Add the missing
      setAgentFilter(agent: string) method to SessionsStore (similar to setProjectFilter).

Medium

  • Inconsistent .txt vs .jsonl Precedence
    • File refs: [internal/sync/discovery.go#L500](/home/robore
      v/.roborev/clones/wesm/agentsview/internal/sync/discovery.go#L500), [internal/sync/discovery.go#L575](/home/roborev/.roborev/clones/wesm/agentsview/internal/sync
      /discovery.go#L575), internal/sync/engine.go#L553
    • Problem: Discover CursorSessions always prefers .jsonl when both exist for the same stem, but FindCursorSourceFile picks whichever file is newer. That means full sync and single-session sync can read different source files for the same cursor:<id>, potentially causing stale/oscillating session data.

Suggested fix: Use one policy in both places (e.g., newest wins with .jsonl as tiebreaker, or always .jsonl).

  • Premature Loop Break on Unmatched Inline Backtick
    • File refs: frontend/src/lib/utils/content- parser.ts, line 115 (if (!found) break;)
    • Problem: Encountering an unmatched inline backtick run (where found is false) prematurely breaks the outer while loop. This stops all subsequent text from being scanned for valid inline code spans
      , causing any tool/thinking markers inside later backtick runs to be falsely parsed as active markers.
    • Suggested fix: Remove if (!found) break; so the loop continues scanning the rest of the text. (The i pointer is already safely advanced past the opening run, so no
      infinite loop will occur).

Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

wesm and others added 3 commits February 26, 2026 22:14
…acktick

Unify .jsonl/.txt precedence: both DiscoverCursorSessions and
FindCursorSourceFile now prefer .jsonl over .txt (previously
FindCursorSourceFile used mtime which could diverge from discovery).

DecodeCursorProjectDir now scans forward from the minimum marker
position instead of checking a single fixed index, handling
multi-token usernames like "john-doe" correctly.

scanInlineCodeSpans no longer breaks the outer loop when an
opening backtick run has no matching closer. This allows later
valid spans of different lengths to be found.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split directory markers into high-confidence (Documents, Code,
projects, repos) and low-confidence (code, src, work, dev) tiers.
Scan for high-confidence markers first, so a username containing
a low-confidence marker word (e.g. "john-code-doe") doesn't
shadow a later high-confidence marker (e.g. "Documents").

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add test for the inherent ambiguity in Cursor's lossy path
encoding where a low-confidence marker word could be either a
directory name or part of a username. The two-pass scan favors
high-confidence markers, which is correct for the common case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Feb 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (7fadc67)

Verdict: No medium, high, or critical issues were found in the provided reviews.


Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

@roborev-ci

roborev-ci Bot commented Feb 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (bef5eb0)

Summary: The PR implements Cursor transcript support and agent filtering, but contains a high-severity frontend compilation error and a medium-severity state overwrite bug in the sync engine.

High

  • Files: frontend/src/lib/components/layout/AppHeader.svelte (line 14), frontend/src/lib/stores/sessions.svelte.ts
  • Issue: The handleAgentChange function calls sessions.setAgentFilter(select.value),
    but the setAgentFilter method was never added to the SessionsStore class. This will result in a TypeScript compilation error and a runtime crash.
  • Remediation: Add the missing method to the SessionsStore class in sessions.svelte.ts:

setAgentFilter(agent: string) {
this.filters = { ...defaultFilters(), project: this.filters.project, agent };
this.activeSessionId = null;
this.resetPagination();
this.load();
}


### Medium
*
**File:** [`internal/sync/engine.go:311`](/home/roborev/.roborev/clones/wesm/agentsview/internal/sync/engine.go:311)
* **Issue:** `.txt` files can overwrite preferred `.jsonl` files
during watcher-driven sync. While `DiscoverCursorSessions` and `FindCursorSourceFile` prefer `.jsonl`, `classifyOnePath` accepts both formats with no sibling-precedence check. If both files exist for one session, event ordering can let `.txt` reprocess and overwrite a richer `.jsonl
` parse.
* **Remediation:** When classifying/processing a Cursor `.txt` file, skip it if a sibling `<stem>.jsonl` exists, or centralize precedence logic inside `processCursor`.

---
*Synthesized from 4 reviews (agents: codex, gemini | types: default, security)*

When the file watcher triggers processCursor for a .txt file,
check if a sibling .jsonl exists for the same stem. If so, skip
the .txt to prevent it from overwriting a richer .jsonl parse.
This closes the gap where discovery and source lookup prefer
.jsonl but watcher-driven sync could still process a .txt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wesm

wesm commented Feb 27, 2026

Copy link
Copy Markdown
Member Author

@jfan-nux — thanks for working on this! please feel free to send follow up PRs if you run into issues

@wesm
wesm merged commit 0f0988c into main Feb 27, 2026
5 of 6 checks passed
@roborev-ci

roborev-ci Bot commented Feb 27, 2026

Copy link
Copy Markdown

roborev: Combined Review (7009e87)

Summary Verdict: The security posture is robust, but there is one high-severity frontend issue that will cause a runtime error when using the new agent filter.

High

Missing setAgentFilter method in SessionsStore

  • Location:
    frontend/src/lib/components/layout/AppHeader.svelte (line 14) and frontend/src/lib/stores/sessions.svelte.ts
  • Description: AppHeader.svelte calls sessions.setAgentFilter(select.value), but the
    setAgentFilter method is missing/never defined in SessionsStore (sessions.svelte.ts). This will result in a TypeError: sessions.setAgentFilter is not a function when attempting to use the new agent filter UI.
  • Suggested Fix: Add the missing method to SessionsStore in
    sessions.svelte.ts. For example:
    setAgentFilter(agent: string) { 
        this.filters = { ...defaultFilters(), agent, project: this.filters.project }; 
        this.activeSessionId = null; 
        this.resetPagination();
    
        this.load(); 
    }

Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

cursor Bot pushed a commit to diazMelgarejo/periscope that referenced this pull request Jun 1, 2026
## Summary

Supersedes #22 (squashed, rebased on main, with review fixes).

- Add Cursor as a supported agent type, syncing transcripts from
`~/.cursor/projects/<project>/agent-transcripts/` with a dedicated
parser, file discovery, and sync engine wiring
- Support both `.txt` (legacy plain-text) and `.jsonl` (Anthropic API
message format) transcript files with automatic format detection
- Add `/api/v1/agents` endpoint and agent filter dropdown in the
dashboard header to filter sessions by source
- Thread agent filter through analytics so all dashboard panels respect
the selection
- Map Cursor tool names (Shell, StrReplace, LS) to normalized categories

### Parser and sync details

- Format detection: first non-empty line is checked for valid JSON to
dispatch between JSONL and text parsers
- JSONL parser reuses `ExtractTextContent` for assistant messages
(handles `text`, `thinking`, `tool_use`, `tool_result` blocks) and
`extractUserQuery` for user messages (strips `<user_query>` tags)
- Discovery dedupes by basename when both `.txt` and `.jsonl` exist,
preferring `.jsonl`
- `FindCursorSourceFile` picks the newest file by mtime when both
extensions exist
- `DecodeCursorProjectDir` anchors marker matching to the home-directory
position to avoid truncating project names containing marker words (e.g.
`my-dev-tool`)
- `CursorSessionID` derives extension-agnostic session IDs

### Content parser fix

- Frontend content parser no longer false-matches `[Thinking]` /
`[Bash]` markers inside inline code spans (backtick-quoted text in
markdown)
- Replaced regex-based inline code detection with a CommonMark-compliant
scanner supporting arbitrary backtick delimiter lengths
- Fenced code blocks at line start are correctly distinguished from
inline triple-backtick spans

### Earlier review fixes

- Fix `extractAssistantContent` dropping visible prose after marker
blocks (added `isBlockBodyEnd` heuristic)
- Fix `isContainedIn` rejecting `..`-prefixed child names and accepting
`rel == "."`
- Exclude empty agent names from `GetAgents` query
- Return `[]` instead of `null` JSON when no agents match
- Use black accent color for Cursor agent

Co-authored-by: jfan-nux <jfan3@wellesley.edu>

Closes #22

---------

Co-authored-by: jfan-nux <jfan3@wellesley.edu>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@wesm
wesm deleted the cursor-agent branch June 25, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants