# Cron Job: 热点雷达

**Job ID:** 472ca7495b44
**Run Time:** 2026-06-26 16:31:13
**Schedule:** */30 8-22 * * *

## Prompt

[IMPORTANT: The user has invoked the "wecom-topic-assistant" skill, indicating they want you to follow its instructions. The full skill content is loaded below.]

---
name: wecom-topic-assistant
description: "企微选题助手 — we-mp-rss 文章抓取、企微群推送、Hermes 直接作为企微机器人（替代 MCP 插件架构）。"
version: 1.0.0
platforms: [linux]
---

# 企微选题助手 (chunkaonewshub)

基于 we-mp-rss 抓取公众号文章，通过企微群推送 + AI 智能机器人实现选题策划对话。

## 服务器拓扑（2026-06-24 统一后）

单服务器架构，全部运行在北京 Lighthouse：

| 服务器 | 实例ID | 公网IP | 角色 |
|--------|--------|--------|------|
| **北京** | ins-jrstx6f5 | 120.53.228.229 | Hermes Agent + we-mp-rss 全部 |

上海 (ins-5mj1p2zt, 110.40.207.3) 已停用。

cloudflared tunnel `40f60a3f` 路由：
- `rss.gdcjgk.net` → `localhost:8001` (we-mp-rss)
- `callback.gdcjgk.net` → `localhost:8645` (Hermes wecom_callback)
- `bot.gdcjgk.net` → `localhost:8645`
- `files.gdcjgk.net` → `localhost:8088` (文件管理器)

企微自建应用回调 URL：`https://callback.gdcjgk.net/wecom/callback`

- 项目根目录: `~/project/`
- 脚本目录: `~/project/scripts/`
- we-mp-rss 源码: `~/project/we-mp-rss-main/`
- we-mp-rss 数据库: `~/project/we-mp-rss-data/db.db`
- 状态文件: `~/project/state/`
- 配置文件: `~/project/.env`

## 核心组件

| 组件 | 端口 | 说明 |
|------|------|------|
| we-mp-rss | 8001 | 公众号文章抓取 + REST API（systemd 管理） |
| Hermes Agent | - | 企微机器人、cron 调度、内容生成 |
| Hermes wecom_callback | 8645 | 接收企微回调消息 |
| Cloudflare Tunnel | - | 公网穿透（tunnel 40f60a3f） |
| ~~MCP Server~~ | ~~8100~~ | **已废弃** |

## 企微推送架构（重要：不要混淆推送通道）

群里文章更新推送由 **Hermes cron job**（`94d230f73e7f` 春考选题-文章抓取）完成，**不是** we-mp-rss 内置的 message_tasks。三者独立：

| 通道 | 用途 | 依赖 |
|------|------|------|
| 文章更新推送 | 每120分钟抓取最新文章 → 推送到群 | Hermes cron `94d230f73e7f` → fetch_and_push.py → 群 webhook |
| 智能机器人 | 群内 @交互、webhook 直接推送 | 群聊 → 群管理 → 添加智能机器人 |
| wecom_callback DM | 选题日报、文章审核等私聊推送 | cloudflared tunnel + Hermes 内置回调 |

⚠️ we-mp-rss 内置的 message_tasks 表实际为空（未配置），所有群推送实际走的是 Hermes cron + fetch_and_push.py 脚本。不要试图通过 message_tasks API 排查群推送问题——查 cron job `94d230f73e7f` 和脚本 `~/project/scripts/fetch_and_push.py`。
⚠️ 删掉智能机器人后：文章更新推送不受影响（走独立 webhook），但无法 @交互。**DM 通道完全独立**，继续正常工作。

**内容审核推送模式：** 选题日报和每日发布同时推给多人审核（TuKeXin + 曹小芬），cron job 的 deliver 设置为 `wecom_callback:TuKeXin,wecom_callback:CaoXiaoFen`。

## WeCom 回调验证与调试

回调 URL: `https://callback.gdcjgk.net/wecom/callback`

**架构链路：** 企微 → Cloudflare → tunnel 40f60a3f → localhost:8645 (Hermes)

**关键诊断方法：** 用 `curl -sI` 看 `Server` 响应头区分问题层级：
- `server: cloudflare` = 没进 tunnel，Dashboard 缺少 Public Hostname
- `Server: Python/3.11 aiohttp` = 到达 Hermes，Token/AES Key 不匹配

