nonebot-plugin-hermes 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/PKG-INFO +22 -1
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/README.md +20 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/__init__.py +1 -1
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/config.py +60 -0
- nonebot_plugin_hermes-0.3.4/nonebot_plugin_hermes/core/inflight.py +107 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/handlers/__init__.py +1 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/handlers/message.py +408 -47
- nonebot_plugin_hermes-0.3.4/nonebot_plugin_hermes/handlers/notices.py +140 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/tools/push_message.py +5 -5
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/utils.py +11 -3
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes.egg-info/PKG-INFO +22 -1
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes.egg-info/SOURCES.txt +10 -1
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes.egg-info/requires.txt +1 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/pyproject.toml +2 -1
- nonebot_plugin_hermes-0.3.4/tests/test_ack_scope.py +305 -0
- nonebot_plugin_hermes-0.3.4/tests/test_b0_config.py +15 -0
- nonebot_plugin_hermes-0.3.4/tests/test_explicit_trigger_guarantee.py +696 -0
- nonebot_plugin_hermes-0.3.4/tests/test_inflight.py +284 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_mcp_push_message.py +7 -8
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_message_handler_coalesce.py +234 -16
- nonebot_plugin_hermes-0.3.4/tests/test_notices.py +250 -0
- nonebot_plugin_hermes-0.3.4/tests/test_notices_config.py +15 -0
- nonebot_plugin_hermes-0.3.4/tests/test_route_synthesized_input.py +160 -0
- nonebot_plugin_hermes-0.3.4/tests/test_segments_placeholders.py +128 -0
- nonebot_plugin_hermes-0.3.4/tests/test_utils_adapter_name.py +57 -0
- nonebot_plugin_hermes-0.3.2/nonebot_plugin_hermes/core/inflight.py +0 -71
- nonebot_plugin_hermes-0.3.2/tests/test_inflight.py +0 -91
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/hermes_install_skill.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/active_session.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/bot_registry.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/hermes_client.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/message_buffer.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/outbound.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/prompt_builder.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/session.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/storage/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/storage/image_cache.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/storage/image_fetcher.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/core/storage/message_store.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/handlers/commands.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/auth.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/server.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/tools/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/tools/get_message_images.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/tools/get_recent_messages.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/mcp/tools/list_active_sessions.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/scripts/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/scripts/install_skill.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/skill/SKILL.md +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/skill/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/tasks/__init__.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/tasks/expire_active_sessions.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes/tasks/storage_vacuum.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes.egg-info/dependency_links.txt +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes.egg-info/entry_points.txt +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/nonebot_plugin_hermes.egg-info/top_level.txt +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/setup.cfg +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_active_session.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_bot_registry.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_handlers_at_filter.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_handlers_nickname.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_handlers_telegram_url_cache.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_hermes_client_structured.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_image_cache.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_image_fetcher.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_mcp_auth.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_mcp_get_message_images.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_mcp_read_tools.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_message_buffer.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_message_store.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_prompt_builder.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_session_manager.py +0 -0
- {nonebot_plugin_hermes-0.3.2 → nonebot_plugin_hermes-0.3.4}/tests/test_tasks_storage_vacuum.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nonebot-plugin-hermes
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: NoneBot plugin for Hermes Agent — multi-platform AI chatbot via Hermes API Server
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/gsskk/nonebot-plugin-hermes
|
|
@@ -18,6 +18,7 @@ Provides-Extra: dev
|
|
|
18
18
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
19
19
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
20
20
|
Requires-Dist: ruff>=0.15; extra == "dev"
|
|
21
|
+
Requires-Dist: nonebot-adapter-onebot>=2.4.6; extra == "dev"
|
|
21
22
|
|
|
22
23
|
# nonebot-plugin-hermes
|
|
23
24
|
|
|
@@ -61,6 +62,8 @@ Requires-Dist: ruff>=0.15; extra == "dev"
|
|
|
61
62
|
- 🧪 **群活跃态 (M1, 实验性)**:@bot 后 5 分钟内主动监听群对话,由 Hermes 通过结构化决策判断是否插话
|
|
62
63
|
- 🧪 **反向通道 (M1, 实验性)**:内嵌本地 MCP server,让 Hermes 主动 push 消息进群(延迟回复 / 异步通知)
|
|
63
64
|
- 🧪 **历史图片召回 (0.3+, 实验性)**:SQLite 持久化消息日志 + 文件系统图字节缓存 + MCP 工具 `get_message_images`,让 Hermes 在用户说"上图"/"刚才那张"时按消息 id 精确取回历史图字节
|
|
65
|
+
- 🧪 **OneBot v11 Notice 触发 (0.3.3+, 实验性)**:戳一戳作为第二种 @ 等价触发;有人入群时让 Hermes 自决要不要欢迎(noop 合法,不做模板欢迎语)
|
|
66
|
+
- 🧪 **消息段感知扩展 (0.3.4+, 实验性)**:语音/视频/QQ 表情/sticker 占位文本注入 LLM 视野;sticker 自动跳过 vision API。OneBot v11 NapCat 显式 @ 时贴 emoji 回执(`HERMES_ACK_FEEDBACK_ENABLED=true`)
|
|
64
67
|
|
|
65
68
|
## 快速开始
|
|
66
69
|
|
|
@@ -325,6 +328,10 @@ T+5s 用户 B: @bot 评价下上图
|
|
|
325
328
|
| `HERMES_ACTIVE_SESSION_ENABLED` | `false` | 启用群活跃态(M1)。`false` 时退化为 v0.1.6 等价行为 |
|
|
326
329
|
| `HERMES_ACTIVE_SESSION_TTL_SEC` | `300` | 活跃窗口 TTL(秒),每次插话滑动续期 |
|
|
327
330
|
| `HERMES_ACTIVE_SWEEP_INTERVAL_SEC` | `30` | 活跃态过期清扫 cron 频率(秒) |
|
|
331
|
+
| `HERMES_POKE_TRIGGER_ENABLED` | `false` | OneBot v11:被戳一戳时触发对话(私聊 / 群都生效,等价于被 @)。其他适配器静默忽略 |
|
|
332
|
+
| `HERMES_GREET_ON_JOIN` | `false` | OneBot v11:有人加入群且 `HERMES_ACTIVE_SESSION_ENABLED=true` 时,触发一次 reactive turn 让 Hermes 自决是否欢迎(`noop` 是合法返回)。active 关时不触发 |
|
|
333
|
+
| `HERMES_ACK_FEEDBACK_ENABLED` | `false` | 用户消息上显示 ack 回执(B-0 实装 OneBot v11 NapCat emoji)。B-0.5 规划扩 TG/Discord 私聊 typing |
|
|
334
|
+
| `HERMES_ACK_EMOJI_ID` | `341` | B-0 OneBot v11 路径下贴的 QQ 表情 id(默认 341 = /打招呼;`373` /忙 = 打字动物;`129` /挥手 = 经典挥手) |
|
|
328
335
|
| `HERMES_BUFFER_PER_GROUP_CAP` | `200` | ⚠️ **0.3 起空转**——MessageBuffer 改为 SQLite 后端,无内存 per-group 上限;消息淘汰由 `HERMES_STORAGE_MESSAGE_*` 控制。下一个 major 版本会移除 |
|
|
329
336
|
| `HERMES_BUFFER_TOTAL_GROUPS_CAP` | `50` | ⚠️ **0.3 起空转**——同上,SQLite 后端无 LRU,改为 retention + 行数上限 |
|
|
330
337
|
| `HERMES_MCP_ENABLED` | `false` | 启动内嵌 FastMCP server(M1 反向通道) |
|
|
@@ -339,6 +346,20 @@ T+5s 用户 B: @bot 评价下上图
|
|
|
339
346
|
| `HERMES_IMAGE_FETCH_TIMEOUT_S` | `10` | 单图 HTTP 抓取超时秒数 |
|
|
340
347
|
| `HERMES_IMAGE_FETCH_MAX_ATTEMPTS` | `2` | 单图总尝试次数(1=不重试,2=一次重试,以此类推) |
|
|
341
348
|
|
|
349
|
+
### Busy notice(显式 @ 被 plumbing 丢单时的可见信号)
|
|
350
|
+
|
|
351
|
+
当 `_refire` 链触顶 `MAX_REFIRE_DEPTH=3`(同群短时间内塞了 ≥ 4 条 explicit @ 而上游 Hermes 跟不上)时,最新一条 explicit @ 会被 plumbing 丢掉。此时插件会在那条原消息上贴 `HERMES_BUSY_EMOJI_ID`(默认 97 = QQ 经典表情 /擦汗),**不撤销**,作为"我看见了但确实忙不过来"的视觉信号。
|
|
352
|
+
|
|
353
|
+
与 ack-feedback emoji(`HERMES_ACK_EMOJI_ID`,默认 341 /打招呼)是不同语义:
|
|
354
|
+
- ack-feedback:chat() 期间常驻,完成后撤销,表示"工作中"
|
|
355
|
+
- busy notice:depth-cap 触顶时常驻,**不撤销**,表示"工作不下去"
|
|
356
|
+
|
|
357
|
+
默认值刻意取互相区分明显的表情;改默认值前请验证 OneBot 实现端的 emoji_id 映射表。
|
|
358
|
+
|
|
359
|
+
仅 OneBot v11 群聊路径生效;其它 adapter(Telegram / Discord)或 msg_id 缺失时降级为 WARN 日志,不会文本兜底,避免在 burst 上下文里加噪声。
|
|
360
|
+
|
|
361
|
+
同样有一类失败路径有 user-visible 兜底:上游 Hermes 5xx / 网络断时,refire 路径上的 explicit @ 会发 `HERMES_TRANSPORT_ERROR_FALLBACK_TEXT`(默认"嗯…我这边遇到点状况,稍后再问一次")。设为空串可关闭文本兜底。
|
|
362
|
+
|
|
342
363
|
## 限制
|
|
343
364
|
|
|
344
365
|
由于通过 HTTP API 与 Hermes 通信(而非原生 Gateway Adapter),以下功能不可用:
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
- 🧪 **群活跃态 (M1, 实验性)**:@bot 后 5 分钟内主动监听群对话,由 Hermes 通过结构化决策判断是否插话
|
|
41
41
|
- 🧪 **反向通道 (M1, 实验性)**:内嵌本地 MCP server,让 Hermes 主动 push 消息进群(延迟回复 / 异步通知)
|
|
42
42
|
- 🧪 **历史图片召回 (0.3+, 实验性)**:SQLite 持久化消息日志 + 文件系统图字节缓存 + MCP 工具 `get_message_images`,让 Hermes 在用户说"上图"/"刚才那张"时按消息 id 精确取回历史图字节
|
|
43
|
+
- 🧪 **OneBot v11 Notice 触发 (0.3.3+, 实验性)**:戳一戳作为第二种 @ 等价触发;有人入群时让 Hermes 自决要不要欢迎(noop 合法,不做模板欢迎语)
|
|
44
|
+
- 🧪 **消息段感知扩展 (0.3.4+, 实验性)**:语音/视频/QQ 表情/sticker 占位文本注入 LLM 视野;sticker 自动跳过 vision API。OneBot v11 NapCat 显式 @ 时贴 emoji 回执(`HERMES_ACK_FEEDBACK_ENABLED=true`)
|
|
43
45
|
|
|
44
46
|
## 快速开始
|
|
45
47
|
|
|
@@ -304,6 +306,10 @@ T+5s 用户 B: @bot 评价下上图
|
|
|
304
306
|
| `HERMES_ACTIVE_SESSION_ENABLED` | `false` | 启用群活跃态(M1)。`false` 时退化为 v0.1.6 等价行为 |
|
|
305
307
|
| `HERMES_ACTIVE_SESSION_TTL_SEC` | `300` | 活跃窗口 TTL(秒),每次插话滑动续期 |
|
|
306
308
|
| `HERMES_ACTIVE_SWEEP_INTERVAL_SEC` | `30` | 活跃态过期清扫 cron 频率(秒) |
|
|
309
|
+
| `HERMES_POKE_TRIGGER_ENABLED` | `false` | OneBot v11:被戳一戳时触发对话(私聊 / 群都生效,等价于被 @)。其他适配器静默忽略 |
|
|
310
|
+
| `HERMES_GREET_ON_JOIN` | `false` | OneBot v11:有人加入群且 `HERMES_ACTIVE_SESSION_ENABLED=true` 时,触发一次 reactive turn 让 Hermes 自决是否欢迎(`noop` 是合法返回)。active 关时不触发 |
|
|
311
|
+
| `HERMES_ACK_FEEDBACK_ENABLED` | `false` | 用户消息上显示 ack 回执(B-0 实装 OneBot v11 NapCat emoji)。B-0.5 规划扩 TG/Discord 私聊 typing |
|
|
312
|
+
| `HERMES_ACK_EMOJI_ID` | `341` | B-0 OneBot v11 路径下贴的 QQ 表情 id(默认 341 = /打招呼;`373` /忙 = 打字动物;`129` /挥手 = 经典挥手) |
|
|
307
313
|
| `HERMES_BUFFER_PER_GROUP_CAP` | `200` | ⚠️ **0.3 起空转**——MessageBuffer 改为 SQLite 后端,无内存 per-group 上限;消息淘汰由 `HERMES_STORAGE_MESSAGE_*` 控制。下一个 major 版本会移除 |
|
|
308
314
|
| `HERMES_BUFFER_TOTAL_GROUPS_CAP` | `50` | ⚠️ **0.3 起空转**——同上,SQLite 后端无 LRU,改为 retention + 行数上限 |
|
|
309
315
|
| `HERMES_MCP_ENABLED` | `false` | 启动内嵌 FastMCP server(M1 反向通道) |
|
|
@@ -318,6 +324,20 @@ T+5s 用户 B: @bot 评价下上图
|
|
|
318
324
|
| `HERMES_IMAGE_FETCH_TIMEOUT_S` | `10` | 单图 HTTP 抓取超时秒数 |
|
|
319
325
|
| `HERMES_IMAGE_FETCH_MAX_ATTEMPTS` | `2` | 单图总尝试次数(1=不重试,2=一次重试,以此类推) |
|
|
320
326
|
|
|
327
|
+
### Busy notice(显式 @ 被 plumbing 丢单时的可见信号)
|
|
328
|
+
|
|
329
|
+
当 `_refire` 链触顶 `MAX_REFIRE_DEPTH=3`(同群短时间内塞了 ≥ 4 条 explicit @ 而上游 Hermes 跟不上)时,最新一条 explicit @ 会被 plumbing 丢掉。此时插件会在那条原消息上贴 `HERMES_BUSY_EMOJI_ID`(默认 97 = QQ 经典表情 /擦汗),**不撤销**,作为"我看见了但确实忙不过来"的视觉信号。
|
|
330
|
+
|
|
331
|
+
与 ack-feedback emoji(`HERMES_ACK_EMOJI_ID`,默认 341 /打招呼)是不同语义:
|
|
332
|
+
- ack-feedback:chat() 期间常驻,完成后撤销,表示"工作中"
|
|
333
|
+
- busy notice:depth-cap 触顶时常驻,**不撤销**,表示"工作不下去"
|
|
334
|
+
|
|
335
|
+
默认值刻意取互相区分明显的表情;改默认值前请验证 OneBot 实现端的 emoji_id 映射表。
|
|
336
|
+
|
|
337
|
+
仅 OneBot v11 群聊路径生效;其它 adapter(Telegram / Discord)或 msg_id 缺失时降级为 WARN 日志,不会文本兜底,避免在 burst 上下文里加噪声。
|
|
338
|
+
|
|
339
|
+
同样有一类失败路径有 user-visible 兜底:上游 Hermes 5xx / 网络断时,refire 路径上的 explicit @ 会发 `HERMES_TRANSPORT_ERROR_FALLBACK_TEXT`(默认"嗯…我这边遇到点状况,稍后再问一次")。设为空串可关闭文本兜底。
|
|
340
|
+
|
|
321
341
|
## 限制
|
|
322
342
|
|
|
323
343
|
由于通过 HTTP API 与 Hermes 通信(而非原生 Gateway Adapter),以下功能不可用:
|
|
@@ -92,11 +92,71 @@ class Config(BaseModel):
|
|
|
92
92
|
hermes_active_sweep_interval_sec: int = 30
|
|
93
93
|
"""expire_active_sessions cron 频率(秒)"""
|
|
94
94
|
|
|
95
|
+
# --- Notice 事件触发 (Phase A) ---
|
|
96
|
+
hermes_poke_trigger_enabled: bool = False
|
|
97
|
+
"""OneBot v11: 被戳一戳时触发对话(私聊/群无差别),等价 @。
|
|
98
|
+
其他适配器无效,缺省全关 = 老用户行为零变化。"""
|
|
99
|
+
|
|
100
|
+
hermes_greet_on_join: bool = False
|
|
101
|
+
"""OneBot v11: 群里有人加入时,在 active_session 开启的群触发一次 reactive turn
|
|
102
|
+
让 Hermes 自决是否欢迎(decision_protocol 的 noop 是合法选择)。
|
|
103
|
+
active_session 关时不触发——passive 是 1:1 Q&A 语义,不适用欢迎场景。"""
|
|
104
|
+
|
|
105
|
+
# --- Phase B-0: ack 反馈 + 非文本段感知 ---
|
|
106
|
+
hermes_ack_feedback_enabled: bool = False
|
|
107
|
+
"""显式触发 (用户主动 @ bot 或私聊) 时给一个'已收到'视觉回执。
|
|
108
|
+
B-0 实装: OneBot v11 **群聊** (NapCat/LLOneBot/LuckyLilliaBot) 贴 emoji,
|
|
109
|
+
chat 完成后撤销。其他适配器或场景 silently no-op。
|
|
110
|
+
|
|
111
|
+
开关名通用,B-0.5 规划里要扩 Telegram/Discord 私聊 typing 状态,避免改名 breaking。
|
|
112
|
+
|
|
113
|
+
**已知限制**:
|
|
114
|
+
- 私聊不贴 emoji: QQ NT 协议下 set_msg_emoji_like 仅支持群聊
|
|
115
|
+
(LuckyLilliaBot/NapCat 在私聊调用都会 raise '只支持群聊消息')
|
|
116
|
+
- emoji 撤销依赖 OneBot 实现端较新版本:
|
|
117
|
+
NapCat: 全版本支持 (用 set=False)
|
|
118
|
+
LuckyLilliaBot / 较新 LLOneBot: 支持 (unset_msg_emoji_like 或 set=False)
|
|
119
|
+
老 LLOneBot: 两条路径都不支持, emoji 永久留在消息上 (启动后 WARN-once 告知)
|
|
120
|
+
若 emoji 不撤销但你不想 disable, 接受'永久已读痕迹'即可——不影响主流程。"""
|
|
121
|
+
|
|
122
|
+
hermes_ack_emoji_id: int = 341
|
|
123
|
+
"""B-0 OneBot v11 路径下贴的 QQ 表情 id。默认 341 (/打招呼) ——
|
|
124
|
+
动画"hi 打招呼",bot 收到 @ 的最自然反馈。
|
|
125
|
+
其他推荐:
|
|
126
|
+
- 373 (/忙):一只小动物在打字,typing-indicator 风格
|
|
127
|
+
- 129 (/挥手):经典小表情版的挥手,跨版本更稳但视觉平淡
|
|
128
|
+
注意:341/373 是 QQ NT 超表情 (EMCode 10000+),NapCat 内部有个
|
|
129
|
+
`length > 3 ? type=2 : type=1` 启发式,3 位 id 会被当经典型——
|
|
130
|
+
实测多数 NapCat 版本仍能正常 render,但跨版本不保证。
|
|
131
|
+
若实测视觉异常,可换经典型小表情 (EMCode < 300,如 129)。
|
|
132
|
+
NapCat schema 接受 Number | String,走 int 是为了 .env 直接写数字
|
|
133
|
+
(HERMES_ACK_EMOJI_ID=237 不用引号),pydantic-settings 不会撞类型错。
|
|
134
|
+
完整列表见 NapCat face_config.json。仅 OneBot 路径用到。"""
|
|
135
|
+
|
|
95
136
|
hermes_reactive_post_reply_cooldown_sec: int = 8
|
|
96
137
|
"""reactive 模式下,bot 刚回复完群里 N 秒内,非显式 @ 触发的新消息直接静默。
|
|
97
138
|
用来阻断「我刚说完别人接话→我又凑一句」类型的过触发。
|
|
98
139
|
0 = 关闭(回退到旧行为)。显式 @bot 不受影响,任何时候都会立刻进入决策。"""
|
|
99
140
|
|
|
141
|
+
hermes_transport_error_fallback_text: str = "嗯…我这边遇到点状况,稍后再问一次"
|
|
142
|
+
"""Hermes 上游返 5xx / 传输错误时, 替代 LLM raw_text 的兜底文本。
|
|
143
|
+
没有这条兜底, 服务端英文错误信息(如 "Model generated invalid tool call: ...")
|
|
144
|
+
会被原文当 raw_text 发到群里,体验差也泄露内部信息。
|
|
145
|
+
空串 → 不发任何文本(等价于 silent)。仅在 reactive 显式触发 / passive 路径生效——
|
|
146
|
+
非显式触发本来就静默,不受影响。"""
|
|
147
|
+
|
|
148
|
+
hermes_busy_emoji_id: int = 97
|
|
149
|
+
"""depth-cap 触顶丢 explicit @ 时贴在原消息上的 emoji_id,仅 OneBot v11 群聊路径生效。
|
|
150
|
+
默认 97 = QQ 经典表情 /擦汗,语义"忙不过来"。
|
|
151
|
+
与 hermes_ack_emoji_id(默认 341 /打招呼)的"已收到"反向情绪,视觉上明显区分。
|
|
152
|
+
|
|
153
|
+
本字段独立于 hermes_ack_feedback_enabled 控制:即使关掉 ack 反馈,
|
|
154
|
+
busy notice 仍是丢单时唯一的用户可见信号,默认开启。
|
|
155
|
+
|
|
156
|
+
EMCode 解析:9x 段是经典型小表情(NapCat heuristics 把 length<=3 判为
|
|
157
|
+
type=1 经典表情),跨 OneBot 实现端 (NapCat / LLOneBot / Lagrange) 兼容性好。
|
|
158
|
+
完整 emoji_id 表见各实现端 face_config / sysface_config。"""
|
|
159
|
+
|
|
100
160
|
# --- M1: 反向 MCP 通道 ---
|
|
101
161
|
hermes_mcp_enabled: bool = False
|
|
102
162
|
"""是否启动内嵌 FastMCP server(False 时 Hermes 反向调用全失败,出向不影响)"""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""In-flight 调用追踪 + coalesce 重燃支持。
|
|
2
|
+
|
|
3
|
+
修同一 (adapter, group_id|user_id) 上事件 task 并发调 chat() 的 bug:
|
|
4
|
+
in-flight 时新消息只更新 pending 单元,等当前一发完成后再合并跑一次。
|
|
5
|
+
|
|
6
|
+
线程安全:**否**。预设单线程 asyncio 事件循环,与 ActiveSessionManager 一致。
|
|
7
|
+
|
|
8
|
+
Pending 优先级:explicit-trigger 不被 bystander 覆盖,以保证 @bot 一旦排进
|
|
9
|
+
pending 就一定能在前一发完成后被 _refire 跑到 chat()。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from typing import Dict, Literal, Optional, Tuple, Union
|
|
16
|
+
|
|
17
|
+
from .message_buffer import BufferedMessage
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Refire 链最大深度。超过则丢 pending、warn,等下一个新触发。
|
|
21
|
+
# 一次 burst 最多产出 1(主回) + MAX_REFIRE_DEPTH(链尾)= 4 发回复。
|
|
22
|
+
MAX_REFIRE_DEPTH = 3
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class PendingEntry:
|
|
27
|
+
"""In-flight 期间排队的消息及触发元数据。
|
|
28
|
+
|
|
29
|
+
pending 不再裸存 BufferedMessage——单独抽 PendingEntry 是为了跨 turn 边界
|
|
30
|
+
保留两个 plumbing 决策所需要的事实:
|
|
31
|
+
- 这条排队消息原本是不是 explicit-trigger
|
|
32
|
+
- 它的原始 adapter 侧 message_id(用于失败时贴 emoji notice)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
msg: BufferedMessage
|
|
36
|
+
is_explicit_trigger: bool
|
|
37
|
+
original_msg_id: Optional[Union[str, int]] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class InflightSlot:
|
|
42
|
+
started_at: int
|
|
43
|
+
pending: Optional[PendingEntry] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InflightRegistry:
|
|
47
|
+
"""per-target 非阻塞 busy 标记 + pending 单元。
|
|
48
|
+
|
|
49
|
+
Key 约定:
|
|
50
|
+
- 群: ("adapter", "group:" + group_id)
|
|
51
|
+
- 私聊: ("adapter", "private:" + user_id)
|
|
52
|
+
|
|
53
|
+
Pending 覆盖规则:
|
|
54
|
+
- 空 pending → 写入,返回 'pending_set'
|
|
55
|
+
- 已存 bystander pending,新到 explicit → 覆盖升级,返回 'pending_set'
|
|
56
|
+
- 已存 bystander pending,新到 bystander → latest wins 覆盖,返回 'pending_set'
|
|
57
|
+
- 已存 explicit pending,新到 explicit → latest wins 覆盖,返回 'pending_set'
|
|
58
|
+
- 已存 explicit pending,新到 bystander → 不覆盖,返回 'pending_kept'
|
|
59
|
+
|
|
60
|
+
不持有任何 asyncio.Task 引用 —— 重燃由 caller 用 create_task 自己接手,
|
|
61
|
+
registry 只负责「现在有没有人在跑」+「跑完后是否要再跑一次」两个状态。
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self) -> None:
|
|
65
|
+
self._slots: Dict[Tuple[str, str], InflightSlot] = {}
|
|
66
|
+
|
|
67
|
+
def try_enter(
|
|
68
|
+
self,
|
|
69
|
+
key: Tuple[str, str],
|
|
70
|
+
current_msg: BufferedMessage,
|
|
71
|
+
*,
|
|
72
|
+
is_explicit_trigger: bool,
|
|
73
|
+
original_msg_id: Optional[Union[str, int]],
|
|
74
|
+
now_ms: int,
|
|
75
|
+
) -> Literal["entered", "pending_set", "pending_kept"]:
|
|
76
|
+
"""无 slot → 占位 started_at=now_ms,返回 'entered'。
|
|
77
|
+
有 slot 且 pending 是 explicit 而新到 bystander → 'pending_kept'。
|
|
78
|
+
其它情况 → 写 pending,返回 'pending_set'。
|
|
79
|
+
"""
|
|
80
|
+
slot = self._slots.get(key)
|
|
81
|
+
if slot is None:
|
|
82
|
+
self._slots[key] = InflightSlot(started_at=now_ms)
|
|
83
|
+
return "entered"
|
|
84
|
+
# explicit 不被 bystander 覆盖,保护用户明示意图
|
|
85
|
+
if not is_explicit_trigger and slot.pending is not None and slot.pending.is_explicit_trigger:
|
|
86
|
+
return "pending_kept"
|
|
87
|
+
slot.pending = PendingEntry(
|
|
88
|
+
msg=current_msg,
|
|
89
|
+
is_explicit_trigger=is_explicit_trigger,
|
|
90
|
+
original_msg_id=original_msg_id,
|
|
91
|
+
)
|
|
92
|
+
return "pending_set"
|
|
93
|
+
|
|
94
|
+
def take_pending(self, key: Tuple[str, str]) -> Optional[PendingEntry]:
|
|
95
|
+
"""Destructive read。无 slot 或 pending 为 None 都返回 None。"""
|
|
96
|
+
slot = self._slots.get(key)
|
|
97
|
+
if slot is None:
|
|
98
|
+
return None
|
|
99
|
+
entry = slot.pending
|
|
100
|
+
slot.pending = None
|
|
101
|
+
return entry
|
|
102
|
+
|
|
103
|
+
def exit(self, key: Tuple[str, str]) -> None:
|
|
104
|
+
"""释放 slot。pending 仍在的话由调用方自行先 take_pending。
|
|
105
|
+
slot 不存在则 no-op。
|
|
106
|
+
"""
|
|
107
|
+
self._slots.pop(key, None)
|