Skip to content

Latest commit

 

History

History
313 lines (220 loc) · 16.2 KB

README.md

File metadata and controls

313 lines (220 loc) · 16.2 KB

OpenCode DiffGate

OpenCode DiffGate

English | 中文

AI changed your files and you're not sure whether to keep them? Preview the diff first.

Gives OpenCode the same Accept / Reject workflow you know from Copilot, right inside VS Code. It connects to the OpenCode CLI via real-time SSE, automatically tracks every AI file edit, and lets you review, accept, or reject changes file by file.


⚠️ Only tracks AI tool calls (write / edit) — NOT a filesystem watcher

Unlike tools like Hunkwise that watch the entire Git working tree, DiffGate listens exclusively to OpenCode's SSE tool events. It never scans your workspace or monitors file system changes.

Why this matters: In a monorepo with dozens of sub-packages, a filesystem watcher would scan every node_modules, every build artifact, every ignored directory — and crash or freeze. DiffGate stays fast because it only sees what the AI actually touches, nothing more.

Features

  • Manually start or connect to an OpenCode server via VS Code commands — no auto-launch, no surprises
  • After each AI edit: red deleted-line insets, green added-line highlights, and ✓ Accept / ✗ Reject CodeLens buttons inline
  • Sidebar panel lists all pending files with Accept All / Reject All bulk actions
  • Click a filename in the sidebar to open the VS Code native diff view (with the proposed API enabled, it shows the before/after comparison; without it, it degrades to CodeLens only)
  • Dirty check before Reject: detects manual edits and warns before overwriting
  • AI-only change tracking — listens to SSE tool events (write / edit), not the filesystem. No performance hit in large folders like node_modules or monorepos
  • Instant disconnect detection + one-click reconnect — retains the terminal process, reconnects SSE automatically
  • Multi-session support — connect to multiple OpenCode instances simultaneously, status bar shows all sessions
  • Status bar shows live session states; click to open the management panel

Installation

Install via .vsix file:

code --install-extension opencode-diffgate-0.0.1.vsix --enable-proposed-api oc-diff.oc-vscode-diff

Note: --enable-proposed-api is required because the red deleted-line insets (WebviewEditorInset) depend on a VS Code proposed API. Without it, the extension still works (green highlights + CodeLens), but red deletion insets won't render.

Engine requirement: VS Code ≥ 1.86.0

Getting Started

After installing, open any VS Code workspace. The extension activates automatically (onStartupFinished). Three entry points become available:

💡 Tip: If you notice high memory usage, check whether debug logging is enabled. Debug logs output detailed processing traces to the VS Code Output panel (Output → OpenCode Diff), which adds memory overhead. It is off by default. Toggle it via Ctrl+Shift+P → diffgate: Toggle Debug Logging (切换调试日志), or look for diffgate.debugLog in extension settings.

① Command Palette (Ctrl+Shift+P)

Type these commands to start or connect to OpenCode:

  • diffgate: Start OpenCode Server (启动服务端) — auto-creates a terminal and connects
  • diffgate: Connect to OpenCode Server (连接服务器) — enter a URL manually to connect

② Status Bar

The extension icon appears on the left side of the VS Code status bar:

  • Idle: shows OpenCode Diffgate
  • Connected: shows ✓ oc(port)
  • Click the status bar at any time → opens the session management panel where you can view all sessions, reconnect disconnected ones, or focus a terminal

③ Sidebar Panel

Find the OpenCode DiffGate icon or panel name in the Activity Bar (left sidebar). If it's not visible there:

  1. Check the bottom panel area (same row as the terminal output) — if collapsed, expand it first
  2. Right-click the panel tab → Move to Side Bar to pin it to the sidebar

Once connected, the panel automatically populates with pending files whenever AI edits something.


How It Works

Connection

DiffGate provides two connection modes, both triggered from the command palette (Ctrl+Shift+P):

Command Description
diffgate: Start OpenCode Server (启动服务端) Creates a new VS Code terminal → runs opencode --port <random> → connects via SSE automatically
diffgate: Connect to OpenCode Server (连接服务器) Prompts for an OpenCode URL → connects via SSE directly (no terminal)

Multi-root workspaces: When you have multiple workspace folders open, Start OpenCode Server shows a QuickPick selector so you can choose which directory OpenCode runs in.