**⚠️ 回调验证通过 ≠ 消息回复能通。** 详见 IP 白名单说明。

详见 `references/wecom-callback-debugging.md`。

## 架构演进：Hermes 直接作为企微机器人（当前方案）

**旧架构已废弃：** 企微 AI 机器人（内置 DeepSeek）→ MCP 插件 → MCP Server（调 DeepSeek）→ 返回。三重代理，MCP 不稳定。

**新架构：** Hermes 直接接入企微，作为群聊机器人。无需 MCP 中转层，无需额外 DeepSeek API 调用。

| 能力 | 旧方案 | 新方案 |
|------|--------|--------|
| 数据查询 | MCP Server → SQLite | Hermes 直接查 SQLite |
| 内容生成 | MCP → DeepSeek API | Hermes 直接推理生成 |
| 群内 @ 响应 | 企微 AI 机器人 → MCP | Hermes 直接回复 |
| 定时推送 | 脚本调 DeepSeek → webhook | Cron → Hermes 分析 → 推送 |
| 去重/记忆 | MCP 无状态 | Hermes 有持久化 memory + skill |

**关键变化：**
- MCP Server (`mcp_server.py`, 端口 8100) 已停止并禁用
- 选题日报和热点雷达的 cron job 不再运行 Python 脚本调 DeepSeek，而是由 Hermes 自己推理
- `mcp.icoach.chat` 域名已移除（MCP Server 停用，域名从隧道删除）

**群内交互：** 用户在企微群 @Hermes → Hermes 收到消息 → 可查数据库、搜网络、生成文案 → 直接回复到群

## 选题日报（每日 9:00）— 已修复AI幻觉，2026-06-26

自动分析过去 36 小时文章，生成选题推荐推送。运行 `~/.hermes/scripts/daily_topic_data.py 36` 采集数据，Hermes 分析趋势并推荐选题。

**⚠️ 关键修复：** AI 自写 SQL 频繁误读 Unix 时间戳。改为固化脚本查库输出格式化文本，AI 只做分析。

⚠️ 曾用 `0 9 * * *`，因 DeepSeek API 在 9:00 整点高峰频繁无响应（多 job 同时抢 API，详见「Cron Job 运维 → API 高峰时段超时」），提前 5 分钟错开。

**工作流（Hermes 自主推理）：**
1. 复制 we-mp-rss DB 到 /tmp/ 避免锁冲突
2. SQL 查询最近 36 小时文章（articles JOIN feeds，取 title/url/source/publish_time）
3. 加载选题日历 `~/project/config/exam_calendar.json`，提取未来 14 天内节点
4. Hermes 直接分析趋势 + 生成选题推荐（不调外部 API）
5. 输出到 cron deliver 渠道

**输出格式（字节数 ≤4000）：**
- 📊 今日概况 — 2-3 句纯文本
- 🔥 值得追的选题 — 3-5 个，每个含切入角度 + 参考文章
- 💡 可储备选题
- ⚠️ 结合考试日历的提醒

### 旧方案：Python 脚本（备选）

**脚本：** `scripts/daily_topics.py` — 同上流程但调 DeepSeek API 生成

## 选题日报（每日 9:00）— 已修复AI幻觉，2026-06-26

自动分析过去 36 小时文章，生成选题推荐推送。运行 `~/.hermes/scripts/daily_topic_data.py 36` 采集数据，Hermes 分析趋势并推荐选题。

**⚠️ 关键修复：** AI 自写 SQL 频繁误读 Unix 时间戳。改为固化脚本查库输出格式化文本，AI 只做分析。

**工作流（Hermes 自主推理）：**
1. 复制 DB 到 /tmp/，SQL 查询最近 3 小时文章
2. 文章 < 2 篇 → 静默退出
3. 关键词预筛选：不同来源标题有 ≥2 个共同关键词 → 候选
4. 去重合并同一话题组，查 `~/project/state/hot_radar_sent.json` 避免重复推送
5. Hermes 判断是否同追一个事件
6. 有热点 → 推送；无热点 → 静默

**推送格式：**
```
🚨 热点预警
**话题名**
>判断依据
> [来源1] [标题](链接)
> [来源2] [标题](链接)
```

