feat(gui): macOS assistance-GUI backend (status item, consent, NSWindowSharingNone) (VA-55) #147

Merged
longjacksonle merged 1 commit from feat/VA-55-macos-assistance-gui into main 2026-07-15 19:35:22 +02:00

Implements VA-55 (VA-24 follow-up): the macOS assistance-GUI backend, bringing the tray/status-item, in-process consent dialog, operator-connected notification, and screen-capture opt-out to macOS. VA-24 shipped this for Linux and VA-56 for Windows; both deferred macOS because it cannot be built in the Linux CI sandbox (no macOS SDK).

What landed

GUI crate (vervain-agent-gui):

  • Tray / status item: uses tray-icon's macOS NSStatusItem backend on the same in-winit-thread model as Windows (created + pumped on eframe's main thread, menu clicks via the global MenuEvent channel). tray::build/refresh and the app's owned-tray logic are now any(windows, macos). Shows connection state and a "Disconnect now" item.
  • Capture exclusion: apply_capture_exclusion gains a macOS arm that reads the winit NSView via raw-window-handle and sends -[NSView window] then -[NSWindow setSharingType:] with NSWindowSharingNone (0) via raw objc2 messages, the peer of the Windows WDA_EXCLUDEFROMCAPTURE path. Retried per frame until the handle exists.
  • Notification: the operator-attached toast is now any(windows, macos) (notify-rust posts it via NSUserNotification).
  • Consent + IPC: the in-process egui consent dialog is unchanged (it is platform-agnostic); the IPC transport was already cfg(unix) UnixStream, so macOS reuses it. proto::default_socket_path returns /var/run/vervain-agent/assistance.sock on macOS (no /run).

Daemon (vervain-agent): the agent-gui platform gates (gui_ipc module, assistance subcommand + dispatch, IPC-server start, consent GUI path) now include macos. The #[cfg(unix)] IPC server binds/hardens the unix socket unchanged.

Dependencies

objc2 pinned to winit 0.30's 0.5 line (already in the lock) so the messaged objects are the exact runtime objects winit created; raw-window-handle reused from the same 0.6 line. Both app-feature-gated, macOS target only. No new crate for the tray (tray-icon's macOS backend is already pulled).

Validation

  • Linux: vervain-agent-gui --features app and vervain-agent --features agent-gui compile clean; workspace + gui clippy --deny warnings and fmt clean; gui unit tests pass (proto round-trips + the macOS socket-path assertion).
  • macOS FFI: the NSWindowSharingNone objc2 message path was cross-checked for aarch64-apple-darwin in isolation (objc2 0.5.2) - msg_send![view, window] and msg_send![window, setSharingType: 0] typecheck.
  • Gap (needs a Mac / the macOS runner): a full macOS compile of the agent-gui path cannot run in the Linux sandbox because notify-rust's mac-notification-sys builds Objective-C (needs the macOS SDK), same class of blocker as ring. So AC#1 (builds on macOS with the feature) and the runtime ACs (tray, consent round-trip, disconnect, capture exclusion, stripped binary size < 20 MB) must be validated on the runner / a real Mac:
    cargo build -p vervain-agent --features agent-gui --target x86_64-apple-darwin
    
    agent-gui is off by default, so this is not part of the shipped release binary; happy to add a macOS CI cargo check --features agent-gui step as a follow-up to keep AC#1 enforced.

Out of scope / follow-up noted

The desktop-tunnel key dispatch in host/tunnel.rs still routes macOS through a not(any(linux, windows)) discard arm (a VA-93 gap for the tunnel keymap, unrelated to the GUI); flagged separately, not touched here.

Acceptance criteria

  • vervain-agent-gui --features app / vervain-agent --features agent-gui build wired for macOS (compile verified on a Mac pending, per gap above)
  • Status item shows connection state + "disconnect now"
  • In-process egui consent dialog serves host/consent.rs over IPC on macOS
  • Operator-connected notification fires
  • Assistance window opts out of capture via NSWindowSharingNone
  • Stripped release binary size recorded (needs a Mac build)
  • Runtime-validated on a real macOS host (needs a Mac)

🤖 Generated with Claude Code

