Skip to content

Windows terminal host: BlitzOS agents run via conpty-host (supermux session-host)#1

Open
silfralabs wants to merge 7 commits into
windows-helperfrom
windows-terminal-host
Open

Windows terminal host: BlitzOS agents run via conpty-host (supermux session-host)#1
silfralabs wants to merge 7 commits into
windows-helperfrom
windows-terminal-host

Conversation

@silfralabs

Copy link
Copy Markdown
Owner

Stacked on #1 (windows-helper -> main): base is windows-helper, so this PR shows ONLY the terminal-host diff. Re-target to main once #1 merges.

Brings the agent terminal layer to Windows with NO tmux and NO native addon, preserving restart-survival. BlitzOS agents (claude/codex) run in a real ConPTY via the supermux session-host, driven over its host_rpc named-pipe protocol.

What's here (6 commits)

  • conpty-host.mjs: a drop-in for tmux-host.mjs satisfying the full TmuxHost interface but talking host_rpc instead of tmux -CC. Control plane (spawn/kill/exists/pane_pid/capture/send/resize, FIFO-serialized, reconnect-once) + live stream plane (Subscribe + stream pipe -> 256KB scrollback ring + onData) + restart adoption (ListSessions) + auto-spawn of the detached session-host.
  • terminal-ops.mjs: selects conpty-host on win32 (tmux elsewhere); tmux preflight guarded.
  • agent-runtime.mjs: win32 launcher writes the POSIX agent command to a launch.sh run under Git Bash (so $(cat bootstrap) expands), forward-slashes the paths (Git Bash eats backslashes), and pins the agent's Bash tool to Git Bash (CLAUDE_CODE_GIT_BASH_PATH + CLAUDE_CODE_USE_POWERSHELL_TOOL=0).
  • onboarding.ts: win32-aware CLI detection (claude.exe via .exe candidates + where; stop mangling the ;-separated Windows PATH).
  • index.ts: tmux not a prerequisite on win32; optional env-gated renderer debug port.
  • electron-builder.yml + dist-win.ps1 + vendor/bin/session-host.exe: bundle the session-host (self-contained windows-gnu build, ldd shows only system DLLs) for packaged installs.

Verified live on Windows

A real claude agent boots through the full stack (launchAgent -> conpty-host -> session-host -> ConPTY -> Git Bash -> claude.exe), renders its TUI, runs the agent runtime (curl /events), authenticates, and the chat round-trip works (user message -> agent reply in chat). Harnesses: control plane 21/21, stream plane 6/6, restart-survival 8/8.

Not yet done

  • The packaged build (npm run dist:win) has not been run end to end (bundling is configured; the binary is vendored).
  • Codex backend not verified on Windows (claude verified).
  • Minor conpty-host kill-then-stop race (a fire-and-forget kill can be dropped by an immediate stop).
  • Requires Git for Windows on the end-user machine (the agent runtime is POSIX; documented in dist-win.ps1).

🤖 Generated with Claude Code

sqllocks and others added 7 commits June 30, 2026 14:04
…-host

New src/main/conpty-host.mjs: a drop-in for tmux-host.mjs that drives
supermux-session-host over its host_rpc named-pipe protocol instead of
`tmux -CC`. Satisfies the full TmuxHost interface (tmux-host.d.mts), so
terminal-manager.mjs / agent-runtime.mjs are unchanged. No native addon,
restart-survival preserved, a real ConPTY TTY for claude/codex.

Control plane (M-b): start/handshake, spawn/kill/exists/pane_pid/resize/
send_text, FIFO-serialized request/response with reconnect-once. The
TmuxHost interface is mixed sync/async (only start/spawn/adoptExisting are
awaited), so a local terminals map answers the synchronous methods and a
1s poll keeps capture() fresh and backstops exit via PaneDead.

Stream plane (M-c): onData via Subscribe + the per-session stream pipe
(supermux-stream-<token>), a 256KB scrollback ring with replay, and onExit
via stream EOF (guarded against a stop()-detach reading as a session exit).

Proven against a live session-host: control plane 21/21, stream plane 6/6
(live ANSI bytes incl. post-write markers, ring replay, natural-exit onExit).
NOT yet wired into the terminal-ops selector (M-e); BlitzOS does not yet
spawn the session-host (gap 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-e.1)

- terminal-ops.mjs: on win32 create conpty-host instead of tmux-host (same
  TmuxHost interface); guard the tmux preflight so Windows logs the ConPTY
  backend instead of a spurious "tmux NOT found". Zero change off win32.
