Skip to content

feat(desktop): Windows support - #9975

Open
waleedlatif1 wants to merge 44 commits into
stagingfrom
feat/desktop-windows-support
Open

waleedlatif1 wants to merge 44 commits into
stagingfrom
feat/desktop-windows-support

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds Windows (x64, NSIS) support to the desktop app. macOS behaviour is unchanged. Replaces #9499 with one clean branch rebased on current staging.

Shell

  • Single instance: a relaunch focuses the existing window. Each channel has its own AppUserModelID.
  • Windows File/Edit/View/Help menu (auto-hidden, Alt shows it). Alt+Left/Right go Back/Forward, and Ctrl+Tab cycles Browser tabs.
  • Native frame with a caption that follows Sim's theme. Full-colour tray icon.
  • Foreground focus after the browser sign-in hand-off. Taskbar flash when a download finishes. Ctrl shortcut labels in the web app (formatShortcut).

Terminal on Windows

  • Supports PowerShell 7, Windows PowerShell 5.1 and Git Bash.
  • Runs on ConPTY through the bundled conpty.dll, with OSC 633 shell integration for each shell.
  • Stop kills the command's whole process tree: descendants are found by CreationDate, then taskkill /T /F.
  • The update restart check counts a busy Windows terminal as running work.
  • The shell-integration parser now returns output and markers in the order the shell wrote them. Before, when a command's start marker, its output and its finish marker arrived in one pty chunk, the output was dropped. This fixes that on every platform.

Native file tools on Windows

  • A Node-fs backend (win32-native-files.ts) sits behind the existing openNativeFile seam.
  • Paths are validated as text before any filesystem call. This refuses UNC paths, \\?\ and \\.\ paths, drive-relative and rooted paths, reserved device names, alternate data streams, and trailing dots or spaces. It closes an SMB/NTLM leak through realpath that could happen before consent.

Feature gating

  • The bridge exposes platform.
  • One feature table, src/shared/platform-features.ts, drives the preload surface, IPC registration (check:desktop-ipc enforces that the two match), executor registration and the background runner.
  • Still off on Windows: computer use, Help search, terminal themes, browser import (including the sign-in hand-off channels and the Import Passwords menu item), copy-password and tmux.

Packaging and updates

  • NSIS one-click, per-user installer with a separate install folder per channel and a multi-size .ico. The .node files are signed.
  • On Windows, electron-updater's NsisUpdater drives the ready state and installs silently.
  • New routes: /api/desktop/update/latest.yml and /api/desktop/update/download?platform=windows. A release without a Windows manifest means "no update", not a 502.
  • Mothership receives desktop.platform and terminals[].shell. That is the companion Mothership PR; this PR includes the synced protocol.

CI

  • New build-windows job in the new release layout: shared source and publish jobs, no caches.
  • Signs with Azure Artifact Signing over OIDC, then checks the Authenticode signature and publisherName, then runs the packaged smoke test. The job is gated on its own signing probe and never blocks the macOS release.
  • New package-smoke-windows leg in desktop-e2e.yml.

Behaviour changes on macOS (deliberate)

  • Terminal kill (SIGTERM/SIGKILL) now stops only the running command. node-pty's kill(signal) signalled the shell itself, so on macOS a SIGTERM did nothing (interactive shells ignore it) and a SIGKILL closed the user's tab. It now signals the tty's foreground process group on macOS and Linux, and on Windows it ends the command's process tree. An idle shell is never signalled, and the result reports signalled.
  • Stop captures the foreground process group before Ctrl-C and escalates only against that group. A prompt helper such as git status from vcs_info, which takes the foreground after the command dies, is never signalled.
  • bash integration joins its PROMPT_COMMAND parts with newlines, so a user value ending in ; (e.g. history -a; $PROMPT_COMMAND) no longer causes a syntax error. The user's own PROMPT_COMMAND no longer reads as a running command.
  • Shell-integration output is applied in the order the shell wrote it, so a command's output is kept even when its start and finish markers arrive in the same chunk.

Known limits on Windows

  • Network locations (UNC paths, mapped drives, links to shares) are refused before any consent prompt.
  • Native file opens are weaker than macOS against one race. If a local process swaps an intermediate folder for a junction between the check and the open, the junction is followed; the post-open identity check then refuses the handle. macOS closes this race with openat(O_NOFOLLOW).
  • The signed NSIS release path has not run end to end. It needs the Azure signing setup. The first run should be a workflow_dispatch with platform=windows, sign=true, publish=false.
  • User-facing docs don't advertise Windows yet. Commit e1a1363204 holds those claims back; revert it once a signed Windows release ships.

Rollout

No Windows artifacts are published until the Azure signing secrets and variables exist (see the README table). Until then the probe skips the job with a warning, and macOS releases are unaffected.

Test plan

  • Full desktop Vitest suite on a Windows Server 2025 box at 4aa9612784: 91/91 files, 1399 passed, 15 skipped (all POSIX-only).
  • windows-shells.test.ts (pwsh 7, PS 5.1, Git Bash): 17 passed. This covers Stop sparing an earlier background job's workers, a stale $LASTEXITCODE, and kill keeping the tab. win32-native-files and local-realpath: 23/23.
  • On the box: packaged build, NSIS install, launch, second-launch focus, uninstall.
  • The regression test for the marker-ordering fix fails on the old code with output: "".
  • Locally after the rebase: desktop type-check, driver.test.ts, check:desktop-ipc, check:test-patterns, and biome on the changed files.
  • CI: the full gate plus the macOS and Windows desktop legs.

Adds a Windows x64 build of the desktop app alongside macOS.

- Shell: single-instance focus, AppUserModelID per channel, a Windows
  File/Edit/View/Help menu, auto-hidden menu bar, native caption that follows
  Sim's theme, a full-colour tray that keeps the app resident.
- Terminal: PowerShell 7, Windows PowerShell 5.1 and Git Bash through the
  bundled ConPTY with OSC 633 shell integration; Stop targets only the
  stopped command's process tree; the update-restart check counts a busy
  Windows terminal.
- Native file tools: a Node-fs backend behind the same seam as the POSIX
  addon, with lexical path validation, canonical-path and identity checks.
- One per-platform feature table gates computer use, Help search, terminal
  themes, browser import and password copy; IPC channel specs declare their
  feature.
- Packaging and updates: NSIS per-user installer, per-channel install folder
  and icons, NSIS updater flow, latest.yml feed and a platform-aware download
  route; Windows release job signed with Azure Artifact Signing, never
  published unsigned, never blocking macOS.
- The web app reads the bridge's platform and sends it, with each terminal's
  shell, to Mothership.
The preload exposes continue-sign-in, bring-sign-in-back, cancel-sign-in and
pending-sign-in only under features.browserImport, but main registered them
everywhere. Gate them on browserImport, and teach check:desktop-ipc to fail
whenever a channel's platformFeature disagrees with the preload's feature
spread, so the two sides cannot drift again.
…mpt cases

The Windows install test queued a dialog response the no-prompt-without-work
path never consumes, leaving it for whichever test asks next. Replace it with
the three cases that reach quitAndInstall(true, true): nothing running, running
work confirmed, and a mandatory install, and drop the same stale response from
the failed-installer test.
The resource menus and the terminal paste toast hard-coded Command glyphs.
Add formatShortcut beside isMacPlatform and use it for Find, zoom, Clear, New
Tab, Close Terminal and paste, which the Windows shell binds to the same keys
with Control.
The native toolbar menu offered Import Passwords on every platform, but the
import bridge ships only where desktopFeatures enables browserImport.
List the sign-in hand-off and password copy among what Windows withholds,
describe which processes Stop ends without process groups, and say when an
update restart asks before installing.
Chrome binds Ctrl+Tab and Ctrl+Shift+Tab to tab switching on every platform,
macOS included, ahead of the page. Resolve them in browserShortcutForInput so a
page that handles Tab no longer swallows the chord; agent keystrokes still
cannot trigger it.
… mock

The hand-rolled window stub grew one method per Windows failure: bringing
the window forward on win32 also pins and unpins it on top, which the stub
lacked. The shared BrowserWindow mock already carries every window method,
so the test overrides only the page URL and redeem result, and runs on both
darwin and win32.
The shell-integration parser returned a chunk's text and markers apart, and
the session applied every marker before any text. When one chunk carried a
command's start marker, its output, and its finish marker together, the
finish marker resolved the run first and the output that came before it was
then dropped, so the agent saw a completed command with exit 0 and no
output. Under load node-pty on Windows coalesces Git Bash's separate writes
into one read, which is how 'sim-ok-10' went missing.

The parser now returns the stream as ordered segments and the session
applies text and markers in the order the shell wrote them, which also
stops the echoed command line before the start marker leaking into the
captured output.
…service tests

