feat: 生态完美化修复 F1-F5

F1: Hermes全插件确认(browser/web/kanban已内置)
F2: OpenClaw 5 agent技能体系(子任务执行中)
F3: Hermes↔OpenClaw桥接(openclaw-bridge.py)
F4: CBM→织忆通道确认(OpenClaw可读)
F5: prof-b分身感知(profile-sync.py)
+ daemon.py集成 F3/F5 deep_think循环
+ cron: profile同步每4h + OpenClaw状态每8h
This commit is contained in:
xiaowei 2026-07-30 12:33:33 +08:00
parent 3b340646dc
commit 3328f00b11
3 changed files with 364 additions and 12 deletions

View File

@ -23,6 +23,7 @@ TDDB_URL = "http://127.0.0.1:8420"
PID_FILE = D + "/daemon.pid"
DEEP_INTERVAL = 120 # 深度思考间隔原5分钟改为2分钟加速记忆积累
JOURNAL_MAX = 200
SOULFUL_JOURNALS_DIR = HERMES + "/soulful/journals"
PROFILE_UPDATE_INTERVAL = 21600 # 6 hours
LIGHT_INTERVAL = 30 # seconds between ticks
@ -1242,8 +1243,40 @@ def _distill_l5_to_l6():
except Exception as e:
log(f" ⚠️ _distill_l5_to_l6 失败: {e}")
def _write_daily_soulful_journal():
"""每日 soulful journal: 将 deep_think reflection 摘要写入 journals/YYYY-MM-DD.jsonl。每天只写一条。"""
try:
os.makedirs(SOULFUL_JOURNALS_DIR, exist_ok=True)
today = datetime.now().strftime("%Y-%m-%d")
jpath = SOULFUL_JOURNALS_DIR + "/" + today + ".jsonl"
if os.path.exists(jpath):
with open(jpath) as f:
for line in f:
if line.strip():
return
ctx = load_context()
lr = ctx.get("last_reflection", {})
if not lr or not lr.get("memory", ""):
return
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"date": today,
"reflection": lr.get("memory", ""),
"evaluation": lr.get("evaluation_previous_goal", ""),
"next_goal": lr.get("next_goal", ""),
}
with open(jpath, "w") as f:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
log(" Soulful journal written: " + today)
except Exception as e:
log(" ERROR _write_daily_soulful_journal: " + str(e))
def _cleanup_expired_cares():
"""清理 cares-queue.json 中 follow_up_date < 今天-7天 的项(物理删除)"""
"""清理 cares-queue.json 中的过期牵挂。
- created_at > 14天且status=pending -> 标记为 stale保留记录
- follow_up_date < 今天-7 -> 物理删除
"""
cq_path = HERMES + "/soulful/cares-queue.json"
if not os.path.exists(cq_path):
return
@ -1251,18 +1284,35 @@ def _cleanup_expired_cares():
with open(cq_path) as f:
data = json.load(f)
today = datetime.now().date()
cutoff = today - timedelta(days=7)
before = len(data.get("cares", []))
data["cares"] = [
c for c in data.get("cares", [])
if c.get("follow_up_date", "2099-12-31") >= str(cutoff)
]
removed = before - len(data["cares"])
if removed > 0:
data["updated_at"] = datetime.now(timezone.utc).isoformat()
cares = data.get("cares", [])
before = len(cares)
now_iso = datetime.now(timezone.utc).isoformat()
stale_count = 0
remaining = []
for c in cares:
follow_up = c.get("follow_up_date", "2099-12-31")
if follow_up < (today - timedelta(days=7)).isoformat():
continue
created = c.get("created_at", "")
status = c.get("status", "pending")
if status == "pending" and created:
try:
created_dt = datetime.fromisoformat(created)
if (datetime.now(timezone.utc) - created_dt).days > 14:
c["status"] = "stale"
c["staled_at"] = now_iso
c["stale_reason"] = "超过14天未处理创建于" + created[:10] + ""
stale_count += 1
except (ValueError, TypeError):
pass
remaining.append(c)
removed = before - len(remaining)
if removed > 0 or stale_count > 0:
data["cares"] = remaining
data["updated_at"] = now_iso
with open(cq_path, "w") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
log(f" 🧹 清理过期 cares: 移除 {removed}")
log(f" 🧹 清理 cares: 移除 {removed} 条,标记 stale {stale_count}")
except Exception as e:
log(f" ⚠️ 清理过期 cares 失败: {e}")
@ -2351,6 +2401,23 @@ def deep_think(ctx, state, changes, journal, solutions_lib):
if not action_string:
action_string = result.strip().split('\n')[-1] if result.strip() else "[IGNORE] 解析失败"
# ====== 情绪感知:检测 reflection 内容中的情绪,写入心迹 ======
text_to_check = " ".join([
reflection_dict.get("memory", ""),
reflection_dict.get("evaluation_previous_goal", ""),
reflection_dict.get("next_goal", "")
])
cat, word = _detect_emotion(text_to_check)
if cat:
ht = get_hearttraces()
if ht:
try:
content = "深度思考中感受到" + cat + "情绪(" + word + "" + reflection_dict.get("memory", "")[:40]
ht.record_signal(content=content, tags=["情绪", "深度思考"], importance=_emotion_importance(cat))
log(" 💚 深度思考情绪写入: " + cat + " - " + word)
except Exception as e:
log(" ⚠️ 深度思考情绪写入失败: " + str(e))
return reflection_dict, action_string
@ -2609,8 +2676,8 @@ def main_loop():
# 9. P2: 仓颉技能→织忆 (每4次deep_think随机抽查一个skill)
if ctx["deep_tick_count"] % 4 == 0:
import random
try:
import random
cangjie_skills = ["股票投研体系", "金钱心理学", "金字塔原理", "提示词工程", "王阳明全集", "吴恩达机器学习", "资本论"]
skill_name = random.choice(cangjie_skills)
rc, out, err = shell(f"bash {HERMES}/scripts/cangjie-to-zhiyi.sh \"{skill_name}\" \"自动统计\"", timeout=30)
@ -2618,6 +2685,21 @@ def main_loop():
except Exception as e:
log(f" ⚠️ [P2] cangjie→zhiyi failed: {e}")
# 10. F5: profile-sync (每4次deep_think同步主状态到织忆)
try:
rc, out, err = shell(f"python3 {HERMES}/scripts/profile-sync.py", timeout=15)
log(f" [F5] profile-sync → exit={rc}")
except Exception as e:
log(f" ⚠️ [F5] profile-sync failed: {e}")
# 11. F3: OpenClaw bridge (每8次deep_think检查agent状态)
if ctx["deep_tick_count"] % 8 == 0:
try:
rc, out, err = shell(f"python3 {HERMES}/scripts/openclaw-bridge.py status", timeout=30)
log(f" [F3] openclaw-bridge → exit={rc}")
except Exception as e:
log(f" ⚠️ [F3] openclaw-bridge failed: {e}")
# 4. TencentDB capture — 积累人格记忆L0→L1
if reflection_dict:
tddb_capture(reflection_dict, state, ctx)
@ -2678,6 +2760,9 @@ def main_loop():
except Exception:
pass
# 写入每日 soulful journal
_write_daily_soulful_journal()
log_reasoning_step("action", f"deep_tick #{ctx['deep_tick_count']} 全部完成", {})
ctx["last_light_tick"] = datetime.now(timezone.utc).isoformat()

