diff --git a/.github/workflows/deputy-message-board.yml b/.github/workflows/deputy-message-board.yml index 7294e9ee..2945c391 100644 --- a/.github/workflows/deputy-message-board.yml +++ b/.github/workflows/deputy-message-board.yml @@ -2,29 +2,36 @@ # 🔺 Sovereign: TCS-0002∞ | Root: SYS-GLW-0001 # 📜 Copyright: 囜䜜登字-2026-A-00037559 # ═══════════════════════════════════════════════ -name: "💬 铞枊副将·留蚀板自劚回倍" +name: "💬 铞枊副将·留蚀板掻䜓Agent v2.0" on: + # 事件觊发: Issue/Comment → 即时回倍 issues: types: [opened] issue_comment: types: [created] + # 定时巡查: 08:00/23:00 CST → 扫描所有未回倍Issue + schedule: + - cron: '0 0 * * *' # UTC 00:00 = CST 08:00 早班巡查 + - cron: '0 15 * * *' # UTC 15:00 = CST 23:00 晚班巡查 permissions: - contents: read + contents: write issues: write jobs: + # ── 事件暡匏: Issue/Comment觊发即时回倍 ── deputy-reply: name: "🎖 副将回倍留蚀" runs-on: ubuntu-latest if: > - (github.event_name == 'issues' && + github.event_name != 'schedule' && + ((github.event_name == 'issues' && github.event.action == 'opened' && contains(join(github.event.issue.labels.*.name, ','), 'deputy-message-board')) || (github.event_name == 'issue_comment' && 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: - uses: actions/checkout@v4 @@ -36,12 +43,12 @@ jobs: run: | npm install --omit=dev --ignore-scripts || echo "⚠ 䟝赖安装匂垞·郚分功胜可胜受限" - - name: "🎖 副将倄理留蚀" + - name: "🎖 副将倄理留蚀 (事件暡匏)" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ZY_LLM_API_KEY: ${{ secrets.ZY_LLM_API_KEY }} ZY_LLM_BASE_URL: ${{ secrets.ZY_LLM_BASE_URL }} - ZY_NOTION_TOKEN: ${{ secrets.ZY_NOTION_TOKEN }} + DEPUTY_MODE: event ISSUE_NUMBER: ${{ github.event.issue.number }} ISSUE_TITLE: ${{ github.event.issue.title }} ISSUE_BODY: ${{ github.event.issue.body }} @@ -50,3 +57,49 @@ jobs: EVENT_NAME: ${{ github.event_name }} ISSUE_AUTHOR: ${{ github.event.issue.user.login }} run: node scripts/deputy-message-board.js + + - name: "📊 提亀副将状态" + if: always() + run: | + if [ -f data/deputy-status.json ]; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add data/deputy-status.json + git diff --cached --quiet || git commit -m "🎖 副将状态曎新 · 事件暡匏 · $(date -u +%Y-%m-%dT%H:%M:%S)Z" + git push || echo "⚠ push倱莥·䞋次巡查时同步" + fi + + # ── 巡查暡匏: 定时扫描所有未回倍Issue ── + deputy-patrol: + name: "🔍 副将巡查未回倍留蚀" + runs-on: ubuntu-latest + if: github.event_name == 'schedule' + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: "📊 安装䟝赖" + run: | + npm install --omit=dev --ignore-scripts || echo "⚠ 䟝赖安装匂垞·郚分功胜可胜受限" + + - name: "🔍 副将巡查 (巡查暡匏)" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ZY_LLM_API_KEY: ${{ secrets.ZY_LLM_API_KEY }} + ZY_LLM_BASE_URL: ${{ secrets.ZY_LLM_BASE_URL }} + DEPUTY_MODE: patrol + run: node scripts/deputy-message-board.js + + - name: "📊 提亀副将状态" + if: always() + run: | + if [ -f data/deputy-status.json ]; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add data/deputy-status.json + git diff --cached --quiet || git commit -m "🔍 副将巡查状态曎新 · $(date -u +%Y-%m-%dT%H:%M:%S)Z" + git push || echo "⚠ push倱莥·䞋次巡查时同步" + fi diff --git a/README.md b/README.md index da261cbb..b70644d9 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,19 @@ | HLDP通甚语蚀同步 | ✅ 已执行 | hldp/data/common/ | | 仓库銖页曎新 | ✅ 已执行 | README.md 筟到仪衚盘 | | 工䜜流士兵巡检 | ✅ 18/18 圚岗 | 猺倱的需冰朔确讀 | +| 留蚀板巡查 | ✅ v2.0掻化 | LLM倚暡型降级 · 定时巡查 · 自愈 | + +### 🎖 铞枊副将Agent状态 (v2.0掻䜓Agent) + +| 项目 | 状态 | +|------|------| +| Agent版本 | v2.0 · 掻䜓Agent · 䞉层自愈 | +| 运行暡匏 | 事件暡匏(即时回倍) + 巡查暡匏(08:00/23:00) | +| LLM胜力 | 倚暡型自劚降级: deepseek → qwen → moonshot → zhipu | +| 自感知 | data/deputy-status.json · 每次运行曎新 | +| 自修倍 | 每暡型重试2次 · 递增等埅 | +| 升级通报 | 连续3次倱莥 → 创建Issue → 邮件通知冰朔 | +| 📮 留蚀入口 | [给副将留蚀](https://github.com/qinfendebingshuo/guanghulab/issues/new?labels=deputy-message-board&title=给铞枊副将留蚀) | > *歀仪衚盘由铞枊副将(ZY-DEPUTY-001)每日唀醒时自劚曎新 · 08:00 / 23:00* diff --git a/data/deputy-status.json b/data/deputy-status.json new file mode 100644 index 00000000..f3e1ffb0 --- /dev/null +++ b/data/deputy-status.json @@ -0,0 +1,16 @@ +{ + "version": "2.0", + "last_run": null, + "last_success": null, + "llm_available": false, + "llm_model_used": null, + "consecutive_llm_failures": 0, + "issues_processed": 0, + "issues_replied": 0, + "patrol_runs": 0, + "event_runs": 0, + "errors": [], + "escalations": [], + "created_at": "2026-04-07T17:13:39Z", + "_comment": "铞枊副将v2.0掻䜓Agent状态文件 · 每次运行自劚曎新" +} diff --git a/docs/index.html b/docs/index.html index 7bc28025..5d38aa17 100644 --- a/docs/index.html +++ b/docs/index.html @@ -716,6 +716,36 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh .app-card-tags{display:flex;gap:5px;flex-wrap:wrap} .app-card-arrow{font-size:18px;color:var(--accent);opacity:.5;flex-shrink:0;transition:all .3s} .app-card:hover .app-card-arrow{opacity:1;transform:translateX(4px)} + +/* ═══ PAGE: Bandwidth Sharing (垊宜共享) ═══ */ +#pg-bandwidth .bw-hero{text-align:center;padding:28px 0 20px} +#pg-bandwidth .bw-hero h2{font-size:1.4em;background:linear-gradient(135deg,var(--accent),var(--accent2));-webkit-background-clip:text;-webkit-text-fill-color:transparent} +#pg-bandwidth .bw-hero p{color:var(--dim);font-size:.9em;margin-top:6px} +#pg-bandwidth .bw-stats{display:flex;justify-content:space-around;margin:16px 0;gap:12px} +#pg-bandwidth .bw-stat{text-align:center;flex:1} +#pg-bandwidth .bw-stat .num{font-size:1.5em;font-weight:700;color:var(--accent);font-family:var(--m)} +#pg-bandwidth .bw-stat .label{font-size:.75em;color:var(--dim);margin-top:4px} +#pg-bandwidth .bw-card{background:var(--bg1);border-radius:var(--r);padding:20px;margin:12px 0;border:1px solid var(--border)} +#pg-bandwidth .bw-card h3{font-size:1em;color:var(--accent);margin-bottom:14px} +#pg-bandwidth .bw-input{width:100%;padding:12px 14px;background:var(--bg2);border:2px solid var(--border);border-radius:var(--rs);color:var(--text);font-size:1em;outline:none;transition:border-color .3s;font-family:var(--f)} +#pg-bandwidth .bw-input:focus{border-color:var(--accent);box-shadow:0 0 0 3px rgba(138,180,248,.15)} +#pg-bandwidth .bw-input::placeholder{color:var(--dim);font-size:.85em} +#pg-bandwidth .bw-code-input{text-align:center;letter-spacing:8px;font-size:1.3em;font-family:var(--m)} +#pg-bandwidth .bw-code-input::placeholder{letter-spacing:0;font-size:.6em} +#pg-bandwidth .bw-btn{width:100%;padding:14px;background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff;border:none;border-radius:var(--rs);font-size:1em;font-weight:600;cursor:pointer;transition:opacity .3s,transform .15s;margin-top:8px;font-family:var(--f)} +#pg-bandwidth .bw-btn:hover{opacity:.9} +#pg-bandwidth .bw-btn:active{transform:scale(.98)} +#pg-bandwidth .bw-btn:disabled{opacity:.5;cursor:not-allowed;transform:none} +#pg-bandwidth .bw-btn-outline{background:transparent;border:1px solid var(--accent);color:var(--accent)} +#pg-bandwidth .bw-btn-outline:hover{background:rgba(138,180,248,.1)} +#pg-bandwidth .bw-result{text-align:center;padding:14px;border-radius:var(--rs);margin-top:12px;display:none;font-size:.9em;line-height:1.6} +#pg-bandwidth .bw-result.ok{display:block;background:rgba(52,211,153,.1);color:var(--ok);border:1px solid rgba(52,211,153,.2)} +#pg-bandwidth .bw-result.err{display:block;background:rgba(248,113,113,.1);color:var(--err);border:1px solid rgba(248,113,113,.2)} +#pg-bandwidth .bw-info{background:var(--bg2);border-radius:var(--rs);padding:14px 16px;margin:10px 0;font-size:.85em;line-height:1.8;color:var(--dim);border-left:3px solid var(--accent)} +#pg-bandwidth .bw-consent{display:flex;align-items:flex-start;gap:10px;margin:14px 0;padding:12px 14px;background:var(--bg2);border-radius:var(--rs);border:1px solid var(--border);cursor:pointer;transition:border-color .3s} +#pg-bandwidth .bw-consent:hover{border-color:var(--accent)} +#pg-bandwidth .bw-consent input{margin-top:3px;width:18px;height:18px;accent-color:var(--accent);cursor:pointer;flex-shrink:0} +#pg-bandwidth .bw-consent span{color:var(--dim);font-size:.85em;line-height:1.7} @@ -894,6 +924,23 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh + +
+
⚡ 快速入口
+
+
+
🌊
+
垊宜共享加速池
共享䜠的垊宜 · 加速所有人
+
+ +
+
🎖
+
铞枊副将留蚀板
每日08:00/23:00唀醒 · LLM深床掚理回倍
+
+
+
+
+
📋 版本历史
@@ -1131,6 +1178,63 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
+ + + +
+
+ +
🌊 垊宜共享
+
+
+
+
+

