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:
@@ -149,3 +149,60 @@ def test_tool_loop_degrades_when_model_returns_no_dict():
|
||||
statuses = asyncio.run(_drain(_run_tool_loop(_NoToolManager(), messages, "m", [{}], None, None)))
|
||||
assert statuses == [] # no tool ran
|
||||
assert messages == before # untouched -> falls back to a plain stream
|
||||
|
||||
|
||||
def test_read_file_stays_inside_the_repo():
|
||||
"""The repo-file tools are the fix for the model inventing paths like
|
||||
`nexus/nlp.py`; the deny-list is what keeps them from reading secrets."""
|
||||
import json
|
||||
|
||||
def read(p):
|
||||
return asyncio.run(tools._read_file(p))
|
||||
|
||||
assert "escapes" in read("../../etc/passwd")
|
||||
# a leading slash is treated as repo-relative, so it lands nowhere real
|
||||
assert "root:" not in read("/etc/passwd")
|
||||
assert "required" in read("")
|
||||
# private data and heavy trees are refused even though they're in-repo
|
||||
for denied in ("synapse/memory/memory.db", ".git/config", "Promethean/pyvenv.cfg"):
|
||||
assert "not readable" in read(denied), denied
|
||||
assert "does not exist" in read("nexus/nlp.py")
|
||||
assert "PROJECT_ROOT" in json.loads(read("synapse/nexus_config.py"))["content"]
|
||||
|
||||
|
||||
def test_list_files_globs_the_repo_without_leaking_denied_paths():
|
||||
import json
|
||||
|
||||
hits = json.loads(asyncio.run(tools._list_files("synapse/**/*")))
|
||||
assert "synapse/main.py" in hits
|
||||
assert not [h for h in hits if h.endswith(".db") or "__pycache__" in h], hits
|
||||
|
||||
|
||||
def test_routed_reference_playbook_contributes_its_tools(tmp_path, monkeypatch):
|
||||
"""A reference playbook routed into the prompt must bring its tools with it.
|
||||
Without this the model reads instructions like "you can read the codebase"
|
||||
while being advertised zero tools — and narrates tool calls it never made."""
|
||||
from synapse.main import _route_playbooks
|
||||
from synapse.playbooks.store import PlaybookFileStore, PlaybookItem
|
||||
|
||||
# Own store, not data/playbooks: the live set is the operator's, and a
|
||||
# published clone ships different playbooks - this asserted on data that
|
||||
# travels with one machine.
|
||||
store = PlaybookFileStore(tmp_path)
|
||||
store.add_playbook(PlaybookItem(id="main", title="Main", goal="g",
|
||||
instructions="i", order=0))
|
||||
store.add_playbook(PlaybookItem(id="dev", title="NexusOS Developer", goal="g",
|
||||
instructions="You can read the codebase.", order=1,
|
||||
tags=["synapse", "backend"],
|
||||
tools=["read_file", "list_files"]))
|
||||
monkeypatch.setattr("synapse.playbook_manager.playbook_store", store)
|
||||
import synapse.playbook_manager as pm
|
||||
|
||||
routed = _route_playbooks("why is the memory endpoint in synapse returning 500", pm.get_context_playbooks())
|
||||
names = {pb.title for pb in routed}
|
||||
assert "NexusOS Developer" in names, names
|
||||
|
||||
granted = {t for pb in routed for t in (pb.tools or [])}
|
||||
assert {"read_file", "list_files"} <= granted, granted
|
||||
# none of them are action tools, so they survive the default policy (off)
|
||||
assert tools.schemas_for(sorted(granted), allow_actions=False)
|
||||
|
||||
Reference in New Issue
Block a user