删除Direct打开项目自动重发测试
移除已废弃的hydrated Direct回合自动恢复场景。 保留当前新发送与历史回放语义的测试覆盖。
This commit is contained in:
@@ -2302,303 +2302,6 @@ export function registerHomeProjectCreationTests() {
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('releases a hydrated Direct Codex turn claim after an in-progress rejection so the same App can resume it later', async () => {
|
||||
const projectPath =
|
||||
'C:\\Users\\tester\\Documents\\Genarrative GameAgent\\running-direct-project';
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'running-direct-project',
|
||||
'运行中直连项目',
|
||||
);
|
||||
const stableTurnId = 'stable-running-turn-001';
|
||||
let directTurnCallCount = 0;
|
||||
const persistedMessages: Array<Record<string, unknown>> = [
|
||||
{
|
||||
schemaVersion: 'game-creator-conversation.v1',
|
||||
role: 'user',
|
||||
content: '继续完成运行中的项目',
|
||||
agentId: null,
|
||||
messageId: `direct-codex:${stableTurnId}:user`,
|
||||
updatedAt: 1,
|
||||
},
|
||||
];
|
||||
const invoke = vi.fn(
|
||||
async (command: string, args?: Record<string, unknown>) => {
|
||||
if (command === 'get_local_game_manifest') {
|
||||
return manifest;
|
||||
}
|
||||
if (command === 'read_local_conversation') {
|
||||
return {
|
||||
path: `${projectPath}/.agent/conversations/project.jsonl`,
|
||||
agentId: null,
|
||||
sessionId: null,
|
||||
messages: [...persistedMessages],
|
||||
};
|
||||
}
|
||||
if (command === 'append_local_permission_log') {
|
||||
return {};
|
||||
}
|
||||
if (command === 'read_project_permission_policy') {
|
||||
return {
|
||||
path: '.agent/policy.json',
|
||||
policy: { deniedCommands: [], confirmCommands: [] },
|
||||
};
|
||||
}
|
||||
if (command === 'append_local_conversation_message') {
|
||||
const message = args?.message as Record<string, unknown>;
|
||||
const messageId = String(args?.messageId ?? '');
|
||||
if (
|
||||
!messageId ||
|
||||
!persistedMessages.some(
|
||||
(candidate) => candidate.messageId === messageId,
|
||||
)
|
||||
) {
|
||||
persistedMessages.push({
|
||||
schemaVersion: 'game-creator-conversation.v1',
|
||||
...message,
|
||||
messageId,
|
||||
updatedAt: Number(
|
||||
message.updatedAt ?? persistedMessages.length + 1,
|
||||
),
|
||||
});
|
||||
}
|
||||
return {
|
||||
path: `${projectPath}/.agent/conversations/project.jsonl`,
|
||||
agentId: null,
|
||||
sessionId: null,
|
||||
messages: [...persistedMessages],
|
||||
};
|
||||
}
|
||||
if (command === 'chat_with_game_creator_direct_codex') {
|
||||
directTurnCallCount += 1;
|
||||
if (directTurnCallCount === 1) {
|
||||
throw new Error(
|
||||
'direct-codex-turn-already-running: 当前 Direct 客户端回合仍在运行',
|
||||
);
|
||||
}
|
||||
return '恢复后的最终回复';
|
||||
}
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
|
||||
render(
|
||||
React.createElement(App, {
|
||||
initialProjectPath: projectPath,
|
||||
initialProjectManifest: manifest,
|
||||
projectSupervisorOnly: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await screen.findByText('继续完成运行中的项目')).not.toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
'chat_with_game_creator_direct_codex',
|
||||
{
|
||||
projectPath,
|
||||
prompt: '继续完成运行中的项目',
|
||||
clientTurnId: stableTurnId,
|
||||
},
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
expect(persistedMessages).toHaveLength(1);
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command, args]) =>
|
||||
command === 'append_local_conversation_message' &&
|
||||
(args as Record<string, unknown> | undefined)?.messageId ===
|
||||
`direct-codex:${stableTurnId}:assistant`,
|
||||
),
|
||||
).toHaveLength(0);
|
||||
expect(screen.queryByText(/direct-codex-turn-already-running/)).toBeNull();
|
||||
|
||||
const directComposer = screen.getByLabelText('陶泥儿对话内容');
|
||||
fireEvent.change(directComposer, { target: { value: '/history' } });
|
||||
fireEvent.submit(directComposer.closest('form') as HTMLFormElement);
|
||||
|
||||
expect(await screen.findByText('恢复后的最终回复')).not.toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(directTurnCallCount).toBe(2);
|
||||
expect(
|
||||
persistedMessages.filter(
|
||||
(message) =>
|
||||
message.messageId === `direct-codex:${stableTurnId}:assistant`,
|
||||
),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
role: 'assistant',
|
||||
content: '恢复后的最终回复',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
expect(
|
||||
invoke.mock.calls.filter(
|
||||
([command]) => command === 'read_local_conversation',
|
||||
).length,
|
||||
).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
caseName: 'a successful reply',
|
||||
firstError: null,
|
||||
firstReply: '首次成功回复',
|
||||
firstVisibleText: '首次成功回复',
|
||||
},
|
||||
{
|
||||
caseName: 'an ordinary error reply',
|
||||
firstError: 'codex-app-server-error:unauthorized',
|
||||
firstReply: null,
|
||||
firstVisibleText: '陶泥儿智能创作 鉴权失败,请检查 API Key 或登录态',
|
||||
},
|
||||
])(
|
||||
'reconciles a hydrated Direct Codex claim after persisting $caseName fails',
|
||||
async ({ firstError, firstReply, firstVisibleText }) => {
|
||||
const projectPath =
|
||||
'C:\\Users\\tester\\Documents\\Genarrative GameAgent\\retry-terminal-persistence';
|
||||
const manifest = createGameCreationAppManifest(
|
||||
'retry-terminal-persistence',
|
||||
'终态持久化重试项目',
|
||||
);
|
||||
const stableTurnId = 'stable-terminal-persistence-001';
|
||||
let directTurnCallCount = 0;
|
||||
let allowAssistantPersistence = false;
|
||||
let failedAssistantPersistenceCount = 0;
|
||||
const directTurnIds: string[] = [];
|
||||
const persistedMessages: Array<Record<string, unknown>> = [
|
||||
{
|
||||
schemaVersion: 'game-creator-conversation.v1',
|
||||
role: 'user',
|
||||
content: '恢复终态持久化失败的回合',
|
||||
agentId: null,
|
||||
messageId: `direct-codex:${stableTurnId}:user`,
|
||||
updatedAt: 1,
|
||||
},
|
||||
];
|
||||
const invoke = vi.fn(
|
||||
async (command: string, args?: Record<string, unknown>) => {
|
||||
if (command === 'get_local_game_manifest') {
|
||||
return manifest;
|
||||
}
|
||||
if (command === 'read_local_conversation') {
|
||||
return {
|
||||
path: `${projectPath}/.agent/conversations/project.jsonl`,
|
||||
agentId: null,
|
||||
sessionId: null,
|
||||
messages: [...persistedMessages],
|
||||
};
|
||||
}
|
||||
if (command === 'append_local_permission_log') {
|
||||
return {};
|
||||
}
|
||||
if (command === 'read_project_permission_policy') {
|
||||
return {
|
||||
path: '.agent/policy.json',
|
||||
policy: { deniedCommands: [], confirmCommands: [] },
|
||||
};
|
||||
}
|
||||
if (command === 'append_local_conversation_message') {
|
||||
const message = args?.message as Record<string, unknown>;
|
||||
if (message.role === 'assistant' && !allowAssistantPersistence) {
|
||||
failedAssistantPersistenceCount += 1;
|
||||
throw new Error('assistant conversation persistence unavailable');
|
||||
}
|
||||
const messageId = String(args?.messageId ?? '');
|
||||
if (
|
||||
!messageId ||
|
||||
!persistedMessages.some(
|
||||
(candidate) => candidate.messageId === messageId,
|
||||
)
|
||||
) {
|
||||
persistedMessages.push({
|
||||
schemaVersion: 'game-creator-conversation.v1',
|
||||
...message,
|
||||
messageId,
|
||||
updatedAt: Number(
|
||||
message.updatedAt ?? persistedMessages.length + 1,
|
||||
),
|
||||
});
|
||||
}
|
||||
return {
|
||||
path: `${projectPath}/.agent/conversations/project.jsonl`,
|
||||
agentId: null,
|
||||
sessionId: null,
|
||||
messages: [...persistedMessages],
|
||||
};
|
||||
}
|
||||
if (command === 'chat_with_game_creator_direct_codex') {
|
||||
directTurnCallCount += 1;
|
||||
directTurnIds.push(String(args?.clientTurnId ?? ''));
|
||||
if (directTurnCallCount === 1) {
|
||||
if (firstError) {
|
||||
throw new Error(firstError);
|
||||
}
|
||||
persistedMessages.push({
|
||||
schemaVersion: 'game-creator-conversation.v1',
|
||||
role: 'assistant',
|
||||
content: firstReply,
|
||||
agentId: null,
|
||||
messageId: `direct-codex:${stableTurnId}:assistant`,
|
||||
updatedAt: 2,
|
||||
});
|
||||
return firstReply ?? '';
|
||||
}
|
||||
allowAssistantPersistence = true;
|
||||
return '恢复后的最终回复';
|
||||
}
|
||||
throw new Error(`unexpected invoke ${command}`);
|
||||
},
|
||||
);
|
||||
window.__TAURI__ = { core: { invoke } };
|
||||
|
||||
render(
|
||||
React.createElement(App, {
|
||||
initialProjectPath: projectPath,
|
||||
initialProjectManifest: manifest,
|
||||
projectSupervisorOnly: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(await screen.findByText(firstVisibleText)).not.toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(directTurnCallCount).toBe(1);
|
||||
expect(failedAssistantPersistenceCount).toBeGreaterThan(
|
||||
firstError ? 1 : 0,
|
||||
);
|
||||
expect(persistedMessages).toHaveLength(firstError ? 1 : 2);
|
||||
});
|
||||
|
||||
const directComposer = screen.getByLabelText('陶泥儿对话内容');
|
||||
fireEvent.change(directComposer, { target: { value: '/history' } });
|
||||
fireEvent.submit(directComposer.closest('form') as HTMLFormElement);
|
||||
|
||||
expect(
|
||||
await screen.findByText(firstError ? '恢复后的最终回复' : firstReply!),
|
||||
).not.toBeNull();
|
||||
await waitFor(() => {
|
||||
expect(directTurnCallCount).toBe(firstError ? 2 : 1);
|
||||
expect(directTurnIds).toEqual(
|
||||
firstError ? [stableTurnId, stableTurnId] : [stableTurnId],
|
||||
);
|
||||
expect(
|
||||
persistedMessages.filter(
|
||||
(message) =>
|
||||
message.messageId === `direct-codex:${stableTurnId}:assistant`,
|
||||
),
|
||||
).toEqual([
|
||||
expect.objectContaining({
|
||||
role: 'assistant',
|
||||
content: firstError ? '恢复后的最终回复' : firstReply,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export function registerRecentProjectsTests() {
|
||||
|
||||
Reference in New Issue
Block a user