first commit

This commit is contained in:
Hermes Agent
2026-05-10 13:52:46 +08:00
commit ccc63d1e70
4583 changed files with 584341 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
# Custom OpenAI-Compatible Providers in Hermes
When a provider isn't built-in but offers an OpenAI-compatible `/v1` endpoint, add it manually to `config.yaml`.
## Steps
1. **Find the base URL and API key env var** — usually `/v1` at the provider's domain.
2. **List available models:**
```bash
curl -s "https://<provider>/v1/models" \
-H "Authorization: Bearer $API_KEY" \
--max-time 15 | python3 -m json.tool
```
Note model `id`, `input_modalities`, `context_length`, `supported_features`.
3. **Add provider to config.yaml via Python** (don't hand-edit YAML — indentation errors break everything):
```python
import yaml, json, os
config_path = os.path.expanduser("~/.hermes/config.yaml")
with open(config_path) as f:
config = yaml.safe_load(f)
# Read API key from .env
api_key = ""
with open(os.path.expanduser("~/.hermes/.env")) as f:
for line in f:
if line.startswith("YOUR_KEY_PREFIX="):
api_key = line.strip().split("=", 1)[1]
break
config.setdefault("providers", {})["your-provider"] = {
"api_key": api_key,
"base_url": "https://provider.example.com/v1",
"available_models_json": json.dumps([
{"id": "model-id", "name": "Display Name"},
]),
"model": "default-model-id",
"model_display_name": "Default Display Name"
}
with open(config_path, "w") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
```
4. **Verify with a test call:**
```bash
curl -s "https://provider.example.com/v1/chat/completions" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"model-id","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'
```
5. **Use the model:** `hermes -m your-provider/model-id` or via `hermes model` picker.
## Provider Config Fields
| Field | Required | Notes |
|-------|----------|-------|
| `api_key` | Yes | Actual key value, not env var reference |
| `base_url` | Yes | Must end with `/v1` (or `/v1/`) |
| `available_models_json` | Yes | JSON string of `[{id, name}]` array |
| `model` | No | Default model ID |
| `model_display_name` | No | Human-readable default model name |
| `api_mode` | No | Only set if non-standard (e.g. `anthropic-messages` for MiniMax). Omit for OpenAI-compatible. |
| `protocol` | No | Usually leave as `''` |
## Pitfalls
- **Don't hand-edit YAML** — use Python `yaml.safe_load` + `yaml.dump` to avoid indentation corruption.
- **`api_key` must be the actual value**, not `$ENV_VAR` — Hermes doesn't resolve env vars inside provider config (only in `.env`).
- **No `api_mode` needed for OpenAI-compatible** — only set this for providers with custom protocols (Anthropic Messages, etc.).
- **`reasoning` field in responses** — some providers (SenseNova, DeepSeek) return a `reasoning` field in the message object. Hermes handles this natively for reasoning-capable models.
- **Model discovery** — always call `/v1/models` first; don't guess model IDs from documentation (they change).
## Known Custom Providers
| Provider | Base URL | Key Env Var | Models |
|----------|----------|-------------|--------|
| SenseNova | `https://token.sensenova.cn/v1` | `SN_API_KEY` | sensenova-6.7-flash-lite, deepseek-v4-flash, sensenova-u1-fast |

View File

@@ -0,0 +1,68 @@
# Dashboard Remote Access
## Problem
Dashboard binds to 127.0.0.1:9119 by default. Accessing from a different machine (e.g., local laptop → cloud VPS) requires either SSH tunnel or insecure bind.
## Recommended: SSH Port Forwarding
```bash
# On your local machine
ssh -L 9119:127.0.0.1:9119 user@server-ip
# Then open http://127.0.0.1:9119 in browser
```
**Pitfall (Windows):** `ssh: connect to host ... port 22: Connection timed out` — almost always a cloud security group issue. Check your cloud provider's security group / firewall rules to allow inbound TCP 22. SSH socket activation (`ssh.socket`) is enabled by default on Ubuntu; the service itself may show `inactive (dead)` — that's normal, socket activation triggers it on connection.
## Password Protection (Reverse Proxy)
Dashboard has **no built-in password auth**. Options:
### Nginx + Basic Auth
```bash
sudo apt install nginx apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd your-username
```
```nginx
server {
listen 8080;
location / {
auth_basic "Hermes Dashboard";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:9119;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket support for Chat TUI
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
```
### Caddy (simpler config)
```
:8080 {
basicauth * {
username $hashed_password
}
reverse_proxy localhost:9119
}
```
Generate hash: `caddy hash-password --plaintext 'your-password'`
## Alternative: Insecure Bind (⚠️ exposes API keys)
```bash
hermes dashboard --insecure --port 9119
# Access via http://server-ip:9119
```
Only use on trusted/private networks. The dashboard exposes `.env` contents including API keys. The `--insecure` flag exists because there's no built-in auth — the warning is intentional.
## TUI Mode (Embedded Chat)
```bash
hermes dashboard --tui --no-open
```
Adds a Chat tab to the web UI — a browser-based `hermes --tui` via PTY/WebSocket. Useful when CLI access is inconvenient.
## Common Issues
- Multiple dashboard processes: `hermes dashboard --stop` kills all
- Port conflict: change port with `--port 8080`
- Gateway must be running for Kanban dispatch to work (`hermes gateway status`)
- SSH connection timeout from Windows: check cloud security group allows inbound TCP 22

View File

@@ -0,0 +1,110 @@
# Hermes Dashboard Reverse Proxy with Nginx
## Quick Setup (Nginx + Basic Auth)
### 1. Install Dependencies
```bash
sudo apt update && sudo apt install -y nginx apache2-utils
```
### 2. Create Password File
```bash
# Generate password (will prompt for password twice)
sudo htpasswd -c /etc/nginx/.htpasswd <username>
# Or non-interactive:
echo -n '<username>:' | sudo tee /etc/nginx/.htpasswd
openssl passwd -apr1 '<password>' | sudo tee -a /etc/nginx/.htpasswd
```
### 3. Nginx Config (`/etc/nginx/sites-available/hermes-dashboard`)
```nginx
server {
listen 80;
server_name <your-domain-or-ip>; # e.g., 111.230.53.30 or hermes.example.com
location / {
auth_basic "Hermes Dashboard";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://127.0.0.1:9119;
# IMPORTANT: Use "localhost" for Host header, NOT $host
# Dashboard validates Host header and rejects non-localhost values
# This causes "Invalid Host header" error if set to $host
proxy_set_header Host localhost;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (required for Chat TUI)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
```
### 4. Enable & Reload
```bash
sudo ln -sf /etc/nginx/sites-available/hermes-dashboard /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
### 5. Ensure Dashboard Running
```bash
hermes dashboard --no-open --port 9119
```
## Access
- URL: `http://<domain-or-ip>`
- Auth: Browser popup for username/password
## Commands
```bash
sudo systemctl status nginx
sudo systemctl restart nginx
hermes dashboard --status
hermes dashboard --stop
```
## Cleanup (Remove Reverse Proxy)
```bash
# Stop services
hermes dashboard --stop
sudo systemctl stop nginx
sudo systemctl disable nginx
# Remove config files
sudo rm -f /etc/nginx/sites-available/hermes-dashboard
sudo rm -f /etc/nginx/sites-enabled/hermes-dashboard
sudo rm -f /etc/nginx/.htpasswd
```
## HTTPS (Optional)
Use Certbot for Let's Encrypt:
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d hermes.example.com
```
## Pitfalls
### Invalid Host Header Error
If you see `{"detail":"Invalid Host header. Dashboard requests must use the hostname the server was bound to."}`:
- **Cause**: Nginx is passing `$host` (the public domain/IP) but Dashboard only accepts `localhost`
- **Fix**: Change `proxy_set_header Host $host;` to `proxy_set_header Host localhost;`
### Domain Requires ICP Filing (China)
If accessing via domain in China triggers ICP filing requirement:
- **Solution**: Use IP address directly instead of domain
- Update `server_name` to the server's public IP
### Security Notes
- Dashboard has NO built-in password auth
- Without reverse proxy, anyone with network access can see API keys
- Always use reverse proxy + basic auth for remote access
- Consider SSH port forwarding as a more secure alternative

View File

@@ -0,0 +1,76 @@
# Hermes Update Autostash Triage
`hermes update` auto-stashes local changes before pulling. These accumulate as `stash@{N}` with the naming pattern:
```
hermes-update-autostash-YYYYMMDD-HHMMSS
```
## Triage Workflow
### Step 1: List all stashes
```bash
git stash list
```
### Step 2: Quick scan each stash — file types matter
```bash
git stash show stash@{N} --stat
```
**Lock-file-only stashes** (only `package.json`, `package-lock.json`, `ui-tui/package-lock.json`):
- Usually npm dependency resolution artifacts (registry mirror switches, peer dependency reclassification)
- Safe to drop: `git stash drop stash@{N}`
**Source-code stashes** (`.py`, `.ts`, `.tsx` files changed):
- Need detailed analysis — these may contain valuable local features
### Step 3: For source-code stashes — compare against current code
Don't just `git stash pop`. First check if the features were already merged upstream:
```bash
# Get the full diff
git stash show -p stash@{N}
# For each key feature, search current code:
grep -n "feature_keyword" path/to/file.py
```
**Classification:**
- ✅ Already in current code → safe to drop
- ❌ Missing from current code → candidate for restoration
### Step 4: Decision matrix
| Stash type | Action |
|------------|--------|
| Lock files only | Drop immediately |
| Source code, all features merged | Drop |
| Source code, some features missing | Selective restore (cherry-pick specific hunks) or apply + resolve conflicts |
| Source code, all features missing | `git stash apply stash@{N}` then test |
### Pitfalls
- **Don't blindly pop stashes on an active branch** — always `apply` first (preserves stash), test, then `drop` if good.
- **Registry mirror changes in lock files** (npmmirror.com, mirrors.tencentyun.com) are local environment artifacts, not valuable code. Drop them.
- **`peer: true` removal** in lock files = npm re-resolved peer deps as direct deps. Not meaningful.
- **5+ day old stashes** with source changes are likely abandoned experiments. Check if the user still needs them before restoring.
- **Merge conflicts** are common after 5+ days — upstream moves fast. Expect to resolve manually.
### Restoring selectively
If only some hunks from a stash are needed:
```bash
# Apply but don't drop
git stash apply stash@{N}
# Review conflicts
git diff
# Or use interactive checkout for specific files
git checkout stash@{N} -- path/to/specific/file.py
```

View File

@@ -0,0 +1,61 @@
# QQ Bot Rich Media API Reference
Source: https://bot.q.qq.com/wiki/develop/api-v2/server-inter/message/send-receive/rich-media.html
## Endpoints
| Scope | Endpoint | Method |
|-------|----------|--------|
| 单聊 | `/v2/users/{openid}/files` | POST |
| 群聊 | `/v2/groups/{group_openid}/files` | POST |
## Parameters
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| file_type | int | Yes | 1=图片, 2=视频, 3=语音, 4=文件 |
| url | string | Yes* | 媒体资源 URL*url 或 file_data 二选一) |
| file_data | string | No | base64 二进制数据 |
| srv_send_msg | bool | Yes | true=直接发送占用主动消息频次false=仅上传获取 file_info |
| file_name | string | No | 文件名file_type=4 时建议传) |
## Supported Formats
- 图片: png/jpg
- 视频: mp4
- 语音: silk/wav/mp3/flac
- 文件: 无限制(群场景暂不开放 file_type=4
## Response
```json
{
"file_uuid": "...",
"file_info": "...", // 用于发送消息接口的 media 字段
"ttl": 3600 // 剩余秒数0=长期有效
}
```
## Sending with file_info
```json
{
"msg_type": 7,
"media": {"file_info": "<file_info_from_upload>"},
"msg_seq": 1
}
```
## Notes
- `file_info` 不受目标端影响,可复用到多个群/用户
-`/v2/users/{openid}/files` 上传的仅能发单聊,群上传的仅能发群聊
- 建议 `srv_send_msg=false`,先获取 file_info 再发送
- 文件大小限制:~100MB分块上传~10MBinline base64
## Hermes Adapter Implementation
- Gateway adapter: `gateway/platforms/qqbot/adapter.py``_send_media()` (line ~2690)
- Chunked upload: `gateway/platforms/qqbot/chunked_upload.py`
- Media types defined in `gateway/platforms/qqbot/constants.py` (MEDIA_TYPE_IMAGE=1, VIDEO=2, VOICE=3, FILE=4)
- send_message tool gap: `_send_qqbot()` in `tools/send_message_tool.py:1677` is text-only

View File

@@ -0,0 +1,96 @@
# Search Tools for Hermes Agent
## Current Environment
| Tool | Type | Usage |
|------|------|-------|
| **web** (built-in) | Web Search & Scraping | `web_search`, `web_extract` tools |
| **session_search** | Session history | `session_search(query="...")` |
| **mmx search** | MiniMax CLI | `mmx search query "关键词"` |
## Popular Search APIs for AI Agents
| Tool | Best For | Pricing | MCP Server |
|------|----------|---------|------------|
| **Tavily Search** | AI-native search, structured results | Free tier available | `tavily-mcp` |
| **SerpAPI** | Google results scraping | Paid (100 free/month) | `serpapi-mcp` |
| **Brave Search** | Privacy-focused, no tracking | Free tier (2000/month) | `brave-search-mcp` |
| **Perplexity API** | AI search with citations | Paid | API only |
| **Firecrawl** | Web scraping + extraction | Free tier | `firecrawl-mcp` |
| **Jina Reader** | URL to Markdown conversion | Free tier | API only |
| **SearXNG** | Self-hosted meta search | Free (self-hosted) | `searxng-mcp` |
## Configuration Examples
### Tavily Search (Recommended for AI Agents)
```yaml
# ~/.hermes/config.yaml
mcp_servers:
tavily:
command: npx
args: ["-y", "tavily-mcp@latest"]
env:
TAVILY_API_KEY: "tvly-xxxxx"
```
Get API key: https://tavily.com (free tier: 1000 searches/month)
### Brave Search
```yaml
mcp_servers:
brave-search:
command: npx
args: ["-y", "@anthropic/brave-search-mcp@latest"]
env:
BRAVE_API_KEY: "BSAxxxxx"
```
Get API key: https://brave.com/search/api/ (free tier: 2000 queries/month)
### Firecrawl (for Web Scraping)
```yaml
mcp_servers:
firecrawl:
command: npx
args: ["-y", "firecrawl-mcp"]
env:
FIRECRAWL_API_KEY: "fc-xxxxx"
```
## When to Use Which
| Scenario | Recommended Tool |
|----------|------------------|
| Quick factual lookup | `mmx search` or built-in `web_search` |
| Structured data extraction | Tavily Search |
| Google-specific results | SerpAPI |
| Privacy-sensitive search | Brave Search |
| Deep web scraping | Firecrawl |
| Convert page to markdown | Jina Reader |
| Previous conversation context | `session_search` |
## Pitfalls
- **mcp_servers: {}** is currently empty in this environment
- Tool changes require `/reset` (new session) to take effect
- Free tiers have rate limits; monitor usage for production use
- Some MCP servers require Node.js (`npx` command)
- **mmx search Token Plan 限制**`mmx search query` 需要 MiniMax Token Plan 支持 `coding-plan-search` 模型。报错 "your current token plan not support model" 表示 plan 不含搜索能力,需升级或换替代方案
- **服务器出站网络受限**:某些云服务器/容器的出站网络被限制,`urllib``Network is unreachable` (errno 101)。此时 curl、Python requests、mimo_search.py 均无法访问外部。排查:`curl -s -o /dev/null -w "%{http_code}" https://www.baidu.com`
- **Playwright/Chrome 沙盒问题**:服务器上 Chrome 需要 `--no-sandbox` 参数才能启动。Hermes 的 browser 工具可能因沙盒限制失败。排查:检查 Chrome stderr 中是否有 "No usable sandbox" 错误
- **mimo_search.py 环境变量**:脚本依赖 `XIAOMI_API_KEY` 环境变量,但 Hermes config.yaml 中的 key 不会自动 export。需手动 `export XIAOMI_API_KEY=...` 或从 config 提取
## 搜索降级策略(按优先级)
当用户请求搜索时,按以下顺序尝试:
1. **mmx search** — 最简单,但需 Token Plan 支持
2. **mimo_search.py** — MiMo 原生搜索,需 XIAOMI_API_KEY 环境变量 + 网络可达
3. **web_search 工具** — Hermes 内置,需 web toolset 启用
4. **Playwright 浏览器** — 通用兜底,但需 Chrome 可启动
5. **curl + 搜索引擎** — 最基础,需服务器出站网络可达
全部失败时告知用户具体失败原因Token Plan 限制 / 网络不通 / 浏览器沙盒问题),并建议用户在本地自行搜索。

