fix(pi): retain ownership for uncertain mutations

This commit is contained in:
2026-08-25 23:53:54 +08:00
parent 621ebb1781
commit 019cbb115a
21 changed files with 1113 additions and 67 deletions

View File

@@ -2,6 +2,7 @@ import { act, fireEvent, render, screen, waitFor, within } from '@testing-librar
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AppError } from '@/lib/error-model';
import type { ConversationSnapshot } from '@/types/coding-conversation';
import type {
CodingConversationMetadata,
@@ -596,6 +597,89 @@ describe('CodingChatPanel first Conversation', () => {
await waitFor(() => expect(conversationApi.submit).toHaveBeenCalledTimes(2));
});
it('clears a confirmation uncertainty automatically after the authoritative run settles', async () => {
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });
projectApi.conversations.mockResolvedValue([conversation]);
conversationApi.events.mockResolvedValue(new FakeEventSource() as unknown as EventSource);
conversationApi.recover.mockResolvedValue(undefined);
conversationApi.submit.mockRejectedValue(new AppError(
'UNKNOWN',
'请求确认延迟,可能仍在执行。请等待结果,或中止/恢复后再重试。',
undefined,
{ backendCode: 'CODING_REQUEST_UNCERTAIN' },
));
const { CodingChatPanel } = await import('@/pages/Chat/CodingChatPanel');
const { createLocalConversationSnapshot } = await import('@/pages/Chat/coding-chat-snapshot');
const { codingConversationStore } = await import('@/stores/coding-conversations');
conversationApi.snapshot.mockResolvedValue(createLocalConversationSnapshot(project.id, conversation));
render(<CodingChatPanel />);
const textbox = await screen.findByRole('textbox');
await waitFor(() => expect(codingConversationStore.getState().selectedConversationId)
.toBe(conversation.id));
fireEvent.change(textbox, { target: { value: 'Slow prompt' } });
fireEvent.click(screen.getByRole('button', { name: '发送' }));
expect(await screen.findByText(/请求确认延迟,可能仍在执行/)).toBeInTheDocument();
expect(textbox).toHaveValue('Slow prompt');
act(() => codingConversationStore.getState().applyPatchBatchEvent({
type: 'patch-batch',
conversationId: conversation.id,
workerGeneration: 0,
fromSeq: 1,
toSeq: 1,
items: [{
seq: 1,
at: 1_000,
runId: 'run-uncertain',
patch: {
op: 'run.state',
run: {
status: 'running',
runId: 'run-uncertain',
mode: 'prompt',
error: {
code: 'CODING_REQUEST_UNCERTAIN',
message: '请求确认延迟,可能仍在执行。',
recoverable: true,
},
},
},
}],
}));
await waitFor(() => expect(screen.getByRole('button', { name: '整理上下文' })).toBeDisabled());
act(() => codingConversationStore.getState().applyPatchBatchEvent({
type: 'patch-batch',
conversationId: conversation.id,
workerGeneration: 0,
fromSeq: 2,
toSeq: 2,
items: [{
seq: 2,
at: 12_000,
runId: 'run-uncertain',
patch: {
op: 'run.state',
run: {
status: 'idle',
runId: 'run-uncertain',
mode: 'prompt',
settledAt: 12_000,
terminalReason: 'completed',
},
},
}],
}));
await waitFor(() => expect(screen.queryByText(/请求确认延迟,可能仍在执行/))
.not.toBeInTheDocument());
expect(screen.queryByText(/本地编程运行时暂时不可用/)).not.toBeInTheDocument();
expect(textbox).toHaveValue('Slow prompt');
expect(screen.getByRole('button', { name: '发送' })).toBeEnabled();
});
it('caps one message at 16 images and uploads at most four concurrently', async () => {
projectApi.list.mockResolvedValue({ projects: [project], activeProjectId: project.id });
projectApi.config.mockResolvedValue({ project, config });