feat: sync with upstream — v1.2.0, in-app updates, Projects, modules

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)
This commit is contained in:
janvanwan
2026-08-25 09:13:55 -05:00
parent fe5d18afa7
commit 42eaed647a
88 changed files with 4280 additions and 1888 deletions
+89
View File
@@ -155,6 +155,66 @@ async def _remember(text: str = "", section: str = "General", **_) -> str:
return json.dumps({"saved": text, "section": section or "General"})
# --- Repo file access (read-only, scoped to PROJECT_ROOT) -------------------
# Paths never leave the repo: every request is resolve()d and checked against
# PROJECT_ROOT, which also kills symlink escapes. _DENIED covers the parts of
# the tree that are either secrets, private data, or multi-GB noise.
_DENIED = {
".git", ".env", "Promethean", "node_modules", "models", "ollama",
"runtime", "dist", "__pycache__", ".git-credentials",
}
_READ_MAX = 60_000
def _repo_path(rel: str) -> "tuple[object, str | None]":
"""Resolve a repo-relative path. Returns (path, error-string)."""
from .nexus_config import PROJECT_ROOT
rel = (rel or "").strip().lstrip("/")
if not rel:
return None, "path is required"
target = (PROJECT_ROOT / rel).resolve()
if not target.is_relative_to(PROJECT_ROOT):
return None, "path escapes the project root"
parts = set(target.relative_to(PROJECT_ROOT).parts)
if parts & _DENIED or target.name.endswith((".db", ".db.sql", ".pem", ".key")):
return None, f"{rel} is not readable"
return target, None
async def _read_file(path: str = "", **_) -> str:
target, err = _repo_path(path)
if err:
return json.dumps({"error": err})
if not target.is_file():
return json.dumps({"error": f"{path} does not exist"})
try:
text = target.read_text(encoding="utf-8", errors="replace")
except OSError as e:
return json.dumps({"error": f"cannot read {path}: {e}"})
return json.dumps({
"path": path,
"truncated": len(text) > _READ_MAX,
"content": text[:_READ_MAX],
})
async def _list_files(pattern: str = "", **_) -> str:
"""Glob the repo so the model discovers real paths instead of inventing them."""
from .nexus_config import PROJECT_ROOT
pattern = (pattern or "**/*.py").strip().lstrip("/")
hits = []
for f in PROJECT_ROOT.glob(pattern):
if not f.is_file():
continue
target, err = _repo_path(str(f.relative_to(PROJECT_ROOT)))
if err:
continue
hits.append(str(f.relative_to(PROJECT_ROOT)))
if len(hits) >= 200:
break
return json.dumps(sorted(hits))
# name -> (schema, callable). Schema is the OpenAI/Ollama function-tool format.
REGISTRY: dict[str, tuple[dict, Callable[..., Awaitable[str]]]] = {
"search_memory": (
@@ -197,6 +257,35 @@ REGISTRY: dict[str, tuple[dict, Callable[..., Awaitable[str]]]] = {
},
_list_models,
),
"read_file": (
{
"type": "function",
"function": {
"name": "read_file",
"description": "Read a source file from the NexusOS repository. Path is relative to the project root, e.g. 'synapse/main.py'.",
"parameters": {
"type": "object",
"properties": {"path": {"type": "string", "description": "repo-relative file path"}},
"required": ["path"],
},
},
},
_read_file,
),
"list_files": (
{
"type": "function",
"function": {
"name": "list_files",
"description": "List files in the NexusOS repository matching a glob, e.g. 'synapse/**/*.py' or 'interface/web/src/*.jsx'. Use this to find real paths before reading.",
"parameters": {
"type": "object",
"properties": {"pattern": {"type": "string", "description": "glob relative to the project root"}},
},
},
},
_list_files,
),
"search_documents": (
{
"type": "function",