From 8926cd182b3c535c0b6b5bc64e1628536630c533 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Fri, 18 Sep 2026 13:09:44 -0400 Subject: [PATCH] docs: document the imminent-op reminder flow design.md section + env row, bot AGENTS.md feature flow, root AGENTS.md env list and services line. --- AGENTS.md | 4 ++-- docs/bot/design.md | 7 +++++++ src/bot/AGENTS.md | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4a91a30..005d962 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -323,8 +323,8 @@ Five XP-earning minigames under one parent: routes live at `/minigames/*` (moved A Discord bot living in `src/bot/`, run as a standalone long-running process via `bun run bot` — it imports `@payload-config` directly (same pattern as `src/scripts/` and `src/tools/seed/`) and shares the Postgres DB with the web app. **Under active development and testing.** **Full product requirements live in `docs/bot/context.md`; the approved implementation design is in `docs/bot/design.md` — read both before touching bot code.** -- **Env / run**: requires `DISCORD_TOKEN` and `DISCORD_GUILD_ID` (fail-fast on missing required vars in `src/bot/config.ts`); optional `DISCORD_OPS_CHANNEL_ID`, `DISCORD_ANNOUNCE_CHANNEL_ID`, `DISCORD_STAFF_ROLE_IDS`, `DISCORD_ATTENDANCE_POLL_MS` (default 60s), `DISCORD_NOTIFICATION_POLL_MS` (default 20s), `DISCORD_EVALUATION_POLL_MS` (default 60s), plus existing `APP_URL`. Logs through `payload.logger`. -- **Structure**: `commands/` — `ping`, `signup`, `link`, `announce`, `remindEvaluations`; `events/interactionCreate.ts` — routes `ptf-att:` RSVP buttons; `services/` — `missionEmbeds` (attendance embed lifecycle + reconcile loop), `notificationBridge` (poll → Discord DMs), and `evaluationReminders` (post-mission evaluation reminder DMs); `lib/` — `roles.ts` (`isStaff`), `resolve.ts` (discordId ↔ Payload user lookups). Command registration scope: `signup`/`link`/`ping` are **global** (DM-usable — guild-scoped commands never appear in DMs), `announce`/`remind-evaluations` are guild-only. +- **Env / run**: requires `DISCORD_TOKEN` and `DISCORD_GUILD_ID` (fail-fast on missing required vars in `src/bot/config.ts`); optional `DISCORD_OPS_CHANNEL_ID`, `DISCORD_ANNOUNCE_CHANNEL_ID`, `DISCORD_STAFF_ROLE_IDS`, `DISCORD_ATTENDANCE_POLL_MS` (default 60s), `DISCORD_NOTIFICATION_POLL_MS` (default 20s), `DISCORD_EVALUATION_POLL_MS` (default 60s), `DISCORD_IMMINENT_POLL_MS` (default 60s), plus existing `APP_URL`. Logs through `payload.logger`. +- **Structure**: `commands/` — `ping`, `signup`, `link`, `announce`, `remindEvaluations`; `events/interactionCreate.ts` — routes `ptf-att:` RSVP buttons; `services/` — `missionEmbeds` (attendance embed lifecycle + reconcile loop), `notificationBridge` (poll → Discord DMs), `evaluationReminders` (post-mission evaluation reminder DMs), and `imminentReminders` (pre-op thread + attendee pings); `lib/` — `roles.ts` (`isStaff`), `resolve.ts` (discordId ↔ Payload user lookups). Command registration scope: `signup`/`link`/`ping` are **global** (DM-usable — guild-scoped commands never appear in DMs), `announce`/`remind-evaluations` are guild-only. - **Evaluation reminders**: once a mission is evaluable (`Completed`, or `Scheduled`/`Active` past its start — same rule as `isMissionEvaluable`), the bot sends a one-shot DM (per user with a linked `discordId`) asking them to rate their leadership, plus a "rate your subordinates" section for leaders whose members RSVP'd yes. Links go to each ratee's profile page where the rating dialog lives. Recipient computation: `computeEvaluationReminderPlan` in `src/lib/evaluations/reminders.ts`; state marker: `evaluationRemindersSentAt` on Missions (one-shot, same pattern as `discordAttendanceSentAt`; withheld if the 40-DM/tick cap is hit — retries next tick). These DMs deliberately ignore `preferences.discord.enabled` (defaults false, not yet exposed in the web UI — honoring it would DM nobody). Staff can trigger manually via `/remind-evaluations` (optional `mission` option accepts an ID, name, or code name and re-sends even if the marker is set; omitted, it sweeps all pending missions and reports `{processed, sent}`). - **Sign-up / linking (feature 1)**: `/signup` is **DM-only** (the temp password flows through the DM). Creates the Payload user with `username` = `discordUsername` = the caller's Discord username, plus `discordId`, `displayName`, `steamId`, and a random temp password. The ephemeral reply carries the password as the guaranteed delivery path; `interaction.user.send()` is a best-effort persistent copy, so a blocked DM never orphans the account. `/link` works in servers **and** DMs (credential-free, ephemeral reply only) — matches `discordUsername` → sets `discordId`. - **DM gotcha**: a user with "Allow direct messages from server members" off in Discord privacy settings can neither receive the bot's DMs nor open a DM with the bot. The `/signup` rejection message explains how to enable it. diff --git a/docs/bot/design.md b/docs/bot/design.md index 0e741ab..8acf315 100644 --- a/docs/bot/design.md +++ b/docs/bot/design.md @@ -129,6 +129,12 @@ Dependency: `discord.js` ^14 (add to root `package.json`). Script: `"bot": "bun - **Deliberate exception**: these DMs ignore `preferences.discord.enabled` (defaults false, not yet exposed in the web UI — honoring it would DM nobody). Dedicated unit-admin flow, not notification-stream forwarding. - **Manual trigger**: `/remind-evaluations` (staff only, guild-only). Optional `mission` option (mission ID, name, or code name — ambiguous matches list candidates) sends reminders for that one mission even if the marker is already set (as long as it's evaluable); omitted, it runs the reconcile sweep over all pending missions immediately and reports `{processed, sent}`. Replies are ephemeral; the interaction is deferred because DM sending takes minutes at scale. +### E. Imminent-op reminders (pre-op) + +- **Trigger**: a posted op (unit-visible, Ready/Scheduled, live roll-call message) is within 15 minutes of `classification.startDateTime` (`src/lib/intelligence/imminentReminders.ts` holds the constants + query). Poll loop every `DISCORD_IMMINENT_POLL_MS` (default 60s), one-shot via the `imminentReminderSentAt` marker on Missions. +- **Action**: the bot opens a thread on the roll-call message ("Op starting soon: ", 100-char cap) and posts a reminder embed with the relative start time (`<t:...:R>`) plus a link to the mission page, pinging every yes-RSVP via `<@discordId>`. Pings ignore `preferences.discord.enabled` (same deliberate exception as evaluation reminders: a yes RSVP is explicit intent to attend). Unlinked attendees are skipped; a thread with no pings is still posted so players have a check-in point. +- **Staleness bound**: a missed reminder still fires within 15 minutes after the start (bot-restart grace), never later; the cancel flow clears the marker so a rescheduled op re-reminds for its new start time. + ### Authz (`src/bot/lib/roles.ts`) `isStaff(member)`: caller has any `DISCORD_STAFF_ROLE_IDS`, **or** their linked Payload user has roles `admin`/`developer`. RSVP/signup/link are open to all members. The bot performs Payload writes with `overrideAccess` and relies on these checks + its own validations. @@ -145,6 +151,7 @@ Dependency: `discord.js` ^14 (add to root `package.json`). Script: `"bot": "bun | `DISCORD_ATTENDANCE_POLL_MS` | no | default 60000 | | `DISCORD_NOTIFICATION_POLL_MS` | no | default 20000 | | `DISCORD_EVALUATION_POLL_MS` | no | default 60000 | +| `DISCORD_IMMINENT_POLL_MS` | no | default 60000 | `APP_URL` already exists — used for login links in DMs. diff --git a/src/bot/AGENTS.md b/src/bot/AGENTS.md index faaf9d9..c697aa1 100644 --- a/src/bot/AGENTS.md +++ b/src/bot/AGENTS.md @@ -30,6 +30,7 @@ bot/ 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) + imminentReminders.ts # Pre-op reminder: thread on the roll-call message + attendee pings (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 @@ -61,6 +62,10 @@ Bot posts RSVP embeds (Yes/Tentative/No) for future, Ready/Scheduled, visibility 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: imminent op reminder + +15 minutes before a posted op starts (`visibility: "unit"`, Ready/Scheduled, live roll-call message), `imminentReminders` opens a thread on the roll-call message ("Op starting soon: <name>") and pings every yes-RSVP via `<@discordId>` (pings ignore `preferences.discord.enabled`, same exception as evaluation reminders). One-shot via the `imminentReminderSentAt` marker on Missions; the cancel flow clears it so a rescheduled op re-reminds. Grace bound: a missed reminder still fires within 15 minutes after the start, never later. Query: `src/lib/intelligence/imminentReminders.ts` (window constants + `getImminentReminderMissions`). + ## 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. @@ -74,6 +79,7 @@ Once a mission is evaluable (`Completed`, or `Scheduled`/`Active` past its start | 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` | +| Imminent-op reminder logic | `bot/services/imminentReminders.ts` + `src/lib/intelligence/imminentReminders.ts` | | Transfer workflow | `bot/services/transferRequests.ts` + `lib/transferMessaging.ts` | | User lookup patterns | `bot/lib/resolve.ts` |