推送后更新 `hot_radar_sent.json` 记录已推送文章ID。

### 旧方案：Python 脚本（备选）

**脚本：** `scripts/hot_radar.py` — 关键词预筛选后调 DeepSeek API 验证

## 选题日历

配置文件：`~/project/config/exam_calendar.json`。日报自动加载未来 14 天内节点并织入选题建议。全年节点速查见 `references/exam-calendar.md`。

## we-mp-rss 操作

### 启动（systemd 管理）

```bash
# 服务已设为 systemd，开机自启
sudo systemctl start we-mp-rss
sudo systemctl status we-mp-rss

# 手动启动（调试用）
cd ~/project/we-mp-rss-main && source venv/bin/activate
DB="sqlite:////home/ubuntu/project/we-mp-rss-data/db.db" \
ENABLE_JOB=True PORT=8001 BROWSER_TYPE=chromium \
python main.py
```

**⚠️ 常见启动失败：`ImportError: cannot import name 'router' from 'apis.message_task'`**
`apis/message_task.py` 可能是空文件（0 bytes）。该文件只需导出一个 FastAPI router 供 `web.py` 注册。最小修复：

```python
from fastapi import APIRouter
router = APIRouter(prefix="/wx/message_tasks", tags=["消息任务"])
```

实际的 message_task 路由在 `apis/cascade.py` 中。详见 `references/we-mp-rss-deploy.md`。

### we-mp-rss 从零部署

完整部署流程（源码获取、依赖安装、Chromium、systemd 服务、Tunnel 路由）见 `references/we-mp-rss-deploy.md`。

### API 路径

**注意：** API 的 base path 是 `/api/v1/wx`（不是 `/api/v1`）。

| 操作 | 方法 | 路径 |
|------|------|------|
| 消息任务列表 | GET | `/api/v1/wx/message_tasks` |
| 创建消息任务 | POST | `/api/v1/wx/message_tasks` |
| 更新消息任务 | PUT | `/api/v1/wx/message_tasks/{id}` |
| 执行消息任务 | GET | `/api/v1/wx/message_tasks/{id}/run?isTest=false` |
| 重载任务 | PUT | `/api/v1/wx/message_tasks/job/fresh` |

### 认证

使用 AK/SK 认证，格式: `Authorization: AK-SK <ak>:<sk>`

AK/SK 在 `.env` 中配置:
```
WE_MP_RSS_AK=xxx
WE_MP_RSS_SK=xxx
```

### 查询数据库

```bash
cd ~/project/scripts && ~/project/we-mp-rss-main/venv/bin/python3 <script>.py
import sqlite3
db = sqlite3.connect('../we-mp-rss-data/db.db')
db.row_factory = sqlite3.Row
# 查询文章
rows = db.execute('SELECT a.id, a.title, f.mp_name FROM articles a JOIN feeds f ON a.mp_id=f.id ORDER BY a.publish_time DESC LIMIT 10').fetchall()
for r in rows: print(dict(r))
"
```

### Python 脚本运行

所有脚本必须在 we-mp-rss 的 venv 下运行:
```bash
cd ~/project/scripts && ~/project/we-mp-rss-main/venv/bin/python3 <script>.py
```

### Web 登录凭据

rss.gdcjgk.net 网站登录（JWT token）:
- 用户名: `gdcjgk`
- 密码: `admin888`

初始化逻辑在 `init_sys.py`：`os.getenv("USERNAME", "admin")` / `os.getenv("PASSWORD", "admin@123")`。
部署时设置了 `USERNAME=gdcjgk`、`PASSWORD=admin888`，因此凭据与默认值不同。

### 文章抓取推送脚本（已修复去重，2026-06-26）

**脚本:** `~/project/scripts/fetch_and_push.py` — Hermes cron `94d230f73e7f`（春考选题-文章抓取）每 120 分钟调用。

**v2 关键修复——去重：**
- 旧版：每次取 API 最新 10 篇全量推送，同一批文章一天推 12 次
- 新版：`~/project/state/pushed_articles.json` 记录已推送文章 ID，只推新文章；无新文章自动静默（不推消息）；每次最多推 5 篇
- 验证：第一次跑 `30 new → push 5`，第二次 `0 new → silent`

