Skip to content

feat(claude-loop): Windows ConPTY proxy — live human-typing detection (#281, strategy B)#10

Merged
quazardous merged 1 commit into
mainfrom
feat/win
May 21, 2026
Merged

feat(claude-loop): Windows ConPTY proxy — live human-typing detection (#281, strategy B)#10
quazardous merged 1 commit into
mainfrom
feat/win

Conversation

@quazardous

Copy link
Copy Markdown
Owner

What

Ports the claude-loop PTY proxy (#269) to Windows. The Unix proxy is POSIX-only (pty/termios/AF_UNIX); Windows needs ConPTY + a named pipe. This is strategy B — a standalone nested-ConPTY proxy that ships the feature now; strategy A (psmux emits the signal natively) is the planned follow-up (see the design doc).

Same feature as the Unix side: tell a human typing apart from claude's output and from the loop's own wake injection — live, busy included.

New crate: windows/cl-pty-proxy/ (Rust)

  • GNU toolchain (x86_64-pc-windows-gnu) — no MSVC / VS Build Tools needed.
  • Nested ConPTY for claude via portable-pty (same layer psmux is built on); bridges three channels: human stdin → claude, claude output → stdout, named-pipe injection → claude (marker untouched).
  • win32-input-mode parsing — the key Windows gotcha: under psmux/ConPTY keystrokes arrive as ESC[Vk;Sc;Uc;Kd;Cs;Rc_, not raw VT, so every keystroke starts with ESC. A naive "first byte printable" test never fires. The parser extracts the unicode + key-down to detect text keystrokes. 7 unit tests on real captured sequences.
  • Markers (human-typing, PID-stamped proxy-alive), @cl_human painting (#274 parity), resize + exit-code propagation, fail-safe direct-run if ConPTY init fails (pane never bricked).

Wiring

  • cli.ts: on win32, launch cl-pty-proxy.exe -- claude … when the binary exists; the Python proxy branch is gated to non-Windows (its POSIX APIs would crash the pane). claude-loop check reports proxy active/inactive.
  • state.ts: injectWakePhrase → named pipe \.\pipe\cl-inject-<name> via Node net (named pipes are first-class, no native dep), gated on proxyIsAlive (a pipe can't be stat-ed). New injectPipeName() helper.

Validation (under real psmux)

  • ✅ ConPTY bridging (cmd renders, DSR cursor handshake completes)
  • ✅ human typing → human-typing marker + @cl_human=stop, reverts to loop after TTL
  • ✅ named-pipe injection reaches claude without touching the marker (channel separation)
  • ✅ graceful cleanup; kill -9 leaves a stale marker handled by the PID-liveness check (#278 contract)
  • ✅ end-to-end via claude-loop start
  • cargo test (7), cargo clippy, TS typecheck + eslint all clean

Build (not committed — platform-specific artifact, target/ gitignored)

cargo build --release --manifest-path windows/cl-pty-proxy/Cargo.toml

Without it, claude-loop still works — falls back to idle-only pane-diff detection.

Known limitation → motivates strategy A

claude runs under a second ConPTY nested inside psmux's ConPTY (double conhost). Basic rendering / resize work, but complex TUI cases can glitch. This is inherent to any nested-proxy approach. Strategy A (psmux touches the marker natively in forward_key_to_active — no nested PTY, no double translation, no native dep) is documented in docs/PTY-PROXY-WINDOWS.md as the follow-up PR; strategy B validates the markers/named-pipe/status wiring that A would reuse.

Docs

  • docs/PTY-PROXY-WINDOWS.md — full design + strategy A proposal.
  • docs/WIN-INSTALL.md — optional build step.

…on (#281, strategy B)

Port the claude-loop PTY proxy (#269) to Windows. The Unix proxy is
POSIX-only (pty/termios/AF_UNIX); Windows needs ConPTY + a named pipe.

New crate `windows/cl-pty-proxy/` (Rust, GNU toolchain — no MSVC needed):
- Nested ConPTY for claude via portable-pty (same layer psmux uses);
  bridges human-stdin / claude-output / named-pipe wake injection.
- Detects win32-input-mode keystrokes (ESC[Vk;Sc;Uc;Kd;Cs;Rc_) — what
  psmux/ConPTY actually delivers, not raw VT — so the human-typing
  marker fires busy-included. 7 unit tests on captured sequences.
- Markers (human-typing, PID-stamped proxy-alive), @cl_human painting,
  resize + exit-code propagation, fail-safe direct-run on ConPTY failure.

Wiring:
- cli.ts: launch the .exe on win32 when built (Python proxy gated to
  non-Windows — its POSIX APIs would crash the pane); `check` probe.
- state.ts: injectWakePhrase → named pipe (\.\pipe\cl-inject-<name>)
  via Node net, gated on proxyIsAlive; injectPipeName() helper.

The .exe is a platform-specific build artifact (target/ gitignored);
build with `cargo build --release --manifest-path windows/cl-pty-proxy`.
Without it, claude-loop falls back to idle-only pane-diff detection.

Known limit: double conhost (nested ConPTY) can glitch complex TUIs —
docs/PTY-PROXY-WINDOWS.md documents this and proposes strategy A
(psmux emits the signal natively, no nested PTY) as the follow-up PR.
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