| 2026-06-16 |
Harden realtor profile search rules and filters
...
- Add hard rule: only listings returned by MCP tools may be reported;
no invented addresses, prices, contacts, or 'similar' examples.
- Replace weak fallback with mandatory multi-attempt fallback order
(switch search type, drop filters, simplify query, widen location,
minimal search) before reporting no results.
- Expand filter list to full MetadataFilters set with enum values.
- Add explicit mapping of vague user phrases to concrete filters
(e.g. 'с ремонтом' -> renovation_status, 'у метро' -> metro_distance).
- Add forbidden query patterns and good/bad Ukrainian query examples.
- Sync MCP server instructions with same anti-hallucination and filter
rules, including example queries.
Eugene Sukhodolskiy
committed
9 days ago
|
Apply rent_bot user flow to realtor profile
...
- Expand realtor system prompt with full user-flow semantics:
onboarding, parameter extraction, clarifying questions, semantic vs FTS
search, carousel-style results, detail card, contact/favorite/more/passive
search follow-up actions. Add image rendering instructions.
- Extend vmk_data MCP server instructions to mention listing image URLs.
Eugene Sukhodolskiy
committed
9 days ago
|
| 2026-06-12 |
Add vmk_data MCP server and realtor profile for real estate search
...
- mcp_servers.d/vmk_data.json: streamable_http to localhost:8080
- navi/profiles/realtor/: config + system_prompt + subagent_system_prompt
Profile includes semantic & FTS search, listing details, schema tools
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
13 days ago
|
| 2026-06-01 |
Fix spawn_agent profile selection: stronger prompt + alias fallback
...
- Rewrite PROFILE SELECTION description to explicitly say: when the
plan specifies a profile, you MUST pass profile_id. Only omit when
the plan does NOT specify a profile.
- Add JSON examples showing correct profile_id usage.
- Add fallback for 'profile' parameter name (models sometimes use
'profile' instead of 'profile_id' when the description says
'Profile to use').\n\nCo-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
Fix scratchpad tool: rename op→action, add examples, improve error messages
...
- Rename primary parameter from 'op' to 'action' for consistency with
all other tools (terminal, filesystem, todo, etc.). Legacy 'op' still
works as a fallback to avoid breaking old calls in compressed context.
- Add JSON examples directly in the tool description so the model sees
the exact structure to produce.
- Improve all error messages to include the correct JSON example,
making it obvious what the model did wrong.
- Add manuals/scratchpad.md for tool_manual support.
- Update and expand tests for new syntax and error cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
Remove offline_access from OAuth scopes
...
gnexus-auth does not support offline_access yet (SUPPORTED scopes are
only openid, email, profile, roles, permissions). Requesting it caused
invalid_scope error and login failures.
Keep refresh token support as-is — tokens are still issued and
refreshed, just bound to the SSO session lifetime.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
Fix OAuth callback to handle missing code and error responses
...
FastAPI validated code/state as required query params before the
handler body ran, so any OAuth error response (e.g. invalid_scope
from offline_access, user denied consent) produced an opaque 422
"Field required" instead of a readable error message.
- Make code, state, error, error_description all optional with defaults
- Detect OAuth error responses and return a clear 400 with the error
- Guard state[:8] slicing when state may be None
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
Make shared files and published content publicly accessible
...
Remove auth requirements from:
- GET /sessions/{id}/files/{filename} — direct download links (session ID
acts as unguessable capability token)
- GET /sessions/{id}/content — published inline content list
Both endpoints still verify session exists and protect against path
traversal. File upload and file listing remain auth-gated.
Update tests to match new signatures.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
Fix frequent OAuth logouts: offline_access scope, transient error handling, fetchMe resilience
...
- Add 'offline_access' to OAuth scopes so gnexus-auth issues offline
refresh tokens instead of SSO-session-bound ones.
- Distinguish TokenRefreshException (invalid/expired refresh token)
from transient network errors during token refresh:
* TokenRefreshException → logout (token genuinely dead)
* Other exceptions → fallback to cached user or API token
- Improve refresh failure logging with exc_type and error message.
- Frontend fetchMe: swallow non-401 errors so transient 5xx/network
failures don't flash the login screen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
Fix CompressionWorker: sync context compression with session messages
...
- Mark removed messages as is_context=False so DB reload doesn't resurrect them
- Persist summary_msg in messages (is_display=False) so summary survives reload
- Add is_compression marker with is_context=False
- Set context_token_count to real estimate instead of zero
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
24 days ago
|
| 2026-05-26 |
Clarify terminal description param and profile prompts
...
- terminal.py: stronger description for the 'description' field so
the model knows it is REQUIRED for action=open and what to write
- server_admin and developer system prompts: add a short section
explaining persistent terminal usage (open/close/list/status/send_input)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
Move terminal_manager to _internal subpackage
...
- terminal_manager is an internal helper, not a tool
- Update imports in terminal.py, container.py, test_terminal.py
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
Fix terminal review issues: security, lifecycle, reactivity
...
- TerminalManager.open now accepts exec_tokens to use create_subprocess_exec
for restricted commands instead of always using shell
- Fix kill/terminate order: SIGTERM first, SIGKILL fallback
- Pop closed sessions from _sessions dict to prevent memory leak
- Add terminal_manager.shutdown() to AppContainer.shutdown()
- Wait for reader tasks in foreground open before returning output
- Add _MAX_TERMINALS_PER_SESSION limit (10)
- Wrap cleanup_idle tasks in _close_one_safe with error logging
- send_input catches BrokenPipeError/ConnectionResetError specifically
- Foreground terminals auto-close after gathering output
- Vue reactivity: replace terminals object immutably instead of mutating
- onTerminalClosed marks matching tool card as no longer pending
- Update tests for new behavior (foreground auto-close, max limit)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
Add persistent multi-session terminal tool with background support
...
- New TerminalManager module: named subprocess sessions per Navi session,
background readers, event-sink streaming, idle auto-cleanup
- Refactor terminal tool to multi-action: run, open, close, list,
status, send_input
- Add TerminalOutputDelta and TerminalClosed events for streaming
- Wire TerminalManager into AppContainer, orchestrator, and registry
- Persist session_metadata in Session model and pg_session_store
- Close all session terminals on session delete
- Webclient: handle terminal_output/terminal_closed WS events,
display live terminal output in tool cards
- Update unit tests for new terminal actions
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
Fix dead code in websocket.py — stream_start never sent on first message
...
websocket.py lines 301-308 were indented inside the `except` block after
`raise`, making them unreachable. This meant `stream_start` and
`_stream_to_client` never ran for the initial message send, so the client
saw no response until page reload (when the reconnect path took over).
Fix: dedent lines 301-308 so they execute after the try/except/finally
block on the normal success path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
Fix delta loss during thinking-to-text transition and streaming orphaning
...
Backend:
- agent.py _consume_stream: replace elif with if so a single chunk can
carry both the final thinking fragment AND the first text delta.
Fixes the 'first token lost after tools' bug.
Frontend:
- chat.js reloadSession: bail out early when streaming.value is true.
Replacing messages.value mid-stream orphans streamingMsg, breaking
Vue reactivity for all subsequent deltas and tool cards.
- useWebSocket.js session_sync handler: add !chat.streaming guard
before calling reloadSession.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
| 2026-05-25 |

