feat(desktop): Windows system tray with live running-agent count - #8423
feat(desktop): Windows system tray with live running-agent count#8423SkyVence wants to merge 10 commits into
Conversation
…ns support Add ElectronTray Effect wrapper and DesktopTray service that creates a system tray icon (Windows hidden icons / taskbar) using the T3 icon from DesktopAssets. Tray shows running agents count, offers Show/Settings/Pause agents/Enable background service/Quit actions, and syncs tooltip. Close/minimize to tray gated by new DesktopAppSettings fields closeToTray/minimizeToTray (persisted via electron-store) and only on win32 to avoid changing darwin behavior. DesktopWindow hides on close/minimize when tray enabled instead of destroying; DesktopLifecycle window-all-closed is suppressed when closeToTray is active so the backend pool stays alive. Wired via main.ts layers and registered after app.whenReady. Adds unit tests for tray menu building and fixes pre-existing typecheck issues in orchestration and DesktopClerk. Background service keeps agents alive after UI dismissed; Pause agents toggle provides the requested disable button rather than a header control. Tested with vp exec vitest for DesktopAppSettings, DesktopLifecycle, DesktopWindow, DesktopTray.
…ttings toggle, sync icons
- Fix blank Show by using desktopWindow.activate instead of
revealOrCreateMain; fixes stuck background-color window on first
tray Show after hide.
- Fix Settings via desktopWindow.dispatchMenuAction('open-settings')
(MENU_ACTION_CHANNEL) instead of raw 't3-menu-action' send.
- Simplify tray menu: remove Pause agents / Enable background toggles
from tray per feedback; keep only header count + Show/Settings/Quit.
Background controls belong in Settings.
- Add desktop tray settings to UI: new IPC tray.ts
(GET_TRAY_SETTINGS, SET_CLOSE/MINIMIZE) + preload + contracts
DesktopBridge types, and DesktopTraySettings section in
GeneralSettingsPanel (Desktop group) with switches for
closeToTray/minimizeToTray (persisted via DesktopAppSettings).
Positions considered: General→Desktop (chosen, near confirmQuit),
Appearance→Window, System→Desktop.
- Fix icon sync nightly/stable: DesktopTray now checks
settings.updateChannel and prefers assets/nightly/nightly-windows.ico
for unpacked dev when nightly, otherwise uses DesktopAssets
iconPaths (packaged builds already bundle correct per-channel icon
via electron-builder resources).
- Fix tray lifetime: remove Effect.scoped that destroyed tray right
after register (now forkScoped only).
- Keep T3_TRAY_TEST_BYPASS gate for parallel nightly testing.
- Add IPC SET_TRAY_RUNNING_COUNT (tray.ts, channels, preload, DesktopIpcHandlers, contracts DesktopBridge) - Add useTrayRunningCountSync hook in web (AppRoot) that counts threads where session.status is running/starting via appAtomRegistry + environmentThreadDetails.detailAtom and pushes to main via window.desktopBridge.setTrayRunningCount - Tray header/tooltip now shows live N agents running instead of 0
The tray count was computed from per-thread detail atoms that are only mounted for the open thread, read imperatively with no subscription, so it stayed at 0 and the tray always said no agents were running. Derive the count from thread shell snapshots instead, which carry every thread's session status, and subscribe reactively. Scope it to this machine only: the primary backend plus desktop-local secondaries such as WSL. Background liveness (subagents outliving the turn) is a known limitation and is not counted.
The tray toggles configure whether the desktop-hosted backend keeps running after the window closes, so they belong with the other backend controls in Connections (under the WSL backend rows) rather than a lone Desktop section on the General page.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
| wslBackendEnabled: false, | ||
| wslDistro: null, | ||
| wslOnly: false, | ||
| closeToTray: true, |
There was a problem hiding this comment.
🟠 High settings/DesktopAppSettings.ts:93
On Linux, closing the last window leaves the process and backend running with no restorable window. closeToTray: true suppresses app.quit, but the close-to-tray handler only hides windows on win32; default this setting to true only on Windows.
| closeToTray: true, | |
| closeToTray: process.platform === "win32", |
Also found in 1 other location(s)
apps/desktop/src/app/DesktopLifecycle.ts:242
window-all-closedsuppressesapp.quitfor every non-macOS platform whencloseToTrayis true, but the window close handler only hides instead of destroys onwin32. Since the default settings setcloseToTray: truewithout a platform override, closing the last Linux window destroys it and then leaves the Linux process/backend alive (and no normal window) instead of exiting.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/settings/DesktopAppSettings.ts around line 93:
On Linux, closing the last window leaves the process and backend running with no restorable window. `closeToTray: true` suppresses `app.quit`, but the close-to-tray handler only hides windows on `win32`; default this setting to `true` only on Windows.
Also found in 1 other location(s):
- apps/desktop/src/app/DesktopLifecycle.ts:242 -- `window-all-closed` suppresses `app.quit` for every non-macOS platform when `closeToTray` is true, but the window close handler only hides instead of destroys on `win32`. Since the default settings set `closeToTray: true` without a platform override, closing the last Linux window destroys it and then leaves the Linux process/backend alive (and no normal window) instead of exiting.
There was a problem hiding this comment.
Fixed in c0734e9 by gating the behavior rather than the default: the window-all-closed suppression now requires win32 (with lifecycle tests for both paths), so the flag is inert off Windows everywhere it is read (DesktopWindow hide-on-close, DesktopLifecycle, and the settings UI). The default itself stays platform-independent because this repo's lint forbids process.platform outside the HostProcessPlatform service, which is not available at this module's top level.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
| ipcRenderer.invoke(IpcChannels.SET_WSL_BACKEND_ENABLED_CHANNEL, enabled), | ||
| setWslDistro: (distro) => ipcRenderer.invoke(IpcChannels.SET_WSL_DISTRO_CHANNEL, distro), | ||
| setWslOnly: (enabled) => ipcRenderer.invoke(IpcChannels.SET_WSL_ONLY_CHANNEL, enabled), | ||
| getTraySettings: () => ipcRenderer.invoke(IpcChannels.GET_TRAY_SETTINGS_CHANNEL), |
There was a problem hiding this comment.
🟡 Medium src/preload.ts:103
getTraySettings is exposed on macOS and Linux, so DesktopTrayRows displays Windows-only close/minimize-to-tray toggles that can be persisted but have no effect because the window handlers only honor them on environment.platform === "win32". Gate this bridge method (and the related tray setters) by platform, or make the UI visibility check explicitly require Windows.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/desktop/src/preload.ts around line 103:
`getTraySettings` is exposed on macOS and Linux, so `DesktopTrayRows` displays Windows-only close/minimize-to-tray toggles that can be persisted but have no effect because the window handlers only honor them on `environment.platform === "win32"`. Gate this bridge method (and the related tray setters) by platform, or make the UI visibility check explicitly require Windows.
There was a problem hiding this comment.
Fixed in c0734e9 via the UI-gate option: DesktopTrayRows now also requires isWindowsPlatform(navigator.platform), the tray count sync hook is a no-op off Windows, and the tray itself only registers on win32. The preload stays platform-independent because the lint bans process.platform there.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
There was a problem hiding this comment.
Effect service conventions review of the tray feature. Five findings, mostly around the new DesktopTray service's error model and runtime boundary, plus two collateral changes that weaken existing code.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Reviewed the in-scope web UI changes (AppRoot.tsx, ConnectionsSettings.tsx, hooks/useTrayRunningCountSync.*). The new rows correctly reuse SettingsRow + the Switch primitive with aria-labels, and the tray count hook stays local-environment scoped. Three consistency points on the new settings rows and AppRoot.
Posted via Macroscope — UI Consistency
Address PR review findings: - Suppress window-all-closed quit only on win32; elsewhere the window handlers destroy on close, so suppressing stranded a windowless app. Covered by new lifecycle tests for both paths. - Register the tray only on win32 and hide the tray settings rows and running-count sync off Windows, where the toggles had no effect. - Run tray Electron callbacks with the captured fiber context so logging and tracing stay wired to the app runtime. - Derive DesktopTrayError's message from the class instead of a singleton reason field; drop the (error as any).message casts. - Revert the single-instance test bypass and the contracts schema cast that leaked in from manual testing. - Keep AppRoot's doc comment attached to AppRoot. - Render tray settings rows only after the persisted state loads, disable them while a write is in flight, and surface failures.
There was a problem hiding this comment.
Effect service conventions review of the tray changes. The earlier findings on the tray error's singleton reason, the bare Effect.runPromise in the tray callbacks, the win32 gating of the window-all-closed suppression (now covered by tests), and the unrelated DesktopClerk/contracts edits all look resolved. Three remaining items below, all in apps/desktop.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Effect service conventions review of the tray changes. The earlier findings on the tray error's singleton reason, the bare Effect.runPromise in the tray callbacks, the win32 gating of the window-all-closed suppression (now covered by tests), and the unrelated DesktopClerk/contracts edits all look resolved. Three remaining items below, all in apps/desktop.
Posted via Macroscope — Effect Service Conventions
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR adds a substantial Windows tray workflow with native Electron integration, persistent settings, IPC, live renderer synchronization, and changed close/minimize/shutdown behavior. The cross-platform lifecycle surface and unresolved platform-specific findings require human verification. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
There was a problem hiding this comment.
One finding on the new Windows tray settings rows: the write-failure message is only wired into the first row, so a failed "Minimize to tray" write reports under the neighbouring setting.
Posted via Macroscope — UI Consistency
What Changed
Adds a Windows system tray for the desktop app:
Why
Closing the desktop window on Windows killed all running agents — there was no way to keep long-running work alive in the background. The tray keeps the backend running and makes "is anything still working?" visible at a glance without reopening the window.
UI Changes
System tray (new) — the header shows the live running-agent count; before this fix it always read "No agents running":
Settings placement — tray toggles under Connections → This environment, below the WSL backend row:
Checklist
Built with Claude Fable 5 via Zed agent.
Note
Add Windows system tray with live running-agent count and close-to-tray settings
DesktopTrayandElectronTrayservices to manage the system tray icon, context menu, and tooltip on Windows.closeToTrayandminimizeToTrayboolean settings toDesktopSettingsand persists them via IPC.useTrayRunningCountSynchook in the web app to push the local running agent count to the tray.closeToTrayorminimizeToTrayare enabled (defaultcloseToTrayistrue).DesktopLifecyclesuppressesapp.quitonwindow-all-closedunder these conditions.Macroscope summarized 41d4231.