fix session rename (#1046)
This commit is contained in:
@@ -25,6 +25,7 @@ import {
|
||||
clearSessionLabelHydrationTracking,
|
||||
finishSessionLabelHydration,
|
||||
getSessionLabelHydrationCandidate,
|
||||
getSessionLabelHydrationVersion,
|
||||
} from './chat/session-label-hydration';
|
||||
import {
|
||||
DEFAULT_CANONICAL_PREFIX,
|
||||
@@ -147,14 +148,15 @@ function applySessionLabelSummaries(
|
||||
|
||||
for (const summary of summaries) {
|
||||
const labelText = toSessionLabel(summary.firstUserText || '');
|
||||
if (labelText) {
|
||||
if (nextLabels[summary.sessionKey] !== labelText) {
|
||||
if (nextLabels === state.sessionLabels) {
|
||||
nextLabels = { ...state.sessionLabels };
|
||||
}
|
||||
nextLabels[summary.sessionKey] = labelText;
|
||||
changed = true;
|
||||
// Only auto-hydrate missing labels. Existing entries include user renames
|
||||
// and must not be overwritten by transcript-derived titles.
|
||||
const existingLabel = nextLabels[summary.sessionKey]?.trim();
|
||||
if (labelText && !existingLabel) {
|
||||
if (nextLabels === state.sessionLabels) {
|
||||
nextLabels = { ...state.sessionLabels };
|
||||
}
|
||||
nextLabels[summary.sessionKey] = labelText;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (typeof summary.lastTimestamp === 'number' && Number.isFinite(summary.lastTimestamp)) {
|
||||
@@ -2204,9 +2206,18 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
throw err;
|
||||
}
|
||||
|
||||
const session = get().sessions.find((entry) => entry.key === key);
|
||||
if (session) {
|
||||
finishSessionLabelHydration(
|
||||
key,
|
||||
getSessionLabelHydrationVersion(session, get().sessionLastActivity),
|
||||
'backend-label',
|
||||
);
|
||||
}
|
||||
|
||||
set((s) => ({
|
||||
sessions: s.sessions.map((session) =>
|
||||
session.key === key ? { ...session, label: normalized } : session,
|
||||
sessions: s.sessions.map((entry) =>
|
||||
entry.key === key ? { ...entry, label: normalized } : entry,
|
||||
),
|
||||
sessionLabels: { ...s.sessionLabels, [key]: normalized },
|
||||
}));
|
||||
|
||||
@@ -193,7 +193,7 @@ export function createSessionActions(
|
||||
const labelText = firstUser ? getMessageText(firstUser.content).trim() : '';
|
||||
set((s) => {
|
||||
const next: Partial<typeof s> = {};
|
||||
if (labelText) {
|
||||
if (labelText && !s.sessionLabels[session.key]?.trim()) {
|
||||
const truncated = labelText.length > 50 ? `${labelText.slice(0, 50)}…` : labelText;
|
||||
next.sessionLabels = { ...s.sessionLabels, [session.key]: truncated };
|
||||
}
|
||||
|
||||
@@ -409,4 +409,72 @@ describe('chat store session label summary hydration', () => {
|
||||
});
|
||||
expect(useChatStore.getState().sessionLabels['agent:main:session-a']).toBe('new label');
|
||||
});
|
||||
|
||||
it('preserves user-renamed labels when visible session summaries refresh', async () => {
|
||||
gatewayRpcMock.mockImplementation(async (method: string, params?: Record<string, unknown>) => {
|
||||
if (method === 'sessions.list') {
|
||||
return {
|
||||
sessions: [
|
||||
{ key: 'agent:main:session-a', displayName: 'Session A', updatedAt: 1000 },
|
||||
{ key: 'agent:main:main', displayName: 'Main', updatedAt: 1001 },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (method === 'chat.history') {
|
||||
return {
|
||||
messages: [{ role: 'user', content: 'visible chat', timestamp: Date.now() }],
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected gateway RPC: ${method} ${JSON.stringify(params)}`);
|
||||
});
|
||||
|
||||
hostApiFetchMock.mockImplementation(async (path: string) => {
|
||||
if (path === '/api/sessions/summaries') {
|
||||
return {
|
||||
success: true,
|
||||
summaries: [
|
||||
{
|
||||
sessionKey: 'agent:main:session-a',
|
||||
firstUserText: 'original first message',
|
||||
lastTimestamp: 1_700_000_000_000,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
return { success: true, summaries: [] };
|
||||
});
|
||||
|
||||
const { useChatStore } = await import('@/stores/chat');
|
||||
useChatStore.setState({
|
||||
currentSessionKey: 'agent:main:main',
|
||||
currentAgentId: 'main',
|
||||
sessions: [
|
||||
{ key: 'agent:main:session-a', displayName: 'Session A', updatedAt: 1000 },
|
||||
{ key: 'agent:main:main', displayName: 'Main', updatedAt: 1001 },
|
||||
],
|
||||
messages: [],
|
||||
sessionLabels: { 'agent:main:session-a': 'Custom name' },
|
||||
sessionLastActivity: {},
|
||||
sending: false,
|
||||
activeRunId: null,
|
||||
streamingText: '',
|
||||
streamingMessage: null,
|
||||
streamingTools: [],
|
||||
pendingFinal: false,
|
||||
lastUserMessageAt: null,
|
||||
pendingToolImages: [],
|
||||
error: null,
|
||||
loading: false,
|
||||
thinkingLevel: null,
|
||||
runError: null,
|
||||
});
|
||||
|
||||
await useChatStore.getState().loadHistory(false);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
expect(useChatStore.getState().sessionLabels['agent:main:session-a']).toBe('Custom name');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user