**工作流:**
1. 从 `.env` 读取 `WE_MP_RSS_AK`/`WE_MP_RSS_SK` 作为登录凭据 → 获取 JWT token
2. 列出全部公众号 → 逐个触发文章更新
3. 取最新 30 篇文章 → 过滤已推送 ID → 取前 5 篇新文章
4. 格式化 markdown → POST 到 `QW_WEBHOOK_URL`
5. 更新 `pushed_articles.json` 记录

- 使用 `WE_MP_RSS_AK`/`WE_MP_RSS_SK` 作为用户名/密码登录（而非 AK/SK 认证）

**关键细节:**
- 使用 `WE_MP_RSS_AK`/`WE_MP_RSS_SK` 作为用户名/密码登录（而非 AK/SK 认证），因为 AK/SK API 认证需要先创建 access_key，而脚本直接用用户帐密获取 bearer token
- webhook 硬编码在脚本中作为 fallback，如需换群应设 `QW_WEBHOOK_URL` 环境变量

## 消息推送

### 创建消息推送任务

`mps_id` 格式必须是 `[{"id": "MP_WXS_xxx"}, ...]`（对象数组，不是字符串数组）。

```bash
curl -X POST "http://localhost:8001/api/v1/wx/message_tasks" \
  -H "Authorization: AK-SK $AK:$SK" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "推送名称",
    "message_type": 0,
    "message_template": "📰 {{ feed.mp_name }} 更新\n{% for article in articles %}\n> {{ article.title }}\n{% endfor %}",
    "web_hook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx",
    "mps_id": "[{\"id\": \"MP_WXS_3886864470\"}, {\"id\": \"MP_WXS_3596295911\"}]",
    "cron_exp": "*/30 * * * *",
    "status": 1
  }'
curl -X PUT "http://localhost:8001/api/v1/wx/message_tasks/job/fresh" \
  -H "Authorization: AK-SK $AK:$SK"
```

### 模板语法

使用 `{{ }}` 双括号，支持 `{% for %}` / `{% if %}` 控制流。

上下文变量:
- `{{ feed.mp_name }}` — 公众号名称
- `{{ article.title }}` — 文章标题
- `{{ article.url }}` — 文章链接
- `{{ article.publish_time }}` — 发布时间

### 直接通过 webhook 推送

消息任务可能因为文章已被抓取而不推送。可以直接通过企微群机器人 webhook 发送:

```python
requests.post(webhook_url, json={
    "msgtype": "markdown",
    "markdown": {"content": "markdown内容"}
})
```

**⚠️ 企微 markdown 限制是 4096 BYTES（不是字符）！** 中文字符 UTF-8 编码每字 3 bytes，所以实际只能推送约 1300 个中文字。推送前必须按 bytes 截断，不是按字符数截断。`errcode:40058` 就是超过 bytes 限制。

## Cloudflare Tunnel

### 当前 Tunnel 映射

Tunnel ID: `40f60a3f-8a68-4ae3-bf7d-8d49951975ce`，账号 `60fe57f5a5a310cd8073e41f82546c79`

| 域名 | → 服务 | 所在服务器 |
|------|--------|-----------|
| `rss.gdcjgk.net` | `localhost:8001` | 北京 (we-mp-rss) |
| `callback.gdcjgk.net` | `localhost:8645` | 北京 (Hermes wecom_callback) |
| `bot.gdcjgk.net` | `localhost:8645` | 北京 |
| `files.gdcjgk.net` | `localhost:8088` | 北京 (文件管理器) |

### 添加新的 Public Hostname

⚠️ **两步缺一不可**：

1. **Cloudflare Zero Trust Dashboard**（浏览器操作）:
   Networks → Tunnels → 40f60a3f → Public Hostname → Add
   Subdomain + Domain + Service (HTTP + localhost:PORT)
   ——这一步让 Cloudflare 知道把该域名的流量发给 tunnel

2. **服务器 `~/.cloudflared/config.yml`**（已由 tunnel 管理，通常自动同步）:
   ingress 规则告诉 tunnel 收到流量后转发到哪个本地端口

