port clide runtime to Windows (ConPTY, AF_UNIX, PATHEXT)

Bring the runtime up on Windows without disturbing the POSIX paths.

PTY: introduce a platform-neutral PtySession contract with a factory
that picks NativePty (posix_openpt/posix_spawn) or the new WindowsPty
(ConPTY via CreatePseudoConsole). The pane registry programs against
the interface; NativePty now implements it.

IPC: the per-workspace AF_UNIX socket lives under %LOCALAPPDATA% and
is hashed from a canonical workspace key (backslash + ASCII-folded
case) so the Dart server and the C client agree despite NTFS case-
insensitivity. The C client grows a Win32 shim (winsock afunix);
chmod is a no-op on Windows where the per-user ACL is the gate.

Toolchain: PATH probing splits on ';' and tries PATHEXT extensions;
the shell defaults to PowerShell (pwsh, then powershell); tmux is
treated as optional since it has no Windows build; dugite falls back
to PATH git for now.

Build: add `make build-windows`, a clide-cli MSVC build wrapped by
ci/build_cli_windows.sh, and a ConPTY smoke-test suite that self-
skips off-platform.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-14 17:42:55 +02:00
co-authored by Claude
parent 3a59f4f2e6
commit 03cc0603b0
19 changed files with 1257 additions and 103 deletions
+14 -6
View File
@@ -120,20 +120,28 @@ class _ClideTestAppState extends State<ClideTestApp> {
await _testExists('git', tc.git);
await _testExists('pql', tc.pql);
await _testExists('tmux', tc.tmux);
// tmux has no Windows build — its absence there is the documented
// no-tmux mode, so the probes would only report a non-failure.
if (!Platform.isWindows) await _testExists('tmux', tc.tmux);
await _testExists('shell', tc.shell);
_say('');
await _testExec('git --version', tc.git, ['--version'], workDir);
await _testExec('pql --version', tc.pql, ['--version'], workDir);
await _testExec('tmux -V', tc.tmux, ['-V'], workDir);
await _testExec('shell --version', tc.shell, ['--version'], workDir);
if (!Platform.isWindows) await _testExec('tmux -V', tc.tmux, ['-V'], workDir);
// PowerShell has no --version flag; ask for the version variable
// through the same -c path the passthrough tests use.
await _testExec('shell --version', tc.shell, Platform.isWindows ? ['-c', r'$PSVersionTable.PSVersion.ToString()'] : ['--version'], workDir);
_say('');
// Shell passthrough — use the resolved shell, not a hardcoded path
await _testExec('shell -c git', tc.shell, ['-c', '${tc.git} --version'], workDir);
await _testExec('shell -c pql', tc.shell, ['-c', '${tc.pql} --version'], workDir);
await _testExec('shell -c tmux', tc.shell, ['-c', '${tc.tmux} -V'], workDir);
// (-c works for POSIX shells and as PowerShell's -Command alias).
// PowerShell needs the & call operator to run a quoted path; POSIX
// shells take the bare path.
String shellCall(String exe, String args) => Platform.isWindows ? "& '$exe' $args" : '$exe $args';
await _testExec('shell -c git', tc.shell, ['-c', shellCall(tc.git, '--version')], workDir);
await _testExec('shell -c pql', tc.shell, ['-c', shellCall(tc.pql, '--version')], workDir);
if (!Platform.isWindows) await _testExec('shell -c tmux', tc.shell, ['-c', shellCall(tc.tmux, '-V')], workDir);
await _testExec('shell -c git (bare)', tc.shell, ['-c', 'git --version'], workDir);
_say('');