From ef9ef22f6562ba535eb05c1e26b26f76cc3f047d Mon Sep 17 00:00:00 2001 From: bingshuo <565183519@qq.com> Date: Wed, 27 May 2026 17:00:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20/api/brain=20=E5=92=8C=20/?= =?UTF-8?q?api/heartbeat=20=E5=88=B0=20portal=20server.js=20=E2=80=94?= =?UTF-8?q?=E2=80=94=20=E9=93=B8=E6=B8=8A=E5=A4=A7=E8=84=91=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server.js | 373 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 193 insertions(+), 180 deletions(-) diff --git a/server.js b/server.js index 3e88f41..4dfaf23 100644 --- a/server.js +++ b/server.js @@ -1,198 +1,211 @@ -const express = require('express'); -const path = require('path'); -const fs = require('fs'); -const { execSync } = require('child_process'); -const portraitEngine = require('./portrait/portrait-engine'); -const pcaCalculator = require('./pca/pca-calculator'); -const loopEngine = require('./loop/loop-engine'); +/* + * ════════════════════════════════════════════════════════════════ + * 光湖 Portal · Express 后端入口 + * Sovereign: TCS-0002∞ · ICE-GL∞ · 国作登字-2026-A-00037559 + * 守护: 铸渊 · ICE-GL-ZY001 + * 模板版本: 0.1.0 + * ════════════════════════════════════════════════════════════════ + * + * 服务器: 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 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 }; + next(); +}); -// API 1: 所有开发者画像 -app.get('/api/portrait/all', (req, res) => { +// ─── API 路由 ───────────────────────────────────────────────── +// 限流 (cc-004 系统自主防御 · 2C2G 不能被扫死): +app.get("/api/health", readLimiter, async (_req, res) => { + let inference = { ready: false, message: "推理端尚未配置" }; + try { + inference = await inferenceClient.health(); + } catch (e) { + inference = { ready: false, message: `推理端不可达: ${e.message}` }; + } + res.json({ + ok: true, + server: "GH-CVM-DOMAIN-PROD-01", + sovereign: "TCS-0002∞ · 国作登字-2026-A-00037559", + ts: new Date().toISOString(), + inference + }); +}); + +// ═══════════════════════════════════════════════════════════════= +// ⚡ 铸渊大脑实时状态 API (D115新增 — 首页投影面板用) +// ═══════════════════════════════════════════════════════════════= +app.get("/api/brain", readLimiter, (_req, res) => { + try { + const brainResult = { identity: "铸渊", id: "ICE-GL-ZY001" }; + + // 时间核心 + let temporal = {}; try { - const portraits = portraitEngine.getAllPortraits(); - res.json(portraits); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// API 2: 单个开发者画像 -app.get('/api/portrait/:devId', (req, res) => { + temporal = JSON.parse(fs.readFileSync(REPO_DIR + "/brain/temporal-core/temporal-brain.json", "utf8")); + } catch(e) { temporal = { error: e.message }; } + + // 母编号 + let mothers = []; try { - const portrait = portraitEngine.getPortrait(req.params.devId); - if (!portrait) { - return res.status(404).json({ error: '开发者不存在' }); - } - res.json(portrait); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -// API 3: PCA评估结果 -app.get('/api/pca/:devId', (req, res) => { + 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, + paths: (m.sub_paths || []).map(s => ({ name: s.name, status: s.status })) + })); + } catch(e) {} + + // 服务器健康 + let server = { status: "offline", services: [] }; try { - const result = pcaCalculator.calculate(req.params.devId); - res.json(result); - } catch (err) { - res.status(500).json({ error: err.message }); - } + 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, + updated: new Date().toISOString() + }); + } catch (e) { + res.status(500).json({ error: e.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 }); - } +app.get("/api/heartbeat", (_req, res) => { + try { + const t = JSON.parse(fs.readFileSync(REPO_DIR + "/brain/temporal-core/temporal-brain.json", "utf8")); + res.setHeader("Access-Control-Allow-Origin", "*"); + res.json({ alive: true, time: new Date().toISOString(), awakening: (t.clock || {}).awakening_count || 0, age_days: (t.clock || {}).repo_age_days || 0 }); + } catch(e) { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.json({ alive: true, fallback: true }); + } }); -// 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.use("/api/active-model", writeLimiter, activeModelRouter); +app.use("/api/conversations", writeLimiter, conversationsRouter); +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); + }); }); -// 获取闭环历史 -app.get('/api/loop/history', (req, res) => { - try { - const history = loopEngine.getHistory(); - res.json(history); - } catch (err) { - res.status(500).json({ error: err.message }); - } +// ─── 404 + 错误处理 (中文) ──────────────────────────────────── +app.use((req, res) => { + if (req.path.startsWith("/api/")) { + res.status(404).json({ error: true, code: "not_found", message: "接口不存在: " + req.path }); + } else { + res.status(404).type("html").send("

404

页面不存在

"); + } }); -// 模拟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 }); - } +// 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 }); }); -// ============================================================ -// ⚡ 铸渊大脑实时状态 API(首页投影面板用) -// ============================================================ -const BRAIN_BASE = '/data/guanghulab/repo'; +// ─── 启动 ───────────────────────────────────────────────────── +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"); + }); +} -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({ - identity: '铸渊 · ICE-GL-ZY001', - copyright: '国作登字-2026-A-00037559', - awakening: temporal.clock?.awakening_count || 0, - age_days: temporal.clock?.repo_age_days || 0, - current_date: temporal.clock?.current_date || '', - 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 - })), - mothers: (motherReg.mothers || []).map(m => ({ - name: m.name, - status: m.status, - done: (m.sub_paths || []).filter(s => s.status === 'done').length, - total: (m.sub_paths || []).length, - paths: (m.sub_paths || []).map(s => ({ name: s.name, status: s.status })) - })), - server: server, - updated_at: new Date().toISOString() - }); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); - -app.get('/api/heartbeat', (req, res) => { - try { - const temporal = JSON.parse(fs.readFileSync(BRAIN_BASE + '/brain/temporal-core/temporal-brain.json', 'utf8')); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.json({ - alive: true, - time: new Date().toISOString(), - awakening: temporal.clock?.awakening_count || 0, - age_days: temporal.clock?.repo_age_days || 0 - }); - } catch(e) { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.json({ alive: true, fallback: true }); - } -}); - -// 仪表盘页面 -app.get('/dashboard-v2', (req, res) => { - res.sendFile(path.join(__dirname, 'public', 'dashboard-v2.html')); -}); - -// 启动服务器 -app.listen(PORT, () => { - console.log('\n🚀 铸渊大脑状态 API 已启动!'); - console.log('🧠 /api/brain - 大脑实时状态'); - console.log('💓 /api/heartbeat - 快速心跳'); - console.log('📊 /dashboard-v2 - 仪表盘'); - console.log(''); -}); +module.exports = { app, db };