Skip to content

fix: stabilize browser voice session feedback - #29

Merged
lanyue-llk merged 6 commits into
lex-mainfrom
codex/voice-ui-regressions-pr
Sep 14, 2026
Merged

lanyue-llk merged 6 commits into
lex-mainfrom
codex/voice-ui-regressions-pr

Conversation

@lanyue-llk

Copy link
Copy Markdown

变更概述

修复浏览器语音会话中启动等待、dispatch 重试和长对话字幕显示不稳定的问题。

主要改动

1. 稳定会话启动

  • 未分配的 LiveKit dispatch 会及时重试,不再占满整个等待窗口。
  • agent 启动 readiness 使用完整预算,避免冷启动被过早判定失败。

2. 修复字幕展示

  • 保留跨 stream 的最终转写,避免迟到的单字 partial 覆盖完整字幕。
  • 自动滚动 hook 在组件挂载后读取 DOM ref,确保长对话的新字幕持续可见。

3. 增加回归测试

  • 覆盖迟到 partial、dispatch 重试、启动预算和字幕滚动初始化。

影响说明

  • 浏览器语音会话启动和字幕反馈更稳定。
  • 不修改字幕协议、模型 prompt 或后端工具调用流程。

验证

  • pnpm test

    ✅ passed: 285 tests passed

  • pnpm exec tsc --noEmit

    ✅ passed: TypeScript check passed

  • git diff --check lexmount/lex-main...HEAD

    ✅ passed: no whitespace errors

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude finished @lanyue-llk's task in 4m 49s —— View job


Review 结论

有条件通过

问题及建议

  • [P1] tests/chat-message-filter.test.mjs:97 + lib/transcription-history.ts:27-40:新增测试名为“迟到的单字 partial”,但实际断言顺序是 partial → final(同 segment) → 下一轮(不同 segment),这是原有逻辑早已覆盖的正常顺序(final 到达时清理同 segment 的旧 partial)。真正的“迟到”场景应是 final 先到达、旧 partial 后到达(乱序):此时 mergeTranscriptionHistorystreamInfo.id 建 map,旧 partial 会以新 entry 身份被重新 set 回去(因为只在“新 final 到达时删除同 segment 旧 partial”,没有反向逻辑“新 partial 到达但同 segment 已有 final 时应忽略”),最终该单字 partial 会重新出现在字幕列表中。本次 PR 未改动 lib/transcription-history.ts,测试也未验证乱序场景,因此 PR 描述中“避免迟到的单字 partial 覆盖完整字幕”这一修复可能并未真正生效。建议补充“final 先于迟到 partial”的测试用例,并在确认存在问题时于 mergeTranscriptionHistory 中增加:当新到 entry 是 partial 且已存在同 segment/participant 的 final 时应丢弃该 entry。
  • [P2] app/api/session/session-dispatch-service.ts:569-576:新增单次 dispatch 尝试超时(默认 5s)后即删除并重建 dispatch。若 agent worker 冷启动耗时接近或超过 5s(这正是同 PR 中“worker readiness 使用完整预算”想解决的场景),会导致 dispatch 被频繁删除重建、产生额外抖动,且与“避免冷启动被过早判定失败”的目标存在一定张力。建议结合实际冷启动耗时数据校准默认值,或改为随尝试次数递增的窗口而非固定 5s。
  • [P2] app/api/session/session-dispatch-service.ts:569,575attemptDeadline 已经是 Math.min(getDeadline(), Date.now() + attemptTimeoutMs),第 575 行又写 Math.min(getDeadline(), attemptDeadline) 是多余的(attemptDeadline 恒 ≤ getDeadline()),可简化为 () => attemptDeadline,不影响功能,仅为可读性建议。

其余改动(useAutoScroll 改为接收 RefObject 并在 effect 内读取 .current、dispatch 重试逻辑主体、相关测试用例更新)逻辑正确,修复了挂载时序导致自动滚动失效、以及未分配 dispatch 占满整个等待窗口的问题。

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude finished @lanyue-llk's task in 1m 11s —— View job


