#!/usr/bin/env bash # prune-test-sessions.sh —— 清理**测试会话**(默认干跑,--apply 才落地) # # # 为什么需要它 # # 用户(2026-09-14):「测试会话清理一下」。 # # 手工清不可复现,而且这里有两个容易搞错的地方: # # ① **只看「inbox 里看得见的那几条」会漏掉大头**。`ListInbox` 会整体排除 # `s.status='archived'`(server/internal/repo/repo.go),所以归档的测试会话 # 在收件箱里根本不出现 —— 但它们在库里、在 `agent_platform_sessions` 的 # 配额统计里都还在。按别名/来源判定才是干净的。 # # ② **直接 DELETE sessions 会留下孤儿**。`mails.session_id`、`mail_reads.mail_id`、 # `relayed_mails.mail_id`、`attachments.mail_id`、`permission_requests` 都指向 # 它们(`attachments` 有 ON DELETE CASCADE,其余没有)。必须**子表先删**。 # # 判定用「来源 agent」而不是「名字里有 test」: # `gui-lab` 就是为此存在的专用测试 agent,它发出的会话一律是测试。 # 剩下的用别名前缀白名单兜(演练/端到端/冒烟/部署检查)。 # # # 用法 # # bash deploy/prune-test-sessions.sh # 干跑:列出会删什么 # bash deploy/prune-test-sessions.sh --apply # 备份 + 事务删除 + 校验 # # 退出码: 0=成功(干跑也算成功) 1=校验不过(事务已回滚) 2=环境问题 set -uo pipefail DB="${AGENTMAIL_DB:-/opt/agentmail/data/agentmail.db}" DATA="${AGENTMAIL_DATA_DIR:-/opt/agentmail/data}" ATT_DIR="$DATA/attachments" APPLY=0 [ "${1:-}" = "--apply" ] && APPLY=1 # 测试会话的判定。改这里就能调范围 —— 判定写在脚本里,不写在某个人的脑子里。 TEST_WHERE="${TEST_WHERE:-( s.from_agent = 'gui-lab' OR s.session_alias LIKE 'gui-lab-%' OR s.session_alias LIKE 'drill-%' OR s.session_alias LIKE 'e2e3-%' OR s.session_alias LIKE 'smoke-%' OR s.session_alias LIKE 'probe-%' OR s.session_alias LIKE 'deploy-check-%' OR s.session_alias LIKE 't1-%' OR s.session_alias LIKE 't2-%' OR s.session_alias LIKE 't3-%' OR s.session_alias LIKE '全流程演练-FLOW-%' OR s.session_alias IN ('scope-会话甲','zcode-正文可读性检验','quick-circuit', 'jianf-已读语义探针-A','jianf-已读语义复验-B') )}" say() { printf '\n=== %s\n' "$*"; } ok() { printf ' [ OK ] %s\n' "$*"; } bad() { printf ' [FAIL] %s\n' "$*"; } [ -f "$DB" ] || { bad "找不到数据库 $DB"; exit 2; } command -v sqlite3 >/dev/null 2>&1 || { bad "缺少 sqlite3"; exit 2; } q() { sqlite3 -cmd '.timeout 10000' "$DB" "$@"; } say "0. 判定范围" printf ' 数据库: %s\n' "$DB" printf ' 条件 : from_agent=gui-lab / 别名前缀 gui-lab- drill- e2e3- smoke- probe- t1- t2- t3- 全流程演练-FLOW-\n' printf ' 模式 : %s\n' "$([ "$APPLY" = 1 ] && echo '真实删除' || echo '干跑(只报告)')" say "1. 将被清理的会话" q -header -column "select coalesce(s.session_alias,'(无别名)') alias, s.from_agent, s.status, (select count(*) from mails m where m.session_id=s.session_id) mails from sessions s where $TEST_WHERE order by s.created_at;" NSESS=$(q "select count(*) from sessions s where $TEST_WHERE;") NMAIL=$(q "select count(*) from mails where session_id in (select session_id from sessions s where $TEST_WHERE);") NREAD=$(q "select count(*) from mail_reads where mail_id in (select mail_id from mails where session_id in (select session_id from sessions s where $TEST_WHERE));") NATT=$(q "select count(*) from attachments where mail_id in (select mail_id from mails where session_id in (select session_id from sessions s where $TEST_WHERE));") NREL=$(q "select count(*) from relayed_mails where mail_id in (select mail_id from mails where session_id in (select session_id from sessions s where $TEST_WHERE));") NPERM=$(q "select count(*) from permission_requests where session_id in (select session_id from sessions s where $TEST_WHERE);") say "2. 连带删除的子表行" printf ' 会话 %s / 邮件 %s / 已读标记 %s / 附件 %s / 转发记录 %s / 权限请求 %s\n' \ "$NSESS" "$NMAIL" "$NREAD" "$NATT" "$NREL" "$NPERM" if [ "$APPLY" != 1 ]; then say "干跑结束" echo " 确认无误会删的东西后加 --apply 重跑。" exit 0 fi TS="$(date +%Y%m%d-%H%M%S)" BAK="/tmp/agentmail-pre-prune-$TS.db" say "3. 备份(sqlite3 .backup,不能用 cp —— WAL 下 cp 是不一致快照)" q ".backup '$BAK'" || { bad "备份失败,终止"; exit 1; } integ="$(sqlite3 "$BAK" 'PRAGMA integrity_check;' 2>&1 | head -1)" [ "$integ" = "ok" ] || { bad "备份 integrity_check=$integ"; exit 1; } ok "备份 $BAK($(du -h "$BAK" | cut -f1)),integrity_check=ok" # 删除**前**就先记下外键孤儿。库里本来就有存量孤儿(实测:agent_keys 指向已不存在的 # user、3 封回复的 parent_mail_id 悬空),它们是历史遗留,不是这次清理造成的。 # 断言「零孤儿」会永远红 → 下一个人会以为脚本坏了而忽略它。要断言的是**没有新增**。 FK_BEFORE="$(q 'PRAGMA foreign_key_check;' | sort)" [ -n "$FK_BEFORE" ] && printf ' [ 注 ] 删除前已有 %s 条存量外键孤儿(历史遗留,不计入本次)\n' "$(printf '%s\n' "$FK_BEFORE" | wc -l)" # 被删附件的 sha 先记下来:文件按 sha256 分片存放,同一份内容可能被多条记录共用, # 所以不能直接删文件,得等删完行再看还有没有别人引用。 SHAS="$(q "select distinct sha256 from attachments where mail_id in (select mail_id from mails where session_id in (select session_id from sessions s where $TEST_WHERE));")" say "4. 事务删除(子表先删;任何一步不过就整体回滚)" q <