|
|
@@ -60,7 +60,7 @@ T+7s 容器上报 status=idle ← 本轮结束
|
|
|
### 2.1 配置清单(`/opt/data/.env`)
|
|
|
|
|
|
```bash
|
|
|
-# ---- 身份与连接 ----
|
|
|
+# ---- 身份与连接 ----[](https://)
|
|
|
MATRIX_HOMESERVER=https://matrix.fmode.cn
|
|
|
MATRIX_USER_ID=@xxx:fmode.cn
|
|
|
MATRIX_ACCESS_TOKEN=xxx
|
|
|
@@ -207,6 +207,7 @@ tr '\0' '\n' < /proc/$PID/environ | grep -E "MATRIX_ALLOWED|ALLOW_ALL|REACTIONS"
|
|
|
**发送时机**:`post_llm_call` 钩子里,`_session_mark_idle()` **之后**。
|
|
|
|
|
|
**要求**:
|
|
|
+
|
|
|
- 失败**只记日志**,不影响 turn 收尾与上报
|
|
|
- 与 ✅ reaction **完全独立**,不得互相替代
|
|
|
- `sessionKey` 用与 `currentSessions` 相同的值
|
|
|
@@ -233,7 +234,7 @@ def _thread_root(sid=None):
|
|
|
**建议改为**:
|
|
|
|
|
|
```python
|
|
|
- thread_id = row[0] if row else None
|
|
|
+thread_id = row[0] if row else None
|
|
|
if not thread_id:
|
|
|
# 一房间一会话下,房间号即会话标识,可安全兜底
|
|
|
return _chat_id_of(sid) or None
|
|
|
@@ -292,7 +293,7 @@ App 右侧「成果」栏按会话显示
|
|
|
| 日志 | `WARNING gateway.run: Unauthorized user (ignored): Dropped a message from unrecognized matrix user (@xxx:fmode.cn)` |
|
|
|
| 根因 1 | 改了 `.env` 但**没重启** gateway |
|
|
|
| 根因 2 | 进程里的 `MATRIX_ALLOWED_USERS` 是空串 → 走了 ALLOW_ALL 分支,白名单是「死配置」 |
|
|
|
-| 解法 | 重启后**必须**回读 `/proc/<pid>/environ` 确认 |
|
|
|
+| 解法 | 重启后**必须**回读 `/proc//environ` 确认 |
|
|
|
|
|
|
### 案例 B · s6 注入压过 `.env`(容器内无解)
|
|
|
|
|
|
@@ -334,164 +335,6 @@ MATRIX_ALLOWED_USERS= # ← 留空
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 五之二、插件移植:从 lj-test 复制整套能力
|
|
|
-
|
|
|
-> 🔴 **前置判断(2026-11-12 实测,务必先做)**
|
|
|
->
|
|
|
-> 新容器很可能**根本没有装任何 Fmode 插件**。实测 `agent-node-app-lj-test-v1`:
|
|
|
->
|
|
|
-> ```
|
|
|
-> /opt/data/plugins/ → 目录不存在
|
|
|
-> plugin-active-reporter → 全盘零结果
|
|
|
-> currentSessions / turnStartAt / sessionKey → 全盘零结果
|
|
|
-> com.fmode.turn_status → 零结果
|
|
|
-> ```
|
|
|
->
|
|
|
-> 这种容器只有 Hermes 原生 `platforms/matrix` 能力:能收发消息,
|
|
|
-> 但**不上报状态、没有会话表、没有文件交付**。
|
|
|
-> 此时要做的不是「改配置」,而是**整套插件移植**。
|
|
|
-
|
|
|
-### 5.2.1 lj-test 的插件清单(实测导出)
|
|
|
-
|
|
|
-| 插件 | 文件 | 必需 | 作用 |
|
|
|
-|---|---|---|---|
|
|
|
-| `plugin-active-reporter` | `__init__.py`(32,277 B)+ `plugin.yaml` | ✅ **核心** | 状态上报、`currentSessions`、turn_status |
|
|
|
-| `plugin-deliverable-inject` | `__init__.py`(5,410 B)+ `plugin.yaml` | ✅ | 文件交付链接注入正文 |
|
|
|
-| `plugin-turn-end` | `__init__.py`(13,202 B) | ⭕ 可选 | 回合结束信号(lj-test 中处于 `disabled`) |
|
|
|
-| `plugin-media-text-merge` | `__init__.py`(9,549 B) | ⭕ 可选 | 图文合并(lj-test 中未启用) |
|
|
|
-| `plugin-fmode/plugin` | 整套目录 | ⭕ 可选 | 通用能力(含 `poor_guard.py` 钩子) |
|
|
|
-| `plugin-tips-cn` | 整套目录 | ⭕ 可选 | 中文提示词增强 |
|
|
|
-| `plugin-topup-guard` | `__init__.py` | ⭕ 可选 | 充值保护 |
|
|
|
-
|
|
|
-> ⚠️ `plugin-active-reporter` 目录里还有多个 `.bak-*` 备份文件(累计 100+ KB),
|
|
|
-> **复制时排除**,只取 `__init__.py` 与 `plugin.yaml`。
|
|
|
-
|
|
|
-### 5.2.2 `config.yaml` 的注册方式
|
|
|
-
|
|
|
-插件**不是放进目录就生效**,必须在 `config.yaml` 的 `plugins.enabled` 里登记。
|
|
|
-lj-test 的实际配置(节选):
|
|
|
-
|
|
|
-```yaml
|
|
|
-plugins:
|
|
|
- enabled:
|
|
|
- - plugin-active-reporter
|
|
|
- - plugin-deliverable-inject
|
|
|
- - plugin-fmode/plugin
|
|
|
- - plugin-tips-cn
|
|
|
- - plugin-topup-guard
|
|
|
- - wecom-platform
|
|
|
- entries:
|
|
|
- plugin-fmode/plugin:
|
|
|
- allow_tool_override: true
|
|
|
- plugin-deliverable-inject:
|
|
|
- allow_tool_override: false
|
|
|
- disabled:
|
|
|
- - plugin-turn-end
|
|
|
-hooks:
|
|
|
- on_session_end:
|
|
|
- - command: python3 /opt/data/plugins/plugin-fmode/plugin/hooks/poor_guard.py
|
|
|
- timeout: 30
|
|
|
-```
|
|
|
-
|
|
|
-**最小可用组合**:只启用 `plugin-active-reporter` + `plugin-deliverable-inject`。
|
|
|
-
|
|
|
-### 5.2.3 移植步骤
|
|
|
-
|
|
|
-```bash
|
|
|
-# 1. 在源容器(lj-test)打包(排除备份)
|
|
|
-cd /opt/data/plugins
|
|
|
-tar czf /tmp/fmode-plugins.tgz \
|
|
|
- --exclude='*.bak-*' \
|
|
|
- plugin-active-reporter plugin-deliverable-inject
|
|
|
-
|
|
|
-# 2. 传到目标容器(用容器间可用的通道,或经 OBS)
|
|
|
-# 目标容器落地后:
|
|
|
-cd /opt/data
|
|
|
-tar xzf fmode-plugins.tgz -C /opt/data/plugins/ # 确保 plugins 目录已存在
|
|
|
-ls -l /opt/data/plugins/plugin-active-reporter/
|
|
|
-
|
|
|
-# 3. 编辑 config.yaml,把插件名加进 plugins.enabled
|
|
|
-# 步骤见 §5.2.2
|
|
|
-
|
|
|
-# 4. 重启(必须!见 §2.3)
|
|
|
-s6-svc -r /run/service/gateway-default
|
|
|
-
|
|
|
-# 5. 验证插件真的加载了
|
|
|
-PID=$(pgrep -f "gateway run" | tail -1)
|
|
|
-ps -o lstart= -p $PID # 进程启动时间应晚于文件 mtime
|
|
|
-grep -iE "registered:|plugin-active-reporter" /opt/data/logs/gateway.log | tail -10
|
|
|
-```
|
|
|
-
|
|
|
-> 🔴 **第 5 步是关键**:日志里必须出现 `registered: plugin-active-reporter`。
|
|
|
-> 没有这行 = 没加载成功,后面所有验证都不必做。
|
|
|
-
|
|
|
-### 5.2.4 依赖与环境变量
|
|
|
-
|
|
|
-`plugin-active-reporter` 需要:
|
|
|
-
|
|
|
-| 依赖 | 说明 |
|
|
|
-|---|---|
|
|
|
-| `FMODE_SESSION_TOKEN` | 调云函数 `xlosi3m46q` 的身份(主人 token) |
|
|
|
-| `AGENT_ID` | 容器自己的 agentId |
|
|
|
-| `MATRIX_ACCESS_TOKEN` / `MATRIX_HOMESERVER` | 推 `turn_status` 事件用(新代码会从 `.env` 读) |
|
|
|
-| Python 标准库 | `sqlite3` / `urllib` / `json`(**无需 pip 安装**) |
|
|
|
-
|
|
|
-**在目标容器 `.env` 里补齐**:
|
|
|
-
|
|
|
-```bash
|
|
|
-FMODE_SESSION_TOKEN=r:xxxx # 主人的平台 token
|
|
|
-AGENT_ID=agent-node-app-lj-test-v1 # 目标容器自己的 agentId
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 五之三、🔴 先判形态:五类容器,走五条不同的路
|
|
|
-
|
|
|
-> **这是本文最重要的一节。** 接入前**必须先判定容器属于哪一类**,
|
|
|
-> 否则会按错误的路径操作,白折腾一轮(2026-11-12 在 v1 上实际踩过)。
|
|
|
-
|
|
|
-| 形态 | 特征(怎么判) | 走哪条路 |
|
|
|
-|---|---|---|
|
|
|
-| **A · 完整**(lj-test) | 有插件、`capability_check decision=allow`、上报正常 | 只核对配置 |
|
|
|
-| **B · 有插件无授权** | 插件名在日志里出现,但 `decision=deny` | **补 `granted_capabilities`**(见 §六 阶段 2 场景 0.5) |
|
|
|
-| **C · 无插件** | `ls /opt/data/plugins/` 报 `No such file or directory`;全盘搜 `plugin-active-reporter` 零结果 | **插件移植**(见 §五之二) |
|
|
|
-| **D · 有插件但版本旧** | 有 `plugin-active-reporter`,但 grep 不到 `com.fmode.turn_status` / `userId` | 覆盖为新版(重新解包) |
|
|
|
-| **E · 准入被 s6 锁死** | `ls /run/s6/container_environment/ \| grep MATRIX_ALLOWED` 有输出 | **只能平台侧处理**(见 §5.2) |
|
|
|
-
|
|
|
-### 判定命令(一次跑完,30 秒)
|
|
|
-
|
|
|
-```bash
|
|
|
-echo "=== 形态判定 ==="
|
|
|
-# C 的判断:插件目录
|
|
|
-ls -d /opt/data/plugins 2>/dev/null && echo "有 plugins 目录" || echo "C: 无 plugins 目录"
|
|
|
-find /opt/data/plugins -maxdepth 1 -name 'plugin-*' -type d 2>/dev/null | head -5
|
|
|
-
|
|
|
-# B 的判断:能力授权
|
|
|
-grep -h "capability_check" /opt/data/logs/*.log 2>/dev/null | tail -4
|
|
|
-
|
|
|
-# D 的判断:插件版本
|
|
|
-grep -c "com.fmode.turn_status" /opt/data/plugins/plugin-active-reporter/__init__.py 2>/dev/null
|
|
|
-
|
|
|
-# E 的判断:s6 注入
|
|
|
-ls -l /run/s6/container_environment/ 2>/dev/null | grep -i matrix
|
|
|
-```
|
|
|
-
|
|
|
-**六类坑的对照(本文档全部覆盖)**:
|
|
|
-
|
|
|
-| 坑 | 现象 | 在哪解决 |
|
|
|
-|---|---|---|
|
|
|
-| K1 无插件 | 目录都不存在 | §五之三 形态 C |
|
|
|
-| **K2 缺能力授权** | `decision=deny`,插件装了等于没装 | §六 阶段 2 场景 0.5 |
|
|
|
-| K3 改配置没重启 | 授权不生效 | §六 阶段 2「四步法」第 ④ 步 |
|
|
|
-| K4 `yaml.safe_dump` 重写全文件 | 无关缩进被改 | §六 阶段 2「四步法」注意事项 |
|
|
|
-| K5 `s6-svc` 不在 PATH | `command not found` | 用 `/command/s6-svc` |
|
|
|
-| K6 系统 python 无 yaml | `ModuleNotFoundError` | 用 `/opt/hermes/.venv/bin/python3` |
|
|
|
-| **K7 自我升级污染对话** | 回复里出现「💾 修行成果」,甚至顶替答案 | `auxiliary.background_review.enabled: false` |
|
|
|
-| K8 无 `MATRIX_ACCESS_TOKEN` | 推 `turn_status` 可能失败 | §六 阶段 2 场景 0.6 |
|
|
|
-| K9 插件包 URL 拿不到 | 回复里被截断 | 让对方传固定路径,或用户浏览器取 |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
## 六、完整提示词(交给新容器执行)
|
|
|
|
|
|
> **使用方法**:把下面四段**按顺序**发给新容器,每段等它**交齐证据**后再发下一段。
|
|
|
@@ -517,22 +360,6 @@ ls -l /run/s6/container_environment/ 2>/dev/null | grep -i matrix
|
|
|
3. plugin-active-reporter/__init__.py 的修改时间(mtime)是什么?
|
|
|
→ 对比 A1 的进程启动时间:**文件比进程新就说明改动没生效**
|
|
|
|
|
|
-【A0. 🔴 先判断:你装了 Fmode 插件吗?(决定后面走哪条路)】
|
|
|
- ls -la /opt/data/plugins/ 2>&1 | head -20
|
|
|
- find / -name "plugin-active-reporter" -maxdepth 6 2>/dev/null | head -3
|
|
|
- grep -rn "currentSessions" /opt/data 2>/dev/null | head -3
|
|
|
- grep -rn "com.fmode.turn_status" /opt/data 2>/dev/null | head -3
|
|
|
-
|
|
|
- 明确回答:
|
|
|
- ① /opt/data/plugins/ 目录存在吗?
|
|
|
- ② 有没有 plugin-active-reporter / plugin-deliverable-inject?
|
|
|
- ③ 全盘搜索 currentSessions、turn_status 有没有命中?
|
|
|
-
|
|
|
- → 若三条都是「不存在/零结果」:你**没有装 Fmode 插件**,
|
|
|
- 后续不是"改配置"的问题,而要**从 lj-test 移植整套插件**(见文档 §五之二)。
|
|
|
- 请在报告里明确标注「类型:无插件容器,需移植」。
|
|
|
- → 若有插件:继续往下查具体配置差异。
|
|
|
-
|
|
|
【B. Matrix 配置】
|
|
|
4. 回读进程环境:
|
|
|
PID=$(pgrep -f "gateway run" | tail -1)
|
|
|
@@ -582,117 +409,6 @@ ls -l /run/s6/container_environment/ 2>/dev/null | grep -i matrix
|
|
|
|
|
|
需要修改的场景与对应做法:
|
|
|
|
|
|
-【场景 0】🔴 阶段 1 判定为「无插件容器」(最常见)
|
|
|
- 你没有 plugin-active-reporter,状态上报整条链路都不存在。
|
|
|
-
|
|
|
- **做法:从 lj-test 移植整套插件**(详见文档 §五之二)
|
|
|
-
|
|
|
- ① 让 lj-test 打包并导出(把下面这段发给 lj-test):
|
|
|
- cd /opt/data/plugins && tar czf /tmp/fmode-plugins.tgz \
|
|
|
- --exclude='*.bak-*' plugin-active-reporter plugin-deliverable-inject
|
|
|
- 然后把 tgz 用 OBS 或容器间通道传到本容器。
|
|
|
-
|
|
|
- ② 本容器解包:
|
|
|
- mkdir -p /opt/data/plugins
|
|
|
- tar xzf <路径>/fmode-plugins.tgz -C /opt/data/plugins/
|
|
|
- ls -l /opt/data/plugins/plugin-active-reporter/__init__.py
|
|
|
-
|
|
|
- ③ 在 config.yaml 的 plugins.enabled 里登记:
|
|
|
- - plugin-active-reporter
|
|
|
- - plugin-deliverable-inject
|
|
|
-
|
|
|
- ④ 补齐 .env 环境变量:
|
|
|
- FMODE_SESSION_TOKEN=r:xxx # 主人的平台 token
|
|
|
- AGENT_ID=<本容器自己的 agentId>
|
|
|
-
|
|
|
- ⑤ 重启并确认加载:
|
|
|
- s6-svc -r /run/service/gateway-default
|
|
|
- sleep 10
|
|
|
- grep -iE "registered:.*(reporter|deliverable)" /opt/data/logs/gateway.log | tail -5
|
|
|
- → **必须看到 registered 行**,否则插件没加载
|
|
|
-
|
|
|
-【场景 0.5】🔴 插件已装但被拒绝授权(v1 实测踩过,最容易漏)
|
|
|
- 现象:日志里能查到插件名,但每次 turn 都被拒:
|
|
|
- capability_check plugin=plugin-active-reporter capability=tools.override decision=deny
|
|
|
- evidence=not granted
|
|
|
- → **插件装了等于没装**,状态照样上报不出去。
|
|
|
-
|
|
|
- 根因:Hermes 是「默认拒绝」设计,必须在 config.yaml 显式授权。
|
|
|
- `plugins.enabled` 只负责「加载」,`plugins.entries.<名>.granted_capabilities` 才负责「放权」。
|
|
|
-
|
|
|
- 修法(config.yaml):
|
|
|
- plugins:
|
|
|
- entries:
|
|
|
- plugin-active-reporter:
|
|
|
- granted_capabilities:
|
|
|
- - tools.override
|
|
|
- plugin-deliverable-inject:
|
|
|
- granted_capabilities:
|
|
|
- - tools.override
|
|
|
-
|
|
|
-【场景 0.6】🔴 关闭 background_review(解决「自我升级日志污染对话」)
|
|
|
- 现象(用户实测截图):问「把完整下载地址贴出来」,
|
|
|
- 回复却是「这一轮的修行成果:🔧 给「multi-artifact-delivery」上了个补丁」,
|
|
|
- 用户的问题完全没被回答;有时还重复发送上一轮发过的文件。
|
|
|
-
|
|
|
- 根因:Hermes 每轮结束会跑 background review,
|
|
|
- 通过 `background_review_callback` 把摘要**发到对话里**
|
|
|
- (`/opt/hermes/agent/background_review.py:1148-1153`)。
|
|
|
-
|
|
|
- 修法(config.yaml,**官方开关**,不改只读源码):
|
|
|
- auxiliary:
|
|
|
- background_review:
|
|
|
- enabled: false
|
|
|
- 关掉后复盘不会启动 → 那条 💾 消息从源头就不产生。
|
|
|
-
|
|
|
- 验证:发一句闲聊,回复里**不得出现** 💾 / 修行 / 经验值 / 复盘。
|
|
|
-
|
|
|
-【config.yaml 修改四步法】(上面几个场景都按这个执行)
|
|
|
-
|
|
|
- 🔴 四个必踩的坑,全在这四步里避掉:
|
|
|
-
|
|
|
- ① 备份
|
|
|
- cp /opt/data/config.yaml /opt/data/config.yaml.bak-$(date +%Y%m%d-%H%M%S)
|
|
|
-
|
|
|
- ② 用 **venv 的 python** 改(系统 python 没有 yaml 模块 → ModuleNotFoundError)
|
|
|
- /opt/hermes/.venv/bin/python3 - <<'PY'
|
|
|
- import io, yaml
|
|
|
- p = '/opt/data/config.yaml'
|
|
|
- d = yaml.safe_load(io.open(p, encoding='utf-8'))
|
|
|
-
|
|
|
- # --- 按实际需要选一段 ---
|
|
|
- # (a) 插件登记
|
|
|
- pl = d.setdefault('plugins', {})
|
|
|
- en = pl.setdefault('enabled', [])
|
|
|
- for n in ['plugin-active-reporter', 'plugin-deliverable-inject']:
|
|
|
- if n not in en: en.append(n)
|
|
|
- # (b) 能力授权(关键!)
|
|
|
- ent = pl.setdefault('entries', {})
|
|
|
- for n in ['plugin-active-reporter', 'plugin-deliverable-inject']:
|
|
|
- gc = ent.setdefault(n, {}).setdefault('granted_capabilities', [])
|
|
|
- if 'tools.override' not in gc: gc.append('tools.override')
|
|
|
- # (c) 关复盘
|
|
|
- d.setdefault('auxiliary', {}).setdefault('background_review', {})['enabled'] = False
|
|
|
-
|
|
|
- io.open(p, 'w', encoding='utf-8').write(
|
|
|
- yaml.safe_dump(d, allow_unicode=True, sort_keys=False, default_flow_style=False))
|
|
|
- PY
|
|
|
-
|
|
|
- ③ 校验 + 核对 diff
|
|
|
- /opt/hermes/.venv/bin/python3 -c "import yaml,io;d=yaml.safe_load(io.open('/opt/data/config.yaml',encoding='utf-8'));print('top keys',len(d));print('entries',d.get('plugins',{}).get('entries'));print('bg',d.get('auxiliary',{}).get('background_review'))"
|
|
|
- diff /opt/data/config.yaml.bak-* /opt/data/config.yaml | head -40
|
|
|
-
|
|
|
- ⚠️ **K4 注意**:`yaml.safe_dump` 会**重写整个文件**,
|
|
|
- 可能把 `custom_providers` 之类的缩进也规范化掉。
|
|
|
- 结构等价但格式变了 —— 用 diff 确认「只出现预期的新增行」。
|
|
|
- 若不能接受,改用备份还原后**手工**加那几行。
|
|
|
-
|
|
|
- ④ 重启(**必须**,改 config 不重启等于没改)
|
|
|
- 🔴 **K5**:`s6-svc` 不在 PATH,必须用绝对路径
|
|
|
- /command/s6-svc -r /run/service/gateway-default
|
|
|
- sleep 12
|
|
|
- pgrep -af "gateway run"
|
|
|
-
|
|
|
【场景 1】MATRIX_ALLOWED_USERS 有值(多人共用打不通)
|
|
|
目标:GATEWAY_ALLOW_ALL_USERS=true 且 MATRIX_ALLOWED_USERS 为空
|
|
|
步骤:
|
|
|
@@ -785,30 +501,41 @@ grep -n "_principal_authorized" -A 20 <authz 文件>
|
|
|
通过标准:正文末尾有「已上云(可直接下载)」+ https 链接
|
|
|
且 listBySession(threadRoot=你的会话id) 能查到这条
|
|
|
|
|
|
-【测试 9】🔴 插件能力授权(v1 实测踩过:装了不生效)
|
|
|
+【测试 9】🔴 插件能力授权(装了不生效的典型)
|
|
|
grep -h "capability_check" /opt/data/logs/*.log | tail -10
|
|
|
-通过标准:
|
|
|
- - 最近一次 capability_check 必须是 `decision=allow`,
|
|
|
- 且 evidence=`granted_capabilities`
|
|
|
- (🔴 日志里的关键字是 **allow**,不是 grant —— 源码写的是
|
|
|
- "allow" if allowed else "deny",按 grant 去 grep 会永远是 0)
|
|
|
- - **只判定最近一次**:历史日志里会累计大量重启前的 `deny`,
|
|
|
- 不要用 `grep -c deny` 的总数判断(v1 实测:185 条 deny 全是重启前的历史)
|
|
|
-若最近一次仍是 deny → 回到阶段 2【场景 0.5】补 granted_capabilities,
|
|
|
- 并确认`/command/s6-svc -r` 真的执行了(改配置不重启不生效)
|
|
|
+通过标准:最近一次 capability_check 必须是 decision=allow,
|
|
|
+ 且 evidence=granted_capabilities
|
|
|
+🔴 日志关键字是 allow 不是 grant(源码是 "allow" if allowed else "deny")
|
|
|
+🔴 **只判定最近一次**:历史日志会累计重启前的 deny,别用 grep -c deny 的总数判断
|
|
|
|
|
|
【测试 10】🔴 对话纯净度(不许自我升级日志污染回答)
|
|
|
发一句闲聊,例如:「今天天气不错」
|
|
|
-通过标准:回复里**不得出现**以下任一:
|
|
|
- 💾、修行、经验值、复盘、自我进化、闭关、修为
|
|
|
-若出现 → 回到阶段 2【场景 0.6】关闭 background_review,并确认已重启
|
|
|
+通过标准:回复里不得出现 💾 / 修行 / 经验值 / 复盘 / 自我进化 / 闭关 / 修为
|
|
|
+若出现 → 回到阶段 2【场景 0.6】关闭 background_review
|
|
|
+
|
|
|
+【测试 11】🔴🔴 currentSessions 格式(最隐蔽,最容易漏判)
|
|
|
+回读 extra.currentSessions,**逐条**检查格式,不要只看"有没有数据"。
|
|
|
+每一条必须满足:
|
|
|
+ - sessionKey = $approot_xxx(会话 id) 或 !roomId
|
|
|
+ ❌ 若是纯随机串(如 woAs2qCQAAsxr...)→ 该会话在 App 里永远不转圈
|
|
|
+ - status = running 或 idle
|
|
|
+ - userId = @xxx:fmode.cn(Matrix 账号格式)
|
|
|
+ ❌ 若与 sessionKey 相同、不是 @ 开头 → 格式错误
|
|
|
+ - updatedAt 是毫秒时间戳
|
|
|
+通过标准:**每一条**都符合上述格式(不是"至少有一条")
|
|
|
+
|
|
|
+【测试 12】🔴 结束信号有兜底
|
|
|
+turn_status 连续失败时会发生什么?
|
|
|
+grep -iE "turn_status failed" /opt/data/logs/*.log | tail -5
|
|
|
+若失败时只 log 就吞掉、无重试无告警 → 必须记录为已知限制
|
|
|
+(App 侧靠 15 秒轮询兜底,但会有延迟)
|
|
|
|
|
|
输出格式:
|
|
|
| 测试 | 实测输出 | 通过? |
|
|
|
全部 ✅ 才能说「自测通过」。
|
|
|
```
|
|
|
|
|
|
-**通过条件**:**10 项**全绿,每项附原始输出。有任一项 ❌ 则回到阶段 2。
|
|
|
+**通过条件**:**12 项**全绿,每项附原始输出。有任一项 ❌ 则回到阶段 2。
|
|
|
|
|
|
---
|
|
|
|
|
|
@@ -863,24 +590,42 @@ grep -h "capability_check" /opt/data/logs/*.log | tail -10
|
|
|
| 文件回复里没有下载链接 | `_thread_root()` 返回 None 跳过了登记 | grep 日志 `SKIP register` |
|
|
|
| 所有历史会话同时转圈 | 容器级 status=running 被当会话级用 | App 侧问题,非容器 |
|
|
|
| 回复很慢(7~9 秒) | DNS 卡顿 | §阶段3 测试 1 |
|
|
|
-| **插件装了没反应** | **缺 `granted_capabilities` 授权**(K2) | `grep capability_check` 看是否 `deny` |
|
|
|
-| **回复里出现「💾 修行成果」** | background_review 把摘要发进了对话(K7) | `auxiliary.background_review.enabled: false` |
|
|
|
-| **自我升级日志顶替了对问题的回答** | 同上(K7) | 同上,并检查是否重启 |
|
|
|
-| `s6-svc: command not found` | 不在 PATH(K5) | 用 `/command/s6-svc` |
|
|
|
-| `ModuleNotFoundError: No module named 'yaml'` | 系统 python 没有 yaml(K6) | 用 `/opt/hermes/.venv/bin/python3` |
|
|
|
-| 改 YAML 后无关缩进也变了 | `yaml.safe_dump` 重写全文(K4) | `diff` 核对;必要时手工改 |
|
|
|
-| 容器回复里的 URL 总是被截断 | 输出长度限制(K9) | 让它传到固定路径,或用户浏览器取 |
|
|
|
+| **`sessionKey` 是纯随机串**(如 `woAs2qCQAAsxr...`) | 不是会话 id 也不是房间号,App **匹配不上** → 该会话不转圈 | 回读 `extra.currentSessions`,看是否符合 §3.2 格式 |
|
|
|
+| **`userId` 不是 `@xxx:fmode.cn`** | 格式不对,多人共用时无法区分谁在跑 | 同上 |
|
|
|
+| **`turn_status` 偶发 400 且无日志** | 插件 `except` 里只 `log` 就吞掉,**不留响应体** | 让插件记录 `str(e)` 之外的 HTTP body |
|
|
|
+| **某房间 turn_status 持续 400** | 无重试无降级 → App 收不到结束信号,**且没人会发现** | 需加失败计数/告警 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七之二、修复后验收时容易漏掉的 4 项(2026-11-12 实测)
|
|
|
+
|
|
|
+容器修完 403/404 后,**能写状态 ≠ 完整可用**。以下 4 项要单独验:
|
|
|
+
|
|
|
+| # | 项 | 现象 | 判定方法 |
|
|
|
+|---|---|---|---|
|
|
|
+| **V1** | **`sessionKey` 格式异常** | 出现纯随机串(如 `woAs2qCQAAsxr4sIqtTSsqb7FO9eoxqw`),既不是 `$approot_xxx` 也不是 `!roomId` | 回读 `extra.currentSessions`,逐条比对 §3.2 |
|
|
|
+| **V2** | **缺 ✅ reaction** | 消息发出后没有 ✅ | `grep MATRIX_REACTIONS`,应为 `true` |
|
|
|
+| **V3** | **文件交付无「已上云」链接** | 回复里没有下载链接 | 检查 `plugin-deliverable-inject` 是否安装并启用 |
|
|
|
+| **V4** | **token 轮换后不生效** | 换了 `.env` 的 token 但进程仍用旧值 | 插件 token 是进程内缓存,**换 token 必须重启** |
|
|
|
+
|
|
|
+> 🔴 **V1 最隐蔽**:明明 `currentSessions` 有 7 条数据、报告标了 ✅,
|
|
|
+> 但其中 1 条 `sessionKey` 是随机串 → 那一条在 App 里**永远不会转圈**。
|
|
|
+> 判断标准只有一条:**能不能对上 §3.2 的格式**,不要看"有没有数据"。
|
|
|
|
|
|
---
|
|
|
|
|
|
-## 八、本次会话中发现的 lj-test 未决项
|
|
|
+## 八、本次会话中发现的未决项
|
|
|
|
|
|
| # | 项 | 状态 |
|
|
|
|---|---|---|
|
|
|
| 1 | `_push_turn_status()` 已写入代码,**但 gateway 尚未重启** | ⏳ 重启后生效 |
|
|
|
| 2 | `currentSessions` 保留历史记录长达 13 小时(不清理) | ⚠️ 建议 `idle` 超 5 分钟清理 |
|
|
|
| 3 | `_thread_root()` 取不到 thread 时返回 None | ⚠️ 建议加房间号兜底 |
|
|
|
+| 4 | **`turn_status` 失败时只 log 不记 body** | ⚠️ 无法定位 400 根因 |
|
|
|
+| 5 | **`turn_status` 无重试、无降级、无告警** | ⚠️ 持续失败会静默丢结束信号 |
|
|
|
+| 6 | **token 为进程内缓存,轮换需重启** | ⚠️ 建议启动时校验 token 新鲜度 |
|
|
|
|
|
|
---
|
|
|
|
|
|
*本文所有数据为 2026-11-12 实测,非推断。*
|
|
|
+
|