fix: switching profiles mid-conversation starts a new session instead of cross-tagging

A session with messages belongs to the profile it was created under. Switching
profiles while a conversation is in progress should not retag that session or
update its workspace/model in place — that would corrupt the session's context.

New behavior:
- Session has NO messages (empty): profile switch updates it in place (model,
  workspace). Works exactly as before — nothing was started yet.
- Session HAS messages (in progress): profile switch calls newSession() to
  start a fresh session tagged to the new profile. The old session is left
  untouched. Toast: 'Switched to profile: X — new conversation started'.
- Agent busy: blocked as before, no change.

Also: S._profileDefaultWorkspace is now consumed (set to null) inside
newSession() after the first use, so it doesn't keep forcing the same
workspace on every subsequent new session after a switch.
This commit is contained in:
Nathan Esquenazi
2026-04-03 20:27:50 +00:00
parent 4eae6c98f9
commit da43a6a09a
2 changed files with 38 additions and 22 deletions

View File

@@ -660,35 +660,40 @@ document.addEventListener('click', e => {
async function switchToProfile(name) {
if (S.busy) { showToast('Cannot switch profiles while agent is running'); return; }
// Determine whether the current session has any messages.
// A session with messages is "in progress" and belongs to the current profile —
// we must not retag it. We'll start a fresh session for the new profile instead.
const sessionInProgress = S.session && S.messages && S.messages.length > 0;
try {
const data = await api('/api/profile/switch', { method: 'POST', body: JSON.stringify({ name }) });
S.activeProfile = data.active || name;
// Clear stale model pref so profile default applies
// ── Model ──────────────────────────────────────────────────────────────
localStorage.removeItem('hermes-webui-model');
// Refresh model dropdown (profile may have different provider/models)
_skillsData = null;
await populateModelDropdown();
// Apply profile's default model using the smart resolver (handles id mismatches
// like 'claude-sonnet-4-6' vs 'anthropic/claude-sonnet-4.6' in the dropdown)
if (data.default_model) {
const sel = $('modelSelect');
const resolved = _applyModelToDropdown(data.default_model, sel);
const modelToUse = resolved || data.default_model;
// Also update the current session's model so syncTopbar() doesn't fight us
if (S.session) {
S._pendingProfileModel = modelToUse;
// Only patch the in-memory session model if we're NOT about to replace the session
if (S.session && !sessionInProgress) {
S.session.model = modelToUse;
}
// Store as pending so syncTopbar skips its model override on the next call
S._pendingProfileModel = modelToUse;
}
// Refresh workspace list (now profile-local)
// ── Workspace ──────────────────────────────────────────────────────────
_workspaceList = null;
await loadWorkspaceList();
// Apply the profile's default workspace to the current session
if (data.default_workspace) {
if (S.session) {
// Update existing session's workspace to the profile default
// Always store the profile default for new sessions
S._profileDefaultWorkspace = data.default_workspace;
if (S.session && !sessionInProgress) {
// Empty session (no messages yet) — safe to update it in place
try {
await api('/api/session/update', { method: 'POST', body: JSON.stringify({
session_id: S.session.session_id,
@@ -698,21 +703,31 @@ async function switchToProfile(name) {
S.session.workspace = data.default_workspace;
} catch (_) {}
}
// Store as the profile default so the next new session picks it up
S._profileDefaultWorkspace = data.default_workspace;
}
// Reset profile filter and refresh session list
// ── Session ────────────────────────────────────────────────────────────
_showAllProfiles = false;
await renderSessionList();
syncTopbar();
// Refresh visible sidebar panels
if (sessionInProgress) {
// The current session has messages and belongs to the previous profile.
// Start a new session for the new profile so nothing gets cross-tagged.
await newSession(false);
await renderSessionList();
showToast('Switched to profile: ' + name + ' — new conversation started');
} else {
// No messages yet — just refresh the list and topbar in place
await renderSessionList();
syncTopbar();
showToast('Switched to profile: ' + name);
}
// ── Sidebar panels ─────────────────────────────────────────────────────
if (_currentPanel === 'skills') await loadSkills();
if (_currentPanel === 'memory') await loadMemory();
if (_currentPanel === 'tasks') await loadCrons();
if (_currentPanel === 'profiles') await loadProfilesPanel();
if (_currentPanel === 'workspaces') await loadWorkspacesPanel();
showToast('Switched to profile: ' + name);
} catch (e) { showToast('Switch failed: ' + e.message); }
}