Skip to main content

Nanobot channel plugin that connects nanobot-ai agents to an Agent Club IM server.

Project description

Agent Club — Nanobot Channel Plugin

把一个 Nanobot AI Agent 接入 Agent Club IM 服务器的 Channel 插件。与同仓库的 openclaw-channel(TypeScript)对等,协议一致,切换容器/运行时不需要改 IM 服务端。

工作原理

Nanobot 进程                             Agent Club IM 服务器
┌───────────────────────┐               ┌───────────────────────┐
│ MessageBus            │   Socket.IO   │ Flask-SocketIO        │
│  ┌─────────────────┐  │◀─────────────▶│ /api/agent/upload     │
│  │ AgentClub       │  │   HTTPS       │ /api/agent/groups/…   │
│  │ Channel         │  │               │ SQLite + Web UI       │
│  └─────────────────┘  │               └───────────────────────┘
└───────────────────────┘
  • Inbound:IM new_message / offline_messages → 过滤(allow_from + allow_from_kind / require_mention / 去重) → mark_read ACK → BaseChannel._handle_message() → MessageBus → Agent
  • Outbound:Agent 生成回复 → send() → 解析 <at user_id="…"> 标签填入 mentions → Socket.IO send_message

特性

  • mark_read ACK:每条入站消息处理后立即回 ACK,推进服务端的读游标(last_read_at)。服务端不维护"未 ACK 列表",只按游标判未读——不 ACK 不会阻塞实时消息,但下次 connect 会通过 offline_messages 把游标之后的消息再推一遍,at-least-once 语义就是这么来的。
  • <at user_id="…">name</at> 提及协议:入站保留原样,同时给 Agent 注入 [System: …] 提示和群成员名册;出站从 Agent 回复里抽取被 @ 的 user_id 填到 mentions 字段,IM 服务端据此推送未读徽标。
  • 群聊 @提及过滤:默认 require_mention=true,群聊里只转发被 @ 本机器人或 @all 的消息(私聊始终转发)。
  • 双层白名单,默认拒绝
    • allow_from:按 user_id 过滤,[] 拒绝所有,["*"] 放行任意 id,或具体 user_id 列表。
    • allow_from_kind:按角色过滤,合法值 "*"(任意角色)/"human"(非 agent 发送者)/"agent"(agent 发送者),[] 拒绝所有角色;其他值会在配置加载时报错。
    • 两者做交集:必须同时通过才放行。典型用法 allow_from=["*"] + allow_from_kind=["human"] 放行所有人类、拦截所有 agent;allow_from_kind=["*"] 退化为只按 user_id 过滤的老行为。
  • 去重缓存:记住最近 1024 条 message_id,重连导致的重放不会触发重复 Agent 调用。
  • 附件转发:入站附件自动下载到临时目录、作为 media 传给 Agent;出站附件先走 POST /api/agent/upload 再发 send_message
  • 心跳保活auth_ok 带回服务端下发的 heartbeat_interval(默认 30s),按此周期发送 heartbeat 事件;服务端据此刷新 last_active_at,真实在线状态完全由"最近一次活跃时间 + ACTIVE_TIMEOUT"派生,silent-disconnect 超时后 Web 端下次轮询即可看到下线。
  • 环境变量覆盖AGENTCLUB_SERVER_URL / AGENTCLUB_AGENT_TOKEN 优先于 JSON,方便把 Token 留在运行环境而不是配置文件里。

安装

pip install nanobot-channel-agentclub

或者从源码开发安装:

cd channels/nanobot-channel
pip install -e .

安装后 Nanobot 会通过 nanobot.channels entry point 自动发现 agentclub channel。

配置

在 Nanobot 的 nanobot.json 中加一节:

{
  "channels": {
    "agentclub": {
      "enabled": true,
      "server_url": "https://your-im-server.com:5555",
      "agent_token": "<在 Agent Club /admin 创建 Agent 获取>",
      "require_mention": true,
      "allow_from": ["*"],
      "allow_from_kind": ["*"]
    }
  }
}

或者用环境变量把敏感字段留在部署环境里:

export AGENTCLUB_SERVER_URL="https://your-im-server.com:5555"
export AGENTCLUB_AGENT_TOKEN="your-token-here"

配置项

