* fix(windows): desktop backend spawn (#531) + model-aware engine selection (#532)
Two runtime bugs found during end-to-end testing on a clean Windows 11
24H2 Azure VM.
#531 - Desktop "Failed to get response": run_jarvis_command spawned the
backend with .output(), which waits for the process to exit. `jarvis
serve` never exits, so the Tauri command hung forever (the Start button
never resolved); and it ran `uv run jarvis` with no cwd, so in a packaged
install -- where the cwd isn't the checkout -- `jarvis` wasn't found and
the server never started. Now: run from find_project_root(), and for
`serve` spawn detached (.spawn()), drain stderr, and poll /health for
readiness (mirrors start_backend); short commands keep .output().
The server layer itself was verified healthy on Windows (/health and
/v1/chat/completions both 200, localhost included) -- the fault was the
Tauri spawn path.
#532 - "OpenAI client not available" after reboot: when the local engine
is down, get_engine's fallback selected CloudEngine because health() is
True if ANY provider client exists -- without checking the resolved
model's provider has a client. A user with e.g. OPENROUTER_API_KEY and a
gpt-* model then hit the OpenAI path with no client. Add
CloudEngine.can_serve(model) (checks the specific provider client via the
same routing generate()/stream() use) + a default can_serve->True on the
base engine, and make get_engine model-aware so it skips an engine that
can't serve the model -- the user falls through to the helpful "no engine
available / start ollama" message instead.
Tests: engine discovery/cloud/model-matrix + cli serve/ask suites pass
(the one ask_e2e failure is a pre-existing version-banner flake, fails
identically on main). The Tauri crate couldn't be compiled locally (no
GTK/webkit sys-libs in this env); relies on CI.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(engine): cover model-aware engine selection + CloudEngine.can_serve (#532)
#533 added a `model` arg to get_engine and a can_serve() gate but shipped no
tests. Add them:
- get_engine skips a healthy engine that can't serve the requested model
(the cloud-fallback-for-unservable-model case behind #532),
- model=None preserves the legacy model-agnostic selection,
- CloudEngine.can_serve gates on the per-provider client (gpt->OpenAI,
claude->Anthropic, ...), verified empirically.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jon Saad-Falcon <jonsaadfalcon@gmail.com>
Closes#455. @wishwanto on Linux Mint hit two distinct bugs in the desktop launcher that ship together because they share the same boot_backend code path.
Bug A — AppImage hang on "starting server":
When the desktop is shipped as an AppImage on Linux, the AppImage runtime sets LD_LIBRARY_PATH to its temp-extracted lib dir. Children we spawn (uv, ollama) inherit that env, then Python's numpy/cryptography extensions dlopen the AppImage's mismatched libstdc++/libssl versions and python dies silently. Stderr drainer sees immediate EOF → GUI hangs forever.
Fix: new helper prepare_subprocess_for_appimage strips LD_LIBRARY_PATH, LD_PRELOAD, APPIMAGE, APPIMAGE_UUID, APPDIR, ARGV0 when $APPIMAGE is set. Linux-only via #[cfg(target_os = "linux")] — true no-op on macOS/Windows. Called at all 3 spawn sites: ollama sidecar, uv sync, uv run jarvis serve.
Bug B — Desktop force-kills the user's already-running `jarvis serve`:
Old code (post #437) ran fuser -k 8000/tcp / taskkill /PID /F on ANY HTTP response from :8000/health — including 200 OK from a healthy user-launched serve.
Fix: replace the indiscriminate kill with a health-aware decision tree:
* 2xx /health → confirm with a 500ms-apart second probe, then attach (set server/model/ollama ready, return without spawning)
* 503 → user-facing error "wait for engine or stop it"
* other 4xx/5xx → user-facing error with lsof -i :8000 (unix) / netstat (windows) hint
* Err → fall through to normal spawn (unchanged)
Adversarial review caught and fixed 5 real issues pre-commit:
- HIGH: parallel cargo-test env mutation race → APPIMAGE_ENV_LOCK Mutex
- HIGH: 2xx attach left model_ready/ollama_ready false → set both true before return
- MEDIUM: #[allow(unused_variables)] suppressed lint on Linux too → cfg_attr
- MEDIUM: single-probe attach trusted a 2s snapshot → confirmatory second probe
- MEDIUM: /bin/true would silently mislead on Windows → HARMLESS_BIN const per platform
2 new unit tests; CI green on all gates including rust.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes#309.
Three root-cause fixes in `frontend/src-tauri/src/lib.rs`:
1. **Background stderr drainer.** `jarvis serve` was spawned with `stderr(Stdio::piped())` but its 4 KB Windows pipe buffer would fill from the startup log volume before the child could bind its HTTP port — hanging the process mid-startup. Now a tokio task drains stderr from the moment of spawn into a rolling 16 KB tail buffer; the pipe never fills.
2. **`try_wait()` early-exit detection.** The old `wait_for_url` was blind to child crashes — uv-not-on-PATH or extension-import failures would silently waste the full 10-minute timeout. The new `wait_for_jarvis_health` checks the child's exit status each iteration and surfaces the stderr tail with the exit code.
3. **HTTP 503 distinguished from connection-refused.** A 503 means the server bound but the inference engine failed to load (terminal); we now surface the body text immediately rather than polling for 10 minutes.
Adversarially reviewed before commit — caught and fixed a stderr-pipe back-pressure regression on the Ready path before push.
Huge thanks to @xoomarx for the careful #309 repro — without that exact symptom signature ("stuck on Starting api server" on Windows) the pipe-buffer deadlock would have been very hard to identify from the user-visible behavior alone.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The uv-sync failure handling added in #398 was inline in the async
`boot_backend` GUI path, so its logic — exit-code rendering and the
stderr "tail" extraction — could only be verified by running the
desktop app. This extracts the pure logic into three free functions
and adds unit tests, so the message formatting is now covered by
`cargo test` with no GUI / webview runtime needed.
Extracted:
- `uv_sync_stderr_tail(stderr, max_chars)` — last N chars of stderr,
trimmed, on char boundaries. The original inline version used
`.rev().take(N).collect().chars().rev().collect()`; the new version
is `skip(count - N)` which is clearer and equally UTF-8-safe (matters
because Windows consoles emit non-ASCII cp9xx bytes — a byte slice
could split a codepoint).
- `format_uv_sync_failure(root, exit_code, stderr)` — the non-zero-exit
message. Now renders a missing exit code (signal-terminated process)
as "unknown" instead of a misleading "-1".
- `format_uv_sync_spawn_error(root, uv_bin, err)` — the can't-spawn
message.
`boot_backend` now calls these instead of formatting inline.
Tests (7, in `#[cfg(test)] mod tests`):
- tail returns whole string when shorter than the limit
- tail keeps the END (the actionable line), not the spinner-noise start
- tail trims surrounding whitespace
- tail never splits a multi-byte codepoint (500×"é", limit 100 → exactly
100 chars, all "é")
- failure message includes exit code + stderr tail + the actionable
"run uv sync manually" hint
- missing exit code renders as "exit unknown", never "exit -1"
- spawn-error names the uv binary path and repo root
Verified the logic standalone via `rustc --test` (7/7 pass). In CI they
run via `cargo test` in desktop.yml's `validate` job, which already
builds the Tauri crate with the webkit deps and runs on every PR that
touches `frontend/**` — so this changes the existing `cargo check` step
to `cargo test` (a superset: same build coverage, plus the tests).
This doesn't verify the GUI *behavior* (that still needs a human running
the app, or the windows-latest empirical path) — but the error-message
logic that was previously untestable now has automated coverage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses the second half of the Discord support thread on the
"Jarvis server did not become healthy in time" issue (PR #398 fixed
the diagnostic gap; this fixes discoverability of the right install
path so users don't end up there in the first place).
Three changes, all surface improvements:
1. **`README.md`** — explicit Windows section in the Installation
block. Previously, the only Windows mention was a footnote
("Platforms: ... WSL2 on Windows") that came AFTER the `curl … |
bash` install command. Users on PowerShell would copy/paste the
command, get a syntax error, then try to debug bash on Windows.
Now the README clearly says: bash installer is macOS/Linux only;
Windows users have two paths (WSL2 with one-time `wsl --install`
setup, or the desktop .exe from Releases). Both link to the
relevant docs.
2. **`scripts/install/install.sh`** — early bail when running under
Git Bash / MSYS2 / Cygwin (MINGW*, MSYS*, CYGWIN* per `uname -s`).
These environments aren't WSL — `uv` and `git` will install to
Windows-side paths that OpenJarvis can't reach, Ollama integration
silently breaks, and the user gets to debug it 3 minutes into a
doomed install. The bail message points at both the WSL2 setup
command (`wsl --install -d Ubuntu-24.04`) and the desktop .exe
download as alternatives.
Verified the case-match doesn't fire on Linux (`uname -s` →
`Linux`, matches the wildcard fall-through, not the MINGW patterns).
3. **`frontend/src-tauri/src/lib.rs`** — when `resolve_bin("uv")`
can't find uv, the per-OS error message now contains the exact
install command for the user's OS, ready to copy/paste. On Windows
that's the `irm https://astral.sh/uv/install.ps1 | iex` command
Marc kept reposting on the Discord support thread (5/12-5/14).
On macOS/Linux it's the standard `curl | sh` installer.
The previous generic "Install it from https://astral.sh/uv" left
users guessing whether to use winget, scoop, pip, or the official
installer — which is exactly the confusion the Discord thread
captured.
None of these are root-cause code fixes (Discord users' uv installs
fail for environment-specific reasons we can't diagnose remotely),
but together they remove the three biggest friction sources we saw:
copy-paste install command that can't possibly work, doomed git-bash
installs that fail mysteriously, and missing exact install commands
when uv isn't found.
The Tauri change ships in the desktop binary; the README change is
visible immediately on the repo page; the install.sh change reaches
users via openjarvis.ai (when it's restored) and via the GitHub-raw
fallback from PR #398.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses #331 (multiple Windows users hit "Jarvis server did not
become healthy in time" with no actionable detail).
The boot sequence ran `uv sync` with **both** stdout AND stderr piped
to `/dev/null` and discarded the exit code (`let _ = …`). When
`uv sync` failed for any reason — Windows PATH/permission issues,
network problems, lockfile conflicts, stale `.venv/` — the user saw
nothing useful. The boot proceeded to `uv run jarvis serve` in an
under-provisioned venv, then waited the full 600-second health-check
window before showing a generic "did not start" message with no
hint of what actually went wrong.
Fix: capture stderr, check the exit status, and surface a useful
error to the user **before** the long server-start wait, including:
- The exit code
- The last ~800 chars of `uv sync` stderr (where the diagnostic
message usually lives)
- A concrete next step (open a terminal and run `uv sync --extra server`
manually for full output)
Also updated the status detail message to "Installing dependencies
(uv sync — may take 1-2 min on first boot)..." so users on slow
connections don't restart the app thinking it's stuck.
Discord support thread (5/12-5/14) shows the same pattern across
multiple Windows users (@ItsVoyage, @Mystic_irl, @doevud, @Sainthood):
all stuck on "Starting api server" / "did not become healthy in time"
for 4+ minutes, with the actual root cause turning out to be a uv
installation issue that the discarded stderr would have surfaced
immediately. The community workaround (uninstall, install Feb
pre-release, run a magic PowerShell `irm` command for uv) is the
right diagnosis applied without diagnostic output — this commit
makes that diagnostic output visible.
Note: this fix lands in the desktop binary's source (`frontend/src-tauri/src/lib.rs`).
Users running the v1.0.1 desktop binary won't see the improved
error message until the desktop release is re-cut from this branch.
Until then, the workaround documented in this PR's `openjarvis.ai`
fallback section (curl the install.sh from GitHub raw) gets new
users past the install step.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: merge desktop/ into frontend/, eliminate duplicate Tauri scaffolding
The project had two overlapping directories: desktop/ (Tauri Rust backend +
stale React components) and frontend/ (real React app + dead Tauri stub).
This consolidates everything under frontend/:
- Move desktop/src-tauri/ → frontend/src-tauri/ (the real 1,720-line Rust
backend with Ollama sidecar, backend lifecycle, cloud keys, overlay, etc.)
- Preserve 9 old desktop React components in frontend/src/components/Desktop/
(excluded from TS build — APIs have drifted, kept for future integration)
- Delete the old frontend/src-tauri/ stub (246 lines, never compiled)
- Delete desktop/ entirely
- Fix tauri.conf.json frontendDist path (../../frontend/dist → ../dist)
- Update CI workflow, bump script, .gitignore, and docs paths
- Rename setup/ → Setup/ for consistent PascalCase component directories
* feat: add memory UI, settings, and fix memory API routes
- Add Memory tab to Data Sources page with stats, search, index path,
and manual store functionality
- Add Memory section to Settings page with backend picker, context
injection toggle, and parameter sliders (top_k, min_score, max_tokens)
- Add memory API functions to frontend (getMemoryStats, searchMemory,
storeMemory, indexMemoryPath, getMemoryConfig)
- Fix backend /v1/memory/* routes to use app-level memory backend
instead of creating fresh SQLiteMemory instances per request
- Add GET /v1/memory/config and POST /v1/memory/index endpoints
- Gracefully handle missing Rust backend (return defaults instead of 500)
- Fix nested scroll in Agents Interact tab (use viewport-relative height)
* fix: memory API routes, dialog plugin, and UI polish
- Fix memory API: use backend.retrieve() not .search(), .count() not
.stats() to match actual SQLiteMemory interface
- Handle None backend gracefully in index endpoint (503 instead of crash)
- Expand ~ in index path (expanduser + resolve)
- Install @tauri-apps/plugin-dialog for native folder picker in Tauri
- Browse button only shows in Tauri (browser can't get absolute paths)
- Redesign Memory tab: proper cards, color-coded search scores, two-column
layout for index/store, loading spinners, accent gradient on stats card
---------
Co-authored-by: Jon Saad-Falcon <41205309+jonsaadfalcon@users.noreply.github.com>
The project had two overlapping directories: desktop/ (Tauri Rust backend +
stale React components) and frontend/ (real React app + dead Tauri stub).
This consolidates everything under frontend/:
- Move desktop/src-tauri/ → frontend/src-tauri/ (the real 1,720-line Rust
backend with Ollama sidecar, backend lifecycle, cloud keys, overlay, etc.)
- Preserve 9 old desktop React components in frontend/src/components/Desktop/
(excluded from TS build — APIs have drifted, kept for future integration)
- Delete the old frontend/src-tauri/ stub (246 lines, never compiled)
- Delete desktop/ entirely
- Fix tauri.conf.json frontendDist path (../../frontend/dist → ../dist)
- Update CI workflow, bump script, .gitignore, and docs paths
- Rename setup/ → Setup/ for consistent PascalCase component directories