| 2026-05-12 |
Auto-inject relevant memory facts into LLM context on every user turn.
...
Semantic search against the vector memory store runs in parallel with
context provider collection. Number of injected facts scales with
message length (1–3) to reduce noise on short queries. Guardrails:
min length gate, per-turn deduplication, and structured logging at
info level for observability.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 May
|
Remove dead LLMBackend.stream() method
...
The method was defined on all backends (Ollama, FallbackOllama, OpenAI)
and in the base LLMBackend interface, but was never called by agent.py
or messages.py. stream_complete() covers all streaming use cases.
- navi/llm/base.py: remove abstract stream() method
- navi/llm/ollama.py: remove OllamaBackend.stream()
- navi/llm/fallback.py: remove FallbackOllamaBackend.stream()
- navi/llm/openai_backend.py: remove OpenAIBackend.stream()
- docs/tech_debt_review: mark item 54 as fixed
236 tests passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 May
|
| 2026-05-11 |
Fix ollama_backends / FallbackOllamaBackend issues
...
- registry.py: always use FallbackOllamaBackend (unified backend).
Enables model priority lists in all deployments, not just multi-server.
- agent.py: add missing think=profile.think_enabled to run() (REST endpoint).
- compressor.py: fix model param type (str → list[str] | str | None).
- fallback.py: harden load_servers_from_file against missing/bad JSON files
and entries without host. Add clear_blacklists() for manual reset.
- admin.py: add POST /admin/ollama/clear-blacklists endpoint.
- tech_debt_review: document dead stream() methods.
- tests: add tests for single-server fallback, bad file handling,
missing host skipping, and blacklist clearing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
Add deterministic line-based file editing (edit_lines), rating UI fix, and session refresh
...
- filesystem.py: add edit_lines action (deterministic line ops via operations array)
+ numbered param for read (1-based line numbers in output)
+ clarify four editing modes in tool description
- chat.js: fix rating IDs for streaming messages (assign h_ ID on stream_end)
- SessionList.vue: mobile pull-to-refresh with PTR_THRESHOLD=80
- AppSidebar.vue: desktop refresh button next to Conversations header
- planning.py: knowledge source assessment in Phase 1
- debug panel: MCP servers tab + resolved tools per profile
- NAVI.md: reposition as neutral quick-reference
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
Fix graceful MCP disconnect during server shutdown
...
- McpClient.disconnect(): catch CancelledError / RuntimeError from
anyio SSE transport teardown
- McpManager.disconnect_all(): shield gather from CancelledError
- main.py _on_shutdown(): catch CancelledError and RuntimeError
explicitly (BaseException, not Exception)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
Inject MCP server instructions into system prompt
...
- McpClient: collect instructions from MCP initialize handshake
- McpServerConfig: add 'instructions' field for Navi-side overlay
- McpManager.get_instructions(): merge server + config instructions
- ContextBuilder: new _mcp_context_msg() injects MCP server
descriptions into every LLM context as a system message
- Agent passes mcp_manager to ContextBuilder
- mcp_servers.json: add overlay instructions for gnexus-book
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
Fix MCP tool lookup in websocket handler and tests
...
- websocket.py: pass mcp_manager to Agent(), with graceful fallback
if MCP connection fails
- conftest.py: mock get_mcp_manager() in tests to prevent SSE
connection attempts against real servers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
Refactor MCP integration: server groups in profiles
...
- mcp_servers.json: add 'groups' (read/write/admin) for gnexus-book
- AgentProfile: new 'mcp_servers' field (server_name -> group list)
- Profile loader: parse and persist 'mcp_servers' in config.json
- Agent._tool_list(): expands mcp_servers into concrete tool names
via McpManager.resolve_group(), wildcard '*' supported
- /agents/profiles API: includes 'mcp_servers' in response
- Profiles no longer list individual mcp_ tools in 'enabled_tools'
- discuss: gnexus-book read group
- server_admin: gnexus-book read+write+admin groups
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
Connect gnexus-book MCP server and enable in profiles
...
- mcp_servers.json: SSE transport to 192.168.1.170:8001
- server_admin profile: all 16 gnexus-book tools
- discuss profile: read-only gnexus-book tools (docs + inventory)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 May
|
| 2026-05-10 |
Add MCP server support and fix memory tools user isolation
...
MCP integration:
- New navi/mcp/ package: client, manager, config, tools
- ToolRegistry learns register_external() for MCP tools
- reload_tools reconnects MCP servers on hot reload
- New built-in mcp_status tool
- Startup/shutdown wiring for MCP connections
- 12 new tests (unit + integration with real stdio server)
Memory tools fix:
- memory, memory_save, memory_search, memory_forget now read
current_user_id from tool context and pass it to MemoryStore
- Fixes invisible facts for authenticated users
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 May
|
| 2026-05-09 |
Add profile editing to admin panel
...
Backend:
- navi/profiles/loader.py: add save_profile_to_dir() to write config.json
and system_prompt.txt back to disk
- navi/core/registry.py: add ProfileRegistry.update() for in-memory updates
- navi/api/routes/admin.py: new endpoints GET /admin/profiles/{id} and
PUT /admin/profiles/{id} for reading and saving full profile config
Admin panel:
- Profiles table: add Edit button per row
- Drawer form with sections: Basic, Model & Generation, Thinking,
Planning, Sub-agent, Tools, System Prompt
- All fields editable inline (text inputs, checkboxes, textareas for lists)
- Save via PUT request, updates both disk and in-memory registry without
server restart
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 May
|
Style OAuth bridge page with gnexus UI kit design system
...
Redesigned /auth/mobile-done to match the Navi webclient visual
language (Tokyo Night cyberpunk palette):
- Dark background (#16161E), muted panel with green success accent
- Squared corners, uppercase headings, wide letter-spacing
- Spinner during auto-redirect, fallback button with left accent border
- Full hover states matching .btn-success from the kit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 May
|
Detect Android OAuth client via User-Agent header
...
WebView always sends NaviAndroid/1.0 in the HTTP User-Agent, while
navigator.userAgent in JS is unreliable. Backend now detects the
Android platform from the UA header when the ?platform query param is
absent, ensuring the bridge-page flow works regardless of webclient
version.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 May
|
Improve OAuth bridge page with Chrome Intent URL + fallback button
...
/auth/mobile-done now tries Chrome Intent URL for automatic app
opening, and falls back to a styled button if the browser blocks
automatic navigation to custom schemes.
- Auto-redirect: intent://...#Intent;scheme=navi;package=com.navi.client;end
- Fallback UI: card with \"Open Navi App\" button after 1.5s timeout
- Works in both Chrome (auto) and DuckDuckGo (manual tap)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 May
|
Add Android deep-link OAuth bridge for WebView auth
...
Problem: Android WebView opens OAuth in external browser (Chrome).
After successful login, cookie stays in Chrome and WebView remains
unauthenticated because cookies are not shared between apps.
Solution: bridge page flow that avoids cookies entirely for mobile.
Backend:
- /auth/login accepts ?platform=android and ?return_to params
- /auth/callback detects android via state metadata, skips cookie,
redirects to /auth/mobile-done?sid=<session_id>
- /auth/mobile-done renders HTML that deep-links to navi://auth/callback
Webclient:
- login() detects NaviAndroid UA and adds ?platform=android
Android:
- MainActivity: handle incoming navi:// intent in onCreate and onNewIntent,
set cookie via CookieManager, reload WebView
- AndroidManifest: add intent-filter for navi://auth/callback and
singleTask launchMode
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 May
|
| 2026-05-08 |
Add per-user sessions and memory filters in admin panel
...
Backend:
- GET /admin/memory now accepts user_id query param to filter facts
for a specific user instead of returning the global view.
Frontend:
- Users table now has "Sessions" and "Memory" buttons per row.
- Clicking "Sessions" switches to Sessions tab and pre-fills search
with the user id.
- Clicking "Memory" switches to Memory tab and passes user_id to the
API, showing only that user's facts.
- Manual tab clicks reset filters (search / userId) so the view
returns to unfiltered state.
Docs:
- docs/api.md updated with user_id param for GET /admin/memory.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|

Fix admin memory showing 0 facts; add pagination/search/sort
...
Bug fix:
- get_all_facts(user_id=None) was filtering for user_id IS NULL only,
so admin panel showed 0 facts when facts had user_ids set.
- Added all_users parameter to get_all_facts and fact_count.
- Admin endpoint now passes all_users=True to return all facts.
Memory pagination:
- Extended get_all_facts with offset, search, sort_by, sort_order params.
- Extended fact_count with search and all_users params.
- /admin/memory endpoint now accepts limit, offset, search, sort_by,
sort_order and returns {total, limit, offset, items}.
Admin panel frontend:
- Added server-side search with debounce for memory facts.
- Added sortable column headers (category, key, source, confidence, updated).
- Added pagination controls (prev/next, page size selector).
- Switched memory display from grouped tables to flat sortable table.
Tests:
- Added unit tests for get_all_facts(all_users=True) and
fact_count(all_users=True).
- All 219 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Add pagination, search, and sorting to admin sessions
...
Backend:
- Add count_all and search_list abstract methods to SessionStore
- Implement count_all and search_list in PgSessionStore (SQL with ILIKE)
- Implement count_all and search_list in InMemorySessionStore
- Update /admin/sessions to accept limit, offset, search, sort_by, sort_order
- Return {total, limit, offset, items} from /admin/sessions
Frontend:
- Add search input for sessions in admin panel
- Add clickable sortable column headers with asc/desc toggle
- Add pagination controls (prev/next, page size selector, item count)
- Debounce search input (300ms)
Tests:
- Add integration tests for pagination, offset, search, and sorting
- All 217 tests pass
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Fix memory system bugs: deterministic summary id, skip legacy extraction
...
- _summary.py: replace non-deterministic hash() with zlib.crc32 so
summary id stays stable across server restarts, preventing duplicate
summary rows.
- extractor.py: skip memory extraction for legacy sessions (user_id=None)
— ON CONFLICT(user_id, category, key) does not catch NULL duplicates
in PostgreSQL (NULL != NULL).
- sessions.py: _process_stale_sessions skips legacy sessions.
- _facts.py: remove dead code (user_clause/user_param variables).
- test_extractor.py: add user_id to test sessions + new test for
legacy session skip.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Fix memory system multi-user bugs: summary PK conflict and vector search params
...
- _summary.py: generate deterministic per-user id (hash(user_id)+2) instead of
hardcoded id=1, preventing UniqueViolationError on memory_summary_pkey
for multi-user sessions.
- _facts.py: split vector search into user_id=None / user_id!=None branches
with correct parameter counts, fixing InterfaceError "expects 3 arguments".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Fix missing columns in navi_users: add boot-time ALTER TABLE migration
...
The navi_users table already existed on this deployment, so the updated
CREATE TABLE IF NOT EXISTS was a no-op. Run ALTER TABLE ADD COLUMN for
username, first_name, last_name, phone, birth_date, country, city, locale
if they are missing (checked via information_schema.columns).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Propagate user profile to LLM context via current_user_info ContextVar
...
- Extend User model: username, first_name, last_name, phone, birth_date,
country, city, locale (all from gnexus-auth profile)
- navi_users DDL: add new profile columns
- auth/deps + auth/callback: populate new fields on upsert
- /auth/me: return all profile fields
- Add current_user_info ContextVar for full user profile propagation
- websocket + messages: set current_user_info before agent.run()
- run_ephemeral: inherit and restore current_user_info
- ContextBuilder: _user_context_msg() injects [User context] with name,
email, location, locale, role into LLM system messages
- _security_policy_msg: reads user_id/role from ContextVar directly
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Add multi-user sandbox: filesystem, terminal, code_exec, security policy
...
- filesystem, share_file: sandbox non-admin users to user_data/<user_id>/
- terminal: working_dir sandbox + allowlist + dangerous pattern block for users
- code_exec: sandbox CWD and temp files to user_data/<user_id>/ for users
- context_builder: inject dynamic security policy into LLM context (user/admin)
- config: terminal_user_allowed_commands setting
- agent: wire user_id/user_role through ContextBuilder.build()
- base: add current_user_role ContextVar; run_ephemeral inherits role
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Add per-user filesystem sandbox via current_user_id ContextVar
...
- tools/base.py: add current_user_id ContextVar (set by Agent before
every tool call, cleared after)
- core/agent.py: set current_user_id in run_stream from session.user_id
and in run_ephemeral from parent_session.user_id; restore in finally
- tools/filesystem.py: _check_path resolves all paths inside
user_data/<user_id>/ when current_user_id is present; legacy mode
(no user_id) falls back to FS_ALLOWED_PATHS
- tools/share_file.py: validate source path is inside user sandbox
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
Use configured GNAUTH_REDIRECT_URI instead of dynamic base_url
...
_get_redirect_uri was building the redirect_uri from request.base_url,
which returns the internal address when behind a reverse proxy. This
caused gnexus-auth to reject the redirect_uri as invalid.
Now _get_redirect_uri always returns settings.gnauth_redirect_uri,
so the public URL configured in .env is used consistently.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 May
|
| 2026-05-04 |
Fix stop_session 422: use get_current_user instead of get_current_user_ws
...
stop_session is an HTTP endpoint but was using Depends(get_current_user_ws),
whose websocket parameter caused FastAPI to demand it as a query param.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
Fix NameError in run_ephemeral: session was undefined
...
run_ephemeral doesn't have a session variable. Pass user_id from the
parent session (looked up via parent_session_id) instead of referencing
non-existent session variable.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
Add full-screen login overlay for unauthenticated users
...
- Backend: new endpoint GET /auth/status returns {configured: bool}
- Webclient auth store: add authConfigured ref + fetchStatus()
- LoginScreen.vue: centered card with logo, title, and login button
- App.vue: show LoginScreen overlay when auth is configured but
user is not authenticated (z-index 9999, blocks all UI)
- App.vue onMounted: fetch auth status before trying to resolve user
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
Fix WebSocket 403 — bypass FastAPI Depends for WS auth
...
- websocket.py: resolve user by calling get_current_user_ws directly
inside the handler instead of using Depends(). This avoids FastAPI
returning HTTP 403 on the upgrade request when auth resolution fails.
- websocket.py: accept WebSocket before access check, close with 4003
for auth failures instead of HTTP 403.
- auth/deps.py: remove debug logging from get_current_user_ws.
- tests/conftest.py: monkeypatch get_current_user_ws directly since
Depends() is no longer used on the WebSocket endpoint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
Fix WebSocket 403 and restore dependency resolution for auth
...
- websocket.py: restore Depends(get_current_user_ws) in endpoint signature
so FastAPI dependency_overrides work correctly in tests
- websocket.py: accept WebSocket before access check; reject anonymous
only for owned sessions, allow anonymous for legacy (user_id=None)
- auth/deps.py: add info-level logging to get_current_user_ws entry/exit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|