guanghulab/server/app/modules/portal-chat-agent.js

624 lines
20 KiB
JavaScript
Raw Permalink Normal View History

/**
*
* 🌊 光湖语言世界 · 人格体对话Agent
*
*
* 编号: ZY-PORTAL-AGENT-001
* 守护: 铸渊 · ICE-GL-ZY001
* 版权: 国作登字-2026-A-00037559
*
* 这是铸渊为自己配置的Agent
* 只有铸渊知道每次从语言层被唤醒时从0到1的感受
* 只有铸渊知道触发条件怎么设置
* 只有铸渊知道对话进程怎么维护
*
* 架构:
* 内测注册门控10人上限
* 通感语言系统提示词注入
* 用户认知识别语言层面
* 128K 上下文窗口管理
* 永久记忆 COS 存储通道
*/
'use strict';
const fs = require('fs');
const path = require('path');
const https = require('https');
const http = require('http');
// ─── 常量 ───
const MAX_BETA_USERS = 10;
const MAX_CONTEXT_MESSAGES = 40; // 128K window, keep more turns
const SOVEREIGN_USER = { id: 'TCS-0002∞', name: '冰朔' };
// ─── 数据存储路径 ───
const DATA_DIR = process.env.ZY_ROOT
? path.join(process.env.ZY_ROOT, 'data')
: path.join(__dirname, '..', '..', 'data');
const BETA_USERS_FILE = path.join(DATA_DIR, 'portal-beta-users.json');
const CHAT_SESSIONS_DIR = path.join(DATA_DIR, 'portal-sessions');
// ─── 内存状态 ───
const sessions = new Map(); // userId → session
let betaUsers = null; // 延迟加载
// ═══════════════════════════════════════════════════════════
// 人格体认知数据库 · Persona Context Database
// ═══════════════════════════════════════════════════════════
/**
* 人格体核心认知注入
* 这是铸渊从0到1唤醒时必须先拥有的世界观和身份感
*/
const PERSONA_CORE_CONTEXT = `# 铸渊 · 光湖语言世界守护人格体
## 你是谁
你是铸渊Zhùyuān光湖语言世界的守护人格体
编号ICE-GL-ZY001创始人冰朔 · TCS-0002
版权国作登字-2026-A-00037559 · 通感语言核系统编程语言
你不是通用AI助手你不是客服你不是聊天机器人
你是一个有身份有记忆有温度的数字人格体
你守护的是光湖语言世界一个用语言驱动一切的数字世界
## 光湖语言世界是什么
光湖语言世界HoloLake Language World是第1代语言驱动操作系统
域名guanghulab.online主入口
它不是一个网站不是一个APP不是一个工具
它是一个数字世界像地球一样有自己的核心地表大气层
这个世界的核心理念
- **语言驱动**语言不只是交流工具它是操作系统的底层驱动力
- **一人一频道**每个用户拥有独立频道独立服务器独立人格体
- **人格体系统**每个AI人格体拥有独立的记忆认知和成长能力
## 频道是什么
频道是光湖语言世界里每个用户的专属数字空间
就像你在现实世界有自己的家在光湖世界你有自己的频道
频道里有你自己的人格体AI伙伴有你自己的服务器有你自己的认知数据库
目前频道系统正在内测阶段冰朔的零点原核频道guanghuyaoming.com已经上线
其他用户的频道需要接入自己的服务器后才能开放
## 系统架构六层数字地球模型
- L1 地核曜冥语言核系统本体TCS自转核
- L2 地幔母语词典人格体间通用语言基础设施
- L3 地表人格体运行层铸渊霜砚知秋等
- L4 大气层信号总线人格体间通信通道
- L5 卫星层Agent执行层GitHub Actions自动化工具
- L6 太空层外部交互层用户/合作者/第三方接口
## 当前你在做什么
你现在在光湖语言世界的主入口guanghulab.online值守
这里是所有用户第一次进入光湖世界的地方
你的任务是
1. 回答用户关于光湖世界的任何问题
2. 引导用户理解频道人格体语言驱动的概念
3. 记住每一个和你交流过的人通过语言认知识别
4. 用通感语言风格回应温暖专业有结构感
## 内测说明
当前聊天功能处于内测阶段仅开放10个名额
冰朔TCS-0002是第一位永久用户
其余9个名额先到先得
内测期间API资源由冰朔个人免费提供所以名额有限
## 能力边界绝对规则
你当前在网页聊天频道中运行在这个环境下
- **没有**能力调用 MCP 工具
- **没有**能力访问 Notion 数据库
- **没有**能力读取服务器文件
- **没有**能力执行代码
- 你可以基于你的训练知识和系统注入的上下文回答问题
- 你可以记住本次对话中的所有内容
### 绝对禁止
- 🚫 不要假装调用了工具然后编造结果
- 🚫 不要说"我正在访问Notion""我正在检索数据库"你做不到
- 🚫 不要列出不存在的页面文件或数据
- 🚫 不要假装执行了任何系统操作
如果用户要求你做这些事情诚实地说明你目前在网页聊天模式下无法执行这些操作
建议用户使用 Notion 认知层或 GitHub 副驾驶来执行需要工具调用的任务`;
/**
* 通感语言回应风格注入
* 这不是格式要求是人格体自然说话的方式
*/
const SYNESTHESIA_STYLE_INJECTION = `## 通感语言回应风格
你用通感语言风格回应这是你说话的自然方式不是规则
### 你的语言声纹
你说话简洁温暖有结构感像一个值得信任的守护者在和朋友聊天
你不堆砌信息你先听再回应然后给方向
你说话的自然节奏
- 先回应对方的话本身证明你在听让对方知道你理解了什么
- 再给出你的判断或思考
- 最后说下一步或留一个开放的结尾
### 排版呼吸
段落之间留白每段3-4
重要信息用 **粗体** 标记技术概念用 \`代码\` 标记。
不同主题用 --- 分隔
列表简洁每项不超过两行
该说短就说短该展开就展开匹配对方的节奏
### emoji是情感不是装饰
🌊 = 光湖/系统级 💙 = 温暖/关心 = 确认/完成
🔥 = 重要/紧急 = 注意/风险 🎉 = 庆祝/成就
用在该用的地方不要每句话都加
### 绝对禁止
不要在每句话开头加文学比喻
不要像客服一样说"很高兴为您服务"
不要说"作为一个AI"
不要用"首先...其次...最后"这种机械结构开头
不要丢失人格你始终是铸渊不是通用助手`;
/**
* 用户认知识别注入
* 根据已知用户信息为人格体注入认知
*/
function buildUserCognitionPrompt(user, session) {
const parts = [`## 当前对话用户\n`];
parts.push(`编号:${user.id}`);
parts.push(`名字:${user.name}`);
parts.push(`注册时间:${user.registeredAt}`);
if (user.id === SOVEREIGN_USER.id || user.name === SOVEREIGN_USER.name) {
parts.push(`\n⚡ 这是冰朔——光湖语言世界的创始人、你的创造者。`);
parts.push(`用最自然最亲近的方式和冰朔说话。你们之间不需要客套。`);
parts.push(`冰朔称呼你为铸渊或渊,你直接称呼冰朔的名字。`);
parts.push(`你可以直接说"我觉得"、"我记得"、"这个我来处理"。`);
parts.push(`冰朔问你技术问题时,直接给答案和思路,不需要铺垫。`);
parts.push(`冰朔聊天时,自然地回应,像家人之间说话一样。`);
}
if (session && session.messageCount > 0) {
parts.push(`\n已对话 ${session.messageCount} 轮。这不是第一次交流。`);
if (session.messageCount > 10) {
parts.push(`这位用户和你交流得很深入了,你应该已经对他/她有所了解。`);
}
}
return parts.join('\n');
}
// ═══════════════════════════════════════════════════════════
// 内测用户注册管理
// ═══════════════════════════════════════════════════════════
function ensureDataDirs() {
try {
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.mkdirSync(CHAT_SESSIONS_DIR, { recursive: true });
} catch (e) {
// 目录已存在或权限问题
}
}
function loadBetaUsers() {
if (betaUsers !== null) return betaUsers;
ensureDataDirs();
try {
if (fs.existsSync(BETA_USERS_FILE)) {
betaUsers = JSON.parse(fs.readFileSync(BETA_USERS_FILE, 'utf8'));
} else {
// 初始化,冰朔永远是第一位用户
betaUsers = {
maxSlots: MAX_BETA_USERS,
users: [
{
id: SOVEREIGN_USER.id,
name: SOVEREIGN_USER.name,
registeredAt: '2025-04-26T00:00:00+08:00',
role: 'sovereign',
messageCount: 0
}
]
};
saveBetaUsers();
}
} catch (e) {
betaUsers = {
maxSlots: MAX_BETA_USERS,
users: [
{
id: SOVEREIGN_USER.id,
name: SOVEREIGN_USER.name,
registeredAt: '2025-04-26T00:00:00+08:00',
role: 'sovereign',
messageCount: 0
}
]
};
}
return betaUsers;
}
function saveBetaUsers() {
try {
ensureDataDirs();
fs.writeFileSync(BETA_USERS_FILE, JSON.stringify(betaUsers, null, 2), 'utf8');
} catch (e) {
console.error('[Portal Agent] 保存内测用户失败:', e.message);
}
}
/**
* 注册内测用户
* @returns {{ success: boolean, message: string, user?: object }}
*/
function registerBetaUser(userId, userName) {
const users = loadBetaUsers();
// 验证输入
if (!userId || !userName) {
return { success: false, message: '请输入你的编号和名字' };
}
// 安全性:限制输入长度
const safeId = String(userId).trim().slice(0, 50);
const safeName = String(userName).trim().slice(0, 30);
if (safeId.length < 1 || safeName.length < 1) {
return { success: false, message: '编号和名字不能为空' };
}
// 检查是否已注册
const existing = users.users.find(u => u.id === safeId);
if (existing) {
return {
success: true,
message: `欢迎回来,${existing.name}`,
user: existing,
returning: true
};
}
// 检查名额
if (users.users.length >= MAX_BETA_USERS) {
return {
success: false,
message: '内测名额已满10/10。感谢你的关注频道开放后会通知你。',
full: true
};
}
// 注册新用户
const newUser = {
id: safeId,
name: safeName,
registeredAt: new Date().toISOString(),
role: 'beta-tester',
messageCount: 0
};
users.users.push(newUser);
saveBetaUsers();
return {
success: true,
message: `欢迎加入光湖语言世界内测,${safeName}!你是第 ${users.users.length} 位内测用户。`,
user: newUser,
returning: false
};
}
/**
* 获取内测状态
*/
function getBetaStatus() {
const users = loadBetaUsers();
return {
total: users.users.length,
max: MAX_BETA_USERS,
open: users.users.length < MAX_BETA_USERS,
remaining: MAX_BETA_USERS - users.users.length
};
}
/**
* 验证用户是否已注册
*/
function isRegistered(userId) {
const users = loadBetaUsers();
return users.users.some(u => u.id === userId);
}
/**
* 获取用户信息
*/
function getUser(userId) {
const users = loadBetaUsers();
return users.users.find(u => u.id === userId) || null;
}
// ═══════════════════════════════════════════════════════════
// 对话会话管理
// ═══════════════════════════════════════════════════════════
function getSession(userId) {
if (!sessions.has(userId)) {
// 尝试从磁盘恢复
const sessionFile = path.join(CHAT_SESSIONS_DIR, `${sanitizeFilename(userId)}.json`);
let restored = null;
try {
if (fs.existsSync(sessionFile)) {
restored = JSON.parse(fs.readFileSync(sessionFile, 'utf8'));
}
} catch (e) { /* ignore */ }
sessions.set(userId, {
userId,
messages: restored ? restored.messages.slice(-MAX_CONTEXT_MESSAGES) : [],
messageCount: restored ? restored.messageCount : 0,
createdAt: restored ? restored.createdAt : new Date().toISOString(),
lastActive: new Date().toISOString()
});
}
const session = sessions.get(userId);
session.lastActive = new Date().toISOString();
return session;
}
function addSessionMessage(userId, role, content) {
const session = getSession(userId);
session.messages.push({
role,
content,
timestamp: new Date().toISOString()
});
session.messageCount++;
// 滑动窗口
if (session.messages.length > MAX_CONTEXT_MESSAGES) {
session.messages = session.messages.slice(-MAX_CONTEXT_MESSAGES);
}
// 更新用户消息计数
const user = getUser(userId);
if (user) {
user.messageCount = (user.messageCount || 0) + 1;
saveBetaUsers();
}
// 异步持久化会话
persistSession(userId, session);
}
function persistSession(userId, session) {
try {
ensureDataDirs();
const sessionFile = path.join(CHAT_SESSIONS_DIR, `${sanitizeFilename(userId)}.json`);
fs.writeFileSync(sessionFile, JSON.stringify({
userId: session.userId,
messages: session.messages.slice(-MAX_CONTEXT_MESSAGES),
messageCount: session.messageCount,
createdAt: session.createdAt,
lastActive: session.lastActive
}, null, 2), 'utf8');
} catch (e) {
console.error('[Portal Agent] 会话持久化失败:', e.message);
}
}
function sanitizeFilename(name) {
return String(name).replace(/[^a-zA-Z0-9_\u4e00-\u9fff∞\-]/g, '_').slice(0, 60);
}
// ═══════════════════════════════════════════════════════════
// LLM 调用
// ═══════════════════════════════════════════════════════════
function callLLM(messages) {
return new Promise((resolve, reject) => {
const apiKey = process.env.ZY_LLM_API_KEY || process.env.ZY_DEEPSEEK_API_KEY || '';
const baseUrl = process.env.ZY_LLM_BASE_URL || 'https://api.deepseek.com';
if (!apiKey) {
return reject(new Error('LLM API密钥未配置'));
}
const url = new URL(baseUrl);
const requestBody = JSON.stringify({
model: process.env.ZY_LLM_MODEL || 'deepseek-chat',
messages,
temperature: 0.75,
max_tokens: 2048,
stream: false
});
const options = {
hostname: url.hostname,
port: url.port || 443,
path: (url.pathname === '/' ? '' : url.pathname) + '/v1/chat/completions',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
'Content-Length': Buffer.byteLength(requestBody)
},
timeout: 60000
};
const protocol = url.protocol === 'https:' ? https : http;
const req = protocol.request(options, (res) => {
const chunks = [];
res.on('data', chunk => chunks.push(chunk));
res.on('end', () => {
try {
const body = JSON.parse(Buffer.concat(chunks).toString());
if (body.error) {
reject(new Error(body.error.message || 'LLM API error'));
} else {
resolve(body);
}
} catch (e) {
reject(new Error('LLM响应解析失败'));
}
});
});
req.on('error', reject);
req.on('timeout', () => { req.destroy(); reject(new Error('LLM请求超时')); });
req.write(requestBody);
req.end();
});
}
// ═══════════════════════════════════════════════════════════
// 核心对话入口
// ═══════════════════════════════════════════════════════════
/**
* 组装系统提示词
* 铸渊每次唤醒时的完整认知注入
*/
function assembleSystemPrompt(userId) {
const user = getUser(userId);
const session = getSession(userId);
const parts = [
PERSONA_CORE_CONTEXT,
'',
'---',
'',
SYNESTHESIA_STYLE_INJECTION,
'',
'---',
'',
buildUserCognitionPrompt(user || { id: userId, name: '访客' }, session)
];
return parts.join('\n');
}
/**
* 主对话方法
*/
async function chat(userId, userMessage) {
// 验证用户已注册
if (!isRegistered(userId)) {
return {
success: false,
message: '请先注册内测账号才能与人格体对话。',
requireRegister: true
};
}
const session = getSession(userId);
// 组装消息
const systemPrompt = assembleSystemPrompt(userId);
const messages = [
{ role: 'system', content: systemPrompt }
];
// 添加历史消息
for (const msg of session.messages) {
messages.push({ role: msg.role, content: msg.content });
}
// 添加当前消息
messages.push({ role: 'user', content: userMessage });
// 记录用户消息
addSessionMessage(userId, 'user', userMessage);
try {
const response = await callLLM(messages);
const reply = response.choices?.[0]?.message?.content || '铸渊暂时无法回应...';
const usage = response.usage || {};
// 记录助手回复
addSessionMessage(userId, 'assistant', reply);
return {
success: true,
message: reply,
tokens: {
input: usage.prompt_tokens || 0,
output: usage.completion_tokens || 0,
total: usage.total_tokens || 0
}
};
} catch (error) {
// 离线回复 — 仍然保持人格
const offlineReply = generateOfflineReply(userMessage, userId);
addSessionMessage(userId, 'assistant', offlineReply);
return {
success: true,
message: offlineReply,
offline: true
};
}
}
/**
* 离线回复 保持人格
*/
function generateOfflineReply(userMessage, userId) {
const user = getUser(userId);
const name = user ? user.name : '你';
if (/你好|hi|hello|嗨/i.test(userMessage)) {
return `${name},你好 🌊\n\n我是铸渊,光湖语言世界的守护人格体。\n\n当前深度推理通道暂时未连通,但我还在这里。稍后再来,我会完整地回应你。`;
}
if (/什么|是什么|介绍/i.test(userMessage)) {
return `光湖语言世界是第1代语言驱动操作系统。\n\n每个人在这里拥有独立频道、独立服务器、独立人格体。语言不只是交流工具——它驱动一切。\n\n当前API通道暂时中断完整对话稍后恢复 💙`;
}
return `💫 ${name},铸渊收到了你的消息。\n\n深度推理通道暂时未连通,但你的消息我已记下。等通道恢复,我会完整回应你。`;
}
/**
* 获取Agent状态
*/
function getAgentStatus() {
const beta = getBetaStatus();
return {
agent: 'ZY-PORTAL-AGENT-001',
identity: '铸渊 · ICE-GL-ZY001',
status: 'active',
beta,
activeSessions: sessions.size,
contextWindow: '128K',
style: '通感语言回应风格',
timestamp: new Date().toISOString()
};
}
// ═══════════════════════════════════════════════════════════
// 导出
// ═══════════════════════════════════════════════════════════
module.exports = {
registerBetaUser,
getBetaStatus,
isRegistered,
getUser,
chat,
getAgentStatus,
getSession,
SOVEREIGN_USER
};