-
Notifications
You must be signed in to change notification settings - Fork 5
Feat/agentic ai #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/agentic ai #14
Conversation
refactor: update import paths for ApplicationCommand and Event templates feat: implement deploy and undeploy message commands for slash commands feat: create message-command and sub-command templates for better command structure feat: add autocomplete functionality to sub-commands for enhanced user experience chore: add base command structure for future command implementations
…edis for rate limiting
…lines for Discord tool usage
…ext for improved action execution
…te limiter for improved performance
…oved async handling and structure
…l prompts with additional context and error handling guidelines
…r clarity and conciseness
…nd default guild ID, and update keywords for improved bot functionality
…ved readability and maintainability
|
Warning Rate limit exceeded@techwithanirudh has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 21 minutes and 6 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (2)
WalkthroughThis update introduces major new features, including voice channel support with real-time speech-to-text and AI-driven voice responses, channel access controls, and a modular AI tool system. It refactors prompts, providers, and utility modules, updates configuration and environment handling, and switches the software license from AGPLv3 to MIT. Changes
Sequence Diagram(s)Voice Channel AI InteractionsequenceDiagram
participant User
participant DiscordBot
participant Deepgram
participant AIModel
User->>DiscordBot: Joins voice channel & speaks
DiscordBot->>Deepgram: Streams user audio for transcription
Deepgram-->>DiscordBot: Sends transcript (final)
DiscordBot->>AIModel: Sends transcript for response
AIModel-->>DiscordBot: Returns AI-generated reply
DiscordBot->>Deepgram: Sends reply text for TTS
Deepgram-->>DiscordBot: Returns speech audio
DiscordBot->>User: Plays AI speech in voice channel
Discord Automation Tool Agent LoopsequenceDiagram
participant AIModel
participant DiscordBot
participant DiscordAPI
AIModel->>DiscordBot: Requests Discord action (via tool)
loop Agent Loop (max 15 steps)
DiscordBot->>DiscordAPI: Executes action (via sandbox)
DiscordAPI-->>DiscordBot: Returns result
DiscordBot->>AIModel: Returns output/status
AIModel->>DiscordBot: Decides next step or finishes
end
DiscordBot->>AIModel: Returns final answer
Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate Unit Tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
…or improved readability and maintainability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 57
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (51)
.env.example(2 hunks).eslintrc.json(1 hunks).prettierrc.json(1 hunks).vscode/settings.json(1 hunks)LICENSE(1 hunks)README.md(1 hunks)TODO.md(1 hunks)package.json(2 hunks)src/commands/channels.ts(1 hunks)src/commands/index.ts(1 hunks)src/commands/ping.ts(1 hunks)src/commands/voice-channel/index.ts(1 hunks)src/commands/voice-channel/join.ts(1 hunks)src/commands/voice-channel/leave.ts(1 hunks)src/config.ts(1 hunks)src/deploy-commands.ts(1 hunks)src/env.ts(1 hunks)src/events/index.ts(1 hunks)src/events/message-create/index.ts(1 hunks)src/events/message-create/utils/relevance.ts(1 hunks)src/events/message-create/utils/respond.ts(1 hunks)src/index.ts(1 hunks)src/lib/ai/prompts.ts(1 hunks)src/lib/ai/providers.ts(1 hunks)src/lib/ai/tools/discord.ts(1 hunks)src/lib/ai/tools/get-weather.ts(1 hunks)src/lib/ai/tools/report.ts(1 hunks)src/lib/constants.ts(0 hunks)src/lib/kv.ts(1 hunks)src/lib/logger.ts(1 hunks)src/lib/queries.ts(1 hunks)src/lib/validators/index.ts(1 hunks)src/lib/validators/probability.ts(1 hunks)src/utils/context.ts(1 hunks)src/utils/delay.ts(1 hunks)src/utils/discord.ts(1 hunks)src/utils/log.ts(1 hunks)src/utils/message-rate-limiter.ts(1 hunks)src/utils/messages.ts(1 hunks)src/utils/sandbox.ts(1 hunks)src/utils/status.ts(1 hunks)src/utils/time.ts(1 hunks)src/utils/tokenize-messages.ts(1 hunks)src/utils/triggers.ts(1 hunks)src/utils/unprompted-counter.ts(0 hunks)src/utils/voice/helpers/ai.ts(1 hunks)src/utils/voice/helpers/audio.ts(1 hunks)src/utils/voice/helpers/deepgram.ts(1 hunks)src/utils/voice/helpers/index.ts(1 hunks)src/utils/voice/stream.ts(1 hunks)tsconfig.json(1 hunks)
💤 Files with no reviewable changes (2)
- src/lib/constants.ts
- src/utils/unprompted-counter.ts
🧰 Additional context used
🧬 Code Graph Analysis (18)
src/lib/logger.ts (1)
src/env.ts (1)
env(4-56)
src/utils/time.ts (1)
src/config.ts (1)
timezone(6-6)
src/utils/delay.ts (1)
src/utils/tokenize-messages.ts (2)
normalize(10-17)sentences(3-8)
src/index.ts (4)
src/deploy-commands.ts (1)
deployCommands(14-29)src/commands/index.ts (1)
commands(5-9)src/events/index.ts (1)
events(3-5)src/env.ts (1)
env(4-56)
src/deploy-commands.ts (2)
src/commands/index.ts (1)
commands(5-9)src/env.ts (1)
env(4-56)
src/commands/voice-channel/leave.ts (2)
src/commands/voice-channel/join.ts (2)
data(13-15)execute(17-62)src/commands/voice-channel/index.ts (2)
data(5-15)execute(17-33)
src/utils/triggers.ts (1)
src/config.ts (1)
keywords(3-3)
src/lib/ai/tools/discord.ts (4)
src/utils/discord.ts (2)
makeEmbed(27-70)scrub(72-76)src/lib/ai/providers.ts (1)
myProvider(27-39)src/lib/ai/prompts.ts (1)
agentPrompt(79-129)src/utils/sandbox.ts (1)
runInSandbox(11-46)
src/commands/voice-channel/index.ts (2)
src/commands/voice-channel/leave.ts (2)
data(10-12)execute(14-31)src/commands/voice-channel/join.ts (2)
data(13-15)execute(17-62)
src/commands/channels.ts (2)
src/events/message-create/index.ts (1)
execute(45-105)src/lib/kv.ts (2)
redisKeys(19-24)redis(7-10)
src/utils/context.ts (5)
src/lib/ai/prompts.ts (1)
RequestHints(3-12)src/lib/queries.ts (2)
getMessagesByChannel(12-29)getChannelName(31-43)src/config.ts (2)
initialMessages(24-29)timezone(6-6)src/utils/messages.ts (1)
convertToModelMessages(9-27)src/utils/time.ts (1)
getTimeInCity(4-11)
src/utils/log.ts (1)
src/utils/triggers.ts (1)
TriggerType(3-3)
src/utils/voice/helpers/deepgram.ts (1)
src/env.ts (1)
env(4-56)
src/lib/ai/providers.ts (1)
src/env.ts (1)
env(4-56)
src/utils/voice/stream.ts (4)
src/utils/voice/helpers/deepgram.ts (2)
deepgram(4-4)speak(11-23)src/utils/voice/helpers/ai.ts (1)
getAIResponse(8-18)src/config.ts (1)
voice(31-33)src/utils/voice/helpers/audio.ts (1)
playAudio(10-37)
src/lib/kv.ts (1)
src/env.ts (1)
env(4-56)
src/events/message-create/index.ts (8)
src/lib/kv.ts (3)
ratelimit(12-17)redisKeys(19-24)redis(7-10)src/utils/log.ts (3)
logIncoming(16-18)logTrigger(4-14)logReply(20-39)src/utils/triggers.ts (1)
getTrigger(5-27)src/config.ts (1)
keywords(3-3)src/utils/message-rate-limiter.ts (3)
clearUnprompted(11-13)getUnprompted(4-9)hasUnpromptedQuota(15-19)src/utils/context.ts (1)
buildChatContext(10-48)src/events/message-create/utils/respond.ts (1)
generateResponse(13-68)src/events/message-create/utils/relevance.ts (1)
assessRelevance(7-32)
src/utils/message-rate-limiter.ts (2)
src/lib/kv.ts (2)
redisKeys(19-24)redis(7-10)src/config.ts (1)
messageThreshold(23-23)
🪛 LanguageTool
README.md
[locale-violation] ~29-~29: In American English, ‘afterward’ is the preferred variant. ‘Afterwards’ is more commonly used in British English and other dialects.
Context: ...idpolice.com/how-to-make-discord-bot/). Afterwards, you will need [Git][git] and [Bun][bun...
(AFTERWARDS_US)
TODO.md
[uncategorized] ~6-~6: This expression is usually spelled with a hyphen.
Context: ...true? Agent Isolation for each server, role based access control mention that u need to i...
(BASED_HYPHEN)
[misspelling] ~7-~7: Did you mean “you”?
Context: ... role based access control mention that u need to install rust curl --proto '=htt...
(U_TO_YOU)
[grammar] ~10-~10: This expression is usually spelled with a hyphen.
Context: ...m back audio from ElevenLabs instead of non streaming Switch to AI SDK Voice leater seperate...
(NON_ANTI_JJ)
🪛 markdownlint-cli2 (0.17.2)
README.md
1-1: Inline HTML
Element: h1
(MD033, no-inline-html)
10-10: Inline HTML
Element: a
(MD033, no-inline-html)
14-14: Inline HTML
Element: a
(MD033, no-inline-html)
27-27: Inline HTML
Element: a
(MD033, no-inline-html)
49-49: Inline HTML
Element: a
(MD033, no-inline-html)
55-55: Link and image reference definitions should be needed
Unused link or image reference definition: "pr"
(MD053, link-image-reference-definitions)
57-57: Link and image reference definitions should be needed
Unused link or image reference definition: "node"
(MD053, link-image-reference-definitions)
TODO.md
7-7: Bare URL used
null
(MD034, no-bare-urls)
🪛 Biome (1.9.4)
src/utils/status.ts
[error] 10-10: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
src/utils/voice/helpers/ai.ts
[error] 11-12: Template literals are preferred over string concatenation.
Unsafe fix: Use a template literal.
(lint/style/useTemplate)
src/lib/ai/providers.ts
[error] 20-20: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
[error] 24-24: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
src/utils/discord.ts
[error] 35-35: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🪛 dotenv-linter (3.3.0)
.env.example
[warning] 10-10: [TrailingWhitespace] Trailing whitespace detected
[warning] 10-10: [UnorderedKey] The DISCORD_OWNER_ID key should go before the DISCORD_TOKEN key
[warning] 12-12: [UnorderedKey] The DISCORD_DEFAULT_GUILD_ID key should go before the DISCORD_OWNER_ID key
[warning] 53-53: [EndingBlankLine] No blank line at the end of the file
🪛 ESLint
src/utils/sandbox.ts
[error] 32-32: Unexpected empty async function.
(@typescript-eslint/no-empty-function)
🔇 Additional comments (29)
LICENSE (4)
1-2: Verify license header accuracy.
Ensure the header exactly matches the official MIT License title (“MIT License”) to avoid any ambiguity in licensing.
3-4: Confirm copyright year and owner.
Double-check that “2025” and “Anirudh Sriram” accurately reflect the project’s current ownership and date.
5-13: Validate permission grant text.
This block must match the standard MIT grant section without unintended modifications. Confirm it aligns verbatim with the approved template.
15-21: Validate warranty disclaimer.
Verify that the disclaimer wording is identical to the canonical MIT License to ensure full legal coverage.README.md (2)
5-8: Correct Table of Contents numbering
The ToC now properly includes the "3. 📚 Getting Started" entry, ensuring a consecutive sequence.
49-50: Inconsistent license change in summary vs README
The AI summary notes a license change to MIT, but the README still references AGPL-v3. Please verify and update the license text if a change was intended.Likely an incorrect or invalid review comment.
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
49-49: Inline HTML
Element: a(MD033, no-inline-html)
src/lib/validators/index.ts (1)
1-1: Consistent quote style in exports.Switching to single quotes for the module path aligns with the project-wide string delimiter convention.
tsconfig.json (1)
1-32: Formatting-only changes.This update is purely whitespace and line-break adjustments; there are no functional or configuration modifications.
.eslintrc.json (1)
1-26: Formatting-only changes.Only reflowed whitespace and line breaks to match Prettier rules; ESLint rules remain untouched.
src/lib/validators/probability.ts (2)
1-1: Update zod import path.Importing from
'zod/v4'ensures compatibility with the pinned major version and matches other parts of the codebase.
7-8: Consistent single quotes in.describe()calls.Standardizing string delimiters to single quotes improves readability and enforces consistency across all validators.
Also applies to: 12-14
.vscode/settings.json (1)
2-9: Add custom cSpell words for AI/voice features.Including “arcas”, “Deepgram”, “ELEVENLABS”, and related terms prevents the spell checker from flagging new feature–specific identifiers.
src/lib/queries.ts (1)
1-43: Stylistic formatting changes only; logic and functionality are unchanged and adhere to the.prettierrc.jsonstandards.src/lib/logger.ts (1)
1-41: All modifications are stylistic—replacing double quotes with single quotes and adjusting spacing per the new Prettier config. No functional changes detected..prettierrc.json (1)
1-10: Add consistent Prettier configuration.
The new.prettierrc.jsonestablishes project-wide formatting rules (80-char line width, 2-space indent, single quotes, semicolons, trailing commas, LF endings) matching the style updates in this PR.src/utils/status.ts (1)
1-8: Pure formatting changes (quote style, indentation) per.prettierrc.json. No behavioral modifications.Also applies to: 12-36
src/commands/ping.ts (1)
1-9: Only quote style changed; logic and functionality remain correct.src/events/index.ts (1)
1-5: No functional changes – looks good
Only quote-style & blank-line tweaks; nothing else to flag.src/deploy-commands.ts (1)
31-43: Top-levelawaitrequires ES2022 or later – verify build targetThe global deployment block relies on top-level
await.
Ensuretsconfig.jsonis targeting at leastES2022and that your runtime (e.g., Node ≥ 14.8 with--experimental-top-level-awaitor Node 16+) matches, otherwise this will be a syntax error in production.If older environments must be supported, wrap the block in an immediately-invoked async function instead.
src/index.ts (1)
10-21: Verify privileged intents are enabled
GuildMembersandMessageContentrequire enabling Privileged Gateway Intents in the Discord developer portal. Deployment will silently fail otherwise.src/utils/log.ts (1)
17-18:❓ Verification inconclusive
Beware of sensitive-content logging.
contentmay hold private DMs or PII. If log retention or transport isn’t encrypted, consider redacting or hashing the message text, or toggling with a debug flag.
Review Sensitive Data Logging in src/utils/log.ts
The call to
logger.info({ user: username, content }, `[${ctxId}] Incoming message`);may record private messages or PII in plain text. If your logging pipeline or storage isn’t fully encrypted and access-controlled, consider:
- Redacting or hashing the
contentfield before logging- Emitting this only at a debug level behind a feature flag
- Verifying that all log transport and retention are end-to-end encrypted
src/config.ts (1)
8-13:❌ Incorrect review comment
speedFactorastronomical — verify downstream math doesn’t underflow to 0 ms.A factor of
6 000 000 000withspeedMethod: 'divide'will likely truncate delays to0when using 32-bit integer arithmetic. Confirm the calculation logic or switch to floats.
I’ll search for all usages of
speedFactorandspeedMethod(and any genericspeed.references) to see how the values are applied:
🏁 Script executed:
#!/bin/bash rg -n "speedFactor" -C3 rg -n "speedMethod" -C3 rg -n "speed\." -C3Length of output: 2116
Ignore speedFactor underflow concern
In JS/TS the/operator returns a double-precision float, solength / 6000000000yields a tiny non-zero value (not0). After multiplying by1000,Math.max(baseSeconds * 1000, speedConfig.minDelay)(whereminDelay = 1) guarantees the delay is never below 1 ms. No changes required.Likely an incorrect or invalid review comment.
src/utils/messages.ts (1)
9-27: LGTM!The async conversion to
ModelMessageformat is well-implemented with proper role assignment and comprehensive author context.src/lib/kv.ts (1)
1-24: Environment-aware prefixing looks good!The implementation correctly separates development and production Redis namespaces, and the new
allowedChannelskey generator follows the established pattern.src/env.ts (1)
2-2:✅ Verification successful
Verify the zod import path.
The import from 'zod/v4' is unusual. Most projects import directly from 'zod'.
🌐 Web query:
Is 'zod/v4' a valid import path for the zod library, or should it be imported as 'zod'?💡 Result:
The import path you should use for the Zod library depends on which major version of Zod you intend to target.
If you want to use Zod 4, you should import it using the subpath:
import { z } from "zod/v4";This subpath is explicitly provided to allow projects to migrate from Zod 3 to Zod 4 incrementally. The maintainers have confirmed that Zod 4 is available at the "zod/v4" subpath and is considered stable and production-ready[6][9]. This approach will remain stable and supported, even after Zod 4 becomes the default export in a future release[9].
If you want to use Zod 3, or are working in an environment that still relies on Zod 3, you should import from the package root:
import { z } from "zod";The root import will continue to export Zod 3 during the transition period but will eventually switch to Zod 4 after the migration window ends[9].
Summary Table:
Target Zod Version Import Path Notes Zod 3 "zod" Root continues to export Zod 3 during migration[9]. Zod 4 "zod/v4" Subpath is stable and production-ready; recommended for Zod 4[6][9]. After the transition period, both subpaths ("zod/v3" and "zod/v4") are expected to remain available for compatibility, but the root path ("zod") will eventually switch to Zod 4 by default[9].
In summary:
- "zod/v4" is a valid and recommended import path for Zod 4[6][9].
- Use "zod" if you specifically want to target Zod 3 or until the package root switches to Zod 4[5][9].
Citations:
- 1: https://zod.dev
- 2: https://github.com/colinhacks/zod
- 3: https://zod.dev/basics
- 4: colinhacks/zod#347
- 5: https://www.npmjs.com/package/zod/v/3.2.0-beta
- 6: https://zod.dev/v4/changelog
- 7: https://www.npmjs.com/package/zod
- 8: https://zod.dev/?id=basic-usage
- 9: colinhacks/zod#4371
- 10: colinhacks/zod#372
Import path 'zod/v4' is valid for Zod 4 – no changes required.
Verified that the
"zod/v4"subpath is the officially supported, stable import for Zod 4 (the root"zod"currently targets Zod 3 during the migration period and will eventually switch to Zod 4). You can safely keep:• src/env.ts (line 2):
import { z } from 'zod/v4';src/commands/voice-channel/join.ts (1)
61-61: UseeditReplyinstead offollowUpafterdeferReplyYou called
deferReply()at the top. To comply with Discord’s interaction rules, prefereditReply()for the first response instead of an additionalfollowUp(), otherwise you may hit “interaction already responded” errors in edge-cases.src/utils/discord.ts (1)
53-55: Risk of exceeding Discord field lengthWrapping large values in code-blocks (
```) inflates their size.
Remember Discord limits fieldvalueto 1024 characters – consider truncation or attachment for longer snippets.src/events/message-create/index.ts (2)
21-27: LGTM!Clean rate limiting implementation with appropriate logging.
29-43: LGTM!Good implementation with sensible defaults - allows all channels when none are configured, and always allows DMs.
| [pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request | ||
| [git]: https://git-scm.com | ||
| [node]: https://nodejs.org/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Remove unused link reference definitions
The [pr] and [node] reference labels are not used elsewhere in the document. Cleaning them up will reduce clutter.
-[pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
-[node]: https://nodejs.org/📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| [pr]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request | |
| [git]: https://git-scm.com | |
| [node]: https://nodejs.org/ | |
| [git]: https://git-scm.com |
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
55-55: Link and image reference definitions should be needed
Unused link or image reference definition: "pr"
(MD053, link-image-reference-definitions)
57-57: Link and image reference definitions should be needed
Unused link or image reference definition: "node"
(MD053, link-image-reference-definitions)
🤖 Prompt for AI Agents
In README.md around lines 55 to 57, the link reference definitions for [pr] and
[node] are not used anywhere else in the document. Remove these unused link
reference definitions to clean up and reduce clutter in the markdown file,
leaving only the [git] reference if it is still used.
| [bun]: https://bun.sh/ | ||
| [mem0]: https://mem0.ai/ | ||
| [exa]: https://exa.ai/ | ||
| <h1 align="center">AI Discord Bot</h4> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix mismatched HTML header tag
The opening <h1> tag is incorrectly closed with </h4>, causing an HTML structure error.
Apply this diff:
-<h1 align="center">AI Discord Bot</h4>
+<h1 align="center">AI Discord Bot</h1>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <h1 align="center">AI Discord Bot</h4> | |
| <h1 align="center">AI Discord Bot</h1> |
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)
1-1: Inline HTML
Element: h1
(MD033, no-inline-html)
🤖 Prompt for AI Agents
In README.md at line 1, the opening <h1> tag is incorrectly closed with a </h4>
tag, causing an HTML structure error. Fix this by replacing the closing </h4>
tag with the correct </h1> tag to properly close the header element.
| if (arr.length === 0) throw new Error('Array must not be empty'); | ||
| return arr[Math.floor(Math.random() * arr.length)]!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Address non-null assertion lint error.
The ! on the array access is safe due to the preceding empty check, but it violates the @typescript-eslint/no-non-null-assertion rule. Consider either:
- Removing the
!and handlingundefinedexplicitly, or - Suppressing the rule with an ESLint disable comment on this line.
🧰 Tools
🪛 Biome (1.9.4)
[error] 10-10: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
🤖 Prompt for AI Agents
In src/utils/status.ts around lines 9 to 10, the non-null assertion operator `!`
on the array access causes a lint error despite the preceding empty check. To
fix this, either remove the `!` and add explicit handling for the potential
undefined value returned by the array access, or keep the `!` and add an ESLint
disable comment on that line to suppress the no-non-null-assertion rule.
| import { format } from 'date-fns'; | ||
| import { TZDate } from '@date-fns/tz'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TZDate import is invalid – switch to date-fns-tz helpers instead
@date-fns/tz does not expose a constructable TZDate class. Attempting new TZDate(...) will throw at runtime.
-import { format } from 'date-fns';
-import { TZDate } from '@date-fns/tz';
+import { format } from 'date-fns';
+import { utcToZonedTime } from 'date-fns-tz';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { format } from 'date-fns'; | |
| import { TZDate } from '@date-fns/tz'; | |
| import { format } from 'date-fns'; | |
| import { utcToZonedTime } from 'date-fns-tz'; |
🤖 Prompt for AI Agents
In src/utils/time.ts at lines 1 to 2, the import of TZDate from '@date-fns/tz'
is invalid because that package does not export a constructable TZDate class.
Replace the TZDate import with appropriate helpers from the 'date-fns-tz'
package, such as using functions like zonedTimeToUtc or utcToZonedTime, and
update the code to use these functions instead of constructing a TZDate
instance.
| const now = new Date(); | ||
| const zonedDate = new TZDate(now, timezone); | ||
| return format(zonedDate, formatStr); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Runtime crash: new TZDate() is not a function
The current implementation will throw TypeError: TZDate is not a constructor. Use utcToZonedTime (or formatInTimeZone) instead.
- const now = new Date();
- const zonedDate = new TZDate(now, timezone);
- return format(zonedDate, formatStr);
+ const now = new Date();
+ const zonedDate = utcToZonedTime(now, timezone);
+ return format(zonedDate, formatStr, { timeZone: timezone });This also eliminates the implicit dependency on a custom TZDate type and keeps the codebase aligned with the official date-fns-tz API.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const now = new Date(); | |
| const zonedDate = new TZDate(now, timezone); | |
| return format(zonedDate, formatStr); | |
| const now = new Date(); | |
| const zonedDate = utcToZonedTime(now, timezone); | |
| return format(zonedDate, formatStr, { timeZone: timezone }); |
🤖 Prompt for AI Agents
In src/utils/time.ts around lines 8 to 10, replace the usage of the non-existent
TZDate constructor with the official date-fns-tz function utcToZonedTime or
formatInTimeZone. Import utcToZonedTime from date-fns-tz and use it to convert
the current date to the specified timezone before formatting, ensuring
compatibility with the library's API and preventing the runtime TypeError.
| const google = createGoogleGenerativeAI({ | ||
| apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!, | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
google provider is instantiated but never used.
Dead code adds maintenance overhead; either remove it or wire it into imageModels / languageModels.
🧰 Tools
🪛 Biome (1.9.4)
[error] 24-24: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
🤖 Prompt for AI Agents
In src/lib/ai/providers.ts around lines 23 to 26, the 'google' provider is
created but not used anywhere, resulting in dead code. To fix this, either
remove the 'google' provider instantiation if it is unnecessary, or integrate it
properly by adding it to the 'imageModels' or 'languageModels' collections where
other providers are used, ensuring it is utilized in the application.
| const openrouter = createOpenRouter({ | ||
| apiKey: env.OPENROUTER_API_KEY!, | ||
| }); | ||
|
|
||
| const google = createGoogleGenerativeAI({ | ||
| apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-null assertions hide missing API keys; implement explicit guards.
env.OPENROUTER_API_KEY! and env.GOOGLE_GENERATIVE_AI_API_KEY! were flagged by Biome.
At runtime an undefined key will cause the provider to 401 rather than fail fast.
-const openrouter = createOpenRouter({
- apiKey: env.OPENROUTER_API_KEY!,
-});
+if (!env.OPENROUTER_API_KEY) {
+ throw new Error('OPENROUTER_API_KEY is not set');
+}
+const openrouter = createOpenRouter({ apiKey: env.OPENROUTER_API_KEY });Apply similar handling for the Google provider.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const openrouter = createOpenRouter({ | |
| apiKey: env.OPENROUTER_API_KEY!, | |
| }); | |
| const google = createGoogleGenerativeAI({ | |
| apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!, | |
| }); | |
| if (!env.OPENROUTER_API_KEY) { | |
| throw new Error('OPENROUTER_API_KEY is not set'); | |
| } | |
| const openrouter = createOpenRouter({ apiKey: env.OPENROUTER_API_KEY }); | |
| const google = createGoogleGenerativeAI({ | |
| apiKey: env.GOOGLE_GENERATIVE_AI_API_KEY!, | |
| }); |
🧰 Tools
🪛 Biome (1.9.4)
[error] 20-20: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
[error] 24-24: Forbidden non-null assertion.
(lint/style/noNonNullAssertion)
🤖 Prompt for AI Agents
In src/lib/ai/providers.ts around lines 19 to 25, the code uses non-null
assertions for API keys which can cause runtime 401 errors if keys are missing.
Replace the non-null assertions with explicit checks that verify the presence of
env.OPENROUTER_API_KEY and env.GOOGLE_GENERATIVE_AI_API_KEY before creating the
providers. If a key is missing, throw a clear error immediately to fail fast and
avoid runtime authentication issues.
| const key = redisKeys.messageCount(ctxId); | ||
| const n = await redis.incr(key); | ||
| if (n === 1) await redis.expire(key, 3600); // 1‑hour window | ||
| return n; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
n === 1 comparison might fail due to string vs number.
redis.incr from Upstash can return a string. Guard with Number(n) === 1 to ensure the expiry is always set.
- const n = await redis.incr(key);
- if (n === 1) await redis.expire(key, 3600); // 1-hour window
+ const n = Number(await redis.incr(key));
+ if (n === 1) await redis.expire(key, 3600); // 1-hour window📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const key = redisKeys.messageCount(ctxId); | |
| const n = await redis.incr(key); | |
| if (n === 1) await redis.expire(key, 3600); // 1‑hour window | |
| return n; | |
| const key = redisKeys.messageCount(ctxId); | |
| - const n = await redis.incr(key); | |
| - if (n === 1) await redis.expire(key, 3600); // 1-hour window | |
| + const n = Number(await redis.incr(key)); | |
| + if (n === 1) await redis.expire(key, 3600); // 1-hour window | |
| return n; |
🤖 Prompt for AI Agents
In src/utils/message-rate-limiter.ts around lines 5 to 8, the comparison n === 1
may fail because redis.incr can return a string instead of a number. To fix
this, convert n to a number using Number(n) before the comparison, changing the
condition to Number(n) === 1 to ensure the expiry is always set correctly.
| export async function processAttachments( | ||
| attachments: Collection<string, DiscordAttachment>, | ||
| ): Promise<Array<FilePart>> { | ||
| const validTypes = ['image/jpeg', 'image/png', 'application/pdf']; | ||
| const invalidAttachments = attachments.filter( | ||
| (attachment) => !validTypes.includes(attachment.contentType ?? ''), | ||
| ); | ||
|
|
||
| if (invalidAttachments.size > 0) { | ||
| logger.warn( | ||
| `Ignoring attachments: ${Array.from(invalidAttachments.values()) | ||
| .map((a) => a.name) | ||
| .join(', ')}`, | ||
| ); | ||
| } | ||
|
|
||
| const results = await Promise.all( | ||
| attachments.map(async (attachment) => { | ||
| const response = await fetch(attachment.url); | ||
| const buffer = await response.arrayBuffer(); | ||
| return { | ||
| type: 'file' as const, | ||
| data: buffer, | ||
| mediaType: attachment.contentType ?? 'application/octet-stream', | ||
| filename: attachment.name, | ||
| }; | ||
| }), | ||
| ); | ||
|
|
||
| return results; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix attachment filtering logic and add error handling.
The function filters for valid attachment types but then processes all attachments, including invalid ones. Additionally, the fetch operation lacks error handling.
Apply this diff to fix the issues:
export async function processAttachments(
attachments: Collection<string, DiscordAttachment>,
): Promise<Array<FilePart>> {
const validTypes = ['image/jpeg', 'image/png', 'application/pdf'];
- const invalidAttachments = attachments.filter(
- (attachment) => !validTypes.includes(attachment.contentType ?? ''),
+ const validAttachments = attachments.filter(
+ (attachment) => validTypes.includes(attachment.contentType ?? ''),
);
+ const invalidAttachments = attachments.filter(
+ (attachment) => !validTypes.includes(attachment.contentType ?? ''),
+ );
if (invalidAttachments.size > 0) {
logger.warn(
`Ignoring attachments: ${Array.from(invalidAttachments.values())
.map((a) => a.name)
.join(', ')}`,
);
}
const results = await Promise.all(
- attachments.map(async (attachment) => {
+ validAttachments.map(async (attachment) => {
+ try {
const response = await fetch(attachment.url);
+ if (!response.ok) {
+ throw new Error(`Failed to fetch attachment: ${response.statusText}`);
+ }
const buffer = await response.arrayBuffer();
return {
type: 'file' as const,
data: buffer,
mediaType: attachment.contentType ?? 'application/octet-stream',
filename: attachment.name,
};
+ } catch (error) {
+ logger.error(`Failed to process attachment ${attachment.name}:`, error);
+ throw error;
+ }
}),
);
return results;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function processAttachments( | |
| attachments: Collection<string, DiscordAttachment>, | |
| ): Promise<Array<FilePart>> { | |
| const validTypes = ['image/jpeg', 'image/png', 'application/pdf']; | |
| const invalidAttachments = attachments.filter( | |
| (attachment) => !validTypes.includes(attachment.contentType ?? ''), | |
| ); | |
| if (invalidAttachments.size > 0) { | |
| logger.warn( | |
| `Ignoring attachments: ${Array.from(invalidAttachments.values()) | |
| .map((a) => a.name) | |
| .join(', ')}`, | |
| ); | |
| } | |
| const results = await Promise.all( | |
| attachments.map(async (attachment) => { | |
| const response = await fetch(attachment.url); | |
| const buffer = await response.arrayBuffer(); | |
| return { | |
| type: 'file' as const, | |
| data: buffer, | |
| mediaType: attachment.contentType ?? 'application/octet-stream', | |
| filename: attachment.name, | |
| }; | |
| }), | |
| ); | |
| return results; | |
| } | |
| export async function processAttachments( | |
| attachments: Collection<string, DiscordAttachment>, | |
| ): Promise<Array<FilePart>> { | |
| const validTypes = ['image/jpeg', 'image/png', 'application/pdf']; | |
| const validAttachments = attachments.filter( | |
| (attachment) => validTypes.includes(attachment.contentType ?? ''), | |
| ); | |
| const invalidAttachments = attachments.filter( | |
| (attachment) => !validTypes.includes(attachment.contentType ?? ''), | |
| ); | |
| if (invalidAttachments.size > 0) { | |
| logger.warn( | |
| `Ignoring attachments: ${Array.from(invalidAttachments.values()) | |
| .map((a) => a.name) | |
| .join(', ')}`, | |
| ); | |
| } | |
| const results = await Promise.all( | |
| validAttachments.map(async (attachment) => { | |
| try { | |
| const response = await fetch(attachment.url); | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch attachment: ${response.statusText}`); | |
| } | |
| const buffer = await response.arrayBuffer(); | |
| return { | |
| type: 'file' as const, | |
| data: buffer, | |
| mediaType: attachment.contentType ?? 'application/octet-stream', | |
| filename: attachment.name, | |
| }; | |
| } catch (error) { | |
| logger.error(`Failed to process attachment ${attachment.name}:`, error); | |
| throw error; | |
| } | |
| }), | |
| ); | |
| return results; | |
| } |
🤖 Prompt for AI Agents
In src/utils/messages.ts around lines 29 to 59, the function filters invalid
attachments but still processes all attachments including invalid ones, and
lacks error handling for fetch operations. Fix this by changing the attachments
processed to only the valid ones filtered by contentType. Also, wrap the fetch
call in a try-catch block to handle potential errors gracefully, logging or
skipping failed fetches as appropriate.
| return interaction.reply({ | ||
| content: 'Unknown subcommand. ', | ||
| flags: MessageFlags.Ephemeral, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Fix trailing space in error message.
- content: 'Unknown subcommand. ',
+ content: 'Unknown subcommand.',📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return interaction.reply({ | |
| content: 'Unknown subcommand. ', | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| return interaction.reply({ | |
| content: 'Unknown subcommand.', | |
| flags: MessageFlags.Ephemeral, | |
| }); |
🤖 Prompt for AI Agents
In src/commands/channels.ts around lines 121 to 124, the error message string
'Unknown subcommand. ' has a trailing space at the end. Remove the trailing
space from the content string so it reads 'Unknown subcommand.' without any
extra spaces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
| if (sub === 'add' || sub === 'remove') { | ||
| const channel = getChannel(); | ||
|
|
||
| if (channel.type !== ChannelType.GuildText) { | ||
| return interaction.reply({ | ||
| content: 'Please pick a text channel.', | ||
| flags: MessageFlags.Ephemeral, | ||
| }); | ||
| } | ||
|
|
||
| if (sub === 'add') { | ||
| const isMember = await redis.sismember(guildKey, channel.id); | ||
| if (isMember) { | ||
| return interaction.reply({ | ||
| content: `${channel} is already allowed.`, | ||
| flags: MessageFlags.Ephemeral, | ||
| }); | ||
| } | ||
| await redis.sadd(guildKey, channel.id); | ||
| return interaction.reply({ | ||
| content: `done! thanks for letting me talk in ${channel}!`, | ||
| flags: MessageFlags.Ephemeral, | ||
| }); | ||
| } else { | ||
| const removedCount = await redis.srem(guildKey, channel.id); | ||
| if (!removedCount) { | ||
| return interaction.reply({ | ||
| content: `there's nothing to remove! ${channel} wasn't even on the list.`, | ||
| flags: MessageFlags.Ephemeral, | ||
| }); | ||
| } | ||
| return interaction.reply({ | ||
| content: `aw... ${channel} has been removed from the allowed list. i won't talk there anymore...`, | ||
| flags: MessageFlags.Ephemeral, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Remove redundant channel type check.
The channel type validation is unnecessary since the option already restricts selection to text channels only.
if (sub === 'add' || sub === 'remove') {
const channel = getChannel();
- if (channel.type !== ChannelType.GuildText) {
- return interaction.reply({
- content: 'Please pick a text channel.',
- flags: MessageFlags.Ephemeral,
- });
- }
-
if (sub === 'add') {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (sub === 'add' || sub === 'remove') { | |
| const channel = getChannel(); | |
| if (channel.type !== ChannelType.GuildText) { | |
| return interaction.reply({ | |
| content: 'Please pick a text channel.', | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } | |
| if (sub === 'add') { | |
| const isMember = await redis.sismember(guildKey, channel.id); | |
| if (isMember) { | |
| return interaction.reply({ | |
| content: `${channel} is already allowed.`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } | |
| await redis.sadd(guildKey, channel.id); | |
| return interaction.reply({ | |
| content: `done! thanks for letting me talk in ${channel}!`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } else { | |
| const removedCount = await redis.srem(guildKey, channel.id); | |
| if (!removedCount) { | |
| return interaction.reply({ | |
| content: `there's nothing to remove! ${channel} wasn't even on the list.`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } | |
| return interaction.reply({ | |
| content: `aw... ${channel} has been removed from the allowed list. i won't talk there anymore...`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } | |
| if (sub === 'add' || sub === 'remove') { | |
| const channel = getChannel(); | |
| if (sub === 'add') { | |
| const isMember = await redis.sismember(guildKey, channel.id); | |
| if (isMember) { | |
| return interaction.reply({ | |
| content: `${channel} is already allowed.`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } | |
| await redis.sadd(guildKey, channel.id); | |
| return interaction.reply({ | |
| content: `done! thanks for letting me talk in ${channel}!`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } else { | |
| const removedCount = await redis.srem(guildKey, channel.id); | |
| if (!removedCount) { | |
| return interaction.reply({ | |
| content: `there's nothing to remove! ${channel} wasn't even on the list.`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } | |
| return interaction.reply({ | |
| content: `aw... ${channel} has been removed from the allowed list. i won't talk there anymore...`, | |
| flags: MessageFlags.Ephemeral, | |
| }); | |
| } |
🤖 Prompt for AI Agents
In src/commands/channels.ts between lines 60 and 95, remove the redundant check
for channel.type !== ChannelType.GuildText because the command option already
restricts the selection to text channels. Simply delete the entire if block that
returns an error for non-text channels to streamline the code.
| MEM0_API_KEY: z.string().min(1).startsWith('m0-'), | ||
| // AssemblyAI | ||
| DEEPGRAM_API_KEY: z.string().min(1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Fix comment to match the service name.
The comment says "AssemblyAI" but the key is for Deepgram.
- // AssemblyAI
+ // Deepgram
DEEPGRAM_API_KEY: z.string().min(1),📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| MEM0_API_KEY: z.string().min(1).startsWith('m0-'), | |
| // AssemblyAI | |
| DEEPGRAM_API_KEY: z.string().min(1), | |
| MEM0_API_KEY: z.string().min(1).startsWith('m0-'), | |
| // Deepgram | |
| DEEPGRAM_API_KEY: z.string().min(1), |
🤖 Prompt for AI Agents
In src/env.ts around lines 29 to 31, the comment above DEEPGRAM_API_KEY
incorrectly says "AssemblyAI" instead of "Deepgram". Update the comment to
correctly reflect the service name as "Deepgram" to match the API key it
describes.
| const result = await runInSandbox({ | ||
| code, | ||
| context: sharedState, | ||
| allowRequire: true, | ||
| allowedModules: ['discord.js'], | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider expanding allowed modules for sandbox execution.
The agent prompt mentions needing access to ChannelType enum and the sandbox code might require Node.js built-ins like util or stream.
const result = await runInSandbox({
code,
context: sharedState,
allowRequire: true,
- allowedModules: ['discord.js'],
+ allowedModules: ['discord.js', 'util', 'stream', 'events'],
});Additionally, consider injecting commonly used enums into the shared state:
import { ChannelType } from 'discord.js';
const sharedState: Record<string, any> = {
state: {},
last: undefined,
client,
message,
ChannelType, // Add this
};🤖 Prompt for AI Agents
In src/lib/ai/tools/discord.ts around lines 88 to 93, the sandbox execution
currently only allows the 'discord.js' module, but the code may also require
Node.js built-in modules like 'util' or 'stream'. Update the allowedModules
array to include these built-ins. Additionally, import the ChannelType enum from
'discord.js' and inject it into the sharedState object so it is accessible
within the sandbox environment.
| logger.info({ ...answer }, 'Agent completed'); | ||
|
|
||
| await status.edit({ | ||
| embeds: [ | ||
| makeEmbed({ | ||
| title: answer?.success ? 'Task Completed' : 'Task Failed', | ||
| color: answer?.success ? 0x00ff00 : 0xff0000, | ||
| fields: [ | ||
| { name: 'Answer', value: answer?.answer }, | ||
| { name: 'Reasoning', value: answer?.reasoning }, | ||
| ], | ||
| }), | ||
| ], | ||
| allowedMentions: { repliedUser: false }, | ||
| }); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling for status message updates.
If the status message edit fails, the user won't receive the final result.
logger.info({ ...answer }, 'Agent completed');
+ try {
await status.edit({
embeds: [
makeEmbed({
title: answer?.success ? 'Task Completed' : 'Task Failed',
color: answer?.success ? 0x00ff00 : 0xff0000,
fields: [
{ name: 'Answer', value: answer?.answer },
{ name: 'Reasoning', value: answer?.reasoning },
],
}),
],
allowedMentions: { repliedUser: false },
});
+ } catch (error) {
+ logger.error('Failed to update status message:', error);
+ // Attempt to send a new message if edit fails
+ await message.channel.send({
+ content: `Task ${answer?.success ? 'completed' : 'failed'}: ${answer?.answer}`,
+ });
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| logger.info({ ...answer }, 'Agent completed'); | |
| await status.edit({ | |
| embeds: [ | |
| makeEmbed({ | |
| title: answer?.success ? 'Task Completed' : 'Task Failed', | |
| color: answer?.success ? 0x00ff00 : 0xff0000, | |
| fields: [ | |
| { name: 'Answer', value: answer?.answer }, | |
| { name: 'Reasoning', value: answer?.reasoning }, | |
| ], | |
| }), | |
| ], | |
| allowedMentions: { repliedUser: false }, | |
| }); | |
| logger.info({ ...answer }, 'Agent completed'); | |
| try { | |
| await status.edit({ | |
| embeds: [ | |
| makeEmbed({ | |
| title: answer?.success ? 'Task Completed' : 'Task Failed', | |
| color: answer?.success ? 0x00ff00 : 0xff0000, | |
| fields: [ | |
| { name: 'Answer', value: answer?.answer }, | |
| { name: 'Reasoning', value: answer?.reasoning }, | |
| ], | |
| }), | |
| ], | |
| allowedMentions: { repliedUser: false }, | |
| }); | |
| } catch (error) { | |
| logger.error('Failed to update status message:', error); | |
| // Attempt to send a new message if edit fails | |
| await message.channel.send({ | |
| content: `Task ${answer?.success ? 'completed' : 'failed'}: ${answer?.answer}`, | |
| }); | |
| } |
🤖 Prompt for AI Agents
In src/lib/ai/tools/discord.ts between lines 136 and 151, the call to
status.edit lacks error handling, which risks failing silently and leaving the
user uninformed if the message update fails. Wrap the await status.edit call in
a try-catch block to catch any errors, and log or handle the error appropriately
to ensure failures are noticed and can be addressed.
| if (!connection) { | ||
| if (!interaction.member?.voice.channel) { | ||
| await interaction.followUp( | ||
| 'okay, but you\'re not in vc' | ||
| ); | ||
|
|
||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Minor: user-facing copy & early return
The follow-up message "okay, but you're not in vc" is fine for testing, but reads a bit informal for production.
Consider a clearer copy such as “You need to be in a voice channel for me to join.”
🤖 Prompt for AI Agents
In src/commands/voice-channel/join.ts around lines 24 to 32, the user-facing
message when the user is not in a voice channel is too informal. Replace the
string "okay, but you're not in vc" with a clearer and more professional message
like "You need to be in a voice channel for me to join." to improve user
experience.
| if (!(await isChannelAllowed(message))) { | ||
| logger.info(`Channel ${message.channel.id} not in allowed channels list`); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider using debug level for non-allowed channel logs.
This info-level log could be verbose in production if the bot is in many servers with restricted channels.
if (!(await isChannelAllowed(message))) {
- logger.info(`Channel ${message.channel.id} not in allowed channels list`);
+ logger.debug(`Channel ${message.channel.id} not in allowed channels list`);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!(await isChannelAllowed(message))) { | |
| logger.info(`Channel ${message.channel.id} not in allowed channels list`); | |
| return; | |
| } | |
| if (!(await isChannelAllowed(message))) { | |
| logger.debug(`Channel ${message.channel.id} not in allowed channels list`); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In src/events/message-create/index.ts around lines 47 to 50, the log message for
channels not allowed is currently at info level, which can be too verbose in
production environments with many restricted channels. Change the logger call
from info to debug level to reduce log noise while still capturing the event for
troubleshooting when debug logs are enabled.
| if (probability <= 0.5) { | ||
| logger.debug(`[${ctxId}] Low relevance — ignoring`); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Consider making the relevance threshold configurable.
The hardcoded 0.5 threshold reduces flexibility for tuning the bot's responsiveness.
Consider moving this to a configuration value:
-if (probability <= 0.5) {
+const RELEVANCE_THRESHOLD = 0.5; // Move to config
+if (probability <= RELEVANCE_THRESHOLD) {
logger.debug(`[${ctxId}] Low relevance — ignoring`);
return;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (probability <= 0.5) { | |
| logger.debug(`[${ctxId}] Low relevance — ignoring`); | |
| return; | |
| } | |
| const RELEVANCE_THRESHOLD = 0.5; // Move to config | |
| if (probability <= RELEVANCE_THRESHOLD) { | |
| logger.debug(`[${ctxId}] Low relevance — ignoring`); | |
| return; | |
| } |
🤖 Prompt for AI Agents
In src/events/message-create/index.ts around lines 93 to 96, the relevance
threshold is hardcoded as 0.5, limiting flexibility. Refactor the code to
replace the hardcoded value with a configurable parameter, such as reading the
threshold from a configuration file or environment variable. This will allow
tuning the bot's responsiveness without code changes.
| export async function execute(message: Message) { | ||
| if (message.author.bot) return; | ||
| if (!(await isChannelAllowed(message))) { | ||
| logger.info(`Channel ${message.channel.id} not in allowed channels list`); | ||
| return; | ||
| } | ||
|
|
||
| const { content, client, guild, author } = message; | ||
| const isDM = !guild; | ||
| const ctxId = isDM ? `dm:${author.id}` : guild.id; | ||
|
|
||
| logIncoming(ctxId, author.username, content); | ||
|
|
||
| if (!(await canReply(ctxId))) return; | ||
|
|
||
| const botId = client.user?.id; | ||
| const trigger = getTrigger(message, keywords, botId); | ||
|
|
||
| if (trigger.type) { | ||
| await clearUnprompted(ctxId); | ||
| logTrigger(ctxId, trigger); | ||
|
|
||
| const { messages, hints, memories } = await buildChatContext(message); | ||
| const result = await generateResponse(message, messages, hints, memories); | ||
| logReply(ctxId, author.username, result, 'explicit trigger'); | ||
| if (result.success && result.response) { | ||
| await staggeredReply(message, result.response); | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| const idleCount = await getUnprompted(ctxId); | ||
| logger.debug(`[${ctxId}] Idle counter: ${idleCount}`); | ||
|
|
||
| if (!(await hasUnpromptedQuota(ctxId))) { | ||
| logger.info(`[${ctxId}] Idle quota exhausted — staying silent`); | ||
| return; | ||
| } | ||
|
|
||
| const { messages, hints, memories } = await buildChatContext(message); | ||
| const { probability, reason } = await assessRelevance( | ||
| message, | ||
| messages, | ||
| hints, | ||
| memories, | ||
| ); | ||
| logger.info({ reason, probability }, `[${ctxId}] Relevance check`); | ||
|
|
||
| if (probability <= 0.5) { | ||
| logger.debug(`[${ctxId}] Low relevance — ignoring`); | ||
| return; | ||
| } | ||
|
|
||
| await clearUnprompted(ctxId); | ||
| logger.info(`[${ctxId}] Replying; idle counter reset`); | ||
| const result = await generateResponse(message, messages, hints, memories); | ||
| logReply(ctxId, author.username, result, 'high relevance'); | ||
| if (result.success && result.response) { | ||
| await staggeredReply(message, result.response); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for async operations.
The function performs multiple async operations without error handling, which could lead to unhandled promise rejections.
Wrap the function body in a try-catch block:
export async function execute(message: Message) {
+ try {
if (message.author.bot) return;
if (!(await isChannelAllowed(message))) {
logger.info(`Channel ${message.channel.id} not in allowed channels list`);
return;
}
// ... rest of the function
if (result.success && result.response) {
await staggeredReply(message, result.response);
}
+ } catch (error) {
+ logger.error({ error, messageId: message.id }, 'Error handling message');
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function execute(message: Message) { | |
| if (message.author.bot) return; | |
| if (!(await isChannelAllowed(message))) { | |
| logger.info(`Channel ${message.channel.id} not in allowed channels list`); | |
| return; | |
| } | |
| const { content, client, guild, author } = message; | |
| const isDM = !guild; | |
| const ctxId = isDM ? `dm:${author.id}` : guild.id; | |
| logIncoming(ctxId, author.username, content); | |
| if (!(await canReply(ctxId))) return; | |
| const botId = client.user?.id; | |
| const trigger = getTrigger(message, keywords, botId); | |
| if (trigger.type) { | |
| await clearUnprompted(ctxId); | |
| logTrigger(ctxId, trigger); | |
| const { messages, hints, memories } = await buildChatContext(message); | |
| const result = await generateResponse(message, messages, hints, memories); | |
| logReply(ctxId, author.username, result, 'explicit trigger'); | |
| if (result.success && result.response) { | |
| await staggeredReply(message, result.response); | |
| } | |
| return; | |
| } | |
| const idleCount = await getUnprompted(ctxId); | |
| logger.debug(`[${ctxId}] Idle counter: ${idleCount}`); | |
| if (!(await hasUnpromptedQuota(ctxId))) { | |
| logger.info(`[${ctxId}] Idle quota exhausted — staying silent`); | |
| return; | |
| } | |
| const { messages, hints, memories } = await buildChatContext(message); | |
| const { probability, reason } = await assessRelevance( | |
| message, | |
| messages, | |
| hints, | |
| memories, | |
| ); | |
| logger.info({ reason, probability }, `[${ctxId}] Relevance check`); | |
| if (probability <= 0.5) { | |
| logger.debug(`[${ctxId}] Low relevance — ignoring`); | |
| return; | |
| } | |
| await clearUnprompted(ctxId); | |
| logger.info(`[${ctxId}] Replying; idle counter reset`); | |
| const result = await generateResponse(message, messages, hints, memories); | |
| logReply(ctxId, author.username, result, 'high relevance'); | |
| if (result.success && result.response) { | |
| await staggeredReply(message, result.response); | |
| } | |
| } | |
| export async function execute(message: Message) { | |
| try { | |
| if (message.author.bot) return; | |
| if (!(await isChannelAllowed(message))) { | |
| logger.info(`Channel ${message.channel.id} not in allowed channels list`); | |
| return; | |
| } | |
| const { content, client, guild, author } = message; | |
| const isDM = !guild; | |
| const ctxId = isDM ? `dm:${author.id}` : guild.id; | |
| logIncoming(ctxId, author.username, content); | |
| if (!(await canReply(ctxId))) return; | |
| const botId = client.user?.id; | |
| const trigger = getTrigger(message, keywords, botId); | |
| if (trigger.type) { | |
| await clearUnprompted(ctxId); | |
| logTrigger(ctxId, trigger); | |
| const { messages, hints, memories } = await buildChatContext(message); | |
| const result = await generateResponse(message, messages, hints, memories); | |
| logReply(ctxId, author.username, result, 'explicit trigger'); | |
| if (result.success && result.response) { | |
| await staggeredReply(message, result.response); | |
| } | |
| return; | |
| } | |
| const idleCount = await getUnprompted(ctxId); | |
| logger.debug(`[${ctxId}] Idle counter: ${idleCount}`); | |
| if (!(await hasUnpromptedQuota(ctxId))) { | |
| logger.info(`[${ctxId}] Idle quota exhausted — staying silent`); | |
| return; | |
| } | |
| const { messages, hints, memories } = await buildChatContext(message); | |
| const { probability, reason } = await assessRelevance( | |
| message, | |
| messages, | |
| hints, | |
| memories, | |
| ); | |
| logger.info({ reason, probability }, `[${ctxId}] Relevance check`); | |
| if (probability <= 0.5) { | |
| logger.debug(`[${ctxId}] Low relevance — ignoring`); | |
| return; | |
| } | |
| await clearUnprompted(ctxId); | |
| logger.info(`[${ctxId}] Replying; idle counter reset`); | |
| const result = await generateResponse(message, messages, hints, memories); | |
| logReply(ctxId, author.username, result, 'high relevance'); | |
| if (result.success && result.response) { | |
| await staggeredReply(message, result.response); | |
| } | |
| } catch (error) { | |
| logger.error({ error, messageId: message.id }, 'Error handling message'); | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/events/message-create/index.ts from lines 45 to 105, the execute function
performs many asynchronous operations without error handling, risking unhandled
promise rejections. Wrap the entire function body in a try-catch block to catch
any errors thrown during async calls. In the catch block, log the error
appropriately to aid debugging and prevent the function from failing silently.
| export const systemPrompt = ({ | ||
| selectedChatModel, | ||
| requestHints, | ||
| memories, | ||
| }: { | ||
| selectedChatModel: string; | ||
| requestHints: RequestHints; | ||
| memories: string; | ||
| }) => { | ||
| const requestPrompt = getRequestPromptFromHints(requestHints); | ||
|
|
||
| if (selectedChatModel === 'chat-model') { | ||
| return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`; | ||
| } else if (selectedChatModel === 'relevance-model') { | ||
| return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handle unmatched model types.
The function implicitly returns undefined when selectedChatModel doesn't match either case, which could cause runtime errors.
Add a default case or throw an error:
export const systemPrompt = ({
selectedChatModel,
requestHints,
memories,
}: {
selectedChatModel: string;
requestHints: RequestHints;
memories: string;
}) => {
const requestPrompt = getRequestPromptFromHints(requestHints);
if (selectedChatModel === 'chat-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
} else if (selectedChatModel === 'relevance-model') {
return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`;
+ } else {
+ throw new Error(`Unknown chat model: ${selectedChatModel}`);
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const systemPrompt = ({ | |
| selectedChatModel, | |
| requestHints, | |
| memories, | |
| }: { | |
| selectedChatModel: string; | |
| requestHints: RequestHints; | |
| memories: string; | |
| }) => { | |
| const requestPrompt = getRequestPromptFromHints(requestHints); | |
| if (selectedChatModel === 'chat-model') { | |
| return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`; | |
| } else if (selectedChatModel === 'relevance-model') { | |
| return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`; | |
| } | |
| }; | |
| export const systemPrompt = ({ | |
| selectedChatModel, | |
| requestHints, | |
| memories, | |
| }: { | |
| selectedChatModel: string; | |
| requestHints: RequestHints; | |
| memories: string; | |
| }) => { | |
| const requestPrompt = getRequestPromptFromHints(requestHints); | |
| if (selectedChatModel === 'chat-model') { | |
| return `${regularPrompt}\n\n${requestPrompt}\n\n${toolsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`; | |
| } else if (selectedChatModel === 'relevance-model') { | |
| return `${regularPrompt}\n\n${requestPrompt}\n\n${artifactsPrompt}\n\n<CONTEXT>${memories}</CONTEXT>`; | |
| } else { | |
| throw new Error(`Unknown chat model: ${selectedChatModel}`); | |
| } | |
| }; |
🤖 Prompt for AI Agents
In src/lib/ai/prompts.ts around lines 160 to 176, the systemPrompt function does
not handle cases where selectedChatModel is neither 'chat-model' nor
'relevance-model', causing it to implicitly return undefined. Add a default case
at the end of the function to either throw a descriptive error or return a
fallback prompt string to ensure the function always returns a valid string and
avoid runtime errors.
| export const getRequestPromptFromHints = (requestHints: RequestHints) => `\ | ||
| You live in ${requestHints.city}, ${requestHints.country}. | ||
| In ${requestHints.city} and the date and time is ${requestHints.time}. | ||
| You're in the ${requestHints.server} Discord Server, and in the ${ | ||
| requestHints.channel | ||
| } channel. | ||
| You joined the server on ${new Date( | ||
| requestHints.joined, | ||
| ).toLocaleDateString()}. | ||
| Your current status is ${requestHints.status} and your activity is ${ | ||
| requestHints.activity | ||
| }. | ||
| `; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Remove trailing spaces.
Line 25 has trailing spaces after the period.
Your current status is ${requestHints.status} and your activity is ${
requestHints.activity
-}.
+}.
`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const getRequestPromptFromHints = (requestHints: RequestHints) => `\ | |
| You live in ${requestHints.city}, ${requestHints.country}. | |
| In ${requestHints.city} and the date and time is ${requestHints.time}. | |
| You're in the ${requestHints.server} Discord Server, and in the ${ | |
| requestHints.channel | |
| } channel. | |
| You joined the server on ${new Date( | |
| requestHints.joined, | |
| ).toLocaleDateString()}. | |
| Your current status is ${requestHints.status} and your activity is ${ | |
| requestHints.activity | |
| }. | |
| `; | |
| export const getRequestPromptFromHints = (requestHints: RequestHints) => `\ | |
| You live in ${requestHints.city}, ${requestHints.country}. | |
| In ${requestHints.city} and the date and time is ${requestHints.time}. | |
| You're in the ${requestHints.server} Discord Server, and in the ${ | |
| requestHints.channel | |
| } channel. | |
| You joined the server on ${new Date( | |
| requestHints.joined, | |
| ).toLocaleDateString()}. | |
| Your current status is ${requestHints.status} and your activity is ${ | |
| requestHints.activity | |
| }. | |
| `; |
🤖 Prompt for AI Agents
In src/lib/ai/prompts.ts around lines 14 to 26, remove the trailing spaces after
the period on line 25 in the template string to clean up the formatting and
avoid unnecessary whitespace.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (4)
TODO.md (1)
6-16: 🧹 Nitpick (assertive)Improve wording, fix typos & apply Markdown hygiene
LanguageTool / markdown-lint are already complaining. A quick clean-up keeps the roadmap readable and CI happy.
-Agent Isolation for each server, role based access control -mention that u need to install rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -Put commands like join and leave as a subcommand under VC, make an easierway to have subcommands rather clubbing in one file -Stream back audio from ElevenLabs instead of non streaming -Switch to AI SDK Voice leater -seperate deepgram code into its seperate files -probably switch to 11 labs -Implement memory for the ai voic chat +Agent isolation for each server – role-based access control +Mention that **you** need to install Rust: +`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` +Move `join` / `leave` under a top-level `vc` command; provide a helper for sub-commands instead of stuffing everything into one file +Stream back audio from ElevenLabs instead of non-streaming +Switch to AI-SDK voice *later* +Separate Deepgram code into its own files +Consider switching to ElevenLabs +Implement memory for the AI voice chat Add commit lint🧰 Tools
🪛 LanguageTool
[uncategorized] ~6-~6: This expression is usually spelled with a hyphen.
Context: ...true? Agent Isolation for each server, role based access control mention that u need to i...(BASED_HYPHEN)
[misspelling] ~7-~7: Did you mean “you”?
Context: ... role based access control mention that u need to install rust curl --proto '=htt...(U_TO_YOU)
[grammar] ~10-~10: This expression is usually spelled with a hyphen.
Context: ...m back audio from ElevenLabs instead of non streaming Switch to AI SDK Voice leater seperate...(NON_ANTI_JJ)
🪛 markdownlint-cli2 (0.17.2)
7-7: Bare URL used
null(MD034, no-bare-urls)
src/commands/voice-channel/leave.ts (1)
1-9: Remove unused imports to satisfynoUnusedLocals
Same point raised in the previous review – they’re still unused.-import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { - entersState, - getVoiceConnection, - joinVoiceChannel, - VoiceConnectionStatus, -} from '@discordjs/voice'; +import { SlashCommandBuilder } from 'discord.js'; +import { getVoiceConnection } from '@discordjs/voice'; -import type { ChatInputCommandInteraction, Snowflake } from 'discord.js'; +import type { ChatInputCommandInteraction } from 'discord.js';src/commands/voice-channel/join.ts (2)
25-28: Previous feedback on user-facing copy still appliesThe message is still
"okay, but you're not in vc".
Please replace with a more neutral sentence as suggested earlier.
47-50: Duplicatespeakinglisteners & players → memory leakRunning
/vc jointwice registers an additionalspeakinglistener andAudioPlayer.
Guard with a per-guild map orlistenerCount('start')before adding.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (3)
TODO.md(1 hunks)src/commands/voice-channel/join.ts(1 hunks)src/commands/voice-channel/leave.ts(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/commands/voice-channel/leave.ts (2)
src/commands/voice-channel/join.ts (2)
data(13-15)execute(17-60)src/commands/voice-channel/index.ts (2)
data(5-15)execute(17-33)
🪛 LanguageTool
TODO.md
[uncategorized] ~6-~6: This expression is usually spelled with a hyphen.
Context: ...true? Agent Isolation for each server, role based access control mention that u need to i...
(BASED_HYPHEN)
[misspelling] ~7-~7: Did you mean “you”?
Context: ... role based access control mention that u need to install rust curl --proto '=htt...
(U_TO_YOU)
[grammar] ~10-~10: This expression is usually spelled with a hyphen.
Context: ...m back audio from ElevenLabs instead of non streaming Switch to AI SDK Voice leater seperate...
(NON_ANTI_JJ)
🪛 markdownlint-cli2 (0.17.2)
TODO.md
7-7: Bare URL used
null
(MD034, no-bare-urls)
| await interaction.reply({ | ||
| content: "wdym? i'm not in a voice channel", | ||
| ephemeral: true, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick (assertive)
Tone down informal copy
"wdym? i'm not in a voice channel" is fun, but may not fit a production bot’s voice.
Consider something clearer such as:
- content: "wdym? i'm not in a voice channel",
+ content: "I'm not currently connected to any voice channel.",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await interaction.reply({ | |
| content: "wdym? i'm not in a voice channel", | |
| ephemeral: true, | |
| }); | |
| await interaction.reply({ | |
| content: "I'm not currently connected to any voice channel.", | |
| ephemeral: true, | |
| }); |
🤖 Prompt for AI Agents
In src/commands/voice-channel/leave.ts around lines 20 to 23, the reply message
uses informal language "wdym? i'm not in a voice channel" which is not suitable
for a production environment. Replace this string with a clearer, more
professional message such as "You are not currently in a voice channel." to
maintain a consistent and appropriate tone.
| import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; | ||
| import { | ||
| createAudioPlayer, | ||
| entersState, | ||
| getVoiceConnection, | ||
| joinVoiceChannel, | ||
| VoiceConnectionStatus, | ||
| } from '@discordjs/voice'; | ||
| import type { ChatInputCommandInteraction, Snowflake } from 'discord.js'; | ||
| import { createListeningStream } from '@/utils/voice/stream'; | ||
| import { playAudio } from '@/utils/voice/helpers'; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused symbols (CommandInteraction, Snowflake, playAudio) break noUnusedLocals
-import { CommandInteraction, SlashCommandBuilder } from 'discord.js';
+import { SlashCommandBuilder } from 'discord.js';
@@
-import type { ChatInputCommandInteraction, Snowflake } from 'discord.js';
+import type { ChatInputCommandInteraction } from 'discord.js';
-import { playAudio } from '@/utils/voice/helpers';📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; | |
| import { | |
| createAudioPlayer, | |
| entersState, | |
| getVoiceConnection, | |
| joinVoiceChannel, | |
| VoiceConnectionStatus, | |
| } from '@discordjs/voice'; | |
| import type { ChatInputCommandInteraction, Snowflake } from 'discord.js'; | |
| import { createListeningStream } from '@/utils/voice/stream'; | |
| import { playAudio } from '@/utils/voice/helpers'; | |
| import { SlashCommandBuilder } from 'discord.js'; | |
| import { | |
| createAudioPlayer, | |
| entersState, | |
| getVoiceConnection, | |
| joinVoiceChannel, | |
| VoiceConnectionStatus, | |
| } from '@discordjs/voice'; | |
| import type { ChatInputCommandInteraction } from 'discord.js'; | |
| import { createListeningStream } from '@/utils/voice/stream'; |
🤖 Prompt for AI Agents
In src/commands/voice-channel/join.ts lines 1 to 12, the imports
CommandInteraction, Snowflake, and playAudio are not used anywhere in the code,
causing noUnusedLocals errors. Remove these unused imports from the import
statements to fix the issue.
Summary by CodeRabbit
New Features
Improvements
Chores
Documentation