Skip to content

[HELP] Как подключить Aider CLI (aider-chat) как агента в QwenPaw #2309

[HELP] Как подключить Aider CLI (aider-chat) как агента в QwenPaw

[HELP] Как подключить Aider CLI (aider-chat) как агента в QwenPaw #2309

Workflow file for this run

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);