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

The CLI does not have a separate Chat configuration command. Use the shared Plugin Resource manager to create a channel Resource, then save its Resource ID in the current Agent Binding:

city plugin resource create chat --interactive
city plugin config chat <agent_id> --resources '["telegram-a1b2c3"]'

Use the generic Plugin Action command when you need to invoke a Chat Action from the terminal:

city plugin action chat send <agent_id> --input '{"chat_key":"telegram:123456","text":"Done"}' --token <token>

SDK Assembly

In embedded SDK scenarios, ChatPlugin receives already constructed channel objects. The SDK host directly provides the ID, display name, and runtime credentials; it does not read City Plugin Resources:

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

const plugin = new ChatPlugin({
  channels: [
    new TelegramChannel({
      id: "telegram-sdk",
      name: "SDK Telegram Bot",
      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,
      },
    }),
  ],
});

Plugin Resource Schema, Resolver, encrypted storage, and Binding ID resolution belong to the CLI control plane. They do not enter @downcity/agent or become a Store dependency of ChatPlugin.

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

Use the isolated Chat plugin subpath:

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

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.run_action({
  plugin: "chat",
  action: "send",
  payload: {
    chat_key: "telegram:123456",
    text: "Done",
  },
});

The common 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.run_action({
  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.