[HELP] Как подключить Aider CLI (aider-chat) как агента в QwenPaw #2309
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
| name: Issue Welcome Bot | |
| on: | |
| issues: | |
| types: [opened] | |
| permissions: | |
| issues: write | |
| jobs: | |
| welcome: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Welcome Issue Author | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| // Helper function for English ordinal suffix (1st, 2nd, 3rd, 4th...) | |
| function getOrdinalSuffix(num) { | |
| const j = num % 10; | |
| const k = num % 100; | |
| if (j === 1 && k !== 11) return 'st'; | |
| if (j === 2 && k !== 12) return 'nd'; | |
| if (j === 3 && k !== 13) return 'rd'; | |
| return 'th'; | |
| } | |
| const author = context.payload.issue.user.login; | |
| const issueNumber = context.payload.issue.number; | |
| const issueTitle = context.payload.issue.title; | |
| const issueBody = context.payload.issue.body || ''; | |
| // --------------------------------------------------------------- | |
| // Spam gate: close if author has >10 open issues in this repo. | |
| // Bypassed for repo maintainers and admins. | |
| // per_page=1 is sufficient — we only need total_count. | |
| // --------------------------------------------------------------- | |
| try { | |
| // Bypass for repo maintainers / admins. | |
| try { | |
| const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| username: author, | |
| }); | |
| if (['admin', 'maintain'].includes(perm.permission)) { | |
| console.log(`@${author} is a repo ${perm.permission} — bypassing spam gate`); | |
| // Fall through to normal welcome flow. | |
| throw new Error('__bypass__'); | |
| } | |
| } catch (permErr) { | |
| if (permErr.message === '__bypass__') throw permErr; | |
| // 404 = not a collaborator, proceed with spam check. | |
| if (permErr.status !== 404) { | |
| console.log(`Permission check warning: ${permErr.message} — proceeding with spam check`); | |
| } | |
| } | |
| const { data: openResult } = await github.rest.search.issuesAndPullRequests({ | |
| q: `repo:${context.repo.owner}/${context.repo.repo} type:issue is:open author:${author}`, | |
| per_page: 1, | |
| }); | |
| const openCount = openResult.total_count; | |
| console.log(`@${author} has ${openCount} open issue(s) in this repo`); | |
| if (openCount > 10) { | |
| // Ensure the label exists (create it if missing) | |
| try { | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Close-and-review-later', | |
| color: 'e4e669', | |
| description: 'Closed automatically for later review', | |
| }); | |
| } catch (labelErr) { | |
| // 422 = label already exists, that's fine | |
| if (labelErr.status !== 422) console.log(`Label creation warning: ${labelErr.message}`); | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| labels: ['Close-and-review-later'], | |
| }); | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| state: 'closed', | |
| }); | |
| const warningComment = [ | |
| `<div align="center">`, | |
| ``, | |
| `<img src="https://img.alicdn.com/imgextra/i2/O1CN01eSmIfy1hJImze8NN8_!!6000000004256-2-tps-1858-1858.png" width="80" height="80" />`, | |
| ``, | |
| `</div>`, | |
| ``, | |
| `Hi @${author},`, | |
| ``, | |
| `This issue has been **automatically closed** because you currently have more than **10 open issues** in this repository.`, | |
| ``, | |
| `> **Why this happened**`, | |
| `> Our [contribution policy](https://github.com/${context.repo.owner}/${context.repo.repo}/issues/4333) asks contributors to maintain a manageable number of active issues to ensure fair use of maintainer resources. A high volume of simultaneous open issues — especially those that are AI-generated and submitted without personal verification — places a significant burden on maintainers.`, | |
| ``, | |
| `**What you can do:**`, | |
| `1. Review your open issues and close any that are no longer relevant, duplicated, or have not been personally verified.`, | |
| `2. Once you have fewer than 10 open issues, you are welcome to reopen this issue or file a new one.`, | |
| `3. Please ensure every issue you file has been **personally reproduced** and includes all required information per our issue templates.`, | |
| ``, | |
| `This issue has been labeled \`Close-and-review-later\` and may be revisited by a maintainer.`, | |
| ``, | |
| `Thank you for your understanding. 🐾`, | |
| ``, | |
| `---`, | |
| ``, | |
| `你好 @${author},`, | |
| ``, | |
| `由于你目前在本仓库有超过 **10 个处于 open 状态的 issue**,此 issue 已被**自动关闭**。`, | |
| ``, | |
| `> **原因说明**`, | |
| `> 根据我们的[贡献规范](https://github.com/${context.repo.owner}/${context.repo.repo}/issues/4333),为保障维护者资源的合理使用,我们要求贡献者控制活跃 issue 的数量。大量未经人工验证的 open issue(尤其是 AI 生成内容)会给维护者带来沉重负担。`, | |
| ``, | |
| `**你可以采取以下措施:**`, | |
| `1. 检查并关闭不再相关、重复或未经本人验证的 open issue。`, | |
| `2. 当你的 open issue 数量少于 10 个时,欢迎重新开启此 issue 或提交新的 issue。`, | |
| `3. 请确保每个 issue 均经过**本人亲自复现**,并按照 issue 模板填写所有必填信息。`, | |
| ``, | |
| `此 issue 已被标记为 \`Close-and-review-later\`,维护者可能会在后续进行审核。`, | |
| ``, | |
| `感谢你的理解与配合。🐾`, | |
| ].join('\n'); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| body: warningComment, | |
| }); | |
| console.log(`Closed issue #${issueNumber} for @${author} — open issue limit exceeded (${openCount} open)`); | |
| return; | |
| } | |
| } catch (spamErr) { | |
| if (spamErr.message !== '__bypass__') { | |
| // Fail open: if the check itself errors, don't block legitimate contributors. | |
| console.log(`Spam gate check failed: ${spamErr.message} — continuing with normal welcome`); | |
| } | |
| } | |
| // --------------------------------------------------------------- | |
| // Check if Chinese characters exist in title | |
| const hasChinese = /[\u4e00-\u9fff]/.test(issueTitle); | |
| // Count user's total issues in this repo using search API | |
| let issueCount = 0; | |
| let hasValidIssueCount = false; | |
| let isFirstIssue = false; | |
| try { | |
| const { data: searchResult } = await github.rest.search.issuesAndPullRequests({ | |
| q: `repo:${context.repo.owner}/${context.repo.repo} type:issue author:${author}`, | |
| }); | |
| issueCount = searchResult.total_count; | |
| hasValidIssueCount = true; | |
| isFirstIssue = issueCount === 1; | |
| } catch (err) { | |
| console.log(`Could not get issue count for ${author}, will skip count display`); | |
| hasValidIssueCount = false; | |
| } | |
| // Check if user has starred the repo by listing user's starred | |
| // repos | |
| let hasStarred = false; | |
| try { | |
| const fullRepoName = `${context.repo.owner}/${context.repo.repo}`; | |
| let page = 1; | |
| let found = false; | |
| while (page <= 10 && !found) { | |
| const { data: starredRepos } = await github.rest.activity.listReposStarredByUser({ | |
| username: author, | |
| per_page: 100, | |
| page: page | |
| }); | |
| if (starredRepos.length === 0) break; | |
| found = starredRepos.some(repo => repo.full_name === fullRepoName); | |
| if (found) { | |
| hasStarred = true; | |
| console.log(`User ${author} has starred the repo`); | |
| break; | |
| } | |
| page++; | |
| } | |
| if (!found) { | |
| console.log(`User ${author} has not starred the repo`); | |
| } | |
| } catch (error) { | |
| console.log( | |
| `Could not verify star status for ${author}:`, | |
| error.message | |
| ); | |
| } | |
| // Check if this is a bug report by title or label | |
| // Match "bug", "bugs", or "[Bug]:" prefix (case-insensitive) | |
| const titleContainsBug = /\bbug(s)?\b/i.test(issueTitle) || /^\s*\[bug\]\s*:/i.test(issueTitle); | |
| const labels = context.payload.issue.labels.map(label => label.name); | |
| const hasBugLabel = labels.some(name => name && name.toLowerCase() === 'bug'); | |
| const isBugRelated = titleContainsBug || hasBugLabel; | |
| // Check if issue uses bug report template | |
| const usedBugTemplate = issueBody.includes('## QwenPaw Version') || | |
| issueBody.includes('## Environment') || | |
| issueBody.includes('## Steps to Reproduce'); | |
| // If bug-related but template not used, need to remind | |
| const needBugTemplateReminder = isBugRelated && !usedBugTemplate; | |
| // Build comment | |
| let comment = ''; | |
| if (hasChinese) { | |
| // Bilingual response with logo | |
| comment = `<div align="center">\n\n`; | |
| comment += `<img src="https://img.alicdn.com/imgextra/i2/O1CN01eSmIfy1hJImze8NN8_!!6000000004256-2-tps-1858-1858.png" width="80" height="80" />\n\n`; | |
| comment += `## 欢迎来到 QwenPaw! 🐾 Welcome to QwenPaw!\n\n`; | |
| comment += `</div>\n\n`; | |
| if (hasValidIssueCount) { | |
| if (isFirstIssue) { | |
| comment += `你好 @${author},感谢你提交的第一个 issue!\n`; | |
| comment += `Hi @${author}, thank you for your first issue!\n\n`; | |
| } else { | |
| comment += `你好 @${author},这是你提交的第 ${issueCount} 个 issue。\n`; | |
| comment += `Hi @${author}, this is your ${issueCount}${getOrdinalSuffix(issueCount)} issue.\n\n`; | |
| } | |
| } else { | |
| comment += `你好 @${author},感谢你提交 issue!\n`; | |
| comment += `Hi @${author}, thank you for your issue!\n\n`; | |
| } | |
| if (needBugTemplateReminder) { | |
| comment += `### 📋 关于 Bug Report 模板 / About Bug Report Template\n\n`; | |
| comment += `我们注意到你的 issue 似乎与 bug 相关,但没有使用 Bug Report 模板。为了帮助我们更快地定位和修复问题,请确保你的 bug report 包含以下信息:\n`; | |
| comment += `We noticed your issue seems to be bug-related, but doesn't use the Bug Report template. To help us reproduce and fix the issue faster, please make sure your bug report includes:\n\n`; | |
| comment += `- ✅ **QwenPaw 版本 / Version** (使用 \`qwenpaw --version\` 查看)\n`; | |
| comment += `- ✅ **操作系统 / OS** (macOS/Linux/Windows 及版本)\n`; | |
| comment += `- ✅ **复现步骤 / Steps to Reproduce** (详细的步骤)\n`; | |
| comment += `- ✅ **实际结果 vs 预期结果 / Actual vs Expected**\n`; | |
| comment += `- ✅ **日志或截图 / Logs or Screenshots**\n\n`; | |
| comment += `你可以编辑 issue 来补充这些信息。如果你的 issue 缺少这些信息,维护者可能需要额外时间来询问细节。\n`; | |
| comment += `You can edit your issue to add this information. Missing information may require maintainers to ask follow-up questions.\n\n`; | |
| } | |
| comment += `我们会尽快查看你的 issue。感谢你对 QwenPaw 的支持!\n`; | |
| comment += `We'll review your issue soon. Thank you for supporting QwenPaw!\n\n`; | |
| // Internationalization reminder | |
| comment += `---\n\n`; | |
| comment += `> **🌍 关于国际化 / About Internationalization**\n`; | |
| comment += `> \n`; | |
| comment += `> QwenPaw 是一个国际化的开源社区。我们建议使用英文提交 issue,这样可以让更多的开发者参与讨论和贡献。\n`; | |
| comment += `> \n`; | |
| comment += `> QwenPaw is an international open-source community. We recommend using English for issues so that more developers worldwide can participate in discussions and contributions.\n\n`; | |
| // Star reminder at the end | |
| if (!hasStarred) { | |
| comment += `> [!TIP]\n`; | |
| comment += `> **⭐ 如果你觉得 QwenPaw 有帮助,请给我们一个 Star!**\n`; | |
| comment += `> \n`; | |
| comment += `> **⭐ If you find QwenPaw useful, please give us a Star!**\n`; | |
| comment += `> \n`; | |
| comment += `> <div align="center">\n`; | |
| comment += `> \n`; | |
| comment += `> <a href="https://github.com/agentscope-ai/QwenPaw"><img src="https://img.alicdn.com/imgextra/i1/O1CN01V8HYv61By0HYcIDaq_!!6000000000013-1-tps-1698-954.gif" alt="Star QwenPaw" width="600" /></a>\n`; | |
| comment += `> \n`; | |
| comment += `> </div>\n`; | |
| comment += `> \n`; | |
| comment += `> **掌握最新动态 / Staying ahead**\n`; | |
| comment += `> \n`; | |
| comment += `> 在 GitHub 上 Star QwenPaw,第一时间收到新版本发布通知。\n`; | |
| comment += `> \n`; | |
| comment += `> Star QwenPaw on GitHub and be instantly notified of new releases.\n`; | |
| comment += `> \n`; | |
| comment += `> 你的 star 将帮助更多开发者发现这个项目!🐾\n`; | |
| comment += `> \n`; | |
| comment += `> Your star helps more developers discover this project! 🐾`; | |
| } | |
| } else { | |
| // English only with logo | |
| comment = `<div align="center">\n\n`; | |
| comment += `<img src="https://img.alicdn.com/imgextra/i2/O1CN01eSmIfy1hJImze8NN8_!!6000000004256-2-tps-1858-1858.png" width="80" height="80" />\n\n`; | |
| comment += `## Welcome to QwenPaw! 🐾\n\n`; | |
| comment += `</div>\n\n`; | |
| if (hasValidIssueCount) { | |
| if (isFirstIssue) { | |
| comment += `Hi @${author}, thank you for your first issue!\n\n`; | |
| } else { | |
| comment += `Hi @${author}, this is your ${issueCount}${getOrdinalSuffix(issueCount)} issue.\n\n`; | |
| } | |
| } else { | |
| comment += `Hi @${author}, thank you for your issue!\n\n`; | |
| } | |
| if (needBugTemplateReminder) { | |
| comment += `### 📋 About Bug Report Template\n\n`; | |
| comment += `We noticed your issue seems to be bug-related, but doesn't use the Bug Report template. To help us reproduce and fix the issue faster, please make sure your bug report includes:\n\n`; | |
| comment += `- ✅ **QwenPaw Version** (use \`qwenpaw --version\`)\n`; | |
| comment += `- ✅ **Operating System** (macOS/Linux/Windows and version)\n`; | |
| comment += `- ✅ **Steps to Reproduce** (detailed steps)\n`; | |
| comment += `- ✅ **Actual vs Expected Behavior**\n`; | |
| comment += `- ✅ **Logs or Screenshots**\n\n`; | |
| comment += `You can edit your issue to add this information. Missing information may require us to ask follow-up questions, which can delay the fix.\n\n`; | |
| } | |
| comment += `We'll review your issue soon. Thank you for supporting QwenPaw!\n\n`; | |
| // Star reminder at the end | |
| if (!hasStarred) { | |
| comment += `---\n\n`; | |
| comment += `> [!TIP]\n`; | |
| comment += `> **⭐ If you find QwenPaw useful, please give us a Star!**\n`; | |
| comment += `> \n`; | |
| comment += `> <div align="center">\n`; | |
| comment += `> \n`; | |
| comment += `> <a href="https://github.com/agentscope-ai/QwenPaw"><img src="https://img.alicdn.com/imgextra/i1/O1CN01V8HYv61By0HYcIDaq_!!6000000000013-1-tps-1698-954.gif" alt="Star QwenPaw" width="600" /></a>\n`; | |
| comment += `> \n`; | |
| comment += `> </div>\n`; | |
| comment += `> \n`; | |
| comment += `> **Staying ahead**\n`; | |
| comment += `> \n`; | |
| comment += `> Star QwenPaw on GitHub and be instantly notified of new releases.\n`; | |
| comment += `> \n`; | |
| comment += `> Your star helps more developers discover this project! 🐾`; | |
| } | |
| } | |
| // Post comment | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: issueNumber, | |
| body: comment | |
| }); | |
| const logMessage = hasValidIssueCount | |
| ? `Posted welcome comment on issue #${issueNumber} for @${author} (issue #${issueCount})` | |
| : `Posted welcome comment on issue #${issueNumber} for @${author}`; | |
| console.log(logMessage); |