Skip to content

[G1] Cursor: event-level parity (messages, tool calls, file edits, model, usage, real timestamps) #166

Closed

Description

@willwashburn

Part of #160 (group 1 — independent).

Problem

Cursor is advertised as a supported harness (README, SOURCE_CHOICES), but hydration stores prompts only:

  • ingest_cursor (crates/ai-hist/src/hydrate.rs:1546-1582) reads every line and calls ai_hist_core::parse_cursor_text (crates/ai-hist-core/src/lib.rs:1175), which requires role == "user", reads message.content, strips the <user_query> wrapper, and returns text.
  • Result: history rows + a sessions row. Zero session_events, tool_calls, file_edits, models, token_json.
  • Every prompt gets the file mtime as its timestamp (hydrate.rs; discovery discover.rs:1095-1100 also sets first_activity_ms: None).
  • Relationship capability is "never" (crates/ai-hist-core/src/relationships.rs:164).
  • Despite all of that, HydrateSessionResult.capability reports "full" (see the capability-honesty issue).

Discovery already locates the files correctly: ~/.cursor/projects/<encoded-path>/agent-transcripts/<id>/<id>.jsonl (discover.rs:1033-1063), cwd from decode_cursor_project (lib.rs:3956).

Scope

  1. Characterize the format first. Capture real Cursor transcripts (several Cursor versions) into tests/fixtures/cursor/ (see the fixture-corpus issue): assistant records, tool calls (Cursor's tool names: read_file, edit_file, run_terminal_cmd, codebase_search, grep, etc.), tool results, thinking, and whatever usage/model metadata Cursor writes. Document the findings in docs/session-catalog.md "How each adapter works → cursor" before writing the parser, including an explicit list of fields Cursor does not write (so the capability matrix can say "unavailable" truthfully rather than "not implemented").
  2. Implement ingest_cursor_transcript in crates/ai-hist/src/lib.rs mirroring the Claude parser's shape: session_events for text/thinking/tool_use/tool_result, tool_calls with tool_use_id/name/target/args_json/is_error, file_edits for edit tools (with structured_patch_json when Cursor records a diff, lines_added/removed via the existing count_patch_text), model and token_json when present, real per-record timestamps when present (fall back to mtime only for records that carry no time, and record that fact in a diagnostic).
  3. Discovery: populate first_activity_ms, models, last_assistant_text from the head/tail scan when the format allows (discover.rs CursorProvider).
  4. Relationships: if Cursor records subagent/background-agent spawns, wire them through record_relationship and update relationship_capabilities; otherwise document "never" with the evidence.
  5. Update the un-ignored corpus tests, docs/session-catalog.md matrix, and README.md ("Provider-aware evidence" paragraph) to state exactly what Cursor exposes.

Acceptance

  • Snapshot for the Cursor fixture contains session_events of all four kinds, ≥1 tool_calls row, ≥1 file_edits row, and non-null ts_ms that differ across records.
  • ai-hist sessions tools cursor <id> and ai-hist sessions edits cursor <id> return rows.
  • getSessionEventsPage({source:'cursor'}) events carry tokenUsage/model when the fixture has them; when Cursor genuinely lacks usage, the capability matrix says so and hydrateSession reports partial (after the honesty issue lands) rather than full.
  • No prompt timestamp is the file mtime unless the record has no timestamp field, and that case emits a CURSOR_TIMESTAMP_FROM_MTIME diagnostic.

Out of scope

Cost (burn); cloud/remote Cursor sessions.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions