Files
awoooi/apps/api/src/hermes/agent_loader.py
Your Name 2572ec46d2 feat(ws4): Hermes NL 自然語言介面 — 12-Agent Claude SDK 接入(ADR-094/095)
## hermes/ 套件(5 個新模組)

### display_names.py
- 12 agent 視覺識別表(emoji + hashtag + handle + short_name)
- format_response_header() 產生 Telegram 前綴

### agent_loader.py
- 解析 .claude/agents/*.md frontmatter → system prompt
- lru_cache 避免重複讀檔

### safety_hooks.py
- 移植 awoooi-guard.js 20 條 HARD BLOCK 規則(DENY_PATTERNS)
- 5 條 MUTATE_PATTERNS → 須走審批流

### nl_gateway.py
- Layer 1: 關鍵字正則路由(12 條規則,<10ms)
- Layer 3: DEFAULT_AGENT = "debugger"
- Claude Agent SDK query() 非同步串流,取 ResultMessage.result
- 安全降級:SDK error → 友好錯誤訊息

### telegram_webhook.py
- WS4 Hermes NL 接入(@tsenyangbot mention 或私訊觸發)
- HERMES_NL_ENABLED=False(feature flag 保護,預設關閉)

## telegram_gateway.py
- send_hermes_reply(text, chat_id, reply_to_message_id)
  無 500 字截斷,支援 Agent 長回覆

## config.py
- HERMES_NL_ENABLED: bool = False
- TELEGRAM_BOT_USERNAME: str = "tsenyangbot"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 02:10:06 +08:00

38 lines
1.1 KiB
Python
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.
"""載入 .claude/agents/*.md 並解析 system promptADR-095
2026-04-24 Claude Sonnet 4.6 (WS4 Hermes NL)
"""
from __future__ import annotations
import pathlib
from functools import lru_cache
_AGENTS_DIR = pathlib.Path("/Users/ogt/awoooi/.claude/agents")
def _parse_agent_md(path: pathlib.Path) -> str:
"""去除 YAML frontmatter回傳 body 作為 system prompt"""
text = path.read_text(encoding="utf-8")
# --- frontmatter ---
if text.startswith("---"):
end = text.find("---", 3)
if end != -1:
return text[end + 3:].strip()
return text.strip()
@lru_cache(maxsize=None)
def get_agent_system_prompt(agent_name: str) -> str | None:
"""
回傳指定 agent 的 system prompt。
若 .md 不存在回傳 Nonecaller 決定是否 fallback
"""
path = _AGENTS_DIR / f"{agent_name}.md"
if not path.exists():
return None
return _parse_agent_md(path)
def list_available_agents() -> list[str]:
"""回傳目前存在的 agent 名稱列表(無副檔名)"""
return [p.stem for p in sorted(_AGENTS_DIR.glob("*.md"))]