Operation flow:

         ┌──────────────────────────────────┐
         │   Ctrl+Shift+P →                  │
         │   "Start OpenCode Server"         │
         └────────────┬─────────────────────┘
                      │
                      ▼
         ┌──────────────────────────────────┐
         │   Multi-root workspace?           │
         │   Yes → QuickPick folder picker   │
         │   No → use the only root          │
         └────────────┬─────────────────────┘
                      │
                      ▼
         ┌──────────────────────────────────┐
         │   Create terminal "oc(port)"      │
         │   Run `opencode --port XXXX`      │
         └────────────┬─────────────────────┘
                      │
                      ▼
         ┌──────────────────────────────────┐
         │   SSE connected                   │
         │   Status bar: ✓ oc(port)          │
         │   Ready to receive AI events      │
         └──────────────────────────────────┘

Disconnect & Reconnect

When the OpenCode process exits or the network drops:

         ┌──────────────────────────────────┐
         │   SSE connection drops            │
         │   onDisconnect fires immediately  │
         └────────────┬─────────────────────┘
                      │
                      ▼
         ┌──────────────────────────────────┐
         │   Terminal still alive?           │
         │   ← Yes ──────────────────────── │
         │   Keep session, mark ✗            │
         │   ← No (Ctrl+W closed terminal)   │
         │   Remove session entirely         │
         └──────────────────────────────────┘

One-click reconnect:

         Status bar shows ✗ oc(port)
                  │
                  ▼
         Click status bar → QuickPick panel
                  │
                  ▼
         Lists all sessions with terminals:
         ✓ oc(8080)  Connected
         ✗ oc(8081)  Disconnected · click to reconnect
                  │
         Click ✗ item ────────────────►
                  │                     ▼
         Sends into terminal:          Immediate SSE connect
         "opencode --port 8081"        SSE built-in retry 
                                       catches server startup
                                       
                                       
         Status bar back to ✓ oc(8081)

Status bar format:

Display Meaning
✓ oc(8080) Session on port 8080 is connected
◎ oc(8080) Session is starting or connecting
✗ oc(8080) Session disconnected, can reconnect
✓ oc(8080) ✗ oc(8081) Up to 2 sessions shown at once
✓ oc(8080) ✗ oc(8081) (3) Parenthesized number = more sessions exist

Clicking the status bar in any state opens the session management panel.

Snapshot Tracking

When OpenCode is about to run a write or edit tool, DiffGate receives a tool.called SSE event and immediately snapshots the target file (in memory). After the tool succeeds (tool.success), it snapshots the modified content.

Diff calculation is cumulative: if AI modifies the same file multiple times, DiffGate always compares against the very first baseline. This shows the full accumulated change from "AI started editing" to "AI finished".

AI-Only Tracking (≠ Filesystem Watchers)

Unlike tools like Hunkwise that monitor the entire Git working tree, DiffGate does not watch the filesystem or scan the workspace. It relies solely on SSE tool events:

  • You edit a file manually → nothing happens, no CodeLens, no sidebar entries
  • AI edits a file → immediately captured and marked for review
  • Large folders (node_modules, big monorepos) → zero performance impact
  • Each change is cleaned up immediately after Accept / Reject — no stale state

Dirty Check

If you manually edit a file after AI modified it, and then click Reject, DiffGate shows a warning:

"File xxx has been manually modified. Force restore?"

Choosing "Force restore" overwrites the file with the baseline content (including your manual edits). Choosing "Cancel" aborts the restore.

In-Memory Snapshots (No Disk Writes)

All snapshot data is stored in memory (SnapshotStore) and never touches disk. Closing VS Code or reloading the window loses all pending review state. This is intentional: snapshots are temporary diff-review helpers, not persistent state.


Usage

Reviewing a Single File

After AI modifies a file, the editor shows red deletion insets, green added-line highlights, and CodeLens buttons above each hunk:

  • ✓ Accept — keep the AI change, remove the file from review tracking
  • ✗ Reject — restore the file to its pre-AI baseline, remove from tracking

Clicking Reject triggers the dirty check. If the file was manually modified, a confirmation dialog appears.

Using the Sidebar

Open the OpenCode DiffGate sidebar panel:

  • List of all pending files, each showing filename, directory, added lines (green), and deleted lines (red)
  • Accept / Discard buttons on each file
  • Accept All / Discard All bulk action buttons at the top
  • Click a filename to open the VS Code native diff view comparing baseline vs. current content

