Skip to content
Closed
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
117 changes: 117 additions & 0 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: Windows

# Windows is the one supported platform the Linux CI cannot speak for: the
# things that break there are path layout (Scripts\python.exe), archive format
# (zip instead of tar.gz), console allocation, and Win32 error codes for
# filesystem races. None of those have a Linux equivalent that would catch a
# regression, so they get their own job.

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: windows-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
build-check:
name: Build and typecheck (Windows)
runs-on: windows-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7.0.0
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

- name: Lint and format
run: npx biome check --error-on-warnings .

test:
name: Platform tests (Windows)
runs-on: windows-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7.0.0
with:
node-version: 22
cache: npm

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

# Scoped to the suites whose behaviour is platform-dependent. The rest of
# the suite still carries POSIX-only fixtures (shell-scripted stubs, chmod
# permission bits, symlinks) and is covered by the Linux jobs.
- name: Test
working-directory: packages/coding-agent
run: >-
npx vitest --run
test/kernel-venv-python-path.test.ts
test/expand-tilde-path.test.ts
test/session-lease.test.ts
test/command-recovery-journal.test.ts
test/worker-recovery-journal.test.ts
test/orphan-process-journal.test.ts
test/child-process.test.ts
test/bash-close-hang-windows.test.ts
test/daemon-socket.test.ts
test/paths.test.ts
test/path-utils.test.ts
test/args.test.ts

kernel-smoke:
name: IPython kernel smoke (Windows)
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Setup Node.js
uses: actions/setup-node@v7.0.0
with:
node-version: 22
cache: npm

- name: Install uv
run: |
irm https://astral.sh/uv/install.ps1 | iex
"$env:USERPROFILE\.local\bin" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

# The regression this guards: the venv interpreter used to be looked up at
# <venv>/bin/python, which does not exist on Windows. Every readiness probe
# failed, so the venv was rebuilt on each launch and the IPython tool never
# came up. Bootstrapping twice proves the second run is a cache hit.
- name: Bootstrap kernel venv and execute a cell
working-directory: packages/coding-agent
run: node scripts/windows-kernel-smoke.mjs
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh

The installer downloads a versioned release, verifies its SHA-256 checksum, installs the `prime-agent` command, and can prepare the IPython runtime used by the agent.

On Windows, install from npm and run it from PowerShell, Command Prompt, or Windows Terminal — no WSL or Git Bash session required:

```powershell
npm install -g prime-agent
```

