diff --git a/cron/jobs.json b/cron/jobs.json index 0688ab6..2ee09d1 100644 --- a/cron/jobs.json +++ b/cron/jobs.json @@ -17,7 +17,7 @@ "wakeMode": "now", "payload": { "kind": "agentTurn", - "message": "执行备份脚本:/home/starmen1265/work/backup/backup_openclaw.sh" + "message": "执行备份脚本:/home/starmen1265/.openclaw/cron/scripts/backup_openclaw.sh" }, "delivery": { "mode": "announce" @@ -46,7 +46,7 @@ "wakeMode": "now", "payload": { "kind": "agentTurn", - "message": "执行每日 capability-evolver 任务:\n\n1. 执行脚本:bash ~/work/backup/evolver-run.sh\n2. 读取输出:cat ~/work/backup/evolver-output.txt\n3. 将完整的修改内容(包含 Git 备份信息和本次变更)通过 feishu 发送给蒋先生\n\n如果本次无任何变更,回复 '今日无变更' 即可。", + "message": "执行每日 capability-evolver 任务:\n\n1. 执行脚本:bash ~/.openclaw/cron/scripts/evolver-run.sh\n2. 读取输出:cat ~/work/backup/evolver-output.txt\n3. 将完整的修改内容(包含 Git 备份信息和本次变更)通过 feishu 发送给蒋先生\n\n如果本次无任何变更,回复 '今日无变更' 即可。", "model": "g47f", "thinking": "off", "timeoutSeconds": 300 @@ -80,7 +80,7 @@ "wakeMode": "now", "payload": { "kind": "systemEvent", - "text": "执行知识库同步:bash ~/work/backup/zq-sync.sh" + "text": "执行知识库同步:bash ~/.openclaw/cron/scripts/zq-sync.sh" }, "state": { "nextRunAtMs": 1772823600000, @@ -106,7 +106,7 @@ "wakeMode": "now", "payload": { "kind": "systemEvent", - "text": "执行 OpenClaw 备份:bash ~/work/backup/git-auto-sync.sh" + "text": "执行 OpenClaw 备份:bash ~/.openclaw/cron/scripts/git-auto-sync.sh" }, "state": { "nextRunAtMs": 1772823600000, @@ -115,6 +115,33 @@ "lastDurationMs": 9616, "consecutiveErrors": 0 } + }, + { + "id": "3245fca7-045b-4977-8fab-020f6df2e453", + "agentId": "haonan", + "name": "ai-conversation-logger", + "description": "每天早上7点整理与各个 AI 代理的对话记录", + "enabled": true, + "createdAtMs": 1772776369645, + "updatedAtMs": 1772777284202, + "schedule": { + "kind": "cron", + "expr": "0 7 * * *" + }, + "sessionTarget": "isolated", + "wakeMode": "now", + "payload": { + "kind": "agentTurn", + "message": "请执行 ~/.openclaw/cron/scripts/ai-conversation-logger.sh 脚本来整理昨天的 AI 对话记录。完成后汇报整理结果。", + "timeoutSeconds": 300 + }, + "delivery": { + "mode": "announce", + "channel": "last" + }, + "state": { + "nextRunAtMs": 1772838000000 + } } ] } \ No newline at end of file diff --git a/cron/scripts/ai-conversation-logger.sh b/cron/scripts/ai-conversation-logger.sh new file mode 100755 index 0000000..a264599 --- /dev/null +++ b/cron/scripts/ai-conversation-logger.sh @@ -0,0 +1,212 @@ +#!/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" +YESTERDAY=$(date -d "yesterday" +%Y-%m-%d) +CURRENT_MONTH=$(date -d "yesterday" +%Y-%m) + +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 "推送完成" diff --git a/cron/scripts/backup_openclaw.sh b/cron/scripts/backup_openclaw.sh new file mode 100755 index 0000000..012ffa4 --- /dev/null +++ b/cron/scripts/backup_openclaw.sh @@ -0,0 +1,17 @@ +#!/bin/bash +# 备份 openclaw.json +BACKUP_DIR="/home/starmen1265/work/backup" +SOURCE_FILE="/home/starmen1265/.openclaw/openclaw.json" + +# 创建备份目录(如果不存在) +mkdir -p "$BACKUP_DIR" + +# 备份文件,文件名包含日期时间 +TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) +cp "$SOURCE_FILE" "$BACKUP_DIR/openclaw.json.$TIMESTAMP" + +# 只保留最近30个备份 +cd "$BACKUP_DIR" +ls -t openclaw.json.* | tail -n +31 | xargs -r rm + +echo "Backup completed: openclaw.json.$TIMESTAMP" diff --git a/cron/scripts/evolver-run.sh b/cron/scripts/evolver-run.sh new file mode 100755 index 0000000..61f4691 --- /dev/null +++ b/cron/scripts/evolver-run.sh @@ -0,0 +1,75 @@ +#!/bin/bash +# Capability Evolver 定期运行脚本 +# 策略:使用 repair-only 模式,仅修复问题不做大改动 + +EVOLVER_DIR="$HOME/.openclaw/skills/capability-evolver" +OUTPUT_FILE="$HOME/work/backup/evolver-output.txt" +OPENCLAW_DIR="$HOME/.openclaw" + +echo "=== Capability Evolver 运行开始 ===" > "$OUTPUT_FILE" +echo "时间: $(date)" >> "$OUTPUT_FILE" +echo "" >> "$OUTPUT_FILE" + +# 切换到 openclaw 目录 +cd "$OPENCLAW_DIR" + +# 推送到远端(如果有远端) +if git remote get-url origin &>/dev/null; then + echo "=== 推送到远端 ===" >> "$OUTPUT_FILE" + git push origin main >> "$OUTPUT_FILE" 2>&1 + echo "推送完成" >> "$OUTPUT_FILE" +else + echo "无远端配置,跳过推送" >> "$OUTPUT_FILE" +fi + +echo "" >> "$OUTPUT_FILE" + +# 检查是否有未提交的更改 +if [[ -n $(git status --porcelain) ]]; then + echo "发现有未提交的更改,先提交..." >> "$OUTPUT_FILE" + git add -A + git commit -m "Auto backup before evolver run - $(date)" 2>/dev/null +fi + +# 创建备份分支 +BACKUP_BRANCH="backup/evolver-$(date +%Y%m%d-%H%M%S)" +echo "创建备份分支: $BACKUP_BRANCH" >> "$OUTPUT_FILE" +git branch -D "$BACKUP_BRANCH" 2>/dev/null || true +git branch "$BACKUP_BRANCH" 2>/dev/null + +# 推送备份分支 +if git remote get-url origin &>/dev/null; then + git push origin "$BACKUP_BRANCH" >> "$OUTPUT_FILE" 2>&1 || echo "备份分支推送失败" >> "$OUTPUT_FILE" +fi + +echo "" >> "$OUTPUT_FILE" +echo "=== Git 备份完成 ===" >> "$OUTPUT_FILE" +echo "" >> "$OUTPUT_FILE" + +# 运行 evolver - 使用 repair-only 策略确保安全 +cd "$EVOLVER_DIR" +export EVOLVE_STRATEGY="repair-only" +echo "=== 运行 Capability Evolver (repair-only 模式) ===" >> "$OUTPUT_FILE" +node index.js run >> "$OUTPUT_FILE" 2>&1 +EXIT_CODE=$? + +echo "" >> "$OUTPUT_FILE" +echo "=== Evolver 运行完成 (exit code: $EXIT_CODE) ===" >> "$OUTPUT_FILE" + +# 检查变更 +cd "$OPENCLAW_DIR" +echo "" >> "$OUTPUT_FILE" +echo "=== 本次变更 ===" >> "$OUTPUT_FILE" +git status --short >> "$OUTPUT_FILE" + +# 如果有变更,推送 +if [[ -n $(git status --porcelain) ]]; then + echo "" >> "$OUTPUT_FILE" + echo "=== 推送变更到远端 ===" >> "$OUTPUT_FILE" + git add -A + git commit -m "Evolver auto evolution - $(date)" 2>/dev/null || true + git push origin main >> "$OUTPUT_FILE" 2>&1 || echo "推送失败" >> "$OUTPUT_FILE" +fi + +echo "" >> "$OUTPUT_FILE" +echo "=== 输出完成 ===" >> "$OUTPUT_FILE" diff --git a/cron/scripts/git-auto-sync.sh b/cron/scripts/git-auto-sync.sh new file mode 100755 index 0000000..a9b7e4c --- /dev/null +++ b/cron/scripts/git-auto-sync.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +# 自动检查并提交 git 修改 + +cd ~/.openclaw +git add -A +if ! git diff --cached --quiet; then + git commit -m "自动同步: $(date '+%Y-%m-%d %H:%M')" + git push origin main +fi + +cd ~/.openclaw/extensions +git add -A +if ! git diff --cached --quiet; then + git commit -m "自动同步: $(date '+%Y-%m-%d %H:%M')" + git push origin main +fi diff --git a/cron/scripts/zq-sync.sh b/cron/scripts/zq-sync.sh new file mode 100755 index 0000000..709f6f4 --- /dev/null +++ b/cron/scripts/zq-sync.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# 知识库同步和备份脚本 + +ZQ_DIR="$HOME/work/zq" +LOG_FILE="$HOME/work/zq-sync.log" +DATE=$(date '+%Y-%m-%d %H:%M:%S') + +log() { + echo "[$DATE] $1" >> "$LOG_FILE" +} + +log "========== 开始同步 ==========" + +cd "$ZQ_DIR" || exit 1 + +# 1. 先拉取远程更新 +log "拉取远程更新..." +git pull origin main --rebase >> "$LOG_FILE" 2>&1 + +# 2. 从 NAS WebDAV 拉取最新内容(排除 .git 目录) +log "从 NAS 拉取更新..." +rclone sync "nas-webdav:/home/Drive/工作区/张青/工作区/" "$ZQ_DIR/" \ + --exclude ".git/" \ + --exclude ".gitignore" \ + --log-level INFO >> "$LOG_FILE" 2>&1 + +# 3. 检查是否有变更 +CHANGES=$(git status --porcelain) + +if [ -n "$CHANGES" ]; then + log "检测到变更,提交到 Git..." + git add -A + git commit -m "自动备份: $DATE" >> "$LOG_FILE" 2>&1 + + # 4. 推送到 Gitea + log "推送到 Gitea..." + git push origin main >> "$LOG_FILE" 2>&1 + log "备份完成!" +else + log "没有变更,跳过提交。" +fi + +log "========== 同步完成 =========="