A runaway preview script (sync infinite loop, or a re-render loop
outpacing the bootstrap's own coalescing) had nothing detecting it -
the frame just spun. The bootstrap now heartbeats every second, and the
parent tears the iframe down if it goes _WATCHDOG_MS silent, whatever
the cause.
_coerce_tool_calls recovers a tool call guessed from `content` for
models with no native tool_calls field. That guess is weaker evidence
than the API's own structured field - a model can land on JSON shaped
like a call while only meaning to describe one - so an action tool
recovered this way now always requires approval, even under the
"allow" policy that lets a native tool_calls field run unattended.
Load Sucrase only when a JSX/TSX preview is opened, remove the hand-written transform, and leave subjective render evaluation to the reader while retaining structural fence validation.
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)
- Clear button beside Export deletes every conversation currently listed. It
respects the search filter, so it doubles as "delete these matches"; the
confirm text names which of the two is about to happen. Starts a fresh
conversation id if the active one was among those deleted.
- The separate Start/Stop AI button is gone. The status dot was already
colour-coding the same state and sitting right above it, so clicking it now
toggles Ollama and the tooltip carries the phase text ("loading model…"),
which is where the eye already is during the wait.
- Active nav/conversation items are marked with a blue left border and
weighted text instead of a filled background, and the sidebar is wider so
conversation titles stop truncating.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ported from downstream development. Four independent defects.
1. The memory dump was unrestorable. iterdump() serializes sqlite_vec virtual
tables as a raw INSERT INTO sqlite_master(...) followed by inserts into a
table the replaying connection cannot see, so replaying memory.db.sql died
on "no such table: vec_messages" and left ZERO tables behind. dump_db() now
loads the vec0 extension and filters the derived vec tables out of the
iterdump stream, matched on each statement's target table rather than as a
substring - a chat message whose text mentions vec_messages is an
INSERT INTO "messages" and has to survive.
compare() reported an unreadable dump as "diverged", which read like a real
verdict and made both guards refuse backup AND restore, locking the machine
out of syncing in either direction. Unreadable is now its own verdict.
_extra() compared updated_at against a "" default, but the column is REAL,
so the comparison raises TypeError on the first conversation the other side
lacks - exactly the case it counts. It tests membership first now. The
direction test declared updated_at TEXT, which is why this survived: the
test compared str to str while the field compared str to float.
2. The memory curator invented facts. It attributed the ASSISTANT's words to
the user, wrote absence claims read off the existing-memory block, and added
judgements ("favorite") the user never used. The prompt now scopes the USER
line as the only source, and two deterministic guards drop absence claims
and facts whose distinctive tokens appear nowhere in the user's message -
prompt wording alone did not hold on a 7B curator.
3. _best_vulkan_device scored Mesa's llvmpipe above an integrated GPU, pinning
Ollama to a software rasterizer advertising 31 GiB of "VRAM" - CPU inference
with Vulkan overhead on top. Software rasterizers are dropped.
4. Models.jsx compared catalog names to installed names literally, but Ollama
resolves a bare name to ":latest", so an untagged entry (nomic-embed-text)
read as missing forever and the Required gate never opened. Chatbot.jsx
fetched the model list once on mount although App keeps the page mounted
behind display:none, so a newly pulled model never appeared in the picker
until a full browser reload.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.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>
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>
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>
Ports the ncp PATH work from upstream. Registering ncp as a shell-profile
function failed three ways on Windows: the default Restricted execution policy
blocks the profile itself, profiles don't exist outside PowerShell (cmd, Win+R,
Task Scheduler), and the self-elevating installer writes the admin's profile.
Windows now ships management/ncp.cmd and the installer appends management\ to
the Machine PATH via [Environment]::SetEnvironmentVariable -- never setx, which
truncates PATH at 1024 chars. A .cmd is exempt from the execution policy.
Linux symlinks /usr/local/bin/ncp -> management/nexus-cli.sh, falling back to
the old .bashrc function when sudo is unavailable.
Also renames requirements-wsl.txt to requirements-windows.txt and purges stale
WSL references, including vite.config.js's dev-server comment and
controlpanel.py's "Check WSLg." error string.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>