Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
4.8 KiB
AGENTS.md — Discord Bot
Parent:
../../AGENTS.md— env vars (DISCORD_TOKEN,DISCORD_GUILD_ID), deployment, Payload config.
Overview
Standalone long-running process (bun run bot). Imports @payload-config directly, shares PostgreSQL with web app. Under active development.
Structure
bot/
index.ts # Entry point
config.ts # Env validation (fail-fast on missing required vars)
commands/
index.ts # Command registry (global vs guild scope)
ping.ts # /ping (global, DM-usable)
signup.ts # /signup (DM-only, creates Payload user with temp password)
link.ts # /link (global, links discordId to existing user)
announce.ts # /announce (guild-only, staff only; message omitted = compose modal, handler in this file)
remindEvaluations.ts # /remind-evaluations (guild-only, staff only, manual evaluation-reminder trigger)
events/
interactionCreate.ts # Routes ptf-att: RSVP button interactions
transferInteractions.ts # Handles transfer decision buttons (approve/deny/appeal)
services/
index.ts # Service registry
missionEmbeds.ts # Attendance embed lifecycle + reconcile loop (poll tick)
notificationBridge.ts # Poll user-notifications → Discord DMs
evaluationReminders.ts # Post-mission evaluation reminder DMs (one-shot marker on Missions)
signup.ts # Signup service logic
transferRequests.ts # Polls assignment transfer requests awaiting leader decision
transferDelivery.ts # Transfer leader-request / rejection / info delivery DMs
lib/
roles.ts # isStaff check
resolve.ts # discordId ↔ Payload user lookups
transferMessaging.ts # Transfer embed builders + delivery keys
Command registration scope
- Global (DM-usable):
signup,link,ping - Guild-only:
announce,remind-evaluations(guild-scoped commands never appear in DMs)
Feature flow: signup/link
/signup is DM-only. Creates Payload user with username = discordUsername = caller's Discord username. Ephemeral reply carries temp password (guaranteed delivery path); interaction.user.send() is best-effort persistent copy. /link works in servers and DMs — matches discordUsername → sets discordId.
Feature flow: attendance
Bot posts RSVP embeds (Yes/Tentative/No) for future, Ready/Scheduled, visibility:"unit" missions into ops channel. Stores discordMessageId + discordAttendanceHash on mission. Reconciles hash changes every poll tick (web ↔ Discord two-way sync). Web UI: MissionAttendance component.
Feature flow: notifications
notificationBridge polls user-notifications (cursor = last seen id; cap 5 DMs/tick). DMs opted-in users (preferences.discord.enabled, not in mutedTypes).
Feature flow: evaluation reminders
Once a mission is evaluable (Completed, or Scheduled/Active past its start), sends a one-shot DM per linked user asking them to rate their leadership (leaders also get a "rate your subordinates" section). Recipient plan: computeEvaluationReminderPlan in src/lib/evaluations/reminders.ts; state marker evaluationRemindersSentAt on Missions (withheld if the 40-DM/tick cap is hit — retries next tick). DMs ignore preferences.discord.enabled (defaults false, not yet exposed in web UI). Staff can re-send manually via /remind-evaluations.
Feature flow: assignment transfers
transferRequests polls assignment-transfers for requests awaiting leader decision; leaders approve/deny via buttons handled in transferInteractions.ts; DMs are built by lib/transferMessaging.ts and sent through transferDelivery.ts (leader request, requester rejection notice, decision info). Web side: src/lib/transfers/ + /transfers page.
Where to look
| Task | Path |
|---|---|
| Add new slash command | bot/commands/<name>.ts + register in bot/commands/index.ts |
| Add button interaction | bot/events/interactionCreate.ts (RSVP) or transferInteractions.ts (transfers) |
| Modify embed lifecycle | bot/services/missionEmbeds.ts |
| Change DM bridging | bot/services/notificationBridge.ts |
| Evaluation reminder logic | bot/services/evaluationReminders.ts + src/lib/evaluations/reminders.ts |
| Transfer workflow | bot/services/transferRequests.ts + lib/transferMessaging.ts |
| User lookup patterns | bot/lib/resolve.ts |
Anti-patterns
- NEVER run bot and web app with separate database connections without connection pooling — they share PostgreSQL
- NEVER register guild-only commands if the command needs to work in DMs (signup, link, ping must be global)
- NEVER assume DM delivery succeeded —
/signupuses ephemeral reply as primary delivery path - NEVER modify
discordIddirectly in Payload admin — use the/linkcommand or the resolve helper