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 解释协议

IM 服务端原生给所有实体 id 打前缀(u_ 用户 / gc_ 群聊 / dc_ 私聊 / msg_ 消息),语义上对标 Stripe / 飞书的 cus_ / oc_ / ou_gc_group chat)和 dc_direct chat)特意做成对称形态:第一字母切换 chat 类型,第二字母统一锚在 chat,跟数据库 chat_type ∈ {'group','direct'} 一一对应。Channel 是纯转发:inbound 的 chat_id 已经是 gc_… / dc_… 原样传给 Agent,Agent 回复时 chat_id 原样回传,send() 只按前缀还原 chat_type 用于填 send_messagechat_type 字段,剥前缀。这样:

  • LLM 在 Runtime Context 里看到的始终是一个不透明 identifier(形如 gc_abc123),不会被识别为 key:value 结构而被"清理";
  • Agent 只要把收到的 chat_id 原样放回 OutboundMessage.chat_id 即可;
  • 无需维护额外映射状态,重启、冷启动、跨进程都不会丢 chat_type。

如果某个 chat_id 没带 gc_ / dc_ 前缀,又没有配套的 metadata["chat_type"],channel 会记 warning 并丢弃这条消息——这是防止前缀约定被破坏后静默错投的兜底。

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_idgc_ / dc_ 前缀自动还原 chat_type(群聊 / 私聊),与服务端下发的 chat_id 命名保持一致。

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

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": "dc_…", "peer_id": "u_…", "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. 发送 —— bob["id"] 已经是服务端下发的 dc_<uuid> 形态,原样回传即可
await bus.publish_outbound(
    OutboundMessage(
        channel="agentclub",
        chat_id=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.5.tar.gz (37.3 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.5-py3-none-any.whl (23.8 kB view details)

Uploaded Python 3

File details

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

File metadata

File hashes

Hashes for nanobot_channel_agentclub-0.2.5.tar.gz
Algorithm Hash digest
SHA256 2ae8c1e478a1d05f1a7d4025efcd274e5919a6c0c68e259ed116cc1cf61e7389
MD5 227f5a956d2428ab5c6872a06e82e139
BLAKE2b-256 832dae6e9d39925127ee44b94b303224472ff779c6b7a24396eff6e54be03529

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for nanobot_channel_agentclub-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 896de17b039a4223e42bef0661f2b1ee9b81c675b4686f918ae536009f034842
MD5 2ac1db491a5663d8e73530acdfdcf12a
BLAKE2b-256 324fd42dd389b57b50878386032a9d2c12be63107819452f05383749381ad685

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