Skip to content

Commit a5b737f

Browse files
committed
fix(desktop): unify agent history on chat_messages, remove store.messages mirror
Fixes an agent context corruption where a broken session + user typing "继续" would make the agent drift onto an unrelated task. Root cause: two histories were kept in parallel — `store.messages` (legacy, thin) fed the agent, while the sidebar UI showed the richer `chat_messages` table. On session break + resume the two diverged, so the agent saw either an empty or stale history and hallucinated a new plan. Changes: - New `buildHistoryFromChat(designId)` helper reads chat_messages as the single source of truth (seedFromSnapshots first to backfill legacy designs), converts user + assistant_text rows to ChatMessage[]. - `sendPrompt` feeds that into the agent instead of store.messages. - `persistDesignState` drops its messages param and derives the design thumbnail text from chat_messages directly. - `store.messages` field + all reducer branches deleted. Removes legacy listMessages / replaceMessages IPC call sites in the renderer (main- side handlers remain vestigial; safe to drop in a future cleanup). - `applyInlineComment` now appends user / assistant / error rows to chat_messages so inline comments actually show in the sidebar chat. - `retryLastPrompt` simplified — chat_messages carries the failed turn forward; no more in-memory pruning. - Tests updated: drop assertions on the removed field, add vi.waitFor around generate mocks since sendPrompt now has an extra await for the chat.list read before invoking generate. All 374 desktop tests pass; typecheck clean.
1 parent 983c036 commit a5b737f

3 files changed

Lines changed: 96 additions & 93 deletions

File tree