**常见陷阱：只改了 config.yml 没在 Dashboard 加 Public Hostname。** 结果：`curl -sI https://新域名` 看到 `server: cloudflare`（Cloudflare 边缘直接返回），而不是 `server: Python/3.11 aiohttp`（到达了 Hermes）。

**双服务器陷阱**：同一 Tunnel 在两台服务器运行 → 负载均衡。一台停机后存活服务器承接全部流量，但停机服务器独有的服务（如 we-mp-rss）会 502。诊断：`journalctl -u cloudflared-tunnel | grep "Registered tunnel connection"` 看连接数。

WeCom 回调完整排障流程见 `references/wecom-callback-debugging.md`。

## 网站自动发布 (gdcjgk.net)

广东高职高考信息网支持通过 API 自动发布：`we-mp-rss 文章 → AI 改写 → POST 到网站`。

详见 `references/fastadmin-cms-publishing.md`（含 API 调用、栏目 ID、缩略图随机选取方案）。

**每日自动发布 cron job:** `533d92e64b59` (gdcjgk-每日发布)，每天 10:00 自动执行：
1. 查询 we-mp-rss 数据库最近 36 小时文章
2. AI 评分筛选（信息增量 0-5 + 时效性 0-3 + 可改写性 0-2，≥7 分入选）
3. 基于 gdcjgk-style-guide 撰写 800-1000 字原创文章
4. 通过 FastAdmin CMS API 存为草稿 (`status=draft`)
5. 企微通知审核（含后台编辑链接）

质量门槛：宁缺毋滥，不足 5 篇就少发。

## Cron Job 运维

### 投递失败排查

**现象：** cron job 执行成功（`last_status: ok`），但用户收不到消息，`last_delivery_error: "platform 'weixin' not configured/enabled"`。

**根因：** 旧的 Hermes 版本使用 `weixin` 作为企微平台名，升级后改为 `wecom`。已创建的 cron job 的 `deliver` 字段仍指向旧平台名。

**修复：** `cronjob action=update job_id=xxx deliver=origin` 将投递目标重置为当前聊天。如需多目标投递，用逗号分隔：`deliver=origin,wecom:TuKeXin`（群 + 个人 DM）。

**验证：** `cronjob action=run job_id=xxx` 手动触发一次，确认能收到消息。

### API 高峰时段超时（DeepSeek 特定）

**现象：** cron job 在 9:00 整点运行报 `RuntimeError: [Errno 32] Broken pipe`，agent 日志显示 `Stream stale for 180s — no chunks received`，连续 3 次重试各等 180 秒后全部失败。**同时段其他 cron job 也受波及。**

**根因：** DeepSeek API (`deepseek-v4-pro`) 在每日 9:00 整点高峰时段完全无响应——TCP 连接建立但服务端不发任何数据（不是返回错误，是零 chunk）。多个 cron job 同时触发（选题日报 + 小红书选题 + 热点雷达都在 9:00 同一秒）加剧了争抢。

**排查方法：**
```bash
# 1. 按 session ID 查 agent 日志（格式：cron_<job_id>_<timestamp>）
grep "cron_218c1e953f63" ~/.hermes/logs/agent.log | grep "Stream stale\|Broken pipe\|no chunks"

# 2. 查 errors.log
grep "cron_218c1e953f63" ~/.hermes/logs/errors.log | tail -10

# 3. 查 cron 输出文件
ls -lt ~/.hermes/cron/output/<job_id>/ | head -3
```

关键信号：3 次日志中都有 `no chunks received` = API 侧问题（不是网络抖动）；只有偶尔一次 = 可忽略。

**验证 API 是否恢复：**
```bash
cronjob action=run job_id=218c1e953f63
# 等约30秒后查看，若有 tool 调用（terminal/read_file）= API 正常
```

**修复：错开 cron 调度（已验证有效）**

| Job | ID | 原时间 | 新时间 | |
|-----|-----|--------|--------|---|
| 选题日报 | 218c1e953f63 | 0 9 * * * | **55 8 * * *** | ← 最早触发，最重要 |
| 小红书每日选题 | c27628909af0 | 0 9 * * * | **5 9 * * *** | ← 等选题日报跑完 |
| 热点雷达 | 472ca7495b44 | */30 8-22 * * * | 不变 | ← 自然错开 |

