🎉 init: 小龙的工作空间
This commit is contained in:
Executable
+349
@@ -0,0 +1,349 @@
|
||||
#!/usr/bin/env bash
|
||||
# dream-cycle.sh — 记忆 consolidation 周期(Dream Cycle)
|
||||
#
|
||||
# 灵感: openclaw-auto-dream 的 4 Phase Dream Cycle
|
||||
# v2 — 加入远程记忆服务器同步
|
||||
#
|
||||
# 用法:
|
||||
# bash scripts/dream-cycle.sh # 完整运行
|
||||
# bash scripts/dream-cycle.sh collect # Phase 1: 扫描 daily/
|
||||
# bash scripts/dream-cycle.sh sync-remote # 推送新条目到远程服务器
|
||||
# bash scripts/dream-cycle.sh evaluate # Phase 3: 健康评分
|
||||
# bash scripts/dream-cycle.sh health # 只看健康分
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
WORKSPACE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
MEMORY_DIR="$WORKSPACE/memory"
|
||||
DAILY_DIR="$MEMORY_DIR/daily"
|
||||
REGISTERS_DIR="$MEMORY_DIR/registers"
|
||||
PROJECTS_DIR="$MEMORY_DIR/projects"
|
||||
MARKER="$MEMORY_DIR/.dream-cycle-last-run"
|
||||
NOW=$(date +%Y-%m-%d)
|
||||
NOW_EPOCH=$(date +%s)
|
||||
|
||||
# 远程记忆服务器
|
||||
MEMORY_SERVER="http://111.229.145.18"
|
||||
|
||||
# 上次运行时间
|
||||
LAST_RUN=""
|
||||
if [ -f "$MARKER" ]; then
|
||||
LAST_RUN=$(cat "$MARKER")
|
||||
fi
|
||||
|
||||
# ─── 智能标记检测 ──────────────────────────────────────
|
||||
|
||||
# 自动检测重要性(返回类型或空)
|
||||
detect_importance() {
|
||||
local line="$1"
|
||||
|
||||
# 决策句式
|
||||
if echo "$line" | grep -qE '(选择|决定|配置为|建立|部署|接入|升级|迁移|切换|采用)'; then
|
||||
echo "decision"
|
||||
return
|
||||
fi
|
||||
# 偏好表达
|
||||
if echo "$line" | grep -qE '(喜欢|习惯|总是|不要|偏好|改为|改成|以后都)'; then
|
||||
echo "preference"
|
||||
return
|
||||
fi
|
||||
# 纠正信号
|
||||
if echo "$line" | grep -qE '(不对|应该是|更正|其实是|原来是|发现.*错|误)'; then
|
||||
echo "correction"
|
||||
return
|
||||
fi
|
||||
# 重要事件
|
||||
if echo "$line" | grep -qE '(完成|成功|失败|上线|修复|解决|搞定|落地|生效)'; then
|
||||
echo "event"
|
||||
return
|
||||
fi
|
||||
# 工具/服务状态
|
||||
if echo "$line" | grep -qE '(状态|正常|异常|在线|离线|连接|断开)'; then
|
||||
echo "status"
|
||||
return
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─── Phase 1: Collect — 扫描 daily/ ──────────────────────
|
||||
|
||||
collect() {
|
||||
echo "📡 Phase 1: Collect — 日志扫描(智能标记)"
|
||||
echo " 上次运行: ${LAST_RUN:-从未}"
|
||||
echo ""
|
||||
|
||||
local tempfile=$(mktemp)
|
||||
local count=0
|
||||
|
||||
for f in "$DAILY_DIR"/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
local fname=$(basename "$f")
|
||||
local fdate="${fname%.md}"
|
||||
|
||||
# 跳过旧文件
|
||||
if [ -n "$LAST_RUN" ] && [ "$fdate" != "$LAST_RUN" ] && [[ "$fdate" < "$LAST_RUN" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
while IFS= read -r line; do
|
||||
local trimmed=$(echo "$line" | sed 's/^\*\*//;s/\*\*$//')
|
||||
local type=""
|
||||
|
||||
# 1. 显式标记优先
|
||||
if echo "$trimmed" | grep -q '\[session-flush\]'; then
|
||||
type="session-summary"
|
||||
local ctx=$(grep -A 5 '\[session-flush\]' "$f" 2>/dev/null | grep '^-' | head -4 | tr '\n' ' ' | head -c 500 || true)
|
||||
[ -n "$ctx" ] && echo "$fdate|$type|$ctx" >> "$tempfile" && count=$((count+1)) && continue
|
||||
fi
|
||||
if echo "$trimmed" | grep -q '\[correction'; then
|
||||
type="correction"
|
||||
local ctx=$(echo "$trimmed" | head -c 300)
|
||||
[ -n "$ctx" ] && echo "$fdate|$type|$ctx" >> "$tempfile" && count=$((count+1)) && continue
|
||||
fi
|
||||
|
||||
# 2. 智能检测(只对非标题行、非空行)
|
||||
if [ -n "$trimmed" ] && ! echo "$trimmed" | grep -qE '^(#|\||-*$|>)'; then
|
||||
type=$(detect_importance "$trimmed")
|
||||
if [ -n "$type" ]; then
|
||||
local ctx=$(echo "$trimmed" | head -c 300)
|
||||
echo "$fdate|$type|$ctx" >> "$tempfile" && count=$((count+1))
|
||||
fi
|
||||
fi
|
||||
done < "$f"
|
||||
done
|
||||
|
||||
echo " 收集 $count 条待同步"
|
||||
echo "$tempfile"
|
||||
}
|
||||
|
||||
# ─── Phase 1.5: Sync Remote — 推送到记忆服务器 ────────────
|
||||
|
||||
sync_remote() {
|
||||
echo "📤 Phase 1.5: Sync Remote → $MEMORY_SERVER"
|
||||
echo ""
|
||||
|
||||
# 用 collect 扫出新条目
|
||||
local tmpfile=$(collect | tail -1)
|
||||
[ ! -f "$tmpfile" ] && echo " 无待同步条目" && return
|
||||
|
||||
local total=0 failed=0
|
||||
local tmpfile2=$(mktemp)
|
||||
|
||||
# 逐行解析发送
|
||||
while IFS='|' read -r date type content; do
|
||||
[ -z "$content" ] && continue
|
||||
local clean=$(echo "$content" | sed 's/^[- ]*//;s/"/\\"/g')
|
||||
[ -z "$clean" ] && continue
|
||||
|
||||
local 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 "{\"content\":\"$clean\",\"type\":\"$type\",\"project\":\"xiaolong\",\"source\":\"dream-cycle\"}" 2>/dev/null || echo '{"error":"curl fail"}')
|
||||
|
||||
if echo "$resp" | grep -q 'stored'; then
|
||||
total=$((total+1))
|
||||
else
|
||||
failed=$((failed+1))
|
||||
fi
|
||||
done < "$tmpfile"
|
||||
|
||||
echo " 成功 $total / 失败 $failed"
|
||||
[ "$total" -gt 0 ] && echo " ✅ 记忆已写入远程服务器"
|
||||
echo "$NOW" > "$MARKER"
|
||||
rm -f "$tmpfile" "$tmpfile2"
|
||||
}
|
||||
|
||||
# ─── Phase 1.6: Sync Context Graph — 推送结构化记忆 ─────────
|
||||
|
||||
sync_context_graph() {
|
||||
echo "🧬 Phase 1.6: Sync ContextGraph → $MEMORY_SERVER"
|
||||
|
||||
if [ -f "$WORKSPACE/src/memory/sync-context-graph.js" ]; then
|
||||
node "$WORKSPACE/src/memory/sync-context-graph.js" 2>&1 | sed 's/^/ /'
|
||||
echo " ✅ ContextGraph 已同步"
|
||||
else
|
||||
echo " ⚠️ sync-context-graph.js 未找到,跳过"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─── Phase 2: Consolidate — 本地路由 ──────────────────────
|
||||
|
||||
consolidate() {
|
||||
echo "🔄 Phase 2: Consolidate — 本地路由"
|
||||
echo ""
|
||||
|
||||
mkdir -p "$REGISTERS_DIR"
|
||||
for reg in "$REGISTERS_DIR"/*.md; do
|
||||
[ -f "$reg" ] || continue
|
||||
local name=$(basename "$reg" .md)
|
||||
local cnt=$(grep -c '\- \[' "$reg" 2>/dev/null || echo 0)
|
||||
echo " registers/$name.md: ~${cnt} 条"
|
||||
done
|
||||
|
||||
local proj_cnt=$(ls "$PROJECTS_DIR"/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
local vault_lines=$(wc -l < "$MEMORY_DIR/vault.md" 2>/dev/null || echo 0)
|
||||
local mem_lines=$(wc -l < "$WORKSPACE/MEMORY.md" 2>/dev/null || echo 0)
|
||||
|
||||
echo " projects/: ${proj_cnt} 个"
|
||||
echo " vault.md: ${vault_lines} 行"
|
||||
echo " MEMORY.md: ${mem_lines} 行"
|
||||
}
|
||||
|
||||
# ─── Phase 2.5: Refresh Core Memory — 动态刷新核心记忆块 ───
|
||||
|
||||
refresh_core_memory() {
|
||||
echo "🧠 Phase 2.5: Refresh Core Memory Blocks"
|
||||
echo ""
|
||||
|
||||
local core_dir="$MEMORY_DIR/core"
|
||||
[ ! -d "$core_dir" ] && echo " ⚠️ memory/core/ 不存在,跳过" && return
|
||||
|
||||
local now_iso=$(date -Iseconds)
|
||||
|
||||
# 1. active_projects.md — 从最近 3 天的 daily/ 提取项目动态
|
||||
local active_file="$core_dir/active_projects.md"
|
||||
if [ -f "$active_file" ]; then
|
||||
local recent_projects=$(grep -h '\*\*.*\*\*' "$DAILY_DIR"/*.md 2>/dev/null | grep -E '(项目|仓库|同步|接入|部署|升级|完成|进行中)' | tail -10 | sed 's/^/- /' || true)
|
||||
if [ -n "$recent_projects" ]; then
|
||||
local header=$(head -8 "$active_file")
|
||||
echo "$header" > "$active_file"
|
||||
echo "" >> "$active_file"
|
||||
echo "## 进行中" >> "$active_file"
|
||||
echo "" >> "$active_file"
|
||||
echo "$recent_projects" >> "$active_file"
|
||||
sed -i "" "s/updated_at:.*/updated_at: \"$now_iso\"/" "$active_file" 2>/dev/null || true
|
||||
echo " ✅ active_projects.md 已刷新"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. recent_decisions.md — 从最近 7 天的 vault.md 提取决策
|
||||
local decisions_file="$core_dir/recent_decisions.md"
|
||||
if [ -f "$decisions_file" ]; then
|
||||
local recent_decisions=$(grep -E '^- [0-9]{4}-[0-9]{2}-[0-9]{2}:' "$MEMORY_DIR/vault.md" 2>/dev/null | tail -10 || true)
|
||||
if [ -n "$recent_decisions" ]; then
|
||||
local header=$(head -8 "$decisions_file")
|
||||
echo "$header" > "$decisions_file"
|
||||
echo "" >> "$decisions_file"
|
||||
echo "# 最近决策" >> "$decisions_file"
|
||||
echo "" >> "$decisions_file"
|
||||
echo "$recent_decisions" >> "$decisions_file"
|
||||
sed -i "" "s/updated_at:.*/updated_at: \"$now_iso\"/" "$decisions_file" 2>/dev/null || true
|
||||
echo " ✅ recent_decisions.md 已刷新"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 3. tool_status.md — 只更新时间戳,内容由事件触发更新
|
||||
local tool_file="$core_dir/tool_status.md"
|
||||
if [ -f "$tool_file" ]; then
|
||||
sed -i "" "s/updated_at:.*/updated_at: \"$now_iso\"/" "$tool_file" 2>/dev/null || true
|
||||
echo " ✅ tool_status.md 时间戳已更新"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ─── Phase 3: Evaluate — 健康评分 ─────────────────────────
|
||||
|
||||
evaluate() {
|
||||
echo "📊 Phase 3: Evaluate — 健康评分"
|
||||
echo ""
|
||||
|
||||
local total_daily=$(ls "$DAILY_DIR"/*.md 2>/dev/null | wc -l | tr -d ' ')
|
||||
local reg_count=0 updated_regs=0
|
||||
|
||||
for f in "$REGISTERS_DIR"/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
reg_count=$((reg_count+1))
|
||||
local mtime=$(stat -f "%m" "$f" 2>/dev/null || echo 0)
|
||||
[ "$mtime" -gt 0 ] && [ $(( (NOW_EPOCH-mtime)/86400 )) -le 30 ] && updated_regs=$((updated_regs+1))
|
||||
done
|
||||
|
||||
local vault_lines=$(wc -l < "$MEMORY_DIR/vault.md" 2>/dev/null || echo 0)
|
||||
|
||||
# 各维度评分
|
||||
local freshness=100; [ "$total_daily" -gt 0 ] && freshness=$(( $(ls -t "$DAILY_DIR"/*.md 2>/dev/null | head -1 | xargs stat -f "%m" 2>/dev/null || echo 0) > $((NOW_EPOCH-86400*7)) ? 100 : 50 ))
|
||||
local coverage=100; [ "$reg_count" -gt 0 ] && coverage=$(( updated_regs*100/reg_count ))
|
||||
local efficiency=100; [ "$vault_lines" -gt 200 ] && efficiency=60
|
||||
local reliability=90
|
||||
local security=95
|
||||
local health=$(( (freshness*25+coverage*25+efficiency*20+reliability*15+security*15)/100 ))
|
||||
|
||||
echo " 📈 健康评分: ${health}/100"
|
||||
echo " Freshness: ${freshness}/100 | Coverage: ${coverage}/100 | Efficiency: ${efficiency}/100"
|
||||
echo " Reliability: ${reliability}/100 | Security: ${security}/100"
|
||||
echo " daily: ${total_daily}篇 | registers: ${reg_count}个 | vault: ${vault_lines}行"
|
||||
}
|
||||
|
||||
# ─── 入口 ───────────────────────────────────────────
|
||||
|
||||
case "${1:-all}" in
|
||||
collect) collect ;;
|
||||
sync-remote) sync_remote ;;
|
||||
sync-graph) sync_context_graph ;;
|
||||
evaluate) evaluate ;;
|
||||
health) evaluate 2>&1 | grep -E '健康|Freshness|daily' ;;
|
||||
all)
|
||||
echo "🌙 小龙的 Dream Cycle — $NOW $(date +%H:%M)"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
sync_remote
|
||||
sync_context_graph
|
||||
echo ""
|
||||
refresh_core_memory
|
||||
echo ""
|
||||
consolidate
|
||||
forget_context_graph
|
||||
echo ""
|
||||
evaluate
|
||||
echo ""
|
||||
echo "✅ Dream Cycle 完成 (下一轮: 每天 4:00)"
|
||||
;;
|
||||
core-refresh) refresh_core_memory ;;
|
||||
*) echo "用法: bash scripts/dream-cycle.sh [collect|sync-remote|evaluate|health]"; exit 1 ;;
|
||||
esac
|
||||
|
||||
# ─── Phase 1.7: ContextGraph 遗忘机制 ─────────────────────
|
||||
|
||||
forget_context_graph() {
|
||||
echo "🧠 Phase 1.7: ContextGraph 遗忘机制"
|
||||
|
||||
[ ! -f ~/.openclaw/context-graph.json ] && echo " 无文件" && return
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const g = JSON.parse(fs.readFileSync(process.env.HOME + '/.openclaw/context-graph.json', 'utf8'));
|
||||
|
||||
let removed = 0;
|
||||
const now = Date.now();
|
||||
const THRESHOLD = 90 * 86400 * 1000; // 90天
|
||||
|
||||
for (const [id, entity] of Object.entries(g.entities)) {
|
||||
// 遗忘条件:非 pinned + 最后观察 >90天 + 低优先级
|
||||
if (entity.pinned) continue;
|
||||
if (!entity.observations || entity.observations.length === 0) continue;
|
||||
|
||||
const lastObs = entity.observations[entity.observations.length - 1];
|
||||
const age = now - new Date(lastObs.timestamp).getTime();
|
||||
|
||||
const hasHighPriority = entity.observations.some(o =>
|
||||
o.priority === 'critical' || o.priority === 'high'
|
||||
);
|
||||
|
||||
if (age > THRESHOLD && !hasHighPriority) {
|
||||
delete g.entities[id];
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理孤立关系
|
||||
const before = g.relations.length;
|
||||
g.relations = g.relations.filter(r =>
|
||||
g.entities[r.from] && g.entities[r.to]
|
||||
);
|
||||
const relRemoved = before - g.relations.length;
|
||||
|
||||
fs.writeFileSync(process.env.HOME + '/.openclaw/context-graph.json', JSON.stringify(g, null, 2));
|
||||
console.log(' 遗忘实体: ' + removed + ' | 孤立关系: ' + relRemoved);
|
||||
" 2>/dev/null || echo " 跳过"
|
||||
}
|
||||
Reference in New Issue
Block a user