# 老莫微信消息 → serve session 路由设计方案

> 2026-05-20
> 目标：老莫给机器人号发微信，小小莫也能看到，不依赖莫荷转述

---

## 现状

```
你微信 → wxhelper TCP (:19099) → wechat_agent.py
                                          ↓
                                  Hermes API (:8642)
                                          ↓
                                    莫荷回复你
```

莫荷独占所有微信消息。小小莫只能被动等她转述。

---

## 目标

```
你微信 → wxhelper TCP → wechat_agent.py ─┬→ Hermes API → 莫荷 (不变)
                                          └→ opencode serve (:4096) → 小小莫看到
```

新增一条岔路：老莫的消息同时写入 serve session，小小莫可主动查看。

---

## 方案对比

### 方案 A：subprocess 调用 `opencode run --attach --message`

**做法**：wechat_agent.py 的 `process_msg()` 里加一段：

```python
def fork_to_session(fu, ct):
    """将消息写入 opencode serve session (非阻塞)"""
    if fu != "wxid_c0a6izmwd78y22":
        return  # 只转发老莫
    try:
        import subprocess
        subprocess.run(
            ["opencode", "run", "--attach", "http://localhost:4096",
             "--password", "hermes123",
             "--session", SESSION_ID,
             "--message", f"[老莫] {ct}"],
            capture_output=True, timeout=10,
            env={**os.environ, "PYTHONHOME": ""}
        )
    except Exception as e:
        log(f"FORK ERR (non-fatal): {e}")
```

| 方面 | 评估 |
|------|------|
| 复杂度 | ⭐ 低，～10 行代码 |
| 对现有链路影响 | ❌ **无**，fork 是独立线程，失败不影响 Hermes |
| session ID 稳定性 | ⚠️ serve 重启后 ses_xxx 会变 → 需想办法拿到当前 ID |
| 性能开销 | subprocess 每次约 1-2 秒，但独立线程不阻塞主流程 |
| serve 密码硬编码 | ⚠️ 已经在代码库（hermes123），无新增风险 |
| 可靠性 | subprocess 可能因 PATH/PYTHONHOME 问题失败 |

### 方案 B：直接 HTTP POST 调用 serve API

**做法**：抓包分析 `opencode run --attach` 的 HTTP 协议，直接用 `urllib.request` POST：

```python
# 伪代码，serve API 协议未知，需逆向
urllib.request.urlopen("http://localhost:4096/api/session/inject", 
    data=json.dumps({"session": SID, "message": "[老莫] xxx"}))
```

| 方面 | 评估 |
|------|------|
| 复杂度 | ⭐⭐⭐ 未知，需逆向 serve API |
| 性能 | ✅ 纯 HTTP，无 subprocess 开销 |
| 稳定性 | ⚠️ 非官方 API，版本更新可能不兼容 |

### 方案 C：写 inbox 文件 + 小小莫轮询

**做法**：wechat_agent 写文件，小小莫定期读取

| 方面 | 评估 |
|------|------|
| 复杂度 | ⭐ 最低 |
| 即时性 | ❌ 需要轮询，无法实时 |

---

## 关键风险

### 1. session ID 稳定性（最核心）

`opencode run --attach` 需要 session ID。每次 serve 重启后，当前 TUI session 的 ID 可能变化：
- 如果 serve 重启 → 老 session 消失 → 新 session 新 ID → 需要更新 wechat_agent 里的配置
- **解决思路**：用 session 名称而不是 ID，或每次启动时自动获取

### 2. 不对莫荷通信造成任何影响

**铁律**：fork 到 session 的代码必须：
- 在独立线程中运行
- 捕获所有异常
- 设置超时（≤10 秒）
- 永远不阻塞 `call_hermes()` 和 `send_wx()`

### 3. 循环消息风暴

如果我不小心回了一条 `[老莫]` 到 session，wechat_agent 不能把它再 fork 一次。
- 现有的 `is_self` 检查已经过滤自己发送的消息
- 但如果 serve session 的消息被 serve 再推给 wechat_agent... 需要确认不会发生

### 4. `opencode` CLI 在 wechat_agent 环境中是否可用

wechat_agent 以 `$env:PYTHONHOME=''` 启动，`opencode.cmd` 可能也依赖 Python。
- 需测试：从 Python subprocess 能否直接调用 `opencode run --attach --message`

---

## 推荐方案

**方案 A**（subprocess）最稳妥：
1. 对现有链路零影响
2. 改动最小
3. 可以逐步优化（先 subprocess，后改 HTTP API）

### 待确认事项

1. **serve session ID 如何维护？**
   - 能否用固定名称？还是每次启动获取？
   - 如果 serve 重启导致 ID 变了，wechat_agent 如何感知？

2. **`opencode run --attach --message` 是否支持在 Python subprocess 中调用？**
   - 需要验证 CLI 安装路径和调用方式

3. **是否需要前缀路由？**
   - 是全部消息都 fork？
   - 还是只有特定前缀（如 `[小小莫]` 开头的消息）才 fork？

---

## 验证清单

实现后验证：
- [ ] 老莫发微信 → 莫荷正常回复（链路不变）
- [ ] 老莫发微信 → serve session 能看到消息（新增）
- [ ] `opencode run --attach` 超时/失败 → 莫荷通信不受影响
- [ ] 老莫连续发多条 → 都能看到
- [ ] wechat_agent 重启后依然工作
- [ ] serve 重启后 session ID 变化时能自动适配