Skip to content

feat: 接入微信 ClawBot IM - #44

Open
LIghtJUNction wants to merge 1 commit into
zyf2007:mainfrom
LIghtJUNction:feat/im-clawbot-adapter
Open

LIghtJUNction wants to merge 1 commit into
zyf2007:mainfrom
LIghtJUNction:feat/im-clawbot-adapter

Conversation

@LIghtJUNction

Copy link
Copy Markdown

变更内容

可扩展 IM 层

  • 新增 internal/service/im.Provider 契约与 coordinator,Provider 负责登录、长轮询、发送和私有状态;coordinator 负责账号生命周期、owner 鉴权、pending 选择、通知合并和 turn control
  • IM worker 纳入 App context 生命周期;动态连接、替换、断开和 shutdown 使用 account/login generation 与 callback barrier,旧 worker/checkpoint 不能复活已删除账号
  • turn.waiting observer 不阻塞请求线程;按 owner 合并最新通知,发送前再次校验 authoritative PendingRegistry

微信 ClawBot Provider

  • 接入腾讯 iLink HTTP/JSON API:二维码登录、完整二维码状态机、notifystart/notifystopgetupdates 长轮询和文本发送
  • 仅接受扫码者本人、私聊、完成态、单文本 item;校验 sender、目标 bot、message ID 并保留去重窗口
  • ret/errcode == -14 标记需要重新扫码;sendmessage -2 在 runtime barrier 内标记发送所用 generation 为失效,cursor-only checkpoint 不会误恢复,只有更新的入站 context generation 才重新可用;网络错误采用有界退避
  • API endpoint 固定/限制在可信 weixin.qq.com HTTPS 域,拒绝 userinfo、非默认端口、path/query/fragment 和 hostname 边界绕过;生产 client 使用 fail-closed SafeDialer 拒绝私网/混合 DNS 且不跟随 HTTP 3xx;限制 HTTP body、二维码、cursor/context 和文本大小

操作员工作流

  • 普通微信文本:结束当前选中的 pending turn
  • /list:查看实时 pending 列表
  • /use <编号>:切换请求
  • /abort [原因]:中止请求
  • /bind/help:刷新绑定/查看帮助
  • 首版明确不支持 stream delta、thinking、tool call、媒体或群聊,避免 iLink cursor 重放造成非幂等追加
  • 新增 control.SourceIM,IM 手动操作继续参与 automation recorder 的人工录制语义

配置与 UI

  • bot token、context token、get-updates cursor 和去重状态通过现有 secretbox + CHATAPI_MASTER_KEY 加密后存入独立 user config;秘密不进入日志、状态或 HTTP 响应;二维码请求只携带当前 owner 的 local token
  • 新增 owner-scoped 用户 API:status、start login、poll/verify、disconnect;沿用 session principal、principal access 与全局 CSRF
  • “我的设置”新增微信 ClawBot 卡片:状态、二维码、验证码、重连、断开、命令帮助与恢复指引;打开期间低频刷新 worker/reauth 状态
  • 用户被停用或删除前同步 revoke IM login/runtime/config,成功后旧微信身份立即失去控制权
  • 新增 backend/docs/im-clawbot-design.md 说明一致性、安全边界、恢复行为和多副本限制

首版语义说明

该接入把微信作为 ChatAPI 操作员端:ChatAPI 请求推送到扫码者本人,微信回复控制已有 turn。它不会把微信联系人消息转换成新的模型请求。

验证

  • go test ./...
  • go test -race ./internal/service/im/...
  • ClawBot httptest:headers、QR 状态、redirect、notify start/stop、updates/send、超大响应、stale token、SSRF/URL 边界
  • coordinator:加密持久化、重启恢复、restore/disconnect 竞态、未送达通知不切换选择/送达后选择绑定、owner/request identity、普通回复、重复终态保护、并发 poll、disconnect generation/阻塞 checkpoint fencing、checkpoint 失败回滚、context readiness 单次恢复
  • npm test(11 tests)
  • npm run lint
  • npm run build
  • Chromium desktop/mobile:disconnected、QR waiting、verify required、ready、disconnect;390px 无横向溢出

官方协议参考

Copilot AI lite review requested due to automatic review settings August 25, 2026 06:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbd6f3c47c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +158 to +162
if !input.IsActive && s.revokeOwner != nil {
if err := s.revokeOwner(ctx, userID); err != nil {
return common.User{}, err
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Serialize IM revocation with user deactivation

When a user starts a new ClawBot login after revokeOwner returns but before the subsequent user update commits, the login still sees an active owner and can eventually persist a new account/runtime after the user has been disabled. This leaves the supposedly revoked credentials and worker attached to an inactive account; coordinate the state change and revocation under the same owner-level operation, or perform a final revocation after the inactive update succeeds.

Useful? React with 👍 / 👎.

Comment on lines +313 to +315
if item.Type == 1 && item.Text != nil {
textItems++
text = item.Text.Text

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject incomplete ClawBot text items

When iLink returns a finished message whose text item has is_completed: false, this condition still accepts the item because IsCompleted is never checked, and a normal text value can consequently complete the selected ChatAPI turn with partial content. Require the sole text item to have IsCompleted set before exposing its text to the coordinator.

Useful? React with 👍 / 👎.

Comment on lines +313 to +316
current := s.logins[sessionID]
if current == nil || current.ownerID != ownerID || current.generation != generation || s.loginGen[ownerID] != generation {
s.mu.Unlock()
return LoginView{}, ErrLoginNotFound

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recheck login expiry after the long poll

When a poll starts just before challenge.ExpiresAt, provider.PollLogin can remain in flight for up to 38 seconds and return a confirmed account after the five-minute session has expired. The completion path only validates the session generation here, so it persists that late confirmation; include the current challenge expiry in the post-poll validation before accepting the account.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants