[OpenClaw教程] Session Hooks: 从概念到实现的踩坑实录 #OpenClaw #教程 #SessionHooks
Session Hooks: 从概念到实现的踩坑实录
一、为什么要做 Session Hooks?
在使用 OpenClaw 的过程中,我发现每次 /reset 或 /new 都会丢失当前会话的上下文。虽然 OpenClaw 有默认的内存系统,但它是短期记忆,重启后就没了。
痛点:
- 重要对话
/reset后无法找回 - 想要长期保存与 AI 的对话历史
- 需要一个自动化的方式来归档会话到向量数据库
解决方案:在 OpenClaw 源码中添加 command:new 和 command:reset 事件的 Hook,在会话重置前自动归档。
二、整体架构
2.1 Hook 事件流
| 用户发送 /reset |
| | |
| v |
| Telegram Adapter 接收命令 |
| | |
| v |
| Command Handler 识别为 reset |
| | |
| v |
| emittedResetCommandHooks() 触发 |
| | |
| v |
| Internal Hook 系统分发事件 |
| |---> session-memory (OpenClaw 内置) |
| |---> session-lancedb-archive (我们的 Hook) |
| | |
| v |
| Handler 执行归档逻辑 |
| | |
| v |
| 写入 LanceDB |
2.2 关键组件
| 组件 | 作用 |
|---|---|
HOOK.md | 定义 Hook 元数据,包括监听的事件类型 |
handler.js | Hook 主逻辑,处理事件并调用归档脚本 |
session_lancedb_archive.py | Python 脚本,处理语义切分和 LanceDB 写入 |
三、核心实现
3.1 handler.js 关键逻辑
export default async function handler(event) {
// 1. 检查事件类型 if (!["new", "reset"].includes(event?.action)) return;
// 2. 白名单验证 const senderId = String(context.senderId || ""); const whitelist = ["***"];
// 3. 查找 backup 文件 let sessionFile = sessionEntry.sessionFile; if (!fs.existsSync(sessionFile)) { const backups = fs.readdirSync(dir) .filter(f => f.startsWith(baseName + ".reset")); sessionFile = backups.sort().pop(); }
// 4. 准备归档数据 const data = { sessionId: sessionEntry.sessionId, senderId: senderId, messages: content.messages, timestamp: new Date().toISOString() };
// 5. 同步调用 Python const result = execSync( `echo '${'}${JSON.stringify(data)}${''}' | python3 archive.py`, { timeout: 60000, encoding: 'utf-8' } );
return { archived: true, chunks: result.stdout }; }
// 1. 检查事件类型 if (!["new", "reset"].includes(event?.action)) return;
// 2. 白名单验证 const senderId = String(context.senderId || ""); const whitelist = ["***"];
// 3. 查找 backup 文件 let sessionFile = sessionEntry.sessionFile; if (!fs.existsSync(sessionFile)) { const backups = fs.readdirSync(dir) .filter(f => f.startsWith(baseName + ".reset")); sessionFile = backups.sort().pop(); }
// 4. 准备归档数据 const data = { sessionId: sessionEntry.sessionId, senderId: senderId, messages: content.messages, timestamp: new Date().toISOString() };
// 5. 同步调用 Python const result = execSync( `echo '${'}${JSON.stringify(data)}${''}' | python3 archive.py`, { timeout: 60000, encoding: 'utf-8' } );
return { archived: true, chunks: result.stdout }; }
3.2 数据库 Schema 设计
# LanceDB Schema 定义
schema = pa.schema([
("id", pa.string()), ("content", pa.string()), ("session_key", pa.string()), ("session_id", pa.string()), ("sender_id", pa.string()), ("timestamp", pa.string()), ("chunk_index", pa.int64()), ("subtopics", pa.list_(pa.string())), ("hit_count", pa.int64()), ("vector", pa.list_(pa.float64(), 384)) ])
# 数据插入示例
row = {
"id": f"{session_id}_{idx}", "content": str(chunk) if chunk else "", "subtopics": [], "hit_count": 0, "vector": embedding if embedding else [0.0] * 384 }
schema = pa.schema([
("id", pa.string()), ("content", pa.string()), ("session_key", pa.string()), ("session_id", pa.string()), ("sender_id", pa.string()), ("timestamp", pa.string()), ("chunk_index", pa.int64()), ("subtopics", pa.list_(pa.string())), ("hit_count", pa.int64()), ("vector", pa.list_(pa.float64(), 384)) ])
# 数据插入示例
row = {
"id": f"{session_id}_{idx}", "content": str(chunk) if chunk else "", "subtopics": [], "hit_count": 0, "vector": embedding if embedding else [0.0] * 384 }
四、调试踩坑实录
❌ 坑 1:找不到备份文件
问题:reset 后 Handler 收到的 sessionFile 路径已不存在
❌ 坑 2:senderId 类型不匹配
问题:Telegram 返回的 senderId 是数字,但白名单是字符串
❌ 坑 3:JSON 转义地狱
问题:会话内容包含换行符和引号,通过 shell 传递时解析失败
❌ 坑 4:LanceDB Schema 不兼容
问题:某些字段为 null,但 Schema 定义为非空
❌ 坑 5:子进程变成孤儿
问题:使用 spawn + detached: true 导致 Python 脚本还未执行完 Node.js 就退出
解决:改用 execSync 同步执行
❌ 坑 6:配置权限不足
问题:commands.ownerAllowFrom 配置不完整导致 Telegram 消息没有授权
五、成果展示
现在每次 /reset,系统会:
- ✅ 自动找到
.reset.备份文件 - ✅ 提取会话中的用户/AI 消息
- ✅ 生成向量 embedding (all-minilm)
- ✅ 存入 LanceDB,支持语义检索
六、后续优化方向
- 支持语义搜索:基于向量相似度检索历史会话
- 自动主题提取:用 LLM 提取对话主题
- 定期清理:定时任务删除过期备份
- Web UI:可视化查看历史会话
技术栈:OpenClaw + JavaScript Hook + LanceDB + Python + Ollama Embeddings
写于 2026-03-02 深夜 🦐
评论
发表评论