Windows Support Architecture

April 4, 2026 ยท View on GitHub

This document describes how jcode achieves cross-platform support for Linux, macOS, and Windows.

Status

  • Transport layer: Implemented (src/transport/)
  • Platform module: Implemented (src/platform.rs)
  • Windows transport: Implemented but untested (src/transport/windows.rs)
  • Windows platform: Implemented (src/platform.rs has #[cfg(windows)] branches)
  • Windows CI: Not yet set up

Design Principle

Zero cost on Unix. The abstraction layer uses #[cfg] compile-time gates and type aliases so that Linux and macOS code paths compile to the exact same binary as before. Windows gets its own implementations behind #[cfg(windows)]. No traits, no dynamic dispatch, no runtime branching.

Install Paths

Current Windows install paths from scripts/install.ps1:

  • Launcher: %LOCALAPPDATA%\\jcode\\bin\\jcode.exe
  • Stable channel binary: %LOCALAPPDATA%\\jcode\\builds\\stable\\jcode.exe
  • Immutable versioned binaries: %LOCALAPPDATA%\\jcode\\builds\\versions\\<version>\\jcode.exe

Unlike the current Unix self-dev/local-build flow, the PowerShell installer currently installs the stable channel rather than a separate current channel.

Transport Layer (src/transport/)

The transport layer abstracts IPC (Inter-Process Communication). On Unix, jcode uses Unix domain sockets. On Windows, jcode uses named pipes.

Module Structure

src/transport/
  mod.rs        - conditional re-exports (cfg-gated)
  unix.rs       - type aliases wrapping tokio Unix sockets (zero-cost)
  windows.rs    - named pipe Listener/Stream with split support

Unix (Linux + macOS)

Unix transport is a thin re-export of existing types:

pub use tokio::net::UnixListener as Listener;
pub use tokio::net::UnixStream as Stream;
pub use tokio::net::unix::OwnedWriteHalf as WriteHalf;
pub use tokio::net::unix::OwnedReadHalf as ReadHalf;
pub use std::os::unix::net::UnixStream as SyncStream;

The compiled binary is byte-for-byte identical to what it was before the abstraction.

Windows

Windows transport provides custom types wrapping tokio::net::windows::named_pipe:

  • Listener: Wraps NamedPipeServer with an accept loop that creates new pipe instances for each connection (named pipes are single-client, so a new instance is created after each accept)
  • Stream: Enum over NamedPipeServer (accepted) or NamedPipeClient (connected), implementing AsyncRead + AsyncWrite
  • ReadHalf / WriteHalf: Created via stream.into_split() using Arc<Mutex<Stream>> since named pipes don't support native kernel-level splitting
  • SyncStream: Opens the named pipe as a regular file for blocking I/O

Socket paths are converted to pipe names: /run/user/1000/jcode.sock becomes \\.\pipe\jcode.

API Surface

Both platforms export the same interface:

ExportUnixWindows
Listenertokio::net::UnixListenerCustom struct wrapping NamedPipeServer
Streamtokio::net::UnixStreamEnum over NamedPipeServer/NamedPipeClient
ReadHalftokio::net::unix::OwnedReadHalfArc<Mutex<Stream>> wrapper
WriteHalftokio::net::unix::OwnedWriteHalfArc<Mutex<Stream>> wrapper
SyncStreamstd::os::unix::net::UnixStreamstd::fs::File wrapper

Platform Module (src/platform.rs)

Centralizes all non-IPC OS-specific operations:

FunctionUnixWindows
symlink_or_copy(src, dst)std::os::unix::fs::symlink()Try symlink_file/dir, fall back to copy
atomic_symlink_swap(src, dst, temp)Create temp symlink + renameRemove + copy (best effort)
set_permissions_owner_only(path)chmod 600No-op
set_permissions_executable(path)chmod 755No-op
is_process_running(pid)kill(pid, 0)Returns true (stub)
replace_process(cmd)exec() (replaces process)spawn() + exit()

Files Migrated

All OS-specific code has been moved out of application files into the transport and platform modules:

FileWhat was migrated
src/server.rsUnixListener, UnixStream, OwnedReadHalf, OwnedWriteHalf
src/tui/backend.rsUnixStream, OwnedWriteHalf, OwnedReadHalf
src/tui/client.rsUnixStream, OwnedWriteHalf
src/tui/app.rsUnixListener, OwnedWriteHalf, file permissions
src/tool/communicate.rsstd::os::unix::net::UnixStream
src/tool/debug_socket.rstokio::net::UnixStream
src/main.rsUnixStream (health checks), all exec() calls, file permissions
src/build.rsSymlinks, executable permissions
src/update.rsSymlinks, permissions, atomic swap
src/auth/oauth.rsCredential file permissions
src/skill.rsSymlink creation
src/video_export.rsFrame symlinks
src/ambient.rsProcess liveness check
src/registry.rsProcess liveness check
src/session.rsProcess liveness check

Dependencies

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Threading"] }

The tokio dependency already includes named pipe support on Windows (part of features = ["full"]).

What Doesn't Change

The vast majority of the codebase is platform-agnostic:

  • All provider code (HTTP-based)
  • All tool implementations (except bash tool's shell selection)
  • TUI rendering (crossterm + ratatui already cross-platform)
  • Agent logic, memory, sessions, config
  • MCP client/server protocol
  • JSON serialization, protocol handling

Remaining Work

  1. Windows CI - Add GitHub Actions Windows runner, test compilation and basic IPC
  2. Shell tool - Detect platform and use cmd.exe or pwsh.exe on Windows
  3. Self-update - Handle Windows exe replacement (can't overwrite running binary)
  4. Testing - Run full test suite on Windows