Skip to content

feat: floating always-on-top agent status overlay#514

Merged
Juliusolsson05 merged 2 commits into
mainfrom
feat/agent-status-overlay
Jul 9, 2026
Merged

feat: floating always-on-top agent status overlay#514
Juliusolsson05 merged 2 commits into
mainfrom
feat/agent-status-overlay

Conversation

@Juliusolsson05

Copy link
Copy Markdown
Owner

What

A floating agent-status overlay: a small frameless, always-on-top window that stays visible over other apps (Chrome, editors, fullscreen Spaces) and shows what your agents are doing at a glance.

  • Collapsed pill (default): activity-dot counts — amber pulsing = waiting on you (permission/trust prompt), green = actively working, gray = idle, red = exited. Shows no agents when empty.
  • Expanded list (click the pill): one row per agent with activity dot, title, ★ for pinned, a project chip when agents span multiple projects, and the most-actionable right-side text (attention label → provider verb like running Bash → activity word).
  • Click a row → raises Agent Code and focuses that agent's pane (same focus path as the Agent Activity modal).
  • Toggled via the command palette: Floating Agent Status. Enabled state, window position, and pill/list mode persist across restarts (~/.config/<app>/agent-overlay.json).
  • Auto-hides while Agent Code is focused (the app already shows richer status everywhere); reappears when focus leaves the app. Never steals key focus (focusable: false, showInactive).

How

The overlay deliberately does not consume session IPC. The main renderer — which already owns all status derivation (dispatchActivity, dispatchAttentionLabelFromConditions, sessionTitle, buildVisibleDispatchRows) — publishes a precomputed snapshot (throttled 200ms, deduped on content) over new agent-overlay:* channels; main caches and forwards it to the overlay window. Consequences:

  • sendToMainWindow / the session forwarder / mainWindow.ts's single-window assumption are all untouched — no fan-out refactor.
  • The overlay renderer is a dumb display: its whole bundle is 8.5 kB (second Vite renderer entry, overlay.html), sharing styles.css, the preload bundle, and the theme via applyTheme on an opaque settings pass-through.
  • dispatchActivity + palette helpers moved from DispatchAgentList.tsx into workspace/dispatch/dispatchActivity.ts (re-exported for existing importers) so all three surfaces — Dispatch list, mini-list, overlay — share one activity vocabulary.

Verification

  • tsc -b tsconfig.node.json and tsc -b tsconfig.web.json both clean
  • electron-vite build passes; out/renderer/overlay.html + chunks emitted
  • vitest run: 481 tests pass. One failing suite (agent-voice-dictation submodule hotkeyBinding.test.ts, "No test suite found" — it's a node:test file) fails identically on main at the same submodule SHA; pre-existing and unrelated.
  • Not yet runtime-smoked in a live npm run dev session (didn't want to fight the state process lock with a running instance); worth a quick manual pass: toggle the command, drag the pill, expand, click a row from Chrome.

🤖 Generated with Claude Code

Juliusolsson05 and others added 2 commits July 9, 2026 18:09
A second frameless BrowserWindow that floats above other apps (Chrome,
editors) showing live agent status: a compact pill of activity-dot
counts that expands on click into per-agent rows. Clicking a row raises
Agent Code and focuses that agent's pane. Toggled from the command
palette ("Floating Agent Status"); enabled state, position, and
pill/list mode persist across restarts; auto-hides while Agent Code
itself is focused.

Architecture: the main renderer (which already owns all status
derivation) publishes a precomputed snapshot over agent-overlay:* IPC;
main caches and forwards it to the overlay window. The overlay stays a
dumb display, so no session IPC fan-out was needed and mainWindow.ts's
single-window assumption is untouched. dispatchActivity + palette
helpers moved out of DispatchAgentList.tsx into their own module so the
overlay bundle (8.5 kB) shares the activity vocabulary without
dragging in the Dispatch component graph.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d, attention gate

Fixes all findings from the two-agent review:

1. macOS activate/window-all-closed no longer confused by the second
   window: hasMainWindow() drives activation, and the overlay is
   destroyed with the main window (its data source) and recreated via
   syncAgentOverlayWindow on Dock re-activation.
2. Overlay now loads a dedicated least-privilege preload (overlay.mjs,
   four methods) instead of the full api bridge.
3. Navigation hardening on the overlay window (deny window.open,
   prevent will-navigate) matching the main window's stance.
4. IPC-vs-restore race serialized: toggle/get-enabled await the
   persisted-state load.
5. agent-overlay:report payload is runtime-validated and bounded (it
   is cached in main and replayed to the overlay on every load).
6. Persisted position and expand-resizes are clamped to the matching
   display work area so the overlay can never strand off-screen.
7. ready-to-show guards on enabled so a fast toggle off mid-load
   can't resurrect the window.
8. enabled/quit persistence is write-through (flush on toggle and
   before-quit); only drag positions stay debounced.
9. dispatchAttentionLabelFromConditions now applies the same
   visible:false gate as conditionRequiresAttention, so hidden
   permission prompts no longer pulse the overlay's waiting pill
   (fixed in the selector so Dispatch and the overlay agree).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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