Windows needs Node.js 22.8+ and [Git for Windows](https://git-scm.com/download/win), whose bundled Bash backs the agent's shell tool. See [Windows support](packages/coding-agent/docs/windows.md) for the full requirements and known limitations.

Start Prime Agent from the repository or directory you want it to work in:

```bash
Expand Down Expand Up @@ -98,6 +106,7 @@ Prime Agent is built for long-running work, especially for evaluations in resear
- [Provider setup](packages/coding-agent/docs/providers.md) — subscription and API-key providers
- [Architecture overview](packages/coding-agent/docs/architecture.md) — daemon, worker, kernel, and persistence boundaries
- [Development](packages/coding-agent/docs/development.md) — build and run from source
- [Windows support](packages/coding-agent/docs/windows.md) — requirements, what differs from macOS and Linux, and troubleshooting

## Acknowledgements

Expand Down
8 changes: 8 additions & 0 deletions packages/coding-agent/docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ curl -fsSL https://app.primeintellect.ai/prime-agent/install.sh | sh -s -- beta

Both commands fetch versioned Prime Agent release artifacts and install the `prime-agent` command. The inherited npm workspace identifiers in the source tree are not the public install path.

The bootstrap installer is POSIX-only. On Windows, install from npm and run Prime Agent from PowerShell, Command Prompt, or Windows Terminal:

```powershell
npm install -g prime-agent
```

See [Windows](windows.md) for requirements and platform notes.

Then start Prime Agent in the project directory you want it to work on:

```bash
Expand Down
141 changes: 134 additions & 7 deletions packages/coding-agent/docs/windows.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,144 @@
# Windows Setup
# Windows support

Prime Agent requires a bash shell on Windows. Checked locations (in order):
Prime Agent runs natively on Windows. It is launched from PowerShell, Command
Prompt, or Windows Terminal like any other console program — a WSL distribution
or a Git Bash session is not required, and the daemon, session workers, and the
IPython kernel all run as ordinary Windows processes.

1. Custom path from `~/.prime/agent/settings.json`
2. Git Bash (`C:\Program Files\Git\bin\bash.exe`)
3. `bash.exe` on PATH (Cygwin, MSYS2, WSL)
## Requirements

For most users, [Git for Windows](https://git-scm.com/download/win) is sufficient.
| Requirement | Notes |
| --- | --- |
| Windows 10 1803+ or Windows 11 | Earlier builds lack `tar.exe`, which is used to unpack the ripgrep and fd archives. |
| Node.js 22.8 or newer | Same floor as every other platform. |
| [Git for Windows](https://git-scm.com/download/win) | Supplies the `bash.exe` that backs the agent's shell tool. |
| [uv](https://docs.astral.sh/uv/) | Installed automatically on first launch if missing; `PRIME_AGENT_INSTALL_UV=1` skips the prompt. |

## Custom Shell Path
Install and run:

```powershell
npm install -g prime-agent
cd C:\path\to\project
prime-agent
```

## What differs from macOS and Linux

**The Python kernel lives in `Scripts\`.** `uv` creates the kernel virtualenv at
`%USERPROFILE%\.prime\agent\kernel-venv` with the interpreter at
`Scripts\python.exe` rather than `bin/python`. Set `PRIME_AGENT_KERNEL_PYTHON`
to point at your own interpreter, or `PRIME_AGENT_KERNEL_VENV` to relocate the
managed one.

**The shell tool runs Bash, not `cmd.exe` or PowerShell.** Commands the model
writes are executed with Git for Windows' `bash.exe`, so the agent composes
POSIX-style command lines on every platform. Resolution order:

1. `shellPath` in `settings.json`, if set.
2. Git for Windows in `%ProgramFiles%`, `%ProgramFiles(x86)%`, or
`%LOCALAPPDATA%\Programs` — this covers the machine-wide, 32-bit, and
per-user (winget default) installers.
3. A Git install found via `git.exe` on `PATH`, which covers scoop and
Chocolatey shims.
4. Any other `bash.exe` on `PATH` (MSYS2, Cygwin).

`%SystemRoot%\System32\bash.exe` — the WSL launcher — is deliberately ranked
last. It resolves a different filesystem (`/mnt/c/...`) than the Windows paths
the agent works with, so it is only used when nothing else is available.

To pin a specific shell, set `shellPath` in `~/.prime/agent/settings.json`:

```json
{
"shellPath": "C:\\cygwin64\\bin\\bash.exe"
}
```

**`fd` and `ripgrep` are unpacked with `tar.exe`.** These optional search
helpers ship as `.zip` on Windows and are extracted into
`%USERPROFILE%\.prime\agent\bin`. Both are optional: without them the agent
falls back to slower search paths. Installing them yourself
(`winget install BurntSushi.ripgrep.MSVC`, `winget install sharkdp.fd`) makes
Prime Agent use the copies on `PATH` instead.

**asyncio subprocesses need their own event loop.** ipykernel runs on a
Windows *selector* event loop (pyzmq needs `add_reader`), and a selector loop
cannot spawn subprocesses. Prime Agent restores the proactor event loop
*policy* at kernel startup while leaving the kernel's own running loop alone,
so any loop created afterwards can spawn processes. Because the kernel's main
thread is already driving a loop, async libraries that start helper binaries —
playwright is the common one — have to run on a loop of their own:

```python
import asyncio, threading

def run_async(factory):
box = {}
def worker():
loop = asyncio.new_event_loop() # proactor loop: subprocesses work
asyncio.set_event_loop(loop)
try:
box["value"] = loop.run_until_complete(factory())
except BaseException as exc:
box["error"] = exc
finally:
loop.close()
thread = threading.Thread(target=worker)
thread.start()
thread.join()
if "error" in box:
raise box["error"]
return box["value"]
```

Use the async API rather than the sync one (`playwright.async_api`, not
`sync_playwright`): the sync API drives its own loop and conflicts with the
kernel's.

**Subprocesses are spawned with `windowsHide`.** The daemon and its session
workers are detached and therefore have no console of their own, so any console
tool they run (git, uv, python) would otherwise allocate — and flash — a console
window per invocation. Every spawn whose output is piped or discarded now
suppresses that window.

## Configuration paths

| Path | Contents |
| --- | --- |
| `%USERPROFILE%\.prime\agent` | Agent state root |
| `%USERPROFILE%\.prime\agent\kernel-venv` | Managed Python kernel virtualenv |
| `%USERPROFILE%\.prime\agent\bin` | Downloaded `rg.exe` and `fd.exe` |
| `%USERPROFILE%\.prime\agent\logs` | Daemon, worker, and client logs |
| `%USERPROFILE%\.prime\agent\sessions` | Saved session transcripts |
| `\\.\pipe\prime-agent-daemon` | Daemon socket (a named pipe, not a Unix socket) |

## Troubleshooting

**`No bash shell found`** — install Git for Windows, or point `shellPath` in
`settings.json` at a `bash.exe` you already have.

**Kernel setup fails.** First launch needs network access to install uv, Python
3.11, `ipykernel`, and the runtime packages. Check
`%USERPROFILE%\.prime\agent\logs` and re-run; to inspect the venv directly, use
`%USERPROFILE%\.prime\agent\kernel-venv\Scripts\python.exe`.

**Antivirus interference.** Real-time scanning can hold handles on freshly
extracted binaries. Provisioning `rg`/`fd` tolerates this — a failed cleanup
leaves a temp directory behind rather than failing the install — but repeated
failures usually mean `%USERPROFILE%\.prime\agent\bin` needs an exclusion.

**Stale background services** — `prime-agent doctor` inspects them and
`prime-agent shutdown --force` stops everything, including sessions in other
windows.

## Not covered

- The `install.sh` bootstrap installer is POSIX-only; use npm on Windows.
- Terminal-dependent behaviour (bracketed paste, image protocols, hyperlinks)
varies by host. Windows Terminal is the best-supported console.
- The kernel forkserver fast path is Linux-only; Windows always cold-starts the
kernel via `python -m ipykernel_launcher`.
- Much of the test suite uses POSIX-only fixtures (shell-scripted stubs, `chmod`
permission bits, symlinks, `/tmp` paths) and does not run on Windows. The
platform-sensitive suites plus an end-to-end kernel smoke test run in the
`Windows` CI workflow.
72 changes: 72 additions & 0 deletions packages/coding-agent/scripts/windows-kernel-smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env node

// End-to-end guard for the Windows kernel path, run from .github/workflows/windows.yml.
//
// Asserts three things the Linux jobs cannot:
// 1. ensureKernelPython resolves to <venv>\Scripts\python.exe and that file exists.
// 2. A second ensureKernelPython call is a cache hit rather than a full rebuild.
// (The venv used to be torn down and rebuilt on every launch because the
// readiness probe looked for a POSIX interpreter path that never exists.)
// 3. A kernel actually starts over ZMQ and runs a cell.

import { existsSync } from "node:fs";
import { join } from "node:path";

const REBUILD_BUDGET_MS = 30_000;

const { ensureKernelPython, getKernelVenvDir, getVenvPythonPath } = await import(
"../dist/core/kernel/bootstrap.js"
);
const { KernelManager } = await import("../dist/core/kernel/index.js");

function fail(message) {
console.error(`FAIL: ${message}`);
process.exit(1);
}

const venv = getKernelVenvDir();
const expectedPython = join(venv, "Scripts", "python.exe");

if (getVenvPythonPath(venv) !== expectedPython) {
fail(`getVenvPythonPath returned ${getVenvPythonPath(venv)}, expected ${expectedPython}`);
}

console.log("bootstrapping kernel venv (cold)...");
const coldStarted = Date.now();
const python = await ensureKernelPython({ onProgress: (message) => console.log(` ${message}`) });
console.log(`cold bootstrap finished in ${Math.round((Date.now() - coldStarted) / 1000)}s -> ${python}`);

if (python !== expectedPython) {
fail(`ensureKernelPython returned ${python}, expected ${expectedPython}`);
}
if (!existsSync(python)) {
fail(`${python} does not exist`);
}

console.log("bootstrapping again (should be a cache hit)...");
const warmStarted = Date.now();
await ensureKernelPython();
const warmMs = Date.now() - warmStarted;
console.log(`warm bootstrap finished in ${warmMs}ms`);

if (warmMs > REBUILD_BUDGET_MS) {
fail(`second bootstrap took ${warmMs}ms; the venv is being rebuilt instead of reused`);
}

console.log("starting kernel...");
const kernel = new KernelManager({ cwd: process.cwd() });
try {
await kernel.start();
const result = await kernel.execute("import platform; print(platform.system())");
console.log(`kernel stdout: ${JSON.stringify(result.stdout)}`);
if (result.status !== "ok") {
fail(`kernel execution status was ${result.status}: ${result.stderr}`);
}
if (!result.stdout.includes("Windows")) {
fail(`kernel did not report a Windows interpreter: ${result.stdout}`);
}
} finally {
await kernel.shutdown().catch(() => undefined);
}

console.log("OK: Windows kernel path is healthy");
1 change: 1 addition & 0 deletions packages/coding-agent/src/cli/daemon-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,7 @@ async function runStart(parsed: ParsedDaemonClientCommand): Promise<void> {
detached: true,
env: process.env,
stdio: "ignore",
windowsHide: true,
});
child.unref();

Expand Down
Loading