From af0ebff46bafa8fbcb4fceee7beb17268374f271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E5=94=AF=20A06?= Date: Sun, 2 Aug 2026 23:41:48 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20tdai-gateway=20=E7=9C=8B=E9=97=A8?= =?UTF-8?q?=E7=8B=97=20+=20skill-curator/learner/daemon=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 tdai-gateway-watchdog.sh(每30min检查 systemd active + 8420 端口,异常飞书报警) - skill-curator: PROTECTED_SKILLS + stopwords + .archive 扫描修复 - learner: pattern 支持度≥2 门禁 + episode_id 返回值修复 - daemon: L1→L2 蒸馏 procedural 分类 --- scripts/daemon.py | 3 ++ scripts/skill-curator.py | 48 ++++++++++++++++++++++++++------ scripts/tdai-gateway-watchdog.sh | 25 +++++++++++++++++ 3 files changed, 68 insertions(+), 8 deletions(-) create mode 100755 scripts/tdai-gateway-watchdog.sh diff --git a/scripts/daemon.py b/scripts/daemon.py index c0b9a009..e93df3f0 100755 --- a/scripts/daemon.py +++ b/scripts/daemon.py @@ -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: diff --git a/scripts/skill-curator.py b/scripts/skill-curator.py index d1427c48..8438797a 100644 --- a/scripts/skill-curator.py +++ b/scripts/skill-curator.py @@ -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']})" diff --git a/scripts/tdai-gateway-watchdog.sh b/scripts/tdai-gateway-watchdog.sh new file mode 100755 index 00000000..fcc9b4bc --- /dev/null +++ b/scripts/tdai-gateway-watchdog.sh @@ -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