View File

@@ -0,0 +1,64 @@
# Skill Management Pitfalls
Learned from attempting to optimize the skill library based on SkillRouter paper findings.
## Pitfall 1: "Same Output" ≠ "Functionally Overlapping"
**Wrong:** Deleted `pptx-generator` (python-pptx) because `powerpoint` (pptxgenjs) also makes .pptx files.
**Right:** Different tech stacks = different fallback options. python-pptx is pure Python, pptxgenjs needs Node.js. Keep both.
**Rule:** Two skills overlap only when they use the same tools AND serve the same user intent. Same output format is not enough.
## Pitfall 2: Don't Cross-Reference in Descriptions
**Wrong:** In arxiv's description: "需要多源学术搜索优先用 sn-search-academic"
**Right:** Each skill describes itself only. No competitive recommendations.
**Why:** Creates circular dependencies. If skill A recommends B, and B recommends A, the LLM loops.
## Pitfall 3: Don't Expose Implementation Details
**Wrong:** In sn-infographic description: "需要 SN_API_KEY"
**Right:** "需要 SenseNova API"
**Rule:** Descriptions should express user-facing capabilities, not internal tool/API names.
## Pitfall 4: Check Hard Dependencies Before Deleting
**Wrong:** Marked sn-research-planning for deletion because it was "never called."
**Right:** sn-deep-research calls it via `skill_view("sn-research-planning")` at runtime. Deleting breaks the pipeline.
**How to check:**
```python
# Search all SKILL.md files for references to the target skill name
# Only HARD dependencies count: skill_view("target-name") or "读取 target-name"
# "Related skills" mentions are SOFT and don't block deletion
```
## Pitfall 5: "Never skill_view'd" ≠ "Unused"
Skills can be auto-loaded via the system prompt's "MUST load" instruction without explicit `skill_view()` calls. Session data only shows explicit tool calls.
**Better metric:** Check if the skill is referenced as a runtime dependency by other skills.
## Pitfall 6: Don't Batch Recommendations Without Verification
**Wrong:** Generated all 87 recommendations at once, sent to user, then had to fix multiple errors.
**Right:** Verify each category before sending. Check dependencies. Then send once.
**User feedback:** "你这建议就不能确认好之后再发给我吗" (Can you verify before sending?)
## Description Quality Formula
Good skill description = **What it does** + **Trigger words** + **Negative boundary**
Example:
```
"深度调研全流程编排器(入口 skill。自动完成规划→分维度取证→综合→成稿。
触发词:深度研究/调研/全面研究/调研报告/deep research。
不用于:单点事实问答、一句话摘要。"
```
- What: 深度调研全流程编排器
- Triggers: 深度研究/调研/全面研究
- Boundary: 不用于单点事实问答

