Files
Cyrene/docs/debug_log/2026-05-21-round2-fixes.md
T
AskaEth b123a36aae fix: 第四轮调试 — 回复去重/消息时序/UI布局/自主思考深度优化 + 文档重整
后端修复:
- main.go: 恢复 /api/v1/chat 路由中丢失的 handleChat 调用 (空响应回归)
- orchestrator.go: splitChatByLines 改为双换行分割, 避免单换行误拆
- chat_handler.go: multi_message 增加 !hasReview 守卫, 消息延迟 200→800ms
- thinker.go: RecordUserMessage 追踪活跃会话ID, 推送主动消息到正确会话
- thinker.go: 增强思考提示词 — 禁止在用户休息/离开时发送主动消息

前端修复:
- useWebSocket.ts: stream_segments 不再创建消息气泡, 消除重复回复
- MessageBubble.tsx: 动作消息居左对齐无头像, 时间戳移至气泡外侧 hover 显示
- ChatInput.tsx: 昔涟输入提示移至输入框上方, 波点动画效果
- MessageList/TypingIndicator/ChatContainer: 清理冗余 isTyping 传递
- MemoryPanel.tsx: 新增记忆面板组件

文档重整:
- docs/debug/ → docs/debug_log/ 重命名统一
- 新增 debug_log/README.md 索引
- .gitignore: 新增 android/ 排除规则

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 13:09:18 +08:00

8.0 KiB
Raw Blame History

Round 2 深度调试报告

日期: 2026-05-21
类型: 综合性深度调试 + 即时修复
状态: 完成


1. 执行摘要

本次 Round 2 深度调试通过 Chromium CDP 进行前端 E2E 测试 + 后端全面 API 验证,发现并修复了 3 个问题(1 个 P0 崩溃、1 个 P0 后端 PANIC、1 个测试工具缺陷),最终 14/14 全部通过


2. 发现的问题与修复

2.1 P0 — useSpeechSynthesis.ts cancel() 未守卫调用

文件: frontend/web/src/hooks/useSpeechSynthesis.ts

问题: 参考 docs/debug/2026-05-21-crash-cancel.md 的详细分析。

  • stop() 回调 (原 L223):仅当 utteranceRef.current 存在时才调用 cancel(),但浏览器 Web Speech API 可能在任意时刻有活跃 utterance,导致 cancel() 被跳过
  • Cleanup effect (原 L261):同样的问题,组件卸载时可能跳过 cancel()

修复: 将守卫条件从 utteranceRef.current 改为 isSupported

// stop() — 当 isSupported 为 true 时始终调用 cancel()
const stop = useCallback(() => {
  if (!isSupported) {
    console.warn('[useSpeechSynthesis] stop: speechSynthesis not supported');
    return;
  }
  window.speechSynthesis.cancel();
  // ... 重置状态
}, [isSupported]);

// cleanup effect — 同样始终调用 cancel(),并清理 resumeIntervalRef
useEffect(() => {
  return () => {
    if (isSupported) {
      window.speechSynthesis.cancel();
    }
    if (resumeIntervalRef.current) {
      clearInterval(resumeIntervalRef.current);
      resumeIntervalRef.current = null;
    }
  };
}, [isSupported]);

状态: 已修复


2.2 P0 — IoT 子会话 nil pointer dereference PANIC

文件: backend/ai-core/internal/subsession/iot_provider.go

根因:

  1. iot_provider.go:116: persona.NewLoader("") 传入空字符串
  2. persona/loader.go:24-27: os.ReadDir("") 失败,返回 nil, error
  3. iot_provider.go:118: 仅 log.Printf 错误,未检查 loader 是否为 nil
  4. iot_provider.go:120: loader.Get("cyrene") 对 nil 解引用 → PANIC

崩溃日志:

[iot-provider] 加载人格配置失败: 读取人格目录失败: open : no such file or directory
[subsession] dispatch goroutine panic 恢复 (type=iot): runtime error: invalid memory address or nil pointer dereference

修复: 添加 loader != nil 守卫,优雅降级使用默认值:

// 加载人格配置
trueName := "昔涟"
loader, err := persona.NewLoader("")
if err != nil {
    log.Printf("[iot-provider] 加载人格配置失败: %v", err)
}
if loader != nil {
    if personaConfig, err := loader.Get("cyrene"); err == nil && personaConfig != nil {
        trueName = personaConfig.Identity.TrueName
    }
}

编译与部署: 重新编译 ai-core (go build)kill 旧进程 (PID 12870),启动新进程 (PID 22942)。

状态: 已修复并验证


