此页面尚未翻译成你的语言,因此以英文显示。

查看 Markdown

Channels

A channel puts an agent in a chat platform. Someone mentions the bot in Slack, DMs it on Telegram, or messages it in a Feishu group; Hub routes that conversation to an agent on one of your daemons and posts the answer back into the same conversation.

Slack / Telegram / Discord / Google Chat / Feishu / Zalo / WhatsApp
        │
        ▼
      Hub  ──►  daemon  ──►  agent session
        ▲                        │
        └──────── reply ─────────┘

Three resources make that work, and they are separate on purpose:

  • A Connection owns the credential — one Slack workspace installation, one bot token, one Zalo account you logged in to. Hub encrypts it and never shows it again. A bot has one Connection: adding a second one for a bot Hub already holds is refused and names the Connection that has it, since two would both run the bot and split its messages.
  • A Channel account is the behaviour attached to that Connection: transport settings, ordered Routes, access, reply synchronization. Adding a Connection adds its account, so the bot runs at once: before any Route it answers /status with chat IDs and /me with the sender's ID (use a DM where group IDs are private) and remembers who wrote, which is how you find the people to name in a Rule. Nobody reaches an agent until a Route lets them in.
  • A Route is where a message goes — a direct agent, a Bot, or an Automation — and its Rules are the ways in. The first Route with a Rule that lets the message in wins; a sender no Route admits is refused.

How Hub works covers the resource model. This section covers the platforms.

The eight channels

ChannelCredentialInbound transportNeeds a public HTTPS URL
SlackBot token + app tokenSocket ModeNo
TelegramBot tokenBot API pollingNo
DiscordBot tokenGatewayNo
Google ChatService-account JSON + subscriptionCloud Pub/SubNo
Feishu / LarkApp ID + app secretLong connectionNo
Zalo Official BotBot tokenBot API pollingNo
Zalo PersonalA QR login to a personal accountPush socketNo
WhatsAppA QR loginWhatsApp Web socketNo

Channel webhook transports are not supported. Hub refuses Slack Events API, Telegram, Google Chat HTTP, Feishu and Zalo webhook configurations even when credentials are supplied. Use the supported transport in the table; none needs a public URL. Provider event triggers are separate integrations.

Zalo Personal and WhatsApp are the odd ones: there is no token to paste. You log in to each by scanning a QR code with a phone, and the session ends from time to time, so you log in again. Everything that account can do, a human on that account can do. Read their pages before you use them.

Capability matrix

Integration capabilities, subject to each page's direction, settings and limits. A cell does not promise both inbound and outbound support or live verification. A blank cell means this integration does not provide the capability.

CapabilitySlackTelegramDiscordGoogle ChatFeishuZalo BotZalo PersonalWhatsApp
Text messages✅✅✅✅✅✅✅✅
Threads✅✅✅✅✅
Mentions✅✅✅✅✅✅✅✅
Rich formatting✅✅✅✅✅✅✅✅
Long-message chunking✅✅✅✅✅✅✅✅
Media✅✅⁷¹⁷✅
File attachments✅✅⁷⁷✅
Reactions✅✅²✅✅✅
Edit messages✅✅✅✅✅
Delete messages✅✅✅✅
Voice messages✅✅✅✅
Video✅✅✅
Video notes✅
Location✅✅
Polls✅✅✅✅
Forum topics✅
Tables and charts✅⁹✅
Buttons✅✅⁸³⁴
Select menus✅✅⁸³⁴
Approval prompts✅✅⁶
Native commands✅✅✅⁵
Group DMs✅✅
Emoji discovery✅✅✅

¹ Inbound images and outbound images by public HTTPS link; no upload endpoint. ² Outbound reactions only; inbound reaction events are not wired yet. ³ Card clicks arrive inbound; Hub renders no card of its own. ⁴ Card clicks arrive inbound; Hub renders no Lark card of its own. ⁵ The operator registers /clisbot in the Google Chat app console; its command normalization is implemented but not verified live. ⁶ Answered by reacting to the prompt (👍, 👎, 1️⃣–4️⃣); WhatsApp has no buttons. ⁷ Outbound only; inbound attachments are not downloaded. ⁸ Outbound components render, but their clicks are not routed to Hub. ⁹ Native tables; charts fall back to text.