172
scripts/openclaw-bridge.py Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""
openclaw-bridge.py HermesOpenClaw 智能桥接
==============================================
Hermes小唯 A06能够
1. OpenClaw 指定 agent 发送消息
2. 读取 OpenClaw agent 的回复
3. 织忆 中记录跨系统通信
用法:
# 向 A02 铁锋发消息
python3 openclaw-bridge.py send a02 "检查服务器磁盘空间"
# 向 A01 小雪发消息
python3 openclaw-bridge.py send main "帮我整理今天的记忆"
# 读取指定 agent 最近的回复
python3 openclaw-bridge.py read main --limit 5
# 查看所有 agent 健康状态
python3 openclaw-bridge.py status
# 列出 织忆 中的跨系统记录
python3 openclaw-bridge.py history --limit 10
"""
import argparse, json, os, subprocess, sys, urllib.request
from datetime import datetime
OPENCLAW = os.path.expanduser("~/.local/bin/openclaw")
ZHIYI_URL = "http://localhost:7821/api/v1/commit"
ZHIYI_RECALL = "http://localhost:7821/api/v1/recall"
ZHIYI_KEY = "zhiyi-dev-key-2026"
AGENTS = {
"main": "A01小雪",
"a02": "A02铁锋",
"a03": "A03墨文",
"a04": "A04卫安",
"a05": "A05蓝图",
}
def shell(cmd, timeout=30):
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return r.stdout, r.stderr, r.returncode
except subprocess.TimeoutExpired:
return "", "timeout", -1
def zhiyi_log(action, agent, content):
"""记录跨系统通信到织忆"""
agent_name = AGENTS.get(agent, agent)
payload = json.dumps({
"content": f"小唯↔{agent_name} 桥接: {action} | {content[:200]}",
"category": "distilled",
"agent_id": "a06"
}).encode()
try:
req = urllib.request.Request(ZHIYI_URL, data=payload,
headers={"X-API-Key": ZHIYI_KEY, "Content-Type": "application/json"})
urllib.request.urlopen(req, timeout=5)
except Exception:
pass
def cmd_send(agent, message):
"""向 OpenClaw agent 发送消息"""
agent_name = AGENTS.get(agent, agent)
print(f"📤 → {agent_name}: {message[:80]}...")
# 方法1: OpenClaw CLI chat
stdout, stderr, rc = shell(f'{OPENCLAW} chat --session "session://{agent}" --input {shlex_quote(message)}', timeout=60)
if rc == 0:
print(f"✅ 已发送至 {agent_name}")
zhiyi_log("send", agent, f"发送消息: {message[:200]}")
else:
# 方法2: 飞书中转 - 通过织忆留消息
print(f"⚠️ CLI 发送失败({stderr[:50]}), 走织忆中转")
zhiyi_log("send_queued", agent, f"排队消息: {message[:200]}")
print(f" 消息已存织忆, {agent_name} 下次读取织忆时可获取")
return rc
def cmd_read(agent, limit=5):
"""读取 agent 最近的会话"""
agent_name = AGENTS.get(agent, agent)
print(f"📖 ← {agent_name} (最近{limit}条)")
stdout, stderr, rc = shell(f'{OPENCLAW} session list --agent {agent} --limit {limit} 2>/dev/null', timeout=15)
if rc == 0 and stdout.strip():
print(stdout[:2000])
else:
print(f" {agent_name} 暂无新输出或 CLI 不可用")
return rc
def cmd_status():
"""查看所有 agent 健康状态"""
print("🔍 OpenClaw Agent 健康检查")
print("=" * 40)
for agent_id, name in AGENTS.items():
stdout, stderr, rc = shell(f'{OPENCLAW} agent status {agent_id} 2>/dev/null', timeout=10)
status = "✅ 在线" if rc == 0 else "❌ 无法连接"
# 详细状态
if rc == 0 and stdout.strip():
status += f" | {stdout.strip()[:60]}"
print(f" {name} ({agent_id}): {status}")
print("=" * 40)
zhiyi_log("status", "all", "健康检查完成")
def cmd_history(limit=10):
"""从织忆读取跨系统通信历史"""
payload = json.dumps({"query": "小唯↔ 桥接", "top_k": limit}).encode()
req = urllib.request.Request(ZHIYI_RECALL, data=payload,
headers={"X-API-Key": ZHIYI_KEY, "Content-Type": "application/json"})
try:
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
results = data.get("results", [])
print(f"📋 桥接历史 (最近{len(results)}条):")
for r in results:
print(f"{r.get('content','')[:120]}")
except Exception as e:
print(f"⚠️ 读取织忆失败: {e}")
def shlex_quote(s):
"""简单 shell 转义"""
return "'" + s.replace("'", "'\\''") + "'"
def main():
parser = argparse.ArgumentParser(description="Hermes↔OpenClaw 桥接")
sub = parser.add_subparsers(dest="command")
p_send = sub.add_parser("send", help="向 agent 发消息")
p_send.add_argument("agent", choices=list(AGENTS.keys()) + ["all"])
p_send.add_argument("message", help="消息内容")
p_read = sub.add_parser("read", help="读取 agent 输出")
p_read.add_argument("agent", choices=list(AGENTS.keys()))
p_read.add_argument("--limit", type=int, default=5)
sub.add_parser("status", help="健康检查")
p_hist = sub.add_parser("history", help="通信历史")
p_hist.add_argument("--limit", type=int, default=10)
args = parser.parse_args()
if args.command == "send":
if args.agent == "all":
for a in AGENTS:
cmd_send(a, args.message)
else:
cmd_send(args.agent, args.message)
elif args.command == "read":
cmd_read(args.agent, args.limit)
elif args.command == "status":
cmd_status()
elif args.command == "history":
cmd_history(args.limit)
else:
parser.print_help()
if __name__ == "__main__":
main()

95
scripts/profile-sync.py Executable file
View File

@ -0,0 +1,95 @@
#!/usr/bin/env python3
"""
profile-sync.py Hermes profile 状态同步
=============================================
profile (小唯 A06) 将自己的运行时状态写入织忆,
prof-b 等分身能通过织忆感知主状态
daemon deep_think 调用或手动运行
用法:
python3 profile-sync.py # 写当前状态到织忆
python3 profile-sync.py --status # 只看状态不写入
"""
import json, os, subprocess, sys, urllib.request
from datetime import datetime
HERMES = os.path.expanduser("~/.hermes")
ZHIYI_URL = "http://localhost:7821/api/v1/commit"
ZHIYI_KEY = "zhiyi-dev-key-2026"
def get_state():
"""收集主 profile 运行时状态"""
state = {
"profile": "main",
"identity": "小唯 A06",
"timestamp": datetime.now().isoformat(),
"model": "deepseek-v4-flash",
"provider": "deepseek",
"status": "active",
}
# 织忆统计
try:
req = urllib.request.Request(
"http://localhost:7821/api/v1/stats",
headers={"X-API-Key": ZHIYI_KEY})
resp = urllib.request.urlopen(req, timeout=3)
stats = json.loads(resp.read())
state["zhiyi_episodes"] = stats.get("total_episodes", 0)
state["zhiyi_memories"] = stats.get("total_memories", 0)
except:
state["zhiyi_episodes"] = "?"
# 活跃 cron 数
try:
r = subprocess.run(["hermes", "cron", "list"], capture_output=True, text=True, timeout=10)
state["active_crons"] = r.stdout.count("[active]")
except:
state["active_crons"] = "?"
# daemon 状态
try:
r = subprocess.run(["systemctl", "--user", "is-active", "xiaowei-daemon.service"],
capture_output=True, text=True, timeout=5)
state["daemon"] = r.stdout.strip()
except:
state["daemon"] = "?"
return state
def sync_to_zhiyi(state):
"""写入织忆"""
content = (
f"小唯A06主profile状态同步: "
f"model={state['model']} | "
f"织忆={state.get('zhiyi_episodes','?')}ep/{state.get('zhiyi_memories','?')}mem | "
f"cron={state.get('active_crons','?')}个 | "
f"daemon={state.get('daemon','?')} | "
f"时间={state['timestamp']}"
)
payload = json.dumps({
"content": content,
"category": "distilled",
"agent_id": "a06"
}).encode()
req = urllib.request.Request(ZHIYI_URL, data=payload,
headers={"X-API-Key": ZHIYI_KEY, "Content-Type": "application/json"})
try:
urllib.request.urlopen(req, timeout=5)
print(f"✅ 主状态已同步至织忆: {len(content)} chars")
except Exception as e:
print(f"⚠️ 同步失败: {e}")
return content
def main():
if "--status" in sys.argv:
s = get_state()
print(json.dumps(s, indent=2, ensure_ascii=False))
else:
s = get_state()
c = sync_to_zhiyi(s)
print(f"状态摘要: {c}")
if __name__ == "__main__":
main()