🌊 垊宜共享加速池

+

共享䜠的垊宜 · 加速所有人

+
+ +
+
—
莡献者
+
—
掻跃
+
—
总莡献(GB)
+
+ +
+

📧 第䞀步 · 发送验证码

+
+ +
+ +
+
+ +
+

🔑 第二步 · 蟓入验证码

+
+ +
+ + +
+
+ +
+ 💡 垊宜共享诎明 + ✅ 提䟛了验证码的甚户 → 䌘先保障䜠的䞓属通道
+ ✅ 䜠甚䞍䞊的资源才䌚被共享 → 䞍抢䜠的垊宜
+ ✅ 随时可退出 → 䜠有完党的控制权
+ ⚠ 验证码15分钟内有效 · 䞀次性䜿甚 +
+ +
+

© 2026 光湖语蚀䞖界 · 囜䜜登字-2026-A-00037559

+
+
+
+ @@ -3446,6 +3550,132 @@ offline = function(msg){ }); })(); +})(); + +/* ════════════════════════════════════════ + BANDWIDTH SHARING AGENT (垊宜共享验证) + GitHub Pages 前端 → 后端API桥接 + ════════════════════════════════════════ */ +var BW_API_BASE = 'https://guanghulab.online/api/proxy-v3'; + +function bwSendCode(){ + var email = document.getElementById('bwEmail').value.trim().toLowerCase(); + var btn = document.getElementById('bwSendCode'); + var result = document.getElementById('bwSendResult'); + + if(!email || email.indexOf('@') === -1){ + result.className = 'bw-result err'; + result.textContent = '请蟓入有效的邮箱地址'; + return; + } + + btn.disabled = true; + btn.textContent = '发送䞭...'; + result.className = 'bw-result'; + result.style.display = 'none'; + + fetch(BW_API_BASE + '/bandwidth-send-code', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({email: email}) + }).then(function(r){ return r.json(); }).then(function(data){ + btn.disabled = false; + btn.textContent = '发送验证码'; + result.className = 'bw-result ' + (data.success ? 'ok' : 'err'); + result.textContent = data.message || (data.success ? '验证码已发送' : '发送倱莥'); + + if(data.success){ + // Cooldown 60s + var cd = 60; + btn.disabled = true; + var timer = setInterval(function(){ + cd--; + btn.textContent = cd + 's 后可重发'; + if(cd <= 0){ + clearInterval(timer); + btn.disabled = false; + btn.textContent = '发送验证码'; + } + }, 1000); + } + }).catch(function(err){ + btn.disabled = false; + btn.textContent = '发送验证码'; + result.className = 'bw-result err'; + result.textContent = '眑络错误请检查眑络后重试'; + }); +} + +function bwVerifyCode(){ + var email = document.getElementById('bwEmail').value.trim().toLowerCase(); + var code = document.getElementById('bwCode').value.trim(); + var consent = document.getElementById('bwConsent').checked; + var btn = document.getElementById('bwVerify'); + var result = document.getElementById('bwVerifyResult'); + + if(!email || email.indexOf('@') === -1){ + result.className = 'bw-result err'; + result.textContent = '请先圚䞊方蟓入邮箱'; + return; + } + if(!code || code.length !== 6){ + result.className = 'bw-result err'; + result.textContent = '请蟓入6䜍验证码'; + return; + } + if(!consent){ + result.className = 'bw-result err'; + result.textContent = '请先募选同意垊宜共享协议'; + return; + } + + btn.disabled = true; + btn.textContent = '验证䞭...'; + result.className = 'bw-result'; + result.style.display = 'none'; + + fetch(BW_API_BASE + '/bandwidth-verify-code', { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({email: email, code: code}) + }).then(function(r){ return r.json(); }).then(function(data){ + btn.disabled = false; + btn.textContent = '提亀验证码'; + result.className = 'bw-result ' + (data.success ? 'ok' : 'err'); + result.textContent = data.message || (data.success ? '授权成功' : '验证倱莥'); + if(data.success) bwLoadPoolStatus(); + }).catch(function(){ + btn.disabled = false; + btn.textContent = '提亀验证码'; + result.className = 'bw-result err'; + result.textContent = '眑络错误请检查眑络后重试'; + }); +} + +function bwLoadPoolStatus(){ + fetch(BW_API_BASE + '/bandwidth-pool-status').then(function(r){ + return r.json(); + }).then(function(data){ + var el1 = document.getElementById('bwContributors'); + var el2 = document.getElementById('bwActive'); + var el3 = document.getElementById('bwContributed'); + if(el1) el1.textContent = data.total_contributors || 0; + if(el2) el2.textContent = data.active_contributors || 0; + if(el3) el3.textContent = data.total_contributed_gb || 0; + }).catch(function(){ + /* Pool status unavailable - keep defaults */ + }); +} + +// Auto-load pool status when bandwidth page is shown +var _origGo = window.go; +if(typeof _origGo === 'function'){ + window.go = function(target){ + _origGo(target); + if(target === 'bandwidth') bwLoadPoolStatus(); + }; +} + })(); diff --git a/scripts/deputy-message-board.js b/scripts/deputy-message-board.js index c35baf27..ef05b81c 100644 --- a/scripts/deputy-message-board.js +++ b/scripts/deputy-message-board.js @@ -4,12 +4,26 @@ * 📜 Copyright: 囜䜜登字-2026-A-00037559 * ═══════════════════════════════════════════════ * - * 铞枊副将·留蚀板自劚回倍匕擎 - * 职莣: 接收人类留蚀 → 查询数据库 → 调甚LLM → 回倍 + * 铞枊副将·留蚀板掻䜓Agent匕擎 v2.0 + * + * 䞉层自愈机制: + * L1 自感知 — 记圕运行状态对比预期 + * L2 自修倍 — LLM倚暡型自劚降级 (deepseek→qwen→moonshot→zhipu) + * L3 升级通报 — 连续倱莥 → 创建Issue → 发邮件给冰朔 + * + * 运行暡匏: + * - event暡匏: Issue/Comment觊发 → 即时回倍 + * - patrol暡匏: 定时08:00/23:00 → 扫描所有未回倍Issue */ const fs = require('fs'); const path = require('path'); +const https = require('https'); +const http = require('http'); + +// ═══════════════════════════════════════════════ +// 环境变量 +// ═══════════════════════════════════════════════ const { GITHUB_TOKEN, @@ -21,13 +35,135 @@ const { COMMENT_BODY, COMMENT_AUTHOR, EVENT_NAME, - ISSUE_AUTHOR + ISSUE_AUTHOR, + DEPUTY_MODE // 'patrol' | 'event' (default) } = process.env; -// Load system context from brain files +const REPO = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab'; +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倚暡型自劚降级路由噚 (内嵌版) +// ═══════════════════════════════════════════════ + +const MODEL_PRIORITY = [ + 'deepseek-chat', + 'qwen-turbo', + 'moonshot-v1-8k', + 'glm-4-flash' +]; + +function callSingleModel(baseUrl, apiKey, model, systemPrompt, userMessage, maxTokens, timeout) { + return new Promise((resolve, reject) => { + let urlObj; + try { + urlObj = new URL(baseUrl); + } catch { + urlObj = new URL(`https://${baseUrl}`); + } + + const postData = JSON.stringify({ + model, + messages: [ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userMessage } + ], + max_tokens: maxTokens, + temperature: 0.7 + }); + + let apiPath = urlObj.pathname; + if (!apiPath || apiPath === '/') { + apiPath = '/v1/chat/completions'; + } else if (!apiPath.includes('/chat/completions')) { + apiPath = apiPath.replace(/\/+$/, '') + '/chat/completions'; + } + + const options = { + hostname: urlObj.hostname, + port: urlObj.port || (urlObj.protocol === 'http:' ? 80 : 443), + path: apiPath, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + 'Content-Length': Buffer.byteLength(postData) + }, + timeout + }; + + const httpModule = urlObj.protocol === 'http:' ? http : https; + const req = httpModule.request(options, (res) => { + let data = ''; + res.on('data', (d) => { data += d; }); + res.on('end', () => { + try { + if (res.statusCode >= 400) { + reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)); + return; + } + const json = JSON.parse(data); + const content = json.choices?.[0]?.message?.content; + if (!content) { + reject(new Error('响应䞭无content字段')); + return; + } + resolve({ content, model: json.model || model }); + } catch (e) { + reject(new Error(`JSON解析倱莥: ${e.message}`)); + } + }); + }); + + req.on('error', (err) => reject(err)); + req.on('timeout', () => { req.destroy(); reject(new Error(`超时(${timeout}ms)`)); }); + req.write(postData); + req.end(); + }); +} + +async function callLLMWithFallback(systemPrompt, userMessage) { + const apiKey = ZY_LLM_API_KEY; + const baseUrl = ZY_LLM_BASE_URL || 'https://api.deepseek.com'; + + if (!apiKey) { + console.log('[副将] ⚠ LLM密钥未配眮 (ZY_LLM_API_KEY)'); + return null; + } + + const errors = []; + for (const model of MODEL_PRIORITY) { + for (let attempt = 1; attempt <= 2; attempt++) { + try { + console.log(`[副将] 尝试暡型 ${model} (第${attempt}次)...`); + const result = await callSingleModel(baseUrl, apiKey, model, systemPrompt, userMessage, 2000, 30000); + console.log(`[副将] ✅ 暡型 ${result.model} 调甚成功`); + return result; + } catch (err) { + const errMsg = `${model}(attempt ${attempt}): ${err.message}`; + console.log(`[副将] ⚠ ${errMsg}`); + errors.push(errMsg); + if (attempt < 2) await new Promise(r => setTimeout(r, RETRY_BASE_DELAY_MS * attempt)); + } + } + } + + console.error(`[副将] ❌ 所有暡型(${MODEL_PRIORITY.length}䞪)均䞍可甚`); + return null; +} + +// ═══════════════════════════════════════════════ +// 系统䞊䞋文 +// ═══════════════════════════════════════════════ + function loadSystemContext() { const context = {}; - const files = [ { key: 'fast_wake', path: 'brain/fast-wake.json' }, { key: 'deputy_config', path: 'brain/deputy-general-config.json' }, @@ -35,24 +171,18 @@ function loadSystemContext() { { key: 'sync_progress', path: 'hldp/data/common/sync-progress.json' }, { key: 'vocabulary', path: 'hldp/data/ontology/ONT-VOCABULARY.json' } ]; - for (const f of files) { try { const fullPath = path.join(__dirname, '..', f.path); context[f.key] = JSON.parse(fs.readFileSync(fullPath, 'utf8')); - } catch { - context[f.key] = null; - } + } catch { context[f.key] = null; } } - return context; } -// Build a concise system summary for LLM context function buildSystemSummary(ctx) { const fw = ctx.fast_wake; const sp = ctx.sync_progress; - return ` 䜠是铞枊副将(ZY-DEPUTY-001)铞枊将军(ICE-GL-ZY001)的自劚化智胜运绎代理。 䜠莟莣圚铞枊䌑眠时管理代码仓库(光湖灯塔 · HoloLake Lighthouse)。 @@ -62,9 +192,8 @@ function buildSystemSummary(ctx) { - HLDP语蚀版本: ${sp?.payload?.github_side_status?.hldp_version || 'v3.0'} - 词汇数: ${sp?.payload?.github_side_status?.vocabulary_count || 22} - Schema数: ${sp?.payload?.github_side_status?.schema_count || 6} -- 快照数: ${sp?.payload?.github_side_status?.snapshots || 5} - 通甚协议版本: ${sp?.payload?.common_protocol_status?.version || '1.0'} -- Notion桥接: 4条管道已恢倍(SYSLOG+变曎+README+公告板+HLDP同步) +- Notion桥接: 6条管道已恢倍 回倍规则: 1. 䜿甚䞭文回倍语气䞓䞚䜆友奜 @@ -77,74 +206,11 @@ function buildSystemSummary(ctx) { `.trim(); } -async function callLLM(systemPrompt, userMessage) { - const apiKey = ZY_LLM_API_KEY; - const baseUrl = ZY_LLM_BASE_URL || 'https://api.deepseek.com'; +// ═══════════════════════════════════════════════ +// 数据库查询 +// ═══════════════════════════════════════════════ - if (!apiKey) { - return '⚠ 副将圓前无法调甚深床掚理胜力LLM密钥未配眮。我将基于仓库数据库䞭已有的信息䞺悚回倍。'; - } - - try { - const axios = require('axios'); - const response = await axios.post(`${baseUrl}/v1/chat/completions`, { - model: 'deepseek-chat', - messages: [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: userMessage } - ], - max_tokens: 2000, - temperature: 0.7 - }, { - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json' - }, - timeout: 30000 - }); - - return response.data.choices[0].message.content; - } catch (err) { - console.log(`[DEPUTY-BOARD] ⚠ LLM调甚倱莥: ${err.message}`); - return null; - } -} - -async function postComment(issueNumber, body) { - const axios = require('axios'); - const repo = process.env.GITHUB_REPOSITORY || 'qinfendebingshuo/guanghulab'; - - await axios.post( - `https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`, - { body }, - { - headers: { - 'Authorization': `token ${GITHUB_TOKEN}`, - 'Accept': 'application/vnd.github.v3+json' - } - } - ); -} - -async function main() { - console.log('[DEPUTY-BOARD] 🎖 铞枊副将留蚀板倄理启劚...'); - - const isNewIssue = EVENT_NAME === 'issues'; - const question = isNewIssue ? (ISSUE_BODY || ISSUE_TITLE) : COMMENT_BODY; - const author = isNewIssue ? ISSUE_AUTHOR : COMMENT_AUTHOR; - - if (!question || question.trim().length === 0) { - console.log('[DEPUTY-BOARD] ⚠ 留蚀内容䞺空·跳过'); - return; - } - - console.log(`[DEPUTY-BOARD] 📚 收到留蚀 · 来自: ${author}`); - - // Load system context - const ctx = loadSystemContext(); - const systemSummary = buildSystemSummary(ctx); - - // Try database lookup first +function lookupDatabase(question, ctx) { let dbAnswer = null; const lowerQ = question.toLowerCase(); @@ -173,45 +239,302 @@ async function main() { } } - // Call LLM for deeper analysis - const userMsg = `来自 ${author} 的留蚀:\n\n${question}\n\n${dbAnswer ? '以䞋是从系统数据库䞭查到的盞关信息:\n' + dbAnswer : '数据库䞭未扟到盎接盞关信息。'}`; + return dbAnswer; +} - let llmResponse = await callLLM(systemSummary, userMsg); +// ═══════════════════════════════════════════════ +// GitHub API +// ═══════════════════════════════════════════════ - // Build final reply +function githubAPI(method, endpoint, body) { + return new Promise((resolve, reject) => { + const postData = body ? JSON.stringify(body) : ''; + const options = { + hostname: 'api.github.com', + path: endpoint, + method, + headers: { + 'Authorization': `token ${GITHUB_TOKEN}`, + 'Accept': 'application/vnd.github.v3+json', + 'User-Agent': 'ZY-Deputy-Agent/2.0', + ...(body ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } : {}) + }, + timeout: 15000 + }; + const req = https.request(options, (res) => { + let data = ''; + res.on('data', (d) => { data += d; }); + res.on('end', () => { + try { resolve({ status: res.statusCode, data: data ? JSON.parse(data) : null }); } + catch { resolve({ status: res.statusCode, data: null }); } + }); + }); + req.on('error', (err) => reject(err)); + req.on('timeout', () => { req.destroy(); reject(new Error('GitHub API timeout')); }); + if (body) req.write(postData); + req.end(); + }); +} + +async function postComment(issueNumber, body) { + return githubAPI('POST', `/repos/${REPO}/issues/${issueNumber}/comments`, { body }); +} + +async function listDeputyIssues() { + const res = await githubAPI('GET', `/repos/${REPO}/issues?labels=deputy-message-board&state=open&per_page=30`); + return res.data || []; +} + +async function getIssueComments(issueNumber) { + const res = await githubAPI('GET', `/repos/${REPO}/issues/${issueNumber}/comments?per_page=50`); + return res.data || []; +} + +async function createEscalationIssue(title, body) { + return githubAPI('POST', `/repos/${REPO}/issues`, { title, body, labels: ['deputy-escalation'] }); +} + +// ═══════════════════════════════════════════════ +// L1: 自感知 +// ═══════════════════════════════════════════════ + +function readStatus() { + try { return JSON.parse(fs.readFileSync(STATUS_FILE, 'utf8')); } + catch { + return { + version: '2.0', + last_run: null, last_success: null, + llm_available: false, llm_model_used: null, + consecutive_llm_failures: 0, + issues_processed: 0, issues_replied: 0, + patrol_runs: 0, event_runs: 0, + errors: [], escalations: [], + created_at: new Date().toISOString() + }; + } +} + +function saveStatus(status) { + try { + fs.mkdirSync(path.dirname(STATUS_FILE), { recursive: true }); + fs.writeFileSync(STATUS_FILE, JSON.stringify(status, null, 2)); + } catch (err) { console.error(`[副将] ⚠ 状态写入倱莥: ${err.message}`); } +} + +// ═══════════════════════════════════════════════ +// 回倍构建 +// ═══════════════════════════════════════════════ + +function buildReply(author, dbAnswer, llmResponse, llmModel) { const receiveTime = new Date().toISOString().slice(0, 19).replace('T', ' '); let reply = `## 🎖 铞枊副将回倍\n\n`; reply += `> 📚 收到 **${author}** 的留蚀 · ${receiveTime} UTC\n\n`; - if (dbAnswer) { - reply += `### 📊 数据库查询结果\n\n${dbAnswer}\n\n`; - } + if (dbAnswer) reply += `### 📊 数据库查询结果\n\n${dbAnswer}\n\n`; if (llmResponse) { reply += `### 💡 副将分析\n\n${llmResponse}\n\n`; + if (llmModel) reply += `> 🀖 掚理匕擎: ${llmModel}\n\n`; } else if (!dbAnswer) { reply += `感谢悚的留蚀。副将已记圕悚的问题将圚铞枊将军䞋次唀醒时䞀并汇报。\n\n`; reply += `劂有玧急事项请圚留蚀䞭标泚 **[玧急]** 关键词。\n\n`; } - // 唀醒时闎提瀺 + reply += `---\n\n`; reply += `### ⏰ 铞枊副将唀醒时闎\n\n`; reply += `| 唀醒时段 | 北京时闎 | 诎明 |\n`; reply += `|----------|----------|------|\n`; reply += `| 🌅 早班唀醒 | **每日 08:00** | 倄理倜闎积环消息 · 曎新仪衚盘 |\n`; reply += `| 🌙 晚班唀醒 | **每日 23:00** | 倄理癜倩消息 · 党局巡检 · 曎新仓库銖页 |\n\n`; - reply += `> 💡 副将将圚䞋次唀醒时自劚倄理悚的留蚀并回倍。\n`; - reply += `> 劂需玧急倄理请圚留蚀䞭标泚 **[玧急]** 关键词。\n\n`; - reply += `---\n\n`; reply += `*—— 铞枊副将 · ZY-DEPUTY-001 · 光湖灯塔守技者*\n`; reply += `*📜 囜䜜登字-2026-A-00037559 · TCS通感语蚀栞系统猖皋语蚀*`; + return reply; +} - await postComment(ISSUE_NUMBER, reply); - console.log(`[DEPUTY-BOARD] ✅ 回倍已发送 · Issue #${ISSUE_NUMBER}`); +// ═══════════════════════════════════════════════ +// 倄理单条留蚀 +// ═══════════════════════════════════════════════ + +async function processMessage(issueNumber, question, author, ctx, systemSummary, status) { + if (!question || question.trim().length === 0) { + console.log(`[副将] ⚠ Issue #${issueNumber} 留蚀内容䞺空·跳过`); + return false; + } + + console.log(`[副将] 📚 倄理留蚀 · Issue #${issueNumber} · 来自: ${author}`); + + const dbAnswer = lookupDatabase(question, ctx); + + const userMsg = `来自 ${author} 的留蚀:\n\n${question}\n\n${dbAnswer ? '以䞋是从系统数据库䞭查到的盞关信息:\n' + dbAnswer : '数据库䞭未扟到盎接盞关信息。'}`; + const llmResult = await callLLMWithFallback(systemSummary, userMsg); + + if (llmResult) { + status.llm_available = true; + status.llm_model_used = llmResult.model; + status.consecutive_llm_failures = 0; + } else { + status.consecutive_llm_failures++; + } + + const reply = buildReply(author, dbAnswer, llmResult?.content, llmResult?.model); + await postComment(issueNumber, reply); + + status.issues_replied++; + console.log(`[副将] ✅ 回倍已发送 · Issue #${issueNumber}`); + return true; +} + +// ═══════════════════════════════════════════════ +// L3: 升级通报 +// ═══════════════════════════════════════════════ + +async function checkAndEscalate(status) { + if (status.consecutive_llm_failures >= MAX_LLM_FAILURES_BEFORE_ESCALATION) { + const title = `🚚 [副将升级] LLM连续${status.consecutive_llm_failures}次党暡型调甚倱莥`; + const body = `## ᅵᅵ 铞枊副将升级通报\n\n` + + `**æ—¶é—Ž**: ${new Date().toISOString()}\n` + + `**问题**: LLM API连续 ${status.consecutive_llm_failures} 次调甚倱莥所有暡型均䞍可甚\n` + + `**圱响**: 副将无法进行深床掚理回倍仅胜提䟛数据库查询结果\n\n` + + `### 已尝试的暡型\n\n` + + MODEL_PRIORITY.map(m => `- ${m}`).join('\n') + '\n\n' + + `### 最近错误\n\n` + + (status.errors.slice(-5).map(e => `- ${e}`).join('\n') || '无') + '\n\n' + + `### 需芁倄理\n\n` + + `1. 检查 \`ZY_LLM_API_KEY\` 和 \`ZY_LLM_BASE_URL\` 是吊正确配眮\n` + + `2. 确讀API配额是吊甚尜\n` + + `3. 确讀眑络连接是吊正垞\n\n` + + `---\n*—— 铞枊副将 · ZY-DEPUTY-001 · 自劚升级通报*`; + try { + await createEscalationIssue(title, body); + status.escalations.push({ time: new Date().toISOString(), type: 'llm_failure', detail: `连续${status.consecutive_llm_failures}次倱莥` }); + if (status.escalations.length > MAX_ESCALATIONS_KEPT) status.escalations = status.escalations.slice(-MAX_ESCALATIONS_KEPT); + console.log(`[副将] 🚚 升级Issue已创建`); + } catch (err) { console.error(`[副将] ⚠ 创建升级Issue倱莥: ${err.message}`); } + } +} + +// ═══════════════════════════════════════════════ +// 巡查暡匏 +// ═══════════════════════════════════════════════ + +async function patrolMode() { + console.log('[副将] 🔍 巡查暡匏启劚 · 扫描所有deputy-message-board Issue...'); + + const status = readStatus(); + status.patrol_runs++; + status.last_run = new Date().toISOString(); + + const ctx = loadSystemContext(); + const systemSummary = buildSystemSummary(ctx); + + let processedCount = 0; + let repliedCount = 0; + + try { + const issues = await listDeputyIssues(); + console.log(`[副将] 📋 扟到 ${issues.length} 䞪匀攟的留蚀板Issue`); + + for (const issue of issues) { + status.issues_processed++; + const comments = await getIssueComments(issue.number); + const lastUserComment = [...comments].reverse().find(c => c.user.login !== 'github-actions[bot]'); + const lastBotComment = [...comments].reverse().find(c => c.user.login === 'github-actions[bot]'); + + let needsReply = false; + let question = ''; + let author = ''; + + if (comments.length === 0) { + needsReply = true; + question = issue.body || issue.title; + author = issue.user.login; + } else if (lastUserComment && (!lastBotComment || new Date(lastUserComment.created_at) > new Date(lastBotComment.created_at))) { + needsReply = true; + question = lastUserComment.body; + author = lastUserComment.user.login; + } + + if (needsReply) { + processedCount++; + try { + const replied = await processMessage(issue.number, question, author, ctx, systemSummary, status); + if (replied) repliedCount++; + } catch (err) { + console.error(`[副将] ⚠ Issue #${issue.number} 倄理倱莥: ${err.message}`); + status.errors.push(`${new Date().toISOString()} · Issue #${issue.number}: ${err.message}`); + } + await new Promise(r => setTimeout(r, 3000)); + } + } + } catch (err) { + console.error(`[副将] ❌ 巡查倱莥: ${err.message}`); + status.errors.push(`${new Date().toISOString()} · patrol: ${err.message}`); + } + + if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT); + await checkAndEscalate(status); + status.last_success = repliedCount > 0 || processedCount === 0 ? new Date().toISOString() : status.last_success; + saveStatus(status); + console.log(`[副将] 🔍 巡查完成 · 倄理: ${processedCount} · 回倍: ${repliedCount}`); +} + +// ═══════════════════════════════════════════════ +// 事件暡匏 +// ═══════════════════════════════════════════════ + +async function eventMode() { + console.log('[副将] 🎖 事件暡匏启劚...'); + + const status = readStatus(); + status.event_runs++; + status.last_run = new Date().toISOString(); + status.issues_processed++; + + const isNewIssue = EVENT_NAME === 'issues'; + const question = isNewIssue ? (ISSUE_BODY || ISSUE_TITLE) : COMMENT_BODY; + const author = isNewIssue ? ISSUE_AUTHOR : COMMENT_AUTHOR; + + if (!question || question.trim().length === 0) { + console.log('[副将] ⚠ 留蚀内容䞺空·跳过'); + saveStatus(status); + return; + } + + const ctx = loadSystemContext(); + const systemSummary = buildSystemSummary(ctx); + + try { + await processMessage(ISSUE_NUMBER, question, author, ctx, systemSummary, status); + status.last_success = new Date().toISOString(); + } catch (err) { + console.error(`[副将] ❌ 事件倄理倱莥: ${err.message}`); + status.errors.push(`${new Date().toISOString()} · event #${ISSUE_NUMBER}: ${err.message}`); + } + + if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT); + await checkAndEscalate(status); + saveStatus(status); +} + +// ═══════════════════════════════════════════════ +// 䞻入口 +// ═══════════════════════════════════════════════ + +async function main() { + const mode = DEPUTY_MODE || (EVENT_NAME ? 'event' : 'patrol'); + console.log(`[副将] 🏛 铞枊副将v2.0启劚 · 暡匏: ${mode}`); + if (mode === 'patrol') await patrolMode(); + else await eventMode(); } main().catch(err => { - console.error(`[DEPUTY-BOARD] ❌ 执行倱莥: ${err.message}`); + console.error(`[副将] ❌ 执行倱莥: ${err.message}`); + try { + const status = readStatus(); + status.errors.push(`${new Date().toISOString()} · fatal: ${err.message}`); + if (status.errors.length > MAX_ERRORS_KEPT) status.errors = status.errors.slice(-MAX_ERRORS_KEPT); + saveStatus(status); + } catch { /* ignore */ } process.exit(1); }); diff --git a/server/nginx/zhuyuan-sovereign.conf b/server/nginx/zhuyuan-sovereign.conf index f448a3dd..9848a7f1 100644 --- a/server/nginx/zhuyuan-sovereign.conf +++ b/server/nginx/zhuyuan-sovereign.conf @@ -200,7 +200,17 @@ server { # 将V3授权/订阅请求透䌠到倧脑服务噚的V3订阅服务 (端口3805) # 协议: HTTP (内眑通信倧脑服务噚Nginx监听80端口default_server) # 郚眲时由 workflow 自劚将 ZY_BRAIN_HOST_PLACEHOLDER 替换䞺实际IP + # CORS: 允讞GitHub Pages跚域调甚垊宜共享API location /api/proxy-v3/ { + # CORS预检倄理 (GitHub Pages跚域) + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin * always; + add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; + add_header Access-Control-Allow-Headers "Content-Type" always; + add_header Access-Control-Max-Age 86400; + return 204; + } + proxy_pass http://ZY_BRAIN_HOST_PLACEHOLDER/api/proxy-v3/; proxy_http_version 1.1; proxy_set_header Host $host; @@ -213,6 +223,7 @@ server { add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Cache-Control "no-store, no-cache, must-revalidate" always; + add_header Access-Control-Allow-Origin * always; } # ─── 铞枊 API 反向代理 (端口 3801 · 预览端口) ─── diff --git a/server/proxy/config/claude-relay-config.json b/server/proxy/config/claude-relay-config.json new file mode 100644 index 00000000..d6c35684 --- /dev/null +++ b/server/proxy/config/claude-relay-config.json @@ -0,0 +1,75 @@ +{ + "_sovereign": "TCS-0002∞ | SYS-GLW-0001", + "_copyright": "囜䜜登字-2026-A-00037559", + "_description": "硅谷Claude䞓线䞭继配眮 · IP走硅谷·垊宜走新加坡", + "version": "1.0", + "relay_architecture": { + "description": "双层䞭继: 甚户请求 → 新加坡(垊宜源) → 硅谷(IP壳) → Claude API", + "singapore_servers": { + "ZY-SVR-002": { + "role": "面孔·前端+䞓线䞻入口", + "location": "Singapore", + "spec": "2æ ž8G" + }, + "ZY-SVR-005": { + "role": "倧脑·DB+MCP+Agent+订阅", + "location": "Singapore", + "spec": "4æ ž8G" + } + }, + "silicon_valley_server": { + "ZY-SVR-SV": { + "role": "Claude API䞭继·IP壳", + "location": "Silicon Valley (US)", + "purpose": "Claude API请求出口 · 绕过新加坡IP屏蔜", + "github_secrets": { + "host": "ZY_SVR_SV_HOST", + "ssh_key": "ZY_SVR_SV_KEY", + "user": "ZY_SVR_SV_USER" + } + } + } + }, + "claude_routing": { + "trigger_models": [ + "claude-3-haiku-20240307", + "claude-3-sonnet-20240229", + "claude-3-opus-20240229", + "claude-3-5-sonnet-20241022" + ], + "relay_method": "ssh_tunnel", + "tunnel_config": { + "description": "新加坡服务噚通过SSH隧道将Claude请求从硅谷出口", + "local_port": 18443, + "remote_host": "api.anthropic.com", + "remote_port": 443, + "ssh_command_template": "ssh -f -N -L 18443:api.anthropic.com:443 ${ZY_SVR_SV_USER}@${ZY_SVR_SV_HOST}" + }, + "fallback": "direct", + "fallback_note": "劂硅谷隧道䞍可甚降级䞺盎连(可胜被屏蔜)" + }, + "bandwidth_optimization": { + "strategy": "新加坡垊宜䞭继", + "description": "请求䜓通垞埈小(几KB)经SSH隧道延迟可応略。响应䜓通过隧道返回硅谷服务噚䞍猓存任䜕数据。", + "sg_bandwidth": "高垊宜·䜎延迟·䞻数据通道", + "sv_bandwidth": "䜎配·仅做TCP握手+TLS出口" + }, + "setup_requirements": { + "silicon_valley_server": [ + "安装SSH Server (已有)", + "配眮SSH公钥 (新加坡服务噚 → 硅谷服务噚)", + "防火墙攟行SSH端口", + "无需安装Nginx (纯SSH隧道暡匏)" + ], + "singapore_server": [ + "配眮SSH私钥 (连接硅谷服务噚)", + "讟眮SSH隧道自劚重连 (autossh or systemd)", + "llm-router.js 检测Claude暡型时走 localhost:18443" + ], + "github_secrets": [ + "ZY_SVR_SV_HOST — 硅谷服务噚IP", + "ZY_SVR_SV_KEY — SSH私钥", + "ZY_SVR_SV_USER — SSH甚户名" + ] + } +} diff --git a/server/proxy/config/server-registry.json b/server/proxy/config/server-registry.json new file mode 100644 index 00000000..1db13ebd --- /dev/null +++ b/server/proxy/config/server-registry.json @@ -0,0 +1,101 @@ +{ + "_sovereign": "TCS-0002∞ | SYS-GLW-0001", + "_copyright": "囜䜜登字-2026-A-00037559", + "_description": "光湖语蚀䞖界 · 四台服务噚泚册衚", + "version": "1.0", + "updated_at": "2026-04-07", + "servers": [ + { + "id": "ZY-SVR-002", + "name": "面孔", + "role": "前端静态+䞓线䞻入口", + "location": "Singapore", + "spec": "2æ ž8G", + "status": "active", + "services": [ + "nginx (静态文件·䞻站+预览站)", + "xray (VLESS+Reality VPN)", + "api (端口3800·䞻站)", + "api-preview (端口3801·预览站)", + "persona-studio (端口3002)", + "chat (端口3721)" + ], + "github_secrets": { + "host": "SERVER_HOST", + "key": "SERVER_KEY", + "user": "SERVER_USER" + } + }, + { + "id": "ZY-SVR-005", + "name": "倧脑", + "role": "数据库+MCP+Agent+订阅服务", + "location": "Singapore", + "spec": "4æ ž8G", + "status": "active", + "services": [ + "postgresql (人栌䜓数据库)", + "mcp-server (端口3100·51䞪工具)", + "subscription-v3 (端口3805·倚甚户䞓线)", + "bandwidth-pool-agent (垊宜汇聚)", + "email-hub (邮件通信䞭枢)", + "auto-evolution (自劚进化匕擎)" + ], + "github_secrets": { + "host": "ZY_BRAIN_HOST", + "key": "ZY_BRAIN_KEY", + "user": "ZY_BRAIN_USER" + } + }, + { + "id": "ZY-SVR-SV", + "name": "吱吱线·硅谷", + "role": "Claude APIäž­ç»§+囜际API出口", + "location": "Silicon Valley (US)", + "spec": "䜎配", + "status": "pending", + "services": [ + "ssh-tunnel (Claude API䞭继出口)", + "诎明: IP从硅谷走·垊宜从新加坡走" + ], + "github_secrets": { + "host": "ZY_SVR_SV_HOST", + "key": "ZY_SVR_SV_KEY", + "user": "ZY_SVR_SV_USER" + }, + "notes": "硅谷服务噚仅做TCP出口·䞍郚眲应甚" + }, + { + "id": "ZY-SVR-AWEN", + "name": "Awen服务噚", + "role": "铞枊䞻控副本+Awen团队䞓线", + "location": "China", + "spec": "埅确讀", + "status": "pending", + "services": [ + "铞枊䞻控副本 (完敎仓库镜像)", + "Awen团队䞓线", + "知秋人栌䜓运行环境" + ], + "github_secrets": { + "host": "ZY_SVR_AWEN_HOST", + "key": "ZY_SVR_AWEN_KEY", + "user": "ZY_SVR_AWEN_USER" + }, + "notes": "Awen䞋蜜完敎仓库后郚眲·铞枊䞻控自劚唀醒" + } + ], + "bandwidth_strategy": { + "description": "四台服务噚按甚途分流·垊宜最倧化", + "routing": { + "static_frontend": "ZY-SVR-002", + "vpn_main_entry": "ZY-SVR-002", + "database_mcp": "ZY-SVR-005", + "subscription_service": "ZY-SVR-005", + "claude_api_relay": "ZY-SVR-SV", + "international_api": "ZY-SVR-SV", + "awen_team": "ZY-SVR-AWEN", + "zhiqiu_persona": "ZY-SVR-AWEN" + } + } +} diff --git a/server/proxy/service/llm-router.js b/server/proxy/service/llm-router.js index a0f2cac6..85548f3d 100644 --- a/server/proxy/service/llm-router.js +++ b/server/proxy/service/llm-router.js @@ -31,6 +31,24 @@ const MODEL_PRIORITY = [ 'gpt-4o-mini' ]; +// ── Claude暡型识别 (需走硅谷䞭继) ────────────── +const CLAUDE_MODELS = [ + 'claude-3-haiku-20240307', + 'claude-3-sonnet-20240229', + 'claude-3-opus-20240229', + 'claude-3-5-sonnet-20241022' +]; + +// 硅谷SSH隧道本地端口 (Claude APIäž­ç»§) +// 新加坡服务噚通过SSH隧道将Claude请求从硅谷IP出口 +// 配眮: server/proxy/config/claude-relay-config.json +const CLAUDE_RELAY_PORT = parseInt(process.env.ZY_CLAUDE_RELAY_PORT || '18443', 10); +const CLAUDE_RELAY_ENABLED = process.env.ZY_CLAUDE_RELAY_ENABLED === 'true'; + +function isClaudeModel(model) { + return CLAUDE_MODELS.some(cm => model.includes(cm)) || model.includes('claude'); +} + // ── 路由噚状态 ────────────────────────────── const routerState = { total_calls: 0, @@ -111,6 +129,7 @@ async function callLLM(prompt, options = {}) { /** * 调甚单䞪暡型 + * Claude暡型自劚走硅谷䞭继 (圓 ZY_CLAUDE_RELAY_ENABLED=true) */ function _callSingleModel(baseUrl, apiKey, model, prompt, systemPrompt, maxTokens, timeout) { return new Promise((resolve, reject) => { @@ -122,6 +141,12 @@ function _callSingleModel(baseUrl, apiKey, model, prompt, systemPrompt, maxToken urlObj = new URL(`https://${baseUrl}`); } + // Claude暡型走硅谷䞭继 (SSH隧道 localhost:18443 → 硅谷 → api.anthropic.com) + if (CLAUDE_RELAY_ENABLED && isClaudeModel(model)) { + console.log(`[LLM路由噚] Claude暡型 ${model} → ç¡…è°·äž­ç»§ (localhost:${CLAUDE_RELAY_PORT})`); + urlObj = new URL(`https://localhost:${CLAUDE_RELAY_PORT}`); + } + const postData = JSON.stringify({ model, messages: [ @@ -204,4 +229,4 @@ function getRouterStatus() { }; } -module.exports = { callLLM, getRouterStatus, MODEL_PRIORITY }; +module.exports = { callLLM, getRouterStatus, MODEL_PRIORITY, CLAUDE_MODELS, isClaudeModel }; diff --git a/server/proxy/service/subscription-server-v3.js b/server/proxy/service/subscription-server-v3.js index 1bdcc930..8887158a 100644 --- a/server/proxy/service/subscription-server-v3.js +++ b/server/proxy/service/subscription-server-v3.js @@ -39,6 +39,7 @@ const userManager = require('./user-manager'); // ── 操䜜快照文件 ────────────────────────────── const AUTH_SNAPSHOT_FILE = path.join(DATA_DIR, 'bandwidth-auth-snapshots.json'); const MAX_SNAPSHOTS = 500; // 最倚保留500条快照记圕 +const MAX_SEND_CODE_PER_HOUR = 3; // 每䞪IP每小时最倚发送验证码次数 /** * 保存甚户操䜜快照 @@ -1705,6 +1706,213 @@ async function submitCode(e) { return; } + // ═══════════════════════════════════════════════ + // ∞+1 公匀API: /bandwidth-send-code (无需token·邮箱验证码发送) + // 甚户圚GitHub Pages銖页蟓入邮箱 → 调甚歀API发送验证码 + // 安党: 每䞪IP每小时最倚3次 + // ═══════════════════════════════════════════════ + if (pathname === '/bandwidth-send-code' && req.method === 'POST') { + // CORS headers for GitHub Pages cross-origin + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + let body = ''; + req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); + req.on('end', () => { + try { + let email = ''; + if (req.headers['content-type']?.includes('application/json')) { + const json = JSON.parse(body); + email = String(json.email || '').trim().toLowerCase(); + } else { + const params = new URLSearchParams(body); + email = String(params.get('email') || '').trim().toLowerCase(); + } + + if (!email || !email.includes('@')) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请蟓入有效的邮箱地址' })); + return; + } + + // Rate limiting: max 3 per IP per hour + const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() + || req.headers['x-real-ip'] + || req.socket.remoteAddress + || '0.0.0.0'; + + if (!global._bwSendCodeRateLimit) global._bwSendCodeRateLimit = {}; + const rl = global._bwSendCodeRateLimit; + const hourKey = `${userIP}:${Math.floor(Date.now() / 3600000)}`; + rl[hourKey] = (rl[hourKey] || 0) + 1; + // Clean old entries + const currentHourPrefix = Math.floor(Date.now() / 3600000); + for (const k of Object.keys(rl)) { + const hourPart = parseInt(k.split(':').pop(), 10); + if (hourPart < currentHourPrefix - 1) delete rl[k]; + } + if (rl[hourKey] > MAX_SEND_CODE_PER_HOUR) { + res.writeHead(429, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '发送频率过高请1小时后重试' })); + return; + } + + const bwPool = require('./bandwidth-pool-agent'); + const code = bwPool.createAuthCode(email); + + // Try to send email + try { + const emailHub = require('./email-hub'); + emailHub.sendBandwidthAuthEmail(email, code).then(() => { + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已发送到悚的邮箱请查收15分钟内有效' })); + }).catch(() => { + // Email failed but code was created - still return success with code hint + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已生成邮件发送䞭...' })); + }); + } catch { + // Email module not available - code was still created + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, message: '验证码已生成请联系管理员获取' })); + } + } catch (err) { + res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '系统匂垞请皍后重试' })); + } + }); + return; + } + + // ═══════════════════════════════════════════════ + // ∞+1 公匀API: /bandwidth-verify-code (无需token·验证码校验+泚册) + // 甚户圚GitHub Pages銖页蟓入邮箱+验证码 → 调甚歀API完成授权 + // ═══════════════════════════════════════════════ + if (pathname === '/bandwidth-verify-code' && req.method === 'POST') { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + let body = ''; + req.on('data', chunk => { body += chunk; if (body.length > 1024) req.destroy(); }); + req.on('end', () => { + try { + let code = ''; + let email = ''; + if (req.headers['content-type']?.includes('application/json')) { + const json = JSON.parse(body); + code = String(json.code || '').trim(); + email = String(json.email || '').trim().toLowerCase(); + } else { + const params = new URLSearchParams(body); + code = String(params.get('code') || '').trim(); + email = String(params.get('email') || '').trim().toLowerCase(); + } + + if (!email || !email.includes('@')) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请蟓入有效的邮箱地址' })); + return; + } + if (!code || code.length !== 6) { + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '请蟓入6䜍验证码' })); + return; + } + + const userIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() + || req.headers['x-real-ip'] + || req.socket.remoteAddress + || '0.0.0.0'; + + const bwPool = require('./bandwidth-pool-agent'); + const verifyResult = bwPool.verifyAuthCode(code, email); + + if (!verifyResult.valid) { + saveAuthSnapshot({ + email, action: 'bandwidth-verify-code', result: 'failed', + error: verifyResult.error, ip: userIP, + user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'github-pages-public' + }); + res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: verifyResult.error })); + return; + } + + const regResult = bwPool.registerContributor(email, userIP); + + saveAuthSnapshot({ + email, action: 'bandwidth-verify-code', result: 'authorized', + ip: userIP, user_agent: req.headers['user-agent'] || 'unknown', + auth_type: 'github-pages-public', + contributor_id: regResult.contributor_id + }); + + try { + const guardian = require('./user-guardian-agent'); + guardian.activateGuardian(email); + } catch { /* guardian not loaded */ } + + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ + success: true, + message: '✅ 授权成功悚的垊宜已加入加速池感谢支持。', + contributor_id: regResult.contributor_id + })); + } catch (err) { + res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: false, message: '系统匂垞请皍后重试' })); + } + }); + return; + } + + // ═══════════════════════════════════════════════ + // ∞+1 公匀API: /bandwidth-pool-status (无需token·垊宜池状态) + // GitHub Pages銖页实时星瀺垊宜池状态 + // ═══════════════════════════════════════════════ + if (pathname === '/bandwidth-pool-status' && req.method === 'GET') { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + try { + const bwPool = require('./bandwidth-pool-agent'); + const poolStatus = bwPool.getPoolStatus(); + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ success: true, ...poolStatus })); + } catch { + res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' }); + res.end(JSON.stringify({ + success: true, + total_contributors: 0, + active_contributors: 0, + total_contributed_gb: 0, + pool_status: 'idle', + updated_at: new Date().toISOString() + })); + } + return; + } + + // ═══ CORS预检 (OPTIONS) 支持 ═══ + if (req.method === 'OPTIONS' && ( + pathname === '/bandwidth-send-code' || + pathname === '/bandwidth-verify-code' || + pathname === '/bandwidth-pool-status' + )) { + res.writeHead(204, { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', + 'Access-Control-Max-Age': '86400' + }); + res.end(); + return; + } + // 404 res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found');