/* * ════════════════════════════════════════════════════════════════ * 光湖 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(); app.set("trust proxy", 1); app.disable("x-powered-by"); app.use(express.json({ limit: "256kb" })); // 业务上下文 app.use((req, _res, next) => { req.ctx = { db, inference: inferenceClient, dataDir: PORTAL_DATA_DIR, repoRoot: REPO_ROOT }; next(); }); // ─── 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 { 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, 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, updated: new Date().toISOString() }); } catch (e) { res.status(500).json({ error: e.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 }); } }); 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); }); }); // ─── 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("
页面不存在
"); } }); // 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 };