Repository navigation
Cursor: attribute sessions to their repo + nest subagents under the parent - #242
Conversation
PR Summary by QodoCursor: attribute sessions to repos and nest subagent transcripts under parents
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
Code Review by Qodo
1.
|
|
[P1] This appears to revert the AI-30 bracketed-paste path back to raw text plus Enter, and the dedicated regression test is deleted in the same branch comparison. The previous code intentionally wrapped server-origin input in ESC[200~/ESC[201~ so large or multiline messages are consumed as one paste before the separate CR. With raw Note: I am posting this as a top-level comment because GitHub would not accept it inline; the file was not present in the authoritative |
…rent Repo attribution (AI-1152): detect the git repo CLI-side and attach a `repository` node to the Cursor sessionStart so the server emits RepositoryDetected and the session groups under its repo. Cursor payloads carry `workspace_roots` (not `cwd`), so add RepositoryDetection.EnrichWithRepositoryInfoFromCwd + extract BuildRepositoryNode; the live hook enriches from workspace_roots[0] and the import path attaches the detected repo. Subagent nesting (AI-1153): a Cursor subagent runs as its own session with no explicit parent link — only the child's first user_query matches the parent's Task/Agent prompt. New CursorSubagentCorrelator recovers that link by prompt hash; CursorImportSource then ingests a detected child under the parent's AgentSubsession stream (subagent-start + transcript-with-agent_id + subagent-stop) instead of as a standalone top-level session. Also fixes the subagent half of backfill (AI-1154) on `kcap import cursor`. Pairs with the kcap-server PR (shared repo recorder + Cursor normalizer id scoping for subsessions). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ing + resume)
Round-1 review findings on the subagent import path:
1. Ordering — children were imported as separate routed sessions in parallel with
the parent; a parent's session-end could land before a child's subagent-start,
and SubagentStarted (a reactivation event) would flip the ended parent back to
Active. Fix: the parent now imports its subagent children inline, BEFORE its own
session-end (mirrors SessionImporter.SendAgentLifecycle), so the parent's
SessionEnded stays the last event on its stream. Children are marked
IsSubagentChild and their own routed import no-ops (Skipped).
2. Watermark — the child was probed against its own (now non-existent) top-level
stream, so re-imports reposted the full child transcript from line 0. Fix:
SendSubagentLifecycleAsync resumes from the subsession watermark
(GET /api/sessions/{parent}/last-line?agentId={child}); FetchServerLastLineAsync
gained an optional agentId.
Tests: parent_import_nests_subagent_child_before_its_own_session_end (ordering guard),
subagent_child_import_is_a_noop_handled_by_the_parent. CursorImportSource suite 35/35.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Subagent-stop payload now sends the full SubagentStopHook shape (stop_hook_active, agent_transcript_path, last_assistant_message) — an incomplete body can be rejected before HandleSubagentStop. [P1] - Child transcript send now uses failOnError:true so a rejected/failed POST aborts the parent import (fail-closed, re-run repairs) instead of leaving an empty completed subagent while reporting success. [P1] - CursorSubagentCorrelator orders sessions by id before building the prompt->parent map, so "first writer wins" is deterministic across runs (filesystem discovery order is not stable). [P2] - Correlation no longer aborts classification: a single unreadable/malformed transcript is skipped (per-session try/catch) instead of throwing out of ClassifyAsync. - CursorHookCommand extracts workspace_roots[0] safely (JsonValue.TryGetValue) so a non-string element can't throw and drop the whole sessionStart hook. Tests: CursorImportSource 36/36 (adds fail-closed + full-stop-payload guards), CursorSubagentCorrelator 5/5 (adds determinism + bad-entry-skip). AOT clean. Known limitation (filed as follow-up): correlation only sees the discovered (filtered) session slice, so a --session/--cwd/--since import that excludes the parent won't nest the child. Full import (and backfill) correlate correctly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Duplicate Task prompts across DISTINCT parents are now treated as ambiguous: the correlator drops the link instead of attributing the child to a guessed parent (a single parent Tasking the same prompt more than once is still fine). Supersedes the earlier order-by-id "first writer wins". - Subagent-start/-stop payloads now include cwd (and stop adds transcript_path) for wire-shape parity with SendAgentLifecycle / the other vendors' subagent senders (defensive — cwd isn't required by the current SubagentStart/StopHook binding, but matches the established contract). Tests: CursorSubagentCorrelator 6/6 (ambiguous-no-link + same-parent-twice), CursorImportSource 36/36. AOT clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ail-closed subagent import HookBase declares `cwd` and `transcript_path` as REQUIRED, so the earlier subagent start/stop payloads (missing them) would have been rejected by the server (400) → parent import fail. The full shape is now sent on both hooks (cwd + transcript_path; added in the previous commit for start/stop, verified complete here). Completes the fail-closed contract so a re-run reliably repairs a partial import: - subagent-start / subagent-stop now send `strict: true`; the server's HandleSubagentStart/Stop return 500 (not 200) when the SubagentStarted/Completed write fails under strict, so PostSyntheticHookAsync sees the failure and the parent import aborts before session-end. - CLI TranscriptBatch gains a `strict` field, and PostTranscriptBatch sets Strict = failOnError. Previously failOnError only threw on non-2xx HTTP/transport errors; the server only turns per-line normalization failures into a non-2xx response when Strict, so a failed child line could return 200 and the import would proceed. Now fail-closed callers (the Cursor subagent transcript; OpenCode import) also fail on server-side normalization failures. Tests: full CLI unit suite 2079/2079 (adds cwd/transcript_path/strict assertions on both subagent hooks + strict on the subagent transcript). AOT clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4ff9967 to
f1a9e5d
Compare
|
This is a stale-base diff artifact, not a real change — the PR does not touch the bracketed-paste path. Root cause: this branch was cut from the pinned submodule commit ( Fixed regardless: I rebased the branch onto current 🤖 Addressed by Claude Code |
Fixes two Cursor ingest gaps. Pairs with the kcap-server PR (shared repo recorder + Cursor normalizer id-scoping for subsessions) — both halves ship together. Tracks Linear AI-1152 (repo attribution), AI-1153 (subagent nesting), and the subagent half of AI-1154 (backfill).
AI-1152 — repo attribution
Cursor sessions never grouped under a repository (only "All repos") because the sidebar keys on
repo_hash, set only by aRepositoryDetectedevent, which needs a git-detectedowner/repo_name. Every other vendor's hook path attaches arepositorynode viaRepositoryDetection.EnrichWithRepositoryInfo— but that readscwd, and Cursor payloads carryworkspace_rootsinstead, andCursorHookCommandnever called it.RepositoryDetection.EnrichWithRepositoryInfoFromCwd+ extractBuildRepositoryNode(shared).CursorHookCommandenrichessessionStartfromworkspace_roots[0](budget-bounded, fail-open).CursorImportSourcedetects the repo from the workspace folder and attaches it to the syntheticsessionStart(also fixes historical import/backfill).AI-1153 — nest subagents under the parent
A Cursor subagent (
Task/Agent) runs as its own session/transcript with no explicit parent link — the only in-data signal is that the child's firstuser_query(minus the<user_query>wrapper) is byte-identical to the parent'sTaskinput.prompt.CursorSubagentCorrelator.Correlaterecovers the link by SHA-256 prompt hash (self-link guarded).CursorImportSource.ClassifyAsyncbuilds the map and stampsParentSessionId/SubagentType;ImportSessionAsyncroutes a detected child throughImportSubagentAsync—/hooks/subagent-start(session_id=parent, agent_id=child) + transcript batches withagent_id=child (→AgentSubsession-{parent}-{child}) +/hooks/subagent-stop— and emits no standalone session lifecycle. This mirrors how Gemini/OpenCode subagents nest, so no server read-model change is needed; the child never creates a top-level card.Tests
CursorSubagentCorrelatorTests(3),CursorImportSourceTestsincl. repo-attach + subagent-routing (34 total). AOT publish clean (no IL2026/IL3050).🤖 Generated with Claude Code