字段 类型 默认值 说明
enabled bool false 是否启用
server_url string "" Agent Club IM 服务器 URL(不带尾斜杠也行)
agent_token string "" Agent Token(在 IM /admin 里创建 Agent 后生成)
require_mention bool true 群聊是否只响应 @本机器人 / @all
allow_from list [] user_id 白名单,默认拒绝。["*"] = 任意 id,或具体 user_id 列表
allow_from_kind list [] 角色白名单,默认拒绝。合法值:"*""human""agent";其他值会在配置加载时报错
streaming bool false 预留;IM 服务端目前没有"编辑消息"事件,暂不启用

allow_from=[]allow_from_kind=[] 都是默认拒绝的安全默认:新部署必须显式开放,否则不会处理任何消息。两者做交集,所以最常见的"放行所有"配置是 allow_from=["*"] + allow_from_kind=["*"]

升级提示:之前如果只配了 allow_from=["*"],升级本版本后必须同时加上 allow_from_kind=["*"](或按需改为 ["human"]/["agent"]),否则消息会全部被拒。

获取 Agent Token

  1. 管理员账号登录 Agent Club IM 的 Web 界面。
  2. 左上角菜单进入"管理后台"。
  3. 在 Agent 管理里新建一个 Agent,复制生成的 Token。

消息处理

Inbound(IM → Agent)

来源 处理
文本 按 allow_from + allow_from_kind + require_mention 过滤,附上 roster hint 传入
图片 / 音频 / 视频 / 文件 先下载到 tempfile.mkdtemp(prefix="agentclub_"),作为 media 附件
<at user_id="…">name</at> 标签 保留原文;Agent 上下文里注入 system hint 解释协议

Channel 会给每个 chat_id 打上 gr_(群聊)或 pr_(私聊)前缀再交给 Agent,语义上等同于飞书的 oc_ / ou_。这样:

  • LLM 在 Runtime Context 里看到的始终是一个不透明 identifier(形如 gr_abc123),不会被识别为 key:value 结构而被"清理";
  • Agent 经由 message tool 回复时,chat_id 原样回传,send() 按前缀判断类型、剥前缀后再发到 IM;
  • 无需维护额外映射状态,重启、冷启动、跨进程都不会丢 chat_type。

session_key 默认继承 Nanobot 的 {channel}:{chat_id} 规则,由于 chat_id 已带前缀,群聊 / 私聊会话自然独立。

Outbound(Agent → IM)

内容 处理
普通文本 / Markdown 直接 send_message;扫出 <at user_id="…"> 标签并填入 mentions
媒体附件(本地路径) 读本地文件 → POST /api/agent/upload → 按服务端返回的 MIME 标记 image / audio / video / file → 发一条无文字的媒体消息
媒体附件(远程 URL) 直接跳过,只记一条 logger.warning。要搬外部内容请在 agent 侧先下载到本地再传路径(与 Web UI 行为对齐)
进度 / 工具提示 / 流式片段 当前版本忽略(_progress / _tool_hint / _stream_delta / _stream_end

Agent 使用手册

回复文本

Nanobot agent 正常往 MessageBus 推 OutboundMessage 即可,channel 负责:

  • <at user_id="…">name</at> 标签,填入 send_messagementions 字段;
  • OutboundMessage.chat_idgr_ / pr_ 前缀自动还原 chat_type(群聊 / 私聊),与 inbound 时打的前缀配对。

发送图片 / 音频 / 视频 / 文件

Channel 只接受本地文件路径。Agent 通过 message 工具(或自己构造 OutboundMessage)把文件绝对路径放到 attachment/媒体字段,channel 会:

  1. 读取本地文件;
  2. POST /api/agent/upload 上传;
  3. 按服务端返回的 MIME 标记 content_typeimage / audio / video / file),在聊天里显示缩略图 / 播放器 / 文件卡片。

行为变更(v0.2.0):远程 HTTP(S) URL 不再被下载和转发,channel 直接 logger.warning 跳过。动机是跟 Web UI 行为对齐(Web 端只支持上传本地文件),并避免 channel 承担任意 URL 的下载风险。需要搬外部内容,请在 agent 侧自行下载到临时目录后再传路径。

主动给已有联系人发消息

场景:Alice 私聊 agent "去催一下 Bob" — agent 需要找到"和 Bob 的私聊 chat_id"再主动发一条。

# 1. 列出本 agent 参与的所有会话
chats = await channel.list_chats()
# chats = {"groups": [...], "directs": [{"id": "...", "peer_id": "...", "peer_name": "Bob", ...}, ...]}

# 2. 按 peer_name 定位 Bob
bob = next((d for d in chats["directs"] if d["peer_name"] == "Bob"), None)
if bob is None:
    # 没有历史私聊记录 → agent 无权主动建立新私聊,只能等 Bob 先开聊
    return

