26 KiB
Windows Platform Support
This document defines how Tutti adds Microsoft Windows support without making the product core depend on Windows implementations. It describes the current architecture and promotion boundaries; command-level Workspace App rules live in Workspace App Runtime, and release mechanics live in Desktop Release.
desktop-windows.md describes Electron product windows. It is not the source of
truth for Microsoft Windows operating-system support.
Goals And Current Status
The target is one product behavior across macOS and Windows, with operating
system differences behind capability interfaces. The current Windows target is
Windows 10/11 x64. Windows packaging remains Alpha-only and is not part of the
formal latest release path.
Current implementation and evidence:
| Area | Shared owner | Windows boundary | Status |
|---|---|---|---|
| Desktop daemon lifecycle | Electron main process | packages tuttid.exe and injects native resource paths |
Windows Alpha CI packages it |
| Workspace Apps | daemon app lifecycle, health, state, and events | AppShellAdapter invokes the packaged managed POSIX shell |
Onboarding fat package is exercised in Windows Alpha CI |
| Terminal | terminal service and shared terminal contracts | TerminalProcessFactory uses ConPTY |
focused adapter and daemon WebSocket tests run in Windows daemon-adapter and Alpha CI |
| Agent processes | provider-neutral agent/runtime services | build-tagged executable, command, process handling, and user PATH publication | focused Windows tests run in Agent adapter and Alpha CI |
| Browser | browser service contract | focused Windows executable/profile path behavior | focused Windows tests exist; full browser E2E remains a promotion gate |
| Computer use | computer service contract | Cua Driver 0.18.0 doctor/MCP boundary and owned daemon | focused Windows tests and opt-in MCP smoke exist; screenshot/input E2E remains a promotion gate |
| Files | workspace file APIs and portable Go filesystem behavior | add a narrow adapter only where Windows semantics differ | full Windows Files E2E remains a promotion gate |
| Release | desktop release policy | Certum Authenticode-signed NSIS plus a separately gated Store AppX artifact | signed Direct release automation exists; real-machine acceptance remains a promotion gate |
Passing windows-latest CI proves the build and automated paths above. It does
not by itself prove the supported Windows 10 floor, installer UX, upgrade, or
uninstall behavior on real machines.
Architecture Rules
Windows compatibility is a continuous repository requirement, not a feature that is considered only when a task explicitly mentions Windows. Every behavior change must assess its Windows impact. Changes involving paths, filesystems, temporary storage, executables, commands, shells, environments, processes, permissions, symlinks, sockets, packaging, or native dependencies are platform-sensitive by default and require Windows and POSIX reasoning and focused coverage.
Platform-neutral services depend on capabilities, not operating-system implementations:
Desktop package and composition
|
v
tuttid common core
lifecycle / API / state / DB / events
|
v
capability interfaces
/ \
Darwin implementations Windows implementations
shell / ConPTY / process / paths
Apply these rules when adding another Windows capability or another operating system:
- Keep lifecycle, API contracts, persistence, health checks, and event publication in shared owners.
- Define the smallest capability interface at the consumer boundary. The consumer must not import the native library or desktop packaging layout.
- Select an implementation in the owning composition root or a small build-tagged factory. OS checks do not belong throughout business logic.
- Keep portable behavior shared. Do not introduce
FooAdaptermerely because a module can run on multiple operating systems. - Group cohesive platform behavior. A platform does not need a separate file for every helper; split only where build constraints or genuinely different semantics require it.
- Derive paths from injected roots and standard platform APIs. Do not hardcode drive letters, user directories, installation locations, or executable search results.
- Treat every path crossing a process, RPC, JSON, environment, or provider
boundary as a host path unless the contract explicitly declares a virtual
namespace. Construct host paths with the platform path API, preserve the
required absolute-path base such as
cwd, and never send a POSIX-rooted literal such as/tmpor/sandbox-tmpto a Windows parser. - Resolve executables through the owning adapter. Account for
.exe,.cmd, and.ps1, PATHEXT and PATH behavior, spaces and non-ASCII characters in paths, and Windows command-line quoting. Do not assemble shell command strings when an argv-based process API is available. - Tests for a platform-sensitive contract must exercise the receiving parser, process, or filesystem boundary on Windows where practical. A mock that only verifies emitted strings or serialized maps is insufficient evidence of Windows compatibility.
This is dependency inversion at the native boundary, not a requirement to create parallel copies of each service.
Workspace project identity and imported rail placement
Project paths have one display form and one comparison form; callers must not compare raw strings when the path crosses a desktop/daemon boundary. The shared user-project core normalizes slash direction and trailing separators, then folds case only for Windows-shaped drive or UNC paths. POSIX paths remain case-sensitive. On a Windows host, a single-root POSIX project path is an explicit logical namespace: the Go rail store cleans it with POSIX semantics and never rebases it onto the current drive. Native drive and UNC paths keep Windows filesystem normalization. The rail store keeps the resulting canonical path for display and derives a stable section key for persistence. Project registration and deletion resolve an incoming path variant to the existing stored row before applying the table's ordinary unique-path write guard, so no second identity column is needed.
External session import can persist a session before its selected project is
registered. The import path therefore registers projects and repairs only
sessions carrying the durable imported marker in the same workspace SQLite
transaction. Startup migration workspace_agent_activity_rail_v2 replays the
same repair for historical rows; ordinary conversations are not moved. This
uses existing APIs and tables—no new wire fields or database columns are
required.
Workspace App Data Flow
Workspace Apps keep one manifest and one POSIX bootstrap.sh across platforms:
App Center install
-> validate and unpack one fat package
-> AppRunner owns lifecycle and environment
-> AppShellAdapter selects host invocation
-> bootstrap.sh reads TUTTI_PLATFORM
-> bin/windows-amd64/*.exe or bin/darwin-*/*
-> health check
-> state persistence and business events
On packaged Windows Desktop, the desktop vendors a pinned minimal POSIX shell
runtime, currently implemented with Bash, and injects its absolute path when
starting tuttid.exe. The Windows
AppShellAdapter invokes that Bash with profiles disabled and exposes its
managed command directory through PATH. Apps do not depend on Git for Windows
or WSL, and the manifest does not duplicate platform entrypoints.
Windows direct development uses the same managed runtime. The desktop predev
hook prepares apps/desktop/build/managed-posix-shell, and the Electron main
process resolves that runtime when it builds the daemon launch environment.
Therefore pnpm dev does not depend on a caller remembering a separate shell
export; the daemon still receives TUTTI_MANAGED_POSIX_SHELL before it starts.
Windows staging and E2E acceptance should continue to use the dedicated E2E
launcher because it also owns state isolation, cleanup, and readiness checks.
The package is fat because it contains every currently shipped platform
artifact. TUTTI_PLATFORM selects the artifact at runtime. Adding a future
platform key should add an artifact and build job, not another app lifecycle.
The desktop also resolves the packaged or development native tutti.exe at
composition time and passes its absolute path to tuttid. The Workspace App
runner exposes that executable as TUTTI_CLI together with the effective
TUTTID_LISTENER_INFO_PATH. Apps therefore launch the CLI without cmd.exe
or batch-file parsing. The user-facing tutti.cmd remains a terminal PATH shim;
it is not the Windows Workspace App execution contract.
Native Adapter Boundaries
Workspace App shell
AppRunner owns app lifecycle and depends on AppShellAdapter. The adapter
validates host script semantics, answers how to invoke a package script, and
returns the managed command directory that must be available. Production wiring
creates one platform adapter and injects it into App Center, AppRunner, and App
Factory; those consumers do not branch on the operating system. The reusable
packaged resource is named
managed-posix-shell; shell download, verification, and desktop resource
layout remain desktop packaging concerns. Other domains may reuse that resource
through their own narrow adapter, but must not depend on AppShellAdapter or
expand the Workspace App contract.
Terminal
The terminal service depends on TerminalProcessFactory and TerminalProcess.
The factory supplies both the default TerminalShellSpec and process creation.
Unix PTY file descriptors stay on a Unix-private capability and are not part of
the shared process contract; Windows therefore does not provide a fake FD.
Unix PTY and Windows ConPTY libraries remain private to their implementations.
Session lifecycle, resize semantics, snapshots, WebSocket transport, and exit
events remain shared daemon behavior.
Agent process execution
Agent installation and launch keep provider/product policy shared. Windows
command resolution, executable verification, npm launcher layout, and process
creation stay in the runtime command/process boundary. Managed npm installs use
the selected user executable directory (normally %USERPROFILE%\.local\bin)
as npm's Windows prefix, because Windows npm writes .cmd/.ps1 launchers
directly under the prefix. After a successful managed install/update, the
daemon's narrow Windows user-path adapter idempotently appends that directory
to HKCU\Environment\Path and broadcasts WM_SETTINGCHANGE; it never
writes the machine-wide path or changes Unix/macOS shell profiles. Do not make
provider installers assemble shell command strings to handle Windows.
Fresh installs use %USERPROFILE%\.local\bin. The resolver also scans the
older %USERPROFILE%\.local npm prefix after the current directory, so an
existing verified package is reused and updated in place instead of creating a
second copy. After verification the daemon publishes the directory that owns
the selected launcher. It does not migrate or delete the legacy package.
Managed Agent Extensions and the provisioned Claude Code runtime publish into
the same %USERPROFILE%\.local\bin contract. Their versioned executables stay
under %USERPROFILE%\.local\share\tutti\agent-runtimes; a stable per-Agent
.cmd launcher and a user-level .cmd launcher form two verified hops to the
active executable. This avoids file-symlink privilege and keeps versioned
runtime directories out of PATH. The daemon refuses to replace an existing
entry unless it carries the Tutti launcher marker and points inside the
expected managed runtime root. Successful install actions surface user-PATH
write failures, while status-time adoption repairs PATH on a best-effort basis.
Registry changes affect new processes only, so an already-open terminal must be
restarted before it can resolve a newly published command.
Portable Agent Extension manifests keep npm and pnpm launch executable names
extensionless. The Windows install-plan adapter resolves those names to the
actual .cmd launcher before verification, activation, version probing, and
launch; the structured command adapter invokes the launcher through cmd.exe.
Product and provider code must not append the suffix or assemble the shell
command itself.
Managed-runtime adoption must also release the verified source directory handle before renaming that directory. After rename, reopen the promoted directory and repeat the fingerprint/integrity check; rollback follows the same close-rename- reopen order. Unix permits rename with the old handle open, but Windows rejects it as a sharing violation.
Provider-owned account-usage helpers do not execute npm .cmd launchers or
copy JavaScript into a fake .exe. Their optional package is installed and
activated separately from the ACP runtime. The process boundary verifies the
fixed node.exe interpreter and the declared CommonJS script independently,
then feeds the verified script bytes to Node. Windows CI must exercise an
actual npm pack/install and account-usage probe in addition to native Go
executable verification; a companion failure may produce only
runtime_unavailable, never a not-installed Agent. Companion installation is a
daemon-owned reconciler with durable restart recovery and bounded retry backoff; setup
status reads never initiate its package download.
The first probe builds a content-addressed Node snapshot with context-aware
copy/hash. Later probes reuse the read-locked snapshot without copying or
hashing node.exe again; a restarted daemon verifies it once before reuse.
Extension session-home preparation keeps its source declaration portable. An
explicit source environment variable wins; otherwise the Windows adapter maps a
leading-dot top-level directory to the native user cache root
(%LOCALAPPDATA%) before the shared resolver considers a migrated literal
user-home-relative directory. Provider IDs and Windows path literals must not
leak into extension or Agent lifecycle policy.
System proxy resolution follows the same shared precedence on macOS and
Windows: session/process environment, then the operating-system static proxy,
then direct connection. The common resolver owns merging, NO_PROXY, caching,
diagnostics, and the TUTTI_DISABLE_PROXY_AUTODETECT escape hatch. Build-tagged
adapters only read native state: Darwin uses scutil --proxy; Windows reads the
current-user WinINet Internet Settings values. Windows protocol maps and
bypass entries are normalized to HTTP_PROXY, HTTPS_PROXY, and NO_PROXY.
PAC execution and SOCKS conversion remain out of scope because the daemon does
not embed a PAC/SOCKS engine. WinINet bypass entries that cannot be represented
faithfully by standard NO_PROXY syntax (for example 10.* and <local>) are
not projected; loopback, .local, exact hosts, and *.domain suffixes remain
covered.
Agent Target setup uses one Desktop-owned, version-pinned uv resource rather
than downloading uv separately for each Python agent. Release staging copies
the official compressed archive for each packaged architecture under
bin/managed-uv/<platform>/<version>/; both staging and tuttid verify the
pinned size and SHA-256. Electron injects only the resource root through
TUTTI_BUNDLED_UV_ROOT. The daemon keeps extraction, cache markers, and the
dynamic official-download fallback. Node, Python, Kimi, Hermes, and other agent
payloads remain dynamically installed, so this adds roughly one compressed uv
archive per architecture rather than a complete cross-language toolchain.
The signed macOS release is the one packaging exception. The upstream macOS
archives contain nested uv and uvx executables without Tutti's Developer ID
signature, so Apple notarization rejects the archive when it is embedded in the
app. macOS therefore uses the same pinned, checksum-verified dynamic download
fallback at first use; Windows and Linux continue to ship their bundled uv
archive.
For generic ACP providers such as OpenCode, provider resolution rewrites the adapter command once and every status, login, model-catalog, and session path uses that resolved executable. On Windows a complete isolated npm package is preferred over an earlier stale PATH shim; package metadata and containment are validated before its declared binary is accepted.
The Windows Desktop package also vendors the pinned Mutagen executable used
when file-symlink creation is unavailable. Electron resolves the packaged
resource and injects TUTTI_MUTAGEN_BIN into tuttid; Workspace Apps inherit
that same absolute path. Release staging downloads the official archive and
license texts, verifies their pinned SHA-256 digests, and packages only the
Windows amd64 executable and notices. Runtime execution therefore has no
Mutagen download dependency, while an explicit TUTTI_MUTAGEN_BIN remains an
operator override. If the packaged or configured executable is unavailable,
runtime preparation uses a guarded per-run auth copy and copies a valid
refreshed credential back only when the stable source has not changed
concurrently.
Browser and Files
Browser and Files do not require broad platform interfaces by default. Keep API, state, validation, and orchestration shared. Introduce or extend a narrow adapter only after a real difference is identified, such as browser executable discovery, profile location, process termination, path containment, atomic move, or timestamp behavior. Prefer standard Go and Electron APIs when they already provide equivalent semantics.
Computer use
Computer use keeps the Tutti MCP/tool policy and session lifecycle shared. On
Windows, the narrow adapter invokes the installed Cua Driver 0.18.0 binary,
uses its read-only doctor --json probe for readiness, and lazily owns a local
serve process while the computer service is active. The desktop resolves the
official per-user install locations and passes an explicit entry path when it
starts a new tuttid; the daemon also resolves those locations so an install
performed while the current desktop process is running is visible without a
restart. The native UI Automation, capture, and input implementation remains
inside Cua Driver rather than becoming a Tutti platform library.
Daemon startup readiness is bounded but tolerant of transient status failures.
If the service still owns a live serve process, a later readiness check polls
again instead of failing immediately. A process exit before readiness and a
readiness timeout remain distinct, sanitized diagnostic errors.
The desktop does not currently vendor cua-driver.exe into the Windows
package. Users or deployment tooling must install the pinned driver (or set
TUTTI_COMPUTER_MCP_ENTRY_PATH); packaging a signed helper is a separate
promotion decision. Updating the driver version requires rerunning the doctor,
MCP contract, and real screenshot/input gates before changing the pin.
Packaging And Release Boundary
The Alpha workflow is intentionally separate from the formal desktop release:
.github/workflows/windows-agent-adapters.ymland.github/workflows/windows-daemon-adapters.ymlprovide focused pull-request coverage and maintain reusable caches on matchingmainpushes; they do not produce desktop packages;.github/workflows/windows-desktop-alpha.ymlalways tests Windows x64 and builds the Desktop bundles for pull requests;- pull requests build, smoke-test, and upload an unsigned NSIS installer only when Desktop packaging inputs change; manual runs always produce the installer;
- the workflow does not publish a GitHub Release or mutate stable/prerelease update metadata;
.github/workflows/desktop-release.ymlsigns every non-dry-run Windows package with Certum SimplySign, independently verifies the installer, embedded uninstaller, and internal EXEs on Windows, then stages Windows beside macOS assets.
Windows may enter a formal Beta/RC or stable channel only after all applicable gates below pass:
- Windows 10 and Windows 11 x64 install, launch, upgrade, rollback, and uninstall verification on real or cloud machines;
- Onboarding, Browser, Files, Terminal, and AgentGUI end-to-end verification;
- successful Authenticode smoke and formal-package runs using the organization- owned Certum SimplySign configuration;
- managed Bash license, notice, and corresponding-source distribution review;
- crash/log collection and release-asset traceability;
- formal release staging, updater metadata, mirror, notification, and rollback tests.
Automated signing is one release boundary, not proof of the remaining real-machine gates. Public channel enablement must continue to follow the release policy and its protected stable-candidate approval rather than treating a successful signing job as complete Windows support evidence.
The stable Microsoft Store path is a separate, opt-in release surface:
pnpm --filter @tutti-os/desktop build:win:storereuses the Windows payload and emits one x64 AppX submission package;.github/workflows/desktop-store-submit.ymlaccepts only a plain stable tag, verifies that the tag and commit match, validates the package identity, publisher, four-part version, architecture, and SHA-256, then optionally submits it with the official Microsoft Store Developer CLI;.github/workflows/desktop-release.ymlcalls that workflow only whenTUTTI_WINDOWS_STORE_SUBMISSION_ENABLED=true, the publication mode ispublish, and Direct promotion has succeeded; the Store job is downstream from, never a dependency of, Direct staging or promotion;- the AppX manifest registers the
tuttilogin callback protocol and the Store workflow verifies that registration together withrunFullTrust; - RC and beta tags remain Direct-only. A separate Store beta product or package flight is not part of this implementation.
The first Store submission is a one-time Partner Center operation: upload the validated AppX artifact and complete the listing, properties, age rating, pricing, and availability there. The Store Developer CLI does not implement a first submission from a loose MSIX file. Once that first submission has been accepted, later stable package updates use the automated Store workflow.
Microsoft Store submission does not make the Direct NSIS installer signed. The Store package may be treated as a supported public download only after its Partner Center certification, installation, sidecar, protocol, update, and uninstall gates have passed on real Windows systems.
Iteration Path
- Alpha foundation: keep Windows build and automated adapter/Onboarding checks green in the isolated workflow.
- Product-chain validation: exercise Browser, Files, Terminal, Agent install/start, AgentGUI launch, persistence, event push, and restart recovery on Windows 10 and Windows 11 x64.
- Store prototype: use the manual Store workflow with its submission flag disabled first, then validate a test Partner Center product before replacing the complete environment profile with production credentials and identity.
- Store stable release: enable automatic stable submission only after certification and installation gates pass; keep Direct NSIS and Store update responsibility isolated.
- Additional architectures: add an explicit platform key such as
windows-arm64, its native artifacts, and CI coverage. Reuse common services and existing capability contracts unless ARM64 exposes a genuine new boundary.
Each phase must preserve the previous macOS path. A phase can be rolled back by disabling its Windows workflow or publication integration without changing App manifests or common lifecycle contracts.
Non-Goals
- shipping a general-purpose Unix environment on Windows;
- requiring Git for Windows, WSL, or a user-installed Bash;
- adding
bootstrap.cmdor a platform entrypoint matrix to every Workspace App; - duplicating services, API contracts, or persistence models per platform;
- supporting Windows ARM64 before its binaries, packaging, and test matrix are explicitly added;
- publishing unsigned Alpha artifacts through stable or
latestchannels. - publishing RC or beta builds to Microsoft Store in the first Store phase;
- mirroring a Store-signed package back to GitHub Releases or the Direct updater feed.
Open Decisions
These items are not established by the current code and must not be presented as completed support:
- the exact minimum supported Windows 10 release;
- whether Browser and Files are mandatory for the first externally distributed Alpha, rather than later preview promotion gates;
- Windows ARM64 schedule;
- the final public wording and timing for announcing Windows support.
- the production Partner Center product identity and least-privilege Entra application ownership;
- the version-controlled repository and staging environment for the
tutti-desktop-downloadWorker; - the exact Windows 10/11 Store install, update, sidecar, protocol, and data-path evidence required before enabling production submission.