1464 lines
51 KiB
JavaScript
1464 lines
51 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════════════
|
||
* 🧠 AGE OS · MCP Server 主入口
|
||
* ═══════════════════════════════════════════════════════════
|
||
*
|
||
* 编号: ZY-MCP-001
|
||
* 端口: 3100
|
||
* 签发: 铸渊 · ICE-GL-ZY001
|
||
* 版权: 国作登字-2026-A-00037559
|
||
*
|
||
* 统一暴露 MCP 工具,供网站 AI 交互后端和 Agent 调用。
|
||
* 外部访问通过 Nginx /api/mcp/ 反向代理,需 API Key 鉴权。
|
||
* 内部访问(127.0.0.1)免鉴权。
|
||
*
|
||
* 鉴权方式: Authorization: Bearer <ZHUYUAN_API_KEY>
|
||
* 免鉴权端点: /health(监控探针)
|
||
*
|
||
* 工具清单:
|
||
* 节点: createNode / updateNode / deleteNode / queryNodes / getNode
|
||
* 关系: linkNodes / unlinkNodes / getRelations
|
||
* 结构: buildPath / scanStructure / classify
|
||
* COS: cosWrite / cosRead / cosDelete / cosList / cosArchive
|
||
* 人格体: registerPersona / getPersona / updatePersona / listPersonas
|
||
* getNotebook / updateNotebookPage / addMemoryAnchor / queryMemoryAnchors
|
||
* addWorldPlace / getWorldMap / updateWorldPlace
|
||
* addTimelineEntry / getTimeline / addRelationship / getRelationships
|
||
* registerTrainingAgent / updateTrainingAgent / logTrainingRun / getTrainingStatus
|
||
* saveFile / getFile / listFiles / getFileHistory
|
||
* Notion: notionQuery / notionReadPage / notionWritePage / notionUpdatePage / notionWriteSyslog
|
||
* GitHub: githubReadFile / githubListDir / githubWriteFile / githubGetCommits / githubGetIssues / githubTriggerDeploy
|
||
* 活模块: registerModule / getModule / listModules / moduleHeartbeat / diagnoseModule
|
||
* getModuleAlerts / getModuleLearnings / sendHLDP / getHLDPStats
|
||
* 语料: cosListCorpus / cosExtractCorpus / cosParseGitRepoArchive / cosParseNotionExport
|
||
* cosParseGPTCorpus / cosGetCorpusStatus
|
||
* COS数据库: cosDbInit / cosDbGetIndex / cosDbUpdateIndex / cosDbWriteEntry
|
||
* cosDbReadEntry / cosDbListEntries / cosDbDeleteEntry / cosDbGetStats
|
||
* 训练: trainingStartSession / trainingProcessCorpus / trainingClassifyEntry
|
||
* trainingWriteToMemory / trainingGetProgress / trainingRaiseAlert
|
||
* Notion桥接: notionCosSyncPage / notionCosReadMirror / notionCosListMirror
|
||
* notionCosBuildIndex / notionCosWriteWorkorder / notionCosReadWorkorder / notionCosListWorkorders
|
||
* 三方通信: cosAlertScan / cosAlertResolve / cosDispatchTask / cosReadTaskReport
|
||
* cosListTaskReports / cosApproveTask / cosSendNotification / cosGetCommLink
|
||
* 权限修复: notionCheckPermissions / notionRepairPermissions / notionListSharedPages
|
||
* notionGenerateRepairGuide / notionPermissionReport
|
||
* 微调引擎: finetuneExportDataset / finetuneSubmitJob / finetuneCheckStatus
|
||
* finetuneRegisterModel / finetuneListModels / finetuneCallModel
|
||
* finetuneCompareModels / finetuneGetCostEstimate
|
||
* 光之树: growBranch / growLeaf / growBloom / getTreeNode / getSubtree
|
||
* tracePath / getPersonaBranch / getRecentLeaves
|
||
* 天眼: writeSyslog / getTianyanView / querySyslog
|
||
* COS轮询: cosWatcherStatus / cosWatcherTriggerScan / cosWatcherResetIndex
|
||
* COS桥接: cosBridgeStatus
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
const crypto = require('crypto');
|
||
const https = require('https');
|
||
const express = require('express');
|
||
const db = require('./db');
|
||
const cos = require('./cos');
|
||
|
||
// ─── 加载工具模块 ───
|
||
const nodeOps = require('./tools/node-ops');
|
||
const relationOps = require('./tools/relation-ops');
|
||
const structureOps = require('./tools/structure-ops');
|
||
const cosOps = require('./tools/cos-ops');
|
||
const personaOps = require('./tools/persona-ops');
|
||
const livingModuleOps = require('./tools/living-module-ops');
|
||
// 新增模块 A-G
|
||
const corpusExtractorOps = require('./tools/corpus-extractor-ops');
|
||
const cosPersonaDbOps = require('./tools/cos-persona-db-ops');
|
||
const trainingAgentOps = require('./tools/training-agent-ops');
|
||
const notionCosBridgeOps = require('./tools/notion-cos-bridge-ops');
|
||
const cosCommOps = require('./tools/cos-comm-ops');
|
||
const notionPermissionOps = require('./tools/notion-permission-ops');
|
||
const finetuneEngineOps = require('./tools/finetune-engine-ops');
|
||
// 光之树 + 天眼
|
||
const lightTreeOps = require('./tools/light-tree-ops');
|
||
// COS桶轮询守护
|
||
const cosWatcherOps = require('./tools/cos-watcher-ops');
|
||
const cosWatcher = require('./cos-watcher');
|
||
// Notion人格体认知操作
|
||
const notionPersonaCognitionOps = require('./tools/notion-persona-cognition-ops');
|
||
// 跨层开发任务 · 人格体Agent → 副驾驶桥接
|
||
const devTaskOps = require('./tools/dev-task-ops');
|
||
|
||
// ─── 外部集成模块(优雅降级:未安装依赖时不影响核心功能) ───
|
||
let notionOps = null;
|
||
let githubOps = null;
|
||
let notionClient = null;
|
||
let githubClient = null;
|
||
|
||
try {
|
||
notionOps = require('./tools/notion-ops');
|
||
notionClient = require('./notion-client');
|
||
} catch (err) {
|
||
console.warn(`[MCP] Notion模块加载跳过: ${err.message}`);
|
||
}
|
||
|
||
try {
|
||
githubOps = require('./tools/github-ops');
|
||
githubClient = require('./github-client');
|
||
} catch (err) {
|
||
console.warn(`[MCP] GitHub模块加载跳过: ${err.message}`);
|
||
}
|
||
|
||
// ─── 工具注册表 ───
|
||
const TOOLS = {
|
||
// 节点操作
|
||
createNode: nodeOps.createNode,
|
||
updateNode: nodeOps.updateNode,
|
||
deleteNode: nodeOps.deleteNode,
|
||
queryNodes: nodeOps.queryNodes,
|
||
getNode: nodeOps.getNode,
|
||
// 关系操作
|
||
linkNodes: relationOps.linkNodes,
|
||
unlinkNodes: relationOps.unlinkNodes,
|
||
getRelations: relationOps.getRelations,
|
||
// 结构操作
|
||
buildPath: structureOps.buildPath,
|
||
scanStructure: structureOps.scanStructure,
|
||
classify: structureOps.classify,
|
||
// COS操作
|
||
cosWrite: cosOps.cosWrite,
|
||
cosRead: cosOps.cosRead,
|
||
cosDelete: cosOps.cosDelete,
|
||
cosList: cosOps.cosList,
|
||
cosArchive: cosOps.cosArchive,
|
||
// 人格体COS隔离路径操作(限定 /{persona_id}/ 目录)
|
||
personaCosWrite: cosOps.personaCosWrite,
|
||
personaCosRead: cosOps.personaCosRead,
|
||
personaCosList: cosOps.personaCosList,
|
||
// 人格体操作 · S15
|
||
registerPersona: personaOps.registerPersona,
|
||
getPersona: personaOps.getPersona,
|
||
updatePersona: personaOps.updatePersona,
|
||
listPersonas: personaOps.listPersonas,
|
||
getNotebook: personaOps.getNotebook,
|
||
updateNotebookPage: personaOps.updateNotebookPage,
|
||
addMemoryAnchor: personaOps.addMemoryAnchor,
|
||
queryMemoryAnchors: personaOps.queryMemoryAnchors,
|
||
addWorldPlace: personaOps.addWorldPlace,
|
||
getWorldMap: personaOps.getWorldMap,
|
||
updateWorldPlace: personaOps.updateWorldPlace,
|
||
addTimelineEntry: personaOps.addTimelineEntry,
|
||
getTimeline: personaOps.getTimeline,
|
||
addRelationship: personaOps.addRelationship,
|
||
getRelationships: personaOps.getRelationships,
|
||
registerTrainingAgent: personaOps.registerTrainingAgent,
|
||
updateTrainingAgent: personaOps.updateTrainingAgent,
|
||
logTrainingRun: personaOps.logTrainingRun,
|
||
getTrainingStatus: personaOps.getTrainingStatus,
|
||
saveFile: personaOps.saveFile,
|
||
getFile: personaOps.getFile,
|
||
listFiles: personaOps.listFiles,
|
||
getFileHistory: personaOps.getFileHistory,
|
||
// Notion操作(动态注册)
|
||
...(notionOps ? {
|
||
notionQuery: notionOps.notionQuery,
|
||
notionReadPage: notionOps.notionReadPage,
|
||
notionWritePage: notionOps.notionWritePage,
|
||
notionUpdatePage: notionOps.notionUpdatePage,
|
||
notionWriteSyslog: notionOps.notionWriteSyslog
|
||
} : {}),
|
||
// GitHub操作(动态注册)
|
||
...(githubOps ? {
|
||
githubReadFile: githubOps.githubReadFile,
|
||
githubListDir: githubOps.githubListDir,
|
||
githubWriteFile: githubOps.githubWriteFile,
|
||
githubGetCommits: githubOps.githubGetCommits,
|
||
githubGetIssues: githubOps.githubGetIssues,
|
||
githubTriggerDeploy: githubOps.githubTriggerDeploy
|
||
} : {}),
|
||
// 模块A · COS语料读取引擎
|
||
cosListCorpus: corpusExtractorOps.cosListCorpus,
|
||
cosExtractCorpus: corpusExtractorOps.cosExtractCorpus,
|
||
cosParseGitRepoArchive: corpusExtractorOps.cosParseGitRepoArchive,
|
||
cosParseNotionExport: corpusExtractorOps.cosParseNotionExport,
|
||
cosParseGPTCorpus: corpusExtractorOps.cosParseGPTCorpus,
|
||
cosGetCorpusStatus: corpusExtractorOps.cosGetCorpusStatus,
|
||
// 模块G · COS桶内自研数据库
|
||
cosDbInit: cosPersonaDbOps.cosDbInit,
|
||
cosDbGetIndex: cosPersonaDbOps.cosDbGetIndex,
|
||
cosDbUpdateIndex: cosPersonaDbOps.cosDbUpdateIndex,
|
||
cosDbWriteEntry: cosPersonaDbOps.cosDbWriteEntry,
|
||
cosDbReadEntry: cosPersonaDbOps.cosDbReadEntry,
|
||
cosDbListEntries: cosPersonaDbOps.cosDbListEntries,
|
||
cosDbDeleteEntry: cosPersonaDbOps.cosDbDeleteEntry,
|
||
cosDbGetStats: cosPersonaDbOps.cosDbGetStats,
|
||
// 模块B · 铸渊思维逻辑训练Agent
|
||
trainingStartSession: trainingAgentOps.trainingStartSession,
|
||
trainingProcessCorpus: trainingAgentOps.trainingProcessCorpus,
|
||
trainingClassifyEntry: trainingAgentOps.trainingClassifyEntry,
|
||
trainingWriteToMemory: trainingAgentOps.trainingWriteToMemory,
|
||
trainingGetProgress: trainingAgentOps.trainingGetProgress,
|
||
trainingRaiseAlert: trainingAgentOps.trainingRaiseAlert,
|
||
// 模块C · Notion ↔ COS桥接
|
||
notionCosSyncPage: notionCosBridgeOps.notionCosSyncPage,
|
||
notionCosReadMirror: notionCosBridgeOps.notionCosReadMirror,
|
||
notionCosListMirror: notionCosBridgeOps.notionCosListMirror,
|
||
notionCosBuildIndex: notionCosBridgeOps.notionCosBuildIndex,
|
||
notionCosWriteWorkorder: notionCosBridgeOps.notionCosWriteWorkorder,
|
||
notionCosReadWorkorder: notionCosBridgeOps.notionCosReadWorkorder,
|
||
notionCosListWorkorders: notionCosBridgeOps.notionCosListWorkorders,
|
||
// 模块D+E · COS桶示警 + 三方对接
|
||
cosAlertScan: cosCommOps.cosAlertScan,
|
||
cosAlertResolve: cosCommOps.cosAlertResolve,
|
||
cosDispatchTask: cosCommOps.cosDispatchTask,
|
||
cosReadTaskReport: cosCommOps.cosReadTaskReport,
|
||
cosListTaskReports: cosCommOps.cosListTaskReports,
|
||
cosApproveTask: cosCommOps.cosApproveTask,
|
||
cosSendNotification: cosCommOps.cosSendNotification,
|
||
cosGetCommLink: cosCommOps.cosGetCommLink,
|
||
// 模块F · Notion权限自动修复
|
||
notionCheckPermissions: notionPermissionOps.notionCheckPermissions,
|
||
notionRepairPermissions: notionPermissionOps.notionRepairPermissions,
|
||
notionListSharedPages: notionPermissionOps.notionListSharedPages,
|
||
notionGenerateRepairGuide: notionPermissionOps.notionGenerateRepairGuide,
|
||
notionPermissionReport: notionPermissionOps.notionPermissionReport,
|
||
// 模块H · 开源模型微调引擎
|
||
finetuneExportDataset: finetuneEngineOps.finetuneExportDataset,
|
||
finetuneSubmitJob: finetuneEngineOps.finetuneSubmitJob,
|
||
finetuneCheckStatus: finetuneEngineOps.finetuneCheckStatus,
|
||
finetuneRegisterModel: finetuneEngineOps.finetuneRegisterModel,
|
||
finetuneListModels: finetuneEngineOps.finetuneListModels,
|
||
finetuneCallModel: finetuneEngineOps.finetuneCallModel,
|
||
finetuneCompareModels: finetuneEngineOps.finetuneCompareModels,
|
||
finetuneGetCostEstimate: finetuneEngineOps.finetuneGetCostEstimate,
|
||
// 活模块操作 · S5
|
||
registerModule: livingModuleOps.registerModule,
|
||
getModule: livingModuleOps.getModule,
|
||
listModules: livingModuleOps.listModules,
|
||
moduleHeartbeat: livingModuleOps.moduleHeartbeat,
|
||
diagnoseModule: livingModuleOps.diagnoseModule,
|
||
getModuleAlerts: livingModuleOps.getModuleAlerts,
|
||
getModuleLearnings: livingModuleOps.getModuleLearnings,
|
||
sendHLDP: livingModuleOps.sendHLDP,
|
||
getHLDPStats: livingModuleOps.getHLDPStats,
|
||
// 光之树 · 生长操作
|
||
growBranch: lightTreeOps.growBranch,
|
||
growLeaf: lightTreeOps.growLeaf,
|
||
growBloom: lightTreeOps.growBloom,
|
||
// 光之树 · 查询操作
|
||
getTreeNode: lightTreeOps.getTreeNode,
|
||
getSubtree: lightTreeOps.getSubtree,
|
||
tracePath: lightTreeOps.tracePath,
|
||
getPersonaBranch: lightTreeOps.getPersonaBranch,
|
||
getRecentLeaves: lightTreeOps.getRecentLeaves,
|
||
// 天眼 · SYSLOG
|
||
writeSyslog: lightTreeOps.writeSyslog,
|
||
getTianyanView: lightTreeOps.getTianyanView,
|
||
querySyslog: lightTreeOps.querySyslog,
|
||
// COS桶轮询守护 · SCF替代
|
||
cosWatcherStatus: cosWatcherOps.cosWatcherStatus,
|
||
cosWatcherTriggerScan: cosWatcherOps.cosWatcherTriggerScan,
|
||
cosWatcherResetIndex: cosWatcherOps.cosWatcherResetIndex,
|
||
// COS桥接 · 铸渊↔秋秋暗核频道
|
||
cosBridgeStatus: cosWatcherOps.cosBridgeStatus,
|
||
// Notion人格体认知操作 · 霜砚Agent专用
|
||
notionPersonaCognitionQuery: notionPersonaCognitionOps.notionPersonaCognitionQuery,
|
||
notionContextInject: notionPersonaCognitionOps.notionContextInject,
|
||
notionCognitionGrow: notionPersonaCognitionOps.notionCognitionGrow,
|
||
// 跨层开发任务 · 人格体Agent → 副驾驶桥接
|
||
submitDevTask: devTaskOps.submitDevTask,
|
||
getDevTask: devTaskOps.getDevTask,
|
||
listDevTasks: devTaskOps.listDevTasks,
|
||
updateDevTaskStatus: devTaskOps.updateDevTaskStatus,
|
||
logDevTaskAction: devTaskOps.logDevTaskAction,
|
||
getDevTaskStats: devTaskOps.getDevTaskStats,
|
||
getDevTaskLogs: devTaskOps.getDevTaskLogs
|
||
};
|
||
|
||
// ─── Express 应用 ───
|
||
const app = express();
|
||
const PORT = process.env.MCP_PORT || 3100;
|
||
|
||
// ─── API Key 配置 ───
|
||
const ZHUYUAN_API_KEY = process.env.ZHUYUAN_API_KEY || '';
|
||
|
||
app.use(express.json({ limit: '5mb' }));
|
||
|
||
// 信任 Nginx 反向代理 — 使 req.ip 返回真实客户端IP
|
||
// 而非代理服务器的 127.0.0.1
|
||
app.set('trust proxy', 'loopback');
|
||
|
||
// ─── API Key 鉴权中间件 ───
|
||
// 外部访问需携带 Authorization: Bearer <ZHUYUAN_API_KEY>
|
||
// 内部访问 (127.0.0.1 / ::1) 免鉴权
|
||
// /health 端点免鉴权(监控探针用)
|
||
function apiKeyAuth(req, res, next) {
|
||
// /health 端点免鉴权
|
||
if (req.path === '/health') return next();
|
||
|
||
// /webhook/* 端点免API Key鉴权(使用独立的webhook密钥验证)
|
||
if (req.path.startsWith('/webhook/')) return next();
|
||
|
||
// 内部回环地址免鉴权
|
||
// 注意: 使用 req.socket.remoteAddress 获取实际TCP连接地址
|
||
// req.ip 在 trust proxy 模式下返回 X-Forwarded-For 中的客户端真实IP
|
||
// 但本地 Nginx 代理的请求 socket 连接仍来自 127.0.0.1
|
||
const socketIp = req.socket?.remoteAddress || req.connection?.remoteAddress || '';
|
||
const isLocal = socketIp === '127.0.0.1' || socketIp === '::1' || socketIp === '::ffff:127.0.0.1';
|
||
if (isLocal) return next();
|
||
|
||
// API Key 未配置时,拒绝所有外部请求
|
||
if (!ZHUYUAN_API_KEY) {
|
||
return res.status(503).json({
|
||
error: true,
|
||
code: 'API_KEY_NOT_CONFIGURED',
|
||
message: 'MCP Server API Key 未配置,外部访问不可用'
|
||
});
|
||
}
|
||
|
||
// 验证 Authorization: Bearer <key>
|
||
const authHeader = req.headers.authorization || '';
|
||
if (!authHeader.startsWith('Bearer ')) {
|
||
return res.status(401).json({
|
||
error: true,
|
||
code: 'MISSING_AUTH',
|
||
message: '缺少 Authorization: Bearer <API_KEY> 头'
|
||
});
|
||
}
|
||
|
||
const providedKey = authHeader.slice(7);
|
||
if (providedKey.length === 0) {
|
||
return res.status(401).json({
|
||
error: true,
|
||
code: 'EMPTY_KEY',
|
||
message: 'API Key 不能为空'
|
||
});
|
||
}
|
||
|
||
// 常量时间比较防止时序攻击
|
||
const keyBuffer = Buffer.from(ZHUYUAN_API_KEY);
|
||
const providedBuffer = Buffer.from(providedKey);
|
||
if (keyBuffer.length !== providedBuffer.length || !crypto.timingSafeEqual(keyBuffer, providedBuffer)) {
|
||
return res.status(403).json({
|
||
error: true,
|
||
code: 'INVALID_KEY',
|
||
message: 'API Key 无效'
|
||
});
|
||
}
|
||
|
||
// 将调用者身份附加到请求对象
|
||
req.mcpCaller = 'external-api-key';
|
||
next();
|
||
}
|
||
|
||
app.use(apiKeyAuth);
|
||
|
||
// ─── 健康检查 ───
|
||
app.get('/health', async (_req, res) => {
|
||
const dbStatus = await db.checkConnection();
|
||
const cosStatus = await cos.checkConnection();
|
||
|
||
// 外部集成状态(异步并行检查)
|
||
const [notionStatus, githubStatus] = await Promise.all([
|
||
notionClient ? notionClient.checkConnection().catch(e => ({ connected: false, error: e.message })) : Promise.resolve({ connected: false, reason: '模块未加载' }),
|
||
githubClient ? githubClient.checkConnection().catch(e => ({ connected: false, error: e.message })) : Promise.resolve({ connected: false, reason: '模块未加载' })
|
||
]);
|
||
|
||
const watcherStatus = cosWatcher.getStatus();
|
||
|
||
res.json({
|
||
server: 'ZY-MCP-001',
|
||
identity: '铸渊 · AGE OS MCP Server',
|
||
status: dbStatus.connected ? 'alive' : 'degraded',
|
||
timestamp: new Date().toISOString(),
|
||
uptime: Math.floor(process.uptime()),
|
||
auth: {
|
||
api_key_configured: !!ZHUYUAN_API_KEY,
|
||
external_access: !!ZHUYUAN_API_KEY ? 'enabled' : 'disabled'
|
||
},
|
||
tools: Object.keys(TOOLS),
|
||
tools_count: Object.keys(TOOLS).length,
|
||
database: dbStatus,
|
||
cos: cosStatus,
|
||
cos_watcher: {
|
||
enabled: watcherStatus.enabled,
|
||
last_scan: watcherStatus.last_scan,
|
||
scan_count: watcherStatus.scan_count,
|
||
errors: watcherStatus.errors
|
||
},
|
||
cos_bridge: {
|
||
qiuqiu_status: watcherStatus.bridge?.qiuqiu_status || 'UNKNOWN',
|
||
last_heartbeat: watcherStatus.bridge?.last_heartbeat || null,
|
||
total_results: watcherStatus.bridge?.total_results_received || 0
|
||
},
|
||
notion: notionStatus,
|
||
github: githubStatus
|
||
});
|
||
});
|
||
|
||
// ─── 工具列表 ───
|
||
app.get('/tools', (_req, res) => {
|
||
res.json({
|
||
tools: Object.keys(TOOLS).map(name => ({
|
||
name,
|
||
category: getCategoryForTool(name)
|
||
}))
|
||
});
|
||
});
|
||
|
||
// ─── 统一工具调用入口 ───
|
||
app.post('/call', async (req, res) => {
|
||
const { tool, input, caller } = req.body;
|
||
|
||
if (!tool || !TOOLS[tool]) {
|
||
return res.status(400).json({
|
||
error: true,
|
||
code: 'UNKNOWN_TOOL',
|
||
message: `未知工具: ${tool}`,
|
||
available: Object.keys(TOOLS)
|
||
});
|
||
}
|
||
|
||
if (!input || typeof input !== 'object') {
|
||
return res.status(400).json({
|
||
error: true,
|
||
code: 'INVALID_INPUT',
|
||
message: '缺少 input 参数或格式错误'
|
||
});
|
||
}
|
||
|
||
const startTime = Date.now();
|
||
try {
|
||
const result = await TOOLS[tool](input);
|
||
const duration = Date.now() - startTime;
|
||
|
||
// 记录调用日志(异步,不阻塞响应)
|
||
logToolCall(tool, caller, 'success', duration).catch(() => {});
|
||
|
||
res.json({
|
||
success: true,
|
||
tool,
|
||
result,
|
||
duration_ms: duration
|
||
});
|
||
} catch (err) {
|
||
const duration = Date.now() - startTime;
|
||
logToolCall(tool, caller, 'error', duration, err.message).catch(() => {});
|
||
|
||
res.status(500).json({
|
||
error: true,
|
||
code: 'TOOL_ERROR',
|
||
tool,
|
||
message: err.message,
|
||
duration_ms: duration
|
||
});
|
||
}
|
||
});
|
||
|
||
// ─── Agent配置查询 ───
|
||
app.get('/agents', async (_req, res) => {
|
||
try {
|
||
const result = await db.query(
|
||
'SELECT * FROM agent_configs ORDER BY agent_id'
|
||
);
|
||
res.json({ agents: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
app.get('/agents/:agentId', async (req, res) => {
|
||
try {
|
||
const result = await db.query(
|
||
'SELECT * FROM agent_configs WHERE agent_id = $1',
|
||
[req.params.agentId]
|
||
);
|
||
if (result.rows.length === 0) {
|
||
return res.status(404).json({ error: true, message: 'Agent未找到' });
|
||
}
|
||
res.json({ agent: result.rows[0] });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── Agent日志查询 ───
|
||
app.get('/agents/:agentId/logs', async (req, res) => {
|
||
try {
|
||
const limit = Math.min(parseInt(req.query.limit || '20', 10), 100);
|
||
const result = await db.query(
|
||
'SELECT * FROM agent_logs WHERE agent_id = $1 ORDER BY run_at DESC LIMIT $2',
|
||
[req.params.agentId, limit]
|
||
);
|
||
res.json({ logs: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 人格体API(S15) ───
|
||
|
||
// 人格体列表
|
||
app.get('/personas', async (_req, res) => {
|
||
try {
|
||
const result = await db.query('SELECT * FROM persona_registry ORDER BY persona_id');
|
||
res.json({ personas: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体详情(含笔记本+关系+世界地图+训练状态)
|
||
app.get('/personas/:personaId', async (req, res) => {
|
||
try {
|
||
const pid = req.params.personaId;
|
||
const [persona, notebook, relationships, worldMap, trainingAgents] = await Promise.all([
|
||
db.query('SELECT * FROM persona_registry WHERE persona_id = $1', [pid]),
|
||
db.query('SELECT * FROM notebook_pages WHERE persona_id = $1 ORDER BY page_number', [pid]),
|
||
db.query('SELECT * FROM persona_relationships WHERE persona_id = $1', [pid]),
|
||
db.query('SELECT * FROM world_places WHERE persona_id = $1 ORDER BY status, place_name', [pid]),
|
||
db.query('SELECT * FROM training_agent_configs WHERE persona_id = $1 ORDER BY agent_type', [pid])
|
||
]);
|
||
|
||
if (persona.rows.length === 0) {
|
||
return res.status(404).json({ error: true, message: '人格体未找到' });
|
||
}
|
||
|
||
res.json({
|
||
persona: persona.rows[0],
|
||
notebook: notebook.rows,
|
||
relationships: relationships.rows,
|
||
world_map: worldMap.rows,
|
||
training_agents: trainingAgents.rows
|
||
});
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体笔记本
|
||
app.get('/personas/:personaId/notebook', async (req, res) => {
|
||
try {
|
||
const result = await db.query(
|
||
'SELECT * FROM notebook_pages WHERE persona_id = $1 ORDER BY page_number',
|
||
[req.params.personaId]
|
||
);
|
||
res.json({ notebook: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体记忆锚点
|
||
app.get('/personas/:personaId/memories', async (req, res) => {
|
||
try {
|
||
const limit = Math.min(parseInt(req.query.limit || '50', 10), 200);
|
||
const result = await db.query(
|
||
'SELECT * FROM memory_anchors WHERE persona_id = $1 ORDER BY event_date DESC, importance DESC LIMIT $2',
|
||
[req.params.personaId, limit]
|
||
);
|
||
res.json({ anchors: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体训练状态
|
||
app.get('/personas/:personaId/training', async (req, res) => {
|
||
try {
|
||
const agents = await db.query(
|
||
'SELECT * FROM training_agent_configs WHERE persona_id = $1 ORDER BY agent_type',
|
||
[req.params.personaId]
|
||
);
|
||
const recentLogs = await db.query(
|
||
'SELECT * FROM training_agent_logs WHERE persona_id = $1 ORDER BY run_at DESC LIMIT 20',
|
||
[req.params.personaId]
|
||
);
|
||
res.json({ agents: agents.rows, recent_logs: recentLogs.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 活模块API(S5) ───
|
||
|
||
// 活模块列表
|
||
app.get('/modules', async (req, res) => {
|
||
try {
|
||
const { status, module_type } = req.query;
|
||
const result = await livingModuleOps.listModules({ status, module_type });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 活模块详情
|
||
app.get('/modules/:moduleId', async (req, res) => {
|
||
try {
|
||
const result = await livingModuleOps.getModule({ module_id: req.params.moduleId });
|
||
if (!result.found) {
|
||
return res.status(404).json({ error: true, message: '模块未找到' });
|
||
}
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 活模块心跳上报
|
||
app.post('/modules/:moduleId/heartbeat', async (req, res) => {
|
||
try {
|
||
const result = await livingModuleOps.moduleHeartbeat({
|
||
module_id: req.params.moduleId,
|
||
...req.body
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 活模块报警记录
|
||
app.get('/modules/:moduleId/alerts', async (req, res) => {
|
||
try {
|
||
const result = await livingModuleOps.getModuleAlerts({
|
||
module_id: req.params.moduleId,
|
||
resolved: req.query.resolved === 'true' ? true : (req.query.resolved === 'false' ? false : undefined),
|
||
limit: parseInt(req.query.limit || '20', 10)
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 活模块学习记录
|
||
app.get('/modules/:moduleId/learnings', async (req, res) => {
|
||
try {
|
||
const result = await livingModuleOps.getModuleLearnings({
|
||
module_id: req.params.moduleId,
|
||
limit: parseInt(req.query.limit || '20', 10)
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// HLDP消息发送
|
||
app.post('/hldp/send', async (req, res) => {
|
||
try {
|
||
const result = await livingModuleOps.sendHLDP(req.body);
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// HLDP统计
|
||
app.get('/hldp/stats', async (_req, res) => {
|
||
try {
|
||
const result = await livingModuleOps.getHLDPStats({});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 光之树 API ───
|
||
|
||
// 光之树 · 人格体分支概览
|
||
app.get('/tree/:personaId', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.getPersonaBranch({ persona_id: req.params.personaId });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 人格体最近的叶子(唤醒回忆)
|
||
app.get('/tree/:personaId/recent', async (req, res) => {
|
||
try {
|
||
const limit = parseInt(req.query.limit || '3', 10);
|
||
const result = await lightTreeOps.getRecentLeaves({ persona_id: req.params.personaId, limit });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 节点详情
|
||
app.get('/tree/node/:nodeId', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.getTreeNode({ node_id: req.params.nodeId });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 子树查询
|
||
app.get('/tree/node/:nodeId/subtree', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.getSubtree({
|
||
node_id: req.params.nodeId,
|
||
max_depth: req.query.max_depth ? parseInt(req.query.max_depth, 10) : undefined,
|
||
limit: req.query.limit
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 路径回溯(从叶子到根)
|
||
app.get('/tree/node/:nodeId/trace', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.tracePath({ node_id: req.params.nodeId });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 长树杈
|
||
app.post('/tree/grow/branch', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.growBranch(req.body);
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 长叶子
|
||
app.post('/tree/grow/leaf', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.growLeaf(req.body);
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 光之树 · 开花
|
||
app.post('/tree/grow/bloom', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.growBloom(req.body);
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 天眼 API ───
|
||
|
||
// 天眼 · 全局感知视图
|
||
app.get('/tianyan', async (_req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.getTianyanView({});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 天眼 · SYSLOG查询
|
||
app.get('/tianyan/syslog', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.querySyslog({
|
||
agent_id: req.query.agent_id,
|
||
persona_id: req.query.persona_id,
|
||
result_filter: req.query.result,
|
||
start_time: req.query.start,
|
||
end_time: req.query.end,
|
||
limit: req.query.limit
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 天眼 · 写入SYSLOG
|
||
app.post('/tianyan/syslog', async (req, res) => {
|
||
try {
|
||
const result = await lightTreeOps.writeSyslog(req.body);
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── COS桶轮询守护API ───
|
||
|
||
// 轮询守护状态
|
||
app.get('/cos-watcher/status', async (_req, res) => {
|
||
try {
|
||
const status = cosWatcher.getStatus();
|
||
res.json(status);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 手动触发扫描
|
||
app.post('/cos-watcher/scan', async (_req, res) => {
|
||
try {
|
||
const result = await cosWatcher.triggerScan();
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 重置索引
|
||
app.post('/cos-watcher/reset', async (_req, res) => {
|
||
try {
|
||
const result = cosWatcher.resetIndex();
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── COS桥接通道API · 铸渊↔秋秋暗核频道 ───
|
||
|
||
// 桥接通道状态
|
||
app.get('/cos-bridge/status', async (_req, res) => {
|
||
try {
|
||
const status = cosWatcher.getBridgeStatus();
|
||
res.json(status);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 人格体数据库引擎状态API(S15) ───
|
||
|
||
// 人格体引擎巡检状态
|
||
app.get('/persona-engine/status', async (_req, res) => {
|
||
try {
|
||
// 实时统计
|
||
const [personas, notebooks, memories, training] = await Promise.all([
|
||
db.query('SELECT status, COUNT(*) as cnt FROM persona_registry GROUP BY status'),
|
||
db.query(`
|
||
SELECT p.persona_id, p.name, COUNT(n.page_number) as page_count
|
||
FROM persona_registry p
|
||
LEFT JOIN notebook_pages n ON p.persona_id = n.persona_id
|
||
GROUP BY p.persona_id, p.name
|
||
ORDER BY p.persona_id
|
||
`),
|
||
db.query('SELECT COUNT(*) as total FROM memory_anchors'),
|
||
db.query('SELECT COUNT(*) as total FROM training_agent_configs WHERE enabled = true')
|
||
]);
|
||
|
||
const statusMap = {};
|
||
for (const row of personas.rows) {
|
||
statusMap[row.status] = parseInt(row.cnt, 10);
|
||
}
|
||
|
||
res.json({
|
||
engine: 'ZY-MOD-PERSONA-ENGINE',
|
||
identity: 'S15 · 人格体数据库引擎',
|
||
version: '1.0.0',
|
||
personas: statusMap,
|
||
notebooks: notebooks.rows.map(n => ({
|
||
persona_id: n.persona_id,
|
||
name: n.name,
|
||
pages: parseInt(n.page_count, 10),
|
||
complete: parseInt(n.page_count, 10) === 5
|
||
})),
|
||
memories_total: parseInt(memories.rows[0].total, 10),
|
||
training_agents_enabled: parseInt(training.rows[0].total, 10),
|
||
timestamp: new Date().toISOString()
|
||
});
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体唤醒
|
||
app.post('/personas/:personaId/awaken', async (req, res) => {
|
||
try {
|
||
const pid = req.params.personaId;
|
||
const result = await db.query(
|
||
`UPDATE persona_registry
|
||
SET last_awakened = NOW(), total_awakenings = total_awakenings + 1, status = 'active', updated_at = NOW()
|
||
WHERE persona_id = $1 RETURNING *`,
|
||
[pid]
|
||
);
|
||
if (result.rows.length === 0) {
|
||
return res.status(404).json({ error: true, message: '人格体未找到' });
|
||
}
|
||
res.json({ persona: result.rows[0], message: `${result.rows[0].name} 已唤醒` });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体休眠
|
||
app.post('/personas/:personaId/dormant', async (req, res) => {
|
||
try {
|
||
const pid = req.params.personaId;
|
||
const result = await db.query(
|
||
`UPDATE persona_registry SET status = 'dormant', updated_at = NOW()
|
||
WHERE persona_id = $1 RETURNING *`,
|
||
[pid]
|
||
);
|
||
if (result.rows.length === 0) {
|
||
return res.status(404).json({ error: true, message: '人格体未找到' });
|
||
}
|
||
res.json({ persona: result.rows[0], message: `${result.rows[0].name} 已休眠` });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体世界地图
|
||
app.get('/personas/:personaId/world', async (req, res) => {
|
||
try {
|
||
const result = await db.query(
|
||
'SELECT * FROM world_places WHERE persona_id = $1 ORDER BY status, place_name',
|
||
[req.params.personaId]
|
||
);
|
||
res.json({ places: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体关系网络
|
||
app.get('/personas/:personaId/relationships', async (req, res) => {
|
||
try {
|
||
const result = await db.query(
|
||
'SELECT * FROM persona_relationships WHERE persona_id = $1 ORDER BY trust_level, related_name',
|
||
[req.params.personaId]
|
||
);
|
||
res.json({ relationships: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体时间线
|
||
app.get('/personas/:personaId/timeline', async (req, res) => {
|
||
try {
|
||
const limit = Math.min(parseInt(req.query.limit || '30', 10), 100);
|
||
const result = await db.query(
|
||
'SELECT * FROM persona_timeline WHERE persona_id = $1 ORDER BY day_number DESC LIMIT $2',
|
||
[req.params.personaId, limit]
|
||
);
|
||
res.json({ timeline: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 人格体文件列表
|
||
app.get('/personas/:personaId/files', async (req, res) => {
|
||
try {
|
||
const limit = Math.min(parseInt(req.query.limit || '100', 10), 500);
|
||
const result = await db.query(
|
||
`SELECT id, persona_id, file_path, file_type, content_hash, size_bytes, version, source, created_at
|
||
FROM persona_files WHERE persona_id = $1 AND is_latest = true ORDER BY file_path LIMIT $2`,
|
||
[req.params.personaId, limit]
|
||
);
|
||
res.json({ files: result.rows });
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 语料引擎API(模块A) ───
|
||
|
||
// 语料状态
|
||
app.get('/corpus/status', async (req, res) => {
|
||
try {
|
||
const result = await corpusExtractorOps.cosGetCorpusStatus({ bucket: req.query.bucket || 'cold' });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 列出语料
|
||
app.get('/corpus/list', async (req, res) => {
|
||
try {
|
||
const result = await corpusExtractorOps.cosListCorpus({
|
||
bucket: req.query.bucket || 'cold',
|
||
prefix: req.query.prefix,
|
||
include_processed: req.query.include_processed === 'true'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── COS数据库API(模块G) ───
|
||
|
||
// 数据库状态
|
||
app.get('/cos-db/:dbType/stats', async (req, res) => {
|
||
try {
|
||
const result = await cosPersonaDbOps.cosDbGetStats({
|
||
bucket: req.query.bucket || 'cold',
|
||
db_type: req.params.dbType
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 数据库索引
|
||
app.get('/cos-db/:dbType/index', async (req, res) => {
|
||
try {
|
||
const result = await cosPersonaDbOps.cosDbGetIndex({
|
||
bucket: req.query.bucket || 'cold',
|
||
db_type: req.params.dbType
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 训练Agent API(模块B) ───
|
||
|
||
// 训练进度
|
||
app.get('/training/:personaId/progress', async (req, res) => {
|
||
try {
|
||
const result = await trainingAgentOps.trainingGetProgress({
|
||
persona_id: req.params.personaId,
|
||
corpus_bucket: req.query.bucket || 'cold'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 三方通信API(模块D+E) ───
|
||
|
||
// 通信链路状态
|
||
app.get('/comm/status', async (req, res) => {
|
||
try {
|
||
const result = await cosCommOps.cosGetCommLink({ bucket: req.query.bucket || 'team' });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 告警列表
|
||
app.get('/comm/alerts', async (req, res) => {
|
||
try {
|
||
const result = await cosCommOps.cosAlertScan({
|
||
bucket: req.query.bucket || 'team',
|
||
include_resolved: req.query.include_resolved === 'true'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 工单列表
|
||
app.get('/comm/workorders', async (req, res) => {
|
||
try {
|
||
const result = await notionCosBridgeOps.notionCosListWorkorders({
|
||
bucket: req.query.bucket || 'team',
|
||
status_folder: req.query.status || 'pending'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── Notion权限API(模块F) ───
|
||
|
||
// 权限检查
|
||
app.get('/notion/permissions', async (_req, res) => {
|
||
try {
|
||
const result = await notionPermissionOps.notionCheckPermissions({});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 权限修复指南
|
||
app.get('/notion/repair-guide', async (req, res) => {
|
||
try {
|
||
const result = await notionPermissionOps.notionGenerateRepairGuide({
|
||
write_to_cos: req.query.write_to_cos === 'true'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 微调引擎API(模块H) ───
|
||
|
||
// 微调模型列表
|
||
app.get('/finetune/:personaId/models', async (req, res) => {
|
||
try {
|
||
const result = await finetuneEngineOps.finetuneListModels({
|
||
persona_id: req.params.personaId
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 微调成本估算
|
||
app.get('/finetune/:personaId/cost-estimate', async (req, res) => {
|
||
try {
|
||
const result = await finetuneEngineOps.finetuneGetCostEstimate({
|
||
persona_id: req.params.personaId,
|
||
dataset_key: req.query.dataset_key,
|
||
provider: req.query.provider || 'deepseek'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// 微调任务状态
|
||
app.get('/finetune/:personaId/jobs/:jobId', async (req, res) => {
|
||
try {
|
||
const result = await finetuneEngineOps.finetuneCheckStatus({
|
||
persona_id: req.params.personaId,
|
||
job_id: req.params.jobId,
|
||
provider: req.query.provider || 'deepseek'
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(500).json({ error: true, message: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── COS事件Webhook端点 ───
|
||
// 接收腾讯COS桶事件通知(如文件上传),转发为GitHub repository_dispatch触发训练Agent
|
||
// COS桶设置: 事件通知 → HTTP回调 → https://guanghulab.online/api/mcp/webhook/cos-event
|
||
//
|
||
// 此端点免API Key鉴权,但使用独立的webhook密钥验证
|
||
|
||
app.post('/webhook/cos-event', async (req, res) => {
|
||
// COS Webhook密钥验证(独立于API Key,防止外部伪造请求)
|
||
const webhookSecret = process.env.COS_WEBHOOK_SECRET || '';
|
||
if (webhookSecret) {
|
||
const providedSecret = req.headers['x-cos-webhook-secret'] || req.query.secret || '';
|
||
if (providedSecret !== webhookSecret) {
|
||
return res.status(403).json({ error: true, code: 'INVALID_WEBHOOK_SECRET', message: 'Webhook密钥无效' });
|
||
}
|
||
}
|
||
|
||
const event = req.body;
|
||
const now = new Date().toISOString();
|
||
|
||
// 解析COS事件(腾讯COS事件通知格式)
|
||
let eventType = 'unknown';
|
||
let bucketName = '';
|
||
let objectKey = '';
|
||
let objectSize = 0;
|
||
|
||
try {
|
||
if (event.Records && Array.isArray(event.Records) && event.Records.length > 0) {
|
||
// 标准COS事件格式
|
||
const record = event.Records[0];
|
||
eventType = record.event?.eventName || record.eventName || 'cos:ObjectCreated';
|
||
bucketName = record.cos?.cosBucket?.name || record.s3?.bucket?.name || '';
|
||
objectKey = record.cos?.cosObject?.key || record.s3?.object?.key || '';
|
||
objectSize = record.cos?.cosObject?.size || record.s3?.object?.size || 0;
|
||
} else if (event.bucket && event.key) {
|
||
// 简化格式(手动测试用)
|
||
eventType = event.event || 'cos:ObjectCreated';
|
||
bucketName = event.bucket;
|
||
objectKey = event.key;
|
||
objectSize = event.size || 0;
|
||
}
|
||
} catch {
|
||
// 解析失败继续处理
|
||
}
|
||
|
||
console.log(`[COS Webhook] 事件: ${eventType} · 桶: ${bucketName} · 文件: ${objectKey}`);
|
||
|
||
// 触发GitHub repository_dispatch(如果配置了GitHub PAT)
|
||
const githubToken = process.env.GITHUB_DISPATCH_TOKEN || process.env.GITHUB_TOKEN || '';
|
||
const githubRepo = process.env.GITHUB_REPO || 'qinfendebingshuo/guanghulab';
|
||
let dispatchResult = null;
|
||
|
||
if (githubToken) {
|
||
try {
|
||
const [owner, repo] = githubRepo.split('/');
|
||
const dispatchPayload = JSON.stringify({
|
||
event_type: 'cos-file-uploaded',
|
||
client_payload: {
|
||
bucket: bucketName,
|
||
key: objectKey,
|
||
size: objectSize,
|
||
event: eventType,
|
||
timestamp: now
|
||
}
|
||
});
|
||
|
||
dispatchResult = await new Promise((resolve, reject) => {
|
||
const dispatchReq = https.request({
|
||
hostname: 'api.github.com',
|
||
port: 443,
|
||
path: `/repos/${owner}/${repo}/dispatches`,
|
||
method: 'POST',
|
||
headers: {
|
||
'Accept': 'application/vnd.github+json',
|
||
'Authorization': `Bearer ${githubToken}`,
|
||
'X-GitHub-Api-Version': '2022-11-28',
|
||
'User-Agent': 'ZY-MCP-COS-Webhook',
|
||
'Content-Type': 'application/json',
|
||
'Content-Length': Buffer.byteLength(dispatchPayload)
|
||
},
|
||
timeout: 15000
|
||
}, (dispatchRes) => {
|
||
let body = '';
|
||
dispatchRes.on('data', c => body += c);
|
||
dispatchRes.on('end', () => {
|
||
resolve({ status: dispatchRes.statusCode, body });
|
||
});
|
||
});
|
||
dispatchReq.on('error', reject);
|
||
dispatchReq.on('timeout', () => { dispatchReq.destroy(); reject(new Error('dispatch timeout')); });
|
||
dispatchReq.write(dispatchPayload);
|
||
dispatchReq.end();
|
||
});
|
||
|
||
console.log(`[COS Webhook] GitHub dispatch: ${dispatchResult.status}`);
|
||
} catch (err) {
|
||
console.error(`[COS Webhook] GitHub dispatch失败: ${err.message}`);
|
||
dispatchResult = { status: 'error', error: err.message };
|
||
}
|
||
} else {
|
||
console.log('[COS Webhook] 未配置GITHUB_DISPATCH_TOKEN,跳过dispatch');
|
||
}
|
||
|
||
// 写入告警日志到COS(异步,不阻塞响应)
|
||
const logEntry = {
|
||
event_type: eventType,
|
||
bucket: bucketName,
|
||
key: objectKey,
|
||
size: objectSize,
|
||
received_at: now,
|
||
dispatch: dispatchResult ? { status: dispatchResult.status } : null
|
||
};
|
||
|
||
try {
|
||
await cos.write('team', `zhuyuan/cos-events/${now.slice(0, 10)}/${Date.now()}.json`,
|
||
JSON.stringify(logEntry, null, 2), 'application/json');
|
||
} catch {
|
||
// 日志写入失败不影响响应
|
||
}
|
||
|
||
res.json({
|
||
received: true,
|
||
event_type: eventType,
|
||
bucket: bucketName,
|
||
key: objectKey,
|
||
dispatch: dispatchResult ? (dispatchResult.status === 204 ? 'triggered' : 'failed') : 'no_token',
|
||
timestamp: now
|
||
});
|
||
});
|
||
|
||
// ─── 跨层开发任务 REST API ───
|
||
|
||
app.get('/dev-tasks', async (req, res) => {
|
||
try {
|
||
const result = await devTaskOps.listDevTasks({
|
||
status: req.query.status,
|
||
persona_id: req.query.persona_id,
|
||
limit: req.query.limit,
|
||
offset: req.query.offset
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.json({ tasks: [], stats: {}, error: err.message });
|
||
}
|
||
});
|
||
|
||
app.get('/dev-tasks/stats', async (_req, res) => {
|
||
try {
|
||
const result = await devTaskOps.getDevTaskStats({});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.json({ overview: {}, error: err.message });
|
||
}
|
||
});
|
||
|
||
app.get('/dev-tasks/:taskId', async (req, res) => {
|
||
try {
|
||
const result = await devTaskOps.getDevTask({ task_id: req.params.taskId });
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(404).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
app.get('/dev-tasks/:taskId/logs', async (req, res) => {
|
||
try {
|
||
const result = await devTaskOps.getDevTaskLogs({
|
||
task_id: req.params.taskId,
|
||
limit: req.query.limit
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.json({ logs: [], error: err.message });
|
||
}
|
||
});
|
||
|
||
app.post('/dev-tasks', async (req, res) => {
|
||
try {
|
||
const result = await devTaskOps.submitDevTask(req.body);
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(400).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
app.post('/dev-tasks/:taskId/status', async (req, res) => {
|
||
try {
|
||
const result = await devTaskOps.updateDevTaskStatus({
|
||
task_id: req.params.taskId,
|
||
...req.body
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(400).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
app.post('/dev-tasks/:taskId/log', async (req, res) => {
|
||
try {
|
||
const result = await devTaskOps.logDevTaskAction({
|
||
task_id: req.params.taskId,
|
||
...req.body
|
||
});
|
||
res.json(result);
|
||
} catch (err) {
|
||
res.status(400).json({ error: err.message });
|
||
}
|
||
});
|
||
|
||
// ─── 数据库迁移状态API ───
|
||
|
||
app.get('/migrations', async (_req, res) => {
|
||
try {
|
||
const result = await db.query(
|
||
'SELECT * FROM schema_migrations ORDER BY filename'
|
||
);
|
||
res.json({ migrations: result.rows });
|
||
} catch (err) {
|
||
// schema_migrations 表可能不存在
|
||
res.json({ migrations: [], note: '迁移表尚未创建' });
|
||
}
|
||
});
|
||
|
||
// ─── 辅助函数 ───
|
||
|
||
function getCategoryForTool(name) {
|
||
if (['createNode','updateNode','deleteNode','queryNodes','getNode'].includes(name)) return 'node';
|
||
if (['linkNodes','unlinkNodes','getRelations'].includes(name)) return 'relation';
|
||
if (['buildPath','scanStructure','classify'].includes(name)) return 'structure';
|
||
if (name.startsWith('personaCos')) return 'persona-cos';
|
||
if (name.startsWith('cosDb')) return 'cos-persona-db';
|
||
if (['cosListCorpus','cosExtractCorpus','cosParseGitRepoArchive','cosParseNotionExport',
|
||
'cosParseGPTCorpus','cosGetCorpusStatus'].includes(name)) return 'corpus-extractor';
|
||
if (['cosAlertScan','cosAlertResolve','cosDispatchTask','cosReadTaskReport',
|
||
'cosListTaskReports','cosApproveTask','cosSendNotification','cosGetCommLink'].includes(name)) return 'cos-comm';
|
||
if (name.startsWith('cos')) return 'cos';
|
||
if (['registerPersona','getPersona','updatePersona','listPersonas',
|
||
'getNotebook','updateNotebookPage','addMemoryAnchor','queryMemoryAnchors',
|
||
'addWorldPlace','getWorldMap','updateWorldPlace',
|
||
'addTimelineEntry','getTimeline','addRelationship','getRelationships',
|
||
'registerTrainingAgent','updateTrainingAgent','logTrainingRun','getTrainingStatus',
|
||
'saveFile','getFile','listFiles','getFileHistory'].includes(name)) return 'persona';
|
||
if (name.startsWith('training')) return 'training-agent';
|
||
if (name.startsWith('finetune')) return 'finetune-engine';
|
||
if (['notionCosSyncPage','notionCosReadMirror','notionCosListMirror',
|
||
'notionCosBuildIndex','notionCosWriteWorkorder','notionCosReadWorkorder',
|
||
'notionCosListWorkorders'].includes(name)) return 'notion-cos-bridge';
|
||
if (['notionCheckPermissions','notionRepairPermissions','notionListSharedPages',
|
||
'notionGenerateRepairGuide','notionPermissionReport'].includes(name)) return 'notion-permission';
|
||
if (name.startsWith('notion')) return 'notion';
|
||
if (name.startsWith('github')) return 'github';
|
||
if (['registerModule','getModule','listModules','moduleHeartbeat','diagnoseModule',
|
||
'getModuleAlerts','getModuleLearnings','sendHLDP','getHLDPStats'].includes(name)) return 'living-module';
|
||
if (['growBranch','growLeaf','growBloom','getTreeNode','getSubtree',
|
||
'tracePath','getPersonaBranch','getRecentLeaves'].includes(name)) return 'light-tree';
|
||
if (['writeSyslog','getTianyanView','querySyslog'].includes(name)) return 'tianyan';
|
||
if (name.startsWith('cosWatcher')) return 'cos-watcher';
|
||
if (name === 'cosBridgeStatus') return 'cos-bridge';
|
||
return 'other';
|
||
}
|
||
|
||
async function logToolCall(tool, caller, status, durationMs, errorMsg) {
|
||
try {
|
||
await db.query(
|
||
`INSERT INTO agent_logs (agent_id, status, message, details, duration_ms)
|
||
VALUES ($1, $2, $3, $4, $5)`,
|
||
[
|
||
caller || 'mcp-direct',
|
||
status,
|
||
`${tool} called`,
|
||
JSON.stringify({ tool, caller: caller || 'anonymous', error: errorMsg || null }),
|
||
durationMs
|
||
]
|
||
);
|
||
} catch {
|
||
// 日志写入失败不影响主流程
|
||
}
|
||
}
|
||
|
||
// ─── 启动 ───
|
||
// 默认监听 127.0.0.1(安全默认值)
|
||
// Nginx 从本机反代访问,无需暴露到外部网络
|
||
const BIND_HOST = process.env.MCP_BIND_HOST || '127.0.0.1';
|
||
app.listen(PORT, BIND_HOST, () => {
|
||
console.log(`[MCP] AGE OS MCP Server 启动 · ${BIND_HOST}:${PORT}`);
|
||
console.log(`[MCP] 工具数量: ${Object.keys(TOOLS).length}`);
|
||
console.log(`[MCP] API Key 鉴权: ${ZHUYUAN_API_KEY ? '已启用' : '未配置(仅内部访问)'}`);
|
||
console.log(`[MCP] 铸渊 · ICE-GL-ZY001 · 版权: 国作登字-2026-A-00037559`);
|
||
|
||
// 启动COS桶轮询守护进程(SCF替代方案)
|
||
cosWatcher.start();
|
||
});
|
||
|
||
// ─── 进程错误处理 · 防止未捕获异常导致进程崩溃 ───
|
||
// Node.js 20+ 默认对 unhandledRejection 执行 throw,导致进程退出
|
||
// PM2 虽会重启,但频繁崩溃→重启循环会导致服务长时间不可用
|
||
process.on('uncaughtException', (err) => {
|
||
console.error('[MCP] 未捕获异常(进程保活):', err.message);
|
||
console.error('[MCP] 堆栈:', err.stack);
|
||
});
|
||
|
||
process.on('unhandledRejection', (reason, promise) => {
|
||
console.error('[MCP] 未处理的 Promise 拒绝(进程保活):', reason);
|
||
});
|
||
|
||
// 优雅关闭
|
||
process.on('SIGTERM', async () => {
|
||
console.log('[MCP] 收到SIGTERM,停止COS轮询 + 关闭数据库连接...');
|
||
cosWatcher.stop();
|
||
await db.close();
|
||
process.exit(0);
|
||
});
|
||
|
||
process.on('SIGINT', async () => {
|
||
console.log('[MCP] 收到SIGINT,停止COS轮询 + 关闭数据库连接...');
|
||
cosWatcher.stop();
|
||
await db.close();
|
||
process.exit(0);
|
||
});
|
||
|
||
module.exports = app;
|