Fix 19 issues found in full codebase review
...
Backend:
- Stop session auth bypass: require auth for owned sessions, reject anonymous with 401
- upload_file: stream chunks directly to disk instead of buffering in RAM
- MCP config: validate name against path traversal regex
- auth deps: cleanup stale refresh locks periodically
- auth routes: expire mobile auth states after 10 min to prevent unbounded growth
- compressor: meta-summarize existing summaries before compression; preserve assistant content when tool_calls present; rewrite hard_truncate to keep whole turns
- orchestrator: configurable WS replay buffer size; async cleanup/remove_websocket/clear_busy; fix run_recall ContextVar order to avoid deadlock on _build_agent failure; await cleanup in finally
- agent: persist image_msg in session.messages; remove archived messages from session after archive; remove duplicate StreamStopped yield on tool stop
- websocket: try/except around create_task with cleanup on failure; await remove_websocket
Frontend:
- App.vue: hashchange listener lifecycle in onMounted/onUnmounted
- MessageList.vue: passive scroll, flash timeout cleanup, archive scroll snapshot
- InputBar.vue: 300 ms debounce on draft save to localStorage
- SessionList.vue: remove :key from DynamicScroller to avoid remount jitter
Tests: 422 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Add meta-summary for multi-level compression
...
When to_summarize contains multiple existing summary messages whose
combined length exceeds 8000 chars (~1/3 of max summarizer input),
run a quick meta-summary pass first to consolidate them into a single
compact summary before the main compression. This prevents information
loss when repeated compressions stack up long summary chains.
- _meta_summarize(): fast LLM pass (think=False, max_tokens=1500)
- compress_context(): detects >1 long summaries and triggers meta pass
- Graceful fallback: if meta-summary fails, continue with raw summaries
- 3 new unit tests: consolidation, skipped for short summaries, failure fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Add archive message pagination, configurable WS replay buffer
...
Backend:
- Add archive_threshold to Session model and getSession response
- Add next_before_seq to archive endpoint for cursor pagination
- Make WS replay buffer size configurable via WS_REPLAY_BUFFER_SIZE
Webclient:
- Add getArchivedMessages API function
- Add archive pagination state and loadArchivedMessages to chat store
- MessageList: auto-load older messages on scroll-to-top with scroll
position preservation and loading spinner
Docs: update config.md with new env vars
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Wire archive trigger into agent after compression
...
After _do_compress_and_save finishes, if the total persisted message count
(db_next_sequence) exceeds session_messages_window (default 1000), the agent
now calls archive_old_messages() to move older rows into
session_messages_archive.
Adds session_messages_window config and unit tests for archive SQL.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Add session message archive table + global sequence_number tracking
...
Schema:
- session_messages_archive — identical structure, stores old messages.
- sessions.next_sequence — monotonic seq counter per session.
- sessions.archive_threshold — split point between hot and archive.
Behaviour:
- get() / _build_sessions() load only seq >= archive_threshold (hot).
- save() UPDATEs existing rows (seq >= 0) and INSERTs new ones (seq = -1)
with auto-assigned sequence_number = next_sequence, next_sequence+1, ...
- archive_old_messages() moves a batch to archive and bumps the threshold.
This keeps the hot table bounded so list/get RAM stays flat regardless
of total session history size.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Implement delta-save for session messages
...
Replace full DELETE/INSERT with efficient delta writes:
- Track db_message_count on Session (how many rows already persisted).
- On save(): UPDATE mutable flags for existing rows, DELETE only extras
(race guard), INSERT new messages via executemany.
- Reduces DB write amplification from O(N) to O(delta) per turn.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Fix broken MCP admin endpoints after deps refactor
...
Replace stale `_mcp_manager` module-level import with `get_mcp_manager()`
calls in admin endpoints. The module-level variable was removed during the
AppContainer refactor, causing 500 errors on:
- POST /admin/mcp/{server}/reconnect
- GET /admin/mcp/status
- POST /admin/mcp/test
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Review fixes: restore _build_sessions, fix flags, search filter, tests
...
- Restored _load_messages_map and _build_sessions helpers that were
accidentally dropped in Phase 5 (list_all/list_page/search_list
called them but they were missing, causing NameError at runtime)
- _build_sessions now filters messages by is_display so list methods
return consistent display-only history like get()
- count_all/search_list EXISTS subquery now filters to is_display=true
so search only matches visible chat messages
- Updated pg_session_store docstring to remove stale dual-write claim
- compressor summary_msg now defaults to is_display=False
- Added unit tests for message flags (agent, compressor, planning)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Phase 5: Cleanup legacy JSON dual-write, remove duplicated class
...
- Removed JSON column writes from create() and save()
- Removed duplicated PgSessionStore class block that leaked in during Phase 4
- _serialize/_deserialize retained for boot migration helper only
- All session I/O now goes exclusively through session_messages table
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Phase 4: Read list/search from session_messages, drop JSON fallback
...
- list_all / list_page / search_list now load messages via _build_sessions()
which batch-loads session_messages in a single query
- count_all search uses EXISTS (SELECT 1 FROM session_messages ...) instead
of ILIKE on the JSON text column
- Removed _row_to_session and all legacy JSON column reads from list paths
- get() continues to load session_messages directly (Phase 3)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Phase 3: Build context from session_messages table
...
- get() always reads from session_messages, never falls back to JSON columns
- Load all rows in a single query, then split by is_display / is_context flags
- messages[] and context[] now share the same Python objects,
enabling reliable id() matching in the agent compression path
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Phase 2: Dual-write with is_context/is_display flags on Message
...
- Message model gets is_context and is_display bools
- PgSessionStore.save() writes flags directly to session_messages
- Agent sets is_context=False on display-only messages, is_display=False on context-only
- Planning: plan context msg is_display=False, plan marker is_context=False
- Compression: summarized messages get is_context=False, summary added to messages with is_display=False
- Tests updated for extra user display+context messages per turn
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Fix user tool execute() signature to accept ctx keyword argument
...
The tool_executor always passes ctx=ctx when calling tool.execute(),
but _try_module_level in loader.py created user tools with
_execute(self, params) only. This caused:
Error: _execute() got an unexpected keyword argument 'ctx'
when calling get_current_datetime and any other module-level user tool.
- Add ctx=None parameter to the generated _execute wrapper
- Preserves backward compatibility with existing user tools
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Add subagent progress report on failure
...
When a subagent stops (timeout, max iterations, thinking stall, user stop),
it now returns a structured progress report built from its local message
context, so the parent agent knows what tools were called and what was
accomplished before the stop.
- Add _build_progress_report() to SubAgentRunner
- Report includes: turn number, assistant text, tool calls with results
- Prepended to result_text for every stop reason (completed also gets it)
- Updated test_run_ephemeral_complete to expect the report prefix
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|