Commit Graph

11 Commits

Author SHA1 Message Date
Vantz Stockwell
661490e925 perf: RDP event-driven frames + MCP terminal \r fix
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m28s
RDP performance overhaul:
- Switched from polling (rAF loop calling rdp_get_frame every tick)
  to event-driven rendering (backend emits rdp:frame:{id} when
  frame buffer updates, frontend fetches on demand)
- Eliminates thousands of empty IPC round-trips per second when
  the screen is idle
- Backend passes AppHandle into run_active_session for event emission
- Frontend uses listen() instead of requestAnimationFrame polling

MCP terminal fix:
- terminal_type and terminal_execute now send \r (carriage return)
  instead of \n (newline) — PTY terminals expect CR to submit
- Fixes commands not auto-sending, requiring manual Enter press

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:44:53 -04:00
Vantz Stockwell
9f6085d251 perf: RDP optimizations — binary IPC, frame throttle, fast PNG
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 4m8s
1. Binary IPC: get_frame returns Vec<u8> directly instead of base64
   string. Eliminates 33% encoding overhead + string allocation +
   atob() decode on frontend. Frontend receives number[] from Tauri.

2. Frame throttle: reduced from ~30fps to ~20fps (every 3rd rAF tick).
   20% fewer frames with negligible visual difference for remote desktop.

3. Fast PNG compression: screenshot_png_base64 uses Compression::Fast
   for MCP screenshots, reducing encode time.

4. Dirty flag: already existed but documented — empty Vec returned when
   frame hasn't changed, frontend skips rendering.

Net effect: ~45% reduction in IPC bandwidth (no base64 overhead) +
20% fewer frame fetches + faster screenshot encoding.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:54:25 -04:00
Vantz Stockwell
58df4ac5c8 fix: MCP sees live sessions — wrap DashMap in Arc for shared state
DashMap::clone() deep-copies all entries into a new map. The MCP
server's cloned SshService/SftpService/RdpService/ScrollbackRegistry
were snapshots from startup that never saw new sessions.

Fix: wrap all DashMap fields in Arc<DashMap<...>> so clones share
the same underlying map. Sessions added after MCP startup are now
visible to MCP tools.

Affected: SshService, SftpService, RdpService, ScrollbackRegistry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 14:28:13 -04:00
Vantz Stockwell
e6766062b1 fix: MCP startup panic + RDP crypto provider panic
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m57s
MCP: RdpService had a manual Clone impl that called unreachable!().
Replaced with a real clone that shares the DashMap. MCP server can
now clone all services and start successfully.

RDP: rustls needs CryptoProvider::install_default() before any TLS
operations. ironrdp-tls uses rustls for the RDP TLS handshake.
Added aws_lc_rs provider installation at app startup.

Both panics found via wraith.log debug logging from v1.6.3.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:28:46 -04:00
Vantz Stockwell
357491b4e8 feat: debug logging macro + MCP tools inject button in copilot
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m58s
Debug logging:
- wraith_log!() macro available in all modules, writes to wraith.log
- SSH connect/auth, PTY spawn, RDP connect all log with session IDs
- MCP startup panic now shows the actual error message

Copilot "Tools" button:
- Shows when a PTY session is active in the copilot panel
- Injects a formatted list of all 18 MCP tools into the chat
- Groups tools by category: session, terminal, SFTP, network, utilities
- Includes parameter signatures so the AI knows how to call them

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:12:23 -04:00
Vantz Stockwell
03bb6f3ccf fix: RDP panic logging + CWD starts at home directory
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m16s
RDP: wrapped connection thread in catch_unwind so panics are logged
to wraith.log instead of silently killing the channel. Error message
now directs user to check the log.

CWD: changed cd . to cd ~ after OSC 7 hook injection so SFTP starts
at the user's home directory on macOS (where / requires explicit nav).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:02:30 -04:00
Vantz Stockwell
add0f0628f feat: MCP auto-inject + RDP screenshot tool
Some checks failed
Build & Sign Wraith / Build Windows + Sign (push) Failing after 16s
- Auto-inject CLAUDE_MCP_SERVERS env var when copilot PTY spawns,
  so Claude Code auto-discovers wraith-mcp-bridge without manual config
- RDP screenshot_png_base64() encodes frame buffer as PNG via png crate
- Bridge binary exposes terminal_screenshot tool returning MCP image
  content (base64 PNG with mimeType) for multimodal AI analysis
- MCP session list now includes RDP sessions with dimensions
- /mcp/screenshot HTTP endpoint on the internal server

"Screenshot that RDP session, what's the error?" now works.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 23:17:36 -04:00
Vantz Stockwell
8c431d3d12 fix: SSH input deadlock — output loop held channel mutex across await
Root cause: The output reader loop held Arc<TokioMutex<Channel>> while
calling ch.wait().await. After the initial prompt rendered and the server
went idle, wait() blocked indefinitely holding the lock. ssh_write()
could never acquire the mutex to send keystrokes. Permanent deadlock.

Fix: Separated read/write paths. The output loop now owns the Channel
exclusively via tokio::select!, receiving resize/shutdown commands through
an mpsc channel. Writes go through Handle::data(channel_id, data) which
bypasses the Channel entirely — no shared mutex, no deadlock.

Also killed all compiler warnings (unused imports in rdp module).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:28:09 -04:00
Vantz Stockwell
99ecbe739e feat: RDP clipboard paste, keyboard grab default ON, frame dirty flag
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 2m49s
1. Clipboard paste (rdp_send_clipboard): simulates typing each character
   via scancode key press/release events. Full ASCII coverage including
   all symbols, numbers, and shifted characters. Handles 32-char
   generated passwords without manual typing.

2. Keyboard grab defaults to ON so RDP sessions accept keyboard input
   immediately without requiring the user to click the toolbar toggle.

3. Frame dirty flag: GraphicsUpdate sets an AtomicBool, get_frame only
   encodes + returns base64 when dirty (returns empty string otherwise).
   Eliminates ~8MB/frame base64 encoding on unchanged frames at 30fps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 17:57:16 -04:00
Vantz Stockwell
8e335f92b5 refactor: clean up backend — strip verbose docs, add connection timeouts, fix macOS data dir
- Remove redundant doc comments and section headers across SSH, RDP, and command modules
- Add 10s timeout on SSH connect/auth, 15s timeout on RDP connection
- Fix macOS data directory to use ~/Library/Application Support/Wraith
- Add generic disconnect_session command alongside disconnect_ssh
- Simplify SFTP setup and RDP error handling
- Add explicit label/url to main window config

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:30:02 -04:00
Vantz Stockwell
c75da74ecd feat: Phase 4 complete — RDP via ironrdp
Rust RDP service: ironrdp client with full connection handshake
(TCP -> TLS -> CredSSP -> NLA), pixel buffer frame delivery,
mouse/keyboard input via scancode mapping, graceful disconnect.
Runs in dedicated thread with own tokio runtime to avoid Send
lifetime issues with ironrdp trait objects.

Vue frontend: RdpView canvas renderer with 30fps polling,
mouse/keyboard capture, RdpToolbar with Ctrl+Alt+Del and
clipboard. SessionContainer handles both SSH and RDP tabs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:05:11 -04:00