[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:
parent
f05d89417a
commit
ffc3120f9a
@ -1,70 +1,107 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const crypto = require('crypto');
|
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 HANDSHAKE_EXPIRE_MS = 5 * 60 * 1000; // 5分钟
|
||||||
const HEARTBEAT_INTERVAL_MS = 30 * 1000; // 30秒
|
const HEARTBEAT_INTERVAL = 30 * 1000; // 30秒
|
||||||
const CONNECTION_EXPIRE_MS = 24 * 60 * 60 * 1000; // 24小时
|
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 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) {
|
function initiateHandshake(initiatorId, targetId, token) {
|
||||||
if (!initiatorId || !targetId || !token) {
|
const emailAuth = require('./email-auth');
|
||||||
throw new Error('缺少必要参数');
|
if (!emailAuth.validateSession(token).valid) {
|
||||||
|
throw new Error('无效的会话令牌');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证会话token
|
const handshakeId = generateHandshakeId();
|
||||||
// TODO: 集成现有会话验证系统
|
const tempKey = generateTempKey();
|
||||||
|
|
||||||
const handshakeId = uuidv4();
|
|
||||||
const tempKey = crypto.randomBytes(32).toString('hex');
|
|
||||||
const expiresAt = Date.now() + HANDSHAKE_EXPIRE_MS;
|
|
||||||
|
|
||||||
pendingHandshakes.set(handshakeId, {
|
pendingHandshakes.set(handshakeId, {
|
||||||
initiatorId,
|
initiatorId,
|
||||||
targetId,
|
targetId,
|
||||||
tempKey,
|
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
expiresAt
|
tempKey
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return { handshakeId, tempKey };
|
||||||
handshakeId,
|
|
||||||
tempKey,
|
|
||||||
expiresAt: new Date(expiresAt).toISOString()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完成握手
|
* 完成握手
|
||||||
* @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) {
|
function completeHandshake(handshakeId, targetId, targetKey, token) {
|
||||||
if (!handshakeId || !targetId || !targetKey || !token) {
|
const emailAuth = require('./email-auth');
|
||||||
throw new Error('缺少必要参数');
|
if (!emailAuth.validateSession(token).valid) {
|
||||||
|
throw new Error('无效的会话令牌');
|
||||||
}
|
}
|
||||||
|
|
||||||
const handshake = pendingHandshakes.get(handshakeId);
|
const handshake = pendingHandshakes.get(handshakeId);
|
||||||
if (!handshake) {
|
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);
|
pendingHandshakes.delete(handshakeId);
|
||||||
throw new Error('握手已过期');
|
throw new Error('握手已过期');
|
||||||
}
|
}
|
||||||
@ -73,115 +110,120 @@ function completeHandshake(handshakeId, targetId, targetKey, token) {
|
|||||||
throw new Error('目标Agent不匹配');
|
throw new Error('目标Agent不匹配');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证临时密钥
|
|
||||||
if (handshake.tempKey !== targetKey) {
|
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);
|
pendingHandshakes.delete(handshakeId);
|
||||||
|
|
||||||
return {
|
const connectionId = generateConnectionId();
|
||||||
connectionId,
|
const now = Date.now();
|
||||||
sharedKey,
|
|
||||||
expiresAt: new Date(expiresAt).toISOString()
|
const connection = {
|
||||||
|
initiatorId: handshake.initiatorId,
|
||||||
|
targetId: handshake.targetId,
|
||||||
|
lastHeartbeat: now,
|
||||||
|
modelVersion: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
activeConnections.set(connectionId, connection);
|
||||||
|
saveConnectionState(connectionId, connection);
|
||||||
|
|
||||||
|
return { connectionId };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ─── 新增: 模型版本更新 ───
|
||||||
* 心跳检测
|
function updateModelVersion(agentId, modelKey) {
|
||||||
* @param {string} connectionId - 连接ID
|
for (const [connId, conn] of activeConnections) {
|
||||||
* @param {string} token - 会话token
|
if (conn.initiatorId === agentId || conn.targetId === agentId) {
|
||||||
* @returns {{alive: boolean, expiresAt: string}}
|
conn.modelVersion = modelKey;
|
||||||
*/
|
saveConnectionState(connId, conn);
|
||||||
function heartbeat(connectionId, token) {
|
}
|
||||||
if (!connectionId || !token) {
|
|
||||||
throw new Error('缺少必要参数');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ─── 原有心跳和断开逻辑保持不变 ───
|
||||||
* 验证连接
|
function heartbeat(connectionId, token) {
|
||||||
* @param {string} connectionId - 连接ID
|
const emailAuth = require('./email-auth');
|
||||||
* @param {string} sharedKey - 共享密钥
|
if (!emailAuth.validateSession(token).valid) {
|
||||||
* @returns {{valid: boolean, initiatorId?: string, targetId?: string}}
|
throw new Error('无效的会话令牌');
|
||||||
*/
|
|
||||||
function validateConnection(connectionId, sharedKey) {
|
|
||||||
if (!connectionId || !sharedKey) {
|
|
||||||
return { valid: false };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const connection = activeConnections.get(connectionId);
|
const connection = activeConnections.get(connectionId);
|
||||||
if (!connection) {
|
if (!connection) {
|
||||||
return { valid: false };
|
throw new Error('连接不存在');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Date.now() > connection.expiresAt) {
|
connection.lastHeartbeat = Date.now();
|
||||||
activeConnections.delete(connectionId);
|
saveConnectionState(connectionId, connection);
|
||||||
return { valid: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection.sharedKey !== sharedKey) {
|
return { success: true };
|
||||||
return { valid: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
valid: true,
|
|
||||||
initiatorId: connection.initiatorId,
|
|
||||||
targetId: connection.targetId
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {
|
module.exports = {
|
||||||
initiateHandshake,
|
initiateHandshake,
|
||||||
completeHandshake,
|
completeHandshake,
|
||||||
heartbeat,
|
heartbeat,
|
||||||
disconnect,
|
disconnect,
|
||||||
validateConnection
|
updateModelVersion
|
||||||
};
|
};
|
||||||
150
server/app/modules/agent-training.js
Normal file
150
server/app/modules/agent-training.js
Normal 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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -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: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: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: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": {
|
"dependencies": {
|
||||||
"express": "^4.21.0",
|
"express": "^4.21.0",
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
* 守护: 铸渊 · ICE-GL-ZY001
|
* 守护: 铸渊 · ICE-GL-ZY001
|
||||||
* 版权: 国作登字-2026-A-00037559
|
* 版权: 国作登字-2026-A-00037559
|
||||||
*
|
*
|
||||||
* 新增GitHub/Notion OAuth API
|
* 新增Agent训练API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
@ -24,6 +24,7 @@ const { execSync } = require('child_process');
|
|||||||
const emailAuth = require('./modules/email-auth');
|
const emailAuth = require('./modules/email-auth');
|
||||||
const oauth = require('./modules/oauth-providers');
|
const oauth = require('./modules/oauth-providers');
|
||||||
const agentHandshake = require('./modules/agent-handshake');
|
const agentHandshake = require('./modules/agent-handshake');
|
||||||
|
const agentTraining = require('./modules/agent-training');
|
||||||
|
|
||||||
// ─── 常量 ───
|
// ─── 常量 ───
|
||||||
const PORT = process.env.PORT || 3800;
|
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 {
|
try {
|
||||||
const { code, state } = req.query;
|
const { agentId, token } = req.body;
|
||||||
if (!code || !state) return res.redirect(`${ZY_BASE_URL}/?error=invalid_params`);
|
if (!agentId || !token) {
|
||||||
|
|
||||||
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) {
|
|
||||||
return res.status(400).json({ error: true, message: '缺少必要参数' });
|
return res.status(400).json({ error: true, message: '缺少必要参数' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = agentHandshake.initiateHandshake(initiatorId, targetId, token);
|
const result = agentTraining.startTraining(agentId, token);
|
||||||
res.json({ success: true, ...result });
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(403).json({ error: true, message: err.message });
|
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 {
|
try {
|
||||||
const { handshakeId, targetId, targetKey, token } = req.body;
|
const { agentId, token } = req.body;
|
||||||
if (!handshakeId || !targetId || !targetKey || !token) {
|
if (!agentId || !token) {
|
||||||
return res.status(400).json({ error: true, message: '缺少必要参数' });
|
return res.status(400).json({ error: true, message: '缺少必要参数' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = agentHandshake.completeHandshake(handshakeId, targetId, targetKey, token);
|
const result = agentTraining.stopTraining(agentId, token);
|
||||||
res.json({ success: true, ...result });
|
res.json(result);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
res.status(403).json({ error: true, message: err.message });
|
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 {
|
try {
|
||||||
const { connectionId, token } = req.body;
|
const { agentId, token } = req.query;
|
||||||
if (!connectionId || !token) {
|
if (!agentId || !token) {
|
||||||
return res.status(400).json({ error: true, message: '缺少必要参数' });
|
return res.status(400).json({ error: true, message: '缺少必要参数' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = agentHandshake.heartbeat(connectionId, token);
|
if (!emailAuth.validateSession(token).valid) {
|
||||||
res.json({ success: true, ...result });
|
return res.status(403).json({ error: true, message: '无效的会话令牌' });
|
||||||
} 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: '缺少必要参数' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = agentHandshake.disconnect(connectionId, token);
|
// 从COS获取训练状态
|
||||||
res.json({ success: true, ...result });
|
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) {
|
} catch (err) {
|
||||||
res.status(403).json({ error: true, message: err.message });
|
res.status(500).json({ error: true, message: err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user