Web 运行时可靠性与性能设计
本文统一记录 Web 页面刷新、会话加载、WebSocket 消息分发、后台错误展示、EventBus 通知、异步 Job 状态、静态资源加载和本地状态目录的已验证问题,并给出相互兼容的目标合同、任务边界和量化验收标准。
实测基线
以下数据来自当前默认状态目录、当前最大会话、直接执行会话加载处理器以及禁用浏览器缓存的本地页面加载。它们是后续前后对比的固定基线,不代表所有机器的绝对时间。
handle_load_session;不含网络、浏览器解析与 React 状态写入。chrome-profile 约 6.1 GB;sessions 约 8.5 MB。| 测量 | 方法 | 限制 | 后续使用 |
|---|---|---|---|
| 会话载荷 | 使用真实 SessionDB 数据和只记录发送内容的 WebSocket 替身执行公开 handler。 | 关闭冷 context stats,避免把独立后台刷新计入首帧。 | 同一会话执行 before/after;记录总帧、messages、graph、context tree 各自字节。 |
| 浏览器加载 | 默认端口页面,缓存关闭,读取 Navigation/Resource Timing 和响应头。 | 内置诊断浏览器没有默认 App 的 owner credential,因此未执行已认证聊天操作。 | 用于静态资源体积、CSP、压缩和脚本数量验收;真实聊天由默认 App 验收。 |
| EventBus 日志 | 统计全局与会话 events.jsonl 的类型和字节占用。 | 日志内容随真实使用变化。 | 后续比较事件行数、重复 ID、大型字段和写入延迟。 |
| worker 资源 | 对默认 worker 进行短时间进程采样并统计状态目录。 | 不是长时间 workload profile。 | 用于排除持续 CPU 饱和;正式实现后增加事件循环延迟和队列指标。 |
当前数据流与失效位置
当前系统同时存在浏览器 WebSocket、原始 WS frame、EventBus、AgentEvent、SessionDB 和未接入生产的 MessageStore。下图标出用户操作从前端到持久化及后台事件返回前端的主要路径。
红色表示已验证缺口;黄色表示设计与实现语义不一致;绿色表示当前已有合理优化。MessageStore v2 是可复用候选,不应再引入第三套增量协议。
问题清单与用户影响
优先级依据数据丢失、错误不可见、事件循环阻塞、资源无界增长和普通用户触发概率确定。每项都指向当前源码,不把设计提案写成已经实现的事实。
| ID | 优先级 | 当前行为 | 用户可见影响 | 根因与证据 | 目标状态 |
|---|---|---|---|---|---|
| R1 | P0 | 公共 dispatcher 以及 agent/channel/settings 管理 action 的未处理存储异常已在发送前写入 profile-scoped UserErrorStore,并发送最终低敏 operation_error;runtime/session 的顶层 type="error"、project 的空结果或内嵌 error、channel validation/QR 的专用错误形态和 chat reply error turn 仍保留,前端尚无持久全局 error store。 | 已迁移管理操作不再无提示,且传输失败后仍有服务端记录;其余旧形态仍缺少一致展示、确认和刷新恢复。 | server.py 统一处理未捕获 action 异常;user_errors.py 保存有界低敏记录;ws_actions/ 仍有待迁移形态。 | 所有用户相关失败进入统一错误合同,并按 scope 持久显示。 |
| R2 | P0 | 公共 dispatcher 的 unknown action 与 handler exception 已生成最终低敏 operation_error,每次失败都有独立 error/correlation ID、明确 scope 和 UTC 时间;服务端已提供 principal-scoped list、exact-ID acknowledge 和多窗口 recovery 协议;前端继续兼容旧 action_error。 | 现有 handler failure 不再误报为缺少后端处理器;发送前记录允许后续恢复;未知或未来 code 使用不含 wire message/error 的通用失败提示。 | server.py 使用共享安全构造器、记录 correlation metadata,并在持久化失败时以 state_recovery_required 关闭连接;user_error.py 实现严格分页、确认关联和同 principal 恢复广播;action-error.ts 负责新旧 envelope 归一化。 | validation code、其余 action 迁移与前端 hydration/展示仍属于 TASK 1 后续切片。 |
| R3 | P0 | agent/channel 的列表、增删、binding 和 alias 存储异常不再转换为空数组或静默成功;异常由公共 receive loop 持久化并返回一个低敏 operation_error,且不发送对应正常列表或成功通知。 | 协议层现在可以区分正常空状态与该组管理故障;页面不会收到替换旧数据的正常响应,但尚无 degraded 标记和刷新恢复。 | agent.py、channel.py 的存储异常上抛到公共 dispatcher;记录按已认证 principal 隔离。 | 持久记录和日志 correlation ID 已实现;degraded 标记、确认和恢复仍由 TASK 1 后续切片完成。 |
| R4 | P0 | Job broadcaster 已发送 error,Branches 状态和 AttachCard 没有保存/显示详情。 | 后台任务只显示 errored,刷新后无法知道原因和是否可重试。 | runner.py 与 branches/index.tsx 字段不对称。 | 错误摘要随 Job 实体持久化;卡片显示安全原因、重试属性和日志关联 ID。 |
| R5 | P0 | 连接恢复和会话切换发送完整 session_loaded。 | 大会话刷新传输和解析超过 11 MB,页面响应时间随历史增长。 | session.py 一次发送 messages、graph、context tree;客户端重连直接调用 load_session。 | 首屏 manifest + 当前链;Graph、上下文树和大字段按需;重连按 revision/seq 增量恢复。 |
| R6 | P1 | 会话加载在 WS event loop 内同步聚合、排序、建图和 JSON 序列化。 | 一个大会话的刷新可延迟同一 worker 上其他 socket 的命令处理。 | _fn_run_siblings 中反复 all_msgs.index,整体 handler 无计算/发送分离。 | 纯计算快照在线程执行;异步层只负责按序发送和状态校验。 |
| R7 | P0 | TASK 3 候选实现已将生产连接收敛到单一 sender,并对 queued + in-flight 帧施加 256 帧 / 4 MiB 上限。 | 慢客户端的同时 send_text 从 RED 的 400 降为 1;普通更新可合并,终态使用关键预留。 | ws_delivery.py 与公共 handler 慢客户端回归。 | complete;规格、质量和完整 Python gate 均为 PASS。 |
| R8 | P1 | 重连固定每 2 秒尝试,成功后重新加载整个会话。 | 服务重启或网络异常时多个页面会同步重试并重复传输完整状态。 | use-ws.ts。 | 指数退避、随机抖动、在线恢复后发送已知 revision/seq;不重复生成 UI 错误。 |
| R9 | P0 | EventBus notify 的普通 handler 在 emitter 线程同步执行;async handler 无 event loop 时被跳过。 | 慢插件阻塞 Agent;部分后台事件没有任何消费者收到。 | events/bus.py。 | notify 总是进入专用队列;gate 保持同步并执行明确预算。 |
| R10 | P1 | 事件日志仍同步写;tool.before 的重复行已经修复并通过两类复审;注册表测试仍漏掉跨行 emit。 | 高频工具输出仍增加同步 IO;未注册事件仍可能无法由测试发现。 | event_log.py、test_event_gate.py。 | 实现异步批量日志、AST 注册检查和大字段摘要化。 |
| R11 | P1 | TASK 6 已移除产品运行时 CDN 资源并将 marked/KaTeX 纳入构建;Starlette gzip responder 为可压缩响应协商 gzip,并按响应 MIME 排除已压缩格式。 | 源码、legacy template、构建产物和 HTTP public entry 已有自动化回归;browser home 不再自动请求 Google favicon;规格、质量、完整 Python gate、默认 App 和本地 browser trace 均通过。 | markdown-render.ts 与 frontend.py。 | complete;保持 self-only CSP。 |
| R12 | P2 | Chrome profile 没有容量展示和保留策略;当前约 6.1 GB。 | 长期使用后占用磁盘,用户无法从产品中判断来源或安全清理范围。 | 最大目录为浏览器 on-device model、Default profile 和 model store。 | 展示分类容量;只提供显式、可恢复的清理操作;不自动删除登录态。 |
统一错误合同与前端展示
“后台报错前端必须显示”定义为:所有影响用户请求、持久状态、后台任务或可恢复操作的失败都必须进入可观察错误记录。它不要求把每个内部降级或重试都显示成 Toast,也不允许把 traceback、文件内容或凭据发送到浏览器。下列 schema 是唯一最终 wire 合同;TASK 1 服务端已为公共 dispatcher 和首组管理 action 生成完整字段、在发送前记录,并实现 principal-scoped list、exact-ID acknowledge 和多窗口 recovery。前端 hydration、展示和其余错误源仍按后续切片实现。
// WebSocket envelope;nullable 字段使用 null,不省略语义必需字段 { "type": "operation_error", "data": { "error_id": "err_opaque_unique_id", "request_id": "client_request_id_or_null", "scope": "session | job | settings | channel | agent | transport | system", "code": "stable_machine_code", "message": "safe user-facing summary", "action": "safe_action_or_null", "session_id": "session_id_or_null", "operation_id": "action_job_execution_id_or_null", "retryable": false, "severity": "info | warning | error | fatal", "correlation_id": "opaque log lookup id", "occurred_at": "ISO-8601 UTC" } }
| 错误合同字段/状态 | 唯一语义 |
|---|---|
request_id | 由客户端为请求/响应 action 生成;成功或失败必须回传同一值并结束 pending。operation_id 标识服务端实体,不能代替 request ID。 |
error_id | 每次失败唯一。Toast 去重键为 scope + code + session_id + operation_id,5 秒内相同键只显示一次;错误记录仍分别保留。 |
UserErrorStore | 使用当前 profile 下独立 SQLite 文件,按已认证 principal 隔离;创建时间起最多保留 7 天且每个 principal 最多 1,000 条。开放记录按 occurred_at、error_id newest-first 严格分页,确认操作按 principal + exact error_id 事务性幂等关闭。发送必须发生在事务提交之后;记录失败时不发送伪 durable envelope,而关闭连接并要求 state recovery。 |
| severity | info 只进入诊断;warning 显示局部 degraded 状态;error 显示卡片和一次 Toast;fatal 显示持久阻断状态并停止自动重试。 |
| 生命周期 | 请求成功只结束对应 request_id 的 pending。acknowledge_user_error 在请求连接内依次发送带同一 request_id 的 user_error_acknowledged 和 {type:"operation_recovered", data:{error_ids:[...], scope, operation_id, occurred_at}};持久关闭后,其他同 principal 窗口独立接收同一 recovery,跨连接不承诺相对顺序,请求连接发送失败也不得阻止其他窗口恢复。服务器必须列出要关闭的具体 error_id,前端不得按模糊 code 批量关闭;重复确认可再次发送同一 exact-ID recovery 以使窗口收敛,数据库关闭保持幂等。用户确认或实体删除也只关闭明确 ID;普通后续成功不能隐式清除不相关错误。 |
| Job 失败 | job_status(status=errored) 是 Job 状态的唯一 wire 事件,前端由它生成 error-store 记录;不得再为同一失败发送第二个 operation_error。operation_error 只用于 Job action 请求本身失败。 |
| 事件类别 | 前端行为 | 持久性 | 不得包含 |
|---|---|---|---|
| 当前会话请求失败 | 对应卡片或控制项附近显示原因;首次接收显示一次 Toast。 | 写入会话错误记录;刷新后仍可见。 | traceback、原始环境变量、未过滤 provider body。 |
| 后台 Job 失败 | 会话列表显示错误标记;AttachCard/Jobs 显示摘要和可重试状态。 | 跟随 Job 实体持久化。 | 子进程完整 stdout、文件全文。 |
| 设置或管理操作失败 | 保留上一个已知状态,显示操作失败;不把失败解释为空列表。 | 错误中心保留到用户确认或后续成功。 | credential value、token、绝对 secret path。 |
| 可恢复内部降级 | 默认不 Toast;相关表面显示 degraded 状态,诊断面可查询。 | 结构化日志与指标。 | 面向用户的重复提示。 |
| 传输断开 | 显示连接状态;自动恢复期间不重复产生相同错误。 | 仅在超过恢复预算或有未确认操作时进入错误中心。 | 每次重连尝试对应一个 Toast。 |
单一安全 payload 构造器
helper 只生成经过过滤的 error data,并负责字段校验、日志关联和 session routing。WS action/channels/system 将该 data 包装为 operation_error;JobRunner 把同一 data 嵌入唯一的 job_status,不生成第二个 error envelope。
单一状态入口
use-ws 只解析并交给 error store;错误卡片、会话标记和 Toast 从同一记录派生,避免不同组件各自解释 code。
明确请求完成
需要请求/响应的 action 使用 request ID。成功和失败都结束 pending 状态,任何按钮都不能无限保持 loading。
旧帧逐步迁移
迁移期间前端把现有 error、action_error 和 Job error 适配到统一记录;后端逐项切换后删除兼容分支。
会话加载与刷新设计
目标是让首屏成本由当前可见内容决定,而不是由整个会话历史决定。持久化仍以 SessionDB/DAG 为准;Web wire 只传页面当前需要的投影。
| 阶段 | 返回内容 | 触发条件 | 缓存键 |
|---|---|---|---|
| Session manifest | 标题、状态、head、revision、当前链 ID、计数、功能状态、错误摘要。 | 切换会话或无有效 manifest。 | session_id + revision |
| Viewport messages | 当前可见链尾部消息和必要的 compaction/attach 引用。 | 首屏或滚动到未加载范围。 | session_id + head_id + cursor |
| Graph summary | 节点 ID、边、角色、状态和最小标签,不包含消息正文与工具结果。 | 用户打开 DAG 或分支面板。 | session_id + graph_revision |
| Node/detail | 单节点上下文、完整工具输出、runtime tree、附件详情。 | 用户展开或导航到具体节点。 | session_id + node_id + content_revision |
| Reconnect delta | 已知 revision 后的持久提交,以及每个运行中实体已知 seq 后的状态和终态事件。 | 连接恢复且服务器仍保留连续增量。 | 客户端提交 known_session_revision 和 known_stream_seqs: {entity_id: seq} |
| Full recovery | 新的 manifest 和有限 viewport,不是完整历史。 | revision 不兼容、delta 已淘汰或客户端状态无效。 | 重置本地 session projection |
MessageStore 只复用 block、seq 和 delta 计算代码,不成为第二个持久 store;不能复用的独立持久化和 singleton 接口在 TASK 4 删除。所有 full recovery 都从 SessionDB projection 生成。| 协议量 | 定义 | 保留与回退 |
|---|---|---|
session_revision | 每个 session 单调递增的整数。一次 SessionDB/DAG 原子提交只增加一次;manifest、message header、Graph summary 和持久错误都标记其读取 revision。 | 永久随 session 持久化。客户端 revision 高于服务端或 schema 不兼容时拒绝 delta 并返回新 manifest。 |
stream_seq | 每个运行中 message/job 独立单调递增;只描述尚未形成持久提交的 text/tree/progress 增量。 | 当前 worker 每实体保留最近 256 个 delta 或 15 分钟,以先达到者为准。worker 重启把未完成实体持久标记 error/aborted,客户端随后执行 manifest recovery。 |
| delta journal | 由持久提交生成的 from_revision → to_revision projection change;不保存完整正文副本。 | 内存保留最近 512 次提交或 15 分钟。缺口超过保留范围时返回 manifest + 有限 viewport。 |
| viewport cursor | 稳定二元组 (created_at, message_id),按当前 head 链逆序分页;首屏最多 100 条且未压缩 JSON 最多 512 KiB,以先达到者为准。 | 向历史方向分页;head 改变时 manifest 给出新 cursor。单条消息超过剩余预算时只返回 header、摘要和 detail marker。 |
| detail cursor | 单节点/工具结果每次最多 1 MiB 未压缩内容;更大结果按 UTF-8 安全边界分块并返回 next cursor。 | 每次读取都重新执行 session/node/caller 授权;不得通过 cursor 跨节点或跨 session。 |
计算与发送的分离边界
同步线程读取不可变 SessionDB 快照、建立 ID 索引、计算当前链与 Graph summary,并返回普通数据对象。异步 handler 在发送前再次校验 session revision;若期间发生写入,发送 manifest 后追加 delta,或放弃旧快照重新读取。线程内不得直接操作 WebSocket。
需要删除的重复计算
预先创建 id → insertion_index,替代排序中的 all_msgs.index;兄弟分组按 predecessor 一次建立;同一请求只读取一次 session row;Graph summary 与消息正文分开序列化。
大字段与兼容
保留当前 32 KiB 工具结果 marker 和 get_full_tool_output 权限检查。进一步把 context tree、runtime blocks、Graph node content 和附件 metadata 纳入按需读取。旧客户端仍可请求旧 action,但服务端需要设置硬载荷上限并在超过时返回升级提示,而不是继续生成无界帧。
WebSocket 队列、顺序与恢复
浏览器标准 WebSocket API 不提供自动背压,因此服务器必须限制自己的发送队列。每个连接由一个 sender task 顺序发送,生产线程只执行线程安全的 enqueue。
| 帧类别 | 示例 | 队列规则 | 溢出行为 |
|---|---|---|---|
| 不可丢终态 | error、question、chat final、job terminal、ack、permission result | 保持顺序,不覆盖,不跨 session 合并。 | 无法在预算内排队时关闭连接并标记需要 state recovery;不得静默丢弃。 |
| 可合并增量 | text/thinking delta、tree update、progress、context stats | 按 session + entity + type 保留最新或合并相邻文本。 | 删除被更新取代的旧增量,并记录 coalesced 计数。 |
| 快照 | sessions list、branches、settings、graph summary | 同一 key 只保留最新未发送快照。 | 替换旧快照;不影响终态。 |
| 控制 | pong、auth/status、reload required | 小型独立预算,防止被大消息完全延迟。 | 认证或协议错误立即关闭;pong 可丢弃旧值。 |
队列上限与关键预留
每连接限制 256 帧或 4 MiB,以先达到者为准;其中 64 帧和 1 MiB 只供不可丢终态。关键帧入队前先清除可合并增量,不能占用普通快照的旧版本。
发送所有权
只有 sender task 调用 ws.send_text。断开时取消 sender、释放队列并注销 focused session;其他线程不保留 WebSocket Future。
重连
指数退避并加入随机抖动;页面重新可见或用户明确重试时可提前触发。成功后先恢复协议状态,再恢复会话投影。
观测指标
记录 queue_frames、queue_bytes、oldest_age、coalesced、send_failures、disconnect_reason 和 recovery_mode,不记录帧正文。
UserErrorStore,按 error_id 查询,最多保留 1,000 条或 7 天;session manifest 只携带相关未关闭 ID,重连通过 list_user_errors(cursor) 读取详情。transport close 由客户端根据 close/timeout 本地生成,不依赖服务端错误帧。chat ack 只在用户消息落盘后发送;其他 request ack 必须能通过 request_id 幂等查询或重放。若关键预留仍满,服务器关闭该连接并标记 state_recovery_required;客户端重连后从 manifest、UserErrorStore、pending-question registry、Job store 和 request result 查询恢复,不能依赖未发送的内存帧。标准 WebSocket API 不提供自动背压;继续使用兼容性最好的 WebSocket,但必须在应用层限制队列。
客户端可用 bufferedAmount 监测未发送字节,但该值不能替代服务端每连接队列和恢复协议。
采用正数 maxsize 的 FIFO 队列;跨线程入口使用 event loop 的线程安全调度,不直接跨线程操作 Queue。
EventBus 执行模型与日志
EventBus 保留两种明确语义:gate 是同步决策;notify 是异步观察。二者不能共用“当前线程直接调用 handler”的执行方式。
| 属性 | Gate | Notify |
|---|---|---|
| 用途 | 在动作发生前允许或拒绝。 | 记录、更新 UI、触发后续非阻塞工作。 |
| 执行位置 | emitter 线程;默认总预算 events.gate_timeout_ms=50。 | EventBus 自有 event loop;同步 consumer 在线程池执行,async consumer 在该 loop 执行。 |
| 失败 | 维持现有 fail-open,并写结构化警告和 gate 结果。 | 隔离消费者;重试只由消费者策略决定;不得静默跳过。 |
| 顺序 | 注册顺序。 | 同一 session + entity 有序;不同 key 可并发。 |
| 背压 | 不排队;超过预算时跳过剩余 gate 并记录。 | 有界队列;关键事件不能被普通遥测占满。 |
| 日志 | 事件和 gate verdict 合并成一条记录。 | 异步批量写;大型 payload 使用长度、摘要和受控片段。 |
| Notify 队列合同 | 规则 |
|---|---|
| 顺序键 | 优先使用 session_id + entity_id;只有 session 时使用 session;两者都没有时使用 type + origin。同一键严格 FIFO,不同键可并发。 |
| 容量 | 普通队列最多 4,096 事件;关键状态队列最多 512 事件。关键类型是 operation error/recovered、question、job terminal、turn end 和标记为 critical 的 ws.frame。 |
| 满队列 | progress/context stats 等遥测按键合并或丢弃并增加明确指标;关键队列满时 source 的持久状态仍是恢复依据,bus 写入 emergency loss marker 并通知 Web 连接执行 state recovery。不得只打印 stderr 后继续假定已投递。 |
| 关闭与重启 | 正常关闭最多排空 2 秒;剩余 notify 记录 shutdown loss marker。关键业务状态在 emit 前持久化,重启后由各 store 的 recovery 扫描恢复,不把 EventBus 当 durable queue。 |
| Gate 安全边界 | EventBus gate 只承载插件/工作流的附加决策,维持 fail-open。权限、认证、sandbox 和凭据校验不使用该 fail-open gate,继续由各自强制边界拒绝。 |
| 日志唯一性 | 事件根据 session 是否存在只写 session log 或 global log 之一;同一目标文件中一个 event ID 一行。gate event 在 verdict 完成后写一次,不能先由 notify 写一行。 |
AST 级注册检查
测试解析 Python AST 中的 emit_safe、make_event 和常量事件名,覆盖跨行调用;动态名称必须显式登记例外和消费者。
事件只记录一次
tool.before 的观察与 gate 共享同一 Event;最终日志包含 gate 字段。不能先写普通行再写 verdict 行。
高体积字段摘要化
tool.after.result_text 默认记录字节数、错误状态、哈希和有限预览。完整结果仍在 SessionDB/工具结果存储,不复制到事件日志。
错误不是原样转发所有事件
EventBus 到 Web 的桥只转发经过注册的用户相关 envelope。内部事件日志和用户错误流相关联,但不能直接暴露任意 payload。
前端渲染、刷新与静态资源
当前流式 reducer 已按 animation frame 合并更新,MessageRow 按 ID 订阅并对长列表执行行回收。这些机制继续保留。优化重点是减少进入 store 的数据、消除无效外部依赖和限制首次资源体积。
| 区域 | 保留 | 修改 | 验证 |
|---|---|---|---|
| 流式消息 | 每个 animation frame 合并 text/thinking;单消息 selector。 | 终态到达前先 flush 对应 pending delta;错误记录与回复状态原子更新。 | 高频 delta 下每帧最多一次相关 store update;终态内容不缺失。 |
| 长列表 | MessageRow memo、viewport 行回收、稳定 ID 数组。 | 首屏只 hydrate viewport 数据;Graph 面板不再强迫 chat store 保存完整 Graph 正文。 | 1,000+ 节点会话首屏 DOM 和 React commit 不随完整历史线性增长。 |
| 错误 UI | 现有 Toast 组件和会话 unread 状态。 | 增加 error store、持久错误卡片和后台会话错误标记;Toast 去重。 | 当前/后台/刷新后三种场景都有自动化断言。 |
| marked / KaTeX | Markdown 和数学语法行为。 | 从前端 bundle 本地导入,删除 CDN loader 和 WS 初始化等待。 | 严格 script-src 'self' 下零 CSP violation;离线可渲染。 |
| 静态响应 | 同源静态 export 和缓存头。 | 为文本、JS、CSS、SVG 增加 gzip;后续可加入构建期 Brotli 预压缩。 | Accept-Encoding 协商正确,已压缩内容不重复压缩,SSE 不压缩。 |
| 代码分割 | Next.js 静态 export。 | 按 settings、DAG、browser、programs 等不在首屏的表面延迟加载。 | 首次传输与最大 chunk 进入预算;交互后加载有明确 pending 状态。 |
采用官方 ASGI middleware 的最小体积和压缩级别配置;排除 SSE 和已经压缩的格式。
'self' 与 CDN runtime script 不兼容。选择本地依赖,不放宽 CSP,也不增加 unsafe-inline/unsafe-eval。
浏览器支持范围不足,而且不能解决服务端队列、持久状态、错误合同和增量恢复。保留标准 WebSocket 并补齐应用层控制。
运行指标、容量和状态目录
性能改动必须能在默认 worker 上持续测量。指标只记录计数、耗时、字节和关联 ID,不记录提示词、模型回复、工具输出或凭据。
WebSocket
- 活动连接数和每连接队列字节。
- send latency、失败数、coalesced 数。
- 重连次数、恢复模式、全量恢复原因。
Session hydration
- manifest/messages/graph 各自字节。
- DB read、projection、serialize、send 耗时。
- viewport hit、detail fetch、delta fallback 数。
EventBus
- notify enqueue 与 handler duration。
- 队列深度、失败、重试和丢弃原因。
- gate 总耗时与超过预算次数。
Storage
- sessions、logs、chrome cache、models、IndexedDB 分类容量。
- 用户触发清理前展示目标和影响。
- 默认不删除 Cookie、登录态、扩展数据和会话。
实施任务与依赖
每个任务独立提交、独立规格审查和质量审查。TASK 1 的命令隔离、首组管理 action 迁移、服务端持久化与恢复协议已经分切片实现,但整体仍为 partial;TASK 3 与 TASK 6 已经通过两类审查、完整 gate 和各自的运行时验收,状态为 complete;TASK 5 的 gate single-record 局部也已通过两类复审,但 TASK 5 整体仍未完成。后一个任务不得依赖未审查的前一个工作区状态。
统一错误 envelope 与前端错误状态 partial
公共 dispatcher 异常隔离、agent/channel/settings 管理存储异常迁移、服务端 UserErrorStore 以及 list/ack/recovery 协议已经实现;最终 envelope 在进入发送队列前按已认证 principal 持久化,记录有 1,000 条/7 天上限,存储失败要求 state recovery。确认使用 exact error ID,结果按 request ID 关联,并只向同 principal 窗口广播恢复。Web 兼容 operation_error/action_error,TUI 使用低敏文本消费两种 frame。当前会话卡片、后台标记、Toast 去重、前端 reconnect hydration 和其余旧错误路径仍未实现。聊天回复现有 error bubble 保持兼容;Job 实体错误由 TASK 2 处理。
后台 Job 错误完整展示
让 Job error 从实体、广播、列表加载到 AttachCard/Branches 全程保留;终态错误在刷新后仍存在。失败信息使用安全摘要,完整 traceback 只写日志。
每连接有界发送队列 complete
在 server 层新增 _webui/ws_delivery.py,建立 sender 生命周期、帧分类、合并规则、终态保障和断连清理;所有广播路径只 enqueue。慢客户端、发送失败、跨线程并发、队列溢出、终态预留和 in-flight 容量回归已通过,未修改 events/bus.py。
Session projection 与增量恢复
先固定载荷测量和协议合同,再分离同步计算与异步发送;实现 manifest、viewport messages、Graph summary、detail fetch 和 reconnect delta。评估并复用或删除未接入的 MessageStore。
EventBus notify 与日志执行模型 单记录切片完整
gate event 的 observer 通知阶段已经延迟磁盘写入,verdict 完成后只写一条含 gate 字段的记录,并已通过独立规格与质量复审。其余范围仍包括:notify 转移到专用执行环境;gate 保持同步;日志异步批量写;摘要化大型 payload;用 AST 验证事件注册完整性。兼容现有 plugin hook 公开签名。
本地前端依赖、压缩与资源预算 complete
CDN runtime loader 和 legacy template 标签已删除,marked/KaTeX 由构建本地提供,产品 CDN 图标与 browser home 自动 favicon 请求改为本地文字标识;Starlette gzip responder 按权重协商并按响应 MIME 排除已压缩格式;center-tab 非首屏表面使用动态导入并显示 pending 状态;构建预算、HTTP 回归、完整 Python gate、默认 App 可视验收和五次禁用缓存的本地 browser trace 均已通过。
容量诊断与显式清理
增加只读容量分类和诊断输出;设计 cache/model 的显式清理入口;身份和浏览器用户数据保持默认不清理。该任务不阻塞错误与刷新性能修复。
| 任务 | depends_on | 可并行关系 | 职责排除 |
|---|---|---|---|
| TASK 1 | 无 | 可与 TASK 3、6、7 并行 | 只定义通用 action error/error store;不实现 Job 实体状态。 |
| TASK 2 | TASK 1 | 完成 TASK 1 后可与 TASK 4 并行 | job_status 唯一承载 Job 终态;复用 error store,不再新增第二种 Job error frame。 |
| TASK 3 | 无 | 可与 TASK 1、6、7 并行 | 只修改 server 的连接队列和 ws.frame enqueue adapter;不修改 events/bus.py 的 notify 调度。 |
| TASK 4 | TASK 3 | 可与 TASK 2、5 并行 | SessionDB/DAG 是唯一持久来源;删除不能复用的 MessageStore 持久接口。 |
| TASK 5 | TASK 1、TASK 3 | 与 TASK 4 的代码文件不重叠 | 唯一拥有 events/bus.py、registry 和 event log;Web bridge 使用 TASK 1 envelope、TASK 3 enqueue。 |
| TASK 6 | 无 | 可独立执行 | 不改变 CSP 信任边界,不修改 WebSocket 协议。 |
| TASK 7 | 无 | 可独立执行 | 默认只读诊断;删除入口需要单独交互设计和明确确认。 |
量化验收矩阵
目标值是实现合同,不是当前已达成的数据。每项需要自动化测试或可重复测量命令,不能只靠人工观察。
| 公共入口 | 场景 | 验收标准 | 证据类型 |
|---|---|---|---|
| 任意已注册 WS action | handler 抛异常 | 同一 socket 收到安全 operation_error;连接继续;UI 在 250 ms 内显示;日志可按 correlation ID 定位。 | 服务端 receive-loop component test + Web reducer/UI test。 |
| 列举、删除、设置和通道操作 | 底层存储异常 | 不得返回正常空集合或静默成功;旧数据保留并标 degraded,或显示明确失败。 | 逐 action 合同测试。 |
| JobRunner | 后台任务异常后刷新 | Job、AttachCard 和会话错误标记显示同一安全原因;retryable 一致。 | 持久化 + WS + React 集成测试。 |
| 最大已知会话首次加载 | 无客户端缓存 | 首个未压缩 wire projection ≤ 1 MiB;服务端 event-loop 占用 ≤ 50 ms;其余计算在线程执行。 | 固定会话 fixture 的 payload/time budget test。 |
| 同一会话重连 | 客户端已知 revision,缺失不超过 64 个 delta | 不发送完整 history;恢复帧 ≤ 256 KiB;终态、错误和问题均存在。 | 断连/恢复协议测试。 |
| 慢 WebSocket 客户端 | 持续产生 stream delta 与终态 | 队列不超过 256 帧或 4 MiB;增量可合并;终态不丢;无法恢复时明确关闭并要求重新同步。 | 受控慢 sender component test。 |
| EventBus notify | 慢同步 plugin + worker thread async subscriber | emitter 的 enqueue p99 ≤ 1 ms;async subscriber 不因缺少当前 loop 被跳过;失败不阻塞其他消费者。 | 并发 benchmark + 行为测试。 |
| Event log | 一次带 gate 的 tool.before | 事件只选择 session 或 global 一个目标日志;该目标中同一 event ID 只出现一行,包含 gate verdict,大型结果不写入完整正文。 | 真实事件文件断言。 |
| Web 首次加载 | 缓存关闭、接受 gzip | 零 CSP violation;零 CDN runtime 请求;首次 transfer ≤ 2 MiB;最大压缩 JS chunk ≤ 350 KiB。 | 默认 App browser trace + HTTP headers test。 |
| 状态目录诊断 | Profile 超过配置提醒值 | 准确分类容量;未获明确确认不执行删除;清理报告列出删除类别和可恢复性。 | 临时 profile 文件树测试。 |
测量口径与阈值依据
| 指标 | 起止点与环境 | 样本 | 阈值依据 |
|---|---|---|---|
| 会话 wire bytes | 后端 json.dumps 结果的 UTF-8 未压缩字节;默认 profile 的固定大型脱敏 fixture,关闭冷 context stats。 | 每个候选提交 5 次,报告中位数和最大值;消息/graph/detail 分项。 | 1 MiB 是当前 11.66 MB 的首阶段硬上限,并保证本地 JSON parse 和远程传输可控;单 detail 另有 1 MiB 分块上限。 |
| event-loop 占用 | 从 async handler 进入到第一次 await send 前,仅统计 event-loop 线程 wall time;projection 在线程的时间单独报告。 | 固定 fixture 20 次,报告 p50/p95/max。 | 50 ms 对齐交互任务不长期占用主循环的预算;不是整个页面完成时间。 |
| 错误 UI 延迟 | 浏览器收到完整 WS frame 到错误卡片出现在 DOM;不含网络。 | 20 次,报告 p95。 | 250 ms 允许一次状态更新和渲染,同时明显低于现有 2 秒重连周期。 |
| Notify enqueue | emit 入口到事件成功进入 bus 自有队列,不含 consumer。 | 单线程和 8 worker 并发各 10,000 次,报告 p50/p95/p99。 | p99 ≤ 1 ms 确保普通 notify 不显著增加工具和 turn 执行时间。 |
| 静态 transfer | 默认 App、缓存关闭、无浏览器扩展注入;统计 document origin 资源的 encodedBodySize。 | 冷加载 5 次,报告中位数和最大值。 | 2 MiB 是当前 3.42 MB 的首阶段预算;350 KiB 约束单个压缩 JS chunk,防止一个延迟表面进入首包。 |
| CSP | 只统计 OpenProgram document origin 产生的 violation;浏览器扩展或开发工具注入单独报告。 | 冷加载、打开数学消息、进入设置和 DAG 各一次。 | 产品自身零 violation 是安全和功能合同。 |
| 容量分类 | 以文件系统实际 allocated/logical bytes 为准,并列出分类规则;符号链接不跨出 state root。 | 固定临时树和真实只读诊断。 | 分类合计与根目录差异不超过 1% 或 1 MiB,以较大者为准。 |
兼容风险、采用与排除
| 选择 | 处理 | 理由与边界 |
|---|---|---|
| 标准 WebSocket + 应用层有界队列 | 已选设计 | 浏览器兼容性稳定;服务端仍需要明确队列、合并和恢复语义。 |
| 现有 MessageStore seq/delta | 部分复用 | 只复用 block/seq/delta 计算;SessionDB/DAG 已确定为唯一持久来源,独立 store 接口删除。 |
| 统一 operation_error | 已选设计 | 前端需要稳定 machine code 和安全文本;旧错误帧通过短期 adapter 兼容。 |
| EventBus notify 专用执行环境 | 已选设计 | 满足非阻塞观察合同;gate 不迁移,继续同步。 |
| 本地打包 marked/KaTeX | 已选设计 | 与严格 CSP、离线使用和可重复构建一致。 |
| 放宽 CSP 允许 CDN | 拒绝 | 增加远程脚本信任和可用性依赖,不能解决首次连接等待。 |
| WebSocketStream / WebTransport 全面迁移 | 拒绝 | 浏览器支持或复杂度不符合当前范围,也不能替代持久恢复和错误合同。 |
| 全局 React store/组件重写 | 拒绝 | 当前已有 rAF 合并、单消息订阅和行回收;未证明它们是主要限制。 |
| 自动删除 Chrome Profile | 拒绝 | 可能移除登录态、IndexedDB、扩展或其他用户数据;只允许显式分类清理。 |
to_thread。实现状态附录
本节集中区分当前已有能力、部分能力和未实现设计。上文的目标合同不因当前状态而弱化。
| 能力 | 状态 | 当前证据 | 剩余工作 |
|---|---|---|---|
| 统一 operation_error 与 error store | 服务端恢复协议 | 公共 dispatcher 和已迁移的 agent/channel/settings 管理存储异常使用同一最终低敏 operation_error;服务端按已认证 principal 在发送前写入独立 SQLite UserErrorStore,有 1,000 条/7 天上限、并发写保护和 correlation ID;list_user_errors 严格分页,acknowledge_user_error 按 exact ID 幂等关闭并关联请求,同 principal 窗口收到 exact-ID recovery;Web 与 TUI 已有兼容消费入口。 | 实现前端 hydration、scope 展示与去重,并迁移其余旧错误路径。 |
| 聊天回复错误 bubble | 已有 | 主 chat dispatcher 的 error turn 会终结 placeholder 并进入现有渲染路径。 | 与统一错误中心关联,避免重复 Toast。 |
| WebSocket 命令级异常隔离 | 四切片 | unknown action、未处理 handler exception 和已覆盖的管理存储异常先持久化、再返回低敏 operation_error;合法 request_id/session_id/action/operation_id 可关联,scope 明确,日志共享 correlation ID;错误 list/ack/recovery 已按 authenticated principal 隔离;Web 兼容旧 action_error,TUI 不展示 wire message/error,并隔离其他 session 的错误。 | 补齐 validation code,迁移 runtime/session/project/channel QR 等旧路径,并实现前端恢复和展示。 |
| 工具输出按需读取 | 已有 | 超过 32 KiB 的工具结果在 wire 中截断,展开时按 session/node/caller 授权读取。 | 把 Graph、context tree 和其他重复大字段纳入 projection。 |
| 流式渲染合并与长列表回收 | 已有 | text/thinking delta 按 animation frame 合并;MessageRow 使用稳定 selector;长列表计算 live rows。 | 补性能预算,确保新的 error/delta store 不破坏这些性质。 |
| R6 请求内 sibling navigation 索引 | 局部完成 | fn-run 使用一次枚举生成稳定排序位置;普通 chat 的 sibling_navigation_index 按请求建立 ID、目标 sibling group 与 deepest-leaf child 索引,只为当前可见 chain 涉及的完整 sibling group 排序,并只物化该 chain 的导航。公开 handle_load_session 回归同时约束 sibling 分组与 child 遍历为线性扫描,并覆盖相同时间戳和深层相邻分支;纯 DAG helper 回归覆盖重复/空 ID、runtime/tool/code、spawn、dangling predecessor 与 cycle。2,000 条消息、100 个可见节点的合成算法测量中位数由 134.076 ms 降为 1.355 ms;50,000 条离屏 sibling 的稀疏场景保持约 15 ms 与 2.75 MiB 峰值内存。两类独立复审均通过。 | R6 其余会话加载计算/发送分离、单次 session row snapshot、projection/delta 恢复和完整 event-loop 预算仍未实现;本行不表示 R6 整体完成。 |
| 后台 Job error 字段 | 部分 | Runner 广播和 TypeScript 类型包含 error。 | 状态存储、卡片渲染、刷新恢复和 Toast/错误中心关联。 |
| 会话增量恢复 | 未接入 | MessageStore 模块定义 seq/delta/full,但生产 server/web 没有使用。 | 按已确定的 SessionDB/DAG 单一来源实现 projection recovery,并删除独立 store 接口。 |
| 每连接 WebSocket 背压 | complete | 每个生产连接使用单一 sender;queued + in-flight 总量不超过 256 帧 / 4 MiB;慢客户端并发发送峰值从 400 降为 1。规格审查、质量审查和完整 Python gate 均为 PASS。 | 无。 |
| EventBus gate 日志唯一性 | 局部完成 | 已注册 gate 类型通过 emit 通知 observer 时延迟磁盘写入;同一个 Event 在 emit_gate 完成 verdict 后只写一条含 gate 字段的 JSONL。公共回归同时约束 observer 仍只收到一次;规格与质量复审均为 PASS。每批 1,000 个 paired tool.before 的同步日志微基准中位数由 105.15 ms 降为 58.48 ms,写入由 2,000 行降为 1,000 行;该结果不代表完整 EventBus 或端到端延迟。 | TASK 5 的异步 notify、批量日志、大型 payload 和 AST registry 检查仍未实现。 |
| EventBus 异步 notify 保证 | 未实现 | 普通 handler 同步执行;无 loop 的 async handler 被跳过。 | TASK 5。 |
| 本地 marked/KaTeX 与静态压缩 | complete | 源码与 legacy template 零产品 CDN 资源;marked/KaTeX 本地导入;JS/CSS/SVG gzip、加权协商、identity、小响应、MIME 排除、既有 encoding 和 SSE 回归通过;browser home 使用本地文字图标;构建预算为 2,089,594 B,最大构建 gzip JS chunk 为 186,752 B。五次本地禁用缓存加载的传输中位数和最大值均为 1,221,805 B,最大脚本传输均为 187,384 B;每次 65 个请求全部为本地 origin,零加载失败、零 CSP/console 错误。 | 远程部署环境的 browser trace 不属于本地 TASK 6 完成条件,部署时另行验证。 |
| Profile 容量诊断与清理 | 未实现 | 只能通过文件系统检查容量。 | TASK 7。 |
TASK 1 长期合同与实施切片
| 边界 | 长期合同 | 当前实现 |
|---|---|---|
| Wire envelope | 第 04 节 schema 是唯一最终合同;nullable 字段必须显式为 null,不得把 exception 或命令 payload 复制到 wire。 | 公共 WebSocket dispatcher 的 unknown action、未处理 handler exception 和首组管理 action 已生成全部最终字段;chat reply 自有 error turn 不在该路径重复生成。 |
| 关联身份 | request_id 是客户端生成的单次命令关联值,不作为权限或幂等凭据;服务端只回显非空、无控制字符且不超过 128 个字符的值。session_id 和 action 同样只回显安全字符串。 | 缺失或非法关联值返回 null,不得回显原始对象、超长文本或内部异常。 |
| 错误分类 | unknown_action 表示协议或版本不匹配且不可重试;handler_error 表示命令处理失败并保守标为不可重试。后续 action 必须使用注册 code,前端未知 code 使用通用低敏提示。 | 当前公共路径按 session、agent、settings、channel 或 system 分类;不把 exception message、traceback、路径、凭据或命令 payload 放入 wire/store,完整 traceback 只进入本地结构化日志。 |
| 兼容 | 新前端同时消费 operation_error 与旧 action_error;新服务端不发送双帧。Web 静态资源和 worker 属于同一发布单元,协议不承诺新服务端配旧前端。 | 保留 actionErrorNotice 与 consumeActionError 导出作为源码兼容别名,内部归一化为同一个 notice。 |
| 持久恢复 | UserErrorStore 以 error_id、session 和 request 建索引,记录按已认证 principal 隔离;list、acknowledge 与 reconnect recovery 使用明确 ID。 | SQLite store、1,000 条/7 天界限、persist-before-send、严格分页 list、exact-ID acknowledge、请求关联结果和同 principal recovery 已实现;前端 hydration 尚未实现,因此当前不声明刷新恢复完成。 |
| 展示 | 当前会话内错误、后台错误标记和 Toast 共享去重键;聊天回复已有 error bubble 保持原语义。 | dispatcher 命令失败继续显示一个 Toast;不同 session 的错误不得进入当前会话专属视图。 |
_websocket_handler 对 unknown action 和 handler exception 返回 operation_error,回显合法 request_id,拒绝非法元数据,保持连接处理后续 ping;新前端对新旧帧生成相同低敏 notice,未知 code 不显示 wire message/error。_websocket_handler 生成且只生成一个低敏 operation_error,不发送对应正常空集合或成功结果,并继续处理后续 ping。TUI 同时消费新旧命令错误 frame,不渲染 wire message/error;带有其他有效 session ID 的错误不会写入当前 transcript,只有当前 session 的 chat action failure 会结束对应 turn。state_recovery_required 关闭连接。存储回归覆盖 reopen、principal 隔离、线程与多进程首次并发写、跨 principal 过期清理、容量、7 天边界和低敏 wire/store。list_user_errors 只返回 authenticated principal 的开放记录,按 newest-first 严格分页;acknowledge_user_error 在请求连接内按确认结果、exact-ID recovery 的顺序发送,并独立通知同 principal 的其他窗口,不会通知其他 principal,请求连接发送失败也不阻止其他窗口恢复。重复或未知 exact ID 保持幂等且不创建错误;非法字段、畸形 JSON 和非对象 JSON 生成低敏 invalid_request,连接继续处理后续命令。存储、组件和协议回归共 35 项通过。TASK 3 任务简报与实施证据
| 证据项 | 已核对内容 |
|---|---|
| 批准来源 | 本页 WebSocket 队列、TASK 3 与量化验收条款;任务从当时的 main 基线启动。 |
| 生产文件 | server.py、_webui/ws_delivery.py、ws_actions/webtab.py、routes/misc.py。 |
| 测试文件 | test_websocket_delivery.py、test_websocket_command_lifecycle.py、test_webtab_control.py、test_web_owner_auth.py。 |
| 必须行为 | 单一 sender;跨线程只入队;queued + in-flight 有界;文本与状态合并;终态关键预留;溢出和发送失败要求 state recovery;断连完成所有 waiter。 |
| 兼容与排除 | 保留 _ws_connections、await ws.send_text 和精确 socket 控制的现有语义;不修改 EventBus、Session projection、前端重连或错误合同。 |
| RED | 通过真实 _websocket_handler 和 _broadcast 注入 400 个更新,旧实现的同时 send_text 峰值为 400,期望值为 1。 |
| GREEN 与受影响验证 | 聚焦命令 37 项通过;WebUI 与 web-use 扩大验证 448 项通过、1 项跳过,另有 1 个已复现的基线失败。 |
| 审查 | 规格审查 PASS;质量审查 PASS。两轮确认的 waiter、失败语义、终态分类和 in-flight 容量 finding 都有定向回归。 |
| 完整 gate | Ruff PASS。确定性基线问题修复并完成独立审查后,Python 完整执行为 6,243 passed、19 skipped、7 deselected、1 xfailed、零 failed、零 errors。 |
| 交付状态 | complete。规格、质量和完整 Python gate 均为 PASS;已进入隔离集成分支,尚未合入 main 或推送。 |
TASK 6 任务简报与实施证据
| 证据项 | 已核对内容 |
|---|---|
| 批准来源 | 本页前端与资源合同、TASK 6、Web 首次加载验收和静态 transfer 测量口径;任务从 TASK 3 基线提交创建独立工作区。 |
| 生产文件 | apps/server/openprogram_server/_webui/frontend.py;apps/web/app/layout.tsx;components/app-shell.tsx、components/chat/messages/markdown.ts、components/settings/provider-icon.tsx;lib/format-utils/markdown.tsx、lib/global.d.ts、lib/net/use-ws.ts、lib/runtime-bridge/conversations.ts、lib/runtime-bridge/markdown-render.ts;public/html/index.html;apps/web/package.json 与根 package-lock.json。删除 lib/external-libs.ts 和 lib/cdn-globals.d.ts。 |
| 验证文件 | tests/component/webui/runtime/test_webui_frontend.py;apps/web/scripts/runtime/check-local-runtime-assets.mjs、check-static-resource-budget.mjs;同步更新 check-chat-ui.mjs、check-markdown-sanitize.mjs 和 check-socket-access.mjs。 |
| 必须行为 | 所有产品 runtime asset 使用本地资源;marked 与 KaTeX 不等待外部 loader;browser home 不自动请求外部 favicon;CSP 继续使用 self-only 信任边界;JS/CSS/SVG 按大小写不敏感且遵守 q 权重的 Accept-Encoding 使用 Starlette gzip responder;小响应、已压缩响应 MIME、SSE 和已有 Content-Encoding 不压缩或不重复压缩;非首屏 center-tab 表面按需加载并显示 pending 状态;构建产物进入固定 2 MiB / 350 KiB 预算。 |
| 兼容与排除 | 没有修改 WebSocket 协议、Session projection、EventBus 或错误合同;useMarkdownReady 保留兼容导出;OwnerAuth 的 CSP 定义未放宽。 |
| RED | 初始 HTTP public-entry fixture 在大静态 JS 请求中缺少 Content-Encoding,执行结果为 1 failed / 13 passed;本地 runtime 资源脚本在 CDN loader 上失败;资源预算脚本在缺少生产构建产物时失败。规格修复 RED 以真实 PNG/WOFF2 请求复现重复 gzip,为 2 failed / 16 passed。质量修复 RED 新增无后缀 API MIME 与 q 权重入口,为 6 failed / 19 passed;browser 组件检查在 Google favicon helper 上失败。 |
| GREEN 与受影响验证 | 修复后 frontend 25 项全部通过,覆盖 JS/CSS/SVG gzip、identity、小响应、静态与无后缀 API MIME 排除、大小写与 q 权重、SSE 与已有 encoding;与 OwnerAuth/CSP 合并为 67 项通过。Web unit 115 项、browser/bookmarks/local-runtime source check、TypeScript 和生产构建通过。 |
| 资源预算 | chat.html 的保守构建产物检查统计 110 个初始及 CSS 引用资源,gzip level 6 编码合计 2,089,594 B;最大压缩 JS chunk 为 186,752 B,均低于固定预算。 |
| 审查与修复 | 规格初审确认请求后缀排除无法覆盖无后缀 public response;首轮修复后继续保留 Starlette responder。质量初审确认三项:排除条件必须基于响应 MIME、Accept-Encoding 必须处理大小写与 q 权重、browser home 不得自动请求 Google favicon;质量修复分别增加 MIME-aware responder、显式 gzip 优先于 wildcard 的协商和本地 DOM 文字图标。最终规格限定复审 PASS,质量限定复审 PASS。 |
| 完整 gate | 确定性基线问题已分别修复并通过独立规格与质量审查;随后 Python 完整执行为 6,243 passed、19 skipped、7 deselected、1 xfailed、零 failed、零 errors。此前偶发的 models.dev 失败与后台线程错误没有复现。 |
| 交付状态 | complete。规格、质量、完整 Python gate、默认 App 刷新与可视验收均为 PASS;五次本地禁用缓存 trace 均低于 2 MiB / 350 KiB 预算,且无外部请求、加载失败或 CSP/console 错误。远程服务器 trace 和推送未执行。 |
当前审计验证入口
PYTHONPATH=apps/server:. .venv/bin/python -m pytest \ tests/component/agent/turns/test_event_bus.py \ tests/component/agent/turns/test_event_gate.py \ tests/unit/agent/behavior/test_event_bridges.py \ tests/unit/agent/dispatcher/test_error_turn_finalized.py \ tests/component/agent/jobs/test_async_job_recovery.py \ tests/component/webui/chat/test_session_tool_output_transport.py \ tests/unit/webui/test_webui_chat_dispatcher.py -q cd apps/web && npm test cd apps/web && npx tsc --noEmit python3 -m scripts.docs_site.build python3 -m scripts.docs_site.checklinks
这些入口证明当前受支持路径没有被文档改动破坏,不证明本文目标已经实现。各 TASK 必须先增加对应的错误 UI、慢客户端、projection、队列、EventBus 并发或资源预算 RED。
相关权威文档
实现证据只在对应任务经过规格审查、质量审查和完整验证后更新。本文不记录日期或提交历史;演变过程由 Git 保存。
有界会话历史
会话加载将最新记录页与更早的历史分开。页面保留其捕获的分支头和稳定的最早消息游标。一页包含完整显示行及其 caller 后代,最多 50 个顶层行,目标序列化预算为 512 KiB。无法拆分的超大行单独返回。分页不修改已存储历史或模型上下文。历史读取不改变焦点、实时会话头、执行状态或问题重放。
Codex 通过分页历史 API 分离线程元数据、轮次和条目。DeepSeek Harness 提供按消息对齐的页面和序列游标。本实现采用稳定分支快照、向前读取历史和增量实时更新,保留 OpenProgram 现有的分支图和执行恢复合同。不引入另一套会话存储,也不改变模型上下文压缩。
仅用于传输的 extra 对象省略重复提升的字段。现有工具输出预览保留经授权展开完整输出的能力。超过传输帧预算的逻辑历史和图响应以有序、逐次等待完成的分片发送;客户端在分派前重建完整响应,并按首片到达顺序与后续实时通知保持排序。延迟响应共享每连接 6400 万个 UTF-16 代码单元、8 个未完成传输和 1,024 个逻辑响应的预算。这样可以处理单个大行,而不静默丢弃数据或扩大 4 MiB 发送队列。普通实时更新独立继续。分片缓冲区属于单个连接,断连时丢弃。重连加载新的最近页,并使用现有执行恢复机制。
客户端按稳定标识在前面插入更早的行,不替换更新的实时内容,也不移动当前选中会话。分页请求捕获当前加载代次,在重新加载、切换分支或更换 socket 后被忽略。不可用游标产生明确的可重试错误,而不是反复重连。插入更早消息时保持滚动位置。图保持完整以支持现有分支和压缩控件;服务端历史投影仍扫描捕获的会话。存储索引和图的延迟渲染是单独的优化,本次不声称完成。
验收:复制出的故障会话可打开且不发生队列溢出;每个逻辑页保留准确内容及 caller 归属;页面帧保持有界,包括 Unicode 和单个超大行;游标耗尽时不丢失或重复消息;陈旧响应不能覆盖实时或更新状态;断连释放未完成缓冲;分支、压缩、工具展开和执行恢复仍可用。实现状态:已实现并经过独立审查。联合验证通过 8,591 项 Python 测试、635 项 Web 测试、4 项浏览器测试和 190 项 CLI 测试,以及 Desktop、静态、类型、构建和文档检查。已安装 App 分五页获取全部 26 个原始显示行,无重复、乱序或断连;观测到的最大传输帧为 51,564 字节。重新加载和心跳正常。
References: Codex 历史协议; DeepSeek Harness 历史.
长会话布局与加载
有界历史协议将传输大小与会话记录长度分离。长会话还需要避免递归栈溢出的图构建:此前的深度遍历对每个节点追溯全部祖先,并递归访问会话链。包含 1,500 个节点的线性会话在首个页面交付前就会超过 Python 递归限制。
图深度现在先通过带记忆缓存的父链分类计算,再进行迭代前序遍历。父路径进入环的节点仍作为独立根节点,保留现有防御行为。分叉、重试、派生分支和调用子节点保留当前行顺序及偏移。父链分类只访问每个节点一次;遍历不依赖 Python 调用栈。后续消息规范化只对回复建立一次索引。泳道列布局维护每条泳道的宽度和已占用的最大列,不再重复扫描已经放置的节点。
会话消息聚合和按字节预算准备页面在事件循环外执行,需等待的 WebSocket 发送仍在所属循环执行。分支图构建也在该循环外执行。快照和增量事件顺序不变。现有内存会话索引仍是读取的权威来源;本次不引入持久缓存、模式迁移或历史删除。
验收通过公开图构建器和会话加载处理器测试长链、大量分叉和非法环。小图输出必须符合现有布局合同,包含 10,000 个节点的会话记录必须能加载有界最近页且不发生递归失败。完整图和初始存储索引的开销仍与会话大小成正比;本次不声称实现恒定内存存储或基于索引的会话记录投影。有界历史章节中的 Codex 和 DeepSeek 参考资料仍用于分页对比。
实现状态:已实现并经过独立审查。公开布局和会话加载测试覆盖 10,000 个节点的长链、大量分叉和非法环;保留完整图输出和有界会话记录页面。联合验证及已安装 App 的长历史重放通过。初始图和存储内存仍随会话大小增长。