| 2026-04-17 |
persona: add execution discipline — no false fix confirmations
...
Navi must call the tool before claiming a fix is done. Explicitly
forbids saying "I've fixed X" without a tool result in the same turn
confirming the change. Targets the common pattern of acknowledging
a problem in text without actually executing the correction.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 17 Apr
|
reflect: force clarification when Detailer finds strategic ambiguity
...
Closing instruction now explicitly requires stopping to ask the user
if the Detailer identified ambiguities about core direction (what to
build, which approach, what the user actually wants). Prevents Navi
from using the Pragmatist's simplifications as an escape hatch when
the real problem is underspecified requirements.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 17 Apr
|
Add reflect tool: three parallel expert perspectives
...
ReflectTool runs Critic / Pragmatist / Detailer advisors concurrently
via asyncio.gather() + AIHelper.ask(). Each role has a distinct system
prompt designed to produce genuinely different analysis:
- Critic: challenges assumptions, surfaces risks and logical gaps
- Pragmatist: finds the simplest path, cuts unnecessary complexity
- Detailer: spots missing requirements, edge cases, ambiguities
Parameters: situation (required), assumptions (required list — the key
input that forces Navi to surface implicit beliefs), tried (optional).
Registered as a builtin with AIHelper injection. Added to all three
profiles. Persona updated with guidance on when to use it (complex or
ambiguous tasks before planning, or when stuck mid-execution).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 17 Apr
|
Add Prompts and Tools tabs to debug page
...
Backend:
- GET /agents/prompts — returns full built system prompt for every
profile, broken into sections (persona / profile / profiles block)
with char/token counts; mirrors Agent._build_system_prompt() exactly
- GET /agents/tools — now includes parameters schema alongside name
and description
Debug page:
- Tab bar: Context / Prompts / Tools
- Prompts tab: profile sidebar + collapsible sections per prompt part
(persona, profile prompt, profiles block), togglable tools list
- Tools tab: searchable list of all tools with description and
parameter table (name, type, description, required marker)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 17 Apr
|
Add standalone debug page at /debug
...
Replaces the old_webclient/debug.html with a proper self-contained
tool at debug/index.html. New features over the old page:
- Sidebar session list with profile, message count, pin indicator
- Auto-refresh toggle (3s interval)
- Refresh button
- Renders thinking blocks, is_plan and is_summary tags
- Shows tool call name on tool result messages
- Clickable image thumbnails (open full-size)
- All new fields from the current LLM context API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 17 Apr
|
| 2026-04-16 |
Handle heartbeat and session_sync on WS reconnect
...
- Ignore heartbeat events (server keep-alive pings)
- On session_sync after a reconnect: call reloadSession() to fetch
the latest saved history so the user sees the response even if
the agent finished while the client was disconnected
- Add chat.reloadSession(): force-reloads session from API bypassing
the same-id guard, also restores context token metrics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Fix WS disconnect and missed stream on reconnect
...
Two related problems:
- During long AIHelper calls (non-streaming LLM), no data flows to the
WebSocket and browsers drop the connection after ~30-60s of inactivity.
Fixed with a 20s heartbeat: _stream_to_client now uses asyncio.wait_for
and sends {"type":"heartbeat"} on timeout to keep the connection alive.
- After reconnect, if the agent finished while the client was offline,
_runs no longer holds the session and no stream_start is sent. Client
would reconnect silently with no response shown. Fixed by sending
{"type":"session_sync"} on every new WS connection (after reattach
completes or immediately when no run is active) so the client knows
to reload session history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Count AIHelper tokens in session metrics
...
Adds prompt/completion token fields to LLMResponse, populated by
OllamaBackend.complete(). AIHelper emits AIHelperTokensUsed into the
current event sink after each LLM call; run_stream drains it into
_subagent_tokens so AIHelper usage is reflected in the turn token delta.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Improve filesystem tool description: prioritize AI actions with examples
...
- Description now opens with explicit ALWAYS PREFER rule for query/smart_edit
- Each AI action has concrete examples (function lookup, renaming, config search)
- Standard actions demoted to 'use only when AI not applicable'
- question/instruction parameters include examples so the model understands
the full range of applicable cases
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|

