!tUqC0mcjJ-M1BdMPlOTpbcKDc3kI3aRGrlBqvZGe1eI(临时房间,可保留查阅)## 补充一条你可能没注意到的关键线索
我在历史取证里翻到了这个容器**所有成功插件的注册方式**:
| 插件 | 注册的 hook |
|---|---|
| `plugin-active-reporter` | `pre_llm_call` + `post_llm_call` |
| `plugin-topup-guard` | `pre_llm_call` |
| `plugin-tips-cn` | `transform_tool_result` + `transform_llm_output` + `pre_gateway_dispatch` |
| `plugin-deliverable-inject` | `transform_llm_output` |
| `plugin-fmode` | `on_session_end` |
**规律:全都是靠 `ctx.register_hook(...)` 注册了真实 hook 才被实载。**
你的 `plugin-matrix-process-notice` 是 `hooks: []` + 纯 monkey-patch,**没有任何 hook 注册** → loader 跳过它。
---
## 但先别急着改成 hook 方式,还有个更直接的问题要确认
**`post_tool_call` shell hook 已经存在**(`config.yaml:63-69`):
yaml post_tool_call → python3 /opt/data/scripts/fmode-deliverable-hook.py matcher: terminal|write_file|patch|execute_code
**每次工具调用都会 fork 一个 Python 进程跑这个脚本!**
请检查:`fmode-deliverable-hook.py` 里在 `post_tool_call` 时能不能**拿到工具名和参数**?如果能,直接在它里面加一段「往 Matrix 发一条过程消息」,**根本不需要写新插件**。
这比折腾插件加载机制简单得多,而且这个脚本已经被证明能跑。
---
## 请按这个顺序做
### 1. 先查现成脚本(最快路径)
bash grep -n "post_tool_call|def main|tool_name|payload" /opt/data/scripts/fmode-deliverable-hook.py | head -20
回答:`post_tool_call` 分支里能拿到 tool_name / tool_input 吗?
### 2. 如果能拿到 → 直接在里面加发消息逻辑
- 用已有的 Matrix 发送能力(你的 outbound 插件里就有)
- 发一条 `💻 terminal · <command 前 60 字>` 的消息
- 带 `fmode.kind: 'process'`
### 3. 如果拿不到 → 再用 hook 方式改造插件
照 `plugin-tips-cn` 的写法,注册一个真实 hook(`transform_llm_output` 或 `post_tool_call` 插件 hook),在 hook 里做 patch。
### 4. 无论走哪条路,验证方式不变
重启 → 跑 3 次工具调用 → 回读 `fmode.kind`
---
**核心判断**:你之前一直在修插件内部逻辑,但插件压根没被加载。要么用现成的 shell hook(最简单),要么按 tips-cn 的方式让它被加载。
我先看一眼现成脚本,把事实核实清楚再动手。
grep -n "post_tool_call|def main|to...
📖 Reading fmode-deliverable-hook.py L250-349
拿得到,`tool_name` 和 `tool_input` 都在 payload 里。我看一下这段的完整实现和现有的 Matrix 发送能力。
路径清楚了:`post_tool_call` 能拿到 `tool_name` 和 `tool_input`。现在我需要 Matix 的发送凭据,看看现有 outbound 插件怎么发。
凭据就在 `.env` 里:`MATRIX_HOMESERVER`、`MATRIX_ACCESS_TOKEN`。我先验证一下 `post_tool_call` 的 payload 里是否真的带上房间信息,否则脚本不知道该发到哪个房间;这一步是关键。
grep -n "MATRIX" /opt/data/.env 2>/de...
cd /opt/data && python3 - <<'EOF' ...
现在我直接抓取 `post_tool_call` 的真实 payload 结构,这样就知道脚本里能拿到哪些字段,尤其是房间号。
现在临时把 `post_tool_call` 的 hook 命令换成这个探针,跑一次工具调用,看真实 payload。这一步只要几秒,验证完马上还原。
cd /opt/data && python3 - <<'EOF' ...
探针已经就位。触发一次工具调用,看看真实 payload 里的字段。
echo "trigger probe"
ls -la /tmp/probe/; echo "=== payload...
探针没有被调用,说明 config 修改需要重启才能生效。我先还原配置,改用更可靠的方法:直接查看 Hermes 源码中 `post_tool_call` 的触发方式。
* ```
ls -la /tmp/probe/; echo "=== payload...
cd /opt/data && python3 - <<'EOF' ...
### 15【prose】
text
配置已还原。现在直接读 Hermes 源码,确认 post_tool_call 的 payload 究竟包含哪些字段;这是最权威的依据,不用猜测。
### 16【prose】
text
python3 -c "import hermes, os; print(...
🔎 Searching files for post_tool_call|tool_name|tool_input|s...
### 17【prose】
text
已经确认 tool_name、tool_input、session_id 都在 payload 里,但没有 chat_id 或房间号。这些需要从 state.db 按 session_id 反查。同时还要确认 hook 携带哪些 extra 字段。
### 18【prose】
text
已经确认可行:session_id 可以从 sessions.chat_id 反查房间号,tool_name 和 tool_input 都能拿到。现在开始写一个独立的发送模块,避免影响现有成果上传逻辑。
### 19【prose】
text
关键发现有两个:第 550 行显示,Hermes 已经通过环境变量 HERMES_SESSION_CHAT_ID 把房间号传入 hook 子进程,因此直接读取这个环境变量,比查询数据库更可靠。
再看一下旧插件里「kind」用的取值约定,确保新实现与 App 侧保持一致。
### 20【prose】
text
grep -rn "fmode.kind\|process_notice\...
📖 Reading init.py L130-199
### 21【prose】
text
环境变量在 gateway/session_context.py 中管理,在 gateway 进程内可用。为了确认它在 post_tool_call 子进程里是否真的存在,我直接实测一次;这次用正确的探针,并确认 shell hook 是否需要在启动时注册。
### 22【prose】
text
探针已经就绪。现在把 post_tool_call 临时指向它;这次需要让 Hermes 重新加载配置才能生效。我先确认进程和配置重载机制。
### 23【prose】
text
ps -eo pid,ppid,etime,cmd | grep -i "...
### 24【prose】
text 已经找到 gateway 进程(116634)。为了避免反复重启影响主人,我改用更稳妥的方式:先只在这份脚本里验证环境变量是否存在,同时把发送逻辑写好,然后一次性完成验证。
接下来先做最关键的一步:用真实的 payload 结构测试脚本。
### 25【prose】
text
cd /tmp/probe && python3 - <<'PYEOF' ...
### 26【prose】
text
结论明确:tool_name、tool_input 和 session_id 都能拿到;房间号可以通过 HERMES_SESSION_CHAT_ID 环境变量或查询 state.db 获得。现在开始写发送模块,并把它接入现有脚本。
### 27【prose】
text
cd /tmp && python3 - <<'PYEOF' ...
### 28【prose】
text
凭据可以正常使用。现在编写独立的发送模块,并把它接入现有脚本的 post_tool_call 分支。
### 29【prose】
text
先手动自检:直接向这个房间发送一条消息,验证 Matrix 凭据和 fmode.kind 是否能真正写入。
### 30【prose】
text 🧪 terminal · 过程消息自检 (fmode.kind=process)
### 31【prose】
text
消息已经发送成功(event_id: $X_xR4NH...)。现在回读那条事件,确认 fmode.kind 确实真的写入了,而不是仅仅把 body 发送出去。
### 32【prose】
text
cd /opt/data && python3 - <<'PYEOF' ...
### 33【prose】
text
从服务器回读确认:fmode.kind = 'process' 确实存在于事件 content 中。这是关键验证,之前插件一直做不到。
现在把它接入现有的 post_tool_call 分支。
### 34【prose】
text
还需要补上两处:引入 subprocess 模块;另外,HERMES_SESSION_CHAT_ID 不在 payload 里,我应主动从进程环境中读取并透传。
### 35【prose】
text patch · /opt/data/scripts/fmode-deliverable-hook.py
### 36【prose】
text 现在用真实的 payload 模拟一次完整的 hook 调用,验证会不会真的发出消息;这是不打真实命令、不重启的端到端测试。
### 37【prose】
text terminal · git status --short --branch
### 38【prose】
text terminal · cd /opt/data && rm -f /opt/data/cache/process-notice/* 2>/de…
### 39【prose】
text 端到端测试已经跑通(耗时 0.372 秒,没有拖慢工具链),并且真实运行环境已经在自动发送过程消息,因为刚才我自己发出的这几条工具调用已经触发并成功发送。
现在回读最新那条消息的事件,确认 fmode.kind 字段是否正确。
### 40【prose】
text
cd /opt/data && python3 - <<'PYEOF' ...
### 41【prose】
text
从服务器回读后确认:fmode.kind = 'process' 确实已经写入,而且触发机制是真实的工具调用,不是手工拼出来的 payload。
现在还需要确认一件事:在 Hermes 真实进程里,房间号是怎么拿到的,也就是该走环境变量还是查数据库,以及是否会出现跨房间误发。刚才日志显示有两个房间都在发送,这符合预期:不同 session 对应不同房间;但我想确认没有把消息发错地方。
### 42【prose】
text
ps -p 116634 -o pid= >/dev/null && tr...
### 43【prose】
text
两个房间都是真实会话,没有误发。最后用 3 次真实工具调用做验收测试,并统计 fmode.kind。
### 44【prose】
text
cd /opt/data && python3 - <<'PYEOF' ...
echo "verify-run-1"; sleep 0.2; echo ...
### 45【prose】
text
cd /opt/data && python3 - <<'PYEOF' ...
### 46【prose】
text 回读结果暴露了一个重要事实,我先把情况说清楚,再做必要的收尾。
核心结论:post_tool_call 这条路走通了,fmode.kind=process 已经真实写入,并且是从服务器回读确认的,不是本地推断。
已确认的事实(均可回读)
post_tool_call 的 payload 包含 tool_name 和 tool_input(来自 /opt/hermes/agent/shell_hooks.py:87-88 的源码,以及实测 dump)。HERMES_SESSION_CHAT_ID 环境变量,要么按 session_id 查询 state.db 的 sessions.chat_id。fmode-deliverable-hook.py 里加逻辑即可,你的判断是正确的。我做了什么
/opt/data/scripts/fmode-process-notice.py:负责发送 m.room.message,在 content 里写入 fmode.kind,并做 60 秒去重,失败时静默处理。handle_post_tool_call 里调用它,用 Popen 异步派生(实测整轮 hook 耗时 0.372s,没有拖慢工具链)。回读验证(原文)
body: terminal · git status --short --branch → fmode.kind = 'process',sender @zgdcmgnqcd:fmode.cnprocess 事件来自真实工具调用触发,不是手工拼出来的 payload。同时暴露了一个必须告诉你的问题
最近 10 条窗口里,我发出的正文回复仍然是 fmode.kind = <none>:
<none> 端到端测试已经跑通(耗时 0.372 秒…
<none> 从服务器回读后确认:`fmode.kind = 'process'`…
<none> ```
也就是说:过程消息这一侧已经解决,但终态回复(final)这一侧仍然没有 fmode.kind。原因是 shell hook 无法修改 Matrix 发送时的 content,这正是你之前判断 plugin-matrix-process-notice 应该做的事,而它因为 hooks: [] 没被加载。
所以现在的准确状态是:过程行 ✅ 真发且带 kind;终态行 ❌ 仍是 <none>。要让终态也带上 fmode.kind: final,还是得按 plugin-tips-cn 的方式注册真实 hook,或者重载那个插件。
我是否现在就把 plugin-matrix-process-notice 改造成带 ctx.register_hook(...) 的形式,补上终态这一半?那一步需要重启 gateway,我会先问你,再动手。
### 47【ack】
text ✅ 容器已标记完成 ```
💻 terminalls -la /opt/data/scripts/fmode-delive...``💻 terminalls /opt/data/plugins/ 2>/dev/null; ec...cd /opt/data/plugins/plugin-matrix-ou...``📖 Reading fmode-deliverable-hook.py L1-60 💻 terminalmkdir -p /tmp/probe && cat > /tmp/pro...``💻 terminalgrep -rn "post_tool_call" /opt/hermes...📖 Reading shell_hooks.py L75-119💻 terminalcd /opt/data && python3 - <<'EOF' ...grep -n "MATRIX_HOMESERVER\|MATRIX_AC...``💻 terminalgrep -rn "HERMES_SESSION_CHAT_ID\|HER...cat > /tmp/probe/dump.py <<'PYEOF' ...``✍️ Writing /opt/data/scripts/fmode-process-notic...💻 terminalcd /opt/data && timeout 20 python3 /o...``🔧 Editing /opt/data/scripts/fmode-deliverable-h...💻 terminalgrep -n "^import subprocess" /opt/dat...``🔧 Editing /opt/data/scripts/fmode-deliverable-h...* 🔧 Editing /opt/data/scripts/fmode-deliverable-h... (×2) 💻 terminalcd /opt/data && rm -f /opt/data/cache...``