docs(webhook): complete filters + route-scripts coverage across doc surfaces (#60983)

Follow-up to #60944 (webhook payload filters and route scripts):
- reference/cli-commands.md (en+zh): document the new --script option on
  'hermes webhook subscribe'
- zh-Hans user-guide webhooks.md: mirror the Payload Filters and Script
  Filters/Transforms sections plus the filters/script route properties
  (the salvage shipped English-only docs)
- hermes-agent skill webhooks reference: teach the agent the filters/
  script surface so agent-driven subscriptions can use them
This commit is contained in:
Teknium 2026-07-08 11:56:24 -07:00 committed by GitHub
parent 76381e2a8e
commit aabfedcac0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 86 additions and 0 deletions

View file

@ -67,6 +67,22 @@ hermes webhook subscribe <name> \
Returns the webhook URL and HMAC secret. The user configures their service to POST to that URL.
### Filter or transform payloads before the agent runs
Two mechanisms narrow broad event streams (e.g. Todoist/GitHub fire on every update) so only relevant payloads wake the agent:
- **Declarative `filters`** (config.yaml routes only): list of conditions on payload fields, event type, or headers — operators `equals`, `not_equals`, `contains`, `exists`, `missing`, `in`, `in_file`, `regex`, with `all`/`any`/`not` grouping. Non-matching events are ignored with HTTP 200.
- **Route scripts** (`--script` on subscribe, or `script:` on a config route): a script under `~/.hermes/scripts/` receives the payload as JSON on stdin. JSON stdout replaces the payload before prompt templating; empty stdout, `[SILENT]`, or a nonzero exit ignores the webhook. `.sh`/`.bash` run with bash, everything else with Python. Scripts cannot live outside `~/.hermes/scripts/` (path traversal is blocked).
```bash
hermes webhook subscribe todoist-hermes \
--prompt "Task changed: {payload.content}" \
--script "todoist-hermes-label.py" \
--deliver telegram --deliver-chat-id "12345"
```
Full filter syntax: https://hermes-agent.nousresearch.com/docs/user-guide/messaging/webhooks#payload-filters
### List subscriptions
```bash
hermes webhook list

View file

@ -671,6 +671,7 @@ hermes webhook subscribe <name> [options]
| `--deliver-chat-id` | Target chat/channel ID for cross-platform delivery. |
| `--secret` | Custom HMAC secret. Auto-generated if omitted. |
| `--deliver-only` | Skip the agent — deliver the rendered `--prompt` as the literal message. Zero LLM cost, sub-second delivery. Requires `--deliver` to be a real target (not `log`). |
| `--script` | Filter/transform script under `~/.hermes/scripts/`. The webhook payload is passed as JSON on stdin; JSON stdout replaces the payload, and empty stdout, `[SILENT]`, or a nonzero exit code ignores the webhook. See [Script Filters and Transforms](../user-guide/messaging/webhooks.md#script-filters-and-transforms). |
Subscriptions persist to `~/.hermes/webhook_subscriptions.json` and are hot-reloaded by the webhook adapter without a gateway restart.

View file

@ -477,6 +477,7 @@ hermes webhook subscribe <name> [options]
| `--deliver-chat-id` | 跨平台投递的目标聊天/频道 ID。 |
| `--secret` | 自定义 HMAC 密钥。省略时自动生成。 |
| `--deliver-only` | 跳过 agent——将渲染后的 `--prompt` 作为字面消息投递。零 LLM 成本,亚秒级投递。要求 `--deliver` 为真实目标(非 `log`)。 |
| `--script` | 位于 `~/.hermes/scripts/` 下的过滤/转换脚本。webhook payload 以 JSON 形式通过 stdin 传入JSON stdout 会替换 payload空 stdout、`[SILENT]` 或非零退出码会忽略该 webhook。参见[脚本过滤与转换](../user-guide/messaging/webhooks.md#script-filters-and-transforms)。 |
订阅持久化到 `~/.hermes/webhook_subscriptions.json`webhook 适配器无需重启 gateway 即可热重载。

View file

@ -81,6 +81,8 @@ curl http://localhost:8644/health
| `events` | 否 | 要接受的事件类型列表(例如 `["pull_request"]`)。若为空,则接受所有事件。事件类型从 `X-GitHub-Event``X-GitLab-Event` 或 payload 中的 `event_type` 读取。 |
| `secret` | **是** | 用于签名验证的 HMAC secret。若路由未设置则回退到全局 `secret`。仅用于测试时可设为 `"INSECURE_NO_AUTH"`(跳过验证)。 |
| `prompt` | 否 | 使用点号表示法访问 payload 字段的模板字符串(例如 `{pull_request.title}`)。若省略,则将完整 JSON payload 转储到 prompt 中。 |
| `filters` | 否 | 声明式 payload 过滤器,在认证/请求体/事件过滤之后、agent 或直接投递之前求值。不匹配时返回 `{"status":"ignored","reason":"filter"}`HTTP 200。 |
| `script` | 否 | 位于 `~/.hermes/scripts/` 下的过滤/转换脚本。webhook payload 以 JSON 形式通过 stdin 传入。stdout 为 JSON 对象时会在模板渲染前替换 payload文本 stdout 以 `script_output` 形式暴露;空 stdout、`[SILENT]` 或非零退出码会忽略该 webhook。 |
| `skills` | 否 | agent 运行时加载的 skill 名称列表。 |
| `deliver` | 否 | 响应发送目标:`github_comment``telegram``discord``slack``signal``sms``whatsapp``matrix``mattermost``homeassistant``email``dingtalk``feishu``wecom``weixin``bluebubbles``qqbot`,或 `log`(默认)。 |
| `deliver_extra` | 否 | 额外的投递配置——键取决于 `deliver` 类型(例如 `repo``pr_number``chat_id`)。值支持与 `prompt` 相同的 `{dot.notation}` 模板语法。 |
@ -116,9 +118,75 @@ platforms:
events: ["push"]
secret: "deploy-secret"
prompt: "New push to {repository.full_name} branch {ref}: {head_commit.message}"
filters:
- field: "ref"
equals: "refs/heads/main"
deliver: "telegram"
```
### Payload 过滤器 {#payload-filters}
当服务商发送宽泛的事件流、但只有部分 payload 应唤醒 agent 或触发 `deliver_only` 投递时,使用 `filters`。过滤器在签名验证、请求体解析和 `events` 之后运行,但在 prompt 渲染、幂等去重、agent 调度或直接投递之前运行。
```yaml
platforms:
webhook:
extra:
routes:
todoist:
events: ["item:updated"]
secret: "todoist-secret"
filters:
- field: "payload.labels"
contains: "hermes"
- any:
- field: "payload.priority"
equals: 4
- field: "payload.project_id"
in_file: "~/.hermes/data/todoist/watchlist.json"
prompt: "Todoist task changed: {payload.content}"
```
支持的操作符:
- `exists: true|false`
- `missing: true`
- `equals` / `not_equals`
- `contains` — 适用于字符串、列表和 dict 键
- `in` — 内联列表
- `in_file` — JSON 数组、JSON 对象(使用其键)或按行分隔的文本文件
- `regex`
- `all``any``not` 分组
字段路径使用点号表示法。当存在顶层 `payload` 对象时,`payload.foo` 从中读取;对扁平 payload 则从 webhook 请求体根部读取。`event` / `event_type` 匹配解析出的事件类型,`headers.<Name>` 读取请求头。
### 脚本过滤与转换 {#script-filters-and-transforms}
当声明式过滤器不够用时,使用 `script`。脚本必须位于当前 profile 的 `~/.hermes/scripts/` 目录下;相对路径在该目录内解析,且禁止路径穿越到目录之外。`.sh``.bash` 脚本用 bash 运行,其他扩展名用当前 Python 解释器运行。
路由 payload 以 JSON 形式发送到 stdin
```python
# ~/.hermes/scripts/todoist-hermes-label.py
import json
import sys
payload = json.load(sys.stdin)
labels = payload.get("payload", {}).get("labels", [])
if "hermes" not in labels:
print("[SILENT]")
raise SystemExit(0)
payload["body"] = payload["payload"]["content"]
print(json.dumps(payload))
```
脚本结果:
- stdout 为 JSON 对象时,替换 `prompt``deliver_extra` 使用的 payload。
- 非 JSON 文本 stdout 会以 `script_output` 字段加入 payload。
- 空 stdout、精确的 `[SILENT]``{"__hermes_ignore__": true}`、超时、脚本缺失或非零退出码,均返回 HTTP 200 及 `{"status":"ignored","reason":"script"}`
### Prompt 模板
Prompt 使用点号表示法访问 webhook payload 中的嵌套字段: