Merge cloud-owned teaching policy and bounded new shortcuts
This commit is contained in:
@@ -21,6 +21,13 @@ type HostConnection = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
type ConsultationReply = {
|
||||
response: string;
|
||||
suggestedQuestions: string[];
|
||||
replyParseError?: string;
|
||||
unparsedResponse?: string;
|
||||
};
|
||||
|
||||
type TeacherCatalogFixture = {
|
||||
nextTeacherReplyIncomplete?: boolean;
|
||||
teacherCatalog: TeacherCatalog;
|
||||
@@ -97,9 +104,10 @@ async function installCodingFirstChatHost(
|
||||
managedCapabilities = false,
|
||||
removedModel = false,
|
||||
audioPreview?: { executionId: string; path: string; dataUrl: string },
|
||||
consultationReply?: ConsultationReply,
|
||||
): Promise<void> {
|
||||
await electronApp.evaluate(async (_, payload) => {
|
||||
const { connection, featureComplete, managedCapabilities, removedModel, audioPreview } = payload;
|
||||
const { connection, featureComplete, managedCapabilities, removedModel, audioPreview, consultationReply } = payload;
|
||||
const { ipcMain } = process.mainModule!.require('electron') as typeof import('electron');
|
||||
type MainState = TeacherCatalogFixture & {
|
||||
captured: CapturedRequest[];
|
||||
@@ -575,6 +583,13 @@ async function installCodingFirstChatHost(
|
||||
const suggestions = body!.intent === 'suggestions';
|
||||
const response = suggestions ? '我们可以从你最近试过的地方聊起。' : body!.intent === 'guided-help' ? '你最近做的哪一步,让你停下来想了一会儿?' : '先理解状态如何随点击变化,再修改代码。';
|
||||
consultationTopics[agentId] = {...current,revision:Number(current.revision)+1,requests:[...(current.requests as unknown[]),{id:body!.requestId,projectId:body!.projectId,sourceConversationId:body!.sourceConversationId,text:body!.text,intent:body!.intent,references:body!.references??[],createdAt:new Date().toISOString(),sourceCursor:{workerGeneration:1,seq:1},sourceCapturedAt:now,includedSourceMessageIds:[],omittedMessages:0,truncatedMessages:1,status:'completed',response,...(suggestions?{suggestedQuestions:['怎样观察别人玩游戏?','我该先试哪个想法?']}:{})}]};
|
||||
const last = (consultationTopics[agentId].requests as Record<string, unknown>[]).at(-1)!;
|
||||
Object.assign(last, consultationReply);
|
||||
if (state.nextTeacherReplyIncomplete) {
|
||||
state.nextTeacherReplyIncomplete = false;
|
||||
Object.assign(last, { response: '它早就不是', replyIncomplete: true, replyParseError: '格式不完整',
|
||||
unparsedResponse: '{"reply":"它早就不是"刚搭好架子' });
|
||||
}
|
||||
return respond(consultationTopics[agentId],202);
|
||||
}
|
||||
}
|
||||
@@ -843,7 +858,7 @@ async function installCodingFirstChatHost(
|
||||
if (path === '/api/coding/runtime/diagnostics') return respond({ runtime: { revision: { provider: 1, resources: 1 }, workers: [{ conversationId: conversation.id, generation: 1, state: 'running', stage: 'running' }] } });
|
||||
return respond({ success: false, error: `Unhandled E2E route: ${method} ${path}` }, 404);
|
||||
});
|
||||
}, { connection: hostConnection, featureComplete, managedCapabilities, removedModel, audioPreview });
|
||||
}, { connection: hostConnection, featureComplete, managedCapabilities, removedModel, audioPreview, consultationReply });
|
||||
}
|
||||
|
||||
test('saved Game Audio offers click-only playable local preview in Electron', async ({ launchElectronApp }, testInfo) => {
|
||||
@@ -1965,6 +1980,57 @@ test('incomplete consultation replies retry only on click and preserve both draf
|
||||
} finally { await releaseSnapshot(app); }
|
||||
});
|
||||
|
||||
test('project consultations show reply protocol errors without exposing rejected shortcuts', async ({ launchElectronApp }) => {
|
||||
const electronApp = await launchElectronApp({ skipSetup: true });
|
||||
let page = await getStableWindow(electronApp);
|
||||
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 }));
|
||||
const response = '可以先观察玩家在哪里停下来,再聊聊你想改的地方。';
|
||||
const quickReplies = ['看懂规则|怎样观察玩家?', '想想作用|我想聊聊按钮。', '试试结果|怎样看出变化?', '<button>原始选项</button> **不要渲染**'];
|
||||
const replyParseError = '快捷回复超过当前界面最多 3 条的限制,正文已保留,快捷回复未展示。';
|
||||
const unparsedResponse = '\n' + JSON.stringify({ reply: response, quickReplies }, null, 2) + '\n';
|
||||
await installCodingFirstChatHost(electronApp, connection, true, false, false, undefined, {
|
||||
response, suggestedQuestions: [], replyParseError, unparsedResponse,
|
||||
});
|
||||
await settleSnapshot(electronApp);
|
||||
await disableCodingEventSource(page);
|
||||
try {
|
||||
await page.reload(); page = await getStableWindow(electronApp);
|
||||
await page.getByTestId('ai-module-option-programming').click();
|
||||
await page.evaluate(() => { window.location.hash = '/chat'; });
|
||||
await expect(page.getByTestId('project-conversations')).toBeVisible();
|
||||
const composer = page.getByTestId('coding-message-composer').getByRole('textbox', { includeHidden: true });
|
||||
await composer.fill('保留操作草稿');
|
||||
await page.getByRole('button', { name: '与朋友聊天', exact: true }).click();
|
||||
const teacher = page.getByTestId('teacher-chat-panel');
|
||||
const teacherComposer = teacher.getByRole('textbox', { name: '向智能体提问' });
|
||||
await teacherComposer.fill('还没说完的困惑');
|
||||
await teacher.getByRole('button', { name: '提问', exact: true }).click();
|
||||
const reply = teacher.locator('.consultation-messages > div').filter({ has: page.getByText(response, { exact: true }) });
|
||||
await expect(reply.getByText(response, { exact: true })).toBeVisible();
|
||||
await expect(reply.getByRole('status')).toHaveText('回答正文已保留,部分附加内容未能显示。');
|
||||
await expect(reply.getByRole('status')).toBeVisible();
|
||||
await expect(teacher.locator('.consultation-follow-up')).toHaveCount(0);
|
||||
for (const question of quickReplies) {
|
||||
await expect(teacher.getByRole('button', { name: question, exact: true })).toHaveCount(0);
|
||||
}
|
||||
const raw = reply.locator('details').filter({ has: page.getByText('查看收到的原始内容', { exact: true }) });
|
||||
await expect(raw).not.toHaveAttribute('open');
|
||||
await expect(raw.locator('pre')).toBeHidden();
|
||||
await raw.locator('summary').click();
|
||||
await expect(raw.locator('pre')).toBeVisible();
|
||||
expect(await raw.locator('pre').textContent()).toBe(unparsedResponse);
|
||||
await expect(raw.locator('button, strong')).toHaveCount(0);
|
||||
await expect(teacherComposer).toHaveValue('');
|
||||
await expect(composer).toHaveValue('保留操作草稿');
|
||||
const requests = (await readState(electronApp)).captured;
|
||||
expect(requests.filter(item => item.path.endsWith('/messages') && item.method === 'POST')).toHaveLength(1);
|
||||
expect(requests.filter(item => item.path.endsWith('/prompt') && item.method === 'POST')).toHaveLength(0);
|
||||
} finally {
|
||||
await releaseSnapshot(electronApp);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('manual agent refresh updates the catalog and retries failures without replacing the ongoing chat or drafts', async ({ launchElectronApp }) => {
|
||||
const electronApp = await launchElectronApp({ skipSetup: true });
|
||||
let page = await getStableWindow(electronApp);
|
||||
|
||||
Reference in New Issue
Block a user