| 2026-05-25 |
Add rebuilt webclient dist assets
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Fix streaming message orphaning and duplicate message keys
...
chat.js:
- reloadSession: during an active stream, replace the last built assistant
message with the live streamingMsg instead of discarding it on text match.
This fixes the root cause of invisible first messages and flickering tool
cards when session_sync arrives mid-stream.
- buildMessageList: remove assistantCounter and use firstIdx as the message id
to avoid duplicate Vue keys (e.g. user h_0 and assistant h_0).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|

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
|
Add client-side image resize and server-side image token budgeting
...
Client (useFileUpload.js):
- Resize images to max 1024px on longest side via Canvas before upload
- JPEG quality 0.9, preserves EXIF orientation
- Reduces typical phone photos from 8 MB → ~400-900 KB
Server (websocket.py):
- Increase limit to 8 images, 50 MB total payload
Server (agent.py):
- Before adding user message to session.context, estimate how many images
fit in remaining context (500 tok/img, up to 80% of max_context_tokens)
- Images that don't fit are saved as resized JPEGs to session_files/
- References to saved images are appended to the user message text
so Navi can view them later via image_view tool
- session.messages keeps ALL images for full UI history
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
| 2026-05-24 |
Rebuild webclient with tool_call_id matching fix
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Update gnexus-creds MCP instructions: require tags, category, and allow_mcp
...
- Add rule to always include `tags` and `category` when creating secrets
- Add rule to always set `allow_mcp=true` so secrets remain visible through MCP
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Fix MCP tool spinner bug: match tool_started → tool_call by tool_call_id
...
- Add tool_call_id field to ToolStarted and ToolEvent dataclasses
- Pass tc.id as tool_call_id from agent.py, subagent_runner.py, and tool_executor.py
- Update frontend chat.js onToolStarted/onToolCall to match cards by toolCallId
with fallback to name-matching for backward compatibility
Closes spinner issue where LLM short name ("search_docs") didn't match
resolved MCP name ("mcp__gnexus_book__search_docs").
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Fix MCP tool registration at startup
...
Root cause: _on_mcp_server_connected was defined and registered AFTER
mcp_manager.load_all(), so startup connections never registered their
tools into the ToolRegistry.
Fix: explicitly iterate connected clients after build_default_registries()
creates tool_registry, and register their MCP tools before starting the
health-check loop. The callback stays for future reconnects.
Also adds diagnostic warning in build_tool_list when tools are missing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Add debug logging to trace MCP tool registration / resolution
...
Logs added to:
- _on_mcp_server_connected: shows how many tools are registered per server
- build_tool_list: lists missing tool names and counts
- _resolve_tool: dumps tool_map keys when a lookup fails
This is diagnostic — will be removed once the root cause is found.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Fix MCP tool availability after health-check disconnect/reconnect
...
- connect() now calls _cleanup() first to avoid stale transport leaks.
- mark_disconnected() clears _session so _ensure_connected() knows
the client truly needs a fresh session.
- register_mcp_tools() is no longer a no-op; it re-registers all
connected MCP tools so reload_tools correctly restores them.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Fix MCP health-check spamming "connected" toasts every 30s
...
Only publish McpStatusUpdate "connected" when a server transitions
from disconnected -> connected, not on every routine poll.
Track last-known state in _connected_status dict.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Suppress noisy MCP SDK health-check logs
...
mcp.server.lowlevel.server logs "Processing request of type ListToolsRequest"
on every health-check poll. Raise its threshold to WARNING so we only
see real errors.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Add auth resilience: user cache, retry, and API token fallback
...
- 30-second in-memory _user_cache to avoid hammering gnexus-auth
- _fetch_user_with_retry: one retry after 1.5s sleep on transient failure
- API token fallback when OAuth cookie is present but refresh fails
- Clear cache/locks in test fixture to prevent cross-test pollution
- Fix registry timeout test after lowering default to 90s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Fix link deduplication in ArtifactsPanel — three root causes
...
1. normalizeUrlForDedup returned only pathname, so https://a.com/foo
and https://b.com/foo shared key /foo. Now uses host+pathname
(with www stripped).
2. Trailing punctuation was stripped for display but not before dedup,
so https://example.com/path. and https://example.com/path got
different keys /path. vs /path. Now stripTrailingPunctuation() runs
before normalizeUrlForDedup().
3. Fragile before==='(' skip logic for markdown hrefs replaced with
precise protectedRanges tracking — bare URLs whose match falls inside
a recorded markdown href range are skipped regardless of spacing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Raise first-chunk timeout to 90s and retry same server+model before fallback
...
- config.py: llm_stream_first_chunk_timeout 180s → 90s
- fallback.py stream_complete: wrap gen.__anext__() in asyncio.wait_for()
with llm_stream_first_chunk_timeout; on TimeoutError or LLMConnectionError
sleep 2s and retry once on the same server+model before blacklisting/fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
Document mcp_status_update WebSocket event
Eugene Sukhodolskiy
committed
on 24 May
|
Add MCP health check loop, auto-reconnect, and system toast notifications
...
Backend:
- McpManager: keep all configured servers in the pool even if connect()
fails at startup; health-check loop (30s) tries to reconnect dead servers
and verifies live ones with list_tools()
- McpManager: set_on_server_connected callback re-registers tools when a
dead server comes back online
- McpClient: add mark_disconnected() for silent drop detection
- McpStatusTool: skip list_tools() for disconnected servers
- Orchestrator: broadcast mcp_status_update to all WebSocket sessions
- New event type McpStatusUpdate with server_name, status, tool_count
Webclient:
- useWebSocket: handle mcp_status_update → toast.success/toast.error
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|