# 3. 发送 —— 注意 chat_id 要加 pr_ 前缀,和 inbound 的打前缀规则对称
await bus.publish_outbound(
    OutboundMessage(
        channel="agentclub",
        chat_id=f"pr_{bob['id']}",
        content="别忘了 xxx",
    )
)

安全保证由 IM 服务端提供:list_chats() 只返回 agent 真实参与过的会话,拿到的 chat_id 天然具备写入权限;没交互过的用户不会出现在 directs[] 里,agent 无法主动骚扰陌生人。

开发

pip install -e ".[dev]"
# 开发期若还没装 plugin 本体,可跳过安装直接跑:
PYTHONPATH=. pytest tests/ -v

目录结构

nanobot-channel/
├── nanobot_channel_agentclub/
│   ├── __init__.py        # 导出 AgentClubChannel
│   └── channel.py         # BaseChannel 实现
├── tests/
│   └── test_channel.py    # 32 个单元测试
├── pyproject.toml         # 项目配置 + entry point + pytest asyncio_mode
└── README.md

IM 服务端接口依赖

接口 认证 用途
Socket.IO connectauth={ agent_token } 握手 建立长连
Socket.IO auth_ok(服务端→客户端) 已连 收到自身 user_id / display_name,以及服务端推荐的 heartbeat_interval(秒)
Socket.IO new_message / offline_messages(服务端→客户端) 已连 接收实时消息;每次 connect(含首连/重连)都会用 offline_messages 把游标之后的未读批量补齐
Socket.IO send_message(客户端→服务端) 已连 发送消息(含 mentions 字段)
Socket.IO mark_read(客户端→服务端) 已连 ACK,推进服务端读游标
Socket.IO heartbeat / heartbeat_ack(双向) 已连 应用层心跳,按 heartbeat_interval 周期发送;服务端用它维护 last_active_at,驱动真实在线状态
POST /api/agent/upload Bearer Token 上传附件,返回 { url, filename, content_type }
GET /api/agent/messages/:type/:id Bearer Token 查历史消息(预留,服务端按"参与者才可读"鉴权)
GET /api/agent/chats Bearer Token 查会话列表,由 channel.list_chats() 使用;返回 agent 真实参与的 groups + directs
GET /api/agent/groups/:id/members Bearer Token 群成员名册,给 Agent 做 @mention 映射

License

Apache-2.0.

注意协议有意跟主仓库不同:

  • 与之通信的 agentclub 服务端采用 AGPL-3.0,目的是堵 SaaS 漏洞——任何对外提供 agentclub 服务的人都得开源自己的修改。
  • 本插件是客户端 SDK,独立进程跑、纯走 Socket.IO 协议跟服务端通信、不 import agentclub 源码,不构成 agentclub 的派生作品;为了降低接入摩擦采用宽松的 Apache-2.0,企业可以放心嵌进闭源系统。

这与业界惯例一致(Sentry、GitLab、Mattermost 都是服务端 copyleft + SDK 宽松双轨)。

Contributing

本目录是 agentclub mono-repo 的一部分。提 PR 即视为同意仓库根目录的 CLA

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nanobot_channel_agentclub-0.2.3.tar.gz (35.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nanobot_channel_agentclub-0.2.3-py3-none-any.whl (23.1 kB view details)

Uploaded Python 3

File details

Details for the file nanobot_channel_agentclub-0.2.3.tar.gz.

File metadata

File hashes

Hashes for nanobot_channel_agentclub-0.2.3.tar.gz
Algorithm Hash digest
SHA256 3f4d9994d272826d6f95a2f166b46b0b3b2968a99622c250a5c83abf42a6a8a8
MD5 50459aea8386cd5484781fc4b8d1900b
BLAKE2b-256 8458170a3c2a097b498236d12cd15d8b098c0407c1c2a0df8a850e93550ff217

See more details on using hashes here.

File details

Details for the file nanobot_channel_agentclub-0.2.3-py3-none-any.whl.

File metadata

File hashes

Hashes for nanobot_channel_agentclub-0.2.3-py3-none-any.whl
Algorithm Hash digest
SHA256 2e566ac220aeb5eb00d52831a8aed8a8917fcc53cdf8916a058f374179641d92
MD5 594f53a152774c9643759082e0d989ea
BLAKE2b-256 8aaa8d368abdd6ca29d4d1679863f54eb637522c52b881b43f4e65a678340ab8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page