OpenProgram / WebUI reliability / implementation handoff
WebSocket 命令与交互等待生命周期
基线:c1f10273。本页限定单条 WebSocket 命令失败、连接断开和 legacy ask_user 等待的所有权与可观察行为;实现状态与验证证据见第 07 节。
确定方案。 单条命令异常在 receive loop 内隔离并返回稳定的
action_error;dispatcher 不清理 action 自己取得的 reservation,避免误删另一连接的 owner。连接断开时,仅当该 session 没有其他聚焦连接,才向 legacy follow-up queue 投递进程内 sentinel;等待者收到 sentinel 后返回 None,不能把断连解释为空字符串答案。01 · 当前实现与已验证缺口
| 入口 | 当前行为 | 缺口 |
|---|---|---|
server._websocket_handler | receive loop 只捕获 JSON 解析异常;handler 异常由外层连接级捕获。 | 一条坏命令结束整条连接,后续有效命令无法处理。 |
server._handle_ws_command | 未知 action 返回 action_error;已注册 handler 的异常直接传播。 | 错误协议不一致,并可能泄漏为连接级 traceback。 |
server._web_follow_up | 使用 session queue 等待 300 秒;timeout 返回空字符串。 | 最后一个可回答页面断开后仍等待,且断连/超时与用户回答空字符串无法区分。 |
| stop/delete | 已有路径向 queue 投递取消对象或移除 queue。 | 行为未在 WebSocket 断连入口复用;取消对象仍可能被当作答案。 |
02 · 参考机制与取舍
Starlette WebSocket
transport 断开通过 WebSocketDisconnect 结束连接。采用连接级清理放在 finally,命令级应用异常留在 receive loop 内处理。
QuestionRegistry
新 runtime question 已有 session-scoped、claim-once 的 cancel_session()。legacy follow-up 不迁移协议,只采用相同的“取消不是答案”语义。
现有 run reservation
_release_run_reservation(session_id,msg_id) 已按 owner 清理。dispatcher 缺少 owner token,因此拒绝在通用异常分支按 session 清理。
采用:命令级错误帧、最后观察者断连 sentinel、owner-scoped action cleanup。修改:legacy queue 的返回类型允许 None。拒绝:任何 tab 断开都取消、dispatcher 按 session 删除 reservation、异常后关闭连接、把 sentinel 传给业务函数。
03 · 目标数据流
1
收到文本;非法 JSON 记录低敏诊断并继续 receive。
2解析为 object 后执行 action handler;未知 action 沿用现有
3action_error。handler 异常写结构化日志,仅包含 action/session/type;向同一 socket 返回
4code=handler_error,然后继续接收下一条命令。transport 断开后先从连接集合移除当前 socket,再检查其 focused session 是否仍有其他连接。
5若没有其他聚焦连接且存在 legacy follow-up queue,投递唯一 sentinel;queue 仍由
6_web_follow_up 的 finally 删除。等待者把 sentinel 转为
None;真实空字符串仍保持空字符串,因此两者可区分。04 · 信任边界、失败与兼容
- 客户端 payload 不可信;错误帧不得回显 payload、exception message、路径、凭据或 traceback。
- 日志可记录 action、session_id 和异常类型,traceback 只进入本地 logger。
- handler 在取得 reservation 或其他资源后必须按自己的 owner token 回滚;dispatcher 只隔离 transport,不推测业务所有权。
WebSocketDisconnect和错误帧发送失败仍结束连接;不能无限重试 socket 写入。- 多窗口兼容:只要另一连接仍聚焦同一 session,关闭一个 tab 不取消问题。
- legacy
follow_up_answerwire shape 不变;正常字符串答案和真实空字符串继续可用。
05 · 明确排除
- 不把 legacy global
set_ask_user重构为 QuestionRegistry。 - 不修改 runtime.ask、ACP、channel question、发送队列或 run reservation 协议。
- 不处理 Windows、系统 Keychain/keyring、孤儿文件和其他审计项。
06 · 验收矩阵
| 公共入口 | 场景 | 可观察断言 |
|---|---|---|
_websocket_handler | 第一条已注册 action 抛 KeyError,第二条为 ping | 同一连接先收到低敏 action_error,再收到 pong;连接未因第一条关闭。 |
_websocket_handler | 非法 JSON | 忽略该帧并继续处理下一条;不回显原文本。 |
_web_follow_up + disconnect | 唯一聚焦连接断开 | 等待线程在测试时限内返回 None,不等待 300 秒;queue 最终删除。 |
| disconnect | 同 session 仍有另一聚焦连接 | 不投 sentinel,等待保持;另一连接提交正常答案后返回该答案。 |
follow_up_answer | 用户提交空字符串 | 返回 "",不得转换为断连。 |
07 · 实现文件与证据
| 文件 | 职责 |
|---|---|
openprogram/webui/server.py | 命令级异常边界、低敏错误帧、连接观察者清理、follow-up sentinel 解释。 |
tests/unit/test_websocket_command_lifecycle.py | 真实 receive-loop、断连和等待线程的公共入口回归。 |
已实现并完成独立审查。 实现提交:4bc76c83、be307d4b、781de2b1。最终定向验证:67 passed;Ruff 通过;文档构建 457 页;链接检查 0 broken links。