Conversation
3fd2bcf to
9ed1bed
Compare
Implement the full Discord bot following the Vercel chat architecture pattern: - Gateway WebSocket listener (/api/discord/gateway) connects to Discord via discord.js, receives MESSAGE_CREATE events, and forwards them to the webhook handler for unified processing - Webhook handler (/discord/webhook) processes both Discord HTTP Interactions (PING, slash commands) and forwarded Gateway events - Bot processor reuses the platform-agnostic runBot() engine with spawn_cloud_agent tool support, conversation context, and GitHub repository context - All bot API calls use the DISCORD_BOT_TOKEN (Bot auth), not OAuth tokens - Message posting, reactions, and channel context fetching via Discord REST API v10 New dependencies: discord.js, discord-interactions, discord-api-types New env var: DISCORD_PUBLIC_KEY (Ed25519 verification for HTTP Interactions)
discord.js depends on optional native modules (zlib-sync, bufferutil, utf-8-validate) that webpack cannot bundle. Add them to serverExternalPackages so Next.js resolves them at runtime via Node.js instead of trying to bundle them.
9ed1bed to
99ed1e1
Compare
- Capture bot user ID from discord.js client on ready event - Include botUserId in forwarded Gateway events - Filter messages in webhook to only process those mentioning the bot - Use correct botUserId for stripping bot mention from message Previously the code incorrectly assumed the first non-author mention was the bot, which failed when users mentioned others before the bot.
Each heartbeat iteration added an abort listener that was never removed when the timer resolved normally. Over a 10-minute run this accumulated ~60 stale listeners on the AbortSignal. Use a shared done() callback that both the timer and abort paths call, which clears the timer and removes the event listener.
When runGatewayListener rejected (e.g. login failure), the catch block returned an error response without aborting the heartbeat or awaiting its promise, leaving background DB polling alive. Move abort+await into a finally block so cleanup always runs.
The webhook handler defers long-running bot processing via after(), which needs the same 800s function timeout as the other bot routes. Without this export, Vercel applies a shorter default limit that can cut off background work mid-processing.
The Ed25519 signature check validates integrity but not age — a captured valid request could be replayed later. Add a 5-minute staleness window (matching the Slack verifier) that rejects requests with timestamps too far in the past or future before calling verifyKey.
Move claimActiveListener + heartbeat start into the ClientReady callback so a failed Discord login never evicts the previous healthy listener from the active-listener DB slot. Also gate event forwarding on isLeader to avoid forwarding before ownership is confirmed.
…client The Discord bot was using the old createCloudAgentClient (SSE-based) which connects to an endpoint that is no longer available, causing ECONNREFUSED. Migrate to createCloudAgentNextClient + runSessionToCompletion (WebSocket-based) to match the working Slack bot implementation.
Discord was hardcoding 'anthropic/claude-sonnet-4' while Slack properly resolved the configured model from integration metadata. Add model_slug storage, getModel/updateModel to discord-service, a TRPC updateModel procedure, and a model selector to the Discord settings UI.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Retry the processing emoji removal if the initial parallel attempt fails due to Discord API rate limiting.
Code Review SummaryStatus: 12 Issues Found | Recommendation: Address before merge Fix these issues in Kilo Cloud Overview
Issue Details (click to expand)WARNING
SUGGESTION
Other Observations (not in diff)Issues found in unchanged code or general patterns that cannot receive inline comments:
Files Reviewed (24 files)
|
Co-authored-by: kilo-code-bot[bot] <240665456+kilo-code-bot[bot]@users.noreply.github.com>
| return new NextResponse('Invalid signature', { status: 401 }); | ||
| } | ||
|
|
||
| const body = JSON.parse(rawBody); |
There was a problem hiding this comment.
[WARNING]: Unhandled JSON.parse — this can throw on malformed request bodies from Discord HTTP Interactions, crashing the handler with a 500.
The gateway path at line 63 has the same issue (already flagged), but this is a separate code path for Discord's HTTP Interaction endpoint. A malformed body here would bypass the signature verification that already passed and crash before the PING/command handling.
Wrap in try-catch or use a safe parse utility.
| * Discord Gateway listener with Postgres-based coordination. | ||
| * | ||
| * Architecture: | ||
| * - This route is triggered by a cron job (e.g., every 3 minutes) |
There was a problem hiding this comment.
[SUGGESTION]: Comment says "every 3 minutes" but the actual cron schedule in vercel.json is */9 * * * * (every 9 minutes).
| * - This route is triggered by a cron job (e.g., every 3 minutes) | |
| * - This route is triggered by a cron job (every 9 minutes) |
| /** | ||
| * Get the bot's own user ID by calling /users/@me with the bot token. | ||
| */ | ||
| export async function getDiscordBotUserId(): Promise<string | null> { |
There was a problem hiding this comment.
[SUGGESTION]: getDiscordBotUserId is exported but never referenced anywhere in the codebase. If it's intended for future use, consider removing it until needed to avoid dead code.
|
|
||
| if (existing) { | ||
| // Preserve existing model_slug when re-authorizing | ||
| const existingMetadata = (existing.metadata || {}) as Record<string, unknown>; |
There was a problem hiding this comment.
[WARNING]: as Record<string, unknown> cast bypasses type safety (per project coding style: "STRONGLY AVOID typescript's as operator"). This pattern is repeated at lines 191, 341, and 380 in this file, plus line 32 in discord-router.ts.
Consider defining a typed metadata schema (e.g. with zod) and parsing it, or using satisfies to validate the shape. This would catch issues if the metadata shape changes unexpectedly.
This PR adds a Kilo for Discord implementation, heavily based on Kilo for Slack.
Instead of abstracting shared code paths, I've opted to duplicate a bunch of logic. The main reasoning here is that I want to move as fast as possible, and I'm currently reviewing switching the whole infrastructure over to using the vercel/chat SDK anyway, so I don't want to waste time abstracting code that I'm going to remove anyway.
I've tested the bot locally to make sure that it can do everything that the Slack bot can do:
Review Plan
Honestly, I wouldn't expect you to read through all the code. I mainly made sure that the code is on-par with the Slack implementation, so if Kilo Code Reviewer agrees with that I think that's great 😄