Skip to main content

Bot Modules

Bot modules are extensions that handle chat messages, platform events, and timed actions. All bot modules -- built-in and community -- run as handler-based server functions inside V8 isolate sandboxes via the Bot Module Worker.

Migration complete

The native Rust BotModule trait, ModuleRegistry, and crates/lo-bot-modules/ crate have been removed. Built-in modules (Link Protection, Spam Protection, Word Filter, Timed Messages) are now system extensions in extensions/system/ and run through the same V8 execution model as third-party bot modules. See Extensions for the full list.

Architecture

Chat message / Platform event

Bot Module Worker
├─ Subscribes to Redis channel: bot_modules:{account_id}
├─ Loads active triggers from internal API
├─ Matches incoming messages/events against triggers (built-in + community)
└─ Dispatches to V8 isolate handler
├─ Handler receives ctx (db, fetch, secrets, auth, user, cache, defer)
├─ Returns action: send_message | delete | timeout | ban | none
└─ Worker executes action via platform API

Bot Module Worker

The Bot Module Worker (apps/bot-module-worker/) is a standalone service that bridges the chat event stream with bot module handlers. Bots match triggers in Rust (<1ms) and dispatch to the Worker via sync HTTP (commands/moderation, 500ms deadline) or async Redis (keywords/patterns/events/timers).

Lifecycle:

  1. On startup, the Worker loads all active bot module triggers for connected accounts via GET /v1/internal/bot-modules/{account_id}/triggers (SystemKey auth).
  2. It subscribes to bot_modules:{account_id} Redis channels for each active account.
  3. When a chat message or platform event arrives, the Worker evaluates it against the loaded trigger set.
  4. On a match, the Worker dispatches the message to the corresponding V8 isolate handler via crates/lo-extensions-v8/.
  5. The handler's return value (action + optional message) is executed by the Worker using the account's channel connection credentials.

Trigger refresh: When an account installs, uninstalls, enables, or disables a bot module, a Redis pub/sub message on bot_module_triggers:{account_id} triggers the Worker to reload that account's trigger set.

Redis Channels

ChannelPurpose
bot_modules:{account_id}Chat messages and events forwarded to extension bot modules
bot_module_triggers:{account_id}Trigger set invalidation (install/uninstall/enable/disable)

V8 Execution

Extension bot module handlers execute inside the same V8 isolate sandbox used by all extension server functions (crates/lo-extensions-v8/). The handler context (ctx) includes:

PropertyDescription
ctx.dbExtension-scoped database access (ext_{extension_id}.* tables)
ctx.fetchHTTP client (egress allowlist enforced)
ctx.secretsServer-side secrets
ctx.authCaller identity (userId, accountId)
ctx.userChat user context (platform, platform_user_id, display_name, roles)
ctx.cacheIn-memory key-value cache scoped to the extension install (TTL-based)
ctx.defer()Schedule a follow-up action after the handler returns (e.g., delayed response)

The same sandbox limits apply: 256 MB memory, 10s CPU timeout, no filesystem access, no dynamic code evaluation.

Trigger Matching Flow

1. Message arrives on bot_modules:{account_id} Redis channel
2. Worker iterates over active triggers for the account (ordered by priority)
3. For each trigger:
a. command → check if message starts with the configured prefix
b. keyword → check if message contains any configured keyword
c. pattern → match message against configured regex
d. event → check if the event type matches
e. timer → check if the interval has elapsed since last fire
f. moderate → always matches (runs on every message)
4. First matching trigger wins (like built-in modules)
5. Worker spawns V8 isolate, passes message + trigger config to handler
6. Handler returns { action, message?, duration? }
7. Worker executes the action via platform API

Priority order: moderate triggers run first (they are moderation gates), followed by command, keyword, pattern, event, and timer in registration order.

Key Files

PathDescription
apps/api/src/workers/bot_module_worker.rsBot Module Worker service
crates/lo-extensions-v8/src/bot_module.rsV8 isolate handler execution
crates/lo-extensions/src/triggers.rsTrigger types, matching, and serialization
crates/lo-extensions/src/manifest.rsBot module manifest validation
apps/api/src/rest/bot_modules.rsREST endpoints (kill switch, trigger resolution)

Security Headers

The Extension Supervisor and Runtime apps serve HTTP security headers via Cloudflare Pages _headers files:

  • Supervisor (apps/extension-supervisor/_headers): frame-src restricts which origins can be iframed (only ext.lumio.vision and staging/prod preview). frame-ancestors restricts who can embed the Supervisor (only lumio.vision and staging/prod preview).
  • Runtime (apps/extension-runtime/_headers): worker-src restricts Worker origins. frame-ancestors restricts embedding to Supervisor origins only. connect-src allows Sentry error reporting.

When adding a new environment domain, both _headers files must be updated alongside DNS and CF Pages configuration.

Config Validation

Extension config values are validated server-side against the extension's config_schema before writing to extension_installs.config. The validator (crates/lo-extensions/src/config_validator.rs) checks:

  • Type match (string, number, boolean, color, select)
  • Required fields present and non-null
  • Select values in declared options
  • Color hex format (#RRGGBB)
  • Unknown keys rejected

Both GraphQL (configureExtension mutation) and REST (PATCH /extension-installs/\{id\}) enforce validation. The config is loaded from the database and passed to V8 handlers via ctx.config.