Implements VA-55 (VA-24 follow-up): the macOS assistance-GUI backend, bringing the tray/status-item, in-process consent dialog, operator-connected notification, and screen-capture opt-out to macOS. VA-24 shipped this for Linux and VA-56 for Windows; both deferred macOS because it cannot be built in the Linux CI sandbox (no macOS SDK). ## What landed GUI crate (`vervain-agent-gui`): - **Tray / status item**: uses `tray-icon`'s macOS NSStatusItem backend on the same in-winit-thread model as Windows (created + pumped on eframe's main thread, menu clicks via the global `MenuEvent` channel). `tray::build`/`refresh` and the app's owned-tray logic are now `any(windows, macos)`. Shows connection state and a "Disconnect now" item. - **Capture exclusion**: `apply_capture_exclusion` gains a macOS arm that reads the winit `NSView` via `raw-window-handle` and sends `-[NSView window]` then `-[NSWindow setSharingType:]` with `NSWindowSharingNone` (0) via raw `objc2` messages, the peer of the Windows `WDA_EXCLUDEFROMCAPTURE` path. Retried per frame until the handle exists. - **Notification**: the operator-attached toast is now `any(windows, macos)` (notify-rust posts it via `NSUserNotification`). - **Consent + IPC**: the in-process egui consent dialog is unchanged (it is platform-agnostic); the IPC transport was already `cfg(unix)` `UnixStream`, so macOS reuses it. `proto::default_socket_path` returns `/var/run/vervain-agent/assistance.sock` on macOS (no `/run`). Daemon (`vervain-agent`): the `agent-gui` platform gates (gui_ipc module, `assistance` subcommand + dispatch, IPC-server start, consent GUI path) now include `macos`. The `#[cfg(unix)]` IPC server binds/hardens the unix socket unchanged. ## Dependencies `objc2` pinned to winit 0.30's 0.5 line (already in the lock) so the messaged objects are the exact runtime objects winit created; `raw-window-handle` reused from the same 0.6 line. Both `app`-feature-gated, macOS target only. No new crate for the tray (tray-icon's macOS backend is already pulled). ## Validation - Linux: `vervain-agent-gui --features app` and `vervain-agent --features agent-gui` compile clean; workspace + gui `clippy --deny warnings` and `fmt` clean; gui unit tests pass (proto round-trips + the macOS socket-path assertion). - macOS FFI: the `NSWindowSharingNone` objc2 message path was cross-checked for `aarch64-apple-darwin` in isolation (objc2 0.5.2) - `msg_send![view, window]` and `msg_send![window, setSharingType: 0]` typecheck. - **Gap (needs a Mac / the macOS runner)**: a full macOS compile of the agent-gui path cannot run in the Linux sandbox because notify-rust's `mac-notification-sys` builds Objective-C (needs the macOS SDK), same class of blocker as `ring`. So AC#1 (builds on macOS with the feature) and the runtime ACs (tray, consent round-trip, disconnect, capture exclusion, stripped binary size < 20 MB) must be validated on the runner / a real Mac: ``` cargo build -p vervain-agent --features agent-gui --target x86_64-apple-darwin ``` `agent-gui` is off by default, so this is not part of the shipped release binary; happy to add a macOS CI `cargo check --features agent-gui` step as a follow-up to keep AC#1 enforced. ## Out of scope / follow-up noted The desktop-tunnel key dispatch in `host/tunnel.rs` still routes macOS through a `not(any(linux, windows))` discard arm (a VA-93 gap for the tunnel keymap, unrelated to the GUI); flagged separately, not touched here. ## Acceptance criteria - [x] `vervain-agent-gui --features app` / `vervain-agent --features agent-gui` build wired for macOS (compile verified on a Mac pending, per gap above) - [x] Status item shows connection state + "disconnect now" - [x] In-process egui consent dialog serves `host/consent.rs` over IPC on macOS - [x] Operator-connected notification fires - [x] Assistance window opts out of capture via `NSWindowSharingNone` - [ ] Stripped release binary size recorded (needs a Mac build) - [ ] Runtime-validated on a real macOS host (needs a Mac) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(gui): macOS assistance-GUI backend (status item, consent, NSWindowSharingNone) (VA-55)
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 4m26s
Create release / Create release from merged PR (pull_request) Has been skipped
4fadd7c174
VA-24 shipped the assistance GUI (tray, status window, in-process consent over IPC, operator-connected notification) for Linux, and VA-56 added Windows; both deferred macOS because it cannot be built or runtime-validated in the Linux CI sandbox. This brings the same assistance GUI to macOS.

GUI crate (vervain-agent-gui):
- Cargo.toml: a macOS target-dep table (objc2 pinned to winit 0.30's 0.5 line, plus raw-window-handle) and `dep:objc2` in the `app` feature. The tray uses tray-icon's macOS NSStatusItem backend already in the cross-platform dep.
- Tray: the NSStatusItem backend uses the same in-winit-thread model as the Windows tray (created and pumped on eframe's main thread, menu clicks via the global MenuEvent channel), so `tray::build`/`refresh` and the app's owned-tray + refresh-on-change logic are now `any(windows, macos)` rather than windows-only. Linux keeps its separate gtk-thread tray.
- Capture exclusion: `apply_capture_exclusion` gets a macOS arm that reads the winit NSView via raw-window-handle and sends `-[NSView window]` then `-[NSWindow setSharingType:]` with NSWindowSharingNone (0) via raw objc2 messages, the peer of the Windows WDA_EXCLUDEFROMCAPTURE path. Retried per frame until the window handle exists.
- Notification: the operator-attached toast is now `any(windows, macos)` (notify-rust posts it via NSUserNotification on macOS); the interactive dbus action stays Linux-only.
- IPC: unchanged transport (already `cfg(unix)` UnixStream, so macOS reuses it); proto `default_socket_path` returns `/var/run/vervain-agent/assistance.sock` on macOS (no `/run`), with the socket-path test updated.

Daemon (vervain-agent): the agent-gui platform gates (the gui_ipc module, the `assistance` subcommand + its dispatch, the gui_ipc server start, and the consent GUI path) now include macos. The daemon's `#[cfg(unix)]` IPC server already binds/hardens the unix socket, so it serves macOS unchanged.

Validation: Linux builds of the gui crate (`--features app`) and the agent (`--features agent-gui`) compile clean, workspace + gui clippy `--deny warnings` and fmt are clean, and the gui unit tests pass. The macOS NSWindowSharingNone objc2 FFI was cross-checked for aarch64-apple-darwin in isolation (objc2 0.5.2). A full macOS compile of the gui path cannot run in the Linux sandbox because notify-rust's mac-notification-sys builds Objective-C (needs the macOS SDK), so AC#1 (builds on macOS with the feature) and the runtime ACs (tray, consent round-trip, disconnect, capture exclusion, binary size) must be validated on the macOS runner / a real Mac: `cargo build -p vervain-agent --features agent-gui --target x86_64-apple-darwin`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYb1fkK32XQdwkGkStxXR9
Signed-off-by: longjacksonle <longjacksonle@gmail.com>
longjacksonle deleted branch feat/VA-55-macos-assistance-gui 2026-07-15 19:35:22 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/vervain-agent!147
No description provided.