Built-ins

chat Plugin

Channel runtime, queue worker, chat authorization, chat system text, and chat-facing plugin actions

chat Plugin

chat is the channel runtime plugin.

It owns:

  • channel state
  • the chat queue worker
  • the chat queue store
  • chat authorization hooks and resolves
  • chat-facing actions
  • chat system text

Main shape

  • lifecycle
  • actions
  • hooks
  • resolves
  • system

What it is for

Use chat when you need:

  • Telegram, Feishu, or QQ chat channel runtime
  • one queue worker per agent/plugin instance
  • chat delivery and inbound message execution
  • inbound user authorization, principal observation, and role resolution

How users use it

Most users do not control the channel runtime directly. They use the downcity chat shortcuts that Downcity exposes for chat sessions:

downcity chat
downcity chat list
downcity chat info --chat-key <chatKey>
downcity chat send --chat-key <chatKey> --text "Done"
downcity chat react --chat-key <chatKey> --message-id <messageId> --emoji "👍"
downcity chat context --chat-key <chatKey>
downcity chat history --chat-key <chatKey> --limit 30
downcity chat delete --chat-key <chatKey>

downcity chat with no subcommand opens the interactive chat account manager. Use it to add Telegram, Feishu, or QQ accounts before connecting an agent to those accounts.

Downcity intentionally hides channel-runtime actions such as status, test, open, close, reconnect, configuration, and configure from the user-facing downcity chat shortcut. Those actions exist inside the plugin runtime, but they are not the normal CLI path for users.

SDK Assembly

In embedded SDK scenarios, ChatPlugin receives channel objects. Each channel owns its env or account binding details:

import {
  ChatPlugin,
  FeishuChannel,
  QqChannel,
  TelegramChannel,
} from "@downcity/plugins";

const plugin = new ChatPlugin({
  channels: [
    new TelegramChannel({
      env: {
        TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN,
      },
    }),
    new FeishuChannel({
      env: {
        FEISHU_APP_ID: process.env.FEISHU_APP_ID,
        FEISHU_APP_SECRET: process.env.FEISHU_APP_SECRET,
        FEISHU_DOMAIN: process.env.FEISHU_DOMAIN,
      },
    }),
    new QqChannel({
      env: {
        QQ_APP_ID: process.env.QQ_APP_ID,
        QQ_APP_SECRET: process.env.QQ_APP_SECRET,
        QQ_SANDBOX: process.env.QQ_SANDBOX,
      },
    }),
  ],
});

channelAccountId is still available as an account-pool binding, but the SDK-first path is to pass channel-specific env to the channel object.

Feishu dependency

@downcity/plugins keeps the Feishu/Lark SDK outside its default production dependencies. Apps that do not enable Feishu can import @downcity/plugins without installing Feishu runtime dependencies.

If your host enables FeishuChannel, install the SDK in that host:

npm install @larksuiteoapi/node-sdk@^1.66.0

The import path does not change:

import { ChatPlugin, FeishuChannel } from "@downcity/plugins";

When Feishu is enabled without the SDK installed, the runtime error tells you which package to install before enabling channel "feishu".

SDK action use

In an embedded SDK scenario, call the visible chat actions by plugin name:

await agent.plugins.runAction({
  plugin: "chat",
  action: "send",
  payload: {
    chatKey: "telegram:123456",
    text: "Done",
  },
});

The main user-facing actions are list, info, send, react, context, history, and delete.

Built-in Authorization

Chat authorization now belongs to ChatPlugin itself. You no longer attach a separate authorization plugin.

It participates in inbound chat execution through three chat plugin points:

  • chat.observePrincipal: records observed users and chats
  • chat.authorizeIncoming: decides whether the current message may enter the agent based on roles and permissions
  • chat.resolveUserRole: enriches history / queue metadata with the current user role

Authorization actions also live under chat:

ActionPurpose
authorization-snapshotread the authorization catalog, config, observed users, and observed chats
authorization-read-configread the current authorization config
authorization-write-configreplace the authorization config
authorization-set-user-roleset a user's role for a channel

SDK example:

await agent.plugins.runAction({
  plugin: "chat",
  action: "authorization-set-user-role",
  payload: {
    channel: "telegram",
    userId: "12345678",
    roleId: "admin",
  },
});

Important runtime semantics

  • queue worker lifecycle belongs to the plugin instance
  • channel bot state also belongs to the plugin instance
  • inbound authorization belongs to ChatPlugin, while still being dispatched through generic plugin hooks / resolves
  • Agent only assembles it; it does not own chat domain state itself

Public status

ChatPlugin is exported from @downcity/plugins.

That makes it one of the few lifecycle-oriented built-ins that SDK users may still directly understand and attach in local embedded scenarios.