08feae8bef
落地 H5 Session 架构 Patch 1–5(Broker 收口、Router decision、SSE taxonomy、goosed 边界检查), 并新增可选 MEMIND_RUN_STREAM_REPLAY run 事件回放与 H5 假交付 guard;修复 Finish 先于 agent-run gate 导致 UI 永久 loading 的竞态,接入 verify:h5-session-patches 回归脚本。 Co-authored-by: Cursor <cursoragent@cursor.com>
82 lines
5.0 KiB
Markdown
82 lines
5.0 KiB
Markdown
# 待修复:H5 页面「假交付」检测与 write_file 自动重试
|
||
|
||
> **状态:已实现 guard 模块(2026-07-06),默认关闭 `MEMIND_H5_HTML_FINISH_GUARD=0`**
|
||
> **原暂缓原因**:H5 session / broker 分支已在本分支落地,Finish guard 已接入 `server.mjs` onAfterFinish。
|
||
|
||
## 症状
|
||
|
||
- H5 Agent 回复含「页面已生成 / 生成完成 / 已保存至 public/…」及公网 Markdown 链接
|
||
- 用户点击链接 **404**
|
||
- 磁盘 `MindSpace/<userId>/public/*.html` **不存在**
|
||
- Finish 后 `syncPublicHtmlAfterFinish` 无 tool call 可 materialize,文件不会凭空出现
|
||
|
||
**典型案例(103 生产,2026-07-06):**
|
||
|
||
- 目标:`public/kids-posture-business.html`
|
||
- 第一次:Agent 在聊天里输出完整商业包内容 + 链接,但未调用 `write_file` → 404
|
||
- 用户追问后第二次同会话重试:真正 `write_file` 落盘 → 200
|
||
- **后续(同页 10:54)**:HTML 用 `edit_file` 加了 5 张 `<img src="assets/*.svg">`,Agent 宣称「SVG 已 write_file 落盘」,但 `public/assets/kids-posture-hero.svg` 等 **5 个文件均不存在** → 页面 200、插图全部 404(与 HTML 假交付同类问题,范围扩展到非 `.html` 资源)
|
||
|
||
## 根因(非意图识别)
|
||
|
||
| 层级 | 行为 | 第一次失败时 |
|
||
|------|------|--------------|
|
||
| 意图路由 | `chat-intent-router` / `buildAutoChatSkillPrefix` 命中「生成页面」 | ✅ |
|
||
| 技能提示 | `static-page-publish` 要求「必须先 write_file」 | ✅ 已注入(软约束) |
|
||
| 模型执行 | 应调用 `write_file` | ❌ 未调用 |
|
||
| Finish materialize | 仅从 messages 中 tool call 写盘 | ❌ 无 tool call |
|
||
| 回复校验 | 拦截「已生成」但磁盘无文件 | ❌ **H5 无此闸门** |
|
||
|
||
微信通道已有 `shouldRetryHtmlGenerationReply`(`wechat-mp.mjs`),检测到链接/宣称与磁盘不一致时会 **session 重试**。H5 SSE 路径(`server.mjs` → `tkmindProxy.proxySessionEvents` → `onAfterFinish`)**没有等价逻辑**。
|
||
|
||
## 拟议修复(合并其它分支后统一做)
|
||
|
||
### 目标行为
|
||
|
||
当 Assistant 回复同时满足:
|
||
|
||
1. 含「页面已生成 / 生成完成 / 已发布 / …」类成功宣称,**或**含 `MindSpace/.../public/*.html` 公网链接
|
||
2. 磁盘上对应 `public/*.html` **不存在**(或仅为 stub)
|
||
|
||
则:
|
||
|
||
1. **自动重试**:向同 session 发送 follow-up(参考 `wechat-mp.mjs` 的 `executeSessionReply`),明确要求 `load_skill` → `static-page-publish` → `write_file` 写入缺失路径
|
||
2. **继续检验**:重试后再次 `materializeMissingPublicHtmlWrites` + 磁盘校验;仍失败则打 `[MindSpace]` warn 日志(可选:前端提示「页面落盘失败,请重试」)
|
||
3. **资源完整性**:解析已落盘 HTML 中的 `src`/`href`(含 `assets/*.svg|png|jpg|webp`),缺失则一并列入 retry 清单(`materializePublicHtmlWritesFromSessionEvent` 目前只处理 `.html` tool call,不能假设插图已落盘)
|
||
3. **上限**:建议最多 1~2 次重试,避免无限 loop
|
||
|
||
### 建议落点(实现时再对齐当时主线)
|
||
|
||
| 模块 | 改动 |
|
||
|------|------|
|
||
| 新建 `mindspace-h5-html-finish-guard.mjs`(或并入 `mindspace-public-finish-sync.mjs`) | 检测假交付、提取缺失 `public/*.html`、构建 retry prompt |
|
||
| `server.mjs` `onAfterFinish` | Finish 后 sync 仍失败 → 调用 guard 重试 → 再 sync + register artifacts |
|
||
| 复用 | `wechat-mp.mjs` 中 `shouldRetryHtmlGenerationReply`、`createPublicHtmlLinkExists`、`resolveHtmlPublishArtifacts` 等(考虑抽到共享模块,避免 H5 直接 import 整个 wechat-mp) |
|
||
| 单测 | `mindspace-h5-html-finish-guard.test.mjs`;接入 `npm run verify:mindspace-publish-guards` |
|
||
| 文档 | 合并后更新 [mindspace-publish-and-chat-finish.md](../regression-guards/mindspace-publish-and-chat-finish.md) |
|
||
|
||
### 与进行中分支的协调点
|
||
|
||
合并前必须先读并对齐:
|
||
|
||
- [docs/h5-session-architecture-20260706.md](../h5-session-architecture-20260706.md)
|
||
- `session-broker.mjs`、`agent-run-gateway.mjs`、`tkmind-proxy.mjs` 的 Finish / SSE 收口
|
||
|
||
**不要在 broker 分支未合并前单独改上述文件的 Finish 路径**,否则合并冲突与行为分叉。
|
||
|
||
## 实现 checklist(待开工时)
|
||
|
||
- [ ] 其它分支(H5 session / broker)已合并 `main` 且 CI 绿
|
||
- [ ] 从 `wechat-mp.mjs` 抽取或复用 HTML 发布校验纯函数
|
||
- [ ] H5 `onAfterFinish` 接入 guard + 有限次 retry
|
||
- [ ] 单测覆盖:假链接 404、retry 后落盘、正常 Finish 不误触发
|
||
- [ ] `npm run verify:mindspace-publish-guards` 全绿
|
||
- [ ] 103 手工:生成页 → 故意 mock 无 write_file → 确认自动 repair 或明确失败提示
|
||
|
||
## 参考
|
||
|
||
- 回归守卫:[mindspace-publish-and-chat-finish.md](../regression-guards/mindspace-publish-and-chat-finish.md)
|
||
- 微信 retry:`wechat-mp.mjs` — `shouldRetryHtmlGenerationReply`、`executeSessionReply`
|
||
- 落盘:`mindspace-public-finish-sync.mjs` — `materializeMissingPublicHtmlWrites`、`syncPublicHtmlAfterFinish`
|
||
- 技能前缀:`chat-skills.mjs` — `generate-page` prompt
|