一条通道一张令牌,机器人把消息 POST 到 /integrations/clawbot/{token},
正文按前缀落到任务或知识草稿:没有前缀就是任务,「知识:」「笔记:」记成知识,
「帮助」只回用法。首行当标题、其余当正文,`#标签` 摘出来挂到事项上。
同一个 msg_id 只处理一次——微信会重推,重推不能变成两条任务。
为什么不塞进 system_setting 的 JSON:一个工作区可能挂多个通道,还得能单独停用。
令牌库里只存 sha256,只在创建/重新生成时显示一次;收消息时还不知道是哪个工作区,
所以 bot_channel 的 RLS 额外放行 token_hash = current_setting('app.bot_token_hash'),
读到通道再 bind_tenant 进上下文,按通道创建者记账。
- 后端:models_integrations.py(bot_channel / bot_message)、迁移 0012(RLS)、
api/integrations.py(收消息 + 管理接口)、错误码 9002/9003
- Web:/w/:wsId/integrations「接入」页——建通道、复制回调地址与令牌、最近消息、
停用/换令牌/删除,只有 owner/admin 能改
- 测试:test_clawbot.py 10 例;web/e2e/integrations.mjs 7 项(冒充 clawbot 真推一条)
验证:pytest 150 passed,ruff check/format 全绿,tsc 0,vite build 通过,e2e 7 项全绿。
This commit is contained in:
@@ -697,6 +697,81 @@ Query:
|
||||
|
||||
---
|
||||
|
||||
## 6.5 外部接入(微信 clawbot)
|
||||
|
||||
微信里那个机器人把消息 POST 到本服务,消息就变成任务或知识草稿。
|
||||
**这一组接口不带登录态**:机器人不会登我们的账号,它只照着配置好的地址发。
|
||||
|
||||
### 6.5.1 建通道(工作区 admin 以上)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/workspaces/{ws}/integrations` | 列出这个工作区的通道 |
|
||||
| POST | `/workspaces/{ws}/integrations` | `{"name":"微信"}`,**响应里带一次性令牌** |
|
||||
| PATCH | `/workspaces/{ws}/integrations/{id}` | `{"name"?}`、`{"status":"active"|"disabled"}` |
|
||||
| POST | `/workspaces/{ws}/integrations/{id}/rotate` | 换令牌,旧地址立刻失效 |
|
||||
| DELETE | `/workspaces/{ws}/integrations/{id}` | 删通道(历史消息级联删掉) |
|
||||
| GET | `/workspaces/{ws}/integrations/{id}/messages?limit=20` | 最近的收信记录 |
|
||||
|
||||
创建响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0, "message": "ok",
|
||||
"data": {
|
||||
"channel": { "id": "…", "kind": "wechat_clawbot", "name": "微信", "status": "active",
|
||||
"last_seen_at": null, "created_at": "2026-11-05T14:02:11Z" },
|
||||
"token": "lS7…(43 字符)",
|
||||
"webhook_url": "https://example.com/api/v1/integrations/clawbot/lS7…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 令牌只在这里出现一次。库里存的是 `sha256(令牌)`,页面刷新之后连管理员也看不到;
|
||||
> 丢了就 `rotate` 重新生成。
|
||||
|
||||
### 6.5.2 收消息(clawbot → 本服务)
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|---|---|---|
|
||||
| GET | `/integrations/clawbot/{token}?echostr=xxx` | 地址校验:有 `echostr` 就原样回纯文本 |
|
||||
| POST | `/integrations/clawbot/{token}` | 收一条消息,回 `{"reply": "...", "action": "...", "duplicate": false}` |
|
||||
|
||||
请求体**宽松解析**(不同机器人的壳子不一样),按顺序找第一个非空字符串:
|
||||
|
||||
- 正文:`text` / `content` / `msg` / `message` / `query` / `body` / `data`(嵌套对象也会往里找 `text`、`content`、`body`、`message`)
|
||||
- 消息 id:`msg_id` / `message_id` / `msgid` / `id` / `uuid` / `event_id`
|
||||
- 发送者:`from` / `sender` / `from_user` / `user` / `uid` / `sender_id`
|
||||
|
||||
正文的写法:
|
||||
|
||||
| 开头 | 结果 |
|
||||
|---|---|
|
||||
| 直接说 | 任务(`status=inbox`) |
|
||||
| `任务:` / `待办:` / `todo:` | 任务 |
|
||||
| `知识:` / `笔记:` / `note:` / `kb:` | 知识草稿(`doc_type=note`、`status_hint=draft`) |
|
||||
| `?` / `帮助` / `help` / `用法` | 只回一段用法说明,不建事项 |
|
||||
|
||||
首行是标题(截到 180 字),其余行是正文;标题与正文里的 `#标签` 会被摘出来挂到事项上
|
||||
(纯数字的 `#123` 不算标签,避免把编号当成标签)。
|
||||
|
||||
**幂等**:带 `msg_id` 的请求重复到达只处理一次,第二次直接回上次的 `reply` 且 `duplicate: true`
|
||||
——微信会重推,重推不能变成两条任务。
|
||||
|
||||
### 6.5.3 错误码
|
||||
|
||||
| code | HTTP | 场景 |
|
||||
|---|---|---|
|
||||
| 9002 | 404 | 令牌不存在或已被换掉;对外一律"通道不存在",不区分工作区 |
|
||||
| 9003 | 403 | 通道被停用(`status=disabled`) |
|
||||
|
||||
### 6.5.4 租户隔离
|
||||
|
||||
`bot_channel` / `bot_message` 都带 `workspace_id NOT NULL` + RLS。收消息时还不知道它属于哪个
|
||||
工作区,所以 `bot_channel` 的策略额外允许 `token_hash = current_setting('app.bot_token_hash')`:
|
||||
请求先把 `app.bot_token_hash` 设成本次令牌的哈希,策略只放行命中那一行,其余行照旧看不见。
|
||||
读到通道之后再 `bind_tenant()` 进这个工作区的上下文,任务是按**通道创建者**记账的。
|
||||
|
||||
## 7. 安卓发布与升级 `/app`
|
||||
|
||||
安装包不进版本库:`backend/static/apk/` 由 `publish_apk.py` 写入,里面有 APK 与一份
|
||||
|
||||
@@ -739,3 +739,60 @@
|
||||
- 新增 `web/e2e/session-refresh.mjs`(6 项):access 换成假串但 refresh 是真的 → 不被踢、首页正常、
|
||||
拿到新令牌、新令牌能调接口;access 与 refresh 都假 → 才回登录页;控制台零异常。
|
||||
- 安卓 `assembleDebug` 通过(JDK 17 + Gradle 8.9)。
|
||||
|
||||
## 13. 微信接入(clawbot,2026-11-05)
|
||||
|
||||
### 13.1 要解决的问题
|
||||
|
||||
记事的动作发生在手机上,而任务和知识都在浏览器里。人正在外面、手上只有微信的时候,
|
||||
"回去再记"通常等于"再也想不起来"。所以要有一条从微信直接落到工作区里的路。
|
||||
|
||||
### 13.2 决定
|
||||
|
||||
1. **一条通道一张令牌,令牌在 URL 里**。机器人不会登我们的账号;它只会照着配置好的地址
|
||||
POST。令牌是 32 字节随机串,库里只存 `sha256`,页面上"创建/重新生成"时显示一次。
|
||||
2. **一个工作区可以有多个通道**,各自能单独停用。塞进 `system_setting` 的 JSON 里就做不到
|
||||
"停这个、留那个"。
|
||||
3. **收消息用宽松解析**。clawbot 的消息壳子没有公开的固定 schema,与其猜错就 422,
|
||||
不如按常见字段名依次摸一遍,摸不到正文就记一条 `ignored` 留痕。
|
||||
4. **同一 `msg_id` 只处理一次**。微信会重推,重推变成第二条任务是最难解释的脏数据。
|
||||
5. **每条消息都留痕**(`bot_message`,含原始 JSON、处理结果、 reply)。
|
||||
出问题时能直接回答"那条任务到底进没进"。
|
||||
|
||||
### 13.3 后端
|
||||
|
||||
| 位置 | 内容 |
|
||||
|---|---|
|
||||
| `backend/app/models_integrations.py` | `BotChannel`(令牌哈希、状态、最后收信时间)、`BotMessage`(文本、动作、item_id、reply、原始 JSON) |
|
||||
| `backend/alembic/versions/0012_bot_channel.py` | 建两张表 + RLS;`bot_channel` 策略额外放行 `token_hash = current_setting('app.bot_token_hash')` |
|
||||
| `backend/app/api/integrations.py` | 收消息 `GET/POST /integrations/clawbot/{token}`;管理接口 `/workspaces/{ws}/integrations` |
|
||||
| `backend/app/errors.py` | `9002` 令牌无效、`9003` 通道停用 |
|
||||
|
||||
正文解析:首行是标题(180 字封顶),其余是正文;`#标签` 摘出来挂事项上(纯数字不算);
|
||||
前缀 `知识:`/`笔记:` → 知识草稿,`任务:`/`todo:` → 任务,没有前缀就是任务。
|
||||
|
||||
### 13.4 Web
|
||||
|
||||
新增 `/w/:wsId/integrations`「接入」页(左侧栏最后一项):
|
||||
|
||||
- 「怎么用」三步说明;
|
||||
- 「新建通道」→ 一次性展示**回调地址 + 令牌**,各带复制按钮(clawbot 后台要粘的就是这里);
|
||||
- 每条通道:状态、最后收信时间、`最近消息`(最近 5 条,带"建了任务/建了知识"标签)、
|
||||
停用/启用、重新生成令牌、删除;
|
||||
- 只有 owner/admin 看得到写操作。
|
||||
|
||||

|
||||
|
||||
### 13.5 验证
|
||||
|
||||
- `backend/tests/test_clawbot.py` 10 例:纯文本成任务、`知识:`前缀成草稿并带标签、
|
||||
`帮助`只回复不建事项、错令牌 404/9002、停用通道 403/9003、同 `msg_id` 幂等、
|
||||
GET `echostr` 原样回、令牌只属于一个工作区、换令牌后旧地址失效、普通成员不能建通道。
|
||||
- `web/e2e/integrations.mjs` 7 项(真跑):页面建通道 → 取回调地址 → 冒充 clawbot POST →
|
||||
任务出现在任务列表 → 重复推送不建第二条 → 「最近消息」看得到 → 控制台零报错。
|
||||
|
||||
### 13.6 还没做
|
||||
|
||||
- 只做了"进"(收消息建事项),没做"出"(改完任务回一条微信通知)。
|
||||
- 消息不认人:机器人只知道"这是这个工作区的消息",按通道创建者记账;要认人得先做微信身份绑定。
|
||||
- 语音、图片、文件只留痕不处理。
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
Reference in New Issue
Block a user