Skill: liuxiaolong1988/lingxi-memory (version 1.1.15)

--- name: Lingxi-MindVault 灵曦记忆系统 description: 自动记忆提炼 & 写入飞书知识库。定时扫描 OpenClaw 会话文件，自动提炼有价值的记忆，写入飞书多维表格/云文档知识库。 author: liuxiaolong1988 license: MIT tags: memory, feishu, lark, automation, openclaw required_env: - FEISHU_USER_OPEN_ID - L2_APP_TOKEN - L2_TABLE_ID - L3_DOC_ID - L4_DOC_ID optional_env: - SESSIONS_DIR - NOTIFY_TARGET requires: - openclaw - python3 - bash --- # Lingxi-MindVault - 灵曦记忆系统：自动记忆提炼 & 写入飞书知识库 ## ⚠️ 风险提示 本技能的正常工作需要： 1. **读取本地 OpenClaw 会话文件**（包含你的聊天记录）→ 这是功能必需，因为要提炼记忆内容 2. **将会话内容传给 AI agent 进行提炼** → AI 需要分析内容才能提炼 3. **将提炼结果写入你** **自己配置的** **飞书知识库** → 结果存储在你自己的飞书空间 如果你不希望会话内容被 AI 处理，或者不希望数据存储到飞书，请不要安装。 ## 描述 灵曦自动记忆系统，定时扫描 OpenClaw 会话文件，自动提炼有价值的记忆，写入飞书多维表格/云文档知识库。 **v5 新架构**：一次完成，省去队列，bash 调用 agent，agent 直接提炼 + 写入 + 通知，更简洁更实时！ ## 功能特点 - ✅ 稳定排除 heartbeat 会话（当前 + 历史自动标记） - ✅ 支持 `.jsonl` / `.jsonl.reset.*` / `.jsonl.deleted.*` 所有会话格式 - ✅ 自动过滤空会话/小会话（用户消息数 < 1 或内容长度 < 20） - ✅ 完整的异常处理和通知，每个阶段都有错误通知 - ✅ 超时支持 300 秒（5分钟），适应大会话 - ✅ 开源友好，**所有配置通过环境变量指定**，无硬编码敏感信息 - ✅ 启动检查，如果缺少必需环境变量会立即报错，不会静默使用错误配置 - ✅ 原子进程锁，防止重复运行 - ✅ 过期锁自动清理，不会死锁 ## 架构流程图 ```plaintext 【阶段 1：定时触发】 │ ├─▶ crontab │ ├─ 白天（8:00-22:00）：每 1 分钟 │ └─ 夜间（22:00-次日8:00）：每 5 分钟 │ 【阶段 2：会话检查】 │ ├─▶ session_check.sh │ │ │ ├─ 1. 获取进程锁（/tmp/session_check.lock） │ │ │ ├─ 2. 读取 sessions.json │ │ │ ├─ 3. 获取活跃会话 │ │ ├─ 只保留最近 24 小时内更新的 │ │ └─ 排除 key 为 "agent:main:main" 的 heartbeat 会话（活跃列表层面排除） │ │ │ ├─ 4. 遍历所有会话文件 │ │ ├─ .jsonl │ │ ├─ .jsonl.reset.* │ │ └─ .jsonl.deleted.* │ │ │ ├─ 5. 对每个会话： │ │ │ │ │ ├─ a. 文件存在？ │ │ │ └─ 否 → 下一个 │ │ │ │ │ ├─ b. 已处理过？（/tmp/processed_sessions.tmp） │ │ │ └─ 是 → 下一个 │ │ │ │ │ ├─ c. 是 heartbeat 会话？ │ │ │ ├─ 判断逻辑： │ │ │ │ 1. 从 sessions.json 读取 agent:main:main 的 sessionFile │ │ │ │ 2. 从 sessionFile 中提取 session_id │ │ │ │ 3. 如果当前文件的 session_id == heartbeat 会话的 session_id → 跳过 │ │ │ └─ 是 → 跳过，下一个 │ │ │ └─ 否 → 继续 │ │ │ │ │ ├─ d. 是活跃会话？ │ │ │ └─ 是 → 下一个 │ │ │ │ │ ├─ e. 已提炼过？（memory/.extracted_sessions） │ │ │ └─ 是 → 下一个 │ │ │ │ │ └─ f. 发现未提炼会话！ │ │ │ │ │ ├─ 发送通知："发现未提炼会话: <session_id>，正在处理..." │ │ │ │ │ ├─ 调用 do_extract_and_write.sh <会话文件路径> │ │ │ │ │ └─ 检查结果： │ │ ├─ 成功（退出码 0）→ 标记为已提炼 │ │ └─ 失败（退出码非 0）→ 不标记，下次重试 │ │ │ └─ 6. 释放进程锁 │ 【阶段 3：AI 提炼 + 写入（一次完成）】 │ ├─▶ do_extract_and_write.sh │ │ │ ├─ 1. 完整读取会话文件（无行数限制） │ │ │ ├─ 2. 统计用户消息数和内容长度 │ │ ├─ 支持 .message.role │ │ └─ 支持 .message.content[0].text（数组和字符串两种格式） │ │ │ ├─ 3. 过滤条件检查 │ │ ├─ 用户消息数 ≥ 1 │ │ └─ 内容长度 ≥ 20 │ │ │ ├─ 4. 过滤通过？ │ │ ├─ 否 → 发送通知（CLI）→ 标记为已提炼 → 返回 0 │ │ └─ 是 → 继续 │ │ │ ├─ 5. 准备 AI 提炼 prompt（占位符替换，解决多行引号问题） │ │ │ ├─ 6. 调用 openclaw agent 进行 AI 提炼（后台运行，最多等待 300 秒 = 5 分钟） │ │ │ ├─ 7. 检查 AI 调用结果： │ │ ├─ a. 超时？（> 300 秒） │ │ │ └─ 是 → 发送通知（CLI）→ "❌ 会话 <SESSION_ID> 提炼失败：AI调用超时（300秒）" → 退出 1 │ │ ├─ b. 退出码非 0？ │ │ │ └─ 是 → 发送通知（CLI）→ "❌ 会话 <SESSION_ID> 提炼失败：AI调用失败（退出码：<code>）" → 退出 1 │ │ ├─ c. 输出为空？ │ │ │ └─ 是 → 发送通知（CLI）→ "❌ 会话 <SESSION_ID> 提炼失败：AI返回为空" → 退出 2 │ │ ├─ d. 输出包含 "DONE" → AI 已完成所有写入 → 标记为已提炼 → 退出 0 │ │ ├─ 不包含 "DONE" 但包含错误 → 发送通知（CLI）→ "❌ 会话 <SESSION_ID> 提炼失败：AI返回错误信息" → 退出 1 │ │ └─ 不包含 "DONE" 也不包含错误 → 发送通知（CLI）→ "❌ 会话 <SESSION_ID> 提炼失败：AI 没有完成写入" → 退出 3 │ │ │ └─ 完成，返回 0 │ 【阶段 4：完成】 │ └─▶ 结束，agent 已经完成所有提炼 + 写入 + 通知 ``` ## 文件结构 ``` skill/lingxi-memory/ ├── SKILL.md # 这个文件 ├── session_check.sh # 会话检查脚本 └── do_extract_and_write.sh # AI 提炼 + 写入脚本 ``` ## 环境变量配置（必填） **你必须在 `workspace/.env` 文件中配置以下环境变量**，否则脚本无法运行： ```bash # 你的飞书 open_id（必填，用于接收通知） export FEISHU_USER_OPEN_ID="ou_xxxxxxxxxxxxxxxxxx" # L2 任务看板：飞书多维表格 app_token（必填） export L2_APP_TOKEN="your_l2_app_token" # L2 任务看板：数据表 ID（必填） export L2_TABLE_ID="your_l2_table_id" # L3 项目日志：飞书云文档 doc_id（必填） export L3_DOC_ID="your_l3_doc_id" # L4 知识沉淀：飞书云文档 doc_id（必填） export L4_DOC_ID="your_l4_doc_id" # 可选配置：OpenClaw 会话目录（默认：/root/.openclaw/agents/main/sessions） # export SESSIONS_DIR="/path/to/your/sessions" # 可选配置：通知目标（默认：user:$FEISHU_USER_OPEN_ID） # export NOTIFY_TARGET="user:ou_xxxxxxxxxxxxxxxxxx" ``` **重要安全提示**： - 不要使用默认提供的示例 ID，那是作者的飞书资源 - 必须替换为你自己的飞书多维表格和文档 ID - 如果不配置，脚本会启动失败并提示你缺少环境变量，这是正常的安全设计 ## 通知格式 | 场景 | 通知内容 | 发起方 | |------|---------|--------| | **发现未提炼会话** | `发现未提炼会话: <session_id>，正在处理...` | session_check.sh (bash) | | **会话文件不存在** | `❌ 会话 <SESSION_ID> 提炼失败：文件不存在` | do_extract_and_write.sh (bash) | | **过滤跳过** | `ℹ️ 会话 <SESSION_ID> 过滤跳过：<原因>` | do_extract_and_write.sh (bash) | | **AI 调用超时** | `❌ 会话 <SESSION_ID> 提炼失败：AI调用超时（300秒）` | do_extract_and_write.sh (bash) | | **AI 调用失败** | `❌ 会话 <SESSION_ID> 提炼失败：AI调用失败（退出码：<code>）` | do_extract_and_write.sh (bash) | | **AI 返回为空** | `❌ 会话 <SESSION_ID> 提炼失败：AI返回为空` | do_extract_and_write.sh (bash) | | **AI 返回错误** | `❌ 会话 <SESSION_ID> 提炼失败：AI返回错误信息` | do_extract_and_write.sh (bash) | | **AI 没有完成写入** | `❌ 会话 <SESSION_ID> 提炼失败：AI 没有完成写入` | do_extract_and_write.sh (bash) | | **提炼成功（有内容）** | `## 提炼结果\n\n- **L2 任务**: X 条\n- **L3 项目**: X 条\n- **L4 知识**: X 条` | agent (AI) | | **提炼成功（无内容）** | `ℹ️ 会话 <SESSION_ID> 提炼完成，无内容需要写入` | agent (AI) | ## 安装部署 ```bash # 1. 克隆/复制脚本到你的 workspace/scripts 目录 cp session_check.sh /root/.openclaw/workspace/scripts/ cp do_extract_and_write.sh /root/.openclaw/workspace/scripts/ # 2. 添加执行权限 chmod +x /root/.openclaw/workspace/scripts/*.sh # 3. 创建已提炼记录文件 mkdir -p /root/.openclaw/workspace/memory touch /root/.openclaw/workspace/memory/.extracted_sessions # 4. 配置 crontab # 编辑 crontab: crontab -e # 添加以下内容： # 白天（8:00-22:00）每 1 分钟运行一次 # */1 8-22 * * * /root/.openclaw/workspace/scripts/session_check.sh >> /tmp/session_check.log 2>&1 # 夜间（22:00-次日8:00）每 5 分钟运行一次 # */5 22-23 * * * /root/.openclaw/workspace/scripts/session_check.sh >> /tmp/session_check.log 2>&1 # */5 0-7 * * * /root/.openclaw/workspace/scripts/session_check.sh >> /tmp/session_check.log 2>&1 ``` ## 依赖 - OpenClaw >= 2026.3.13 - python3 >= 3.8 - bash >= 4.0 - **需要配合 OpenClaw 飞书官方插件使用** - **需要开通飞书相关用户身份读写权限，完成用户 OAuth 授权后才能正常使用** ## 配置说明 使用前必须完成： 1. 在 OpenClaw 中启用飞书插件，并完成用户 OAuth 授权（获得飞书 API 访问权限） 2. 在 `workspace/.env` 文件中配置以下环境变量： ```bash # 你的飞书 open_id（必填，用于接收通知） export FEISHU_USER_OPEN_ID="ou_xxxxxxxxxxxxxxxxxx" # L2 任务看板：飞书多维表格 app_token export L2_APP_TOKEN="your_l2_app_token" # L2 任务看板：数据表 ID export L2_TABLE_ID="your_l2_table_id" # L3 项目日志：飞书云文档 doc_id export L3_DOC_ID="your_l3_doc_id" # L4 知识沉淀：飞书云文档 doc_id export L4_DOC_ID="your_l4_doc_id" ``` 如果不配置环境变量，会使用默认配置，但默认配置只能在灵曦的记忆空间使用，其他人使用需要修改为自己的飞书资源。 ## 安全说明 ### 安全扫描结果 | 检查项 | 状态 | 说明 | |--------|------|------| | 命令注入 | ✅ 安全 | 所有变量都正确使用引号包裹，文件名使用 `basename` 提取，不会导致命令注入 | | 路径遍历 | ✅ 安全 | 使用 `basename` 正确提取 session_id，不会路径遍历 | | 硬编码密钥 | ✅ 已修复 | 所有飞书资源 ID 都通过环境变量配置，无硬编码 | | 必需环境变量 | ✅ 已声明 | 在 `skill.json` 中完整声明了所有必需环境变量 | | 进程锁 | ✅ 安全 | 使用 `mkdir` 原子操作获取锁，支持过期锁（超过 10 分钟）自动清理，不会死锁 | | 临时文件 | ✅ 安全 | 使用 `$$` PID 隔离临时文件，用完自动删除，不会冲突 | | 错误处理 | ✅ 安全 | 每个步骤都有错误检查和通知，不会静默失败 | ### 风险声明 本技能的核心功能是**读取本地 OpenClaw 会话文件，将完整对话内容发送给 AI 代理进行提炼，然后写入你的飞书知识库**。这是本技能的设计初衷，如果你使用本技能，即同意： - 本 skill 需要读取你的本地 OpenClaw `$SESSIONS_DIR` 目录下**所有未提炼的会话文件**（包含你的对话历史），这是自动化提炼的必要设计 - 本 skill 会将完整对话内容发送给 OpenClaw agent（使用你配置的 AI 模型）进行内容提炼 - 本 skill 需要访问飞书 API，**必须完成飞书用户 OAuth 授权**才能使用 - 所有飞书资源 ID 都通过环境变量指定，**你必须自行配置你自己的**飞书多维表格和文档 - 飞书 API 凭证由 OpenClaw 飞书插件通过 OAuth 授权管理，本技能**不会接触或存储**你的飞书凭证 - 请确保你已经备份了重要数据，使用本技能产生的一切后果由使用者自行承担 ### 权限范围说明 - **本地读取**：读取 `$SESSIONS_DIR` 目录下的所有未提炼会话文件（默认：`/root/.openclaw/agents/main/sessions/`），这是本技能的核心功能 - **AI 调用**：调用 OpenClaw agent 进行内容提炼（由 OpenClaw 控制，使用你配置的 AI 模型） - **飞书配置传递**：将你配置的飞书资源 ID（app_token、table_id、doc_id）传递给 AI agent，用于正确调用飞书 API - ⚠️ **重要说明**：飞书 API 凭证（token/secret）由 OpenClaw 飞书插件通过 OAuth 授权管理，本技能**不会接触或存储**你的飞书凭证 - 本技能只传递飞书资源 ID，不传递任何凭证信息 - **飞书写入**：通过 OpenClaw 飞书插件调用飞书 API，将提炼结果写入**你自己配置**的多维表格和云文档 - **发送通知**：通过 OpenClaw CLI 发送通知到**你自己的**飞书账号 请确认你理解这些权限范围后再安装使用。 ## 版本历史 - **v1**: 初始版本，cron → check → extract → queue → heartbeat → write，队列架构 - **v2-v4**: 逐步修复 heartbeat 排除、参数解析等问题 - **v5**: 简化架构，移除队列，bash → agent → write 一次完成，更简洁更实时 ## 作者 灵曦 / Linxi-MindVault