Add AIHelper + filesystem query/smart_edit AI actions
...
AIHelper (navi/core/ai_helper.py):
- Reusable LLM utility for AI-enhanced tools: ask() and ask_json()
- Reads current_model ContextVar (set per-turn) so tools always use
the session's active model without extra wiring
- Robust JSON extraction: strips markdown fences, bracket-matching fallback
current_model ContextVar (navi/tools/base.py):
- New ContextVar set by run_stream() and run_ephemeral() after profile
is resolved; AIHelper reads it to pick the right model automatically
filesystem query action:
- Natural language question about any file, chunked at ~20k tokens of
content (~80k chars) with 30-line overlap between chunks
- Single-chunk: one LLM call; multi-chunk: partial answers accumulated
then synthesized in a final call
filesystem smart_edit action:
- Natural language edit instruction on files up to ~200k chars
- LLM outputs JSON patch ops: replace / delete / insert (1-based lines)
- Ops validated then applied bottom-up to preserve line numbers
- Returns unified diff of changes; preserves trailing newline
registry: AIHelper created once, OllamaBackend reused (no double init),
FilesystemTool receives ai_helper at construction
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Make profile switching autonomous: switch immediately, inform after
...
Previously Navi asked for permission before switching profiles.
Updated both the injected profiles block in the system prompt and the
switch_profile tool description to explicitly say: switch on your own
judgment, do not ask, then inform the user which profile is active and why.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Add profile discoverability: list_profiles tool + system prompt injection
...
- AgentProfile: new short_description (1-line) and full_description (dict
with specialization / when_to_use / key_tools) fields
- All 3 profile configs: structured descriptions added; list_profiles added
to enabled_tools
- _build_system_prompt: now accepts full AgentProfile; injects compact
"Available profiles" block into every system prompt so Navi always knows
what other profiles exist and when to switch — dynamically, no hardcoding
- ListProfilesTool: new built-in; returns structured per-profile details
(specialization, when_to_use, key_tools); accepts optional profile_id
for single-profile lookup
- registry: register list_profiles_tool after profiles registry is built
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Fix UnicodeEncodeError for Cyrillic in email headers
...
_enc_header() checks if a value is ASCII-safe; if not, wraps it in
email.header.Header with utf-8 charset, producing RFC 2047 encoding
(=?utf-8?b?...?=). Applied to Subject and To in _build_mime — covers
both send and reply with Cyrillic subjects or display names.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Fix gmail auth: read credentials from settings, not os.environ
...
pydantic-settings loads .env only into the Settings object — it does not
populate os.environ. Added gmail_address and gmail_app_password fields to
Settings; gmail tool now reads from settings instead of os.environ.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Add gmail tool manual
...
Concise reference covering all 5 actions, IMAP query syntax table,
UID concept, HTML body requirement, and typical workflow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Rewrite Gmail tool: IMAP/SMTP + App Password (drop OAuth2)
...
Standard library only (imaplib, smtplib) — no google-api dependencies.
Auth via GMAIL_ADDRESS + GMAIL_APP_PASSWORD env vars.
Pagination via integer offset instead of page token.
Message IDs are IMAP UIDs (shown in list output).
html2text still used for HTML→plain conversion on read.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Add Gmail tool: send, list, list_unread, read, reply via Gmail API
...
- tools/gmail.py: single tool with 'action' param routing to 5 operations.
Prefers text/plain MIME part; converts HTML→text via html2text for
HTML-only emails. Truncates body at 5000 chars. Marks message read on
'read'. Reply preserves thread via threadId + In-Reply-To/References headers.
Outgoing emails sent as multipart/alternative (plain + html).
- tools/gmail_auth.py: one-time OAuth2 setup script (InstalledAppFlow).
Reads gmail_credentials.json, saves token to gmail_token.json.
- tools/enabled.json: add gmail to global tool list
- Dependencies: google-api-python-client, google-auth-oauthlib, html2text
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Persist context token count: return from API, restore on session load
...
- GET /sessions/{id} now returns context_token_count and max_context_tokens
(max pulled from settings.ollama_num_ctx)
- loadSession() in chat store sets contextTokens/maxContextTokens from the
response so ContextBar shows the last known fill level immediately on load,
not only after the first new message
- Restore v-if guard on ContextBar (hides for brand-new sessions with 0 tokens)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Fix metrics: net token delta, subagent aggregation, ContextBar always visible
...
- run_stream: track _prev_tokens baseline before turn loop; compute net
token cost as (context_tokens - prev) + subagent_tokens for per-message cost
- run_stream: intercept SubagentComplete in sink drain loop to accumulate
subagent token and tool-call counts into the parent turn's totals
- run_ephemeral: already emitting SubagentComplete (from prior session)
- msg-meta-row: remove margin-left:auto from .msg-meta-time so time
groups inline with elapsed/tools/tokens instead of floating right
- ContextBar: remove v-if guard so bar is always visible (not only after
first LLM response with token data)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Add response metrics: elapsed time, tool calls, token count
...
Server:
- Message model: elapsed_seconds, tool_call_count, token_count fields
(display-only, excluded from LLM context via exclude_none)
- StreamEnd event: carries same three fields
- agent.run_stream: tracks turn start time, counts ToolEvent completions,
writes metrics onto the final assistant Message before saving to DB
- WebSocket: forwards metrics in stream_end payload
Client:
- chat.onStreamEnd: attaches elapsed_seconds, tool_call_count, token_count
to the streaming message on completion
- buildMessageList: scans each assistant group for metrics from history
- AssistantMessage: renders .msg-meta-row below the response —
timer icon + Xs · wrench icon + N tools · coins icon + Nk tokens · time
(each item only shown if present; time pushed right via margin-left: auto)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: UI density improvements for session names and header
...
Sidebar:
- session-name: 12px → 13px (more prominent with real names)
- session-preview: 13px → 12px, clamp 2 → 3 lines (more content visible)
- session-actions buttons: 26×26px, gap 2px (narrower, more room for info)
Chat header (mobile):
- chat-header-info: column layout on mobile so title and profile badge
stack vertically (flex-direction: column; align-items: flex-start)
ContextBar:
- Recomposed to single row: [track] [pct%]
- Track 100px → 60px, height 5px → 4px
- Full token counts moved to title attribute (hover tooltip)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|

