错误出现已结束信息 #458

Closed
opened 2026-09-22 12:59:00 +08:00 by lhk229 · 1 comment
Member

问题定位
现象原因:发送提示词后到宿主真正回报回合开始之间存在一个空窗期,这期间最新回合被投影成“已结束”。
具体链路:

  1. 用户按下发送后,本地乐观用户气泡立刻出现,并开出一个新回合。但 turn.active 只由 directTurnRunning(reducer 里的 state.turnRunning)决定,而 turnRunning 只有在订阅循环 consume 到原生 turn.started 事件时才变 true(directThreadChat.ts 的 reduceDirectThreadEvent)。
  2. 从 invoke 发出 → Rust runtime 发 turn.started → notify → consume 回前端,这个窗口通常有几百毫秒到数秒。窗口内 turnRunning=false、endedAt=0。
  3. DirectProjectTurn.tsx 的 DirectProjectTurnUsage 渲染条件是 !turn.active && turn.startedAt,此时满足;并且 endedAt = Math.max(turn.endedAt, turn.startedAt) 把缺失的终态时间(0)用开始时间顶替,于是显示“本轮结束于 12:52:41 · 耗时 0.0秒”。
  4. turn.started 事件到达后 active=true,这行消失,回合正常推进 —— 正好对应你看到的“过一会儿会正常推进”。
    注意 turnBusy(本地 invoke 忙态)从发送瞬间就置 true,但它没有参与 buildDirectChatTurns 的 turnRunning,所以覆盖不了这个窗口。
    引入的 commit
    33ddb5de2 — “DirectProject 聊天框改用线程订阅作为唯一运行态来源”(9 月 16 日,#384 前后)。
    这个 commit 之前,active 由 activeTurnId 驱动,而 activeDirectCodexTurnRef 是在发送路径里同步设置的(App.tsx 发送时立即写入),按下发送的瞬间回合就是 active,不存在空窗。改成“订阅是唯一运行态来源”后,active 只能等订阅事件回放,于是引入了这段空窗。Math.max(endedAt, startedAt) 的兜底又把缺失的 endedAt 伪装成了“0.0 秒结束”,让假象更显眼。
问题定位 现象原因:发送提示词后到宿主真正回报回合开始之间存在一个空窗期,这期间最新回合被投影成“已结束”。 具体链路: 1. 用户按下发送后,本地乐观用户气泡立刻出现,并开出一个新回合。但 turn.active 只由 directTurnRunning(reducer 里的 state.turnRunning)决定,而 turnRunning 只有在订阅循环 consume 到原生 turn.started 事件时才变 true([directThreadChat.ts](D:\\Projects\\2026\\Genarrative\\wt3\\apps\\ai-game-creator-shell\\src\\view\\project-development\\chat\\conversation\\directThreadChat.ts) 的 reduceDirectThreadEvent)。 2. 从 invoke 发出 → Rust runtime 发 turn.started → notify → consume 回前端,这个窗口通常有几百毫秒到数秒。窗口内 turnRunning=false、endedAt=0。 3. [DirectProjectTurn.tsx](D:\\Projects\\2026\\Genarrative\\wt3\\apps\\ai-game-creator-shell\\src\\view\\project-development\\chat\\components\\DirectProjectConversation\\DirectProjectTurn.tsx) 的 DirectProjectTurnUsage 渲染条件是 !turn.active && turn.startedAt,此时满足;并且 endedAt = Math.max(turn.endedAt, turn.startedAt) 把缺失的终态时间(0)用开始时间顶替,于是显示“本轮结束于 12:52:41 · 耗时 0.0秒”。 4. turn.started 事件到达后 active=true,这行消失,回合正常推进 —— 正好对应你看到的“过一会儿会正常推进”。 注意 turnBusy(本地 invoke 忙态)从发送瞬间就置 true,但它没有参与 buildDirectChatTurns 的 turnRunning,所以覆盖不了这个窗口。 引入的 commit 33ddb5de2 — “DirectProject 聊天框改用线程订阅作为唯一运行态来源”(9 月 16 日,#384 前后)。 这个 commit 之前,active 由 activeTurnId 驱动,而 activeDirectCodexTurnRef 是在发送路径里同步设置的(App.tsx 发送时立即写入),按下发送的瞬间回合就是 active,不存在空窗。改成“订阅是唯一运行态来源”后,active 只能等订阅事件回放,于是引入了这段空窗。Math.max(endedAt, startedAt) 的兜底又把缺失的 endedAt 伪装成了“0.0 秒结束”,让假象更显眼。
Author
Member

问题定位

现象原因:发送提示词后到宿主真正回报回合开始之间存在一个空窗期,这期间最新回合被投影成“已结束”。

具体链路:

  1. 用户按下发送后,本地乐观用户气泡立刻出现,并开出一个新回合。但 turn.active 只由 directTurnRunning(reducer 里的 state.turnRunning)决定,而 turnRunning 只有在订阅循环 consume 到原生 turn.started 事件时才变 true(directThreadChat.tsreduceDirectThreadEvent)。
  2. 从 invoke 发出 → Rust runtime 发 turn.started → notify → consume 回前端,这个窗口通常有几百毫秒到数秒。窗口内 turnRunning=falseendedAt=0
  3. DirectProjectTurn.tsxDirectProjectTurnUsage 渲染条件是 !turn.active && turn.startedAt,此时满足;并且 endedAt = Math.max(turn.endedAt, turn.startedAt) 把缺失的终态时间(0)用开始时间顶替,于是显示“本轮结束于 12:52:41 · 耗时 0.0秒”。
  4. turn.started 事件到达后 active=true,这行消失,回合正常推进 —— 正好对应你看到的“过一会儿会正常推进”。

注意 turnBusy(本地 invoke 忙态)从发送瞬间就置 true,但它没有参与 buildDirectChatTurnsturnRunning,所以覆盖不了这个窗口。

引入的 commit

33ddb5de2 — “DirectProject 聊天框改用线程订阅作为唯一运行态来源”(9 月 16 日,#384 前后)。

这个 commit 之前,activeactiveTurnId 驱动,而 activeDirectCodexTurnRef 是在发送路径里同步设置的(App.tsx 发送时立即写入),按下发送的瞬间回合就是 active,不存在空窗。改成“订阅是唯一运行态来源”后,active 只能等订阅事件回放,于是引入了这段空窗。Math.max(endedAt, startedAt) 的兜底又把缺失的 endedAt 伪装成了“0.0 秒结束”,让假象更显眼。

修复建议(最小改动)

两个方向,任选其一:

  1. 组件层兜底(更简单、符合现有注释约定)DirectProjectTurnUsageendedAt === 0 时不渲染 —— directThreadChat.ts 里本来就写明“缺失就是缺失,宁可不显示总耗时”,endedAt=0 且非 active 就是不可证明已结束,不该出这行。
  2. 状态层修正DirectProjectChatView 里传 turnRunning: directTurnRunning || turnBusy,让本地发送忙态也算回合进行中,覆盖 invoke 到 turn.started 的窗口。

我建议两个一起改也行,但单做 1 就能消除你看到的错误文案。需要我直接改掉的话告诉我。

# 问题定位 **现象原因**:发送提示词后到宿主真正回报回合开始之间存在一个空窗期,这期间最新回合被投影成“已结束”。 具体链路: 1. 用户按下发送后,本地乐观用户气泡立刻出现,并开出一个新回合。但 `turn.active` 只由 `directTurnRunning`(reducer 里的 `state.turnRunning`)决定,而 `turnRunning` 只有在订阅循环 consume 到原生 `turn.started` 事件时才变 true([directThreadChat.ts](D:\Projects\2026\Genarrative\wt3\apps\ai-game-creator-shell\src\view\project-development\chat\conversation\directThreadChat.ts) 的 `reduceDirectThreadEvent`)。 2. 从 invoke 发出 → Rust runtime 发 `turn.started` → notify → consume 回前端,这个窗口通常有几百毫秒到数秒。窗口内 `turnRunning=false`、`endedAt=0`。 3. [DirectProjectTurn.tsx](D:\Projects\2026\Genarrative\wt3\apps\ai-game-creator-shell\src\view\project-development\chat\components\DirectProjectConversation\DirectProjectTurn.tsx) 的 `DirectProjectTurnUsage` 渲染条件是 `!turn.active && turn.startedAt`,此时满足;并且 `endedAt = Math.max(turn.endedAt, turn.startedAt)` 把缺失的终态时间(0)用开始时间顶替,于是显示“本轮结束于 12:52:41 · 耗时 0.0秒”。 4. `turn.started` 事件到达后 `active=true`,这行消失,回合正常推进 —— 正好对应你看到的“过一会儿会正常推进”。 注意 `turnBusy`(本地 invoke 忙态)从发送瞬间就置 true,但它没有参与 `buildDirectChatTurns` 的 `turnRunning`,所以覆盖不了这个窗口。 # 引入的 commit **`33ddb5de2` — “DirectProject 聊天框改用线程订阅作为唯一运行态来源”**(9 月 16 日,`#384` 前后)。 这个 commit 之前,`active` 由 `activeTurnId` 驱动,而 `activeDirectCodexTurnRef` 是在发送路径里**同步**设置的(App.tsx 发送时立即写入),按下发送的瞬间回合就是 active,不存在空窗。改成“订阅是唯一运行态来源”后,active 只能等订阅事件回放,于是引入了这段空窗。`Math.max(endedAt, startedAt)` 的兜底又把缺失的 endedAt 伪装成了“0.0 秒结束”,让假象更显眼。 # 修复建议(最小改动) 两个方向,任选其一: 1. **组件层兜底(更简单、符合现有注释约定)**:`DirectProjectTurnUsage` 在 `endedAt === 0` 时不渲染 —— `directThreadChat.ts` 里本来就写明“缺失就是缺失,宁可不显示总耗时”,`endedAt=0` 且非 active 就是不可证明已结束,不该出这行。 2. **状态层修正**:`DirectProjectChatView` 里传 `turnRunning: directTurnRunning || turnBusy`,让本地发送忙态也算回合进行中,覆盖 invoke 到 `turn.started` 的窗口。 我建议两个一起改也行,但单做 1 就能消除你看到的错误文案。需要我直接改掉的话告诉我。
k88936 self-assigned this 2026-09-22 13:03:02 +08:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GenarrativeAI/Genarrative#458