Close SQLite handles safely on Windows, clean orphaned vectors, normalize Ollama endpoints, and surface model errors without leaking reasoning tags.
Co-authored-by: Cursor <cursoragent@cursor.com>
DoS/quota guardrails for the unauthenticated local APIs:
* Body-size middleware rejects oversized requests (Content-Length) before they
are buffered/base64-decoded (NEXUS_MAX_REQUEST_MB, default 32).
* Document upload enforces a decoded-byte cap (NEXUS_MAX_UPLOAD_MB, default 20)
and a PDF page-count cap (NEXUS_MAX_PDF_PAGES, default 500) as backstops for
chunked bodies and pathological files.
* A counter-based in-flight limiter bounds concurrent chats and document
ingests (NEXUS_MAX_CONCURRENT_CHATS/UPLOADS), returning 429 when saturated;
the chat slot is held for the whole SSE stream and released on completion or
client disconnect.
* /models/pull gains an opt-in allowlist (NEXUS_MODEL_ALLOWLIST); empty by
default so behaviour is unchanged, otherwise a bare repo name permits all its
tags.
Co-authored-by: Cursor <cursoragent@cursor.com>
Two tool/agent-layer hardening changes:
* fetch_url now resolves the target host and refuses to connect if any
resolved address is loopback, private (RFC1918/ULA), link-local (incl. the
169.254.169.254 cloud-metadata endpoint), multicast, reserved, or
unspecified. IPv4-mapped IPv6 is unwrapped first, and the guard re-runs on
every redirect hop so a public URL cannot 302 its way to an internal target.
* /chat/approve now requires a single-use token minted when the stream pauses
for approval and delivered only in that stream's tool_request event, compared
in constant time. Previously the pending approval was keyed solely on a
client-supplied conversation_id, so anyone who could enumerate a
conversation_id could approve another client's pending action.
The frontend threads the token from the tool_request event into the approve
call.
Co-authored-by: Cursor <cursoragent@cursor.com>
The Synapse backend and memory service bound 0.0.0.0 with wildcard CORS and no
auth, exposing the full unauthenticated admin/data API to the LAN. Default the
uvicorn bind to 127.0.0.1 (NEXUS_BIND_HOST override), scope CORS to known local
origins instead of "*", and add TrustedHostMiddleware to reject foreign Host
headers (which defeats DNS-rebinding, something same-origin CORS cannot stop).
NEXUS_ALLOWED_HOSTS / NEXUS_ALLOWED_ORIGINS allow opt-in LAN exposure, intended
to be paired with real authentication.
Co-authored-by: Cursor <cursoragent@cursor.com>
New Modules entry in Flagship features + Architecture describes the
modules/ plugin auto-discovery. Chat/vision/voice, action tools,
Layout, and Configuration reworded from prose/tables into bullets for
scannability.
Bugs go through this repo's Issues tab with an ncp doctor ask; feature
requests stay pointed at NexusOS-requests so the bug tracker doesn't
mix with the planning backlog.
core.fileMode=false (repo-local or a machine-wide /etc/gitconfig) can
silently drop the +x bit on every tracked script during checkout, which
then surfaces later as a confusing "Permission denied" on whatever
script happens to run next rather than as an obvious failure up front.
ensure_exec_bits() re-applies +x from `git ls-files -s` (mode 100755)
right after every pull, so a restore is self-healing regardless of cause.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
requirements-amd.txt/requirements-nvidia.txt were pulling a multi-GB
torch wheel by default even though nothing in synapse/ imports torch,
transformers, accelerate, bitsandbytes, or PySide6 - dead weight that
made the pip batch fragile (one failed download could take unrelated
base deps down with it on a slow connection). Split the unused ML/GUI
stack out of requirements-base.txt into a new opt-in requirements-ml.txt,
and dropped the torch lines from the AMD/NVIDIA overlays and generator.
Also: recreate the venv if it exists but pip is missing, instead of
silently reusing a half-built one (ensurepip can fail during venv
creation and leave an interpreter with no pip).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Newer distros drop the python3-venv transitional package once it no longer
resolves to a real pythonX.Y-venv candidate, which crashed sync.py's venv
creation with a raw traceback. Prep now resolves the exact versioned
package via apt-cache, and skips xfce4-genmon-plugin/plank/blueman on
boxes with no xfconf-query so a non-XFCE box doesn't get noisy "unable to
locate package" failures for a panel it'll never run. venv_python() also
fails with an actionable message instead of an uncaught CalledProcessError.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Distilled from the private repo's playbook set with names and personal
specifics genericized. Reuses the same IDs as their private counterparts,
matching the existing main.yaml.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ported from the private repo (bin/publish.sh's README-only path): adds a
Flagship features section, reorders Quick start (Linux first), splits out
a Promethean section, corrects the ncp cross-platform parity note, and
replaces the stale in-README Roadmap with a pointer to NexusOS-requests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ported from the private repo via bin/publish.sh, plus a manual catch-up
on files that had drifted out of sync before today:
- launch_nexus.ps1: health-check based restart decisions instead of a
bare port-listen check (a wedged leftover process squatting a port
used to look "already running" and block the real service from
starting), a script-path quoting fix for Start-Process, hidden
console via a wscript.exe wrapper (bin/launch_nexus_hidden.vbs), and
a taskbar/window icon for the native app window.
- Sidebar: slim icon+text nav rows instead of bulky bordered buttons,
tighter spacing throughout.
- Settings: full-width layout, a Vite dev-server Start/Stop toggle
(synapse/frontend_manager.py + /frontend/* endpoints), and the
Linux-only Icon Branding section now gated on the new /status
`platform` field instead of always rendering.
- Chatbot: a Think toggle next to the model picker, so extended
thinking can be flipped without leaving the chat page.
- management/ncp.py: faster start/stop polling (0.25s steps instead of
1s), Vite no longer blocks `ncp start` on Linux and is skipped
outright on Windows.
Note: the private repo also has a Mail (IMAP/SMTP) feature; it's
intentionally not included here, so the Mail-only pieces of main.py,
App.jsx, and requirements-windows.txt were left out of this port.
Models page redesign: tabs (Required/Recommended/Installed) with a
multi-column card grid, required models (memory curator + embeddings)
surfaced first and gating the rest until both are installed. Adds a
qwen2.5:14b tier to the hardware-fit catalog for high-VRAM machines.
Installer and restore messaging updated to match. Also declares pytest
in requirements-base.txt so bin/check.sh's test suite is reproducible
on a fresh venv.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Models.jsx clipped the whole card with overflow:hidden while only the
"Available Models" section (never visible when the hardware-recommended
list alone filled the card) had its own scroll — the installed-models
list was unreachable with no scrollbar. The whole card now scrolls as
one region instead.
install-windows.ps1's original (non-elevated) window printed "Requesting
administrator privileges..." and exited immediately, so all real
progress and the "installed!" banner only ever appeared in the separate
elevated window — making the original window look like it silently
quit. It now waits (-Wait -PassThru) and reports success/failure itself.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Opt-in repair pass: reinstall Python deps on import failure, npm install when
node_modules missing, npm run build when dist missing, fetch Ollama binary.
Plain doctor stays read-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Debian apt ships EOL Node 18 which crashes Vite 8. Prep stage installs Node 20
from NodeSource when node is missing or <20; package.json declares engines.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Settings "Auto model routing" picks which installed model fires for chat vs
coding intent when no model is pinned (auto_chat_model / auto_code_model).
_auto_select_model honors the remap; _MODEL_PREFERENCE["code"] prefers real
coder models first.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows installer no longer auto-downloads models; points to the Models tab.
synapse/hardware.py detects RAM + best-effort VRAM and a curated catalog;
GET /models/recommended annotates each model with fit (gpu/ram/no); the Models
page shows detected RAM/VRAM with fit badges and per-row Pull buttons.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3-way action_tool_policy (off/ask/allow). In "ask", the chat stream stays
open and the tool loop awaits approval: emits event:tool_request, the UI
shows Approve/Deny, POST /chat/approve resumes the same stream. Declined
actions return a denied result; a timeout denies.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Conversations bind to a project on creation; RAG scopes to the
conversation's project, not the global setting.
- Action tools (web_search/fetch_url/remember) are withheld unless
allow_action_tools is enabled (off by default). Settings toggle.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Projects/workspaces: documents grouped into projects; chat RAG scopes to the
active project. Switcher in the Documents page.
- Agentic action tools: web_search, fetch_url, and remember (first write tool),
allowlist-gated per playbook.
- Local Whisper STT (faster-whisper, no torch): on-device dictation replacing
the browser Web Speech API. POST /stt + GET /stt/status; browser fallback.
- Vector index extended to conversation recall (message_vectors), with the
brute-force cosine scan kept as the fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Chunker: char overlap across boundaries + hard-split of oversized paragraphs.
- Retrieval knobs: rag_top_k / rag_min_score in settings + Settings UI.
- Vector index: sqlite-vec ANN over document embeddings, dual-written and
backfilled, with brute-force cosine as the guaranteed fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Upload endpoint (base64 JSON, no multipart dep): extracts text from
pdf/docx/txt/md via pypdf + python-docx, then runs the existing
chunk/embed pipeline. Documents page uploads files straight through.
- Citations: the chat stream emits an SSE `sources` event listing the
documents that fed the answer; the UI shows them as chips under the reply.
- Deps: pypdf, python-docx (both pure-Python, Windows-safe).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
install-windows.ps1 now removes everything under assets\ except
NexusOS.ico (the only file anything on Windows reads) right after the
desktop shortcut step - the GTK/KDE/XFWM4/Plymouth theme has nothing to
theme without a matching desktop environment under it.
Ported via bin/publish.sh from NexusOS-jon.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- 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>
Process scope for the current window, LocalMachine RemoteSigned so it persists
and ncp works from later shells without -ExecutionPolicy Bypass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ctrl+C in PS 5.1 kills the whole script, so the advertised way to skip the
download was also the way to abort the install before its final steps. Now a
'Download them now? [Y/n]' prompt, so declining continues to the end. The
default-model seed moves above the pull - it only writes a settings row and was
being lost along with the download. A test forbids offering Ctrl+C again.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows pulled into %USERPROFILE%\.ollama while ncp and doctor looked in
nexus-core\models. The installer now sets OLLAMA_MODELS for its pulls and moves
an existing default store across first (instant on the same volume, no
refetch), and ollama_manager sets OLLAMA_MODELS on every platform. The env block
was duplicated in two methods, which is why the carve-out existed twice; folded
into _serve_env().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'ncp doctor' reported a missing Ollama binary on Windows, where winget installs
it to %LOCALAPPDATA%\Programs and on PATH rather than into the repo; the same
assumption made 'ncp models install' refuse to run there. Adds ollama_bin()
mirroring _ollama_bin() in ollama_manager.py.
Also renames wait_for_port's early message to READY - 'already running' for a
service the same command just started reads like a stale process.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The backend logged 200 OK for polls the client had already timed out on.
is_running() waited 2s for an Ollama that ships OFF (now 0.5s), is_available()
spawned 'ollama --version' every call and that command blocks ~5s when Ollama is
wedged (now cached), and both ran synchronously inside an async def, stalling the
event loop on every poll while the UI polls continuously (now to_thread).
Measured with Ollama's port blackholed: /status 5.89s -> 0.64s, concurrent GET /
stalled -> 0.06s. Poll timeout in nexus_window.py raised 2s -> 5s for margin.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ctrl+C is how you quit 'ncp web', which blocks on the UI window. It printed a
six-frame traceback ending in WaitForSingleObject, reading as a crash. Exits 130
instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The two black terminals had two causes: uvicorn --reload (a dev flag whose
reloader spawns the server as a child, and Windows gives a console window to a
child of a console-less parent), and DETACHED_PROCESS in launch(), which is
exactly the console-less condition that triggers it. Drops --reload here
(launch_nexus.sh keeps it for the Linux dev loop) and uses CREATE_NO_WINDOW.
The window now opens immediately on an inline 'Loading Nexus core services' page
and navigates to the app once /status answers, instead of waiting 40s offscreen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A process reads PATH once, at startup, so the shell the installer ran in can
never resolve ncp. The ending now says that and closes the window. Uses
Stop-Process -Id $PID rather than 'exit', which only closes the window when the
host was started with -File.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
'Press Enter to close' closed nothing when run from an existing shell, and
closing unconditionally would kill the caller's session and any transcript in
it. Adds an -OwnsWindow switch passed only to the elevated run the script starts
for itself; that run may close its own window, a guest run returns quietly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PowerShell resolves ExternalScript (.ps1) ahead of Application (.cmd), and both
lived in the directory the installer puts on PATH -- so in PowerShell `ncp` ran
the .ps1 and was execution-policy-bound again, the exact thing the .cmd exists
to avoid. Its other justification (giving ncp to pwsh on Linux) stopped being
true once /usr/local/bin/ncp existed: pwsh runs a PATH symlink to a shell script
as an Application. A test now prevents the file coming back.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Piping winget to Out-Host defeats its carriage-return redraw, so every spinner
and progress-bar frame lands as its own line. Filters the frames, keeps the
messages. Block characters spelled by code point to keep the file ASCII.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
taskkill writes to stderr when the process isn't running, and with
$ErrorActionPreference = Stop PowerShell 5.1 promotes native stderr to a
terminating error (2>$null doesn't prevent it). The normal case -- Ollama not
running -- threw and printed a failure warning for work that had succeeded.
Uses Get-Process | Stop-Process instead.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
winget draws its progress bar with UTF-8 block characters, but PowerShell
decodes native output using the console codepage (437), turning each block into
three Latin characters and burying the real messages. Sets
[Console]::OutputEncoding to UTF-8 in install-windows.ps1, and chcp 65001 in
ncp.cmd for the check marks and em-dashes ncp.py prints.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>