```bash
cronjob action=update job_id=218c1e953f63 schedule='55 8 * * *'
cronjob action=update job_id=c27628909af0 schedule='5 9 * * *'
```

**不要做：** 在 9:00 手动触发多个 job 验证——会加剧拥堵。先错开再逐个手动验证。

**历史记录：**
- 2026-06-25: 3 次重试中前 2 次失败，第 3 次侥幸成功（延迟 371 秒）
- 2026-06-26: 3 次重试全部失败，10 分钟耗尽。同年小红书选题和热点雷达同时炸
- 2026-06-26 09:19: 手动触发 → API 恢复正常，证明只是整点高峰问题



## 企微新版 AI 机器人 + MCP 插件（⚠️ 已废弃）

> **此方案已废弃。** Hermes 现在直接接入企微作为机器人，不再需要 MCP 中转层。
> 以下内容保留作为历史参考，部署新环境时请使用上方「Hermes 直接作为企微机器人」方案。

### 机器人类型区别

| 类型 | 连接方式 | 能接收消息 | 能对话 |
|------|----------|-----------|--------|
| 群机器人 | Webhook URL | ❌ | ❌ (只能推送) |
| 旧版智能机器人 | Bot ID + Secret + WebSocket | ✅ | ✅ |
| **新版 AI 机器人** | MCP 插件 (Streamable HTTP) | ✅ | ✅ (内置 LLM) |

新版 AI 机器人自带 DeepSeek，只需通过 MCP 插件给它数据访问能力。

### MCP Server 部署

MCP Server 提供三个工具:
1. `get_today_articles` — 查询今日文章列表
2. `get_article_detail` — 查询文章详情（含正文前 2000 字）
3. `generate_distribution` — 基于话题生成平台分发文案

**`generate_distribution` 参数：**
- `topic`（必填）：话题名称或文章链接
- `platforms`（选填）：`["公众号","微信群","朋友圈","小红书"]` 的子集。不传默认四个全出。

**generate_distribution 工作方式：** 收到请求后调 DeepSeek API（`deepseek-chat`，`response_format: json_object`），返回四/指定平台的标题+文案 JSON。MCP 返回结果给机器人，机器人格式化后直接回复在群里。

**完整触发链路：** 群内 @机器人 → 机器人根据提示词决定调工具 → MCP `/rpc` 端点处理 → MCP Server 调 DeepSeek → 返回 JSON → 机器人格式化回复

代码: `~/project/scripts/mcp_server.py`

**传输协议：仅用 Streamable HTTP (`/rpc`)，不能用 SSE。** SSE 在企微上 session 频繁丢失导致插件被移除。

**`Mcp-Session-Id` 头必须返回：** 企微使用多台服务器轮询 MCP 请求，`initialize` 响应必须包含 `Mcp-Session-Id` 头，否则后续 `tools/call` 会被企微内部拒绝（报「工具名称无法正确匹配」）。

详细实现见 `wecom-bot-integration` 技能的 `references/mcp-server-implementation.md`。

### 企微后台配置

1. 应用管理 → 智能机器人 → 选择机器人 → API/MCP 插件
2. 添加 MCP 插件:
   - 插件 URL: `https://mcp.icoach.chat/rpc`
   - 传输协议: **Streamable HTTP**（不是 SSE）
   - 授权方式: 不需要授权

### 机器人指令（System Prompt）

**格式约束：** 企微机器人指令不支持代码块、Markdown 表格、emoji。包含这些会导致「AI 处理失败」。

**写法原则：** 
- **不要写成 API 文档**。不要写「当用户输入 X 时调用工具 Y，参数 Z」——太刻板，触发率低。
- **用「听到→做」模式**。写场景化描述而非规则列表。例如「看到有人说"帮我写一篇XX的"就调 generate_distribution」而不是「调用 generate_distribution(topic, platforms?)」。
- **工具只描述功能，不写匹配规则**。DeepSeek 会根据工具描述自动判断何时调用，不需要手写 if-this-then-that。
- **极简>详尽**。3 句话描述角色 + 工具列表 + 回复风格就够了。

**推荐模板：**
```
你是内容创作助手，帮团队从公众号文章里挖选题、写文案。

三个工具：
• get_today_articles — 查今天抓取的文章
• get_article_detail — 看某篇文章详情
• generate_distribution(topic, platforms?) — 生成各平台发布文案
  四个平台：公众号/微信群/朋友圈/小红书
  没说具体平台就四个都出

回复直接给可复制文案，语气轻松不啰嗦。
```

