Port Windows installer/ncp fixes and a real nomic-embed-text default

- install-windows.ps1: warn about a leftover profile-based ncp() that
  would shadow ncp.cmd; the "press any key to close" footer now skips
  the wait when stdin is redirected (was hanging indefinitely) and
  exits cleanly instead of Stop-Process when it owns the window; pulls
  nomic-embed-text alongside the chat/memory models.
- management/ncp.py: ncp start / start -b bring Ollama up automatically;
  longer timeout + real error message on a slow model warm.
- synapse/nexus_config.py: DEFAULT_EMBED_MODEL, single source of truth
  alongside DEFAULT_CHAT_MODEL/DEFAULT_MEMORY_MODEL.
- synapse/ollama_manager.py: is_available() cached instead of spawning a
  process per /status poll; is_running() timeout dropped 2s -> 0.5s so a
  healthy backend stops reading as dead; embed() reads the new default
  instead of a hardcoded string; de-duplicated serve-env setup.

Ported via bin/publish.sh from NexusOS-jon.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 22:05:37 -05:00
co-authored by Claude Sonnet 5
parent 4a13767ffc
commit ec8f2e08f6
5 changed files with 120 additions and 50 deletions
+40 -27
View File
@@ -9,7 +9,7 @@ import signal
from pathlib import Path
from typing import Any
from .nexus_config import settings, DEFAULT_CHAT_MODEL
from .nexus_config import settings, DEFAULT_CHAT_MODEL, DEFAULT_EMBED_MODEL
from .memory.store import store
OLLAMA_PORT = 11434
@@ -193,6 +193,7 @@ class OllamaManager:
def __init__(self, runtime_dir=None):
self.process = None
self.running = False
self._available = None # see is_available()
self.runtime_dir = Path(runtime_dir) if runtime_dir else Path(__file__).resolve().parent.parent / "runtime"
(self.runtime_dir / "logs").mkdir(parents=True, exist_ok=True)
@@ -241,17 +242,45 @@ class OllamaManager:
except Exception as e:
_log.warning("warm: preload failed: %s", e)
def _serve_env(self, gpu_env: dict) -> dict:
"""Environment for a `serve` we spawn. One definition - this was
duplicated verbatim in two methods, so the Windows carve-out below had
to be fixed in both places or the two paths would disagree."""
env = os.environ.copy()
env["OLLAMA_HOST"] = self._api_base
# Every platform uses the project's own model store. Windows used to be
# exempt, because the installer pulled with a bare `ollama pull` into
# %USERPROFILE%\.ollama and a NexusOS-spawned serve pointed elsewhere
# would not have seen those models. The installer sets OLLAMA_MODELS for
# its pulls now (and migrates an existing store), so the exemption just
# meant Windows kept models somewhere `ncp models` could not see.
env["OLLAMA_MODELS"] = str(settings.models_dir)
env.update(gpu_env)
return env
def is_available(self):
bin_path = _ollama_bin()
try:
subprocess.run([bin_path, "--version"], capture_output=True, check=True, timeout=5)
return True
except Exception:
return False
# ponytail: cached for the life of the process. This spawns a subprocess,
# and /status calls it on every poll - the frontend polls continuously,
# so it was a process spawn per tick to answer a question whose answer
# cannot change without someone installing Ollama. Restart to re-detect.
if self._available is None:
bin_path = _ollama_bin()
try:
subprocess.run([bin_path, "--version"], capture_output=True,
check=True, timeout=5)
self._available = True
except Exception:
self._available = False
return self._available
def is_running(self):
# 0.5s, not 2s: this is a loopback request to a server that is either
# listening or is not. Ollama ships OFF (the user presses Start AI), so
# the "not running" path is the common one and every /status paid the
# full 2s for it - which pushed /status past the 2s client timeout in
# bin/nexus_window.py and made a healthy backend look dead.
try:
r = httpx.get(f"{self._api_base}/api/tags", timeout=2.0)
r = httpx.get(f"{self._api_base}/api/tags", timeout=0.5)
return r.status_code == 200
except httpx.RequestError:
return False
@@ -269,15 +298,7 @@ class OllamaManager:
try:
backend, gpu_env = _detect_gpu_backend()
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
env = os.environ.copy()
env["OLLAMA_HOST"] = self._api_base
# Linux ships a bundled model store under the project. On Windows,
# Ollama is installed system-wide and pulls into its default store,
# so don't override OLLAMA_MODELS or a NexusOS-spawned `serve` won't
# see the models the installer already pulled.
if os.name != "nt":
env["OLLAMA_MODELS"] = str(Path(__file__).resolve().parent.parent / "models")
env.update(gpu_env)
env = self._serve_env(gpu_env)
with open(self.log_file, "w") as log:
self.process = subprocess.Popen(
@@ -318,15 +339,7 @@ class OllamaManager:
try:
backend, gpu_env = _detect_gpu_backend()
_log.info("Starting Ollama service via %s (backend: %s)...", _ollama_bin(), backend)
env = os.environ.copy()
env["OLLAMA_HOST"] = self._api_base
# Linux ships a bundled model store under the project. On Windows,
# Ollama is installed system-wide and pulls into its default store,
# so don't override OLLAMA_MODELS or a NexusOS-spawned `serve` won't
# see the models the installer already pulled.
if os.name != "nt":
env["OLLAMA_MODELS"] = str(Path(__file__).resolve().parent.parent / "models")
env.update(gpu_env)
env = self._serve_env(gpu_env)
with open(self.log_file, "w") as log:
self.process = subprocess.Popen(
@@ -522,7 +535,7 @@ class OllamaManager:
_log.exception("chat error after %.3fs: %s", elapsed, e)
return None
async def embed(self, text: str, model: str = "nomic-embed-text") -> list[float] | None:
async def embed(self, text: str, model: str = DEFAULT_EMBED_MODEL) -> list[float] | None:
"""Return an embedding vector for `text` via /api/embeddings.
Returns None on any failure so callers can fall back to lexical search —