
### Added
- **Models registry sync** — new `POST /api/config/models/sync` endpoint that fetches the upstream models registry and merges new providers/models into the local `~/.jean2/models.json`. Supports two modes: `merge` (default, adds missing providers/models without touching existing ones) and `override` (replaces the entire local config with upstream). Returns a `SyncResult` with counts of added providers and models. The upstream URL defaults to the `models.json` in the GitHub repo and can be overridden with `JEAN2_MODELS_REGISTRY_URL`.
- **`jean2 models sync` CLI command** — syncs models from the upstream registry directly from the command line. Use `--override` to replace local models entirely instead of merging. Works without the server running (reads/writes `~/.jean2/models.json` directly). Run `jean2 models sync --help` for usage.
- **Built-in client auto-launch** — the server now automatically installs and launches the `@jean2/client` npm package as a child process on startup. On first start, it installs the latest version from npm into `~/.jean2/client/` using `@npmcli/arborist`. The client launches immediately, then checks for updates in the background. If a newer version is found, it stops the running client, reinstalls, and relaunches. Controlled by `JEAN2_CLIENT_ENABLED` (default `true`) and `JEAN2_CLIENT_PORT` (default `3774`).
- **Client launcher service** (`packages/server/src/services/client-launcher.ts`) — new service that manages the client lifecycle: install via Arborist, spawn via hidden `_client` command, version tracking in `~/.jean2/client/.client-manifest.json`, npm registry update checks, and graceful stop/relaunch.
- **`jean2 open` CLI command** — opens the built-in client in the default browser using native platform commands (`open` on macOS, `cmd /c start` on Windows, `xdg-open` on Linux).
- **Hidden `_client` CLI command** — internal command that re-implements the client's static file server using `node:http` inline. The server binary spawns itself with `jean2 _client --cli-path X --port Y` to serve the client SPA, reusing the embedded Bun runtime instead of needing a separate Node.js process.
- **`client` feature flag in `/api/info`** — the server info endpoint now reports `{ features: { client: true/false } }` so clients and tools can detect whether the built-in client is enabled.
- **Client URL in daemon output** — `jean2 start` now prints `Client running at http://localhost:3774` (when enabled) alongside the server URL.
- **`getClientDir()` path** — new centralized path `~/.jean2/client/` for the client installation directory.
- **`JEAN2_NPM_REGISTRY` env var** — override the npm registry URL used for client installs (default `https://registry.npmjs.org`).
- **`JEAN2_NPM_CACHE_DIR` env var** — override the npm cache directory used during client installs.

### Changed
- **Server startup now includes client lifecycle** — `startServer()` in `index.ts` creates a `ClientLauncher`, ensures the client is installed, launches it, and kicks off a background update check. The client process is stopped on server shutdown. Client launch failures are logged as warnings and do not prevent the server from starting.

### Fixed
- **Self-update now replaces the correct binary on Windows** — the updater was using a hardcoded path (`~/.jean2/bin/jean2.exe`) instead of the actual running binary location (`process.execPath`). If the binary was installed or running from a different location, the update would download successfully but replace the wrong file (or no file at all).
- **Self-update on Windows now works correctly using the rename trick** — Windows locks running `.exe` files, preventing direct overwrite. The previous approach tried to wait for the process to exit and then retry replacement via a detached PowerShell script, which was unreliable (the detached process often never ran). The new approach exploits the fact that Windows **allows renaming** a running `.exe`: the running binary is renamed to `.old`, the new binary is moved into the original path immediately (while still running), and then a simple `cmd.exe` background process handles cleanup and daemon restart. No retry loops, no PowerShell, no waiting for file locks.
- **SPA asset path resolution with relative base** — the server's inline HTTP server and client CLI both now detect missed asset requests (e.g. `/sessions/assets/foo.js` caused by `base: './'` on nested routes) and redirect them to `/assets/foo.js` before falling back to `index.html`. This fixes MIME type errors when refreshing on SPA routes.
- **Switching sessions during compaction no longer produces a false failure** — `reconcileSessionCompaction()` (called on `session.resume`) previously assumed that any session with `compacting=true` was stuck after a crash and would forcibly clear the flag and persist a "Compaction interrupted" failure message. This was wrong when compaction was genuinely in-flight (the user had simply switched to another session while it was running). The compaction executor now tracks in-flight compactions in an in-memory set (`activeCompactionSessions`), and reconciliation checks this set before interfering with a running compaction. This also prevents double compaction — a second manual compact request while one is already running now returns `skipped` instead of launching a parallel compaction that would produce duplicate results.
