Use a plugin
config is typed by the plugin (definePlugin<MyConfig>()): an unknown option
is a compile error, and a config declared with the plugin’s config interface can
be passed as is.
bot.plug(plugin, config?) returns an unplug function (not the bot, so
plug() calls do not chain). Calling it first runs the plugin’s
uninstall(ctx), if it has one, and then reverts, newest first, what the
plugin registered through ctx.bot:
- commands (
command()), matchers (when(),onMessage(),onType()), conversations and flow triggers (conversation(),use(flow)); - hooks (
before(),after(),onError(),hook()); - nested plugins (
ctx.bot.plug()); - flows registered at runtime with
ctx.bot.registerFlow(), or created (not replaced) withctx.bot.replaceFlow(); - every
ctx.onDispose(fn)callback.
ctx.bot.on() /
once()), injected tools (ctx.bot.provide() / ctx.tools), locale messages
(ctx.bot.locale()), scheduled jobs (schedule(), every(), delay()),
idle handlers (onIdle()), a flow that replaceFlow() overwrote (it is not
restored), handoff state, store writes, or timers and connections of your own.
Undo those in ctx.onDispose() (or uninstall()). Calling unplug twice is a
no-op. If install() throws synchronously, whatever it registered so far is
rolled back before the error reaches you; if it returns a promise that
rejects, the rejection goes to the 'error' event and nothing is rolled back.
Dependencies between plugins (requires)
A plugin can list the plugins it needs by name. bot.plug() checks them
before running install() and throws a descriptive error when one is not
plugged:
inbox later does not unplug
crm. A plugin nested inside another one (ctx.bot.plug() from install())
may require its parent.
One instance per name (multiple)
bot.plug() refuses a plugin whose name is already plugged on the bot, so a
plugin registered twice by mistake never runs its handlers twice:
multiple: true in its spec (every instance must set it). Each instance is
then unplugged on its own, and any of them satisfies requires.
Built-in: slashCommands
Config:{ greeting?: string; unknownMessage?: string }. The command
prefix is a bot option (new Bot({ commandPrefix })), not a plugin option.
The replies are built-in texts in the bot’s
language.
Unknown
/commands get an automatic “unknown command” reply
(unknownMessage).
Create a plugin
Simple plugin
Configurable plugin with cleanup
Plugin structure
definePlugin(spec) returns the spec unchanged, typed. A plugin can also be a
function (config) => spec.
The
ctx that install receives:
Plugin packages
BesidesslashCommandsPlugin, these packages ship ready-made plugins:
- AI —
@botmesh/ai: LLM replies (aiPlugin), with tools, memory and RAG and streaming. - Voice —
@botmesh/voice(voicePlugin): speech-to-text for inbound voice notes and text-to-speech replies. - Integrations —
@botmesh/chatwoot,@botmesh/tickets,@botmesh/moderation,@botmesh/dialogflowand@botmesh/nlu(local intent matching). - Tracing —
@botmesh/otel(otelMetricsPlugin):bot.metrics()and the bot’s spans in OpenTelemetry.
@botmesh/rag (retrieval for aiPlugin) is
described with memory and RAG.
Next steps
- Dependency injection — share tools between plugins
- Example: rate limiter — full plugin