Add session name generation via LLM
...
Backend:
- Session model gets name: str | None field
- SQLite migration: ADD COLUMN name TEXT
- PostgreSQL: ADD COLUMN IF NOT EXISTS name TEXT (applied on pool init)
- SessionStore: add set_name() abstract method, implemented in all stores
- navi/core/name_generator.py: LLM worker that reads user messages and
returns a 3–6 word title or None if content isn't substantial yet
- POST /sessions/{id}/generate-name endpoint: fires LLM, saves and
returns name; skips if session already named or has no user messages
- GET /sessions and GET /sessions/{id} now include name field
Client:
- api.generateSessionName(id) — calls the new endpoint
- sessions store: updateName(id, name) mutation
- chat store: after stream_end, _tryGenerateName() runs fire-and-forget;
skips silently if session already has a name or if request fails
- SessionItem already displays session.name (falls back to id prefix)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: remove 'Planning' label from wait indicator; add scroll-to-bottom button
...
- Waiting indicator now shows only a spinner (text was misleading — model
doesn't always plan before responding)
- Add floating scroll-to-bottom button that appears when scrolled >200px
from bottom, disappears when near bottom; smooth fade+slide transition;
positioned bottom-right (under thumb on mobile)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: reduce message-list horizontal padding to 10px on mobile
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: remove horizontal padding on message-list-inner for mobile
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: wrap markdown tables in scrollable container for mobile
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: fix history scroll by hiding content until positioned
...
Hide message-list-inner (opacity: 0) before scrolling to bottom,
then reveal it — so the user never sees the jump from top to bottom
on page reload or session switch. Content fades in already at the
correct scroll position.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
webclient: message timestamps, confirm dialog, layout and UX fixes
...
- Add useTime.js: relative time labels ("just now", "5m ago", HH:MM) with auto-refresh every 30s
- Show message timestamps below user bubbles and assistant replies
- Show session last_active time in sidebar below preview
- Add ConfirmDialog.vue + useConfirm.js: kit-styled modal confirm, wired to delete in SessionItem
- SessionList: switch RecycleScroller → DynamicScroller to support variable item heights
- SessionItem: remove fixed 74px height; show action buttons always on touch devices (hover: none)
- MessageList: constrain content to max-width 920px centered (message-list-inner, input-row)
- MessageList: replace TransitionGroup with plain v-for; animate only new messages via .msg-enter CSS class, history loads silently without scroll animation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
Fix scroll-to-bottom after page reload
...
Watch chat.loading instead of currentId for post-load scroll.
nextTick + rAF ensures browser layout is settled before scrolling.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|
ThinkingCard: add chevron, open by default; plan loading indicator
...
- ThinkingCard: add ph-caret-down chevron (matches plan/tool card style),
open by default (removed auto-close on thinking_end)
- AssistantMessage: show "Planning…" spinner when streaming but no content
yet — gives feedback during the two-LLM-call planning phase
- app.scss: thinking-chevron styles, planning-indicator styles
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 Apr
|