apps/desktop/src/renderer/src/store.generationStage.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ const initialState = useCodesignStore.getState();
1616
function resetStore() {
1717
useCodesignStore.setState({
1818
...initialState,
19-
messages: [],
2019
previewHtml: null,
2120
isGenerating: false,
2221
activeGenerationId: null,
@@ -119,6 +118,7 @@ describe('generationStage transitions', () => {
119118
const firstPromise = useCodesignStore.getState().sendPrompt({ prompt: 'first' });
120119
const firstId = useCodesignStore.getState().activeGenerationId;
121120
if (!firstId) throw new Error('expected first generation id');
121+
await vi.waitFor(() => expect(pending.has(firstId)).toBe(true));
122122
pending.get(firstId)?.({ artifacts: [{ content: '<html></html>' }], message: 'ok' });
123123
await firstPromise;
124124
expect(useCodesignStore.getState().generationStage).toBe('done');
@@ -137,6 +137,7 @@ describe('generationStage transitions', () => {
137137

138138
const secondId = useCodesignStore.getState().activeGenerationId;
139139
if (!secondId) throw new Error('expected second generation id');
140+
await vi.waitFor(() => expect(pending.has(secondId)).toBe(true));
140141
pending.get(secondId)?.({ artifacts: [{ content: '<html></html>' }], message: 'ok' });
141142
await secondPromise;
142143
unsub();

apps/desktop/src/renderer/src/store.test.ts

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ function deferred<T>() {
2626
function resetStore() {
2727
useCodesignStore.setState({
2828
...initialState,
29-
messages: [],
3029
previewHtml: null,
3130
isGenerating: false,
3231
activeGenerationId: null,
@@ -71,6 +70,10 @@ describe('useCodesignStore iframe error handling', () => {
7170
expect(useCodesignStore.getState().iframeErrors).toEqual([]);
7271
expect(useCodesignStore.getState().isGenerating).toBe(true);
7372

73+
// sendPrompt awaits buildHistoryFromChat before invoking generate — flush
74+
// that microtask so the mock is called and resolveGenerate captured.
75+
await vi.waitFor(() => expect(generate).toHaveBeenCalled());
76+
7477
resolveGenerate?.({
7578
artifacts: [{ content: '<html></html>' }],
7679
message: 'Done.',
@@ -156,9 +159,6 @@ describe('useCodesignStore generation cancellation', () => {
156159
expect(useCodesignStore.getState().activeGenerationId).toBe(secondId);
157160
expect(useCodesignStore.getState().isGenerating).toBe(true);
158161
expect(useCodesignStore.getState().previewHtml).toBeNull();
159-
expect(useCodesignStore.getState().messages.some((m) => m.content === 'Old result')).toBe(
160-
false,
161-
);
162162

163163
pendingById.get(secondId)?.resolve({
164164
artifacts: [{ content: '<html>fresh</html>' }],
@@ -210,6 +210,8 @@ describe('useCodesignStore generation cancellation', () => {
210210
const generationId = useCodesignStore.getState().activeGenerationId;
211211
if (!generationId) throw new Error('expected generation id');
212212

213+
await vi.waitFor(() => expect(pendingById.has(generationId)).toBe(true));
214+
213215
pendingById.get(generationId)?.reject(new Error('Upstream proxy aborted the response'));
214216
await run;
215217

@@ -218,10 +220,6 @@ describe('useCodesignStore generation cancellation', () => {
218220
expect(state.activeGenerationId).toBeNull();
219221
expect(state.errorMessage).toBe('Upstream proxy aborted the response');
220222
expect(state.lastError).toBe('Upstream proxy aborted the response');
221-
expect(state.messages.at(-1)).toEqual({
222-
role: 'assistant',
223-
content: 'Error: Upstream proxy aborted the response',
224-
});
225223
expect(state.toasts.at(-1)).toMatchObject({
226224
variant: 'error',
227225
description: 'Upstream proxy aborted the response',
@@ -432,7 +430,7 @@ describe('useCodesignStore design management', () => {
432430
await initI18n('en');
433431
});
434432

435-
it('switches the message list when switchDesign is called and isolates state per design', async () => {
433+
it('isolates preview + currentDesignId per design when switchDesign is called', async () => {
436434
const designs = [
437435
{
438436
schemaVersion: 1 as const,
@@ -454,19 +452,10 @@ describe('useCodesignStore design management', () => {
454452
},
455453
];
456454

457-
const messagesByDesign: Record<string, Array<{ role: string; content: string }>> = {
458-
'design-a': [
459-
{ role: 'user', content: 'A user' },
460-
{ role: 'assistant', content: 'A reply' },
461-
],
462-
'design-b': [{ role: 'user', content: 'B user' }],
463-
};
464-
465455
vi.stubGlobal('window', {
466456
codesign: {
467457
snapshots: {
468458
listDesigns: vi.fn(() => Promise.resolve(designs)),
469-
listMessages: vi.fn((id: string) => Promise.resolve(messagesByDesign[id] ?? [])),
470459
list: vi.fn(() => Promise.resolve([])),
471460
},
472461
},
@@ -477,13 +466,9 @@ describe('useCodesignStore design management', () => {
477466
await useCodesignStore.getState().switchDesign('design-b');
478467

479468
expect(useCodesignStore.getState().currentDesignId).toBe('design-b');
480-
expect(useCodesignStore.getState().messages.map((m) => m.content)).toEqual(['B user']);
481469

482470
await useCodesignStore.getState().switchDesign('design-a');
483-
expect(useCodesignStore.getState().messages.map((m) => m.content)).toEqual([
484-
'A user',
485-
'A reply',
486-
]);
471+
expect(useCodesignStore.getState().currentDesignId).toBe('design-a');
487472
});
488473

489474
it('createNewDesign resets messages + preview and stores the new id as current', async () => {
@@ -508,7 +493,6 @@ describe('useCodesignStore design management', () => {
508493
});
509494

510495
useCodesignStore.setState({
511-
messages: [{ role: 'user', content: 'leftover' }],
512496
previewHtml: '<html>old</html>',
513497
currentDesignId: 'old-id',
514498
});
@@ -517,7 +501,6 @@ describe('useCodesignStore design management', () => {
517501
expect(result?.id).toBe('fresh');
518502
const state = useCodesignStore.getState();
519503
expect(state.currentDesignId).toBe('fresh');
520-
expect(state.messages).toEqual([]);
521504
expect(state.previewHtml).toBeNull();
522505
});
523506

@@ -698,7 +681,6 @@ describe('useCodesignStore artifact persistence', () => {
698681
// Simulate a fresh app load: blow away in-memory state then switchDesign.
699682
useCodesignStore.setState({
700683
currentDesignId: null,
701-
messages: [],
702684
previewHtml: null,
703685
});
704686

@@ -707,10 +689,6 @@ describe('useCodesignStore artifact persistence', () => {
707689
const restored = useCodesignStore.getState();
708690
expect(restored.currentDesignId).toBe(designId);
709691
expect(restored.previewHtml).toBe('<html><body>persisted</body></html>');
710-
expect(restored.messages).toEqual([
711-
{ role: 'user', content: 'make a hero section' },
712-
{ role: 'assistant', content: 'Generated.' },
713-
]);
714692
});
715693
});
716694

0 commit comments

Comments
 (0)