refactor(teacher): retire discussion components and keep text replies

This commit is contained in:
鲨鱼辣椒
2026-09-28 13:54:55 +08:00
parent ae0f72d8fa
commit 998796d77c
32 changed files with 1060 additions and 1794 deletions

View File

@@ -12,10 +12,9 @@ import { TeacherError } from '../../electron/coding-teacher/config-client';
import * as teacherConfig from '../../electron/coding-teacher/config-client';
import * as cloudFetch from '../../electron/utils/proxy-fetch';
import { compileTeacherContext, estimateTeacherTokens } from '../../electron/coding-teacher/context';
import { discussionInstructions } from '../../electron/coding-teacher/discussion';
import { teacherReplyInstructions } from '../../electron/coding-teacher/reply';
const TEACHER_BEHAVIOR_PROMPT = '你是麦洛的创作老师';
import { parseTeacherSuggestions } from '../../electron/coding-teacher/suggestions';
import { parseTeacherDiscussionReply } from '../../shared/teacher-discussion';
import { parseTeacherReply } from '../../shared/teacher-reply';
import type { TeacherRequestIntent, TeacherTopic } from '../../shared/coding-teacher';
const roots: string[] = [];
@@ -366,25 +365,20 @@ it('closes a pending tool as failed when its question fails after a raw tool err
]);
});
it('submits the active discussion protocol, current tool content and selected focus', async () => {
it('submits only the reply protocol without archived component state or focus', async () => {
const f = await fixture();
f.topic.definition.limits.max_input_tokens = 16000;
f.topic.discussion = {
id: 'ideas', revision: 4, status: 'active',
content: { kind: 'ideas', title: '跳跃游戏', items: [{ id: 'gravity', text: '比较两种重力', state: 'kept' }] },
};
const protocol = discussionInstructions(f.topic, { toolId: 'ideas', revision: 4, focusId: 'gravity' });
const { compiled } = await submitCompiledContext(f, 'question', protocol);
f.topic.discussion = { id: 'legacy', revision: 4, status: 'active', content: { kind: 'ideas', title: 'legacy-state-sentinel' } };
const protocol = teacherReplyInstructions();
const { compiled, body } = await submitCompiledContext(f, 'question', protocol);
expect(compiled.messages.at(-2)).toEqual({ role: 'system', content: protocol });
expect(protocol).toContain('只输出一个JSON对象');
expect(protocol).toContain('"status":"active"');
expect(protocol).toContain('"focusId":"gravity"');
expect(protocol).toContain('比较两种重力');
expect(protocol).toContain('quickReplies');
expect(body.query).not.toContain('legacy-state-sentinel');
for (const removed of ['focusId', 'firstItemId', '先这些', 'tool:', '组件']) expect(protocol).not.toContain(removed);
});
it('submits a short first question with the normal discussion protocol and published budget', async () => {
const f = await fixture();
const { body } = await submitCompiledContext(f, 'question', discussionInstructions(f.topic));
const { body } = await submitCompiledContext(f, 'question', teacherReplyInstructions());
expect(body.query).toContain('下一步怎么想?');
});
@@ -400,7 +394,7 @@ it('fits code excerpts beside the unchanged discussion protocol in the original
const f = await fixture();
const original = 'const config = { "title": "小游戏" };\n'.repeat(500);
f.access.source.messages[0].text = original;
const protocol = discussionInstructions(f.topic);
const protocol = teacherReplyInstructions();
const { body, compiled } = await submitCompiledContext(f, 'question', protocol);
expect(compiled.messages.at(-2)?.content).toBe(protocol);
expect(compiled.truncatedMessages).toBeGreaterThan(0);
@@ -738,14 +732,14 @@ it('reconnects from the last cursor and fills the final message after an earlier
expect(f.progress).toHaveBeenCalledWith('连接中断,正在恢复智能体回复…');
});
it.each(['suggestions', 'discussion-v1'] as const)(
it.each(['suggestions', 'reply-v1', 'discussion-v1'] as const)(
'delivers only the final %s JSON after a streamed preamble and a local read', async (format) => {
const f = await fixture();
f.topic.requests.push({
id: requestId, text: '一起讨论', references: [], createdAt: 'now',
sourceCursor: f.access.source.cursor, sourceCapturedAt: 'now',
includedSourceMessageIds: [], omittedMessages: 0, status: 'running', response: '',
...(format === 'suggestions' ? { intent: 'suggestions' } : { presentation: 'discussion-v1' }),
...(format === 'suggestions' ? { intent: 'suggestions' } : { presentation: format }),
});
const result = format === 'suggestions'
? { intro: '我们可以从重力聊起。', questions: ['重力影响了什么?', '怎样比较两种重力?'] }
@@ -795,9 +789,9 @@ it.each(['suggestions', 'discussion-v1'] as const)(
[{ id: requestId + ':file', name: 'read_project_file', status: 'completed' }],
]);
const response = text.mock.calls.map(([delta]) => delta).join('');
expect(format === 'suggestions'
? parseTeacherSuggestions(response)
: parseTeacherDiscussionReply(response)).toMatchObject(result);
expect(parseTeacherReply(response)).toEqual(format === 'suggestions'
? { reply: result.intro, quickReplies: result.questions }
: { reply: result.reply, quickReplies: result.quickReplies });
expect(f.progress).toHaveBeenCalledWith('正在读取项目与会话…');
expect(f.progress).toHaveBeenCalledWith('智能体正在继续思考…');
expect(transport.json).toHaveBeenCalledWith('/runs/before-read/tool-results', {