- 先用最简版验证连接: `你是内容创作助手。当用户询问最新文章或选题时，必须调用 get_today_articles 工具获取真实数据。`
- 通过后再逐步添加完整版。模板见 `wecom-bot-integration/references/bot-prompt-template.md`。
   - 现象：日志报 `Executable doesn't exist at .../webkit-2070/pw_run.sh`
   - 原因：Playwright 更新或缓存清理时 webkit 文件被删除
   - 修复：`cd ~/project/we-mp-rss-main源文件/we-mp-rss-main && source venv/bin/activate && playwright install webkit && sudo playwright install-deps`

2. **代码默认使用 webkit 而非 chromium**
   - 现象：webkit 已安装但仍然超时无响应
   - 根因：`driver/wxarticle.py` 中 `PlaywrightController` 调用时未传 `browser_type`，默认值 `webkit`。而环境配置的 `BROWSER_TYPE=chromium` 只对主抓取进程生效
   - 修复：在 `driver/wxarticle.py` 的 `get_article_content()` 中修改：
     ```python
     # 原来
     async with PlaywrightController(proxy_url=..., mobile_mode=True) as controller:
     # 改为
     async with PlaywrightController(proxy_url=..., mobile_mode=True,
                                     browser_type=os.environ.get("BROWSER_TYPE", "chromium")) as controller:
     ```

3. **后端实际成功但前端超时**
   - 现象：日志显示 HTTP 200 + 滚动完成，但前端显示「获取中」
   - 原因：by_article 接口需 30-40 秒（开浏览器 + 翻页加载图片），前端默认超时比这短
   - 验证：直接 curl API 确认后端是否成功
   - 状态：后端可正常工作，前端体验问题暂无修复

**MCP 插件在企微后台消失了：**
- 根因：使用了 SSE（`/sse`）。SSE 长连接断开会触发企微自动移除插件
- 修复：重新添加，URL 用 `/rpc`，协议选 Streamable HTTP

**机器人不调用插件：**
- 检查 MCP 插件状态是否绿色 ✓
- 检查传输协议是否为 Streamable HTTP（不是 SSE）
- 检查 MCP URL 是否以 `/rpc` 结尾
- ~~检查 Cloudflare Tunnel 是否正确路由 `mcp.icoach.chat` → `localhost:8100`~~ **MCP 已停用，此检查已过时**
- 检查机器人指令是否写明了工具使用规则（参考 `references/bot-persona.md`）

**tools/list 成功但 tools/call 静默失败（日志无 tools/call 记录）：**
- 这是企微 MCP 基础设施的问题，不是我们的代码问题
- 确认方法：`tail -f /tmp/mcp_server.log` → 能看到 initialize → tools/list，但永远看不到 tools/call
- 解决方案：删掉 MCP 插件重新添加（碰运气），或考虑 API 插件替代方案

**SSE 模式下 session 丢失：**
- SSE 的 session 与连接绑定，断开后 POST 会返回 404
- 改用 Streamable HTTP（`/rpc`）无此问题

## 用户偏好

- **简洁直接**：用表格、清单、少量段落，不要大段解释
- **先行动再汇报**：遇到问题先解决，解决后简洁汇报
- **遇到障碍给方案**：不要反复分析原因，直接给可操作的下一步
- **用户是运营人员，不是开发者，避免过度技术术语**。当用户说"听不懂"时，立即切换到最简单的方案（SQL 语句、复制粘贴命令），不要解释服务器架构
- 遇到 Nginx/服务器配置问题时优先找**应用层方案**（改数据库、换路径、用内置 API），而非让用户改服务器配置

## 选题日报 AI 幻觉排查（已修复：2026-06-26）

**修复方案：** 不再让 AI 自己写 SQL 查库。改用固化脚本 `daily_topic_data.py` 先跑查询，输出格式化结果，AI 只做选题分析。

**脚本：** `~/.hermes/scripts/daily_topic_data.py` — 接受 `hours` 参数，输出文章列表（含格式化时间/来源/标题/摘要/链接）+ 考试日历节点。时间已格式化为北京时间，消除 AI 时间戳误读根因。

