Files
16gagent/scripts/session-compact.sh
T
2026-06-06 10:40:48 +08:00

270 lines
11 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# session-compact.sh — 会话压缩与快速存档 + Write Gate 前置检查
#
# v3 — 加入 pre-compact 钩子:上下文压缩前自动扫描未保存的纠正/决策/承诺
# v2 — 加入 Write Gate 自动路由:preference/correction/decision 触发寄存器写入
#
# 用法:
# bash scripts/session-compact.sh save "任何想记住的内容"
# bash scripts/session-compact.sh save --type preference "偏好内容"
# bash scripts/session-compact.sh save --type correction "纠正内容"
# bash scripts/session-compact.sh save --type decision "决策内容"
# bash scripts/session-compact.sh pre-compact # 上下文压缩前扫描 + 同步
# bash scripts/session-compact.sh status # 看会话统计
# bash scripts/session-compact.sh flush # 触发 Dream Cycle 同步
# bash scripts/session-compact.sh check # Write Gate 诊断
set -euo pipefail
WORKSPACE="$(cd "$(dirname "$0")/.." && pwd)"
MEMORY_SERVER="http://111.229.145.18"
# 加载环境变量(MEMORY_API_KEY 等)
if [ -z "${MEMORY_API_KEY:-}" ] && [ -f ~/.zshrc ]; then
MEMORY_API_KEY=$(grep '^export MEMORY_API_KEY=' ~/.zshrc 2>/dev/null | cut -d'"' -f2 || true)
fi
export MEMORY_API_KEY
DAILY="$WORKSPACE/memory/daily/$(date +%Y-%m-%d).md"
REGISTERS="$WORKSPACE/memory/registers"
SESSION_LOG="$WORKSPACE/memory/.session-compact.log"
# ── Write Gate 路由 ──────────────────────────────────────
write_to_register() {
local type="$1" content="$2" timestamp
timestamp=$(date '+%Y-%m-%d %H:%M')
case "$type" in
preference)
printf "\n| %s | %s | %s | session-compact | medium |\n" \
"$(wc -l < "$REGISTERS/preferences.md" | tr -d ' ')" \
"$content" "$timestamp" >> "$REGISTERS/preferences.md"
echo " 📝 → preferences 寄存器"
;;
correction)
printf "\n[correction: %s]\n- **new**: %s\n- **source**: session-compact\n" \
"$timestamp" "$content" >> "$REGISTERS/open-loops.md"
echo " 🔧 → open-loops 寄存器 (correction)"
;;
decision)
printf "\n- %s: %s (session-compact)\n" "$timestamp" "$content" \
>> "$WORKSPACE/memory/vault.md"
echo " 📋 → vault.md (decision)"
;;
event)
# 事件记录到 daily/ 已足够,不写寄存器
echo " 📌 → daily/ (event,不写寄存器)"
;;
fact|behavior|code|rule|tool|project)
# 新类型:写入 vault.md 的决策时间线
printf "\n- %s: [%s] %s (session-compact)\n" "$timestamp" "$type" "$content" \
>> "$WORKSPACE/memory/vault.md"
echo " 📋 → vault.md ($type)"
;;
*)
echo " ⚠️ 未知类型 '$type',仅写入 daily/"
;;
esac
}
# ── 命令处理 ─────────────────────────────────────────────
case "${1:-help}" in
save)
shift
# 解析参数
type=""
content=""
recall_context=""
while [ $# -gt 0 ]; do
case "$1" in
--type) type="$2"; shift 2 ;;
--type=*) type="${1#*=}"; shift ;;
--recall-context) recall_context="$2"; shift 2 ;;
--recall-context=*) recall_context="${1#*=}"; shift ;;
*) content="$1"; shift ;;
esac
done
if [ -z "$content" ]; then
echo "用法: bash scripts/session-compact.sh save [--type <type>] [--recall-context <context>] <内容>"
exit 1
fi
# ── Step 1: LLM Write Gate 判断(如果未指定 type)──
if [ -z "$type" ] && [ -f "$WORKSPACE/scripts/memory-write-gate.sh" ]; then
echo " 🧠 Write Gate LLM 判断中..."
GATE_RESULT=$(echo "$content" | bash "$WORKSPACE/scripts/memory-write-gate.sh" 2>/dev/null || echo '{"should_remember":false}')
SHOULD_REMEMBER=$(echo "$GATE_RESULT" | jq -r '.should_remember // false' 2>/dev/null || echo "false")
if [ "$SHOULD_REMEMBER" = "true" ]; then
type=$(echo "$GATE_RESULT" | jq -r '.type // ""' 2>/dev/null || echo "")
importance=$(echo "$GATE_RESULT" | jq -r '.importance // 0.5' 2>/dev/null || echo "0.5")
gate_content=$(echo "$GATE_RESULT" | jq -r '.content // ""' 2>/dev/null || echo "")
gate_recall=$(echo "$GATE_RESULT" | jq -r '.recall_context // [] | join(",")' 2>/dev/null || echo "")
gate_tags=$(echo "$GATE_RESULT" | jq -r '.tags // [] | join(",")' 2>/dev/null || echo "")
# 用 LLM 精炼的内容覆盖原始内容
[ -n "$gate_content" ] && [ "$gate_content" != "null" ] && content="$gate_content"
# 用 LLM 的 recall_context(如果用户没指定)
[ -z "$recall_context" ] && [ -n "$gate_recall" ] && [ "$gate_recall" != "null" ] && recall_context="$gate_recall"
echo " ✅ Write Gate: 值得记 (type=$type, importance=$importance)"
else
gate_reason=$(echo "$GATE_RESULT" | jq -r '.reason // "not_important"' 2>/dev/null || echo "not_important")
echo " ️ Write Gate: 不值得记 ($gate_reason),仅写入 daily/"
fi
fi
# ── Step 2: 写入 daily/(带 YAML frontmatter)──
mkdir -p "$(dirname "$DAILY")"
memory_id="mem_$(date +%Y%m%d)_$(openssl rand -hex 3 2>/dev/null || date +%s)"
# 如果有 recall_context,用 YAML frontmatter 格式
if [ -n "$type" ] || [ -n "$recall_context" ]; then
{
echo ""
echo "---"
echo "memory_id: $memory_id"
[ -n "$type" ] && echo "type: $type"
echo "created_at: $(date -Iseconds)"
[ -n "$recall_context" ] && echo "recall_context: [$recall_context]"
echo "---"
echo "[session-flush] $(date +%H:%M)$content"
} >> "$DAILY"
else
echo "" >> "$DAILY"
echo "[session-flush] $(date +%H:%M)$content" >> "$DAILY"
fi
echo " ✅ 已写入 $DAILY (id: $memory_id)"
# ── Step 3: Write Gate — 写寄存器 ──
if [ -n "$type" ]; then
write_to_register "$type" "$content"
fi
# ── Step 4: ContextGraph 集成 ──
if [ "$type" = "decision" ] || [ "$type" = "correction" ] || [ "$type" = "event" ]; then
node -e "
try {
const { ContextGraph } = require('$WORKSPACE/src/memory/context-graph');
const g = new ContextGraph();
const entityId = 'session:$(date +%Y%m%d)';
g.entity(entityId, { type: 'session', name: 'Session $(date +%Y-%m-%d)' });
g.observe(entityId, {
type: '$type',
priority: '$type' === 'decision' ? 'high' : 'normal',
summary: process.argv[1].substring(0, 200),
details: { source: 'session-compact', memory_id: '$memory_id' }
});
g.save();
console.log(' 🕸️ → ContextGraph');
} catch(e) { console.log(' ⚠️ ContextGraph: ' + e.message); }
" "$content" 2>/dev/null || echo " ⚠️ ContextGraph 写入失败"
fi
# ── Step 5: 推到远程服务器(带新字段)──
clean=$(echo "$content" | sed 's/"/\\"/g' | head -c 400)
remote_payload=$(jq -n \
--arg c "$clean" \
--arg t "${type:-session-flush}" \
--arg rc "${recall_context:-}" \
--arg mid "$memory_id" \
'{content:$c, type:$t, project:"xiaolong", source:"session-compact", memory_id:$mid, recall_context:($rc | split(",") | map(select(. != "")))}')
resp=$(curl -s --max-time 5 -X POST "$MEMORY_SERVER/api/v2/add" \
-H "Content-Type: application/json" \
-H "X-API-Key: ${MEMORY_API_KEY:-}" \
-d "$remote_payload" 2>/dev/null) || true
if echo "$resp" | grep -q 'stored\|ok'; then
echo " ✅ 已同步到远程服务器"
else
echo " ⚠️ 远程同步: ${resp:-无响应}"
fi
echo "$(date '+%Y-%m-%d %H:%M') | ${type:-general} | $content" >> "$SESSION_LOG"
;;
check)
echo "🔍 Write Gate 诊断"
echo ""
echo " 寄存器状态:"
for f in "$REGISTERS"/*.md; do
[ -f "$f" ] || continue
name=$(basename "$f" .md)
lines=$(wc -l < "$f" | tr -d ' ')
size=$(wc -c < "$f" | tr -d ' ')
echo " $name: ${lines}行 / ${size}字节"
done
echo ""
echo " 最近 5 条 compact:"
[ -f "$SESSION_LOG" ] && tail -5 "$SESSION_LOG" || echo " 无记录"
echo ""
echo " 今天 daily:"
[ -f "$DAILY" ] && wc -l < "$DAILY" | xargs echo " lines:" || echo " (空)"
;;
status)
echo "📊 会话状态"
echo " 每日日志: $DAILY"
echo " 寄存器:"
ls "$REGISTERS"/*.md 2>/dev/null | while read -r f; do
echo " $(basename "$f" .md): $(wc -l < "$f")"
done
echo " Compact 历史:"
[ -f "$SESSION_LOG" ] && tail -5 "$SESSION_LOG" || echo " 无记录"
;;
flush)
echo "🔄 触发立即存档..."
bash "$WORKSPACE/scripts/dream-cycle.sh" sync-remote 2>&1
;;
pre-compact)
echo "🛡️ Pre-Compaction Hook — 上下文压缩前安全检查"
echo ""
has_flush=false
if [ -f "$DAILY" ]; then
if grep -q '\[session-flush\]' "$DAILY"; then
echo " ✅ 今天已有 [session-flush] 标记"
has_flush=true
grep '\[session-flush\]' "$DAILY" | tail -3 | while read -r line; do
echo " $(echo "$line" | cut -c1-80)"
done
fi
fi
if [ "$has_flush" = false ]; then
echo " ⚠️ 今天 daily/ 无 [session-flush] 标记 — 压缩前请先存档关键信息"
fi
echo ""
echo " 🌐 远程服务器:"
remote_resp=$(curl -s --max-time 5 "$MEMORY_SERVER/api/v2/stats" 2>/dev/null || echo '{"error":"unreachable"}')
if echo "$remote_resp" | grep -q 'xiaolong'; then
echo " ✅ 在线"
else
echo " ⚠️ 不可达"
fi
echo ""
echo " 📤 触发同步..."
bash "$WORKSPACE/scripts/dream-cycle.sh" sync-remote 2>&1 | sed 's/^/ /'
echo ""
echo " ✅ Pre-Compaction Hook 完成"
;;
*)
echo "用法:"
echo " bash scripts/session-compact.sh save <内容> — 快速存关键信息"
echo " bash scripts/session-compact.sh save --type preference <内容> — 记偏好(→registers"
echo " bash scripts/session-compact.sh save --type correction <内容> — 记纠正(→registers"
echo " bash scripts/session-compact.sh save --type decision <内容> — 记决策(→vault"
echo " bash scripts/session-compact.sh pre-compact — 压缩前安全检查 + 同步"
echo " bash scripts/session-compact.sh status — 查看状态"
echo " bash scripts/session-compact.sh check — Write Gate 诊断"
echo " bash scripts/session-compact.sh flush — 触发全部同步"
;;
esac