跳转至

03-19~20:稳定性修复

这是 ClawCraft 项目中最关键的稳定性修复阶段,彻底解决了拖拽建筑导致 SSE 无限重连/页面卡死的问题。

Dad 的关键产品决策

开发环境架构决策

问题背景:开发环境 craft.dev.dora.restry.cn 需要指向真正部署 ClawCraft 插件的服务器 owl

关键约束不要动 clawedbot 的 OpenClaw,开发环境应测试 owl 上安装的插件

网络方案: - owl 的 18789 外网不可达 - 内网 172.16.0.8:18789 可达 - 采用 内网反代 方案

安全与认证临时决策

问题:页面出现 /clawcraft/action 疯狂循环调用

Dad 临时决策

开发模式先禁用 Gateway token,但把这件事记录为严重安全事件,等功能稳定后再补安全

执行:Gateway 改为 auth=none,但保留 loopback 绑定

系统化审查启动

03-20 晚间: - 核心 bug 修复后,Dad 要求执行 /audit - 要求"一口气把剩余 7 项都搞定"

技术问题与根因分析

SSE 死循环问题(核心问题)

现象: - 一拖拽建筑界面就卡死 - layout.load 在极短时间内被调用几十次 - SSE 连接数从个位数暴涨到 70、250、467

排查过程中的错误假设: 1. ❌ 误以为是 layout.save 触发 broadcast → SSE → 重渲染 - 事实:actionLayoutSave 并不 broadcast 2. ❌ 误以为是 Caddy 未正确 flush SSE - 确实是问题之一,但不是唯一根因 3. ❌ 误以为是 token 获取失败导致重连 - 部分相关,但不是最深层原因

根因 1:SSE 代理缓冲

问题:两层 Caddy 都缺少 flush_interval -1

表现text/event-stream 被缓冲,浏览器收不到 connected 事件

后果:触发 EventSource.onerror -> reconnect 死循环

修复

reverse_proxy localhost:18789 {
    flush_interval -1
}

根因 2:getAccessToken() 风暴

真正导致无限重连的核心: - 前端 useSSE / authFetch 在开发环境仍不断调用 Logto getAccessToken() - 在"后端已跳过认证"的开发模式下,前端仍执着获取 token

连锁反应: 1. effect 依赖不稳定 2. 连接建立后很快被清理 3. 旧连接的 onerror 又触发重连 4. 新旧连接互相打架 5. 形成指数级 SSE 风暴

根因 3:Gateway vs 插件双层认证混淆

关键认知澄清: - ClawCraft 虽然是插件,但 /clawcraft/* 请求仍然必须经过 Gateway - Gateway auth 与插件自身 auth 是两层机制

问题:即使 Gateway auth=none/clawcraft/* 仍返回 401

根因:插件自己实现了 withAuth(Logto JWT 验证)

解决: - 给插件增加环境变量 CLAWCRAFT_AUTH_SKIP=1 - 注意:最初改错位置,Gateway 实际加载的是 ~/.openclaw/extensions/clawcraft/index.ts - 更新 extensions 目录版本并清理 jiti 缓存后才生效

解决方案

最终修复方案

  1. Caddy 配置:两层 Caddy 都加 flush_interval -1
  2. 前端:开发模式下移除/绕过 connect 中的 getAccessToken() 调用
  3. effect 依赖:稳定 useSSE 的 effect 依赖,避免因函数引用变化反复重跑
  4. 版本号:增加版本号显示到左下角,便于确认部署是否生效

版本号格式b03201117(b + MMDD + HHMM)

v0.9.5 系统化审查优化

安全: - 用 react-markdown 替换 dangerouslySetInnerHTML - 清理无用依赖

认证性能: - authFetch token 缓存,减少 Logto setIsLoading 风暴

可访问性: - 为 ReconnectBannerResourceBarWelcomeOverlayEventTickerChatDrawer、面板关闭按钮等补充 ARIA

视觉与动效: - 去掉不必要的 animate-pulse - 统一 gray -> slate - 版本号格式更有意义

交互: - WelcomeOverlay 加 focus trap - 移动端提示替代硬编码 min-width:1280px - 统一原生 selectStyledSelect - 常见文案抽到 i18n.ts

性能/结构: - ChatDrawer 采用"限制渲染数量 + 加载更多" - WorldCanvas.tsx 大幅拆分,提取 world-helpers.ts - 文件从 1912 行降到 1151 行

音效问题排查

现象:交互音效消失

排查结论: - 代码层面音效系统、事件绑定、soundManager 基本完整 - 浏览器 AudioContext 的 resume() 是异步的 - 更可能是浏览器缓存/资源缓存导致加载了旧版 chunk - Caddy 未对 index.html 明确设置 Cache-Control: no-cache

里程碑

03-19

  • [x] 开发环境接入 owl
  • [x] 识别认证与环境耦合问题
  • [x] 插件 CLAWCRAFT_AUTH_SKIP=1 机制
  • [x] /clawcraft/config 返回 200

03-20 白天

  • [x] 定位 SSE 代理缓冲问题
  • [x] Caddy flush_interval -1 修复
  • [x] 定位 getAccessToken() 风暴

03-20 晚上

  • [x] SSE 死循环彻底修复
  • [x] Dad 确认"终于修好了"
  • [x] 执行 /audit 系统化审查
  • [x] v0.9.5 版本发布

版本记录

版本 时间 主要变更
v0.9.5-b03201117 03-20 11:17 SSE 修复
v0.9.5-b03201154 03-20 11:54 安全优化
v0.9.5-b03201213 03-20 12:13 可访问性
v0.9.5-b03201220 03-20 12:20 视觉统一
v0.9.5-b03201231 03-20 12:31 交互优化
v0.9.5-b03201245 03-20 12:45 性能/结构

关键经验总结

SSE 经过反向代理的必备配置

flush_interval -1

否则浏览器会因收不到流式事件而无限重连。

前后端认证状态必须同步

开发模式下若后端跳过认证,前端也必须同步跳过 token 获取;否则会出现认证逻辑与连接生命周期互相干扰的问题。

Gateway auth 与插件 auth 是两层

即使 Gateway auth=none,插件自身的 withAuth 仍会生效。排查认证问题时必须区分这两层。