fix: preserve complete consultation replies and recover retained history

This commit is contained in:
鲨鱼辣椒 committed 2026-09-28 17:38:31 +08:00
1 parent dda2f0a3be
commit d530abe580
14 files changed
+643 -38

No files matched your search

+51
View File
@@ -22,6 +22,7 @@ type HostConnection = {
};
type TeacherCatalogFixture = {
nextTeacherReplyIncomplete?: boolean;
teacherCatalog: TeacherCatalog;
teacherCatalogFailure: boolean;
holdTeacherCatalog: boolean;
@@ -575,6 +576,12 @@ async function installCodingFirstChatHost(
const suggestionRequest = body!.intent === 'suggestions';
const teacherResponse = suggestionRequest ? '我们可以从你最近试过的地方聊起。' : '先理解状态如何随点击变化,再修改代码。';
consultationTopics[consultationPath]={...currentTopic,revision:Number(currentTopic?.revision??0)+1,requests:[...(currentTopic?.requests as unknown[]??[]),{id:body!.requestId,text:body!.text,intent:body!.intent,references:body!.references??[],createdAt:now,sourceCursor:{workerGeneration:1,seq:1},sourceCapturedAt:now,includedSourceMessageIds:[],omittedMessages:0,truncatedMessages:1,status:'completed',response:teacherResponse,...(suggestionRequest?{suggestedQuestions:['怎样观察别人玩游戏?','我该先试哪个想法?']}:{})}]};
if (state.nextTeacherReplyIncomplete) {
state.nextTeacherReplyIncomplete = false;
const reply = (consultationTopics[consultationPath].requests as Record<string, unknown>[]).at(-1)!;
Object.assign(reply, { response: '它早就不是', replyIncomplete: true, replyParseError: '格式不完整',
sourceConversationId: body!.sourceConversationId, unparsedResponse: '{"reply":"它早就不是"刚搭好架子' });
}
return respond(consultationTopics[consultationPath],202);
}
if (path.endsWith(`${topicBase}/${topicId}`)) return respond(currentTopic);
@@ -1894,6 +1901,50 @@ test('project consultations preserve student drafts and switch between work and
});
test('incomplete consultation replies retry only on click and preserve both drafts', async ({ launchElectronApp }) => {
const app = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(app);
const connection = await page.evaluate(async () => ({
token: await window.electron.ipcRenderer.invoke('hostapi:token') as string,
baseUrl: await window.electron.ipcRenderer.invoke('hostapi:base-url') as string,
}));
await installCodingFirstChatHost(app, connection, true);
await settleSnapshot(app);
await disableCodingEventSource(page);
await app.evaluate(() => {
(globalThis as typeof globalThis & { __makelorePiFirstChatE2E: TeacherCatalogFixture })
.__makelorePiFirstChatE2E.nextTeacherReplyIncomplete = true;
});
try {
await page.reload();
page = await getStableWindow(app);
await page.getByTestId('ai-module-option-programming').click();
await page.evaluate(() => { window.location.hash = '/chat'; });
const mainDraft = page.getByTestId('coding-message-composer').getByRole('textbox', { includeHidden: true });
await mainDraft.fill('主对话草稿');
await page.getByRole('button', { name: '与代码智能体聊天', exact: true }).click();
const teacher = page.getByTestId('teacher-chat-panel');
const draft = teacher.getByRole('textbox', { name: '向智能体提问' });
await draft.fill('现在项目是什么阶段?');
await teacher.getByRole('button', { name: '提问', exact: true }).click();
await expect(teacher.getByRole('status')).toHaveText('这次回复未能完整显示');
await expect(teacher.getByTestId('teacher-reply')).toHaveCount(0);
await expect(teacher.getByText('查看收到的原始内容').locator('..')).not.toHaveAttribute('open');
const sends = async () => (await readState(app)).captured.filter(item => item.path.endsWith('/messages') && item.method === 'POST');
expect(await sends()).toHaveLength(1);
await draft.fill('咨询里还没写完的草稿');
await teacher.getByRole('button', { name: '重新回答' }).click();
await expect(teacher.getByText('先理解状态如何随点击变化,再修改代码。')).toBeVisible();
await expect(draft).toHaveValue('咨询里还没写完的草稿');
await expect(mainDraft).toHaveValue('主对话草稿');
const inputs = await sends();
expect(inputs).toHaveLength(2);
expect(inputs[1].path).toBe(inputs[0].path);
expect(inputs[1].body).toEqual({ ...inputs[0].body, requestId: expect.any(String) });
expect(inputs[1].body!.requestId).not.toBe(inputs[0].body!.requestId);
} finally { await releaseSnapshot(app); }
});
test('manual agent refresh updates the catalog and retries failures without replacing the open topic or drafts', async ({ launchElectronApp }) => {
const electronApp = await launchElectronApp({ skipSetup: true });
let page = await getStableWindow(electronApp);