View File

@@ -0,0 +1,51 @@
# 服务器环境网络搜索指南
## Playwright 浏览器启动(服务器/容器环境)
服务器环境Ubuntu 23.10+、容器、VM需要 `--no-sandbox` 参数:
```python
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=['--no-sandbox', '--disable-setuid-sandbox']
)
page = browser.new_page()
page.goto('https://example.com', timeout=30000)
# ... 操作页面
browser.close()
```
注意Hermes 内置的 `browser_navigate` 工具不支持传递启动参数,必须直接用 Playwright API。
## 搜索引擎反爬情况2026年测试
| 搜索引擎 | 状态 | 备注 |
|----------|------|------|
| 百度 | ❌ 验证码 | 滑块验证,无法绕过 |
| 搜狗 | ❌ 验证码 | 图片点选验证 |
| 必应(cn.bing.com) | ⚠️ 可用但质量差 | 中文搜索结果常偏离关键词 |
| Google | ❌ 超时 | 服务器网络不可达 |
| DuckDuckGo | ❌ 超时 | 服务器网络不可达 |
**结论**:服务器环境下,主流搜索引擎基本不可用。必应是唯一能返回结果的,但质量不稳定。
## mmx search 限制
`mmx search` 需要 Token Plan 支持 `coding-plan-search` 模型。如果报错:
```
your current token plan not support model, coding-plan-search
```
说明当前计划不支持搜索功能,需要升级或使用其他方式。
## MiMo 模型搜索能力
MiMo (mimo-v2.5-pro) 可以声明 `web_search` 工具调用,但**实际上不会真正联网搜索**。它只能基于训练数据回答,无法获取实时信息。
## 替代方案优先级
1. Playwright + 必应(唯一可行的浏览器方案)
2. 直接访问目标网站(如培训机构官网)
3. 用户自行搜索后提供信息