diff --git a/client/js/app.js b/client/js/app.js index 894b3eb..f147e77 100644 --- a/client/js/app.js +++ b/client/js/app.js @@ -301,6 +301,19 @@ setInputEnabled(true); break; + case 'profile_switched': { + // Update local session record so sidebar and header stay in sync. + const idx = sessions.findIndex(s => s.session_id === currentId); + if (idx !== -1) { + sessions[idx].profile_id = event.profile_id; + sessions[idx].profile_name = event.profile_name; + } + profileSelect.value = event.profile_id; + updateChatHeader(chatHeaderEl, event.profile_id, event.profile_name); + rerenderSidebar(); + break; + } + case 'context_compressed': appendCompressionNotice(messagesEl); scrollToBottom(messagesEl); diff --git a/navi/api/websocket.py b/navi/api/websocket.py index 62bf39d..dd61975 100644 --- a/navi/api/websocket.py +++ b/navi/api/websocket.py @@ -25,7 +25,7 @@ from navi.api.deps import get_session_store from navi.core import Agent, ContextCompressed, StreamEnd, TextDelta, ThinkingDelta, ThinkingEnd, ToolEvent -from navi.core.events import ToolStarted, TurnThinking +from navi.core.events import ProfileSwitched, ToolStarted, TurnThinking from navi.exceptions import MaxIterationsReached, NaviError, SessionNotFound router = APIRouter(tags=["websocket"]) @@ -100,6 +100,8 @@ } if isinstance(event, TurnThinking): return {"type": "turn_thinking", "thinking": event.thinking, "is_subagent": event.is_subagent} + if isinstance(event, ProfileSwitched): + return {"type": "profile_switched", "profile_id": event.profile_id, "profile_name": event.profile_name} return None diff --git a/navi/core/events.py b/navi/core/events.py index fd12a45..15081ca 100644 --- a/navi/core/events.py +++ b/navi/core/events.py @@ -60,6 +60,14 @@ @dataclass +class ProfileSwitched: + """Emitted by switch_profile tool when it successfully changes the session profile.""" + + profile_id: str + profile_name: str + + +@dataclass class TurnThinking: """Full thinking/reasoning block from a tool-calling turn (complete() response). @@ -74,5 +82,5 @@ AgentEvent = ( ToolStarted | ToolEvent | TextDelta | ThinkingDelta | ThinkingEnd - | StreamEnd | ContextCompressed | TurnThinking + | StreamEnd | ContextCompressed | TurnThinking | ProfileSwitched ) diff --git a/navi/tools/switch_profile.py b/navi/tools/switch_profile.py index 6add542..78b4c4e 100644 --- a/navi/tools/switch_profile.py +++ b/navi/tools/switch_profile.py @@ -1,6 +1,7 @@ """Built-in tool for switching the active agent profile mid-session.""" -from navi.tools.base import Tool, ToolResult, current_session_id +from navi.core.events import ProfileSwitched +from navi.tools.base import Tool, ToolResult, current_event_sink, current_session_id class SwitchProfileTool(Tool): @@ -56,6 +57,11 @@ session.profile_id = profile_id await self._sessions.save(session) + # Notify the client immediately so it can update the UI. + sink = current_event_sink.get() + if sink is not None: + await sink.put(ProfileSwitched(profile_id=profile_id, profile_name=profile.name)) + return ToolResult( success=True, output=(