path: README.md
content: # OpenClaw Skill: Lingxi Memory - 灵曦记忆系统

> 基于 OpenClaw + 飞书的 AI 记忆解决方案
> OpenClaw Agent 专用记忆系统

## 简介

Lingxi Memory（灵曦记忆系统）是一套专为 AI Agent 设计的记忆解决方案，结合飞书 IM 和飞书官方插件实现自动记忆功能。

### 核心特性

- 🤖 **自动提炼** - 会话结束自动触发 AI 提炼，无需手动操作
- 📊 **五层存储** - L1 原始会话 → L5 全局规则，结构化沉淀
- 🔄 **跨会话记忆** - 新会话可查看历史提炼报告
- 💾 **永久存储** - 飞书云端永久保存

### 技术栈

- [OpenClaw](https://github.com/openclaw/openclaw) - AI Agent 框架
- 飞书 IM - 消息推送
- 飞书多维表格 - 任务待办（L2）
- 飞书云文档 - 项目记忆（L3）、知识沉淀（L4）
- inotify-tools - 文件监听

---

## ⚠️ 数据安全说明

**在安装和使用本 Skill 前，请务必阅读以下内容：**

### 数据流向

```
本地 OpenClaw 会话文件 (.jsonl)
    ↓ 读取对话内容
AI 提炼（openclaw agent）
    ↓ 提取任务/项目/知识
飞书云端（L2/L3/L4）
    ↓ 推送提炼报告
飞书 IM（用户接收）
```

### 风险提示

1. **数据读取**：本 Skill 会读取本地 OpenClaw 会话文件，提取对话内容
2. **数据外传**：对话内容会被发送到飞书云端存储
3. **AI 处理**：对话内容会经过 OpenClaw AI 处理

### 适用场景

- ✅ 个人使用或内部团队使用
- ✅ 同意对话内容被存储到飞书的场景
- ❌ 对数据安全要求极高的场景
- ❌ 涉及敏感隐私信息的对话

---

## 系统架构

```
用户对话
    ↓
会话结束（.jsonl.reset 文件生成）
    ↓
session_watch.sh 监听检测
    ↓
do_extract.sh AI 提炼
    ↓
process_extract_queue.sh 队列处理
    ↓
┌─────────┬─────────┬─────────┐
│  L2     │  L3     │  L4     │
│ 任务待办│ 项目记忆│ 知识沉淀│
│多维表格 │ 云文档  │ 云文档  │
└─────────┴─────────┴─────────┘
    ↓
飞书 IM 推送提炼报告
```

## 快速开始

### 1. 安装依赖

```bash
# inotify-tools（文件监听）
apt install inotify-tools

# jq（JSON 处理）
apt install jq
```

### 2. 配置飞书

```bash
# 配置飞书 OAuth 授权
openclaw auth feishu
```

### 3. 配置环境变量

创建 `.env` 文件（不要提交到版本控制）：

```bash
# 飞书用户 ID
export FEISHU_USER_OPEN_ID="ou_xxxxxxxx"

# 飞书多维表格（L2-任务待办）
export L2_APP_TOKEN="xxxxxxxxxxxx"
export L2_TABLE_ID="tblxxxxxxxxxxxx"

# 飞书云文档
export L3_DOC_ID="xxxxxxxxxxxx"
export L4_DOC_ID="xxxxxxxxxxxx"

# OpenClaw 目录（可选）
export OPENCLAW_DIR="$HOME/.openclaw"
```

加载环境变量：

```bash
source .env
```

### 4. 启动监听

```bash
# 添加到 crontab
*/5 * * * * cd /path/to/lingxi-memory && source .env && bash scripts/session_watch.sh
```

详细安装说明见 [SKILL.md](./SKILL.md)

## 目录结构

```
lingxi-memory/
├── SKILL.md                    # Skill 定义
├── README.md                   # 项目说明（含数据安全说明）
├── LICENSE                     # MIT 协议
├── .gitignore                  # 忽略 .env 等敏感文件
└── scripts/                    # 核心脚本
    ├── session_watch.sh        # 会话监听
    ├── do_extract.sh           # AI 提炼
    └── process_extract_queue.sh # 队列处理
```

## 五层存储

| 层级 | 名称 | 存储位置 | 说明 |
|:----:|------|---------|------|
| L1 | 原始会话 | OpenClaw 本地 | 完整会话记录 |
| L2 | 任务待办 | 飞书多维表格 | 待办/阻塞事项 |
| L3 | 项目记忆 | 飞书云文档 | 决策/进展/配置 |
| L4 | 知识沉淀 | 飞书云文档 | 经验/SOP/踩坑 |
| L5 | 全局规则 | 本地文件 | 系统规则 |

## 故障排查

```bash
# 检查监听进程
ps aux | grep session_watch

# 查看日志
tail -50 ~/.openclaw/workspace/logs/session_extract.log

# 检查队列
cat /tmp/extract_queue.txt
```

## 开源协议

MIT License - See [LICENSE](./LICENSE)

---

⭐ 如果这个项目对你有帮助，欢迎 star！


path: scripts/session_check.sh
content: #!/bin/bash
# 会话检查脚本 - 最终完善版
# 功能: 定期检查未提炼的会话，通知用户并触发提炼
# 特点: 
# - 从sessions.json获取所有活跃会话ID（私聊+群聊）
# - 跳过所有活跃会话
# - 跳过已提炼会话
# - 进程锁防止多个实例同时运行
# - 会话去重（避免同一个会话被处理多次）
# - 防重复调用：新增 "正在处理中" 标记，解决频繁检查导致的重复调用问题

# 加载环境变量
WORKSPACE="/root/.openclaw/workspace"
if [ -f "$WORKSPACE/.env" ]; then
    source "$WORKSPACE/.env"
fi

# 配置
SESSIONS_DIR="/root/.openclaw/agents/main/sessions"
EXTRACTED_FILE="$WORKSPACE/memory/.extracted_sessions"
PROCESSING_FILE="$WORKSPACE/memory/.processing_sessions"
SCRIPT_DIR="$WORKSPACE/scripts"
DO_EXTRACT_SCRIPT="$SCRIPT_DIR/do_extract_and_write.sh"
SESSIONS_JSON="$SESSIONS_DIR/sessions.json"
LOCK_FILE="/tmp/session_check.lock"
PROCESSED_SESSIONS="/tmp/processed_sessions.tmp"

# 消息发送目标（从环境变量读取，默认发送给 FEISHU_USER_OPEN_ID）
NOTIFY_TARGET="${NOTIFY_TARGET:-user:${FEISHU_USER_OPEN_ID}}"

# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

# 进程锁函数（使用mkdir原子操作）
acquire_lock() {
    # 尝试创建锁目录（原子操作）
    if ! mkdir "$LOCK_FILE" 2>/dev/null; then
        # 锁已存在，检查是否过期（超过10分钟认为是死锁）
        local lock_mtime=$(stat -c %Y "$LOCK_FILE" 2>/dev/null || echo 0)
        local now=$(date +%s)
        local lock_age=$((now - lock_mtime))
        
        if [ "$lock_age" -gt 600 ]; then
            log "警告: 锁文件已过期（${lock_age}秒），强制删除"
            rm -rf "$LOCK_FILE"
            if ! mkdir "$LOCK_FILE" 2>/dev/null; then
                log "错误: 无法获取锁，另一个进程正在运行"
                return 1
            fi
        else
            log "跳过: 另一个进程正在运行（锁年龄: ${lock_age}秒）"
            return 1
        fi
    fi
    
    # 记录锁创建时间
    touch "$LOCK_FILE"
    log "已获取进程锁"
    return 0
}

# 释放锁函数
release_lock() {
    rm -rf "$LOCK_FILE"
    rm -f "$PROCESSED_SESSIONS" 2>/dev/null
    log "已释放进程锁"
}

# 确保脚本退出时释放锁
trap 'release_lock' EXIT

# 从sessions.json获取所有活跃会话ID（包括私聊和群聊，只保留最近24小时更新的，排除heartbeat会话）
get_active_sessions() {
    if [ ! -f "$SESSIONS_JSON" ]; then
        log "错误: sessions.json不存在: $SESSIONS_JSON"
        return 1
    fi
    
    python3 -c "
import json
import time
try:
    data = json.load(open('$SESSIONS_JSON'))
    now = time.time() * 1000  # 当前时间（毫秒）
    one_day = 24 * 60 * 60 * 1000  # 24小时（毫秒）
    # 遍历所有key，提取最近24小时更新的sessionId，排除heartbeat会话
    for key in data:
        if key == 'agent:main:main':
            continue  # 跳过heartbeat会话
        if 'sessionId' in data[key] and 'updatedAt' in data[key]:
            updated_at = data[key]['updatedAt']
            if now - updated_at < one_day:
                print(data[key]['sessionId'])
except Exception as e:
    print(f'Error: {e}')
" 2>/dev/null
}

# 发送通知（直接用 openclaw CLI 发送）
send_notification() {
    local message="$1"
    log "通知: $message"
    
    # 用 openclaw CLI 发送消息
    if command -v openclaw &>/dev/null; then
        log "使用 openclaw CLI 发送消息到: $NOTIFY_TARGET"
        openclaw message send --channel feishu --target "$NOTIFY_TARGET" --message "$message" 2>&1 || {
            log "警告: openclaw CLI 发送失败"
        }
    else
        log "警告: openclaw CLI 不可用，无法发送通知"
    fi
}

# ============== 主程序开始 ==============

# 获取进程锁
if ! acquire_lock; then
    exit 1
fi

# 确保目录和文件存在
mkdir -p "$(dirname "$EXTRACTED_FILE")"
touch "$EXTRACTED_FILE"
touch "$PROCESSING_FILE"
rm -f "$PROCESSED_SESSIONS" 2>/dev/null
touch "$PROCESSED_SESSIONS"

# 获取所有活跃会话ID
ACTIVE_SESSIONS=$(get_active_sessions)
if [ -z "$ACTIVE_SESSIONS" ]; then
    log "提示: 未检测到活跃会话"
else
    log "活跃会话ID:"
    echo "$ACTIVE_SESSIONS" | while read -r session_id; do
        [ -n "$session_id" ] && log "  - $session_id"
    done
fi

# 检查提炼脚本是否存在
if [ ! -f "$DO_EXTRACT_SCRIPT" ]; then
    log "错误: 提炼脚本不存在: $DO_EXTRACT_SCRIPT"
    exit 1
fi
chmod +x "$DO_EXTRACT_SCRIPT"

# 遍历所有会话文件（包括 .jsonl、.jsonl.reset.*、.jsonl.deleted.*）
for session_file in "$SESSIONS_DIR"/*.jsonl "$SESSIONS_DIR"/*.jsonl.*; do
    # 跳过不存在的文件（避免*.jsonl匹配失败）
    [ -e "$session_file" ] || continue
    
    # 获取session_id（去掉路径和所有后缀：.jsonl、.jsonl.reset.*、.jsonl.deleted.*等）
    session_id=$(basename "$session_file" | sed -E 's/\.jsonl(\..*)?$//')
    
    # 跳过空的session_id
    [ -z "$session_id" ] && continue
    
    # 去重：检查是否已经处理过这个session_id
    if grep -q "^${session_id}$" "$PROCESSED_SESSIONS" 2>/dev/null; then
        log "跳过重复会话（同一session_id多个文件）: $session_id -> $session_file"
        continue
    fi
    
    # 标记为已处理（同一个session_id只处理一次，不管有多少个后缀文件）
    echo "$session_id" >> "$PROCESSED_SESSIONS"
    
    # 获取当前 heartbeat 会话的 sessionFile，并跳过对应的文件
    HEARTBEAT_SESSION_FILE=$(python3 -c "
import json
import os
try:
    data = json.load(open('$SESSIONS_JSON'))
    if 'agent:main:main' in data:
        session_file = data['agent:main:main'].get('sessionFile', '')
        if session_file:
            # 从完整路径中提取 session_id
            filename = os.path.basename(session_file)
            # 去掉 .jsonl 后缀（如果文件是 /path/to/xxx.jsonl）
            if filename.endswith('.jsonl'):
                print(filename[:-6])
            else:
                # 如果已经是 session_id 格式（没有 .jsonl 后缀）
                print(filename)
        else:
            print('')
    else:
        print('')
except:
    print('')
" 2>/dev/null)
    
    # 跳过 heartbeat 会话（当前和历史）
    # 1. 跳过当前 heartbeat 会话
    if [ -n "$HEARTBEAT_SESSION_FILE" ] && [ "$session_id" = "$HEARTBEAT_SESSION_FILE" ]; then
        log "跳过 heartbeat 会话: $session_id"
        # 把当前 heartbeat 会话标记为已提炼，这样下次它被重置后就不会被处理
        if ! grep -q "^agent:main:session:${session_id}$" "$EXTRACTED_FILE" 2>/dev/null; then
            echo "agent:main:session:${session_id}" >> "$EXTRACTED_FILE"
            log "已将历史 heartbeat 会话标记为已提炼: $session_id"
        fi
        continue
    fi
    
    # 跳过所有活跃会话
    if echo "$ACTIVE_SESSIONS" | grep -q "^${session_id}$" 2>/dev/null; then
        log "跳过活跃会话: $session_id"
        continue
    fi
    
    # 检查是否已提炼过
    if grep -q "^agent:main:session:${session_id}$" "$EXTRACTED_FILE" 2>/dev/null; then
        log "跳过已提炼会话: $session_id"
        continue
    fi
    
    # 检查是否正在处理中（已经有提炼进程在运行）
    if grep -q "^agent:main:session:${session_id}$" "$PROCESSING_FILE" 2>/dev/null; then
        # 检查处理是否超时（超过30分钟认为是死锁，清理掉）
        # 读取行最后记录的时间戳，格式: agent:main:session:xxx timestamp
        # 如果文件中没有时间戳，默认600秒（10分钟）超时
        local processing_age=600
        if [ -n "$(grep "^agent:main:session:${session_id} [0-9]\+$" "$PROCESSING_FILE" 2>/dev/null)" ]; then
            local timestamp=$(grep "^agent:main:session:${session_id} [0-9]\+$" "$PROCESSING_FILE" | awk '{print $2}')
            local now=$(date +%s)
            processing_age=$((now - timestamp))
        fi
        if [ "$processing_age" -lt 1800 ]; then  # 30分钟超时
            log "跳过正在处理中的会话: $session_id (已处理 ${processing_age} 秒)"
            continue
        else
            log "警告: 会话处理超时（${processing_age} 秒），清理标记后重试"
            sed -i "/^agent:main:session:${session_id}/d" "$PROCESSING_FILE" 2>/dev/null
        fi
    fi
    
    # 发现未提炼会话！
    log "发现未提炼会话: $session_id"
    # 标记为正在处理中，防止其他检查进程重复处理
    echo "agent:main:session:${session_id} $(date +%s)" >> "$PROCESSING_FILE"
    
    # 发送通知（只在第一次发现时）
    send_notification "发现未提炼会话: $session_id，正在处理..."
    
    # ========== 真实调用 ==========
    # 后台运行提炼脚本，避免阻塞检查流程导致重复调用
    # 问题根源：AI提炼需要很长时间，如果同步调用/同进程等待，下一次检查会在提炼完成前触发，导致重复调用
    # 日志由cron统一记录到系统log，不在/tmp留存敏感的会话日志
    log "后台启动提炼脚本处理会话: $session_id"
    (
        # 执行提炼脚本
        "$DO_EXTRACT_SCRIPT" "$session_file"
        extract_exit_code=$?
        # 无论成功失败，都先从「正在处理」列表移除
        sed -i "/^agent:main:session:${session_id}/d" "$PROCESSING_FILE" 2>/dev/null
        # 提炼成功（含过滤跳过），标记到「已提炼」列表，下次直接跳过
        if [ $extract_exit_code -eq 0 ]; then
            echo "agent:main:session:${session_id}" >> "$EXTRACTED_FILE"
            log "会话处理完成: $session_id"
        else
            # 提炼失败，不标记到「已提炼」，下次检查会重试
            log "会话处理失败，下次重试: $session_id (退出码: $extract_exit_code)"
        fi
    ) &
    # 短暂等待，避免CPU争抢
    sleep 0.2
    # =============================

    # 注意：这里不等待提炼完成，标记工作交给后台进程
    # 通过「PROCESSING_FILE」保证同一个会话同一时间只会有一个提炼进程在运行
done

log "会话检查完成"
exit 0


path: skill.json
content: {
  "name": "Lingxi-MindVault - Auto Memory Extraction for OpenClaw",
  "description": "自动记忆提炼 & 写入飞书知识库。定时扫描 OpenClaw 会话文件，自动提炼有价值的记忆，写入飞书多维表格/云文档知识库。",
  "author": "liuxiaolong1988",
  "license": "MIT",
  "tags": ["memory", "feishu", "lark", "automation", "openclaw"],
  "requiredEnv": ["FEISHU_USER_OPEN_ID", "L2_APP_TOKEN", "L2_TABLE_ID", "L3_DOC_ID", "L4_DOC_ID"],
  "optionalEnv": ["SESSIONS_DIR", "NOTIFY_TARGET"],
  "requires": ["openclaw", "python3", "bash"]
}


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)