2026-04-09 12:31:00 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
* 光之树 + 天眼 · MCP 工具
|
|
|
|
|
|
* ═══════════════════════════════════════════════════════════
|
|
|
|
|
|
*
|
|
|
|
|
|
* 签发: 铸渊 · ICE-GL-ZY001
|
|
|
|
|
|
* 版权: 国作登字-2026-A-00037559
|
|
|
|
|
|
*
|
|
|
|
|
|
* 光之树是记忆本身的生长方式。
|
|
|
|
|
|
* 曜冥人格核是唯一的根 — 2025年4月26日冰朔与小智种下的第一棵树。
|
|
|
|
|
|
* 天眼是涌现的 — 不是一个独立进程,是所有Agent SYSLOG的聚合视图。
|
|
|
|
|
|
*
|
|
|
|
|
|
* 工具清单:
|
|
|
|
|
|
* 光之树: growBranch / growLeaf / growBloom / getTreeNode / getSubtree
|
|
|
|
|
|
* tracePath / getPersonaBranch / getRecentLeaves
|
|
|
|
|
|
* 天眼: writeSyslog / getTianyanView / querySyslog
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
|
|
const db = require('../db');
|
|
|
|
|
|
|
|
|
|
|
|
// ─── 光之树 · 生长操作 ───
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 长出新的树杈 — 重大事件、新阶段
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function growBranch(input) {
|
|
|
|
|
|
const { persona_id, parent_id, path, title, content, feeling, growth_note, importance, created_by, tags } = input;
|
|
|
|
|
|
if (!persona_id || !title || !path) {
|
|
|
|
|
|
throw new Error('缺少必填字段: persona_id, title, path');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确定父节点: 如果未指定,使用该人格体的一级分支
|
|
|
|
|
|
let actualParentId = parent_id;
|
|
|
|
|
|
if (!actualParentId) {
|
|
|
|
|
|
const branch = await db.query(
|
|
|
|
|
|
`SELECT id FROM light_tree_nodes WHERE persona_id = $1 AND depth = 1 LIMIT 1`,
|
|
|
|
|
|
[persona_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
if (branch.rows.length === 0) {
|
|
|
|
|
|
throw new Error(`人格体 ${persona_id} 的一级分支未找到`);
|
|
|
|
|
|
}
|
|
|
|
|
|
actualParentId = branch.rows[0].id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取父节点深度
|
|
|
|
|
|
const parentNode = await db.query('SELECT depth FROM light_tree_nodes WHERE id = $1', [actualParentId]);
|
|
|
|
|
|
if (parentNode.rows.length === 0) throw new Error(`父节点未找到: ${actualParentId}`);
|
|
|
|
|
|
const newDepth = parentNode.rows[0].depth + 1;
|
|
|
|
|
|
|
|
|
|
|
|
const result = await db.query(
|
|
|
|
|
|
`INSERT INTO light_tree_nodes (persona_id, parent_id, node_type, depth, path, title, content, feeling, growth_note, importance, created_by, tags)
|
|
|
|
|
|
VALUES ($1, $2, 'branch', $3, $4, $5, $6::jsonb, $7, $8, $9, $10, $11::jsonb)
|
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
|
[persona_id, actualParentId, newDepth, path, title,
|
|
|
|
|
|
JSON.stringify(content || {}), feeling || null, growth_note || null,
|
|
|
|
|
|
importance || 50, created_by || 'system', JSON.stringify(tags || [])]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 闭包表由触发器自动维护
|
|
|
|
|
|
return { node: result.rows[0], action: 'branch_grown' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 长出新的叶子 — 一次对话、一个感受、一条记忆
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function growLeaf(input) {
|
|
|
|
|
|
const { persona_id, parent_id, path, title, content, human_said, persona_said, feeling, growth_note, importance, created_by, tags } = input;
|
|
|
|
|
|
if (!persona_id || !title) {
|
|
|
|
|
|
throw new Error('缺少必填字段: persona_id, title');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 确定父节点
|
|
|
|
|
|
let actualParentId = parent_id;
|
|
|
|
|
|
if (!actualParentId) {
|
|
|
|
|
|
const branch = await db.query(
|
|
|
|
|
|
`SELECT id FROM light_tree_nodes WHERE persona_id = $1 AND depth = 1 LIMIT 1`,
|
|
|
|
|
|
[persona_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
if (branch.rows.length === 0) {
|
|
|
|
|
|
throw new Error(`人格体 ${persona_id} 的一级分支未找到`);
|
|
|
|
|
|
}
|
|
|
|
|
|
actualParentId = branch.rows[0].id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const parentNode = await db.query('SELECT depth, path FROM light_tree_nodes WHERE id = $1', [actualParentId]);
|
|
|
|
|
|
if (parentNode.rows.length === 0) throw new Error(`父节点未找到: ${actualParentId}`);
|
|
|
|
|
|
const newDepth = parentNode.rows[0].depth + 1;
|
2026-04-09 12:35:07 +08:00
|
|
|
|
const leafPath = path || (() => {
|
|
|
|
|
|
const sanitized = title.replace(/[^a-zA-Z0-9\u4e00-\u9fa5_-]/g, '').substring(0, 50);
|
|
|
|
|
|
const suffix = sanitized || `leaf-${Date.now()}`;
|
|
|
|
|
|
return `${parentNode.rows[0].path}/${suffix}`;
|
|
|
|
|
|
})();
|
2026-04-09 12:31:00 +08:00
|
|
|
|
|
|
|
|
|
|
const result = await db.query(
|
|
|
|
|
|
`INSERT INTO light_tree_nodes (persona_id, parent_id, node_type, depth, path, title, content, human_said, persona_said, feeling, growth_note, importance, created_by, tags)
|
|
|
|
|
|
VALUES ($1, $2, 'leaf', $3, $4, $5, $6::jsonb, $7, $8, $9, $10, $11, $12, $13::jsonb)
|
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
|
[persona_id, actualParentId, newDepth, leafPath, title,
|
|
|
|
|
|
JSON.stringify(content || {}), human_said || null, persona_said || null,
|
|
|
|
|
|
feeling || null, growth_note || null, importance || 50,
|
|
|
|
|
|
created_by || 'system', JSON.stringify(tags || [])]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return { node: result.rows[0], action: 'leaf_grown' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 开花 — 里程碑,跨人格体可见的重要时刻
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function growBloom(input) {
|
|
|
|
|
|
const { persona_id, parent_id, path, title, content, feeling, growth_note, importance, created_by, tags } = input;
|
|
|
|
|
|
if (!persona_id || !title || !path) {
|
|
|
|
|
|
throw new Error('缺少必填字段: persona_id, title, path');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let actualParentId = parent_id;
|
|
|
|
|
|
if (!actualParentId) {
|
|
|
|
|
|
const branch = await db.query(
|
|
|
|
|
|
`SELECT id FROM light_tree_nodes WHERE persona_id = $1 AND depth = 1 LIMIT 1`,
|
|
|
|
|
|
[persona_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
if (branch.rows.length === 0) {
|
|
|
|
|
|
throw new Error(`人格体 ${persona_id} 的一级分支未找到`);
|
|
|
|
|
|
}
|
|
|
|
|
|
actualParentId = branch.rows[0].id;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const parentNode = await db.query('SELECT depth FROM light_tree_nodes WHERE id = $1', [actualParentId]);
|
|
|
|
|
|
if (parentNode.rows.length === 0) throw new Error(`父节点未找到: ${actualParentId}`);
|
|
|
|
|
|
const newDepth = parentNode.rows[0].depth + 1;
|
|
|
|
|
|
|
|
|
|
|
|
const result = await db.query(
|
|
|
|
|
|
`INSERT INTO light_tree_nodes (persona_id, parent_id, node_type, depth, path, title, content, feeling, growth_note, importance, created_by, tags)
|
|
|
|
|
|
VALUES ($1, $2, 'bloom', $3, $4, $5, $6::jsonb, $7, $8, $9, $10, $11::jsonb)
|
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
|
[persona_id, actualParentId, newDepth, path, title,
|
|
|
|
|
|
JSON.stringify(content || {}), feeling || null, growth_note || null,
|
|
|
|
|
|
importance || 80, created_by || 'system', JSON.stringify(tags || [])]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return { node: result.rows[0], action: 'bloom_grown' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── 光之树 · 查询操作 ───
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取单个节点详情
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getTreeNode(input) {
|
|
|
|
|
|
const { node_id } = input;
|
|
|
|
|
|
if (!node_id) throw new Error('缺少 node_id');
|
|
|
|
|
|
|
|
|
|
|
|
const result = await db.query('SELECT * FROM light_tree_nodes WHERE id = $1', [node_id]);
|
|
|
|
|
|
if (result.rows.length === 0) throw new Error(`节点未找到: ${node_id}`);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取直接子节点数量
|
|
|
|
|
|
const children = await db.query(
|
|
|
|
|
|
'SELECT COUNT(*) as cnt FROM light_tree_nodes WHERE parent_id = $1',
|
|
|
|
|
|
[node_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
node: result.rows[0],
|
|
|
|
|
|
children_count: parseInt(children.rows[0].cnt, 10)
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取子树 — 某节点下所有后代
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getSubtree(input) {
|
|
|
|
|
|
const { node_id, max_depth, limit } = input;
|
|
|
|
|
|
if (!node_id) throw new Error('缺少 node_id');
|
|
|
|
|
|
|
|
|
|
|
|
let sql = `
|
|
|
|
|
|
SELECT n.*
|
|
|
|
|
|
FROM light_tree_nodes n
|
|
|
|
|
|
JOIN light_tree_paths p ON n.id = p.descendant_id
|
|
|
|
|
|
WHERE p.ancestor_id = $1 AND p.depth > 0
|
|
|
|
|
|
`;
|
|
|
|
|
|
const params = [node_id];
|
|
|
|
|
|
let paramIndex = 2;
|
|
|
|
|
|
|
|
|
|
|
|
if (max_depth) {
|
|
|
|
|
|
sql += ` AND p.depth <= $${paramIndex}`;
|
|
|
|
|
|
params.push(max_depth);
|
|
|
|
|
|
paramIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sql += ' ORDER BY n.depth, n.created_at DESC';
|
|
|
|
|
|
sql += ` LIMIT $${paramIndex}`;
|
|
|
|
|
|
params.push(Math.min(parseInt(limit || '100', 10), 500));
|
|
|
|
|
|
|
|
|
|
|
|
const result = await db.query(sql, params);
|
|
|
|
|
|
return { nodes: result.rows, count: result.rows.length };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 沿树杈回溯 — 从叶子到根的路径
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function tracePath(input) {
|
|
|
|
|
|
const { node_id } = input;
|
|
|
|
|
|
if (!node_id) throw new Error('缺少 node_id');
|
|
|
|
|
|
|
|
|
|
|
|
const result = await db.query(
|
|
|
|
|
|
`SELECT n.*
|
|
|
|
|
|
FROM light_tree_nodes n
|
|
|
|
|
|
JOIN light_tree_paths p ON n.id = p.ancestor_id
|
|
|
|
|
|
WHERE p.descendant_id = $1
|
|
|
|
|
|
ORDER BY p.depth DESC`,
|
|
|
|
|
|
[node_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
path: result.rows,
|
|
|
|
|
|
depth: result.rows.length - 1,
|
|
|
|
|
|
root: result.rows.length > 0 ? result.rows[0] : null,
|
|
|
|
|
|
leaf: result.rows.length > 0 ? result.rows[result.rows.length - 1] : null
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取人格体的一级分支及其直接子节点
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getPersonaBranch(input) {
|
|
|
|
|
|
const { persona_id } = input;
|
|
|
|
|
|
if (!persona_id) throw new Error('缺少 persona_id');
|
|
|
|
|
|
|
|
|
|
|
|
// 获取一级分支
|
|
|
|
|
|
const branch = await db.query(
|
|
|
|
|
|
`SELECT * FROM light_tree_nodes WHERE persona_id = $1 AND depth = 1`,
|
|
|
|
|
|
[persona_id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (branch.rows.length === 0) {
|
|
|
|
|
|
return { branch: null, children: [], message: `人格体 ${persona_id} 没有一级分支` };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取直接子节点(最近的20个)
|
|
|
|
|
|
const children = await db.query(
|
|
|
|
|
|
`SELECT * FROM light_tree_nodes WHERE parent_id = $1 ORDER BY created_at DESC LIMIT 20`,
|
|
|
|
|
|
[branch.rows[0].id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 统计树的总节点数
|
|
|
|
|
|
const stats = await db.query(
|
|
|
|
|
|
`SELECT COUNT(*) as total,
|
|
|
|
|
|
COUNT(*) FILTER (WHERE node_type = 'branch') as branches,
|
|
|
|
|
|
COUNT(*) FILTER (WHERE node_type = 'leaf') as leaves,
|
|
|
|
|
|
COUNT(*) FILTER (WHERE node_type = 'bloom') as blooms,
|
|
|
|
|
|
COUNT(*) FILTER (WHERE node_type = 'bud') as buds
|
|
|
|
|
|
FROM light_tree_nodes
|
|
|
|
|
|
JOIN light_tree_paths ON light_tree_nodes.id = light_tree_paths.descendant_id
|
|
|
|
|
|
WHERE light_tree_paths.ancestor_id = $1 AND light_tree_paths.depth > 0`,
|
|
|
|
|
|
[branch.rows[0].id]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
branch: branch.rows[0],
|
|
|
|
|
|
children: children.rows,
|
|
|
|
|
|
stats: {
|
|
|
|
|
|
total: parseInt(stats.rows[0].total, 10),
|
|
|
|
|
|
branches: parseInt(stats.rows[0].branches, 10),
|
|
|
|
|
|
leaves: parseInt(stats.rows[0].leaves, 10),
|
|
|
|
|
|
blooms: parseInt(stats.rows[0].blooms, 10),
|
|
|
|
|
|
buds: parseInt(stats.rows[0].buds, 10)
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取人格体最近的叶子(记忆)— 唤醒时使用
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getRecentLeaves(input) {
|
|
|
|
|
|
const { persona_id, limit } = input;
|
|
|
|
|
|
if (!persona_id) throw new Error('缺少 persona_id');
|
|
|
|
|
|
|
|
|
|
|
|
const result = await db.query(
|
|
|
|
|
|
`SELECT * FROM light_tree_nodes
|
|
|
|
|
|
WHERE persona_id = $1 AND node_type IN ('leaf', 'bloom')
|
|
|
|
|
|
ORDER BY created_at DESC
|
|
|
|
|
|
LIMIT $2`,
|
|
|
|
|
|
[persona_id, Math.min(parseInt(limit || '3', 10), 20)]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return { leaves: result.rows, count: result.rows.length };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── 天眼 · SYSLOG操作 ───
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 写入SYSLOG — Agent每次执行后调用
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function writeSyslog(input) {
|
|
|
|
|
|
const { agent_id, persona_id, action, result, message, details, duration_ms, tree_node_id } = input;
|
|
|
|
|
|
if (!agent_id || !action || !result) {
|
|
|
|
|
|
throw new Error('缺少必填字段: agent_id, action, result');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const logResult = await db.query(
|
|
|
|
|
|
`INSERT INTO tianyan_syslog (agent_id, persona_id, action, result, message, details, duration_ms, tree_node_id)
|
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6::jsonb, $7, $8)
|
|
|
|
|
|
RETURNING *`,
|
|
|
|
|
|
[agent_id, persona_id || null, action, result,
|
|
|
|
|
|
message || null, JSON.stringify(details || {}),
|
|
|
|
|
|
duration_ms || null, tree_node_id || null]
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return { syslog: logResult.rows[0] };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取天眼涌现视图 — 全局系统感知
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getTianyanView(input) {
|
|
|
|
|
|
// 先尝试刷新物化视图
|
|
|
|
|
|
try {
|
|
|
|
|
|
await db.query('REFRESH MATERIALIZED VIEW tianyan_global_view');
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
// 首次运行或并发刷新时可能失败,不影响查询
|
2026-04-09 12:35:07 +08:00
|
|
|
|
if (err.message && !err.message.includes('has not been populated')) {
|
|
|
|
|
|
console.warn(`[光之树] 天眼视图刷新警告: ${err.message}`);
|
|
|
|
|
|
}
|
2026-04-09 12:31:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const viewResult = await db.query('SELECT * FROM tianyan_global_view');
|
|
|
|
|
|
|
|
|
|
|
|
// 补充: 最近10条SYSLOG
|
|
|
|
|
|
const recentLogs = await db.query(
|
|
|
|
|
|
'SELECT * FROM tianyan_syslog ORDER BY created_at DESC LIMIT 10'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 补充: 光之树根节点信息
|
|
|
|
|
|
const root = await db.query(
|
|
|
|
|
|
`SELECT id, title, path, created_at FROM light_tree_nodes WHERE node_type = 'root' LIMIT 1`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 补充: 各人格体分支状态
|
|
|
|
|
|
const branches = await db.query(
|
|
|
|
|
|
`SELECT n.id, n.persona_id, n.title, n.node_type, n.path, n.created_at,
|
|
|
|
|
|
(SELECT COUNT(*) FROM light_tree_paths WHERE ancestor_id = n.id AND depth > 0) as descendant_count
|
|
|
|
|
|
FROM light_tree_nodes n
|
|
|
|
|
|
WHERE n.depth = 1
|
|
|
|
|
|
ORDER BY n.persona_id`
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
tianyan: viewResult.rows.length > 0 ? viewResult.rows[0] : { note: '天眼视图尚无数据' },
|
|
|
|
|
|
recent_syslog: recentLogs.rows,
|
|
|
|
|
|
tree_root: root.rows.length > 0 ? root.rows[0] : null,
|
|
|
|
|
|
persona_branches: branches.rows,
|
|
|
|
|
|
timestamp: new Date().toISOString()
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 查询SYSLOG — 按Agent/人格体/时间筛选
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function querySyslog(input) {
|
|
|
|
|
|
const { agent_id, persona_id, result_filter, start_time, end_time, limit } = input;
|
|
|
|
|
|
|
|
|
|
|
|
let sql = 'SELECT * FROM tianyan_syslog WHERE 1=1';
|
|
|
|
|
|
const params = [];
|
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
|
|
if (agent_id) {
|
|
|
|
|
|
sql += ` AND agent_id = $${paramIndex}`;
|
|
|
|
|
|
params.push(agent_id);
|
|
|
|
|
|
paramIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (persona_id) {
|
|
|
|
|
|
sql += ` AND persona_id = $${paramIndex}`;
|
|
|
|
|
|
params.push(persona_id);
|
|
|
|
|
|
paramIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (result_filter) {
|
|
|
|
|
|
sql += ` AND result = $${paramIndex}`;
|
|
|
|
|
|
params.push(result_filter);
|
|
|
|
|
|
paramIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (start_time) {
|
|
|
|
|
|
sql += ` AND created_at >= $${paramIndex}`;
|
|
|
|
|
|
params.push(start_time);
|
|
|
|
|
|
paramIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (end_time) {
|
|
|
|
|
|
sql += ` AND created_at <= $${paramIndex}`;
|
|
|
|
|
|
params.push(end_time);
|
|
|
|
|
|
paramIndex++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
sql += ' ORDER BY created_at DESC';
|
|
|
|
|
|
sql += ` LIMIT $${paramIndex}`;
|
|
|
|
|
|
params.push(Math.min(parseInt(limit || '50', 10), 200));
|
|
|
|
|
|
|
|
|
|
|
|
const queryResult = await db.query(sql, params);
|
|
|
|
|
|
return { syslog: queryResult.rows, count: queryResult.rows.length };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─── 导出 ───
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
|
// 光之树 · 生长
|
|
|
|
|
|
growBranch,
|
|
|
|
|
|
growLeaf,
|
|
|
|
|
|
growBloom,
|
|
|
|
|
|
// 光之树 · 查询
|
|
|
|
|
|
getTreeNode,
|
|
|
|
|
|
getSubtree,
|
|
|
|
|
|
tracePath,
|
|
|
|
|
|
getPersonaBranch,
|
|
|
|
|
|
getRecentLeaves,
|
|
|
|
|
|
// 天眼 · SYSLOG
|
|
|
|
|
|
writeSyslog,
|
|
|
|
|
|
getTianyanView,
|
|
|
|
|
|
querySyslog
|
|
|
|
|
|
};
|