| 2026-05-12 |
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
|
Fix WebSocket 403 by accepting before access check
...
- websocket.py: move await websocket.accept() before check_session_access
so auth failures close the WebSocket with 4003 instead of returning
HTTP 403 on the upgrade request
- Reject anonymous WebSocket connections explicitly (4003)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|