Hermes 的 Milky QQ 平台适配器
让 Hermes 进入 QQ 私聊和群聊,成为一个会判断何时回应的 AI 参与者:
- 接收消息,并识别提及、引用、图片等上下文;
- 根据会话状态判断何时回应、何时保持沉默;
- 发送文本、@、引用、图片、语音、视频和文件;
- 提供群组、成员、文件和好友/入群请求等 QQ 能力。
Warning
当前仍有一类权限隔离尚未完成:
- ToolSpec: 26 个 QQ 工具没有独立的调用者和目标授权。模型、其他会话或 cron 可能查询无关群/好友,或执行禁言、踢人、撤回、退群、删好友、接受/拒绝请求等操作。
最低限度的安全配置:
MILKY_ALLOWED_CHATS只填写你能控制、成员可信且用途明确的会话;- 不要加入公开群、成员可随意加入的群或不受控私聊;
- 记住:该配置只限制入站会话,不等于 ToolSpec 或通用出站 sender 的授权;
- 留空、未配置或原生空列表表示阻止全部普通入站;全部放行需显式填写 group:* 和 dm:*。
示例:
MILKY_ALLOWED_CHATS=group:123456789,dm:987654321示例中的 ID 仅为占位值。
允许使用 dm:* 放行所有私聊,或使用 group:* 放行所有群聊;通配符只匹配对应的
dm:/group: 命名空间,也可以和具体 chat key 混用。
详细的模块职责、生命周期和行为契约见 ARCHITECTURE.md;主规范、未归档 change 和已归档 change 的测试证据见 openspec/。
插件适合希望把 Hermes 放进 QQ 私聊和群聊的场景:
- 自然参与: 根据提及、引用、关键词和会话状态,决定回应还是保持沉默;
- 静默标记兜底提示: 普通文本出站会精确过滤 Hermes 在拒绝
[SILENT]后生成的固定提示;相似或包含该提示的其他文本,以及媒体和文件发送,不受影响。Gateway 仍可能将过滤结果记为 delivered,但不会产生 QQ 消息。 - 多媒体消息: 接收图片、语音等上下文,并发送文本、@、引用、图片、语音、视频和文件;入站视频只展示 Milky 提供的资源引用和时长,不提供视频内容分析;当前语音交给 Hermes core 的 STT 流程,插件不负责 provider 适配或音频格式转换;
- QQ 信息能力: 代码和 manifest 声明群组、成员、文件和好友/入群请求等固定工具;这些工具是否进入 Agent toolset 取决于 Hermes 的加载支持,当前目标宿主尚未加载,见下方迁移阻塞说明;
- 会话安全边界: 支持 chat 白名单、禁言状态同步、消息去重和有界历史缓冲。
- QQ 会话介绍: 在支持 system prompt section 的 Hermes 宿主中,首次 Milky friend/group session prompt 可看到当前会话的最小资料;介绍来自入站消息快照,不实时查询 Milky。
- 人工贴纸维护: 通过显式
/milky sticker命令维护插件持久目录中的图片库;普通消息、入站图片、 关键词、Will 和 Agent 输出不会自动收集贴纸。 - Agent 贴纸工具: 插件代码和 manifest 声明
sticker_search与sticker_send。在宿主将工具加载进 Agent toolset、运行时依赖jieba且库中存在可用条目时,前者可严格搜索、显式放宽意图或浏览有界候选, 后者可按同样的查询或已知贴纸 ID 向当前 Milky 会话发送一张贴纸;目标、路径和 URL 仍由工具边界管理。 指定 Hermes0e37a439bda15ef3c28a4d20593964d7c6a527a6未将这两个工具或其余 manifest 工具加载进 user-installed plugin 的 Agent toolset,因此该宿主中不能由 Agent 调用。 - 工具调用历史:
post_tool_call只观察 manifest 声明的 Milky/QQ 工具,并在每个 profile 的plugin-data/hermes-plugin-milky/tool-call-history.db中保留 UTC 时间、耗时、观察状态和逐工具白名单参数摘要。
运行环境:Python 3.13+、Hermes Gateway 0.21.4 或更新版本、Milky v1.3 服务,以及插件声明的
httpx、python-multipart、Pillow 和 jieba。requires_hermes: ">=0.21.4" 是 Hermes 版本下限,
不是特定 commit 的锁定;部署时应使用已验证的 Hermes 版本。Hermes 负责 Agent 队列及入站资源的
下载、缓存和权限边界;本插件负责 Milky 适配和已声明的 QQ 能力。
hermes plugins install ByteColtX/hermes-plugin-milky --enableHermes PluginManager 会读取插件 pyproject.toml 中的运行依赖,在启用前准备依赖环境并请求所需的
依赖授权。若拒绝授权或依赖准备失败,新插件会保持 disabled;更新失败会保留当前代码和已发布环境。
不要通过手动安装依赖或在插件导入时安装依赖来绕过该流程。--enable 会在安装及依赖准备成功后
直接启用插件并跳过启用确认提示。首次安装完成后,可以用下面的命令确认
插件状态:
hermes plugins list如果 hermes-plugin-milky 显示为 disabled,启用插件:
hermes plugins enable hermes-plugin-milky插件启用后,按配置完成 Milky 服务地址、access token 和可选白名单配置, 然后重启 Gateway:
hermes gateway restart已有安装需要拉取新版本时,执行:
hermes plugins update hermes-plugin-milky
hermes gateway restart更新会按当前 pyproject.toml 重新准备运行依赖;准备或发布失败时会保留现有插件版本。更新插件代码
或配置后都需要重启 Gateway,运行中的进程不会自动加载新的插件代码。
本版本包含 Hermes 迁移的其余改动;当前 main 暂由根 register(ctx) 调用
outbound.tools::register_tools(ctx),作为兼容尝试。该函数只在宿主 context 提供 register_tool 时登记工具;
指定目标 Hermes 的实际验证仍发现插件工具注册表和 Milky Agent toolset 为空。按官方 第三方平台插件指南
拆出的独立 tools.py::register_tools(ctx) 已提交到 codex/milky-tools-entry,待 Hermes core 修复
user-installed platform loader 后再合并到 main。社区平台仍以第三方插件分发,不需要改动或并入 Hermes core。
指定 Hermes 0e37a439bda15ef3c28a4d20593964d7c6a527a6(v0.21.5+8508.g0e37a43)的实际验证发现,用户安装的 Milky platform 走 eager register() 路径,不会调用独立 tools.py::register_tools(ctx)。因此新版分支不能依赖该 loader;当前 main 保留根入口调用作为过渡。该 SHA 的源码只对 bundled platform 使用 deferred loader,因此没有履行官方指南中第三方 platform provides_tools 的注册路径。这是该目标宿主与公开插件合同的差异,不表示 Milky 应作为内置插件安装。requires_hermes: ">=0.21.4" 只是版本下限,不能代替新版分支的目标 Gateway toolset 实测。详见 platform contract evidence。
官方发布版 Hermes v0.21.5(v2026.9.24)的源码静态核对也显示相同的 loader 分支:独立 tools.py 的预注册只发生在 bundled deferred platform 路径。该源码检查不是一次新的 Milky Gateway 运行验证;新版入口分支保留官方 provides_tools/tools.py 结构,当前 main 继续使用根入口兼容路径。
社区跟踪显示这是 Hermes 平台 loader 的已知问题族:#78050 / #81163 及已合并的 #86660 修复了 bundled deferred platform 的 client-tool 预注册;开放的 #111643 / #111669 继续跟踪第三方 platform 的 discovery 生命周期。后续确认 user-installed loader 修复后,再把 codex/milky-tools-entry 合并回 main。
Milky 是 kind: platform 插件,需要 Hermes Gateway 提供原生平台注册能力和 Gateway 事件循环。请在
当前 profile 的 ~/.hermes/config.yaml 中显式设置:
plugins:
isolation: in_processin_process 表示 Hermes 在自身进程中加载并运行 Milky platform adapter。host 会把普通第三方插件代码
放到独立的 plugin-host 进程;该进程不持有平台注册所需的 live Gateway 对象,因此 Hermes 会将 Milky
平台报告为 unsupported,不注册也不连接 adapter。
Dashboard plugin API 的 host proxy 是另一条路径:它可将 Dashboard HTTP API 请求转交给 plugin-host 中的
插件处理,并按请求指定且由宿主确认的 profile 路由;proxy 会缓冲 HTTP 响应,不支持 streaming 或
websocket API。它不创建 Gateway platform adapter,也不改变 Milky 必须使用 in_process 的要求。
目标 Hermes 0e37a439bda15ef3c28a4d20593964d7c6a527a6 的 Dashboard scanner 当前只扫描根级
plugins/<name>/dashboard/manifest.json,不会发现官方第三方 platform 安装位置
plugins/platforms/<name>/dashboard/manifest.json;因此 Milky 的原生 host proxy 能力在该目标上保持
unknown,临时 flat alias 的诊断通过不作为部署路径。
git clone https://github.com/ByteColtX/hermes-plugin-milky.git
cd hermes-plugin-milky
uv sync源码安装适合开发和调试。拉取新代码后重新同步环境,并重启 Gateway:
git pull
uv sync
hermes gateway restartImportant
启动前先完成下方的 Milky 配置,并将凭证保存到对应 Hermes profile 的 .env 或宿主凭证源。
Gateway 会在该 profile 的作用域读取配置;修改后需要重启 Gateway。
每个 adapter 创建时都从所属 Hermes profile 读取普通设置、兼容配置和凭证。必需的服务地址或 token
缺失时 adapter 不会启动;其他 profile 的进程环境值不会补入当前 profile。MILKY_ACCESS_TOKEN
由 Hermes 的 profile-scoped secret helper 提供,不写入 YAML 示例。当前 profile 未配置的可选设置使用本插件
表格列出的默认值;allowed_chats 默认为空并阻止普通入站消息,session_buffer_size 默认为 20,
群成员即时通知默认为关闭。
贴纸数据库和图片库、Dashboard 维护任务、上传批次都保存在所属 profile 的
plugin-data/hermes-plugin-milky/ 下。切换 Dashboard profile 会切换对应设置、凭证、图库、任务和暂存目录;
工具调用历史也按 profile 单独保存;profile A 与 profile B 不共享这些文件。
当前工作区包含 dashboard/manifest.json、API 和 dashboard/dist/ 预构建资源。
已在固定版本的真实 Hermes 与 Debian 环境验证配置、视觉导入和浏览器维护流程;版本、证据类型及环境限制见
Dashboard evidence。
该历史 Dashboard 证据使用的宿主 core 为 b3a1900e72a16da450ff637aaa37cc23f68992a6,Web SDK 为 1.1.0,
不代表本次目标 Gateway 集成已经通过。当前 manifest 声明的 Hermes 最低版本为 0.21.4;这是版本下限,
不是 commit 锁定,也不代替目标 PluginManager/Gateway 集成验证。本次目标 SHA、fake host 测试、宿主执行状态和
仍 unknown 的边界见 platform contract evidence。
将完整 directory plugin 安装到宿主启用的用户插件位置,
重启 Hermes Dashboard 后打开 Milky QQ;项目本地安装不足以挂载插件 Python API。
Gateway 与 Dashboard 分别拥有连接和任务生命周期,缺少 QQ 地址或凭证也可以访问配置页。
普通配置逐个顶层键按 plugins.entries.hermes-plugin-milky.settings → 旧 config →
当前 profile 的 MILKY_* 环境 → 默认值解析。settings 使用原生类型,Will 使用完整对象选源;
非法高优先级值会报错。地址可放在 YAML,access token 仍由宿主凭证机制管理。
此配置示例只作用于当前 profile;在其他 profile 中单独设置所需值。例如在当前 profile 的现有 plugins
配置中合并:
plugins:
enabled: [hermes-plugin-milky]
entries:
hermes-plugin-milky:
settings:
base_url: http://127.0.0.1:3000
allowed_chats: ["group:123456"]
session_buffer_size: 20
group_member_event_notifications: false页面明确选择 profile;页面所选 profile 的设置与凭证仅写入该 profile,保存普通设置与保持/替换/清除凭证是独立操作。
保存前检查版本、逐字段核验保存结果,但 core 没有跨入口条件事务,不能保证检测全部竞争。
保存成功只表示持久化;Gateway 重启后使用新快照,页面运行态保持 unknown。
托管策略明确拒绝凭证修改时显示 blocked;无法核验写入结果时显示 unknown。两种结果都不回显拒绝正文、
凭证或宿主路径,也不表示运行配置已经应用。
fake host 与 fixture 测试只证明插件边界和本地行为,不等价于目标 Hermes Gateway/host proxy 实测;真实 Milky 服务或 QQ 投递也需要单独的外部 smoke 证据。本次各层结果和阻塞原因记录在上述 evidence 文件中。
图库浏览为只读,预览通过宿主认证按可见 ID 获取。Web 上传与命令 inbox 分开:单图 10 MiB,
每批最多 50 张及 100 MiB,每 profile 暂存配额 500 MiB;无活动引用的上传 7 天后可显式回收。
上传后另行确认导入;失败项可重新明确提交或丢弃批次。编辑、删除、重新分析、清理和索引修复
使用持久任务,每 profile 一个执行批次、最多 10 个排队。任务区分 queued、running、
succeeded、partial、failed、cancelled、interrupted,历史保留 7 天且最多 1000 条。
取消不撤销已提交条目,关闭和恢复不自动重放;过期请求返回 expired。
工具历史页是只读的,使用宿主认证和明确 profile,支持时间、工具、观察状态筛选、稳定分页及同范围统计。
摘要只按各 Tool schema 的白名单保留数值 QQ ID、序号、数量、布尔选项和贴纸 ID;其他参数只显示是否提供。
原始参数、完整结果、错误正文、middleware trace、凭证、URL、路径、媒体引用和文件内容不会进入数据库。
ok、error 等值表示 Hermes 观察到的 Tool 状态,不表示 QQ 远端业务成功;观察 hook 未触发、超时或写入失败时可能漏记。
每个 profile 的历史最多保留 30 天或 10,000 条,超限时清理最早记录;没有数据库的空 profile 浏览不会创建文件。
回滚前停止新维护请求并关闭 Dashboard,保留图库、上传和任务记录。旧插件只读环境配置,
需手动把要保留的普通 settings 转成原 MILKY_* 格式后再回滚,不删除宿主凭证。
后端更新需要重启 Dashboard,刷新页面不能替换已加载 Python 路由。
开发前端使用 npm ci --prefix dashboard、npm run build --prefix dashboard,
npm run check --prefix dashboard 检查源码与预构建产物一致;Python wheel 不包含 directory plugin。
建议将环境变量集中保存到当前 Hermes profile 的 .env:
MILKY_BASE_URL=http://127.0.0.1:3000
MILKY_ACCESS_TOKEN=<从安全凭证存储注入>
MILKY_ALLOWED_CHATS=group:123456789,dm:987654321
MILKY_SESSION_BUFFER_SIZE=20
MILKY_HOME_CHANNEL=group:123456789
# MILKY_MAX_LOCAL_MEDIA_BYTES=33554432
# MILKY_GROUP_MEMBER_EVENT_NOTIFICATIONS=false
# MILKY_WILL_POLICY=<JSON 字符串,见下方 Will policy>上面的 QQ/群号仅为合成示例。.env 只应保存在本机或安全的部署环境中,不要提交到版本库。
| 变量 | 必需 | 作用 |
|---|---|---|
MILKY_BASE_URL |
有效地址必需,环境可选 | Milky 服务基址,也可来自 YAML settings/config;Action 使用 <base>/api/{action},事件流使用 <base>/event。远程部署请使用 HTTPS。 |
MILKY_ACCESS_TOKEN |
是 | 当前 profile 的 Milky access token,只用于 Bearer 认证;通过 Hermes profile-scoped secret source 提供,不写入 YAML。 |
MILKY_ALLOWED_CHATS |
否 | 入站 chat key 白名单,支持具体 group:<群号>/dm:<QQ号> 以及 group:*/dm:*;通配符只匹配对应命名空间,可混用;留空、未配置或原生空列表表示阻止全部普通入站;全部放行需显式填写 group:* 和 dm:*。 |
MILKY_WILL_POLICY |
否 | 决定消息等待(wait)或触发(trigger)的嵌套 JSON 配置。 |
MILKY_SESSION_BUFFER_SIZE |
否 | wait 历史消息上限,默认 20;设为 0 可关闭历史缓冲。 |
MILKY_HOME_CHANNEL |
否 | 系统消息和 cron 的默认目标;不参与入站白名单。 |
MILKY_MAX_LOCAL_MEDIA_BYTES |
否 | 出站本地资源原始字节数上限,默认 33554432(32 MiB),合法范围 8388608(8 MiB)至 33554432(32 MiB)。 |
MILKY_LONG_TEXT_FORWARD_THRESHOLD |
否 | 超长文本合并转发阈值,默认 0(关闭);只接受 0..4096 的十进制整数,只有可见规范化文本长度严格大于正值时才选择一个 forward。 |
MILKY_GROUP_MEMBER_EVENT_NOTIFICATIONS |
否 | 群成员入退群即时通知,默认 false;只接受大小写不敏感的 true/false,只在启动时读取,非法值会阻止启动。开启后追加固定英文 Tip,并通过 Hermes profile-scoped inject_message(origin=...) 提交群来源请求。同步返回 true 只表示已排队;上下文 drain 一次,但不代表 Gateway 已 admission 或 Agent turn 已完成,排队后的异步拒绝也无法由插件恢复。 |
消息 chat key 只接受 group:<十进制群号> 或 dm:<十进制 QQ 号>;白名单另支持完整的
group:* 和 dm:* 规则,temp 会话不会回退到其他目标。
下面的配置让群聊共享 session、在 Agent 忙碌时排队,并减少进度消息。请合并到
~/.hermes/config.yaml,保留已有的其他配置:
启用 MILKY_GROUP_MEMBER_EVENT_NOTIFICATIONS=true 时,还必须保留下面的
plugins.entries.hermes-plugin-milky.allow_gateway_injection: true;这是允许成员事件请求
profile-scoped Gateway injection 的插件级授权。Gateway 仍会按来源 profile、群路由和当前授权同步检查。
# 群友共享同一个 group:<群号> 会话
group_sessions_per_user: false
# 显式指定时区
timezone: Asia/Shanghai
# 长任务跟进与卡住自恢复
agent:
gateway_timeout: 360 # 无 Agent 活动 6 分钟后终止当前 turn
gateway_timeout_warning: 120 # 超时前 2 分钟发出提醒
gateway_auto_continue_freshness: 3600
gateway_notify_interval: 180 # 每 3 分钟发一次“仍在处理”
session_stall_timeout: 120 # 有排队消息且无进展 2 分钟时提醒
local_stream_stale_timeout: 180 # 本地 provider 无实际流内容 3 分钟后重连
api_max_retries: 1 # 减少卡住 provider 的重复等待
# 已确认主模型支持图片输入时,直接以内联图片交给主模型
image_input_mode: native
# 自定义 provider 的模型不会总能从模型目录自动识别视觉能力。
# 请在已有 model 配置中保留其他字段,并追加 supports_vision: true。
model:
supports_vision: true
# 群聊消息不要打断当前任务,排队处理
display:
busy_input_mode: queue
busy_ack_enabled: false
tool_progress_command: false
background_process_notifications: result
memory_notifications: off
platforms:
milky:
thinking_progress: off # 关闭“思考中”状态
tool_progress: off # 关闭工具进度
interim_assistant_messages: false
long_running_notifications: false # 可选:关闭“仍在处理”心跳
show_reasoning: false # 关闭最终回复中的思考摘要
streaming: false # 关闭本插件会话的流式输出
busy_ack_detail: false # 隐藏忙碌提示中的迭代/工具详情
busy_steer_ack_enabled: false
live_status: off # 关闭支持状态文本时的实时状态
# Slash Command 发送者门禁;命令名不带 /
platforms:
milky:
extra:
# 私聊管理员 QQ 号
allow_admin_from:
- "123456789"
# 私聊普通用户允许的只读命令
user_allowed_commands:
- "status"
- "context"
- "agents"
# 群聊管理员 QQ 号
group_allow_admin_from:
- "123456789"
# 群聊普通用户允许的只读命令
group_user_allowed_commands:
- "status"
- "context"
- "agents"
# 成员入退群即时通知需要允许插件按已验证群 origin 请求 Gateway 注入
plugins:
entries:
hermes-plugin-milky:
allow_gateway_injection: true
# 启用后台自动复盘和内置记忆更新
auxiliary:
background_review:
enabled: true
memory:
memory_enabled: true
user_profile_enabled: true
nudge_interval: 20 # 每累计 20 个对话回合,触发一次自动记忆复盘,写入 MEMORY.md / USER.md
# 关闭 Skill 创建建议
skills:
creation_nudge_interval: 0
# /goal 的最大自动续行轮数
goals:
max_turns: 20
# 推荐闲置 2 小时后重置,避免群聊上下文无限变旧
session_reset:
mode: idle
idle_minutes: 120
notify: false管理员可以执行所有已注册命令;普通用户默认可以执行 /help 和 /whoami,以及对应
user_allowed_commands 中列出的命令。私聊和群聊的管理员列表分别配置;某个作用域未配置
对应的 *_allow_admin_from 时,该作用域的 Slash Command 门禁不会启用。
示例不向普通用户开放 /milky。Hermes core 按顶层命令授权,放行 milky 也会放行其子命令;
插件不额外设置子命令权限门禁。
建议只开放明确的只读命令。config、tools、model、sessions、cron、goal、
memory、suggestions 和 skills 等命令包含配置、会话、工具或任务状态变更,不建议加入普通用户白名单。
修改后需要重启 Gateway;配置只在启动时读取。
设置 MILKY_LONG_TEXT_FORWARD_THRESHOLD 为正数后,超出阈值的有序文本/native
image/record/video 批次会通过一次 send_group_message 或
send_private_message 发送,顶层 message 只含一个 forward,节点身份优先使用已确认的
Bot uin/昵称。身份读取失败、昵称为空或含控制字符时固定使用 user_id=10001、
sender_name=QQ用户;成功出参使用单一 data.message_seq。配置为 0 或文本长度不超过阈值时,
继续使用普通分块和 [SPLIT] 三条预检。文档/文件不是该自动 forward 的目标,继续走独立
file upload;Hermes 若只提供分离的 MEDIA: 调用,插件不会猜测其与文本属于同一批次。
需要回滚时删除该变量或改回 0,然后重启 Gateway。本文不宣称 Milky 服务端对 forward
总大小或节点数量的未验证上限。
group_sessions_per_user: false 会让群友共享同一个 Hermes session;这适合群聊,但也意味着
群内消息会共同影响上下文。busy_input_mode: queue 让 Hermes 负责 queue、follow-up、
pending 和 interrupt/steer,插件不复制 Agent 执行队列。
Tip
平台显示设置必须放在 display.platforms.milky 下,不要放到全局 display 下。
如果不希望在 QQ 中看到自动压缩过程中的 413/compression 提示,可通过 CLI 关闭:
hermes config set compression.progress_notices false
hermes gateway restart该配置只隐藏常规压缩进度提示;压缩最终失败时的错误提示仍会保留。
image_input_mode: native 与 model.supports_vision: true 只应配置在已确认支持
OpenAI-compatible image_url 输入的主模型上。如果接口不支持原生图片输入,请移除这两项并
使用文本视觉路径。修改后重启 Gateway;日志应出现 Image routing: native。
建议使用 Hermes 的 holographic 记忆后端:
hermes config set memory.provider holographic它适合本地部署:数据保存在本地,不依赖付费云服务,并支持围绕实体召回长期上下文。修改后 重启 Gateway 使配置生效。
查看本地记忆库中的事实记录:
sqlite3 -header -column ~/.hermes/memory_store.db "SELECT fact_id, content, category, tags, trust_score, updated_at FROM facts ORDER BY fact_id;"Will 决定一条消息是先等待,还是交给 Hermes:
wait:放入当前 chat 的有界缓冲,暂不启动 Agent;trigger:先取出该 chat 的等待历史,再把当前消息交给 Hermes。
MILKY_WILL_POLICY.engine 只选择一套引擎。routing 和 willingness 共用消息特征、都输出
wait/trigger,但不会叠加运行。
三类关键词(routing.keywords、willingness.forceKeywords 和
willingness.interestKeywords)只匹配当前消息顶层的 text、markdown 内容,按连续文本直接
做子串匹配。结构化 @ 的显示名称、回退 QQ 号和“全体成员”展示文字不参与关键词匹配,
也不会带来兴趣关键词倍率;直接 @Bot 的独立触发与提及增益仍按对应配置生效。
用户在普通文本里手动输入 @提醒小助手 时,“提醒”仍可命中。相邻 text、markdown 可以
组成关键词,但中间有 @、图片等非文本片段时不会跨过它拼接,例如“提”+@某人+“醒”
不会命中“提醒”。未命中兴趣关键词时仍使用 defaultMultiplier 计算原有基础和属性增益。
| 引擎 | 决策方式 | 适合场景 |
|---|---|---|
routing(默认) |
当前消息命中规则就触发,结果确定 | 希望行为可预测、方便排查 |
willingness |
按 chat 维护分数,再按概率抽样 | 希望机器人偶尔参与、减少刷屏 |
routing 只看当前消息:不维护分数、不使用随机数。direct 等规则字段的值只能是 wait 或
trigger;keywords 不填写动作,命中时固定为 trigger。
| 字段 | 命中条件 | 默认行为 |
|---|---|---|
direct |
friend 私聊 | trigger |
mention |
直接 @Bot(只认 mention.user_id == self_id) |
trigger |
mentionAll |
@全体成员 | wait |
quote |
回复 Bot 的消息(只认 reply.data.sender_id == self_id) |
wait |
poke |
协议明确指向 Bot 的 poke | wait |
allMessage |
每条普通 friend/group 消息 | wait |
keywords |
正文包含任意一个非空关键词 | 空数组(不命中) |
一条消息可以同时命中多条规则;结果按 OR 合并,任一规则为 trigger 就触发,不会被其他
wait 抵消。图片没有独立 routing 规则,单独出现时仍由 allMessage 决定;
friend_nudge 和 group_nudge 在普通消息流程中保持 observe-only。
示例:只有私聊、@Bot 或包含“提醒”的消息进入 Hermes,其余消息等待:
{
"engine": "routing",
"routing": {
"allMessage": "wait",
"direct": "trigger",
"mention": "trigger",
"keywords": ["提醒"]
}
}willingness 不把每个信号直接设成 wait/trigger,而是为每个 chat 单独维护一个分数。
普通消息大致经过以下步骤:
- 先让分数按静默时间衰减;
- 根据文本、提及、reply、图片、私聊等特征增加分数;
- 命中
willingness.interestKeywords时提高本次增益倍率; - 命中
willingness.forceKeywords时直接得到trigger,否则在分数超过probabilityThreshold后换算成概率并抽样,得到wait或trigger。
因此,同一条消息可能因为当前分数或随机抽样不同而得到不同结果。willingness.interestKeywords
只控制加分倍率,不是确定性触发器;willingness.forceKeywords 才是包含即触发的
确定性规则。
directForce、mentionForce、quoteForce 可让对应信号跳过随机抽样,直接 trigger。
其中 mentionForce 只匹配直接提及当前 Bot(mention.user_id == self_id),quoteForce 只匹配
至少一个明确引用当前 Bot 的 reply(reply.data.sender_id == self_id);他人提及、mention_all、
here、他人引用和无法确认目标的引用都会继续走其他 force 条件或概率抽样。
forceKeywords 与这些 force 字段等价地跳过随机抽样,但不额外增加 score;两类关键词同时
命中时,interestKeywords 仍控制增益倍率,forceKeywords 决定最终触发。
mentionGain 只在直接提及当前 Bot 时加分;quoteGain 只在至少一个 reply 明确引用当前 Bot
时加分,has_reply 只保留 reply 存在性事实,不会单独产生 quoteGain;pokeGain 只在协议
确认 Bot 为接收者的 self-poke 时加分。非 Bot 或无法确认目标的 mention、reply、poke 均不产生
对应 gain。routing 的 quote 规则同样只认引用 Bot;friend_nudge 和 group_nudge 仍是
observe-only,不会直接创建 Agent turn。通过 Gate 且得到 trigger 后立即扣除一次
replyCost 参与成本,不等待 Hermes 接受、资源解析或最终发送;后续失败不回滚。等待、Gate
拒绝、命令、temp 和系统事件不会扣费。
示例:默认按概率参与,命中“提醒”时提高增益,命中“紧急”时直接触发:
{
"engine": "willingness",
"willingness": {
"interestKeywords": ["提醒"],
"forceKeywords": ["紧急"],
"keywordMultiplier": 1.2,
"directForce": false,
"mentionForce": false,
"quoteForce": false
}
}迁移时将旧的 willingness.keywords 改为 willingness.interestKeywords;旧字段不会被静默
兼容。需要包含即触发时再配置 willingness.forceKeywords,省略或配置为空数组表示关闭。
两者都只匹配规范化正文的直接子串,不支持正则、分词或隐式大小写转换。
- 不确定时使用默认的
routing;它最容易预测和调试; - 想切换到概率决策时,将
engine改为willingness,并配置willingness对象; - 完整配置可以同时保留两套参数,但运行时只使用
engine选中的一套; priority当前只保留在配置 schema 中,不参与 routing 优先级或 willingness 权重计算;- 旧的扁平字段、
routing.group、routing.image和routing.mentionHere不会被静默转换, 启动时会直接拒绝。
完整默认配置示例(包含两套引擎参数,默认折叠):
展开完整默认配置
{
"engine": "routing",
"routing": {
"direct": "trigger",
"mention": "trigger",
"mentionAll": "wait",
"quote": "wait",
"poke": "wait",
"allMessage": "wait",
"keywords": []
},
"willingness": {
"maxScore": 100,
"initialScore": 0,
"decayHalfLifeSeconds": 600,
"probabilityThreshold": 55,
"probabilityAmplifier": 0.04,
"replyCost": 35,
"textGain": 12,
"mentionGain": 100,
"quoteGain": 15,
"directGain": 40,
"imageGain": 8,
"pokeGain": 80,
"interestKeywords": [],
"forceKeywords": [],
"keywordMultiplier": 1.2,
"defaultMultiplier": 1,
"hotWindowSeconds": 15,
"warmWindowSeconds": 60,
"hotDecayWeight": 0.3,
"warmDecayWeight": 0.7,
"mentionForce": false,
"quoteForce": false,
"directForce": false
},
"priority": 1000
}未列出的字段使用插件默认值。配置写入 MILKY_WILL_POLICY 时必须是 JSON 字符串。
MILKY_HOME_CHANNEL 只影响 Hermes 系统消息和 cron 的默认出站目标,不参与入站 allowlist。
显式的 milky:group:<id> 或 milky:dm:<id> 目标优先。
未配置 home channel 时不会回退到 origin、默认频道、群聊或私聊,也不会猜测目标。standalone
cron 每次创建并关闭临时 Milky client,目前只支持无附件文本;媒体和文件输入返回
unsupported。
运行时日志使用 hermes_plugins.milky.* 标准 logger,并传播到 Hermes root。关键消息使用
event=milky.lifecycle、milky.action、milky.sse、milky.inbound、milky.resource、
milky.outbound、milky.mute 或 milky.tool 标签;插件不创建独立日志文件、handler、异步队列
或脱敏后端。
常用查看命令:
hermes logs -f
hermes logs --level DEBUG -f
hermes logs gateway -fAction、Tool 和出站日志保留结果分类、已知状态码和 duration_ms,不记录 token、Authorization、
完整 URL、请求/响应 body、消息正文、媒体引用、路径、文件内容、Tool 原始参数或结果。Tool 日志
使用 delivered 表示已取得远端响应;协议拒绝和 HTTP 错误也使用该分类并附已知状态码,只有
invalid_input、unsupported 和 transport_unknown 是插件本地 Tool 结果分类。日志不可用或
handler 失败不改变连接、重连、Gate/Will、扣费、发送和未知结果语义。
协议 raw 保真与日志/输出最小化是两条独立边界:parser、入站规范化和 canonical 会保留远端已收到的
字段和值,包括名称看起来像 token、authorization、password 或 cookie 的业务扩展;这些 raw
字段不会因此进入正文、关键词、会话介绍、隐式工具调用或授权判断。Hermes logger 只接收插件选择的
固定低敏元数据;Tool 结果、模型上下文和 session 持久化属于不同的宿主出口,插件不假设宿主会在每个
出口自动清洗秘密,也不在插件日志中复制 raw。当前仓库的 fake host/fixture 证据不能替代真实 Hermes
logger、Tool 结果后处理、模型上下文和 session 落盘验证。
# 查看 Gateway 状态
hermes gateway status
# 重启 Gateway,使启动配置生效
hermes gateway restart
# 执行 Hermes 整体状态和深度健康检查
hermes status --deep
hermes doctor# 查看最近的 Gateway 日志和错误
hermes logs gateway -n 100
hermes logs errors --since 30m
# 临时提高日志级别并实时跟踪
hermes logs gateway --level DEBUG --since 15m -f
# 列出日志文件,以及检查配置文件位置和有效性
hermes logs list
hermes config path
hermes config env-path
hermes config check
hermes config get security.redact_secrets# 查看已安装插件及启用状态
hermes plugins list
# 在源码 checkout 中执行只读 Milky smoke
uv run scripts/milky_smoke.py
# 生成用于提交 issue 的脱敏诊断摘要
hermes dumpmilky_smoke.py 默认只执行登录、群列表、Bot 成员禁言同步和有界 SSE 连接。发送消息或上传文件
必须显式使用 --allow-write,并且目标还必须位于 MILKY_ALLOWED_CHATS 中;没有明确授权时不要
使用该选项。hermes dump 用于生成脱敏诊断摘要,不要把包含敏感配置的命令输出直接粘贴到公开 issue。
Hermes core 默认会对日志中的敏感字段进行脱敏。如果需要临时确认日志中的完整 chat_key,可用
Hermes CLI 关闭全局脱敏:
hermes config set security.redact_secrets false修改后必须重启 Gateway;该配置只在进程启动时读取。排查完成后立即恢复脱敏:
hermes config set security.redact_secrets true该开关影响 Hermes 全局的日志、工具输出和聊天响应,不只影响 Milky。关闭期间可能泄露 API key、 token 或密码,仅应在受控环境中短时使用。
普通入站只处理 message_receive:
- friend 和 group 消息进入普通 Agent 流程;
temp会话直接忽略;- Milky SSE
GET /event中的message_recall、request、notice、lifecycle 和未知事件默认只观察,少数系统事件可作为上下文; facesegment 的正文占位符对非emoji 表情pack 优先使用随插件发布的本地 catalog 名称;未命中、冲突或目录不可用时回退原face_id,缺失 ID 时使用NOT SUPPORTED;- 同一 chat 按顺序处理,
wait消息进入有界历史,trigger时再交给 Hermes。
入站 video segment 在正文中显示为
[video:resource_id=<resource_id>,duration=<duration>];字段缺失时单独使用 NOT SUPPORTED,不展示
Milky temp_url。普通 wait 和 trigger 流程都只保留视频引用,不自动查询链接或交给媒体 materializer。
Agent 可显式调用 get_resource_temp_url 获取临时链接;该工具只返回远端响应,不下载、解码、抽帧、转录或分析视频。
获得临时链接不表示 Hermes 或当前模型能够理解视频内容。
支持 system prompt section 的 Hermes 宿主会额外注册
hermes-plugin-milky.qq-session-context。合法 friend 介绍只包含 user_id、nickname、
sex;合法 group 介绍只包含 group_id、group_name、member_count、description、
announcement。快照在资源解析、MessageEvent mapper 成功后、Hermes handle_message() 前登记,
以 dm:<id>/group:<id> 隔离并使用有界进程内缓存;不同 Hermes user session 可以共享同一个 group
介绍。介绍不会写入当前消息正文、历史 channel_context、platform hint 或出站正文。
昵称、群名、描述和公告按不可信 metadata 处理:控制字符和换行会被中和并限制长度,协议 raw、未知 扩展、凭证字段、媒体 URL、文件路径和敏感正文不会渲染到会话介绍。Gate deny、wait、temp、系统事件、重复消息或 资源/mapper 失败不登记介绍;缓存淘汰和资料缺失安全返回空 section。Hermes 已持久化 prompt 恢复 时保留原介绍字节,显式 prompt rebuild 才使用当前本地快照;插件不提供实时刷新。
message_recall 的上下文行为如下:
- 只有字段完整且
message_scene为friend或group时才登记;friend 写入dm:<peer_id>,group 写入group:<peer_id>,非法场景或 ID 只记录安全诊断; - 合法事件进入对应 chat 的有界 system context FIFO,在下一次同 chat
trigger的channel_context中按 ingress 顺序出现一次,格式为<event message_recall> ...; - 无
operator_id或operator_id == sender_id时显示uid <sender_id> recalled message msg_seq <message_seq>;群聊仅在operator_id != sender_id时显示Admin uid <operator_id> recalled uid <sender_id>'s message msg_seq <message_seq>,好友有不同操作人时不添加管理员角色; - 撤回事件不创建普通 Agent turn、不发送回复、不调用主动撤回工具,也不调用
get_message或下载资源;插件只展示撤回元数据,不承诺恢复被撤回消息正文; - 该路径仍是 observe-only,不经过普通消息的 Gate/Will,也不扣 reply cost。fixture 和 fake host 测试不代表真实 Milky 服务端能力已被集成验证。
group_nudge 和 friend_nudge 也只进入对应 chat 的 system context,固定英文 body 分别为
uid <sender_id> poked uid <receiver_id> 和 uid <user_id> poked once。成员事件使用以下基础
英文 body,并由 renderer 统一添加 <event group_member_increase> 或
<event group_member_decrease> 前缀:
uid <user_id> joined the group. Details: {"group_id": ..., "user_id": ..., "operator_id": ..., "invitor_id": ...}uid <user_id> left the group. Details: {"group_id": ..., "user_id": ..., "operator_id": ...}
缺失或为 null 的 operator_id/invitor_id 会从 Details 省略;display text、URL、timestamp、raw
扩展和撤回正文不会进入 body。默认 MILKY_GROUP_MEMBER_EVENT_NOTIFICATIONS=false 时,成员事件不
带 Tip、不即时触发 Agent,保留在 system context 等下一次普通消息。设置为大小写不敏感的 true
后,成员 body 末尾追加固定英文 Tip,并只通过 Hermes 公开的 inject_message(origin=...) 交接;origin
只含事件已确认的平台、群 chat key 和群类型,不拼造 Gateway session key。profile 注入权限、Gateway
运行状态、来源授权及当前 adapter 路由由宿主同步检查。同步拒绝、缺少来源或宿主不可用时,带 Tip 的上下文保留;
同步排队成功后上下文 drain 一次并记为 scheduled,不代表 Gateway ingress admission 或 Agent turn 成功,
异步拒绝不会回传给插件,也不保证已 drain 的上下文可恢复。此路径不经过普通消息 Gate/Will,不扣 reply cost,
不直接调用 Milky Action。配置值在启动后不热切换,修改后需要重启 Gateway。
Agent 发送本地媒体时,在回复中写入:
MEDIA:<local_path>
例如 MEDIA:~/path/to/clip.mp4。显式调用 Hermes send_message 时,把同一指令放在
message 参数中。图片、语音和视频使用 Milky native segment,文档使用独立 file upload。
如果无需回复,只返回 [SILENT],不附加其他内容;该标记由 Hermes core 按当前三态
reply_expected 处理。确定不需要回复时 core 保持静默;预期回复或状态 unknown 时 core 会生成固定
可见兜底,但 Milky 会在 QQ 发送前精确过滤这段文案。因此 QQ 可能没有消息,即使 Hermes 仍将发送结果
记为成功。插件不单独解析 [SILENT]。
需要模拟自然聊天节奏时,可把区分大小写且未转义的 [SPLIT] 单独放在一行,或直接放在普通
正文行中。独立行标记及其分隔边界会被删除,行中标记只删除自身;空段不发送,文本按原顺序
最多发送三条。超过三段时尾部合并到第三段,每个文本单元仍遵守既有长度边界,若实际文本消息
会超过三条,则在网络访问前整体拒绝。需要显示字面量 [SPLIT] 时使用 [[SPLIT]];语法完整的
CQ-compatible 或 unknown type CQ 候选中的标记不触发分段;候选的 CQ 前缀大小写不敏感,但
malformed 或未闭合 CQ-like 内容中的标记按普通文本规则处理。普通长文本没有有效 [SPLIT] 时
继续使用原有长度分块。
回复同时包含文本分段和 MEDIA: 附件时,Hermes 先投递全部文本,再按提取顺序投递图片、语音、
视频和文档;当前不支持文本段与附件交错,[SPLIT] 不改变 MEDIA: 的独立交接。
Caution
MEDIA: 会读取本地文件并上传;默认只限制常规、非空且不超过 33554432 字节(32 MiB)
的文件,可用 MILKY_MAX_LOCAL_MEDIA_BYTES 在 8388608 至 33554432 字节之间调整,
没有固定的安全目录隔离。Base64 编码会带来约 4/3 的请求体放大;内网连接不代表
Milky、代理或下游平台没有更低的服务端限制。
CQ image 仅用于本地 file:// URI 的 sticker,例如:
[CQ:image,file=file:///path/to/sticker.ext,type=sticker]
CQ 前缀大小写不敏感,但 type、字段和真实 ID 仍按既有规则校验;未知或转换失败的 CQ
控制码保留完整原文。普通图片请使用 MEDIA:<local_path>。sticker 会在发送前转换为 base64://。
本地路径、Path 和 file://localhost 只在 plugin 边界读取一次并受上述本地字节上限约束;
格式合法的 http(s):// 和显式 base64:// 会原样传递,plugin 不下载、读取或解码,也不应用
本地文件大小检查。
纯文本 /... 消息会在 canonical、去重和 Gate 之后分流,不进入 Will 历史或普通 Agent 正文。
合法命令交给 Hermes 既有命令分发。无参数 /milky 继续查询实现信息,
/milky help 显示顶层帮助;/milky sticker 与 /milky sticker help 等价,
/milky allowlist 与 /milky allowlist help 等价。三层帮助均为静态文本,
不要求活动连接,也不会读取配置、创建图库或执行视觉分析;可达性仍由会话 Gate 和 core 授权决定。
Milky · 命令帮助
Usage:
/milky [command] [args...]
不带参数时查看实现信息。
Commands:
status 查看运行状态
sticker 维护贴纸库
allowlist 管理会话白名单
help 显示帮助
子命令帮助: /milky <command> help(sticker、allowlist)
/milky status 只读取当前可信实例的本地状态及同一 profile 的最新白名单配置,
不会探测网络、查询 QQ 登录状态、打开图库或应用配置。例如:
Milky · 运行状态
插件: 运行中
事件流: 已连接
本次运行: 2 小时 18 分钟
运行白名单: 6 条规则
白名单配置与当前运行一致。
首次 SSE 建连时显示“事件流: 连接中”,首次失败后的退避、重试和内部重连显示 “事件流: 重连中”。本地观察到连接建立后才显示“已连接”,这一状态不保证端到端收发健康。 “本次运行”从实例完成初始化、进入运行阶段开始,按单调时间累计;SSE 内部重连不重置, 停止或失败后冻结,新运行代次重新计时。少于一分钟显示“不足 1 分钟”,其余舍去秒, 按天、小时、分钟显示非零部分;确认尚未开始时显示“尚未开始”,缺少证据时显示“未知”。
运行白名单统计字面规则,一个通配符计一条。Web 保存与运行集合不同,即使条数相等,也显示:
白名单配置与当前运行不同。
请重启 Gateway 以应用当前配置。
配置读取失败、非法或规则版本变化时显示“白名单配置一致性未知。”,保留其他可确认字段。 缺少事件流观察能力时显示“事件流: 未知”。无唯一可信实例、profile 归属失效或读取期间运行 代次变化时,整条回执为“运行状态暂不可用”及安全说明,不猜选其他 profile。 空运行集合显示 0 条,并说明“当前不接收任何会话的普通消息。”,不等同于插件停止。
贴纸只处理显式人工维护;remove 是 del 的等价别名,例如 /milky sticker remove demo_id。 实际贴纸帮助如下:
Milky · 贴纸维护
Usage:
/milky sticker [command] [args...]
Commands:
add [--dry-run] 导入图片
list [--limit <n>] 查看贴纸
edit <sticker_id> [--emotion=<enum>] [--tags=<tag1>,<tag2>,...] [--description=<text>] [--clear=<field>[,<field>...]] 编辑字段
reanalyze <sticker_id> 重新分析
del <sticker_id> 移除贴纸(别名 remove)
cleanup [--dry-run] 清理库文件
reindex 重建技术索引
help 显示帮助
Options:
--dry-run 仅适用于 add/cleanup;只预览,未作更改。
--limit <n> 默认 20,十进制整数 1 至 100;也接受 --limit=<n>。
--emotion=<enum> joy、sadness、anger、surprise、fear、disgust、love、approval、confusion、neutral、mixed、unknown
--tags=<tag1>,<tag2>,... 2 至 5 个不重复标签,每个不超过 16 字符且含中文。
--description=<text> 非空、不超过 20 字符且含中文,不跨空白 token;不支持 shell 引号语法。
--clear=<field>[,<field>...] 恢复 emotion、tags、description 对应视觉基线。
edit 至少指定一个 set/clear;同一字段不能同时设置和清除,不接受重复选项或清除字段。
Examples:
/milky sticker add --dry-run
/milky sticker edit demo_id --clear=tags
不带子命令时显示帮助。
非法参数在业务操作前返回就近 Usage 和 Help,不回显输入。例如删除缺少 ID:
指令格式不正确。
Usage:
/milky sticker del <sticker_id>
Help:
/milky sticker help
查询使用“Milky · 贴纸维护”标题及“本次显示 N 条”,数量表示当前有界返回数。 空库示例为“尚未添加贴纸。”和“本次显示 0 条”,附贴纸帮助入口;非空列表保留 ID、 情绪中文含义及枚举、标签、描述、字段来源、格式、大小、创建时间、使用次数和最近使用 UTC 时间。 尚未使用时明确显示“尚未使用”。
预览使用“贴纸导入预览”或“贴纸清理预览”标题,计划结果显示“待添加”“待隔离”或 “待清理”,末尾明确“未作更改。”;导入预览逐候选保留情绪、标签、描述和贴纸判定, 不虚构 ID 或显示原文件名。正式批次分别报告添加、重复、隔离、拒绝、失败和延后计数, 有失败或延后时明确未完成项;空 inbox 显示“没有待处理的图片。”。 清理和索引重建分别保留各自计数单位,不声称缺失文件已恢复。
下列列表与导入预览使用合成条目,文本由实际展示入口生成:
Milky · 贴纸维护
本次显示 1 条
ID: demo_id
情绪: 喜悦 (joy)
标签: 开心、反应
描述: 小图表达情绪
来源: 人工 (manual)
字段来源: 情绪=视觉 (vision)、标签=人工 (manual)、描述=视觉 (vision)
格式: PNG
大小: 68 字节
创建时间: 2026-01-01T00:00:00+00:00 UTC
使用次数: 0
最近使用: 尚未使用
贴纸导入预览
待添加: 1
待隔离: 0
重复图片: 0
拒绝图片: 0
视觉分析失败: 0
存储失败: 0
延后处理: 0
候选 1: 待添加
贴纸判定: 是
情绪: 喜悦 (joy)
标签: 开心、反应
描述: 小图表达情绪
未作更改。
slash 回执已从贴纸 JSON、实现信息英文错误前缀和旧标题改为中文纯文本;allowlist 帮助也改为 紧凑布局。这是展示兼容性变化,程序消费者继续使用原有结构化维护结果、Web JSON 和 Agent Tool 字段与分类,不应解析中文回执。长帮助、列表和批次继续由既有发送流程分块或合并转发,发送失败 不会重复执行维护操作。
首次有效维护命令才会在 Hermes plugin-data 下创建 stickers/inbox/、stickers/library/、
stickers/junk/ 和独立 stickers.db。add 递归扫描 inbox,只接受 PNG、JPEG、GIF、WebP,单文件
上限为 10 MiB,按流式 SHA-256 去重;视觉辅助只在显式 add、add --dry-run 或 reanalyze
中调用。每次 add 最多处理 50 张唯一候选,同时最多 10 个视觉调用;其余候选留在 inbox 并报告
batch_deferred。合法 is_sticker=true 原文件原子移动到 library,false 原文件移动到 junk,
视觉失败或结构非法留在 inbox。dry-run 只校验、去重、分析和预览,不移动文件或写入数据库。
库条目保存 detected_* 视觉基线、当前生效 emotion/tags/description、字段级 vision/manual
来源、created_at/updated_at/detected_at、use_count 和 UTC last_used_at。edit 只更新指定
字段;--clear 恢复对应视觉基线。reanalyze 只刷新视觉基线,人工字段保持不变;返回 false 时
报告 not_sticker 并保留原条目。list 输出受限摘要,不输出路径、URL、原文件名或图片 bytes。
cleanup 不扫描或删除 junk;reindex 只重建 library 的 sticker_files 技术索引,不创建贴纸条目。
贴纸 command handler 只收到 raw_args,不推断 Milky friend/group 或操作者身份,也不增加
MILKY_STICKER_OPERATOR_IDS 等插件授权配置。贴纸维护不创建旁路 Milky client、Agent Tool、主 Agent
transcript、普通消息 handoff 或脱离命令生命周期的后台视觉任务。
sticker_search 和 sticker_send 是独立的语义 Tool,不是任意 Milky Action。插件通过 plugin.yaml 的
python_dependencies 和项目运行时依赖声明提供 Pillow>=12.3.0、jieba>=0.42.1;两个工具只在库中有可用条目时进入
Agent definitions,空库时隐藏,不按需导入或检查可选 tokenizer,也不影响维护命令和其他 Tool。sticker_search 只接受
mode、intent、emotion、tags 和 1..10 的 limit(默认 5)。有查询字段时默认 strict,空对象或只有 limit
默认 browse;显式 null、未知字段和归一化后重复标签均拒绝。候选结果只含 status、match_mode、items,
每项只含 sticker_id、emotion、tags、description。
strict至少有一个查询字段,沿用完整短语/全部 token/部分 token 固定相关性层级,并列按 ID 排序;零命中返回空 items,不自动兜底。fallback必须显式指定,并同时提供 intent 与 emotion 或 tags;忽略 intent,保留 emotion 精确条件及 tags 至少命中一个的条件,按 tag 命中数降序、ID 升序返回。browse禁止查询字段,只按 ID 升序列出当前可用条目,不代表与聊天内容相关。
所有模式只返回当前可见、索引有效且文件可用的条目,不返回路径、URL、hash、图片、统计或匹配解释。
sticker_send 接受互斥的查询或 1..128 字符的
opaque sticker_id,目标来自当前 task-local HERMES_SESSION_PLATFORM=milky 和 HERMES_SESSION_CHAT_ID,不要求先搜索。
搜索只读、不改变使用统计或轮换状态;精确发送跳过匹配和轮换但重新校验当前条目、文件和双索引 SHA-256,单次调用只发一张
sub_type=sticker 图片。结果使用 ok、sent、no_match、not_found、invalid_input、missing_session_context、
unsupported、missing_file、storage_error、rejected、http_error、malformed 和 transport_unknown 等固定分类。
严格查询发送返回 no_match 时,回执带 alternatives:原查询含 intent 与 emotion 或 tags 时,
按 fallback 规则提供最多 5 项,否则为空。返回备选不发送消息、不更新统计;按备选 ID 发送需单独调用
sticker_send(sticker_id)。http_error、malformed、transport_unknown 不保证消息未发送,插件不自动重试。
元数据是不可信数据,不执行描述中的指令,也不注入平台提示。
插件和 bundled skill 仅提供通用接口说明,不规定贴纸使用时机、调用顺序、搜索次数或聊天风格。 用户可在自己的 SOUL 或 memory 中配置个性化使用策略。
manifest 和 outbound.tools 固定声明 26 个与 Milky operationId 对齐的 QQ Action ToolSpec,另有受限的语义 sticker_search 和 sticker_send:
- 群组和成员查询;
- 媒体临时链接、文件、转发消息和私聊文件链接查询;
- 戳一戳、点赞、撤回、禁言、踢人、退群和删好友;
- 好友请求、入群请求和群邀请的接受/拒绝。
上述工具需要由 Hermes 宿主加载当前根入口或新版独立 tools.py;当前新版独立 loader 仍受目标宿主问题阻塞。工具可用时,请求/邀请的接受和拒绝不会由通知、普通正文、关键词或 Will 自动触发,必须由 Agent 显式提供
完整参数。26 个 Action Tool 只要取得响应体就把 UTF-8 解码后的字符串原样交给 Hermes core:
不校验 HTTP 状态、status/retcode、data 结构,不重建 envelope,不附加状态码,也不脱敏
access_token、authorization、cookie、password、token(任意大小写)字段。无法按 UTF-8
解码的字节使用替换字符。参数非法、Tool 不支持或未取得响应体时分别返回 invalid_input、
unsupported 或 transport_unknown;有副作用的调用最多提交一次且不自动重试。
get_resource_temp_url 只接受非空 resource_id,仅在 Agent 显式调用时请求对应 Action。工具结果只交付给该
Tool 调用,不自动进入普通入站正文或 media_urls;插件不下载、缓存或分析链接对应的视频。
Tool 结果进入 Hermes core 后,宿主可能运行 transform_tool_result、截断 JSON error 字段,或
把超长结果落盘并以预览替换上下文内容。这些后置处理由宿主负责,插件不注册、不规避,也不承诺
最终进入模型上下文的内容与 Milky body 一致。原样交付可能使上述五类字段进入宿主转录或落盘,
上下文策略由宿主负责。
两个贴纸工具不属于上述 Action catalog;它们不接受 chat_id、session_id、路径或 URL,每次调用独立校验参数和当前会话。
sticker_search 的结果只含有界元数据;sticker_send 的 target、库读取、统计 claim 和单次发送由独立的贴纸 service 管理。
入站文件只显示为安全占位符,例如
[file:file_id=<file_id>,file_name=<file_name>,file_hash=<file_hash>];它不会被当作本地路径
或出站文件。
连接时依次完成登录信息、群列表和每个群的 Bot 成员状态同步,之后才启动事件流并开放普通 消息入口。断开时会取消 event、pipeline、TTL 任务,解除 sender/command 绑定,并关闭 HTTP/SSE 资源。
非 Tool 出站成功在插件侧使用远端 data.message_seq 的稳定字符串作为 message_seq,交给 Hermes 时映射为宿主要求的 message_id;发送响应即使协议状态异常,
只要仍含可验证的远端 message_seq,就归一化为已确认发送并保留该序号。没有该证据的协议拒绝、传输未知、malformed 和 unsupported 会保持明确失败分类;缺少消息序号时不会伪造稳定去重 ID。
纯文本命令通过 Hermes core 授权后执行;顶层 milky 许可覆盖 allowlist 的全部子命令。 插件不另查管理员或 QQ 角色。普通用户命令白名单示例不包含 milky;未配置管理员时是否关闭 门禁也服从 core。未放行会话可以发送下面的直接管理命令,其他命令仍需通过会话 Gate。 结构化 mention、图片、未知子命令和其他命令展开的别名不享有路由例外;已放行会话的 core 别名正常执行。
帮助使用紧凑的 Usage 与 Commands,不另列 Targets 或 Examples:
Milky · 会话白名单
Usage:
/milky allowlist [command] [args...]
Commands:
list 查看名单
add [target] 添加规则
del [target] 移除规则(别名 remove)
help 显示帮助
e.g. target: group:123456、dm:654321、group:*、dm:*
add/del 省略目标时使用当前会话。
不带子命令时显示帮助。
- /milky allowlist 或 /milky allowlist help:显示静态帮助;Commands 末尾的一行 e.g. 给出 add/del 共用目标,不读取或修改配置。
- /milky allowlist list:完整查看配置及运行规则、来源和差异,不支持分页;长消息由既有发送流程合并转发或拆分。
- /milky allowlist add:添加当前群或当前私聊。
- /milky allowlist add dm:123456:添加显式目标;同样支持 group:123456、group:、dm:。
- /milky allowlist del:删除当前会话的字面条目;remove 与 del 完全等价。
- /milky allowlist del group:*:只删除该通配符,保留其他具体条目。
目标管理范围是当前 profile 的 group/dm 规则,裸数字和 temp 被拒绝。具体条目与通配符独立, 添加具体条目不会因已有通配符返回 unchanged;删除具体条目不会修改通配符。回执只报告条目结果。 管理命令不查询来源群或目标群状态,不以禁言状态阻止名单读写,也不附加禁言提示。 新授权群首次收到获准入站的消息时按需准备成员状态;状态未知、准备失败或禁言仍拒绝普通消息。 回执沿既有发送流程处理,是否送达不改变已提交的规则。
QQ 修改只保存 plugins.entries.hermes-plugin-milky.settings.allowed_chats,读回确认后在线发布。 settings 空列表遮蔽环境值;纯环境部署首次实际修改提升为 settings。回执使用中文结果: “更改已保存并生效”表示已持久化并在线应用;“更改已保存,尚未生效”提示重启 Gateway; “规则已存在/规则不存在”表示未作更改,不隐式 reload。无法确认的结果不会报告成功或自动重试。 缺少可信调用来源、明确 profile 或唯一活动实例时提示管理暂不可用,绝不使用环境中的旧会话。 list 保留 settings、legacy、environment、default 来源名称;一致时只显示一份完整名单, 不一致时分别显示“当前配置”和“当前运行”,提示重启 Gateway。Web 仍只保存配置,不新增重启按钮。
实例内修改串行,保存前版本检查及读回核验不等于与 Web/人工编辑的跨入口条件事务。 未知结果不自动重试或回滚;回执发送失败不重放修改。其他配置保持启动快照。 完整断开重连(含原注册工厂新建实例)重新读取当前 profile 白名单;纯 SSE 重连保留在线策略。 实际撤销会清理插件尚未交接的等待正文、系统上下文和 Will 状态,删除再添加不复活旧批次; 已交给 Hermes 的任务、出站工具、cron 和 home channel 不被撤销。
BREAKING 迁移与回滚: 升级前,需要全放行的部署将旧空配置改为两个通配符。 旧版本把空列表解释为全部放行,回滚前先停止接收并设置经审阅的非空受限名单; 若需全部关闭,在宿主停用平台。只回滚代码并保留空列表会扩大权限。 真实 QQ 验收范围与当前验证结果见 openspec/changes/archive/2026-09-26-add-hot-chat-allowlist/evidence.md。
插件不是以 Python package entry point 发布;Hermes 从根目录加载 plugin.yaml,再发现平台注册入口
__init__.py::register(ctx) 和 Dashboard 管理入口 dashboard/plugin_api.py。新版 manifest client tools
入口 tools.py::register_tools(ctx) 目前位于 codex/milky-tools-entry 分支。
| 对象 | 作用 |
|---|---|
__init__.py::register(ctx) |
当前 main 注册 platform、/milky、skills、standalone sender、QQ 指引 section、QQ 会话介绍 section,并调用 outbound.tools::register_tools(ctx) 尝试登记固定 Agent tools;目标 Hermes 实际验证中 toolset 仍为空。 |
tools.py::register_tools(ctx) |
新版分支中的官方独立入口;委托 outbound.tools 注册 manifest 声明的固定 ToolSpec,注册阶段不联网。 |
dashboard/plugin_api.py |
由宿主 Dashboard 扩展加载的管理 API;请求按宿主确认的 profile 处理。 |
MilkyAdapter |
管理连接、停止、入站交接和出站委托。 |
MilkyOutboundSender |
校验 group:/dm: 目标,格式化消息并调用 Milky Action/upload。 |
SlashCommandService |
管理活动 Milky client,处理 /milky 和显式贴纸维护命令。 |
stickers/ |
懒加载独立 stickers.db,校验 inbox 图片,执行维护命令以及受限 sticker_search/sticker_send 搜索、精确发送和 claim;jieba 由插件运行时依赖提供。 |
支持 register_system_prompt_section 的 Hermes 宿主会在 after_memory 登记
hermes-plugin-milky.qq-platform-guidance,并在连接完成后使用已确认的 QQ UID 和昵称渲染
媒体、CQ-compatible、无回复和 bundled skill 指引。Milky 要求 register_platform 和
register_system_prompt_section 均可用;缺少任一 API 时会报告 unsupported 并停止注册。
同一宿主还会登记 hermes-plugin-milky.qq-session-context;其 callback 只读取当前
HERMES_SESSION_CHAT_ID 对应的本地安全快照,不发起网络或文件 I/O。没有当前 chat context、资料缺失
或快照已淘汰时不注入会话介绍。
详细的稳定模块边界见 ARCHITECTURE.md;可观察行为和测试要求见 openspec/。新建的未归档 change 会放在 openspec/changes/。
欢迎通过 GitHub Issues 提问、报告 问题或提交 pull request。贡献前请阅读 CONTRIBUTING.md 和 ARCHITECTURE.md。
贡献要求:
- 行为变化先补充脱敏契约或 fixture,再实现并增加回归测试;
- 使用
uv管理 Python 环境和依赖,不使用pip、pipx或直接调用python/python3; - 遵循 Google Python Style Guide,并保持各模块依赖边界;
- 运行
uv run pytest -q、uv run ruff check .、uv run ruff format --check .、uv build和git diff --check; - 不提交 token、Authorization header、真实 QQ/群 ID、真实媒体 URL/路径、文件内容或敏感正文;
- 使用中文 Conventional Commits。
PR 应说明变更范围、实际执行的命令、测试结果和未解决风险。若行为契约发生变化,请同步 更新对应的 OpenSpec change;安全问题不要公开粘贴到 issue。
维护者:ByteColtX。问题、功能建议和安全联系入口见 CONTRIBUTING.md。
感谢 Hermes Gateway 的 platform adapter contract、Milky v1.3 协议生态,以及提供协议 fixture、测试和文档改进的贡献者。
本项目使用 MIT License,版权所有 © 2026 ByteColtX。完整条款见 LICENSE。