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:
@@ -660,35 +660,40 @@ document.addEventListener('click', e => {
|
|||||||
|
|
||||||
async function switchToProfile(name) {
|
async function switchToProfile(name) {
|
||||||
if (S.busy) { showToast('Cannot switch profiles while agent is running'); return; }
|
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 {
|
try {
|
||||||
const data = await api('/api/profile/switch', { method: 'POST', body: JSON.stringify({ name }) });
|
const data = await api('/api/profile/switch', { method: 'POST', body: JSON.stringify({ name }) });
|
||||||
S.activeProfile = data.active || name;
|
S.activeProfile = data.active || name;
|
||||||
// Clear stale model pref so profile default applies
|
|
||||||
|
// ── Model ──────────────────────────────────────────────────────────────
|
||||||
localStorage.removeItem('hermes-webui-model');
|
localStorage.removeItem('hermes-webui-model');
|
||||||
// Refresh model dropdown (profile may have different provider/models)
|
|
||||||
_skillsData = null;
|
_skillsData = null;
|
||||||
await populateModelDropdown();
|
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) {
|
if (data.default_model) {
|
||||||
const sel = $('modelSelect');
|
const sel = $('modelSelect');
|
||||||
const resolved = _applyModelToDropdown(data.default_model, sel);
|
const resolved = _applyModelToDropdown(data.default_model, sel);
|
||||||
const modelToUse = resolved || data.default_model;
|
const modelToUse = resolved || data.default_model;
|
||||||
// Also update the current session's model so syncTopbar() doesn't fight us
|
S._pendingProfileModel = modelToUse;
|
||||||
if (S.session) {
|
// Only patch the in-memory session model if we're NOT about to replace the session
|
||||||
|
if (S.session && !sessionInProgress) {
|
||||||
S.session.model = modelToUse;
|
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;
|
_workspaceList = null;
|
||||||
await loadWorkspaceList();
|
await loadWorkspaceList();
|
||||||
|
|
||||||
// Apply the profile's default workspace to the current session
|
|
||||||
if (data.default_workspace) {
|
if (data.default_workspace) {
|
||||||
if (S.session) {
|
// Always store the profile default for new sessions
|
||||||
// Update existing session's workspace to the profile default
|
S._profileDefaultWorkspace = data.default_workspace;
|
||||||
|
|
||||||
|
if (S.session && !sessionInProgress) {
|
||||||
|
// Empty session (no messages yet) — safe to update it in place
|
||||||
try {
|
try {
|
||||||
await api('/api/session/update', { method: 'POST', body: JSON.stringify({
|
await api('/api/session/update', { method: 'POST', body: JSON.stringify({
|
||||||
session_id: S.session.session_id,
|
session_id: S.session.session_id,
|
||||||
@@ -698,21 +703,31 @@ async function switchToProfile(name) {
|
|||||||
S.session.workspace = data.default_workspace;
|
S.session.workspace = data.default_workspace;
|
||||||
} catch (_) {}
|
} 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;
|
_showAllProfiles = false;
|
||||||
|
|
||||||
|
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();
|
await renderSessionList();
|
||||||
syncTopbar();
|
syncTopbar();
|
||||||
// Refresh visible sidebar panels
|
showToast('Switched to profile: ' + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sidebar panels ─────────────────────────────────────────────────────
|
||||||
if (_currentPanel === 'skills') await loadSkills();
|
if (_currentPanel === 'skills') await loadSkills();
|
||||||
if (_currentPanel === 'memory') await loadMemory();
|
if (_currentPanel === 'memory') await loadMemory();
|
||||||
if (_currentPanel === 'tasks') await loadCrons();
|
if (_currentPanel === 'tasks') await loadCrons();
|
||||||
if (_currentPanel === 'profiles') await loadProfilesPanel();
|
if (_currentPanel === 'profiles') await loadProfilesPanel();
|
||||||
if (_currentPanel === 'workspaces') await loadWorkspacesPanel();
|
if (_currentPanel === 'workspaces') await loadWorkspacesPanel();
|
||||||
showToast('Switched to profile: ' + name);
|
|
||||||
} catch (e) { showToast('Switch failed: ' + e.message); }
|
} catch (e) { showToast('Switch failed: ' + e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ async function newSession(flash){
|
|||||||
MSG_QUEUE.length=0;updateQueueBadge();
|
MSG_QUEUE.length=0;updateQueueBadge();
|
||||||
S.toolCalls=[];
|
S.toolCalls=[];
|
||||||
clearLiveToolCards();
|
clearLiveToolCards();
|
||||||
// Use profile default workspace for new sessions after a profile switch,
|
// Use profile default workspace for new sessions after a profile switch (one-shot),
|
||||||
// otherwise inherit from the current session (or let server pick the default)
|
// otherwise inherit from the current session (or let server pick the default)
|
||||||
const inheritWs=S._profileDefaultWorkspace||(S.session?S.session.workspace:null);
|
const inheritWs=S._profileDefaultWorkspace||(S.session?S.session.workspace:null);
|
||||||
|
S._profileDefaultWorkspace=null; // consume — only applies to the first new session after switch
|
||||||
const data=await api('/api/session/new',{method:'POST',body:JSON.stringify({model:$('modelSelect').value,workspace:inheritWs})});
|
const data=await api('/api/session/new',{method:'POST',body:JSON.stringify({model:$('modelSelect').value,workspace:inheritWs})});
|
||||||
S.session=data.session;S.messages=data.session.messages||[];
|
S.session=data.session;S.messages=data.session.messages||[];
|
||||||
if(flash)S.session._flash=true;
|
if(flash)S.session._flash=true;
|
||||||
|
|||||||
Reference in New Issue
Block a user