Command Reference

The following commands are available in the command palette (Ctrl+Shift+P):

Type in Command Palette Description
diffgate: Start OpenCode Server (启动服务端) Create terminal and start an OpenCode server
diffgate: Connect to OpenCode Server (连接服务器) Enter a URL and connect directly
Diffgate: 管理会话 Open session management panel (view/reconnect/focus terminals)
diffgate: Accept AI Change (接受AI修改) Accept AI changes in the active editor
diffgate: Reject AI Change / Restore Original (拒绝AI修改) Revert AI changes in the active editor
diffgate: Accept All AI Changes (接受所有AI修改) Accept all pending files at once
diffgate: Reject All AI Changes (拒绝所有AI修改) Reject all pending files at once
diffgate: Open Diff View (打开差异对比) Open VS Code native diff view
diffgate: Show OpenCode Diff Logs (查看日志) Show extension log output
diffgate: Toggle Debug Logging (切换调试日志) Toggle debug logging on/off

Accept / Reject / Accept All / Reject All can also be triggered from the sidebar panel buttons.


Development

Project Structure

src/
├── extension.ts          # Entry: multi-session management, connect/reconnect, status bar
├── sseClient.ts          # SSE event stream client (auto-reconnect, tool events)
├── snapshotStore.ts      # In-memory snapshot storage (baseline + post-AI content)
├── decorationManager.ts  # Editor decoration rendering (red/green highlights + insets)
├── diffCodeLens.ts       # CodeLens provider (Accept / Reject buttons)
├── reviewPanel.ts        # Sidebar panel
├── diffContentProvider.ts # Native diff view content provider
├── commands.ts           # Command registration
└── storePool.ts          # Multi-session snapshot pool

Build & Package

# Compile TypeScript
npm run compile

# Package .vsix
npm run package

Internally: esbuild bundles the source + diff dependency into a single out/extension.js, then @vscode/vsce generates the .vsix.


Known Limitations

This is a personal project built by an individual developer — core features work, but expect rough edges. Always welcome to any contributions!

🐛 Bugs

  • Sidebar → native diff view breaks proposal API insets: Clicking a filename in the sidebar panel opens a VS Code native diff view, which causes the proposed API insets (red deletion lines) to malfunction — the before/after diff comparison fails to render, degrading to CodeLens-only display. Reload the window to restore normal diff view.

  • Scrolling while many webview insets are visible causes high CPU: When AI modifies a large number of hunks, each deleted chunk gets its own webview inset. Scrolling through the file with the mouse wheel while many webviews are present causes continuous re-rendering that spikes CPU usage. Note: using the sidebar panel's file expand/collapse to navigate between modification positions does not trigger this issue.

  • First modification may get silently accepted after subsequent edits: In rare cases, the first hunk tracked by DiffGate is automatically accepted after AI makes additional modifications (2nd, 3rd, etc.). The root cause is unclear and cannot be stably reproduced.

  • First file's diff gets auto-accepted after second file is modified: When AI modifies multiple files sequentially, the pending diff in the first file may be silently accepted (removed from the sidebar) after the second file's modification completes. Root cause unknown, cannot be stably reproduced.

🔮 Future Roadmap (Known Limitations)

  • In-memory snapshots are not persistent. Closing VS Code or reloading the window loses all pending review state.
  • Only write / edit tools are tracked. AI changes made via shell, command, or other indirect methods are not captured.
  • OpenCode CLI must be available in the terminal for auto-start to work.
  • Red deletion insets depend on proposed API. Without --enable-proposed-api, only green highlights + CodeLens are shown.

License & Credits

This project is open source under the MIT License. See LICENSE.md for the full text.

Third-Party Notices — This project incorporates code and inspiration adapted from the following open-source projects:

  • hunkwise — The inline decoration manager (red deletion insets), sidebar review panel UI, and splash screen were adapted from hunkwise. Licensed under MIT. Copyright (c) the hunkwise contributors.
  • AI Diff Viewer by mtglitch — The snapshot-based inline diff review approach and per-hunk tracking logic were studied as reference.
  • AI CLI diff view by konan1947 — The extension's SSE event handling design was referenced.
  • Diffus by RostislavDugin — The workspace snapshot and filesystem detection patterns were reviewed.