fix(pi): settle delayed compact ownership

This commit is contained in:
2026-08-26 09:41:25 +08:00
parent f5e6a04c7d
commit 9f05e2d7e1
12 changed files with 334 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
import { fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import { describe, expect, it, vi } from 'vitest';
import type { ProviderAccount, ProviderVendorInfo } from '@/lib/providers';
import type { ConversationSnapshot } from '@/types/coding-conversation';
const interactionApi = vi.hoisted(() => ({
respond: vi.fn(),
@@ -275,6 +276,95 @@ describe('PI-130 feature-complete Coding UI', () => {
expect(screen.queryByText(/分享|回滚|待办/)).not.toBeInTheDocument();
});
it('clears a compact confirmation uncertainty when the authoritative run settles', async () => {
interactionApi.compact.mockRejectedValueOnce(Object.assign(
new Error('请求确认延迟,可能仍在执行。请等待结果,或中止/恢复后再重试。'),
{ details: { backendCode: 'CODING_REQUEST_UNCERTAIN' } },
));
const conversation = {
id: 'conversation-compact-uncertain',
agentId: 'agent-1',
title: 'Compact uncertainty',
archivedAt: null,
unread: false,
createdAt: '2026-08-25T00:00:00.000Z',
updatedAt: '2026-08-25T00:00:00.000Z',
model: { accountId: 'account-1', modelId: 'model-a', thinkingLevel: 'medium' as const },
modelResolution: 'resolved' as const,
};
const snapshot = (run: ConversationSnapshot['run'], seq = 1): ConversationSnapshot => ({
schemaVersion: 1,
conversation: {
id: conversation.id,
projectId: 'project-1',
agentId: conversation.agentId,
title: conversation.title,
model: { model: conversation.model, modelResolution: 'resolved' },
},
nodes: [],
run,
queue: { items: [] },
context: { usedTokens: 200, contextWindow: 1000, compaction: 'idle' },
pendingInteractions: [],
worker: { status: 'ready', generation: 1 },
cursor: { workerGeneration: 1, seq },
});
const callbacks = {
onRename: vi.fn(async () => undefined),
onArchive: vi.fn(async () => undefined),
onToggleUnread: vi.fn(async () => undefined),
onFork: vi.fn(async () => undefined),
onRefresh: vi.fn(async () => undefined),
onRecover: vi.fn(async () => undefined),
onOpenInspector: vi.fn(),
};
const { CodingConversationHeader } = await import('@/pages/Chat/CodingConversationHeader');
const { rerender } = render(
<CodingConversationHeader
conversation={conversation}
snapshot={snapshot({ status: 'idle' })}
connectionState="live"
{...callbacks}
/>,
);
fireEvent.click(screen.getByRole('button', { name: '整理上下文' }));
expect(await screen.findByText(/请求确认延迟,可能仍在执行/)).toBeInTheDocument();
rerender(
<CodingConversationHeader
conversation={conversation}
snapshot={snapshot({
status: 'compacting',
runId: 'run-compact-uncertain',
error: {
code: 'CODING_REQUEST_UNCERTAIN',
message: '请求确认延迟,可能仍在执行。',
recoverable: true,
},
}, 2)}
connectionState="live"
{...callbacks}
/>,
);
expect(screen.getByText(/请求确认延迟,可能仍在执行/)).toBeInTheDocument();
rerender(
<CodingConversationHeader
conversation={conversation}
snapshot={snapshot({
status: 'idle',
runId: 'run-compact-uncertain',
settledAt: 12_000,
terminalReason: 'completed',
}, 3)}
connectionState="live"
{...callbacks}
/>,
);
await waitFor(() => expect(screen.queryByText(/请求确认延迟,可能仍在执行/))
.not.toBeInTheDocument());
});
it('blocks overlapping mutations but keeps abort and recover available while confirmation is uncertain', async () => {
interactionApi.abort.mockResolvedValue(undefined);
const recover = vi.fn(async () => undefined);