fix: address code review feedback - extract constants, fix Claude model matching, workflow conditions

- llm-router.js: fix isClaudeModel() to properly match against CLAUDE_MODELS array
- deputy-message-board.js: extract magic numbers to named constants (RETRY_BASE_DELAY_MS, MAX_ERRORS_KEPT, etc.)
- subscription-server-v3.js: extract rate limit to MAX_SEND_CODE_PER_HOUR constant
- deputy-message-board.yml: add github.event_name != 'schedule' condition to event mode job

Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/a513d091-ce5c-40d4-bb3c-0adeff70b5b0

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-07 17:27:51 +00:00 committed by GitHub
parent 656febf8d6
commit 8d408d1ce2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 18 additions and 10 deletions

View File

@ -25,12 +25,13 @@ jobs:
name: "🎖️ 副将回复留言" name: "🎖️ 副将回复留言"
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: > if: >
(github.event_name == 'issues' && github.event_name != 'schedule' &&
((github.event_name == 'issues' &&
github.event.action == 'opened' && github.event.action == 'opened' &&
contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board')) || contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board')) ||
(github.event_name == 'issue_comment' && (github.event_name == 'issue_comment' &&
contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board') && contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board') &&
github.event.comment.user.login != 'github-actions[bot]') github.event.comment.user.login != 'github-actions[bot]'))
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -42,6 +42,12 @@ const {
const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab'; const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab';
const STATUS_FILE = path.join(__dirname, '..', 'data', 'deputy-status.json'); const STATUS_FILE = path.join(__dirname, '..', 'data', 'deputy-status.json');
// ── 可调常量 ──
const RETRY_BASE_DELAY_MS = 2000; // LLM重试基础延迟
const MAX_ERRORS_KEPT = 20; // 保留最近N条错误记录
const MAX_ESCALATIONS_KEPT = 10; // 保留最近N条升级记录
const MAX_LLM_FAILURES_BEFORE_ESCALATION = 3; // 连续N次全模型失败后升级
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════
// LLM多模型自动降级路由器 (内嵌版) // LLM多模型自动降级路由器 (内嵌版)
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════
@ -143,7 +149,7 @@ async function callLLMWithFallback(systemPrompt, userMessage) {
const errMsg = `${model}(attempt ${attempt}): ${err.message}`; const errMsg = `${model}(attempt ${attempt}): ${err.message}`;
console.log(`[副将] ⚠️ ${errMsg}`); console.log(`[副将] ⚠️ ${errMsg}`);
errors.push(errMsg); errors.push(errMsg);
if (attempt < 2) await new Promise(r => setTimeout(r, 2000 * attempt)); if (attempt < 2) await new Promise(r => setTimeout(r, RETRY_BASE_DELAY_MS * attempt));
} }
} }
} }
@ -384,7 +390,7 @@ async function processMessage(issueNumber, question, author, ctx, systemSummary,
// ═══════════════════════════════════════════════ // ═══════════════════════════════════════════════
async function checkAndEscalate(status) { async function checkAndEscalate(status) {
if (status.consecutive_llm_failures >= 3) { if (status.consecutive_llm_failures >= MAX_LLM_FAILURES_BEFORE_ESCALATION) {
const title = `🚨 [副将升级] LLM连续${status.consecutive_llm_failures}次全模型调用失败`; const title = `🚨 [副将升级] LLM连续${status.consecutive_llm_failures}次全模型调用失败`;
const body = `## <20><> 铸渊副将升级通报\n\n` + const body = `## <20><> 铸渊副将升级通报\n\n` +
`**时间**: ${new Date().toISOString()}\n` + `**时间**: ${new Date().toISOString()}\n` +
@ -402,7 +408,7 @@ async function checkAndEscalate(status) {
try { try {
await createEscalationIssue(title, body); await createEscalationIssue(title, body);
status.escalations.push({ time: new Date().toISOString(), type: 'llm_failure', detail: `连续${status.consecutive_llm_failures}次失败` }); status.escalations.push({ time: new Date().toISOString(), type: 'llm_failure', detail: `连续${status.consecutive_llm_failures}次失败` });
if (status.escalations.length > 10) status.escalations = status.escalations.slice(-10); if (status.escalations.length > MAX_ESCALATIONS_KEPT) status.escalations = status.escalations.slice(-MAX_ESCALATIONS_KEPT);
console.log(`[副将] 🚨 升级Issue已创建`); console.log(`[副将] 🚨 升级Issue已创建`);
} catch (err) { console.error(`[副将] ⚠️ 创建升级Issue失败: ${err.message}`); } } catch (err) { console.error(`[副将] ⚠️ 创建升级Issue失败: ${err.message}`); }
} }
@ -466,7 +472,7 @@ async function patrolMode() {
status.errors.push(`${new Date().toISOString()} · patrol: ${err.message}`); status.errors.push(`${new Date().toISOString()} · patrol: ${err.message}`);
} }
if (status.errors.length > 20) status.errors = status.errors.slice(-20); if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT);
await checkAndEscalate(status); await checkAndEscalate(status);
status.last_success = repliedCount > 0 || processedCount === 0 ? new Date().toISOString() : status.last_success; status.last_success = repliedCount > 0 || processedCount === 0 ? new Date().toISOString() : status.last_success;
saveStatus(status); saveStatus(status);
@ -506,7 +512,7 @@ async function eventMode() {
status.errors.push(`${new Date().toISOString()} · event #${ISSUE_NUMBER}: ${err.message}`); status.errors.push(`${new Date().toISOString()} · event #${ISSUE_NUMBER}: ${err.message}`);
} }
if (status.errors.length > 20) status.errors = status.errors.slice(-20); if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT);
await checkAndEscalate(status); await checkAndEscalate(status);
saveStatus(status); saveStatus(status);
} }
@ -527,7 +533,7 @@ main().catch(err => {
try { try {
const status = readStatus(); const status = readStatus();
status.errors.push(`${new Date().toISOString()} · fatal: ${err.message}`); status.errors.push(`${new Date().toISOString()} · fatal: ${err.message}`);
if (status.errors.length > 20) status.errors = status.errors.slice(-20); if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT);
saveStatus(status); saveStatus(status);
} catch { /* ignore */ } } catch { /* ignore */ }
process.exit(1); process.exit(1);

View File

@ -46,7 +46,7 @@ const CLAUDE_RELAY_PORT = parseInt(process.env.ZY_CLAUDE_RELAY_PORT || '18443',
const CLAUDE_RELAY_ENABLED = process.env.ZY_CLAUDE_RELAY_ENABLED === 'true'; const CLAUDE_RELAY_ENABLED = process.env.ZY_CLAUDE_RELAY_ENABLED === 'true';
function isClaudeModel(model) { function isClaudeModel(model) {
return CLAUDE_MODELS.some(cm => model.includes('claude')); return CLAUDE_MODELS.some(cm => model.includes(cm)) || model.includes('claude');
} }
// ── 路由器状态 ────────────────────────────── // ── 路由器状态 ──────────────────────────────

View File

@ -39,6 +39,7 @@ const userManager = require('./user-manager');
// ── 操作快照文件 ────────────────────────────── // ── 操作快照文件 ──────────────────────────────
const AUTH_SNAPSHOT_FILE = path.join(DATA_DIR, 'bandwidth-auth-snapshots.json'); const AUTH_SNAPSHOT_FILE = path.join(DATA_DIR, 'bandwidth-auth-snapshots.json');
const MAX_SNAPSHOTS = 500; // 最多保留500条快照记录 const MAX_SNAPSHOTS = 500; // 最多保留500条快照记录
const MAX_SEND_CODE_PER_HOUR = 3; // 每个IP每小时最多发送验证码次数
/** /**
* 保存用户操作快照 * 保存用户操作快照
@ -1751,7 +1752,7 @@ async function submitCode(e) {
const hourPart = parseInt(k.split(':').pop(), 10); const hourPart = parseInt(k.split(':').pop(), 10);
if (hourPart < currentHourPrefix - 1) delete rl[k]; if (hourPart < currentHourPrefix - 1) delete rl[k];
} }
if (rl[hourKey] > 3) { if (rl[hourKey] > MAX_SEND_CODE_PER_HOUR) {
res.writeHead(429, { 'Content-Type': 'application/json; charset=utf-8' }); res.writeHead(429, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({ success: false, message: '发送频率过高请1小时后重试' })); res.end(JSON.stringify({ success: false, message: '发送频率过高请1小时后重试' }));
return; return;