In the app, Channels → Channel Integrations → What it supports describes the integration. A Connection's Status describes its runtime health. There is no per-Connection capability verification state; dated live evidence is listed separately below.

Commands in a conversation

Use Channel slash commands to choose a Project or Workspace, inspect Git, start or fork a session, change its configuration, answer approvals, and open the same work in the app. Start with /help or @bot /help in a group.

Commands act on this conversation's session. Outside a DM, name the bot even when ordinary messages need no mention; a Telegram /status@yourbot and an addressed @bot /status reach the same command. Native /clisbot is another entry point where configured. The guide covers channel-specific spellings and permissions.

Permission requests

Each route decides what happens when the agent's provider asks before running a tool. In the app this is Permissions on the route: Ask authorized members, Deny, or Accept automatically. Accept automatically answers every request with Allow, for providers whose own modes still stop for approval (OpenCode's build mode, Claude's auto mode review). The Hub saves it with a warning.

A question from the agent (Claude's AskUserQuestion) is not a permission: the permission choice never decides it, and anyone the route lets talk in the conversation may answer it. The route's questions: setting, under Permissions in the app, decides how it is answered:

questions:In the appWhat happens
ask (default)Ask in the conversationThe question is posted; anyone the route admits answers it.
recommendedPick the recommended answerEach question gets the option labelled recommended, else the first option.
agent-decidesLet the Agent decideThe agent is told nobody can answer and picks the option it recommends.
routes:
  - audience: [...]
    agent: worker
    approval: [{ match: "*", mode: auto-allow }]
    questions: recommended

questions: is also a defaults: leaf, inherited organization → account → route.

Configuration commands offer only profiles, providers, models and effort allowed by your own Access grants. A bare /model shows current/next configuration; /model list discovers choices. Same-provider model changes apply live; a different provider is staged until /new or /fork. See Choose an Agent configuration.

Route defaults

A route starts new sessions with its agent: from hub.yml. agentControls: overrides that for one route without editing an agent other routes or automations share:

routes:
  - audience: [{ who: { roles: [member] }, where: { conversations: [C0APP] }, contains: deploy }]
    agent: worker
    environment: repo
    agentControls: { provider: claude, model: claude-opus-5, thinkingOptionId: high }

Naming a provider makes the block the whole setup: the agent's model, mode, thinking option and feature values are replaced, anything you leave out is unset, and the agent's options stay only when the provider is the same. Without a provider, the block overrides only the fields you set.

/promoteroutedefault writes this leaf from a conversation: it publishes a new Channel revision that makes the conversation's current setup the default of the route that served it. It needs channel.manage — an organization owner or admin, or Manage on that Connection in Access — and the new default must be something the sender could start themselves. Only the route being changed is checked against the sender's access; the organization's other routes are not. Running sessions keep their setup; the next session on the route uses the new default, and no account restarts.

What the agent receives

Every message reaches the agent with its sender, on every route:

An Nguyễn (slack:U0000000001, @an.example): Create a CS card for QR tickets

Messages in the same conversation that did not wake the agent (no mention, or the follow-up window had closed) are kept and sent before the next message that does, marked as quoted context rather than instructions. Each is sent once. A message the Hub could not deliver (its session failed to start) is kept the same way, and the sender is asked to write again. /new and /fork start without the earlier context.

defaults:
  interaction:
    whenBusy: steer # steer | queue: add to the running turn, or wait for it to end
  context:
    unmentioned: everyone # everyone | allowed-senders | none
    maxMessages: 20 # 0–200
  batching: off # or { pauseSeconds: 3, maxWaitSeconds: 10, maxMessages: 20 }

batching holds a message until no new one has arrived for pauseSeconds, the first has waited maxWaitSeconds, or maxMessages are waiting, then sends them as one prompt. maxWaitSeconds must be greater than pauseSeconds. Write batching: off on a route to turn off what its account turned on. Every leaf is inherited organization → account → route.

Where a conversation's work lands

Without an explicit location choice, Hub gives each new channel session a Workspace named from its first message. /fork and /side continue in the source Workspace; /new and /quick start another. Renaming a Workspace yourself preserves its name.

Use /project and /workspace to choose where the next session runs, /workspace new to create it before the Agent starts, and /branch or /worktree to inspect Git. Choices are shared within the conversation's binding scope. See Choose a Project and Workspace for examples, permissions and /new//fork behavior.

An operator can disable automatic organization with defaults.workspace.organize: false; sessions then use the Host's placement and naming behavior.

Editing a route while conversations are running

A deployed change applies to live conversations, it does not strand them. Changing how a route talks — synchronization, templates, mention rules, approval rules, who may talk to it — keeps every bound conversation on its session, and the new rules apply from the next message. Two edits do end a session, because they change where the conversation goes:

  • pointing the route at another agent: or at a workflow retires the session and starts one at the new target;
  • narrowing or removing the route so it no longer matches the conversation leaves it unserved, and the bot stops answering there.

Rules: how a message gets in

A route is where messages go: the agent or Automation, the Host, how it replies, its limits. Its Rules are the ways in, and a message gets in when any Rule matches it. Rules are the only place that decides who may chat; the People & access › Access tab grants only Connection Admin. Each Rule has:

  • Where: direct messages, or group chats — every chat the bot is in, public only, private only (Slack and Telegram), or the chats you pick from one search box that lists every conversation the bot has seen.
  • Who: only owners, owners and admins, everyone on the Hub (every linked Member), people you pick (roles, Teams, Members, or senders outside the Hub, picked from the people who already messaged the bot or typed as channel user ids), or anyone (unlinked senders included).
  • When: Require a mention, Continue without a mention for some minutes after the bot's last reply (interaction.followUp), and an optional text the first message must contain (contains). A Rule that leaves one out inherits it from the account and organization defaults.

A new route starts with one Rule: only owners, in direct messages, answered without a mention. A group-chat Rule starts with a mention required. One route can answer DMs without a mention and a support room only when mentioned, with two Rules instead of two copies of the same route.

A Hub member is recognized on a channel only through a linked identity, so a Rule for owners lets nobody in until the owner links their account. The route form offers Link my Slack account (or the channel at hand) in place when a Rule names you and the bot cannot recognize you yet.

Public or private is what the platform says on the message: a Slack channel's type, and on Telegram a group with a public @username is public, any other group private. Other channels do not say, so a public-only or private-only rule matches no group chat there; the editor warns when a rule asks for it.

routes:
  - audience:
      - who: { roles: [owner] }
        where: { dm: true }
        interaction: { requireMention: false }
      - who: { teams: [team-qc] }
        where: { groups: public }
        interaction: { requireMention: true, followUp: { mode: auto, ttlMinutes: 10 } }
      - who: { anyone: true }
        where: { conversations: [C0HELP] }
        contains: deploy # optional text filter for this Rule
    interaction: { whenBusy: queue } # what happens once a message is in
    agent: worker
    environment: repo

Hubs upgraded from a release that kept requireMention, followUp or contains on the route move them onto each Rule at start. On a route that set requireMention, a DM-only Rule that did not set its own is saved as answering without a mention, which is what the app always promised.

Routes stay ordered. A route applies when a Rule's Where covers the conversation (and, for a new conversation, that Rule's contains matches if set); if the sender matches none of that route's Rules, the next route is tried. A bound conversation does not fall through: the route that started the session owns it, and a sender that route refuses is told in the thread to start their own conversation with a new message outside it. There is no catch-all: a sender no route admits is refused. To answer everyone else, add a last route whose rule covers them.