- conpty-host.mjs: start() now ensures a session-host is running (gap 1):
  connect if one already runs, else spawn the binary DETACHED (resolved via
  BLITZ_SESSION_HOST_BIN or Resources/bin/session-host.exe) with
  SUPERMUX_HOST_ID pinned, so it outlives BlitzOS (restart-survival).

Verified: with no host running, start() auto-spawns it and a terminal runs;
the detached host survives the client's stop().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The claude/codex agent command is POSIX-shaped: `claude ... "$(cat bootstrap)"`
plus a runtime that calls bash/curl/tail/wait.sh. The Windows ConPTY terminal
host runs a program directly (no shell), so on win32 prepareAgentLaunch now
writes the command to a per-session launch.sh and returns `bash <launch.sh>`
(two clean tokens; exec makes the agent the pane process). Git Bash expands
$(cat ...) and gives the agent its POSIX runtime. resolveBashBin finds it via
BLITZ_BASH_BIN or the Git-for-Windows install paths. No-op off win32.

Validated on Windows against the live ConPTY host: MSYS cat reads the backslash
bootstrap path, claude.exe (2.1.196) resolves under Git Bash, and
prepareAgentLaunch emits the bash launcher + a correct launch.sh.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uncher + tmux gate)

Three Windows-port fixes so a real claude agent boots through conpty-host:

- onboarding.ts: resolveCli now finds claude.exe/codex.exe on Windows
  (.exe/.cmd candidates under ~/.local/bin etc. + `where`), and ensureFullPath
  no longer mangles the `;`-separated Windows PATH by splitting/joining on `:`.
  Without this claudeCliPath() returned null -> "no agent backend" -> the agent
  never launched.
- agent-runtime.mjs: forward-slash the launch.sh command before write, so Git
  Bash does not eat the backslashes in `exec C:\...\claude.exe` (which collapsed
  to "C:...claude.exe: not found").
- index.ts: tmux is not a prerequisite on win32 (conpty-host replaces it), so the
  brain stops posting a false "I can't respond yet -- missing tmux".

Verified live on Windows: agent 0 boots through conpty-host -> session-host ->
ConPTY -> Git Bash -> claude.exe, renders its TUI, reads its chat, polls /events,
and thinks at xhigh effort (authed, tokens flowing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… debug port

- agent-runtime.mjs: the win32 launcher now exports CLAUDE_CODE_GIT_BASH_PATH and
  CLAUDE_CODE_USE_POWERSHELL_TOOL=0 before exec'ing claude, so the agent's OWN Bash
  tool reliably uses Git Bash (not the progressively-rolling-out PowerShell tool)
  for its POSIX runtime (wait.sh / curl / tail / cat). Git for Windows is OPTIONAL
  for claude (code.claude.com/docs/en/setup), so without this the agent runtime
  could silently fall back to PowerShell and break. Verified: agent 0 launch.sh
  carries both vars and the agent boots with them.
- index.ts: optional renderer remote-debugging port (app.commandLine
  remote-debugging-port) gated behind BLITZ_REMOTE_DEBUG_PORT, off by default, for
  automated UI verification. No effect on normal or production launches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- electron-builder.yml: bundle vendor/bin/session-host.exe -> bin/session-host.exe
  (win.extraResources), matching conpty-host's resolveSessionHostBin
  (resourcesPath/bin/session-host.exe). A packaged install now carries the terminal
  backend, so conpty-host auto-spawns it with no BLITZ_SESSION_HOST_BIN needed.
- dist-win.ps1: build the session-host (cargo --release, windows-gnu) from
  $env:SUPERMUX_REPO and vendor it; else use the committed vendored binary (the
  vendor/bin/tmux model). Plus a Git-for-Windows prerequisite note.
- vendor/bin/session-host.exe: the release binary (stripped). Self-contained
  (ldd: only system DLLs incl. ucrtbase, no MinGW runtime), so it runs on a clean
  Win10 1809+ machine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he socket

stop() closed the control socket immediately, which could abort a just-fired
fire-and-forget kill() (kill-then-stop left stale sessions in the host). Drain
rpc.chain before rpc.close() so a pending kill flushes first; the chain always
settles (request swallows rejections), so close still runs.

Reasoned + syntax-checked, not separately runtime-tested: the race is a
teardown-only path (real shutdown via terminal-ops stopHosts calls stop() with
no preceding per-id kills), so it never fires on a production path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants