Merge teacher composer and compact prompt UI into main
This commit is contained in:
commit
2b2d39449c
62 files changed
+2327
-158
No files matched your search
@@ -103,6 +103,7 @@ async function fixture({ durableSource = false, sourceContext = context, liveMod
|
||||
const prepareCloud = vi.fn((..._args: unknown[]) => ({ inputLimit: 8000, run }));
|
||||
const createService = () => new CodingTeacherService({
|
||||
projects,
|
||||
models: async () => ({ supported: true, models: [{ id: 'qwen' }, { id: 'other-model' }] }),
|
||||
runtime: new InMemoryConversationRuntime(),
|
||||
userDataDir: root,
|
||||
account: async () => account,
|
||||
@@ -164,6 +165,35 @@ async function fixture({ durableSource = false, sourceContext = context, liveMod
|
||||
};
|
||||
}
|
||||
describe('cloud coding teacher', () => {
|
||||
it.each([false, true])('persists terminal times for local/cloud=%s replies across restart', async cloudTeacher => {
|
||||
const f = await fixture({ cloudTeacher, mockCloud: cloudTeacher });
|
||||
const topic = await f.service.create(f.scope, undefined, undefined, cloudTeacher ? 9 : undefined);
|
||||
const id = '22222222-2222-4222-8222-222222222222';
|
||||
await f.service.send(f.scope, topic.id, { requestId: id, text: '解释一下' });
|
||||
const running = (await f.service.read(f.scope, topic.id)).requests[0];
|
||||
expect(running.finishedAt).toBeUndefined();
|
||||
const endingAfter = Date.now();
|
||||
f.finish();
|
||||
await vi.waitFor(async () => expect((await f.service.read(f.scope, topic.id)).requests[0].finishedAt).toBeTruthy());
|
||||
const saved = (await f.service.read(f.scope, topic.id)).requests[0];
|
||||
expect(saved.createdAt).toBe(running.createdAt);
|
||||
expect(Date.parse(saved.finishedAt!)).toBeGreaterThanOrEqual(endingAfter);
|
||||
expect(Date.parse(saved.finishedAt!)).toBeLessThanOrEqual(Date.now());
|
||||
const restarted = await f.restart();
|
||||
expect((await restarted.read(f.scope, topic.id)).requests[0]).toEqual(saved);
|
||||
});
|
||||
it.each(['cancelled', 'failed'] as const)('also saves the actual terminal time for %s replies', async status => {
|
||||
const f = await fixture();
|
||||
if (status === 'failed') f.run.mockRejectedValueOnce(new Error('failure'));
|
||||
const topic = await f.service.create(f.scope);
|
||||
const id = '22222222-2222-4222-8222-222222222222';
|
||||
await f.service.send(f.scope, topic.id, { requestId: id, text: '解释一下' });
|
||||
if (status === 'cancelled') await f.service.cancel(f.scope, topic.id, id);
|
||||
await vi.waitFor(async () => expect((await f.service.read(f.scope, topic.id)).requests[0]).toMatchObject({ status, finishedAt: expect.any(String) }));
|
||||
const saved = (await f.service.read(f.scope, topic.id)).requests[0];
|
||||
expect((await (await f.restart()).read(f.scope, topic.id)).requests[0].finishedAt).toBe(saved.finishedAt);
|
||||
});
|
||||
|
||||
it('does not change the selected discussion when the companion polls an older topic', async () => {
|
||||
const f = await fixture();
|
||||
const old = await f.service.create(f.scope);
|
||||
@@ -1633,3 +1663,22 @@ describe('project consultations with selected cloud teachers', () => {
|
||||
await vi.waitFor(async () => expect((await f.service.read(scope, topic.id)).requests[0].status).toBe('completed'));
|
||||
});
|
||||
});
|
||||
|
||||
it.each([false, true])('freezes explicit model without changing teacher definition (cloud=%s)', async cloudTeacher => {
|
||||
const f = await fixture({ cloudTeacher, mockCloud: cloudTeacher });
|
||||
const t = await f.service.create(f.scope, undefined, undefined, cloudTeacher ? 9 : undefined);
|
||||
const input = { requestId: '22222222-2222-4222-8222-222222222222', text: '帮我理解', modelId: 'other-model' };
|
||||
const accepted = await f.service.send(f.scope, t.id, input);
|
||||
expect(accepted.requests[0].modelId).toBe('other-model');
|
||||
expect(accepted.definition.model.model_id).toBe('qwen');
|
||||
if (!cloudTeacher) expect(f.prepareModel.mock.calls[0][1].model).toEqual({ model_id: 'other-model', reasoning_choice: { mode: 'default' } });
|
||||
await expect(f.service.send(f.scope, t.id, { ...input, modelId: 'qwen' })).rejects.toMatchObject({ code: 'teacher_request_conflict' });
|
||||
await f.service.send(f.scope, t.id, input);
|
||||
expect(f.run).toHaveBeenCalledTimes(1);
|
||||
f.finish();
|
||||
await vi.waitFor(async () => expect((await f.service.read(f.scope, t.id)).requests[0].status).toBe('completed'));
|
||||
const restarted = await f.restart();
|
||||
expect((await restarted.read(f.scope, t.id)).requests[0].modelId).toBe('other-model');
|
||||
await expect(restarted.send(f.scope, t.id, { ...input, requestId: '33333333-3333-4333-8333-333333333333', modelId: 'unavailable' })).rejects.toMatchObject({ code: 'teacher_model_unavailable' });
|
||||
expect(f.run).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
Reference in new issue
Block a user