diff --git a/RUNEWAGER_FUNCTIONALITY_MAP.md b/RUNEWAGER_FUNCTIONALITY_MAP.md index 3888a01..b73b57d 100644 --- a/RUNEWAGER_FUNCTIONALITY_MAP.md +++ b/RUNEWAGER_FUNCTIONALITY_MAP.md @@ -119,12 +119,13 @@ The bot uses `user.pendingAction.type` as its input state machine. Key families: - **Forward registration**: `await_register_chat_forward`. ### Timeouts -- No global pendingAction auto-timeout was found for most flows. +- Global pending-action timeout now enforced in text flow: `PENDING_ACTION_TIMEOUT_MS = 15 minutes`; expired wait states are reset with a user-facing timeout message and `Main Menu` recovery buttons. - SSHV sessions have TTL-based GC (`SSHV_SESSION_TTL_MS`), with stale-state normalization and recovery. ## 8. Fallback Logic & Error Handling - Permission failures: `requireAdmin` + `sendCommandError`. +- Pending slash escapes: when a pending state exists, `/cancel` clears state; `/menu`, `/start`, and `/help` clear pending state and return user to main menu flow. - Invalid pending input: explicit prompts to retry (numeric checks, yes/no checks, IDs). - Missing entities (promo/giveaway not found): user-visible explanatory replies. - Announce/test flows return actionable follow-up messages. @@ -323,6 +324,7 @@ Admin starts wizard (gwiz) - 2026-02-26: Added repo-wide baseline docstrings to named function declarations (runtime, helper, and test modules) and re-verified command/callback coverage. - 2026-02-26: Re-verified removal of `/language` command/callback surface, reran smoke/full tests, and synchronized agent-contract wording in `CLAUDE.md` with map-first/update-map/verify requirements. - 2026-02-26: Completed a menu-integrity pass by adding explicit `Main Menu` + `Cancel` exits to admin category keyboards and settings submenu, then revalidated smoke/full tests. +- 2026-02-26: Added global 15-minute pending-action timeout and slash-command escape fallbacks (`/cancel`, `/menu`, `/start`, `/help`) for wait-for-input state safety. ## 25. AI Coder Contract Requirements diff --git a/index.js b/index.js index 56d815a..f7b0a05 100644 --- a/index.js +++ b/index.js @@ -378,6 +378,8 @@ const NON_USERNAME_WORDS = new Set([ ]); // Slash commands implemented in this file. Used for unknown-command fallback. +const PENDING_ACTION_TIMEOUT_MS = 15 * 60 * 1000; // 15m timeout for wait-for-input states + const REGISTERED_COMMANDS = new Set([ 'A', 'a', @@ -4178,46 +4180,6 @@ function escapeMarkdownFull(text) { return String(text).replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, '\\$&'); } -/** - - * ensureDataDir executes its scoped Runewager logic and participates in menu/command or utility flow composition. - - * Parameters: See the function signature for exact argument names and accepted values. - - * Returns: Returns the computed value or a Promise resolving to the operation result; may return void for side-effect handlers. - - * Side effects: May mutate runtime stores, pendingAction state, menu state, persistence files, logs, and callback progression. - - * Validation/safety: Uses existing guard utilities (admin checks, input checks, path checks, cooldown checks) where applicable. - - * Timeouts/fallbacks: Timeout and fallback behavior are controlled by the calling flow and global handler/state machine conventions. - - * Errors: Surfaces user-facing error replies and/or logs when inputs, permissions, or dependencies are invalid. - - * System fit: This function is part of the Runewager command/callback/state orchestration pipeline. - - */ - -/** - - * ensureDataDir executes its scoped Runewager logic and participates in menu/command or utility flow composition. - - * Parameters: See the function signature for exact argument names and accepted values. - - * Returns: Returns the computed value or a Promise resolving to the operation result; may return void for side-effect handlers. - - * Side effects: May mutate runtime stores, pendingAction state, menu state, persistence files, logs, and callback progression. - - * Validation/safety: Uses existing guard utilities (admin checks, input checks, path checks, cooldown checks) where applicable. - - * Timeouts/fallbacks: Timeout and fallback behavior are controlled by the calling flow and global handler/state machine conventions. - - * Errors: Surfaces user-facing error replies and/or logs when inputs, permissions, or dependencies are invalid. - - * System fit: This function is part of the Runewager command/callback/state orchestration pipeline. - - */ - /** * ensureDataDir executes its scoped Runewager logic and participates in menu/command or utility flow composition. @@ -7792,6 +7754,50 @@ bot.action('admin_cat_support', async (ctx) => { }); +/** + + + * adminTestsToolsKeyboard executes its scoped Runewager logic and participates in menu/command or utility flow composition. + + + * Parameters: See the function signature for exact argument names and accepted values. + + + * Returns: Returns the computed value or a Promise resolving to the operation result; may return void for side-effect handlers. + + + * Side effects: May mutate runtime stores, pendingAction state, menu state, persistence files, logs, and callback progression. + + + * Validation/safety: Uses existing guard utilities (admin checks, input checks, path checks, cooldown checks) where applicable. + + + * Timeouts/fallbacks: Timeout and fallback behavior are controlled by the calling flow and global handler/state machine conventions. + + + * Errors: Surfaces user-facing error replies and/or logs when inputs, permissions, or dependencies are invalid. + + + * System fit: This function is part of the Runewager command/callback/state orchestration pipeline. + + + */ + + +bot.action('admin_cat_system', async (ctx) => { + if (!requireAdmin(ctx)) return; + await ctx.answerCbQuery(); + const user = getUser(ctx); + await replaceCallbackPanel(ctx, '⚙️ *System Tools*', { parse_mode: 'Markdown', ...adminSystemToolsKeyboard(user) }); +}); + +bot.action('admin_cat_support', async (ctx) => { + if (!requireAdmin(ctx)) return; + await ctx.answerCbQuery(); + await replaceCallbackPanel(ctx, '🛟 *Support Tools*', { parse_mode: 'Markdown', ...adminSupportToolsKeyboard() }); +}); + + /** @@ -9010,6 +9016,32 @@ bot.on('text', async (ctx) => { const text = (ctx.message.text || '').trim(); + if (user.pendingAction && !user.pendingAction.createdAt) user.pendingAction.createdAt = Date.now(); + + if (user.pendingAction && (Date.now() - Number(user.pendingAction.createdAt || 0)) > PENDING_ACTION_TIMEOUT_MS) { + const expiredType = user.pendingAction.type || 'current action'; + user.pendingAction = null; + await ctx.reply( + `⏱️ Your pending step (${expiredType}) timed out after 15 minutes. Please start again from the menu.`, + Markup.inlineKeyboard([[Markup.button.callback('🏠 Main Menu', 'to_main_menu')], [Markup.button.callback('❌ Cancel', 'to_main_menu')]]), + ); + return; + } + + if (user.pendingAction && text.startsWith('/')) { + const cmd = text.slice(1).split(/\s+/)[0].split('@')[0].toLowerCase(); + if (cmd === 'cancel') { + user.pendingAction = null; + await ctx.reply('✅ Pending action cancelled.', Markup.inlineKeyboard([[Markup.button.callback('🏠 Main Menu', 'to_main_menu')]])); + return; + } + if (cmd === 'menu' || cmd === 'start' || cmd === 'help') { + user.pendingAction = null; + await sendMainMenu(ctx, user, 1); + return; + } + } + if (!user.pendingAction && text.startsWith('/')) { const cmd = text.slice(1).split(/\s+/)[0].split('@')[0]; if (!REGISTERED_COMMANDS.has(cmd)) {