The sign-out test polled for the sleep blocker with vi.waitFor's 1 s
deadline, but the path there is three sequential atomic writes to userData
(install id, then the journal's claiming and claimed records). On Windows
Defender scans each new file, measured at up to ~220 ms for a single write
on an idle box, so under the parallel suite the deadline lapsed with the
call not yet held. The tests now await the registration request and the
busy change themselves, bounded only by the test timeout.
Nothing imports it; callers read it through the parse return type.
@waleedlatif1
waleedlatif1 requested a review from a team as a code owner November 13, 2026 03:29
@vercel

vercel Bot commented Nov 13, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Nov 13, 2026 8:29am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[High impact] The PR appears safe to merge, with one non-blocking Windows theme mismatch.

Findings

  1. P2 Light pages get dark captions ▶

Summary

Adds Windows desktop support while keeping platform-specific features behind a shared feature table.

  • Sim opens in a native Windows window and focuses it when relaunched.
  • Windows terminals run supported shells and keep command output in order.
  • Windows local-file tools use checked paths and a Windows filesystem backend.
  • The desktop bridge and shared messages identify the platform and available features.
  • Windows desktops receive updates through a platform-specific release feed.
  • Windows builds use a signed per-user NSIS installer and a separate release job.
  • Build scripts, shared helpers, and desktop docs adapt to the Windows release.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  Platform[Desktop platform] --> Features[Shared feature table]
  Features --> Preload[Exposed bridge methods]
  Features --> IPC[Registered IPC handlers]
  Platform --> Terminal[Shell and terminal backend]
  Platform --> Files[Native file backend]
  Platform --> Updates[Platform update manifest]
  Updates --> Feed[Deployment release feed]
  Feed --> GitHub[Signed GitHub release assets]
Loading

Reviews (2) · Last reviewed commit: "fix(desktop): tell a stale PowerShell ex..." · Reviewed by Greptile

Comment thread apps/desktop/scripts/build.ts Resolved

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 136 files

Turn on auto-fix | Re-trigger cubic

Comment thread scripts/check-desktop-ipc-contract.ts Resolved
Comment thread packages/terminal-protocol/src/index.ts Resolved
Comment thread apps/desktop/src/main/downloads.ts Resolved
Comment thread apps/desktop/src/main/terminal/index.ts Resolved
Comment thread apps/desktop/src/preload/shell-theme.ts Resolved
…eep a failed ps probe unknown

Only the shell and the real shell behind a launcher (its first child, created before the startup
marker) are walked, so a worker an earlier background job starts mid-command is never taken for
the command's and force-killed. On POSIX an empty ps result reads as idle only once the shell pid
is confirmed gone.
…out clearing it

Clearing $LASTEXITCODE as a line was accepted hid it from that line itself. The code now counts
as the line's when it changed, or when it is unchanged and the line added no error record.
@waleedlatif1

Copy link
Copy Markdown
Collaborator Author

@greptile

Comment thread apps/desktop/src/preload/shell-theme.ts Resolved
…p preference only

A forced page theme (/login, /chat) or the landing pages' own storage key
flipped nativeTheme.themeSource for every window, flashing the workspace
window light. Report only the app's sim-theme preference again and drop the
theme-provider script attributes that carried the per-page values.
…up before Ctrl-C and escalate only against it
…follows them

realpath follows every symlink in a path, so a link planted in a repository
that targets \\host\share made Windows send the user's NTLM hash before the
consent dialog. localRealpath walks the path with lstat first and refuses a
link to a network path, and refuses a path that resolves to a share (a mapped
drive), which the native backend could never open. Authorization, the VFS
grant and URI paths, and the Windows backend all resolve through it. Covers the
Windows backend's in-grant junction, post-open canonical and root checks with
tests, and states the remaining pathname-open race precisely.
… compare import revisions as bigints

Also describes the credential vault's Windows protection accurately (DPAPI
plus the profile ACL, not file modes).
…s release ships

The Windows download links and install steps 404 until Authenticode signing
exists and a signed release is published. Restores the macOS-only wording in
the desktop and self-hosting docs and the sim-setup README; the developer
notes in apps/desktop/README.md stay.
A deployment older than Windows support ignores ?platform=windows and
redirects to the macOS DMG, which the CLI reported as success. The resolved
name must now carry the platform's installer extension.
…ws CI jobs actually guarantee

forceCodeSigning in electron-builder 26 fails only when no signing
configuration exists, DLLs are not Authenticode-signed by signExts, and the
prerelease publish waits for the Windows build to finish even though a
Windows failure does not block it.
…name Windows commands in their own shell's syntax before a restart, and tidy session docs

This branch was successfully deployed

1 active deployment
Preview — d01ebf86 Deployed Nov 13, 2026 by vercel[bot]
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