终止按钮改按综合繁忙信号判定

- handleCancelDirectCodexTurn 的守卫改用 supervisorChatBusy,与「终止」按钮的可见条件对齐
- 刚提交、turn.started 还没到达的窗口里点击终止不再回"当前没有正在运行的回合"
- chat-composer 用例补一条:生命周期事件缺席时也能发出 cancel_direct_codex_turn
This commit is contained in:
2026-09-17 00:10:20 +08:00
parent 1b40f030e7
commit 975a2e577c
2 changed files with 40 additions and 1 deletions
+3 -1
View File
@@ -12051,7 +12051,9 @@ export function App({
}
const invoke = resolveTauriInvoke();
const directProjectPath = resolveChatProjectPath(localProject);
if (!invoke || !directProjectPath || !directTurnRunning) {
// 繁忙判据与「终止」按钮的可见条件一致:本地 invoke 正在跑,或订阅说还有一条回合没结束。
// 只认 `turn.started` 推导出来的 `directTurnRunning` 会漏掉"刚提交、事件还没到"的窗口。
if (!invoke || !directProjectPath || !supervisorChatBusy) {
setProjectSupervisorRuntimeError('当前没有正在运行的回合,无法终止。');
return;
}
@@ -524,6 +524,43 @@ export function registerChatComposerControlTests() {
expect(within(surface).getByText('已终止本次回合。')).not.toBeNull();
});
it('terminates a turn that was submitted before its lifecycle event arrived', async () => {
// 刚提交、`turn.started` 还没下发:按钮已经变成「终止」,点击不能回"没有正在运行的回合"。
const pending: Array<{ resolve: (value: string) => void }> = [];
const { invoke, path, surface } = await openDirectCodexSurface({
chat_with_game_creator_direct_codex: () =>
new Promise<string>((resolve) => {
pending.push({ resolve });
}),
cancel_direct_codex_turn: async () => undefined,
});
const composer = within(surface).getByLabelText('陶泥儿对话内容');
await submitDirectTurn(surface, composer, '做一个小游戏');
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith(
'chat_with_game_creator_direct_codex',
expect.objectContaining({ prompt: '做一个小游戏' }),
);
});
const stopButton = await within(surface).findByRole('button', {
name: '终止',
});
fireEvent.click(stopButton);
await waitFor(() => {
expect(invoke).toHaveBeenCalledWith('cancel_direct_codex_turn', {
projectPath: path,
});
});
expect(
within(surface).queryByText('当前没有正在运行的回合,无法终止。'),
).toBeNull();
await act(async () => {
pending[0]?.resolve('回合回复');
});
});
it('moves the reasoning effort control next to the model selector and persists only for later turns', async () => {
let stored = 'high';
const { invoke, surface } = await openDirectCodexSurface({