Add Cursor agent support on new agent interface#392
Add Cursor agent support on new agent interface#392squishykid wants to merge 20 commits intomainfrom
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
There was a problem hiding this comment.
Pull request overview
Adds Cursor as a first-class agent implementation in the refactored agent/hook architecture, enabling lifecycle event dispatch, hook installation into .cursor/hooks.json, and reuse of the existing JSONL transcript + incremental checkpoint flow.
Changes:
- Introduces
cmd/entire/cli/agent/cursor/implementing Cursor agent identity, transcript handling, lifecycle event mapping, and hook management. - Wires Cursor into hook command registration/dispatch (including PostTodo incremental checkpoints) and agent registry constants.
- Updates summarization to treat Cursor transcripts as JSONL (shared path with Claude Code).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/summarize/summarize.go | Routes Cursor to the JSONL condensed-transcript path. |
| cmd/entire/cli/setup.go | Adds Cursor to “Preview” messaging and to global hook removal. |
| cmd/entire/cli/hooks_cursor_posttodo.go | Adds Cursor PostTodo handler delegating to Claude’s incremental checkpoint logic. |
| cmd/entire/cli/hooks_cmd.go | Ensures Cursor agent package is registered via blank import. |
| cmd/entire/cli/hook_registry.go | Adds Cursor PostTodo dispatch path alongside Claude’s. |
| cmd/entire/cli/agent/registry.go | Adds AgentNameCursor / AgentTypeCursor constants. |
| cmd/entire/cli/agent/cursor/types.go | Defines Cursor hooks.json structures + hook input raw types with conversation_id fallback. |
| cmd/entire/cli/agent/cursor/lifecycle.go | Implements ParseHookEvent mapping Cursor hooks to normalized lifecycle events + transcript analyzer methods. |
| cmd/entire/cli/agent/cursor/lifecycle_test.go | Adds unit tests for lifecycle mapping and conversation_id fallback behavior. |
| cmd/entire/cli/agent/cursor/hooks.go | Implements install/uninstall/detection for .cursor/hooks.json with matcher-based tool hooks. |
| cmd/entire/cli/agent/cursor/hooks_test.go | Adds unit tests for hook install/uninstall/idempotency/preservation behavior. |
| cmd/entire/cli/agent/cursor/cursor.go | Implements Cursor agent identity, legacy hook parsing, session I/O, transcript chunking, and modified-file extraction. |
905aeb4 to
2abcac1
Compare
dd067cd to
c072c01
Compare
950ba73 to
4ae0a70
Compare
- Remove dead code: ParseHookInput, GetHookConfigPath, SupportsHooks (zero callers) - Remove incorrect extractModifiedFiles/FileModificationTools (Cursor transcript lacks tool_use blocks) - Fix FormatResumeCommand: return human-readable instruction instead of invalid CLI command - Document transcript.Line.Role field (Cursor uses "role", Claude Code uses "type") - Add shared transcript.ExtractModifiedFiles utility to transcript package - ReadSession no longer populates ModifiedFiles (relies on git status instead) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: cd204de06bd2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: d5891c95e5a4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 86d61e18931c
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Cursor JSONL uses "role" instead of "type" to distinguish user/assistant messages. Normalize role→type during transcript parsing so all downstream consumers (summarize, explain) work uniformly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: fac4ff295b71
| func TestBuildCondensedTranscriptFromBytes_CursorRoleBasedJSONL(t *testing.T) { | ||
| // Cursor transcripts use "role" instead of "type" and wrap user text in <user_query> tags. | ||
| // The transcript parser normalizes role→type, so condensation should work. | ||
| cursorJSONL := `{"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nhello\n</user_query>"}]}} | ||
| {"role":"assistant","message":{"content":[{"type":"text","text":"Hi there!"}]}} | ||
| {"role":"user","message":{"content":[{"type":"text","text":"<user_query>\nadd one to a file and commit\n</user_query>"}]}} | ||
| {"role":"assistant","message":{"content":[{"type":"text","text":"Created one.txt with one and committed."}]}} | ||
| ` |
There was a problem hiding this comment.
Cursor user prompts are wrapped in <user_query>...</user_query> (as noted in this test), but the current extraction path (transcript.ExtractUserContent → textutil.StripIDEContextTags) does not strip those tags. This means summarized/condensed user prompts will likely include the wrapper markup, which will leak into intent extraction and summaries. Consider stripping Cursor’s <user_query> wrapper during user-content extraction (either by extending StripIDEContextTags or adding a Cursor-specific cleanup step) and tightening this test to assert the tags are removed (not just that it contains "hello").
Entire-Checkpoint: f15cd73bd481
| if raw.ToolResponse.AgentID != "" { | ||
| event.SubagentID = raw.ToolResponse.AgentID | ||
| } | ||
| // TODO "tool_output": "{\"status\":\"success\",\"agentId\":\"3211cc34-8d8f-42de-9dcf-c19625b17566\",\"durationMs\":7901,\"messageCount\":1,\"toolCallCount\":1}", |
There was a problem hiding this comment.
need to fix sub-agent extraction
cmd/entire/cli/agent/cursor/hooks.go
Outdated
| HookNameSessionEnd = "session-end" | ||
| HookNameBeforeSubmitPrompt = "before-submit-prompt" | ||
| HookNameStop = "stop" | ||
| HookNamePreTool = "pre-tool" |
There was a problem hiding this comment.
use subagent hooks, not pre-tool
Entire-Checkpoint: 6ad0ff14a0da
# Conflicts: # cmd/entire/cli/explain.go
Cursor transcripts don't include token usage data, so return nil instead of an empty struct. Adds tests covering Cursor nil return, empty data, Claude Code basic parsing, and offset slicing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: a2ac14809312
|
@cursor review |
PR SummaryMedium Risk Overview Implements Cursor hook install/uninstall for Written by Cursor Bugbot for commit 0597c53. Configure here. |
| if raw.Task == "" { | ||
| return nil, nil //nolint:nilnil // nil event = no lifecycle action | ||
| } |
There was a problem hiding this comment.
The parseSubagentStart and parseSubagentStop functions both return nil, nil when raw.Task == "". However, the returned Event still includes the ToolUseID field populated from raw.SubagentID, which would be inconsistent - you're returning an event with a tool use ID but no task. Consider checking if raw.SubagentID is also empty, or remove the task check if subagents can exist without tasks.
| // TODO ToolInput: raw.Task, | ||
| Timestamp: time.Now(), | ||
| } | ||
| if raw.SubagentID != "" { | ||
| event.SubagentID = raw.SubagentID | ||
| } |
There was a problem hiding this comment.
The condition at lines 156-158 appears redundant. The event.SubagentID field is only set if raw.SubagentID != "", but event.ToolUseID was already unconditionally set to raw.SubagentID at line 152. If raw.SubagentID is empty, both fields end up empty anyway. Either the check should apply to both fields (move the conditional before the Event creation), or the SubagentID assignment should use a different source value (like Claude Code uses raw.ToolResponse.AgentID), or the check can be removed entirely.
| // TODO ToolInput: raw.Task, | |
| Timestamp: time.Now(), | |
| } | |
| if raw.SubagentID != "" { | |
| event.SubagentID = raw.SubagentID | |
| } | |
| SubagentID: raw.SubagentID, | |
| // TODO ToolInput: raw.Task, | |
| Timestamp: time.Now(), | |
| } |
| sessionStartCmd := cmdPrefix + "session-start" | ||
| sessionEndCmd := cmdPrefix + "session-end" | ||
| beforeSubmitPromptCmd := cmdPrefix + "before-submit-prompt" | ||
| stopCmd := cmdPrefix + HookNameStop | ||
| preCompactCmd := cmdPrefix + HookNamePreCompact | ||
| subagentStartCmd := cmdPrefix + HookNameSubagentStart | ||
| subagentEndCmd := cmdPrefix + HookNameSubagentStop |
There was a problem hiding this comment.
Inconsistent use of string literals vs constants when building hook commands. Lines 125-127 use hardcoded strings ("session-start", "session-end", "before-submit-prompt") while lines 128-131 use constants (HookNameStop, HookNamePreCompact, etc.). For consistency and maintainability, consider using constants for all hook commands, like cmdPrefix + HookNameSessionStart instead of cmdPrefix + "session-start".
Summary
Adds Cursor as a supported agent with full hook lifecycle, transcript storage, and checkpoint integration.
New: Cursor agent package (
agent/cursor/)types.go— Cursor-specific types: session info, prompt input, pre-compact, subagent start/stop with native Cursor fields (conversation_id,subagent_id,task, etc.)cursor.go— Agent implementation with JSONL transcript storage (ReadSession, WriteSession, ChunkTranscript, ReassembleTranscript)lifecycle.go—ParseHookEventmapping 7 Cursor hooks (session-start,session-end,before-submit-prompt,stop,pre-compact,subagent-start,subagent-stop) to normalized lifecycle events, plusReadTranscripthooks.go— Hook install/uninstall for.cursor/hooks.jsonwith unknown field preservationhooks_test.go— 11 hook tests (install, idempotent, force, preserve existing/unknown fields, uninstall)lifecycle_test.go— 14 lifecycle tests covering all hook types, edge cases, conversation_id fallback, and ReadTranscriptcursor_test.go— 32 tests covering identity, session storage (ReadSession/WriteSession round-trips), chunking, DetectPresence, and sanitizePathForCursorModified
registry.go— AddsAgentNameCursorandAgentTypeCursorconstantsexplain.go— IncludesAgentTypeCursorin JSONL transcript scoping/offset logichooks_cmd.go— Blank import for cursor packageinit()registrationsummarize/summarize.go+summarize_test.go— Cursor agent support in summarizationtranscript/types.go— DocumentedRolefield (Cursor usesrole, Claude Code usestype)transcript/parse.go+parse_test.go— Added sharedExtractModifiedFilesutility for agents with tool_use blocksDesign decisions
subagent_id,task,subagent_type, etc.) rather than mapping to Claude Code conventionssession-start,session-end,before-submit-prompt,stop,pre-compact,subagent-start,subagent-stoptool_useblocks, so modified file detection relies on git status instead of transcript parsingTest plan
mise run test)mise run test:integration)mise run lint)go build ./cmd/entire/)entire enable --agent cursorcreates.cursor/hooks.jsonentire disableremoves hooks from.cursor/hooks.jsonentire hooks cursor --helpshows all hook verbs🤖 Generated with Claude Code