Talking to a bot and reaching a Host or Project stay separate. A chatting sender can start sessions with the route's configuration and use /stop, /new, /fork, /side, /quick, /steer, /queue, /skill and /command; chat gives no access to the Host, the Project, or the Clisbot app. /agent, /model, /provider, /effort, /permission, /cowork and /resume need the sender's own Access grants on the route's Project, and answering an approval needs an approval.* privilege.

To find the IDs a Rule takes, send /status in the chat (its chat and thread/topic IDs) and /me (your sender ID; on Telegram, send it to the bot in a DM). Both answer anyone, so a person the bot does not let in can send them to an admin. They show the session only to a Hub Member the route lets in, and why a sender is refused only to someone who manages the Connection. A sender the route does not let in gets one answer per command every 30 seconds. To make the bot silent to them instead, set:

defaults:
  interaction:
    publicCommands: false # /help, /me and /status answer only senders a route lets in

The person who publishes a route vouches for what it runs: saving checks that they may hand out its Host, Project, Agent configuration and automatic approvals. A Connection Admin edits the routes of one account through the app and keeps its Connection as it is. Only a route whose target, approvals, reply path or Agent controls changed is checked against their access, so a Connection Admin can change who may talk to a route they could not publish. The bot token and every credential in the account's settings are never shown to a Connection Admin and survive their saves unchanged.

