Entry Point and Initialization Flow
src/index.ts is a thin orchestrator. All initialization logic lives in src/init/ modules.
src/index.ts— orchestrator (calls init modules in order)src/init/healthServer.ts— health HTTP serversrc/init/secrets.ts— secrets loading + key manager initsrc/init/discord.ts— Discord client construction + error handlerssrc/init/database.ts— DB init, cooldown cleanup, pg_cron setupsrc/init/loaders.ts— tool registry, localizer, caches, event handlersrc/init/bridges.ts— Matrix bridge (optional)src/init/timers.ts— health tracker, scheduled work, memory monitor, cache metrics, quota cleanupsrc/types/config.ts—AppConfiginterface +resolveEnvironment()
Startup Sequence
Section titled “Startup Sequence”- Load
.env(dotenv); resolveAppEnvironment. - In production: bind health HTTP server on
$PORT(default 8080) — returns 503 until Discord ready. - Load secrets via
getAppSecrets(); populateprocess.envfor downstream consumers; initializekeyManager. - Construct Discord client with intents + sweepers; register process/client error handlers.
- Initialize database:
- run narrow pre-schema legacy rename bridges for known table renames that would otherwise conflict with fresh
schema.sql- The persona rename bridge (
runPreSchemaPersonaRenameBridge) also self-heals rollback artifacts: running pre-rename code against an already-migrated database re-materializes legacy tables (tomoris,tomori_presets,tomori_configs) as empty shells via their oldCREATE TABLE IF NOT EXISTSschema. When an empty legacy table coexists with its populated renamed counterpart, the bridge drops the empty shell (logged vialog.warn) and continues. A non-empty legacy table coexisting with the renamed table is treated as an ambiguous data fork and still throws, requiring human inspection before boot. This runs on every startup (unlike run-once migrations), so the fix survives repeated rollback/merge cycles.
- The persona rename bridge (
- run
src/db/schema.sql - run
src/db/schema_rag.sqlonly when pgvector is detected - run
src/db/schema_stpreset.sql - run typed seed catalogs in order: models, personas, system prompts, NovelAI presets
- run pending numbered migrations from
src/db/migrations/(fresh databases instead record every historical migration as applied viamarkAllMigrationsApplied, becauseschema.sqlalready embodies the final shape)- Because
schema.sqlis applied before the migration runner, a legacy untracked production database (still carryingtomori_configs, never seen by the runner) reaches the historical “expand + backfill” migrations with the split tables already in their final post-migration shape. The data-mover backfills that copy out of the god table (002,003,004,007) therefore detect whether the destination still has the pre-021image-tag columns (nai_style_tags/nai_tags/nai_char_tags) or the post-021ones (image_default_positive_tags/physical_appearance_tags) and write the matching column set. Without this guard the backfill fails withcolumn "nai_style_tags" of relation "server_novelai_imagegen_configs" does not exist(42703). Migration021reconciles the rename afterwards, so no tag data is lost.
- Because
- run narrow pre-schema legacy rename bridges for known table renames that would otherwise conflict with fresh
- Cleanup expired cooldown rows at startup (
cleanupExpiredCooldowns). - Attempt optional
pg_cronregistration for hourly cooldown cleanup job. - Initialize tool registry (
initializeTools). - Initialize localization (
initializeLocalizer). - Initialize model caches:
- LLM cache (
initializeLLMCache) - OpenRouter capability cache (
initializeOpenRouterCapabilityCache)
- LLM cache (
- Preload preset avatar cache from DB presets.
- Initialize Matrix bridge (optional; non-fatal on failure).
- Attach all event listeners (
eventHandler(client)). - Register post-ready startup hooks (deferred until
clientReady):- health tracker init
- scheduled work coordinator init (reminders + random triggers)
- memory monitor init
- cache metrics logger init
- Initialize upload quota cleanup scheduler.
await client.login(DISCORD_TOKEN)inside a try/catch — aDisallowedIntentsrejection (privileged intent requested without approval) is logged as an actionable misconfiguration and exits, rather than leaving the process alive but disconnected.
Error Criticality
Section titled “Error Criticality”- Fatal (process exits):
- database init failure
- tool registry init failure
- Discord login failure (including
DisallowedIntentsfor an unapproved privileged intent)
- Non-fatal (warn and continue):
- cache warmup failures
- pg_cron setup failures
- matrix init failure
- cooldown cleanup failure
- scheduled work/memory monitor/quota cleanup init failures
Discord Client Configuration Notes
Section titled “Discord Client Configuration Notes”GuildPresencesis a privileged intent resolved byresolvePresenceIntentEnabled()insrc/init/discord.ts. Before the client is built, it probesGET /applications/@meand includes the intent only when Discord reports it as enabled (ApplicationFlags.GatewayPresenceorGatewayPresenceLimited). This is self-resolving: the intent turns on automatically on the next restart once Discord approves it — no code or env change. If the probe fails (e.g. network error), it falls back to the legacy default: enabled outside production, disabled in production.- Consumers detect the intent at runtime via
client.options.intents.has(GatewayIntentBits.GuildPresences)(see the participants context builder) and omit presence/status lines when it is absent, so toggling it needs no other code changes. - Sweeper configuration is enabled for message/user cache pressure control.
clientReady Event Work
Section titled “clientReady Event Work”eventHandler executes all handlers in src/events/clientReady/ (sorted), including:
- command registration
- MCP server registration
- command registry initialization
- status/presence setup
Additional client.once("clientReady") hooks in index.ts initialize health tracking, scheduled work, and memory monitoring.
Production Health Endpoint
Section titled “Production Health Endpoint”GET /health returns:
200when healthy503when unhealthy
Health is computed from:
- Discord ready state
- websocket ping threshold
- recent Discord event activity