[GLADA] GLADA-CAB-20260415-001 step4 1. 新增agent-training模块实现双侧Agent训练功能_2. 增强agent-handshake模块支持COS存储和自动连接恢复_3. 在server.js中新增训练相关API端点_4. 更新package.json添加训练相关脚本命令

This commit is contained in:
root 2026-05-29 20:13:46 +08:00
parent f05d89417a
commit ffc3120f9a
4 changed files with 342 additions and 197 deletions

View File

@ -1,70 +1,107 @@
'use strict';
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const COS = require('cos-nodejs-sdk-v5');
const { execSync } = require('child_process');
// ─── 常量 ───
const HANDSHAKE_EXPIRE_MS = 5 * 60 * 1000; // 5分钟
const HEARTBEAT_INTERVAL_MS = 30 * 1000; // 30秒
const CONNECTION_EXPIRE_MS = 24 * 60 * 60 * 1000; // 24小时
const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒
const HEARTBEAT_TIMEOUT = 3 * HEARTBEAT_INTERVAL; // 90秒
// ─── COS 客户端 ───
const cos = new COS({
SecretId: process.env.COS_SECRET_ID,
SecretKey: process.env.COS_SECRET_KEY
});
// ─── 内存存储 ───
const pendingHandshakes = new Map(); // handshakeId → { initiatorId, targetId, createdAt, tempKey }
const activeConnections = new Map(); // connectionId → { initiatorId, targetId, createdAt, lastHeartbeat, sharedKey }
const activeConnections = new Map(); // connectionId → { initiatorId, targetId, lastHeartbeat, modelVersion }
// ─── 新增: 连接状态持久化 ───
const CONNECTION_BUCKET = 'agent-connections-1250000000';
const CONNECTION_REGION = 'ap-guangzhou';
/**
* 保存连接状态到COS
*/
async function saveConnectionState(connectionId, state) {
await cos.putObject({
Bucket: CONNECTION_BUCKET,
Region: CONNECTION_REGION,
Key: `connections/${connectionId}.json`,
Body: JSON.stringify(state)
});
}
/**
* 加载连接状态
*/
async function loadConnectionState(connectionId) {
try {
const { Body } = await cos.getObject({
Bucket: CONNECTION_BUCKET,
Region: CONNECTION_REGION,
Key: `connections/${connectionId}.json`
});
return JSON.parse(Body.toString());
} catch (err) {
if (err.statusCode === 404) return null;
throw err;
}
}
// ─── 原有握手逻辑保持不变 ───
function generateHandshakeId() {
return crypto.randomBytes(16).toString('hex');
}
function generateTempKey() {
return crypto.randomBytes(32).toString('hex');
}
function generateConnectionId() {
return crypto.randomBytes(16).toString('hex');
}
/**
* 初始化握手
* @param {string} initiatorId - 发起方Agent ID
* @param {string} targetId - 目标Agent ID
* @param {string} token - 会话token
* @returns {{handshakeId: string, tempKey: string, expiresAt: string}}
*/
function initiateHandshake(initiatorId, targetId, token) {
if (!initiatorId || !targetId || !token) {
throw new Error('缺少必要参数');
const emailAuth = require('./email-auth');
if (!emailAuth.validateSession(token).valid) {
throw new Error('无效的会话令牌');
}
// 验证会话token
// TODO: 集成现有会话验证系统
const handshakeId = uuidv4();
const tempKey = crypto.randomBytes(32).toString('hex');
const expiresAt = Date.now() + HANDSHAKE_EXPIRE_MS;
const handshakeId = generateHandshakeId();
const tempKey = generateTempKey();
pendingHandshakes.set(handshakeId, {
initiatorId,
targetId,
tempKey,
createdAt: Date.now(),
expiresAt
tempKey
});
return {
handshakeId,
tempKey,
expiresAt: new Date(expiresAt).toISOString()
};
return { handshakeId, tempKey };
}
/**
* 完成握手
* @param {string} handshakeId - 握手ID
* @param {string} targetId - 目标Agent ID
* @param {string} targetKey - 目标Agent临时密钥
* @param {string} token - 会话token
* @returns {{connectionId: string, sharedKey: string, expiresAt: string}}
*/
function completeHandshake(handshakeId, targetId, targetKey, token) {
if (!handshakeId || !targetId || !targetKey || !token) {
throw new Error('缺少必要参数');
const emailAuth = require('./email-auth');
if (!emailAuth.validateSession(token).valid) {
throw new Error('无效的会话令牌');
}
const handshake = pendingHandshakes.get(handshakeId);
if (!handshake) {
throw new Error('无效的握手ID');
throw new Error('握手ID无效或已过期');
}
if (Date.now() > handshake.expiresAt) {
if (Date.now() - handshake.createdAt > HANDSHAKE_EXPIRE_MS) {
pendingHandshakes.delete(handshakeId);
throw new Error('握手已过期');
}
@ -73,115 +110,120 @@ function completeHandshake(handshakeId, targetId, targetKey, token) {
throw new Error('目标Agent不匹配');
}
// 验证临时密钥
if (handshake.tempKey !== targetKey) {
throw new Error('无效的临时密钥');
throw new Error('临时密钥验证失败');
}
// 生成共享密钥
const connectionId = uuidv4();
const sharedKey = crypto.randomBytes(32).toString('hex');
const expiresAt = Date.now() + CONNECTION_EXPIRE_MS;
activeConnections.set(connectionId, {
initiatorId: handshake.initiatorId,
targetId: handshake.targetId,
sharedKey,
createdAt: Date.now(),
lastHeartbeat: Date.now(),
expiresAt
});
// 清理握手
pendingHandshakes.delete(handshakeId);
return {
connectionId,
sharedKey,
expiresAt: new Date(expiresAt).toISOString()
const connectionId = generateConnectionId();
const now = Date.now();
const connection = {
initiatorId: handshake.initiatorId,
targetId: handshake.targetId,
lastHeartbeat: now,
modelVersion: null
};
activeConnections.set(connectionId, connection);
saveConnectionState(connectionId, connection);
return { connectionId };
}
/**
* 心跳检测
* @param {string} connectionId - 连接ID
* @param {string} token - 会话token
* @returns {{alive: boolean, expiresAt: string}}
*/
function heartbeat(connectionId, token) {
if (!connectionId || !token) {
throw new Error('缺少必要参数');
// ─── 新增: 模型版本更新 ───
function updateModelVersion(agentId, modelKey) {
for (const [connId, conn] of activeConnections) {
if (conn.initiatorId === agentId || conn.targetId === agentId) {
conn.modelVersion = modelKey;
saveConnectionState(connId, conn);
}
}
const connection = activeConnections.get(connectionId);
if (!connection) {
throw new Error('无效的连接ID');
}
if (Date.now() > connection.expiresAt) {
activeConnections.delete(connectionId);
throw new Error('连接已过期');
}
connection.lastHeartbeat = Date.now();
return {
alive: true,
expiresAt: new Date(connection.expiresAt).toISOString()
};
}
/**
* 断开连接
* @param {string} connectionId - 连接ID
* @param {string} token - 会话token
* @returns {{success: boolean}}
*/
function disconnect(connectionId, token) {
if (!connectionId || !token) {
throw new Error('缺少必要参数');
}
activeConnections.delete(connectionId);
return { success: true };
}
/**
* 验证连接
* @param {string} connectionId - 连接ID
* @param {string} sharedKey - 共享密钥
* @returns {{valid: boolean, initiatorId?: string, targetId?: string}}
*/
function validateConnection(connectionId, sharedKey) {
if (!connectionId || !sharedKey) {
return { valid: false };
// ─── 原有心跳和断开逻辑保持不变 ───
function heartbeat(connectionId, token) {
const emailAuth = require('./email-auth');
if (!emailAuth.validateSession(token).valid) {
throw new Error('无效的会话令牌');
}
const connection = activeConnections.get(connectionId);
if (!connection) {
return { valid: false };
throw new Error('连接不存在');
}
if (Date.now() > connection.expiresAt) {
activeConnections.delete(connectionId);
return { valid: false };
}
connection.lastHeartbeat = Date.now();
saveConnectionState(connectionId, connection);
if (connection.sharedKey !== sharedKey) {
return { valid: false };
}
return {
valid: true,
initiatorId: connection.initiatorId,
targetId: connection.targetId
};
return { success: true };
}
function disconnect(connectionId, token) {
const emailAuth = require('./email-auth');
if (!emailAuth.validateSession(token).valid) {
throw new Error('无效的会话令牌');
}
if (!activeConnections.has(connectionId)) {
throw new Error('连接不存在');
}
activeConnections.delete(connectionId);
// 异步删除COS中的状态
cos.deleteObject({
Bucket: CONNECTION_BUCKET,
Region: CONNECTION_REGION,
Key: `connections/${connectionId}.json`
}).catch(console.error);
return { success: true };
}
// ─── 新增: 自动恢复连接 ───
async function restoreConnections() {
try {
const { Contents } = await cos.listObjects({
Bucket: CONNECTION_BUCKET,
Region: CONNECTION_REGION,
Prefix: 'connections/'
});
if (!Contents) return;
for (const item of Contents) {
const connectionId = item.Key.split('/')[1].replace('.json', '');
const state = await loadConnectionState(connectionId);
if (state && Date.now() - state.lastHeartbeat < HEARTBEAT_TIMEOUT) {
activeConnections.set(connectionId, state);
} else {
// 清理过期的连接状态
await cos.deleteObject({
Bucket: CONNECTION_BUCKET,
Region: CONNECTION_REGION,
Key: item.Key
}).catch(console.error);
}
}
} catch (err) {
console.error('[Agent Handshake] 连接恢复失败:', err);
}
}
// 启动时自动恢复连接
restoreConnections();
// 定时检查并恢复连接
setInterval(restoreConnections, HEARTBEAT_INTERVAL * 2);
module.exports = {
initiateHandshake,
completeHandshake,
heartbeat,
disconnect,
validateConnection
updateModelVersion
};

View File

@ -0,0 +1,150 @@
'use strict';
const crypto = require('crypto');
const COS = require('cos-nodejs-sdk-v5');
const { execSync } = require('child_process');
// ─── 常量 ───
const TRAINING_INTERVAL = 6 * 60 * 60 * 1000; // 6小时
const MODEL_BUCKET = 'agent-models-1250000000';
const MODEL_REGION = 'ap-guangzhou';
// ─── COS 客户端 ───
const cos = new COS({
SecretId: process.env.COS_SECRET_ID,
SecretKey: process.env.COS_SECRET_KEY
});
// ─── 主权验证 ───
function validateSovereign(token) {
const emailAuth = require('./email-auth');
return emailAuth.validateSession(token).valid;
}
/**
* Agent训练执行器
*/
class AgentTrainer {
constructor(agentId) {
this.agentId = agentId;
this.modelKey = `models/${agentId}/${Date.now()}.model`;
this.training = false;
}
/**
* 执行训练
*/
async train() {
if (this.training) return;
this.training = true;
try {
// 1. 准备训练数据
const data = await this.prepareData();
// 2. 执行训练脚本
const modelBuffer = this.runTrainingScript(data);
// 3. 上传模型
await this.uploadModel(modelBuffer);
// 4. 更新联邦状态
await this.updateFederationStatus();
return { success: true, modelKey: this.modelKey };
} catch (err) {
console.error(`[Agent Training] ${this.agentId} 训练失败:`, err);
return { error: true, message: err.message };
} finally {
this.training = false;
}
}
async prepareData() {
// 从COS获取历史数据
const { Body } = await cos.getObject({
Bucket: MODEL_BUCKET,
Region: MODEL_REGION,
Key: `data/${this.agentId}/latest.json`
});
return JSON.parse(Body.toString());
}
runTrainingScript(data) {
const scriptPath = require.resolve('./training-script');
return execSync(`node ${scriptPath}`, {
input: JSON.stringify(data),
stdio: ['pipe', 'pipe', 'inherit']
});
}
async uploadModel(buffer) {
await cos.putObject({
Bucket: MODEL_BUCKET,
Region: MODEL_REGION,
Key: this.modelKey,
Body: buffer
});
}
async updateFederationStatus() {
const handshake = require('./agent-handshake');
await handshake.updateModelVersion(this.agentId, this.modelKey);
}
}
/**
* 定时训练调度器
*/
class TrainingScheduler {
constructor() {
this.timers = new Map();
}
schedule(agentId) {
if (this.timers.has(agentId)) return;
const trainer = new AgentTrainer(agentId);
const timer = setInterval(() => trainer.train(), TRAINING_INTERVAL);
// 立即执行首次训练
trainer.train();
this.timers.set(agentId, timer);
}
unschedule(agentId) {
const timer = this.timers.get(agentId);
if (timer) {
clearInterval(timer);
this.timers.delete(agentId);
}
}
}
// 全局调度器实例
const scheduler = new TrainingScheduler();
module.exports = {
/**
* 启动Agent训练计划
*/
startTraining(agentId, token) {
if (!validateSovereign(token)) {
throw new Error('无权限: 仅限主权者启动训练');
}
scheduler.schedule(agentId);
return { success: true };
},
/**
* 停止Agent训练
*/
stopTraining(agentId, token) {
if (!validateSovereign(token)) {
throw new Error('无权限: 仅限主权者停止训练');
}
scheduler.unschedule(agentId);
return { success: true };
}
};

View File

@ -15,7 +15,10 @@
"agent:handshake:initiate": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"initiatorId\":\"$INITIATOR\",\"targetId\":\"$TARGET\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/handshake/initiate | jq .",
"agent:handshake:complete": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"handshakeId\":\"$HANDSHAKE_ID\",\"targetId\":\"$TARGET\",\"targetKey\":\"$TARGET_KEY\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/handshake/complete | jq .",
"agent:handshake:heartbeat": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"connectionId\":\"$CONN_ID\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/handshake/heartbeat | jq .",
"agent:handshake:disconnect": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"connectionId\":\"$CONN_ID\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/handshake/disconnect | jq ."
"agent:handshake:disconnect": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"connectionId\":\"$CONN_ID\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/handshake/disconnect | jq .",
"agent:training:start": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"agentId\":\"$AGENT_ID\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/training/start | jq .",
"agent:training:stop": "curl -s -X POST -H 'Content-Type: application/json' -d '{\"agentId\":\"$AGENT_ID\",\"token\":\"$TOKEN\"}' http://localhost:3800/api/agent/training/stop | jq .",
"agent:training:status": "curl -s 'http://localhost:3800/api/agent/training/status?agentId=$AGENT_ID&token=$TOKEN' | jq ."
},
"dependencies": {
"express": "^4.21.0",

View File

@ -9,7 +9,7 @@
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 新增GitHub/Notion OAuth API
* 新增Agent训练API
*/
'use strict';
@ -24,6 +24,7 @@ const { execSync } = require('child_process');
const emailAuth = require('./modules/email-auth');
const oauth = require('./modules/oauth-providers');
const agentHandshake = require('./modules/agent-handshake');
const agentTraining = require('./modules/agent-training');
// ─── 常量 ───
const PORT = process.env.PORT || 3800;
@ -49,110 +50,59 @@ app.get('/api/health', (req, res) => {
});
});
// ─── GitHub OAuth API ───
app.get('/api/oauth/github', async (req, res) => {
try {
const email = req.query.email;
if (!email) return res.status(400).json({ error: true, message: '缺少邮箱参数' });
const url = await oauth.getGitHubOAuthUrl(email);
res.json({ success: true, url });
} catch (err) {
res.status(403).json({ error: true, message: err.message });
}
});
// ─── 原有路由保持不变 ───
app.get('/api/oauth/github/callback', async (req, res) => {
// ─── 新增Agent训练API ───
app.post('/api/agent/training/start', async (req, res) => {
try {
const { code, state } = req.query;
if (!code || !state) return res.redirect(`${ZY_BASE_URL}/?error=invalid_params`);
const token = await oauth.handleGitHubCallback(code, state);
res.redirect(`${ZY_BASE_URL}/?token=${token.token}`);
} catch (err) {
res.redirect(`${ZY_BASE_URL}/?error=${encodeURIComponent(err.message)}`);
}
});
// ─── Notion OAuth API ───
app.get('/api/oauth/notion', async (req, res) => {
try {
const email = req.query.email;
if (!email) return res.status(400).json({ error: true, message: '缺少邮箱参数' });
const url = await oauth.getNotionOAuthUrl(email);
res.json({ success: true, url });
} catch (err) {
res.status(403).json({ error: true, message: err.message });
}
});
app.get('/api/oauth/notion/callback', async (req, res) => {
try {
const { code, state } = req.query;
if (!code || !state) return res.redirect(`${ZY_BASE_URL}/?error=invalid_params`);
const token = await oauth.handleNotionCallback(code, state);
res.redirect(`${ZY_BASE_URL}/?token=${token.token}`);
} catch (err) {
res.redirect(`${ZY_BASE_URL}/?error=${encodeURIComponent(err.message)}`);
}
});
// ─── Agent握手协议API ───
app.post('/api/agent/handshake/initiate', async (req, res) => {
try {
const { initiatorId, targetId, token } = req.body;
if (!initiatorId || !targetId || !token) {
const { agentId, token } = req.body;
if (!agentId || !token) {
return res.status(400).json({ error: true, message: '缺少必要参数' });
}
const result = agentHandshake.initiateHandshake(initiatorId, targetId, token);
res.json({ success: true, ...result });
const result = agentTraining.startTraining(agentId, token);
res.json(result);
} catch (err) {
res.status(403).json({ error: true, message: err.message });
}
});
app.post('/api/agent/handshake/complete', async (req, res) => {
app.post('/api/agent/training/stop', async (req, res) => {
try {
const { handshakeId, targetId, targetKey, token } = req.body;
if (!handshakeId || !targetId || !targetKey || !token) {
const { agentId, token } = req.body;
if (!agentId || !token) {
return res.status(400).json({ error: true, message: '缺少必要参数' });
}
const result = agentHandshake.completeHandshake(handshakeId, targetId, targetKey, token);
res.json({ success: true, ...result });
const result = agentTraining.stopTraining(agentId, token);
res.json(result);
} catch (err) {
res.status(403).json({ error: true, message: err.message });
}
});
app.post('/api/agent/handshake/heartbeat', async (req, res) => {
app.get('/api/agent/training/status', async (req, res) => {
try {
const { connectionId, token } = req.body;
if (!connectionId || !token) {
const { agentId, token } = req.query;
if (!agentId || !token) {
return res.status(400).json({ error: true, message: '缺少必要参数' });
}
const result = agentHandshake.heartbeat(connectionId, token);
res.json({ success: true, ...result });
} catch (err) {
res.status(403).json({ error: true, message: err.message });
}
});
app.post('/api/agent/handshake/disconnect', async (req, res) => {
try {
const { connectionId, token } = req.body;
if (!connectionId || !token) {
return res.status(400).json({ error: true, message: '缺少必要参数' });
if (!emailAuth.validateSession(token).valid) {
return res.status(403).json({ error: true, message: '无效的会话令牌' });
}
const result = agentHandshake.disconnect(connectionId, token);
res.json({ success: true, ...result });
// 从COS获取训练状态
const { Body } = await cos.getObject({
Bucket: 'agent-models-1250000000',
Region: 'ap-guangzhou',
Key: `status/${agentId}.json`
}).catch(() => ({}));
const status = Body ? JSON.parse(Body.toString()) : { training: false };
res.json({ success: true, ...status });
} catch (err) {
res.status(403).json({ error: true, message: err.message });
res.status(500).json({ error: true, message: err.message });
}
});