From 7941aaa6605a3e8df183400dac42609a27cddf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Thu, 24 Sep 2026 14:48:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=8D=E7=AB=AF=EF=BC=9A=E5=9F=8B=E7=82=B9?= =?UTF-8?q?=E5=8F=A5=E6=9F=84=E5=8F=AA=E5=9C=A8=E7=BB=93=E6=9E=84=E5=8C=96?= =?UTF-8?q?=E6=8B=92=E5=8D=95=E6=97=B6=E6=B8=85=E6=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `runTurn` 的 catch 先读结构化拒单,只有"这一轮没接单"的拒单才清 `pendingRunAnalyticsRef`;非结构化错误(IPC 失败、命令 panic)可能发生在接单之后,句柄留着等 `turn.completed` 结算,不再让宿主侧这一轮的候选永远没人结算 - chat-composer 用例的注释同步:这一轮没接单,就没有回合终态事件来驱动结算 --- .../controller/useDirectProjectChatController.ts | 13 +++++++++---- .../tests/appSurface/chat-composer.suite.ts | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts index 803d821fe..7a3423117 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts +++ b/apps/ai-game-creator-shell/src/view/project-development/chat/controller/useDirectProjectChatController.ts @@ -666,13 +666,18 @@ export function useDirectProjectChatController({ turnAccepted = true; // 清单刷新统一交给 startTurn 的 finally:成功与报错路径都覆盖,且只读一次。 } catch (error) { - // 拒单那一轮没有接单,也就不会有埋点候选:句柄只清不发(发出去也只是空操作)。 - if (pendingRunAnalyticsRef.current?.clientTurnId === input.clientTurnId) { - pendingRunAnalyticsRef.current = null; - } // 命令的拒单是**结构化的**:命令返回 `Ok` 只说明接单成立,所以这条 catch 从接单化之后 // 只剩"拒单"一种输入(整轮结果由 `turn.completed` 事件回答,不再回到这里)。 const rejection = readDirectTurnRejection(error); + // 只有结构化拒单能证明"这一轮没接单"(拒单不产生回合事件、也就不会有埋点候选),句柄才 + // 只清不发;非结构化错误(IPC 失败、命令 panic)可能发生在接单之后,那时必须留着句柄等 + // `turn.completed` 来结算——提前清掉会让宿主侧这一轮的候选永远没有人结算。 + if ( + rejection && + pendingRunAnalyticsRef.current?.clientTurnId === input.clientTurnId + ) { + pendingRunAnalyticsRef.current = null; + } if (rejection) { const notice = directTurnRejectionNotice(rejection); if (notice) { diff --git a/apps/ai-game-creator-shell/tests/appSurface/chat-composer.suite.ts b/apps/ai-game-creator-shell/tests/appSurface/chat-composer.suite.ts index ed3a94467..a01a07d3f 100644 --- a/apps/ai-game-creator-shell/tests/appSurface/chat-composer.suite.ts +++ b/apps/ai-game-creator-shell/tests/appSurface/chat-composer.suite.ts @@ -420,7 +420,7 @@ export function registerChatComposerControlTests() { ); expect(attempts).toHaveLength(1); expect(refresh).not.toHaveBeenCalled(); - // 拒单没有接单、也就没有埋点候选:句柄直接丢掉,不产生一次注定被丢弃的结算。 + // 这一轮没有接单,不会有回合终态事件来驱动结算:埋点一次也不结算。 expect( invoke.mock.calls.filter( ([command]) => command === 'settle_direct_run_analytics',