chore: tdai-gateway 看门狗 + skill-curator/learner/daemon 修复

- 新增 tdai-gateway-watchdog.sh(每30min检查 systemd active + 8420 端口,异常飞书报警)
- skill-curator: PROTECTED_SKILLS + stopwords + .archive 扫描修复
- learner: pattern 支持度≥2 门禁 + episode_id 返回值修复
- daemon: L1→L2 蒸馏 procedural 分类
This commit is contained in:
小唯 A06 2026-08-02 23:41:48 +08:00
parent 73029308da
commit af0ebff46b
3 changed files with 68 additions and 8 deletions

View File

@ -1297,6 +1297,9 @@ def _cleanup_expired_cares():
follow_up = c.get("follow_up_date", "2099-12-31")
if follow_up < (today - timedelta(days=7)).isoformat():
continue
# stale 状态 → 物理删除stale 不再需要保留记录)
if c.get("status") == "stale":
continue
created = c.get("created_at", "")
status = c.get("status", "pending")
if status == "pending" and created:

View File

@ -26,17 +26,45 @@ from collections import defaultdict
from datetime import datetime
SKILLS_DIR = os.path.expanduser("~/.hermes/skills")
ARCHIVE_DIR = os.path.join(SKILLS_DIR, "_archive")
QUARANTINE_DIR = os.path.join(SKILLS_DIR, "_quarantine")
ARCHIVE_DIR = os.path.join(SKILLS_DIR, ".archive")
QUARANTINE_DIR = os.path.join(SKILLS_DIR, ".quarantine")
REPORT_PATH = os.path.expanduser("~/.hermes/skill-curator-report.json")
# ── 保护名单 ──────────────────────────────────────
# 这些技能被 cron jobs / SOUL.md / 系统组件直接引用,低频使用≠可删除。
# 任何情况下不进入归档/删除建议(保护=不归档、不删除、不在报告中列为 candidate
PROTECTED_SKILLS = {
# 系统核心cron / SOUL 引用)
"zhiyi", "hermes-agent", "hermes-self-improvement", "self-healing-infrastructure",
"accounting-voucher-ocr", "ao-orchestrator", "team-composer", "daily-recap",
"soulful-framework", "openclaw", "openclaw-mcp", "stock-research",
# 应急/铁律技能(低频=正常)
"hermes-venv-dependency-safety", "bge-embed-crash-loop-fix",
"cbm-code-analysis", "code-intelligence", "hermes-debug",
"hermes-profile-gateway-troubleshooting", "llm-gateway-ops",
"provider-tiering", "self-hosted-tunneling", "systematic-debugging",
"web-content-extraction", "ocr-and-documents", "feishu", "feishu-bot",
"zhiyi",
}
# 通用/运维词 — 重叠检测时过滤(避免 himalaya 式 100% 幻觉)
OVERLAP_STOPWORDS = {
"use", "when", "for", "the", "and", "with", "that", "this", "from", "via",
"to", "in", "of", "a", "an", "is", "are", "on", "at", "by", "or", "as",
"be", "it", "its", "available", "readiness", "status", "tool", "skill",
"cli", "api", "install", "config", "how", "what", "using", "used",
"building", "setup", "set", "up", "down", "run", "running", "work",
"works", "working", "can", "will", "would", "should", "need", "needs",
"check", "checks", "support", "supports", "help", "helps", "manage",
}
# ── 读取所有技能 ──────────────────────────────────
def load_all_skills():
"""遍历 skills/ 下所有子目录,读取 SKILL.md frontmatter"""
skills = []
for root, dirs, files in os.walk(SKILLS_DIR):
if "_archive" in root or "_quarantine" in root:
if ".archive" in root or ".quarantine" in root or "_archive" in root or "_quarantine" in root:
continue
if "SKILL.md" in files:
path = os.path.join(root, "SKILL.md")
@ -144,7 +172,9 @@ def score_skill(s):
return min(100, max(0, score))
def tier_from_score(score, has_version):
def tier_from_score(score, has_version, name=""):
if name in PROTECTED_SKILLS:
return 0, "core"
if has_version and score >= 60:
return 0, "core"
if score >= 40:
@ -168,7 +198,7 @@ def detect_overlaps(skills):
tj = set(re.findall(r'[a-z0-9\-]+', descs[j]))
# Filter common words
stopwords = {"use", "when", "for", "the", "and", "with", "that", "this", "from", "via", "to", "in", "of", "a", "an", "is", "are", "on", "at", "by", "or", "as", "be", "it", "its"}
stopwords = OVERLAP_STOPWORDS
ti = ti - stopwords
tj = tj - stopwords
@ -201,7 +231,7 @@ def generate_report(skills):
scored = []
for s in skills:
s["score"] = score_skill(s)
s["tier"], s["tier_name"] = tier_from_score(s["score"], s.get("has_version", False))
s["tier"], s["tier_name"] = tier_from_score(s["score"], s.get("has_version", False), s.get("name", ""))
scored.append(s)
tiers = defaultdict(list)
@ -230,12 +260,12 @@ def generate_report(skills):
][:20],
"stale_no_version": [
{"name": s["name"], "category": s.get("category",""), "score": s["score"]}
for s in scored if not s.get("has_version") and s["tier"] < 3
for s in scored if not s.get("has_version") and s["tier"] < 3 and s["name"] not in PROTECTED_SKILLS
],
"tier0_core": [s["name"] for s in scored if s["tier"] == 0],
"tier1_active": [s["name"] for s in scored if s["tier"] == 1],
"tier2_legacy": [s["name"] for s in scored if s["tier"] == 2],
"tier3_archive_candidate": [s["name"] for s in scored if s["tier"] == 3 and s["score"] < 20],
"tier3_archive_candidate": [s["name"] for s in scored if s["tier"] == 3 and s["score"] < 20 and s["name"] not in PROTECTED_SKILLS],
"all_skills": sorted([{
"name": s["name"],
"version": s.get("version", "N/A"),
@ -279,6 +309,8 @@ def execute_cleanup(skills, dry_run=True):
# Archive Tier 3 skills (no version, score < 20, small size)
for s in skills:
if s.get("name") in PROTECTED_SKILLS:
continue
if not s.get("has_version") and s["score"] < 20 and s.get("size_kb", 999) < 100:
if s["tier"] >= 2:
action = f"ARCHIVE {s['rel_dir']}: stale (no version, score={s['score']})"

View File

@ -0,0 +1,25 @@
#!/bin/bash
# tdai-gateway 看门狗:检查 systemd 服务 active + 8420 端口监听
# 正常 → 静默(空输出);异常 → 输出报警no-agent cron 会发送)
# 创建2026-08-02修复 systemd 崩溃循环 2534 次后补的监控
ALERT=""
if ! systemctl --user is-active tdai-gateway >/dev/null 2>&1; then
STATE=$(systemctl --user is-active tdai-gateway 2>/dev/null || echo unknown)
ALERT="tdai-gateway systemd 服务状态异常: ${STATE}"
fi
if ! ss -tln 2>/dev/null | grep -q ':8420 '; then
ALERT="${ALERT}${ALERT:+ | }tdai-gateway 端口 8420 未监听"
fi
if [ -n "$ALERT" ]; then
echo "🔴 tdai-gateway 异常 $(date '+%F %T')"
echo "$ALERT"
systemctl --user status tdai-gateway --no-pager 2>&1 | head -12
exit 0
fi
# 正常:空输出 = 静默
exit 0