get_ai_agents_status used to probe all six agent CLIs (claude_code,
codex, opencode, pi, gemini, kiro) sequentially during cold start. For
each agent missing from PATH it would fall through to three login-shell
spawns (`<shell> -lc 'command -v <agent>'`), each evaluating the user's
full zsh init. On a machine with no agents installed this added ~5 s to
cold start.
The result is not on the first-paint render path. It feeds the status
bar AI badge, the AI tab in settings, and the AI agents onboarding
prompt — all of which are gated behind aiFeaturesEnabled and the
onboarding flow, so they happily render nothing while the probe is
pending.
Three additive changes:
* useAiAgentsStatus now takes an `enabled` option. When false (AI
features off, or running in a detached note window) the probe never
fires. App.tsx passes `aiFeaturesEnabled && !noteWindowParams`.
* When enabled, the invoke call is deferred via requestIdleCallback
(with setTimeout(0) fallback because WKWebView lacks the API) so it
lands after first paint instead of during it.
* Rust get_ai_agents_status is now async and fans the six check_cli()
calls out under tokio::task::spawn_blocking + tokio::join!. Per-agent
check_cli() signatures stay sync, so run_agent_stream callers are
unaffected and the per-agent unit tests need no changes.
Cold-start measurements on this machine (no agents on PATH, dev mode):
Baseline (main, AI on): cascade settles at t+15.5 s
Patched, AI features off: t+5.0 s
Patched, AI features on: t+6.0 s,
AI status arrives ~1.6 s after first
paint, fully off the critical path.
Fixes: https://github.com/refactoringhq/tolaria/issues/726
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The test's name says "report_no_remote" but its push assertion still
expected the pre-fix behavior ("error"). After 6d97335d made has_remote
URL-aware, git_push on a vault with no URL-bearing remote short-circuits
with "no_remote" — matching the test name and the pull assertion above.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sibling to the has_remote fix on this branch (6d97335d). Plain `git remote`
lists remote names defined in any config layer, so a [remote "origin"]
section inherited from ~/.gitconfig (e.g. with `prune = true`) made
list_remotes return ["origin"] for fresh local vaults. That broke
git_add_remote (returned "already_configured") and silently neutered
disconnect_all_remotes.
Reuse remote::remote_line_has_url to parse `git remote -v` output and
keep only remotes with a non-empty URL column. Dedupe by name across
the (fetch)/(push) line pair.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`has_remote()` ran bare `git remote`, which lists every remote name from
any config layer — so a `[remote "origin"]` section inherited from the
user's `~/.gitconfig` (e.g. with only `prune = true`) made every vault
look remote-backed. `git_pull` then ran `git pull --no-rebase`, hit
"no tracking information", and the status bar surfaced "Sync failed".
Parse `git remote -v` and require at least one line with a non-empty URL
column. Mirror the guard on `git_push` so direct callers return
`no_remote` instead of issuing a doomed push. Handle `no_remote`
explicitly in `useAutoSync.performPull` / `pullAndPush` / `handlePushResult`
so the frontend short-circuits the push step and keeps a neutral status.
Fixes: https://github.com/refactoringhq/tolaria/issues/734
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Create Folder action ignored the sidebar selection and always
wrote to the vault root. Now it threads the selected folder as
parent_path to the backend, and expands the parent in the tree so
the new child is visible without re-clicking.
Fixes: https://github.com/refactoringhq/tolaria/issues/728
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes#695
`where claude` on Windows returns both the npm-generated POSIX shell
wrapper (extensionless) and the Windows batch shim (`claude.cmd`)
side-by-side. The existing `path_from_successful_output` helpers in
`claude_cli.rs` and `codex_cli.rs` picked the first existing line,
which gave the POSIX wrapper - Windows tried to load it as a PE32,
`CreateProcessW` returned ERROR_BAD_EXE_FORMAT (193), and the AI chat
panel surfaced "%1 is not a valid Win32 application."
Mirror the fix that landed for Gemini in `gemini_discovery.rs`: when
`cfg!(windows)`, filter candidates through the existing shared utility
`cli_agent_runtime::has_windows_cli_extension` so .bat/.cmd/.com/.exe
win over extensionless POSIX scripts. Non-Windows behavior is unchanged.
Added a regression test in each file matching the
`windows_path_lookup_prefers_cmd_shim_over_extensionless_npm_script`
pattern from gemini_discovery.rs.
Fixes#705
GUI launches of the AppImage on Linux do not inherit the shell PATH, so
`find_on_path` and `find_in_user_shell` cannot reach Homebrew-on-Linux's
Node install. Add the canonical Linuxbrew paths to `fallback_node_paths`
and to `node_binary_candidates_for_home`; `.is_file()` filtering keeps
machines without Linuxbrew unaffected.
Tolaria's MCP server is pure ESM with only standard `node:fs|path|http|child_process`
imports and pure-JS dependencies, so Bun can execute it identically to Node.
Until now `find_node()` was the single entry point for spawning the WebSocket
bridge and writing external AI tool config — users with Bun but no Node would
hit "node not found in PATH or common install locations" and lose access to MCP
tools entirely.
Introduce `find_mcp_runtime()` which returns the first verifying runtime,
preferring Node 18+ when present and falling back to Bun 1+. The generic
PATH and login-shell lookup helpers (`find_on_path`, `find_in_user_shell`,
`lookup_command`, `lookup_paths`) are now parameterised by command name so
both runtimes share the same machinery. Bun candidates cover `~/.bun/bin/bun`,
mise/asdf/proto shims, Homebrew, and `%USERPROFILE%\.bun\bin\bun.exe` on
Windows. The Codex CLI and Windows .cmd-shim resolution paths keep using the
strict `find_node()` since they specifically need Node.
`spawn_ws_bridge_with_paths`, `mcp_config_snippet`, and `register_mcp` now
resolve through `find_mcp_runtime` so the runtime that gets registered into
`~/.claude.json`, `~/.gemini/settings.json`, `~/.cursor/mcp.json`, etc.
matches the one actually present on the machine.
Locale copy updated from "nodeRequirement" to "runtimeRequirement" across
all 15 catalogs to reflect "Node.js 18+ or Bun 1+".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>