feat(coding): add conversation archive and first-message titles
This commit is contained in:
@@ -100,6 +100,60 @@ async function createConversation(
|
||||
}
|
||||
|
||||
describe('PI-100 coding core Host contract', () => {
|
||||
it('names new conversations from the first real user event without a Renderer stream', async () => {
|
||||
const result = await setup();
|
||||
result.conversations.dispose();
|
||||
let publish!: (event: ConversationPatchEnvelope) => void;
|
||||
vi.spyOn(result.runtime, 'subscribe').mockImplementation((listener) => { publish = listener; return () => undefined; });
|
||||
const service = new CodingConversationService(result.projects, result.runtime);
|
||||
const conversation = await service.createConversation({ agentId: 'builder', title: '新对话' });
|
||||
await service.getSnapshot(conversation.id);
|
||||
const sendUser = (text: string, status: 'complete' | 'optimistic' = 'complete') => publish({
|
||||
conversationId: conversation.id, workerGeneration: 1, seq: 1, at: 1,
|
||||
patch: { op: 'message.upsert', node: { kind: 'message', id: text, role: 'user', status,
|
||||
blocks: [{ kind: 'text', id: text, text, status: 'complete' }] } },
|
||||
});
|
||||
sendUser('未被接受的草稿', 'optimistic');
|
||||
expect((await service.getConversation(conversation.id)).title).toBe('新对话');
|
||||
sendUser('修复登录页\n这是第二行');
|
||||
sendUser('后续消息');
|
||||
await vi.waitFor(async () => expect((await service.getConversation(conversation.id)).title).toBe('修复登录页'));
|
||||
await service.patchConversation(conversation.id, { title: '我的标题' });
|
||||
sendUser('再次更新');
|
||||
expect((await service.getConversation(conversation.id)).title).toBe('我的标题');
|
||||
expect(await service.getConversation(conversation.id)).not.toHaveProperty('titleMode');
|
||||
service.dispose();
|
||||
});
|
||||
|
||||
it('archives without stopping work, emits metadata, and guards only new prompts and forks', async () => {
|
||||
const result = await setup();
|
||||
const conversation = await createConversation(result.conversations);
|
||||
await result.conversations.acceptPrompt({ conversationId: conversation.id, clientRequestId: 'first',
|
||||
mode: 'prompt', text: 'Build' });
|
||||
const dispose = vi.spyOn(result.runtime, 'dispose');
|
||||
const abort = vi.spyOn(result.runtime, 'abort');
|
||||
const stream = await result.conversations.openEventStream();
|
||||
await result.conversations.patchConversation(conversation.id, { archived: true });
|
||||
expect((await stream.events[Symbol.asyncIterator]().next()).value).toEqual({
|
||||
type: 'conversation.metadata-changed', projectId: 'project-a', conversationId: conversation.id,
|
||||
});
|
||||
expect(dispose).not.toHaveBeenCalled();
|
||||
expect(abort).not.toHaveBeenCalled();
|
||||
expect((await result.runtime.getSnapshot(conversation.id)).run.status).toBe('running');
|
||||
for (const mode of ['prompt', 'steer', 'follow-up']) {
|
||||
await expect(result.conversations.acceptPrompt({ conversationId: conversation.id,
|
||||
clientRequestId: mode, mode, text: 'New work' })).rejects.toMatchObject({ code: 'CODING_CONVERSATION_ARCHIVED' });
|
||||
}
|
||||
await expect(result.conversations.fork(conversation.id, 'user-entry')).rejects.toMatchObject({ code: 'CODING_CONVERSATION_ARCHIVED' });
|
||||
await expect(result.conversations.acceptPrompt({ conversationId: conversation.id, clientRequestId: 'first',
|
||||
mode: 'prompt', text: 'Build' })).resolves.toMatchObject({ accepted: true });
|
||||
await result.conversations.abort(conversation.id);
|
||||
expect(abort).toHaveBeenCalledOnce();
|
||||
await result.conversations.patchConversation(conversation.id, { archived: false });
|
||||
expect((await result.conversations.getConversation(conversation.id)).archivedAt).toBeNull();
|
||||
stream.close();
|
||||
});
|
||||
|
||||
it('uses one vendor-neutral Main composition without spawning on create', async () => {
|
||||
const projectPath = await mkdtemp(path.join(tmpdir(), 'makelore-pi-composition-project-'));
|
||||
const userDataDir = await mkdtemp(path.join(tmpdir(), 'makelore-pi-composition-user-'));
|
||||
|
||||
Reference in New Issue
Block a user