#!/bin/bash # AI 对话记录定时任务 # 功能:每天凌晨整理与各个 AI 代理的对话记录 set -e OUTPUT_BASE="$HOME/work/zq/30.资源/AI会话历史" ZQ_DIR="$HOME/work/zq" OPENCLAW_DIR="$HOME/.openclaw" LOG_FILE="$HOME/work/backup/ai-conversation-logger.log" export YESTERDAY=$(date -d "yesterday" +%Y-%m-%d) export CURRENT_MONTH=$(date -d "yesterday" +%Y-%m) # 确保日志目录存在 mkdir -p "$(dirname "$LOG_FILE")" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" } log "=== AI 对话记录整理开始 ===" log "处理日期: $YESTERDAY" # 从 NAS 拉取最新内容 log "从 NAS 拉取最新内容..." cd "$ZQ_DIR" || { log "错误:无法进入 $ZQ_DIR"; exit 1; } rclone sync "nas-webdav:/home/Drive/工作区/张青/工作区/" "$ZQ_DIR/" \ --exclude ".git/" --exclude ".gitignore" \ --log-level INFO >> "$LOG_FILE" 2>&1 || log "警告:rclone 拉取失败" log "同步完成" mkdir -p "$OUTPUT_BASE" AGENTS=$(ls -1 "$OPENCLAW_DIR/agents/" 2>/dev/null | grep -v "^\." || true) if [[ -z "$AGENTS" ]]; then log "未找到任何代理目录" exit 0 fi log "发现代理: $AGENTS" # Python 脚本处理对话记录 python3 << 'PYEOF' import json import os import re from datetime import datetime from pathlib import Path OUTPUT_BASE = os.path.expanduser("~/work/zq/30.资源/AI会话历史") OPENCLAW_DIR = os.path.expanduser("~/.openclaw/agents") YESTERDAY = os.environ.get('YESTERDAY', '2026-03-05') CURRENT_MONTH = os.environ.get('CURRENT_MONTH', '2026-03') def clean_user_message(text): if not text: return "" if '[Feishu' in text: match = re.search(r'\[Feishu[^\]]+\]\s*[^:]+:\s*(.+)', text, re.DOTALL) if match: content = match.group(1) content = re.sub(r'\s*\[message_id:[^\]]+\]\s*$', '', content) return content.strip() if text.startswith('System:') and '[Feishu' not in text: return "" return text.strip() def extract_tags(messages): tags = set() all_text = ' '.join([m['content'] for m in messages]).lower() tag_rules = { 'AI/LLM': ['ai', 'llm', '模型', 'gpt', 'claude', '推理', 'token'], 'OpenClaw': ['openclaw', 'gateway', 'agent', '代理', '会话'], '知识库': ['知识库', 'kb', '归档', 'memory'], '编程开发': ['代码', 'code', '开发', 'git', 'python', 'bash'], '部署运维': ['部署', 'deploy', '服务器', 'docker', '配置'], '游戏开发': ['ue', 'unreal', 'unity', '游戏', '3d'], 'iOS/Apple': ['ios', 'mac', 'xcode', 'apple', 'swift'], '问题解决': ['报错', '错误', 'error', '怎么', '如何'], '技术探讨': ['探讨', '讨论', '原理', '架构', '研究'], '任务规划': ['计划', '规划', 'todo', '待办', '任务'], '定时任务': ['cron', '定时', '自动', '备份'], '飞书相关': ['飞书', 'feishu'], } for tag, keywords in tag_rules.items(): for kw in keywords: if kw in all_text: tags.add(tag) break return sorted(list(tags))[:5] if tags else ['对话记录'] def parse_jsonl(filepath): messages = [] with open(filepath, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line: continue try: entry = json.loads(line) except: continue if entry.get('type') != 'message': continue msg = entry.get('message', {}) role = msg.get('role', '') if role not in ['user', 'assistant']: continue content = msg.get('content', '') if isinstance(content, list): texts = [] for p in content: if isinstance(p, dict) and p.get('type') == 'text' and p.get('type') not in ['thinking', 'toolCall']: t = p.get('text', '') if t: texts.append(t) content = '\n\n'.join(texts) if role == 'user': content = clean_user_message(content) if content and content.strip(): messages.append({'role': role, 'content': content.strip(), 'timestamp': entry.get('timestamp', 0)}) return messages def gen_md(agent, date, messages): if not messages: return "", 0 messages.sort(key=lambda x: x.get('timestamp', 0)) seen, unique = set(), [] for m in messages: k = (m['role'], m['content'][:100]) if k not in seen: seen.add(k); unique.append(m) tags = extract_tags(unique) tags_str = ', '.join([f'"{t}"' for t in tags]) md = f"""--- agent: {agent} date: {date} generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} messages: {len(unique)} tags: [{tags_str}] --- # {agent} 对话记录 - {date} **标签**: {', '.join(tags)} 共 {len(unique)} 条消息 --- """ for m in unique: icon = "👤" if m['role'] == 'user' else "🤖" name = "用户" if m['role'] == 'user' else "助手" md += f"\n### {icon} {name}\n\n{m['content']}\n\n---\n" return md, len(unique) agents = [d for d in os.listdir(OPENCLAW_DIR) if not d.startswith('.')] for agent in agents: sdir = os.path.join(OPENCLAW_DIR, agent, "sessions") if not os.path.isdir(sdir): continue files = [] for f in os.listdir(sdir): if f.endswith('.jsonl') and not f.endswith('.lock') and '.deleted' not in f: fp = os.path.join(sdir, f) if datetime.fromtimestamp(os.stat(fp).st_mtime).strftime('%Y-%m-%d') == YESTERDAY: files.append(fp) if not files: continue msgs = [] for fp in files: msgs.extend(parse_jsonl(fp)) if not msgs: continue md, cnt = gen_md(agent, YESTERDAY, msgs) if md: odir = os.path.join(OUTPUT_BASE, agent, CURRENT_MONTH) os.makedirs(odir, exist_ok=True) ofile = os.path.join(odir, f"对话-{YESTERDAY}.md") with open(ofile, 'w', encoding='utf-8') as f: f.write(md) print(f"{agent}: {cnt} 条消息, 标签: {extract_tags(msgs)}") # 更新月度汇总 for agent in agents: mdir = os.path.join(OUTPUT_BASE, agent, CURRENT_MONTH) if not os.path.isdir(mdir): continue dfiles = list(Path(mdir).glob("对话-*.md")) if not dfiles: continue sfile = os.path.join(mdir, f"汇总-{CURRENT_MONTH}.md") smd = f"""--- agent: {agent} month: {CURRENT_MONTH} generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} days: {len(dfiles)} --- # {agent} 月度汇总 - {CURRENT_MONTH} ## 概览 - 对话天数:{len(dfiles)} 天 - 生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ## 每日对话索引 """ for df in sorted(dfiles): d = df.stem.replace("对话-", "") smd += f"- [{d}](./对话-{d}.md)\n" with open(sfile, 'w', encoding='utf-8') as f: f.write(smd) print(f"{agent}: 更新汇总") print("处理完成!") PYEOF log "=== AI 对话记录整理完成 ===" # 推送到 Git 和 NAS cd "$ZQ_DIR" CHANGES=$(git status --porcelain 2>/dev/null || true) if [[ -n "$CHANGES" ]]; then log "提交到 Git..." git add -A >> "$LOG_FILE" 2>&1 git commit -m "AI对话记录: $YESTERDAY" >> "$LOG_FILE" 2>&1 || true git push origin main >> "$LOG_FILE" 2>&1 || log "警告:Git push 失败" fi log "推送到 NAS..." rclone copy "$OUTPUT_BASE" "nas-webdav:/home/Drive/工作区/张青/工作区/30.资源/AI会话历史/" \ --log-level INFO >> "$LOG_FILE" 2>&1 || log "警告:rclone 推送失败" log "推送完成"