2.3 测试工具 — CDP 脚本按钮匹配错误

文件: debug/cache/test_cdp_e2e_v4.py

问题: 测试脚本查找 btn.textContent.includes('登录') 的按钮,但页面上有两个包含"登录"的按钮:

  • 模式切换按钮 "登录" → 调用 switchMode('login')(不触发表单提交)
  • 真正的提交按钮 "进入昔涟的世界 ♪" → 调用 handleLogin()

修复: 改为匹配提交按钮的特征文字 "进入""昔涟"

状态: 已修复并验证


3. 验证结果汇总

3.1 CDP E2E v4 测试 (14/14 全部通过)

# 检查项 状态
1 页面无 JS 异常
2 无硬编码 test-token-cyrene
3 找到用户名输入框
4 找到密码输入框
5 找到登录按钮 ("进入昔涟的世界 ♪")
6 无认证错误 (API 调用 8 次)
7 localStorage 有 token
8 侧边栏或聊天区域存在
9 会话列表
10 创建新会话
11 获取消息历史
12 记忆列表
13 WebSocket 连接
14 收到 IoT 响应

3.2 后端 API 深度验证

API 方法 状态 备注
/api/v1/health GET 200 5 服务全部健康
/api/v1/auth/login POST 200 返回 token, user_id, expires
/api/v1/auth/refresh POST 200 返回新 token
/api/v1/sessions?user_id=admin GET 200 {"sessions": [...]}
/api/v1/sessions POST 201 创建会话成功
/api/v1/sessions/{id}/messages GET 200 历史消息正常
/api/v1/memory GET 200 记忆列表正常
/api/v1/memory/search?query=IoT GET ⚠️ 400 参数格式问题(非阻塞)
/ws/chat WS 101 消息流正常

3.3 WebSocket + IoT 流验证

  • WebSocket 连接: 正常建立
  • 设备状态广播: 每 10 秒推送 8 个设备状态
  • IoT 查询消息流: 完整流程:device_updatestream_chunkresponse
  • IoT 子会话: 不再 PANIC,优雅降级
  • 后台思考: post_chat 触发正常

3.4 服务运行时状态

服务 端口 PID 状态
gateway 8080 12874
ai-core 8081 22942 (已更新)
memory-service 8091 12864
tool-engine 8092 12868
iot-debug-service 8083 12866
vite preview 5199 1266
chromium CDP 9225 4741

4. 已知遗留问题

4.1 低优先级 — Memory Search 400

GET /api/v1/memory/search?query=IoT 返回 400。需要在 memory_handler.go 中确认查询参数名是否正确(可能是 q 而非 query)。

4.2 低优先级 — MessageBubble.tsx setInterval 无清理

MessageBubble.tsx:105-110AIMessageActionscheckEnd interval 没有在组件卸载时清理,可能导致内存泄漏。

4.3 低优先级 — IoT 子会话未匹配到"列出所有设备"

iot_provider.gomatchIotOperation() 函数未匹配 "列出所有IoT设备" 关键词。该消息被降级到 General 子会话处理,虽不影响功能但无法触发 IoT 专用响应。

4.4 信息 — 登录响应无 refresh_token 字段

后端 auth_handler.go:208 登录响应仅返回 token, user_id, expires,前端 client.ts:80 getRefreshToken() 读取 localStorage.getItem('refresh_token') 始终为 null。


5. 修改文件清单

文件 变更类型 描述
frontend/web/src/hooks/useSpeechSynthesis.ts 🐛 修复 P0: cancel() 守卫条件改为 isSupported
backend/ai-core/internal/subsession/iot_provider.go 🐛 修复 P0: 添加 loader nil 检查,防止 PANIC
backend/ai-core/cmd/ai-core 🔄 重新编译 包含上述修复
debug/cache/test_cdp_e2e_v4.py 🐛 修复 按钮匹配逻辑改为匹配提交按钮
debug/cache/test_cdp_token_investigation.py 新增 test-token-cyrene 来源诊断脚本
debug/cache/test_cdp_e2e_v3.py 新增 E2E v3 测试(发现 API 格式差异)

6. 结论

Round 2 深度调试成功完成。发现了 2 个 P0 级别缺陷useSpeechSynthesis cancel 守卫缺失、IoT 子会话 nil pointer PANIC)和 1 个测试工具缺陷(按钮匹配错误),全部已修复并验证通过。系统各组件(gateway、ai-core、memory-service、tool-engine、iot-debug-service)协同工作正常,WebSocket 消息流、IoT 设备广播、记忆存储链路完整。