Skip to content

feat(chat): one-click "Revert changes" button for agent modifications (closes #85)#192

Open
haoyu-haoyu wants to merge 1 commit intoOpenLAIR:mainfrom
haoyu-haoyu:feat/revert-agent-changes
Open

feat(chat): one-click "Revert changes" button for agent modifications (closes #85)#192
haoyu-haoyu wants to merge 1 commit intoOpenLAIR:mainfrom
haoyu-haoyu:feat/revert-agent-changes

Conversation

@haoyu-haoyu
Copy link
Copy Markdown
Contributor

Summary

Adds a "Revert changes" button to each finished Coding Agent turn so users can roll back that turn's file modifications from the chat UI instead of context-switching to the terminal for git restore .. Closes #85.

What it does

  • Scans the messages in the finished agent turn for Edit / Write / ApplyPatch / MultiEdit / FileChanges tool calls
  • Extracts every file path those tools actually modified (handles both object and stringified-JSON toolInput, multi-file file_paths arrays, Codex's kind: path newline format, and object-map changes from patch-apply results)
  • Click → inline confirmation with an explicit warning → POST /api/git/revert-agent-changes
  • Backend reverts each file independently:
    • untracked regular files → unlink (directories and out-of-repo symlinks are refused)
    • tracked worktree+staged modifications → git restore --source=HEAD --staged --worktree (handles MM / AM / AD / MD / DM)
    • staged adds → git reset HEAD + unlink
    • rename / copy pairs → skipped (refuses to leave half-renamed state)
    • per-file error reporting so one bad entry doesn't abort the rest

Safety

  • Only touches files this specific agent turn modified — the main ask from the issue. Any manual edits to unrelated files are preserved.
  • Path traversal blocked via the existing safePath helper.
  • Untracked-symlink deletion re-validates realpath() against the project root before unlink.
  • git status --porcelain -z prevents filenames containing " -> " from confusing rename parsing.
  • Confirmation dialog explicitly warns: "Any manual edits to these files will also be lost" — honest about git restore semantics since we restore to HEAD, not a pre-agent snapshot. Per-turn exact-snapshot revert (with conflict detection for files the user also touched) would need pre-agent content capture and feels like a separate PR.

Accessibility

  • role="group" on the inline confirmation with aria-label, focus moves to the destructive action on open, Escape cancels and restores focus to the trigger
  • role="status" + aria-live on reverting / done / error states
  • Decorative SVGs marked aria-hidden

Test plan

All 130 Vitest tests pass (incl. 8 new backend integration tests against a real temp git repo, 1 in-process Express route test, and 12 new frontend unit tests). tsc --noEmit clean.

  • npm test -- --run (130 passed, 18 test files)
  • npm run typecheck (clean)
  • Backend: untracked file delete, tracked file restore, staged add, mixed MM state, rename skip, rename-with-modification skip, symlink safety (external file preserved), directory rejection, path traversal blocked, filename containing " -> "
  • Frontend: object toolInput, stringified-JSON toolInput, multi-file file_paths, Codex FileChanges string format, object-map changes, array-of-strings changes, malformed JSON, subagent children, tool-error skipping, no-result skipping
  • Route: 400 on missing project, 400 on non-array files, empty list, happy path, partial success reporting
  • Manual smoke test: run agent, modify multiple files, click Revert, confirm only those files roll back

i18n

Added revertAgent block to en, zh-CN, ko chat locales with i18next pluralization (_one / _other).

Follow-ups (not in this PR)

  • Per-turn exact-snapshot revert with conflict detection (requires capturing pre-agent content)
  • Rename handling: currently skipped; could surface a custom UI prompt
  • Korean locale has some unrelated pre-existing missing keys (providerInfo.nano, readyPrompt.nano) — left for a separate i18n-sync PR

…R#85)

Adds a "Revert changes" button to each finished agent turn.  Clicking it
opens an inline confirmation, then sends the list of files that turn
modified (via Edit / Write / ApplyPatch / MultiEdit / FileChanges tool
calls, scraped from both toolInput and toolResult payload shapes) to a
new POST /api/git/revert-agent-changes endpoint.  The endpoint reverts
each file independently:

  - untracked regular files the agent created are unlinked (directories
    and symlinks pointing outside the repo are refused)
  - tracked worktree+staged modifications are restored to HEAD with
    git restore --source=HEAD --staged --worktree
  - staged adds are unstaged and the working-tree copy is removed
  - rename / copy pairs are skipped so a revert cannot leave the repo
    with a half-applied rename
  - per-file error reporting, never all-or-nothing

Porcelain is read once with --porcelain -z so filenames containing a
literal " -> " do not confuse rename parsing.  Path traversal is blocked
via the existing safePath helper; untracked-symlink deletion is
additionally re-validated with realpath() before unlink.

Frontend uses authenticatedFetch (matching GitPanel patterns) and
exposes aria-live status updates, focus-on-open, Escape to cancel.
Copy is translatable via a new revertAgent block in en / zh-CN / ko.

Tests: 8 new backend integration tests against a real temp git repo
(including MM, AD, renamed, rename-with-modification, filenames
containing " -> ", symlink safety, directory-deletion refusal) plus a
route-level smoke test hitting POST /api/git/revert-agent-changes
through an in-process Express app.  12 new frontend unit tests covering
object / stringified-JSON / multi-file / Codex FileChanges / object-map
changes / malformed input / subagent-child-tool extraction.

All 130 tests pass; tsc --noEmit clean.

Design notes:
- Reverts restore to HEAD state.  Any manual edits to one of the same
  files the agent modified will also be lost; the confirmation text
  warns about this explicitly.  Edits to OTHER files are untouched.
- Per-turn exact-snapshot revert with conflict detection would require
  capturing pre-agent content for each tool call; that's a larger
  change and can be a follow-up.
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.

[Feature]: Add one-click "Revert/Discard Changes" for Coding Agent modifications

1 participant