**Cron job 更新：** `218c1e953f63` 的 prompt 已改为先跑脚本再分析，详见该 cron job 的 prompt。

**现象：** 用户反馈选题日报中有事实性错误（如「最近一次更新在 6 月 23 日上午，此后静默」），但数据库实际有 6 月 24 日文章。

**排查步骤：**
1. 不要相信 AI 的摘要——直接查原始数据库验证
2. 运行脚本：`~/project/we-mp-rss-main/venv/bin/python3 ~/.hermes/scripts/daily_topic_data.py 36`
3. 对比脚本输出与日报陈述
4. 定位根因后向用户汇报「管线正常，AI 误读」而非怀疑系统故障

**常见根因：**
- `publish_time` 是 INTEGER (Unix 时间戳)，AI 用 `strftime` 生成字符串 cutoff 做比较，SQLite 类型强制转换可能产生意外结果
- LLM 在摘要长篇查询结果时容易误读时间戳
- 不要因为 AI 一句错误陈述就怀疑整个管线——先验证原始数据

## 当前订阅的公众号

| ID | 名称 |
|----|------|
| MP_WXS_3886864470 | 广东中职菌 |
| MP_WXS_3596295911 | 中职生 |

## 参考：WebSocket 智能机器人方案（旧）

主方案（企微 AI 机器人 + MCP 插件）出现前使用的 WebSocket 长连接 + DeepSeek function calling 方案。详细架构、交互命令、会话管理见 `references/conversation-agent-architecture.md`。

交互命令速查：
| 用户输入 | 功能 |
|----------|------|
| `帮助` | 显示指令指南 |
| `今日` | 列出最近文章 |
| `1`~`N` | 查看文章 N 的选题角度推荐 |
| `生成1A` | 基于文章1角度A生成文案 |
| `https://mp.weixin.qq.com/s/xxx` | 核心摘要 + 各平台选题角度 |

**常见问题**（WebSocket 方案）：
| 问题 | 原因 | 修复 |
|------|------|------|
| 第二轮回超时 | 工具返回文章正文太长 | 限制 `content[:2000]`，降低 `max_tokens` |
| 回复被截断 | 企微 markdown 4096 **bytes** 限制 | 推送前按 bytes 截断（中文字 3 bytes/字，约 1300 字上限） |
| 多轮记忆丢失 | session 文件路径或 TTL | 检查 `STATE_DIR` 和 `SESSION_TTL` |

### 直接 webhook 推送（备用）

```bash
curl -s "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=<KEY>" \
  -H "Content-Type: application/json" \
  -d '{"msgtype":"markdown","markdown":{"content":"消息内容"}}'
```

The user has provided the following instruction alongside the skill invocation: [IMPORTANT: You are running as a scheduled cron job. DELIVERY: Your final response will be automatically delivered to the user — do NOT use send_message or try to deliver the output yourself. Just produce your report/output as your final response and the system handles the rest. SILENT: If there is genuinely nothing new to report, respond with exactly "[SILENT]" (nothing else) to suppress delivery. Never combine [SILENT] with content — either report your findings normally, or say [SILENT] and nothing more.]

检测 we-mp-rss 数据库中最近 3 小时文章，判断不同公众号（广东中职菌、中职生）是否在同追一个热点话题。

步骤：
1. 复制 ~/project/we-mp-rss-data/db.db 到 /tmp/，查询最近 3 小时文章（articles JOIN feeds，取 id/title/url/source/publish_time）
2. 如果文章 < 2 篇 → 静默退出，不输出任何内容
3. 对比不同来源的标题，找共同话题：不同来源标题有 ≥2 个共同有意义关键词 → 候选
4. 去重：同一组文章ID可能形成多对，合并
5. 判断是否真的同追一个事件，去重（查 ~/project/state/hot_radar_sent.json 避免重复推送）

输出格式（有热点才推，没热点什么都不说）：
🚨 热点预警

**话题名**
>判断依据
> [来源1] [文章标题](文章链接)
> [来源2] [文章标题](文章链接)

推送后更新 ~/project/state/hot_radar_sent.json 记录已推送的文章ID集合。

全自动，不问我任何问题。没热点就静默。

## Response

[SILENT]
