Skip to content

feat(session): add turn diff route - #47795

Closed
nexxeln wants to merge 6 commits into
v2from
turn-diffs
Closed

nexxeln wants to merge 6 commits into
v2from
turn-diffs

Conversation

@nexxeln

@nexxeln nexxeln commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

summary

  • GET /api/session/:sessionID/diff?messageID&to&context (session.diff) → { data: FileDiff.Info[] }: the files changed by a turn, computed on request from the per-step snapshots already stored on assistant messages. Nothing new is persisted.
  • A turn is the assistant steps that follow a user message up to the next user message (a steer is a new prompt, so it starts a new turn). messageID defaults to the newest user message; to extends the range through a later user message's turn, so a client can show "all my steers" or a commit-range-like selection. Fully derivable from session_message, so forks and reverts need no special handling.
  • Compares the range's first recorded start snapshot with its last recorded end snapshot; only a step still running in the actively executing session compares against the working copy, so a crashed turn never diffs against later manual edits. Omitted context → full-file patches like vcs.diff.
  • Snapshot trees live in the repository of the Location that captured them: the range's Location is derived from location-switched messages and resolved through LocationServiceMap.get, so pre-move turns stay diffable; a range spanning a move is rejected. Errors: 400 InvalidRequestError (to before messageID, not a user message, location change), 404 MessageNotFoundError / SessionNotFoundError, 500 UnknownError with a logged ref on snapshot failure (shared failedSnapshot helper, also used by the revert handlers; missingMessage replaces three inlined mappings).
  • Second area: Git.tree.diff runs --name-status, --numstat, and the patch once per tree pair instead of three spawns per changed file, caps patch output at MAX_TOTAL_PATCH_BYTES (capped files get an empty patch, stats stay exact), uses core.quotepath=false so non-ASCII paths still match their patch chunk, and --no-ext-diff. Snapshot.diff diffs first and filters ignored paths from the result instead of listing changed files twice.
  • Backend only. History note: this branch first tried vcs.diff?mode=turn and then an idle-based turn model with a session_turn projection; both were reverted after discussion in favour of the per-user-message definition, so the intermediate commits net to zero and the PR should be squash-merged.

Note for reviewers (not changed here): packages/core/src/session/stats.ts:296 filters EventTable.type with the unversioned SessionEvent.UsageRecorded.type while bus.ts stores versioned type names (session.usage.recorded.1), so that query cannot match persisted rows.

testing

  • bun typecheck in schema, protocol, core, server, client, plugin, tui, cli, app, sdk, www.
  • core test/session-diff.test.ts (one scenario: default/last turn, explicit turn, ranges, steer as its own turn, to ordering and non-user-message errors, unknown message, unrecorded end fallback, running-step working-copy diff only when active, revert truncation, fork) and test/git.test.ts (patch cap + non-ASCII header match; verified the assertion fails without core.quotepath=false); plus snapshot/revert/projector/compaction/prompt suites — 97 pass.
  • server test/session-diff.test.ts (200/400/404 contract on the default non-persisting bus); full server suite 57 pass. client 160 pass; schema 61 pass + the pre-existing contract-hygiene "limits Any" failure.
  • protocol generate-openapi.ts --check, core migration --check, www check:generated + build, prettier on touched files.

Extend vcs.diff with mode=turn, which diffs the files changed by a session's last turn from the per-step snapshots: the first step's start tree against the last step's end tree, or the working copy while that step still runs. Vcs.Mode stays the VCS backend contract; the wider Vcs.DiffSource is route-only and the server dispatches turn to Session.turnDiff instead of the VCS provider.
Add a Last turn option to the /diff source chooser when the viewer opens from a session. It requests mode=turn with the session ID, skips base resolution, and falls back to branch when a remembered or configured turn source is used outside a session. Accept turn in diffs.source.
Replace the vcs.diff?mode=turn source and TUI picker with session routes: GET /api/session/:id/turn lists turns (one busy period each, projected into a new session_turn table because durable event persistence is opt-in) and GET /api/session/:id/diff diffs a contiguous turn range from its first recorded start snapshot to its last recorded end snapshot.
Git.tree.diff ran name-status, numstat, and a patch per changed file. Run each once over the tree pair, split the patch with VcsPatch.chunksByFile, cap patch output at MAX_TOTAL_PATCH_BYTES with empty patches past the cap, and let Snapshot.diff filter ignored paths from the result instead of passing every changed path as a pathspec.
@nexxeln nexxeln changed the title feat(vcs): add last turn diff source feat(session): add turn list and turn diff routes Sep 7, 2026
A turn is the assistant steps that follow a user message up to the next user message, so a steer is its own turn. That is fully derivable from session_message, so the session_turn projection, its migration, the turn list route, and the Session.Turn contract go away. session.diff takes messageID (default: newest user message) plus an optional to for contiguous ranges, resolves the range's Location from location-switched messages, and only compares against the working copy while the Session is actively executing. Shared missingMessage and failedSnapshot handler helpers replace the inlined mappings.
Patch headers have no -z form, so core.quotepath=false keeps chunksByFile matching non-ASCII names, and --no-ext-diff keeps a user's external diff tool out of the batched spawn. Snapshot.diff now diffs first and filters ignored paths from the result instead of listing changed files twice, and returns early for an empty explicit selection.
@nexxeln nexxeln changed the title feat(session): add turn list and turn diff routes feat(session): add turn diff route Sep 7, 2026
@nexxeln

nexxeln commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #47821: same feature reworked per the design discussion (turn = between idle states, recorded as an idle message instead of a table; GET /session/:id/diff?messageID&to), on a clean branch.

@nexxeln nexxeln closed this Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant