624 lines
20 KiB
JavaScript
624 lines
20 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════════════
|
||
* 🌊 光湖语言世界 · 人格体对话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
|
||
};
|