Summary
The Windows desktop build installs and the daemon launches, but the app is not actually usable on Windows because its core runtime dependency, zellij, is not bundled and is not present on a typical Windows machine. This is the architectural reason the Windows build "doesn't work" while a self-contained Electron app (no external runtime deps) would.
Root cause
ao-agents is an Electron shell around a Go daemon that orchestrates zellij sessions. zellij is the only runtime adapter (backend/internal/adapters/runtime/ contains just zellij/), and it is resolved at runtime, not bundled:
frontend/forge.config.ts extraResource is only ["daemon", "assets/icon.png"], so zellij.exe is never shipped.
- The daemon resolves zellij via
exec.LookPath("zellij.exe") plus %LOCALAPPDATA%\Programs\zellij\zellij.exe (backend/internal/adapters/runtime/zellij/zellij.go:166-193).
On macOS this is invisible: zellij is a brew install away, and the login-shell PATH recovery (frontend/src/shared/shell-env.ts) finds it. That recovery is intentionally a no-op on Windows (frontend/src/main.ts:288; daemonEnv() returns plain process.env at frontend/src/main.ts:302). So on Windows the daemon launches fine, but the moment a session is created it shells out to a zellij that isn't there, and session creation fails.
Notes:
- The Windows plumbing itself exists and looks correct:
ao.exe is built natively on a windows-latest runner, there is pty_windows.go / process_windows.go, and a PowerShell-based background launch. The wall is the unbundled external dependency, not the packaging tool.
- zellij's own Windows support is immature, so even a user-installed zellij is not at parity with Unix.
Symptoms
- Installer completes and the app launches, but no agent session can be created on Windows ("installs but won't run").
- Failure surfaces as a generic error rather than "zellij not found", so it is hard to diagnose.
Secondary packaging gaps (lower priority)
- Installer: move off Squirrel to NSIS. ao-agents currently uses
@electron-forge/maker-squirrel. Squirrel.Windows is a poor fit for this app: per-user install only, no custom install directory, no proper add/remove-programs UX, fragile updates, and it really only behaves when code-signed. Recordly (which works on Windows) packages with electron-builder + NSIS, which gives a real installer: per-machine or per-user install, custom install dir, uninstaller, and clean update semantics. We should bundle via NSIS the same way (either migrate the desktop packaging to electron-builder, or replace the Squirrel maker with an NSIS maker) and drop Squirrel.
- Unsigned Windows installer.
.github/workflows/frontend-release.yml wires only macOS signing secrets, so the Windows installer ships unsigned and triggers SmartScreen / AV on end-user machines. Add a Windows code-signing cert/hook (this also makes NSIS installs/updates behave).
Proposed fix
Highest leverage, mirrors the self-contained approach:
- Bundle
zellij.exe as an extraResource and make the resolver prefer the bundled path first.
- If not bundling, at minimum detect zellij absence at session-create and surface an actionable error ("zellij not found, install it").
- Switch Windows packaging to NSIS (drop Squirrel), matching recordly's working setup.
- Add a Windows code-signing hook to the release workflow + packaging config.
- Longer term: a non-zellij runtime adapter for real Windows parity (large lift).
Acceptance criteria
- A freshly installed Windows build (NSIS installer) can create and attach to an agent session without the user manually installing zellij.
Summary
The Windows desktop build installs and the daemon launches, but the app is not actually usable on Windows because its core runtime dependency,
zellij, is not bundled and is not present on a typical Windows machine. This is the architectural reason the Windows build "doesn't work" while a self-contained Electron app (no external runtime deps) would.Root cause
ao-agents is an Electron shell around a Go daemon that orchestrates
zellijsessions.zellijis the only runtime adapter (backend/internal/adapters/runtime/contains justzellij/), and it is resolved at runtime, not bundled:frontend/forge.config.tsextraResourceis only["daemon", "assets/icon.png"], sozellij.exeis never shipped.exec.LookPath("zellij.exe")plus%LOCALAPPDATA%\Programs\zellij\zellij.exe(backend/internal/adapters/runtime/zellij/zellij.go:166-193).On macOS this is invisible: zellij is a
brew installaway, and the login-shell PATH recovery (frontend/src/shared/shell-env.ts) finds it. That recovery is intentionally a no-op on Windows (frontend/src/main.ts:288;daemonEnv()returns plainprocess.envatfrontend/src/main.ts:302). So on Windows the daemon launches fine, but the moment a session is created it shells out to azellijthat isn't there, and session creation fails.Notes:
ao.exeis built natively on awindows-latestrunner, there ispty_windows.go/process_windows.go, and a PowerShell-based background launch. The wall is the unbundled external dependency, not the packaging tool.Symptoms
Secondary packaging gaps (lower priority)
@electron-forge/maker-squirrel. Squirrel.Windows is a poor fit for this app: per-user install only, no custom install directory, no proper add/remove-programs UX, fragile updates, and it really only behaves when code-signed. Recordly (which works on Windows) packages with electron-builder + NSIS, which gives a real installer: per-machine or per-user install, custom install dir, uninstaller, and clean update semantics. We should bundle via NSIS the same way (either migrate the desktop packaging to electron-builder, or replace the Squirrel maker with an NSIS maker) and drop Squirrel..github/workflows/frontend-release.ymlwires only macOS signing secrets, so the Windows installer ships unsigned and triggers SmartScreen / AV on end-user machines. Add a Windows code-signing cert/hook (this also makes NSIS installs/updates behave).Proposed fix
Highest leverage, mirrors the self-contained approach:
zellij.exeas anextraResourceand make the resolver prefer the bundled path first.Acceptance criteria