Merge pull request #309 from qinfendebingshuo/copilot/setup-vpn-singapore-node

feat: Deputy agent activation, bandwidth sharing UI on Pages, Silicon Valley Claude relay
This commit is contained in:
冰朔 2026-04-08 01:49:01 +08:00 committed by GitHub
commit ad788cb12b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1157 additions and 102 deletions

View File

@ -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

View File

@ -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*

16
data/deputy-status.json Normal file
View File

@ -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状态文件 · 每次运行自动更新"
}

View File

@ -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}
</style>
</head>
<body>
@ -894,6 +924,23 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
</div>
</div>
<!-- Quick Actions: Bandwidth Sharing + Deputy -->
<div class="sys-section">
<div class="sys-section-title">⚡ 快速入口</div>
<div style="display:flex;flex-direction:column;gap:8px">
<div class="sys-team-card" style="cursor:pointer" onclick="go('bandwidth')">
<div class="sys-team-av" style="background:linear-gradient(135deg,#22d3ee,#06b6d4)">🌊</div>
<div><div class="sys-team-nm">带宽共享加速池</div><div class="sys-team-role">共享你的带宽 · 加速所有人</div></div>
</div>
<a href="https://github.com/qinfendebingshuo/guanghulab/issues/new?labels=deputy-message-board&title=给铸渊副将留言" style="text-decoration:none;color:inherit" target="_blank">
<div class="sys-team-card" style="cursor:pointer">
<div class="sys-team-av" style="background:linear-gradient(135deg,#fb923c,#f97316)">🎖️</div>
<div><div class="sys-team-nm">铸渊副将留言板</div><div class="sys-team-role">每日08:00/23:00唤醒 · LLM深度推理回复</div></div>
</div>
</a>
</div>
</div>
<!-- Version History -->
<div class="sys-section">
<div class="sys-section-title">📋 版本历史</div>
@ -1131,6 +1178,63 @@ label{display:block;font-size:12px;color:var(--dim);margin-bottom:4px;font-weigh
</div>
</div>
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Bandwidth Sharing (带宽共享) -->
<!-- ═══════════════════════════════════════ -->
<div class="page" id="pg-bandwidth" data-state="hidden">
<div class="bar">
<button class="btn" onclick="go('home')">← 首页</button>
<div class="bar-title">🌊 带宽共享</div>
<div class="bar-right"><span class="dot"></span></div>
</div>
<div class="content">
<div class="bw-hero">
<h2>🌊 带宽共享加速池</h2>
<p>共享你的带宽 · 加速所有人</p>
</div>
<div class="bw-stats">
<div class="bw-stat"><div class="num" id="bwContributors"></div><div class="label">贡献者</div></div>
<div class="bw-stat"><div class="num" id="bwActive"></div><div class="label">活跃</div></div>
<div class="bw-stat"><div class="num" id="bwContributed"></div><div class="label">总贡献(GB)</div></div>
</div>
<div class="bw-card">
<h3>📧 第一步 · 发送验证码</h3>
<div style="margin-bottom:10px">
<input type="email" class="bw-input" id="bwEmail" placeholder="输入你的QQ邮箱" autocomplete="email">
</div>
<button class="bw-btn" id="bwSendCode" onclick="bwSendCode()">发送验证码</button>
<div class="bw-result" id="bwSendResult"></div>
</div>
<div class="bw-card">
<h3>🔑 第二步 · 输入验证码</h3>
<div style="margin-bottom:10px">
<input type="text" class="bw-input bw-code-input" id="bwCode" maxlength="6" placeholder="6位验证码" inputmode="numeric" pattern="[0-9]*">
</div>
<div class="bw-consent" onclick="var cb=document.getElementById('bwConsent');cb.checked=!cb.checked">
<input type="checkbox" id="bwConsent">
<span>我自愿共享多余带宽加入加速池。系统优先保障我的专属通道,仅将我用不上的资源共享给其他成员。我可以随时退出。</span>
</div>
<button class="bw-btn" id="bwVerify" onclick="bwVerifyCode()">提交验证码</button>
<div class="bw-result" id="bwVerifyResult"></div>
</div>
<div class="bw-info">
<strong style="color:var(--text);display:block;margin-bottom:6px">💡 带宽共享说明</strong>
✅ 提供了验证码的用户 → 优先保障你的专属通道<br>
✅ 你用不上的资源才会被共享 → 不抢你的带宽<br>
✅ 随时可退出 → 你有完全的控制权<br>
⚠️ 验证码15分钟内有效 · 一次性使用
</div>
<div style="text-align:center;padding:20px 0;color:var(--dim);font-size:.75em">
<p>© 2026 光湖语言世界 · 国作登字-2026-A-00037559</p>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════ -->
<!-- PAGE: Workspace (个人码字工作台) -->
<!-- ═══════════════════════════════════════ -->
@ -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();
};
}
})();
</script>
</body>

View File

@ -4,12 +4,26 @@
* 📜 Copyright: 国作登字-2026-A-00037559
*
*
* 铸渊副将·留言板自动回复引擎
* 职责: 接收人类留言 查询数据库 调用LLM 回复
* 铸渊副将·留言板活体Agent引擎 v2.0
*
* 三层自愈机制:
* L1 自感知 记录运行状态对比预期
* L2 自修复 LLM多模型自动降级 (deepseekqwenmoonshotzhipu)
* 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 = `## <20><> 铸渊副将升级通报\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);
});

View File

@ -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 · 预览端口) ───

View File

@ -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用户名"
]
}
}

View File

@ -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"
}
}
}

View File

@ -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 };

View File

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