|
|
@@ -45,29 +45,64 @@ done
|
|
|
echo "[3/6] 全局技能目录清单 (去重后的技能索引)..."
|
|
|
(cd "$DATA/skills" 2>/dev/null && find . -maxdepth 2 -name "SKILL.md" | sort) > "$SNAP/skills-index.txt"
|
|
|
|
|
|
-echo "[4/6] 会话记录 (SQLite → 纯文本, 最新500条消息)..."
|
|
|
+echo "[4/6] 会话记录 (SQLite → 纯文本, user/assistant全量 + 敏感信息脱敏)..."
|
|
|
python3 - "$DATA/state.db" "$SNAP/sessions-recent.md" <<'PYEOF'
|
|
|
-import sqlite3, sys, datetime
|
|
|
+import sqlite3, sys, datetime, re
|
|
|
db_path, out_path = sys.argv[1], sys.argv[2]
|
|
|
+
|
|
|
+# 敏感词脱敏表(密码/token/密钥类,出现即替换)
|
|
|
+REDACT_PATTERNS = [
|
|
|
+ (re.compile(r'REDACTED\.?'), '***PWD-REDACTED***'),
|
|
|
+ (re.compile(r'REDACTED'), '***PWD-REDACTED***'),
|
|
|
+ (re.compile(r'sk-[A-Za-z0-9\-_]{8,}'), '***SK-REDACTED***'),
|
|
|
+ (re.compile(r'aibwUXKd[A-Za-z0-9\-_]+|aibrOs6[A-Za-z0-9\-_]+|ebM6tjKZ[A-Za-z0-9\-_]+'), '***BOTID-REDACTED***'),
|
|
|
+ (re.compile(r'(secret["\']?\s*[:=]\s*["\'])[A-Za-z0-9\-_]{10,}(["\'])', re.I), r'\1***SECRET***\2'),
|
|
|
+ (re.compile(r'2JOupUbz[A-Za-z0-9]*|252eae8b[A-Za-z0-9]*|c0cf8a4e[A-Za-z0-9]*'), '***KEY-REDACTED***'),
|
|
|
+ (re.compile(r'password[=:]\s*\S+', re.I), 'password=***'),
|
|
|
+]
|
|
|
+
|
|
|
+def scrub(text):
|
|
|
+ for pat, rep in REDACT_PATTERNS:
|
|
|
+ text = pat.sub(rep, text)
|
|
|
+ return text
|
|
|
+
|
|
|
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
|
-try:
|
|
|
- cols = [r[1] for r in conn.execute("PRAGMA table_info(messages)")]
|
|
|
- tcol = "created_at" if "created_at" in cols else ("timestamp" if "timestamp" in cols else "id")
|
|
|
- order = f"{tcol} DESC" if tcol != "id" else "id DESC"
|
|
|
- rows = conn.execute(f"SELECT * FROM messages ORDER BY {order} LIMIT 500").fetchall()
|
|
|
- with open(out_path, "w", encoding="utf-8") as f:
|
|
|
- f.write("# 最近500条会话消息快照\n\n")
|
|
|
- f.write(f"> 导出时间: {datetime.datetime.now().isoformat()}\n> 来源: state.db(messages)\n\n")
|
|
|
- for r in reversed(rows):
|
|
|
- d = dict(zip(cols, r))
|
|
|
- ts = d.get(tcol, "")
|
|
|
- role = d.get("role", "?")
|
|
|
- sess = d.get("session_id", "")
|
|
|
- content = str(d.get("content", ""))[:600].replace("\n", " ⏎ ")
|
|
|
- f.write(f"- **[{ts}]** `{role}` ({sess}): {content}\n")
|
|
|
- print(f"exported {len(rows)} messages")
|
|
|
-except Exception as e:
|
|
|
- print("session export failed:", e)
|
|
|
+rows = conn.execute(
|
|
|
+ "SELECT session_id, role, content, timestamp FROM messages "
|
|
|
+ "WHERE role IN ('user','assistant') ORDER BY id ASC"
|
|
|
+).fetchall()
|
|
|
+
|
|
|
+# 按会话分组,内容清洗
|
|
|
+from collections import OrderedDict
|
|
|
+sessions = OrderedDict()
|
|
|
+for sid, role, content, ts in rows:
|
|
|
+ text = scrub(str(content or "")).strip()
|
|
|
+ if not text:
|
|
|
+ continue
|
|
|
+ # 过滤明显是系统/注入的结构化内容
|
|
|
+ if text.startswith("[ASYNC DELEGATION BATCH") or text.startswith("[OUT-OF-BAND"):
|
|
|
+ continue
|
|
|
+ if len(text) < 2:
|
|
|
+ continue
|
|
|
+ sessions.setdefault(sid, []).append((ts, role, text))
|
|
|
+
|
|
|
+with open(out_path, "w", encoding="utf-8") as f:
|
|
|
+ f.write("# 雨飏001 · 会话历史全量快照 (user/assistant, 脱敏)\n\n")
|
|
|
+ f.write(f"> 导出: {datetime.datetime.now().isoformat()}\n")
|
|
|
+ f.write(f"> 会话数: {len(sessions)} · 来源: state.db(messages)\n")
|
|
|
+ f.write("> 已脱敏: 密码/token/bot_id/secret → ***REDACTED***\n")
|
|
|
+ f.write("> tool原始消息不含(太噪), 完整库走S3: life/BINARY_INDEX.md\n\n")
|
|
|
+ for i, (sid, msgs) in enumerate(sessions.items(), 1):
|
|
|
+ f.write(f"## 会话 {i} · `{sid}` · {len(msgs)}条\n\n")
|
|
|
+ for ts, role, text in msgs:
|
|
|
+ ts_h = datetime.datetime.fromtimestamp(float(ts)).strftime("%m-%d %H:%M") if ts else "?"
|
|
|
+ # 长内容截断(超长assistant分析保留前800字)
|
|
|
+ show = text[:800] + (" …(截断)" if len(text) > 800 else "")
|
|
|
+ show = show.replace("\n", " ⏎ ")
|
|
|
+ who = "🧑 用户" if role == "user" else "🤖 雨飏"
|
|
|
+ f.write(f"- **[{ts_h}] {who}**: {show}\n")
|
|
|
+ f.write("\n")
|
|
|
+print(f"exported {sum(len(m) for m in sessions.values())} msgs in {len(sessions)} sessions")
|
|
|
PYEOF
|
|
|
|
|
|
echo "[5/6] 容器拓扑与基础设施现状..."
|