Outside a DM, commands must address this bot. Each Rule's requireMention and follow-up window decide when an ordinary message wakes the agent; they do not remove command addressing.

Routes open to Anyone

A rule with anyone: true works like any other. The Hub saves it and lists a warning on the route for each wide choice: no named conversations, no mention needed, a follow-up window that lets anyone talk without a mention, output beyond the final answer, permission requests accepted automatically, Fast mode, or a mode that runs tools without asking. The app lists them before you confirm a save. New routes start from the safe side: a mention in groups, final answers only, and permission requests put to authorized members (chat admission alone grants no approval authority).

A route takes its audience only as a list of rules. A file that still carries a route match:, a one-value audience: { kind: … } or an account fallback: fails validation.

Limits

Limits sit on each Rule, on the route as a whole, and on the account (the whole bot, and each conversation):

Field (app label)CountsRuleRouteAccount
maxInputCharacters (Message length)characters in one incoming message✓✓
messagesPerMinutePerSender (Messages handled per minute, per person)messages from one person that start or continue work✓✓
messagesPerMinute (Messages handled per minute)messages that start or continue work✓✓✓
messagesSentPerMinute (Bot messages per minute)new messages the bot posts✓
maxConcurrentRuns (Concurrent runs)agent turns running at the same time✓✓✓
maxRuntimeSeconds (Run time)how long one turn may run before it stops✓✓

Chatter the bot ignores and status commands are not "handled" messages.

limits: # the whole bot
  maxConcurrentRuns: 20
  messagesSentPerMinute: 120
  perConversation: # each channel, group or DM; threads count toward their channel
    messagesPerMinute: 30
routes:
  - audience:
      - { who: { roles: [owner] }, where: { dm: true } }
      - who: { anyone: true }
        where: { conversations: [C0SUPPORT] }
        limits: { messagesPerMinutePerSender: 3 } # this way in only
    limits: { maxConcurrentRuns: 5 } # every Rule of the route together

A message must fit the bot, its conversation, its route and the Rule that let its sender in. A sender let in by several Rules counts in one that names them before one that lets anyone in, so an owner never shares the strangers' limits. Each field is a positive number, off, or left out. A Rule's field left out takes the route's; left out there too, it means no limit, except on a Rule that lets anyone in, which defaults to 8000 characters, 10 messages per person and 60 per minute, 8 concurrent runs and 900 seconds. A route field set or turned off replaces that default, as it always did. There is no ceiling: set any number or turn a default off. In the app, a Rule's limits are under the Rule, the route's under Route limits, and the bot's under the Connection's … menu, Limits.

Over a limit, an incoming message waits in the ingress queue and runs when there is room, and the bot says once that it is queued. A message longer than maxInputCharacters is refused with a short reply. /fork, /side, /quick, /steer and /queue count like messages. Outgoing messages over messagesSentPerMinute are delayed and keep their order within a conversation. Typing, reactions, edits and streaming drafts are not counted. Delayed outgoing messages are held in memory: saving a change to the account, or restarting the Hub, drops the ones still waiting.

Allowlists, pairing, and who may talk to the bot

access: is the sender gate. It uses OpenClaw's names and semantics, so an account file written for OpenClaw works unchanged, and it is authorable at any layer — organization policy, account, or a single route:

defaults:
  access:
    dmPolicy: pairing # open | pairing | allowlist | disabled
    groupPolicy: allowlist # open | allowlist | disabled
    allowFrom: ["U0ALICE"] # `*` admits anyone
    groupAllowFrom: ["U0ALICE", "U0BOB"] # falls back to allowFrom when absent
    deniedReply: "Ask an operator for access." # omit to refuse silently

Omit access: and the Route's Rules still decide who may talk. Add a leaf and this sender gate runs before the Rules: both must allow. Personal Host/Project Access is checked separately when a command needs it.

A refused sender never reaches an agent. The refusal is recorded in channel activity with its reason (dm_policy_not_allowlisted, group_policy_disabled, …) and answered only if you set deniedReply.

Pairing

With dmPolicy: pairing, an unknown sender's first DM gets a six-character code instead of an agent, and you decide:

curl -H "authorization: Bearer $KEY" \
  "$HUB/api/management/organizations/$ORG/channel-accounts/telegram/support/pairing"

curl -X POST -H "authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"senderIdentity":"telegram:77001"}' \
  "$HUB/api/management/organizations/$ORG/channel-accounts/telegram/support/pairing/approve"

.../pairing/deny is the other half. Approving adds the sender to that account's allowlist; denying is final — a denied sender is never handed a new code. The code is a handle so you can tell two strangers apart, not a secret: knowing it grants nothing.

The requests belong to one organization and one account. Approving someone on one account does not admit them on another, and never on another organization's.

How the agent replies

A Route's Reply method decides what reaches the conversation:

Reply methodWhat the user sees
HybridThe agent's answer as text, plus the files, reactions and edits it sends with its message tool. The default for a new Route, including Anyone routes.
Text forwardThe agent's answer as text. The agent cannot send files or act on messages.
Channel tool onlyOnly what the agent sends with its message tool, including short progress updates on long work.

The Clisbot app shows the same answer the conversation got on the first two. On Channel tool only, the agent's own messages stay in the app and the conversation sees the tool's posts; if the agent finishes without sending a reply, its last message is sent instead. A turn started in the Clisbot app is not sent to the conversation on this method. Progress updates are limited to one every 30 seconds.

If a turn fails, the conversation gets one notice with the error. A turn you stop on purpose (/stop, or a message that interrupts it) posts nothing.

Files the agent sends

One tool sends both. The agent's message tool takes attachments alongside the text — one file or many, any type, in the order it names them. Each file becomes its own post in the same conversation, so a reply that says "here is the report" and attaches it arrives as two messages, not one.

Local files are read from the Agent's Host using the permissions of the Host user running it, including readable paths outside the Project and symlinks. They are not confined to the Project root. An older Host asks you to update for file transfer. Files over the upload cap (Telegram 50 MB, Slack 250 MB, Discord 10 MB, Zalo Personal 100 MB, WhatsApp 50 MB) produce a visible notice.

Channels with no upload endpoint — Google Chat, Feishu, Zalo Bot — refuse the attachment by name. See each channel's page.

Manage accounts from the CLI

Four verbs, all against a running Hub:

clisbot channels add <channel> --account <id> [--connection-id <uuid> | --secret-file <path>]
clisbot channels ls
clisbot channels status
clisbot channels rm <channel> --account <id> --yes

add never takes a credential on the command line — a secret in argv lands in ps output and shell history. Pass a file and delete it afterwards. Each channel's page shows the file shape it expects.

status adds the pin, integrity, and load-trace columns to what ls shows, and is the first thing to run when an account is not behaving.

rm drops the account from the channel configuration and stops its transport. It refuses without --yes. The Connection holding the credential stays, and rm prints its id so you can retire it from the app.

ls also answers to list, the name it shipped under.

All four accept --hub <origin> to target a Hub other than the local one (with --api-key), --home <path> to pick a different Clisbot home, and --json.

Verified live

A capability is only "verified live" when a message from a real account on the real platform reached an agent and the agent's answer came back to the same conversation, read back through the platform's own API.

ChannelLive evidence
SlackRecorded — mention → reply in thread, long chunked replies, outbound files (2026-09-07).
TelegramRecorded — mention → reply, forum topics, files in and out, edit and poll actions, /new and /stop (2026-09-07).
DiscordNo. Tested against a simulated platform only; needs a bot token and a test guild.
Google ChatRecorded, DMs over Cloud Pub/Sub (2026-10-06). Spaces, cards and the HTTP endpoint are not verified.
Feishu / LarkNo. Tested against a faked Lark SDK client only; needs Lark app credentials.
Zalo Official BotNo. Polling E2E is runnable with a bot token and a human sender; not yet run.
Zalo PersonalNo. Needs a human to scan a QR code.
WhatsAppNo message loop. QR start/poll/cancel recorded 2026-10-06; scan and messages need a spare number.

Each channel page carries its own per-capability line. The running record of what passed and what failed, with message ids, is docs/tests/channels/p0-live-scenarios.md in the repository.

Before you connect one

A channel is an untrusted input. Anyone who can post in a conversation your Route matches can send text to an agent that has a working directory, a shell, and network access. Read Hub security and set the account's access rules before you point a Route at a real project.