-
Notifications
You must be signed in to change notification settings - Fork 0
Cursor agent to fix upgrade PRs #480
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
Open
pgte
wants to merge
5
commits into
main
Choose a base branch
from
feat/cursor-agent-to-fix-pr
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,514
−0
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
apps/backend/src/http/post-api-discord_messages/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| # Discord PR Fix Agent | ||
|
|
||
| This service monitors Discord for failed CI workflow notifications, identifies Renovate PR failures, and triggers Cursor's Background Agents API to automatically fix errors. | ||
|
|
||
| ## Overview | ||
|
|
||
| The service listens to Discord messages about failed CI workflows. When it detects a failed PR from Renovate bot, it triggers a Cursor Background Agent to: | ||
|
|
||
| 1. Send a Discord notification that it's starting | ||
| 2. Fix TypeScript, linting, test, and build errors | ||
| 3. Commit and push fixes to the PR branch | ||
| 4. Send a Discord notification when finished | ||
|
|
||
| ## Architecture | ||
|
|
||
| - **Fire-and-Forget Pattern**: The Lambda function triggers the Cursor agent and returns immediately. It does NOT wait for the agent to complete. | ||
| - **Agent Autonomy**: The Cursor agent runs independently and handles all fixes and notifications. | ||
|
|
||
| ## Endpoint | ||
|
|
||
| - `POST /api/discord-messages` - Receives Discord message events | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| Required environment variables: | ||
|
|
||
| - `DISCORD_PUBLIC_KEY` - Discord application public key for signature verification (reuse existing) | ||
| - `DISCORD_WEBHOOK_URL` - Discord webhook URL for agent notifications (passed to Cursor agent) | ||
| - `GITHUB_TOKEN` - Personal access token with repo read access (for fetching PR details) | ||
| - `CURSOR_API_KEY` - API key from Cursor Dashboard for Background Agents API | ||
| - `CURSOR_API_URL` - (Optional) Cursor API base URL (default: `https://api.cursor.com/v1/background-agents`) | ||
|
|
||
| ## How It Works | ||
|
|
||
| 1. **Discord Message Received**: Service receives a Discord message about a failed CI workflow | ||
| 2. **Message Parsing**: Extracts PR number, branch name, repository, and workflow status from Discord embed | ||
| 3. **Validation**: Checks if: | ||
| - Workflow status is "FAILED" | ||
| - PR is from Renovate bot | ||
| 4. **Agent Trigger**: Calls Cursor Background Agents API with: | ||
| - Task description including Discord webhook URL | ||
| - Instructions to send start/finish notifications | ||
| - PR details | ||
| 5. **Immediate Return**: Lambda returns immediately after triggering agent (fire-and-forget) | ||
| 6. **Agent Execution**: Cursor agent: | ||
| - Sends Discord notification: "🤖 Starting to fix PR #X..." | ||
| - Checks out PR branch | ||
| - Runs diagnostics (typecheck, lint, test, test:e2e, build) | ||
| - Fixes errors | ||
| - Commits and pushes fixes | ||
| - Sends Discord notification: "✅ Finished fixing PR #X" or "❌ Failed..." | ||
|
|
||
| ## Message Format | ||
|
|
||
| The service expects Discord messages with embeds containing: | ||
|
|
||
| - **PR/Event field**: Contains PR number (e.g., "🔗 **PR #123**: [Title](URL)") | ||
| - **Branch field**: Contains branch name | ||
| - **Status**: Embed title or fields indicating "FAILED" status | ||
| - **Repository field**: Contains repository (e.g., "owner/repo") | ||
|
|
||
| ## Discord Webhooks (How Cursor Agent Sends Notifications) | ||
|
|
||
| **Important**: The Cursor agent uses Discord **webhooks** (not the Discord API) to send notifications. | ||
|
|
||
| ### Key Points: | ||
| - **No signatures required**: Discord webhooks are simple HTTP POST endpoints | ||
| - **No authentication needed**: The webhook URL itself is the only credential | ||
| - **Simple HTTP requests**: The agent can use `curl`, `fetch`, or any HTTP client | ||
|
|
||
| ### How It Works: | ||
| The Cursor agent receives the webhook URL in the task description and makes simple POST requests: | ||
|
|
||
| **Using curl**: | ||
| ```bash | ||
| curl -X POST "https://discord.com/api/webhooks/YOUR_WEBHOOK_URL" \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"content": "🤖 Starting to fix PR #123..."}' | ||
| ``` | ||
|
|
||
| **Using Node.js fetch**: | ||
| ```javascript | ||
| await fetch("https://discord.com/api/webhooks/YOUR_WEBHOOK_URL", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ content: "🤖 Starting to fix PR #123..." }) | ||
| }); | ||
| ``` | ||
|
|
||
| **Note**: This is different from receiving messages (which requires signature verification). Sending messages via webhooks is much simpler - no special tools or authentication needed. | ||
|
|
||
| ## Error Handling | ||
|
|
||
| - Invalid messages are logged and ignored | ||
| - Non-Renovate PRs are skipped | ||
| - Non-failed workflows are skipped | ||
| - API errors are logged and returned to caller | ||
| - Cursor agent handles its own errors and notifies Discord | ||
|
|
||
| ## Limitations | ||
|
|
||
| - Lambda execution time: Not a concern (returns immediately) | ||
| - Cursor API rate limits: May need to handle rate limiting | ||
| - Agent execution time: Not a concern (runs asynchronously) | ||
| - Discord webhook rate limits: Agent must respect Discord rate limits | ||
|
|
||
| ## Setup | ||
|
|
||
| See `docs/deployment/discord-pr-fix-agent-setup.md` for detailed setup instructions. | ||
|
|
||
| ## Testing | ||
|
|
||
| To test the endpoint: | ||
|
|
||
| ```bash | ||
| curl -X POST https://your-api.com/api/discord-messages \ | ||
| -H "Content-Type: application/json" \ | ||
| -H "x-signature-ed25519: <signature>" \ | ||
| -H "x-signature-timestamp: <timestamp>" \ | ||
| -d '{ | ||
| "embeds": [{ | ||
| "title": "❌ Test Workflow - FAILED", | ||
| "fields": [ | ||
| {"name": "PR/Event", "value": "🔗 **PR #123**: [Test PR](https://github.com/owner/repo/pull/123)"}, | ||
| {"name": "Branch", "value": "renovate/test-package"}, | ||
| {"name": "Repository", "value": "owner/repo"} | ||
| ] | ||
| }] | ||
| }' | ||
| ``` | ||
|
|
||
| ## Related Documentation | ||
|
|
||
| - [Discord Setup Guide](../../../../docs/deployment/discord-pr-fix-agent-setup.md) | ||
| - [Discord Notifications](../../../../docs/deployment/discord-notifications.md) |
174 changes: 174 additions & 0 deletions
174
apps/backend/src/http/post-api-discord_messages/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| import { APIGatewayProxyEventV2, APIGatewayProxyResult } from "aws-lambda"; | ||
|
|
||
| import { handlingErrors } from "../../utils/handlingErrors"; | ||
| import { verifyDiscordSignature } from "../any-api-discord/services/discordService"; | ||
|
|
||
| import { parseRepository } from "./services/githubService"; | ||
| import { parseDiscordMessage } from "./services/messageParser"; | ||
| import { triggerPRFix } from "./services/prFixService"; | ||
|
|
||
| /** | ||
| * Discord Message Handler | ||
| * Receives Discord messages about failed CI workflows | ||
| * Triggers Cursor agent to fix Renovate PR failures | ||
| */ | ||
| export const handler = handlingErrors( | ||
| async (event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResult> => { | ||
| // Only handle POST requests | ||
| if (event.requestContext.http.method !== "POST") { | ||
| return { | ||
| statusCode: 405, | ||
| body: JSON.stringify({ error: "Method not allowed" }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Verify Discord webhook signature (required for security) | ||
| const signature = event.headers["x-signature-ed25519"]; | ||
| if (!signature || !verifyDiscordSignature(event)) { | ||
| console.warn("Discord signature verification failed or missing signature"); | ||
| return { | ||
| statusCode: 401, | ||
| body: JSON.stringify({ | ||
| error: "Invalid or missing Discord signature", | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Parse Discord message payload | ||
| let body; | ||
| try { | ||
| body = JSON.parse(event.body || "{}"); | ||
| } catch (error) { | ||
| console.error("Error parsing Discord message payload:", error); | ||
| return { | ||
| statusCode: 400, | ||
| body: JSON.stringify({ | ||
| error: "Invalid JSON payload", | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Handle Discord interaction types (for Gateway bots) | ||
| if (body.type === 1) { | ||
| // PING - Discord verification | ||
| return { | ||
| statusCode: 200, | ||
| body: JSON.stringify({ type: 1 }), // PONG | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Parse Discord message to extract PR information | ||
| const parsedInfo = parseDiscordMessage(body); | ||
|
|
||
| // Log parsed information for debugging | ||
| console.log("Parsed Discord message:", { | ||
| prNumber: parsedInfo.prNumber, | ||
| branchName: parsedInfo.branchName, | ||
| workflowStatus: parsedInfo.workflowStatus, | ||
| repository: parsedInfo.repository, | ||
| isValid: parsedInfo.isValid, | ||
| }); | ||
|
|
||
| // Only process if we have valid PR info and it's a failed workflow | ||
| if (!parsedInfo.isValid) { | ||
| console.log("Discord message does not contain valid PR failure information"); | ||
| return { | ||
| statusCode: 200, | ||
| body: JSON.stringify({ | ||
| message: "Message processed but no action taken (not a failed PR)", | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Validate repository format | ||
| if (!parsedInfo.repository) { | ||
| console.error("Repository information missing from Discord message"); | ||
| return { | ||
| statusCode: 400, | ||
| body: JSON.stringify({ | ||
| error: "Repository information missing", | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| const repoInfo = parseRepository(parsedInfo.repository); | ||
| if (!repoInfo) { | ||
| console.error(`Invalid repository format: ${parsedInfo.repository}`); | ||
| return { | ||
| statusCode: 400, | ||
| body: JSON.stringify({ | ||
| error: `Invalid repository format: ${parsedInfo.repository}`, | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
|
|
||
| // Trigger Cursor agent to fix the PR (fire-and-forget) | ||
| try { | ||
| const result = await triggerPRFix({ | ||
| prNumber: parsedInfo.prNumber!, | ||
| branchName: parsedInfo.branchName || "unknown", | ||
| repository: parsedInfo.repository, | ||
| }); | ||
|
|
||
| if (result.success) { | ||
| console.log( | ||
| `Successfully triggered Cursor agent for PR #${parsedInfo.prNumber}` | ||
| ); | ||
| return { | ||
| statusCode: 200, | ||
| body: JSON.stringify({ | ||
| message: result.message, | ||
| agentId: result.agentId, | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } else { | ||
| console.error(`Failed to trigger Cursor agent: ${result.message}`); | ||
| return { | ||
| statusCode: 500, | ||
| body: JSON.stringify({ | ||
| error: result.message, | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
| } catch (error) { | ||
| console.error("Error triggering PR fix:", error); | ||
| return { | ||
| statusCode: 500, | ||
| body: JSON.stringify({ | ||
| error: | ||
| error instanceof Error ? error.message : "Unknown error occurred", | ||
| }), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
| ); | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.