OpenProgram / WebUI 可靠性 / 实施交接
WebSocket 命令与交互等待生命周期
本页限定单条 WebSocket 命令失败、连接断开和 legacy ask_user 等待的所有权与可观察行为;实现状态与验证证据见第 07 节。
operation_error;新前端继续消费旧服务端的 action_error。dispatcher 不清理 action 自己取得的 reservation,避免误删另一连接的 owner。连接断开时,仅当该 session 没有其他聚焦连接,才向 legacy follow-up queue 投递进程内 sentinel;等待者收到 sentinel 后返回 None,不能把断连解释为空字符串答案。01 · 当前实现与边界
| 入口 | 当前行为 | 边界 |
|---|---|---|
server._websocket_handler | receive loop 分别隔离 JSON 解析和单条 handler 异常;handler failure 返回 operation_error 后继续接收后续命令。 | transport disconnect 与错误帧发送失败仍结束连接;dispatcher 不推测 action 已取得资源的所有权。 |
server._handle_ws_command | 未知 action 返回 operation_error(code=unknown_action);已注册 handler 的异常传播到 receive loop,由同一构造器返回 handler_error。 | wire 只含受约束的 action、session、request metadata 和注册错误分类;异常详情只写本地日志。 |
server._web_follow_up | 使用 session queue 等待;最后观察者断开或取消时返回 None,真实空字符串保持 ""。 | legacy queue 仍是当前进程内状态,不承担 worker 重启后的持久恢复。 |
| stop/delete | 取消对象与断连 sentinel 统一解释为非答案;queue 由 owner 的 finally 清理。 | 通用 dispatcher 不按 session 清理 reservation;取得资源的 action 必须按自己的 owner token 回滚。 |
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 · 目标数据流
operation_error,并回显合法 request_id。code=handler_error,然后继续接收下一条命令。_web_follow_up 的 finally 删除。None;真实空字符串仍保持空字符串,因此两者可区分。04 · 信任边界、失败与兼容
- 客户端 payload 不可信;错误帧不得回显 payload、exception message、路径、凭据或 traceback。
- 日志可记录 action、session_id 和异常类型,traceback 只进入本地 logger。
- handler 在取得 reservation 或其他资源后必须按自己的 owner token 回滚;dispatcher 只隔离 transport,不推测业务所有权。
WebSocketDisconnect和错误帧发送失败仍结束连接;不能无限重试 socket 写入。- 多窗口兼容:只要另一连接仍聚焦同一 session,关闭一个 tab 不取消问题。
- 旧版
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 | 同一连接先收到低敏 operation_error,再收到 pong;连接未因第一条关闭。 |
_websocket_handler | 非法 JSON | 忽略该帧并继续处理下一条;不回显原文本。 |
_web_follow_up + disconnect | 唯一聚焦连接断开 | 等待线程在测试时限内返回 None,不等待 300 秒;queue 最终删除。 |
| disconnect | 同 session 仍有另一聚焦连接 | 不投 sentinel,等待保持;另一连接提交正常答案后返回该答案。 |
follow_up_answer | 用户提交空字符串 | 返回 "",不得转换为断连。 |
07 · 实现文件与证据
| 文件 | 职责 |
|---|---|
apps/server/openprogram_server/server.py | 命令级异常边界、低敏错误帧、连接观察者清理、follow-up sentinel 解释。 |
tests/component/webui/runtime/test_websocket_command_lifecycle.py | 真实 receive-loop、断连和等待线程的公共入口回归。 |
第一切片已实现并完成规格与质量独立审查。 命令级隔离、低敏 operation_error、旧帧前端兼容和 follow-up 断连语义均由公共入口回归覆盖;持久 error store、acknowledge 和 reconnect recovery 不在本页实现范围。
交互响应速度
Socket 写入成功后,发送方立即仅清除已捕获的输入草稿,并在所属对话中显示本地待确认用户消息。待确认记录保留原始文本与附件,直到收到权威确认。服务端确认协调本地身份而不重复消息;拒绝时保留可恢复消息,不覆盖更新的草稿。客户端待确认消息不代表模型已经开始执行。
无关资源投影和会话/侧栏工作不得延迟消息接收或阻塞服务端事件循环。资源关闭定位精确页面与关联身份,保留代次和序列顺序,不扫描无关执行输入。本地关闭反馈与原生关闭确认保持区分;旧待处理动作不能隐藏失败或更新的资源代次。
受控测试覆盖确认延迟、拒绝、会话切换、绑定输入框、排队/steer 行为、资源关闭顺序和无关事件循环进展。不从合成测试计时推断生产延迟。本次仅修改源码,不重新构建、打包、重启或操作已安装 App 与运行中的 worker。
实现状态:源码实现通过独立规格与质量审查。公开入口回归检查覆盖 socket 接收、确认协调、可恢复拒绝、会话历史并发、附件取消和已确认原生关闭。Runtime 安装延期。后端改动限于已识别的阻塞调用路径,不引入通用协议替换或并发框架。