Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ jobs:
cargo test --locked --manifest-path host-rs/Cargo.toml
bun run test:host
bash scripts/build-mac-host.sh
# Windows (phase 3b): clippy for the Windows code, and the tests, whose smoke test runs
# the built host over its named pipe with ConPTY panes (the parity suite is Unix only).
- name: Rust host on Windows
if: runner.os == 'Windows'
working-directory: host-rs
run: |
cargo clippy --all-targets --locked -- -D warnings
cargo test --locked
- if: inputs.package
run: bun run ${{ matrix.dist }}
env:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 2026-10-27

- [host] Rust host phase 3b (#76, ADR 0004): Windows builds carry `wraithgrid-host.exe` (`dist:win` builds it). Panes run in a ConPTY of our own (portable-pty quotes every argument, and an npm `.cmd` CLI comes as a ready `cmd.exe /d /s /c "…"` line): file and args quoted the way CreateProcess reads them, a ready line kept as is, stdio cut off from the host's log, the exit code from the process handle (no signals), kill = close the console then `taskkill /T /F` after the grace, as the Node host does. The host serves a named pipe (one instance per client, remote clients refused), checks the lock owner with OpenProcess, gets its token from BCryptGenRandom, and a Ctrl-C hook that can't be set (no console) never stops it; the ssh bridge stays Unix. `host-rs/tests/smoke.rs` starts the built host on every OS, connects over its socket or pipe, runs a pane that prints and one typed into, then shuts it down; CI's Windows job runs it with clippy. Settings → Advanced "Use the Rust host" shows on every OS (files: host-rs/src/{pty,main,server}.rs, host-rs/Cargo.{toml,lock}, host-rs/tests/smoke.rs, src/main/app.ts, src/renderer/components/SettingsView.tsx, package.json, electron-builder.yml, .github/workflows/build.yml, docs/CONTEXT.md)
- [host] Rust host phase 3a (#76, ADR 0004): macOS builds carry it too: `bun run dist:mac` builds one universal (arm64 + x64) `wraithgrid-host` (`scripts/build-mac-host.sh`, lipo) bundled in both apps and ad-hoc signed with them; Settings → Advanced "Use the Rust host" shows on macOS; CI's macOS job runs `cargo test` and the host parity suite. Windows is phase 3b: portable-pty always quotes arguments, so the ready `cmd.exe /d /s /c "…"` line of an npm `.cmd` CLI needs a ConPTY spawn of our own (files: scripts/build-mac-host.sh, package.json, electron-builder.yml, .github/workflows/build.yml, src/main/app.ts, src/renderer/components/SettingsView.tsx, docs/CONTEXT.md)
- [chat] A ↑ recall or a `/` pick no longer moves the caret a frame later (cause of the intermittent `chat-history.spec.ts` CI failures, e.g. `second promptline one`): the frame could land between a select-all and the typing that replaces it, so the text was added instead; the caret is now placed in a layout effect right after the text is in the box (files: src/renderer/components/chat/Composer.tsx)
- [chat] A sideways swipe over the chat no longer pauses follow (the cause of the intermittent `chat.spec.ts:299` CI failure): Chromium sometimes scrolls the chat by the swipe's few px of vertical noise, and that upward move read as the reader going back; for 150 ms after a mostly sideways wheel the chat's own upward scroll is ignored (files: src/renderer/components/chat/ChatView.tsx)
Expand Down
2 changes: 1 addition & 1 deletion docs/CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ react-resizable-panels, @dnd-kit. Tests: Vitest (unit), Playwright (Electron e2e
- `src/main/host-start.ts`: start a detached host (fd masking) + socket connect; used by app and bridge `src/main/pane-router.ts`: routes pane ops to the local host or a machine's (by workspace `machineId`)
- `src/main/spawn-resolve.ts`: pane spec → exact process (binary, args, cwd, env); local in ipc, remote in the bridge `src/main/ssh.ts`: `~/.ssh/config` hosts, ssh args, ssh child as a duplex stream
- `src/main/ipc.ts`: all IPC handlers; validates every payload with zod contracts `src/main/pane-host.ts`: GUI side of the pty host: find/start it, hello + token, create/attach/write/kill, version mismatch
- `host-rs/`: the Rust pty host (ADR 0004; phase 2/3a: Settings → Advanced "Use the Rust host", off by default, Linux and macOS (Windows: phase 3b); `settings.rustHost` or `WRAITHGRID_RUST_HOST=1` → app.ts `startHost` runs the binary from resources (packaged) or `host-rs/target/release` (dev) instead of host.js; Linux packages bundle it via `extraResources`): `main.rs` lock/token/socket/signals, `server.rs` clients+panes (same frames as `src/host/protocol.ts`), `pty.rs` portable-pty + waitpid, `screen.rs` vt100 snapshot/text, `protocol.rs` frame validation; phase 5: `bridge.rs` = `wraithgrid-host stdio <ver>`, the ssh end on a Linux machine (starts/joins a host under `~/.wraithgrid/remote`, injects the token, turns `plan` frames into `create`), `plan.rs` = login-shell PATH, binary, folder, env on that machine; GUI side `src/main/remote-plan.ts` (spec → plan: the rules) + `remote-bin.ts` (preflight `uname -sm`, upload once per version to `~/.wraithgrid/bin`, fallback `wraithgrid host-stdio`), static musl binaries from `scripts/build-remote-hosts.sh` → `host-rs/target/remote` → resources `remote/`; `tests/host/` = parity suite vs the Node host (`bun run test:host`)
- `host-rs/`: the Rust pty host (ADR 0004; phase 2/3: Settings → Advanced "Use the Rust host", off by default, Linux, macOS and Windows (own ConPTY spawn + named pipe there; `host-rs/tests/smoke.rs` runs the built host on every OS); `settings.rustHost` or `WRAITHGRID_RUST_HOST=1` → app.ts `startHost` runs the binary from resources (packaged) or `host-rs/target/release` (dev) instead of host.js; Linux packages bundle it via `extraResources`): `main.rs` lock/token/socket/signals, `server.rs` clients+panes (same frames as `src/host/protocol.ts`), `pty.rs` portable-pty + waitpid, `screen.rs` vt100 snapshot/text, `protocol.rs` frame validation; phase 5: `bridge.rs` = `wraithgrid-host stdio <ver>`, the ssh end on a Linux machine (starts/joins a host under `~/.wraithgrid/remote`, injects the token, turns `plan` frames into `create`), `plan.rs` = login-shell PATH, binary, folder, env on that machine; GUI side `src/main/remote-plan.ts` (spec → plan: the rules) + `remote-bin.ts` (preflight `uname -sm`, upload once per version to `~/.wraithgrid/bin`, fallback `wraithgrid host-stdio`), static musl binaries from `scripts/build-remote-hosts.sh` → `host-rs/target/remote` → resources `remote/`; `tests/host/` = parity suite vs the Node host (`bun run test:host`)
- `src/host/`: background pty host (ADR 0001), built as `out/main/host.js`, never imports electron: `index.ts` entry (lock, token, socket), `server.ts` panes + clients, `protocol.ts` NDJSON frames (zod), `paths.ts` socket/token paths per user-data dir, `screen.ts` headless xterm + serialize, `fds.ts` inherited-fd masking, `pty-manager.ts` spawns/kills pane processes (per-OS KillPolicy) `src/main/chat/`: chat panes (ADR 0002): `sessions.ts` runs one headless CLI per pane (pipes, JSON lines), `adapter.ts` registry, `claude.ts` stream-json ↔ ChatEvent (+ approvals), `agy.ts` agy's stream-json (no approvals/interrupt: `controls` event → the view restarts it), `codex.ts` `codex app-server` JSON-RPC, `images.ts` pasted images → temp files, `spawn.ts` masked fds/process group, `attach.ts` files from the attach picker, `transcript.ts` copies a conversation to another account's dir (claude transcript, or codex rollout under `sessions/YYYY/MM/DD`, #60) (switch account, #29); add a CLI = one adapter + `chat: true` in agent-clis
- `src/main/login.ts`: sign-in dialog backend, per CLI (`LOGIN_FLOWS`): claude `auth login` + `auth status --json`; codex `login --device-auth` (one-time code) + `login status`; `DIALOG_LOGIN_CLIS` in agent-clis says who gets the dialog `src/main/clipboard-image.ts`: clipboard image → temp file → text to paste (path, or `@path` for gemini/agy)
- `src/main/pane-env.ts`: builds a pane's env (per-CLI config dir var for agent panes; mod folders as `CLAUDE_CODE_PLUGIN_DIRS` for claude; strips the launching terminal's vars; UTF-8 locale fallback) `src/main/config-store.ts`: config.json load/save, atomic write, bad-file quarantine
Expand Down
2 changes: 1 addition & 1 deletion docs/TODO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# TODO

## Now
- #76 Rust host: 3a macOS done (never run on a real Mac: only CI's parity suite); 3b Windows next (own ConPTY spawn with a raw command line, named pipe, taskkill tree, Ctrl-C), then 4 (default on, drop node-pty); phase 5 arm64 binary never run on real arm64; old `~/.wraithgrid/bin/wraithgrid-host-*` versions on remote machines are never removed
- #76 Rust host: 3a macOS done (never run on a real Mac: only CI's parity suite); 3b Windows done on feat/rust-host-windows-20261027 (only CI's smoke test: never run in the real app on Windows, nor with a real npm claude.cmd); then 4 (default on, drop node-pty); phase 5 arm64 binary never run on real arm64; old `~/.wraithgrid/bin/wraithgrid-host-*` versions on remote machines are never removed
- CI flakes fixed 2026-10-27 (PR #152): chat.spec.ts:299 (sideways-wheel noise paused follow), chat-history.spec.ts (caret placed a frame late collapsed a select-all). Still seen once locally: responsive.spec.ts:131 (Settings nav heading not hidden in a small window), passed 3/3 on rerun
- Audit (2026-10-26) bugs: all fixed (#135–#138 PR #148, #139 PR #150, #140 PR #151). Not seen live: #138 cancelled pkexec prompt, #140 renderer-crash reattach, codex cancel/stop paths, agy first-start effort
- Audit (2026-10-26) perf: #141 done (PR #149); not measured live yet (a DevTools profile of a long streaming chat before/after would confirm)
Expand Down
5 changes: 5 additions & 0 deletions electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ win:
arch:
- x64
icon: build/icon.png
# The Rust background host (ADR 0004 phase 3b), off unless Settings → Advanced turns it on;
# `bun run dist:win` builds it first.
extraResources:
- from: host-rs/target/release/wraithgrid-host.exe
to: wraithgrid-host.exe
nsis:
oneClick: false
perMachine: false
Expand Down
1 change: 1 addition & 0 deletions host-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions host-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ sha2 = "0.11.1"
tokio = { version = "1", features = ["rt-multi-thread", "net", "io-util", "time", "sync", "macros", "signal", "io-std"] }
vt100 = "0.18.1"

# Windows (ADR 0004 phase 3b): our own ConPTY spawn, the lock's liveness check and the token.
[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Cryptography",
"Win32_System_Console",
"Win32_System_Pipes",
"Win32_System_Threading",
] }

[profile.release]
lto = true
codegen-units = 1
Expand Down
Loading
Loading