Files
memind/docs/regression-guards/h5-session-stream-replay.md
T

3.3 KiB
Raw Blame History

H5 会话 SSE 断线续播守卫

症状

后台 h5_agent_runs 已经是 succeeded、Goose 也已发送 Finish,但 H5 输入区仍显示“正在执行任务…”。常见触发条件是手机切后台、锁屏或网络切换发生在 ActiveRequestsFinish 之间。

根因

Portal 对 SSE 帧使用本地持久化游标。该游标可能是 Portal 生成的 UUID,并不一定是 Goose 能识别的上游事件 ID。若重连时把 Portal UUID 原样作为 Last-Event-ID 发送给 Goose,Goose 无法续播,浏览器就收不到遗漏的 Finish

必须保留的行为

  1. Portal 游标只用于查询 h5_session_stream_events,不得原样传给 Goose。
  2. 连接 Goose 时,只能使用已持久化的 upstream_event_id
  3. 当前游标或重放尾部没有可用 upstream_event_id 时,必须省略 Last-Event-ID,让 Goose 从权威事件流重新回放。
  4. 本地重放最后一条已经是 FinishError 时,不再连接 Goose。
  5. 重放不得破坏现有消息 ID 合并、Finish 幂等计费和 MindSpace Finish 同步守卫。

视觉工具故障旁路

文本模型可能在会话历史中遇到 read_image 返回的图片块,并以 unknown variant image_url, expected text 拒绝整次请求。图片检查属于可选增强, 不得因此中断页面编辑、写文件、发布或其它文本主任务。

必须保留:

  1. 将该上游错误分类为 SESSION_VISUAL_CONTEXT_UNSUPPORTED,不得作为普通瞬时错误盲重试。
  2. 用干净会话恢复可见文本上下文,并移除新会话策略中的 read_image,明确要求 Agent 跳过视觉检查、继续主任务。
  3. 一次执行中允许先恢复污染的工具历史,再单独进行一次视觉降级恢复;两个原因不得共用同一个“一次性恢复”开关。
  4. 普通重试前必须取消旧会话的活动请求,避免后续请求被 Session already has an active request 锁死。
  5. 视觉降级只影响当前恢复会话,不得全局关闭图片能力,也不得删减其它开发工具或数据能力。

关键路径

  • session-stream.mjs
  • session-stream-store.mjs
  • session-reply-wait.mjs
  • agent-run-gateway.mjs
  • capabilities.mjs
  • tkmind-proxy.mjs
  • server/portal-session-routes.mjsPortal Session 路由、流中 public HTML 落盘与 Finish 后同步)
  • src/hooks/useTKMindChat.ts

回归测试

node --test session-stream.test.mjs session-stream-store.test.mjs chat-agent-run-gate.test.mjs
node --test --test-name-pattern='proxySessionEvents' tkmind-proxy.test.mjs
node --test session-reply-wait.test.mjs capabilities.test.mjs agent-run-gateway.test.mjs tkmind-proxy.test.mjs
npm run verify:h5-session-patches
node --test billing-session-concurrency.test.mjs

必须覆盖:

  • Portal 游标存在但 upstream_event_id 为空时,不向 Goose 发送错误游标,并能收到 Finish
  • 有映射时,Portal 游标正确转换成 Goose 游标。
  • 多轮会话中选择最新可映射的上游游标,不被上一轮 Finish 截断。
  • Finish 重放不重复扣费,消息合并与页面同步守卫继续通过。
  • image_url 不兼容时切换到无 read_image 的干净会话,并继续文本主任务。
  • 工具历史恢复后仍可执行一次独立的视觉降级恢复。
  • 瞬时重试前先取消旧请求,避免活动请求冲突。