### Install and Run Daemon - Bash Source: https://context7.com/hinha/agent-pr/llms.txt Provides instructions for setting up and running the PR monitor daemon. This includes installing Node.js dependencies, configuring environment variables, starting the daemon in the background using `nohup`, and managing its process ID for graceful stopping. ```bash # Install dependencies npm install # Configure environment cp .env.example .env nano .env # Fill in required values # Start daemon with nohup for background execution nohup node index.js > logs/daemon.out 2>&1 < /dev/null & echo $! > daemon.pid # Stop the daemon kill $(cat daemon.pid) ``` -------------------------------- ### Environment Configuration Example Source: https://context7.com/hinha/agent-pr/llms.txt An example of the .env file used to configure the MCP-based PR Monitor Daemon. It includes settings for Telegram integration, MCP and OpenClaw agent paths, GitHub repository details, and operational parameters. ```javascript // .env configuration example TELEGRAM_BOT_TOKEN=your_telegram_bot_token_from_botfather TELEGRAM_CHAT_ID=0000000001 MCP_CONFIG_PATH=/home/ubuntu/.mcporter/mcporter.json OPENCLAW_AGENT_SUMMARY=pr-summary-agent OPENCLAW_AGENT_REVIEW=pr-review-agent OPENCLAW_REVIEW_MODEL=openai/claude-sonnet-4.6 MCP_SERVER_NAME=github-work GITHUB_OWNER=your-org GITHUB_REPO=your-repo CHECK_INTERVAL_MINUTES=30 SKIP_CACHE_DURATION_HOURS=3 LOG_LEVEL=info ``` -------------------------------- ### Manage Processed PR State - JavaScript Source: https://context7.com/hinha/agent-pr/llms.txt Manages the state of processed Pull Requests (PRs) to prevent duplicate notifications. It provides methods to check if a PR has been processed, get its notification count, increment the count, and mark a PR as fully processed, persisting this state. ```javascript // src/services/prStateManager.js // Check if PR has been fully processed async isProcessed(prId) { return this.processedPRs.has(prId.toString()); } // Get current notification count for a PR getNotificationCount(prId) { return this.notificationCount.get(prId.toString()) || 0; } // Increment notification counter (max 3 notifications per PR) async incrementNotificationCount(prId) { const prIdStr = prId.toString(); const current = this.getNotificationCount(prId); const newCount = current + 1; this.notificationCount.set(prIdStr, newCount); await this.saveNotificationCounts(); return newCount; } // Mark PR as fully processed async markProcessed(prId) { const prIdStr = prId.toString(); if (!this.processedPRs.has(prIdStr)) { this.processedPRs.add(prIdStr); await this.saveProcessedPRs(); logger.info(`Marked PR #${prId} as fully processed`); } } ``` -------------------------------- ### Manage Telegram Notifications and Interactive Actions Source: https://context7.com/hinha/agent-pr/llms.txt Handles Telegram bot initialization, sends formatted PR notifications with AI-generated summaries, and processes interactive inline button callbacks to perform GitHub PR operations like approval, rejection, or closure. ```javascript constructor() { this.bot = new TelegramBot(config.telegram.botToken, { polling: true }); this.chatId = config.telegram.chatId; this.threadId = config.telegram.threadId; this.setupButtonHandlers(); } async sendPRNotification(pr, summary) { return this.retryOperation(async () => { const message = `πŸ”” NEW PR DETECTED\nPR #${pr.number}: ${pr.title}\nπŸ‘€ Author: ${pr.author}\nπŸ”— URL: ${pr.url}\n\n---\nAI PR Summary\nπŸ“ Purpose: ${summary.purpose}\n🏷️ Type: ${summary.type}\n⚠️ Risk Level: ${summary.riskLevel}\n🎯 Impact Area: ${summary.impactArea}\nπŸ“Š Diff Size: ${summary.diffSize}\n🚨 Suspicious Patterns: ${summary.suspiciousPatterns?.length ? summary.suspiciousPatterns.join(', ') : 'None detected'}\nβœ… Recommended Review: ${summary.recommendedReview}`; const inlineKeyboard = { inline_keyboard: [ [{ text: 'πŸ” Review Now', callback_data: `review_now:${pr.id}` }, { text: 'πŸ”— Visit PR', callback_data: `visit:${pr.id}` }], [{ text: 'βœ… Approve', callback_data: `approve:${pr.id}` }, { text: '❌ Reject', callback_data: `reject:${pr.id}` }], [{ text: 'πŸ”’ Close PR', callback_data: `close:${pr.id}` }, { text: '⏸️ Skip (3h)', callback_data: `skip:${pr.id}` }] ] }; await this.bot.sendMessage(this.chatId, message, { reply_markup: inlineKeyboard, disable_web_page_preview: true, message_thread_id: this.threadId, parse_mode: 'HTML' }); }, config.retries.telegramRetries, 3000, config.retries.backoffFactor); } this.bot.on('callback_query', async (query) => { const [action, prId] = query.data.split(':'); if (action === 'approve') { await mcpService.callMCP('create_pull_request_review', { owner: config.github.owner, repo: config.github.repo, pull_number: pr.number, event: 'APPROVE', body: 'Approved via OpenClaw PR Monitor' }); await prStateManager.markProcessed(pr.id); } else if (action === 'reject') { await mcpService.callMCP('create_pull_request_review', { owner: config.github.owner, repo: config.github.repo, pull_number: pr.number, event: 'REQUEST_CHANGES', body: 'Changes requested via OpenClaw PR Monitor.' }); } else if (action === 'close') { await mcpService.callMCP('update_pull_request', { owner: config.github.owner, repo: config.github.repo, pull_number: pr.number, state: 'closed' }); } else if (action === 'skip') { await skipManager.addSkip(parseInt(prId)); } }); ``` -------------------------------- ### Invoke AI PR Analysis with OpenClawAgentService Source: https://context7.com/hinha/agent-pr/llms.txt Provides methods to generate structured PR summaries and perform deep code reviews by invoking OpenClaw CLI agents. These methods utilize retry logic to ensure robustness against transient network or execution failures. ```javascript async generateSummary(pr, prDetails) { return this.retryOperation(async () => { const payload = JSON.stringify({ pr, prDetails }); const command = `openclaw agent invoke ${config.openclaw.summaryAgent} --message '${payload.replace(/'/g, "\\'")}' --timeout 90`; const { stdout, stderr } = await execPromise(command); return JSON.parse(stdout); }, config.retries.agentRetries, 5000, config.retries.backoffFactor); } async runFullReview(pr) { return this.retryOperation(async () => { const fullReviewPrompt = `Review pull request #${pr.number} in repository.\n INSTRUCTIONS:\n 1. Compare code between source branch (${pr.headBranch}) and target branch (${pr.baseBranch})\n 2. Create HIGH LEVEL SUMMARY: main purpose, scale of changes, affected areas\n 3. Provide REVIEW COMMENTS: potential issues, best practices, positive points\n 4. ONLY GENERATE REVIEW COMMENTS - do not auto-approve\n\n PR Details:\n - Author: ${pr.author}\n - URL: ${pr.url}\n - Source branch: ${pr.headBranch}\n - Target branch: ${pr.baseBranch}`; const command = `openclaw run --model github-copilot/claude-sonnet-4.6 --message '${fullReviewPrompt.replace(/'/g, "\\'")}' --timeout 120`; const { stdout } = await execPromise(command); return JSON.parse(stdout); }, config.retries.agentRetries, 10000, config.retries.backoffFactor); } ``` -------------------------------- ### Run PR Check Cycle - JavaScript Source: https://context7.com/hinha/agent-pr/llms.txt Orchestrates the main PR monitoring cycle by fetching open PRs, filtering them based on processing status, age, and skip rules, and initiating background processing for eligible PRs. It relies on external services like mcpGithubService, prStateManager, and skipManager. ```javascript // src/services/schedulerDaemon.js // Main PR check cycle async runPRCheckCycle() { const openPRs = await mcpGithubService.getOpenPRs(); logger.info(`Found ${openPRs.length} total open PRs in repository`); for (const pr of openPRs) { // Skip if already processing, processed, or skipped if (this.activeProcesses.has(pr.id.toString()) || await prStateManager.isProcessed(pr.id) || skipManager.isSkipped(pr.id)) { continue; } // Skip PRs older than configured max age const prAge = Date.now() - pr.createdAt.getTime(); if (prAge > config.scheduler.maxAgeMs) { await prStateManager.markProcessed(pr.id); continue; } // Start processing PR in background this.processSinglePR(pr); } } // Start the scheduler daemon start() { logger.info('βœ… MCP-based PR monitor daemon started'); this.runPRCheckCycle(); this.checkInterval = setInterval(() => this.runPRCheckCycle(), config.scheduler.checkIntervalMs); } // Gracefully stop the daemon stop() { if (this.checkInterval) clearInterval(this.checkInterval); logger.info('Scheduler daemon stopped'); } ``` -------------------------------- ### Configuration Object Structure Source: https://context7.com/hinha/agent-pr/llms.txt The JavaScript configuration object for the MCP-based PR Monitor Daemon, derived from environment variables. It structures settings for MCP, OpenClaw, GitHub, Telegram, scheduler intervals, and retry logic. ```javascript const config = { mcp: { configPath: process.env.MCP_CONFIG_PATH, serverName: process.env.MCP_SERVER_NAME || 'github-work', baseCommand: 'mcporter' }, openclaw: { summaryAgent: process.env.OPENCLAW_AGENT_SUMMARY, reviewAgent: process.env.OPENCLAW_AGENT_REVIEW, reviewModel: process.env.OPENCLAW_REVIEW_MODEL || 'github-copilot/claude-sonnet-4.6' }, github: { owner: process.env.GITHUB_OWNER || 'git_owner', repo: process.env.GITHUB_REPO || 'git_repo' }, telegram: { botToken: process.env.TELEGRAM_BOT_TOKEN, chatId: parseInt(process.env.TELEGRAM_CHAT_ID), threadId: parseInt(process.env.TELEGRAM_THREAD_ID) }, scheduler: { checkIntervalMs: 7 * 60 * 1000, skipDurationMs: (parseInt(process.env.SKIP_CACHE_DURATION_HOURS) || 3) * 60 * 60 * 1000, maxAgeMs: 24 * 60 * 60 * 1000 }, retries: { mcpRetries: 3, telegramRetries: 3, agentRetries: 2, backoffFactor: 2 } }; ``` -------------------------------- ### MCPGitHubService - GitHub Interaction via MCP Source: https://context7.com/hinha/agent-pr/llms.txt The MCPGitHubService class in Node.js handles all GitHub API interactions using the `mcporter` CLI, avoiding direct REST API calls. It includes methods for executing MCP commands with retries, fetching open pull requests, and retrieving PR details. ```javascript // src/services/mcpGithubService.js // Execute MCP tool call with exponential backoff retries async callMCP(method, args = {}) { return this.retryOperation(async () => { const argStrings = Object.entries(args) .map(([key, value]) => { if (typeof value === 'object') return `${key}:${JSON.stringify(value)}`; return `${key}=${JSON.stringify(value)}`; }) .join(' '); const fullCommand = `mcporter call github-work.${method} ${argStrings} --output json`; const { stdout, stderr } = await execPromise(fullCommand); return JSON.parse(stdout); }, config.retries.mcpRetries, 2000, config.retries.backoffFactor); } // Fetch all open pull requests async getOpenPRs() { const rawPRs = await this.callMCP('list_pull_requests', { owner: this.owner, repo: this.repo, state: 'open', per_page: 100, page: 1 }); return rawPRs.map(pr => ({ id: pr.id, number: pr.number, title: pr.title, url: pr.html_url, author: pr.user?.login || 'unknown', createdAt: new Date(pr.created_at), description: pr.body || 'No description provided', baseBranch: pr.base?.ref, headBranch: pr.head?.ref })); } // Get PR changed files and diff metadata async getPRDetails(prNumber) { const files = await this.callMCP('get_pull_request_files', { owner: this.owner, repo: this.repo, pull_number: prNumber }); return { filesChanged: files.length, files: files.map(f => ({ filename: f.filename, additions: f.additions, deletions: f.deletions, changes: f.changes, status: f.status })), totalChanges: files.reduce((sum, f) => sum + f.changes, 0) }; } ``` -------------------------------- ### Manage Skipped PRs - JavaScript Source: https://context7.com/hinha/agent-pr/llms.txt Handles temporary suppression of notifications for specific PRs. It allows adding a PR to a skip cache with an expiry duration and checking if a PR is currently skipped, including automatic cleanup of expired entries. This functionality is crucial for preventing alert fatigue. ```javascript // src/services/skipManager.js // Add a PR to skip cache for configured duration (default 3 hours) async addSkip(prId) { const expiry = new Date(Date.now() + config.scheduler.skipDurationMs); const prIdStr = prId.toString(); this.skipCache.set(prIdStr, expiry.toISOString()); await this.saveSkipCache(); logger.info(`Added PR #${prId} to skip cache until ${expiry.toISOString()}`); } // Check if PR is currently skipped, auto-cleanup expired entries isSkipped(prId) { const prIdStr = prId.toString(); if (!this.skipCache.has(prIdStr)) return false; const expiry = new Date(this.skipCache.get(prIdStr)); if (new Date() > expiry) { this.skipCache.delete(prIdStr); this.saveSkipCache(); return false; } return true; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.