Brings the public tree back in line with the development repo after several
weeks of drift caused by a stale publish include list.
New:
- In-app update path: GET /update/check compares the checkout against
origin/main and POST /update/apply runs `ncp upgrade` detached (pull,
rebuild, restart). The sidebar shows the version, checks on click, and
offers an "update available" pill.
- Projects: a project workspace groups chats and RAG documents, with
per-project instructions and document retrieval scoped to the active
project. Replaces the standalone Documents page.
- modules/: auto-discovered feature plugins (mail, network) with their
frontend counterparts and tests.
- Memory curation runs in-process (synapse/memory/curator.py) on the chat
model when a conversation goes idle. The separate memory service on :8001
is gone, along with the launcher lines that started it.
Also: the KDE theme, panel and Promethean terminal assets, the full test
suite, and VERSION 1.2.0.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
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.
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>
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>