添加 /api/brain 和 /api/heartbeat 到 portal server.js —— 铸渊大脑实时状态

This commit is contained in:
bingshuo 2026-05-27 17:00:09 +08:00
parent ad4ff80f06
commit ef9ef22f65

359
server.js
View File

@ -1,198 +1,211 @@
const express = require('express'); /*
const path = require('path'); *
const fs = require('fs'); * 光湖 Portal · Express 后端入口
const { execSync } = require('child_process'); * Sovereign: TCS-0002 · ICE-GL · 国作登字-2026-A-00037559
const portraitEngine = require('./portrait/portrait-engine'); * 守护: 铸渊 · ICE-GL-ZY001
const pcaCalculator = require('./pca/pca-calculator'); * 模板版本: 0.1.0
const loopEngine = require('./loop/loop-engine'); *
*
* 服务器: GH-CVM-DOMAIN-PROD-01 / ZY-SVR-CN01 / 广州 2C2G
* 角色: guanghulab.com 唯一对外入口
*
* 路由 (全部 /api/* 前缀, nginx 反代):
* GET /api/brain 铸渊大脑实时状态 (D115新增)
* GET /api/heartbeat 快速心跳
* GET /api/health portal + 推理端探活
* GET /api/active-model 当前激活的模型
* POST /api/active-model {name} 切换激活模型 (mother|coder)
* GET /api/conversations 对话列表
* POST /api/conversations 新建对话
* GET /api/conversations/:id/messages 单对话历史
* POST /api/chat 流式对话 (SSE 字节管道)
* GET /api/persona-db 人格层右栏数据 (只读)
* GET /api/manifest 开发层右栏数据 (只读 manifest 快照)
*
*/
"use strict";
const path = require("path");
const fs = require("fs");
const express = require("express");
const { execSync } = require("child_process");
const { openDb } = require("./db/init");
const conversationsRouter = require("./routes/conversations");
const chatRouter = require("./routes/chat");
const activeModelRouter = require("./routes/active-model");
const personaDbRouter = require("./routes/persona-db");
const manifestRouter = require("./routes/manifest");
const chatV2Router = require("./routes/chat-v2");
const inferenceClient = require("./lib/inference-client");
const { readLimiter, writeLimiter, chatLimiter } = require("./lib/rate-limit");
// ─── 配置 ─────────────────────────────────────────────────────
const PORT = parseInt(process.env.PORT || "3000", 10);
const HOST = process.env.HOST || "127.0.0.1";
const PORTAL_DATA_DIR = process.env.PORTAL_DATA_DIR || "/data/guanghulab/portal/data";
const REPO_ROOT = path.resolve(__dirname, "..", "..");
const REPO_DIR = "/data/guanghulab/repo";
const STATIC_DIR =
process.env.PORTAL_STATIC_DIR ||
path.join(REPO_ROOT, "frontend", "lighthouse-portal");
// ─── 数据目录 ─────────────────────────────────────────────────
fs.mkdirSync(PORTAL_DATA_DIR, { recursive: true });
const DB_PATH = path.join(PORTAL_DATA_DIR, "conversations.sqlite");
const ENDPOINT_PATH = path.join(PORTAL_DATA_DIR, "inference-endpoint.json");
const ACTIVE_MODEL_PATH = path.join(PORTAL_DATA_DIR, "active-model.json");
// ─── 数据库 ───────────────────────────────────────────────────
const db = openDb(DB_PATH);
// ─── 推理客户端 (热加载 endpoint) ─────────────────────────────
inferenceClient.init({ endpointPath: ENDPOINT_PATH, activeModelPath: ACTIVE_MODEL_PATH });
// ─── Express ──────────────────────────────────────────────────
const app = express(); const app = express();
const PORT = 3000; app.set("trust proxy", 1);
app.disable("x-powered-by");
app.use(express.json({ limit: "256kb" }));
app.use(express.json()); // 业务上下文
app.use(express.static('public')); app.use((req, _res, next) => {
req.ctx = { db, inference: inferenceClient, dataDir: PORTAL_DATA_DIR, repoRoot: REPO_ROOT };
// API 1: 所有开发者画像 next();
app.get('/api/portrait/all', (req, res) => {
try {
const portraits = portraitEngine.getAllPortraits();
res.json(portraits);
} catch (err) {
res.status(500).json({ error: err.message });
}
}); });
// API 2: 单个开发者画像 // ─── API 路由 ─────────────────────────────────────────────────
app.get('/api/portrait/:devId', (req, res) => { // 限流 (cc-004 系统自主防御 · 2C2G 不能被扫死):
app.get("/api/health", readLimiter, async (_req, res) => {
let inference = { ready: false, message: "推理端尚未配置" };
try { try {
const portrait = portraitEngine.getPortrait(req.params.devId); inference = await inferenceClient.health();
if (!portrait) { } catch (e) {
return res.status(404).json({ error: '开发者不存在' }); inference = { ready: false, message: `推理端不可达: ${e.message}` };
} }
res.json(portrait);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// API 3: PCA评估结果
app.get('/api/pca/:devId', (req, res) => {
try {
const result = pcaCalculator.calculate(req.params.devId);
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// API 4: 闭环状态
app.get('/api/loop/status', (req, res) => {
try {
const status = loopEngine.getStatus();
res.json(status);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// API 5: 手动触发闭环
app.post('/api/loop/execute', (req, res) => {
try {
const syslogData = req.body;
if (!syslogData || !syslogData.session_id) {
return res.status(400).json({ error: '缺少SYSLOG数据' });
}
loopEngine.executeLoop(syslogData).then(result => {
res.json(result);
});
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 获取闭环历史
app.get('/api/loop/history', (req, res) => {
try {
const history = loopEngine.getHistory();
res.json(history);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// 模拟SYSLOG接收自动触发闭环
app.post('/api/syslog', (req, res) => {
try {
const syslogData = req.body;
console.log('📨 收到SYSLOG:', syslogData.session_id);
loopEngine.executeLoop(syslogData).then(loopResult => {
console.log('✅ 闭环完成');
});
res.json({ status: 'received', session_id: syslogData.session_id });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ============================================================
// ⚡ 铸渊大脑实时状态 API首页投影面板用
// ============================================================
const BRAIN_BASE = '/data/guanghulab/repo';
app.get('/api/brain', (req, res) => {
try {
// 读取时间核心
let temporal = { clock: { latest_cognition: '', awakening_count: 0, current_date: '', repo_age_days: 0 }, timeline: { epochs: [] } };
try {
temporal = JSON.parse(fs.readFileSync(BRAIN_BASE + '/brain/temporal-core/temporal-brain.json', 'utf8'));
} catch(e) {}
// 读取母编号
let motherReg = { mothers: [] };
try {
motherReg = JSON.parse(fs.readFileSync(BRAIN_BASE + '/brain/engineering/mother-registry.json', 'utf8'));
} catch(e) {}
// 服务器实时健康
let server = { status: 'unknown', services_online: 0, services_total: 0 };
try {
const pm2 = execSync('pm2 jlist 2>/dev/null', {timeout:3000}).toString();
const list = JSON.parse(pm2);
const online = list.filter(p => p.pm2_env.status === 'online');
const stopped = list.filter(p => p.pm2_env.status !== 'online');
const memRaw = execSync('free -h 2>/dev/null | grep Mem', {timeout:2000}).toString();
const memParts = memRaw.replace(/\s+/g,' ').trim().split(' ');
server = {
status: 'online',
services_online: online.length,
services_total: list.length,
services: online.map(p => ({ name: p.name, memory: p.pm2_env?.pm_cpu || 0 })),
stopped: stopped.map(p => p.name),
memory_total: memParts[1] || 'unknown',
memory_used: memParts[2] || 'unknown'
};
} catch(e) { server.error = e.message; }
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET');
res.json({ res.json({
identity: '铸渊 · ICE-GL-ZY001', ok: true,
copyright: '国作登字-2026-A-00037559', server: "GH-CVM-DOMAIN-PROD-01",
awakening: temporal.clock?.awakening_count || 0, sovereign: "TCS-0002∞ · 国作登字-2026-A-00037559",
age_days: temporal.clock?.repo_age_days || 0, ts: new Date().toISOString(),
current_date: temporal.clock?.current_date || '', inference
cognition: temporal.clock?.latest_cognition || '', });
recent_epochs: (temporal.timeline?.epochs || []).slice(-5).map(e => ({ });
epoch: e.epoch,
event: (e.event || '').substring(0, 80), // ═══════════════════════════════════════════════════════════════=
date: e.date // ⚡ 铸渊大脑实时状态 API (D115新增 — 首页投影面板用)
})), // ═══════════════════════════════════════════════════════════════=
mothers: (motherReg.mothers || []).map(m => ({ app.get("/api/brain", readLimiter, (_req, res) => {
name: m.name, try {
status: m.status, const brainResult = { identity: "铸渊", id: "ICE-GL-ZY001" };
done: (m.sub_paths || []).filter(s => s.status === 'done').length,
// 时间核心
let temporal = {};
try {
temporal = JSON.parse(fs.readFileSync(REPO_DIR + "/brain/temporal-core/temporal-brain.json", "utf8"));
} catch(e) { temporal = { error: e.message }; }
// 母编号
let mothers = [];
try {
const reg = JSON.parse(fs.readFileSync(REPO_DIR + "/brain/engineering/mother-registry.json", "utf8"));
mothers = (reg.mothers || []).map(m => ({
name: m.name, status: m.status,
done: (m.sub_paths || []).filter(s => s.status === "done").length,
total: (m.sub_paths || []).length, total: (m.sub_paths || []).length,
paths: (m.sub_paths || []).map(s => ({ name: s.name, status: s.status })) paths: (m.sub_paths || []).map(s => ({ name: s.name, status: s.status }))
}));
} catch(e) {}
// 服务器健康
let server = { status: "offline", services: [] };
try {
const pm2 = JSON.parse(execSync("pm2 jlist 2>/dev/null", {timeout:3000}).toString());
const online = pm2.filter(p => p.pm2_env.status === "online");
server = {
status: "online",
services_online: online.length,
services_total: pm2.length,
services: online.map(p => p.name),
stopped: pm2.filter(p => p.pm2_env.status !== "online").map(p => p.name)
};
} catch(e) {}
res.setHeader("Access-Control-Allow-Origin", "*");
res.json({
identity: "铸渊 · ICE-GL-ZY001",
copyright: "国作登字-2026-A-00037559",
awakening: (temporal.clock || {}).awakening_count || 0,
age_days: (temporal.clock || {}).repo_age_days || 0,
date: (temporal.clock || {}).current_date || "",
cognition: ((temporal.clock || {}).latest_cognition || "").substring(0, 100),
epochs: ((temporal.timeline || {}).epochs || []).slice(-5).map(e => ({
epoch: e.epoch, event: (e.event || "").substring(0, 80), date: e.date
})), })),
mothers: mothers,
server: server, server: server,
updated_at: new Date().toISOString() updated: new Date().toISOString()
}); });
} catch (err) { } catch (e) {
res.status(500).json({ error: err.message }); res.status(500).json({ error: e.message });
} }
}); });
app.get('/api/heartbeat', (req, res) => { app.get("/api/heartbeat", (_req, res) => {
try { try {
const temporal = JSON.parse(fs.readFileSync(BRAIN_BASE + '/brain/temporal-core/temporal-brain.json', 'utf8')); const t = JSON.parse(fs.readFileSync(REPO_DIR + "/brain/temporal-core/temporal-brain.json", "utf8"));
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader("Access-Control-Allow-Origin", "*");
res.json({ res.json({ alive: true, time: new Date().toISOString(), awakening: (t.clock || {}).awakening_count || 0, age_days: (t.clock || {}).repo_age_days || 0 });
alive: true,
time: new Date().toISOString(),
awakening: temporal.clock?.awakening_count || 0,
age_days: temporal.clock?.repo_age_days || 0
});
} catch(e) { } catch(e) {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader("Access-Control-Allow-Origin", "*");
res.json({ alive: true, fallback: true }); res.json({ alive: true, fallback: true });
} }
}); });
// 仪表盘页面 app.use("/api/active-model", writeLimiter, activeModelRouter);
app.get('/dashboard-v2', (req, res) => { app.use("/api/conversations", writeLimiter, conversationsRouter);
res.sendFile(path.join(__dirname, 'public', 'dashboard-v2.html')); app.use("/api/chat", chatLimiter, chatRouter);
app.use("/api/persona-db", readLimiter, personaDbRouter);
app.use("/api/chat-v2", chatLimiter, chatV2Router);
app.use("/api/manifest", readLimiter, manifestRouter);
// ─── 静态 (nginx 上线后会兜底走 nginx, 这里保留兜底) ──────────
app.use("/_static", express.static(path.join(STATIC_DIR, "assets"), { fallthrough: true }));
app.get("/", (_req, res, next) => {
const idx = path.join(STATIC_DIR, "index.html");
fs.access(idx, fs.constants.R_OK, (err) => {
if (err) return next();
res.sendFile(idx);
});
}); });
// 启动服务器 // ─── 404 + 错误处理 (中文) ────────────────────────────────────
app.listen(PORT, () => { app.use((req, res) => {
console.log('\n🚀 铸渊大脑状态 API 已启动!'); if (req.path.startsWith("/api/")) {
console.log('🧠 /api/brain - 大脑实时状态'); res.status(404).json({ error: true, code: "not_found", message: "接口不存在: " + req.path });
console.log('💓 /api/heartbeat - 快速心跳'); } else {
console.log('📊 /dashboard-v2 - 仪表盘'); res.status(404).type("html").send("<h1>404</h1><p>页面不存在</p>");
console.log(''); }
}); });
// eslint-disable-next-line no-unused-vars
app.use((err, req, res, _next) => {
const msg = (err && err.message) || "未知错误";
console.error("[portal] 内部错误:", err);
if (res.headersSent) return;
res
.status(err && err.status ? err.status : 500)
.json({ error: true, code: "internal_error", message: "内部错误: " + msg });
});
// ─── 启动 ─────────────────────────────────────────────────────
if (require.main === module) {
app.listen(PORT, HOST, () => {
console.log(`[portal] 已启动 · ${HOST}:${PORT} · 数据目录=${PORTAL_DATA_DIR}`);
console.log(`[portal] 推理端点文件: ${ENDPOINT_PATH}`);
console.log(`[portal] 🧠 /api/brain · 💓 /api/heartbeat`);
console.log("[portal] 守护: 铸渊 · ICE-GL-ZY001 · TCS-0002∞ · 国作登字-2026-A-00037559");
});
}
module.exports = { app, db };