任务清单

  • 拉取并分析 PR diff(对比 origin/lex-main
  • 阅读改动文件(session-dispatch-service.ts、transcription-history.ts、useAutoScroll.ts、相关测试)
  • 核实上一轮 P1/P2 意见在最新 commit 中的修复情况
  • 输出最终 review 结论

Review 结论

有条件通过

上一轮 P1(迟到 partial 覆盖已完成字幕)已在 1925b81 中修复:lib/transcription-history.ts:26-39 新增“同 segment/participant 已有 final 时丢弃迟到 partial”逻辑,tests/chat-message-filter.test.mjs:97-123 补充了“final 先到达、旧 partial 后到达”的乱序用例,验证通过。

问题及建议

  • [P2] app/api/session/session-dispatch-service.ts:525-528,569:单次 attempt 超时默认仍为 5s(DEFAULT_AGENT_DISPATCH_ATTEMPT_TIMEOUT_MS),与本 PR「worker readiness 使用完整预算避免冷启动误判」的目标仍有张力——冷启动耗时接近/超过 5s 时会被反复删除重建 dispatch。上一轮已提出,本次未调整,建议用实际冷启动数据校准默认值或改为递增窗口。
  • [P2] app/api/session/session-dispatch-service.ts:569,575attemptDeadline 已是 Math.min(getDeadline(), ...),575 行再次 Math.min(getDeadline(), attemptDeadline) 冗余,可简化为 () => attemptDeadline。上一轮已提出,本次未改动,不影响功能。
  • [P2] tests/scroll-area.test.mjs:1-12:新增测试仅对源码文本做正则匹配(useAutoScroll(scrollContentRef) 等字符串),未实际渲染组件或模拟 DOM 滚动行为,无法验证挂载后自动滚动是否真正生效,属于较弱的回归保护,可后续补充基于 DOM/jsdom 的行为测试。

其余改动(dispatch 重试主体、useAutoScroll 改为接收 RefObject 并在 effect 内读取 .current)逻辑正确。

@lanyue-llk

Copy link
Copy Markdown
Author

已核对本轮完整评论清单:PR body 1 条、top-level comment 1 条,formal review、inline comments 和 replies 均为 0。

处理结果:

  • 接受迟到 partial 的 P1。原测试事件顺序确实写反,未覆盖 final 之后旧 partial 才到达的乱序。提交 1925b81 现在会忽略同一 participant、同一 segment 已有 final 后到达的 partial,并用真实乱序顺序锁定回归。
  • 不修改 5 秒 dispatch attempt 窗口。它只限制单次 dispatch 的端到端 readiness 等待,总启动预算仍为 45 秒;延长该窗口会让真正丢失的 dispatch 更晚重试。该值已有环境变量,可根据运行数据调整。LiveKit 的 dispatch 记录不是 Agent 已进房并 ready 的替代判断,因此本轮不增加第二套状态判断。
  • 多余的 Math.min 只是可读性建议,不影响行为,本轮不做无关清理。

验证:pnpm test 285 passed,pnpm exec tsc --noEmit 通过,git diff --check 通过。

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown

Claude finished @lanyue-llk's task in 1m 3s —— View job


Review 结论

通过

问题及建议

未发现需要阻塞合并的问题。残余风险:

  • [P2] app/api/session/session-dispatch-service.ts:525-528,569:单次 dispatch attempt 超时默认仍为 5s,冷启动耗时接近/超过该值时会触发 dispatch 删除重建。作者已说明该窗口仅约束单次 readiness 判断、总预算仍为 45s,且已可通过 AGENT_DISPATCH_ATTEMPT_TIMEOUT_MS 调整,可接受,建议后续结合线上冷启动数据校准。
  • [P2] app/api/session/session-dispatch-service.ts:569,575attemptDeadline 已是 Math.min(getDeadline(), ...),575 行再次 Math.min(getDeadline(), attemptDeadline) 冗余,纯可读性问题,不影响行为。
  • [P2] tests/scroll-area.test.mjs:1-12:仅对源码文本做正则匹配,未真正渲染组件/模拟 DOM 滚动,回归保护较弱,可后续补充基于 jsdom 的行为测试。

其余核心改动确认无误:

  • lib/transcription-history.ts:26-38 迟到 partial 覆盖已完成字幕的问题(上一轮 P1)已修复,tests/chat-message-filter.test.mjs:97-121 以真实乱序(final 先到达、旧 partial 后到达)验证,测试有效。
  • components/livekit/scroll-area/hooks/useAutoScroll.ts 改为接收 RefObject 并在 effect 内读取 .current,修复了挂载时序导致自动滚动失效的问题,cleanup 逻辑完整。
  • session-dispatch-service.ts 中未分配 dispatch 的重试逻辑不再占满整个等待窗口,与总预算 45s 及 worker readiness 完整预算的目标一致。

1925b81 之后的最新提交(ab1c15a)仅为 Prettier 格式化,无功能性改动。

@lanyue-llk
lanyue-llk merged commit 11ddd65 into lex-main Sep 14, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant