feat(h5): web 联网能力、实时查询路由与 session Finish 对齐

- 新增 web 能力并挂载 platform/web(web_search/fetch_url)
- 实时查询强制 web skill,router fallback 与 await session Finish
- Session Broker 覆盖率/指标、stream replay 与相关单测/E2E 脚本

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-07-06 16:06:26 +08:00
parent 08feae8bef
commit 14a00774d9
41 changed files with 3501 additions and 126 deletions
+3
View File
@@ -5,6 +5,9 @@
| 文档 | 问题 | 状态 | 阻塞原因 |
|------|------|------|----------|
| [h5-finish-before-run-gate-20260706.md](./h5-finish-before-run-gate-20260706.md) | Finish 先于 agent-run gate 导致 UI 永久 loading | 已修复(分支内) | 随 H5 session 分支合并 |
| [h5-run-stream-replay-4b-20260706.md](./h5-run-stream-replay-4b-20260706.md) | Run SSE replay4b | 已修复(分支内) | 随 H5 session 分支合并 |
| [h5-session-stream-replay-4c-20260706.md](./h5-session-stream-replay-4c-20260706.md) | Session SSE replay4c | 已修复(分支内) | 全量 soak 待执行 |
| [h5-public-html-fake-delivery-guard-20260706.md](./h5-public-html-fake-delivery-guard-20260706.md) | H5 Agent 宣称「页面已生成」但 `public/*.html` 未落盘(无 write_file | 待修复 | 等 H5 session / broker / SSE 收口分支合并后,与 `server.mjs``tkmind-proxy.mjs` 统一改 |
| [h5-local-soak-20260706.md](./h5-local-soak-20260706.md) | 本地全开开关 soak 验收清单 | **待执行** | 等 P0–P1 开发全部完成后再跑 |
开工前:读对应 md 全文 + [h5-session-architecture-20260706.md](../h5-session-architecture-20260706.md),确认主线已合并再动 Finish 路径。
@@ -0,0 +1,88 @@
# H5 Session 本地 Soak 测试
> **状态:自动化已通过(2026-07-06 14:57);手工场景待 john 账号验证**
> 分支:`0706-bug干净` · 禁止 merge main / 上 103 / commit(除非另行指令)
## 前置
```bash
# .env 建议全开(勿提交 Git)
MEMIND_SESSION_BROKER_ENABLED=1
MEMIND_SESSION_BROKER_METRICS=1 # 场景 8
MEMIND_ROUTER_NORMALIZED_DECISION=1
MEMIND_SSE_EVENT_TAXONOMY=1
MEMIND_RUN_STREAM_REPLAY=1
MEMIND_SESSION_STREAM_REPLAY=1 # 场景 54c
MEMIND_H5_HTML_FINISH_GUARD=1
pnpm dev
# 访问 http://127.0.0.1:5173/ 全屏聊天
```
**2026-07-06 检查**:本地 `.env` 已有 5 个开关,**缺少** `MEMIND_SESSION_STREAM_REPLAY``MEMIND_SESSION_BROKER_METRICS`
手工测场景 5/8 前请补上并 **重启 `pnpm dev`**
**DB**:已执行 `migrateSchema``h5_session_stream_events` 表已创建。若 dev 在迁移前启动,需重启后 4c store 才会注入。
---
## 自动化验收(2026-07-06 14:57
| 命令 | 结果 |
|------|------|
| `npm run verify:h5-session-patches` | ✅ pass(含单测 + broker coverage + goosed boundary + publish guards |
| `npm run verify:chat-finish-sync` | ✅ pass |
| `npm run verify:mindspace-page-sync-guards` | ✅ pass |
| `node scripts/check-session-broker-coverage.mjs` | ✅ pass |
| `node scripts/check-goosed-proxy-boundary.mjs` | ✅ pass |
| 本地服务探活 `5173` / `8081` | ✅ HTTP 200 |
| `h5_session_stream_events` 表 | ✅ 迁移后存在 |
### 历史数据辅助(john,近 2h intent_routed
| 指标 | 值 |
|------|-----|
| 采样条数 | 10 |
| router timeout fallback | 2/10(睡前故事、讲笑话各 1 次) |
| 其余 | `source: llm`,路由正常 |
> 自动化**不能**替代场景 1–8 的浏览器/DevTools 验证。
---
## 测试账号
| 用户 | 用途 |
|------|------|
| john | 主 soak 账号(已有 session 历史) |
## 场景清单
| # | 场景 | 操作 | 预期 | 结果 | 备注 |
|---|------|------|------|------|------|
| 1 | 普通 direct_chat | 「讲一个睡前故事吧」 | ~7s 内整段回复;loading 点消失 | ☐ 待人工 | 历史:7.1s,曾 router timeout |
| 2 | 简单 hi | 「hi」 | 23s 回复;无永久 loading | ☐ 待人工 | Finish 竞态 |
| 3 | agent 生成页 | 「帮我生成一个唐诗页面…」 | agent 路径;Finish 后 public HTML 落盘 | ☐ 待人工 | publish guards 已自动化 ✅ |
| 4 | run SSE 重连 | 发消息 → DevTools 断网 3s → 恢复 | run events 从 Last-Event-ID 补发 | ☐ 待人工 | 4b,需 `MEMIND_RUN_STREAM_REPLAY=1` |
| 5 | session SSE 重连 | 流式回复中断网 | 重连后 merge,不盲覆盖 | ☐ 待人工 | 4c,需补 `MEMIND_SESSION_STREAM_REPLAY=1` + 重启 dev |
| 6 | MindSpace 小窗 | `/?preview=mindspace` 发消息 | 同全屏,Finish 不丢消息 | ☐ 待人工 | |
| 7 | Router 稳定性 | 连发笑话/写诗/故事各 1 条 | 无频繁 1500ms timeout fallback | ☐ 待人工 | 历史 2/10 timeout;快路径已合入未 soak |
| 8 | Broker metrics | 403 访问他人 session | Portal 日志含 `[session_broker.ownership.denied]` | ☐ 待人工 | 需 `MEMIND_SESSION_BROKER_METRICS=1` |
## 记录模板(每条失败场景填写)
```text
场景 #:
时间:
sessionId / runId:
现象:
Network / Console:
是否 blocker:
```
## 结论(手工测试后填写)
- **自动化闸门**:✅ 通过(2026-07-06
- **整体 soak**:☐ 待人工
- **blocker 列表**:(暂无)
- **是否可进入 merge main 讨论**:等手工场景 1–8 完成后再定
@@ -0,0 +1,59 @@
# Patch 4cSession SSE Replay(进行中,禁止 merge main / 上 103
> **状态:代码 + 单测已完成(2026-07-06);全量 soak 待开发完成后执行。**
## 目标
解决 H5 session SSE 重连丢状态问题(§8.3):
- Portal 持久化经 sanitizer 后的 session 事件到 `h5_session_stream_events`
- SSE 帧带 Portal `id:` cursor(与 run replay 4b 对齐)
- 客户端带 `Last-Event-ID` 重连时,先从 DB 补发 missed events
- 若补发以 `Finish`/`Error` 终态结束,则不再挂 upstream(避免重复 Finish
## 开关
```bash
MEMIND_SESSION_STREAM_REPLAY=1
```
与 4b 独立:可只开 run replay、只开 session replay、或两者同开。
## 改动文件
| 文件 | 说明 |
|------|------|
| `session-stream.mjs` | replay 纯函数、flag |
| `session-stream-store.mjs` | MySQL 读写 |
| `session-stream*.test.mjs` | 单测 |
| `db.mjs` / `schema.sql` | `h5_session_stream_events` 表 |
| `tkmind-proxy.mjs` | 重连补发 + live persist + SSE `id:` |
| `server.mjs` | store 注入、启动 flag 日志 |
| `.env.example` / `docs/local-dev.md` | 文档 |
## 行为说明
| 场景 | 行为 |
|------|------|
| 首次订阅(无 Last-Event-ID | 纯透传 goosedlive 持久化(开关开启时) |
| 重连(有 Last-Event-ID | DB 补发 → 若未到 terminal 再挂 upstream |
| cursor miss | 全量补发(同 4b |
| direct chat session | 仍走 `sendDirectChatSessionEvents`,不经本路径 |
## 验证(全量开发完成后)
```bash
npm run verify:h5-session-patches
```
手工:
1. `.env``MEMIND_SESSION_STREAM_REPLAY=1`,重启 `pnpm dev`
2. agent 路径发消息,DevTools 看 `/sessions/:id/events``id:`
3. 流式过程中断网/刷新,确认重连后 token/Finish 不丢
## 与 4b 关系
- **4b**`GET /agent/runs/:id/events` + `h5_agent_run_events`
- **4c**`GET /sessions/:id/events` + `h5_session_stream_events`
- 双通道职责仍分离(§1.3