Files
2026Technology-Competition/docs/superpowers/plans/ws-final-review.md
T
lhl 80daadcd31 fix(websocket): 修复 app.py 不可导入并新增真实链路验证
- 将 /api/sessions/{sid}/ws 端点移入 create_app(此前置于模块级导致整模块 import NameError,回归被验证拦截)
- register_loop + subscribe 调整至 accept 之前,缩小连接已开但未订阅期间的进度丢失窗口
- 新增 tests/test_verify_ws_real_flow.py:驱动真实 HTTP 聊天流程断言 WS 收到 agent 实际发射的 parse/impact 进度
- 同步 WebSocket 计划文档 Task 3 代码片段(标注端点必须位于 create_app 内)
- 全量 pytest 实测 583 passed / 99.03% 达标
2026-08-29 14:36:12 +08:00

5.5 KiB
Raw Blame History

WebSocket 实时进度流 — 全分支最终代码评审

  • 评审对象:分支 feat/websocket-progress(相对基线 014a51a
  • 评审范围:5 个任务落地、工程质量、测试、计划决策遵守、风险
  • 全量验证结果:582 passed / 582coverage 99.03%(达到 fail_under=99 门禁)
  • 计划决策:D1=单向(server→client),D2=单进程(无跨进程总线)

总体结论

APPROVED_WITH_MINORS

五个任务全部落地,规格一致性高;跨线程 emit、disconnect 清理、前端异常降级、持久化兜底均按设计实现;全量测试 582 通过、覆盖率 99.03% 达标。存在若干非阻塞的工程质量与测试覆盖小问题(见下),建议合并后于后续迭代修复。


Critical 发现

无。


Important 发现

I1. WS 端点在「空闲阻塞于 q.get()」时无法感知客户端断开,且 disconnect 分支未被测试真正覆盖

  • 位置:src/genesis/server/app.py:316-328session_progress_ws
  • 问题:
    1. while True: await q.get() 在等待事件时并不触碰 socket,因此客户端在此期间断开连接不会被检测;连接会一直挂起直到下一条 emit 触发 send_jsonWebSocketDisconnect 才清理。若会话长时间无进度事件,连接会泄漏(占用队列、FD)。
    2. 全量覆盖率显示 app.py:326except WebSocketDisconnect: pass未覆盖。对应测试 tests/test_progress_e2e.py::test_ws_progress_second_emit_after_disconnect 的时序是:客户端读取首条后即关闭,此时服务端正阻塞在第二次 q.get(),尚未走到 send_json,故 WebSocketDisconnect 实际未被触发,pass 分支为死分支/未被测到。
  • 建议修复(二选一,均不阻塞本次合并):
    • q.get() 增加超时并周期性 await ws.receive() 或发送心跳 ping,使断开可及时感知;
    • 或将循环改为 while True: event = await q.get(); try: await ws.send_json(event) except WebSocketDisconnect: break,并在测试中显式验证断开后队列被 unsubscribe(断言 hub._subs.get(sid) is None)。

I2. hub.register_loop 在每次 WS 连接时重复注册

  • 位置:src/genesis/server/app.py:319
  • 问题:单进程下功能正确,但语义上「事件循环注册」应属于应用生命周期,而非每个连接。若未来多 worker/多 loop,后连的连接会覆盖 _loop,造成早连连接的 run_coroutine_threadsafe 投递到错误 loop。
  • 建议:在 create_app 启动期(lifespan 或首次请求钩子)注册一次 loop;WS 端点仅 subscribe/unsubscribe。属加固项,单进程假设下非缺陷。

Minor 发现

  • M1(风格/潜在缺陷)src/genesis/chat/agent.py:189-192 缩进过深_run_parseexcept 块内 reply = ..._emit_error_store_errorreturn 比同块其他语句多缩进 4 空格(16 空格 vs 12 空格)。语法合法、功能正确(异常分支仍正常执行),但触发 E117 over-indented 类 lint 告警,且易误读。建议统一为 12 空格缩进。
  • M2(项目约定)_AI_USAGE_LOG.md 未更新:AGENTS.md 规定每次代码改动须在根目录 _AI_USAGE_LOG.md 追加记录,但本次 6 个提交均未修改该文件。建议补登本次迭代的范式步骤与涉及文件。
  • M3(文档锚点)docs/design.md 实际写入为 §12.8,而计划文档 Task5 注明「§12 追加」。非错误,但命名与计划略有出入,可统一。
  • M4(前端连接策略)chat.html send() 每次发消息都会 closeprogressWs 再重新 connectProgressWs:逻辑正确(避免泄漏),但每次消息重建 WS 略显浪费;可考虑仅在会话切换(loadSession / newSession)时开关,消息发送阶段复用同一连接。
  • M5(竞态,已知可接受) 若用户在前端 WS 尚未完成订阅前即触发会发射进度的请求,首条事件可能丢失(队列尚未创建)。因 role='progress'/'error' 持久化兜底 + 重载渲染仍可见,正确性不受影响,属已知限制,无需阻塞。

计划决策遵守核对

决策 要求 实现核对 结论
D1 单向 WS 仅 server→client;聊天仍走 HTTP chat_ws.jsonmessage 消费,从不 send/api/chat/{sid}/messages 仍是 HTTP POST 遵守 ✓
D2 单进程 无跨进程总线;限制写入文档 无 Redis/消息中间件依赖;docs/design.md §12.8 已标注「多 worker 跨进程不互通」 遵守 ✓

规格一致性(5 任务)

  1. Task1 ProgressHub 单例:hub.py 新建,3 个单测通过 ✓
  2. Task2 agent 发射:_emit_progress/_emit_error 在 4 个流程方法的进度/错误分支接线,持久化 _persist_progress/_store_error 保留 ✓
  3. Task3 WS 端点:/api/sessions/{sid}/ws + /chat_ws.js 静态路由 + websockets>=12 依赖 ✓
  4. Task4 前端 chat_ws.js + chat.html 集成(含 try/catch 降级、会话切换关闭旧连接)✓
  5. Task5 e2e 冒烟 + design.md 记录 ✓

无范围蔓延、无占位 TODO、无调试残留、无裸 except:(均为 except Exception,且带 noqa: BLE001)。


是否可合并

可以合并。 功能完整、全量测试通过、覆盖率达标、决策遵守、无 Critical/阻塞性问题。建议在合并后的后续迭代中修复 I1(空闲断开检测与 disconnect 分支测试覆盖)与 I2(loop 注册时机),并补登 M2 的项目日志。